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

packages = \
['pydantic_duality']

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

install_requires = \
['pydantic>=1.9.2', 'typing-extensions>=4.4.0']

setup_kwargs = {
    'name': 'pydantic-duality',
    'version': '0.2.3',
    'description': 'Automatically generate two versions of your pydantic models: one with Extra.forbid and one with Extra.ignore',
    'long_description': '# pydantic-duality\n\nAutomatically generate two versions of your pydantic models: one with Extra.forbid and one with Extra.ignore\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## 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 ConfigMixin\n\nclass User(ConfigMixin):\n    id: UUID\n    name: str\n\n\nclass Auth(ConfigMixin):\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## Usage\n\n### Creation\n\nModels are created in the exact same manner as pydantic models but you use our `ConfigMixin` as base instead of `BaseModel`.\n\n```python\nfrom pydantic_duality import ConfigMixin\n\nclass User(ConfigMixin):\n    id: UUID\n    name: str\n\n\nclass Auth(ConfigMixin):\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_config_mixin\n\n# Any configuration options you like\nclass MyConfig:\n    orm_mode = True\n    ...\n\n\nConfigMixin = generate_config_mixin(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(ConfigMixin):\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(ConfigMixin):\n    id: UUID\n    name: str\n\n\nUser.__request__(id="e65014c9-4990-4b8d-8ce7-ab5a34ab41bc", name="Ovsyanka", hello="world") # ValidationError\nUser.__response__(id="e65014c9-4990-4b8d-8ce7-ab5a34ab41bc", name="Ovsyanka", hello="world") # UserResponse object without "hello" field\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.\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)
