From eb8b7c76c9614d8a8d83ec0eed130099ec2a9ca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20G=C3=B3recki?= Date: Thu, 5 Jan 2023 14:32:41 +0100 Subject: [PATCH] refactor business module to a base class, add bidding/listing data mapper --- .../tests/.editorconfig => .editorconfig | 0 .gitignore | 1 - poetry.lock | 951 ++++++++++++++++++ src/modules/bidding/__init__.py | 26 + .../bidding/application/command/__init__.py | 2 + .../bidding/application/command/place_bid.py | 16 +- .../application/command/retract_bid.py | 14 +- .../bidding/application/query/__init__.py | 1 + .../infrastructure/listing_repository.py | 100 ++ src/modules/bidding/module.py | 46 - src/modules/bidding/test_module.py | 21 - src/modules/bidding/tests/__init__.py | 0 .../bidding/tests/infrastructure/__init__.py | 0 .../infrastructure/test_listing_repository.py | 101 ++ src/modules/bidding/tests/test_module.py | 0 src/modules/catalog/__init__.py | 132 +-- .../catalog/infrastructure/unit_of_work.py | 1 - src/modules/catalog/module.py | 2 - src/seedwork/application/decorators.py | 4 +- src/seedwork/application/modules.py | 139 ++- src/seedwork/application/queries.py | 7 +- src/seedwork/tests/application/test_module.py | 0 22 files changed, 1329 insertions(+), 235 deletions(-) rename src/modules/catalog/tests/.editorconfig => .editorconfig (100%) create mode 100644 poetry.lock create mode 100644 src/modules/bidding/__init__.py create mode 100644 src/modules/bidding/application/command/__init__.py create mode 100644 src/modules/bidding/application/query/__init__.py create mode 100644 src/modules/bidding/infrastructure/listing_repository.py delete mode 100644 src/modules/bidding/module.py delete mode 100644 src/modules/bidding/test_module.py create mode 100644 src/modules/bidding/tests/__init__.py create mode 100644 src/modules/bidding/tests/infrastructure/__init__.py create mode 100644 src/modules/bidding/tests/infrastructure/test_listing_repository.py create mode 100644 src/modules/bidding/tests/test_module.py delete mode 100644 src/modules/catalog/infrastructure/unit_of_work.py delete mode 100644 src/modules/catalog/module.py create mode 100644 src/seedwork/tests/application/test_module.py diff --git a/src/modules/catalog/tests/.editorconfig b/.editorconfig similarity index 100% rename from src/modules/catalog/tests/.editorconfig rename to .editorconfig diff --git a/.gitignore b/.gitignore index 1be2c3f..76d0350 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,3 @@ htmlcov/ logs.json tmp/ .idea/ -poetry.lock diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..a53ce3c --- /dev/null +++ b/poetry.lock @@ -0,0 +1,951 @@ +[[package]] +name = "alembic" +version = "1.8.1" +description = "A database migration tool for SQLAlchemy." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +Mako = "*" +SQLAlchemy = ">=1.3.0" + +[package.extras] +tz = ["python-dateutil"] + +[[package]] +name = "anyio" +version = "3.6.2" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +category = "main" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["packaging", "sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"] +test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "contextlib2", "uvloop (<0.15)", "mock (>=4)", "uvloop (>=0.15)"] +trio = ["trio (>=0.16,<0.22)"] + +[[package]] +name = "asgiref" +version = "3.5.2" +description = "ASGI specs, helper code, and adapters" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] + +[[package]] +name = "atomicwrites" +version = "1.4.1" +description = "Atomic file writes." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "22.1.0" +description = "Classes Without Boilerplate" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "cloudpickle"] + +[[package]] +name = "black" +version = "21.12b0" +description = "The uncompromising code formatter." +category = "main" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +click = ">=7.1.2" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0,<1" +platformdirs = ">=2" +tomli = ">=0.2.6,<2.0.0" +typing-extensions = [ + {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}, + {version = "!=3.10.0.1", markers = "python_version >= \"3.10\""}, +] + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +python2 = ["typed-ast (>=1.4.3)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2022.12.7" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "cfgv" +version = "3.3.1" +description = "Validate configuration and produce human readable error messages." +category = "main" +optional = false +python-versions = ">=3.6.1" + +[[package]] +name = "charset-normalizer" +version = "2.1.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.6.0" + +[package.extras] +unicode_backport = ["unicodedata2"] + +[[package]] +name = "click" +version = "8.0.4" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" + +[[package]] +name = "colorlog" +version = "5.0.1" +description = "Add colours to the output of Python's logging module." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} + +[[package]] +name = "coverage" +version = "6.5.0" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "dependency-injector" +version = "4.40.0" +description = "Dependency injection framework for Python" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +six = ">=1.7.0,<=1.16.0" + +[package.extras] +aiohttp = ["aiohttp"] +flask = ["flask"] +pydantic = ["pydantic"] +yaml = ["pyyaml"] + +[[package]] +name = "distlib" +version = "0.3.6" +description = "Distribution utilities" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "fastapi" +version = "0.67.0" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" +starlette = "0.14.2" + +[package.extras] +all = ["requests (>=2.24.0,<3.0.0)", "aiofiles (>=0.5.0,<0.6.0)", "jinja2 (>=2.11.2,<3.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "itsdangerous (>=1.1.0,<2.0.0)", "pyyaml (>=5.3.1,<6.0.0)", "graphene (>=2.1.8,<3.0.0)", "ujson (>=4.0.1,<5.0.0)", "orjson (>=3.2.1,<4.0.0)", "email_validator (>=1.1.1,<2.0.0)", "uvicorn[standard] (>=0.12.0,<0.14.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)"] +dev = ["python-jose[cryptography] (>=3.3.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.14.0)", "graphene (>=2.1.8,<3.0.0)"] +doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=7.1.9,<8.0.0)", "markdown-include (>=0.6.0,<0.7.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.2.0)", "typer-cli (>=0.0.12,<0.0.13)", "pyyaml (>=5.3.1,<6.0.0)"] +test = ["pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<3.0.0)", "pytest-asyncio (>=0.14.0,<0.15.0)", "mypy (==0.812)", "flake8 (>=3.8.3,<4.0.0)", "black (==20.8b1)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.15.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<1.4.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.4.0)", "orjson (>=3.2.1,<4.0.0)", "ujson (>=4.0.1,<5.0.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "aiofiles (>=0.5.0,<0.6.0)", "flask (>=1.1.2,<2.0.0)"] + +[[package]] +name = "filelock" +version = "3.8.2" +description = "A platform independent file lock." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2022.9.29)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] +testing = ["covdefaults (>=2.2.2)", "coverage (>=6.5)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "freezegun" +version = "1.2.2" +description = "Let your Python tests travel through time" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +python-dateutil = ">=2.7" + +[[package]] +name = "greenlet" +version = "2.0.1" +description = "Lightweight in-process concurrent programming" +category = "main" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" + +[package.extras] +docs = ["sphinx", "docutils (<0.18)"] +test = ["objgraph", "psutil", "faulthandler"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "httpcore" +version = "0.16.2" +description = "A minimal low-level HTTP client." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +anyio = ">=3.0,<5.0" +certifi = "*" +h11 = ">=0.13,<0.15" +sniffio = ">=1.0.0,<2.0.0" + +[package.extras] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] + +[[package]] +name = "httpx" +version = "0.23.1" +description = "The next generation HTTP client." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +certifi = "*" +httpcore = ">=0.15.0,<0.17.0" +rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] + +[[package]] +name = "identify" +version = "2.5.9" +description = "File identification library for Python" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "mako" +version = "1.2.4" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["babel"] +lingua = ["lingua"] +testing = ["pytest"] + +[[package]] +name = "markupsafe" +version = "2.1.1" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "mypy" +version = "0.910" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +mypy-extensions = ">=0.4.3,<0.5.0" +toml = "*" +typing-extensions = ">=3.7.4" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +python2 = ["typed-ast (>=1.4.0,<1.5.0)"] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "nodeenv" +version = "1.7.0" +description = "Node.js virtual environment builder" +category = "main" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" + +[[package]] +name = "packaging" +version = "22.0" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "pastel" +version = "0.2.1" +description = "Bring colors to your terminal." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pathspec" +version = "0.10.2" +description = "Utility library for gitignore style pattern matching of file paths." +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "platformdirs" +version = "2.6.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx-autodoc-typehints (>=1.19.4)", "sphinx (>=5.3)"] +test = ["appdirs (==1.4.4)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest (>=7.2)"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "poethepoet" +version = "0.10.0" +description = "A task runner that works well with poetry." +category = "dev" +optional = false +python-versions = ">=3.6,<4.0" + +[package.dependencies] +pastel = ">=0.2.0,<0.3.0" +tomlkit = ">=0.6.0,<1.0.0" + +[[package]] +name = "pre-commit" +version = "2.20.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +toml = "*" +virtualenv = ">=20.0.8" + +[[package]] +name = "psycopg2-binary" +version = "2.9.5" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pydantic" +version = "1.10.2" +description = "Data validation and settings management using python type hints" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +typing-extensions = ">=4.1.0" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pytest" +version = "6.2.5" +description = "pytest: simple powerful testing with Python" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +py = ">=1.8.2" +toml = "*" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "2.12.1" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +coverage = ">=5.2.1" +pytest = ">=4.6" +toml = "*" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-json-logger" +version = "2.0.4" +description = "A python library adding a json log formatter" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "python-multipart" +version = "0.0.5" +description = "A streaming multipart parser for Python" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +six = ">=1.4.0" + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "requests" +version = "2.28.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=3.7, <4" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<3" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rfc3986" +version = "1.5.0" +description = "Validating URI References per RFC 3986" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} + +[package.extras] +idna2008 = ["idna"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "sqlalchemy" +version = "1.4.44" +description = "Database Abstraction Library" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} + +[package.extras] +aiomysql = ["greenlet (!=0.4.17)", "aiomysql"] +aiosqlite = ["typing_extensions (!=3.10.0.1)", "greenlet (!=0.4.17)", "aiosqlite"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["greenlet (!=0.4.17)", "asyncmy (>=0.2.3,!=0.2.4)"] +mariadb_connector = ["mariadb (>=1.0.1,!=1.1.2)"] +mssql = ["pyodbc"] +mssql_pymssql = ["pymssql"] +mssql_pyodbc = ["pyodbc"] +mypy = ["sqlalchemy2-stubs", "mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0,<2)", "mysqlclient (>=1.4.0)"] +mysql_connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=7,<8)", "cx_oracle (>=7)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql_asyncpg = ["greenlet (!=0.4.17)", "asyncpg"] +postgresql_pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] +postgresql_psycopg2binary = ["psycopg2-binary"] +postgresql_psycopg2cffi = ["psycopg2cffi"] +pymysql = ["pymysql (<1)", "pymysql"] +sqlcipher = ["sqlcipher3-binary"] + +[[package]] +name = "sqlalchemy-json" +version = "0.4.0" +description = "JSON type with nested change tracking for SQLAlchemy" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +six = "*" +sqlalchemy = ">=0.7" + +[[package]] +name = "sqlalchemy-utils" +version = "0.38.3" +description = "Various utility functions for SQLAlchemy." +category = "main" +optional = false +python-versions = "~=3.6" + +[package.dependencies] +SQLAlchemy = ">=1.3" + +[package.extras] +arrow = ["arrow (>=0.3.4)"] +babel = ["Babel (>=1.3)"] +color = ["colour (>=0.0.4)"] +encrypted = ["cryptography (>=0.6)"] +intervals = ["intervals (>=0.7.1)"] +password = ["passlib (>=1.6,<2.0)"] +pendulum = ["pendulum (>=2.0.5)"] +phone = ["phonenumbers (>=5.9.2)"] +test = ["pytest (>=2.7.1)", "Pygments (>=1.2)", "Jinja2 (>=2.3)", "docutils (>=0.10)", "flexmock (>=0.9.7)", "psycopg2 (>=2.5.1)", "psycopg2cffi (>=2.8.1)", "pg8000 (>=1.12.4)", "pytz (>=2014.2)", "python-dateutil (>=2.6)", "pymysql", "flake8 (>=2.4.0)", "isort (>=4.2.2)", "pyodbc", "backports.zoneinfo"] +test_all = ["Babel (>=1.3)", "Jinja2 (>=2.3)", "Pygments (>=1.2)", "arrow (>=0.3.4)", "colour (>=0.0.4)", "cryptography (>=0.6)", "docutils (>=0.10)", "flake8 (>=2.4.0)", "flexmock (>=0.9.7)", "furl (>=0.4.1)", "intervals (>=0.7.1)", "isort (>=4.2.2)", "passlib (>=1.6,<2.0)", "pendulum (>=2.0.5)", "pg8000 (>=1.12.4)", "phonenumbers (>=5.9.2)", "psycopg2 (>=2.5.1)", "psycopg2cffi (>=2.8.1)", "pymysql", "pyodbc", "pytest (>=2.7.1)", "python-dateutil", "python-dateutil (>=2.6)", "pytz (>=2014.2)", "backports.zoneinfo"] +timezone = ["python-dateutil"] +url = ["furl (>=0.4.1)"] + +[[package]] +name = "starlette" +version = "0.14.2" +description = "The little ASGI library that shines." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +full = ["aiofiles", "graphene", "itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"] + +[[package]] +name = "starlette-context" +version = "0.3.5" +description = "Access context in Starlette" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +starlette = "*" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomli" +version = "1.2.3" +description = "A lil' TOML parser" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "tomlkit" +version = "0.11.6" +description = "Style preserving TOML library" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "typed-ast" +version = "1.5.4" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "typing-extensions" +version = "4.4.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "urllib3" +version = "1.26.13" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.extras] +brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "urllib3-secure-extra", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "uvicorn" +version = "0.14.0" +description = "The lightning-fast ASGI server." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +asgiref = ">=3.3.4" +click = ">=7" +h11 = ">=0.8" + +[package.extras] +standard = ["websockets (>=9.1)", "httptools (>=0.2.0,<0.3.0)", "watchgod (>=0.6)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"] + +[[package]] +name = "virtualenv" +version = "20.17.1" +description = "Virtual Python Environment builder" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +distlib = ">=0.3.6,<1" +filelock = ">=3.4.1,<4" +platformdirs = ">=2.4,<3" + +[package.extras] +docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"] +testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.10.0" +content-hash = "ebab2517e43c36706bcd8b09ee86f428e11177008d15330e85ce1b8e6932b001" + +[metadata.files] +alembic = [] +anyio = [] +asgiref = [] +atomicwrites = [] +attrs = [] +black = [] +certifi = [] +cfgv = [ + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, +] +charset-normalizer = [] +click = [] +colorama = [] +colorlog = [ + {file = "colorlog-5.0.1-py2.py3-none-any.whl", hash = "sha256:4e6be13d9169254e2ded6526a6a4a1abb8ac564f2fa65b310a98e4ca5bea2c04"}, + {file = "colorlog-5.0.1.tar.gz", hash = "sha256:f17c013a06962b02f4449ee07cfdbe6b287df29efc2c9a1515b4a376f4e588ea"}, +] +coverage = [] +dependency-injector = [] +distlib = [] +fastapi = [ + {file = "fastapi-0.67.0-py3-none-any.whl", hash = "sha256:b05f5af77af3b21cab896b8dade8b383b2d2f254caae4681a56313e29196f1ac"}, + {file = "fastapi-0.67.0.tar.gz", hash = "sha256:24f45d65e589db3bab162c02a1e2e8b798c098861b1fa3e266efeb71b4faa8e2"}, +] +filelock = [] +freezegun = [] +greenlet = [] +h11 = [] +httpcore = [] +httpx = [] +identify = [] +idna = [] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +mako = [] +markupsafe = [] +mypy = [ + {file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"}, + {file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"}, + {file = "mypy-0.910-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9"}, + {file = "mypy-0.910-cp35-cp35m-win_amd64.whl", hash = "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e"}, + {file = "mypy-0.910-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921"}, + {file = "mypy-0.910-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6"}, + {file = "mypy-0.910-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212"}, + {file = "mypy-0.910-cp36-cp36m-win_amd64.whl", hash = "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885"}, + {file = "mypy-0.910-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0"}, + {file = "mypy-0.910-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de"}, + {file = "mypy-0.910-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703"}, + {file = "mypy-0.910-cp37-cp37m-win_amd64.whl", hash = "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a"}, + {file = "mypy-0.910-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504"}, + {file = "mypy-0.910-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9"}, + {file = "mypy-0.910-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072"}, + {file = "mypy-0.910-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811"}, + {file = "mypy-0.910-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e"}, + {file = "mypy-0.910-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b"}, + {file = "mypy-0.910-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2"}, + {file = "mypy-0.910-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97"}, + {file = "mypy-0.910-cp39-cp39-win_amd64.whl", hash = "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8"}, + {file = "mypy-0.910-py3-none-any.whl", hash = "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d"}, + {file = "mypy-0.910.tar.gz", hash = "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +nodeenv = [] +packaging = [] +pastel = [ + {file = "pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364"}, + {file = "pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d"}, +] +pathspec = [] +platformdirs = [] +pluggy = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] +poethepoet = [ + {file = "poethepoet-0.10.0-py3-none-any.whl", hash = "sha256:6fb3021603d4421c6fcc40072bbcf150a6c52ef70ff4d3be089b8b04e015ef5a"}, + {file = "poethepoet-0.10.0.tar.gz", hash = "sha256:70b97cb194b978dc464c70793e85e6f746cddf82b84a38bfb135946ad71ae19c"}, +] +pre-commit = [] +psycopg2-binary = [] +py = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] +pydantic = [] +pytest = [] +pytest-cov = [ + {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, + {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, +] +python-dateutil = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] +python-json-logger = [] +python-multipart = [ + {file = "python-multipart-0.0.5.tar.gz", hash = "sha256:f7bb5f611fc600d15fa47b3974c8aa16e93724513b49b5f95c81e6624c83fa43"}, +] +pyyaml = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] +requests = [] +rfc3986 = [] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +sniffio = [] +sqlalchemy = [] +sqlalchemy-json = [ + {file = "sqlalchemy-json-0.4.0.tar.gz", hash = "sha256:d8e72cac50724a17cc137c98bec5cb5990e9f1e8fc3eb30dd225fb47c087ea27"}, + {file = "sqlalchemy_json-0.4.0-py2.py3-none-any.whl", hash = "sha256:0f52f24301aa3b5ea240b622facc489eff2e7bfddde931ba988bfabc306b1778"}, +] +sqlalchemy-utils = [] +starlette = [ + {file = "starlette-0.14.2-py3-none-any.whl", hash = "sha256:3c8e48e52736b3161e34c9f0e8153b4f32ec5d8995a3ee1d59410d92f75162ed"}, + {file = "starlette-0.14.2.tar.gz", hash = "sha256:7d49f4a27f8742262ef1470608c59ddbc66baf37c148e938c7038e6bc7a998aa"}, +] +starlette-context = [] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +tomli = [] +tomlkit = [] +typed-ast = [] +typing-extensions = [] +urllib3 = [] +uvicorn = [ + {file = "uvicorn-0.14.0-py3-none-any.whl", hash = "sha256:2a76bb359171a504b3d1c853409af3adbfa5cef374a4a59e5881945a97a93eae"}, + {file = "uvicorn-0.14.0.tar.gz", hash = "sha256:45ad7dfaaa7d55cab4cd1e85e03f27e9d60bc067ddc59db52a2b0aeca8870292"}, +] +virtualenv = [] diff --git a/src/modules/bidding/__init__.py b/src/modules/bidding/__init__.py new file mode 100644 index 0000000..c7891c5 --- /dev/null +++ b/src/modules/bidding/__init__.py @@ -0,0 +1,26 @@ +from modules.bidding.application.command import PlaceBidCommand, RetractBidCommand +from modules.bidding.application.query import GetPastdueListingsQuery +from modules.bidding.infrastructure.listing_repository import ( + PostgresJsonListingRepository, +) +from seedwork.application.modules import BusinessModule + +# +# @dataclass +# class UnitOfWork: +# module: Any # FIXME: type +# db_session: Session +# correlation_id: uuid.UUID +# listing_repository: ListingRepository + + +class BiddingModule(BusinessModule): + supported_commands = (PlaceBidCommand, RetractBidCommand) + supported_queries = (GetPastdueListingsQuery,) + supported_events = () + + def configure_unit_of_work(self, uow): + """Here we have a chance to add extra UOW attributes to be injected into command/query handers""" + uow.listing_repository = PostgresJsonListingRepository( + db_session=uow.db_session + ) diff --git a/src/modules/bidding/application/command/__init__.py b/src/modules/bidding/application/command/__init__.py new file mode 100644 index 0000000..e49b5d1 --- /dev/null +++ b/src/modules/bidding/application/command/__init__.py @@ -0,0 +1,2 @@ +from .place_bid import PlaceBidCommand +from .retract_bid import RetractBidCommand diff --git a/src/modules/bidding/application/command/place_bid.py b/src/modules/bidding/application/command/place_bid.py index 39edb1a..4fbc077 100644 --- a/src/modules/bidding/application/command/place_bid.py +++ b/src/modules/bidding/application/command/place_bid.py @@ -1,13 +1,14 @@ from decimal import Decimal +from modules.bidding.domain.entities import Listing +from modules.bidding.domain.repositories import ListingRepository +from modules.bidding.domain.value_objects import Bid, Bidder, Money from seedwork.application.command_handlers import CommandResult +from seedwork.application.commands import Command from seedwork.application.decorators import command_handler -from src.modules.bidding.domain.entities import Listing -from src.modules.bidding.domain.repositories import ListingRepository -from src.modules.bidding.domain.value_objects import Bid, Bidder, Money -class PlaceBidCommand: +class PlaceBidCommand(Command): listing_id: str bidder_id: str amount: Decimal @@ -15,12 +16,13 @@ class PlaceBidCommand: @command_handler -def place_bid(command: PlaceBidCommand, repository: ListingRepository) -> CommandResult: +def place_bid( + command: PlaceBidCommand, listing_repository: ListingRepository +) -> CommandResult: bidder = Bidder(id=command.bidder_id) bid = Bid(bidder=bidder, price=Money(command.amount)) - listing: Listing = repository.get_by_id(id=command.listing_id) + listing: Listing = listing_repository.get_by_id(id=command.listing_id) events = listing.place_bid(bid) - repository.update(listing) return CommandResult.ok(events=events) diff --git a/src/modules/bidding/application/command/retract_bid.py b/src/modules/bidding/application/command/retract_bid.py index 1b8cec9..fa07571 100644 --- a/src/modules/bidding/application/command/retract_bid.py +++ b/src/modules/bidding/application/command/retract_bid.py @@ -1,13 +1,14 @@ from decimal import Decimal +from modules.bidding.domain.entities import Listing +from modules.bidding.domain.repositories import ListingRepository +from modules.bidding.domain.value_objects import Bidder from seedwork.application.command_handlers import CommandResult +from seedwork.application.commands import Command from seedwork.application.decorators import command_handler -from src.modules.bidding.domain.entities import Listing -from src.modules.bidding.domain.repositories import ListingRepository -from src.modules.bidding.domain.value_objects import Bidder -class RetractBidCommand: +class RetractBidCommand(Command): listing_id: str bidder_id: str amount: Decimal @@ -16,12 +17,11 @@ class RetractBidCommand: @command_handler def retract_bid( - command: RetractBidCommand, repository: ListingRepository + command: RetractBidCommand, listing_repository: ListingRepository ) -> CommandResult: bidder = Bidder(id=command.bidder_id) - listing: Listing = repository.get_by_id(id=command.listing_id) + listing: Listing = listing_repository.get_by_id(id=command.listing_id) events = listing.retract_bid_of(bidder) - repository.update(listing) return CommandResult.ok(events=events) diff --git a/src/modules/bidding/application/query/__init__.py b/src/modules/bidding/application/query/__init__.py new file mode 100644 index 0000000..3f18906 --- /dev/null +++ b/src/modules/bidding/application/query/__init__.py @@ -0,0 +1 @@ +from .get_pastdue_listings import GetPastdueListingsQuery diff --git a/src/modules/bidding/infrastructure/listing_repository.py b/src/modules/bidding/infrastructure/listing_repository.py new file mode 100644 index 0000000..d3bee1d --- /dev/null +++ b/src/modules/bidding/infrastructure/listing_repository.py @@ -0,0 +1,100 @@ +import datetime +import uuid + +from sqlalchemy.dialects.postgresql import JSONB +from sqlalchemy.sql.schema import Column +from sqlalchemy_json import mutable_json_type +from sqlalchemy_utils import UUIDType + +from modules.bidding.domain.entities import Bid, Bidder, Listing, Money, Seller +from modules.bidding.domain.repositories import ListingRepository +from seedwork.infrastructure.database import Base +from seedwork.infrastructure.repository import SqlAlchemyGenericRepository + +""" +References: +"Introduction to SQLAlchemy 2020 (Tutorial)" by: Mike Bayer +https://youtu.be/sO7FFPNvX2s?t=7214 +""" + + +class ListingModel(Base): + """Data model for listing domain object in the bidding context""" + + __tablename__ = "bidding_listing" + id = Column(UUIDType(binary=False), primary_key=True, default=uuid.uuid4) + data = Column(mutable_json_type(dbtype=JSONB, nested=True)) + + +def serialize_money(money: Money) -> dict: + return { + "amount": money.amount, + "currency": money.currency, + } + + +def serialize_id(value: uuid.UUID) -> str: + return str(value) + + +def deserialize_id(value: str) -> uuid.UUID: + if isinstance(value, uuid.UUID): + return value + return uuid.UUID(value) + + +def deserialize_money(data: dict) -> Money: + return Money(amount=data["amount"], currency=data["currency"]) + + +def serialize_datetime(value) -> str: + return value.isoformat() + + +def deserialize_datetime(value) -> str: + return datetime.datetime.fromisoformat(value) + + +def serialize_bid(bid: Bid) -> dict: + return { + "bidder_id": serialize_id(bid.bidder.id), + "price": serialize_money(bid.price), + "placed_at": serialize_datetime(bid.placed_at), + } + + +def deserialize_bid(data: dict) -> Bid: + return Bid( + bidder=Bidder(id=deserialize_id(data["bidder_id"])), + price=deserialize_money(data["price"]), + placed_at=deserialize_datetime(data["placed_at"]), + ) + + +class ListingDataMapper: + def model_to_entity(self, instance: ListingModel) -> Listing: + d = instance.data + return Listing( + id=deserialize_id(instance.id), + seller=Seller(id=deserialize_id(d["seller_id"])), + initial_price=deserialize_money(d["initial_price"]), + ends_at=deserialize_datetime(d["ends_at"]), + ) + + def entity_to_model(self, entity: Listing) -> ListingModel: + return ListingModel( + id=entity.id, + data={ + "ends_at": serialize_datetime(entity.ends_at), + "initial_price": serialize_money(entity.initial_price), + "seller_id": serialize_id(entity.seller.id), + "bids": [serialize_bid(b) for b in entity.bids], + }, + ) + + +class PostgresJsonListingRepository(SqlAlchemyGenericRepository, ListingRepository): + """Listing repository implementation""" + + data_mapper = ListingDataMapper() + model_class = ListingModel diff --git a/src/modules/bidding/module.py b/src/modules/bidding/module.py deleted file mode 100644 index 81800f6..0000000 --- a/src/modules/bidding/module.py +++ /dev/null @@ -1,46 +0,0 @@ -from modules.bidding.application.query.get_pastdue_listings import ( - GetPastdueListingsQuery, - get_past_due_listings, -) -from modules.bidding.domain.repositories import ListingRepository -from seedwork.application.modules import BusinessModule -from seedwork.domain.events import EventPublisher - - -class BiddingModule(BusinessModule): - query_handlers = { - GetPastdueListingsQuery: lambda self, q: get_past_due_listings( - q, listing_repository=self.listing_repository - ) - # GetAllListings: lambda self, q: get_all_listings(q, self.listing_repository), - # GetListingDetails: lambda self, q: get_listing_details( - # q, self.listing_repository - # ), - # GetListingsOfSeller: lambda self, q: get_listings_of_seller( - # q, self.listing_repository - # ), - } - command_handlers = { - # CreateListingDraftCommand: lambda self, c: create_listing_draft( - # c, - # self.event_publisher, - # self.listing_repository, - # ), - } - - def __init__( - self, - event_publisher: EventPublisher, - listing_repository: ListingRepository, - ) -> None: - self.event_publisher = event_publisher - self.listing_repository = listing_repository - - @staticmethod - def create(container): - """Factory method for creating a module by using dependencies from a DI container""" - return BiddingModule( - logger=container.logger(), - event_publisher=container.event_publisher(), - listing_repository=container.listing_repository(), - ) diff --git a/src/modules/bidding/test_module.py b/src/modules/bidding/test_module.py deleted file mode 100644 index c8c1c1e..0000000 --- a/src/modules/bidding/test_module.py +++ /dev/null @@ -1,21 +0,0 @@ -# from modules.bidding.application.query.get_pastdue_listings import ( -# GetPastdueListingsQuery, -# ) -# from modules.bidding.module import BiddingModule -# from seedwork.infrastructure.repository import InMemoryRepository -# -# -# class DummyEventPublisher: -# ... -# - -# def test_module(): -# module = BiddingModule( -# event_publisher=DummyEventPublisher(), listing_repository=InMemoryRepository() -# ) -# -# query = GetPastdueListingsQuery() -# result = module.execute_query(query) -# -# assert result.is_ok() -# assert result.get_result() == [] diff --git a/src/modules/bidding/tests/__init__.py b/src/modules/bidding/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/bidding/tests/infrastructure/__init__.py b/src/modules/bidding/tests/infrastructure/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/bidding/tests/infrastructure/test_listing_repository.py b/src/modules/bidding/tests/infrastructure/test_listing_repository.py new file mode 100644 index 0000000..d14430d --- /dev/null +++ b/src/modules/bidding/tests/infrastructure/test_listing_repository.py @@ -0,0 +1,101 @@ +import datetime +import uuid + +from modules.bidding.domain.entities import Bid, Bidder, Listing, Money, Seller +from modules.bidding.infrastructure.listing_repository import ( + ListingDataMapper, + ListingModel, + PostgresJsonListingRepository, +) +from seedwork.domain.value_objects import UUID + + +def test_listing_repo_is_empty(db_session): + repo = PostgresJsonListingRepository(db_session=db_session) + assert repo.count() == 0 + + +def test_listing_data_mapper_maps_entity_to_model(): + listing = Listing( + id=UUID("00000000000000000000000000000001"), + seller=Seller(id=UUID("00000000000000000000000000000002")), + initial_price=Money(100, "PLN"), + ends_at=datetime.datetime(2020, 12, 31), + bids=[ + Bid( + price=Money(200, "PLN"), + bidder=Bidder(id=UUID("00000000000000000000000000000003")), + placed_at=datetime.datetime(2020, 12, 30), + ) + ], + ) + mapper = ListingDataMapper() + + actual = mapper.entity_to_model(listing) + + expected = ListingModel( + id=UUID("00000000000000000000000000000001"), + data={ + "seller_id": "00000000-0000-0000-0000-000000000002", + "initial_price": { + "amount": 100, + "currency": "PLN", + }, + "ends_at": "2020-12-31T00:00:00", + "bids": [ + { + "price": { + "amount": 200, + "currency": "PLN", + }, + "bidder_id": "00000000-0000-0000-0000-000000000003", + "placed_at": "2020-12-30T00:00:00", + } + ], + }, + ) + assert actual.id == expected.id + assert actual.data == expected.data + + +def test_listing_data_mapper_maps_model_to_entity(): + instance = ListingModel( + id=UUID("00000000000000000000000000000001"), + data={ + "seller_id": "00000000-0000-0000-0000-000000000002", + "initial_price": { + "amount": 100, + "currency": "PLN", + }, + "ends_at": "2020-12-31T00:00:00", + }, + ) + mapper = ListingDataMapper() + + actual = mapper.model_to_entity(instance) + + expected = Listing( + id=UUID("00000000000000000000000000000001"), + seller=Seller(id=UUID("00000000000000000000000000000002")), + initial_price=Money(100, "PLN"), + ends_at=datetime.datetime(2020, 12, 31), + ) + assert actual == expected + + +def test_listing_persistence(db_session): + original = Listing( + id=Listing.next_id(), + seller=Seller(id=uuid.uuid4()), + initial_price=Money(100, "PLN"), + ends_at=datetime.datetime(2020, 12, 31), + ) + repository = PostgresJsonListingRepository(db_session=db_session) + + repository.add(original) + repository.persist_all() + + repository = PostgresJsonListingRepository(db_session=db_session) + persisted = repository.get_by_id(original.id) + + assert original == persisted diff --git a/src/modules/bidding/tests/test_module.py b/src/modules/bidding/tests/test_module.py new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/catalog/__init__.py b/src/modules/catalog/__init__.py index c5943fa..1858d4b 100644 --- a/src/modules/catalog/__init__.py +++ b/src/modules/catalog/__init__.py @@ -1,121 +1,31 @@ -import uuid -from contextlib import contextmanager -from contextvars import ContextVar -from dataclasses import dataclass -from typing import Any - -from sqlalchemy.orm import Session - from modules.catalog.application.command import ( CreateListingDraftCommand, PublishListingCommand, UpdateListingDraftCommand, ) -from modules.catalog.application.command.create_listing_draft import ( - create_listing_draft, +from modules.catalog.application.query import ( + GetAllListings, + GetListingDetails, + GetListingsOfSeller, ) -from modules.catalog.application.query import GetAllListings from modules.catalog.domain.repositories import ListingRepository from modules.catalog.infrastructure.listing_repository import ( PostgresJsonListingRepository, ) -from seedwork.application.decorators import registry -from seedwork.infrastructure.request_context import request_context - - -def handles_command(command_class): - ... - - -def handles_query(query_class): - ... - - -class implemented_as: - def __init__(self, repo_class): - self.repo_class = repo_class - - def __get__(self, obj, obj_type=None): - raise NotImplementedError() - - -def get_arg(name, kwargs1, kwargs2): - return kwargs1.get(name, None) or kwargs2.get(name) - - -@dataclass -class UnitOfWork: - module: Any # FIXME: type - db_session: Session - correlation_id: uuid.UUID - listing_repository: ListingRepository - - -class CatalogModule: - handles_command(CreateListingDraftCommand) - handles_query(GetAllListings) - - def __init__(self, **kwargs): - self._uow: ContextVar[UnitOfWork] = ContextVar("_uow", default=None) - self.extra_kwargs = kwargs - - @contextmanager - def unit_of_work(self, **kwargs): - engine = get_arg("engine", kwargs, self.extra_kwargs) - correlation_id = uuid.uuid4() - db_session = None - with Session(engine) as db_session: - uow = UnitOfWork( - module=self, - correlation_id=correlation_id, - db_session=db_session, - listing_repository=PostgresJsonListingRepository(db_session=db_session), - ) - # begin unit of work - self._uow.set(uow) - self.begin_unit_of_work(uow) - yield uow - self.end_unit_of_work(uow) - # end unit of work - self._uow.set(None) - - def begin_unit_of_work(self, uow: UnitOfWork): - request_context.correlation_id.set(uow.correlation_id) - - def end_unit_of_work(self, uow): - uow.listing_repository.persist_all() - uow.db_session.commit() - - def configure(self, **kwargs): - self.extra_kwargs = kwargs - - def execute_command(self, command): - command_class = type(command) - handler = registry.get_command_handler_for(command_class) - kwargs = registry.get_command_handler_parameters_for(command_class) - - for param_name, param_type in kwargs.items(): - for attr in self.uow.__dict__.values(): - if isinstance(attr, param_type): - kwargs[param_name] = attr - - return handler(command, **kwargs) - - def execute_query(self, query): - query_class = type(query) - handler = registry.get_query_handler_for(query_class) - kwargs = registry.get_query_handler_parameters_for(query_class) - - for param_name, param_type in kwargs.items(): - for attr in self.uow.__dict__.values(): - if isinstance(attr, param_type): - kwargs[param_name] = attr - - return handler(query, **kwargs) - - @property - def uow(self) -> UnitOfWork: - uow = self._uow.get() - print(self._uow, self._uow.get()) - assert uow, "Unit of work not set, use context manager" - return uow +from seedwork.application.modules import BusinessModule + + +class CatalogModule(BusinessModule): + supported_commands = ( + CreateListingDraftCommand, + UpdateListingDraftCommand, + PublishListingCommand, + ) + supported_queries = (GetAllListings, GetListingDetails, GetListingsOfSeller) + supported_events = () + + def configure_unit_of_work(self, uow): + """Here we have a chance to add extra UOW attributes to be injected into command/query handers""" + uow.listing_repository = PostgresJsonListingRepository( + db_session=uow.db_session + ) diff --git a/src/modules/catalog/infrastructure/unit_of_work.py b/src/modules/catalog/infrastructure/unit_of_work.py deleted file mode 100644 index 8b13789..0000000 --- a/src/modules/catalog/infrastructure/unit_of_work.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/modules/catalog/module.py b/src/modules/catalog/module.py deleted file mode 100644 index 139597f..0000000 --- a/src/modules/catalog/module.py +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/seedwork/application/decorators.py b/src/seedwork/application/decorators.py index 59db4e4..17afd80 100644 --- a/src/seedwork/application/decorators.py +++ b/src/seedwork/application/decorators.py @@ -30,7 +30,7 @@ def get_command_handler_for(self, command_class) -> Callable: ), f"handler for {command_class} not registered" return self.command_handlers[command_class][0] - def get_command_handler_parameters_for(self, command_class) -> Callable: + def get_command_handler_parameters_for(self, command_class) -> dict: return self.command_handlers[command_class][1].copy() def register_query_handler( @@ -39,7 +39,7 @@ def register_query_handler( logger.info(f"registering query handler for {query_class} as {handler}") self.query_handlers[query_class] = (handler, handler_parameters) - def get_query_handler_for(self, query_class) -> Callable: + def get_query_handler_for(self, query_class) -> dict: assert ( query_class in self.query_handlers ), f"handler for {query_class} not registered" diff --git a/src/seedwork/application/modules.py b/src/seedwork/application/modules.py index 1550632..63803ab 100644 --- a/src/seedwork/application/modules.py +++ b/src/seedwork/application/modules.py @@ -1,9 +1,5 @@ from seedwork.infrastructure.logging import logger -from .command_handlers import CommandResult -from .commands import Command -from .queries import Query - def logging_handler(fn): def handle(module, query_or_command, *args, **kwargs): @@ -22,45 +18,124 @@ def handle(module, query_or_command, *args, **kwargs): return handle +import uuid +from contextlib import contextmanager +from contextvars import ContextVar +from dataclasses import dataclass +from typing import Any + +from sqlalchemy.orm import Session + +from seedwork.application.decorators import registry +from seedwork.domain.repositories import GenericRepository +from seedwork.infrastructure.request_context import request_context + + +def get_arg(name, kwargs1, kwargs2): + return kwargs1.get(name, None) or kwargs2.get(name) + + +@dataclass +class UnitOfWork: + module: Any # FIXME: type + db_session: Session + correlation_id: uuid.UUID + + def get_repositories(self): + for attr in self.__dict__.values(): + if isinstance(attr, GenericRepository): + yield attr + + class BusinessModule: """ Base class for creating business modules. As a rule of thumb, each module should expose a minimal set of operations via an interface that acts as a facade between the module and an external world. - query_handlers: a mapping between Query and `lambda self, query: query_handler(query, ...)` - command_handlers: a mapping between Command and `lambda self, command: command_handler(command, ...)` + We are following CQRS, and such operations are: + - execute_command() + - execute_query() """ - query_handlers = {} - command_handlers = {} + unit_of_work_class = UnitOfWork + supported_commands = () + supported_queries = () + supported_events = () - def __init__(self) -> None: - self.setup() + def __init__(self, **kwargs): + self._uow: ContextVar[UnitOfWork] = ContextVar("_uow", default=None) + self.init_kwargs = kwargs - def setup(self): - ... + @contextmanager + def unit_of_work(self, **kwargs): + """Instantiates new unit of work""" + engine = get_arg("engine", kwargs, self.init_kwargs) + correlation_id = uuid.uuid4() + with Session(engine) as db_session: + uow = self.create_unit_of_work(correlation_id, db_session) + self.configure_unit_of_work(uow) + request_context.correlation_id.set(correlation_id) + self._uow.set(uow) + yield uow + self.end_unit_of_work(uow) + # end unit of work + self._uow.set(None) + request_context.correlation_id.set(None) - @logging_handler - def execute_query(self, query: Query): - assert isinstance(query, Query), "Provided query must subclass Query" - try: - handler = self.query_handlers[type(query)] - except KeyError: - raise NotImplementedError( - f"No query handler for {type(query)} in {type(self)}" - ) + def create_unit_of_work(self, correlation_id, db_session): + """Unit of Work factory""" + uow = self.unit_of_work_class( + module=self, + correlation_id=correlation_id, + db_session=db_session, + **self.get_unit_of_work_init_kwargs(), + ) + return uow - return handler(self, query) + def get_unit_of_work_init_kwargs(self): + """Provide additional kwargs for Unit of Work if you are using a custom one""" + return dict() - @logging_handler - def execute_command(self, command: Command) -> CommandResult: - assert isinstance(command, Command), "Provided query must subclass Query" - try: - handler = self.command_handlers[type(command)] - except KeyError: - raise NotImplementedError( - f"No command handler for {type(command)} in {type(self)}" - ) + def configure_unit_of_work(self, uow): + """Allows to alter Unit of Work (i.e. add extra attributes)""" + + def end_unit_of_work(self, uow): + uow.db_session.commit() + + def configure(self, **kwargs): + self.init_kwargs = kwargs + + def execute_command(self, command): + command_class = type(command) + assert ( + command_class in self.supported_commands + ), f"{command_class} is not included in {type(self).__name__}.supported_commands" + handler = registry.get_command_handler_for(command_class) + kwarg_params = registry.get_command_handler_parameters_for(command_class) + kwargs = self.resolve_handler_kwargs(kwarg_params) + return handler(command, **kwargs) + + def execute_query(self, query): + query_class = type(query) + assert ( + query_class in self.supported_queries + ), f"{query_class} is not included in {type(self).__name__}.supported_queries" + handler = registry.get_query_handler_for(query_class) + kwarg_params = registry.get_query_handler_parameters_for(query_class) + kwargs = self.resolve_handler_kwargs(kwarg_params) + return handler(query, **kwargs) + + @property + def uow(self) -> UnitOfWork: + uow = self._uow.get() + assert uow, "Unit of work not set, use context manager" + return uow - return handler(self, command) + def resolve_handler_kwargs(self, kwarg_params) -> dict: + kwargs = {} + for param_name, param_type in kwarg_params.items(): + for attr in self.uow.__dict__.values(): + if isinstance(attr, param_type): + kwargs[param_name] = attr + return kwargs diff --git a/src/seedwork/application/queries.py b/src/seedwork/application/queries.py index da5b389..01dfc7a 100644 --- a/src/seedwork/application/queries.py +++ b/src/seedwork/application/queries.py @@ -1,5 +1,2 @@ -from pydantic import BaseModel - - -class Query(BaseModel): - pass +class Query: + """Base class for all queries""" diff --git a/src/seedwork/tests/application/test_module.py b/src/seedwork/tests/application/test_module.py new file mode 100644 index 0000000..e69de29