Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14']
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14', '3.15']
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,5 @@ repos:
pass_filenames: false
args: ["--package", "pydantic_xml"]
additional_dependencies:
- pydantic>=2.6.0
- pydantic>=2.14.0
- lxml-stubs>=0.4.0
10 changes: 1 addition & 9 deletions pydantic_xml/serializers/factories/call.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
import inspect

from pydantic_core import core_schema as pcs

from pydantic_xml import errors
from pydantic_xml.serializers.factories import named_tuple
from pydantic_xml.serializers.serializer import Serializer


def from_core_schema(schema: pcs.CallSchema, ctx: Serializer.Context) -> Serializer:
func = schema['function']

if inspect.isclass(func) and issubclass(func, tuple):
return named_tuple.from_core_schema(schema, ctx)
else:
raise errors.ModelError("type call is not supported")
raise errors.ModelError("type call is not supported")
2 changes: 2 additions & 0 deletions pydantic_xml/serializers/factories/homogeneous.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ def from_core_schema(schema: HomogeneousCollectionTypeSchema, ctx: Serializer.Co
SchemaTypeFamily.IS_INSTANCE,
SchemaTypeFamily.CALL,
SchemaTypeFamily.TUPLE,
SchemaTypeFamily.NAMED_TUPLE,
):
raise errors.ModelFieldError(
ctx.model_name, ctx.field_name, "collection item must be of primitive, model, mapping, union or tuple type",
Expand All @@ -133,6 +134,7 @@ def from_core_schema(schema: HomogeneousCollectionTypeSchema, ctx: Serializer.Co
SchemaTypeFamily.UNION,
SchemaTypeFamily.TAGGED_UNION,
SchemaTypeFamily.TUPLE,
SchemaTypeFamily.NAMED_TUPLE,
SchemaTypeFamily.CALL,
) and ctx.entity_location is None:
raise errors.ModelFieldError(ctx.model_name, ctx.field_name, "entity name is not provided")
Expand Down
17 changes: 8 additions & 9 deletions pydantic_xml/serializers/factories/named_tuple.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import typing
from typing import Any, Dict, List, Optional, Tuple

from pydantic_core import core_schema as pcs
Expand All @@ -12,11 +11,11 @@

class ElementSerializer(Serializer):
@classmethod
def from_core_schema(cls, schema: pcs.ArgumentsSchema, ctx: Serializer.Context) -> 'ElementSerializer':
def from_core_schema(cls, schema: pcs.NamedTupleSchema, ctx: Serializer.Context) -> 'ElementSerializer':
model_name = ctx.model_name
computed = ctx.field_computed
inner_serializers: List[Serializer] = []
for argument_schema in schema['arguments_schema']:
for argument_schema in schema['fields']:
param_schema = argument_schema['schema']
inner_serializers.append(Serializer.parse_core_schema(param_schema, ctx))

Expand Down Expand Up @@ -61,10 +60,10 @@ def deserialize(
)


def from_core_schema(schema: pcs.CallSchema, ctx: Serializer.Context) -> Serializer:
arguments_schema = typing.cast(pcs.ArgumentsSchema, schema['arguments_schema'])
for argument_schema in arguments_schema['arguments_schema']:
param_schema = argument_schema['schema']
def from_core_schema(schema: pcs.NamedTupleSchema, ctx: Serializer.Context) -> Serializer:
print(schema)
for field_schema in schema['fields']:
param_schema = field_schema['schema']
param_schema, ctx = Serializer.preprocess_schema(param_schema, ctx)

param_type_family = TYPE_FAMILY.get(param_schema['type'])
Expand All @@ -86,9 +85,9 @@ def from_core_schema(schema: pcs.CallSchema, ctx: Serializer.Context) -> Seriali
raise errors.ModelFieldError(ctx.model_name, ctx.field_name, "entity name is not provided")

if ctx.entity_location is EntityLocation.ELEMENT:
return ElementSerializer.from_core_schema(arguments_schema, ctx)
return ElementSerializer.from_core_schema(schema, ctx)
elif ctx.entity_location is None:
return ElementSerializer.from_core_schema(arguments_schema, ctx)
return ElementSerializer.from_core_schema(schema, ctx)
elif ctx.entity_location is EntityLocation.ATTRIBUTE:
raise errors.ModelFieldError(ctx.model_name, ctx.field_name, "attributes of tuple types are not supported")
else:
Expand Down
6 changes: 6 additions & 0 deletions pydantic_xml/serializers/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class SchemaTypeFamily(IntEnum):
JSON_OR_PYTHON = 11
IS_INSTANCE = 12
CALL = 13
NAMED_TUPLE = 14


TYPE_FAMILY = {
Expand Down Expand Up @@ -68,6 +69,7 @@ class SchemaTypeFamily(IntEnum):
'model': SchemaTypeFamily.MODEL,

'tuple': SchemaTypeFamily.TUPLE,
'named-tuple': SchemaTypeFamily.NAMED_TUPLE,
'list': SchemaTypeFamily.HOMOGENEOUS_COLLECTION,
'set': SchemaTypeFamily.HOMOGENEOUS_COLLECTION,
'frozenset': SchemaTypeFamily.HOMOGENEOUS_COLLECTION,
Expand Down Expand Up @@ -246,6 +248,10 @@ def select_serializer(cls, schema: pcs.CoreSchema, ctx: Context) -> 'Serializer'
schema = typing.cast(pcs.TupleSchema, schema)
return factories.tuple.from_core_schema(schema, ctx)

elif type_family is SchemaTypeFamily.NAMED_TUPLE:
schema = typing.cast(pcs.NamedTupleSchema, schema)
return factories.named_tuple.from_core_schema(schema, ctx)

elif type_family is SchemaTypeFamily.MAPPING:
schema = typing.cast(pcs.DictSchema, schema)
return factories.mapping.from_core_schema(schema, ctx)
Expand Down
7 changes: 3 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,19 @@ classifiers = [
"Framework :: Pydantic :: 1",
"Framework :: Pydantic :: 2",
"Programming Language :: Python",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"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 :: 3.15",
"Typing :: Typed",
]

[tool.poetry.dependencies]
python = ">=3.8"
python = ">=3.10"
lxml = {version = ">=4.9.0", optional = true}
pydantic = ">=2.6.0, !=2.10.0b1"
pydantic = ">=2.14.0"
pydantic-core = ">=2.15.0"

furo = {version = "^2022.12.7", optional = true}
Expand Down
14 changes: 5 additions & 9 deletions tests/test_examples.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import importlib.machinery
import runpy
import sys
from pathlib import Path

Expand All @@ -12,22 +12,19 @@

@pytest.mark.parametrize('snippet', list((EXAMPLES_PATH / 'snippets').glob('*.py')), ids=lambda p: p.name)
def test_snippets(snippet: Path):
loader = importlib.machinery.SourceFileLoader('snippet', str(snippet))
loader.load_module('snippet')
runpy.run_path(str(snippet), run_name='snippet')


@pytest.mark.skipif(sys.version_info < (3, 9), reason="requires python 3.9 and above")
@pytest.mark.parametrize('snippet', list((EXAMPLES_PATH / 'snippets' / 'py3.9').glob('*.py')), ids=lambda p: p.name)
def test_snippets_py39(snippet: Path):
loader = importlib.machinery.SourceFileLoader('snippet', str(snippet))
loader.load_module('snippet')
runpy.run_path(str(snippet), run_name='snippet')


@pytest.mark.skipif(not is_lxml_native(), reason='not lxml used')
@pytest.mark.parametrize('snippet', list((EXAMPLES_PATH / 'snippets' / 'lxml').glob('*.py')), ids=lambda p: p.name)
def test_snippets_py39(snippet: Path):
loader = importlib.machinery.SourceFileLoader('snippet', str(snippet))
loader.load_module('snippet')
runpy.run_path(str(snippet), run_name='snippet')


@pytest.fixture(
Expand All @@ -54,5 +51,4 @@ def example_dir(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch)


def test_example(example_dir):
loader = importlib.machinery.SourceFileLoader('example', str(example_dir / 'model.py'))
loader.load_module('example')
runpy.run_path(str(example_dir / 'model.py'), run_name='example')
Loading