diff --git a/.gitignore b/.gitignore index a88aff4..cd55f92 100644 --- a/.gitignore +++ b/.gitignore @@ -176,5 +176,5 @@ pyrightconfig.json # End of https://www.toptal.com/developers/gitignore/api/python # file generated by setuptools_scm -/jsont/_version.py +/jsonype/_version.py diff --git a/.idea/jsont.iml b/.idea/jsonype.iml similarity index 100% rename from .idea/jsont.iml rename to .idea/jsonype.iml diff --git a/.idea/misc.xml b/.idea/misc.xml index a85b351..fae8e54 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 7dd5928..98f7f69 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/README.md b/README.md index f6c38b7..598240f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# json-t +# jsonype ## Usage -See [documentation](https://json-t.readthedocs.io). +See [documentation](https://jsonype.readthedocs.io). ## Similar tools @@ -11,7 +11,7 @@ See [documentation](https://json-t.readthedocs.io). a less permissive license. - [Pydantic](https://docs.pydantic.dev) is a widely used data validation library for Python. It is often used for converting to and from JSON, but offers much more than that. Unlike in case of - json-t classes that should be converted from/to JSON need to inherit from a base class (`BaseModel`). + jsonype classes that should be converted from/to JSON need to inherit from a base class (`BaseModel`). ## Development @@ -56,5 +56,5 @@ current directory is set to the project's root folder. ```bash cd docs -sphinx-apidoc -o source ../jsont/ +sphinx-apidoc -o source ../jsonype/ ``` diff --git a/check.sh b/check.sh index 9fd13aa..46c614e 100755 --- a/check.sh +++ b/check.sh @@ -2,4 +2,7 @@ pylama ruff check +mypy pytest +[ -f jsonype/_version.py ] || echo "__version__='0'" > jsonype/_version.py +(cd docs && make doctest) diff --git a/docs/source/conf.py b/docs/source/conf.py index 562b124..f695922 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -8,12 +8,12 @@ sys.path.insert(0, os.path.abspath("../../")) -from jsont._version import __version__ +from jsonype._version import __version__ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -project = 'json-t' +project = 'jsonype' # noinspection PyShadowingBuiltins copyright = '2023, Volker Stampa' author = 'Volker Stampa' diff --git a/docs/source/index.rst b/docs/source/index.rst index d6bb2b9..3614918 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,5 +1,5 @@ -Welcome to json-t's documentation! -================================== +Welcome to jsonype's documentation! +=================================== .. toctree:: :maxdepth: 1 @@ -7,7 +7,7 @@ Welcome to json-t's documentation! modules -json-t is a package for converting Python's +jsonype is a package for converting Python's `JSON representation `_ to (or from) a Python object of a given type if possible (i.e. a suitable converter is available). @@ -17,11 +17,11 @@ This is most useful when the given type contains type-hints such that this type- Getting Started --------------- -Add ``json-t`` to your dependencies or install with pip:: +Add ``jsonype`` to your dependencies or install with pip:: - pip install json-t + pip install jsonype -For details on how to use it see :class:`jsont.typed_json.TypedJson`. +For details on how to use it see :class:`jsonype.TypedJson`. diff --git a/docs/source/jsont.rst b/docs/source/jsonype.rst similarity index 67% rename from docs/source/jsont.rst rename to docs/source/jsonype.rst index 57f89ef..094903b 100644 --- a/docs/source/jsont.rst +++ b/docs/source/jsonype.rst @@ -1,10 +1,10 @@ -jsont package -============= +jsonype package +=============== Module contents --------------- -.. automodule:: jsont +.. automodule:: jsonype :members: :special-members: __init__ :undoc-members: diff --git a/docs/source/modules.rst b/docs/source/modules.rst index 18e51c7..9ed47d7 100644 --- a/docs/source/modules.rst +++ b/docs/source/modules.rst @@ -1,7 +1,7 @@ -jsont -===== +jsonype +======= .. toctree:: :maxdepth: 3 - jsont + jsonype diff --git a/jsont/__init__.py b/jsonype/__init__.py similarity index 79% rename from jsont/__init__.py rename to jsonype/__init__.py index eee2ed7..3e290ab 100644 --- a/jsont/__init__.py +++ b/jsonype/__init__.py @@ -1,5 +1,10 @@ # pylint: disable=C0414 from .base_types import Json as Json # noqa: W0611 + +# isort: off +from .basic_from_json_converters import (FromJsonConversionError # noqa: W0611 + as FromJsonConversionError) +# isort: on from .basic_from_json_converters import FromJsonConverter as FromJsonConverter # noqa: W0611 from .basic_from_json_converters import ToAny as ToAny # noqa: W0611 from .basic_from_json_converters import ToList as ToList # noqa: W0611 @@ -10,6 +15,11 @@ from .basic_from_json_converters import ToTuple as ToTuple # noqa: W0611 from .basic_from_json_converters import ToTypedMapping as ToTypedMapping # noqa: W0611 from .basic_from_json_converters import ToUnion as ToUnion # noqa: W0611 + +# isort: off +from .basic_from_json_converters import (UnsupportedTargetTypeError # noqa: W0611 + as UnsupportedTargetTypeError) +# isort: on from .basic_to_json_converters import FromMapping as FromMapping # noqa: W0611 from .basic_to_json_converters import FromNone as FromNone # noqa: W0611 from .basic_to_json_converters import FromSequence as FromSequence # noqa: W0611 diff --git a/jsont/base_types.py b/jsonype/base_types.py similarity index 100% rename from jsont/base_types.py rename to jsonype/base_types.py diff --git a/jsont/basic_from_json_converters.py b/jsonype/basic_from_json_converters.py similarity index 99% rename from jsont/basic_from_json_converters.py rename to jsonype/basic_from_json_converters.py index 23cbc0a..984f47e 100644 --- a/jsont/basic_from_json_converters.py +++ b/jsonype/basic_from_json_converters.py @@ -4,7 +4,7 @@ from typing import (Any, Callable, Generic, Iterable, Literal, Mapping, Optional, Protocol, Sequence, TypeVar, Union, cast, get_args, runtime_checkable) -from jsont.base_types import Json, JsonSimple +from jsonype.base_types import Json, JsonSimple TargetType = TypeVar("TargetType") ContainedTargetType = TypeVar("ContainedTargetType") @@ -343,7 +343,7 @@ def type_for_key(k: str) -> type[TargetType]: items = js.items() if self.strict \ else [(k, v) for k, v in js.items() if k in annotations] return {k: from_json(v, type_for_key(k)) for k, v in items} - raise FromJsonConversionError(js, target_type, + raise FromJsonConversionError(js, cast(type, target_type), f"Required key missing: {target_type.__required_keys__}") raise FromJsonConversionError(js, target_type) diff --git a/jsont/basic_to_json_converters.py b/jsonype/basic_to_json_converters.py similarity index 98% rename from jsont/basic_to_json_converters.py rename to jsonype/basic_to_json_converters.py index ec22704..22cb4ff 100644 --- a/jsont/basic_to_json_converters.py +++ b/jsonype/basic_to_json_converters.py @@ -1,7 +1,7 @@ from abc import ABC, abstractmethod from typing import Any, Callable, Generic, Mapping, Sequence, TypeVar, get_args -from jsont.base_types import Json, JsonNull, JsonSimple +from jsonype.base_types import Json, JsonNull, JsonSimple SourceType_contra = TypeVar("SourceType_contra", contravariant=True) diff --git a/jsont/typed_json.py b/jsonype/typed_json.py similarity index 85% rename from jsont/typed_json.py rename to jsonype/typed_json.py index ceb2723..c93daa7 100644 --- a/jsont/typed_json.py +++ b/jsonype/typed_json.py @@ -1,12 +1,12 @@ from inspect import get_annotations from typing import Any, TypeVar, cast, get_origin -from jsont.base_types import Json -from jsont.basic_from_json_converters import (FromJsonConverter, ToAny, ToList, ToLiteral, - ToMapping, ToNone, ToSimple, ToTuple, ToTypedMapping, - ToUnion, UnsupportedTargetTypeError) -from jsont.basic_to_json_converters import (FromMapping, FromNone, FromSequence, FromSimple, - ToJsonConverter, UnsupportedSourceTypeError) +from jsonype.base_types import Json +from jsonype.basic_from_json_converters import (FromJsonConverter, ToAny, ToList, ToLiteral, + ToMapping, ToNone, ToSimple, ToTuple, + ToTypedMapping, ToUnion, UnsupportedTargetTypeError) +from jsonype.basic_to_json_converters import (FromMapping, FromNone, FromSequence, FromSimple, + ToJsonConverter, UnsupportedSourceTypeError) TargetType = TypeVar("TargetType") @@ -22,7 +22,7 @@ class TypedJson: Example: >>> from typing import TypedDict - >>> from jsont import TypedJson + >>> from jsonype import TypedJson >>> from json import loads >>> >>> typed_json = TypedJson() @@ -59,9 +59,9 @@ class TypedJson: >>> try: ... person = TypedJson(strict=True).from_json(js, Person) ... except ValueError as e: - ... print(e) - Cannot convert {'street': '...', ..., 'zip': 'ignored'} - to as it contains unknown key zip + ... print(e) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE + ("Cannot convert {'street': '...', ..., 'zip': 'ignored'} + to : Unknown key: zip", ... """ def __init__(self, strict: bool = False) -> None: diff --git a/pyproject.toml b/pyproject.toml index 7e132fc..99b20bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,8 @@ [tool.poetry] -name = "json-t" +name = "jsonype" version = "0" # ignored description = "A package for converting classes with type hints to/from JSON" -homepage = "https://github.com/volkerstampa/json-t" -packages = [{include = "jsont"}] +homepage = "https://github.com/volkerstampa/jsonype" authors = ["Volker Stampa "] maintainers = ["Volker Stampa "] readme = "README.md" @@ -22,27 +21,34 @@ requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" [tool.setuptools_scm] -version_file = "jsont/_version.py" +version_file = "jsonype/_version.py" local_scheme = "poetry_setuptools_scm_plugin:no_local_scheme" [tool.pylama] -linters = "eradicate,mccabe,mypy,pycodestyle,pydocstyle,pyflakes,pylint,radon,vulture,isort" +# dont run mypy through pylama because of: +# https://github.com/klen/pylama/issues/163 +linters = "eradicate,mccabe,pycodestyle,pydocstyle,pyflakes,pylint,radon,vulture,isort" skip = "docs/*" [tool.pylama.linter.pydocstyle] ignore = "D100,D101,D102,D103,D104,D107,D203,D213,D410,D411,D406,D413,D407" +[tool.pylama.linter.mypy] +cache_dir = "build/.mypy_cache" +strict = "True" +files = "jsonype,tests" + [tool.pydocstyle] ignore = "D100,D101,D102,D103,D104,D107,D203,D213,D410,D411,D406,D413,D407" [tool.mypy] cache_dir = "build/.mypy_cache" strict = "True" -files = "jsont,tests" +files = "jsonype,tests" [tool.isort] line_length = 100 -src_paths = ["jsont", "tests"] +src_paths = ["jsonype", "tests"] [tool.pylint."messages control"] disable = ["invalid-name", "missing-module-docstring", "missing-function-docstring", "missing-class-docstring"] @@ -55,7 +61,7 @@ cc_min = "C" [tool.vulture] min_confidence = 61 -paths = ["jsont", "tests"] +paths = ["jsonype", "tests"] [tool.ruff] line-length = 100 diff --git a/tests/jsont/__init__.py b/tests/jsonype/__init__.py similarity index 100% rename from tests/jsont/__init__.py rename to tests/jsonype/__init__.py diff --git a/tests/jsont/test_typed_json.py b/tests/jsonype/test_typed_json.py similarity index 95% rename from tests/jsont/test_typed_json.py rename to tests/jsonype/test_typed_json.py index ffde401..1cbe13b 100644 --- a/tests/jsont/test_typed_json.py +++ b/tests/jsonype/test_typed_json.py @@ -6,9 +6,9 @@ from typing import (Any, Callable, Iterable, List, Mapping, Optional, Sequence, Tuple, TypeAlias, TypedDict, TypeVar, Union, cast) -from pytest import mark +from pytest import mark, raises -from jsont import TypedJson +from jsonype import FromJsonConversionError, TypedJson _T = TypeVar("_T") @@ -19,7 +19,7 @@ strict_typed_json = TypedJson(strict=True) -@mark.parametrize( # type: ignore [misc] +@mark.parametrize( "simple_obj", [0, -1, 2, 0.0, 1.0, -2.0, True, False, "Hello", "", None], ) @@ -27,14 +27,14 @@ def test_simple(simple_obj: Any) -> None: assert_can_convert_from_to_json(simple_obj, type(simple_obj)) -@mark.parametrize("simple_obj", [0, "Hello", None]) # type: ignore [misc] +@mark.parametrize("simple_obj", [0, "Hello", None]) def test_simple_with_union_type(simple_obj: Union[int, str, None]) -> None: # Union is a type-special-form so cast to type explicitly assert_can_convert_from_to_json( simple_obj, cast(type[Optional[Union[int, str]]], Optional[Union[int, str]])) -@mark.parametrize( # type: ignore [misc] +@mark.parametrize( ("li", "ty"), [ ([0, -1, 2], int), @@ -48,7 +48,7 @@ def test_homogeneous_list(li: Sequence[Any], ty: TypeAlias) -> None: assert_can_convert_from_to_json(li, list[ty]) -@mark.parametrize("li", [[1], ["Hi"]]) # type: ignore [misc] +@mark.parametrize("li", [[1], ["Hi"]]) def test_untyped_list(li: Sequence[Any]) -> None: assert_can_convert_from_to_json(li, list) @@ -67,7 +67,7 @@ def test_empty_tuple() -> None: assert_can_convert_from_to_json((), tuple[()]) -@mark.parametrize( # type: ignore [misc] +@mark.parametrize( ("m", "ty"), [({"k1": 1}, int), ({"k1": True, "k2": False}, bool), ({"k1": None}, NoneType)], ) @@ -97,6 +97,16 @@ class Map(TypedDict): assert_can_convert_from_to_json({"k1": 1., "k2": 2}, Map) +def test_typed_dict_fail_if_key_missing() -> None: + class Map(TypedDict): + k1: float + k2: int + + with raises(FromJsonConversionError) as exc_info: + typed_json.from_json({"k1": 1.}, Map) + assert "k2" in str(exc_info.value) + + def test_random_objects() -> None: for _ in range(500): assert_can_convert_from_to_json(*(_random_typed_object(8)))