Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update pydantic dependency to >= 2 #76

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Currently supported types are:
* [`dict`](https://docs.python.org/3/library/stdtypes.html#dict)
* [`dataclass`](https://docs.python.org/3/library/dataclasses.html)-based classes
* [`attrs`](https://www.attrs.org)-based classes
* [`pydantic`](https://pydantic-docs.helpmanual.io/)-based classes (`pydantic>=2` not yet supported)
* [`pydantic`](https://pydantic-docs.helpmanual.io/)-based classes

Additionally, interaction with arbitrary types is supported, by implementing
a pre-defined interface (see [extending `itemadapter`](#extending-itemadapter)).
Expand All @@ -28,7 +28,7 @@ a pre-defined interface (see [extending `itemadapter`](#extending-itemadapter)).
* [`scrapy`](https://scrapy.org/): optional, needed to interact with `scrapy` items
* [`attrs`](https://pypi.org/project/attrs/): optional, needed to interact with `attrs`-based items
* [`pydantic`](https://pypi.org/project/pydantic/): optional, needed to interact with
`pydantic`-based items (`pydantic>=2` not yet supported)
`pydantic`-based items

---

Expand Down Expand Up @@ -196,7 +196,7 @@ The returned value is taken from the following sources, depending on the item ty
for `dataclass`-based items
* [`attr.Attribute.metadata`](https://www.attrs.org/en/stable/examples.html#metadata)
for `attrs`-based items
* [`pydantic.fields.FieldInfo`](https://pydantic-docs.helpmanual.io/usage/schema/#field-customisation)
* [`pydantic.fields.FieldInfo`](https://docs.pydantic.dev/latest/api/fields/#pydantic.fields.FieldInfo)
for `pydantic`-based items

#### class method `get_field_names_from_class(item_class: type) -> Optional[list[str]]`
Expand Down
12 changes: 6 additions & 6 deletions itemadapter/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,24 +179,24 @@ def get_field_meta_from_class(cls, item_class: type, field_name: str) -> Mapping

@classmethod
def get_field_names_from_class(cls, item_class: type) -> Optional[List[str]]:
return list(item_class.__fields__.keys()) # type: ignore[attr-defined]
return list(item_class.model_fields.keys()) # type: ignore[attr-defined]

def field_names(self) -> KeysView:
return KeysView(self.item.__fields__)
return KeysView(self.item.model_fields)

def __getitem__(self, field_name: str) -> Any:
if field_name in self.item.__fields__:
if field_name in self.item.model_fields:
return getattr(self.item, field_name)
raise KeyError(field_name)

def __setitem__(self, field_name: str, value: Any) -> None:
if field_name in self.item.__fields__:
if field_name in self.item.model_fields:
setattr(self.item, field_name, value)
else:
raise KeyError(f"{self.item.__class__.__name__} does not support field: {field_name}")

def __delitem__(self, field_name: str) -> None:
if field_name in self.item.__fields__:
if field_name in self.item.model_fields:
try:
delattr(self.item, field_name)
except AttributeError:
Expand All @@ -205,7 +205,7 @@ def __delitem__(self, field_name: str) -> None:
raise KeyError(f"{self.item.__class__.__name__} does not support field: {field_name}")

def __iter__(self) -> Iterator:
return iter(attr for attr in self.item.__fields__ if hasattr(self.item, attr))
return iter(attr for attr in self.item.model_fields if hasattr(self.item, attr))

def __len__(self) -> int:
return len(list(iter(self)))
Expand Down
21 changes: 6 additions & 15 deletions itemadapter/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,21 @@ def _is_pydantic_model(obj: Any) -> bool:

def _get_pydantic_model_metadata(item_model: Any, field_name: str) -> MappingProxyType:
metadata = {}
field = item_model.__fields__[field_name].field_info
field = item_model.model_fields[field_name]

for attribute in [
"alias",
"title",
"description",
"const",
"gt",
"ge",
"lt",
"le",
"multiple_of",
"min_items",
"max_items",
"min_length",
"max_length",
"regex",
]:
value = getattr(field, attribute)
if value is not None:
metadata[attribute] = value
if not field.allow_mutation:
metadata["allow_mutation"] = field.allow_mutation
metadata.update(field.extra)
if field.frozen is not None:
metadata["frozen"] = field.frozen

if field.json_schema_extra is not None:
metadata.update(field.json_schema_extra)

return MappingProxyType(metadata)

Expand Down
12 changes: 4 additions & 8 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class AttrsItemEmpty:


try:
from pydantic import BaseModel, Field as PydanticField
from pydantic import ConfigDict, BaseModel, Field as PydanticField
except ImportError:
PydanticModel = None
PydanticSpecialCasesModel = None
Expand All @@ -125,11 +125,9 @@ class PydanticSpecialCasesModel(BaseModel):
special_cases: Optional[int] = PydanticField(
default_factory=lambda: None,
alias="special_cases",
allow_mutation=False,
frozen=False,
)

class Config:
validate_assignment = True
model_config = ConfigDict(validate_assignment=True)

class PydanticModelNested(BaseModel):
nested: PydanticModel
Expand All @@ -139,9 +137,7 @@ class PydanticModelNested(BaseModel):
set_: set
tuple_: tuple
int_: int

class Config:
arbitrary_types_allowed = True
model_config = ConfigDict(arbitrary_types_allowed=True)

class PydanticModelSubclassed(PydanticModel):
subclassed: bool = PydanticField(
Expand Down
2 changes: 1 addition & 1 deletion tests/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
attrs
pydantic<2
pydantic>2
pytest-cov>=2.8
pytest>=5.4
scrapy>=2.0
2 changes: 1 addition & 1 deletion tests/test_adapter_pydantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def test_true(self):
)
self.assertEqual(
get_field_meta_from_class(PydanticSpecialCasesModel, "special_cases"),
MappingProxyType({"alias": "special_cases", "allow_mutation": False}),
MappingProxyType({"alias": "special_cases", "frozen": False}),
)
with self.assertRaises(KeyError, msg="PydanticModel does not support field: non_existent"):
get_field_meta_from_class(PydanticModel, "non_existent")
Expand Down
Loading