From c3c0d1b0d85ecbcb4032479149197d58f564c78c Mon Sep 17 00:00:00 2001 From: Pierre Gronlier <pierre@gronlier.fr> Date: Mon, 13 Jan 2025 15:56:49 +0100 Subject: [PATCH] feat: vendor jsonasobj2 --- Makefile | 2 + README.md | 10 + linkml_runtime/dumpers/json_dumper.py | 2 +- linkml_runtime/linkml_model/annotations.py | 2 +- linkml_runtime/linkml_model/datasets.py | 2 +- linkml_runtime/linkml_model/extensions.py | 2 +- linkml_runtime/linkml_model/mappings.py | 2 +- linkml_runtime/linkml_model/meta.py | 2 +- linkml_runtime/linkml_model/types.py | 2 +- linkml_runtime/linkml_model/units.py | 2 +- linkml_runtime/linkml_model/validation.py | 2 +- linkml_runtime/loaders/loader_root.py | 4 +- .../processing/validation_datamodel.py | 2 +- linkml_runtime/utils/context_utils.py | 2 +- linkml_runtime/utils/formatutils.py | 2 +- linkml_runtime/utils/inference_utils.py | 2 +- linkml_runtime/utils/jsonasobj2/__init__.py | 6 + linkml_runtime/utils/jsonasobj2/_jsonobj.py | 313 ++++++++++++++++++ .../utils/jsonasobj2/extendednamespace.py | 49 +++ linkml_runtime/utils/yamlutils.py | 4 +- pyproject.toml | 1 - tests/support/filters.py | 2 +- tests/test_index/model/container_test.py | 2 +- tests/test_issues/input/issue_355.py | 2 +- tests/test_issues/models/linkml_issue_576.py | 2 +- tests/test_issues/models/model_817.py | 2 +- tests/test_issues/test_issue_355.py | 2 +- .../models/books_normalized.py | 2 +- .../test_loaders_dumpers/models/enum_model.py | 2 +- .../models/node_object.py | 2 +- .../test_loaders_dumpers/models/personinfo.py | 2 +- .../models/personinfo_test_issue_429.py | 2 +- .../models/phenopackets.py | 2 +- .../test_csv_tsv_loader_dumper.py | 2 +- tests/test_utils/model/inference_example.py | 2 +- tests/test_utils/test_context_utils.py | 2 +- tests/test_utils/test_formatutils.py | 2 +- .../test_utils/test_inlined_as_dict_forms.py | 2 +- .../test_utils/test_inlined_as_list_forms.py | 2 +- tests/test_utils/test_metamodelcore.py | 2 +- tests/test_utils/test_schemaview.py | 2 +- tests/test_utils/test_walker_utils.py | 2 +- 42 files changed, 418 insertions(+), 39 deletions(-) create mode 100644 linkml_runtime/utils/jsonasobj2/__init__.py create mode 100644 linkml_runtime/utils/jsonasobj2/_jsonobj.py create mode 100644 linkml_runtime/utils/jsonasobj2/extendednamespace.py diff --git a/Makefile b/Makefile index a7d4a8e2..a19bcc20 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ MODEL_DIR = ../linkml-model/linkml_model/ update_model: cp -pr $(MODEL_DIR)/* linkml_runtime/linkml_model + sed -i 's/from jsonasobj2/from linkml_runtime.utils.jsonasobj2/g' linkml_runtime/linkml_model/*.py test: poetry run pytest @@ -16,3 +17,4 @@ test: # temporary measure until linkml-model is synced linkml_runtime/processing/validation_datamodel.py: linkml_runtime/processing/validation_datamodel.yaml gen-python $< > $@.tmp && mv $@.tmp $@ + sed -i 's/from jsonasobj2/from linkml_runtime.utils.jsonasobj2/g' linkml_runtime/processing/validation_datamodel.py diff --git a/README.md b/README.md index 4020b7c9..65dd747f 100644 --- a/README.md +++ b/README.md @@ -29,3 +29,13 @@ It also includes the [SchemaView](https://linkml.io/linkml/developers/manipulati ## Notebooks See the [notebooks](https://github.com/linkml/linkml-runtime/tree/main/notebooks) folder for examples + +## Development + +### Measure tests code coverage + +```shell +poetry add pytest-coverage +poetry run coverage run -m pytest # run the existing tests, measuring coverage +poetry run coverage html # to generate an HTML, browsable report +``` diff --git a/linkml_runtime/dumpers/json_dumper.py b/linkml_runtime/dumpers/json_dumper.py index 45837f05..20997ad1 100644 --- a/linkml_runtime/dumpers/json_dumper.py +++ b/linkml_runtime/dumpers/json_dumper.py @@ -10,7 +10,7 @@ from linkml_runtime.utils.context_utils import CONTEXTS_PARAM_TYPE from linkml_runtime.utils.formatutils import remove_empty_items from linkml_runtime.utils.yamlutils import YAMLRoot, as_json_object -from jsonasobj2 import JsonObj +from linkml_runtime.utils.jsonasobj2 import JsonObj class JSONDumper(Dumper): diff --git a/linkml_runtime/linkml_model/annotations.py b/linkml_runtime/linkml_model/annotations.py index 9b229b22..1c90198c 100644 --- a/linkml_runtime/linkml_model/annotations.py +++ b/linkml_runtime/linkml_model/annotations.py @@ -8,7 +8,7 @@ import dataclasses import re -from jsonasobj2 import JsonObj, as_dict +from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict from typing import Optional, List, Union, Dict, ClassVar, Any from dataclasses import dataclass diff --git a/linkml_runtime/linkml_model/datasets.py b/linkml_runtime/linkml_model/datasets.py index e8bb1521..6e1b5fbe 100644 --- a/linkml_runtime/linkml_model/datasets.py +++ b/linkml_runtime/linkml_model/datasets.py @@ -8,7 +8,7 @@ import dataclasses import re -from jsonasobj2 import JsonObj, as_dict +from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict from typing import Optional, List, Union, Dict, ClassVar, Any from dataclasses import dataclass diff --git a/linkml_runtime/linkml_model/extensions.py b/linkml_runtime/linkml_model/extensions.py index 3fe8b661..fc23df2a 100644 --- a/linkml_runtime/linkml_model/extensions.py +++ b/linkml_runtime/linkml_model/extensions.py @@ -8,7 +8,7 @@ import dataclasses import re -from jsonasobj2 import JsonObj, as_dict +from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict from typing import Optional, List, Union, Dict, ClassVar, Any from dataclasses import dataclass diff --git a/linkml_runtime/linkml_model/mappings.py b/linkml_runtime/linkml_model/mappings.py index d6135152..d94bec42 100644 --- a/linkml_runtime/linkml_model/mappings.py +++ b/linkml_runtime/linkml_model/mappings.py @@ -8,7 +8,7 @@ import dataclasses import re -from jsonasobj2 import JsonObj, as_dict +from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict from typing import Optional, List, Union, Dict, ClassVar, Any from dataclasses import dataclass diff --git a/linkml_runtime/linkml_model/meta.py b/linkml_runtime/linkml_model/meta.py index fee7f676..bdcad64c 100644 --- a/linkml_runtime/linkml_model/meta.py +++ b/linkml_runtime/linkml_model/meta.py @@ -32,7 +32,7 @@ import dataclasses import re -from jsonasobj2 import JsonObj, as_dict +from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict from typing import Optional, List, Union, Dict, ClassVar, Any from dataclasses import dataclass diff --git a/linkml_runtime/linkml_model/types.py b/linkml_runtime/linkml_model/types.py index b7034f6f..bde71483 100644 --- a/linkml_runtime/linkml_model/types.py +++ b/linkml_runtime/linkml_model/types.py @@ -8,7 +8,7 @@ import dataclasses import re -from jsonasobj2 import JsonObj, as_dict +from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict from typing import Optional, List, Union, Dict, ClassVar, Any from dataclasses import dataclass diff --git a/linkml_runtime/linkml_model/units.py b/linkml_runtime/linkml_model/units.py index 88f68a31..f021a925 100644 --- a/linkml_runtime/linkml_model/units.py +++ b/linkml_runtime/linkml_model/units.py @@ -8,7 +8,7 @@ import dataclasses import re -from jsonasobj2 import JsonObj, as_dict +from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict from typing import Optional, List, Union, Dict, ClassVar, Any from dataclasses import dataclass diff --git a/linkml_runtime/linkml_model/validation.py b/linkml_runtime/linkml_model/validation.py index 222f1511..dd60cb1c 100644 --- a/linkml_runtime/linkml_model/validation.py +++ b/linkml_runtime/linkml_model/validation.py @@ -8,7 +8,7 @@ import dataclasses import re -from jsonasobj2 import JsonObj, as_dict +from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict from typing import Optional, List, Union, Dict, ClassVar, Any from dataclasses import dataclass diff --git a/linkml_runtime/loaders/loader_root.py b/linkml_runtime/loaders/loader_root.py index e690c4b3..d695b221 100644 --- a/linkml_runtime/loaders/loader_root.py +++ b/linkml_runtime/loaders/loader_root.py @@ -4,7 +4,7 @@ from pydantic import BaseModel from hbreader import FileInfo, hbread -from jsonasobj2 import as_dict, JsonObj +from linkml_runtime.utils.jsonasobj2 import as_dict, JsonObj from linkml_runtime.utils.yamlutils import YAMLRoot from linkml_runtime import URI_TO_LOCAL @@ -168,4 +168,4 @@ def _read_source(self, else: data = source - return data \ No newline at end of file + return data diff --git a/linkml_runtime/processing/validation_datamodel.py b/linkml_runtime/processing/validation_datamodel.py index 035835c3..71a27eee 100644 --- a/linkml_runtime/processing/validation_datamodel.py +++ b/linkml_runtime/processing/validation_datamodel.py @@ -9,7 +9,7 @@ import dataclasses import sys import re -from jsonasobj2 import JsonObj, as_dict +from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict from typing import Optional, List, Union, Dict, ClassVar, Any from dataclasses import dataclass from linkml_runtime.linkml_model.meta import ( diff --git a/linkml_runtime/utils/context_utils.py b/linkml_runtime/utils/context_utils.py index 1d28fe1b..25d683cf 100644 --- a/linkml_runtime/utils/context_utils.py +++ b/linkml_runtime/utils/context_utils.py @@ -4,7 +4,7 @@ from typing import Optional, Union, List, Any, Dict, Callable import yaml -from jsonasobj2 import JsonObj, loads +from linkml_runtime.utils.jsonasobj2 import JsonObj, loads CONTEXT_TYPE = Union[str, dict, JsonObj] CONTEXTS_PARAM_TYPE = Optional[Union[CONTEXT_TYPE, List[CONTEXT_TYPE]]] diff --git a/linkml_runtime/utils/formatutils.py b/linkml_runtime/utils/formatutils.py index da77ba73..d871e833 100644 --- a/linkml_runtime/utils/formatutils.py +++ b/linkml_runtime/utils/formatutils.py @@ -3,7 +3,7 @@ from numbers import Number from typing import List, Any, Union -from jsonasobj2 import JsonObj, as_dict, is_list, is_dict, items, as_json_obj +from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict, is_list, is_dict, items, as_json_obj ws_pattern = re.compile(r'\s+') us_pattern = re.compile(r'_+') diff --git a/linkml_runtime/utils/inference_utils.py b/linkml_runtime/utils/inference_utils.py index ea554529..f1a43cb7 100644 --- a/linkml_runtime/utils/inference_utils.py +++ b/linkml_runtime/utils/inference_utils.py @@ -2,7 +2,7 @@ from dataclasses import field, dataclass from enum import Enum from typing import Union, Optional, Any, Dict, Callable -from jsonasobj2 import JsonObj, items +from linkml_runtime.utils.jsonasobj2 import JsonObj, items from linkml_runtime import SchemaView from linkml_runtime.linkml_model import SlotDefinitionName, PermissibleValue, ClassDefinitionName diff --git a/linkml_runtime/utils/jsonasobj2/__init__.py b/linkml_runtime/utils/jsonasobj2/__init__.py new file mode 100644 index 00000000..a05492df --- /dev/null +++ b/linkml_runtime/utils/jsonasobj2/__init__.py @@ -0,0 +1,6 @@ +from ._jsonobj import JsonObj, as_dict, as_json, as_json_obj, get, items, loads, load, setdefault, \ + keys, items, values, JsonTypes, JsonObjTypes, is_dict, is_list +from .extendednamespace import ExtendedNamespace + +__all__ = ['JsonObj', 'ExtendedNamespace', 'is_dict', 'is_list', 'as_dict', 'as_json', 'as_json_obj', 'get', 'items', + 'load', 'loads', 'setdefault', 'keys', 'values', 'JsonTypes', 'JsonObjTypes'] diff --git a/linkml_runtime/utils/jsonasobj2/_jsonobj.py b/linkml_runtime/utils/jsonasobj2/_jsonobj.py new file mode 100644 index 00000000..1df7d0ca --- /dev/null +++ b/linkml_runtime/utils/jsonasobj2/_jsonobj.py @@ -0,0 +1,313 @@ +import json +from typing import Union, List, Dict, Tuple, Optional, Callable, Any, Iterator +from hbreader import hbread + +from .extendednamespace import ExtendedNamespace + +# Possible types in the JsonObj representation +JsonObjTypes = Union["JsonObj", List["JsonObjTypes"], str, bool, int, float, None] + +# Types in the pure JSON representation +JsonTypes = Union[Dict[str, "JsonTypes"], List["JsonTypes"], str, bool, int, float, None] + +# Control variables -- note that subclasses can add to this list +hide = ['_if_missing', '_root'] + + +class JsonObj(ExtendedNamespace): + """ A namespace/dictionary representation of a JSON object. Any name in a JSON object that is a valid python + identifier is represented as a first-class member of the objects. JSON identifiers that begin with "_" are + disallowed in this implementation. + """ + # Set this class variable to False if recursive construction is absolutely necessare (see: test_issue13.py for + # details + _idempotent = True + + def __new__(cls, *args, _if_missing: Callable[["JsonObj", str], Tuple[bool, Any]] = None, **kwargs): + """ Construct a JsonObj from set of keyword/value pairs + + :param list_or_dict: A list or dictionary that can be used to construct the object + :param _if_missing: Function to call if attempt is made to access an undefined value. Function takes JsonObj + instance and parameter as input and returns a tuple -- handled (y or n) and result. If handled is 'n' inline + processing proceeds. + :param kwargs: A dictionary as an alternative constructor. + """ + # This makes JsonObj idempotent + if cls._idempotent and args and isinstance(args[0], JsonObj): + # If we're being called with a single argument + if not kwargs and not args[1:] and\ + (not _if_missing or _if_missing == args[0]._if_missing) and cls == type(args[0]): + return args[0] + obj = super(ExtendedNamespace, cls).__new__(cls) + return obj + + def __init__(self, *args, _if_missing: Callable[["JsonObj", str], Tuple[bool, Any]] = None, **kwargs): + """ Construct a JsonObj from set of keyword/value pairs + + :param list_or_dict: A list or dictionary that can be used to construct the object + :param _if_missing: Function to call if attempt is made to access an undefined value. Function takes JsonObj + instance and parameter as input and returns a tuple -- handled (y or n) and result. If handled is 'n' inline + processing proceeds. + :param kwargs: A dictionary as an alternative constructor. + """ + if args and isinstance(args[0], JsonObj) and not kwargs and not args[1:] and type(self)._idempotent and \ + (not _if_missing or _if_missing == args[0]._if_missing) and type(self) == type(args[0]): + return + + if _if_missing and _if_missing != self._if_missing: + self._if_missing = _if_missing + if args: + if kwargs: + raise TypeError("Constructor can't have both a single item and a dict") + if isinstance(args[0], JsonObj): + pass + elif isinstance(args[0], dict): + self._init_from_dict(args[0]) + elif isinstance(args[0], list): + ExtendedNamespace.__init__(self, + _root=[JsonObj(e) if isinstance(e, (dict, list)) else + e for e in args[0]]) + else: + raise TypeError("JSON Object can only be a list or dictionary") + else: + self._init_from_dict(kwargs) + + @staticmethod + def _if_missing(obj: "JsonObj", item: str) -> Tuple[bool, Any]: + return False, None + + def _init_from_dict(self, d: Union[dict, "JsonObj"]) -> None: + """ Construct a JsonObj from a dictionary or another JsonObj """ + if not isinstance(d, JsonObj): + ExtendedNamespace.__init__(self, _if_missing=self._if_missing, + **{str(k): JsonObj(v) if isinstance(v, dict) else v for k, v in d.items()}) + + def _hide_list(self): + return self._root if '_root' in self else self + + def __str__(self) -> str: + return str(self._root) if '_root' in self else super().__str__() + + # =================================================== + # JSON Serializer method + # =================================================== + @staticmethod + def _static_default(obj, filtr: Callable[[Dict], Dict] = lambda e: e): + """ return a serialized version of obj or raise a TypeError. Used by the JSON serializer + + :param obj: + :param filtr: dictionary filter + :return: Serialized version of obj + """ + return filtr(obj._as_dict) if isinstance(obj, JsonObj) else json.JSONDecoder().decode(obj) + + def _default(self, obj, filtr: Callable[[Dict], Dict] = lambda e: e): + """ This default method is here to allow inheriting classes to override it when needed """ + return JsonObj._static_default(obj, filtr) + + # =================================================== + # Underscore equivalent of useful dictionary functions + # =================================================== + def _get(self, item: str, default: JsonObjTypes = None) -> JsonObjTypes: + """ Equivalent to dictionary get function w/o polluting namespace """ + return self[item] if item in self else default + + def _setdefault(self, k: str, value: Union[Dict, JsonTypes]) -> JsonObjTypes: + """ Equivalent of dictionary setdefault without messing in namespace """ + if k not in self: + self[k] = JsonObj(_if_missing=self._if_missing, **value) if isinstance(value, dict) else value + return self[k] + + def _keys(self) -> List[str]: + """ Return all non-hidden keys """ + for k in self._hide_list().__dict__.keys(): + if k not in hide: + yield k + + def _items(self) -> List[Tuple[str, JsonObjTypes]]: + """ Return all non-hidden items """ + for k, v in self._hide_list().__dict__.items(): + if k not in hide: + yield k, v + + def _values(self) -> List[JsonObjTypes]: + """ Return all non hidden values """ + for _, v in self._items(): + yield v + + # =================================================== + # Various converters -- use exposed methods in place of underscores + # =================================================== + def _as_json_obj(self) -> JsonTypes: + """ Return self as pure json """ + return json.loads(self._as_json_dumps()) + + def __getitem__(self, item): + if '_root' in self: + return self._root[item] + else: + found, val = self._if_missing(self, item) + if found: + return val + else: + return super().__getitem__(item) + + def __getattr__(self, item): + found, val = self._if_missing(self, item) + if found: + return val + else: + return super().__getattribute__(item) + + def __setattr__(self, key, value): + super().__setattr__(key, JsonObj(value) if isinstance(value, dict) else value) + + def __bool__(self): + if '_root' in self: + return bool(self._root) + else: + return bool(any(self._keys())) + + @property + def _as_json(self) -> str: + """ Convert a JsonObj into straight json text + + :return: JSON formatted str + """ + return json.dumps(self, default=self._default) + + def _as_json_dumps(self, indent: str = ' ', filtr: Callable[[Dict], Dict] = None, **kwargs) -> str: + """ Convert to a stringified json object. + + This is the same as _as_json with the exception that it isn't + a property, meaning that we can actually pass arguments... + :param indent: indent argument to dumps + :param filtr: dictionary filter + :param kwargs: other arguments for dumps + :return: JSON formatted string + """ + return json.dumps(self, + default=lambda obj: self._default(obj, filtr) if filtr else self._default(obj), + indent=indent, + **kwargs) + + @property + def _as_dict(self) -> Dict[str, JsonTypes]: + """ Convert a JsonObj into a straight dictionary + + :return: dictionary that cooresponds to the json object + """ + return as_dict(self) + + +def loads(s: str, **kwargs) -> JsonObj: + """ Convert a json_str into a JsonObj + + :param s: a str instance containing a JSON document + :param kwargs: arguments see: json.load for details + :return: JsonObj representing the json string + """ + if isinstance(s, (bytes, bytearray)): + s = s.decode(json.detect_encoding(s), 'surrogatepass') + + return JsonObj(json.loads(s, object_hook=lambda pairs: JsonObj(pairs), **kwargs)) + + +def load(source, **kwargs) -> JsonObj: + """ Deserialize a JSON source. + + :param source: a URI, File name or a .read()-supporting file-like object containing a JSON document + :param kwargs: arguments. see: json.load for details + :return: JsonObj representing fp + """ + return loads(hbread(source, accept_header="application/json, text/json;q=0.9"), **kwargs) + + +def is_dict(obj: Union[JsonObj, Any]) -> bool: + """ + Determine whether obj is a dictionary or a JsonObj containing a dictionary + """ + return isinstance(obj, dict) or (isinstance(obj, JsonObj) and not isinstance(obj._hide_list(), list)) + + +def is_list(obj: Union[JsonObj, Any]) -> bool: + """ + Determine whether obj is a dictionary or a JsonObj containing a dictionary + """ + return isinstance(obj, list) or (isinstance(obj, JsonObj) and isinstance(obj._hide_list(), list)) + + +def as_dict(obj: Union[JsonObj, List]) -> Union[List, Dict[str, JsonTypes]]: + """ Convert a JsonObj into a straight dictionary or list + + :param obj: pseudo 'self' + :return: dictionary that cooresponds to the json object + """ + if isinstance(obj, (list, JsonObj)): + return \ + {k: as_dict(v) if isinstance(v, (list, dict, JsonObj)) else v + for k, v in items(obj)} if is_dict(obj) else \ + [as_dict(e) if isinstance(e, (list, dict, JsonObj)) else + e for e in (obj._hide_list() if isinstance(obj, JsonObj) else obj)] + else: + return obj + +def as_json(obj: Union[Dict, JsonObj, List], indent: Optional[str] = ' ', + filtr: Callable[[Dict], Dict] = None, **kwargs) -> str: + """ Convert obj to json string representation. + + :param obj: pseudo 'self' + :param indent: indent argument to dumps + :param filtr: filter to remove unwanted elements + :param kwargs: other arguments for dumps + :return: JSON formatted string + """ + if isinstance(obj, JsonObj) and '_root' in obj: + obj = obj._root + default_processor = \ + obj._default if isinstance(obj, JsonObj) else JsonObj._static_default + return obj._as_json_dumps(indent, + filtr=filtr, + **kwargs) if isinstance(obj, JsonObj) else \ + json.dumps(obj, + default=lambda o: default_processor(o, filtr) if filtr else default_processor(o), + indent=indent, + *kwargs) + + +def as_json_obj(obj: Union[Dict, JsonObj, List]) -> JsonTypes: + """ Return obj as pure python json (vs. JsonObj) + :param obj: pseudo 'self' + :return: Pure python json image + """ + if isinstance(obj, JsonObj): + obj = obj._hide_list() + return [as_json_obj(e) for e in obj] if isinstance(obj, list) else\ + obj._as_json_obj() if isinstance(obj, JsonObj) else obj + + +def get(obj: Union[Dict, JsonObj], item: str, default: JsonObjTypes = None) -> JsonObjTypes: + """ Dictionary get routine """ + return obj._get(item, default) if isinstance(obj, JsonObj) else obj.get(item, default) + + +def setdefault(obj: Union[Dict, JsonObj], k: str, value: Union[Dict, JsonTypes]) -> JsonObjTypes: + """ Dictionary setdefault routine """ + return obj._setdefault(k, value) if isinstance(obj, JsonObj) else obj.setdefault(k, value) + + +def keys(obj: Union[Dict, JsonObj]) -> Iterator[str]: + """ same as dict keys() without polluting the namespace """ + return obj._keys() if isinstance(obj, JsonObj) else obj.keys() + + +def items(obj: Union[Dict, JsonObj]) -> Iterator[Tuple[str, JsonObjTypes]]: + """ Same as dict items() except that the values are JsonObjs instead of vanilla dictionaries + :return: + """ + return obj._items() if isinstance(obj, JsonObj) else obj.items() + + +def values(obj: Union[Dict, JsonObj]) -> Iterator[JsonObjTypes]: + """ Same as dict values() except that the values are JsonObjs """ + return obj._values() if isinstance(obj, JsonObj) else obj.values() diff --git a/linkml_runtime/utils/jsonasobj2/extendednamespace.py b/linkml_runtime/utils/jsonasobj2/extendednamespace.py new file mode 100644 index 00000000..27359a20 --- /dev/null +++ b/linkml_runtime/utils/jsonasobj2/extendednamespace.py @@ -0,0 +1,49 @@ + + +class ExtendedNamespace: + """ A combination of a namespace and a dictionary. This allows direct access to python properties plus + dictionary access to everything. + """ + def __init__(self, **kwargs): + for name in kwargs: + setattr(self, name, kwargs[name]) + + def __eq__(self, other): + if not isinstance(other, ExtendedNamespace): + return NotImplemented + return vars(self) == vars(other) + + def __contains__(self, key): + return key in self.__dict__ + + def __getitem__(self, item): + return self.__dict__[item] + + def __setitem__(self, key, item): + self.__dict__[key] = item + + def __delitem__(self, key): + del self.__dict__[key] + + def __iter__(self): + return self.__dict__.__iter__() + + def __len__(self): + return len(self.__dict__) + + def __repr__(self): + type_name = type(self).__name__ + arg_strings = [] + star_args = {} + for name, value in list(self.__dict__.items()): + if not name.startswith('_'): + if name.isidentifier(): + arg_strings.append('%s=%r' % (name, value)) + else: + star_args[name] = value + if star_args: + arg_strings.append('**%s' % repr(star_args)) + return '%s(%s)' % (type_name, ', '.join(arg_strings)) + + def _get(self, key, default=None): + return self.__dict__.get(key, default) diff --git a/linkml_runtime/utils/yamlutils.py b/linkml_runtime/utils/yamlutils.py index 219cb135..0fa2bb94 100644 --- a/linkml_runtime/utils/yamlutils.py +++ b/linkml_runtime/utils/yamlutils.py @@ -7,8 +7,8 @@ import yaml from deprecated.classic import deprecated -from jsonasobj2 import JsonObj, as_json, as_dict, JsonObjTypes, items -import jsonasobj2 +from .jsonasobj2 import JsonObj, as_json, as_dict, JsonObjTypes, items +from . import jsonasobj2 from rdflib import Graph, URIRef from yaml.constructor import ConstructorError diff --git a/pyproject.toml b/pyproject.toml index b50cba65..620fa726 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,6 @@ click = "*" deprecated = "*" hbreader = "*" json-flattener = ">=0.1.9" -jsonasobj2 = "==1.*,>=1.0.0,>=1.0.4" jsonschema = ">=3.2.0" prefixcommons = ">=0.1.12" pyyaml = "*" diff --git a/tests/support/filters.py b/tests/support/filters.py index fa7967db..70949139 100644 --- a/tests/support/filters.py +++ b/tests/support/filters.py @@ -3,7 +3,7 @@ import re from json import loads -from jsonasobj2 import as_json +from linkml_runtime.utils.jsonasobj2 import as_json def ldcontext_metadata_filter(s: str) -> str: """ diff --git a/tests/test_index/model/container_test.py b/tests/test_index/model/container_test.py index 23d1f84a..a7de3797 100644 --- a/tests/test_index/model/container_test.py +++ b/tests/test_index/model/container_test.py @@ -9,7 +9,7 @@ import dataclasses import sys import re -from jsonasobj2 import JsonObj, as_dict +from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict from typing import Optional, List, Union, Dict, ClassVar, Any from dataclasses import dataclass from linkml_runtime.linkml_model.meta import EnumDefinition, PermissibleValue, PvFormulaOptions diff --git a/tests/test_issues/input/issue_355.py b/tests/test_issues/input/issue_355.py index b5117559..e9a825ee 100644 --- a/tests/test_issues/input/issue_355.py +++ b/tests/test_issues/input/issue_355.py @@ -9,7 +9,7 @@ import dataclasses import sys import re -from jsonasobj2 import JsonObj +from linkml_runtime.utils.jsonasobj2 import JsonObj from typing import Optional, List, Union, Dict, ClassVar, Any from dataclasses import dataclass diff --git a/tests/test_issues/models/linkml_issue_576.py b/tests/test_issues/models/linkml_issue_576.py index 81fb80c7..c689c68c 100644 --- a/tests/test_issues/models/linkml_issue_576.py +++ b/tests/test_issues/models/linkml_issue_576.py @@ -9,7 +9,7 @@ import dataclasses import sys import re -from jsonasobj2 import JsonObj, as_dict +from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict from typing import Optional, List, Union, Dict, ClassVar, Any from dataclasses import dataclass from linkml_runtime.linkml_model.meta import EnumDefinition, PermissibleValue, PvFormulaOptions diff --git a/tests/test_issues/models/model_817.py b/tests/test_issues/models/model_817.py index ce6ac68f..2cf2a721 100644 --- a/tests/test_issues/models/model_817.py +++ b/tests/test_issues/models/model_817.py @@ -9,7 +9,7 @@ import dataclasses import sys import re -from jsonasobj2 import JsonObj, as_dict +from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict from typing import Optional, List, Union, Dict, ClassVar, Any from dataclasses import dataclass from linkml_runtime.linkml_model.meta import EnumDefinition, PermissibleValue, PvFormulaOptions diff --git a/tests/test_issues/test_issue_355.py b/tests/test_issues/test_issue_355.py index a1dc7872..ae4a118c 100644 --- a/tests/test_issues/test_issue_355.py +++ b/tests/test_issues/test_issue_355.py @@ -9,7 +9,7 @@ import dataclasses import sys import re -from jsonasobj2 import JsonObj +from linkml_runtime.utils.jsonasobj2 import JsonObj from typing import Optional, List, Union, Dict, ClassVar, Any from dataclasses import dataclass diff --git a/tests/test_loaders_dumpers/models/books_normalized.py b/tests/test_loaders_dumpers/models/books_normalized.py index b06d6547..63726d73 100644 --- a/tests/test_loaders_dumpers/models/books_normalized.py +++ b/tests/test_loaders_dumpers/models/books_normalized.py @@ -9,7 +9,7 @@ import dataclasses import sys import re -from jsonasobj2 import JsonObj, as_dict +from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict from typing import Optional, List, Union, Dict, ClassVar, Any from dataclasses import dataclass from linkml_runtime.linkml_model.meta import EnumDefinition, PermissibleValue, PvFormulaOptions diff --git a/tests/test_loaders_dumpers/models/enum_model.py b/tests/test_loaders_dumpers/models/enum_model.py index 141e6874..b8e90517 100644 --- a/tests/test_loaders_dumpers/models/enum_model.py +++ b/tests/test_loaders_dumpers/models/enum_model.py @@ -9,7 +9,7 @@ import dataclasses import sys import re -from jsonasobj2 import JsonObj, as_dict +from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict from typing import Optional, List, Union, Dict, ClassVar, Any from dataclasses import dataclass from linkml_runtime.linkml_model.meta import EnumDefinition, PermissibleValue, PvFormulaOptions diff --git a/tests/test_loaders_dumpers/models/node_object.py b/tests/test_loaders_dumpers/models/node_object.py index 4ebaa2b5..7204367b 100644 --- a/tests/test_loaders_dumpers/models/node_object.py +++ b/tests/test_loaders_dumpers/models/node_object.py @@ -9,7 +9,7 @@ import dataclasses import sys import re -from jsonasobj2 import JsonObj, as_dict +from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict from typing import Optional, List, Union, Dict, ClassVar, Any from dataclasses import dataclass from linkml_runtime.linkml_model.meta import EnumDefinition, PermissibleValue, PvFormulaOptions diff --git a/tests/test_loaders_dumpers/models/personinfo.py b/tests/test_loaders_dumpers/models/personinfo.py index a061168e..a50521e4 100644 --- a/tests/test_loaders_dumpers/models/personinfo.py +++ b/tests/test_loaders_dumpers/models/personinfo.py @@ -9,7 +9,7 @@ import dataclasses import sys import re -from jsonasobj2 import JsonObj, as_dict +from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict from typing import Optional, List, Union, Dict, ClassVar, Any from dataclasses import dataclass from linkml_runtime.linkml_model.meta import EnumDefinition, PermissibleValue, PvFormulaOptions diff --git a/tests/test_loaders_dumpers/models/personinfo_test_issue_429.py b/tests/test_loaders_dumpers/models/personinfo_test_issue_429.py index fed09fa2..8da9e7c6 100644 --- a/tests/test_loaders_dumpers/models/personinfo_test_issue_429.py +++ b/tests/test_loaders_dumpers/models/personinfo_test_issue_429.py @@ -9,7 +9,7 @@ import dataclasses import sys import re -from jsonasobj2 import JsonObj, as_dict +from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict from typing import Optional, List, Union, Dict, ClassVar, Any from dataclasses import dataclass from linkml_runtime.linkml_model.meta import EnumDefinition, PermissibleValue, PvFormulaOptions diff --git a/tests/test_loaders_dumpers/models/phenopackets.py b/tests/test_loaders_dumpers/models/phenopackets.py index 255a4578..4bf44c8a 100644 --- a/tests/test_loaders_dumpers/models/phenopackets.py +++ b/tests/test_loaders_dumpers/models/phenopackets.py @@ -9,7 +9,7 @@ import dataclasses import sys import re -from jsonasobj2 import JsonObj, as_dict +from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict from typing import Optional, List, Union, Dict, ClassVar, Any from dataclasses import dataclass from linkml_runtime.linkml_model.meta import EnumDefinition, PermissibleValue, PvFormulaOptions diff --git a/tests/test_loaders_dumpers/test_csv_tsv_loader_dumper.py b/tests/test_loaders_dumpers/test_csv_tsv_loader_dumper.py index 80dc5a5d..202ad62b 100644 --- a/tests/test_loaders_dumpers/test_csv_tsv_loader_dumper.py +++ b/tests/test_loaders_dumpers/test_csv_tsv_loader_dumper.py @@ -3,7 +3,7 @@ import json import logging -from jsonasobj2 import as_json_obj, JsonObj +from linkml_runtime.utils.jsonasobj2 import as_json_obj, JsonObj from linkml_runtime.dumpers import json_dumper, yaml_dumper from linkml_runtime.loaders import yaml_loader diff --git a/tests/test_utils/model/inference_example.py b/tests/test_utils/model/inference_example.py index 3cbc5d3f..7da374c3 100644 --- a/tests/test_utils/model/inference_example.py +++ b/tests/test_utils/model/inference_example.py @@ -9,7 +9,7 @@ import dataclasses import sys import re -from jsonasobj2 import JsonObj, as_dict +from linkml_runtime.utils.jsonasobj2 import JsonObj, as_dict from typing import Optional, List, Union, Dict, ClassVar, Any from dataclasses import dataclass from linkml_runtime.linkml_model.meta import EnumDefinition, PermissibleValue, PvFormulaOptions diff --git a/tests/test_utils/test_context_utils.py b/tests/test_utils/test_context_utils.py index 8e8bd2b8..4ad7c52b 100644 --- a/tests/test_utils/test_context_utils.py +++ b/tests/test_utils/test_context_utils.py @@ -1,6 +1,6 @@ import unittest -from jsonasobj2 import JsonObj, loads +from linkml_runtime.utils.jsonasobj2 import JsonObj, loads from linkml_runtime.utils.context_utils import merge_contexts from tests.test_utils import METAMODEL_CONTEXT_URI, META_BASE_URI diff --git a/tests/test_utils/test_formatutils.py b/tests/test_utils/test_formatutils.py index 5107cd3d..49ac58be 100644 --- a/tests/test_utils/test_formatutils.py +++ b/tests/test_utils/test_formatutils.py @@ -2,7 +2,7 @@ import unittest from typing import List, Tuple, Any -from jsonasobj2 import JsonObj, as_json +from linkml_runtime.utils.jsonasobj2 import JsonObj, as_json from linkml_runtime.utils.formatutils import camelcase, underscore, lcamelcase, be, split_line, wrapped_annotation, \ is_empty, remove_empty_items, uncamelcase diff --git a/tests/test_utils/test_inlined_as_dict_forms.py b/tests/test_utils/test_inlined_as_dict_forms.py index 01aa4ac1..f1dcd971 100644 --- a/tests/test_utils/test_inlined_as_dict_forms.py +++ b/tests/test_utils/test_inlined_as_dict_forms.py @@ -1,6 +1,6 @@ import unittest -from jsonasobj2 import JsonObj +from linkml_runtime.utils.jsonasobj2 import JsonObj from tests.test_utils.input.inlined_as_dict import E, EInst diff --git a/tests/test_utils/test_inlined_as_list_forms.py b/tests/test_utils/test_inlined_as_list_forms.py index 6c606097..9f22f8c4 100644 --- a/tests/test_utils/test_inlined_as_list_forms.py +++ b/tests/test_utils/test_inlined_as_list_forms.py @@ -1,6 +1,6 @@ import unittest -from jsonasobj2 import JsonObj +from linkml_runtime.utils.jsonasobj2 import JsonObj from tests.test_utils.input.inlined_as_list import E, EInst diff --git a/tests/test_utils/test_metamodelcore.py b/tests/test_utils/test_metamodelcore.py index 1bc26739..6cab502f 100644 --- a/tests/test_utils/test_metamodelcore.py +++ b/tests/test_utils/test_metamodelcore.py @@ -2,7 +2,7 @@ import unittest from dataclasses import dataclass -from jsonasobj2 import as_json +from linkml_runtime.utils.jsonasobj2 import as_json from rdflib import Literal, XSD, Graph, RDF, Namespace from linkml_runtime.utils.metamodelcore import NCName, Bool, URIorCURIE, URI, XSDDate, XSDDateTime, XSDTime, Curie, \ diff --git a/tests/test_utils/test_schemaview.py b/tests/test_utils/test_schemaview.py index c9f28ff8..aa39c3f3 100644 --- a/tests/test_utils/test_schemaview.py +++ b/tests/test_utils/test_schemaview.py @@ -3,7 +3,7 @@ from copy import copy from pathlib import Path import pytest -from jsonasobj2 import JsonObj +from linkml_runtime.utils.jsonasobj2 import JsonObj from linkml_runtime.dumpers import yaml_dumper from linkml_runtime.linkml_model.meta import Example, SchemaDefinition, ClassDefinition, SlotDefinitionName, SlotDefinition, \ diff --git a/tests/test_utils/test_walker_utils.py b/tests/test_utils/test_walker_utils.py index 195b1100..a613a482 100644 --- a/tests/test_utils/test_walker_utils.py +++ b/tests/test_utils/test_walker_utils.py @@ -2,7 +2,7 @@ import unittest from copy import deepcopy -from jsonasobj2 import as_dict +from linkml_runtime.utils.jsonasobj2 import as_dict from linkml_runtime.linkml_model import SchemaDefinition, ClassDefinition from linkml_runtime.utils.walker_utils import traverse_object_tree