Code Monkey home page Code Monkey logo

adaptix's Introduction

adaptix logo

PyPI version downloads versions license

An extremely flexible and configurable data model conversion library.

Important

Adaptix is ready for production! The beta version only means there may be some backward incompatible changes, so you need to pin a specific version.

📚 Documentation

TL;DR

Install

pip install adaptix==3.0.0b5

Use for model loading and dumping.

from dataclasses import dataclass

from adaptix import Retort


@dataclass
class Book:
    title: str
    price: int


data = {
    "title": "Fahrenheit 451",
    "price": 100,
}

# Retort is meant to be global constant or just one-time created
retort = Retort()

book = retort.load(data, Book)
assert book == Book(title="Fahrenheit 451", price=100)
assert retort.dump(book) == data

Use for converting one model to another.

from dataclasses import dataclass

from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

from adaptix.conversion import get_converter


class Base(DeclarativeBase):
    pass


class Book(Base):
    __tablename__ = 'books'

    id: Mapped[int] = mapped_column(primary_key=True)
    title: Mapped[str]
    price: Mapped[int]


@dataclass
class BookDTO:
    id: int
    title: str
    price: int


convert_book_to_dto = get_converter(Book, BookDTO)

assert (
    convert_book_to_dto(Book(id=183, title="Fahrenheit 451", price=100))
    ==
    BookDTO(id=183, title="Fahrenheit 451", price=100)
)

Use cases

  • Validation and transformation of received data for your API.
  • Conversion between data models and DTOs.
  • Config loading/dumping via codec that produces/takes dict.
  • Storing JSON in a database and representing it as a model inside the application code.
  • Creating API clients that convert a model to JSON sending to the server.
  • Persisting entities at cache storage.
  • Implementing fast and primitive ORM.

Advantages

  • Sane defaults for JSON processing, no configuration is needed for simple cases.
  • Separated model definition and rules of conversion that allow preserving SRP and have different representations for one model.
  • Speed. It is one of the fastest data parsing and serialization libraries.
  • There is no forced model representation, adaptix can adjust to your needs.
  • Support dozens of types, including different model kinds: @dataclass, TypedDict, NamedTuple, attrs, sqlalchemy and pydantic.
  • Working with self-referenced data types (such as linked lists or trees).
  • Saving path where an exception is raised (including unexpected errors).
  • Machine-readable errors that could be dumped.
  • Support for user-defined generic models.
  • Automatic name style conversion (e.g. snake_case to camelCase).
  • Predicate system that allows to concisely and precisely override some behavior.
  • Disabling additional checks to speed up data loading from trusted sources.
  • No auto casting by default. The loader does not try to guess value from plenty of input formats.

adaptix's People

Contributors

andrewsergienko avatar culnaen avatar daler-sz avatar decorator-factory avatar eclips4 avatar lubaskinc0de avatar marshalx avatar niccolum avatar nomilkinmyhome avatar tdakkota avatar tishka17 avatar uvicorn avatar zhpavel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

adaptix's Issues

Deserialization fails for Optional[Dict[...]]

Trying to deserialize a nullable Dict[T, U] results in get_dict_parser being called anyway and raising an AttributeError due to the target dict being None.

Testing with Python 3.7 and dataclass_factory 2.4.1 the following code will raise:

from typing import Optional, Dict, Any
from dataclass_factory import Factory

@dataclass
class Test:
    x: Optional[Dict[str, Any]]

f = Factory()
f.load({"x": None}, Test)

The root issue seems to be that Optional is not being detected correctly as it is indistinguishable from Union[T, NoneType], which ends up in get_dict_parser raising due to the order of the type arguments.

'list' can't work

import dataclass_factory
from dataclasses import dataclass


@dataclass
class Problem:
    name: str
    eventid: int
    urls: list


data = {
    'name': 'TestProblem',
    'eventid': 1,
    'urls': []
}

