diff --git a/.github/actions/test/action.yml b/.github/actions/test/action.yml new file mode 100644 index 0000000..18f4366 --- /dev/null +++ b/.github/actions/test/action.yml @@ -0,0 +1,33 @@ +name: 'Test' + +inputs: + python-version: + description: 'Python version to test with' + required: true + type: string + pytest-args: + description: 'pytest args to test with' + required: false + default: '' + type: string + +runs: + using: "composite" + steps: + - name: Set up Python ${{ inputs.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ inputs.python-version }} + - name: Install dependencies + shell: bash + run: | + python -m pip install --upgrade pip build + python -m build + python -m pip install '.[test]' + python -m pip install wheel pytest pytest-cov pytest-xdist requests "coveralls<4" codacy-coverage + pip install --ignore-installed pytest>=4.4.0 + pytest --version + - name: Test with pytest + shell: bash + run: | + pytest ${{ inputs.pytest-args }} -m "not no_expected" -n 8 test/ diff --git a/.github/workflows/test_and_release.yml b/.github/workflows/test_and_release.yml index 2ee1142..2d80373 100644 --- a/.github/workflows/test_and_release.yml +++ b/.github/workflows/test_and_release.yml @@ -1,6 +1,8 @@ name: Build 📦 -on: [ push, pull_request ] +on: + - push + - pull_request jobs: test: @@ -8,31 +10,21 @@ jobs: strategy: fail-fast: false matrix: - python-version: [ '3.7', '3.8', '3.9', '3.10', '3.11' ] + python-version: [ '3.9', '3.10', '3.11', '3.12' ] steps: - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + - name: Test + uses: ./.github/actions/test with: python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python setup.py sdist - python setup.py install - python -m pip install wheel pytest pytest-cov pytest-xdist requests coveralls codacy-coverage - pip install --ignore-installed pytest>=4.4.0 - pytest --version - - name: Test with pytest - run: | - python setup.py test -a '--cov-config .coveragerc --cov=json_to_models -m "not no_expected" test/' - - name: Coverage - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - coverage xml - coveralls --service=github + pytest-args: '--cov-config .coveragerc --cov=json_to_models' +# - name: Coverage +# env: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +# run: | +# coverage xml +# coveralls --service=github # TODO: Fix codacy issue with token # python-codacy-coverage -r coverage.xml @@ -43,17 +35,17 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Set up Python 3.7 + - name: Set up Python 3.11 uses: actions/setup-python@v2 with: - python-version: 3.7 + python-version: 3.11 - name: Install deps run: | python -m pip install --upgrade pip pip install setuptools wheel twine - name: Build a binary wheel and a source tarball run: | - python setup.py sdist bdist_wheel + python -m build - name: Publish distribution 📦 to PyPI env: TWINE_USERNAME: __token__ diff --git a/.github/workflows/test_every_week.yml b/.github/workflows/test_every_week.yml new file mode 100644 index 0000000..c150293 --- /dev/null +++ b/.github/workflows/test_every_week.yml @@ -0,0 +1,21 @@ +name: Test every month + +on: + schedule: + - cron: "0 * * * *" +# - cron: "0 0 1 * *" + +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: [ '3.9', '3.10', '3.11', '3.12' ] + + steps: + - uses: actions/checkout@v2 + - name: Test + uses: ./.github/actions/test + with: + python-version: ${{ matrix.python-version }} diff --git a/README.md b/README.md index 53c2846..5f586fa 100644 --- a/README.md +++ b/README.md @@ -444,9 +444,6 @@ class Swagger(BaseModel): ## Installation -| **Beware**: this project supports only `python3.7` and higher. | -| --- | - To install it, use `pip`: `pip install json2python-models` diff --git a/json_to_models/__init__.py b/json_to_models/__init__.py index d239267..260c070 100644 --- a/json_to_models/__init__.py +++ b/json_to_models/__init__.py @@ -1,4 +1 @@ -from pkg_resources import parse_version - -__version__ = "0.3.0" -VERSION = parse_version(__version__) +__version__ = "0.3.1" diff --git a/json_to_models/cli.py b/json_to_models/cli.py index 4d367d7..6a4a6e1 100644 --- a/json_to_models/cli.py +++ b/json_to_models/cli.py @@ -15,9 +15,12 @@ try: import ruamel.yaml as yaml + + yaml_load = yaml.YAML(typ='safe', pure=True).load except ImportError: try: import yaml + yaml_load = yaml.safe_load except ImportError: yaml = None @@ -417,11 +420,11 @@ def json(path: Path) -> Union[dict, list]: @staticmethod def yaml(path: Path) -> Union[dict, list]: - if yaml is None: - print('Yaml parser is not installed. To parse yaml files PyYaml (or ruamel.yaml) is required.') + if yaml_load is None: + print('Yaml parser is not installed. To parse yaml files ruamel.yaml (or PyYaml) is required.') raise ImportError('yaml') with path.open() as fp: - return yaml.safe_load(fp) + return yaml_load(fp) @staticmethod def ini(path: Path) -> dict: diff --git a/json_to_models/models/pydantic.py b/json_to_models/models/pydantic.py index fe47958..2937f42 100644 --- a/json_to_models/models/pydantic.py +++ b/json_to_models/models/pydantic.py @@ -46,7 +46,7 @@ def generate(self, nested_classes: List[str] = None, extra: str = "", **kwargs) nested_classes=nested_classes, extra=extra ) - imports.append(('pydantic', ['BaseModel', 'Field'])) + imports.append(('pydantic.v1', ['BaseModel', 'Field'])) return imports, body def _filter_fields(self, fields): diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..8030da8 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,50 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +name = "json2python-models" +description = "Python models (pydantic, attrs, dataclasses or custom) generator from JSON data with typing module support" +readme = "README.md" +license = { text = "MIT" } +requires-python = ">=3.9" +authors = [ + { name = "bogdandm (Bogdan Kalashnikov)", email = "bogdan.dm1995@yandex.ru" } +] +dynamic = ['version', 'dependencies'] + +[project.urls] +"Repository" = "https://github.com/bogdandm/json2python-models" + +[project.optional-dependencies] +test = [ + "pytest>=4.4.0", + "pytest-xdist", + "pytest-cov", + "requests", + "attrs", + "pydantic>=1.3", + "ruamel.yaml", + "coverage" +] + +[project.scripts] +json2models = "json_to_models.cli:main" + +[tool.setuptools] +packages = { find = { exclude = ["test", "testing_tools"] } } + +[tool.setuptools.dynamic] +dependencies = { file = ["requirements.txt"] } +version = { attr = "json_to_models.__version__" } +readme = { file = ['README.md'] } + +[tool.setuptools.package-data] +"*" = ['*.txt.', '*.ini', ".coveragerc", "LICENSE", "*.md"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +markers = [ + "no_expected: testing data has no expected value", + "slow_http: api that provides testing data is slow" +] \ No newline at end of file diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 31fac25..0000000 --- a/pytest.ini +++ /dev/null @@ -1,4 +0,0 @@ -[pytest] -markers = - no_expected: testing data has no expected value - slow_http: api that provides testing data is slow \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 48afdcf..df7c532 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -python-dateutil>=2.7.* -inflection>=0.3.* -unidecode>=1.0.* -Jinja2>=2.10.* +python-dateutil>=2.7 +inflection>=0.3 +unidecode>=1.0 +Jinja2>=2.10 ordered-set==4.* -typing-extensions>=3.1.* +typing-extensions>=3.1 diff --git a/setup.py b/setup.py.backup similarity index 100% rename from setup.py rename to setup.py.backup diff --git a/test/test_cli/test_script.py b/test/test_cli/test_script.py index 7e2ce3c..6e77145 100644 --- a/test/test_cli/test_script.py +++ b/test/test_cli/test_script.py @@ -1,12 +1,12 @@ -import imp import json import re import subprocess import sys import tempfile +import types +import uuid from pathlib import Path from time import time -from typing import Tuple import pytest @@ -95,6 +95,14 @@ def test_help(): ] +def load_model(code, module_name=''): + module_name = module_name or uuid.uuid4().hex + module = types.ModuleType(module_name) + sys.modules[module_name] = module + exec(code, module.__dict__) + return module + + def execute_test(command, output_file: Path = None, output=None) -> str: proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = map(bytes.decode, proc.communicate()) @@ -107,13 +115,7 @@ def execute_test(command, output_file: Path = None, output=None) -> str: assert not stderr, stderr assert stdout, stdout assert proc.returncode == 0 - # Note: imp package is deprecated but I can't find a way to create dummy module using importlib - module = imp.new_module("test_model") - sys.modules["test_model"] = module - try: - exec(compile(stdout, "test_model.py", "exec"), module.__dict__) - except Exception as e: - assert not e, stdout + load_model(stdout) print(stdout) return stdout diff --git a/test/test_cli/test_self_validate_pydantic.py b/test/test_cli/test_self_validate_pydantic.py index b1a5f35..d163305 100644 --- a/test/test_cli/test_self_validate_pydantic.py +++ b/test/test_cli/test_self_validate_pydantic.py @@ -1,9 +1,7 @@ -import imp import json -import sys from inspect import isclass -import pydantic +import pydantic.v1 as pydantic import pytest from json_to_models.generator import MetadataGenerator @@ -11,7 +9,7 @@ from json_to_models.models.pydantic import PydanticModelCodeGenerator from json_to_models.models.structure import compose_models_flat from json_to_models.registry import ModelRegistry -from .test_script import test_data_path +from .test_script import test_data_path, load_model test_self_validate_pydantic_data = [ pytest.param(test_data_path / "gists.json", list, id="gists.json"), @@ -39,14 +37,9 @@ def test_self_validate_pydantic(data, data_type): structure = compose_models_flat(reg.models_map) code = generate_code(structure, PydanticModelCodeGenerator) - module = imp.new_module("test_models") - sys.modules["test_models"] = module - try: - exec(compile(code, "test_models.py", "exec"), module.__dict__) - except Exception as e: - assert not e, code - - import test_models + + test_models = load_model(code, 'test_models') + for name in dir(test_models): cls = getattr(test_models, name) if isclass(cls) and issubclass(cls, pydantic.BaseModel): diff --git a/test/test_code_generation/test_pydantic_generation.py b/test/test_code_generation/test_pydantic_generation.py index 95c058f..7ad8d0c 100644 --- a/test/test_code_generation/test_pydantic_generation.py +++ b/test/test_code_generation/test_pydantic_generation.py @@ -53,7 +53,7 @@ ] }, "generated": trim(f""" - from pydantic import BaseModel, Field + from pydantic.v1 import BaseModel, Field class Test(BaseModel): @@ -120,7 +120,7 @@ class Test(BaseModel): } }, "generated": trim(f""" - from pydantic import BaseModel, Field + from pydantic.v1 import BaseModel, Field from typing import Dict, List, Optional @@ -146,7 +146,7 @@ class Test(BaseModel): "u": DUnion(DDict(IntString), DList(DList(IntString))), }), "generated": trim(""" - from pydantic import BaseModel, Field + from pydantic.v1 import BaseModel, Field from typing import Dict, List, Optional, Union