Skip to content
Draft
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
8 changes: 4 additions & 4 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ jobs:
fail-fast: false
matrix:
include:
- python-version: 3.13
- python-version: 3.14
env:
TOXENV: typing
- python-version: 3.13
- python-version: 3.14
env:
TOXENV: docs
- python-version: 3.13
- python-version: 3.14
env:
TOXENV: twinecheck
- python-version: 3.13
- python-version: 3.14
env:
TOXENV: pylint

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: 3.13
python-version: 3.14
- run: |
python -m pip install --upgrade build
python -m build
Expand Down
27 changes: 11 additions & 16 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,18 @@ jobs:
fail-fast: false
matrix:
include:
- python-version: "3.9"
- python-version: "3.10"
env:
TOXENV: min-attrs
- python-version: "3.9"
- python-version: "3.10"
env:
TOXENV: min-pydantic
- python-version: "3.9"
- python-version: "3.10"
env:
TOXENV: min-scrapy
- python-version: "3.9"
- python-version: "3.10"
env:
TOXENV: min-extra
- python-version: "3.9"
env:
TOXENV: py
- python-version: "3.10"
env:
TOXENV: py
Expand All @@ -37,21 +34,19 @@ jobs:
- python-version: "3.13"
env:
TOXENV: py
- python-version: "3.14.0-rc.2"
- python-version: "3.14"
env:
TOXENV: py
- python-version: "3.14.0-rc.2"
- python-version: "3.14"
env:
TOXENV: attrs
# pydantic doesn't yet support 3.14
- python-version: "3.13"
- python-version: "3.14"
env:
TOXENV: pydantic
- python-version: "3.14.0-rc.2"
- python-version: "3.14"
env:
TOXENV: scrapy
# pydantic doesn't yet support 3.14
- python-version: "3.13"
- python-version: "3.14"
env:
TOXENV: extra
- python-version: "pypy3.11"
Expand Down Expand Up @@ -86,7 +81,7 @@ jobs:
uses: codecov/codecov-action@v5

tests-other-os:
name: "Test: py39, ${{ matrix.os }}"
name: "Test: py310, ${{ matrix.os }}"
runs-on: "${{ matrix.os }}"
strategy:
matrix:
Expand All @@ -98,7 +93,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: 3.9
python-version: 3.10

