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

packages = \
['repka', 'repka.repositories']

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

install_requires = \
['aiopg>=1,<2',
 'pydantic>=1.0,<2.0',
 'sqlalchemy>=1.3,<2.0',
 'typing_inspect>=0.5.0,<0.6.0']

setup_kwargs = {
    'name': 'repka',
    'version': '3.2.0',
    'description': 'Python repository pattern implementation',
    'long_description': '# repka\n\n[![PyPI](https://img.shields.io/pypi/v/repka)](https://pypi.org/project/repka/)\n[![CI](https://travis-ci.org/potykion/repka.svg?branch=master)](https://travis-ci.org/github/potykion/repka)\n\nRepository pattern implementation - isolate db manipulation from domain models\n\n## Installation\n\nVia pip:\n\n```\npip install repka\n```\n\nVia poetry:\n\n```\npoetry add repka\n```\n\n## Usage\n\n### repka.api.BaseRepository\n\nBaseRepository used to execute sql-queries (via [aiopg & sqlalchemy](https://github.com/aio-libs/aiopg)) and convert sql-rows to/from [pydantic](https://github.com/samuelcolvin/pydantic) models\n\n```python\nimport sqlalchemy as sa\nfrom repka.api import AiopgRepository, IdModel\nfrom repka.utils import create_async_db_connection\n\n# Define pydantic model\n# It should inherit repka.api.IdModel \n#   to set id on entity insert, to update entity with id and more\n# IdModel inherits pydantic.BaseModel and defines int id field\nclass Task(IdModel):\n    title: str\n\n# Define sqlachemy table with same model columns\nmetadata = sa.MetaData()\ntasks_table = sa.Table(\n    "tasks", metadata,\n    sa.Column("id", sa.Integer, primary_key=True, autoincrement=True),\n    sa.Column("title", sa.String)\n)\n\n# Define repository\n# You should inherit repka.api.BaseRepository and \n#   set sqlalchemy-table via table property \n# Kwargs is sql-row data returned by sqlalchemy  \nclass TaskRepo(AiopgRepository[Task]):\n    table = tasks_table\n\n# To use the repository you should instantiate it with async sqlalchemy-connection\ndb_url = "postgresql://postgres@localhost/test"\nasync with create_async_db_connection(db_url) as conn:\n    repo = TaskRepo(conn)\n\n    # Now you can use the repo\n    # Here we select first task with matching title\n    task = await repo.first(tasks_table.c.title == "My first task")\n```\n\n#### BaseRepository methods\n\n>`T` means generic type passed to BaseRepository (e.g. `BaseRepository[Task]` means that type of `T` is `Task`)\n\n##### Select methods\n\n- `repo.first(*filters: BinaryExpression, orders: Optional[Columns])` - get first entity matching sqlalchemy {filters} and {orders}; if no entity matches {filters} then `None` is returned\n    \n    > Example of {filters}: `table.c.title == \'test task\'` - equals to sql where clause: `where title = \'test task\'` \n \n    > Example of {orders}: `table.c.title` - equals to sql order by clause: `order by title`\n \n- `repo.get_by_ids(entity_ids: List[int])` - get all entities whose id in {entity_ids} (same as sql `where id in ({entity_ids})`)\n- `repo.get_by_id(entity_id: int)` - get entity with id = {entity_id}\n- `repo.get_or_create(filters: Optional[List[BinaryExpression]], defaults: Optional[Dict])` - get entity that matches {filters} if no entity found create new entity with {defaults}; return tuple of entity and entity existence flag\n- `repo.get_all(filters: Optional[List[BinaryExpression]], orders: Optional[Columns])` - return all entities matching {filters} and {orders}\n- `repo.get_all_ids(filters: Optional[List[BinaryExpression]], orders: Optional[Columns])` - return ids of entites matching {filters} and {orders}\n- `repo.exists(*filters: BinaryExpression)` - check that entity matching {filters} exists using sql `count` statement\n\n##### Insert methods\n\n- `repo.insert(entity: T)` - insert entity to db table and set id field to the entity\n- `repo.insert_many(entities: List[T])` - insert multiple entities and set ids to them in single transaction\n\n##### Update methods\n\n- `repo.update(entity: T)` - updates entity in db\n- `repo.update_partial(entity: T, **updated_values)` - update entity fields via kwargs and update entity fields in db\n- `repo.update_many(entities: List[T])` - update multiple entities in single transaction\n\n##### Delete methods\n\n- `repo.delete(*filters: BinaryExpression)` - delete entities matching {filters} via sql `delete` statement\n\n    > To delete all entities pass `None` as an arg: `repo.delete(None)`   \n\n- `repo.delete_by_id(entity_id: int)` - delete entity with {entity_id}\n- `repo.delete_by_ids(entity_ids: List[int])` - delete entities whose id in {entity_ids}\n\n##### Other methods & properties\n\n- `repo.serialize(entity: T)` - convert {entity} to dict (e.g. in `insert` and `update` methods)  \n- `repo.deserialize(**kwargs)` - convert {kwargs} to entity (e.g. in `first` and `get_all` methods)\n- `repo.execute_in_transaction()` - context manager that allows execute multiple queries in transaction \n\n    Example: delete all old entities and insert new one in single transaction:\n    \n    ```python\n    async with repo.execute_in_transaction():\n      await repo.delete()\n      await repo.insert(Task(title="New task"))\n    ``` \n  \n- `repo.ignore_default` - list of entity fields that will be ignored on insert and set after insert if they equal to default field value. \nUseful for auto incrementing / default fields like dates or sequence numbers\n\n#### ContextVar support\n\nYou can create lazy-connection repositories with context vars\n\n```python\nfrom contextvars import ContextVar\nfrom repka.utils import create_async_db_connection\n\n# Create context var and instantiate repository\ndb_connection = ContextVar("db_connection")\nrepo = TaskRepo(db_connection)\n\n# Now you should set the context var somewhere (e.g. in middleware)\n#   And start using the repository\nasync with create_async_db_connection(db_url) as conn:\n    db_connection.set(conn)\n\n    await repo.insert(Task(title="New task"))\n```\n\n#### Other sqlalchemy repositories\n\nFollowing repositories have same api as `AiopgRepository` (select methods, insert methods, etc.)\n\n- `repka.api.FakeRepo` - repository that uses lists instead of database tables, can be used as mock\n    - This repository is implemented partially, because implementing sqlalchemy features (like filters or orders) is hard and pointless for python lists \n\n### repka.json_.DictJsonRepo\n\nThis kind of repository used to save/load json objects from file:\n\n```python\nfrom repka.json_ import DictJsonRepo\n\nsongs = [\n    {"artist": "Pig Destroyer", "title": "Thumbsucker"}, \n    {"artist": "Da Menace", "title": "Bag of Funk"}\n]\n\nrepo = DictJsonRepo()\n\nrepo.write(songs, "songs.json")\n\nassert repo.read("songs.json") == songs\n```\n\n#### DictJsonRepo methods\n\n- `repo.read(filename: str)` - read json file with {filename}, return its content as json primitive (list, dict, str, etc.)\n\n- `repo.write(data: T, filename: str)` - serialize json primitive {data} and save it to file with {filename}\n\n- `repo.read_or_write_default(filename: str, default_factory: Callable[[], T])` - check file with {filename} exists, read its content if exists, execute {default_factory} and write it to file with {filename} otherwise\n    \n    - Example: read data from `test.json` or create `test.json` with `[{"field": "value"}]` if no such file: \n    \n        ```python\n        repo = DictJsonRepo()\n        repo.read_or_write_default("test.json", lambda: [{"field": "value"}])\n        ```\n\n#### DictJsonRepo constructor\n\n- `DictJsonRepo(directory: str)` - set directory where files will be read / written; if not set current working directory will be used  \n\n    - Example: read files from `data/` dir: \n    \n        ```python\n        repo = DictJsonRepo("data")\n        repo.read("test.json") # will read "./data/test.json"\n        ``` \n    \n## Development and contribution\n\n### Dependencies \n\nInstall production and development dependencies via poetry:\n\n```\npoetry install\n```\n\n### Tests \n\nTo run tests:\n\n1. Setup [database url](https://docs.sqlalchemy.org/en/13/core/engines.html#database-urls) via `DB_URL` environment variable (e.g. via .env file)\n\n**WARNING:** Every test run will drop all tables from the db\n\n2. Run tests via `pytest`\n\n### Contribution\n\n1. Create fork/branch for new feature/fix/whatever\n\n2. [Optional] Install pre-commit hooks: `pre-commit install` (for manual pre-commit run use`pre-commit run -a`)\n\n3. When you done create pull request and wait for approval\n\n### Deploy\n\nTo deploy new version you need to increment version via bump2version and publish it to PyPI via poetry:\n\n```\nbump2version major/minor/patch\npoetry publish --build\n``` \n\nDon\'t forget to fill the CHANGELOG.md before release \n',
    'author': 'potykion',
    'author_email': 'potykion@gmail.com',
    'maintainer': None,
    'maintainer_email': None,
    'url': 'https://github.com/potykion/repka',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'python_requires': '>=3.7,<4.0',
}


setup(**setup_kwargs)
