Skip to content
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
20 changes: 0 additions & 20 deletions .github/workflows/github-actions-lint.yml

This file was deleted.

20 changes: 0 additions & 20 deletions .github/workflows/github-actions-mypy.yml

This file was deleted.

20 changes: 8 additions & 12 deletions .github/workflows/github-actions-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,18 @@ jobs:
fail-fast: false
matrix:
python-version: [ "3.9", "3.10", "3.11", "3.12" ]
poetry-version: [ 1.2 ]
pydantic-version: [ 1.10.0, 2.11 ]
os: [ ubuntu-latest ]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Run image
uses: abatilo/actions-poetry@v2
- name: Set up uv
uses: astral-sh/setup-uv@v5
with:
poetry-version: ${{ matrix.poetry-version }}
- name: poetry install
run: poetry install
- name: pydantic install
run: poetry add pydantic==${{ matrix.pydantic-version }}
enable-cache: true
cache-dependency-glob: "uv.lock"
- name: run tests
run: poetry run pytest
run: uv run pytest
27 changes: 16 additions & 11 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
repos:
- repo: https://github.com/ambv/black
rev: 22.12.0
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.7
hooks:
- id: black
language_version: python3.12
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
hooks:
- id: flake8
hooks:
# Run the linter.
- id: ruff-check
args: [ --fix ]
# Run the formatter.
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.991
rev: v1.17.1
hooks:
- id: mypy
exclude: ^tests/

additional_dependencies:
- types-click
- types-toml
- repo: https://github.com/astral-sh/uv-pre-commit
rev: 0.8.6
hooks:
- id: uv-lock
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ This library provides a lazy interface for parsing objects from dictionaries. Du

## Install

uv
```shell
uv add lazy-model
```

poetry
```shell
poetry add lazy-model
Expand Down
4 changes: 2 additions & 2 deletions lazy_model/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from lazy_model.main import LazyModel
from lazy_model.nao import NAO
from lazy_model.parser.lazy_model import LazyModel

__all__ = ["LazyModel", "NAO"]
__all__ = ["NAO", "LazyModel"]
12 changes: 0 additions & 12 deletions lazy_model/main.py

This file was deleted.

5 changes: 4 additions & 1 deletion lazy_model/nao.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
class NotAnObject:
def __repr__(self):
def __init__(self) -> None:
pass

def __repr__(self) -> str:
return "NAO"


Expand Down
40 changes: 22 additions & 18 deletions lazy_model/parser/new.py → lazy_model/parser/lazy_model.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
from typing import TYPE_CHECKING, Any, Optional, Set, Dict
from __future__ import annotations

from typing import TYPE_CHECKING, Any, TypeVar

import pydantic_core
from pydantic import BaseModel, PrivateAttr, ValidationError, TypeAdapter
from pydantic import BaseModel, PrivateAttr, TypeAdapter, ValidationError
from pydantic._internal import _fields

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize this comment is not related to the functionality this PR introduces but, I'm really struggling to see why do we need to import from Pydantic internal stuff.
The only method we use is on ln 76, the is_valid_field_name method and we could simply "vendor it in" ourselves as a helper method.


from lazy_model.nao import NAO

ROOT_KEY = "__root__"
_object_setattr = object.__setattr__

T = TypeVar("T")


class LazyModel(BaseModel):
_store: Dict[str, Any] = PrivateAttr(default_factory=dict)
_store: dict[str, Any] = PrivateAttr(default_factory=dict)
_lazily_parsed: bool = PrivateAttr(default=False)

@classmethod
def lazy_parse(
cls,
data: Dict[str, Any],
fields: Optional[Set[str]] = None,
):
data: dict[str, Any],
fields: set[str] | None = None,
) -> LazyModel:
fields_values = {}
field_alias_map = {}
if fields is None:
Expand All @@ -37,21 +41,21 @@ def lazy_parse(
m._set_attr(field_alias_map[alias], data[alias])
return m

def _parse_value(self, name, value):
field_type = self.__class__.model_fields.get(name).annotation
def _parse_value(self, name: str, value: Any) -> Any:
field_type = self.__class__.model_fields.get(name).annotation # type: ignore
try:
value = TypeAdapter(field_type).validate_python(value)
except ValidationError as e:
if (
value is None
and self.__class__.model_fields.get(name).required is False
and self.__class__.model_fields.get(name).required is False # type: ignore
):
value = None
else:
raise e
return value

def parse_store(self):
def parse_store(self) -> None:
for name in self.__class__.model_fields:
self.__getattribute__(name)

Expand All @@ -66,8 +70,8 @@ def _set_attr(self, name: str, value: Any) -> None:
"""
if name in self.__class_vars__:
raise AttributeError(
f"{name!r} is a ClassVar of `{self.__class__.__name__}` and cannot be set on an instance. " # noqa: E501
f"If you want to set a value on the class, use `{self.__class__.__name__}.{name} = value`." # noqa: E501
f"{name!r} is a ClassVar of `{self.__class__.__name__}` and cannot be set on an instance. "
f"If you want to set a value on the class, use `{self.__class__.__name__}.{name} = value`."
)
elif not _fields.is_valid_field_name(name):
if (
Expand All @@ -78,7 +82,7 @@ def _set_attr(self, name: str, value: Any) -> None:
else:
attribute = self.__private_attributes__[name]
if hasattr(attribute, "__set__"):
attribute.__set__(self, value) # type: ignore
attribute.__set__(self, value)
else:
self.__pydantic_private__[name] = value
return
Expand All @@ -99,27 +103,27 @@ def _set_attr(self, name: str, value: Any) -> None:

if not TYPE_CHECKING:

def __getattribute__(self, item):
def __getattribute__(self, item: str) -> Any:
# If __class__ is accessed, return it directly to avoid recursion
if item == "__class__":
return super().__getattribute__(item)

# If called on the class itself,
# delegate to super's __getattribute__
if type(self) is type: # Check if self is a class
return super(type, self).__getattribute__(item)
return super().__getattribute__(item)

# For instances, use the object's __getattribute__
# to prevent recursion
res = object.__getattribute__(self, item)
if res is NAO:
field_info = self.__class__.model_fields.get(item)
alias = field_info.alias or item
alias = field_info.alias or item # type: ignore
value = self._store.get(alias, NAO)
if value is NAO:
value = field_info.get_default()
value = field_info.get_default() # type: ignore
else:
value = self._parse_value(item, value)
self._set_attr(item, value)
res = super(LazyModel, self).__getattribute__(item)
res = super().__getattribute__(item)
return res
128 changes: 0 additions & 128 deletions lazy_model/parser/old.py

This file was deleted.

Loading