- name: Install tox
run: pip install tox
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.13.0
rev: v0.14.4
hooks:
- id: ruff-check
args: [ --fix ]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ a pre-defined interface (see [extending `itemadapter`](#extending-itemadapter)).

## Requirements

* Python 3.9+, either the CPython implementation (default) or the PyPy
* Python 3.10+, either the CPython implementation (default) or the PyPy
implementation
* [`scrapy`](https://scrapy.org/) 2.2+: optional, needed to interact with
`scrapy` items
Expand Down
14 changes: 4 additions & 10 deletions itemadapter/_json_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from copy import copy
from enum import Enum
from textwrap import dedent
from types import MappingProxyType, UnionType
from typing import (
TYPE_CHECKING,
Any,
Expand All @@ -24,8 +25,6 @@
from .utils import _is_pydantic_model

if TYPE_CHECKING:
from types import MappingProxyType

from .adapter import AdapterInterface, ItemAdapter


Expand Down Expand Up @@ -139,20 +138,15 @@ def array_type(type_hint):
unique_args = set(args)
if len(unique_args) == 1:
return next(iter(unique_args))
return Union[tuple(unique_args)]
return Union[tuple(unique_args)] # noqa: UP007


def update_prop_from_pattern(prop: dict[str, Any], pattern: str) -> None:
if is_valid_pattern(pattern):
prop.setdefault("pattern", pattern)


try:
from types import UnionType
except ImportError: # Python < 3.10
UNION_TYPES: set[Any] = {Union}
else:
UNION_TYPES = {Union, UnionType}
UNION_TYPES = {Union, UnionType}


def update_prop_from_origin(
Expand Down Expand Up @@ -204,7 +198,7 @@ def update_prop_from_type(prop: dict[str, Any], prop_type: Any, state: _JsonSche
if issubclass(prop_type, Enum):
values = [item.value for item in prop_type]
value_types = tuple({type(v) for v in values})
prop_type = value_types[0] if len(value_types) == 1 else Union[value_types]
prop_type = value_types[0] if len(value_types) == 1 else Union[value_types] # noqa: UP007
update_prop_from_type(prop, prop_type, state)
prop.setdefault("enum", values)
return
Expand Down
11 changes: 5 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ authors = [
readme = "README.md"
license = "BSD-3-Clause"
license-files = ["LICENSE"]
requires-python = ">=3.9"
requires-python = ">=3.10"
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"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 :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Operating System :: OS Independent",
Expand Down Expand Up @@ -74,6 +74,9 @@ regex = true
[[tool.bumpversion.files]]
filename = "itemadapter/__init__.py"

[tool.coverage.run]
branch = true

[tool.pylint.MASTER]
persistent = "no"
load-plugins=[
Expand Down Expand Up @@ -257,7 +260,3 @@ split-on-trailing-comma = false

[tool.ruff.lint.pydocstyle]
convention = "pep257"

[tool.ruff.lint.pyupgrade]
# for Pydantic annotations while we support Python 3.9
keep-runtime-typing = true
26 changes: 13 additions & 13 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
from contextlib import contextmanager
from dataclasses import dataclass, field
from enum import Enum
from typing import TYPE_CHECKING, Any, Callable, Optional, Union
from typing import TYPE_CHECKING, Any

from itemadapter import ItemAdapter
from itemadapter._imports import pydantic, pydantic_v1

if TYPE_CHECKING:
from collections.abc import Generator
from collections.abc import Callable, Generator


def make_mock_import(block_name: str) -> Callable:
Expand Down Expand Up @@ -88,7 +88,7 @@ class DataClassItemJsonSchema:
name: str = field(metadata={"json_schema_extra": {"title": "Name"}})
"""Display name"""
color: Color
answer: Union[str, float, int, None]
answer: str | float | int | None
numbers: list[float]
aliases: dict[str, str]
nested: DataClassItemJsonSchemaNested
Expand Down Expand Up @@ -151,7 +151,7 @@ class AttrsItemJsonSchema:
name: str = attr.ib(metadata={"json_schema_extra": {"title": "Name"}})
"""Display name"""
color: Color = attr.ib()
answer: Union[str, float, int, None] = attr.ib()
answer: str | float | int | None = attr.ib()
numbers: list[float] = attr.ib()
aliases: dict[str, str] = attr.ib()
nested: AttrsItemJsonSchemaNested = attr.ib()
Expand All @@ -173,17 +173,17 @@ class AttrsItemJsonSchema:
else:

class PydanticV1Model(pydantic_v1.BaseModel):
name: Optional[str] = pydantic_v1.Field(
name: str | None = pydantic_v1.Field(
default_factory=lambda: None,
serializer=str,
)
value: Optional[int] = pydantic_v1.Field(
value: int | None = pydantic_v1.Field(
default_factory=lambda: None,
serializer=int,
)

class PydanticV1SpecialCasesModel(pydantic_v1.BaseModel):
special_cases: Optional[int] = pydantic_v1.Field(
special_cases: int | None = pydantic_v1.Field(
default_factory=lambda: None,
alias="special_cases",
allow_mutation=False,
Expand Down Expand Up @@ -220,7 +220,7 @@ class PydanticV1ModelJsonSchema(pydantic_v1.BaseModel):
value: Any = None
color: Color
produced: bool
answer: Union[str, float, int, None]
answer: str | float | int | None
numbers: list[float]
aliases: dict[str, str]
nested: PydanticV1ModelJsonSchemaNested
Expand All @@ -245,17 +245,17 @@ class Config:
else:

class PydanticModel(pydantic.BaseModel):
name: Optional[str] = pydantic.Field(
name: str | None = pydantic.Field(
default_factory=lambda: None,
json_schema_extra={"serializer": str},
)
value: Optional[int] = pydantic.Field(
value: int | None = pydantic.Field(
default_factory=lambda: None,
json_schema_extra={"serializer": int},
)

class PydanticSpecialCasesModel(pydantic.BaseModel):
special_cases: Optional[int] = pydantic.Field(
special_cases: int | None = pydantic.Field(
default_factory=lambda: None,
alias="special_cases",
frozen=True,
Expand Down Expand Up @@ -294,7 +294,7 @@ class PydanticModelJsonSchema(pydantic.BaseModel):
value: Any = None
color: Color
produced: bool = pydantic.Field(default_factory=lambda: True)
answer: Union[str, float, int, None]
answer: str | float | int | None
numbers: list[float]
aliases: dict[str, str]
nested: PydanticModelJsonSchemaNested
Expand Down Expand Up @@ -367,7 +367,7 @@ class ScrapySubclassedItemJsonSchema(ScrapyItem):
)
color: Color = Field()
produced = Field()
answer: Union[str, float, int, None] = Field()
answer: str | float | int | None = Field()
numbers: list[float] = Field()
aliases: dict[str, str] = Field()
nested: ScrapySubclassedItemJsonSchemaNested = Field()
Expand Down
9 changes: 5 additions & 4 deletions tests/test_adapter_pydantic.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import unittest
from types import MappingProxyType
from typing import Optional
from unittest import mock

import pytest
Expand Down Expand Up @@ -86,7 +87,7 @@ def test_true(self):
mapping_proxy_type = get_field_meta_from_class(PydanticModel, "name")
assert mapping_proxy_type == MappingProxyType(
{
"annotation": Optional[str],
"annotation": str | None,
"default_factory": mapping_proxy_type["default_factory"],
"json_schema_extra": {"serializer": str},
"repr": True,
Expand All @@ -95,7 +96,7 @@ def test_true(self):
mapping_proxy_type = get_field_meta_from_class(PydanticModel, "value")
assert get_field_meta_from_class(PydanticModel, "value") == MappingProxyType(
{
"annotation": Optional[int],
"annotation": int | None,
"default_factory": mapping_proxy_type["default_factory"],
"json_schema_extra": {"serializer": int},
"repr": True,
Expand All @@ -104,7 +105,7 @@ def test_true(self):
mapping_proxy_type = get_field_meta_from_class(PydanticSpecialCasesModel, "special_cases")
assert mapping_proxy_type == MappingProxyType(
{
"annotation": Optional[int],
"annotation": int | None,
"alias": "special_cases",
"alias_priority": 2,
"default_factory": mapping_proxy_type["default_factory"],
Expand Down
11 changes: 5 additions & 6 deletions tests/test_json_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from collections.abc import Mapping, Sequence # noqa: TC003
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Optional, Union
from typing import Any

import pytest

Expand Down Expand Up @@ -48,7 +48,7 @@ class OptionalItemListNestedItem:

@dataclass
class OptionalItemListItem:
foo: Optional[list[OptionalItemListNestedItem]] = None
foo: list[OptionalItemListNestedItem] | None = None


@dataclass
Expand Down Expand Up @@ -330,7 +330,7 @@ class TestItem:
def test_union_single(self):
@dataclass
class TestItem:
foo: Union[str]
foo: str

actual = ItemAdapter.get_json_schema(TestItem)
expected = {
Expand All @@ -346,7 +346,7 @@ class TestItem:
def test_custom_any_of(self):
@dataclass
class TestItem:
foo: Union[str, SimpleItem] = field(
foo: str | SimpleItem = field(
metadata={"json_schema_extra": {"anyOf": []}},
)

Expand Down Expand Up @@ -459,7 +459,6 @@ class TestItem:
check_schemas(actual, expected)

@unittest.skipIf(not AttrsItem, "attrs module is not available")
@unittest.skipIf(PYTHON_VERSION < (3, 10), "Modern optional annotations require Python 3.10+")
def test_modern_optional_annotations(self):
import attr

Expand Down Expand Up @@ -583,7 +582,7 @@ class TestItem:
@unittest.skipIf(not ScrapySubclassedItem, "scrapy module is not available")
@unittest.skipIf(not AttrsItem, "attrs module is not available")
def test_scrapy_attrs(self):
actual = ItemAdapter.get_json_schema(ScrapySubclassedItemCrossNested)
actual = ItemAdapter.get_json_schema(ScrapySubclassedItemCrossNested) # pylint: disable=possibly-used-before-assignment
expected = {
"type": "object",
"additionalProperties": False,
Expand Down
Loading
Loading