factory = dataclass_factory.Factory()
problem: Problem = factory.load(data, Problem)
print(problem)
Traceback (most recent call last):
  File "C:/Users/themida/Documents/project/test.py", line 19, in <module>
    problem: Problem = factory.load(data, Problem)
  File "C:\Users\themida\Documents\project\venv\lib\site-packages\dataclass_factory\factory.py", line 118, in load
    return self.parser(class_)(data)
  File "C:\Users\themida\Documents\project\venv\lib\site-packages\dataclass_factory\factory.py", line 77, in parser
    return self._parser_with_stack(class_, StackedFactory(self))
  File "C:\Users\themida\Documents\project\venv\lib\site-packages\dataclass_factory\factory.py", line 93, in _parser_with_stack
    schema.parser = create_parser(stacked_factory, schema, self.debug_path, class_)
  File "C:\Users\themida\Documents\project\venv\lib\site-packages\dataclass_factory\parsers.py", line 256, in create_parser
    parser = create_parser_impl(factory, schema, debug_path, cls)
  File "C:\Users\themida\Documents\project\venv\lib\site-packages\dataclass_factory\parsers.py", line 340, in create_parser_impl
    parsers = {
  File "C:\Users\themida\Documents\project\venv\lib\site-packages\dataclass_factory\parsers.py", line 341, in <dictcomp>
    field.name: factory.parser(resolved_hints[field.name])
  File "C:\Users\themida\Documents\project\venv\lib\site-packages\dataclass_factory\factory.py", line 29, in parser
    return self.factory._parser_with_stack(class_, self)
  File "C:\Users\themida\Documents\project\venv\lib\site-packages\dataclass_factory\factory.py", line 93, in _parser_with_stack
    schema.parser = create_parser(stacked_factory, schema, self.debug_path, class_)
  File "C:\Users\themida\Documents\project\venv\lib\site-packages\dataclass_factory\parsers.py", line 256, in create_parser
    parser = create_parser_impl(factory, schema, debug_path, cls)
  File "C:\Users\themida\Documents\project\venv\lib\site-packages\dataclass_factory\parsers.py", line 316, in create_parser_impl
    if args_unspecified(cls):
  File "C:\Users\themida\Documents\project\venv\lib\site-packages\dataclass_factory\type_detection.py", line 119, in args_unspecified
    (not cls.__args__ and cls.__parameters__) or
AttributeError: type object 'list' has no attribute '__args__'

Leave None type as it is.

I have a dataclass which str item default is "a" and int item default is 2.

But in data dict, their value is None.

When I call factory.load(data, DataClass), I want to leave the None type as it is, don't change it.
Or I don't want to get an exception when type .

Any solution?
Thanks

Store unparsed fields separately

Store all extra input fields, which absent in dataclass, separately in field with name from schema.rest, if provided.

Also unpack rest when serialising

Validate TypedDict

  1. Support of creating TypedDict instances (from both mypy_extensions and typing) from normal Dict.
  2. Do not break compatibility with 3.6
  3. Do not add new dependencies
  4. Need validation of __total__

Prepare data before passing to custom parser

Custom parser can be provided in Schema.

It is ok to parse data in dict before passing to custom parser or provide a way to call use factory inside custo mparser.

  1. Pass factory instance
  2. Check custom parser anotations
  3. Add zero-step parsing setting (e.g. link to other type)

Recursive parsing

@dataclass 
class List:
   data: int
   next: Optional[List]

This should be parsed as well

Serialization

  • name styles
  • dataclass
  • list
  • dict
  • tuple
  • Any
  • Optional
  • Union
  • classes
  • cache serializers
  • custom serializers

json factory wrapper

class JsonFactory:
   ...

   def dump(self, data, type):
      return json.dumps(self.real_factory.dump(data, type))

   def load(self, data, type):
      return self.real_facory.load(json.loads(data), type)

Incorrect parsing of tuple

Tuple type; Tuple[X, Y] is the type of a tuple of two items with the first item of type X and the second of type Y.

Example: Tuple[T1, T2] is a tuple of two elements corresponding to type variables T1 and T2. Tuple[int, float, str] is a tuple of an int, a float and a string.

To specify a variable-length tuple of homogeneous type, use literal ellipsis, e.g. Tuple[int, ...]. A plain Tuple is equivalent to Tuple[Any, ...], and in turn to tuple.

Ошибка парсинга иерахии, если она начинается со списка

@dataclass
class A:
    x: str
    y: str


schema_num = Schema[A](
    name_mapping={
        "x": (1, "a", "b"),
    }
)


class Test1(TestCase):
    def test_load_num(self):
        factory = Factory(
            schemas={
                A: schema_num
            }
        )
        data = {
            "a": [None, {"b": "hello"}],
            "y": "world"
        }
        expected = A("hello", "world")
        self.assertEqual(expected, factory.load(data, A))

Ошибка:

======================================================================
ERROR: test_load_num (test_path.Test1)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tishka17/src/dataclass_factory/tests/test_path.py", line 39, in test_load_num
    self.assertEqual(expected, factory.load(data, A))
  File "/home/tishka17/src/dataclass_factory/dataclass_factory/factory.py", line 94, in load
    return self.parser(class_)(data)
  File "/home/tishka17/src/dataclass_factory/dataclass_factory/parsers.py", line 139, in dataclass_parser
    for field, name, parser in field_info
TypeError: __init__() missing 1 required positional argument: 'x'


Polymorphic parsing

Create instruction or provide builin mechanism for creating polymorphics parsers.

Ошибка парсинга datetime если он уже нужного типа

from dataclasses import dataclass
from dataclass_factory import Factory
from datetime import datetime


@dataclass()
class Traffic:
    time: datetime
    rx: int
    tx: int


if __name__ == '__main__':
    dict_traff = {
        'time': datetime(2019, 12, 1, 1, 20, 13),
        'rx': 10,
        'tx': 15
    }

    factory = Factory()
    traff: Traffic = factory.load(dict_traff, Traffic)
    print(factory.dump(traff))
    
    

Ошибка:

Traceback (most recent call last):
  File "E:/NMS/NMSProgect/modules/traffic.py", line 24, in <module>
    traff: Traffic = factory.load(dict_traff, Traffic)
  File "E:\NMS\lib\site-packages\dataclass_factory\factory.py", line 94, in load
    return self.parser(class_)(data)
  File "E:\NMS\lib\site-packages\dataclass_factory\parsers.py", line 139, in dataclass_parser
    for field, name, parser in field_info
  File "E:\NMS\lib\site-packages\dataclass_factory\parsers.py", line 140, in <dictcomp>
    if name in data
  File "E:\NMS\lib\site-packages\dataclass_factory\parsers.py", line 186, in class_parser
    k: parser(data.get(k)) for k, parser in parsers.items() if k in data
  File "E:\NMS\lib\site-packages\dataclass_factory\parsers.py", line 186, in <dictcomp>
    k: parser(data.get(k)) for k, parser in parsers.items() if k in data
TypeError: argument of type 'datetime.datetime' is not iterable

Load/dump many objects

Do you think to write the example in the docs with many parameter (like marshmallow), i.e. list comprehesion or as an alternative with a new dataclass and List[Item]?

very few examples with namestyles

What will happen if some of the fields are in one style and some in another? How does it work in practice? I would like to see more examples in Readme.nd

Поддержать работу с Generic

Пример:

T = TypeVar('T')

@dataclass
class Foo(Generic[T]):
    value: T

Должен парситься Foo[int]
Он не детектится как датакласс, надо работать с Origin

Validate Literal

  • check value
  • check type
  • support multiple values
  • both typing in python 3.8 and typing_extensions

model inheritance

Допустим есть классы с наследованием

@dataclass
class A:
   a: int

@dataclass
class B(A):
   b: int

Сейчас, если мы хотим сделать предобработку поля a, мы должны сделать две схемы: для класс A и для класса B и в обеих определить pre_parse.

Необходимо продумать как это делать более удобно

Skip default None

When serialising skip None values if it is also set as default

We need new field in Schema and change logic of dataclass serializer.

Skipping None for all Optionals is not safe, as object cannot be created from such a dict.

Skipping other defaults is not safe enough, but can be separatly implemented

Setting this feature for each field should be done separately

Implement update_forward_refs for delayed forward refs calculation

аналогичная функциональность в Pydantic

model_a.py

from pydantic import BaseModel


class ModelA(BaseModel):
    field: int

model_b.py

from __future__ import annotations

from typing import TYPE_CHECKING

from pydantic import BaseModel

if TYPE_CHECKING:
    from model_a import ModelA


class ModelB(BaseModel):
    field: int
    a: ModelA

main.py

from model_b import ModelB

print(ModelB.__fields__)
-> {'field': ModelField(name='field', type=int, required=True), 'a': ModelField(name='a', type=ForwardRef('ModelA'), required=PydanticUndefined)}

по type=ForwardRef и в этом месте неизвестно что именно из себя представляет ModelA для ModelB потому что его нет в скоупе модуля model_b.

для этого есть ModelB.update_forward_refs()
вызываем из main.py
только прямой вызов ModelB.update_forward_refs() ничего не сделает - NameError: name 'ModelA' is not defined, потому что в модуле где находится класс все еще нет такого имени

делаем следующее - ModelB.update_forward_refs(ModelA=ModelA)

и далее снова смотрим поля:
{'field': ModelField(name='field', type=int, required=True), 'a': ModelField(name='a', type=ModelA, required=True)}
ModelA теперь имеет реальный тип а не референс

Deserialize recursive hierarchy of optional lists

How can I deserialize a recursive structure?

I have seen #17 but I am getting errors trying to deserialize the following:

@dataclass(frozen=True)
class JsonSport:
    sport_id: int
    sport_name: str
    sport_description: Optional[str]
    sports: Optional[List["JsonSport"]]

The error is
dataclass_factory.exceptions.InvalidFieldError: Invalid data at path [sports]: No suitable parsers in union found for [{'sportId': 63, 'sportName': 'Idrettsskoler', 'sportDescription': 'Idrett generelt...`

Typing support

Hi! Thanks for the awesome project.

It looks like you have covered almost all of your source code with type annotations, but currently it is not shipped to the final user.

I suggest to fix this situation. Here's how:

  1. Create py.typed file in the root of the dataclass_factory folder, it is required for PEP561 to work. Example: https://github.com/dry-python/returns/blob/master/returns/py.typed
  2. Change how public API is typed. Some method do miss return values. Some of them are missing generic parameters. Like this one: https://github.com/Tishka17/dataclass_factory/blob/master/dataclass_factory/factory.py#L80 It should be something like: def load(self, data: Any, class_: Type[T]) -> T: (do not forget about Generic base class)
  3. Integrate mypy to check these annotations

This feature would improve developer experience and make this project even more awesome!

Parsing interfaces/protocols

Add to schema "impl" attribute which provides real type of interface.

So factory.load(data, Interface) will be equal to factory.load(data, impl)
Also must be applyed to fields with Interface type

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.