From 553224ad35128788d0089e77fdb95b8fbb83a0c7 Mon Sep 17 00:00:00 2001 From: Dongze Li Date: Mon, 19 Feb 2024 10:28:21 +0800 Subject: [PATCH] Add graphs info in deployment interface --- flex/coordinator/.openapi-generator/FILES | 1 + .../core/client_wrapper.py | 90 ++++++++++-- .../gs_flex_coordinator/core/config.py | 5 + .../gs_flex_coordinator/core/utils.py | 54 ++++++- .../gs_flex_coordinator/models/__init__.py | 1 + .../models/deployment_info.py | 34 ++++- .../deployment_info_graphs_info_value.py | 139 ++++++++++++++++++ .../gs_flex_coordinator/openapi/openapi.yaml | 32 ++++ flex/openapi/openapi_coordinator.yaml | 13 ++ python/graphscope/flex/rest/__init__.py | 1 + .../graphscope/flex/rest/models/__init__.py | 1 + .../flex/rest/models/deployment_info.py | 19 ++- .../deployment_info_graphs_info_value.py | 94 ++++++++++++ 13 files changed, 469 insertions(+), 15 deletions(-) create mode 100644 flex/coordinator/gs_flex_coordinator/models/deployment_info_graphs_info_value.py create mode 100644 python/graphscope/flex/rest/models/deployment_info_graphs_info_value.py diff --git a/flex/coordinator/.openapi-generator/FILES b/flex/coordinator/.openapi-generator/FILES index ec8eef479551..1f61972423f8 100644 --- a/flex/coordinator/.openapi-generator/FILES +++ b/flex/coordinator/.openapi-generator/FILES @@ -16,6 +16,7 @@ gs_flex_coordinator/models/column_mapping.py gs_flex_coordinator/models/connection.py gs_flex_coordinator/models/connection_status.py gs_flex_coordinator/models/deployment_info.py +gs_flex_coordinator/models/deployment_info_graphs_info_value.py gs_flex_coordinator/models/deployment_status.py gs_flex_coordinator/models/edge_mapping.py gs_flex_coordinator/models/edge_mapping_destination_vertex_mappings_inner.py diff --git a/flex/coordinator/gs_flex_coordinator/core/client_wrapper.py b/flex/coordinator/gs_flex_coordinator/core/client_wrapper.py index 99b0c4d50f88..9f5cce451a44 100644 --- a/flex/coordinator/gs_flex_coordinator/core/client_wrapper.py +++ b/flex/coordinator/gs_flex_coordinator/core/client_wrapper.py @@ -19,19 +19,40 @@ import datetime import itertools import logging +import os +import pickle import socket import threading from typing import List, Union import psutil -from gs_flex_coordinator.core.config import (CLUSTER_TYPE, INSTANCE_NAME, - SOLUTION) + +from gs_flex_coordinator.core.config import ( + CLUSTER_TYPE, + COORDINATOR_STARTING_TIME, + INSTANCE_NAME, + SOLUTION, + WORKSPACE, +) from gs_flex_coordinator.core.interactive import init_hqps_client -from gs_flex_coordinator.core.utils import encode_datetime -from gs_flex_coordinator.models import (DeploymentInfo, Graph, JobStatus, - ModelSchema, NodeStatus, Procedure, - SchemaMapping, ServiceStatus, - StartServiceRequest) +from gs_flex_coordinator.core.scheduler import schedule +from gs_flex_coordinator.core.utils import ( + GraphInfo, + decode_datetimestr, + encode_datetime, + get_current_time, +) +from gs_flex_coordinator.models import ( + DeploymentInfo, + Graph, + JobStatus, + ModelSchema, + NodeStatus, + Procedure, + SchemaMapping, + ServiceStatus, + StartServiceRequest, +) from gs_flex_coordinator.version import __version__ logger = logging.getLogger("graphscope") @@ -45,6 +66,34 @@ def __init__(self): self._lock = threading.RLock() # initialize specific client self._client = self._initialize_client() + # graphs info + self._graphs_info = {} + # pickle path + self._pickle_path = os.path.join(WORKSPACE, "graphs_info.pickle") + # recover + self._try_to_recover_from_disk() + + def _try_to_recover_from_disk(self): + try: + if os.path.exists(self._pickle_path): + logger.info("Recover graphs info from file %s", self._pickle_path) + with open(self._pickle_path, "rb") as f: + self._graphs_info = pickle.load(f) + except Exception as e: + logger.warn("Failed to recover graphs info: %s", str(e)) + # set default graph info + for g in self.list_graphs(): + if g.name not in self._graphs_info: + self._graphs_info[g.name] = GraphInfo( + name=g.name, creation_time=COORDINATOR_STARTING_TIME + ) + + def _pickle_graphs_info_impl(self): + try: + with open(self._pickle_path, "wb") as f: + pickle.dump(self._graphs_info, f) + except Exception as e: + logger.warn("Failed to dump graphs info: %s", str(e)) def _initialize_client(self): service_initializer = {"INTERACTIVE": init_hqps_client} @@ -73,10 +122,18 @@ def create_graph(self, graph: Graph) -> str: graph_dict = graph.to_dict() if "_schema" in graph_dict: graph_dict["schema"] = graph_dict.pop("_schema") - return self._client.create_graph(graph_dict) + rlt = self._client.create_graph(graph_dict) + self._graphs_info[graph.name] = GraphInfo( + name=graph.name, creation_time=get_current_time() + ) + self._pickle_graphs_info_impl() + return rlt def delete_graph_by_name(self, graph_name: str) -> str: - return self._client.delete_graph_by_name(graph_name) + rlt = self._client.delete_graph_by_name(graph_name) + del self._graphs_info[graph_name] + self._pickle_graphs_info_impl() + return rlt def create_procedure(self, graph_name: str, procedure: Procedure) -> str: procedure_dict = procedure.to_dict() @@ -111,10 +168,25 @@ def get_node_status(self) -> List[NodeStatus]: return rlt def get_deployment_info(self) -> DeploymentInfo: + # update graphs info + for job in self.list_jobs(): + if ( + job.detail["graph_name"] in self._graphs_info + and job.end_time is not None + ): + self._graphs_info[job.detail["graph_name"]].last_dataloading_time = ( + decode_datetimestr(job.end_time) + ) + self._pickle_graphs_info_impl() + graphs_info = {} + for name, info in self._graphs_info.items(): + graphs_info[name] = info.to_dict() info = { "name": INSTANCE_NAME, "cluster_type": CLUSTER_TYPE, "version": __version__, + "solution": SOLUTION, + "graphs_info": graphs_info, } return DeploymentInfo.from_dict(info) diff --git a/flex/coordinator/gs_flex_coordinator/core/config.py b/flex/coordinator/gs_flex_coordinator/core/config.py index 1a6c940a0367..44e7cd5c739f 100644 --- a/flex/coordinator/gs_flex_coordinator/core/config.py +++ b/flex/coordinator/gs_flex_coordinator/core/config.py @@ -16,6 +16,7 @@ # limitations under the License. # +import datetime import logging import os import tempfile @@ -79,3 +80,7 @@ def config_logging(log_level: str): # interactive configuration HQPS_ADMIN_SERVICE_PORT = os.environ.get("HIACTOR_ADMIN_SERVICE_PORT", 7777) + + +# coordinator starting time +COORDINATOR_STARTING_TIME = datetime.datetime.now() diff --git a/flex/coordinator/gs_flex_coordinator/core/utils.py b/flex/coordinator/gs_flex_coordinator/core/utils.py index e4b3b3b18da3..0c5d3e93efc8 100644 --- a/flex/coordinator/gs_flex_coordinator/core/utils.py +++ b/flex/coordinator/gs_flex_coordinator/core/utils.py @@ -20,11 +20,12 @@ import functools import logging import random -import requests import socket import string from typing import Union +import requests + logger = logging.getLogger("graphscope") @@ -74,6 +75,10 @@ def random_string(nlen): return "".join([random.choice(string.ascii_lowercase) for _ in range(nlen)]) +def get_current_time() -> datetime.datetime: + return datetime.datetime.now() + + def str_to_bool(s): if isinstance(s, bool): return s @@ -97,3 +102,50 @@ def get_public_ip() -> Union[str, None]: except requests.exceptions.RequestException as e: logger.warn("Failed to get public ip: %s", str(e)) return None + + +class GraphInfo(object): + def __init__( + self, name, creation_time, update_time=None, last_dataloading_time=None + ): + self._name = name + self._creation_time = creation_time + self._update_time = update_time + if self._update_time is None: + self._update_time = self._creation_time + self._last_dataloading_time = last_dataloading_time + + @property + def name(self): + return self._name + + @property + def creation_time(self): + return self._creation_time + + @property + def update_time(self): + return self._update_time + + @property + def last_dataloading_time(self): + return self._last_dataloading_time + + @update_time.setter + def update_time(self, new_time): + self._update_time = new_time + + @last_dataloading_time.setter + def last_dataloading_time(self, new_time): + if self._last_dataloading_time is None: + self._last_dataloading_time = new_time + elif new_time > self._last_dataloading_time: + self._last_dataloading_time = new_time + + def to_dict(self): + return { + "name": self._name, + "creation_time": encode_datetime(self._creation_time), + "update_time": encode_datetime(self._update_time), + "last_dataloading_time": encode_datetime(self._last_dataloading_time), + } diff --git a/flex/coordinator/gs_flex_coordinator/models/__init__.py b/flex/coordinator/gs_flex_coordinator/models/__init__.py index 4d718329279b..d92d12df56c0 100644 --- a/flex/coordinator/gs_flex_coordinator/models/__init__.py +++ b/flex/coordinator/gs_flex_coordinator/models/__init__.py @@ -7,6 +7,7 @@ from gs_flex_coordinator.models.connection import Connection from gs_flex_coordinator.models.connection_status import ConnectionStatus from gs_flex_coordinator.models.deployment_info import DeploymentInfo +from gs_flex_coordinator.models.deployment_info_graphs_info_value import DeploymentInfoGraphsInfoValue from gs_flex_coordinator.models.deployment_status import DeploymentStatus from gs_flex_coordinator.models.edge_mapping import EdgeMapping from gs_flex_coordinator.models.edge_mapping_destination_vertex_mappings_inner import EdgeMappingDestinationVertexMappingsInner diff --git a/flex/coordinator/gs_flex_coordinator/models/deployment_info.py b/flex/coordinator/gs_flex_coordinator/models/deployment_info.py index 3ebcf18878da..62800ecc0a96 100644 --- a/flex/coordinator/gs_flex_coordinator/models/deployment_info.py +++ b/flex/coordinator/gs_flex_coordinator/models/deployment_info.py @@ -3,8 +3,10 @@ from typing import List, Dict # noqa: F401 from gs_flex_coordinator.models.base_model import Model +from gs_flex_coordinator.models.deployment_info_graphs_info_value import DeploymentInfoGraphsInfoValue from gs_flex_coordinator import util +from gs_flex_coordinator.models.deployment_info_graphs_info_value import DeploymentInfoGraphsInfoValue # noqa: E501 class DeploymentInfo(Model): """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). @@ -12,7 +14,7 @@ class DeploymentInfo(Model): Do not edit the class manually. """ - def __init__(self, name=None, cluster_type=None, version=None, solution=None): # noqa: E501 + def __init__(self, name=None, cluster_type=None, version=None, solution=None, graphs_info=None): # noqa: E501 """DeploymentInfo - a model defined in OpenAPI :param name: The name of this DeploymentInfo. # noqa: E501 @@ -23,25 +25,30 @@ def __init__(self, name=None, cluster_type=None, version=None, solution=None): :type version: str :param solution: The solution of this DeploymentInfo. # noqa: E501 :type solution: str + :param graphs_info: The graphs_info of this DeploymentInfo. # noqa: E501 + :type graphs_info: Dict[str, DeploymentInfoGraphsInfoValue] """ self.openapi_types = { 'name': str, 'cluster_type': str, 'version': str, - 'solution': str + 'solution': str, + 'graphs_info': Dict[str, DeploymentInfoGraphsInfoValue] } self.attribute_map = { 'name': 'name', 'cluster_type': 'cluster_type', 'version': 'version', - 'solution': 'solution' + 'solution': 'solution', + 'graphs_info': 'graphs_info' } self._name = name self._cluster_type = cluster_type self._version = version self._solution = solution + self._graphs_info = graphs_info @classmethod def from_dict(cls, dikt) -> 'DeploymentInfo': @@ -149,3 +156,24 @@ def solution(self, solution: str): ) self._solution = solution + + @property + def graphs_info(self) -> Dict[str, DeploymentInfoGraphsInfoValue]: + """Gets the graphs_info of this DeploymentInfo. + + + :return: The graphs_info of this DeploymentInfo. + :rtype: Dict[str, DeploymentInfoGraphsInfoValue] + """ + return self._graphs_info + + @graphs_info.setter + def graphs_info(self, graphs_info: Dict[str, DeploymentInfoGraphsInfoValue]): + """Sets the graphs_info of this DeploymentInfo. + + + :param graphs_info: The graphs_info of this DeploymentInfo. + :type graphs_info: Dict[str, DeploymentInfoGraphsInfoValue] + """ + + self._graphs_info = graphs_info diff --git a/flex/coordinator/gs_flex_coordinator/models/deployment_info_graphs_info_value.py b/flex/coordinator/gs_flex_coordinator/models/deployment_info_graphs_info_value.py new file mode 100644 index 000000000000..639d1c5d593e --- /dev/null +++ b/flex/coordinator/gs_flex_coordinator/models/deployment_info_graphs_info_value.py @@ -0,0 +1,139 @@ +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from gs_flex_coordinator.models.base_model import Model +from gs_flex_coordinator import util + + +class DeploymentInfoGraphsInfoValue(Model): + """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + + Do not edit the class manually. + """ + + def __init__(self, name=None, creation_time=None, update_time=None, last_dataloading_time=None): # noqa: E501 + """DeploymentInfoGraphsInfoValue - a model defined in OpenAPI + + :param name: The name of this DeploymentInfoGraphsInfoValue. # noqa: E501 + :type name: str + :param creation_time: The creation_time of this DeploymentInfoGraphsInfoValue. # noqa: E501 + :type creation_time: str + :param update_time: The update_time of this DeploymentInfoGraphsInfoValue. # noqa: E501 + :type update_time: str + :param last_dataloading_time: The last_dataloading_time of this DeploymentInfoGraphsInfoValue. # noqa: E501 + :type last_dataloading_time: str + """ + self.openapi_types = { + 'name': str, + 'creation_time': str, + 'update_time': str, + 'last_dataloading_time': str + } + + self.attribute_map = { + 'name': 'name', + 'creation_time': 'creation_time', + 'update_time': 'update_time', + 'last_dataloading_time': 'last_dataloading_time' + } + + self._name = name + self._creation_time = creation_time + self._update_time = update_time + self._last_dataloading_time = last_dataloading_time + + @classmethod + def from_dict(cls, dikt) -> 'DeploymentInfoGraphsInfoValue': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The DeploymentInfo_graphs_info_value of this DeploymentInfoGraphsInfoValue. # noqa: E501 + :rtype: DeploymentInfoGraphsInfoValue + """ + return util.deserialize_model(dikt, cls) + + @property + def name(self) -> str: + """Gets the name of this DeploymentInfoGraphsInfoValue. + + + :return: The name of this DeploymentInfoGraphsInfoValue. + :rtype: str + """ + return self._name + + @name.setter + def name(self, name: str): + """Sets the name of this DeploymentInfoGraphsInfoValue. + + + :param name: The name of this DeploymentInfoGraphsInfoValue. + :type name: str + """ + + self._name = name + + @property + def creation_time(self) -> str: + """Gets the creation_time of this DeploymentInfoGraphsInfoValue. + + + :return: The creation_time of this DeploymentInfoGraphsInfoValue. + :rtype: str + """ + return self._creation_time + + @creation_time.setter + def creation_time(self, creation_time: str): + """Sets the creation_time of this DeploymentInfoGraphsInfoValue. + + + :param creation_time: The creation_time of this DeploymentInfoGraphsInfoValue. + :type creation_time: str + """ + + self._creation_time = creation_time + + @property + def update_time(self) -> str: + """Gets the update_time of this DeploymentInfoGraphsInfoValue. + + + :return: The update_time of this DeploymentInfoGraphsInfoValue. + :rtype: str + """ + return self._update_time + + @update_time.setter + def update_time(self, update_time: str): + """Sets the update_time of this DeploymentInfoGraphsInfoValue. + + + :param update_time: The update_time of this DeploymentInfoGraphsInfoValue. + :type update_time: str + """ + + self._update_time = update_time + + @property + def last_dataloading_time(self) -> str: + """Gets the last_dataloading_time of this DeploymentInfoGraphsInfoValue. + + + :return: The last_dataloading_time of this DeploymentInfoGraphsInfoValue. + :rtype: str + """ + return self._last_dataloading_time + + @last_dataloading_time.setter + def last_dataloading_time(self, last_dataloading_time: str): + """Sets the last_dataloading_time of this DeploymentInfoGraphsInfoValue. + + + :param last_dataloading_time: The last_dataloading_time of this DeploymentInfoGraphsInfoValue. + :type last_dataloading_time: str + """ + + self._last_dataloading_time = last_dataloading_time diff --git a/flex/coordinator/gs_flex_coordinator/openapi/openapi.yaml b/flex/coordinator/gs_flex_coordinator/openapi/openapi.yaml index 240e9f0e5689..ec81a42dc8f8 100644 --- a/flex/coordinator/gs_flex_coordinator/openapi/openapi.yaml +++ b/flex/coordinator/gs_flex_coordinator/openapi/openapi.yaml @@ -1313,6 +1313,12 @@ components: x-body-name: procedure DeploymentInfo: example: + graphs_info: + key: + creation_time: creation_time + update_time: update_time + last_dataloading_time: last_dataloading_time + name: name solution: INTERACTIVE name: name cluster_type: HOSTS @@ -1336,6 +1342,11 @@ components: - GRAPHSCOPE_INSIGHT title: solution type: string + graphs_info: + additionalProperties: + $ref: '#/components/schemas/DeploymentInfo_graphs_info_value' + title: graphs_info + type: object title: DeploymentInfo type: object x-body-name: deployment_info @@ -2194,6 +2205,27 @@ components: type: string title: Procedure_params_inner type: object + DeploymentInfo_graphs_info_value: + example: + creation_time: creation_time + update_time: update_time + last_dataloading_time: last_dataloading_time + name: name + properties: + name: + title: name + type: string + creation_time: + title: creation_time + type: string + update_time: + title: update_time + type: string + last_dataloading_time: + title: last_dataloading_time + type: string + title: DeploymentInfo_graphs_info_value + type: object ServiceStatus_sdk_endpoints: example: gremlin: gremlin diff --git a/flex/openapi/openapi_coordinator.yaml b/flex/openapi/openapi_coordinator.yaml index 37d7ed8c73d2..c02857beedba 100644 --- a/flex/openapi/openapi_coordinator.yaml +++ b/flex/openapi/openapi_coordinator.yaml @@ -1030,6 +1030,19 @@ components: enum: - INTERACTIVE - GRAPHSCOPE_INSIGHT + graphs_info: + type: object + additionalProperties: + type: object + properties: + name: + type: string + creation_time: + type: string + update_time: + type: string + last_dataloading_time: + type: string DeploymentStatus: x-body-name: deployment_status type: object diff --git a/python/graphscope/flex/rest/__init__.py b/python/graphscope/flex/rest/__init__.py index f21cb5e5c026..f57d515623c0 100644 --- a/python/graphscope/flex/rest/__init__.py +++ b/python/graphscope/flex/rest/__init__.py @@ -47,6 +47,7 @@ from graphscope.flex.rest.models.connection import Connection from graphscope.flex.rest.models.connection_status import ConnectionStatus from graphscope.flex.rest.models.deployment_info import DeploymentInfo +from graphscope.flex.rest.models.deployment_info_graphs_info_value import DeploymentInfoGraphsInfoValue from graphscope.flex.rest.models.deployment_status import DeploymentStatus from graphscope.flex.rest.models.edge_mapping import EdgeMapping from graphscope.flex.rest.models.edge_mapping_destination_vertex_mappings_inner import EdgeMappingDestinationVertexMappingsInner diff --git a/python/graphscope/flex/rest/models/__init__.py b/python/graphscope/flex/rest/models/__init__.py index 7e9609b5acf0..c7c6deab9a38 100644 --- a/python/graphscope/flex/rest/models/__init__.py +++ b/python/graphscope/flex/rest/models/__init__.py @@ -22,6 +22,7 @@ from graphscope.flex.rest.models.connection import Connection from graphscope.flex.rest.models.connection_status import ConnectionStatus from graphscope.flex.rest.models.deployment_info import DeploymentInfo +from graphscope.flex.rest.models.deployment_info_graphs_info_value import DeploymentInfoGraphsInfoValue from graphscope.flex.rest.models.deployment_status import DeploymentStatus from graphscope.flex.rest.models.edge_mapping import EdgeMapping from graphscope.flex.rest.models.edge_mapping_destination_vertex_mappings_inner import EdgeMappingDestinationVertexMappingsInner diff --git a/python/graphscope/flex/rest/models/deployment_info.py b/python/graphscope/flex/rest/models/deployment_info.py index e0957be536db..c8b6843296cb 100644 --- a/python/graphscope/flex/rest/models/deployment_info.py +++ b/python/graphscope/flex/rest/models/deployment_info.py @@ -20,6 +20,7 @@ from pydantic import BaseModel, StrictStr, field_validator from typing import Any, ClassVar, Dict, List, Optional +from graphscope.flex.rest.models.deployment_info_graphs_info_value import DeploymentInfoGraphsInfoValue from typing import Optional, Set from typing_extensions import Self @@ -31,7 +32,8 @@ class DeploymentInfo(BaseModel): cluster_type: Optional[StrictStr] = None version: Optional[StrictStr] = None solution: Optional[StrictStr] = None - __properties: ClassVar[List[str]] = ["name", "cluster_type", "version", "solution"] + graphs_info: Optional[Dict[str, DeploymentInfoGraphsInfoValue]] = None + __properties: ClassVar[List[str]] = ["name", "cluster_type", "version", "solution", "graphs_info"] @field_validator('cluster_type') def cluster_type_validate_enum(cls, value): @@ -92,6 +94,13 @@ def to_dict(self) -> Dict[str, Any]: exclude=excluded_fields, exclude_none=True, ) + # override the default output from pydantic by calling `to_dict()` of each value in graphs_info (dict) + _field_dict = {} + if self.graphs_info: + for _key in self.graphs_info: + if self.graphs_info[_key]: + _field_dict[_key] = self.graphs_info[_key].to_dict() + _dict['graphs_info'] = _field_dict return _dict @classmethod @@ -107,7 +116,13 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "name": obj.get("name"), "cluster_type": obj.get("cluster_type"), "version": obj.get("version"), - "solution": obj.get("solution") + "solution": obj.get("solution"), + "graphs_info": dict( + (_k, DeploymentInfoGraphsInfoValue.from_dict(_v)) + for _k, _v in obj["graphs_info"].items() + ) + if obj.get("graphs_info") is not None + else None }) return _obj diff --git a/python/graphscope/flex/rest/models/deployment_info_graphs_info_value.py b/python/graphscope/flex/rest/models/deployment_info_graphs_info_value.py new file mode 100644 index 000000000000..5c925517d3dd --- /dev/null +++ b/python/graphscope/flex/rest/models/deployment_info_graphs_info_value.py @@ -0,0 +1,94 @@ +# coding: utf-8 + +""" + GraphScope FLEX HTTP SERVICE API + + This is a specification for GraphScope FLEX HTTP service based on the OpenAPI 3.0 specification. You can find out more details about specification at [doc](https://swagger.io/specification/v3/). Some useful links: - [GraphScope Repository](https://github.com/alibaba/GraphScope) - [The Source API definition for GraphScope Interactive](https://github.com/GraphScope/portal/tree/main/httpservice) + + The version of the OpenAPI document: 0.9.1 + Contact: graphscope@alibaba-inc.com + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, StrictStr +from typing import Any, ClassVar, Dict, List, Optional +from typing import Optional, Set +from typing_extensions import Self + +class DeploymentInfoGraphsInfoValue(BaseModel): + """ + DeploymentInfoGraphsInfoValue + """ # noqa: E501 + name: Optional[StrictStr] = None + creation_time: Optional[StrictStr] = None + update_time: Optional[StrictStr] = None + last_dataloading_time: Optional[StrictStr] = None + __properties: ClassVar[List[str]] = ["name", "creation_time", "update_time", "last_dataloading_time"] + + model_config = { + "populate_by_name": True, + "validate_assignment": True, + "protected_namespaces": (), + } + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of DeploymentInfoGraphsInfoValue from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of DeploymentInfoGraphsInfoValue from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "name": obj.get("name"), + "creation_time": obj.get("creation_time"), + "update_time": obj.get("update_time"), + "last_dataloading_time": obj.get("last_dataloading_time") + }) + return _obj + +