pax_global_header00006660000000000000000000000064152643732660014530gustar00rootroot0000000000000052 comment=cf7afe8f7ac5402e329ea64dbc0b1040f1aa1b56 tefra-xsdata-cf7afe8/000077500000000000000000000000001526437326600147005ustar00rootroot00000000000000tefra-xsdata-cf7afe8/.github/000077500000000000000000000000001526437326600162405ustar00rootroot00000000000000tefra-xsdata-cf7afe8/.github/FUNDING.yml000066400000000000000000000011771526437326600200630ustar00rootroot00000000000000# These are supported funding model platforms github: tefra patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] tefra-xsdata-cf7afe8/.github/dependabot.yml000066400000000000000000000005141526437326600210700ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: pip directory: "/" schedule: interval: daily open-pull-requests-limit: 10 - package-ecosystem: github-actions directory: / schedule: interval: monthly groups: # open a single pull-request for all GitHub actions updates github-actions: patterns: - '*' tefra-xsdata-cf7afe8/.github/pull_request_template.md000066400000000000000000000006531526437326600232050ustar00rootroot00000000000000## 📒 Description > Write a brief description of your PR. Resolves #xxxx ## 🔗 What I've Done > Write a description of the steps taken to resolve the issue ## 💬 Comments > A place to write any comments to the reviewer. ## 🛫 Checklist - [ ] Updated docs - [ ] Added unit-tests - [ ] [Sample tests](https://github.com/tefra/xsdata-samples) pass - [ ] [W3C tests](https://github.com/tefra/xsdata-w3c-tests) pass tefra-xsdata-cf7afe8/.github/workflows/000077500000000000000000000000001526437326600202755ustar00rootroot00000000000000tefra-xsdata-cf7afe8/.github/workflows/publish.yml000066400000000000000000000031561526437326600224730ustar00rootroot00000000000000name: Publish Python 🐍 distributions 📦 to PyPI and TestPyPI on: workflow_dispatch: release: types: [released] jobs: build: name: Build Python 🐍 distributions 📦 runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: persist-credentials: false - name: Set up Python 3.11 uses: actions/setup-python@v7 with: python-version: 3.11 - name: Install pypa/build run: >- python -m pip install build --user - name: Build a binary wheel and a source tarball run: python3 -m build - name: Store the distribution packages uses: actions/upload-artifact@v7 with: name: python-package-distributions path: dist/ publish: name: Publish Python 🐍 distributions 📦 to PyPI and TestPyPI needs: build runs-on: ubuntu-latest environment: name: release url: https://pypi.org/p/xsdata permissions: id-token: write steps: - name: Download all the dists uses: actions/download-artifact@v8 with: name: python-package-distributions path: dist/ - name: Publish distribution 📦 to Test PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: https://test.pypi.org/legacy/ # Attestations are written into dist/ and would clash with the PyPI upload attestations: false - name: Publish distribution 📦 to PyPI # Manual runs stop at TestPyPI, only releases reach PyPI if: github.event_name == 'release' uses: pypa/gh-action-pypi-publish@release/v1 tefra-xsdata-cf7afe8/.github/workflows/tests.yml000066400000000000000000000066421526437326600221720ustar00rootroot00000000000000name: tests on: push: branches: [ main ] pull_request: branches: [ main ] jobs: tests: name: ${{ matrix.name }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - {name: Python 3.10, python: '3.10'} - {name: Python 3.11, python: '3.11'} - {name: Python 3.12, python: '3.12'} - {name: Python 3.13, python: '3.13'} - {name: Python 3.14, python: '3.14'} steps: - uses: actions/checkout@v7 - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v7 with: python-version: ${{ matrix.python }} - name: Install dependencies run: | python -m pip install --upgrade pip setuptools python -m pip install .[lxml,cli,test,soap] - name: Test run: | pytest --cov=./xsdata --cov-report=xml --cov-branch --doctest-glob="docs/*.md" - name: Upload coverage to Codecov uses: codecov/codecov-action@v7 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true w3c-tests: name: W3C Tests runs-on: ubuntu-latest strategy: fail-fast: false steps: - uses: actions/checkout@v7 - uses: actions/checkout@v7 with: repository: tefra/xsdata-w3c-tests ref: main submodules: true path: w3c-tests - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v7 with: python-version: '3.10' - name: Install dependencies run: | python -m pip install --upgrade pip setuptools pytest pytest-xdist xmlschema python -m pip install .[lxml,cli] - name: Test run: | pytest -n auto --mode=build w3c-tests/tests pytest -n auto --mode=xsd w3c-tests/tests - name: Generated models diff run: | git -C w3c-tests diff --stat output benchmark: name: Benchmarks runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - uses: actions/setup-python@v7 with: python-version: '3.10' - name: Install dependencies run: | python -m pip install .[lxml,cli,test] - name: Benchmark run: | pytest --benchmark-only --benchmark-columns=min,max,mean,median tests/integration/benchmarks minimum: name: Minimum Installation runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - uses: actions/setup-python@v7 with: python-version: '3.11' - name: Install pip install . run: | pip install -e .[test] - name: Verify run: | python benchmark.py -c XmlEventWriter python benchmark.py -c XmlEventHandler python benchmark.py -c JsonParser python benchmark.py -c JsonSerializer xsdata | xargs -0 python -c "import sys; assert 'Install cli' in sys.argv[1]" pre-commit: name: Pre-commit Hooks runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - uses: actions/setup-python@v7 - uses: pre-commit/action@v3.0.1 check-package: name: Build & Inspect Package. runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - name: Set up Python 3.11 uses: actions/setup-python@v7 with: python-version: 3.11 - uses: hynek/build-and-inspect-python-package@v3.0.1 tefra-xsdata-cf7afe8/.gitignore000066400000000000000000000025011526437326600166660ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # PyBuilder #target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # Ignore everything in the docs/api directory docs/api/* # But not the .gitkeep file !docs/api/.gitkeep # mypy .mypy_cache/ # idea .idea/ # vscode .vscode/ # idea runs .run/ .DS_STORE mise.toml tefra-xsdata-cf7afe8/.pre-commit-config.yaml000066400000000000000000000017131526437326600211630ustar00rootroot00000000000000exclude: tests/fixtures repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: debug-statements - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.17.1 hooks: - id: ruff-check args: [ --fix, --show-fixes] - id: ruff-format types_or: [python, pyi] - repo: https://github.com/pre-commit/mirrors-mypy rev: v2.4.0 hooks: - id: mypy files: ^(xsdata/) additional_dependencies: - tokenize-rt - types-requests - types-Jinja2 - types-click - types-docutils - types-toposort args: [ "--check-untyped-defs", "--ignore-missing-imports" ] - repo: https://github.com/rbubley/mirrors-prettier rev: v3.9.10 hooks: - id: prettier types_or: [markdown] args: [--prose-wrap=always, --print-width=88] tefra-xsdata-cf7afe8/.readthedocs.yml000066400000000000000000000003121526437326600177620ustar00rootroot00000000000000version: 2 build: os: "ubuntu-22.04" tools: python: "3.11" mkdocs: configuration: mkdocs.yml python: install: - method: pip path: . extra_requirements: - docs,cli tefra-xsdata-cf7afe8/CHANGES.md000066400000000000000000001267671526437326600163150ustar00rootroot00000000000000## 26.10 (2026-10-16) **Security** - Disable external entity resolution in LxmlEventHandler by default ([#1228](https://github.com/tefra/xsdata/pull/1228)) **Fixes** - Fix wrapper binding when collection item element name is reused ([#1224](https://github.com/tefra/xsdata/pull/1224)) - Fix KeyError in is_binding_model when models_package is not set ([#1219](https://github.com/tefra/xsdata/pull/1219)) - Validate calendar date in XmlDate.from_string ([#1227](https://github.com/tefra/xsdata/pull/1227)) - Add charset to the SOAP client content-type header ([#1231](https://github.com/tefra/xsdata/pull/1231)) - Skip xsi:nil for a present instance of a type without content ([#1234](https://github.com/tefra/xsdata/pull/1234)) - Skip None text when binding wildcard list elements ([#1237](https://github.com/tefra/xsdata/pull/1237)) **Features** - Share compiled jinja templates across generators ([#1235](https://github.com/tefra/xsdata/pull/1235)) - Add third-party license notices for bundled schemas and graphs util ([#1238](https://github.com/tefra/xsdata/pull/1238)) ## 26.2 (2026-02-15) **Fixes** - Fix elements in same choice different branches incorrectly merged as list ([#1206](https://github.com/tefra/xsdata/pull/1206)) **Features** - Infer XML required property from typing annotations ([#1208](https://github.com/tefra/xsdata/pull/1208)) - Generate required metadata only for attributes without default values ([#1208](https://github.com/tefra/xsdata/pull/1208)) ## 26.1 (2026-01-20) **Fixes** - Fixed missing imports on inheriting multiple mixins for the same class ([#1156](https://github.com/tefra/xsdata/pull/1156)) - Fixed choice elements with minOccurs <= 1 not marked as optional - Fixed incorrect max_occurs for elements in different choice blocks ([#1179](https://github.com/tefra/xsdata/pull/1179)) - Fixed AttributeError when retrieving subclasses from cython packages ([#1200](https://github.com/tefra/xsdata/pull/1200)) **Features** - Added flag for specifying extensions ([#1168](https://github.com/tefra/xsdata/pull/1168)) - Added support for python 3.14 ([#1173](https://github.com/tefra/xsdata/pull/1173)) - Switched to Sequence when using generic containers ([#1201](https://github.com/tefra/xsdata/pull/1201)) **Deprecations** - Removed Python 3.9 support ([#1189](https://github.com/tefra/xsdata/pull/1189)) ([#1191](https://github.com/tefra/xsdata/pull/1191)) - Removed cli options union-type, kw-only, postponed-annotations are now always enabled - Removed xsdata shorthand ([#1128](https://github.com/tefra/xsdata/pull/1128)) ## 25.7 (2025-07-06) **Fixes** - Ignore dynamic classes in auto-discovery mode. ([#1144](https://github.com/tefra/xsdata/pull/1144)) - Parsing tail content with nested mixed classes ([#1151](https://github.com/tefra/xsdata/pull/1151)) ## 25.4 (2025-04-13) **Features** - Allow extensions to match module paths ([#1132](https://github.com/tefra/xsdata/pull/1132)) - Improve duplicate class names detection and resolution ([#1127](https://github.com/tefra/xsdata/pull/1127)) **Fixes** - Resolve Code Quality Issue ## 24.12 (2024-12-22) **Fixes** - Set the default value of prohibited fields to None ([#1098](https://github.com/tefra/xsdata/pull/1098)) - Set body and fault as optional in output soap classes ([#1099](https://github.com/tefra/xsdata/pull/1099)) - Resolve a conflict with type location when an element and a complex type have the same name ([#1107](https://github.com/tefra/xsdata/pull/1107)) **Features** - Allow Xml Parser subclasses to override how the root class is located ([#1090](https://github.com/tefra/xsdata/pull/1090)) - Include common soap encoding schema ([#1100](https://github.com/tefra/xsdata/pull/1100)) **Deprecations** - Remove subscritable types config option - Remove Type,Tuple,List,Dict from stop words - Deprecate xsdata shorthand, use xsdata generate instead ## 24.11 (2024-11-03) **Fixes** - Avoid conflict with attributes named value when flattening extensions ([#1085](https://github.com/tefra/xsdata/pull/1085)) **Features** - Add cli config to use generic collections ([#1082](https://github.com/tefra/xsdata/pull/1082)) **Deprecations** - Drop support for python 3.8 ## 24.9 (2024-09-21) **Fixes** - Fix typing annotations deprecation warning in Python 3.13 ([#1077](https://github.com/tefra/xsdata/pull/1077)) **Features** - Allow generators as array elements ([#1074](https://github.com/tefra/xsdata/pull/1074)) ## 24.7 (2024-07-28) **Features** - Improve XML parsing performance on union elements with fixed attributes ([#1066](https://github.com/tefra/xsdata/pull/1066)) - Skip optional and nillable elements on XML Serializer ([#1066](https://github.com/tefra/xsdata/pull/1066)) **Fixes** - Reset attr types derived from missing simple types ([#1062](https://github.com/tefra/xsdata/pull/1062)) ## 24.6.1 (2024-06-28) **Fixes** - Ruff check command typo breaks generator with v0.5.0 ([#1061](https://github.com/tefra/xsdata/pull/1061)) ## 24.6 (2024-06-24) **Features** - Add class and field info in parsing warnings ([#1036](https://github.com/tefra/xsdata/pull/1036)) - Remove whitespace from bytes encoded xml strings ([#1037](https://github.com/tefra/xsdata/pull/1037)) - Improve codegen performance ([#1043](https://github.com/tefra/xsdata/pull/1043)) **Fixes** - JSON serializer fails on derived elements ([#1053](https://github.com/tefra/xsdata/pull/1053)) - Update typing-extensions minimum version ([#1039](https://github.com/tefra/xsdata/pull/1039)) - Avoid using not-threadsafe warnings.catch_warning ([#1042](https://github.com/tefra/xsdata/pull/1042)) - Unnest classes doesn't update inner classes recursively ([#1047](https://github.com/tefra/xsdata/pull/1047)) - Restore support for optional lists ([#1053](https://github.com/tefra/xsdata/pull/1053)) ## 24.5 (2024-05-07) **Features** - Rewrite TreeSerializer, drop support for native python ElementTree ([#1032](https://github.com/tefra/xsdata/pull/1032)) - Validate fields fixed values ([#1013](https://github.com/tefra/xsdata/pull/1013)) - Detect optional fields in dict mapper - Refactor typing annotations analyze process - Generate ForwardRef() instead of Type[] **Fixes** - Allow soap client config subclassing ([#1010](https://github.com/tefra/xsdata/pull/1010)) - Avoid recursive error on nested group references ([#1016](https://github.com/tefra/xsdata/pull/1016)) - Add warning for same module designation ([#1018](https://github.com/tefra/xsdata/pull/1018)) ## 24.4 (2024-04-01) **Features** - Add xml and lxml tree serializers ([#975](https://github.com/tefra/xsdata/pull/975)) - Capture namespace prefixes in user dicts ([#978](https://github.com/tefra/xsdata/pull/978)) - Add cli option to generate wrapper fields ([#982](https://github.com/tefra/xsdata/pull/982)) - Support wrapper fields in JSON data bindings ([#982](https://github.com/tefra/xsdata/pull/982)) - Use abstract suffixes to resolve class name conflicts ([#985](https://github.com/tefra/xsdata/pull/985)) - Add the version number in the cli cache key ([#990](https://github.com/tefra/xsdata/pull/990)) - Use unicodedata.name for attrs with only special characters ([#993](https://github.com/tefra/xsdata/pull/993)) - Add src code excerpts on ruff errors ([#996](https://github.com/tefra/xsdata/pull/996)) - Detect circular imports and raise appropriate error ([#999](https://github.com/tefra/xsdata/pull/999)) - Add support for Python 3.13 ([#1001](https://github.com/tefra/xsdata/pull/1001)) - Add cli debug messages with performance stats **Fixes** - Use deepcopy to clone codegen models ([#980](https://github.com/tefra/xsdata/pull/980)) - Generate type hints for compound fields with token elements ([#997](https://github.com/tefra/xsdata/pull/997)) - Protect prohibited attrs from turning into lists ([#998](https://github.com/tefra/xsdata/pull/998)) - Convert child attr to list when parent is list ([#998](https://github.com/tefra/xsdata/pull/998)) ## 24.3.1 (2024-03-10) **Fixes** - Unnest class with circular reference ([#974](https://github.com/tefra/xsdata/pull/974)) ## 24.3 (2024-03-10) **Features** - Avoid flattening root elements ([#945](https://github.com/tefra/xsdata/pull/945)) - Avoid generating ambiguous choices ([#946](https://github.com/tefra/xsdata/pull/946)) - Added various type reference validations ([#966](https://github.com/tefra/xsdata/pull/966), [#967](https://github.com/tefra/xsdata/pull/967), [#968](https://github.com/tefra/xsdata/pull/968)) - Calculate circular references more accurately ([#969](https://github.com/tefra/xsdata/pull/969)) - Prettify codegen errors ([#970](https://github.com/tefra/xsdata/pull/970)) - Use Ruff to sort imports ([#972](https://github.com/tefra/xsdata/pull/972)) **Fixes** - Move ruff format in the code generator ([#964](https://github.com/tefra/xsdata/pull/964)) ## 24.2.1 (2024-02-19) - Fixed FieldInfo type errors ([#949](https://github.com/tefra/xsdata/pull/949)) - Fixed private package names ([#950](https://github.com/tefra/xsdata/pull/950)) ## 24.2 (2024-02-17) - Added Dict encoder/decoder ([#921](https://github.com/tefra/xsdata/pull/921)) - Deprecated Serializer config pretty_print/pretty_print_indentation ([#942](https://github.com/tefra/xsdata/pull/942)) - Fixed lxml event writer to respect the encoding configuration ([#940](https://github.com/tefra/xsdata/pull/940)) - Migrated documentation to mkdocs with markdown - Refactored project docstrings ## 24.1 (2024-01-04) - Fixed XmlParser to ignore xsi attrs when fail on unknown attributes is enabled ([#846](https://github.com/tefra/xsdata/pull/846)) - Fixed parsing mandatory byte elements with no value ([#873](https://github.com/tefra/xsdata/pull/873)) - Fixed issue in json binding with union fields ([#864](https://github.com/tefra/xsdata/pull/864)) - Fixed PycodeSerializer to escape unicode characters in string values ([#877](https://github.com/tefra/xsdata/pull/877)) - Fixed compound field choices with forward references ([#886](https://github.com/tefra/xsdata/pull/886)) - Fixed google style docstrings to add missing colon ([#884](https://github.com/tefra/xsdata/pull/884)) - Fixed deprecation warnings for datetime.datetime.utcnow() - Fixed XmlSerializer to ignore empty non-nillable/required tokens ([#902](https://github.com/tefra/xsdata/pull/902)) - Fixed issue with invalid variable names when using originalCase naming convention ([#881](https://github.com/tefra/xsdata/pull/881)) - Added type hints for compound fields ([#858](https://github.com/tefra/xsdata/pull/858), [#885](https://github.com/tefra/xsdata/pull/885)) - Added http header when loading remote resources ([#867](https://github.com/tefra/xsdata/pull/867)) - Added warning when converting parent field to a list ([#871](https://github.com/tefra/xsdata/pull/871)) - Added ruff to format generated code ([#892](https://github.com/tefra/xsdata/pull/892)) - Added option to use substitution group names for compound field name ([#905](https://github.com/tefra/xsdata/pull/905)) - Updated base64 decoding to enable validation ([#875](https://github.com/tefra/xsdata/pull/875)) - Updated generator to render prohibited parent fields with restriction extensions ([#908](https://github.com/tefra/xsdata/pull/908)) - Updated generator so plugins can easily override templates ## 23.8 (2023-08-12) - Removed Python 3.7 support - Fixed PycodeSerializer not adding imports for nested classes - Fixed imports ordering - Added support for strict content wildcard processing ([#803](https://github.com/tefra/xsdata/pull/803)) ## 23.7 (2023-07-23) - Fixed decimal converter to avoid scientific notations ([#826](https://github.com/tefra/xsdata/pull/826)) - Fixed nympy parameter docstring format ([#827](https://github.com/tefra/xsdata/pull/827)) - Fixed optional/required override validation ([#820](https://github.com/tefra/xsdata/pull/820)) - Fixed WSDL mapper to respect the elements original location ([#832](https://github.com/tefra/xsdata/pull/832)) - Added Python 3.12 support ## 23.6 (2023-06-24) - Fixed conflicting enum values leading to wrong default values ([#806](https://github.com/tefra/xsdata/pull/806)) - Added support for custom decorators and base classes ([#793](https://github.com/tefra/xsdata/pull/793)) - Added parser config to load external dtd to resolve entities ([#797](https://github.com/tefra/xsdata/pull/797)) - Added requests sessions on the wsdl client transport ([#798](https://github.com/tefra/xsdata/pull/798)) - Added support subscriptable types and UnionType ([#801](https://github.com/tefra/xsdata/pull/801)) - Added option to restrict models package for auto-locator ([#809](https://github.com/tefra/xsdata/pull/809)) - Updated context to only cache supported classes ([#796](https://github.com/tefra/xsdata/pull/796)) - Removed tox requirement ([#800](https://github.com/tefra/xsdata/pull/800)) - Converted to pyproject.toml ([#802](https://github.com/tefra/xsdata/pull/802)) ## 23.5 (2023-05-21) - Fixed XML meta var index conflicts. - Fixed mixed content handling for DTD elements. ([#749](https://github.com/tefra/xsdata/pull/749), [#762](https://github.com/tefra/xsdata/pull/762)) - Fixed an issue with required attributes turning into optional ones. - Fixed calculation of min/max occurs when parsing XML/JSON documents. ([#756](https://github.com/tefra/xsdata/pull/756)) - Fixed calculation of min/max occurs when parsing DTD choice content types. ([#760](https://github.com/tefra/xsdata/pull/760)) - Fixed an issue when parsing tail content for compound wildcard elements. - Fixed an issue with the code analyzer not fully processing some classes. - Fixed an issue with the code analyzer taking forever to process very large enumerations. ([#776](https://github.com/tefra/xsdata/issue/776)) - Fixed an issue in the JSON parser with optional choice elements. - Updated the transformer to silently ignore malformed JSON files. ([#750](https://github.com/tefra/xsdata/pull/750)) - Updated the override attribute handler to fix naming conflicts. - Updated the override attribute handler to allow wildcard overrides. - Updated conditions on extensions flattening (over-flattening). ([#754](https://github.com/tefra/xsdata/pull/754)) - Updated Group, AttributeGroup handling, skipping a few cases. - Updated how min/max occurs are calculated with nested containers. - Updated handling of element substitutions to treat them as choices. ([#786](https://github.com/tefra/xsdata/pull/786)) - Updated Pycodeserializer to skip default field values. - Updated flattening restriction base classes when sequence elements are out of order. - Updated docformatter to v1.6.5. - Added support to override compound fields. - Added support for multiple sequential groups in a class. - Added support for non-list compound fields. - Added support to mix list and non-list fields with sequence groups. - Added an option to include headers in generated files. ([#746](https://github.com/tefra/xsdata/pull/746)) - Added an option to cache the initial load and mapping of resources. - Added support for regular expressions in config substitutions. ([#755](https://github.com/tefra/xsdata/pull/755)) - Added a pretty print indentation option in the serializer config. ([#780](https://github.com/tefra/xsdata/pull/780)) - Added an option to set the encoding in the SOAP Client. ([#773](https://github.com/tefra/xsdata/pull/773)) - Added a CLI flag to show debug messages. - Added a debug message for possible circular references during code generation. - Added support to generate prohibited fields when they restrict parent fields. ([#781](https://github.com/tefra/xsdata/pull/781)) This release is bigger than intended and includes many major changes, that's why it took so long. ## 22.12 (2022-12-17) - Added option to ignore xml pattern restrictions [#727](https://github.com/tefra/xsdata/pull/727) - Added globalns support via SerializerConfig [#724](https://github.com/tefra/xsdata/pull/724) - Pinned docformatter version to v1.5.0 [#729](https://github.com/tefra/xsdata/pull/729) ## 22.11 (2022-11-06) - Added list wrapper for elements and primitive nodes [#710](https://github.com/tefra/xsdata/pull/710) ## 22.9 (2022-09-24) - Fixed code generation inconsistencies in different operating systems. - Fixed circular imports error [#706](https://github.com/tefra/xsdata/pull/706) - Fixed naming conflicts in imports [#706](https://github.com/tefra/xsdata/pull/706) - Fixed issue with wrong occurrences in DTD code generation [#705](https://github.com/tefra/xsdata/pull/705) - Fixed xs:group and xs:attrGroup name conflicts [#702](https://github.com/tefra/xsdata/pull/702) - Added mathml3 in standard schemas ## 22.8 (2022-08-21) - Added pycode serializer [#626](https://github.com/tefra/xsdata/issues/626) - Added option to filter out unused global types [#691](https://github.com/tefra/xsdata/issues/691) - Avoid using generics for mixed content when possible [#696](https://github.com/tefra/xsdata/pull/696) - Removed support for python 3.6 [#671](https://github.com/tefra/xsdata/pull/671) ## 22.7 (2022-07-22) - Fix empty lists do not get serialized [#686](https://github.com/tefra/xsdata/issues/686) - Added external DTD code generator [#688](https://github.com/tefra/xsdata/pull/688) - Added support for python 3.11 In the next release we will drop python 3.6 support!!! ## 22.5 (2022-05-08) - Added support for xml date/time nanoseconds [#679](https://github.com/tefra/xsdata/pull/679) ## 22.4 (2022-04-10) - Added config option to unnest classes - Added new class meta option global_type, to hide classes from discovery - Removed min/max length restrictions for enum type fields - Allow builtin xml types to be fully extended [#672](https://github.com/tefra/xsdata/pull/672) ## 22.3 (2022-03-20) - Added official support for python 3.11 - Fixed enumerations restricting complex types [#659](https://github.com/tefra/xsdata/issues/659) - Fixed attribute name duplicate check to avoid invalid slugs ## 22.2 (2022-02-06) - Fixed substitution groups on duplicate global types [#648](https://github.com/tefra/xsdata/issues/648) - Added Postponed Annotations config option [#646](https://github.com/tefra/xsdata/issues/646) - Added support for subclasses with different namespaces [#654](https://github.com/tefra/xsdata/issues/654) ## 22.1 (2022-01-23) - Added recursive glob for cli dir source [#643](https://github.com/tefra/xsdata/issues/643) - Added cfg options to change/force compound field names [#639](https://github.com/tefra/xsdata/issues/639) ## 21.12 (2021-12-05) - Fixed wsdl generator to use operation name for rpc input messages [#609](https://github.com/tefra/xsdata/issues/609) - Fixed wsdl generator to check for qualified elements for message part types [#612](https://github.com/tefra/xsdata/issues/612) - Fixed compound field matcher to prefer exact types over derived [#617](https://github.com/tefra/xsdata/issues/617) - Added async to the reserved keywords [#600](https://github.com/tefra/xsdata/issues/600) - Added generator config for search & replace substitutions [#624](https://github.com/tefra/xsdata/issues/624) - Updated code generator to remove abstract elements from class attrs [#627](https://github.com/tefra/xsdata/issues/627) - Updated code generator to filter out all unused types [#629](https://github.com/tefra/xsdata/issues/629) ## 21.11 (2021-11-02) - Fixed unescaped quotes in regex pattern [#592](https://github.com/tefra/xsdata/issues/592) - Added config option fail_on_unknown_attributes [#597](https://github.com/tefra/xsdata/issues/597) - Fixed build for python 3.10 ## 21.9 (2021-09-04) - Automate cli generate options [#578](https://github.com/tefra/xsdata/pull/578) - Generate cli options by the config model - Allow to enable/disable any flag - Allow to bypass any value from the config - Removed -cf/-ri as we can't have switches with short names - Fixed generator not cascading default values to inner classes [#579](https://github.com/tefra/xsdata/issues/579) - Re-raise xml syntax errors as xsdata.exceptions.ParserError [#571](https://github.com/tefra/xsdata/issues/571) - Added cli summary with recovered warnings and issues [#583](https://github.com/tefra/xsdata/pull/583) - Removed the native and lxml SAX handlers [#582](https://github.com/tefra/xsdata/issues/582) ## 21.8 (2021-08-03) - Deprecated JsonSerializer indent property, use SerializerConfig instead - Fixed SchemaMapper assigning wrong namespace for imported unqualified elements - Fixed AttributeTypeHandler to maintain occurs between any flattening - Fixed missing required field metadata property - Fixed nillable fields not being marked as optional - Fixed fields ordering during class reduce process (Codegen from xml/json) - Added support for xs:defaultOpenContent:appliesToEmpty attribute - Added ParserConfig class factory option [#549](https://github.com/tefra/xsdata/pull/549) - Added SerializerConfig option to ignore optional default attributes [#555](https://github.com/tefra/xsdata/pull/555) - Added warning on unexpected duplicate types [#564](https://github.com/tefra/xsdata/pull/564) - Added GeneratorConfig support for kw_only and slots for python >= 3.10 - Added structure style namespace-clusters [#573](https://github.com/tefra/xsdata/pull/573) - Updated text fields default value to empty string and marked as required [#570](https://github.com/tefra/xsdata/pull/570) - Updated fields derived from xs:substitutionGroups to optional - Updated fields derived from xs:any to optional - Updated AttributeDefaultValueHandler to preserve acceptable default values - Updated AttributeDefaultValueHandler to mark as optional any xsi:type attribute - Updated xs:alternative handling to resemble xs:choice - Updated mixed content handler to group all elements under wildcard - Updated ElementMapper to detect nillable types - Updated DictMapper to generate list of xs:anySimpleType for empty list nodes - Updated the compatibility layer for dataclass style plugins - Updated namespaces structure style to convert namespaces similar to jaxb - `http://www.w3.org/XML/1998/namespace` to `org.w3.XML.1998.namespace` - Update binding process for nillable types and fields - nillable types can be initialized - nillable fields are initialized with None values ## 21.7 (2021-07-01) - Fixed docstrings backslash escaping [#518](https://github.com/tefra/xsdata/pull/518) - Fixed analyzer flattening bare types [#541](https://github.com/tefra/xsdata/pull/541) - Fixed multiple issues with compound fields and override fields [#533](https://github.com/tefra/xsdata/pull/533) - Fixed missing derived elements types during xml parsing [#541](https://github.com/tefra/xsdata/pull/541) - Added structure style: clusters for smaller packages [#509](https://github.com/tefra/xsdata/pull/509) - Added configuration to generate relative imports [#519](https://github.com/tefra/xsdata/pull/519) - Added configuration to toggle all dataclasses features [#529](https://github.com/tefra/xsdata/pull/529) - Added binding support for tuple typing annotations (frozen dataclasses) [#529](https://github.com/tefra/xsdata/pull/529) - Added support to bind data directly from xml/lxml Element and ElementTree [#531](https://github.com/tefra/xsdata/pull/531) [#546](https://github.com/tefra/xsdata/pull/546) - Updated analyzer to avoid same name for outer-inner classes [#511](https://github.com/tefra/xsdata/pull/511) - Updated cli to fail early if config file is invalid [#514](https://github.com/tefra/xsdata/pull/514) - Updated cli to remove setuptools from runtime dependencies [#515](https://github.com/tefra/xsdata/pull/515) - Updated analyzer to relax override field validations completely [#516](https://github.com/tefra/xsdata/pull/516) - Updated analyzer to sort classes before class name conflict resolution [#517](https://github.com/tefra/xsdata/pull/517) - Updated JSON parser to attempt binding against subclasses [#527](https://github.com/tefra/xsdata/pull/527) - Updated analyzer to guard against multiple substitution group runs [#538](https://github.com/tefra/xsdata/pull/538) - Updated code generation to use case-sensitive reserved words [#545](https://github.com/tefra/xsdata/pull/545) ## 21.6 (2021-06-01) - Fixed no args Dict annotation, raising an exception [#494](https://github.com/tefra/xsdata/issues/494) - Fixed original name case not working for field names [#498](https://github.com/tefra/xsdata/issues/498) - Fixed element type resolution with duplicate name conflicts [#503](https://github.com/tefra/xsdata/issues/503) - Added handler to flatten bare inner classes - Added the ability for custom types to subclass named tuples - Added keyword meta in the reserved words [#491](https://github.com/tefra/xsdata/issues/491) - Added new xml type `Ignore` to skip fields during binding [#504](https://github.com/tefra/xsdata/issues/504) - Updated generic model DerivedElement.substituted flag with xsi:type - Updated core components to improve binding performance - Converted almost all internal dataclasses to simple objects with **slots** - Converted the internal xml date/time types to named tuples - Reduced models metadata lookup times and memory footprint - Updated JSON parser [#495](https://github.com/tefra/xsdata/issues/495) - Support failing on unknown properties - Support required properties - Support parser config - Stricter binding process - Enhance DerivedElement support - Moved Definitive XML Schema tests to the samples repository ## 21.5 (2021-05-07) - Added output structure style single-package [#469](https://github.com/tefra/xsdata/issues/469) - Added support for marshalling array of objects for json [#448](https://github.com/tefra/xsdata/issues/448) - Added support to generate code from raw json documents [#445](https://github.com/tefra/xsdata/issues/445) - Added docstring style Blank to avoid generating them [#460](https://github.com/tefra/xsdata/issues/460) - Added validations for non-supported type hints - Added support for python 3.10 - Generate package **all** lists [#459](https://github.com/tefra/xsdata/issues/459) - Generate factory for xs:list enumeration default values [#471](https://github.com/tefra/xsdata/issues/471) - Avoid generating prohibited elements with maxOccurs==0 [#478](https://github.com/tefra/xsdata/issues/478) - Avoid generating identical overriding fields [#466](https://github.com/tefra/xsdata/issues/466) - Fixed flattening base classes if they are also subclasses [#473](https://github.com/tefra/xsdata/issues/473) - Fixed unchecked class name conflict resolution [#457](https://github.com/tefra/xsdata/issues/457) - Refactored context components to improve binding performance [#476](https://github.com/tefra/xsdata/issues/476) ## 21.4 (2021-04-02) - Split requirements to extras cli, soap and lxml [#419](https://github.com/tefra/xsdata/issues/419) - Fixed parser conflict when an attribute and element field have the same qualified name - Added cli auto-detection for source types, removed cli flag `--wsdl` - Added cli support to generate code from raw xml documents - Added cli entry point to allow pluggable output formats [#429](https://github.com/tefra/xsdata/issues/429) - Added cli short flags for all options and flags - Added handler to set effective choice groups [#433](https://github.com/tefra/xsdata/issues/433) - Moved plantUML output format to a standalone [plugin](https://github.com/tefra/xsdata-plantuml) - Updated xml parser to allow unions of primitive and class types - Updated XmlDateTime parser to catch invalid cases with extra leading zeros - Updated QName converter to validate uri/ncname when parsing string representations - Updated JsonParser to allow parsing from filename string - Updated cli option `--compound-fields` to a boolean flag ## 21.3 (2021-03-04) - Added constant name convention config [#407](https://github.com/tefra/xsdata/issues/407) - Added naming schemes screaming snake case and original case - Updated xsi:lookup on xs:any derived elements [#315](https://github.com/tefra/xsdata/issues/315) - Updated fields restriction inheritance [#417](https://github.com/tefra/xsdata/issues/417) - Updated cli to allow package override from arguments [#416](https://github.com/tefra/xsdata/issues/416) - Updated code generation to merge duplicate global types earlier [#406](https://github.com/tefra/xsdata/issues/406) - Fixed docstrings issue breaking python syntax [#403](https://github.com/tefra/xsdata/issues/403) - Fixed bindings for nillable content without workarounds [#408](https://github.com/tefra/xsdata/issues/408) - Fixed resolver to apply aliases on extensions and choice fields [#414](https://github.com/tefra/xsdata/issues/414) - Fixed schema models limiting xs:appinfo occurrences [#420](https://github.com/tefra/xsdata/issues/420) - Decoupled core systems from click and lxml **Notice**: In the next release installation profiles will be introduced that will turn the cli, lxml and soap features **optional**. ## 21.2 (2021-02-02) - Added class name context for user naming schemes [#348](https://github.com/tefra/xsdata/issues/348) - Added mixed pascal naming scheme [#348](https://github.com/tefra/xsdata/issues/348) - Added access to element/attribute name generators [#381](https://github.com/tefra/xsdata/issues/381) - Added XmlHexBinary/XmlBase64Binary builtin data types [#387](https://github.com/tefra/xsdata/issues/387) - Added support for xs:anyType root elements [#399](https://github.com/tefra/xsdata/issues/399) - Updated JSON binding modules to use the fields local name [#389](https://github.com/tefra/xsdata/issues/389) - Updated enum classes generation - Promote all inner enums to root [#383](https://github.com/tefra/xsdata/issues/383) - Fixed issues with producing invalid members [#385](https://github.com/tefra/xsdata/issues/385) - Added support for list/tuple member values - Updated parsers accuracy for Union types - Updated dependency resolution accuracy - Update base classes generation strategies - Updated builtin data types with helper constructors/methods - Fixed inner class names conflicts [#375](https://github.com/tefra/xsdata/issues/375) - Fixed issue not generating fields derived from xs:alternative elements [#393](https://github.com/tefra/xsdata/issues/393) - Fixed duplicate root class name regression from v20.12 - Fixed issue adding unused lib imports - Fixed issue adding unused name properties to choice elements This is a sleeper release ✨✨✨ so many code generation improvements and finally the JSON binding is aligned with XML. ## 21.1 (2021-01-08) - Fixed XmlWriter converting attribute keys to QName. [#346](https://github.com/tefra/xsdata/issues/346) - Set empty complexType base to anySimpleType [#349](https://github.com/tefra/xsdata/issues/349) - Improve duplicate attr names detection [#351](https://github.com/tefra/xsdata/issues/351) - Add SerializerConfig::xml_declaration option [#357](https://github.com/tefra/xsdata/issues/357) - Generate default value/factory for compound fields [#359](https://github.com/tefra/xsdata/issues/359) - Fixed default value for token fields [#360](https://github.com/tefra/xsdata/issues/360) - Add doc metadata for compound fields [#362](https://github.com/tefra/xsdata/issues/362) - JsonParser: handle class and primitive unions [#369](https://github.com/tefra/xsdata/issues/369) - Update python mappings [#366](https://github.com/tefra/xsdata/issues/366) - Map xs:hexBinary and xs:base64Binary to bytes - Map xs:date/time types to builtin types XmlDate/Time - Map xs:duration to builtin type XmlDuration - Map xs:g[Year[Month[Day]]] to builtin type XmlPeriod - Map xs:Notation to QName - Add converter adapters for datetime.date/time - Add fields metadata key 'format' for time/date/binary types - Fixed issues with default literal values - Fixed issue with random field types order ## 20.12 (2020-12-10) - Added SerializerConfig with new options. [#268](https://github.com/tefra/xsdata/issues/268), [#320](https://github.com/tefra/xsdata/issues/320) - Added docstring styles: rst, google, numpy, accessible. [#318](https://github.com/tefra/xsdata/issues/318), [#340](https://github.com/tefra/xsdata/issues/340) - Added `max line length` generator configuration. [#342](https://github.com/tefra/xsdata/issues/342) - Added dynamic type locator for parsers. [#332](https://github.com/tefra/xsdata/issues/332) - Fixed multiple issues with json binding. `98[7%](https://github.com/tefra/xsdata-w3c-tests/actions) successful roundtrips ## 20.11.1 (2020-11-13) - Catch all type errors on xsi cache build [#316](https://github.com/tefra/xsdata/issues/316) ## 20.11 (2020-11-10) - Added sub command to download remote schemas and definitions. [#279](https://github.com/tefra/xsdata/issues/279) - Added new optional xml type `Elements` to maintain ordering for repeatable choices. [#296](https://github.com/tefra/xsdata/issues/296) - Added xsi:type lookup procedure for xs:anyType derived elements. [#306](https://github.com/tefra/xsdata/issues/306) - Updated simple type flattening detection. [#286](https://github.com/tefra/xsdata/issues/286) - Updated generator to allow namespace structure on schemas without target namespace. - Updated generator to avoid writing min/max occurs metadata for implied values. [#297](https://github.com/tefra/xsdata/issues/297) - Update generator to use literal dictionary initialization. - Updated parser security, disable lxml network and entities resolve. - Fixed field types detection for elements with xs:alternative children. [#284](https://github.com/tefra/xsdata/issues/284) - Fixed file generation to enforce default charset UTF-8. [#302](https://github.com/tefra/xsdata/issues/302) - Fixed jinja2 undefined namespace var collision. [#298](https://github.com/tefra/xsdata/issues/298) - Fixed import class name collision. [#300](https://github.com/tefra/xsdata/issues/300) - Fixed restriction inheritance on xs:group elements. [#301](https://github.com/tefra/xsdata/issues/301) ## 20.10 (2020-10-02) - Fixed generator adding multiple default value fields. [#249](https://github.com/tefra/xsdata/issues/249) - Fixed generator not applying nested container restrictions. [#263](https://github.com/tefra/xsdata/issues/253) - Fixed generator to avoid case insensitive class name conflicts. [#269](https://github.com/tefra/xsdata/issues/269) - Fixed generator rendering unused simple types. - Fixed generator unsorted libraries imports. - Fixed JsonParser trying to parse init=False fields. [#253](https://github.com/tefra/xsdata/issues/253) - Fixed NodeParser binding tail content more than once with mixed vars. [#256](https://github.com/tefra/xsdata/issues/256) - Added XmlWriter interface to decouple serialize from lxml. [#247](https://github.com/tefra/xsdata/issues/247) - Added native python xml content writer XmlEventWriter. ✨✨✨ - Added lxml based content writer LxmlEventWriter. - Added generator config with options to control naming cases and aliases. [#265](https://github.com/tefra/xsdata/issues/265) - Updated field xml type auto-detection to be more flexible. [#246](https://github.com/tefra/xsdata/issues/246) - Updated EnumConverter to resort to canonical form matching as last resort. [#273](https://github.com/tefra/xsdata/issues/273) - Updated support for derived elements. [#267](https://github.com/tefra/xsdata/issues/267) This is my favorite release so far, maybe because xsdata reached one year of development ✨✨✨ or maybe because some of the last original components finally got the rewrite they deserved. ## 20.9 (2020-09-03) - Added field metadata key `tokens` for xs:list or xs:NMTOKENS derived elements. - Added datatype factory to register custom converters. - Added XmlHandler interface to decouple parsing from lxml. - Added lxml based content handlers: LxmlEventHandler, LxmlSaxHandler - Added native python xml content handlers: XmlEventHandler, XmlSaxHandler - Added support for python >= 3.6 [#241](https://github.com/tefra/xsdata/issues/241) - Added codegen for soap 1.1 fault messages. - Fixed converting to enum members derived from xs:NMTOKENS. - Fixed package level import naming conflicts. [#228](https://github.com/tefra/xsdata/issues/206) - Fixed xml serializing to allow empty strings in attribute values. [#230](https://github.com/tefra/xsdata/issues/230) - Fixed xml serializing for mixed content with non generics. [#238](https://github.com/tefra/xsdata/issues/238) ## 20.8 (2020-08-01) - Added codegen support for **WSDL 1.1 and SOAP 1.1** bindings. - Added experimental web services client. - Added cli flag `--ns-struct` to group classes by target namespaces. [#206](https://github.com/tefra/xsdata/issues/206) - Added parser config to support xinclude statements. [#207](https://github.com/tefra/xsdata/issues/207) - Added new xml union node to improve bindings for fields with union type. [#207](https://github.com/tefra/xsdata/issues/207) - Fixed class resolve issue with mixed namespaces. [#204](https://github.com/tefra/xsdata/issues/204) - Fixed attribute comparison issue. [#209](https://github.com/tefra/xsdata/issues/209) - Fixed data type mapping for various schema elements. [#221](https://github.com/tefra/xsdata/issues/221) - Fixed mixed content handling. [#213](https://github.com/tefra/xsdata/issues/213) - Code cleanup & 100% coverage. ## 20.7 (2020-07-04) - Updated analyzer to allow abstract types to be generated. [#199](https://github.com/tefra/xsdata/issues/199) - Removed support to generate code from multiple sources. [#172](https://github.com/tefra/xsdata/issues/172) - Fixed naming conflict with AttributeGroup analyzer handler. [#194](https://github.com/tefra/xsdata/issues/194) - Fixed analyzer to merge redefined attribute groups. [#196](https://github.com/tefra/xsdata/issues/196) - Fixed analyzer to block inheritance on xs:override derived types. [#198](https://github.com/tefra/xsdata/issues/198) - Refactored code to prepare for wsdl support. ## 20.6 (2020-06-01) - Updated XmlSerializer to render default namespace whenever possible. - Fixed issue generating modules outside the target package. - Fixed issue not creating nested package **init** files. - Code cleanup & docstrings ## 20.5.5 (2020-05-23) - Added version option in the xsdata cli. - Added generation of missing python **init** files. - Added support for default values to inner enum classes. - Fixed multiple issues with abstract classes and attributes/extension flattening. - Fixed instance cross references causing codegen unpredictable results. - Fixed xml serialization of wildcard attributes with user defined model values. - Fixed issue with redefined/override elements with annotations. - Fixed expand attribute groups recursively. - Fixed false positive circular references. - Fixed enumeration unions detection. - Refactored ClassAnalyzer to smaller components. ## 20.5.4 (2020-05-15) - Fix flattening enumeration unions. - Fix generation for enum fields with default/fixed value. - Fix duplicate attribute names handler to be case-insensitive. ## 20.5.1 (2020-05-14) - Added support to fetch remote schemas. - Updated duplicate attribute names handling. - Updated code generation for enum type fields and default values. - Fixed issue not generating classes derived from simple types. - Fixed analyzer reaching the maximum recursion depth. - Fixed analyzer to flatten properly inner self referencing classes. - Moved dataclasses python conventions to jinja filters. ## 20.5 (2020-05-02) - Updated codegen cli to accept multiple definitions or directories as argument. - Update ClassBuilder to recursively search for anonymous types. - Updated XmlParser to be thread-safe. - Added performance tweaks on XmlParser. - Added parser config to fail or not on unknown properties. - Fixed primitive types being marked as forward references. - Fixed nested restrictions on xs:simpleType. - Fixed ClassAnalyzer to recover/ignore missing types. ## 20.4.2 (2020-04-21) - Added support for abstract xsi:types in XmlParser. - Added cache for event names in XmlParser. - Added sanitization for generated module names. - Fixed not flattening abstract extension. - Fixed extension name conflicts between simple and complex types. - Fixed possible memory leak in CodeWriter. - Fixed looping variables twice to find next node in XmlParser. - Fixed CodeWriter adding unnecessary new lines. ## 20.4.1 (2020-04-13) - Fixed open content attribute with mode suffix to be generated last. - Fixed issues with wildcard and mixed content parsing. - Updated xs:qname mapping to lxml.QName - Updated support for xs:list. - Updated parser to ignore xsi:type attributes default/fixed values. - Refactored code components. - Pass more than 99% of the `W3C XML Schema 1.1 test cases https://travis-ci.org/tefra/xsdata-w3c-tests ## 20.4 (2020-04-01) - Added support for sequential fields. - Added support for open content. - Added support multiple redefined elements. - Updated support for wildcards to be aware of generic namespaces. - Updated support for wildcards to be aware of non-generic objects. - Updated codegen to run after fully parsing all the definitions. - Updated codegen to skip unresolved schema locations. - Updated xml parser to ignore comments. - Updated xml parser to retain a copy of the input namespaces. - Fixed issues with nillable fields being ignored. - Fixed multiple issues with wrong restrictions being applied. - Fixed binding issues when there are naming conflicts. - Fixed serialization for inf/nan/exponential float and decimal values. - Fixed naming conflicts with class/package names. - Fixed multiple circular import issues during parsing and code generation. - Pass more than 98% of the `W3C XML Schema 1.1 test cases https://travis-ci.org/tefra/xsdata-w3c-tests ## 20.3 (2020-03-01) - Added copies of common schemas xlink, xsi, xml. - Added XML Schema 1.1 models and properties. - Added support for redefines, overrides, alternatives and default attributes. - Added missing xsd data types: dateTimestamp, anyType, anyAtomicType. - Added protection against duplicate class fields enumerations. - Added python common types to the stop word list. - Updated wildcards parsing to avoid duplicate elements. - Updated native datatype detection made stricter. - Updated enumerations generation to sort and filter values. - Updated mapping xs:decimal to python Decimal - Fixed elements/attribute not inheriting namespaces from references. - Fixed module names collisions. - Fixed self referencing classes. - Fixed class name collisions complexTypes vs elements. - Fixed parsers not respecting default values. - Fixed AbstractXmlParser to handle leafless root nodes. - Pass more than 90% of the `W3C XML Schema 1.1 test cases https://travis-ci.org/tefra/xsdata-w3c-tests. ## 20.2 (2020-02-09) - Added support xs:any and xs:anyAttribute elements. - Added support for auto-detecting XML Schema namespace prefix. - Added support for xml datatypes lang and base. - Refactored SchemaParser to use the XmlParser. - Updated XmlParser to bind after elements are fully parsed. ## 20.1.3 (2020-01-26) - Fixed elements min|man occurs inheritance from their container. - Fixed global elements and attributes are now always qualified. - Fixed including no namespace schemas. - Fixed list elements attribute handling. - Added support for unqualified elements. - Added support for qualified attributes. - Added support for nillable elements. - Added support for unions of member and simple types. - Added binding test suite ## 20.1.2 (2020-01-13) - Generate anonymous Enumerations - Generate attributes from List and Union elements - Fix restriction inheritance - Officially support python 3.8 - Completely migrate to setup.cfg - Introduce integration test suite ## 20.1.1 (2020-01-09) - Change print mode to print rendered output - Added new format PlantUML class diagram to replace the old print/debug mode ## 20.1 (2020-01-07) - Initial release tefra-xsdata-cf7afe8/LICENSE000066400000000000000000000020771526437326600157130ustar00rootroot00000000000000MIT License Copyright (c) 2021-2024 Christodoulos Tsoulloftas Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. tefra-xsdata-cf7afe8/MANIFEST.in000066400000000000000000000004021526437326600164320ustar00rootroot00000000000000include LICENSE README.md LICENSE CHANGES.md THIRD_PARTY_LICENSES.md recursive-include docs * recursive-include tests * recursive-include xsdata * exclude .readthedocs.yml exclude .mkdocs.yml exclude .pre-commit-config.yaml global-exclude .DS_Store *.pyc tefra-xsdata-cf7afe8/README.md000066400000000000000000000074031526437326600161630ustar00rootroot00000000000000[![image](https://github.com/tefra/xsdata/raw/main/docs/logo.svg)](https://xsdata.readthedocs.io/) # Naive XML Bindings for python [![image](https://github.com/tefra/xsdata/workflows/tests/badge.svg)](https://github.com/tefra/xsdata/actions) [![image](https://readthedocs.org/projects/xsdata/badge)](https://xsdata.readthedocs.io/) [![image](https://codecov.io/gh/tefra/xsdata/branch/main/graph/badge.svg)](https://codecov.io/gh/tefra/xsdata) [![image](https://www.codefactor.io/repository/github/tefra/xsdata/badge)](https://www.codefactor.io/repository/github/tefra/xsdata) [![image](https://img.shields.io/pypi/pyversions/xsdata.svg)](https://pypi.org/pypi/xsdata/) [![image](https://img.shields.io/pypi/v/xsdata.svg)](https://pypi.org/pypi/xsdata/) --- xsData is a complete data binding library for python allowing developers to access and use XML and JSON documents as simple objects rather than using DOM. The code generator supports XML schemas, DTD, WSDL definitions, XML & JSON documents. It produces simple dataclasses with type hints and simple binding metadata. The included XML and JSON parser/serializer are highly optimized and adaptable, with multiple handlers and configuration properties. xsData is constantly tested against the [W3C XML Schema 1.1 test suite](https://github.com/tefra/xsdata-w3c-tests). ## Getting started ```console $ # Install all dependencies $ pip install xsdata[cli,lxml,soap] ``` ```console $ # Generate models $ xsdata generate tests/fixtures/primer/order.xsd --package tests.fixtures.primer ``` ```python >>> from tests.fixtures.primer import PurchaseOrder >>> from xsdata.formats.dataclass.parsers import XmlParser >>> >>> parser = XmlParser() >>> order = parser.parse("tests/fixtures/primer/sample.xml", PurchaseOrder) >>> order.bill_to Usaddress(name='Robert Smith', street='8 Oak Avenue', city='Old Town', state='PA', zip=Decimal('95819'), country='US') ``` Check the [documentation](https://xsdata.readthedocs.io) for more ✨✨✨ ## Features **Code Generator** - XML Schemas 1.0 & 1.1 - WSDL 1.1 definitions with SOAP 1.1 bindings - DTD external definitions - Directly from XML and JSON Documents - Extensive configuration to customize output - Pluggable code writer for custom output formats **Default Output** - Pure python dataclasses with metadata - Type hints with support for forward references and unions - Enumerations and inner classes - Support namespace qualified elements and attributes **Data Binding** - XML and JSON parser, serializer - PyCode serializer - Handlers and Writers based on lxml and native xml python - Support wildcard elements and attributes - Support xinclude statements and unknown properties - Customize behaviour through config ## Changelog: 26.10 (2026-10-16) **Security** - Disable external entity resolution in LxmlEventHandler by default ([#1228](https://github.com/tefra/xsdata/pull/1228)) **Fixes** - Fix wrapper binding when collection item element name is reused ([#1224](https://github.com/tefra/xsdata/pull/1224)) - Fix KeyError in is_binding_model when models_package is not set ([#1219](https://github.com/tefra/xsdata/pull/1219)) - Validate calendar date in XmlDate.from_string ([#1227](https://github.com/tefra/xsdata/pull/1227)) - Add charset to the SOAP client content-type header ([#1231](https://github.com/tefra/xsdata/pull/1231)) - Skip xsi:nil for a present instance of a type without content ([#1234](https://github.com/tefra/xsdata/pull/1234)) - Skip None text when binding wildcard list elements ([#1237](https://github.com/tefra/xsdata/pull/1237)) **Features** - Share compiled jinja templates across generators ([#1235](https://github.com/tefra/xsdata/pull/1235)) - Add third-party license notices for bundled schemas and graphs util ([#1238](https://github.com/tefra/xsdata/pull/1238)) tefra-xsdata-cf7afe8/THIRD_PARTY_LICENSES.md000066400000000000000000000033561526437326600203470ustar00rootroot00000000000000# Third-party licenses xsdata includes material from the following third-party sources. ## W3C schemas The bundled schemas in `xsdata/schemas/` are copied from or derived from W3C documents. See [`xsdata/schemas/LICENSE.W3C`](xsdata/schemas/LICENSE.W3C) for the list of files, their sources, and the W3C Software and Document License. ## Strongly connected components `xsdata/utils/graphs.py` is derived from: - [ActiveState recipe 578507](https://code.activestate.com/recipes/578507/), Copyright 2013 Mark Dickinson - [mypy](https://github.com/python/mypy/blob/master/mypy/graph_utils.py), Copyright (c) 2012-2023 Jukka Lehtosalo and contributors, Copyright (c) 2015-2023 Dropbox, Inc. Both are distributed under the MIT License: ``` Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` tefra-xsdata-cf7afe8/benchmark.py000066400000000000000000000041421526437326600172050ustar00rootroot00000000000000import argparse import statistics from timeit import Timer from tests import xsdata_temp_dir from tests.integration.benchmarks.utils import ( make_books, parse, parse_json, write, write_json, ) from xsdata.formats.dataclass.parsers import JsonParser, handlers from xsdata.formats.dataclass.serializers import JsonSerializer, writers if __name__ == "__main__": components = [ "LxmlEventHandler", "XmlEventHandler", "LxmlEventWriter", "XmlEventWriter", "JsonParser", "JsonSerializer", ] parser = argparse.ArgumentParser() parser.add_argument("-c", "--component", choices=components, required=True) parser.add_argument("-n", "--number", default=1000, type=int) parser.add_argument("-r", "--repeat", default=10, type=int) args = parser.parse_args() if args.component in writers.__all__: component = getattr(writers, args.component) books = make_books(args.number) t = Timer(lambda: write(args.number, books, component)) elif args.component in handlers.__all__: fixture = xsdata_temp_dir.joinpath(f"benchmark_{args.number}.xml") if not fixture.exists(): write(args.number, make_books(args.number), writers.XmlEventWriter) component = getattr(handlers, args.component) t = Timer(lambda: parse(fixture.read_bytes(), component)) elif args.component == "JsonParser": component = JsonParser fixture = xsdata_temp_dir.joinpath(f"benchmark_{args.number}.json") if not fixture.exists(): write_json(args.number, make_books(args.number)) t = Timer(lambda: parse_json(fixture.read_bytes())) elif args.component == "JsonSerializer": component = JsonSerializer books = make_books(args.number) t = Timer(lambda: write_json(args.number, books)) print(f"Benchmark {component.__name__} - n{args.number}/r{args.repeat}") result = t.repeat(repeat=args.repeat, number=1) print(f"avg {statistics.mean(result)}") print(f"med {statistics.median(result)}") print(f"stdev {statistics.stdev(result)}") tefra-xsdata-cf7afe8/docs/000077500000000000000000000000001526437326600156305ustar00rootroot00000000000000tefra-xsdata-cf7afe8/docs/__init__.py000066400000000000000000000000001526437326600177270ustar00rootroot00000000000000tefra-xsdata-cf7afe8/docs/api/000077500000000000000000000000001526437326600164015ustar00rootroot00000000000000tefra-xsdata-cf7afe8/docs/api/.gitkeep000066400000000000000000000000001526437326600200200ustar00rootroot00000000000000tefra-xsdata-cf7afe8/docs/changelog.md000066400000000000000000000000241526437326600200750ustar00rootroot00000000000000--8<-- "CHANGES.md" tefra-xsdata-cf7afe8/docs/codegen/000077500000000000000000000000001526437326600172345ustar00rootroot00000000000000tefra-xsdata-cf7afe8/docs/codegen/architecture.md000066400000000000000000000120021526437326600222330ustar00rootroot00000000000000# Architecture The [ResourceTransformer][xsdata.codegen.transformer.ResourceTransformer] is the orchestrator of the code generation procedure. ```mermaid graph LR A[Load Resources] --> B(Parse transfer objects) B --> C[Convert to classes] C --> D[Analyze classes] D--> E[Write Output] ``` ## Load Resource The code generator accepts URIs indicating either local or remote file locations. The resource type (xsd, wsdl, dtd, xml, json) is identified based on the file extension, if present. If the resource lacks an extension, the loader will attempt to locate specific syntax markings associated with the resource type. If a resource cannot be accessed, a warning is issued, and the program continues its normal flow. In the case of circular imports, resources are loaded only once. ## Parse transfer objects A resource-specific parser is utilized to bind document information to transfer objects. Additionally, the parsers are responsible for assigning common values required for later analysis, such as locations, a namespace prefix-URI map, and common namespaces like xsi and xlink. - XSD: [SchemaParser][xsdata.codegen.parsers.SchemaParser] - DTD: [DtdParser][xsdata.codegen.parsers.DtdParser] - WSDL: [DefinitionsParser][xsdata.codegen.parsers.DefinitionsParser] - XML: [TreeParser][xsdata.formats.dataclass.parsers.TreeParser] - JSON: [json.loads][] ## Convert to classes A resource-specific parser is utilized to convert the transfer objects to codegen classes. These mappers encapsulate the pertinent logic detailing how the resource types should be interpreted. - XSD: [SchemaMapper][xsdata.codegen.mappers.SchemaMapper] - DTD: [DtdMapper][xsdata.codegen.mappers.DtdMapper] - WSDL: [DefinitionsMapper][xsdata.codegen.mappers.DefinitionsMapper] - XML: [ElementMapper][xsdata.codegen.mappers.ElementMapper] - JSON: [DictMapper][xsdata.codegen.mappers.DictMapper] ## Analyze classes ```mermaid graph LR A[Validate classes] --> B(Process classes) B --> C[Validate class references] ``` ### Validate Classes - Remove types with unknown references ```xml ``` - Remove duplicate types: Keep the last definition ```xml ``` - Remove duplicate overridden types: ```xml ``` - Merge redefined types: ```xml ``` API: [xsdata.codegen.validator.ClassValidator][] ### Analyze Classes The classes are wrapped in a [ClassContainer][xsdata.codegen.container.ClassContainer] instance. It includes some easy finder methods and orchestrates flattening/filtering processes. The process is divided into multiple steps and handlers per step. All classes have to pass through each step before next one starts. The order of the steps is very important! ### Step: Ungroup - [FlattenAttributeGroups][xsdata.codegen.handlers.FlattenAttributeGroups] ### Step: Flatten - [CalculateAttributePaths][xsdata.codegen.handlers.CalculateAttributePaths] - [FlattenClassExtensions][xsdata.codegen.handlers.FlattenClassExtensions] - [SanitizeEnumerationClass][xsdata.codegen.handlers.SanitizeEnumerationClass] - [UpdateAttributesEffectiveChoice][xsdata.codegen.handlers.UpdateAttributesEffectiveChoice] - [UnnestInnerClasses][xsdata.codegen.handlers.UnnestInnerClasses] - [AddAttributeSubstitutions][xsdata.codegen.handlers.AddAttributeSubstitutions] - [ProcessAttributeTypes][xsdata.codegen.handlers.ProcessAttributeTypes] - [MergeAttributes][xsdata.codegen.handlers.MergeAttributes] - [ProcessMixedContentClass][xsdata.codegen.handlers.ProcessMixedContentClass] ### Step: Filer - [FilterClasses][xsdata.codegen.handlers.FilterClasses] ### Step: Sanitize - [ResetAttributeSequences][xsdata.codegen.handlers.ResetAttributeSequences] - [RenameDuplicateAttributes][xsdata.codegen.handlers.RenameDuplicateAttributes] ### Step: Resolve - [ValidateAttributesOverrides][xsdata.codegen.handlers.ValidateAttributesOverrides] ### Step: Vacuum - [VacuumInnerClasses][xsdata.codegen.handlers.VacuumInnerClasses] ### Step: Finalize - [DetectCircularReferences][xsdata.codegen.handlers.DetectCircularReferences] - [CreateCompoundFields][xsdata.codegen.handlers.CreateCompoundFields] - [CreateWrapperFields][xsdata.codegen.handlers.CreateWrapperFields] - [DisambiguateChoices][xsdata.codegen.handlers.DisambiguateChoices] - [SanitizeAttributesDefaultValue][xsdata.codegen.handlers.SanitizeAttributesDefaultValue] - [ResetAttributeSequenceNumbers][xsdata.codegen.handlers.ResetAttributeSequenceNumbers] ### Step: Designate - [RenameDuplicateClasses][xsdata.codegen.handlers.RenameDuplicateClasses] - [ValidateReferences][xsdata.codegen.handlers.ValidateReferences] - [DesignateClassPackages][xsdata.codegen.handlers.DesignateClassPackages] tefra-xsdata-cf7afe8/docs/codegen/config.md000066400000000000000000000254531526437326600210340ustar00rootroot00000000000000# Configuration The command line tool includes most but not all available options. The most advanced settings can be enabled through a project configuration file. ## Create or Update You can create a default one with the command: ```console exec="1" source="console" $ xsdata init-config --help ``` **Output:** `.xsdata.xml ` ```python exec="true" result="xml" from io import StringIO from xsdata.models.config import GeneratorConfig config = GeneratorConfig.create() output = StringIO() config.write(output, config) print(output.getvalue()) ``` **Usage** ```console $ xsdata generate --config project/.xsdata.xml ``` !!! Info "CLI options override the project configuration settings." ## Output Settings Configuration settings related to output generation. ### `maxLineLength` The maximum line length of the generated code. **Default Value:** `79` **CLI Option:** `-mll, --max-line-length INTEGER` ### `genericCollections` Use `collections.abc.Iterable` and `collections.abc.Mapping` instead of `List|Tuple` and `Dict` **Default Value:** `False` **CLI Option:** `--generic-collections / --no-generic-collections` ### Package The output package for the generated code, e.g. `code.models` **Default Value:** `generated` **CLI Option:** `-p, --package TEXT` !!! Warning Formatting and linting is executed on any directory that contains a Python file created during generation, including other Python files in the same directory prior to generation. As such it is recommended that the directories represented by this option do not include any previously created files. ### Format The output format for the generated code, e.g. `code.models` **Default Value:** `dataclasses` **CLI Option:** `-o, --output TEXT` **Attributes** The [dataclass][dataclasses.dataclass] parameters. - `repr`: Generate the [**repr**][object.__repr__] method. - `eq`: Generate the [**eq**][object.__eq__] method. - `order`: Generates the [**lt**][object.__lt__], [**le**][object.__le__], [**gt**][object.__gt__], [**ge**][object.__ge__] methods. - `frozen`: This emulates read-only frozen instances. - `unsafeHash`: Generates a [**hash**][object.__hash__] method according to how `eq` and `frozen` are set. - `slots`: Generates the class [**slots**][object.__slots__] attribute. `python >= 3.10` !!! Warning A TypeError is raised if a field without a default value follows a field with a default value. This is true whether this occurs in a single class, or as a result of class inheritance. If this option is not enabled, the generator will mark all required fields without default values as optional with default value `None`. ### Structure The file structure style to create. | Style | Description | | -------------------- | ------------------------------------------------------------------------------------------------------------------------- | | `filenames` | Group classes by the resource file location. | | `namespaces` | Group classes by the target namespace. | | `clusters` | Group classes by strongly connected dependencies. The closest thing to one-class-per-package, safe from circular imports. | | `single-package` | Group classes in a single package. Safe from circular imports, but can create some huge files. | | `namespace-clusters` | Group classes by strongly connected dependencies and target namespaces. | **Default Value:** `filenames` **CLI Option:** `-ss, --structure-style [filenames|namespaces|clusters|single-package|namespace-clusters]` ### DocstringStyle The style of docstrings to create. **Default Value:** `reStructuredText` **CLI Option:** `-ds, --docstring-style [reStructuredText|NumPy|Google|Accessible|Blank]` ### RelativeImports Generate relative instead of absolute imports. **Default Value:** `False` **CLI Option:** `--relative-imports / --no-relative-imports` ### CompoundFields xsdata relies on the field ordering for serialization. This process fails for repeating choice or complex sequence elements. When you enable compound fields, these elements are grouped into a single field. ```xml --8<-- "tests/fixtures/compound/schema.xsd:2:10" ``` **Default Value:** `False` **CLI Option:** `--compound-fields / --no-compound-fields` **Sub Settings** - `defaultName`: The default compound field name, default: `choice` - `forceDefaultName`: Force the default name in all compound fields, default: `False` - `useSubstitutionGroups`: When all elements are part of a substitution, use the group name as the field name, default: `False` - `maxNameParts`: The maximum length of elements names allowed before using the default name, default: `3` **Examples:** ```python # Force default name or max name parts > 3 choice: list[str | int | float | bool] = field(...) # max name parts <= 3 hat_or_bat_cat: list[str | int | float] = field(...) # All types belong to the same substitution group `product` product: list[Shoe | Shirt | Hat] = field(...) ``` ### WrapperFields Generate wrapper fields whenever possible for single or collections of simple and complex elements. The wrapper and wrapped elements can't be optional. If the wrapped value is a list it must have minimum `occurs >= 1`. ```xml --8<-- "tests/fixtures/wrapper/schema.xsd:2:17" ``` **Default Value:** `False` **CLI Option:** `--wrapper-fields / --no-wrapper-fields` **Examples:** ```python alpha: str = field( metadata={ "wrapper": "alphas", "type": "Element", }, ) bravo: List[int] = field( default_factory=list, metadata={ "wrapper": "bravos", "type": "Element", }, ) charlie: List[Charlie] = field( default_factory=list, metadata={ "wrapper": "charlies", "type": "Element", }, ) ``` ### UnnestClasses The generator creates inner classes for `xs:complexContent`. This option allows to unnest all inner classes. **Default Value:** `False` **CLI Option:** `--unnest-classes / --no-unnest-classes` ### IgnorePatterns The generator will create a field metadata property for `xs:pattern` elements. This property is not used during parsing, it's only informative for the developer, if you want to reduce the noise in the generated code you can enable this option. **Default Value:** `False` **CLI Option:** `--ignore-patterns / --no-ignore-patterns` ### IncludeHeader The generator will add a module docstring in all the output files. **Example** ```python """This file was generated by xsdata, v24.1, on 2024-01-22 10:20:25 Generator: DataclassGenerator See: https://xsdata.readthedocs.io/ """ ``` **Default Value:** `False` **CLI Option:** `--include-header / --no-include-header` ## Convention Settings Apply different naming convention per identifier. | Element | Default Case | Default Safe Prefix | | -------------- | -------------------- | ------------------- | | `ClassName` | `pascalCase` | `type` | | `FieldName` | `snakeCase` | `value` | | `ConstantName` | `screamingSnakeCase` | `value` | | `ModuleName` | `snakeCase` | `mod` | | `PackageName` | `snakeCase` | `pkg` | **Attributes** - `case`: The naming class to apply - `safePrefix`: A prefix to add when the output name is reserved **Cases** | Case | Input | Output | | -------------------- | --------- | --------- | | `originalCase` | `aBBc` | `aBBc` | | `pascalCase` | `my_type` | `MyType` | | `camelCase` | `my_type` | `myType` | | `snakeCase` | `MyType` | `my_type` | | `screamingSnakeCase` | `MyType` | `My_Type` | | `mixedCase` | `MY_TyPE` | `MYTyPE` | | `mixedSnakeCase` | `MyType` | `My_Type` | | `mixedPascalCase` | `my_TYPE` | `MyTYPE` | ## Substitution Settings A list of search and replace patterns for identifier names, the substitutions run **before** and **after** the naming conventions. **Attributes** - `type`: The identifier type `[class|field|module|package]` - `search`: Search Pattern - `replace`: Replace Pattern **Defaults** ```xml ``` ## Extension Settings Though extensions you can add base classes, mixins or decorators to the generated classes. This way you can enhance the models functionality and add any custom business logic. The following configuration will add a base class and a decorator to all the generated classes. **Attributes** - `type`: The extension type `[class|decorator]` - `class`: The class name search pattern - `import`: The absolute import of the base class or decorator object - `prepend` Specify if you want the base class or decorator to added before all other - `apply_if_derived` Specify if you want to add the extension if the class already extends another class. - `module` Optional pattern to match against the fully-qualified parent element's name !!! Warning If there are two extensions of the same type for the same class with the `prepend==True`, the base classes or decorators are added in the reverse order they are defined in the configuration. **Example:** ```xml ``` ```python from dataclasses import dataclass from dataclasses_jsonschema import JsonSchemaMixin from typed_dataclass import typed_dataclass @dataclass @typed_dataclass class Cores(JsonSchemaMixin): ... ``` tefra-xsdata-cf7afe8/docs/codegen/docstrings.md000066400000000000000000000012731526437326600217400ustar00rootroot00000000000000# Docstring styles xsdata follows [PEP 257](https://www.python.org/dev/peps/pep-0257/) docstring conventions and offers the ability to switch between the most popular styles or disable them completely. === "reStructuredText" ```python --8<-- "tests/fixtures/docstrings/rst/schema.py:31:" ``` === "NumPy" ```python --8<-- "tests/fixtures/docstrings/numpy/schema.py:31:" ``` === "Google" ```python --8<-- "tests/fixtures/docstrings/google/schema.py:31:" ``` === "Accessible" ```python --8<-- "tests/fixtures/docstrings/accessible/schema.py:31:" ``` === "Blank" ```python --8<-- "tests/fixtures/docstrings/blank/schema.py:21:" ``` tefra-xsdata-cf7afe8/docs/codegen/download_schemas.md000066400000000000000000000021321526437326600230660ustar00rootroot00000000000000# Download Schemas Generating from remote resources is not a great idea, the cli includes a command to download schemas and wsdl locally. The command will download any included schemas recursively. ```console exec="1" source="console" $ xsdata download --help ``` **Example** ```console ❯ xsdata download https://www.w3.org/Math/XMLSchema/mathml3/mathml3.xsd -o ~/schemas ========= xsdata v24.6.1 / Python 3.11.8 / Platform linux ========= Setting base path to https:/www.w3.org/Math/XMLSchema/mathml3 Fetching https://www.w3.org/Math/XMLSchema/mathml3/mathml3.xsd Fetching https://www.w3.org/Math/XMLSchema/mathml3/mathml3-content.xsd Fetching https://www.w3.org/Math/XMLSchema/mathml3/mathml3-strict-content.xsd Writing /home/chris/schemas/mathml3-strict-content.xsd Writing /home/chris/schemas/mathml3-content.xsd Fetching https://www.w3.org/Math/XMLSchema/mathml3/mathml3-presentation.xsd Writing /home/chris/schemas/mathml3-presentation.xsd Fetching https://www.w3.org/Math/XMLSchema/mathml3/mathml3-common.xsd Writing /home/chris/schemas/mathml3-common.xsd Writing /home/chris/schemas/mathml3.xsd ``` tefra-xsdata-cf7afe8/docs/codegen/dtd_modeling.md000066400000000000000000000011511526437326600222050ustar00rootroot00000000000000# DTD Modeling The code generator supports processing **external** document type definitions (DTD), with `lxml`. ```console $ pip install xsdata[lxml] ``` !!! Warning "DTDParseError: error parsing DTD" Try removing the `DOCTYPE` wrapper if you are sure the rest of the definition is correct. ## Example ```console $ xsdata generate --package tests.fixtures.dtd.models tests/fixtures/dtd/complete_example.dtd ``` === "DTD Definition" ```dtd --8<-- "tests/fixtures/dtd/complete_example.dtd" ``` === "Output" ```python --8<-- "tests/fixtures/dtd/models/complete_example.py" ``` tefra-xsdata-cf7afe8/docs/codegen/intro.md000066400000000000000000000016641526437326600207200ustar00rootroot00000000000000# Introduction The code generator works with: - XML Schemas v1.0 & v1.1 - WSDL v1.1 definitions with SOAP v1.1 bindings - DTD external definitions - Directly from XML and JSON Documents The [xsdata.codegen.transformer][xsdata.codegen.transformer] will detect and switch parsers based on the resource extension otherwise it will do some heuristic content searches. Before you start make sure the cli requirements are installed. ```console $ pip install xsdata[cli] ``` ## Command Line Tool ```console exec="1" source="console" $ xsdata generate --help ``` ## Use a directory as source You can instruct the cli to search all subdirectories recursively with the `-r, --recursive` flag. ```console $ xsdata generate project/schemas $ xsdata generate project/schemas --recursive ``` ## Use a filename or URI as source ```console $ xsdata generate project/schemas/feed.xsd $ xsdata generate http://www.gstatic.com/localfeed/local_feed.xsd ``` tefra-xsdata-cf7afe8/docs/codegen/samples_modeling.md000066400000000000000000000022151526437326600231000ustar00rootroot00000000000000# Samples Modeling The code generator supports processing xml/json documents directly. That means even without a schema you can easily create at the very least an initial draft of your models just from samples. If you use a directory with multiple samples the transformer will merge and flatten duplicate classes, fields and field types. ## XML Documents ```console $ xsdata generate --package tests.fixtures.artists tests/fixtures/artists ``` === "Sample #1" ```xml --8<-- "tests/fixtures/artists/art001.xml" ``` === "Sample #2" ```xml --8<-- "tests/fixtures/artists/art002.xml" ``` === "Sample #3" ```xml --8<-- "tests/fixtures/artists/art003.xml" ``` === "Output" ```python --8<-- "tests/fixtures/artists/metadata.py" ``` ## JSON Documents ```console $ xsdata generate --package tests.fixtures.series tests/fixtures/series/samples ``` === "Sample #1" ```json --8<-- "tests/fixtures/series/samples/show1.json" ``` === "Sample #2" ```json --8<-- "tests/fixtures/series/samples/show2.json" ``` === "Output" ```python --8<-- "tests/fixtures/series/series.py" ``` tefra-xsdata-cf7afe8/docs/codegen/wsdl_modeling.md000066400000000000000000000077311526437326600224150ustar00rootroot00000000000000# WSDL Modeling The implementation is experimental and currently only **WSDL 1.1 and SOAP 1.1** bindings have been implemented. In addition to models derived from XML schemas, the code generator also generates dataclasses for messages and simple classes to describe the unique operations. Make sure you install both the CLI and SOAP requirements. ```console $ pip install xsdata[cli,soap] ``` ```console $ xsdata generate --package calculator http://www.dneonline.com/calculator.asmx?WSDL ``` ## Message Model The message models are no different from XSD-derived classes and include the complete structure of the `Envelope` wrapper. ```python --8<-- "tests/fixtures/calculator/services.py:148:169" ``` ## Operation Class The [DefinitionsMapper][xsdata.codegen.mappers.DefinitionsMapper] will generate simple static classes to describe all the unique operations and the binding procedure. ```python --8<-- "tests/fixtures/calculator/services.py:488:494" ``` ## Client The [Client][xsdata.formats.dataclass.client.Client] is a proxy for consuming web services. The client needs a web service [Config][xsdata.formats.dataclass.client.Config] with the directives to process requests and responses. **You can also optionally provide and override:** - A [Transport][xsdata.formats.dataclass.transports.Transport] implementation - An [XmlParser][xsdata.formats.dataclass.parsers.XmlParser] instance - An [XmlSerializer][xsdata.formats.dataclass.serializers.XmlSerializer] instance ### Creating instances The client can be initialized from the operation class directly: ```python >>> from xsdata.formats.dataclass.client import Client >>> from tests.fixtures.calculator import CalculatorSoapAdd >>> client = Client.from_service(CalculatorSoapAdd) >>> client.config Config(style='document', location='http://www.dneonline.com/calculator.asmx', transport='http://schemas.xmlsoap.org/soap/http', soap_action='http://tempuri.org/Add', input=, output=, encoding=None) ``` You can also override any properties as needed: ```python >>> client = Client.from_service(CalculatorSoapAdd, location="http://testurl.com") >>> client.config Config(style='document', location='http://testurl.com', transport='http://schemas.xmlsoap.org/soap/http', soap_action='http://tempuri.org/Add', input=, output=, encoding=None) ``` Or you can configure the client manually: ```python config = Config( style="document", location="", transport=TransportTypes.SOAP, soap_action="", input=None, output=None, ) client = Client(config=config) ``` ### Override transport Initialize the transport with a custom requests session instance: ```python from requests import Session transport = DefaultTransport(session=Session()) client = Client(config=config, transport=transport) ``` ### Performing Requests The send method requires either an object that matches the config input type or a dictionary with raw values that matches the input dataclass field names and structure. ```python request = CalculatorSoapAddInput(body=CalculatorSoapAddInput.Body(add=Add(10, 2))) client.send(request) # CalculatorSoapAddOutput(body=CalculatorSoapAddOutput.Body(add_response=AddResponse(add_result=12))) ``` ```python client = Client.from_service(CalculatorSoapAdd) params = {"body": {"add": {"int_a": 3, "int_b": 4}}} client.send(params) # CalculatorSoapAddOutput(body=CalculatorSoapAddOutput.Body(add_response=AddResponse(add_result=7))) ``` You can also provide a dictionary of custom headers, although headers required for the web service to function cannot be overwritten: ```python client.send(params, headers={"User-Agent": "xsdata"}) ``` You need to specify an encoding if you intend to send non-ASCII characters: ```python client = Client.from_service(encoding="utf-8") ``` tefra-xsdata-cf7afe8/docs/conftest.py000066400000000000000000000001001526437326600200160ustar00rootroot00000000000000import os import sys sys.path.insert(0, os.path.abspath("./")) tefra-xsdata-cf7afe8/docs/data_binding/000077500000000000000000000000001526437326600202335ustar00rootroot00000000000000tefra-xsdata-cf7afe8/docs/data_binding/basics.md000066400000000000000000000165421526437326600220310ustar00rootroot00000000000000# Basics ## Context All binding metadata is generated and cached in a [XmlContext][xsdata.formats.dataclass.context.XmlContext] instance. It's recommended to either reuse the same parser/serializer instance or reuse the context instance. ```python >>> from xsdata.formats.dataclass.context import XmlContext >>> from xsdata.formats.dataclass.parsers import XmlParser >>> from xsdata.formats.dataclass.parsers import JsonParser >>> from xsdata.formats.dataclass.serializers import XmlSerializer >>> from xsdata.formats.dataclass.serializers import JsonSerializer >>> context = XmlContext() >>> xml_parser = XmlParser(context=context) >>> json_parser = JsonParser(context=context) >>> xml_serializer = XmlSerializer(context=context) >>> json_serializer = JsonSerializer(context=context) ``` ### Global Property Names Through the [XmlContext][xsdata.formats.dataclass.context.XmlContext] instance you can provide callables to apply a naming scheme for all models and their fields. This way you can avoid declaring them for every model, but you have to use the same context whenever you want to use a parser/serializer. ```python >>> from dataclasses import dataclass, field >>> from datetime import date >>> from xsdata.utils import text >>> from xsdata.formats.dataclass.context import XmlContext ... >>> @dataclass ... class Person: ... ... first_name: str ... last_name: str ... birth_date: date = field( ... metadata=dict( ... type="Attribute", ... format="%Y-%m-%d" ... ) ... ) ... >>> obj = Person( ... first_name="Chris", ... last_name="T", ... birth_date=date(1986, 9, 25), ... ) ... >>> context = XmlContext( ... element_name_generator=text.camel_case, ... attribute_name_generator=text.kebab_case ... ) >>> serializer = XmlSerializer(context=context) >>> serializer.config.indent = " " >>> print(serializer.render(obj)) Chris T ``` ## Parser Config API: [ParserConfig][xsdata.formats.dataclass.parsers.config.ParserConfig] ### `base_url` Specifies a base URL when parsing from memory and the parser has to import included documents. e.g. [XML Inclusions](xml_parsing.md#xml-inclusions) **Type:** `Optional[str]` **Default:** `None` ### `process_xinclude` Enable [XML Inclusions](xml_parsing.md#xml-inclusions) processing. The parser will follow and retrieve the remote documents. **Type:** `bool` **Default:** `False` ### `load_dtd` Enable loading external dtd with [LxmlEventHandler][xsdata.formats.dataclass.parsers.handlers.LxmlEventHandler]. **Type:** `bool` **Default:** `False` ### `resolve_entities` Enable loading of external reference within XML documents with [LxmlEventHandler][xsdata.formats.dataclass.parsers.handlers.LxmlEventHandler]. Enabling this option has security implications: the code becomes vulnerable for External Entity Injection (XXE). **Type:** `bool` **Default:** `False` ### `class_factory` Override default object instantiation, to apply pre/post-initialization logic. ```python >>> from dataclasses import dataclass >>> from xsdata.formats.dataclass.parsers import JsonParser >>> from xsdata.formats.dataclass.parsers.config import ParserConfig ... >>> def custom_class_factory(clazz, params): ... if clazz.__name__ == "Person": ... return clazz(**{k: v.upper() for k, v in params.items()}) ... ... return clazz(**params) ... >>> config = ParserConfig(class_factory=custom_class_factory) >>> parser = JsonParser(config=config) ... >>> @dataclass ... class Person: ... first_name: str ... last_name: str ... >>> json_str = """{"first_name": "chris", "last_name": "foo"}""" ... ... >>> print(parser.from_string(json_str, Person)) Person(first_name='CHRIS', last_name='FOO') ``` **Type:** `Callable[[Type[T], Dict[str, Any]], T]` **Default:** `lambda: cls, params: return cls(**params)` ### `fail_on_unknown_properties` Fail if the document includes properties not defined in the model. **Type:** `bool` **Default:** `True` ### `fail_on_unknown_attributes` Fail if the document includes attributes not defined in the model. **Type:** `bool` **Default:** `False` ### `fail_on_converter_warnings` Fail if a document value can't be correctly converted to python. **Type:** `bool` **Default:** `False` ## Serializer Config API: [SerializerConfig][xsdata.formats.dataclass.serializers.config.SerializerConfig] ### `encoding` The text encoding for the XML header. ```xml ``` **Type**: `str` **Default:** `"UTF-8"` ### `xml_version` The XML version number to render `(1.0|1.1)`. ```xml ``` **Type**: `str` **Default:** `"1.0"` ### `xml_declaration` Renders the XML declaration header. **Type**: `bool` **Default:** `True` ### `indent` Indent output by the given string. **Type**: `Optional[str]` **Default:** `None` ### `ignore_default_attributes` Ignore optional attributes with default values. **Type**: `bool` **Default:** `False` ### `schema_location` Render a `xsi:schemaLocation` attribute on the root element. ```xml ``` **Type**: `Optional[str]` **Default:** `None` ### `globalns` Dictionary containing global variables to extend or overwrite for typing. When managing a big collection of models, it is sometimes tricky to split them into multiple python modules. Even more so if they depend on each other. For the models to be serializable by xsdata, they need to be able to import all other referenced models, which might not be possible due to circular imports. One solution to get around this problem is to fence the imports within the python modules by using [typing.TYPE_CHECKING][] and use this option to manually define the types. === "city.py" ```python --8<-- "tests/fixtures/typemapping/city.py" ``` === "street.py" ```python --8<-- "tests/fixtures/typemapping/street.py" ``` === "house.py" ```python --8<-- "tests/fixtures/typemapping/house.py" ``` ```python >>> from xsdata.formats.dataclass.serializers import XmlSerializer >>> from xsdata.formats.dataclass.serializers.config import SerializerConfig >>> from tests.fixtures.typemapping.city import City >>> from tests.fixtures.typemapping.house import House >>> from tests.fixtures.typemapping.street import Street >>> >>> city1 = City(name="footown") >>> street1 = Street(name="foostreet") >>> house1 = House(number=23) >>> city1.streets.append(street1) >>> street1.houses.append(house1) >>> type_map = {"City": City, "Street": Street, "House": House} >>> serializer_config = SerializerConfig(indent=" ", globalns=type_map) >>> xml_serializer = XmlSerializer(config=serializer_config) >>> serialized_house = xml_serializer.render(city1) >>> print(serialized_house) footown foostreet 23 ``` **Type**: `Optional[Dict[str, Callable]]` None tefra-xsdata-cf7afe8/docs/data_binding/dict_decoding.md000066400000000000000000000033431526437326600233370ustar00rootroot00000000000000# Dict Decoding The [DictDecoder][xsdata.formats.dataclass.parsers.DictDecoder] can be used to bind a dictionary or a list of dictionaries to data models. It's the backbone of the [JsonParser][xsdata.formats.dataclass.parsers.JsonParser] without the loading entrypoints. ## Single object ```python >>> from xsdata.formats.dataclass.context import XmlContext >>> from xsdata.formats.dataclass.parsers import DictDecoder >>> from xsdata.formats.dataclass.parsers.config import ParserConfig >>> from tests.fixtures.books.books import BookForm >>> >>> config = ParserConfig() >>> context = XmlContext() >>> decoder = DictDecoder(context=context, config=config) >>> >>> data = { ... "author": "Hightower, Kim", ... "title": "The First Book", ... "genre": "Fiction", ... "price": 44.95, ... "pub_date": "2000-10-01", ... "review": "An amazing story of nothing.", ... "id": "bk001" ... } >>> book = decoder.decode(data, BookForm) >>> book.author 'Hightower, Kim' ``` ## List of objects ```python >>> from typing import List >>> from tests.fixtures.books.books import BookForm >>> >>> data = [ ... { ... "author": "Hightower, Kim", ... "title": "The First Book", ... "genre": "Fiction", ... "price": 44.95, ... "pub_date": "2000-10-01", ... "review": "An amazing story of nothing.", ... "id": "bk001" ... }, ... { ... "author": "Nagata, Suanne", ... "title": "Becoming Somebody", ... "genre": "Biography", ... "price": None, ... "pub_date": None, ... "review": "A masterpiece of the fine art of gossiping.", ... "id": "bk002" ... } ... ] >>> booklist = decoder.decode(data, List[BookForm]) >>> booklist[1].author 'Nagata, Suanne' ``` tefra-xsdata-cf7afe8/docs/data_binding/dict_encoding.md000066400000000000000000000035771526437326600233620ustar00rootroot00000000000000# Dict Encoding The [DictEncoder][xsdata.formats.dataclass.serializers.DictEncoder] converts a data model instance to a dictionary and encodes all values to make them safe for serialization. ```python >>> from xsdata.formats.dataclass.context import XmlContext >>> from xsdata.formats.dataclass.serializers import DictEncoder >>> from xsdata.formats.dataclass.serializers.config import SerializerConfig >>> config = SerializerConfig(indent=" ") >>> context = XmlContext() >>> encoder = DictEncoder() >>> encoder = DictEncoder(context=context, config=config) ``` ## Example ```python >>> import pprint >>> from xsdata.models.datatype import XmlDate >>> from tests.fixtures.books import BookForm >>> >>> book = BookForm( ... id="bk001", ... author="Hightower, Kim", ... title="The First Book", ... genre="Fiction", ... price=44.95, ... pub_date=XmlDate(2000, 10, 1), ... review="An amazing story of nothing.", ... ) >>> pprint.pprint(encoder.encode(book)) {'author': 'Hightower, Kim', 'genre': 'Fiction', 'id': 'bk001', 'lang': 'en', 'price': 44.95, 'pub_date': '2000-10-01', 'review': 'An amazing story of nothing.', 'title': 'The First Book'} ``` ## Custom dict factory By using a custom dict factory you can change the output behaviour, like filter out `None` values. ```python >>> from typing import Dict, Tuple >>> >>> def filter_none(x: Tuple) -> Dict: ... return {k: v for k, v in x if v is not None} >>> >>> encoder = DictEncoder(dict_factory=filter_none) >>> pprint.pprint(encoder.encode(book)) {'author': 'Hightower, Kim', 'genre': 'Fiction', 'id': 'bk001', 'lang': 'en', 'price': 44.95, 'pub_date': '2000-10-01', 'review': 'An amazing story of nothing.', 'title': 'The First Book'} ``` or conveniently ```python >>> from xsdata.formats.dataclass.serializers import DictFactory >>> >>> encoder = DictEncoder(dict_factory=DictFactory.FILTER_NONE) ``` tefra-xsdata-cf7afe8/docs/data_binding/json_parsing.md000066400000000000000000000106551526437326600232600ustar00rootroot00000000000000# JSON Parsing ```python >>> from xsdata.formats.dataclass.context import XmlContext >>> from xsdata.formats.dataclass.parsers import JsonParser >>> from xsdata.formats.dataclass.parsers.config import ParserConfig >>> config = ParserConfig() >>> context = XmlContext() >>> parser = JsonParser(context=context, config=config) >>> parser = JsonParser() ``` ## Filenames ```python >>> from tests.fixtures.books.books import Books ... >>> books = parser.parse("tests/fixtures/books/books.json", Books) >>> books.book[0] BookForm(author='Hightower, Kim', title='The First Book', genre='Fiction', price=44.95, pub_date=XmlDate(2000, 10, 1), review='An amazing story of nothing.', id='bk001', lang='en') ``` ## File-like objects ```python >>> with open("tests/fixtures/books/books.json", "rb") as fp: ... books = parser.parse(fp, Books) >>> books.book[0] BookForm(author='Hightower, Kim', title='The First Book', genre='Fiction', price=44.95, pub_date=XmlDate(2000, 10, 1), review='An amazing story of nothing.', id='bk001', lang='en') ``` ## String ```python >>> from pathlib import Path >>> >>> json_string = Path("tests/fixtures/books/books.json").read_text() >>> books = parser.from_string(json_string, Books) >>> books.book[0] BookForm(author='Hightower, Kim', title='The First Book', genre='Fiction', price=44.95, pub_date=XmlDate(2000, 10, 1), review='An amazing story of nothing.', id='bk001', lang='en') ``` ## Bytes ```python >>> json_bytes = Path("tests/fixtures/books/books.json").read_bytes() >>> books = parser.from_bytes(json_bytes, Books) >>> books.book[0] BookForm(author='Hightower, Kim', title='The First Book', genre='Fiction', price=44.95, pub_date=XmlDate(2000, 10, 1), review='An amazing story of nothing.', id='bk001', lang='en') ``` ## pathlib.Path ```python >>> file_path = Path("tests/fixtures/books/books.json") >>> books = parser.from_path(file_path, Books) >>> books.book[0] BookForm(author='Hightower, Kim', title='The First Book', genre='Fiction', price=44.95, pub_date=XmlDate(2000, 10, 1), review='An amazing story of nothing.', id='bk001', lang='en') ``` ## Unknown target clazz It's optimal to provide the target class but completely optional. The parser can scan all the imported modules to find a matching dataclass. ```python >>> order = parser.parse("tests/fixtures/books/books.json") >>> type(order) ``` !!! Warning The class locator searches for a dataclass that includes all the input object properties. This process doesn't work for documents with unknown properties even if the configuration option is disabled! ## List of objects Specify the target binding type to `List[ModelName]` ```python >>> from typing import List >>> from tests.fixtures.books.books import BookForm >>> >>> json_string = """[ ... { ... "author": "Hightower, Kim", ... "title": "The First Book", ... "genre": "Fiction", ... "price": 44.95, ... "pub_date": "2000-10-01", ... "review": "An amazing story of nothing.", ... "id": "bk001" ... }, ... { ... "author": "Nagata, Suanne", ... "title": "Becoming Somebody", ... "genre": "Biography", ... "price": null, ... "pub_date": null, ... "review": "A masterpiece of the fine art of gossiping.", ... "id": "bk002" ... } ... ]""" >>> booklist = parser.from_string(json_string, List[BookForm]) >>> booklist[1].author 'Nagata, Suanne' ``` ## Ignore unknown properties By default the parser will fail on unknown properties, but you can disable these errors through configuration. ```python >>> config = ParserConfig( ... fail_on_unknown_properties=False, ... fail_on_unknown_attributes=False, ... ) >>> json_string = """{ ... "author": "Hightower, Kim", ... "title": "The First Book", ... "genre": "Fiction", ... "price": 44.95, ... "pub_date": "2000-10-01", ... "review": "An amazing story.", ... "unknown_property": "I will be ignored" ... }""" >>> parser = JsonParser(config=config) >>> parser.from_string(json_string, BookForm) BookForm(author='Hightower, Kim', title='The First Book', genre='Fiction', price=44.95, pub_date=XmlDate(2000, 10, 1), review='An amazing story.', id=None, lang='en') ``` ## Custom json load factory The default factory is python's builtin :func:`python:json.load` but you can use any other implementation as long as it has a compatible signature. ```python import ujson parser = JsonParser(load_factory=ujson.load) ``` tefra-xsdata-cf7afe8/docs/data_binding/json_serializing.md000066400000000000000000000070171526437326600241330ustar00rootroot00000000000000# JSON Serializing ```python >>> from xsdata.formats.dataclass.context import XmlContext >>> from xsdata.formats.dataclass.serializers import JsonSerializer >>> from xsdata.formats.dataclass.serializers.config import SerializerConfig >>> config = SerializerConfig(indent=" ") >>> context = XmlContext() >>> serializer = JsonSerializer() >>> serializer = JsonSerializer(context=context, config=config) ``` ## Return as string ```python >>> from xsdata.models.datatype import XmlDate >>> from tests.fixtures.books import Books, BookForm >>> >>> books = Books( ... book=[ ... BookForm( ... id="bk001", ... author="Hightower, Kim", ... title="The First Book", ... genre="Fiction", ... price=44.95, ... pub_date=XmlDate(2000, 10, 1), ... review="An amazing story of nothing.", ... ), ... BookForm( ... id="bk002", ... author="Nagata, Suanne", ... title="Becoming Somebody", ... genre="Biography", ... price=33.95, ... pub_date=XmlDate(2001, 1, 10), ... review="A masterpiece of the fine art of gossiping.", ... ), ... ] ... ) >>> print(serializer.render(books)) { "book": [ { "author": "Hightower, Kim", "title": "The First Book", "genre": "Fiction", "price": 44.95, "pub_date": "2000-10-01", "review": "An amazing story of nothing.", "id": "bk001", "lang": "en" }, { "author": "Nagata, Suanne", "title": "Becoming Somebody", "genre": "Biography", "price": 33.95, "pub_date": "2001-01-10", "review": "A masterpiece of the fine art of gossiping.", "id": "bk002", "lang": "en" } ] } ``` ## Write to file-like objects ```python >>> from pathlib import Path ... >>> path = Path("output.json") >>> with path.open("w") as fp: ... serializer.write(fp, books) ... >>> print(path.read_text()) { "book": [ { "author": "Hightower, Kim", "title": "The First Book", "genre": "Fiction", "price": 44.95, "pub_date": "2000-10-01", "review": "An amazing story of nothing.", "id": "bk001", "lang": "en" }, { "author": "Nagata, Suanne", "title": "Becoming Somebody", "genre": "Biography", "price": 33.95, "pub_date": "2001-01-10", "review": "A masterpiece of the fine art of gossiping.", "id": "bk002", "lang": "en" } ] } >>> path.unlink() ``` ## Custom dict factory By using a custom dict factory you can change the output behaviour, like filter out `None` values. ```python >>> from typing import Dict, Tuple >>> >>> def filter_none(x: Tuple) -> Dict: ... return {k: v for k, v in x if v is not None} >>> >>> books.book[0].id = None >>> serializer = JsonSerializer(dict_factory=filter_none, config=config) >>> print(serializer.render(books.book[0])) { "author": "Hightower, Kim", "title": "The First Book", "genre": "Fiction", "price": 44.95, "pub_date": "2000-10-01", "review": "An amazing story of nothing.", "lang": "en" } ``` or conveniently ```python >>> from xsdata.formats.dataclass.serializers import DictFactory >>> >>> serializer = JsonSerializer(dict_factory=DictFactory.FILTER_NONE) ``` ## Custom json dump factory The default factory is python's builtin :func:`python:json.dump` but you can use any other implementation as long as it's has a compatible signature. ```python import ujson serializer = JsonSerializer(dump_factory=ujson.dump) ``` tefra-xsdata-cf7afe8/docs/data_binding/pycode_serializing.md000066400000000000000000000021071526437326600244400ustar00rootroot00000000000000# Pycode Serializer The pycode serializer will render an object tree into python representation code. ```python >>> from tests.fixtures.books.fixtures import books >>> from xsdata.formats.dataclass.serializers import PycodeSerializer ... >>> serializer = PycodeSerializer() >>> print(serializer.render(books, var_name="books")) from tests.fixtures.books.books import BookForm from tests.fixtures.books.books import Books from xsdata.models.datatype import XmlDate books = Books( book=[ BookForm( author='Hightower, Kim', title='The First Book', genre='Fiction', price=44.95, pub_date=XmlDate(2000, 10, 1), review='An amazing story of nothing.', id='bk001' ), BookForm( author='Nagata, Suanne', title='Becoming Somebody', genre='Biography', price=33.95, pub_date=XmlDate(2001, 1, 10), review='A masterpiece of the fine art of gossiping.', id='bk002' ), ] ) ``` tefra-xsdata-cf7afe8/docs/data_binding/tree_serializing.md000066400000000000000000000024061526437326600241160ustar00rootroot00000000000000# Element Tree Serializer The tree serializer renders an object into an lxml element tree that you can use to run XPath evaluations or XSLT transformations. The [TreeSerializer][xsdata.formats.dataclass.serializers.tree.TreeSerializer] depends on lxml. There is no native Python ElementTree implementation due to limitations with namespaces. ## Example ```python >>> from lxml import etree >>> from tests.fixtures.books.fixtures import books >>> from xsdata.formats.dataclass.serializers import TreeSerializer ... >>> serializer = TreeSerializer() >>> serializer.config.indent = " " >>> result = serializer.render(books, ns_map={'bk': "urn:books"}) ... >>> actual = etree.tostring(result) >>> print(actual.decode()) Hightower, Kim The First Book Fiction 44.95 2000-10-01 An amazing story of nothing. Nagata, Suanne Becoming Somebody Biography 33.95 2001-01-10 A masterpiece of the fine art of gossiping. ``` tefra-xsdata-cf7afe8/docs/data_binding/xml_parsing.md000066400000000000000000000124101526437326600230760ustar00rootroot00000000000000# XML Parsing ```python >>> from xsdata.formats.dataclass.context import XmlContext >>> from xsdata.formats.dataclass.parsers import XmlParser >>> from xsdata.formats.dataclass.parsers.config import ParserConfig >>> config = ParserConfig() >>> context = XmlContext() >>> parser = XmlParser(context=context, config=config) >>> parser = XmlParser() ``` ## Filenames ```python >>> from tests.fixtures.primer import PurchaseOrder ... >>> order = parser.parse("tests/fixtures/primer/sample.xml", PurchaseOrder) >>> order.bill_to Usaddress(name='Robert Smith', street='8 Oak Avenue', city='Old Town', state='PA', zip=Decimal('95819'), country='US') ``` ## File-like objects ```python >>> with open("tests/fixtures/primer/sample.xml", "rb") as fp: ... order = parser.parse(fp, PurchaseOrder) >>> order.bill_to.street '8 Oak Avenue' ``` ## String ```python >>> from pathlib import Path >>> >>> xml_string = Path("tests/fixtures/primer/sample.xml").read_text() >>> order = parser.from_string(xml_string, PurchaseOrder) >>> order.bill_to.street '8 Oak Avenue' ``` ## Bytes ```python >>> xml_bytes = Path("tests/fixtures/primer/sample.xml").read_bytes() >>> order = parser.from_bytes(xml_bytes, PurchaseOrder) >>> order.bill_to.street '8 Oak Avenue' ``` ## pathlib.Path ```python >>> file_path = Path("tests/fixtures/primer/sample.xml") >>> order = parser.from_path(file_path, PurchaseOrder) >>> order.bill_to.street '8 Oak Avenue' ``` ## lxml.etree.ElementTree For selective parsing you can use an Element or ElementTree as source. This way you can modify the dom or pick from which node to start binding data. Using [`lxml`][] with [`LxmlEventHandler`][xsdata.formats.dataclass.parsers.handlers.LxmlEventHandler] ```python >>> import lxml >>> from xsdata.formats.dataclass.parsers.handlers import LxmlEventHandler >>> from tests.fixtures.primer import Usaddress ... >>> parser = XmlParser(handler=LxmlEventHandler) >>> tree = lxml.etree.parse("tests/fixtures/primer/sample.xml") >>> bill_to = parser.parse(tree.find('.//billTo'), Usaddress) >>> bill_to Usaddress(name='Robert Smith', street='8 Oak Avenue', city='Old Town', state='PA', zip=Decimal('95819'), country='US') ``` ## xml.etree.ElementTree Using the [`xml`][] module with [`XmlEventHandler`][xsdata.formats.dataclass.parsers.handlers.XmlEventHandler] ```python >>> from xml.etree import ElementTree >>> from xsdata.formats.dataclass.parsers.handlers import XmlEventHandler ... >>> parser = XmlParser(handler=XmlEventHandler) >>> tree = ElementTree.parse("tests/fixtures/primer/sample.xml") >>> ship_to = parser.parse(tree.find('.//shipTo'), Usaddress) >>> ship_to Usaddress(name='Alice Smith', street='123 Maple Street', city='Mill Valley', state='CA', zip=Decimal('90952'), country='US') ``` !!! Note The [xml.etree.ElementTree][] api doesn't preserve the namespace prefixes, the handler will auto generate new ones. ## Unknown target clazz It's optimal to provide the target class but completely optional. The parser can scan all the imported modules to find a matching dataclass. ```python >>> order = parser.parse("tests/fixtures/primer/sample.xml") >>> type(order) ``` ## Capture namespace prefixes ```python >>> from tests.fixtures.books.books import Books >>> ns_map = {} >>> order = parser.parse("tests/fixtures/books/books.xml", Books, ns_map=ns_map) >>> ns_map {'brk': 'urn:books'} ``` ## Alternative handlers XmlHandlers read the xml source and push build events to create the target class. xsData ships with two handlers based on lxml and native python that vary in performance and features. !!! Hint If you installed xsdata with lxml the default handler is set to [LxmlEventHandler][xsdata.formats.dataclass.parsers.handlers.LxmlEventHandler] otherwise [XmlEventHandler][xsdata.formats.dataclass.parsers.handlers.XmlEventHandler] will be used. ```python >>> from xsdata.formats.dataclass.parsers.handlers import XmlEventHandler ... >>> parser = XmlParser(handler=XmlEventHandler) >>> order = parser.parse("tests/fixtures/primer/sample.xml") >>> order.bill_to.street '8 Oak Avenue' ``` !!! Hint It's recommended to give all of them a try, based on your use case you might get different results. You can also extend one of them if you want to do any optimizations or customize the default behaviour. ## XML Inclusions XML Inclusions (XInclude) is a W3C specification that defines a way to include XML fragments from different sources into a single document. If you are parsing from memory you need to specify the base url in the config in order to load the included documents correctly. ```python >>> from tests.fixtures.books import Books >>> xml = """ ... ... ... ... """ >>> config = ParserConfig(process_xinclude=True, base_url="tests/fixtures/books/books-xinclude.xml") >>> parser = XmlParser(config=config) >>> books = parser.from_string(xml, Books) >>> print(books.book[1]) BookForm(author='Nagata, Suanne', title='Becoming Somebody', genre='Biography', price=33.95, pub_date=XmlDate(2001, 1, 10), review='A masterpiece of the fine art of gossiping.', id='bk002', lang='en') ``` tefra-xsdata-cf7afe8/docs/data_binding/xml_serializing.md000066400000000000000000000200701526437326600237540ustar00rootroot00000000000000# XML Serializing ```python >>> from xsdata.formats.dataclass.context import XmlContext >>> from xsdata.formats.dataclass.serializers import XmlSerializer >>> from xsdata.formats.dataclass.serializers.config import SerializerConfig >>> config = SerializerConfig(indent=" ") >>> context = XmlContext() >>> serializer = XmlSerializer(context=context, config=config) ``` ## Return as string ```python >>> from tests.fixtures.books import Books, BookForm ... >>> books = Books( ... book=[ ... BookForm( ... id="bk001", ... author="Hightower, Kim", ... title="The First Book", ... genre="Fiction", ... price=44.95, ... pub_date="2000-10-01", ... review="An amazing story of nothing.", ... ) ... ] ... ) ... >>> print(serializer.render(books)) Hightower, Kim The First Book Fiction 44.95 2000-10-01 An amazing story of nothing. ``` ## Write to file-like objects ```python >>> from pathlib import Path ... >>> path = Path("output.xml") >>> with path.open("w") as fp: ... serializer.write(fp, books) ... >>> print(path.read_text()) Hightower, Kim The First Book Fiction 44.95 2000-10-01 An amazing story of nothing. >>> path.unlink() ``` ## Custom namespace prefixes ```python >>> print(serializer.render(books, ns_map={"bk": "urn:books"})) Hightower, Kim The First Book Fiction 44.95 2000-10-01 An amazing story of nothing. ``` ## Default namespace ```python >>> print(serializer.render(books, ns_map={None: "urn:books"})) Hightower, Kim The First Book Fiction 44.95 2000-10-01 An amazing story of nothing. ``` ## Skip attributes with default values Attributes are allowed to have default or fixed values and be marked as optional. The default behaviour is to write them explicitly during serialization, but you can disable them through config. ```python >>> from xsdata.formats.dataclass.serializers.config import SerializerConfig ... >>> serializer = XmlSerializer(config=SerializerConfig( ... indent=" ", ... xml_declaration=False, ... ignore_default_attributes=True, ... schema_location="urn books.xsd", ... no_namespace_schema_location=None, ... )) >>> print(serializer.render(books)) Hightower, Kim The First Book Fiction 44.95 2000-10-01 An amazing story of nothing. ``` ## Alternative writers There are two writers based on lxml and native python that may vary in performance in some cases. The output of all them is consistent with a few exceptions when handling mixed content and enabled indentation. !!! Hint If you installed xsdata with lxml the default writer is set to [`LxmlEventWriter`][xsdata.formats.dataclass.serializers.writers.LxmlEventWriter] otherwise [`XmlEventWriter`][xsdata.formats.dataclass.serializers.writers.XmlEventWriter] will be used. ```python >>> from xsdata.formats.dataclass.serializers.writers import XmlEventWriter >>> from xsdata.formats.dataclass.serializers.writers import LxmlEventWriter ... >>> serializer = XmlSerializer(config=config, writer=XmlEventWriter) >>> serializer = XmlSerializer(config=config, writer=LxmlEventWriter) ``` ## Working with wildcards One of the xml schema traits is to support any extensions with wildcards. ```xml ``` The generator will roughly create this class for you. ```python >>> from dataclasses import dataclass >>> from dataclasses import field >>> from typing import Dict >>> from typing import List ... >>> @dataclass ... class MetadataType: ... any_element: List[object] = field( ... default_factory=list, ... metadata={ ... "type": "Wildcard", ... "namespace": "##any", ... } ... ) ... other_attributes: Dict[str, str] = field( ... default_factory=dict, ... metadata={ ... "type": "Attributes", ... "namespace": "##other", ... } ... ) ``` ### Generics xsdata comes with two generic models that are used during parsing and you can also use to generate any custom xml element. - [`AnyElement`][xsdata.formats.dataclass.models.generics.AnyElement]: Used to represent any xml structure, resembles a DOM Element - [`DerivedElement`][xsdata.formats.dataclass.models.generics.DerivedElement]: Wrapper for type substitution elements eg `...` ```python >>> from xsdata.formats.dataclass.models.generics import AnyElement >>> from xsdata.formats.dataclass.models.generics import DerivedElement ... >>> obj = MetadataType( ... any_element=[ ... AnyElement( ... qname="bar", ... children=[ ... AnyElement(qname="first", text="1st", attributes={"a": "1"}), ... AnyElement(qname="second", text="2nd", attributes={"b": "2"}), ... AnyElement(qname="{http://xsdata}third", text="2nd", attributes={"b": "2"}), ... DerivedElement( ... qname="fourth", ... value=MetadataType(other_attributes={"c": "3"}) ... ) ... ] ... ) ... ] ... ) >>> print(serializer.render(obj)) 1st 2nd 2nd ``` ### Mixed content For mixed content with known choices you can skip wrapping your instances with a generic model. During data binding xsdata will try first to match one of the qualified choices. ```python >>> @dataclass ... class Beta: ... class Meta: ... name = "beta" ... >>> @dataclass ... class Alpha: ... class Meta: ... name = "alpha" ... >>> @dataclass ... class Doc: ... class Meta: ... name = "doc" ... ... content: List[object] = field( ... default_factory=list, ... metadata={ ... "type": "Wildcard", ... "namespace": "##any", ... "mixed": True, ... "choices": ( ... { ... "name": "a", ... "type": Alpha, ... "namespace": "", ... }, ... { ... "name": "b", ... "type": Beta, ... "namespace": "", ... }, ... ), ... } ... ) ... >>> obj = Doc( ... content=[ ... Alpha(), ... Beta(), ... ] ... ) ... >>> print(serializer.render(obj)) ``` tefra-xsdata-cf7afe8/docs/faq.md000066400000000000000000000010371526437326600167220ustar00rootroot00000000000000# Frequently Asked Questions ## How can I compare output results between versions? See the [`--include-header`](codegen/config.md#includeheader) config. ## Why are elements out of order? See the [`--compound-fields`](codegen/config.md#compoundfields) config. ## Why are non-nullable fields marked as optional? A TypeError is raised if a [dataclasses][] field without a default value follows a field with a default value. Since Python 3.10+ is required, xsdata always generates dataclasses with `kw_only=True`, which resolves this issue. tefra-xsdata-cf7afe8/docs/favicon.png000066400000000000000000000005261526437326600177660ustar00rootroot00000000000000‰PNG  IHDRóÿasRGBÙÉ, pHYs  šœûIDATxœc`þ@¶ø¤!+PbÀ Î'V±$÷Ê�`ñ¥ �3ùo€Ø\¦ø$�n‘):Á‰¡Q*ï$Ha âÃ2Å€lk þ ÔÄ&St’Ⱦ ķ�ØÙV >RÄÉ@Å,Hrý@¼ ÎÚä÷ñ ž¶ÈxµY‹—Îqq{ ~’qL�ø.[Á�,äÿb94Í*@¼ˆqDqñI�“ º¤ N�Â$ äg‰ÂSȚ�ø+¯byl±àÄׁXˆqÂE'@�x ˆ½¤‹N0bhÆb˜"Ðe¢}�0:e}ÆIEND®B`‚tefra-xsdata-cf7afe8/docs/hooks.py000066400000000000000000000002601526437326600173230ustar00rootroot00000000000000from pathlib import Path from mkdocs.config import Config docs_dir = Path(__file__).parent def on_pre_build(config: Config) -> None: """Mkdocs pre-build entrypoint.""" tefra-xsdata-cf7afe8/docs/index.md000066400000000000000000000007261526437326600172660ustar00rootroot00000000000000--8<-- "./README.md:3" !!! Hint "Why naive?" The W3C XML Schema is too complicated but with good reason. It needs to support any api design. On the other hand when you consume xml you don't necessarily care about any of that. This is where xsData comes in, to simplify things by making a lot of assumptions like the following one that started everything: All xs:schema elements are classes everything else is either noise or class properties tefra-xsdata-cf7afe8/docs/installation.md000066400000000000000000000021071526437326600206530ustar00rootroot00000000000000# Installation ## Using pip ```console pip install xsdata[cli,lxml,soap] ``` !!! hint - Install the cli requirements for the code generator - Install the soap requirements for the builtin wsdl client - Install lxml for enhanced performance and advanced features ## From repository ```console pip install xsdata[cli,lxml] @ git+https://github.com/tefra/xsdata ``` ## Using conda ```console conda install -c conda-forge xsdata ``` ## Verify installation Verify installation using the cli entry point. ```console exec="1" source="console" $ xsdata --help ``` ## Requirements !!! Note "xsData relies on these awesome libraries and supports `python >= 3.10`" - [lxml](https://lxml.de/) - XML advanced features - [requests](https://requests.readthedocs.io/) - Webservice Default Transport - [click](https://click.palletsprojects.com/) - CLI entry point - [toposort](https://pypi.org/project/toposort/) - Resolve class ordering - [jinja2](https://jinja.palletsprojects.com/) - Code generation - [ruff](https://pypi.org/project/ruff/) - Code formatting tefra-xsdata-cf7afe8/docs/logo-small.svg000066400000000000000000000014301526437326600204150ustar00rootroot00000000000000 tefra-xsdata-cf7afe8/docs/logo.svg000066400000000000000000000067171526437326600173240ustar00rootroot00000000000000xsdata-logo tefra-xsdata-cf7afe8/docs/models/000077500000000000000000000000001526437326600171135ustar00rootroot00000000000000tefra-xsdata-cf7afe8/docs/models/classes.md000066400000000000000000000063121526437326600210740ustar00rootroot00000000000000# Classes xsdata is using python's [dataclasses][] as representation models for document entities. Plugins can extend support for output formats. ## Basic Example ```python exec="true" source="above" result="xml" from dataclasses import dataclass # markdown-exec: hide from xsdata.formats.dataclass.serializers import XmlSerializer # markdown-exec: hide serializer = XmlSerializer() # markdown-exec: hide serializer.config.indent = " " # markdown-exec: hide @dataclass class Book: title: str author: str year: int book = Book(title="The Catcher in the Rye", author="J.D. Salinger", year=1951) print(serializer.render(book)) ``` ## Metadata Additional class metadata are required in order to access some of the more advance features. ### `name` The local name of the XML/JSON element. ```python >>> from dataclasses import dataclass, field >>> from xsdata.formats.dataclass.serializers import XmlSerializer >>> serializer = XmlSerializer() >>> serializer.config.indent = " " >>> serializer.config.xml_declaration = False >>> >>> @dataclass ... class Root: ... class Meta: ... name = "xsdata" ... >>> print(serializer.render(Root())) ``` **Type:** `str` **Default:** `The class name` ### `namespace` The namespace name of the XML element ```python >>> @dataclass ... class Root: ... class Meta: ... namespace = "xsdata" ... >>> print(serializer.render(Root())) ``` **Type:** `str | None` **Default:** `None` ### `nillable` Specify if the attribute `xsi:nil="true"` is needed when the class is serialized and it doesn't have any meaningful content. ```python >>> @dataclass ... class Child: ... class Meta: ... nillable = True ... >>> @dataclass ... class Root: ... child: Child ... >>> root = Root(child=Child()) >>> print(serializer.render(root)) ``` **Type:** `bool` **Default:** `False` ### `target_namespace` The namespace name, the XML element was defined under. Used during auto-type discovery when the element form is `unqualified`. **Type:** `str | None` **Default:** `None` ### `global_type` Specify if the class represents an element that can appear in the document root. When false the class can be used only another class dependency, and it's excluded from auto-type discovery. **Type:** `bool` **Default:** `True` ### `element_name_generator` A callable to convert element names when no explicit names are defined. ```python >>> from xsdata.utils.text import camel_case >>> >>> @dataclass ... class RootType: ... class Meta: ... element_name_generator = camel_case ... >>> print(serializer.render(RootType())) ``` **Type:** `Callable[[str], str]` **Default:** `lambda x: x` ### `attribute_name_generator` A callable to convert attribute names when no explicit names are defined. ```python >>> @dataclass ... class Root: ... who_are_you: str = field(default="xsdata", metadata={"type": "Attribute"}) ... class Meta: ... attribute_name_generator = camel_case ... >>> print(serializer.render(Root())) ``` **Type:** `Callable[[str], str]` **Default:** `lambda x: x` tefra-xsdata-cf7afe8/docs/models/fields.md000066400000000000000000000244401526437326600207070ustar00rootroot00000000000000# Fields The [dataclasses][] fields come with a useful metadata mapping that is provided as a third-party extension mechanism. This mechanism is used by xsdata to support advance xml features. ## Metadata ### `name` The local name of the XML/JSON field. ```python >>> from dataclasses import dataclass, field >>> from xsdata.formats.dataclass.serializers import XmlSerializer >>> >>> serializer = XmlSerializer() >>> serializer.config.indent = " " >>> serializer.config.xml_declaration = False >>> >>> @dataclass ... class Root: ... first: str = field(metadata={"name": "one"}) ... second: str = field(metadata={"name": "two"}) ... >>> root = Root(first="xml", second="json") >>> print(serializer.render(root)) xml json ``` **Type:** `str` **Default:** `The field name` ### `namespace` The namespace name of the XML element or attribute. ```python >>> @dataclass ... class Root: ... attr: str = field(metadata={"type": "Attribute", "namespace": "a"}) ... child: str = field(metadata={"type": "Element", "namespace": "c"}) ... >>> print(serializer.render(Root(attr="a", child="c"))) c ``` **Type:** `str` **Default:** `None` ### `nillable` Specify if the field has to be present in the serialized result even when it doesn't have any meaningful content. ```python >>> @dataclass ... class Root: ... first: None | str = field(metadata={"nillable": True}, default=None) ... second: None | str = field(default=None) ... >>> print(serializer.render(Root())) ``` **Type:** `bool` **Default:** `False` ### `required` Specify if an attribute has to always be present in the serialized result, in all other XML Types this attribute is ignored. ```python >>> @dataclass ... class Root: ... att: str = field(default="here", metadata={"type": "Attribute", "required": True}) ... >>> print(serializer.render(Root())) ``` **Type:** `bool` **Default:** `False` ### `sequence` Fields will the same sequence number are rendered sequentially. ```python >>> @dataclass ... class Root: ... a: list[int] = field(metadata={"sequence": 1}) ... b: int = field(metadata={"sequence": 1}) ... c: list[int] = field(metadata={"sequence": 1}) ... >>> root = Root(a=[1, 2, 3], b=4, c=[6, 7, 8]) >>> print(serializer.render(root)) 1 4 6 2 7 3 8 ``` **Type:** `int | None` **Default:** `None` ### `tokens` Specify if the field value is a whitespace concatenated sequence. ```python >>> @dataclass ... class Root: ... child: list[int] = field(metadata={"type": "Element", "tokens": True}) ... >>> root = Root([1, 2, 3]) >>> print(serializer.render(root)) 1 2 3 ``` **Type:** `bool` **Default:** `False` ### `format` The field format option for types like datetime, or bytes. ```python >>> import datetime >>> >>> @dataclass ... class Root: ... base16: bytes = field(metadata={"format": "base16"}) ... base64: bytes = field(metadata={"format": "base64"}) ... date: datetime.date = field(metadata=dict(format="%d/%m/%Y")) ... >>> root = Root( ... base16="xsdata".encode(), ... base64="xsdata".encode(), ... date=datetime.date(2024, 1, 15) ... ) >>> print(serializer.render(root)) 787364617461 eHNkYXRh 15/01/2024 ``` **Type:** `str` **Default:** `None` ### `type` The type property allows to change how a field should behave during data binding. #### Text Represents a xml value node. ```python >>> @dataclass ... class Root: ... value: str = field(metadata={"type": "Text"}, default="abc") ... >>> print(serializer.render(Root())) abc ``` #### Element Represents a xml element node. ```python >>> @dataclass ... class Root: ... value: str = field(metadata={"type": "Element"}, default="abc") ... >>> print(serializer.render(Root())) abc ``` #### Attribute Represents a xml attribute. ```python >>> @dataclass ... class Root: ... value: str = field(metadata={"type": "Attribute"}, default="abc") ... >>> print(serializer.render(Root())) ``` #### Elements Elements type represents repeatable choice elements. It's more commonly referred as `Compound Fields`. ```python >>> @dataclass ... class Root: ... value: list[str | int | bool] = field( ... metadata={ ... "type": "Elements", ... "choices": ( ... {"name": "string", "type": str}, ... {"name": "integer", "type": int}, ... {"name": "bool", "type": bool} ... ) ... } ... ) ... >>> root = Root(value=[1, True, "a", "b", True]) >>> print(serializer.render(root)) 1 true a b true ``` **Choice elements properties** | Property | type | The real name of the element this choice represents. | | --------------- | ---- | ------------------------------------------------------------------ | | name | str | The local name of the XML/JSON field. | | type | str | The type hint. | | nillable | bool | Specify if the choice has to be serialized when it has no content. | | wildcard | bool | Wildcard element to match any xml string. | | tokens | bool | Specify if the choice value is a whitespace concatenated sequence. | | namespace | str | The namespace name of the XML element. | | format | str | The value format for types like datetime, or bytes. | | default | Any | Default value. | | default_factory | Any | Default value factory. | !!! Warning A compound field can not contain ambiguous types because it's impossible to infer the element from the actual value. The xml context will raise an error. The solution is to introduce intermediate simple types or subclasses per element. This will resolve xml roundtrips but it will not work for certain json roundtrips. #### Wildcard This type represents `xs:any` elements or elements with type `xs:AnyType`. This type optionally can have a list of acceptable choices similar to compound fields, otherwise during binding the parsers will try to find a suitable model automatically, if everything fails, the parser will use the generic `AnyElement`. Wildcards can have a normal uri namespace or use one of xml schema generics. - `##any`: element from any namespace is allowed - `##other`: element from any namespace other than the parent’s namespace - `##local`: element must come from no namespace - `##targetNamespace`: element from the namespace of the parent can be present ```python >>> from xsdata.formats.dataclass.parsers import XmlParser >>> >>> @dataclass ... class Root: ... any: object = field(metadata={"type": "Wildcard"}) ... >>> xml = 'foo' >>> parser = XmlParser() >>> parser.from_string(xml, clazz=Root) Root(any=AnyElement(qname='child', text='foo', tail=None, children=[], attributes={'a': 'b'})) ``` #### Attributes This type represents `xs:anyAttribute` elements and can practically absorb any attribute that it is not defined in the class, but it has to be defined as a dictionary. Similar to Wildcard, this type can have a normal uri namespace or use one of xml schema generics. - `##any`: attributes from any namespace is allowed - `##other`: attributes from any namespace other than the parent’s namespace - `##local`: attributes must come from no namespace - `##targetNamespace`: attributes from the namespace of the parent can be present ```python >>> @dataclass ... class Root: ... known: int = field(metadata={"type": "Attribute"}) ... attrs: dict = field(metadata={"type": "Attributes"}) ... >>> xml = '' >>> xml = '' >>> parser.from_string(xml, clazz=Root) Root(known=1, attrs={'unknown': '2'}) ``` #### Ignore This type will force the binding context to ignore the field. Make sure your field is declared with `init=False` or with a default value otherwise data binding will fail. ```python >>> @dataclass ... class Root: ... index: int = field(default_factory=int, metadata={"type": "Ignore"}) ... >>> print(serializer.render(Root())) ``` ### `wrapper` The element name to wrap a single or a collection of elements or primitives, in order to avoid having a dedicated wrapper class. ```python >>> @dataclass ... class Library: ... books: list[str] = field( ... metadata={ ... "wrapper": "Books", ... "name": "Title", ... "type": "Element", ... } ... ) ... >>> obj = Library( ... books = [ ... "python for beginners", ... "beautiful xml", ... ] ... ) >>> >>> print(serializer.render(obj)) python for beginners beautiful xml ``` **Type:** `Optional[str]` **Default:** `None` ### `mixed` Specifies whether the field supports mixed content. The flag is indented for `Wildcard` types and indicates that the parser should attempt to grab any text/tail content from the xml element. ```python >>> @dataclass ... class Root: ... content: object = field(metadata={"type": "Wildcard", "mixed": True}) ... >>> xml = '

Paragraph

Tail
' >>> parser.from_string(xml, clazz=Root) Root(content=[AnyElement(qname='p', text='Paragraph', tail=' Tail', children=[], attributes={})]) ``` **Type:** `bool` **Default:** `False` ### `process_contents` Specifies how `Wildcard` fields should behave, when they don't have a predefined list of choices. If `strict` is enabled, binding context will try to auto-locate a class that matches the qualified name, of the xml element, before trying using the [`AnyElement`][xsdata.formats.dataclass.models.generics.AnyElement] generic. **Type:** `strict | skip` **Default:** `strict` tefra-xsdata-cf7afe8/docs/models/types.md000066400000000000000000000260141526437326600206040ustar00rootroot00000000000000# Types ## Important Dataclasses don't validate values when creating a new instances or assigning values to fields. Automatic conversion of values to appropriate python data types occurs only during the parsing process. The parser handles value/type discrepancies leniently, potentially ignoring the defined field type hint. When such an error arises, the converter issues a `ConverterWarning`. If you prefer to treat conversion warnings as exceptions you need to activate the [ParserConfig.fail_on_converter_warnings](../data_binding/basics.md#fail_on_converter_warnings) option. ## Collections [PEP-585](https://peps.python.org/pep-0585/) type hinting generics in standard collections are also supported. ### List `typing.List` | Case | Example | | -------------- | --------------------------------------------------------------------------------- | | List | `value: List[str] = field(default_factory=list)` | | Optional List | `value: Optional[List[str]] = field(default=None)` | | List Union | `value: List[Union[str, int]] = field(default_factory=list)` | | Tokens List | `value: List[str] = field(default_factory=list, metadata={"tokens": True})` | | List of Tokens | `value: List[List[str]] = field(default_factory=list, metadata={"tokens": True})` | ### Tuple `typing.Tuple` can be use in `frozen` dataclasses, for immutable instances. | Case | Example | | --------------- | ---------------------------------------------------------------------------------------------- | | Tuple | `value: Tuple[str, ...] = field(default_factory=tuple)` | | Optional Tuple | `value: Optional[Tuple[str, ...]] = field(default=None)` | | Tuple Union | `value: Tuple[Union[str, int], ...] = field(default_factory=tuple)` | | Tokens Tuple | `value: Tuple[str, ...] = field(default_factory=tuple, metadata={"tokens": True})` | | Tuple of Tokens | `value: Tuple[Tuple[str, ...], ...] = field(default_factory=tuple, metadata={"tokens": True})` | ### Dict `typing.Dict` is reserved for `Attributes` type fields to capture any undefined attribute. | Case | Example | | ---------- | -------------------------------------------------------------------------------------- | | Attributes | `attrs: Dict[str, str] = field(default_factory=dict, metadata={"type": "Attributes"})` | ### Union `typing.Union` [PEP-604](https://peps.python.org/pep-0604/) union types as X | Y are also supported. | Case | Example | | -------------- | --------------------------------------------------------------- | | Union | `value: Union[str, int, float] = field()` | | Optional Union | `value: Optional[Union[str, int, float]] = field(default=None)` | The order of the types doesn't matter, internally the converter sorts the types by priority: 1. `int` 2. `bool` 3. `float` 4. `Decimal` 5. `datetime` 6. `date` 7. `time` 8. `XmlTime` 9. `XmlDate` 10. `XmlDateTime` 11. `XmlDuration` 12. `XmlPeriod` 13. `QName` 14. `str` ## Standard Types This is the list of all the supported primitive types. ### Boolean | Input | Python | XML | JSON | | --------- | ------- | --------- | ------- | | `"true"` | `True` | `"true"` | `true` | | `"1"` | `True` | `"true"` | `true` | | `"false"` | `False` | `"false"` | `false` | | `"1"` | `False` | `"false"` | `false` | **Python Type**: [bool][] **XML Type:** [boolean](https://www.w3.org/TR/xmlschema11-2/#boolean) ### Bytes Array The fields must provide the `format` metadata property. - `base16` for hexadecimal string - `base64` for base64 encoded bytes-like object or ASCII strings **Python Type**: [bytes][] **XML Types:** [hexBinary](https://www.w3.org/TR/xmlschema11-2/#hexBinary), [base64Binary](https://www.w3.org/TR/xmlschema11-2/#base64Binary) ### Decimal **Python Type**: [decimal.Decimal][] **XML Type:** [decimal](https://www.w3.org/TR/xmlschema11-2/#decimal) ### Float **Python Type**: [float][] **XML Types:** [double](https://www.w3.org/TR/xmlschema11-2/#double), [float](https://www.w3.org/TR/xmlschema11-2/#float) ### Integer **Python Type**: [int][] **XML Types:** - [int](https://www.w3.org/TR/xmlschema11-2/#int) - [nonPositiveInteger](https://www.w3.org/TR/xmlschema11-2/#nonPositiveInteger) - [negativeInteger](https://www.w3.org/TR/xmlschema11-2/#negativeInteger) - [long](https://www.w3.org/TR/xmlschema11-2/#long) - [integer](https://www.w3.org/TR/xmlschema11-2/#integer) - [short](https://www.w3.org/TR/xmlschema11-2/#short) - [byte](https://www.w3.org/TR/xmlschema11-2/#byte) - [nonNegativeInteger](https://www.w3.org/TR/xmlschema11-2/#nonNegativeInteger) - [unsignedLong](https://www.w3.org/TR/xmlschema11-2/#unsignedLong) - [unsignedInt](https://www.w3.org/TR/xmlschema11-2/#unsignedInt) - [unsignedShort](https://www.w3.org/TR/xmlschema11-2/#unsignedShort) - [unsignedByte](https://www.w3.org/TR/xmlschema11-2/#unsignedByte) - [positiveInteger](https://www.w3.org/TR/xmlschema11-2/#positiveInteger) ### Object **Python Type**: [object][] **XML Type:** [anySimpleType](https://www.w3.org/TR/xmlschema11-2/#anySimpleType) ### QName **Python Type**: [xml.etree.ElementTree.QName][] **XML Types:** [QName](https://www.w3.org/TR/xmlschema11-2/#QName), [NOTATION](https://www.w3.org/TR/xmlschema11-2/#NOTATION) ### String **Python Type**: [str][] **XML Types:** - [string](https://www.w3.org/TR/xmlschema11-2/#string) - [anyURI](https://www.w3.org/TR/xmlschema11-2/#anyURI) - [normalizedString](https://www.w3.org/TR/xmlschema11-2/#normalizedString) - [token](https://www.w3.org/TR/xmlschema11-2/#token) - [language](https://www.w3.org/TR/xmlschema11-2/#language) - [NMTOKEN](https://www.w3.org/TR/xmlschema11-2/#NMTOKEN) - [NMTOKENS](https://www.w3.org/TR/xmlschema11-2/#NMTOKENS) - [Name](https://www.w3.org/TR/xmlschema11-2/#Name) - [NCName](https://www.w3.org/TR/xmlschema11-2/#NCName) - [ID](https://www.w3.org/TR/xmlschema11-2/#ID) - [IDREF](https://www.w3.org/TR/xmlschema11-2/#IDREF) - [IDREFS](https://www.w3.org/TR/xmlschema11-2/#IDREFS) - [ENTITIES](https://www.w3.org/TR/xmlschema11-2/#ENTITIES) - [ENTITY](https://www.w3.org/TR/xmlschema11-2/#ENTITY) - [anyAtomicType](https://www.w3.org/TR/xmlschema11-2/#anyAtomicType) - [error](https://www.w3.org/TR/xmlschema11-2/#error) ### XmlDate **Python Type**: [xsdata.models.datatype.XmlDate][] **XML Type:** [date](https://www.w3.org/TR/xmlschema11-2/#date) ### XmlDateTime **Python Type**: [xsdata.models.datatype.XmlDateTime][] **XML Types:** [dateTime](https://www.w3.org/TR/xmlschema11-2/#dateTime), [dateTimeStamp](https://www.w3.org/TR/xmlschema11-2/#dateTimeStamp) ### XmlDuration **Python Type**: [xsdata.models.datatype.XmlDuration][] **XML Types:** - [duration](https://www.w3.org/TR/xmlschema11-2/#duration) - [dayTimeDuration](https://www.w3.org/TR/xmlschema11-2/#dayTimeDuration) - [yearMonthDuration](https://www.w3.org/TR/xmlschema11-2/#yearMonthDuration) ### XmlPeriod **Python Type**: [xsdata.models.datatype.XmlPeriod][] **XML Types:** - [gYearMonth](https://www.w3.org/TR/xmlschema11-2/#gYearMonth) - [gYear](https://www.w3.org/TR/xmlschema11-2/#gYear) - [gMonthDay](https://www.w3.org/TR/xmlschema11-2/#gMonthDay) - [gMonth](https://www.w3.org/TR/xmlschema11-2/#gMonth) - [gDay](https://www.w3.org/TR/xmlschema11-2/#gDay) ### XmlTime **Python Type**: [xsdata.models.datatype.XmlTime][] **XML Type:**: [time](https://www.w3.org/TR/xmlschema11-2/#time) ### Enum **Python Type**: [enum.Enum][] **XML Type:**: [enumeration](https://www.w3.org/TR/xmlschema11-2/#rf-enumeration) ## User Types You can register your own custom types as well as long as they are not dataclasses. ```python >>> from dataclasses import dataclass, field >>> from xsdata.formats.converter import Converter, converter >>> from xsdata.formats.dataclass.parsers import XmlParser >>> from xsdata.formats.dataclass.serializers import XmlSerializer ... >>> serializer = XmlSerializer() >>> serializer.config.indent = " " >>> serializer.config.xml_declaration = False >>> >>> class TheGoodFloat(float): ... pass ... >>> @dataclass ... class Example: ... good: TheGoodFloat = field(metadata=dict(format="{:.2f}")) ... bad: float ... >>> class TheGoodFloatConverter(Converter): ... def deserialize(self, value: str, **kwargs) -> TheGoodFloat: ... return round(TheGoodFloat(value), 1) # Even nicer ... ... def serialize(self, value: TheGoodFloat, **kwargs) -> str: ... if kwargs["format"]: ... return kwargs["format"].format(value) ... return str(value) ... >>> converter.register_converter(TheGoodFloat, TheGoodFloatConverter()) >>> output = serializer.render(Example(TheGoodFloat(10.983263748), -9.9827632)) >>> print(output) 10.98 -9.9827632 >>> XmlParser().from_string(output) Example(good=11.0, bad=-9.9827632) ``` ### Overriding standard type converters It is also possible to override the converter for any of the standard types. For example, we can override the standard serialize function for `XmlDateTime`. ```python >>> from dataclasses import dataclass >>> from typing import Any, Optional >>> >>> from xsdata.formats.converter import Converter, converter >>> from xsdata.formats.dataclass.parsers import XmlParser >>> from xsdata.formats.dataclass.serializers import XmlSerializer >>> from xsdata.formats.dataclass.serializers.config import SerializerConfig >>> from xsdata.models.datatype import XmlDateTime ... >>> serializer = XmlSerializer(config=SerializerConfig(xml_declaration=False)) ... ... >>> @dataclass ... class DateTimeObject: ... datetime: XmlDateTime ... ... >>> datetime_obj = DateTimeObject( ... datetime=XmlDateTime( ... year=2023, ... month=11, ... day=24, ... hour=10, ... minute=38, ... second=56, ... fractional_second=123_000_000, ... ) ... ) ... >>> print(serializer.render(datetime_obj)) 2023-11-24T10:38:56.123 >>> >>> >>> class MyXmlDateTimeConverter(Converter): ... def deserialize(self, value: Any, **kwargs: Any) -> XmlDateTime: ... return XmlDateTime.from_string(value) ... ... def serialize(self, value: Any, **kwargs: Any) -> Optional[str]: ... if isinstance(value, XmlDateTime): ... # Can be anything you like ... return ( ... f"{value.day}-{value.month}-{value.year}" ... f"T{value.hour}:{value.minute}:{value.second}" ... ) ... ... >>> converter.register_converter(XmlDateTime, MyXmlDateTimeConverter()) >>> print(serializer.render(datetime_obj)) 24-11-2023T10:38:56 >>> >>> converter.unregister_converter(XmlDateTime) ``` tefra-xsdata-cf7afe8/docs/overrides/000077500000000000000000000000001526437326600176325ustar00rootroot00000000000000tefra-xsdata-cf7afe8/docs/overrides/main.html000066400000000000000000000002641526437326600214460ustar00rootroot00000000000000{% extends "base.html" %} {% block extrahead %} {{ super() }} {% endblock %} tefra-xsdata-cf7afe8/docs/plugins/000077500000000000000000000000001526437326600173115ustar00rootroot00000000000000tefra-xsdata-cf7afe8/docs/plugins/how_to.md000066400000000000000000000022721526437326600211350ustar00rootroot00000000000000# How to extend xsdata There are two main entrypoints that developers can leverage to add support a new generator output or/and a new class type for data bindings. ## `xsdata.plugins.cli` This entrypoint allows developers to register a new [xsdata.formats.mixins.AbstractGenerator][]. ```python from xsdata.codegen.writer import CodeWriter from xsdata.formats.mixins import AbstractGenerator class AwesomeGenerator(AbstractGenerator): ... CodeWriter.register_generator("awesome", AwesomeGenerator) ``` Which can be used during code generation. ```console $ xsdata generate --output awesome ``` ## `xsdata.plugins.class_types` This entrypoint can be used to register a new [xsdata.formats.dataclass.compat.ClassType][] for binding operations. ```python from xsdata.formats.dataclass.compat import class_types from xsdata.formats.dataclass.compat import ClassType class AwesomeType(ClassType): ... class_types.register("awesome", AwesomeType()) ``` Which then can be used like this: ```python from xsdata.formats.dataclass.context import XmlContext from xsdata.formats.dataclass.parsers import XmlParser context = XmlContext(class_types="awesome") parser = XmlParser(context=context) ``` tefra-xsdata-cf7afe8/docs/plugins/list.md000066400000000000000000000014061526437326600206070ustar00rootroot00000000000000# Plugin List If you have built a plugin to extend xsdata, please reach out! | Name | Description | | ------------------------------------------------------------ | ----------------------------------------------------------------------- | | [xsdata-plantuml](https://pypi.org/project/xsdata-plantuml) | Generate [PlantUML](https://plantuml.com/class-diagram) class diagrams. | | [xsdata-attrs](https://pypi.org/project/xsdata-attrs/) | Add support for [attrs](https://www.attrs.org) classes. | | [xsdata-pydantic](https://pypi.org/project/xsdata-pydantic/) | Add support for [pydantic](https://docs.pydantic.dev/) models. | tefra-xsdata-cf7afe8/docs/robots.txt000066400000000000000000000001141526437326600176750ustar00rootroot00000000000000User-agent: * Sitemap: https://xsdata.readthedocs.io/en/latest/sitemap.xml tefra-xsdata-cf7afe8/docs/samples.md000066400000000000000000000014031526437326600176140ustar00rootroot00000000000000# Samples **Samples Repository** You can find a collection of schemas and examples in the sample [repository](https://github.com/tefra/xsdata-samples). Most of these collections came from bug reports, and they represent some of the most interesting and complex production level cases I have come across, showcasing the code generator and binding capabilities of xsdata. **W3C XML Schema Suite** xsdata is constantly tested and measured against the [W3C XML Schema 1.1 test suite](https://github.com/w3c/xsdtests). Because of the size and time it takes to run, the test runner is on it's own [repo](https://github.com/tefra/xsdata-w3c-tests>). The suite is quite extensible and consist of more than 14k valid tests cases for both XML and JSON Binding. ✨✨✨✨ tefra-xsdata-cf7afe8/docs/scripts/000077500000000000000000000000001526437326600173175ustar00rootroot00000000000000tefra-xsdata-cf7afe8/docs/scripts/__init__.py000066400000000000000000000000001526437326600214160ustar00rootroot00000000000000tefra-xsdata-cf7afe8/docs/scripts/generate_api.py000066400000000000000000000014241526437326600223150ustar00rootroot00000000000000from pathlib import Path import mkdocs_gen_files src = Path(__file__).parent.parent.parent / "xsdata" nav = mkdocs_gen_files.Nav() for path in sorted(src.rglob("*.py")): module_path = path.relative_to(src).with_suffix("") parts = tuple(module_path.parts) if parts[-1] in ("__init__", "__main__"): continue doc_path = path.relative_to(src).with_suffix(".md") full_doc_path = Path("api", doc_path) nav[parts] = doc_path.as_posix() with mkdocs_gen_files.open(full_doc_path, "w") as fd: identifier = ".".join(parts) print("::: xsdata." + identifier, file=fd) mkdocs_gen_files.set_edit_path(full_doc_path, path) with mkdocs_gen_files.open("api/SUMMARY.md", "w") as nav_file: nav_file.writelines(nav.build_literate_nav()) tefra-xsdata-cf7afe8/mkdocs.yml000066400000000000000000000064331526437326600167110ustar00rootroot00000000000000site_name: "xsdata" site_url: https://xsdata.readthedocs.io/ site_description: >- xsData is a complete data binding library for python allowing developers to access and use XML and JSON documents as simple objects rather than using DOM. repo_name: tefra/xsdata repo_url: https://github.com/tefra/xsdata edit_uri: edit/main/docs/ exclude_docs: | scripts/ __pycache__/ theme: name: 'material' custom_dir: docs/overrides palette: - media: "(prefers-color-scheme: light)" scheme: default primary: white accent: deep purple toggle: icon: material/lightbulb-outline name: "Switch to dark mode" - media: "(prefers-color-scheme: dark)" scheme: slate primary: black accent: deep purple toggle: icon: material/lightbulb name: "Switch to light mode" features: - content.action.edit - content.action.view - content.code.annotate - content.code.copy - content.tooltips - navigation.tabs - navigation.footer - navigation.top - content.tabs.link - search.suggest logo: 'logo-small.svg' favicon: 'favicon.png' watch: - xsdata plugins: - search - minify: minify_html: true - markdown-exec - gen-files: scripts: - docs/scripts/generate_api.py - literate-nav: nav_file: SUMMARY.md - mkdocstrings: handlers: python: load_external_modules: true inventories: - https://docs.python-requests.org/en/master/objects.inv - https://docs.python.org/3/objects.inv - https://lxml.de/apidoc/objects.inv options: members_order: source show_source: true show_root_heading: true markdown_extensions: - admonition - pymdownx.details - pymdownx.snippets: dedent_subsections: true - pymdownx.superfences: custom_fences: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format - pymdownx.tabbed: alternate_style: true slugify: !!python/object/apply:pymdownx.slugs.slugify kwds: case: lower nav: - Get Started: - Welcome to xsdata: index.md - Installation: installation.md - Changelog: changelog.md - Samples: samples.md - PyPI: https://pypi.org/project/xsdata/ - Conda: https://anaconda.org/conda-forge/xsdata - Code Generator: - Introduction: codegen/intro.md - Configuration: codegen/config.md - Docstring styles: codegen/docstrings.md - DTD Modeling: codegen/dtd_modeling.md - WSDL Modeling: codegen/wsdl_modeling.md - Samples Modeling: codegen/samples_modeling.md - Download Schemas: codegen/download_schemas.md - Architecture: codegen/architecture.md - Data Models: - Classes: models/classes.md - Fields: models/fields.md - Types: models/types.md - Data Binding: - Basics: data_binding/basics.md - XML Parsing: data_binding/xml_parsing.md - XML Serializing: data_binding/xml_serializing.md - JSON Parsing: data_binding/json_parsing.md - JSON Serializing: data_binding/json_serializing.md - Pycode Serializing: data_binding/pycode_serializing.md - Tree Serializing: data_binding/tree_serializing.md - Dict Decoding: data_binding/dict_decoding.md - Dict Encoding: data_binding/dict_encoding.md - FAQ: faq.md - API: api/ - Plugins: - How-to: plugins/how_to.md - List: plugins/list.md tefra-xsdata-cf7afe8/pyproject.toml000066400000000000000000000064101526437326600176150ustar00rootroot00000000000000[build-system] requires = ["setuptools>=77.0.0"] build-backend = "setuptools.build_meta" [tool.setuptools.packages.find] include = ["xsdata*"] [project] name = "xsdata" description = "Python XML Binding" authors = [{name = "Christodoulos Tsoulloftas", email = "chris@komposta.net"}] license = "MIT" license-files = ["LICENSE", "THIRD_PARTY_LICENSES.md"] classifiers = [ "Intended Audience :: Developers", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Code Generators", "Topic :: Text Processing :: Markup :: XML", ] keywords = ["xsd", "wsdl", "schema", "dtd", "binding", "xml", "json", "dataclasses", "generator", "cli"] requires-python = ">=3.10" dependencies = [ "typing-extensions>=4.12.0", ] dynamic = ["version"] [project.readme] file = "README.md" content-type = "text/markdown" [project.urls] Homepage = "https://github.com/tefra/xsdata" Source = "https://github.com/tefra/xsdata" Documentation = "https://xsdata.readthedocs.io/" Changelog = "https://xsdata.readthedocs.io/en/latest/changelog/" [project.optional-dependencies] cli = [ "click>=8.0.0", "jinja2>=3.0.0", "toposort>=1.9", "ruff>=0.9.8" ] docs = [ "mkdocs", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-material", "mkdocs-minify-plugin", "mkdocstrings[python]", "markdown-exec[ansi]", ] lxml = ["lxml>=5.0.0"] soap = ["requests>=2.28.0"] test = [ "pre-commit>=3.0.0", "pytest>=8.0.0", "pytest-benchmark>=4.0.0", "pytest-cov>=5.0.0", ] [project.scripts] xsdata = "xsdata.__main__:main" [tool.setuptools] include-package-data = true [tool.setuptools.dynamic] version = {attr = "xsdata.__version__"} [tool.ruff] target-version = "py310" line-length = 88 [tool.ruff.lint] select = [ # pycodestyle "E", # Pyflakes "F", # pyupgrade "UP", # flake8-bugbear "B", # flake8-simplify "SIM", # flake8-annotations "ANN", # flake8-comprehensions "C4", # isort "I", # pydocstyle "D", # flake-raise "RSE", # flake-return "RET", # Ruff rules "RUF", # Flake Simplify "SIM", ] ignore = [ "ANN204", "ANN206", "ANN401", "E203", "B028", "B904", "D100", "D104", "RUF009", ] exclude = ["tests/fixtures"] [tool.ruff.lint.per-file-ignores] "**/{tests}/*" = ["ANN001", "ANN002", "ANN003", "E501", "B018", "D"] "tests/formats/dataclass/cases/**" = ["UP"] "**/utils/testing.py" = ["D"] "docs/*" = ["D"] [tool.ruff.lint.pydocstyle] convention = "google" [tool.ruff.format] line-ending = "lf" quote-style = "double" indent-style = "space" [tool.pytest.ini_options] addopts = "--color=yes --benchmark-skip" doctest_optionflags = "NORMALIZE_WHITESPACE" [tool.coverage.run] omit = [ "xsdata/__main__.py", "xsdata/utils/debug.py", ] tefra-xsdata-cf7afe8/tests/000077500000000000000000000000001526437326600160425ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/__init__.py000066400000000000000000000002761526437326600201600ustar00rootroot00000000000000import tempfile from pathlib import Path root = Path(__file__).parent.parent fixtures_dir = root.joinpath("tests/fixtures") xsdata_temp_dir = Path(tempfile.gettempdir()).joinpath("xsdata") tefra-xsdata-cf7afe8/tests/codegen/000077500000000000000000000000001526437326600174465ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/codegen/__init__.py000066400000000000000000000000001526437326600215450ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/codegen/handlers/000077500000000000000000000000001526437326600212465ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/codegen/handlers/__init__.py000066400000000000000000000000001526437326600233450ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/codegen/handlers/test_add_attribute_substitutions.py000066400000000000000000000131551526437326600305160ustar00rootroot00000000000000from unittest import mock from xsdata.codegen.container import ClassContainer from xsdata.codegen.handlers import AddAttributeSubstitutions from xsdata.codegen.models import AttrType from xsdata.models.config import GeneratorConfig from xsdata.models.enums import Tag from xsdata.utils.namespaces import build_qname from xsdata.utils.testing import ( AttrFactory, AttrTypeFactory, ClassFactory, FactoryTestCase, ) class AddAttributeSubstitutionsTests(FactoryTestCase): def setUp(self) -> None: super().setUp() container = ClassContainer(config=GeneratorConfig()) self.processor = AddAttributeSubstitutions(container=container) @mock.patch.object(AddAttributeSubstitutions, "process_attribute") @mock.patch.object(AddAttributeSubstitutions, "create_substitutions") def test_process(self, mock_create_substitutions, mock_process_attribute) -> None: def init_substitutions() -> None: self.processor.substitutions = {} mock_create_substitutions.side_effect = init_substitutions target = ClassFactory.create( attrs=[AttrFactory.enumeration(), AttrFactory.any(), AttrFactory.element()] ) self.processor.process(target) self.processor.process(ClassFactory.create()) mock_process_attribute.assert_called_once_with(target, target.attrs[2]) mock_create_substitutions.assert_called_once() @mock.patch("xsdata.utils.collections.find") def test_process_attribute(self, mock_find) -> None: target = ClassFactory.create( attrs=[ AttrFactory.create(types=[AttrTypeFactory.create("foo")]), AttrFactory.create(types=[AttrTypeFactory.create("bar")]), ] ) mock_find.side_effect = [-1, 2] first_attr = target.attrs[0] second_attr = target.attrs[1] first_attr.restrictions.min_occurs = 1 first_attr.restrictions.max_occurs = 2 second_attr.restrictions.min_occurs = 1 attr_qname = first_attr.types[0].qname reference_attrs = AttrFactory.list(2) reference_attrs[0].restrictions.min_occurs = 1 reference_attrs[1].restrictions.min_occurs = 1 self.processor.create_substitutions() self.processor.substitutions[attr_qname] = reference_attrs self.processor.process_attribute(target, first_attr) self.assertEqual(4, len(target.attrs)) # Guard against multiple runs in case of xs:groups self.processor.process_attribute(target, first_attr) self.assertEqual(4, len(target.attrs)) self.assertEqual(reference_attrs[0], target.attrs[0]) self.assertIsNot(reference_attrs[0], target.attrs[0]) self.assertEqual(reference_attrs[1], target.attrs[3]) self.assertIsNot(reference_attrs[1], target.attrs[3]) self.assertEqual(2, target.attrs[0].restrictions.max_occurs) self.assertEqual(2, target.attrs[3].restrictions.max_occurs) self.assertEqual(0, target.attrs[0].restrictions.min_occurs) self.assertEqual(0, target.attrs[1].restrictions.min_occurs) self.assertEqual(1, target.attrs[2].restrictions.min_occurs) self.assertEqual(0, target.attrs[3].restrictions.min_occurs) self.assertEqual("foo", target.attrs[0].substitution) self.assertEqual("foo", target.attrs[1].substitution) self.assertEqual(None, target.attrs[2].substitution) self.assertEqual("foo", target.attrs[3].substitution) self.processor.process_attribute(target, second_attr) self.assertEqual(4, len(target.attrs)) @mock.patch.object(AddAttributeSubstitutions, "create_substitution") def test_create_substitutions(self, mock_create_substitution) -> None: ns = "xsdata" classes = [ ClassFactory.create( substitutions=[build_qname(ns, "foo"), build_qname(ns, "bar")], abstract=True, ), ClassFactory.create(substitutions=[build_qname(ns, "foo")], abstract=True), ] reference_attrs = AttrFactory.list(3) mock_create_substitution.side_effect = reference_attrs self.processor.container.extend(classes) self.processor.create_substitutions() expected = { build_qname(ns, "foo"): [reference_attrs[0], reference_attrs[2]], build_qname(ns, "bar"): [reference_attrs[1]], } self.assertEqual(expected, self.processor.substitutions) mock_create_substitution.assert_has_calls( [mock.call(classes[0]), mock.call(classes[0]), mock.call(classes[1])] ) def test_create_substitution(self) -> None: item = ClassFactory.elements(1, qname=build_qname("foo", "bar")) actual = self.processor.create_substitution(item) expected = AttrFactory.create( name=item.name, default=None, types=[AttrType(qname=build_qname("foo", "bar"))], tag=Tag.ELEMENT, ) self.assertEqual(expected, actual) def test_prepare_substituted(self) -> None: attr = AttrFactory.create() attr.restrictions.min_occurs = 1 attr.restrictions.path.append(("s", 0, 1, 1)) self.processor.prepare_substituted(attr) self.assertEqual(0, attr.restrictions.min_occurs) self.assertEqual(id(attr), attr.restrictions.choice) self.assertEqual(2, len(attr.restrictions.path)) self.assertEqual(("c", id(attr), 1, 1), attr.restrictions.path[-1]) attr.restrictions.choice = 1 self.processor.prepare_substituted(attr) self.assertEqual(("c", id(attr), 1, 1), attr.restrictions.path[-1]) tefra-xsdata-cf7afe8/tests/codegen/handlers/test_calculate_attribute_paths.py000066400000000000000000000075401526437326600301040ustar00rootroot00000000000000from xsdata.codegen.handlers import CalculateAttributePaths from xsdata.codegen.models import Restrictions from xsdata.utils.testing import AttrFactory, ClassFactory, FactoryTestCase class CalculateAttributePathsTests(FactoryTestCase): def setUp(self) -> None: super().setUp() self.processor = CalculateAttributePaths() def test_process(self) -> None: target = ClassFactory.create( attrs=[ AttrFactory.enumeration(), AttrFactory.attribute(), AttrFactory.element(), AttrFactory.element( restrictions=Restrictions( min_occurs=1, max_occurs=1, path=[("s", 1, 1, 1), ("s", 2, 1, 2)], ) ), AttrFactory.element( restrictions=Restrictions( min_occurs=1, max_occurs=1, path=[("s", 1, 1, 1)] ) ), AttrFactory.element( restrictions=Restrictions( min_occurs=1, max_occurs=1, path=[("s", 1, 1, 1), ("g", 3, 1, 1), ("g", 4, 1, 2)], ) ), AttrFactory.element( restrictions=Restrictions( min_occurs=1, max_occurs=1, path=[("s", 1, 1, 1), ("g", 3, 1, 1)], ) ), AttrFactory.element( restrictions=Restrictions( min_occurs=1, max_occurs=1, path=[("s", 1, 1, 1), ("c", 4, 0, 1), ("c", 100, 1, 1)], ) ), AttrFactory.element( restrictions=Restrictions( min_occurs=1, max_occurs=1, path=[("s", 1, 1, 1), ("c", 4, 0, 1), ("a", 101, 1, 1)], ) ), AttrFactory.element( restrictions=Restrictions( min_occurs=1, max_occurs=1, path=[("s", 1, 1, 1), ("c", 5, 2, 2)], ) ), AttrFactory.element( restrictions=Restrictions( min_occurs=1, max_occurs=1, path=[("s", 1, 1, 1), ("c", 5, 2, 2)], ), ), AttrFactory.element( restrictions=Restrictions( min_occurs=1, max_occurs=1, path=[("s", 1, 1, 1), ("c", 6, 1, 21)], ) ), ] ) self.processor.process(target) actual = [] for attr in target.attrs: restrictions = attr.restrictions actual.append( ( restrictions.sequence, restrictions.group, restrictions.min_occurs, restrictions.max_occurs, ) ) expected = [ (None, None, None, None), (None, None, None, None), (None, None, None, None), (1, None, 1, 2), (1, None, 1, 1), (1, 4, 1, 2), (1, 3, 1, 1), (1, None, 0, 1), (1, None, 0, 1), (1, None, 2, 2), # choice min_occurs=2, so element stays required (1, None, 2, 2), # choice min_occurs=2, so element stays required (1, None, 0, 21), # choice min_occurs=1, so element becomes optional ] self.assertEqual(expected, actual) tefra-xsdata-cf7afe8/tests/codegen/handlers/test_create_compound_fields.py000066400000000000000000000257161526437326600273670ustar00rootroot00000000000000from unittest import mock from xsdata.codegen.container import ClassContainer from xsdata.codegen.handlers import CreateCompoundFields from xsdata.codegen.models import Restrictions from xsdata.models.config import GeneratorConfig from xsdata.models.enums import Tag from xsdata.utils.testing import ( AttrFactory, ClassFactory, ExtensionFactory, FactoryTestCase, ) class CreateCompoundFieldsTests(FactoryTestCase): def setUp(self) -> None: super().setUp() self.config = GeneratorConfig() self.config.output.compound_fields.enabled = True self.container = ClassContainer(config=self.config) self.processor = CreateCompoundFields(container=self.container) @mock.patch.object(CreateCompoundFields, "group_fields") def test_process(self, mock_group_fields) -> None: target = ClassFactory.elements(8) # First group repeating target.attrs[0].restrictions.choice = "1" target.attrs[1].restrictions.choice = "1" target.attrs[1].restrictions.max_occurs = 1 # Second group repeating target.attrs[2].restrictions.choice = "2" target.attrs[3].restrictions.choice = "2" target.attrs[3].restrictions.max_occurs = 2 # Third group optional target.attrs[4].restrictions.choice = "3" target.attrs[5].restrictions.choice = "3" self.processor.process(target) mock_group_fields.assert_has_calls( [ mock.call(target, target.attrs[0:2]), mock.call(target, target.attrs[2:4]), ] ) def test_process_with_config_enabled_false_calculate_min_occurs(self) -> None: self.processor.config.enabled = False target = ClassFactory.elements(5) target.attrs[0].restrictions.choice = 1 target.attrs[1].restrictions.choice = 1 target.attrs[2].restrictions.choice = 2 target.attrs[3].restrictions.choice = 2 for attr in target.attrs: attr.restrictions.min_occurs = 2 attr.restrictions.max_occurs = 3 target.attrs[0].restrictions.path = [("g", 0, 1, 1), ("c", 1, 2, 1)] target.attrs[1].restrictions.path = [("g", 0, 1, 1), ("c", 1, 2, 1)] target.attrs[2].restrictions.path = [("g", 0, 1, 1), ("c", 2, 1, 1)] target.attrs[3].restrictions.path = [("g", 0, 1, 1), ("c", 2, 1, 1)] self.processor.process(target) actual = [ (attr.restrictions.min_occurs, attr.restrictions.max_occurs) for attr in target.attrs ] expected = [(2, 3), (2, 3), (0, 3), (0, 3), (2, 3)] self.assertEqual(expected, actual) def test_group_fields(self) -> None: target = ClassFactory.create(attrs=AttrFactory.list(4)) target.attrs[0].restrictions.choice = 1 target.attrs[1].restrictions.choice = 1 target.attrs[0].restrictions.min_occurs = 10 target.attrs[0].restrictions.max_occurs = 15 target.attrs[1].restrictions.min_occurs = 5 target.attrs[1].restrictions.max_occurs = 20 target.attrs[2].restrictions.min_occurs = 2 target.attrs[2].restrictions.max_occurs = 4 target.attrs[3].restrictions.min_occurs = 1 target.attrs[3].restrictions.max_occurs = 3 target.attrs[0].restrictions.path = [("g", 0, 1, 1), ("c", 1, 1, 1)] target.attrs[1].restrictions.path = [("g", 0, 1, 1), ("c", 1, 1, 1)] target.attrs[2].restrictions.path = [("g", 0, 1, 1), ("c", 1, 1, 1)] target.attrs[3].restrictions.path = [("g", 0, 1, 1), ("c", 1, 1, 1)] expected = AttrFactory.create( name="choice", tag="Choice", index=0, types=[], choices=[ AttrFactory.create( tag=target.attrs[0].tag, name="attr_B", types=target.attrs[0].types, ), AttrFactory.create( tag=target.attrs[1].tag, name="attr_C", types=target.attrs[1].types, ), AttrFactory.create( tag=target.attrs[2].tag, name="attr_D", types=target.attrs[2].types, ), AttrFactory.create( tag=target.attrs[3].tag, name="attr_E", types=target.attrs[3].types, ), ], ) expected_res = Restrictions(min_occurs=0, max_occurs=20) self.processor.group_fields(target, target.attrs.copy()) self.assertEqual(1, len(target.attrs)) self.assertEqual(expected, target.attrs[0]) self.assertEqual(expected_res, target.attrs[0].restrictions) def test_group_fields_with_effective_choices_sums_occurs(self) -> None: target = ClassFactory.create(attrs=AttrFactory.list(2)) target.attrs[0].restrictions.choice = -1 target.attrs[1].restrictions.choice = -1 target.attrs[0].restrictions.min_occurs = 1 target.attrs[0].restrictions.max_occurs = 2 target.attrs[1].restrictions.min_occurs = 3 target.attrs[1].restrictions.max_occurs = 4 expected_res = Restrictions(min_occurs=4, max_occurs=6) self.processor.group_fields(target, target.attrs.copy()) self.assertEqual(1, len(target.attrs)) self.assertEqual(expected_res, target.attrs[0].restrictions) def test_choose_name(self) -> None: target = ClassFactory.create() actual = self.processor.choose_name(target, ["a", "b", "c"], []) self.assertEqual("a_Or_b_Or_c", actual) actual = self.processor.choose_name(target, ["a", "b", "c", "d"], []) self.assertEqual("choice", actual) target.attrs.append(AttrFactory.create(name="choice")) actual = self.processor.choose_name(target, ["a", "b", "c", "d"], []) self.assertEqual("choice_1", actual) actual = self.processor.choose_name(target, ["a", "b", "c", "d"], []) self.assertEqual("choice_1", actual) self.processor.config.default_name = "ThisOrThat" actual = self.processor.choose_name(target, ["a", "b", "c", "d"], []) self.assertEqual("ThisOrThat", actual) self.processor.config.force_default_name = True actual = self.processor.choose_name(target, ["a", "b", "c"], []) self.assertEqual("ThisOrThat", actual) self.processor.config.force_default_name = False self.processor.config.use_substitution_groups = True actual = self.processor.choose_name(target, ["a", "b", "c"], ["d", "e", "f"]) self.assertEqual("d_Or_e_Or_f", actual) actual = self.processor.choose_name(target, ["a", "b", "c"], ["d", "f"]) self.assertEqual("a_Or_b_Or_c", actual) def test_build_reserved_names(self) -> None: base = ClassFactory.create( attrs=[ AttrFactory.create("standalone"), AttrFactory.create( name="first", tag=Tag.CHOICE, choices=[ AttrFactory.create(name="a"), AttrFactory.create(name="b"), AttrFactory.create(name="c"), ], ), AttrFactory.create( name="second", tag=Tag.CHOICE, choices=[ AttrFactory.create(name="b"), AttrFactory.create(name="c"), ], ), ] ) target = ClassFactory.create() target.extensions.append(ExtensionFactory.reference(qname=base.qname)) self.processor.container.extend([base, target]) actual = self.processor.build_reserved_names(target, names=["b", "c"]) expected = {"standalone", "first"} self.assertEqual(expected, actual) def test_build_attr_choice(self) -> None: attr = AttrFactory.create( name="a", namespace="xsdata", default="123", help="help", fixed=True ) attr.local_name = "aaa" attr.restrictions = Restrictions( min_occurs=1, max_occurs=1, min_exclusive="1.1", min_inclusive="1", min_length=1, max_exclusive="1", max_inclusive="1.1", max_length=10, total_digits=333, fraction_digits=2, length=5, white_space="collapse", pattern=r"[A-Z]", explicit_timezone="+1", nillable=True, choice="abc", sequence=1, ) expected_res = attr.restrictions.clone(min_occurs=0, sequence=None) actual = self.processor.build_attr_choice(attr) self.assertEqual(attr.name, actual.name) self.assertEqual(attr.local_name, actual.local_name) self.assertEqual(attr.namespace, actual.namespace) self.assertIsNone(actual.default) self.assertEqual(attr.tag, actual.tag) self.assertEqual(attr.types, actual.types) self.assertEqual(expected_res, actual.restrictions) self.assertEqual(attr.help, actual.help) self.assertFalse(actual.fixed) def test_sum_counters(self) -> None: counters = { ("g", 184, 1, 1): { "min": [], "max": [], ("s", 183, 1, 1): { "min": [], "max": [], ("g", 185, 1, 1): { "min": [], "max": [], ("s", 188, 1, 1): { "min": [], "max": [], ("c", 192, 1, 1): { "min": [0], "max": [1], ("s", 193, 1, 1): { "min": [0, 0], "max": [1, 1], ("c", 200, 1, 1): {"min": [0, 0], "max": [1, 1]}, }, }, }, }, }, } } result = self.processor.sum_counters(counters) self.assertEqual((0, 3), (sum(result[0]), sum(result[1]))) def test_update_counters(self) -> None: attr = AttrFactory.create() attr.restrictions.min_occurs = 2 attr.restrictions.max_occurs = 3 attr.restrictions.path = [("c", 0, 1, 1)] counters = {} self.processor.update_counters(attr, counters) expected = {("c", 0, 1, 1): {"max": [3], "min": [0]}} self.assertEqual(expected, counters) attr.restrictions.min_occurs = 2 attr.restrictions.path = [("c", 0, 2, 1)] counters = {} self.processor.update_counters(attr, counters) expected = {("c", 0, 2, 1): {"max": [3], "min": [2]}} self.assertEqual(expected, counters) tefra-xsdata-cf7afe8/tests/codegen/handlers/test_create_wrapper_fields.py000066400000000000000000000105731526437326600272160ustar00rootroot00000000000000from xsdata.codegen.container import ClassContainer from xsdata.codegen.handlers import CreateWrapperFields from xsdata.models.config import GeneratorConfig from xsdata.models.enums import DataType, Tag from xsdata.utils.testing import ( AttrFactory, AttrTypeFactory, ClassFactory, ExtensionFactory, FactoryTestCase, ) class CreateWrapperFieldsTests(FactoryTestCase): def setUp(self) -> None: super().setUp() self.config = GeneratorConfig() self.config.output.wrapper_fields = True self.container = ClassContainer(config=self.config) self.processor = CreateWrapperFields(container=self.container) self.target = ClassFactory.create() self.target.attrs.append( AttrFactory.reference("foo", name="items", tag=Tag.ELEMENT) ) self.source = ClassFactory.create(qname="foo") self.source.attrs.append(AttrFactory.native(DataType.STRING, name="item")) self.container.extend([self.target, self.source]) def test_process_skip_with_config_disabled(self) -> None: self.config.output.wrapper_fields = False self.processor.process(self.target) self.assertIsNone(self.target.attrs[0].wrapper) def test_process_with_valid_attr_wrapper(self) -> None: self.processor.process(self.target) self.assertEqual("items", self.target.attrs[0].wrapper) def test_process_with_forward_reference(self) -> None: self.container.remove(self.source) self.target.inner.append(self.source) self.target.attrs[0].types[0].forward = True self.processor.process(self.target) self.assertEqual("items", self.target.attrs[0].wrapper) self.assertFalse(self.target.attrs[0].types[0].forward) self.assertEqual(0, len(self.target.inner)) def test_process_with_invalid_attr(self) -> None: self.target.attrs[0].tag = Tag.EXTENSION self.processor.process(self.target) self.assertIsNone(self.target.attrs[0].wrapper) def test_process_with_invalid_source(self) -> None: self.source.extensions.append(ExtensionFactory.create()) self.processor.process(self.target) self.assertIsNone(self.target.attrs[0].wrapper) def test_wrap_field(self) -> None: source = AttrFactory.create() attr = AttrFactory.create() wrapper = attr.local_name self.processor.wrap_field(source, attr, False) self.assertEqual(source.name, attr.name) self.assertEqual(source.local_name, attr.local_name) self.assertEqual(wrapper, attr.wrapper) def test_validate_attr(self) -> None: # Not an element attr = AttrFactory.create(tag=Tag.EXTENSION) self.assertFalse(self.processor.validate_attr(attr)) # Multiple types attr.tag = Tag.ELEMENT attr.types = AttrTypeFactory.list(2) self.assertFalse(self.processor.validate_attr(attr)) # Native type attr.types = [AttrTypeFactory.native(DataType.STRING)] self.assertFalse(self.processor.validate_attr(attr)) # Not any of the above issues attr.types = [AttrTypeFactory.create()] self.assertTrue(self.processor.validate_attr(attr)) def test_validate_source(self) -> None: source = ClassFactory.create() # Has extensions source.extensions = ExtensionFactory.list(1) self.assertFalse(self.processor.validate_source(source, None)) # Has multiple attrs source.extensions.clear() source.attrs = AttrFactory.list(2) self.assertFalse(self.processor.validate_source(source, None)) # Has forwarded references source.attrs.pop(0) source.attrs[0].tag = Tag.EXTENSION source.attrs[0].types[0].forward = True self.assertFalse(self.processor.validate_source(source, None)) # Namespace doesn't match source.attrs[0].types[0].forward = False self.assertFalse(self.processor.validate_source(source, "bar")) # Optional source.attrs[0].namespace = "bar" self.assertFalse(self.processor.validate_source(source, "bar")) # Not Element source.attrs[0].restrictions.min_occurs = 1 self.assertFalse(self.processor.validate_source(source, "bar")) # All rules pass source.attrs[0].tag = Tag.ELEMENT self.assertTrue(self.processor.validate_source(source, "bar")) tefra-xsdata-cf7afe8/tests/codegen/handlers/test_designate_class_packages.py000066400000000000000000000203111526437326600276420ustar00rootroot00000000000000from xsdata.codegen.container import ClassContainer from xsdata.codegen.exceptions import CodegenError from xsdata.codegen.handlers import DesignateClassPackages from xsdata.models.config import ( GeneratorConfig, GeneratorSubstitution, ObjectType, StructureStyle, ) from xsdata.models.enums import Namespace from xsdata.utils.testing import AttrFactory, ClassFactory, FactoryTestCase class DesignateClassPackagesTests(FactoryTestCase): def setUp(self) -> None: super().setUp() self.config = GeneratorConfig() self.container = ClassContainer(config=self.config) self.handler = DesignateClassPackages(container=self.container) def test_group_by_filenames(self) -> None: voc = "file://HL7V3/NE2008/coreschemas/voc.xsd" prpa = "file://HL7V3/NE2008/multicacheschemas/PRPA_MT201307UV02.xsd" coct = "file://HL7V3/NE2008/multicacheschemas/COCT_MT080000UV.xsd" foo_bar = "http://xsdata/foo/bar/schema.xsd" foo_wsdl = "http://xsdata/foo/bar/schema.wsdl" foo_common = "http://xsdata/foo/common.xsd" xsi = Namespace.XSI.location xlink = Namespace.XLINK.location core = ClassFactory.list(1, inner=[ClassFactory.create()], location=voc) multi_one = ClassFactory.list(2, location=prpa) multi_two = ClassFactory.list(1, location=coct) http_one = ClassFactory.list(1, location=foo_bar) wsdl_one = ClassFactory.list(1, location=foo_wsdl) http_two = ClassFactory.list(1, location=foo_common) local_one = ClassFactory.list(1, location=xsi) local_two = ClassFactory.list(1, location=xlink) self.container.extend(core) self.container.extend(multi_one) self.container.extend(multi_two) self.container.extend(http_one) self.container.extend(wsdl_one) self.container.extend(http_two) self.container.extend(local_one) self.container.extend(local_two) self.config.output.package = "foo" self.handler.run() self.assertEqual("foo.coreschemas", core[0].package) self.assertEqual("foo.coreschemas", core[0].inner[0].package) self.assertEqual("foo.multicacheschemas", multi_one[0].package) self.assertEqual("foo.multicacheschemas", multi_one[1].package) self.assertEqual("foo.multicacheschemas", multi_two[0].package) self.assertEqual("foo.bar", http_one[0].package) self.assertEqual("foo", http_two[0].package) self.assertEqual("foo.bar", wsdl_one[0].package) self.assertEqual("foo", local_one[0].package) self.assertEqual("foo", local_two[0].package) def test_group_by_namespace(self) -> None: classes = [ ClassFactory.create(qname="{myNS.tempuri.org}a", location="foo"), ClassFactory.create(qname="{myNS.tempuri.org}b", location="foo"), ClassFactory.create(qname="b", location="bar"), ] self.container.extend(classes) self.config.output.structure_style = StructureStyle.NAMESPACES self.config.output.package = "bar" self.handler.run() self.assertEqual("bar.org.tempuri", classes[0].package) self.assertEqual("bar.org.tempuri", classes[1].package) self.assertEqual("", classes[2].package) self.assertEqual("myNS", classes[0].module) self.assertEqual("myNS", classes[1].module) self.assertEqual("bar", classes[2].module) def test_group_all_together(self) -> None: classes = [ ClassFactory.create(qname="{a}a", location="foo"), ClassFactory.create(qname="{a}b", location="foo"), ClassFactory.create(qname="b", location="bar"), ] self.container.extend(classes) self.config.output.structure_style = StructureStyle.SINGLE_PACKAGE self.config.output.package = "foo.bar.thug" self.handler.run() self.assertEqual("foo.bar", classes[0].package) self.assertEqual("foo.bar", classes[1].package) self.assertEqual("foo.bar", classes[2].package) self.assertEqual("thug", classes[0].module) self.assertEqual("thug", classes[1].module) self.assertEqual("thug", classes[2].module) # No sub-package self.config.output.package = "foo" self.handler.run() self.assertEqual("", classes[0].package) self.assertEqual("", classes[1].package) self.assertEqual("", classes[2].package) self.assertEqual("foo", classes[0].module) self.assertEqual("foo", classes[1].module) self.assertEqual("foo", classes[2].module) def test_group_by_strong_components(self) -> None: classes = ClassFactory.list(4) classes[0].attrs.append(AttrFactory.reference(classes[1].qname)) classes[1].attrs.append(AttrFactory.reference(classes[2].qname)) classes[2].attrs.append(AttrFactory.reference(classes[3].qname)) self.config.output.structure_style = StructureStyle.CLUSTERS self.config.output.package = "foo.bar" self.container.extend(classes) self.handler.run() self.assertEqual("class_B", classes[0].module) self.assertEqual("class_C", classes[1].module) self.assertEqual("class_D", classes[2].module) self.assertEqual("class_E", classes[3].module) classes[3].attrs.append(AttrFactory.reference(classes[1].qname, circular=True)) self.handler.run() self.assertEqual("class_B", classes[0].module) self.assertEqual("class_E", classes[1].module) self.assertEqual("class_E", classes[2].module) self.assertEqual("class_E", classes[3].module) def test_group_by_namespace_clusters(self) -> None: classes = [ ClassFactory.create("{urn:foo-bar:com}a"), ClassFactory.create("{urn:foo-bar:add}b"), ClassFactory.create("{urn:foo-bar:add}c"), ClassFactory.create("{urn:foo-bar:add}d"), ] classes[0].attrs.append(AttrFactory.reference(classes[1].qname)) classes[1].attrs.append(AttrFactory.reference(classes[2].qname)) classes[2].attrs.append(AttrFactory.reference(classes[3].qname)) classes[3].attrs.append(AttrFactory.reference(classes[1].qname, circular=True)) self.config.output.structure_style = StructureStyle.NAMESPACE_CLUSTERS self.config.output.package = "models" self.container.extend(classes) self.handler.run() self.assertEqual("a", classes[0].module) self.assertEqual("d", classes[1].module) self.assertEqual("d", classes[2].module) self.assertEqual("d", classes[3].module) self.assertEqual("models.bar.foo.com", classes[0].package) self.assertEqual("models.bar.foo.add", classes[1].package) self.assertEqual("models.bar.foo.add", classes[2].package) self.assertEqual("models.bar.foo.add", classes[3].package) def test_group_by_namespace_clusters_raises_exception(self) -> None: classes = [ ClassFactory.create("{urn:foo-bar:com}a"), ClassFactory.create("{urn:foo-bar:add}b"), ClassFactory.create("{urn:foo-bar:exc}c"), ClassFactory.create("{urn:foo-bar:add}d"), ] classes[0].attrs.append(AttrFactory.reference(classes[1].qname)) classes[1].attrs.append(AttrFactory.reference(classes[2].qname)) classes[2].attrs.append(AttrFactory.reference(classes[3].qname)) classes[3].attrs.append(AttrFactory.reference(classes[1].qname, circular=True)) self.config.output.structure_style = StructureStyle.NAMESPACE_CLUSTERS self.config.output.package = "models" self.container.extend(classes) with self.assertRaises(CodegenError): self.handler.run() def test_combine_ns_package(self) -> None: namespace = "urn:foo-bar:add" result = self.handler.combine_ns_package(namespace) self.assertEqual(["generated", "bar", "foo", "add"], result) alias = GeneratorSubstitution( type=ObjectType.PACKAGE, search=namespace, replace="add.again" ) self.config.substitutions.substitution.append(alias) result = self.handler.combine_ns_package(namespace) self.assertEqual(["generated", "add", "again"], result) tefra-xsdata-cf7afe8/tests/codegen/handlers/test_detect_circular_references.py000066400000000000000000000050661526437326600302230ustar00rootroot00000000000000from xsdata.codegen.container import ClassContainer from xsdata.codegen.handlers.detect_circular_references import DetectCircularReferences from xsdata.models.config import GeneratorConfig from xsdata.models.enums import DataType from xsdata.utils.testing import ( AttrFactory, AttrTypeFactory, ClassFactory, FactoryTestCase, ) class DetectCircularReferencesTests(FactoryTestCase): def setUp(self) -> None: super().setUp() config = GeneratorConfig() self.container = ClassContainer(config=config) self.processor = DetectCircularReferences(self.container) def test_process(self) -> None: first = ClassFactory.create(qname="first") second = ClassFactory.create(qname="second") third = ClassFactory.create(qname="third") first.attrs.append(AttrFactory.native(DataType.STRING)) first.attrs.append( AttrFactory.create( types=[ AttrTypeFactory.create(qname="second", reference=second.ref), AttrTypeFactory.create(qname="third", reference=third.ref), ], choices=[ AttrFactory.reference("second", reference=second.ref), AttrFactory.reference("third", reference=third.ref), ], ) ) second.attrs = AttrFactory.list(2) third.attrs.append(AttrFactory.reference("first", reference=first.ref)) self.container.extend([first, second, third]) self.processor.process(first) first_flags = [tp.circular for tp in first.types()] self.assertEqual([False, False, True, False, True], first_flags) second_flags = [tp.circular for tp in second.types()] self.assertEqual([False, False], second_flags) # First has the flags this doesn't need it :) third_flags = [tp.circular for tp in third.types()] self.assertEqual([False], third_flags) def test_build_reference_types(self) -> None: target = ClassFactory.create() inner = ClassFactory.create() outer_attr = AttrFactory.create() inner_attr = AttrFactory.reference("foo", reference=target.ref) inner.attrs.append(inner_attr) target.inner.append(inner) target.attrs.append(outer_attr) self.container.add(target) self.processor.build_reference_types() expected = { target.ref: [inner_attr.types[0]], inner.ref: [inner_attr.types[0]], } self.assertEqual(expected, self.processor.reference_types) tefra-xsdata-cf7afe8/tests/codegen/handlers/test_disambiguate_choices.py000066400000000000000000000226441526437326600270220ustar00rootroot00000000000000from dataclasses import replace from xsdata.codegen.container import ClassContainer from xsdata.codegen.handlers import DisambiguateChoices from xsdata.codegen.models import Restrictions, Status from xsdata.models.config import GeneratorConfig from xsdata.models.enums import DataType, Tag from xsdata.utils.testing import ( AttrFactory, AttrTypeFactory, ClassFactory, FactoryTestCase, ) class DisambiguateChoicesTest(FactoryTestCase): maxDiff = None def setUp(self) -> None: super().setUp() self.container = ClassContainer(config=GeneratorConfig()) self.handler = DisambiguateChoices(self.container) def test_process_with_duplicate_wildcards(self) -> None: compound = AttrFactory.create(tag=Tag.CHOICE, types=[]) target = ClassFactory.create() target.attrs.append(compound) compound.choices.append(AttrFactory.native(DataType.STRING)) compound.choices.append(AttrFactory.any(namespace="foo")) compound.choices.append( AttrFactory.any( namespace="bar", restrictions=Restrictions(min_occurs=1, max_occurs=1) ) ) compound.choices.append( AttrFactory.any( namespace="bar", restrictions=Restrictions(max_occurs=3, min_occurs=0) ) ) self.container.add(target) self.handler.process(target) self.assertEqual(2, len(compound.choices)) wildcard = compound.choices[-1] self.assertEqual("content", wildcard.name) self.assertEqual([AttrTypeFactory.native(DataType.ANY_TYPE)], wildcard.types) self.assertEqual("foo bar", wildcard.namespace) self.assertEqual(1, wildcard.restrictions.min_occurs) self.assertEqual(4, wildcard.restrictions.max_occurs) def test_process_with_duplicate_simple_types(self) -> None: compound = AttrFactory.create(tag=Tag.CHOICE) compound.types.clear() target = ClassFactory.create() target.attrs.append(compound) compound.choices.append(AttrFactory.native(DataType.STRING, name="a")) compound.choices.append( AttrFactory.native(DataType.STRING, name="b", namespace="xs") ) self.container.add(target) self.handler.process(target) self.assertEqual(2, len(compound.choices)) self.assertEqual("a", compound.choices[0].types[0].qname) self.assertEqual("{xs}b", compound.choices[1].types[0].qname) self.assertEqual(2, len(target.inner)) self.assertEqual("a", target.inner[0].qname) self.assertEqual("{xs}b", target.inner[1].qname) self.assertEqual([], [x.qname for x in compound.types]) def test_process_with_duplicate_any_types(self) -> None: compound = AttrFactory.create(tag=Tag.CHOICE, types=[]) target = ClassFactory.create() target.attrs.append(compound) compound.choices.append(AttrFactory.native(DataType.ANY_TYPE, name="a")) compound.choices.append( AttrFactory.native(DataType.ANY_TYPE, name="b", namespace="xs") ) self.container.add(target) self.handler.process(target) self.assertEqual(2, len(compound.choices)) self.assertEqual("a", compound.choices[0].types[0].qname) self.assertEqual("{xs}b", compound.choices[1].types[0].qname) self.assertEqual(2, len(target.inner)) self.assertEqual("a", target.inner[0].qname) self.assertEqual("{xs}b", target.inner[1].qname) def test_process_with_duplicate_complex_types(self) -> None: compound = AttrFactory.any() target = ClassFactory.create() target.attrs.append(compound) compound.choices.append(AttrFactory.reference(qname="myint")) compound.choices.append(AttrFactory.reference(qname="myint")) self.container.add(target) self.handler.process(target) self.assertEqual(2, len(compound.choices)) self.assertEqual("attr_C", compound.choices[0].types[0].qname) self.assertEqual("attr_D", compound.choices[1].types[0].qname) self.assertEqual(2, len(target.inner)) self.assertEqual("attr_C", target.inner[0].qname) self.assertEqual("attr_D", target.inner[1].qname) for inner in target.inner: self.assertEqual("myint", inner.extensions[0].type.qname) self.assertEqual("myint", inner.extensions[0].type.qname) self.assertEqual(DataType.ANY_TYPE, compound.types[0].datatype) def test_disambiguate_choice_with_unnest_true(self) -> None: target = ClassFactory.create() attr = AttrFactory.reference(qname="a") config = GeneratorConfig() config.output.unnest_classes = True container = ClassContainer(config=config) handler = DisambiguateChoices(container) container.add(target) handler.disambiguate_choice(target, attr) self.assertIsNotNone(container.find(attr.qname)) def test_disambiguate_choice_with_circular_ref(self) -> None: target = ClassFactory.create() attr = AttrFactory.reference(qname="a") attr.types[0].circular = True self.container.add(target) self.handler.disambiguate_choice(target, attr) self.assertTrue(attr.types[0].circular) self.assertIsNotNone(self.container.find(attr.qname)) def test_find_ambiguous_choices_ignore_wildcards(self) -> None: """Wildcards are merged.""" attr = AttrFactory.create() attr.choices.append(AttrFactory.any()) attr.choices.append(AttrFactory.any()) attr.choices.append( AttrFactory.create( name="this", types=[AttrTypeFactory.native(DataType.ANY_TYPE)] ) ) result = self.handler.find_ambiguous_choices(attr) self.assertEqual(["this"], [x.name for x in result]) def test_is_simple_type(self) -> None: attr = AttrFactory.native(DataType.STRING) self.assertTrue(self.handler.is_simple_type(attr)) enumeration = ClassFactory.enumeration(2) self.container.add(enumeration) attr = AttrFactory.reference(qname=enumeration.qname) self.assertTrue(self.handler.is_simple_type(attr)) complex = ClassFactory.create() self.container.add(complex) attr = AttrFactory.reference(qname=complex.qname) self.assertFalse(self.handler.is_simple_type(attr)) def test_create_ref_class(self) -> None: source = ClassFactory.create( status=Status.RESOLVED, location="here.xsd", ns_map={"foo": "bar"}, ) attr = AttrFactory.create( namespace="test", restrictions=Restrictions(nillable=True), ) result = self.handler.create_ref_class(source, attr, inner=True) self.assertTrue(result.local_type) self.assertEqual("{test}attr_B", result.qname) self.assertEqual(Status.RAW, result.status) self.assertEqual(Tag.ELEMENT, result.tag) self.assertEqual(source.location, result.location) self.assertEqual(source.ns_map, result.ns_map) self.assertEqual(attr.restrictions.nillable, result.nillable) def test_create_ref_class_creates_unique_inner_names(self) -> None: source = ClassFactory.create( status=Status.RESOLVED, location="here.xsd", ns_map={"foo": "bar"}, ) attr = AttrFactory.create(name="a") source.inner.append(ClassFactory.create(qname="{xs}a")) result = self.handler.create_ref_class(source, attr, inner=True) self.assertEqual("a_1", result.name) def test_add_any_type_value(self) -> None: target = ClassFactory.elements(2) source = AttrFactory.any() self.handler.add_any_type_value(target, source) last = target.attrs[-1] self.assertEqual("content", last.name) self.assertEqual(Tag.ANY, last.tag) self.assertEqual(source.namespace, last.namespace) self.assertEqual([AttrTypeFactory.native(DataType.ANY_TYPE)], last.types) self.assertTrue(last.restrictions.is_optional) # anyType elements can be empty self.assertFalse(last.restrictions.is_list) def test_add_simply_type_value(self) -> None: target = ClassFactory.elements(2) source = AttrFactory.native( DataType.STRING, restrictions=Restrictions( max_length=2, nillable=True, path=[("s", 1, 1, 1)] ), ) self.handler.add_simple_type_value(target, source) last = target.attrs[-1] self.assertEqual("value", last.name) self.assertEqual(Tag.EXTENSION, last.tag) self.assertIsNone(last.namespace) self.assertEqual(source.types, last.types) self.assertFalse(last.restrictions.is_optional) self.assertFalse(last.restrictions.is_list) self.assertEqual([], last.restrictions.path) self.assertFalse(last.restrictions.nillable) def test_add_extension(self) -> None: target = ClassFactory.create() source = AttrFactory.reference("{xs}type") source.types[0].forward = True source.types[0].circular = True self.handler.add_extension(target, source) last = target.extensions[-1] self.assertEqual(Tag.EXTENSION, last.tag) expected = replace(source.types[0], forward=False, circular=False) self.assertEqual(expected, last.type) self.assertEqual(Restrictions(), last.restrictions) tefra-xsdata-cf7afe8/tests/codegen/handlers/test_filter_classes.py000066400000000000000000000031301526437326600256560ustar00rootroot00000000000000from unittest import mock from xsdata.codegen.container import ClassContainer from xsdata.codegen.handlers import FilterClasses from xsdata.models.config import GeneratorConfig from xsdata.models.enums import Tag from xsdata.utils.testing import ClassFactory, FactoryTestCase class FilterClassesTests(FactoryTestCase): maxDiff = None def setUp(self) -> None: super().setUp() self.container = ClassContainer(config=GeneratorConfig()) self.handler = FilterClasses(self.container) def test_filter_all_globals(self) -> None: complex_type = ClassFactory.elements(1) enum_1 = ClassFactory.enumeration(2) complex_type.attrs[0].types[0].reference = enum_1.ref simple_type = ClassFactory.simple_type() enum_2 = ClassFactory.enumeration(3) simple_type.attrs[0].types[0].reference = enum_2.ref element = ClassFactory.create(tag=Tag.ELEMENT, abstract=True) expected = [complex_type, enum_1, element] self.container.extend([complex_type, enum_1, simple_type, enum_2, element]) self.handler.run() self.assertEqual(expected, list(self.container)) @mock.patch("xsdata.codegen.handlers.filter_classes.logger.warning") def test_run_with_no_global_types(self, mock_warning) -> None: classes = [ClassFactory.enumeration(2), ClassFactory.simple_type()] self.container.extend(classes) self.handler.run() self.assertEqual(classes, list(self.container)) mock_warning.assert_called_once_with( "No global types exist, will generate all types.", ) tefra-xsdata-cf7afe8/tests/codegen/handlers/test_flatten_attribute_groups.py000066400000000000000000000050421526437326600277770ustar00rootroot00000000000000from xsdata.codegen.container import ClassContainer from xsdata.codegen.exceptions import CodegenError from xsdata.codegen.handlers import FlattenAttributeGroups from xsdata.codegen.models import Status from xsdata.models.config import GeneratorConfig from xsdata.models.enums import Tag from xsdata.utils.testing import AttrFactory, ClassFactory, FactoryTestCase class FlattenAttributeGroupsTests(FactoryTestCase): def setUp(self) -> None: super().setUp() container = ClassContainer(config=GeneratorConfig()) self.processor = FlattenAttributeGroups(container=container) def test_process(self) -> None: group = ClassFactory.create(qname="group", tag=Tag.GROUP) group.attrs = [ AttrFactory.reference(name="one", qname="inner_one", forward=True), AttrFactory.reference(name="two", qname="inner_two", forward=True), ] inner_one = ClassFactory.create( qname="inner_one", attrs=[ AttrFactory.reference(qname="group", tag=Tag.GROUP), ], ) inner_two = inner_one.clone(qname="inner_two") inner_one.parent = group inner_two.parent = group group.inner.extend([inner_one, inner_two]) target = ClassFactory.create( attrs=[ AttrFactory.reference(qname="group", tag=Tag.GROUP), ] ) self.processor.container.extend([group, target]) self.processor.container.process() self.assertEqual(["one", "two"], [x.name for x in target.attrs]) self.assertEqual(["inner_one", "inner_two"], [x.name for x in target.inner]) for inner in target.inner: self.assertEqual(["one", "two"], [x.name for x in inner.attrs]) self.assertEqual(0, len(inner.inner)) def test_process_attribute_with_self_reference(self) -> None: group_attr = AttrFactory.attribute_group(name="bar") target = ClassFactory.create(qname="bar", tag=Tag.ATTRIBUTE_GROUP) target.attrs.append(group_attr) target.status = Status.FLATTENING self.processor.container.add(target) self.processor.process_attribute(target, group_attr) self.assertFalse(group_attr in target.attrs) def test_process_attribute_with_unknown_source(self) -> None: group_attr = AttrFactory.attribute_group(name="bar") target = ClassFactory.create() target.attrs.append(group_attr) with self.assertRaises(CodegenError): self.processor.process_attribute(target, group_attr) tefra-xsdata-cf7afe8/tests/codegen/handlers/test_flatten_class_extensions.py000066400000000000000000000502221526437326600277610ustar00rootroot00000000000000import sys from unittest import mock from xsdata.codegen.container import ClassContainer from xsdata.codegen.handlers import FlattenClassExtensions from xsdata.codegen.models import Restrictions, Status from xsdata.codegen.utils import ClassUtils from xsdata.models.config import GeneratorConfig from xsdata.models.enums import DataType, Tag from xsdata.utils.testing import ( AttrFactory, AttrTypeFactory, ClassFactory, ExtensionFactory, FactoryTestCase, ) class FlattenClassExtensionsTests(FactoryTestCase): def setUp(self) -> None: super().setUp() container = ClassContainer(config=GeneratorConfig()) self.processor = FlattenClassExtensions(container=container) @mock.patch.object(FlattenClassExtensions, "process_extension") def test_process_with_no_extensions(self, mock_process_extension) -> None: target = ClassFactory.elements(1) self.processor.process(target) self.assertEqual(0, mock_process_extension.call_count) @mock.patch.object(FlattenClassExtensions, "process_native_extension") def test_process_extension_with_native_type( self, mock_flatten_extension_native ) -> None: extension = ExtensionFactory.native(DataType.STRING) target = ClassFactory.elements(1, extensions=[extension]) self.processor.process_extension(target, extension) mock_flatten_extension_native.assert_called_once_with(target, extension) @mock.patch.object(FlattenClassExtensions, "process_dependency_extension") def test_process_extension_with_dependency_type( self, mock_process_dependency_extension ) -> None: extension = ExtensionFactory.create(AttrTypeFactory.create("foo")) target = ClassFactory.elements(1, extensions=[extension]) self.processor.process_extension(target, extension) mock_process_dependency_extension.assert_called_once_with(target, extension) @mock.patch.object(FlattenClassExtensions, "process_complex_extension") @mock.patch.object(FlattenClassExtensions, "process_simple_extension") @mock.patch.object(FlattenClassExtensions, "find_dependency") def test_process_dependency_extension_with_absent_type( self, mock_find_dependency, mock_process_simple_extension, mock_process_complex_extension, ) -> None: extension = ExtensionFactory.create() target = ClassFactory.create(extensions=[extension]) mock_find_dependency.return_value = None self.processor.process_extension(target, extension) self.assertEqual(0, len(target.extensions)) self.assertEqual(0, mock_process_simple_extension.call_count) self.assertEqual(0, mock_process_complex_extension.call_count) @mock.patch.object(FlattenClassExtensions, "process_complex_extension") @mock.patch.object(FlattenClassExtensions, "process_simple_extension") @mock.patch.object(FlattenClassExtensions, "find_dependency") def test_process_dependency_extension_with_simple_type( self, mock_find_dependency, mock_process_simple_extension, mock_process_complex_extension, ) -> None: extension = ExtensionFactory.create() target = ClassFactory.create(extensions=[extension]) source = ClassFactory.create(tag=Tag.SIMPLE_TYPE) mock_find_dependency.return_value = source self.processor.process_extension(target, extension) self.assertEqual(0, mock_process_complex_extension.call_count) mock_process_simple_extension.assert_called_once_with(source, target, extension) @mock.patch.object(FlattenClassExtensions, "process_complex_extension") @mock.patch.object(FlattenClassExtensions, "process_simple_extension") @mock.patch.object(FlattenClassExtensions, "find_dependency") def test_process_dependency_extension_with_enum_type( self, mock_find_dependency, mock_process_simple_extension, mock_process_complex_extension, ) -> None: extension = ExtensionFactory.create() target = ClassFactory.create(extensions=[extension]) source = ClassFactory.create(tag=Tag.COMPLEX_TYPE) source.attrs.append(AttrFactory.enumeration()) mock_find_dependency.return_value = source self.processor.process_extension(target, extension) self.assertEqual(0, mock_process_complex_extension.call_count) mock_process_simple_extension.assert_called_once_with(source, target, extension) def test_process_enum_extension_with_enum_source(self) -> None: source = ClassFactory.enumeration(3) target = ClassFactory.enumeration(2) target.attrs[1].name = source.attrs[2].name extension = ExtensionFactory.reference(source.qname) target.extensions.append(extension) self.processor.container.add(source) self.processor.container.add(target) self.processor.process_dependency_extension(target, extension) self.assertEqual(2, len(target.attrs)) self.assertEqual(0, len(target.extensions)) self.assertEqual(source.attrs[2], target.attrs[1]) def test_process_enum_extension_with_simple_source(self) -> None: base = ClassFactory.enumeration(2) base.attrs[0].types = [ AttrTypeFactory.native(DataType.FLOAT), AttrTypeFactory.native(DataType.DECIMAL), ] source = ClassFactory.simple_type() source.attrs[0].types = [ AttrTypeFactory.native(DataType.INT), AttrTypeFactory.create(base.qname), ] target = ClassFactory.enumeration(2) target.attrs[0].name = base.attrs[0].name extension = ExtensionFactory.reference(source.qname) target.extensions.append(extension) self.processor.container.add(base) self.processor.container.add(source) self.processor.container.add(target) self.processor.process_dependency_extension(target, extension) self.assertEqual(2, len(target.attrs)) self.assertEqual( ["float", "decimal"], [tp.name for tp in target.attrs[0].types] ) self.assertEqual(["string", "int"], [tp.name for tp in target.attrs[1].types]) def test_process_enum_extension_with_complex_source(self) -> None: source = ClassFactory.create( tag=Tag.COMPLEX_TYPE, attrs=[ AttrFactory.create(tag=Tag.ATTRIBUTE), AttrFactory.create(tag=Tag.RESTRICTION), ], extensions=ExtensionFactory.list(2), status=Status.FLATTENED, ) target = ClassFactory.enumeration(1) target.attrs[0].default = "Yes" extension = ExtensionFactory.reference(source.qname) target.extensions.append(extension) self.processor.container.add(source) self.processor.container.add(target) self.processor.process_dependency_extension(target, extension) expected = source.attrs[1] self.assertEqual(1, len(target.attrs)) self.assertEqual(expected, target.attrs[0]) self.assertTrue(target.attrs[0].fixed) self.assertEqual("Yes", target.attrs[0].default) self.assertEqual(expected.types, target.attrs[0].types) def test_process_enum_extension_with_complex_source_and_multiple_members( self, ) -> None: source = ClassFactory.elements(2) target = ClassFactory.enumeration(2) extension = ExtensionFactory.reference(source.qname) target.extensions.append(extension) self.processor.container.add(source) self.processor.container.add(target) self.processor.process_dependency_extension(target, extension) self.assertEqual(0, len(target.attrs)) self.assertIn(extension, target.extensions) @mock.patch.object(FlattenClassExtensions, "process_complex_extension") @mock.patch.object(FlattenClassExtensions, "process_simple_extension") @mock.patch.object(FlattenClassExtensions, "find_dependency") def test_process_dependency_extension_with_complex_type( self, mock_find_dependency, mock_process_simple_extension, mock_process_complex_extension, ) -> None: extension = ExtensionFactory.create() target = ClassFactory.create(extensions=[extension]) source = ClassFactory.create(tag=Tag.COMPLEX_TYPE) mock_find_dependency.return_value = source self.processor.process_extension(target, extension) self.assertEqual(0, mock_process_simple_extension.call_count) mock_process_complex_extension.assert_called_once_with( source, target, extension ) @mock.patch.object(FlattenClassExtensions, "add_default_attribute") def test_process_extension_native(self, mock_add_default_attribute) -> None: extension = ExtensionFactory.create() target = ClassFactory.elements(1) self.processor.process_native_extension(target, extension) mock_add_default_attribute.assert_called_once_with(target, extension) @mock.patch.object(FlattenClassExtensions, "replace_attributes_type") def test_process_native_extension_with_enumeration_target( self, mock_replace_attributes_type ) -> None: extension = ExtensionFactory.create() target = ClassFactory.enumeration(1) self.processor.process_native_extension(target, extension) mock_replace_attributes_type.assert_called_once_with(target, extension) def test_process_simple_extension_with_circular_reference(self) -> None: extension = ExtensionFactory.create() target = ClassFactory.create(extensions=[extension]) self.processor.process_simple_extension(target, target, extension) self.assertEqual(0, len(target.extensions)) @mock.patch.object(FlattenClassExtensions, "add_default_attribute") def test_process_simple_extension_when_source_is_enumeration_and_target_is_not( self, mock_add_default_attribute ) -> None: source = ClassFactory.enumeration(2) target = ClassFactory.elements(1) extension = ExtensionFactory.create() self.processor.process_simple_extension(source, target, extension) mock_add_default_attribute.assert_called_once_with(target, extension) @mock.patch.object(ClassUtils, "copy_attributes") @mock.patch.object(FlattenClassExtensions, "add_default_attribute") def test_process_simple_extension_when_target_is_enumeration_and_source_is_not( self, mock_add_default_attribute, mock_copy_attributes ) -> None: extension = ExtensionFactory.create() source = ClassFactory.elements(2) target = ClassFactory.enumeration(1, extensions=[extension]) self.processor.process_simple_extension(source, target, extension) self.assertEqual(0, mock_add_default_attribute.call_count) self.assertEqual(0, mock_copy_attributes.call_count) self.assertEqual(0, len(target.extensions)) @mock.patch.object(ClassUtils, "copy_attributes") def test_process_simple_extension_when_source_and_target_are_enumerations( self, mock_copy_attributes ) -> None: source = ClassFactory.enumeration(2) target = ClassFactory.enumeration(1) extension = ExtensionFactory.create() self.processor.process_simple_extension(source, target, extension) mock_copy_attributes.assert_called_once_with(source, target, extension) @mock.patch.object(ClassUtils, "copy_attributes") def test_process_simple_extension_when_source_and_target_are_not_enumerations( self, mock_copy_attributes ) -> None: source = ClassFactory.elements(2) target = ClassFactory.elements(1) extension = ExtensionFactory.create() self.processor.process_simple_extension(source, target, extension) mock_copy_attributes.assert_called_once_with(source, target, extension) @mock.patch.object(ClassUtils, "copy_attributes") @mock.patch.object(FlattenClassExtensions, "should_remove_extension") def test_process_complex_extension_removes_extension( self, mock_should_remove_extension, mock_copy_attributes ) -> None: mock_should_remove_extension.return_value = True extension = ExtensionFactory.create() target = ClassFactory.elements(1, extensions=[extension]) source = ClassFactory.elements(5) self.processor.process_complex_extension(source, target, extension) self.assertEqual(0, len(target.extensions)) self.assertEqual(1, len(target.attrs)) mock_should_remove_extension.assert_called_once_with(source, target, extension) self.assertEqual(0, mock_copy_attributes.call_count) self.assertEqual(0, extension.type.reference) @mock.patch.object(ClassUtils, "copy_attributes") @mock.patch.object(FlattenClassExtensions, "should_flatten_extension") def test_process_complex_extension_copies_attributes( self, mock_compare_attributes, mock_should_flatten_extension ) -> None: mock_should_flatten_extension.return_value = True extension = ExtensionFactory.create() target = ClassFactory.create() source = ClassFactory.create() self.processor.process_complex_extension(source, target, extension) self.assertEqual(0, extension.type.reference) mock_compare_attributes.assert_called_once_with(source, target) mock_should_flatten_extension.assert_called_once_with(source, target, extension) @mock.patch.object(FlattenClassExtensions, "should_flatten_extension") @mock.patch.object(FlattenClassExtensions, "should_remove_extension") def test_process_complex_extension_ignores_extension( self, mock_should_remove_extension, mock_should_flatten_extension ) -> None: mock_should_remove_extension.return_value = False mock_should_flatten_extension.return_value = False extension = ExtensionFactory.create() target = ClassFactory.create(extensions=[extension]) source = ClassFactory.create() self.processor.process_complex_extension(source, target, extension) self.assertEqual(1, len(target.extensions)) self.assertEqual(id(source), extension.type.reference) def test_find_dependency(self) -> None: attr_type = AttrTypeFactory.create(qname="a") self.assertIsNone(self.processor.find_dependency(attr_type)) complex = ClassFactory.create(qname="a", tag=Tag.COMPLEX_TYPE) self.processor.container.add(complex) self.assertEqual(complex, self.processor.find_dependency(attr_type)) simple = ClassFactory.create(qname="a", tag=Tag.SIMPLE_TYPE) self.processor.container.add(simple) self.assertEqual(simple, self.processor.find_dependency(attr_type)) def test_should_remove_extension(self) -> None: source = ClassFactory.create() target = ClassFactory.create() extension = ExtensionFactory.create(tag=Tag.EXTENSION) callback = self.processor.should_remove_extension # source is target self.assertTrue(callback(source, source, extension)) self.assertFalse(callback(source, target, extension)) # Source is parent class source.inner.append(target) self.assertTrue(callback(target, target, extension)) # MRO Violation source.inner.clear() target.extensions.append(ExtensionFactory.reference("foo")) target.extensions.append(ExtensionFactory.reference("bar")) self.assertFalse(callback(source, target, extension)) source.extensions.append(ExtensionFactory.reference("bar")) self.assertTrue(callback(source, target, extension)) # Sequential violation extension.tag = Tag.RESTRICTION source = ClassFactory.elements(4) target = source.clone() self.assertFalse(callback(source, target, extension)) for attr in target.attrs: attr.restrictions.sequence = 1 target.attrs[3].restrictions.max_occurs = 0 self.assertFalse(callback(source, target, extension)) target.attrs = [ target.attrs[1], target.attrs[0], target.attrs[2], target.attrs[3], ] self.assertTrue(callback(source, target, extension)) def test_should_flatten_extension(self) -> None: source = ClassFactory.create(tag=Tag.COMPLEX_TYPE) target = ClassFactory.create(tag=Tag.ELEMENT) self.assertFalse(self.processor.should_flatten_extension(source, target)) # Source has suffix attr and target has its own attrs source = ClassFactory.elements(1) source.attrs[0].index = sys.maxsize target.attrs.append(AttrFactory.create()) self.assertTrue(self.processor.should_flatten_extension(source, target)) # Source has is a subclass source.extensions.append(ExtensionFactory.create()) self.assertFalse(self.processor.should_flatten_extension(source, target)) # Target has suffix attr source = ClassFactory.create() target = ClassFactory.elements(1) target.attrs[0].index = sys.maxsize self.assertTrue(self.processor.should_flatten_extension(source, target)) # Source is a simple type source = ClassFactory.simple_type() target = ClassFactory.elements(1) self.assertTrue(self.processor.should_flatten_extension(source, target)) def test_replace_attributes_type(self) -> None: extension = ExtensionFactory.create() target = ClassFactory.elements(2) target.extensions.append(extension) FlattenClassExtensions.replace_attributes_type(target, extension) self.assertEqual(1, len(target.attrs[0].types)) self.assertEqual(1, len(target.attrs[1].types)) self.assertEqual(extension.type, target.attrs[0].types[0]) self.assertEqual(extension.type, target.attrs[1].types[0]) self.assertEqual(0, len(target.extensions)) def test_add_default_attribute(self) -> None: xs_string = AttrTypeFactory.native(DataType.STRING) extension = ExtensionFactory.create( xs_string, Restrictions(min_occurs=1, max_occurs=1) ) item = ClassFactory.elements(1, extensions=[extension]) FlattenClassExtensions.add_default_attribute(item, extension) expected = AttrFactory.create( name="value", default=None, types=[xs_string], tag=Tag.EXTENSION ) self.assertEqual(2, len(item.attrs)) self.assertEqual(0, len(item.extensions)) self.assertEqual(expected, item.attrs[0]) xs_int = AttrTypeFactory.native(DataType.INT) extension = ExtensionFactory.create(xs_int, Restrictions(tokens=True)) item.extensions.append(extension) FlattenClassExtensions.add_default_attribute(item, extension) expected.types.append(xs_int) expected_restrictions = Restrictions(tokens=True, min_occurs=1, max_occurs=1) self.assertEqual(2, len(item.attrs)) self.assertEqual(0, len(item.extensions)) self.assertEqual(expected, item.attrs[0]) self.assertEqual(expected_restrictions, item.attrs[0].restrictions) def test_add_default_attribute_with_any_type(self) -> None: extension = ExtensionFactory.create( AttrTypeFactory.native(DataType.ANY_TYPE), Restrictions(min_occurs=1, max_occurs=1), ) item = ClassFactory.create(extensions=[extension]) FlattenClassExtensions.add_default_attribute(item, extension) expected = AttrFactory.create( name="@any_element", default=None, types=[extension.type.clone()], tag=Tag.ANY, namespace="##any", restrictions=Restrictions(min_occurs=1, max_occurs=1), ) self.assertEqual(1, len(item.attrs)) self.assertEqual(0, len(item.extensions)) self.assertEqual(expected, item.attrs[0]) self.assertEqual(expected.restrictions, item.attrs[0].restrictions) def test_get_or_create_attribute_ignore_existing_attribute(self) -> None: target = ClassFactory.create() attr = AttrFactory.create(tag=Tag.ATTRIBUTE, name="value") result = FlattenClassExtensions.get_or_create_attribute( target, "value", Tag.EXTENSION ) self.assertIsNot(attr.tag, result) tefra-xsdata-cf7afe8/tests/codegen/handlers/test_merge_attributes.py000066400000000000000000000122371526437326600262310ustar00rootroot00000000000000from xsdata.codegen.handlers import MergeAttributes from xsdata.codegen.models import Restrictions from xsdata.models.enums import DataType from xsdata.utils.testing import ( AttrFactory, AttrTypeFactory, ClassFactory, FactoryTestCase, ) class MergeAttributesTests(FactoryTestCase): def setUp(self) -> None: super().setUp() self.processor = MergeAttributes def test_process_with_enumeration(self) -> None: target = ClassFactory.create() target.attrs = [ AttrFactory.enumeration(default=1), AttrFactory.enumeration(default=1), AttrFactory.enumeration(default=2), AttrFactory.enumeration(default=2), ] self.processor.process(target) self.assertEqual([1, 2], [x.default for x in target.attrs]) def test_process_with_non_enumeration(self) -> None: one = AttrFactory.attribute(fixed=True) one_clone = one.clone() restrictions = Restrictions(min_occurs=10, max_occurs=15) two = AttrFactory.element(restrictions=restrictions, fixed=True) two_clone = two.clone() two_clone.restrictions.min_occurs = 5 two_clone.restrictions.max_occurs = 5 two_clone_two = two.clone() two_clone_two.restrictions.min_occurs = 4 two_clone_two.restrictions.max_occurs = 4 three = AttrFactory.element() four = AttrFactory.enumeration() four_clone = four.clone() five = AttrFactory.element() five.types = [AttrTypeFactory.native(DataType.INT)] five_clone = five.clone() five_clone_two = five.clone() five_clone_two.restrictions.sequence = 1 five_clone_two.types.append(AttrTypeFactory.native(DataType.FLOAT)) target = ClassFactory.create( attrs=[ one, one_clone, two, two_clone, two_clone_two, three, four, four_clone, five, five_clone, five_clone_two, ] ) winners = [one, two, three, four, five] self.processor.process(target) self.assertEqual(winners, target.attrs) self.assertTrue(one.fixed) self.assertIsNone(one.restrictions.min_occurs) self.assertIsNone(one.restrictions.max_occurs) self.assertFalse(two.fixed) self.assertEqual(4, two.restrictions.min_occurs) self.assertEqual(24, two.restrictions.max_occurs) self.assertIsNone(three.restrictions.min_occurs) self.assertIsNone(three.restrictions.max_occurs) self.assertIsNone(four.restrictions.min_occurs) self.assertIsNone(four.restrictions.max_occurs) self.assertEqual(1, five.restrictions.sequence) self.assertEqual(0, five.restrictions.min_occurs) self.assertEqual(3, five.restrictions.max_occurs) self.assertEqual(["int", "float"], [x.name for x in five.types]) def test_process_elements_in_different_choice_groups(self) -> None: """Test that elements in different choice groups use max, not sum.""" choice1_id = 12345 choice2_id = 67890 attr1 = AttrFactory.element( name="configOption", index=5, restrictions=Restrictions(min_occurs=0, max_occurs=1, choice=choice1_id), ) attr2 = AttrFactory.element( name="configOption", index=8, restrictions=Restrictions(min_occurs=0, max_occurs=1, choice=choice2_id), ) target = ClassFactory.create(attrs=[attr1, attr2]) self.processor.process(target) # Should merge into one attr with max_occurs=1 (not 2) self.assertEqual(1, len(target.attrs)) self.assertEqual("configOption", target.attrs[0].name) self.assertEqual(0, target.attrs[0].restrictions.min_occurs) self.assertEqual(1, target.attrs[0].restrictions.max_occurs) def test_process_elements_in_same_choice_different_branches(self) -> None: """Test that elements in the same choice but different branches use max. This tests the case where an element appears in multiple branches of the same choice. Since only one branch is selected at runtime, they are mutually exclusive and max_occurs should use max(), not sum(). """ choice_id = 12345 attr1 = AttrFactory.element( name="b", index=10, restrictions=Restrictions(min_occurs=0, max_occurs=1, choice=choice_id), ) attr2 = AttrFactory.element( name="b", index=11, restrictions=Restrictions(min_occurs=1, max_occurs=1, choice=choice_id), ) target = ClassFactory.create(attrs=[attr1, attr2]) self.processor.process(target) # Should merge into one attr with max_occurs=1 (not 2) # because both are in different branches of the same choice self.assertEqual(1, len(target.attrs)) self.assertEqual("b", target.attrs[0].name) self.assertEqual(0, target.attrs[0].restrictions.min_occurs) self.assertEqual(1, target.attrs[0].restrictions.max_occurs) tefra-xsdata-cf7afe8/tests/codegen/handlers/test_merge_duplicate_classes.py000066400000000000000000000030061526437326600275240ustar00rootroot00000000000000from xsdata.codegen.container import ClassContainer from xsdata.codegen.handlers import MergeDuplicateClasses from xsdata.models.config import GeneratorConfig from xsdata.utils.testing import ( AttrFactory, ClassFactory, FactoryTestCase, ) class MergeDuplicateClassesTests(FactoryTestCase): def setUp(self) -> None: super().setUp() self.container = ClassContainer(config=GeneratorConfig()) self.processor = MergeDuplicateClasses(container=self.container) def test_run_merges_same_classes(self) -> None: first = ClassFactory.create() second = first.clone() third = first.clone() fourth = ClassFactory.create() fifth = ClassFactory.create() self.container.extend([first, second, third, fourth, fifth]) self.processor.run() self.assertEqual([first, fourth, fifth], list(self.container)) self.assertEqual( {first.ref: third.ref, second.ref: third.ref}, self.processor.merges ) def test_update_references(self) -> None: target = ClassFactory.create() target.attrs.append(AttrFactory.reference(qname="foo", reference=1)) target.attrs.append( AttrFactory.reference( qname="thug", reference=2, ) ) self.processor.merges = {2: 3} self.processor.update_references(target) self.assertEqual(1, target.attrs[0].types[0].reference) self.assertEqual(3, target.attrs[1].types[0].reference) tefra-xsdata-cf7afe8/tests/codegen/handlers/test_process_attributes_types.py000066400000000000000000000416061526437326600300360ustar00rootroot00000000000000from unittest import mock from xsdata.codegen.container import ClassContainer from xsdata.codegen.handlers import ProcessAttributeTypes from xsdata.codegen.models import Restrictions, Status from xsdata.codegen.utils import ClassUtils from xsdata.models.config import GeneratorConfig from xsdata.models.enums import DataType, Tag from xsdata.utils.testing import ( AttrFactory, AttrTypeFactory, ClassFactory, FactoryTestCase, ) class ProcessAttributeTypesTests(FactoryTestCase): def setUp(self) -> None: super().setUp() container = ClassContainer(config=GeneratorConfig()) self.processor = ProcessAttributeTypes(container=container) @mock.patch.object(ProcessAttributeTypes, "cascade_properties") @mock.patch.object(ProcessAttributeTypes, "process_types") def test_process(self, mock_process_types, mock_cascade_properties) -> None: target = ClassFactory.elements(2) self.processor.process(target) calls = [mock.call(target, attr) for attr in target.attrs] mock_process_types.assert_has_calls(calls) mock_cascade_properties.assert_has_calls(calls) self.assertEqual(2, len(calls)) @mock.patch.object(ProcessAttributeTypes, "process_type") def test_process_types(self, mock_process_type) -> None: target = ClassFactory.create() attr = AttrFactory.create() types = [ AttrTypeFactory.native(DataType.STRING), AttrTypeFactory.native(DataType.INT), AttrTypeFactory.native(DataType.STRING), ] attr.types = types self.processor.process_types(target, attr) mock_process_type.assert_has_calls( [mock.call(target, attr, tp) for tp in types] ) self.assertEqual(types[:-1], attr.types) def test_process_types_with_ignore_patterns(self) -> None: target = ClassFactory.create() attr = AttrFactory.native(DataType.DECIMAL) attr.restrictions.pattern = r"\d{2}.\d{2}" self.processor.container.config.output.ignore_patterns = True self.processor.process_types(target, attr) self.assertEqual(DataType.DECIMAL, attr.types[0].datatype) def test_cascade_properties(self) -> None: target = ClassFactory.create(default="2", fixed=True, nillable=True) attr = AttrFactory.native(DataType.STRING, tag=Tag.EXTENSION) self.processor.cascade_properties(target, attr) self.assertEqual(target.default, attr.default) self.assertTrue(attr.fixed) self.assertTrue(attr.restrictions.nillable) target.default = "3" target.fixed = False target.nillable = False self.processor.cascade_properties(target, attr) self.assertNotEqual(target.default, attr.default) self.assertTrue(attr.fixed) self.assertTrue(attr.restrictions.nillable) attr = AttrFactory.native(DataType.STRING, tag=Tag.ELEMENT) self.processor.cascade_properties(target, attr) self.assertIsNone(attr.default) self.assertFalse(attr.fixed) self.assertFalse(attr.restrictions.nillable) @mock.patch.object(ProcessAttributeTypes, "process_dependency_type") @mock.patch.object(ProcessAttributeTypes, "process_native_type") def test_process_type_with_native_type( self, mock_process_native_type, mock_process_dependency_type ) -> None: attr = AttrFactory.create() target = ClassFactory.create() xs_int = AttrTypeFactory.native(DataType.INT) self.processor.process_type(target, attr, xs_int) self.assertEqual(0, mock_process_dependency_type.call_count) mock_process_native_type.assert_called_once_with(attr, xs_int) @mock.patch.object(ProcessAttributeTypes, "process_dependency_type") @mock.patch.object(ProcessAttributeTypes, "process_native_type") def test_process_type_with_dependency_type( self, mock_process_native_type, mock_process_dependency_type ) -> None: attr = AttrFactory.create() target = ClassFactory.create() attr_type = AttrTypeFactory.create() self.processor.process_type(target, attr, attr_type) self.assertEqual(0, mock_process_native_type.call_count) mock_process_dependency_type.assert_called_once_with(target, attr, attr_type) @mock.patch.object(ProcessAttributeTypes, "process_inner_type") def test_process_type_with_forward_reference(self, mock_process_inner_type) -> None: attr = AttrFactory.create() target = ClassFactory.create() attr_type = AttrTypeFactory.create(forward=True) self.processor.process_type(target, attr, attr_type) mock_process_inner_type.assert_called_once_with(target, attr, attr_type) def test_process_native_type(self) -> None: attr = AttrFactory.native(DataType.INT) nm_tokens_type = AttrTypeFactory.native(DataType.NMTOKENS) self.processor.process_native_type(attr, attr.types[0]) self.assertEqual(str(DataType.INT), attr.types[0].qname) attr.restrictions.pattern = "[a-z]" self.processor.process_native_type(attr, attr.types[0]) self.assertEqual(str(DataType.STRING), attr.types[0].qname) self.processor.process_native_type(attr, nm_tokens_type) self.assertTrue(attr.restrictions.tokens) @mock.patch.object(ProcessAttributeTypes, "reset_attribute_type") @mock.patch.object(ProcessAttributeTypes, "find_dependency") def test_process_dependency_type_with_absent_type( self, mock_find_dependency, mock_reset_attribute_type ) -> None: mock_find_dependency.return_value = None target = ClassFactory.create() attr = AttrFactory.create() attr_type = attr.types[0] self.processor.process_dependency_type(target, attr, attr_type) mock_reset_attribute_type.assert_called_once_with(attr_type) @mock.patch.object(ProcessAttributeTypes, "copy_attribute_properties") @mock.patch.object(ProcessAttributeTypes, "find_dependency") def test_process_dependency_type_with_simple_type( self, mock_find_dependency, mock_copy_attribute_properties ) -> None: simple = ClassFactory.simple_type() target = ClassFactory.create() attr = AttrFactory.create() attr_type = attr.types[0] mock_find_dependency.return_value = simple self.processor.process_dependency_type(target, attr, attr_type) mock_copy_attribute_properties.assert_called_once_with( simple, target, attr, attr_type ) @mock.patch.object(ProcessAttributeTypes, "find_dependency") def test_process_dependency_type_with_enumeration_type( self, mock_find_dependency ) -> None: enumeration = ClassFactory.enumeration(2) enumeration.attrs[1].restrictions.format = "base16" mock_find_dependency.return_value = enumeration target = ClassFactory.simple_type() attr = target.attrs[0] attr.restrictions.min_length = 10 attr.restrictions.max_length = 12 attr.types[0] = AttrTypeFactory.create(qname=enumeration.qname) self.processor.process_dependency_type(target, attr, attr.types[0]) self.assertEqual("base16", attr.restrictions.format) self.assertIsNone(attr.restrictions.min_length) self.assertIsNone(attr.restrictions.max_length) @mock.patch.object(ProcessAttributeTypes, "find_dependency") def test_process_dependency_type_with_complex_type( self, mock_find_dependency ) -> None: complex_type = ClassFactory.elements(1) mock_find_dependency.return_value = complex_type target = ClassFactory.create() attr = AttrFactory.create() attr_type = attr.types[0] self.processor.process_dependency_type(target, attr, attr_type) self.assertFalse(attr.restrictions.nillable) complex_type.nillable = True self.processor.process_dependency_type(target, attr, attr_type) self.assertTrue(attr.restrictions.nillable) self.assertEqual(complex_type.ref, attr_type.reference) @mock.patch.object(ProcessAttributeTypes, "find_dependency") def test_process_dependency_type_with_abstract_type_type( self, mock_find_dependency ) -> None: complex_type = ClassFactory.create(tag=Tag.ELEMENT, abstract=True) mock_find_dependency.return_value = complex_type attr = AttrFactory.create() attr_type = attr.types[0] target = ClassFactory.create() target.attrs.append(attr) self.assertEqual(1, len(target.attrs)) self.processor.process_dependency_type(target, attr, attr_type) self.assertEqual(0, len(target.attrs)) @mock.patch.object(ProcessAttributeTypes, "update_restrictions") @mock.patch.object(ProcessAttributeTypes, "copy_attribute_properties") def test_process_inner_type_with_simple_type( self, mock_copy_attribute_properties, mock_update_restrictions ) -> None: attr = AttrFactory.create(types=[AttrTypeFactory.create(qname="{bar}a")]) inner = ClassFactory.simple_type(qname="{bar}a", status=Status.FLATTENED) target = ClassFactory.create(inner=[inner]) self.processor.process_inner_type(target, attr, attr.types[0]) self.assertNotIn(inner, target.inner) self.assertEqual(0, mock_update_restrictions.call_count) mock_copy_attribute_properties.assert_called_once_with( inner, target, attr, attr.types[0] ) @mock.patch.object(ProcessAttributeTypes, "update_restrictions") @mock.patch.object(ProcessAttributeTypes, "copy_attribute_properties") def test_process_inner_type_with_complex_type( self, mock_copy_attribute_properties, mock_update_restrictions ) -> None: target = ClassFactory.create() inner = ClassFactory.elements(2, qname="a", status=Status.FLATTENED) attr = AttrFactory.create(types=[AttrTypeFactory.create(qname="a")]) target.inner.append(inner) self.processor.process_inner_type(target, attr, attr.types[0]) self.assertIn(inner, target.inner) self.assertEqual(0, mock_copy_attribute_properties.call_count) self.assertEqual(0, mock_update_restrictions.call_count) @mock.patch.object(ProcessAttributeTypes, "update_restrictions") @mock.patch.object(ProcessAttributeTypes, "copy_attribute_properties") def test_process_inner_type_with_circular_reference( self, mock_copy_attribute_properties, mock_update_restrictions ) -> None: target = ClassFactory.create() attr = AttrFactory.create() attr_type = AttrTypeFactory.create(circular=True) self.processor.process_inner_type(target, attr, attr_type) self.assertEqual(target.ref, attr_type.reference) self.assertEqual(0, mock_copy_attribute_properties.call_count) self.assertEqual(0, mock_update_restrictions.call_count) @mock.patch.object(ClassUtils, "copy_inner_class") def test_copy_attribute_properties(self, mock_copy_inner_class) -> None: source = ClassFactory.elements(1, qname="Foobar") source.attrs[0].restrictions.max_length = 100 source.attrs[0].restrictions.min_length = 1 source.attrs[0].restrictions.min_occurs = 0 source.attrs[0].restrictions.max_occurs = 10 source.attrs[0].help = "foo" source.attrs[0].types = [ AttrTypeFactory.create(qname="first"), AttrTypeFactory.create(qname="second"), ] target = ClassFactory.elements(1) attr = target.attrs[0] attr.restrictions.min_occurs = 1 attr.restrictions.max_occurs = 2 attr.restrictions.min_length = 2 attr.types.clear() attr.types.append(AttrTypeFactory.create(qname=source.name)) self.assertEqual("Foobar", attr.types[0].name) self.processor.copy_attribute_properties(source, target, attr, attr.types[0]) self.assertEqual("first", attr.types[0].name) self.assertEqual("second", attr.types[1].name) self.assertEqual("foo", attr.help) self.assertEqual( Restrictions(min_length=2, min_occurs=1, max_occurs=2, max_length=100), attr.restrictions, ) mock_copy_inner_class.assert_has_calls( [ mock.call(source, target, source.attrs[0].types[0]), mock.call(source, target, source.attrs[0].types[1]), ] ) @mock.patch.object(ProcessAttributeTypes, "reset_attribute_type") def test_copy_attribute_properties_from_empty_source( self, mock_reset_attribute_type ) -> None: source = ClassFactory.create() target = ClassFactory.elements(1) attr = target.attrs[0] self.processor.copy_attribute_properties(source, target, attr, attr.types[0]) mock_reset_attribute_type.assert_called_once_with(attr.types[0]) def test_copy_attribute_properties_from_nillable_source(self) -> None: source = ClassFactory.elements(1, nillable=True) target = ClassFactory.elements(1) attr = target.attrs[0] self.processor.copy_attribute_properties(source, target, attr, attr.types[0]) self.assertTrue(attr.restrictions.nillable) def test_copy_attribute_properties_set_default_value_if_none(self) -> None: target = ClassFactory.create(attrs=AttrFactory.list(1, tag=Tag.ATTRIBUTE)) attr = target.attrs[0] source = ClassFactory.elements(1) source.attrs[0].default = "foo" source.attrs[0].fixed = True self.processor.copy_attribute_properties(source, target, attr, attr.types[0]) self.assertEqual("foo", attr.default) self.assertTrue("foo", attr.fixed) source.attrs[0].default = "bar" source.attrs[0].fixed = False self.processor.copy_attribute_properties(source, target, attr, attr.types[0]) self.assertEqual("foo", attr.default) self.assertTrue("foo", attr.fixed) def test_find_dependency(self) -> None: attr_type = AttrTypeFactory.create(qname="a") element = ClassFactory.create(qname="a", tag=Tag.ELEMENT) complex_type = ClassFactory.create(qname="a", tag=Tag.COMPLEX_TYPE) simple_type = ClassFactory.create(qname="a", tag=Tag.SIMPLE_TYPE) actual = self.processor.find_dependency(element, attr_type, Tag.ELEMENT) self.assertIsNone(actual) self.processor.container.add(simple_type) actual = self.processor.find_dependency(element, attr_type, Tag.ELEMENT) self.assertEqual(simple_type, actual) self.processor.container.add(complex_type) actual = self.processor.find_dependency(element, attr_type, Tag.ELEMENT) self.assertEqual(complex_type, actual) self.processor.container.add(element) actual = self.processor.find_dependency(complex_type, attr_type, Tag.ELEMENT) self.assertEqual(element, actual) actual = self.processor.find_dependency(element, attr_type, Tag.ELEMENT) self.assertEqual(complex_type, actual) actual = self.processor.find_dependency(element, attr_type, Tag.SIMPLE_TYPE) self.assertEqual(simple_type, actual) actual = self.processor.find_dependency(element, attr_type, Tag.EXTENSION) self.assertEqual(simple_type, actual) referenced = ClassFactory.create() self.processor.container.add(referenced) attr_type.reference = referenced.ref attr_type.qname = referenced.qname actual = self.processor.find_dependency(element, attr_type, Tag.EXTENSION) self.assertEqual(referenced, actual) def test_update_restrictions(self) -> None: attr = AttrFactory.native(DataType.NMTOKENS) self.processor.update_restrictions(attr, attr.types[0].datatype) self.assertTrue(attr.restrictions.tokens) attr = AttrFactory.native(DataType.IDREFS) self.processor.update_restrictions(attr, attr.types[0].datatype) self.assertTrue(attr.restrictions.tokens) attr = AttrFactory.native(DataType.BASE64_BINARY) self.processor.update_restrictions(attr, attr.types[0].datatype) self.assertEqual("base64", attr.restrictions.format) attr = AttrFactory.native(DataType.HEX_BINARY) self.processor.update_restrictions(attr, attr.types[0].datatype) self.assertEqual("base16", attr.restrictions.format) def test_detect_lazy_namespace(self) -> None: source = ClassFactory.create(namespace="foo", tag=Tag.ELEMENT) target = ClassFactory.create() attr = AttrFactory.create(namespace="##lazy") self.processor.detect_lazy_namespace(source, target, attr) self.assertEqual("foo", attr.namespace) attr.namespace = "##lazy" source.namespace = None self.processor.detect_lazy_namespace(source, target, attr) self.assertIsNone(attr.namespace) attr.namespace = "##lazy" target.namespace = "foo" self.processor.detect_lazy_namespace(source, target, attr) self.assertEqual("", attr.namespace) tefra-xsdata-cf7afe8/tests/codegen/handlers/test_process_mixed_content_class.py000066400000000000000000000033421526437326600304440ustar00rootroot00000000000000import sys from xsdata.codegen.handlers import ProcessMixedContentClass from xsdata.codegen.models import Restrictions from xsdata.models.enums import DataType, NamespaceType from xsdata.utils.testing import AttrFactory, ClassFactory, FactoryTestCase class ProcessMixedContentClassTests(FactoryTestCase): def setUp(self) -> None: super().setUp() self.processor = ProcessMixedContentClass() def test_process(self) -> None: res = Restrictions(min_occurs=1, max_occurs=1, sequence=1) attrs = [ AttrFactory.attribute(), # keep AttrFactory.reference("foo", restrictions=res.clone()), # choice AttrFactory.native(DataType.INT, restrictions=res.clone()), # choice AttrFactory.native(DataType.ANY_TYPE, restrictions=res.clone()), # drop AttrFactory.any(), # drop ] item = ClassFactory.create(attrs=attrs) self.processor.process(item) self.assertEqual(5, len(item.attrs)) item.mixed = True self.processor.process(item) self.assertEqual(2, len(item.attrs)) self.assertEqual(attrs[0], item.attrs[0]) expected = AttrFactory.any( mixed=True, name="content", namespace=NamespaceType.ANY_NS ) expected.restrictions.min_occurs = 0 expected.restrictions.max_occurs = sys.maxsize choices = [] for attr in attrs[1:-2]: choice = attr.clone() choice.restrictions.min_occurs = None choice.restrictions.max_occurs = None choice.restrictions.sequence = None choices.append(choice) self.assertEqual(expected, item.attrs[1]) self.assertEqual(choices, item.attrs[1].choices) tefra-xsdata-cf7afe8/tests/codegen/handlers/test_rename_duplicate_attributes.py000066400000000000000000000012111526437326600304210ustar00rootroot00000000000000from unittest import mock from xsdata.codegen.handlers import RenameDuplicateAttributes from xsdata.codegen.utils import ClassUtils from xsdata.utils.testing import ClassFactory, FactoryTestCase class RenameDuplicateAttributesTests(FactoryTestCase): def setUp(self) -> None: super().setUp() self.processor = RenameDuplicateAttributes() @mock.patch.object(ClassUtils, "rename_duplicate_attributes") def test_process(self, mock_rename_duplicate_attributes) -> None: target = ClassFactory.create() self.processor.process(target) mock_rename_duplicate_attributes.assert_called_once_with(target) tefra-xsdata-cf7afe8/tests/codegen/handlers/test_rename_duplicate_classes.py000066400000000000000000000135401526437326600277000ustar00rootroot00000000000000from unittest import mock from xsdata.codegen.container import ClassContainer from xsdata.codegen.handlers import RenameDuplicateClasses from xsdata.models.config import GeneratorConfig from xsdata.models.enums import Tag from xsdata.utils.testing import ( AttrFactory, ClassFactory, FactoryTestCase, ) class RenameDuplicateClassesTests(FactoryTestCase): def setUp(self) -> None: super().setUp() self.container = ClassContainer(config=GeneratorConfig()) self.processor = RenameDuplicateClasses(container=self.container) def test_run_renames_classes_with_duplicate_qnames(self) -> None: classes = [ ClassFactory.create(qname="{foo}A", tag=Tag.ELEMENT), ClassFactory.create(qname="{foo}a", tag=Tag.ELEMENT), ClassFactory.create(qname="_a", tag=Tag.ELEMENT), ClassFactory.create(qname="_b", tag=Tag.ELEMENT), ClassFactory.create(qname="b", location="!@#$", tag=Tag.ELEMENT), ] self.container.extend(classes) self.processor.run() expected = { classes[0].ref: "{foo}A_1", classes[1].ref: "{foo}a_2", classes[3].ref: "_b_1", classes[4].ref: "b_2", } self.assertEqual(expected, self.processor.renames) self.assertEqual("{foo}A_1", classes[0].qname) self.assertEqual("{foo}a_2", classes[1].qname) self.assertEqual("_b_1", classes[3].qname) self.assertEqual("b_2", classes[4].qname) @mock.patch.object(RenameDuplicateClasses, "add_numeric_suffix") def test_rename_classes(self, mock_add_numeric_suffix) -> None: classes = [ ClassFactory.create(qname="_a", tag=Tag.ELEMENT), ClassFactory.create(qname="_A", tag=Tag.ELEMENT), ClassFactory.create(qname="a", tag=Tag.COMPLEX_TYPE), ] self.processor.rename_classes(classes) self.processor.rename_classes(classes) mock_add_numeric_suffix.assert_has_calls( [ mock.call(classes[1]), mock.call(classes[0]), mock.call(classes[2]), mock.call(classes[1]), mock.call(classes[0]), mock.call(classes[2]), ] ) @mock.patch.object(RenameDuplicateClasses, "add_abstract_suffix") def test_rename_classes_with_abstract_type(self, mock_add_abstract_suffix) -> None: classes = [ ClassFactory.create(qname="_a", tag=Tag.ELEMENT), ClassFactory.create(qname="_A", tag=Tag.ELEMENT, abstract=True), ] self.processor.rename_classes(classes) mock_add_abstract_suffix.assert_called_once_with(classes[1]) @mock.patch.object(RenameDuplicateClasses, "add_numeric_suffix") def test_rename_classes_protects_single_element(self, mock_rename_class) -> None: classes = [ ClassFactory.create(qname="_a", tag=Tag.ELEMENT), ClassFactory.create(qname="a", tag=Tag.COMPLEX_TYPE), ] self.processor.rename_classes(classes) mock_rename_class.assert_called_once_with(classes[1]) def test_add_numeric_suffix_by_slug(self) -> None: target = ClassFactory.create(qname="{foo}_a") self.processor.container.add(target) self.processor.container.add(ClassFactory.create(qname="{foo}a_1")) self.processor.container.add(ClassFactory.create(qname="{foo}A_2")) self.processor.container.add(ClassFactory.create(qname="{bar}a_3")) self.processor.add_numeric_suffix(target) self.assertEqual("{foo}_a_3", target.qname) self.assertEqual("_a", target.meta_name) self.assertEqual([target], self.container.data["{foo}_a_3"]) self.assertEqual([], self.container.data["{foo}_a"]) self.assertEqual({target.ref: target.qname}, self.processor.renames) def test_add_numeric_suffix_by_name(self) -> None: target = ClassFactory.create(qname="{foo}_a") self.processor.use_names = True self.processor.container.add(target) self.processor.container.add(ClassFactory.create(qname="{bar}a_1")) self.processor.container.add(ClassFactory.create(qname="{thug}A_2")) self.processor.container.add(ClassFactory.create(qname="{bar}a_3")) self.processor.add_numeric_suffix(target) self.assertEqual("{foo}_a_4", target.qname) self.assertEqual("_a", target.meta_name) self.assertEqual([target], self.container.data["{foo}_a_4"]) self.assertEqual([], self.container.data["{foo}_a"]) self.assertEqual({target.ref: target.qname}, self.processor.renames) def test_add_abstract_suffix(self) -> None: target = ClassFactory.create(qname="{xsdata}line", abstract=True) self.processor.container.add(target) self.processor.add_abstract_suffix(target) self.assertEqual("{xsdata}line_abstract", target.qname) self.assertEqual("line", target.meta_name) self.assertEqual({target.ref: target.qname}, self.processor.renames) def test_update_references(self) -> None: target = ClassFactory.create() target.attrs.append(AttrFactory.reference(qname="foo", reference=1)) target.attrs.append( AttrFactory.reference( qname="bar", reference=2, ) ) target.attrs[1].default = "@enum@bar::member" target.attrs.append( AttrFactory.reference( qname="thug", reference=3, ) ) self.processor.renames = {1: "bar", 2: "foo"} self.processor.update_references(target) self.assertEqual("bar", target.attrs[0].types[0].qname) self.assertEqual("foo", target.attrs[1].types[0].qname) self.assertEqual("@enum@foo::member", target.attrs[1].default) self.assertEqual("thug", target.attrs[2].types[0].qname) tefra-xsdata-cf7afe8/tests/codegen/handlers/test_reset_attribute_sequence_numbers.py000066400000000000000000000056771526437326600315260ustar00rootroot00000000000000from xsdata.codegen.container import ClassContainer, Steps from xsdata.codegen.handlers import ResetAttributeSequenceNumbers from xsdata.codegen.models import Restrictions, Status from xsdata.models.config import GeneratorConfig from xsdata.utils.testing import ( AttrFactory, ClassFactory, ExtensionFactory, FactoryTestCase, ) class ResetAttributeSequencesTests(FactoryTestCase): def setUp(self) -> None: super().setUp() self.config = GeneratorConfig() self.container = ClassContainer(config=self.config) self.container.step = Steps.FINALIZE self.processor = ResetAttributeSequenceNumbers(container=self.container) def test_process_without_sequence_fields(self) -> None: target = ClassFactory.elements(2) self.processor.process(target) def test_process_without_parent_class(self) -> None: target = ClassFactory.create( attrs=[ AttrFactory.create(restrictions=Restrictions(sequence=100)), AttrFactory.create(restrictions=Restrictions(sequence=100)), AttrFactory.create(), AttrFactory.create(restrictions=Restrictions(sequence=101)), AttrFactory.create(restrictions=Restrictions(sequence=101)), ] ) self.processor.process(target) actual = [x.restrictions.sequence for x in target.attrs] expected = [1, 1, None, 2, 2] self.assertEqual(expected, actual) def test_process_with_parent_classes(self) -> None: target = ClassFactory.create( status=Status.FINALIZING, attrs=[ AttrFactory.create(restrictions=Restrictions(sequence=100)), AttrFactory.create(restrictions=Restrictions(sequence=100)), AttrFactory.create(), AttrFactory.create(restrictions=Restrictions(sequence=101)), AttrFactory.create(restrictions=Restrictions(sequence=101)), ], ) parent_a = ClassFactory.create( status=Status.RESOLVED, attrs=[ AttrFactory.create(restrictions=Restrictions(sequence=102)), ], ) parent_b = ClassFactory.create( status=Status.RESOLVED, attrs=[ AttrFactory.create(restrictions=Restrictions(sequence=103)), ], ) parent_a.extensions.append(ExtensionFactory.reference(parent_b.qname)) target.extensions.append(ExtensionFactory.reference(parent_a.qname)) self.container.add(target) self.container.add(parent_a) self.container.add(parent_b) self.processor.process(target) actual = [x.restrictions.sequence for x in target.attrs] expected = [3, 3, None, 4, 4] self.assertEqual(expected, actual) self.assertEqual(2, parent_a.attrs[0].restrictions.sequence) self.assertEqual(1, parent_b.attrs[0].restrictions.sequence) tefra-xsdata-cf7afe8/tests/codegen/handlers/test_reset_attribute_sequences.py000066400000000000000000000046511526437326600301450ustar00rootroot00000000000000from xsdata.codegen.handlers import ResetAttributeSequences from xsdata.codegen.models import Restrictions from xsdata.utils.testing import AttrFactory, ClassFactory, FactoryTestCase class ResetAttributeSequencesTests(FactoryTestCase): def setUp(self) -> None: super().setUp() self.processor = ResetAttributeSequences() def test_process(self) -> None: target = ClassFactory.create( attrs=[ AttrFactory.element(), AttrFactory.element(restrictions=Restrictions(sequence=1)), AttrFactory.element( restrictions=Restrictions(sequence=2, path=[("s", 2, 1, 1)]), ), AttrFactory.element( restrictions=Restrictions(sequence=2, path=[("s", 2, 1, 1)]), ), AttrFactory.element( restrictions=Restrictions(sequence=3, path=[("s", 3, 1, 1)]), ), # Effective choices AttrFactory.element( restrictions=Restrictions(sequence=3, path=[("s", 3, 1, 2)]), ), AttrFactory.element( restrictions=Restrictions(sequence=3, path=[("s", 3, 1, 2)]), ), # Nested Group Sequence AttrFactory.element( restrictions=Restrictions( sequence=4, path=[("g", 3, 1, 2), ("s", 4, 1, 1)] ), ), AttrFactory.element( restrictions=Restrictions( sequence=4, path=[("g", 3, 1, 2), ("s", 4, 1, 1)] ), ), ] ) self.processor.process(target) actual = [x.restrictions.sequence for x in target.attrs] expected = [None, None, None, None, None, 3, 3, 4, 4] self.assertEqual(expected, actual) def test_is_repeatable_sequence(self) -> None: attr = AttrFactory.create() self.assertFalse(self.processor.is_repeatable_sequence(attr)) attr.restrictions.sequence = 1 self.assertFalse(self.processor.is_repeatable_sequence(attr)) attr.restrictions.path.append(("s", 15, 1, 1)) self.assertFalse(self.processor.is_repeatable_sequence(attr)) attr.restrictions.path.append(("s", 15, 1, 2)) self.assertTrue(self.processor.is_repeatable_sequence(attr)) tefra-xsdata-cf7afe8/tests/codegen/handlers/test_sanitize_attributes_default_value.py000066400000000000000000000301151526437326600316530ustar00rootroot00000000000000from unittest import mock from xsdata.codegen.container import ClassContainer from xsdata.codegen.handlers import SanitizeAttributesDefaultValue from xsdata.codegen.models import Restrictions from xsdata.models.config import GeneratorConfig from xsdata.models.enums import DataType, Namespace, Tag from xsdata.utils.testing import ( AttrFactory, AttrTypeFactory, ClassFactory, FactoryTestCase, ) class SanitizeAttributesDefaultValueTests(FactoryTestCase): def setUp(self) -> None: super().setUp() container = ClassContainer(config=GeneratorConfig()) self.processor = SanitizeAttributesDefaultValue(container=container) def test_process_attribute_with_enumeration(self) -> None: target = ClassFactory.create() attr = AttrFactory.enumeration() attr.restrictions.max_occurs = 2 attr.fixed = True self.processor.process_attribute(target, attr) self.assertTrue(attr.fixed) @mock.patch.object(SanitizeAttributesDefaultValue, "process_attribute") def test_process_with_attr_choices(self, mock_process_attribute) -> None: choice = AttrFactory.create( name="attr_B_Or_attr_C", tag="Choice", index=0, types=[AttrTypeFactory.native(DataType.ANY_TYPE)], choices=[ AttrFactory.reference("one"), AttrFactory.reference("two"), AttrFactory.reference("three"), ], ) target = ClassFactory.create() target.attrs.append(choice) self.processor.process(target) self.assertEqual(4, mock_process_attribute.call_count) mock_process_attribute.assert_has_calls( [ mock.call(target, target.attrs[0]), mock.call(target, target.attrs[0].choices[0]), mock.call(target, target.attrs[0].choices[1]), mock.call(target, target.attrs[0].choices[2]), ] ) @mock.patch.object(SanitizeAttributesDefaultValue, "process_types") @mock.patch.object(SanitizeAttributesDefaultValue, "should_reset_default") @mock.patch.object(SanitizeAttributesDefaultValue, "should_reset_required") def test_process_attribute( self, mock_should_reset_required, mock_should_reset_default, mock_process_types ) -> None: target = ClassFactory.create() mock_should_reset_required.side_effect = [ True, False, False, False, False, False, False, ] mock_should_reset_default.side_effect = [ False, True, False, False, False, False, False, ] attr = AttrFactory.element(restrictions=Restrictions(min_occurs=2)) self.processor.process_attribute(target, attr) self.assertEqual(0, attr.restrictions.min_occurs) attr = AttrFactory.element(fixed=True, default="abc") self.processor.process_attribute(target, attr) self.assertIsNone(attr.default) self.assertFalse(attr.fixed) attr = AttrFactory.element(default="abc") self.processor.process_attribute(target, attr) mock_process_types.assert_called_once_with(target, attr) attr = AttrFactory.extension() self.processor.process_attribute(target, attr) self.assertEqual("", attr.default) attr = AttrFactory.extension(default="abc") self.processor.process_attribute(target, attr) self.assertEqual("abc", attr.default) attr = AttrFactory.extension(types=[AttrTypeFactory.native(DataType.INTEGER)]) self.processor.process_attribute(target, attr) self.assertIsNone(attr.default) attr = AttrFactory.extension( types=[AttrTypeFactory.native(DataType.BASE64_BINARY)] ) self.processor.process_attribute(target, attr) self.assertEqual("", attr.default) def test_should_reset_required(self) -> None: attr = AttrFactory.create() self.assertFalse(self.processor.should_reset_required(attr)) attr = AttrFactory.any(restrictions=Restrictions(max_occurs=2)) self.assertFalse(self.processor.should_reset_required(attr)) attr = AttrFactory.any(default="abc") self.assertFalse(self.processor.should_reset_required(attr)) attr = AttrFactory.any() self.assertTrue(self.processor.should_reset_required(attr)) def test_should_reset_default(self) -> None: attr = AttrFactory.create() self.assertFalse(self.processor.should_reset_default(attr)) attr = AttrFactory.create(name="type", namespace=Namespace.XSI.uri) self.assertFalse(self.processor.should_reset_default(attr)) attr.default = "abc" self.assertTrue(self.processor.should_reset_default(attr)) attr = AttrFactory.element( default="abc", restrictions=Restrictions(min_occurs=1) ) self.assertFalse(self.processor.should_reset_default(attr)) attr.restrictions.max_occurs = 2 self.assertTrue(self.processor.should_reset_default(attr)) attr = AttrFactory.attribute( default="abc", restrictions=Restrictions(min_occurs=0) ) self.assertFalse(self.processor.should_reset_default(attr)) attr.tag = Tag.ELEMENT self.assertTrue(self.processor.should_reset_default(attr)) @mock.patch( "xsdata.codegen.handlers.sanitize_attributes_default_value.logger.warning" ) @mock.patch.object(SanitizeAttributesDefaultValue, "reset_attribute_types") @mock.patch.object(SanitizeAttributesDefaultValue, "is_valid_native_value") @mock.patch.object(SanitizeAttributesDefaultValue, "is_valid_external_value") def test_process_types( self, mock_is_valid_external_value, mock_is_valid_native_value, mock_reset_attribute_types, mock_warning, ) -> None: mock_is_valid_external_value.side_effect = [True, False, False] mock_is_valid_native_value.side_effect = [True, False] target = ClassFactory.create() attr = AttrFactory.native(DataType.INT) attr.restrictions.format = "foo" self.processor.process_types(target, attr) self.assertEqual(0, mock_is_valid_native_value.call_count) self.processor.process_types(target, attr) self.assertEqual(0, mock_reset_attribute_types.call_count) self.processor.process_types(target, attr) self.assertEqual(1, mock_reset_attribute_types.call_count) mock_warning.assert_called_once_with( "Failed to match %s.%s default value `%s` to one of %s", target.name, attr.local_name, attr.default, [str(DataType.INT)], ) def test_is_valid_native_value(self) -> None: target = ClassFactory.create() # Not native types attr = AttrFactory.create(types=[AttrTypeFactory.create("foo")], default="abc") self.assertFalse(self.processor.is_valid_native_value(target, attr)) # Successful attr = AttrFactory.native(DataType.INT, default="2") self.assertTrue(self.processor.is_valid_native_value(target, attr)) # Failed: mixed types attr = AttrFactory.native( DataType.INT, default="2 a 3", restrictions=Restrictions(tokens=True) ) self.assertFalse(self.processor.is_valid_native_value(target, attr)) # Successful: mixed types attr.types.append(AttrTypeFactory.native(DataType.STRING)) self.assertTrue(self.processor.is_valid_native_value(target, attr)) # Successful: not strict attr = AttrFactory.native(DataType.FLOAT, default="2.00") self.assertTrue(self.processor.is_valid_native_value(target, attr)) # Failed: strict attr.fixed = True self.assertFalse(self.processor.is_valid_native_value(target, attr)) # Successful qname attr = AttrFactory.enumeration() attr.types = [AttrTypeFactory.native(DataType.QNAME)] attr.restrictions.tokens = True attr.fixed = True attr.default = "a:b b:a" target.ns_map["a"] = "b" target.ns_map["b"] = "a" self.assertTrue(self.processor.is_valid_native_value(target, attr)) # Filed qname: missing prefix / Bonus tokens downgrade attr.default = "nope:a" self.assertFalse(self.processor.is_valid_native_value(target, attr)) self.assertFalse(attr.restrictions.tokens) @mock.patch.object(SanitizeAttributesDefaultValue, "is_valid_enum_type") @mock.patch.object(SanitizeAttributesDefaultValue, "is_valid_inner_type") def test_is_valid_external_value( self, mock_is_valid_inner_type, mock_is_valid_enum_type ) -> None: mock_is_valid_inner_type.side_effect = [False, False, False, True] mock_is_valid_enum_type.side_effect = [False, False, True] attr = AttrFactory.create() attr.types = [ AttrTypeFactory.create("foo"), AttrTypeFactory.create("bar"), ] target = ClassFactory.create() foo = ClassFactory.create(qname="foo") bar = ClassFactory.create(qname="bar") self.processor.container.add(foo) self.processor.container.add(bar) self.assertFalse(self.processor.is_valid_external_value(target, attr)) self.assertTrue(self.processor.is_valid_external_value(target, attr)) self.assertTrue(self.processor.is_valid_external_value(target, attr)) def test_is_valid_inner_type(self) -> None: source = ClassFactory.elements(2) attr_type = AttrTypeFactory.create() attr = AttrFactory.create() self.assertFalse(self.processor.is_valid_inner_type(source, attr, attr_type)) attr_type.forward = True self.assertFalse(self.processor.is_valid_inner_type(source, attr, attr_type)) source.attrs.append(AttrFactory.extension()) attr.default = "abc" attr.fixed = True self.assertTrue(self.processor.is_valid_inner_type(source, attr, attr_type)) self.assertFalse(attr.fixed) self.assertIsNone(attr.default) self.assertTrue(source.attrs[2].fixed) self.assertEqual("abc", source.attrs[2].default) def test_is_valid_enum_type(self) -> None: enumeration = ClassFactory.enumeration(2, qname="{a}root") enumeration.attrs[0].default = "1" enumeration.attrs[0].name = "one" enumeration.attrs[1].default = "2" enumeration.attrs[1].name = "two" attr = AttrFactory.create(default="1") self.assertTrue(self.processor.is_valid_enum_type(enumeration, attr)) self.assertEqual("@enum@{a}root::one", attr.default) attr = AttrFactory.create(default=" 1 2") self.assertTrue(self.processor.is_valid_enum_type(enumeration, attr)) self.assertEqual("@enum@{a}root::one@two", attr.default) attr = AttrFactory.create(default="3") self.assertFalse(self.processor.is_valid_enum_type(enumeration, attr)) self.assertEqual("3", attr.default) def test_find_inner_type(self) -> None: target = ClassFactory.create() attr_type = AttrTypeFactory.create("foo") foo = ClassFactory.create(qname="foo") self.processor.container.add(foo) self.assertIs(foo, self.processor.find_inner_type(target, attr_type)) attr_type = AttrTypeFactory.create("bar", forward=True) bar = ClassFactory.create(qname="bar") target.inner.append(bar) self.assertIs(bar, self.processor.find_inner_type(target, attr_type)) def test_reset_attribute_types(self) -> None: attr = AttrFactory.create( types=[ AttrTypeFactory.native(DataType.INT), AttrTypeFactory.native(DataType.FLOAT), ] ) attr.restrictions.format = "foo" self.processor.reset_attribute_types(attr) self.assertIsNone(attr.restrictions.format) self.assertEqual(1, len(attr.types)) self.assertEqual(str(DataType.STRING), attr.types[0].qname) self.assertTrue(attr.types[0].native) tefra-xsdata-cf7afe8/tests/codegen/handlers/test_sanitize_enumeration_class.py000066400000000000000000000052731526437326600303070ustar00rootroot00000000000000from xsdata.codegen.container import ClassContainer from xsdata.codegen.handlers import SanitizeEnumerationClass from xsdata.models.config import GeneratorConfig from xsdata.models.enums import DataType, Tag from xsdata.utils.testing import ( AttrFactory, AttrTypeFactory, ClassFactory, FactoryTestCase, ) class SanitizeEnumerationClassTests(FactoryTestCase): def setUp(self) -> None: super().setUp() self.root_enum = ClassFactory.enumeration(2) self.inner_enum = ClassFactory.enumeration(2) self.target = ClassFactory.create( attrs=[ AttrFactory.create( name="value", tag=Tag.UNION, types=[ AttrTypeFactory.create(qname=self.root_enum.qname), AttrTypeFactory.create( qname=self.inner_enum.qname, forward=True ), ], ), ] ) self.target.inner.append(self.inner_enum) self.container = ClassContainer(config=GeneratorConfig()) self.container.add(self.target) self.container.add(self.root_enum) self.processor = SanitizeEnumerationClass(container=self.container) def test_filter(self) -> None: target = ClassFactory.elements(2) self.processor.process(target) self.assertEqual(2, len(target.attrs)) target.attrs.append(AttrFactory.enumeration()) self.processor.process(target) self.assertEqual(1, len(target.attrs)) self.assertTrue(target.attrs[0].is_enumeration) def test_flatten_skip_if_class_has_more_than_one_attribute(self) -> None: self.target.attrs.append(AttrFactory.create()) self.processor.process(self.target) self.assertFalse(self.target.is_enumeration) self.assertEqual(2, len(self.target.attrs)) def test_flatten_skip_when_attribute_tag_is_not_union(self) -> None: self.target.attrs[0].tag = Tag.ELEMENT self.processor.process(self.target) self.assertFalse(self.target.is_enumeration) def test_flatten_skip_when_types_is_not_enumeration_union(self) -> None: self.target.attrs[0].types.append(AttrTypeFactory.native(DataType.INT)) self.processor.process(self.target) self.assertFalse(self.target.is_enumeration) def test_flatten_merges_enumeration_unions(self) -> None: self.processor.process(self.target) self.assertTrue(self.target.is_enumeration) expected = self.root_enum.attrs + self.inner_enum.attrs self.assertEqual(expected, self.target.attrs) self.assertEqual(0, len(self.target.inner)) tefra-xsdata-cf7afe8/tests/codegen/handlers/test_unnest_inner_classes.py000066400000000000000000000067221526437326600271120ustar00rootroot00000000000000from xsdata.codegen.container import ClassContainer from xsdata.codegen.handlers import UnnestInnerClasses from xsdata.models.config import GeneratorConfig from xsdata.utils.testing import ( AttrFactory, AttrTypeFactory, ClassFactory, FactoryTestCase, ) class UnnestInnerClassesTests(FactoryTestCase): def setUp(self) -> None: super().setUp() self.container = ClassContainer(config=GeneratorConfig()) self.processor = UnnestInnerClasses(container=self.container) def test_process_with_config_enabled(self) -> None: self.container.config.output.unnest_classes = True a = ClassFactory.create() b = ClassFactory.create() c = ClassFactory.create() a.attrs.append(AttrFactory.reference(b.qname, forward=True)) b.attrs.append(AttrFactory.reference(c.qname, forward=True)) c.attrs.append(AttrFactory.reference(b.qname, forward=True)) a.inner.append(b) b.inner.append(c) b.parent = a c.parent = b self.container.add(a) self.processor.process(a) self.assertEqual(3, len(list(self.container))) self.assertEqual(b.qname, a.attrs[0].types[0].qname) self.assertEqual(c.qname, b.attrs[0].types[0].qname) self.assertEqual(b.qname, c.attrs[0].types[0].qname) def test_process_with_config_disabled_promotes_only_enumerations(self) -> None: self.container.config.output.unnest_classes = False a = ClassFactory.create() b = ClassFactory.create() c = ClassFactory.enumeration(2) a.attrs.append(AttrFactory.reference(b.qname, forward=True)) b.attrs.append(AttrFactory.reference(c.qname, forward=True)) a.inner.append(b) b.inner.append(c) b.parent = a c.parent = b self.container.add(a) self.processor.process(a) self.assertEqual(2, len(list(self.container))) self.assertEqual(c.qname, b.attrs[0].types[0].qname) self.assertEqual(1, len(a.inner)) self.assertEqual(0, len(b.inner)) def test_process_with_orphan_nested_class(self) -> None: self.container.config.output.unnest_classes = True a = ClassFactory.create() b = ClassFactory.create() c = ClassFactory.create() a.inner.append(b) b.inner.append(c) b.parent = a c.parent = b self.container.add(a) self.processor.process(a) self.assertEqual(1, len(list(self.container))) self.assertEqual(0, len(a.inner)) self.assertEqual(1, len(b.inner)) def test_update_inner_class(self) -> None: a = ClassFactory.create(qname="a") b = ClassFactory.create(qname="b") c = ClassFactory.create(qname="c") a.inner.append(b) b.inner.append(c) b.parent = a c.parent = b self.processor.update_inner_class(c) self.assertEqual("b_c", c.qname) self.assertTrue(c.local_type) self.assertIsNone(c.parent) self.assertEqual(0, len(b.inner)) def test_update_types(self) -> None: types = [ AttrTypeFactory.create(qname="a", forward=True), AttrTypeFactory.create(qname="a", forward=True), ] inner = ClassFactory.create() self.processor.update_types(types, inner) for tp in types: self.assertEqual(inner.qname, tp.qname) self.assertEqual(inner.ref, tp.reference) self.assertFalse(tp.forward) tefra-xsdata-cf7afe8/tests/codegen/handlers/test_update_attributes_effective_choice.py000066400000000000000000000266721526437326600317560ustar00rootroot00000000000000from xsdata.codegen.handlers import UpdateAttributesEffectiveChoice from xsdata.codegen.models import Restrictions from xsdata.utils.testing import AttrFactory, ClassFactory, FactoryTestCase class UpdateAttributesEffectiveChoiceTests(FactoryTestCase): def setUp(self) -> None: super().setUp() self.processor = UpdateAttributesEffectiveChoice() def test_process(self) -> None: target = ClassFactory.create( attrs=[ AttrFactory.attribute( name="i", namespace="b", restrictions=Restrictions(min_occurs=1, max_occurs=1), ), AttrFactory.element( name="a", namespace="b", restrictions=Restrictions(min_occurs=1, max_occurs=1), ), AttrFactory.element( name="b", namespace="b", restrictions=Restrictions(min_occurs=1, max_occurs=1), ), AttrFactory.element( name="a", namespace="b", restrictions=Restrictions(min_occurs=1, max_occurs=1), ), AttrFactory.element( name="b", namespace="b", restrictions=Restrictions(min_occurs=1, max_occurs=1), ), AttrFactory.element( name="a", namespace="b", restrictions=Restrictions(min_occurs=1, max_occurs=1), ), ] ) self.processor.process(target) self.assertEqual(3, len(target.attrs)) actual = [ ( x.name, x.restrictions.choice, x.restrictions.min_occurs, x.restrictions.max_occurs, ) for x in target.attrs ] expected = [("i", None, 1, 1), ("a", -1, 3, 3), ("b", -1, 2, 2)] self.assertEqual(expected, actual) def test_process_symmetrical_sequence(self) -> None: restrictions = Restrictions( sequence=1, min_occurs=1, max_occurs=1, path=[("g", 0, 1, 1), ("s", 1, 1, 1)], ) target = ClassFactory.create( attrs=[ AttrFactory.element( name="a", namespace="b", restrictions=restrictions.clone() ), AttrFactory.element( name="b", namespace="b", restrictions=restrictions.clone() ), AttrFactory.element( name="a", namespace="b", restrictions=restrictions.clone() ), AttrFactory.element( name="b", namespace="b", restrictions=restrictions.clone() ), AttrFactory.element(name="c", namespace="b"), AttrFactory.element( name="d", namespace="b", restrictions=Restrictions(choice=1) ), ] ) self.processor.process(target) self.assertEqual(4, len(target.attrs)) actual = [ ( x.name, x.restrictions.choice, x.restrictions.sequence, x.restrictions.min_occurs, x.restrictions.max_occurs, x.restrictions.path, ) for x in target.attrs ] expected = [ ("a", None, 1, 2, 2, [("g", 0, 1, 1), ("s", 1, 1, 2)]), ("b", None, 1, 2, 2, [("g", 0, 1, 1), ("s", 1, 1, 2)]), ("c", None, None, None, None, []), ("d", 1, None, None, None, []), ] self.assertEqual(expected, actual) def test_process_enumeration(self) -> None: target = ClassFactory.create( attrs=[ AttrFactory.enumeration(name="a"), AttrFactory.enumeration(name="a"), AttrFactory.enumeration(name="a"), ] ) self.processor.process(target) self.assertEqual(3, len(target.attrs)) def test_reset_effective_choice_for_coverage(self) -> None: paths = [("g", 0, 1, 1), ("g", 1, 1, 1)] self.processor.reset_effective_choice(paths, 3, 4) self.assertEqual([("g", 0, 1, 1), ("g", 1, 1, 1)], paths) def test_process_elements_in_different_choice_blocks(self) -> None: """Test that elements in different choice blocks are not merged.""" choice1_id = 12345 choice2_id = 67890 target = ClassFactory.create( attrs=[ AttrFactory.element( name="configOption", namespace="", restrictions=Restrictions( min_occurs=0, max_occurs=1, choice=choice1_id ), ), AttrFactory.element( name="optionA", namespace="", restrictions=Restrictions( min_occurs=0, max_occurs=1, choice=choice1_id ), ), AttrFactory.element( name="configOption", namespace="", restrictions=Restrictions( min_occurs=0, max_occurs=1, choice=choice2_id ), ), AttrFactory.element( name="optionB", namespace="", restrictions=Restrictions( min_occurs=0, max_occurs=1, choice=choice2_id ), ), ] ) self.processor.process(target) # Should not merge configOption elements since they're in different choice blocks self.assertEqual(4, len(target.attrs)) self.assertEqual( ["configOption", "optionA", "configOption", "optionB"], [x.name for x in target.attrs], ) def test_process_elements_in_same_choice_different_branches(self) -> None: """Test that elements in same choice but different branches are not merged. This tests the case where an element appears in multiple branches of the same choice, but at different nesting levels (different path lengths). Example XSD pattern: The two 'b' elements are mutually exclusive (different branches of the same choice) and should not be grouped as repeating elements. """ choice_id = 12345 outer_seq_id = 11111 inner_seq_id = 22222 target = ClassFactory.create( attrs=[ AttrFactory.element( name="a", namespace="ns", restrictions=Restrictions( min_occurs=1, max_occurs=1, choice=choice_id, sequence=outer_seq_id, path=[ ("s", outer_seq_id, 0, 1), ("c", choice_id, 1, 1), ("s", inner_seq_id, 1, 1), ], ), ), AttrFactory.element( name="b", namespace="ns", restrictions=Restrictions( min_occurs=0, max_occurs=1, choice=choice_id, sequence=outer_seq_id, path=[ ("s", outer_seq_id, 0, 1), ("c", choice_id, 1, 1), ("s", inner_seq_id, 1, 1), ], ), ), AttrFactory.element( name="b", namespace="ns", restrictions=Restrictions( min_occurs=1, max_occurs=1, choice=choice_id, sequence=outer_seq_id, path=[ ("s", outer_seq_id, 0, 1), ("c", choice_id, 1, 1), ], ), ), ] ) self.processor.process(target) # Should not merge 'b' elements since they have different path lengths # (different nesting levels within the same choice) self.assertEqual(3, len(target.attrs)) self.assertEqual(["a", "b", "b"], [x.name for x in target.attrs]) # Both 'b' elements should retain their original max_occurs=1 self.assertEqual(1, target.attrs[1].restrictions.max_occurs) self.assertEqual(1, target.attrs[2].restrictions.max_occurs) def test_process_elements_in_same_choice_same_level(self) -> None: """Test that elements in the same choice at the same nesting level are merged. This tests the repeating pattern where an element appears multiple times within the same choice at the same nesting level. Example XSD pattern: The two 'a' elements are in the same choice at the same level and should be grouped as a repeating element. """ choice_id = 12345 path = [("c", choice_id, 1, 1)] target = ClassFactory.create( attrs=[ AttrFactory.element( name="a", namespace="ns", restrictions=Restrictions( min_occurs=1, max_occurs=1, choice=choice_id, path=path.copy(), ), ), AttrFactory.element( name="b", namespace="ns", restrictions=Restrictions( min_occurs=1, max_occurs=1, choice=choice_id, path=path.copy(), ), ), AttrFactory.element( name="a", namespace="ns", restrictions=Restrictions( min_occurs=1, max_occurs=1, choice=choice_id, path=path.copy(), ), ), ] ) self.processor.process(target) # Should merge 'a' elements since they're in the same choice at the same level self.assertEqual(2, len(target.attrs)) self.assertEqual(["a", "b"], [x.name for x in target.attrs]) # 'a' should have merged occurrences self.assertEqual(2, target.attrs[0].restrictions.min_occurs) self.assertEqual(2, target.attrs[0].restrictions.max_occurs) # 'a' should have a negative choice ID (effective choice marker) self.assertEqual(-1, target.attrs[0].restrictions.choice) tefra-xsdata-cf7afe8/tests/codegen/handlers/test_vacuum_inner_classes.py000066400000000000000000000111671526437326600270750ustar00rootroot00000000000000from collections.abc import Generator from xsdata.codegen.handlers import VacuumInnerClasses from xsdata.models.enums import DataType from xsdata.utils.testing import ( AttrFactory, AttrTypeFactory, ClassFactory, ExtensionFactory, FactoryTestCase, ) class VacuumInnerClassesTests(FactoryTestCase): def setUp(self) -> None: super().setUp() self.processor = VacuumInnerClasses() def test_remove_duplicate_inners(self) -> None: target = ClassFactory.elements(3) inner_1 = ClassFactory.elements(2) inner_2 = inner_1.clone() target.inner.extend([inner_1, inner_2]) target.attrs[1].types[0].qname = inner_1.qname target.attrs[1].types[0].forward = True target.attrs[1].types[0].reference = 4 target.attrs[2].types[0].qname = inner_2.qname target.attrs[2].types[0].forward = True target.attrs[2].types[0].circular = True target.attrs[2].types[0].reference = 5 self.processor.process(target) self.assertEqual(1, len(target.inner)) def test_remove_inner(self) -> None: target = ClassFactory.elements(3) inner_1 = ClassFactory.elements(2) inner_2 = ClassFactory.create(extensions=ExtensionFactory.list(2)) inner_3 = ClassFactory.create() inner_4 = ClassFactory.create( extensions=[ExtensionFactory.reference("foo", reference=15)] ) target.inner.extend([inner_1, inner_2, inner_3, inner_4]) target.attrs[1].types[0].qname = inner_3.qname target.attrs[1].types[0].forward = True target.attrs[1].types[0].reference = 4 target.attrs[2].types[0].qname = inner_4.qname target.attrs[2].types[0].forward = True target.attrs[2].types[0].circular = True target.attrs[2].types[0].reference = 5 self.processor.process(target) self.assertEqual(2, len(target.inner)) # Not bare: has attrs self.assertIn(inner_1, target.inner) # Not bare: has more than one extension self.assertIn(inner_2, target.inner) self.assertEqual(str(DataType.ANY_SIMPLE_TYPE), target.attrs[1].types[0].qname) self.assertEqual(0, target.attrs[1].types[0].reference) self.assertFalse(target.attrs[1].types[0].forward) self.assertFalse(target.attrs[1].types[0].circular) self.assertTrue(target.attrs[1].types[0].native) self.assertEqual( inner_4.extensions[0].type.qname, target.attrs[2].types[0].qname ) self.assertEqual( inner_4.extensions[0].type.reference, target.attrs[2].types[0].reference ) self.assertFalse(target.attrs[2].types[0].forward) self.assertFalse(target.attrs[2].types[0].circular) self.assertFalse(target.attrs[2].types[0].native) def test_rename_inner(self) -> None: outer = ClassFactory.create(qname="{xsdata}foo") inner = ClassFactory.elements(1, qname="{xsdata}foo") outer.attrs.append(AttrFactory.reference(inner.qname, forward=True)) outer.inner.append(inner) self.assertEqual("{xsdata}foo", outer.attrs[0].types[0].qname) self.assertEqual("{xsdata}foo", outer.inner[0].qname) self.processor.process(outer) self.assertEqual("{xsdata}foo_Inner", outer.attrs[0].types[0].qname) self.assertEqual("{xsdata}foo_Inner", outer.inner[0].qname) def test_find_attr_types_with_attr_choices(self) -> None: choices = [ AttrFactory.create( types=[ AttrTypeFactory.create("bar", forward=True), AttrTypeFactory.create("foo", forward=True), ] ), AttrFactory.reference("foo"), AttrFactory.reference("foo", forward=True), AttrFactory.reference("bar", forward=True), ] choice = AttrFactory.create( name="attr_B_Or_attr_C", tag="Choice", index=0, types=[AttrTypeFactory.native(DataType.ANY_TYPE)], choices=choices, ) target = ClassFactory.create() target.attrs.append(choice) result = self.processor.find_attr_types(target, "foo") self.assertIsInstance(result, Generator) self.assertEqual(choices[0].types[1], next(result)) self.assertEqual(choices[2].types[0], next(result)) self.assertIsNone(next(result, None)) result = self.processor.find_attr_types(target, "bar") self.assertEqual(choices[0].types[0], next(result)) self.assertEqual(choices[3].types[0], next(result)) self.assertIsNone(next(result, None)) tefra-xsdata-cf7afe8/tests/codegen/handlers/test_validate_attributes_overrides.py000066400000000000000000000170571526437326600310120ustar00rootroot00000000000000import sys from unittest import mock from xsdata.codegen.container import ClassContainer from xsdata.codegen.handlers import ValidateAttributesOverrides from xsdata.codegen.models import Status from xsdata.models.config import GeneratorConfig from xsdata.models.enums import DataType, Tag from xsdata.utils.testing import ( AttrFactory, ClassFactory, ExtensionFactory, FactoryTestCase, ) class ValidateAttributesOverridesTests(FactoryTestCase): def setUp(self) -> None: super().setUp() self.container = ClassContainer(config=GeneratorConfig()) self.processor = ValidateAttributesOverrides(container=self.container) def test_prohibit_parent_attrs(self) -> None: child = ClassFactory.create( status=Status.FLATTENING, attrs=[ AttrFactory.create(name="el", tag=Tag.ELEMENT), AttrFactory.create(name="at", tag=Tag.ATTRIBUTE), ], ) parent = ClassFactory.create( status=Status.FLATTENED, attrs=[ AttrFactory.element(default="foo"), AttrFactory.attribute(), AttrFactory.extension(), AttrFactory.element(default="bar"), ], ) child.extensions.append( ExtensionFactory.reference(parent.qname, tag=Tag.RESTRICTION) ) self.container.extend((parent, child)) self.processor.process(child) self.assertEqual(4, len(child.attrs)) self.assertEqual(parent.attrs[0].name, child.attrs[0].name) self.assertEqual([], child.attrs[0].types) self.assertIsNone(child.attrs[0].default) self.assertTrue(child.attrs[0].is_prohibited) self.assertEqual(parent.attrs[3].name, child.attrs[1].name) self.assertEqual([], child.attrs[1].types) self.assertIsNone(child.attrs[1].default) self.assertTrue(child.attrs[1].is_prohibited) @mock.patch.object(ValidateAttributesOverrides, "resolve_conflict") @mock.patch.object(ValidateAttributesOverrides, "validate_override") def test_validate_attrs( self, mock_validate_override, mock_resolve_conflict ) -> None: class_a = ClassFactory.create( status=Status.FLATTENING, attrs=[ AttrFactory.create(name="el", tag=Tag.ELEMENT), AttrFactory.create(name="at", tag=Tag.ATTRIBUTE), ], ) class_b = ClassFactory.elements(2, status=Status.FLATTENED) class_c = ClassFactory.create(status=Status.FLATTENED) class_b.extensions.append( ExtensionFactory.reference(class_c.qname, tag=Tag.EXTENSION) ) class_a.extensions.append( ExtensionFactory.reference(class_b.qname, tag=Tag.EXTENSION) ) class_c.attrs.append(class_a.attrs[0].clone()) class_c.attrs.append(class_a.attrs[1].clone()) class_c.attrs[1].tag = Tag.ELEMENT self.container.extend((class_a, class_b, class_c)) self.processor.process(class_a) mock_validate_override.assert_called_once_with( class_a, class_a.attrs[0], class_c.attrs[0] ) mock_resolve_conflict.assert_called_once_with( class_a.attrs[1], class_c.attrs[1] ) def test_validate_attrs_remove_non_overriding_prohibited_attrs(self) -> None: target = ClassFactory.elements(1) target.attrs[0].restrictions.max_occurs = 0 self.processor.process(target) self.assertEqual(0, len(target.attrs)) def test_overrides(self) -> None: a = AttrFactory.create(tag=Tag.SIMPLE_TYPE) b = a.clone() self.assertTrue(self.processor.overrides(a, b)) b.tag = Tag.EXTENSION self.assertTrue(self.processor.overrides(a, b)) b.namespace = "foo" self.assertFalse(self.processor.overrides(a, b)) def test_validate_override(self) -> None: attr_a = AttrFactory.create() attr_b = attr_a.clone() attr_b.parent = ClassFactory.create().qname target = ClassFactory.create() target.attrs.append(attr_a) # 100 Match remove override attrs self.processor.validate_override(target, attr_a, attr_b) self.assertEqual(0, len(target.attrs)) # default doesn't match target.attrs.append(attr_a) attr_b.default = "1" self.processor.validate_override(target, attr_a, attr_b) self.assertEqual(1, len(target.attrs)) # mixed doesn't match attr_b.default = attr_a.default attr_b.mixed = True self.processor.validate_override(target, attr_a, attr_b) self.assertEqual(1, len(target.attrs)) # fixed doesn't match attr_b.mixed = attr_a.mixed attr_b.fixed = True self.processor.validate_override(target, attr_a, attr_b) self.assertEqual(1, len(target.attrs)) # Restrictions don't match attr_b.fixed = attr_a.fixed attr_a.restrictions.tokens = not attr_b.restrictions.tokens attr_a.restrictions.nillable = not attr_b.restrictions.nillable attr_a.restrictions.min_occurs = 0 attr_b.restrictions.min_occurs = 1 attr_a.restrictions.max_occurs = 0 attr_b.restrictions.max_occurs = 1 self.processor.validate_override(target, attr_a, attr_b) self.assertEqual(1, len(target.attrs)) # Restrictions are compatible again attr_a.restrictions.tokens = attr_b.restrictions.tokens attr_a.restrictions.nillable = attr_b.restrictions.nillable attr_a.restrictions.min_occurs = attr_b.restrictions.min_occurs = 1 attr_a.restrictions.max_occurs = attr_b.restrictions.max_occurs = 1 self.processor.validate_override(target, attr_a, attr_b) self.assertEqual(0, len(target.attrs)) # Source is list, parent is not target.attrs.append(attr_a) attr_a.restrictions.min_occurs = None attr_a.restrictions.max_occurs = 10 attr_b.restrictions.min_occurs = None attr_b.restrictions.max_occurs = None self.processor.validate_override(target, attr_a, attr_b) self.assertEqual(sys.maxsize, attr_b.restrictions.max_occurs) # Parent is list, source is not target.attrs = [attr_a] attr_a.restrictions.min_occurs = 0 attr_a.restrictions.max_occurs = 1 attr_b.restrictions.min_occurs = 0 attr_b.restrictions.max_occurs = 2 self.processor.validate_override(target, attr_a, attr_b) self.assertEqual(2, attr_b.restrictions.max_occurs) # Source is list, parent is prohibited target.attrs = [attr_a] attr_a.restrictions.min_occurs = None attr_a.restrictions.max_occurs = 10 attr_b.restrictions.min_occurs = 0 attr_b.restrictions.max_occurs = 0 self.processor.validate_override(target, attr_a, attr_b) self.assertEqual(0, attr_b.restrictions.max_occurs) self.assertIn(attr_a, target.attrs) # Parent is any type, source isn't, skip attr_a = AttrFactory.native(DataType.STRING) attr_b = AttrFactory.native(DataType.ANY_SIMPLE_TYPE) target = ClassFactory.create(attrs=[attr_a]) self.processor.validate_override(target, attr_a.clone(), attr_b) self.assertEqual(attr_a, target.attrs[0]) def test_resolve_conflicts(self) -> None: a = AttrFactory.create(name="foo", tag=Tag.ATTRIBUTE) b = a.clone() b.tag = Tag.ELEMENT self.processor.resolve_conflict(a, b) self.assertEqual("foo_Attribute", a.name) self.assertEqual("foo", b.name) tefra-xsdata-cf7afe8/tests/codegen/handlers/test_validate_references.py000066400000000000000000000057361526437326600266640ustar00rootroot00000000000000from xsdata.codegen.container import ClassContainer from xsdata.codegen.exceptions import CodegenError from xsdata.codegen.handlers import ValidateReferences from xsdata.models.config import ( GeneratorConfig, ) from xsdata.utils.testing import ( AttrFactory, AttrTypeFactory, ClassFactory, ExtensionFactory, FactoryTestCase, ) class ValidateReferencesTests(FactoryTestCase): def setUp(self) -> None: super().setUp() self.config = GeneratorConfig() self.container = ClassContainer(config=self.config) self.handler = ValidateReferences(container=self.container) def test_validate_unique_qualified_names(self) -> None: first = ClassFactory.create() second = first.clone() self.container.extend([first, second]) with self.assertRaises(CodegenError): self.handler.run() def test_validate_unique_instances(self) -> None: first = ClassFactory.create() first.extensions.append(ExtensionFactory.create()) second = ClassFactory.create() second.extensions = first.extensions self.container.extend([first, second]) with self.assertRaises(CodegenError): self.handler.run() def test_validate_unresolved_references(self) -> None: first = ClassFactory.create() first.attrs.append(AttrFactory.create()) first.attrs.append(AttrFactory.reference("foo")) self.container.add(first) with self.assertRaises(CodegenError): self.handler.run() def test_validate_misrepresented_references(self) -> None: first = ClassFactory.create() inner = ClassFactory.create() first.inner.append(inner) first.attrs.append( AttrFactory.create( types=[AttrTypeFactory.create(qname="foo", reference=inner.ref)] ) ) self.container.add(first) with self.assertRaises(CodegenError): self.handler.run() def test_validate_parent_references_with_root_class_with_parent(self) -> None: target = ClassFactory.create() target.parent = ClassFactory.create() self.container.add(target) with self.assertRaises(CodegenError): self.handler.run() def test_validate_parent_references_with_wrong_parent(self) -> None: parent = ClassFactory.create() child = ClassFactory.create() wrong = ClassFactory.create() parent.inner.append(child) child.parent = wrong self.container.extend([parent, wrong]) with self.assertRaises(CodegenError): self.handler.run() def test_validate_parent_references_with_wrong_parent_ref(self) -> None: parent = ClassFactory.create() child = ClassFactory.create() wrong = parent.clone() parent.inner.append(child) child.parent = wrong self.container.extend([parent]) with self.assertRaises(CodegenError): self.handler.run() tefra-xsdata-cf7afe8/tests/codegen/mappers/000077500000000000000000000000001526437326600211155ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/codegen/mappers/__init__.py000066400000000000000000000000001526437326600232140ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/codegen/mappers/test_definitions.py000066400000000000000000000715411526437326600250510ustar00rootroot00000000000000from collections.abc import Generator from typing import Any from unittest import mock from xsdata.codegen.mappers import DefinitionsMapper from xsdata.codegen.models import Attr, Class, Status from xsdata.formats.dataclass.models.generics import AnyElement from xsdata.models.enums import DataType, Namespace, Tag from xsdata.models.wsdl import ( Binding, BindingMessage, BindingOperation, Definitions, Message, Part, PortType, PortTypeMessage, PortTypeOperation, Service, ServicePort, ) from xsdata.utils.namespaces import build_qname from xsdata.utils.testing import AttrFactory, ClassFactory, FactoryTestCase def mock_create_inner(target: Class, name: str) -> Class: for inner in target.inner: if inner.name == name: return inner inner = ClassFactory.create(qname=name) target.inner.append(inner) return inner def mock_create_attr(*args: Any, **kwargs: Any) -> list[Attr]: return AttrFactory.list(1) class DefinitionsMapperTests(FactoryTestCase): @mock.patch.object(DefinitionsMapper, "map_port") def test_map(self, mock_map_port) -> None: classes = [ ClassFactory.list(2), ClassFactory.list(3), ] mock_map_port.side_effect = classes definitions = Definitions() service = Service(ports=[ServicePort() for _ in range(2)]) definitions.services.append(service) result = DefinitionsMapper.map(definitions) self.assertEqual(classes[0] + classes[1], result) mock_map_port.assert_has_calls( [mock.call(definitions, port) for port in service.ports] ) @mock.patch.object(DefinitionsMapper, "map_binding") @mock.patch.object(DefinitionsMapper, "attributes") @mock.patch.object(Definitions, "find_port_type") @mock.patch.object(Definitions, "find_binding") def test_map_port( self, mock_find_binding, mock_find_port_type, mock_attributes, mock_map_binding ) -> None: definitions = Definitions() service_port = ServicePort(binding="zaa:port", extended=[AnyElement()]) port_type = PortType(name="Calc") binding = Binding( type="zaa:zoo", extended=[AnyElement()], operations=[BindingOperation(name="ADD"), BindingOperation(name="SUB")], ) classes = ClassFactory.list(2) cfg = { "style": "document", "location": "http://endpoint.stub/action", "transport": "public", } mock_find_binding.return_value = binding mock_find_port_type.return_value = port_type mock_map_binding.return_value = classes mock_attributes.return_value = cfg result = DefinitionsMapper.map_port(definitions, service_port) self.assertIsInstance(result, Generator) self.assertEqual(classes, list(result)) mock_find_binding.assert_called_once_with("port") mock_find_port_type.assert_called_once_with("zoo") self.assertEqual(2, len(list(mock_attributes.call_args[0][0]))) self.assertEqual(1, mock_attributes.call_count) mock_map_binding.assert_called_once_with(definitions, binding, port_type, cfg) @mock.patch.object(DefinitionsMapper, "map_binding_operation") @mock.patch.object(PortType, "find_operation") @mock.patch.object(DefinitionsMapper, "attributes") def test_map_binding( self, mock_attributes, mock_find_operation, mock_map_binding_operation ) -> None: definitions = Definitions() port_type = PortType(name="Calc") binding = Binding( type="zaa:zoo", operations=[BindingOperation(name="ADD"), BindingOperation(name="SUB")], ) port_type_add = PortTypeOperation(name="PortAdd") port_type_sub = PortTypeOperation(name="PortSub") classes = [ ClassFactory.list(2), ClassFactory.list(2), ] mock_map_binding_operation.side_effect = classes mock_find_operation.side_effect = [port_type_add, port_type_sub] mock_attributes.side_effect = [ {"soapAction": "add"}, {"soapAction": "sub"}, ] config = {"foo": "bar"} result = DefinitionsMapper.map_binding(definitions, binding, port_type, config) self.assertIsInstance(result, Generator) self.assertEqual(classes[0] + classes[1], list(result)) mock_find_operation.assert_has_calls([mock.call("ADD"), mock.call("SUB")]) self.assertEqual(2, mock_attributes.call_count) config_add = {"soapAction": "add", **config} config_sub = {"soapAction": "sub", **config} mock_map_binding_operation.assert_has_calls( [ mock.call( definitions, binding.operations[0], port_type_add, config_add, "Calc", ), mock.call( definitions, binding.operations[1], port_type_sub, config_sub, "Calc", ), ] ) @mock.patch.object(DefinitionsMapper, "map_binding_operation_messages") @mock.patch.object(DefinitionsMapper, "operation_namespace") def test_map_binding_operation( self, mock_operation_namespace, mock_map_binding_operation_messages ) -> None: definitions = Definitions(target_namespace="xsdata") operation = BindingOperation(name="Add", location="foo.wsdl") operation.ns_map["foo"] = "bar" port_operation = PortTypeOperation() config = {"a": "one", "b": "two", "style": "rpc"} name = "Calc" namespace = "SomeNS" first = ClassFactory.create(qname="some_name_first", meta_name="Envelope") second = ClassFactory.create(qname="some_name_second", meta_name="Envelope") other = ClassFactory.create() service = ClassFactory.create( qname=build_qname("xsdata", "Calc_Add"), status=Status.FLATTENED, tag=Tag.BINDING_OPERATION, location="foo.wsdl", module=None, ns_map={"foo": "bar"}, attrs=[ DefinitionsMapper.build_attr( "a", str(DataType.STRING), native=True, default="one" ), DefinitionsMapper.build_attr( "b", str(DataType.STRING), native=True, default="two" ), DefinitionsMapper.build_attr( "style", str(DataType.STRING), native=True, default="rpc" ), DefinitionsMapper.build_attr("first", first.qname), DefinitionsMapper.build_attr("second", second.qname), ], ) mock_operation_namespace.return_value = namespace mock_map_binding_operation_messages.return_value = [first, second, other] result = DefinitionsMapper.map_binding_operation( definitions, operation, port_operation, config, name ) expected = [first, second, other, service] self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) mock_operation_namespace.assert_called_once_with(config) mock_map_binding_operation_messages.assert_called_once_with( definitions, operation, port_operation, service.name, "rpc", namespace ) @mock.patch.object(DefinitionsMapper, "build_envelope_fault") @mock.patch.object(DefinitionsMapper, "build_envelope_class") def test_map_binding_operation_messages( self, mock_build_envelope_class, mock_build_envelope_fault ) -> None: definitions = Definitions() operation = BindingOperation() port_operation = PortTypeOperation() name = "Add" namespace = "someNS" target = ClassFactory.create() style = "document" mock_build_envelope_class.return_value = target result = DefinitionsMapper.map_binding_operation_messages( definitions, operation, port_operation, name, style, namespace ) self.assertIsInstance(result, Generator) self.assertEqual(0, len(list(result))) operation.input = BindingMessage() port_operation.input = PortTypeMessage() result = DefinitionsMapper.map_binding_operation_messages( definitions, operation, port_operation, name, style, namespace ) self.maxDiff = None self.assertEqual([target], list(result)) operation.output = BindingMessage() port_operation.output = PortTypeMessage() result = DefinitionsMapper.map_binding_operation_messages( definitions, operation, port_operation, name, style, namespace ) self.assertEqual([target, target], list(result)) mock_build_envelope_class.assert_has_calls( [ mock.call( definitions, operation.input, port_operation.input, f"{name}_input", style, namespace, None, ), mock.call( definitions, operation.input, port_operation.input, f"{name}_input", style, namespace, None, ), mock.call( definitions, operation.output, port_operation.output, f"{name}_output", style, namespace, None, ), ] ) mock_build_envelope_fault.assert_called_once_with( definitions, port_operation, target ) @mock.patch.object(DefinitionsMapper, "build_message_class") @mock.patch.object(DefinitionsMapper, "build_envelope_class") def test_map_binding_operation_messages_with_style_rpc( self, mock_build_envelope_class, mock_build_message_class ) -> None: definitions = Definitions() operation = BindingOperation() port_operation = PortTypeOperation() name = "Add" namespace = "someNS" target = ClassFactory.create() message = ClassFactory.create() style = "rpc" mock_build_message_class.return_value = message mock_build_envelope_class.return_value = target operation.input = BindingMessage() operation.name = "Add" port_operation.input = PortTypeMessage() result = DefinitionsMapper.map_binding_operation_messages( definitions, operation, port_operation, name, style, namespace ) self.maxDiff = None self.assertEqual([message, target], list(result)) mock_build_message_class.assert_called_once_with( definitions, port_operation.input ) mock_build_envelope_class.assert_called_once_with( definitions, operation.input, port_operation.input, f"{name}_input", style, namespace, "Add", ) @mock.patch.object(DefinitionsMapper, "map_port_type_message") @mock.patch.object(DefinitionsMapper, "map_binding_message_parts") @mock.patch.object(DefinitionsMapper, "build_inner_class") def test_build_envelope_class( self, mock_get_or_create_inner_class, mock_map_binding_message_parts, mock_map_port_type_message, ) -> None: mock_get_or_create_inner_class.side_effect = mock_create_inner mock_map_binding_message_parts.side_effect = mock_create_attr name = "some_operation_bindings" style = "document" namespace = "xsdata" definitions = Definitions(target_namespace="bar") port_type_message = PortTypeMessage( message="some_operation", location="foo.wsdl" ) binding_message = BindingMessage( extended=[ AnyElement(qname="body"), AnyElement(qname="header"), AnyElement(qname="header"), ], location="foo.wsdl", ) binding_message.ns_map["foo"] = "bar" result = DefinitionsMapper.build_envelope_class( definitions, binding_message, port_type_message, name, style, namespace, None, ) expected = Class( qname=build_qname("bar", name), meta_name="Envelope", tag=Tag.BINDING_MESSAGE, location="foo.wsdl", ns_map={"foo": "bar"}, namespace="xsdata", ) self.assertEqual(2, len(result.inner)) self.assertEqual(1, len(result.inner[0].attrs)) self.assertEqual(2, len(result.inner[1].attrs)) self.assertEqual(0, mock_map_port_type_message.call_count) mock_map_binding_message_parts.assert_has_calls( [ mock.call( definitions, port_type_message.message, binding_message.extended[0], result.inner[0].ns_map, ), mock.call( definitions, port_type_message.message, binding_message.extended[1], result.inner[1].ns_map, ), mock.call( definitions, port_type_message.message, binding_message.extended[2], result.inner[1].ns_map, ), ] ) mock_get_or_create_inner_class.assert_has_calls( [ mock.call(mock.ANY, "Body"), mock.call(mock.ANY, "Header"), mock.call(mock.ANY, "Header"), ] ) result.inner.clear() self.assertEqual(expected, result) @mock.patch.object(DefinitionsMapper, "map_port_type_message") @mock.patch.object(DefinitionsMapper, "map_binding_message_parts") @mock.patch.object(DefinitionsMapper, "build_inner_class") def test_build_envelope_class_with_style_rpc( self, mock_get_or_create_inner_class, mock_map_binding_message_parts, mock_map_port_type_message, ) -> None: mock_get_or_create_inner_class.side_effect = mock_create_inner mock_map_binding_message_parts.side_effect = mock_create_attr mock_map_port_type_message.side_effect = mock_create_attr name = "some_operation_bindings" style = "rpc" namespace = "xsdata" definitions = Definitions(target_namespace="bar") port_type_message = PortTypeMessage( message="some_operation", location="foo.wsdl" ) binding_message = BindingMessage( extended=[ AnyElement(qname="body", attributes={"namespace": "bodyns"}), AnyElement(qname="header"), AnyElement(qname="header"), ], location="foo.wsdl", ) binding_message.ns_map["foo"] = "bar" result = DefinitionsMapper.build_envelope_class( definitions, binding_message, port_type_message, name, style, namespace, None, ) expected = Class( qname=build_qname("bar", name), meta_name="Envelope", tag=Tag.BINDING_MESSAGE, location="foo.wsdl", ns_map={"foo": "bar"}, namespace="xsdata", ) self.assertEqual(2, len(result.inner)) self.assertEqual(1, len(result.inner[0].attrs)) self.assertEqual(2, len(result.inner[1].attrs)) self.maxDiff = None mock_map_port_type_message.assert_called_once_with( None, port_type_message, "bodyns" ) mock_map_binding_message_parts.assert_has_calls( [ mock.call( definitions, port_type_message.message, binding_message.extended[1], result.inner[1].ns_map, ), mock.call( definitions, port_type_message.message, binding_message.extended[2], result.inner[1].ns_map, ), ] ) mock_get_or_create_inner_class.assert_has_calls( [ mock.call(mock.ANY, "Body"), mock.call(mock.ANY, "Header"), mock.call(mock.ANY, "Header"), ] ) result.inner.clear() self.assertEqual(expected, result) def test_build_envelope_fault(self) -> None: body = ClassFactory.create(qname="Body") target = ClassFactory.create() target.inner.append(body) port_type_operation = PortTypeOperation() definitions = Definitions() DefinitionsMapper.build_envelope_fault(definitions, port_type_operation, target) expected_fault_attr = DefinitionsMapper.build_attr( "Fault", body.inner[0].qname, forward=True, namespace=target.namespace ) str_qname = str(DataType.STRING) expected_fault_attrs = [ DefinitionsMapper.build_attr(name, str_qname, native=True, namespace="") for name in ["faultcode", "faultstring", "faultactor", "detail"] ] self.assertEqual(1, len(body.attrs)) self.assertEqual(expected_fault_attr, body.attrs[0]) self.assertEqual(expected_fault_attrs, body.inner[0].attrs) for attr in body.attrs: self.assertTrue(attr.is_optional) def test_build_envelope_fault_with_detail_messages(self) -> None: body = ClassFactory.create(qname="Body") target = ClassFactory.create() target.inner.append(body) port_type_operation = PortTypeOperation() port_type_operation.faults.append(PortTypeMessage(message="x:foo")) port_type_operation.faults.append(PortTypeMessage(message="x:bar")) definitions = Definitions() definitions.messages.append(Message(name="foo", parts=[Part(element="fooEl")])) definitions.messages.append(Message(name="bar", parts=[Part(element="barEl")])) DefinitionsMapper.build_envelope_fault(definitions, port_type_operation, target) expected_fault_attr = DefinitionsMapper.build_attr( "Fault", body.inner[0].qname, forward=True, namespace=target.namespace ) str_qname = str(DataType.STRING) expected_fault_attrs = [ DefinitionsMapper.build_attr(name, str_qname, native=True, namespace="") for name in ["faultcode", "faultstring", "faultactor"] ] expected_fault_attrs.append( DefinitionsMapper.build_attr( "detail", body.inner[0].inner[0].qname, forward=True, namespace="" ) ) expected_fault_detail_attrs = [ DefinitionsMapper.build_attr( name, qname=name, namespace=target.namespace, native=False ) for name in ["fooEl", "barEl"] ] self.assertEqual(1, len(body.attrs)) self.assertEqual(expected_fault_attr, body.attrs[0]) self.assertEqual(expected_fault_attrs, body.inner[0].attrs) self.assertEqual(expected_fault_detail_attrs, body.inner[0].inner[0].attrs) def test_build_envelope_fault_raises_error_if_missing_inner_body(self) -> None: target = ClassFactory.create() operation = PortTypeOperation() definitions = Definitions() with self.assertRaises(StopIteration): DefinitionsMapper.build_envelope_fault(definitions, operation, target) @mock.patch.object(DefinitionsMapper, "build_parts_attributes") @mock.patch.object(Definitions, "find_message") def test_map_binding_message_parts_with_part_token( self, mock_find_message, mock_create_message_attributes ) -> None: definitions = Definitions() message_name = "session" ns_map = {} message = Message( name="session", parts=[ Part(name="token", element="foo:token"), Part(name="messageId", type="id"), ], ) extended = AnyElement(attributes={"part": "token", "message": "{bar}session"}) mock_create_message_attributes.return_value = AttrFactory.list(2) mock_find_message.return_value = message actual = DefinitionsMapper.map_binding_message_parts( definitions, message_name, extended, ns_map ) self.assertIsInstance(actual, Generator) self.assertEqual(2, len(list(actual))) mock_create_message_attributes.assert_called_once_with( message.parts[0:1], ns_map ) @mock.patch.object(DefinitionsMapper, "build_parts_attributes") @mock.patch.object(Definitions, "find_message") def test_map_binding_message_parts_with_token_parts( self, mock_find_message, mock_create_message_attributes ) -> None: definitions = Definitions() message_name = "session" ns_map = {} message = Message( name="session", parts=[ Part(name="token", element="foo:token"), Part(name="messageId", type="id"), Part(name="another", type="id"), ], ) extended = AnyElement( attributes={"parts": "token messageId", "message": "{bar}session"} ) mock_create_message_attributes.return_value = AttrFactory.list(2) mock_find_message.return_value = message actual = DefinitionsMapper.map_binding_message_parts( definitions, message_name, extended, ns_map ) self.assertIsInstance(actual, Generator) self.assertEqual(2, len(list(actual))) mock_create_message_attributes.assert_called_once_with( message.parts[0:2], ns_map ) @mock.patch.object(DefinitionsMapper, "build_parts_attributes") @mock.patch.object(Definitions, "find_message") def test_map_binding_message_parts_with_all_parts( self, mock_find_message, mock_create_message_attributes ) -> None: definitions = Definitions() message_name = "session" ns_map = {} message = Message( name="session", parts=[ Part(name="token", element="foo:token"), Part(name="messageId", type="id"), Part(name="another", type="id"), ], ) extended = AnyElement(attributes={"message": "{bar}session"}) mock_create_message_attributes.return_value = AttrFactory.list(2) mock_find_message.return_value = message actual = DefinitionsMapper.map_binding_message_parts( definitions, message_name, extended, ns_map ) self.assertIsInstance(actual, Generator) self.assertEqual(2, len(list(actual))) mock_find_message.assert_called_once_with("session") mock_create_message_attributes.assert_called_once_with(message.parts, ns_map) @mock.patch.object(DefinitionsMapper, "build_parts_attributes") @mock.patch.object(Definitions, "find_message") def test_map_binding_message_parts_with_original_message( self, mock_find_message, mock_create_message_attributes ) -> None: definitions = Definitions message_name = "foo:bar" ns_map = {} message = Message( name="session", parts=[ Part(name="token", element="foo:token"), Part(name="messageId", type="id"), Part(name="another", type="id"), ], ) extended = AnyElement() mock_create_message_attributes.return_value = AttrFactory.list(2) mock_find_message.return_value = message actual = DefinitionsMapper.map_binding_message_parts( definitions, message_name, extended, ns_map ) self.assertIsInstance(actual, Generator) self.assertEqual(2, len(list(actual))) mock_create_message_attributes.assert_called_once_with(message.parts, ns_map) mock_find_message.assert_called_once_with("bar") @mock.patch("xsdata.codegen.mappers.definitions.logger.warning") def test_build_parts_attributes(self, mock_warning) -> None: p_one = Part(element="a:bar") p_one.ns_map["a"] = "great" p_two = Part(name="arg0", type="xs:string") p_two.ns_map["xs"] = Namespace.XS.uri p_three = Part(name="arg1", type="b:cafe") p_three.ns_map["b"] = "boo" p_four = Part(name="arg2") ns_map = {} parts = [p_one, p_two, p_three, p_four] result = DefinitionsMapper.build_parts_attributes(parts, ns_map) expected = [ DefinitionsMapper.build_attr( "bar", build_qname("great", "bar"), namespace="great", native=False ), DefinitionsMapper.build_attr( "arg0", str(DataType.STRING), namespace="", native=True ), DefinitionsMapper.build_attr( "arg1", build_qname("boo", "cafe"), namespace="##lazy", native=False ), ] self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) mock_warning.assert_called_once_with("Skip untyped message part %s", "arg2") @mock.patch.object(DefinitionsMapper, "build_parts_attributes") def test_build_message_class(self, mock_create_message_attributes) -> None: message = Message(name="bar", parts=[Part()]) message.ns_map["foo"] = "bar" definitions = Definitions(messages=[message], target_namespace="xsdata") port_type_message = PortTypeMessage(message="foo:bar", location="foo.wsdl") attrs = AttrFactory.list(2) mock_create_message_attributes.return_value = attrs actual = DefinitionsMapper.build_message_class(definitions, port_type_message) expected = Class( qname=build_qname("bar", "bar"), status=Status.RAW, tag=Tag.ELEMENT, location="foo.wsdl", namespace="bar", ns_map=message.ns_map, attrs=attrs, ) self.assertEqual(expected, actual) def test_get_or_create_inner_class(self) -> None: target = ClassFactory.create( location="foo", package=None, ns_map={"foo": "bar"} ) actual = DefinitionsMapper.build_inner_class(target, "body") expected = ClassFactory.create( qname="{xsdata}body", tag=Tag.BINDING_MESSAGE, location=target.location, module=None, ns_map=target.ns_map, ) self.assertEqual(expected, actual) self.assertIn(actual, target.inner) self.assertIsNot(actual.ns_map, target.ns_map) expected_attr = DefinitionsMapper.build_attr("body", "body", forward=True) self.assertEqual(expected_attr, target.attrs[0]) repeat = DefinitionsMapper.build_inner_class(target, "body") self.assertIs(repeat, actual) def test_map_port_type_message(self) -> None: port_type_message = PortTypeMessage(message="foo:bar") port_type_message.ns_map["foo"] = "foobar" target_namespace = "xsdata" actual = DefinitionsMapper.map_port_type_message( None, port_type_message, target_namespace ) expected = DefinitionsMapper.build_attr( "bar", qname=build_qname("foobar", "bar"), namespace=target_namespace ) self.assertIsInstance(actual, Generator) self.assertEqual([expected], list(actual)) actual = DefinitionsMapper.map_port_type_message( "Add", port_type_message, target_namespace ) expected = DefinitionsMapper.build_attr( "Add", qname=build_qname("foobar", "bar"), namespace=target_namespace ) self.assertIsInstance(actual, Generator) self.assertEqual([expected], list(actual)) def test_operation_namespace(self) -> None: self.assertIsNone(DefinitionsMapper.operation_namespace({})) self.assertIsNone(DefinitionsMapper.operation_namespace({"transport": "foo"})) self.assertEqual( "http://schemas.xmlsoap.org/soap/envelope/", DefinitionsMapper.operation_namespace( {"transport": "http://schemas.xmlsoap.org/soap/http"} ), ) def test_attributes(self) -> None: elements = [ None, "string", AnyElement(qname="{foo}bar", attributes={"foo": "bar"}), AnyElement(qname="{foo}bar", attributes={"{foo}bar": "foo"}), AnyElement(qname="{foo}bar", attributes={"{foo}foo": "bar2"}), ] self.assertEqual( {"foo": "bar2", "bar": "foo"}, DefinitionsMapper.attributes(elements) ) tefra-xsdata-cf7afe8/tests/codegen/mappers/test_dict.py000066400000000000000000000103571526437326600234570ustar00rootroot00000000000000import sys from unittest import mock from xsdata.codegen.mappers import DictMapper from xsdata.codegen.models import Restrictions from xsdata.codegen.utils import ClassUtils from xsdata.models.enums import DataType, Tag from xsdata.utils.testing import ( AttrFactory, AttrTypeFactory, ClassFactory, FactoryTestCase, ) class DictMapperTests(FactoryTestCase): @mock.patch.object(ClassUtils, "flatten") @mock.patch.object(DictMapper, "build_class") def test_map(self, mock_build_class, mock_flatten) -> None: data = {"value": 1} root_class = ClassFactory.create() flat_classes = ClassFactory.list(5) iter_flat_classes = iter(flat_classes) mock_build_class.return_value = root_class mock_flatten.return_value = iter_flat_classes actual = DictMapper.map(data, "root", "tests") self.assertEqual(flat_classes, actual) mock_build_class.assert_called_once_with(data, "root") mock_flatten.assert_called_once_with(root_class, "tests/root") def test_build_class(self) -> None: data = {"a": 1, "b": True} actual = DictMapper.build_class(data, "root") expected = ClassFactory.create( tag=Tag.ELEMENT, qname="root", location="", module=None, ns_map={}, attrs=[ AttrFactory.native( DataType.SHORT, tag=Tag.ELEMENT, name="a", index=0, ), AttrFactory.native( DataType.BOOLEAN, tag=Tag.ELEMENT, name="b", index=1, ), ], ) self.assertEqual(expected, actual) def test_build_class_attribute_from_list(self) -> None: target = ClassFactory.create() data = [1, True, 1.1] DictMapper.build_class_attribute(target, "a", data) expected = AttrFactory.create( name="a", tag=Tag.ELEMENT, types=[ AttrTypeFactory.native(DataType.SHORT, tag=Tag.ELEMENT), AttrTypeFactory.native(DataType.BOOLEAN, tag=Tag.ELEMENT), AttrTypeFactory.native(DataType.FLOAT, tag=Tag.ELEMENT), ], ) restrictions = Restrictions(min_occurs=1, max_occurs=sys.maxsize) self.assertEqual(expected, target.attrs[0]) self.assertEqual(restrictions, target.attrs[0].restrictions) def test_build_class_attribute_from_empty_list(self) -> None: target = ClassFactory.create() data = [] DictMapper.build_class_attribute(target, "a", data) expected = AttrFactory.create( name="a", tag=Tag.ELEMENT, types=[ AttrTypeFactory.native(DataType.ANY_SIMPLE_TYPE, tag=Tag.ELEMENT), ], ) restrictions = Restrictions(min_occurs=0, max_occurs=sys.maxsize) self.assertEqual(expected, target.attrs[0]) self.assertEqual(restrictions, target.attrs[0].restrictions) def test_build_class_attribute_from_dict(self) -> None: target = ClassFactory.create() data = {"sub1": 1, "sub2": "value", "sub3": None} DictMapper.build_class_attribute(target, "a", data) expected = AttrFactory.create( name="a", tag=Tag.ELEMENT, types=[AttrTypeFactory.create(qname="a", forward=True)], ) expected_inner = ClassFactory.create( qname="a", tag=Tag.ELEMENT, location="", module=None, ns_map={}, attrs=[ AttrFactory.native(DataType.SHORT, name="sub1"), AttrFactory.native(DataType.STRING, name="sub2"), AttrFactory.native(DataType.STRING, name="sub3"), ], ) self.assertEqual(expected, target.attrs[0]) self.assertEqual(expected_inner, target.inner[0]) self.assertEqual(1, len(target.inner)) self.assertFalse(target.inner[0].attrs[0].restrictions.is_optional) self.assertFalse(target.inner[0].attrs[1].restrictions.is_optional) self.assertTrue(target.inner[0].attrs[2].restrictions.is_optional) tefra-xsdata-cf7afe8/tests/codegen/mappers/test_dtd.py000066400000000000000000000356261526437326600233150ustar00rootroot00000000000000import sys from collections.abc import Iterator from unittest import mock from xsdata.codegen.mappers import DtdMapper from xsdata.codegen.models import Class, Restrictions from xsdata.models.dtd import ( DtdAttributeDefault, DtdAttributeType, DtdContentOccur, DtdContentType, DtdElementType, ) from xsdata.models.enums import DataType, Namespace, Tag from xsdata.utils.testing import ( AttrFactory, AttrTypeFactory, ClassFactory, DtdAttributeFactory, DtdContentFactory, DtdElementFactory, DtdFactory, FactoryTestCase, ) class DtdMapperTests(FactoryTestCase): def test_map(self) -> None: dtd = DtdFactory.root(2, location="tests.dtd") result = DtdMapper.map(dtd) self.assertIsInstance(result, Iterator) for cls in result: self.assertIsInstance(cls, Class) self.assertEqual(cls.location, dtd.location) @mock.patch.object(DtdMapper, "build_attributes") @mock.patch.object(DtdMapper, "build_elements") def test_build_class(self, mock_build_elements, mock_build_attributes) -> None: location = "tests.dtd" element = DtdElementFactory.create( name="root", prefix="ns", ns_map={"ns": "xsdata"} ) actual = DtdMapper.build_class(element, "tests.dtd") expected = ClassFactory.create( qname="{xsdata}root", ns_map=element.ns_map, tag=Tag.ELEMENT, location=location, module=None, ) self.assertEqual(expected, actual) mock_build_attributes.assert_called_once_with(actual, element) mock_build_elements.assert_called_once_with(actual, element) @mock.patch.object(DtdMapper, "build_attribute") def test_build_attributes(self, mock_build_attribute) -> None: attributes = DtdAttributeFactory.list(2) element = DtdElementFactory.create(attributes=attributes) target = ClassFactory.create() DtdMapper.build_attributes(target, element) mock_build_attribute.assert_has_calls( [ mock.call(target, attributes[0]), mock.call(target, attributes[1]), ] ) def test_build_attribute(self) -> None: target = ClassFactory.create() attribute = DtdAttributeFactory.create( name="lang", prefix="xml", default=DtdAttributeDefault.IMPLIED ) DtdMapper.build_attribute(target, attribute) self.assertEqual(1, len(target.attrs)) attr = target.attrs[0] self.assertEqual(0, attr.index) self.assertEqual(attribute.name, attr.name) self.assertEqual(Tag.ATTRIBUTE, attr.tag) self.assertEqual(Namespace.XML.uri, attr.namespace) self.assertEqual(1, len(attr.types)) self.assertEqual(AttrTypeFactory.native(DataType.STRING), attr.types[0]) self.assertEqual(1, attr.restrictions.max_occurs) self.assertEqual(0, attr.restrictions.min_occurs) def test_build_attribute_type_with_enumeration(self) -> None: target = ClassFactory.create() attribute = DtdAttributeFactory.create( type=DtdAttributeType.ENUMERATION, values=["a", "b", "c"] ) actual = DtdMapper.build_attribute_type(target, attribute) self.assertTrue(actual.forward) self.assertEqual(attribute.name, actual.qname) self.assertEqual(1, len(target.inner)) self.assertTrue(target.inner[0].is_enumeration) self.assertEqual(["a", "b", "c"], [x.name for x in target.inner[0].attrs]) self.assertEqual(["a", "b", "c"], [x.default for x in target.inner[0].attrs]) for attr in target.inner[0].attrs: self.assertEqual(Tag.ENUMERATION, attr.tag) self.assertTrue(attr.fixed) self.assertEqual([AttrTypeFactory.native(DataType.STRING)], attr.types) def test_build_attribute_restrictions_with_default_required(self) -> None: attr = AttrFactory.create() DtdMapper.build_attribute_restrictions(attr, DtdAttributeDefault.REQUIRED, "") self.assertEqual(1, attr.restrictions.max_occurs) self.assertEqual(1, attr.restrictions.min_occurs) self.assertFalse(attr.fixed) self.assertIsNone(attr.default) def test_build_attribute_restrictions_with_default_implied(self) -> None: attr = AttrFactory.create() DtdMapper.build_attribute_restrictions(attr, DtdAttributeDefault.IMPLIED, "") self.assertEqual(1, attr.restrictions.max_occurs) self.assertEqual(0, attr.restrictions.min_occurs) self.assertFalse(attr.fixed) self.assertIsNone(attr.default) def test_build_attribute_restrictions_with_default_fixed(self) -> None: attr = AttrFactory.create() DtdMapper.build_attribute_restrictions(attr, DtdAttributeDefault.FIXED, "abc") self.assertEqual(1, attr.restrictions.max_occurs) self.assertEqual(1, attr.restrictions.min_occurs) self.assertTrue(attr.fixed) self.assertEqual("abc", attr.default) def test_build_attribute_restrictions_with_default_none_and_no_value(self) -> None: attr = AttrFactory.create() DtdMapper.build_attribute_restrictions(attr, DtdAttributeDefault.NONE, None) self.assertEqual(1, attr.restrictions.max_occurs) self.assertEqual(0, attr.restrictions.min_occurs) self.assertFalse(attr.fixed) self.assertIsNone(attr.default) def test_build_attribute_restrictions_with_default_none_and_default_value( self, ) -> None: attr = AttrFactory.create() DtdMapper.build_attribute_restrictions(attr, DtdAttributeDefault.NONE, "abc") self.assertEqual(1, attr.restrictions.max_occurs) self.assertEqual(1, attr.restrictions.min_occurs) self.assertFalse(attr.fixed) self.assertEqual("abc", attr.default) def test_build_elements_with_mixed_element_type(self) -> None: target = ClassFactory.create(tag=Tag.ELEMENT) left = DtdContentFactory.create( type=DtdContentType.PCDATA, occur=DtdContentOccur.ONCE ) right = DtdContentFactory.create( name="rad", type=DtdContentType.ELEMENT, occur=DtdContentOccur.ONCE ) element = DtdElementFactory.create( type=DtdElementType.MIXED, content=DtdContentFactory.create( type=DtdContentType.OR, left=left, right=right ), ) DtdMapper.build_elements(target, element) expected = [ AttrFactory.create( tag=Tag.ELEMENT, name="rad", index=1, types=[AttrTypeFactory.create(qname="rad")], ), ] self.assertTrue(target.mixed) self.assertEqual(expected, target.attrs) self.assertEqual(Tag.COMPLEX_TYPE, target.tag) element.content.left = right element.content.right = left target.attrs.clear() target.mixed = False DtdMapper.build_elements(target, element) self.assertTrue(target.mixed) self.assertEqual(expected, target.attrs) self.assertEqual(Tag.COMPLEX_TYPE, target.tag) def test_build_elements_with_mixed_element_type_with_no_content(self) -> None: target = ClassFactory.create(tag=Tag.ELEMENT) element = DtdElementFactory.create( type=DtdElementType.MIXED, content=DtdContentFactory.create( type=DtdContentType.PCDATA, ), ) DtdMapper.build_elements(target, element) expected = [ AttrFactory.create( tag=Tag.EXTENSION, name="value", index=0, types=[AttrTypeFactory.native(DataType.STRING)], ), ] self.assertFalse(target.mixed) self.assertEqual(expected, target.attrs) self.assertEqual(Tag.COMPLEX_TYPE, target.tag) def test_build_elements_with_any_element_type(self) -> None: target = ClassFactory.create() element = DtdElementFactory.create(type=DtdElementType.ANY) DtdMapper.build_elements(target, element) self.assertEqual(0, len(target.attrs)) self.assertEqual(1, len(target.extensions)) self.assertEqual(str(DataType.ANY_TYPE), target.extensions[0].type.qname) self.assertTrue(target.extensions[0].type.native) self.assertFalse(target.mixed) def test_build_elements_with_undefined_element_type(self) -> None: target = ClassFactory.create() element = DtdElementFactory.create(type=DtdElementType.UNDEFINED) DtdMapper.build_elements(target, element) self.assertEqual(0, len(target.attrs)) self.assertEqual(0, len(target.extensions)) self.assertFalse(target.mixed) def test_build_elements_with_empty_element_type(self) -> None: target = ClassFactory.create() element = DtdElementFactory.create(type=DtdElementType.EMPTY) DtdMapper.build_elements(target, element) self.assertEqual(0, len(target.attrs)) self.assertEqual(0, len(target.extensions)) self.assertFalse(target.mixed) def test_build_elements_with_element_element_type(self) -> None: target = ClassFactory.create() element = DtdElementFactory.create( type=DtdElementType.ELEMENT, content=DtdContentFactory.create(name="child") ) DtdMapper.build_elements(target, element) self.assertEqual(1, len(target.attrs)) self.assertEqual(0, len(target.extensions)) self.assertFalse(target.mixed) def test_build_content_with_type_element(self) -> None: content = DtdContentFactory.create( type=DtdContentType.ELEMENT, occur=DtdContentOccur.ONCE ) target = ClassFactory.create() DtdMapper.build_content(target, content, nillable=True) self.assertEqual(1, len(target.attrs)) self.assertEqual(content.name, target.attrs[0].name) self.assertTrue(target.attrs[0].restrictions.nillable) self.assertEqual(1, target.attrs[0].restrictions.max_occurs) self.assertEqual(1, target.attrs[0].restrictions.min_occurs) def test_build_content_type_seq(self) -> None: content = DtdContentFactory.create( type=DtdContentType.SEQ, left=DtdContentFactory.create(type=DtdContentType.ELEMENT), right=DtdContentFactory.create(type=DtdContentType.ELEMENT), ) target = ClassFactory.create() DtdMapper.build_content(target, content, nillable=True) self.assertEqual(2, len(target.attrs)) self.assertTrue(target.attrs[0].restrictions.nillable) self.assertTrue(target.attrs[1].restrictions.nillable) def test_build_content_type_or(self) -> None: content = DtdContentFactory.create( type=DtdContentType.OR, occur=DtdContentOccur.MULT, left=DtdContentFactory.create(type=DtdContentType.ELEMENT), right=DtdContentFactory.create(type=DtdContentType.ELEMENT), ) target = ClassFactory.create() DtdMapper.build_content(target, content, nillable=True) self.assertEqual(2, len(target.attrs)) for attr in target.attrs: self.assertEqual(id(content), attr.restrictions.choice) self.assertEqual(0, attr.restrictions.min_occurs) self.assertEqual(sys.maxsize, attr.restrictions.max_occurs) def test_build_content_type_or_nested(self) -> None: content = DtdContentFactory.create( type=DtdContentType.OR, left=DtdContentFactory.create(type=DtdContentType.ELEMENT), right=DtdContentFactory.create(type=DtdContentType.ELEMENT), ) target = ClassFactory.create() DtdMapper.build_content(target, content, min_occurs=1, choice="abc") self.assertEqual(2, len(target.attrs)) for attr in target.attrs: self.assertEqual("abc", attr.restrictions.choice) self.assertEqual(1, attr.restrictions.min_occurs) def test_build_content_type_pcdata(self) -> None: content = DtdContentFactory.create( type=DtdContentType.PCDATA, ) target = ClassFactory.create() DtdMapper.build_content(target, content) expected = [ AttrFactory.create( tag=Tag.EXTENSION, name="value", index=0, types=[AttrTypeFactory.native(DataType.STRING)], ) ] self.assertEqual(expected, target.attrs) @mock.patch.object(DtdMapper, "build_content") def test_build_content_tree(self, mock_build_content) -> None: center = DtdContentFactory.create() target = ClassFactory.create() DtdMapper.build_content_tree(target, center, nillable=True) center.left = DtdContentFactory.create() center.right = DtdContentFactory.create() DtdMapper.build_content_tree(target, center, nillable=False) mock_build_content.assert_has_calls( [ mock.call(target, center.left, nillable=False), mock.call(target, center.right, nillable=False), ] ) def test_build_restrictions(self) -> None: result = DtdMapper.build_restrictions(DtdContentOccur.ONCE) self.assertEqual(1, result.min_occurs) self.assertEqual(1, result.max_occurs) result = DtdMapper.build_restrictions(DtdContentOccur.OPT) self.assertEqual(0, result.min_occurs) self.assertEqual(1, result.max_occurs) result = DtdMapper.build_restrictions(DtdContentOccur.MULT) self.assertEqual(0, result.min_occurs) self.assertEqual(sys.maxsize, result.max_occurs) result = DtdMapper.build_restrictions(DtdContentOccur.PLUS) self.assertEqual(1, result.min_occurs) self.assertEqual(sys.maxsize, result.max_occurs) result = DtdMapper.build_restrictions(DtdContentOccur.PLUS, nillable=True) self.assertEqual(1, result.min_occurs) self.assertEqual(sys.maxsize, result.max_occurs) self.assertTrue(result.nillable) result = DtdMapper.build_restrictions(DtdContentOccur.PLUS, min_occurs=0) self.assertEqual(0, result.min_occurs) def test_build_element(self) -> None: target = ClassFactory.create() restrictions = Restrictions() names = ["firs", "second", "third"] for name in names: DtdMapper.build_element(target, name, restrictions) self.assertEqual(len(names), len(target.attrs)) for index, attr in enumerate(target.attrs): self.assertEqual(names[index], attr.name) self.assertEqual(index, attr.index) self.assertEqual(Tag.ELEMENT, attr.tag) self.assertEqual(restrictions, attr.restrictions) self.assertIsNot(restrictions, attr.restrictions) self.assertEqual(1, len(attr.types)) self.assertFalse(attr.types[0].native) self.assertEqual(names[index], attr.types[0].qname) tefra-xsdata-cf7afe8/tests/codegen/mappers/test_element.py000066400000000000000000000273521526437326600241700ustar00rootroot00000000000000import sys from unittest import mock from xsdata.codegen.mappers import ElementMapper from xsdata.codegen.models import Restrictions from xsdata.codegen.utils import ClassUtils from xsdata.formats.dataclass.models.generics import AnyElement from xsdata.models.enums import DataType, QNames, Tag from xsdata.utils.testing import ( AttrFactory, AttrTypeFactory, ClassFactory, FactoryTestCase, ) class ElementMapperTests(FactoryTestCase): @mock.patch.object(ClassUtils, "flatten") @mock.patch.object(ElementMapper, "build_class") def test_map(self, mock_build_class, mock_flatten) -> None: element = AnyElement(qname="{xsdata}root") root_class = ClassFactory.create() flat_classes = ClassFactory.list(5) iter_flat_classes = iter(flat_classes) mock_build_class.return_value = root_class mock_flatten.return_value = iter_flat_classes actual = ElementMapper.map(element, "tests") self.assertEqual(flat_classes, actual) mock_build_class.assert_called_once_with(element, "xsdata") mock_flatten.assert_called_once_with(root_class, "tests/root") def test_build_class_simple_type(self) -> None: element = AnyElement( qname="{xsdata}root", attributes={"{foo}bar": "1", "{bar}foo": "2.0"}, text="true", ) actual = ElementMapper.build_class(element, "target") expected = ClassFactory.create( tag=Tag.ELEMENT, qname="{xsdata}root", namespace="xsdata", location="", module=None, ns_map={}, attrs=[ AttrFactory.native( DataType.INT, tag=Tag.ATTRIBUTE, name="bar", namespace="foo", index=0, ), AttrFactory.native( DataType.FLOAT, tag=Tag.ATTRIBUTE, name="foo", namespace="bar", index=1, ), AttrFactory.native( DataType.BOOLEAN, tag=Tag.SIMPLE_TYPE, name="value", namespace=None, index=2, ), ], ) self.assertEqual(expected, actual) for attr in actual.attrs: self.assertEqual(1, attr.restrictions.min_occurs) self.assertEqual(1, attr.restrictions.max_occurs) def test_build_class_complex_type(self) -> None: element = AnyElement( qname="{xsdata}root", children=[ AnyElement(qname="{xsdata}child", text="primitive"), AnyElement( qname="{inner}child", attributes={"{foo}bar": "1", "{bar}foo": "2"} ), ], ) actual = ElementMapper.build_class(element, "target") expected = ClassFactory.create( tag=Tag.ELEMENT, qname="{xsdata}root", namespace="xsdata", location="", module=None, ns_map={}, attrs=[ AttrFactory.native( DataType.STRING, tag=Tag.ELEMENT, name="child", namespace="xsdata", index=0, ), AttrFactory.element( name="child", namespace="inner", types=[AttrTypeFactory.create(qname="{target}child", forward=True)], index=1, ), ], ) self.assertEqual(1, len(actual.inner)) actual.inner.clear() self.assertEqual(expected, actual) def test_build_class_with_sequences(self) -> None: element = AnyElement( qname="root", children=[ AnyElement(qname="a"), AnyElement(qname="b"), AnyElement(qname="a"), AnyElement(qname="b"), AnyElement(qname="c"), AnyElement(qname="d"), AnyElement(qname="e"), AnyElement(qname="d"), ], ) actual = ElementMapper.build_class(element, "target") expected = ClassFactory.create( tag=Tag.ELEMENT, qname="root", namespace="", location="", module=None, ns_map={}, attrs=[ AttrFactory.native( DataType.ANY_SIMPLE_TYPE, tag=Tag.ELEMENT, name="a", namespace="", index=0, restrictions=Restrictions( min_occurs=1, max_occurs=sys.maxsize, path=[("s", 1, 1, sys.maxsize)], ), ), AttrFactory.native( DataType.ANY_SIMPLE_TYPE, tag=Tag.ELEMENT, name="b", namespace="", index=1, restrictions=Restrictions( min_occurs=1, max_occurs=sys.maxsize, path=[("s", 1, 1, sys.maxsize)], ), ), AttrFactory.native( DataType.ANY_SIMPLE_TYPE, tag=Tag.ELEMENT, name="c", namespace="", index=2, restrictions=Restrictions(min_occurs=1, max_occurs=1), ), AttrFactory.native( DataType.ANY_SIMPLE_TYPE, tag=Tag.ELEMENT, name="d", namespace="", index=3, restrictions=Restrictions( min_occurs=1, max_occurs=sys.maxsize, path=[("s", 2, 1, sys.maxsize)], ), ), AttrFactory.native( DataType.ANY_SIMPLE_TYPE, tag=Tag.ELEMENT, name="e", namespace="", index=4, restrictions=Restrictions( min_occurs=1, max_occurs=sys.maxsize, path=[("s", 2, 1, sys.maxsize)], ), ), ], ) self.assertEqual(expected, actual) def test_build_class_mixed_content(self) -> None: element = AnyElement( qname="{xsdata}root", children=[ AnyElement(qname="{xsdata}child", text="primitive"), AnyElement(qname="something", text="foo", tail="bar"), ], ) actual = ElementMapper.build_class(element, None) expected = ClassFactory.create( tag=Tag.ELEMENT, qname="{xsdata}root", namespace="xsdata", location="", module=None, mixed=True, ns_map={}, attrs=[ AttrFactory.native( DataType.STRING, name="child", namespace="xsdata", index=0, restrictions=Restrictions(min_occurs=0, max_occurs=1), ), AttrFactory.native( DataType.STRING, namespace="", name="something", index=1, restrictions=Restrictions(min_occurs=0, max_occurs=1), ), ], ) self.assertEqual(expected, actual) element = AnyElement( qname="{xsdata}root", text="foo", children=[ AnyElement(qname="{xsdata}child", text="primitive"), ], ) actual = ElementMapper.build_class(element, None) self.assertTrue(actual.mixed) def test_build_class_nillable(self) -> None: element = AnyElement(qname="{xsdata}root", attributes={QNames.XSI_NIL: "1"}) target = ElementMapper.build_class(element, None) self.assertTrue(target.nillable) element.attributes[QNames.XSI_NIL] = " true " target = ElementMapper.build_class(element, None) self.assertTrue(target.nillable) def test_build_class_ignore_invalid(self) -> None: element = AnyElement( qname="{xsdata}root", children=[ AnyElement(text="primitive"), "", ], ) actual = ElementMapper.build_class(element, None) self.assertEqual(0, len(actual.attrs)) def test_build_attr_type(self) -> None: actual = ElementMapper.build_attr_type(QNames.XSI_TYPE, "") self.assertEqual(str(DataType.QNAME), actual.qname) self.assertTrue(actual.native) actual = ElementMapper.build_attr_type("name", "foo") self.assertEqual(str(DataType.STRING), actual.qname) self.assertTrue(actual.native) actual = ElementMapper.build_attr_type("name", "") self.assertEqual(str(DataType.ANY_SIMPLE_TYPE), actual.qname) self.assertTrue(actual.native) actual = ElementMapper.build_attr_type("name", None) self.assertEqual(str(DataType.ANY_SIMPLE_TYPE), actual.qname) self.assertTrue(actual.native) actual = ElementMapper.build_attr_type("name", 1) self.assertEqual(str(DataType.SHORT), actual.qname) self.assertTrue(actual.native) actual = ElementMapper.build_attr_type("name", "1.9") self.assertEqual(str(DataType.FLOAT), actual.qname) self.assertTrue(actual.native) def test_add_attribute(self) -> None: target = ClassFactory.elements(2) attr = target.attrs[0].clone() attr.index += 1 ElementMapper.add_attribute(target, attr) self.assertEqual(2, len(target.attrs)) self.assertEqual(sys.maxsize, target.attrs[0].restrictions.max_occurs) self.assertEqual(1, len(target.attrs[0].types)) attr = target.attrs[1].clone() ElementMapper.add_attribute(target, attr) attr = AttrFactory.create() ElementMapper.add_attribute(target, attr) self.assertEqual(3, len(target.attrs)) def test_select_namespace(self) -> None: self.assertEqual("a", ElementMapper.select_namespace("a", "a", Tag.ELEMENT)) self.assertEqual("b", ElementMapper.select_namespace("b", "a", Tag.ELEMENT)) self.assertEqual("", ElementMapper.select_namespace(None, "a", Tag.ELEMENT)) self.assertEqual("a", ElementMapper.select_namespace("a", "a", Tag.ATTRIBUTE)) self.assertEqual("b", ElementMapper.select_namespace("b", "a", Tag.ATTRIBUTE)) self.assertIsNone(ElementMapper.select_namespace(None, "a", Tag.ATTRIBUTE)) def test_sequential_groups(self) -> None: a = AnyElement(qname="a") b = AnyElement(qname="b") c = AnyElement(qname="c") d = AnyElement(qname="d") e = AnyElement(qname="e") p = AnyElement(children=[a, b, c, d]) actual = ElementMapper.sequential_groups(p) self.assertEqual([], actual) p = AnyElement(children=[a, b, a]) actual = ElementMapper.sequential_groups(p) self.assertEqual([[0, 1, 2]], actual) p = AnyElement(children=[a, b, a, c]) actual = ElementMapper.sequential_groups(p) self.assertEqual([[0, 1, 2]], actual) p = AnyElement(children=[a, b, a, c, d, e, d]) actual = ElementMapper.sequential_groups(p) self.assertEqual([[0, 1, 2], [4, 5, 6]], actual) tefra-xsdata-cf7afe8/tests/codegen/mappers/test_schema.py000066400000000000000000000413371526437326600237760ustar00rootroot00000000000000from collections.abc import Iterator from types import GeneratorType from unittest import mock from xsdata.codegen.mappers import SchemaMapper from xsdata.codegen.models import Class, Restrictions from xsdata.models.enums import DataType, FormType, Tag from xsdata.models.xsd import ( Alternative, Annotation, Attribute, AttributeGroup, Choice, ComplexContent, ComplexType, Element, Enumeration, Extension, Group, Override, Redefine, Restriction, Schema, Sequence, SimpleContent, SimpleType, ) from xsdata.utils.namespaces import build_qname from xsdata.utils.testing import ( AttrFactory, AttrTypeFactory, ClassFactory, ExtensionFactory, FactoryTestCase, ) class SchemaMapperTests(FactoryTestCase): @mock.patch.object(SchemaMapper, "build_class") @mock.patch.object(SchemaMapper, "root_elements") def test_map(self, mock_root_elements, mock_build_class) -> None: simple_type = ComplexType() complex_type = ComplexType() schema = Schema(target_namespace="fooNS", location="foo.xsd") mock_build_class.side_effect = ClassFactory.list(3) mock_root_elements.return_value = [ (Tag.SCHEMA, Group), (Tag.OVERRIDE, simple_type), (Tag.REDEFINE, complex_type), ] actual = SchemaMapper.map(schema) self.assertEqual(3, len(actual)) self.assertIsInstance(actual[0], Class) mock_root_elements.assert_called_once_with(schema) def test_root_elements(self) -> None: override = Override() redefine = Redefine() redefine.annotation = Annotation() redefine.complex_types.append(ComplexType()) override.annotation = Annotation() override.groups.append(Group()) override.simple_types.append(SimpleType()) schema = Schema() schema.simple_types.append(SimpleType()) schema.attribute_groups.append(AttributeGroup()) schema.groups.append(Group()) schema.attributes.append(Attribute()) schema.complex_types.append(ComplexType()) schema.elements.append(Element()) schema.redefines.append(redefine) schema.overrides.append(override) iterator = SchemaMapper.root_elements(schema) expected = [ ("Override", override.simple_types[0]), ("Override", override.groups[0]), ("Redefine", redefine.complex_types[0]), ("Schema", schema.simple_types[0]), ("Schema", schema.complex_types[0]), ("Schema", schema.groups[0]), ("Schema", schema.attribute_groups[0]), ("Schema", schema.elements[0]), ("Schema", schema.attributes[0]), ] self.assertEqual(expected, list(iterator)) def test_element_namespace(self) -> None: target_ns = "foobar" element = Element(ref="foo:something") element.ns_map["foo"] = "bar" self.assertEqual("bar", SchemaMapper.element_namespace(element, target_ns)) element = Element(form=FormType.QUALIFIED) self.assertEqual("foobar", SchemaMapper.element_namespace(element, target_ns)) element = Element() self.assertEqual("", SchemaMapper.element_namespace(element, target_ns)) element.target_namespace = "tns" self.assertEqual("tns", SchemaMapper.element_namespace(element, target_ns)) attribute = Attribute() self.assertIsNone(SchemaMapper.element_namespace(attribute, target_ns)) attribute.target_namespace = "tns" self.assertEqual("tns", SchemaMapper.element_namespace(attribute, target_ns)) element = Element(ref="something") self.assertEqual("foobar", SchemaMapper.element_namespace(element, target_ns)) element.ns_map["foo"] = "foobar" self.assertEqual("", SchemaMapper.element_namespace(element, target_ns)) element.ns_map[None] = "foobar" self.assertEqual("foobar", SchemaMapper.element_namespace(element, target_ns)) @mock.patch.object(SchemaMapper, "element_namespace") @mock.patch.object(SchemaMapper, "build_class_attributes") @mock.patch.object(SchemaMapper, "build_class_extensions") @mock.patch.object(Element, "substitutions", new_callable=mock.PropertyMock) @mock.patch.object(Element, "is_abstract", new_callable=mock.PropertyMock) @mock.patch.object(Element, "is_nillable", new_callable=mock.PropertyMock) @mock.patch.object(Element, "display_help", new_callable=mock.PropertyMock) @mock.patch.object(Element, "real_name", new_callable=mock.PropertyMock) def test_build_class( self, mock_real_name, mock_display_help, mock_is_nillable, mock_is_abstract, mock_substitutions, mock_build_class_extensions, mock_build_class_attributes, mock_element_namespace, ) -> None: mock_real_name.return_value = "name" mock_display_help.return_value = "sos" mock_is_abstract.return_value = True mock_is_nillable.return_value = True mock_substitutions.return_value = ["foo", "sm:bar"] mock_element_namespace.return_value = "foo:name" element = Element() element.ns_map["sm"] = "sm_ns" result = SchemaMapper.build_class(element, "container", "tests", "target_ns") mock_build_class_attributes.assert_called_once_with(element, result) mock_build_class_extensions.assert_called_once_with(element, result) mock_element_namespace.assert_called_once_with(element, "target_ns") expected = ClassFactory.create( qname=build_qname("target_ns", "name"), tag=Tag.ELEMENT, help="sos", abstract=True, nillable=True, namespace="foo:name", ns_map=element.ns_map, module=None, location="tests", substitutions=[ build_qname("target_ns", "foo"), build_qname("sm_ns", "bar"), ], container="container", ) self.assertEqual(expected, result) @mock.patch.object(SchemaMapper, "children_extensions") def test_build_class_extensions(self, mock_children_extensions) -> None: bar_type = AttrTypeFactory.create(qname="bar") foo_type = AttrTypeFactory.create(qname="foo") bar = ExtensionFactory.create(bar_type, tag=Tag.RESTRICTION) double = ExtensionFactory.create(bar_type, tag=Tag.RESTRICTION) foo = ExtensionFactory.create(foo_type, tag=Tag.EXTENSION) mock_children_extensions.return_value = [bar, double, foo] self_ext = ExtensionFactory.reference( qname="{xsdata}something", tag=Tag.ELEMENT, restrictions=Restrictions(min_occurs=1, max_occurs=1), ) item = ClassFactory.create() element = Element(type="something") SchemaMapper.build_class_extensions(element, item) self.assertEqual(3, len(item.extensions)) self.assertCountEqual([bar, self_ext, foo], item.extensions) def test_element_children(self) -> None: sequence_one = Sequence(elements=[Element(), Element()]) sequence_one.index = 1 sequence_two = Sequence(max_occurs=2, elements=[Element(), Element()]) sequence_two.index = 2 restriction = Restriction( enumerations=[Enumeration(value=x) for x in "abc"], sequence=sequence_two ) complex_type = ComplexType( attributes=[Attribute(), Attribute()], sequence=sequence_one, simple_content=SimpleContent(restriction=Restriction()), complex_content=ComplexContent(restriction=restriction), ) restrictions = Restrictions.from_element(complex_type) children = SchemaMapper.element_children(complex_type, restrictions) expected = [ (sequence_two.elements[0], Restrictions.from_element(sequence_two)), (sequence_two.elements[1], Restrictions.from_element(sequence_two)), (restriction.enumerations[0], Restrictions.from_element(restriction)), (restriction.enumerations[1], Restrictions.from_element(restriction)), (restriction.enumerations[2], Restrictions.from_element(restriction)), (sequence_one.elements[0], Restrictions.from_element(sequence_one)), (sequence_one.elements[1], Restrictions.from_element(sequence_one)), (complex_type.attributes[0], Restrictions.from_element(complex_type)), (complex_type.attributes[1], Restrictions.from_element(complex_type)), ] self.assertIsInstance(children, GeneratorType) self.assertEqual(expected, list(children)) def test_element_children_with_parents_restrictions(self) -> None: choice = Choice(elements=[Element(name="elem1")]) complex_type = ComplexType( sequence=Sequence(choices=[choice], min_occurs=0, max_occurs=3) ) complex_type.sequence.index = 2 parent_restrictions = Restrictions.from_element(complex_type) children = SchemaMapper.element_children(complex_type, parent_restrictions) _child, restrictions = next(children) expected = Restrictions( path=[("s", id(complex_type.sequence), 0, 3), ("c", id(choice), 1, 1)] ) self.assertEqual(expected, restrictions) def test_children_extensions(self) -> None: complex_type = ComplexType( attributes=[Attribute() for _ in range(2)], simple_content=SimpleContent(restriction=Restriction(base="bk:b")), complex_content=ComplexContent(extension=Extension(base="bk:c")), ) complex_type.simple_content.restriction.index = 4 complex_type.complex_content.extension.index = 7 item = ClassFactory.create(ns_map={"bk": "book"}) children = SchemaMapper.children_extensions(complex_type, item) expected = [ ExtensionFactory.create( AttrTypeFactory.create(qname=build_qname("book", "b")), tag=Tag.RESTRICTION, ), ExtensionFactory.create( AttrTypeFactory.create(qname=build_qname("book", "c")), tag=Tag.EXTENSION, ), ] self.assertIsInstance(children, GeneratorType) self.assertEqual(expected, list(children)) @mock.patch.object(SchemaMapper, "build_attr_types") @mock.patch.object(SchemaMapper, "element_namespace") @mock.patch.object(Attribute, "get_restrictions") @mock.patch.object(Attribute, "is_fixed", new_callable=mock.PropertyMock) @mock.patch.object(Attribute, "default_value", new_callable=mock.PropertyMock) @mock.patch.object(Attribute, "prefix", new_callable=mock.PropertyMock) @mock.patch.object(Attribute, "display_help", new_callable=mock.PropertyMock) @mock.patch.object(Attribute, "real_name", new_callable=mock.PropertyMock) def test_build_class_attribute( self, mock_real_name, mock_display_help, mock_prefix, mock_default_value, mock_is_fixed, mock_get_restrictions, mock_element_namespace, mock_build_attr_types, ) -> None: item = ClassFactory.create(ns_map={"bar": "foo"}) mock_build_attr_types.return_value = AttrTypeFactory.list(1, qname="int") mock_real_name.return_value = item.name mock_display_help.return_value = "sos" mock_prefix.return_value = "com" mock_default_value.return_value = "default" mock_is_fixed.return_value = True mock_element_namespace.return_value = "http://something/common" mock_get_restrictions.return_value = {"min_occurs": 1, "max_occurs": 1} attribute = Attribute(default="false") attribute.index = 66 attribute.ns_map["foo"] = "bar" SchemaMapper.build_class_attribute(item, attribute, Restrictions()) expected = AttrFactory.create( name=mock_real_name.return_value, types=mock_build_attr_types.return_value, tag=Tag.ATTRIBUTE, namespace=mock_element_namespace.return_value, help=mock_display_help.return_value, default=mock_default_value.return_value, fixed=mock_is_fixed.return_value, index=66, restrictions=Restrictions(min_occurs=1, max_occurs=1), ) self.assertEqual(expected, item.attrs[0]) self.assertEqual({"bar": "foo", "foo": "bar"}, item.ns_map) mock_build_attr_types.assert_called_once_with(item, attribute) mock_element_namespace.assert_called_once_with(attribute, item.target_namespace) @mock.patch.object(Attribute, "attr_types", new_callable=mock.PropertyMock) @mock.patch.object(SchemaMapper, "build_inner_classes") def test_build_attr_types(self, mock_build_inner_classes, mock_attr_types) -> None: mock_attr_types.return_value = ["xs:integer", "xs:string"] mock_build_inner_classes.return_value = [] item = ClassFactory.create() attribute = Attribute(default="false") actual = SchemaMapper.build_attr_types(item, attribute) expected = [ AttrTypeFactory.native(DataType.INTEGER), AttrTypeFactory.native(DataType.STRING), ] self.assertEqual(expected, actual) @mock.patch.object(Attribute, "attr_types", new_callable=mock.PropertyMock) @mock.patch.object(SchemaMapper, "build_inner_classes") def test_build_attr_types_when_obj_has_inner_class( self, mock_build_inner_classes, mock_attr_types ) -> None: inner_class = ClassFactory.create(qname="foo") mock_attr_types.return_value = ["xs:integer", "xs:string"] mock_build_inner_classes.return_value = [inner_class] item = ClassFactory.create() attribute = Attribute(default="false") actual = SchemaMapper.build_attr_types(item, attribute) expected = [ AttrTypeFactory.native(DataType.INTEGER), AttrTypeFactory.native(DataType.STRING), AttrTypeFactory.create(qname=inner_class.qname, forward=True), ] self.assertEqual(expected, actual) self.assertEqual([inner_class], item.inner) @mock.patch.object(Attribute, "default_type", new_callable=mock.PropertyMock) @mock.patch.object(Attribute, "attr_types", new_callable=mock.PropertyMock) @mock.patch.object(SchemaMapper, "build_inner_classes") def test_build_attr_types_when_obj_has_no_types( self, mock_build_inner_classes, mock_attr_types, mock_default_type ) -> None: mock_attr_types.return_value = "" mock_build_inner_classes.return_value = [] mock_default_type.return_value = "xs:string" item = ClassFactory.create() attribute = Attribute(default="false", name="attr") actual = SchemaMapper.build_attr_types(item, attribute) self.assertEqual(1, len(actual)) self.assertEqual(AttrTypeFactory.native(DataType.STRING), actual[0]) @mock.patch.object(SchemaMapper, "build_class") def test_build_inner_classes(self, mock_build_class) -> None: inner_classes = ClassFactory.list(2) mock_build_class.side_effect = inner_classes simple_type = SimpleType() complex_type = ComplexType() enumeration = SimpleType( restriction=Restriction(enumerations=[Enumeration(value="a")]) ) element = Element( alternatives=[ Alternative(complex_type=complex_type, id="a"), Alternative(simple_type=simple_type, id="b"), Alternative(simple_type=enumeration, id="c"), ] ) result = SchemaMapper.build_inner_classes(element, "module", "target_ns") self.assertIsInstance(result, Iterator) self.assertEqual(inner_classes, list(result)) self.assertEqual("a", complex_type.name) self.assertEqual("c", enumeration.name) mock_build_class.assert_has_calls( [ mock.call(complex_type, Tag.ALTERNATIVE, "module", "target_ns"), mock.call(enumeration, Tag.ALTERNATIVE, "module", "target_ns"), ] ) @mock.patch.object(SchemaMapper, "build_class") def test_build_inner_classes_with_enumeration(self, mock_build_class) -> None: inner = ClassFactory.enumeration(2) mock_build_class.return_value = inner enumeration = SimpleType( restriction=Restriction(enumerations=[Enumeration(value="a")]) ) result = SchemaMapper.build_inner_classes(enumeration, "module", "target_ns") self.assertIsInstance(result, Iterator) self.assertEqual([inner], list(result)) self.assertIsNone(enumeration.name) mock_build_class.assert_called_once_with( enumeration, Tag.SIMPLE_TYPE, "module", "target_ns" ) tefra-xsdata-cf7afe8/tests/codegen/models/000077500000000000000000000000001526437326600207315ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/codegen/models/__init__.py000066400000000000000000000000001526437326600230300ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/codegen/models/test_attr.py000066400000000000000000000156661526437326600233320ustar00rootroot00000000000000import sys from xsdata.codegen.models import Restrictions from xsdata.models.enums import DataType, Namespace, Tag from xsdata.utils.testing import AttrFactory, AttrTypeFactory, FactoryTestCase class AttrTests(FactoryTestCase): def test__post__init__(self) -> None: attr = AttrFactory.create(name="$") self.assertEqual("$", attr.local_name) self.assertEqual("DOLLAR SIGN", attr.name) attr = AttrFactory.create(local_name="$", name="dollar") self.assertEqual("$", attr.local_name) self.assertEqual("dollar", attr.name) def test__eq__(self) -> None: attr = AttrFactory.element() clone = attr.clone() self.assertIsNot(attr, clone) self.assertEqual(attr, clone) attr.default = "foo" self.assertEqual(attr, clone) attr.restrictions.length = 10 self.assertEqual(attr, clone) attr.index = -1 self.assertEqual(attr, clone) attr.namespace = __file__ self.assertNotEqual(attr, clone) def test_can_be_restricted(self) -> None: self.assertFalse(AttrFactory.create(tag=Tag.ATTRIBUTE).can_be_restricted()) self.assertFalse(AttrFactory.create(tag=Tag.EXTENSION).can_be_restricted()) self.assertFalse(AttrFactory.create(tag=Tag.RESTRICTION).can_be_restricted()) self.assertTrue(AttrFactory.create(tag=Tag.ELEMENT).can_be_restricted()) def test_property_key(self) -> None: attr = AttrFactory.attribute(name="a", namespace="b") self.assertEqual("Attribute.b.a", attr.key) def test_property_qname(self) -> None: attr = AttrFactory.attribute(name="a", namespace="b") self.assertEqual("{b}a", attr.qname) def test_property_is_attribute(self) -> None: self.assertTrue(AttrFactory.attribute().is_attribute) self.assertTrue(AttrFactory.any_attribute().is_attribute) self.assertFalse(AttrFactory.element().is_attribute) def test_property_is_enumeration(self) -> None: self.assertTrue(AttrFactory.enumeration().is_enumeration) self.assertFalse(AttrFactory.element().is_enumeration) def test_property_is_factory(self) -> None: self.assertTrue(AttrFactory.any_attribute().is_factory) element = AttrFactory.element() self.assertFalse(element.is_factory) element.restrictions.max_occurs = 2 self.assertTrue(element.is_factory) def test_property_is_forward_ref(self) -> None: attr = AttrFactory.create() self.assertFalse(attr.is_forward_ref) attr.types.append(AttrTypeFactory.create("foo", circular=True)) self.assertTrue(attr.is_forward_ref) attr.types.clear() self.assertFalse(attr.is_forward_ref) attr.types.append(AttrTypeFactory.create("foo", forward=True)) self.assertTrue(attr.is_forward_ref) def test_property_is_circular_ref(self) -> None: attr = AttrFactory.create() self.assertFalse(attr.is_circular_ref) attr.types.append(AttrTypeFactory.create("foo", forward=True)) self.assertFalse(attr.is_circular_ref) self.assertFalse(attr.is_circular_ref) attr.types.append(AttrTypeFactory.create("foo", circular=True)) self.assertTrue(attr.is_circular_ref) def test_property_is_group(self) -> None: self.assertTrue(AttrFactory.group().is_group) self.assertTrue(AttrFactory.attribute_group().is_group) self.assertFalse(AttrFactory.element().is_group) def test_property_is_list(self) -> None: attr = AttrFactory.create(restrictions=Restrictions(max_occurs=2)) self.assertTrue(attr.is_list) attr.restrictions.max_occurs = 1 self.assertFalse(attr.is_list) def test_property_is_prohibited(self) -> None: attr = AttrFactory.create(restrictions=Restrictions(max_occurs=0)) self.assertTrue(attr.is_prohibited) attr.restrictions.max_occurs = 1 self.assertFalse(attr.is_list) def test_property_is_optional(self) -> None: attr = AttrFactory.create(restrictions=Restrictions(min_occurs=0)) self.assertTrue(attr.is_optional) attr.restrictions.min_occurs = 1 self.assertFalse(attr.is_optional) def test_property_is_suffix(self) -> None: attr = AttrFactory.create() self.assertFalse(attr.is_suffix) attr.index = sys.maxsize self.assertTrue(attr.is_suffix) def test_property_is_wild_attr(self) -> None: attr = AttrFactory.create() self.assertFalse(attr.is_wildcard) attr = AttrFactory.any() self.assertTrue(attr.is_wildcard) def test_property_is_xsi_type(self) -> None: attr = AttrFactory.create() self.assertFalse(attr.is_xsi_type) attr.namespace = Namespace.XSI.uri self.assertFalse(attr.is_xsi_type) attr.name = "type" self.assertTrue(attr.is_xsi_type) def test_property_is_nameless(self) -> None: self.assertFalse(AttrFactory.create(tag=Tag.ELEMENT).is_nameless) self.assertFalse(AttrFactory.create(tag=Tag.ATTRIBUTE).is_nameless) self.assertTrue(AttrFactory.create(tag=Tag.ANY).is_nameless) def test_property_is_any_type(self) -> None: attr = AttrFactory.create( types=[ AttrTypeFactory.create(qname="foo"), AttrTypeFactory.native(DataType.FLOAT), ] ) self.assertFalse(attr.is_any_type) attr.types.append(AttrTypeFactory.native(DataType.ANY_SIMPLE_TYPE)) self.assertTrue(attr.is_any_type) def test_property_native_types(self) -> None: attr = AttrFactory.create( types=[ AttrTypeFactory.create(qname="foo"), AttrTypeFactory.native(DataType.INT), AttrTypeFactory.native(DataType.SHORT), AttrTypeFactory.native(DataType.INTEGER), AttrTypeFactory.native(DataType.FLOAT), ] ) self.assertCountEqual([float, int], attr.native_types) def test_property_user_types(self) -> None: attr = AttrFactory.create( types=[ AttrTypeFactory.create(qname="foo"), AttrTypeFactory.native(DataType.INT), AttrTypeFactory.native(DataType.SHORT), AttrTypeFactory.create(qname="bar"), ] ) self.assertCountEqual([attr.types[0], attr.types[-1]], list(attr.user_types)) def test_property_xml_type(self) -> None: attr = AttrFactory.create(tag=Tag.ELEMENT) self.assertEqual("Element", attr.xml_type) attr = AttrFactory.create(tag=Tag.ATTRIBUTE) self.assertEqual("Attribute", attr.xml_type) attr = AttrFactory.create(tag=Tag.ANY_ATTRIBUTE) self.assertEqual("Attributes", attr.xml_type) attr = AttrFactory.create(tag=Tag.ANY) self.assertEqual("Wildcard", attr.xml_type) attr = AttrFactory.create(tag=Tag.RESTRICTION) self.assertIsNone(attr.xml_type) tefra-xsdata-cf7afe8/tests/codegen/models/test_attr_type.py000066400000000000000000000012171526437326600243560ustar00rootroot00000000000000from xsdata.utils.testing import AttrTypeFactory, FactoryTestCase class AttrTypeTests(FactoryTestCase): def test_property_is_dependency(self) -> None: attr_type = AttrTypeFactory.create(forward=True, native=True, circular=True) self.assertFalse(attr_type.is_dependency(False)) attr_type.forward = False self.assertFalse(attr_type.is_dependency(False)) attr_type.native = False self.assertFalse(attr_type.is_dependency(False)) # Allow circular self.assertTrue(attr_type.is_dependency(True)) attr_type.circular = False self.assertTrue(attr_type.is_dependency(False)) tefra-xsdata-cf7afe8/tests/codegen/models/test_class.py000066400000000000000000000157531526437326600234620ustar00rootroot00000000000000import sys from xsdata.codegen.exceptions import CodegenError from xsdata.models.enums import DataType, Namespace, Tag from xsdata.utils.namespaces import build_qname from xsdata.utils.testing import ( AttrFactory, AttrTypeFactory, ClassFactory, ExtensionFactory, FactoryTestCase, ) class ClassTests(FactoryTestCase): def test_dependencies(self) -> None: obj = ClassFactory.create( attrs=[ AttrFactory.create(types=[AttrTypeFactory.native(DataType.DECIMAL)]), AttrFactory.create( types=[ AttrTypeFactory.create( qname=build_qname(Namespace.XS.uri, "annotated"), forward=True, ), AttrTypeFactory.create( qname="circular", circular=True, ), ], choices=[ AttrFactory.create( name="x", types=[ AttrTypeFactory.create(qname="choiceAttr"), AttrTypeFactory.native(DataType.STRING), ], ), AttrFactory.create( name="x", types=[ AttrTypeFactory.create(qname="choiceAttrTwo"), AttrTypeFactory.create(qname="choiceAttrEnum"), ], ), ], ), AttrFactory.create( types=[ AttrTypeFactory.create( qname=build_qname(Namespace.XS.uri, "openAttrs") ), AttrTypeFactory.create( qname=build_qname(Namespace.XS.uri, "localAttribute") ), ] ), ], extensions=[ ExtensionFactory.reference(build_qname(Namespace.XS.uri, "foobar")), ExtensionFactory.reference(build_qname(Namespace.XS.uri, "foobar")), ], inner=[ ClassFactory.create( attrs=AttrFactory.list( 2, types=[ AttrTypeFactory.create(qname="{xsdata}foo"), AttrTypeFactory.create( qname="{xsdata}circular", circular=True ), ], ) ) ], ) expected = [ "choiceAttr", "choiceAttrTwo", "choiceAttrEnum", "{http://www.w3.org/2001/XMLSchema}openAttrs", "{http://www.w3.org/2001/XMLSchema}localAttribute", "{http://www.w3.org/2001/XMLSchema}foobar", "{xsdata}foo", ] self.assertCountEqual(expected, list(obj.dependencies())) self.assertIn("circular", list(obj.dependencies(allow_circular=True))) self.assertIn("{xsdata}circular", list(obj.dependencies(allow_circular=True))) def test_property_has_suffix_attr(self) -> None: obj = ClassFactory.create() self.assertFalse(obj.has_suffix_attr) obj.attrs.append(AttrFactory.create()) obj.attrs.append(AttrFactory.create()) self.assertFalse(obj.has_suffix_attr) obj.attrs[1].index = sys.maxsize self.assertTrue(obj.has_suffix_attr) def test_property_is_element(self) -> None: obj = ClassFactory.create(tag=Tag.ELEMENT) self.assertTrue(obj.is_element) obj = ClassFactory.create(tag=Tag.SIMPLE_TYPE) self.assertFalse(obj.is_element) def test_property_is_enumeration(self) -> None: obj = ClassFactory.enumeration(2) self.assertTrue(obj.is_enumeration) obj.attrs.append(AttrFactory.element()) self.assertFalse(obj.is_enumeration) obj.attrs.clear() self.assertFalse(obj.is_enumeration) def test_property_is_restricted(self) -> None: obj = ClassFactory.create() ext = ExtensionFactory.create(tag=Tag.EXTENSION) obj.extensions.append(ext) self.assertFalse(obj.is_restricted) ext.tag = Tag.RESTRICTION self.assertTrue(obj.is_restricted) def test_property_is_group(self) -> None: self.assertTrue(ClassFactory.create(tag=Tag.GROUP).is_group) self.assertTrue(ClassFactory.create(tag=Tag.ATTRIBUTE_GROUP).is_group) self.assertFalse(ClassFactory.create(tag=Tag.ELEMENT).is_group) def test_property_ref(self) -> None: obj = ClassFactory.create() self.assertEqual(id(obj), obj.ref) def test_property_references(self) -> None: ext_1 = ExtensionFactory.create(AttrTypeFactory.create(reference=1)) ext_2 = ExtensionFactory.create(AttrTypeFactory.create(reference=2)) obj = ClassFactory.elements(3) obj.extensions.append(ext_1) obj.extensions.append(ext_2) obj.attrs[0].types[0].reference = 3 obj.attrs[1].choices.append(AttrFactory.create()) obj.attrs[1].choices[0].types[0].reference = 4 obj.attrs[2].types[0].reference = 5 obj.inner.append(ClassFactory.elements(2)) obj.inner[0].attrs[1].types[0].reference = 6 self.assertEqual(list(range(1, 7)), list(obj.references)) def test_property_target_module(self) -> None: obj = ClassFactory.create(module=None, package=None) with self.assertRaises(CodegenError): obj.target_module obj.module = "bar" self.assertEqual("bar", obj.target_module) obj.package = "foo" self.assertEqual("foo.bar", obj.target_module) def test_property_is_mixed(self) -> None: obj = ClassFactory.create() self.assertFalse(obj.is_mixed) obj.attrs.append(AttrFactory.any(mixed=True)) self.assertTrue(obj.is_mixed) obj = ClassFactory.create(mixed=True) self.assertTrue(obj.is_mixed) def test_has_forward_ref(self) -> None: forward_type = AttrTypeFactory.create("foo", forward=True) circular_type = AttrTypeFactory.create("foo", circular=True) obj = ClassFactory.elements(1) self.assertFalse(obj.has_forward_ref()) obj.attrs[0].types.append(forward_type) self.assertTrue(obj.has_forward_ref()) obj.attrs[0].types.pop() obj.attrs[0].choices.append(AttrFactory.create()) obj.attrs[0].choices.append(AttrFactory.create(types=[circular_type])) self.assertTrue(obj.has_forward_ref()) obj.attrs.clear() self.assertFalse(obj.has_forward_ref()) inner = ClassFactory.elements(1) inner.attrs[0].types.append(circular_type) obj.inner.append(inner) self.assertTrue(obj.has_forward_ref()) tefra-xsdata-cf7afe8/tests/codegen/models/test_codegen.py000066400000000000000000000015161526437326600237510ustar00rootroot00000000000000import unittest from dataclasses import dataclass from xsdata.codegen.models import CodegenModel @dataclass class Foo(CodegenModel): bar: list["Bar"] @dataclass class Bar(CodegenModel): foo: str class CodegenModelTests(unittest.TestCase): def test_clone(self) -> None: obj = Foo(bar=[Foo.Bar("one"), Foo.Bar("two")]) actual = obj.clone() self.assertIsNot(obj, actual) self.assertIsNot(obj.bar, actual.bar) self.assertIsNot(obj.bar[0], actual.bar[0]) self.assertIsNot(obj.bar[1], actual.bar[1]) def test_swap(self) -> None: obj = Foo(bar=[Foo.Bar("one")]) src = Foo(bar=[Foo.Bar("two")]) obj.swap(src) self.assertEqual(src.bar, obj.bar) self.assertIsNot(src.bar, obj.bar) self.assertIsNot(src.bar[0], obj.bar[0]) tefra-xsdata-cf7afe8/tests/codegen/models/test_restrictions.py000066400000000000000000000104741526437326600251000ustar00rootroot00000000000000import sys from unittest import TestCase from xsdata.codegen.models import Restrictions class RestrictionsTests(TestCase): def setUp(self) -> None: self.restrictions = Restrictions( min_occurs=1, max_occurs=1, min_exclusive="1.1", min_inclusive="1", min_length=1, max_exclusive="1", max_inclusive="1.1", max_length=10, total_digits=333, fraction_digits=2, length=5, white_space="collapse", pattern=r"[A-Z]", explicit_timezone="+1", nillable=True, path=[("s", 0, 1, 1)], process_contents="skip", ) def test_property_is_list(self) -> None: restrictions = Restrictions() self.assertFalse(restrictions.is_list) restrictions.max_occurs = 1 self.assertFalse(restrictions.is_list) restrictions.max_occurs = 2 self.assertTrue(restrictions.is_list) def test_property_is_prohibited(self) -> None: self.assertFalse(Restrictions().is_prohibited) self.assertTrue(Restrictions(max_occurs=0).is_prohibited) def test_merge(self) -> None: source = Restrictions(min_length=2, max_length=10, format="base16") target = Restrictions(min_length=1, pattern=r"[A-Z]") target.merge(source) self.assertEqual(2, target.min_length) self.assertEqual(10, target.max_length) self.assertEqual(r"[A-Z]", target.pattern) self.assertEqual("base16", target.format) def test_merge_ignore_nillable(self) -> None: parent = Restrictions(nillable=True) child = Restrictions() child.merge(parent) self.assertIsNone(child.nillable) child.nillable = False child.merge(parent) self.assertFalse(child.nillable) def test_merge_occurs(self) -> None: a = Restrictions() b = Restrictions() a.merge(b) self.assertIsNone(a.min_occurs) self.assertIsNone(a.max_occurs) b.min_occurs = 0 b.max_occurs = 1 a.merge(b) self.assertEqual(0, a.min_occurs) self.assertEqual(1, a.max_occurs) def test_asdict(self) -> None: expected = { "explicit_timezone": "+1", "fraction_digits": 2, "length": 5, "max_exclusive": "1", "max_inclusive": "1.1", "max_length": 10, "min_exclusive": "1.1", "min_inclusive": "1", "min_length": 1, "nillable": True, "pattern": "[A-Z]", "process_contents": "skip", "total_digits": 333, "white_space": "collapse", } self.assertEqual(expected, self.restrictions.asdict()) self.restrictions.nillable = None del expected["nillable"] expected["required"] = True self.assertEqual(expected, self.restrictions.asdict()) def test_asdict_with_types(self) -> None: expected = { "explicit_timezone": "+1", "fraction_digits": 2, "length": 5, "max_exclusive": 1.0, # str -> float "max_inclusive": 1.1, # str -> float "max_length": 10, "min_exclusive": 1.1, # str -> float "min_inclusive": 1.0, # str -> float "min_length": 1, "nillable": True, "pattern": "[A-Z]", "process_contents": "skip", "total_digits": 333, "white_space": "collapse", } self.assertEqual(expected, self.restrictions.asdict(types=[float])) def test_asdict_with_implied_types(self) -> None: restrictions = Restrictions(min_occurs=1, max_occurs=4) self.assertEqual({"max_occurs": 4, "min_occurs": 1}, restrictions.asdict()) restrictions.min_occurs = 0 self.assertEqual({"max_occurs": 4}, restrictions.asdict()) restrictions.max_occurs = sys.maxsize self.assertEqual({}, restrictions.asdict()) def test_asdict_with_process_contents(self) -> None: restrictions = Restrictions(process_contents="skip") self.assertEqual({"process_contents": "skip"}, restrictions.asdict()) restrictions.process_contents = "strict" self.assertEqual({}, restrictions.asdict()) tefra-xsdata-cf7afe8/tests/codegen/parsers/000077500000000000000000000000001526437326600211255ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/codegen/parsers/__init__.py000066400000000000000000000000001526437326600232240ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/codegen/parsers/test_definitions.py000066400000000000000000000024321526437326600250520ustar00rootroot00000000000000from unittest import TestCase from tests import fixtures_dir from xsdata.codegen.parsers import DefinitionsParser from xsdata.models.wsdl import Definitions, Import class DefinitionsParserTests(TestCase): def setUp(self) -> None: self.parser = DefinitionsParser() super().setUp() def test_complete(self) -> None: path = fixtures_dir.joinpath("calculator/services.wsdl").resolve() parser = DefinitionsParser(location="here.wsdl") definitions = parser.from_path(path, Definitions) self.assertIsInstance(definitions, Definitions) self.assertEqual(1, len(definitions.services)) self.assertEqual(2, len(definitions.bindings)) self.assertEqual(1, len(definitions.port_types)) self.assertEqual(1, len(definitions.types.schemas)) self.assertEqual(8, len(definitions.messages)) self.assertEqual(parser.location, definitions.bindings[0].location) def test_end_import(self) -> None: parser = DefinitionsParser(location="foo/bar.wsdl") imp = Import(location="../hello/foo.wsdl") parser.end_import(imp) self.assertEqual("hello/foo.wsdl", imp.location) parser.location = None parser.end_import(imp) self.assertEqual("hello/foo.wsdl", imp.location) tefra-xsdata-cf7afe8/tests/codegen/parsers/test_dtd.py000066400000000000000000000146061526437326600233200ustar00rootroot00000000000000from dataclasses import asdict from unittest import TestCase, mock from tests import fixtures_dir from xsdata.codegen.parsers import DtdParser from xsdata.exceptions import ParserError from xsdata.models.dtd import ( DtdAttributeDefault, DtdAttributeType, DtdContentOccur, DtdContentType, DtdElementType, ) from xsdata.models.enums import Namespace class DtdParserTests(TestCase): @classmethod def parse(cls, file_name: str): file_path = fixtures_dir.joinpath(file_name) return DtdParser.parse(file_path.read_bytes(), str(file_path.resolve())) @mock.patch("lxml.etree.DTD") def test_parse_requires_lxml(self, mock_lxml) -> None: mock_lxml.side_effect = ImportError() with self.assertRaises(ParserError) as cm: DtdParser.parse(b"", "/file/path/def.dtd") self.assertEqual("DtdParser requires lxml to run.", str(cm.exception)) def test_build_element(self) -> None: dtd = self.parse("dtd/complete_example.dtd") self.assertEqual(8, len(dtd.elements)) element = dtd.elements[1] self.assertEqual(4, len(element.attributes)) self.assertIsNotNone(element.content) self.assertEqual(4, len(element.ns_map)) element.attributes = [] element.content = None element.ns_map = {} expected = { "attributes": [], "content": None, "name": "Post", "ns_map": {}, "prefix": None, "type": DtdElementType.ELEMENT, } self.assertEqual(expected, asdict(dtd.elements[1])) def test_build_content(self) -> None: dtd = self.parse("dtd/complete_example.dtd") post = next(el for el in dtd.elements if el.name == "Post") actual = asdict(post.content) expected = { "left": { "left": { "left": None, "name": "Origin", "occur": DtdContentOccur.ONCE, "right": None, "type": DtdContentType.ELEMENT, }, "name": None, "occur": DtdContentOccur.MULT, "right": { "left": None, "name": "Source", "occur": DtdContentOccur.ONCE, "right": None, "type": DtdContentType.ELEMENT, }, "type": DtdContentType.OR, }, "name": None, "occur": DtdContentOccur.ONCE, "right": { "left": { "left": None, "name": "Title", "occur": DtdContentOccur.ONCE, "right": None, "type": DtdContentType.ELEMENT, }, "name": None, "occur": DtdContentOccur.ONCE, "right": { "left": { "left": None, "name": "Body", "occur": DtdContentOccur.ONCE, "right": None, "type": DtdContentType.ELEMENT, }, "name": None, "occur": DtdContentOccur.ONCE, "right": { "left": None, "name": "Tags", "occur": DtdContentOccur.ONCE, "right": None, "type": DtdContentType.ELEMENT, }, "type": DtdContentType.SEQ, }, "type": DtdContentType.SEQ, }, "type": DtdContentType.SEQ, } self.assertEqual(expected, actual) def test_build_attribute(self) -> None: dtd = self.parse("dtd/complete_example.dtd") post = next(el for el in dtd.elements if el.name == "Post") self.assertEqual(4, len(post.attributes)) # The order doesn't match the definition... actual = {attr.name: asdict(attr) for attr in post.attributes} expected = { "author": { "default": DtdAttributeDefault.REQUIRED, "default_value": None, "name": "author", "prefix": None, "type": DtdAttributeType.CDATA, "values": [], }, "created_at": { "default": DtdAttributeDefault.REQUIRED, "default_value": None, "name": "created_at", "prefix": None, "type": DtdAttributeType.CDATA, "values": [], }, "lang": { "default": DtdAttributeDefault.NONE, "default_value": "en", "name": "lang", "prefix": "xml", "type": DtdAttributeType.NMTOKEN, "values": [], }, "status": { "default": DtdAttributeDefault.NONE, "default_value": "draft", "name": "status", "prefix": None, "type": DtdAttributeType.ENUMERATION, "values": ["draft", "published"], }, } self.assertEqual(expected, actual) def test_build_ns_map(self) -> None: dtd = self.parse("dtd/complete_example.dtd") expected = {ns.prefix: ns.uri for ns in Namespace.common()} for element in dtd.elements: self.assertEqual(expected, element.ns_map) def test_with_prefix_namespace(self) -> None: dtd = self.parse("dtd/prefix_namespace.dtd") self.assertEqual(2, len(dtd.elements)) self.assertEqual("ns", dtd.elements[0].prefix) self.assertEqual("ns", dtd.elements[1].prefix) self.assertIn("ns", dtd.elements[0].ns_map) self.assertEqual("http://www.example.com/", dtd.elements[0].ns_map["ns"]) self.assertNotIn("ns", dtd.elements[1].ns_map) def test_with_default_namespace(self) -> None: dtd = self.parse("dtd/default_namespace.dtd") self.assertEqual(2, len(dtd.elements)) self.assertIsNone(dtd.elements[0].prefix) self.assertIsNone(dtd.elements[1].prefix) self.assertIn(None, dtd.elements[0].ns_map) self.assertEqual("http://www.example.com/", dtd.elements[0].ns_map[None]) self.assertNotIn(None, dtd.elements[1].ns_map) tefra-xsdata-cf7afe8/tests/codegen/parsers/test_schema.py000066400000000000000000000367551526437326600240160ustar00rootroot00000000000000import sys from pathlib import Path from unittest import TestCase, mock from xsdata.codegen.parsers.schema import SchemaParser from xsdata.models.enums import FormType, Mode, Namespace from xsdata.models.xsd import ( All, Any, AnyAttribute, Attribute, AttributeGroup, ComplexContent, ComplexType, DefaultOpenContent, Element, Extension, Group, Import, Include, OpenContent, Override, Redefine, Restriction, Schema, Sequence, SimpleContent, ) class SchemaParserTests(TestCase): def setUp(self) -> None: self.parser = SchemaParser() super().setUp() def test_complete(self) -> None: xsd = """ country """ schema = self.parser.from_string(xsd, Schema) expected_namespaces = { "xlink": "http://www.w3.org/1999/xlink", "xml": "http://www.w3.org/XML/1998/namespace", "xs": "http://www.w3.org/2001/XMLSchema", "xsi": "http://www.w3.org/2001/XMLSchema-instance", } self.assertIsInstance(schema, Schema) self.assertEqual(expected_namespaces, schema.simple_types[0].ns_map) self.assertEqual(2, schema.simple_types[0].index) self.assertEqual(3, schema.simple_types[0].annotations[0].index) self.assertEqual( 4, schema.simple_types[0].annotations[0].documentations[0].index ) def test_start_schema(self) -> None: self.parser.start_schema({}) self.assertIsNone(self.parser.element_form) self.assertIsNone(self.parser.attribute_form) self.assertIsNone(self.parser.default_attributes) attrs = { "elementFormDefault": "qualified", "attributeFormDefault": "unqualified", "defaultAttributes": "tns:attr", } self.parser.start_schema(attrs) self.assertEqual("qualified", self.parser.element_form) self.assertEqual("unqualified", self.parser.attribute_form) self.assertEqual("tns:attr", self.parser.default_attributes) def test_set_schema_forms_default(self) -> None: schema = Schema() schema.elements.append(Element()) schema.elements.append(Element()) schema.attributes.append(Element()) schema.attributes.append(Element()) self.parser.set_schema_forms(schema) self.assertEqual(FormType.UNQUALIFIED, schema.element_form_default) self.assertEqual(FormType.UNQUALIFIED, schema.attribute_form_default) for child_element in schema.elements: self.assertEqual(FormType.QUALIFIED, child_element.form) for child_attribute in schema.attributes: self.assertEqual(FormType.QUALIFIED, child_attribute.form) def test_set_schema_forms(self) -> None: schema = Schema() schema.elements.append(Element()) schema.elements.append(Element()) schema.attributes.append(Element()) schema.attributes.append(Element()) self.parser.element_form = "unqualified" self.parser.attribute_form = "qualified" self.parser.set_schema_forms(schema) self.assertEqual(FormType.UNQUALIFIED, schema.element_form_default) self.assertEqual(FormType.QUALIFIED, schema.attribute_form_default) for child_element in schema.elements: self.assertEqual(FormType.QUALIFIED, child_element.form) for child_attribute in schema.attributes: self.assertEqual(FormType.QUALIFIED, child_attribute.form) def test_set_schema_namespaces(self) -> None: schema = Schema() self.parser.set_schema_namespaces(schema) self.assertIsNone(schema.target_namespace) self.parser.target_namespace = "bar" self.parser.set_schema_namespaces(schema) self.assertEqual("bar", schema.target_namespace) schema.target_namespace = "foo" self.parser.set_schema_namespaces(schema) self.assertEqual("foo", schema.target_namespace) def test_set_namespace_map(self) -> None: schema = Schema() ns_map = {} self.parser.set_namespace_map(schema, ns_map) expected = { "xlink": "http://www.w3.org/1999/xlink", "xml": "http://www.w3.org/XML/1998/namespace", "xs": "http://www.w3.org/2001/XMLSchema", "xsi": "http://www.w3.org/2001/XMLSchema-instance", } self.assertEqual(expected, schema.ns_map) self.parser.set_namespace_map(schema, None) self.assertEqual(expected, schema.ns_map) ns_map = {"foo": "bar", "not": "http://www.w3.org/2001/XMLSchema"} schema = Schema() expected = { "foo": "bar", "xlink": "http://www.w3.org/1999/xlink", "xml": "http://www.w3.org/XML/1998/namespace", "not": "http://www.w3.org/2001/XMLSchema", "xsi": "http://www.w3.org/2001/XMLSchema-instance", } self.parser.set_namespace_map(schema, ns_map) self.assertEqual(expected, schema.ns_map) foo = [] self.parser.set_namespace_map(foo, ns_map) self.assertFalse(hasattr(foo, "ns_map")) def test_add_default_imports(self) -> None: schema = Schema() schema.imports.append(Import(namespace="foo")) self.parser.add_default_imports(schema) self.assertEqual(1, len(schema.imports)) xsi = Namespace.XSI.uri schema.ns_map["foo"] = xsi self.parser.add_default_imports(schema) self.assertEqual(2, len(schema.imports)) self.assertEqual(Import(namespace=xsi), schema.imports[0]) @mock.patch.object(SchemaParser, "resolve_local_path") @mock.patch.object(SchemaParser, "resolve_path") def test_resolve_schemas_locations( self, mock_resolve_path, mock_resolve_local_path ) -> None: schema = Schema() self.parser.resolve_schemas_locations(schema) self.parser.location = Path.cwd() mock_resolve_path.side_effect = lambda x: Path.cwd().joinpath(x) mock_resolve_local_path.side_effect = lambda x, y: Path.cwd().joinpath(x) schema.overrides.append(Override(schema_location="o1")) schema.overrides.append(Override(schema_location="o2")) schema.redefines.append(Redefine(schema_location="r1")) schema.redefines.append(Redefine(schema_location="r2")) schema.includes.append(Include(schema_location="i1")) schema.includes.append(Include(schema_location="i2")) schema.imports.append(Import(schema_location="i3", namespace="ns_i3")) schema.imports.append(Import(schema_location="i4", namespace="ns_i4")) self.parser.resolve_schemas_locations(schema) mock_resolve_path.assert_has_calls( [ mock.call("o1"), mock.call("o2"), mock.call("r1"), mock.call("r2"), mock.call("i1"), mock.call("i2"), ] ) mock_resolve_local_path.assert_has_calls( [mock.call("i3", "ns_i3"), mock.call("i4", "ns_i4")] ) for sub in schema.included(): self.assertEqual(Path.cwd().joinpath(sub.location), sub.location) def test_resolve_path(self) -> None: self.assertIsNone(self.parser.resolve_path("foo")) iam = Path(__file__) self.parser.location = iam.as_uri() self.assertIsNone(self.parser.resolve_path("")) self.assertIsNone(self.parser.resolve_path(None)) actual = self.parser.resolve_path(iam.name) self.assertEqual(iam.as_uri(), actual) def test_resolve_local_path(self) -> None: self.assertIsNone(self.parser.resolve_local_path("foo", "bar")) self.assertIsNone(self.parser.resolve_local_path("foo", None)) self.assertIsNone(self.parser.resolve_local_path(None, None)) self.assertIsNone(self.parser.resolve_local_path(None, "bar")) self.assertEqual( Namespace.XSI.location, self.parser.resolve_local_path(None, Namespace.XSI.uri), ) iam = Path(__file__) self.parser.location = iam.as_uri() self.assertEqual(iam.as_uri(), self.parser.resolve_local_path(iam.name, None)) self.assertEqual( "http://something", self.parser.resolve_local_path("http://something", Namespace.XSI.uri), ) self.assertEqual( iam.parent.parent.joinpath("xsi.xsd").as_uri(), self.parser.resolve_local_path("../xsi.xsd", Namespace.XSI.uri), ) def test_end_attribute(self) -> None: attribute = Attribute() self.parser.end_attribute(attribute) self.assertIsNone(attribute.form) self.parser.attribute_form = "qualified" self.parser.end_attribute(attribute) self.assertEqual(FormType.QUALIFIED, attribute.form) obj = Element() self.parser.end_attribute(obj) self.assertIsNone(obj.form) def test_end_complex_type(self) -> None: complex_type = ComplexType() not_complex_type = Element() self.parser.end_complex_type(not_complex_type) self.parser.end_complex_type(complex_type) self.assertEqual(0, len(complex_type.attribute_groups)) self.assertIsNone(complex_type.open_content) self.parser.default_attributes = "tns:attrs" self.parser.end_complex_type(complex_type) expected = AttributeGroup(ref="tns:attrs") self.assertEqual([expected], complex_type.attribute_groups) self.assertIsNone(complex_type.open_content) default_open_content = DefaultOpenContent(applies_to_empty=False) self.parser.default_open_content = default_open_content self.parser.end_complex_type(complex_type) self.assertIsNone(complex_type.open_content) default_open_content.applies_to_empty = True complex_type.simple_content = SimpleContent() self.parser.end_complex_type(complex_type) self.assertIsNone(complex_type.open_content) complex_type.simple_content = None complex_type.complex_content = ComplexContent() self.parser.end_complex_type(complex_type) self.assertIsNone(complex_type.open_content) complex_type.complex_content = None complex_type.open_content = OpenContent() self.parser.end_complex_type(complex_type) self.assertIsNot(default_open_content, complex_type.open_content) complex_type.open_content = None default_open_content.applies_to_empty = False complex_type.sequence = Sequence(elements=[Element()]) self.parser.end_complex_type(complex_type) self.assertIs(default_open_content, complex_type.open_content) def test_end_default_open_content(self) -> None: default_open_content = DefaultOpenContent() default_open_content.any = Any() self.parser.end_default_open_content(default_open_content) self.assertEqual(default_open_content, self.parser.default_open_content) self.assertEqual(0, default_open_content.any.index) default_open_content.mode = Mode.SUFFIX self.parser.end_default_open_content(default_open_content) self.assertEqual(sys.maxsize, default_open_content.any.index) obj = ComplexType() self.parser.end_default_open_content(obj) self.assertIsNone(obj.open_content) def test_end_Element(self) -> None: obj = Element() self.parser.end_element(obj) self.assertIsNone(obj.form) self.parser.element_form = "qualified" self.parser.end_element(obj) self.assertEqual(FormType.QUALIFIED, obj.form) obj = Attribute() self.parser.end_element(obj) self.assertIsNone(obj.form) def test_end_extension(self) -> None: extension = Extension() not_extension = Element() self.parser.end_extension(not_extension) self.parser.end_extension(extension) default_open_content = DefaultOpenContent() self.parser.default_open_content = default_open_content self.parser.end_extension(extension) self.assertIs(default_open_content, extension.open_content) open_content = OpenContent() extension.open_content = open_content self.parser.end_extension(extension) self.assertIs(open_content, extension.open_content) obj = ComplexType() self.parser.end_extension(obj) self.assertIsNone(obj.open_content) def test_end_open_content(self) -> None: open_content = OpenContent() open_content.any = Any() self.parser.end_open_content(open_content) self.assertEqual(0, open_content.any.index) open_content.mode = Mode.SUFFIX self.parser.end_open_content(open_content) self.assertEqual(sys.maxsize, open_content.any.index) obj = All(any=Any()) self.parser.end_open_content(obj) self.assertEqual(0, obj.any.index) def test_end_restriction(self) -> None: restriction = Restriction() not_restriction = Element() self.parser.end_restriction(not_restriction) self.parser.end_restriction(restriction) default_open_content = DefaultOpenContent() self.parser.default_open_content = default_open_content self.parser.end_restriction(restriction) self.assertIs(default_open_content, restriction.open_content) open_content = OpenContent() restriction.open_content = open_content self.parser.end_restriction(restriction) self.assertIs(open_content, restriction.open_content) obj = ComplexType() self.parser.end_open_content(obj) self.assertIsNone(obj.open_content) @mock.patch.object(SchemaParser, "resolve_schemas_locations") @mock.patch.object(SchemaParser, "add_default_imports") @mock.patch.object(SchemaParser, "set_schema_namespaces") @mock.patch.object(SchemaParser, "set_schema_forms") def test_end_schema( self, mock_set_schema_forms, mock_set_schema_namespaces, mock_add_default_imports, mock_resolve_schemas_locations, ) -> None: schema = Schema() schema.elements.append(Element()) schema.elements.append(Element()) schema.elements.append(Element()) self.parser.end_schema(schema) for el in schema.elements: self.assertEqual(1, el.min_occurs) self.assertEqual(1, el.max_occurs) self.parser.end_schema(ComplexType()) mock_set_schema_forms.assert_called_once_with(schema) mock_set_schema_namespaces.assert_called_once_with(schema) mock_add_default_imports.assert_called_once_with(schema) mock_resolve_schemas_locations.assert_called_once_with(schema) def test_has_elements(self) -> None: restriction = Restriction() restriction.sequence = Sequence() restriction.sequence.any_attribute = AnyAttribute() self.assertFalse(self.parser.has_elements(restriction)) restriction.sequence.elements.append(Element()) self.assertTrue(self.parser.has_elements(restriction)) restriction.sequence.elements.clear() restriction.sequence.groups.append(Group()) self.assertTrue(self.parser.has_elements(restriction)) tefra-xsdata-cf7afe8/tests/codegen/test_container.py000066400000000000000000000142421526437326600230440ustar00rootroot00000000000000from unittest import mock from xsdata.codegen.container import ClassContainer, Steps from xsdata.codegen.models import Class, Status from xsdata.models.config import GeneratorConfig from xsdata.models.enums import Tag from xsdata.utils.testing import AttrFactory, ClassFactory, FactoryTestCase class ClassContainerTests(FactoryTestCase): def setUp(self) -> None: super().setUp() self.container = ClassContainer(config=GeneratorConfig()) def test_initialize(self) -> None: classes = [ ClassFactory.create(qname="{xsdata}foo", tag=Tag.ELEMENT), ClassFactory.create(qname="{xsdata}foo", tag=Tag.COMPLEX_TYPE), ClassFactory.create(qname="{xsdata}foobar", tag=Tag.COMPLEX_TYPE), ] config = GeneratorConfig() container = ClassContainer(config) container.extend(classes) self.assertEqual(2, len(container.data)) self.assertEqual(3, len(list(container))) self.assertEqual(classes, list(container)) actual = { step: [processor.__class__.__name__ for processor in processors] for step, processors in container.processors.items() } expected = { 10: [ "FlattenAttributeGroups", ], 20: [ "CalculateAttributePaths", "FlattenClassExtensions", "SanitizeEnumerationClass", "UpdateAttributesEffectiveChoice", "UnnestInnerClasses", "AddAttributeSubstitutions", "ProcessAttributeTypes", "MergeAttributes", "ProcessMixedContentClass", ], 30: [ "ResetAttributeSequences", "RenameDuplicateAttributes", ], 40: ["ValidateAttributesOverrides"], 50: ["VacuumInnerClasses"], 60: [ "DetectCircularReferences", "CreateCompoundFields", "CreateWrapperFields", "DisambiguateChoices", "SanitizeAttributesDefaultValue", "ResetAttributeSequenceNumbers", ], } self.assertEqual(expected, actual) @mock.patch.object(ClassContainer, "process_class") def test_find(self, mock_process_class) -> None: def process_class(x: Class, step: int) -> None: x.status = Status.FLATTENED class_a = ClassFactory.create(qname="a") class_b = ClassFactory.create(qname="b", status=Status.FLATTENED) class_c = ClassFactory.enumeration(2, qname="b", status=Status.FLATTENING) mock_process_class.side_effect = process_class self.container.extend([class_a, class_b, class_c]) self.container.step = Steps.FLATTEN self.assertIsNone(self.container.find("nope")) self.assertEqual(class_a, self.container.find(class_a.qname)) self.assertEqual(class_b, self.container.find(class_b.qname)) self.assertEqual( class_c, self.container.find(class_b.qname, lambda x: x.is_enumeration) ) mock_process_class.assert_called_once_with(class_a, Steps.FLATTEN) @mock.patch.object(ClassContainer, "process_class") def test_find_inner(self, mock_process_class) -> None: obj = ClassFactory.create() first = ClassFactory.create(qname="{a}a") second = ClassFactory.create(qname="{a}b", status=Status.FLATTENED) obj.inner.extend((first, second)) def process_class(x: Class, step: int) -> None: x.status = Status.FLATTENED mock_process_class.side_effect = process_class self.container.step = Steps.FLATTEN self.assertEqual(first, self.container.find_inner(obj, "{a}a")) self.assertEqual(second, self.container.find_inner(obj, "{a}b")) mock_process_class.assert_called_once_with(first, Steps.FLATTEN) def test_first(self) -> None: obj = ClassFactory.create() self.container.add(obj) self.assertEqual(obj, self.container.first(obj.qname)) with self.assertRaises(KeyError): self.container.first("aa") def test_process_class(self) -> None: target = ClassFactory.create( inner=[ClassFactory.elements(2), ClassFactory.elements(1)] ) for inner in target.inner: inner.parent = target self.container.add(target) self.container.process() self.assertEqual(Status.FINALIZED, target.status) self.assertEqual(Status.FINALIZED, target.inner[0].status) self.assertEqual(Status.FINALIZED, target.inner[1].status) def test_process_classes(self) -> None: target = ClassFactory.create() inner = ClassFactory.enumeration(2, qname="enumeration") target.inner.append(inner) inner.parent = target target.attrs.append(AttrFactory.reference("enumeration", forward=True)) self.container.add(target) self.container.process_classes(Steps.FLATTEN) self.assertEqual(2, len(list(self.container))) for obj in self.container: self.assertEqual(Status.FLATTENED, obj.status) def test_filter_classes(self) -> None: complex_type = ClassFactory.elements(1) enum_1 = ClassFactory.enumeration(2) complex_type.attrs[0].types[0].reference = enum_1.ref simple_type = ClassFactory.simple_type() enum_2 = ClassFactory.enumeration(3) simple_type.attrs[0].types[0].reference = enum_2.ref element = ClassFactory.create(tag=Tag.ELEMENT, abstract=True) container = ClassContainer(config=GeneratorConfig()) container.extend([complex_type, enum_1, enum_2, simple_type, element]) expected = [complex_type, enum_1, element] container.filter_classes() self.assertEqual(expected, list(container)) def test_remove_groups(self) -> None: classes = [ ClassFactory.create(tag=Tag.ATTRIBUTE_GROUP), ClassFactory.create(tag=Tag.GROUP), ClassFactory.create(tag=Tag.ELEMENT), ] self.container.extend(classes) self.container.remove_groups() self.assertEqual(1, len(list(self.container))) tefra-xsdata-cf7afe8/tests/codegen/test_resolver.py000066400000000000000000000214061526437326600227230ustar00rootroot00000000000000from unittest import mock from xsdata.codegen.exceptions import CodegenError from xsdata.codegen.models import Class from xsdata.codegen.resolver import DependenciesResolver from xsdata.models.enums import DataType from xsdata.utils.namespaces import build_qname from xsdata.utils.testing import ( AttrFactory, AttrTypeFactory, ClassFactory, ExtensionFactory, FactoryTestCase, PackageFactory, ) class DependenciesResolverTest(FactoryTestCase): def setUp(self) -> None: super().setUp() self.resolver = DependenciesResolver({}) @mock.patch.object(DependenciesResolver, "resolve_imports") @mock.patch.object(DependenciesResolver, "create_class_list") @mock.patch.object(DependenciesResolver, "create_class_map") def test_process( self, mock_create_class_map, create_class_list, mock_resolve_imports ) -> None: classes = ClassFactory.list(3) mock_create_class_map.return_value = {"b": classes[0]} create_class_list.return_value = classes[::-1] self.resolver.imports.append(PackageFactory.create(name="foo", source="bar")) self.resolver.aliases = {"a": "a"} self.resolver.process(classes) self.assertEqual([], self.resolver.imports) self.assertEqual({}, self.resolver.aliases) self.assertEqual(mock_create_class_map.return_value, self.resolver.class_map) self.assertEqual(create_class_list.return_value, self.resolver.class_list) mock_resolve_imports.assert_called_once_with() def test_sorted_imports(self) -> None: packages = [ PackageFactory.create(name=x, alias=None, source="foo") for x in "cab" ] self.resolver.imports = packages result = self.resolver.sorted_imports() self.assertIsNot(packages, result) self.assertEqual(packages[1], result[0]) self.assertEqual(packages[2], result[1]) self.assertEqual(packages[0], result[2]) @mock.patch.object(DependenciesResolver, "apply_aliases") def test_sorted_classes(self, mock_apply_aliases) -> None: mock_apply_aliases.side_effect = lambda x: x self.resolver.class_list = ["a", "b", "c", "d"] self.resolver.class_map = {x: ClassFactory.create(qname=x) for x in "ca"} result = self.resolver.sorted_classes() expected = [self.resolver.class_map[x] for x in "ac"] self.assertEqual(expected, result) mock_apply_aliases.assert_has_calls([mock.call(x) for x in expected]) def test_apply_aliases(self) -> None: self.resolver.aliases = { build_qname("xsdata", "d"): "IamD", build_qname("xsdata", "a"): "IamA", } type_a = AttrTypeFactory.create(qname="{xsdata}a") type_b = AttrTypeFactory.create(qname="{xsdata}b") type_c = AttrTypeFactory.create(qname="{xsdata}c") type_d = AttrTypeFactory.create(qname="{xsdata}d") obj = ClassFactory.create( qname="a", attrs=[ AttrFactory.create(name="a", types=[type_a]), AttrFactory.create(name="b", types=[type_b]), AttrFactory.create(name="c", types=[type_a, type_d]), ], inner=[ ClassFactory.create( qname="b", attrs=[ AttrFactory.create(name="c", types=[type_c]), AttrFactory.create(name="d", types=[type_d]), AttrFactory.create( name="compound", types=[AttrTypeFactory.native(DataType.ANY_TYPE)], choices=[ AttrFactory.create(name="a", types=[type_a, type_d]), ], ), ], ) ], extensions=[ExtensionFactory.create(type_a)], ) self.resolver.apply_aliases(obj) self.assertEqual(3, len(obj.attrs)) self.assertEqual(1, len(obj.attrs[0].types)) self.assertEqual(1, len(obj.attrs[1].types)) self.assertEqual(2, len(obj.attrs[2].types)) self.assertEqual("IamA", obj.attrs[0].types[0].alias) self.assertIsNone(obj.attrs[1].types[0].alias) self.assertEqual("IamA", obj.attrs[2].types[0].alias) self.assertEqual("IamD", obj.attrs[2].types[1].alias) self.assertEqual("IamA", obj.extensions[0].type.alias) self.assertEqual(1, len(obj.inner)) self.assertEqual(3, len(obj.inner[0].attrs)) self.assertEqual(1, len(obj.inner[0].attrs[0].types)) self.assertEqual(1, len(obj.inner[0].attrs[1].types)) self.assertEqual("IamA", obj.inner[0].attrs[2].choices[0].types[0].alias) self.assertEqual("IamD", obj.inner[0].attrs[2].choices[0].types[1].alias) self.assertIsNone(obj.inner[0].attrs[0].types[0].alias) self.assertEqual("IamD", obj.inner[0].attrs[1].types[0].alias) @mock.patch.object(DependenciesResolver, "set_aliases") @mock.patch.object(DependenciesResolver, "resolve_conflicts") @mock.patch.object(DependenciesResolver, "get_class_module") @mock.patch.object(DependenciesResolver, "import_classes") def test_resolve_imports( self, mock_import_classes, mock_get_class_module, mock_resolve_conflicts, mock_set_aliases, ) -> None: class_life = ClassFactory.create(qname="life") import_names = [ "foo_1", # cool "bar", # cool "{another}foo1", # another foo "{thug}life", # life class exists add alias "{common}type", # type class doesn't exist add just the name ] self.resolver.class_map = {class_life.qname: class_life} mock_import_classes.return_value = import_names mock_get_class_module.side_effect = [ "first", "second", "third", "forth", "fifth", ] self.resolver.resolve_imports() mock_resolve_conflicts.assert_called_once_with( self.resolver.imports, {class_life.slug} ) mock_set_aliases.assert_called_once_with() def test_resolve_conflicts(self) -> None: imports = [ PackageFactory.create(qname="{a}a", source="models.aa"), PackageFactory.create(qname="{b}a", source="models.ba"), PackageFactory.create(qname="{c}root", source="models.common"), PackageFactory.create(qname="{c}penalty", source="models.common"), ] protected = {"penalty"} self.resolver.resolve_conflicts(imports, protected) self.assertEqual("aa:a", imports[0].alias) self.assertEqual("ba:a", imports[1].alias) self.assertEqual(None, imports[2].alias) self.assertEqual("common:penalty", imports[3].alias) def test_set_aliases(self) -> None: self.resolver.imports = [ PackageFactory.create(qname="{a}a", alias="aa"), PackageFactory.create(qname="{b}a", alias="ba"), PackageFactory.create(qname="{c}a"), ] self.resolver.set_aliases() self.assertEqual({"{a}a": "aa", "{b}a": "ba"}, self.resolver.aliases) def test_get_class_module(self) -> None: class_a = ClassFactory.create() self.resolver.registry[class_a.qname] = "foo.bar" self.assertEqual("foo.bar", self.resolver.get_class_module(class_a.qname)) with self.assertRaises(CodegenError): self.resolver.get_class_module("nope") def test_import_classes(self) -> None: self.resolver.class_list = list("abcdefg") self.resolver.class_map = {x: x for x in "bdg"} self.assertEqual(["a", "c", "e", "f"], self.resolver.import_classes()) def test_create_class_map(self) -> None: classes = [ClassFactory.create(qname=name) for name in "ab"] expected = {obj.qname: obj for obj in classes} self.assertEqual(expected, self.resolver.create_class_map(classes)) def test_create_class_map_for_duplicate_classes(self) -> None: classes = ClassFactory.list(2, qname="a") with self.assertRaises(CodegenError): self.resolver.create_class_map(classes) @mock.patch.object(Class, "dependencies") def test_create_class_list(self, mock_dependencies) -> None: classes = ClassFactory.list(3) mock_dependencies.side_effect = [ {build_qname("xsdata", "class_C"), "b"}, {"c", "d"}, {"e", "d"}, ] actual = self.resolver.create_class_list(classes) expected = [ "b", "c", "d", "e", "{xsdata}class_C", "{xsdata}class_D", "{xsdata}class_B", ] self.assertEqual(expected, list(map(str, actual))) tefra-xsdata-cf7afe8/tests/codegen/test_transformer.py000066400000000000000000000443141526437326600234270ustar00rootroot00000000000000import pickle import tempfile from pathlib import Path from unittest import mock from toposort import CircularDependencyError from xsdata import __version__ from xsdata.codegen.container import ClassContainer from xsdata.codegen.exceptions import CodegenError from xsdata.codegen.mappers import ( DefinitionsMapper, DictMapper, DtdMapper, ElementMapper, SchemaMapper, ) from xsdata.codegen.parsers import DefinitionsParser, DtdParser from xsdata.codegen.transformer import ResourceTransformer from xsdata.codegen.utils import ClassUtils from xsdata.codegen.writer import CodeWriter from xsdata.formats.dataclass.models.generics import AnyElement from xsdata.formats.dataclass.parsers import TreeParser from xsdata.models.config import GeneratorConfig from xsdata.models.wsdl import Binding, Definitions, Types from xsdata.models.xsd import Import, Include, Override, Schema from xsdata.utils.testing import ClassFactory, DtdFactory, FactoryTestCase class ResourceTransformerTests(FactoryTestCase): def setUp(self) -> None: config = GeneratorConfig() self.transformer = ResourceTransformer(config=config) super().setUp() @mock.patch.object(ResourceTransformer, "process_classes") @mock.patch.object(ResourceTransformer, "process_dtds") @mock.patch.object(ResourceTransformer, "process_json_documents") @mock.patch.object(ResourceTransformer, "process_xml_documents") @mock.patch.object(ResourceTransformer, "process_schemas") @mock.patch.object(ResourceTransformer, "process_definitions") def test_process( self, mock_process_definitions, mock_process_schemas, mock_process_xml_documents, mock_process_json_documents, mock_process_dtds, mock_process_classes, ) -> None: uris = [ "a.wsdl", "b.wsdl", "c.xsd", "d.xsd", "e.xml", "f.xml", "g.json", "h.json", "i.dtd", ] self.transformer.process(uris) mock_process_definitions.assert_called_once_with(uris[:2]) mock_process_schemas.assert_called_once_with(uris[2:4]) mock_process_xml_documents.assert_called_once_with(uris[4:6]) mock_process_json_documents.assert_called_once_with(uris[6:8]) mock_process_dtds.assert_called_once_with(uris[8:]) mock_process_classes.assert_called_once_with() @mock.patch.object(ResourceTransformer, "process_classes") @mock.patch.object(ResourceTransformer, "process_sources") @mock.patch.object(ResourceTransformer, "get_cache_file") def test_process_from_cache( self, mock_get_cache_file, mock_process_sources, mock_process_classes ) -> None: with tempfile.TemporaryDirectory() as tmpdir: cache = Path(tmpdir).joinpath("foo.cache") classes = ClassFactory.list(2) cache.write_bytes(pickle.dumps(classes)) mock_get_cache_file.return_value = cache uris = ["a.xml", "b.xml"] self.transformer.process(uris, cache=True) self.assertEqual(classes, self.transformer.classes) self.assertEqual(0, mock_process_sources.call_count) mock_process_classes.assert_called_once_with() @mock.patch.object(ResourceTransformer, "process_classes") @mock.patch.object(ResourceTransformer, "process_sources") @mock.patch.object(ResourceTransformer, "get_cache_file") def test_process_with_cache( self, mock_get_cache_file, mock_process_sources, mock_process_classes ) -> None: with tempfile.TemporaryDirectory() as tmpdir: cache = Path(tmpdir).joinpath("foo.cache") classes = ClassFactory.list(2) self.transformer.classes = classes mock_get_cache_file.return_value = cache uris = ["a.xml", "b.xml"] self.transformer.process(uris, cache=True) self.assertEqual(1, mock_process_sources.call_count) self.assertEqual(classes, pickle.loads(cache.read_bytes())) mock_process_classes.assert_called_once_with() @mock.patch.object(ResourceTransformer, "process_classes") def test_process_with_circular_dependencies_error( self, mock_process_classes ) -> None: mock_process_classes.side_effect = CircularDependencyError({}) with self.assertRaises(CodegenError): self.transformer.process([]) @mock.patch("xsdata.codegen.transformer.logger.warning") @mock.patch.object(ResourceTransformer, "process_classes") def test_process_with_module_not_found_error( self, mock_process_classes, mock_warning ) -> None: mock_process_classes.side_effect = ModuleNotFoundError({}) self.transformer.process([]) mock_warning.assert_called_once_with( "Module not found on imports validation, please report it." ) @mock.patch.object(ResourceTransformer, "convert_schema") @mock.patch.object(ResourceTransformer, "convert_definitions") @mock.patch.object(ResourceTransformer, "parse_definitions") def test_process_definitions( self, mock_parse_definitions, mock_convert_definitions, mock_convert_schema, ) -> None: uris = [ "http://xsdata/services.wsdl", "http://xsdata/abstractServices.wsdl", "http://xsdata/notfound.wsdl", ] fist_def = Definitions(types=Types(schemas=[Schema(), Schema()])) second_def = Definitions(bindings=[Binding()]) mock_parse_definitions.side_effect = [fist_def, second_def, None] self.transformer.process_definitions(uris) mock_convert_schema.assert_has_calls([mock.call(x) for x in fist_def.schemas]) mock_parse_definitions.assert_has_calls( [mock.call(uris[0], namespace=None), mock.call(uris[1], namespace=None)] ) mock_convert_definitions.assert_called_once_with(fist_def) @mock.patch.object(ResourceTransformer, "process_schema") def test_process_schemas(self, mock_process_schema) -> None: uris = ["http://xsdata/foo.xsd", "http://xsdata/bar.xsd"] self.transformer.process_schemas(uris) mock_process_schema.assert_has_calls([mock.call(uri) for uri in uris]) @mock.patch.object(ClassUtils, "reduce_classes") @mock.patch.object(ElementMapper, "map") @mock.patch.object(TreeParser, "from_bytes") @mock.patch.object(ResourceTransformer, "load_resource") def test_process_xml_documents( self, mock_load_resource, mock_from_bytes, mock_map, mock_reduce_classes ) -> None: uris = ["foo/a.xml", "foo/b.xml", "foo/c.xml"] resources = [b"a", None, b"c"] elements = [AnyElement(), AnyElement()] classes_a = ClassFactory.list(2) classes_c = ClassFactory.list(3) mock_load_resource.side_effect = resources mock_from_bytes.side_effect = elements mock_map.side_effect = [classes_a, classes_c] mock_reduce_classes.return_value = classes_a + classes_c self.transformer.process_xml_documents(uris) self.assertEqual(5, len(self.transformer.classes)) mock_from_bytes.assert_has_calls( [mock.call(resources[0]), mock.call(resources[2])] ) mock_map.assert_has_calls([mock.call(x, "foo") for x in elements]) mock_reduce_classes.assert_called_once_with(classes_a + classes_c) @mock.patch("xsdata.codegen.transformer.logger.warning") @mock.patch.object(ClassUtils, "reduce_classes") @mock.patch.object(DictMapper, "map") @mock.patch.object(ResourceTransformer, "load_resource") def test_process_json_documents( self, mock_load_resource, mock_map, mock_reduce_classes, mock_warning ) -> None: uris = ["foo/a.json", "foo/b.json", "foo/c.json", "bar.json"] resources = [b'{"foo": 1}', None, b'[{"foo": true}]', b"notjson"] classes_a = ClassFactory.list(2) classes_c = ClassFactory.list(3) mock_load_resource.side_effect = resources mock_map.side_effect = [classes_a, classes_c] mock_reduce_classes.return_value = classes_a + classes_c self.transformer.config.output.package = "some.books" self.transformer.process_json_documents(uris) self.assertEqual(5, len(self.transformer.classes)) mock_map.assert_has_calls( [ mock.call({"foo": 1}, "books", "foo"), mock.call({"foo": True}, "books", "foo"), ] ) mock_reduce_classes.assert_called_once_with(classes_a + classes_c) mock_warning.assert_called_once_with( "JSON load failed for file: %s", uris[3], exc_info=mock.ANY ) @mock.patch.object(DtdMapper, "map") @mock.patch.object(DtdParser, "parse") @mock.patch.object(ResourceTransformer, "load_resource") def test_process_dtds(self, mock_load_resource, mock_parse, mock_map) -> None: uris = ["foo/a.dtd", "foo/b.dtd", "foo/c.dtd"] resources = [b"a", None, b"c"] dtds = DtdFactory.list(2) classes_a = ClassFactory.list(2) classes_c = ClassFactory.list(3) mock_load_resource.side_effect = resources mock_parse.side_effect = dtds mock_map.side_effect = [classes_a, classes_c] self.transformer.process_dtds(uris) self.assertEqual(5, len(self.transformer.classes)) mock_parse.assert_has_calls( [ mock.call(resources[0], location=uris[0]), mock.call(resources[2], location=uris[2]), ] ) mock_map.assert_has_calls( [ mock.call(dtds[0]), mock.call(dtds[1]), ] ) @mock.patch("xsdata.codegen.transformer.logger.info") @mock.patch.object(CodeWriter, "write") @mock.patch.object(ResourceTransformer, "analyze_classes") def test_process_classes( self, mock_analyze_classes, mock_writer_write, mock_logger_into, ) -> None: schema_classes = ClassFactory.list(3) analyzer_classes = ClassFactory.list(2) mock_analyze_classes.return_value = analyzer_classes self.transformer.classes = schema_classes self.transformer.process_classes() mock_analyze_classes.assert_called_once_with(schema_classes) mock_writer_write.assert_called_once_with(analyzer_classes) mock_logger_into.assert_has_calls( [ mock.call("Analyzer input: %d main and %d inner classes", 3, 0), mock.call("Analyzer output: %d main and %d inner classes", 2, 0), ] ) @mock.patch.object(ResourceTransformer, "convert_schema") @mock.patch.object(ResourceTransformer, "parse_schema") def test_process_schema( self, mock_parse_schema, mock_convert_schema, ) -> None: schema = Schema() mock_parse_schema.return_value = schema uri = "http://xsdata/services.xsd" namespace = "fooNS" self.transformer.process_schema(uri, namespace) mock_convert_schema.assert_called_once_with(schema) @mock.patch.object(ResourceTransformer, "convert_schema") @mock.patch.object(ResourceTransformer, "parse_schema") def test_process_schema_ignores_empty_schema( self, mock_parse_schema, mock_convert_schema, ) -> None: mock_parse_schema.return_value = None uri = "http://xsdata/services.xsd" namespace = "fooNS" self.transformer.process_schema(uri, namespace) self.assertEqual(0, mock_convert_schema.call_count) @mock.patch.object(ResourceTransformer, "generate_classes") @mock.patch.object(ResourceTransformer, "process_schema") def test_convert_schema(self, mock_process_schema, mock_generate_classes) -> None: schema = Schema(target_namespace="thug", location="main") schema.includes.append(Include(location="foo")) schema.overrides.append(Override()) schema.imports.append(Import(location="bar")) schema.imports.append(Import(location="fails")) mock_generate_classes.return_value = ClassFactory.list(2) self.transformer.convert_schema(schema) self.assertEqual(2, len(self.transformer.classes)) mock_process_schema.assert_has_calls( [ mock.call("bar", "thug"), mock.call("fails", "thug"), mock.call("foo", "thug"), ] ) @mock.patch.object(DefinitionsMapper, "map") def test_convert_definitions(self, mock_definitions_map) -> None: classes = ClassFactory.list(2) mock_definitions_map.return_value = classes definitions = Definitions(location="foo") self.transformer.convert_definitions(definitions) self.assertEqual(classes, self.transformer.classes) @mock.patch("xsdata.codegen.transformer.logger.info") @mock.patch.object(ResourceTransformer, "count_classes") @mock.patch.object(SchemaMapper, "map") def test_generate_classes( self, mock_mapper_map, mock_count_classes, mock_logger_info ) -> None: schema = Schema(location="edo.xsd") classes = ClassFactory.list(2) mock_mapper_map.return_value = classes mock_count_classes.return_value = 2, 4 self.transformer.generate_classes(schema) mock_mapper_map.assert_called_once_with(schema) mock_logger_info.assert_has_calls( [ mock.call("Compiling schema %s", schema.location), mock.call("Builder: %d main and %d inner classes", 2, 4), ] ) def test_parse_schema(self) -> None: uri = Path(__file__).parent.joinpath("../fixtures/books/schema.xsd").as_uri() schema = self.transformer.parse_schema(uri, "foo.bar") self.assertIsInstance(schema, Schema) self.assertEqual(2, len(schema.complex_types)) self.assertIsNone(self.transformer.parse_schema(uri, None)) # Once @mock.patch.object(ResourceTransformer, "process_schema") @mock.patch.object(Definitions, "merge") @mock.patch.object(DefinitionsParser, "from_bytes") @mock.patch.object(ResourceTransformer, "load_resource") def test_parse_definitions( self, mock_load_resource, mock_definitions_parser, mock_definitions_merge, mock_process_schema, ) -> None: def_one = Definitions( imports=[ Import(), Import(location="file://sub.wsdl"), Import(location="file://sub.wsdl"), Import(location="file://types.xsd"), ] ) def_two = Definitions() mock_load_resource.side_effect = ["a", "b", None] mock_definitions_parser.side_effect = [def_one, def_two] actual = self.transformer.parse_definitions("main.wsdl", "fooNS") self.assertEqual(def_one, actual) mock_definitions_merge.assert_called_once_with(def_two) mock_process_schema.assert_called_once_with("file://types.xsd") @mock.patch("xsdata.codegen.transformer.logger.debug") def test_load_resource(self, mock_debug) -> None: path = Path(__file__).as_uri() result = self.transformer.load_resource(path) self.assertIn(path, self.transformer.processed) self.assertTrue(len(result) > 0) result = self.transformer.load_resource(path) self.assertIsNone(result) mock_debug.assert_called_once_with("Skipping already processed: %s", path) def test_classify_resource(self) -> None: self.assertEqual(0, self.transformer.classify_resource("file://notexists")) self.assertEqual(1, self.transformer.classify_resource("a.xsd")) self.assertEqual(2, self.transformer.classify_resource("a.wsdl")) self.assertEqual(2, self.transformer.classify_resource("a?wsdl")) self.assertEqual(3, self.transformer.classify_resource("a.dtd")) self.assertEqual(4, self.transformer.classify_resource("a.xml")) self.assertEqual(5, self.transformer.classify_resource("a.json")) file_path = Path(tempfile.mktemp()) file_path.write_bytes(b" \n") self.assertEqual(1, self.transformer.classify_resource(file_path.as_uri())) file_path.write_bytes(b" \n") self.transformer.preloaded.clear() self.assertEqual(2, self.transformer.classify_resource(file_path.as_uri())) file_path.write_bytes(b" \n") self.transformer.preloaded.clear() self.assertEqual(4, self.transformer.classify_resource(file_path.as_uri())) file_path.write_bytes(b"\n} \n") self.transformer.preloaded.clear() self.assertEqual(5, self.transformer.classify_resource(file_path.as_uri())) file_path.write_bytes(b"aaa\n") self.transformer.preloaded.clear() self.assertEqual(0, self.transformer.classify_resource(file_path.as_uri())) file_path.unlink() @mock.patch("xsdata.codegen.transformer.logger.warning") def test_load_resource_missing(self, mock_warning) -> None: uri = Path.cwd().joinpath("foo/bar.xsd").as_uri() result = self.transformer.process([uri]) self.assertIsNone(result) mock_warning.assert_called_once_with("Resource not found %s", uri) def test_count_classes(self) -> None: classes = ClassFactory.list( 2, inner=ClassFactory.list(2, inner=ClassFactory.list(3)) ) self.assertEqual((2, 16), self.transformer.count_classes(classes)) @mock.patch.object(ClassContainer, "process") def test_analyze_classes(self, mock_process) -> None: classes = ClassFactory.list(2) result = self.transformer.analyze_classes(classes) self.assertEqual(2, len(result)) mock_process.assert_called_once() def test_get_cache_file(self) -> None: uris = ["a.xml", "b.json"] actual = self.transformer.get_cache_file(uris) tempdir = Path(tempfile.gettempdir()) expected = tempdir.joinpath( f"xsdata.{__version__}.ae1bed744d3d3611e698a2d2ef5335d2.cache" ) self.assertEqual(expected, actual) tefra-xsdata-cf7afe8/tests/codegen/test_utils.py000066400000000000000000000362161526437326600222270ustar00rootroot00000000000000import sys from collections.abc import Generator from unittest import mock from xsdata.codegen.exceptions import CodegenError from xsdata.codegen.models import Restrictions, Status from xsdata.codegen.utils import ClassUtils from xsdata.models.enums import DataType, Tag from xsdata.utils.testing import ( AttrFactory, AttrTypeFactory, ClassFactory, ExtensionFactory, FactoryTestCase, ) class ClassUtilsTests(FactoryTestCase): def test_find_value_attr(self) -> None: target = ClassFactory.create() with self.assertRaises(CodegenError): ClassUtils.find_value_attr(target) target.attrs.append(AttrFactory.element()) with self.assertRaises(CodegenError): ClassUtils.find_value_attr(target) target.attrs.append(AttrFactory.extension()) actual = ClassUtils.find_value_attr(target) self.assertEqual(target.attrs[1], actual) def test_remove_attribute(self) -> None: target = ClassFactory.elements(1) attr = target.attrs[0] ClassUtils.remove_attribute(target, attr) self.assertEqual(0, len(target.attrs)) @mock.patch.object(ClassUtils, "is_orphan_inner") def test_clean_inner_classes(self, mock_is_orphan_inner) -> None: mock_is_orphan_inner.side_effect = [True, False, True] target = ClassFactory.create() target.inner.extend(ClassFactory.list(3)) survivor = target.inner[1] ClassUtils.clean_inner_classes(target) self.assertEqual(1, len(target.inner)) self.assertEqual(survivor, target.inner[0]) def test_is_orphan_inner(self) -> None: inner = ClassFactory.create(qname="thug") target = ClassFactory.create( attrs=[ AttrFactory.create( types=[ AttrTypeFactory.create(qname="bar"), AttrTypeFactory.create(qname="foo"), ] ) ] ) self.assertTrue(ClassUtils.is_orphan_inner(target, inner)) target.attrs[0].types[1].qname = "thug" target.attrs[0].types[1].forward = True self.assertFalse(ClassUtils.is_orphan_inner(target, inner)) @mock.patch.object(ClassUtils, "copy_inner_classes") @mock.patch.object(ClassUtils, "clone_attribute") def test_copy_attributes( self, mock_clone_attribute, mock_copy_inner_classes ) -> None: mock_clone_attribute.side_effect = lambda x, y: x.clone() target = ClassFactory.create( attrs=[AttrFactory.create(name="a"), AttrFactory.create(name="b")] ) source = ClassFactory.create( attrs=[ AttrFactory.create(name="c", index=sys.maxsize), AttrFactory.create(name="a"), AttrFactory.create(name="b"), AttrFactory.create(name="d"), ] ) extension = ExtensionFactory.create(AttrTypeFactory.create(qname="foo")) target.extensions.append(extension) ClassUtils.copy_attributes(source, target, extension) self.assertEqual(["a", "b", "d", "c"], [attr.name for attr in target.attrs]) mock_copy_inner_classes.assert_has_calls( [ mock.call(source, target, source.attrs[0]), mock.call(source, target, source.attrs[3]), ] ) mock_clone_attribute.assert_has_calls( [ mock.call(source.attrs[0], extension.restrictions), mock.call(source.attrs[3], extension.restrictions), ] ) @mock.patch.object(ClassUtils, "copy_inner_classes") @mock.patch.object(ClassUtils, "clone_attribute") def test_copy_group_attributes( self, mock_clone_attribute, mock_copy_inner_classes ) -> None: mock_clone_attribute.side_effect = lambda x, y: x.clone() source = ClassFactory.elements(2) source.inner.append(ClassFactory.create()) target = ClassFactory.elements(3) attrs = target.attrs.copy() attrs[1].name = "bar" attr = target.attrs[1] ClassUtils.copy_group_attributes(source, target, attr) self.assertEqual(4, len(target.attrs)) self.assertEqual(source.attrs[0], target.attrs[1]) self.assertEqual(source.attrs[1], target.attrs[2]) mock_copy_inner_classes.assert_has_calls( [ mock.call(source, target, source.attrs[0]), mock.call(source, target, source.attrs[1]), ] ) mock_clone_attribute.assert_has_calls( [ mock.call(source.attrs[0], attrs[1].restrictions), mock.call(source.attrs[1], attrs[1].restrictions), ] ) def test_copy_extensions(self) -> None: target = ClassFactory.create(extensions=ExtensionFactory.list(1)) source = ClassFactory.create(extensions=ExtensionFactory.list(2)) link_extension = ExtensionFactory.create() link_extension.restrictions.max_occurs = 2 ClassUtils.copy_extensions(source, target, link_extension) self.assertEqual(3, len(target.extensions)) self.assertEqual(2, target.extensions[1].restrictions.max_occurs) self.assertEqual(2, target.extensions[2].restrictions.max_occurs) def test_clone_attribute(self) -> None: attr = AttrFactory.create( restrictions=Restrictions(length=1), types=[ AttrTypeFactory.create(qname="x"), AttrTypeFactory.create(qname="y"), AttrTypeFactory.native(DataType.INT), ], ) restrictions = Restrictions(length=2) clone = ClassUtils.clone_attribute(attr, restrictions) self.assertEqual(2, clone.restrictions.length) self.assertIsNot(attr, clone) @mock.patch.object(ClassUtils, "copy_inner_class") def test_copy_inner_classes(self, mock_copy_inner_class) -> None: source = ClassFactory.create() target = ClassFactory.create() attr = AttrFactory.create(types=AttrTypeFactory.list(3)) ClassUtils.copy_inner_classes(source, target, attr) mock_copy_inner_class.assert_has_calls( [ mock.call(source, target, attr.types[0]), mock.call(source, target, attr.types[1]), mock.call(source, target, attr.types[2]), ] ) def test_copy_inner_class(self) -> None: source = ClassFactory.create() inner = ClassFactory.create( qname="a", module="b", package="c", status=Status.FLATTENED ) target = ClassFactory.create() attr_type = AttrTypeFactory.create(forward=True, qname=inner.qname) source.inner.append(inner) ClassUtils.copy_inner_class(source, target, attr_type) self.assertEqual(1, len(target.inner)) self.assertIsNot(inner, target.inner[0]) self.assertEqual(target.package, target.inner[0].package) self.assertEqual(target.module, target.inner[0].module) self.assertEqual(inner.qname, target.inner[0].qname) self.assertIs(Status.RAW, target.inner[0].status) self.assertIs(Status.FLATTENED, inner.status) def test_copy_inner_class_check_circular_reference(self) -> None: source = ClassFactory.create() target = ClassFactory.create() attr_type = AttrTypeFactory.create(forward=True, qname=target.qname) source.inner.append(target) ClassUtils.copy_inner_class(source, target, attr_type) self.assertTrue(attr_type.circular) self.assertEqual(0, len(target.inner)) def test_copy_inner_class_with_missing_inner(self) -> None: source = ClassFactory.create() target = ClassFactory.create() attr_type = AttrTypeFactory.create(forward=True, qname=target.qname) with self.assertRaises(CodegenError): ClassUtils.copy_inner_class(source, target, attr_type) def test_flatten(self) -> None: target = ClassFactory.create( qname="{xsdata}root", attrs=AttrFactory.list(3), inner=ClassFactory.list(2) ) for attr in target.attrs: attr.types.extend([x.clone() for x in attr.types]) for tp in attr.types: tp.forward = True result = ClassUtils.flatten(target, "xsdata") actual = list(result) self.assertIsInstance(result, Generator) self.assertEqual(3, len(actual)) for obj in actual: self.assertEqual("xsdata", obj.location) for attr in target.attrs: self.assertEqual(1, len(attr.types)) self.assertFalse(attr.types[0].forward) def test_reduce_classes(self) -> None: class_a = ClassFactory.elements(3) class_a_clone = class_a.clone() class_a_clone.mixed = True class_a.attrs.pop() class_b = ClassFactory.elements(2) result = ClassUtils.reduce_classes([class_a, class_b, class_a_clone]) self.assertEqual(2, len(result)) self.assertTrue(result[0].mixed) self.assertEqual( ["attr_B", "attr_C", "attr_D"], [x.name for x in result[0].attrs] ) self.assertEqual(["attr_E", "attr_F"], [x.name for x in result[1].attrs]) def test_reduce_attributes(self) -> None: restrictions = Restrictions(min_occurs=1, max_occurs=1) attr_a = AttrFactory.create(name="a", restrictions=restrictions.clone()) attr_b = AttrFactory.create(name="b", restrictions=restrictions.clone()) attr_c = AttrFactory.create(name="c", restrictions=restrictions.clone()) attr_d = AttrFactory.create(name="d", restrictions=restrictions.clone()) first = ClassFactory.create(qname="alphabet", attrs=[attr_b, attr_c, attr_d]) second = ClassFactory.create(qname="alphabet", attrs=[attr_a, attr_b]) result = ClassUtils.reduce_attributes([first, second]) expected_names = ["a", "b", "c", "d"] self.assertEqual(expected_names, [x.name for x in result]) self.assertEqual(0, result[0].restrictions.min_occurs) self.assertEqual(1, result[1].restrictions.min_occurs) self.assertEqual(0, result[2].restrictions.min_occurs) self.assertEqual(0, result[3].restrictions.min_occurs) def test_merge_attributes(self) -> None: a = AttrFactory.native(DataType.INT, name="a") c = AttrFactory.native(DataType.FLOAT, name="c") ClassUtils.merge_attributes(a, c) self.assertEqual(["int", "float"], [x.name for x in a.types]) self.assertEqual(0, a.restrictions.min_occurs) self.assertEqual(1, a.restrictions.max_occurs) self.assertIsNone(a.restrictions.sequence) c.restrictions.min_occurs = 2 a.restrictions.sequence = 1 a.restrictions.max_occurs = 4 ClassUtils.merge_attributes(c, a) self.assertEqual(0, c.restrictions.min_occurs) self.assertEqual(4, c.restrictions.max_occurs) self.assertEqual(1, c.restrictions.sequence) def test_rename_duplicate_attributes(self) -> None: attrs = [ AttrFactory.create(name="a", tag=Tag.ELEMENT), AttrFactory.create(name="a", tag=Tag.ATTRIBUTE), AttrFactory.create(name="b", tag=Tag.ATTRIBUTE), AttrFactory.create(name="c", tag=Tag.ATTRIBUTE), AttrFactory.create(name="c", tag=Tag.ELEMENT), AttrFactory.create(name="d", tag=Tag.ELEMENT), AttrFactory.create(name="d", tag=Tag.ELEMENT), AttrFactory.create(name="e", tag=Tag.ELEMENT, namespace="b"), AttrFactory.create(name="e", tag=Tag.ELEMENT), AttrFactory.create(name="f", tag=Tag.ELEMENT), AttrFactory.create(name="f", tag=Tag.ELEMENT, namespace="a"), AttrFactory.create(name="gA", tag=Tag.ENUMERATION), AttrFactory.create(name="g[A]", tag=Tag.ENUMERATION), AttrFactory.create(name="g_a", tag=Tag.ENUMERATION), AttrFactory.create(name="g_a_1", tag=Tag.ENUMERATION), ] target = ClassFactory.create(attrs=attrs) ClassUtils.rename_duplicate_attributes(target) expected = [ "a", "a_Attribute", "b", "c_Attribute", "c", "d_Element", "d", "b_e", "e", "f", "a_f", "gA", "g[A]_2", "g_a_3", "g_a_1", ] self.assertEqual(expected, [x.name for x in attrs]) def test_rename_attribute_by_preference(self) -> None: one = AttrFactory.create(name="a", tag=Tag.ELEMENT) two = AttrFactory.create(name="a", tag=Tag.ATTRIBUTE) ClassUtils.rename_attribute_by_preference(one, two) self.assertEqual("a", one.name) self.assertEqual("a_Attribute", two.name) one = AttrFactory.create(name="a", tag=Tag.ELEMENT) two = AttrFactory.create(name="a", tag=Tag.ELEMENT, namespace="foo") ClassUtils.rename_attribute_by_preference(one, two) self.assertEqual("a", one.name) self.assertEqual("foo_a", two.name) one = AttrFactory.create(name="a", tag=Tag.ELEMENT, namespace="foo") two = AttrFactory.create(name="a", tag=Tag.ELEMENT) ClassUtils.rename_attribute_by_preference(one, two) self.assertEqual("foo_a", one.name) self.assertEqual("a", two.name) one = AttrFactory.create(name="a", tag=Tag.ELEMENT) two = AttrFactory.create(name="a", tag=Tag.ELEMENT) ClassUtils.rename_attribute_by_preference(one, two) self.assertEqual("a_Element", one.name) self.assertEqual("a", two.name) def test_filter_types(self) -> None: xs_string = AttrTypeFactory.native(DataType.STRING) xs_error = AttrTypeFactory.native(DataType.ERROR) xs_any = AttrTypeFactory.native(DataType.ANY_TYPE) types = [ xs_string.clone(), xs_string.clone(), xs_string.clone(), xs_error.clone(), ] actual = ClassUtils.filter_types(types) self.assertEqual(1, len(actual)) types.append(xs_any) actual = ClassUtils.filter_types(types) self.assertEqual(1, len(actual)) self.assertEqual(xs_string, actual[0]) actual = ClassUtils.filter_types([]) self.assertEqual(xs_string, actual[0]) types = [xs_any] actual = ClassUtils.filter_types(types) self.assertEqual(1, len(actual)) def test_find_nested(self) -> None: a = ClassFactory.create(qname="a") b = ClassFactory.create(qname="b") c = ClassFactory.create(qname="c") a.inner.append(b) b.inner.append(c) c.parent = b b.parent = a self.assertEqual(a, ClassUtils.find_nested(a, "a")) self.assertEqual(b, ClassUtils.find_nested(a, "b")) self.assertEqual(b, ClassUtils.find_nested(c, "b")) self.assertEqual(a, ClassUtils.find_nested(c, "a")) a2 = ClassFactory.create(qname="a") c.inner.append(a2) a2.parent = c # Breadth-first search self.assertEqual(a2, ClassUtils.find_nested(c, "a")) with self.assertRaises(CodegenError): ClassUtils.find_nested(a, "nope") tefra-xsdata-cf7afe8/tests/codegen/test_validator.py000066400000000000000000000151271526437326600230520ustar00rootroot00000000000000from unittest import mock from xsdata.codegen.container import ClassContainer from xsdata.codegen.utils import ClassUtils from xsdata.codegen.validator import ClassValidator from xsdata.models.config import GeneratorConfig from xsdata.models.enums import DataType, Tag from xsdata.utils.testing import ( AttrFactory, AttrTypeFactory, ClassFactory, ExtensionFactory, FactoryTestCase, ) class ClassValidatorTests(FactoryTestCase): def setUp(self) -> None: super().setUp() self.container = ClassContainer(config=GeneratorConfig()) self.validator = ClassValidator(container=self.container) @mock.patch.object(ClassValidator, "merge_global_types") @mock.patch.object(ClassValidator, "handle_duplicate_types") @mock.patch.object(ClassValidator, "remove_invalid_classes") def test_process( self, mock_remove_invalid_classes, mock_handle_duplicate_types, mock_merge_global_types, ) -> None: first = ClassFactory.create() second = first.clone() third = ClassFactory.create() self.container.extend([first, second, third]) self.validator.process() mock_remove_invalid_classes.assert_called_once_with([first, second]) mock_handle_duplicate_types.assert_called_once_with([first, second]) mock_merge_global_types.assert_called_once_with([first, second]) def test_remove_invalid_classes(self) -> None: first = ClassFactory.create( extensions=[ ExtensionFactory.create(AttrTypeFactory.native(DataType.BOOLEAN)), ExtensionFactory.create(AttrTypeFactory.create(qname="foo")), ] ) second = ClassFactory.create( extensions=[ ExtensionFactory.create(AttrTypeFactory.native(DataType.BOOLEAN)) ] ) third = ClassFactory.create() self.validator.container.extend([first, second, third]) classes = [first, second, third] self.validator.remove_invalid_classes(classes) self.assertEqual([second, third], classes) @mock.patch("xsdata.codegen.mappers.definitions.logger.warning") def test_handle_duplicate_types(self, mock_warning) -> None: one = ClassFactory.create(tag=Tag.ELEMENT) two = one.clone() three = one.clone() four = ClassFactory.create(tag=Tag.ATTRIBUTE) classes = [one, two, three, four] self.validator.handle_duplicate_types(classes) self.assertEqual([three, four], classes) mock_warning.assert_called_once_with( "Duplicate type %s, will keep the last defined", "{xsdata}class_B", ) @mock.patch.object(ClassValidator, "merge_redefined_type") def test_handle_duplicate_types_with_redefined_type( self, mock_merge_redefined_type ) -> None: one = ClassFactory.create(tag=Tag.ELEMENT) two = one.clone() three = one.clone() four = ClassFactory.create(tag=Tag.ATTRIBUTE) one.container = Tag.REDEFINE classes = [one, two, three, four] self.validator.handle_duplicate_types(classes) self.assertEqual([one, four], classes) mock_merge_redefined_type.assert_has_calls( [mock.call(two, one), mock.call(three, one)] ) def test_merge_global_types(self) -> None: one = ClassFactory.create( qname="foo", tag=Tag.ELEMENT, namespace="a", help="b", substitutions=["a", "b"], ) two = ClassFactory.create(qname="foo", tag=Tag.COMPLEX_TYPE, substitutions=[]) three = ClassFactory.create(qname="foo", tag=Tag.SIMPLE_TYPE) classes = [one, two, three] self.validator.merge_global_types(classes) self.assertEqual(3, len(classes)) classes = [one, three] self.validator.merge_global_types(classes) self.assertEqual(2, len(classes)) classes = [two, three] self.validator.merge_global_types(classes) self.assertEqual(2, len(classes)) classes = [one, two, three] one.attrs.append(AttrFactory.create) one.extensions.append(ExtensionFactory.reference(two.qname)) self.validator.merge_global_types(classes) self.assertEqual(3, len(classes)) one.attrs.clear() one.extensions.append(ExtensionFactory.reference("foo")) self.validator.merge_global_types(classes) self.assertEqual(3, len(classes)) one.extensions.pop() self.validator.merge_global_types(classes) self.assertEqual(2, len(classes)) self.assertIn(two, classes) self.assertIn(three, classes) self.assertEqual(one.namespace, two.namespace) self.assertEqual(one.help, two.help) self.assertEqual(one.substitutions, two.substitutions) self.assertEqual(2, len(one.substitutions)) @mock.patch.object(ClassUtils, "copy_extensions") @mock.patch.object(ClassUtils, "copy_attributes") def test_merge_redefined_type_with_circular_extension( self, mock_copy_attributes, mock_copy_extensions ) -> None: source = ClassFactory.create() target = source.clone() ext_a = ExtensionFactory.create(AttrTypeFactory.create(qname=source.name)) ext_str = ExtensionFactory.create(AttrTypeFactory.create(qname="foo")) target.extensions.append(ext_str) target.extensions.append(ext_a) self.validator.merge_redefined_type(source, target) mock_copy_attributes.assert_called_once_with(source, target, ext_a) mock_copy_extensions.assert_called_once_with(source, target, ext_a) @mock.patch.object(ClassUtils, "copy_group_attributes") def test_merge_redefined_type_with_circular_group( self, mock_copy_group_attributes ) -> None: source = ClassFactory.create() target = source.clone() target.container = Tag.REDEFINE first_attr = AttrFactory.create() second_attr = AttrFactory.create(name=source.name) target.attrs.extend((first_attr, second_attr)) self.validator.merge_redefined_type(source, target) mock_copy_group_attributes.assert_called_once_with(source, target, second_attr) def test_select_winner(self) -> None: classes = ClassFactory.list(2) self.assertEqual(-1, self.validator.select_winner(classes)) classes[0].container = Tag.OVERRIDE self.assertEqual(0, self.validator.select_winner(classes)) classes[0].container = Tag.SCHEMA classes[1].container = Tag.REDEFINE self.assertEqual(1, self.validator.select_winner(classes)) tefra-xsdata-cf7afe8/tests/codegen/test_writer.py000066400000000000000000000043411526437326600223750ustar00rootroot00000000000000from collections.abc import Iterator from pathlib import Path from tempfile import TemporaryDirectory from unittest import mock from xsdata.codegen.exceptions import CodegenError from xsdata.codegen.models import Class from xsdata.codegen.writer import CodeWriter from xsdata.formats.dataclass.generator import DataclassGenerator from xsdata.formats.mixins import AbstractGenerator, GeneratorResult from xsdata.models.config import GeneratorConfig from xsdata.utils.testing import ClassFactory, FactoryTestCase class NoneGenerator(AbstractGenerator): def render(self, classes: list[Class]) -> Iterator[GeneratorResult]: pass class CodeWriterTests(FactoryTestCase): def setUp(self) -> None: config = GeneratorConfig() generator = NoneGenerator(config) self.writer = CodeWriter(generator) @mock.patch.object(NoneGenerator, "render_header") @mock.patch.object(NoneGenerator, "render") @mock.patch.object(NoneGenerator, "normalize_packages") def test_write( self, mock_normalize_packages, mock_render, mock_render_header ) -> None: classes = ClassFactory.list(2) with TemporaryDirectory() as tmpdir: mock_render.return_value = [ GeneratorResult(Path(f"{tmpdir}/foo/a.py"), "file", "aAa"), GeneratorResult(Path(f"{tmpdir}/bar/b.py"), "file", "bBb"), GeneratorResult(Path(f"{tmpdir}/c.py"), "file", " "), ] mock_render_header.return_value = "// Head\n" self.writer.write(classes) self.assertEqual("// Head\naAa", Path(f"{tmpdir}/foo/a.py").read_text()) self.assertEqual("// Head\nbBb", Path(f"{tmpdir}/bar/b.py").read_text()) self.assertFalse(Path(f"{tmpdir}/c.py").exists()) mock_normalize_packages.assert_called_once_with(classes) def test_from_config(self) -> None: CodeWriter.unregister_generator("dataclasses") config = GeneratorConfig() with self.assertRaises(CodegenError): CodeWriter.from_config(config) CodeWriter.register_generator("dataclasses", DataclassGenerator) writer = CodeWriter.from_config(config) self.assertIsInstance(writer.generator, DataclassGenerator) tefra-xsdata-cf7afe8/tests/conftest.py000066400000000000000000000027141526437326600202450ustar00rootroot00000000000000from pathlib import Path from lxml import etree from xsdata.formats.dataclass.context import XmlContext from xsdata.formats.dataclass.parsers import JsonParser, XmlParser from xsdata.formats.dataclass.serializers import ( JsonSerializer, PycodeSerializer, XmlSerializer, ) from xsdata.formats.dataclass.serializers.config import SerializerConfig def validate_bindings(schema: Path, clazz: type) -> None: __tracebackhide__ = True sample = schema.parent.joinpath("sample.xml") context = XmlContext() config = SerializerConfig(indent=" ") xml_parser = XmlParser(context=context) xml_serializer = XmlSerializer(context=context, config=config) json_serializer = JsonSerializer(context=context, config=config) pycode_serializer = PycodeSerializer(context=context) obj = xml_parser.from_path(sample, clazz) code = pycode_serializer.render(obj) sample.with_suffix(".py").write_text(code) actual = json_serializer.render(obj) expected = sample.with_suffix(".json") if expected.exists(): assert expected.read_text() == actual assert obj == JsonParser().from_string(actual, clazz) else: expected.write_text(actual, encoding="utf-8") xml = xml_serializer.render(obj) expected.with_suffix(".xsdata.xml").write_text(xml, encoding="utf-8") validator = etree.XMLSchema(etree.parse(str(schema))) assert validator.validate(etree.fromstring(xml.encode())), validator.error_log tefra-xsdata-cf7afe8/tests/fixtures/000077500000000000000000000000001526437326600177135ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/__init__.py000066400000000000000000000000001526437326600220120ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/annotations/000077500000000000000000000000001526437326600222505ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/annotations/__init__.py000066400000000000000000000002741526437326600243640ustar00rootroot00000000000000from tests.fixtures.annotations.model import ( Measurement, Weight, ) from tests.fixtures.annotations.units import unit __all__ = [ "Measurement", "Weight", "unit", ] tefra-xsdata-cf7afe8/tests/fixtures/annotations/model.py000066400000000000000000000007601526437326600237250ustar00rootroot00000000000000from __future__ import annotations from dataclasses import dataclass, field from tests.fixtures.annotations.units import unit __NAMESPACE__ = "http://domain.org/schema/model" @dataclass(kw_only=True) class Measurement: value: float = field() unit: None | unit = field( default=None, metadata={ "type": "Attribute", }, ) @dataclass(kw_only=True) class Weight(Measurement): class Meta: namespace = "http://domain.org/schema/model" tefra-xsdata-cf7afe8/tests/fixtures/annotations/model.xsd000066400000000000000000000011041526437326600240640ustar00rootroot00000000000000 tefra-xsdata-cf7afe8/tests/fixtures/annotations/sample.xml000066400000000000000000000001061526437326600242500ustar00rootroot00000000000000 2.0 tefra-xsdata-cf7afe8/tests/fixtures/annotations/units.py000066400000000000000000000003051526437326600237620ustar00rootroot00000000000000from __future__ import annotations from enum import Enum __NAMESPACE__ = "http://domain.org/schema/model/units" class unit(Enum): M = "m" KG = "kg" PERCENT_SIGN = "%" NA = "NA" tefra-xsdata-cf7afe8/tests/fixtures/annotations/units.xsd000066400000000000000000000012241526437326600241310ustar00rootroot00000000000000 tefra-xsdata-cf7afe8/tests/fixtures/annotations/xsdata.xml000066400000000000000000000016661526437326600242670ustar00rootroot00000000000000 tests.fixtures.annotations dataclasses filenames reStructuredText false true true tefra-xsdata-cf7afe8/tests/fixtures/artists/000077500000000000000000000000001526437326600214045ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/artists/__init__.py000066400000000000000000000007171526437326600235220ustar00rootroot00000000000000from tests.fixtures.artists.metadata import ( Alias, AliasList, Area, Artist, BeginArea, EndArea, Gender, IpiList, IsniList, Iso31661CodeList, Iso31662CodeList, LifeSpan, Metadata, ) __all__ = [ "Alias", "AliasList", "Area", "Artist", "BeginArea", "EndArea", "Gender", "IpiList", "IsniList", "Iso31661CodeList", "Iso31662CodeList", "LifeSpan", "Metadata", ] tefra-xsdata-cf7afe8/tests/fixtures/artists/art001.xml000066400000000000000000000106451526437326600231430ustar00rootroot00000000000000 Ludwig van Beethoven Beethoven, Ludwig van German composer 00002507239 00002507239 0000000121268987 Male DE Germany Germany DE Bonn Bonn Wien Wien AT-9 1770-12-17 1827-03-26 true Beethoven Beethoven _ Ludwig Van Beethoven, Ludwig Beethoven, Ludwig Van Beethoven, Ludwig Van (1770 - 1827) Beethoven, Ludwig Von Beethoven, Ludwig van (1770-1827) Beethoven, Ludwig van, 1770-1827 Bethoveen L v Beethoven L. Van Bēthovens L. Von Beethoven L. van Beethoven L.Van Beethoven Ludvig Von Beethoven Ludvig van Beethoven Ludvigs van Bēthovens Ludwid van Beethoven Ludwig Beethoven Ludwig Van Beethoven (1770-1827) Ludwig Von Beethoven Ludwig van Beethoven LvB bheetoven van Beethoven, Ludwig Бетховен Л. Бетховен ベートーヴェン ルートヴィヒ・ヴァン・ベートーヴェン 貝多芬 베토벤 tefra-xsdata-cf7afe8/tests/fixtures/artists/art002.xml000066400000000000000000000022761526437326600231450ustar00rootroot00000000000000 AC/DC AC/DC Australian hard rock band 000000012271298X AU Australia Australia AU Sydney Sydney 1973-11 AC DC AC-DC AC.DC AC?DC ACDC AC\DC Ballbreaker tefra-xsdata-cf7afe8/tests/fixtures/artists/art003.xml000066400000000000000000000051051526437326600231400ustar00rootroot00000000000000 Eminem Eminem 00354929235 00354929235 00354929431 0000000117699370 0000000368611900 Male US United States United States US St. Joseph St. Joseph 1972-10-17 EMINƎM Em Eminem EminemMusic M&M M. Mathers Marshall B. Mathers III Marshall Bruce Mathers III Marshall Mathers III Ominem Slim Shady The Real Slim Shady エミネム tefra-xsdata-cf7afe8/tests/fixtures/artists/metadata.py000066400000000000000000000163411526437326600235430ustar00rootroot00000000000000from __future__ import annotations from dataclasses import dataclass, field from xsdata.models.datatype import XmlDate, XmlPeriod __NAMESPACE__ = "http://musicbrainz.org/ns/mmd-2.0#" @dataclass(kw_only=True) class Alias: class Meta: name = "alias" namespace = "http://musicbrainz.org/ns/mmd-2.0#" locale: None | str = field( default=None, metadata={ "type": "Attribute", }, ) sort_name: str = field( metadata={ "name": "sort-name", "type": "Attribute", } ) type_value: None | str = field( default=None, metadata={ "name": "type", "type": "Attribute", }, ) type_id: None | str = field( default=None, metadata={ "name": "type-id", "type": "Attribute", }, ) primary: None | str = field( default=None, metadata={ "type": "Attribute", }, ) value: str = field(default="") @dataclass(kw_only=True) class BeginArea: class Meta: name = "begin-area" namespace = "http://musicbrainz.org/ns/mmd-2.0#" id: str = field( metadata={ "type": "Attribute", } ) name: str = field( metadata={ "type": "Element", } ) sort_name: str = field( metadata={ "name": "sort-name", "type": "Element", } ) @dataclass(kw_only=True) class Gender: class Meta: name = "gender" namespace = "http://musicbrainz.org/ns/mmd-2.0#" id: str = field( metadata={ "type": "Attribute", } ) value: str = field(default="") @dataclass(kw_only=True) class IpiList: class Meta: name = "ipi-list" namespace = "http://musicbrainz.org/ns/mmd-2.0#" ipi: list[str] = field( default_factory=list, metadata={ "type": "Element", "min_occurs": 1, }, ) @dataclass(kw_only=True) class IsniList: class Meta: name = "isni-list" namespace = "http://musicbrainz.org/ns/mmd-2.0#" isni: list[str] = field( default_factory=list, metadata={ "type": "Element", "min_occurs": 1, }, ) @dataclass(kw_only=True) class Iso31661CodeList: class Meta: name = "iso-3166-1-code-list" namespace = "http://musicbrainz.org/ns/mmd-2.0#" iso_3166_1_code: str = field( metadata={ "name": "iso-3166-1-code", "type": "Element", } ) @dataclass(kw_only=True) class Iso31662CodeList: class Meta: name = "iso-3166-2-code-list" namespace = "http://musicbrainz.org/ns/mmd-2.0#" iso_3166_2_code: str = field( metadata={ "name": "iso-3166-2-code", "type": "Element", } ) @dataclass(kw_only=True) class LifeSpan: class Meta: name = "life-span" namespace = "http://musicbrainz.org/ns/mmd-2.0#" begin: XmlDate | XmlPeriod = field( metadata={ "type": "Element", } ) end: None | XmlDate = field( default=None, metadata={ "type": "Element", }, ) ended: None | bool = field( default=None, metadata={ "type": "Element", }, ) @dataclass(kw_only=True) class AliasList: class Meta: name = "alias-list" namespace = "http://musicbrainz.org/ns/mmd-2.0#" count: int = field( metadata={ "type": "Attribute", } ) alias: list[Alias] = field( default_factory=list, metadata={ "type": "Element", "min_occurs": 1, }, ) @dataclass(kw_only=True) class Area: class Meta: name = "area" namespace = "http://musicbrainz.org/ns/mmd-2.0#" id: str = field( metadata={ "type": "Attribute", } ) name: str = field( metadata={ "type": "Element", } ) sort_name: str = field( metadata={ "name": "sort-name", "type": "Element", } ) iso_3166_1_code_list: Iso31661CodeList = field( metadata={ "name": "iso-3166-1-code-list", "type": "Element", } ) @dataclass(kw_only=True) class EndArea: class Meta: name = "end-area" namespace = "http://musicbrainz.org/ns/mmd-2.0#" id: str = field( metadata={ "type": "Attribute", } ) name: str = field( metadata={ "type": "Element", } ) sort_name: str = field( metadata={ "name": "sort-name", "type": "Element", } ) iso_3166_2_code_list: Iso31662CodeList = field( metadata={ "name": "iso-3166-2-code-list", "type": "Element", } ) @dataclass(kw_only=True) class Artist: class Meta: name = "artist" namespace = "http://musicbrainz.org/ns/mmd-2.0#" id: str = field( metadata={ "type": "Attribute", } ) type_value: str = field( metadata={ "name": "type", "type": "Attribute", } ) type_id: str = field( metadata={ "name": "type-id", "type": "Attribute", } ) name: str = field( metadata={ "type": "Element", } ) sort_name: str = field( metadata={ "name": "sort-name", "type": "Element", } ) disambiguation: None | str = field( default=None, metadata={ "type": "Element", }, ) ipi: None | str = field( default=None, metadata={ "type": "Element", }, ) ipi_list: None | IpiList = field( default=None, metadata={ "name": "ipi-list", "type": "Element", }, ) isni_list: IsniList = field( metadata={ "name": "isni-list", "type": "Element", } ) gender: None | Gender = field( default=None, metadata={ "type": "Element", }, ) country: str = field( metadata={ "type": "Element", } ) area: Area = field( metadata={ "type": "Element", } ) begin_area: BeginArea = field( metadata={ "name": "begin-area", "type": "Element", } ) end_area: None | EndArea = field( default=None, metadata={ "name": "end-area", "type": "Element", }, ) life_span: LifeSpan = field( metadata={ "name": "life-span", "type": "Element", } ) alias_list: AliasList = field( metadata={ "name": "alias-list", "type": "Element", } ) @dataclass(kw_only=True) class Metadata: class Meta: name = "metadata" namespace = "http://musicbrainz.org/ns/mmd-2.0#" artist: Artist = field( metadata={ "type": "Element", } ) tefra-xsdata-cf7afe8/tests/fixtures/books/000077500000000000000000000000001526437326600210305ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/books/__init__.py000066400000000000000000000002201526437326600231330ustar00rootroot00000000000000from tests.fixtures.books.books import ( BookForm, Books, BooksForm, ) __all__ = [ "BookForm", "BooksForm", "Books", ] tefra-xsdata-cf7afe8/tests/fixtures/books/bk001.xml000066400000000000000000000004201526437326600223630ustar00rootroot00000000000000 Hightower, Kim The First Book Fiction 44.95 2000-10-01 An amazing story of nothing. tefra-xsdata-cf7afe8/tests/fixtures/books/bk002.xml000066400000000000000000000004441526437326600223720ustar00rootroot00000000000000 Nagata, Suanne Becoming Somebody Biography 33.95 2001-01-10 A masterpiece of the fine art of gossiping. tefra-xsdata-cf7afe8/tests/fixtures/books/books-xinclude.xml000066400000000000000000000003021526437326600244730ustar00rootroot00000000000000 tefra-xsdata-cf7afe8/tests/fixtures/books/books.json000066400000000000000000000007451526437326600230460ustar00rootroot00000000000000{ "book": [ { "author": "Hightower, Kim", "title": "The First Book", "genre": "Fiction", "price": 44.95, "pub_date": "2000-10-01", "review": "An amazing story of nothing.", "id": "bk001" }, { "author": "Nagata, Suanne", "title": "Becoming Somebody", "genre": "Biography", "price": null, "pub_date": null, "review": "A masterpiece of the fine art of gossiping.", "id": "bk002" } ] }tefra-xsdata-cf7afe8/tests/fixtures/books/books.py000066400000000000000000000034671526437326600225310ustar00rootroot00000000000000from __future__ import annotations from dataclasses import dataclass, field from xsdata.models.datatype import XmlDate __NAMESPACE__ = "urn:books" @dataclass(kw_only=True) class BookForm: """ Book Definition. Attributes: author: Writer's name title: Book Title genre: Book Genre price: Amount in USD pub_date: Publication date review: Sticky Review id: International Standard Book Number lang: Language ISO Code """ author: str = field( metadata={ "type": "Element", "namespace": "", } ) title: str = field( metadata={ "type": "Element", "namespace": "", } ) genre: str = field( metadata={ "type": "Element", "namespace": "", } ) price: float = field( metadata={ "type": "Element", "namespace": "", } ) pub_date: XmlDate = field( metadata={ "type": "Element", "namespace": "", } ) review: str = field( metadata={ "type": "Element", "namespace": "", } ) id: None | str = field( default=None, metadata={ "type": "Attribute", }, ) lang: str = field( init=False, default="en", metadata={ "type": "Attribute", }, ) @dataclass(kw_only=True) class BooksForm: book: list[BookForm] = field( default_factory=list, metadata={ "type": "Element", "namespace": "", }, ) @dataclass(kw_only=True) class Books(BooksForm): """ Το βιβλίο. """ class Meta: name = "books" namespace = "urn:books" tefra-xsdata-cf7afe8/tests/fixtures/books/books.xml000066400000000000000000000011321526437326600226640ustar00rootroot00000000000000 Hightower, Kim The First Book Fiction 44.95 2000-10-01 An amazing story of nothing. Nagata, Suanne Becoming Somebody Biography 33.95 2001-01-10 A masterpiece of the fine art of gossiping. tefra-xsdata-cf7afe8/tests/fixtures/books/books_auto_ns.xml000066400000000000000000000011321526437326600244140ustar00rootroot00000000000000 Hightower, Kim The First Book Fiction 44.95 2000-10-01 An amazing story of nothing. Nagata, Suanne Becoming Somebody Biography 33.95 2001-01-10 A masterpiece of the fine art of gossiping. tefra-xsdata-cf7afe8/tests/fixtures/books/books_default_ns.xml000066400000000000000000000011401526437326600250670ustar00rootroot00000000000000 Hightower, Kim The First Book Fiction 44.95 2000-10-01 An amazing story of nothing. Nagata, Suanne Becoming Somebody Biography 33.95 2001-01-10 A masterpiece of the fine art of gossiping. tefra-xsdata-cf7afe8/tests/fixtures/books/fixtures.py000066400000000000000000000074031526437326600232570ustar00rootroot00000000000000from tests.fixtures.books import BookForm, Books from xsdata.models.datatype import XmlDate books = Books( book=[ BookForm( id="bk001", author="Hightower, Kim", title="The First Book", genre="Fiction", price=44.95, pub_date=XmlDate(2000, 10, 1), review="An amazing story of nothing.", ), BookForm( id="bk002", author="Nagata, Suanne", title="Becoming Somebody", genre="Biography", price=33.95, pub_date=XmlDate(2001, 1, 10), review="A masterpiece of the fine art of gossiping.", ), ] ) events = [ ("start-ns", "brk", "urn:books"), ("start", "{urn:books}books", {}, {"brk": "urn:books"}), ("start", "book", {"id": "bk001", "lang": "en"}, {"brk": "urn:books"}), ("start", "author", {}, {"brk": "urn:books"}), ("end", "author", "Hightower, Kim", "\n "), ("start", "title", {}, {"brk": "urn:books"}), ("end", "title", "The First Book", "\n "), ("start", "genre", {}, {"brk": "urn:books"}), ("end", "genre", "Fiction", "\n "), ("start", "price", {}, {"brk": "urn:books"}), ("end", "price", "44.95", "\n "), ("start", "pub_date", {}, {"brk": "urn:books"}), ("end", "pub_date", "2000-10-01", "\n "), ("start", "review", {}, {"brk": "urn:books"}), ("end", "review", "An amazing story of nothing.", "\n "), ("end", "book", "\n ", "\n "), ("start", "book", {"id": "bk002", "lang": "en"}, {"brk": "urn:books"}), ("start", "author", {}, {"brk": "urn:books"}), ("end", "author", "Nagata, Suanne", "\n "), ("start", "title", {}, {"brk": "urn:books"}), ("end", "title", "Becoming Somebody", "\n "), ("start", "genre", {}, {"brk": "urn:books"}), ("end", "genre", "Biography", "\n "), ("start", "price", {}, {"brk": "urn:books"}), ("end", "price", "33.95", "\n "), ("start", "pub_date", {}, {"brk": "urn:books"}), ("end", "pub_date", "2001-01-10", "\n "), ("start", "review", {}, {"brk": "urn:books"}), ("end", "review", "A masterpiece of the fine art of gossiping.", "\n "), ("end", "book", "\n ", "\n"), ("end", "{urn:books}books", "\n ", None), ] events_default_ns = [ ("start-ns", None, "urn:books"), ("start", "{urn:books}books", {}, {None: "urn:books"}), ("start-ns", None, ""), ("start", "book", {"id": "bk001", "lang": "en"}, {None: ""}), ("start", "author", {}, {None: ""}), ("end", "author", "Hightower, Kim", "\n "), ("start", "title", {}, {None: ""}), ("end", "title", "The First Book", "\n "), ("start", "genre", {}, {None: ""}), ("end", "genre", "Fiction", "\n "), ("start", "price", {}, {None: ""}), ("end", "price", "44.95", "\n "), ("start", "pub_date", {}, {None: ""}), ("end", "pub_date", "2000-10-01", "\n "), ("start", "review", {}, {None: ""}), ("end", "review", "An amazing story of nothing.", "\n "), ("end", "book", "\n ", "\n "), ("start-ns", None, ""), ("start", "book", {"id": "bk002", "lang": "en"}, {None: ""}), ("start", "author", {}, {None: ""}), ("end", "author", "Nagata, Suanne", "\n "), ("start", "title", {}, {None: ""}), ("end", "title", "Becoming Somebody", "\n "), ("start", "genre", {}, {None: ""}), ("end", "genre", "Biography", "\n "), ("start", "price", {}, {None: ""}), ("end", "price", "33.95", "\n "), ("start", "pub_date", {}, {None: ""}), ("end", "pub_date", "2001-01-10", "\n "), ("start", "review", {}, {None: ""}), ("end", "review", "A masterpiece of the fine art of gossiping.", "\n "), ("end", "book", "\n ", "\n"), ("end", "{urn:books}books", "\n ", None), ] tefra-xsdata-cf7afe8/tests/fixtures/books/schema.xsd000066400000000000000000000045451526437326600230200ustar00rootroot00000000000000 Το βιβλίο Book Definition Writer's name Book Title Book Genre Amount in USD Publication date Sticky Review International Standard Book Number Language ISO Code tefra-xsdata-cf7afe8/tests/fixtures/calculator/000077500000000000000000000000001526437326600220445ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/calculator/AddRQ.xml000066400000000000000000000004421526437326600235210ustar00rootroot00000000000000 1 3 tefra-xsdata-cf7afe8/tests/fixtures/calculator/AddRS.xml000066400000000000000000000006171526437326600235270ustar00rootroot00000000000000 4 tefra-xsdata-cf7afe8/tests/fixtures/calculator/__init__.py000066400000000000000000000020741526437326600241600ustar00rootroot00000000000000from tests.fixtures.calculator.services import ( Add, AddResponse, CalculatorSoapAdd, CalculatorSoapAddInput, CalculatorSoapAddOutput, CalculatorSoapDivide, CalculatorSoapDivideInput, CalculatorSoapDivideOutput, CalculatorSoapMultiply, CalculatorSoapMultiplyInput, CalculatorSoapMultiplyOutput, CalculatorSoapSubtract, CalculatorSoapSubtractInput, CalculatorSoapSubtractOutput, Divide, DivideResponse, Multiply, MultiplyResponse, Subtract, SubtractResponse, ) __all__ = [ "Add", "AddResponse", "CalculatorSoapAdd", "CalculatorSoapAddInput", "CalculatorSoapAddOutput", "CalculatorSoapDivide", "CalculatorSoapDivideInput", "CalculatorSoapDivideOutput", "CalculatorSoapMultiply", "CalculatorSoapMultiplyInput", "CalculatorSoapMultiplyOutput", "CalculatorSoapSubtract", "CalculatorSoapSubtractInput", "CalculatorSoapSubtractOutput", "Divide", "DivideResponse", "Multiply", "MultiplyResponse", "Subtract", "SubtractResponse", ] tefra-xsdata-cf7afe8/tests/fixtures/calculator/services.py000066400000000000000000000301731526437326600242450ustar00rootroot00000000000000from __future__ import annotations from dataclasses import dataclass, field __NAMESPACE__ = "http://tempuri.org/" @dataclass(kw_only=True) class Add: class Meta: namespace = "http://tempuri.org/" int_a: int = field( metadata={ "name": "intA", "type": "Element", } ) int_b: int = field( metadata={ "name": "intB", "type": "Element", } ) @dataclass(kw_only=True) class AddResponse: class Meta: namespace = "http://tempuri.org/" add_result: int = field( metadata={ "name": "AddResult", "type": "Element", } ) @dataclass(kw_only=True) class Divide: class Meta: namespace = "http://tempuri.org/" int_a: int = field( metadata={ "name": "intA", "type": "Element", } ) int_b: int = field( metadata={ "name": "intB", "type": "Element", } ) @dataclass(kw_only=True) class DivideResponse: class Meta: namespace = "http://tempuri.org/" divide_result: int = field( metadata={ "name": "DivideResult", "type": "Element", } ) @dataclass(kw_only=True) class Multiply: class Meta: namespace = "http://tempuri.org/" int_a: int = field( metadata={ "name": "intA", "type": "Element", } ) int_b: int = field( metadata={ "name": "intB", "type": "Element", } ) @dataclass(kw_only=True) class MultiplyResponse: class Meta: namespace = "http://tempuri.org/" multiply_result: int = field( metadata={ "name": "MultiplyResult", "type": "Element", } ) @dataclass(kw_only=True) class Subtract: class Meta: namespace = "http://tempuri.org/" int_a: int = field( metadata={ "name": "intA", "type": "Element", } ) int_b: int = field( metadata={ "name": "intB", "type": "Element", } ) @dataclass(kw_only=True) class SubtractResponse: class Meta: namespace = "http://tempuri.org/" subtract_result: int = field( metadata={ "name": "SubtractResult", "type": "Element", } ) @dataclass(kw_only=True) class CalculatorSoapAddInput: class Meta: name = "Envelope" namespace = "http://schemas.xmlsoap.org/soap/envelope/" body: CalculatorSoapAddInput.Body = field( metadata={ "name": "Body", "type": "Element", } ) @dataclass(kw_only=True) class Body: add: Add = field( metadata={ "name": "Add", "type": "Element", "namespace": "http://tempuri.org/", } ) @dataclass(kw_only=True) class CalculatorSoapAddOutput: class Meta: name = "Envelope" namespace = "http://schemas.xmlsoap.org/soap/envelope/" body: CalculatorSoapAddOutput.Body = field( metadata={ "name": "Body", "type": "Element", } ) @dataclass(kw_only=True) class Body: add_response: None | AddResponse = field( default=None, metadata={ "name": "AddResponse", "type": "Element", "namespace": "http://tempuri.org/", }, ) fault: None | CalculatorSoapAddOutput.Body.Fault = field( default=None, metadata={ "name": "Fault", "type": "Element", }, ) @dataclass(kw_only=True) class Fault: faultcode: str = field( metadata={ "type": "Element", "namespace": "", } ) faultstring: str = field( metadata={ "type": "Element", "namespace": "", } ) faultactor: None | str = field( default=None, metadata={ "type": "Element", "namespace": "", }, ) detail: None | str = field( default=None, metadata={ "type": "Element", "namespace": "", }, ) @dataclass(kw_only=True) class CalculatorSoapDivideInput: class Meta: name = "Envelope" namespace = "http://schemas.xmlsoap.org/soap/envelope/" body: CalculatorSoapDivideInput.Body = field( metadata={ "name": "Body", "type": "Element", } ) @dataclass(kw_only=True) class Body: divide: Divide = field( metadata={ "name": "Divide", "type": "Element", "namespace": "http://tempuri.org/", } ) @dataclass(kw_only=True) class CalculatorSoapDivideOutput: class Meta: name = "Envelope" namespace = "http://schemas.xmlsoap.org/soap/envelope/" body: CalculatorSoapDivideOutput.Body = field( metadata={ "name": "Body", "type": "Element", } ) @dataclass(kw_only=True) class Body: divide_response: None | DivideResponse = field( default=None, metadata={ "name": "DivideResponse", "type": "Element", "namespace": "http://tempuri.org/", }, ) fault: None | CalculatorSoapDivideOutput.Body.Fault = field( default=None, metadata={ "name": "Fault", "type": "Element", }, ) @dataclass(kw_only=True) class Fault: faultcode: str = field( metadata={ "type": "Element", "namespace": "", } ) faultstring: str = field( metadata={ "type": "Element", "namespace": "", } ) faultactor: None | str = field( default=None, metadata={ "type": "Element", "namespace": "", }, ) detail: None | str = field( default=None, metadata={ "type": "Element", "namespace": "", }, ) @dataclass(kw_only=True) class CalculatorSoapMultiplyInput: class Meta: name = "Envelope" namespace = "http://schemas.xmlsoap.org/soap/envelope/" body: CalculatorSoapMultiplyInput.Body = field( metadata={ "name": "Body", "type": "Element", } ) @dataclass(kw_only=True) class Body: multiply: Multiply = field( metadata={ "name": "Multiply", "type": "Element", "namespace": "http://tempuri.org/", } ) @dataclass(kw_only=True) class CalculatorSoapMultiplyOutput: class Meta: name = "Envelope" namespace = "http://schemas.xmlsoap.org/soap/envelope/" body: CalculatorSoapMultiplyOutput.Body = field( metadata={ "name": "Body", "type": "Element", } ) @dataclass(kw_only=True) class Body: multiply_response: None | MultiplyResponse = field( default=None, metadata={ "name": "MultiplyResponse", "type": "Element", "namespace": "http://tempuri.org/", }, ) fault: None | CalculatorSoapMultiplyOutput.Body.Fault = field( default=None, metadata={ "name": "Fault", "type": "Element", }, ) @dataclass(kw_only=True) class Fault: faultcode: str = field( metadata={ "type": "Element", "namespace": "", } ) faultstring: str = field( metadata={ "type": "Element", "namespace": "", } ) faultactor: None | str = field( default=None, metadata={ "type": "Element", "namespace": "", }, ) detail: None | str = field( default=None, metadata={ "type": "Element", "namespace": "", }, ) @dataclass(kw_only=True) class CalculatorSoapSubtractInput: class Meta: name = "Envelope" namespace = "http://schemas.xmlsoap.org/soap/envelope/" body: CalculatorSoapSubtractInput.Body = field( metadata={ "name": "Body", "type": "Element", } ) @dataclass(kw_only=True) class Body: subtract: Subtract = field( metadata={ "name": "Subtract", "type": "Element", "namespace": "http://tempuri.org/", } ) @dataclass(kw_only=True) class CalculatorSoapSubtractOutput: class Meta: name = "Envelope" namespace = "http://schemas.xmlsoap.org/soap/envelope/" body: CalculatorSoapSubtractOutput.Body = field( metadata={ "name": "Body", "type": "Element", } ) @dataclass(kw_only=True) class Body: subtract_response: None | SubtractResponse = field( default=None, metadata={ "name": "SubtractResponse", "type": "Element", "namespace": "http://tempuri.org/", }, ) fault: None | CalculatorSoapSubtractOutput.Body.Fault = field( default=None, metadata={ "name": "Fault", "type": "Element", }, ) @dataclass(kw_only=True) class Fault: faultcode: str = field( metadata={ "type": "Element", "namespace": "", } ) faultstring: str = field( metadata={ "type": "Element", "namespace": "", } ) faultactor: None | str = field( default=None, metadata={ "type": "Element", "namespace": "", }, ) detail: None | str = field( default=None, metadata={ "type": "Element", "namespace": "", }, ) class CalculatorSoapAdd: style = "document" location = "http://www.dneonline.com/calculator.asmx" transport = "http://schemas.xmlsoap.org/soap/http" soap_action = "http://tempuri.org/Add" input = CalculatorSoapAddInput output = CalculatorSoapAddOutput class CalculatorSoapDivide: style = "document" location = "http://www.dneonline.com/calculator.asmx" transport = "http://schemas.xmlsoap.org/soap/http" soap_action = "http://tempuri.org/Divide" input = CalculatorSoapDivideInput output = CalculatorSoapDivideOutput class CalculatorSoapMultiply: style = "document" location = "http://www.dneonline.com/calculator.asmx" transport = "http://schemas.xmlsoap.org/soap/http" soap_action = "http://tempuri.org/Multiply" input = CalculatorSoapMultiplyInput output = CalculatorSoapMultiplyOutput class CalculatorSoapSubtract: style = "document" location = "http://www.dneonline.com/calculator.asmx" transport = "http://schemas.xmlsoap.org/soap/http" soap_action = "http://tempuri.org/Subtract" input = CalculatorSoapSubtractInput output = CalculatorSoapSubtractOutput tefra-xsdata-cf7afe8/tests/fixtures/calculator/services.wsdl000066400000000000000000000173101526437326600245640ustar00rootroot00000000000000 Adds two integers. This is a test WebService. ©DNE Online tefra-xsdata-cf7afe8/tests/fixtures/compound/000077500000000000000000000000001526437326600215375ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/compound/__init__.py000066400000000000000000000002041526437326600236440ustar00rootroot00000000000000from tests.fixtures.compound.models import ( Alpha, Bravo, Root, ) __all__ = [ "Alpha", "Bravo", "Root", ] tefra-xsdata-cf7afe8/tests/fixtures/compound/models.py000066400000000000000000000023301526437326600233720ustar00rootroot00000000000000from __future__ import annotations from dataclasses import dataclass, field @dataclass(kw_only=True) class Alpha: class Meta: name = "alpha" a: bool = field( init=False, default=True, metadata={ "type": "Attribute", }, ) @dataclass(kw_only=True) class Bravo: class Meta: name = "bravo" b: bool = field( init=False, default=True, metadata={ "type": "Attribute", }, ) @dataclass(kw_only=True) class Root: class Meta: name = "root" alpha_or_bravo_or_charlie: list[Alpha | Bravo | list[str]] = field( default_factory=list, metadata={ "type": "Elements", "choices": ( { "name": "alpha", "type": Alpha, }, { "name": "bravo", "type": Bravo, }, { "name": "charlie", "type": list[str], "namespace": "", "default_factory": list, "tokens": True, }, ), }, ) tefra-xsdata-cf7afe8/tests/fixtures/compound/sample.json000066400000000000000000000005511526437326600237140ustar00rootroot00000000000000{ "alpha_or_bravo_or_charlie": [ { "a": true }, { "a": true }, { "b": true }, { "b": true }, { "a": true }, { "b": true }, { "a": true }, [ "a", "b", "c" ], { "b": true }, [ "d", "e", "f" ] ] }tefra-xsdata-cf7afe8/tests/fixtures/compound/sample.py000066400000000000000000000010601526437326600233670ustar00rootroot00000000000000from tests.fixtures.compound.models import Alpha from tests.fixtures.compound.models import Bravo from tests.fixtures.compound.models import Root obj = Root( alpha_or_bravo_or_charlie=[ Alpha( ), Alpha( ), Bravo( ), Bravo( ), Alpha( ), Bravo( ), Alpha( ), [ 'a', 'b', 'c', ], Bravo( ), [ 'd', 'e', 'f', ], ] ) tefra-xsdata-cf7afe8/tests/fixtures/compound/sample.xml000066400000000000000000000003541526437326600235440ustar00rootroot00000000000000 a b c d e f tefra-xsdata-cf7afe8/tests/fixtures/compound/sample.xsdata.xml000066400000000000000000000004141526437326600250240ustar00rootroot00000000000000 a b c d e f tefra-xsdata-cf7afe8/tests/fixtures/compound/schema.xsd000066400000000000000000000015141526437326600235200ustar00rootroot00000000000000 tefra-xsdata-cf7afe8/tests/fixtures/datatypes.py000066400000000000000000000012021526437326600222560ustar00rootroot00000000000000from collections import namedtuple from typing import Any from xsdata.exceptions import ConverterError from xsdata.formats.converter import Converter, converter Telephone = namedtuple('Telephone', ['country_code', 'area_code', 'number']) class PhoneConverter(Converter): def deserialize(self, value: Any, **kwargs: Any) -> Any: parts = value.split("-") if len(parts) == 3: return Telephone(*map(int, parts)) raise ConverterError def serialize(self, value: Telephone, **kwargs: Any) -> str: return "-".join(map(str, value)) converter.register_converter(Telephone, PhoneConverter()) tefra-xsdata-cf7afe8/tests/fixtures/docstrings/000077500000000000000000000000001526437326600220725ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/docstrings/__init__.py000066400000000000000000000000001526437326600241710ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/docstrings/accessible/000077500000000000000000000000001526437326600241675ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/docstrings/accessible/__init__.py000066400000000000000000000004371526437326600263040ustar00rootroot00000000000000from tests.fixtures.docstrings.accessible.schema import ( DoubleQuotesDescription, DoubleQuotesSummary, Root, RootB, RootD, RootEnum, ) __all__ = [ "DoubleQuotesDescription", "DoubleQuotesSummary", "Root", "RootEnum", "RootB", "RootD", ] tefra-xsdata-cf7afe8/tests/fixtures/docstrings/accessible/schema.py000066400000000000000000000052051526437326600260030ustar00rootroot00000000000000from __future__ import annotations from dataclasses import dataclass, field from enum import Enum __NAMESPACE__ = "urn:docs" @dataclass(kw_only=True) class DoubleQuotesDescription: """ Let's trip. Dont trip on quotes: "A", "B", "C", "D" My\\Ipsum. """ class Meta: namespace = "urn:docs" @dataclass(kw_only=True) class DoubleQuotesSummary: """ Dont trip on quotes: "A", "B", "C", "D" My\\Ipsum. """ class Meta: namespace = "urn:docs" class RootEnum(Enum): A = "A" B = "B" RootEnum.A.__doc__ = "Lorem ipsum dolor" RootEnum.B.__doc__ = ( "Lorem ipsum dolor '''sit''' amet, consectetur adipiscing elit. Morbi " "dapibus. My\\Ipsum" ) class RootB(Enum): YES = "Yes" NO = "No" RootB.YES.__doc__ = ( "This is an inner enum member documentation. Lorem ipsum dolor sit amet, " "consectetur adipiscing elit. Etiam mollis." ) RootB.NO.__doc__ = "Lorem ipsum dolor\nMy\\Ipsum" class RootD(Enum): TRUE = "true" FALSE = "false" @dataclass(kw_only=True) class Root: """ This is the root type documentation. '''Lorem ipsum''' dolor sit amet, consectetur adipiscing elit. Morbi dapibus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec imperdiet lacus sed sagittis scelerisque. Ut sodales metus: "sit", "amet", "lectus" My\\Ipsum. """ class Meta: namespace = "urn:docs" a: Root.A = field( metadata={ "type": "Element", "namespace": "", "doc": ( "This is an inner type '''field''' documentation.\nLorem ipsum" " dolor sit amet, consectetur adipiscing elit. Aliquam " "nec.\nMy\\Ipsum" ), } ) b: RootB = field( metadata={ "type": "Element", "namespace": "", "doc": "This is a second root type field documentation.", } ) c: RootEnum = field( metadata={ "type": "Element", "namespace": "", } ) d: RootD = field( metadata={ "type": "Element", "namespace": "", } ) @dataclass(kw_only=True) class A: """ This is an inner type documentation. """ sub_a: str = field( metadata={ "type": "Element", "namespace": "", "doc": ( "This is an inner type '''field''' documentation.\nLorem ipsum" " dolor sit amet, consectetur adipiscing elit. Vivamus " "efficitur.\nMy\\Ipsum" ), } ) tefra-xsdata-cf7afe8/tests/fixtures/docstrings/blank/000077500000000000000000000000001526437326600231615ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/docstrings/blank/__init__.py000066400000000000000000000004321526437326600252710ustar00rootroot00000000000000from tests.fixtures.docstrings.blank.schema import ( DoubleQuotesDescription, DoubleQuotesSummary, Root, RootB, RootD, RootEnum, ) __all__ = [ "DoubleQuotesDescription", "DoubleQuotesSummary", "Root", "RootEnum", "RootB", "RootD", ] tefra-xsdata-cf7afe8/tests/fixtures/docstrings/blank/schema.py000066400000000000000000000023241526437326600247740ustar00rootroot00000000000000from __future__ import annotations from dataclasses import dataclass, field from enum import Enum __NAMESPACE__ = "urn:docs" @dataclass(kw_only=True) class DoubleQuotesDescription: class Meta: namespace = "urn:docs" @dataclass(kw_only=True) class DoubleQuotesSummary: class Meta: namespace = "urn:docs" class RootEnum(Enum): A = "A" B = "B" class RootB(Enum): YES = "Yes" NO = "No" class RootD(Enum): TRUE = "true" FALSE = "false" @dataclass(kw_only=True) class Root: class Meta: namespace = "urn:docs" a: Root.A = field( metadata={ "type": "Element", "namespace": "", } ) b: RootB = field( metadata={ "type": "Element", "namespace": "", } ) c: RootEnum = field( metadata={ "type": "Element", "namespace": "", } ) d: RootD = field( metadata={ "type": "Element", "namespace": "", } ) @dataclass(kw_only=True) class A: sub_a: str = field( metadata={ "type": "Element", "namespace": "", } ) tefra-xsdata-cf7afe8/tests/fixtures/docstrings/google/000077500000000000000000000000001526437326600233465ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/docstrings/google/__init__.py000066400000000000000000000004331526437326600254570ustar00rootroot00000000000000from tests.fixtures.docstrings.google.schema import ( DoubleQuotesDescription, DoubleQuotesSummary, Root, RootB, RootD, RootEnum, ) __all__ = [ "DoubleQuotesDescription", "DoubleQuotesSummary", "Root", "RootEnum", "RootB", "RootD", ] tefra-xsdata-cf7afe8/tests/fixtures/docstrings/google/schema.py000066400000000000000000000051111526437326600251560ustar00rootroot00000000000000from __future__ import annotations from dataclasses import dataclass, field from enum import Enum __NAMESPACE__ = "urn:docs" @dataclass(kw_only=True) class DoubleQuotesDescription: """ Let's trip. Dont trip on quotes: "A", "B", "C", "D" My\\Ipsum. """ class Meta: namespace = "urn:docs" @dataclass(kw_only=True) class DoubleQuotesSummary: """ Dont trip on quotes: "A", "B", "C", "D" My\\Ipsum. """ class Meta: namespace = "urn:docs" class RootEnum(Enum): """ Attributes: A: Lorem ipsum dolor B: Lorem ipsum dolor '''sit''' amet, consectetur adipiscing elit. Morbi dapibus. My\\Ipsum """ A = "A" B = "B" class RootB(Enum): """ Attributes: YES: This is an inner enum member documentation. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam mollis. NO: Lorem ipsum dolor My\\Ipsum """ YES = "Yes" NO = "No" class RootD(Enum): TRUE = "true" FALSE = "false" @dataclass(kw_only=True) class Root: """ This is the root type documentation. '''Lorem ipsum''' dolor sit amet, consectetur adipiscing elit. Morbi dapibus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec imperdiet lacus sed sagittis scelerisque. Ut sodales metus: "sit", "amet", "lectus" My\\Ipsum. Attributes: a: This is an inner type '''field''' documentation. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam nec. My\\Ipsum b: This is a second root type field documentation. c: d: """ class Meta: namespace = "urn:docs" a: Root.A = field( metadata={ "type": "Element", "namespace": "", } ) b: RootB = field( metadata={ "type": "Element", "namespace": "", } ) c: RootEnum = field( metadata={ "type": "Element", "namespace": "", } ) d: RootD = field( metadata={ "type": "Element", "namespace": "", } ) @dataclass(kw_only=True) class A: """ This is an inner type documentation. Attributes: sub_a: This is an inner type '''field''' documentation. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus efficitur. My\\Ipsum """ sub_a: str = field( metadata={ "type": "Element", "namespace": "", } ) tefra-xsdata-cf7afe8/tests/fixtures/docstrings/numpy/000077500000000000000000000000001526437326600232425ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/docstrings/numpy/__init__.py000066400000000000000000000004321526437326600253520ustar00rootroot00000000000000from tests.fixtures.docstrings.numpy.schema import ( DoubleQuotesDescription, DoubleQuotesSummary, Root, RootB, RootD, RootEnum, ) __all__ = [ "DoubleQuotesDescription", "DoubleQuotesSummary", "Root", "RootEnum", "RootB", "RootD", ] tefra-xsdata-cf7afe8/tests/fixtures/docstrings/numpy/schema.py000066400000000000000000000051641526437326600250620ustar00rootroot00000000000000from __future__ import annotations from dataclasses import dataclass, field from enum import Enum __NAMESPACE__ = "urn:docs" @dataclass(kw_only=True) class DoubleQuotesDescription: """ Let's trip. Dont trip on quotes: "A", "B", "C", "D" My\\Ipsum. """ class Meta: namespace = "urn:docs" @dataclass(kw_only=True) class DoubleQuotesSummary: """ Dont trip on quotes: "A", "B", "C", "D" My\\Ipsum. """ class Meta: namespace = "urn:docs" class RootEnum(Enum): """ Properties ---------- A Lorem ipsum dolor B Lorem ipsum dolor '''sit''' amet, consectetur adipiscing elit. Morbi dapibus. My\\Ipsum """ A = "A" B = "B" class RootB(Enum): """ Properties ---------- YES This is an inner enum member documentation. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam mollis. NO Lorem ipsum dolor My\\Ipsum """ YES = "Yes" NO = "No" class RootD(Enum): TRUE = "true" FALSE = "false" @dataclass(kw_only=True) class Root: """ This is the root type documentation. '''Lorem ipsum''' dolor sit amet, consectetur adipiscing elit. Morbi dapibus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec imperdiet lacus sed sagittis scelerisque. Ut sodales metus: "sit", "amet", "lectus" My\\Ipsum. Parameters ---------- a This is an inner type '''field''' documentation. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam nec. My\\Ipsum b This is a second root type field documentation. c d """ class Meta: namespace = "urn:docs" a: Root.A = field( metadata={ "type": "Element", "namespace": "", } ) b: RootB = field( metadata={ "type": "Element", "namespace": "", } ) c: RootEnum = field( metadata={ "type": "Element", "namespace": "", } ) d: RootD = field( metadata={ "type": "Element", "namespace": "", } ) @dataclass(kw_only=True) class A: """ This is an inner type documentation. Parameters ---------- sub_a This is an inner type '''field''' documentation. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus efficitur. My\\Ipsum """ sub_a: str = field( metadata={ "type": "Element", "namespace": "", } ) tefra-xsdata-cf7afe8/tests/fixtures/docstrings/rst/000077500000000000000000000000001526437326600227025ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/docstrings/rst/__init__.py000066400000000000000000000004301526437326600250100ustar00rootroot00000000000000from tests.fixtures.docstrings.rst.schema import ( DoubleQuotesDescription, DoubleQuotesSummary, Root, RootB, RootD, RootEnum, ) __all__ = [ "DoubleQuotesDescription", "DoubleQuotesSummary", "Root", "RootEnum", "RootB", "RootD", ] tefra-xsdata-cf7afe8/tests/fixtures/docstrings/rst/schema.py000066400000000000000000000047771526437326600245330ustar00rootroot00000000000000from __future__ import annotations from dataclasses import dataclass, field from enum import Enum __NAMESPACE__ = "urn:docs" @dataclass(kw_only=True) class DoubleQuotesDescription: """ Let's trip. Dont trip on quotes: "A", "B", "C", "D" My\\Ipsum. """ class Meta: namespace = "urn:docs" @dataclass(kw_only=True) class DoubleQuotesSummary: """ Dont trip on quotes: "A", "B", "C", "D" My\\Ipsum. """ class Meta: namespace = "urn:docs" class RootEnum(Enum): """ :cvar A: Lorem ipsum dolor :cvar B: Lorem ipsum dolor '''sit''' amet, consectetur adipiscing elit. Morbi dapibus. My\\Ipsum """ A = "A" B = "B" class RootB(Enum): """ :cvar YES: This is an inner enum member documentation. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam mollis. :cvar NO: Lorem ipsum dolor My\\Ipsum """ YES = "Yes" NO = "No" class RootD(Enum): TRUE = "true" FALSE = "false" @dataclass(kw_only=True) class Root: """ This is the root type documentation. '''Lorem ipsum''' dolor sit amet, consectetur adipiscing elit. Morbi dapibus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec imperdiet lacus sed sagittis scelerisque. Ut sodales metus: "sit", "amet", "lectus" My\\Ipsum. :ivar a: This is an inner type '''field''' documentation. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam nec. My\\Ipsum :ivar b: This is a second root type field documentation. :ivar c: :ivar d: """ class Meta: namespace = "urn:docs" a: Root.A = field( metadata={ "type": "Element", "namespace": "", } ) b: RootB = field( metadata={ "type": "Element", "namespace": "", } ) c: RootEnum = field( metadata={ "type": "Element", "namespace": "", } ) d: RootD = field( metadata={ "type": "Element", "namespace": "", } ) @dataclass(kw_only=True) class A: """ This is an inner type documentation. :ivar sub_a: This is an inner type '''field''' documentation. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus efficitur. My\\Ipsum """ sub_a: str = field( metadata={ "type": "Element", "namespace": "", } ) tefra-xsdata-cf7afe8/tests/fixtures/docstrings/schema.xsd000066400000000000000000000077611526437326600240650ustar00rootroot00000000000000 a b This is the root type documentation. """Lorem ipsum""" dolor sit amet, consectetur adipiscing elit. Morbi dapibus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec imperdiet lacus sed sagittis scelerisque. Ut sodales metus: "sit", "amet", "lectus" My\Ipsum This is an inner type """field""" documentation. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam nec. My\Ipsum This is an inner type documentation. This is an inner type '''field''' documentation. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus efficitur. My\Ipsum This is a second root type field documentation. This is an inner enum member documentation. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam mollis. Lorem ipsum dolor My\Ipsum Dont trip on quotes: "A", "B", "C", "D" My\Ipsum Let's trip. Dont trip on quotes: "A", "B", "C", "D" My\Ipsum Lorem ipsum dolor Lorem ipsum dolor """sit""" amet, consectetur adipiscing elit. Morbi dapibus. My\Ipsum tefra-xsdata-cf7afe8/tests/fixtures/dtd/000077500000000000000000000000001526437326600204665ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/dtd/__init__.py000066400000000000000000000000171526437326600225750ustar00rootroot00000000000000# nothing here tefra-xsdata-cf7afe8/tests/fixtures/dtd/complete_example.dtd000066400000000000000000000006201526437326600245040ustar00rootroot00000000000000 tefra-xsdata-cf7afe8/tests/fixtures/dtd/default_namespace.dtd000066400000000000000000000001611526437326600246210ustar00rootroot00000000000000 tefra-xsdata-cf7afe8/tests/fixtures/dtd/models/000077500000000000000000000000001526437326600217515ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/dtd/models/__init__.py000066400000000000000000000004441526437326600240640ustar00rootroot00000000000000from tests.fixtures.dtd.models.complete_example import ( Blog, Body, Origin, Post, PostStatus, Source, Tag, Tags, Title, ) __all__ = [ "Blog", "Body", "Origin", "Post", "PostStatus", "Source", "Tag", "Tags", "Title", ] tefra-xsdata-cf7afe8/tests/fixtures/dtd/models/complete_example.py000066400000000000000000000043031526437326600256460ustar00rootroot00000000000000from __future__ import annotations from dataclasses import dataclass, field from enum import Enum @dataclass(kw_only=True) class Body: value: str = field(default="") @dataclass(kw_only=True) class Origin: value: str = field(default="") class PostStatus(Enum): DRAFT = "draft" PUBLISHED = "published" @dataclass(kw_only=True) class Source: value: str = field(default="") @dataclass(kw_only=True) class Tag: value: str = field(default="") @dataclass(kw_only=True) class Title: value: str = field(default="") @dataclass(kw_only=True) class Tags: tag: list[Tag] = field( default_factory=list, metadata={ "name": "Tag", "type": "Element", }, ) @dataclass(kw_only=True) class Post: status: PostStatus = field( default=PostStatus.DRAFT, metadata={ "type": "Attribute", "required": True, }, ) lang: str = field( default="en", metadata={ "type": "Attribute", "namespace": "http://www.w3.org/XML/1998/namespace", "required": True, }, ) created_at: str = field( metadata={ "type": "Attribute", } ) author: str = field( metadata={ "type": "Attribute", } ) origin: list[Origin] = field( default_factory=list, metadata={ "name": "Origin", "type": "Element", }, ) source: list[Source] = field( default_factory=list, metadata={ "name": "Source", "type": "Element", }, ) title: Title = field( metadata={ "name": "Title", "type": "Element", } ) body: Body = field( metadata={ "name": "Body", "type": "Element", } ) tags: Tags = field( metadata={ "name": "Tags", "type": "Element", } ) @dataclass(kw_only=True) class Blog: post: list[Post] = field( default_factory=list, metadata={ "name": "Post", "type": "Element", "min_occurs": 1, }, ) tefra-xsdata-cf7afe8/tests/fixtures/dtd/prefix_namespace.dtd000066400000000000000000000002031526437326600244670ustar00rootroot00000000000000 tefra-xsdata-cf7afe8/tests/fixtures/hello/000077500000000000000000000000001526437326600210165ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/hello/HelloRQ.xml000066400000000000000000000004251526437326600230470ustar00rootroot00000000000000 chris tefra-xsdata-cf7afe8/tests/fixtures/hello/HelloRS.xml000066400000000000000000000004231526437326600230470ustar00rootroot00000000000000 Hello chris tefra-xsdata-cf7afe8/tests/fixtures/hello/HelloRS_SoapFault.xml000066400000000000000000000007511526437326600250310ustar00rootroot00000000000000 S:Server foobar foobar tefra-xsdata-cf7afe8/tests/fixtures/hello/__init__.py000066400000000000000000000006561526437326600231360ustar00rootroot00000000000000from tests.fixtures.hello.hello import ( GetHelloAsString, GetHelloAsStringResponse, HelloByeError, HelloError, HelloGetHelloAsString, HelloGetHelloAsStringInput, HelloGetHelloAsStringOutput, ) __all__ = [ "HelloByeError", "HelloError", "HelloGetHelloAsString", "HelloGetHelloAsStringInput", "HelloGetHelloAsStringOutput", "GetHelloAsString", "GetHelloAsStringResponse", ] tefra-xsdata-cf7afe8/tests/fixtures/hello/hello.py000066400000000000000000000105431526437326600224760ustar00rootroot00000000000000from __future__ import annotations from dataclasses import dataclass, field __NAMESPACE__ = "http://hello/" @dataclass(kw_only=True) class HelloByeError: class Meta: namespace = "http://hello/" message: None | str = field( default=None, metadata={ "type": "Element", "namespace": "", }, ) @dataclass(kw_only=True) class HelloError: class Meta: namespace = "http://hello/" message: None | str = field( default=None, metadata={ "type": "Element", "namespace": "", }, ) @dataclass(kw_only=True) class GetHelloAsString: class Meta: name = "getHelloAsString" namespace = "http://hello/" arg0: str = field( metadata={ "type": "Element", "namespace": "", } ) @dataclass(kw_only=True) class GetHelloAsStringResponse: class Meta: name = "getHelloAsStringResponse" namespace = "http://hello/" return_value: str = field( metadata={ "name": "return", "type": "Element", "namespace": "", } ) @dataclass(kw_only=True) class HelloGetHelloAsStringInput: class Meta: name = "Envelope" namespace = "http://schemas.xmlsoap.org/soap/envelope/" body: HelloGetHelloAsStringInput.Body = field( metadata={ "name": "Body", "type": "Element", } ) @dataclass(kw_only=True) class Body: get_hello_as_string: GetHelloAsString = field( metadata={ "name": "getHelloAsString", "type": "Element", "namespace": "http://hello/", } ) @dataclass(kw_only=True) class HelloGetHelloAsStringOutput: class Meta: name = "Envelope" namespace = "http://schemas.xmlsoap.org/soap/envelope/" body: HelloGetHelloAsStringOutput.Body = field( metadata={ "name": "Body", "type": "Element", } ) @dataclass(kw_only=True) class Body: get_hello_as_string_response: None | GetHelloAsStringResponse = field( default=None, metadata={ "name": "getHelloAsStringResponse", "type": "Element", "namespace": "http://hello/", }, ) fault: None | HelloGetHelloAsStringOutput.Body.Fault = field( default=None, metadata={ "name": "Fault", "type": "Element", }, ) @dataclass(kw_only=True) class Fault: faultcode: str = field( metadata={ "type": "Element", "namespace": "", } ) faultstring: str = field( metadata={ "type": "Element", "namespace": "", } ) faultactor: None | str = field( default=None, metadata={ "type": "Element", "namespace": "", }, ) detail: None | HelloGetHelloAsStringOutput.Body.Fault.Detail = ( field( default=None, metadata={ "type": "Element", "namespace": "", }, ) ) @dataclass(kw_only=True) class Detail: hello_error: None | HelloError = field( default=None, metadata={ "name": "HelloError", "type": "Element", "namespace": "http://hello/", }, ) hello_bye_error: None | HelloByeError = field( default=None, metadata={ "name": "HelloByeError", "type": "Element", "namespace": "http://hello/", }, ) class HelloGetHelloAsString: style = "rpc" location = "http://localhost:9999/ws/hello" transport = "http://schemas.xmlsoap.org/soap/http" input = HelloGetHelloAsStringInput output = HelloGetHelloAsStringOutput tefra-xsdata-cf7afe8/tests/fixtures/hello/hello.wsdl000066400000000000000000000060141526437326600230150ustar00rootroot00000000000000 tefra-xsdata-cf7afe8/tests/fixtures/hello/hello.xsd000066400000000000000000000014151526437326600226420ustar00rootroot00000000000000 tefra-xsdata-cf7afe8/tests/fixtures/models.py000066400000000000000000000111231526437326600215460ustar00rootroot00000000000000from dataclasses import dataclass, field from decimal import Decimal from typing import Any, Union, Optional from xml.etree.ElementTree import QName __NAMESPACE__ = "xsdata" @dataclass class TypeA: x: int @dataclass class TypeB: x: int y: str skip: Optional[str] = field(default=None, metadata={"type": "Ignore"}) @dataclass class TypeC: x: int y: str z: float fixed: str = field(init=False, default="ignored") restricted: Any = field(init=False, metadata={"type": "Ignore"}) @dataclass class TypeD: x: int y: str z: Optional[bool] @dataclass class TypeDuplicate: x: int x1: str = field(metadata={"name": "x"}) @dataclass class BaseA: x: str @dataclass class BaseB(BaseA): y: str @dataclass class BaseC(BaseB): z: str @dataclass class NillableType: class Meta: nillable = True value: Optional[str] = field(default="abc") @dataclass class FixedType: value: str = field(init=False, default="abc") @dataclass class ExtendedType: a: Optional[TypeA] = field(default=None) any: Optional[object] = field(default=None) wildcard: Optional[object] = field(default=None, metadata={"type": "Wildcard"}) @dataclass class ExtendedListType: wildcard: list[object] = field(default_factory=list, metadata={"type": "Wildcard"}) @dataclass class ChoiceType: choice: list[object] = field( metadata={ "type": "Elements", "choices": ( {"name": "a", "type": TypeA}, {"name": "b", "type": TypeB}, {"name": "int", "type": int}, {"name": "float", "type": float}, {"name": "qname", "type": QName}, {"name": "union", "type": type["UnionType"], "namespace": "foo"}, {"name": "tokens", "type": list[Decimal], "tokens": True, "default_factory": list}, { "wildcard": True, "type": object, "namespace": "http://www.w3.org/1999/xhtml", }, ), } ) @dataclass class OptionalChoiceType: a_or_b: Optional[object] = field( metadata={ "type": "Elements", "choices": ( {"name": "a", "type": TypeA}, {"name": "b", "type": TypeB}, ), } ) @dataclass class AmbiguousChoiceType: choice: int = field( metadata={ "type": "Elements", "choices": ( {"name": "a", "type": int}, {"name": "b", "type": int}, ), } ) @dataclass class UnionType: element: Union[TypeA, TypeB, TypeC, TypeD] @dataclass class BaseType: element: BaseA @dataclass class AttrsType: index: int = field(metadata={"type": "Attribute"}) attrs: dict[str, str] = field(metadata={"type": "Attributes", "namespace": "##any"}) fixed: str = field(init=False, default="ignored", metadata={"type": "Attribute"}) @dataclass class SequentialType: a0: Optional[str] = field(default=None, metadata={"type": "Attribute"}) a1: dict[str, str] = field(default_factory=dict, metadata={"type": "Attributes"}) a2: list[str] = field( default_factory=list, metadata={"type": "Attribute", "tokens": True} ) x0: Optional[int] = field(default=None) x1: list[int] = field( default_factory=list, metadata={"type": "Element", "sequence": 1} ) x2: list[int] = field( default_factory=list, metadata={"type": "Element", "sequence": 1} ) x3: list[int] = field( default_factory=list, metadata={"type": "Element", "sequence": 2} ) x4: Optional[int] = field( default=None, metadata={"type": "Element", "sequence": 2} ) x5: Optional[str] = field(default=None, metadata={"type": "Element", "nillable": True}) @dataclass class Span: class Meta: name = "span" content: str @dataclass class Paragraph: class Meta: name = "p" content: list[object] = field( default_factory=list, metadata={ "type": "Wildcard", "namespace": "##any", "mixed": True, "choices": ( {"name": "span", "type": Span}, ), } ) @dataclass class Parent: class Meta: global_type = False @dataclass class Inner: pass @dataclass class TypeNS2: class Meta: namespace = "ns2" x1: int = field(metadata={"type": "Element"}) @dataclass class TypeNS1(TypeNS2): class Meta: namespace = "ns1" x2: int = field(metadata={"type": "Element"})tefra-xsdata-cf7afe8/tests/fixtures/primer/000077500000000000000000000000001526437326600212115ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/primer/__init__.py000066400000000000000000000003471526437326600233260ustar00rootroot00000000000000from tests.fixtures.primer.order import ( Comment, Items, PurchaseOrder, PurchaseOrderType, Usaddress, ) __all__ = [ "Items", "PurchaseOrderType", "Usaddress", "Comment", "PurchaseOrder", ] tefra-xsdata-cf7afe8/tests/fixtures/primer/order.py000066400000000000000000000073761526437326600227130ustar00rootroot00000000000000from __future__ import annotations from dataclasses import dataclass, field from decimal import Decimal from xsdata.models.datatype import XmlDate @dataclass(kw_only=True) class Usaddress: class Meta: name = "USAddress" name: str = field( metadata={ "type": "Element", "namespace": "", } ) street: str = field( metadata={ "type": "Element", "namespace": "", } ) city: str = field( metadata={ "type": "Element", "namespace": "", } ) state: str = field( metadata={ "type": "Element", "namespace": "", } ) zip: Decimal = field( metadata={ "type": "Element", "namespace": "", } ) country: str = field( init=False, default="US", metadata={ "type": "Attribute", }, ) @dataclass(kw_only=True) class Comment: class Meta: name = "comment" value: str = field(default="") @dataclass(kw_only=True) class Items: item: list[Items.Item] = field( default_factory=list, metadata={ "type": "Element", "namespace": "", }, ) @dataclass(kw_only=True) class Item: """ Parameters ---------- product_name quantity usprice Price amount in USD comment ship_date part_num Stock Keeping Unit """ product_name: str = field( metadata={ "name": "productName", "type": "Element", "namespace": "", } ) quantity: int = field( metadata={ "type": "Element", "namespace": "", "max_exclusive": 100, } ) usprice: Decimal = field( metadata={ "name": "USPrice", "type": "Element", "namespace": "", } ) comment: None | Comment = field( default=None, metadata={ "type": "Element", }, ) ship_date: None | XmlDate = field( default=None, metadata={ "name": "shipDate", "type": "Element", "namespace": "", }, ) part_num: str = field( metadata={ "name": "partNum", "type": "Attribute", "pattern": r"\d{3}-[A-Z]{2}", } ) @dataclass(kw_only=True) class PurchaseOrderType: """ Purchase order schema for Example.com. Copyright 2000 Example.com. All rights reserved. Parameters ---------- ship_to Shipping Address bill_to Billing Address comment items order_date """ ship_to: Usaddress = field( metadata={ "name": "shipTo", "type": "Element", "namespace": "", } ) bill_to: Usaddress = field( metadata={ "name": "billTo", "type": "Element", "namespace": "", } ) comment: None | Comment = field( default=None, metadata={ "type": "Element", }, ) items: Items = field( metadata={ "type": "Element", "namespace": "", } ) order_date: None | XmlDate = field( default=None, metadata={ "name": "orderDate", "type": "Attribute", }, ) @dataclass(kw_only=True) class PurchaseOrder(PurchaseOrderType): class Meta: name = "purchaseOrder" tefra-xsdata-cf7afe8/tests/fixtures/primer/order.xsd000066400000000000000000000057301526437326600230510ustar00rootroot00000000000000 Purchase order schema for Example.com. Copyright 2000 Example.com. All rights reserved. Shipping Address Billing Address Price amount in USD Stock Keeping Unit tefra-xsdata-cf7afe8/tests/fixtures/primer/sample.json000066400000000000000000000015721526437326600233720ustar00rootroot00000000000000{ "shipTo": { "name": "Alice Smith", "street": "123 Maple Street", "city": "Mill Valley", "state": "CA", "zip": "90952", "country": "US" }, "billTo": { "name": "Robert Smith", "street": "8 Oak Avenue", "city": "Old Town", "state": "PA", "zip": "95819", "country": "US" }, "comment": { "value": "Hurry, my lawn is going wild!" }, "items": { "item": [ { "productName": "Lawnmower", "quantity": 1, "USPrice": "148.95", "comment": { "value": "Confirm this is electric" }, "shipDate": null, "partNum": "872-AA" }, { "productName": "Baby Monitor", "quantity": 1, "USPrice": "39.98", "comment": null, "shipDate": "1999-05-21", "partNum": "926-AA" } ] }, "orderDate": "1999-10-20" }tefra-xsdata-cf7afe8/tests/fixtures/primer/sample.py000066400000000000000000000024431526437326600230470ustar00rootroot00000000000000from decimal import Decimal from tests.fixtures.primer.order import Comment from tests.fixtures.primer.order import Items from tests.fixtures.primer.order import PurchaseOrder from tests.fixtures.primer.order import Usaddress from xsdata.models.datatype import XmlDate obj = PurchaseOrder( ship_to=Usaddress( name='Alice Smith', street='123 Maple Street', city='Mill Valley', state='CA', zip=Decimal('90952') ), bill_to=Usaddress( name='Robert Smith', street='8 Oak Avenue', city='Old Town', state='PA', zip=Decimal('95819') ), comment=Comment( value='Hurry, my lawn is going wild!' ), items=Items( item=[ Items.Item( product_name='Lawnmower', quantity=1, usprice=Decimal('148.95'), comment=Comment( value='Confirm this is electric' ), part_num='872-AA' ), Items.Item( product_name='Baby Monitor', quantity=1, usprice=Decimal('39.98'), ship_date=XmlDate(1999, 5, 21), part_num='926-AA' ), ] ), order_date=XmlDate(1999, 10, 20) ) tefra-xsdata-cf7afe8/tests/fixtures/primer/sample.xml000066400000000000000000000015611526437326600232170ustar00rootroot00000000000000 Alice Smith 123 Maple Street Mill Valley CA 90952 Robert Smith 8 Oak Avenue Old Town PA 95819 Hurry, my lawn is going wild! Lawnmower 1 148.95 Confirm this is electric Baby Monitor 1 39.98 1999-05-21 tefra-xsdata-cf7afe8/tests/fixtures/primer/sample.xsdata.xml000066400000000000000000000015711526437326600245030ustar00rootroot00000000000000 Alice Smith 123 Maple Street Mill Valley CA 90952 Robert Smith 8 Oak Avenue Old Town PA 95819 Hurry, my lawn is going wild! Lawnmower 1 148.95 Confirm this is electric Baby Monitor 1 39.98 1999-05-21 tefra-xsdata-cf7afe8/tests/fixtures/series/000077500000000000000000000000001526437326600212055ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/series/__init__.py000066400000000000000000000005301526437326600233140ustar00rootroot00000000000000from tests.fixtures.series.series import ( Country, Externals, Image, Links, Network, Previousepisode, Rating, Schedule, Self, Series, ) __all__ = [ "Links", "Country", "Externals", "Image", "Network", "Previousepisode", "Rating", "Schedule", "Self", "Series", ] tefra-xsdata-cf7afe8/tests/fixtures/series/samples/000077500000000000000000000000001526437326600226515ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/series/samples/show1.json000066400000000000000000000023571526437326600246140ustar00rootroot00000000000000{ "id": 2, "name": "Person of Interest", "type": "Scripted", "language": "English", "genres": [ "Action", "Crime", "Science-Fiction" ], "status": "Ended", "runtime": 60, "premiered": "2011-09-22", "officialSite": "http://www.cbs.com/shows/person_of_interest/", "schedule": { "time": "22:00", "days": [ "Tuesday" ] }, "rating": { "average": 8.9 }, "network": { "id": 2, "name": "CBS", "country": { "name": "United States", "code": "US", "timezone": "America/New_York" } }, "webChannel": null, "externals": { "tvrage": 28376, "imdb": "tt1839578" }, "summary": "

You are being watched. The government has a secret system, a machine that spies on you every hour of every day. I know because I built it. I designed the Machine to detect acts of terror but it sees everything. Violent crimes involving ordinary people. People like you. Crimes the government considered \"irrelevant\". They wouldn't act so I decided I would. But I needed a partner. Someone with the skills to intervene. Hunted by the authorities, we work in secret. You'll never find us. But victim or perpetrator, if your number is up, we'll find you.

", "updated": 1588773151 }tefra-xsdata-cf7afe8/tests/fixtures/series/samples/show2.json000066400000000000000000000023761526437326600246160ustar00rootroot00000000000000{ "id": 5, "url": "https://www.tvmaze.com/shows/5/true-detective", "name": "True Detective", "type": "Scripted", "language": "English", "genres": [ "Drama", "Crime", "Thriller" ], "status": "Running", "runtime": 60, "premiered": "2014-01-12", "officialSite": "http://www.hbo.com/true-detective", "schedule": { "time": "21:00", "days": [ "Sunday" ] }, "rating": { "average": 8.3 }, "weight": 97, "network": { "name": "HBO", "country": { "name": "United States", "code": "US" } }, "externals": { "tvrage": 31369, "thetvdb": 270633, "imdb": "tt2356777" }, "image": { "medium": "https://static.tvmaze.com/uploads/images/medium_portrait/178/445621.jpg", "original": "https://static.tvmaze.com/uploads/images/original_untouched/178/445621.jpg" }, "summary": "

Touch darkness and darkness touches you back. True Detective centers on troubled cops and the investigations that drive them to the edge. Each season features a new cast and a new case.

", "updated": 1614762066, "_links": { "self": { "href": "https://api.tvmaze.com/shows/5" }, "previousepisode": { "href": "https://api.tvmaze.com/episodes/1590271" } } }tefra-xsdata-cf7afe8/tests/fixtures/series/series.py000066400000000000000000000116331526437326600230550ustar00rootroot00000000000000from __future__ import annotations from dataclasses import dataclass, field from xsdata.models.datatype import XmlDate @dataclass(kw_only=True) class Country: class Meta: name = "country" name: str = field( metadata={ "type": "Element", } ) code: str = field( metadata={ "type": "Element", } ) timezone: None | str = field( default=None, metadata={ "type": "Element", }, ) @dataclass(kw_only=True) class Externals: class Meta: name = "externals" tvrage: int = field( metadata={ "type": "Element", } ) thetvdb: None | int = field( default=None, metadata={ "type": "Element", }, ) imdb: str = field( metadata={ "type": "Element", } ) @dataclass(kw_only=True) class Image: class Meta: name = "image" medium: str = field( metadata={ "type": "Element", } ) original: str = field( metadata={ "type": "Element", } ) @dataclass(kw_only=True) class Previousepisode: class Meta: name = "previousepisode" href: str = field( metadata={ "type": "Element", } ) @dataclass(kw_only=True) class Rating: class Meta: name = "rating" average: float = field( metadata={ "type": "Element", } ) @dataclass(kw_only=True) class Schedule: class Meta: name = "schedule" time: str = field( metadata={ "type": "Element", } ) days: list[str] = field( default_factory=list, metadata={ "type": "Element", "min_occurs": 1, }, ) @dataclass(kw_only=True) class Self: class Meta: name = "self" href: str = field( metadata={ "type": "Element", } ) @dataclass(kw_only=True) class Links: class Meta: name = "_links" self_value: Self = field( metadata={ "name": "self", "type": "Element", } ) previousepisode: Previousepisode = field( metadata={ "type": "Element", } ) @dataclass(kw_only=True) class Network: class Meta: name = "network" id: None | int = field( default=None, metadata={ "type": "Element", }, ) name: str = field( metadata={ "type": "Element", } ) country: Country = field( metadata={ "type": "Element", } ) @dataclass(kw_only=True) class Series: class Meta: name = "series" id: int = field( metadata={ "type": "Element", } ) url: None | str = field( default=None, metadata={ "type": "Element", }, ) name: str = field( metadata={ "type": "Element", } ) type_value: str = field( metadata={ "name": "type", "type": "Element", } ) language: str = field( metadata={ "type": "Element", } ) genres: list[str] = field( default_factory=list, metadata={ "type": "Element", "min_occurs": 1, }, ) status: str = field( metadata={ "type": "Element", } ) runtime: int = field( metadata={ "type": "Element", } ) premiered: XmlDate = field( metadata={ "type": "Element", } ) official_site: str = field( metadata={ "name": "officialSite", "type": "Element", } ) schedule: Schedule = field( metadata={ "type": "Element", } ) rating: Rating = field( metadata={ "type": "Element", } ) weight: None | int = field( default=None, metadata={ "type": "Element", }, ) network: Network = field( metadata={ "type": "Element", } ) web_channel: None | object = field( default=None, metadata={ "name": "webChannel", "type": "Element", }, ) externals: Externals = field( metadata={ "type": "Element", } ) image: None | Image = field( default=None, metadata={ "type": "Element", }, ) summary: str = field( metadata={ "type": "Element", } ) updated: int = field( metadata={ "type": "Element", } ) links: None | Links = field( default=None, metadata={ "name": "_links", "type": "Element", }, ) tefra-xsdata-cf7afe8/tests/fixtures/stripe/000077500000000000000000000000001526437326600212215ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/stripe/.xsdata.xml000066400000000000000000000016431526437326600233110ustar00rootroot00000000000000 tests.fixtures.stripe.models.balance dataclasses single-package reStructuredText true false tefra-xsdata-cf7afe8/tests/fixtures/stripe/__init__.py000066400000000000000000000000001526437326600233200ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/stripe/models/000077500000000000000000000000001526437326600225045ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/stripe/models/__init__.py000066400000000000000000000003201526437326600246100ustar00rootroot00000000000000from .balance import ( Available, Balance, ConnectReserved, Pending, SourceTypes, ) __all__ = [ "Available", "Balance", "ConnectReserved", "Pending", "SourceTypes", ] tefra-xsdata-cf7afe8/tests/fixtures/stripe/models/balance.py000066400000000000000000000044551526437326600244530ustar00rootroot00000000000000from __future__ import annotations from dataclasses import dataclass, field @dataclass(order=True, frozen=True, kw_only=True) class ConnectReserved: class Meta: name = "connect_reserved" amount: int = field( metadata={ "type": "Element", } ) currency: str = field( metadata={ "type": "Element", } ) @dataclass(order=True, frozen=True, kw_only=True) class SourceTypes: class Meta: name = "source_types" bank_account: int = field( metadata={ "type": "Element", } ) card: int = field( metadata={ "type": "Element", } ) @dataclass(order=True, frozen=True, kw_only=True) class Available: class Meta: name = "available" amount: int = field( metadata={ "type": "Element", } ) currency: str = field( metadata={ "type": "Element", } ) source_types: SourceTypes = field( metadata={ "type": "Element", } ) @dataclass(order=True, frozen=True, kw_only=True) class Pending: class Meta: name = "pending" amount: int = field( metadata={ "type": "Element", } ) currency: str = field( metadata={ "type": "Element", } ) source_types: SourceTypes = field( metadata={ "type": "Element", } ) @dataclass(order=True, frozen=True, kw_only=True) class Balance: class Meta: name = "balance" object_value: str = field( metadata={ "name": "object", "type": "Element", } ) available: tuple[Available, ...] = field( default_factory=tuple, metadata={ "type": "Element", "min_occurs": 1, }, ) connect_reserved: tuple[ConnectReserved, ...] = field( default_factory=tuple, metadata={ "type": "Element", "min_occurs": 1, }, ) livemode: bool = field( metadata={ "type": "Element", } ) pending: tuple[Pending, ...] = field( default_factory=tuple, metadata={ "type": "Element", "min_occurs": 1, }, ) tefra-xsdata-cf7afe8/tests/fixtures/stripe/samples/000077500000000000000000000000001526437326600226655ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/stripe/samples/balance.json000066400000000000000000000051041526437326600251450ustar00rootroot00000000000000{ "object": "balance", "available": [ { "amount": 2685, "currency": "nok", "source_types": { "bank_account": 0, "card": 2685 } }, { "amount": 218420, "currency": "nzd", "source_types": { "bank_account": 0, "card": 218420 } }, { "amount": 779902, "currency": "czk", "source_types": { "bank_account": 0, "card": 779902 } }, { "amount": -1854, "currency": "aud", "source_types": { "bank_account": 0, "card": -1854 } }, { "amount": 278067892166, "currency": "usd", "source_types": { "bank_account": 280201532, "card": 277786138508 } }, { "amount": -54204, "currency": "eur", "source_types": { "bank_account": 0, "card": -54204 } }, { "amount": 2213741, "currency": "cad", "source_types": { "bank_account": 0, "card": 2213741 } }, { "amount": 7259805, "currency": "gbp", "source_types": { "bank_account": 0, "card": 7259805 } }, { "amount": -40320, "currency": "jpy", "source_types": { "bank_account": 0, "card": -40320 } }, { "amount": 12000, "currency": "brl", "source_types": { "bank_account": 0, "card": 12000 } }, { "amount": -412, "currency": "sek", "source_types": { "bank_account": 0, "card": -412 } } ], "connect_reserved": [ { "amount": 0, "currency": "nok" }, { "amount": 0, "currency": "nzd" }, { "amount": 0, "currency": "czk" }, { "amount": 0, "currency": "aud" }, { "amount": 55880, "currency": "usd" }, { "amount": 54584, "currency": "eur" }, { "amount": 0, "currency": "cad" }, { "amount": 0, "currency": "gbp" }, { "amount": 0, "currency": "jpy" }, { "amount": 0, "currency": "brl" }, { "amount": 0, "currency": "sek" } ], "livemode": false, "pending": [ { "amount": 835408472, "currency": "usd", "source_types": { "bank_account": 0, "card": 835408472 } }, { "amount": -22251, "currency": "eur", "source_types": { "bank_account": 0, "card": -22251 } } ] }tefra-xsdata-cf7afe8/tests/fixtures/submodels.py000066400000000000000000000002111526437326600222540ustar00rootroot00000000000000from dataclasses import dataclass from tests.fixtures.models import ChoiceType @dataclass class ChoiceTypeChild(ChoiceType): pass tefra-xsdata-cf7afe8/tests/fixtures/typemapping/000077500000000000000000000000001526437326600222505ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/typemapping/__init__.py000066400000000000000000000000001526437326600243470ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/typemapping/city.py000066400000000000000000000004521526437326600235730ustar00rootroot00000000000000from dataclasses import dataclass, field from typing import TYPE_CHECKING, List if TYPE_CHECKING: from tests.fixtures.typemapping.street import Street @dataclass class City: class Meta: global_type = False name: str streets: List["Street"] = field(default_factory=list) tefra-xsdata-cf7afe8/tests/fixtures/typemapping/house.py000066400000000000000000000004261526437326600237470ustar00rootroot00000000000000from dataclasses import dataclass from typing import TYPE_CHECKING, Optional if TYPE_CHECKING: from tests.fixtures.typemapping.street import Street @dataclass class House: class Meta: global_type = False number: int street: Optional["Street"] = None tefra-xsdata-cf7afe8/tests/fixtures/typemapping/street.py000066400000000000000000000006111526437326600241260ustar00rootroot00000000000000from dataclasses import dataclass, field from typing import TYPE_CHECKING, List, Optional if TYPE_CHECKING: from tests.fixtures.typemapping.city import City from tests.fixtures.typemapping.house import House @dataclass class Street: class Meta: global_type = False name: str city: Optional["City"] = None houses: List["House"] = field(default_factory=list) tefra-xsdata-cf7afe8/tests/fixtures/wrapper/000077500000000000000000000000001526437326600213735ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/fixtures/wrapper/__init__.py000066400000000000000000000003071526437326600235040ustar00rootroot00000000000000from tests.fixtures.wrapper.models import ( Alphas, Bravos, Charlie, Charlies, Wrapper, ) __all__ = [ "Alphas", "Bravos", "Charlie", "Charlies", "Wrapper", ] tefra-xsdata-cf7afe8/tests/fixtures/wrapper/models.py000066400000000000000000000033161526437326600232330ustar00rootroot00000000000000from __future__ import annotations from dataclasses import dataclass, field __NAMESPACE__ = "xsdata" @dataclass(kw_only=True) class Alphas: class Meta: name = "alphas" namespace = "xsdata" alpha: str = field( metadata={ "type": "Element", } ) @dataclass(kw_only=True) class Bravos: class Meta: name = "bravos" namespace = "xsdata" bravo: list[int] = field( default_factory=list, metadata={ "type": "Element", "min_occurs": 1, }, ) @dataclass(kw_only=True) class Charlie: class Meta: name = "charlie" namespace = "xsdata" value: str = field(default="") lang: None | object = field( default=None, metadata={ "type": "Attribute", }, ) @dataclass(kw_only=True) class Charlies: class Meta: name = "charlies" namespace = "xsdata" charlie: list[Charlie] = field( default_factory=list, metadata={ "type": "Element", "min_occurs": 1, }, ) @dataclass(kw_only=True) class Wrapper: class Meta: name = "wrapper" namespace = "xsdata" alpha: str = field( metadata={ "wrapper": "alphas", "type": "Element", } ) bravo: list[int] = field( default_factory=list, metadata={ "wrapper": "bravos", "type": "Element", "min_occurs": 1, }, ) charlie: list[Charlie] = field( default_factory=list, metadata={ "wrapper": "charlies", "type": "Element", "min_occurs": 1, }, ) tefra-xsdata-cf7afe8/tests/fixtures/wrapper/sample.json000066400000000000000000000004461526437326600235530ustar00rootroot00000000000000{ "alphas": { "alpha": "\u03b1\u03b1\u03b1" }, "bravos": { "bravo": [ 1, 2 ] }, "charlies": { "charlie": [ { "value": "\u03b4\u03b4\u03b4", "lang": "en" }, { "value": "eee", "lang": "en" } ] } }tefra-xsdata-cf7afe8/tests/fixtures/wrapper/sample.py000066400000000000000000000005411526437326600232260ustar00rootroot00000000000000from tests.fixtures.wrapper.models import Charlie from tests.fixtures.wrapper.models import Wrapper obj = Wrapper( alpha='ααα', bravo=[ 1, 2, ], charlie=[ Charlie( value='δδδ', lang='en' ), Charlie( value='eee', lang='en' ), ] ) tefra-xsdata-cf7afe8/tests/fixtures/wrapper/sample.xml000066400000000000000000000005611526437326600234000ustar00rootroot00000000000000 ααα 1 2 δδδ eee tefra-xsdata-cf7afe8/tests/fixtures/wrapper/sample.xsdata.xml000066400000000000000000000005611526437326600246630ustar00rootroot00000000000000 ααα 1 2 δδδ eee tefra-xsdata-cf7afe8/tests/fixtures/wrapper/schema.xsd000066400000000000000000000027121526437326600233550ustar00rootroot00000000000000 tefra-xsdata-cf7afe8/tests/formats/000077500000000000000000000000001526437326600175155ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/formats/__init__.py000066400000000000000000000000001526437326600216140ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/formats/dataclass/000077500000000000000000000000001526437326600214545ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/formats/dataclass/__init__.py000066400000000000000000000000001526437326600235530ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/formats/dataclass/cases/000077500000000000000000000000001526437326600225525ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/formats/dataclass/cases/__init__.py000066400000000000000000000000001526437326600246510ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/formats/dataclass/cases/attribute.py000066400000000000000000000023631526437326600251330ustar00rootroot00000000000000from collections.abc import Iterable, Sequence from typing import Dict, List, Literal, Optional, Set, Tuple, Union from xsdata.models.enums import Mode tokens = [ (int, False), (Dict[int, int], False), (Dict, False), (Literal["foo"], False), (Set[str], False), (List[Union[List[int], int]], False), (List[List[int]], False), (Tuple[int, ...], ((int,), None, tuple, False)), (Iterable[int], ((int,), None, list, False)), (Sequence[int], ((int,), None, list, False)), (List[int], ((int,), None, list, False)), (List[Union[str, int]], ((str, int), None, list, False)), (Optional[List[Union[str, int]]], ((str, int), None, list, True)), (list[int, int], False), (dict[str, str], False), (dict, False), (set[str], False), (tuple[int, ...], ((int,), None, tuple, False)), (list[int], ((int,), None, list, False)), (list[Union[str, int]], ((str, int), None, list, False)), (tuple[int | str], ((int, str), None, tuple, False)), ] not_tokens = [ (List[int], False), (Dict[int, str], False), (int, ((int,), None, None, False)), (str, ((str,), None, None, False)), (Union[str, Mode], ((str, Mode), None, None, False)), (int | str, ((int, str), None, None, False)), ] tefra-xsdata-cf7afe8/tests/formats/dataclass/cases/attributes.py000066400000000000000000000006101526437326600253070ustar00rootroot00000000000000from collections.abc import Mapping from typing import Dict, List, Set, Tuple cases = [ (int, False), (Set, False), (List, False), (Tuple, False), (Dict[str, int], False), (Dict, ((str,), dict, None, False)), (Dict[str, str], ((str,), dict, None, False)), (Mapping[str, str], ((str,), dict, None, False)), (dict[str, str], ((str,), dict, None, False)), ] tefra-xsdata-cf7afe8/tests/formats/dataclass/cases/element.py000066400000000000000000000031411526437326600245540ustar00rootroot00000000000000from collections.abc import Iterable, Sequence from typing import Dict, List, Optional, Set, Tuple, Union tokens = [ (Set, False), (Dict[str, int], False), (Tuple[str, str], False), (List[str], ((str,), None, list, False)), (Optional[List[str]], ((str,), None, list, True)), (Tuple[str, ...], ((str,), None, tuple, False)), (List[List[str]], ((str,), list, list, False)), (Optional[List[List[Union[str, int]]]], ((str, int), list, list, True)), (List[Tuple[str, ...]], ((str,), list, tuple, False)), (Iterable[Iterable[str, ...]], ((str,), list, list, False)), (Sequence[Sequence[str, ...]], ((str,), list, list, False)), (Tuple[List[str], ...], ((str,), tuple, list, False)), (Optional[Tuple[List[str], ...]], ((str,), tuple, list, True)), (list[str], ((str,), None, list, False)), (tuple[str, ...], ((str,), None, tuple, False)), (list[list[str]], ((str,), list, list, False)), (list[tuple[str, ...]], ((str,), list, tuple, False)), (tuple[list[str], ...], ((str,), tuple, list, False)), ] not_tokens = [ (Set, False), (Dict[str, int], False), (Tuple[str, int], False), (List[List[str]], False), (List[Tuple[str, ...]], False), (Tuple[List[str], ...], False), (str, ((str,), None, None, False)), (List[str], ((str,), list, None, False)), (List[Union[str, int]], ((str, int), list, None, False)), (Optional[List[Union[str, int]]], ((str, int), list, None, True)), (Tuple[str, ...], ((str,), tuple, None, False)), (list[str], ((str,), list, None, False)), (tuple[str, ...], ((str,), tuple, None, False)), ] tefra-xsdata-cf7afe8/tests/formats/dataclass/cases/elements.py000066400000000000000000000013551526437326600247440ustar00rootroot00000000000000from typing import Dict, List, Optional, Tuple, Union cases = [ (Dict, False), (str, ((object,), None, None, False)), (List[str], ((object,), list, None, False)), (Tuple[str, ...], ((object,), tuple, None, False)), (Optional[Union[str, int]], ((object,), None, None, True)), (Union[str, int, None], ((object,), None, None, True)), (List[Union[List[str], Tuple[str, ...]]], ((object,), list, None, False)), (list[str], ((object,), list, None, False)), (tuple[str, ...], ((object,), tuple, None, False)), (list[Union[list[str], tuple[str, ...]]], ((object,), list, None, False)), (str | int | None, ((object,), None, None, True)), (list[list[str] | tuple[str, ...]], ((object,), list, None, False)), ] tefra-xsdata-cf7afe8/tests/formats/dataclass/cases/wildcard.py000066400000000000000000000013331526437326600247150ustar00rootroot00000000000000from collections.abc import Iterable, Sequence from typing import Dict, List, Literal, Optional, Set, Tuple cases = [ (int, False), (Dict[int, int], False), (Dict, False), (Set, False), (Literal["foo"], False), (object, ((object,), None, None, False)), (List[object], ((object,), list, None, False)), (Tuple[object, ...], ((object,), tuple, None, False)), (Iterable[object, ...], ((object,), list, None, False)), (Sequence[object, ...], ((object,), list, None, False)), (Optional[object], ((object,), None, None, True)), (list[object], ((object,), list, None, False)), (tuple[object, ...], ((object,), tuple, None, False)), (object | None, ((object,), None, None, True)), ] tefra-xsdata-cf7afe8/tests/formats/dataclass/models/000077500000000000000000000000001526437326600227375ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/formats/dataclass/models/__init__.py000066400000000000000000000000001526437326600250360ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/formats/dataclass/models/test_builders.py000066400000000000000000000352111526437326600261630ustar00rootroot00000000000000import sys from collections.abc import Iterator from dataclasses import dataclass, field, fields, make_dataclass from decimal import Decimal from typing import get_type_hints from unittest import TestCase, mock from xml.etree.ElementTree import QName from tests.fixtures.artists import Artist from tests.fixtures.books import BookForm from tests.fixtures.models import ( AmbiguousChoiceType, ChoiceType, Parent, TypeA, TypeB, TypeNS1, UnionType, ) from tests.fixtures.series import Country from tests.fixtures.submodels import ChoiceTypeChild from xsdata.exceptions import XmlContextError from xsdata.formats.dataclass.compat import class_types from xsdata.formats.dataclass.models.builders import XmlMetaBuilder, XmlVarBuilder from xsdata.formats.dataclass.models.elements import XmlType from xsdata.models.datatype import XmlDate from xsdata.utils import text from xsdata.utils.constants import return_input from xsdata.utils.namespaces import build_qname from xsdata.utils.testing import FactoryTestCase, XmlMetaFactory, XmlVarFactory class XmlMetaBuilderTests(FactoryTestCase): def setUp(self) -> None: super().setUp() self.builder = XmlMetaBuilder( class_type=class_types.get_type("dataclasses"), element_name_generator=return_input, attribute_name_generator=return_input, ) @mock.patch.object(XmlMetaBuilder, "build_vars") def test_build(self, mock_build_vars) -> None: var = XmlVarFactory.create( xml_type=XmlType.ELEMENT, name="foo", namespaces=("foo",), types=(int,) ) mock_build_vars.return_value = [var] result = self.builder.build(Artist, None) expected = XmlMetaFactory.create( clazz=Artist, qname="{http://musicbrainz.org/ns/mmd-2.0#}artist", elements={var.qname: [var]}, ) self.assertEqual(expected, result) mock_build_vars.assert_called_once_with( Artist, "http://musicbrainz.org/ns/mmd-2.0#", return_input, return_input ) @mock.patch.object(XmlMetaBuilder, "build_vars", return_value=[]) def test_build_with_parent_namespace(self, mock_build_vars) -> None: result = self.builder.build(Country, "http://xsdata") self.assertEqual(build_qname("http://xsdata", "country"), result.qname) mock_build_vars.assert_called_once_with( Country, "http://xsdata", return_input, return_input ) @mock.patch.object(XmlMetaBuilder, "build_vars", return_value=[]) def test_build_with_no_meta_name_and_name_generator(self, *args) -> None: self.builder.element_name_generator = text.snake_case result = self.builder.build(BookForm, None) self.assertEqual("book_form", result.qname) def test_build_block_meta_inheritance(self) -> None: @dataclass class Bar: class Meta: name = "bar" @dataclass class Foo(Bar): pass @dataclass class Thug(Bar): class Meta: name = "thug" result = self.builder.build(Foo, None) self.assertEqual("Foo", result.qname) result = self.builder.build(Thug, None) self.assertEqual("thug", result.qname) def test_build_with_no_dataclass_raises_exception(self, *args) -> None: with self.assertRaises(XmlContextError) as cm: self.builder.build(int, None) self.assertEqual(f"Type '{int}' is not a dataclass.", str(cm.exception)) def test_build_locates_globalns_per_field(self) -> None: actual = self.builder.build(ChoiceTypeChild, None) self.assertEqual(1, len(actual.choices)) self.assertEqual(7, len(actual.choices[0].elements)) with self.assertRaises(XmlContextError): self.builder.find_declared_class(object, "foo") def test_build_locates_parent_namespace_per_field(self) -> None: actual = self.builder.build(TypeNS1, None) self.assertEqual(["{ns2}x1", "{ns1}x2"], list(actual.elements.keys())) def test_build_inner_type_has_no_target_qname(self) -> None: actual = self.builder.build(Parent.Inner, None) self.assertIsNone(actual.target_qname) def test_build_local_type_has_no_target_qname(self) -> None: actual = self.builder.build(Parent, None) self.assertIsNone(actual.target_qname) def test_target_namespace(self) -> None: class Meta: namespace = "bar" target_namespace = "foo" self.assertEqual("foo", self.builder.target_namespace(None, Meta)) del Meta.target_namespace self.assertEqual("bar", self.builder.target_namespace(None, Meta)) class Module: __NAMESPACE__ = "gl" self.assertEqual("gl", self.builder.target_namespace(Module, Meta)) def test_build_vars(self) -> None: result = self.builder.build_vars(BookForm, None, text.pascal_case, str.upper) self.assertIsInstance(result, Iterator) expected = [ XmlVarFactory.create( xml_type=XmlType.ELEMENT, index=1, name="author", local_name="Author", types=(str,), required=True, ), XmlVarFactory.create( xml_type=XmlType.ELEMENT, index=2, name="title", local_name="Title", types=(str,), required=True, ), XmlVarFactory.create( xml_type=XmlType.ELEMENT, index=3, name="genre", local_name="Genre", types=(str,), required=True, ), XmlVarFactory.create( xml_type=XmlType.ELEMENT, index=4, name="price", local_name="Price", types=(float,), required=True, ), XmlVarFactory.create( xml_type=XmlType.ELEMENT, index=5, name="pub_date", local_name="PubDate", types=(XmlDate,), required=True, ), XmlVarFactory.create( xml_type=XmlType.ELEMENT, index=6, name="review", local_name="Review", types=(str,), required=True, ), XmlVarFactory.create( xml_type=XmlType.ATTRIBUTE, index=7, name="id", local_name="ID", types=(str,), ), XmlVarFactory.create( xml_type=XmlType.ATTRIBUTE, index=8, name="lang", local_name="LANG", types=(str,), init=False, default="en", ), ] result = list(result) self.assertEqual(expected, result) for var in result: self.assertIsNone(var.clazz) def test_build_vars_with_ignore_types(self) -> None: result = self.builder.build_vars(TypeB, None, return_input, return_input) self.assertIsInstance(result, Iterator) actual = list(result) self.assertEqual(2, len(actual)) def test_default_xml_type(self) -> None: cls = make_dataclass("a", [("x", int)]) self.assertEqual(XmlType.TEXT, self.builder.default_xml_type(cls)) cls = make_dataclass("b", [("x", int), ("y", int)]) self.assertEqual(XmlType.ELEMENT, self.builder.default_xml_type(cls)) cls = make_dataclass( "c", [("x", int), ("y", int, field(metadata={"type": "Text"}))] ) self.assertEqual(XmlType.ELEMENT, self.builder.default_xml_type(cls)) cls = make_dataclass( "d", [("x", int), ("y", int, field(metadata={"type": "Element"}))] ) self.assertEqual(XmlType.TEXT, self.builder.default_xml_type(cls)) with self.assertRaises(XmlContextError) as cm: cls = make_dataclass( "e", [ ("x", int, field(metadata={"type": "Text"})), ("y", int, field(metadata={"type": "Text"})), ], ) self.builder.default_xml_type(cls) self.assertEqual( "Dataclass `e` includes more than one text node!", str(cm.exception) ) class XmlVarBuilderTests(TestCase): maxDiff = None def setUp(self) -> None: self.builder = XmlVarBuilder( class_type=class_types.get_type("dataclasses"), default_xml_type=XmlType.ELEMENT, element_name_generator=return_input, attribute_name_generator=return_input, ) super().setUp() def test_build_with_choice_field(self) -> None: globalns = sys.modules[ChoiceType.__module__].__dict__ type_hints = get_type_hints(ChoiceType) class_field = fields(ChoiceType)[0] actual = self.builder.build( ChoiceType, "choice", type_hints["choice"], class_field.metadata, True, "bar", list, globalns, ) expected = XmlVarFactory.create( index=1, name="choice", types=(object,), factory=list, any_type=True, default=list, xml_type=XmlType.ELEMENTS, elements={ "{bar}a": XmlVarFactory.create( index=2, name="choice", local_name="a", types=(TypeA,), clazz=TypeA, factory=list, namespaces=("bar",), ), "{bar}b": XmlVarFactory.create( index=3, name="choice", local_name="b", types=(TypeB,), clazz=TypeB, factory=list, namespaces=("bar",), ), "{bar}int": XmlVarFactory.create( index=4, name="choice", local_name="int", types=(int,), factory=list, namespaces=("bar",), ), "{bar}float": XmlVarFactory.create( index=5, name="choice", local_name="float", types=(float,), factory=list, namespaces=("bar",), ), "{bar}qname": XmlVarFactory.create( index=6, name="choice", local_name="qname", types=(QName,), factory=list, namespaces=("bar",), ), "{foo}union": XmlVarFactory.create( index=7, name="choice", local_name="union", types=(UnionType,), clazz=UnionType, factory=list, namespaces=("foo",), ), "{bar}tokens": XmlVarFactory.create( index=8, name="choice", local_name="tokens", types=(Decimal,), tokens_factory=list, derived=True, default=list, factory=list, namespaces=("bar",), ), }, wildcards=[ XmlVarFactory.create( index=9, name="choice", xml_type=XmlType.WILDCARD, local_name="any", types=(object,), factory=list, default=None, namespaces=("http://www.w3.org/1999/xhtml",), ), ], ) self.assertEqual(expected, actual) def test_build_with_ambiguous_choices(self) -> None: type_hints = get_type_hints(AmbiguousChoiceType) class_field = fields(AmbiguousChoiceType)[0] with self.assertRaises(XmlContextError) as cm: self.builder.build( AmbiguousChoiceType, "choice", type_hints["choice"], class_field.metadata, True, None, None, {}, ) self.assertEqual( "Error on AmbiguousChoiceType::choice: Compound field contains ambiguous types", str(cm.exception), ) def test_build_validates_result(self) -> None: with self.assertRaises(XmlContextError) as cm: self.builder.build( BookForm, "foo", list[int], {"type": "Attributes"}, True, None, None, None, ) self.assertEqual( "Error on BookForm::foo: Xml Attributes does not support typing `list[int]`", str(cm.exception), ) def test_resolve_namespaces(self) -> None: func = self.builder.resolve_namespaces actual = func(XmlType.ELEMENT, "foo", "bar") self.assertEqual(("foo",), actual) actual = func(XmlType.ELEMENT, "", "bar") self.assertEqual((), actual) actual = func(XmlType.ELEMENT, None, "bar") self.assertEqual(("bar",), actual) actual = func(XmlType.ATTRIBUTE, None, "bar") self.assertEqual((), actual) actual = func(XmlType.WILDCARD, None, "bar") self.assertEqual(("bar",), actual) actual = func(XmlType.WILDCARD, "##any", "bar") self.assertEqual(("##any",), actual) actual = func(XmlType.WILDCARD, "##targetNamespace", "") self.assertEqual(("##any",), actual) actual = func(XmlType.WILDCARD, "##targetNamespace", None) self.assertEqual(("##any",), actual) actual = func(XmlType.WILDCARD, "##targetNamespace", "p") self.assertEqual(("p",), actual) actual = func(XmlType.WILDCARD, "##local", "p") self.assertEqual(("",), actual) actual = func(XmlType.WILDCARD, "##other", "p") self.assertEqual(("!p",), actual) actual = func(XmlType.WILDCARD, "##other ##local", "p") self.assertEqual(("", "!p"), tuple(sorted(actual))) actual = func(XmlType.WILDCARD, "##targetNamespace foo", "p") self.assertEqual(("foo", "p"), tuple(sorted(actual))) tefra-xsdata-cf7afe8/tests/formats/dataclass/parsers/000077500000000000000000000000001526437326600231335ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/formats/dataclass/parsers/__init__.py000066400000000000000000000000001526437326600252320ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/formats/dataclass/parsers/handlers/000077500000000000000000000000001526437326600247335ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/formats/dataclass/parsers/handlers/__init__.py000066400000000000000000000000001526437326600270320ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/formats/dataclass/parsers/handlers/test_lxml.py000066400000000000000000000072671526437326600273340ustar00rootroot00000000000000import tempfile from pathlib import Path from unittest.case import TestCase from lxml import etree from tests import fixtures_dir from tests.fixtures.books import BookForm, Books from tests.fixtures.books.fixtures import books, events, events_default_ns from xsdata.exceptions import ParserError, XmlHandlerError from xsdata.formats.dataclass.parsers.bases import RecordParser from xsdata.formats.dataclass.parsers.handlers import LxmlEventHandler class LxmlEventHandlerTests(TestCase): def setUp(self) -> None: self.parser = RecordParser(handler=LxmlEventHandler) def test_parse(self) -> None: path = fixtures_dir.joinpath("books/books.xml") self.assertEqual(books, self.parser.from_path(path, Books)) self.assertEqual({"brk": "urn:books"}, self.parser.ns_map) self.assertEqual(events, self.parser.events) def test_parse_with_default_ns(self) -> None: path = fixtures_dir.joinpath("books/books_default_ns.xml") self.assertEqual(books, self.parser.from_path(path, Books)) self.assertEqual({None: "urn:books"}, self.parser.ns_map) self.assertEqual(events_default_ns, self.parser.events) def test_parse_with_element_or_tree(self) -> None: path = fixtures_dir.joinpath("books/books.xml") tree = etree.parse(str(path)) result = self.parser.parse(tree, Books) self.assertEqual(books, result) tree = etree.parse(str(path)) result = self.parser.parse(tree.find(".//book"), BookForm) self.assertEqual(books.book[0], result) def test_parse_with_xinclude(self) -> None: path = fixtures_dir.joinpath("books/books-xinclude.xml") ns_map = {"brk": "urn:books", "xi": "http://www.w3.org/2001/XInclude"} self.parser.config.process_xinclude = True self.assertEqual(books, self.parser.from_path(path, Books)) self.assertEqual(ns_map, self.parser.ns_map) def test_parse_with_xinclude_from_memory(self) -> None: path = fixtures_dir.joinpath("books/books-xinclude.xml") ns_map = {"brk": "urn:books", "xi": "http://www.w3.org/2001/XInclude"} self.parser.config.process_xinclude = True self.parser.config.base_url = path.as_uri() self.assertEqual(books, self.parser.from_bytes(path.read_bytes(), Books)) self.assertEqual(ns_map, self.parser.ns_map) def test_parse_with_external_entities(self) -> None: with tempfile.TemporaryDirectory() as tmp: secret = Path(tmp).joinpath("secret.txt") secret.write_text("leaked") xml = ( f']>' '' "Hightower, Kim&xxe;" "Fiction44.95" "2000-10-01Nothing" "" ) result = self.parser.from_string(xml, Books) self.assertEqual("", result.book[0].title) self.parser.config.resolve_entities = True result = self.parser.from_string(xml, Books) self.assertEqual("leaked", result.book[0].title) def test_parse_context_with_unhandled_event(self) -> None: handler = LxmlEventHandler(clazz=Books, parser=self.parser) with self.assertRaises(XmlHandlerError) as cm: handler.process_context([("reverse", "")], {}) self.assertEqual("Unhandled event: `reverse`.", str(cm.exception)) def test_parse_with_xml_syntax_error(self) -> None: with self.assertRaises(ParserError): self.parser.from_string("<", Books) tefra-xsdata-cf7afe8/tests/formats/dataclass/parsers/handlers/test_native.py000066400000000000000000000062631526437326600276410ustar00rootroot00000000000000import sys from unittest.case import TestCase from xml import etree import pytest from tests import fixtures_dir from tests.fixtures.books import BookForm, Books from tests.fixtures.books.fixtures import books, events, events_default_ns from xsdata.exceptions import ParserError, XmlHandlerError from xsdata.formats.dataclass.parsers.bases import RecordParser from xsdata.formats.dataclass.parsers.handlers import XmlEventHandler from xsdata.formats.dataclass.parsers.handlers.native import get_base_url class XmlEventHandlerTests(TestCase): def setUp(self) -> None: self.parser = RecordParser(handler=XmlEventHandler) def test_parse(self) -> None: path = fixtures_dir.joinpath("books/books.xml") self.assertEqual(books, self.parser.from_path(path, Books)) self.assertEqual({"brk": "urn:books"}, self.parser.ns_map) self.assertEqual(events, self.parser.events) def test_parse_with_default_ns(self) -> None: path = fixtures_dir.joinpath("books/books_default_ns.xml") self.assertEqual(books, self.parser.from_path(path, Books)) self.assertEqual({None: "urn:books"}, self.parser.ns_map) self.assertEqual(events_default_ns, self.parser.events) def test_parse_context_with_unhandled_event(self) -> None: context = [("reverse", None)] handler = XmlEventHandler(parser=self.parser, clazz=Books) with self.assertRaises(XmlHandlerError) as cm: handler.process_context(context, {}) self.assertEqual("Unhandled event: `reverse`.", str(cm.exception)) def test_parse_with_xml_syntax_error(self) -> None: with self.assertRaises(ParserError): self.parser.from_string("<", Books) def test_parse_with_element_or_tree(self) -> None: path = fixtures_dir.joinpath("books/books.xml") tree = etree.ElementTree.parse(str(path)) result = self.parser.parse(tree, Books) self.assertEqual(books, result) tree = etree.ElementTree.parse(str(path)) result = self.parser.parse(tree.find(".//book"), BookForm) self.assertEqual(books.book[0], result) @pytest.mark.skipif(sys.platform == "win32", reason="urljoin + path sep") def test_parse_with_xinclude(self) -> None: path = fixtures_dir.joinpath("books/books-xinclude.xml") ns_map = {"ns0": "urn:books"} self.parser.config.process_xinclude = True self.assertEqual(books, self.parser.parse(str(path), Books)) self.assertEqual(ns_map, self.parser.ns_map) @pytest.mark.skipif(sys.platform == "win32", reason="urljoin + path sep") def test_parse_with_xinclude_from_memory(self) -> None: path = fixtures_dir.joinpath("books/books-xinclude.xml") ns_map = {"ns0": "urn:books"} self.parser.config.process_xinclude = True self.parser.config.base_url = str(path) self.assertEqual(books, self.parser.from_string(path.read_text(), Books)) self.assertEqual(ns_map, self.parser.ns_map) def test_get_base_url(self) -> None: self.assertIsNone(get_base_url(None, None)) self.assertIsNone(get_base_url(None, None)) self.assertEqual("config/", get_base_url("config/", "/tmp/foo.xml")) tefra-xsdata-cf7afe8/tests/formats/dataclass/parsers/nodes/000077500000000000000000000000001526437326600242435ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/formats/dataclass/parsers/nodes/__init__.py000066400000000000000000000000001526437326600263420ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/formats/dataclass/parsers/nodes/test_element.py000066400000000000000000000532711526437326600273150ustar00rootroot00000000000000import copy from dataclasses import make_dataclass from unittest import mock from tests.fixtures.books import Books from tests.fixtures.models import ( AttrsType, ExtendedListType, ExtendedType, FixedType, NillableType, Paragraph, SequentialType, Span, TypeA, TypeB, TypeC, ) from xsdata.exceptions import ParserError from xsdata.formats.dataclass.context import XmlContext from xsdata.formats.dataclass.models.elements import XmlType from xsdata.formats.dataclass.models.generics import AnyElement, DerivedElement from xsdata.formats.dataclass.parsers.config import ParserConfig from xsdata.formats.dataclass.parsers.nodes import ( ElementNode, PrimitiveNode, SkipNode, StandardNode, UnionNode, WildcardNode, ) from xsdata.formats.dataclass.parsers.utils import ParserUtils from xsdata.models.enums import DataType, Namespace, QNames from xsdata.utils.testing import FactoryTestCase, XmlMetaFactory, XmlVarFactory class ElementNodeTests(FactoryTestCase): def setUp(self) -> None: super().setUp() self.context = XmlContext() self.meta = XmlMetaFactory.create(clazz=TypeC, qname="foo", wildcards=[]) self.node = ElementNode( position=0, meta=self.meta, context=self.context, config=ParserConfig(), attrs={}, ns_map={}, ) def test_bind(self) -> None: node = ElementNode( position=0, meta=self.context.build(SequentialType), context=self.context, config=ParserConfig(), attrs={"a": "b", "a0": "0"}, ns_map={"ns0": "xsdata"}, ) objects = [("x1", 1), ("x2", 2), ("x2", 3)] expected = SequentialType(a0="0", a1={"a": "b"}, x0=1, x1=[1], x2=[2, 3]) self.assertTrue(node.bind("foo", "1", "tail", objects)) self.assertListEqual(objects, [("foo", expected), (None, "tail")]) def test_bind_nil_value(self) -> None: self.node.xsi_nil = True objects = [] self.assertTrue(self.node.bind("foo", None, None, objects)) self.assertEqual(("foo", None), objects[-1]) def test_bind_nillable_type(self) -> None: self.node.meta = self.context.build(NillableType) self.node.xsi_nil = True objects = [] self.assertTrue(self.node.bind("foo", None, None, objects)) self.assertEqual(("foo", NillableType(None)), objects[-1]) def test_bind_fixed_value(self) -> None: self.node.meta = self.context.build(FixedType) objects = [] self.assertTrue(self.node.bind("foo", "abc", None, objects)) self.assertEqual(("foo", FixedType()), objects[-1]) def test_bind_with_derived_element(self) -> None: self.node.meta = self.context.build(TypeA) self.node.derived_factory = DerivedElement objects = [] self.assertTrue(self.node.bind("foo", "2", None, objects)) self.assertEqual("foo", objects[-1][0]) self.assertEqual(DerivedElement("foo", TypeA(2)), objects[-1][1]) def test_bind_with_wildcard_var(self) -> None: self.node.meta = self.context.build(ExtendedType) self.node.attrs = {"a": "b"} self.node.ns_map = {"ns0": "xsdata"} objects = [("a", "1"), ("b", "2")] expected = ExtendedType( a="1", wildcard=AnyElement( text="text", tail="tail", children=[AnyElement(qname="b", text="2")], attributes={"a": "b"}, ), ) self.assertTrue(self.node.bind("foo", "text", "tail", objects)) self.assertEqual("foo", objects[-1][0]) self.assertEqual(expected, objects[-1][1]) def test_bind_with_mixed_flag_true(self) -> None: self.node.meta = self.context.build(TypeB) self.node.attrs = {"a": "b"} self.node.ns_map = {"ns0": "xsdata"} self.node.mixed = True objects = [("x", 1), ("y", "a")] self.assertTrue(self.node.bind("foo", "text", " ", objects)) self.assertEqual(1, len(objects)) self.assertEqual(TypeB(x=1, y="a"), objects[-1][1]) objects = [("x", 1), ("y", "a")] self.assertTrue(self.node.bind("foo", "text", " tail ", objects)) self.assertEqual(2, len(objects)) self.assertEqual(None, objects[-1][0]) self.assertEqual(" tail ", objects[-1][1]) def test_bind_with_mixed_content_var(self) -> None: self.node.meta = self.context.build(Paragraph) self.node.attrs = {"a": "b"} self.node.ns_map = {"ns0": "xsdata"} objects = [("a", 1)] expected = Paragraph(content=["text", AnyElement(qname="a", text="1")]) self.assertTrue(self.node.bind("foo", "text", "tail", objects)) self.assertListEqual([("foo", expected), (None, "tail")], objects) def test_bind_wild_text(self) -> None: self.node.meta = self.context.build(ExtendedType) var = self.node.meta.wildcards[0] params = {} self.node.bind_wild_text(params, var, None, None) self.assertEqual(0, len(params)) params = {} self.node.bind_wild_text(params, var, "txt", "tail") expected = AnyElement(text="txt", tail="tail") self.assertEqual({"wildcard": expected}, params) self.node.attrs = {"a": "b"} self.node.ns_map = {"ns0": "a"} self.node.bind_wild_text(params, var, "txt", "tail") expected = AnyElement( text="txt", tail="tail", children=[expected], attributes=self.node.attrs ) self.assertEqual({"wildcard": expected}, params) self.node.meta = self.context.build(ExtendedListType) var = self.node.meta.wildcards[0] params = {} self.node.bind_wild_text(params, var, "txt", "tail") self.assertEqual({"wildcard": ["txt"]}, params) self.node.bind_wild_text(params, var, None, "tail") self.assertEqual({"wildcard": ["txt"]}, params) self.node.bind_wild_text(params, var, "first", None) self.assertEqual({"wildcard": ["first", "txt"]}, params) def test_bind_attrs(self) -> None: self.node.meta = self.context.build(AttrsType) self.node.attrs = { "index": "0", "fixed": "ignored", "{what}ever": "qname", "extended": "attr", } params = {} self.node.bind_attrs(params) expected = {"attrs": {"extended": "attr", "{what}ever": "qname"}, "index": 0} self.assertEqual(expected, params) def test_bind_attrs_with_fail_on_unknown_attributes(self) -> None: self.node.meta = self.context.build(AttrsType) self.node.config.fail_on_unknown_attributes = True self.node.attrs = { "index": "0", "fixed": "ignored", "{what}ever": "qname", "extended": "attr", } params = {} self.node.bind_attrs(params) expected = {"attrs": {"extended": "attr", "{what}ever": "qname"}, "index": 0} self.assertEqual(expected, params) def test_bind_with_fail_on_unknown_attributes(self) -> None: self.node.meta = self.context.build(ExtendedType) self.node.config.fail_on_unknown_attributes = True self.node.attrs = {"a": "b"} objects = [("a", "1")] with self.assertRaises(ParserError) as cm: self.node.bind("foo", "text", "tail", objects) self.assertEqual("Unknown attribute ExtendedType:a", str(cm.exception)) def test_bind_with_fail_on_unknown_attributes_ignores_xsi_attributes(self) -> None: self.node.meta = self.context.build(ExtendedType) self.node.config.fail_on_unknown_attributes = True self.node.attrs = {QNames.XSI_TYPE: "b"} objects = [] self.node.bind("foo", "text", "tail", objects) self.assertEqual(1, len(objects)) @mock.patch("xsdata.formats.dataclass.parsers.nodes.element.logger.warning") def test_bind_objects(self, mock_warning) -> None: self.node.meta = self.context.build(TypeC) objects = [("x", 1), ("x", 2), ("z", 3.0), ("fixed", "bar")] params = {} self.node.bind_objects(params, objects) self.assertEqual({"x": 1, "z": 3.0}, params) mock_warning.assert_called_once_with("Unassigned parsed object %s", "x") def test_bind_wild_var(self) -> None: self.node.meta = self.context.build(ExtendedType) x = make_dataclass("x", [("value", int)]) params = {} objects = [("x", x(1)), ("y", 2), ("z", 3.0)] self.node.bind_objects(params, objects) expected = { "wildcard": AnyElement( children=[ x(1), AnyElement(qname="y", text="2"), AnyElement(qname="z", text="3.0"), ] ) } self.assertEqual(expected, params) def test_bind_wild_list_var(self) -> None: self.node.meta = self.context.build(ExtendedListType) params = {} objects = [("x", 1), ("x", 2), ("z", 3.0)] self.node.bind_objects(params, objects) expected = { "wildcard": [ AnyElement(qname="x", text="1"), AnyElement(qname="x", text="2"), AnyElement(qname="z", text="3.0"), ] } self.assertEqual(expected, params) def test_prepare_generic_value(self) -> None: actual = self.node.prepare_generic_value(None, 1) self.assertEqual(1, actual) actual = self.node.prepare_generic_value("a", 1) expected = AnyElement(qname="a", text="1") self.assertEqual(expected, actual) actual = self.node.prepare_generic_value("a", "foo") expected = AnyElement(qname="a", text="foo") self.assertEqual(expected, actual) fixture = make_dataclass("Fixture", [("content", str)]) actual = self.node.prepare_generic_value("a", fixture("foo")) self.assertEqual(fixture("foo"), actual) def test_bind_tail_of_non_wildcard_attaches_to_wildcard_parent(self) -> None: self.node.meta = self.context.build(Paragraph) expected_inner_node = Span(content="This is a note inside the paragraph.") expected_first_text = "This is before the note." expected_tail = "This is after the note." expected_outer_node = Paragraph( content=[expected_first_text, expected_inner_node, expected_tail] ) objects = [] inner_node = ElementNode( position=0, meta=self.context.build(Span), context=self.context, config=ParserConfig(), attrs={}, ns_map={}, ) self.assertTrue( inner_node.bind( qname="span", text=expected_inner_node.content, tail=expected_tail, objects=objects, ) ) self.assertListEqual( objects, [("span", expected_inner_node), (None, expected_tail)] ) outer_node = ElementNode( position=0, meta=self.context.build(Paragraph), context=self.context, config=ParserConfig(), attrs={}, ns_map={}, ) self.assertTrue( outer_node.bind( qname="p", text=expected_first_text, tail=None, objects=objects, ) ) self.assertListEqual(objects, [("p", expected_outer_node)]) def test_bind_tail_of_wildcard_attaches_to_wildcard_parent(self) -> None: self.node.meta = self.context.build(Paragraph) subparagraph = Paragraph( content=["This is a subparagraph inside the paragraph."], ) objects = [ (None, "This is before the subparagraph."), ("p", subparagraph), (None, "This is after the subparagraph."), ] self.assertTrue(self.node.bind("foo", None, None, objects)) result = objects[-1][1] expected = Paragraph( content=[ "This is before the subparagraph.", subparagraph, "This is after the subparagraph.", ] ) self.assertEqual(result, expected) def test_child(self) -> None: var = XmlVarFactory.create(xml_type=XmlType.ELEMENT, name="a", types=(TypeC,)) attrs = {"a": "b"} ns_map = {"ns0": "xsdata"} position = 1 self.meta.elements[var.qname] = [var] actual = self.node.child("a", attrs, ns_map, position) self.assertIsInstance(actual, ElementNode) self.assertEqual(attrs, actual.attrs) self.assertEqual(ns_map, actual.ns_map) self.assertEqual(position, actual.position) def test_child_with_unique_element(self) -> None: single = XmlVarFactory.create( index=1, xml_type=XmlType.ELEMENT, name="cc", types=(TypeC,) ) wildcard = XmlVarFactory.create( index=2, xml_type=XmlType.WILDCARD, name="cc", types=(object,) ) self.meta.elements[single.qname] = [single] self.meta.wildcards.append(wildcard) attrs = {"a": "b"} ns_map = {"ns0": "xsdata"} position = 1 actual = self.node.child("cc", attrs, ns_map, position) self.assertIsInstance(actual, ElementNode) self.assertIn(single.index, self.node.assigned) actual = self.node.child("cc", attrs, ns_map, position) self.assertIsInstance(actual, WildcardNode) self.assertNotIn(wildcard.index, self.node.assigned) @mock.patch.object(ElementNode, "build_node") def test_child_when_failed_to_build_next_node(self, mock_build_node) -> None: mock_build_node.return_value = None element = XmlVarFactory.create(xml_type=XmlType.ELEMENT, name="a") wildcard = XmlVarFactory.create(xml_type=XmlType.WILDCARD, name="a") self.meta.elements[element.qname] = [element] self.meta.wildcards.append(wildcard) with self.assertRaises(ParserError) as cm: self.node.child("a", {}, {}, 0) self.assertEqual("Unknown property foo:a", str(cm.exception)) self.node.config.fail_on_unknown_properties = False actual = self.node.child("foobar", {}, {}, 0) self.assertIsInstance(actual, SkipNode) def test_build_node_with_dataclass_union_var(self) -> None: var = XmlVarFactory.create( xml_type=XmlType.ELEMENT, name="a", types=(TypeC, TypeB), ) attrs = {"a": "b"} ns_map = {"ns0": "xsdata"} actual = self.node.build_node(var.qname, var, attrs, ns_map, 10) self.assertIsInstance(actual, UnionNode) self.assertEqual(10, actual.position) self.assertIs(var, actual.var) self.assertEqual(attrs, actual.attrs) self.assertEqual(ns_map, actual.ns_map) self.assertEqual(0, actual.level) self.assertEqual(0, len(actual.events)) @mock.patch.object(ParserUtils, "xsi_type", return_value="foo") @mock.patch.object(XmlContext, "fetch") def test_build_node_with_dataclass_var(self, mock_ctx_fetch, mock_xsi_type) -> None: var = XmlVarFactory.create( xml_type=XmlType.ELEMENT, name="a", types=(TypeC,), ) xsi_type = "foo" namespace = self.meta.namespace mock_ctx_fetch.return_value = self.meta mock_xsi_type.return_value = xsi_type attrs = {"a": "b"} ns_map = {"ns0": "xsdata"} actual = self.node.build_node(var.qname, var, attrs, ns_map, 10) self.assertIsInstance(actual, ElementNode) self.assertEqual(10, actual.position) self.assertIs(mock_ctx_fetch.return_value, actual.meta) mock_xsi_type.assert_called_once_with(attrs, ns_map) mock_ctx_fetch.assert_called_once_with(var.clazz, namespace, xsi_type) @mock.patch.object(ParserUtils, "xsi_type", return_value="foo") @mock.patch.object(XmlContext, "fetch") def test_build_node_with_dataclass_var_and_mismatch_xsi_type( self, mock_ctx_fetch, mock_xsi_type ) -> None: var = XmlVarFactory.create( xml_type=XmlType.ELEMENT, name="a", types=(TypeB,), derived=False, ) xsi_type = "foo" namespace = self.meta.namespace mock_ctx_fetch.return_value = self.meta mock_xsi_type.return_value = xsi_type attrs = {"a": "b"} ns_map = {"ns0": "xsdata"} actual = self.node.build_node(var.qname, var, attrs, ns_map, 10) self.assertIsInstance(actual, ElementNode) self.assertEqual(10, actual.position) self.assertEqual(DerivedElement, actual.derived_factory) self.assertIs(mock_ctx_fetch.return_value, actual.meta) mock_xsi_type.assert_called_once_with(attrs, ns_map) mock_ctx_fetch.assert_called_once_with(var.clazz, namespace, xsi_type) @mock.patch.object(XmlContext, "fetch") def test_build_node_with_dataclass_var_validates_nillable( self, mock_ctx_fetch ) -> None: var = XmlVarFactory.create(xml_type=XmlType.ELEMENT, name="a", types=(TypeC,)) ns_map = {} nillable_meta = copy.deepcopy(self.meta) nillable_meta.nillable = True mock_ctx_fetch.side_effect = [self.meta, self.meta, nillable_meta] attrs = {QNames.XSI_NIL: "false"} self.assertIsNotNone(self.node.build_node(var.qname, var, attrs, ns_map, 10)) attrs = {QNames.XSI_NIL: "true"} self.assertIsNone(self.node.build_node(var.qname, var, attrs, ns_map, 10)) attrs = {QNames.XSI_NIL: "false"} self.assertIsNone(self.node.build_node(var.qname, var, attrs, ns_map, 10)) def test_build_node_with_any_type_var_with_matching_xsi_type(self) -> None: var = XmlVarFactory.create( xml_type=XmlType.ELEMENT, name="a", types=(object,), any_type=True, ) attrs = {QNames.XSI_TYPE: "bk:books"} ns_map = {"bk": "urn:books"} actual = self.node.build_node(var.qname, var, attrs, ns_map, 10) self.assertIsInstance(actual, ElementNode) self.assertEqual(10, actual.position) self.assertEqual(self.context.build(Books), actual.meta) self.assertEqual(attrs, actual.attrs) self.assertEqual(ns_map, actual.ns_map) self.assertFalse(actual.mixed) def test_build_node_with_any_type_var_with_datatype(self) -> None: var = XmlVarFactory.create( xml_type=XmlType.ELEMENT, name="a", types=(object,), any_type=True, ) attrs = {QNames.XSI_TYPE: "xs:hexBinary"} ns_map = {Namespace.XS.prefix: Namespace.XS.uri} actual = self.node.build_node(var.qname, var, attrs, ns_map, 10) self.assertIsInstance(actual, StandardNode) self.assertEqual(ns_map, actual.ns_map) self.assertEqual(DataType.HEX_BINARY, actual.datatype) self.assertIsNone(actual.derived_factory) def test_build_node_with_any_type_var_with_no_matching_xsi_type(self) -> None: var = XmlVarFactory.create( xml_type=XmlType.ELEMENT, name="a", local_name="aaa", types=(object,), any_type=True, ) attrs = {QNames.XSI_TYPE: "noMatch"} actual = self.node.build_node(var.qname, var, attrs, {}, 10) self.assertIsInstance(actual, WildcardNode) self.assertEqual(10, actual.position) self.assertEqual(var, actual.var) self.assertEqual(attrs, actual.attrs) self.assertEqual({}, actual.ns_map) def test_build_node_with_any_type_var_with_no_xsi_type(self) -> None: var = XmlVarFactory.create( xml_type=XmlType.ELEMENT, name="a", local_name="aaaa", types=(object,), any_type=True, ) attrs = {} actual = self.node.build_node(var.qname, var, attrs, {}, 10) self.assertIsInstance(actual, WildcardNode) self.assertEqual(10, actual.position) self.assertEqual(var, actual.var) self.assertEqual(attrs, actual.attrs) self.assertEqual({}, actual.ns_map) def test_build_node_with_any_type_var_with_no_xsi_type_and_type_exists( self, ) -> None: var = XmlVarFactory.create( xml_type=XmlType.ELEMENT, name="a", local_name="a", types=(object,), any_type=True, ) attrs = {} a = make_dataclass("a", [("a", int)]) actual = self.node.build_node(var.qname, var, attrs, {}, 10) self.assertIsInstance(actual, ElementNode) self.assertEqual(10, actual.position) self.assertEqual(self.context.build(a), actual.meta) self.assertEqual(attrs, actual.attrs) self.assertEqual({}, actual.ns_map) self.assertFalse(actual.mixed) def test_build_node_with_wildcard_var(self) -> None: var = XmlVarFactory.create(xml_type=XmlType.WILDCARD, local_name="aaaaa") var.process_contents = "skip" actual = self.node.build_node(var.qname, var, {}, {}, 10) self.assertIsInstance(actual, WildcardNode) self.assertEqual(10, actual.position) self.assertEqual(var, actual.var) def test_build_node_with_primitive_var(self) -> None: var = XmlVarFactory.create( xml_type=XmlType.TEXT, name="a", types=(int,), default=100 ) attrs = {"a": "b"} ns_map = {"ns0": "xsdata"} actual = self.node.build_node(var.qname, var, attrs, ns_map, 10) self.assertIsInstance(actual, PrimitiveNode) self.assertEqual(ns_map, actual.ns_map) self.assertEqual(self.meta, actual.meta) self.assertEqual(var, actual.var) tefra-xsdata-cf7afe8/tests/formats/dataclass/parsers/nodes/test_primitive.py000066400000000000000000000065701526437326600276740ustar00rootroot00000000000000from unittest import TestCase, mock from tests.fixtures.artists import Artist from xsdata.exceptions import XmlContextError from xsdata.formats.dataclass.models.elements import XmlType from xsdata.formats.dataclass.parsers.config import ParserConfig from xsdata.formats.dataclass.parsers.nodes import PrimitiveNode from xsdata.formats.dataclass.parsers.utils import ParserUtils from xsdata.utils.testing import XmlMetaFactory, XmlVarFactory class PrimitiveNodeTests(TestCase): def setUp(self) -> None: super().setUp() self.meta = XmlMetaFactory.create(clazz=Artist) self.config = ParserConfig() @mock.patch.object(ParserUtils, "parse_var") def test_bind(self, mock_parse_var) -> None: mock_parse_var.return_value = 13 var = XmlVarFactory.create( xml_type=XmlType.TEXT, name="foo", types=(int,), format="Nope" ) ns_map = {"foo": "bar"} node = PrimitiveNode(self.meta, var, ns_map, self.config) objects = [] self.assertTrue(node.bind("foo", "13", "Impossible", objects)) self.assertEqual(("foo", 13), objects[-1]) mock_parse_var.assert_called_once_with( meta=self.meta, var=var, config=self.config, value="13", ns_map=ns_map ) def test_bind_nillable_content(self) -> None: var = XmlVarFactory.create( xml_type=XmlType.TEXT, name="foo", types=(str,), nillable=False ) ns_map = {"foo": "bar"} node = PrimitiveNode(self.meta, var, ns_map, self.config) objects = [] self.assertTrue(node.bind("foo", None, None, objects)) self.assertEqual("", objects[-1][1]) var.nillable = True self.assertTrue(node.bind("foo", None, None, objects)) self.assertIsNone(objects[-1][1]) def test_bind_nillable_bytes_content(self) -> None: var = XmlVarFactory.create( xml_type=XmlType.TEXT, name="foo", types=(bytes,), nillable=False, ) ns_map = {"foo": "bar"} node = PrimitiveNode(self.meta, var, ns_map, self.config) objects = [] self.assertTrue(node.bind("foo", None, None, objects)) self.assertEqual(b"", objects[-1][1]) var.nillable = True self.assertTrue(node.bind("foo", None, None, objects)) self.assertIsNone(objects[-1][1]) def test_bind_mixed_with_tail_content(self) -> None: self.meta.mixed_content = True var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="foo", types=(int,)) node = PrimitiveNode(self.meta, var, {}, self.config) objects = [] self.assertTrue(node.bind("foo", "13", "tail", objects)) self.assertEqual((None, "tail"), objects[-1]) self.assertEqual(13, objects[-2][1]) def test_bind_mixed_without_tail_content(self) -> None: self.meta.mixed_content = True var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="foo", types=(int,)) node = PrimitiveNode(self.meta, var, {}, self.config) objects = [] self.assertTrue(node.bind("foo", "13", "", objects)) self.assertEqual(13, objects[-1][1]) def test_child(self) -> None: var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="foo") node = PrimitiveNode(self.meta, var, {}, self.config) with self.assertRaises(XmlContextError): node.child("foo", {}, {}, 0) tefra-xsdata-cf7afe8/tests/formats/dataclass/parsers/nodes/test_skip.py000066400000000000000000000006111526437326600266200ustar00rootroot00000000000000from unittest import TestCase from xsdata.formats.dataclass.parsers.nodes import SkipNode class SKipNodeTests(TestCase): def test_child(self) -> None: node = SkipNode() actual = node.child("foo", {}, {}, 1) self.assertIs(node, actual) def test_bind(self) -> None: node = SkipNode() self.assertEqual(False, node.bind("foo", None, None, [])) tefra-xsdata-cf7afe8/tests/formats/dataclass/parsers/nodes/test_standard.py000066400000000000000000000045411526437326600274600ustar00rootroot00000000000000from unittest import TestCase from tests.fixtures.artists import Artist from xsdata.exceptions import XmlContextError from xsdata.formats.dataclass.models.generics import DerivedElement from xsdata.formats.dataclass.parsers.config import ParserConfig from xsdata.formats.dataclass.parsers.nodes import StandardNode from xsdata.models.enums import DataType from xsdata.utils.testing import XmlMetaFactory, XmlVarFactory class StandardNodeTests(TestCase): def setUp(self) -> None: super().setUp() self.meta = XmlMetaFactory.create(clazz=Artist) self.var = XmlVarFactory.create() self.config = ParserConfig() def test_bind_simple(self) -> None: datatype = DataType.INT node = StandardNode( self.meta, self.var, datatype, {}, self.config, False, False ) objects = [] self.assertTrue(node.bind("a", "13", None, objects)) self.assertEqual(("a", 13), objects[-1]) def test_bind_derived(self) -> None: datatype = DataType.INT node = StandardNode( self.meta, self.var, datatype, {}, self.config, False, DerivedElement ) objects = [] self.assertTrue(node.bind("a", "13", None, objects)) self.assertEqual(("a", DerivedElement("a", 13)), objects[-1]) def test_bind_wrapper_type(self) -> None: datatype = DataType.HEX_BINARY node = StandardNode( self.meta, self.var, datatype, {}, self.config, False, DerivedElement ) objects = [] self.assertTrue(node.bind("a", "13", None, objects)) self.assertEqual(("a", DerivedElement(qname="a", value=b"\x13")), objects[-1]) def test_bind_nillable(self) -> None: datatype = DataType.STRING node = StandardNode(self.meta, self.var, datatype, {}, self.config, True, None) objects = [] self.assertTrue(node.bind("a", None, None, objects)) self.assertEqual(("a", None), objects[-1]) node.nillable = False self.assertTrue(node.bind("a", None, None, objects)) self.assertEqual(("a", ""), objects[-1]) def test_child(self) -> None: datatype = DataType.STRING node = StandardNode( self.meta, self.var, datatype, {}, self.config, False, False ) with self.assertRaises(XmlContextError): node.child("foo", {}, {}, 0) tefra-xsdata-cf7afe8/tests/formats/dataclass/parsers/nodes/test_union.py000066400000000000000000000124441526437326600270110ustar00rootroot00000000000000from dataclasses import field, make_dataclass from unittest import TestCase from tests.fixtures.artists import Artist from tests.fixtures.models import UnionType from xsdata.exceptions import ParserError from xsdata.formats.dataclass.context import XmlContext from xsdata.formats.dataclass.models.elements import XmlType from xsdata.formats.dataclass.parsers.config import ParserConfig from xsdata.formats.dataclass.parsers.nodes import UnionNode from xsdata.models.mixins import attribute from xsdata.utils.testing import XmlMetaFactory, XmlVarFactory class UnionNodeTests(TestCase): def setUp(self) -> None: super().setUp() self.context = XmlContext() self.config = ParserConfig() def test_child(self) -> None: attrs = {"id": "1"} ns_map = {"ns0": "xsdata"} meta = XmlMetaFactory.create(clazz=Artist) var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="foo") node = UnionNode( meta=meta, var=var, position=0, config=self.config, context=self.context, attrs={}, ns_map={}, ) self.assertEqual(node, node.child("foo", attrs, ns_map, 10)) self.assertEqual(1, node.level) self.assertEqual([("start", "foo", attrs, ns_map)], node.events) self.assertIsNot(attrs, node.events[0][2]) def test_bind_appends_end_event_when_level_not_zero(self) -> None: meta = XmlMetaFactory.create(clazz=Artist) var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="foo") node = UnionNode( meta=meta, var=var, position=0, config=self.config, context=self.context, attrs={}, ns_map={}, ) node.level = 1 objects = [] self.assertFalse(node.bind("bar", "text", "tail", objects)) self.assertEqual(0, len(objects)) self.assertEqual(0, node.level) self.assertEqual([("end", "bar", "text", "tail")], node.events) def test_filter_fixed_attrs(self) -> None: a = make_dataclass( "A", [("x", int, field(init=False, default=1, metadata={"type": "Attribute"}))], ) b = make_dataclass( "A", [("x", int, field(init=False, default=2, metadata={"type": "Attribute"}))], ) root = make_dataclass("Root", [("value", a | b | int)]) meta = self.context.build(root) var = next(meta.find_children("value")) node = UnionNode( meta=meta, var=var, position=0, config=self.config, context=self.context, attrs={"x": 2}, ns_map={}, ) self.assertEqual([b], node.candidates) node = UnionNode( meta=meta, var=var, position=0, config=self.config, context=self.context, attrs={}, ns_map={}, ) self.assertEqual([a, b, int], node.candidates) def test_bind_returns_best_matching_object(self) -> None: item = make_dataclass( "Item", [("value", str), ("a", int, attribute()), ("b", int, attribute())] ) item2 = make_dataclass("Item2", [("a", int, attribute())]) root = make_dataclass("Root", [("item", str | int | item2 | item)]) meta = self.context.build(root) var = next(meta.find_children("item")) attrs = {"a": "1", "b": 2} ns_map = {} node = UnionNode( meta=meta, var=var, position=0, config=self.config, context=self.context, attrs=attrs, ns_map=ns_map, ) objects = [] self.assertTrue(node.bind("item", "1", None, objects)) self.assertIsInstance(objects[-1][1], item) self.assertEqual(1, objects[-1][1].a) self.assertEqual(2, objects[-1][1].b) self.assertEqual("1", objects[-1][1].value) self.assertEqual("item", objects[-1][0]) self.assertEqual(2, len(node.events)) self.assertEqual(("start", "item", attrs, ns_map), node.events[0]) self.assertEqual(("end", "item", "1", None), node.events[1]) self.assertIsNot(node.attrs, node.events[0][2]) self.assertIs(node.ns_map, node.events[0][3]) node = UnionNode( meta=meta, var=var, position=0, config=self.config, context=self.context, attrs={}, ns_map=ns_map, ) self.assertTrue(node.bind("item", "1", None, objects)) self.assertEqual(1, objects[-1][1]) self.assertTrue(node.bind("item", "a", None, objects)) self.assertEqual("a", objects[-1][1]) def test_bind_raises_parser_error_on_failure(self) -> None: meta = self.context.build(UnionType) var = next(meta.find_children("element")) node = UnionNode( meta=meta, var=var, position=0, config=self.config, context=self.context, attrs={}, ns_map={}, ) with self.assertRaises(ParserError) as cm: node.bind("element", None, None, []) self.assertEqual("Failed to parse union node: element", str(cm.exception)) tefra-xsdata-cf7afe8/tests/formats/dataclass/parsers/nodes/test_wildcard.py000066400000000000000000000043051526437326600274470ustar00rootroot00000000000000from unittest import TestCase from xsdata.formats.dataclass.models.elements import XmlType from xsdata.formats.dataclass.models.generics import AnyElement from xsdata.formats.dataclass.parsers.nodes import WildcardNode from xsdata.utils.testing import XmlVarFactory class WildcardNodeTests(TestCase): def test_bind(self) -> None: text = "\n " tail = "bar" attrs = {"id": "1"} ns_map = {"ns0": "xsdata"} generic = AnyElement( qname="foo", text="", tail="bar", attributes=attrs, children=[1, 2, 3], ) var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="foo", local_name="a") node = WildcardNode( position=0, var=var, attrs=attrs, ns_map=ns_map, factory=AnyElement ) objects = [("a", 1), ("b", 2), ("c", 3)] self.assertTrue(node.bind("foo", text, tail, objects)) self.assertEqual(var.qname, objects[-1][0]) self.assertEqual(generic, objects[-1][1]) # Preserve whitespace if no children node.position = 1 node.bind("foo", text, tail, objects) generic.text = text generic.children = [] self.assertEqual(generic, objects[-1][1]) # Not a wildcard, no tail/attrs/children skip wrapper tail = None text = "1" node.attrs = {} node.position = 2 node.bind("a", text, tail, objects) self.assertEqual("1", objects[-1][1]) def test_child(self) -> None: attrs = {"id": "1"} ns_map = {"ns0": "xsdata"} var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="foo") node = WildcardNode( position=0, var=var, attrs={}, ns_map={}, factory=AnyElement ) actual = node.child("foo", attrs, ns_map, 10) self.assertIsInstance(actual, WildcardNode) self.assertEqual(10, actual.position) self.assertEqual(var, actual.var) self.assertEqual(ns_map, actual.ns_map) self.assertEqual(attrs, actual.attrs) def test_fetch_any_children(self) -> None: objects = [(x, x) for x in "abc"] self.assertEqual(["b", "c"], WildcardNode.fetch_any_children(1, objects)) tefra-xsdata-cf7afe8/tests/formats/dataclass/parsers/nodes/test_wrapper.py000066400000000000000000000074671526437326600273520ustar00rootroot00000000000000from dataclasses import dataclass, field from unittest import TestCase from xsdata.formats.dataclass.parsers import XmlParser class WrapperTests(TestCase): def setUp(self) -> None: self.parser = XmlParser() def test_namespace(self) -> None: @dataclass class NamespaceWrapper: items: list[str] = field( metadata={ "wrapper": "Items", "type": "Element", "name": "item", "namespace": "ns", } ) xml = 'ab' obj = self.parser.from_string(xml, clazz=NamespaceWrapper) self.assertIsInstance(obj, NamespaceWrapper) self.assertTrue(hasattr(obj, "items")) self.assertEqual(len(obj.items), 2) self.assertEqual(obj.items[0], "a") self.assertEqual(obj.items[1], "b") def test_primitive(self) -> None: @dataclass class PrimitiveWrapper: primitive_list: list[str] = field( metadata={ "wrapper": "PrimitiveList", "type": "Element", "name": "Value", } ) xml = r"Value 1Value 2" obj = self.parser.from_string(xml, clazz=PrimitiveWrapper) self.assertTrue(hasattr(obj, "primitive_list")) self.assertIsInstance(obj.primitive_list, list) self.assertEqual(len(obj.primitive_list), 2) self.assertEqual(obj.primitive_list[0], "Value 1") self.assertEqual(obj.primitive_list[1], "Value 2") def test_element(self) -> None: @dataclass class ElementObject: content: str = field(metadata={"type": "Element"}) @dataclass class ElementWrapper: elements: list[ElementObject] = field( metadata={"wrapper": "Elements", "type": "Element", "name": "Object"} ) xml = "HelloWorld" obj = self.parser.from_string(xml, clazz=ElementWrapper) self.assertTrue(hasattr(obj, "elements")) self.assertIsInstance(obj.elements, list) self.assertEqual(len(obj.elements), 2) self.assertIsInstance(obj.elements[0], ElementObject) self.assertIsInstance(obj.elements[1], ElementObject) self.assertEqual(obj.elements[0].content, "Hello") self.assertEqual(obj.elements[1].content, "World") def test_reused_item_name(self) -> None: @dataclass class Foo: class Meta: name = "Property" foo_id: int = field(metadata={"name": "Foo-Id", "type": "Attribute"}) @dataclass class Bar: class Meta: name = "Property" bar_id: str = field(metadata={"name": "Bar-Id", "type": "Attribute"}) @dataclass class Response: foos: list[Foo] = field( metadata={"wrapper": "Foos", "name": "Property", "type": "Element"} ) bars: list[Bar] = field( metadata={"wrapper": "Bars", "name": "Property", "type": "Element"} ) xml = ( "" '' '' "" ) obj = self.parser.from_string(xml, clazz=Response) self.assertEqual([Foo(foo_id=1), Foo(foo_id=2)], obj.foos) self.assertEqual([Bar(bar_id="3"), Bar(bar_id="4")], obj.bars) tefra-xsdata-cf7afe8/tests/formats/dataclass/parsers/test_dict.py000066400000000000000000000362121526437326600254730ustar00rootroot00000000000000import json from dataclasses import asdict, dataclass, field from decimal import Decimal from xml.etree.ElementTree import QName from tests import fixtures_dir from tests.fixtures.books import BookForm, Books from tests.fixtures.models import ( AttrsType, BaseC, BaseType, ChoiceType, ExtendedType, FixedType, OptionalChoiceType, TypeA, TypeB, TypeC, TypeD, UnionType, ) from tests.fixtures.wrapper import Charlie, Wrapper from xsdata.exceptions import ParserError from xsdata.formats.dataclass.models.generics import AnyElement, DerivedElement from xsdata.formats.dataclass.parsers import DictDecoder from xsdata.models.datatype import XmlDate from xsdata.utils.testing import FactoryTestCase # Default values for BookForm required fields BOOK_DEFAULTS = { "author": "Test Author", "title": "Test Title", "genre": "Fiction", "price": 9.99, "pub_date": XmlDate(2020, 1, 1), "review": "Test review", } class DictDecoderTests(FactoryTestCase): def setUp(self) -> None: super().setUp() self.decoder = DictDecoder() def test_decode(self) -> None: path = fixtures_dir.joinpath("books/books.json") source = json.loads(path.read_text()) books = self.decoder.decode(source, Books) self.assertIsInstance(books, Books) self.assertEqual( BookForm( author="Hightower, Kim", title="The First Book", genre="Fiction", price=44.95, pub_date=XmlDate.from_string("2000-10-01"), review="An amazing story of nothing.", id="bk001", ), books.book[0], ) self.assertEqual( BookForm( author="Nagata, Suanne", title="Becoming Somebody", genre="Biography", price=None, pub_date=None, review="A masterpiece of the fine art of gossiping.", id="bk002", ), books.book[1], ) def test_decode_list_of_objects(self) -> None: path = fixtures_dir.joinpath("books/books.json") data = json.loads(path.read_text()) book_list = data["book"] books = self.decoder.decode(book_list, list[BookForm]) self.assertIsInstance(books, list) self.assertEqual(2, len(books)) self.assertIsInstance(books[0], BookForm) self.assertIsInstance(books[1], BookForm) def test_decode_with_unknown_class(self) -> None: path = fixtures_dir.joinpath("books/books.json") source = json.loads(path.read_text()) books = self.decoder.decode(source) self.assertIsInstance(books, Books) self.assertEqual(2, len(books.book)) book_list = self.decoder.decode(source["book"]) self.assertIsInstance(book_list, list) self.assertEqual(2, len(book_list)) self.assertIsInstance(book_list[0], BookForm) self.assertIsInstance(book_list[1], BookForm) def test_decode_with_fail_on_converter_warnings(self) -> None: json_str = {"x": "foo"} self.decoder.config.fail_on_converter_warnings = True with self.assertRaises(ParserError) as cm: self.decoder.decode(json_str, TypeA) self.assertEqual( "Failed to convert value for `TypeA.x`\n `foo` is not a valid `int`", str(cm.exception), ) def test_decode_wrapper(self) -> None: data = { "alphas": {"alpha": "value"}, "bravos": {"bravo": [1, 2]}, "charlies": { "charlie": [ {"value": "first", "lang": "en"}, {"value": "second", "lang": "en"}, ] }, } actual = self.decoder.decode(data, Wrapper) expected = Wrapper( alpha="value", bravo=[1, 2], charlie=[ Charlie(value="first", lang="en"), Charlie(value="second", lang="en"), ], ) self.assertEqual(expected, actual) def test_verify_type(self) -> None: invalid_cases = [ ( {"not": 1, "found": 1}, None, "Unable to locate model with properties(['not', 'found'])", ), ({}, None, "Document is empty, can not detect type"), ([], BookForm, "Document is array, expected object"), ({}, list[BookForm], "Document is object, expected array"), ( {}, None | ChoiceType, f"Invalid clazz argument: {None | ChoiceType}", ), ([], list[int], f"Invalid clazz argument: {list[int]}"), ] for source, clazz, exc_msg in invalid_cases: with self.assertRaises(ParserError) as cm: self.decoder.decode(source, clazz=clazz) self.assertEqual(exc_msg, str(cm.exception)) def test_bind_dataclass_with_unknown_property(self) -> None: data = {"unknown": True} with self.assertRaises(ParserError) as cm: self.decoder.bind_dataclass(data, Books) self.assertEqual("Unknown property Books.unknown", str(cm.exception)) self.decoder.config.fail_on_unknown_properties = False self.assertEqual(Books(), self.decoder.bind_dataclass(data, Books)) def test_bind_dataclass_with_required_fields(self) -> None: obj = self.decoder.bind_dataclass({"x": 1, "y": "a", "z": None}, TypeD) self.assertEqual(1, obj.x) self.assertEqual("a", obj.y) self.assertIsNone(obj.z) with self.assertRaises(ParserError): self.decoder.bind_dataclass({"x": 1, "y": "a"}, TypeD) def test_bind_dataclass_with_fixed_field(self) -> None: obj = self.decoder.bind_dataclass({"value": "abc"}, FixedType) self.assertEqual("abc", obj.value) with self.assertRaises(ParserError): self.decoder.bind_dataclass({"value": "abcd"}, FixedType) def test_bind_derived_dataclass(self) -> None: data = { "qname": "{urn:books}BookForm", "type": None, "value": { "author": "Nagata, Suanne", "title": "Becoming Somebody", "genre": "Biography", "price": 19.99, "pub_date": "2020-01-01", "review": "A masterpiece.", }, } actual = self.decoder.bind_dataclass(data, BookForm) expected = DerivedElement( qname="{urn:books}BookForm", value=BookForm( author="Nagata, Suanne", title="Becoming Somebody", genre="Biography", price=19.99, pub_date=XmlDate(2020, 1, 1), review="A masterpiece.", ), type=None, ) self.assertEqual(expected, actual) def test_bind_derived_dataclass_with_xsi_type(self) -> None: data = { "qname": "foobar", "type": "{urn:books}BookForm", "value": { "author": "Nagata, Suanne", "title": "Becoming Somebody", "genre": "Biography", "price": 19.99, "pub_date": "2020-01-01", "review": "A masterpiece.", }, } actual = self.decoder.bind_dataclass(data, DerivedElement) expected = DerivedElement( qname="foobar", value=BookForm( author="Nagata, Suanne", title="Becoming Somebody", genre="Biography", price=19.99, pub_date=XmlDate(2020, 1, 1), review="A masterpiece.", ), type="{urn:books}BookForm", ) self.assertEqual(expected, actual) with self.assertRaises(ParserError) as cm: data["type"] = "notExists" self.decoder.bind_dataclass(data, DerivedElement) self.assertEqual( "Unable to locate derived model with properties(['author', 'title', 'genre', 'price', 'pub_date', 'review'])", str(cm.exception), ) with self.assertRaises(ParserError) as cm: data["type"] = None self.decoder.bind_dataclass(data, DerivedElement) def test_bind_dataclass_union(self) -> None: data = {"element": {"x": 1, "y": "foo", "z": "1.0"}} actual = self.decoder.bind_dataclass(data, UnionType) self.assertIsInstance(actual.element, TypeC) with self.assertRaises(ParserError) as cm: self.decoder.bind_dataclass({"element": {"a": 1}}, UnionType) self.assertEqual( "Failed to bind object with properties(['a']) " "to any of the ['TypeA', 'TypeB', 'TypeC', 'TypeD']", str(cm.exception), ) def test_bind_dataclass_subclasses(self) -> None: data = {"element": {"x": "1", "y": "foo", "z": "1.0"}} actual = self.decoder.bind_dataclass(data, BaseType) self.assertIsInstance(actual.element, BaseC) def test_bind_attributes(self) -> None: data = {"attrs": {"a": 1, "b": 2}, "index": 1} actual = self.decoder.bind_dataclass(data, AttrsType) self.assertEqual(data["attrs"], actual.attrs) self.assertIsNot(data["attrs"], actual.attrs) def test_bind_simple_type_with_wildcard_var(self) -> None: data = {"any": 1, "wildcard": 2} actual = self.decoder.bind_dataclass(data, ExtendedType) self.assertEqual(1, actual.any) self.assertEqual(2, actual.wildcard) def test_bind_simple_type_with_elements_var(self) -> None: data = {"choice": ["1.0", 1, ["1.2"], "{a}b"]} actual = self.decoder.bind_dataclass(data, ChoiceType) self.assertEqual(1.0, actual.choice[0]) self.assertEqual(1, actual.choice[1]) self.assertEqual([Decimal("1.2")], actual.choice[2]) self.assertEqual(QName("{a}b"), actual.choice[3]) self.assertIsInstance(actual.choice[3], QName) data = {"choice": ["!NotAQname"]} with self.assertRaises(ParserError) as cm: self.decoder.bind_dataclass(data, ChoiceType) self.assertEqual( "Failed to bind '!NotAQname' to ChoiceType.choice field", str(cm.exception), ) def test_bind_simple_type_with_optional_elements_var(self) -> None: data = {"a_or_b": None} actual = self.decoder.bind_dataclass(data, OptionalChoiceType) self.assertIsNone(None, actual.a_or_b) def test_bind_any_element(self) -> None: data = { "wildcard": { "qname": "a", "text": 1, "tail": None, "children": [], "attributes": {}, } } self.assertEqual( ExtendedType(wildcard=AnyElement(qname="a", text="1")), self.decoder.bind_dataclass(data, ExtendedType), ) def test_bind_choice_dataclass(self) -> None: data = {"choice": [{"x": 1}, {"x": 1, "y": "a"}]} expected = ChoiceType(choice=[TypeA(x=1), TypeB(x=1, y="a")]) self.assertEqual(expected, self.decoder.bind_dataclass(data, ChoiceType)) def test_bind_derived_value_with_choice_var(self) -> None: data = { "choice": [ { "qname": "b", "type": None, "value": { "x": "1", "y": "a", }, } ] } expected = ChoiceType( choice=[ DerivedElement( qname="b", value=TypeB(x=1, y="a"), ) ] ) self.assertEqual(expected, self.decoder.bind_dataclass(data, ChoiceType)) with self.assertRaises(ParserError) as cm: data["choice"][0]["qname"] = "nope" self.decoder.bind_dataclass(data, ChoiceType) self.assertEqual( "Unable to locate compound element ChoiceType.choice[nope]", str(cm.exception), ) def test_bind_derived_value_with_simple_type(self) -> None: data = {"choice": [{"qname": "float", "value": 1, "type": None}]} actual = self.decoder.bind_dataclass(data, ChoiceType) expected = ChoiceType(choice=[DerivedElement(qname="float", value=1)]) self.assertEqual(expected, actual) def test_bind_wildcard_dataclass(self) -> None: data = {"a": None, "wildcard": {"x": 1}} expected = ExtendedType(wildcard=TypeA(x=1)) self.assertEqual(expected, self.decoder.bind_dataclass(data, ExtendedType)) def test_bind_wildcard_with_derived_dataclass(self) -> None: data = { "wildcard": { "qname": "b", "type": "{xsdata}TypeB", "value": { "x": "1", "y": "a", }, } } expected = ExtendedType( wildcard=DerivedElement( qname="b", value=TypeB(x=1, y="a"), type="{xsdata}TypeB" ) ) self.assertEqual(expected, self.decoder.bind_dataclass(data, ExtendedType)) def test_bind_any_type_with_derived_dataclass(self) -> None: data = { "any": { "qname": "any", "type": None, "value": {"x": "1"}, } } expected = ExtendedType(any=DerivedElement(qname="any", value=TypeA(x=1))) self.assertEqual(expected, self.decoder.bind_dataclass(data, ExtendedType)) with self.assertRaises(ParserError) as cm: data["any"]["type"] = "notexists" self.decoder.bind_dataclass(data, ExtendedType) self.assertEqual("Unable to locate xsi:type `notexists`", str(cm.exception)) def test_bind_text_with_unions(self) -> None: @dataclass class Fixture: x: list[int | float | str | bool] = field(metadata={"tokens": True}) values = ["foo", 12.2, "12.2", 12, "12", True, "false"] result = self.decoder.decode({"x": values}, Fixture) expected = ["foo", 12.2, 12.2, 12, 12, True, False] self.assertEqual({"x": expected}, asdict(result)) def test_find_var(self) -> None: meta = self.decoder.context.build(TypeB) xml_vars = meta.get_all_vars() self.assertEqual(xml_vars[0], self.decoder.find_var(xml_vars, "x", 1)) self.assertIsNone(self.decoder.find_var(xml_vars, "x", [1, 2])) meta = self.decoder.context.build(ExtendedType) xml_vars = meta.get_all_vars() self.assertIsNone(self.decoder.find_var(xml_vars, "a", [1, 2])) self.assertEqual(xml_vars[0], self.decoder.find_var(xml_vars, "a", {"x": 1})) meta = self.decoder.context.build(Wrapper) xml_vars = meta.get_all_vars() self.assertIsNone(self.decoder.find_var(xml_vars, "charlies", {})) self.assertIsNone(self.decoder.find_var(xml_vars, "bravos", {"bravo": 1})) self.assertEqual( xml_vars[0], self.decoder.find_var(xml_vars, "alphas", {"alpha": "foo"}) ) tefra-xsdata-cf7afe8/tests/formats/dataclass/parsers/test_json.py000066400000000000000000000014671526437326600255250ustar00rootroot00000000000000from tests import fixtures_dir from tests.fixtures.books import Books from xsdata.formats.dataclass.parsers.json import JsonParser from xsdata.utils.testing import FactoryTestCase class JsonParserTests(FactoryTestCase): def setUp(self) -> None: super().setUp() self.parser = JsonParser() def test_parser_entry_points(self) -> None: path = fixtures_dir.joinpath("books/books.json") books = self.parser.from_path(path) self.assertIsInstance(books, Books) books = self.parser.from_string(path.read_text(), Books) self.assertIsInstance(books, Books) books = self.parser.from_bytes(path.read_bytes(), Books) self.assertIsInstance(books, Books) books = self.parser.parse(str(path), Books) self.assertIsInstance(books, Books) tefra-xsdata-cf7afe8/tests/formats/dataclass/parsers/test_mixins.py000066400000000000000000000022541526437326600260560ustar00rootroot00000000000000from unittest.case import TestCase from tests.fixtures.books import Books from tests.fixtures.books.fixtures import books, events from xsdata.exceptions import XmlHandlerError from xsdata.formats.dataclass.parsers.bases import RecordParser from xsdata.formats.dataclass.parsers.mixins import EventsHandler, XmlHandler class XmlHandlerTests(TestCase): def test_process(self) -> None: parser = RecordParser() handler = XmlHandler(clazz=Books, parser=parser) self.assertEqual([], handler.queue) self.assertEqual([], handler.objects) with self.assertRaises(NotImplementedError): handler.parse(None, {}) class EventsHandlerTests(TestCase): def setUp(self) -> None: self.parser = RecordParser(handler=EventsHandler) def test_parse(self) -> None: self.assertEqual(books, self.parser.parse(events, Books)) self.assertEqual({"brk": "urn:books"}, self.parser.ns_map) def test_parse_with_unhandled_event(self) -> None: with self.assertRaises(XmlHandlerError) as cm: self.parser.parse([("reverse", "")], Books) self.assertEqual("Unhandled event: `reverse`.", str(cm.exception)) tefra-xsdata-cf7afe8/tests/formats/dataclass/parsers/test_node.py000066400000000000000000000172451526437326600255020ustar00rootroot00000000000000from dataclasses import make_dataclass from typing import Any from unittest import mock from unittest.case import TestCase from tests.fixtures.books import BookForm, Books from tests.fixtures.models import TypeA from xsdata.exceptions import ParserError from xsdata.formats.dataclass.models.elements import XmlType from xsdata.formats.dataclass.models.generics import DerivedElement from xsdata.formats.dataclass.parsers.bases import NodeParser from xsdata.formats.dataclass.parsers.handlers import XmlEventHandler from xsdata.formats.dataclass.parsers.mixins import XmlHandler from xsdata.formats.dataclass.parsers.nodes.primitive import PrimitiveNode from xsdata.formats.dataclass.parsers.nodes.skip import SkipNode from xsdata.models.enums import Namespace, QNames from xsdata.utils.testing import XmlVarFactory class NodeParserTests(TestCase): maxDiff = None def setUp(self) -> None: self.parser = NodeParser() def test_parse(self) -> None: class TestHandler(XmlHandler): def parse(self, source: Any, ns_map: dict) -> Any: return Books() self.parser.handler = TestHandler self.assertEqual(Books(), self.parser.parse([], Books, {})) def test_parse_when_result_type_is_wrong(self) -> None: parser = self.parser with self.assertRaises(ParserError) as cm: parser.parse([], Books) self.assertEqual("Failed to create target class `Books`", str(cm.exception)) def test_parse_with_fail_on_converter_warnings(self) -> None: parser = NodeParser(handler=XmlEventHandler) parser.config.fail_on_converter_warnings = True xml = """foo""" with self.assertRaises(ParserError) as cm: parser.from_string(xml, TypeA) self.assertEqual( "Failed to convert value for `TypeA.x`\n `foo` is not a valid `int`", str(cm.exception), ) def test_start(self) -> None: queue = [] objects = [] attrs = {"k": "v"} ns_map = {"a": "b"} self.parser.start(Books, queue, objects, "{urn:books}books", attrs, ns_map) actual = queue[0] self.assertEqual(1, len(queue)) self.assertEqual(0, actual.position) self.assertEqual(self.parser.context, actual.context) self.assertEqual(self.parser.context.build(Books), actual.meta) self.assertEqual(self.parser.config, actual.config) self.assertEqual(attrs, actual.attrs) self.assertEqual(ns_map, actual.ns_map) self.assertFalse(actual.mixed) self.assertIsNone(actual.derived_factory) self.assertIsNone(actual.xsi_type) self.parser.start(Books, queue, objects, "book", {}, {}) actual = queue[1] self.assertEqual(2, len(queue)) self.assertEqual(0, actual.position) self.assertEqual(self.parser.context, actual.context) self.assertEqual(self.parser.context.build(BookForm), actual.meta) self.assertEqual(self.parser.config, actual.config) self.assertEqual({}, actual.attrs) self.assertEqual({}, actual.ns_map) self.assertFalse(actual.mixed) self.assertIsNone(actual.derived_factory) self.assertIsNone(actual.xsi_type) def test_start_with_undefined_class(self) -> None: parser = self.parser queue = [] objects = [] attrs = {"k": "v"} ns_map = {"a": "b"} parser.start(None, queue, objects, "{urn:books}books", attrs, ns_map) actual = queue[0] self.assertEqual(1, len(queue)) self.assertEqual(0, actual.position) self.assertEqual(self.parser.context, actual.context) self.assertEqual(self.parser.context.build(Books), actual.meta) self.assertEqual(self.parser.config, actual.config) self.assertEqual(attrs, actual.attrs) self.assertEqual(ns_map, actual.ns_map) self.assertFalse(actual.mixed) self.assertIsNone(actual.derived_factory) self.assertIsNone(actual.xsi_type) with self.assertRaises(ParserError) as cm: parser.start(None, [], [], "{unknown}hopefully", {}, {}) self.assertEqual( "No class found matching root: {unknown}hopefully", str(cm.exception) ) def test_start_with_any_type_root(self) -> None: parser = self.parser queue = [] objects = [] attrs = {QNames.XSI_TYPE: "bk:books"} ns_map = {"bk": "urn:books", "xsi": Namespace.XSI.uri} parser.start(None, queue, objects, "whatever", attrs, ns_map) actual = queue[0] self.assertEqual(1, len(queue)) self.assertEqual(0, actual.position) self.assertEqual(self.parser.context, actual.context) self.assertEqual(self.parser.context.build(Books), actual.meta) self.assertEqual(self.parser.config, actual.config) self.assertEqual(attrs, actual.attrs) self.assertEqual(ns_map, actual.ns_map) self.assertFalse(actual.mixed) self.assertEqual(DerivedElement, actual.derived_factory) self.assertEqual("{urn:books}books", actual.xsi_type) def test_start_with_derived_class(self) -> None: a = make_dataclass("a", fields=[]) b = make_dataclass("b", fields=[], bases=(a,)) parser = NodeParser() queue = [] objects = [] attrs = {QNames.XSI_TYPE: "b"} ns_map = {} parser.start(a, queue, objects, "a", attrs, ns_map) actual = queue[0] self.assertEqual(1, len(queue)) self.assertEqual(0, actual.position) self.assertEqual(parser.context, actual.context) self.assertEqual(parser.context.build(b), actual.meta) self.assertEqual(parser.config, actual.config) self.assertEqual(attrs, actual.attrs) self.assertEqual({}, actual.ns_map) self.assertFalse(actual.mixed) self.assertEqual(DerivedElement, actual.derived_factory) self.assertEqual("b", actual.xsi_type) def test_start_with_nillable_element(self) -> None: a = make_dataclass("a", fields=[]) parser = NodeParser() queue = [] objects = [] attrs = {QNames.XSI_NIL: "true"} ns_map = {} parser.start(a, queue, objects, "a", attrs, ns_map) actual = queue[0] self.assertEqual(1, len(queue)) self.assertEqual(0, actual.position) self.assertTrue("b", actual.xsi_nil) @mock.patch.object(PrimitiveNode, "bind", return_value=True) def test_end(self, mock_assemble) -> None: parser = NodeParser() objects = [("q", "result")] queue = [] var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="foo") queue.append(PrimitiveNode(var, {}, False, parser.config)) self.assertTrue(parser.end(queue, objects, "author", "foobar", None)) self.assertEqual(0, len(queue)) self.assertEqual(("q", "result"), objects[-1]) mock_assemble.assert_called_once_with("author", "foobar", None, objects) def test_end_with_no_result(self) -> None: parser = NodeParser() objects = [("q", "result")] queue = [SkipNode()] self.assertFalse(parser.end(queue, objects, "author", "foobar", None)) self.assertEqual(0, len(queue)) def test_register_namespace(self) -> None: parser = NodeParser() ns_map = {} parser.register_namespace(ns_map, "bar", "foo") parser.register_namespace(ns_map, "bar", "exists") self.assertEqual({"bar": "foo"}, ns_map) parser.register_namespace(ns_map, None, "a") self.assertEqual({"bar": "foo", None: "a"}, ns_map) parser.register_namespace(ns_map, None, "b") self.assertEqual({"bar": "foo", None: "a"}, ns_map) tefra-xsdata-cf7afe8/tests/formats/dataclass/parsers/test_tree.py000066400000000000000000000017351526437326600255110ustar00rootroot00000000000000from unittest import TestCase from tests import fixtures_dir from xsdata.formats.dataclass.models.generics import AnyElement from xsdata.formats.dataclass.parsers.tree import TreeParser class TreeParserTests(TestCase): def test_parser(self) -> None: parser = TreeParser() path = fixtures_dir.joinpath("books/bk001.xml") actual = parser.from_path(path) expected = AnyElement( qname="book", text="", children=[ AnyElement(qname="author", text="Hightower, Kim"), AnyElement(qname="title", text="The First Book"), AnyElement(qname="genre", text="Fiction"), AnyElement(qname="price", text="44.95"), AnyElement(qname="pub_date", text="2000-10-01"), AnyElement(qname="review", text="An amazing story of nothing."), ], attributes={"id": "bk001"}, ) self.assertEqual(expected, actual) tefra-xsdata-cf7afe8/tests/formats/dataclass/parsers/test_utils.py000066400000000000000000000133661526437326600257150ustar00rootroot00000000000000import warnings from unittest import mock from tests.fixtures.models import TypeA from xsdata.exceptions import ParserError from xsdata.formats.converter import ConverterFactory from xsdata.formats.dataclass.context import XmlContext from xsdata.formats.dataclass.parsers.config import ParserConfig from xsdata.formats.dataclass.parsers.utils import ParserUtils from xsdata.models.enums import Namespace, ProcessType, QNames from xsdata.utils.testing import FactoryTestCase, XmlMetaFactory, XmlVarFactory class ParserUtilsTests(FactoryTestCase): def setUp(self) -> None: super().setUp() self.ctx = XmlContext() def test_xsi_type(self) -> None: ns_map = {"bar": "xsdata"} attrs = {} self.assertIsNone(ParserUtils.xsi_type(attrs, ns_map)) attrs = {QNames.XSI_TYPE: "foo"} self.assertEqual("foo", ParserUtils.xsi_type(attrs, ns_map)) attrs = {QNames.XSI_TYPE: "bar:foo"} self.assertEqual("{xsdata}foo", ParserUtils.xsi_type(attrs, ns_map)) def test_xsi_nil(self) -> None: attrs = {} self.assertIsNone(ParserUtils.xsi_nil(attrs)) attrs = {QNames.XSI_NIL: "true"} self.assertTrue(ParserUtils.xsi_nil(attrs)) attrs = {QNames.XSI_NIL: "false"} self.assertFalse(ParserUtils.xsi_nil(attrs)) @mock.patch.object(ConverterFactory, "deserialize", return_value=2) def test_parse_value(self, mock_deserialize) -> None: self.assertEqual(1, ParserUtils.parse_value(None, [int], 1)) self.assertIsNone(ParserUtils.parse_value(None, [int], lambda: 1)) self.assertTrue(2, ParserUtils.parse_value("1", [int], None)) mock_deserialize.assert_called_once_with("1", [int], ns_map=None, format=None) def test_parse_value_with_tokens_true(self) -> None: actual = ParserUtils.parse_value(" 1 2 3", [int], list, None, list) self.assertEqual([1, 2, 3], actual) actual = ParserUtils.parse_value(["1", "2", "3"], [int], list, None, tuple) self.assertEqual((1, 2, 3), actual) actual = ParserUtils.parse_value(None, [int], lambda: [1, 2, 3], None, list) self.assertEqual([1, 2, 3], actual) @mock.patch.object(ConverterFactory, "deserialize", return_value=2) def test_parse_value_with_ns_map(self, mock_to_python) -> None: ns_map = {"a": 1} ParserUtils.parse_value(" 1 2 3", [int], list, ns_map, list) ParserUtils.parse_value(" 1 2 3", [str], None, ns_map) self.assertEqual(4, mock_to_python.call_count) mock_to_python.assert_has_calls( [ mock.call("1", [int], ns_map=ns_map, format=None), mock.call("2", [int], ns_map=ns_map, format=None), mock.call("3", [int], ns_map=ns_map, format=None), mock.call(" 1 2 3", [str], ns_map=ns_map, format=None), ] ) @mock.patch.object(ConverterFactory, "deserialize", return_value=2) def test_parse_value_with_format(self, mock_to_python) -> None: ParserUtils.parse_value(" 1 2 3", [str], list, format="Nope") self.assertEqual(1, mock_to_python.call_count) mock_to_python.assert_called_once_with( " 1 2 3", [str], ns_map=None, format="Nope" ) def test_parse_any_attributes(self) -> None: attrs = {QNames.XSI_TYPE: "xsd:string", "a": "b"} ns_map = {"xsi": Namespace.XSI.uri, "xsd": Namespace.XS.uri} result = ParserUtils.parse_any_attributes(attrs, ns_map) expected = { QNames.XSI_TYPE: "{http://www.w3.org/2001/XMLSchema}string", "a": "b", } self.assertEqual(expected, result) def test_parse_any_attribute(self) -> None: ns_map = {"xsi": Namespace.XSI.uri, "xsd": Namespace.XS.uri} value = ParserUtils.parse_any_attribute("xsd:string", ns_map) self.assertEqual("{http://www.w3.org/2001/XMLSchema}string", value) ns_map["http"] = "happens" value = ParserUtils.parse_any_attribute("http://www.com", ns_map) self.assertEqual("http://www.com", value) def test_validate_fixed_value(self) -> None: meta = XmlMetaFactory.create(clazz=TypeA, qname="foo") var = XmlVarFactory.create("fixed", default="a") with self.assertRaises(ParserError) as cm: ParserUtils.validate_fixed_value(meta, var, "b") self.assertEqual("Fixed value mismatch foo:fixed, `a != b`", str(cm.exception)) var = XmlVarFactory.create("fixed", default=lambda: "a") with self.assertRaises(ParserError): ParserUtils.validate_fixed_value(meta, var, "b") var = XmlVarFactory.create("fixed", default=lambda: " a ") ParserUtils.validate_fixed_value(meta, var, " a ") var = XmlVarFactory.create("fixed", default=lambda: float("nan")) ParserUtils.validate_fixed_value(meta, var, float("nan")) var = XmlVarFactory.create("fixed", default=lambda: ProcessType.LAX) ParserUtils.validate_fixed_value(meta, var, "lax") def test_parse_var_with_error(self) -> None: meta = XmlMetaFactory.create(clazz=TypeA, qname="foo") var = XmlVarFactory.create("fixed", default="a") config = ParserConfig() with warnings.catch_warnings(record=True) as w: result = ParserUtils.parse_var(meta, var, config, "a", types=[int, float]) expected = ( "Failed to convert value for `TypeA.fixed`\n" " `a` is not a valid `int | float`" ) self.assertEqual("a", result) self.assertEqual(expected, str(w[-1].message)) config.fail_on_converter_warnings = True with self.assertRaises(ParserError) as cm: ParserUtils.parse_var(meta, var, config, "a", types=[int, float]) self.assertEqual(expected, str(cm.exception)) tefra-xsdata-cf7afe8/tests/formats/dataclass/parsers/test_xml.py000066400000000000000000000045121526437326600253460ustar00rootroot00000000000000from unittest import mock from tests.fixtures.artists import Artist from tests.fixtures.books import Books from xsdata.formats.dataclass.models.elements import XmlType from xsdata.formats.dataclass.parsers.nodes import PrimitiveNode, SkipNode from xsdata.formats.dataclass.parsers.xml import UserXmlParser from xsdata.models.enums import EventType from xsdata.utils.testing import FactoryTestCase, XmlMetaFactory, XmlVarFactory class UserXmlParserTests(FactoryTestCase): def setUp(self) -> None: super().setUp() self.parser = UserXmlParser() self.parser.objects = [(x, x) for x in "abcde"] @mock.patch.object(UserXmlParser, "emit_event") def test_start(self, mock_emit_event) -> None: attrs = {"a": "b"} queue = [] self.parser.start(Books, queue, [], "{urn:books}books", attrs, {}) self.assertEqual(1, len(queue)) mock_emit_event.assert_called_once_with( EventType.START, "{urn:books}books", attrs=attrs ) @mock.patch.object(UserXmlParser, "emit_event") def test_end(self, mock_emit_event) -> None: objects = [] queue = [] meta = XmlMetaFactory.create(clazz=Artist) var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="foo", types=(bool,)) queue.append(PrimitiveNode(meta, var, {}, self.parser.config)) result = self.parser.end(queue, objects, "enabled", "true", None) self.assertTrue(result) self.assertEqual(0, len(queue)) self.assertEqual(("enabled", True), objects[-1]) mock_emit_event.assert_called_once_with(EventType.END, "enabled", obj=result) @mock.patch.object(UserXmlParser, "emit_event") def test_end_with_no_result(self, mock_emit_event) -> None: objects = [] queue = [SkipNode()] result = self.parser.end(queue, "author", "foobar", None, objects) self.assertFalse(result) self.assertEqual(0, len(objects)) self.assertEqual(0, len(queue)) self.assertEqual(0, mock_emit_event.call_count) def test_emit_event(self) -> None: mock_func = mock.Mock() self.parser.foo_bar_el = mock_func self.parser.emit_event("foo", "{tns}BarEl", a=1, b=2) mock_func.assert_called_once_with(a=1, b=2) self.assertEqual({("foo", "{tns}BarEl"): mock_func}, self.parser.hooks_cache) tefra-xsdata-cf7afe8/tests/formats/dataclass/serializers/000077500000000000000000000000001526437326600240105ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/formats/dataclass/serializers/__init__.py000066400000000000000000000000001526437326600261070ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/formats/dataclass/serializers/test_code.py000066400000000000000000000123671526437326600263440ustar00rootroot00000000000000from unittest import TestCase from tests.fixtures.books import BookForm, Books from tests.fixtures.books.fixtures import books from tests.fixtures.models import Parent from xsdata.formats.dataclass.serializers import PycodeSerializer from xsdata.models.datatype import XmlDate from xsdata.models.enums import Namespace class PycodeSerializerTests(TestCase): def setUp(self) -> None: self.serializer = PycodeSerializer() def test_render(self) -> None: result = self.serializer.render(books, var_name="books") expected = ( "from tests.fixtures.books.books import BookForm\n" "from tests.fixtures.books.books import Books\n" "from xsdata.models.datatype import XmlDate\n" "\n" "\n" "books = Books(\n" " book=[\n" " BookForm(\n" " author='Hightower, Kim',\n" " title='The First Book',\n" " genre='Fiction',\n" " price=44.95,\n" " pub_date=XmlDate(2000, 10, 1),\n" " review='An amazing story of nothing.',\n" " id='bk001'\n" " ),\n" " BookForm(\n" " author='Nagata, Suanne',\n" " title='Becoming Somebody',\n" " genre='Biography',\n" " price=33.95,\n" " pub_date=XmlDate(2001, 1, 10),\n" " review='A masterpiece of the fine art of gossiping.',\n" " id='bk002'\n" " ),\n" " ]\n" ")\n" ) self.assertEqual(expected, result) def test_write_class_with_default_values(self) -> None: books = Books( book=[ BookForm( author="me", title="My Book", genre="Fiction", price=9.99, pub_date=XmlDate.from_string("2020-01-01"), review="Great!", ) ] ) result = self.serializer.render(books, var_name="books") expected = ( "from tests.fixtures.books.books import BookForm\n" "from tests.fixtures.books.books import Books\n" "from xsdata.models.datatype import XmlDate\n" "\n" "\n" "books = Books(\n" " book=[\n" " BookForm(\n" " author='me',\n" " title='My Book',\n" " genre='Fiction',\n" " price=9.99,\n" " pub_date=XmlDate(2020, 1, 1),\n" " review='Great!'\n" " ),\n" " ]\n" ")\n" ) self.assertEqual(expected, result) def test_write_string_with_unicode_characters(self) -> None: books = Books( book=[ BookForm( author="Backslashes \\ One Two \x12 Three", title="Test", genre="Test", price=1.0, pub_date=XmlDate.from_string("2020-01-01"), review="Test", ) ] ) result = self.serializer.render(books, var_name="books") expected = ( "from tests.fixtures.books.books import BookForm\n" "from tests.fixtures.books.books import Books\n" "from xsdata.models.datatype import XmlDate\n" "\n" "\n" "books = Books(\n" " book=[\n" " BookForm(\n" " author='Backslashes \\\\ One Two \\x12 Three',\n" " title='Test',\n" " genre='Test',\n" " price=1.0,\n" " pub_date=XmlDate(2020, 1, 1),\n" " review='Test'\n" " ),\n" " ]\n" ")\n" ) self.assertEqual(expected, result) def test_write_object_with_empty_array(self) -> None: iterator = self.serializer.repr_object([], 0, set()) self.assertEqual("[]", "".join(iterator)) iterator = self.serializer.repr_object((), 0, set()) self.assertEqual("()", "".join(iterator)) iterator = self.serializer.repr_object(set(), 0, set()) self.assertEqual("set()", "".join(iterator)) def test_write_object_with_mapping(self) -> None: iterator = self.serializer.repr_object({}, 0, set()) self.assertEqual("{}", "".join(iterator)) iterator = self.serializer.repr_object({"foo": "bar"}, 0, set()) self.assertEqual("{\n 'foo': 'bar',\n}", "".join(iterator)) def test_write_object_with_enum(self) -> None: iterator = self.serializer.repr_object(Namespace.SOAP11, 0, set()) self.assertEqual("Namespace.SOAP11", "".join(iterator)) def test_build_imports_with_nested_types(self) -> None: expected = "from tests.fixtures.models import Parent\n" actual = self.serializer.build_imports({Parent.Inner}) self.assertEqual(expected, actual) tefra-xsdata-cf7afe8/tests/formats/dataclass/serializers/test_config.py000066400000000000000000000023561526437326600266740ustar00rootroot00000000000000import unittest import warnings from xsdata.formats.dataclass.serializers.config import SerializerConfig class SerializerConfigTest(unittest.TestCase): def test_deprecated_fields(self) -> None: with warnings.catch_warnings(record=True) as w: config = SerializerConfig(pretty_print=True) self.assertEqual(" ", config.indent) config = SerializerConfig(pretty_print_indent="\t") self.assertEqual("\t", config.indent) expected = [ "Setting `pretty_print` is deprecated, use `indent` instead", "Setting `pretty_print_indent` is deprecated, use `indent` instead", ] self.assertEqual(expected, [str(m.message) for m in w]) with warnings.catch_warnings(record=True) as w: config = SerializerConfig() config.pretty_print = True self.assertTrue(" ", config.indent) config.pretty_print_indent = "\t" self.assertTrue("\t", config.indent) expected = [ "Setting `pretty_print` is deprecated, use `indent` instead", "Setting `pretty_print_indent` is deprecated, use `indent` instead", ] self.assertEqual(expected, [str(m.message) for m in w]) tefra-xsdata-cf7afe8/tests/formats/dataclass/serializers/test_dict.py000066400000000000000000000107001526437326600263420ustar00rootroot00000000000000from unittest.case import TestCase from tests.fixtures.books import BookForm, Books from tests.fixtures.datatypes import Telephone from tests.fixtures.wrapper import Wrapper from xsdata.formats.dataclass.models.generics import AnyElement, DerivedElement from xsdata.formats.dataclass.serializers import DictEncoder, DictFactory from xsdata.models.datatype import XmlDate from xsdata.models.xsd import Attribute from xsdata.utils.testing import XmlVarFactory class DictEncoderTests(TestCase): def setUp(self) -> None: super().setUp() self.encoder = DictEncoder(dict_factory=DictFactory.FILTER_NONE) self.books = Books( book=[ BookForm( id="bk001", author="Hightower, Kim", title="The First Book", genre="Fiction", price=44.95, pub_date=XmlDate.from_string("2000-10-01"), review="An amazing story of nothing.", ), BookForm( id="bk002", author="Nagata, Suanne", title="Becoming Somebody", genre="Biography", price=29.95, pub_date=XmlDate.from_string("2001-05-15"), review="A masterpiece of the fine art of gossiping.", ), ] ) self.expected = { "book": [ { "author": "Hightower, Kim", "genre": "Fiction", "id": "bk001", "lang": "en", "price": 44.95, "pub_date": "2000-10-01", "review": "An amazing story of nothing.", "title": "The First Book", }, { "author": "Nagata, Suanne", "genre": "Biography", "id": "bk002", "lang": "en", "price": 29.95, "pub_date": "2001-05-15", "review": "A masterpiece of the fine art of gossiping.", "title": "Becoming Somebody", }, ] } def test_encode(self) -> None: actual = self.encoder.encode(self.books) self.assertEqual(self.expected, actual) def test_encode_list_of_objects(self) -> None: actual = self.encoder.encode(self.books.book) self.assertEqual(self.expected["book"], actual) def test_encode_with_enum(self) -> None: obj = Attribute() actual = self.encoder.encode(obj) self.assertEqual("optional", actual["use"]) def test_convert_namedtuple(self) -> None: var = XmlVarFactory.create(types=(Telephone,)) actual = self.encoder.encode(Telephone(30, 234, 56783), var) self.assertEqual("30-234-56783", actual) def test_convert_wrapper(self) -> None: obj = Wrapper(alpha="value") value = self.encoder.encode(obj) expected = { "alphas": {"alpha": "value"}, "bravos": {"bravo": []}, "charlies": {"charlie": []}, } self.assertEqual(expected, value) def test_next_value(self) -> None: book = self.books.book[0] actual = [name for name, value in self.encoder.next_value(book)] expected = [ "author", "title", "genre", "price", "pub_date", "review", "id", "lang", ] self.assertEqual(expected, actual) self.encoder.config.ignore_default_attributes = True expected = expected[:-1] actual = [name for name, value in self.encoder.next_value(book)] self.assertEqual(expected, actual) def test_generics(self) -> None: self.obj = AnyElement( children=[ AnyElement(qname="foo", text="bar"), DerivedElement(qname="bar", value="1"), DerivedElement(qname="bar", value=2), ] ) result = self.encoder.encode(self.obj) expected = { "attributes": {}, "children": [ {"attributes": {}, "children": [], "qname": "foo", "text": "bar"}, {"qname": "bar", "value": "1"}, {"qname": "bar", "value": 2}, ], } self.assertEqual(expected, result) tefra-xsdata-cf7afe8/tests/formats/dataclass/serializers/test_json.py000066400000000000000000000067301526437326600264000ustar00rootroot00000000000000import json from unittest.case import TestCase from tests.fixtures.books import BookForm, Books from xsdata.formats.dataclass.serializers import DictFactory from xsdata.formats.dataclass.serializers.config import SerializerConfig from xsdata.formats.dataclass.serializers.json import JsonSerializer from xsdata.models.datatype import XmlDate class JsonSerializerTests(TestCase): def setUp(self) -> None: super().setUp() self.books = Books( book=[ BookForm( id="bk001", author="Hightower, Kim", title="The First Book", genre="Fiction", price=44.95, pub_date=XmlDate.from_string("2000-10-01"), review="An amazing story of nothing.", ), BookForm( id="bk002", author="Nagata, Suanne", title="Becoming Somebody", genre="Biography", price=29.95, pub_date=XmlDate.from_string("2001-05-15"), review="A masterpiece of the fine art of gossiping.", ), ] ) self.expected = { "book": [ { "author": "Hightower, Kim", "genre": "Fiction", "id": "bk001", "lang": "en", "price": 44.95, "pub_date": "2000-10-01", "review": "An amazing story of nothing.", "title": "The First Book", }, { "author": "Nagata, Suanne", "genre": "Biography", "id": "bk002", "lang": "en", "price": 29.95, "pub_date": "2001-05-15", "review": "A masterpiece of the fine art of gossiping.", "title": "Becoming Somebody", }, ] } def test_render(self) -> None: serializer = JsonSerializer(dict_factory=DictFactory.FILTER_NONE) actual = serializer.render(self.books) self.assertEqual(self.expected, json.loads(actual)) def test_indent(self) -> None: config = SerializerConfig(indent=" ") serializer = JsonSerializer(config=config) actual = serializer.render(self.books) expected = f"""{{ "book": [ {{ "author": "{self.expected["book"][0]["author"]}", "title": "{self.expected["book"][0]["title"]}", "genre": "{self.expected["book"][0]["genre"]}", "price": {self.expected["book"][0]["price"]}, "pub_date": "{self.expected["book"][0]["pub_date"]}", "review": "{self.expected["book"][0]["review"]}", "id": "{self.expected["book"][0]["id"]}", "lang": "en" }}, {{ "author": "{self.expected["book"][1]["author"]}", "title": "{self.expected["book"][1]["title"]}", "genre": "{self.expected["book"][1]["genre"]}", "price": {self.expected["book"][1]["price"]}, "pub_date": "{self.expected["book"][1]["pub_date"]}", "review": "{self.expected["book"][1]["review"]}", "id": "{self.expected["book"][1]["id"]}", "lang": "en" }} ] }}""" self.assertEqual(expected, actual) tefra-xsdata-cf7afe8/tests/formats/dataclass/serializers/test_mixins.py000066400000000000000000001120651526437326600267350ustar00rootroot00000000000000from collections.abc import Generator from dataclasses import dataclass, field, make_dataclass from io import StringIO from unittest import TestCase from xml.etree.ElementTree import QName from xml.sax import ContentHandler from xml.sax.saxutils import XMLGenerator from tests.fixtures.books import BookForm from tests.fixtures.datatypes import Telephone from tests.fixtures.models import AttrsType, Paragraph, SequentialType, Span, TypeA from xsdata.exceptions import SerializerError, XmlContextError, XmlWriterError from xsdata.formats.dataclass.models.elements import XmlType from xsdata.formats.dataclass.models.generics import AnyElement, DerivedElement from xsdata.formats.dataclass.serializers import XmlSerializer from xsdata.formats.dataclass.serializers.config import SerializerConfig from xsdata.formats.dataclass.serializers.mixins import ( EventGenerator, XmlWriter, ) from xsdata.models.datatype import XmlDate from xsdata.models.enums import DataType, QNames from xsdata.utils.testing import XmlVarFactory # Default values for BookForm required fields BOOK_DEFAULTS = { "author": "Test Author", "title": "Test Title", "genre": "Fiction", "price": 9.99, "pub_date": XmlDate(2020, 1, 1), "review": "Test review", } class XmlWriterImpl(XmlWriter): def build_handler(self) -> ContentHandler: return XMLGenerator( self.output, encoding="UTF-8", short_empty_elements=True, ) class XmlWriterTests(TestCase): def setUp(self) -> None: super().setUp() output = StringIO() config = SerializerConfig() self.writer = XmlWriterImpl(output=output, config=config, ns_map={}) def test_write(self) -> None: events = iter( [ ("start", "{http://www.w3.org/1999/xhtml}p"), ("attr", "class", "section"), ("data", "total:"), ("data", 105.22), ("start", "{http://www.w3.org/1999/xhtml}br"), ("end", "{http://www.w3.org/1999/xhtml}br"), ("end", "{http://www.w3.org/1999/xhtml}p"), ] ) self.writer.write(events) lines = self.writer.output.getvalue().splitlines() self.assertEqual(2, len(lines)) self.assertEqual('', lines[0]) self.assertEqual( ( '' "total:105.22" ), lines[1], ) def test_convert_with_schema_location(self) -> None: self.writer.config.schema_location = "foo bar" events = iter( [ ("start", "root"), ("end", "root"), ] ) self.writer.write(events) lines = self.writer.output.getvalue().splitlines() self.assertEqual( ( '' ), lines[1], ) def test_convert_with_no_namespace_schema_location(self) -> None: self.writer.config.no_namespace_schema_location = "foo.xsd" events = iter( [ ("start", "root"), ("end", "root"), ] ) self.writer.write(events) lines = self.writer.output.getvalue().splitlines() self.assertEqual( ( '' ), lines[1], ) def test_convert_with_unhandled_event_raises_exception(self) -> None: events = iter([("reverse", "p")]) with self.assertRaises(XmlWriterError) as cm: self.writer.write(events) self.assertEqual("Unhandled event: `reverse`", str(cm.exception)) def test_convert_removes_xsi_nil_if_necessary(self) -> None: events = iter( [ ("start", "root"), ("start", "a"), ("attr", QNames.XSI_NIL, "true"), ("end", "a"), ("start", "a"), ("attr", QNames.XSI_NIL, "true"), ("data", "0"), ("end", "a"), ("start", "a"), ("attr", QNames.XSI_NIL, "true"), ("data", ""), ("end", "a"), ("start", "a"), ("attr", QNames.XSI_NIL, "true"), ("data", None), ("end", "a"), ("start", "a"), ("attr", QNames.XSI_NIL, "true"), ("data", [""]), ("end", "a"), ("start", "a"), ("attr", QNames.XSI_NIL, "true"), ("data", []), ("end", "a"), ("end", "root"), ] ) self.writer.write(events) lines = self.writer.output.getvalue().splitlines() expected = ( "" '' "0" "" '' "" '' "" ) self.assertEqual(expected, lines[1]) def test_convert_resets_default_namespace_for_unqualified_elements(self) -> None: events = iter( [ ("start", "{a}a"), ("start", "b"), ("data", "foo"), ("start", "b"), ("end", "{a}a"), ] ) self.writer.ns_map = {None: "a"} self.writer.write(events) lines = self.writer.output.getvalue().splitlines() self.assertEqual('foo', lines[1]) def test_add_attribute(self) -> None: with self.assertRaises(XmlWriterError) as cm: self.writer.add_attribute("foo", "bar") self.assertEqual("Empty pending tag.", str(cm.exception)) self.writer.start_tag("a") self.writer.add_attribute("a", "bar") self.writer.add_attribute("b", True) self.writer.add_attribute("c", "{") self.writer.add_attribute("d", "{a}b") self.writer.add_attribute(QNames.XSI_TYPE, str(DataType.STRING)) expected = { (None, "a"): "bar", (None, "b"): "true", (None, "c"): "{", (None, "d"): "{a}b", ("http://www.w3.org/2001/XMLSchema-instance", "type"): "xs:string", } self.assertEqual(expected, self.writer.attrs) def test_is_xsi_type(self) -> None: self.assertFalse(self.writer.is_xsi_type("key", 1)) self.assertFalse(self.writer.is_xsi_type(QNames.XSI_TYPE, 1)) self.assertFalse(self.writer.is_xsi_type(QNames.XSI_TYPE, "a")) self.assertTrue(self.writer.is_xsi_type(QNames.XSI_TYPE, "{b}a")) self.assertFalse(self.writer.is_xsi_type("type", "{b}a")) self.assertTrue(self.writer.is_xsi_type("type", str(DataType.STRING))) class EventGeneratorTests(TestCase): def setUp(self) -> None: self.generator = EventGenerator() def test_with_primitive_wrapper(self) -> None: @dataclass class PrimitiveWrapper: primitive_list: list[str] = field( metadata={ "wrapper": "PrimitiveList", "type": "Element", "name": "Value", } ) obj = PrimitiveWrapper(primitive_list=["Value 1", "Value 2"]) events = self.generator.generate(obj) expected = [ ("start", "PrimitiveWrapper"), ("start", "PrimitiveList"), ("start", "Value"), ("data", "Value 1"), ("end", "Value"), ("start", "Value"), ("data", "Value 2"), ("end", "Value"), ("end", "PrimitiveList"), ("end", "PrimitiveWrapper"), ] self.assertEqual(expected, list(events)) def test_with_wrapper_element(self) -> None: @dataclass class ElementObject: content: str = field(metadata={"type": "Element"}) @dataclass class ElementWrapper: elements: list[ElementObject] = field( metadata={"wrapper": "Elements", "type": "Element", "name": "Object"} ) obj = ElementWrapper( elements=[ElementObject(content="Hello"), ElementObject(content="World")] ) events = self.generator.generate(obj) expected = [ ("start", "ElementWrapper"), ("start", "Elements"), ("start", "Object"), ("start", "content"), ("data", "Hello"), ("end", "content"), ("end", "Object"), ("start", "Object"), ("start", "content"), ("data", "World"), ("end", "content"), ("end", "Object"), ("end", "Elements"), ("end", "ElementWrapper"), ] self.assertEqual(expected, list(events)) def test_with_wrapper_namespace(self) -> None: @dataclass class NamespaceWrapper: items: list[str] = field( metadata={ "wrapper": "Items", "type": "Element", "name": "item", "namespace": "ns", } ) obj = NamespaceWrapper(items=["a", "b"]) events = self.generator.generate(obj) expected = [ ("start", "NamespaceWrapper"), ("start", "{ns}Items"), ("start", "{ns}item"), ("data", "a"), ("end", "{ns}item"), ("start", "{ns}item"), ("data", "b"), ("end", "{ns}item"), ("end", "{ns}Items"), ("end", "NamespaceWrapper"), ] self.assertEqual(expected, list(events)) def test_with_derived_element(self) -> None: book = BookForm(**BOOK_DEFAULTS, id="123") obj = DerivedElement(qname="item", value=book) result = self.generator.generate(obj) expected = [ ("start", "item"), ("attr", "id", "123"), ("attr", "lang", "en"), ("attr", QNames.XSI_TYPE, "{urn:books}BookForm"), ("start", "author"), ("data", "Test Author"), ("end", "author"), ("start", "title"), ("data", "Test Title"), ("end", "title"), ("start", "genre"), ("data", "Fiction"), ("end", "genre"), ("start", "price"), ("data", "9.99"), ("end", "price"), ("start", "pub_date"), ("data", "2020-01-01"), ("end", "pub_date"), ("start", "review"), ("data", "Test review"), ("end", "review"), ("end", "item"), ] self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) obj = DerivedElement(qname="{urn:books}BookForm", value=book) result = self.generator.generate(obj) expected = [ ("start", "{urn:books}BookForm"), ("attr", "id", "123"), ("attr", "lang", "en"), ("start", "author"), ("data", "Test Author"), ("end", "author"), ("start", "title"), ("data", "Test Title"), ("end", "title"), ("start", "genre"), ("data", "Fiction"), ("end", "genre"), ("start", "price"), ("data", "9.99"), ("end", "price"), ("start", "pub_date"), ("data", "2020-01-01"), ("end", "pub_date"), ("start", "review"), ("data", "Test review"), ("end", "review"), ("end", "{urn:books}BookForm"), ] self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) def test_with_dataclass(self) -> None: book = BookForm( author="Test Author", title="Misterioso: A Crime Novel", genre="Fiction", price=19.5, pub_date=XmlDate(2020, 1, 1), review="Test review", id="123", ) result = self.generator.generate(book) expected = [ ("start", "BookForm"), ("attr", "id", "123"), ("attr", "lang", "en"), ("start", "author"), ("data", "Test Author"), ("end", "author"), ("start", "title"), ("data", "Misterioso: A Crime Novel"), ("end", "title"), ("start", "genre"), ("data", "Fiction"), ("end", "genre"), ("start", "price"), ("data", "19.5"), ("end", "price"), ("start", "pub_date"), ("data", "2020-01-01"), ("end", "pub_date"), ("start", "review"), ("data", "Test review"), ("end", "review"), ("end", "BookForm"), ] self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) def test_convert_dataclass_can_overwrite_params(self) -> None: book = BookForm( author="Test Author", title="Misterioso: A Crime Novel", genre="Fiction", price=19.5, pub_date=XmlDate(2020, 1, 1), review="Test review", id="123", ) result = self.generator.convert_dataclass( book, "xsdata", "book", True, "foo:book" ) expected = [ ("start", "book"), ("attr", "id", "123"), ("attr", "lang", "en"), ("attr", QNames.XSI_TYPE, "foo:book"), ("attr", QNames.XSI_NIL, "true"), ("start", "author"), ("data", "Test Author"), ("end", "author"), ("start", "title"), ("data", "Misterioso: A Crime Novel"), ("end", "title"), ("start", "genre"), ("data", "Fiction"), ("end", "genre"), ("start", "price"), ("data", "19.5"), ("end", "price"), ("start", "pub_date"), ("data", "2020-01-01"), ("end", "pub_date"), ("start", "review"), ("data", "Test review"), ("end", "review"), ("end", "book"), ] self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) def test_convert_dataclass_with_empty_content_in_nillable_field(self) -> None: obj = AttrsType(index=1, attrs={}) result = self.generator.convert_dataclass(obj, qname="a", nillable=True) expected = [ ("start", "a"), ("attr", "index", "1"), ("attr", "fixed", "ignored"), ("end", "a"), ] self.assertEqual(expected, list(result)) def test_convert_dataclass_with_no_dataclass(self) -> None: with self.assertRaises(XmlContextError) as cm: next(self.generator.convert_dataclass(1)) self.assertEqual("Type '' is not a dataclass.", str(cm.exception)) def test_convert_mixed_content(self) -> None: var = XmlVarFactory.create(xml_type=XmlType.WILDCARD, name="a", mixed=True) book = BookForm(**BOOK_DEFAULTS, id="123") ebook = DerivedElement("ebook", BookForm(**BOOK_DEFAULTS, id="123")) value = ["text", AnyElement(qname="br"), book, ebook, "tail"] result = self.generator.convert_value(value, var, "xsdata") expected = [ ("data", "text"), ("start", "br"), ("data", None), ("end", "br"), ("start", "{xsdata}BookForm"), ("attr", "id", "123"), ("attr", "lang", "en"), ("start", "author"), ("data", "Test Author"), ("end", "author"), ("start", "title"), ("data", "Test Title"), ("end", "title"), ("start", "genre"), ("data", "Fiction"), ("end", "genre"), ("start", "price"), ("data", "9.99"), ("end", "price"), ("start", "pub_date"), ("data", "2020-01-01"), ("end", "pub_date"), ("start", "review"), ("data", "Test review"), ("end", "review"), ("end", "{xsdata}BookForm"), ("start", "ebook"), ("attr", "id", "123"), ("attr", "lang", "en"), ("attr", QNames.XSI_TYPE, QName("{urn:books}BookForm")), ("start", "author"), ("data", "Test Author"), ("end", "author"), ("start", "title"), ("data", "Test Title"), ("end", "title"), ("start", "genre"), ("data", "Fiction"), ("end", "genre"), ("start", "price"), ("data", "9.99"), ("end", "price"), ("start", "pub_date"), ("data", "2020-01-01"), ("end", "pub_date"), ("start", "review"), ("data", "Test review"), ("end", "review"), ("end", "ebook"), ("data", "tail"), ] self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) def test_convert_data(self) -> None: var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="a") expected = [("data", "123")] result = self.generator.convert_value("123", var, "xsdata") self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) def test_convert_tokens(self) -> None: var = XmlVarFactory.create( xml_type=XmlType.ELEMENT, local_name="a", tokens_factory=list, nillable=False, ) result = self.generator.convert_value(None, var, "xsdata") self.assertIsInstance(result, Generator) self.assertListEqual([], list(result)) result = self.generator.convert_value([], var, "xsdata") self.assertIsInstance(result, Generator) self.assertListEqual([], list(result)) expected = [ ("start", "a"), ("attr", "{http://www.w3.org/2001/XMLSchema-instance}nil", "true"), ("data", []), ("end", "a"), ] var.nillable = True result = self.generator.convert_value([], var, "xsdata") self.assertIsInstance(result, Generator) self.assertListEqual(expected, list(result)) expected = [("start", "a"), ("data", ["1", QName("{a}b"), "3"]), ("end", "a")] result = self.generator.convert_value([1, QName("{a}b"), 3], var, "xsdata") self.assertEqual(expected, list(result)) expected = [ ("start", "a"), ("data", ["1", "2", "3"]), ("end", "a"), ("start", "a"), ("data", ["4", "5", "6"]), ("end", "a"), ] result = self.generator.convert_value([[1, 2, 3], [4, 5, 6]], var, "xsdata") self.assertEqual(expected, list(result)) var = XmlVarFactory.create( xml_type=XmlType.ELEMENT, name="a", tokens=True, nillable=True ) expected = [ ("start", "a"), ("attr", QNames.XSI_NIL, "true"), ("data", []), ("end", "a"), ] result = self.generator.convert_value([], var, "xsdata") self.assertEqual(expected, list(result)) def test_convert_any_type_with_primitive(self) -> None: var = XmlVarFactory.create(xml_type=XmlType.WILDCARD, name="a") expected = [("data", "str")] result = self.generator.convert_value("str", var, "xsdata") self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) def test_convert_any_type_with_primitive_element(self) -> None: var = XmlVarFactory.create(xml_type=XmlType.ELEMENT, name="a", types=(object,)) expected = [ ("start", "a"), ("data", "str"), ("end", "a"), ] result = self.generator.convert_value("str", var, "xsdata") self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) def test_convert_any_type_with_any_element(self) -> None: var = XmlVarFactory.create(xml_type=XmlType.WILDCARD, name="a") value = AnyElement( qname="a", text="b", tail="c", attributes={"d": 1, "e": 2}, children=[AnyElement(text="g"), "h"], ) expected = [ ("start", "a"), ("attr", "d", 1), ("attr", "e", 2), ("data", "b"), ("data", "g"), ("data", "h"), ("end", "a"), ("data", "c"), ] result = self.generator.convert_value(value, var, "xsdata") self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) def test_convert_any_type_with_derived_element_primitive(self) -> None: var = XmlVarFactory.create(xml_type=XmlType.WILDCARD, name="a") value = DerivedElement(qname="a", value=1) expected = [ ("start", "a"), ("attr", QNames.XSI_TYPE, QName(str(DataType.SHORT))), ("data", 1), ("end", "a"), ] result = self.generator.convert_value(value, var, "xsdata") self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) def test_convert_any_type_with_derived_element_dataclass(self) -> None: var = XmlVarFactory.create(xml_type=XmlType.WILDCARD, name="a") value = DerivedElement( qname="a", value=BookForm( author="Test Author", title="def", genre="Fiction", price=9.99, pub_date=XmlDate(2020, 1, 1), review="Test review", ), type="{urn:books}BookForm", ) expected = [ ("start", "a"), ("attr", "lang", "en"), ("attr", QNames.XSI_TYPE, QName("{urn:books}BookForm")), ("start", "author"), ("data", "Test Author"), ("end", "author"), ("start", "title"), ("data", "def"), ("end", "title"), ("start", "genre"), ("data", "Fiction"), ("end", "genre"), ("start", "price"), ("data", "9.99"), ("end", "price"), ("start", "pub_date"), ("data", "2020-01-01"), ("end", "pub_date"), ("start", "review"), ("data", "Test review"), ("end", "review"), ("end", "a"), ] result = self.generator.convert_value(value, var, "xsdata") self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) def test_convert_xsi_type(self) -> None: var = XmlVarFactory.create( xml_type=XmlType.ELEMENT, name="a", types=(BookForm,), ) value = BookForm(**BOOK_DEFAULTS, id="123") expected = [ ("start", "a"), ("attr", "id", "123"), ("attr", "lang", "en"), ("start", "author"), ("data", "Test Author"), ("end", "author"), ("start", "title"), ("data", "Test Title"), ("end", "title"), ("start", "genre"), ("data", "Fiction"), ("end", "genre"), ("start", "price"), ("data", "9.99"), ("end", "price"), ("start", "pub_date"), ("data", "2020-01-01"), ("end", "pub_date"), ("start", "review"), ("data", "Test review"), ("end", "review"), ("end", "a"), ] result = self.generator.convert_value(value, var, "xsdata") self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) def test_convert_xsi_type_with_derived_class(self) -> None: var = XmlVarFactory.create( xml_type=XmlType.ELEMENT, name="a", types=(BookForm,), ) eBookClass = make_dataclass("eBook", [], bases=(BookForm,)) ebook = eBookClass(**BOOK_DEFAULTS, id="123") expected = [ ("start", "a"), ("attr", "id", "123"), ("attr", "lang", "en"), ("attr", QNames.XSI_TYPE, QName("eBook")), ("start", "author"), ("data", "Test Author"), ("end", "author"), ("start", "title"), ("data", "Test Title"), ("end", "title"), ("start", "genre"), ("data", "Fiction"), ("end", "genre"), ("start", "price"), ("data", "9.99"), ("end", "price"), ("start", "pub_date"), ("data", "2020-01-01"), ("end", "pub_date"), ("start", "review"), ("data", "Test review"), ("end", "review"), ("end", "a"), ] result = self.generator.convert_value(ebook, var, "xsdata") self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) def test_convert_xsi_type_with_illegal_derived_class(self) -> None: var = XmlVarFactory.create( xml_type=XmlType.ELEMENT, name="a", types=(BookForm,), ) value = TypeA(1) result = self.generator.convert_value(value, var, "xsdata") with self.assertRaises(SerializerError) as cm: list(result) self.assertEqual("TypeA is not derived from BookForm", str(cm.exception)) def test_convert_element(self) -> None: var = XmlVarFactory.create(xml_type=XmlType.ELEMENT, name="a") expected = [ ("start", "a"), ("data", "123"), ("end", "a"), ] result = self.generator.convert_value("123", var, "xsdata") self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) def test_convert_element_with_nillable_true(self) -> None: var = XmlVarFactory.create(xml_type=XmlType.ELEMENT, name="a", nillable=True) expected = [ ("start", "a"), ("attr", QNames.XSI_NIL, "true"), ("data", None), ("end", "a"), ] result = self.generator.convert_value(None, var, "xsdata") self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) def test_convert_element_with_any_type_var(self) -> None: var = XmlVarFactory.create( xml_type=XmlType.ELEMENT, name="a", types=(object,), any_type=True, ) expected = [ ("start", "a"), ("attr", QNames.XSI_TYPE, QName(str(DataType.SHORT))), ("data", "123"), ("end", "a"), ] result = self.generator.convert_value(123, var, "xsdata") self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) def test_convert_element_with_any_type_var_ignore_xs_string(self) -> None: var = XmlVarFactory.create( xml_type=XmlType.ELEMENT, name="a", types=(object,), any_type=True, ) expected = [ ("start", "a"), ("data", ""), ("end", "a"), ] result = self.generator.convert_value("", var, "xsdata") self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) expected = [ ("start", "a"), ("data", "123"), ("end", "a"), ] result = self.generator.convert_value("123", var, "xsdata") self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) def test_convert_choice_with_derived_primitive_value(self) -> None: var = XmlVarFactory.create( xml_type=XmlType.ELEMENTS, name="compound", elements={ "a": XmlVarFactory.create( xml_type=XmlType.ELEMENT, name="a", types=(int,) ) }, ) value = DerivedElement(qname="a", value=1) expected = [ ("start", "a"), ("data", "1"), ("end", "a"), ] result = self.generator.convert_value(value, var, "xsdata") self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) def test_convert_choice_with_derived_dataclass(self) -> None: var = XmlVarFactory.create( xml_type=XmlType.ELEMENTS, name="compound", elements={ "a": XmlVarFactory.create( xml_type=XmlType.ELEMENT, name="a", types=(SequentialType,) ) }, ) value = DerivedElement(qname="a", value=SequentialType("foo")) expected = [ ("start", "a"), ("attr", "a0", "foo"), ("start", "{xsdata}x5"), ("attr", "{http://www.w3.org/2001/XMLSchema-instance}nil", "true"), ("data", None), ("end", "{xsdata}x5"), ("end", "a"), ] result = self.generator.convert_value(value, var, "xsdata") self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) def test_convert_choice_with_generic_object(self) -> None: var = XmlVarFactory.create( xml_type=XmlType.ELEMENTS, name="compound", elements={ "a": XmlVarFactory.create( xml_type=XmlType.ELEMENT, name="a", types=(int,) ) }, ) value = AnyElement(qname="a", text="1") expected = [ ("start", "a"), ("data", "1"), ("end", "a"), ] result = self.generator.convert_value(value, var, "xsdata") self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) def test_convert_choice_with_raw_value(self) -> None: var = XmlVarFactory.create( xml_type=XmlType.ELEMENTS, name="compound", elements={ "a": XmlVarFactory.create( xml_type=XmlType.ELEMENT, name="a", types=(int,) ), "b": XmlVarFactory.create( xml_type=XmlType.ELEMENT, name="b", types=(int,), tokens_factory=list, ), }, ) expected = [ ("start", "a"), ("data", "1"), ("end", "a"), ("start", "b"), ("data", ["1", "2"]), ("end", "b"), ] result = self.generator.convert_value([1, [1, 2]], var, "xsdata") self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) def test_convert_choice_when_no_matching_choice_exists_but_value_is_model( self, ) -> None: var = XmlVarFactory.create( xml_type=XmlType.ELEMENTS, name="compound", elements={ "a": XmlVarFactory.create( xml_type=XmlType.WILDCARD, name="a", types=(object,) ) }, ) eBookClass = make_dataclass("eBook", [], bases=(BookForm,)) ebook = eBookClass(**BOOK_DEFAULTS) expected = [ ("start", "eBook"), ("attr", "lang", "en"), ("start", "author"), ("data", "Test Author"), ("end", "author"), ("start", "title"), ("data", "Test Title"), ("end", "title"), ("start", "genre"), ("data", "Fiction"), ("end", "genre"), ("start", "price"), ("data", "9.99"), ("end", "price"), ("start", "pub_date"), ("data", "2020-01-01"), ("end", "pub_date"), ("start", "review"), ("data", "Test review"), ("end", "review"), ("end", "eBook"), ] result = self.generator.convert_value(ebook, var, None) self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) def test_convert_choice_when_no_matching_choice_exists(self) -> None: var = XmlVarFactory.create( xml_type=XmlType.ELEMENTS, name="compound", elements={ "a": XmlVarFactory.create( xml_type=XmlType.ELEMENT, name="a", types=(float,) ) }, ) with self.assertRaises(SerializerError) as cm: result = self.generator.convert_value(1, var, "xsdata") next(result) msg = "XmlElements undefined choice: `compound` for ``" self.assertEqual(msg, str(cm.exception)) def test_convert_value_with_list_value(self) -> None: var = XmlVarFactory.create(xml_type=XmlType.ELEMENT, name="a", factory=list) value = [True, False] expected = [ ("start", "a"), ("data", "true"), ("end", "a"), ("start", "a"), ("data", "false"), ("end", "a"), ] result = self.generator.convert_value(value, var, "xsdata") self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) def test_next_value(self) -> None: obj = SequentialType(x0=1, x1=[2, 3, 4, None], x2=[6, 7], x3=[9], x4=10) meta = self.generator.context.build(SequentialType) x0 = meta.text x1 = next(meta.find_children("x1")) x2 = next(meta.find_children("x2")) x3 = next(meta.find_children("x3")) x4 = next(meta.find_children("x4")) x5 = next(meta.find_children("x5")) actual = self.generator.next_value(obj, meta) expected = [ (x0, 1), (x1, 2), (x2, 6), (x1, 3), (x2, 7), (x1, 4), (x3, 9), (x4, 10), (x5, None), ] self.assertIsInstance(actual, Generator) self.assertEqual(expected, list(actual)) def test_next_attribute(self) -> None: obj = SequentialType(a0="foo", a1={"b": "c", "d": "e"}) meta = self.generator.context.build(SequentialType) actual = self.generator.next_attribute(obj, meta, False, None, False) expected = [ ("a0", "foo"), ("b", "c"), ("d", "e"), ] self.assertIsInstance(actual, Generator) self.assertEqual(expected, list(actual)) actual = self.generator.next_attribute(obj, meta, True, "xs:bool", False) expected.extend( [ (QNames.XSI_TYPE, "xs:bool"), (QNames.XSI_NIL, "true"), ] ) self.assertEqual(expected, list(actual)) meta.attributes["a0"].required = False meta.attributes["a0"].default = "foo" actual = self.generator.next_attribute(obj, meta, False, None, True) expected = [ ("b", "c"), ("d", "e"), ] self.assertIsInstance(actual, Generator) self.assertEqual(expected, list(actual)) def test_with_mixed_content(self) -> None: obj = Paragraph() obj.content.append(AnyElement(qname="b", text="Mr.")) obj.content.append(Span("chris")) obj.content.append("!") result = self.generator.generate(obj) expected = [ ("start", "p"), ("start", "b"), ("data", "Mr."), ("end", "b"), ("start", "span"), ("data", "chris"), ("end", "span"), ("data", "!"), ("end", "p"), ] self.assertEqual(expected, list(result)) obj = Paragraph() obj.content.append("Hi ") obj.content.append(AnyElement(qname="b", text="Mr.")) obj.content.append(Span("chris")) obj.content.append("!") result = self.generator.generate(obj) expected = [ ("start", "p"), ("data", "Hi "), ("start", "b"), ("data", "Mr."), ("end", "b"), ("start", "span"), ("data", "chris"), ("end", "span"), ("data", "!"), ("end", "p"), ] self.assertEqual(expected, list(result)) def test_encode_primitive_with_namedtuple(self) -> None: var = XmlVarFactory.create(types=(Telephone,)) actual = XmlSerializer.encode_primitive(Telephone(30, 234, 56783), var) self.assertEqual("30-234-56783", actual) tefra-xsdata-cf7afe8/tests/formats/dataclass/serializers/test_xml.py000066400000000000000000000050421526437326600262220ustar00rootroot00000000000000from dataclasses import dataclass, field from unittest import TestCase from tests.fixtures.books.fixtures import books from xsdata.formats.dataclass.parsers import XmlParser from xsdata.formats.dataclass.serializers import XmlSerializer from xsdata.formats.dataclass.serializers.config import SerializerConfig @dataclass class Country: code: str = field(metadata={"type": "Attribute"}) @dataclass class Address: country: Country | None = field( default=None, metadata={"type": "Element", "nillable": True} ) class XmlSerializerTests(TestCase): def setUp(self) -> None: config = SerializerConfig(indent=" ") self.serializer = XmlSerializer(config=config) super().setUp() def test_render(self) -> None: result = self.serializer.render(books, ns_map={None: "urn:books"}) expected = ( '\n' '\n' ' \n' " Hightower, Kim\n" " The First Book\n" " Fiction\n" " 44.95\n" " 2000-10-01\n" " An amazing story of nothing.\n" " \n" ' \n' " Nagata, Suanne\n" " Becoming Somebody\n" " Biography\n" " 33.95\n" " 2001-01-10\n" " A masterpiece of the fine art of gossiping.\n" " \n" "\n" ) self.assertEqual(expected, result) def test_render_nillable_field_with_attributes_only_type(self) -> None: serializer = XmlSerializer(config=SerializerConfig(xml_declaration=False)) present = serializer.render(Address(country=Country(code="DE"))) absent = serializer.render(Address()) self.assertEqual('
', present) self.assertEqual( "
" '' "
", absent, ) parser = XmlParser() self.assertEqual( Country(code="DE"), parser.from_string(present, Address).country ) self.assertIsNone(parser.from_string(absent, Address).country) tefra-xsdata-cf7afe8/tests/formats/dataclass/serializers/writers/000077500000000000000000000000001526437326600255075ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/formats/dataclass/serializers/writers/__init__.py000066400000000000000000000000001526437326600276060ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/formats/dataclass/serializers/writers/test_lxml.py000066400000000000000000000065211526437326600301000ustar00rootroot00000000000000from dataclasses import make_dataclass from unittest import TestCase import lxml from tests import fixtures_dir from tests.fixtures.books.fixtures import books from xsdata.formats.dataclass.serializers import TreeSerializer, XmlSerializer from xsdata.formats.dataclass.serializers.config import SerializerConfig from xsdata.formats.dataclass.serializers.writers import ( LxmlEventWriter, ) class LxmlEventWriterTests(TestCase): def setUp(self) -> None: config = SerializerConfig(indent=" ") self.serializer = XmlSerializer(config=config, writer=LxmlEventWriter) def test_render(self) -> None: actual = self.serializer.render(books) expected = fixtures_dir.joinpath("books/books_auto_ns.xml").read_text() self.assertEqual(expected, actual) def test_render_with_provided_namespaces(self) -> None: actual = self.serializer.render(books, {"brk": "urn:books"}) expected = fixtures_dir.joinpath("books/books.xml").read_text() self.assertEqual(expected, actual) def test_render_with_default_namespace_prefix(self) -> None: actual = self.serializer.render(books, {None: "urn:books"}) expected = fixtures_dir.joinpath("books/books_default_ns.xml").read_text() _xml_declaration, actual = actual.split("\n", 1) _, expected = expected.split("\n", 1) self.assertEqual(expected, actual) def test_encoding(self) -> None: self.serializer.config.encoding = "ISO-8859-1" x = make_dataclass("x", [("value", str)]) obj = x("á, é, í, ó") actual = self.serializer.render(obj) expected = '\ná, é, í, ó\n' self.assertEqual(expected, actual) def test_declaration_disabled(self) -> None: self.serializer.config.xml_declaration = False actual = self.serializer.render(books, {None: "urn:books"}) expected = fixtures_dir.joinpath("books/books_default_ns.xml").read_text() _xml_declaration, expected = expected.split("\n", 1) self.assertEqual(expected, actual) def test_no_indent(self) -> None: self.serializer.config.indent = None actual = self.serializer.render(books) expected = fixtures_dir.joinpath("books/books_auto_ns.xml").read_text() _, actual = actual.split("\n", 1) _, expected = expected.split("\n", 1) self.assertEqual(expected.replace(" ", "").replace("\n", ""), actual) class LxmlTreeBuilderTests(TestCase): def setUp(self) -> None: super().setUp() self.serializer = TreeSerializer() self.serializer.config.indent = " " def test_render(self) -> None: tree = self.serializer.render(books) actual = lxml.etree.tostring(tree).decode() expected = fixtures_dir.joinpath("books/books_auto_ns.xml").read_text() expected = "\n".join(expected.splitlines()[1:]) self.assertEqual(expected, actual) def test_render_with_no_indent(self) -> None: self.serializer.config.indent = "" tree = self.serializer.render(books) lxml.etree.indent(tree, " ") actual = lxml.etree.tostring(tree).decode() expected = fixtures_dir.joinpath("books/books_auto_ns.xml").read_text() expected = "\n".join(expected.splitlines()[1:]) self.assertEqual(expected, actual) tefra-xsdata-cf7afe8/tests/formats/dataclass/serializers/writers/test_native.py000066400000000000000000000044641526437326600304160ustar00rootroot00000000000000from dataclasses import make_dataclass from unittest import TestCase from tests import fixtures_dir from tests.fixtures.books.fixtures import books from xsdata.formats.dataclass.serializers import XmlSerializer from xsdata.formats.dataclass.serializers.config import SerializerConfig from xsdata.formats.dataclass.serializers.writers import XmlEventWriter class XmlEventWriterTests(TestCase): def setUp(self) -> None: config = SerializerConfig(indent=" ") self.serializer = XmlSerializer(config=config, writer=XmlEventWriter) def test_render(self) -> None: actual = self.serializer.render(books) expected = fixtures_dir.joinpath("books/books_auto_ns.xml").read_text() self.assertEqual(expected, actual) def test_render_with_provided_namespaces(self) -> None: actual = self.serializer.render(books, {"brk": "urn:books"}) expected = fixtures_dir.joinpath("books/books.xml").read_text() self.assertEqual(expected, actual) def test_render_with_default_namespace_prefix(self) -> None: actual = self.serializer.render(books, {None: "urn:books"}) expected = fixtures_dir.joinpath("books/books_default_ns.xml").read_text() self.assertEqual(expected, actual) def test_encoding(self) -> None: self.serializer.config.encoding = "ISO-8859-1" x = make_dataclass("x", [("value", str)]) obj = x("á, é, í, ó") actual = self.serializer.render(obj) expected = '\ná, é, í, ó\n' self.assertEqual(expected, actual) def test_declaration_disabled(self) -> None: self.serializer.config.xml_declaration = False actual = self.serializer.render(books, {None: "urn:books"}) expected = fixtures_dir.joinpath("books/books_default_ns.xml").read_text() _xml_declaration, expected = expected.split("\n", 1) self.assertEqual(expected, actual) def test_no_indent(self) -> None: self.serializer.config.indent = None actual = self.serializer.render(books) expected = fixtures_dir.joinpath("books/books_auto_ns.xml").read_text() _, actual = actual.split("\n", 1) _, expected = expected.split("\n", 1) self.assertEqual(expected.replace(" ", "").replace("\n", ""), actual) tefra-xsdata-cf7afe8/tests/formats/dataclass/test_bytecode.py000066400000000000000000000026431526437326600246700ustar00rootroot00000000000000from unittest import TestCase from jinja2 import Environment from jinja2.bccache import Bucket from xsdata.formats.dataclass.bytecode import MemoryBytecodeCache class MemoryBytecodeCacheTests(TestCase): def setUp(self) -> None: super().setUp() self.env = Environment() self.cache = MemoryBytecodeCache() def test_load_bytecode_with_empty_cache(self) -> None: bucket = Bucket(self.env, "foo", "abc") self.cache.load_bytecode(bucket) self.assertIsNone(bucket.code) def test_load_bytecode_after_dump(self) -> None: bucket = Bucket(self.env, "foo", "abc") bucket.code = self.env.compile("{{ bar }}") self.cache.dump_bytecode(bucket) other = Bucket(self.env, "foo", "abc") self.cache.load_bytecode(other) self.assertEqual(bucket.code, other.code) def test_load_bytecode_with_changed_checksum(self) -> None: bucket = Bucket(self.env, "foo", "abc") bucket.code = self.env.compile("{{ bar }}") self.cache.dump_bytecode(bucket) other = Bucket(self.env, "foo", "xyz") self.cache.load_bytecode(other) self.assertIsNone(other.code) def test_clear(self) -> None: bucket = Bucket(self.env, "foo", "abc") bucket.code = self.env.compile("{{ bar }}") self.cache.dump_bytecode(bucket) self.cache.clear() self.assertEqual({}, self.cache.store) tefra-xsdata-cf7afe8/tests/formats/dataclass/test_client.py000066400000000000000000000136401526437326600243470ustar00rootroot00000000000000from dataclasses import asdict, replace from unittest import TestCase, mock from tests.fixtures.calculator import ( Add, CalculatorSoapAdd, CalculatorSoapAddInput, CalculatorSoapAddOutput, ) from tests.fixtures.hello import HelloGetHelloAsString from xsdata.exceptions import ClientValueError from xsdata.formats.dataclass.client import Client, Config, TransportTypes from xsdata.formats.dataclass.parsers import XmlParser from xsdata.formats.dataclass.serializers import XmlSerializer from xsdata.formats.dataclass.transports import DefaultTransport response = """ 7 """ class ClientTests(TestCase): def test__init__(self) -> None: config = Config.from_service(CalculatorSoapAdd, transport="foobar") client = Client(config) self.assertIs(client.parser.context, client.serializer.context) client = Client(config, parser=XmlParser()) self.assertIs(client.parser.context, client.serializer.context) client = Client(config, serializer=XmlSerializer()) self.assertIs(client.parser.context, client.serializer.context) def test_from_service(self) -> None: client = Client.from_service(CalculatorSoapAdd, location="http://testurl.com") actual = asdict(client.config) expected = { "style": "document", "input": CalculatorSoapAddInput, "location": "http://testurl.com", "soap_action": "http://tempuri.org/Add", "output": CalculatorSoapAddOutput, "transport": "http://schemas.xmlsoap.org/soap/http", "encoding": None, } self.assertEqual(expected, actual) @mock.patch.object(DefaultTransport, "post") def test_send_with_dict_params(self, mock_post) -> None: mock_post.return_value = response.encode() client = Client.from_service(CalculatorSoapAdd) params = {"Body": {"Add": {"intA": 3, "intB": 4}}} result = client.send(params, headers={"User-Agent": "xsdata"}) self.assertIsInstance(result, CalculatorSoapAddOutput) self.assertEqual(7, result.body.add_response.add_result) obj = CalculatorSoapAddInput( body=CalculatorSoapAddInput.Body(add=Add(int_a=3, int_b=4)) ) request = client.serializer.render(obj) mock_post.assert_called_once_with( "http://www.dneonline.com/calculator.asmx", data=request, headers={ "User-Agent": "xsdata", "content-type": "text/xml", "SOAPAction": "http://tempuri.org/Add", }, ) @mock.patch.object(DefaultTransport, "post") def test_send_with_instance_object(self, mock_post) -> None: mock_post.return_value = response.encode() client = Client.from_service(CalculatorSoapAdd) obj = CalculatorSoapAddInput( body=CalculatorSoapAddInput.Body(add=Add(int_a=3, int_b=4)) ) result = client.send(obj) self.assertIsInstance(result, CalculatorSoapAddOutput) self.assertEqual(7, result.body.add_response.add_result) request = client.serializer.render(obj) mock_post.assert_called_once_with( "http://www.dneonline.com/calculator.asmx", data=request, headers={ "content-type": "text/xml", "SOAPAction": "http://tempuri.org/Add", }, ) def test_prepare_payload_with_encoding(self) -> None: client = Client.from_service(HelloGetHelloAsString, encoding="utf-8") result = client.prepare_payload( {"Body": {"getHelloAsString": {"arg0": "Χριστόδουλος"}}} ) self.assertIn("Χριστόδουλος".encode(), result) def test_prepare_payload_raises_error_with_type_mismatch(self) -> None: client = Client.from_service(CalculatorSoapAdd) with self.assertRaises(ClientValueError) as cm: client.prepare_payload( CalculatorSoapAddOutput(body=CalculatorSoapAddOutput.Body()) ) self.assertEqual( "Invalid input service type, expected " "`CalculatorSoapAddInput` got `CalculatorSoapAddOutput`", str(cm.exception), ) def test_prepare_headers(self) -> None: config = Config( style="document", location="", transport=TransportTypes.SOAP, soap_action="", input=None, output=None, ) client = Client(config=config) headers = {"foo": "bar"} result = client.prepare_headers(headers) self.assertEqual({"content-type": "text/xml", "foo": "bar"}, result) self.assertEqual(1, len(headers)) config = replace(config, soap_action="add") client = Client(config=config) result = client.prepare_headers({}) self.assertEqual({"SOAPAction": "add", "content-type": "text/xml"}, result) config = replace(config, encoding="utf-8") client = Client(config=config) result = client.prepare_headers({}) self.assertEqual( {"SOAPAction": "add", "content-type": "text/xml; charset=utf-8"}, result ) def test_prepare_headers_raises_error_with_unsupported_binding_transport( self, ) -> None: config = Config.from_service(CalculatorSoapAdd, transport="foobar") client = Client(config=config) with self.assertRaises(ClientValueError) as cm: client.prepare_headers({}) self.assertEqual("Unsupported binding transport: `foobar`", str(cm.exception)) tefra-xsdata-cf7afe8/tests/formats/dataclass/test_compat.py000066400000000000000000000022421526437326600243500ustar00rootroot00000000000000from unittest import TestCase from tests.fixtures.models import TypeA, TypeC from xsdata.exceptions import XmlContextError from xsdata.formats.dataclass.compat import Dataclasses class ClassTypeTests(TestCase): def test_score_object(self) -> None: class_type = Dataclasses() self.assertEqual(-1.0, class_type.score_object(None)) obj = TypeC(1, "1", 1.1) self.assertEqual(5.0, class_type.score_object(obj)) self.assertEqual(-1, class_type.score_object(None)) self.assertEqual(1.0, class_type.score_object("a")) self.assertEqual(1.5, class_type.score_object(2.9)) class DataclassesTests(TestCase): def test_is_model(self) -> None: class_type = Dataclasses() self.assertTrue(class_type.is_model(TypeA)) self.assertTrue(class_type.is_model(TypeA(1))) self.assertFalse(class_type.is_model(1)) def test_verify_model(self) -> None: class_type = Dataclasses() class_type.verify_model(TypeA) with self.assertRaises(XmlContextError) as cm: class_type.verify_model(int) self.assertEqual(f"Type '{int}' is not a dataclass.", str(cm.exception)) tefra-xsdata-cf7afe8/tests/formats/dataclass/test_context.py000066400000000000000000000127251526437326600245600ustar00rootroot00000000000000import copy from dataclasses import make_dataclass from pathlib import Path from unittest import mock from tests.fixtures.artists import Artist, BeginArea from tests.fixtures.books import BookForm, BooksForm from tests.fixtures.models import BaseType, ChoiceType, UnionType from xsdata.formats.dataclass.context import XmlContext from xsdata.models.enums import DataType from xsdata.utils.testing import FactoryTestCase, XmlMetaFactory class XmlContextTests(FactoryTestCase): def setUp(self) -> None: self.ctx = XmlContext() self.ctx.reset() super().setUp() @mock.patch.object(XmlContext, "find_subclass") @mock.patch.object(XmlContext, "build") def test_fetch(self, mock_build, mock_find_subclass) -> None: meta = XmlMetaFactory.create(clazz=Artist, qname="Artist") mock_build.return_value = meta actual = self.ctx.fetch(Artist, "foo") self.assertEqual(meta, actual) self.assertEqual(0, mock_find_subclass.call_count) mock_build.assert_called_once_with(Artist, "foo") @mock.patch.object(XmlContext, "find_subclass") @mock.patch.object(XmlContext, "build") def test_fetch_with_xsi_type_and_subclass_not_found( self, mock_build, mock_find_subclass ) -> None: meta = XmlMetaFactory.create( clazz=Artist, qname="Artist", target_qname="Artist", nillable=False, ) mock_build.return_value = meta mock_find_subclass.return_value = None actual = self.ctx.fetch(Artist, xsi_type="foo") self.assertEqual(meta, actual) mock_find_subclass.assert_called_once_with(Artist, "foo") @mock.patch.object(XmlContext, "find_subclass") @mock.patch.object(XmlContext, "build") def test_fetch_with_xsi_type_and_subclass_found( self, mock_build, mock_find_subclass ) -> None: meta = XmlMetaFactory.create(clazz=Artist) xsi_meta = copy.deepcopy(meta) xsi_meta.qname = "XsiType" mock_build.side_effect = [meta, xsi_meta] mock_find_subclass.return_value = xsi_meta actual = self.ctx.fetch(Artist, xsi_type="foo") self.assertEqual(xsi_meta, actual) mock_find_subclass.assert_called_once_with(Artist, "foo") def test_find(self) -> None: self.assertIsNone(self.ctx.find_type(str(DataType.FLOAT))) self.assertEqual(BookForm, self.ctx.find_type("{urn:books}BookForm")) self.ctx.xsi_cache["{urn:books}BookForm"].append(BooksForm) self.assertEqual(BooksForm, self.ctx.find_type("{urn:books}BookForm")) def test_find_type_by_fields(self) -> None: field_names = {"id", "name", "sort-name"} self.assertEqual(BeginArea, self.ctx.find_type_by_fields(field_names)) field_names.update({"please", "dont", "exist"}) # Test matching with more self.assertIsNone(self.ctx.find_type_by_fields(field_names)) def test_local_names_match_remove_clazz_from_cache_on_error(self) -> None: undefined = make_dataclass("UndefinedType", [("content", "Literal['yes']")]) unsupported = make_dataclass("UndefinedType", [("content", Path)]) uncached = make_dataclass("Uncached", [("content", Path)]) builder = self.ctx.get_builder() for clazz in (undefined, unsupported): meta = builder.build_class_meta(clazz) self.ctx.xsi_cache[meta.target_qname].append(clazz) self.assertFalse(self.ctx.local_names_match({"content"}, undefined)) self.assertFalse(self.ctx.local_names_match({"content"}, unsupported)) self.assertFalse(self.ctx.local_names_match({"content"}, uncached)) total = [x for types in self.ctx.cache.values() for x in types] self.assertEqual(0, len(total)) def test_find_subclass(self) -> None: a = make_dataclass("A", fields=[]) b = make_dataclass("B", fields=[], bases=(a,)) c = make_dataclass("C", fields=[], bases=(a,)) make_dataclass("Other", fields=[]) # Included in the locals self.assertEqual(b, self.ctx.find_subclass(a, "B")) self.assertEqual(b, self.ctx.find_subclass(c, "B")) self.assertIsNone(self.ctx.find_subclass(b, "A")) self.assertIsNone(self.ctx.find_subclass(c, "A")) self.assertIsNone(self.ctx.find_subclass(c, "Unknown")) self.assertIsNone(self.ctx.find_subclass(c, "Other")) def is_binding_model(self) -> None: self.assertTrue(self.ctx.is_binding_model(ChoiceType)) self.ctx.models_package = "xsdata.models" self.assertFalse(self.ctx.is_binding_model(ChoiceType)) def test_is_derived(self) -> None: a = make_dataclass("A", fields=[]) b = make_dataclass("B", fields=[], bases=(a,)) c = make_dataclass("C", fields=[], bases=(a,)) d = make_dataclass("D", fields=[]) self.assertTrue(self.ctx.is_derived(c(), b)) self.assertTrue(self.ctx.is_derived(b(), c)) self.assertTrue(self.ctx.is_derived(a(), b)) self.assertTrue(self.ctx.is_derived(a(), c)) self.assertTrue(self.ctx.is_derived(a(), a)) self.assertFalse(self.ctx.is_derived(a(), d)) self.assertFalse(self.ctx.is_derived(None, d)) def test_build_recursive(self) -> None: self.ctx.build_recursive(ChoiceType) self.assertEqual(6, len(self.ctx.cache)) self.ctx.build_recursive(BaseType) self.assertEqual(8, len(self.ctx.cache)) self.ctx.build_recursive(UnionType) self.assertEqual(8, len(self.ctx.cache)) tefra-xsdata-cf7afe8/tests/formats/dataclass/test_elements.py000066400000000000000000000213761526437326600247120ustar00rootroot00000000000000from dataclasses import make_dataclass from decimal import Decimal from unittest import mock from unittest.case import TestCase from xml.etree.ElementTree import QName from tests.fixtures.models import ( ChoiceType, ExtendedType, Paragraph, TypeA, TypeB, TypeC, TypeD, TypeDuplicate, UnionType, ) from xsdata.formats.dataclass.context import XmlContext from xsdata.formats.dataclass.models.elements import XmlType, XmlVar from xsdata.utils.testing import XmlMetaFactory, XmlVarFactory class XmlValTests(TestCase): def setUp(self) -> None: self.context = XmlContext() def test_property_qname(self) -> None: var = XmlVarFactory.create(name="a", local_name="A", namespaces=("B",)) self.assertEqual("{B}A", var.qname) def test_property_wrapper_qname(self) -> None: var = XmlVarFactory.create( name="a", local_name="A", wrapper="AA", namespaces=("B",) ) self.assertEqual("{B}AA", var.wrapper_qname) def test_property_clazz(self) -> None: var = XmlVarFactory.create(name="foo") self.assertIsNone(var.clazz) var = XmlVarFactory.create(name="foo", types=(TypeA,)) self.assertEqual(TypeA, var.clazz) def test_property_is_clazz_union(self) -> None: var = XmlVarFactory.create(name="foo", types=(TypeA,)) self.assertFalse(var.is_clazz_union) var = XmlVarFactory.create(name="foo", types=(TypeA, int)) self.assertTrue(var.is_clazz_union) def test_property_element_types(self) -> None: meta = self.context.build(ChoiceType) var = meta.choices[0] self.assertEqual( {TypeA, TypeB, int, float, QName, UnionType, Decimal}, var.element_types ) def test_find_choice(self) -> None: var = XmlVarFactory.create( xml_type=XmlType.ELEMENTS, name="foo", elements={ "{a}a": XmlVarFactory.create( xml_type=XmlType.ELEMENT, name="a", namespaces=("a",) ), "b": XmlVarFactory.create(xml_type=XmlType.ELEMENT, name="b"), }, ) self.assertIsNone(var.find_choice("a")) self.assertEqual("a", var.find_choice("{a}a").name) self.assertEqual("b", var.find_choice("b").name) var.elements.clear() var.wildcards = [ XmlVarFactory.create( xml_type=XmlType.WILDCARD, name="target", namespaces=("foo",), ), XmlVarFactory.create( xml_type=XmlType.WILDCARD, name="other", namespaces=("!foo",), ), ] self.assertEqual(var.wildcards[1], var.find_choice("{a}a")) self.assertEqual(var.wildcards[0], var.find_choice("{foo}a")) def test_find_value_choice(self) -> None: meta = self.context.build(ChoiceType) var = meta.choices[0] self.assertEqual(var.elements["tokens"], var.find_value_choice(["1.2"], False)) self.assertIsNone(var.find_value_choice([], False)) self.assertEqual(var.elements["qname"], var.find_value_choice("foo", False)) self.assertEqual(var.elements["int"], var.find_value_choice(1, False)) self.assertEqual(var.elements["a"], var.find_value_choice(TypeA(1), True)) der = make_dataclass("Der", fields=[], bases=(TypeA,)) self.assertEqual(var.elements["a"], var.find_value_choice(der(1), True)) def test_is_optional(self) -> None: var = XmlVarFactory.create(xml_type=XmlType.ATTRIBUTE, name="att") self.assertTrue(var.is_optional(None)) self.assertFalse(var.is_optional("foo")) var.default = lambda: [1, 2, 3] self.assertTrue(var.is_optional([1, 2, 3])) var.required = True self.assertFalse(var.is_optional([1, 2, 3])) def test_match_namespace(self) -> None: var = XmlVarFactory.create(xml_type=XmlType.WILDCARD, name="foo") self.assertTrue(var.match_namespace("a")) var = XmlVarFactory.create( xml_type=XmlType.WILDCARD, name="foo", namespaces=("tns",) ) self.assertFalse(var.match_namespace("a")) self.assertTrue(var.match_namespace("{tns}a")) var = XmlVarFactory.create( xml_type=XmlType.WILDCARD, name="foo", namespaces=("##any",) ) self.assertTrue(var.match_namespace("a")) self.assertTrue(var.match_namespace("{tns}a")) var = XmlVarFactory.create( xml_type=XmlType.WILDCARD, name="foo", namespaces=("",) ) self.assertTrue(var.match_namespace("a")) self.assertFalse(var.match_namespace("{tns}a")) var = XmlVarFactory.create( xml_type=XmlType.WILDCARD, name="foo", namespaces=("!tns",) ) self.assertTrue(var.match_namespace("{foo}a")) self.assertFalse(var.match_namespace("{tns}a")) var.namespace_matches["{tns}cached"] = True self.assertTrue(var.match_namespace("{tns}cached")) class XmlMetaTests(TestCase): def setUp(self) -> None: self.context = XmlContext() self.meta = XmlMetaFactory.create( clazz=TypeA, qname="a", choices=[], wildcards=[], attributes={}, elements={}, any_attributes=[], ) def test__repr__(self) -> None: self.assertIn("XmlMeta(", repr(self.meta)) def test_property_element_types(self) -> None: meta = self.context.build(UnionType) self.assertEqual({TypeA, TypeB, TypeC, TypeD}, meta.element_types) def test_find_attribute(self) -> None: a = XmlVarFactory.create(xml_type=XmlType.ATTRIBUTE, name="a") b = XmlVarFactory.create(xml_type=XmlType.ATTRIBUTE, name="b") self.meta.attributes[a.qname] = a self.meta.attributes[b.qname] = b self.assertEqual(a, self.meta.find_attribute("a")) self.assertEqual(b, self.meta.find_attribute("b")) @mock.patch.object(XmlVar, "match_namespace") def test_find_any_attributes(self, mock_match_namespace) -> None: mock_match_namespace.side_effect = [False, False, False, True] attributes = [ XmlVarFactory.create(xml_type=XmlType.ATTRIBUTES, name="any"), XmlVarFactory.create( xml_type=XmlType.ATTRIBUTES, local_name="other", name="any" ), ] self.meta.any_attributes = attributes self.assertIsNone(self.meta.find_any_attributes("a")) self.assertEqual(attributes[1], self.meta.find_any_attributes("a")) mock_match_namespace.assert_has_calls([mock.call("a") for _ in range(4)]) def test_find_wildcard(self) -> None: wildcards = [ XmlVarFactory.create(xml_type=XmlType.WILDCARD, namespaces=("a",)), XmlVarFactory.create(xml_type=XmlType.WILDCARD, namespaces=("b",)), XmlVarFactory.create( xml_type=XmlType.WILDCARD, namespaces=["##any"], elements={ "{c}root": XmlVarFactory.create( xml_type=XmlType.ELEMENT, name="root", namespaces=("c",) ), }, ), ] self.assertIsNone(self.meta.find_wildcard("a")) self.meta.wildcards = wildcards self.assertIs(wildcards[0], self.meta.find_wildcard("{a}root")) self.assertIs(wildcards[1], self.meta.find_wildcard("{b}root")) self.assertIs( wildcards[2].elements["{c}root"], self.meta.find_wildcard("{c}root") ) self.assertIs(wildcards[2], self.meta.find_wildcard("{c}random")) def test_find_any_wildcard(self) -> None: meta = self.context.build(TypeDuplicate) self.assertIsNone(meta.find_any_wildcard()) meta = self.context.build(ExtendedType) self.assertEqual("wildcard", meta.find_any_wildcard().qname) def test_find_children(self) -> None: meta = self.context.build(TypeDuplicate) self.assertIsNone(next(meta.find_children("a"), None)) self.assertEqual(["x", "x1"], [el.name for el in meta.find_children("x")]) meta = self.context.build(TypeB) self.assertEqual("x", next(meta.find_children("x")).qname) self.assertEqual("y", next(meta.find_children("y")).qname) meta = self.context.build(ChoiceType) self.assertIsNone(next(meta.find_children("404"), None)) self.assertEqual("a", next(meta.find_children("a")).qname) self.assertEqual("b", next(meta.find_children("b")).qname) self.assertEqual("int", next(meta.find_children("int")).qname) meta = self.context.build(Paragraph) self.assertEqual("content", next(meta.find_children("404")).qname) self.assertTrue(next(meta.find_children("content")).is_wildcard) tefra-xsdata-cf7afe8/tests/formats/dataclass/test_filters.py000066400000000000000000001332001526437326600245340ustar00rootroot00000000000000from collections import namedtuple from unittest import mock from tests.fixtures.datatypes import Telephone from xsdata.codegen.models import Restrictions from xsdata.formats.dataclass.filters import Filters from xsdata.models.config import ( DocstringStyle, ExtensionType, GeneratorConfig, GeneratorExtension, GeneratorSubstitution, NameCase, ObjectType, ) from xsdata.models.enums import DataType, Namespace, Tag from xsdata.utils.testing import ( AttrFactory, AttrTypeFactory, ClassFactory, ExtensionFactory, FactoryTestCase, ) type_str = AttrTypeFactory.native(DataType.STRING) type_int = AttrTypeFactory.native(DataType.INT) type_float = AttrTypeFactory.native(DataType.FLOAT) type_decimal = AttrTypeFactory.native(DataType.DECIMAL) type_bool = AttrTypeFactory.native(DataType.BOOLEAN) type_qname = AttrTypeFactory.native(DataType.QNAME) type_tokens = AttrTypeFactory.native(DataType.NMTOKENS) type_datetime = AttrTypeFactory.native(DataType.DATE_TIME) type_duration = AttrTypeFactory.native(DataType.DURATION) class FiltersTests(FactoryTestCase): def setUp(self) -> None: super().setUp() config = GeneratorConfig() self.filters = Filters(config) obj = ClassFactory.create(qname="a") obj_nested = ClassFactory.create(qname="b") obj_nested_nested = ClassFactory.create(qname="c") obj_nested_nested_nested = ClassFactory.create(qname="d") obj_nested_nested_nested.parent = obj_nested_nested obj_nested_nested.parent = obj_nested obj_nested.parent = obj obj.inner.append(obj_nested) obj_nested.inner.append(obj_nested_nested) obj_nested_nested.inner.append(obj_nested_nested_nested) self.obj = obj self.obj_nested = obj_nested self.obj_nested_nested = obj_nested_nested self.obj_nested_nested_nested = obj_nested_nested_nested def test_class_name(self) -> None: self.filters.substitutions[ObjectType.CLASS]["Abc"] = "Cba" self.assertEqual("XsString", self.filters.class_name("xs:string")) self.assertEqual("FooBarBam", self.filters.class_name("foo:bar_bam")) self.assertEqual("List", self.filters.class_name("List")) self.assertEqual("Type", self.filters.class_name(".*")) self.assertEqual("Cbad", self.filters.class_name("abcd")) def test_class_bases(self) -> None: etp = ExtensionType.CLASS self.filters.extensions[etp] = [ GeneratorExtension( type=etp, class_name=".*Bar", import_string="a.b", apply_if_derived=True, prepend=False, ), GeneratorExtension( type=etp, class_name="Foo.*", import_string="a.b", apply_if_derived=True, prepend=True, ), GeneratorExtension( type=etp, class_name="Foo.*", import_string="a.c", apply_if_derived=True, prepend=True, ), GeneratorExtension( type=etp, class_name="Foo.*", import_string="a.d", apply_if_derived=False, prepend=True, ), GeneratorExtension( type=etp, class_name="Nope.*", import_string="a.e", apply_if_derived=True, prepend=False, ), ] target = ClassFactory.create(extensions=ExtensionFactory.list(1)) expected = self.filters.class_bases(target, "FooBar") self.assertEqual(["c", "b", "AttrB"], expected) target.extensions.clear() expected = self.filters.class_bases(target, "FooBar") self.assertEqual(["d", "c", "b"], expected) def test_class_annotations(self) -> None: etp = ExtensionType.DECORATOR self.filters.extensions[etp] = [ GeneratorExtension( type=etp, class_name=".*Bar", import_string="a.b", apply_if_derived=True, prepend=False, ), GeneratorExtension( type=etp, class_name="Foo.*", import_string="a.b", apply_if_derived=True, prepend=True, ), GeneratorExtension( type=etp, class_name="Foo.*", import_string="a.c", apply_if_derived=True, prepend=True, ), GeneratorExtension( type=etp, class_name="Foo.*", import_string="a.d", apply_if_derived=False, prepend=False, ), GeneratorExtension( type=etp, class_name="Foo.*", parent_path=r"Grandpa\.Papa$", import_string="a.d", apply_if_derived=False, prepend=False, ), GeneratorExtension( type=etp, class_name="Nope.*", import_string="a.e", apply_if_derived=True, prepend=False, ), ] target = ClassFactory.create(extensions=ExtensionFactory.list(1)) expected = self.filters.class_annotations(target, "FooBar") self.assertEqual(["@c", "@b", "@dataclass(kw_only=True)"], expected) target.extensions.clear() expected = self.filters.class_annotations(target, "FooBar") self.assertEqual(["@c", "@b", "@dataclass(kw_only=True)", "@d"], expected) self.filters.default_class_annotation = None expected = self.filters.class_annotations(target, "FooBar") self.assertEqual(["@c", "@b", "@d"], expected) self.filters.default_class_annotation = None expected = self.filters.class_annotations(target, "FooBar") self.assertEqual(["@c", "@b", "@d"], expected) def test_field_name(self) -> None: self.filters.substitutions[ObjectType.FIELD]["abc"] = "cba" self.assertEqual("value", self.filters.field_name("", "cls")) self.assertEqual("foo", self.filters.field_name("foo", "cls")) self.assertEqual("foo_bar", self.filters.field_name("foo:bar", "cls")) self.assertEqual("foo_bar", self.filters.field_name("FooBar", "cls")) self.assertEqual("none", self.filters.field_name("None", "cls")) self.assertEqual("br_eak", self.filters.field_name("BrEak", "cls")) self.assertEqual("value_1", self.filters.field_name("1", "cls")) self.assertEqual("value_minus_1_1", self.filters.field_name("-1.1", "cls")) self.assertEqual("cbad", self.filters.field_name("abcd", "cls")) self.filters.field_case = NameCase.ORIGINAL self.assertEqual("foo", self.filters.field_name("@foo", "cls")) def test_constant_name(self) -> None: self.filters.substitutions[ObjectType.FIELD]["ABC"] = "CBA" self.assertEqual("VALUE", self.filters.constant_name("", "cls")) self.assertEqual("FOO", self.filters.constant_name("foo", "cls")) self.assertEqual("FOO_BAR", self.filters.constant_name("foo:bar", "cls")) self.assertEqual("FOO_BAR", self.filters.constant_name("FooBar", "cls")) self.assertEqual("NONE", self.filters.constant_name("None", "cls")) self.assertEqual("BR_EAK", self.filters.constant_name("BrEak", "cls")) self.assertEqual("VALUE_1", self.filters.constant_name("1", "cls")) self.assertEqual("VALUE_MINUS_1", self.filters.constant_name("-1", "cls")) self.assertEqual("CBAD", self.filters.constant_name("ABCD", "cls")) def test_module_name(self) -> None: self.filters.substitutions[ObjectType.MODULE].update( {"http://pypi.org/project/xsdata/": "xsdata"} ) self.assertEqual("foo_bar", self.filters.module_name("fooBar")) self.assertEqual("foo_bar_wtf", self.filters.module_name("fooBar.wtf")) self.assertEqual("mod_1111", self.filters.module_name("1111")) self.assertEqual("xs_string", self.filters.module_name("xs:string")) self.assertEqual("foo_bar_bam", self.filters.module_name("foo:bar_bam")) self.assertEqual("bar_bam", self.filters.module_name("urn:bar_bam")) self.assertEqual( "xsdata", self.filters.module_name("http://pypi.org/project/xsdata/") ) def test_package_name(self) -> None: self.filters.substitutions[ObjectType.PACKAGE]["bam"] = "boom" self.filters.substitutions[ObjectType.PACKAGE]["abc"] = "a.b.c" self.assertEqual( "foo.bar_bar.pkg_1", self.filters.package_name("Foo.BAR_bar.1") ) self.assertEqual("foo.boom.pkg_1", self.filters.package_name("Foo.boom.1")) self.assertEqual("", self.filters.package_name("")) def test_type_name(self) -> None: self.assertEqual("str", self.filters.type_name(type_str)) type_foo_bar_bam = AttrTypeFactory.create(qname="bar_bam") self.assertEqual("BarBam", self.filters.type_name(type_foo_bar_bam)) def test_constant_value(self) -> None: attr = AttrFactory.create( types=[AttrTypeFactory.native(DataType.STRING)], default="foo" ) self.assertEqual('"foo"', self.filters.constant_value(attr)) attr = AttrFactory.create(types=[AttrTypeFactory.create(qname="foo")]) self.assertEqual("Foo", self.filters.constant_value(attr)) attr = AttrFactory.create(types=[AttrTypeFactory.create(alias="alias")]) self.assertEqual("Alias", self.filters.constant_value(attr)) def test_apply_substitutions_with_regexes(self) -> None: self.filters.substitutions[ObjectType.CLASS]["(.*)Class"] = "\\1Type" actual = self.filters.apply_substitutions("FooClass", ObjectType.CLASS) self.assertEqual("FooType", actual) @mock.patch.object(Filters, "field_default_value") def test_field_definition(self, mock_field_default_value) -> None: mock_field_default_value.side_effect = [1, False] attr = AttrFactory.native(DataType.INT) result = self.filters.field_definition(self.obj, attr, None) expected = ( "field(\n" " default=1,\n" " metadata={\n" ' "name": "attr_B",\n' ' "type": "Element",\n' " }\n" " )" ) self.assertEqual(expected, result) result = self.filters.field_definition(self.obj, attr, None) expected = ( "field(\n" " metadata={\n" ' "name": "attr_B",\n' ' "type": "Element",\n' " }\n" " )" ) self.assertEqual(expected, result) def test_field_definition_with_prohibited_attr(self) -> None: attr = AttrFactory.native(DataType.INT) attr.restrictions.max_occurs = 0 attr.default = "1" result = self.filters.field_definition(self.obj, attr, None) expected = ( "field(\n" " init=False,\n" " default=None,\n" " metadata={\n" ' "type": "Ignore",\n' " }\n" " )" ) self.assertEqual(expected, result) @mock.patch.object(Filters, "field_default_value") def test_field_definition_with_restriction_pattern( self, mock_field_default_value ) -> None: mock_field_default_value.return_value = None str_attr = AttrFactory.create(types=[type_str], tag=Tag.RESTRICTION) # intentionally using a double-quote in the pattern to test for a regression in # https://github.com/tefra/xsdata/issues/592 pattern = '([^\\ \\? > < \\* / " ": |]{1,256})' str_attr.restrictions.pattern = pattern result = self.filters.field_definition(self.obj, str_attr, None) expected = ( "field(\n" " default=None,\n" " metadata={\n" ' "pattern": r\'([^\\ \\? > < \\* / " ": |]{1,256})\',\n' " }\n" " )" ) self.assertEqual(expected, result) @mock.patch.object(Filters, "field_metadata") def test_field_definition_without_metadata(self, mock_field_metadata) -> None: mock_field_metadata.return_value = {} str_attr = AttrFactory.create(types=[type_str], tag=Tag.RESTRICTION) result = self.filters.field_definition(self.obj, str_attr, None) expected = "field()" self.assertEqual(expected, result) def test_field_default_value_with_value_none(self) -> None: attr = AttrFactory.create(types=[type_str]) self.assertEqual(False, self.filters.field_default_value(attr)) attr.restrictions.min_occurs = 0 self.assertEqual(None, self.filters.field_default_value(attr)) def test_field_default_value_with_type_str(self) -> None: attr = AttrFactory.create(types=[type_str], default="foo") self.assertEqual("'foo'", self.filters.field_default_value(attr)) def test_field_default_value_with_type_tokens(self) -> None: attr = AttrFactory.create(types=[type_int, type_str], default="1 \n bar") attr.restrictions.tokens = True expected = """lambda: [ 1, "bar", ]""" self.assertEqual(expected, self.filters.field_default_value(attr)) expected = """lambda: ( 1, "bar", )""" self.filters.format.frozen = True self.assertEqual(expected, self.filters.field_default_value(attr)) attr.tag = Tag.ENUMERATION expected = """( 1, "bar", )""" self.assertEqual(expected, self.filters.field_default_value(attr)) def test_field_default_value_with_type_float(self) -> None: attr = AttrFactory.create(types=[type_float], default="1.5") self.assertEqual("1.5", self.filters.field_default_value(attr)) attr.default = "inf" attr.types = [type_int, type_float] self.assertEqual('float("inf")', self.filters.field_default_value(attr)) attr.default = "-inf" self.assertEqual('float("-inf")', self.filters.field_default_value(attr)) attr.default = "NaN" self.assertEqual('float("nan")', self.filters.field_default_value(attr)) def test_field_default_value_with_type_decimal(self) -> None: attr = AttrFactory.create(types=[type_decimal], default="1.5") self.assertEqual("Decimal('1.5')", self.filters.field_default_value(attr)) attr.default = "-inf" self.assertEqual("Decimal('-Infinity')", self.filters.field_default_value(attr)) attr.default = "inf" self.assertEqual("Decimal('Infinity')", self.filters.field_default_value(attr)) def test_field_default_value_with_type_int(self) -> None: attr = AttrFactory.create(types=[type_int], default="1") self.assertEqual("1", self.filters.field_default_value(attr)) def test_field_default_value_with_type_bool(self) -> None: attr = AttrFactory.create(types=[type_bool], default="true") self.assertTrue(self.filters.field_default_value(attr)) def test_field_default_value_with_type_enum(self) -> None: attr = AttrFactory.create( types=AttrTypeFactory.list(1, qname="{a}foo"), default="@enum@{a}foo::bar" ) self.assertEqual("Foo.BAR", self.filters.field_default_value(attr)) attr.types[0].alias = "foo_bar" self.assertEqual("FooBar.BAR", self.filters.field_default_value(attr)) attr.default = "@enum@{a}foo::bar@thug" self.assertEqual("FooBar.BAR_THUG", self.filters.field_default_value(attr)) attr.restrictions.tokens = True expected = """lambda: [ FooBar.BAR, FooBar.THUG, ]""" self.assertEqual(expected, self.filters.field_default_value(attr)) attr.types[0].qname = "nomatch" # impossible with self.assertRaises(StopIteration): self.filters.field_default_value(attr) def test_field_default_value_with_type_qname(self) -> None: attr = AttrFactory.create(types=[type_qname], default="xs:anyType") ns_map = {"xs": Namespace.XS.uri} self.assertEqual( 'QName("{http://www.w3.org/2001/XMLSchema}anyType")', self.filters.field_default_value(attr, ns_map), ) def test_field_default_value_with_xml_duration(self) -> None: attr = AttrFactory.create(types=[type_duration], default="P30M") self.assertEqual('XmlDuration("P30M")', self.filters.field_default_value(attr)) def test_field_default_value_with_any_attribute(self) -> None: attr = AttrFactory.any_attribute() self.assertEqual("dict", self.filters.field_default_value(attr)) def test_field_default_value_with_array_type(self) -> None: attr = AttrFactory.create(types=[type_bool]) attr.restrictions.max_occurs = 2 self.assertEqual("list", self.filters.field_default_value(attr)) self.filters.format.frozen = True self.assertEqual("tuple", self.filters.field_default_value(attr)) def test_field_default_value_with_multiple_types(self) -> None: attr = AttrFactory.create(types=[type_bool, type_int, type_float], default="2") self.assertEqual("2", self.filters.field_default_value(attr)) attr.default = 1.0 self.assertEqual("1.0", self.filters.field_default_value(attr)) attr.default = "true" self.assertEqual("True", self.filters.field_default_value(attr)) def test_field_metadata(self) -> None: attr = AttrFactory.element() expected = {"name": "attr_B", "type": "Element"} self.assertEqual(expected, self.filters.field_metadata(self.obj, attr, None)) def test_field_metadata_namespace(self) -> None: attr = AttrFactory.element(namespace="foo") expected = {"name": "attr_B", "namespace": "foo", "type": "Element"} actual = self.filters.field_metadata(self.obj, attr, None) self.assertEqual(expected, actual) actual = self.filters.field_metadata(self.obj, attr, "foo") self.assertNotIn("namespace", actual) attr = AttrFactory.attribute(namespace="foo") expected = {"name": "attr_C", "namespace": "foo", "type": "Attribute"} actual = self.filters.field_metadata(self.obj, attr, None) self.assertEqual(expected, actual) actual = self.filters.field_metadata(self.obj, attr, "foo") self.assertIn("namespace", actual) def test_field_metadata_name(self) -> None: attr = AttrFactory.element(name="bar") attr.local_name = "foo" actual = self.filters.field_metadata(self.obj, attr, None) self.assertEqual("foo", actual["name"]) attr = AttrFactory.element(name="Foo") attr.local_name = "foo" actual = self.filters.field_metadata(self.obj, attr, None) self.assertNotIn("name", actual) attr = AttrFactory.create(tag=Tag.ANY, name="bar") attr.local_name = "foo" actual = self.filters.field_metadata(self.obj, attr, None) self.assertNotIn("name", actual) def test_field_metadata_wrapper(self) -> None: attr = AttrFactory.element(wrapper="foo") expected = {"name": "attr_B", "wrapper": "foo", "type": "Element"} actual = self.filters.field_metadata(self.obj, attr, None) self.assertEqual(expected, actual) def test_field_metadata_restrictions(self) -> None: attr = AttrFactory.create(tag=Tag.RESTRICTION) attr.types.append(AttrTypeFactory.native(DataType.INT)) attr.restrictions.min_occurs = 1 attr.restrictions.max_occurs = 2 attr.restrictions.max_inclusive = "2" expected = {"min_occurs": 1, "max_occurs": 2, "max_inclusive": 2} self.assertEqual(expected, self.filters.field_metadata(self.obj, attr, None)) attr.restrictions.min_occurs = 1 attr.restrictions.max_occurs = 1 expected = {"max_inclusive": 2} self.assertEqual(expected, self.filters.field_metadata(self.obj, attr, None)) attr.tag = Tag.ATTRIBUTE attr.default = "foo" self.assertIn("required", self.filters.field_metadata(self.obj, attr, None)) attr.tag = Tag.RESTRICTION attr.restrictions.nillable = True expected = {"nillable": True, "max_inclusive": 2} self.assertEqual(expected, self.filters.field_metadata(self.obj, attr, None)) attr.default = None attr.restrictions.tokens = True expected = {"max_inclusive": 2, "nillable": True, "tokens": True} self.assertEqual(expected, self.filters.field_metadata(self.obj, attr, None)) def test_field_metadata_mixed(self) -> None: attr = AttrFactory.element(mixed=True) expected = {"mixed": True, "name": "attr_B", "type": "Element"} self.assertEqual(expected, self.filters.field_metadata(self.obj, attr, "foo")) def test_field_metadata_choices(self) -> None: attr = AttrFactory.create(choices=AttrFactory.list(2, tag=Tag.ELEMENT)) actual = self.filters.field_metadata(self.obj, attr, "foo") expected = ( {"name": "attr_B", "type": "Type[str]"}, {"name": "attr_C", "type": "Type[str]"}, ) self.assertEqual(expected, actual["choices"]) def test_field_metadata_docs(self) -> None: self.filters.docstring_style = DocstringStyle.ACCESSIBLE attr = AttrFactory.element(name="a", help="help") actual = self.filters.field_metadata(self.obj, attr, None) expected = {"doc": "help", "type": "Element"} self.assertEqual(expected, actual) def test_field_choices(self) -> None: attr = AttrFactory.create( choices=[ AttrFactory.element( name="$", namespace="foo", types=[type_float], restrictions=Restrictions(max_exclusive="10"), ), AttrFactory.element(namespace="bar"), AttrFactory.any(namespace="##other"), AttrFactory.element(name="bar", default="aa"), AttrFactory.element(name="tok", restrictions=Restrictions(tokens=True)), ] ) actual = self.filters.field_choices(self.obj, attr, "foo") expected = ( {"name": "$", "type": "Type[float]", "max_exclusive": 10.0}, {"name": "attr_B", "namespace": "bar", "type": "Type[str]"}, { "namespace": "##other", "wildcard": True, "type": "Type[object]", }, {"name": "bar", "type": "Type[str]"}, { "default_factory": "list", "name": "tok", "tokens": True, "type": "Type[list[str]]", }, ) self.assertEqual(expected, actual) self.filters.docstring_style = DocstringStyle.ACCESSIBLE attr.choices[0].help = "help" actual = self.filters.field_choices(self.obj, attr, None) self.assertEqual(attr.choices[0].help, actual[0]["doc"]) self.assertNotIn("doc", actual[1]) def test_field_type_with_default_value(self) -> None: attr = AttrFactory.element( default="1", types=AttrTypeFactory.list(1, qname="foo_bar") ) self.assertEqual("FooBar", self.filters.field_type(self.obj, attr)) attr.restrictions.nillable = True self.assertEqual("None | FooBar", self.filters.field_type(self.obj, attr)) def test_field_type_with_optional_value(self) -> None: attr = AttrFactory.create(types=AttrTypeFactory.list(1, qname="foo_bar")) self.assertEqual("FooBar", self.filters.field_type(self.obj, attr)) attr.restrictions.min_occurs = 0 self.assertEqual("None | FooBar", self.filters.field_type(self.obj, attr)) def test_field_type_with_circular_reference(self) -> None: attr = AttrFactory.create( types=AttrTypeFactory.list(1, qname="c", circular=True) ) self.assertEqual( "C", self.filters.field_type(self.obj_nested_nested_nested, attr), ) def test_field_type_with_forward_reference(self) -> None: attr = AttrFactory.create( types=AttrTypeFactory.list(1, qname="b", forward=True) ) self.assertEqual("A.B", self.filters.field_type(self.obj_nested_nested, attr)) def test_field_type_with_array_type(self) -> None: attr = AttrFactory.create( types=AttrTypeFactory.list(1, qname="c", forward=True) ) attr.restrictions.max_occurs = 2 self.assertEqual( "list[A.B.C]", self.filters.field_type(self.obj, attr), ) self.filters.format.frozen = True self.assertEqual("tuple[A.B.C, ...]", self.filters.field_type(self.obj, attr)) self.filters.format.frozen = False self.assertEqual("list[A.B.C]", self.filters.field_type(self.obj, attr)) self.filters.generic_collections = True self.assertEqual("Sequence[A.B.C]", self.filters.field_type(self.obj, attr)) def test_field_type_with_token_attr(self) -> None: attr = AttrFactory.create( types=AttrTypeFactory.list(1, qname="foo_bar"), restrictions=Restrictions(tokens=True), ) self.assertEqual("list[FooBar]", self.filters.field_type(self.obj, attr)) attr.restrictions.max_occurs = 2 self.assertEqual("list[list[FooBar]]", self.filters.field_type(self.obj, attr)) attr.restrictions.max_occurs = 1 self.filters.format.frozen = True self.assertEqual("tuple[FooBar, ...]", self.filters.field_type(self.obj, attr)) attr.restrictions.max_occurs = 2 self.assertEqual( "tuple[tuple[FooBar, ...], ...]", self.filters.field_type(self.obj, attr) ) def test_field_type_with_alias(self) -> None: attr = AttrFactory.create( types=AttrTypeFactory.list(1, qname="b", forward=True, alias="Boss:Life") ) attr.restrictions.max_occurs = 2 self.assertEqual( "list[A.BossLife]", self.filters.field_type(self.obj_nested_nested_nested, attr), ) def test_field_type_with_multiple_types(self) -> None: attr = AttrFactory.create( types=[ AttrTypeFactory.create(qname="c", alias="Boss:Life", forward=True), AttrTypeFactory.native(DataType.INT), ] ) attr.restrictions.max_occurs = 2 self.assertEqual( "list[A.B.BossLife | int]", self.filters.field_type(self.obj_nested_nested_nested, attr), ) def test_field_type_with_any_attribute(self) -> None: attr = AttrFactory.any_attribute() self.assertEqual("dict[str, str]", self.filters.field_type(self.obj, attr)) self.filters.generic_collections = True self.assertEqual("Mapping[str, str]", self.filters.field_type(self.obj, attr)) def test_field_type_with_native_type(self) -> None: attr = AttrFactory.create( types=[ AttrTypeFactory.native(DataType.INT), AttrTypeFactory.native(DataType.POSITIVE_INTEGER), AttrTypeFactory.native(DataType.STRING), ] ) self.assertEqual("int | str", self.filters.field_type(self.obj, attr)) def test_field_type_with_prohibited_attr(self) -> None: attr = AttrFactory.create(restrictions=Restrictions(max_occurs=0)) self.assertEqual("Any", self.filters.field_type(self.obj, attr)) def test_field_type_with_compound_attr(self) -> None: attr = AttrFactory.create( tag=Tag.CHOICE, choices=[ AttrFactory.create( name="a", types=[AttrTypeFactory.native(DataType.STRING)] ), AttrFactory.create( name="b", types=[AttrTypeFactory.native(DataType.INT)] ), AttrFactory.create( name="c", types=[AttrTypeFactory.native(DataType.DECIMAL)], restrictions=Restrictions(tokens=True), ), ], restrictions=Restrictions(min_occurs=0, max_occurs=1), ) expected = "None | str | int | list[Decimal]" self.assertEqual(expected, self.filters.field_type(self.obj, attr)) attr.restrictions.max_occurs = 2 expected = "list[str | int | list[Decimal]]" self.assertEqual(expected, self.filters.field_type(self.obj, attr)) attr.restrictions.min_occurs = attr.restrictions.max_occurs = 1 self.filters.format.kw_only = True expected = "str | int | list[Decimal]" self.assertEqual(expected, self.filters.field_type(self.obj, attr)) def test_choice_type(self) -> None: choice = AttrFactory.create(types=[AttrTypeFactory.create("foobar")]) target = ClassFactory.create() actual = self.filters.choice_type(target, choice) self.assertEqual("Type[Foobar]", actual) def test_choice_type_with_forward_reference(self) -> None: choice = AttrFactory.create( types=[AttrTypeFactory.create("foobar", forward=True)] ) target = ClassFactory.create(qname="foobar") parent = ClassFactory.create(qname="a") parent.inner.append(target) target.parent = parent actual = self.filters.choice_type(parent, choice) self.assertEqual('ForwardRef("A.Foobar")', actual) def test_choice_type_with_circular_reference(self) -> None: choice = AttrFactory.create(types=[AttrTypeFactory.create("c", circular=True)]) actual = self.filters.choice_type(self.obj_nested_nested_nested, choice) self.assertEqual('ForwardRef("C")', actual) def test_choice_type_with_multiple_types(self) -> None: choice = AttrFactory.create(types=[type_str, type_bool]) target = ClassFactory.create() actual = self.filters.choice_type(target, choice) self.assertEqual("Type[str | bool]", actual) def test_choice_type_with_list_types_are_ignored(self) -> None: choice = AttrFactory.create(types=[type_str, type_bool]) choice.restrictions.max_occurs = 200 target = ClassFactory.create() actual = self.filters.choice_type(target, choice) self.assertEqual("Type[str | bool]", actual) def test_choice_type_with_restrictions_tokens_true(self) -> None: choice = AttrFactory.create(types=[type_str, type_bool]) choice.restrictions.tokens = True target = ClassFactory.create() actual = self.filters.choice_type(target, choice) self.assertEqual("Type[list[str | bool]]", actual) self.filters.format.frozen = True actual = self.filters.choice_type(target, choice) self.assertEqual("Type[tuple[str | bool, ...]]", actual) def test_default_imports_with_decimal(self) -> None: expected = "from decimal import Decimal" self.assertIn(expected, self.filters.default_imports("Optional[Decimal]")) self.assertIn(expected, self.filters.default_imports("Union[str, Decimal]")) self.assertIn(expected, self.filters.default_imports("Union[Decimal, ")) self.assertIn(expected, self.filters.default_imports("Union[str, Decimal, int")) self.assertIn(expected, self.filters.default_imports("number: Decimal = ")) self.assertIn(expected, self.filters.default_imports(" = Decimal(")) self.assertNotIn(expected, self.filters.default_imports("class fooDecimal")) def test_default_imports_with_qname(self) -> None: expected = "from xml.etree.ElementTree import QName" self.assertIn(expected, self.filters.default_imports("Optional[QName]")) self.assertIn(expected, self.filters.default_imports("Union[str, QName]")) self.assertIn(expected, self.filters.default_imports("Union[QName, ")) self.assertIn(expected, self.filters.default_imports("Union[str, QName, int")) self.assertIn(expected, self.filters.default_imports("number: QName = ")) self.assertIn(expected, self.filters.default_imports(" = QName(")) self.assertNotIn(expected, self.filters.default_imports("class fooQName")) def test_default_imports_with_enum(self) -> None: output = " (Enum) " expected = "from enum import Enum" self.assertIn(expected, self.filters.default_imports(output)) def test_default_imports_with_dataclasses(self) -> None: output = " @dataclass " expected = "from dataclasses import dataclass" self.assertIn(expected, self.filters.default_imports(output)) output = " = field( " expected = "from dataclasses import field" self.assertIn(expected, self.filters.default_imports(output)) output = " = field( @dataclass " expected = "from dataclasses import dataclass, field" self.assertIn(expected, self.filters.default_imports(output)) def test_default_imports_with_builtin_datatype(self) -> None: expected = "from xsdata.models.datatype import XmlDateTime" self.assertIn(expected, self.filters.default_imports("Optional[XmlDateTime]")) self.assertIn(expected, self.filters.default_imports("Union[str, XmlDateTime]")) self.assertIn(expected, self.filters.default_imports("Union[XmlDateTime, ")) self.assertIn(expected, self.filters.default_imports("Union[a, XmlDateTime, a")) self.assertIn(expected, self.filters.default_imports("number: XmlDateTime = ")) self.assertIn(expected, self.filters.default_imports(" = XmlDateTime(")) self.assertNotIn(expected, self.filters.default_imports("class fooXmlDateTime")) def test_default_imports_with_typing(self) -> None: output = ": ForwardRef(" expected = "from typing import ForwardRef" self.assertIn(expected, self.filters.default_imports(output)) output = ": Any = " expected = "from typing import Any" self.assertIn(expected, self.filters.default_imports(output)) output = ": Sequence[str] = " expected = "from collections.abc import Sequence" self.assertIn(expected, self.filters.default_imports(output)) output = ": Mapping[str, str] = " expected = "from collections.abc import Mapping" self.assertIn(expected, self.filters.default_imports(output)) def test_default_imports_with_module(self) -> None: output = "@attrs.s\n" self.filters.import_patterns["attrs"] = {"__module__": ["@attrs.s"]} expected = "from __future__ import annotations\nimport attrs" self.assertEqual(expected, self.filters.default_imports(output)) def test_format_docstring_simple(self) -> None: """Test simple single-line docstring.""" result = self.filters.format_docstring('"""Short doc."""', level=1) self.assertEqual('"""\nShort doc.\n"""', result) def test_format_docstring_adds_period(self) -> None: """Test that period is added if missing.""" result = self.filters.format_docstring('"""No period"""', level=1) self.assertEqual('"""\nNo period.\n"""', result) # Should not add period if already has punctuation result = self.filters.format_docstring('"""Has period."""', level=1) self.assertEqual('"""\nHas period.\n"""', result) result = self.filters.format_docstring('"""Has question?"""', level=1) self.assertEqual('"""\nHas question?\n"""', result) result = self.filters.format_docstring('"""Has exclamation!"""', level=1) self.assertEqual('"""\nHas exclamation!\n"""', result) result = self.filters.format_docstring('"""Has colon:"""', level=1) self.assertEqual('"""\nHas colon:\n"""', result) def test_format_docstring_with_params(self) -> None: """Test docstring with RST-style params.""" doc = '"""Class description."""\n:ivar foo: Foo desc.\n:ivar bar: Bar desc.' result = self.filters.format_docstring(doc, level=1) expected = ( '"""\nClass description.\n\n:ivar foo: Foo desc.\n:ivar bar: Bar desc.\n"""' ) self.assertEqual(expected, result) def test_format_docstring_empty_with_params(self) -> None: """Test docstring with only params, no description.""" doc = '""""""\n:ivar foo: Foo description.' result = self.filters.format_docstring(doc, level=1) expected = '"""\n:ivar foo: Foo description.\n"""' self.assertEqual(expected, result) def test_format_docstring_empty(self) -> None: """Test completely empty docstring returns empty string.""" result = self.filters.format_docstring('""""""', level=1) self.assertEqual("", result) def test_format_docstring_wraps_long_lines(self) -> None: """Test that long lines are wrapped.""" self.filters.max_line_length = 79 long_text = "A " * 50 # Much longer than 79 chars doc = f'"""{long_text.strip()}"""' result = self.filters.format_docstring(doc, level=1) # Result should be multi-line lines = result.split("\n") self.assertGreater(len(lines), 3) # Opening, at least 2 content lines, closing # Each content line should respect max_line_length minus indentation for line in lines[1:-1]: # Skip opening and closing quotes # level=1 means 4 spaces indent, plus some margin self.assertLessEqual(len(line), self.filters.max_line_length - 4) def test_format_docstring_nested_level(self) -> None: """Test that nested classes get different wrapping width.""" self.filters.max_line_length = 79 long_text = "A " * 40 doc = f'"""{long_text.strip()}"""' result_level1 = self.filters.format_docstring(doc, level=1) result_level2 = self.filters.format_docstring(doc, level=2) # Level 2 should wrap earlier (more indent = less space) lines_level1 = result_level1.split("\n") lines_level2 = result_level2.split("\n") # Level 2 should have more lines due to narrower width self.assertGreaterEqual(len(lines_level2), len(lines_level1)) def test_format_docstring_no_separator(self) -> None: """Test that missing closing quotes returns empty string.""" result = self.filters.format_docstring("no quotes here", level=1) self.assertEqual("", result) def test_format_docstring_splits_summary_description(self) -> None: """Test that first sentence becomes summary, rest becomes description.""" doc = '"""First sentence here. Second sentence follows. Third one too."""' result = self.filters.format_docstring(doc, level=1) expected = ( '"""\nFirst sentence here.\n\nSecond sentence follows. Third one too.\n"""' ) self.assertEqual(expected, result) # With params doc = '"""Summary sentence. Description here."""\n:ivar x: X desc.' result = self.filters.format_docstring(doc, level=1) expected = ( '"""\nSummary sentence.\n\nDescription here.\n\n:ivar x: X desc.\n"""' ) self.assertEqual(expected, result) # Single sentence - no split doc = '"""Just one sentence here."""' result = self.filters.format_docstring(doc, level=1) expected = '"""\nJust one sentence here.\n"""' self.assertEqual(expected, result) def test_format_docstring_normalizes_whitespace(self) -> None: """Test that internal whitespace is normalized.""" doc = '"""Text with multiple spaces."""' result = self.filters.format_docstring(doc, level=1) self.assertEqual('"""\nText with multiple spaces.\n"""', result) # Multi-line input gets joined and sentences are split doc = '"""Line one.\nLine two."""' result = self.filters.format_docstring(doc, level=1) # "Line one." becomes summary, "Line two." becomes description self.assertEqual('"""\nLine one.\n\nLine two.\n"""', result) # Single sentence stays together doc = '"""Line one and\nline two."""' result = self.filters.format_docstring(doc, level=1) self.assertEqual('"""\nLine one and line two.\n"""', result) def test_format_metadata(self) -> None: data = { "num": 1, "text": "foo", "text_two": "fo'o", "text_three": 'fo"o', "pattern": "foo", "custom": Telephone(30, 123, 4567), "level_two": {"a": 1}, "list": [ {"type": "Type[object]"}, {"type": 'ForwardRef("something")'}, ], "default": "1", "default_factory": "list", } expected = ( "{\n" ' "num": 1,\n' ' "text": "foo",\n' ' "text_two": "fo\'o",\n' ' "text_three": "fo\\"o",\n' " \"pattern\": r'foo',\n" ' "custom": Telephone(country_code=30, area_code=123, number=4567),\n' ' "level_two": {\n' ' "a": 1,\n' " },\n" ' "list": [\n' " {\n" ' "type": object,\n' " },\n" " {\n" ' "type": ForwardRef("something"),\n' " },\n" " ],\n" ' "default": 1,\n' ' "default_factory": list,\n' "}" ) self.assertEqual(expected, self.filters.format_metadata(data)) self.assertEqual('""', self.filters.format_metadata("")) def test_import_module(self) -> None: case = namedtuple("Case", ["module", "from_module", "result"]) cases = [ case("foo.bar", "foo", ".bar"), case("bar.foo", "foo", "bar.foo"), case("a.b.e.f", "a.b.c.d", "..e.f"), case("a.b.c.f", "a.b.c.d", ".f"), case("a.b.c.f.e", "a.b", ".c.f.e"), case("a.b.c.f", "", "a.b.c.f"), ] transform = self.filters.import_module self.filters.relative_imports = False for case in cases: self.assertEqual(case.module, transform(case.module, case.from_module)) self.filters.relative_imports = True for case in cases: self.assertEqual(case.result, transform(case.module, case.from_module)) def test_build_class_annotation(self) -> None: config = GeneratorConfig() format = config.output.format actual = self.filters.build_class_annotation(format) self.assertEqual("@dataclass(kw_only=True)", actual) format.frozen = True actual = self.filters.build_class_annotation(format) self.assertEqual("@dataclass(frozen=True, kw_only=True)", actual) format.repr = False format.eq = False format.order = True format.unsafe_hash = True format.slots = True actual = self.filters.build_class_annotation(format) expected = ( "@dataclass(repr=False, eq=False, order=True," " unsafe_hash=True, frozen=True, slots=True, kw_only=True)" ) self.assertEqual(expected, actual) def test__init(self) -> None: config = GeneratorConfig() config.conventions.package_name.safe_prefix = "safe_package" config.conventions.package_name.case = NameCase.MIXED config.conventions.class_name.safe_prefix = "safe_class" config.conventions.class_name.case = NameCase.CAMEL config.conventions.field_name.safe_prefix = "safe_field" config.conventions.field_name.case = NameCase.PASCAL config.conventions.module_name.safe_prefix = "safe_module" config.conventions.module_name.case = NameCase.SNAKE config.substitutions.substitution.append( GeneratorSubstitution(ObjectType.FIELD, "k", "l") ) config.substitutions.substitution.append( GeneratorSubstitution(ObjectType.PACKAGE, "m", "n") ) config.extensions.extension.extend( [ GeneratorExtension(ExtensionType.DECORATOR, "a", "a.b"), GeneratorExtension(ExtensionType.DECORATOR, "b", "a.c"), GeneratorExtension(ExtensionType.CLASS, "c", "a.d"), GeneratorExtension(ExtensionType.CLASS, "d", "a.e"), ] ) filters = Filters(config) self.assertFalse(filters.relative_imports) self.assertEqual("safe_class", filters.class_safe_prefix) self.assertEqual("safe_field", filters.field_safe_prefix) self.assertEqual("safe_package", filters.package_safe_prefix) self.assertEqual("safe_module", filters.module_safe_prefix) self.assertEqual("cAb", filters.class_name("cAB")) self.assertEqual("CAb", filters.field_name("cAB", "cls")) self.assertEqual("cAB", filters.package_name("cAB")) self.assertEqual("c_ab", filters.module_name("cAB")) expected_substitutions = { ObjectType.CLASS: {}, ObjectType.FIELD: {"k": "l"}, ObjectType.MODULE: {}, ObjectType.PACKAGE: {"m": "n"}, } self.assertEqual(expected_substitutions, filters.substitutions) expected_extensions = { ExtensionType.DECORATOR: config.extensions.extension[0:2], ExtensionType.CLASS: config.extensions.extension[2:4], } self.assertEqual(expected_extensions, filters.extensions) expected_imports = { "b": {"@b"}, "c": {"@c"}, "d": {"(d", ", d", " d)"}, "e": {"(e", ", e", " e)"}, } self.assertEqual(expected_imports, filters.import_patterns["a"]) tefra-xsdata-cf7afe8/tests/formats/dataclass/test_generator.py000066400000000000000000000203671526437326600250630ustar00rootroot00000000000000import random import tempfile from pathlib import Path from unittest import mock from xsdata.codegen.exceptions import CodegenError from xsdata.codegen.resolver import DependenciesResolver from xsdata.formats.dataclass.generator import DataclassGenerator, bytecode_cache from xsdata.models.config import GeneratorConfig from xsdata.utils.testing import ClassFactory, FactoryTestCase class DataclassGeneratorTests(FactoryTestCase): def setUp(self) -> None: super().setUp() self.maxDiff = None config = GeneratorConfig() self.generator = DataclassGenerator(config) def test_init_shares_bytecode_cache(self) -> None: other = DataclassGenerator(GeneratorConfig()) self.assertIs(bytecode_cache, self.generator.env.bytecode_cache) self.assertIs(bytecode_cache, other.env.bytecode_cache) @mock.patch.object(DataclassGenerator, "ruff_code") @mock.patch.object(DataclassGenerator, "validate_imports") @mock.patch.object(DataclassGenerator, "render_package") @mock.patch.object(DataclassGenerator, "render_module") def test_render( self, mock_render_module, mock_render_package, mock_validate_imports, mock_ruff_code, ) -> None: classes = [ ClassFactory.create(package="foo.bar", module="tests"), ClassFactory.create(package="bar.foo", module="tests"), ClassFactory.create(package="thug.life", module="tests"), ] mock_render_module.return_value = "module" mock_render_package.return_value = "package" iterator = self.generator.render(classes) cwd = Path.cwd() actual = [(out.path, out.title, out.source) for out in iterator] expected = [ (cwd.joinpath("foo/bar/__init__.py"), "init", "package"), (cwd.joinpath("foo/__init__.py"), "init", "# nothing here\n"), (cwd.joinpath("bar/foo/__init__.py"), "init", "package"), (cwd.joinpath("bar/__init__.py"), "init", "# nothing here\n"), (cwd.joinpath("thug/life/__init__.py"), "init", "package"), (cwd.joinpath("thug/__init__.py"), "init", "# nothing here\n"), (cwd.joinpath("foo/bar/tests.py"), "foo.bar.tests", "module"), (cwd.joinpath("bar/foo/tests.py"), "bar.foo.tests", "module"), (cwd.joinpath("thug/life/tests.py"), "thug.life.tests", "module"), ] self.assertEqual(expected, actual) mock_render_package.assert_has_calls( [ mock.call([classes[0]], "foo.bar"), mock.call([classes[1]], "bar.foo"), mock.call([classes[2]], "thug.life"), ] ) mock_render_module.assert_has_calls([mock.call(mock.ANY, [x]) for x in classes]) mock_validate_imports.assert_called_once() def test_render_package(self) -> None: classes = [ ClassFactory.create(qname="a", package="foo", module="tests"), ClassFactory.create(qname="b", package="foo", module="tests"), ClassFactory.create(qname="c", package="foo", module="tests"), ClassFactory.create(qname="a", package="foo", module="bar"), ] random.shuffle(classes) actual = self.generator.render_package(classes, "foo.tests") expected = ( "from foo.bar import A as BarA\n" "from foo.tests import (\n" " A as TestsA,\n" " B,\n" " C,\n" ")\n" "\n" "__all__ = [\n" ' "BarA",\n' ' "TestsA",\n' ' "B",\n' ' "C",\n' "]" ) self.assertEqual(expected, actual) def test_render_module(self) -> None: classes = [ ClassFactory.enumeration(2, help="\n\nI am enum "), ClassFactory.elements(2), ClassFactory.service(2), ] classes[0].attrs[0].help = "I am a member" classes[1].attrs[0].help = "I am a field" resolver = DependenciesResolver({}) actual = self.generator.render_module(resolver, classes) expected = ( "from __future__ import annotations\n" "from dataclasses import dataclass, field\n" "from enum import Enum\n" "\n" '__NAMESPACE__ = "xsdata"\n' "\n" "\n" "class ClassB(Enum):\n" ' """\n' " I am enum.\n" "\n" " :cvar ATTR_B: I am a member\n" " :cvar ATTR_C:\n" ' """\n' " ATTR_B = False\n" " ATTR_C = False\n" "@dataclass(kw_only=True)\n" "class ClassC:\n" ' """\n' " :ivar attr_d: I am a field\n" " :ivar attr_e:\n" ' """\n' " class Meta:\n" ' name = "class_C"\n' "\n" " attr_d: str = field(\n" " metadata={\n" ' "name": "attr_D",\n' ' "type": "Element",\n' " }\n" " )\n" " attr_e: str = field(\n" " metadata={\n" ' "name": "attr_E",\n' ' "type": "Element",\n' " }\n" " )\n" "class ClassD:\n" ' attr_f = "None"\n' ' attr_g = "None"' ) self.assertEqual(expected, actual) def test_render_module_with_mixed_target_namespaces(self) -> None: classes = [ ClassFactory.elements(1, qname="{foo}bar"), ClassFactory.elements(1, qname="{bar}foo"), ] resolver = DependenciesResolver({}) actual = self.generator.render_module(resolver, classes) expected = ( "from __future__ import annotations\n" "from dataclasses import dataclass, field\n" "\n" "\n" "@dataclass(kw_only=True)\n" "class Foo:\n" " class Meta:\n" ' name = "foo"\n' ' target_namespace = "bar"\n' "\n" " attr_c: str = field(\n" " metadata={\n" ' "name": "attr_C",\n' ' "type": "Element",\n' " }\n" " )\n" "@dataclass(kw_only=True)\n" "class Bar:\n" " class Meta:\n" ' name = "bar"\n' ' target_namespace = "foo"\n' "\n" " attr_b: str = field(\n" " metadata={\n" ' "name": "attr_B",\n' ' "type": "Element",\n' " }\n" " )" ) self.assertEqual(expected, actual) def test_module_name(self) -> None: self.assertEqual("foo_bar", self.generator.module_name("fooBar")) self.assertEqual("foo_bar_wtf", self.generator.module_name("fooBar.wtf")) self.assertEqual("mod_1111", self.generator.module_name("1111")) self.assertEqual("xs_string", self.generator.module_name("xs:string")) self.assertEqual("foo_bar_bam", self.generator.module_name("foo:bar_bam")) self.assertEqual("bar_bam", self.generator.module_name("urn:bar_bam")) def test_package_name(self) -> None: self.assertEqual( "foo.bar_bar.pkg_1", self.generator.package_name("Foo.BAR_bar.1") ) self.assertEqual("", self.generator.package_name("")) def test_ruff_code_with_invalid_code(self) -> None: src_code = ( "class AlternativeText:\n" " class Meta:\n" ' namespace = "xsdata"\n' "\n" " foo: Optional[Union[]] = field(\n" " init=False,\n" ' metadata={"type": "Ignore"}\n' " )\n" " bar: str\n" " thug: str" ) with tempfile.NamedTemporaryFile(delete=True, suffix=".py") as temp_file: temp_file.write(src_code.encode()) temp_file.seek(0) with self.assertRaises(CodegenError): self.generator.ruff_code([temp_file.name]) tefra-xsdata-cf7afe8/tests/formats/dataclass/test_transport.py000066400000000000000000000051471526437326600251300ustar00rootroot00000000000000from unittest import TestCase, mock from requests import HTTPError, Response, Session from xsdata.formats.dataclass.transports import DefaultTransport class DefaultTransportTest(TestCase): @mock.patch.object(Response, "content", new_callable=mock.PropertyMock) @mock.patch.object(Response, "raise_for_status") @mock.patch.object(Session, "get") def test_get(self, mock_get, mock_raise_for_status, mock_content) -> None: transport = DefaultTransport() params = {"a": "b"} url = "http://endpoint.stub/action" headers = {"content-type": "text/xml"} response = Response() response.status_code = 200 mock_get.return_value = response mock_content.return_value = b"foobar" result = transport.get(url, params, headers) self.assertEqual("foobar", result.decode()) self.assertEqual(0, mock_raise_for_status.call_count) mock_get.assert_called_once_with( url, params=params, headers=headers, timeout=transport.timeout ) @mock.patch.object(Response, "content", new_callable=mock.PropertyMock) @mock.patch.object(Response, "raise_for_status") @mock.patch.object(Session, "post") def test_post(self, mock_post, mock_raise_for_status, mock_content) -> None: transport = DefaultTransport(timeout=1.0) data = {"a": "b"} url = "http://endpoint.stub/action" headers = {"content-type": "text/xml"} response = Response() response.status_code = 500 mock_post.return_value = response mock_content.return_value = b"foobar" result = transport.post(url, data, headers) self.assertEqual("foobar", result.decode()) self.assertEqual(0, mock_raise_for_status.call_count) mock_post.assert_called_once_with( url, data=data, headers=headers, timeout=transport.timeout ) @mock.patch.object(Response, "content", new_callable=mock.PropertyMock) def test_handle_response(self, mock_content) -> None: response = Response() response.status_code = 200 mock_content.return_value = b"foobar" transport = DefaultTransport() self.assertEqual(b"foobar", transport.handle_response(response)) response.status_code = 500 self.assertEqual(b"foobar", transport.handle_response(response)) response.status_code = 401 response.reason = "Nope" response.url = "xsdata" with self.assertRaises(HTTPError) as cm: transport.handle_response(response) self.assertEqual("401 Client Error: Nope for url: xsdata", str(cm.exception)) tefra-xsdata-cf7afe8/tests/formats/dataclass/test_typing.py000066400000000000000000000047521526437326600244070ustar00rootroot00000000000000import pytest from tests.formats.dataclass.cases import ( attribute, attributes, element, elements, wildcard, ) from xsdata.formats.dataclass.typing import ( evaluate, evaluate_attribute, evaluate_attributes, evaluate_element, evaluate_elements, evaluate_wildcard, ) def test_evaluate_with_typevar() -> None: result = evaluate(type["str"], None) assert result is str with pytest.raises(TypeError): evaluate(type["str", "int"], None) @pytest.mark.parametrize("case,expected", attribute.tokens) def test_evaluate_attribute_with_tokens(case, expected) -> None: if expected: assert expected == evaluate_attribute(case, tokens=True) else: with pytest.raises(TypeError): evaluate_attribute(case, tokens=True) @pytest.mark.parametrize("case,expected", attribute.not_tokens) def test_evaluate_attribute_without_tokens(case, expected) -> None: if expected: assert expected == evaluate_attribute(case, tokens=False) else: with pytest.raises(TypeError): evaluate_attribute(case, tokens=False) @pytest.mark.parametrize("case,expected", attributes.cases) def test_evaluate_attributes(case, expected) -> None: if expected: assert expected == evaluate_attributes(case) else: with pytest.raises(TypeError): evaluate_attributes(case) @pytest.mark.parametrize("case,expected", element.tokens) def test_evaluate_element_with_tokens(case, expected) -> None: if expected: assert expected == evaluate_element(case, tokens=True) else: with pytest.raises(TypeError): evaluate_element(case, tokens=True) @pytest.mark.parametrize("case,expected", element.not_tokens) def test_evaluate_element_without_tokens(case, expected) -> None: if expected: assert expected == evaluate_element(case, tokens=False) else: with pytest.raises(TypeError): evaluate_element(case, tokens=False) @pytest.mark.parametrize("case,expected", elements.cases) def test_evaluate_elements(case, expected) -> None: if expected: assert expected == evaluate_elements(case) else: with pytest.raises(TypeError): evaluate_elements(case) @pytest.mark.parametrize("case,expected", wildcard.cases) def test_evaluate_wildcard(case, expected) -> None: if expected: assert expected == evaluate_wildcard(case) else: with pytest.raises(TypeError): evaluate_wildcard(case) tefra-xsdata-cf7afe8/tests/formats/test_converter.py000066400000000000000000000410461526437326600231420ustar00rootroot00000000000000import sys import unittest from datetime import date, datetime, time from decimal import Decimal from enum import Enum from typing import Any from xml.etree.ElementTree import QName import pytest from tests.fixtures.datatypes import Telephone from xsdata.exceptions import ConverterError from xsdata.formats.converter import Converter, ProxyConverter, converter from xsdata.models.datatype import XmlDuration, XmlPeriod from xsdata.models.enums import UseType class ConverterFactoryTests(unittest.TestCase): def test_deserialize(self) -> None: with self.assertRaises(ConverterError) as cm: converter.deserialize("a", [int]) self.assertEqual("`a` is not a valid `int`", str(cm.exception)) self.assertFalse(converter.deserialize("false", [int, bool])) self.assertEqual(1, converter.deserialize("1", [int, bool])) def test_serialize(self) -> None: self.assertEqual(None, converter.serialize(None)) self.assertEqual("1", converter.serialize(1)) self.assertEqual("1 2 3", converter.serialize([1, "2", 3])) self.assertEqual(None, converter.serialize(None)) self.assertEqual("1", converter.serialize(1)) self.assertEqual("1.5", converter.serialize(1.5)) self.assertEqual("true", converter.serialize(True)) self.assertEqual("optional", converter.serialize(UseType.OPTIONAL)) self.assertEqual("0.0000000877683", converter.serialize(Decimal("8.77683E-8"))) self.assertEqual("8.77683E-08", converter.serialize(float("8.77683E-8"))) def test_test(self) -> None: self.assertTrue(converter.test("1", [int])) self.assertTrue(converter.test("1", [float])) self.assertFalse(converter.test("1", [float], strict=True)) self.assertFalse(converter.test(None, [int])) self.assertFalse(converter.test("a", [int])) self.assertTrue(converter.test("01", [int])) self.assertFalse(converter.test("01", [int], strict=True)) self.assertTrue(converter.test("1", [int])) self.assertTrue(converter.test("0", [float])) self.assertFalse(converter.test("0", [float], strict=True)) self.assertTrue(converter.test("INF", [float], strict=True)) self.assertTrue(converter.test("inf", [float], strict=True)) self.assertTrue(converter.test("+INF", [float], strict=True)) self.assertTrue(converter.test("+inf", [float], strict=True)) self.assertTrue(converter.test("-INF", [float], strict=True)) self.assertTrue(converter.test("-inf", [float], strict=True)) self.assertTrue(converter.test("NAN", [float], strict=True)) self.assertTrue(converter.test("NaN", [float], strict=True)) self.assertTrue(converter.test("nan", [float], strict=True)) self.assertTrue(converter.test("-NAN", [float], strict=True)) self.assertTrue(converter.test(".0", [float])) self.assertFalse(converter.test(".0", [float], strict=True)) self.assertTrue(converter.test("1.0", [float])) def test_unknown_converter(self) -> None: class A: pass with self.assertRaises(ConverterError) as cm: converter.serialize(A()) self.assertEqual( f"No converter registered for `{A.__qualname__}`", str(cm.exception) ) def test_register_converter(self) -> None: class MinusOneInt(int): pass class MinusOneIntConverter(Converter): def deserialize(self, value: str, **kwargs: Any) -> Any: return int(value) - 1 def serialize(self, value: Any, **kwargs: Any) -> str: return str(value) self.assertEqual(1, converter.deserialize("1", [MinusOneInt])) converter.register_converter(MinusOneInt, MinusOneIntConverter()) self.assertEqual(1, converter.deserialize("2", [MinusOneInt])) self.assertEqual(2, converter.deserialize("3", [MinusOneInt])) self.assertEqual("3", converter.serialize(MinusOneInt("3"))) converter.unregister_converter(MinusOneInt) def test_register_converter_with_lambda(self) -> None: class MinusOneInt(int): pass converter.register_converter(MinusOneInt, lambda x: int(x) - 1) self.assertEqual(1, converter.deserialize("2", [MinusOneInt])) self.assertEqual(2, converter.deserialize("3", [MinusOneInt])) converter.unregister_converter(MinusOneInt) class StrConverterTests(unittest.TestCase): def setUp(self) -> None: self.converter = converter.type_converter(str) def test_deserialize(self) -> None: self.assertEqual("a", self.converter.deserialize("a")) self.assertEqual("1", self.converter.deserialize(1)) def test_serialize(self) -> None: self.assertEqual("a", self.converter.serialize("a")) self.assertEqual("1", self.converter.serialize(1)) class StringConverterTests(unittest.TestCase): def setUp(self) -> None: self.converter = converter.type_converter(str) def test_deserialize(self) -> None: string = "foo" self.assertIs(string, self.converter.deserialize(string)) self.assertEqual("1", self.converter.deserialize(1)) def test_serialize(self) -> None: string = "foo" self.assertIs(string, self.converter.serialize(string)) self.assertEqual("1", self.converter.serialize(1)) class BoolConverterTests(unittest.TestCase): def setUp(self) -> None: self.converter = converter.type_converter(bool) def test_deserialize(self) -> None: for invalid in ("True", "False", 1, 0): with self.assertRaises(ConverterError): self.converter.deserialize(invalid) self.assertTrue(self.converter.deserialize("true")) self.assertTrue(self.converter.deserialize("1")) self.assertFalse(self.converter.deserialize("false")) self.assertFalse(self.converter.deserialize("0")) self.assertTrue(self.converter.deserialize(True)) self.assertFalse(self.converter.deserialize(False)) def test_serialize(self) -> None: self.assertEqual("true", self.converter.serialize(True)) self.assertEqual("false", self.converter.serialize(False)) class IntConverterTests(unittest.TestCase): def setUp(self) -> None: self.converter = converter.type_converter(int) def test_deserialize(self) -> None: with self.assertRaises(ConverterError): self.converter.deserialize("a") self.assertEqual(2, self.converter.deserialize("2")) self.assertEqual(2, self.converter.deserialize("+2")) self.assertEqual(-2, self.converter.deserialize("-2")) def test_serialize(self) -> None: self.assertEqual("2", self.converter.serialize(2)) class FloatConverterTests(unittest.TestCase): def setUp(self) -> None: self.converter = converter.type_converter(float) def test_deserialize(self) -> None: with self.assertRaises(ConverterError): self.converter.deserialize("a") self.assertEqual(2.0, self.converter.deserialize("2")) self.assertEqual(2.1, self.converter.deserialize("2.1")) def test_serialize(self) -> None: self.assertEqual("2.1", self.converter.serialize(2.1)) self.assertEqual("INF", self.converter.serialize(float("inf"))) self.assertEqual("INF", self.converter.serialize(float("+inf"))) self.assertEqual("-INF", self.converter.serialize(float("-inf"))) self.assertEqual("NaN", self.converter.serialize(float("nan"))) self.assertEqual("8.77683E-08", self.converter.serialize(float("8.77683E-8"))) class BytesConverterTests(unittest.TestCase): def setUp(self) -> None: self.converter = converter.type_converter(bytes) def test_serialize_with_base16_format(self) -> None: inputs = [ ("312d\n322d33", "312D322D33"), ("312D \t322D33", "312D322D33"), ] for actual, expected in inputs: obj = self.converter.deserialize(actual, format="base16") output = self.converter.serialize(obj, format="base16") self.assertIsInstance(obj, bytes) self.assertEqual(b"1-2-3", obj) self.assertEqual(expected, output) def test_serialize_with_base64_format(self) -> None: obj = self.converter.deserialize("MS0yLTM=", format="base64") output = self.converter.serialize(obj, format="base64") self.assertIsInstance(obj, bytes) self.assertEqual(b"1-2-3", obj) self.assertEqual("MS0yLTM=", output) def test_serialize_raises_exception(self) -> None: with self.assertRaises(ConverterError) as cm: self.converter.deserialize(1, format="foo") self.assertEqual("Input value must be 'str' got 'int'", str(cm.exception)) with self.assertRaises(ConverterError): self.converter.deserialize("aaa", format="base16") with self.assertRaises(ConverterError): self.converter.deserialize("AA~AA", format="base64") def test_unknown_formats(self) -> None: with self.assertRaises(ConverterError): self.converter.serialize("foo") with self.assertRaises(ConverterError) as cm: self.converter.serialize("foo", format="foo") self.assertEqual("Unknown format 'foo'", str(cm.exception)) with self.assertRaises(ConverterError): self.converter.deserialize("foo") with self.assertRaises(ConverterError): self.converter.deserialize("foo", format="foo") class DecimalConverterTests(unittest.TestCase): def setUp(self) -> None: self.converter = converter.type_converter(Decimal) def test_deserialize(self) -> None: with self.assertRaises(ConverterError): self.converter.deserialize("a") self.assertEqual(Decimal(1), self.converter.deserialize("1")) def test_serialize(self) -> None: self.assertEqual("2.1", self.converter.serialize(Decimal("2.1"))) self.assertEqual("INF", self.converter.serialize(Decimal("inf"))) self.assertEqual("INF", self.converter.serialize(Decimal("+inf"))) self.assertEqual("-INF", self.converter.serialize(Decimal("-inf"))) self.assertEqual( "0.0000000877683", self.converter.serialize(Decimal("8.77683E-8")) ) class DateTimeConverterTests(unittest.TestCase): def setUp(self) -> None: self.converter = converter.type_converter(datetime) def test_converter(self) -> None: original = "21 June 2018 15:40" fmt = "%d %B %Y %H:%M" obj = self.converter.deserialize(original, format=fmt) self.assertEqual(datetime(2018, 6, 21, 15, 40), obj) self.assertEqual(original, self.converter.serialize(obj, format=fmt)) def test_serialize_raises_exception(self) -> None: with self.assertRaises(ConverterError): self.converter.serialize(datetime(2018, 6, 21, 15, 40)) with self.assertRaises(ConverterError): self.converter.serialize(1, format="") def test_deserialize_raises_exception(self) -> None: with self.assertRaises(ConverterError): self.converter.deserialize("21 June 2018 15:40", format="%Y") with self.assertRaises(ConverterError): self.converter.deserialize(1, format="%Y") with self.assertRaises(ConverterError): self.converter.deserialize("21 June 2018 15:40") with self.assertRaises(ConverterError): self.converter.deserialize("21 June 2018 15:40", format="") class DateConverterTests(unittest.TestCase): def setUp(self) -> None: self.converter = converter.type_converter(date) def test_converter(self) -> None: original = "21 June 2018" fmt = "%d %B %Y" obj = self.converter.deserialize(original, format=fmt) self.assertEqual(date(2018, 6, 21), obj) self.assertEqual(original, self.converter.serialize(obj, format=fmt)) class TimeConverterTests(unittest.TestCase): def setUp(self) -> None: self.converter = converter.type_converter(time) def test_converter(self) -> None: original = "1255" fmt = "%H%M" obj = self.converter.deserialize(original, format=fmt) self.assertEqual(time(12, 55), obj) self.assertEqual(original, self.converter.serialize(obj, format=fmt)) class QNameConverterTests(unittest.TestCase): def setUp(self) -> None: self.converter = converter.type_converter(QName) def test_deserialize(self) -> None: convert = self.converter.deserialize invalid = ["a:b", "", " ", "{a}1b", "{a} b", "{|}b", "a a"] for inv in invalid: with self.assertRaises(ConverterError): convert(inv) self.assertEqual(QName("{a}b"), convert("{a}b")) self.assertEqual(QName("a"), convert("a", ns_map={})) self.assertEqual(QName("aa", "b"), convert("a:b", ns_map={"a": "aa"})) def test_serialize(self) -> None: ns_map = {"c_prefix": "c"} convert = self.converter.serialize self.assertEqual("a", convert(QName("a"), ns_map=ns_map)) self.assertEqual("a", convert(QName("a"))) self.assertEqual("ns1:b", convert(QName("a", "b"), ns_map=ns_map)) self.assertEqual("{a}b", convert(QName("a", "b"))) self.assertEqual("c_prefix:c", convert(QName("c", "c"), ns_map=ns_map)) self.assertEqual("{c}c", convert(QName("c", "c"))) class EnumA(Enum): A = QName("a", "b") B = Decimal("+inf") C = 2.1 D = (2.1, "a") E = (2.1, "a", float("nan")) F = (2.1, "a", 2) G = "x y z" H = Telephone(1, 2, 3) J = (Telephone(1, 2, 3), Telephone(4, 5, 6)) class EnumConverterTests(unittest.TestCase): def setUp(self) -> None: self.converter = converter.type_converter(Enum) @pytest.mark.skipif( sys.version_info == (3, 11, 1, "final", 0), reason="https://github.com/python/cpython/issues/100098", ) def test_deserialize(self) -> None: convert = self.converter.deserialize self.assertEqual(EnumA.C, convert("2.1", data_type=EnumA)) self.assertEqual(EnumA.C, convert(2.1, data_type=EnumA)) self.assertEqual(EnumA.E, convert(["2.1", "a", "NaN"], data_type=EnumA)) self.assertEqual(EnumA.F, convert([2.1, "a", 2], data_type=EnumA)) self.assertEqual(EnumA.D, convert(" 2.1 a ", data_type=EnumA)) self.assertEqual(EnumA.G, convert(" x \n y z ", data_type=EnumA)) self.assertEqual(EnumA.H, convert("1-2-3", data_type=EnumA)) self.assertEqual(EnumA.J, convert(("1-2-3", "4-5-6"), data_type=EnumA)) with self.assertRaises(ConverterError) as cm: convert(["2.1", "a", "NaN"], data_type=int) self.assertEqual("'' is not an enum", str(cm.exception)) with self.assertRaises(ConverterError): convert("nope", data_type=EnumA) def test_serialize(self) -> None: ns_map = {} self.assertEqual("ns0:b", self.converter.serialize(EnumA.A, ns_map=ns_map)) self.assertEqual("INF", self.converter.serialize(EnumA.B)) self.assertEqual("2.1", self.converter.serialize(EnumA.C)) class ProxyConverterTests(unittest.TestCase): def setUp(self) -> None: self.converter = ProxyConverter(lambda x: int(x)) def test_deserialize(self) -> None: with self.assertRaises(ConverterError): self.converter.deserialize("a") self.assertEqual(1, self.converter.deserialize("1")) def test_serialize(self) -> None: self.assertEqual("1", self.converter.serialize(1)) class XmlDurationConverterTests(unittest.TestCase): def setUp(self) -> None: self.converter = converter.type_converter(XmlDuration) def test_deserialize(self) -> None: self.assertIsInstance(self.converter.deserialize("P20M"), XmlDuration) with self.assertRaises(ConverterError): self.converter.deserialize("P-20M") def test_serialize(self) -> None: actual = self.converter.serialize(XmlDuration("PT3S")) self.assertEqual("PT3S", actual) self.assertNotIsInstance(actual, XmlDuration) class XmlPeriodConverterTests(unittest.TestCase): def setUp(self) -> None: self.converter = converter.type_converter(XmlPeriod) def test_deserialize(self) -> None: self.assertIsInstance(self.converter.deserialize("---15Z"), XmlPeriod) with self.assertRaises(ConverterError): self.converter.deserialize("---1Z") def test_serialize(self) -> None: actual = self.converter.serialize(XmlPeriod("---15Z")) self.assertEqual("---15Z", actual) self.assertNotIsInstance(actual, XmlPeriod) tefra-xsdata-cf7afe8/tests/formats/test_mixins.py000066400000000000000000000051301526437326600224340ustar00rootroot00000000000000import datetime from collections.abc import Iterator from unittest import mock from xsdata import __version__ from xsdata.codegen.exceptions import CodegenError from xsdata.codegen.models import Class from xsdata.formats.mixins import AbstractGenerator, GeneratorResult from xsdata.models.config import GeneratorConfig from xsdata.utils.testing import ClassFactory, FactoryTestCase class NoneGenerator(AbstractGenerator): def render(self, classes: list[Class]) -> Iterator[GeneratorResult]: """Do nothing.""" class AbstractGeneratorTests(FactoryTestCase): def setUp(self) -> None: config = GeneratorConfig() self.generator = NoneGenerator(config) super().setUp() def test_module_name(self) -> None: self.assertEqual("a", self.generator.module_name("a")) def test_package_name(self) -> None: self.assertEqual("a", self.generator.package_name("a")) @mock.patch.object(NoneGenerator, "module_name", return_value="mod") @mock.patch.object(NoneGenerator, "package_name", return_value="pck") def test_normalize_packages(self, *args) -> None: classes = [ ClassFactory.create(qname="{a}a", package="bar", module="mod"), ClassFactory.create(qname="{a}b", package="bar", module="mod"), ClassFactory.create(qname="b", package="bar", module="mod"), ] self.generator.normalize_packages(classes) self.assertEqual("pck", classes[0].package) self.assertEqual("pck", classes[1].package) self.assertEqual("pck", classes[2].package) self.assertEqual("mod", classes[0].module) self.assertEqual("mod", classes[1].module) self.assertEqual("mod", classes[2].module) with self.assertRaises(CodegenError): self.generator.normalize_packages(ClassFactory.list(1)) @mock.patch("datetime.datetime", wraps=datetime.datetime) def test_render_header(self, mock_datetime) -> None: actual = self.generator.render_header() self.assertEqual("", actual) mock_datetime.now.return_value = datetime.datetime( year=2023, month=2, day=22, hour=10, minute=20, second=25 ) self.generator.config.output.include_header = True actual = self.generator.render_header() now_iso_format = "2023-02-22 10:20:25" expected = ( f'"""This file was generated by xsdata, v{__version__}, on {now_iso_format}\n' "\n" "Generator: NoneGenerator\n" "See: https://xsdata.readthedocs.io/\n" '"""\n' ) self.assertEqual(expected, actual) tefra-xsdata-cf7afe8/tests/integration/000077500000000000000000000000001526437326600203655ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/integration/__init__.py000066400000000000000000000000001526437326600224640ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/integration/benchmarks/000077500000000000000000000000001526437326600225025ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/integration/benchmarks/__init__.py000066400000000000000000000000001526437326600246010ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/integration/benchmarks/conftest.py000066400000000000000000000004651526437326600247060ustar00rootroot00000000000000from tests import xsdata_temp_dir from xsdata.formats.dataclass.context import XmlContext xsdata_temp_dir.mkdir(parents=True, exist_ok=True) context = XmlContext() def pytest_unconfigure(config) -> None: for tmp_file in xsdata_temp_dir.glob("*"): tmp_file.unlink() xsdata_temp_dir.rmdir() tefra-xsdata-cf7afe8/tests/integration/benchmarks/test_converters.py000066400000000000000000000014461526437326600263120ustar00rootroot00000000000000import pytest from xsdata.models.datatype import XmlDate, XmlDateTime, XmlDuration, XmlTime @pytest.mark.benchmark(disable_gc=True, group="converters", min_rounds=100000) def test_xml_date_time(benchmark) -> None: benchmark(XmlDateTime.from_string, "2010-09-20T13:00:00.000+01:00") @pytest.mark.benchmark(disable_gc=True, group="converters", min_rounds=100000) def test_xml_time(benchmark) -> None: benchmark(XmlTime.from_string, "13:00:00.000+01:00") @pytest.mark.benchmark(disable_gc=True, group="converters", min_rounds=100000) def test_xml_date(benchmark) -> None: benchmark(XmlDate.from_string, "2010-09-20") @pytest.mark.benchmark(disable_gc=True, group="converters", min_rounds=100000) def test_xml_duration(benchmark) -> None: benchmark(XmlDuration, "P2Y6M5DT12H35M30.5S") tefra-xsdata-cf7afe8/tests/integration/benchmarks/test_handlers.py000066400000000000000000000023341526437326600257150ustar00rootroot00000000000000import random import pytest from tests import xsdata_temp_dir from tests.integration.benchmarks.utils import make_books, parse, write from xsdata.formats.dataclass.context import XmlContext from xsdata.formats.dataclass.parsers.handlers import LxmlEventHandler, XmlEventHandler from xsdata.formats.dataclass.serializers.writers import LxmlEventWriter, XmlEventWriter context = XmlContext() readers_list = [ LxmlEventHandler, XmlEventHandler, ] writers_list = [ XmlEventWriter, LxmlEventWriter, ] random.shuffle(readers_list) random.shuffle(writers_list) numbers = [100, 1000, 10000] fixtures = {number: make_books(number) for number in numbers} @pytest.mark.benchmark(disable_gc=True, group="Serialize") @pytest.mark.parametrize("number", numbers) @pytest.mark.parametrize("writer", writers_list) def test_serialize(benchmark, writer, number) -> None: benchmark(write, number, fixtures[number], writer) @pytest.mark.benchmark(disable_gc=True, group="Parse") @pytest.mark.parametrize("number", numbers) @pytest.mark.parametrize("handler", readers_list) def test_parse(benchmark, handler, number) -> None: src = xsdata_temp_dir.joinpath(f"benchmark_{number}.xml").read_bytes() benchmark(parse, src, handler) tefra-xsdata-cf7afe8/tests/integration/benchmarks/utils.py000066400000000000000000000035451526437326600242230ustar00rootroot00000000000000from tests.fixtures.books import BookForm, Books from tests.integration.benchmarks.conftest import context, xsdata_temp_dir from xsdata.formats.dataclass.parsers import JsonParser, XmlParser from xsdata.formats.dataclass.serializers import JsonSerializer, XmlSerializer from xsdata.models.datatype import XmlDate def make_books(how_many: int) -> Books: return Books( book=[ BookForm( author="Arne Dahl", title="Misterioso: A Crime Novel", genre="Thrillers & Suspense", price=15.95, pub_date=XmlDate(1999, 10, 5), review=( "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " "Integer at erat sagittis, accumsan mauris eu, egestas " "quam. Name tristique felis justo, vel iaculis ipsum cursus " "at. Praesent various enim ac nunc interdum placerat. " "Integer porttitor, nibh at viverra vehicula, leo dui " "suscipit nisi, ornare laoreet eros neque nec mi. Proin." ), id="9788831796781", ) for _ in range(how_many) ] ) def parse(source, handler) -> None: parser = XmlParser(context=context, handler=handler) parser.from_bytes(source, Books) def parse_json(source) -> None: parser = JsonParser(context=context) parser.from_bytes(source, Books) def write(size, obj, writer) -> None: with xsdata_temp_dir.joinpath(f"benchmark_{size}.xml").open("w") as f: serializer = XmlSerializer(writer=writer, context=context) serializer.write(f, obj) def write_json(size, obj) -> None: with xsdata_temp_dir.joinpath(f"benchmark_{size}.json").open("w") as f: serializer = JsonSerializer(context=context) serializer.write(f, obj) tefra-xsdata-cf7afe8/tests/integration/test_annotations.py000066400000000000000000000021031526437326600243270ustar00rootroot00000000000000import os import pytest from click.testing import CliRunner from tests import fixtures_dir, root from xsdata.cli import cli from xsdata.formats.dataclass.context import XmlContext from xsdata.formats.dataclass.parsers.xml import XmlParser from xsdata.utils.testing import load_class os.chdir(root) def test_annotations() -> None: filepath = fixtures_dir.joinpath("annotations") schema = filepath.joinpath("model.xsd") runner = CliRunner() result = runner.invoke( cli, ["generate", str(schema), f"--config={filepath.joinpath('xsdata.xml')!s}"] ) if result.exception: raise result.exception try: Measurement = load_class(result.output, "Measurement") unit = load_class(result.output, "unit") except Exception: pytest.fail("Could not load class with member having the same name as type") filename = str(filepath.joinpath("sample.xml")) parser = XmlParser(context=XmlContext()) measurement = parser.parse(filename, Measurement) assert measurement.value == 2.0 assert measurement.unit == unit.KG tefra-xsdata-cf7afe8/tests/integration/test_artists.py000066400000000000000000000022131526437326600234650ustar00rootroot00000000000000import os from click.testing import CliRunner from tests import fixtures_dir, root from xsdata.cli import cli from xsdata.formats.dataclass.parsers import XmlParser from xsdata.formats.dataclass.serializers import XmlSerializer from xsdata.formats.dataclass.serializers.writers import XmlEventWriter from xsdata.utils.testing import load_class os.chdir(root) def test_xml_documents() -> None: filepath = fixtures_dir.joinpath("artists") package = "tests.fixtures.artists" runner = CliRunner() result = runner.invoke(cli, ["generate", str(filepath), "--package", package]) if result.exception: raise result.exception clazz = load_class(result.output, "Metadata") parser = XmlParser() serializer = XmlSerializer(writer=XmlEventWriter) serializer.config.indent = " " serializer.config.xml_declaration = False ns_map = {None: "http://musicbrainz.org/ns/mmd-2.0#"} for i in range(1, 4): ap = filepath.joinpath(f"art00{i}.xml") obj = parser.from_path(ap, clazz) actual = serializer.render(obj, ns_map) assert ap.read_bytes().splitlines() == actual.encode().splitlines() tefra-xsdata-cf7afe8/tests/integration/test_books.py000066400000000000000000000014311526437326600231120ustar00rootroot00000000000000import os from click.testing import CliRunner from tests import fixtures_dir, root from xsdata.cli import cli from xsdata.utils.testing import load_class os.chdir(root) def test_books_schema() -> None: schema = fixtures_dir.joinpath("books/schema.xsd") package = "tests.fixtures.books" runner = CliRunner() result = runner.invoke( cli, [ "generate", str(schema), "--package", package, "--structure-style=namespaces", "--docstring-style=Google", ], catch_exceptions=False, ) if result.exception: raise result.exception clazz = load_class(result.output, "Books") assert clazz.Meta.name == "books" assert clazz.Meta.namespace == "urn:books" tefra-xsdata-cf7afe8/tests/integration/test_calculator.py000066400000000000000000000035501526437326600241320ustar00rootroot00000000000000import os from unittest import TestCase, mock from click.testing import CliRunner from tests import fixtures_dir, root from tests.fixtures.calculator import CalculatorSoapAdd, CalculatorSoapAddOutput from xsdata.cli import cli from xsdata.formats.dataclass.client import Client, Config from xsdata.formats.dataclass.serializers import XmlSerializer from xsdata.formats.dataclass.serializers.config import SerializerConfig from xsdata.formats.dataclass.transports import DefaultTransport from xsdata.utils.testing import load_class os.chdir(root) class CalculatorServiceTests(TestCase): def test_wsdl_codegen(self) -> None: schema = fixtures_dir.joinpath("calculator/services.wsdl") package = "tests.fixtures.calculator" runner = CliRunner() result = runner.invoke(cli, ["generate", str(schema), "--package", package]) if result.exception: raise result.exception clazz = load_class(result.output, "CalculatorSoapMultiplyOutput") self.assertEqual("Envelope", clazz.Meta.name) @mock.patch.object(DefaultTransport, "post") def test_client(self, mock_most) -> None: url = "http://www.dneonline.com/calculator.asmx" request = fixtures_dir.joinpath("calculator/AddRQ.xml").read_text() response = fixtures_dir.joinpath("calculator/AddRS.xml").read_bytes() headers = {"content-type": "text/xml", "SOAPAction": "http://tempuri.org/Add"} mock_most.return_value = response config = Config.from_service(CalculatorSoapAdd) serializer = XmlSerializer(config=SerializerConfig(indent=" ")) client = Client(config=config, serializer=serializer) result = client.send({"Body": {"Add": {"intA": 1, "intB": 3}}}) self.assertIsInstance(result, CalculatorSoapAddOutput) mock_most.assert_called_once_with(url, data=request, headers=headers) tefra-xsdata-cf7afe8/tests/integration/test_compound.py000066400000000000000000000013721526437326600236250ustar00rootroot00000000000000import os from click.testing import CliRunner from tests import fixtures_dir, root from tests.conftest import validate_bindings from xsdata.cli import cli from xsdata.utils.testing import load_class os.chdir(root) def test_xml_documents() -> None: schema = fixtures_dir.joinpath("compound/schema.xsd") package = "tests.fixtures.compound.models" runner = CliRunner() result = runner.invoke( cli, [ "generate", str(schema), "-p", package, "-ss", "single-package", "--compound-fields", ], ) if result.exception: raise result.exception clazz = load_class(result.output, "Root") validate_bindings(schema, clazz) tefra-xsdata-cf7afe8/tests/integration/test_docstrings.py000066400000000000000000000037471526437326600241700ustar00rootroot00000000000000import os from click.testing import CliRunner from tests import fixtures_dir, root from xsdata.cli import cli os.chdir(root) schema = fixtures_dir.joinpath("docstrings/schema.xsd") def test_generate_restructured_docstrings() -> None: package = "tests.fixtures.docstrings.rst" runner = CliRunner() runner.invoke( cli, [ "generate", str(schema), "--package", package, "--docstring-style", "reStructuredText", ], catch_exceptions=False, ) def test_generate_numpy_docstrings() -> None: package = "tests.fixtures.docstrings.numpy" runner = CliRunner() runner.invoke( cli, [ "generate", str(schema), "--package", package, "--docstring-style", "NumPy", ], catch_exceptions=False, ) def test_generate_google_docstrings() -> None: package = "tests.fixtures.docstrings.google" runner = CliRunner() runner.invoke( cli, [ "generate", str(schema), "--package", package, "--docstring-style", "Google", ], catch_exceptions=False, ) def test_generate_accessible_docstrings() -> None: package = "tests.fixtures.docstrings.accessible" runner = CliRunner() runner.invoke( cli, [ "generate", str(schema), "--package", package, "--docstring-style", "Accessible", ], catch_exceptions=False, ) def test_generate_blank_docstrings() -> None: package = "tests.fixtures.docstrings.blank" runner = CliRunner() runner.invoke( cli, [ "generate", str(schema), "--package", package, "--docstring-style", "Blank", ], catch_exceptions=False, ) tefra-xsdata-cf7afe8/tests/integration/test_dtd.py000066400000000000000000000011211526437326600225440ustar00rootroot00000000000000import os from click.testing import CliRunner from tests import fixtures_dir, root from xsdata.cli import cli from xsdata.utils.testing import load_class os.chdir(root) def test_dtd_documents() -> None: runner = CliRunner() result = runner.invoke( cli, [ "generate", str(fixtures_dir.joinpath("dtd/complete_example.dtd")), "--package", "tests.fixtures.dtd.models", ], ) if result.exception: raise result.exception clazz = load_class(result.output, "Post") assert clazz is not None tefra-xsdata-cf7afe8/tests/integration/test_hello_rpc.py000066400000000000000000000071041526437326600237470ustar00rootroot00000000000000import os from unittest import TestCase, mock import pytest from click.testing import CliRunner from tests import fixtures_dir, root from tests.fixtures.hello import ( GetHelloAsStringResponse, HelloError, HelloGetHelloAsString, HelloGetHelloAsStringOutput, ) from xsdata.cli import cli from xsdata.formats.dataclass.client import Client, Config from xsdata.formats.dataclass.serializers import XmlSerializer from xsdata.formats.dataclass.serializers.config import SerializerConfig from xsdata.formats.dataclass.transports import DefaultTransport from xsdata.utils.testing import load_class os.chdir(root) class HelloRpcServiceTests(TestCase): def test_wsdl_codegen(self) -> None: schema = fixtures_dir.joinpath("hello/hello.wsdl") package = "tests.fixtures.hello" runner = CliRunner() result = runner.invoke(cli, ["generate", str(schema), "--package", package]) if result.exception: raise result.exception clazz = load_class(result.output, "HelloGetHelloAsStringOutput") self.assertEqual("Envelope", clazz.Meta.name) @mock.patch.object(DefaultTransport, "post") def test_client(self, mock_most) -> None: url = "http://localhost:9999/ws/hello" request = fixtures_dir.joinpath("hello/HelloRQ.xml").read_text() response = fixtures_dir.joinpath("hello/HelloRS.xml").read_bytes() headers = {"content-type": "text/xml"} mock_most.return_value = response config = Config.from_service(HelloGetHelloAsString) serializer = XmlSerializer(config=SerializerConfig(indent=" ")) client = Client(config=config, serializer=serializer) result = client.send({"Body": {"getHelloAsString": {"arg0": "chris"}}}) self.assertIsInstance(result, HelloGetHelloAsString.output) body = HelloGetHelloAsStringOutput.Body( get_hello_as_string_response=GetHelloAsStringResponse( return_value="Hello chris" ) ) self.assertEqual(body, result.body) mock_most.assert_called_once_with(url, data=request, headers=headers) @mock.patch.object(DefaultTransport, "post") def test_client_with_soap_fault(self, mock_most) -> None: url = "http://localhost:9999/ws/hello" request = fixtures_dir.joinpath("hello/HelloRQ.xml").read_text() response = fixtures_dir.joinpath("hello/HelloRS_SoapFault.xml").read_bytes() headers = {"content-type": "text/xml"} mock_most.return_value = response config = Config.from_service(HelloGetHelloAsString) serializer = XmlSerializer(config=SerializerConfig(indent=" ")) client = Client(config=config, serializer=serializer) result = client.send({"Body": {"getHelloAsString": {"arg0": "chris"}}}) self.assertIsInstance(result, HelloGetHelloAsString.output) fault = HelloGetHelloAsStringOutput.Body.Fault( faultcode="S:Server", faultstring="foobar", detail=HelloGetHelloAsStringOutput.Body.Fault.Detail( hello_error=HelloError(message="foobar") ), ) self.assertEqual(fault, result.body.fault) mock_most.assert_called_once_with(url, data=request, headers=headers) @pytest.mark.skip def test_live(self) -> None: config = Config.from_service(HelloGetHelloAsString) serializer = XmlSerializer(config=SerializerConfig(indent=" ")) client = Client(config=config, serializer=serializer) result = client.send({"Body": {"getHelloAsString": {"arg0": "chris"}}}) print(result) tefra-xsdata-cf7afe8/tests/integration/test_primer.py000066400000000000000000000012761526437326600233020ustar00rootroot00000000000000import os from click.testing import CliRunner from tests import fixtures_dir, root from tests.conftest import validate_bindings from xsdata.cli import cli from xsdata.utils.testing import load_class os.chdir(root) def test_primer_schema() -> None: schema = fixtures_dir.joinpath("primer/order.xsd") package = "tests.fixtures.primer" runner = CliRunner() result = runner.invoke( cli, ["generate", str(schema), "--package", package, "--docstring-style", "NumPy"], ) if result.exception: raise result.exception clazz = load_class(result.output, "PurchaseOrder") assert clazz.Meta.name == "purchaseOrder" validate_bindings(schema, clazz) tefra-xsdata-cf7afe8/tests/integration/test_series.py000066400000000000000000000021621526437326600232710ustar00rootroot00000000000000import json import os from click.testing import CliRunner from tests import fixtures_dir, root from xsdata.cli import cli from xsdata.formats.dataclass.parsers import JsonParser from xsdata.formats.dataclass.serializers import JsonSerializer from xsdata.formats.dataclass.serializers.config import SerializerConfig from xsdata.utils.testing import filter_none, load_class os.chdir(root) def test_json_documents() -> None: filepath = fixtures_dir.joinpath("series") package = "tests.fixtures.series" runner = CliRunner() result = runner.invoke( cli, ["generate", str(filepath.joinpath("samples")), "--package", package] ) if result.exception: raise result.exception clazz = load_class(result.output, "Series") parser = JsonParser() config = SerializerConfig(indent=" ") serializer = JsonSerializer(config=config) for i in range(1, 3): ori = filepath.joinpath(f"samples/show{i}.json").read_text() obj = parser.from_string(ori, clazz) actual = serializer.render(obj) assert filter_none(json.loads(ori)) == filter_none(json.loads(actual)) tefra-xsdata-cf7afe8/tests/integration/test_stripe.py000066400000000000000000000022421526437326600233040ustar00rootroot00000000000000import json import os from click.testing import CliRunner from tests import fixtures_dir, root from xsdata.cli import cli from xsdata.formats.dataclass.parsers import JsonParser from xsdata.formats.dataclass.serializers import JsonSerializer from xsdata.formats.dataclass.serializers.config import SerializerConfig from xsdata.utils.testing import filter_none, load_class os.chdir(root) def test_json_documents() -> None: filepath = fixtures_dir.joinpath("stripe") runner = CliRunner() result = runner.invoke( cli, [ "generate", str(filepath.joinpath("samples")), f"--config={filepath.joinpath('.xsdata.xml')!s}", ], ) if result.exception: raise result.exception clazz = load_class(result.output, "Balance") parser = JsonParser() config = SerializerConfig(indent=" ") serializer = JsonSerializer(config=config) for sample in filepath.joinpath("samples").glob("*.json"): ori = sample.read_text() obj = parser.from_string(ori, clazz) actual = serializer.render(obj) assert filter_none(json.loads(ori)) == filter_none(json.loads(actual)) tefra-xsdata-cf7afe8/tests/integration/test_type_mapping.py000066400000000000000000000021561526437326600244760ustar00rootroot00000000000000from unittest import TestCase from tests.fixtures.typemapping.city import City from tests.fixtures.typemapping.house import House from tests.fixtures.typemapping.street import Street from xsdata.formats.dataclass.serializers import ( JsonSerializer, PycodeSerializer, XmlSerializer, ) from xsdata.formats.dataclass.serializers.config import SerializerConfig class TypeMappingTests(TestCase): def test_type_mapping(self) -> None: city1 = City(name="footown") street1 = Street(name="foostreet") house1 = House(number=23) city1.streets.append(street1) street1.houses.append(house1) type_mapping = {"City": City, "Street": Street, "House": House} serializer_config = SerializerConfig(globalns=type_mapping) json_serializer = JsonSerializer(config=serializer_config) xml_serializer = XmlSerializer(config=serializer_config) pycode_serializer = PycodeSerializer() for model in (city1, street1, house1): json_serializer.render(model) xml_serializer.render(model) pycode_serializer.render(model) tefra-xsdata-cf7afe8/tests/integration/test_wrapper.py000066400000000000000000000014331526437326600234570ustar00rootroot00000000000000import os from click.testing import CliRunner from tests import fixtures_dir, root from tests.conftest import validate_bindings from xsdata.cli import cli from xsdata.utils.testing import load_class os.chdir(root) def test_xml_documents() -> None: schema = fixtures_dir.joinpath("wrapper/schema.xsd") package = "tests.fixtures.wrapper.models" runner = CliRunner() result = runner.invoke( cli, [ "generate", str(schema), "-p", package, "-ss", "single-package", "--wrapper-fields", "--compound-fields", ], ) if result.exception: raise result.exception clazz = load_class(result.output, "Wrapper") validate_bindings(schema, clazz) tefra-xsdata-cf7afe8/tests/models/000077500000000000000000000000001526437326600173255ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/models/__init__.py000066400000000000000000000000001526437326600214240ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/models/enums/000077500000000000000000000000001526437326600204545ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/models/enums/__init__.py000066400000000000000000000000001526437326600225530ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/models/enums/test_datatype.py000066400000000000000000000055101526437326600237010ustar00rootroot00000000000000from decimal import Decimal from unittest import TestCase from xml.etree.ElementTree import QName from xsdata.models.datatype import XmlDate, XmlDateTime, XmlPeriod, XmlTime from xsdata.models.enums import DataType class DataTypeTests(TestCase): def test_from_value_int(self) -> None: self.assertEqual(DataType.SHORT, DataType.from_value(-32768)) self.assertEqual(DataType.SHORT, DataType.from_value(32767)) self.assertEqual(DataType.INT, DataType.from_value(-32769)) self.assertEqual(DataType.INT, DataType.from_value(32768)) self.assertEqual(DataType.INT, DataType.from_value(-2147483648)) self.assertEqual(DataType.INT, DataType.from_value(2147483647)) self.assertEqual(DataType.LONG, DataType.from_value(-2147483649)) self.assertEqual(DataType.LONG, DataType.from_value(2147483648)) self.assertEqual(DataType.LONG, DataType.from_value(-9223372036854775808)) self.assertEqual(DataType.LONG, DataType.from_value(9223372036854775807)) self.assertEqual(DataType.INTEGER, DataType.from_value(-9223372036854775809)) self.assertEqual(DataType.INTEGER, DataType.from_value(9223372036854775808)) def test_from_value_float(self) -> None: self.assertEqual(DataType.FLOAT, DataType.from_value(-1.175494351e-38)) self.assertEqual(DataType.FLOAT, DataType.from_value(3.402823466e38)) self.assertEqual(DataType.DOUBLE, DataType.from_value(-1.175494352e-38)) self.assertEqual(DataType.DOUBLE, DataType.from_value(3.402823467e38)) def test_from_value_period(self) -> None: g_day = XmlPeriod("---20-02:44") self.assertEqual(DataType.G_DAY, DataType.from_value(g_day)) g_month_day = XmlPeriod("--12-20-02:44") self.assertEqual(DataType.G_MONTH_DAY, DataType.from_value(g_month_day)) g_month = XmlPeriod("--12") self.assertEqual(DataType.G_MONTH, DataType.from_value(g_month)) g_year_month = XmlPeriod("2021-01") self.assertEqual(DataType.G_YEAR_MONTH, DataType.from_value(g_year_month)) g_year = XmlPeriod("2021") self.assertEqual(DataType.G_YEAR, DataType.from_value(g_year)) def test_from_value_simple(self) -> None: self.assertEqual(DataType.BOOLEAN, DataType.from_value(True)) self.assertEqual(DataType.DECIMAL, DataType.from_value(Decimal("1.1"))) self.assertEqual(DataType.QNAME, DataType.from_value(QName("a"))) self.assertEqual(DataType.STRING, DataType.from_value("a")) self.assertEqual(DataType.DATE, DataType.from_value(XmlDate(2021, 1, 1))) self.assertEqual(DataType.TIME, DataType.from_value(XmlTime(12, 1, 1))) self.assertEqual( DataType.DATE_TIME, DataType.from_value(XmlDateTime(2002, 1, 1, 12, 1, 1)) ) self.assertEqual(DataType.STRING, DataType.from_value(self)) tefra-xsdata-cf7afe8/tests/models/test_config.py000066400000000000000000000201171526437326600222040ustar00rootroot00000000000000import tempfile import warnings from pathlib import Path from unittest import TestCase from xsdata import __version__ from xsdata.codegen.exceptions import CodegenError from xsdata.exceptions import ParserError from xsdata.models.config import ( ExtensionType, GeneratorConfig, GeneratorExtension, GeneratorOutput, OutputFormat, ) class GeneratorConfigTests(TestCase): def setUp(self) -> None: self.maxDiff = None def test_create(self) -> None: file_path = Path(tempfile.mktemp()) obj = GeneratorConfig.create() with file_path.open("w") as fp: obj.write(fp, obj) expected = ( '\n' f'\n' ' \n' " generated\n" ' dataclasses\n' " filenames\n" " reStructuredText\n" " false\n" ' false\n' " false\n" " false\n" " false\n" " false\n" " \n" " \n" ' \n' ' \n' ' \n' ' \n' ' \n' " \n" " \n" ' \n' ' \n' ' \n' ' \n' ' \n' ' \n' ' \n' ' \n' ' \n' ' \n' ' \n' " \n" " \n" "\n" ) self.assertEqual(expected, file_path.read_text()) file_path.unlink() def test_read(self) -> None: existing = ( '\n' '\n' ' \n' " foo.bar\n" " \n" " \n" ' \n' " \n" " \n" "\n" ) file_path = Path(tempfile.mktemp()) file_path.write_text(existing, encoding="utf-8") config = GeneratorConfig.read(file_path) with file_path.open("w") as fp: GeneratorConfig.write(fp, config) expected = ( '\n' f'\n' ' \n' " foo.bar\n" ' dataclasses\n' " filenames\n" " reStructuredText\n" " false\n" ' false\n' " false\n" " false\n" " false\n" " false\n" " \n" " \n" ' \n' ' \n' ' \n' ' \n' ' \n' " \n" " \n" " \n" "\n" ) self.assertEqual(expected, file_path.read_text()) def test_read_with_wrong_value(self) -> None: existing = ( '\n' '\n' ' \n' " unknown\n" " \n" "\n" ) file_path = Path(tempfile.mktemp()) file_path.write_text(existing, encoding="utf-8") with self.assertRaises(ParserError): GeneratorConfig.read(file_path) def test_format_with_invalid_eq_config(self) -> None: with warnings.catch_warnings(record=True) as w: OutputFormat(eq=False, order=True) self.assertEqual("Enabling eq because order is true", str(w[-1].message)) def test_generic_collections_requires_frozen_false(self) -> None: with warnings.catch_warnings(record=True) as w: output = GeneratorOutput( generic_collections=True, format=OutputFormat(frozen=True) ) self.assertFalse(output.generic_collections) self.assertEqual( "Generic Collections, requires frozen=False, reverting...", str(w[-1].message), ) def test_extension_with_invalid_import_string(self) -> None: cases = [None, "", "bar"] for case in cases: with self.assertRaises(CodegenError): GeneratorExtension( type=ExtensionType.DECORATOR, import_string=case, class_name="Test" ) def test_extension_with_invalid_class_name_pattern(self) -> None: with self.assertRaises(CodegenError): GeneratorExtension( type=ExtensionType.DECORATOR, import_string="a.b", class_name="*Foo" ) def test_extension_with_parent_path(self) -> None: GeneratorExtension( type=ExtensionType.DECORATOR, import_string="a.b", class_name="Foo", parent_path=r"Grandpa\.Papa$", ) def test_extension_with_invalid_parent_path(self) -> None: with self.assertRaises(CodegenError): GeneratorExtension( type=ExtensionType.DECORATOR, import_string="a.b", class_name="Foo", parent_path=r"*Grandpa\.Papa$", ) tefra-xsdata-cf7afe8/tests/models/test_datatype.py000066400000000000000000000530411526437326600225540ustar00rootroot00000000000000from datetime import date, datetime, time, timedelta, timezone from unittest import TestCase from xsdata.models.datatype import XmlDate, XmlDateTime, XmlDuration, XmlPeriod, XmlTime def filter_none(mapping: dict) -> dict: return {k: v for k, v in mapping.items() if v is not None} class XmlDateTests(TestCase): def test_from_string(self) -> None: examples = { "2002-01-01-00:00": XmlDate(2002, 1, 1, 0), "2002-01-01-02:15": XmlDate(2002, 1, 1, -135), "2002-01-01+02:15": XmlDate(2002, 1, 1, 135), "2002-01-01Z": XmlDate(2002, 1, 1, 0), "2002-01-01": XmlDate(2002, 1, 1), } for value, expected in examples.items(): actual = XmlDate.from_string(value) self.assertEqual(expected, actual, value) def test_from_string_invalid(self) -> None: examples = [ "a", 1, "2002/01-01", "2002/01/01", "2002-01-01U", "2019-02-29", "2001-13-45", "2001-00-00", "2001-10-32", ] for example in examples: with self.assertRaises(ValueError, msg=example): XmlDate.from_string(example) def test_str(self) -> None: examples = { "2002-01-01-00:00": "2002-01-01Z", "2002-01-01-02:15": "2002-01-01-02:15", "2002-01-01+02:15": "2002-01-01+02:15", "2002-01-01Z": "2002-01-01Z", "2002-01-01": "2002-01-01", "-2002-01-01": "-2002-01-01", } for value, expected in examples.items(): actual = XmlDate.from_string(value) self.assertEqual(expected, str(actual), value) def test_repr(self) -> None: examples = { "2002-01-01-00:00": "XmlDate(2002, 1, 1, 0)", "2002-01-01-02:15": "XmlDate(2002, 1, 1, -135)", "2002-01-01+02:15": "XmlDate(2002, 1, 1, 135)", "2002-01-01Z": "XmlDate(2002, 1, 1, 0)", "2002-01-01": "XmlDate(2002, 1, 1)", "-2002-01-01": "XmlDate(-2002, 1, 1)", } for value, expected in examples.items(): actual = XmlDate.from_string(value) self.assertEqual(expected, repr(actual), value) def test_datetime_helpers(self) -> None: tz_plus_two = timezone(timedelta(minutes=120)) obj = datetime(2021, 1, 1, 0, 0, tzinfo=tz_plus_two) actual = XmlDate(2021, 1, 1, 120) self.assertEqual(actual, XmlDate.from_datetime(obj)) self.assertEqual(obj, actual.to_datetime()) self.assertEqual(obj.date(), actual.to_date()) actual = XmlDate(2021, 1, 1) self.assertEqual(actual, XmlDate.from_date(obj.date())) self.assertEqual(obj.date(), actual.to_date()) obj = datetime(2021, 1, 1, 0, 0, tzinfo=timezone.utc) actual = XmlDate(2021, 1, 1, 0) self.assertEqual(actual, XmlDate.from_datetime(obj)) actual = XmlDate(2021, 1, 1) self.assertEqual(actual, XmlDate.from_date(obj.date())) obj = datetime(2021, 1, 1, 0, 0) actual = XmlDate(2021, 1, 1, None) self.assertEqual(actual, XmlDate.from_datetime(obj)) self.assertEqual(actual, XmlDate.from_date(obj.date())) self.assertEqual(XmlDate.from_date(date.today()), XmlDate.today()) now = datetime.now() self.assertEqual(XmlDate.from_datetime(now), XmlDate.today()) def test_replace(self) -> None: actual = XmlDate(2021, 1, 1, 120) self.assertIsNot(actual, actual.replace()) self.assertEqual("2022-01-01+02:00", str(actual.replace(2022))) self.assertEqual("2021-01-01", str(actual.replace(offset=None))) self.assertEqual("2021-01-01Z", str(actual.replace(offset=False))) self.assertEqual("2022-12-25+02:00", str(actual.replace(2022, 12, 25))) self.assertEqual("2022-12-25+00:10", str(actual.replace(2022, 12, 25, 10))) def test_comparisons(self) -> None: a = XmlDate(2021, 1, 1) b = XmlDate(2021, 1, 2) self.assertLess(a, b) self.assertLessEqual(a, b) self.assertLessEqual(a, a.replace()) self.assertGreater(b, a) self.assertGreaterEqual(b, a) self.assertGreaterEqual(a.replace(), a) self.assertEqual(a, a.replace()) self.assertNotEqual(a, a.replace(offset=0)) class XmlDateTimeTests(TestCase): def test_from_string(self) -> None: examples = { "2002-01-01T12:01:01-00:00": XmlDateTime(2002, 1, 1, 12, 1, 1, 0, 0), "2002-01-01T12:01:01-02:15": XmlDateTime(2002, 1, 1, 12, 1, 1, 0, -135), "2002-01-01T12:01:01+02:15": XmlDateTime(2002, 1, 1, 12, 1, 1, 0, 135), "2002-01-01T12:01:01.010Z": XmlDateTime(2002, 1, 1, 12, 1, 1, 10000000, 0), "2002-01-01T12:01:01.123456": XmlDateTime(2002, 1, 1, 12, 1, 1, 123456000), "2002-01-01T12:01:01": XmlDateTime(2002, 1, 1, 12, 1, 1, 0), "2010-09-19T24:00:00Z": XmlDateTime(2010, 9, 19, 24, 0, 0, 0, 0), "2010-09-19T03:35:25.482845071Z": XmlDateTime( 2010, 9, 19, 3, 35, 25, 482845071, 0 ), } for value, expected in examples.items(): actual = XmlDateTime.from_string(value) self.assertEqual(expected, actual, value) def test_from_string_invalid(self) -> None: examples = [ "a", 1, "2002/01-01T12:01:01", "2002/01/01T12:01:01", "2002-01-01 12:01:01", "2002-01-01T12-01:01", "2002-01-01T12:01-01", "2002-01-01T12:01:01U", ] for example in examples: with self.assertRaises(ValueError, msg=example): XmlDateTime.from_string(example) def test_str(self) -> None: examples = { "2002-01-01T12:01:01-00:00": "2002-01-01T12:01:01Z", "2002-01-01T12:01:01-02:15": "2002-01-01T12:01:01-02:15", "2002-01-01T12:01:01+02:15": "2002-01-01T12:01:01+02:15", "2002-01-01T12:01:01.0100Z": "2002-01-01T12:01:01.010Z", "2002-01-01T12:01:01.123456": "2002-01-01T12:01:01.123456", "2002-01-01T12:01:01.123456000": "2002-01-01T12:01:01.123456", "2002-01-01T12:01:01.123000": "2002-01-01T12:01:01.123", "2002-01-01T12:01:01.123456001": "2002-01-01T12:01:01.123456001", "2002-01-01T12:01:01.0": "2002-01-01T12:01:01", "2002-01-01T12:01:01": "2002-01-01T12:01:01", "2010-09-19T24:00:00Z": "2010-09-19T24:00:00Z", "-0001-09-19T24:00:00Z": "-0001-09-19T24:00:00Z", } for value, expected in examples.items(): actual = XmlDateTime.from_string(value) self.assertEqual(expected or value, str(actual), value) def test_repr(self) -> None: examples = { "2002-01-01T12:01:01-00:00": "XmlDateTime(2002, 1, 1, 12, 1, 1, 0, 0)", "2002-01-01T12:01:01-02:15": "XmlDateTime(2002, 1, 1, 12, 1, 1, 0, -135)", "2002-01-01T12:01:01+02:15": "XmlDateTime(2002, 1, 1, 12, 1, 1, 0, 135)", "2002-01-01T12:01:01.0100Z": "XmlDateTime(2002, 1, 1, 12, 1, 1, 10000000, 0)", "2002-01-01T12:01:01.123456": "XmlDateTime(2002, 1, 1, 12, 1, 1, 123456000)", "2002-01-01T12:01:01.123000": "XmlDateTime(2002, 1, 1, 12, 1, 1, 123000000)", "2002-01-01T12:01:01.0": "XmlDateTime(2002, 1, 1, 12, 1, 1)", "2002-01-01T12:01:01": "XmlDateTime(2002, 1, 1, 12, 1, 1)", "2010-09-19T24:00:00Z": "XmlDateTime(2010, 9, 19, 24, 0, 0, 0, 0)", "-0001-09-19T24:00:00Z": "XmlDateTime(-1, 9, 19, 24, 0, 0, 0, 0)", } for value, expected in examples.items(): actual = XmlDateTime.from_string(value) self.assertEqual(expected, repr(actual), value) def test_datetime_helpers(self) -> None: tz_plus_135 = timezone(timedelta(seconds=8100)) obj = datetime(2002, 1, 1, 12, 1, 1, tzinfo=tz_plus_135) actual = XmlDateTime(2002, 1, 1, 12, 1, 1, 0, 135) self.assertEqual(actual, XmlDateTime.from_datetime(obj)) self.assertEqual(obj, actual.to_datetime()) obj = datetime(2002, 1, 1, 12, 1, 1, tzinfo=timezone.utc) actual = XmlDateTime(2002, 1, 1, 12, 1, 1, 0, 0) self.assertEqual(actual, XmlDateTime.from_datetime(obj)) self.assertEqual(obj, actual.to_datetime()) obj = datetime(2002, 1, 1, 12, 1, 1) actual = XmlDateTime(2002, 1, 1, 12, 1, 1, 0, None) self.assertEqual(actual, XmlDateTime.from_datetime(obj)) self.assertEqual(obj, actual.to_datetime()) now = datetime.now().replace(microsecond=0, second=0, minute=1) self.assertEqual( XmlDateTime.from_datetime(now), XmlDateTime.now().replace(fractional_second=0, second=0, minute=1), ) now = datetime.now(tz=timezone.utc).replace(microsecond=0, second=0, minute=1) self.assertEqual( XmlDateTime.from_datetime(now), XmlDateTime.utcnow().replace(fractional_second=0, second=0, minute=1), ) now = datetime.now(timezone.utc).replace(microsecond=0, second=0, minute=1) self.assertEqual( XmlDateTime.from_datetime(now), XmlDateTime.utcnow().replace(fractional_second=0, second=0, minute=1), ) def test_comparisons(self) -> None: a = XmlDateTime.from_string("2010-09-20T12:00:00Z") b = XmlDateTime.from_string("2010-09-20T13:00:00.000+01:00") c = a.replace(second=1) self.assertLess(a, c) self.assertLessEqual(a, c) self.assertLessEqual(a, a.replace()) self.assertGreater(c, a) self.assertGreaterEqual(c, a) self.assertGreaterEqual(a.replace(), a) self.assertEqual(a, a.replace()) self.assertNotEqual(a, c) self.assertEqual(a, b) a = XmlDateTime.from_string("-2010-09-20T12:00:00Z") b = XmlDateTime.from_string("-2010-09-20T13:00:00.000+01:00") self.assertEqual(a, b) def test_replace(self) -> None: actual = XmlDateTime(2002, 1, 1, 12, 1, 1, 0, -120) self.assertIsNot(actual, actual.replace()) self.assertEqual("2022-01-01T12:01:01-02:00", str(actual.replace(2022))) self.assertEqual("2022-12-01T12:01:01-02:00", str(actual.replace(2022, 12))) self.assertEqual("2022-12-25T12:01:01-02:00", str(actual.replace(2022, 12, 25))) self.assertEqual( "2022-12-25T10:01:01-02:00", str(actual.replace(2022, 12, 25, 10)) ) self.assertEqual( "2022-12-25T10:15:01-02:00", str(actual.replace(2022, 12, 25, 10, 15)) ) self.assertEqual( "2022-12-25T10:15:30-02:00", str(actual.replace(2022, 12, 25, 10, 15, 30)) ) self.assertEqual( "2022-12-25T10:15:30.150-02:00", str(actual.replace(2022, 12, 25, 10, 15, 30, 150000000)), ) self.assertEqual( "2022-12-25T10:15:30.150+01:00", str(actual.replace(2022, 12, 25, 10, 15, 30, 150000000, 60)), ) self.assertEqual( "2022-12-25T10:15:30.150", str(actual.replace(2022, 12, 25, 10, 15, 30, 150000000, None)), ) self.assertEqual( "2022-12-25T10:15:30.150Z", str(actual.replace(2022, 12, 25, 10, 15, 30, 150000000, False)), ) class XmlTimeTests(TestCase): def test_from_string(self) -> None: examples = { "12:01:01-00:00": XmlTime(12, 1, 1, 0, 0), "12:01:01-02:15": XmlTime(12, 1, 1, 0, -135), "12:01:01+02:15": XmlTime(12, 1, 1, 0, +135), "12:01:01.010Z": XmlTime(12, 1, 1, 10000000, 0), "12:01:01.123456": XmlTime(12, 1, 1, 123456000), "12:01:01": XmlTime(12, 1, 1, 0), "24:00:00Z": XmlTime(24, 0, 0, 0, 0), } for value, expected in examples.items(): actual = XmlTime.from_string(value) self.assertEqual(expected, actual, value) def test_from_string_invalid(self) -> None: examples = [ "a", 1, "12-01:01", "12:01-01", "12:01:01U", ] for example in examples: with self.assertRaises(ValueError, msg=example): XmlTime.from_string(example) def test_str(self) -> None: examples = { "12:01:01-00:00": "12:01:01Z", "12:01:01-02:15": "12:01:01-02:15", "12:01:01+02:15": "12:01:01+02:15", "12:01:01.010Z": "12:01:01.010Z", "12:01:01.123456": "12:01:01.123456", "12:01:01.123000": "12:01:01.123", "24:00:00Z": "24:00:00Z", } for value, expected in examples.items(): actual = XmlTime.from_string(value) self.assertEqual(expected or value, str(actual), value) def test_repr(self) -> None: examples = { "12:01:01-00:00": "XmlTime(12, 1, 1, 0, 0)", "12:01:01-02:15": "XmlTime(12, 1, 1, 0, -135)", "12:01:01+02:15": "XmlTime(12, 1, 1, 0, 135)", "12:01:01.010Z": "XmlTime(12, 1, 1, 10000000, 0)", "12:01:01.123456": "XmlTime(12, 1, 1, 123456000)", "12:01:01.123000": "XmlTime(12, 1, 1, 123000000)", "24:00:00Z": "XmlTime(24, 0, 0, 0, 0)", } for value, expected in examples.items(): actual = XmlTime.from_string(value) self.assertEqual(expected, repr(actual), value) def test_datetime_helpers(self) -> None: tz_minus_120 = timezone(timedelta(minutes=-120)) obj = time(12, 1, 1, 1, tzinfo=tz_minus_120) actual = XmlTime(12, 1, 1, 1000, -120) self.assertEqual(actual, XmlTime.from_time(obj)) self.assertEqual(obj, actual.to_time()) obj = time(12, 1, 1, 1, tzinfo=timezone.utc) actual = XmlTime(12, 1, 1, 1000, 0) self.assertEqual(actual, XmlTime.from_time(obj)) self.assertEqual(obj, actual.to_time()) obj = time(12, 1, 1, 1, tzinfo=timezone.utc) actual = XmlTime(12, 1, 1, 1000, 0) self.assertEqual(actual, XmlTime.from_time(obj)) self.assertEqual(obj, actual.to_time()) now = datetime.now().replace(microsecond=0, second=0, minute=1) self.assertEqual( XmlTime.from_time(now.time()), XmlTime.now().replace(fractional_second=0, second=0, minute=1), ) now = datetime.now(tz=timezone.utc).replace(microsecond=0, second=0, minute=1) self.assertEqual( XmlTime.from_time(now.time()), XmlTime.utcnow().replace(fractional_second=0, second=0, minute=1), ) now = datetime.now(timezone.utc).replace(microsecond=0, second=0, minute=1) self.assertEqual( XmlTime.from_time(now.time()), XmlTime.utcnow().replace(fractional_second=0, second=0, minute=1), ) def test_comparisons(self) -> None: a = XmlTime.from_string("12:00:00Z") b = XmlTime.from_string("13:00:00.000+01:00") c = XmlTime.from_string("13:00:00.000") self.assertLess(a, c) self.assertLessEqual(a, c) self.assertLessEqual(a, a.replace()) self.assertGreater(c, a) self.assertGreaterEqual(c, a) self.assertGreaterEqual(a.replace(), a) self.assertEqual(a, a.replace()) self.assertNotEqual(a, c) self.assertEqual(a, b) self.assertNotEqual(a, True) def test_replace(self) -> None: actual = XmlTime(12, 1, 1, 1, 0) self.assertIsNot(actual, actual.replace()) self.assertEqual("14:01:01.000000001Z", str(actual.replace(14))) self.assertEqual("14:02:01.000000001Z", str(actual.replace(14, 2))) self.assertEqual("14:02:03.000000001Z", str(actual.replace(14, 2, 3))) self.assertEqual("14:02:03Z", str(actual.replace(14, 2, 3, 0))) self.assertEqual("14:02:03", str(actual.replace(14, 2, 3, 0, None))) self.assertEqual("14:02:03+00:55", str(actual.replace(14, 2, 3, 0, 55))) class XmlDurationTests(TestCase): def test_properties(self) -> None: duration = XmlDuration("P2Y6M5DT12H35M30.5S") self.assertEqual(2, duration.years) self.assertEqual(6, duration.months) self.assertEqual(5, duration.days) self.assertEqual(12, duration.hours) self.assertEqual(35, duration.minutes) self.assertEqual(30.5, duration.seconds) self.assertFalse(duration.negative) def test_init_valid(self) -> None: fixtures = { "P2Y6M5DT12H35M30.5S": { "days": 5, "hours": 12, "minutes": 35, "months": 6, "negative": False, "seconds": 30.5, "years": 2, }, "P1DT2H": {"days": 1, "hours": 2, "negative": False}, "P20M": {"months": 20, "negative": False}, "PT20M": {"minutes": 20, "negative": False}, "P0Y20M0D": {"days": 0, "months": 20, "negative": False, "years": 0}, "P0Y": {"negative": False, "years": 0}, "-P60D": {"days": 60, "negative": True}, "-P60DT1M": {"days": 60, "minutes": 1, "negative": True}, "PT1M30.5S": {"minutes": 1, "negative": False, "seconds": 30.5}, } for value, expected in fixtures.items(): obj = XmlDuration(value) self.assertEqual(expected, filter_none(obj.asdict()), f"Failed in: {value}") self.assertEqual(value, str(obj), f"Failed out: {value}") def test_init_invalid(self) -> None: fixtures = [ True, "P-20M", "P20MT", "P1YM5D", "P15.5Y", "P1D2H", "1Y2M", "P2M1Y", "P", "PT15.S", ] for fixture in fixtures: with self.assertRaises(ValueError, msg=fixture): XmlDuration(fixture) def test_repr(self) -> None: obj = XmlDuration("PT20M") self.assertEqual('XmlDuration("PT20M")', repr(obj)) def test_user_string(self) -> None: obj = XmlDuration("PT20M") obj_b = obj + "5.0S" expected = { "days": None, "hours": None, "minutes": 20, "months": None, "negative": False, "seconds": 5.0, "years": None, } self.assertIsNot(obj, obj_b) self.assertEqual(expected, obj_b.asdict()) class XmlPeriodTests(TestCase): def test_properties(self) -> None: period = XmlPeriod("2001-10+02:00") self.assertEqual(2001, period.year) self.assertEqual(10, period.month) self.assertEqual(120, period.offset) self.assertIsNone(period.day) period = XmlPeriod("---20-02:44") self.assertIsNone(period.year) self.assertIsNone(period.month) self.assertEqual(20, period.day) self.assertEqual(-164, period.offset) def test_init_valid(self) -> None: fixtures = { "--05---05:00": {"month": 5, "offset": -300}, "---01 ": {"day": 1}, "---01Z": {"day": 1, "offset": 0}, "---01+02:00": {"day": 1, "offset": 120}, " ---01-04:00": {"day": 1, "offset": -240}, "---15": {"day": 15}, "---31": {"day": 31}, "--05": {"month": 5}, "--11Z": {"month": 11, "offset": 0}, "--11+02:00": {"month": 11, "offset": 120}, "--11-04:00": {"month": 11, "offset": -240}, "--02": {"month": 2}, "--05-01": {"day": 1, "month": 5}, "--11-01Z": {"day": 1, "month": 11, "offset": 0}, "--11-01+02:00": {"day": 1, "month": 11, "offset": 120}, "--11-01-04:00": {"day": 1, "month": 11, "offset": -240}, "--11-15": {"day": 15, "month": 11}, "--02-29": {"day": 29, "month": 2}, "2001": {"year": 2001}, "2001+02:00": {"year": 2001, "offset": 120}, "2001Z": {"year": 2001, "offset": 0}, "2001+00:00": {"year": 2001, "offset": 0}, "-2001": {"year": -2001}, "-20000": {"year": -20000}, "2001-10": {"month": 10, "year": 2001}, "2001-10+02:00": {"month": 10, "year": 2001, "offset": 120}, "2001-10Z": {"month": 10, "year": 2001, "offset": 0}, "2001-10+00:00": {"month": 10, "year": 2001, "offset": 0}, "-2001-10": {"month": 10, "year": -2001}, "-20000-04": {"month": 4, "year": -20000}, } for value, expected in fixtures.items(): self.assertEqual(expected, filter_none(XmlPeriod(value).as_dict())) def test_init_invalid(self) -> None: fixtures = [ # gDay "--30-", "---35", "---5", "15" # gMonth "-01-", "--13", "--1", # gYear "01", "210", # gMonthDay "-01-30-", "--01-35", "--1-5", "01-15", # gYearMonth "2001-13", "2001-13-26+02:00", "01-10", ] for value in fixtures: with self.assertRaises(ValueError, msg=value): XmlPeriod(value) def test_repr(self) -> None: obj = XmlPeriod("--02-29") self.assertEqual('XmlPeriod("--02-29")', repr(obj)) def test_equal(self) -> None: a = XmlPeriod("--02-29") b = XmlPeriod("--02-29") c = XmlPeriod("--03-30") self.assertEqual(a, b) self.assertNotEqual(a, c) self.assertNotEqual(1, c) tefra-xsdata-cf7afe8/tests/models/test_mixins.py000066400000000000000000000136461526437326600222570ustar00rootroot00000000000000from collections.abc import Generator, Iterator from unittest import TestCase from xsdata.codegen.exceptions import CodegenError from xsdata.models.enums import FormType, Namespace from xsdata.models.mixins import ElementBase from xsdata.models.xsd import Alternative, ComplexType, Element, SimpleType class ElementBaseTests(TestCase): def test_property_class_name(self) -> None: class Foo(ElementBase): pass self.assertEqual("Foo", Foo().class_name) def test_property_default_type(self) -> None: element = ElementBase() self.assertEqual("string", element.default_type) element = ElementBase() element.ns_map["xsd"] = Namespace.XS.uri self.assertEqual("xsd:string", element.default_type) def test_property_default_value(self) -> None: element = ElementBase() self.assertIsNone(element.default_value) element.fixed = "foo" self.assertEqual("foo", element.default_value) element.default = "bar" self.assertEqual("bar", element.default_value) element.default = "" self.assertEqual("", element.default_value) element.default = None element.fixed = "" self.assertEqual("", element.default_value) def test_property_display_help(self) -> None: element = ElementBase() self.assertIsNone(element.display_help) def test_property_bases(self) -> None: element = ElementBase() self.assertIsInstance(element.bases, Iterator) self.assertEqual([], list(element.bases)) def test_property_has_children(self) -> None: element = ElementBase() self.assertFalse(element.has_children) element = Element() self.assertFalse(element.has_children) element.complex_type = ComplexType() self.assertTrue(element.has_children) def test_property_has_form(self) -> None: element = ElementBase() self.assertFalse(element.has_form) element.form = None self.assertTrue(element.has_form) def test_property_is_abstract(self) -> None: element = ElementBase() self.assertFalse(element.is_abstract) element.abstract = False self.assertFalse(element.is_abstract) element.abstract = True self.assertTrue(element.is_abstract) def test_property_is_property(self) -> None: element = ElementBase() self.assertFalse(element.is_property) def test_property_is_fixed(self) -> None: element = ElementBase() self.assertFalse(element.is_fixed) element.fixed = None self.assertFalse(element.is_fixed) element.fixed = "foo" self.assertTrue(element.is_fixed) def test_property_is_mixed(self) -> None: element = ElementBase() self.assertFalse(element.is_mixed) def test_property_is_nillable(self) -> None: element = ElementBase() self.assertFalse(element.is_nillable) element.nillable = True self.assertTrue(element.is_nillable) def test_property_is_qualified(self) -> None: element = ElementBase() self.assertFalse(element.is_qualified) element.form = None self.assertFalse(element.is_qualified) element.form = FormType.UNQUALIFIED self.assertFalse(element.is_qualified) element.form = FormType.QUALIFIED self.assertTrue(element.is_qualified) element = ElementBase() element.form = FormType.UNQUALIFIED element.ref = None self.assertFalse(element.is_qualified) element.ref = "foo" self.assertTrue(element.is_qualified) def test_property_is_wildcard(self) -> None: element = ElementBase() self.assertFalse(element.is_wildcard) def test_property_prefix(self) -> None: element = ElementBase() self.assertIsNone(element.prefix) element.ref = "foo" self.assertIsNone(element.prefix) element.ref = "foo:bar" self.assertEqual("foo", element.prefix) def test_property_raw_namespace(self) -> None: element = ElementBase() self.assertIsNone(element.raw_namespace) element.target_namespace = "tns" self.assertEqual("tns", element.raw_namespace) def test_property_real_name(self) -> None: element = ElementBase() with self.assertRaises(CodegenError): element.real_name element.ref = "bar:foo" self.assertEqual("foo", element.real_name) element.name = "foo:bar" self.assertEqual("bar", element.real_name) def test_property_attr(self) -> None: element = ElementBase() self.assertIsInstance(element.attr_types, Iterator) self.assertEqual([], list(element.attr_types)) def test_property_substitutions(self) -> None: element = ElementBase() self.assertEqual([], element.substitutions) def test_property_xs_prefix(self) -> None: element = ElementBase() self.assertIsNone(element.xs_prefix) element.ns_map = {"a": "a", "foo": Namespace.XS.uri} self.assertEqual("foo", element.xs_prefix) def test_children(self) -> None: one = SimpleType(id="1") two = ComplexType(id="10") three = Alternative(id="11") four = Alternative(id="12") element = Element( name="super", complex_type=two, simple_type=one, alternatives=[three, four], ) children = element.children() self.assertIsInstance(children, Generator) self.assertEqual([one, two, three, four], list(children)) children = list(element.children(lambda x: x.id == "10")) self.assertEqual([two], children) children = list(element.children(lambda x: x.id == "12")) self.assertEqual([four], children) children = list(element.children(lambda x: int(x.id) % 2 == 0)) self.assertEqual([two, four], children) tefra-xsdata-cf7afe8/tests/models/wsdl/000077500000000000000000000000001526437326600202765ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/models/wsdl/__init__.py000066400000000000000000000000001526437326600223750ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/models/wsdl/test_binding.py000066400000000000000000000012461526437326600233240ustar00rootroot00000000000000from unittest import TestCase from xsdata.models.wsdl import Binding, BindingOperation class BindingTests(TestCase): def test_unique_operations(self) -> None: binding = Binding( type="foo", operations=[ BindingOperation(name="bar"), BindingOperation(name="bar"), BindingOperation(name="bar"), BindingOperation(name="foo"), ], ) operations = list(binding.unique_operations()) self.assertEqual(2, len(operations)) self.assertEqual(binding.operations[2], operations[0]) self.assertEqual(binding.operations[3], operations[1]) tefra-xsdata-cf7afe8/tests/models/wsdl/test_definitions.py000066400000000000000000000052531526437326600242270ustar00rootroot00000000000000import copy from collections.abc import Generator from unittest import TestCase from xsdata.codegen.exceptions import CodegenError from xsdata.formats.dataclass.models.generics import AnyElement from xsdata.models.wsdl import Binding, Definitions, Message, PortType, Service, Types from xsdata.models.xsd import Schema class DefinitionsTests(TestCase): def test_property_schemas(self) -> None: obj = Definitions() self.assertIsInstance(obj.schemas, Generator) self.assertEqual([], list(obj.schemas)) obj.types = Types() self.assertEqual([], list(obj.schemas)) schemas = [Schema(), Schema] obj.types.schemas.extend(schemas) self.assertEqual(schemas, list(obj.schemas)) def test_find_binding(self) -> None: res = Binding(name="foo") obj = Definitions(bindings=[res]) self.assertEqual(res, obj.find_binding("foo")) with self.assertRaises(CodegenError): obj.find_binding("nope") def test_find_message(self) -> None: res = Message(name="foo") obj = Definitions(messages=[res]) self.assertEqual(res, obj.find_message("foo")) with self.assertRaises(CodegenError): obj.find_message("nope") def test_find_port_type(self) -> None: res = PortType(name="foo") obj = Definitions(port_types=[res]) self.assertEqual(res, obj.find_port_type("foo")) with self.assertRaises(CodegenError): obj.find_port_type("nope") def test_merge(self) -> None: target = Definitions() source = Definitions() source.types = Types() source.messages.append(Message()) source.port_types.append(PortType()) source.bindings.append(Binding()) source.services.append(Service()) source.extended.append(AnyElement()) source_two = copy.deepcopy(source) source_two.types.schemas.append(Schema()) target.merge(source) self.assertEqual(source.types, target.types) self.assertEqual(0, len(target.types.schemas)) self.assertEqual(1, len(target.messages)) self.assertEqual(1, len(target.port_types)) self.assertEqual(1, len(target.bindings)) self.assertEqual(1, len(target.services)) self.assertEqual(1, len(target.extended)) target.merge(source_two) target.merge(Definitions()) self.assertEqual(1, len(target.types.schemas)) self.assertEqual(2, len(target.messages)) self.assertEqual(2, len(target.port_types)) self.assertEqual(2, len(target.bindings)) self.assertEqual(2, len(target.services)) self.assertEqual(2, len(target.extended)) tefra-xsdata-cf7afe8/tests/models/wsdl/test_port_type.py000066400000000000000000000006761526437326600237450ustar00rootroot00000000000000from unittest import TestCase from xsdata.codegen.exceptions import CodegenError from xsdata.models.wsdl import PortType, PortTypeOperation class PortTypeTests(TestCase): def test_find_operating(self) -> None: res = PortTypeOperation(name="foo") obj = PortType(operations=[res]) self.assertEqual(res, obj.find_operation("foo")) with self.assertRaises(CodegenError): obj.find_operation("nope") tefra-xsdata-cf7afe8/tests/models/xsd/000077500000000000000000000000001526437326600201235ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/models/xsd/__init__.py000066400000000000000000000000001526437326600222220ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/models/xsd/test_all.py000066400000000000000000000011511526437326600223020ustar00rootroot00000000000000import sys from unittest import TestCase from xsdata.models.xsd import All class AllTests(TestCase): def test_normalize_max_occurs(self) -> None: obj = All(min_occurs=3, max_occurs=2) self.assertEqual(3, obj.max_occurs) self.assertEqual(3, obj.min_occurs) obj = All(min_occurs=3, max_occurs="unbounded") self.assertEqual(sys.maxsize, obj.max_occurs) self.assertEqual(3, obj.min_occurs) def test_get_restrictions(self) -> None: obj = All(min_occurs=1, max_occurs=2) self.assertEqual({"path": [("a", id(obj), 1, 2)]}, obj.get_restrictions()) tefra-xsdata-cf7afe8/tests/models/xsd/test_alternative.py000066400000000000000000000013521526437326600240530ustar00rootroot00000000000000from unittest import TestCase from xsdata.models.xsd import Alternative class AlternativeTests(TestCase): def test_property_real_name(self) -> None: obj = Alternative() self.assertEqual("value", obj.real_name) obj.id = "foo" self.assertEqual("foo", obj.real_name) obj.test = "@type='text'" self.assertEqual("type_text", obj.real_name) def test_property_bases(self) -> None: obj = Alternative() self.assertEqual([], list(obj.bases)) obj.type = "foo" self.assertEqual(["foo"], list(obj.bases)) def test_get_restrictions(self) -> None: obj = Alternative() self.assertEqual({"path": [("alt", id(obj), 0, 1)]}, obj.get_restrictions()) tefra-xsdata-cf7afe8/tests/models/xsd/test_annotation_base.py000066400000000000000000000025211526437326600247000ustar00rootroot00000000000000from unittest import TestCase from xsdata.formats.dataclass.models.generics import AnyElement from xsdata.models.xsd import Annotation, AnnotationBase, Documentation class AnnotationBaseTest(TestCase): def test_property_display_help(self) -> None: base = AnnotationBase() self.assertIsNone(base.display_help) base.annotations.append(Annotation(documentations=[Documentation()])) self.assertIsNone(base.display_help) base.annotations.append( Annotation( documentations=[ Documentation( content=[ " I am a ", AnyElement( qname="{http://www.w3.org/1999/xhtml}p", text="test", tail="\n", children=[ AnyElement( qname="{http://www.w3.org/1999/xhtml}span", text="!", ) ], ), ] ) ] ) ) self.assertEqual("I am a

test!\n

", base.display_help) tefra-xsdata-cf7afe8/tests/models/xsd/test_any.py000066400000000000000000000034061526437326600223260ustar00rootroot00000000000000import sys from unittest import TestCase from xsdata.models.enums import Namespace, NamespaceType from xsdata.models.xsd import Any class AnyTests(TestCase): def test_normalize_max_occurs(self) -> None: obj = Any(min_occurs=3, max_occurs=2) self.assertEqual(3, obj.max_occurs) self.assertEqual(3, obj.min_occurs) obj = Any(min_occurs=3, max_occurs="unbounded") self.assertEqual(sys.maxsize, obj.max_occurs) self.assertEqual(3, obj.min_occurs) def test_property_is_property(self) -> None: self.assertTrue(Any().is_property) def test_property_attr_types(self) -> None: obj = Any() obj.ns_map["xs"] = Namespace.XS.uri self.assertEqual(["xs:anyType"], list(obj.attr_types)) def test_property_raw_namespace(self) -> None: obj = Any() self.assertEqual(NamespaceType.ANY_NS, obj.raw_namespace) obj.namespace = "foo" self.assertEqual("foo", obj.raw_namespace) obj = Any(namespace=" foo \n \t \r bar foo ") self.assertEqual("foo bar", obj.raw_namespace) def test_property_real_name(self) -> None: obj = Any() self.assertEqual("@any_element", obj.real_name) obj.namespace = "foo" self.assertEqual("@foo_element", obj.real_name) obj.namespace = "http://www.xsdata.com/somewhere.xsd" self.assertEqual("@xsdata_com/somewhere_element", obj.real_name) obj.namespace = "http://foo http://bar" self.assertEqual("@foo_bar_element", obj.real_name) def test_get_restrictions(self) -> None: obj = Any(min_occurs=1, max_occurs=2) expected = {"max_occurs": 2, "min_occurs": 0, "process_contents": "strict"} self.assertEqual(expected, obj.get_restrictions()) tefra-xsdata-cf7afe8/tests/models/xsd/test_any_attribute.py000066400000000000000000000026141526437326600244110ustar00rootroot00000000000000from unittest import TestCase from xsdata.models.enums import Namespace, NamespaceType from xsdata.models.xsd import AnyAttribute class AnyAttributeTests(TestCase): def test_is_attribute(self) -> None: obj = AnyAttribute() self.assertTrue(obj.is_property) def test_property_raw_namespace(self) -> None: obj = AnyAttribute() self.assertEqual(NamespaceType.ANY_NS, obj.raw_namespace) obj.namespace = "foo" self.assertEqual("foo", obj.raw_namespace) obj = AnyAttribute(namespace=" foo \n \t \r bar foo ") self.assertEqual("foo bar", obj.raw_namespace) def test_property_real_name(self) -> None: obj = AnyAttribute() self.assertEqual("@any_attributes", obj.real_name) obj.namespace = "foo" self.assertEqual("@foo_attributes", obj.real_name) obj.namespace = "http://www.xsdata.com/somewhere.xsd" self.assertEqual("@xsdata_com/somewhere_attributes", obj.real_name) obj.namespace = "http://foo http://bar" self.assertEqual("@foo_bar_attributes", obj.real_name) def test_property_real_type(self) -> None: obj = AnyAttribute() obj.ns_map["xs"] = Namespace.XS.uri self.assertEqual(["xs:anyType"], list(obj.attr_types)) def get_restrictions(self) -> None: obj = AnyAttribute() self.assertEqual({}, obj.get_restrictions()) tefra-xsdata-cf7afe8/tests/models/xsd/test_attribute.py000066400000000000000000000047231526437326600235450ustar00rootroot00000000000000from unittest import TestCase from xsdata.codegen.exceptions import CodegenError from xsdata.models.enums import Namespace, UseType from xsdata.models.xsd import Attribute, Length, Restriction, SimpleType class AttributeTests(TestCase): def test_property_is_property(self) -> None: obj = Attribute() self.assertTrue(obj) def test_property_attr_types(self) -> None: obj = Attribute() self.assertEqual([], list(obj.attr_types)) obj.ref = "foo" self.assertEqual([obj.ref], list(obj.attr_types)) obj.type = "bar" self.assertEqual([obj.type], list(obj.attr_types)) obj.simple_type = SimpleType() self.assertEqual([], list(obj.attr_types)) obj.simple_type.restriction = Restriction(base="thug") self.assertEqual([obj.simple_type.restriction.base], list(obj.attr_types)) def test_property_real_name(self) -> None: obj = Attribute(ref="bar") self.assertEqual("bar", obj.real_name) obj.name = "foo" self.assertEqual("foo", obj.real_name) with self.assertRaises(CodegenError): Attribute().real_name def test_get_restrictions(self) -> None: obj = Attribute() self.assertEqual({"max_occurs": 1, "min_occurs": 0}, obj.get_restrictions()) obj.use = UseType.REQUIRED expected = {"max_occurs": 1, "min_occurs": 1} self.assertEqual(expected, obj.get_restrictions()) obj.use = UseType.PROHIBITED expected = {"max_occurs": 0, "min_occurs": 0} self.assertEqual(expected, obj.get_restrictions()) obj.simple_type = SimpleType(restriction=Restriction(length=Length(value=1))) expected["length"] = 1 self.assertEqual(expected, obj.get_restrictions()) def test_property_bases(self) -> None: obj = Attribute() obj.ns_map["xs"] = Namespace.XS.uri self.assertEqual(["xs:string"], list(obj.bases)) obj.simple_type = SimpleType() self.assertEqual([], list(obj.bases)) obj.type = "foo" obj.simple_type = None self.assertEqual(["foo"], list(obj.bases)) def test_property_default_type(self) -> None: obj = Attribute() self.assertEqual("anySimpleType", obj.default_type) obj = Attribute() obj.ns_map["foo"] = Namespace.XS.uri self.assertEqual("foo:anySimpleType", obj.default_type) obj.fixed = "aa" self.assertEqual("foo:string", obj.default_type) tefra-xsdata-cf7afe8/tests/models/xsd/test_attribute_group.py000066400000000000000000000007001526437326600247500ustar00rootroot00000000000000from unittest import TestCase from xsdata.models.xsd import AttributeGroup class AttributeGroupTests(TestCase): def test_property_is_property(self) -> None: obj = AttributeGroup() self.assertTrue(obj.is_property) def test_property_attr_types(self) -> None: obj = AttributeGroup() self.assertEqual([], list(obj.attr_types)) obj.ref = "foo" self.assertEqual(["foo"], list(obj.attr_types)) tefra-xsdata-cf7afe8/tests/models/xsd/test_choice.py000066400000000000000000000011701526437326600227650ustar00rootroot00000000000000import sys from unittest import TestCase from xsdata.models.xsd import Choice class ChoiceTests(TestCase): def test_normalize_max_occurs(self) -> None: obj = Choice(min_occurs=3, max_occurs=2) self.assertEqual(3, obj.max_occurs) self.assertEqual(3, obj.min_occurs) obj = Choice(min_occurs=3, max_occurs="unbounded") self.assertEqual(sys.maxsize, obj.max_occurs) self.assertEqual(3, obj.min_occurs) def test_get_restrictions(self) -> None: obj = Choice(min_occurs=1, max_occurs=2) self.assertEqual({"path": [("c", id(obj), 1, 2)]}, obj.get_restrictions()) tefra-xsdata-cf7afe8/tests/models/xsd/test_complex_content.py000066400000000000000000000004321526437326600247340ustar00rootroot00000000000000from unittest import TestCase from xsdata.models.xsd import ComplexContent, SimpleContent class ComplexContentTests(TestCase): def test_class(self) -> None: obj = ComplexContent() self.assertIsInstance(obj, SimpleContent) self.assertFalse(obj.mixed) tefra-xsdata-cf7afe8/tests/models/xsd/test_complex_type.py000066400000000000000000000010001526437326600242330ustar00rootroot00000000000000from unittest import TestCase from xsdata.models.xsd import ComplexContent, ComplexType class ComplexTypeTests(TestCase): def test_property_is_mixed(self) -> None: obj = ComplexType() self.assertFalse(obj.is_mixed) obj.complex_content = ComplexContent() self.assertFalse(obj.is_mixed) obj.complex_content.mixed = True self.assertTrue(obj.is_mixed) obj.complex_content.mixed = False obj.mixed = True self.assertTrue(obj.is_mixed) tefra-xsdata-cf7afe8/tests/models/xsd/test_element.py000066400000000000000000000072161526437326600231730ustar00rootroot00000000000000import sys from unittest import TestCase from xsdata.codegen.exceptions import CodegenError from xsdata.models.enums import Namespace from xsdata.models.xsd import ( Alternative, ComplexType, Element, Length, Restriction, SimpleType, ) class ElementTests(TestCase): def test_normalize_max_occurs(self) -> None: obj = Element(min_occurs=3, max_occurs=2) self.assertEqual(3, obj.max_occurs) self.assertEqual(3, obj.min_occurs) obj = Element(min_occurs=3, max_occurs="unbounded") self.assertEqual(sys.maxsize, obj.max_occurs) self.assertEqual(3, obj.min_occurs) def test_property_is_property(self) -> None: obj = Element() self.assertTrue(obj) def test_property_default_type(self) -> None: obj = Element() self.assertEqual("anyType", obj.default_type) obj = Element() obj.ns_map["foo"] = Namespace.XS.uri self.assertEqual("foo:anyType", obj.default_type) obj.fixed = "aa" self.assertEqual("foo:string", obj.default_type) def test_property_bases(self) -> None: obj = Element() obj.ns_map["xs"] = Namespace.XS.uri self.assertEqual(["xs:anyType"], list(obj.bases)) obj.type = "foo" self.assertEqual(["foo"], list(obj.bases)) obj.type = None obj.complex_type = ComplexType() self.assertEqual([], list(obj.bases)) def test_property_real_name(self) -> None: obj = Element(ref="bar") self.assertEqual("bar", obj.real_name) obj.name = "foo" self.assertEqual("foo", obj.real_name) with self.assertRaises(CodegenError): obj = Element() obj.real_name def test_property_attr_types(self) -> None: obj = Element() self.assertEqual([], list(obj.attr_types)) # Inner classes depend on the this to be None obj.complex_type = ComplexType() self.assertEqual([], list(obj.attr_types)) restriction = Restriction(base="xs:int") obj.simple_type = SimpleType(restriction=restriction) self.assertEqual([restriction.base], list(obj.attr_types)) obj.ref = "foo" self.assertEqual([obj.ref], list(obj.attr_types)) obj.type = "bar" self.assertEqual([obj.type], list(obj.attr_types)) obj.alternatives.append(Alternative(type="foo")) obj.alternatives.append(Alternative(type="bar")) obj.alternatives.append(Alternative(type="thug")) self.assertEqual(["bar", "foo", "bar", "thug"], list(obj.attr_types)) def test_property_is_mixed(self) -> None: obj = Element() self.assertFalse(obj.is_mixed) obj.complex_type = ComplexType() self.assertFalse(obj.is_mixed) obj.complex_type.mixed = True self.assertTrue(obj.is_mixed) def test_property_substitutions(self) -> None: obj = Element() self.assertEqual([], obj.substitutions) obj.substitution_group = "foo bar xs:any" self.assertEqual(["foo", "bar", "xs:any"], obj.substitutions) def test_get_restrictions(self) -> None: obj = Element(min_occurs=1, max_occurs=1) expected = {"min_occurs": 1, "max_occurs": 1} self.assertEqual(expected, obj.get_restrictions()) obj.simple_type = SimpleType(restriction=Restriction(length=Length(value=9))) expected["length"] = 9 self.assertEqual(expected, obj.get_restrictions()) obj.nillable = False self.assertEqual(expected, obj.get_restrictions()) obj.nillable = True expected["nillable"] = True self.assertEqual(expected, obj.get_restrictions()) tefra-xsdata-cf7afe8/tests/models/xsd/test_enumeration.py000066400000000000000000000013331526437326600240620ustar00rootroot00000000000000from unittest import TestCase from xsdata.models.xsd import Enumeration class EnumerationTests(TestCase): def test_property_is_property(self) -> None: obj = Enumeration() self.assertTrue(obj.is_property) def test_property_is_fixed(self) -> None: obj = Enumeration() self.assertTrue(obj.is_fixed) def test_property_real_name(self) -> None: obj = Enumeration(value="foo") self.assertEqual("foo", obj.real_name) def test_property_default(self) -> None: obj = Enumeration(value="foo") self.assertEqual("foo", obj.default) def test_get_restrictions(self) -> None: obj = Enumeration() self.assertEqual({}, obj.get_restrictions()) tefra-xsdata-cf7afe8/tests/models/xsd/test_extension.py000066400000000000000000000004541526437326600235530ustar00rootroot00000000000000from unittest import TestCase from xsdata.models.xsd import Extension class ExtensionTests(TestCase): def test_property_bases(self) -> None: obj = Extension() self.assertEqual([], list(obj.bases)) obj.base = "a b c" self.assertEqual(["a b c"], list(obj.bases)) tefra-xsdata-cf7afe8/tests/models/xsd/test_group.py000066400000000000000000000016571526437326600227010ustar00rootroot00000000000000import sys from unittest import TestCase from xsdata.models.xsd import Group class GroupTests(TestCase): def test_normalize_max_occurs(self) -> None: obj = Group(min_occurs=3, max_occurs=2) self.assertEqual(3, obj.max_occurs) self.assertEqual(3, obj.min_occurs) obj = Group(min_occurs=3, max_occurs="unbounded") self.assertEqual(sys.maxsize, obj.max_occurs) self.assertEqual(3, obj.min_occurs) def test_property_is_property(self) -> None: obj = Group() self.assertTrue(obj.is_property) def test_property_attr_types(self) -> None: obj = Group() self.assertEqual([], list(obj.attr_types)) obj.ref = "foo" self.assertEqual(["foo"], list(obj.attr_types)) def test_get_restrictions(self) -> None: obj = Group(min_occurs=1, max_occurs=2) self.assertEqual({"path": [("g", id(obj), 1, 2)]}, obj.get_restrictions()) tefra-xsdata-cf7afe8/tests/models/xsd/test_list.py000066400000000000000000000011771526437326600225150ustar00rootroot00000000000000from unittest import TestCase from xsdata.models.xsd import List class ListTests(TestCase): def test_is_attribute(self) -> None: obj = List() self.assertTrue(obj.is_property) def test_real_name(self) -> None: obj = List() self.assertEqual("value", obj.real_name) def test_real_type(self) -> None: obj = List() self.assertEqual([], list(obj.attr_types)) obj.item_type = "foo" self.assertEqual(["foo"], list(obj.attr_types)) def test_get_restrictions(self) -> None: obj = List() self.assertEqual({"tokens": True}, obj.get_restrictions()) tefra-xsdata-cf7afe8/tests/models/xsd/test_restriction.py000066400000000000000000000054441526437326600241100ustar00rootroot00000000000000from collections.abc import Iterator from unittest import TestCase from xsdata.models.xsd import ( Enumeration, FractionDigits, Length, MaxExclusive, MaxInclusive, MaxLength, MinExclusive, MinInclusive, MinLength, Pattern, Restriction, SimpleType, TotalDigits, WhiteSpace, ) class RestrictionTests(TestCase): def test_property_attr_types(self) -> None: obj = Restriction() self.assertEqual([], list(obj.attr_types)) obj = Restriction(base="foo") self.assertEqual([obj.base], list(obj.attr_types)) obj.enumerations.append(Enumeration()) self.assertEqual([], list(obj.attr_types)) obj = Restriction(simple_type=SimpleType(restriction=Restriction(base="bar"))) self.assertEqual(["bar"], list(obj.attr_types)) def test_property_real_name(self) -> None: obj = Restriction() self.assertEqual("value", obj.real_name) def test_property_bases(self) -> None: obj = Restriction() self.assertEqual([], list(obj.bases)) obj.base = "foo" self.assertIsInstance(obj.bases, Iterator) self.assertEqual(["foo"], list(obj.bases)) def test_get_restrictions(self) -> None: self.assertEqual({}, Restriction().get_restrictions()) obj = Restriction( min_exclusive=MinExclusive(value=1), min_inclusive=MinInclusive(value=2), min_length=MinLength(value=3), max_exclusive=MaxExclusive(value=4), max_inclusive=MaxInclusive(value=5), max_length=MaxLength(value=6), total_digits=TotalDigits(value=7), fraction_digits=FractionDigits(value=8), length=Length(value=9), white_space=WhiteSpace(value="collapse"), patterns=[Pattern(value="[0-9]"), Pattern(value="[A-Z]")], enumerations=[Enumeration(value="str")], ) expected = { "fraction_digits": 8, "length": 9, "max_exclusive": 4, "max_inclusive": 5, "max_length": 6, "min_exclusive": 1, "min_inclusive": 2, "min_length": 3, "pattern": "[0-9]|[A-Z]", "total_digits": 7, "white_space": "collapse", } self.assertEqual(expected, obj.get_restrictions()) def test_get_restrictions_with_nested_simple_type(self) -> None: obj = Restriction( min_length=MinLength(value=2), simple_type=SimpleType( restriction=Restriction( max_length=MaxLength(value=10), min_length=MinLength(value=5) ) ), ) expected = {"max_length": 10, "min_length": 2} self.assertEqual(expected, obj.get_restrictions()) tefra-xsdata-cf7afe8/tests/models/xsd/test_schema.py000066400000000000000000000025071526437326600230000ustar00rootroot00000000000000from collections.abc import Iterator from unittest import TestCase from xsdata.models.enums import Namespace from xsdata.models.xsd import Import, Include, Override, Redefine, Schema class SchemaTests(TestCase): def test_meta(self) -> None: schema = Schema() self.assertEqual("schema", schema.Meta.name) self.assertEqual(Namespace.XS.uri, schema.Meta.namespace) def test_sub_schemas(self) -> None: imports = [ Import(schema_location="../foo.xsd"), Import(schema_location="../bar.xsd"), ] includes = [ Include(schema_location="common.xsd"), Include(schema_location="uncommon.xsd"), ] redefines = [ Redefine(schema_location="a.xsd"), Redefine(schema_location="b.xsd"), ] overrides = [ Override(schema_location="a.xsd"), Override(schema_location="b.xsd"), ] schema = Schema( imports=imports, includes=includes, redefines=redefines, overrides=overrides ) actual = schema.included() expected = imports + includes + redefines + overrides self.assertIsInstance(actual, Iterator) self.assertEqual(expected, list(actual)) schema = Schema() self.assertEqual([], list(schema.included())) tefra-xsdata-cf7afe8/tests/models/xsd/test_sequence.py000066400000000000000000000012021526437326600233370ustar00rootroot00000000000000import sys from unittest import TestCase from xsdata.models.xsd import Sequence class SequenceTests(TestCase): def test_normalize_max_occurs(self) -> None: obj = Sequence(min_occurs=3, max_occurs=2) self.assertEqual(3, obj.max_occurs) self.assertEqual(3, obj.min_occurs) obj = Sequence(min_occurs=3, max_occurs="unbounded") self.assertEqual(sys.maxsize, obj.max_occurs) self.assertEqual(3, obj.min_occurs) def test_get_restrictions(self) -> None: obj = Sequence(min_occurs=1, max_occurs=2) self.assertEqual({"path": [("s", id(obj), 1, 2)]}, obj.get_restrictions()) tefra-xsdata-cf7afe8/tests/models/xsd/test_simple_type.py000066400000000000000000000033361526437326600240730ustar00rootroot00000000000000from unittest import TestCase from xsdata.models.xsd import Enumeration, Length, List, Restriction, SimpleType, Union class SimpleTypeTests(TestCase): def test_property_real_name(self) -> None: obj = SimpleType() self.assertEqual("value", obj.real_name) obj.name = "foo" self.assertEqual("foo", obj.real_name) def test_property_attr_types(self) -> None: obj = SimpleType() self.assertEqual([], list(obj.attr_types)) obj.union = Union(member_types="thug") self.assertEqual(["thug"], list(obj.attr_types)) obj.list = List(item_type="foo") self.assertEqual(["foo"], list(obj.attr_types)) obj.restriction = Restriction(base="bar") self.assertEqual(["bar"], list(obj.attr_types)) obj = SimpleType(restriction=Restriction()) obj.restriction.enumerations.append(Enumeration()) self.assertEqual([], list(obj.attr_types)) def test_property_is_property(self) -> None: obj = SimpleType() self.assertTrue(obj.is_property) def test_property_is_enumeration(self) -> None: obj = SimpleType() self.assertFalse(obj.is_enumeration) obj.restriction = Restriction() self.assertFalse(obj.is_enumeration) obj.restriction.enumerations.append(Enumeration()) self.assertTrue(obj.is_enumeration) def test_get_restrictions(self) -> None: obj = SimpleType() self.assertEqual({}, obj.get_restrictions()) obj.list = List() self.assertEqual({"tokens": True}, obj.get_restrictions()) expected = {"length": 2} obj.restriction = Restriction(length=Length(value=2)) self.assertEqual(expected, obj.get_restrictions()) tefra-xsdata-cf7afe8/tests/models/xsd/test_union.py000066400000000000000000000035141526437326600226670ustar00rootroot00000000000000from collections.abc import Iterator from unittest import TestCase from xsdata.models.xsd import ( MaxExclusive, MinExclusive, MinInclusive, MinLength, Restriction, SimpleType, Union, ) class UnionTests(TestCase): def test_property_bases(self) -> None: obj = Union() self.assertIsInstance(obj.bases, Iterator) self.assertEqual([], list(obj.bases)) obj.member_types = "foo bar " self.assertEqual(["foo", "bar"], list(obj.bases)) def test_property_is_property(self) -> None: obj = Union() self.assertTrue(obj.is_property) def test_property_real_name(self) -> None: obj = Union() self.assertEqual("value", obj.real_name) def test_property_attr_types(self) -> None: obj = Union() obj.member_types = "thug life" self.assertEqual(["thug", "life"], list(obj.attr_types)) obj = Union( simple_types=[ SimpleType(restriction=Restriction(base="foo")), SimpleType(restriction=Restriction(base="bar")), ] ) self.assertEqual(["foo", "bar"], list(obj.attr_types)) def test_get_restrictions(self) -> None: first = Restriction( min_exclusive=MinExclusive(value="1"), min_inclusive=MinInclusive(value="2") ) second = Restriction( min_length=MinLength(value="3"), max_exclusive=MaxExclusive(value="4") ) obj = Union( simple_types=[ SimpleType(restriction=first), SimpleType(restriction=second), ] ) expected = { "max_exclusive": "4", "min_exclusive": "1", "min_inclusive": "2", "min_length": "3", } self.assertEqual(expected, obj.get_restrictions()) tefra-xsdata-cf7afe8/tests/test_cli.py000066400000000000000000000201661526437326600202270ustar00rootroot00000000000000import logging import platform import sys import tempfile from pathlib import Path from unittest import TestCase, mock from click.testing import CliRunner from tests import fixtures_dir from xsdata import __version__ from xsdata.cli import cli, resolve_source from xsdata.codegen.exceptions import CodegenError from xsdata.codegen.transformer import ResourceTransformer from xsdata.codegen.writer import CodeWriter from xsdata.formats.dataclass.generator import DataclassGenerator from xsdata.logger import logger from xsdata.models.config import GeneratorConfig, StructureStyle from xsdata.utils.downloader import Downloader CodeWriter.register_generator("testing", DataclassGenerator) class CliTests(TestCase): def setUp(self) -> None: self.runner = CliRunner() super().setUp() @classmethod def setUpClass(cls): CodeWriter.register_generator("testing", DataclassGenerator) @classmethod def tearDownClass(cls): CodeWriter.unregister_generator("testing") @mock.patch.object(ResourceTransformer, "process") @mock.patch.object(ResourceTransformer, "__init__", return_value=None) def test_generate(self, mock_init, mock_process) -> None: source = fixtures_dir.joinpath("defxmlschema/chapter03.xsd") result = self.runner.invoke(cli, ["generate", str(source), "--package", "foo"]) config = mock_init.call_args[1]["config"] self.assertIsNone(result.exception) self.assertEqual("foo", config.output.package) self.assertEqual("dataclasses", config.output.format.value) self.assertFalse(config.output.relative_imports) self.assertEqual(StructureStyle.FILENAMES, config.output.structure_style) self.assertEqual([source.as_uri()], mock_process.call_args[0][0]) @mock.patch.object(ResourceTransformer, "process") def test_generate_with_error(self, mock_process) -> None: mock_process.side_effect = CodegenError("Testing", foo="bar") source = fixtures_dir.joinpath("defxmlschema/chapter03.xsd") result = self.runner.invoke(cli, ["generate", str(source), "--package", "foo"]) expected = "=========\nError: Testing\nfoo: bar\n" self.assertIn(expected, result.output) @mock.patch.object(ResourceTransformer, "process") @mock.patch.object(ResourceTransformer, "__init__", return_value=None) def test_generate_with_configuration_file(self, mock_init, mock_process) -> None: file_path = Path(tempfile.mktemp()) config = GeneratorConfig() config.output.package = "foo.bar" config.output.structure_style = StructureStyle.NAMESPACES with file_path.open("w") as fp: config.write(fp, config) source = fixtures_dir.joinpath("defxmlschema/chapter03.xsd") result = self.runner.invoke( cli, ["generate", str(source), "--config", str(file_path), "--no-eq"], catch_exceptions=False, ) config = mock_init.call_args[1]["config"] self.assertIsNone(result.exception) self.assertEqual("foo.bar", config.output.package) self.assertEqual("dataclasses", config.output.format.value) self.assertFalse(config.output.format.eq) self.assertEqual(StructureStyle.NAMESPACES, config.output.structure_style) self.assertEqual([source.as_uri()], mock_process.call_args[0][0]) file_path.unlink() @mock.patch.object(ResourceTransformer, "process") @mock.patch.object(ResourceTransformer, "__init__", return_value=None) def test_generate_with_debug_mode(self, *args) -> None: self.runner.invoke(cli, ["generate", "foo.xsd", "--package", "foo", "--debug"]) self.assertEqual(logging.DEBUG, logger.level) @mock.patch("xsdata.cli.logger.info") def test_init_config(self, mock_info) -> None: output = tempfile.mktemp() output_path = Path(output) result = self.runner.invoke(cli, ["init-config", str(output_path)]) self.assertIsNone(result.exception) self.assertEqual(GeneratorConfig.create(), GeneratorConfig.read(output_path)) mock_info.assert_has_calls( [ mock.call( "========= xsdata v%s / Python %s / Platform %s =========\n", __version__, platform.python_version(), sys.platform, ), mock.call("Initializing configuration file %s", str(output_path)), ] ) output_path.unlink() @mock.patch("xsdata.cli.logger.info") def test_init_config_when_file_exists(self, mock_info) -> None: output = tempfile.mktemp() output_path = Path(output).resolve() config = GeneratorConfig.create() config.version = "20.8" with output_path.open("w") as fp: config.write(fp, config) result = self.runner.invoke(cli, ["init-config", str(output_path)]) self.assertIsNone(result.exception) self.assertNotEqual("20.8", GeneratorConfig.read(output_path)) mock_info.assert_has_calls( [ mock.call( "========= xsdata v%s / Python %s / Platform %s =========\n", __version__, platform.python_version(), sys.platform, ), mock.call("Updating configuration file %s", str(output_path)), ] ) output_path.unlink() @mock.patch.object(Downloader, "wget") @mock.patch.object(Downloader, "__init__", return_value=None) def test_download(self, mock_init, mock_wget) -> None: uri = "http://www.w3.org/2009/01/xml.xsd" result = self.runner.invoke(cli, ["download", uri]) self.assertIsNone(result.exception) mock_init.assert_called_once_with(output=Path.cwd()) mock_wget.assert_called_once_with(uri) @mock.patch.object(Downloader, "wget") @mock.patch.object(Downloader, "__init__", return_value=None) def test_download_with_custom_output(self, mock_init, mock_wget) -> None: uri = "http://www.w3.org/2009/01/xml.xsd" result = self.runner.invoke(cli, ["download", uri, "--output", "here/schemas"]) self.assertIsNone(result.exception) mock_init.assert_called_once_with(output=Path("here/schemas").resolve()) mock_wget.assert_called_once_with(uri) def test_download_rejects_file_uri(self) -> None: result = self.runner.invoke(cli, ["download", "file:///path/to/schema.xsd"]) self.assertIsNotNone(result.exception) self.assertEqual(result.exit_code, 2) self.assertIn("Source must be an HTTP URL", result.output) @mock.patch.object(Downloader, "wget") @mock.patch.object(Downloader, "__init__", return_value=None) def test_download_accepts_http(self, mock_init, mock_wget) -> None: """Test that download command accepts HTTPS URLs.""" uri = "https://example.com/schema.xsd" result = self.runner.invoke(cli, ["download", uri]) self.assertIsNone(result.exception) mock_wget.assert_called_once_with(uri) @mock.patch.object(Downloader, "wget") @mock.patch.object(Downloader, "__init__", return_value=None) def test_download_accepts_https(self, mock_init, mock_wget) -> None: """Test that download command accepts HTTPS URLs.""" uri = "http://example.com/schema.xsd" result = self.runner.invoke(cli, ["download", uri]) self.assertIsNone(result.exception) mock_wget.assert_called_once_with(uri) def test_resolve_source(self) -> None: hello_path = fixtures_dir.joinpath("hello") file = hello_path.joinpath("hello.xsd") url = "http://www.xsdata/schema.xsd" self.assertEqual([file.as_uri()], list(resolve_source(str(file), False))) self.assertEqual([url], list(resolve_source(url, False))) self.assertEqual(5, len(list(resolve_source(str(hello_path), False)))) def_xml_path = fixtures_dir.joinpath("calculator") self.assertEqual(3, len(list(resolve_source(str(def_xml_path), False)))) actual = list(resolve_source(str(fixtures_dir), True)) self.assertEqual(43, len(actual)) tefra-xsdata-cf7afe8/tests/utils/000077500000000000000000000000001526437326600172025ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/utils/__init__.py000066400000000000000000000000001526437326600213010ustar00rootroot00000000000000tefra-xsdata-cf7afe8/tests/utils/test_collections.py000066400000000000000000000040151526437326600231310ustar00rootroot00000000000000from collections import namedtuple from collections.abc import Iterator from unittest import TestCase from xsdata.utils import collections class CollectionsTests(TestCase): def test_find(self) -> None: self.assertEqual(-1, collections.find([0, 1], 2)) self.assertEqual(1, collections.find([0, 1], 1)) def test_prepend(self) -> None: target = [1, 2, 3] prepend_values = [4, 5, 6] collections.prepend(target, *prepend_values) self.assertEqual([4, 5, 6, 1, 2, 3], target) def test_remove(self) -> None: self.assertEqual([1, 3], collections.remove([1, 2, 2, 3], lambda x: x == 2)) self.assertEqual([2, 2, 3], collections.remove([1, 2, 2, 3], lambda x: x == 1)) self.assertEqual([3], collections.remove([1, 2, 2, 3], lambda x: x < 3)) def test_is_array(self) -> None: fixture = namedtuple("fixture", ["a", "b"]) def foo() -> Iterator[int]: yield 1 self.assertFalse(collections.is_array(1)) self.assertFalse(collections.is_array(fixture(1, 2))) self.assertTrue(collections.is_array([])) self.assertTrue(collections.is_array(())) self.assertTrue(collections.is_array(frozenset())) self.assertTrue(collections.is_array(foo())) def test_connected_components(self) -> None: lists = [[1, 2, 3], [4], [3, 4], [6]] actual = collections.connected_components(lists) self.assertIsInstance(actual, Iterator) self.assertEqual([[1, 2, 3, 4], [6]], list(actual)) def test_find_connected_component(self) -> None: groups = [[1, 2, 3], [4, 5, 6]] actual = collections.find_connected_component(groups, 1) self.assertEqual(0, actual) actual = collections.find_connected_component(groups, 2) self.assertEqual(0, actual) actual = collections.find_connected_component(groups, 4) self.assertEqual(1, actual) actual = collections.find_connected_component(groups, 100) self.assertEqual(-1, actual) tefra-xsdata-cf7afe8/tests/utils/test_dates.py000066400000000000000000000110271526437326600217140ustar00rootroot00000000000000from unittest import TestCase from xsdata.utils.dates import parse_date_args, validate_date, validate_time class DatesUtilsTests(TestCase): def test_parse_date_args(self) -> None: args = parse_date_args("2002-01-02T12:14:30-01:22", "%Y-%m-%dT%H:%M:%S%z") self.assertEqual(2002, next(args)) self.assertEqual(1, next(args)) self.assertEqual(2, next(args)) self.assertEqual(12, next(args)) self.assertEqual(14, next(args)) self.assertEqual(30, next(args)) self.assertEqual(0, next(args)) self.assertEqual(-82, next(args)) self.assertIsNone(next(args, None)) def test_parse_date_args_microsecond(self) -> None: args = parse_date_args("55.1345", "%S") self.assertEqual(55, next(args)) self.assertEqual(134500000, next(args)) self.assertIsNone(next(args, None)) def test_parse_date_args_nanosecond(self) -> None: args = parse_date_args("55.1345678", "%S") self.assertEqual(55, next(args)) self.assertEqual(134567800, next(args)) self.assertIsNone(next(args, None)) def test_parse_date_args_year(self) -> None: args = parse_date_args("-1970-05", "%Y-%m") self.assertEqual(-1970, next(args)) self.assertEqual(5, next(args)) self.assertIsNone(next(args, None)) args = parse_date_args("0009", "%Y") self.assertEqual(9, next(args)) args = parse_date_args("0099", "%Y") self.assertEqual(99, next(args)) args = parse_date_args("0999", "%Y") self.assertEqual(999, next(args)) args = parse_date_args("9999", "%Y") self.assertEqual(9999, next(args)) with self.assertRaises(ValueError): next(parse_date_args("09999", "%Y")) with self.assertRaises(ValueError): next(parse_date_args("00999", "%Y")) with self.assertRaises(ValueError): next(parse_date_args("00099", "%Y")) with self.assertRaises(ValueError): next(parse_date_args("00009", "%Y")) def test_parse_date_args_utc_timezone(self) -> None: args = parse_date_args("Z", "%z") self.assertEqual(0, next(args)) self.assertIsNone(next(args, None)) def test_parse_date_args_optional_timezone(self) -> None: args = parse_date_args("2020", "%Y%z") self.assertEqual(2020, next(args)) self.assertIsNone(None, next(args)) self.assertIsNone(next(args, None)) def test_parse_date_args_trim_input(self) -> None: args = parse_date_args("\n Z ", "%z") self.assertEqual(0, next(args)) self.assertIsNone(next(args, None)) def test_parse_date_args_raises_value_error(self) -> None: cases = { "2002-12-01": "%F", # Unknown var "2002-01-01": "%Y", # Mismatch format "2002-1-01": "%Y", # Minimum required digits "2002": "%Y-%M", # Run out of value "$": "%z", # Unknown timezone } for value, fmt in cases.items(): with self.assertRaises(ValueError, msg=f"{value} ({fmt})") as cm: list(parse_date_args(value, fmt)) self.assertEqual( f"String '{value}' does not match format '{fmt}'", str(cm.exception) ) def test_validate_date(self) -> None: invalid = { (0, 0, 0): "Month must be in 1..12", (0, 2, 0): "Day must be in 1..29", (2003, 2, 29): "Day must be in 1..28", } for args, msg in invalid.items(): with self.assertRaises(ValueError) as cm: validate_date(*args) self.assertEqual(msg, str(cm.exception)) def test_validate_time(self) -> None: invalid = { (-1, 0, 0, 0): "Hour must be in 0..24", (25, 0, 0, 0): "Hour must be in 0..24", (24, 1, 0, 0): "Day time exceeded", (24, 0, 1, 0): "Day time exceeded", (24, 0, 0, 1): "Day time exceeded", (23, -1, 0, 1): "Minute must be in 0..59", (23, 66, 0, 1): "Minute must be in 0..59", (23, 59, -1, 1): "Second must be in 0..59", (23, 59, 60, 1): "Second must be in 0..59", (23, 59, 59, -1): "Fractional second must be in 0..999999999", (23, 59, 59, 1000000000): "Fractional second must be in 0..999999999", } for args, msg in invalid.items(): with self.assertRaises(ValueError) as cm: validate_time(*args) self.assertEqual(msg, str(cm.exception)) tefra-xsdata-cf7afe8/tests/utils/test_downloader.py000066400000000000000000000125101526437326600227500ustar00rootroot00000000000000import tempfile from pathlib import Path from unittest import TestCase, mock from urllib.request import urlopen from tests import fixtures_dir from xsdata.models.xsd import Import, Schema from xsdata.utils.downloader import Downloader class DownloaderTests(TestCase): def setUp(self) -> None: super().setUp() self.downloader = Downloader(Path.cwd()) @mock.patch.object(Downloader, "write_file") def test_wget_with_schema(self, mock_write_file) -> None: path = fixtures_dir.joinpath("books/schema.xsd") content = path.read_bytes() uri = path.as_uri() self.downloader.wget(uri) self.downloader.wget(uri) # once mock_write_file.assert_called_once_with(uri, None, content.decode()) @mock.patch.object(Downloader, "write_file") def test_wget_with_definitions(self, mock_write_file) -> None: wsdl = fixtures_dir.joinpath("hello/hello.wsdl").as_uri() xsd = fixtures_dir.joinpath("hello/hello.xsd").as_uri() self.downloader.wget(wsdl) mock_write_file.assert_has_calls( [ mock.call(xsd, "hello.xsd", urlopen(xsd).read().decode()), mock.call(wsdl, None, urlopen(wsdl).read().decode()), ] ) @mock.patch.object(Downloader, "wget") def test_wget_included(self, mock_wget) -> None: schema = Schema( imports=[ Import(location="foo.xsd"), Import(), Import(location="foo.xsd", schema_location="../foo.xsd"), ] ) self.downloader.wget_included(schema) mock_wget.assert_has_calls( [ mock.call(schema.imports[0].location, None), mock.call( schema.imports[2].location, schema.imports[2].schema_location ), ] ) def test_write_file_uses_full_url_path(self) -> None: """Test that HTTP URLs preserve full domain/path structure.""" uri = "http://www.w3.org/2001/xml.xsd" content = "foo" with tempfile.TemporaryDirectory() as dir: output_path = Path(dir) # New behavior: always uses full URL path structure file_path = output_path.joinpath("www.w3.org/2001/xml.xsd") self.downloader.output = output_path self.downloader.write_file(uri, None, content) self.assertTrue(file_path.exists()) self.assertEqual(content, file_path.read_text()) self.assertEqual(file_path, self.downloader.downloaded[uri]) def test_adjust_imports(self) -> None: content = ( '\n' '\n' '\n' '\n' ) with tempfile.TemporaryDirectory() as dir: dir_path = Path(dir) self.downloader.output = dir_path self.downloader.downloaded.update( { "http://foo.com/bar.xsd": dir_path.joinpath("some/place/bar.xsd"), "http://foo.com/bar/bar.xsd": dir_path.joinpath("other/bar.xsd"), } ) result = self.downloader.adjust_imports(dir_path, content) expected = ( '\n' '\n' '\n' '\n' ) self.assertEqual(expected, result) result = self.downloader.adjust_imports(dir_path.joinpath("foo"), content) expected = ( '\n' '\n' '\n' '\n' ) self.assertEqual(expected, result) def test_write_file_with_bundled_schema(self) -> None: file_uri = fixtures_dir.joinpath("books/schema.xsd").as_uri() http_location = "http://www.example.com/schemas/v1/schema.xsd" content = "foo" with tempfile.TemporaryDirectory() as dir: output_path = Path(dir) file_path = output_path.joinpath("www.example.com/schemas/v1/schema.xsd") self.downloader.output = output_path self.downloader.write_file(file_uri, http_location, content) self.assertTrue(file_path.exists()) self.assertEqual(content, file_path.read_text()) self.assertEqual(file_path, self.downloader.downloaded[file_uri]) def test_write_file_raises_for_local_without_location(self) -> None: file_uri = fixtures_dir.joinpath("books/schema.xsd").as_uri() content = "foo" with tempfile.TemporaryDirectory() as dir: output_path = Path(dir) self.downloader.output = output_path with self.assertRaises(ValueError) as cm: self.downloader.write_file(file_uri, None, content) self.assertIn("Cannot download local file", str(cm.exception)) tefra-xsdata-cf7afe8/tests/utils/test_hooks.py000066400000000000000000000023041526437326600217350ustar00rootroot00000000000000from unittest import TestCase, mock from xsdata.utils.hooks import load_entry_points class HooksTests(TestCase): @mock.patch("xsdata.utils.hooks.metadata.entry_points") def test_load_entry_points_with_mapping_api(self, mock_entry_points) -> None: first_ep = mock.Mock() second_ep = mock.Mock() third_ep = mock.Mock() mock_entry_points.return_value = { "foo": [first_ep, second_ep], "bar": [third_ep], } load_entry_points("foo") first_ep.load.assert_called_once_with() second_ep.load.assert_called_once_with() self.assertEqual(0, third_ep.load.call_count) @mock.patch("xsdata.utils.hooks.metadata.entry_points") def test_load_entry_points_with_select_api(self, mock_entry_points) -> None: first_ep = mock.Mock() second_ep = mock.Mock() entry_points = mock.Mock() entry_points.select.return_value = [first_ep, second_ep] mock_entry_points.return_value = entry_points load_entry_points("foo") first_ep.load.assert_called_once_with() second_ep.load.assert_called_once_with() entry_points.select.assert_called_once_with(group="foo") tefra-xsdata-cf7afe8/tests/utils/test_namespaces.py000066400000000000000000000122221526437326600227310ustar00rootroot00000000000000from unittest import TestCase from xsdata.models.enums import Namespace from xsdata.utils.namespaces import ( build_qname, clean_prefixes, clean_uri, generate_prefix, is_default, is_ncname, is_uri, load_prefix, prefix_exists, split_qname, to_package_name, ) class NamespacesTests(TestCase): def test_load_prefix(self) -> None: ns_map: dict = {} self.assertEqual("ns0", load_prefix("a", ns_map)) self.assertEqual("ns0", load_prefix("a", ns_map)) self.assertEqual("ns0", load_prefix("a", ns_map)) self.assertEqual("xs", load_prefix(Namespace.XS.uri, ns_map)) self.assertEqual("soapenv", load_prefix(Namespace.SOAP_ENV.uri, ns_map)) expected = { "ns0": "a", "soapenv": "http://schemas.xmlsoap.org/soap/envelope/", "xs": "http://www.w3.org/2001/XMLSchema", } self.assertEqual(expected, ns_map) def test_generate_prefix(self) -> None: ns_map: dict = {} self.assertEqual("ns0", generate_prefix("a", ns_map)) self.assertEqual("xs", generate_prefix(Namespace.XS.uri, ns_map)) self.assertEqual("soapenv", generate_prefix(Namespace.SOAP_ENV.uri, ns_map)) self.assertEqual("ns3", generate_prefix("b", ns_map)) expected = { "ns0": "a", "ns3": "b", "soapenv": "http://schemas.xmlsoap.org/soap/envelope/", "xs": "http://www.w3.org/2001/XMLSchema", } self.assertEqual(expected, ns_map) def test_prefix_exists(self) -> None: self.assertFalse(prefix_exists("a", {})) self.assertTrue(prefix_exists("a", {"foo": "a"})) self.assertTrue(prefix_exists("a", {None: "a"})) def test_clean_prefixes(self) -> None: ns_map = {"": "a", None: "b", "foo": ""} self.assertEqual({None: "a"}, clean_prefixes(ns_map)) ns_map = {None: "b", "": "a"} self.assertEqual({None: "b"}, clean_prefixes(ns_map)) ns_map = {None: "b", "bb": "b", "c": 1} self.assertEqual({"bb": "b", "c": 1}, clean_prefixes(ns_map)) ns_map = {"bb": "b", None: "b", "c": 1} self.assertEqual({"bb": "b", "c": 1}, clean_prefixes(ns_map)) def test_clean_uri(self) -> None: self.assertEqual("any", clean_uri("##any")) self.assertEqual("a", clean_uri("urn:a")) self.assertEqual("a_com/b", clean_uri("http://a.com/b.xsd")) self.assertEqual("a_com/b", clean_uri("http://www.a.com/b.xsd")) self.assertEqual("a_com/b", clean_uri("https://a.com/b.wsdl")) self.assertEqual("a_com/b", clean_uri("https://www.a.com/b.xsd")) def test_build_qname(self) -> None: self.assertEqual("{a}b", build_qname("a", "b")) self.assertEqual("b", build_qname("", "b")) self.assertEqual("b", build_qname(None, "b")) self.assertEqual("b", build_qname("b", "")) self.assertEqual("b", build_qname("b")) self.assertEqual("b", build_qname("b", None)) with self.assertRaises(ValueError): build_qname(None, None) def test_split_qname(self) -> None: self.assertEqual(("a", "b"), split_qname("{a}b")) self.assertEqual((None, "b"), split_qname("b")) self.assertEqual((None, "{"), split_qname("{")) self.assertEqual((None, "{foobar"), split_qname("{foobar")) with self.assertRaises(IndexError): split_qname("") def test_is_ncname(self) -> None: self.assertFalse(is_ncname("")) self.assertFalse(is_ncname(":abc123")) self.assertFalse(is_ncname("9abc123")) self.assertFalse(is_ncname("abc:123")) self.assertTrue(is_ncname("a_1.2-b3")) def test_is_uri(self) -> None: self.assertFalse(is_uri("|")) self.assertFalse(is_uri("")) self.assertTrue(is_uri("a")) self.assertTrue(is_uri("urn:books")) self.assertTrue(is_uri(Namespace.XS.uri)) def test_is_default(self) -> None: self.assertFalse(is_default("foo", {})) self.assertFalse(is_default("foo", {"a": "foo"})) self.assertTrue(is_default("foo", {"": "foo"})) self.assertTrue(is_default("foo", {None: "foo"})) self.assertTrue(is_default("foo", {"a": "foo", None: "foo"})) def test_to_package_name(self) -> None: cases = { "http://www.w3.org/XML/1998/namespace": "org.w3.XML.1998.namespace", "http://www.w3.org/XML/2008/06/xlink.xsd": "org.w3.XML.2008.06.xlink", "http://xsdtesting": "xsdtesting", "http://xsdtesting:8080": "xsdtesting", "http://xsdtesting:8080#target": "xsdtesting", "myNS.tempuri.org": "org.tempuri.myNS", "ElemDecl/disallowedSubst": "ElemDecl.disallowedSubst", "http://xstest-tns/schema11": "xstest-tns.schema11", "http://uri.etsi.org/#": "org.etsi.uri", "urn:xmlns:25hoursaday-com:address": "com.25hoursaday.address", "urn:my.test:SchemaB": "my.test.SchemaB", "http://": "", "": "", " ": "", None: "", } for uri, package in cases.items(): self.assertEqual(package, to_package_name(uri)) tefra-xsdata-cf7afe8/tests/utils/test_objects.py000066400000000000000000000006641526437326600222520ustar00rootroot00000000000000from types import SimpleNamespace from unittest import TestCase from xsdata.utils import objects class ObjectsTests(TestCase): def test_update(self) -> None: obj = SimpleNamespace() obj.foo = SimpleNamespace() obj.foo.bar = 2 obj.bar = 1 kwargs = {"foo.bar": 1, "bar": 2} objects.update(obj, **kwargs) self.assertEqual(1, obj.foo.bar) self.assertEqual(2, obj.bar) tefra-xsdata-cf7afe8/tests/utils/test_testing.py000066400000000000000000000010141526437326600222640ustar00rootroot00000000000000from unittest import TestCase from tests.fixtures.books import BooksForm from xsdata.utils.testing import load_class class TestingTests(TestCase): def test_load(self) -> None: output = "Generating package: tests.fixtures.books.books" self.assertEqual(BooksForm, load_class(output, "BooksForm")) def test_load_class_raises_exception(self) -> None: output = "Generating package: generated.foo.bar" with self.assertRaises(ModuleNotFoundError): load_class(output, "foo") tefra-xsdata-cf7afe8/tests/utils/test_text.py000066400000000000000000000173601526437326600216060ustar00rootroot00000000000000import string from unittest import TestCase from xsdata.utils.text import ( CharType, alnum, camel_case, capitalize, classify, kebab_case, mixed_case, mixed_pascal_case, mixed_snake_case, original_case, pascal_case, screaming_snake_case, snake_case, split_words, ) class TextTests(TestCase): def test_original_case(self) -> None: self.assertEqual("p00p", original_case("p00p", foobar=True)) self.assertEqual("p00p", original_case("p00p")) self.assertEqual("USERName", original_case("USERName")) self.assertEqual("_USERName", original_case("_USERName")) self.assertEqual("UserNAME", original_case("UserNAME")) self.assertEqual("USER_name", original_case("USER_name")) self.assertEqual("USER_name", original_case("3USER_name")) self.assertEqual("USERNAME", original_case("USER-NAME")) self.assertEqual("User_Name", original_case("User_Name")) self.assertEqual("user_name", original_case("user_name")) self.assertEqual("SUserNAME", original_case("SUserNAME")) def test_snake_case(self) -> None: self.assertEqual("p00p", snake_case("p00p", foobar=True)) self.assertEqual("p00p", snake_case("p00p")) self.assertEqual("username", snake_case("USERName")) self.assertEqual("user_name", snake_case("UserNAME")) self.assertEqual("user_name", snake_case("USER_name")) self.assertEqual("user_name", snake_case("USER-NAME")) self.assertEqual("user_name", snake_case("User_Name")) self.assertEqual("user_name", snake_case("user_name")) self.assertEqual("suser_name", snake_case("SUserNAME")) def test_screaming_snake_case(self) -> None: self.assertEqual("P00P", screaming_snake_case("p00p", foobar=True)) self.assertEqual("P00P", screaming_snake_case("p00p")) self.assertEqual("USERNAME", screaming_snake_case("USERName")) self.assertEqual("USER_NAME", screaming_snake_case("UserNAME")) self.assertEqual("USER_NAME", screaming_snake_case("USER_name")) self.assertEqual("USER_NAME", screaming_snake_case("USER-NAME")) self.assertEqual("USER_NAME", screaming_snake_case("User_Name")) self.assertEqual("USER_NAME", screaming_snake_case("user_name")) self.assertEqual("SUSER_NAME", screaming_snake_case("SUserNAME")) def test_pascal_case(self) -> None: self.assertEqual("P00P", pascal_case("p00p", foobar=True)) self.assertEqual("P00P", pascal_case("p00p")) self.assertEqual("Username", pascal_case("USERName")) self.assertEqual("UserName", pascal_case("UserNAME")) self.assertEqual("UserName", pascal_case("USER_name")) self.assertEqual("UserName", pascal_case("USER-NAME")) self.assertEqual("UserName", pascal_case("User_Name")) self.assertEqual("UserName", pascal_case("user_name")) self.assertEqual("SuserName", pascal_case("SUserNAME")) def test_camel_case(self) -> None: self.assertEqual("p00P", camel_case("p00p", foobar=True)) self.assertEqual("p00P", camel_case("p00p")) self.assertEqual("username", camel_case("USERName")) self.assertEqual("userName", camel_case("UserNAME")) self.assertEqual("userName", camel_case("USER_name")) self.assertEqual("userName", camel_case("USER-NAME")) self.assertEqual("userName", camel_case("User_Name")) self.assertEqual("userName", camel_case("user_name")) self.assertEqual("suserName", camel_case("SUserNAME")) def test_mixed_case(self) -> None: self.assertEqual("p00p", mixed_case("p00p", foobar=True)) self.assertEqual("p00p", mixed_case("p00p")) self.assertEqual("USERName", mixed_case("USERName")) self.assertEqual("UserNAME", mixed_case("UserNAME")) self.assertEqual("USERname", mixed_case("USER_name")) self.assertEqual("USERNAME", mixed_case("USER-NAME")) self.assertEqual("UserName", mixed_case("User_Name")) self.assertEqual("username", mixed_case("user_name")) self.assertEqual("SUserNAME", mixed_case("SUserNAME")) def test_mixed_pascal_case(self) -> None: self.assertEqual("P00p", mixed_pascal_case("p00p", foobar=True)) self.assertEqual("P00p", mixed_pascal_case("p00p")) self.assertEqual("USERName", mixed_pascal_case("USERName")) self.assertEqual("UserNAME", mixed_pascal_case("UserNAME")) self.assertEqual("USERname", mixed_pascal_case("USER_name")) self.assertEqual("USERNAME", mixed_pascal_case("USER-NAME")) self.assertEqual("UserName", mixed_pascal_case("User_Name")) self.assertEqual("Username", mixed_pascal_case("user_name")) self.assertEqual("SUserNAME", mixed_pascal_case("SUserNAME")) def test_mixed_snake_case(self) -> None: self.assertEqual("p00p", mixed_snake_case("p00p", foobar=True)) self.assertEqual("p00p", mixed_snake_case("p00p")) self.assertEqual("USERName", mixed_snake_case("USERName")) self.assertEqual("User_NAME", mixed_snake_case("UserNAME")) self.assertEqual("USER_name", mixed_snake_case("USER_name")) self.assertEqual("USER_NAME", mixed_snake_case("USER-NAME")) self.assertEqual("User_Name", mixed_snake_case("User_Name")) self.assertEqual("user_name", mixed_snake_case("user_name")) self.assertEqual("SUser_NAME", mixed_snake_case("SUserNAME")) def test_kebab_case(self) -> None: self.assertEqual("p00p", kebab_case("p00p", foobar=True)) self.assertEqual("p00p", kebab_case("p00p")) self.assertEqual("USERName", kebab_case("USERName")) self.assertEqual("User-NAME", kebab_case("UserNAME")) self.assertEqual("USER-name", kebab_case("USER_name")) self.assertEqual("USER-NAME", kebab_case("USER-NAME")) self.assertEqual("User-Name", kebab_case("User_Name")) self.assertEqual("user-name", kebab_case("user_name")) self.assertEqual("SUser-NAME", kebab_case("SUserNAME")) def test_capitalize(self) -> None: self.assertEqual("UserName", capitalize("userName")) self.assertEqual(".userName", capitalize(".userName")) def test_split_words(self) -> None: self.assertEqual(["user", "Name"], split_words("userName")) self.assertEqual(["User", "Name"], split_words("User.Name")) self.assertEqual(["User", "Name"], split_words("UserName")) self.assertEqual(["USER", "NAME"], split_words("USER_NAME")) self.assertEqual(["user", "name"], split_words("user_name")) self.assertEqual(["user", "name"], split_words("user__name")) self.assertEqual(["common", "v48", "0"], split_words("common_v48_0")) self.assertEqual(["user"], split_words("user")) self.assertEqual(["user"], split_words("_user")) self.assertEqual(["user"], split_words("__user")) self.assertEqual(["TMessage", "DB"], split_words("TMessageDB")) self.assertEqual(["GLOBAL", "REF"], split_words("GLOBAL-REF")) self.assertEqual(["book"], split_words("βιβλιο-book")) def test_alnum(self) -> None: self.assertEqual("foo1", alnum("foo 1")) self.assertEqual("foo1", alnum(" foo_1 ")) self.assertEqual("foo1", alnum("\tfoo*1")) self.assertEqual("foo1", alnum(" foo*1")) self.assertEqual("1", alnum(" βιβλίο*1")) def test_classify(self) -> None: for ltr in string.ascii_uppercase: self.assertEqual(CharType.UPPER, classify(ltr)) for ltr in string.ascii_lowercase: self.assertEqual(CharType.LOWER, classify(ltr)) for ltr in string.digits: self.assertEqual(CharType.NUMERIC, classify(ltr)) for ltr in "~!@#$%^&*()_+β": self.assertEqual(CharType.OTHER, classify(ltr)) tefra-xsdata-cf7afe8/xsdata/000077500000000000000000000000001526437326600161645ustar00rootroot00000000000000tefra-xsdata-cf7afe8/xsdata/__init__.py000066400000000000000000000000261526437326600202730ustar00rootroot00000000000000__version__ = "26.10" tefra-xsdata-cf7afe8/xsdata/__main__.py000066400000000000000000000004171526437326600202600ustar00rootroot00000000000000import sys def main() -> None: """Cli entry point.""" try: from xsdata.cli import cli cli() except ImportError: print('Install cli requirements "pip install xsdata[cli]"') sys.exit(1) if __name__ == "__main__": main() tefra-xsdata-cf7afe8/xsdata/cli.py000066400000000000000000000121011526437326600173000ustar00rootroot00000000000000import logging import platform import sys import warnings from collections.abc import Iterator from pathlib import Path from typing import Any import click from xsdata import __version__ from xsdata.codegen.transformer import ResourceTransformer from xsdata.logger import logger from xsdata.models.config import GeneratorConfig, GeneratorOutput from xsdata.utils.click import URL, LogFormatter, LogHandler, model_options from xsdata.utils.downloader import Downloader from xsdata.utils.hooks import load_entry_points # Load cli plugins load_entry_points("xsdata.plugins.cli") # Setup xsdata logger to print records to stdout/stderr handler = LogHandler() handler.formatter = LogFormatter() logger.handlers = [handler] logger.propagate = False # Attach the cli handler to the python warnings logger py_warnings = logging.getLogger("py.warnings") py_warnings.handlers = [handler] py_warnings.propagate = False # Log warnings as well logging.captureWarnings(True) @click.group() @click.pass_context @click.version_option(__version__) def cli(ctx: click.Context, **kwargs: Any) -> None: """Xsdata command line interface.""" logger.setLevel(logging.INFO) formatwarning_orig = warnings.formatwarning logger.info( "========= xsdata v%s / Python %s / Platform %s =========\n", __version__, platform.python_version(), sys.platform, ) def format_warning(message: Any, category: Any, *args: Any) -> str: return ( f"{category.__name__}: {message}" if category else message ) # pragma: no cover def format_warning_restore() -> None: warnings.formatwarning = formatwarning_orig warnings.formatwarning = format_warning # type: ignore ctx.call_on_close(format_warning_restore) @cli.command("init-config") @click.argument("output", type=click.Path(), default=".xsdata.xml") def init_config(**kwargs: Any) -> None: """Create or update a configuration file.""" file_path = Path(kwargs["output"]) if file_path.exists(): config = GeneratorConfig.read(file_path) logger.info("Updating configuration file %s", kwargs["output"]) else: logger.info("Initializing configuration file %s", kwargs["output"]) config = GeneratorConfig.create() with file_path.open("w") as fp: config.write(fp, config) handler.emit_warnings() @cli.command("download") @click.argument("source", type=URL(), required=True) @click.option( "-o", "--output", type=click.Path(), default="./", help="Output directory, default cwd", ) def download(source: str, output: str) -> None: """Download a schema or a definition locally with all its dependencies.""" downloader = Downloader(output=Path(output).resolve()) downloader.wget(source) handler.emit_warnings() _SUPPORTED_EXTENSIONS = ("wsdl", "xsd", "dtd", "xml", "json") @cli.command("generate") @click.argument("source", required=True) @click.option( "-r", "--recursive", is_flag=True, default=False, help="Search files recursively in the source directory", ) @click.option("-c", "--config", default=".xsdata.xml", help="Project configuration") @click.option("--cache", is_flag=True, default=False, help="Cache sources loading") @click.option("--debug", is_flag=True, default=False, help="Show debug messages") @click.option( "--extensions", default=",".join(_SUPPORTED_EXTENSIONS), help="Comma-separated list of extensions to filter", ) @model_options(GeneratorOutput) def generate(**kwargs: Any) -> None: """Generate code from xsd, dtd, wsdl, xml and json files. The input source can be either a filepath, uri or a directory containing xml, json, xsd and wsdl files. """ debug = kwargs.pop("debug") if debug: logger.setLevel(logging.DEBUG) source = kwargs.pop("source") cache = kwargs.pop("cache") recursive = kwargs.pop("recursive") config_file = Path(kwargs.pop("config")).resolve() # Parse the comma-separated extensions string into a tuple extensions_str = kwargs.pop("extensions") extensions = tuple(ext.strip() for ext in extensions_str.split(",") if ext.strip()) params = {k.replace("__", "."): v for k, v in kwargs.items() if v is not None} config = GeneratorConfig.read(config_file) config.output.update(**params) transformer = ResourceTransformer(config=config) uris = sorted(resolve_source(source, recursive=recursive, extensions=extensions)) transformer.process(uris, cache=cache) handler.emit_warnings() def resolve_source( source: str, recursive: bool, extensions: tuple[str, ...] = _SUPPORTED_EXTENSIONS ) -> Iterator[str]: """Yields all supported resource URIs.""" if source.find("://") > -1 and not source.startswith("file://"): yield source else: path = Path(source).resolve() match = "**/*" if recursive else "*" if path.is_dir(): for ext in extensions: yield from (x.as_uri() for x in path.glob(f"{match}.{ext}")) else: # is a file yield path.as_uri() if __name__ == "__main__": # pragma: no cover cli() tefra-xsdata-cf7afe8/xsdata/codegen/000077500000000000000000000000001526437326600175705ustar00rootroot00000000000000tefra-xsdata-cf7afe8/xsdata/codegen/__init__.py000066400000000000000000000002371526437326600217030ustar00rootroot00000000000000from urllib.request import build_opener from xsdata import __version__ opener = build_opener() opener.addheaders = [("User-agent", f"xsdata/{__version__}")] tefra-xsdata-cf7afe8/xsdata/codegen/container.py000066400000000000000000000215641526437326600221340ustar00rootroot00000000000000from collections.abc import Callable, Iterator from xsdata.codegen.handlers import ( AddAttributeSubstitutions, CalculateAttributePaths, CreateCompoundFields, CreateWrapperFields, DesignateClassPackages, DetectCircularReferences, DisambiguateChoices, FilterClasses, FlattenAttributeGroups, FlattenClassExtensions, MergeAttributes, MergeDuplicateClasses, ProcessAttributeTypes, ProcessMixedContentClass, RenameDuplicateAttributes, RenameDuplicateClasses, ResetAttributeSequenceNumbers, ResetAttributeSequences, SanitizeAttributesDefaultValue, SanitizeEnumerationClass, UnnestInnerClasses, UpdateAttributesEffectiveChoice, VacuumInnerClasses, ValidateAttributesOverrides, ValidateReferences, ) from xsdata.codegen.mixins import ContainerInterface from xsdata.codegen.models import Class, Status from xsdata.codegen.stopwatch import stopwatch from xsdata.codegen.utils import ClassUtils from xsdata.codegen.validator import ClassValidator from xsdata.models.config import GeneratorConfig from xsdata.utils import collections from xsdata.utils.constants import return_true class Steps: """Process steps.""" UNGROUP = 10 FLATTEN = 20 SANITIZE = 30 RESOLVE = 40 CLEANUP = 50 FINALIZE = 60 class ClassContainer(ContainerInterface): """A class list wrapper with an easy access api. Args: config: The generator configuration instance Attributes: processors: A step-processors mapping data: The class qname map step: The current process step """ __slots__ = ("processors", "step") def __init__(self, config: GeneratorConfig): """Initialize the container and all the class processors. The order of the steps and the processors is the secret recipe of the xsdata code generator. Args: config: The generator configuration instance """ super().__init__(config) self.step: int = 0 self.processors: dict[int, list] = { Steps.UNGROUP: [ FlattenAttributeGroups(self), ], Steps.FLATTEN: [ CalculateAttributePaths(), FlattenClassExtensions(self), SanitizeEnumerationClass(self), UpdateAttributesEffectiveChoice(), UnnestInnerClasses(self), AddAttributeSubstitutions(self), ProcessAttributeTypes(self), MergeAttributes(), ProcessMixedContentClass(), ], Steps.SANITIZE: [ ResetAttributeSequences(), RenameDuplicateAttributes(), ], Steps.RESOLVE: [ ValidateAttributesOverrides(self), ], Steps.CLEANUP: [ VacuumInnerClasses(), ], Steps.FINALIZE: [ DetectCircularReferences(self), CreateCompoundFields(self), CreateWrapperFields(self), DisambiguateChoices(self), SanitizeAttributesDefaultValue(self), ResetAttributeSequenceNumbers(self), ], } def __iter__(self) -> Iterator[Class]: """Yield an iterator for the class map values.""" for items in list(self.data.values()): yield from items def find(self, qname: str, condition: Callable = return_true) -> Class | None: """Find class that matches the given qualified name and condition callable. Classes are allowed to have the same qualified name, e.g. xsd:Element extending xsd:ComplexType with the same name, you can provide and additional callback to filter the classes like the tag. Args: qname: The qualified name of the class condition: A user callable to filter further Returns: A class instance or None if no match found. """ for row in self.data.get(qname, []): if condition(row): if row.status < self.step: self.process_class(row, self.step) return self.find(qname, condition) return row return None def find_inner(self, source: Class, qname: str) -> Class: """Search by qualified name for a specific inner class or fail. Args: source: The source class to search for the inner class qname: The qualified name of the inner class to look up Returns: The inner class instance Raises: CodeGenerationError: If the inner class is not found. """ inner = ClassUtils.find_nested(source, qname) if inner.status < self.step: self.process_class(inner, self.step) return inner def first(self, qname: str) -> Class: """Return the first class that matches the qualified name. Args: qname: The qualified name of the class Returns: The first matching class Raises: KeyError: If no class matches the qualified name """ classes = self.data.get(qname) if not classes: raise KeyError(f"Class {qname} not found") return classes[0] def process(self) -> None: """Run the processor and filter steps.""" self.validate_classes() self.process_classes(Steps.UNGROUP) self.remove_groups() self.process_classes(Steps.FLATTEN) self.filter_classes() self.process_classes(Steps.SANITIZE) self.process_classes(Steps.RESOLVE) self.process_classes(Steps.CLEANUP) self.process_classes(Steps.FINALIZE) self.designate_classes() def validate_classes(self) -> None: """Merge redefined classes.""" with stopwatch("ClassValidator"): ClassValidator(self).process() def process_classes(self, step: int) -> None: """Run the given step processors for all classes. Args: step: The step reference number """ self.step = step for obj in self: if obj.status < step: self.process_class(obj, step) def process_class(self, target: Class, step: int) -> None: """Run the step processors for the given class. Process recursively any inner classes as well. Args: target: The target class to process step: The step reference number """ target.status = Status(step) for processor in self.processors.get(step, []): with stopwatch(processor.__class__.__name__): processor.process(target) for inner in target.inner: if inner.status < step: self.process_class(inner, step) target.status = Status(step + 1) def designate_classes(self) -> None: """Designate the final class names, packages and modules.""" designators = [ MergeDuplicateClasses(self), RenameDuplicateClasses(self), ValidateReferences(self), DesignateClassPackages(self), ] for designator in designators: with stopwatch(designator.__class__.__name__): designator.run() def filter_classes(self) -> None: """Filter the classes to be generated.""" with stopwatch(FilterClasses.__name__): FilterClasses(self).run() def remove_groups(self) -> None: """Remove xs:groups and xs:attributeGroups from the container.""" self.set([x for x in iter(self) if not x.is_group]) def add(self, item: Class) -> None: """Add class item to the container. Args: item: The class instance to add """ self.data.setdefault(item.qname, []).append(item) def remove(self, *items: Class) -> None: """Safely remove classes from the container. Args: items: The classes to remove """ for item in items: self.data[item.qname] = [ c for c in self.data[item.qname] if c.ref != item.ref ] def reset(self, item: Class, qname: str) -> None: """Update the given class qualified name. Args: item: The target class instance to update qname: The new qualified name of the class """ self.data[qname] = [c for c in self.data[qname] if c.ref != item.ref] self.add(item) def set(self, items: list[Class]) -> None: """Set the list of classes to the container. Args: items: The list of classes """ self.data.clear() self.extend(items) def extend(self, items: list[Class]) -> None: """Add a list of classes to the container. Args: items: The list of class instances to add """ collections.apply(items, self.add) tefra-xsdata-cf7afe8/xsdata/codegen/exceptions.py000066400000000000000000000012621526437326600223240ustar00rootroot00000000000000from typing import IO, Any from click import ClickException, echo class CodegenWarning(Warning): """Recovered errors during code generation recovered errors.""" class CodegenError(ClickException): """Unexpected state during code generation related errors.""" def __init__(self, message: str, **kwargs: Any): """Click exception constructor with metadata.""" super().__init__(message) self.meta = kwargs def show(self, file: IO[Any] | None = None) -> None: """Echo codegen error message and details.""" echo("=========") super().show(file) for key, value in self.meta.items(): echo(f"{key}: {value}") tefra-xsdata-cf7afe8/xsdata/codegen/handlers/000077500000000000000000000000001526437326600213705ustar00rootroot00000000000000tefra-xsdata-cf7afe8/xsdata/codegen/handlers/__init__.py000066400000000000000000000044021526437326600235010ustar00rootroot00000000000000from .add_attribute_substitutions import AddAttributeSubstitutions from .calculate_attribute_paths import CalculateAttributePaths from .create_compound_fields import CreateCompoundFields from .create_wrapper_fields import CreateWrapperFields from .designate_class_packages import DesignateClassPackages from .detect_circular_references import DetectCircularReferences from .disambiguate_choices import DisambiguateChoices from .filter_classes import FilterClasses from .flatten_attribute_groups import FlattenAttributeGroups from .flatten_class_extensions import FlattenClassExtensions from .merge_attributes import MergeAttributes from .merge_duplicate_classes import MergeDuplicateClasses from .process_attributes_types import ProcessAttributeTypes from .process_mixed_content_class import ProcessMixedContentClass from .rename_duplicate_attributes import RenameDuplicateAttributes from .rename_duplicate_classes import RenameDuplicateClasses from .reset_attribute_sequence_numbers import ResetAttributeSequenceNumbers from .reset_attribute_sequences import ResetAttributeSequences from .sanitize_attributes_default_value import SanitizeAttributesDefaultValue from .sanitize_enumeration_class import SanitizeEnumerationClass from .unnest_inner_classes import UnnestInnerClasses from .update_attributes_effective_choice import UpdateAttributesEffectiveChoice from .vacuum_inner_classes import VacuumInnerClasses from .validate_attributes_overrides import ValidateAttributesOverrides from .validate_references import ValidateReferences __all__ = [ "AddAttributeSubstitutions", "CalculateAttributePaths", "CreateCompoundFields", "CreateWrapperFields", "DesignateClassPackages", "DetectCircularReferences", "DisambiguateChoices", "FilterClasses", "FlattenAttributeGroups", "FlattenClassExtensions", "MergeAttributes", "MergeDuplicateClasses", "ProcessAttributeTypes", "ProcessMixedContentClass", "RenameDuplicateAttributes", "RenameDuplicateClasses", "ResetAttributeSequenceNumbers", "ResetAttributeSequences", "SanitizeAttributesDefaultValue", "SanitizeEnumerationClass", "UnnestInnerClasses", "UpdateAttributesEffectiveChoice", "VacuumInnerClasses", "ValidateAttributesOverrides", "ValidateReferences", ] tefra-xsdata-cf7afe8/xsdata/codegen/handlers/add_attribute_substitutions.py000066400000000000000000000101601526437326600275720ustar00rootroot00000000000000from collections import defaultdict from xsdata.codegen.mixins import ContainerInterface, RelativeHandlerInterface from xsdata.codegen.models import Attr, AttrType, Class from xsdata.codegen.utils import ClassUtils from xsdata.models.enums import Tag from xsdata.utils import collections class AddAttributeSubstitutions(RelativeHandlerInterface): """Apply substitution attributes to the given class recursively. Args: container: The class container instance Attributes: substitutions: Mapping of type names to attr values """ __slots__ = "substitutions" def __init__(self, container: ContainerInterface): """Initialize the class.""" super().__init__(container) self.substitutions: dict[str, list[Attr]] | None = None def process(self, target: Class) -> None: """Process the given class attrs for substitution groups. This method will ignore attrs in the class derived from a xs:enumeration, xs:anyType and xs:any. If this is the first time we call the method, build the substitution map. Args: target: The target class instance """ if self.substitutions is None: self.create_substitutions() for attr in target.attrs.copy(): if not (attr.is_enumeration or attr.is_wildcard): self.process_attribute(target, attr) def process_attribute(self, target: Class, attr: Attr) -> None: """Add substitution attrs that refer to the attr type. If the given attr is referenced in substitution groups clone all substitution attrs and place them bellow the original attr. Convert all the attrs of the group to repeatable choice elements. Guard against multiple substitutions in case of xs:groups. Args: target: The target class instance attr: The source attr instance to check and process """ index = target.attrs.index(attr) assert self.substitutions is not None for attr_type in attr.types: if attr_type.substituted: continue attr_type.substituted = True for substitution in self.substitutions.get(attr_type.qname, []): self.prepare_substituted(attr) clone = ClassUtils.clone_attribute(substitution, attr.restrictions) clone.restrictions.min_occurs = 0 clone.restrictions.max_occurs = attr.restrictions.max_occurs attr.substitution = clone.substitution = attr_type.name pos = collections.find(target.attrs, clone) index = pos + 1 if pos > -1 else index target.attrs.insert(index, clone) self.process_attribute(target, clone) def create_substitutions(self) -> None: """Build the substitutions mapping of type names to attr values. The values are simple reference attrs that we can easily clone later on demand. """ self.substitutions = defaultdict(list) for obj in self.container: for qname in obj.substitutions: attr = self.create_substitution(obj) self.substitutions[qname].append(attr) @classmethod def prepare_substituted(cls, attr: Attr): """Prepare the original attr for substitutions. Effectively place the attr inside a xs:choice container with min occurs zero. """ attr.restrictions.min_occurs = 0 if not attr.restrictions.choice: choice = id(attr) attr.restrictions.choice = choice attr.restrictions.path.append(("c", choice, 1, 1)) @classmethod def create_substitution(cls, source: Class) -> Attr: """Create a reference attr to the source class qname. Args: source: The source class to reference Returns: The reference to the source class attr. """ return Attr( name=source.name, types=[AttrType(qname=source.qname)], tag=Tag.ELEMENT, namespace=source.namespace, ) tefra-xsdata-cf7afe8/xsdata/codegen/handlers/calculate_attribute_paths.py000066400000000000000000000060001526437326600271550ustar00rootroot00000000000000from xsdata.codegen.mixins import HandlerInterface from xsdata.codegen.models import Attr, Class ALL = "a" GROUP = "g" SEQUENCE = "s" CHOICE = "c" class CalculateAttributePaths(HandlerInterface): """Calculate min/max occurs and sequence/choice/group from the schema path.""" __slots__ = () @classmethod def process(cls, target: Class): """Calculating the class attrs restrictions by their schema path. For each attr calculate the min/max occurs and set the sequence/choice/group reference id. Ignore attrs derived from xs:attribute and xs:enumeration as these are not affected by the parent element. Args: target: The target class instance """ for attr in target.attrs: if ( attr.restrictions.path and not attr.is_attribute and not attr.is_enumeration ): cls.process_attr_path(attr) @classmethod def process_attr_path(cls, attr: Attr): """Entrypoint for processing a class attr. Example path: ("s", 1, 1, 1), ("s", 2, 1, 2), ("c", 3, 0, 10) -> sequence:1 with min=1 and max_occurs=1 sequence:2 with min=1 and max_occurs=2 choice:3 with min=0 and max_occurs=10 Steps: - Every attr starts with a min/max occurs equal to one. - For every parent container multiply the min/max occurs - Set the sequence/choice/group reference ids as you go along - Elements within a choice with minOccurs <= 1 are effectively optional since only one branch of the choice is selected Args: attr: The attr of the class to check and process """ min_occurs = 1 max_occurs = 1 choice_min_occurs = None for path in attr.restrictions.path: name, index, mi, ma = path if name == SEQUENCE: if not attr.restrictions.sequence: attr.restrictions.sequence = index elif name == CHOICE: if not attr.restrictions.choice: attr.restrictions.choice = index # Track the minimum minOccurs of any choice in the path if choice_min_occurs is None or mi < choice_min_occurs: choice_min_occurs = mi elif name == GROUP: attr.restrictions.group = index else: pass min_occurs *= mi max_occurs *= ma assert attr.restrictions.min_occurs is not None assert attr.restrictions.max_occurs is not None attr.restrictions.min_occurs *= min_occurs attr.restrictions.max_occurs *= max_occurs # Elements within a choice with minOccurs <= 1 are effectively optional # because only one branch of the choice is ever selected at runtime if choice_min_occurs is not None and choice_min_occurs <= 1: attr.restrictions.min_occurs = 0 tefra-xsdata-cf7afe8/xsdata/codegen/handlers/create_compound_fields.py000066400000000000000000000206071526437326600264440ustar00rootroot00000000000000from collections import Counter from xsdata.codegen.mixins import ContainerInterface, RelativeHandlerInterface from xsdata.codegen.models import Attr, Class, Restrictions, get_restriction_choice from xsdata.codegen.utils import ClassUtils from xsdata.formats.dataclass.models.elements import XmlType from xsdata.models.enums import Tag from xsdata.utils import collections from xsdata.utils.collections import group_by ALL = "a" GROUP = "g" SEQUENCE = "s" CHOICE = "c" class CreateCompoundFields(RelativeHandlerInterface): """Process attrs that belong in the same choice. Args: container: The class container instance Attributes: config: The compound fields configuration """ __slots__ = "config" def __init__(self, container: ContainerInterface): """Initialize the class.""" super().__init__(container) self.config = container.config.output.compound_fields def process(self, target: Class) -> None: """Process the attrs of class that belong in the same choice. If the compound fields configuration is enabled replace the attrs with a compound attr, otherwise recalculate the min occurs restriction for each of them. Args: target: The target class instance """ groups = group_by(target.attrs, get_restriction_choice) for choice, attrs in groups.items(): if choice and len(attrs) > 1: if self.config.enabled: self.group_fields(target, attrs) else: self.calculate_choice_min_occurs(attrs) @classmethod def calculate_choice_min_occurs(cls, attrs: list[Attr]): """Calculate the min occurs restriction of the attrs. If that attr has a path that includes a xs:choice with min occurs less than 1, update the attr min occurs restriction to zero, effectively marking it as optional. Args: attrs: A list of attrs that belong in the same xs:choice. """ for attr in attrs: for path in attr.restrictions.path: name, _index, mi, _ma = path if name == CHOICE and mi <= 1: attr.restrictions.min_occurs = 0 @classmethod def update_counters(cls, attr: Attr, counters: dict): """Update the counters dictionary with the attr min/max restrictions. This method builds a nested counters mapping per path. Example: { "min": [0, 1, 2] "max": [3, 4, 5], ("c", 1, 0, 1): { "min": [6, 7, 8], "max": [9, 10, 11], ("g", 2, 0, 1): { ... } } } Args: attr: The source attr instance counters: The nested counters instance to update """ started = False choice = attr.restrictions.choice for path in attr.restrictions.path: name, index, mi, _ma = path if not started and name != CHOICE and index != choice: continue started = True if path not in counters: counters[path] = {"min": [], "max": []} counters = counters[path] if mi <= 1: attr.restrictions.min_occurs = 0 counters["min"].append(attr.restrictions.min_occurs) counters["max"].append(attr.restrictions.max_occurs) def group_fields(self, target: Class, attrs: list[Attr]) -> None: """Group attributes into a new compound field. Args: target: The target class instance attrs: A list of attrs that belong to the same choice """ pos = target.attrs.index(attrs[0]) choice = attrs[0].restrictions.choice assert choice is not None names = [] substitutions = [] choices = [] counters: dict = {"min": [], "max": []} for attr in attrs: ClassUtils.remove_attribute(target, attr) names.append(attr.local_name) substitutions.append(attr.substitution) choices.append(self.build_attr_choice(attr)) self.update_counters(attr, counters) min_occurs, max_occurs = self.sum_counters(counters) name = self.choose_name(target, names, list(filter(None, substitutions))) compound_attr = Attr( name=name, index=0, types=[], tag=Tag.CHOICE, restrictions=Restrictions( min_occurs=sum(min_occurs), max_occurs=max(max_occurs) if choice > 0 else sum(max_occurs), ), choices=choices, ) target.attrs.insert(pos, compound_attr) def sum_counters(self, counters: dict) -> tuple[list[int], list[int]]: """Sum the min/max occurrences for the compound attr. Args: counters: The counters map of all the choice attrs """ min_occurs = counters.pop("min", []) max_occurs = counters.pop("max", []) for path, counter in counters.items(): mi, ma = self.sum_counters(counter) if path[0] == "c": min_occurs.append(min(mi)) max_occurs.append(max(ma)) else: min_occurs.append(sum(mi)) max_occurs.append(sum(ma)) return min_occurs, max_occurs def choose_name( self, target: Class, names: list[str], substitutions: list[str], ) -> str: """Choose a name for the compound attr. If the attrs were placed in the same choice because of a single substitution group and the configuration `use_substitution_groups` is enabled, the group name will be used for the compound attr. Otherwise, the name will be the concatenation of the names of the attrs, if the length of the attrs is less than the `max_name_parts` config and the `force_default_name` is false, e.g. `hat_Or_bat_Or_bar` Otherwise, the name will the default from the config, e.g. `choice` If there are any name collisions with other class attrs, the system will add an integer suffix till the name is unique in the class e.g. `choice_1`, `choice_2`, `choice_3` Args: target: The target class names: The list of the attr names substitutions: The list of the substitution group names of the attrs """ if self.config.use_substitution_groups and len(names) == len(substitutions): names = substitutions names = collections.unique_sequence(names) if self.config.force_default_name or len(names) > self.config.max_name_parts: name = self.config.default_name else: name = "_Or_".join(names) reserved = self.build_reserved_names(target, names) return ClassUtils.unique_name(name, reserved) def build_reserved_names(self, target: Class, names: list[str]) -> set[str]: """Build a set of reserved attr names. The method will also check parent attrs. Args: target: The target class instance names: The potential names for the compound attr """ names_counter = Counter(names) all_attrs = self.base_attrs(target) all_attrs.extend(target.attrs) return { attr.slug for attr in all_attrs if attr.xml_type != XmlType.ELEMENTS or Counter([x.local_name for x in attr.choices]) != names_counter } @classmethod def build_attr_choice(cls, attr: Attr) -> Attr: """Build the choice attr from a normal attr. Steps: - Clone the original attr restrictions - Reset the min/max occurs - Remove the sequence reference - Build the new attr and maintain the basic attributes Args: attr: The source attr instance to use Returns: The new choice attr for the compound attr. """ restrictions = attr.restrictions.clone(min_occurs=0, sequence=None) return Attr( name=attr.name, local_name=attr.local_name, namespace=attr.namespace, types=[x.clone() for x in attr.types], tag=attr.tag, help=attr.help, restrictions=restrictions, ) tefra-xsdata-cf7afe8/xsdata/codegen/handlers/create_wrapper_fields.py000066400000000000000000000102641526437326600262760ustar00rootroot00000000000000from xsdata.codegen.mixins import RelativeHandlerInterface from xsdata.codegen.models import Attr, Class from xsdata.codegen.utils import ClassUtils class CreateWrapperFields(RelativeHandlerInterface): """Create wrapper fields. Args: container: The class container instance """ def process(self, target: Class) -> None: """Process the given class attrs and choices. Args: target: The target class instance """ if not self.container.config.output.wrapper_fields: return wrapped = False wrapped_inner = False for attr in target.attrs: if not self.validate_attr(attr): continue inner, source = self.find_source_attr(target, attr) if not source: continue self.wrap_field(source, attr, inner) wrapped = True wrapped_inner = wrapped_inner or inner if wrapped: ClassUtils.rename_duplicate_attributes(target) if inner: ClassUtils.clean_inner_classes(target) @classmethod def wrap_field(cls, source: Attr, attr: Attr, inner: bool): """Create a wrapper field. Clone the source attr and update its name, local name and wrapper attributes. Args: source: The source attr instance attr: The attr instance to wrap inner: Specify if the source is from an inner class """ wrapper = attr.local_name attr.swap(source) attr.wrapper = wrapper attr.types[0].forward = False def find_source_attr(self, parent: Class, attr: Attr) -> tuple[bool, Attr | None]: """Find the source type for the given attr type instance. If it's a forward reference, look up the source in the parent class inners. Args: parent: The parent class instance attr: The attr instance to find a valid source attr Returns: A tuple of whether the source attr is inner and the source attr. """ tp = attr.types[0] inner = False if tp.forward: source = self.container.find_inner(parent, tp.qname) inner = True else: source = self.container.first(tp.qname) if self.validate_source(source, attr.namespace): return inner, source.attrs[0] return inner, None @classmethod def validate_attr(cls, attr: Attr) -> bool: """Validate if the attr can be converted to a wrapper field. Rules: 1. Must be an element 2. Must have only one user type 4. The element can't be optional 5. The element can't be a list element Args: attr: The attr instance to validate Returns: Whether the attr can be converted to a wrapper. """ return ( attr.is_element and len(attr.types) == 1 and not attr.types[0].native and not attr.is_list and not attr.is_optional ) @classmethod def validate_source(cls, source: Class, namespace: str | None) -> bool: """Validate if the source class can be converted to a wrapper field. Rules: 1. It must not have any extensions 2. It must contain exactly one type 3. It must be derived from a xs:element 4. It must not be optional 5. It must not be a forward reference 6. The source attr namespace must match the namespace Args: source: The source class instance to validate namespace: The processing attr namespace Returns: Whether the source class can be converted to a wrapper. """ def ns_equal(a: str | None, b: str | None) -> bool: return (a or "") == (b or "") return ( not source.extensions and len(source.attrs) == 1 and source.attrs[0].is_element and not source.attrs[0].is_optional and not source.attrs[0].is_forward_ref and ns_equal(source.attrs[0].namespace, namespace) ) tefra-xsdata-cf7afe8/xsdata/codegen/handlers/designate_class_packages.py000066400000000000000000000210101526437326600267220ustar00rootroot00000000000000import os import re from collections import defaultdict from collections.abc import Iterable, Iterator from pathlib import Path from urllib.parse import urlparse from toposort import toposort_flatten from xsdata.codegen.exceptions import CodegenError from xsdata.codegen.mixins import ContainerHandlerInterface from xsdata.codegen.models import Class, get_location, get_target_namespace from xsdata.logger import logger from xsdata.models.config import ObjectType, StructureStyle from xsdata.models.enums import COMMON_SCHEMA_DIR from xsdata.utils import collections from xsdata.utils.graphs import strongly_connected_components from xsdata.utils.namespaces import to_package_name from xsdata.utils.package import module_name class DesignateClassPackages(ContainerHandlerInterface): """Designate classes to packages and modules based on the output structure style.""" __slots__ = () def run(self) -> None: """Group classes to packages and modules based on the output structure style. Structure Styles: - Namespaces: classes with the same namespace are grouped together - Single Package: all classes are grouped together - Clusters: strong connected classes are grouped together - Namespace clusters: A combination of the namespaces and clusters - Filenames: classes are grouped together by the schema file location """ structure_style = self.container.config.output.structure_style if structure_style == StructureStyle.NAMESPACES: self.group_by_namespace() elif structure_style == StructureStyle.SINGLE_PACKAGE: self.group_all_together() elif structure_style == StructureStyle.CLUSTERS: self.group_by_strong_components() elif structure_style == StructureStyle.NAMESPACE_CLUSTERS: self.group_by_namespace_clusters() else: self.group_by_filenames() def group_by_filenames(self) -> None: """Group classes by their schema file location. The classes are organized by the common paths of the file locations. Example: http://xsdata/foo/bar/schema.xsd -> foo.bar.schema.py """ package = self.container.config.output.package class_map = collections.group_by(self.container, key=get_location) groups = self.group_common_paths(class_map.keys()) modules_map = defaultdict(list) for keys in groups: if len(keys) == 1: common_path = os.path.dirname(keys[0]) else: common_path = os.path.commonpath(keys) for key in keys: items = class_map[key] suffix = ".".join(Path(key).parent.relative_to(common_path).parts) package_name = f"{package}.{suffix}" if suffix else package mod_name = module_name(key) modules_map[(package_name, mod_name)].append(key) self.assign(items, package_name, mod_name) for locations in modules_map.values(): if len(locations) > 1: logger.warning( "Classes from different locations are designated to same module.\n" "You can resolve this if you switch to another `--structure-style`." "\n%s", "\n".join(locations), ) def group_by_namespace(self) -> None: """Group classes by their target namespace. Example: {myNS.tempuri.org}Root -> org.tempuri.myNS.py """ groups = collections.group_by(self.container, key=get_target_namespace) for namespace, classes in groups.items(): parts = self.combine_ns_package(namespace) module = parts.pop() package = ".".join(parts) self.assign(classes, package, module) def group_all_together(self) -> None: """Group all classes together in the same module.""" package_parts = self.container.config.output.package.split(".") module = package_parts.pop() package = ".".join(package_parts) self.assign(self.container, package, module) def group_by_strong_components(self) -> None: """Find circular imports and cluster their classes together. This grouping ideally creates a class per file, if there are circular imports, the classes will be grouped together. """ package = self.container.config.output.package for group in self.strongly_connected_classes(): classes = self.sort_classes(group) module = classes[0].name self.assign(classes, package, module) def group_by_namespace_clusters(self) -> None: """Group strongly connected classes together by namespaces.""" for group in self.strongly_connected_classes(): classes = self.sort_classes(group) namespaces = set(map(get_target_namespace, classes)) if len(namespaces) > 1: raise CodegenError( "Found strongly connected types from different namespaces", namespaces=namespaces, ) parts = self.combine_ns_package(classes[0].target_namespace) module = classes[0].name self.assign(classes, ".".join(parts), module) def sort_classes(self, qnames: set[str]) -> list[Class]: """Sort classes by their dependencies graph. Args: qnames: A set of qualified class names Returns: A class list in a safe to generate order. """ edges = { qname: set(self.container.first(qname).dependencies()).intersection(qnames) for qname in qnames } return [self.container.first(qname) for qname in toposort_flatten(edges)] def strongly_connected_classes(self) -> Iterator[set[str]]: """Compute strongly connected classes of a directed graph. Returns: A list of sets of qualified class names. """ edges = {obj.qname: list(set(obj.dependencies(True))) for obj in self.container} return strongly_connected_components(edges) @classmethod def assign(cls, classes: Iterable[Class], package: str, module: str): """Assign package and model to classes. It's important to assign the same for any inner/nested classes as well. """ for obj in classes: obj.package = package obj.module = module cls.assign(obj.inner, package, module) @classmethod def group_common_paths(cls, paths: Iterable[str]) -> list[list[str]]: """Group a list of file paths by their common paths. Args: paths: A list of file paths Returns: A list of file lists that belong to the same common path. """ prev = "" index = 0 groups = defaultdict(list) common_schemas_dir = COMMON_SCHEMA_DIR.as_uri() for path in sorted(paths): if path.startswith(common_schemas_dir): groups[0].append(path) else: path_parsed = urlparse(path) common_path = os.path.commonpath((prev, path)) if not common_path or common_path == path_parsed.scheme: index += 1 prev = path groups[index].append(path) return list(groups.values()) def combine_ns_package(self, namespace: str | None) -> list[str]: """Combine the output package with a namespace. You can add aliases to namespace uri with the substitutions configuration. Without Alias: urn:foo-bar:add -> ["generated", "bar", "foo", "add"] With Package Alias: urn:foo-bar:add -> add.again urn:foo-bar:add -> ["generated", "add", "again"] Returns: The package path as a list of strings. """ result = self.container.config.output.package.split(".") if namespace: substitution = collections.first( re.sub(sub.search, sub.replace, namespace) for sub in self.container.config.substitutions.substitution if sub.type == ObjectType.PACKAGE and re.fullmatch(sub.search, namespace) is not None ) else: substitution = None if substitution: result.extend(substitution.split(".")) else: result.extend(to_package_name(namespace).split(".")) return list(filter(None, result)) tefra-xsdata-cf7afe8/xsdata/codegen/handlers/detect_circular_references.py000066400000000000000000000055101526437326600273000ustar00rootroot00000000000000from collections.abc import Iterator from xsdata.codegen.mixins import ( ContainerInterface, RelativeHandlerInterface, ) from xsdata.codegen.models import AttrType, Class class DetectCircularReferences(RelativeHandlerInterface): """Accurately detect circular dependencies between classes. Args: container: The class container instance Attributes: reference_types: A map of class refs to dependency types """ __slots__ = "container", "reference_types" def __init__(self, container: ContainerInterface): """Initialize the class.""" super().__init__(container) self.reference_types: dict[int, list[AttrType]] = {} def process(self, target: Class) -> None: """Go through all the attr types and find circular references. Args: target: The class to inspect and update """ if not self.reference_types: self.build_reference_types() for attr in target.attrs: self.process_types(attr.types, target.ref) for choice in attr.choices: self.process_types(choice.types, target.ref) def process_types(self, types: list[AttrType], class_reference: int) -> None: """Go through the types and find circular references. Args: types: A list attr/choice type instances class_reference: The parent attr/choice class reference """ for tp in types: if not tp.forward and not tp.native and not tp.circular: tp.circular = self.is_circular(tp.reference, class_reference) def is_circular(self, start: int, stop: int) -> bool: """Detect if the start reference leads to the stop reference. The procedure is a dfs search to avoid max recursion errors. Args: start: The attr type reference stop: The parent class reference Returns: Whether the start reference leads back to the stop reference. """ path = set() stack = [start] while stack: if stop in path: return True ref = stack.pop() path.add(ref) stack.extend( tp.reference for tp in self.reference_types[ref] if not tp.circular and tp.reference not in path ) return stop in path def build_reference_types(self) -> None: """Build the reference types mapping.""" def generate(target: Class) -> Iterator[tuple[int, list[AttrType]]]: yield target.ref, [tp for tp in target.types() if tp.reference] for inner in target.inner: yield from generate(inner) for item in self.container: for ref, types in generate(item): self.reference_types[ref] = types tefra-xsdata-cf7afe8/xsdata/codegen/handlers/disambiguate_choices.py000066400000000000000000000225331526437326600261020ustar00rootroot00000000000000from collections import defaultdict from collections.abc import Iterator from xsdata.codegen.mixins import ContainerInterface, RelativeHandlerInterface from xsdata.codegen.models import Attr, AttrType, Class, Extension, Restrictions, Status from xsdata.models.enums import DataType, Tag from xsdata.utils import collections, text from xsdata.utils.constants import DEFAULT_ATTR_NAME from xsdata.utils.namespaces import build_qname class DisambiguateChoices(RelativeHandlerInterface): """Process choices with the same types and disambiguate them. Essentially, this handler creates intermediate simple and complex types to ensure not two elements in a compound field can have the same type. Args: container: The class container instance Attributes: unnest_classes: Specifies whether to create intermediate inner or outer classes. """ __slots__ = "unnest_classes" def __init__(self, container: ContainerInterface): """Initialize the class.""" super().__init__(container) self.unnest_classes = container.config.output.unnest_classes def process(self, target: Class) -> None: """Process the given class attrs if they contain choices. Args: target: The target class instance """ for attr in target.attrs: if attr.choices: self.process_compound_field(target, attr) def process_compound_field(self, target: Class, attr: Attr) -> None: """Process a compound field. A compound field can be created by a mixed wildcard with explicit children, or because we enabled the configuration to group repeatable choices. Steps: 1. Merge choices derived from xs:any elements 2. Find ambiguous choices and create intermediate classes 3. Reset the attr types if it's not a mixed wildcard. Args: target: The target class instance attr: An attr instance that contains choices """ self.merge_wildcard_choices(attr) for choice in self.find_ambiguous_choices(attr): self.disambiguate_choice(target, choice) @classmethod def merge_wildcard_choices(cls, attr: Attr): """Merge choices derived from xs:any elements. It's a compound field it doesn't make sense to have multiple wildcard choices. Merge them together. Args: attr: The attr instance that contains choices """ choices = [] namespaces = [] min_occurs = 0 max_occurs = 0 has_wildcard = False for choice in attr.choices: if choice.is_wildcard: min_occurs += choice.restrictions.min_occurs or 0 max_occurs += choice.restrictions.max_occurs or 0 namespaces.append(choice.namespace) has_wildcard = True else: choices.append(choice) attr.choices = choices if has_wildcard: attr.choices.append( Attr( name="content", types=[AttrType(qname=str(DataType.ANY_TYPE), native=True)], tag=Tag.ANY, namespace=" ".join( collections.unique_sequence(filter(None, namespaces)) ), restrictions=Restrictions( min_occurs=min_occurs, max_occurs=max_occurs ), ) ) @classmethod def find_ambiguous_choices(cls, attr: Attr) -> Iterator[Attr]: """Find choices with the same types. Args: attr: The attr instance with the choices. Yields: An iterator of the ambiguous choices, except wildcards. """ groups = defaultdict(list) for index, choice in enumerate(attr.choices): for tp in choice.types: dt = tp.datatype if dt: groups[dt.type.__name__].append(index) else: groups[tp.qname].append(index) ambiguous = set() for indexes in groups.values(): if len(indexes) > 1: ambiguous.update(indexes) for index in ambiguous: choice = attr.choices[index] if not choice.is_wildcard: yield choice def disambiguate_choice(self, target: Class, choice: Attr) -> None: """Create intermediate class for the given choice. Scenarios: 1. Choice is derived from xs:anyType 2. Choice is derived from a xs:anySimpleType 3. Choice is a reference to xs:complexType or element Args: target: The target class instance choice: The ambiguous choice attr instance """ is_circular = choice.is_circular_ref inner = not self.unnest_classes and not is_circular ref_class = self.create_ref_class(target, choice, inner=inner) if choice.is_any_type: self.add_any_type_value(ref_class, choice) elif self.is_simple_type(choice): self.add_simple_type_value(ref_class, choice) else: self.add_extension(ref_class, choice) choice.restrictions = Restrictions( min_occurs=choice.restrictions.min_occurs, max_occurs=choice.restrictions.max_occurs, ) ref_type = AttrType( qname=ref_class.qname, reference=id(ref_class), forward=inner, circular=is_circular, ) choice.types = [ref_type] if not inner: self.container.add(ref_class) else: ref_class.parent = target target.inner.append(ref_class) def is_simple_type(self, choice: Attr) -> bool: """Return whether the choice attr is a simple type reference.""" if any(tp.native for tp in choice.types): return True source = self.container.find(choice.types[0].qname) return bool(source and source.is_enumeration) def create_ref_class(self, source: Class, choice: Attr, inner: bool) -> Class: """Create an intermediate class for the given choice. If the reference class is going to be inner, ensure the class name is unique, otherwise we will still end-up with ambiguous choices. Args: source: The source class instance choice: The ambiguous choice attr instance inner: Specifies if the reference class will be inner """ name = choice.name if inner: name = self.next_available_name(source, name) return Class( qname=build_qname(choice.namespace, name), status=Status.RAW, tag=Tag.ELEMENT, local_type=True, location=source.location, ns_map=source.ns_map, nillable=choice.restrictions.nillable or False, ) @classmethod def next_available_name(cls, parent: Class, name: str) -> str: """Find the next available name for an inner class. Args: parent: The parent class instance name: The name of the inner class Returns: The next available class name by adding a integer suffix. """ reserved = {text.alnum(inner.name) for inner in parent.inner} index = 0 new_name = name while True: cmp = text.alnum(new_name) if cmp not in reserved: return new_name index += 1 new_name = f"{name}_{index}" @classmethod def add_any_type_value(cls, reference: Class, choice: Attr): """Add a simple any type content value attr to the reference class. Args: reference: The reference class instance choice: The source choice attr instance """ attr = Attr( name="content", types=[AttrType(qname=str(DataType.ANY_TYPE), native=True)], tag=Tag.ANY, namespace=choice.namespace, restrictions=Restrictions(min_occurs=0, max_occurs=1), ) reference.attrs.append(attr) @classmethod def add_simple_type_value(cls, reference: Class, choice: Attr): """Add a simple type content value attr to the reference class. Args: reference: The reference class instance choice: The source choice attr instance """ new_attr = Attr( tag=Tag.EXTENSION, name=DEFAULT_ATTR_NAME, namespace=None, restrictions=choice.restrictions.clone( min_occurs=1, max_occurs=1, path=[], nillable=False, ), types=[tp.clone() for tp in choice.types], ) reference.attrs.append(new_attr) @classmethod def add_extension(cls, reference: Class, choice: Attr): """Add an extension to the reference class from the choice type. Args: reference: The reference class instance choice: The source choice attr instance """ extension = Extension( tag=Tag.EXTENSION, type=choice.types[0].clone(forward=False, circular=False), restrictions=Restrictions(), ) reference.extensions.append(extension) tefra-xsdata-cf7afe8/xsdata/codegen/handlers/filter_classes.py000066400000000000000000000024201526437326600247420ustar00rootroot00000000000000from xsdata.codegen.mixins import ContainerHandlerInterface from xsdata.codegen.models import Class from xsdata.logger import logger class FilterClasses(ContainerHandlerInterface): """Filter classes for code generation based on the configuration strategy.""" __slots__ = () def run(self) -> None: """Main entrypoint to filter the class container. In order for a class to be considered global it has to be derived from an element, complexType, a binding operation or message If no global types exist, all classes will be generated. """ classes = self.filter_all_globals() if classes: self.container.set(classes) else: logger.warning("No global types exist, will generate all types.") def filter_all_globals(self) -> list[Class]: """Filter all globals and any referenced types. This filter is trying to remove unused simple types. Returns: The list of classes for generation. """ occurs = set() for obj in self.container: if obj.is_complex_type: occurs.add(obj.ref) occurs.update(obj.references) return [obj for obj in self.container if obj.ref in occurs] tefra-xsdata-cf7afe8/xsdata/codegen/handlers/flatten_attribute_groups.py000066400000000000000000000036661526437326600270740ustar00rootroot00000000000000from xsdata.codegen.exceptions import CodegenError from xsdata.codegen.mixins import RelativeHandlerInterface from xsdata.codegen.models import Attr, Class, Status from xsdata.codegen.utils import ClassUtils class FlattenAttributeGroups(RelativeHandlerInterface): """Replace groups and attGroups with the source class attributes.""" __slots__ = () def process(self, target: Class) -> None: """Iterate over all group attributes and apply handler logic. Group attributes can refer to attributes or other group attributes, repeat until there is no group attribute left. Args: target: The target class instance to inspect and process """ repeat = False for attr in target.attrs.copy(): if attr.is_group: repeat = True self.process_attribute(target, attr) if repeat: self.process(target) def process_attribute(self, target: Class, attr: Attr) -> None: """Process a group/attributeGroup attr. Steps: 1. Find the source class by the attr type and tag 2. If the attr is circular reference, remove the attr 3. Otherwise, copy all source attrs to the target class Args: target: The target class instance attr: The group attr to flatten Raises: AnalyzerValueError: if source class is not found. """ qname = attr.types[0].qname # group attributes have one type only. source = self.container.find(qname, condition=lambda x: x.tag == attr.tag) if not source: raise CodegenError("Unknown group reference", tag=attr.tag, qname=qname) if source is target: ClassUtils.remove_attribute(target, attr) else: is_circular_ref = source.status == Status.UNGROUPING ClassUtils.copy_group_attributes(source, target, attr, is_circular_ref) tefra-xsdata-cf7afe8/xsdata/codegen/handlers/flatten_class_extensions.py000066400000000000000000000325131526437326600270470ustar00rootroot00000000000000from xsdata.codegen.mixins import RelativeHandlerInterface from xsdata.codegen.models import Attr, AttrType, Class, Extension from xsdata.codegen.utils import ClassUtils from xsdata.logger import logger from xsdata.models.enums import DataType, NamespaceType, Tag from xsdata.utils.constants import DEFAULT_ATTR_NAME class FlattenClassExtensions(RelativeHandlerInterface): """Reduce class extensions by copying or creating new attributes.""" __slots__ = () def process(self, target: Class) -> None: """Process a class' extensions. Args: target: The target class instance """ for extension in target.extensions.copy(): self.process_extension(target, extension) def process_extension(self, target: Class, extension: Extension) -> None: """Process a class extension. Slit the process to native xsd extensions and user defined types. Args: target: The target class instance extension: The class extension instance """ if extension.type.native: self.process_native_extension(target, extension) else: self.process_dependency_extension(target, extension) @classmethod def process_native_extension(cls, target: Class, extension: Extension): """Native type flatten handler. In case of enumerations copy the native data type to all enum members, otherwise add a default text attr with the extension attributes. Args: target: The target class instance extension: The class extension instance """ if target.is_enumeration: cls.replace_attributes_type(target, extension) else: cls.add_default_attribute(target, extension) def process_dependency_extension(self, target: Class, extension: Extension) -> None: """Process user defined extension types. Case: - Extension source is missing - Target class is an enumeration - Extension source is a simple type or an enumeration - Extension source is a complex type Args: target: The target class instance extension: The class extension instance """ source = self.find_dependency(extension.type) if not source: logger.warning("Missing extension type: %s", extension.type.name) target.extensions.remove(extension) elif target.is_enumeration: self.process_enum_extension(source, target, extension) elif not source.is_complex_type or source.is_enumeration: self.process_simple_extension(source, target, extension) else: self.process_complex_extension(source, target, extension) def process_enum_extension( self, source: Class, target: Class, extension: Extension | None, ) -> None: """Process an enumeration class extension. Cases: 1. Source is an enumeration: merge them 2. Source is a simple type: copy all source attr types 3. Source is a complex type 3.1 Target has a single member: Restrict default value 3.2 Target has multiple members: unsupported reset enumeration Args: source: The source class instance target: The target class instance extension: The class extension instance """ if source.is_enumeration: self.merge_enumerations(source, target) elif not source.is_complex_type: self.merge_enumeration_types(source, target) elif len(target.attrs) == 1: self.set_default_value(source, target) else: # We can't subclass and override the value field # the target enumeration, mypy doesn't play nicely. target.attrs.clear() if extension: if target.is_enumeration: target.extensions.remove(extension) else: extension.type.reference = source.ref @classmethod def merge_enumerations(cls, source: Class, target: Class): """Merge enumeration members from source to target class. Args: source: The source class instance target: The target class instance """ source_attrs = {attr.name: attr for attr in source.attrs} target.attrs = [ source_attrs[attr.name].clone() if attr.name in source_attrs else attr for attr in target.attrs ] def merge_enumeration_types(self, source: Class, target: Class) -> None: """Merge the enumeration attr types and restrictions. Args: source: The source class instance target: The target class instance """ source_attr = source.attrs[0] for tp in source_attr.types: if tp.native: for target_attr in target.attrs: target_attr.types.append(tp.clone()) target_attr.restrictions.merge(source_attr.restrictions) else: base = self.find_dependency(tp) # It's impossible to have a missing reference now, the # source class has passed through AttributeTypeHandler # and any missing types have been reset. assert base is not None self.process_enum_extension(base, target, None) @classmethod def set_default_value(cls, source: Class, target: Class): """Set the default value from the source single enumeration. When a simple type is a restriction of an enumeration with only one member, we can safely set its default value to that member value as fixed. Args: source: The source class instance target: The target class instance """ new_attr = ClassUtils.find_value_attr(source).clone() new_attr.types = target.attrs[0].types new_attr.default = target.attrs[0].default new_attr.fixed = True target.attrs = [new_attr] @classmethod def process_simple_extension(cls, source: Class, target: Class, ext: Extension): """Process simple type extensions. Cases: 1. If target is source: drop the extension. 2. If source is enumeration and target isn't create default value attribute. 3. If both source and target are enumerations copy all attributes. 4. If target is enumeration: drop the extension. Args: source: The source class instance target: The target class instance ext: The extension class instance """ if source is target: target.extensions.remove(ext) elif source.is_enumeration and not target.is_enumeration: cls.add_default_attribute(target, ext) elif source.is_enumeration == target.is_enumeration: ClassUtils.copy_attributes(source, target, ext) else: # this is an enumeration target.extensions.remove(ext) @classmethod def process_complex_extension(cls, source: Class, target: Class, ext: Extension): """Process complex type extensions. Compare source and target classes and either remove the extension completely, copy all source attributes to the target class or leave the extension alone. Args: source: The source class instance target: The target class instance ext: The extension class instance """ if cls.should_remove_extension(source, target, ext): target.extensions.remove(ext) elif cls.should_flatten_extension(source, target): ClassUtils.copy_attributes(source, target, ext) else: ext.type.reference = id(source) def find_dependency(self, attr_type: AttrType) -> Class | None: """Find dependency for the given extension type with priority. Search priority: xs:SimpleType > xs:ComplexType Args: attr_type: The attr type instance Returns: The class instance or None if it's undefined. """ conditions = ( lambda x: x.tag == Tag.SIMPLE_TYPE, lambda x: x.tag == Tag.COMPLEX_TYPE, ) for condition in conditions: result = self.container.find(attr_type.qname, condition=condition) if result: return result return None @classmethod def should_remove_extension( cls, source: Class, target: Class, extension: Extension, ) -> bool: """Return whether the extension should be removed. Violations: - Circular Reference - Forward Reference - Unordered sequences - MRO Violation A(B), C(B) and extensions includes A, B, C Args: source: The source class instance target: The target class instance extension: The extension class instance """ if ( source is target or target in source.inner or cls.have_unordered_sequences(source, target, extension) ): return True # MRO Violation collision = {ext.type.qname for ext in target.extensions} return any(ext.type.qname in collision for ext in source.extensions) @classmethod def should_flatten_extension(cls, source: Class, target: Class) -> bool: """Return whether the extension should be flattened. Rules: 1. Source doesn't have a parent class 2. Source class is a simple type 3. Source class has a suffix attr and target has its own attrs 4. Target class has a suffix attr Args: source: The source class instance target: The target class instance """ return bool( not source.extensions and ( not source.is_complex_type or target.has_suffix_attr or (source.has_suffix_attr and target.attrs) ) ) @classmethod def have_unordered_sequences( cls, source: Class, target: Class, extension: Extension, ) -> bool: """Validate overriding sequence attrs are in order. Dataclasses fields ordering follows the python mro pattern, the parent fields are always first, and they are updated if the subclass is overriding any of them but the overall ordering doesn't change! @todo This needs a complete rewrite and most likely it needs to @todo move way down in the process chain. Args: source: The source class instance target: The target class instance extension: The extension class instance """ if extension.tag == Tag.EXTENSION or source.extensions: return False sequence = [ attr.name for attr in target.attrs if attr.restrictions.sequence is not None and not attr.is_prohibited ] if len(sequence) > 1: compare = [attr.name for attr in source.attrs if attr.name in sequence] if compare and compare != sequence: return True return False @classmethod def replace_attributes_type(cls, target: Class, extension: Extension): """Replace all attrs types with the extension's type. The extension is a native xsd datatype. Args: target: The target class instance extension: The extension class instance """ target.extensions.remove(extension) for attr in target.attrs: attr.types.clear() attr.types.append(extension.type.clone()) @classmethod def add_default_attribute(cls, target: Class, extension: Extension): """Convert extension to a value text attr. If the extension type is xs:anyType convert the attr into a wildcard attr to match everything. Args: target: The target class instance extension: The extension class instance """ if extension.type.datatype != DataType.ANY_TYPE: tag = Tag.EXTENSION name = DEFAULT_ATTR_NAME namespace = None else: tag = Tag.ANY name = "@any_element" namespace = NamespaceType.ANY_NS attr = cls.get_or_create_attribute(target, name, tag) attr.types.append(extension.type.clone()) attr.restrictions.merge(extension.restrictions) attr.namespace = namespace target.extensions.remove(extension) @classmethod def get_or_create_attribute(cls, target: Class, name: str, tag: str) -> Attr: """Find or create an attr with the given name and tag. If the attr doesn't exist, create a new required attr and prepend it in the attrs list. Args: target: The target class instance name: The attr name tag: The attr tag name """ attr = ClassUtils.find_attr(target, name) if attr is None or attr.tag == Tag.ATTRIBUTE: attr = Attr(name=name, tag=tag) attr.restrictions.min_occurs = 1 attr.restrictions.max_occurs = 1 target.attrs.insert(0, attr) return attr tefra-xsdata-cf7afe8/xsdata/codegen/handlers/merge_attributes.py000066400000000000000000000060211526437326600253060ustar00rootroot00000000000000from xsdata.codegen.mixins import HandlerInterface from xsdata.codegen.models import Attr, Class from xsdata.codegen.utils import ClassUtils from xsdata.utils import collections class MergeAttributes(HandlerInterface): """Merge same type attr and their restrictions.""" __slots__ = () @classmethod def process(cls, target: Class): """Detect and process duplicate attributes. Cases: - Enumeration: remove duplicates - Otherwise: merge attrs and Args: target: The target class instance """ if target.is_enumeration: cls.filter_duplicate_attrs(target) else: cls.merge_duplicate_attrs(target) @classmethod def filter_duplicate_attrs(cls, target: Class): """Removes duplicate attrs. Args: target: The target class instance """ attrs = collections.unique_sequence(target.attrs, key="default") target.attrs = attrs @classmethod def merge_duplicate_attrs(cls, target: Class): """Find duplicate attrs and merge them. In order for two attrs to be considered duplicates, they must have the same name, namespace and tag. Args: target: The target class instance """ result: list[Attr] = [] for attr in target.attrs: pos = collections.find(result, attr) existing = result[pos] if pos > -1 else None if not existing: result.append(attr) elif not (attr.is_attribute or attr.is_enumeration): existing.help = existing.help or attr.help e_res = existing.restrictions a_res = attr.restrictions min_occurs = e_res.min_occurs or 0 max_occurs = e_res.max_occurs or 1 attr_min_occurs = a_res.min_occurs or 0 attr_max_occurs = a_res.max_occurs or 1 e_res.min_occurs = min(min_occurs, attr_min_occurs) # Determine if attrs are mutually exclusive (max) or additive (sum) is_mutually_exclusive = ( e_res.choice is not None and a_res.choice is not None and existing.index != attr.index and ( # Same choice, different branches e_res.choice == a_res.choice # Different sibling choices at same depth or len(e_res.path) == len(a_res.path) ) ) if is_mutually_exclusive: e_res.max_occurs = max(max_occurs, attr_max_occurs) else: e_res.max_occurs = max_occurs + attr_max_occurs if a_res.sequence is not None: e_res.sequence = a_res.sequence existing.fixed = False existing.types.extend(attr.types) target.attrs = result ClassUtils.cleanup_class(target) tefra-xsdata-cf7afe8/xsdata/codegen/handlers/merge_duplicate_classes.py000066400000000000000000000041731526437326600266150ustar00rootroot00000000000000from xsdata.codegen.mixins import ContainerHandlerInterface, ContainerInterface from xsdata.codegen.models import ( Class, ) class MergeDuplicateClasses(ContainerHandlerInterface): """Remove duplicate classes. Args: container: The class container instance Attributes: merges: The merge instructions applied at the end """ __slots__ = ("merges",) def __init__(self, container: ContainerInterface): """Initialize the rename duplicate class handler.""" super().__init__(container) self.merges: dict[int, int] = {} def run(self) -> None: """Detect and merge duplicate classes conflicts.""" for classes in self.group_duplicate_classes(): if len(classes) > 1: self.merge_classes(classes) if self.merges: for target in self.container: self.update_references(target) def group_duplicate_classes(self) -> list[list[Class]]: """Group duplicate classes. This is very slow, because our gen models are unhashable. """ groups: list[list[Class]] = [] for cls in self.container: found = False for group in groups: if cls == group[0]: group.append(cls) found = True break if not found: groups.append([cls]) return groups def merge_classes(self, classes: list[Class]) -> None: """Remove the duplicate classes and schedule updates. Args: classes: A list of duplicate classes """ keep = classes.pop() replace = keep.ref self.container.remove(*classes) for item in classes: self.merges[item.ref] = replace def update_references(self, target: Class) -> None: """Search and update the target class for merged references. Args: target: The target class instance to inspect and update """ for tp in target.types(): if tp.reference in self.merges: tp.reference = self.merges[tp.reference] tefra-xsdata-cf7afe8/xsdata/codegen/handlers/process_attributes_types.py000066400000000000000000000262241526437326600271200ustar00rootroot00000000000000from collections.abc import Callable from xsdata.codegen.mixins import ContainerInterface, RelativeHandlerInterface from xsdata.codegen.models import Attr, AttrType, Class from xsdata.codegen.utils import ClassUtils from xsdata.logger import logger from xsdata.models.enums import DataType, Tag from xsdata.utils import collections class ProcessAttributeTypes(RelativeHandlerInterface): """Minimize class attrs complexity by filtering and flattening types. Args: container: The container instance Attributes: dependencies: Class qname dependencies mapping """ __slots__ = "dependencies" def __init__(self, container: ContainerInterface): """Initialize the class.""" super().__init__(container) self.dependencies: dict = {} def process(self, target: Class) -> None: """Process the given class attrs and their types. Cascades class restrictions to class attrs. Args: target: The target class instance """ for attr in target.attrs.copy(): self.process_types(target, attr) self.cascade_properties(target, attr) def process_types(self, target: Class, attr: Attr) -> None: """Process every attr type and filter out duplicates. Args: target: The target class instance attr: The attr instance """ if self.container.config.output.ignore_patterns: attr.restrictions.pattern = None for attr_type in attr.types.copy(): self.process_type(target, attr, attr_type) attr.types = ClassUtils.filter_types(attr.types) @classmethod def cascade_properties(cls, target: Class, attr: Attr): """Cascade class properties to the attr if it's a text node. Properties: - Default value - Fixed flag - Nillable flag Args: target: The target class instance attr: The attr instance """ if attr.xml_type is None: if target.default is not None and attr.default is None: attr.default = target.default attr.fixed = target.fixed if target.nillable: attr.restrictions.nillable = True def process_type(self, target: Class, attr: Attr, attr_type: AttrType) -> None: """Process attr type. Cases: - Attr type is a native xsd type - Attr type is a forward reference (inner class) - Attr type is a complex user defined type Args: target: The target class instance attr: The attr instance attr_type: The attr type instance """ if attr_type.native: self.process_native_type(attr, attr_type) elif attr_type.forward: self.process_inner_type(target, attr, attr_type) else: self.process_dependency_type(target, attr, attr_type) @classmethod def process_native_type(cls, attr: Attr, attr_type: AttrType): """Process native xsd types. Cascade the datatype restrictions to the attr and also resets the type to a simple xsd:string if there is a pattern restriction. Args: attr: The attr instance attr_type: The attr type instance """ datatype = attr_type.datatype assert datatype is not None cls.update_restrictions(attr, datatype) if attr.restrictions.pattern: cls.reset_attribute_type(attr_type) def find_dependency( self, target: Class, attr_type: AttrType, tag: str ) -> Class | None: """Find the source type from the attr type and tag. Avoid conflicts by selecting any matching type by qname and preferably: 1. Match the attr type reference, if it's already set 2. Match element again complexType with no self reference 3. Match the candidate object tag 4. Match non element and complexType 5. Anything Args: target: The target class instance attr_type: The attr type instance tag: The xml tag name, e.g. Element, Attribute, ComplexType Returns: The source class or None if no match is found """ conditions: tuple[Callable[[Class], bool], ...] if attr_type.reference: conditions = (lambda obj: obj.ref == attr_type.reference,) else: conditions = ( lambda obj: ( tag == Tag.ELEMENT and obj.tag == Tag.COMPLEX_TYPE and obj is not target ), lambda obj: obj.tag == tag, lambda obj: not obj.is_complex_type, lambda x: True, ) for condition in conditions: result = self.container.find(attr_type.qname, condition=condition) if result: return result return None def process_inner_type( self, target: Class, attr: Attr, attr_type: AttrType ) -> None: """Process an attr type that depends on a simple inner type. Skip If the source class is not simple type, or it's a circular reference. Args: target: The target class instance attr: The attr instance attr_type: The attr type instance """ if attr_type.circular: attr_type.reference = target.ref return inner = self.container.find_inner(target, attr_type.qname) # If the inner class behaves as a simple type flatten it. if ( len(inner.attrs) == 1 and not inner.attrs[0].xml_type and not inner.extensions ): self.copy_attribute_properties(inner, target, attr, attr_type) target.inner.remove(inner) else: attr_type.reference = inner.ref def process_dependency_type( self, target: Class, attr: Attr, attr_type: AttrType ) -> None: """Process an attr type that depends on any global type. Strategies: 1. Reset absent types with a warning 3. Copy format restriction from an enumeration 2. Copy attribute properties from a simple type 4. Set circular flag for the rest Args: target: The target class instance attr: The attr instance attr_type: The attr type instance """ source = self.find_dependency(target, attr_type, attr.tag) if not source: logger.warning("Reset absent type: %s", attr_type.name) self.reset_attribute_type(attr_type) elif source.is_enumeration: attr.restrictions.min_length = None attr.restrictions.max_length = None attr.restrictions.format = collections.first( x.restrictions.format for x in source.attrs if x.restrictions.format ) attr_type.reference = id(source) elif not source.is_complex_type: self.copy_attribute_properties(source, target, attr, attr_type) elif source.is_element and source.abstract: # Substitution groups with abstract elements are used like # placeholders and shouldn't be added as standalone fields. ClassUtils.remove_attribute(target, attr) else: if source.nillable: attr.restrictions.nillable = True attr_type.reference = id(source) self.detect_lazy_namespace(source, target, attr) @classmethod def copy_attribute_properties( cls, source: Class, target: Class, attr: Attr, attr_type: AttrType, ): """Replace the attr type with the types of the first attr in the source class. Ignore enumerations and gracefully handle dump types with no attrs. Args: source: The source class instance target: The target class instance attr: The attr instance attr_type: The attr type instance Raises: AnalyzerValueError: if the source class has more than one attributes """ if not source.attrs: logger.warning("Reset absent simple type: %s", attr_type.name) cls.reset_attribute_type(attr_type) return source_attr = source.attrs[0] index = attr.types.index(attr_type) attr.types.pop(index) for source_attr_type in source_attr.types: clone_type = source_attr_type.clone() attr.types.insert(index, clone_type) index += 1 ClassUtils.copy_inner_class(source, target, clone_type) restrictions = source_attr.restrictions.clone() restrictions.merge(attr.restrictions) # Maintain occurrences no matter what! restrictions.min_occurs = attr.restrictions.min_occurs restrictions.max_occurs = attr.restrictions.max_occurs if source.nillable: restrictions.nillable = True attr.restrictions = restrictions attr.help = attr.help or source_attr.help attr.fixed = attr.fixed or source_attr.fixed attr.default = attr.default or source_attr.default @classmethod def reset_attribute_type(cls, attr_type: AttrType): """Reset the attribute type to string. The method will also unset the circular/forward flags, as native types only depend on python builtin types. Args: attr_type: The attr type instance to reset """ attr_type.qname = str(DataType.STRING) attr_type.native = True attr_type.circular = False attr_type.forward = False @classmethod def update_restrictions(cls, attr: Attr, datatype: DataType): """Helper method to copy the native datatype restriction to the attr. Sets: - The format restriction, e.g. hexBinary, base64Binary - The tokens flag for xs:NMTOKENS and xs:IDREFS Args: attr: The attr to update datatype: The datatype to extract the restrictions. """ attr.restrictions.format = datatype.format if datatype in (DataType.NMTOKENS, DataType.IDREFS): attr.restrictions.tokens = True @classmethod def detect_lazy_namespace(cls, source: Class, target: Class, attr: Attr): """Set the attr namespace if the current is marked as lazy. Cases: WSDL message part type can be an element, complex or simple type, we can't do the detection during the initial mapping to class objects. Args: source: The source class instance target: The target class instance attr: The target class attr instance """ if attr.namespace == "##lazy": logger.warning( "Overriding field type namespace %s:%s (%s)", target.name, attr.name, source.namespace, ) if not source.namespace: attr.namespace = "" if target.namespace else None else: attr.namespace = source.namespace tefra-xsdata-cf7afe8/xsdata/codegen/handlers/process_mixed_content_class.py000066400000000000000000000026321526437326600275300ustar00rootroot00000000000000import sys from xsdata.codegen.mixins import HandlerInterface from xsdata.codegen.models import Attr, AttrType, Class, Restrictions from xsdata.models.enums import DataType, NamespaceType, Tag class ProcessMixedContentClass(HandlerInterface): """Mixed content handler.""" __slots__ = () def process(self, target: Class) -> None: """Add a wildcard attr if the class supports mixed content. All other elements will be moved as the wildcard attr choices. Args: target: The target class instance """ if not target.is_mixed: return attrs = [] choices = [] for attr in target.attrs.copy(): if attr.is_attribute: attrs.append(attr) elif not attr.is_any_type: choice = attr.clone() choice.restrictions.min_occurs = None choice.restrictions.max_occurs = None choice.restrictions.sequence = None choices.append(choice) wildcard = Attr( name="content", types=[AttrType(qname=str(DataType.ANY_TYPE), native=True)], tag=Tag.ANY, mixed=True, choices=choices, namespace=NamespaceType.ANY_NS, restrictions=Restrictions(min_occurs=0, max_occurs=sys.maxsize), ) attrs.append(wildcard) target.attrs = attrs tefra-xsdata-cf7afe8/xsdata/codegen/handlers/rename_duplicate_attributes.py000066400000000000000000000007531526437326600275160ustar00rootroot00000000000000from xsdata.codegen.mixins import HandlerInterface from xsdata.codegen.models import Class from xsdata.codegen.utils import ClassUtils class RenameDuplicateAttributes(HandlerInterface): """Resolve attr name conflicts defined in the class.""" __slots__ = () def process(self, target: Class) -> None: """Detect and resolve naming conflicts. Args: target: The target class instance """ ClassUtils.rename_duplicate_attributes(target) tefra-xsdata-cf7afe8/xsdata/codegen/handlers/rename_duplicate_classes.py000066400000000000000000000131531526437326600267630ustar00rootroot00000000000000from xsdata.codegen.mixins import ContainerHandlerInterface, ContainerInterface from xsdata.codegen.models import ( Attr, Class, get_location, get_name, get_qname, ) from xsdata.models.config import StructureStyle from xsdata.utils import collections, namespaces, text REQUIRE_UNIQUE_NAMES = (StructureStyle.SINGLE_PACKAGE, StructureStyle.CLUSTERS) class RenameDuplicateClasses(ContainerHandlerInterface): """Resolve class name conflicts depending on the output structure style. Args: container: The class container instance Attributes: use_names: Whether names or qualified names should be used to group classes renames: The renamed instructions applied at the end reserved: The reserved class names or qualified names """ __slots__ = ("merges", "renames", "reserved", "use_names") def __init__(self, container: ContainerInterface): """Initialize the rename duplicate class handler.""" super().__init__(container) self.use_names = self.should_use_names() self.renames: dict[int, str] = {} self.merges: dict[int, int] = {} self.reserved: set[str] = set() def run(self) -> None: """Detect and resolve class name conflicts.""" getter = get_name if self.use_names else get_qname groups = collections.group_by(self.container, lambda x: text.alnum(getter(x))) for classes in groups.values(): if len(classes) > 1: self.rename_classes(classes) if self.renames or self.merges: for target in self.container: self.update_references(target) def should_use_names(self) -> bool: """Determine if names or qualified names should be used for detection. Strict unique names: - Single package - Clustered packages - All classes have the same source location. """ return ( self.container.config.output.structure_style in REQUIRE_UNIQUE_NAMES or len(set(map(get_location, self.container))) == 1 ) def rename_classes(self, classes: list[Class]) -> None: """Rename the classes in the list. Cases: - Two classes, one abstract append the abstract suffix - One element, add numeric suffixes to the rest of the classes - Add numeric suffixes to all classes. Args: classes: A list of classes with duplicate names """ abstract = [x for x in classes if x.abstract] if len(classes) == 2 and len(abstract) == 1: self.add_abstract_suffix(abstract[0]) else: total_elements = sum(x.is_element for x in classes) for target in sorted(classes, key=get_name): if not target.is_element or total_elements > 1: self.add_numeric_suffix(target) def add_abstract_suffix(self, target: Class) -> None: """Add the abstract suffix to class name.""" new_qname = f"{target.qname}_abstract" self.rename_class(target, new_qname) def add_numeric_suffix(self, target: Class) -> None: """Find the next available class name. Save the original name in the class metadata and update the class qualified name and all classes that depend on the target class. Args: target: The target class instance to rename """ namespace, name = namespaces.split_qname(target.qname) new_qname = self.next_qname(namespace, name) self.rename_class(target, new_qname) def rename_class(self, target: Class, new_qname: str) -> None: """Update the class qualified name and schedule updates. Args: target: The target class to update new_qname: The new class qualified name """ qname = target.qname target.qname = new_qname target.meta_name = namespaces.local_name(qname) self.container.reset(target, qname) self.renames[target.ref] = new_qname def next_qname(self, namespace: str, name: str) -> str: """Use int suffixes to get the next available qualified name. Args: namespace: The class namespace name: The class name """ index = 0 reserved = self.get_reserved() while True: index += 1 new_name = f"{name}_{index}" qname = namespaces.build_qname(namespace, new_name) cmp = text.alnum(new_name if self.use_names else qname) if cmp not in reserved: reserved.add(cmp) return qname def get_reserved(self) -> set[str]: """Build the reserved names or qualified names of the container.""" if not self.reserved: getter = get_name if self.use_names else get_qname self.reserved = {text.alnum(getter(obj)) for obj in self.container} return self.reserved def update_references(self, target: Class) -> None: """Search and update the target class for renamed references. Args: target: The target class instance to inspect and update """ for parent, tp in target.types_with_parents(): if tp.reference in self.renames: tp.qname = self.renames[tp.reference] if ( isinstance(parent, Attr) and isinstance(parent.default, str) and parent.default.startswith("@enum@") ): members = text.suffix(parent.default, "::") parent.default = f"@enum@{tp.qname}::{members}" tefra-xsdata-cf7afe8/xsdata/codegen/handlers/reset_attribute_sequence_numbers.py000066400000000000000000000031471526437326600305770ustar00rootroot00000000000000from collections import defaultdict from xsdata.codegen.mixins import RelativeHandlerInterface from xsdata.codegen.models import Class class ResetAttributeSequenceNumbers(RelativeHandlerInterface): """Reset attrs sequence numbers. The sequence numbers are the ids of xs:sequence elements, because up until now it was important to determine which child/parent attrs belong to different sequence numbers. Before we generate the classes let's reset them to simple auto increment numbers per class. """ __slots__ = () def process(self, target: Class) -> None: """Process entrypoint for classes. Args: target: The target class instance """ groups = defaultdict(list) for attr in target.attrs: if attr.restrictions.sequence: groups[attr.restrictions.sequence].append(attr) if groups: next_sequence_number = self.find_next_sequence_number(target) for attrs in groups.values(): for attr in attrs: attr.restrictions.sequence = next_sequence_number next_sequence_number += 1 def find_next_sequence_number(self, target: Class) -> int: """Calculate the next sequence number from the base classes. Args: target: The target class instance Returns: The next sequence number """ base_attrs = self.base_attrs(target) sequences = (attr.restrictions.sequence or 0 for attr in base_attrs) max_sequence = max(sequences, default=0) return max_sequence + 1 tefra-xsdata-cf7afe8/xsdata/codegen/handlers/reset_attribute_sequences.py000066400000000000000000000031371526437326600272260ustar00rootroot00000000000000from xsdata.codegen.mixins import HandlerInterface from xsdata.codegen.models import Attr, Class, get_restriction_sequence from xsdata.utils import collections class ResetAttributeSequences(HandlerInterface): """Inspect a class for non-repeatable choices and unset the sequence number.""" __slots__ = () def process(self, target: Class) -> None: """Process entrypoint for classes. Reset Cases: - A sequence only contains one attr - The sequence includes attrs with max_occurs==1 Args: target: The target class instance """ groups = collections.group_by(target.attrs, get_restriction_sequence) for sequence, attrs in groups.items(): if not sequence: continue if len(attrs) == 1: attrs[0].restrictions.sequence = None else: for attr in attrs: if not self.is_repeatable_sequence(attr): attr.restrictions.sequence = None @classmethod def is_repeatable_sequence(cls, attr: Attr) -> bool: """Determine if the given attr is repeatable. Repeatable means max_occurs > 1 Args: attr: The attr instance Returns: The bool result """ seq = attr.restrictions.sequence if seq: for path in attr.restrictions.path: if path[0] == "s" and path[1] == seq: return path[3] > 1 if path else False if path[3] > 1: return True return False tefra-xsdata-cf7afe8/xsdata/codegen/handlers/sanitize_attributes_default_value.py000066400000000000000000000205401526437326600307370ustar00rootroot00000000000000from xsdata.codegen.mixins import RelativeHandlerInterface from xsdata.codegen.models import Attr, AttrType, Class from xsdata.formats.converter import converter from xsdata.logger import logger from xsdata.models.enums import DataType class SanitizeAttributesDefaultValue(RelativeHandlerInterface): """Sanitize attributes default values. Cases: 1. Ignore enumerations. 2. List fields can not have a default value 3. Optional choice/sequence fields can not have a default value 4. xsi:type fields are ignored, mark them as optional 5. Convert string literal default value for enum fields. """ __slots__ = () def process(self, target: Class) -> None: """Process entrypoint for classes. Inspect all attrs and attr choices. Args: target: The target class instance. """ for attr in target.attrs: self.process_attribute(target, attr) for choice in attr.choices: self.process_attribute(target, choice) def process_attribute(self, target: Class, attr: Attr) -> None: """Process entrypoint for attrs. Cases: - Reset min_occurs - Reset default value - Validate default value against types - Set empty string as default value for string text nodes. Args: target: The target class instance attr: The attr instance """ if self.should_reset_required(attr): attr.restrictions.min_occurs = 0 if self.should_reset_default(attr): attr.fixed = False attr.default = None if attr.default is not None: self.process_types(target, attr) elif attr.xml_type is None: # Text nodes get an empty default value if str in attr.native_types: attr.default = "" elif bytes in attr.native_types: attr.default = "" # Will be converted to b"" during codegen def process_types(self, target: Class, attr: Attr) -> None: """Reset attr types if default value doesn't pass validation. Args: target: The target class instance attr: The attr instance """ if self.is_valid_external_value(target, attr): return if self.is_valid_native_value(target, attr): return logger.warning( "Failed to match %s.%s default value `%s` to one of %s", target.name, attr.local_name, attr.default, [tp.qname for tp in attr.types], ) self.reset_attribute_types(attr) def is_valid_external_value(self, target: Class, attr: Attr) -> bool: """Validate user defined types. Only enumerations and complex content inner types are supported. Args: target: The target class instance attr: The attr instance . """ for tp in attr.user_types: source = self.find_inner_type(target, tp) if self.is_valid_inner_type(source, attr, tp): return True if self.is_valid_enum_type(source, attr): return True return False def find_inner_type(self, target: Class, attr_type: AttrType) -> Class: """Find the inner class for the given attr type. Args: target: The target class instance attr_type: The attr type instance Returns: The inner class instance. """ if attr_type.forward: return self.container.find_inner(target, attr_type.qname) return self.container.first(attr_type.qname) @classmethod def is_valid_inner_type( cls, source: Class, attr: Attr, attr_type: AttrType, ) -> bool: """Return whether the inner class can inherit the attr default value. If it does, then swap the default/fixed values. Args: source: The inner source class instance attr: The attr instance attr_type: The attr type instance Returns: The bool result. """ if attr_type.forward: for src_attr in source.attrs: if src_attr.xml_type is None: src_attr.default = attr.default src_attr.fixed = attr.fixed attr.default = None attr.fixed = False return True return False @classmethod def is_valid_enum_type(cls, source: Class, attr: Attr) -> bool: """Return whether the default value matches an enum member. If it does convert the string literal default value to enumeration members placeholders. The placeholder will be converted to proper references from the generator filters, because we don't know yet the enumeration class name. Placeholder examples: Single -> @enum@qname::member_name Multiple -> @enum@qname::first_member@second_member Args: source: The inner source class instance attr: The attr instance Returns: The bool result. """ assert attr.default is not None value_members = {x.default: x.name for x in source.attrs} name = value_members.get(attr.default) if name: attr.default = f"@enum@{source.qname}::{name}" return True names = [ value_members[token] for token in attr.default.split() if token in value_members ] if names: attr.default = f"@enum@{source.qname}::{'@'.join(names)}" return True return False @classmethod def is_valid_native_value(cls, target: Class, attr: Attr) -> bool: """Return whether the default value can be converted successfully. The test process for enumerations and fixed value fields are strict, meaning the textual representation also needs to match the original. Args: target: The target class instance attr: The attr instance Returns: The bool result. """ assert attr.default is not None types = converter.sort_types(attr.native_types) if not types: return False tokens = attr.default.split() if attr.restrictions.tokens else [attr.default] if len(tokens) == 1 and attr.is_enumeration and attr.restrictions.tokens: attr.restrictions.tokens = False # Enumerations are also fixed!!! strict = attr.fixed return all( converter.test( token, types, strict=strict, ns_map=target.ns_map, format=attr.restrictions.format, ) for token in tokens ) @classmethod def should_reset_required(cls, attr: Attr) -> bool: """Return whether the min_occurs needs to be reset. Condition: - Attr not derived from xs:attribute - It has no default value - It's derived from xs:anyType - It has max_occurs==1 Args: attr: The attr instance Returns: The bool result. """ return ( not attr.is_attribute and attr.default is None and object in attr.native_types and not attr.is_list ) @classmethod def should_reset_default(cls, attr: Attr) -> bool: """Return whether the default value needs to be reset. Cases: - Attr is xsi:type (ignorable) - Attr has max_occurs > 1 - Attr is not derived from xs:attribute and has min_occurs=0 Args: attr: The attr instance Returns: The bool result. """ return attr.default is not None and ( attr.is_xsi_type or attr.is_list or (not attr.is_attribute and attr.is_optional) ) @classmethod def reset_attribute_types(cls, attr: Attr): """Reset the attribute type to string. Args: attr: The attr instance """ attr.types.clear() attr.types.append(AttrType(qname=str(DataType.STRING), native=True)) attr.restrictions.format = None tefra-xsdata-cf7afe8/xsdata/codegen/handlers/sanitize_enumeration_class.py000066400000000000000000000035711526437326600273710ustar00rootroot00000000000000from typing import Any from xsdata.codegen.mixins import RelativeHandlerInterface from xsdata.codegen.models import Class from xsdata.models.enums import Tag class SanitizeEnumerationClass(RelativeHandlerInterface): """Enumeration class processor.""" __slots__ = () def process(self, target: Class) -> None: """Process entrypoint for classes. Steps: 1. Filter attrs not derived from xs:enumeration 2. Flatten attrs derived from xs:union of enumerations Args: target: The target class instance """ self.filter(target) self.flatten(target) @classmethod def filter(cls, target: Class): """Remove attrs not derived from xs:enumeration. Args: target: The target class instance """ enumerations = [attr for attr in target.attrs if attr.is_enumeration] if enumerations: target.attrs = enumerations def flatten(self, target: Class) -> None: """Flatten attrs derived from xs:union of enumeration classes. Find the enumeration classes and merge all of their members in the target class. Args: target: The target class instance """ if len(target.attrs) != 1 or target.attrs[0].tag != Tag.UNION: return enums: list[Any] = [] for attr_type in target.attrs[0].types: if attr_type.forward: enums.extend(target.inner) elif not attr_type.native: enums.append(self.container.find(attr_type.qname)) else: enums.append(None) merge = all(isinstance(x, Class) and x.is_enumeration for x in enums) if merge: target.attrs.clear() target.inner.clear() target.attrs.extend(attr.clone() for enum in enums for attr in enum.attrs) tefra-xsdata-cf7afe8/xsdata/codegen/handlers/unnest_inner_classes.py000066400000000000000000000063661526437326600262010ustar00rootroot00000000000000from collections import defaultdict from collections.abc import Iterator from xsdata.codegen.mixins import RelativeHandlerInterface from xsdata.codegen.models import AttrType, Class from xsdata.codegen.utils import ClassUtils from xsdata.utils.namespaces import build_qname class UnnestInnerClasses(RelativeHandlerInterface): """Promote inner classes to root classes.""" __slots__ = () def process(self, target: Class) -> None: """Promote all inner classes recursively. Args: target: The target class instance to process """ inner_classes = {} inner_references = defaultdict(list) promote_all = self.container.config.output.unnest_classes for attr_type, source in self.find_forward_refs(target): inner = ClassUtils.find_nested(source, attr_type.qname) if not (promote_all or inner.is_enumeration): continue inner_classes[inner.ref] = inner inner_references[inner.ref].append(attr_type) for ref, inner in inner_classes.items(): references = inner_references[ref] self.update_inner_class(inner) self.update_types(references, inner) self.container.add(inner) self.remove_orphan_inner_classes(target, promote_all) @classmethod def remove_orphan_inner_classes(cls, target: Class, promote_all: bool): """Remove inner classes with no attr references. Args: target: The target class instance to process promote_all: Whether to remove all inner classes or just the enumerations """ for inner in target.inner.copy(): if promote_all or inner.is_enumeration: target.inner.remove(inner) @classmethod def find_forward_refs(cls, target: Class) -> Iterator[tuple[AttrType, Class]]: """Find all forward references for all inner classes. Args: target: The target class instance to process Yields: A tuple of attr type and the parent class instance. """ for attr in target.attrs: for tp in attr.types: if tp.forward and not tp.native: yield tp, target for inner in target.inner: yield from cls.find_forward_refs(inner) @classmethod def update_inner_class(cls, target: Class): """Prepare the nested class to be added as root. Args: target: The target class """ assert target.parent is not None name_parts = [target.parent.name, target.name] new_qname = build_qname(target.target_namespace, "_".join(name_parts)) target.qname = new_qname assert target.parent is not None target.parent.inner.remove(target) target.parent = None target.local_type = True @classmethod def update_types(cls, types: list[AttrType], inner: Class): """Search and replace forward references. Return the number changes. Args: types: The types to search and replace inner: The updated inner class """ for tp in types: tp.qname = inner.qname tp.forward = False tp.reference = inner.ref tefra-xsdata-cf7afe8/xsdata/codegen/handlers/update_attributes_effective_choice.py000066400000000000000000000134241526437326600310300ustar00rootroot00000000000000from collections import defaultdict from xsdata.codegen.mixins import HandlerInterface from xsdata.codegen.models import Attr, Class, get_restriction_choice from xsdata.utils import collections class UpdateAttributesEffectiveChoice(HandlerInterface): """Detect implied repeated choices and update them. valid eg:
symmetrical sequence: """ __slots__ = () def process(self, target: Class) -> None: """Process entrypoint for classes. Ignore enumerations, for performance reasons. Args: target: The target class instance """ if target.is_enumeration: return groups = self.group_repeating_attrs(target) if groups: groups = list(collections.connected_components(groups)) target.attrs = self.merge_attrs(target, groups) self.reset_symmetrical_choices(target) @classmethod def reset_symmetrical_choices(cls, target: Class): """Mark symmetrical choices as sequences. Args: target: The target class instance """ groups = collections.group_by(target.attrs, get_restriction_choice) for choice, attrs in groups.items(): if choice is None or choice > 0: continue min_occurs = set() max_occurs = set() sequences = set() for attr in attrs: min_occurs.add(attr.restrictions.min_occurs) max_occurs.add(attr.restrictions.max_occurs) if attr.restrictions.sequence: sequences.add(attr.restrictions.sequence) if len(min_occurs) == len(max_occurs) == len(sequences) == 1: for attr in attrs: assert attr.restrictions.max_occurs is not None assert attr.restrictions.sequence is not None attr.restrictions.choice = None cls.reset_effective_choice( attr.restrictions.path, attr.restrictions.sequence, attr.restrictions.max_occurs, ) @classmethod def reset_effective_choice( cls, paths: list[tuple[str, int, int, int]], index: int, max_occur: int, ): """Update an attr path to resemble a repeatable sequence. Args: paths: The paths of an attr index: The sequence index max_occur: The new max occurrences """ for i, path in enumerate(paths): if path[0] == "s" and path[1] == index and path[3] == 1: new_path = (*path[:-1], max_occur) paths[i] = new_path break @classmethod def merge_attrs(cls, target: Class, groups: list[list[int]]) -> list[Attr]: """Merge same name/tag/namespace attrs. Args: target: The target class groups: The list of connected attr indexes Returns: The final list of target class attrs """ attrs = [] for index, attr in enumerate(target.attrs): group = collections.find_connected_component(groups, index) if group == -1: attrs.append(attr) continue pos = collections.find(attrs, attr) if pos == -1: attr.restrictions.choice = (group * -1) - 1 attrs.append(attr) else: existing = attrs[pos] assert existing.restrictions.min_occurs is not None assert existing.restrictions.max_occurs is not None existing.restrictions.min_occurs += attr.restrictions.min_occurs or 0 existing.restrictions.max_occurs += attr.restrictions.max_occurs or 0 return attrs @classmethod def group_repeating_attrs(cls, target: Class) -> list[list[int]]: """Create a list of indexes of the same attrs. Example: [ [0, 1 ,2], [3, 4, 6], [5,] ] Args: target: The target class instance Returns: The list of indexes """ counters = defaultdict(list) for index, attr in enumerate(target.attrs): if not attr.is_attribute: counters[attr.key].append(index) result = [] for indices in counters.values(): if len(indices) > 1: # Check if all occurrences have the same choice ID # If they have different choice IDs (and both are not None), # they're in different choice blocks and shouldn't be merged choice_ids = {target.attrs[i].restrictions.choice for i in indices} # Also check if all occurrences have the same path length # If they have different path lengths within the same choice, # they're in different branches (mutually exclusive) path_lengths = {len(target.attrs[i].restrictions.path) for i in indices} if None in choice_ids: # At least one element is outside a choice, so they can co-exist # (e.g., one in outer sequence, one in inner choice branch) result.append(list(range(indices[0], indices[-1] + 1))) elif len(choice_ids) == 1 and len(path_lengths) == 1: # All elements in the same choice at the same nesting level # This is a repeating pattern, group them result.append(list(range(indices[0], indices[-1] + 1))) # else: different choices or same choice but different nesting levels # = mutually exclusive branches, don't group return result tefra-xsdata-cf7afe8/xsdata/codegen/handlers/vacuum_inner_classes.py000066400000000000000000000066251526437326600261630ustar00rootroot00000000000000from collections.abc import Iterator from xsdata.codegen.mixins import HandlerInterface from xsdata.codegen.models import AttrType, Class from xsdata.models.enums import DataType from xsdata.utils import collections from xsdata.utils.namespaces import build_qname class VacuumInnerClasses(HandlerInterface): """Cleanup nested classes. Search and vacuum inner classes with no attributes or a single extension or rename inner classes that have the same name as the outer/parent class. Cases: 1. Filter duplicate inner classes 2. Removing identical overriding fields can sometimes leave a class bare with just an extension. For inner classes we can safely replace the forward reference with the inner extension reference. 3. Empty nested complexContent with no restrictions or extensions, we can replace these references with xs:anySimpleType """ __slots__ = () def process(self, target: Class) -> None: """Process entrypoint for classes. Args: target: The target class instance """ target.inner = collections.unique_sequence(target.inner, key="qname") for inner in target.inner.copy(): if not inner.attrs and len(inner.extensions) < 2: self.remove_inner(target, inner) elif inner.qname == target.qname: self.rename_inner(target, inner) @classmethod def remove_inner(cls, target: Class, inner: Class): """Remove inner class and update the target class attrs. Args: target: The target class instance inner: The nested class instance """ target.inner.remove(inner) for attr_type in cls.find_attr_types(target, inner.qname): attr_type.circular = False attr_type.forward = False if inner.extensions: ext = inner.extensions[0] attr_type.reference = ext.type.reference attr_type.qname = ext.type.qname attr_type.native = False else: attr_type.native = True attr_type.qname = str(DataType.ANY_SIMPLE_TYPE) attr_type.reference = 0 @classmethod def rename_inner(cls, target: Class, inner: Class): """Rename the inner class and update the target class attrs. The inner class will get the `Inner suffix`. Args: target: The target class instance inner: The nested class instance """ namespace = inner.target_namespace old_qname = inner.qname inner.qname = build_qname(namespace, f"{inner.name}_Inner") for attr_type in cls.find_attr_types(target, old_qname): attr_type.qname = inner.qname @classmethod def find_attr_types(cls, target: Class, qname: str) -> Iterator[AttrType]: """Find attr and choice types by the qualified name. Args: target: The target class instance qname: The qualified name """ for attr in target.attrs: for attr_type in attr.types: if attr_type.forward and attr_type.qname == qname: yield attr_type for choice in attr.choices: for choice_type in choice.types: if choice_type.forward and choice_type.qname == qname: yield choice_type tefra-xsdata-cf7afe8/xsdata/codegen/handlers/validate_attributes_overrides.py000066400000000000000000000155651526437326600300770ustar00rootroot00000000000000import sys from xsdata.codegen.mixins import RelativeHandlerInterface from xsdata.codegen.models import Attr, Class, get_slug from xsdata.codegen.utils import ClassUtils from xsdata.logger import logger from xsdata.utils import collections class ValidateAttributesOverrides(RelativeHandlerInterface): """Validate override and restricted attributes.""" __slots__ = () def process(self, target: Class) -> None: """Process entrypoint for classes. - Validate override attrs - Add restricted attrs Args: target: The target class instance """ base_attrs_map = self.base_attrs_map(target) # We need the original class attrs before validation, in order to # prohibit the rest of the parent attrs later... explicit_attrs = { attr.slug for attr in target.attrs if attr.can_be_restricted() } self.validate_attrs(target, base_attrs_map) if target.is_restricted: self.prohibit_parent_attrs(target, explicit_attrs, base_attrs_map) @classmethod def prohibit_parent_attrs( cls, target: Class, explicit_attrs: set[str], base_attrs_map: dict[str, list[Attr]], ): """Prepend prohibited parent attrs to the target class. Prepend the parent prohibited attrs and reset their types and default values in order to avoid conflicts later. Args: target: The target class instance explicit_attrs: The list of explicit attrs in the class base_attrs_map: A mapping of qualified names to lists of parent attrs """ for slug, attrs in reversed(base_attrs_map.items()): attr = attrs[0] if attr.can_be_restricted() and slug not in explicit_attrs: attr_restricted = attr.clone() attr_restricted.restrictions.max_occurs = 0 attr_restricted.default = None attr_restricted.types.clear() target.attrs.insert(0, attr_restricted) @classmethod def validate_attrs(cls, target: Class, base_attrs_map: dict[str, list[Attr]]): """Validate overriding attrs. Cases: - Overriding attr, either remove it or update parent attr - Duplicate names, resolve conflicts - Remove prohibited attrs. Args: target: The target class instance base_attrs_map: A mapping of qualified names to lists of parent attrs """ for attr in target.attrs.copy(): base_attrs = base_attrs_map.get(attr.slug) if base_attrs: base_attr = base_attrs[0] if cls.overrides(attr, base_attr): cls.validate_override(target, attr, base_attr) else: cls.resolve_conflict(attr, base_attr) elif attr.is_prohibited: cls.remove_attribute(target, attr) @classmethod def overrides(cls, a: Attr, b: Attr) -> bool: """Override attrs must belong to the same xml type and namespace. Args: a: The first attr b: The second attr Returns: The bool result. """ return a.xml_type == b.xml_type and a.namespace == b.namespace def base_attrs_map(self, target: Class) -> dict[str, list[Attr]]: """Create a mapping of qualified names to lists of parent attrs. Args: target: The target class instance Returns: A mapping of qualified names to lists of parent attrs. """ base_attrs = self.base_attrs(target) return collections.group_by(base_attrs, key=get_slug) @classmethod def validate_override(cls, target: Class, child_attr: Attr, parent_attr: Attr): """Validate the override will not break mypy type checking. - Ignore wildcard attrs. - If child is a list and parent isn't convert parent to list - If restrictions are the same we can safely remove override attr Args: target: The target class instance child_attr: The child attr parent_attr: The parent attr """ if parent_attr.is_any_type and not child_attr.is_any_type: return if ( child_attr.is_list and not parent_attr.is_list and not parent_attr.is_prohibited ) or ( not child_attr.is_list and not child_attr.is_prohibited and parent_attr.is_list ): # Hack much??? idk but Optional[str] can't override List[str] msg = "Converting {} field `{}::{}` to a list to match {} class `{}`" assert parent_attr.parent is not None if child_attr.is_list: parent_attr.restrictions.max_occurs = sys.maxsize log_message = msg.format( "parent", parent_attr.parent, parent_attr.name, "child", target.qname, ) else: child_attr.restrictions.max_occurs = parent_attr.restrictions.max_occurs log_message = msg.format( "child", target.name, child_attr.name, "parent", parent_attr.parent, ) logger.warning(log_message) if ( child_attr.default == parent_attr.default and _bool_eq(child_attr.fixed, parent_attr.fixed) and _bool_eq(child_attr.mixed, parent_attr.mixed) and _bool_eq(child_attr.is_tokens, parent_attr.is_tokens) and _bool_eq(child_attr.is_nillable, parent_attr.is_nillable) and _bool_eq(child_attr.is_prohibited, parent_attr.is_prohibited) and _bool_eq(child_attr.is_optional, parent_attr.is_optional) ): cls.remove_attribute(target, child_attr) @classmethod def remove_attribute(cls, target: Class, attr: Attr): """Safely remove attr. The search is done with the reference id for safety, of removing attrs with same name. If the attr has a forward reference, the inner class will also be removed if it's unused! Args: target: The target class instance attr: The attr to remove """ ClassUtils.remove_attribute(target, attr) ClassUtils.clean_inner_classes(target) @classmethod def resolve_conflict(cls, child_attr: Attr, parent_attr: Attr): """Rename the child or parent attr. Args: child_attr: The child attr instance parent_attr: The parent attr instance """ ClassUtils.rename_attribute_by_preference(child_attr, parent_attr) def _bool_eq(a: bool | None, b: bool | None) -> bool: return bool(a) is bool(b) tefra-xsdata-cf7afe8/xsdata/codegen/handlers/validate_references.py000066400000000000000000000075111526437326600257400ustar00rootroot00000000000000from collections.abc import Iterator from xsdata.codegen.exceptions import CodegenError from xsdata.codegen.mixins import ContainerHandlerInterface from xsdata.codegen.models import Class, get_qname from xsdata.utils import collections class ValidateReferences(ContainerHandlerInterface): """Validate type references. Rules: - An extension, type, restriction can not be shared - All user types have to match the reference number and qualified name of a class. - All global class qualified names must be unique """ def run(self) -> None: """Validate type references.""" self.validate_unique_qualified_names() self.validate_unique_instances() self.validate_resolved_references() self.validate_parent_references() def validate_unique_qualified_names(self) -> None: """Validate all root classes have unique qualified names.""" duplicate_types = [] groups = collections.group_by(self.container, get_qname) for qname, items in groups.items(): if len(items) > 1: duplicate_types.append(qname) if duplicate_types: raise CodegenError("Duplicate types found", qnames=duplicate_types) def validate_unique_instances(self) -> None: """Validate all codegen instances are unique.""" references: set[int] = set() for item in self.container: item_references = {id(child) for child in item.children()} if item_references.intersection(references): raise CodegenError("Cross reference detected", type=item.qname) references.update(item_references) def validate_resolved_references(self) -> None: """Validate all types match a class reference and qualified name.""" def build_reference_map() -> Iterator[tuple[int, str]]: def build(target: Class) -> Iterator[tuple[int, str]]: yield target.ref, target.qname for inner in target.inner: yield from build(inner) for target in self.container: yield from build(target) references = dict(build_reference_map()) for item in self.container: for tp in item.types(): if tp.native: continue if tp.reference not in references: raise CodegenError( "Unresolved reference detected", cls=item.qname, type=tp.qname ) if tp.qname != references[tp.reference]: raise CodegenError( "Misrepresented reference", cls=item.qname, type=tp.qname ) def validate_parent_references(self) -> None: """Validate inner to outer classes is accurate.""" def _validate(target: Class, parent: Class | None = None) -> None: actual_qname = actual_ref = expected_qname = expected_ref = None if target.parent: actual_qname = target.parent.qname actual_ref = target.parent.ref if parent: expected_qname = parent.qname expected_ref = parent.ref if actual_qname != expected_qname: raise CodegenError( "Invalid parent class reference", cls=target.qname, expected=expected_qname, actual=actual_qname, ) if actual_ref != expected_ref: raise CodegenError( "Invalid parent class reference", cls=target.qname, ref=actual_qname, ) for inner in target.inner: _validate(inner, target) for item in self.container: _validate(item) tefra-xsdata-cf7afe8/xsdata/codegen/mappers/000077500000000000000000000000001526437326600212375ustar00rootroot00000000000000tefra-xsdata-cf7afe8/xsdata/codegen/mappers/__init__.py000066400000000000000000000006111526437326600233460ustar00rootroot00000000000000from xsdata.codegen.mappers.definitions import DefinitionsMapper from xsdata.codegen.mappers.dict import DictMapper from xsdata.codegen.mappers.dtd import DtdMapper from xsdata.codegen.mappers.element import ElementMapper from xsdata.codegen.mappers.schema import SchemaMapper __all__ = [ "DefinitionsMapper", "DictMapper", "DtdMapper", "ElementMapper", "SchemaMapper", ] tefra-xsdata-cf7afe8/xsdata/codegen/mappers/definitions.py000066400000000000000000000453171526437326600241360ustar00rootroot00000000000000import itertools from collections.abc import Iterator from xsdata.codegen.models import Attr, AttrType, Class, Restrictions, Status from xsdata.formats.dataclass.models.generics import AnyElement from xsdata.logger import logger from xsdata.models.enums import DataType, Namespace, Tag from xsdata.models.wsdl import ( Binding, BindingMessage, BindingOperation, Definitions, Part, PortType, PortTypeMessage, PortTypeOperation, ServicePort, ) from xsdata.utils import collections, namespaces, text class DefinitionsMapper: """Map a definitions instance to message and service classes. Currently, only SOAP 1.1 bindings with rpc/document style is supported. """ @classmethod def map(cls, definitions: Definitions) -> list[Class]: """Main entrypoint for this mapper. Iterates over their services and their ports and build the binding and service classes. Args: definitions: The definitions instance to map. Returns: The generated class instances """ return [ obj for service in definitions.services for port in service.ports for obj in cls.map_port(definitions, port) ] @classmethod def map_port(cls, definitions: Definitions, port: ServicePort) -> Iterator[Class]: """Map a service port into binding and service classes. Match a ServicePort to a Binding and PortType object and delegate the process to the next entry point. Args: definitions: The definitions instance port: The service port instance Yields: An iterator of class instances. """ binding = definitions.find_binding(text.suffix(port.binding)) port_type = definitions.find_port_type(text.suffix(binding.type)) elements = itertools.chain(binding.extended_elements, port.extended_elements) config = cls.attributes(elements) yield from cls.map_binding(definitions, binding, port_type, config) @classmethod def map_binding( cls, definitions: Definitions, binding: Binding, port_type: PortType, config: dict, ) -> Iterator[Class]: """Map binding operations into binding and service classes. Match every BindingOperation to a PortTypeOperation and delegate the process for each operation to the next entry point. Args: definitions: The definitions instance binding: The binding instance port_type: The port type instance config: Configuration dictionary Yields: An iterator of class instances. """ for operation in binding.unique_operations(): cfg = config.copy() cfg.update(cls.attributes(operation.extended_elements)) port_operation = port_type.find_operation(operation.name) yield from cls.map_binding_operation( definitions, operation, port_operation, cfg, port_type.name ) @classmethod def map_binding_operation( cls, definitions: Definitions, binding_operation: BindingOperation, port_type_operation: PortTypeOperation, config: dict, name: str, ) -> Iterator[Class]: """Map a binding operation to a service and binding classes. Convert a BindingOperation to a service class and delegate the process of all the message classes to the next entry point. Args: definitions: The definitions instance binding_operation: The binding operation instance port_type_operation: The port type operation instance config: Configuration dictionary name: The operation name Yields: An iterator of class instances. """ attrs = [ cls.build_attr(key, str(DataType.STRING), native=True, default=config[key]) for key in sorted(config.keys(), key=len) if config[key] ] style = config.get("style", "document") name = f"{name}_{binding_operation.name}" namespace = cls.operation_namespace(config) operation_messages = cls.map_binding_operation_messages( definitions, binding_operation, port_type_operation, name, style, namespace, ) for message_class in operation_messages: yield message_class # Only Envelope classes need to be added in service input/output if message_class.meta_name: message_type = message_class.name.split("_")[-1] attrs.append( cls.build_attr( message_type, message_class.qname, reference=id(message_class) ) ) assert binding_operation.location is not None yield Class( qname=namespaces.build_qname(definitions.target_namespace, name), status=Status.FLATTENED, tag=type(binding_operation).__name__, location=binding_operation.location, ns_map=binding_operation.ns_map, attrs=attrs, ) @classmethod def map_binding_operation_messages( cls, definitions: Definitions, binding_operation: BindingOperation, port_type_operation: PortTypeOperation, name: str, style: str, namespace: str | None, ) -> Iterator[Class]: """Map the binding operation messages to binding classes. Args: definitions: The definitions instance binding_operation: The binding operation instance port_type_operation: The port type operation instance name: The operation name style: The operation style namespace: The operation namespace Yields: An iterator of class instances. """ messages: list[tuple[str, BindingMessage, PortTypeMessage, str | None]] = [] if binding_operation.input: messages.append( ( "input", binding_operation.input, port_type_operation.input, binding_operation.name, ) ) if binding_operation.output: messages.append( ("output", binding_operation.output, port_type_operation.output, None) ) for suffix, binding_message, port_type_message, operation_name in messages: if style == "rpc": yield cls.build_message_class(definitions, port_type_message) target = cls.build_envelope_class( definitions, binding_message, port_type_message, f"{name}_{suffix}", style, namespace, operation_name, ) if suffix == "output": cls.build_envelope_fault(definitions, port_type_operation, target) yield target @classmethod def build_envelope_fault( cls, definitions: Definitions, port_type_operation: PortTypeOperation, target: Class, ): """Add an inner message fault class with default fields. Args: definitions: The definitions instance port_type_operation: The port type operation instance target: The target class instance """ ns_map: dict = {} body = next(inner for inner in target.inner if inner.name == "Body") fault_class = cls.build_inner_class(body, "Fault", target.namespace) detail_attrs: list[Attr] = [] for fault in port_type_operation.faults: message = definitions.find_message(text.suffix(fault.message)) detail_attrs.extend(cls.build_parts_attributes(message.parts, ns_map)) # faultcode and faultstring are required, faultactor and detail are optional required_fields = ["faultcode", "faultstring"] optional_fields = ["faultactor"] if detail_attrs: detail = cls.build_inner_class(fault_class, "detail", namespace="") detail.attrs.extend(detail_attrs) # Make detail attr optional detail_attr = fault_class.attrs[ -1 ] # Last added attr is the detail forward ref detail_attr.restrictions.min_occurs = 0 # Make detail attributes optional since SOAP faults contain one fault type for attr in detail_attrs: attr.restrictions.min_occurs = 0 else: optional_fields.append("detail") optional_attrs = [ cls.build_attr(f, str(DataType.STRING), native=True, namespace="") for f in optional_fields ] for attr in optional_attrs: attr.restrictions.min_occurs = 0 collections.prepend(fault_class.attrs, *optional_attrs) collections.prepend( fault_class.attrs, *( cls.build_attr(f, str(DataType.STRING), native=True, namespace="") for f in required_fields ), ) for attr in body.attrs: attr.restrictions.min_occurs = 0 @classmethod def build_envelope_class( cls, definitions: Definitions, binding_message: BindingMessage, port_type_message: PortTypeMessage, name: str, style: str, namespace: str | None, operation: str | None, ) -> Class: """Map the binding message to an envelope class. Args: definitions: The definitions instance binding_message: The port type message instance port_type_message: The port type message instance name: The class name style: The operation style e.g. rpc namespace: The operation namespace operation: The custom operation name, if it's empty the message name will be used instead Returns: The class instance. """ assert binding_message.location is not None target = Class( qname=namespaces.build_qname(definitions.target_namespace, name), meta_name="Envelope", tag=Tag.BINDING_MESSAGE, location=binding_message.location, ns_map=binding_message.ns_map, namespace=namespace, ) message = port_type_message.message for ext in binding_message.extended_elements: assert ext.qname is not None class_name = namespaces.local_name(ext.qname).title() inner = cls.build_inner_class(target, class_name) if style == "rpc" and class_name == "Body": namespace = ext.attributes.get("namespace") attrs = cls.map_port_type_message( operation, port_type_message, namespace ) else: attrs = cls.map_binding_message_parts( definitions, message, ext, inner.ns_map ) inner.attrs.extend(attrs) return target @classmethod def build_message_class( cls, definitions: Definitions, port_type_message: PortTypeMessage, ) -> Class: """Map the input/output message of a rpc style operation. Args: definitions: The definitions instance port_type_message: The port type message instance Returns: The class instance. """ prefix, name = text.split(port_type_message.message) definition_message = definitions.find_message(name) ns_map = definition_message.ns_map.copy() source_namespace = ns_map.get(prefix) assert port_type_message.location is not None return Class( qname=namespaces.build_qname(source_namespace, name), namespace=source_namespace, status=Status.RAW, tag=Tag.ELEMENT, location=port_type_message.location, ns_map=ns_map, attrs=list(cls.build_parts_attributes(definition_message.parts, ns_map)), ) @classmethod def build_inner_class( cls, target: Class, name: str, namespace: str | None = None ) -> Class: """Build or retrieve an inner class. This helper will also create a forward reference attribute for the parent class. Args: target: The parent class instance name: The inner class name namespace: The inner class namespace Returns: The inner class instance. """ inner = collections.first(inner for inner in target.inner if inner.name == name) if not inner: inner = Class( qname=namespaces.build_qname(target.target_namespace, name), tag=Tag.BINDING_MESSAGE, location=target.location, ns_map=target.ns_map.copy(), ) attr = cls.build_attr(name, inner.qname, forward=True, namespace=namespace) inner.parent = target target.inner.append(inner) target.attrs.append(attr) return inner @classmethod def map_port_type_message( cls, operation: str | None, message: PortTypeMessage, namespace: str | None, ) -> Iterator[Attr]: """Build an attribute for the given port type message. Args: operation: The operation name, use the message name if it's empty message: The port type message instance namespace: The operation namespace Yields: An iterator of class attrs. """ prefix, name = text.split(message.message) source_namespace = message.ns_map.get(prefix) if operation is None: operation = name yield cls.build_attr( operation, qname=namespaces.build_qname(source_namespace, name), namespace=namespace, ) @classmethod def map_binding_message_parts( cls, definitions: Definitions, message: str, extended: AnyElement, ns_map: dict ) -> Iterator[Attr]: """Find a Message instance and map its parts to attrs. Args: definitions: The definitions instance message: The message qualified name extended: The related extended element ns_map: The namespace prefix-URI map Yields: An iterator of class attrs. """ parts = [] if "part" in extended.attributes: parts.append(extended.attributes["part"]) elif "parts" in extended.attributes: parts.extend(extended.attributes["parts"].split()) if "message" in extended.attributes: message_name = namespaces.local_name(extended.attributes["message"]) else: message_name = text.suffix(message) definition_message = definitions.find_message(message_name) message_parts = definition_message.parts if parts: message_parts = [part for part in message_parts if part.name in parts] yield from cls.build_parts_attributes(message_parts, ns_map) @classmethod def build_parts_attributes(cls, parts: list[Part], ns_map: dict) -> Iterator[Attr]: """Build attributes for the given list of parts. Args: parts: A list of part instances ns_map: The namespace prefix-URI map Yields: An iterator of class attrs. """ for part in parts: if part.element: prefix, type_name = text.split(part.element) name = type_name elif part.type: prefix, type_name = text.split(part.type) name = part.name else: logger.warning("Skip untyped message part %s", part.name) continue ns_map.update(part.ns_map) namespace = part.ns_map.get(prefix) type_qname = namespaces.build_qname(namespace, type_name) native = namespace == Namespace.XS.uri # If part has a type it could reference an element or a complex type or # a simple type, we can't make that detection yet, postpone it till the # classes processing. namespace = "##lazy" if part.type else namespace yield cls.build_attr(name, type_qname, namespace=namespace, native=native) @classmethod def operation_namespace(cls, config: dict) -> str | None: """Return the operation namespace by the operation transport. Args: config: The operation configuration Returns: The operation namespace string or None if transport is not soap. """ transport = config.get("transport") namespace = None if transport == "http://schemas.xmlsoap.org/soap/http": namespace = "http://schemas.xmlsoap.org/soap/envelope/" return namespace @classmethod def attributes(cls, elements: Iterator[AnyElement]) -> dict: """Return all attributes from all extended elements as a dictionary. Args: elements: An iterator of generic elements Returns: A key-value mapping of the xml attributes. """ return { namespaces.local_name(qname): value for element in elements if isinstance(element, AnyElement) for qname, value in element.attributes.items() } @classmethod def build_attr( cls, name: str, qname: str, native: bool = False, forward: bool = False, namespace: str | None = None, default: str | None = None, reference: int = 0, ) -> Attr: """Helper method to build an attr instance. Args: name: The attr name qname: The attr qualified name native: Whether the type is native forward: Whether the type is a forward reference namespace: The attr namespace default: The attr default value reference: The class id reference, if any Returns: The new attr instance. """ occurs = 1 if default is not None else None if native: namespace = "" return Attr( tag=Tag.ELEMENT, name=name, namespace=namespace, default=default, types=[ AttrType( qname=qname, forward=forward, native=native, reference=reference ) ], restrictions=Restrictions(min_occurs=occurs, max_occurs=occurs), ) tefra-xsdata-cf7afe8/xsdata/codegen/mappers/dict.py000066400000000000000000000045161526437326600225420ustar00rootroot00000000000000import sys from typing import Any from xsdata.codegen.mappers.mixins import RawDocumentMapper from xsdata.codegen.models import AttrType, Class from xsdata.codegen.utils import ClassUtils from xsdata.models.enums import Tag class DictMapper(RawDocumentMapper): """Map a dictionary to classes. This mapper is used to build classes from raw json documents. """ @classmethod def map(cls, data: dict, name: str, location: str) -> list[Class]: """Map a dictionary to classes. Args: data: The json resource data name: The main resource name location: The resource location Returns: The list of classes. """ target = cls.build_class(data, name) return list(ClassUtils.flatten(target, f"{location}/{name}")) @classmethod def build_class(cls, data: dict, name: str) -> Class: """Build a class from a data dictionary. Args: data: The json resource data name: The main resource name Returns: The list of classes. """ target = Class(qname=name, tag=Tag.ELEMENT, location="") for key, value in data.items(): cls.build_class_attribute(target, key, value) return target @classmethod def build_class_attribute(cls, target: Class, name: str, value: Any): """Build a class attr. Args: target: The target class instance name: The attr name value: The data value to extract types and restrictions. """ if isinstance(value, list): if not value: cls.build_class_attribute(target, name, None) target.attrs[-1].restrictions.max_occurs = sys.maxsize else: for val in value: cls.build_class_attribute(target, name, val) target.attrs[-1].restrictions.max_occurs = sys.maxsize else: if isinstance(value, dict): inner = cls.build_class(value, name) inner.parent = target attr_type = AttrType(qname=inner.qname, forward=True) target.inner.append(inner) else: attr_type = cls.build_attr_type(name, value) cls.build_attr(target, name, attr_type, value=value) tefra-xsdata-cf7afe8/xsdata/codegen/mappers/dtd.py000066400000000000000000000251331526437326600223700ustar00rootroot00000000000000import sys from collections.abc import Iterator from typing import Any from xsdata.codegen.models import Attr, AttrType, Class, Extension, Restrictions from xsdata.models.dtd import ( Dtd, DtdAttribute, DtdAttributeDefault, DtdAttributeType, DtdContent, DtdContentOccur, DtdContentType, DtdElement, DtdElementType, ) from xsdata.models.enums import DataType, Tag from xsdata.utils.constants import DEFAULT_ATTR_NAME class DtdMapper: """Maps a Dtd instance to a list of class instances.""" @classmethod def map(cls, dtd: Dtd) -> Iterator[Class]: """Map the given Dtd instance to a list of classes. Args: dtd: The Dtd instance to be mapped Yields: An iterator of mapped classes. """ for element in dtd.elements: yield cls.build_class(element, dtd.location) @classmethod def build_class(cls, element: DtdElement, location: str) -> Class: """Build a class for the given element. Args: element: The dtd element to be mapped location: The location of dtd resource Returns: The mapped class instance. """ target = Class( qname=element.qname, ns_map=element.ns_map, tag=Tag.ELEMENT, location=location, ) cls.build_attributes(target, element) cls.build_elements(target, element) return target @classmethod def build_attributes(cls, target: Class, element: DtdElement): """Build attributes from the dtd element attributes. Args: target: The target class instance element: The dtd element containing attributes """ for attribute in element.attributes: cls.build_attribute(target, attribute) @classmethod def build_attribute(cls, target: Class, attribute: DtdAttribute): """Build an attr from a dtd attribute. Args: target: The target class instance attribute: The dtd attribute to be mapped """ attr_type = cls.build_attribute_type(target, attribute) attr = Attr( name=attribute.name, namespace=target.ns_map.get(attribute.prefix), tag=Tag.ATTRIBUTE, types=[attr_type], ) cls.build_attribute_restrictions( attr, attribute.default, attribute.default_value ) attr.index = len(target.attrs) target.attrs.append(attr) @classmethod def build_attribute_restrictions( cls, attr: Attr, default: DtdAttributeDefault, default_value: str | None, ): """Build attribute restrictions based on DtdAttributeDefault. Args: attr: The target attr instance default: The default attribute type default_value: The default value """ attr.restrictions.max_occurs = 1 if default == DtdAttributeDefault.REQUIRED: attr.restrictions.min_occurs = 1 elif default == DtdAttributeDefault.IMPLIED: attr.restrictions.min_occurs = 0 elif default == DtdAttributeDefault.FIXED: attr.fixed = True attr.restrictions.min_occurs = 1 attr.default = default_value elif default_value is not None: attr.restrictions.min_occurs = 1 attr.default = default_value else: attr.restrictions.min_occurs = 0 @classmethod def build_attribute_type(cls, target: Class, attribute: DtdAttribute) -> AttrType: """Build attribute type based on the dtd attribute. Args: target: The target class instance attribute: The dtd attribute to be mapped Returns: The mapped attr type instance. """ if attribute.type == DtdAttributeType.ENUMERATION: cls.build_enumeration(target, attribute.name, attribute.values) return AttrType(qname=attribute.name, forward=True) return AttrType(qname=str(attribute.data_type), native=True) @classmethod def build_elements(cls, target: Class, element: DtdElement): """Build attrs from the dtd element. Args: target: The target class instance element: The dtd containing elements. """ if element.type == DtdElementType.ELEMENT and element.content: cls.build_content(target, element.content) elif element.type == DtdElementType.MIXED and element.content: cls.build_mixed_content(target, element.content) elif element.type == DtdElementType.ANY: cls.build_extension(target, DataType.ANY_TYPE) @classmethod def build_mixed_content(cls, target: Class, content: DtdContent): """Mark class to support mixed content. Args: target: The target class instance content: The dtd content instance """ if content.left and content.left.type == DtdContentType.PCDATA: target.mixed = True content.left = None elif content.right and content.right.type == DtdContentType.PCDATA: target.mixed = True content.right = None target.tag = Tag.COMPLEX_TYPE cls.build_content(target, content) @classmethod def build_extension(cls, target: Class, data_type: DataType): """Add a xsd native type as class extension. Args: target: The target class instance data_type: The data type instance """ ext_type = AttrType(qname=str(data_type), native=True) extension = Extension( tag=Tag.EXTENSION, type=ext_type, restrictions=Restrictions() ) target.extensions.append(extension) @classmethod def build_content(cls, target: Class, content: DtdContent, **kwargs: Any): """Build class content. Args: target: The target class instance content: The dtd content instance. **kwargs: Additional restriction arguments. """ content_type = content.type if content_type == DtdContentType.ELEMENT: restrictions = cls.build_restrictions(content.occur, **kwargs) cls.build_element(target, content.name, restrictions) elif content_type == DtdContentType.SEQ: cls.build_content_tree(target, content, **kwargs) elif content_type == DtdContentType.OR: params = cls.build_occurs(content.occur) params.update( { "choice": id(content), "min_occurs": 0, } ) params.update(**kwargs) cls.build_content_tree(target, content, **params) else: # content_type == DtdContentType.PCDATA: restrictions = cls.build_restrictions(content.occur, **kwargs) cls.build_value(target, restrictions) @classmethod def build_content_tree(cls, target: Class, content: DtdContent, **kwargs: Any): """Build the class content tree. Args: target: The target class instance content: The dtd content instance **kwargs: Additional restriction arguments """ if content.left: cls.build_content(target, content.left, **kwargs) if content.right: cls.build_content(target, content.right, **kwargs) @classmethod def build_occurs(cls, occur: DtdContentOccur) -> dict: """Calculate min/max occurs from the dtd content occur instance. Args: occur: The dtd content occur instance. Returns: The min/max occurs restrictions dictionary """ if occur == DtdContentOccur.ONCE: min_occurs = 1 max_occurs = 1 elif occur == DtdContentOccur.OPT: min_occurs = 0 max_occurs = 1 elif occur == DtdContentOccur.MULT: min_occurs = 0 max_occurs = sys.maxsize else: # occur == DtdContentOccur.PLUS: min_occurs = 1 max_occurs = sys.maxsize return { "min_occurs": min_occurs, "max_occurs": max_occurs, } @classmethod def build_restrictions(cls, occur: DtdContentOccur, **kwargs: Any) -> Restrictions: """Map the dtd content occur instance to a restriction instance. Args: occur: The DtdContentOccur to be mapped **kwargs: Additional restriction arguments Returns: The mapped restrictions instance. """ params = cls.build_occurs(occur) params.update(kwargs) return Restrictions(**params) @classmethod def build_element(cls, target: Class, name: str, restrictions: Restrictions): """Build an element attr for the target class instance. Args: target: The target class instance name: The attr name restrictions: The attr restrictions """ types = AttrType(qname=name, native=False) attr = Attr( name=name, tag=Tag.ELEMENT, types=[types], restrictions=restrictions.clone() ) attr.index = len(target.attrs) target.attrs.append(attr) @classmethod def build_value(cls, target: Class, restrictions: Restrictions): """Build a value attr for the target class instance. Args: target: The target class instance restrictions: The attr restrictions """ types = AttrType(qname=str(DataType.STRING), native=True) attr = Attr( name=DEFAULT_ATTR_NAME, tag=Tag.EXTENSION, types=[types], restrictions=restrictions.clone(), ) attr.index = len(target.attrs) target.attrs.append(attr) @classmethod def build_enumeration(cls, target: Class, name: str, values: list[str]): """Build a nested enumeration class from the given values list. Args: target: The target class instance name: The attr/enum class name values: The enumeration values """ inner = Class(qname=name, tag=Tag.SIMPLE_TYPE, location=target.location) attr_type = AttrType(qname=str(DataType.STRING), native=True) for value in values: inner.attrs.append( Attr( fixed=True, default=value, name=value, tag=Tag.ENUMERATION, types=[attr_type.clone()], ) ) inner.parent = target target.inner.append(inner) tefra-xsdata-cf7afe8/xsdata/codegen/mappers/element.py000066400000000000000000000131731526437326600232470ustar00rootroot00000000000000from collections import defaultdict from xsdata.codegen.mappers.mixins import RawDocumentMapper from xsdata.codegen.models import AttrType, Class from xsdata.codegen.utils import ClassUtils from xsdata.formats.dataclass.models.generics import AnyElement from xsdata.models.enums import QNames, Tag from xsdata.utils import collections from xsdata.utils.namespaces import build_qname, split_qname class ElementMapper(RawDocumentMapper): """Map a generic element to classes. This mapper is used to build classes from raw xml documents. """ @classmethod def map(cls, element: AnyElement, location: str) -> list[Class]: """Map schema children elements to classes. Args: element: The root element to be mapped location: The location of the xml document Returns: The list of mapped class instances. """ assert element.qname is not None uri, name = split_qname(element.qname) target = cls.build_class(element, uri) return list(ClassUtils.flatten(target, f"{location}/{name}")) @classmethod def build_class(cls, element: AnyElement, parent_namespace: str | None) -> Class: """Build a Class instance for the given generic element. Args: element: The generic element to be mapped parent_namespace: The parent element namespace Returns: The mapped class instance. """ assert element.qname is not None namespace, name = split_qname(element.qname) namespace = cls.select_namespace(namespace, parent_namespace) target = Class( qname=build_qname(namespace, name), namespace=namespace, tag=Tag.ELEMENT, location="", ) cls.build_attributes(target, element, namespace) cls.build_elements(target, element, namespace) cls.build_text(target, element) return target @classmethod def build_attributes( cls, target: Class, element: AnyElement, namespace: str | None, ): """Build attributes for the given Class instance based on AnyElement attributes. Args: target: The target class instance element: The AnyElement containing attributes. namespace: The namespace. """ for key, value in element.attributes.items(): if key == QNames.XSI_NIL: target.nillable = value.strip() in ("true", "1") else: attr_type = cls.build_attr_type(key, value) cls.build_attr(target, key, attr_type, namespace, Tag.ATTRIBUTE) @classmethod def build_elements( cls, target: Class, element: AnyElement, namespace: str | None, ): """Build elements for the given Class instance based on AnyElement children. Args: target: The target class instance element: The AnyElement containing children. namespace: The namespace. """ sequences = cls.sequential_groups(element) for index, child in enumerate(element.children): if isinstance(child, AnyElement) and child.qname: if child.tail: target.mixed = True if child.attributes or child.children: inner = cls.build_class(child, namespace) inner.parent = target attr_type = AttrType(qname=inner.qname, forward=True) target.inner.append(inner) else: attr_type = cls.build_attr_type(child.qname, child.text) sequence = collections.find_connected_component(sequences, index) cls.build_attr( target, child.qname, attr_type, namespace, Tag.ELEMENT, sequence + 1, ) @classmethod def build_text(cls, target: Class, element: AnyElement): """Build a text attr from the generic element text value. Args: target: The target class instance element: The AnyElement containing text content. """ if element.text: attr_type = cls.build_attr_type("value", element.text) cls.build_attr(target, "value", attr_type, None, Tag.SIMPLE_TYPE) if any(attr.tag == Tag.ELEMENT for attr in target.attrs): target.mixed = True @classmethod def sequential_groups(cls, element: AnyElement) -> list[list[int]]: """Identify sequential groups of repeating attributes. Args: element: The generic element instance Returns: A list of lists of strongly connected children indexes. """ groups = cls.group_repeating_attrs(element) return list(collections.connected_components(groups)) @classmethod def group_repeating_attrs(cls, element: AnyElement) -> list[list[int]]: """Group repeating children in the given generic element. Args: element: The generic element instance Returns: A list of lists of children indexes. """ counters = defaultdict(list) for index, child in enumerate(element.children): if isinstance(child, AnyElement) and child.qname: counters[child.qname].append(index) groups: list[list[int]] = [] if len(counters) > 1: groups.extend( list(range(x[0], x[-1] + 1)) for x in counters.values() if len(x) > 1 ) return groups tefra-xsdata-cf7afe8/xsdata/codegen/mappers/mixins.py000066400000000000000000000072651526437326600231320ustar00rootroot00000000000000import sys from typing import Any from xsdata.codegen.models import Attr, AttrType, Class from xsdata.formats.converter import converter from xsdata.models.enums import DataType, QNames, Tag from xsdata.utils import collections from xsdata.utils.namespaces import split_qname _UNSET = object() class RawDocumentMapper: """Mixin class for raw json/xml documents.""" @classmethod def build_attr( cls, target: Class, qname: str, attr_type: AttrType, parent_namespace: str | None = None, tag: str = Tag.ELEMENT, sequence: int = 0, value: Any = _UNSET, ): """Build an attr for the given class instance. Args: target: The target class instance qname: The attr qualified name attr_type: The attr type instance parent_namespace: The parent namespace tag: The attr tag sequence: The attr sequence number value: The attr sample value """ namespace, name = split_qname(qname) namespace = cls.select_namespace(namespace, parent_namespace, tag) index = len(target.attrs) attr = Attr(index=index, name=name, tag=tag, namespace=namespace) attr.types.append(attr_type) if sequence: attr.restrictions.path.append(("s", sequence, 1, sys.maxsize)) attr.restrictions.min_occurs = 1 attr.restrictions.max_occurs = 1 if value is None: attr.restrictions.min_occurs = 0 cls.add_attribute(target, attr) @classmethod def build_attr_type(cls, qname: str, value: Any) -> AttrType: """Build an attribute type for the given attribute name and value. Args: qname: The attr qualified name value: The attr value Returns: The new attr type instance. """ def match_type(val: Any) -> DataType: if not isinstance(val, str): return DataType.from_value(val) for tp in converter.explicit_types(): if converter.test(val, [tp], strict=True): return DataType.from_type(tp) return DataType.STRING if qname == QNames.XSI_TYPE: data_type = DataType.QNAME elif value is None or value == "": data_type = DataType.ANY_SIMPLE_TYPE else: data_type = match_type(value) return AttrType(qname=str(data_type), native=True) @classmethod def select_namespace( cls, namespace: str | None, parent_namespace: str | None, tag: str = Tag.ELEMENT, ) -> str | None: """Select the namespace based on the tag and namespace. Args: namespace: The current namespace parent_namespace: The parent namespace tag: The tag name Returns: Optional[str]: The selected namespace. """ if tag == Tag.ATTRIBUTE: return namespace if namespace is None and parent_namespace is not None: return "" return namespace @classmethod def add_attribute(cls, target: Class, attr: Attr): """Add an attr to the target class instance. Args: target: The target class instance attr (Attr): The attribute to be added. """ pos = collections.find(target.attrs, attr) if pos > -1: existing = target.attrs[pos] existing.restrictions.max_occurs = sys.maxsize existing.types.extend(attr.types) existing.types = collections.unique_sequence(existing.types, key="qname") else: target.attrs.append(attr) tefra-xsdata-cf7afe8/xsdata/codegen/mappers/schema.py000066400000000000000000000301711526437326600230530ustar00rootroot00000000000000from collections.abc import Iterator from xsdata.codegen.models import Attr, AttrType, Class, Extension, Restrictions from xsdata.models.enums import DataType, Tag from xsdata.models.mixins import ElementBase from xsdata.models.xsd import ( Attribute, AttributeGroup, ComplexType, Element, Group, Schema, SimpleType, ) from xsdata.utils import collections, text from xsdata.utils.namespaces import build_qname, is_default, prefix_exists ROOT_CLASSES = (SimpleType, ComplexType, Group, AttributeGroup, Element, Attribute) class SchemaMapper: """Map a schema instance to classes. This mapper is used to build classes from xsd documents. """ @classmethod def map(cls, schema: Schema) -> list[Class]: """Map schema children elements to classes. Args: schema: The schema instance Returns: A list of classes. """ assert schema.location is not None location = schema.location target_namespace = schema.target_namespace return [ cls.build_class(element, container, location, target_namespace) for container, element in cls.root_elements(schema) ] @classmethod def root_elements(cls, schema: Schema) -> Iterator[tuple[str, ElementBase]]: """Return the schema root elements. Qualified Elements: - SimpleType - ComplexType - Group - AttributeGroup - Element - Attribute Args: schema: The schema instance Yields: An iterator of element base instances. """ def condition(item: ElementBase) -> bool: return isinstance(item, ROOT_CLASSES) for override in schema.overrides: for child in override.children(condition=condition): yield Tag.OVERRIDE, child for redefine in schema.redefines: for child in redefine.children(condition=condition): yield Tag.REDEFINE, child for child in schema.children(condition=condition): yield Tag.SCHEMA, child @classmethod def build_class( cls, obj: ElementBase, container: str, location: str, target_namespace: str | None, ) -> Class: """Build and return a class instance. Args: obj: The element base instance container: The container name location: The schema location target_namespace: The schema target namespace Returns: The new class instance. """ instance = Class( qname=build_qname(target_namespace, obj.real_name), abstract=obj.is_abstract, namespace=cls.element_namespace(obj, target_namespace), mixed=obj.is_mixed, nillable=obj.is_nillable, tag=obj.class_name, container=container, help=obj.display_help, ns_map=obj.ns_map, location=location, default=obj.default_value, fixed=obj.is_fixed, substitutions=cls.build_substitutions(obj, target_namespace), ) cls.build_class_extensions(obj, instance) cls.build_class_attributes(obj, instance) return instance @classmethod def build_substitutions( cls, obj: ElementBase, target_namespace: str | None, ) -> list[str]: """Builds a list of qualified substitution group names. Args: obj: The element base instance target_namespace: The schema target namespace Returns: A list of qualified substitution group names. """ return [ build_qname(obj.ns_map.get(prefix, target_namespace), suffix) for prefix, suffix in map(text.split, obj.substitutions) ] @classmethod def build_class_attributes(cls, obj: ElementBase, target: Class): """Build the target class attrs from the element children. Args: obj: The element base instance target: The target class instance """ base_restrictions = Restrictions.from_element(obj) for child, restrictions in cls.element_children(obj, base_restrictions): cls.build_class_attribute(target, child, restrictions) target.attrs.sort(key=lambda x: x.index) @classmethod def build_class_extensions(cls, obj: ElementBase, target: Class): """Build the target class extensions from the element children. Args: obj: The element base instance target: The target class instance """ restrictions = obj.get_restrictions() extensions = [ cls.build_class_extension(obj.class_name, target, base, restrictions) for base in obj.bases ] extensions.extend(cls.children_extensions(obj, target)) target.extensions = collections.unique_sequence(extensions) @classmethod def build_attr_type( cls, target: Class, name: str, forward: bool = False, ) -> AttrType: """Create a reference attr type for the target class. Args: target: The target class instance name: the qualified name of the attr forward: Whether the reference is for an inner class Returns: The new attr type instance. """ prefix, suffix = text.split(name) namespace = target.ns_map.get(prefix, target.target_namespace) qname = build_qname(namespace, suffix) datatype = DataType.from_qname(qname) return AttrType( qname=qname, native=datatype is not None, forward=forward, ) @classmethod def element_children( cls, obj: ElementBase, parent_restrictions: Restrictions, ) -> Iterator[tuple[ElementBase, Restrictions]]: """Recursively find and return all child elements. Args: obj: The element base instance. parent_restrictions: The parent element restrictions instance Yields: An iterator of elements and their parent restrictions. """ for child in obj.children(): if child.is_property: yield child, parent_restrictions else: restrictions = Restrictions.from_element(child) restrictions.merge(parent_restrictions) yield from cls.element_children(child, restrictions) @classmethod def element_namespace( cls, obj: ElementBase, target_namespace: str | None, ) -> str | None: """Return the target namespace for the given schema element. Rules: - elements/attributes with specific target namespace - prefixed elements returns the namespace from schema ns_map - qualified elements returns the schema target namespace - unqualified elements return an empty string - unqualified attributes return None Args: obj: The element base instance target_namespace: The schema target namespace Returns: The element real namespace or None if no namespace """ raw_namespace = obj.raw_namespace if raw_namespace: return raw_namespace prefix = obj.prefix if prefix: return obj.ns_map.get(prefix) if obj.is_qualified and ( not obj.is_ref or not target_namespace or not prefix_exists(target_namespace, obj.ns_map) or is_default(target_namespace, obj.ns_map) ): return target_namespace return "" if isinstance(obj, Element) else None @classmethod def children_extensions( cls, obj: ElementBase, target: Class, ) -> Iterator[Extension]: """Recursively find and return all target's extension instances. Args: obj: The element base instance target: The target class instance Yields: An iterator of extension instances. """ for child in obj.children(): if child.is_property: continue for ext in child.bases: yield cls.build_class_extension( child.class_name, target, ext, child.get_restrictions() ) yield from cls.children_extensions(child, target) @classmethod def build_class_extension( cls, tag: str, target: Class, name: str, restrictions: dict, ) -> Extension: """Create a reference extension for the target class. Args: tag: The tag name target: The target class instance name: The qualified name of the extension restrictions: A key-value restrictions mapping Returns: The new extension instance. """ return Extension( type=cls.build_attr_type(target, name), tag=tag, restrictions=Restrictions(**restrictions), ) @classmethod def build_class_attribute( cls, target: Class, obj: ElementBase, parent_restrictions: Restrictions, ): """Build and append a new attr to the target class. Args: target: The target class instance obj: The element base instance to map to an attr parent_restrictions: The parent element restrictions """ target.ns_map.update(obj.ns_map) types = cls.build_attr_types(target, obj) restrictions = Restrictions.from_element(obj) if obj.class_name in (Tag.ELEMENT, Tag.ANY, Tag.GROUP): restrictions.merge(parent_restrictions) name = obj.real_name target.attrs.append( Attr( index=obj.index, name=name, default=obj.default_value, fixed=obj.is_fixed, types=types, tag=obj.class_name, help=obj.display_help, namespace=cls.element_namespace(obj, target.target_namespace), restrictions=restrictions, ) ) @classmethod def build_attr_types(cls, target: Class, obj: ElementBase) -> list[AttrType]: """Convert the element types and inner types to an attr types. Args: target: The target class instance obj: The element base instance to extract the types from Returns: A list of attr type instances. """ types = [cls.build_attr_type(target, tp) for tp in obj.attr_types] location = target.location namespace = target.target_namespace for inner in cls.build_inner_classes(obj, location, namespace): inner.parent = target target.inner.append(inner) types.append(AttrType(qname=inner.qname, forward=True)) if not types: types.append(cls.build_attr_type(target, name=obj.default_type)) return collections.unique_sequence(types) @classmethod def build_inner_classes( cls, obj: ElementBase, location: str, namespace: str | None, ) -> Iterator[Class]: """Find and convert anonymous types to a class instances. Args: obj: The element base instance location: The schema location namespace: The parent element namespace Yields: An iterator of class instances. """ if isinstance(obj, SimpleType) and obj.is_enumeration: yield cls.build_class(obj, obj.class_name, location, namespace) else: for child in obj.children(): if isinstance(child, ComplexType) or ( isinstance(child, SimpleType) and child.is_enumeration ): child.name = obj.real_name yield cls.build_class(child, obj.class_name, location, namespace) else: yield from cls.build_inner_classes(child, location, namespace) tefra-xsdata-cf7afe8/xsdata/codegen/mixins.py000066400000000000000000000117601526437326600214560ustar00rootroot00000000000000import abc from collections.abc import Callable, Iterator from xsdata.codegen.models import Attr, Class from xsdata.models.config import GeneratorConfig from xsdata.utils.constants import return_true class ContainerInterface(abc.ABC): """A class list wrapper with an easy access api. Args: config: The generator configuration instance """ __slots__ = ("config", "data") def __init__(self, config: GeneratorConfig): """Initialize the container.""" self.config = config self.data: dict[str, list[Class]] = {} @abc.abstractmethod def __iter__(self) -> Iterator[Class]: """Yield an iterator for the class map values.""" @abc.abstractmethod def process(self) -> None: """Run the processor and filter steps.""" @abc.abstractmethod def find(self, qname: str, condition: Callable = return_true) -> Class | None: """Find class that matches the given qualified name and condition callable. Classes are allowed to have the same qualified name, e.g. xsd:Element extending xsd:ComplexType with the same name, you can provide and additional callback to filter the classes like the tag. Args: qname: The qualified name of the class condition: A user callable to filter further Returns: A class instance or None if no match found. """ @abc.abstractmethod def find_inner(self, source: Class, qname: str) -> Class: """Search by qualified name for a specific inner class or fail. Args: source: The source class to search for the inner class qname: The qualified name of the inner class to look up Returns: The inner class instance Raises: CodeGenerationError: If the inner class is not found. """ @abc.abstractmethod def first(self, qname: str) -> Class: """Return the first class that matches the qualified name. Args: qname: The qualified name of the class Returns: The first matching class Raises: KeyError: If no class matches the qualified name """ @abc.abstractmethod def add(self, item: Class) -> None: """Add class instance to the container. Args: item: The class instance to add """ @abc.abstractmethod def remove(self, *items: Class) -> None: """Safely remove classes from the container. Args: items: The classes to remove """ @abc.abstractmethod def extend(self, items: list[Class]) -> None: """Add a list of classes to the container. Args: items: The list of class instances to add """ @abc.abstractmethod def reset(self, item: Class, qname: str) -> None: """Update the given class qualified name. Args: item: The target class instance to update qname: The new qualified name of the class """ @abc.abstractmethod def set(self, items: list[Class]) -> None: """Set the list of classes to the container. Args: items: The list of classes """ class HandlerInterface(abc.ABC): """Class handler interface.""" __slots__ = () @abc.abstractmethod def process(self, target: Class) -> None: """Process the given target class. Args: target: The target class instance """ class RelativeHandlerInterface(HandlerInterface, abc.ABC): """An interface for codegen handlers with class container access. Args: container: The container instance """ __slots__ = "container" def __init__(self, container: ContainerInterface): """Initialize the class.""" self.container = container def base_attrs(self, target: Class) -> list[Attr]: """Return a list of all parent attrs recursively. Args: target: The target class Returns: A list of attr instances. """ attrs: list[Attr] = [] for extension in target.extensions: base = self.container.find(extension.type.qname) assert base is not None for attr in base.attrs: attr.parent = base.qname attrs.append(attr) attrs.extend(self.base_attrs(base)) return attrs @abc.abstractmethod def process(self, target: Class) -> None: """Process entrypoint for a class. Args: target: The target class instance """ class ContainerHandlerInterface(abc.ABC): """A codegen interface for processing the whole class container. Args: container: The class container instance """ __slots__ = "container" def __init__(self, container: ContainerInterface): """Initialize the class.""" self.container = container @abc.abstractmethod def run(self) -> None: """Run the process for the whole container.""" tefra-xsdata-cf7afe8/xsdata/codegen/models.py000066400000000000000000000621631526437326600214350ustar00rootroot00000000000000import copy import operator import sys import unicodedata from collections.abc import Iterator from dataclasses import asdict, dataclass, field, fields, replace from enum import IntEnum from typing import Any, Optional, TypeVar from xsdata.codegen.exceptions import CodegenError from xsdata.formats.converter import converter from xsdata.formats.dataclass.models.elements import XmlType from xsdata.models.enums import DataType, Namespace, Tag from xsdata.models.mixins import ElementBase from xsdata.utils import namespaces, text xml_type_map = { Tag.ANY: XmlType.WILDCARD, Tag.ANY_ATTRIBUTE: XmlType.ATTRIBUTES, Tag.ATTRIBUTE: XmlType.ATTRIBUTE, Tag.CHOICE: XmlType.ELEMENTS, Tag.ELEMENT: XmlType.ELEMENT, } GLOBAL_TYPES = ( Tag.ELEMENT, Tag.COMPLEX_TYPE, Tag.BINDING_OPERATION, Tag.BINDING_MESSAGE, Tag.MESSAGE, ) T = TypeVar("T", bound="CodegenModel") @dataclass class CodegenModel: """Base codegen model.""" def clone(self: T, **kwargs: Any) -> T: """Return a deep cloned instance.""" clone = copy.deepcopy(self) return replace(clone, **kwargs) if kwargs else clone def swap(self, source: "CodegenModel") -> None: """Swap the instance attributes from the source instance.""" for f in fields(self): value = copy.deepcopy(getattr(source, f.name)) setattr(self, f.name, value) @dataclass class Restrictions(CodegenModel): """Class field validation restrictions. Args: min_occurs: The minimum number of occurrences max_occurs: The maximum number of occurrences min_exclusive: The lower exclusive bound for numeric values min_inclusive: The lower inclusive bound for numeric values min_length: The minimum length of characters or list items allowed max_exclusive: The upper exclusive bound for numeric values max_inclusive: The upper inclusive bound for numeric values max_length: The max length of characters or list items allowed total_digits: The exact number of digits allowed for numeric values fraction_digits: The maximum number of decimal places allowed length: The exact number of characters or list items allowed white_space: Specifies how white space is handled pattern: Defines the exact sequence of characters that are acceptable explicit_timezone: Require or prohibit the time zone offset in date/time nillable: Specifies whether nil content is allowed sequence: The sequence reference number of the attr tokens: Specifies whether the value needs tokenization format: The output format used for byte and datetime types choice: The choice reference number of the attr group: The group reference number of the attr process_contents: Specifies the content processed mode: strict, lax, skip path: The coded attr path in the source document """ min_occurs: int | None = field(default=None) max_occurs: int | None = field(default=None) min_exclusive: str | None = field(default=None) min_inclusive: str | None = field(default=None) min_length: int | None = field(default=None) max_exclusive: str | None = field(default=None) max_inclusive: str | None = field(default=None) max_length: int | None = field(default=None) total_digits: int | None = field(default=None) fraction_digits: int | None = field(default=None) length: int | None = field(default=None) white_space: str | None = field(default=None) pattern: str | None = field(default=None) explicit_timezone: str | None = field(default=None) nillable: bool | None = field(default=None) sequence: int | None = field(default=None, compare=False) tokens: bool | None = field(default=None) format: str | None = field(default=None) choice: int | None = field(default=None, compare=False) group: int | None = field(default=None) process_contents: str | None = field(default=None) path: list[tuple[str, int, int, int]] = field(default_factory=list) @property def is_list(self) -> bool: """Return whether the max occurs larger than one.""" return self.max_occurs is not None and self.max_occurs > 1 @property def is_optional(self) -> bool: """Return whether the min occurs is zero.""" return self.min_occurs == 0 @property def is_prohibited(self) -> bool: """Return whether the max occurs is zero.""" return self.max_occurs == 0 def merge(self, source: "Restrictions") -> None: """Update properties from another instance. Args: source: The source instance to merge properties from """ keys = ( "min_exclusive", "min_inclusive", "min_length", "max_exclusive", "max_inclusive", "max_length", "total_digits", "fraction_digits", "length", "white_space", "pattern", "explicit_timezone", "process_contents", ) for key in keys: value = getattr(source, key) if value is not None: setattr(self, key, value) self.path = source.path + self.path self.sequence = self.sequence or source.sequence self.choice = self.choice or source.choice self.tokens = self.tokens or source.tokens self.format = self.format or source.format self.group = self.group or source.group if self.min_occurs is None and source.min_occurs is not None: self.min_occurs = source.min_occurs if self.max_occurs is None and source.max_occurs is not None: self.max_occurs = source.max_occurs def asdict(self, types: list[type] | None = None) -> dict: """Return the initialized only properties as a dictionary. Skip None or implied values, and optionally use the attribute types to convert relevant options. Args: types: An optional list of attr python types Returns: A key-value of map of the attr restrictions for generation. """ result = {} sorted_types = converter.sort_types(types) if types else [] if self.is_list: if self.min_occurs is not None and self.min_occurs > 0: result["min_occurs"] = self.min_occurs if self.max_occurs is not None and self.max_occurs < sys.maxsize: result["max_occurs"] = self.max_occurs elif ( self.min_occurs == self.max_occurs == 1 and not self.nillable and not self.tokens ): result["required"] = True for key, value in asdict(self).items(): if value is None or key in ( "choice", "group", "min_occurs", "max_occurs", "path", ): continue if key == "process_contents" and value != "skip": continue if key.endswith("clusive") and types: value = converter.deserialize(value, sorted_types) result[key] = value return result @classmethod def from_element(cls, element: ElementBase) -> "Restrictions": """Static constructor from a xsd model. Args: element: A element base instance. Returns: The new restrictions instance """ return cls(**element.get_restrictions()) @dataclass(unsafe_hash=True) class AttrType(CodegenModel): """Class field typing information. Args: qname: The namespace qualified name alias: The type alias reference: The type reference number native: Specifies if it's python native type forward: Specifies if it's a forward reference circular: Specifies if it's a circular reference substituted: Specifies if it has been processed for substitution groups """ qname: str alias: str | None = field(default=None, compare=False) reference: int = field(default=0, compare=False) native: bool = field(default=False) forward: bool = field(default=False) circular: bool = field(default=False) substituted: bool = field(default=False, compare=False) @property def datatype(self) -> DataType | None: """Return the datatype instance if native, none otherwise.""" return DataType.from_qname(self.qname) if self.native else None @property def name(self) -> str: """Shortcut for qname local name.""" return namespaces.local_name(self.qname) def is_dependency(self, allow_circular: bool) -> bool: """Return whether this type is a dependency. The type must a reference to a user type, not a forward reference and not a circular unless if it's allowed. Args: allow_circular: Allow circular references as dependencies Returns: The bool result/ """ return not ( self.forward or self.native or (not allow_circular and self.circular) ) @dataclass class Attr(CodegenModel): """Class field model representation. Args: tag: The xml tag that produced this attr name: The final attr name local_name: The original attr name wrapper: The wrapper element name index: The index position of this attr in the class default: The default value fixed: Specifies if the default value is fixed mixed: Specifies if the attr supports mixed content types: The attr types list choices: The attr choice list namespace: The attr namespace help: The attr help text restrictions: The attr restrictions instance parent: The parent class qualified name of the attr substitution: The substitution group this attr belongs to """ tag: str name: str = field(compare=False) local_name: str = field(default="") wrapper: str | None = field(default=None) index: int = field(compare=False, default_factory=int) default: str | None = field(default=None, compare=False) fixed: bool = field(default=False, compare=False) mixed: bool = field(default=False, compare=False) types: list[AttrType] = field(default_factory=list, compare=False) choices: list["Attr"] = field(default_factory=list, compare=False) namespace: str | None = field(default=None) help: str | None = field(default=None, compare=False) restrictions: Restrictions = field(default_factory=Restrictions, compare=False) parent: str | None = field(default=None, compare=False) substitution: str | None = field(default=None, compare=False) def __post_init__(self): """Post init processing.""" if not self.local_name: self.local_name = self.name if text.alnum(self.name) == "": self.name = "_".join(unicodedata.name(char) for char in self.name) @property def key(self) -> str: """Generate a key for this attr. Concatenate the tag/namespace/local_name. This key is used to find duplicates, it's not supposed to be unique. Returns: The unique key for this attr. """ return f"{self.tag}.{self.namespace}.{self.local_name}" @property def qname(self) -> str: """Return the fully qualified name of the attr.""" return namespaces.build_qname(self.namespace, self.local_name) @property def is_attribute(self) -> bool: """Return whether this attr represents a xml attribute node.""" return self.tag in (Tag.ATTRIBUTE, Tag.ANY_ATTRIBUTE) @property def is_element(self) -> bool: """Return whether this attr represents a xml element.""" return self.tag == Tag.ELEMENT @property def is_enumeration(self) -> bool: """Return whether this attr an enumeration member.""" return self.tag == Tag.ENUMERATION @property def is_dict(self) -> bool: """Return whether this attr is derived from xs:anyAttribute.""" return self.tag == Tag.ANY_ATTRIBUTE @property def is_factory(self) -> bool: """Return whether this attribute is a list of items or a mapping.""" return self.is_list or self.is_dict or self.is_tokens @property def is_forward_ref(self) -> bool: """Return whether any attr types is a forward or circular reference.""" return any(tp.circular or tp.forward for tp in self.types) @property def is_circular_ref(self) -> bool: """Return whether any attr types is a circular reference.""" return any(tp.circular for tp in self.types) @property def is_group(self) -> bool: """Return whether this attr is a reference to a group class.""" return self.tag in (Tag.ATTRIBUTE_GROUP, Tag.GROUP) @property def is_list(self) -> bool: """Return whether this attr requires a list of values.""" return self.restrictions.is_list @property def is_prohibited(self) -> bool: """Return whether this attr is prohibited.""" return self.restrictions.is_prohibited @property def is_nameless(self) -> bool: """Return whether this attr is a real xml node.""" return self.tag not in (Tag.ATTRIBUTE, Tag.ELEMENT) @property def is_nillable(self) -> bool: """Return whether this attr supports nil values.""" return self.restrictions.nillable is True @property def is_optional(self) -> bool: """Return whether this attr is not required.""" return self.restrictions.is_optional @property def is_suffix(self) -> bool: """Return whether this attr is supposed to be generated last.""" return self.index == sys.maxsize @property def is_xsi_type(self) -> bool: """Return whether this attr represents a xsi:type attribute.""" return self.namespace == Namespace.XSI.uri and self.name == "type" @property def is_tokens(self) -> bool: """Return whether this attr supports token values.""" return self.restrictions.tokens is True @property def is_wildcard(self) -> bool: """Return whether this attr supports any content.""" return self.tag in (Tag.ANY_ATTRIBUTE, Tag.ANY) @property def is_any_type(self) -> bool: """Return whether this attr types support any content.""" return any(tp is object for tp in self.get_native_types()) @property def native_types(self) -> list[type]: """Return a list of all the builtin data types.""" return list(set(self.get_native_types())) @property def user_types(self) -> Iterator[AttrType]: """Yield an iterator of all the user defined types.""" for tp in self.types: if not tp.native: yield tp @property def slug(self) -> str: """Return the slugified name of the attr.""" return text.alnum(self.name) @property def xml_type(self) -> str | None: """Return the xml type this attribute is mapped to.""" return xml_type_map.get(self.tag) def get_native_types(self) -> Iterator[type]: """Yield an iterator of all the native attr types.""" for tp in self.types: datatype = tp.datatype if datatype: yield datatype.type def can_be_restricted(self) -> bool: """Return whether this attr can be restricted.""" return self.xml_type not in (Tag.ATTRIBUTE, None) @dataclass(unsafe_hash=True) class Extension(CodegenModel): """Base class model representation. Args: tag: The xml tag that produced this extension type: The extension type restrictions: The extension restrictions instance """ tag: str type: AttrType restrictions: Restrictions = field(hash=False) class Status(IntEnum): """Class process status enumeration.""" RAW = 0 UNGROUPING = 10 UNGROUPED = 11 FLATTENING = 20 FLATTENED = 21 SANITIZING = 30 SANITIZED = 31 RESOLVING = 40 RESOLVED = 41 CLEANING = 50 CLEANED = 51 FINALIZING = 60 FINALIZED = 61 @dataclass class Class(CodegenModel): """Class model representation. Args: qname: The namespace qualified name tag: The xml tag that produced this class location: The schema/document location uri mixed: Specifies whether this class supports mixed content abstract: Specifies whether this is an abstract class nillable: Specifies whether this class supports nil content local_type: Specifies if this class was an inner type at some point status: The processing status of the class container: The xml container of the class, schema, override, redefine package: The designated package of the class module: The designated module of the class namespace: The class namespace help: The help text meta_name: The xml element name of the class default: The default value fixed: Specifies whether the default value is fixed substitutions: The list of all the substitution groups this class belongs to extensions: The list of all the extension instances attrs: The list of all the attr instances inner: The list of all the inner class instances ns_map: The namespace prefix-URI map parent: The parent outer class """ qname: str tag: str location: str = field(compare=False) mixed: bool = field(default=False) abstract: bool = field(default=False) nillable: bool = field(default=False) local_type: bool = field(default=False) status: Status = field(default=Status.RAW) container: str | None = field(default=None) package: str | None = field(default=None) module: str | None = field(default=None) namespace: str | None = field(default=None) help: str | None = field(default=None) meta_name: str | None = field(default=None) default: Any = field(default=None, compare=False) fixed: bool = field(default=False, compare=False) substitutions: list[str] = field(default_factory=list) extensions: list[Extension] = field(default_factory=list) attrs: list[Attr] = field(default_factory=list) inner: list["Class"] = field(default_factory=list) ns_map: dict = field(default_factory=dict) parent: Optional["Class"] = field(default=None, compare=False) @property def name(self) -> str: """Shortcut for the class local name.""" return namespaces.local_name(self.qname) @property def slug(self) -> str: """Return a slugified version of the class name.""" return text.alnum(self.name) @property def ref(self) -> int: """Return this id reference of this instance.""" return id(self) @property def target_namespace(self) -> str | None: """Return the class target namespace.""" return namespaces.target_uri(self.qname) @property def has_suffix_attr(self) -> bool: """Return whether it includes a suffix attr.""" return any(attr.is_suffix for attr in self.attrs) @property def has_help_attr(self) -> bool: """Return whether at least one of attrs has help content.""" return any(attr.help and attr.help.strip() for attr in self.attrs) @property def is_element(self) -> bool: """Return whether this class represents a xml element.""" return self.tag == Tag.ELEMENT @property def is_enumeration(self) -> bool: """Return whether all attrs are enumeration members.""" return len(self.attrs) > 0 and all(attr.is_enumeration for attr in self.attrs) @property def is_complex_type(self) -> bool: """Return whether this class represents a root/global class. Global classes are the only classes that get generated by default. """ return self.tag in GLOBAL_TYPES @property def is_group(self) -> bool: """Return whether this class is derived from a xs:group/attributeGroup.""" return self.tag in (Tag.ATTRIBUTE_GROUP, Tag.GROUP) @property def is_nillable(self) -> bool: """Return whether this class represents a nillable xml element.""" return self.nillable or any(x.restrictions.nillable for x in self.extensions) @property def is_mixed(self) -> bool: """Return whether this class supports mixed content.""" return self.mixed or any(x.mixed for x in self.attrs) @property def is_restricted(self) -> bool: """Return whether this class includes any restriction extensions.""" return any( True for extension in self.extensions if extension.tag == Tag.RESTRICTION ) @property def is_service(self) -> bool: """Return whether this instance is derived from a wsdl:operation.""" return self.tag == Tag.BINDING_OPERATION @property def references(self) -> Iterator[int]: """Yield all class object reference numbers.""" for tp in self.types(): if tp.reference: yield tp.reference @property def target_module(self) -> str: """Return the designated full module path. Raises: CodeGenerationError: if the target was not designated a package and module. """ if self.package and self.module: return f"{self.package}.{self.module}" if self.module: return self.module raise CodegenError( "Type has not been assigned to a module yet!", type=self.qname ) def dependencies(self, allow_circular: bool = False) -> Iterator[str]: """Yields all class dependencies. Omit circular and forward references by default. Collect: * base classes * attribute types * attribute choice types * recursively go through the inner classes * Ignore inner class references * Ignore native types. Args: allow_circular: Allow circular references """ for tp in set(self.types()): if tp.is_dependency(allow_circular=allow_circular): yield tp.qname def types(self) -> Iterator[AttrType]: """Yields all class types.""" for _, tp in self.types_with_parents(): yield tp def types_with_parents(self) -> Iterator[tuple[CodegenModel, AttrType]]: """Yields all class types with their parent codegen instance.""" for ext in self.extensions: yield ext, ext.type for attr in self.attrs: for tp in attr.types: yield attr, tp for choice in attr.choices: for tp in choice.types: yield choice, tp for inner in self.inner: yield from inner.types_with_parents() def children(self) -> Iterator[CodegenModel]: """Yield all codegen children.""" for attr in self.attrs: yield attr yield attr.restrictions yield from attr.types for choice in attr.choices: yield choice yield choice.restrictions yield from choice.types for ext in self.extensions: yield ext yield ext.type yield ext.restrictions for inner in self.inner: yield from inner.children() def has_forward_ref(self) -> bool: """Return whether this class has any forward references.""" for attr in self.attrs: if attr.is_forward_ref: return True if any(choice for choice in attr.choices if choice.is_forward_ref): return True return any(inner.has_forward_ref() for inner in self.inner) def parent_names(self) -> list[str]: """Return the outer class names.""" result = [] target = self.parent while target is not None: result.append(target.name) target = target.parent return list(reversed(result)) @dataclass class Import: """Python import statement model representation. Args: qname: The qualified name of the imported class source: The absolute module path alias: Specifies an alias to avoid naming conflicts """ qname: str source: str alias: str | None = field(default=None) @property def name(self) -> str: """Return the name of the imported class.""" return namespaces.local_name(self.qname) @property def slug(self) -> str: """Return a slugified version of the imported class name.""" return text.alnum(self.name) # Getters used all over the codegen process get_location = operator.attrgetter("location") get_name = operator.attrgetter("name") get_qname = operator.attrgetter("qname") get_tag = operator.attrgetter("tag") get_restriction_choice = operator.attrgetter("restrictions.choice") get_restriction_sequence = operator.attrgetter("restrictions.sequence") get_slug = operator.attrgetter("slug") get_target_namespace = operator.attrgetter("target_namespace") is_enumeration = operator.attrgetter("is_enumeration") is_group = operator.attrgetter("is_group") tefra-xsdata-cf7afe8/xsdata/codegen/parsers/000077500000000000000000000000001526437326600212475ustar00rootroot00000000000000tefra-xsdata-cf7afe8/xsdata/codegen/parsers/__init__.py000066400000000000000000000003661526437326600233650ustar00rootroot00000000000000from xsdata.codegen.parsers.definitions import DefinitionsParser from xsdata.codegen.parsers.dtd import DtdParser from xsdata.codegen.parsers.schema import SchemaParser __all__ = [ "DefinitionsParser", "DtdParser", "SchemaParser", ] tefra-xsdata-cf7afe8/xsdata/codegen/parsers/definitions.py000066400000000000000000000030351526437326600241350ustar00rootroot00000000000000from dataclasses import dataclass from typing import Any from xsdata.codegen.parsers.schema import SchemaParser from xsdata.formats.dataclass.parsers.bases import Parsed from xsdata.formats.dataclass.parsers.mixins import XmlNode from xsdata.formats.types import T from xsdata.models import wsdl @dataclass class DefinitionsParser(SchemaParser): """Parse a wsdl document into data models.""" def end( self, queue: list[XmlNode], objects: list[Parsed], qname: str, text: str | None, tail: str | None, ) -> Any: """Parse the last xml node and bind any intermediate objects. Override parent method to set source location in every wsdl element. Args: queue: The XmlNode queue list objects: The list of all intermediate parsed objects qname: The element qualified name text: The element text content tail: The element tail content Returns: Whether the binding process was successful. """ obj = super().end(queue, objects, qname, text, tail) if isinstance(obj, wsdl.WsdlElement): obj.location = self.location return obj def end_import(self, obj: T) -> None: """End import element entrypoint. Resolve the location path of import elements. Args: obj: The wsdl import element. """ if isinstance(obj, wsdl.Import) and self.location: obj.location = self.resolve_path(obj.location) tefra-xsdata-cf7afe8/xsdata/codegen/parsers/dtd.py000066400000000000000000000076521526437326600224060ustar00rootroot00000000000000import io from typing import Any from xsdata.exceptions import ParserError from xsdata.models.dtd import ( Dtd, DtdAttribute, DtdAttributeDefault, DtdAttributeType, DtdContent, DtdContentOccur, DtdContentType, DtdElement, DtdElementType, ) from xsdata.models.enums import Namespace class DtdParser: """Document type definition parser. The parser requires lxml package to be installed. """ @classmethod def parse(cls, source: bytes, location: str) -> Dtd: """Parse the input source bytes object into a dtd instance. Args: source: The source bytes object to parse location: The source location uri Returns: A dtd instance representing the parsed content. """ try: from lxml import etree dtd = etree.DTD(io.BytesIO(source)) except ImportError: raise ParserError("DtdParser requires lxml to run.") elements = list(map(cls.build_element, dtd.iterelements())) return Dtd(elements=elements, location=location) @classmethod def build_element(cls, element: Any) -> DtdElement: """Build a dtd element from the lxml element. Args: element: The lxml dtd element instance Returns: The converted xsdata dtd element instance. """ content = cls.build_content(element.content) attributes = list(map(cls.build_attribute, element.iterattributes())) ns_map = cls.build_ns_map(element.prefix, attributes) return DtdElement( name=element.name, prefix=element.prefix, type=DtdElementType(element.type), content=content, attributes=attributes, ns_map=ns_map, ) @classmethod def build_content(cls, content: Any) -> DtdContent | None: """Build a dtd content instance from the lxml content. Args: content: The lxml content instance Returns: The converted xsdata dtd content instance, or None if the content is empty. """ if not content: return None return DtdContent( name=content.name, occur=DtdContentOccur(content.occur), type=DtdContentType(content.type), left=cls.build_content(content.left), right=cls.build_content(content.right), ) @classmethod def build_attribute(cls, attribute: Any) -> DtdAttribute: """Build a dtd attribute instance from the lxml instance. Args: attribute: The lxml attribute instance Returns: The converted xsdata dtd attribute instance. """ return DtdAttribute( prefix=attribute.prefix, name=attribute.name, type=DtdAttributeType(attribute.type), default=DtdAttributeDefault(attribute.default), default_value=attribute.default_value, values=attribute.values(), ) @classmethod def build_ns_map(cls, prefix: str, attributes: list[DtdAttribute]) -> dict: """Build the dtd element namespace prefix-URI map. It also adds common namespaces like xs, xsi, xlink and xml. Args: prefix: The element namespace prefix attributes: Element attributes, to extract any xmlns keys Returns: The element namespace prefix-URI map. """ ns_map = {ns.prefix: ns.uri for ns in Namespace.common()} for attribute in attributes.copy(): if not attribute.default_value: continue if attribute.prefix == "xmlns": ns_map[attribute.name] = attribute.default_value attributes.remove(attribute) elif attribute.name == "xmlns": ns_map[prefix] = attribute.default_value attributes.remove(attribute) return ns_map tefra-xsdata-cf7afe8/xsdata/codegen/parsers/schema.py000066400000000000000000000317541526437326600230730ustar00rootroot00000000000000import sys from dataclasses import dataclass, field from typing import Any from urllib.parse import urljoin from xsdata.formats.dataclass.parsers.bases import Parsed from xsdata.formats.dataclass.parsers.mixins import XmlNode from xsdata.formats.dataclass.parsers.xml import UserXmlParser from xsdata.formats.types import T from xsdata.models import xsd from xsdata.models.enums import FormType, Mode, Namespace from xsdata.models.mixins import ElementBase OPEN_CONTENT_ELEMENT = xsd.ComplexType | xsd.Restriction | xsd.Extension @dataclass class SchemaParser(UserXmlParser): """Xml schema definition parser. Apply implied rules, set indexes, resolve location paths... Args: location: The schema location uri target_namespace: The schema target namespace Attributes: index: The current element index indices: The child element indices element_form: The schema element form attribute_form: The schema attribute form default_attributes: The schema default attributes default_open_content: The schema default open content """ location: str | None = field(default=None) target_namespace: str | None = field(default=None) index: int = field(default_factory=int, init=False) indices: list[int] = field(default_factory=list, init=False) element_form: str | None = field(default=None, init=False) attribute_form: str | None = field(default=None, init=False) default_attributes: str | None = field(default=None, init=False) default_open_content: xsd.DefaultOpenContent | None = field( default=None, init=False ) def start( self, clazz: type[T] | None, queue: list[XmlNode], objects: list[Parsed], qname: str, attrs: dict, ns_map: dict, ) -> None: """Build and queue the XmlNode for the starting element. Override to set the current element index and append it in child element indices. Args: clazz: The target class type, auto locate if omitted queue: The XmlNode queue list objects: The list of all intermediate parsed objects qname: The element qualified name attrs: The element attributes ns_map: The element namespace prefix-URI map """ self.index += 1 self.indices.append(self.index) super().start(clazz, queue, objects, qname, attrs, ns_map) def end( self, queue: list[XmlNode], objects: list[Parsed], qname: str, text: str | None, tail: str | None, ) -> Any: """Parse the last xml node and bind any intermediate objects. Override to set the xsd model index and ns map. Args: queue: The XmlNode queue list objects: The list of all intermediate parsed objects qname: The element qualified name text: The element text content tail: The element tail content Returns: Whether the binding process was successful. """ item = queue[-1] super().end(queue, objects, qname, text, tail) obj = objects[-1][1] self.set_index(obj, self.indices.pop()) self.set_namespace_map(obj, getattr(item, "ns_map", None)) return obj def start_schema(self, attrs: dict[str, str]) -> None: """Start schema element entrypoint. Store the element/attribute default forms and the default attributes, for later processing. Args: attrs: The element attributes """ self.element_form = attrs.get("elementFormDefault") self.attribute_form = attrs.get("attributeFormDefault") self.default_attributes = attrs.get("defaultAttributes") def end_schema(self, obj: T) -> None: """End schema element entrypoint. Normalize various properties for the schema and it's children. Args: obj: The xsd schema instance. """ if isinstance(obj, xsd.Schema): self.set_schema_forms(obj) self.set_schema_namespaces(obj) self.add_default_imports(obj) self.resolve_schemas_locations(obj) def end_attribute(self, obj: T) -> None: """End attribute element entrypoint. Assign the schema's default form in the attribute instance, if it doesn't define its own. Args: obj: The xsd attribute instance """ if isinstance(obj, xsd.Attribute) and obj.form is None and self.attribute_form: obj.form = FormType(self.attribute_form) def end_complex_type(self, obj: T) -> None: """End complex type element entrypoint. Post parsing processor to apply default open content and attributes if applicable. Default open content doesn't apply if the current complex type has one of complex content, simple content or has its own open content. Args: obj: The xsd complex type instance """ if not isinstance(obj, xsd.ComplexType): return if obj.default_attributes_apply and self.default_attributes: attribute_group = xsd.AttributeGroup(ref=self.default_attributes) obj.attribute_groups.insert(0, attribute_group) if ( obj.simple_content or obj.complex_content or obj.open_content or not self.default_open_content ): return if self.default_open_content.applies_to_empty or self.has_elements(obj): obj.open_content = self.default_open_content def end_default_open_content(self, obj: T) -> None: """End default open content element entrypoint. If the open content element mode is suffix, adjust the index to trick later processors into putting attrs derived from this open content last in the generated classes. Store the obj for later processing. Args: obj: The xsd default open content instance . """ if isinstance(obj, xsd.DefaultOpenContent): if obj.any and obj.mode == Mode.SUFFIX: obj.any.index = sys.maxsize self.default_open_content = obj def end_element(self, obj: T) -> None: """End element entrypoint. Assign the schema's default form in the element instance, if it doesn't define its own. Args: obj: The xsd element instance """ if isinstance(obj, xsd.Element) and obj.form is None and self.element_form: obj.form = FormType(self.element_form) def end_extension(self, obj: T) -> None: """End extension element entrypoint. Assign the schema's default open content in the extension instance, if it doesn't define its own. Args: obj: The xsd extension instance """ if isinstance(obj, xsd.Extension) and not obj.open_content: obj.open_content = self.default_open_content @classmethod def end_open_content(cls, obj: T): """End open content element entrypoint. If the open content element mode is suffix, adjust the index to trick later processors into putting attrs derived from this open content last in the generated classes. Args: obj: The xsd open content instance """ if isinstance(obj, xsd.OpenContent) and obj.any and obj.mode == Mode.SUFFIX: obj.any.index = sys.maxsize def end_restriction(self, obj: T) -> None: """End restriction element entrypoint. Assign the schema's default open content in the restriction instance, if it doesn't define its own. Args: obj: The xsd restriction instance """ if isinstance(obj, xsd.Restriction) and not obj.open_content: obj.open_content = self.default_open_content def set_schema_forms(self, obj: xsd.Schema) -> None: """Cascade schema forms to elements and attributes. Args: obj: The xsd schema instance """ if self.element_form: obj.element_form_default = FormType(self.element_form) if self.attribute_form: obj.attribute_form_default = FormType(self.attribute_form) for child_element in obj.elements: child_element.form = FormType.QUALIFIED for child_attribute in obj.attributes: child_attribute.form = FormType.QUALIFIED def set_schema_namespaces(self, obj: xsd.Schema) -> None: """Set the schema target namespace. If the schema was imported and doesn't have a target namespace, it automatically inherits the parent schema target namespace. Args: obj: The xsd schema instance """ obj.target_namespace = obj.target_namespace or self.target_namespace def resolve_schemas_locations(self, obj: xsd.Schema) -> None: """Resolve the location attributes of the schema. This method covers relative paths and implied schema locations to common namespaces like xsi, xlink. Schema elements with location attribute: - override - redefines - include - import Args: obj: The xsd schema instance """ if not self.location: return obj.location = self.location for over in obj.overrides: over.location = self.resolve_path(over.schema_location) for red in obj.redefines: red.location = self.resolve_path(red.schema_location) for inc in obj.includes: inc.location = self.resolve_path(inc.schema_location) for imp in obj.imports: imp.location = self.resolve_local_path(imp.schema_location, imp.namespace) def resolve_path(self, location: str | None) -> str | None: """Resolve the given location string. Use the parser location attribute as the base uri. Args: location: The location uri Returns: The resolved location or None if it was not resolved """ return urljoin(self.location, location) if self.location and location else None def resolve_local_path( self, location: str | None, namespace: str | None, ) -> str | None: """Resolve the given namespace to one of the local standard schemas. w3.org protects against fetching common schemas not from a browser, instead we use the local xsdata copies. Args: location: The schema location namespace: The schema namespace Returns: The local path or the absolute remote uri. """ common_ns = Namespace.get_enum(namespace) local_path = common_ns.location if common_ns else None if local_path and (not location or location.find("w3.org/") > 0): return local_path return self.resolve_path(location) @classmethod def has_elements(cls, obj: ElementBase) -> bool: """Helper function to check if instance has children. Valid children: xs:element, xs:any, xs:group. Args: obj: The element base instance Returns: The bool result. """ accepted_types = (xsd.Element, xsd.Any, xsd.Group) return any( isinstance(child, accepted_types) or cls.has_elements(child) for child in obj.children() ) @classmethod def set_namespace_map(cls, obj: Any, ns_map: dict | None): """Add common namespaces like xml, xsi, xlink if they are missing. These prefixes are implied and we need to support them. Args: obj: A xsd model instance ns_map: The namespace prefix-URI map """ if hasattr(obj, "ns_map"): if ns_map: obj.ns_map.update( {prefix: uri for prefix, uri in ns_map.items() if uri} ) ns_list = obj.ns_map.values() obj.ns_map.update( { ns.prefix: ns.uri for ns in Namespace.common() if ns.uri not in ns_list } ) @classmethod def set_index(cls, obj: Any, index: int): """Helper method to set an object's index. Args: obj: A xsd model instance index: The index number """ if hasattr(obj, "index"): obj.index = index @classmethod def add_default_imports(cls, obj: xsd.Schema): """Add missing imports to the standard schemas. We might need to generate the classes from these common schemas, so add these implied imports. Args: obj: The xsd schema instance """ imp_namespaces = [imp.namespace for imp in obj.imports] xsi_ns = Namespace.XSI.uri if xsi_ns in obj.ns_map.values() and xsi_ns not in imp_namespaces: obj.imports.insert(0, xsd.Import(namespace=xsi_ns)) tefra-xsdata-cf7afe8/xsdata/codegen/resolver.py000066400000000000000000000134721526437326600220120ustar00rootroot00000000000000import logging import re from toposort import toposort_flatten from xsdata.codegen.exceptions import CodegenError from xsdata.codegen.models import Class, Import, get_slug from xsdata.utils import collections logger = logging.getLogger(__name__) class DependenciesResolver: """The dependencies resolver class. Calculate what classes need to be imported per package, with aliases support. Args: registry: The full class qname-module map. Attributes: aliases: The generated aliases dictionary imports: The list of generated imports class_list: The topo-sorted list of class qnames class_map: A qname-class map """ __slots__ = "aliases", "class_list", "class_map", "imports", "registry" def __init__(self, registry: dict[str, str]): """Initialize the resolver.""" self.registry = registry self.aliases: dict[str, str] = {} self.imports: list[Import] = [] self.class_list: list[str] = [] self.class_map: dict[str, Class] = {} def process(self, classes: list[Class]) -> None: """Resolve the dependencies for the given class list. Reset previously resolved imports and aliases. Args: classes: A list of classes that belong to the same target module """ self.imports.clear() self.aliases.clear() self.class_map = self.create_class_map(classes) self.class_list = self.create_class_list(classes) self.resolve_imports() def sorted_imports(self) -> list[Import]: """Return a new sorted by name list of import instances.""" return sorted(self.imports, key=lambda x: x.name) def sorted_classes(self) -> list[Class]: """Apply aliases and return the sorted the generated class list.""" result = [] for name in self.class_list: obj = self.class_map.get(name) if obj is not None: self.apply_aliases(obj) result.append(obj) return result def apply_aliases(self, target: Class) -> None: """Apply import aliases to the target class. Update attr and extension types to point to the new class aliases. Process inner classes too! Args: target: The target class instance to process """ for attr in target.attrs: for attr_type in attr.types: attr_type.alias = self.aliases.get(attr_type.qname) for choice in attr.choices: for choice_type in choice.types: choice_type.alias = self.aliases.get(choice_type.qname) for ext in target.extensions: ext.type.alias = self.aliases.get(ext.type.qname) collections.apply(target.inner, self.apply_aliases) def resolve_imports(self) -> None: """Build the list of class imports and set aliases if necessary.""" self.imports = [ Import(qname=qname, source=self.get_class_module(qname)) for qname in self.import_classes() ] protected = {obj.slug for obj in self.class_map.values()} self.resolve_conflicts(self.imports, protected) self.set_aliases() def set_aliases(self) -> None: """Store generated aliases.""" self.aliases = {imp.qname: imp.alias for imp in self.imports if imp.alias} @classmethod def resolve_conflicts(cls, imports: list[Import], protected: set): """Find naming conflicts between imports and generate aliases. Example: from foo.bar import MyType as BarMyType from bar.foo import MyType as FooMyType Args: imports: The list of class import instances protected: The set of protected class names from the module """ for slug, group in collections.group_by(imports, key=get_slug).items(): if len(group) == 1: if slug in protected: imp = group[0] module = imp.source.split(".")[-1] imp.alias = f"{module}:{imp.name}" continue for index, cur in enumerate(group): cmp = group[index + 1] if index == 0 else group[index - 1] parts = re.split("[_.]", cur.source) diff = set(parts) - set(re.split("[_.]", cmp.source)) add = "_".join(part for part in parts if part in diff) cur.alias = f"{add}:{cur.name}" def get_class_module(self, qname: str) -> str: """Return the module for the given qualified class name. Args: qname: The namespace qualified name of the class Raises: CodeGenerationError: if name doesn't exist. """ if qname not in self.registry: raise CodegenError("Failed to resolve dependency", qname=qname) return self.registry[qname] def import_classes(self) -> list[str]: """Return a list of class qnames that need to be imported.""" return [qname for qname in self.class_list if qname not in self.class_map] @staticmethod def create_class_list(classes: list[Class]) -> list[str]: """Use topology sort to return a flat list for all the dependencies.""" return toposort_flatten({obj.qname: set(obj.dependencies()) for obj in classes}) @staticmethod def create_class_map(classes: list[Class]) -> dict[str, Class]: """Index the list of classes by their qualified names. Raises: CodeGenerationError: If two classes have the same qname. Returns: A qname-class map. """ result: dict[str, Class] = {} for obj in classes: if obj.qname in result: raise CodegenError("Duplicate class during resolve", qname=obj.qname) result[obj.qname] = obj return result tefra-xsdata-cf7afe8/xsdata/codegen/stopwatch.py000066400000000000000000000007041526437326600221570ustar00rootroot00000000000000from collections import defaultdict from collections.abc import Iterator from contextlib import contextmanager from time import perf_counter_ns @contextmanager def stopwatch(name: str) -> Iterator[None]: """Simple context manager that logs elapsed times.""" start_time = perf_counter_ns() yield stop_time = perf_counter_ns() stopwatches[name].append(stop_time - start_time) stopwatches: dict[str, list[int]] = defaultdict(list) tefra-xsdata-cf7afe8/xsdata/codegen/transformer.py000066400000000000000000000344001526437326600225050ustar00rootroot00000000000000import hashlib import io import json import os import pickle import tempfile from collections import defaultdict from collections.abc import Callable from pathlib import Path from typing import NamedTuple from toposort import CircularDependencyError from xsdata import __version__ from xsdata.codegen import opener from xsdata.codegen.container import ClassContainer from xsdata.codegen.exceptions import CodegenError from xsdata.codegen.mappers import ( DefinitionsMapper, DictMapper, DtdMapper, ElementMapper, SchemaMapper, ) from xsdata.codegen.models import Class from xsdata.codegen.parsers import DtdParser from xsdata.codegen.parsers.definitions import DefinitionsParser from xsdata.codegen.parsers.schema import SchemaParser from xsdata.codegen.stopwatch import stopwatch, stopwatches from xsdata.codegen.utils import ClassUtils from xsdata.codegen.writer import CodeWriter from xsdata.formats.dataclass.models.generics import AnyElement from xsdata.formats.dataclass.parsers import TreeParser from xsdata.logger import logger from xsdata.models.config import GeneratorConfig from xsdata.models.wsdl import Definitions from xsdata.models.xsd import Schema from xsdata.utils import collections TYPE_UNKNOWN = 0 TYPE_SCHEMA = 1 TYPE_DEFINITION = 2 TYPE_DTD = 3 TYPE_XML = 4 TYPE_JSON = 5 class SupportedType(NamedTuple): """A supported resource model representation. Attributes: id: The integer identifier name: The name of the resource type match_uri: A callable to match against URI strings match_content: A callable to match against the raw file content """ id: int name: str match_uri: Callable match_content: Callable supported_types = [ SupportedType( id=TYPE_DEFINITION, name="wsdl", match_uri=lambda x: x.endswith("wsdl"), match_content=lambda x: x.endswith("definitions>"), ), SupportedType( id=TYPE_SCHEMA, name="xsd", match_uri=lambda x: x.endswith("xsd"), match_content=lambda x: x.endswith("schema>"), ), SupportedType( id=TYPE_DTD, name="dtd", match_uri=lambda x: x.endswith("dtd"), match_content=lambda x: ""), ), SupportedType( id=TYPE_JSON, name="json", match_uri=lambda x: x.endswith("json"), match_content=lambda x: x.endswith("}"), ), ] class ResourceTransformer: """Orchestrate the code generation from a list of sources. Supports xsd, wsdl, dtd, xml and json documents. Args: config: Generator configuration Attributes: classes: A list of class instances processed: A list of processed uris preloaded: A uri/content map used as cache """ __slots__ = ("classes", "config", "preloaded", "processed") def __init__(self, config: GeneratorConfig): """Initialize the transformer.""" self.config = config self.classes: list[Class] = [] self.processed: list[str] = [] self.preloaded: dict = {} def process(self, uris: list[str], cache: bool = False) -> None: """Process a list of resolved URI strings. Args: uris: A list of absolute URI strings to process cache: Specifies whether to catch the initial parsed classes """ cache_file = self.get_cache_file(uris) if cache else None if cache_file and cache_file.exists(): logger.info(f"Loading from cache {cache_file}") self.classes = pickle.loads(cache_file.read_bytes()) else: self.process_sources(uris) if cache_file and not cache_file.exists(): cache_file.write_bytes(pickle.dumps(self.classes)) try: self.process_classes() except ModuleNotFoundError: logger.warning("Module not found on imports validation, please report it.") except (CircularDependencyError, ImportError): raise CodegenError( "Circular Dependencies Found", help="Try a different structure style and/or enable unnest classes. " "Cleanup previously generated packages and modules.", ) for name, times in stopwatches.items(): logger.debug(f"{name} - {sum(times) / 1e9}s") def process_sources(self, uris: list[str]) -> None: """Process a list of resolved URI strings. Load the source URI strings and map them to codegen classes for further processing. Args: uris: A list of absolute URI strings to process """ sources = defaultdict(list) for uri in uris: tp = self.classify_resource(uri) sources[tp].append(uri) self.process_definitions(sources[TYPE_DEFINITION]) self.process_schemas(sources[TYPE_SCHEMA]) self.process_dtds(sources[TYPE_DTD]) self.process_xml_documents(sources[TYPE_XML]) self.process_json_documents(sources[TYPE_JSON]) def process_definitions(self, uris: list[str]) -> None: """Process a list of wsdl resources. Args: uris: A list of wsdl URI strings to process """ definitions = None for uri in uris: services = self.parse_definitions(uri, namespace=None) if definitions is None: definitions = services elif services: definitions.merge(services) if definitions is not None: collections.apply(definitions.schemas, self.convert_schema) self.convert_definitions(definitions) def process_schemas(self, uris: list[str]) -> None: """Process a list of xsd resources. Args: uris: A list of xsd URI strings to process """ for uri in uris: self.process_schema(uri) def process_dtds(self, uris: list[str]) -> None: """Process a list of dtd resources. Args: uris: A list of dtd URI strings to process """ classes: list[Class] = [] for uri in uris: input_stream = self.load_resource(uri) if input_stream: logger.info("Parsing dtd %s", uri) dtd = DtdParser.parse(input_stream, location=uri) classes.extend(DtdMapper.map(dtd)) self.classes.extend(classes) def process_schema(self, uri: str, namespace: str | None = None) -> None: """Parse and convert schema to codegen models. Args: uri: The schema URI location namespace: The target namespace, if the URI is from an inline import """ schema = self.parse_schema(uri, namespace) if schema: self.convert_schema(schema) def process_xml_documents(self, uris: list[str]) -> None: """Process a list of xml resources. Args: uris: A list of xml URI strings to process """ classes = [] parser = TreeParser() location = os.path.dirname(uris[0]) if uris else "" for uri in uris: input_stream = self.load_resource(uri) if input_stream: logger.info("Parsing document %s", uri) any_element: AnyElement = parser.from_bytes(input_stream) classes.extend(ElementMapper.map(any_element, location)) self.classes.extend(ClassUtils.reduce_classes(classes)) def process_json_documents(self, uris: list[str]) -> None: """Process a list of json resources. Args: uris: A list of json URI strings to process """ classes = [] name = self.config.output.package.split(".")[-1] dirname = os.path.dirname(uris[0]) if uris else "" for uri in uris: input_stream = self.load_resource(uri) if input_stream: try: data = json.load(io.BytesIO(input_stream)) logger.info("Parsing document %s", uri) if isinstance(data, dict): data = [data] for obj in data: classes.extend(DictMapper.map(obj, name, dirname)) except ValueError as exc: logger.warning("JSON load failed for file: %s", uri, exc_info=exc) self.classes.extend(ClassUtils.reduce_classes(classes)) def process_classes(self) -> None: """Process the generated classes and write or print the output.""" class_num, inner_num = self.count_classes(self.classes) if class_num: logger.info( "Analyzer input: %d main and %d inner classes", class_num, inner_num ) classes = self.analyze_classes(self.classes) class_num, inner_num = self.count_classes(classes) logger.info( "Analyzer output: %d main and %d inner classes", class_num, inner_num ) writer = CodeWriter.from_config(self.config) with stopwatch(CodeWriter.__name__): writer.write(classes) def convert_schema(self, schema: Schema) -> None: """Convert a schema instance to codegen classes. Process recursively any schema imports. Args: schema: The xsd schema instance """ for sub in schema.included(): if sub.location: self.process_schema(sub.location, schema.target_namespace) self.classes.extend(self.generate_classes(schema)) def convert_definitions(self, definitions: Definitions) -> None: """Convert a definitions instance to codegen classes.""" self.classes.extend(DefinitionsMapper.map(definitions)) def generate_classes(self, schema: Schema) -> list[Class]: """Convert the given schema instance to a list of classes.""" uri = schema.location logger.info("Compiling schema %s", uri or "...") classes = SchemaMapper.map(schema) class_num, inner_num = self.count_classes(classes) if class_num > 0: logger.info("Builder: %d main and %d inner classes", class_num, inner_num) return classes def parse_schema(self, uri: str, namespace: str | None) -> Schema | None: """Parse the given URI and return the schema instance. Args: uri: The resource URI namespace: The target namespace """ input_stream = self.load_resource(uri) if input_stream is None: return None logger.info("Parsing schema %s", uri) parser = SchemaParser(target_namespace=namespace, location=uri) return parser.from_bytes(input_stream, Schema) def parse_definitions( self, uri: str, namespace: str | None, ) -> Definitions | None: """Parse recursively the given URI and return the definitions instance. Args: uri: The resource URI namespace: The target namespace """ input_stream = self.load_resource(uri) if input_stream is None: return None parser = DefinitionsParser(target_namespace=namespace, location=uri) definitions = parser.from_bytes(input_stream, Definitions) namespace = definitions.target_namespace for imp in definitions.imports: if not imp.location: continue if imp.location.endswith("wsdl"): sub_definition = self.parse_definitions(imp.location, namespace) if sub_definition: definitions.merge(sub_definition) else: self.process_schema(imp.location) return definitions def load_resource(self, uri: str) -> bytes | None: """Read and return the contents of the given URI. Args: uri: The resource URI Returns: The raw bytes content or None if the resource could not be read """ if uri not in self.processed: try: self.processed.append(uri) return self.preloaded.pop(uri, None) or opener.open(uri).read() # nosec except OSError: logger.warning("Resource not found %s", uri) else: logger.debug("Skipping already processed: %s", uri) return None def classify_resource(self, uri: str) -> int: """Detect the resource type by the URI extension or the contents. Args: uri: The resource URI Returns: The resource integer identifier. """ for supported_type in supported_types: if supported_type.match_uri(uri): return supported_type.id src = self.load_resource(uri) if src is not None: self.preloaded[uri] = src self.processed.clear() text = src.decode("utf-8").strip() for supported_type in supported_types: if supported_type.match_content(text): return supported_type.id return TYPE_UNKNOWN def analyze_classes(self, classes: list[Class]) -> list[Class]: """Analyzer the given class list and return the final list of classes.""" container = ClassContainer(config=self.config) container.extend(classes) container.process() return list(container) def count_classes(self, classes: list[Class]) -> tuple[int, int]: """Return a tuple of counters for the main and inner classes. Args: classes: A list of class instances Returns: A tuple of root, inner counters, e.g. (100, 5) """ main = len(classes) inner = 0 for cls in classes: inner += sum(self.count_classes(cls.inner)) return main, inner @classmethod def get_cache_file(cls, uris: list[str]) -> Path: """Return the cache path for the raw mapped classes. Args: uris: A list of URI strings Returns: A temporary file path instance """ key = hashlib.md5("".join(uris).encode()).hexdigest() tempdir = tempfile.gettempdir() return Path(tempdir).joinpath(f"xsdata.{__version__}.{key}.cache") tefra-xsdata-cf7afe8/xsdata/codegen/utils.py000066400000000000000000000413451526437326600213110ustar00rootroot00000000000000import sys from collections import deque from collections.abc import Iterator from xsdata.codegen.exceptions import CodegenError from xsdata.codegen.models import ( Attr, AttrType, Class, Extension, Restrictions, Status, get_qname, get_slug, ) from xsdata.models.enums import DataType from xsdata.utils import collections, namespaces, text from xsdata.utils.constants import DEFAULT_ATTR_NAME class ClassUtils: """General reusable utils methods that didn't fit anywhere else.""" @classmethod def find_value_attr(cls, source: Class) -> Attr: """Find the text attribute of the class. Args: source: The source class instance Returns: The matched attr instance. Raises: CodeGenerationError: If no text node/attribute exists """ for attr in source.attrs: if not attr.xml_type: return attr raise CodegenError("Class has no value attr", type=source.qname) @classmethod def remove_attribute(cls, target: Class, attr: Attr): """Safely remove the given attr from the target class. Make sure you match the attr by the reference id, simple comparison might remove a duplicate attr with the same tag/namespace/name. Args: target: The target class instance attr: The attr instance to remove """ target.attrs = [at for at in target.attrs if id(at) != id(attr)] @classmethod def clean_inner_classes(cls, target: Class): """Check if there are orphan inner classes and remove them. Args: target: The target class instance to inspect. """ for inner in target.inner.copy(): if cls.is_orphan_inner(target, inner): target.inner.remove(inner) @classmethod def is_orphan_inner(cls, target: Class, inner: Class) -> bool: """Check if the inner class is references in the target class. Args: target: The target class instance inner: The inner class instance Returns: The bool result. """ for attr in target.attrs: for attr_type in attr.types: if attr_type.forward and attr_type.qname == inner.qname: return False return True @classmethod def copy_attributes(cls, source: Class, target: Class, extension: Extension): """Copy the attrs from the source to the target class. Remove the extension instance that connects the two classes. The new attrs are prepended in the list unless if they are supposed to be last in a sequence. Args: source: The source/parent class instance target: The target/child class instance extension: The extension instance that connects the classes """ target.extensions.remove(extension) target_attr_names = {attr.name for attr in target.attrs} index = 0 for attr in source.attrs: if attr.name not in target_attr_names: clone = cls.clone_attribute(attr, extension.restrictions) cls.copy_inner_classes(source, target, clone) if attr.index == sys.maxsize: target.attrs.append(clone) continue target.attrs.insert(index, clone) index += 1 @classmethod def copy_group_attributes( cls, source: Class, target: Class, attr: Attr, skip_inner_classes: bool = False ): """Copy the attrs of the source class to the target class. The attr represents a reference to the source class which is derived from xs:group or xs:attributeGroup and will be removed. Args: source: The source class instance target: The target class instance attr: The group attr instance skip_inner_classes: Whether the attr is circular reference, which means we can skip copying the inner classes. """ index = target.attrs.index(attr) target.attrs.pop(index) for source_attr in source.attrs: clone = cls.clone_attribute(source_attr, attr.restrictions) target.attrs.insert(index, clone) index += 1 if not skip_inner_classes: cls.copy_inner_classes(source, target, clone) @classmethod def copy_extensions(cls, source: Class, target: Class, extension: Extension): """Copy the source class extensions to the target class instance. Merge the extension restrictions with the source class extensions restrictions. Args: source: The source class instance target: The target class instance extension: The extension instance that links the two classes together """ for ext in source.extensions: clone = ext.clone() clone.restrictions.merge(extension.restrictions) target.extensions.append(clone) @classmethod def clone_attribute(cls, attr: Attr, restrictions: Restrictions) -> Attr: """Clone the given attr and merge its restrictions with the given. Args: attr: The source attr instance restrictions: The additional restrictions, originated from a substitution or another attr. """ clone = attr.clone() clone.restrictions.merge(restrictions) return clone @classmethod def copy_inner_classes(cls, source: Class, target: Class, attr: Attr): """Copy inner classes from source to the target class instance. Args: source: The source class instance target: The target class instance attr: The attr with the possible forward references """ for attr_type in attr.types: cls.copy_inner_class(source, target, attr_type) @classmethod def copy_inner_class(cls, source: Class, target: Class, attr_type: AttrType): """Find and copy the inner class from source to the target class instance. Steps: - Skip If the attr type is not a forward reference - Validate the inner class is not a circular reference to the target - Otherwise copy the inner class, and make sure it is re-sent for processing Args: source: The source class instance target: The target class instance attr_type: The attr type with the possible forward reference """ if not attr_type.forward: return inner = ClassUtils.find_nested(source, attr_type.qname) if inner is target: attr_type.circular = True attr_type.reference = target.ref else: # In extreme cases this adds duplicate inner classes clone = inner.clone() clone.package = target.package clone.module = target.module clone.status = Status.RAW attr_type.reference = clone.ref clone.parent = target target.inner.append(clone) @classmethod def find_attr(cls, source: Class, name: str) -> Attr | None: """Find an attr in the source class by its name. Args: source: The source class instance name: The attr name to lookup Returns: An attr instance or None if no attr matched. """ for attr in source.attrs: if attr.name == name: return attr return None @classmethod def flatten(cls, target: Class, location: str) -> Iterator[Class]: """Flatten the target class instance and its inner classes. The inner classes are removed from target instance! Args: target: The target class instance location: The source location of the target class Yields: An iterator over all the found classes. """ target.location = location target.parent = None while target.inner: yield from cls.flatten(target.inner.pop(), location) for attr in target.attrs: attr.types = collections.unique_sequence(attr.types, key="qname") for tp in attr.types: tp.forward = False yield target @classmethod def reduce_classes(cls, classes: list[Class]) -> list[Class]: """Find duplicate classes and attrs and reduce them. Args: classes: A list of classes Returns: A list of unique classes with no duplicate attrs. """ result = [] for group in collections.group_by(classes, key=get_qname).values(): target = group[0].clone() target.attrs = cls.reduce_attributes(group) target.mixed = any(x.mixed for x in group) cls.cleanup_class(target) result.append(target) return result @classmethod def reduce_attributes(cls, classes: list[Class]) -> list[Attr]: """Find and merge duplicate attrs from the given class list. Args: classes: A list of class instances Returns: A list of unique attr instances. """ result = [] for attr in cls.sorted_attrs(classes): added = False optional = False for obj in classes: pos = collections.find(obj.attrs, attr) if pos == -1: optional = True elif not added: added = True result.append(obj.attrs.pop(pos)) else: cls.merge_attributes(result[-1], obj.attrs.pop(pos)) if optional: result[-1].restrictions.min_occurs = 0 return result @classmethod def sorted_attrs(cls, classes: list[Class]) -> list[Attr]: """Sort and return the attrs from all the class list. The list contains duplicate classes, the method tries to find all the attrs and sorts them by first occurrence. Args: classes: A list of duplicate class instances. Returns: A list of sorted duplicate attr instances. """ attrs: list[Attr] = [] classes.sort(key=lambda x: len(x.attrs), reverse=True) for obj in classes: i = 0 obj_attrs = obj.attrs.copy() while obj_attrs: pos = collections.find(attrs, obj_attrs[i]) i += 1 if pos > -1: insert = obj_attrs[: i - 1] del obj_attrs[:i] while insert: attrs.insert(pos, insert.pop()) i = 0 elif i == len(obj_attrs): attrs.extend(obj_attrs) obj_attrs.clear() return attrs @classmethod def merge_attributes(cls, target: Attr, source: Attr): """Merge the source attr into the target instance. Merge the types, select the min min_occurs and the max max_occurs from the two instances and copy the source sequence number to the target if it's currently not set. Args: target: The target attr instance which will be updated source: The source attr instance """ target.types.extend(tp for tp in source.types if tp not in target.types) target.restrictions.min_occurs = min( target.restrictions.min_occurs or 0, source.restrictions.min_occurs or 0, ) target.restrictions.max_occurs = max( target.restrictions.max_occurs or 1, source.restrictions.max_occurs or 1, ) if source.restrictions.sequence is not None: target.restrictions.sequence = source.restrictions.sequence @classmethod def rename_duplicate_attributes(cls, target: Class): """Find and rename attributes with the same slug.""" grouped = collections.group_by( target.attrs, key=lambda x: x.slug or DEFAULT_ATTR_NAME ) for items in grouped.values(): total = len(items) if total == 2 and not items[0].is_enumeration: cls.rename_attribute_by_preference(*items) elif total > 1: cls.rename_attributes_by_index(target.attrs, items) @classmethod def rename_attribute_by_preference(cls, a: Attr, b: Attr): """Decide and rename one of the two given attributes. When both attributes are derived from the same xs:tag and one of the two fields has a specific namespace prepend it to the name. Preferable rename the second attribute. Otherwise, append the derived from tag to the name of one of the two attributes. Preferably rename the second field or the field derived from xs:attribute. Args: a: The first attr instance b: The second attr instance """ if a.tag == b.tag and (a.namespace or b.namespace): change = b if b.namespace else a assert change.namespace is not None change.name = f"{namespaces.clean_uri(change.namespace)}_{change.name}" else: change = b if b.is_attribute else a change.name = f"{change.name}_{change.tag}" @classmethod def rename_attributes_by_index(cls, attrs: list[Attr], rename: list[Attr]): """Append the next available index number to all the rename attr names. Args: attrs: A list of attr instances whose names must be protected rename: A list of attr instances that need to be renamed """ for index in range(1, len(rename)): reserved = set(map(get_slug, attrs)) name = rename[index].name rename[index].name = cls.unique_name(name, reserved) @classmethod def unique_name(cls, name: str, reserved: set[str]) -> str: """Append the next available index number to the name. Args: name: An object name reserved: A set of reserved names Returns: The new name with the index suffix """ if text.alnum(name) in reserved: index = 1 while text.alnum(f"{name}_{index}") in reserved: index += 1 return f"{name}_{index}" return name @classmethod def cleanup_class(cls, target: Class): """Go through the target class attrs and filter their types. Removes duplicate and invalid types. Args: target: The target class instance to inspect """ for attr in target.attrs: attr.types = cls.filter_types(attr.types) @classmethod def filter_types(cls, types: list[AttrType]) -> list[AttrType]: """Remove duplicate and invalid types. Invalid: 1. xs:error 2. xs:anyType and xs:anySimpleType when there are other types present Args: types: A list of attr type instances Returns: The new list of unique and valid attr type instances. """ types = collections.unique_sequence(types, key="qname") types = collections.remove(types, lambda x: x.datatype == DataType.ERROR) if len(types) > 1: types = collections.remove( types, lambda x: x.datatype in (DataType.ANY_TYPE, DataType.ANY_SIMPLE_TYPE), ) if not types: types.append(AttrType(qname=str(DataType.STRING), native=True)) return types @classmethod def find_nested(cls, target: Class, qname: str) -> Class: """Find a nested class by qname. Breath-first search implementation, that goes from the current level to bottom before looking for outer classes. Args: target: The class instance to begin the search qname: The qualified name of the nested class to find Raises: CodegenException: If the nested class cannot be found. Returns: The nested class instance. """ queue: deque[Class] = deque() visited: set[int] = set() if target.inner: queue.extend(target.inner) elif target.parent: queue.append(target.parent) while len(queue) > 0: item = queue.popleft() visited.add(item.ref) if item.qname == qname: return item for inner in item.inner: if inner.ref not in visited: queue.append(inner) if len(queue) == 0 and item.parent: queue.append(item.parent) raise CodegenError("Missing inner class", parent=target, qname=qname) tefra-xsdata-cf7afe8/xsdata/codegen/validator.py000066400000000000000000000146331526437326600221360ustar00rootroot00000000000000from xsdata.codegen.mixins import ContainerInterface from xsdata.codegen.models import Attr, Class, Extension, get_tag from xsdata.codegen.utils import ClassUtils from xsdata.logger import logger from xsdata.models.enums import Tag from xsdata.utils import collections from xsdata.utils.collections import group_by class ClassValidator: """Container class validator. Args: container: The class container instance """ __slots__ = "container" def __init__(self, container: ContainerInterface): """Initialize the class validator.""" self.container = container def process(self) -> None: """Main process entrypoint. Runs on groups of classes with the same qualified name. Steps: 1. Remove invalid classes 2. Handle duplicate types 3. Merge global types """ for classes in self.container.data.values(): if len(classes) > 1: self.remove_invalid_classes(classes) if len(classes) > 1: self.handle_duplicate_types(classes) if len(classes) > 1: self.merge_global_types(classes) def remove_invalid_classes(self, classes: list[Class]) -> None: """Remove classes with undefined extensions. Args: classes: A list of class instances """ def is_invalid(ext: Extension) -> bool: """Check if given type declaration is not native and is missing.""" return not ext.type.native and ext.type.qname not in self.container.data for target in classes.copy(): if any(is_invalid(extension) for extension in target.extensions): classes.remove(target) @classmethod def handle_duplicate_types(cls, classes: list[Class]): """Find and handle duplicate classes. If a class is defined more than once, keep either the one that was in redefines or overrides, or the last definition. If a class was redefined merge circular group attrs and extensions. In order for two classes to be duplicated they must have the same qualified name and be derived from the same xsd element. Args: classes: A list of classes with the same qualified name """ for items in group_by(classes, get_tag).values(): if len(items) == 1: continue index = cls.select_winner(items.copy()) if index == -1: logger.warning( "Duplicate type %s, will keep the last defined", items[0].qname, ) winner = items.pop(index) for item in items: classes.remove(item) if winner.container == Tag.REDEFINE: cls.merge_redefined_type(item, winner) @classmethod def merge_redefined_type(cls, source: Class, target: Class): """Merge source properties to the target redefined target class instance. Redefined classes usually have references to the original class. We need to copy those. Args: source: The original source class instance target: The redefined target class instance """ circular_extension = cls.find_circular_extension(target) circular_group = cls.find_circular_group(target) if circular_extension: ClassUtils.copy_attributes(source, target, circular_extension) ClassUtils.copy_extensions(source, target, circular_extension) if circular_group: ClassUtils.copy_group_attributes(source, target, circular_group) @classmethod def select_winner(cls, candidates: list[Class]) -> int: """From a list of classes select which class index will remain. Classes that were extracted from in xs:override/xs:redefined containers have priority, otherwise pick the last in the list. Args: candidates: A list of duplicate class instances Returns: The index of winner class or -1 if there is no clear winner. """ for index, item in enumerate(candidates): if item.container in (Tag.OVERRIDE, Tag.REDEFINE): return index return -1 @classmethod def find_circular_extension(cls, target: Class) -> Extension | None: """Find the first circular reference extension. Redefined classes usually have references to the original class with the same qualified name. We need to locate those and copy any attrs. Args: target: The target class instance to inspect Returns: An extension instance or None if there is no circular extension. """ for ext in target.extensions: if ext.type.name == target.name: return ext return None @classmethod def find_circular_group(cls, target: Class) -> Attr | None: """Find an attr with the same name as the target class name. Redefined classes usually have references to the original class with the same qualified name. We need to locate those and copy any attrs. Args: target: The target class instance to inspect Returns: An attr instance or None if there is no circular attr. """ return ClassUtils.find_attr(target, target.name) @classmethod def merge_global_types(cls, classes: list[Class]): """Merge parent-child global types. Conditions 1. One of them is derived from xs:element 2. One of them is derived from xs:complexType 3. The xs:element is a subclass of the xs:complexType 4. The xs:element has no attributes (This can't happen in a valid schema) Args: classes: A list of duplicate classes """ el = collections.first(x for x in classes if x.tag == Tag.ELEMENT) ct = collections.first(x for x in classes if x.tag == Tag.COMPLEX_TYPE) if ( el is None or ct is None or el is ct or el.attrs or len(el.extensions) != 1 or el.extensions[0].type.qname != el.qname ): return ct.namespace = el.namespace or ct.namespace ct.help = el.help or ct.help ct.substitutions = el.substitutions classes.remove(el) tefra-xsdata-cf7afe8/xsdata/codegen/writer.py000066400000000000000000000052511526437326600214610ustar00rootroot00000000000000from typing import ClassVar from xsdata.codegen.exceptions import CodegenError from xsdata.codegen.models import Class from xsdata.formats.dataclass.generator import DataclassGenerator from xsdata.formats.mixins import AbstractGenerator from xsdata.logger import logger from xsdata.models.config import GeneratorConfig class CodeWriter: """Code writer class. Args: generator: The code generator instance Attributes: generators: A map of registered code generators """ __slots__ = "generator" generators: ClassVar[dict[str, type[AbstractGenerator]]] = { "dataclasses": DataclassGenerator, } def __init__(self, generator: AbstractGenerator): """Initialize CodeWriter.""" self.generator = generator def write(self, classes: list[Class]) -> None: """Write the classes to the designated modules. The classes may be written in the same module or different ones, the entrypoint must create the directory structure write the file outputs. Args: classes: A list of class instances """ self.generator.normalize_packages(classes) header = self.generator.render_header() for result in self.generator.render(classes): if result.source.strip(): logger.info("Generating package: %s", result.title) src_code = header + result.source result.path.parent.mkdir(parents=True, exist_ok=True) result.path.write_text(src_code, encoding="utf-8") @classmethod def from_config(cls, config: GeneratorConfig) -> "CodeWriter": """Instance the code writer from the generator configuration instance. Validates that the output format is registered as a generator. Args: config: The generator configuration instance Returns: A new code writer instance. """ if config.output.format.value not in cls.generators: raise CodegenError( "Unknown output format", format=config.output.format.value ) generator_class = cls.generators[config.output.format.value] return cls(generator=generator_class(config)) @classmethod def register_generator(cls, name: str, clazz: type[AbstractGenerator]): """Register a generator by name. Args: name: The generator name clazz: The generator class """ cls.generators[name] = clazz @classmethod def unregister_generator(cls, name: str): """Remove a generator by name. Args: name: The generator name """ cls.generators.pop(name) tefra-xsdata-cf7afe8/xsdata/exceptions.py000066400000000000000000000012341526437326600207170ustar00rootroot00000000000000class ConverterError(ValueError): """Converting values between document/python types related errors.""" class ConverterWarning(Warning): """Converting values between document/python types recovered errors.""" class ParserError(ValueError): """Parsing related errors.""" class XmlHandlerError(ValueError): """Xml handler related errors.""" class XmlWriterError(ValueError): """Xml writer related errors.""" class SerializerError(ValueError): """Serializing related errors.""" class XmlContextError(ValueError): """Unhandled behaviour during data binding.""" class ClientValueError(ValueError): """Client related errors.""" tefra-xsdata-cf7afe8/xsdata/formats/000077500000000000000000000000001526437326600176375ustar00rootroot00000000000000tefra-xsdata-cf7afe8/xsdata/formats/__init__.py000066400000000000000000000000001526437326600217360ustar00rootroot00000000000000tefra-xsdata-cf7afe8/xsdata/formats/converter.py000066400000000000000000000604621526437326600222300ustar00rootroot00000000000000import abc import base64 import binascii import math import re from collections.abc import Callable, Sequence from contextlib import suppress from datetime import date, datetime, time from decimal import Decimal, InvalidOperation from enum import Enum, EnumMeta from typing import ( Any, cast, ) from xml.etree.ElementTree import QName from xsdata.exceptions import ConverterError from xsdata.models.datatype import ( XmlBase64Binary, XmlDate, XmlDateTime, XmlDuration, XmlHexBinary, XmlPeriod, XmlTime, ) from xsdata.utils import collections, namespaces, text class Converter(abc.ABC): """Abstract converter class.""" @abc.abstractmethod def deserialize(self, value: Any, **kwargs: Any) -> Any: """Convert a value to a python type. Args: value: The input value **kwargs: Additional keyword arguments needed per converter Returns: The converted value. Raises: ConverterError: if the value can't be converted. """ @abc.abstractmethod def serialize(self, value: Any, **kwargs: Any) -> str: """Convert value to string for serialization. Args: value: The input value **kwargs: Additional keyword arguments needed per converter Returns: The converted string value. """ @classmethod def validate_input_type(cls, value: Any, tp: type): """Validate the input value type matches the required type.""" if not isinstance(value, tp): raise ConverterError( f"Input value must be '{tp.__name__}' got '{type(value).__name__}'" ) class ConverterFactory: """Converter factory class. Attributes: registry: The registered converters """ __slots__ = "registry" def __init__(self): """Initialize the registry.""" self.registry: dict[type, Converter] = {} def deserialize(self, value: Any, types: Sequence[type], **kwargs: Any) -> Any: """Attempt to convert any value to one of the given types. Args: value: The input value types: The target candidate types **kwargs: Additional keyword arguments needed per converter Raises: ConverterError: if the value can't be converted to any of the given types. Returns: The converted value """ for data_type in types: with suppress(ConverterError): instance = self.type_converter(data_type) return instance.deserialize(value, data_type=data_type, **kwargs) type_names = " | ".join(tp.__name__ for tp in types) raise ConverterError(f"`{value}` is not a valid `{type_names}`") def serialize(self, value: Any, **kwargs: Any) -> Any: """Convert the given value to string. If the value is a list assume the value is a list of tokens. Args: value: The input value **kwargs: Additional keyword arguments needed per converter Returns: The converted string value or None if the input value is None. """ if value is None: return None if isinstance(value, list): return " ".join(self.serialize(val, **kwargs) for val in value) instance = self.value_converter(value) return instance.serialize(value, **kwargs) def test( self, value: str | None, types: Sequence[type], strict: bool = False, **kwargs: Any, ) -> bool: """Test the given string value can be converted to one of the given types. Args: value: The input value types: The candidate target types strict: validate the string output also matches the original input **kwargs: Additional keyword arguments needed per converter Returns: The bool result. """ if not isinstance(value, str): return False try: decoded = self.deserialize(value, types, **kwargs) except ConverterError: return False if strict and isinstance(decoded, (float, int, Decimal, XmlPeriod)): if isinstance(decoded, float) and ( math.isinf(decoded) or math.isnan(decoded) ): return True encoded = self.serialize(decoded, **kwargs) return value.strip() == encoded return True def register_converter(self, data_type: type, func: Callable | Converter) -> None: """Register a callable or converter for the given data type. Args: data_type: The data type func: The callable or converter instance """ if isinstance(func, Converter): self.registry[data_type] = func else: self.registry[data_type] = ProxyConverter(func) def unregister_converter(self, data_type: type) -> None: """Unregister the converter for the given data type. Args: data_type: The data type Raises: KeyError: if the data type is not registered. """ self.registry.pop(data_type) def type_converter(self, data_type: type) -> Converter: """Find a suitable converter for given data type. Iterate over all but last mro items and check for registered converters. Args: data_type: The data type Raises: ConverterError: if the data type is not registered. Returns: A converter instance """ with suppress(KeyError): # Quick in and out, without checking the whole mro. return self.registry[data_type] # We tested the first, ignore the object for mro in data_type.__mro__[1:-1]: if mro in self.registry: return self.registry[mro] raise ConverterError(f"No converter registered for `{data_type.__qualname__}`") def value_converter(self, value: Any) -> Converter: """Get a suitable converter for the given value.""" return self.type_converter(value.__class__) @classmethod def sort_types(cls, types: Sequence[type]) -> list[type]: """Sort a list of types by giving priority to strict types first.""" if len(types) < 2: return list(types) return sorted(types, key=lambda x: __PYTHON_TYPES_SORTED__.get(x, 0)) @classmethod def explicit_types(cls) -> tuple[type, ...]: """Get a list of types that need strict test.""" return __EXPLICIT_TYPES__ __PYTHON_TYPES_SORTED__ = { int: 1, bool: 2, float: 3, Decimal: 4, datetime: 5, date: 6, time: 7, XmlTime: 8, XmlDate: 9, XmlDateTime: 10, XmlDuration: 11, XmlPeriod: 12, QName: 13, str: 14, } __EXPLICIT_TYPES__ = ( int, bool, float, Decimal, XmlTime, XmlDate, XmlDateTime, XmlDuration, XmlPeriod, ) class StringConverter(Converter): """A str converter.""" def deserialize(self, value: Any, **kwargs: Any) -> Any: """Convert a value to string.""" return value if isinstance(value, str) else str(value) def serialize(self, value: Any, **kwargs: Any) -> str: """Convert a value to string.""" return value if isinstance(value, str) else str(value) class BoolConverter(Converter): """A bool converter.""" def deserialize(self, value: Any, **kwargs: Any) -> bool: """Convert a value to bool. Args: value: The input value **kwargs: Unused keyword arguments Returns: True if the value is in (True, "true", "1") False if the value is in (False, "false", "0") Raises: ConverterError: if the value can't be converted to bool. """ if isinstance(value, str): val = value.strip() if val in ("true", "1"): return True if val in ("false", "0"): return False raise ConverterError(f"Invalid bool literal '{value}'") if value is True or value is False: return value raise ConverterError(f"Invalid bool literal '{value}'") def serialize(self, value: bool, **kwargs: Any) -> str: """Convert a bool value to string. Args: value: The input bool value **kwargs: Unused keyword arguments Returns: "true" or "false" """ return "true" if value else "false" class IntConverter(Converter): """An int converter.""" def deserialize(self, value: Any, **kwargs: Any) -> int: """Convert a value to int. Args: value: The input value **kwargs: Unused keyword arguments Returns: The int converted value. Raises: ConverterError: on value or type errors. """ try: return int(value) except (ValueError, TypeError) as e: raise ConverterError(e) def serialize(self, value: int, **kwargs: Any) -> str: """Convert an int value sto string. Args: value: The input int value **kwargs: Unused keyword arguments Returns: The str converted value. """ return str(value) class FloatConverter(Converter): """A float converter.""" INF = float("inf") def deserialize(self, value: Any, **kwargs: Any) -> float: """Convert a value to float. Args: value: The input value **kwargs: Unused keyword arguments Returns: The float converted value. Raises: ConverterError: on value errors. """ try: return float(value) except ValueError as e: raise ConverterError(e) def serialize(self, value: float, **kwargs: Any) -> str: """Convert a float value sto string. Args: value: The input int value **kwargs: Unused keyword arguments Returns: The str converted value. """ if math.isnan(value): return "NaN" if value == self.INF: return "INF" if value == -self.INF: return "-INF" return repr(value).upper().replace("E+", "E") class BytesConverter(Converter): """A bytes converter for base16 and base64 formats.""" def deserialize(self, value: Any, **kwargs: Any) -> bytes: """Convert a string value to base16 or base64 format. Args: value: The input string value **kwargs: Additional keyword arguments format: The target output format (base16|base64) Returns: The bytes converted value. Raises: ConverterError: If format is empty or not supported or the value contains invalid characters. """ self.validate_input_type(value, str) try: fmt = kwargs.get("format") value = re.sub(r"\s+", "", value) if fmt == "base16": return binascii.unhexlify(value) if fmt == "base64": return base64.b64decode(value, validate=True) raise ConverterError(f"Unknown format '{fmt}'") except ValueError as e: raise ConverterError(e) def serialize(self, value: bytes, **kwargs: Any) -> str: """Convert a bytes value sto string. Args: value: The input bytes value **kwargs: Additional keyword arguments format: The input value format (base16|base64) Returns: The str converted value. Raises: ConverterError: If format doesn't match the value type or it's not supported. """ fmt = kwargs.get("format") if isinstance(value, XmlHexBinary) or fmt == "base16": return base64.b16encode(value).decode() if isinstance(value, XmlBase64Binary) or fmt == "base64": return base64.b64encode(value).decode() raise ConverterError(f"Unknown format '{fmt}'") class DecimalConverter(Converter): """A decimal converter.""" def deserialize(self, value: Any, **kwargs: Any) -> Decimal: """Convert a value to decimal. Args: value: The input value **kwargs: Unused keyword arguments Returns: The decimal converted value. Raises: ConverterError: on InvalidOperation errors. """ try: return Decimal(value) except InvalidOperation: raise ConverterError def serialize(self, value: Decimal, **kwargs: Any) -> str: """Convert a decimal value sto string. Args: value: The input decimal value **kwargs: Unused keyword arguments Returns: The str converted value. """ if value.is_infinite(): return str(value).replace("Infinity", "INF") return f"{value:f}" class QNameConverter(Converter): """A QName converter.""" def deserialize( self, value: str, ns_map: dict | None = None, **kwargs: Any, ) -> QName: """Convert a string value to QName instance. The method supports strings with namespace prefixes or fully namespace qualified strings. Examples: - xs:string -> QName("http://www.w3.org/2001/XMLSchema", "string") - {foo}bar -> QName("foo", "bar" Args: value: The input str value ns_map: A namespace prefix-URI map **kwargs: Unused keyword arguments Returns: A QName instance Raises: ConverterError: If the prefix can't be resolved. """ self.validate_input_type(value, str) namespace, tag = self.resolve(value, ns_map) return QName(namespace, tag) if namespace else QName(tag) def serialize( self, value: QName, ns_map: dict | None = None, **kwargs: Any, ) -> str: """Convert a QName instance value sto string. Convert a QName instance to string either with a namespace prefix if a prefix-URI namespaces mapping is provided or to a fully qualified name with the namespace. Examples: - QName("http://www.w3.org/2001/XMLSchema", "int") & ns_map -> xs:int - QName("foo, "bar") -> {foo}bar Args: value: The qname instance to convert ns_map: A namespace prefix-URI map, if we want to use prefixes **kwargs: Unused keyword arguments Returns: The str converted value. """ if ns_map is None: return value.text namespace, tag = namespaces.split_qname(value.text) if not namespace: return tag prefix = namespaces.load_prefix(namespace, ns_map) return f"{prefix}:{tag}" if prefix else tag @staticmethod def resolve(value: str, ns_map: dict | None = None) -> tuple[str, str]: """Split a qname or ns prefixed string value or a uri, name pair. Args: value: the input value to resolve ns_map: A namespace prefix-URI map Returns: A tuple of uri and name strings. Raises: ConverterError: if the uri is not valid, if the prefix can't be resolved to a URI, if the name is not a valid NCName """ value = value.strip() if not value: raise ConverterError if value[0] == "{": uri, name = text.split(value[1:], "}") if not namespaces.is_uri(uri): raise ConverterError else: prefix, name = text.split(value, ":") uri = ns_map.get(prefix) if ns_map else None if prefix and not uri: raise ConverterError(f"Unknown namespace prefix: `{prefix}`") if " " in name or not namespaces.is_ncname(name): raise ConverterError return uri, name class EnumConverter(Converter): """An enum converter.""" def serialize(self, value: Enum, **kwargs: Any) -> str: """Convert an enum member to a string.""" return converter.serialize(value.value, **kwargs) def deserialize( self, value: Any, data_type: EnumMeta | None = None, **kwargs: Any, ) -> Enum: """Convert a value to an enum member. Args: value: The input value data_type: The enumeration class **kwargs: Additional keyword arguments needed for parsing the value to a python type. Returns: The enum member. Raises: ConverterError: if the data type is not an enum, or the value doesn't match any of the enum members. """ if data_type is None or not isinstance(data_type, EnumMeta): raise ConverterError(f"'{data_type}' is not an enum") if collections.is_array(value): values = value elif isinstance(value, str): value = value.strip() values = value.split() else: values = [value] length = len(values) for member in cast(type[Enum], data_type): if self.match(value, values, length, member.value, **kwargs): return member raise ConverterError @classmethod def match( cls, value: Any, values: Sequence, length: int, real: Any, **kwargs: Any, ) -> bool: """Match a value to one of the enumeration values. Args: value: The input value values: The input value as a sequence, in case of NMTokens length: The length of the sequence values real: The enumeration value **kwargs: Additional keyword arguments needed for parsing the value to a python type. Returns: Whether the value or values matches the enumeration member value. """ if isinstance(value, str) and isinstance(real, str): return real in (value, " ".join(values)) if isinstance(real, (tuple, list)) and not hasattr(real, "_fields"): if len(real) == length and cls._match_list(values, real, **kwargs): return True elif length == 1 and cls._match_atomic(value, real, **kwargs): return True return False @classmethod def _match_list(cls, raw: Sequence, real: Sequence, **kwargs: Any) -> bool: for index, val in enumerate(real): if not cls._match_atomic(raw[index], val, **kwargs): return False return True @classmethod def _match_atomic(cls, raw: Any, real: Any, **kwargs: Any) -> bool: try: cmp = converter.deserialize(raw, [type(real)], **kwargs) except ConverterError: cmp = raw if isinstance(real, float): return cmp == real or repr(cmp) == repr(real) return cmp == real class DateTimeBase(Converter, abc.ABC): """An abstract datetime converter.""" @classmethod def parse(cls, value: Any, **kwargs: Any) -> datetime: """Parse a str into a datetime instance. Args: value: The input string value **kwargs: Additional keyword argument format: The datetime format to use Returns: The datetime instance Raises: ConverterError: If no format was provided or the value could not be converted. """ try: return datetime.strptime(value, kwargs["format"]) except KeyError: raise ConverterError("Missing format keyword argument") except Exception as e: raise ConverterError(e) def serialize(self, value: date | time, **kwargs: Any) -> str: """Convert a datetime instance to string. Args: value: The input datetime instance **kwargs: Additional keyword argument format: The datetime format to use Returns: The converted str value. Raises: ConverterError: If no format was provided or the value could not be converted. """ try: return value.strftime(kwargs["format"]) except KeyError: raise ConverterError("Missing format keyword argument") except Exception as e: raise ConverterError(e) @abc.abstractmethod def deserialize(self, value: Any, **kwargs: Any) -> Any: """Parse string literal value into python.""" class TimeConverter(DateTimeBase): """A datetime.time converter.""" def deserialize(self, value: Any, **kwargs: Any) -> time: """Convert the input str to a time instance. Args: value: The input string value **kwargs: Additional keyword argument format: The time format to use Returns: The time instance Raises: ConverterError: If no format was provided or the value could not be converted. """ return self.parse(value, **kwargs).time() class DateConverter(DateTimeBase): """A datetime.date converter.""" def deserialize(self, value: Any, **kwargs: Any) -> date: """Convert the input str to a date instance. Args: value: The input string value **kwargs: Additional keyword argument format: The time format to use Returns: The date instance Raises: ConverterError: If no format was provided or the value could not be converted. """ return self.parse(value, **kwargs).date() class DateTimeConverter(DateTimeBase): """A datetime.datetime converter.""" def deserialize(self, value: Any, **kwargs: Any) -> datetime: """Convert the input str to a datetime instance. Args: value: The input string value **kwargs: Additional keyword argument format: The time format to use Returns: The datetime instance Raises: ConverterError: If no format was provided or the value could not be converted. """ return self.parse(value, **kwargs) class ProxyConverter(Converter): """Proxy wrapper to treat callables as converters. Args: factory: The callable factory """ __slots__ = "factory" def __init__(self, factory: Callable): """Initialize the proxy converter.""" self.factory = factory def deserialize(self, value: Any, **kwargs: Any) -> Any: """Call the instance factory and return the result. Args: value: The input value to convert **kwargs: Unused keyword arguments Returns: The return result of the callable. Raises: ConverterError: on value errors. """ try: return self.factory(value) except ValueError as e: raise ConverterError(e) def serialize(self, value: Any, **kwargs: Any) -> str: """Cast value to str.""" return str(value) converter = ConverterFactory() converter.register_converter(str, StringConverter()) converter.register_converter(int, IntConverter()) converter.register_converter(bool, BoolConverter()) converter.register_converter(float, FloatConverter()) converter.register_converter(bytes, BytesConverter()) converter.register_converter(object, converter.type_converter(str)) converter.register_converter(time, TimeConverter()) converter.register_converter(date, DateConverter()) converter.register_converter(datetime, DateTimeConverter()) converter.register_converter(XmlTime, ProxyConverter(XmlTime.from_string)) converter.register_converter(XmlDate, ProxyConverter(XmlDate.from_string)) converter.register_converter(XmlDateTime, ProxyConverter(XmlDateTime.from_string)) converter.register_converter(XmlDuration, ProxyConverter(XmlDuration)) converter.register_converter(XmlPeriod, ProxyConverter(XmlPeriod)) converter.register_converter(QName, QNameConverter()) converter.register_converter(Decimal, DecimalConverter()) converter.register_converter(Enum, EnumConverter()) tefra-xsdata-cf7afe8/xsdata/formats/dataclass/000077500000000000000000000000001526437326600215765ustar00rootroot00000000000000tefra-xsdata-cf7afe8/xsdata/formats/dataclass/__init__.py000066400000000000000000000000001526437326600236750ustar00rootroot00000000000000tefra-xsdata-cf7afe8/xsdata/formats/dataclass/bytecode.py000066400000000000000000000021411526437326600237440ustar00rootroot00000000000000from jinja2.bccache import Bucket, BytecodeCache class MemoryBytecodeCache(BytecodeCache): """In-memory jinja2 bytecode cache. Every generator instance creates its own environment, because the filters depend on the generator config. Sharing the compiled templates across environments skips parsing and compiling them on every code generation run in the same process. Buckets are keyed by the template name and filename, and jinja2 discards cached bytecode when the template source checksum changes. """ __slots__ = ("store",) def __init__(self) -> None: self.store: dict[str, bytes] = {} def load_bytecode(self, bucket: Bucket) -> None: """Load the cached bytecode into the bucket, if any.""" code = self.store.get(bucket.key) if code is not None: bucket.bytecode_from_string(code) def dump_bytecode(self, bucket: Bucket) -> None: """Store the bucket bytecode.""" self.store[bucket.key] = bucket.bytecode_to_string() def clear(self) -> None: """Clear the cache.""" self.store.clear() tefra-xsdata-cf7afe8/xsdata/formats/dataclass/client.py000066400000000000000000000142341526437326600234320ustar00rootroot00000000000000from __future__ import annotations from dataclasses import dataclass, fields from typing import Any from xsdata.exceptions import ClientValueError from xsdata.formats.dataclass.context import XmlContext from xsdata.formats.dataclass.parsers import DictDecoder, XmlParser from xsdata.formats.dataclass.serializers import XmlSerializer from xsdata.formats.dataclass.transports import DefaultTransport, Transport @dataclass(frozen=True) class Config: """Service configuration class. Attributes: style: The binding style location: The service endpoint url transport: The transport namespace soap_action: The soap action input: The input class output: The output class """ style: str location: str transport: str soap_action: str input: type output: type encoding: str | None = None @classmethod def from_service(cls, obj: Any, **kwargs: Any) -> Config: """Instantiate from a generated service class. Args: obj: The service class **kwargs: Override the service class properties style: The binding style location: The service endpoint url transport: The transport namespace soap_action: The soap action input: The input class output: The output class Returns: A new config instance. """ params = { f.name: kwargs[f.name] if f.name in kwargs else getattr(obj, f.name, None) for f in fields(cls) } return cls(**params) # type: ignore class TransportTypes: """Transport types.""" SOAP = "http://schemas.xmlsoap.org/soap/http" class Client: """A wsdl client. Args: config: The service config instance transport: The transport instance parser: The xml parser instance serializer: The xml serializer instance """ __slots__ = "config", "parser", "serializer", "transport" def __init__( self, config: Config, transport: Transport | None = None, parser: XmlParser | None = None, serializer: XmlSerializer | None = None, ): """Initialize the client.""" self.config = config self.transport = transport or DefaultTransport() if not serializer and not parser: context = XmlContext() serializer = XmlSerializer(context=context) parser = XmlParser(context=context) elif not serializer: assert parser is not None serializer = XmlSerializer(context=parser.context) else: assert serializer is not None parser = XmlParser(context=serializer.context) self.parser = parser self.serializer = serializer @classmethod def from_service(cls, obj: type, **kwargs: Any) -> Client: """Instantiate client from a service class. Args: obj: The service class **kwargs: Override the service class properties style: The binding style location: The service endpoint url transport: The transport namespace soap_action: The soap action input: The input class output: The output class Returns: A new client instance. """ return cls(config=Config.from_service(obj, **kwargs)) def send(self, obj: Any, headers: dict | None = None) -> Any: """Build and send a request for the input object. ```py params = {"body": {"add": {"int_a": 3, "int_b": 4}}} res = client.send(params) ``` Is equivalent with: ```py req = CalculatorSoapAddInput(body=CalculatorSoapAddInput.Body(add=Add(3, 4))) res = client.send(req) ``` Args: obj: The request model instance or a pure dictionary headers: Additional headers to pass to the transport Returns: The response model instance. """ data = self.prepare_payload(obj) headers = self.prepare_headers(headers or {}) response = self.transport.post(self.config.location, data=data, headers=headers) return self.parser.from_bytes(response, self.config.output) def prepare_headers(self, headers: dict) -> dict: """Prepare the request headers. It merges the custom user headers with the necessary headers to accommodate the service class configuration. Raises: ClientValueError: If the service transport type is not supported. """ result = headers.copy() if self.config.transport == TransportTypes.SOAP: result["content-type"] = "text/xml" if self.config.encoding: result["content-type"] += f"; charset={self.config.encoding}" if self.config.soap_action: result["SOAPAction"] = self.config.soap_action else: raise ClientValueError( f"Unsupported binding transport: `{self.config.transport}`" ) return result def prepare_payload(self, obj: Any) -> str | bytes: """Prepare and serialize the payload to be sent. If the obj is a pure dictionary, it will be converted first to a request model instance. Args: obj: The request model instance or a pure dictionary Returns: The serialized request body content as string or bytes. Raises: ClientValueError: If the config input type doesn't match the given object. """ if isinstance(obj, dict): decoder = DictDecoder(context=self.serializer.context) obj = decoder.decode(obj, self.config.input) if not isinstance(obj, self.config.input): raise ClientValueError( f"Invalid input service type, " f"expected `{self.config.input.__name__}` " f"got `{type(obj).__name__}`" ) result = self.serializer.render(obj) if self.config.encoding: return result.encode(self.config.encoding) return result tefra-xsdata-cf7afe8/xsdata/formats/dataclass/compat.py000066400000000000000000000127651526437326600234460ustar00rootroot00000000000000from __future__ import annotations import abc from collections.abc import Iterator from dataclasses import MISSING, fields, is_dataclass from types import MappingProxyType from typing import Any, Protocol from xsdata.exceptions import XmlContextError from xsdata.formats.dataclass.models.generics import AnyElement, DerivedElement from xsdata.utils.hooks import load_entry_points class FieldInfo(Protocol): """A class field info wrapper.""" name: str init: bool metadata: MappingProxyType[Any, Any] default: Any default_factory: Any class ClassType(abc.ABC): """An interface for class types like attrs, pydantic.""" __slots__ = () @property @abc.abstractmethod def any_element(self) -> Any: """Return the AnyElement used to bind wildcard element nodes.""" @property @abc.abstractmethod def derived_element(self) -> Any: """Return the DerivedElement used to bind ambiguous element nodes.""" @property def any_keys(self) -> set[str]: """Return the field names of the AnyElement class.""" return {field.name for field in self.get_fields(self.any_element)} @property def derived_keys(self) -> set[str]: """Return the field names of the DerivedElement class.""" return {field.name for field in self.get_fields(self.derived_element)} @abc.abstractmethod def is_model(self, obj: Any) -> bool: """Return whether the given value is binding model.""" @abc.abstractmethod def verify_model(self, obj: Any) -> None: """Verify the given value is a binding model. Args: obj: The input model instance Raises: XmlContextError: if not supported """ @abc.abstractmethod def get_fields(self, obj: Any) -> Iterator[FieldInfo]: """Return the models fields in the correct mro ordering.""" @abc.abstractmethod def default_value(self, field: FieldInfo, default: Any | None = None) -> Any: """Return the default value or factory of the given model field.""" @abc.abstractmethod def default_choice_value(self, choice: dict) -> Any: """Return the default value or factory of the given model field choice.""" def score_object(self, obj: Any) -> float: """Score a binding model instance by its field values types. Weights: 1. None: 0 2. str: 1 3. *: 1.5 Args: obj: The input object Returns: The float score value. """ if not obj: return -1.0 def score(value: Any) -> float: if isinstance(value, str): return 1.0 if value is not None: return 1.5 return 0.0 if self.is_model(obj): return sum( score(getattr(obj, var.name, None)) for var in self.get_fields(obj) ) return score(obj) class ClassTypes: """A class types registry. Attributes: types: A name-instance map of the registered class types """ __slots__ = "types" def __init__(self): """Initialize the registry.""" self.types: dict[str, ClassType] = {} def register(self, name: str, fmt: ClassType, **_: Any) -> None: """Register a class type instance by name. Args: name: The name of the class type fmt: The class type instance **_: No idea :( """ self.types[name] = fmt def get_type(self, name: str) -> ClassType: """Get a class type instance by name. Args: name: The class type name Returns: The class type instance Raises: KeyError: If the name is not registered. """ return self.types[name] class Dataclasses(ClassType): """The dataclasses class type.""" __slots__ = () @property def any_element(self) -> Any: """Return the generic any element class.""" return AnyElement @property def derived_element(self) -> Any: """Return the generic derived element class.""" return DerivedElement def is_model(self, obj: Any) -> bool: """Return whether the obj is a dataclass model.""" return is_dataclass(obj) def verify_model(self, obj: Any) -> None: """Validate whether the obj is a dataclass model. Args: obj: The input object to validate. Raises: XmlContextError: If it's not a dataclass model. """ if not self.is_model(obj): raise XmlContextError(f"Type '{obj}' is not a dataclass.") def get_fields(self, obj: Any) -> Iterator[FieldInfo]: """Return a dataclass fields iterator.""" yield from fields(obj) def default_value(self, field: FieldInfo, default: Any | None = None) -> Any: """Return the default value or factory of the given model field.""" if field.default_factory is not MISSING: return field.default_factory if field.default is not MISSING: return field.default return default def default_choice_value(self, choice: dict) -> Any: """Return the default value or factory of the given model field choice.""" factory = choice.get("default_factory") if callable(factory): return factory return choice.get("default") class_types = ClassTypes() class_types.register("dataclasses", Dataclasses()) load_entry_points("xsdata.plugins.class_types") tefra-xsdata-cf7afe8/xsdata/formats/dataclass/context.py000066400000000000000000000250561526437326600236440ustar00rootroot00000000000000import sys from collections import defaultdict from collections.abc import Callable, Iterator from contextlib import suppress from typing import Any from xsdata.exceptions import XmlContextError from xsdata.formats.dataclass.compat import class_types from xsdata.formats.dataclass.models.builders import XmlMetaBuilder from xsdata.formats.dataclass.models.elements import XmlMeta from xsdata.formats.types import T from xsdata.models.enums import DataType from xsdata.utils.constants import return_input class XmlContext: """The models context class. The context is responsible to provide binding metadata for models and their fields. Args: element_name_generator: Default element name generator attribute_name_generator: Default attribute name generator class_type: Default class type `dataclasses` models_package: Restrict auto locate to a specific package Attributes: cache: Internal cache for binding metadata instances xsi_cache: Internal cache for xsi types to class locations sys_modules: The number of loaded sys modules """ __slots__ = ( "attribute_name_generator", "cache", "class_type", "element_name_generator", "models_package", "sys_modules", "xsi_cache", ) def __init__( self, element_name_generator: Callable = return_input, attribute_name_generator: Callable = return_input, class_type: str = "dataclasses", models_package: str | None = None, ): """Initialize the context.""" self.element_name_generator = element_name_generator self.attribute_name_generator = attribute_name_generator self.class_type = class_types.get_type(class_type) self.cache: dict[type, XmlMeta] = {} self.xsi_cache: dict[str, list[type]] = defaultdict(list) self.models_package = models_package self.sys_modules = 0 def reset(self) -> None: """Reset all internal caches.""" self.cache.clear() self.xsi_cache.clear() self.sys_modules = 0 def get_builder( self, globalns: dict[str, Callable] | None = None, ) -> XmlMetaBuilder: """Return a new xml meta builder instance.""" return XmlMetaBuilder( class_type=self.class_type, element_name_generator=self.element_name_generator, attribute_name_generator=self.attribute_name_generator, globalns=globalns, ) def fetch( self, clazz: type, parent_ns: str | None = None, xsi_type: str | None = None, ) -> XmlMeta: """Build the model metadata for the given class. Args: clazz: The requested dataclass type parent_ns: The inherited parent namespace xsi_type: if present it means that the given clazz is derived and the lookup procedure needs to check and match a dataclass model to the qualified name instead. Returns: A xml meta instance """ meta = self.build(clazz, parent_ns) subclass = None if xsi_type and meta.target_qname != xsi_type: subclass = self.find_subclass(clazz, xsi_type) return self.build(subclass, parent_ns) if subclass else meta def build_xsi_cache(self) -> None: """Index all imported data classes by their xsi:type qualified name.""" if len(sys.modules) == self.sys_modules: return self.xsi_cache.clear() builder = self.get_builder() for clazz in self.get_subclasses(object): if self.is_binding_model(clazz): meta = builder.build_class_meta(clazz) if meta.target_qname: self.xsi_cache[meta.target_qname].append(clazz) self.sys_modules = len(sys.modules) def is_binding_model(self, clazz: type[T]) -> bool: """Return whether the clazz is a binding model. If the models package is not empty also validate the class is located within that package. Args: clazz: The class type to inspect Returns: The bool result. """ if not self.class_type.is_model(clazz): return False has_valid_module = ( hasattr(clazz, "__module__") and isinstance(clazz.__module__, str) and clazz.__module__ in sys.modules ) if not has_valid_module: return False return not self.models_package or clazz.__module__.startswith( self.models_package ) def find_types(self, qname: str) -> list[type[T]]: """Find all classes that match the given xsi:type qname. - Ignores native schema types, xs:string, xs:float, xs:int, ... - Rebuild cache if new modules were imported since last run Args: qname: A namespace qualified name Returns: A list of the matched classes. """ if not DataType.from_qname(qname): self.build_xsi_cache() if qname in self.xsi_cache: return self.xsi_cache[qname] return [] def find_type(self, qname: str) -> type[T] | None: """Return the last imported class that matches the given xsi:type qname. Args: qname: A namespace qualified name Returns: A class type or None if no matches. """ types: list[type] = self.find_types(qname) return types[-1] if types else None def find_type_by_fields(self, field_names: set[str]) -> type[T] | None: """Find a data class that matches best the given list of field names. Args: field_names: A set of field names Returns: The best matching class or None if no matches. The class must have all the fields. If more than one classes have all the given fields, return the one with the least extra fields. """ def get_field_diff(clazz: type) -> int: meta = self.cache[clazz] local_names = {var.local_name for var in meta.get_all_vars()} return len(local_names - field_names) self.build_xsi_cache() choices = [ (clazz, get_field_diff(clazz)) for types in self.xsi_cache.values() for clazz in types if self.local_names_match(field_names, clazz) ] choices.sort(key=lambda x: (x[1], x[0].__name__)) return choices[0][0] if choices else None def find_subclass(self, clazz: type, qname: str) -> type | None: """Find a subclass for the given clazz and xsi:type qname. Compare all classes that match the given xsi:type qname and return the first one that is either a subclass or shares the same parent class as the original class. Args: clazz: The input clazz type qname: The xsi:type to lookup from cache Returns: The matching class type or None if no matches. """ types: list[type] = self.find_types(qname) for tp in types: # Why would a xml node with have a xsi:type that points # to parent class is beyond me, but it happens, let's protect # against that scenario if issubclass(clazz, tp): continue for tp_mro in tp.__mro__: if tp_mro is not object and tp_mro in clazz.__mro__: return tp return None def build( self, clazz: type, parent_ns: str | None = None, globalns: dict[str, Callable] | None = None, ) -> XmlMeta: """Fetch or build the binding metadata for the given class. Args: clazz: A class type parent_ns: The inherited parent namespace globalns: Override the global python namespace Returns: The class binding metadata instance. """ if clazz not in self.cache: builder = self.get_builder(globalns) self.cache[clazz] = builder.build(clazz, parent_ns) return self.cache[clazz] def build_recursive(self, clazz: type, parent_ns: str | None = None) -> None: """Build the binding metadata for the given class and all of its dependencies. This method is used in benchmarks! Args: clazz: The class type parent_ns: The inherited parent namespace """ if clazz not in self.cache: meta = self.build(clazz, parent_ns) for var in meta.get_all_vars(): types = var.element_types if var.elements else var.types for tp in types: if self.class_type.is_model(tp): self.build_recursive(tp, meta.namespace) def local_names_match(self, names: set[str], clazz: type) -> bool: """Check if the given field names match the given class type. Silently ignore, typing errors. These classes are from third party libraries most of them time. Args: names: A set of field names clazz: The class type to inspect Returns: Whether the class contains all the field names. """ try: meta = self.build(clazz) local_names = {var.local_name for var in meta.get_all_vars()} return not names.difference(local_names) except (XmlContextError, NameError, TypeError): # The dataclass includes unsupported typing annotations # Let's remove it from xsi_cache builder = self.get_builder() target_qname = builder.build_class_meta(clazz).target_qname if target_qname and target_qname in self.xsi_cache: self.xsi_cache[target_qname].remove(clazz) return False @classmethod def is_derived(cls, obj: Any, clazz: type) -> bool: """Return whether the obj is a subclass or a parent of the given class type.""" if obj is None: return False if isinstance(obj, clazz): return True return any(x is not object and isinstance(obj, x) for x in clazz.__bases__) @classmethod def get_subclasses(cls, clazz: type) -> Iterator[type]: """Return an iterator of the given class subclasses.""" subclass: type with suppress(TypeError, AttributeError): for subclass in clazz.__subclasses__(): yield from cls.get_subclasses(subclass) yield subclass tefra-xsdata-cf7afe8/xsdata/formats/dataclass/filters.py000066400000000000000000001024371526437326600236270ustar00rootroot00000000000000import re import textwrap from collections import defaultdict from collections.abc import Callable, Iterable, Iterator from typing import Any from jinja2 import Environment from xsdata.codegen.models import Attr, AttrType, Class from xsdata.codegen.utils import ClassUtils from xsdata.formats.converter import converter from xsdata.formats.dataclass.models.elements import XmlType from xsdata.models.config import ( DocstringStyle, ExtensionType, GeneratorConfig, GeneratorExtension, ObjectType, OutputFormat, ) from xsdata.models.enums import Tag from xsdata.utils import collections, namespaces, text from xsdata.utils.objects import literal_value class Filters: """Jinja filters for code generation.""" DEFAULT_KEY = "default" FACTORY_KEY = "default_factory" __slots__ = ( "class_case", "class_safe_prefix", "constant_case", "constant_safe_prefix", "default_class_annotation", "docstring_style", "extensions", "field_case", "field_safe_prefix", "format", "generic_collections", "import_patterns", "max_line_length", "module_case", "module_safe_prefix", "package_case", "package_safe_prefix", "relative_imports", "substitutions", ) def __init__(self, config: GeneratorConfig): """Initialize the filters.""" self.substitutions: dict[ObjectType, dict[str, str]] = defaultdict(dict) for sub in config.substitutions.substitution: self.substitutions[sub.type][sub.search] = sub.replace self.import_patterns: dict[str, dict[str, set[str]]] = defaultdict( lambda: defaultdict(set) ) self.extensions: dict[ExtensionType, list[GeneratorExtension]] = defaultdict( list ) for ext in config.extensions.extension: self.extensions[ext.type].append(ext) is_annotation = ext.type is ExtensionType.DECORATOR patterns = self.import_patterns[ext.module_path][ext.func_name] if is_annotation: patterns.add(f"@{ext.func_name}") else: patterns.update( [ f"({ext.func_name}", f", {ext.func_name}", f" {ext.func_name})", ] ) self.class_case: Callable = config.conventions.class_name.case self.field_case: Callable = config.conventions.field_name.case self.constant_case: Callable = config.conventions.constant_name.case self.package_case: Callable = config.conventions.package_name.case self.module_case: Callable = config.conventions.module_name.case self.class_safe_prefix: str = config.conventions.class_name.safe_prefix self.field_safe_prefix: str = config.conventions.field_name.safe_prefix self.constant_safe_prefix: str = config.conventions.constant_name.safe_prefix self.package_safe_prefix: str = config.conventions.package_name.safe_prefix self.module_safe_prefix: str = config.conventions.module_name.safe_prefix self.docstring_style: DocstringStyle = config.output.docstring_style self.max_line_length: int = config.output.max_line_length self.generic_collections: bool = config.output.generic_collections self.relative_imports: bool = config.output.relative_imports self.format = config.output.format # Build things for module, imports in self.build_import_patterns().items(): for imp, patterns in imports.items(): self.import_patterns[module][imp].update(patterns) self.default_class_annotation = self.build_class_annotation(self.format) def register(self, env: Environment) -> None: """Register the template filters to the jinja environment.""" env.globals.update( { "docstring_name": self.docstring_style.name.lower(), } ) env.filters.update( { "field_name": self.field_name, "field_type": self.field_type, "field_default": self.field_default_value, "field_metadata": self.field_metadata, "field_definition": self.field_definition, "class_name": self.class_name, "class_bases": self.class_bases, "class_annotations": self.class_annotations, "class_params": self.class_params, "format_string": self.format_string, "format_docstring": self.format_docstring, "constant_name": self.constant_name, "constant_value": self.constant_value, "default_imports": self.default_imports, "format_metadata": self.format_metadata, "type_name": self.type_name, "text_wrap": self.text_wrap, "clean_docstring": self.clean_docstring, "import_module": self.import_module, "import_class": self.import_class, "post_meta_hook": self.post_meta_hook, } ) @classmethod def build_class_annotation(cls, fmt: OutputFormat) -> str: """Build the class annotations.""" args = [] if not fmt.repr: args.append("repr=False") if not fmt.eq: args.append("eq=False") if fmt.order: args.append("order=True") if fmt.unsafe_hash: args.append("unsafe_hash=True") if fmt.frozen: args.append("frozen=True") if fmt.slots: args.append("slots=True") args.append("kw_only=True") return f"@dataclass({', '.join(args)})" if args else "@dataclass" def class_params(self, obj: Class) -> Iterator[tuple[str, str]]: """Yield the class variables with their docstring text.""" is_enum = obj.is_enumeration for attr in obj.attrs: name = attr.name docstring = self.clean_docstring(attr.help) if is_enum: yield self.constant_name(name, obj.name), docstring else: yield self.field_name(name, obj.name), docstring def class_name(self, name: str) -> str: """Class name filter. Steps: - Apply substitutions before naming conventions - Apply naming convention - Apply substitutions after naming conventions Args: name: The original class name Returns: The final class name """ name = self.apply_substitutions(name, ObjectType.CLASS) name = self.safe_name(name, self.class_safe_prefix, self.class_case) return self.apply_substitutions(name, ObjectType.CLASS) def class_bases(self, obj: Class, class_name: str) -> list[str]: """Return a list of base class names.""" bases = [self.type_name(x.type) for x in obj.extensions] derived = len(obj.extensions) > 0 for ext in self.extensions[ExtensionType.CLASS]: is_valid = not derived or ext.apply_if_derived if is_valid and ext.pattern.match(class_name): if ext.prepend: bases.insert(0, ext.func_name) else: bases.append(ext.func_name) return collections.unique_sequence(bases) @classmethod def _parent_match(clazz, ext: GeneratorExtension, obj: Class) -> bool: if not ext.parent_pattern: return True parent_path = ".".join(obj.parent_names()) return bool(ext.parent_pattern.match(parent_path)) def class_annotations(self, obj: Class, class_name: str) -> list[str]: """Return a list of decorator names.""" annotations = [] if self.default_class_annotation: annotations.append(self.default_class_annotation) derived = len(obj.extensions) > 0 for ext in self.extensions[ExtensionType.DECORATOR]: is_valid = not derived or ext.apply_if_derived if ( is_valid and ext.pattern.match(class_name) and self._parent_match(ext, obj) ): if ext.prepend: annotations.insert(0, f"@{ext.func_name}") else: annotations.append(f"@{ext.func_name}") return collections.unique_sequence(annotations) def apply_substitutions(self, name: str, obj_type: ObjectType) -> str: """Apply name substitutions by obj type.""" for search, replace in self.substitutions[obj_type].items(): name = re.sub(search, replace, name) return name def field_definition( self, obj: Class, attr: Attr, parent_namespace: str | None, ) -> str: """Return the field definition with any extra metadata.""" ns_map = obj.ns_map default_value = self.field_default_value(attr, ns_map) metadata = self.field_metadata(obj, attr, parent_namespace) kwargs: dict[str, Any] = {} if attr.fixed or attr.is_prohibited: kwargs["init"] = False if attr.is_prohibited: kwargs[self.DEFAULT_KEY] = None if default_value is not False and not attr.is_prohibited: key = self.FACTORY_KEY if attr.is_factory else self.DEFAULT_KEY kwargs[key] = default_value if metadata: kwargs["metadata"] = metadata return f"field({self.format_arguments(kwargs, 4)})" def field_name(self, name: str, class_name: str) -> str: """Field name filter. Steps: - Apply substitutions before naming conventions - Apply naming convention - Apply substitutions after naming conventions Args: name: The original field name class_name: The class name, some naming conventions require it Returns: The final field name """ prefix = self.field_safe_prefix name = self.apply_substitutions(name, ObjectType.FIELD) name = self.safe_name(name, prefix, self.field_case, class_name=class_name) return self.apply_substitutions(name, ObjectType.FIELD) def constant_name(self, name: str, class_name: str) -> str: """Constant name filter. Steps: - Apply substitutions before naming conventions - Apply naming convention - Apply substitutions after naming conventions Args: name: The original constant name class_name: The class name, some naming conventions require it Returns: The final constant name """ prefix = self.field_safe_prefix name = self.apply_substitutions(name, ObjectType.FIELD) name = self.safe_name(name, prefix, self.constant_case, class_name=class_name) return self.apply_substitutions(name, ObjectType.FIELD) def module_name(self, name: str) -> str: """Module name filter. Steps: - Apply substitutions before naming conventions - Apply naming convention - Apply substitutions after naming conventions Args: name: The original module name Returns: The final module name """ prefix = self.module_safe_prefix name = self.apply_substitutions(name, ObjectType.MODULE) name = self.safe_name(namespaces.clean_uri(name), prefix, self.module_case) return self.apply_substitutions(name, ObjectType.MODULE) def package_name(self, name: str) -> str: """Package name filter. Steps: - Apply substitutions before naming conventions - Apply naming convention - Apply substitutions after naming conventions Args: name: The original package name Returns: The final package name """ name = self.apply_substitutions(name, ObjectType.PACKAGE) if not name: return name def process_sub_package(pck: str) -> str: pck = self.safe_name(pck, self.package_safe_prefix, self.package_case) return self.apply_substitutions(pck, ObjectType.PACKAGE) parts = map(process_sub_package, name.split(".")) name = ".".join(parts) return self.apply_substitutions(name, ObjectType.PACKAGE) def type_name(self, attr_type: AttrType) -> str: """Field type filter. Args: attr_type: The attr type instance. Returns: The python type name or the user type final name """ datatype = attr_type.datatype if datatype: return datatype.type.__name__ return self.class_name(attr_type.alias or attr_type.name) def safe_name( self, name: str, prefix: str, name_case: Callable, **kwargs: Any, ) -> str: """Sanitize names for safe generation.""" if not name: return self.safe_name(prefix, prefix, name_case, **kwargs) if re.match(r"^-\d*\.?\d+$", name): return self.safe_name(f"{prefix}_minus_{name}", prefix, name_case, **kwargs) slug = text.alnum(name) if not slug or not slug[0].isalpha(): return self.safe_name(f"{prefix}_{name}", prefix, name_case, **kwargs) result = name_case(name, **kwargs) if text.is_reserved(result): return self.safe_name(f"{name}_{prefix}", prefix, name_case, **kwargs) return result def import_module(self, module: str, from_module: str) -> str: """Convert import module to relative path if config is enabled.""" if self.relative_imports: mp = module.split(".") fp = from_module.split(".") index = 0 # Find common parts index while len(mp) > index and len(fp) > index and mp[index] == fp[index]: index += 1 if index > 0: # Replace common parts with dots return f"{'.' * max(1, len(fp) - index)}{'.'.join(mp[index:])}" return module def import_class(self, name: str, alias: str | None) -> str: """Convert import class name with alias support.""" if alias: return f"{self.class_name(name)} as {self.class_name(alias)}" return self.class_name(name) def post_meta_hook(self, obj: Class) -> str | None: """Plugin hook to render additional information after the xsdata meta class.""" return None def field_metadata( self, obj: Class, attr: Attr, parent_namespace: str | None, ) -> dict: """Return a metadata dictionary for the given attribute.""" if attr.is_prohibited: return {"type": XmlType.IGNORE} name = namespace = None if not attr.is_nameless and attr.local_name != self.field_name( attr.name, obj.name ): name = attr.local_name if parent_namespace != attr.namespace or attr.is_attribute: namespace = attr.namespace restrictions = attr.restrictions.asdict(attr.native_types) metadata = { "wrapper": attr.wrapper, "name": name, "type": attr.xml_type, "namespace": namespace, "mixed": attr.mixed, "choices": self.field_choices(obj, attr, parent_namespace), **restrictions, } if not attr.is_attribute or attr.default is None: metadata.pop("required", None) if self.docstring_style == DocstringStyle.ACCESSIBLE and attr.help: metadata["doc"] = self.clean_docstring(attr.help, False) return self.filter_metadata(metadata) def field_choices( self, obj: Class, attr: Attr, parent_namespace: str | None, ) -> tuple | None: """Return a tuple of field metadata if the attr has choices.""" if not attr.choices: return None result = [] for choice in attr.choices: types = choice.native_types restrictions = choice.restrictions.asdict(types) namespace = ( choice.namespace if parent_namespace != choice.namespace else None ) metadata = { "name": choice.local_name, "wildcard": choice.is_wildcard, "type": self.choice_type(obj, choice), "namespace": namespace, } if choice.is_nameless: del metadata["name"] if choice.is_tokens: metadata[self.FACTORY_KEY] = self.field_default_value(choice) metadata.update(restrictions) if self.docstring_style == DocstringStyle.ACCESSIBLE and choice.help: metadata["doc"] = self.clean_docstring(choice.help, False) result.append(self.filter_metadata(metadata)) return tuple(result) @classmethod def filter_metadata(cls, data: dict) -> dict: """Filter out false,none keys from the given dict.""" return { key: value for key, value in data.items() if value is not None and value is not False } def format_arguments(self, data: dict, indent: int = 0) -> str: """Return a pretty keyword arguments representation.""" ind = " " * indent fmt = " {}{}={}" lines = [ fmt.format(ind, key, self.format_metadata(value, indent + 4, key)) for key, value in data.items() ] return "\n{}\n{}".format(",\n".join(lines), ind) if lines else "" def format_metadata(self, data: Any, indent: int = 0, key: str = "") -> str: """Prettify field metadata for code generation.""" if isinstance(data, dict): return self.format_dict(data, indent) if collections.is_array(data): return self.format_iterable(data, indent) if isinstance(data, str): return self.format_string(data, indent, key, 4) return literal_value(data) def format_dict(self, data: dict, indent: int) -> str: """Return a pretty string representation of a dict.""" ind = " " * indent fmt = ' {}"{}": {},' lines = [ fmt.format(ind, key, self.format_metadata(value, indent + 4, key)) for key, value in data.items() ] return "{{\n{}\n{}}}".format("\n".join(lines), ind) def format_iterable(self, data: Iterable, indent: int) -> str: """Return a pretty string representation of an iterable.""" ind = " " * indent fmt = " {}{}," lines = [ fmt.format(ind, self.format_metadata(value, indent + 4)) for value in data ] wrap = "(\n{}\n{})" if isinstance(data, tuple) else "[\n{}\n{}]" return wrap.format("\n".join(lines), ind) def format_string(self, data: str, indent: int, key: str = "", pad: int = 0) -> str: """Return a pretty string representation of a string. If the total length of the input string plus indent plus the key length and the additional pad is more than the max line length, wrap the text into multiple lines, avoiding breaking long words """ if data.startswith("ForwardRef("): return data if data.startswith("Type["): return data[5:-1] if data.startswith("Literal[") and data.endswith("]"): return data[8:-1] if key in (self.FACTORY_KEY, self.DEFAULT_KEY): return data if key == "pattern": return f"r{data!r}".replace("\\\\", "\\") if data == "": return '""' start = indent + 2 # plus quotes start += len(key) + pad if key else 0 value = text.escape_string(data) length = len(value) + start if length < self.max_line_length or " " not in value: return f'"{value}"' next_indent = indent + 4 value = "\n".join( f'{" " * next_indent}"{line}"' for line in textwrap.wrap( value, width=self.max_line_length - next_indent - 2, # plus quotes drop_whitespace=False, replace_whitespace=False, break_long_words=True, ) ) return f"(\n{value}\n{' ' * indent})" def text_wrap( self, string: str, offset: int = 0, subsequent_indent: str = " " ) -> str: """Wrap text in respect to the max line length and the given offset.""" return "\n".join( textwrap.wrap( string, width=self.max_line_length - offset, drop_whitespace=True, replace_whitespace=True, break_long_words=False, subsequent_indent=subsequent_indent, ) ) @classmethod def clean_docstring(cls, string: str | None, escape: bool = True) -> str: """Prepare string for docstring generation. - Strip whitespace from each line - Replace triple double quotes with single quotes - Escape backslashes Args: string: input value escape: skip backslashes escape, if string is going to pass through formatting. Returns: The cleaned docstring text. """ if not string: return "" def _clean(txt: str) -> str: if escape: txt = txt.replace("\\", "\\\\") return txt.replace('"""', "'''").strip() return "\n".join(_clean(line) for line in string.splitlines() if line.strip()) def format_docstring(self, doc_string: str, level: int) -> str: """Format docstrings with proper wrapping. Splits text into summary (first sentence) and description (rest), then wraps each appropriately. Args: doc_string: The raw docstring from the template level: The indentation level (1 for top-level class, 2+ for nested) Returns: The formatted docstring with proper line wrapping. """ sep_pos = doc_string.rfind('"""') if sep_pos == -1: return "" content = doc_string[:sep_pos] params = doc_string[sep_pos + 3 :].strip() # Extract the text (between opening """ and closing """) text = content[3:].strip() if content.startswith('"""') else content.strip() # Empty docstring with no params if not text and not params: return "" # Calculate available width for text (accounting for indentation and quotes) indent_width = level * 4 wrap_width = self.max_line_length - indent_width - 4 # Process the text: split into summary and description if text: # Normalize whitespace text = " ".join(text.split()) # Add period if text doesn't end with punctuation if text and text[-1] not in ".!?:": text += "." # Split into summary (first sentence) and description (rest) summary, description = self._split_docstring(text) # Wrap summary and description separately wrapped_summary = "\n".join( textwrap.wrap( summary, width=wrap_width, break_long_words=False, break_on_hyphens=False, ) ) if description: wrapped_description = "\n".join( textwrap.wrap( description, width=wrap_width, break_long_words=False, break_on_hyphens=False, ) ) wrapped_text = f"{wrapped_summary}\n\n{wrapped_description}" else: wrapped_text = wrapped_summary else: wrapped_text = "" # Build the final docstring if params: if wrapped_text: return f'"""\n{wrapped_text}\n\n{params}\n"""' return f'"""\n{params}\n"""' return f'"""\n{wrapped_text}\n"""' @staticmethod def _split_docstring(text: str) -> tuple[str, str]: """Split docstring text into summary and description. The summary is the first sentence (ending with '. ' followed by a capital letter). The description is everything after. Args: text: The normalized docstring text Returns: A tuple of (summary, description) """ # Look for sentence end: period/exclamation/question followed by # space and a capital letter (start of new sentence) match = re.search(r"[.!?] (?=[A-Z])", text) if match: split_pos = match.start() + 1 # Include the punctuation return text[:split_pos], text[split_pos:].strip() return text, "" def field_default_value(self, attr: Attr, ns_map: dict | None = None) -> Any: """Generate the field default value/factory for the given attribute.""" if attr.is_list or (attr.is_tokens and not attr.default): return "tuple" if self.format.frozen else "list" if attr.is_dict: return "dict" if attr.default is None: return None if attr.is_optional else False if not isinstance(attr.default, str): return literal_value(attr.default) if attr.default.startswith("@enum@"): return self.field_default_enum(attr) types = converter.sort_types(attr.native_types) if attr.is_tokens: return self.field_default_tokens(attr, types, ns_map) return literal_value( converter.deserialize( attr.default, types, ns_map=ns_map, format=attr.restrictions.format ) ) def field_default_enum(self, attr: Attr) -> str: """Generate the default value for enum fields.""" assert attr.default is not None qname, reference = attr.default[6:].split("::", 1) qname = next(x.alias or qname for x in attr.types if x.qname == qname) name = namespaces.local_name(qname) class_name = self.class_name(name) if attr.is_tokens: members = [ f"Literal[{class_name}.{self.constant_name(member, name)}]" for member in reference.split("@") ] return f"lambda: {self.format_metadata(members, indent=8)}" return f"{class_name}.{self.constant_name(reference, name)}" def field_default_tokens( self, attr: Attr, types: list[type], ns_map: dict | None ) -> str: """Generate the default value for tokens fields.""" assert isinstance(attr.default, str) fmt = attr.restrictions.format factory = tuple if self.format.frozen else list tokens = factory( converter.deserialize(val, types, ns_map=ns_map, format=fmt) for val in attr.default.split() ) if attr.is_enumeration: return self.format_metadata(tuple(tokens), indent=8) return f"lambda: {self.format_metadata(tokens, indent=8)}" def field_type(self, obj: Class, attr: Attr) -> str: """Generate type hints for the given attr.""" if attr.is_prohibited: return "Any" if attr.tag == Tag.CHOICE: return self.compound_field_types(obj, attr) result = self._field_type_names(obj, attr, choice=False) iterable_fmt = self._get_iterable_format() if attr.is_tokens: result = iterable_fmt.format(result) if attr.is_list: return iterable_fmt.format(result) if attr.is_tokens: return result if attr.is_dict: if self.generic_collections: return "Mapping[str, str]" return "dict[str, str]" if attr.is_nillable or (attr.default is None and attr.is_optional): return f"None | {result}" return result def compound_field_types(self, obj: Class, attr: Attr) -> str: """Generate type hint for a compound field. Args: obj: The parent class instance attr: The compound attr instance Returns: The string representation of the type hint. """ results = [] iterable_fmt = self._get_iterable_format() for choice in attr.choices: names = self._field_type_names(obj, choice, choice=False) if choice.is_tokens: names = iterable_fmt.format(names) results.append(names) result = self._join_type_names(results) if attr.is_list: return iterable_fmt.format(result) if attr.is_optional: return f"None | {result}" return result def choice_type(self, obj: Class, choice: Attr) -> str: """Generate type hints for the given choice. Choices support a subset of features from normal attributes. First of all we don't have a proper type hint but a type metadata key. That's why we always need to wrap as Type[xxx]. The second big difference is that our choice belongs to a compound field that might be a list, that's why list restriction is also ignored. Args: obj: The parent class instance choice: The choice instance Returns: The string representation of the type hint. """ result = self._field_type_names(obj, choice, choice=True) if choice.is_tokens: iterable_fmt = self._get_iterable_format() result = iterable_fmt.format(result) if result.startswith('"'): return f"ForwardRef({result})" return f"Type[{result}]" def _field_type_names( self, obj: Class, attr: Attr, choice: bool = False, ) -> str: type_names = [self._field_type_name(obj, x, choice=choice) for x in attr.types] return self._join_type_names(type_names) def _join_type_names(self, type_names: list[str]) -> str: type_names = collections.unique_sequence(type_names) if len(type_names) == 1: return type_names[0] return " | ".join(type_names) def _field_type_name( self, obj: Class, attr_type: AttrType, choice: bool = False ) -> str: name = self.type_name(attr_type) if attr_type.forward: inner = ClassUtils.find_nested(obj, attr_type.qname) outer_str = ".".join(map(self.class_name, inner.parent_names())) name = f'"{outer_str}.{name}"' elif attr_type.circular: name = f'"{name}"' if not choice: name = name.strip('"') return name def constant_value(self, attr: Attr) -> str: """Return the attr default value or type as constant value.""" attr_type = attr.types[0] if attr_type.native: return f'"{attr.default}"' if attr_type.alias: return self.class_name(attr_type.alias) return self.type_name(attr_type) def default_imports(self, output: str) -> str: """Generate the default imports for the given package output.""" imports = ["from __future__ import annotations"] for library, types in self.import_patterns.items(): names = [ name for name, searches in types.items() if any(search in output for search in searches) ] if len(names) == 1 and names[0] == "__module__": imports.append(f"import {library}") elif names: imports.append(f"from {library} import {', '.join(names)}") return "\n".join(collections.unique_sequence(imports)) def _get_iterable_format(self) -> str: if self.generic_collections: return "Sequence[{}]" return "tuple[{}, ...]" if self.format.frozen else "list[{}]" @classmethod def build_import_patterns(cls) -> dict[str, dict]: """Build import search patterns.""" type_patterns = cls.build_type_patterns return { "dataclasses": {"dataclass": ["@dataclass"], "field": [" = field("]}, "decimal": {"Decimal": type_patterns("Decimal")}, "enum": {"Enum": ["(Enum)"]}, "typing": { "ForwardRef": [": ForwardRef("], "Any": type_patterns("Any"), }, "collections.abc": { "Sequence": [": Sequence["], "Mapping": [": Mapping["], }, "xml.etree.ElementTree": {"QName": type_patterns("QName")}, "xsdata.models.datatype": { "XmlDate": type_patterns("XmlDate"), "XmlDateTime": type_patterns("XmlDateTime"), "XmlDuration": type_patterns("XmlDuration"), "XmlPeriod": type_patterns("XmlPeriod"), "XmlTime": type_patterns("XmlTime"), }, } @classmethod def build_type_patterns(cls, x: str) -> tuple[str, ...]: """Return all possible type occurrences in the generated code.""" return ( f": {x} =", f"[{x}]", f"[{x},", f" {x},", f" {x}]", f" {x}(", f" | {x}", f"{x} |", ) tefra-xsdata-cf7afe8/xsdata/formats/dataclass/generator.py000066400000000000000000000215011526437326600241350ustar00rootroot00000000000000import importlib import pkgutil import subprocess import sys from collections.abc import Iterator from pathlib import Path from jinja2 import Environment, FileSystemLoader from xsdata.codegen.exceptions import CodegenError from xsdata.codegen.models import Class, Import from xsdata.codegen.resolver import DependenciesResolver from xsdata.formats.dataclass.bytecode import MemoryBytecodeCache from xsdata.formats.dataclass.filters import Filters from xsdata.formats.mixins import AbstractGenerator, GeneratorResult from xsdata.logger import logger from xsdata.models.config import GeneratorConfig bytecode_cache = MemoryBytecodeCache() class DataclassGenerator(AbstractGenerator): """Python dataclasses code generator. Args: config: The generator config instance Attributes: env: The jinja2 environment instance filters: The template filters instance """ __slots__ = ("env", "filters") package_template = "package.jinja2" module_template = "module.jinja2" enum_template = "enum.jinja2" service_template = "service.jinja2" class_template = "class.jinja2" def __init__(self, config: GeneratorConfig): """Initialize the generator.""" super().__init__(config) template_paths = self.get_template_paths() loader = FileSystemLoader(template_paths) self.env = Environment( # nosec loader=loader, autoescape=False, bytecode_cache=bytecode_cache ) self.filters = self.init_filters(config) self.filters.register(self.env) @classmethod def get_template_paths(cls) -> list[str]: """Return a list of template paths to feed the jinja2 loader.""" return [str(Path(__file__).parent.joinpath("templates"))] def render(self, classes: list[Class]) -> Iterator[GeneratorResult]: """Render the given classes to python packages and modules. Args: classes: A list of class instances Yields: An iterator of generator result instances. """ packages = {obj.qname: obj.target_module for obj in classes} resolver = DependenciesResolver(registry=packages) package_dirs = set() # Generate packages for path, cluster in self.group_by_package(classes).items(): module = ".".join(path.relative_to(Path.cwd()).parts) package_path = path.joinpath("__init__.py") src_code = self.render_package(cluster, module) package_dirs.add(str(path)) yield GeneratorResult( path=package_path, title="init", source=src_code, ) yield from self.ensure_packages(path.parent) # Generate modules for path, cluster in self.group_by_module(classes).items(): module_path = path.with_suffix(".py") src_code = self.render_module(resolver, cluster) yield GeneratorResult( path=module_path, title=cluster[0].target_module, source=src_code, ) self.ruff_code(list(package_dirs)) self.validate_imports() def validate_imports(self) -> None: """Recursively import all generated packages. Raises: ImportError: On circular imports """ def import_package(package_name: str) -> None: logger.debug(f"Importing: {package_name}") module = importlib.import_module(package_name) if hasattr(module, "__path__"): for _, name, _ in pkgutil.walk_packages( module.__path__, module.__name__ + "." ): logger.debug(f"Importing: {name}") importlib.import_module(name) sys.path.insert(0, str(Path.cwd().absolute())) package = self.config.output.package import_package(self.package_name(package)) def render_package(self, classes: list[Class], module: str) -> str: """Render the package for the given classes. Args: classes: A list of class instances module: The target dot notation path Returns: The rendered package output. """ imports = [ Import(qname=obj.qname, source=obj.target_module) for obj in sorted(classes, key=lambda x: x.name) ] DependenciesResolver.resolve_conflicts(imports, set()) return self.env.get_template(self.package_template).render( imports=imports, module=module, ) def render_module( self, resolver: DependenciesResolver, classes: list[Class], ) -> str: """Render the module for the given classes. Args: resolver: The dependencies resolver classes: A list of class instances Returns: The rendered module output. """ if len({x.target_namespace for x in classes}) == 1: module_namespace = classes[0].target_namespace else: module_namespace = None resolver.process(classes) imports = resolver.sorted_imports() classes = resolver.sorted_classes() output = self.render_classes(classes, module_namespace) module = classes[0].target_module return self.env.get_template(self.module_template).render( output=output, classes=classes, module=module, imports=imports, namespace=module_namespace, ) def render_classes( self, classes: list[Class], module_namespace: str | None, ) -> str: """Render the classes source code in a module. Args: classes: A list of class instances module_namespace: The module namespace URI Returns: The rendered classes source code output. """ def render_class(obj: Class) -> str: """Render class or enumeration.""" if obj.is_enumeration: template = self.enum_template elif obj.is_service: template = self.service_template else: template = self.class_template return ( self.env.get_template(template) .render( obj=obj, module_namespace=module_namespace, ) .strip() ) return "\n".join(map(render_class, classes)) def module_name(self, name: str) -> str: """Convert the given module name to safe snake case.""" return self.filters.module_name(name) def package_name(self, name: str) -> str: """Convert the given package name to safe snake case.""" return self.filters.package_name(name) @classmethod def ensure_packages(cls, package: Path) -> Iterator[GeneratorResult]: """Ensure __init__.py files exists recursively in the package. Args: package: The package file path Yields: An iterator of generator result instances. """ cwd = Path.cwd() while cwd < package: init = package.joinpath("__init__.py") if not init.exists(): yield GeneratorResult( path=init, title="init", source="# nothing here\n" ) package = package.parent @classmethod def init_filters(cls, config: GeneratorConfig) -> Filters: """Initialize the filters instance by the generator configuration.""" return Filters(config) def ruff_code(self, file_paths: list[str]) -> None: """Run ruff lint and format on a list of file names. Args: file_paths: A list of files/directories to format and check """ ruff_config = Path(__file__).parent.joinpath("ruff.toml") commands = [ [ "ruff", "check", "--config", str(ruff_config), "--config", f"line-length={self.config.output.max_line_length}", "--fix", "--unsafe-fixes", "--exit-zero", *file_paths, ], [ "ruff", "format", "--config", str(ruff_config), "--config", f"line-length={self.config.output.max_line_length}", *file_paths, ], ] try: for command in commands: subprocess.run( command, capture_output=True, check=True, ) except subprocess.CalledProcessError as e: details = e.stderr.decode().replace("error: ", "").strip() raise CodegenError(f"Ruff failed\n{details}") tefra-xsdata-cf7afe8/xsdata/formats/dataclass/models/000077500000000000000000000000001526437326600230615ustar00rootroot00000000000000tefra-xsdata-cf7afe8/xsdata/formats/dataclass/models/__init__.py000066400000000000000000000000001526437326600251600ustar00rootroot00000000000000tefra-xsdata-cf7afe8/xsdata/formats/dataclass/models/builders.py000066400000000000000000000460451526437326600252550ustar00rootroot00000000000000import inspect import sys from collections import defaultdict from collections.abc import Callable, Iterator, Mapping, Sequence from enum import Enum from typing import ( Any, get_type_hints, ) from xsdata.exceptions import XmlContextError from xsdata.formats.converter import converter from xsdata.formats.dataclass.compat import ClassType from xsdata.formats.dataclass.models.elements import XmlMeta, XmlType, XmlVar from xsdata.formats.dataclass.typing import ( evaluate, evaluate_attribute, evaluate_attributes, evaluate_element, evaluate_elements, evaluate_text, evaluate_wildcard, ) from xsdata.models.enums import NamespaceType from xsdata.utils.collections import first from xsdata.utils.constants import EMPTY_SEQUENCE, return_input from xsdata.utils.namespaces import build_qname evaluations: dict[str, Callable] = { XmlType.TEXT: evaluate_text, XmlType.ELEMENT: evaluate_element, XmlType.ELEMENTS: evaluate_elements, XmlType.WILDCARD: evaluate_wildcard, XmlType.ATTRIBUTE: evaluate_attribute, XmlType.ATTRIBUTES: evaluate_attributes, } class ClassMeta: """The binding model combined metadata. Args: element_name_generator: The element name generator attribute_name_generator: The attribute name generator qname: The namespace qualified name of the class local_name: The name of the element this class represents nillable: Specifies whether this class supports nillable content namespace: The class namespace target_qname: The class target namespace qualified name """ __slots__ = ( "attribute_name_generator", "element_name_generator", "local_name", "namespace", "nillable", "qname", "target_qname", ) def __init__( self, element_name_generator: Callable, attribute_name_generator: Callable, qname: str, local_name: str, nillable: bool, namespace: str | None, target_qname: str | None, ): """Initialize class meta.""" self.element_name_generator = element_name_generator self.attribute_name_generator = attribute_name_generator self.qname = qname self.local_name = local_name self.nillable = nillable self.namespace = namespace self.target_qname = target_qname class XmlMetaBuilder: """Binding class metadata builder. Args: class_type: The supported class type, e.g. dataclass, attr, pydantic element_name_generator: The default element name generator attribute_name_generator: The default attribute name generator globalns: The global namespace """ __slots__ = ( "attribute_name_generator", "class_type", "element_name_generator", "globalns", ) def __init__( self, class_type: ClassType, element_name_generator: Callable, attribute_name_generator: Callable, globalns: dict[str, Callable] | None = None, ): """Initialize the builder.""" self.class_type = class_type self.element_name_generator = element_name_generator self.attribute_name_generator = attribute_name_generator self.globalns = globalns def build(self, clazz: type, parent_namespace: str | None) -> XmlMeta: """Build the binding metadata for a dataclass and its fields. Args: clazz: The target class parent_namespace: The parent class namespace Returns: The binding metadata instance. """ self.class_type.verify_model(clazz) meta = self.build_class_meta(clazz, parent_namespace) class_vars = self.build_vars( clazz, meta.namespace, meta.element_name_generator, meta.attribute_name_generator, ) attributes = {} elements: dict[str, list[XmlVar]] = defaultdict(list) wrappers: dict[str, str] = {} choices = [] any_attributes = [] wildcards = [] text = None for var in class_vars: if var.is_attribute: attributes[var.qname] = var elif var.is_element: elements[var.qname].append(var) if var.wrapper_qname: wrappers[var.wrapper_qname] = var.qname elif var.is_elements: choices.append(var) elif var.is_attributes: any_attributes.append(var) elif var.is_wildcard: wildcards.append(var) else: # var.is_text text = var return XmlMeta( clazz=clazz, qname=meta.qname, target_qname=meta.target_qname, nillable=meta.nillable, text=text, attributes=attributes, elements=elements, choices=choices, any_attributes=any_attributes, wildcards=wildcards, wrappers=wrappers, ) def build_vars( self, clazz: type, namespace: str | None, element_name_generator: Callable, attribute_name_generator: Callable, ) -> Iterator[XmlVar]: """Build the binding metadata for the given dataclass fields. Args: clazz: The target class namespace: The target class namespace element_name_generator: The class element name generator attribute_name_generator: The class attribute name generator Yields: An iterator of the field binding metadata instances. """ type_hints = get_type_hints(clazz, globalns=self.globalns) builder = XmlVarBuilder( class_type=self.class_type, default_xml_type=self.default_xml_type(clazz), element_name_generator=element_name_generator, attribute_name_generator=attribute_name_generator, ) for field in self.class_type.get_fields(clazz): real_clazz = self.find_declared_class(clazz, field.name) globalns = sys.modules[real_clazz.__module__].__dict__ parent_namespace = namespace if real_clazz is not clazz and "Meta" in real_clazz.__dict__: parent_namespace = getattr(real_clazz.Meta, "namespace", namespace) var = builder.build( clazz, field.name, type_hints[field.name], field.metadata, field.init, parent_namespace, self.class_type.default_value(field), globalns, ) if var is not None: yield var def build_class_meta( self, clazz: Any, parent_namespace: str | None = None, ) -> ClassMeta: """Build the class meta options and merge with the defaults. The class metaclass is not inheritable. Args: clazz: The target class parent_namespace: The parent class namespace Returns: A class meta instance. """ meta = clazz.Meta if "Meta" in clazz.__dict__ else None element_name_generator = getattr( meta, "element_name_generator", self.element_name_generator ) attribute_name_generator = getattr( meta, "attribute_name_generator", self.attribute_name_generator ) global_type = getattr(meta, "global_type", True) local_name = getattr(meta, "name", None) local_name = local_name or element_name_generator(clazz.__name__) nillable = getattr(meta, "nillable", False) namespace = getattr(meta, "namespace", parent_namespace) qname = build_qname(namespace, local_name) if self.is_inner_class(clazz) or not global_type: target_qname = None else: module = sys.modules[clazz.__module__] target_namespace = self.target_namespace(module, meta) target_qname = build_qname(target_namespace, local_name) return ClassMeta( element_name_generator, attribute_name_generator, qname, local_name, nillable, namespace, target_qname, ) @classmethod def find_declared_class(cls, clazz: type, name: str) -> Any: """Find the user class that matches the name. Todo: Honestly I have no idea why we needed this. """ for base in clazz.__mro__: ann = inspect.get_annotations(base) if ann and name in ann: return base raise XmlContextError(f"Failed to detect the declared class for field {name}") @classmethod def is_inner_class(cls, clazz: type) -> bool: """Return whether the given type is nested inside another type.""" return "." in clazz.__qualname__ @classmethod def target_namespace(cls, module: Any, meta: Any) -> str | None: """The target namespace this class metadata was defined in.""" namespace = getattr(meta, "target_namespace", None) if namespace is not None: return namespace namespace = getattr(module, "__NAMESPACE__", None) if namespace is not None: return namespace return getattr(meta, "namespace", None) def default_xml_type(self, clazz: type) -> str: """Return the default xml type for the fields of the given dataclass. If a class has fields with no xml type defined, attempt to figure it from the rest of the fields. It's either a text or an element field. # Todo hacks like this are so unnecessary... """ counters: dict[str, int] = defaultdict(int) for var in self.class_type.get_fields(clazz): xml_type = var.metadata.get("type") counters[xml_type or "undefined"] += 1 if counters[XmlType.TEXT] > 1: raise XmlContextError( f"Dataclass `{clazz.__name__}` includes more than one text node!" ) if counters["undefined"] == 1 and counters[XmlType.TEXT] == 0: return XmlType.TEXT return XmlType.ELEMENT class XmlVarBuilder: """Binding class field metadata builder. Args: class_type: The supported class type, e.g. dataclass, attr, pydantic default_xml_type: The default xml type of this class fields element_name_generator: The element name generator attribute_name_generator: The attribute name generator Attributes: index: The index of the next var """ __slots__ = ( "attribute_name_generator", "class_type", "default_xml_type", "element_name_generator", "index", ) def __init__( self, class_type: ClassType, default_xml_type: str, element_name_generator: Callable = return_input, attribute_name_generator: Callable = return_input, ): """Initialize the builder.""" self.index = 0 self.class_type = class_type self.default_xml_type = default_xml_type self.element_name_generator = element_name_generator self.attribute_name_generator = attribute_name_generator def build( self, model: type, name: str, type_hint: Any, metadata: Mapping[str, Any], init: bool, parent_namespace: str | None, default_value: Any, globalns: Any, parent_factory: Callable | None = None, ) -> XmlVar | None: """Build the binding metadata for a class field. Args: model: The model class name: The model field name type_hint: The typing annotations of the field metadata: The field metadata mapping init: Specify whether this field can be initialized parent_namespace: The class namespace default_value: The field default value or factory globalns: Python's global namespace parent_factory: The value factory Returns: The field binding metadata instance. """ xml_type = metadata.get("type", self.default_xml_type) if xml_type == XmlType.IGNORE: return None tokens = metadata.get("tokens", False) local_name = metadata.get("name") namespace = metadata.get("namespace") choices = metadata.get("choices", EMPTY_SEQUENCE) mixed = metadata.get("mixed", False) process_contents = metadata.get("process_contents", "strict") nillable = metadata.get("nillable", False) format_str = metadata.get("format", None) sequence = metadata.get("sequence", None) wrapper = metadata.get("wrapper", None) annotation = evaluate(type_hint, globalns) try: result = evaluations[xml_type](annotation, tokens=tokens) types = tuple(converter.sort_types(result.types)) if not self.is_typing_supported(types): raise TypeError except TypeError: raise XmlContextError( f"Error on {model.__qualname__}::{name}: " f"Xml {xml_type} does not support typing `{type_hint}`" ) factory = result.factory or parent_factory tokens_factory = result.tokens_factory required = not result.optional if xml_type == XmlType.ATTRIBUTE and default_value is not None: required = metadata.get("required", False) elif tokens or factory: required = False local_name = local_name or self.build_local_name(xml_type, name) any_type = self.is_any_type(types, xml_type) clazz = first(tp for tp in types if self.class_type.is_model(tp)) namespaces = self.resolve_namespaces(xml_type, namespace, parent_namespace) elements = {} wildcards = [] self.index += 1 cur_index = self.index for choice in self.build_choices( model, name, choices, factory, globalns, parent_namespace ): choice.required = False if choice.is_element: elements[choice.qname] = choice else: # choice.is_wildcard: wildcards.append(choice) return XmlVar( index=cur_index, name=name, local_name=local_name, wrapper=wrapper, init=init, mixed=mixed, format=format_str, clazz=clazz, any_type=any_type, process_contents=process_contents, required=required, nillable=nillable, sequence=sequence, factory=factory, tokens_factory=tokens_factory, default=default_value, types=types, elements=elements, wildcards=wildcards, namespaces=namespaces, xml_type=xml_type, ) def build_choices( self, model: type, name: str, choices: list[dict], factory: Callable | None, globalns: Any, parent_namespace: str | None, ) -> Iterator[XmlVar]: """Build the binding metadata for a compound dataclass field. Args: model: The model class name: The model field name choices: The list of choice metadata factory: The compound field values factory globalns: Python's global namespace parent_namespace: The class namespace Yields: An iterator of field choice binding metadata instance. """ existing_types: set[type] = set() for choice in choices: default_value = self.class_type.default_choice_value(choice) metadata = choice.copy() metadata["name"] = choice.get("name", "any") type_hint = metadata["type"] if choice.get("wildcard"): metadata["type"] = XmlType.WILDCARD else: metadata["type"] = XmlType.ELEMENT var = self.build( model, name, type_hint, metadata, True, parent_namespace, default_value, globalns, factory, ) # It's impossible for choice elements to be ignorable, read above! assert var is not None if any(True for tp in var.types if tp in existing_types): raise XmlContextError( f"Error on {model.__qualname__}::{name}: " f"Compound field contains ambiguous types" ) existing_types.update(var.types) yield var def build_local_name(self, xml_type: str, name: str) -> str: """Transform the name for serialization by the target xml type. Args: xml_type: The xml type: element, attribute, ... name: The field name Returns: The name to use for serialization. """ if xml_type == XmlType.ATTRIBUTE: return self.attribute_name_generator(name) return self.element_name_generator(name) @classmethod def resolve_namespaces( cls, xml_type: str | None, namespace: str | None, parent_namespace: str | None, ) -> tuple[str, ...]: """Resolve a fields supported namespaces. Only elements and wildcards are allowed to inherit the parent namespace if the given namespace is empty. In case of wildcard try to decode the ##any, ##other, ##local, ##target. Args: xml_type: The xml type (Text|Element(s)|Attribute(s)|Wildcard) namespace: The field namespace parent_namespace: The parent namespace Returns: A tuple of supported namespaces. """ if xml_type in (XmlType.ELEMENT, XmlType.WILDCARD) and namespace is None: namespace = parent_namespace if not namespace: return () result = set() for ns in namespace.split(): if ns == NamespaceType.TARGET_NS: result.add(parent_namespace or NamespaceType.ANY_NS) elif ns == NamespaceType.LOCAL_NS: result.add("") elif ns == NamespaceType.OTHER_NS: result.add(f"!{parent_namespace or ''}") else: result.add(ns) return tuple(result) @classmethod def is_any_type(cls, types: Sequence[type], xml_type: str) -> bool: """Return whether the given xml type supports generic values.""" if xml_type in (XmlType.ELEMENT, XmlType.ELEMENTS): return object in types return False def is_typing_supported(self, types: Sequence[type]) -> bool: """Validate all types are registered in the converter.""" for tp in types: if ( not self.class_type.is_model(tp) and tp not in converter.registry and not issubclass(tp, Enum) ): return False return True tefra-xsdata-cf7afe8/xsdata/formats/dataclass/models/elements.py000066400000000000000000000441001526437326600252460ustar00rootroot00000000000000from __future__ import annotations import itertools import operator import sys from collections.abc import Callable, Iterator, Mapping, Sequence from typing import Any from xsdata.formats.converter import converter from xsdata.models.enums import NamespaceType from xsdata.utils import collections from xsdata.utils.namespaces import build_qname, target_uri NoneType = type(None) class XmlType: """Xml node types.""" TEXT = sys.intern("Text") ELEMENT = sys.intern("Element") ELEMENTS = sys.intern("Elements") WILDCARD = sys.intern("Wildcard") ATTRIBUTE = sys.intern("Attribute") ATTRIBUTES = sys.intern("Attributes") IGNORE = sys.intern("Ignore") class MetaMixin: """Use this mixin for unit tests only!!!""" __slots__: tuple[str, ...] = () def __eq__(self, other: Any) -> bool: """Implement equality operator.""" return tuple(self) == tuple(other) def __iter__(self) -> Iterator: """Implement iteration.""" for name in self.__slots__: yield getattr(self, name) def __repr__(self) -> str: """Implement representation.""" params = (f"{name}={getattr(self, name)!r}" for name in self.__slots__) return f"{self.__class__.__qualname__}({', '.join(params)})" def default_namespace(namespaces: Sequence[str]) -> str | None: """Return the first valid namespace uri or None. Args: namespaces: A list of namespace options which may include valid uri(s) or a placeholder e.g. ##any, ##other, ##targetNamespace, ##local Returns: A namespace uri or None if there isn't any. """ for namespace in namespaces: if namespace and not namespace.startswith("#"): return namespace return None class XmlVar(MetaMixin): """Class field binding metadata. Args: index: Position index of the variable name: Name of the variable local_name: Local name of the variable wrapper: The element wrapper name types: Supported types for the variable clazz: Target class type init: Indicates if the field should be included in the constructor mixed: Indicates if the field supports mixed content type values. factory: Callable factory for lists tokens_factory: Callable factory for tokens format: Information about the value format any_type: Indicates if the field supports dynamic value types process_contents: Information about processing contents required: Indicates if the field is mandatory nillable: Indicates if the field supports nillable content sequence: Specifies rendering values in sequential mode default: Default value or factory for the field xml_type: Type of the XML field (element, attribute, etc.) namespaces: List of supported namespaces elements: Mapping of qualified name-repeatable elements wildcards: List of repeatable wildcards Attributes: qname: Namespace-qualified name of the variable wrapper_qname: Namespace-qualified name of the variable wrapper tokens: Indicates if the field has associated tokens list_element: Indicates if the field is a list or tuple element namespace_matches: Matching namespaces information is_clazz_union: Indicates if the field is a union of multiple types is_text: Indicates if the field represents text content is_element: Indicates if the field represents an XML element is_elements: Indicates if the field represents a sequence of XML elements is_wildcard: Indicates if the field represents a wildcard is_attribute: Indicates if the field represents an XML attribute is_attributes: Indicates if the field represents a sequence of XML attributes """ __slots__ = ( "any_type", "clazz", "default", "elements", "factory", "format", "index", "init", "is_attribute", "is_attributes", "is_clazz_union", "is_element", "is_elements", "is_text", "is_wildcard", "list_element", "local_name", "mixed", "name", "namespace_matches", "namespaces", "nillable", "process_contents", # Calculated "qname", "required", "sequence", "tokens", "tokens_factory", "types", "wildcards", "wrapper", "wrapper_qname", ) def __init__( self, index: int, name: str, local_name: str, wrapper: str | None, types: Sequence[type], clazz: type | None, init: bool, mixed: bool, factory: Callable | None, tokens_factory: Callable | None, format: str | None, any_type: bool, process_contents: str, required: bool, nillable: bool, sequence: int | None, default: Any, xml_type: str, namespaces: Sequence[str], elements: Mapping[str, XmlVar], wildcards: Sequence[XmlVar], **kwargs: Any, ): """Initialize the xml var.""" self.index = index self.name = name self.local_name = local_name self.wrapper = wrapper self.types = types self.clazz = clazz self.init = init self.mixed = mixed self.tokens = tokens_factory is not None self.format = format self.any_type = any_type self.process_contents = process_contents self.required = required self.nillable = nillable self.sequence = sequence self.list_element = factory in (list, tuple) self.default = default self.namespaces = namespaces self.elements = elements self.wildcards = wildcards self.wrapper = wrapper self.factory = factory self.tokens_factory = tokens_factory self.namespace_matches: dict[str, bool] | None = None self.is_clazz_union = self.clazz and len(types) > 1 namespace = default_namespace(namespaces) self.qname = build_qname(namespace, local_name) self.wrapper_qname = None if wrapper: self.wrapper_qname = build_qname(namespace, wrapper) self.is_text = False self.is_element = False self.is_elements = False self.is_wildcard = False self.is_attribute = False self.is_attributes = False if xml_type == XmlType.ELEMENTS: self.is_elements = True elif xml_type == XmlType.ELEMENT or self.clazz: self.is_element = True elif xml_type == XmlType.ATTRIBUTE: self.is_attribute = True elif xml_type == XmlType.ATTRIBUTES: self.is_attributes = True elif xml_type == XmlType.WILDCARD: self.is_wildcard = True else: self.is_text = True @property def element_types(self) -> set[type]: """Return the unique element types.""" return {tp for element in self.elements.values() for tp in element.types} def find_choice(self, qname: str) -> XmlVar | None: """Match and return a choice field by its qualified name. Args: qname: The qualified name to lookup Returns: The choice xml var instance or None if there are no matches. """ match = self.elements.get(qname) return match or find_by_namespace(self.wildcards, qname) def find_value_choice(self, value: Any, is_class: bool) -> XmlVar | None: """Match and return a choice field that matches the given value. Cases: - value is none or empty tokens list: look for a nillable choice - value is a dataclass: look for exact type or a subclass - value is primitive: test value against the converter Args: value: The value to match its type to one of the choices is_class: Whether the value is a binding class Returns: The choice xml var instance or None if there are no matches. """ is_tokens = collections.is_array(value) if value is None or (not value and is_tokens): return self.find_nillable_choice(is_tokens) if is_class: return self.find_clazz_choice(type(value)) return self.find_primitive_choice(value, is_tokens) def find_nillable_choice(self, is_tokens: bool) -> XmlVar | None: """Find the first nillable choice. Args: is_tokens: Specify if the choice must support token values Returns: The choice xml var instance or None if there are no matches. """ return collections.first( element for element in self.elements.values() if element.nillable and is_tokens == element.tokens ) def find_clazz_choice(self, clazz: type) -> XmlVar | None: """Find the best matching choice for the given class. Best Matches: 1. The class is explicitly defined in a choice types 2. The class is a subclass of one of the choice types Args: clazz: The class type to match Returns: The choice xml var instance or None if there are no matches. """ derived = None for element in self.elements.values(): if not element.clazz: continue if clazz in element.types: return element if derived is None and any(issubclass(clazz, t) for t in element.types): derived = element return derived def find_primitive_choice(self, value: Any, is_tokens: bool) -> XmlVar | None: """Match and return a choice field that matches the given primitive value. Args: value: A primitive value, e.g. str, int, float, enum is_tokens: Specify whether it's a tokens value Returns: The choice xml var instance or None if there are no matches. """ tp = type(value) if not is_tokens else type(value[0]) for element in self.elements.values(): if (element.any_type or element.clazz) or element.tokens != is_tokens: continue if tp in element.types: return element if is_tokens and all(converter.test(val, element.types) for val in value): return element if converter.test(value, element.types): return element return None def is_optional(self, value: Any) -> bool: """Verify this var is optional and the value matches the default one. Args: value: The value to compare against the default one Returns: The bool result. """ if self.required: return False if callable(self.default): return self.default() == value return self.default == value def match_namespace(self, qname: str) -> bool: """Match the given qname to the wildcard allowed namespaces. Args: qname: The namespace qualified name of an element Returns: The bool result. """ if self.namespace_matches is None: self.namespace_matches = {} matches = self.namespace_matches.get(qname) if matches is None: matches = self._match_namespace(qname) self.namespace_matches[qname] = matches return matches def _match_namespace(self, qname: str) -> bool: uri = target_uri(qname) if not self.namespaces and uri is None: return True for check in self.namespaces: if ( (not check and uri is None) or check in (uri, NamespaceType.ANY_NS) or (check and check[0] == "!" and check[1:] != uri) ): return True return False get_index = operator.attrgetter("index") class XmlMeta(MetaMixin): """Class binding metadata. Args: clazz: The binding model qname: The namespace-qualified name target_qname: The target namespace-qualified name nillable: Specifies whether this class supports nillable content text: A text variable choices: A list of compound variables elements: A mapping of qualified name to sequence of element variables wildcards: A list of wildcard variables attributes: A mapping of qualified name to attribute variable any_attributes: A list of wildcard variables wrappers: a mapping of wrapper names to sequences of wrapped variables Attributes: namespace: The target namespace extracted from the qualified name mixed_content: Specifies if the class supports mixed content """ __slots__ = ( "any_attributes", "attributes", "choices", "clazz", "elements", "mixed_content", # Calculated "namespace", "nillable", "qname", "target_qname", "text", "wildcards", "wrappers", ) def __init__( self, clazz: type, qname: str, target_qname: str | None, nillable: bool, text: XmlVar | None, choices: Sequence[XmlVar], elements: Mapping[str, Sequence[XmlVar]], wildcards: Sequence[XmlVar], attributes: Mapping[str, XmlVar], any_attributes: Sequence[XmlVar], wrappers: Mapping[str, str], **kwargs: Any, ): """Initialize the xml meta.""" self.clazz = clazz self.qname = qname self.namespace = target_uri(qname) self.target_qname = target_qname self.nillable = nillable self.text = text self.choices = choices self.elements = elements self.wildcards = wildcards self.attributes = attributes self.any_attributes = any_attributes self.mixed_content = any(wildcard.mixed for wildcard in self.wildcards) self.wrappers = wrappers @property def element_types(self) -> set[type]: """Return a unique list of all elements types.""" return { tp for elements in self.elements.values() for element in elements for tp in element.types } def get_element_vars(self) -> list[XmlVar]: """Return a sorted list of the class element variables.""" result = list( itertools.chain(self.wildcards, self.choices, *self.elements.values()) ) if self.text: result.append(self.text) return sorted(result, key=get_index) def get_attribute_vars(self) -> list[XmlVar]: """Return a sorted list of the class attribute variables.""" result = itertools.chain(self.any_attributes, self.attributes.values()) return sorted(result, key=get_index) def get_all_vars(self) -> list[XmlVar]: """Return a sorted list of all the class variables.""" result = list( itertools.chain( self.wildcards, self.choices, self.any_attributes, self.attributes.values(), *self.elements.values(), ) ) if self.text: result.append(self.text) return sorted(result, key=get_index) def find_attribute(self, qname: str) -> XmlVar | None: """Find an attribute var with the given qname. Args: qname: The namespace qualified name Returns: The xml var instance or None if there is no match. """ return self.attributes.get(qname) def find_any_attributes(self, qname: str) -> XmlVar | None: """Find a wildcard attribute var that matches the given qname. Args: qname: The namespace qualified name Returns: The xml var instance or None if there is no match. """ return find_by_namespace(self.any_attributes, qname) def find_wildcard(self, qname: str) -> XmlVar | None: """Find a wildcard var that matches the given qname. If the wildcard has choices, attempt to match and return one of them as well. Args: qname: The namespace qualified name Returns: The xml var instance or None if there is no match. """ wildcard = find_by_namespace(self.wildcards, qname) if wildcard and wildcard.elements: choice = wildcard.find_choice(qname) if choice: return choice return wildcard def find_any_wildcard(self) -> XmlVar | None: """Return the first declared wildcard var. Returns: The xml var instance or None if there are no wildcard vars. """ return self.wildcards[0] if self.wildcards else None def find_children(self, qname: str) -> Iterator[XmlVar]: """Find all class vars that match the given qname. Go through the elements, choices and wildcards. Sometimes a class might contain more than one var with the same qualified name. The binding process has to check all of them and see which one to use. Args: qname: The namespace qualified name Yields: An iterator of all the class vars that match the given qname. """ elements = self.elements.get(qname) if elements: yield from elements for choice in self.choices: match = choice.find_choice(qname) if match: yield match chd = self.find_wildcard(qname) if chd: yield chd def find_by_namespace(vars: Sequence[XmlVar], qname: str) -> XmlVar | None: """Match the given qname to one of the given vars. Args: vars: The list of vars to match qname: The namespace qualified name to lookup Returns: The first matching xml var instance or None if there are no matches. """ for xml_var in vars: if xml_var.match_namespace(qname): return xml_var return None tefra-xsdata-cf7afe8/xsdata/formats/dataclass/models/generics.py000066400000000000000000000023061526437326600252330ustar00rootroot00000000000000from dataclasses import dataclass, field from typing import Generic, TypeVar from xsdata.formats.dataclass.models.elements import XmlType T = TypeVar("T", bound=object) @dataclass class AnyElement: """Generic model to bind xml document data to wildcard fields. Args: qname: The element's qualified name text: The element's text content tail: The element's tail content children: The element's list of child elements. attributes: The element's key-value attribute mappings. """ qname: str | None = field(default=None) text: str | None = field(default=None) tail: str | None = field(default=None) children: list[object] = field( default_factory=list, metadata={"type": XmlType.WILDCARD} ) attributes: dict[str, str] = field( default_factory=dict, metadata={"type": XmlType.ATTRIBUTES} ) @dataclass class DerivedElement(Generic[T]): """Generic model wrapper for type substituted elements. Example: e.g. ... Args: qname: The element's qualified name value: The wrapped value type: The real xsi:type """ qname: str value: T type: str | None = None tefra-xsdata-cf7afe8/xsdata/formats/dataclass/parsers/000077500000000000000000000000001526437326600232555ustar00rootroot00000000000000tefra-xsdata-cf7afe8/xsdata/formats/dataclass/parsers/__init__.py000066400000000000000000000005561526437326600253740ustar00rootroot00000000000000from xsdata.formats.dataclass.parsers.dict import DictDecoder from xsdata.formats.dataclass.parsers.json import JsonParser from xsdata.formats.dataclass.parsers.tree import TreeParser from xsdata.formats.dataclass.parsers.xml import UserXmlParser, XmlParser __all__ = [ "DictDecoder", "JsonParser", "TreeParser", "UserXmlParser", "XmlParser", ] tefra-xsdata-cf7afe8/xsdata/formats/dataclass/parsers/bases.py000066400000000000000000000174011526437326600247270ustar00rootroot00000000000000import copy from dataclasses import dataclass, field from typing import Any, cast from xsdata.exceptions import ParserError from xsdata.formats.dataclass.context import XmlContext from xsdata.formats.dataclass.parsers.mixins import ( EventsHandler, PushParser, XmlHandler, XmlNode, ) from xsdata.formats.dataclass.parsers.utils import ParserUtils from xsdata.formats.types import T from xsdata.models.enums import EventType Parsed = tuple[str | None, Any] @dataclass class NodeParser(PushParser): """Bind xml nodes to data classes. Args: context: The models context instance handler: The xml handler class Attributes: ns_map: The parsed namespace prefix-URI map """ context: XmlContext = field(default_factory=XmlContext) handler: type[XmlHandler] = field(default=EventsHandler) def parse( self, source: Any, clazz: type[T] | None = None, ns_map: dict[str | None, str] | None = None, ) -> T: """Parse the input file or stream into the target class type. If no clazz is provided, the binding context will try to locate it from imported dataclasses. Args: source: The source file or stream object to parse clazz: The target class type to parse the source bytes object ns_map: A namespace prefix-URI map to record prefixes during parsing Returns: An instance of the specified class representing the parsed content. """ handler = self.handler(clazz=clazz, parser=self) try: ns_map = self.ns_map if ns_map is None else ns_map result = handler.parse(source, ns_map) except SyntaxError as e: raise ParserError(e) if result is not None: return result target_class = clazz.__name__ if clazz else "" raise ParserError(f"Failed to create target class `{target_class}`") def start( self, clazz: type | None, queue: list[XmlNode], objects: list[Parsed], qname: str, attrs: dict, ns_map: dict, ) -> None: """Build and queue the XmlNode for the starting element. Args: clazz: The target class type, auto locate if omitted queue: The XmlNode queue list objects: The list of all intermediate parsed objects qname: The element qualified name attrs: The element attributes ns_map: The element namespace prefix-URI map """ from xsdata.formats.dataclass.parsers.nodes import ElementNode, WrapperNode try: item = queue[-1] if isinstance(item, ElementNode) and qname in item.meta.wrappers: child = cast(XmlNode, WrapperNode(parent=item, qname=qname)) else: child = item.child(qname, attrs, ns_map, len(objects)) except IndexError: xsi_type = ParserUtils.xsi_type(attrs, ns_map) clazz = self.find_root_clazz( clazz=clazz, qname=qname, attrs=attrs, ns_map=ns_map, xsi_type=xsi_type, ) # Exit if we still have no binding model if clazz is None: raise ParserError(f"No class found matching root: {qname}") meta = self.context.fetch(clazz, xsi_type=xsi_type) if xsi_type is None or meta.qname == qname: derived_factory = None else: derived_factory = self.context.class_type.derived_element xsi_nil = ParserUtils.xsi_nil(attrs) child = ElementNode( position=0, meta=meta, config=self.config, attrs=attrs, ns_map=ns_map, context=self.context, derived_factory=derived_factory, xsi_type=xsi_type if derived_factory else None, xsi_nil=xsi_nil, ) queue.append(child) def end( self, queue: list[XmlNode], objects: list[Parsed], qname: str, text: str | None, tail: str | None, ) -> bool: """Parse the last xml node and bind any intermediate objects. Args: queue: The XmlNode queue list objects: The list of all intermediate parsed objects qname: The element qualified name text: The element text content tail: The element tail content Returns: Whether the binding process was successful. """ item = queue.pop() return item.bind(qname, text, tail, objects) def find_root_clazz( self, clazz: type | None, qname: str, attrs: dict, ns_map: dict, xsi_type: str | None, ) -> type | None: """Obtain the root clazz, maybe from the provided clazz. Args: clazz: The root class type, auto locate if omitted qname: Qualified name attrs: Attribute key-value map ns_map: Namespace prefix-URI map xsi_type: The xsi:type of the object Returns: The root class. """ # Match element qname directly if clazz is None: clazz = self.context.find_type(qname) # Root is xs:anyType try xsi:type if clazz is None and xsi_type: clazz = self.context.find_type(xsi_type) return clazz @dataclass class RecordParser(NodeParser): """Bind xml nodes to dataclasses and store the intermediate events. Attributes: events: The list of recorded events """ events: list = field(init=False, default_factory=list) def start( self, clazz: type | None, queue: list[XmlNode], objects: list[Parsed], qname: str, attrs: dict, ns_map: dict, ) -> None: """Build and queue the XmlNode for the starting element. Record the start event for later processing. Args: clazz: The target class type, auto locate if omitted queue: The XmlNode queue list objects: The list of all intermediate parsed objects qname: The element qualified name attrs: The element attributes ns_map: The element namespace prefix-URI map """ self.events.append((EventType.START, qname, copy.deepcopy(attrs), ns_map)) super().start(clazz, queue, objects, qname, attrs, ns_map) def end( self, queue: list[XmlNode], objects: list[Parsed], qname: str, text: str | None, tail: str | None, ) -> Any: """Parse the last xml node and bind any intermediate objects. Record the end event for later processing Args: queue: The XmlNode queue list objects: The list of all intermediate parsed objects qname: The element qualified name text: The element text content tail: The element tail content Returns: Whether the binding process was successful. """ self.events.append((EventType.END, qname, text, tail)) return super().end(queue, objects, qname, text, tail) def register_namespace( self, ns_map: dict[str | None, str], prefix: str | None, uri: str ) -> None: """Register the uri prefix in the namespace prefix-URI map. Record the start-ns event for later processing. Args: ns_map: The namespace prefix-URI map prefix: The namespace prefix uri: The namespace uri """ self.events.append((EventType.START_NS, prefix, uri)) super().register_namespace(ns_map, prefix, uri) tefra-xsdata-cf7afe8/xsdata/formats/dataclass/parsers/config.py000066400000000000000000000032001526437326600250670ustar00rootroot00000000000000from collections.abc import Callable from dataclasses import dataclass, field from typing import Any from xsdata.formats.types import T def default_class_factory(cls: type[T], params: dict[str, Any]) -> T: """The default class factory. To be used as a hook for plugins. Args: cls: The target class type to instantiate params: The class keyword arguments Returns: A new class instance with the given params. """ return cls(**params) # type: ignore @dataclass class ParserConfig: """Parsing configuration options. Not all options are applicable for both xml and json documents. Args: base_url: Specify a base URL when parsing from memory, and you need support for relative links e.g. xinclude load_dtd: Enable loading external dtd (lxml only) resolve_entities: Enable resoling external entities (lxml only) process_xinclude: Enable xinclude statements processing class_factory: Override default object instantiation fail_on_unknown_properties: Skip unknown properties or fail with exception fail_on_unknown_attributes: Skip unknown XML attributes or fail with exception fail_on_converter_warnings: Turn converter warnings to exceptions """ base_url: str | None = None load_dtd: bool = False resolve_entities: bool = False process_xinclude: bool = False class_factory: Callable[[type[T], dict[str, Any]], T] = field( default=default_class_factory ) fail_on_unknown_properties: bool = True fail_on_unknown_attributes: bool = False fail_on_converter_warnings: bool = False tefra-xsdata-cf7afe8/xsdata/formats/dataclass/parsers/dict.py000066400000000000000000000336221526437326600245600ustar00rootroot00000000000000from collections.abc import Iterable from contextlib import suppress from dataclasses import dataclass, field, replace from typing import Any, get_args, get_origin from xsdata.exceptions import ParserError from xsdata.formats.converter import converter from xsdata.formats.dataclass.context import XmlContext from xsdata.formats.dataclass.models.elements import XmlMeta, XmlVar from xsdata.formats.dataclass.parsers.config import ParserConfig from xsdata.formats.dataclass.parsers.utils import ParserUtils from xsdata.formats.types import T from xsdata.utils import collections from xsdata.utils.constants import EMPTY_MAP @dataclass class DictDecoder: """Bind a dictionary or a list of dictionaries to data models. Args: config: Parser configuration context: The models context instance """ config: ParserConfig = field(default_factory=ParserConfig) context: XmlContext = field(default_factory=XmlContext) def decode(self, data: list | dict, clazz: type[T] | None = None) -> T: """Parse the input stream into the target class type. If no clazz is provided, the binding context will try to locate it from imported dataclasses. Args: data: A dictionary or list of dictionaries clazz: The target class type to decode the input data Returns: An instance of the specified class representing the decoded content. """ tp = self.verify_type(clazz, data) if not isinstance(data, list): return self.bind_dataclass(data, tp) return [self.bind_dataclass(obj, tp) for obj in data] # type: ignore def verify_type(self, clazz: type[T] | None, data: dict | list) -> type[T]: """Verify the given data matches the given clazz. If no clazz is provided, the binding context will try to locate it from imported dataclasses. Args: clazz: The target class type to parse object data: The loaded dictionary or list of dictionaries Returns: The clazz type to bind the loaded data. """ if clazz is None: return self.detect_type(data) try: origin = get_origin(clazz) list_type = False if origin is list: list_type = True args = get_args(clazz) if len(args) != 1 or not self.context.class_type.is_model(args[0]): raise TypeError clazz = args[0] elif origin is not None: raise TypeError except TypeError: raise ParserError(f"Invalid clazz argument: {clazz}") if list_type != isinstance(data, list): if list_type: raise ParserError("Document is object, expected array") raise ParserError("Document is array, expected object") return clazz # type: ignore def detect_type(self, data: dict | list) -> type[T]: """Locate the target clazz type from the data keys. Args: data: The loaded dictionary or list of dictionaries Returns: The clazz type to bind the loaded data. """ if not data: raise ParserError("Document is empty, can not detect type") keys = data[0].keys() if isinstance(data, list) else data.keys() clazz: type[T] | None = self.context.find_type_by_fields(set(keys)) if clazz: return clazz raise ParserError(f"Unable to locate model with properties({list(keys)})") def bind_dataclass(self, data: dict, clazz: type[T]) -> T: """Create a new instance of the given class type with the given data. Args: data: The loaded data clazz: The target class type to bind the input data Returns: An instance of the class type representing the parsed content. """ if set(data.keys()) == self.context.class_type.derived_keys: return self.bind_derived_dataclass(data, clazz) meta = self.context.build(clazz) xml_vars = meta.get_all_vars() params = {} for key, value in data.items(): var = self.find_var(xml_vars, key, value) if var is None: if self.config.fail_on_unknown_properties: raise ParserError(f"Unknown property {clazz.__qualname__}.{key}") continue if var.wrapper: value = value[var.local_name] value = self.bind_value(meta, var, value) if var.init: params[var.name] = value else: ParserUtils.validate_fixed_value(meta, var, value) try: return self.config.class_factory(clazz, params) except TypeError as e: raise ParserError(e) def bind_derived_dataclass(self, data: dict, clazz: type[T]) -> Any: """Bind the input data to the given class type. Examples: { "qname": "foo", "type": "my:type", "value": {"prop": "value"} } Args: data: The derived element dictionary clazz: The target class type to bind the input data Returns: An instance of the class type representing the parsed content. """ qname = data["qname"] xsi_type = data["type"] params = data["value"] generic = self.context.class_type.derived_element if clazz is generic: real_clazz: type[T] | None = None if xsi_type: real_clazz = self.context.find_type(xsi_type) if real_clazz is None: raise ParserError( f"Unable to locate derived model " f"with properties({list(params.keys())})" ) value = self.bind_dataclass(params, real_clazz) else: value = self.bind_dataclass(params, clazz) return generic(qname=qname, type=xsi_type, value=value) def bind_best_dataclass(self, data: dict, classes: Iterable[type[T]]) -> T: """Bind the input data to all the given classes and return best match. Args: data: The derived element dictionary classes: The target class types to try Returns: An instance of one of the class types representing the parsed content. """ obj = None keys = set(data.keys()) max_score = -1.0 config = replace(self.config, fail_on_converter_warnings=True) decoder = DictDecoder(config=config, context=self.context) for clazz in classes: if not self.context.class_type.is_model(clazz): continue if self.context.local_names_match(keys, clazz): candidate = None with suppress(Exception): candidate = decoder.bind_dataclass(data, clazz) score = self.context.class_type.score_object(candidate) if score > max_score: max_score = score obj = candidate if obj: return obj raise ParserError( f"Failed to bind object with properties({list(data.keys())}) " f"to any of the {[cls.__qualname__ for cls in classes]}" ) def bind_value( self, meta: XmlMeta, var: XmlVar, value: Any, recursive: bool = False, ) -> Any: """Main entry point for binding values. Args: meta: The parent xml meta instance var: The xml var descriptor for the field value: The data value recursive: Whether this is a recursive call Returns: The parsed object """ # xs:anyAttributes get it out of the way, it's the mapping exception! if var.is_attributes: return dict(value) # Repeating element, recursively bind the values if not recursive and var.list_element and isinstance(value, list): assert var.factory is not None return var.factory( self.bind_value(meta, var, val, recursive=True) for val in value ) # If not dict this is a text or tokens value. if not isinstance(value, dict): return self.bind_text(meta, var, value) keys = value.keys() if keys == self.context.class_type.any_keys: # Bind data to AnyElement dataclass return self.bind_dataclass(value, self.context.class_type.any_element) if keys == self.context.class_type.derived_keys: # Bind data to AnyElement dataclass return self.bind_derived_value(meta, var, value) # Bind data to a user defined dataclass return self.bind_complex_type(meta, var, value) def bind_text(self, meta: XmlMeta, var: XmlVar, value: Any) -> Any: """Bind text/tokens value entrypoint. Args: meta: The parent xml meta instance var: The xml var descriptor for the field value: The data value Returns: The parsed tokens or text value. """ if var.is_elements: # Compound field we need to match the value to one of the choice elements check_subclass = self.context.class_type.is_model(value) choice = var.find_value_choice(value, check_subclass) if choice: return self.bind_text(meta, choice, value) if value is None: return value raise ParserError( f"Failed to bind '{value}' " f"to {meta.clazz.__qualname__}.{var.name} field" ) if var.any_type or var.is_wildcard: # field can support any object return the value as it is return value value = converter.serialize(value) # Convert value according to the field types return ParserUtils.parse_var( meta=meta, var=var, config=self.config, value=value, ns_map=EMPTY_MAP, ) def bind_complex_type(self, meta: XmlMeta, var: XmlVar, data: dict) -> Any: """Bind complex values entrypoint. Args: meta: The parent xml meta instance var: The xml var descriptor for the field data: The complex data value Returns: The parsed dataclass instance. """ if var.is_clazz_union: # Union of dataclasses return self.bind_best_dataclass(data, var.types) if var.elements: # Compound field with multiple choices return self.bind_best_dataclass(data, var.element_types) if var.any_type or var.is_wildcard: # xs:anyType element, check all meta classes return self.bind_best_dataclass(data, meta.element_types) assert var.clazz is not None subclasses = set(self.context.get_subclasses(var.clazz)) if subclasses: # field annotation is an abstract/base type subclasses.add(var.clazz) return self.bind_best_dataclass(data, subclasses) return self.bind_dataclass(data, var.clazz) def bind_derived_value(self, meta: XmlMeta, var: XmlVar, data: dict) -> Any: """Bind derived data entrypoint. The data is representation of a derived element, e.g. { "qname": "foo", "type": "my:type" "value": Any } The data value can be a primitive value or a complex value. Args: meta: The parent xml meta instance var: The xml var descriptor for the field data: The derived element data Returns: The parsed object. """ qname = data["qname"] xsi_type = data["type"] params = data["value"] if var.elements: choice = var.find_choice(qname) if choice is None: raise ParserError( f"Unable to locate compound element" f" {meta.clazz.__qualname__}.{var.name}[{qname}]" ) return self.bind_derived_value(meta, choice, data) if not isinstance(params, dict): # Is this scenario still possible??? value = self.bind_text(meta, var, params) elif xsi_type: clazz: type | None = self.context.find_type(xsi_type) if clazz is None: raise ParserError(f"Unable to locate xsi:type `{xsi_type}`") value = self.bind_dataclass(params, clazz) elif var.clazz: value = self.bind_complex_type(meta, var, params) else: value = self.bind_best_dataclass(params, meta.element_types) generic = self.context.class_type.derived_element return generic(qname=qname, value=value, type=xsi_type) @classmethod def find_var( cls, xml_vars: list[XmlVar], key: str, value: Any, ) -> XmlVar | None: """Match the name to a xml variable. Args: xml_vars: A list of xml vars key: A key from the loaded data value: The data assigned to the key Returns: One of the xml vars, if all search attributes match, None otherwise. """ for var in xml_vars: if var.local_name == key: var_is_list = var.list_element or var.tokens is_array = collections.is_array(value) if is_array == var_is_list: return var elif var.wrapper == key: if isinstance(value, dict) and var.local_name in value: val = value[var.local_name] var_is_list = var.list_element or var.tokens is_array = collections.is_array(val) if is_array == var_is_list: return var return None tefra-xsdata-cf7afe8/xsdata/formats/dataclass/parsers/handlers/000077500000000000000000000000001526437326600250555ustar00rootroot00000000000000tefra-xsdata-cf7afe8/xsdata/formats/dataclass/parsers/handlers/__init__.py000066400000000000000000000011341526437326600271650ustar00rootroot00000000000000from xsdata.formats.dataclass.parsers.handlers.native import XmlEventHandler from xsdata.formats.dataclass.parsers.mixins import XmlHandler try: from xsdata.formats.dataclass.parsers.handlers.lxml import LxmlEventHandler def default_handler() -> type[XmlHandler]: """Return the default xml handler.""" return LxmlEventHandler except ImportError: # pragma: no cover def default_handler() -> type[XmlHandler]: """Return the default xml handler.""" return XmlEventHandler __all__ = [ "LxmlEventHandler", "XmlEventHandler", "default_handler", ] tefra-xsdata-cf7afe8/xsdata/formats/dataclass/parsers/handlers/lxml.py000066400000000000000000000054011526437326600264030ustar00rootroot00000000000000from collections.abc import Iterable from typing import Any from lxml import etree from xsdata.exceptions import XmlHandlerError from xsdata.formats.dataclass.parsers.mixins import XmlHandler from xsdata.models.enums import EventType EVENTS = (EventType.START, EventType.END, EventType.START_NS) class LxmlEventHandler(XmlHandler): """An lxml event handler.""" def parse(self, source: Any, ns_map: dict[str | None, str]) -> Any: """Parse the source XML document. Args: source: The xml source, can be a file resource or an input stream, or a lxml tree/element. ns_map: A namespace prefix-URI recorder map Returns: An instance of the class type representing the parsed content. """ if isinstance(source, (etree._ElementTree, etree._Element)): ctx = etree.iterwalk(source, EVENTS) elif self.parser.config.process_xinclude: tree = etree.parse(source, base_url=self.parser.config.base_url) # nosec tree.xinclude() ctx = etree.iterwalk(tree, EVENTS) else: ctx = etree.iterparse( source, EVENTS, recover=True, remove_comments=True, load_dtd=self.parser.config.load_dtd, resolve_entities=self.parser.config.resolve_entities, ) return self.process_context(ctx, ns_map) def process_context( self, context: Iterable[tuple[str, Any]], ns_map: dict[str | None, str], ) -> Any: """Iterate context and push events to main parser. Args: context: The iterable lxml context ns_map: A namespace prefix-URI recorder map Returns: An instance of the class type representing the parsed content. """ for event, element in context: if event == EventType.START: self.parser.start( self.clazz, self.queue, self.objects, element.tag, element.attrib, element.nsmap, ) elif event == EventType.END: self.parser.end( self.queue, self.objects, element.tag, element.text, element.tail, ) element.clear() elif event == EventType.START_NS: prefix, uri = element self.parser.register_namespace(ns_map, prefix or None, uri) else: raise XmlHandlerError(f"Unhandled event: `{event}`.") return self.objects[-1][1] if self.objects else None tefra-xsdata-cf7afe8/xsdata/formats/dataclass/parsers/handlers/native.py000066400000000000000000000123051526437326600267160ustar00rootroot00000000000000import functools from collections.abc import Iterable, Iterator from typing import Any, Final, Literal from urllib.parse import urljoin from xml.etree import ElementInclude as xinclude from xml.etree import ElementTree as etree from xsdata.exceptions import XmlHandlerError from xsdata.formats.dataclass.parsers.mixins import XmlHandler from xsdata.models.enums import EventType from xsdata.utils import namespaces EVENTS: Final = (EventType.START, EventType.END, EventType.START_NS) class XmlEventHandler(XmlHandler): """A native xml event handler.""" def parse(self, source: Any, ns_map: dict[str | None, str]) -> Any: """Parse the source XML document. Args: source: The xml source, can be a file resource or an input stream, or a xml tree/element. ns_map: A namespace prefix-URI recorder map Returns: An instance of the class type representing the parsed content. """ if isinstance(source, etree.ElementTree): source = source.getroot() if isinstance(source, etree.Element): ctx = iterwalk(source, {}) elif self.parser.config.process_xinclude: root = etree.parse(source).getroot() # nosec base_url = get_base_url(self.parser.config.base_url, source) loader = functools.partial(xinclude_loader, base_url=base_url) xinclude.include(root, loader=loader) ctx = iterwalk(root, {}) else: ctx = etree.iterparse(source, EVENTS) # nosec return self.process_context(ctx, ns_map) def process_context( self, context: Iterable[tuple[str, Any]], ns_map: dict[str | None, str] ) -> Any: """Iterate context and push events to main parser. Args: context: The iterable xml context ns_map: A namespace prefix-URI recorder map Returns: An instance of the class type representing the parsed content. """ element_ns_map: dict = {} for event, element in context: if event == EventType.START: self.parser.start( self.clazz, self.queue, self.objects, element.tag, element.attrib, self.merge_parent_namespaces(element_ns_map), ) element_ns_map = {} elif event == EventType.END: self.parser.end( self.queue, self.objects, element.tag, element.text, element.tail, ) element.clear() elif event == EventType.START_NS: prefix, uri = element prefix = prefix or None element_ns_map[prefix] = uri self.parser.register_namespace(ns_map, prefix, uri) else: raise XmlHandlerError(f"Unhandled event: `{event}`.") return self.objects[-1][1] if self.objects else None def merge_parent_namespaces(self, ns_map: dict[str | None, str]) -> dict: """Merge the given prefix-URI map with the parent node map. This method also registers new prefixes with the parser. Args: ns_map: The current element namespace prefix-URI map Returns: The new merged namespace prefix-URI map. """ if self.queue: parent_ns_map = self.queue[-1].ns_map if not ns_map: return parent_ns_map result = parent_ns_map.copy() if parent_ns_map else {} else: result = {} for prefix, uri in ns_map.items(): result[prefix] = uri return result def iterwalk(element: etree.Element, ns_map: dict) -> Iterator[tuple[str, Any]]: """Walk over the element tree and emit events. The ElementTree doesn't preserve the original namespace prefixes, we have to generate new ones. Args: element: The etree element instance ns_map: The namespace prefix-URI mapping Yields: An iterator of events """ uri = namespaces.target_uri(element.tag) if uri is not None: prefix = namespaces.load_prefix(uri, ns_map) yield EventType.START_NS, (prefix, uri) yield EventType.START, element for child in element: yield from iterwalk(child, ns_map) yield EventType.END, element def get_base_url(base_url: str | None, source: Any) -> str | None: """Return the base url of the source. Args: base_url: The base url from the parser config source: The xml source input Returns: A base url str or None, if no base url is provided and the source is not a string path. """ if base_url: return base_url return source if isinstance(source, str) else None def xinclude_loader( href: str, parse: Literal["xml"], encoding: str | None = None, base_url: str | None = None, ) -> Any: """Custom loader for xinclude parsing. The base_url argument was added in python >= 3.9. """ return xinclude.default_loader(urljoin(base_url or "", href), parse, encoding) tefra-xsdata-cf7afe8/xsdata/formats/dataclass/parsers/json.py000066400000000000000000000061551526437326600246070ustar00rootroot00000000000000import io import json import pathlib from collections.abc import Callable from dataclasses import dataclass, field from typing import Any from xsdata.formats.dataclass.parsers import DictDecoder from xsdata.formats.types import T @dataclass class JsonParser(DictDecoder): """Json parser for data classes. Args: config: Parser configuration context: The models context instance load_factory: Json loader factory """ load_factory: Callable = field(default=json.load) def from_path(self, path: pathlib.Path, clazz: type[T] | None = None) -> T: """Parse the input file into the target class type. If no clazz is provided, the binding context will try to locate it from imported dataclasses. Args: path: The path to the input file clazz: The target class type to parse the file into Returns: An instance of the specified class representing the parsed content. """ return self.parse(str(path.resolve()), clazz) def from_string(self, source: str, clazz: type[T] | None = None) -> T: """Parse the input source string into the target class type. If no clazz is provided, the binding context will try to locate it from imported dataclasses. Args: source: The source string to parse clazz: The target class type to parse the source string into Returns: An instance of the specified class representing the parsed content. """ return self.from_bytes(source.encode(), clazz) def from_bytes(self, source: bytes, clazz: type[T] | None = None) -> T: """Parse the input source bytes object into the target class type. If no clazz is provided, the binding context will try to locate it from imported dataclasses. Args: source: The source bytes object to parse clazz: The target class type to parse the source bytes object Returns: An instance of the specified class representing the parsed content. """ return self.parse(io.BytesIO(source), clazz) def parse(self, source: Any, clazz: type[T] | None = None) -> T: """Parse the input stream into the target class type. If no clazz is provided, the binding context will try to locate it from imported dataclasses. Args: source: The source file name or stream to parse clazz: The target class type to parse the source object Returns: An instance of the specified class representing the parsed content. """ data = self.load_json(source) return self.decode(data, clazz) def load_json(self, source: Any) -> dict | list: """Load the given json source filename or stream. Args: source: A file name or file stream Returns: The loaded dictionary or list of dictionaries. """ if not hasattr(source, "read"): with open(source, "rb") as fp: return self.load_factory(fp) return self.load_factory(source) tefra-xsdata-cf7afe8/xsdata/formats/dataclass/parsers/mixins.py000066400000000000000000000220561526437326600251430ustar00rootroot00000000000000from __future__ import annotations import abc import io import pathlib from dataclasses import dataclass, field from typing import Any from xsdata.exceptions import XmlHandlerError from xsdata.formats.dataclass.parsers.config import ParserConfig from xsdata.formats.types import T from xsdata.models.enums import EventType @dataclass class PushParser: """A generic interface for event based content handlers like sax. Args: config: The parser configuration instance Attributes: ns_map: The parsed namespace prefix-URI map """ config: ParserConfig = field(default_factory=ParserConfig) ns_map: dict[str | None, str] = field(init=False, default_factory=dict) def from_path( self, path: pathlib.Path, clazz: type[T] | None = None, ns_map: dict[str | None, str] | None = None, ) -> T: """Parse the input file into the target class type. If no clazz is provided, the binding context will try to locate it from imported dataclasses. Args: path: The path to the input file clazz: The target class type to parse the file into ns_map: A namespace prefix-URI map to record prefixes during parsing Returns: An instance of the specified class representing the parsed content. """ return self.parse(str(path.resolve()), clazz, ns_map) def from_string( self, source: str, clazz: type[T] | None = None, ns_map: dict[str | None, str] | None = None, ) -> T: """Parse the input source string into the target class type. If no clazz is provided, the binding context will try to locate it from imported dataclasses. Args: source: The source string to parse clazz: The target class type to parse the source string into ns_map: A namespace prefix-URI map to record prefixes during parsing Returns: An instance of the specified class representing the parsed content. """ return self.from_bytes(source.encode(), clazz, ns_map) def from_bytes( self, source: bytes, clazz: type[T] | None = None, ns_map: dict[str | None, str] | None = None, ) -> T: """Parse the input source bytes object into the target class type. If no clazz is provided, the binding context will try to locate it from imported dataclasses. Args: source: The source bytes object to parse clazz: The target class type to parse the source bytes object ns_map: A namespace prefix-URI map to record prefixes during parsing Returns: An instance of the specified class representing the parsed content. """ return self.parse(io.BytesIO(source), clazz, ns_map) @abc.abstractmethod def parse( self, source: Any, clazz: type[T] | None = None, ns_map: dict[str | None, str] | None = None, ) -> T: """Parse the input file or stream into the target class type. If no clazz is provided, the binding context will try to locate it from imported dataclasses. Args: source: The source stream object to parse clazz: The target class type to parse the source bytes object ns_map: A namespace prefix-URI map to record prefixes during parsing Returns: An instance of the specified class representing the parsed content. """ @abc.abstractmethod def start( self, clazz: type | None, queue: list[Any], objects: list[Any], qname: str, attrs: dict[str, str], ns_map: dict[str | None, str], ) -> None: """Build and queue the XmlNode for the starting element. Args: clazz: The target class type, auto locate if omitted queue: The XmlNode queue list objects: The list of all intermediate parsed objects qname: The element qualified name attrs: The element attributes ns_map: The element namespace prefix-URI map """ @abc.abstractmethod def end( self, queue: list, objects: list, qname: str, text: str | None, tail: str | None, ) -> bool: """Parse the last xml node and bind any intermediate objects. Args: queue: The XmlNode queue list objects: The list of all intermediate parsed objects qname: The element qualified name text: The element text content tail: The element tail content Returns: Whether the binding process was successful. """ def register_namespace( self, ns_map: dict[str | None, str], prefix: str | None, uri: str ) -> None: """Register the uri prefix in the namespace prefix-URI map. Args: ns_map: The namespace prefix-URI map prefix: The namespace prefix uri: The namespace uri """ if prefix not in ns_map: ns_map[prefix] = uri class XmlNode(abc.ABC): """The xml node interface. The nodes are responsible to find and queue the child nodes when a new element starts and build the resulting object tree when the element ends. The parser needs to maintain a queue for these nodes and a list of all the intermediate objects. """ __slots__ = () @abc.abstractmethod def child(self, qname: str, attrs: dict, ns_map: dict, position: int) -> XmlNode: """Initialize the next child node to be queued, when an element starts. This entry point is responsible to create the next node type with all the necessary information on how to bind the incoming input data. Args: qname: The element qualified name attrs: The element attributes ns_map: The element namespace prefix-URI map position: The current length of the intermediate objects Returns: The child xml node instance. """ @abc.abstractmethod def bind( self, qname: str, text: str | None, tail: str | None, objects: list[Any], ) -> bool: """Bind the parsed data into an object for the ending element. This entry point is called when a xml element ends and is responsible to parse the current element attributes/text, bind any children objects and initialize new object. Args: qname: The element qualified name text: The element text content tail: The element tail content objects: The list of intermediate parsed objects Returns: Whether the binding process was successful or not. """ class XmlHandler: """Abstract content handler. Args: parser: The parser instance to feed with events clazz: The target class type, auto locate if omitted Attributes: queue: The XmlNode queue list objects: The list of intermediate parsed objects """ __slots__ = ("clazz", "objects", "parser", "queue") def __init__(self, parser: PushParser, clazz: type | None): """Initialize the handler.""" self.parser = parser self.clazz = clazz self.queue: list = [] self.objects: list = [] def parse(self, source: Any, ns_map: dict[str | None, str]) -> Any: """Parse the source XML document. Args: source: The xml source, can be a file resource or an input stream. ns_map: A dictionary to capture namespace prefixes. Returns: An instance of the class type representing the parsed content. """ raise NotImplementedError("This method must be implemented!") class EventsHandler(XmlHandler): """Sax content handler for pre-recorded events.""" def parse(self, source: list[tuple], ns_map: dict[str | None, str]) -> Any: """Forward the pre-recorded events to the main parser. Args: source: A list of event data ns_map: A namespace prefix-URI map Returns: An instance of the class type representing the parsed content. """ for event, *args in source: if event == EventType.START: qname, attrs, element_ns_map = args self.parser.start( self.clazz, self.queue, self.objects, qname, attrs, element_ns_map, ) elif event == EventType.END: qname, text, tail = args self.parser.end(self.queue, self.objects, qname, text, tail) elif event == EventType.START_NS: prefix, uri = args self.parser.register_namespace(ns_map, prefix or None, uri) else: raise XmlHandlerError(f"Unhandled event: `{event}`.") return self.objects[-1][1] if self.objects else None tefra-xsdata-cf7afe8/xsdata/formats/dataclass/parsers/nodes/000077500000000000000000000000001526437326600243655ustar00rootroot00000000000000tefra-xsdata-cf7afe8/xsdata/formats/dataclass/parsers/nodes/__init__.py000066400000000000000000000012021526437326600264710ustar00rootroot00000000000000from xsdata.formats.dataclass.parsers.nodes.element import ElementNode from xsdata.formats.dataclass.parsers.nodes.primitive import PrimitiveNode from xsdata.formats.dataclass.parsers.nodes.skip import SkipNode from xsdata.formats.dataclass.parsers.nodes.standard import StandardNode from xsdata.formats.dataclass.parsers.nodes.union import UnionNode from xsdata.formats.dataclass.parsers.nodes.wildcard import WildcardNode from xsdata.formats.dataclass.parsers.nodes.wrapper import WrapperNode __all__ = [ "ElementNode", "PrimitiveNode", "SkipNode", "StandardNode", "UnionNode", "WildcardNode", "WrapperNode", ] tefra-xsdata-cf7afe8/xsdata/formats/dataclass/parsers/nodes/element.py000066400000000000000000000524551526437326600264030ustar00rootroot00000000000000from __future__ import annotations from itertools import starmap from typing import Any from xsdata.exceptions import ParserError from xsdata.formats.converter import converter from xsdata.formats.dataclass.context import XmlContext from xsdata.formats.dataclass.models.elements import XmlMeta, XmlVar from xsdata.formats.dataclass.parsers import nodes from xsdata.formats.dataclass.parsers.config import ParserConfig from xsdata.formats.dataclass.parsers.mixins import XmlNode from xsdata.formats.dataclass.parsers.utils import ParserUtils, PendingCollection from xsdata.logger import logger from xsdata.models.enums import DataType, Namespace from xsdata.utils.namespaces import target_uri class ElementNode(XmlNode): """XmlNode for complex elements. Args: meta: The class binding metadata instance attrs: The element attributes ns_map: The element namespace prefix-URI map config: The parser config instance context: The models context instance position: The current objects length, everything after this position are considered children of this node. mixed: Specifies whether this node supports mixed content. derived_factory: Derived element factory xsi_type: The xml type substitution xsi_nil: Specifies whether element has the xsi:nil attribute Attributes: assigned: A set to store the processed sub-nodes tail_processed: Whether the tail process is consumed wrappers: A mapping of child item qname to the queue of wrapper qnames the items were parsed under, in document order """ __slots__ = ( "assigned", "attrs", "config", "context", "derived_factory", "meta", "mixed", "ns_map", "position", "tail_processed", "wrappers", "xsi_nil", "xsi_type", ) def __init__( self, meta: XmlMeta, attrs: dict, ns_map: dict, config: ParserConfig, context: XmlContext, position: int, mixed: bool = False, derived_factory: type | None = None, xsi_type: str | None = None, xsi_nil: bool | None = None, ): """Initialize the xml node.""" self.meta = meta self.attrs = attrs self.ns_map = ns_map self.config = config self.context = context self.position = position self.mixed = mixed self.derived_factory = derived_factory self.xsi_type = xsi_type self.xsi_nil = xsi_nil self.assigned: set[int] = set() self.tail_processed: bool = False # Queue of wrapper qnames per child item qname, recorded in document # order as children are built. It lets binding disambiguate sibling # wrappers that reuse the same item element name (e.g. two wrappers # whose items are both named ``Property``). self.wrappers: dict[str, list[str]] = {} def bind( self, qname: str, text: str | None, tail: str | None, objects: list[Any], ) -> bool: """Bind the parsed data into an object for the ending element. This entry point is called when a xml element ends and is responsible to parse the current element attributes/text, bind any children objects and initialize new object. Args: qname: The element qualified name text: The element text content tail: The element tail content objects: The list of intermediate parsed objects Returns: Whether the binding process was successful or not. """ obj: Any = None if not self.xsi_nil or self.meta.nillable: params: dict = {} self.bind_attrs(params) self.bind_content(params, text, tail, objects) obj = self.config.class_factory(self.meta.clazz, params) if self.derived_factory: obj = self.derived_factory(qname=qname, value=obj, type=self.xsi_type) objects.append((qname, obj)) if not self.tail_processed: tail = ParserUtils.normalize_content(tail) if tail: objects.append((None, tail)) return True def bind_content( self, params: dict, text: str | None, tail: str | None, objects: list[Any], ) -> None: """Parse the text and tail content. Args: params: The class parameters text: The element text content tail: The element tail content objects: The list of intermediate parsed objects """ wild_var = self.meta.find_any_wildcard() if wild_var and wild_var.mixed: self.bind_mixed_objects(params, wild_var, objects) bind_text = False else: self.bind_objects(params, objects) bind_text = self.bind_text(params, text) if not bind_text and wild_var: self.bind_wild_text(params, wild_var, text, tail) for key in params: if isinstance(params[key], PendingCollection): params[key] = params[key].evaluate() def bind_attrs(self, params: dict[str, Any]) -> None: """Parse the element attributes. Scenarios: - Each attribute matches a class field - Class has a wildcard field that sucks everything else Args: params: The class parameters Raises: ParserError: If the document contains an unknown attribute and the configuration is strict. """ if not self.attrs: return for qname, value in self.attrs.items(): var = self.meta.find_attribute(qname) if var and var.name not in params: self.bind_attr(params, var, value) else: var = self.meta.find_any_attributes(qname) if var: self.bind_any_attr(params, var, qname, value) else: if ( self.config.fail_on_unknown_attributes and target_uri(qname) != Namespace.XSI.uri ): raise ParserError( f"Unknown attribute {self.meta.qname}:{qname}" ) def bind_attr(self, params: dict, var: XmlVar, value: Any) -> None: """Parse an element attribute. Ignores fields with init==false! Args: params: The class parameters var: The xml var instance value: The attribute value """ value = ParserUtils.parse_var( meta=self.meta, var=var, config=self.config, value=value, ns_map=self.ns_map, ) if var.init: params[var.name] = value else: ParserUtils.validate_fixed_value(self.meta, var, value) def bind_any_attr(self, params: dict, var: XmlVar, qname: str, value: Any) -> None: """Parse an element attribute to a wildcard field. Args: params: The class parameters var: The xml var instance qname: The attribute namespace qualified name value: The attribute value """ if var.name not in params: params[var.name] = {} params[var.name][qname] = ParserUtils.parse_any_attribute(value, self.ns_map) def bind_objects(self, params: dict, objects: list) -> None: """Bind children objects. Emit a warning if an object doesn't fit in any class parameters. Args: params: The class parameters objects: The list of intermediate parsed objects """ position = self.position for qname, obj in objects[position:]: if not self.bind_object(params, qname, obj): logger.warning("Unassigned parsed object %s", qname) del objects[position:] def bind_object(self, params: dict, qname: str, value: Any) -> bool: """Bind a child object. Args: params: The class parameters qname: The qualified name of the element value: The parsed object Returns: Whether the parsed object can fit in one of class parameters or not. """ wrapper = self.pop_wrapper(qname) for var in self.meta.find_children(qname): if wrapper and var.wrapper_qname != wrapper: continue if var.is_wildcard: return self.bind_wild_var(params, var, qname, value) if self.bind_var(params, var, value): return True return False def pop_wrapper(self, qname: str) -> str | None: """Return the wrapper qname for the next child object of the qname. The wrapper qnames are recorded in document order as children are built, so popping the first one keeps binding aligned with the parsed objects and lets sibling wrappers that reuse the same item element name route to the correct field. Args: qname: The qualified name of the child element Returns: The wrapper qualified name or None if the child isn't wrapped. """ wrappers = self.wrappers.get(qname) if wrappers: return wrappers.pop(0) return None @classmethod def bind_var(cls, params: dict, var: XmlVar, value: Any) -> bool: """Bind a child object to an element field. Args: params: The class parameters var: The matched xml var instance value: The parsed object Returns: Whether the parsed object can fit in one of class parameters or not. """ if var.init: if var.list_element: items = params.get(var.name) if items is None: params[var.name] = PendingCollection([value], var.factory) else: items.append(value) elif var.name not in params: params[var.name] = value else: return False return True def bind_wild_var(self, params: dict, var: XmlVar, qname: str, value: Any) -> bool: """Bind a child object to a wildcard field. The wildcard might support one or more values. If it supports only one the values are nested under a parent generic element instance. Args: params: The class parameters var: The wildcard var instance qname: The qualified name of the element value: The parsed value Returns: Always true, since wildcard fields can absorb any value. """ value = self.prepare_generic_value(qname, value) if var.list_element: items = params.get(var.name) if items is None: params[var.name] = PendingCollection([value], var.factory) else: items.append(value) elif var.name in params: previous = params[var.name] factory = self.context.class_type.any_element if not isinstance(previous, factory) or previous.qname: params[var.name] = factory(children=[previous]) params[var.name].children.append(value) else: params[var.name] = value return True def bind_mixed_objects(self, params: dict, var: XmlVar, objects: list) -> None: """Bind children objects to a mixed content wildcard field. Args: params: The class parameters var: The wildcard var instance objects: The list of intermediate parsed objects """ pos = self.position params[var.name] = list(starmap(self.prepare_generic_value, objects[pos:])) del objects[pos:] def prepare_generic_value(self, qname: str | None, value: Any) -> Any: """Wrap primitive text nodes in a generic element. Args: qname: The qualified name of the element value: The parsed object Returns: The original parsed value if it's a data class, or the wrapped primitive value in a generic element. """ if qname and not self.context.class_type.is_model(value): any_factory = self.context.class_type.any_element value = any_factory(qname=qname, text=converter.serialize(value)) return value def bind_text(self, params: dict, text: str | None) -> bool: """Bind the element text content. Args: params: The class parameters text: The element text content Returns: Whether the text content can fit in one of class parameters or not. """ var = self.meta.text if not var or (text is None and not self.xsi_nil): return False if self.xsi_nil and not text: value = None else: value = ParserUtils.parse_var( meta=self.meta, var=var, config=self.config, value=text, ns_map=self.ns_map, ) if var.init: params[var.name] = value else: ParserUtils.validate_fixed_value(self.meta, var, value) return True def bind_wild_text( self, params: dict, var: XmlVar, text: str | None, tail: str | None, ) -> bool: """Bind the element text and tail content to a wildcard field. If the field is a list, prepend the text. Otherwise, build a generic element with the text/tail content and any attributes. If the field is already occupied, then this means the current node is a child, and we need to nested them. Args: params: The class parameters var: The wildcard var instance text: The element text content tail: The element text content Returns: Whether the text content can fit in one of class parameters or not. """ text = ParserUtils.normalize_content(text) tail = ParserUtils.normalize_content(tail) if text is tail is None: return False if var.list_element: if text is None: return False items = params.get(var.name) if items is None: params[var.name] = items = PendingCollection(None, var.factory) items.insert(0, text) else: previous = params.get(var.name, None) factory = self.context.class_type.any_element generic = factory( text=text, tail=tail, attributes=ParserUtils.parse_any_attributes(self.attrs, self.ns_map), ) if previous: generic.children.append(previous) params[var.name] = generic self.tail_processed = True return True def child( self, qname: str, attrs: dict, ns_map: dict, position: int, wrapper: str | None = None, ) -> XmlNode: """Initialize the next child node to be queued, when an element starts. This entry point is responsible to create the next node type with all the necessary information on how to bind the incoming input data. Args: qname: The element qualified name attrs: The element attributes ns_map: The element namespace prefix-URI map position: The current length of the intermediate objects wrapper: The qualified name of the wrapper element the child is nested under, if any Raises: ParserError: If the child element is unknown """ for var in self.meta.find_children(qname): if wrapper and var.wrapper_qname != wrapper: continue unique = 0 if not var.is_element or var.list_element else var.index if not unique or unique not in self.assigned: node = self.build_node(qname, var, attrs, ns_map, position) if node: if unique: self.assigned.add(unique) if wrapper: self.wrappers.setdefault(qname, []).append(wrapper) return node if self.config.fail_on_unknown_properties: raise ParserError(f"Unknown property {self.meta.qname}:{qname}") return nodes.SkipNode() def build_node( self, qname: str, var: XmlVar, attrs: dict, ns_map: dict, position: int, ) -> XmlNode | None: """Build the next child node based on the xml var instance. Args: qname: The element qualified name var: The xml var instance attrs: The element attributes ns_map: The element namespace prefix-URI map position: The current length of the intermediate objects Returns: The next child node instance, or None if nothing matched the starting element. """ if var.is_clazz_union: return nodes.UnionNode( meta=self.meta, var=var, attrs=attrs, ns_map=ns_map, config=self.config, context=self.context, position=position, ) xsi_type = ParserUtils.xsi_type(attrs, ns_map) xsi_nil = ParserUtils.xsi_nil(attrs) derived_factory = self.context.class_type.derived_element if var.clazz: return self.build_element_node( var.clazz, False, var.nillable, attrs, ns_map, position, derived_factory, xsi_type, xsi_nil, ) if not var.any_type and not var.is_wildcard: return nodes.PrimitiveNode(self.meta, var, ns_map, self.config) datatype = DataType.from_qname(xsi_type) if xsi_type else None derived = var.is_wildcard if datatype: return nodes.StandardNode( self.meta, var, datatype, ns_map, self.config, var.nillable, derived_factory if derived else None, ) node = None clazz: type | None = None if xsi_type: clazz = self.context.find_type(xsi_type) if clazz: node = self.build_element_node( clazz, derived, var.nillable, attrs, ns_map, position, derived_factory, xsi_type, xsi_nil, ) if node: return node if var.process_contents != "skip": clazz = self.context.find_type(qname) if clazz: node = self.build_element_node( clazz, False, var.nillable, attrs, ns_map, position, None, xsi_type, xsi_nil, ) if node: return node return nodes.WildcardNode( var=var, attrs=attrs, ns_map=ns_map, position=position, factory=self.context.class_type.any_element, ) def build_element_node( self, clazz: type, derived: bool, nillable: bool, attrs: dict, ns_map: dict, position: int, derived_factory: type | None, xsi_type: str | None = None, xsi_nil: bool | None = None, ) -> ElementNode | None: """Build the next element child node. Args: clazz: The target class derived: Whether derived elements should wrap the parsed object nillable: Specifies whether nil content is allowed attrs: The element attributes ns_map: The element namespace prefix-URI map position: The current length of the intermediate objects derived_factory: The derived factory xsi_type: The xml type substitution xsi_nil: Specifies whether the node supports nillable content Returns: The next child element node instance, or None if the clazz doesn't match the starting element. """ meta = self.context.fetch(clazz, self.meta.namespace, xsi_type) nillable = nillable or meta.nillable if not meta or (xsi_nil is not None and nillable != xsi_nil): return None if xsi_type and not derived and not issubclass(meta.clazz, clazz): derived = True return ElementNode( meta=meta, config=self.config, attrs=attrs, ns_map=ns_map, context=self.context, position=position, derived_factory=derived_factory if derived else None, xsi_type=xsi_type, xsi_nil=xsi_nil, mixed=self.meta.mixed_content, ) tefra-xsdata-cf7afe8/xsdata/formats/dataclass/parsers/nodes/primitive.py000066400000000000000000000043641526437326600267560ustar00rootroot00000000000000from xsdata.exceptions import XmlContextError from xsdata.formats.dataclass.models.elements import XmlMeta, XmlVar from xsdata.formats.dataclass.parsers.config import ParserConfig from xsdata.formats.dataclass.parsers.mixins import XmlNode from xsdata.formats.dataclass.parsers.utils import ParserUtils class PrimitiveNode(XmlNode): """XmlNode for text elements with simple type values. Args: meta: The parent xml meta instance var: The xml var instance ns_map: The element namespace prefix-URI map config: The parser config instance """ __slots__ = "config", "meta", "ns_map", "var" def __init__(self, meta: XmlMeta, var: XmlVar, ns_map: dict, config: ParserConfig): """Initialize the xml node.""" self.meta = meta self.var = var self.ns_map = ns_map self.config = config def bind( self, qname: str, text: str | None, tail: str | None, objects: list, ) -> bool: """Bind the parsed data into an object for the ending element. This entry point is called when a xml element ends and is responsible to parse the current element attributes/text/tail content. Args: qname: The element qualified name text: The element text content tail: The element tail content objects: The list of intermediate parsed objects Returns: Whether the binding process was successful or not. """ obj = ParserUtils.parse_var( meta=self.meta, var=self.var, config=self.config, value=text, ns_map=self.ns_map, ) if obj is None and not self.var.nillable: obj = b"" if bytes in self.var.types else "" objects.append((qname, obj)) if self.meta.mixed_content: tail = ParserUtils.normalize_content(tail) if tail: objects.append((None, tail)) return True def child(self, qname: str, attrs: dict, ns_map: dict, position: int) -> XmlNode: """Raise an exception if there is a child element inside this node.""" raise XmlContextError("Primitive node doesn't support child nodes!") tefra-xsdata-cf7afe8/xsdata/formats/dataclass/parsers/nodes/skip.py000066400000000000000000000011351526437326600257050ustar00rootroot00000000000000from xsdata.formats.dataclass.parsers.mixins import XmlNode class SkipNode(XmlNode): """Utility node to skip parsing unknown properties.""" __slots__ = "ns_map" def __init__(self): """Initialize the xml node.""" self.ns_map = {} def child(self, qname: str, attrs: dict, ns_map: dict, position: int) -> XmlNode: """Skip nodes children are skipped as well.""" return self def bind( self, qname: str, text: str | None, tail: str | None, objects: list ) -> bool: """Skip nodes are not building any objects.""" return False tefra-xsdata-cf7afe8/xsdata/formats/dataclass/parsers/nodes/standard.py000066400000000000000000000055471526437326600265520ustar00rootroot00000000000000from xsdata.exceptions import XmlContextError from xsdata.formats.dataclass.models.elements import XmlMeta, XmlVar from xsdata.formats.dataclass.parsers.config import ParserConfig from xsdata.formats.dataclass.parsers.mixins import XmlNode from xsdata.formats.dataclass.parsers.utils import ParserUtils from xsdata.models.enums import DataType class StandardNode(XmlNode): """XmlNode for elements with a standard xsi:type. Args: meta: The parent xml meta instance var: The xml var instance datatype: The element standard xsi data type ns_map: The element namespace prefix-URI map config: The parser config instance nillable: Specifies whether nil content is allowed derived_factory: The derived element factory """ __slots__ = ( "config", "datatype", "derived_factory", "meta", "nillable", "ns_map", "var", ) def __init__( self, meta: XmlMeta, var: XmlVar, datatype: DataType, ns_map: dict, config: ParserConfig, nillable: bool, derived_factory: type | None, ): """Initialize the xml node.""" self.meta = meta self.var = var self.datatype = datatype self.ns_map = ns_map self.config = config self.nillable = nillable self.derived_factory = derived_factory def bind( self, qname: str, text: str | None, tail: str | None, objects: list, ) -> bool: """Bind the parsed data into an object for the ending element. This entry point is called when a xml element ends and is responsible to parse the current element text content. Args: qname: The element qualified name text: The element text content tail: The element tail content objects: The list of intermediate parsed objects Returns: Always true, it's not possible to fail during parsing for this node. """ obj = ParserUtils.parse_var( meta=self.meta, var=self.var, config=self.config, value=text, types=[self.datatype.type], ns_map=self.ns_map, format=self.datatype.format, ) if obj is None and not self.nillable: obj = "" if self.datatype.wrapper: obj = self.datatype.wrapper(obj) if self.derived_factory: obj = self.derived_factory(qname=qname, value=obj) objects.append((qname, obj)) return True def child(self, qname: str, attrs: dict, ns_map: dict, position: int) -> XmlNode: """Raise an exception if there is a child element inside this node.""" raise XmlContextError("StandardNode node doesn't support child nodes!") tefra-xsdata-cf7afe8/xsdata/formats/dataclass/parsers/nodes/union.py000066400000000000000000000141271526437326600260740ustar00rootroot00000000000000import copy import functools from contextlib import suppress from dataclasses import replace from typing import Any from xsdata.exceptions import ParserError from xsdata.formats.dataclass.context import XmlContext from xsdata.formats.dataclass.models.elements import XmlMeta, XmlVar from xsdata.formats.dataclass.parsers.bases import NodeParser from xsdata.formats.dataclass.parsers.config import ParserConfig from xsdata.formats.dataclass.parsers.mixins import EventsHandler, XmlNode from xsdata.formats.dataclass.parsers.utils import ParserUtils from xsdata.utils.namespaces import target_uri class UnionNode(XmlNode): """XmlNode for union fields with at least one data class. The node will record all child events and in the end will replay them and try to build all possible objects and sort them by score before deciding the winner. Args: var: The xml var instance attrs: The element attributes ns_map: The element namespace prefix-URI map position: The current objects length, everything after this position are considered children of this node. config: The parser config instance context: The xml context instance """ __slots__ = ( "attrs", "candidates", "config", "context", "events", "level", "meta", "ns_map", "position", "var", ) def __init__( self, meta: XmlMeta, var: XmlVar, attrs: dict, ns_map: dict, position: int, config: ParserConfig, context: XmlContext, ): """Initialize the xml node.""" self.meta = meta self.var = var self.attrs = attrs self.ns_map = ns_map self.position = position self.config = config self.context = context self.level = 0 self.candidates = self.filter_candidates() self.events: list[tuple[str, str, Any, Any]] = [] def filter_candidates(self) -> list[type]: """Filter union candidates by fixed attributes.""" candidates = list(self.var.types) fixed_attribute = functools.partial( self.filter_fixed_attrs, parent_ns=target_uri(self.var.qname) ) return list(filter(fixed_attribute, candidates)) def filter_fixed_attrs(self, candidate: type, parent_ns: str | None) -> bool: """Return whether the node attrs are incompatible with fixed attrs. Args: candidate: The candidate type parent_ns: The parent namespace """ if not self.context.class_type.is_model(candidate): return not self.attrs meta = self.context.build(candidate, parent_ns=parent_ns) for qname, value in self.attrs.items(): var = meta.find_attribute(qname) if not var or var.init: continue try: ParserUtils.validate_fixed_value(meta, var, value) except ParserError: return False return True def child(self, qname: str, attrs: dict, ns_map: dict, position: int) -> XmlNode: """Record the event for the child element. This entry point records all events, as it's not possible to detect the target parsed object type just yet. When this node ends, it will replay all events and attempt to find the best matching type for the parsed object. Args: qname: The element qualified name attrs: The element attributes ns_map: The element namespace prefix-URI map position: The current length of the intermediate objects """ self.level += 1 self.events.append(("start", qname, copy.deepcopy(attrs), ns_map)) return self def bind( self, qname: str, text: str | None, tail: str | None, objects: list, ) -> bool: """Bind the parsed data into an object for the ending element. This entry point is called when a xml element ends and is responsible to replay all xml events and parse/bind all the children objects. Args: qname: The element qualified name text: The element text content tail: The element tail content objects: The list of intermediate parsed objects Returns: Always returns true, if the binding process fails it raises an exception. Raises: ParserError: If none of the candidate types matched the replayed events. """ self.events.append(("end", qname, text, tail)) if self.level > 0: self.level -= 1 return False self.events.insert(0, ("start", qname, copy.deepcopy(self.attrs), self.ns_map)) obj = None max_score = -1.0 parent_namespace = target_uri(qname) config = replace(self.config, fail_on_converter_warnings=True) for candidate in self.candidates: result: Any = None with suppress(Exception): if self.context.class_type.is_model(candidate): self.context.build(candidate, parent_ns=parent_namespace) parser = NodeParser( config=config, context=self.context, handler=EventsHandler, ) result = parser.parse(self.events, candidate) else: result = ParserUtils.parse_var( meta=self.meta, var=self.var, config=config, value=text, types=[candidate], ns_map=self.ns_map, ) score = self.context.class_type.score_object(result) if score > max_score: max_score = score obj = result if obj: objects.append((self.var.qname, obj)) return True raise ParserError(f"Failed to parse union node: {self.var.qname}") tefra-xsdata-cf7afe8/xsdata/formats/dataclass/parsers/nodes/wildcard.py000066400000000000000000000076251526437326600265420ustar00rootroot00000000000000from xsdata.formats.dataclass.models.elements import XmlVar from xsdata.formats.dataclass.parsers.mixins import XmlNode from xsdata.formats.dataclass.parsers.utils import ParserUtils class WildcardNode(XmlNode): """XmlNode for extensible elements that can hold any attribute and content. The resulting object tree will be a :class:`~xsdata.formats.dataclass.models.generics.AnyElement` instance. Args: var: The xml var instance attrs: The element attributes ns_map: The element namespace prefix-URI map position: The current objects length, everything after this position are considered children of this node. factory: The generic element factory """ __slots__ = "attrs", "factory", "ns_map", "position", "var" def __init__( self, var: XmlVar, attrs: dict, ns_map: dict, position: int, factory: type, ): """Initialize the xml node.""" self.var = var self.attrs = attrs self.ns_map = ns_map self.position = position self.factory = factory def bind( self, qname: str, text: str | None, tail: str | None, objects: list ) -> bool: """Bind the parsed data into a generic element. This entry point is called when a xml element ends and is responsible to parse the current element attributes/text, bind any children objects and initialize new generic element that can fit any xml string. Args: qname: The element qualified name text: The element text content tail: The element tail content objects: The list of intermediate parsed objects Returns: Whether the binding process was successful or not. """ children = self.fetch_any_children(self.position, objects) attributes = ParserUtils.parse_any_attributes(self.attrs, self.ns_map) derived = qname != self.var.qname text = ParserUtils.normalize_content(text) if children else text text = "" if text is None and not self.var.nillable else text tail = ParserUtils.normalize_content(tail) if tail or attributes or children or self.var.is_wildcard or derived: obj = self.factory( qname=qname, text=text, tail=tail, attributes=attributes, children=children, ) objects.append((self.var.qname, obj)) else: objects.append((self.var.qname, text)) return True @classmethod def fetch_any_children(cls, position: int, objects: list) -> list: """Fetch the children of this node in the objects list. The children are removed from the objects list. Args: position: The position of the objects when this node was created. objects: The list of intermediate parsed objects Returns: A list of parsed objects. """ children = [value for _, value in objects[position:]] del objects[position:] return children def child(self, qname: str, attrs: dict, ns_map: dict, position: int) -> XmlNode: """Initialize the next child wildcard node to be queued, when an element starts. This entry point is responsible to create the next node type with all the necessary information on how to bind the incoming input data. Wildcard nodes always return wildcard children. Args: qname: The element qualified name attrs: The element attributes ns_map: The element namespace prefix-URI map position: The current length of the intermediate objects """ return WildcardNode( position=position, var=self.var, attrs=attrs, ns_map=ns_map, factory=self.factory, ) tefra-xsdata-cf7afe8/xsdata/formats/dataclass/parsers/nodes/wrapper.py000066400000000000000000000034241526437326600264220ustar00rootroot00000000000000from xsdata.formats.dataclass.parsers.mixins import XmlNode from xsdata.formats.dataclass.parsers.nodes.element import ElementNode class WrapperNode(XmlNode): """XmlNode for wrapper class fields. This node represents wrap class fields, that don't actually appear in the serialized document. These fields simplify classes and this kind of node simply proxies the child requests to the parent node. Args: parent: The parent node qname: The wrapper element qualified name Attributes: ns_map: The node namespace prefix-URI map """ def __init__(self, parent: ElementNode, qname: str): """Initialize the xml node.""" self.parent = parent self.qname = qname self.ns_map = parent.ns_map def bind( self, qname: str, text: str | None, tail: str | None, objects: list ) -> bool: """This node will never appear in the xml, so it never binds any data. Args: qname: The element qualified name text: The element text content tail: The element tail content objects: The list of intermediate parsed objects Returns: Always false because no binding takes place. """ return False def child(self, qname: str, attrs: dict, ns_map: dict, position: int) -> XmlNode: """Proxy the next child node to the parent node. Args: qname: The element qualified name attrs: The element attributes ns_map: The element namespace prefix-URI map position: The current length of the intermediate objects Returns: The child xml node instance. """ return self.parent.child(qname, attrs, ns_map, position, wrapper=self.qname) tefra-xsdata-cf7afe8/xsdata/formats/dataclass/parsers/tree.py000066400000000000000000000045321526437326600245720ustar00rootroot00000000000000from dataclasses import dataclass, field from xsdata.formats.dataclass.models.elements import XmlType, XmlVar from xsdata.formats.dataclass.parsers.bases import NodeParser, Parsed from xsdata.formats.dataclass.parsers.handlers import default_handler from xsdata.formats.dataclass.parsers.mixins import XmlHandler, XmlNode from xsdata.formats.dataclass.parsers.nodes.wildcard import WildcardNode from xsdata.utils import namespaces @dataclass class TreeParser(NodeParser): """Bind xml nodes to a tree of AnyElement objects.""" handler: type[XmlHandler] = field(default=default_handler()) def start( self, clazz: type | None, queue: list[XmlNode], objects: list[Parsed], qname: str, attrs: dict, ns_map: dict, ) -> None: """Build and queue the XmlNode for the starting element. Args: clazz: The target class type, auto locate if omitted queue: The XmlNode queue list objects: The list of all intermediate parsed objects qname: The element qualified name attrs: The element attributes ns_map: The element namespace prefix-URI map """ try: item = queue[-1] child = item.child(qname, attrs, ns_map, len(objects)) except IndexError: namespace, name = namespaces.split_qname(qname) var = XmlVar( name=name, local_name=name, wrapper=None, xml_type=XmlType.WILDCARD, index=0, types=(object,), clazz=None, init=True, mixed=False, factory=None, tokens_factory=None, format=None, derived=False, any_type=False, process_contents="strict", required=False, nillable=False, sequence=None, default=None, namespaces=(namespace,), elements={}, wildcards=(), ) child = WildcardNode( var=var, attrs=attrs, ns_map=ns_map, position=0, factory=self.context.class_type.any_element, ) queue.append(child) tefra-xsdata-cf7afe8/xsdata/formats/dataclass/parsers/utils.py000066400000000000000000000177201526437326600247760ustar00rootroot00000000000000import math import warnings from collections import UserList from collections.abc import Callable, Iterable, Sequence from typing import Any from xsdata.exceptions import ConverterError, ConverterWarning, ParserError from xsdata.formats.converter import QNameConverter, converter from xsdata.formats.dataclass.models.elements import XmlMeta, XmlVar from xsdata.formats.dataclass.parsers.config import ParserConfig from xsdata.models.enums import QNames from xsdata.utils import collections, constants, text from xsdata.utils.namespaces import build_qname class _MissingType: ... MISSING = _MissingType() class PendingCollection(UserList): """An iterable implementation of parsed values. The values are parsed individually in the end we need to convert it to a tuple or a list based on the mutability setting of the data class. Args: initlist: An initial list of values or None factory: A callable factory for the values when the node parse has finished. """ def __init__(self, initlist: Iterable | None, factory: Callable | None): """Initialize the pending collection.""" super().__init__(initlist) self.factory = factory or list def evaluate(self) -> Iterable[Any]: """Evaluate the values factory and return the result. Returns: A list or tuple or set of values """ return self.factory(self.data) class ParserUtils: """Random parser util functions.""" @classmethod def xsi_type(cls, attrs: dict, ns_map: dict) -> str | None: """Parse the xsi:type attribute value if present. Args: attrs: The element attributes ns_map: The element namespace prefix-URI map Returns: The xsi:type attribute value or None """ xsi_type = attrs.get(QNames.XSI_TYPE) if not xsi_type: return None namespace, name = QNameConverter.resolve(xsi_type, ns_map) return build_qname(namespace, name) @classmethod def xsi_nil(cls, attrs: dict) -> bool | None: """Return whether xsi:nil attribute value. Args: attrs: The element attributes Returns: The bool value or None if it doesn't exist. """ xsi_nil = attrs.get(QNames.XSI_NIL) return xsi_nil == constants.XML_TRUE if xsi_nil else None @classmethod def parse_var( cls, meta: XmlMeta, var: XmlVar, config: ParserConfig, value: Any, ns_map: dict | None = None, default: Any = None, types: Sequence[type] | None = None, tokens_factory: Callable | None = None, format: str | None = None, ) -> Any: """Convert a value to a python primitive type. Args: meta: The xml meta instance var: The xml var instance config: The parser config instance value: A primitive value or a list of primitive values ns_map: The element namespace prefix-URI map default: Override the var default value types: Override the var types tokens_factory: Override the var tokens factory format: Override the var format Returns: The converted value or values. """ try: value = cls.parse_value( value=value, types=types or var.types, default=default or var.default, ns_map=ns_map, tokens_factory=tokens_factory or var.tokens_factory, format=format or var.format, ) except ConverterError as ex: message = ( "Failed to convert value for " f"`{meta.clazz.__qualname__}.{var.name}`\n {ex}" ) if config.fail_on_converter_warnings: raise ParserError(message) warnings.warn(message, ConverterWarning) return value @classmethod def parse_value( cls, value: Any, types: Sequence[type], default: Any | None = None, ns_map: dict | None = None, tokens_factory: Callable | None = None, format: str | None = None, ) -> Any: """Convert a value to a python primitive type. Args: value: A primitive value or a list of primitive values types: An iterable of types to try to convert the value default: The default value/factory if the given is None ns_map: The element namespace prefix-URI map tokens_factory: A callable factory for the converted values if the element is derived from xs:NMTOKENS format: The format argument for base64/hex values or dates. Returns: The converted value or values. """ if value is None: if callable(default): return default() if tokens_factory else None return default if tokens_factory: value = value if collections.is_array(value) else value.split() return tokens_factory( converter.deserialize(val, types, ns_map=ns_map, format=format) for val in value ) return converter.deserialize(value, types, ns_map=ns_map, format=format) @classmethod def normalize_content(cls, value: str | None) -> str | None: """Normalize element text or tail content. If content is just whitespace return None, otherwise preserve the original content. Args: value: The element content Returns: The normalized content """ if value and value.strip(): return value return None @classmethod def parse_any_attributes( cls, attrs: dict[str, str], ns_map: dict[str | None, str] ) -> dict[str, str]: """Parse attributes with qname support. Example: {"foo": "bar", "xsi:type": "my:type"} -> {"foo": "bar", "xsi:type" "{http://someuri.com}type"} Args: attrs: The element attributes ns_map: The element namespace prefix-URI map Returns: The parsed attributes with expanded namespace prefixes """ return { key: cls.parse_any_attribute(value, ns_map) for key, value in attrs.items() } @classmethod def parse_any_attribute(cls, value: str, ns_map: dict) -> str: """Expand the value with the full namespace if it has a prefix. Args: value: The attr value ns_map: The element namespace prefix-URI map Returns: The expanded value. """ prefix, suffix = text.split(value) if prefix and prefix in ns_map and not suffix.startswith("//"): value = build_qname(ns_map[prefix], suffix) return value @classmethod def validate_fixed_value(cls, meta: XmlMeta, var: XmlVar, value: Any): """Validate if the parsed value matches the fixed value. Special cases - float nans are never equal in python - strings with whitespaces, need trimming - comparing raw str values """ default_value = var.default() if callable(var.default) else var.default if ( isinstance(default_value, float) and isinstance(value, float) and math.isnan(default_value) and math.isnan(value) ) or ( isinstance(default_value, str) and isinstance(value, str) and default_value.strip() == value.strip() ): return if isinstance(value, str) and not isinstance(default_value, str): default_value = converter.serialize(default_value, format=var.format) if default_value != value: raise ParserError( f"Fixed value mismatch {meta.qname}:{var.qname}, " f"`{default_value} != {value}`" ) tefra-xsdata-cf7afe8/xsdata/formats/dataclass/parsers/xml.py000066400000000000000000000075021526437326600244330ustar00rootroot00000000000000from dataclasses import dataclass, field from typing import Any from xsdata.formats.dataclass.parsers.bases import NodeParser, Parsed from xsdata.formats.dataclass.parsers.handlers import default_handler from xsdata.formats.dataclass.parsers.mixins import XmlHandler, XmlNode from xsdata.models.enums import EventType from xsdata.utils.namespaces import local_name from xsdata.utils.text import snake_case @dataclass class XmlParser(NodeParser): """Default Xml parser for data classes. Args: config: The parser config instance context: The xml context instance handler: The xml handler class Attributes: ns_map: The parsed namespace prefix-URI map """ handler: type[XmlHandler] = field(default=default_handler()) @dataclass class UserXmlParser(NodeParser): """Xml parser for dataclasses with hooks to events. The event hooks allow custom parsers to inject custom logic between the start/end element events. Args: config: The parser config instance context: The xml context instance handler: The xml handler class Attributes: ns_map: The parsed namespace prefix-URI map hooks_cache: The hooks cache is used to avoid inspecting the class for custom methods on duplicate events. """ handler: type[XmlHandler] = field(default=default_handler()) hooks_cache: dict = field(init=False, default_factory=dict) def start( self, clazz: type | None, queue: list[XmlNode], objects: list[Parsed], qname: str, attrs: dict, ns_map: dict, ) -> None: """Build and queue the XmlNode for the starting element. Override to emit the start element event. Args: clazz: The target class type, auto locate if omitted queue: The XmlNode queue list objects: The list of all intermediate parsed objects qname: The element qualified name attrs: The element attributes ns_map: The element namespace prefix-URI map """ super().start(clazz, queue, objects, qname, attrs, ns_map) self.emit_event(EventType.START, qname, attrs=attrs) def end( self, queue: list[XmlNode], objects: list[Parsed], qname: str, text: str | None, tail: str | None, ) -> bool: """Parse the last xml node and bind any intermediate objects. Override to emit the end element event if the binding process is successful. Args: queue: The XmlNode queue list objects: The list of all intermediate parsed objects qname: The element qualified name text: The element text content tail: The element tail content Returns: Whether the binding process was successful. """ result = super().end(queue, objects, qname, text, tail) if result: self.emit_event(EventType.END, qname, obj=objects[-1][1]) return result def emit_event(self, event: str, name: str, **kwargs: Any) -> None: """Propagate event to subclasses. Match event and name to a subclass method and trigger it with any input keyword arguments. Example:: event=start, name={urn}bookTitle -> start_booking_title(**kwargs) Args: event: The event type start|end name: The qualified name of the element kwargs: Additional keyword arguments passed to the hooks """ key = (event, name) if key not in self.hooks_cache: method_name = f"{event}_{snake_case(local_name(name))}" self.hooks_cache[key] = getattr(self, method_name, None) method = self.hooks_cache[key] if method: method(**kwargs) tefra-xsdata-cf7afe8/xsdata/formats/dataclass/ruff.toml000066400000000000000000000003221526437326600234320ustar00rootroot00000000000000target-version = "py310" [format] docstring-code-format = true line-ending = "lf" quote-style = "double" indent-style = "space" [lint] select = ["I", "UP", "F401"] future-annotations = true fixable = ["ALL"] tefra-xsdata-cf7afe8/xsdata/formats/dataclass/serializers/000077500000000000000000000000001526437326600241325ustar00rootroot00000000000000tefra-xsdata-cf7afe8/xsdata/formats/dataclass/serializers/__init__.py000066400000000000000000000011211526437326600262360ustar00rootroot00000000000000from contextlib import suppress from xsdata.formats.dataclass.serializers.code import PycodeSerializer from xsdata.formats.dataclass.serializers.dict import DictEncoder, DictFactory from xsdata.formats.dataclass.serializers.json import JsonSerializer from xsdata.formats.dataclass.serializers.xml import XmlSerializer __all__ = [ "DictEncoder", "DictFactory", "JsonSerializer", "PycodeSerializer", "XmlSerializer", ] with suppress(ImportError): from xsdata.formats.dataclass.serializers.tree import TreeSerializer # noqa: F401 __all__.append("TreeSerializer") tefra-xsdata-cf7afe8/xsdata/formats/dataclass/serializers/code.py000066400000000000000000000132171526437326600254220ustar00rootroot00000000000000from collections.abc import Iterator, Mapping from dataclasses import dataclass, field from enum import Enum from io import StringIO from typing import Any, TextIO from xsdata.formats.dataclass.context import XmlContext from xsdata.utils import collections from xsdata.utils.objects import literal_value spaces = " " unset = object() @dataclass class PycodeSerializer: """Pycode serializer for data class instances. Generate python pretty representation code from a model instance. Args: context: The models context instance """ context: XmlContext = field(default_factory=XmlContext) def render(self, obj: object, var_name: str = "obj") -> str: """Serialize the input model instance to python representation string. Args: obj: The input model instance to serialize var_name: The var name to assign the model instance Returns: The serialized representation string. """ output = StringIO() self.write(output, obj, var_name) return output.getvalue() def write(self, out: TextIO, obj: Any, var_name: str) -> None: """Write the given object to the output text stream. Args: out: The output text stream obj: The input model instance to serialize var_name: The var name to assign the model instance """ types: set[type] = set() tmp = StringIO() for chunk in self.repr_object(obj, 0, types): tmp.write(chunk) imports = self.build_imports(types) out.write(imports) out.write("\n\n") out.write(f"{var_name} = ") out.write(tmp.getvalue()) out.write("\n") @classmethod def build_imports(cls, types: set[type]) -> str: """Build a list of imports from the given types. Args: types: A set of types Returns: The `from x import y` statements as string. """ imports = set() for tp in types: module = tp.__module__ name = tp.__qualname__ if module != "builtins": if "." in name: name = name.split(".")[0] imports.add(f"from {module} import {name}\n") return "".join(sorted(imports)) def repr_object(self, obj: Any, level: int, types: set[type]) -> Iterator[str]: """Write the given object as repr code. Args: obj: The input object to serialize level: The current object level types: The parent object types Yields: An iterator of the representation strings. """ types.add(type(obj)) if collections.is_array(obj): yield from self.repr_array(obj, level, types) elif isinstance(obj, dict): yield from self.repr_mapping(obj, level, types) elif self.context.class_type.is_model(obj): yield from self.repr_model(obj, level, types) elif isinstance(obj, Enum): yield str(obj) else: yield literal_value(obj) def repr_array( self, obj: list | set | tuple, level: int, types: set[type], ) -> Iterator[str]: """Convert an iterable object to repr code. Args: obj: A list, set, tuple instance level: The current object level types: The parent object types Yields: An iterator of the representation strings. """ if not obj: yield str(obj) return next_level = level + 1 yield "[\n" for val in obj: yield spaces * next_level yield from self.repr_object(val, next_level, types) yield ",\n" yield f"{spaces * level}]" def repr_mapping(self, obj: Mapping, level: int, types: set[type]) -> Iterator[str]: """Convert a map object to repr code. Args: obj: A map instance level: The current object level types: The parent object types Yields: An iterator of the representation strings. """ if not obj: yield str(obj) return next_level = level + 1 yield "{\n" for key, value in obj.items(): yield spaces * next_level yield from self.repr_object(key, next_level, types) yield ": " yield from self.repr_object(value, next_level, types) yield ",\n" yield f"{spaces * level}}}" def repr_model(self, obj: Any, level: int, types: set[type]) -> Iterator[str]: """Convert a data model instance to repr code. Args: obj: A map instance level: The current object level types: The parent object types Yields: An iterator of the representation strings. """ yield f"{obj.__class__.__qualname__}(\n" next_level = level + 1 index = 0 for f in self.context.class_type.get_fields(obj): if not f.init: continue value = getattr(obj, f.name, types) default = self.context.class_type.default_value(f, default=unset) if default is not unset and ( (callable(default) and default() == value) or default == value ): continue if index: yield f",\n{spaces * next_level}{f.name}=" else: yield f"{spaces * next_level}{f.name}=" yield from self.repr_object(value, next_level, types) index += 1 yield f"\n{spaces * level})" tefra-xsdata-cf7afe8/xsdata/formats/dataclass/serializers/config.py000066400000000000000000000041751526437326600257600ustar00rootroot00000000000000import warnings from collections.abc import Callable from dataclasses import InitVar, dataclass from typing import Any @dataclass class SerializerConfig: """Serializer configuration options. Not all options are applicable for both xml and json documents. Args: encoding: Text encoding xml_version: XML Version number (1.0|1.1) xml_declaration: Generate XML declaration indent: Indent output by the given string ignore_default_attributes: Ignore optional attributes with default values schema_location: xsi:schemaLocation attribute value no_namespace_schema_location: xsi:noNamespaceSchemaLocation attribute value globalns: Dictionary containing global variables to extend or overwrite for typing """ encoding: str = "UTF-8" xml_version: str = "1.0" xml_declaration: bool = True indent: str | None = None ignore_default_attributes: bool = False schema_location: str | None = None no_namespace_schema_location: str | None = None globalns: dict[str, Callable] | None = None # Deprecated pretty_print: InitVar[bool] = False pretty_print_indent: InitVar[str | None] = None def __post_init__(self, pretty_print: bool, pretty_print_indent: str | None): """Handle deprecated pretty print/indent behaviour.""" if pretty_print: self.__setattr__("pretty_print", pretty_print) if pretty_print_indent: self.__setattr__("pretty_print_indent", pretty_print_indent) def __setattr__(self, key: str, value: Any): """Handle deprecated pretty print/indent behaviour.""" if key == "pretty_print": warnings.warn( "Setting `pretty_print` is deprecated, use `indent` instead", DeprecationWarning, ) self.indent = " " elif key == "pretty_print_indent": warnings.warn( "Setting `pretty_print_indent` is deprecated, use `indent` instead", DeprecationWarning, ) self.indent = value else: super().__setattr__(key, value) tefra-xsdata-cf7afe8/xsdata/formats/dataclass/serializers/dict.py000066400000000000000000000062461526437326600254370ustar00rootroot00000000000000from collections.abc import Callable, Iterator from dataclasses import dataclass, field from enum import Enum from typing import Any from xsdata.formats.converter import converter from xsdata.formats.dataclass.context import XmlContext from xsdata.formats.dataclass.models.elements import XmlVar from xsdata.formats.dataclass.serializers.config import SerializerConfig from xsdata.utils import collections def filter_none(x: tuple) -> dict: """Convert a key-value pairs to dict, ignoring None values. Args: x: Key-value pairs Returns: The filtered dictionary. """ return {k: v for k, v in x if v is not None} class DictFactory: """Dictionary factory types.""" FILTER_NONE = filter_none @dataclass class DictEncoder: """Json serializer for data classes. Args: config: The serializer config instance context: The models context instance dict_factory: Dictionary factory """ config: SerializerConfig = field(default_factory=SerializerConfig) context: XmlContext = field(default_factory=XmlContext) dict_factory: Callable = field(default=dict) def encode( self, value: Any, var: XmlVar | None = None, wrapped: bool = False, ) -> Any: """Convert a value to a dictionary object. Args: value: The input value var: The xml var instance wrapped: Whether this is a wrapped value Returns: The converted json serializable value. """ if value is None: return None if var is None: if collections.is_array(value): return list(map(self.encode, value)) return self.dict_factory(self.next_value(value)) if var and var.wrapper and not wrapped: return self.dict_factory(((var.local_name, self.encode(value, var, True)),)) if self.context.class_type.is_model(value): return self.dict_factory(self.next_value(value)) if collections.is_array(value): return type(value)(self.encode(val, var, wrapped) for val in value) if isinstance(value, (dict, int, float, str, bool)): return value if isinstance(value, Enum): return self.encode(value.value, var, wrapped) return converter.serialize(value, format=var.format) def next_value(self, obj: Any) -> Iterator[tuple[str, Any]]: """Fetch the next value of a model instance to convert. Args: obj: The input model instance Yields: An iterator of field name and value tuples. """ ignore_optionals = self.config.ignore_default_attributes meta = self.context.build(obj.__class__, globalns=self.config.globalns) for var in meta.get_all_vars(): value = getattr(obj, var.name) if ( not var.is_attribute or not ignore_optionals or not var.is_optional(value) ): if var.wrapper: yield var.wrapper, self.encode(value, var) else: yield var.local_name, self.encode(value, var) tefra-xsdata-cf7afe8/xsdata/formats/dataclass/serializers/json.py000066400000000000000000000022631526437326600254600ustar00rootroot00000000000000import json from collections.abc import Callable from dataclasses import dataclass, field from io import StringIO from typing import Any, TextIO from xsdata.formats.dataclass.serializers import DictEncoder @dataclass class JsonSerializer(DictEncoder): """Json serializer for data classes. Args: config: The serializer config instance context: The models context instance dict_factory: Dictionary factory dump_factory: Json dump factory e.g. json.dump """ dump_factory: Callable = field(default=json.dump) def render(self, obj: Any) -> str: """Serialize the input model instance to json string. Args: obj: The input model instance Returns: The serialized json string output. """ output = StringIO() self.write(output, obj) return output.getvalue() def write(self, out: TextIO, obj: Any) -> None: """Serialize the given object to the output text stream. Args: out: The output text stream obj: The input model instance to serialize """ self.dump_factory(self.encode(obj), out, indent=self.config.indent) tefra-xsdata-cf7afe8/xsdata/formats/dataclass/serializers/mixins.py000066400000000000000000001025021526437326600260130ustar00rootroot00000000000000import abc from abc import ABC from collections.abc import Iterable, Iterator from dataclasses import dataclass, field from enum import Enum from io import TextIOBase from typing import ( Any, Final, Literal, TypeAlias, ) from xml.etree.ElementTree import QName from xml.sax.handler import ContentHandler from xsdata.exceptions import SerializerError, XmlWriterError from xsdata.formats.converter import converter from xsdata.formats.dataclass.context import XmlContext from xsdata.formats.dataclass.models.elements import XmlMeta, XmlVar from xsdata.formats.dataclass.serializers.config import SerializerConfig from xsdata.models.enums import DataType, Namespace, QNames from xsdata.utils import collections, namespaces from xsdata.utils.constants import EMPTY_MAP from xsdata.utils.namespaces import generate_prefix, prefix_exists, split_qname XSI_NIL = (Namespace.XSI.uri, "nil") class XmlWriterEvent: """Event names.""" START: Final = "start" ATTR: Final = "attr" DATA: Final = "data" END: Final = "end" StartEvent: TypeAlias = tuple[Literal["start"], str] AttrEvent: TypeAlias = tuple[Literal["attr"], str, Any] DataEvent: TypeAlias = tuple[Literal["data"], str] EndEvent: TypeAlias = tuple[Literal["end"], str] EventIterator = Iterator[StartEvent | AttrEvent | DataEvent | EndEvent] class EventHandler(abc.ABC): """A consistency wrapper for sax events. Args: config: The serializer config instance ns_map: A user defined namespace prefix-URI map Attributes: in_tail: Specifies whether the text content has been written tail: The current element tail content attrs: The current element attributes ns_context: The namespace context queue pending_tag: The pending element namespace, name tuple pending_prefixes: The pending element namespace prefixes """ __slots__ = ( "attrs", "config", # Instance attributes "in_tail", "ns_context", "ns_map", "pending_prefixes", "pending_tag", "tail", ) def __init__(self, config: SerializerConfig, ns_map: dict): """Initialize the event handler.""" self.config = config self.ns_map = ns_map self.in_tail = False self.tail: str | None = None self.attrs: dict = {} self.ns_context: list[dict] = [] self.pending_tag: tuple | None = None self.pending_prefixes: list[list] = [] def write(self, events: EventIterator) -> None: """Feed the sax content handler with events. The receiver will also add additional root attributes like xsi or no namespace location. Args: events: An iterator of sax events Raises: XmlWriterError: On unknown events. """ self.start_document() if self.config.schema_location: self.add_attribute( QNames.XSI_SCHEMA_LOCATION, self.config.schema_location, root=True, ) if self.config.no_namespace_schema_location: self.add_attribute( QNames.XSI_NO_NAMESPACE_SCHEMA_LOCATION, self.config.no_namespace_schema_location, root=True, ) for name, *args in events: if name == XmlWriterEvent.START: self.start_tag(*args) elif name == XmlWriterEvent.END: self.end_tag(*args) elif name == XmlWriterEvent.ATTR: self.add_attribute(*args) elif name == XmlWriterEvent.DATA: self.set_data(*args) else: raise XmlWriterError(f"Unhandled event: `{name}`") self.end_document() def start_tag(self, qname: str) -> None: """Start tag notification receiver. The receiver will flush the start of any pending element, create new namespaces context and queue the current tag for generation. Args: qname: The qualified name of the starting element """ self.flush_start(False) self.ns_context.append(self.ns_map.copy()) self.ns_map = self.ns_context[-1] self.pending_tag = split_qname(qname) self.add_namespace(self.pending_tag[0]) def add_attribute(self, qname: str, value: Any, root: bool = False) -> None: """Add attribute notification receiver. The receiver will convert the key to a namespace, name tuple and convert the value to string. Internally the converter will also generate any missing namespace prefixes. Args: qname: The qualified name of the attribute value: The value of the attribute root: Specifies if attribute is for the root element Raises: XmlWriterError: If it's not a root element attribute and not no element is pending to start. """ if not self.pending_tag and not root: raise XmlWriterError("Empty pending tag.") if self.is_xsi_type(qname, value): value = QName(value) name_tuple = split_qname(qname) self.attrs[name_tuple] = self.encode_data(value) def add_namespace(self, uri: str | None) -> None: """Add the given uri to the current namespace context. If the uri empty or a prefix already exists, skip silently. Args: uri: The namespace URI """ if uri and not prefix_exists(uri, self.ns_map): generate_prefix(uri, self.ns_map) def set_data(self, data: Any) -> None: """Set data notification receiver. The receiver will convert the data to string, flush any previous pending start element and send it to the handler for generation. If the text content of the tag has already been generated then treat the current data as element tail content and queue it to be generated when the tag ends. Args: data: The element text or tail content """ value = self.encode_data(data) self.flush_start(is_nil=value is None) if value: if not self.in_tail: self.set_characters(value) else: self.tail = value self.in_tail = True def end_tag(self, qname: str) -> None: """End tag notification receiver. The receiver will flush if pending the start of the element, end the element, its tail content and its namespaces prefix mapping and current context. Args: qname: The qualified name of the element """ self.flush_start(True) self.end_element(split_qname(qname), qname) if self.tail: self.set_characters(self.tail) self.tail = None self.in_tail = False self.ns_context.pop() if self.ns_context: self.ns_map = self.ns_context[-1] for prefix in self.pending_prefixes.pop(): self.end_prefix_mapping(prefix) def flush_start(self, is_nil: bool = True) -> None: """Flush start notification receiver. The receiver will pop the xsi:nil attribute if the element is not empty, prepare and send the namespace prefix-URI map and the element with its attributes to the content handler for generation. Args: is_nil: Specify if the element requires `xsi:nil="true"` when content is empty """ if not self.pending_tag: return if not is_nil: self.attrs.pop(XSI_NIL, None) for name in self.attrs: self.add_namespace(name[0]) self.reset_default_namespace() self.start_namespaces() self.start_element(self.pending_tag, "", self.attrs) self.attrs = {} self.in_tail = False self.pending_tag = None def start_namespaces(self) -> None: """Send the current namespace prefix-URI map to the content handler. Save the list of prefixes to be removed at the end of the current pending tag. """ prefixes: list[str] = [] self.pending_prefixes.append(prefixes) try: parent_ns_map = self.ns_context[-2] except IndexError: parent_ns_map = EMPTY_MAP for prefix, uri in self.ns_map.items(): if parent_ns_map.get(prefix) != uri: prefixes.append(prefix) self.start_prefix_mapping(prefix, uri) def reset_default_namespace(self) -> None: """Reset the default namespace if the pending element is not qualified.""" if self.pending_tag and not self.pending_tag[0] and None in self.ns_map: self.ns_map[None] = "" @classmethod def is_xsi_type(cls, qname: str, value: Any) -> bool: """Return whether the value is a xsi:type. Args: qname: The attribute qualified name value: The attribute value Returns: The bool result. """ if isinstance(value, str) and value.startswith("{"): return qname == QNames.XSI_TYPE or DataType.from_qname(value) is not None return False def encode_data(self, data: Any) -> str | None: """Encode data for xml rendering. Args: data: The content to encode/serialize Returns: The xml encoded data """ if data is None or isinstance(data, str): return data if isinstance(data, list) and not data: return None return converter.serialize(data, ns_map=self.ns_map) @abc.abstractmethod def start_document(self) -> None: """Start document notification receiver.""" @abc.abstractmethod def end_document(self) -> None: """End document notification receiver.""" @abc.abstractmethod def start_element(self, name: tuple[str, str], qname: str, attrs: dict) -> None: """Start element notification receiver. Args: name: The qname as tuple qname: The qualified name attrs: The attributes mapping """ @abc.abstractmethod def end_element(self, name: tuple[str, str], qname: str) -> None: """End element notification receiver. Args: name: The qname as tuple qname: The qualified name """ @abc.abstractmethod def set_characters(self, data: str) -> None: """Characters notification receiver. Args: data: The characters data to write """ @abc.abstractmethod def start_prefix_mapping(self, prefix: str | None, uri: str) -> None: """Start namespace prefix notification receiver. Args: prefix: The namespace prefix uri: The namespace URI """ @abc.abstractmethod def end_prefix_mapping(self, prefix: str) -> None: """End namespace prefix notification receiver. Args: prefix: The namespace prefix """ class EventContentHandler(EventHandler): """A consistency wrapper for sax content handlers. Args: config: The serializer config instance ns_map: A user defined namespace prefix-URI map Attributes: handler: The content handler instance in_tail: Specifies whether the text content has been written tail: The current element tail content attrs: The current element attributes ns_context: The namespace context queue pending_tag: The pending element namespace, name tuple pending_prefixes: The pending element namespace prefixes """ def __init__(self, config: SerializerConfig, ns_map: dict): """Initialize the content handler.""" super().__init__(config, ns_map) self.handler = self.build_handler() @abc.abstractmethod def build_handler(self) -> ContentHandler: """Build the content handler instance. Returns: A content handler instance. """ def start_document(self) -> None: """Start document notification receiver. Write the xml version and encoding, if the configuration is enabled. """ self.handler.startDocument() def end_document(self) -> None: """End document entrypoint.""" self.handler.endDocument() def end_element(self, name: tuple[str, str], qname: str) -> None: """End element notification receiver. Args: name: The qname as tuple qname: The qualified name """ self.handler.endElementNS(name, qname) def set_characters(self, data: str) -> None: """Characters notification receiver. Args: data: The characters data to write """ self.handler.characters(data) def end_prefix_mapping(self, prefix: str) -> None: """End namespace prefix notification receiver. Args: prefix: The namespace prefix """ self.handler.endPrefixMapping(prefix) def start_element(self, name: tuple[str, str], qname: str, attrs: dict) -> None: """Start element notification receiver. Args: name: The qname as tuple qname: The qualified name attrs: The attributes mapping """ self.handler.startElementNS(name, qname, attrs) # type: ignore def start_prefix_mapping(self, prefix: str | None, uri: str) -> None: """Start namespace prefix notification receiver. Args: prefix: The namespace prefix uri: The namespace URI """ self.handler.startPrefixMapping(prefix, uri) class XmlWriter(EventContentHandler, ABC): """A consistency wrapper for sax content writers. Args: config: The serializer config instance output: The writer output stream ns_map: A user defined namespace prefix-URI map Attributes: handler: The content handler instance in_tail: Specifies whether the text content has been written tail: The current element tail content attrs: The current element attributes ns_context: The namespace context queue pending_tag: The pending element namespace, name tuple pending_prefixes: The pending element namespace prefixes """ def __init__(self, config: SerializerConfig, output: TextIOBase, ns_map: dict): """Initialize the writer.""" self.output = output super().__init__(config, ns_map) def start_document(self) -> None: """Start document notification receiver. Write the xml version and encoding, if the configuration is enabled. """ if self.config.xml_declaration: self.output.write(f'\n') @dataclass class EventGenerator: """Event generator class.""" context: XmlContext = field(default_factory=XmlContext) config: SerializerConfig = field(default_factory=SerializerConfig) def generate(self, obj: Any) -> EventIterator: """Convert a user model, or derived element instance to sax events. Args: obj: A user model, or derived element instance Yields: An iterator of sax events. """ qname = xsi_type = None if isinstance(obj, self.context.class_type.derived_element): meta = self.context.build( obj.value.__class__, globalns=self.config.globalns ) qname = obj.qname obj = obj.value xsi_type = self.real_xsi_type(qname, meta.target_qname) yield from self.convert_dataclass(obj, qname=qname, xsi_type=xsi_type) def convert_dataclass( self, obj: Any, namespace: str | None = None, qname: str | None = None, nillable: bool = False, xsi_type: str | None = None, ) -> EventIterator: """Convert a model instance to sax events. Optionally override the qualified name and the xsi attributes type and nil. Args: obj: A model instance namespace: The field namespace URI qname: Override the field qualified name nillable: Specifies whether the field is nillable xsi_type: Override the field xsi type Yields: An iterator of sax events. """ meta = self.context.build( obj.__class__, namespace, globalns=self.config.globalns, ) qname = qname or meta.qname # A type without text or child elements has no content to be nil, # so a present instance of it in a nillable field is not nil nillable = meta.nillable or (nillable and bool(meta.get_element_vars())) namespace, _tag = namespaces.split_qname(qname) yield XmlWriterEvent.START, qname for key, value in self.next_attribute( obj, meta, nillable, xsi_type, self.config.ignore_default_attributes ): yield XmlWriterEvent.ATTR, key, value for var, value in self.next_value(obj, meta): if var.wrapper_qname: yield XmlWriterEvent.START, var.wrapper_qname yield from self.convert_value(value, var, namespace) if var.wrapper_qname: yield XmlWriterEvent.END, var.wrapper_qname yield XmlWriterEvent.END, qname def convert_xsi_type( self, value: Any, var: XmlVar, namespace: str | None, ) -> EventIterator: """Convert a xsi:type value to sax events. The value can be assigned to wildcard, element or compound fields Args: value: A model instance var: The field metadata instance namespace: The field namespace URI Yields: An iterator of sax events. """ if var.is_wildcard: choice = var.find_value_choice(value, True) if choice: yield from self.convert_value(value, choice, namespace) else: yield from self.convert_dataclass(value, namespace) elif var.is_element: xsi_type = self.xsi_type(var, value, namespace) yield from self.convert_dataclass( value, namespace, var.qname, var.nillable, xsi_type, ) else: # var elements/compound meta = self.context.fetch(value.__class__, namespace) yield from self.convert_dataclass(value, qname=meta.target_qname) def convert_value( self, value: Any, var: XmlVar, namespace: str | None ) -> EventIterator: """Convert any value to sax events according to the var instance. The order of the checks is important as more than one condition can be true. Args: value: The input value var: The field metadata instance namespace: The class namespace URI Yields: An iterator of sax events. """ if var.mixed: yield from self.convert_mixed_content(value, var, namespace) elif var.is_text: yield from self.convert_data(value, var) elif var.tokens: yield from self.convert_tokens(value, var, namespace) elif var.is_elements: yield from self.convert_elements(value, var, namespace) elif var.list_element and collections.is_array(value): yield from self.convert_list(value, var, namespace) else: yield from self.convert_any_type(value, var, namespace) def convert_list( self, values: Iterable, var: XmlVar, namespace: str | None, ) -> EventIterator: """Convert an array of values to sax events. Args: values: A list, set, tuple instance var: The field metadata instance namespace: The class namespace Yields: An iterator of sax events. """ for value in values: yield from self.convert_value(value, var, namespace) def convert_tokens( self, value: Any, var: XmlVar, namespace: str | None ) -> EventIterator: """Convert an array of token values to sax events. Args: value: A list, set, tuple instance var: The field metadata instance namespace: The class namespace Yields: An iterator of sax events. """ if value or var.nillable: if value and collections.is_array(value[0]): for val in value: yield from self.convert_element(val, var, namespace) else: yield from self.convert_element(value, var, namespace) def convert_mixed_content( self, values: list, var: XmlVar, namespace: str | None, ) -> EventIterator: """Convert mixed content values to sax events. Args: values: A list instance of mixed type values var: The field metadata instance namespace: The class namespace Yields: An iterator of sax events. """ for value in values: yield from self.convert_any_type(value, var, namespace) def convert_any_type( self, value: Any, var: XmlVar, namespace: str | None ) -> EventIterator: """Convert a value assigned to a xs:anyType field to sax events. Args: value: A list instance of mixed type values var: The field metadata instance namespace: The class namespace Yields: An iterator of sax events. """ if isinstance(value, self.context.class_type.any_element): yield from self.convert_any_element(value, var, namespace) elif isinstance(value, self.context.class_type.derived_element): yield from self.convert_derived_element(value, namespace) elif self.context.class_type.is_model(value): yield from self.convert_xsi_type(value, var, namespace) elif var.is_element: yield from self.convert_element(value, var, namespace) else: yield from self.convert_data(value, var) def convert_derived_element( self, value: Any, namespace: str | None ) -> EventIterator: """Convert a derived element instance to sax events. Args: value: A list instance of mixed type values namespace: The class namespace Yields: An iterator of sax events. """ if self.context.class_type.is_model(value.value): meta = self.context.fetch(value.value.__class__) qname = value.qname xsi_type = self.real_xsi_type(qname, meta.target_qname) yield from self.convert_dataclass( value.value, namespace, qname=qname, xsi_type=xsi_type ) else: datatype = DataType.from_value(value.value) yield XmlWriterEvent.START, value.qname yield XmlWriterEvent.ATTR, QNames.XSI_TYPE, QName(str(datatype)) yield XmlWriterEvent.DATA, value.value yield XmlWriterEvent.END, value.qname def convert_any_element( self, value: Any, var: XmlVar, namespace: str | None ) -> EventIterator: """Convert a generic any element instance to sax events. Args: value: A list instance of mixed type values var: The field metadata instance namespace: The class namespace Yields: An iterator of sax events. """ if value.qname: namespace, _tag = namespaces.split_qname(value.qname) yield XmlWriterEvent.START, value.qname for key, val in value.attributes.items(): yield XmlWriterEvent.ATTR, key, val yield XmlWriterEvent.DATA, value.text for child in value.children: yield from self.convert_any_type(child, var, namespace) if value.qname: yield XmlWriterEvent.END, value.qname if value.tail: yield XmlWriterEvent.DATA, value.tail def xsi_type(self, var: XmlVar, value: Any, namespace: str | None) -> str | None: """Return the xsi:type for the given value and field metadata instance. If the value type is either a child or parent for one of the var types, we need to declare it as n xsi:type. Args: value: A list instance of mixed type values var: The field metadata instance namespace: The class namespace Raises: SerializerError: If the value type is completely unrelated to the field types. """ if not value or value.__class__ in var.types: return None clazz = var.clazz if clazz is None or self.context.is_derived(value, clazz): meta = self.context.fetch(value.__class__, namespace) return self.real_xsi_type(var.qname, meta.target_qname) raise SerializerError( f"{value.__class__.__name__} is not derived from {clazz.__name__}" ) def convert_elements( self, value: Any, var: XmlVar, namespace: str | None ) -> EventIterator: """Convert the value assigned to a compound field to sax events. Args: value: A list instance of mixed type values var: The field metadata instance namespace: The class namespace Yields: An iterator of sax events. """ if collections.is_array(value): for val in value: yield from self.convert_choice(val, var, namespace) else: yield from self.convert_choice(value, var, namespace) def convert_choice( self, value: Any, var: XmlVar, namespace: str | None ) -> EventIterator: """Convert a single value assigned to a compound field to sax events. Args: value: A list instance of mixed type values var: The field metadata instance namespace: The class namespace Yields: An iterator of sax events. Raises: SerializerError: If the value doesn't match any choice field. """ if isinstance(value, self.context.class_type.derived_element): choice = var.find_choice(value.qname) value = value.value if self.context.class_type.is_model(value): func = self.convert_xsi_type else: func = self.convert_element elif isinstance(value, self.context.class_type.any_element) and value.qname: choice = var.find_choice(value.qname) func = self.convert_any_type else: check_subclass = self.context.class_type.is_model(value) choice = var.find_value_choice(value, check_subclass) func = self.convert_value if not choice and check_subclass: func = self.convert_xsi_type choice = var if not choice: raise SerializerError( f"XmlElements undefined choice: `{var.name}` for `{type(value)}`" ) yield from func(value, choice, namespace) def convert_element( self, value: Any, var: XmlVar, namespace: str | None, ) -> EventIterator: """Convert a value assigned to an element field to sax events. Args: value: A list instance of mixed type values var: The field metadata instance namespace: The class namespace (unused) Yields: An iterator of sax events. """ yield XmlWriterEvent.START, var.qname if var.nillable and not value: yield XmlWriterEvent.ATTR, QNames.XSI_NIL, "true" if value is not None and value != "" and var.any_type: datatype = DataType.from_value(value) if datatype != DataType.STRING: yield XmlWriterEvent.ATTR, QNames.XSI_TYPE, QName(str(datatype)) yield XmlWriterEvent.DATA, self.encode_primitive(value, var) yield XmlWriterEvent.END, var.qname @classmethod def convert_data(cls, value: Any, var: XmlVar) -> EventIterator: """Convert a value assigned to a text field to sax events. Args: value: A list instance of mixed type values var: The field metadata instance Yields: An iterator of sax events. """ yield XmlWriterEvent.DATA, cls.encode_primitive(value, var) @classmethod def next_value(cls, obj: Any, meta: XmlMeta) -> Iterator[tuple[XmlVar, Any]]: """Produce the next non attribute value of a model instance to convert. The generator will produce the values in the order the fields are defined in the model or by their sequence number. Sequential fields need to be rendered together in parallel order eg: Args: obj: The input model instance meta: The model metadata instance Yields: An iterator of field metadata instance and value tuples. """ index = 0 attrs = meta.get_element_vars() stop = len(attrs) while index < stop: var = attrs[index] if var.sequence is None: value = getattr(obj, var.name) if value is not None or var.nillable: yield var, value index += 1 continue indices = range(index, stop) end = next( i for i in indices[::-1] if attrs[i].sequence == var.sequence ) # pragma: no cover sequence = attrs[index : end + 1] index = end + 1 j = 0 rolling = True while rolling: rolling = False for var in sequence: values = getattr(obj, var.name) if collections.is_array(values): if j < len(values): rolling = True value = values[j] if value is not None or var.nillable: yield var, value elif j == 0: rolling = True if values is not None or var.nillable: yield var, values j += 1 @classmethod def next_attribute( cls, obj: Any, meta: XmlMeta, nillable: bool, xsi_type: str | None, ignore_optionals: bool, ) -> Iterator[tuple[str, Any]]: """Produce the next attribute value to convert. Args: obj: The input model instance meta: The model metadata instance nillable: Specifies if the current element supports nillable content xsi_type: The real xsi:type of the object ignore_optionals: Specifies if optional attributes with default values should be ignored. Yields: An iterator of attribute name-value pairs. """ for var in meta.get_attribute_vars(): if var.is_attribute: value = getattr(obj, var.name) if ( value is None or (collections.is_array(value) and not value) or (ignore_optionals and var.is_optional(value)) ): continue yield var.qname, cls.encode_primitive(value, var) else: yield from getattr(obj, var.name, EMPTY_MAP).items() if xsi_type: yield QNames.XSI_TYPE, QName(xsi_type) if nillable: yield QNames.XSI_NIL, "true" @classmethod def encode_primitive(cls, value: Any, var: XmlVar) -> Any: """Encode a value for xml serialization. Converts values to strings. QName instances is an exception, those values need to wait until the XmlWriter assigns prefixes to namespaces per element node. Enums and Tokens may contain QName(s) so they also get a special treatment. We can't do all the conversions in the writer because we would need to carry the xml vars inside the writer. Instead of that we do the easy encoding here and leave the qualified names for later. Args: value: The simple type vale to encode var: The field metadata instance Returns: The encoded value. """ if isinstance(value, (str, QName)) or var is None: return value if collections.is_array(value): return [cls.encode_primitive(v, var) for v in value] if isinstance(value, Enum): return cls.encode_primitive(value.value, var) return converter.serialize(value, format=var.format) @classmethod def real_xsi_type(cls, qname: str, target_qname: str | None) -> str | None: """Compare the qname with the target qname and return the real xsi:type. Args: qname: The field type qualified name target_qname: The value type qualified name Returns: None if the qname and target qname match, otherwise return the target qname. """ return target_qname if target_qname != qname else None tefra-xsdata-cf7afe8/xsdata/formats/dataclass/serializers/tree.py000066400000000000000000000020101526437326600254340ustar00rootroot00000000000000from dataclasses import dataclass from typing import Any from lxml.etree import ElementTree from xsdata.formats.dataclass.serializers.mixins import EventGenerator from xsdata.formats.dataclass.serializers.writers.lxml import LxmlTreeBuilder from xsdata.utils import namespaces @dataclass class TreeSerializer(EventGenerator): """Lxml tree serializer for data classes. Args: config: The serializer config instance context: The models context instance """ def render(self, obj: Any, ns_map: dict | None = None) -> ElementTree: """Serialize the input model instance to a lxml etree instance. Args: obj: The input model instance to serialize ns_map: A user defined namespace prefix-URI map Returns: The element tree instance. """ builder = LxmlTreeBuilder( config=self.config, ns_map=namespaces.clean_prefixes(ns_map) if ns_map else {}, ) return builder.build(self.generate(obj)) tefra-xsdata-cf7afe8/xsdata/formats/dataclass/serializers/writers/000077500000000000000000000000001526437326600256315ustar00rootroot00000000000000tefra-xsdata-cf7afe8/xsdata/formats/dataclass/serializers/writers/__init__.py000066400000000000000000000007241526437326600277450ustar00rootroot00000000000000from xsdata.formats.dataclass.serializers.mixins import XmlWriter from xsdata.formats.dataclass.serializers.writers.native import ( XmlEventWriter, ) try: from xsdata.formats.dataclass.serializers.writers.lxml import LxmlEventWriter DEFAULT_XML_WRITER: type[XmlWriter] = LxmlEventWriter except ImportError: # pragma: no cover DEFAULT_XML_WRITER = XmlEventWriter __all__ = [ "DEFAULT_XML_WRITER", "LxmlEventWriter", "XmlEventWriter", ] tefra-xsdata-cf7afe8/xsdata/formats/dataclass/serializers/writers/lxml.py000066400000000000000000000056551526437326600271720ustar00rootroot00000000000000from collections.abc import Iterator from lxml import etree from lxml.sax import ElementTreeContentHandler from xsdata.formats.dataclass.serializers.mixins import EventContentHandler, XmlWriter class LxmlEventWriter(XmlWriter): """Xml event writer based on `lxml.sax.ElementTreeContentHandler`. The writer converts the events to a lxml tree which is then converted to string. Args: config: The serializer config instance output: The output stream to write the result ns_map: A user defined namespace prefix-URI map Attributes: handler: The content handler instance in_tail: Specifies whether the text content has been written tail: The current element tail content attrs: The current element attributes ns_context: The namespace context queue pending_tag: The pending element namespace, name tuple pending_prefixes: The pending element namespace prefixes """ def build_handler(self) -> ElementTreeContentHandler: """Build the content handler instance. Returns: An element tree content handler instance. """ return ElementTreeContentHandler() def write(self, events: Iterator) -> None: """Feed the sax content handler with events. The receiver will also add additional root attributes like xsi or no namespace location. In the end convert the handler etree to string based on the configuration. Args: events: An iterator of sax events Raises: XmlWriterError: On unknown events. """ super().write(events) assert isinstance(self.handler, ElementTreeContentHandler) if self.config.indent: etree.indent(self.handler.etree, self.config.indent) xml = etree.tostring( self.handler.etree, encoding=self.config.encoding, xml_declaration=False, ).decode(self.config.encoding) self.output.write(xml) if self.config.indent: self.output.write("\n") class LxmlTreeBuilder(EventContentHandler): """Tree builder based on `lxml.sax.ElementTreeContentHandler`.""" def build_handler(self) -> ElementTreeContentHandler: """Build the content handler instance. Returns: An element tree content handler instance. """ return ElementTreeContentHandler() def build(self, events: Iterator) -> etree.ElementTree: """Feed the sax content handler with events. Args: events: An iterator of sax events Raises: XmlWriterError: On unknown events. Returns: The element tree instance. """ self.write(events) assert isinstance(self.handler, ElementTreeContentHandler) if self.config.indent: etree.indent(self.handler.etree, self.config.indent) return self.handler.etree tefra-xsdata-cf7afe8/xsdata/formats/dataclass/serializers/writers/native.py000066400000000000000000000063631526437326600275010ustar00rootroot00000000000000from io import TextIOBase from xml.sax.saxutils import XMLGenerator from xsdata.formats.dataclass.serializers.config import SerializerConfig from xsdata.formats.dataclass.serializers.mixins import XmlWriter class XmlEventWriter(XmlWriter): """Xml event writer based on `xml.sax.saxutils.XMLGenerator`. The writer converts sax events directly to xml output without storing any intermediate results in memory. Args: config: The serializer config instance output: The output stream to write the result ns_map: A user defined namespace prefix-URI map Attributes: handler: The content handler instance in_tail: Specifies whether the text content has been written tail: The current element tail content attrs: The current element attributes ns_context: The namespace context queue pending_tag: The pending element namespace, name tuple pending_prefixes: The pending element namespace prefixes """ __slots__ = ("current_level", "pending_end_element") def __init__(self, config: SerializerConfig, output: TextIOBase, ns_map: dict): """Initialize the writer.""" super().__init__(config, output, ns_map) self.current_level = 0 self.pending_end_element = False def build_handler(self) -> XMLGenerator: """Build the content handler instance. Returns: A xml generator content handler instance. """ return XMLGenerator( out=self.output, encoding=self.config.encoding, short_empty_elements=True, ) def start_tag(self, qname: str) -> None: """Start tag notification receiver. The receiver will flush the start of any pending element, create new namespaces context and queue the current tag for generation. The receiver will also write the necessary whitespace if pretty print is enabled. Args: qname: The qualified name of the starting element """ super().start_tag(qname) if self.config.indent: if self.current_level: self.handler.ignorableWhitespace("\n") self.handler.ignorableWhitespace( self.config.indent * self.current_level ) self.current_level += 1 self.pending_end_element = False def end_tag(self, qname: str) -> None: """End tag notification receiver. The receiver will flush if pending the start of the element, end the element, its tail content and its namespaces prefix mapping and current context. The receiver will also write the necessary whitespace if pretty print is enabled. Args: qname: The qualified name of the element """ if not self.config.indent: super().end_tag(qname) return self.current_level -= 1 if self.pending_end_element: self.handler.ignorableWhitespace("\n") self.handler.ignorableWhitespace(self.config.indent * self.current_level) super().end_tag(qname) self.pending_end_element = True if not self.current_level: self.handler.ignorableWhitespace("\n") tefra-xsdata-cf7afe8/xsdata/formats/dataclass/serializers/xml.py000066400000000000000000000031161526437326600253050ustar00rootroot00000000000000from dataclasses import dataclass, field from io import StringIO, TextIOBase from typing import Any from xsdata.formats.dataclass.serializers.mixins import ( EventGenerator, XmlWriter, ) from xsdata.formats.dataclass.serializers.writers import DEFAULT_XML_WRITER from xsdata.utils import namespaces @dataclass class XmlSerializer(EventGenerator): """Xml serializer for data classes. Args: config: The serializer config instance context: The models context instance writer: The xml writer class """ writer: type[XmlWriter] = field(default=DEFAULT_XML_WRITER) def render(self, obj: Any, ns_map: dict | None = None) -> str: """Serialize the input model instance to xml string. Args: obj: The input model instance to serialize ns_map: A user defined namespace prefix-URI map Returns: The serialized xml string output. """ output = StringIO() self.write(output, obj, ns_map) return output.getvalue() def write(self, out: TextIOBase, obj: Any, ns_map: dict | None = None) -> None: """Serialize the given object to the output text stream. Args: out: The output text stream obj: The input model instance to serialize ns_map: A user defined namespace prefix-URI map """ events = self.generate(obj) handler = self.writer( config=self.config, output=out, ns_map=namespaces.clean_prefixes(ns_map) if ns_map else {}, ) handler.write(events) tefra-xsdata-cf7afe8/xsdata/formats/dataclass/templates/000077500000000000000000000000001526437326600235745ustar00rootroot00000000000000tefra-xsdata-cf7afe8/xsdata/formats/dataclass/templates/class.jinja2000066400000000000000000000044561526437326600260110ustar00rootroot00000000000000{% set level = level|default(0) -%} {% set help | format_docstring(level + 1) %} {%- include "docstrings." + docstring_name + ".jinja2" -%} {% endset -%} {% set parent_namespace = obj.namespace if obj.namespace is not none else parent_namespace|default(None) -%} {% set class_name = obj.name|class_name -%} {% set class_annotations = obj | class_annotations(class_name) -%} {% set global_type = level == 0 and not obj.local_type -%} {% set local_name = obj.meta_name or obj.name -%} {% set local_name = None if class_name == local_name or not global_type else local_name -%} {% set base_classes = obj | class_bases(class_name) | join(', ')-%} {% set post_meta_output = obj | post_meta_hook -%} {% set target_namespace = obj.target_namespace if global_type and module_namespace != obj.target_namespace else None %} {{ class_annotations | join('\n') }} class {{ class_name }}{{"({})".format(base_classes) if base_classes }}: {%- if help %} {{ help|indent(4, first=True) }} {%- endif -%} {%- if local_name or obj.is_nillable or obj.namespace is not none or target_namespace or (obj.local_type and level == 0) %} class Meta: {%- if obj.local_type %} global_type = False {%- endif -%} {%- if local_name %} name = "{{ local_name }}" {%- endif -%} {%- if obj.is_nillable %} nillable = True {%- endif -%} {%- if obj.namespace is not none %} namespace = "{{ obj.namespace }}" {%- endif %} {%- if target_namespace and target_namespace != obj.namespace %} target_namespace = "{{ target_namespace }}" {%- endif %} {% elif obj.attrs|length == 0 and not help and not extra_output %} pass {%- endif -%} {%- if post_meta_output %} {{ post_meta_output|indent(4, first=True) }} {%- endif -%} {%- for attr in obj.attrs %} {%- set field_typing = obj|field_type(attr) %} {%- set field_definition = obj|field_definition(attr, parent_namespace) %} {{ attr.name|field_name(obj.name) }}: {{ field_typing }} = {{ field_definition }} {%- endfor -%} {%- for inner in obj.inner %} {%- set tpl = "enum.jinja2" if inner.is_enumeration else "class.jinja2" -%} {%- filter indent(4) -%} {%- with obj=inner, level=(level + 1) -%} {% include tpl %} {%- endwith -%} {%- endfilter -%} {%- endfor -%} tefra-xsdata-cf7afe8/xsdata/formats/dataclass/templates/docstrings.accessible.jinja2000066400000000000000000000000641526437326600311460ustar00rootroot00000000000000{{ '"""{}"""'.format(obj.help | clean_docstring) }} tefra-xsdata-cf7afe8/xsdata/formats/dataclass/templates/docstrings.blank.jinja2000066400000000000000000000000001526437326600301260ustar00rootroot00000000000000tefra-xsdata-cf7afe8/xsdata/formats/dataclass/templates/docstrings.google.jinja2000066400000000000000000000004441526437326600303270ustar00rootroot00000000000000{% set offset = (level + 2) * 4 + 7 -%} {{ '"""{}"""'.format(obj.help | clean_docstring) }} {% if obj.has_help_attr %} Attributes: {%- for var_name, var_doc in obj | class_params %} {{ "{}: {}".format(var_name, var_doc) | text_wrap(offset) | indent(first=True) }} {%- endfor -%} {%- endif %} tefra-xsdata-cf7afe8/xsdata/formats/dataclass/templates/docstrings.numpy.jinja2000066400000000000000000000006221526437326600302210ustar00rootroot00000000000000{% set offset = (level + 1) * 4 + 7 -%} {{ '"""{}"""'.format(obj.help | clean_docstring) }} {% if obj.has_help_attr %} {{ "Properties" if obj.is_enumeration else "Parameters" }} ---------- {%- for var_name, var_doc in obj | class_params %} {{ var_name }} {%- if var_doc %} {{ var_doc | text_wrap(offset, subsequent_indent="") | indent(width=4, first=True) }} {%- endif -%} {%- endfor -%} {%- endif %} tefra-xsdata-cf7afe8/xsdata/formats/dataclass/templates/docstrings.rst.jinja2000066400000000000000000000005501526437326600276610ustar00rootroot00000000000000{% set offset = (level + 1) * 4 + 7 -%} {% set is_enum = obj.is_enumeration -%} {% set prefix = "cvar" if is_enum else "ivar" -%} {{ '"""{}"""'.format(obj.help | clean_docstring) }} {% if obj.has_help_attr %} {%- for var_name, var_doc in obj | class_params %} {{ ":{} {}: {}".format(prefix, var_name, var_doc) | text_wrap(offset) }} {%- endfor %} {%- endif %} tefra-xsdata-cf7afe8/xsdata/formats/dataclass/templates/enum.jinja2000066400000000000000000000014151526437326600256400ustar00rootroot00000000000000{% set level = level | default(0) -%} {% set help | format_docstring(level + 1) %} {%- include "docstrings." + docstring_name + ".jinja2" -%} {% endset -%} {% set class_name = obj.name | class_name %} class {{ class_name }}(Enum): {%- if help %} {{ help | indent(4, first=True) }} {%- endif -%} {%- for attr in obj.attrs %} {{ attr.name | constant_name(obj.name) }} = {{ attr | field_default(obj.ns_map) }} {%- endfor -%} {% if docstring_name == "accessible" -%} {{ "\n\n" if level == 0 else "\n" }} {%- for attr in obj.attrs if attr.help %} {% set member_name = "{}.{}.__doc__ = ".format(class_name, attr.name | constant_name(obj.name)) -%} {{ member_name }}{{ attr.help | clean_docstring(false) | format_string(indent=0, key=member_name) }} {%- endfor -%} {%- endif -%} tefra-xsdata-cf7afe8/xsdata/formats/dataclass/templates/imports.jinja2000066400000000000000000000005701526437326600263720ustar00rootroot00000000000000{%- for source, items in imports|groupby("source") -%} {%- if items|length == 1 -%} from {{ source | import_module(module) }} import {{ items[0].name | import_class(alias=items[0].alias) }} {% else -%} from {{ source | import_module(module) }} import ( {%- for item in items %} {{ item.name | import_class(alias=item.alias) }}, {%- endfor %} ) {% endif -%} {%- endfor %} tefra-xsdata-cf7afe8/xsdata/formats/dataclass/templates/module.jinja2000066400000000000000000000002151526437326600261560ustar00rootroot00000000000000{{ output|default_imports }} {% include "imports.jinja2" -%} {%- if namespace %} __NAMESPACE__ = "{{ namespace }}" {% endif %} {{ output }} tefra-xsdata-cf7afe8/xsdata/formats/dataclass/templates/package.jinja2000066400000000000000000000004521526437326600262670ustar00rootroot00000000000000{% include "imports.jinja2" %} __all__ = [ {%- for source, items in imports|groupby("source") -%} {%- for item in items %} " {%- if item.alias %} {{- item.alias|class_name -}} {% else %} {{- item.name|class_name -}} {% endif -%} ", {%- endfor %} {%- endfor %} ] tefra-xsdata-cf7afe8/xsdata/formats/dataclass/templates/service.jinja2000066400000000000000000000002221526437326600263270ustar00rootroot00000000000000class {{ obj.name|class_name }}: {%- for attr in obj.attrs %} {{ attr.name|field_name(obj.name) }} = {{ attr|constant_value }} {%- endfor -%} tefra-xsdata-cf7afe8/xsdata/formats/dataclass/transports.py000066400000000000000000000046621526437326600243770ustar00rootroot00000000000000import abc from typing import Any from requests import Response, Session class Transport(abc.ABC): """An HTTP transport interface.""" __slots__ = () @abc.abstractmethod def get(self, url: str, params: dict, headers: dict) -> bytes: """Send a GET request.""" @abc.abstractmethod def post(self, url: str, data: Any, headers: dict) -> bytes: """Send a POST request.""" class DefaultTransport(Transport): """Default transport based on the `requests` library. Args: timeout: Read timeout in seconds """ __slots__ = "session", "timeout" def __init__(self, timeout: float = 2.0, session: Session | None = None): """Initialize the transport.""" self.timeout = timeout self.session = session or Session() def get(self, url: str, params: dict, headers: dict) -> bytes: """Send a GET request. Args: url: The base URL params: The query parameters headers: A key-value map of HTTP headers Returns: The encoded response content. Raises: HTTPError: if status code is not valid for content unmarshalling. """ res = self.session.get( url, params=params, headers=headers, timeout=self.timeout, ) return self.handle_response(res) def post(self, url: str, data: Any, headers: dict) -> Any: """Send a POST request. Args: url: The base URL data: The request body payload headers: A key-value map of HTTP headers Returns: The encoded response content. Raises: HTTPError: if status code is not valid for content unmarshalling. """ res = self.session.post(url, data=data, headers=headers, timeout=self.timeout) return self.handle_response(res) @classmethod def handle_response(cls, response: Response) -> bytes: """Return the response content or raise an exception. Status codes 200 or 500 means that we can unmarshall the response. Args: response: The response instance Returns: The encoded response content. Raises: HTTPError: If the response status code is not 200 or 500 """ if response.status_code not in (200, 500): response.raise_for_status() return response.content tefra-xsdata-cf7afe8/xsdata/formats/dataclass/typing.py000066400000000000000000000175401526437326600234710ustar00rootroot00000000000000import sys from collections.abc import Callable, Iterable, Mapping, Sequence from types import UnionType from typing import Any, NamedTuple, TypeVar, Union, get_args, get_origin if (3, 9) <= sys.version_info[:2] <= (3, 10): # Backport this fix for python 3.9 and 3.10 # https://github.com/python/cpython/pull/30900 from types import GenericAlias from typing import ForwardRef from typing import _eval_type as __eval_type # type: ignore def _eval_type(tp: Any, globalns: Any, localns: Any) -> Any: if isinstance(tp, GenericAlias): args = tuple( ForwardRef(arg) if isinstance(arg, str) else arg for arg in tp.__args__ ) tp = tp.__origin__[args] # type: ignore return __eval_type(tp, globalns, localns) elif sys.version_info[:2] >= (3, 13): # python 3.13+ requires type_params argument from typing import _eval_type as __eval_type # type: ignore def _eval_type(tp: Any, globalns: Any, localns: Any) -> Any: return __eval_type(tp, globalns, localns, type_params=()) else: from typing import _eval_type # type: ignore NONE_TYPE = type(None) UNION_TYPES = (Union, UnionType) ITERABLE_TYPES = (list, tuple, Iterable, Sequence) LIST_CONTAINERS = (Iterable, Sequence) def evaluate(tp: Any, globalns: Any, localns: Any = None) -> Any: """Analyze/Validate the typing annotation.""" result = _eval_type(tp, globalns, localns) # Ugly hack for the Type["str"] # Let's switch to ForwardRef("str") if get_origin(result) is type: args = get_args(result) if len(args) != 1: raise TypeError return args[0] return result class Result(NamedTuple): """Analyze Result Object.""" types: tuple[type[Any], ...] factory: Callable | None = None tokens_factory: Callable | None = None optional: bool = False def analyze_token_args(origin: Any, args: tuple[Any, ...]) -> tuple[Any]: """Analyze token arguments. Ensure it only has one argument, filter out ellipsis. Args: origin: The annotation origin args: The annotation arguments Returns: A tuple that contains only one arg Raises: TypeError: If the origin is not list or tuple, and it has more than one argument """ if origin in ITERABLE_TYPES: args = filter_ellipsis(args) if len(args) == 1: return args raise TypeError def analyze_optional_origin( origin: Any, args: tuple[Any, ...], types: tuple[Any, ...] ) -> tuple[Any, ...]: """Analyze optional type annotations. Remove the NoneType, adjust and return the origin, args and types. Args: origin: The annotation origin args: The annotation arguments types: The annotation types Returns: The old or new origin args and types. """ if origin in UNION_TYPES: new_args = filter_none_type(args) if len(new_args) == 1: return get_origin(new_args[0]), get_args(new_args[0]), new_args return origin, args, types def filter_none_type(args: tuple[Any, ...]) -> tuple[Any, ...]: """Filter out none types from args.""" return tuple(arg for arg in args if arg is not NONE_TYPE) def filter_ellipsis(args: tuple[Any, ...]) -> tuple[Any]: """Filter out ellipsis from args.""" return tuple(arg for arg in args if arg is not Ellipsis) def evaluate_text(annotation: Any, tokens: bool = False) -> Result: """Run exactly the same validations with attribute.""" return evaluate_attribute(annotation, tokens) def evaluate_attribute(annotation: Any, tokens: bool = False) -> Result: """Validate annotations for an XML attribute.""" types = (annotation,) origin = get_origin(annotation) args = get_args(annotation) tokens_factory = None if origin in UNION_TYPES: optional = NONE_TYPE in args else: optional = False if tokens: origin, args, types = analyze_optional_origin(origin, args, types) args = analyze_token_args(origin, args) tokens_factory = origin if tokens_factory in LIST_CONTAINERS: tokens_factory = list origin = get_origin(args[0]) if origin in UNION_TYPES: args = get_args(args[0]) elif origin: raise TypeError if origin in UNION_TYPES: types = filter_none_type(args) elif origin is None: types = args or (annotation,) else: raise TypeError if any(get_origin(tp) for tp in types): raise TypeError return Result(types=types, tokens_factory=tokens_factory, optional=optional) def evaluate_attributes(annotation: Any, **_: Any) -> Result: """Validate annotations for XML wildcard attributes.""" if annotation is dict: args = () else: origin = get_origin(annotation) args = get_args(annotation) if origin is not dict and origin is not Mapping: raise TypeError if args and not all(arg is str for arg in args): raise TypeError # Attributes always have optional=False (nothing else is supported) return Result(types=(str,), factory=dict, optional=False) def evaluate_element(annotation: Any, tokens: bool = False) -> Result: """Validate annotations for an XML element.""" # Only the derived element value field is allowed a typevar if isinstance(annotation, TypeVar) and annotation.__bound__ is object: annotation = object types = (annotation,) origin = get_origin(annotation) args = get_args(annotation) tokens_factory = factory = None # Compute optional status from original annotation before any processing if origin in UNION_TYPES: optional = NONE_TYPE in args else: optional = False origin, args, types = analyze_optional_origin(origin, args, types) if tokens: args = analyze_token_args(origin, args) tokens_factory = origin origin = get_origin(args[0]) types = args args = get_args(args[0]) if origin in ITERABLE_TYPES: args = tuple(arg for arg in args if arg is not Ellipsis) if len(args) != 1: raise TypeError if tokens_factory: factory = tokens_factory tokens_factory = origin else: factory = origin types = args origin = get_origin(args[0]) args = get_args(args[0]) if origin in UNION_TYPES: types = filter_none_type(args) elif origin: raise TypeError if factory in LIST_CONTAINERS: factory = list if tokens_factory in LIST_CONTAINERS: tokens_factory = list return Result( types=types, factory=factory, tokens_factory=tokens_factory, optional=optional, ) def evaluate_elements(annotation: Any, **_: Any) -> Result: """Validate annotations for an XML compound field.""" result = evaluate_element(annotation, tokens=False) for tp in result.types: evaluate_element(tp, tokens=False) return Result(types=(object,), factory=result.factory, optional=result.optional) def evaluate_wildcard(annotation: Any, **_: Any) -> Result: """Validate annotations for an XML wildcard.""" origin = get_origin(annotation) factory = None # Compute optional status from original annotation if origin in UNION_TYPES: args = get_args(annotation) optional = NONE_TYPE in args else: optional = False if origin in UNION_TYPES: types = filter_none_type(args) elif origin in ITERABLE_TYPES: factory = list if origin in LIST_CONTAINERS else origin types = filter_ellipsis(get_args(annotation)) elif origin is None: types = (annotation,) else: raise TypeError if len(types) != 1 or object not in types: raise TypeError return Result(types=types, factory=factory, optional=optional) tefra-xsdata-cf7afe8/xsdata/formats/mixins.py000066400000000000000000000064171526437326600215300ustar00rootroot00000000000000import abc import datetime from collections.abc import Iterator from pathlib import Path from typing import NamedTuple from xsdata import __version__ from xsdata.codegen.exceptions import CodegenError from xsdata.codegen.models import Class from xsdata.models.config import GeneratorConfig from xsdata.utils.collections import group_by from xsdata.utils.package import module_path, package_path class GeneratorResult(NamedTuple): """Generator result transfer object. Attributes: path: The target file path title: The result title for misc usage source: The source code/output to be written """ path: Path title: str source: str class AbstractGenerator(abc.ABC): """Abstract code generator class. Args: config: The generator config instance """ __slots__ = "config" def __init__(self, config: GeneratorConfig): """Initialize the generator.""" self.config = config def module_name(self, module: str) -> str: """Convert the given module name to match the generator conventions.""" return module def package_name(self, package: str) -> str: """Convert the given module name to match the generator conventions.""" return package @abc.abstractmethod def render(self, classes: list[Class]) -> Iterator[GeneratorResult]: """Return an iterator of the generated results.""" @classmethod def group_by_package(cls, classes: list[Class]) -> dict[Path, list[Class]]: """Group the given list of classes by the target package directory.""" return group_by(classes, lambda x: package_path(x.target_module)) @classmethod def group_by_module(cls, classes: list[Class]) -> dict[Path, list[Class]]: """Group the given list of classes by the target module directory.""" return group_by(classes, lambda x: module_path(x.target_module)) def render_header(self) -> str: """Generate a header for the writer to prepend on the output files.""" if not self.config.output.include_header: return "" now = datetime.datetime.now().isoformat(sep=" ", timespec="seconds") return ( f'"""This file was generated by xsdata, v{__version__}, on {now}' f"\n\nGenerator: {self.__class__.__qualname__}\n" f"See: https://xsdata.readthedocs.io/\n" '"""\n' ) def normalize_packages(self, classes: list[Class]) -> None: """Normalize the classes module and package names. Args: classes: A list of class instances Raises: CodeGenerationError: If the analyzer failed to designate a class to a package and module. """ modules = {} packages = {} for obj in classes: if obj.package is None or obj.module is None: raise CodegenError( f"Class `{obj.name}` has not been assigned to a package" ) if obj.module not in modules: modules[obj.module] = self.module_name(obj.module) if obj.package not in packages: packages[obj.package] = self.package_name(obj.package) obj.module = modules[obj.module] obj.package = packages[obj.package] tefra-xsdata-cf7afe8/xsdata/formats/types.py000066400000000000000000000000551526437326600213550ustar00rootroot00000000000000from typing import TypeVar T = TypeVar("T") tefra-xsdata-cf7afe8/xsdata/logger.py000066400000000000000000000000651526437326600200160ustar00rootroot00000000000000import logging logger = logging.getLogger(__name__) tefra-xsdata-cf7afe8/xsdata/models/000077500000000000000000000000001526437326600174475ustar00rootroot00000000000000tefra-xsdata-cf7afe8/xsdata/models/__init__.py000066400000000000000000000000001526437326600215460ustar00rootroot00000000000000tefra-xsdata-cf7afe8/xsdata/models/config.py000066400000000000000000000451711526437326600212760ustar00rootroot00000000000000import re import warnings from collections.abc import Callable from dataclasses import dataclass, field from enum import Enum from io import TextIOBase from pathlib import Path from re import Pattern from typing import Any from xsdata import __version__ from xsdata.codegen.exceptions import CodegenError, CodegenWarning from xsdata.formats.dataclass.context import XmlContext from xsdata.formats.dataclass.parsers import XmlParser from xsdata.formats.dataclass.parsers.config import ParserConfig from xsdata.formats.dataclass.serializers import XmlSerializer from xsdata.formats.dataclass.serializers.config import SerializerConfig from xsdata.formats.dataclass.serializers.writers import XmlEventWriter from xsdata.models.enums import Namespace from xsdata.utils import objects, text class StructureStyle(Enum): """Output structure style enumeration. Attributes: FILENAMES: filenames: groups classes by the schema location NAMESPACES: namespaces: group classes by the target namespace CLUSTERS: clusters: group by strong connected dependencies SINGLE_PACKAGE: single-package: group all classes together NAMESPACE_CLUSTERS: namespace-clusters: group by strong connected dependencies and namespaces """ FILENAMES = "filenames" NAMESPACES = "namespaces" CLUSTERS = "clusters" SINGLE_PACKAGE = "single-package" NAMESPACE_CLUSTERS = "namespace-clusters" class NameCase(Enum): """Naming case convention enumeration. All schemes are using a processor that splits a string into words when it encounters non-alphanumerical characters or when an upper case letter follows a lower case letter. Attributes: ORIGINAL: originalCase PASCAL: pascalCase CAMEL: camelCase SNAKE: snakeCase SCREAMING_SNAKE: screamingSnakeCase MIXED: mixedCase MIXED_SNAKE: mixedSnakeCase MIXED_PASCAL: mixedPascalCase """ ORIGINAL = "originalCase" PASCAL = "pascalCase" CAMEL = "camelCase" SNAKE = "snakeCase" SCREAMING_SNAKE = "screamingSnakeCase" MIXED = "mixedCase" MIXED_SNAKE = "mixedSnakeCase" MIXED_PASCAL = "mixedPascalCase" def __call__(self, string: str, **kwargs: Any) -> str: """Apply the callback to the input string.""" return self.callback(string, **kwargs) @property def callback(self) -> Callable: """Return the actual callable of the scheme.""" return __name_case_func__[self.value] __name_case_func__: dict[str, Callable] = { "originalCase": text.original_case, "pascalCase": text.pascal_case, "camelCase": text.camel_case, "snakeCase": text.snake_case, "screamingSnakeCase": text.screaming_snake_case, "mixedCase": text.mixed_case, "mixedSnakeCase": text.mixed_snake_case, "mixedPascalCase": text.mixed_pascal_case, } class DocstringStyle(Enum): """Docstring style enumeration. Attributes: RST: reStructuredText NUMPY: NumPy GOOGLE: Google ACCESSIBLE: Accessible BLANK: Blank """ RST = "reStructuredText" NUMPY = "NumPy" GOOGLE = "Google" ACCESSIBLE = "Accessible" BLANK = "Blank" class ObjectType(Enum): """Object type enumeration. Attributes: CLASS: class FIELD: field MODULE: module PACKAGE: package """ CLASS = "class" FIELD = "field" MODULE = "module" PACKAGE = "package" class ExtensionType(Enum): """Extension type enumeration. Attributes: CLASS: class DECORATOR: decorator """ CLASS = "class" DECORATOR = "decorator" @dataclass class OutputFormat: """Output format model representation. Args: value: Output format name repr: Generate __repr__ method eq: Generate __eq__ method order: Generate __lt__, __le__, __gt__, and __ge__ methods unsafe_hash: Generate __hash__ method frozen: Enable read only properties slots: Enable __slots__ """ value: str = field( default="dataclasses", metadata={"type": "Text", "cli": "output"} ) repr: bool = field(default=True, metadata={"type": "Attribute"}) eq: bool = field(default=True, metadata={"type": "Attribute"}) order: bool = field(default=False, metadata={"type": "Attribute"}) unsafe_hash: bool = field(default=False, metadata={"type": "Attribute"}) frozen: bool = field(default=False, metadata={"type": "Attribute"}) slots: bool = field(default=False, metadata={"type": "Attribute"}) def __post_init__(self): """Post initialization method.""" self.validate() def validate(self) -> None: """Validate and reset configuration conflicts.""" if self.order and not self.eq: self.eq = True warnings.warn( "Enabling eq because order is true", CodegenWarning, ) @dataclass class CompoundFields: """Compound fields model representation. Args: enabled: Use compound fields for repeatable elements default_name: Default compound field name use_substitution_groups: Use substitution groups if they exist, instead of element names. force_default_name: Always use the default compound field name, or try to generate one by the list of element names if they are no longer than the max name parts. e.g. hat_or_dress_or_something. max_name_parts: Maximum number of element names before using the default name. """ enabled: bool = field( default=False, metadata={"type": "Text", "cli": "compound-fields"} ) default_name: str = field( default="choice", metadata={"type": "Attribute", "cli": False} ) use_substitution_groups: bool = field( default=False, metadata={"type": "Attribute", "cli": False} ) force_default_name: bool = field( default=False, metadata={"type": "Attribute", "cli": False} ) max_name_parts: int = field(default=3, metadata={"type": "Attribute", "cli": False}) @dataclass class GeneratorOutput: """Generator output model representation. Args: package: Target package format: Output format structure_style: Output structure style docstring_style: Docstring style relative_imports: Use relative imports compound_fields: Use compound fields for repeatable elements wrapper_fields: Generate wrapper fields max_line_length: Adjust the maximum line length generic_collections: Use generic collections (Iterable, Mapping) unnest_classes: Move inner classes to upper level ignore_patterns: Ignore pattern restrictions include_header: Include a header with codegen information in the output """ package: str = field(default="generated", metadata={"type": "Element"}) format: OutputFormat = field( default_factory=OutputFormat, metadata={"type": "Element"} ) structure_style: StructureStyle = field( default=StructureStyle.FILENAMES, metadata={"type": "Element", "name": "Structure"}, ) docstring_style: DocstringStyle = field( default=DocstringStyle.RST, metadata={"type": "Element"} ) relative_imports: bool = field(default=False, metadata={"type": "Element"}) compound_fields: CompoundFields = field( default_factory=CompoundFields, metadata={"type": "Element"} ) wrapper_fields: bool = field(default=False, metadata={"type": "Element"}) max_line_length: int = field(default=79, metadata={"type": "Attribute"}) generic_collections: bool = field(default=False, metadata={"type": "Attribute"}) unnest_classes: bool = field(default=False, metadata={"type": "Element"}) ignore_patterns: bool = field(default=False, metadata={"type": "Element"}) include_header: bool = field(default=False, metadata={"type": "Element"}) def __post_init__(self): """Post initialization method.""" self.validate() def validate(self) -> None: """Reset configuration conflicts.""" if self.generic_collections and self.format.frozen: self.generic_collections = False warnings.warn( "Generic Collections, requires frozen=False, reverting...", CodegenWarning, ) def update(self, **kwargs: Any) -> None: """Update instance attributes recursively.""" objects.update(self, **kwargs) self.format.validate() @dataclass class NameConvention: """Name convention model representation. Args: case: Naming scheme, e.g. camelCase, snakeCase safe_prefix: A prefix to be prepended into names that match one of the reserved words. """ case: NameCase = field(metadata={"type": "Attribute"}) safe_prefix: str = field(metadata={"type": "Attribute"}) @dataclass class GeneratorConventions: """Generator naming conventions model representation. Args: class_name: Class naming conventions. field_name: Field naming conventions. module_name: Module naming conventions. package_name: Package naming conventions. """ class_name: NameConvention = field( default_factory=lambda: NameConvention(NameCase.PASCAL, "type"), metadata={"type": "Element"}, ) field_name: NameConvention = field( default_factory=lambda: NameConvention(NameCase.SNAKE, "value"), metadata={"type": "Element"}, ) constant_name: NameConvention = field( default_factory=lambda: NameConvention(NameCase.SCREAMING_SNAKE, "value"), metadata={"type": "Element"}, ) module_name: NameConvention = field( default_factory=lambda: NameConvention(NameCase.SNAKE, "mod"), metadata={"type": "Element"}, ) package_name: NameConvention = field( default_factory=lambda: NameConvention(NameCase.SNAKE, "pkg"), metadata={"type": "Element"}, ) @dataclass class GeneratorAlias: """Generator alias model representation. Define an alias for a module, package, class and field Alias definition model. Each alias has a source attribute that refers to the original name in the schema definition and the target attribute for output name. For package and module aliases the source refers to the schema filename or target namespace depending on the selected output structure. Args: source: The source name from schema definition target: The target name of the object. """ source: str = field(metadata={"type": "Attribute", "required": True}) target: str = field(metadata={"type": "Attribute", "required": True}) @dataclass class GeneratorAliases: """Generator aliases model representation. Generator aliases for classes, fields, packages and modules that bypass the global naming conventions. The aliases are not validated as valid python identifiers. Args: class_name: A list of class name aliases field_name: A list of field name aliases package_name: A list of package name aliases module_name: A list of module name aliases """ class_name: list[GeneratorAlias] = field( default_factory=list, metadata={"type": "Element"} ) field_name: list[GeneratorAlias] = field( default_factory=list, metadata={"type": "Element"} ) package_name: list[GeneratorAlias] = field( default_factory=list, metadata={"type": "Element"} ) module_name: list[GeneratorAlias] = field( default_factory=list, metadata={"type": "Element"} ) @dataclass class GeneratorSubstitution: """Generator substitution model representation. Search and replace substitutions based on `re.sub`. Args: type: The target object type search: The search string or a pattern object replace: The replacement string or pattern object """ type: ObjectType = field(metadata={"type": "Attribute", "required": True}) search: str = field(metadata={"type": "Attribute", "required": True}) replace: str = field(metadata={"type": "Attribute", "required": True}) @dataclass class GeneratorExtension: """Generator extension model representation. Add decorators or base classes on the generated classes that match the class name pattern. Args: type: The extension type class_name: The class name or a pattern to apply the extension import_string: The import string of the extension type prepend: Prepend or append decorator or base class apply_if_derived: Apply or skip if the class is already a subclass parent_path: Optional pattern against the fully-qualified parent element name Attributes: module_path: The module path of the base class or the annotation func_name: The annotation or base class name pattern: The compiled search class name pattern parent_pattern: The compiled search parent pattern or None """ type: ExtensionType = field(metadata={"type": "Attribute", "required": True}) class_name: str = field( metadata={"type": "Attribute", "required": True, "name": "class"} ) import_string: str = field( metadata={"type": "Attribute", "required": True, "name": "import"} ) prepend: bool = field(default=False, metadata={"type": "Attribute"}) apply_if_derived: bool = field( default=False, metadata={"type": "Attribute", "name": "applyIfDerived"} ) module_path: str = field( init=False, metadata={"type": "Ignore"}, ) func_name: str = field( init=False, metadata={"type": "Ignore"}, ) pattern: Pattern = field( init=False, metadata={"type": "Ignore"}, ) parent_path: str | None = field( default=None, metadata={"type": "Attribute", "required": False, "name": "module"}, ) parent_pattern: Pattern | None = field( init=False, metadata={"type": "Ignore"}, ) def __post_init__(self): """Post initialization method. Set the module, func_name and pattern instance attributes. Raises: GeneratorConfigError: If the pattern can not be compiled. """ try: self.module_path, self.func_name = self.import_string.rsplit(".", 1) except (ValueError, AttributeError): raise CodegenError( "Invalid extension import string", value=self.import_string ) try: self.pattern = re.compile(self.class_name) except re.error: raise CodegenError( "Failed to compile extension pattern", pattern=self.class_name ) self.parent_pattern = None if self.parent_path: try: self.parent_pattern = re.compile(self.parent_path) except re.error: raise CodegenError( "Failed to compile extension parent pattern", pattern=self.parent_path, ) @dataclass class GeneratorSubstitutions: """Generator substitutions model representation. Generator search and replace substitutions for classes, fields, packages and modules names. The process runs before and after the default naming conventions. Args: substitution: The list of substitution instances """ substitution: list[GeneratorSubstitution] = field( default_factory=list, metadata={"type": "Element"} ) @dataclass class GeneratorExtensions: """Generator extensions model representation. Generator extensions for classes. The process runs after the default naming conventions. The generator doesn't validate imports! Args: extension: The list of extension instances """ extension: list[GeneratorExtension] = field( default_factory=list, metadata={"type": "Element"} ) @dataclass class GeneratorConfig: """Generator configuration model representation. Args: output: Output options conventions: Generator conventions substitutions: Search and replace substitutions for classes, fields, packages and modules names. extensions: Generator custom base classes and decorators for classes. Attributes: version: The xsdata version number the config was created/updated """ class Meta: """Metadata options.""" name = "Config" namespace = "http://pypi.org/project/xsdata" version: str = field(default=__version__, metadata={"type": "Attribute"}) output: GeneratorOutput = field( default_factory=GeneratorOutput, metadata={"type": "Element"} ) conventions: GeneratorConventions = field( default_factory=GeneratorConventions, metadata={"type": "Element"} ) substitutions: GeneratorSubstitutions = field( default_factory=GeneratorSubstitutions, metadata={"type": "Element"} ) extensions: GeneratorExtensions = field( default_factory=GeneratorExtensions, metadata={"type": "Element"} ) @classmethod def create(cls) -> "GeneratorConfig": """Initialize with default substitutions for common namespaces.""" obj = cls() for ns in Namespace: obj.substitutions.substitution.append( GeneratorSubstitution( type=ObjectType.PACKAGE, search=ns.uri, replace=ns.prefix ) ) obj.substitutions.substitution.append( GeneratorSubstitution( type=ObjectType.CLASS, search="(.*)Class$", replace="\\1Type" ) ) return obj @classmethod def read(cls, path: Path) -> "GeneratorConfig": """Load configuration from a file path.""" if not path.exists(): return cls() ctx = XmlContext( element_name_generator=text.pascal_case, attribute_name_generator=text.camel_case, ) parser = XmlParser( context=ctx, config=ParserConfig( fail_on_unknown_properties=False, fail_on_converter_warnings=True, ), ) cfg = parser.from_path(path, cls) cfg.version = __version__ return cfg @classmethod def write(cls, output: TextIOBase, obj: "GeneratorConfig"): """Write the configuration to the output stream as XML.""" ctx = XmlContext( element_name_generator=text.pascal_case, attribute_name_generator=text.camel_case, ) config = SerializerConfig(indent=" ") serializer = XmlSerializer(context=ctx, config=config, writer=XmlEventWriter) serializer.write(output, obj, ns_map={None: "http://pypi.org/project/xsdata"}) tefra-xsdata-cf7afe8/xsdata/models/datatype.py000066400000000000000000000520731526437326600216430ustar00rootroot00000000000000import datetime import operator import re from collections import UserString from collections.abc import Callable from typing import Any, NamedTuple from xsdata.utils.dates import ( calculate_offset, calculate_timezone, format_date, format_offset, format_time, parse_date_args, validate_date, validate_time, ) xml_duration_re = re.compile( r"^([-]?)P" r"(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?" r"(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(.\d+)?)S)?)?$" ) DS_YEAR = 31556926.0 DS_MONTH = 2629743 DS_DAY = 86400 DS_HOUR = 3600 DS_MINUTE = 60 DS_FRACTIONAL_SECOND = 0.000000001 DS_OFFSET = -60 class DateFormat: """Xml date formats.""" DATE = "%Y-%m-%d%z" TIME = "%H:%M:%S%z" DATE_TIME = "%Y-%m-%dT%H:%M:%S%z" G_DAY = "---%d%z" G_MONTH = "--%m%z" G_MONTH_DAY = "--%m-%d%z" G_YEAR = "%Y%z" G_YEAR_MONTH = "%Y-%m%z" class XmlDate(NamedTuple): """Concrete xs:date builtin type. Represents iso 8601 date format [-]CCYY-MM-DD[Z|(+|-)hh:mm] with rich comparisons and hashing. Attributes: year: Any signed integer, eg (0, -535, 2020) month: Unsigned integer between 1-12 day: Unsigned integer between 1-31 offset: Signed integer representing timezone offset in minutes """ year: int month: int day: int offset: int | None = None def replace( self, year: int | None = None, month: int | None = None, day: int | None = None, offset: int | None = True, ) -> "XmlDate": """Return a new instance replacing the specified fields with new values.""" if year is None: year = self.year if month is None: month = self.month if day is None: day = self.day if offset is True: offset = self.offset return type(self)(year, month, day, offset) @classmethod def from_string(cls, string: str) -> "XmlDate": """Initialize from string with format `%Y-%m-%dT%z`.""" year, month, day, offset = parse_date_args(string, DateFormat.DATE) assert year is not None assert month is not None assert day is not None validate_date(year, month, day) return cls(year, month, day, offset) @classmethod def from_date(cls, obj: datetime.date) -> "XmlDate": """Initialize from a `datetime.date` instance.""" return cls(obj.year, obj.month, obj.day) @classmethod def from_datetime(cls, obj: datetime.datetime) -> "XmlDate": """Initialize from `datetime.datetime` instance.""" return cls(obj.year, obj.month, obj.day, calculate_offset(obj)) @classmethod def today(cls) -> "XmlDate": """Initialize with the current date.""" return cls.from_date(datetime.date.today()) def to_date(self) -> datetime.date: """Convert to a :`datetime.date` instance.""" return datetime.date(self.year, self.month, self.day) def to_datetime(self) -> datetime.datetime: """Convert to a `datetime.datetime` instance.""" tz_info = calculate_timezone(self.offset) return datetime.datetime(self.year, self.month, self.day, tzinfo=tz_info) def __str__(self) -> str: """Return the date formatted according to ISO 8601 for xml. Examples: - 2001-10-26 - 2001-10-26+02:00 - 2001-10-26Z Returns: The str result. """ return format_date(self.year, self.month, self.day) + format_offset(self.offset) def __repr__(self) -> str: """Return the instance string representation.""" args = [self.year, self.month, self.day, self.offset] if args[-1] is None: del args[-1] return f"{self.__class__.__qualname__}({', '.join(map(str, args))})" class XmlDateTime(NamedTuple): """Concrete xs:dateTime builtin type. Represents iso 8601 date time format `[-]CCYY-MM-DDThh:mm: ss[Z|(+|-)hh:mm]` with rich comparisons and hashing. Attributes: year: Any signed integer, eg (0, -535, 2020) month: Unsigned integer between 1-12 day: Unsigned integer between 1-31 hour: Unsigned integer between 0-24 minute: Unsigned integer between 0-59 second: Unsigned integer between 0-59 fractional_second: Unsigned integer between 0-999999999 offset: Signed integer representing timezone offset in minutes """ year: int month: int day: int hour: int minute: int second: int fractional_second: int = 0 offset: int | None = None @property def microsecond(self) -> int: """Calculate the instance microseconds.""" return self.fractional_second // 1000 @property def duration(self) -> float: """Calculate the instance signed duration in seconds.""" if self.year < 0: negative = True year = -self.year else: negative = False year = self.year total = ( year * DS_YEAR + self.month * DS_MONTH + self.day * DS_DAY + self.hour * DS_HOUR + self.minute * DS_MINUTE + self.second + self.fractional_second * DS_FRACTIONAL_SECOND + (self.offset or 0) * DS_OFFSET ) return -total if negative else total @classmethod def from_string(cls, string: str) -> "XmlDateTime": """Initialize from string with format `%Y-%m-%dT%H:%M:%S%z`.""" ( year, month, day, hour, minute, second, fractional_second, offset, ) = parse_date_args(string, DateFormat.DATE_TIME) assert year is not None assert month is not None assert day is not None assert hour is not None assert minute is not None assert second is not None assert fractional_second is not None validate_date(year, month, day) validate_time(hour, minute, second, fractional_second) return cls(year, month, day, hour, minute, second, fractional_second, offset) @classmethod def from_datetime(cls, obj: datetime.datetime) -> "XmlDateTime": """Initialize from `datetime.datetime` instance.""" return cls( obj.year, obj.month, obj.day, obj.hour, obj.minute, obj.second, obj.microsecond * 1000, calculate_offset(obj), ) @classmethod def now(cls, tz: datetime.timezone | None = None) -> "XmlDateTime": """Initialize with the current datetime and the given timezone.""" return cls.from_datetime(datetime.datetime.now(tz=tz)) @classmethod def utcnow(cls) -> "XmlDateTime": """Initialize with the current datetime and utc timezone.""" return cls.from_datetime(datetime.datetime.now(datetime.timezone.utc)) def to_datetime(self) -> datetime.datetime: """Return a `datetime.datetime` instance.""" return datetime.datetime( self.year, self.month, self.day, self.hour, self.minute, self.second, self.microsecond, tzinfo=calculate_timezone(self.offset), ) def replace( self, year: int | None = None, month: int | None = None, day: int | None = None, hour: int | None = None, minute: int | None = None, second: int | None = None, fractional_second: int | None = None, offset: int | None = True, ) -> "XmlDateTime": """Return a new instance replacing the specified fields with new values.""" if year is None: year = self.year if month is None: month = self.month if day is None: day = self.day if hour is None: hour = self.hour if minute is None: minute = self.minute if second is None: second = self.second if fractional_second is None: fractional_second = self.fractional_second if offset is True: offset = self.offset return type(self)( year, month, day, hour, minute, second, fractional_second, offset ) def __str__(self) -> str: """Return the datetime formatted according to ISO 8601 for xml. Examples: - 2001-10-26T21:32:52 - 2001-10-26T21:32:52+02:00 - 2001-10-26T19:32:52Z - 2001-10-26T19:32:52.126789 - 2001-10-26T21:32:52.126 - -2001-10-26T21:32:52.126Z """ date = format_date(self.year, self.month, self.day) time = format_time(self.hour, self.minute, self.second, self.fractional_second) return f"{date}T{time}{format_offset(self.offset)}" def __repr__(self) -> str: """Return the instance string representation.""" args = tuple(self) if args[-1] is None: args = args[:-1] if args[-1] == 0: args = args[:-1] return f"{self.__class__.__qualname__}({', '.join(map(str, args))})" def __eq__(self, other: Any) -> bool: """Return self == other.""" return _cmp(self, other, operator.eq) def __ne__(self, other: Any) -> bool: """Return self != other.""" return _cmp(self, other, operator.ne) def __lt__(self, other: Any) -> bool: """Return self < other.""" return _cmp(self, other, operator.lt) def __le__(self, other: Any) -> bool: """Return self <= other.""" return _cmp(self, other, operator.le) def __gt__(self, other: Any) -> bool: """Return self > other.""" return _cmp(self, other, operator.gt) def __ge__(self, other: Any) -> bool: """Return self >= other.""" return _cmp(self, other, operator.ge) class XmlTime(NamedTuple): """Concrete xs:time builtin type. Represents iso 8601 time format `hh:mm: ss[Z|(+|-)hh:mm]` with rich comparisons and hashing. Attributes: hour: Unsigned integer between 0-24 minute: Unsigned integer between 0-59 second: Unsigned integer between 0-59 fractional_second: Unsigned integer between 0-999999999 offset: Signed integer representing timezone offset in minutes """ hour: int minute: int second: int fractional_second: int = 0 offset: int | None = None @property def microsecond(self) -> int: """Calculate the instance microseconds.""" return self.fractional_second // 1000 @property def duration(self) -> float: """Calculate the total duration in seconds.""" return ( self.hour * DS_HOUR + self.minute * DS_MINUTE + self.second + self.fractional_second * DS_FRACTIONAL_SECOND + (self.offset or 0) * DS_OFFSET ) def replace( self, hour: int | None = None, minute: int | None = None, second: int | None = None, fractional_second: int | None = None, offset: int | None = True, ) -> "XmlTime": """Return a new instance replacing the specified fields with new values.""" if hour is None: hour = self.hour if minute is None: minute = self.minute if second is None: second = self.second if fractional_second is None: fractional_second = self.fractional_second if offset is True: offset = self.offset return type(self)(hour, minute, second, fractional_second, offset) @classmethod def from_string(cls, string: str) -> "XmlTime": """Initialize from string format `%H:%M:%S%z`.""" (hour, minute, second, fractional_second, offset) = parse_date_args( string, DateFormat.TIME ) assert hour is not None assert minute is not None assert second is not None assert fractional_second is not None validate_time(hour, minute, second, fractional_second) return cls(hour, minute, second, fractional_second, offset) @classmethod def from_time(cls, obj: datetime.time) -> "XmlTime": """Initialize from `datetime.time` instance.""" return cls( obj.hour, obj.minute, obj.second, obj.microsecond * 1000, calculate_offset(obj), ) @classmethod def now(cls, tz: datetime.timezone | None = None) -> "XmlTime": """Initialize with the current time and the given timezone.""" return cls.from_time(datetime.datetime.now(tz=tz).time()) @classmethod def utcnow(cls) -> "XmlTime": """Initialize with the current time and utc timezone.""" return cls.from_time(datetime.datetime.now(datetime.timezone.utc).time()) def to_time(self) -> datetime.time: """Convert to a `datetime.time` instance.""" return datetime.time( self.hour, self.minute, self.second, self.microsecond, tzinfo=calculate_timezone(self.offset), ) def __str__(self) -> str: """Return the time formatted according to ISO 8601 for xml. Examples: - 21:32:52 - 21:32:52+02:00, - 19:32:52Z - 21:32:52.126789 - 21:32:52.126Z """ time = format_time(self.hour, self.minute, self.second, self.fractional_second) return f"{time}{format_offset(self.offset)}" def __repr__(self) -> str: """Return the instance string representation.""" args = list(self) if args[-1] is None: del args[-1] return f"{self.__class__.__qualname__}({', '.join(map(str, args))})" def __eq__(self, other: Any) -> bool: """Return self == other.""" return _cmp(self, other, operator.eq) def __ne__(self, other: Any) -> bool: """Return self != other.""" return _cmp(self, other, operator.ne) def __lt__(self, other: Any) -> bool: """Return self < other.""" return _cmp(self, other, operator.lt) def __le__(self, other: Any) -> bool: """Return self <= other.""" return _cmp(self, other, operator.le) def __gt__(self, other: Any) -> bool: """Return self > other.""" return _cmp(self, other, operator.gt) def __ge__(self, other: Any) -> bool: """Return self >= other.""" return _cmp(self, other, operator.ge) DurationType = XmlTime | XmlDateTime def _cmp(a: DurationType, b: DurationType, op: Callable) -> bool: if isinstance(b, a.__class__): return op(a.duration, b.duration) return NotImplemented class TimeInterval(NamedTuple): """Time interval model representation.""" negative: bool years: int | None months: int | None days: int | None hours: int | None minutes: int | None seconds: float | None class XmlDuration(UserString): """Concrete xs:duration builtin type. Represents iso 8601 duration format PnYnMnDTnHnMnS with rich comparisons and hashing. Format PnYnMnDTnHnMnS: - **P**: literal value that starts the expression - **nY**: the number of years followed by a literal Y - **nM**: the number of months followed by a literal M - **nD**: the number of days followed by a literal D - **T**: literal value that separates date and time parts - **nH**: the number of hours followed by a literal H - **nM**: the number of minutes followed by a literal M - **nS**: the number of seconds followed by a literal S Args: value: String representation of a xs:duration, eg **P2Y6M5DT12H** """ def __init__(self, value: str) -> None: """Initialize with the given string.""" super().__init__(value) self._interval = self._parse_interval(value) @property def years(self) -> int | None: """Number of years in the interval.""" return self._interval.years @property def months(self) -> int | None: """Number of months in the interval.""" return self._interval.months @property def days(self) -> int | None: """Number of days in the interval.""" return self._interval.days @property def hours(self) -> int | None: """Number of hours in the interval.""" return self._interval.hours @property def minutes(self) -> int | None: """Number of minutes in the interval.""" return self._interval.minutes @property def seconds(self) -> float | None: """Number of seconds in the interval.""" return self._interval.seconds @property def negative(self) -> bool: """Negative flag of the interval.""" return self._interval.negative @classmethod def _parse_interval(cls, value: str) -> TimeInterval: if not isinstance(value, str): raise ValueError("Value must be string") if len(value) < 3 or value.endswith("T"): raise ValueError(f"Invalid format '{value}'") match = xml_duration_re.match(value) if not match: raise ValueError(f"Invalid format '{value}'") sign, years, months, days, hours, minutes, seconds, _ = match.groups() return TimeInterval( negative=sign == "-", years=int(years) if years else None, months=int(months) if months else None, days=int(days) if days else None, hours=int(hours) if hours else None, minutes=int(minutes) if minutes else None, seconds=float(seconds) if seconds else None, ) def asdict(self) -> dict: """Return instance as a dict.""" return self._interval._asdict() def __repr__(self) -> str: """Return the instance string representation.""" return f'{self.__class__.__qualname__}("{self.data}")' class TimePeriod(NamedTuple): """Time period model representation.""" year: int | None month: int | None day: int | None offset: int | None class XmlPeriod(UserString): """Concrete xs:gYear/Month/Day builtin type. Represents iso 8601 period formats with rich comparisons and hashing. Formats: - xs:gDay: **---%d%z** - xs:gMonth: **--%m%z** - xs:gYear: **%Y%z** - xs:gMonthDay: **--%m-%d%z** - xs:gYearMonth: **%Y-%m%z** Args: value: String representation of a xs:period, eg **--11-01Z** """ def __init__(self, value: str) -> None: """Initialize a xs:period.""" value = value.strip() super().__init__(value) self._period = self._parse_period(value) @property def year(self) -> int | None: """Period year.""" return self._period.year @property def month(self) -> int | None: """Period month.""" return self._period.month @property def day(self) -> int | None: """Period day.""" return self._period.day @property def offset(self) -> int | None: """Period timezone offset in minutes.""" return self._period.offset @classmethod def _parse_period(cls, value: str) -> TimePeriod: year = month = day = offset = None if value.startswith("---"): day, offset = parse_date_args(value, DateFormat.G_DAY) elif value.startswith("--"): # Bogus format --MM--, --05---05:00 if value[4:6] == "--": value = value[:4] + value[6:] if len(value) in (4, 5, 10): # fixed lengths with/out timezone month, offset = parse_date_args(value, DateFormat.G_MONTH) else: month, day, offset = parse_date_args(value, DateFormat.G_MONTH_DAY) else: end = len(value) if value.find(":") > -1: # offset end -= 6 if value[:end].rfind("-") > 3: # Minimum position for month sep year, month, offset = parse_date_args(value, DateFormat.G_YEAR_MONTH) else: year, offset = parse_date_args(value, DateFormat.G_YEAR) validate_date(0, month or 1, day or 1) return TimePeriod(year=year, month=month, day=day, offset=offset) def as_dict(self) -> dict: """Return date units as dict.""" return self._period._asdict() def __repr__(self) -> str: """Return the instance string representation.""" return f'{self.__class__.__qualname__}("{self.data}")' def __eq__(self, other: Any) -> bool: """Return self == other.""" if isinstance(other, XmlPeriod): return self._period == other._period return NotImplemented class XmlHexBinary(bytes): """Subclass bytes to infer base16 format. This type can be used with xs:anyType fields that don't have a format property to specify the target output format. """ class XmlBase64Binary(bytes): """Subclass bytes to infer base64 format. This type can be used with xs:anyType fields that don't have a format property to specify the target output format. """ tefra-xsdata-cf7afe8/xsdata/models/dtd.py000066400000000000000000000060311526437326600205740ustar00rootroot00000000000000import enum from dataclasses import dataclass from typing import Optional from xsdata.models.enums import DataType from xsdata.utils.namespaces import build_qname class DtdElementType(enum.Enum): """DTD Element type enumeration.""" UNDEFINED = "undefined" EMPTY = "empty" ANY = "any" MIXED = "mixed" ELEMENT = "element" class DtdAttributeDefault(enum.Enum): """DTD Attribute default enumeration.""" REQUIRED = "required" IMPLIED = "implied" FIXED = "fixed" NONE = "none" class DtdAttributeType(enum.Enum): """DTD Attribute type enumeration.""" CDATA = "cdata" ID = "id" IDREF = "idref" IDREFS = "idrefs" ENTITY = "entity" ENTITIES = "entities" NMTOKEN = "nmtoken" NMTOKENS = "nmtokens" ENUMERATION = "enumeration" NOTATION = "notation" class DtdContentType(enum.Enum): """DTD Content type enumeration.""" PCDATA = "pcdata" ELEMENT = "element" SEQ = "seq" OR = "or" class DtdContentOccur(enum.Enum): """DTD Content occur enumeration.""" ONCE = "once" OPT = "opt" MULT = "mult" PLUS = "plus" @dataclass class DtdAttribute: """DTD Attribute model representation. Args: name: The attribute name prefix: The attribute namespace prefix type: The attribute type default: The attribute default type default_value: The attribute default value values: The available choices as value """ name: str prefix: str | None type: DtdAttributeType default: DtdAttributeDefault default_value: str | None values: list[str] @property def data_type(self) -> DataType: """Return the data type instance from the attribute type.""" return DataType.from_code(self.type.value.lower()) @dataclass class DtdContent: """DTD Content model representation. Args: name: The content name type: The content type occur: The content occur type left: The parent content right: The child content """ name: str type: DtdContentType occur: DtdContentOccur left: Optional["DtdContent"] right: Optional["DtdContent"] @dataclass class DtdElement: """DTD Element model representation. Args: name: The element name type: The element type prefix: The element namespace prefix content: The element content attributes: The element attribute list ns_map: The namespace prefix-URI map """ name: str type: DtdElementType prefix: str | None content: DtdContent | None attributes: list[DtdAttribute] ns_map: dict @property def qname(self) -> str: """Return the element qualified name.""" namespace = self.ns_map.get(self.prefix) return build_qname(namespace, self.name) @dataclass class Dtd: """The DTD Document model representation. Args: location: The source location URI elements: The list of included elements """ location: str elements: list[DtdElement] tefra-xsdata-cf7afe8/xsdata/models/enums.py000066400000000000000000000235271526437326600211610ustar00rootroot00000000000000import sys from collections.abc import Callable from decimal import Decimal from enum import Enum from pathlib import Path from typing import Any, Final, Optional from xml.etree.ElementTree import QName from xsdata.models.datatype import ( XmlBase64Binary, XmlDate, XmlDateTime, XmlDuration, XmlHexBinary, XmlPeriod, XmlTime, ) COMMON_SCHEMA_DIR = Path(__file__).absolute().parent.parent.joinpath("schemas/") class Namespace(Enum): """Common namespaces.""" XS = ("http://www.w3.org/2001/XMLSchema", "xs") XML = ("http://www.w3.org/XML/1998/namespace", "xml") XSI = ("http://www.w3.org/2001/XMLSchema-instance", "xsi") MATHML = ("http://www.w3.org/1998/Math/MathML", "mathml3") XLINK = ("http://www.w3.org/1999/xlink", "xlink") XHTML = ("http://www.w3.org/1999/xhtml", "xhtml") SOAP11 = ("http://schemas.xmlsoap.org/wsdl/soap/", "soap") SOAP12 = ("http://schemas.xmlsoap.org/wsdl/soap12/", "soap12") SOAP_ENV = ("http://schemas.xmlsoap.org/soap/envelope/", "soapenv") SOAP_ENC = ("http://schemas.xmlsoap.org/soap/encoding/", "soapenc") def __init__(self, uri: str, prefix: str): """Initialize namespace.""" self.uri = uri self.prefix = prefix @property def location(self) -> str | None: """The location of the local file.""" local_path = COMMON_SCHEMA_DIR.joinpath(f"{self.prefix}.xsd") return local_path.as_uri() if local_path.exists() else None @classmethod def get_enum(cls, uri: str | None) -> Optional["Namespace"]: """Get the enum member instance from the uri.""" return __STANDARD_NAMESPACES__.get(uri) if uri else None @classmethod def common(cls) -> tuple["Namespace", ...]: """Return the common namespaces.""" return Namespace.XS, Namespace.XSI, Namespace.XML, Namespace.XLINK __STANDARD_NAMESPACES__ = {ns.uri: ns for ns in Namespace} class QNames: """Common qualified names.""" XSI_NIL = sys.intern(f"{{{Namespace.XSI.uri}}}nil") XSI_TYPE = sys.intern(f"{{{Namespace.XSI.uri}}}type") XSI_SCHEMA_LOCATION = sys.intern(f"{{{Namespace.XSI.uri}}}schemaLocation") XSI_NO_NAMESPACE_SCHEMA_LOCATION = sys.intern( f"{{{Namespace.XSI.uri}}}noNamespaceSchemaLocation" ) class NamespaceType: """Wildcard elements/attributes namespace types. Attributes: ANY_NS: elements from any namespace is allowed OTHER_NS: elements from any namespace except the parent element's namespace LOCAL_NS: elements must come from no namespace TARGET_NS: elements from the namespace of the parent element can be present """ ANY_NS = "##any" OTHER_NS = "##other" LOCAL_NS = "##local" TARGET_NS = "##targetNamespace" class FormType(Enum): """Element/Attribute form types.""" QUALIFIED = "qualified" UNQUALIFIED = "unqualified" class Mode(Enum): """OpenContent mode types.""" NONE = "none" SUFFIX = "suffix" INTERLEAVE = "interleave" class DataType(Enum): """Xml and Schema data types to native python.""" # Primitives STRING = ("string", str) BOOLEAN = ("boolean", bool) DECIMAL = ("decimal", Decimal) FLOAT = ("float", float) DOUBLE = ("double", float) DURATION = ("duration", XmlDuration) DATE_TIME = ("dateTime", XmlDateTime) TIME = ("time", XmlTime) DATE = ("date", XmlDate) G_YEAR_MONTH = ("gYearMonth", XmlPeriod) G_YEAR = ("gYear", XmlPeriod) G_MONTH_DAY = ("gMonthDay", XmlPeriod) G_MONTH = ("gMonth", XmlPeriod) G_DAY = ("gDay", XmlPeriod) HEX_BINARY = ("hexBinary", bytes, "base16", XmlHexBinary) BASE64_BINARY = ("base64Binary", bytes, "base64", XmlBase64Binary) ANY_URI = ("anyURI", str) QNAME = ("QName", QName) NOTATION = ("NOTATION", QName) # Derived strings NORMALIZED_STRING = ("normalizedString", str) TOKEN = ("token", str) LANGUAGE = ("language", str) NMTOKEN = ("NMTOKEN", str) NMTOKENS = ("NMTOKENS", str) NAME = ("Name", str) NCNAME = ("NCName", str) ID = ("ID", str) IDREF = ("IDREF", str) IDREFS = ("IDREFS", str) ENTITIES = ("ENTITIES", str) ENTITY = ("ENTITY", str) # Derived integers INTEGER = ("integer", int) NON_POSITIVE_INTEGER = ("nonPositiveInteger", int) NEGATIVE_INTEGER = ("negativeInteger", int) LONG = ("long", int) INT = ("int", int) SHORT = ("short", int) BYTE = ("byte", int) NON_NEGATIVE_INTEGER = ("nonNegativeInteger", int) UNSIGNED_LONG = ("unsignedLong", int) UNSIGNED_INT = ("unsignedInt", int) UNSIGNED_SHORT = ("unsignedShort", int) UNSIGNED_BYTE = ("unsignedByte", int) POSITIVE_INTEGER = ("positiveInteger", int) # Derived Date/Time/Duration DATE_TIMESTAMP = ("dateTimeStamp", XmlDateTime) DAY_TIME_DURATION = ("dayTimeDuration", XmlDuration) YEAR_MONTH_DURATION = ("yearMonthDuration", XmlDuration) # Extensions ANY_TYPE = ("anyType", object) ANY_ATOMIC_TYPE = ("anyAtomicType", str) ANY_SIMPLE_TYPE = ("anySimpleType", object) ERROR = ("error", str) def __init__( self, code: str, python_type: type, fmt: str | None = None, wrapper: type | None = None, ): """Initialize DataType.""" self.code = code self.type = python_type self.format = fmt self.wrapper = wrapper def __str__(self) -> str: """Return the qualified string representation of the datatype.""" return f"{{{Namespace.XS.uri}}}{self.code}" def prefixed(self, prefix: str | None = Namespace.XS.prefix) -> str: """Return the prefixed string representation of the datatype.""" return f"{prefix}:{self.code}" if prefix else self.code @classmethod def from_value(cls, value: Any) -> "DataType": """Load from a literal value.""" _type = type(value) calculate = __DataTypeInferIndex__.get(_type) if calculate: return calculate(value) return cls.from_type(_type) @classmethod def from_type(cls, tp: type) -> "DataType": """Load from a python type.""" return __DataTypeIndex__.get(tp, DataType.STRING) @classmethod def from_qname(cls, qname: str) -> Optional["DataType"]: """Load from a qualified name.""" return __DataTypeQNameIndex__.get(qname) @classmethod def from_code(cls, code: str) -> "DataType": """Load from the code name.""" return __DataTypeCodeIndex__.get(code, DataType.STRING) def period_datatype(value: XmlPeriod) -> DataType: """Infer the datatype of a xml period instance.""" if value.year is not None: return DataType.G_YEAR_MONTH if value.month else DataType.G_YEAR if value.month: return DataType.G_MONTH_DAY if value.day else DataType.G_MONTH return DataType.G_DAY def int_datatype(value: int) -> DataType: """Infer the datatype of an int value.""" if -32768 <= value <= 32767: return DataType.SHORT if -2147483648 <= value <= 2147483647: return DataType.INT if -9223372036854775808 <= value <= 9223372036854775807: return DataType.LONG return DataType.INTEGER def float_datatype(value: float) -> DataType: """Infer the datatype of a float value.""" if -1.175494351e-38 <= value <= 3.402823466e38: return DataType.FLOAT return DataType.DOUBLE __DataTypeIndex__ = { bool: DataType.BOOLEAN, int: DataType.INT, float: DataType.FLOAT, str: DataType.STRING, Decimal: DataType.DECIMAL, QName: DataType.QNAME, XmlDate: DataType.DATE, XmlTime: DataType.TIME, XmlDateTime: DataType.DATE_TIME, XmlDuration: DataType.DURATION, XmlPeriod: DataType.G_YEAR_MONTH, # bytes: DataType.HEX_BINARY || DataType.BASE64_BINARY, we can't infer formats XmlHexBinary: DataType.HEX_BINARY, XmlBase64Binary: DataType.BASE64_BINARY, } __DataTypeInferIndex__: dict[type, Callable] = { int: int_datatype, float: float_datatype, XmlPeriod: period_datatype, } __DataTypeQNameIndex__ = {str(dt): dt for dt in DataType} __DataTypeCodeIndex__ = {dt.code.lower(): dt for dt in DataType} class EventType: """XmlParsing event types.""" START: Final = "start" START_NS: Final = "start-ns" END: Final = "end" class Tag: """Xml Schema tags.""" ALL = "All" ANNOTATION = "Annotation" ANY = "Any" ANY_ATTRIBUTE = "AnyAttribute" APPINFO = "Appinfo" ASSERTION = "Assertion" ALTERNATIVE = "Alternative" ATTRIBUTE = "Attribute" ATTRIBUTE_GROUP = "AttributeGroup" CHOICE = "Choice" COMPLEX_CONTENT = "ComplexContent" COMPLEX_TYPE = "ComplexType" DOCUMENTATION = "Documentation" ELEMENT = "Element" EXTENSION = "Extension" FIELD = "Field" GROUP = "Group" IMPORT = "Import" INCLUDE = "Include" KEY = "Key" KEYREF = "Keyref" LIST = "List" NOTATION = "Notation" OVERRIDE = "Override" REDEFINE = "Redefine" RESTRICTION = "Restriction" SCHEMA = "Schema" SELECTOR = "Selector" SEQUENCE = "Sequence" SIMPLE_CONTENT = "SimpleContent" SIMPLE_TYPE = "SimpleType" UNION = "Union" UNIQUE = "Unique" # Restrictions ENUMERATION = "Enumeration" FRACTION_DIGITS = "FractionDigits" LENGTH = "Length" MAX_EXCLUSIVE = "MaxExclusive" MAX_INCLUSIVE = "MaxInclusive" MAX_LENGTH = "MaxLength" MIN_EXCLUSIVE = "MinExclusive" MIN_INCLUSIVE = "MinInclusive" MIN_LENGTH = "MinLength" PATTERN = "Pattern" TOTAL_DIGITS = "TotalDigits" WHITE_SPACE = "WhiteSpace" # Wsdl BINDING_OPERATION = "BindingOperation" BINDING_MESSAGE = "BindingMessage" MESSAGE = "Message" class UseType(Enum): """Attribute use types.""" OPTIONAL = "optional" PROHIBITED = "prohibited" REQUIRED = "required" class ProcessType(Enum): """Wildcard process types.""" LAX = "lax" SKIP = "skip" STRICT = "strict" tefra-xsdata-cf7afe8/xsdata/models/mixins.py000066400000000000000000000156121526437326600213350ustar00rootroot00000000000000from collections.abc import Callable, Iterator from dataclasses import dataclass, field, fields from typing import Any from xsdata.codegen.exceptions import CodegenError from xsdata.formats.dataclass.models.elements import XmlType from xsdata.models.enums import DataType, FormType, Namespace, NamespaceType from xsdata.utils import text from xsdata.utils.constants import return_true @dataclass class ElementBase: """Base xsd schema model representation. Attributes: index: The element position in the schema ns_map: The element namespace prefix-URI map """ index: int = field( default_factory=int, init=False, metadata={"type": "Ignore"}, ) ns_map: dict[str, str] = field( default_factory=dict, init=False, metadata={"type": "Ignore"}, ) @property def class_name(self) -> str: """Return the schema element class name.""" return self.__class__.__name__ @property def default_type(self) -> str: """The element's inferred default type qname.""" return DataType.STRING.prefixed(self.xs_prefix) @property def default_value(self) -> Any: """Return the default or the fixed attribute value.""" default = getattr(self, "default", None) if default is None and hasattr(self, "fixed"): default = getattr(self, "fixed", None) return default @property def display_help(self) -> str | None: """Return the display help for this element.""" return None @property def bases(self) -> Iterator[str]: """Return an iterator of all the base types.""" yield from () @property def has_children(self) -> bool: """Return whether this element has any children.""" return next(self.children(), None) is not None @property def has_form(self) -> bool: """Return whether this element has the form attribute.""" return hasattr(self, "form") @property def is_abstract(self) -> bool: """Return whether this element is defined as abstract.""" return getattr(self, "abstract", False) @property def is_property(self) -> bool: """Return whether this element is qualified to be a class property.""" return False @property def is_fixed(self) -> bool: """Return whether this element has a fixed value.""" return getattr(self, "fixed", None) is not None @property def is_mixed(self) -> bool: """Return whether this element accepts mixed content value.""" return False @property def is_nillable(self) -> bool: """Return whether this element accepts nillable content.""" return getattr(self, "nillable", False) @property def is_qualified(self) -> bool: """Return whether this element must be referenced with the target namespace.""" if self.has_form: if getattr(self, "form", FormType.UNQUALIFIED) == FormType.QUALIFIED: return True if self.is_ref: return True return False @property def is_ref(self) -> bool: """Return whether this element is a reference to another element.""" return getattr(self, "ref", None) is not None @property def is_wildcard(self) -> bool: """Return whether this element is a wildcard element/attribute.""" return False @property def prefix(self) -> str | None: """Return the namespace prefix for this element's type.""" ref = getattr(self, "ref", None) return None if ref is None else text.prefix(ref) @property def raw_namespace(self) -> str | None: """Return if present the target namespace attribute value.""" return getattr(self, "target_namespace", None) @property def real_name(self) -> str: """Return the real name for this element.""" name = getattr(self, "name", None) or getattr(self, "ref", None) if name: return text.suffix(name) raise CodegenError( "Schema type can't be used as a class/field", type=self.class_name ) @property def attr_types(self) -> Iterator[str]: """Return the attr types for this element.""" yield from () @property def substitutions(self) -> list[str]: """Return the substitution groups of this element.""" return [] @property def xs_prefix(self) -> str | None: """Return the xml schema URI prefix.""" for prefix, uri in self.ns_map.items(): if uri == Namespace.XS.uri: return prefix return None def get_restrictions(self) -> dict[str, Any]: """Return the restrictions dictionary of this element.""" return {} def children(self, condition: Callable = return_true) -> Iterator["ElementBase"]: """Yield the children recursively that match the given condition.""" for f in fields(self): value = getattr(self, f.name) if isinstance(value, list) and value and isinstance(value[0], ElementBase): yield from (val for val in value if condition(val)) elif isinstance(value, ElementBase) and condition(value): yield value def attribute(optional: bool = True, **kwargs: Any) -> Any: """Shortcut method for attribute fields.""" metadata = extract_metadata(kwargs, type=XmlType.ATTRIBUTE) add_default_value(kwargs, optional=optional) return field(metadata=metadata, **kwargs) def element(optional: bool = True, **kwargs: Any) -> Any: """Shortcut method for element fields.""" metadata = extract_metadata(kwargs, type=XmlType.ELEMENT) add_default_value(kwargs, optional=optional) return field(metadata=metadata, **kwargs) def add_default_value(params: dict, optional: bool) -> None: """Add the default value if it's missing and the optional flag is true.""" if optional and not ("default" in params or "default_factory" in params): params["default"] = None def array_element(**kwargs: Any) -> Any: """Shortcut method for list element fields.""" metadata = extract_metadata(kwargs, type=XmlType.ELEMENT) return field(metadata=metadata, default_factory=list, **kwargs) def array_any_element(**kwargs: Any) -> Any: """Shortcut method for list wildcard fields.""" metadata = extract_metadata( kwargs, type=XmlType.WILDCARD, namespace=NamespaceType.ANY_NS ) return field(metadata=metadata, default_factory=list, **kwargs) def extract_metadata(params: dict, **kwargs: Any) -> dict: """Remove dataclasses standard field properties and merge any additional.""" metadata = { key: params.pop(key) for key in list(params.keys()) if key not in FIELD_PARAMS } metadata.update(kwargs) return metadata FIELD_PARAMS = ( "default", "default_factory", "init", "repr", "hash", "compare", ) tefra-xsdata-cf7afe8/xsdata/models/wsdl.py000066400000000000000000000210231526437326600207700ustar00rootroot00000000000000from collections.abc import Iterator from dataclasses import dataclass, field from typing import TypeVar from xsdata.codegen.exceptions import CodegenError from xsdata.codegen.models import get_name from xsdata.formats.dataclass.models.generics import AnyElement from xsdata.models.enums import Namespace from xsdata.models.mixins import array_any_element, array_element, attribute, element from xsdata.models.xsd import Schema from xsdata.utils import collections @dataclass class Documentation: """WSDL Documentation model representation. Args: elements: A list of generic any elements """ elements: list[object] = array_any_element() @dataclass class WsdlElement: """WSDL Base element model representation. Args: name: The element name documentation: The element documentation """ name: str = attribute() documentation: Documentation | None = element() location: str | None = field(default=None, metadata={"type": "Ignore"}) ns_map: dict[str, str] = field( default_factory=dict, init=False, metadata={"type": "Ignore"} ) @dataclass class ExtensibleElement(WsdlElement): """WSDL Extensible element model representation. Args: name: The element name documentation: The element documentation extended: A list of generic elements """ extended: list[object] = array_any_element() @property def extended_elements(self) -> Iterator[AnyElement]: """Yields all generic element instances.""" yield from (ext for ext in self.extended if isinstance(ext, AnyElement)) @dataclass class Types: """WSDL Types model representation. Args: schemas: Inline xml schema definitions documentation: The type documentation """ schemas: list[Schema] = array_element(name="schema", namespace=Namespace.XS.uri) documentation: Documentation | None = element() @dataclass class Import: """WSDL Import model representation. Args: location: The location URI namespace: The namespace URI """ location: str | None = attribute() namespace: str | None = attribute() @dataclass class Part(WsdlElement): """WSDL Part model representation. Args: name: The part name documentation: The part documentation type: The part type element: The part element """ type: str | None = attribute() element: str | None = attribute() @dataclass class Message(WsdlElement): """WSDL Message model representation. Args: name: The message name documentation: The message documentation parts: The message parts """ parts: list[Part] = array_element(name="part") @dataclass class PortTypeMessage(WsdlElement): """WSDL Port type message model representation. Args: name: The port type name documentation: The port type documentation message: The port type message """ message: str = attribute() @dataclass class PortTypeOperation(WsdlElement): """WSDL Port type operation model representation. Args: input: The input port type message instance output: The output port type message instance faults: The list of error port type message instances """ input: PortTypeMessage = element() output: PortTypeMessage = element() faults: list[PortTypeMessage] = array_element(name="fault") @dataclass class PortType(ExtensibleElement): """WSDL Port type model representation. Args: name: The port type name documentation: The port type documentation extended: The port type extended elements operations: The port type operations """ operations: list[PortTypeOperation] = array_element(name="operation") def find_operation(self, name: str) -> PortTypeOperation: """Find an operation by name or raise an error.""" return find_or_die(self.operations, name, "PortTypeOperation") @dataclass class BindingMessage(ExtensibleElement): """WSDL Binding message model representation. Args: name: The message name documentation: The message documentation extended: The message extended elements """ @dataclass class BindingOperation(ExtensibleElement): """WSDL Binding operation model representation. Args: input: The input binding message instance output: The output binding message instance faults: The list of error binding message instances name: The operation name documentation: The operation documentation extended: The operation extended elements """ input: BindingMessage = element() output: BindingMessage = element() faults: list[BindingMessage] = array_element(name="fault") @dataclass class Binding(ExtensibleElement): """WSDL Binding model representation. Args: name: The binding name documentation: The binding documentation extended: The binding extended elements type: The binding type operations: The binding operations """ type: str = attribute() operations: list[BindingOperation] = array_element(name="operation") def unique_operations(self) -> Iterator[BindingOperation]: """Yields all unique operation instances.""" grouped_operations = collections.group_by(self.operations, key=get_name) for operations in grouped_operations.values(): yield operations[-1] @dataclass class ServicePort(ExtensibleElement): """WSDL Service port model representation. Args: name: The port name documentation: The port documentation extended: The port extended elements binding: The port binding """ binding: str = attribute() @dataclass class Service(WsdlElement): """WSDL Service model representation. Args: name: The service name documentation: The service documentation ports: The service ports """ ports: list[ServicePort] = array_element(name="port") @dataclass class Definitions(ExtensibleElement): """WSDL Definitions model representation. Args: name: The definition name documentation: The definition documentation extended: A list of generic elements types: The definition types imports: The definition imports messages: The definition messages port_types: The definition port types bindings: The definition bindings services: The definition services """ class Meta: """Metadata options.""" name = "definitions" namespace = "http://schemas.xmlsoap.org/wsdl/" target_namespace: str | None = attribute(name="targetNamespace") types: Types | None = element() imports: list[Import] = array_element(name="import") messages: list[Message] = array_element(name="message") port_types: list[PortType] = array_element(name="portType") bindings: list[Binding] = array_element(name="binding") services: list[Service] = array_element(name="service") @property def schemas(self) -> Iterator[Schema]: """Yield all schema definitions.""" if self.types: yield from self.types.schemas def find_binding(self, name: str) -> Binding: """Find a binding by name or raise an error.""" return find_or_die(self.bindings, name, "Binding") def find_message(self, name: str) -> Message: """Find a message by name or raise an error.""" return find_or_die(self.messages, name, "Message") def find_port_type(self, name: str) -> PortType: """Find a port type by name or raise an error.""" return find_or_die(self.port_types, name, "PortType") def merge(self, source: "Definitions") -> None: """Merge the source instance with this instance.""" if not self.types: self.types = source.types elif source.types: self.types.schemas.extend(source.types.schemas) self.messages.extend(source.messages) self.port_types.extend(source.port_types) self.bindings.extend(source.bindings) self.services.extend(source.services) self.extended.extend(source.extended) def included(self) -> Iterator[Import]: """Yield all imports.""" yield from self.imports T = TypeVar("T", bound=WsdlElement) def find_or_die(items: list[T], name: str, type_name: str) -> T: """Find an item by name or raise an error.""" for msg in items: if msg.name == name: return msg raise CodegenError("Unknown WSDL Type", type=type_name, name=name) tefra-xsdata-cf7afe8/xsdata/models/xsd.py000066400000000000000000000777301526437326600206350ustar00rootroot00000000000000import sys import textwrap from collections.abc import Iterator from dataclasses import dataclass, field from typing import Any as Anything from typing import Optional from xsdata.formats.dataclass.serializers import XmlSerializer from xsdata.formats.dataclass.serializers.config import SerializerConfig from xsdata.models.enums import ( DataType, FormType, Mode, Namespace, ProcessType, UseType, ) from xsdata.models.mixins import ( ElementBase, array_any_element, array_element, attribute, element, ) from xsdata.utils import text from xsdata.utils.collections import unique_sequence from xsdata.utils.constants import DEFAULT_ATTR_NAME from xsdata.utils.namespaces import clean_uri docstring_serializer = XmlSerializer( config=SerializerConfig(indent=" ", xml_declaration=False) ) def validate_max_occurs(min_occurs: int, max_occurs: str | int) -> int: """Validate max occurs.""" if max_occurs == "unbounded": max_occurs = sys.maxsize assert isinstance(max_occurs, int) return max(max_occurs, min_occurs) @dataclass(frozen=True) class Docstring: """Docstring model representation. Args: content: A list of mixed content elements """ class Meta: """Metadata options.""" namespace = "http://www.w3.org/1999/xhtml" content: list[object] = array_any_element() @dataclass class Documentation(ElementBase): """XSD MinLength model representation.""" lang: str | None = attribute() source: str | None = attribute() attributes: Optional["AnyAttribute"] = element() content: list[object] = array_any_element(mixed=True) def tostring(self) -> str | None: """Convert the content to a help string.""" obj = Docstring(self.content) ns_map = {None: "http://www.w3.org/1999/xhtml"} xml = docstring_serializer.render(obj, ns_map=ns_map) start = xml.find(">") + 1 end = xml.rfind("<") return textwrap.dedent(xml[start:end]).strip() @dataclass class Appinfo(ElementBase): """XSD Appinfo model representation.""" class Meta: """Metadata options.""" mixed = True source: str | None = attribute() any_attribute: Optional["AnyAttribute"] = element(name="anyAttribute") content: list[object] = array_any_element(mixed=True) @dataclass class Annotation(ElementBase): """XSD Annotation model representation.""" app_infos: list[Appinfo] = array_element(name="appinfo") documentations: list[Documentation] = array_element(name="documentation") any_attribute: Optional["AnyAttribute"] = element(name="anyAttribute") @dataclass class AnnotationBase(ElementBase): """XSD AnnotationBase model representation.""" id: str | None = attribute() annotations: list[Annotation] = array_element(name="annotation") any_attribute: Optional["AnyAttribute"] = element(name="anyAttribute") @property def display_help(self) -> str | None: """Return all annotation documentations concatenated.""" help_str = "\n".join( documentation.tostring() or "" for annotation in self.annotations for documentation in annotation.documentations ).strip() return help_str or None @dataclass class AnyAttribute(AnnotationBase): """XSD AnyAttribute model representation.""" namespace: str = attribute(default="##any") process_contents: ProcessType | None = attribute( name="processContents", default="strict" ) def __post_init__(self): """Post initialization validations.""" self.namespace = " ".join(unique_sequence(self.namespace.split())) @property def is_property(self) -> bool: """Specify it is qualified to be a class property.""" return True @property def raw_namespace(self) -> str | None: """The element explicit namespace.""" return self.namespace @property def real_name(self) -> str: """Return the real name for this element.""" clean_ns = "_".join(map(clean_uri, self.namespace.split())) return f"@{clean_ns}_attributes" @property def attr_types(self) -> Iterator[str]: """Yields the attr types for this element.""" yield DataType.ANY_TYPE.prefixed(self.xs_prefix) @dataclass class Assertion(AnnotationBase): """XSD Assertion model representation.""" test: str | None = attribute() @dataclass class SimpleType(AnnotationBase): """XSD SimpleType model representation.""" name: str | None = attribute() restriction: Optional["Restriction"] = element() list: Optional["List"] = element() union: Optional["Union"] = element() @property def is_property(self) -> bool: """Specify it is qualified to be a class property.""" return True @property def is_enumeration(self) -> bool: """Return whether it is an enumeration restriction.""" return self.restriction is not None and len(self.restriction.enumerations) > 0 @property def real_name(self) -> str: """Return the real name for this element.""" if self.name: return self.name return DEFAULT_ATTR_NAME @property def attr_types(self) -> Iterator[str]: """Return the attr types for this element.""" if not self.is_enumeration and self.restriction: yield from self.restriction.attr_types elif self.list: yield from self.list.attr_types elif self.union: yield from self.union.bases def get_restrictions(self) -> dict[str, Anything]: """Return the restrictions dictionary of this element.""" if self.restriction: return self.restriction.get_restrictions() if self.list: return self.list.get_restrictions() return {} @dataclass class List(AnnotationBase): """XSD List model representation.""" simple_type: SimpleType | None = element(name="simpleType") item_type: str = attribute(name="itemType", default="") @property def is_property(self) -> bool: """Specify it is qualified to be a class property.""" return True @property def real_name(self) -> str: """Return the real name for this element.""" return DEFAULT_ATTR_NAME @property def attr_types(self) -> Iterator[str]: """Return the attr types for this element.""" if self.item_type: yield self.item_type def get_restrictions(self) -> dict[str, Anything]: """Return the restrictions dictionary of this element.""" return {"tokens": True} @dataclass class Union(AnnotationBase): """XSD Union model representation.""" member_types: str | None = attribute(name="memberTypes") simple_types: list[SimpleType] = array_element(name="simpleType") @property def bases(self) -> Iterator[str]: """Return an iterator of all the base types.""" if self.member_types: yield from self.member_types.split() @property def is_property(self) -> bool: """Specify it is qualified to be a class property.""" return True @property def real_name(self) -> str: """Return the real name for this element.""" return DEFAULT_ATTR_NAME @property def attr_types(self) -> Iterator[str]: """Return the attr types for this element.""" for simple_type in self.simple_types: yield from simple_type.attr_types if self.member_types: yield from self.member_types.split() def get_restrictions(self) -> dict[str, Anything]: """Return the restrictions dictionary of this element.""" restrictions = {} for simple_type in self.simple_types: restrictions.update(simple_type.get_restrictions()) return restrictions @dataclass class Attribute(AnnotationBase): """XSD Attribute model representation.""" default: str | None = attribute() fixed: str | None = attribute() form: FormType | None = attribute() name: str | None = attribute() ref: str | None = attribute() type: str | None = attribute() target_namespace: str | None = attribute(name="targetNamespace") simple_type: SimpleType | None = element(name="simpleType") use: UseType | None = attribute(default=UseType.OPTIONAL) @property def bases(self) -> Iterator[str]: """Return an iterator of all the base types.""" if self.type: yield self.type elif not self.has_children: yield DataType.STRING.prefixed(self.xs_prefix) @property def is_property(self) -> bool: """Specify it is qualified to be a class property.""" return True @property def attr_types(self) -> Iterator[str]: """Return the attr types for this element.""" if self.simple_type: yield from self.simple_type.attr_types elif self.type: yield self.type elif self.ref: yield self.ref @property def default_type(self) -> str: """Returned the inferred default type qname.""" datatype = DataType.STRING if self.fixed else DataType.ANY_SIMPLE_TYPE return datatype.prefixed(self.xs_prefix) def get_restrictions(self) -> dict[str, Anything]: """Return the restrictions dictionary of this element.""" if self.use == UseType.REQUIRED: restrictions = {"min_occurs": 1, "max_occurs": 1} elif self.use == UseType.PROHIBITED: restrictions = {"max_occurs": 0, "min_occurs": 0} else: restrictions = {"max_occurs": 1, "min_occurs": 0} if self.simple_type: restrictions.update(self.simple_type.get_restrictions()) return restrictions @dataclass class AttributeGroup(AnnotationBase): """XSD AttributeGroup model representation.""" ref: str = attribute(default="") name: str | None = attribute() attributes: list[Attribute] = array_element(name="attribute") attribute_groups: list["AttributeGroup"] = array_element(name="attributeGroup") @property def is_property(self) -> bool: """Specify it is qualified to be a class property.""" return True @property def attr_types(self) -> Iterator[str]: """Return the attr types for this element.""" if self.ref: yield self.ref @dataclass class Any(AnnotationBase): """XSD Any model representation.""" namespace: str = attribute(default="##any") min_occurs: int = attribute(default=1, name="minOccurs") max_occurs: str | int = attribute(default=1, name="maxOccurs") process_contents: ProcessType = attribute( default=ProcessType.STRICT, name="processContents" ) def __post_init__(self): """Post initialization validations.""" self.namespace = " ".join(unique_sequence(self.namespace.split())) self.max_occurs = validate_max_occurs(self.min_occurs, self.max_occurs) @property def is_property(self) -> bool: """Specify it is qualified to be a class property.""" return True @property def real_name(self) -> str: """Return the real name for this element.""" clean_ns = "_".join(map(clean_uri, self.namespace.split())) return f"@{clean_ns}_element" @property def raw_namespace(self) -> str | None: """The element explicit namespace.""" return self.namespace @property def attr_types(self) -> Iterator[str]: """Return the attr types for this element.""" yield DataType.ANY_TYPE.prefixed(self.xs_prefix) def get_restrictions(self) -> dict[str, Anything]: """Return the restrictions dictionary of this element.""" max_occurs = sys.maxsize if self.max_occurs == "unbounded" else self.max_occurs return { "min_occurs": 0, "max_occurs": max_occurs, "process_contents": self.process_contents.value, } @dataclass class All(AnnotationBase): """XSD All model representation.""" min_occurs: int = attribute(default=1, name="minOccurs") max_occurs: str | int = attribute(default=1, name="maxOccurs") any: list[Any] = array_element(name="any") elements: list["Element"] = array_element(name="element") groups: list["Group"] = array_element(name="group") def __post_init__(self): """Post initialization validations.""" self.max_occurs = validate_max_occurs(self.min_occurs, self.max_occurs) def get_restrictions(self) -> dict[str, Anything]: """Return the restrictions dictionary of this element.""" return { "path": [("a", id(self), self.min_occurs, self.max_occurs)], } @dataclass class Sequence(AnnotationBase): """XSD Sequence model representation.""" min_occurs: int = attribute(default=1, name="minOccurs") max_occurs: str | int = attribute(default=1, name="maxOccurs") elements: list["Element"] = array_element(name="element") groups: list["Group"] = array_element(name="group") choices: list["Choice"] = array_element(name="choice") sequences: list["Sequence"] = array_element(name="sequence") any: list["Any"] = array_element() def __post_init__(self): """Post initialization validations.""" self.max_occurs = validate_max_occurs(self.min_occurs, self.max_occurs) def get_restrictions(self) -> dict[str, Anything]: """Return the restrictions dictionary of this element.""" return { "path": [("s", id(self), self.min_occurs, self.max_occurs)], } @dataclass class Choice(AnnotationBase): """XSD Choice model representation.""" min_occurs: int = attribute(default=1, name="minOccurs") max_occurs: str | int = attribute(default=1, name="maxOccurs") elements: list["Element"] = array_element(name="element") groups: list["Group"] = array_element(name="group") choices: list["Choice"] = array_element(name="choice") sequences: list[Sequence] = array_element(name="sequence") any: list["Any"] = array_element() def __post_init__(self): """Post initialization validations.""" self.max_occurs = validate_max_occurs(self.min_occurs, self.max_occurs) def get_restrictions(self) -> dict[str, Anything]: """Return the restrictions dictionary of this element.""" return { "path": [("c", id(self), self.min_occurs, self.max_occurs)], } @dataclass class Group(AnnotationBase): """XSD Group model representation.""" name: str | None = attribute() ref: str = attribute(default="") min_occurs: int = attribute(default=1, name="minOccurs") max_occurs: str | int = attribute(default=1, name="maxOccurs") all: All | None = element() choice: Choice | None = element() sequence: Sequence | None = element() def __post_init__(self): """Post initialization validations.""" self.max_occurs = validate_max_occurs(self.min_occurs, self.max_occurs) @property def is_property(self) -> bool: """Specify it is qualified to be a class property.""" return True @property def attr_types(self) -> Iterator[str]: """Return the attr types for this element.""" if self.ref: yield self.ref def get_restrictions(self) -> dict[str, Anything]: """Return the restrictions dictionary of this element.""" return { "path": [("g", id(self), self.min_occurs, self.max_occurs)], } @dataclass class OpenContent(AnnotationBase): """XSD OpenContent model representation.""" applies_to_empty: bool = attribute(default=False, name="appliesToEmpty") mode: Mode = attribute(default=Mode.INTERLEAVE) any: Any = element() @dataclass class DefaultOpenContent(OpenContent): """XSD DefaultOpenContent model representation.""" @dataclass class Extension(AnnotationBase): """XSD Extension model representation.""" base: str | None = attribute() group: Group | None = element() all: All | None = element() choice: Choice | None = element() sequence: Sequence | None = element() any_attribute: AnyAttribute | None = element(name="anyAttribute") open_content: OpenContent | None = element(name="openContent") attributes: list[Attribute] = array_element(name="attribute") attribute_groups: list[AttributeGroup] = array_element(name="attributeGroup") assertions: list[Assertion] = array_element(name="assert") @property def bases(self) -> Iterator[str]: """Return an iterator of all the base types.""" if self.base: yield self.base @dataclass class Enumeration(AnnotationBase): """XSD Enumeration model representation.""" value: str = attribute() @property def is_property(self) -> bool: """Specify it is qualified to be a class property.""" return True @property def real_name(self) -> str: """Return the enumeration value as its name.""" return self.value @property def default(self) -> str: """Return the enumeration value as its default value.""" return self.value @property def is_fixed(self) -> bool: """Specify this element has a fixed value.""" return True @dataclass class FractionDigits(AnnotationBase): """XSD FractionDigits model representation.""" value: int = attribute() @dataclass class Length(AnnotationBase): """XSD Length model representation.""" value: int = attribute() @dataclass class MaxExclusive(AnnotationBase): """XSD MaxExclusive model representation.""" value: str = attribute() @dataclass class MaxInclusive(AnnotationBase): """XSD MaxInclusive model representation.""" value: str = attribute() @dataclass class MaxLength(AnnotationBase): """XSD MaxLength model representation.""" value: int = attribute() @dataclass class MinExclusive(AnnotationBase): """XSD MinExclusive model representation.""" value: str = attribute() @dataclass class MinInclusive(AnnotationBase): """XSD MinInclusive model representation.""" value: str = attribute() @dataclass class MinLength(AnnotationBase): """XSD MinLength model representation.""" value: int = attribute() @dataclass class Pattern(AnnotationBase): """XSD Pattern model representation.""" value: str = attribute() @dataclass class TotalDigits(AnnotationBase): """XSD TotalDigits model representation.""" value: int = attribute() @dataclass class WhiteSpace(AnnotationBase): """XSD WhiteSpace model representation.""" value: str = attribute() @dataclass class ExplicitTimezone(AnnotationBase): """XSD ExplicitTimezone model representation.""" value: str = attribute() fixed: bool = attribute(default=False) @dataclass class Restriction(AnnotationBase): """XSD Restriction model representation.""" base: str | None = attribute() group: Group | None = element() all: All | None = element() choice: Choice | None = element() sequence: Sequence | None = element() open_content: OpenContent | None = element(name="openContent") attributes: list[Attribute] = array_element(name="attribute") attribute_groups: list[AttributeGroup] = array_element(name="attributeGroup") enumerations: list[Enumeration] = array_element(name="enumeration") asserts: list[Assertion] = array_element(name="assert") assertions: list[Assertion] = array_element(name="assertion") any_element: list[object] = array_any_element() min_exclusive: MinExclusive | None = element(name="minExclusive") min_inclusive: MinInclusive | None = element(name="minInclusive") min_length: MinLength | None = element(name="minLength") max_exclusive: MaxExclusive | None = element(name="maxExclusive") max_inclusive: MaxInclusive | None = element(name="maxInclusive") max_length: MaxLength | None = element(name="maxLength") total_digits: TotalDigits | None = element(name="totalDigits") fraction_digits: FractionDigits | None = element(name="fractionDigits") length: Length | None = element() white_space: WhiteSpace | None = element(name="whiteSpace") patterns: list[Pattern] = array_element(name="pattern") explicit_timezone: ExplicitTimezone | None = element(name="explicitTimezone") simple_type: SimpleType | None = element(name="simpleType") @property def attr_types(self) -> Iterator[str]: """Return the attr types for this element.""" if self.simple_type: yield from self.simple_type.attr_types elif self.base and not self.enumerations: yield self.base @property def real_name(self) -> str: """Return the real name for this element.""" return DEFAULT_ATTR_NAME @property def bases(self) -> Iterator[str]: """Return an iterator of all the base types.""" if self.base: yield self.base def get_restrictions(self) -> dict[str, Anything]: """Return the restrictions dictionary of this element.""" restrictions = {} if self.simple_type: restrictions.update(self.simple_type.get_restrictions()) keys = ( "min_exclusive", "min_inclusive", "min_length", "max_exclusive", "max_inclusive", "max_length", "total_digits", "fraction_digits", "length", "white_space", "explicit_timezone", ) restrictions.update( { key: getattr(self, key).value for key in keys if getattr(self, key) is not None } ) if self.patterns: restrictions["pattern"] = "|".join( pattern.value for pattern in self.patterns ) return restrictions @dataclass class SimpleContent(AnnotationBase): """XSD SimpleContent model representation.""" restriction: Restriction | None = element() extension: Extension | None = element() @dataclass class ComplexContent(SimpleContent): """XSD ComplexContent model representation.""" mixed: bool = attribute(default=False) @dataclass class ComplexType(AnnotationBase): """XSD ComplexType model representation.""" name: str | None = attribute() block: str | None = attribute() final: str | None = attribute() simple_content: SimpleContent | None = element(name="simpleContent") complex_content: ComplexContent | None = element(name="complexContent") group: Group | None = element() all: All | None = element() choice: Choice | None = element() sequence: Sequence | None = element() any_attribute: AnyAttribute | None = element(name="anyAttribute") open_content: OpenContent | None = element(name="openContent") attributes: list[Attribute] = array_element(name="attribute") attribute_groups: list[AttributeGroup] = array_element(name="attributeGroup") assertion: list[Assertion] = array_element(name="assert") abstract: bool = attribute(default=False) mixed: bool = attribute(default=False) default_attributes_apply: bool = attribute( default=True, name="defaultAttributesApply" ) @property def is_mixed(self) -> bool: """Return whether this element accepts mixed content value.""" if self.mixed: return True if self.complex_content: return self.complex_content.mixed return False @dataclass class Field(AnnotationBase): """XSD Field model representation.""" xpath: str | None = attribute() @dataclass class Selector(Field): """XSD Selector model representation.""" @dataclass class Unique(AnnotationBase): """XSD Unique model representation.""" name: str | None = attribute() ref: str | None = attribute() selector: Selector | None = element() fields: list[Field] = array_element(name="field") @dataclass class Key(AnnotationBase): """XSD Key model representation.""" name: str | None = attribute() ref: str | None = attribute() selector: Selector | None = element() fields: list[Selector] = array_element(name="field") @dataclass class Keyref(AnnotationBase): """XSD Keyref model representation.""" name: str | None = attribute() ref: str | None = attribute() refer: str | None = attribute() selector: Selector | None = element() fields: list[Selector] = array_element(name="field") @dataclass class Alternative(AnnotationBase): """XSD Alternative model representation.""" type: str | None = attribute() test: str | None = attribute() simple_type: SimpleType | None = element(name="simpleType") complex_type: ComplexType | None = element(name="complexType") @property def real_name(self) -> str: """Return the real name for this element.""" if self.test: return text.snake_case(self.test) if self.id: return self.id return DEFAULT_ATTR_NAME @property def bases(self) -> Iterator[str]: """Return an iterator of all the base types.""" if self.type: yield self.type def get_restrictions(self) -> dict[str, Anything]: """Return the restrictions dictionary of this element.""" return { "path": [("alt", id(self), 0, 1)], } @dataclass class Element(AnnotationBase): """XSD Element model representation.""" name: str | None = attribute() ref: str | None = attribute() type: str | None = attribute() substitution_group: str | None = attribute(name="substitutionGroup") default: str | None = attribute() fixed: str | None = attribute() form: FormType | None = attribute() block: str | None = attribute() final: str | None = attribute() target_namespace: str | None = attribute(name="targetNamespace") simple_type: SimpleType | None = element(name="simpleType") complex_type: ComplexType | None = element(name="complexType") alternatives: list[Alternative] = array_element(name="alternative") uniques: list[Unique] = array_element(name="unique") keys: list[Key] = array_element(name="key") keyrefs: list[Keyref] = array_element(name="keyref") min_occurs: int = attribute(default=1, name="minOccurs") max_occurs: str | int = attribute(default=1, name="maxOccurs") nillable: bool = attribute(default=False) abstract: bool = attribute(default=False) def __post_init__(self): """Post initialization validations.""" self.max_occurs = validate_max_occurs(self.min_occurs, self.max_occurs) @property def bases(self) -> Iterator[str]: """Return an iterator of all the base types.""" if self.type: yield self.type elif not self.has_children: yield DataType.ANY_TYPE.prefixed(self.xs_prefix) @property def is_property(self) -> bool: """Specify it is qualified to be a class property.""" return True @property def is_mixed(self) -> bool: """Return whether this element accepts mixed content value.""" return self.complex_type.is_mixed if self.complex_type else False @property def default_type(self) -> str: """Returned the inferred default type qname.""" datatype = DataType.STRING if self.fixed else DataType.ANY_TYPE return datatype.prefixed(self.xs_prefix) @property def attr_types(self) -> Iterator[str]: """Return the attr types for this element.""" if self.type: yield self.type elif self.ref: yield self.ref elif self.simple_type: yield from self.simple_type.attr_types yield from (alt.type for alt in self.alternatives if alt.type) @property def substitutions(self) -> list[str]: """Return a list of the substitution groups.""" return self.substitution_group.split() if self.substitution_group else [] def get_restrictions(self) -> dict[str, Anything]: """Return the restrictions dictionary of this element.""" restrictions = { "min_occurs": self.min_occurs, "max_occurs": self.max_occurs, } if self.simple_type: restrictions.update(self.simple_type.get_restrictions()) if self.nillable: restrictions.update(nillable=True) return restrictions @dataclass class Notation(AnnotationBase): """XSD Notation model representation.""" name: str | None = attribute() public: str | None = attribute() system: str | None = attribute() @dataclass class Import(AnnotationBase): """XSD Import model representation.""" namespace: str | None = attribute() schema_location: str | None = attribute(name="schemaLocation") location: str | None = field(default=None, metadata={"type": "Ignore"}) @dataclass class Include(AnnotationBase): """XSD Include model representation.""" schema_location: str | None = attribute(name="schemaLocation") location: str | None = field(default=None, metadata={"type": "Ignore"}) @dataclass class Redefine(AnnotationBase): """XSD Redefine model representation.""" schema_location: str | None = attribute(name="schemaLocation") simple_types: list[SimpleType] = array_element(name="simpleType") complex_types: list[ComplexType] = array_element(name="complexType") groups: list[Group] = array_element(name="group") attribute_groups: list[AttributeGroup] = array_element(name="attributeGroup") location: str | None = field(default=None, metadata={"type": "Ignore"}) @dataclass class Override(AnnotationBase): """XSD Override model representation.""" schema_location: str | None = attribute(name="schemaLocation") simple_types: list[SimpleType] = array_element(name="simpleType") complex_types: list[ComplexType] = array_element(name="complexType") groups: list[Group] = array_element(name="group") attribute_groups: list[AttributeGroup] = array_element(name="attributeGroup") elements: list[Element] = array_element(name="element") attributes: list[Attribute] = array_element(name="attribute") notations: list[Notation] = array_element(name="notation") location: str | None = field(default=None, metadata={"type": "Ignore"}) @dataclass class Schema(AnnotationBase): """XSD Schema model representation.""" class Meta: """Metadata options.""" name = "schema" namespace = Namespace.XS.uri target: str | None = attribute() block_default: str | None = attribute(name="blockDefault") default_attributes: str | None = attribute(name="defaultAttributes") final_default: str | None = attribute(name="finalDefault") target_namespace: str | None = attribute(name="targetNamespace") version: str | None = attribute() xmlns: str | None = attribute() element_form_default: FormType = attribute( default=FormType.UNQUALIFIED, name="elementFormDefault", ) attribute_form_default: FormType = attribute( default=FormType.UNQUALIFIED, name="attributeFormDefault", ) default_open_content: DefaultOpenContent | None = element(name="defaultOpenContent") includes: list[Include] = array_element(name="include") imports: list[Import] = array_element(name="import") redefines: list[Redefine] = array_element(name="redefine") overrides: list[Override] = array_element(name="override") annotations: list[Annotation] = array_element(name="annotation") simple_types: list[SimpleType] = array_element(name="simpleType") complex_types: list[ComplexType] = array_element(name="complexType") groups: list[Group] = array_element(name="group") attribute_groups: list[AttributeGroup] = array_element(name="attributeGroup") elements: list[Element] = array_element(name="element") attributes: list[Attribute] = array_element(name="attribute") notations: list[Notation] = array_element(name="notation") location: str | None = field(default=None, metadata={"type": "Ignore"}) def included(self) -> Iterator[Import | Include | Redefine | Override]: """Yields an iterator of included resources.""" yield from self.imports yield from self.includes yield from self.redefines yield from self.overrides tefra-xsdata-cf7afe8/xsdata/py.typed000066400000000000000000000000001526437326600176510ustar00rootroot00000000000000tefra-xsdata-cf7afe8/xsdata/schemas/000077500000000000000000000000001526437326600176075ustar00rootroot00000000000000tefra-xsdata-cf7afe8/xsdata/schemas/LICENSE.W3C000066400000000000000000000056451526437326600212210ustar00rootroot00000000000000The following files in this directory are copied from or derived from W3C documents and are distributed under the W3C Software and Document License. File Source Changes mathml3.xsd https://www.w3.org/Math/XMLSchema/mathml3-xsd.zip none mathml3-common.xsd https://www.w3.org/Math/XMLSchema/mathml3-xsd.zip none mathml3-content.xsd https://www.w3.org/Math/XMLSchema/mathml3-xsd.zip none mathml3-presentation.xsd https://www.w3.org/Math/XMLSchema/mathml3-xsd.zip none mathml3-strict-content.xsd https://www.w3.org/Math/XMLSchema/mathml3-xsd.zip none xlink.xsd https://www.w3.org/1999/xlink.xsd none xml.xsd https://www.w3.org/2001/xml.xsd documentation removed, reformatted soapenc.xsd http://www.w3.org/2001/06/soap-encoding see the notice in the file Each file carries its own copyright notice and list of changes. W3C Software and Document License https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document This work is being provided by the copyright holders under the following license. License By obtaining and/or copying this work, you (the licensee) agree that you have read, understood, and will comply with the following terms and conditions. Permission to copy, modify, and distribute this work, with or without modification, for any purpose and without fee or royalty is hereby granted, provided that you include the following on ALL copies of the work or portions thereof, including modifications: - The full text of this NOTICE in a location viewable to users of the redistributed or derivative work. - Any pre-existing intellectual property disclaimers, notices, or terms and conditions. If none exist, the W3C Software and Document Short Notice should be included. - Notice of any changes or modifications, through a copyright statement on the new code or document such as "This software or document includes material copied from or derived from [title and URI of the W3C document]. Copyright (c) [YEAR] W3C® (MIT, ERCIM, Keio, Beihang)." Disclaimers THIS WORK IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENT WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENT. The name and trademarks of copyright holders may NOT be used in advertising or publicity pertaining to the work without specific, written prior permission. Title to copyright in this work will at all times remain with copyright holders. tefra-xsdata-cf7afe8/xsdata/schemas/mathml3-common.xsd000066400000000000000000000111341526437326600231620ustar00rootroot00000000000000 tefra-xsdata-cf7afe8/xsdata/schemas/mathml3-content.xsd000066400000000000000000000706421526437326600233550ustar00rootroot00000000000000 tefra-xsdata-cf7afe8/xsdata/schemas/mathml3-presentation.xsd000066400000000000000000002454321526437326600244170ustar00rootroot00000000000000 tefra-xsdata-cf7afe8/xsdata/schemas/mathml3-strict-content.xsd000066400000000000000000000164671526437326600246700ustar00rootroot00000000000000 tefra-xsdata-cf7afe8/xsdata/schemas/mathml3.xsd000066400000000000000000000020551526437326600216760ustar00rootroot00000000000000 tefra-xsdata-cf7afe8/xsdata/schemas/soapenc.xsd000066400000000000000000000442461526437326600217710ustar00rootroot00000000000000 'root' can be used to distinguish serialization roots from other elements that are present in a serialization but are not roots of a serialized value graph Attributes common to all elements that function as accessors or represent independent (multi-ref) values. The href attribute is intended to be used in a manner like CONREF. That is, the element content should be empty iff the href attribute appears 'Array' is a complex type for accessors identified by position tefra-xsdata-cf7afe8/xsdata/schemas/xlink.xsd000066400000000000000000000207351526437326600214630ustar00rootroot00000000000000 This schema document provides attribute declarations and attribute group, complex type and simple type definitions which can be used in the construction of user schemas to define the structure of particular linking constructs, e.g. ... ... ... ]]> Intended for use as the type of user-declared elements to make them simple links. Intended for use as the type of user-declared elements to make them extended links. Note that the elements referenced in the content model are all abstract. The intention is that by simply declaring elements with these as their substitutionGroup, all the right things will happen. xml:lang is not required, but provides much of the motivation for title elements in addition to attributes, and so is provided here for convenience. label is not required, but locators have no particular XLink function if they are not labeled. from and to have default behavior when values are missing tefra-xsdata-cf7afe8/xsdata/schemas/xml.xsd000066400000000000000000000036001526437326600211260ustar00rootroot00000000000000 tefra-xsdata-cf7afe8/xsdata/schemas/xsi.xsd000066400000000000000000000010431526437326600211300ustar00rootroot00000000000000 tefra-xsdata-cf7afe8/xsdata/utils/000077500000000000000000000000001526437326600173245ustar00rootroot00000000000000tefra-xsdata-cf7afe8/xsdata/utils/__init__.py000066400000000000000000000000001526437326600214230ustar00rootroot00000000000000tefra-xsdata-cf7afe8/xsdata/utils/click.py000066400000000000000000000136051526437326600207700ustar00rootroot00000000000000import enum import inspect import logging from collections.abc import Callable, Iterator from dataclasses import fields, is_dataclass from typing import ( Any, ClassVar, TypeVar, get_type_hints, ) from urllib.parse import urlparse import click from click import Command, Context, Parameter from xsdata.codegen.writer import CodeWriter from xsdata.utils import text F = TypeVar("F", bound=Callable[..., Any]) FC = TypeVar("FC", Callable[..., Any], Command) def model_options(obj: Any) -> Callable[[FC], FC]: """Decorate click commands to add model options.""" def decorator(f: F) -> F: for option in reversed(list(build_options(obj, ""))): option(f) return f return decorator def build_options(obj: Any, parent: str) -> Iterator[Callable[[FC], FC]]: """Build click options by a data class.""" type_hints = get_type_hints(obj) doc_hints = get_doc_hints(obj) for field in fields(obj): type_hint = type_hints[field.name] doc_hint = doc_hints[field.name] name = field.metadata.get("cli", field.name) if not name: continue qname = f"{parent}.{field.name}".strip(".") if is_dataclass(type_hint): yield from build_options(type_hint, qname) else: is_flag = False opt_type = type_hint if name == "output": opt_type = click.Choice(CodeWriter.generators.keys()) names = ["-o", "--output"] elif type_hint is bool: is_flag = True opt_type = None name = text.kebab_case(name) names = [f"--{name}/--no-{name}"] else: if issubclass(type_hint, enum.Enum): opt_type = EnumChoice(type_hint) parts = text.split_words(name) name = "-".join(parts) name_short = "".join(part[0] for part in parts) names = [f"--{name}", f"-{name_short}"] names.append("__".join(qname.split("."))) default_value = ( field.default.value if isinstance(field.default, enum.Enum) else field.default ) doc_hint += f" [default: {default_value}]" yield click.option( *names, help=doc_hint, is_flag=is_flag, type=opt_type, default=None, ) def get_doc_hints(obj: Any) -> dict[str, str]: """Return a param-docstring map of the class arguments.""" docstrings = inspect.getdoc(obj) assert docstrings is not None start = docstrings.index("Args:") + 6 params = docstrings[start:].replace("\n ", " ") result = {} for line in params.splitlines(): param, hint = line.split(":", 1) result[param.strip()] = " ".join(hint.split()) return result class EnumChoice(click.Choice): """Custom click choice widget for enumerations.""" def __init__(self, enumeration: type[enum.Enum]): """Initialize the enum choice widget.""" self.enumeration = enumeration super().__init__([e.value for e in enumeration]) def convert(self, value: Any, *args: Any) -> enum.Enum: """Parse the value into an enumeration member.""" return self.enumeration(value) class LogFormatter(logging.Formatter): """Custom log formatter with click colors.""" colors: ClassVar[dict[str, Any]] = { "error": {"fg": "red"}, "exception": {"fg": "red"}, "critical": {"fg": "red"}, "debug": {"fg": "blue"}, "warning": {"fg": "yellow"}, } def format(self, record: logging.LogRecord) -> str: """Format the log record with click styles.""" if not record.exc_info: level = record.levelname.lower() msg = record.getMessage() if level in self.colors: prefix = click.style(level, **self.colors[level]) msg = f"{prefix}: {msg}" return msg return super().format(record) # pragma: no cover class LogHandler(logging.Handler): """Custom click log handler to record warnings.""" def __init__(self, level: int | str = logging.NOTSET): """Initialize the log handler.""" super().__init__(level) self.warnings: list[str] = [] def emit(self, record: logging.LogRecord) -> None: """Override emit to record warnings.""" try: msg = self.format(record) if record.levelno > logging.INFO: self.warnings.append(msg) else: click.echo(msg, err=True) except Exception: # pragma: no cover self.handleError(record) def emit_warnings(self) -> None: """Print all recorded warnings to click stdout.""" num = len(self.warnings) if num: click.echo(click.style(f"Warnings: {num}", bold=True)) for msg in self.warnings: click.echo(msg, err=True) self.warnings.clear() class URL(click.ParamType): """Click parameter type for validating HTTP/HTTPS URLs. This ensures that only remote HTTP or HTTPS URLs are accepted, rejecting local file paths or file:// URIs. """ name = "url" def convert(self, value: str, param: Parameter | None, ctx: Context | None) -> str: """Validate that the value is an HTTP or HTTPS URL. Args: value: The parameter value to validate param: The parameter object ctx: The click context Returns: The validated URL string Raises: click.BadParameter: If the URL scheme is not http or https """ parsed = urlparse(value) if parsed.scheme not in ("http", "https"): self.fail(f"Source must be an HTTP URL, got: {value}\n", param, ctx) return value tefra-xsdata-cf7afe8/xsdata/utils/collections.py000066400000000000000000000060461526437326600222220ustar00rootroot00000000000000from collections import defaultdict from collections.abc import Callable, Generator, Iterable, Iterator, Sequence from typing import ( Any, TypeVar, ) T = TypeVar("T") def is_array(value: Any) -> bool: """Return whether the value is a list style type.""" if isinstance(value, tuple): return not hasattr(value, "_fields") return isinstance(value, (list, set, frozenset, Generator)) def unique_sequence(items: Iterable[T], key: str | None = None) -> list[T]: """Return a new unique list, preserving the original order. Args: items: The iterable to filter key: An optional callable to generate the unique keys Returns: A new unique list. """ seen = set() def is_new(val: Any) -> bool: if key: val = getattr(val, key) if val in seen: return False seen.add(val) return True return [item for item in items if is_new(item)] def remove(items: Iterable[T], predicate: Callable) -> list[T]: """Return a new list without the items that match the predicate.""" return [x for x in items if not predicate(x)] def group_by(items: Iterable[T], key: Callable) -> dict[Any, list[T]]: """Group the items of an iterable object by the result of the callable.""" result = defaultdict(list) for item in items: result[key(item)].append(item) return result def apply(items: Iterable, func: Callable) -> None: """Apply the given function to each item of the iterable object.""" for item in items: func(item) def find(items: Sequence, value: Any) -> int: """Return the index of the value in the given sequence. Args: items: The sequence to search in value: The value to search for Returns: The index in the sequence or -1 if the value is not found. """ try: return items.index(value) except ValueError: return -1 def first(items: Iterator[T]) -> T | None: """Return the first item of the iterator.""" return next(items, None) def prepend(target: list, *args: Any) -> None: """Prepend items to the target list.""" target[:0] = args def connected_components(lists: list[list[Any]]) -> Iterator[list[Any]]: """Merge lists of lists that share common elements.""" neighbors = defaultdict(set) for each in lists: for item in each: neighbors[item].update(each) def component(node: Any, neigh: dict[Any, set], see: set[Any]) -> Iterator[Any]: nodes = {node} while nodes: next_node = nodes.pop() see.add(next_node) nodes |= neigh[next_node] - see yield next_node seen: set[Any] = set() for item in neighbors: if item not in seen: yield sorted(component(item, neighbors, seen)) def find_connected_component(groups: list[list[Any]], value: Any) -> int: """Find the list index that contains the given value.""" for index, group in enumerate(groups): if value in group: return index return -1 tefra-xsdata-cf7afe8/xsdata/utils/constants.py000066400000000000000000000007011526437326600217100ustar00rootroot00000000000000import sys from collections.abc import Sequence from typing import Any EMPTY_MAP: dict = {} EMPTY_SEQUENCE: Sequence = [] EMPTY_TUPLE: tuple = () XML_FALSE = sys.intern("false") XML_TRUE = sys.intern("true") DEFAULT_ATTR_NAME = "value" def return_true(*_: Any) -> bool: """A dummy function that always returns true.""" return True def return_input(obj: Any) -> Any: """A dummy function that always returns input.""" return obj tefra-xsdata-cf7afe8/xsdata/utils/dates.py000066400000000000000000000176621526437326600210120ustar00rootroot00000000000000import datetime from calendar import isleap from collections.abc import Iterator from typing import Any def parse_date_args(value: Any, fmt: str) -> Iterator[int | None]: """Parse the fmt args from the value.""" if not isinstance(value, str): raise ValueError("") parser = DateTimeParser(value.strip(), fmt) return parser.parse() def calculate_timezone(offset: int | None) -> datetime.timezone | None: """Return a timezone instance by the given hours offset.""" if offset is None: return None if offset == 0: return datetime.timezone.utc return datetime.timezone(datetime.timedelta(minutes=offset)) def calculate_offset(obj: datetime.time | datetime.datetime) -> int | None: """Convert the datetime offset to signed minutes.""" offset = obj.utcoffset() if offset is None: return None return int(offset.total_seconds() // 60) def format_date(year: int, month: int, day: int) -> str: """Return a xml formatted signed date.""" if year < 0: year = -year sign = "-" else: sign = "" return f"{sign}{year:04d}-{month:02d}-{day:02d}" def format_time(hour: int, minute: int, second: int, fractional_second: int) -> str: """Return a xml formatted time.""" if not fractional_second: return f"{hour:02d}:{minute:02d}:{second:02d}" microsecond, nano = divmod(fractional_second, 1000) if nano: return f"{hour:02d}:{minute:02d}:{second:02d}.{fractional_second:09d}" milli, micro = divmod(microsecond, 1000) if micro: return f"{hour:02d}:{minute:02d}:{second:02d}.{microsecond:06d}" return f"{hour:02d}:{minute:02d}:{second:02d}.{milli:03d}" def format_offset(offset: int | None) -> str: """Return a xml formatted time offset.""" if offset is None: return "" if offset == 0: return "Z" if offset < 0: sign = "-" offset = -offset else: sign = "+" hh, mm = divmod(offset, 60) return f"{sign}{hh:02d}:{mm:02d}" # Copied from calendar.monthlen for some reason it's not exported mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] def monthlen(year: int, month: int) -> int: """Return the number of days for a specific month and year.""" return mdays[month] + (month == 2 and isleap(year)) def validate_date(year: int, month: int, day: int) -> None: """Validate the given year, month day is a valid date.""" if not 1 <= month <= 12: raise ValueError("Month must be in 1..12") max_days = monthlen(year, month) if not 1 <= day <= max_days: raise ValueError(f"Day must be in 1..{max_days}") def validate_time(hour: int, minute: int, second: int, franctional_second: int) -> None: """Validate the time args are valid.""" if not 0 <= hour <= 24: raise ValueError("Hour must be in 0..24") if hour == 24 and (minute != 0 or second != 0 or franctional_second != 0): raise ValueError("Day time exceeded") if not 0 <= minute <= 59: raise ValueError("Minute must be in 0..59") if not 0 <= second <= 59: raise ValueError("Second must be in 0..59") if not 0 <= franctional_second <= 999999999: raise ValueError("Fractional second must be in 0..999999999") SIMPLE_TWO_DIGITS_FORMATS = ("d", "m", "H", "M") class DateTimeParser: """XML Datetime parser. Args: value: The datetime string fmt: The target format string Attributes: vlen: The length of the datetime string flen: The length of the format string vidx: The current position of the datetime string fidx: The current position of the format string """ def __init__(self, value: str, fmt: str): """Initialize a DateTimeParser instance.""" self.format = fmt self.value = value self.vlen = len(value) self.flen = len(fmt) self.vidx = 0 self.fidx = 0 def parse(self) -> Iterator[int | None]: """Yield the parsed datetime string arguments.""" try: while self.fidx < self.flen: char = self.next_format_char() if char != "%": self.skip(char) else: var = self.next_format_char() yield from self.parse_var(var) if self.vidx != self.vlen: raise ValueError except Exception: raise ValueError( f"String '{self.value}' does not match format '{self.format}'" ) def next_format_char(self) -> str: """Return the next format character to evaluate.""" char = self.format[self.fidx] self.fidx += 1 return char def has_more(self) -> bool: """Return whether the value is not fully parsed yet.""" return self.vidx < self.vlen def peek(self) -> str: """Return the current evaluated character of the datetime string.""" return self.value[self.vidx] def skip(self, char: str) -> None: """Validate and skip over the given char.""" if not self.has_more() or self.peek() != char: raise ValueError self.vidx += 1 def parse_var(self, var: str) -> Iterator[int | None]: """Parse the given var from the datetime string.""" if var in SIMPLE_TWO_DIGITS_FORMATS: yield self.parse_digits(2) elif var == "Y": yield self.parse_year() elif var == "S": yield self.parse_digits(2) yield self.parse_fractional_second() elif var == "z": yield self.parse_offset() else: raise ValueError def parse_year(self) -> int: """Parse the year argument.""" negative = False if self.peek() == "-": self.vidx += 1 negative = True start = self.vidx year = self.parse_minimum_digits(4) end = self.vidx raw = self.value[start:end] leading_zeros = len(raw) - len(raw.lstrip("0")) if ( (leading_zeros == 1 and year > 999) or (leading_zeros == 2 and year > 99) or (leading_zeros == 3 and year > 9) or (leading_zeros == 4 and year > 0) or (leading_zeros > 4) ): raise ValueError if negative: return -year return year def parse_fractional_second(self) -> int: """Parse the fractional second argument.""" if self.has_more() and self.peek() == ".": self.vidx += 1 return self.parse_fixed_digits(9) return 0 def parse_digits(self, digits: int) -> int: """Parse the given number of digits.""" start = self.vidx self.vidx += digits return int(self.value[start : self.vidx]) def parse_minimum_digits(self, min_digits: int) -> int: """Parse until the next character is not a digit.""" start = self.vidx self.vidx += min_digits while self.has_more() and self.peek().isdigit(): self.vidx += 1 return int(self.value[start : self.vidx]) def parse_fixed_digits(self, max_digits: int) -> int: """Parse a fixed number of digits.""" start = self.vidx just = max_digits while max_digits and self.has_more() and self.peek().isdigit(): self.vidx += 1 max_digits -= 1 return int(self.value[start : self.vidx].ljust(just, "0")) def parse_offset(self) -> int | None: """Parse the xml timezone offset as minutes.""" if not self.has_more(): return None ctrl = self.peek() if ctrl == "Z": self.vidx += 1 return 0 if ctrl in ("-", "+"): self.vidx += 1 offset = self.parse_digits(2) * 60 self.skip(":") offset += self.parse_digits(2) offset *= -1 if ctrl == "-" else 1 return offset raise ValueError tefra-xsdata-cf7afe8/xsdata/utils/debug.py000066400000000000000000000016311526437326600207650ustar00rootroot00000000000000import json from pathlib import Path from typing import Any def dump(obj: Any) -> None: """Write any object into a dump JSON file. For internal troubleshooting purposes only!!! """ with Path.cwd().joinpath("xsdata_dump.json").open("w+") as f: json.dump(convert(obj), f, indent=4) def convert(obj: Any) -> Any: """Dump any obj into a readable dictionary.""" if obj is None: return obj if isinstance(obj, (list, tuple)): return list(map(convert, obj)) if isinstance(obj, dict): return {convert(key): convert(value) for key, value in obj.items()} if hasattr(obj, "__slots__") and obj.__slots__: return {name: convert(getattr(obj, name)) for name in obj.__slots__} if isinstance(obj, type): return f"{obj.__module__}.{obj.__qualname__}" if isinstance(obj, (str, int, bool, float)): return obj return repr(obj) tefra-xsdata-cf7afe8/xsdata/utils/downloader.py000066400000000000000000000102701526437326600220340ustar00rootroot00000000000000import os import re from pathlib import Path from urllib.parse import urlparse from xsdata.codegen import opener from xsdata.codegen.parsers import DefinitionsParser, SchemaParser from xsdata.logger import logger from xsdata.models.wsdl import Definitions from xsdata.models.xsd import Schema class Downloader: """Remote recursive resource downloader. Helper class to download a schema or a definitions with all their imports locally. The imports paths will be adjusted if necessary. Args: output: The output path Attributes: base_path: The base path for the resources downloaded: A cache of the downloaded resources """ __slots__ = ("base_path", "downloaded", "output") def __init__(self, output: Path): """Initialize the downloader.""" self.output = output self.downloaded: dict = {} def wget(self, uri: str, location: str | None = None) -> None: """Download handler for any uri input with circular protection.""" if uri not in self.downloaded: self.downloaded[uri] = None logger.info("Fetching %s", uri) input_stream = opener.open(uri).read() # nosec if uri.endswith("wsdl"): self.parse_definitions(uri, input_stream) else: self.parse_schema(uri, input_stream) self.write_file(uri, location, input_stream.decode()) def parse_schema(self, uri: str, content: bytes) -> None: """Convert content to a schema instance and process all sub imports.""" parser = SchemaParser(location=uri) schema = parser.from_bytes(content, Schema) self.wget_included(schema) def parse_definitions(self, uri: str, content: bytes) -> None: """Convert content to a definitions instance and process all sub imports.""" parser = DefinitionsParser(location=uri) definitions = parser.from_bytes(content, Definitions) self.wget_included(definitions) for schema in definitions.schemas: self.wget_included(schema) def wget_included(self, definition: Schema | Definitions) -> None: """Download the definitions included resources.""" for included in definition.included(): if included.location: schema_location = getattr(included, "schema_location", None) self.wget(included.location, schema_location) def write_file(self, uri: str, location: str | None, content: str) -> None: """Write the downloaded uri to a local file. Keep track of all the written file paths, in case we have to modify the location attribute in an upcoming schema/definition import. Args: uri: The resource URI location: The import location of the resource content: The raw content string """ if uri.startswith("file:"): # This happens for bundled schemas (xlink.xsd, xml.xsd) # which are intercepted by opener and returned as file:// if not location or location.startswith("file:"): raise ValueError( f"Cannot download local file without HTTP location: {uri}\n" ) parsed = urlparse(location) else: parsed = urlparse(uri) rel_path = parsed.netloc + "/" + parsed.path.lstrip("/") file_path = self.output.joinpath(rel_path) content = self.adjust_imports(file_path.parent, content) file_path.parent.mkdir(parents=True, exist_ok=True) file_path.write_text(content, encoding="utf-8") logger.info("Writing %s", file_path) self.downloaded[uri] = file_path def adjust_imports(self, path: Path, content: str) -> str: """Update the location of the imports to point to the downloaded files.""" matches = re.findall(r"ocation=\"(.*)\"", content) for match in matches: if isinstance(self.downloaded.get(match), Path): location = os.path.relpath(self.downloaded[match], path) replace = location.replace("\\", "/") content = content.replace(f'ocation="{match}"', f'ocation="{replace}"') return content tefra-xsdata-cf7afe8/xsdata/utils/graphs.py000066400000000000000000000026671526437326600211750ustar00rootroot00000000000000# strongly_connected_components is derived from: # # https://code.activestate.com/recipes/578507/ # Copyright 2013 Mark Dickinson # # https://github.com/python/mypy/blob/master/mypy/graph_utils.py # Copyright (c) 2012-2023 Jukka Lehtosalo and contributors # Copyright (c) 2015-2023 Dropbox, Inc. # # Both are distributed under the MIT License, see THIRD_PARTY_LICENSES.md. from collections.abc import Iterator def strongly_connected_components(edges: dict[str, list[str]]) -> Iterator[set[str]]: """Compute Strongly Connected Components of a directed graph. Args: edges: A vertex-edges map Yields: A set of the strongly connected components """ identified: set[str] = set() stack: list[str] = [] index: dict[str, int] = {} boundaries: list[int] = [] def dfs(v: str) -> Iterator[set[str]]: index[v] = len(stack) stack.append(v) boundaries.append(index[v]) for w in edges[v]: if w not in index: yield from dfs(w) elif w not in identified: while index[w] < boundaries[-1]: boundaries.pop() if boundaries[-1] == index[v]: boundaries.pop() scc = set(stack[index[v] :]) del stack[index[v] :] identified.update(scc) yield scc for vertex in set(edges): if vertex not in index: yield from dfs(vertex) tefra-xsdata-cf7afe8/xsdata/utils/hooks.py000066400000000000000000000006161526437326600210240ustar00rootroot00000000000000from importlib import metadata def load_entry_points(name: str) -> None: """Load the plugins for the given hook name.""" entry_points = metadata.entry_points() if hasattr(entry_points, "select"): plugins = entry_points.select(group=name) # type: ignore else: plugins = entry_points.get(name, []) # type: ignore for plugin in plugins: plugin.load() tefra-xsdata-cf7afe8/xsdata/utils/namespaces.py000066400000000000000000000111551526437326600220200ustar00rootroot00000000000000import functools import re from xsdata.models.enums import Namespace from xsdata.utils import text __uri_ignore__ = ("www", "xsd", "wsdl") URI_REGEX = re.compile( r"^(([a-zA-Z][0-9a-zA-Z+\\-\\.]*:)?" r"/{0,2}[0-9a-zA-Z;/?:@&=+$\\.\\-_!~*'()%]+)?" r"(#[0-9a-zA-Z;/?:@&=+$\\.\\-_!~*'()%]+)?$" ) def load_prefix(uri: str, ns_map: dict) -> str | None: """Get or create a prefix for the uri in the prefix-URI map.""" for prefix, ns in ns_map.items(): if ns == uri: return prefix return generate_prefix(uri, ns_map) def generate_prefix(uri: str, ns_map: dict) -> str: """Generate a prefix for the given uri and append it in the prefix-URI map.""" namespace = Namespace.get_enum(uri) if namespace: prefix = namespace.prefix else: number = len(ns_map) prefix = f"ns{number}" ns_map[prefix] = uri return prefix def prefix_exists(uri: str, ns_map: dict) -> bool: """Check if the uri exists in the prefix-URI namespace mapping.""" return uri in ns_map.values() def is_default(uri: str, ns_map: dict) -> bool: """Check if the uri exists and it has no prefix.""" return any(uri == ns and not prefix for prefix, ns in ns_map.items()) def clean_prefixes(ns_map: dict) -> dict: """Remove default namespace if it's also assigned to a prefix.""" result = {} for prefix, ns in ns_map.items(): if ns: prefix = prefix or None if prefix not in result: result[prefix] = ns default_ns = result.get(None) if default_ns and any(prefix and ns == default_ns for prefix, ns in result.items()): result.pop(None) return result def clean_uri(namespace: str) -> str: """Remove common prefixes and suffixes from an URI string.""" if namespace[:2] == "##": namespace = namespace[2:] left, right = text.split(namespace) if left == "urn": namespace = right elif left in ("http", "https"): namespace = right[2:] return "_".join(x for x in namespace.split(".") if x not in __uri_ignore__) @functools.lru_cache(maxsize=50) def build_qname(tag_or_uri: str | None, tag: str | None = None) -> str: """Create namespace qualified strings.""" if not tag_or_uri: if not tag: raise ValueError("Invalid input both uri and tag are empty.") return tag return f"{{{tag_or_uri}}}{tag}" if tag else tag_or_uri @functools.lru_cache(maxsize=50) def split_qname(qname: str) -> tuple: """Split namespace qualified strings.""" if qname[0] == "{": left, right = text.split(qname[1:], "}") if left: return left, right return None, qname def target_uri(qname: str) -> str | None: """Return the URI namespace of the qname.""" return split_qname(qname)[0] def local_name(qname: str) -> str: """Return the local name of the qname.""" return split_qname(qname)[1] NCNAME_PUNCTUATION = {"\u00b7", "\u0387", ".", "-", "_"} def is_ncname(name: str | None) -> bool: """Verify given string is a valid ncname.""" if not name: return False char = name[0] if not char.isalpha() and char != "_": return False for char in name[1:]: if char.isalpha() or char.isdigit() or char in NCNAME_PUNCTUATION: continue return False return True def is_uri(uri: str | None) -> bool: """Verify given string is a valid uri.""" return bool(URI_REGEX.search(uri)) if uri else False @functools.lru_cache(maxsize=50) def to_package_name(uri: str | None) -> str: """Util method to convert a namespace to a dot style package name.""" if not uri: return "" # Remove scheme domain_sep = "." if uri.startswith("http://"): uri = uri[7:] elif uri.startswith("urn:"): uri = uri[4:] domain_sep = "-" if uri.startswith("xmlns:"): uri = uri[6:] uri = uri.replace(":", "/") # Remote target pos = uri.find("#") if pos > 0: uri = uri[:pos] tokens = [token for token in uri.split("/") if token.strip()] if not tokens: return "" # Remove extension if len(tokens) > 1: last = tokens[-1] pos = tokens[-1].rfind(".") if pos > 0: tokens[-1] = last[:pos] # Remove port from domain domain = tokens.pop(0) pos = domain.find(":") if pos > 0: domain = domain[:pos] # Remove www from domain if domain.startswith("www"): domain = domain[3:] for part in domain.split(domain_sep): tokens.insert(0, part) return ".".join(token for token in tokens if token) tefra-xsdata-cf7afe8/xsdata/utils/objects.py000066400000000000000000000014131526437326600213260ustar00rootroot00000000000000import math from typing import Any from xml.etree.ElementTree import QName def update(obj: Any, **kwargs: Any) -> None: """Update an object from keyword arguments with dotted keys.""" def attrsetter(obj: Any, attr: str, value: Any) -> None: names = attr.split(".") last = names.pop() for name in names: obj = getattr(obj, name) setattr(obj, last, value) for key, value in kwargs.items(): attrsetter(obj, key, value) def literal_value(value: Any) -> str: """Return the value for code generation.""" if isinstance(value, float): return str(value) if math.isfinite(value) else f'float("{value}")' if isinstance(value, QName): return f'QName("{value.text}")' return repr(value) tefra-xsdata-cf7afe8/xsdata/utils/package.py000066400000000000000000000015441526437326600212750ustar00rootroot00000000000000import functools import os from pathlib import Path @functools.lru_cache(maxsize=50) def package_path(package: str) -> Path: """Join the current working path with the package name.""" return Path.cwd().joinpath(package.replace(".", "/")).parent @functools.lru_cache(maxsize=50) def module_path(module: str) -> Path: """Join the current working path with the given module name.""" return Path.cwd().joinpath(module.replace(".", "/")) @functools.lru_cache(maxsize=50) def module_name(uri: str) -> str: """Convert a file uri to a module name. Args: uri: A file URI location Returns: The last part of the URI path stripped from known extensions. """ module = uri.split("/")[-1] name, extension = os.path.splitext(module) return name if extension in (".xsd", ".dtd", ".wsdl", ".xml", ".json") else module tefra-xsdata-cf7afe8/xsdata/utils/testing.py000066400000000000000000000410311526437326600213520ustar00rootroot00000000000000import abc import copy import importlib import random import unittest from collections.abc import Callable, Sequence from contextlib import suppress from dataclasses import is_dataclass from typing import Any, TypeVar from xsdata.codegen.models import ( Attr, AttrType, Class, Extension, Import, Restrictions, Status, ) from xsdata.formats.dataclass.models.elements import XmlMeta, XmlType, XmlVar from xsdata.models.dtd import ( Dtd, DtdAttribute, DtdAttributeDefault, DtdAttributeType, DtdContent, DtdContentOccur, DtdContentType, DtdElement, DtdElementType, ) from xsdata.models.enums import DataType, Namespace, Tag from xsdata.utils.collections import first from xsdata.utils.namespaces import build_qname T = TypeVar("T") DEFAULT_NS_MAP = { Namespace.XS.prefix: Namespace.XS.uri, Namespace.XSI.prefix: Namespace.XSI.uri, Namespace.XML.prefix: Namespace.XML.uri, Namespace.XLINK.prefix: Namespace.XLINK.uri, } def filter_none(d: Any) -> Any: if not isinstance(d, dict): return d return {k: filter_none(v) for k, v in d.items() if v is not None} def load_class(output: str, clazz_name: str) -> Any: search = "Generating package: " start = len(search) packages = [line[start:] for line in output.splitlines() if line.startswith(search)] for package in reversed(packages): with suppress(ModuleNotFoundError, AttributeError): module = importlib.import_module(package) return getattr(module, clazz_name) raise ModuleNotFoundError(f"Class `{clazz_name}` not found.") class FactoryTestCase(unittest.TestCase): maxDiff = None def setUp(self) -> None: super().setUp() ClassFactory.reset() AttrFactory.reset() AttrTypeFactory.reset() ExtensionFactory.reset() PackageFactory.reset() XmlVarFactory.reset() XmlMetaFactory.reset() DtdElementFactory.reset() DtdAttributeFactory.reset() DtdContentFactory.reset() DtdFactory.reset() class Factory(abc.ABC): counter = 0 model: type @classmethod @abc.abstractmethod def create(cls, **kwargs: Any) -> Any: """Abstract method create.""" @classmethod def reset(cls): cls.counter = 65 @classmethod def next_letter(cls) -> str: cls.counter += 1 return chr(cls.counter) @classmethod def list(cls, number: int, **kwargs: Any) -> list: return [cls.create(**kwargs) for _ in range(number)] class ClassFactory(Factory): tags = (Tag.ELEMENT, Tag.ATTRIBUTE, Tag.COMPLEX_TYPE, Tag.SIMPLE_TYPE) counter = 65 @classmethod def create( cls, qname: str | None = None, meta_name: str | None = None, namespace: str | None = None, target_namespace: str | None = None, tag: str | None = None, abstract: bool = False, mixed: bool = False, nillable: bool = False, extensions: list[Extension] | None = None, substitutions: list[str] | None = None, attrs: list[Attr] | None = None, inner: list[Class] | None = None, ns_map: dict | None = None, location: str = "tests.xsd", package: str | None = None, module: str | None = "tests", status: Status = Status.RAW, container: str | None = None, default: Any | None = None, fixed: bool = False, prefix: str = "class_", **kwargs: Any, ) -> Class: if not qname: qname = build_qname("xsdata", f"{prefix}{cls.next_letter()}") if ns_map is None: ns_map = copy.deepcopy(DEFAULT_NS_MAP) return Class( qname=qname, meta_name=meta_name, namespace=namespace, abstract=abstract, mixed=mixed, nillable=nillable, tag=tag or random.choice(cls.tags), extensions=extensions or [], substitutions=substitutions or [], attrs=attrs or [], inner=inner or [], package=package, location=location, module=module, ns_map=ns_map, status=status, container=container, default=default, fixed=fixed, **kwargs, ) @classmethod def simple_type(cls, **kwargs: Any) -> Class: return cls.create( tag=Tag.SIMPLE_TYPE, attrs=AttrFactory.list(1, tag=Tag.EXTENSION), **kwargs, ) @classmethod def enumeration(cls, attributes: int, **kwargs: Any) -> Class: return cls.create( tag=Tag.SIMPLE_TYPE, attrs=AttrFactory.list(attributes, tag=Tag.ENUMERATION), **kwargs, ) @classmethod def elements(cls, attributes: int, **kwargs: Any) -> Class: return cls.create( tag=Tag.COMPLEX_TYPE, attrs=AttrFactory.list(attributes, tag=Tag.ELEMENT), **kwargs, ) @classmethod def service(cls, attributes: int, **kwargs: Any) -> Class: return cls.create( tag=Tag.BINDING_OPERATION, attrs=AttrFactory.list(attributes, tag=Tag.ELEMENT), **kwargs, ) class ExtensionFactory(Factory): counter = 65 tags = (Tag.ELEMENT, Tag.EXTENSION, Tag.RESTRICTION) @classmethod def create( cls, attr_type: AttrType | None = None, restrictions: Restrictions | None = None, tag: str | None = None, **kwargs: Any, ) -> Extension: return Extension( tag=tag or random.choice(cls.tags), type=attr_type or AttrTypeFactory.create(**kwargs), restrictions=restrictions or Restrictions(), ) @classmethod def reference(cls, qname: str, **kwargs: Any) -> Extension: tag = kwargs.pop("tag", None) restrictions = kwargs.pop("restrictions", None) return cls.create( AttrTypeFactory.create(qname=qname, **kwargs), tag=tag, restrictions=restrictions, ) @classmethod def native(cls, datatype: DataType, **kwargs: Any) -> Extension: return cls.create(AttrTypeFactory.native(datatype), **kwargs) class AttrTypeFactory(Factory): counter = 65 @classmethod def create( cls, qname: str | None = None, alias: str | None = None, native: bool = False, forward: bool = False, circular: bool = False, reference: int = 0, prefix: str = "attr_", **kwargs: Any, ) -> AttrType: if not qname: qname = build_qname("xsdata", f"{prefix}{cls.next_letter()}") return AttrType( qname=str(qname), alias=alias, native=native, circular=circular, forward=forward, reference=reference, ) @classmethod def native(cls, datatype: DataType, **kwargs: Any) -> AttrType: return cls.create(qname=str(datatype), native=True, **kwargs) class AttrFactory(Factory): tags = (Tag.ATTRIBUTE, Tag.ELEMENT, Tag.RESTRICTION) counter = 65 @classmethod def create( cls, name: str | None = None, index: int | None = None, types: list[AttrType] | None = None, choices: list[Attr] | None = None, tag: str | None = None, namespace: str | None = None, wrapper: str | None = None, default: Any | None = None, fixed: bool = False, mixed: bool = False, restrictions: Restrictions | None = None, prefix: str = "attr_", **kwargs: Any, ) -> Attr: name = name or f"{prefix}{cls.next_letter()}" return Attr( name=name, index=cls.counter if index is None else index, types=types or [AttrTypeFactory.native(DataType.STRING)], choices=choices or [], tag=tag or random.choice(cls.tags), namespace=namespace, wrapper=wrapper, default=default, fixed=fixed, mixed=mixed, restrictions=restrictions or Restrictions(), **kwargs, ) @classmethod def reference( cls, qname: str, tag: str = Tag.ELEMENT, name: str | None = None, **kwargs: Any, ) -> Attr: return cls.create( name=name, tag=tag, types=[AttrTypeFactory.create(qname=qname, **kwargs)] ) @classmethod def native( cls, datatype: DataType, tag: str = Tag.ELEMENT, name: str | None = None, **kwargs: Any, ) -> Attr: return cls.create( name=name, tag=tag, types=[AttrTypeFactory.native(datatype)], **kwargs ) @classmethod def enumeration(cls, **kwargs: Any) -> Attr: return cls.create(tag=Tag.ENUMERATION, **kwargs) @classmethod def element(cls, **kwargs: Any) -> Attr: return cls.create(tag=Tag.ELEMENT, **kwargs) @classmethod def extension(cls, **kwargs: Any) -> Attr: return cls.create(tag=Tag.EXTENSION, **kwargs) @classmethod def any(cls, **kwargs: Any) -> Attr: return cls.create( tag=Tag.ANY, types=[AttrTypeFactory.native(DataType.ANY_TYPE)], **kwargs ) @classmethod def any_attribute(cls, **kwargs: Any) -> Attr: return cls.create( tag=Tag.ANY_ATTRIBUTE, types=[AttrTypeFactory.native(DataType.ANY_TYPE)], **kwargs, ) @classmethod def attribute(cls, **kwargs: Any) -> Attr: return cls.create(tag=Tag.ATTRIBUTE, **kwargs) @classmethod def attribute_group(cls, **kwargs: Any) -> Attr: return cls.create( tag=Tag.ATTRIBUTE_GROUP, types=[AttrTypeFactory.create(qname=kwargs.get("name"))], **kwargs, ) @classmethod def group(cls, **kwargs: Any) -> Attr: return cls.create(tag=Tag.GROUP, **kwargs) class PackageFactory(Factory): counter = 65 @classmethod def create( cls, qname: str = "{xsdata}Root", source: str = "generated.models", alias: str | None = None, **kwargs: Any, ) -> Import: return Import(qname=qname, source=source, alias=alias) class XmlVarFactory(Factory): counter = 65 @classmethod def create( cls, name: str | None = None, local_name: str | None = None, wrapper: str | None = None, index: int = 0, types: Sequence[type] | None = None, clazz: type | None = None, init: bool = True, mixed: bool = False, factory: Callable | None = None, tokens_factory: Callable | None = None, format: str | None = None, any_type: bool = False, required: bool = False, nillable: bool = False, sequence: int | None = None, list_element: bool = False, default: Any | None = None, xml_type: str = XmlType.ELEMENT, namespaces: Sequence[str] | None = None, elements: dict[str, XmlVar] | None = None, wildcards: Sequence[XmlVar] | None = None, prefix: str = "field_", **kwargs: Any, ) -> XmlVar: name = name or f"{prefix}{cls.next_letter()}" if local_name is None: local_name = name if types is None: types = () if namespaces is None: namespaces = () if elements is None: elements = {} if wildcards is None: wildcards = [] return XmlVar( index=index, name=name, local_name=local_name, wrapper=wrapper, types=types, clazz=clazz or first(tp for tp in types if is_dataclass(tp)), init=init, mixed=mixed, factory=factory, tokens_factory=tokens_factory, format=format, any_type=any_type, required=required, nillable=nillable, sequence=sequence, process_contents="strict", list_element=list_element, default=default, xml_type=xml_type, namespaces=namespaces, elements=elements, wildcards=wildcards, ) class XmlMetaFactory(Factory): counter = 65 @classmethod def create( # type: ignore cls, clazz: type, qname: str | None = None, target_qname: str | None = None, nillable: bool = False, text: XmlVar | None = None, choices: Sequence[XmlVar] | None = None, elements: dict[str, Sequence[XmlVar]] | None = None, wildcards: Sequence[XmlVar] | None = None, attributes: dict[str, XmlVar] | None = None, any_attributes: Sequence[XmlVar] | None = None, **kwargs: Any, ) -> XmlMeta: if qname is None: qname = clazz.__name__ if target_qname is None: target_qname = qname if choices is None: choices = [] if elements is None: elements = {} if wildcards is None: wildcards = [] if any_attributes is None: any_attributes = [] if attributes is None: attributes = {} return XmlMeta( clazz=clazz, qname=qname, target_qname=target_qname, nillable=nillable, text=text, choices=choices, elements=elements, wildcards=wildcards, attributes=attributes, any_attributes=any_attributes, wrappers={}, ) class DtdAttributeFactory(Factory): counter = 65 @classmethod def create( cls, name: str | None = None, prefix: str | None = None, type: DtdAttributeType | None = None, default: DtdAttributeDefault | None = None, default_value: str | None = None, values: list[str] | None = None, **kwargs: Any, ) -> DtdAttribute: if name is None: name = f"attribute_{cls.next_letter()}" if type is None: type = DtdAttributeType.CDATA if default is None: default = DtdAttributeDefault.NONE if values is None: values = [] return DtdAttribute( name=name, prefix=prefix, type=type, default=default, default_value=default_value, values=values, ) class DtdContentFactory(Factory): counter = 65 @classmethod def create( cls, name: str | None = None, type: DtdContentType | None = None, occur: DtdContentOccur | None = None, left: DtdContent | None = None, right: DtdContent | None = None, **kwargs: Any, ) -> DtdContent: if name is None: name = f"content_{cls.next_letter()}" if type is None: type = DtdContentType.ELEMENT if occur is None: occur = DtdContentOccur.ONCE return DtdContent( name=name, type=type, occur=occur, left=left, right=right, ) class DtdElementFactory(Factory): counter = 65 @classmethod def create( cls, name: str | None = None, prefix: str | None = None, type: DtdElementType | None = None, content: DtdContent | None = None, attributes: list[DtdAttribute] | None = None, ns_map: dict | None = None, **kwargs: Any, ) -> DtdElement: if name is None: name = f"element_{cls.next_letter()}" if type is None: type = DtdElementType.ELEMENT if attributes is None: attributes = [] if ns_map is None: ns_map = {} return DtdElement( name=name, prefix=prefix, type=type, content=content, attributes=attributes, ns_map=ns_map, ) class DtdFactory(Factory): @classmethod def create( cls, elements: list[DtdElement] | None = None, location: str | None = None, **kwargs: Any, ) -> Dtd: if elements is None: elements = [] if location is None: location = "test.dtd" return Dtd(elements=elements, location=location) @classmethod def root(cls, number: int, **kwargs: Any) -> Dtd: elements = DtdElementFactory.list(number) return cls.create(elements=elements, **kwargs) tefra-xsdata-cf7afe8/xsdata/utils/text.py000066400000000000000000000114161526437326600206650ustar00rootroot00000000000000import re import string from re import Match from typing import Any stop_words = { "", "Any", "Decimal", "Enum", "False", "Meta", "None", "Optional", "QName", "True", "Union", "and", "as", "assert", "async", "bool", "break", "class", "continue", "def", "del", "dict", "elif", "else", "except", "field", "Field", "finally", "float", "for", "from", "global", "if", "import", "in", "int", "is", "lambda", "list", "nonlocal", "not", "object", "or", "pass", "raise", "return", "self", "str", "try", "type", "validate", "while", "with", "yield", } is_reserved = stop_words.__contains__ def prefix(value: str, sep: str = ":") -> str: """Return the first part of the string before the separator.""" return split(value, sep)[0] def suffix(value: str, sep: str = ":") -> str: """Return the last part of the string after the separator.""" return split(value, sep)[1] def split(value: str, sep: str = ":") -> tuple: """Split the given value with the given separator once.""" left, _, right = value.partition(sep) return (left, right) if right else (None, left) def capitalize(value: str, **kwargs: Any) -> str: """Capitalize the given string.""" return value[0].upper() + value[1:] def original_case(value: str, **kwargs: Any) -> str: """Return the input string but ensure it's a valid Python variable.""" # Strip out all characters that are not alphanumeric or underscores value = re.sub(r"\W", "", value) # Then strip out leading digit and underscore characters return re.sub(r"^[^a-zA-Z_]+", "", value) def pascal_case(value: str, **kwargs: Any) -> str: """Convert the given string to pascal case.""" return "".join(map(str.title, split_words(value))) def camel_case(value: str, **kwargs: Any) -> str: """Convert the given string to camel case.""" result = "".join(map(str.title, split_words(value))) return result[0].lower() + result[1:] def mixed_case(value: str, **kwargs: Any) -> str: """Convert the given string to mixed case.""" return "".join(split_words(value)) def mixed_pascal_case(value: str, **kwargs: Any) -> str: """Convert the given string to mixed pascal case.""" return capitalize(mixed_case(value)) def mixed_snake_case(value: str, **kwargs: Any) -> str: """Convert the given string to mixed snake case.""" return "_".join(split_words(value)) def snake_case(value: str, **kwargs: Any) -> str: """Convert the given string to snake case.""" return "_".join(map(str.lower, split_words(value))) def screaming_snake_case(value: str, **kwargs: Any) -> str: """Convert the given string to screaming snake case.""" return snake_case(value, **kwargs).upper() def kebab_case(value: str, **kwargs: Any) -> str: """Convert the given string to kebab case.""" return "-".join(split_words(value)) def split_words(value: str) -> list[str]: """Split a string on capital letters and not alphanumeric characters.""" words: list[str] = [] buffer: list[str] = [] previous = None def flush() -> None: if buffer: words.append("".join(buffer)) buffer.clear() for char in value: tp = classify(char) if tp == CharType.OTHER: flush() elif not previous or tp == previous: buffer.append(char) elif tp == CharType.UPPER and previous != CharType.UPPER: flush() buffer.append(char) else: buffer.append(char) previous = tp flush() return words class CharType: """Character types.""" UPPER = 1 LOWER = 2 NUMERIC = 3 OTHER = 4 def classify(character: str) -> int: """String classifier.""" code_point = ord(character) if 64 < code_point < 91: return CharType.UPPER if 96 < code_point < 123: return CharType.LOWER if 47 < code_point < 58: return CharType.NUMERIC return CharType.OTHER ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]') ESCAPE_DCT = { "\\": "\\\\", '"': '\\"', "\b": "\\b", "\f": "\\f", "\n": "\\n", "\r": "\\r", "\t": "\\t", } for i in range(0x20): ESCAPE_DCT.setdefault(chr(i), f"\\u{i:04x}") def escape_string(value: str) -> str: """Escape a string for code generation.""" def replace(match: Match) -> str: return ESCAPE_DCT[match.group(0)] return ESCAPE.sub(replace, value) __alnum_ascii__ = set(string.digits + string.ascii_letters) def alnum(value: str) -> str: """Return the ascii alphanumerical characters in lower case.""" return "".join(filter(__alnum_ascii__.__contains__, value)).lower()