# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['pydantic_duality']

package_data = \
{'': ['*']}

install_requires = \
['cached-classproperty>=0.3.0,<0.4.0',
 'pydantic>=1.9.2',
 'typing-extensions>=4.4.0']

setup_kwargs = {
    'name': 'pydantic-duality',
    'version': '1.0.0',
    'description': 'Automatically generate two versions of your pydantic models: one with Extra.forbid and one with Extra.ignore',
    'long_description': '# pydantic-duality\n\nAutomatically and lazily generate three versions of your pydantic models: one with Extra.forbid, one with Extra.ignore, and one with all fields optional\n\n---\n\n<p align="center">\n<a href="https://github.com/ovsyanka83/pydantic-duality/actions?query=workflow%3ATests+event%3Apush+branch%3Amain" target="_blank">\n    <img src="https://github.com/Ovsyanka83/pydantic-duality/actions/workflows/test.yaml/badge.svg?branch=main&event=push" alt="Test">\n</a>\n<a href="https://codecov.io/gh/ovsyanka83/pydantic-duality" target="_blank">\n    <img src="https://img.shields.io/codecov/c/github/ovsyanka83/pydantic-duality?color=%2334D058" alt="Coverage">\n</a>\n<a href="https://pypi.org/project/pydantic-duality/" target="_blank">\n    <img alt="PyPI" src="https://img.shields.io/pypi/v/pydantic-duality?color=%2334D058&label=pypi%20package" alt="Package version">\n</a>\n<a href="https://pypi.org/project/pydantic-duality/" target="_blank">\n    <img src="https://img.shields.io/pypi/pyversions/pydantic-duality?color=%2334D058" alt="Supported Python versions">\n</a>\n</p>\n\n## Installation\n\n```bash\npip install pydantic-duality\n```\n\n## Quickstart\n\nGiven the following models:\n\n```python\n\nfrom pydantic_duality import DualBaseModel\n\n\nclass User(DualBaseModel):\n    id: UUID\n    name: str\n\nclass Auth(DualBaseModel):\n    some_field: str\n    user: User\n```\n\nUsing pydantic-duality is roughly equivalent to making all of the following models by hand:\n\n```python\n\nfrom pydantic import BaseModel\n\n# Equivalent to User and User.__request__\nclass UserRequest(BaseModel, extra=Extra.forbid):\n    id: UUID\n    name: str\n\n# Rougly equivalent to Auth and Auth.__request__\nclass AuthRequest(BaseModel, extra=Extra.forbid):\n    some_field: str\n    user: UserRequest\n\n\n# Rougly equivalent to User.__response__\nclass UserResponse(BaseModel, extra=Extra.ignore):\n    id: UUID\n    name: str\n\n# Rougly equivalent to Auth.__response__\nclass AuthResponse(BaseModel, extra=Extra.ignore):\n    some_field: str\n    user: UserResponse\n\n\n# Rougly equivalent to User.__patch_request__\nclass UserPatchRequest(BaseModel, extra=Extra.forbid):\n    id: UUID | None\n    name: str | None\n\n# Rougly equivalent to Auth.__patch_request__\nclass AuthPatchRequest(BaseModel, extra=Extra.forbid):\n    some_field: str | None\n    user: UserPatchRequest | None\n\n```\n\nSo it takes you up to 4 times less code to write the same thing. Note also that pydantic-duality does everything lazily so you will not notice any significant performance or memory usage difference when using it instead of pydantic-duality. Think of it as using all the customized models as cached properties.\n\nIt works well and as expected with inheritance, inner models, custom configs, config kwargs, isinstance and subclass checks, and much more!\n\n## Use case\n\n### Problem\n\nIn API design, it is a good pattern to forbid any extra data from being sent to your endpoints. By default, pydantic just ignores extra data in FastAPI requests. You can fix that by passing `extra = Extra.forbid` to your model\'s config. However, we needed to use Extra.ignore in our response models because we might send a lot more data than required with our responses. But then we get into the following conundrum:\n\n```python\nclass User(BaseModel):\n    id: UUID\n    name: str\n\n\nclass AuthResponse(BaseModel):\n    some_field: str\n    user: User\n\n\nclass AuthRequest(SomeResponse, extra=Extra.forbid):\n    pass\n```\n\nNow you have a problem: even though `SomeRequest` is `Extra.forbid`, `User` is not. It means that your clients can still pass the following payload without any issues:\n\n```json\n{\n    "some_field": "value",\n    "user": {"id": "e65014c9-4990-4b8d-8ce7-ab5a34ab41bc", "name": "Ovsyanka", "hello": "world"}\n}\n```\n\nThe easiest way to solve this is to have `UserRequest` and `UserResponse`, and duplicate this field in your models:\n\n```python\nclass UserResponse(BaseModel):\n    id: UUID\n    name: str\n\n\nclass UserRequest(UserResponse, extra=Extra.forbid):\n    pass\n\n\nclass AuthResponse(BaseModel):\n    some_field: str\n    user: UserResponse\n\n\nclass AuthRequest(SomeResponse, extra=Extra.forbid):\n    user: UserRequest\n```\n\nNow imagine that users also have the field named "address" that points to some `Address` model. Essentially nearly all of your models will need to be duplicated in a similar manner, leading to almost twice as much code.\n\nWhen we faced this conundrum, we already had an enormous code base so the duplication solution would be a tad too expensive.\n\n### Solution\n\npydantic-duality does this code duplication for you in an intuitive manner automatically. Here\'s how the models above would look if we used it:\n\n```python\nfrom pydantic_duality import DualBaseModel\n\nclass User(DualBaseModel):\n    id: UUID\n    name: str\n\n\nclass Auth(DualBaseModel):\n    some_field: str\n    user: User\n```\n\nYou would use the models above as follows:\n\n```python\nAuth.__request__.parse_object(\n    {\n        "some_field": "value",\n        "user": {"id": "e65014c9-4990-4b8d-8ce7-ab5a34ab41bc", "name": "Ovsyanka"}\n    }\n)\n\nAuth.__response__.parse_object(\n    {\n        "some_field": "value",\n        "user": {"id": "e65014c9-4990-4b8d-8ce7-ab5a34ab41bc", "name": "Ovsyanka", "hello": "world"}\n    }\n)\n```\n\n### Patch requests\n\nWe applied the same principles to solve the problem of schemas for patching objects. Usually these schemas are one-to-one equivalent to regular request schemas except that all fields are nullable. If you wish to do the same thing automatically, you can use `__patch_request__` attribute similar to how you would use `__request__` and `__response__`.\n\n## Usage\n\n### Creation\n\nModels are created in the exact same manner as pydantic models but you use our `DualBaseModel` as base instead of `BaseModel`.\n\n```python\nfrom pydantic_duality import DualBaseModel\n\nclass User(DualBaseModel):\n    id: UUID\n    name: str\n\n\nclass Auth(DualBaseModel):\n    some_field: str\n    user: User\n```\n\nIf you wish to provide your own base config for all of your models, you can do:\n\n```python\nfrom pydantic_duality import generate_dual_base_model\n\n# Any configuration options you like\nclass MyConfig:\n    orm_mode = True\n    ...\n\n\nDualBaseModel = generate_dual_base_model(MyConfig)\n```\n\n### Parsing\n\n#### Default\n\nWhenever you do not want to use pydantic-duality\'s features, you can use your models as if they were regular pydantic models. For example:\n\n```python\n\nclass User(DualBaseModel):\n    id: UUID\n    name: str\n\n\nuser = User(id="e65014c9-4990-4b8d-8ce7-ab5a34ab41bc", name="Ovsyanka")\nprint(user.dict())\n```\n\nThis is possible because `User` is nearly equivalent to `User.__request__`. It has all the same fields, operations, and hash value. issubclass and isinstance checks will also show that instances of `User.__request__` are also instances of `User`. It is, however, important to realize that `User is not User.__request__`, it just tries to be as similar as possible.\n\n#### Advanced\n\nIf you need to use `__response__` version or both versions of your model, you can do so through `__request__` and `__response__` attributes. They will give you an identical model with only the difference that `__request__` has Extra.forbid and `__response__` has Extra.ignore.\n\n```python\n\nclass User(DualBaseModel):\n    id: str\n    name: str\n\n\nUser.__request__(id="e65014c9", name="John", hello="world") # ValidationError\nUser.__response__(id="e65014c9", name="John", hello="world") # UserResponse(id="e65014c9", name="John")\nUser.__patch_request__(id="e65014c9") # UserResponse(id="e65014c9", name=None)\n```\n\n### Customizing schema names\n\nIf you need to customize the names of your model\'s alternatives (`__request__`, `__response__`, etc), you can pass `request_suffix`, `response_suffix`, and/or `patch_request_suffix` during model creation:\n\n```python\nclass User(DualBaseModel, request_suffix="ForbidVersion", response_suffix="IgnoreVersion"):\n    id: str\n    name: str\n\nprint(User.__request__.__name__) # UserForbidVersion\nprint(User.__response__.__name__) # UserIgnoreVersion\n```\n\nThese attributes will also be inherited by all child models:\n\n```python\nclass UserWithAge(User):\n    age: int\n\n\nprint(UserWithAge.__request__.__name__) # UserWithAgeForbidVersion\nprint(UserWithAge.__response__.__name__) # UserWithAgeIgnoreVersion\n```\n\n### FastAPI integration\n\npydantic-duality works with FastAPI out of the box. Note, however, that if you want to use Extra.ignore schemas for responses, you have to specify it explicitly with `response_model=MyModel.__response__`. Otherwise the Extra.forbid schema will be used.\n\n### Configuration override\n\nIf you specify extra=Extra.forbid or extra=Extra.ignore on your model explicitly, then pydantic-duality will not change its or its children\'s extra configuration. Nested models will still be affected as you might expect.\n\n### Editor support\n\nThis package is fully type hinted. mypy, pyright, and pycharm will detect that `__response__` and `__request__` attributes are equivalent to your model so you have full full editor support for them.\n\n`__patch_request__` is not well supported: pyright and mypy will still think that the model\'s attributes are non-nullable.\n',
    'author': 'Stanislav Zmiev',
    'author_email': 'szmiev2000@gmail.com',
    'maintainer': 'None',
    'maintainer_email': 'None',
    'url': 'https://github.com/Ovsyanka83/pydantic-duality',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'python_requires': '>=3.10,<4.0',
}


setup(**setup_kwargs)
