From 67d5add8847eeaca76eaf3e4fe8d27a9b403fe36 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan Date: Tue, 21 Sep 2021 11:07:16 +0530 Subject: [PATCH 001/150] add more models --- reddit-clone/.flake8 | 7 + reddit-clone/README.md | 45 + reddit-clone/alembic/.gitkeep | 0 reddit-clone/poetry.lock | 1092 ++++++++++++++++++++ reddit-clone/pyproject.toml | 25 + reddit-clone/reddit/__init__.py | 25 + reddit-clone/reddit/comments/__init__.py | 0 reddit-clone/reddit/comments/model.py | 7 + reddit-clone/reddit/core/__init__.py | 0 reddit-clone/reddit/core/config.py | 11 + reddit-clone/reddit/core/models.py | 42 + reddit-clone/reddit/schema.py | 6 + reddit-clone/reddit/subreddits/__init__.py | 0 reddit-clone/reddit/subreddits/models.py | 59 ++ reddit-clone/reddit/threads/__init__.py | 0 reddit-clone/reddit/threads/models.py | 74 ++ reddit-clone/reddit/users/__init__.py | 0 reddit-clone/reddit/users/models.py | 57 + reddit-clone/setup.cfg | 2 + 19 files changed, 1452 insertions(+) create mode 100644 reddit-clone/.flake8 create mode 100644 reddit-clone/README.md create mode 100644 reddit-clone/alembic/.gitkeep create mode 100644 reddit-clone/poetry.lock create mode 100644 reddit-clone/pyproject.toml create mode 100644 reddit-clone/reddit/__init__.py create mode 100644 reddit-clone/reddit/comments/__init__.py create mode 100644 reddit-clone/reddit/comments/model.py create mode 100644 reddit-clone/reddit/core/__init__.py create mode 100644 reddit-clone/reddit/core/config.py create mode 100644 reddit-clone/reddit/core/models.py create mode 100644 reddit-clone/reddit/schema.py create mode 100644 reddit-clone/reddit/subreddits/__init__.py create mode 100644 reddit-clone/reddit/subreddits/models.py create mode 100644 reddit-clone/reddit/threads/__init__.py create mode 100644 reddit-clone/reddit/threads/models.py create mode 100644 reddit-clone/reddit/users/__init__.py create mode 100644 reddit-clone/reddit/users/models.py create mode 100644 reddit-clone/setup.cfg diff --git a/reddit-clone/.flake8 b/reddit-clone/.flake8 new file mode 100644 index 00000000..812e13d9 --- /dev/null +++ b/reddit-clone/.flake8 @@ -0,0 +1,7 @@ +[flake8] +max-line-length = 89 +exclude=.venv,.git +ignore = W503 +extend-ignore = + # See https://github.com/PyCQA/pycodestyle/issues/373 + E203, diff --git a/reddit-clone/README.md b/reddit-clone/README.md new file mode 100644 index 00000000..78a07283 --- /dev/null +++ b/reddit-clone/README.md @@ -0,0 +1,45 @@ +# Reddit GraphQL API + +This example shows you how to create a Reddit API clone using GraphQL. + +## Tech Stack used: + +- Strawberry GraphQL +- FastAPI w/ Starlette +- SQLAlchemy (asyncio) + +## Features at a glance + +- Error handling with unions +- Authorization with the permissions API +- Batch loading with dataloaders +- Pagination with SQLAlchemy + +## How to use + +1. Install dependencies + +Use [poetry](https://python-poetry.org/) to install dependencies: + +```bash +poetry install +``` + +2. Run migrations + +Run [alembic](https://alembic.sqlalchemy.org/en/latest/) to create the database +and populate it with movie data: + +```bash +poetry run alembic upgrade head +``` + +3. Run the server + +Run [uvicorn](https://www.uvicorn.org/) to run the server: + +```bash +poetry run uvicorn reddit:app --reload +``` + +The GraphQL API should now be available at http://localhost:8000/graphql diff --git a/reddit-clone/alembic/.gitkeep b/reddit-clone/alembic/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/reddit-clone/poetry.lock b/reddit-clone/poetry.lock new file mode 100644 index 00000000..eef41ea6 --- /dev/null +++ b/reddit-clone/poetry.lock @@ -0,0 +1,1092 @@ +[[package]] +name = "alembic" +version = "1.7.3" +description = "A database migration tool for SQLAlchemy." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +importlib-resources = {version = "*", markers = "python_version < \"3.9\""} +Mako = "*" +SQLAlchemy = ">=1.3.0" + +[package.extras] +tz = ["python-dateutil"] + +[[package]] +name = "asgiref" +version = "3.4.1" +description = "ASGI specs, helper code, and adapters" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[package.extras] +tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] + +[[package]] +name = "attrs" +version = "21.2.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] + +[[package]] +name = "black" +version = "21.9b0" +description = "The uncompromising code formatter." +category = "dev" +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" +regex = ">=2020.1.8" +tomli = ">=0.2.6,<2.0.0" +typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\""} +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.6.0)", "aiohttp-cors (>=0.4.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +python2 = ["typed-ast (>=1.4.2)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "cached-property" +version = "1.5.2" +description = "A decorator for caching properties in classes." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "click" +version = "8.0.1" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "fastapi" +version = "0.68.1" +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)", "mdx-include (>=1.4.1,<2.0.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 = "flake8" +version = "3.9.2" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.dependencies] +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.7.0,<2.8.0" +pyflakes = ">=2.3.0,<2.4.0" + +[[package]] +name = "flake8-black" +version = "0.2.3" +description = "flake8 plugin to call black as a code style validator" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +black = "*" +flake8 = ">=3.0.0" +toml = "*" + +[[package]] +name = "flake8-bugbear" +version = "21.9.1" +description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +attrs = ">=19.2.0" +flake8 = ">=3.0.0" + +[package.extras] +dev = ["coverage", "black", "hypothesis", "hypothesmith"] + +[[package]] +name = "graphql-core" +version = "3.1.6" +description = "GraphQL implementation for Python, a port of GraphQL.js, the JavaScript reference implementation for GraphQL." +category = "main" +optional = false +python-versions = ">=3.6,<4" + +[[package]] +name = "greenlet" +version = "1.1.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"] + +[[package]] +name = "h11" +version = "0.12.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "httptools" +version = "0.2.0" +description = "A collection of framework independent HTTP protocol utils." +category = "main" +optional = false +python-versions = "*" + +[package.extras] +test = ["Cython (==0.29.22)"] + +[[package]] +name = "importlib-metadata" +version = "4.8.1" +description = "Read metadata from Python packages" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +perf = ["ipython"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] + +[[package]] +name = "importlib-resources" +version = "5.2.2" +description = "Read resources from Python packages" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy"] + +[[package]] +name = "mako" +version = "1.1.5" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["babel"] +lingua = ["lingua"] + +[[package]] +name = "markupsafe" +version = "2.0.1" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "mypy" +version = "0.910" +description = "Optional static typing for Python" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +mypy-extensions = ">=0.4.3,<0.5.0" +toml = "*" +typed-ast = {version = ">=1.4.0,<1.5.0", markers = "python_version < \"3.8\""} +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 = "pathspec" +version = "0.9.0" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[[package]] +name = "platformdirs" +version = "2.3.0" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] + +[[package]] +name = "pycodestyle" +version = "2.7.0" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pydantic" +version = "1.8.2" +description = "Data validation and settings management using python 3.6 type hinting" +category = "main" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +typing-extensions = ">=3.7.4.3" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pyflakes" +version = "2.3.1" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pygments" +version = "2.10.0" +description = "Pygments is a syntax highlighting package written in Python." +category = "main" +optional = false +python-versions = ">=3.5" + +[[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-dotenv" +version = "0.19.0" +description = "Read key-value pairs from a .env file and set them as environment variables" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +cli = ["click (>=5.0)"] + +[[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 = "5.4.1" +description = "YAML parser and emitter for Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[[package]] +name = "regex" +version = "2021.8.28" +description = "Alternative regular expression module, to replace re." +category = "dev" +optional = false +python-versions = "*" + +[[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 = "sqlalchemy" +version = "1.4.23" +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 in \"x86_64 X86_64 aarch64 AARCH64 ppc64le PPC64LE amd64 AMD64 win32 WIN32\""} +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +mypy = {version = ">=0.910", optional = true, markers = "python_version >= \"3\" and extra == \"mypy\""} +sqlalchemy2-stubs = {version = "*", optional = true, markers = "extra == \"mypy\""} + +[package.extras] +aiomysql = ["greenlet (!=0.4.17)", "aiomysql"] +aiosqlite = ["greenlet (!=0.4.17)", "aiosqlite"] +asyncio = ["greenlet (!=0.4.17)"] +mariadb_connector = ["mariadb (>=1.0.1)"] +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 = ["mysqlconnector"] +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)"] +postgresql_psycopg2binary = ["psycopg2-binary"] +postgresql_psycopg2cffi = ["psycopg2cffi"] +pymysql = ["pymysql (<1)", "pymysql"] +sqlcipher = ["sqlcipher3-binary"] + +[[package]] +name = "sqlalchemy2-stubs" +version = "0.0.2a15" +description = "Typing Stubs for SQLAlchemy 1.4" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing-extensions = ">=3.7.4" + +[[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 = "strawberry-graphql" +version = "0.77.12" +description = "A library for creating GraphQL APIs" +category = "main" +optional = false +python-versions = ">=3.7,<4.0" + +[package.dependencies] +cached-property = ">=1.5.2,<2.0.0" +click = ">=7.0,<9.0" +graphql-core = ">=3.1.0,<3.2.0" +pygments = ">=2.3,<3.0" +python-dateutil = ">=2.7.0,<3.0.0" +python-multipart = ">=0.0.5,<0.0.6" +starlette = {version = ">=0.13.6,<0.17.0", optional = true, markers = "extra == \"asgi\" or extra == \"debug-server\""} +typing_extensions = ">=3.7.4,<4.0.0" + +[package.extras] +asgi = ["starlette (>=0.13.6,<0.17.0)"] +debug-server = ["starlette (>=0.13.6,<0.17.0)", "uvicorn (>=0.11.6,<0.16.0)"] +django = ["django (>=2,<4)", "asgiref (>=3.2,<4.0)"] +flask = ["flask (>=1.1,<2.0)"] +opentelemetry = ["opentelemetry-api (<2)", "opentelemetry-sdk (<2)"] +pydantic = ["pydantic (<2)"] +sanic = ["sanic (>=20.12.2,<22.0.0)"] +aiohttp = ["aiohttp (>=3.7.4.post0,<4.0.0)"] + +[[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.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "typed-ast" +version = "1.4.3" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "typing-extensions" +version = "3.10.0.2" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "uvicorn" +version = "0.15.0" +description = "The lightning-fast ASGI server." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +asgiref = ">=3.4.0" +click = ">=7.0" +colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} +h11 = ">=0.8" +httptools = {version = ">=0.2.0,<0.3.0", optional = true, markers = "extra == \"standard\""} +python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} +PyYAML = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} +uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""} +watchgod = {version = ">=0.6", optional = true, markers = "extra == \"standard\""} +websockets = {version = ">=9.1", optional = true, markers = "extra == \"standard\""} + +[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 = "uvloop" +version = "0.16.0" +description = "Fast implementation of asyncio event loop on top of libuv" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +dev = ["Cython (>=0.29.24,<0.30.0)", "pytest (>=3.6.0)", "Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"] +docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)"] +test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"] + +[[package]] +name = "watchgod" +version = "0.7" +description = "Simple, modern file watching and code reload in python." +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "websockets" +version = "10.0" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "zipp" +version = "3.5.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.7" +content-hash = "98fa69364a20e1543505c385c05fd9be26fdc5abbb60fc0e52136a65a1036d88" + +[metadata.files] +alembic = [ + {file = "alembic-1.7.3-py3-none-any.whl", hash = "sha256:d0c580041f9f6487d5444df672a83da9be57398f39d6c1802bbedec6fefbeef6"}, + {file = "alembic-1.7.3.tar.gz", hash = "sha256:bc5bdf03d1b9814ee4d72adc0b19df2123f6c50a60c1ea761733f3640feedb8d"}, +] +asgiref = [ + {file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"}, + {file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"}, +] +attrs = [ + {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, + {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, +] +black = [ + {file = "black-21.9b0-py3-none-any.whl", hash = "sha256:380f1b5da05e5a1429225676655dddb96f5ae8c75bdf91e53d798871b902a115"}, + {file = "black-21.9b0.tar.gz", hash = "sha256:7de4cfc7eb6b710de325712d40125689101d21d25283eed7e9998722cf10eb91"}, +] +cached-property = [ + {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, + {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, +] +click = [ + {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, + {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +fastapi = [ + {file = "fastapi-0.68.1-py3-none-any.whl", hash = "sha256:94d2820906c36b9b8303796fb7271337ec89c74223229e3cfcf056b5a7d59e23"}, + {file = "fastapi-0.68.1.tar.gz", hash = "sha256:644bb815bae326575c4b2842469fb83053a4b974b82fa792ff9283d17fbbd99d"}, +] +flake8 = [ + {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, + {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, +] +flake8-black = [ + {file = "flake8-black-0.2.3.tar.gz", hash = "sha256:c199844bc1b559d91195ebe8620216f21ed67f2cc1ff6884294c91a0d2492684"}, + {file = "flake8_black-0.2.3-py3-none-any.whl", hash = "sha256:cc080ba5b3773b69ba102b6617a00cc4ecbad8914109690cfda4d565ea435d96"}, +] +flake8-bugbear = [ + {file = "flake8-bugbear-21.9.1.tar.gz", hash = "sha256:2f60c8ce0dc53d51da119faab2d67dea978227f0f92ed3c44eb7d65fb2e06a96"}, + {file = "flake8_bugbear-21.9.1-py36.py37.py38-none-any.whl", hash = "sha256:45bfdccfb9f2d8aa140e33cac8f46f1e38215c13d5aa8650e7e188d84e2f94c6"}, +] +graphql-core = [ + {file = "graphql-core-3.1.6.tar.gz", hash = "sha256:e65975b6a13878f9113a1fa5320760585b522d139944e005936b1b8358d0651a"}, + {file = "graphql_core-3.1.6-py3-none-any.whl", hash = "sha256:c78d09596d347e1cffd266c5384abfedf43ed1eae08729773bebb3d527fe5a14"}, +] +greenlet = [ + {file = "greenlet-1.1.1-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:476ba9435afaead4382fbab8f1882f75e3fb2285c35c9285abb3dd30237f9142"}, + {file = "greenlet-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:44556302c0ab376e37939fd0058e1f0db2e769580d340fb03b01678d1ff25f68"}, + {file = "greenlet-1.1.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40abb7fec4f6294225d2b5464bb6d9552050ded14a7516588d6f010e7e366dcc"}, + {file = "greenlet-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:a11b6199a0b9dc868990456a2667167d0ba096c5224f6258e452bfbe5a9742c5"}, + {file = "greenlet-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e22a82d2b416d9227a500c6860cf13e74060cf10e7daf6695cbf4e6a94e0eee4"}, + {file = "greenlet-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bad269e442f1b7ffa3fa8820b3c3aa66f02a9f9455b5ba2db5a6f9eea96f56de"}, + {file = "greenlet-1.1.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:8ddb38fb6ad96c2ef7468ff73ba5c6876b63b664eebb2c919c224261ae5e8378"}, + {file = "greenlet-1.1.1-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:84782c80a433d87530ae3f4b9ed58d4a57317d9918dfcc6a59115fa2d8731f2c"}, + {file = "greenlet-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac991947ca6533ada4ce7095f0e28fe25d5b2f3266ad5b983ed4201e61596acf"}, + {file = "greenlet-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5317701c7ce167205c0569c10abc4bd01c7f4cf93f642c39f2ce975fa9b78a3c"}, + {file = "greenlet-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4870b018ca685ff573edd56b93f00a122f279640732bb52ce3a62b73ee5c4a92"}, + {file = "greenlet-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:990e0f5e64bcbc6bdbd03774ecb72496224d13b664aa03afd1f9b171a3269272"}, + {file = "greenlet-1.1.1-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:a414f8e14aa7bacfe1578f17c11d977e637d25383b6210587c29210af995ef04"}, + {file = "greenlet-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:e02780da03f84a671bb4205c5968c120f18df081236d7b5462b380fd4f0b497b"}, + {file = "greenlet-1.1.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:dfcb5a4056e161307d103bc013478892cfd919f1262c2bb8703220adcb986362"}, + {file = "greenlet-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:655ab836324a473d4cd8cf231a2d6f283ed71ed77037679da554e38e606a7117"}, + {file = "greenlet-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:6ce9d0784c3c79f3e5c5c9c9517bbb6c7e8aa12372a5ea95197b8a99402aa0e6"}, + {file = "greenlet-1.1.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:3fc6a447735749d651d8919da49aab03c434a300e9f0af1c886d560405840fd1"}, + {file = "greenlet-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8039f5fe8030c43cd1732d9a234fdcbf4916fcc32e21745ca62e75023e4d4649"}, + {file = "greenlet-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fddfb31aa2ac550b938d952bca8a87f1db0f8dc930ffa14ce05b5c08d27e7fd1"}, + {file = "greenlet-1.1.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97a807437b81f90f85022a9dcfd527deea38368a3979ccb49d93c9198b2c722"}, + {file = "greenlet-1.1.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf31e894dabb077a35bbe6963285d4515a387ff657bd25b0530c7168e48f167f"}, + {file = "greenlet-1.1.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4eae94de9924bbb4d24960185363e614b1b62ff797c23dc3c8a7c75bbb8d187e"}, + {file = "greenlet-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:c1862f9f1031b1dee3ff00f1027fcd098ffc82120f43041fe67804b464bbd8a7"}, + {file = "greenlet-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:9b02e6039eafd75e029d8c58b7b1f3e450ca563ef1fe21c7e3e40b9936c8d03e"}, + {file = "greenlet-1.1.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:84488516639c3c5e5c0e52f311fff94ebc45b56788c2a3bfe9cf8e75670f4de3"}, + {file = "greenlet-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3f8fc59bc5d64fa41f58b0029794f474223693fd00016b29f4e176b3ee2cfd9f"}, + {file = "greenlet-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3e594015a2349ec6dcceda9aca29da8dc89e85b56825b7d1f138a3f6bb79dd4c"}, + {file = "greenlet-1.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e41f72f225192d5d4df81dad2974a8943b0f2d664a2a5cfccdf5a01506f5523c"}, + {file = "greenlet-1.1.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75ff270fd05125dce3303e9216ccddc541a9e072d4fc764a9276d44dee87242b"}, + {file = "greenlet-1.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cde7ee190196cbdc078511f4df0be367af85636b84d8be32230f4871b960687"}, + {file = "greenlet-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:f253dad38605486a4590f9368ecbace95865fea0f2b66615d121ac91fd1a1563"}, + {file = "greenlet-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a91ee268f059583176c2c8b012a9fce7e49ca6b333a12bbc2dd01fc1a9783885"}, + {file = "greenlet-1.1.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:34e6675167a238bede724ee60fe0550709e95adaff6a36bcc97006c365290384"}, + {file = "greenlet-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:bf3725d79b1ceb19e83fb1aed44095518c0fcff88fba06a76c0891cfd1f36837"}, + {file = "greenlet-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:5c3b735ccf8fc8048664ee415f8af5a3a018cc92010a0d7195395059b4b39b7d"}, + {file = "greenlet-1.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2002a59453858c7f3404690ae80f10c924a39f45f6095f18a985a1234c37334"}, + {file = "greenlet-1.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04e1849c88aa56584d4a0a6e36af5ec7cc37993fdc1fda72b56aa1394a92ded3"}, + {file = "greenlet-1.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8d4ed48eed7414ccb2aaaecbc733ed2a84c299714eae3f0f48db085342d5629"}, + {file = "greenlet-1.1.1-cp38-cp38-win32.whl", hash = "sha256:2f89d74b4f423e756a018832cd7a0a571e0a31b9ca59323b77ce5f15a437629b"}, + {file = "greenlet-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:d15cb6f8706678dc47fb4e4f8b339937b04eda48a0af1cca95f180db552e7663"}, + {file = "greenlet-1.1.1-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b050dbb96216db273b56f0e5960959c2b4cb679fe1e58a0c3906fa0a60c00662"}, + {file = "greenlet-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e0696525500bc8aa12eae654095d2260db4dc95d5c35af2b486eae1bf914ccd"}, + {file = "greenlet-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:07e6d88242e09b399682b39f8dfa1e7e6eca66b305de1ff74ed9eb1a7d8e539c"}, + {file = "greenlet-1.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98b491976ed656be9445b79bc57ed21decf08a01aaaf5fdabf07c98c108111f6"}, + {file = "greenlet-1.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e72db813c28906cdc59bd0da7c325d9b82aa0b0543014059c34c8c4ad20e16"}, + {file = "greenlet-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:090126004c8ab9cd0787e2acf63d79e80ab41a18f57d6448225bbfcba475034f"}, + {file = "greenlet-1.1.1-cp39-cp39-win32.whl", hash = "sha256:1796f2c283faab2b71c67e9b9aefb3f201fdfbee5cb55001f5ffce9125f63a45"}, + {file = "greenlet-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:4adaf53ace289ced90797d92d767d37e7cdc29f13bd3830c3f0a561277a4ae83"}, + {file = "greenlet-1.1.1.tar.gz", hash = "sha256:c0f22774cd8294078bdf7392ac73cf00bfa1e5e0ed644bd064fdabc5f2a2f481"}, +] +h11 = [ + {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"}, + {file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"}, +] +httptools = [ + {file = "httptools-0.2.0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:79dbc21f3612a78b28384e989b21872e2e3cf3968532601544696e4ed0007ce5"}, + {file = "httptools-0.2.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:78d03dd39b09c99ec917d50189e6743adbfd18c15d5944392d2eabda688bf149"}, + {file = "httptools-0.2.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:a23166e5ae2775709cf4f7ad4c2048755ebfb272767d244e1a96d55ac775cca7"}, + {file = "httptools-0.2.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:3ab1f390d8867f74b3b5ee2a7ecc9b8d7f53750bd45714bf1cb72a953d7dfa77"}, + {file = "httptools-0.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a7594f9a010cdf1e16a58b3bf26c9da39bbf663e3b8d46d39176999d71816658"}, + {file = "httptools-0.2.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:01b392a166adcc8bc2f526a939a8aabf89fe079243e1543fd0e7dc1b58d737cb"}, + {file = "httptools-0.2.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:80ffa04fe8c8dfacf6e4cef8277347d35b0442c581f5814f3b0cf41b65c43c6e"}, + {file = "httptools-0.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d5682eeb10cca0606c4a8286a3391d4c3c5a36f0c448e71b8bd05be4e1694bfb"}, + {file = "httptools-0.2.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:a289c27ccae399a70eacf32df9a44059ca2ba4ac444604b00a19a6c1f0809943"}, + {file = "httptools-0.2.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:813871f961edea6cb2fe312f2d9b27d12a51ba92545380126f80d0de1917ea15"}, + {file = "httptools-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:cc9be041e428c10f8b6ab358c6b393648f9457094e1dcc11b4906026d43cd380"}, + {file = "httptools-0.2.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b08d00d889a118f68f37f3c43e359aab24ee29eb2e3fe96d64c6a2ba8b9d6557"}, + {file = "httptools-0.2.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fd3b8905e21431ad306eeaf56644a68fdd621bf8f3097eff54d0f6bdf7262065"}, + {file = "httptools-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:200fc1cdf733a9ff554c0bb97a4047785cfaad9875307d6087001db3eb2b417f"}, + {file = "httptools-0.2.0.tar.gz", hash = "sha256:94505026be56652d7a530ab03d89474dc6021019d6b8682281977163b3471ea0"}, +] +importlib-metadata = [ + {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"}, + {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"}, +] +importlib-resources = [ + {file = "importlib_resources-5.2.2-py3-none-any.whl", hash = "sha256:2480d8e07d1890056cb53c96e3de44fead9c62f2ba949b0f2e4c4345f4afa977"}, + {file = "importlib_resources-5.2.2.tar.gz", hash = "sha256:a65882a4d0fe5fbf702273456ba2ce74fe44892c25e42e057aca526b702a6d4b"}, +] +mako = [ + {file = "Mako-1.1.5-py2.py3-none-any.whl", hash = "sha256:6804ee66a7f6a6416910463b00d76a7b25194cd27f1918500c5bd7be2a088a23"}, + {file = "Mako-1.1.5.tar.gz", hash = "sha256:169fa52af22a91900d852e937400e79f535496191c63712e3b9fda5a9bed6fc3"}, +] +markupsafe = [ + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, + {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +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"}, +] +pathspec = [ + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, +] +platformdirs = [ + {file = "platformdirs-2.3.0-py3-none-any.whl", hash = "sha256:8003ac87717ae2c7ee1ea5a84a1a61e87f3fbd16eb5aadba194ea30a9019f648"}, + {file = "platformdirs-2.3.0.tar.gz", hash = "sha256:15b056538719b1c94bdaccb29e5f81879c7f7f0f4a153f46086d155dffcd4f0f"}, +] +pycodestyle = [ + {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, + {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, +] +pydantic = [ + {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"}, + {file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"}, + {file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"}, + {file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"}, + {file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"}, + {file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"}, + {file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"}, + {file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"}, + {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"}, + {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"}, +] +pyflakes = [ + {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, + {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, +] +pygments = [ + {file = "Pygments-2.10.0-py3-none-any.whl", hash = "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380"}, + {file = "Pygments-2.10.0.tar.gz", hash = "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"}, +] +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-dotenv = [ + {file = "python-dotenv-0.19.0.tar.gz", hash = "sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"}, + {file = "python_dotenv-0.19.0-py2.py3-none-any.whl", hash = "sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1"}, +] +python-multipart = [ + {file = "python-multipart-0.0.5.tar.gz", hash = "sha256:f7bb5f611fc600d15fa47b3974c8aa16e93724513b49b5f95c81e6624c83fa43"}, +] +pyyaml = [ + {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, + {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, + {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, + {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, + {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"}, + {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, + {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, + {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"}, + {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, + {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, + {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, +] +regex = [ + {file = "regex-2021.8.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d05ad5367c90814099000442b2125535e9d77581855b9bee8780f1b41f2b1a2"}, + {file = "regex-2021.8.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3bf1bc02bc421047bfec3343729c4bbbea42605bcfd6d6bfe2c07ade8b12d2a"}, + {file = "regex-2021.8.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f6a808044faae658f546dd5f525e921de9fa409de7a5570865467f03a626fc0"}, + {file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a617593aeacc7a691cc4af4a4410031654f2909053bd8c8e7db837f179a630eb"}, + {file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79aef6b5cd41feff359acaf98e040844613ff5298d0d19c455b3d9ae0bc8c35a"}, + {file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0fc1f8f06977c2d4f5e3d3f0d4a08089be783973fc6b6e278bde01f0544ff308"}, + {file = "regex-2021.8.28-cp310-cp310-win32.whl", hash = "sha256:6eebf512aa90751d5ef6a7c2ac9d60113f32e86e5687326a50d7686e309f66ed"}, + {file = "regex-2021.8.28-cp310-cp310-win_amd64.whl", hash = "sha256:ac88856a8cbccfc14f1b2d0b829af354cc1743cb375e7f04251ae73b2af6adf8"}, + {file = "regex-2021.8.28-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c206587c83e795d417ed3adc8453a791f6d36b67c81416676cad053b4104152c"}, + {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8690ed94481f219a7a967c118abaf71ccc440f69acd583cab721b90eeedb77c"}, + {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:328a1fad67445550b982caa2a2a850da5989fd6595e858f02d04636e7f8b0b13"}, + {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c7cb4c512d2d3b0870e00fbbac2f291d4b4bf2634d59a31176a87afe2777c6f0"}, + {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66256b6391c057305e5ae9209941ef63c33a476b73772ca967d4a2df70520ec1"}, + {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8e44769068d33e0ea6ccdf4b84d80c5afffe5207aa4d1881a629cf0ef3ec398f"}, + {file = "regex-2021.8.28-cp36-cp36m-win32.whl", hash = "sha256:08d74bfaa4c7731b8dac0a992c63673a2782758f7cfad34cf9c1b9184f911354"}, + {file = "regex-2021.8.28-cp36-cp36m-win_amd64.whl", hash = "sha256:abb48494d88e8a82601af905143e0de838c776c1241d92021e9256d5515b3645"}, + {file = "regex-2021.8.28-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b4c220a1fe0d2c622493b0a1fd48f8f991998fb447d3cd368033a4b86cf1127a"}, + {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a332404baa6665b54e5d283b4262f41f2103c255897084ec8f5487ce7b9e8e"}, + {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c61dcc1cf9fd165127a2853e2c31eb4fb961a4f26b394ac9fe5669c7a6592892"}, + {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ee329d0387b5b41a5dddbb6243a21cb7896587a651bebb957e2d2bb8b63c0791"}, + {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f60667673ff9c249709160529ab39667d1ae9fd38634e006bec95611f632e759"}, + {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b844fb09bd9936ed158ff9df0ab601e2045b316b17aa8b931857365ea8586906"}, + {file = "regex-2021.8.28-cp37-cp37m-win32.whl", hash = "sha256:4cde065ab33bcaab774d84096fae266d9301d1a2f5519d7bd58fc55274afbf7a"}, + {file = "regex-2021.8.28-cp37-cp37m-win_amd64.whl", hash = "sha256:1413b5022ed6ac0d504ba425ef02549a57d0f4276de58e3ab7e82437892704fc"}, + {file = "regex-2021.8.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ed4b50355b066796dacdd1cf538f2ce57275d001838f9b132fab80b75e8c84dd"}, + {file = "regex-2021.8.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28fc475f560d8f67cc8767b94db4c9440210f6958495aeae70fac8faec631797"}, + {file = "regex-2021.8.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdc178caebd0f338d57ae445ef8e9b737ddf8fbc3ea187603f65aec5b041248f"}, + {file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:999ad08220467b6ad4bd3dd34e65329dd5d0df9b31e47106105e407954965256"}, + {file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:808ee5834e06f57978da3e003ad9d6292de69d2bf6263662a1a8ae30788e080b"}, + {file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d5111d4c843d80202e62b4fdbb4920db1dcee4f9366d6b03294f45ed7b18b42e"}, + {file = "regex-2021.8.28-cp38-cp38-win32.whl", hash = "sha256:473858730ef6d6ff7f7d5f19452184cd0caa062a20047f6d6f3e135a4648865d"}, + {file = "regex-2021.8.28-cp38-cp38-win_amd64.whl", hash = "sha256:31a99a4796bf5aefc8351e98507b09e1b09115574f7c9dbb9cf2111f7220d2e2"}, + {file = "regex-2021.8.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:04f6b9749e335bb0d2f68c707f23bb1773c3fb6ecd10edf0f04df12a8920d468"}, + {file = "regex-2021.8.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b006628fe43aa69259ec04ca258d88ed19b64791693df59c422b607b6ece8bb"}, + {file = "regex-2021.8.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:121f4b3185feaade3f85f70294aef3f777199e9b5c0c0245c774ae884b110a2d"}, + {file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a577a21de2ef8059b58f79ff76a4da81c45a75fe0bfb09bc8b7bb4293fa18983"}, + {file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1743345e30917e8c574f273f51679c294effba6ad372db1967852f12c76759d8"}, + {file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e1e8406b895aba6caa63d9fd1b6b1700d7e4825f78ccb1e5260551d168db38ed"}, + {file = "regex-2021.8.28-cp39-cp39-win32.whl", hash = "sha256:ed283ab3a01d8b53de3a05bfdf4473ae24e43caee7dcb5584e86f3f3e5ab4374"}, + {file = "regex-2021.8.28-cp39-cp39-win_amd64.whl", hash = "sha256:610b690b406653c84b7cb6091facb3033500ee81089867ee7d59e675f9ca2b73"}, + {file = "regex-2021.8.28.tar.gz", hash = "sha256:f585cbbeecb35f35609edccb95efd95a3e35824cd7752b586503f7e6087303f1"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +sqlalchemy = [ + {file = "SQLAlchemy-1.4.23-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:25e9b2e5ca088879ce3740d9ccd4d58cb9061d49566a0b5e12166f403d6f4da0"}, + {file = "SQLAlchemy-1.4.23-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d9667260125688c71ccf9af321c37e9fb71c2693575af8210f763bfbbee847c7"}, + {file = "SQLAlchemy-1.4.23-cp27-cp27m-win32.whl", hash = "sha256:cec1a4c6ddf5f82191301a25504f0e675eccd86635f0d5e4c69e0661691931c5"}, + {file = "SQLAlchemy-1.4.23-cp27-cp27m-win_amd64.whl", hash = "sha256:ae07895b55c7d58a7dd47438f437ac219c0f09d24c2e7d69fdebc1ea75350f00"}, + {file = "SQLAlchemy-1.4.23-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:967307ea52985985224a79342527c36ec2d1daa257a39748dd90e001a4be4d90"}, + {file = "SQLAlchemy-1.4.23-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:be185b3daf651c6c0639987a916bf41e97b60e68f860f27c9cb6574385f5cbb4"}, + {file = "SQLAlchemy-1.4.23-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a0d3b3d51c83a66f5b72c57e1aad061406e4c390bd42cf1fda94effe82fac81"}, + {file = "SQLAlchemy-1.4.23-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a8395c4db3e1450eef2b68069abf500cc48af4b442a0d98b5d3c9535fe40cde8"}, + {file = "SQLAlchemy-1.4.23-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b128a78581faea7a5ee626ad4471353eee051e4e94616dfeff4742b6e5ba262"}, + {file = "SQLAlchemy-1.4.23-cp36-cp36m-win32.whl", hash = "sha256:43fc207be06e50158e4dae4cc4f27ce80afbdbfa7c490b3b22feb64f6d9775a0"}, + {file = "SQLAlchemy-1.4.23-cp36-cp36m-win_amd64.whl", hash = "sha256:e9d4f4552aa5e0d1417fc64a2ce1cdf56a30bab346ba6b0dd5e838eb56db4d29"}, + {file = "SQLAlchemy-1.4.23-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:512f52a8872e8d63d898e4e158eda17e2ee40b8d2496b3b409422e71016db0bd"}, + {file = "SQLAlchemy-1.4.23-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:355024cf061ed04271900414eb4a22671520241d2216ddb691bdd8a992172389"}, + {file = "SQLAlchemy-1.4.23-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:82c03325111eab88d64e0ff48b6fe15c75d23787429fa1d84c0995872e702787"}, + {file = "SQLAlchemy-1.4.23-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0aa312f9906ecebe133d7f44168c3cae4c76f27a25192fa7682f3fad505543c9"}, + {file = "SQLAlchemy-1.4.23-cp37-cp37m-win32.whl", hash = "sha256:059c5f41e8630f51741a234e6ba2a034228c11b3b54a15478e61d8b55fa8bd9d"}, + {file = "SQLAlchemy-1.4.23-cp37-cp37m-win_amd64.whl", hash = "sha256:cd68c5f9d13ffc8f4d6802cceee786678c5b1c668c97bc07b9f4a60883f36cd1"}, + {file = "SQLAlchemy-1.4.23-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:6a8dbf3d46e889d864a57ee880c4ad3a928db5aa95e3d359cbe0da2f122e50c4"}, + {file = "SQLAlchemy-1.4.23-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c15191f2430a30082f540ec6f331214746fc974cfdf136d7a1471d1c61d68ff"}, + {file = "SQLAlchemy-1.4.23-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cd0e85dd2067159848c7672acd517f0c38b7b98867a347411ea01b432003f8d9"}, + {file = "SQLAlchemy-1.4.23-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:370f4688ce47f0dc1e677a020a4d46252a31a2818fd67f5c256417faefc938af"}, + {file = "SQLAlchemy-1.4.23-cp38-cp38-win32.whl", hash = "sha256:bd41f8063a9cd11b76d6d7d6af8139ab3c087f5dbbe5a50c02cb8ece7da34d67"}, + {file = "SQLAlchemy-1.4.23-cp38-cp38-win_amd64.whl", hash = "sha256:2bca9a6e30ee425cc321d988a152a5fe1be519648e7541ac45c36cd4f569421f"}, + {file = "SQLAlchemy-1.4.23-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:4803a481d4c14ce6ad53dc35458c57821863e9a079695c27603d38355e61fb7f"}, + {file = "SQLAlchemy-1.4.23-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07b9099a95dd2b2620498544300eda590741ac54915c6b20809b6de7e3c58090"}, + {file = "SQLAlchemy-1.4.23-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:37f2bd1b8e32c5999280f846701712347fc0ee7370e016ede2283c71712e127a"}, + {file = "SQLAlchemy-1.4.23-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:448612570aa1437a5d1b94ada161805778fe80aba5b9a08a403e8ae4e071ded6"}, + {file = "SQLAlchemy-1.4.23-cp39-cp39-win32.whl", hash = "sha256:e0ce4a2e48fe0a9ea3a5160411a4c5135da5255ed9ac9c15f15f2bcf58c34194"}, + {file = "SQLAlchemy-1.4.23-cp39-cp39-win_amd64.whl", hash = "sha256:0aa746d1173587743960ff17b89b540e313aacfe6c1e9c81aa48393182c36d4f"}, + {file = "SQLAlchemy-1.4.23.tar.gz", hash = "sha256:76ff246881f528089bf19385131b966197bb494653990396d2ce138e2a447583"}, +] +sqlalchemy2-stubs = [ + {file = "sqlalchemy2-stubs-0.0.2a15.tar.gz", hash = "sha256:9336e0724c985623e055e2db28e8d78a62b40a2628b862c6cd50d7942fd5a238"}, + {file = "sqlalchemy2_stubs-0.0.2a15-py3-none-any.whl", hash = "sha256:3da19252c87c1ae6bf0e49c2873e72a6e5684a1ede2490c8b75b78834239258b"}, +] +starlette = [ + {file = "starlette-0.14.2-py3-none-any.whl", hash = "sha256:3c8e48e52736b3161e34c9f0e8153b4f32ec5d8995a3ee1d59410d92f75162ed"}, + {file = "starlette-0.14.2.tar.gz", hash = "sha256:7d49f4a27f8742262ef1470608c59ddbc66baf37c148e938c7038e6bc7a998aa"}, +] +strawberry-graphql = [ + {file = "strawberry-graphql-0.77.12.tar.gz", hash = "sha256:1adc770643fa8423345a8c4c7a9956172f49a2e01559981be9e4ead6e80611fe"}, + {file = "strawberry_graphql-0.77.12-py3-none-any.whl", hash = "sha256:2080d93c1a80139b10ae5248ba2e8878c0182362ad9853424eecdd5aebdf3331"}, +] +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 = [ + {file = "tomli-1.2.1-py3-none-any.whl", hash = "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f"}, + {file = "tomli-1.2.1.tar.gz", hash = "sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442"}, +] +typed-ast = [ + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, + {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, + {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, + {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, + {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, + {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, + {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, + {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, + {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, + {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, +] +typing-extensions = [ + {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, + {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, + {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, +] +uvicorn = [ + {file = "uvicorn-0.15.0-py3-none-any.whl", hash = "sha256:17f898c64c71a2640514d4089da2689e5db1ce5d4086c2d53699bf99513421c1"}, + {file = "uvicorn-0.15.0.tar.gz", hash = "sha256:d9a3c0dd1ca86728d3e235182683b4cf94cd53a867c288eaeca80ee781b2caff"}, +] +uvloop = [ + {file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6224f1401025b748ffecb7a6e2652b17768f30b1a6a3f7b44660e5b5b690b12d"}, + {file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30ba9dcbd0965f5c812b7c2112a1ddf60cf904c1c160f398e7eed3a6b82dcd9c"}, + {file = "uvloop-0.16.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bd53f7f5db562f37cd64a3af5012df8cac2c464c97e732ed556800129505bd64"}, + {file = "uvloop-0.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:772206116b9b57cd625c8a88f2413df2fcfd0b496eb188b82a43bed7af2c2ec9"}, + {file = "uvloop-0.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b572256409f194521a9895aef274cea88731d14732343da3ecdb175228881638"}, + {file = "uvloop-0.16.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:04ff57aa137230d8cc968f03481176041ae789308b4d5079118331ab01112450"}, + {file = "uvloop-0.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a19828c4f15687675ea912cc28bbcb48e9bb907c801873bd1519b96b04fb805"}, + {file = "uvloop-0.16.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e814ac2c6f9daf4c36eb8e85266859f42174a4ff0d71b99405ed559257750382"}, + {file = "uvloop-0.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bd8f42ea1ea8f4e84d265769089964ddda95eb2bb38b5cbe26712b0616c3edee"}, + {file = "uvloop-0.16.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:647e481940379eebd314c00440314c81ea547aa636056f554d491e40503c8464"}, + {file = "uvloop-0.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e0d26fa5875d43ddbb0d9d79a447d2ace4180d9e3239788208527c4784f7cab"}, + {file = "uvloop-0.16.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6ccd57ae8db17d677e9e06192e9c9ec4bd2066b77790f9aa7dede2cc4008ee8f"}, + {file = "uvloop-0.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:089b4834fd299d82d83a25e3335372f12117a7d38525217c2258e9b9f4578897"}, + {file = "uvloop-0.16.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98d117332cc9e5ea8dfdc2b28b0a23f60370d02e1395f88f40d1effd2cb86c4f"}, + {file = "uvloop-0.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e5f2e2ff51aefe6c19ee98af12b4ae61f5be456cd24396953244a30880ad861"}, + {file = "uvloop-0.16.0.tar.gz", hash = "sha256:f74bc20c7b67d1c27c72601c78cf95be99d5c2cdd4514502b4f3eb0933ff1228"}, +] +watchgod = [ + {file = "watchgod-0.7-py3-none-any.whl", hash = "sha256:d6c1ea21df37847ac0537ca0d6c2f4cdf513562e95f77bb93abbcf05573407b7"}, + {file = "watchgod-0.7.tar.gz", hash = "sha256:48140d62b0ebe9dd9cf8381337f06351e1f2e70b2203fa9c6eff4e572ca84f29"}, +] +websockets = [ + {file = "websockets-10.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cd8c6f2ec24aedace251017bc7a414525171d4e6578f914acab9349362def4da"}, + {file = "websockets-10.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:1f6b814cff6aadc4288297cb3a248614829c6e4ff5556593c44a115e9dd49939"}, + {file = "websockets-10.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:01db0ecd1a0ca6702d02a5ed40413e18b7d22f94afb3bbe0d323bac86c42c1c8"}, + {file = "websockets-10.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:82b17524b1ce6ae7f7dd93e4d18e9b9474071e28b65dbf1dfe9b5767778db379"}, + {file = "websockets-10.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:8bbf8660c3f833ddc8b1afab90213f2e672a9ddac6eecb3cde968e6b2807c1c7"}, + {file = "websockets-10.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b8176deb6be540a46695960a765a77c28ac8b2e3ef2ec95d50a4f5df901edb1c"}, + {file = "websockets-10.0-cp37-cp37m-win32.whl", hash = "sha256:706e200fc7f03bed99ad0574cd1ea8b0951477dd18cc978ccb190683c69dba76"}, + {file = "websockets-10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5b2600e01c7ca6f840c42c747ffbe0254f319594ed108db847eb3d75f4aacb80"}, + {file = "websockets-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:085bb8a6e780d30eaa1ba48ac7f3a6707f925edea787cfb761ce5a39e77ac09b"}, + {file = "websockets-10.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9a4d889162bd48588e80950e07fa5e039eee9deb76a58092e8c3ece96d7ef537"}, + {file = "websockets-10.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:b4ade7569b6fd17912452f9c3757d96f8e4044016b6d22b3b8391e641ca50456"}, + {file = "websockets-10.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:2a43072e434c041a99f2e1eb9b692df0232a38c37c61d00e9f24db79474329e4"}, + {file = "websockets-10.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:7f79f02c7f9a8320aff7d3321cd1c7e3a7dbc15d922ac996cca827301ee75238"}, + {file = "websockets-10.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:1ac35426fe3e7d3d0fac3d63c8965c76ed67a8fd713937be072bf0ce22808539"}, + {file = "websockets-10.0-cp38-cp38-win32.whl", hash = "sha256:ff59c6bdb87b31f7e2d596f09353d5a38c8c8ff571b0e2238e8ee2d55ad68465"}, + {file = "websockets-10.0-cp38-cp38-win_amd64.whl", hash = "sha256:d67646ddd17a86117ae21c27005d83c1895c0cef5d7be548b7549646372f868a"}, + {file = "websockets-10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:82bd921885231f4a30d9bc550552495b3fc36b1235add6d374e7c65c3babd805"}, + {file = "websockets-10.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:7d2e12e4f901f1bc062dfdf91831712c4106ed18a9a4cdb65e2e5f502124ca37"}, + {file = "websockets-10.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:71358c7816e2762f3e4af3adf0040f268e219f5a38cb3487a9d0fc2e554fef6a"}, + {file = "websockets-10.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:fe83b3ec9ef34063d86dfe1029160a85f24a5a94271036e5714a57acfdd089a1"}, + {file = "websockets-10.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:eb282127e9c136f860c6068a4fba5756eb25e755baffb5940b6f1eae071928b2"}, + {file = "websockets-10.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:62160772314920397f9d219147f958b33fa27a12c662d4455c9ccbba9a07e474"}, + {file = "websockets-10.0-cp39-cp39-win32.whl", hash = "sha256:e42a1f1e03437b017af341e9bbfdc09252cd48ef32a8c3c3ead769eab3b17368"}, + {file = "websockets-10.0-cp39-cp39-win_amd64.whl", hash = "sha256:c5880442f5fc268f1ef6d37b2c152c114deccca73f48e3a8c48004d2f16f4567"}, + {file = "websockets-10.0.tar.gz", hash = "sha256:c4fc9a1d242317892590abe5b61a9127f1a61740477bfb121743f290b8054002"}, +] +zipp = [ + {file = "zipp-3.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"}, + {file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"}, +] diff --git a/reddit-clone/pyproject.toml b/reddit-clone/pyproject.toml new file mode 100644 index 00000000..0715528a --- /dev/null +++ b/reddit-clone/pyproject.toml @@ -0,0 +1,25 @@ +[tool.poetry] +name = "reddit-graphql" +version = "0.1.0" +description = "A reddit API clone built with GraphQL." +authors = ["Aryan Iyappan "] + +[tool.poetry.dependencies] +python = "^3.7" +uvicorn = {extras = ["standard"], version = "^0.15.0"} +SQLAlchemy = {extras = ["mypy"], version = "^1.4.23"} +alembic = "^1.7.1" +strawberry-graphql = {extras = ["asgi"], version = "^0.77.0"} +fastapi = "^0.68.1" + + +[tool.poetry.dev-dependencies] +black = "^21.8b0" +flake8 = "^3.9.2" +flake8-black = "^0.2.3" +flake8-bugbear = "^21.4.3" +mypy = "^0.910" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/reddit-clone/reddit/__init__.py b/reddit-clone/reddit/__init__.py new file mode 100644 index 00000000..61797eca --- /dev/null +++ b/reddit-clone/reddit/__init__.py @@ -0,0 +1,25 @@ +from fastapi import FastAPI +from strawberry.asgi import GraphQL + +from reddit.core.config import DEBUG +from reddit.schema import schema + +__all__ = ("app",) + + +def create_application() -> FastAPI: + """ + Creates an application instance. + + :return: The created application. + """ + application = FastAPI(title="Reddit GraphQL", debug=DEBUG) + + application.add_route( + path="/graphql", route=GraphQL(schema=schema, graphiql=True, debug=DEBUG) + ) + + return application + + +app = create_application() diff --git a/reddit-clone/reddit/comments/__init__.py b/reddit-clone/reddit/comments/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/reddit-clone/reddit/comments/model.py b/reddit-clone/reddit/comments/model.py new file mode 100644 index 00000000..fe4e3f57 --- /dev/null +++ b/reddit-clone/reddit/comments/model.py @@ -0,0 +1,7 @@ +from reddit.core.models import BaseModel + + +class Comment(BaseModel): + """ + Represents a Comment in a thread. + """ diff --git a/reddit-clone/reddit/core/__init__.py b/reddit-clone/reddit/core/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/reddit-clone/reddit/core/config.py b/reddit-clone/reddit/core/config.py new file mode 100644 index 00000000..701bbbf2 --- /dev/null +++ b/reddit-clone/reddit/core/config.py @@ -0,0 +1,11 @@ +from starlette.config import Config +from starlette.datastructures import Secret + + +config = Config(env_file=".env") + +# whether the application is in development mode. +DEBUG: bool = config("DEBUG", cast=bool, default=False) + +# secret key to use for sessions. +SECRET_KEY: Secret = config("SECRET_KEY", cast=Secret) diff --git a/reddit-clone/reddit/core/models.py b/reddit-clone/reddit/core/models.py new file mode 100644 index 00000000..b755d3d5 --- /dev/null +++ b/reddit-clone/reddit/core/models.py @@ -0,0 +1,42 @@ +from typing import Optional +from datetime import datetime + +from sqlalchemy import Column +from sqlalchemy.ext.declarative import declarative_base + + +Base = declarative_base() + + +class BaseModel(Base): + """ + The base class which every model + must subclass/ inherit from. + """ + + __abstract__ = True + + id: Optional[int] = Column( + default=None, + primary_key=True, + description=""" + Identifier for the object. + """, + ) + + created_at: datetime = Column( + nullable=False, + default=datetime.now(), + description=""" + When the object was created. + """, + ) + + updated_at: datetime = Column( + nullable=False, + default=datetime.now(), + onupdate=datetime.now(), + description=""" + When the object was updated. + """, + ) diff --git a/reddit-clone/reddit/schema.py b/reddit-clone/reddit/schema.py new file mode 100644 index 00000000..b0f87467 --- /dev/null +++ b/reddit-clone/reddit/schema.py @@ -0,0 +1,6 @@ +from strawberry import Schema + +__all__ = ("schema",) + + +schema = Schema(query=None, mutation=None) diff --git a/reddit-clone/reddit/subreddits/__init__.py b/reddit-clone/reddit/subreddits/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/reddit-clone/reddit/subreddits/models.py b/reddit-clone/reddit/subreddits/models.py new file mode 100644 index 00000000..18e5d27a --- /dev/null +++ b/reddit-clone/reddit/subreddits/models.py @@ -0,0 +1,59 @@ +from typing import Optional + +from sqlalchemy import Column, String, Integer, SmallInteger, ForeignKey +from sqlalchemy.orm import relationship + +from reddit.core.models import Base + + +class SubReddit(Base): + """ + Represents a SubReddit. + """ + + __tablename__ = "subreddits" + + name: str = Column( + String(75), + unique=True, + nullable=False, + comment=""" + The name for the subreddit. + """, + ) + + description: Optional[str] = Column( + String(255), + default=None, + comment=""" + The description for the subreddit. + """, + ) + + admin_id: int = Column( + Integer, + ForeignKey("users.id"), + comment=""" + The ID of the subreddit's admin. + """, + ) + + status: int = Column( + SmallInteger, + comment=""" + The status of the subreddit. + """, + ) + + icon: Optional[str] = Column( + String(255), + default=None, + comment=""" + The icon for the subreddit. + """, + ) + + threads = relationship("Thread", backref="subreddit", lazy="dynamic") + + def __repr__(self) -> str: + return "" % self.name diff --git a/reddit-clone/reddit/threads/__init__.py b/reddit-clone/reddit/threads/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/reddit-clone/reddit/threads/models.py b/reddit-clone/reddit/threads/models.py new file mode 100644 index 00000000..4da12872 --- /dev/null +++ b/reddit-clone/reddit/threads/models.py @@ -0,0 +1,74 @@ +from typing import Optional + +from sqlalchemy import Column, Integer, String, ForeignKey +from sqlalchemy.orm import relationship + +from reddit.core.models import BaseModel + + +class Thread(BaseModel): + """ + Represents a Thread in a SubReddit. + """ + + __tablename__ = "threads" + + title: str = Column( + String(150), + comment=""" + The title for the thread. + """, + ) + + text: Optional[str] = Column( + String(1024), + default=None, + comment=""" + The text for the thread. + """, + ) + + link: Optional[str] = Column( + String(255), + default=None, + comment=""" + The link for the thread. + """, + ) + + thumbnail: Optional[str] = Column( + String(255), + default=None, + comment=""" + The thumbnail URL for the thread. + """, + ) + + user_id: int = Column( + Integer, + ForeignKey("users.id"), + comment=""" + The owner ID the thread. + """, + ) + + subreddit_id: int = Column( + Integer, + ForeignKey("subreddits.id"), + comment=""" + The SubReddit ID of the thread. + """, + ) + + votes: int = Column( + Integer, + default=1, + comment=""" + The votes for the thread. + """, + ) + + comments = relationship("Comment", backref="thread", lazy="dynamic") + + def __repr__(self) -> str: + return "" % self.title diff --git a/reddit-clone/reddit/users/__init__.py b/reddit-clone/reddit/users/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/reddit-clone/reddit/users/models.py b/reddit-clone/reddit/users/models.py new file mode 100644 index 00000000..4e6fb2a9 --- /dev/null +++ b/reddit-clone/reddit/users/models.py @@ -0,0 +1,57 @@ +from typing import Optional + +from sqlalchemy import Column, String +from sqlalchemy.orm import relationship + +from reddit.core.models import Base + + +class User(Base): + """ + Represents an individual user account. + """ + + __tablename__ = "users" + + username: str = Column( + String(32), + nullable=False, + unique=True, + comment=""" + The username for the user. + """, + ) + + email: str = Column( + String(255), + nullable=False, + unique=True, + comment=""" + The email for the user. + """, + ) + + password: str = Column( + String(255), + nullable=False, + comment=""" + The password for the user. + """, + ) + + avatar: Optional[str] = Column( + String(255), + default=None, + comment=""" + The avatar for the user. + """, + ) + + threads = relationship("Thread", backref="user", lazy="dynamic") + + subreddits = relationship("Subreddit", backref="user", lazy="dynamic") + + comments = relationship("Comment", backref="user", lazy="dynamic") + + def __repr__(self) -> str: + return "" % self.username diff --git a/reddit-clone/setup.cfg b/reddit-clone/setup.cfg new file mode 100644 index 00000000..a4fec64b --- /dev/null +++ b/reddit-clone/setup.cfg @@ -0,0 +1,2 @@ +[mypy] +plugins = sqlalchemy.ext.mypy.plugin,strawberry.ext.mypy_plugin From d429da0a28f9bfcf310cb702295b7ff34b8012d7 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan Date: Tue, 21 Sep 2021 11:07:59 +0530 Subject: [PATCH 002/150] rename model.py to models.py --- reddit-clone/reddit/comments/{model.py => models.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename reddit-clone/reddit/comments/{model.py => models.py} (100%) diff --git a/reddit-clone/reddit/comments/model.py b/reddit-clone/reddit/comments/models.py similarity index 100% rename from reddit-clone/reddit/comments/model.py rename to reddit-clone/reddit/comments/models.py From 5e48aff6f5402fcc7e7fea6a2f272c1c036a3b30 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan Date: Tue, 21 Sep 2021 11:11:18 +0530 Subject: [PATCH 003/150] update tech stack --- reddit-clone/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/reddit-clone/README.md b/reddit-clone/README.md index 78a07283..e4f12e40 100644 --- a/reddit-clone/README.md +++ b/reddit-clone/README.md @@ -7,6 +7,7 @@ This example shows you how to create a Reddit API clone using GraphQL. - Strawberry GraphQL - FastAPI w/ Starlette - SQLAlchemy (asyncio) +- Alembic for migrations ## Features at a glance From fc413ec2925d5c88daef19473329395adf30484f Mon Sep 17 00:00:00 2001 From: Aryan Iyappan Date: Tue, 21 Sep 2021 11:16:36 +0530 Subject: [PATCH 004/150] fix base model inheritance --- reddit-clone/reddit/core/models.py | 2 ++ reddit-clone/reddit/subreddits/models.py | 6 +++--- reddit-clone/reddit/users/models.py | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/reddit-clone/reddit/core/models.py b/reddit-clone/reddit/core/models.py index b755d3d5..9db24303 100644 --- a/reddit-clone/reddit/core/models.py +++ b/reddit-clone/reddit/core/models.py @@ -5,6 +5,8 @@ from sqlalchemy.ext.declarative import declarative_base +__all__ = ("BaseModel",) + Base = declarative_base() diff --git a/reddit-clone/reddit/subreddits/models.py b/reddit-clone/reddit/subreddits/models.py index 18e5d27a..3c45b317 100644 --- a/reddit-clone/reddit/subreddits/models.py +++ b/reddit-clone/reddit/subreddits/models.py @@ -3,10 +3,10 @@ from sqlalchemy import Column, String, Integer, SmallInteger, ForeignKey from sqlalchemy.orm import relationship -from reddit.core.models import Base +from reddit.core.models import BaseModel -class SubReddit(Base): +class SubReddit(BaseModel): """ Represents a SubReddit. """ @@ -49,7 +49,7 @@ class SubReddit(Base): String(255), default=None, comment=""" - The icon for the subreddit. + The icon URL for the subreddit. """, ) diff --git a/reddit-clone/reddit/users/models.py b/reddit-clone/reddit/users/models.py index 4e6fb2a9..c0a653bb 100644 --- a/reddit-clone/reddit/users/models.py +++ b/reddit-clone/reddit/users/models.py @@ -3,10 +3,10 @@ from sqlalchemy import Column, String from sqlalchemy.orm import relationship -from reddit.core.models import Base +from reddit.core.models import BaseModel -class User(Base): +class User(BaseModel): """ Represents an individual user account. """ From 9b31170a136dbce9bb872b9fbdf8840d3b4221f8 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan Date: Tue, 21 Sep 2021 11:18:22 +0530 Subject: [PATCH 005/150] update field comments --- reddit-clone/reddit/subreddits/models.py | 2 +- reddit-clone/reddit/threads/models.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/reddit-clone/reddit/subreddits/models.py b/reddit-clone/reddit/subreddits/models.py index 3c45b317..f922a640 100644 --- a/reddit-clone/reddit/subreddits/models.py +++ b/reddit-clone/reddit/subreddits/models.py @@ -34,7 +34,7 @@ class SubReddit(BaseModel): Integer, ForeignKey("users.id"), comment=""" - The ID of the subreddit's admin. + The admin ID of the subreddit. """, ) diff --git a/reddit-clone/reddit/threads/models.py b/reddit-clone/reddit/threads/models.py index 4da12872..d14e06a1 100644 --- a/reddit-clone/reddit/threads/models.py +++ b/reddit-clone/reddit/threads/models.py @@ -48,7 +48,7 @@ class Thread(BaseModel): Integer, ForeignKey("users.id"), comment=""" - The owner ID the thread. + The owner ID of the thread. """, ) From 8c6bd2148a7e569c37fad4d74817478c3907e656 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan Date: Tue, 21 Sep 2021 11:21:00 +0530 Subject: [PATCH 006/150] update __init__.py --- reddit-clone/reddit/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/reddit-clone/reddit/__init__.py b/reddit-clone/reddit/__init__.py index 61797eca..a7a2b453 100644 --- a/reddit-clone/reddit/__init__.py +++ b/reddit-clone/reddit/__init__.py @@ -15,9 +15,9 @@ def create_application() -> FastAPI: """ application = FastAPI(title="Reddit GraphQL", debug=DEBUG) - application.add_route( - path="/graphql", route=GraphQL(schema=schema, graphiql=True, debug=DEBUG) - ) + graphql_app = GraphQL(schema=schema, graphiql=True, debug=DEBUG) + + application.add_route(path="/graphql", route=graphql_app) return application From b4788f7bbcdc50ce31ccd431cd332b2cec800152 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan Date: Tue, 21 Sep 2021 11:26:27 +0530 Subject: [PATCH 007/150] add example goal --- reddit-clone/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/reddit-clone/README.md b/reddit-clone/README.md index e4f12e40..379a3549 100644 --- a/reddit-clone/README.md +++ b/reddit-clone/README.md @@ -1,6 +1,9 @@ # Reddit GraphQL API This example shows you how to create a Reddit API clone using GraphQL. +The goal of this example is not to re-create the entire reddit API, but +to produce a simpler version that is easier to understand, and implements +most of the features that Strawberry gives us. ## Tech Stack used: From 9ce38ab51ebbf909fbdf08d81bead702222bd64b Mon Sep 17 00:00:00 2001 From: Aryan Iyappan Date: Tue, 21 Sep 2021 11:47:15 +0530 Subject: [PATCH 008/150] add alembic skeleton --- reddit-clone/alembic.ini | 100 +++++++++++++++++++++++++ reddit-clone/alembic/.gitkeep | 0 reddit-clone/alembic/README | 1 + reddit-clone/alembic/env.py | 75 +++++++++++++++++++ reddit-clone/alembic/script.py.mako | 24 ++++++ reddit-clone/reddit/comments/models.py | 34 +++++++++ 6 files changed, 234 insertions(+) create mode 100644 reddit-clone/alembic.ini delete mode 100644 reddit-clone/alembic/.gitkeep create mode 100644 reddit-clone/alembic/README create mode 100644 reddit-clone/alembic/env.py create mode 100644 reddit-clone/alembic/script.py.mako diff --git a/reddit-clone/alembic.ini b/reddit-clone/alembic.ini new file mode 100644 index 00000000..49641573 --- /dev/null +++ b/reddit-clone/alembic.ini @@ -0,0 +1,100 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = alembic + +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python-dateutil library that can be +# installed by adding `alembic[tz]` to the pip requirements +# string value is passed to dateutil.tz.gettz() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the +# "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to alembic/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" +# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. Valid values are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +version_path_separator = os # default: use os.pathsep + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +sqlalchemy.url = driver://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/reddit-clone/alembic/.gitkeep b/reddit-clone/alembic/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/reddit-clone/alembic/README b/reddit-clone/alembic/README new file mode 100644 index 00000000..2500aa1b --- /dev/null +++ b/reddit-clone/alembic/README @@ -0,0 +1 @@ +Generic single-database configuration. diff --git a/reddit-clone/alembic/env.py b/reddit-clone/alembic/env.py new file mode 100644 index 00000000..788c43d1 --- /dev/null +++ b/reddit-clone/alembic/env.py @@ -0,0 +1,75 @@ +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = None + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = engine_from_config( + config.get_section(config.config_ini_section), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/reddit-clone/alembic/script.py.mako b/reddit-clone/alembic/script.py.mako new file mode 100644 index 00000000..2c015630 --- /dev/null +++ b/reddit-clone/alembic/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/reddit-clone/reddit/comments/models.py b/reddit-clone/reddit/comments/models.py index fe4e3f57..70647b01 100644 --- a/reddit-clone/reddit/comments/models.py +++ b/reddit-clone/reddit/comments/models.py @@ -1,3 +1,8 @@ +from typing import Optional + +from sqlalchemy import Column, Integer, Text, ForeignKey +from sqlalchemy.orm import relationship + from reddit.core.models import BaseModel @@ -5,3 +10,32 @@ class Comment(BaseModel): """ Represents a Comment in a thread. """ + + content: str = Column( + Text, + nullable=False, + comment=""" + The content of the comment. + """, + ) + + votes: int = Column( + Integer, + default=1, + comment=""" + The votes for the thread. + """, + ) + + user_id: Optional[int] = Column( + Integer, + ForeignKey("users.id"), + comment=""" + The owner ID of the comment. + """, + ) + + replies = relationship("Comment", backref="parent", lazy="dynamic") + + def __repr__(self) -> str: + return "" % self.id From 294396a180f10a8f2d815663aa9cc2292144fa48 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan Date: Sat, 25 Sep 2021 11:15:02 +0530 Subject: [PATCH 009/150] refactor example structure --- reddit-clone/reddit/__init__.py | 2 -- reddit-clone/reddit/core/config.py | 3 ++ .../reddit/{comments => db}/__init__.py | 0 reddit-clone/reddit/db/base.py | 5 ++++ reddit-clone/reddit/db/session.py | 28 +++++++++++++++++++ .../reddit/{subreddits => models}/__init__.py | 0 .../reddit/{core/models.py => models/base.py} | 12 +++----- .../{comments/models.py => models/comment.py} | 2 +- .../{threads/models.py => models/post.py} | 26 ++++++++--------- .../models.py => models/subreddit.py} | 8 +++--- .../{users/models.py => models/user.py} | 4 +-- reddit-clone/reddit/schema.py | 2 -- reddit-clone/reddit/threads/__init__.py | 0 reddit-clone/reddit/users/__init__.py | 0 14 files changed, 60 insertions(+), 32 deletions(-) rename reddit-clone/reddit/{comments => db}/__init__.py (100%) create mode 100644 reddit-clone/reddit/db/base.py create mode 100644 reddit-clone/reddit/db/session.py rename reddit-clone/reddit/{subreddits => models}/__init__.py (100%) rename reddit-clone/reddit/{core/models.py => models/base.py} (79%) rename reddit-clone/reddit/{comments/models.py => models/comment.py} (95%) rename reddit-clone/reddit/{threads/models.py => models/post.py} (65%) rename reddit-clone/reddit/{subreddits/models.py => models/subreddit.py} (85%) rename reddit-clone/reddit/{users/models.py => models/user.py} (90%) delete mode 100644 reddit-clone/reddit/threads/__init__.py delete mode 100644 reddit-clone/reddit/users/__init__.py diff --git a/reddit-clone/reddit/__init__.py b/reddit-clone/reddit/__init__.py index a7a2b453..59fce6d2 100644 --- a/reddit-clone/reddit/__init__.py +++ b/reddit-clone/reddit/__init__.py @@ -4,8 +4,6 @@ from reddit.core.config import DEBUG from reddit.schema import schema -__all__ = ("app",) - def create_application() -> FastAPI: """ diff --git a/reddit-clone/reddit/core/config.py b/reddit-clone/reddit/core/config.py index 701bbbf2..1a8a6b83 100644 --- a/reddit-clone/reddit/core/config.py +++ b/reddit-clone/reddit/core/config.py @@ -9,3 +9,6 @@ # secret key to use for sessions. SECRET_KEY: Secret = config("SECRET_KEY", cast=Secret) + +# sqlalchemy database url. +DATABASE_URI: str = config("DATABASE_URI", cast=str) diff --git a/reddit-clone/reddit/comments/__init__.py b/reddit-clone/reddit/db/__init__.py similarity index 100% rename from reddit-clone/reddit/comments/__init__.py rename to reddit-clone/reddit/db/__init__.py diff --git a/reddit-clone/reddit/db/base.py b/reddit-clone/reddit/db/base.py new file mode 100644 index 00000000..8132fcfe --- /dev/null +++ b/reddit-clone/reddit/db/base.py @@ -0,0 +1,5 @@ +from sqlalchemy.ext.declarative import declarative_base + +__all__ = ("Base",) + +Base = declarative_base() diff --git a/reddit-clone/reddit/db/session.py b/reddit-clone/reddit/db/session.py new file mode 100644 index 00000000..3cdada70 --- /dev/null +++ b/reddit-clone/reddit/db/session.py @@ -0,0 +1,28 @@ +from contextlib import asynccontextmanager + +from sqlalchemy.orm import sessionmaker +from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine + +from reddit.core.config import DATABASE_URI + +async_engine = create_async_engine(DATABASE_URI, future=True) + +session_factory = sessionmaker(bind=async_engine, class_=AsyncSession) + + +@asynccontextmanager +async def get_session() -> AsyncSession: + """ + Gets a session instance. + + :return: the obtained session. + """ + session = session_factory() + try: + yield session + await session.commit() + except Exception as err: + await session.rollback() + raise err + finally: + await session.close() diff --git a/reddit-clone/reddit/subreddits/__init__.py b/reddit-clone/reddit/models/__init__.py similarity index 100% rename from reddit-clone/reddit/subreddits/__init__.py rename to reddit-clone/reddit/models/__init__.py diff --git a/reddit-clone/reddit/core/models.py b/reddit-clone/reddit/models/base.py similarity index 79% rename from reddit-clone/reddit/core/models.py rename to reddit-clone/reddit/models/base.py index 9db24303..9d0373d7 100644 --- a/reddit-clone/reddit/core/models.py +++ b/reddit-clone/reddit/models/base.py @@ -2,12 +2,8 @@ from datetime import datetime from sqlalchemy import Column -from sqlalchemy.ext.declarative import declarative_base - -__all__ = ("BaseModel",) - -Base = declarative_base() +from reddit.db.base import Base class BaseModel(Base): @@ -21,7 +17,7 @@ class BaseModel(Base): id: Optional[int] = Column( default=None, primary_key=True, - description=""" + comment=""" Identifier for the object. """, ) @@ -29,7 +25,7 @@ class BaseModel(Base): created_at: datetime = Column( nullable=False, default=datetime.now(), - description=""" + comment=""" When the object was created. """, ) @@ -38,7 +34,7 @@ class BaseModel(Base): nullable=False, default=datetime.now(), onupdate=datetime.now(), - description=""" + comment=""" When the object was updated. """, ) diff --git a/reddit-clone/reddit/comments/models.py b/reddit-clone/reddit/models/comment.py similarity index 95% rename from reddit-clone/reddit/comments/models.py rename to reddit-clone/reddit/models/comment.py index 70647b01..51fd2cc7 100644 --- a/reddit-clone/reddit/comments/models.py +++ b/reddit-clone/reddit/models/comment.py @@ -3,7 +3,7 @@ from sqlalchemy import Column, Integer, Text, ForeignKey from sqlalchemy.orm import relationship -from reddit.core.models import BaseModel +from .base import BaseModel class Comment(BaseModel): diff --git a/reddit-clone/reddit/threads/models.py b/reddit-clone/reddit/models/post.py similarity index 65% rename from reddit-clone/reddit/threads/models.py rename to reddit-clone/reddit/models/post.py index d14e06a1..b72dc5be 100644 --- a/reddit-clone/reddit/threads/models.py +++ b/reddit-clone/reddit/models/post.py @@ -3,20 +3,20 @@ from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy.orm import relationship -from reddit.core.models import BaseModel +from .base import BaseModel -class Thread(BaseModel): +class Post(BaseModel): """ - Represents a Thread in a SubReddit. + Represents a post in a Subreddit. """ - __tablename__ = "threads" + __tablename__ = "posts" title: str = Column( String(150), comment=""" - The title for the thread. + The title for the post. """, ) @@ -24,7 +24,7 @@ class Thread(BaseModel): String(1024), default=None, comment=""" - The text for the thread. + The text for the post. """, ) @@ -32,7 +32,7 @@ class Thread(BaseModel): String(255), default=None, comment=""" - The link for the thread. + The link for the post. """, ) @@ -40,7 +40,7 @@ class Thread(BaseModel): String(255), default=None, comment=""" - The thumbnail URL for the thread. + The thumbnail URL for the post. """, ) @@ -48,7 +48,7 @@ class Thread(BaseModel): Integer, ForeignKey("users.id"), comment=""" - The owner ID of the thread. + The owner ID of the post. """, ) @@ -56,7 +56,7 @@ class Thread(BaseModel): Integer, ForeignKey("subreddits.id"), comment=""" - The SubReddit ID of the thread. + The SubReddit ID of the post. """, ) @@ -64,11 +64,11 @@ class Thread(BaseModel): Integer, default=1, comment=""" - The votes for the thread. + The votes for the post. """, ) - comments = relationship("Comment", backref="thread", lazy="dynamic") + comments = relationship("Comment", backref="post", lazy="dynamic") def __repr__(self) -> str: - return "" % self.title + return "" % self.title diff --git a/reddit-clone/reddit/subreddits/models.py b/reddit-clone/reddit/models/subreddit.py similarity index 85% rename from reddit-clone/reddit/subreddits/models.py rename to reddit-clone/reddit/models/subreddit.py index f922a640..a54c9013 100644 --- a/reddit-clone/reddit/subreddits/models.py +++ b/reddit-clone/reddit/models/subreddit.py @@ -3,12 +3,12 @@ from sqlalchemy import Column, String, Integer, SmallInteger, ForeignKey from sqlalchemy.orm import relationship -from reddit.core.models import BaseModel +from .base import BaseModel -class SubReddit(BaseModel): +class Subreddit(BaseModel): """ - Represents a SubReddit. + Represents a Subreddit. """ __tablename__ = "subreddits" @@ -53,7 +53,7 @@ class SubReddit(BaseModel): """, ) - threads = relationship("Thread", backref="subreddit", lazy="dynamic") + posts = relationship("Post", backref="subreddit", lazy="dynamic") def __repr__(self) -> str: return "" % self.name diff --git a/reddit-clone/reddit/users/models.py b/reddit-clone/reddit/models/user.py similarity index 90% rename from reddit-clone/reddit/users/models.py rename to reddit-clone/reddit/models/user.py index c0a653bb..6f8308e3 100644 --- a/reddit-clone/reddit/users/models.py +++ b/reddit-clone/reddit/models/user.py @@ -3,7 +3,7 @@ from sqlalchemy import Column, String from sqlalchemy.orm import relationship -from reddit.core.models import BaseModel +from .base import BaseModel class User(BaseModel): @@ -47,7 +47,7 @@ class User(BaseModel): """, ) - threads = relationship("Thread", backref="user", lazy="dynamic") + posrs = relationship("Post", backref="user", lazy="dynamic") subreddits = relationship("Subreddit", backref="user", lazy="dynamic") diff --git a/reddit-clone/reddit/schema.py b/reddit-clone/reddit/schema.py index b0f87467..41baebbf 100644 --- a/reddit-clone/reddit/schema.py +++ b/reddit-clone/reddit/schema.py @@ -1,6 +1,4 @@ from strawberry import Schema -__all__ = ("schema",) - schema = Schema(query=None, mutation=None) diff --git a/reddit-clone/reddit/threads/__init__.py b/reddit-clone/reddit/threads/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/reddit-clone/reddit/users/__init__.py b/reddit-clone/reddit/users/__init__.py deleted file mode 100644 index e69de29b..00000000 From ac8b8e2cc4f88fc1054221e514bf66167922f46b Mon Sep 17 00:00:00 2001 From: Aryan Iyappan Date: Sat, 25 Sep 2021 11:17:26 +0530 Subject: [PATCH 010/150] update model comments --- reddit-clone/reddit/models/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reddit-clone/reddit/models/user.py b/reddit-clone/reddit/models/user.py index 6f8308e3..e05ca18c 100644 --- a/reddit-clone/reddit/models/user.py +++ b/reddit-clone/reddit/models/user.py @@ -43,7 +43,7 @@ class User(BaseModel): String(255), default=None, comment=""" - The avatar for the user. + The avatar URL for the user. """, ) From 458f223041099ad1970b36e3ef20f753d188dfc9 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan Date: Sat, 25 Sep 2021 11:24:29 +0530 Subject: [PATCH 011/150] update typing --- reddit-clone/reddit/db/session.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/reddit-clone/reddit/db/session.py b/reddit-clone/reddit/db/session.py index 3cdada70..357101a2 100644 --- a/reddit-clone/reddit/db/session.py +++ b/reddit-clone/reddit/db/session.py @@ -1,4 +1,5 @@ from contextlib import asynccontextmanager +from typing import Generator from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine @@ -11,7 +12,7 @@ @asynccontextmanager -async def get_session() -> AsyncSession: +async def get_session() -> Generator[AsyncSession]: """ Gets a session instance. From 900d2597f739de8ecf4615535c8417b83996fce8 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+codebyaryan@users.noreply.github.com> Date: Sat, 25 Sep 2021 17:01:14 +0530 Subject: [PATCH 012/150] Update env.py --- reddit-clone/alembic/env.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/reddit-clone/alembic/env.py b/reddit-clone/alembic/env.py index 788c43d1..842f8fd9 100644 --- a/reddit-clone/alembic/env.py +++ b/reddit-clone/alembic/env.py @@ -13,11 +13,9 @@ # This line sets up loggers basically. fileConfig(config.config_file_name) -# add your model's MetaData object here -# for 'autogenerate' support -# from myapp import mymodel -# target_metadata = mymodel.Base.metadata -target_metadata = None +from reddit.db.base import Base # noqa + +target_metadata = Base.metadata # other values from the config, defined by the needs of env.py, # can be acquired: From fa12496df79fe1d9cffefb49797826db8bd8eced Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 25 Sep 2021 11:31:24 +0000 Subject: [PATCH 013/150] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- reddit-clone/alembic/env.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reddit-clone/alembic/env.py b/reddit-clone/alembic/env.py index 842f8fd9..c78f0514 100644 --- a/reddit-clone/alembic/env.py +++ b/reddit-clone/alembic/env.py @@ -13,7 +13,7 @@ # This line sets up loggers basically. fileConfig(config.config_file_name) -from reddit.db.base import Base # noqa +from reddit.db.base import Base # noqa target_metadata = Base.metadata From 972acd433923c95d968a213de1dc839b7f3ec098 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+codebyaryan@users.noreply.github.com> Date: Sat, 25 Sep 2021 17:35:12 +0530 Subject: [PATCH 014/150] Update alembic.ini --- reddit-clone/alembic.ini | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/reddit-clone/alembic.ini b/reddit-clone/alembic.ini index 49641573..eb6b4537 100644 --- a/reddit-clone/alembic.ini +++ b/reddit-clone/alembic.ini @@ -38,32 +38,6 @@ prepend_sys_path = . # The path separator used here should be the separator specified by "version_path_separator" # version_locations = %(here)s/bar:%(here)s/bat:alembic/versions -# version path separator; As mentioned above, this is the character used to split -# version_locations. Valid values are: -# -# version_path_separator = : -# version_path_separator = ; -# version_path_separator = space -version_path_separator = os # default: use os.pathsep - -# the output encoding used when revision files -# are written from script.py.mako -# output_encoding = utf-8 - -sqlalchemy.url = driver://user:pass@localhost/dbname - - -[post_write_hooks] -# post_write_hooks defines scripts or Python functions that are run -# on newly generated revision scripts. See the documentation for further -# detail and examples - -# format using "black" - use the console_scripts runner, against the "black" entrypoint -# hooks = black -# black.type = console_scripts -# black.entrypoint = black -# black.options = -l 79 REVISION_SCRIPT_FILENAME - # Logging configuration [loggers] keys = root,sqlalchemy,alembic From 45502a5fb07d23fb19831ce2db1e32d5a0f383ef Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+codebyaryan@users.noreply.github.com> Date: Sat, 25 Sep 2021 17:37:18 +0530 Subject: [PATCH 015/150] Update post.py --- reddit-clone/reddit/models/post.py | 1 + 1 file changed, 1 insertion(+) diff --git a/reddit-clone/reddit/models/post.py b/reddit-clone/reddit/models/post.py index b72dc5be..b330a45f 100644 --- a/reddit-clone/reddit/models/post.py +++ b/reddit-clone/reddit/models/post.py @@ -31,6 +31,7 @@ class Post(BaseModel): link: Optional[str] = Column( String(255), default=None, + unique=True, comment=""" The link for the post. """, From b75378bd89019ce9132441260a81c0aa0b4dfe50 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+codebyaryan@users.noreply.github.com> Date: Sat, 25 Sep 2021 17:39:26 +0530 Subject: [PATCH 016/150] Update user.py --- reddit-clone/reddit/models/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reddit-clone/reddit/models/user.py b/reddit-clone/reddit/models/user.py index e05ca18c..5efdd992 100644 --- a/reddit-clone/reddit/models/user.py +++ b/reddit-clone/reddit/models/user.py @@ -47,7 +47,7 @@ class User(BaseModel): """, ) - posrs = relationship("Post", backref="user", lazy="dynamic") + posts = relationship("Post", backref="user", lazy="dynamic") subreddits = relationship("Subreddit", backref="user", lazy="dynamic") From d100613bf1ec4ed8cc1ae20f75aacb1ef7cd9612 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+codebyaryan@users.noreply.github.com> Date: Sat, 25 Sep 2021 18:47:21 +0530 Subject: [PATCH 017/150] Update user.py --- reddit-clone/reddit/models/user.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/reddit-clone/reddit/models/user.py b/reddit-clone/reddit/models/user.py index 5efdd992..cf9ce918 100644 --- a/reddit-clone/reddit/models/user.py +++ b/reddit-clone/reddit/models/user.py @@ -49,9 +49,16 @@ class User(BaseModel): posts = relationship("Post", backref="user", lazy="dynamic") - subreddits = relationship("Subreddit", backref="user", lazy="dynamic") + subreddits = relationship("Subreddit", backref="user", secondary="subreddit_users", lazy="dynamic") comments = relationship("Comment", backref="user", lazy="dynamic") def __repr__(self) -> str: return "" % self.username + + +class SubredditUser(BaseModel): + """ + Represents a Subreddit-user relationship. + """ + __tablename__ = "subreddit_users" From 19d6dad3228f63bcdd08f03a0074134907b7b58e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 25 Sep 2021 13:17:30 +0000 Subject: [PATCH 018/150] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- reddit-clone/reddit/models/user.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/reddit-clone/reddit/models/user.py b/reddit-clone/reddit/models/user.py index cf9ce918..3035d499 100644 --- a/reddit-clone/reddit/models/user.py +++ b/reddit-clone/reddit/models/user.py @@ -49,7 +49,9 @@ class User(BaseModel): posts = relationship("Post", backref="user", lazy="dynamic") - subreddits = relationship("Subreddit", backref="user", secondary="subreddit_users", lazy="dynamic") + subreddits = relationship( + "Subreddit", backref="user", secondary="subreddit_users", lazy="dynamic" + ) comments = relationship("Comment", backref="user", lazy="dynamic") @@ -61,4 +63,5 @@ class SubredditUser(BaseModel): """ Represents a Subreddit-user relationship. """ + __tablename__ = "subreddit_users" From 4e8a60e7bc246859c92848dcb169d791dd7d08f1 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+codebyaryan@users.noreply.github.com> Date: Sat, 25 Sep 2021 18:50:46 +0530 Subject: [PATCH 019/150] Update user.py --- reddit-clone/reddit/models/user.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/reddit-clone/reddit/models/user.py b/reddit-clone/reddit/models/user.py index 3035d499..cf813744 100644 --- a/reddit-clone/reddit/models/user.py +++ b/reddit-clone/reddit/models/user.py @@ -53,6 +53,8 @@ class User(BaseModel): "Subreddit", backref="user", secondary="subreddit_users", lazy="dynamic" ) + + comments = relationship("Comment", backref="user", lazy="dynamic") def __repr__(self) -> str: @@ -65,3 +67,21 @@ class SubredditUser(BaseModel): """ __tablename__ = "subreddit_users" + + user_id: int = Column( + Integer, + ForeignKey("users.id"), + nullable=False, + comment=""" + The relationship's user ID. + """, + ) + + subreddit_id: int = Column( + Integer, + ForeignKey("subreddits.id"), + nullable=False, + comment=""" + The relationship's subreddit ID. + """, + ) From bc85d467c0c185179e597c7a39e7d1f1a5509899 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 25 Sep 2021 13:20:56 +0000 Subject: [PATCH 020/150] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- reddit-clone/reddit/models/user.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/reddit-clone/reddit/models/user.py b/reddit-clone/reddit/models/user.py index cf813744..056b73ee 100644 --- a/reddit-clone/reddit/models/user.py +++ b/reddit-clone/reddit/models/user.py @@ -53,8 +53,6 @@ class User(BaseModel): "Subreddit", backref="user", secondary="subreddit_users", lazy="dynamic" ) - - comments = relationship("Comment", backref="user", lazy="dynamic") def __repr__(self) -> str: From 8b0bf797864e2a52ee96988b48f352aea87e9d32 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+codebyaryan@users.noreply.github.com> Date: Sat, 25 Sep 2021 18:53:04 +0530 Subject: [PATCH 021/150] Update user.py --- reddit-clone/reddit/models/user.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reddit-clone/reddit/models/user.py b/reddit-clone/reddit/models/user.py index 056b73ee..9701021f 100644 --- a/reddit-clone/reddit/models/user.py +++ b/reddit-clone/reddit/models/user.py @@ -69,7 +69,7 @@ class SubredditUser(BaseModel): user_id: int = Column( Integer, ForeignKey("users.id"), - nullable=False, + primary_key=True, comment=""" The relationship's user ID. """, @@ -78,7 +78,7 @@ class SubredditUser(BaseModel): subreddit_id: int = Column( Integer, ForeignKey("subreddits.id"), - nullable=False, + primary_key=True, comment=""" The relationship's subreddit ID. """, From 3204fbbc9f5f61956dd0c3756d214790a3166b6b Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+codebyaryan@users.noreply.github.com> Date: Sun, 26 Sep 2021 13:41:39 +0530 Subject: [PATCH 022/150] Create database.py --- reddit-clone/reddit/database.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 reddit-clone/reddit/database.py diff --git a/reddit-clone/reddit/database.py b/reddit-clone/reddit/database.py new file mode 100644 index 00000000..357101a2 --- /dev/null +++ b/reddit-clone/reddit/database.py @@ -0,0 +1,29 @@ +from contextlib import asynccontextmanager +from typing import Generator + +from sqlalchemy.orm import sessionmaker +from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine + +from reddit.core.config import DATABASE_URI + +async_engine = create_async_engine(DATABASE_URI, future=True) + +session_factory = sessionmaker(bind=async_engine, class_=AsyncSession) + + +@asynccontextmanager +async def get_session() -> Generator[AsyncSession]: + """ + Gets a session instance. + + :return: the obtained session. + """ + session = session_factory() + try: + yield session + await session.commit() + except Exception as err: + await session.rollback() + raise err + finally: + await session.close() From 61ae172c7bfb903449d26979656b358366f544fa Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+codebyaryan@users.noreply.github.com> Date: Sun, 26 Sep 2021 13:42:54 +0530 Subject: [PATCH 023/150] Update database.py --- reddit-clone/reddit/database.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/reddit-clone/reddit/database.py b/reddit-clone/reddit/database.py index 357101a2..e862ffa2 100644 --- a/reddit-clone/reddit/database.py +++ b/reddit-clone/reddit/database.py @@ -3,6 +3,8 @@ from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine +from sqlalchemy.ext.declarative import declarative_base + from reddit.core.config import DATABASE_URI @@ -27,3 +29,5 @@ async def get_session() -> Generator[AsyncSession]: raise err finally: await session.close() + +Base = declarative_base() From 64b1a9a0f8df4457e0ee300f9ab48ce3e0755a38 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 26 Sep 2021 08:13:04 +0000 Subject: [PATCH 024/150] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- reddit-clone/reddit/database.py | 1 + 1 file changed, 1 insertion(+) diff --git a/reddit-clone/reddit/database.py b/reddit-clone/reddit/database.py index e862ffa2..9b529274 100644 --- a/reddit-clone/reddit/database.py +++ b/reddit-clone/reddit/database.py @@ -30,4 +30,5 @@ async def get_session() -> Generator[AsyncSession]: finally: await session.close() + Base = declarative_base() From cc4fb355ef3d038ce35fd7b8dd8050ae8536b8c2 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+codebyaryan@users.noreply.github.com> Date: Sun, 26 Sep 2021 13:43:18 +0530 Subject: [PATCH 025/150] Delete reddit-clone/reddit/db directory --- reddit-clone/reddit/db/__init__.py | 0 reddit-clone/reddit/db/base.py | 5 ----- reddit-clone/reddit/db/session.py | 29 ----------------------------- 3 files changed, 34 deletions(-) delete mode 100644 reddit-clone/reddit/db/__init__.py delete mode 100644 reddit-clone/reddit/db/base.py delete mode 100644 reddit-clone/reddit/db/session.py diff --git a/reddit-clone/reddit/db/__init__.py b/reddit-clone/reddit/db/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/reddit-clone/reddit/db/base.py b/reddit-clone/reddit/db/base.py deleted file mode 100644 index 8132fcfe..00000000 --- a/reddit-clone/reddit/db/base.py +++ /dev/null @@ -1,5 +0,0 @@ -from sqlalchemy.ext.declarative import declarative_base - -__all__ = ("Base",) - -Base = declarative_base() diff --git a/reddit-clone/reddit/db/session.py b/reddit-clone/reddit/db/session.py deleted file mode 100644 index 357101a2..00000000 --- a/reddit-clone/reddit/db/session.py +++ /dev/null @@ -1,29 +0,0 @@ -from contextlib import asynccontextmanager -from typing import Generator - -from sqlalchemy.orm import sessionmaker -from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine - -from reddit.core.config import DATABASE_URI - -async_engine = create_async_engine(DATABASE_URI, future=True) - -session_factory = sessionmaker(bind=async_engine, class_=AsyncSession) - - -@asynccontextmanager -async def get_session() -> Generator[AsyncSession]: - """ - Gets a session instance. - - :return: the obtained session. - """ - session = session_factory() - try: - yield session - await session.commit() - except Exception as err: - await session.rollback() - raise err - finally: - await session.close() From 5dc145424d75dda874d3981a21299f0a9a15d6e8 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+codebyaryan@users.noreply.github.com> Date: Sun, 26 Sep 2021 13:43:41 +0530 Subject: [PATCH 026/150] Update env.py --- reddit-clone/alembic/env.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reddit-clone/alembic/env.py b/reddit-clone/alembic/env.py index c78f0514..bea3fde4 100644 --- a/reddit-clone/alembic/env.py +++ b/reddit-clone/alembic/env.py @@ -13,7 +13,7 @@ # This line sets up loggers basically. fileConfig(config.config_file_name) -from reddit.db.base import Base # noqa +from reddit.database import Base # noqa target_metadata = Base.metadata From 4317bbe22ea77586b7889e6df1365abe86abc5b7 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+codebyaryan@users.noreply.github.com> Date: Sun, 26 Sep 2021 13:45:00 +0530 Subject: [PATCH 027/150] Create settings.py --- reddit-clone/reddit/settings.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 reddit-clone/reddit/settings.py diff --git a/reddit-clone/reddit/settings.py b/reddit-clone/reddit/settings.py new file mode 100644 index 00000000..1a8a6b83 --- /dev/null +++ b/reddit-clone/reddit/settings.py @@ -0,0 +1,14 @@ +from starlette.config import Config +from starlette.datastructures import Secret + + +config = Config(env_file=".env") + +# whether the application is in development mode. +DEBUG: bool = config("DEBUG", cast=bool, default=False) + +# secret key to use for sessions. +SECRET_KEY: Secret = config("SECRET_KEY", cast=Secret) + +# sqlalchemy database url. +DATABASE_URI: str = config("DATABASE_URI", cast=str) From 8280d5a0fcfad4d11d0e5197ead061f42e826fdb Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+codebyaryan@users.noreply.github.com> Date: Sun, 26 Sep 2021 13:58:19 +0530 Subject: [PATCH 028/150] Update database.py --- reddit-clone/reddit/database.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reddit-clone/reddit/database.py b/reddit-clone/reddit/database.py index 9b529274..82bb1e54 100644 --- a/reddit-clone/reddit/database.py +++ b/reddit-clone/reddit/database.py @@ -6,7 +6,7 @@ from sqlalchemy.ext.declarative import declarative_base -from reddit.core.config import DATABASE_URI +from .settings import DATABASE_URI async_engine = create_async_engine(DATABASE_URI, future=True) From 74dd9133137a687db54118e100f366e22582dd1f Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+codebyaryan@users.noreply.github.com> Date: Sun, 26 Sep 2021 13:58:30 +0530 Subject: [PATCH 029/150] Delete reddit-clone/reddit/core directory --- reddit-clone/reddit/core/__init__.py | 0 reddit-clone/reddit/core/config.py | 14 -------------- 2 files changed, 14 deletions(-) delete mode 100644 reddit-clone/reddit/core/__init__.py delete mode 100644 reddit-clone/reddit/core/config.py diff --git a/reddit-clone/reddit/core/__init__.py b/reddit-clone/reddit/core/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/reddit-clone/reddit/core/config.py b/reddit-clone/reddit/core/config.py deleted file mode 100644 index 1a8a6b83..00000000 --- a/reddit-clone/reddit/core/config.py +++ /dev/null @@ -1,14 +0,0 @@ -from starlette.config import Config -from starlette.datastructures import Secret - - -config = Config(env_file=".env") - -# whether the application is in development mode. -DEBUG: bool = config("DEBUG", cast=bool, default=False) - -# secret key to use for sessions. -SECRET_KEY: Secret = config("SECRET_KEY", cast=Secret) - -# sqlalchemy database url. -DATABASE_URI: str = config("DATABASE_URI", cast=str) From 8ac9df72df2cab6f89c89afede9d0a1d7a6ba8c7 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Thu, 30 Sep 2021 08:42:01 +0530 Subject: [PATCH 030/150] refactor project structure --- .idea/.gitignore | 3 + .idea/inspectionProfiles/Project_Default.xml | 46 +++ .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 4 + .idea/modules.xml | 8 + .idea/strawberry-examples.iml | 12 + .idea/vcs.xml | 6 + .../models/__init__.py => .projectroot} | 0 reddit-clone/poetry.lock | 299 ++++++++---------- reddit-clone/reddit/comments/__init__.py | 0 reddit-clone/reddit/comments/models.py | 23 ++ reddit-clone/reddit/database.py | 5 +- reddit-clone/reddit/models/base.py | 40 --- reddit-clone/reddit/models/comment.py | 41 --- reddit-clone/reddit/models/post.py | 75 ----- reddit-clone/reddit/models/subreddit.py | 59 ---- reddit-clone/reddit/models/user.py | 85 ----- reddit-clone/reddit/posts/__init__.py | 0 reddit-clone/reddit/posts/models.py | 35 ++ reddit-clone/reddit/subreddits/__init__.py | 0 reddit-clone/reddit/subreddits/models.py | 31 ++ reddit-clone/reddit/users/__init__.py | 0 reddit-clone/reddit/users/models.py | 50 +++ 23 files changed, 366 insertions(+), 462 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/strawberry-examples.iml create mode 100644 .idea/vcs.xml rename reddit-clone/{reddit/models/__init__.py => .projectroot} (100%) create mode 100644 reddit-clone/reddit/comments/__init__.py create mode 100644 reddit-clone/reddit/comments/models.py delete mode 100644 reddit-clone/reddit/models/base.py delete mode 100644 reddit-clone/reddit/models/comment.py delete mode 100644 reddit-clone/reddit/models/post.py delete mode 100644 reddit-clone/reddit/models/subreddit.py delete mode 100644 reddit-clone/reddit/models/user.py create mode 100644 reddit-clone/reddit/posts/__init__.py create mode 100644 reddit-clone/reddit/posts/models.py create mode 100644 reddit-clone/reddit/subreddits/__init__.py create mode 100644 reddit-clone/reddit/subreddits/models.py create mode 100644 reddit-clone/reddit/users/__init__.py create mode 100644 reddit-clone/reddit/users/models.py diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..26d33521 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..3c6e0d59 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,46 @@ + + + + diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 00000000..cc5462da --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..2a9526cd --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..1e10262b --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/.idea/strawberry-examples.iml b/.idea/strawberry-examples.iml new file mode 100644 index 00000000..292aeb08 --- /dev/null +++ b/.idea/strawberry-examples.iml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..5ace414d --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/reddit-clone/reddit/models/__init__.py b/reddit-clone/.projectroot similarity index 100% rename from reddit-clone/reddit/models/__init__.py rename to reddit-clone/.projectroot diff --git a/reddit-clone/poetry.lock b/reddit-clone/poetry.lock index eef41ea6..79b5fd46 100644 --- a/reddit-clone/poetry.lock +++ b/reddit-clone/poetry.lock @@ -146,7 +146,7 @@ toml = "*" [[package]] name = "flake8-bugbear" -version = "21.9.1" +version = "21.9.2" description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." category = "dev" optional = false @@ -169,7 +169,7 @@ python-versions = ">=3.6,<4" [[package]] name = "greenlet" -version = "1.1.1" +version = "1.1.2" description = "Lightweight in-process concurrent programming" category = "main" optional = false @@ -296,7 +296,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [[package]] name = "platformdirs" -version = "2.3.0" +version = "2.4.0" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false @@ -388,7 +388,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [[package]] name = "regex" -version = "2021.8.28" +version = "2021.9.24" description = "Alternative regular expression module, to replace re." category = "dev" optional = false @@ -404,29 +404,30 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "sqlalchemy" -version = "1.4.23" +version = "1.4.25" 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 in \"x86_64 X86_64 aarch64 AARCH64 ppc64le PPC64LE amd64 AMD64 win32 WIN32\""} +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\")"} importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} mypy = {version = ">=0.910", optional = true, markers = "python_version >= \"3\" and extra == \"mypy\""} sqlalchemy2-stubs = {version = "*", optional = true, markers = "extra == \"mypy\""} [package.extras] aiomysql = ["greenlet (!=0.4.17)", "aiomysql"] -aiosqlite = ["greenlet (!=0.4.17)", "aiosqlite"] +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.0)"] mariadb_connector = ["mariadb (>=1.0.1)"] 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 = ["mysqlconnector"] +mysql_connector = ["mysql-connector-python"] oracle = ["cx_oracle (>=7,<8)", "cx_oracle (>=7)"] postgresql = ["psycopg2 (>=2.7)"] postgresql_asyncpg = ["greenlet (!=0.4.17)", "asyncpg"] @@ -438,7 +439,7 @@ sqlcipher = ["sqlcipher3-binary"] [[package]] name = "sqlalchemy2-stubs" -version = "0.0.2a15" +version = "0.0.2a17" description = "Typing Stubs for SQLAlchemy 1.4" category = "main" optional = false @@ -573,7 +574,7 @@ python-versions = ">=3.7" [[package]] name = "zipp" -version = "3.5.0" +version = "3.6.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false @@ -630,64 +631,64 @@ flake8-black = [ {file = "flake8_black-0.2.3-py3-none-any.whl", hash = "sha256:cc080ba5b3773b69ba102b6617a00cc4ecbad8914109690cfda4d565ea435d96"}, ] flake8-bugbear = [ - {file = "flake8-bugbear-21.9.1.tar.gz", hash = "sha256:2f60c8ce0dc53d51da119faab2d67dea978227f0f92ed3c44eb7d65fb2e06a96"}, - {file = "flake8_bugbear-21.9.1-py36.py37.py38-none-any.whl", hash = "sha256:45bfdccfb9f2d8aa140e33cac8f46f1e38215c13d5aa8650e7e188d84e2f94c6"}, + {file = "flake8-bugbear-21.9.2.tar.gz", hash = "sha256:db9a09893a6c649a197f5350755100bb1dd84f110e60cf532fdfa07e41808ab2"}, + {file = "flake8_bugbear-21.9.2-py36.py37.py38-none-any.whl", hash = "sha256:4f7eaa6f05b7d7ea4cbbde93f7bcdc5438e79320fa1ec420d860c181af38b769"}, ] graphql-core = [ {file = "graphql-core-3.1.6.tar.gz", hash = "sha256:e65975b6a13878f9113a1fa5320760585b522d139944e005936b1b8358d0651a"}, {file = "graphql_core-3.1.6-py3-none-any.whl", hash = "sha256:c78d09596d347e1cffd266c5384abfedf43ed1eae08729773bebb3d527fe5a14"}, ] greenlet = [ - {file = "greenlet-1.1.1-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:476ba9435afaead4382fbab8f1882f75e3fb2285c35c9285abb3dd30237f9142"}, - {file = "greenlet-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:44556302c0ab376e37939fd0058e1f0db2e769580d340fb03b01678d1ff25f68"}, - {file = "greenlet-1.1.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40abb7fec4f6294225d2b5464bb6d9552050ded14a7516588d6f010e7e366dcc"}, - {file = "greenlet-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:a11b6199a0b9dc868990456a2667167d0ba096c5224f6258e452bfbe5a9742c5"}, - {file = "greenlet-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e22a82d2b416d9227a500c6860cf13e74060cf10e7daf6695cbf4e6a94e0eee4"}, - {file = "greenlet-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bad269e442f1b7ffa3fa8820b3c3aa66f02a9f9455b5ba2db5a6f9eea96f56de"}, - {file = "greenlet-1.1.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:8ddb38fb6ad96c2ef7468ff73ba5c6876b63b664eebb2c919c224261ae5e8378"}, - {file = "greenlet-1.1.1-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:84782c80a433d87530ae3f4b9ed58d4a57317d9918dfcc6a59115fa2d8731f2c"}, - {file = "greenlet-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac991947ca6533ada4ce7095f0e28fe25d5b2f3266ad5b983ed4201e61596acf"}, - {file = "greenlet-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5317701c7ce167205c0569c10abc4bd01c7f4cf93f642c39f2ce975fa9b78a3c"}, - {file = "greenlet-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4870b018ca685ff573edd56b93f00a122f279640732bb52ce3a62b73ee5c4a92"}, - {file = "greenlet-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:990e0f5e64bcbc6bdbd03774ecb72496224d13b664aa03afd1f9b171a3269272"}, - {file = "greenlet-1.1.1-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:a414f8e14aa7bacfe1578f17c11d977e637d25383b6210587c29210af995ef04"}, - {file = "greenlet-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:e02780da03f84a671bb4205c5968c120f18df081236d7b5462b380fd4f0b497b"}, - {file = "greenlet-1.1.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:dfcb5a4056e161307d103bc013478892cfd919f1262c2bb8703220adcb986362"}, - {file = "greenlet-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:655ab836324a473d4cd8cf231a2d6f283ed71ed77037679da554e38e606a7117"}, - {file = "greenlet-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:6ce9d0784c3c79f3e5c5c9c9517bbb6c7e8aa12372a5ea95197b8a99402aa0e6"}, - {file = "greenlet-1.1.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:3fc6a447735749d651d8919da49aab03c434a300e9f0af1c886d560405840fd1"}, - {file = "greenlet-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8039f5fe8030c43cd1732d9a234fdcbf4916fcc32e21745ca62e75023e4d4649"}, - {file = "greenlet-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fddfb31aa2ac550b938d952bca8a87f1db0f8dc930ffa14ce05b5c08d27e7fd1"}, - {file = "greenlet-1.1.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97a807437b81f90f85022a9dcfd527deea38368a3979ccb49d93c9198b2c722"}, - {file = "greenlet-1.1.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf31e894dabb077a35bbe6963285d4515a387ff657bd25b0530c7168e48f167f"}, - {file = "greenlet-1.1.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4eae94de9924bbb4d24960185363e614b1b62ff797c23dc3c8a7c75bbb8d187e"}, - {file = "greenlet-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:c1862f9f1031b1dee3ff00f1027fcd098ffc82120f43041fe67804b464bbd8a7"}, - {file = "greenlet-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:9b02e6039eafd75e029d8c58b7b1f3e450ca563ef1fe21c7e3e40b9936c8d03e"}, - {file = "greenlet-1.1.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:84488516639c3c5e5c0e52f311fff94ebc45b56788c2a3bfe9cf8e75670f4de3"}, - {file = "greenlet-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3f8fc59bc5d64fa41f58b0029794f474223693fd00016b29f4e176b3ee2cfd9f"}, - {file = "greenlet-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3e594015a2349ec6dcceda9aca29da8dc89e85b56825b7d1f138a3f6bb79dd4c"}, - {file = "greenlet-1.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e41f72f225192d5d4df81dad2974a8943b0f2d664a2a5cfccdf5a01506f5523c"}, - {file = "greenlet-1.1.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75ff270fd05125dce3303e9216ccddc541a9e072d4fc764a9276d44dee87242b"}, - {file = "greenlet-1.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cde7ee190196cbdc078511f4df0be367af85636b84d8be32230f4871b960687"}, - {file = "greenlet-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:f253dad38605486a4590f9368ecbace95865fea0f2b66615d121ac91fd1a1563"}, - {file = "greenlet-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a91ee268f059583176c2c8b012a9fce7e49ca6b333a12bbc2dd01fc1a9783885"}, - {file = "greenlet-1.1.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:34e6675167a238bede724ee60fe0550709e95adaff6a36bcc97006c365290384"}, - {file = "greenlet-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:bf3725d79b1ceb19e83fb1aed44095518c0fcff88fba06a76c0891cfd1f36837"}, - {file = "greenlet-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:5c3b735ccf8fc8048664ee415f8af5a3a018cc92010a0d7195395059b4b39b7d"}, - {file = "greenlet-1.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2002a59453858c7f3404690ae80f10c924a39f45f6095f18a985a1234c37334"}, - {file = "greenlet-1.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04e1849c88aa56584d4a0a6e36af5ec7cc37993fdc1fda72b56aa1394a92ded3"}, - {file = "greenlet-1.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8d4ed48eed7414ccb2aaaecbc733ed2a84c299714eae3f0f48db085342d5629"}, - {file = "greenlet-1.1.1-cp38-cp38-win32.whl", hash = "sha256:2f89d74b4f423e756a018832cd7a0a571e0a31b9ca59323b77ce5f15a437629b"}, - {file = "greenlet-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:d15cb6f8706678dc47fb4e4f8b339937b04eda48a0af1cca95f180db552e7663"}, - {file = "greenlet-1.1.1-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b050dbb96216db273b56f0e5960959c2b4cb679fe1e58a0c3906fa0a60c00662"}, - {file = "greenlet-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e0696525500bc8aa12eae654095d2260db4dc95d5c35af2b486eae1bf914ccd"}, - {file = "greenlet-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:07e6d88242e09b399682b39f8dfa1e7e6eca66b305de1ff74ed9eb1a7d8e539c"}, - {file = "greenlet-1.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98b491976ed656be9445b79bc57ed21decf08a01aaaf5fdabf07c98c108111f6"}, - {file = "greenlet-1.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e72db813c28906cdc59bd0da7c325d9b82aa0b0543014059c34c8c4ad20e16"}, - {file = "greenlet-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:090126004c8ab9cd0787e2acf63d79e80ab41a18f57d6448225bbfcba475034f"}, - {file = "greenlet-1.1.1-cp39-cp39-win32.whl", hash = "sha256:1796f2c283faab2b71c67e9b9aefb3f201fdfbee5cb55001f5ffce9125f63a45"}, - {file = "greenlet-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:4adaf53ace289ced90797d92d767d37e7cdc29f13bd3830c3f0a561277a4ae83"}, - {file = "greenlet-1.1.1.tar.gz", hash = "sha256:c0f22774cd8294078bdf7392ac73cf00bfa1e5e0ed644bd064fdabc5f2a2f481"}, + {file = "greenlet-1.1.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6"}, + {file = "greenlet-1.1.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:aec52725173bd3a7b56fe91bc56eccb26fbdff1386ef123abb63c84c5b43b63a"}, + {file = "greenlet-1.1.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:833e1551925ed51e6b44c800e71e77dacd7e49181fdc9ac9a0bf3714d515785d"}, + {file = "greenlet-1.1.2-cp27-cp27m-win32.whl", hash = "sha256:aa5b467f15e78b82257319aebc78dd2915e4c1436c3c0d1ad6f53e47ba6e2713"}, + {file = "greenlet-1.1.2-cp27-cp27m-win_amd64.whl", hash = "sha256:40b951f601af999a8bf2ce8c71e8aaa4e8c6f78ff8afae7b808aae2dc50d4c40"}, + {file = "greenlet-1.1.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:95e69877983ea39b7303570fa6760f81a3eec23d0e3ab2021b7144b94d06202d"}, + {file = "greenlet-1.1.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:356b3576ad078c89a6107caa9c50cc14e98e3a6c4874a37c3e0273e4baf33de8"}, + {file = "greenlet-1.1.2-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8639cadfda96737427330a094476d4c7a56ac03de7265622fcf4cfe57c8ae18d"}, + {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e5306482182170ade15c4b0d8386ded995a07d7cc2ca8f27958d34d6736497"}, + {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6a36bb9474218c7a5b27ae476035497a6990e21d04c279884eb10d9b290f1b1"}, + {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abb7a75ed8b968f3061327c433a0fbd17b729947b400747c334a9c29a9af6c58"}, + {file = "greenlet-1.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:14d4f3cd4e8b524ae9b8aa567858beed70c392fdec26dbdb0a8a418392e71708"}, + {file = "greenlet-1.1.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:17ff94e7a83aa8671a25bf5b59326ec26da379ace2ebc4411d690d80a7fbcf23"}, + {file = "greenlet-1.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9f3cba480d3deb69f6ee2c1825060177a22c7826431458c697df88e6aeb3caee"}, + {file = "greenlet-1.1.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:fa877ca7f6b48054f847b61d6fa7bed5cebb663ebc55e018fda12db09dcc664c"}, + {file = "greenlet-1.1.2-cp35-cp35m-win32.whl", hash = "sha256:7cbd7574ce8e138bda9df4efc6bf2ab8572c9aff640d8ecfece1b006b68da963"}, + {file = "greenlet-1.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:903bbd302a2378f984aef528f76d4c9b1748f318fe1294961c072bdc7f2ffa3e"}, + {file = "greenlet-1.1.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:049fe7579230e44daef03a259faa24511d10ebfa44f69411d99e6a184fe68073"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:dd0b1e9e891f69e7675ba5c92e28b90eaa045f6ab134ffe70b52e948aa175b3c"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7418b6bfc7fe3331541b84bb2141c9baf1ec7132a7ecd9f375912eca810e714e"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9d29ca8a77117315101425ec7ec2a47a22ccf59f5593378fc4077ac5b754fce"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eff9d20417ff9dcb0d25e2defc2574d10b491bf2e693b4e491914738b7908168"}, + {file = "greenlet-1.1.2-cp36-cp36m-win32.whl", hash = "sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa"}, + {file = "greenlet-1.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f0214eb2a23b85528310dad848ad2ac58e735612929c8072f6093f3585fd342d"}, + {file = "greenlet-1.1.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fdcec0b8399108577ec290f55551d926d9a1fa6cad45882093a7a07ac5ec147b"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:93f81b134a165cc17123626ab8da2e30c0455441d4ab5576eed73a64c025b25c"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f276df9830dba7a333544bd41070e8175762a7ac20350786b322b714b0e654f5"}, + {file = "greenlet-1.1.2-cp37-cp37m-win32.whl", hash = "sha256:64e6175c2e53195278d7388c454e0b30997573f3f4bd63697f88d855f7a6a1fc"}, + {file = "greenlet-1.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06"}, + {file = "greenlet-1.1.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:9633b3034d3d901f0a46b7939f8c4d64427dfba6bbc5a36b1a67364cf148a1b0"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:eb6ea6da4c787111adf40f697b4e58732ee0942b5d3bd8f435277643329ba627"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:f3acda1924472472ddd60c29e5b9db0cec629fbe3c5c5accb74d6d6d14773478"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e859fcb4cbe93504ea18008d1df98dee4f7766db66c435e4882ab35cf70cac43"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec8c433b3ab0419100bd45b47c9c8551248a5aee30ca5e9d399a0b57ac04651b"}, + {file = "greenlet-1.1.2-cp38-cp38-win32.whl", hash = "sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd"}, + {file = "greenlet-1.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3"}, + {file = "greenlet-1.1.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:166eac03e48784a6a6e0e5f041cfebb1ab400b394db188c48b3a84737f505b67"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:572e1787d1460da79590bf44304abbc0a2da944ea64ec549188fa84d89bba7ab"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:be5f425ff1f5f4b3c1e33ad64ab994eed12fc284a6ea71c5243fd564502ecbe5"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7227b47e73dedaa513cdebb98469705ef0d66eb5a1250144468e9c3097d6b59b"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3"}, + {file = "greenlet-1.1.2-cp39-cp39-win32.whl", hash = "sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf"}, + {file = "greenlet-1.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd"}, + {file = "greenlet-1.1.2.tar.gz", hash = "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a"}, ] h11 = [ {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"}, @@ -723,22 +724,12 @@ mako = [ {file = "Mako-1.1.5.tar.gz", hash = "sha256:169fa52af22a91900d852e937400e79f535496191c63712e3b9fda5a9bed6fc3"}, ] markupsafe = [ - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, @@ -747,21 +738,14 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, @@ -771,9 +755,6 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, @@ -816,8 +797,8 @@ pathspec = [ {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, ] platformdirs = [ - {file = "platformdirs-2.3.0-py3-none-any.whl", hash = "sha256:8003ac87717ae2c7ee1ea5a84a1a61e87f3fbd16eb5aadba194ea30a9019f648"}, - {file = "platformdirs-2.3.0.tar.gz", hash = "sha256:15b056538719b1c94bdaccb29e5f81879c7f7f0f4a153f46086d155dffcd4f0f"}, + {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"}, + {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"}, ] pycodestyle = [ {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, @@ -898,87 +879,87 @@ pyyaml = [ {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, ] regex = [ - {file = "regex-2021.8.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d05ad5367c90814099000442b2125535e9d77581855b9bee8780f1b41f2b1a2"}, - {file = "regex-2021.8.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3bf1bc02bc421047bfec3343729c4bbbea42605bcfd6d6bfe2c07ade8b12d2a"}, - {file = "regex-2021.8.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f6a808044faae658f546dd5f525e921de9fa409de7a5570865467f03a626fc0"}, - {file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a617593aeacc7a691cc4af4a4410031654f2909053bd8c8e7db837f179a630eb"}, - {file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79aef6b5cd41feff359acaf98e040844613ff5298d0d19c455b3d9ae0bc8c35a"}, - {file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0fc1f8f06977c2d4f5e3d3f0d4a08089be783973fc6b6e278bde01f0544ff308"}, - {file = "regex-2021.8.28-cp310-cp310-win32.whl", hash = "sha256:6eebf512aa90751d5ef6a7c2ac9d60113f32e86e5687326a50d7686e309f66ed"}, - {file = "regex-2021.8.28-cp310-cp310-win_amd64.whl", hash = "sha256:ac88856a8cbccfc14f1b2d0b829af354cc1743cb375e7f04251ae73b2af6adf8"}, - {file = "regex-2021.8.28-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c206587c83e795d417ed3adc8453a791f6d36b67c81416676cad053b4104152c"}, - {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8690ed94481f219a7a967c118abaf71ccc440f69acd583cab721b90eeedb77c"}, - {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:328a1fad67445550b982caa2a2a850da5989fd6595e858f02d04636e7f8b0b13"}, - {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c7cb4c512d2d3b0870e00fbbac2f291d4b4bf2634d59a31176a87afe2777c6f0"}, - {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66256b6391c057305e5ae9209941ef63c33a476b73772ca967d4a2df70520ec1"}, - {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8e44769068d33e0ea6ccdf4b84d80c5afffe5207aa4d1881a629cf0ef3ec398f"}, - {file = "regex-2021.8.28-cp36-cp36m-win32.whl", hash = "sha256:08d74bfaa4c7731b8dac0a992c63673a2782758f7cfad34cf9c1b9184f911354"}, - {file = "regex-2021.8.28-cp36-cp36m-win_amd64.whl", hash = "sha256:abb48494d88e8a82601af905143e0de838c776c1241d92021e9256d5515b3645"}, - {file = "regex-2021.8.28-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b4c220a1fe0d2c622493b0a1fd48f8f991998fb447d3cd368033a4b86cf1127a"}, - {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a332404baa6665b54e5d283b4262f41f2103c255897084ec8f5487ce7b9e8e"}, - {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c61dcc1cf9fd165127a2853e2c31eb4fb961a4f26b394ac9fe5669c7a6592892"}, - {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ee329d0387b5b41a5dddbb6243a21cb7896587a651bebb957e2d2bb8b63c0791"}, - {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f60667673ff9c249709160529ab39667d1ae9fd38634e006bec95611f632e759"}, - {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b844fb09bd9936ed158ff9df0ab601e2045b316b17aa8b931857365ea8586906"}, - {file = "regex-2021.8.28-cp37-cp37m-win32.whl", hash = "sha256:4cde065ab33bcaab774d84096fae266d9301d1a2f5519d7bd58fc55274afbf7a"}, - {file = "regex-2021.8.28-cp37-cp37m-win_amd64.whl", hash = "sha256:1413b5022ed6ac0d504ba425ef02549a57d0f4276de58e3ab7e82437892704fc"}, - {file = "regex-2021.8.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ed4b50355b066796dacdd1cf538f2ce57275d001838f9b132fab80b75e8c84dd"}, - {file = "regex-2021.8.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28fc475f560d8f67cc8767b94db4c9440210f6958495aeae70fac8faec631797"}, - {file = "regex-2021.8.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdc178caebd0f338d57ae445ef8e9b737ddf8fbc3ea187603f65aec5b041248f"}, - {file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:999ad08220467b6ad4bd3dd34e65329dd5d0df9b31e47106105e407954965256"}, - {file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:808ee5834e06f57978da3e003ad9d6292de69d2bf6263662a1a8ae30788e080b"}, - {file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d5111d4c843d80202e62b4fdbb4920db1dcee4f9366d6b03294f45ed7b18b42e"}, - {file = "regex-2021.8.28-cp38-cp38-win32.whl", hash = "sha256:473858730ef6d6ff7f7d5f19452184cd0caa062a20047f6d6f3e135a4648865d"}, - {file = "regex-2021.8.28-cp38-cp38-win_amd64.whl", hash = "sha256:31a99a4796bf5aefc8351e98507b09e1b09115574f7c9dbb9cf2111f7220d2e2"}, - {file = "regex-2021.8.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:04f6b9749e335bb0d2f68c707f23bb1773c3fb6ecd10edf0f04df12a8920d468"}, - {file = "regex-2021.8.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b006628fe43aa69259ec04ca258d88ed19b64791693df59c422b607b6ece8bb"}, - {file = "regex-2021.8.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:121f4b3185feaade3f85f70294aef3f777199e9b5c0c0245c774ae884b110a2d"}, - {file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a577a21de2ef8059b58f79ff76a4da81c45a75fe0bfb09bc8b7bb4293fa18983"}, - {file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1743345e30917e8c574f273f51679c294effba6ad372db1967852f12c76759d8"}, - {file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e1e8406b895aba6caa63d9fd1b6b1700d7e4825f78ccb1e5260551d168db38ed"}, - {file = "regex-2021.8.28-cp39-cp39-win32.whl", hash = "sha256:ed283ab3a01d8b53de3a05bfdf4473ae24e43caee7dcb5584e86f3f3e5ab4374"}, - {file = "regex-2021.8.28-cp39-cp39-win_amd64.whl", hash = "sha256:610b690b406653c84b7cb6091facb3033500ee81089867ee7d59e675f9ca2b73"}, - {file = "regex-2021.8.28.tar.gz", hash = "sha256:f585cbbeecb35f35609edccb95efd95a3e35824cd7752b586503f7e6087303f1"}, + {file = "regex-2021.9.24-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0628ed7d6334e8f896f882a5c1240de8c4d9b0dd7c7fb8e9f4692f5684b7d656"}, + {file = "regex-2021.9.24-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3baf3eaa41044d4ced2463fd5d23bf7bd4b03d68739c6c99a59ce1f95599a673"}, + {file = "regex-2021.9.24-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c000635fd78400a558bd7a3c2981bb2a430005ebaa909d31e6e300719739a949"}, + {file = "regex-2021.9.24-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:295bc8a13554a25ad31e44c4bedabd3c3e28bba027e4feeb9bb157647a2344a7"}, + {file = "regex-2021.9.24-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0e3f59d3c772f2c3baaef2db425e6fc4149d35a052d874bb95ccfca10a1b9f4"}, + {file = "regex-2021.9.24-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:aea4006b73b555fc5bdb650a8b92cf486d678afa168cf9b38402bb60bf0f9c18"}, + {file = "regex-2021.9.24-cp310-cp310-win32.whl", hash = "sha256:09eb62654030f39f3ba46bc6726bea464069c29d00a9709e28c9ee9623a8da4a"}, + {file = "regex-2021.9.24-cp310-cp310-win_amd64.whl", hash = "sha256:8d80087320632457aefc73f686f66139801959bf5b066b4419b92be85be3543c"}, + {file = "regex-2021.9.24-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:7e3536f305f42ad6d31fc86636c54c7dafce8d634e56fef790fbacb59d499dd5"}, + {file = "regex-2021.9.24-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c31f35a984caffb75f00a86852951a337540b44e4a22171354fb760cefa09346"}, + {file = "regex-2021.9.24-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c7cb25adba814d5f419733fe565f3289d6fa629ab9e0b78f6dff5fa94ab0456"}, + {file = "regex-2021.9.24-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:85c61bee5957e2d7be390392feac7e1d7abd3a49cbaed0c8cee1541b784c8561"}, + {file = "regex-2021.9.24-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c94722bf403b8da744b7d0bb87e1f2529383003ceec92e754f768ef9323f69ad"}, + {file = "regex-2021.9.24-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6adc1bd68f81968c9d249aab8c09cdc2cbe384bf2d2cb7f190f56875000cdc72"}, + {file = "regex-2021.9.24-cp36-cp36m-win32.whl", hash = "sha256:2054dea683f1bda3a804fcfdb0c1c74821acb968093d0be16233873190d459e3"}, + {file = "regex-2021.9.24-cp36-cp36m-win_amd64.whl", hash = "sha256:7783d89bd5413d183a38761fbc68279b984b9afcfbb39fa89d91f63763fbfb90"}, + {file = "regex-2021.9.24-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b15dc34273aefe522df25096d5d087abc626e388a28a28ac75a4404bb7668736"}, + {file = "regex-2021.9.24-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10a7a9cbe30bd90b7d9a1b4749ef20e13a3528e4215a2852be35784b6bd070f0"}, + {file = "regex-2021.9.24-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb9f5844db480e2ef9fce3a72e71122dd010ab7b2920f777966ba25f7eb63819"}, + {file = "regex-2021.9.24-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:17310b181902e0bb42b29c700e2c2346b8d81f26e900b1328f642e225c88bce1"}, + {file = "regex-2021.9.24-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0bba1f6df4eafe79db2ecf38835c2626dbd47911e0516f6962c806f83e7a99ae"}, + {file = "regex-2021.9.24-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:821e10b73e0898544807a0692a276e539e5bafe0a055506a6882814b6a02c3ec"}, + {file = "regex-2021.9.24-cp37-cp37m-win32.whl", hash = "sha256:9c371dd326289d85906c27ec2bc1dcdedd9d0be12b543d16e37bad35754bde48"}, + {file = "regex-2021.9.24-cp37-cp37m-win_amd64.whl", hash = "sha256:1e8d1898d4fb817120a5f684363b30108d7b0b46c7261264b100d14ec90a70e7"}, + {file = "regex-2021.9.24-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8a5c2250c0a74428fd5507ae8853706fdde0f23bfb62ee1ec9418eeacf216078"}, + {file = "regex-2021.9.24-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8aec4b4da165c4a64ea80443c16e49e3b15df0f56c124ac5f2f8708a65a0eddc"}, + {file = "regex-2021.9.24-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:650c4f1fc4273f4e783e1d8e8b51a3e2311c2488ba0fcae6425b1e2c248a189d"}, + {file = "regex-2021.9.24-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2cdb3789736f91d0b3333ac54d12a7e4f9efbc98f53cb905d3496259a893a8b3"}, + {file = "regex-2021.9.24-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e61100200fa6ab7c99b61476f9f9653962ae71b931391d0264acfb4d9527d9c"}, + {file = "regex-2021.9.24-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8c268e78d175798cd71d29114b0a1f1391c7d011995267d3b62319ec1a4ecaa1"}, + {file = "regex-2021.9.24-cp38-cp38-win32.whl", hash = "sha256:658e3477676009083422042c4bac2bdad77b696e932a3de001c42cc046f8eda2"}, + {file = "regex-2021.9.24-cp38-cp38-win_amd64.whl", hash = "sha256:a731552729ee8ae9c546fb1c651c97bf5f759018fdd40d0e9b4d129e1e3a44c8"}, + {file = "regex-2021.9.24-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:86f9931eb92e521809d4b64ec8514f18faa8e11e97d6c2d1afa1bcf6c20a8eab"}, + {file = "regex-2021.9.24-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcbbc9cfa147d55a577d285fd479b43103188855074552708df7acc31a476dd9"}, + {file = "regex-2021.9.24-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29385c4dbb3f8b3a55ce13de6a97a3d21bd00de66acd7cdfc0b49cb2f08c906c"}, + {file = "regex-2021.9.24-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c50a6379763c733562b1fee877372234d271e5c78cd13ade5f25978aa06744db"}, + {file = "regex-2021.9.24-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f74b6d8f59f3cfb8237e25c532b11f794b96f5c89a6f4a25857d85f84fbef11"}, + {file = "regex-2021.9.24-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6c4d83d21d23dd854ffbc8154cf293f4e43ba630aa9bd2539c899343d7f59da3"}, + {file = "regex-2021.9.24-cp39-cp39-win32.whl", hash = "sha256:95e89a8558c8c48626dcffdf9c8abac26b7c251d352688e7ab9baf351e1c7da6"}, + {file = "regex-2021.9.24-cp39-cp39-win_amd64.whl", hash = "sha256:835962f432bce92dc9bf22903d46c50003c8d11b1dc64084c8fae63bca98564a"}, + {file = "regex-2021.9.24.tar.gz", hash = "sha256:6266fde576e12357b25096351aac2b4b880b0066263e7bc7a9a1b4307991bb0e"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] sqlalchemy = [ - {file = "SQLAlchemy-1.4.23-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:25e9b2e5ca088879ce3740d9ccd4d58cb9061d49566a0b5e12166f403d6f4da0"}, - {file = "SQLAlchemy-1.4.23-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d9667260125688c71ccf9af321c37e9fb71c2693575af8210f763bfbbee847c7"}, - {file = "SQLAlchemy-1.4.23-cp27-cp27m-win32.whl", hash = "sha256:cec1a4c6ddf5f82191301a25504f0e675eccd86635f0d5e4c69e0661691931c5"}, - {file = "SQLAlchemy-1.4.23-cp27-cp27m-win_amd64.whl", hash = "sha256:ae07895b55c7d58a7dd47438f437ac219c0f09d24c2e7d69fdebc1ea75350f00"}, - {file = "SQLAlchemy-1.4.23-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:967307ea52985985224a79342527c36ec2d1daa257a39748dd90e001a4be4d90"}, - {file = "SQLAlchemy-1.4.23-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:be185b3daf651c6c0639987a916bf41e97b60e68f860f27c9cb6574385f5cbb4"}, - {file = "SQLAlchemy-1.4.23-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a0d3b3d51c83a66f5b72c57e1aad061406e4c390bd42cf1fda94effe82fac81"}, - {file = "SQLAlchemy-1.4.23-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a8395c4db3e1450eef2b68069abf500cc48af4b442a0d98b5d3c9535fe40cde8"}, - {file = "SQLAlchemy-1.4.23-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b128a78581faea7a5ee626ad4471353eee051e4e94616dfeff4742b6e5ba262"}, - {file = "SQLAlchemy-1.4.23-cp36-cp36m-win32.whl", hash = "sha256:43fc207be06e50158e4dae4cc4f27ce80afbdbfa7c490b3b22feb64f6d9775a0"}, - {file = "SQLAlchemy-1.4.23-cp36-cp36m-win_amd64.whl", hash = "sha256:e9d4f4552aa5e0d1417fc64a2ce1cdf56a30bab346ba6b0dd5e838eb56db4d29"}, - {file = "SQLAlchemy-1.4.23-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:512f52a8872e8d63d898e4e158eda17e2ee40b8d2496b3b409422e71016db0bd"}, - {file = "SQLAlchemy-1.4.23-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:355024cf061ed04271900414eb4a22671520241d2216ddb691bdd8a992172389"}, - {file = "SQLAlchemy-1.4.23-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:82c03325111eab88d64e0ff48b6fe15c75d23787429fa1d84c0995872e702787"}, - {file = "SQLAlchemy-1.4.23-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0aa312f9906ecebe133d7f44168c3cae4c76f27a25192fa7682f3fad505543c9"}, - {file = "SQLAlchemy-1.4.23-cp37-cp37m-win32.whl", hash = "sha256:059c5f41e8630f51741a234e6ba2a034228c11b3b54a15478e61d8b55fa8bd9d"}, - {file = "SQLAlchemy-1.4.23-cp37-cp37m-win_amd64.whl", hash = "sha256:cd68c5f9d13ffc8f4d6802cceee786678c5b1c668c97bc07b9f4a60883f36cd1"}, - {file = "SQLAlchemy-1.4.23-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:6a8dbf3d46e889d864a57ee880c4ad3a928db5aa95e3d359cbe0da2f122e50c4"}, - {file = "SQLAlchemy-1.4.23-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c15191f2430a30082f540ec6f331214746fc974cfdf136d7a1471d1c61d68ff"}, - {file = "SQLAlchemy-1.4.23-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cd0e85dd2067159848c7672acd517f0c38b7b98867a347411ea01b432003f8d9"}, - {file = "SQLAlchemy-1.4.23-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:370f4688ce47f0dc1e677a020a4d46252a31a2818fd67f5c256417faefc938af"}, - {file = "SQLAlchemy-1.4.23-cp38-cp38-win32.whl", hash = "sha256:bd41f8063a9cd11b76d6d7d6af8139ab3c087f5dbbe5a50c02cb8ece7da34d67"}, - {file = "SQLAlchemy-1.4.23-cp38-cp38-win_amd64.whl", hash = "sha256:2bca9a6e30ee425cc321d988a152a5fe1be519648e7541ac45c36cd4f569421f"}, - {file = "SQLAlchemy-1.4.23-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:4803a481d4c14ce6ad53dc35458c57821863e9a079695c27603d38355e61fb7f"}, - {file = "SQLAlchemy-1.4.23-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07b9099a95dd2b2620498544300eda590741ac54915c6b20809b6de7e3c58090"}, - {file = "SQLAlchemy-1.4.23-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:37f2bd1b8e32c5999280f846701712347fc0ee7370e016ede2283c71712e127a"}, - {file = "SQLAlchemy-1.4.23-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:448612570aa1437a5d1b94ada161805778fe80aba5b9a08a403e8ae4e071ded6"}, - {file = "SQLAlchemy-1.4.23-cp39-cp39-win32.whl", hash = "sha256:e0ce4a2e48fe0a9ea3a5160411a4c5135da5255ed9ac9c15f15f2bcf58c34194"}, - {file = "SQLAlchemy-1.4.23-cp39-cp39-win_amd64.whl", hash = "sha256:0aa746d1173587743960ff17b89b540e313aacfe6c1e9c81aa48393182c36d4f"}, - {file = "SQLAlchemy-1.4.23.tar.gz", hash = "sha256:76ff246881f528089bf19385131b966197bb494653990396d2ce138e2a447583"}, + {file = "SQLAlchemy-1.4.25-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:a36ea43919e51b0de0c0bc52bcfdad7683f6ea9fb81b340cdabb9df0e045e0f7"}, + {file = "SQLAlchemy-1.4.25-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:75cd5d48389a7635393ff5a9214b90695c06b3d74912109c3b00ce7392b69c6c"}, + {file = "SQLAlchemy-1.4.25-cp27-cp27m-win32.whl", hash = "sha256:16ef07e102d2d4f974ba9b0d4ac46345a411ad20ad988b3654d59ff08e553b1c"}, + {file = "SQLAlchemy-1.4.25-cp27-cp27m-win_amd64.whl", hash = "sha256:a79abdb404d9256afb8aeaa0d3a4bc7d3b6d8b66103d8b0f2f91febd3909976e"}, + {file = "SQLAlchemy-1.4.25-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7ad59e2e16578b6c1a2873e4888134112365605b08a6067dd91e899e026efa1c"}, + {file = "SQLAlchemy-1.4.25-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:a505ecc0642f52e7c65afb02cc6181377d833b7df0994ecde15943b18d0fa89c"}, + {file = "SQLAlchemy-1.4.25-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a28fe28c359835f3be20c89efd517b35e8f97dbb2ca09c6cf0d9ac07f62d7ef6"}, + {file = "SQLAlchemy-1.4.25-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:41a916d815a3a23cb7fff8d11ad0c9b93369ac074e91e428075e088fe57d5358"}, + {file = "SQLAlchemy-1.4.25-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:842c49dd584aedd75c2ee05f6c950730c3ffcddd21c5824ed0f820808387e1e3"}, + {file = "SQLAlchemy-1.4.25-cp36-cp36m-win32.whl", hash = "sha256:6b602e3351f59f3999e9fb8b87e5b95cb2faab6a6ecdb482382ac6fdfbee5266"}, + {file = "SQLAlchemy-1.4.25-cp36-cp36m-win_amd64.whl", hash = "sha256:6400b22e4e41cc27623a9a75630b7719579cd9a3a2027bcf16ad5aaa9a7806c0"}, + {file = "SQLAlchemy-1.4.25-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:dd4ed12a775f2cde4519f4267d3601990a97d8ecde5c944ab06bfd6e8e8ea177"}, + {file = "SQLAlchemy-1.4.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b7778a205f956755e05721eebf9f11a6ac18b2409bff5db53ce5fe7ede79831"}, + {file = "SQLAlchemy-1.4.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:08d9396a2a38e672133266b31ed39b2b1f2b5ec712b5bff5e08033970563316a"}, + {file = "SQLAlchemy-1.4.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e93978993a2ad0af43f132be3ea8805f56b2f2cd223403ec28d3e7d5c6d39ed1"}, + {file = "SQLAlchemy-1.4.25-cp37-cp37m-win32.whl", hash = "sha256:0566a6e90951590c0307c75f9176597c88ef4be2724958ca1d28e8ae05ec8822"}, + {file = "SQLAlchemy-1.4.25-cp37-cp37m-win_amd64.whl", hash = "sha256:0b08a53e40b34205acfeb5328b832f44437956d673a6c09fce55c66ab0e54916"}, + {file = "SQLAlchemy-1.4.25-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:33a1e86abad782e90976de36150d910748b58e02cd7d35680d441f9a76806c18"}, + {file = "SQLAlchemy-1.4.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ed67aae8cde4d32aacbdba4f7f38183d14443b714498eada5e5a7a37769c0b7"}, + {file = "SQLAlchemy-1.4.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1ebd69365717becaa1b618220a3df97f7c08aa68e759491de516d1c3667bba54"}, + {file = "SQLAlchemy-1.4.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26b0cd2d5c7ea96d3230cb20acac3d89de3b593339c1447b4d64bfcf4eac1110"}, + {file = "SQLAlchemy-1.4.25-cp38-cp38-win32.whl", hash = "sha256:c211e8ec81522ce87b0b39f0cf0712c998d4305a030459a0e115a2b3dc71598f"}, + {file = "SQLAlchemy-1.4.25-cp38-cp38-win_amd64.whl", hash = "sha256:9a1df8c93a0dd9cef0839917f0c6c49f46c75810cf8852be49884da4a7de3c59"}, + {file = "SQLAlchemy-1.4.25-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:1b38db2417b9f7005d6ceba7ce2a526bf10e3f6f635c0f163e6ed6a42b5b62b2"}, + {file = "SQLAlchemy-1.4.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e37621b37c73b034997b5116678862f38ee70e5a054821c7b19d0e55df270dec"}, + {file = "SQLAlchemy-1.4.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:91cd87d1de0111eaca11ccc3d31af441c753fa2bc22df72e5009cfb0a1af5b03"}, + {file = "SQLAlchemy-1.4.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90fe429285b171bcc252e21515703bdc2a4721008d1f13aa5b7150336f8a8493"}, + {file = "SQLAlchemy-1.4.25-cp39-cp39-win32.whl", hash = "sha256:6003771ea597346ab1e97f2f58405c6cacbf6a308af3d28a9201a643c0ac7bb3"}, + {file = "SQLAlchemy-1.4.25-cp39-cp39-win_amd64.whl", hash = "sha256:9ebe49c3960aa2219292ea2e5df6acdc425fc828f2f3d50b4cfae1692bcb5f02"}, + {file = "SQLAlchemy-1.4.25.tar.gz", hash = "sha256:1adf3d25e2e33afbcd48cfad8076f9378793be43e7fec3e4334306cac6bec138"}, ] sqlalchemy2-stubs = [ - {file = "sqlalchemy2-stubs-0.0.2a15.tar.gz", hash = "sha256:9336e0724c985623e055e2db28e8d78a62b40a2628b862c6cd50d7942fd5a238"}, - {file = "sqlalchemy2_stubs-0.0.2a15-py3-none-any.whl", hash = "sha256:3da19252c87c1ae6bf0e49c2873e72a6e5684a1ede2490c8b75b78834239258b"}, + {file = "sqlalchemy2-stubs-0.0.2a17.tar.gz", hash = "sha256:d7e1f63f82711c83d49eb6adccbf6bbf3cc94783436c0d34cf7ae49820023046"}, + {file = "sqlalchemy2_stubs-0.0.2a17-py3-none-any.whl", hash = "sha256:6f112f9381a29575676c3012ce10ed6bceabfad11f1d5569d7072ccf66cfa060"}, ] starlette = [ {file = "starlette-0.14.2-py3-none-any.whl", hash = "sha256:3c8e48e52736b3161e34c9f0e8153b4f32ec5d8995a3ee1d59410d92f75162ed"}, @@ -1087,6 +1068,6 @@ websockets = [ {file = "websockets-10.0.tar.gz", hash = "sha256:c4fc9a1d242317892590abe5b61a9127f1a61740477bfb121743f290b8054002"}, ] zipp = [ - {file = "zipp-3.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"}, - {file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"}, + {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, + {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, ] diff --git a/reddit-clone/reddit/comments/__init__.py b/reddit-clone/reddit/comments/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/reddit-clone/reddit/comments/models.py b/reddit-clone/reddit/comments/models.py new file mode 100644 index 00000000..f44c489a --- /dev/null +++ b/reddit-clone/reddit/comments/models.py @@ -0,0 +1,23 @@ +from typing import Optional + +from sqlalchemy import Column, Integer, Text, ForeignKey +from sqlalchemy.orm import relationship + +from ..database import Base + + +class Comment(Base): + """ + Represents a Comment in a thread. + """ + + content: str = Column(Text, nullable=False) + + votes: int = Column(Integer, default=1) + + user_id: Optional[int] = Column(Integer, ForeignKey("users.id")) + + replies = relationship("Comment", back_populates="parent", lazy="dynamic") + + def __repr__(self) -> str: + return f"" diff --git a/reddit-clone/reddit/database.py b/reddit-clone/reddit/database.py index 82bb1e54..17f08f37 100644 --- a/reddit-clone/reddit/database.py +++ b/reddit-clone/reddit/database.py @@ -5,12 +5,11 @@ from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine from sqlalchemy.ext.declarative import declarative_base - from .settings import DATABASE_URI -async_engine = create_async_engine(DATABASE_URI, future=True) +engine = create_async_engine(DATABASE_URI, future=True) -session_factory = sessionmaker(bind=async_engine, class_=AsyncSession) +session_factory = sessionmaker(bind=engine, class_=AsyncSession) @asynccontextmanager diff --git a/reddit-clone/reddit/models/base.py b/reddit-clone/reddit/models/base.py deleted file mode 100644 index 9d0373d7..00000000 --- a/reddit-clone/reddit/models/base.py +++ /dev/null @@ -1,40 +0,0 @@ -from typing import Optional -from datetime import datetime - -from sqlalchemy import Column - -from reddit.db.base import Base - - -class BaseModel(Base): - """ - The base class which every model - must subclass/ inherit from. - """ - - __abstract__ = True - - id: Optional[int] = Column( - default=None, - primary_key=True, - comment=""" - Identifier for the object. - """, - ) - - created_at: datetime = Column( - nullable=False, - default=datetime.now(), - comment=""" - When the object was created. - """, - ) - - updated_at: datetime = Column( - nullable=False, - default=datetime.now(), - onupdate=datetime.now(), - comment=""" - When the object was updated. - """, - ) diff --git a/reddit-clone/reddit/models/comment.py b/reddit-clone/reddit/models/comment.py deleted file mode 100644 index 51fd2cc7..00000000 --- a/reddit-clone/reddit/models/comment.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Optional - -from sqlalchemy import Column, Integer, Text, ForeignKey -from sqlalchemy.orm import relationship - -from .base import BaseModel - - -class Comment(BaseModel): - """ - Represents a Comment in a thread. - """ - - content: str = Column( - Text, - nullable=False, - comment=""" - The content of the comment. - """, - ) - - votes: int = Column( - Integer, - default=1, - comment=""" - The votes for the thread. - """, - ) - - user_id: Optional[int] = Column( - Integer, - ForeignKey("users.id"), - comment=""" - The owner ID of the comment. - """, - ) - - replies = relationship("Comment", backref="parent", lazy="dynamic") - - def __repr__(self) -> str: - return "" % self.id diff --git a/reddit-clone/reddit/models/post.py b/reddit-clone/reddit/models/post.py deleted file mode 100644 index b330a45f..00000000 --- a/reddit-clone/reddit/models/post.py +++ /dev/null @@ -1,75 +0,0 @@ -from typing import Optional - -from sqlalchemy import Column, Integer, String, ForeignKey -from sqlalchemy.orm import relationship - -from .base import BaseModel - - -class Post(BaseModel): - """ - Represents a post in a Subreddit. - """ - - __tablename__ = "posts" - - title: str = Column( - String(150), - comment=""" - The title for the post. - """, - ) - - text: Optional[str] = Column( - String(1024), - default=None, - comment=""" - The text for the post. - """, - ) - - link: Optional[str] = Column( - String(255), - default=None, - unique=True, - comment=""" - The link for the post. - """, - ) - - thumbnail: Optional[str] = Column( - String(255), - default=None, - comment=""" - The thumbnail URL for the post. - """, - ) - - user_id: int = Column( - Integer, - ForeignKey("users.id"), - comment=""" - The owner ID of the post. - """, - ) - - subreddit_id: int = Column( - Integer, - ForeignKey("subreddits.id"), - comment=""" - The SubReddit ID of the post. - """, - ) - - votes: int = Column( - Integer, - default=1, - comment=""" - The votes for the post. - """, - ) - - comments = relationship("Comment", backref="post", lazy="dynamic") - - def __repr__(self) -> str: - return "" % self.title diff --git a/reddit-clone/reddit/models/subreddit.py b/reddit-clone/reddit/models/subreddit.py deleted file mode 100644 index a54c9013..00000000 --- a/reddit-clone/reddit/models/subreddit.py +++ /dev/null @@ -1,59 +0,0 @@ -from typing import Optional - -from sqlalchemy import Column, String, Integer, SmallInteger, ForeignKey -from sqlalchemy.orm import relationship - -from .base import BaseModel - - -class Subreddit(BaseModel): - """ - Represents a Subreddit. - """ - - __tablename__ = "subreddits" - - name: str = Column( - String(75), - unique=True, - nullable=False, - comment=""" - The name for the subreddit. - """, - ) - - description: Optional[str] = Column( - String(255), - default=None, - comment=""" - The description for the subreddit. - """, - ) - - admin_id: int = Column( - Integer, - ForeignKey("users.id"), - comment=""" - The admin ID of the subreddit. - """, - ) - - status: int = Column( - SmallInteger, - comment=""" - The status of the subreddit. - """, - ) - - icon: Optional[str] = Column( - String(255), - default=None, - comment=""" - The icon URL for the subreddit. - """, - ) - - posts = relationship("Post", backref="subreddit", lazy="dynamic") - - def __repr__(self) -> str: - return "" % self.name diff --git a/reddit-clone/reddit/models/user.py b/reddit-clone/reddit/models/user.py deleted file mode 100644 index 9701021f..00000000 --- a/reddit-clone/reddit/models/user.py +++ /dev/null @@ -1,85 +0,0 @@ -from typing import Optional - -from sqlalchemy import Column, String -from sqlalchemy.orm import relationship - -from .base import BaseModel - - -class User(BaseModel): - """ - Represents an individual user account. - """ - - __tablename__ = "users" - - username: str = Column( - String(32), - nullable=False, - unique=True, - comment=""" - The username for the user. - """, - ) - - email: str = Column( - String(255), - nullable=False, - unique=True, - comment=""" - The email for the user. - """, - ) - - password: str = Column( - String(255), - nullable=False, - comment=""" - The password for the user. - """, - ) - - avatar: Optional[str] = Column( - String(255), - default=None, - comment=""" - The avatar URL for the user. - """, - ) - - posts = relationship("Post", backref="user", lazy="dynamic") - - subreddits = relationship( - "Subreddit", backref="user", secondary="subreddit_users", lazy="dynamic" - ) - - comments = relationship("Comment", backref="user", lazy="dynamic") - - def __repr__(self) -> str: - return "" % self.username - - -class SubredditUser(BaseModel): - """ - Represents a Subreddit-user relationship. - """ - - __tablename__ = "subreddit_users" - - user_id: int = Column( - Integer, - ForeignKey("users.id"), - primary_key=True, - comment=""" - The relationship's user ID. - """, - ) - - subreddit_id: int = Column( - Integer, - ForeignKey("subreddits.id"), - primary_key=True, - comment=""" - The relationship's subreddit ID. - """, - ) diff --git a/reddit-clone/reddit/posts/__init__.py b/reddit-clone/reddit/posts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/reddit-clone/reddit/posts/models.py b/reddit-clone/reddit/posts/models.py new file mode 100644 index 00000000..70e36f57 --- /dev/null +++ b/reddit-clone/reddit/posts/models.py @@ -0,0 +1,35 @@ +from typing import Optional + +from sqlalchemy import Column, Integer, String, ForeignKey +from sqlalchemy.orm import relationship + +from ..database import Base + + +class Post(Base): + """ + Represents a post in a Subreddit. + """ + + __tablename__ = "posts" + + id: Optional[int] = Column(default=None, primary_key=True) + + title: str = Column(String(150)) + + text: Optional[str] = Column(String(1024), default=None) + + link: Optional[str] = Column(String(255), default=None, unique=True) + + thumbnail: Optional[str] = Column(String(255), default=None) + + user_id: int = Column(Integer, ForeignKey("users.id")) + + subreddit_id: int = Column(Integer, ForeignKey("subreddits.id")) + + votes: int = Column(Integer, default=1) + + comments = relationship("Comment", back_populates="post", lazy="dynamic") + + def __repr__(self) -> str: + return f"" diff --git a/reddit-clone/reddit/subreddits/__init__.py b/reddit-clone/reddit/subreddits/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/reddit-clone/reddit/subreddits/models.py b/reddit-clone/reddit/subreddits/models.py new file mode 100644 index 00000000..c1e5e4f1 --- /dev/null +++ b/reddit-clone/reddit/subreddits/models.py @@ -0,0 +1,31 @@ +from typing import Optional + +from sqlalchemy import Column, String, Integer, SmallInteger, ForeignKey +from sqlalchemy.orm import relationship + +from ..database import Base + + +class Subreddit(Base): + """ + Represents a Subreddit. + """ + + __tablename__ = "subreddits" + + id: Optional[int] = Column(default=None, primary_key=True) + + name: str = Column(String(75), unique=True, nullable=False) + + description: Optional[str] = Column(String(255), default=None) + + admin_id: int = Column(Integer, ForeignKey("users.id")) + + status: int = Column(SmallInteger) + + icon: Optional[str] = Column(String(255), default=None) + + posts = relationship("Post", back_populates="subreddit", lazy="dynamic") + + def __repr__(self) -> str: + return f"" diff --git a/reddit-clone/reddit/users/__init__.py b/reddit-clone/reddit/users/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/reddit-clone/reddit/users/models.py b/reddit-clone/reddit/users/models.py new file mode 100644 index 00000000..f34cf72e --- /dev/null +++ b/reddit-clone/reddit/users/models.py @@ -0,0 +1,50 @@ +from typing import Optional + +from sqlalchemy import Column, String, Integer, ForeignKey +from sqlalchemy.orm import relationship + +from ..database import Base + + +class User(Base): + """ + Represents an individual user account. + """ + + __tablename__ = "users" + + id: Optional[int] = Column(default=None, primary_key=True) + + username: str = Column(String(32), nullable=False, unique=True) + + email: str = Column(String(255), nullable=False, unique=True) + + password: str = Column(String(255), nullable=False) + + avatar: Optional[str] = Column(String(255), default=None) + + posts = relationship("Post", back_populates="user", lazy="dynamic") + + subreddits = relationship( + "Subreddit", back_populates="user", secondary="subreddit_users", lazy="dynamic" + ) + + comments = relationship("Comment", back_populates="user", lazy="dynamic") + + def __repr__(self) -> str: + return f"" + + +class SubredditUser(Base): + """ + Represents a Subreddit-user relationship. + """ + + __tablename__ = "subreddit_users" + + user_id: int = Column(Integer, ForeignKey("users.id"), primary_key=True) + + subreddit_id: int = Column(Integer, ForeignKey("subreddits.id"), primary_key=True) + + def __repr__(self) -> str: + return f"" From cef2a1c527830d31e8753a3d094cd6c0853ae319 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Thu, 30 Sep 2021 08:46:14 +0530 Subject: [PATCH 031/150] type hint relationships --- reddit-clone/reddit/comments/models.py | 8 ++++++-- reddit-clone/reddit/posts/models.py | 7 +++++-- reddit-clone/reddit/subreddits/models.py | 5 +++-- reddit-clone/reddit/users/models.py | 13 +++++++++---- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/reddit-clone/reddit/comments/models.py b/reddit-clone/reddit/comments/models.py index f44c489a..50fa1a48 100644 --- a/reddit-clone/reddit/comments/models.py +++ b/reddit-clone/reddit/comments/models.py @@ -1,4 +1,6 @@ -from typing import Optional +from __future__ import annotations + +from typing import Optional, List from sqlalchemy import Column, Integer, Text, ForeignKey from sqlalchemy.orm import relationship @@ -17,7 +19,9 @@ class Comment(Base): user_id: Optional[int] = Column(Integer, ForeignKey("users.id")) - replies = relationship("Comment", back_populates="parent", lazy="dynamic") + replies: List[Comment] = relationship( + "Comment", back_populates="parent", lazy="dynamic" + ) def __repr__(self) -> str: return f"" diff --git a/reddit-clone/reddit/posts/models.py b/reddit-clone/reddit/posts/models.py index 70e36f57..10cdd6dd 100644 --- a/reddit-clone/reddit/posts/models.py +++ b/reddit-clone/reddit/posts/models.py @@ -1,9 +1,10 @@ -from typing import Optional +from typing import Optional, List from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy.orm import relationship from ..database import Base +from ..comments.models import Comment class Post(Base): @@ -29,7 +30,9 @@ class Post(Base): votes: int = Column(Integer, default=1) - comments = relationship("Comment", back_populates="post", lazy="dynamic") + comments: List[Comment] = relationship( + "Comment", back_populates="post", lazy="dynamic" + ) def __repr__(self) -> str: return f"" diff --git a/reddit-clone/reddit/subreddits/models.py b/reddit-clone/reddit/subreddits/models.py index c1e5e4f1..d23f65c6 100644 --- a/reddit-clone/reddit/subreddits/models.py +++ b/reddit-clone/reddit/subreddits/models.py @@ -1,9 +1,10 @@ -from typing import Optional +from typing import Optional, List from sqlalchemy import Column, String, Integer, SmallInteger, ForeignKey from sqlalchemy.orm import relationship from ..database import Base +from ..posts.models import Post class Subreddit(Base): @@ -25,7 +26,7 @@ class Subreddit(Base): icon: Optional[str] = Column(String(255), default=None) - posts = relationship("Post", back_populates="subreddit", lazy="dynamic") + posts: List[Post] = relationship("Post", back_populates="subreddit", lazy="dynamic") def __repr__(self) -> str: return f"" diff --git a/reddit-clone/reddit/users/models.py b/reddit-clone/reddit/users/models.py index f34cf72e..22ceb7a5 100644 --- a/reddit-clone/reddit/users/models.py +++ b/reddit-clone/reddit/users/models.py @@ -1,9 +1,12 @@ -from typing import Optional +from typing import Optional, List from sqlalchemy import Column, String, Integer, ForeignKey from sqlalchemy.orm import relationship from ..database import Base +from ..comments.models import Comment +from ..subreddits.models import Subreddit +from ..posts.models import Post class User(Base): @@ -23,13 +26,15 @@ class User(Base): avatar: Optional[str] = Column(String(255), default=None) - posts = relationship("Post", back_populates="user", lazy="dynamic") + posts: List[Post] = relationship("Post", back_populates="user", lazy="dynamic") - subreddits = relationship( + subreddits: List[Subreddit] = relationship( "Subreddit", back_populates="user", secondary="subreddit_users", lazy="dynamic" ) - comments = relationship("Comment", back_populates="user", lazy="dynamic") + comments: List[Comment] = relationship( + "Comment", back_populates="user", lazy="dynamic" + ) def __repr__(self) -> str: return f"" From 5dc7f903b28d4659bf0425ba7a04bdd1278d6376 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Thu, 30 Sep 2021 08:55:54 +0530 Subject: [PATCH 032/150] add dummy schema (and update instructions) --- reddit-clone/README.md | 8 ++++++-- reddit-clone/reddit/__init__.py | 4 ++-- reddit-clone/reddit/schema.py | 11 +++++++++-- reddit-clone/reddit/settings.py | 4 ---- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/reddit-clone/README.md b/reddit-clone/README.md index 379a3549..05ce54e0 100644 --- a/reddit-clone/README.md +++ b/reddit-clone/README.md @@ -29,7 +29,11 @@ Use [poetry](https://python-poetry.org/) to install dependencies: poetry install ``` -2. Run migrations +2. setup database + This example uses a PostgreSQL database. Make sure you have it installed + on your machine, and configure the `DATABASE_URI` environment variable. + +3. Run migrations Run [alembic](https://alembic.sqlalchemy.org/en/latest/) to create the database and populate it with movie data: @@ -38,7 +42,7 @@ and populate it with movie data: poetry run alembic upgrade head ``` -3. Run the server +4. Run the server Run [uvicorn](https://www.uvicorn.org/) to run the server: diff --git a/reddit-clone/reddit/__init__.py b/reddit-clone/reddit/__init__.py index 59fce6d2..e6684e7a 100644 --- a/reddit-clone/reddit/__init__.py +++ b/reddit-clone/reddit/__init__.py @@ -1,8 +1,8 @@ from fastapi import FastAPI from strawberry.asgi import GraphQL -from reddit.core.config import DEBUG -from reddit.schema import schema +from .settings import DEBUG +from .schema import schema def create_application() -> FastAPI: diff --git a/reddit-clone/reddit/schema.py b/reddit-clone/reddit/schema.py index 41baebbf..77439e13 100644 --- a/reddit-clone/reddit/schema.py +++ b/reddit-clone/reddit/schema.py @@ -1,4 +1,11 @@ -from strawberry import Schema +import strawberry -schema = Schema(query=None, mutation=None) +@strawberry.type +class Query: + @strawberry.field() + def hello_world(self) -> str: + return "Hello world!" + + +schema = strawberry.Schema(query=Query, mutation=None) diff --git a/reddit-clone/reddit/settings.py b/reddit-clone/reddit/settings.py index 1a8a6b83..86fbcac9 100644 --- a/reddit-clone/reddit/settings.py +++ b/reddit-clone/reddit/settings.py @@ -1,5 +1,4 @@ from starlette.config import Config -from starlette.datastructures import Secret config = Config(env_file=".env") @@ -7,8 +6,5 @@ # whether the application is in development mode. DEBUG: bool = config("DEBUG", cast=bool, default=False) -# secret key to use for sessions. -SECRET_KEY: Secret = config("SECRET_KEY", cast=Secret) - # sqlalchemy database url. DATABASE_URI: str = config("DATABASE_URI", cast=str) From 797f7c38feafb0ffaa87a90607d61fe5dc275675 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Thu, 30 Sep 2021 10:22:03 +0530 Subject: [PATCH 033/150] make migrations --- .idea/misc.xml | 3 + reddit-clone/README.md | 6 + reddit-clone/alembic.ini | 32 +++++- reddit-clone/alembic/README | 1 - reddit-clone/migrations/README | 1 + reddit-clone/{alembic => migrations}/env.py | 43 +++++--- .../{alembic => migrations}/script.py.mako | 0 .../versions/6128e6679253_initial.py | 104 ++++++++++++++++++ reddit-clone/poetry.lock | 33 +++++- reddit-clone/pyproject.toml | 1 + reddit-clone/reddit/comments/models.py | 4 + reddit-clone/reddit/database.py | 4 +- reddit-clone/reddit/posts/models.py | 2 +- reddit-clone/reddit/subreddits/models.py | 2 +- reddit-clone/reddit/users/models.py | 2 +- 15 files changed, 215 insertions(+), 23 deletions(-) delete mode 100644 reddit-clone/alembic/README create mode 100644 reddit-clone/migrations/README rename reddit-clone/{alembic => migrations}/env.py (61%) rename reddit-clone/{alembic => migrations}/script.py.mako (100%) create mode 100644 reddit-clone/migrations/versions/6128e6679253_initial.py diff --git a/.idea/misc.xml b/.idea/misc.xml index 2a9526cd..5f549113 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,7 @@ + + diff --git a/reddit-clone/README.md b/reddit-clone/README.md index 05ce54e0..4f4f15ee 100644 --- a/reddit-clone/README.md +++ b/reddit-clone/README.md @@ -33,6 +33,12 @@ poetry install This example uses a PostgreSQL database. Make sure you have it installed on your machine, and configure the `DATABASE_URI` environment variable. + An example database URL: + + ```text + postgresql+asyncpg://localhost/db_name?user=user&password=password + ``` + 3. Run migrations Run [alembic](https://alembic.sqlalchemy.org/en/latest/) to create the database diff --git a/reddit-clone/alembic.ini b/reddit-clone/alembic.ini index eb6b4537..2d4431e1 100644 --- a/reddit-clone/alembic.ini +++ b/reddit-clone/alembic.ini @@ -2,7 +2,7 @@ [alembic] # path to migration scripts -script_location = alembic +script_location = migrations # template used to generate migration files # file_template = %%(rev)s_%%(slug)s @@ -33,10 +33,36 @@ prepend_sys_path = . # sourceless = false # version location specification; This defaults -# to alembic/versions. When using multiple version +# to migrations/versions. When using multiple version # directories, initial revisions must be specified with --version-path. # The path separator used here should be the separator specified by "version_path_separator" -# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions +# version_locations = %(here)s/bar:%(here)s/bat:migrations/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. Valid values are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +version_path_separator = os # default: use os.pathsep + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +sqlalchemy.url = driver://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME # Logging configuration [loggers] diff --git a/reddit-clone/alembic/README b/reddit-clone/alembic/README deleted file mode 100644 index 2500aa1b..00000000 --- a/reddit-clone/alembic/README +++ /dev/null @@ -1 +0,0 @@ -Generic single-database configuration. diff --git a/reddit-clone/migrations/README b/reddit-clone/migrations/README new file mode 100644 index 00000000..a23d4fb5 --- /dev/null +++ b/reddit-clone/migrations/README @@ -0,0 +1 @@ +Generic single-database configuration with an async dbapi. diff --git a/reddit-clone/alembic/env.py b/reddit-clone/migrations/env.py similarity index 61% rename from reddit-clone/alembic/env.py rename to reddit-clone/migrations/env.py index bea3fde4..b76cae02 100644 --- a/reddit-clone/alembic/env.py +++ b/reddit-clone/migrations/env.py @@ -1,20 +1,30 @@ +import asyncio from logging.config import fileConfig from sqlalchemy import engine_from_config from sqlalchemy import pool +from sqlalchemy.ext.asyncio import AsyncEngine from alembic import context +from reddit.database import Base +from reddit.settings import DATABASE_URI + + # this is the Alembic Config object, which provides # access to the values within the .ini file in use. config = context.config +config.set_main_option("sqlalchemy.url", DATABASE_URI) + # Interpret the config file for Python logging. # This line sets up loggers basically. fileConfig(config.config_file_name) -from reddit.database import Base # noqa - +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata target_metadata = Base.metadata # other values from the config, defined by the needs of env.py, @@ -47,27 +57,34 @@ def run_migrations_offline(): context.run_migrations() -def run_migrations_online(): +def do_run_migrations(connection): + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + + +async def run_migrations_online(): """Run migrations in 'online' mode. In this scenario we need to create an Engine and associate a connection with the context. """ - connectable = engine_from_config( - config.get_section(config.config_ini_section), - prefix="sqlalchemy.", - poolclass=pool.NullPool, + connectable = AsyncEngine( + engine_from_config( + config.get_section(config.config_ini_section), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + future=True, + ) ) - with connectable.connect() as connection: - context.configure(connection=connection, target_metadata=target_metadata) - - with context.begin_transaction(): - context.run_migrations() + async with connectable.connect() as connection: + await connection.run_sync(do_run_migrations) if context.is_offline_mode(): run_migrations_offline() else: - run_migrations_online() + asyncio.run(run_migrations_online()) diff --git a/reddit-clone/alembic/script.py.mako b/reddit-clone/migrations/script.py.mako similarity index 100% rename from reddit-clone/alembic/script.py.mako rename to reddit-clone/migrations/script.py.mako diff --git a/reddit-clone/migrations/versions/6128e6679253_initial.py b/reddit-clone/migrations/versions/6128e6679253_initial.py new file mode 100644 index 00000000..a31c917f --- /dev/null +++ b/reddit-clone/migrations/versions/6128e6679253_initial.py @@ -0,0 +1,104 @@ +"""initial + +Revision ID: 6128e6679253 +Revises: +Create Date: 2021-09-30 10:21:28.876680 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "6128e6679253" +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "users", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("username", sa.String(length=32), nullable=False), + sa.Column("email", sa.String(length=255), nullable=False), + sa.Column("password", sa.String(length=255), nullable=False), + sa.Column("avatar", sa.String(length=255), nullable=True), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("email"), + sa.UniqueConstraint("username"), + ) + op.create_table( + "comments", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("content", sa.Text(), nullable=False), + sa.Column("votes", sa.Integer(), nullable=True), + sa.Column("user_id", sa.Integer(), nullable=True), + sa.ForeignKeyConstraint( + ["user_id"], + ["users.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "subreddits", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("name", sa.String(length=75), nullable=False), + sa.Column("description", sa.String(length=255), nullable=True), + sa.Column("admin_id", sa.Integer(), nullable=True), + sa.Column("status", sa.SmallInteger(), nullable=True), + sa.Column("icon", sa.String(length=255), nullable=True), + sa.ForeignKeyConstraint( + ["admin_id"], + ["users.id"], + ), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("name"), + ) + op.create_table( + "posts", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("title", sa.String(length=150), nullable=True), + sa.Column("text", sa.String(length=1024), nullable=True), + sa.Column("link", sa.String(length=255), nullable=True), + sa.Column("thumbnail", sa.String(length=255), nullable=True), + sa.Column("user_id", sa.Integer(), nullable=True), + sa.Column("subreddit_id", sa.Integer(), nullable=True), + sa.Column("votes", sa.Integer(), nullable=True), + sa.ForeignKeyConstraint( + ["subreddit_id"], + ["subreddits.id"], + ), + sa.ForeignKeyConstraint( + ["user_id"], + ["users.id"], + ), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("link"), + ) + op.create_table( + "subreddit_users", + sa.Column("user_id", sa.Integer(), nullable=False), + sa.Column("subreddit_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["subreddit_id"], + ["subreddits.id"], + ), + sa.ForeignKeyConstraint( + ["user_id"], + ["users.id"], + ), + sa.PrimaryKeyConstraint("user_id", "subreddit_id"), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("subreddit_users") + op.drop_table("posts") + op.drop_table("subreddits") + op.drop_table("comments") + op.drop_table("users") + # ### end Alembic commands ### diff --git a/reddit-clone/poetry.lock b/reddit-clone/poetry.lock index 79b5fd46..37b7640c 100644 --- a/reddit-clone/poetry.lock +++ b/reddit-clone/poetry.lock @@ -29,6 +29,22 @@ typing-extensions = {version = "*", markers = "python_version < \"3.8\""} [package.extras] tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] +[[package]] +name = "asyncpg" +version = "0.24.0" +description = "An asyncio PostgreSQL driver" +category = "main" +optional = false +python-versions = ">=3.6.0" + +[package.dependencies] +typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\""} + +[package.extras] +dev = ["Cython (>=0.29.24,<0.30.0)", "pytest (>=6.0)", "Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "pycodestyle (>=2.7.0,<2.8.0)", "flake8 (>=3.9.2,<3.10.0)", "uvloop (>=0.15.3)"] +docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)"] +test = ["pycodestyle (>=2.7.0,<2.8.0)", "flake8 (>=3.9.2,<3.10.0)", "uvloop (>=0.15.3)"] + [[package]] name = "attrs" version = "21.2.0" @@ -587,7 +603,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "98fa69364a20e1543505c385c05fd9be26fdc5abbb60fc0e52136a65a1036d88" +content-hash = "0be25f7a7b2a6d2767a4d40638945fea8d0148af2efd0ab4c26d50b2eaced91b" [metadata.files] alembic = [ @@ -598,6 +614,21 @@ asgiref = [ {file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"}, {file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"}, ] +asyncpg = [ + {file = "asyncpg-0.24.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c4fc0205fe4ddd5aeb3dfdc0f7bafd43411181e1f5650189608e5971cceacff1"}, + {file = "asyncpg-0.24.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a7095890c96ba36f9f668eb552bb020dddb44f8e73e932f8573efc613ee83843"}, + {file = "asyncpg-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:8ff5073d4b654e34bd5eaadc01dc4d68b8a9609084d835acd364cd934190a08d"}, + {file = "asyncpg-0.24.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e36c6806883786b19551bb70a4882561f31135dc8105a59662e0376cf5b2cbc5"}, + {file = "asyncpg-0.24.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ddffcb85227bf39cd1bedd4603e0082b243cf3b14ced64dce506a15b05232b83"}, + {file = "asyncpg-0.24.0-cp37-cp37m-win_amd64.whl", hash = "sha256:41704c561d354bef01353835a7846e5606faabbeb846214dfcf666cf53319f18"}, + {file = "asyncpg-0.24.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:29ef6ae0a617fc13cc2ac5dc8e9b367bb83cba220614b437af9b67766f4b6b20"}, + {file = "asyncpg-0.24.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:eed43abc6ccf1dc02e0d0efc06ce46a411362f3358847c6b0ec9a43426f91ece"}, + {file = "asyncpg-0.24.0-cp38-cp38-win_amd64.whl", hash = "sha256:129d501f3d30616afd51eb8d3142ef51ba05374256bd5834cec3ef4956a9b317"}, + {file = "asyncpg-0.24.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a458fc69051fbb67d995fdda46d75a012b5d6200f91e17d23d4751482640ed4c"}, + {file = "asyncpg-0.24.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:556b0e92e2b75dc028b3c4bc9bd5162ddf0053b856437cf1f04c97f9c6837d03"}, + {file = "asyncpg-0.24.0-cp39-cp39-win_amd64.whl", hash = "sha256:a738f4807c853623d3f93f0fea11f61be6b0e5ca16ea8aeb42c2c7ee742aa853"}, + {file = "asyncpg-0.24.0.tar.gz", hash = "sha256:dd2fa063c3344823487d9ddccb40802f02622ddf8bf8a6cc53885ee7a2c1c0c6"}, +] attrs = [ {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, diff --git a/reddit-clone/pyproject.toml b/reddit-clone/pyproject.toml index 0715528a..7d362310 100644 --- a/reddit-clone/pyproject.toml +++ b/reddit-clone/pyproject.toml @@ -9,6 +9,7 @@ python = "^3.7" uvicorn = {extras = ["standard"], version = "^0.15.0"} SQLAlchemy = {extras = ["mypy"], version = "^1.4.23"} alembic = "^1.7.1" +asyncpg = "^0.24" strawberry-graphql = {extras = ["asgi"], version = "^0.77.0"} fastapi = "^0.68.1" diff --git a/reddit-clone/reddit/comments/models.py b/reddit-clone/reddit/comments/models.py index 50fa1a48..62f3bce7 100644 --- a/reddit-clone/reddit/comments/models.py +++ b/reddit-clone/reddit/comments/models.py @@ -13,6 +13,10 @@ class Comment(Base): Represents a Comment in a thread. """ + __tablename__ = "comments" + + id: int = Column(Integer, primary_key=True, nullable=False) + content: str = Column(Text, nullable=False) votes: int = Column(Integer, default=1) diff --git a/reddit-clone/reddit/database.py b/reddit-clone/reddit/database.py index 17f08f37..aa2cc340 100644 --- a/reddit-clone/reddit/database.py +++ b/reddit-clone/reddit/database.py @@ -1,5 +1,5 @@ from contextlib import asynccontextmanager -from typing import Generator +from typing import AsyncIterator from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine @@ -13,7 +13,7 @@ @asynccontextmanager -async def get_session() -> Generator[AsyncSession]: +async def get_session() -> AsyncIterator[AsyncSession]: """ Gets a session instance. diff --git a/reddit-clone/reddit/posts/models.py b/reddit-clone/reddit/posts/models.py index 10cdd6dd..0d314006 100644 --- a/reddit-clone/reddit/posts/models.py +++ b/reddit-clone/reddit/posts/models.py @@ -14,7 +14,7 @@ class Post(Base): __tablename__ = "posts" - id: Optional[int] = Column(default=None, primary_key=True) + id: int = Column(Integer, primary_key=True, nullable=False) title: str = Column(String(150)) diff --git a/reddit-clone/reddit/subreddits/models.py b/reddit-clone/reddit/subreddits/models.py index d23f65c6..cba9449a 100644 --- a/reddit-clone/reddit/subreddits/models.py +++ b/reddit-clone/reddit/subreddits/models.py @@ -14,7 +14,7 @@ class Subreddit(Base): __tablename__ = "subreddits" - id: Optional[int] = Column(default=None, primary_key=True) + id: int = Column(Integer, primary_key=True, nullable=False) name: str = Column(String(75), unique=True, nullable=False) diff --git a/reddit-clone/reddit/users/models.py b/reddit-clone/reddit/users/models.py index 22ceb7a5..5eec2459 100644 --- a/reddit-clone/reddit/users/models.py +++ b/reddit-clone/reddit/users/models.py @@ -16,7 +16,7 @@ class User(Base): __tablename__ = "users" - id: Optional[int] = Column(default=None, primary_key=True) + id: int = Column(Integer, primary_key=True, nullable=False) username: str = Column(String(32), nullable=False, unique=True) From 784ec420259b21aaafb168f217feb69fb7ce21e7 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Thu, 30 Sep 2021 10:24:42 +0530 Subject: [PATCH 034/150] remove .idea --- .gitignore | 3 ++ .idea/.gitignore | 3 -- .idea/inspectionProfiles/Project_Default.xml | 46 ------------------- .../inspectionProfiles/profiles_settings.xml | 6 --- .idea/misc.xml | 7 --- .idea/modules.xml | 8 ---- .idea/strawberry-examples.iml | 12 ----- .idea/vcs.xml | 6 --- 8 files changed, 3 insertions(+), 88 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/strawberry-examples.iml delete mode 100644 .idea/vcs.xml diff --git a/.gitignore b/.gitignore index b6e47617..50af712e 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,6 @@ dmypy.json # Pyre type checker .pyre/ + +# PyCharm +.idea diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 26d33521..00000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 3c6e0d59..00000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index cc5462da..00000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 5f549113..00000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 1e10262b..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/.idea/strawberry-examples.iml b/.idea/strawberry-examples.iml deleted file mode 100644 index 292aeb08..00000000 --- a/.idea/strawberry-examples.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 5ace414d..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - From 923e5ee6a23a6a15a2ec540882f68aebfda03698 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Thu, 30 Sep 2021 10:28:11 +0530 Subject: [PATCH 035/150] remove broken migrations --- .../versions/6128e6679253_initial.py | 104 ------------------ 1 file changed, 104 deletions(-) delete mode 100644 reddit-clone/migrations/versions/6128e6679253_initial.py diff --git a/reddit-clone/migrations/versions/6128e6679253_initial.py b/reddit-clone/migrations/versions/6128e6679253_initial.py deleted file mode 100644 index a31c917f..00000000 --- a/reddit-clone/migrations/versions/6128e6679253_initial.py +++ /dev/null @@ -1,104 +0,0 @@ -"""initial - -Revision ID: 6128e6679253 -Revises: -Create Date: 2021-09-30 10:21:28.876680 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = "6128e6679253" -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "users", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("username", sa.String(length=32), nullable=False), - sa.Column("email", sa.String(length=255), nullable=False), - sa.Column("password", sa.String(length=255), nullable=False), - sa.Column("avatar", sa.String(length=255), nullable=True), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("email"), - sa.UniqueConstraint("username"), - ) - op.create_table( - "comments", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("content", sa.Text(), nullable=False), - sa.Column("votes", sa.Integer(), nullable=True), - sa.Column("user_id", sa.Integer(), nullable=True), - sa.ForeignKeyConstraint( - ["user_id"], - ["users.id"], - ), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "subreddits", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("name", sa.String(length=75), nullable=False), - sa.Column("description", sa.String(length=255), nullable=True), - sa.Column("admin_id", sa.Integer(), nullable=True), - sa.Column("status", sa.SmallInteger(), nullable=True), - sa.Column("icon", sa.String(length=255), nullable=True), - sa.ForeignKeyConstraint( - ["admin_id"], - ["users.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("name"), - ) - op.create_table( - "posts", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("title", sa.String(length=150), nullable=True), - sa.Column("text", sa.String(length=1024), nullable=True), - sa.Column("link", sa.String(length=255), nullable=True), - sa.Column("thumbnail", sa.String(length=255), nullable=True), - sa.Column("user_id", sa.Integer(), nullable=True), - sa.Column("subreddit_id", sa.Integer(), nullable=True), - sa.Column("votes", sa.Integer(), nullable=True), - sa.ForeignKeyConstraint( - ["subreddit_id"], - ["subreddits.id"], - ), - sa.ForeignKeyConstraint( - ["user_id"], - ["users.id"], - ), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("link"), - ) - op.create_table( - "subreddit_users", - sa.Column("user_id", sa.Integer(), nullable=False), - sa.Column("subreddit_id", sa.Integer(), nullable=False), - sa.ForeignKeyConstraint( - ["subreddit_id"], - ["subreddits.id"], - ), - sa.ForeignKeyConstraint( - ["user_id"], - ["users.id"], - ), - sa.PrimaryKeyConstraint("user_id", "subreddit_id"), - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table("subreddit_users") - op.drop_table("posts") - op.drop_table("subreddits") - op.drop_table("comments") - op.drop_table("users") - # ### end Alembic commands ### From c475d9423c7c7a4d8431ee2f166b5587dfdf175b Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Thu, 30 Sep 2021 11:02:21 +0530 Subject: [PATCH 036/150] populate metadata --- reddit-clone/migrations/env.py | 6 + .../versions/6fd9b8fa9cd7_initial.py | 104 ++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 reddit-clone/migrations/versions/6fd9b8fa9cd7_initial.py diff --git a/reddit-clone/migrations/env.py b/reddit-clone/migrations/env.py index b76cae02..8b43a8a4 100644 --- a/reddit-clone/migrations/env.py +++ b/reddit-clone/migrations/env.py @@ -21,6 +21,12 @@ # This line sets up loggers basically. fileConfig(config.config_file_name) +# populate Base.metadata +from reddit.users.models import User # noqa +from reddit.subreddits.models import Subreddit # noqa +from reddit.posts.models import Post # noqa +from reddit.comments.models import Comment # noqa + # add your model's MetaData object here # for 'autogenerate' support # from myapp import mymodel diff --git a/reddit-clone/migrations/versions/6fd9b8fa9cd7_initial.py b/reddit-clone/migrations/versions/6fd9b8fa9cd7_initial.py new file mode 100644 index 00000000..6a2baa12 --- /dev/null +++ b/reddit-clone/migrations/versions/6fd9b8fa9cd7_initial.py @@ -0,0 +1,104 @@ +"""initial + +Revision ID: 6fd9b8fa9cd7 +Revises: +Create Date: 2021-09-30 11:01:49.083637 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "6fd9b8fa9cd7" +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "users", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("username", sa.String(length=32), nullable=False), + sa.Column("email", sa.String(length=255), nullable=False), + sa.Column("password", sa.String(length=255), nullable=False), + sa.Column("avatar", sa.String(length=255), nullable=True), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("email"), + sa.UniqueConstraint("username"), + ) + op.create_table( + "comments", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("content", sa.Text(), nullable=False), + sa.Column("votes", sa.Integer(), nullable=True), + sa.Column("user_id", sa.Integer(), nullable=True), + sa.ForeignKeyConstraint( + ["user_id"], + ["users.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "subreddits", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("name", sa.String(length=75), nullable=False), + sa.Column("description", sa.String(length=255), nullable=True), + sa.Column("admin_id", sa.Integer(), nullable=True), + sa.Column("status", sa.SmallInteger(), nullable=True), + sa.Column("icon", sa.String(length=255), nullable=True), + sa.ForeignKeyConstraint( + ["admin_id"], + ["users.id"], + ), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("name"), + ) + op.create_table( + "posts", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("title", sa.String(length=150), nullable=True), + sa.Column("text", sa.String(length=1024), nullable=True), + sa.Column("link", sa.String(length=255), nullable=True), + sa.Column("thumbnail", sa.String(length=255), nullable=True), + sa.Column("user_id", sa.Integer(), nullable=True), + sa.Column("subreddit_id", sa.Integer(), nullable=True), + sa.Column("votes", sa.Integer(), nullable=True), + sa.ForeignKeyConstraint( + ["subreddit_id"], + ["subreddits.id"], + ), + sa.ForeignKeyConstraint( + ["user_id"], + ["users.id"], + ), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("link"), + ) + op.create_table( + "subreddit_users", + sa.Column("user_id", sa.Integer(), nullable=False), + sa.Column("subreddit_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["subreddit_id"], + ["subreddits.id"], + ), + sa.ForeignKeyConstraint( + ["user_id"], + ["users.id"], + ), + sa.PrimaryKeyConstraint("user_id", "subreddit_id"), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("subreddit_users") + op.drop_table("posts") + op.drop_table("subreddits") + op.drop_table("comments") + op.drop_table("users") + # ### end Alembic commands ### From a0934b845541ebf339e6f3e9b6e0eaed0033723a Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Tue, 5 Oct 2021 14:51:45 +0530 Subject: [PATCH 037/150] add schema types --- reddit-clone/reddit/base/__init__.py | 0 reddit-clone/reddit/base/types.py | 10 ++++ reddit-clone/reddit/comments/types.py | 45 +++++++++++++++ reddit-clone/reddit/posts/types.py | 74 +++++++++++++++++++++++++ reddit-clone/reddit/subreddits/types.py | 60 ++++++++++++++++++++ reddit-clone/reddit/users/types.py | 55 ++++++++++++++++++ 6 files changed, 244 insertions(+) create mode 100644 reddit-clone/reddit/base/__init__.py create mode 100644 reddit-clone/reddit/base/types.py create mode 100644 reddit-clone/reddit/comments/types.py create mode 100644 reddit-clone/reddit/posts/types.py create mode 100644 reddit-clone/reddit/subreddits/types.py create mode 100644 reddit-clone/reddit/users/types.py diff --git a/reddit-clone/reddit/base/__init__.py b/reddit-clone/reddit/base/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/reddit-clone/reddit/base/types.py b/reddit-clone/reddit/base/types.py new file mode 100644 index 00000000..9057bbf1 --- /dev/null +++ b/reddit-clone/reddit/base/types.py @@ -0,0 +1,10 @@ +from strawberry import type, field, ID + + +@type(name="Node", description="An object with an ID.") +class NodeType: + id: ID = field( + description=""" + ID of the object. + """ + ) diff --git a/reddit-clone/reddit/comments/types.py b/reddit-clone/reddit/comments/types.py new file mode 100644 index 00000000..ed3839a7 --- /dev/null +++ b/reddit-clone/reddit/comments/types.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +from typing import List, Optional + +from strawberry import type, field + +from reddit.base.types import NodeType +from reddit.comments.models import Comment + + +@type(name="Comment") +class CommentType(NodeType): + content: str = field( + description=""" + The content of the comment. + """ + ) + + votes: int = field( + description=""" + The votes the comment has. + """ + ) + + user_id: Optional[int] = field( + description=""" + The owner ID of the comment. + """ + ) + + replies: List[CommentType] = field( + description=""" + The replies for the comment. + """ + ) + + @classmethod + def from_instance(cls, instance: Comment) -> CommentType: + return CommentType( + id=instance.id, + content=instance.content, + votes=instance.votes, + user_id=instance.user_id, + replies=instance.replies, + ) diff --git a/reddit-clone/reddit/posts/types.py b/reddit-clone/reddit/posts/types.py new file mode 100644 index 00000000..728cee89 --- /dev/null +++ b/reddit-clone/reddit/posts/types.py @@ -0,0 +1,74 @@ +from __future__ import annotations + +from typing import List, Optional + +from strawberry import type, field + +from reddit.base.types import NodeType +from reddit.posts.models import Post +from reddit.comments.types import CommentType + + +@type(name="Post") +class PostType(NodeType): + title: str = field( + description=""" + The title of the post. + """ + ) + + text: Optional[str] = field( + description=""" + The text for the post. + """ + ) + + link: Optional[str] = field( + description=""" + The link of the post. + """ + ) + + thumbnail: Optional[str] = field( + description=""" + The thumbnail URL of the post. + """ + ) + + user_id: int = field( + description=""" + The owner ID of the post. + """ + ) + + subreddit_id: int = field( + description=""" + The subreddit ID of the post. + """ + ) + + votes: int = field( + description=""" + The votes the post has. + """ + ) + + comments: List[CommentType] = field( + description=""" + The comments for the post. + """ + ) + + @classmethod + def from_instance(cls, instance: Post) -> PostType: + return PostType( + id=instance.id, + title=instance.title, + text=instance.text, + link=instance.link, + thumbnail=instance.thumbnail, + user_id=instance.user_id, + subreddit_id=instance.subreddit_id, + votes=instance.votes, + comments=instance.comments, + ) diff --git a/reddit-clone/reddit/subreddits/types.py b/reddit-clone/reddit/subreddits/types.py new file mode 100644 index 00000000..ca361891 --- /dev/null +++ b/reddit-clone/reddit/subreddits/types.py @@ -0,0 +1,60 @@ +from __future__ import annotations + +from typing import List + +from strawberry import type, field + +from reddit.base.types import NodeType +from reddit.subreddits.models import Subreddit +from reddit.posts.types import PostType + + +@type(name="Subreddit") +class SubredditType(NodeType): + name: str = field( + description=""" + The name of the subreddit. + """ + ) + + description: str = field( + description=""" + The description of the subreddit. + """ + ) + + admin_id: int = field( + description=""" + The owner ID of the subreddit. + """ + ) + + # TODO: make status an enum + status: int = field( + description=""" + The status of the subreddit. + """ + ) + + icon: str = field( + description=""" + The icon URL of the subreddit. + """ + ) + + posts: List[PostType] = field( + description=""" + The posts for the subreddit. + """ + ) + + @classmethod + def from_instance(cls, instance: Subreddit) -> SubredditType: + return SubredditType( + id=instance.id, + description=instance.description, + admin_id=instance.admin_id, + status=instance.status, + icon=instance.icon, + posts=instance.posts, + ) diff --git a/reddit-clone/reddit/users/types.py b/reddit-clone/reddit/users/types.py new file mode 100644 index 00000000..4aa483b5 --- /dev/null +++ b/reddit-clone/reddit/users/types.py @@ -0,0 +1,55 @@ +from __future__ import annotations + +from typing import List, Optional + +from strawberry import type, field + +from reddit.users.models import User +from reddit.base.types import NodeType +from reddit.posts.types import PostType +from reddit.subreddits.types import SubredditType +from reddit.comments.types import CommentType + + +@type(name="User") +class UserType(NodeType): + username: str = field( + description=""" + The username of the user. + """ + ) + + avatar: Optional[str] = field( + description=""" + The avatar URL of the user. + """ + ) + + posts: List[PostType] = field( + description=""" + The posts for the user. + """ + ) + + subreddits: List[SubredditType] = field( + description=""" + The subreddits the user is in. + """ + ) + + comments: List[CommentType] = field( + description=""" + The comments for the user. + """ + ) + + @classmethod + def from_instance(cls, instance: User) -> UserType: + return UserType( + id=instance.id, + username=instance.username, + avatar=instance.avatar, + posts=instance.posts, + subreddits=instance.subreddits, + comments=instance.comments, + ) From 717a30ad79a034db9138057e88441c145ec33f64 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Tue, 5 Oct 2021 16:10:00 +0530 Subject: [PATCH 038/150] add user loaders --- reddit-clone/reddit/users/loaders.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 reddit-clone/reddit/users/loaders.py diff --git a/reddit-clone/reddit/users/loaders.py b/reddit-clone/reddit/users/loaders.py new file mode 100644 index 00000000..36617a1c --- /dev/null +++ b/reddit-clone/reddit/users/loaders.py @@ -0,0 +1,18 @@ +from typing import List, Optional, Dict + +from sqlalchemy import select +from sqlalchemy.engine import Result + +from reddit.database import get_session +from reddit.users.models import User + + +async def load_users(user_ids: List[int]) -> List[Optional[User]]: + """ + Batch-loads users by their IDs. + """ + query = select(User).filter(User.id.in_(user_ids)) + async with get_session() as session: + result: Result = await session.execute(query) + user_map: Dict[int, User] = {user.id: user for user in result.scalars()} + return [user_map.get(user_id) for user_id in user_ids] From 82c1c99153905f8e8c32cd5adf0d7c0d5243955b Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Tue, 5 Oct 2021 16:15:40 +0530 Subject: [PATCH 039/150] update context --- reddit-clone/poetry.lock | 86 ++++++++++++++++----------------- reddit-clone/pyproject.toml | 1 + reddit-clone/reddit/__init__.py | 22 +++++++-- 3 files changed, 63 insertions(+), 46 deletions(-) diff --git a/reddit-clone/poetry.lock b/reddit-clone/poetry.lock index 37b7640c..a772147d 100644 --- a/reddit-clone/poetry.lock +++ b/reddit-clone/poetry.lock @@ -404,7 +404,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [[package]] name = "regex" -version = "2021.9.24" +version = "2021.9.30" description = "Alternative regular expression module, to replace re." category = "dev" optional = false @@ -603,7 +603,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "0be25f7a7b2a6d2767a4d40638945fea8d0148af2efd0ab4c26d50b2eaced91b" +content-hash = "f3370347eb4ab8f408c13bdc052245300e88eca06607c5302a492c703bf2d4f8" [metadata.files] alembic = [ @@ -910,47 +910,47 @@ pyyaml = [ {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, ] regex = [ - {file = "regex-2021.9.24-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0628ed7d6334e8f896f882a5c1240de8c4d9b0dd7c7fb8e9f4692f5684b7d656"}, - {file = "regex-2021.9.24-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3baf3eaa41044d4ced2463fd5d23bf7bd4b03d68739c6c99a59ce1f95599a673"}, - {file = "regex-2021.9.24-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c000635fd78400a558bd7a3c2981bb2a430005ebaa909d31e6e300719739a949"}, - {file = "regex-2021.9.24-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:295bc8a13554a25ad31e44c4bedabd3c3e28bba027e4feeb9bb157647a2344a7"}, - {file = "regex-2021.9.24-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0e3f59d3c772f2c3baaef2db425e6fc4149d35a052d874bb95ccfca10a1b9f4"}, - {file = "regex-2021.9.24-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:aea4006b73b555fc5bdb650a8b92cf486d678afa168cf9b38402bb60bf0f9c18"}, - {file = "regex-2021.9.24-cp310-cp310-win32.whl", hash = "sha256:09eb62654030f39f3ba46bc6726bea464069c29d00a9709e28c9ee9623a8da4a"}, - {file = "regex-2021.9.24-cp310-cp310-win_amd64.whl", hash = "sha256:8d80087320632457aefc73f686f66139801959bf5b066b4419b92be85be3543c"}, - {file = "regex-2021.9.24-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:7e3536f305f42ad6d31fc86636c54c7dafce8d634e56fef790fbacb59d499dd5"}, - {file = "regex-2021.9.24-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c31f35a984caffb75f00a86852951a337540b44e4a22171354fb760cefa09346"}, - {file = "regex-2021.9.24-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c7cb25adba814d5f419733fe565f3289d6fa629ab9e0b78f6dff5fa94ab0456"}, - {file = "regex-2021.9.24-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:85c61bee5957e2d7be390392feac7e1d7abd3a49cbaed0c8cee1541b784c8561"}, - {file = "regex-2021.9.24-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c94722bf403b8da744b7d0bb87e1f2529383003ceec92e754f768ef9323f69ad"}, - {file = "regex-2021.9.24-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6adc1bd68f81968c9d249aab8c09cdc2cbe384bf2d2cb7f190f56875000cdc72"}, - {file = "regex-2021.9.24-cp36-cp36m-win32.whl", hash = "sha256:2054dea683f1bda3a804fcfdb0c1c74821acb968093d0be16233873190d459e3"}, - {file = "regex-2021.9.24-cp36-cp36m-win_amd64.whl", hash = "sha256:7783d89bd5413d183a38761fbc68279b984b9afcfbb39fa89d91f63763fbfb90"}, - {file = "regex-2021.9.24-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b15dc34273aefe522df25096d5d087abc626e388a28a28ac75a4404bb7668736"}, - {file = "regex-2021.9.24-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10a7a9cbe30bd90b7d9a1b4749ef20e13a3528e4215a2852be35784b6bd070f0"}, - {file = "regex-2021.9.24-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb9f5844db480e2ef9fce3a72e71122dd010ab7b2920f777966ba25f7eb63819"}, - {file = "regex-2021.9.24-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:17310b181902e0bb42b29c700e2c2346b8d81f26e900b1328f642e225c88bce1"}, - {file = "regex-2021.9.24-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0bba1f6df4eafe79db2ecf38835c2626dbd47911e0516f6962c806f83e7a99ae"}, - {file = "regex-2021.9.24-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:821e10b73e0898544807a0692a276e539e5bafe0a055506a6882814b6a02c3ec"}, - {file = "regex-2021.9.24-cp37-cp37m-win32.whl", hash = "sha256:9c371dd326289d85906c27ec2bc1dcdedd9d0be12b543d16e37bad35754bde48"}, - {file = "regex-2021.9.24-cp37-cp37m-win_amd64.whl", hash = "sha256:1e8d1898d4fb817120a5f684363b30108d7b0b46c7261264b100d14ec90a70e7"}, - {file = "regex-2021.9.24-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8a5c2250c0a74428fd5507ae8853706fdde0f23bfb62ee1ec9418eeacf216078"}, - {file = "regex-2021.9.24-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8aec4b4da165c4a64ea80443c16e49e3b15df0f56c124ac5f2f8708a65a0eddc"}, - {file = "regex-2021.9.24-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:650c4f1fc4273f4e783e1d8e8b51a3e2311c2488ba0fcae6425b1e2c248a189d"}, - {file = "regex-2021.9.24-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2cdb3789736f91d0b3333ac54d12a7e4f9efbc98f53cb905d3496259a893a8b3"}, - {file = "regex-2021.9.24-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e61100200fa6ab7c99b61476f9f9653962ae71b931391d0264acfb4d9527d9c"}, - {file = "regex-2021.9.24-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8c268e78d175798cd71d29114b0a1f1391c7d011995267d3b62319ec1a4ecaa1"}, - {file = "regex-2021.9.24-cp38-cp38-win32.whl", hash = "sha256:658e3477676009083422042c4bac2bdad77b696e932a3de001c42cc046f8eda2"}, - {file = "regex-2021.9.24-cp38-cp38-win_amd64.whl", hash = "sha256:a731552729ee8ae9c546fb1c651c97bf5f759018fdd40d0e9b4d129e1e3a44c8"}, - {file = "regex-2021.9.24-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:86f9931eb92e521809d4b64ec8514f18faa8e11e97d6c2d1afa1bcf6c20a8eab"}, - {file = "regex-2021.9.24-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcbbc9cfa147d55a577d285fd479b43103188855074552708df7acc31a476dd9"}, - {file = "regex-2021.9.24-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29385c4dbb3f8b3a55ce13de6a97a3d21bd00de66acd7cdfc0b49cb2f08c906c"}, - {file = "regex-2021.9.24-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c50a6379763c733562b1fee877372234d271e5c78cd13ade5f25978aa06744db"}, - {file = "regex-2021.9.24-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f74b6d8f59f3cfb8237e25c532b11f794b96f5c89a6f4a25857d85f84fbef11"}, - {file = "regex-2021.9.24-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6c4d83d21d23dd854ffbc8154cf293f4e43ba630aa9bd2539c899343d7f59da3"}, - {file = "regex-2021.9.24-cp39-cp39-win32.whl", hash = "sha256:95e89a8558c8c48626dcffdf9c8abac26b7c251d352688e7ab9baf351e1c7da6"}, - {file = "regex-2021.9.24-cp39-cp39-win_amd64.whl", hash = "sha256:835962f432bce92dc9bf22903d46c50003c8d11b1dc64084c8fae63bca98564a"}, - {file = "regex-2021.9.24.tar.gz", hash = "sha256:6266fde576e12357b25096351aac2b4b880b0066263e7bc7a9a1b4307991bb0e"}, + {file = "regex-2021.9.30-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:66696c8336a1b5d1182464f3af3427cc760118f26d0b09a2ddc16a976a4d2637"}, + {file = "regex-2021.9.30-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d87459ad3ab40cd8493774f8a454b2e490d8e729e7e402a0625867a983e4e02"}, + {file = "regex-2021.9.30-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78cf6a1e023caf5e9a982f5377414e1aeac55198831b852835732cfd0a0ca5ff"}, + {file = "regex-2021.9.30-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:255791523f80ea8e48e79af7120b4697ef3b74f6886995dcdb08c41f8e516be0"}, + {file = "regex-2021.9.30-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e502f8d4e5ef714bcc2c94d499684890c94239526d61fdf1096547db91ca6aa6"}, + {file = "regex-2021.9.30-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4907fb0f9b9309a5bded72343e675a252c2589a41871874feace9a05a540241e"}, + {file = "regex-2021.9.30-cp310-cp310-win32.whl", hash = "sha256:3be40f720af170a6b20ddd2ad7904c58b13d2b56f6734ee5d09bbdeed2fa4816"}, + {file = "regex-2021.9.30-cp310-cp310-win_amd64.whl", hash = "sha256:c2b180ed30856dfa70cfe927b0fd38e6b68198a03039abdbeb1f2029758d87e7"}, + {file = "regex-2021.9.30-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e6f2d2f93001801296fe3ca86515eb04915472b5380d4d8752f09f25f0b9b0ed"}, + {file = "regex-2021.9.30-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fa7ba9ab2eba7284e0d7d94f61df7af86015b0398e123331362270d71fab0b9"}, + {file = "regex-2021.9.30-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28040e89a04b60d579c69095c509a4f6a1a5379cd865258e3a186b7105de72c6"}, + {file = "regex-2021.9.30-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f588209d3e4797882cd238195c175290dbc501973b10a581086b5c6bcd095ffb"}, + {file = "regex-2021.9.30-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42952d325439ef223e4e9db7ee6d9087b5c68c5c15b1f9de68e990837682fc7b"}, + {file = "regex-2021.9.30-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cae4099031d80703954c39680323dabd87a69b21262303160776aa0e55970ca0"}, + {file = "regex-2021.9.30-cp36-cp36m-win32.whl", hash = "sha256:0de8ad66b08c3e673b61981b9e3626f8784d5564f8c3928e2ad408c0eb5ac38c"}, + {file = "regex-2021.9.30-cp36-cp36m-win_amd64.whl", hash = "sha256:b345ecde37c86dd7084c62954468a4a655fd2d24fd9b237949dd07a4d0dd6f4c"}, + {file = "regex-2021.9.30-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6f08187136f11e430638c2c66e1db091105d7c2e9902489f0dbc69b44c222b4"}, + {file = "regex-2021.9.30-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b55442650f541d195a535ccec33078c78a9521973fb960923da7515e9ed78fa6"}, + {file = "regex-2021.9.30-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87e9c489aa98f50f367fb26cc9c8908d668e9228d327644d7aa568d47e456f47"}, + {file = "regex-2021.9.30-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e2cb7d4909ed16ed35729d38af585673f1f0833e73dfdf0c18e5be0061107b99"}, + {file = "regex-2021.9.30-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0861e7f6325e821d5c40514c551fd538b292f8cc3960086e73491b9c5d8291d"}, + {file = "regex-2021.9.30-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:81fdc90f999b2147fc62e303440c424c47e5573a9b615ed5d43a5b832efcca9e"}, + {file = "regex-2021.9.30-cp37-cp37m-win32.whl", hash = "sha256:8c1ad61fa024195136a6b7b89538030bd00df15f90ac177ca278df9b2386c96f"}, + {file = "regex-2021.9.30-cp37-cp37m-win_amd64.whl", hash = "sha256:e3770781353a4886b68ef10cec31c1f61e8e3a0be5f213c2bb15a86efd999bc4"}, + {file = "regex-2021.9.30-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9c065d95a514a06b92a5026766d72ac91bfabf581adb5b29bc5c91d4b3ee9b83"}, + {file = "regex-2021.9.30-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9925985be05d54b3d25fd6c1ea8e50ff1f7c2744c75bdc4d3b45c790afa2bcb3"}, + {file = "regex-2021.9.30-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:470f2c882f2672d8eeda8ab27992aec277c067d280b52541357e1acd7e606dae"}, + {file = "regex-2021.9.30-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ad0517df22a97f1da20d8f1c8cb71a5d1997fa383326b81f9cf22c9dadfbdf34"}, + {file = "regex-2021.9.30-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e30838df7bfd20db6466fd309d9b580d32855f8e2c2e6d74cf9da27dcd9b63"}, + {file = "regex-2021.9.30-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b34d2335d6aedec7dcadd3f8283b9682fadad8b9b008da8788d2fce76125ebe"}, + {file = "regex-2021.9.30-cp38-cp38-win32.whl", hash = "sha256:e07049cece3462c626d650e8bf42ddbca3abf4aa08155002c28cb6d9a5a281e2"}, + {file = "regex-2021.9.30-cp38-cp38-win_amd64.whl", hash = "sha256:37868075eda024470bd0feab872c692ac4ee29db1e14baec103257bf6cc64346"}, + {file = "regex-2021.9.30-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d331f238a7accfbbe1c4cd1ba610d4c087b206353539331e32a8f05345c74aec"}, + {file = "regex-2021.9.30-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6348a7ab2a502cbdd0b7fd0496d614007489adb7361956b38044d1d588e66e04"}, + {file = "regex-2021.9.30-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce7b1cca6c23f19bee8dc40228d9c314d86d1e51996b86f924aca302fc8f8bf9"}, + {file = "regex-2021.9.30-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1f1125bc5172ab3a049bc6f4b9c0aae95a2a2001a77e6d6e4239fa3653e202b5"}, + {file = "regex-2021.9.30-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:638e98d069b14113e8afba6a54d1ca123f712c0d105e67c1f9211b2a825ef926"}, + {file = "regex-2021.9.30-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9a0b0db6b49da7fa37ca8eddf9f40a8dbc599bad43e64f452284f37b6c34d91c"}, + {file = "regex-2021.9.30-cp39-cp39-win32.whl", hash = "sha256:9910869c472e5a6728680ca357b5846546cbbd2ab3ad5bef986ef0bc438d0aa6"}, + {file = "regex-2021.9.30-cp39-cp39-win_amd64.whl", hash = "sha256:3b71213ec3bad9a5a02e049f2ec86b3d7c3e350129ae0f4e2f99c12b5da919ed"}, + {file = "regex-2021.9.30.tar.gz", hash = "sha256:81e125d9ba54c34579e4539a967e976a3c56150796674aec318b1b2f49251be7"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, diff --git a/reddit-clone/pyproject.toml b/reddit-clone/pyproject.toml index 7d362310..cb8694d8 100644 --- a/reddit-clone/pyproject.toml +++ b/reddit-clone/pyproject.toml @@ -12,6 +12,7 @@ alembic = "^1.7.1" asyncpg = "^0.24" strawberry-graphql = {extras = ["asgi"], version = "^0.77.0"} fastapi = "^0.68.1" +starlette = "^0.14" [tool.poetry.dev-dependencies] diff --git a/reddit-clone/reddit/__init__.py b/reddit-clone/reddit/__init__.py index e6684e7a..5b1cccc8 100644 --- a/reddit-clone/reddit/__init__.py +++ b/reddit-clone/reddit/__init__.py @@ -1,8 +1,24 @@ +from typing import Union, Optional, Any + from fastapi import FastAPI +from starlette.requests import Request +from starlette.responses import Response +from starlette.websockets import WebSocket +from strawberry.dataloader import DataLoader from strawberry.asgi import GraphQL -from .settings import DEBUG -from .schema import schema +from reddit.settings import DEBUG +from reddit.schema import schema +from reddit.users.loaders import load_users + + +class MyGraphQL(GraphQL): + async def get_context( + self, request: Union[Request, WebSocket], response: Optional[Response] = None + ) -> Optional[Any]: + context: dict = await super().get_context(request, response=response) + context.update(user_loader=DataLoader(load_fn=load_users)) + return context def create_application() -> FastAPI: @@ -13,7 +29,7 @@ def create_application() -> FastAPI: """ application = FastAPI(title="Reddit GraphQL", debug=DEBUG) - graphql_app = GraphQL(schema=schema, graphiql=True, debug=DEBUG) + graphql_app = MyGraphQL(schema=schema, graphiql=True, debug=DEBUG) application.add_route(path="/graphql", route=graphql_app) From c7e5e5d0c7eca6c5d5bf478c47b5649e81325068 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Wed, 6 Oct 2021 14:42:12 +0530 Subject: [PATCH 040/150] update model: user --- reddit-clone/poetry.lock | 18 +++++++++++++++++- reddit-clone/pyproject.toml | 1 + reddit-clone/reddit/base/queries.py | 0 reddit-clone/reddit/base/types.py | 23 +++++++++++++++++++++++ reddit-clone/reddit/schema.py | 8 ++++---- reddit-clone/reddit/users/models.py | 4 ++-- 6 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 reddit-clone/reddit/base/queries.py diff --git a/reddit-clone/poetry.lock b/reddit-clone/poetry.lock index a772147d..0728f1a4 100644 --- a/reddit-clone/poetry.lock +++ b/reddit-clone/poetry.lock @@ -183,6 +183,18 @@ category = "main" optional = false python-versions = ">=3.6,<4" +[[package]] +name = "graphql-relay" +version = "3.1.0" +description = "Relay library for graphql-core" +category = "main" +optional = false +python-versions = ">=3.6,<4" + +[package.dependencies] +graphql-core = ">=3.1" +typing-extensions = {version = ">=3.7,<4", markers = "python_version < \"3.8\""} + [[package]] name = "greenlet" version = "1.1.2" @@ -603,7 +615,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "f3370347eb4ab8f408c13bdc052245300e88eca06607c5302a492c703bf2d4f8" +content-hash = "c7f68ad613b93e290f94e02da38f6c0c46e7b049d42fcf911543dc7762ac2c97" [metadata.files] alembic = [ @@ -669,6 +681,10 @@ graphql-core = [ {file = "graphql-core-3.1.6.tar.gz", hash = "sha256:e65975b6a13878f9113a1fa5320760585b522d139944e005936b1b8358d0651a"}, {file = "graphql_core-3.1.6-py3-none-any.whl", hash = "sha256:c78d09596d347e1cffd266c5384abfedf43ed1eae08729773bebb3d527fe5a14"}, ] +graphql-relay = [ + {file = "graphql-relay-3.1.0.tar.gz", hash = "sha256:70d5a7ee5995ea7c2a9a37e51227663b1a464f1f40e98fdde950be5415dfe0b4"}, + {file = "graphql_relay-3.1.0-py3-none-any.whl", hash = "sha256:2cda0ac0199dd56c28ca4f6e0381cdcf5787809c06d1507df3c2a738f9ad846f"}, +] greenlet = [ {file = "greenlet-1.1.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6"}, {file = "greenlet-1.1.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:aec52725173bd3a7b56fe91bc56eccb26fbdff1386ef123abb63c84c5b43b63a"}, diff --git a/reddit-clone/pyproject.toml b/reddit-clone/pyproject.toml index cb8694d8..0bca5049 100644 --- a/reddit-clone/pyproject.toml +++ b/reddit-clone/pyproject.toml @@ -13,6 +13,7 @@ asyncpg = "^0.24" strawberry-graphql = {extras = ["asgi"], version = "^0.77.0"} fastapi = "^0.68.1" starlette = "^0.14" +graphql-relay = "^3.1.0" [tool.poetry.dev-dependencies] diff --git a/reddit-clone/reddit/base/queries.py b/reddit-clone/reddit/base/queries.py new file mode 100644 index 00000000..e69de29b diff --git a/reddit-clone/reddit/base/types.py b/reddit-clone/reddit/base/types.py index 9057bbf1..c2e15e92 100644 --- a/reddit-clone/reddit/base/types.py +++ b/reddit-clone/reddit/base/types.py @@ -1,4 +1,6 @@ from strawberry import type, field, ID +from strawberry.types import Info +from graphql_relay import from_global_id, to_global_id @type(name="Node", description="An object with an ID.") @@ -8,3 +10,24 @@ class NodeType: ID of the object. """ ) + + @classmethod + def get_node_from_global_id(cls, info: Info, global_id: str, only_type=None): + try: + _type, _id = cls.from_global_id(global_id) + except Exception as e: + raise Exception( + f'Unable to parse global ID "{global_id}". ' + 'Make sure it is a base64 encoded string in the format: "TypeName:id". ' + f"Exception message: {str(e)}" + ) + + info.schema.get_type(_type) + + @classmethod + def from_global_id(cls, global_id: str): + return from_global_id(global_id) + + @classmethod + def to_global_id(cls, schema_type: str, id: str): + return to_global_id(schema_type, id) diff --git a/reddit-clone/reddit/schema.py b/reddit-clone/reddit/schema.py index 77439e13..539ad262 100644 --- a/reddit-clone/reddit/schema.py +++ b/reddit-clone/reddit/schema.py @@ -1,11 +1,11 @@ -import strawberry +from strawberry import type, field, Schema -@strawberry.type +@type class Query: - @strawberry.field() + @field def hello_world(self) -> str: return "Hello world!" -schema = strawberry.Schema(query=Query, mutation=None) +schema = Schema(query=Query, mutation=None) diff --git a/reddit-clone/reddit/users/models.py b/reddit-clone/reddit/users/models.py index 5eec2459..a1b8c0bf 100644 --- a/reddit-clone/reddit/users/models.py +++ b/reddit-clone/reddit/users/models.py @@ -1,4 +1,4 @@ -from typing import Optional, List +from typing import List from sqlalchemy import Column, String, Integer, ForeignKey from sqlalchemy.orm import relationship @@ -24,7 +24,7 @@ class User(Base): password: str = Column(String(255), nullable=False) - avatar: Optional[str] = Column(String(255), default=None) + avatar: str = Column(String(255), nullable=False, default="default.jpg") posts: List[Post] = relationship("Post", back_populates="user", lazy="dynamic") From fdf42aeeb6de32efa9425b023cad1377b6597446 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Wed, 6 Oct 2021 14:49:05 +0530 Subject: [PATCH 041/150] update type: node --- reddit-clone/reddit/base/types.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/reddit-clone/reddit/base/types.py b/reddit-clone/reddit/base/types.py index c2e15e92..5acaa53b 100644 --- a/reddit-clone/reddit/base/types.py +++ b/reddit-clone/reddit/base/types.py @@ -22,7 +22,22 @@ def get_node_from_global_id(cls, info: Info, global_id: str, only_type=None): f"Exception message: {str(e)}" ) - info.schema.get_type(_type) + schema_type = info.schema.get_type(_type) + if schema_type is None: + raise Exception(f'Relay Node "{_type}" not found in schema') + + if only_type: + assert schema_type == only_type + + # We make sure the ObjectType implements the "Node" interface + if cls not in schema_type.interfaces: + raise Exception( + f'ObjectType "{_type}" does not implement the "{cls}" interface.' + ) + + get_node = getattr(schema_type, "get_node", None) + if get_node: + return get_node(info, _id) @classmethod def from_global_id(cls, global_id: str): From c1520b691188f22421a3a3aa193323ba87e2ac10 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Wed, 6 Oct 2021 15:04:10 +0530 Subject: [PATCH 042/150] implement classmethod: get_node on node types --- reddit-clone/reddit/comments/types.py | 13 +++++++++++++ reddit-clone/reddit/posts/types.py | 13 +++++++++++++ reddit-clone/reddit/subreddits/types.py | 15 ++++++++++++++- reddit-clone/reddit/users/types.py | 15 ++++++++++++++- 4 files changed, 54 insertions(+), 2 deletions(-) diff --git a/reddit-clone/reddit/comments/types.py b/reddit-clone/reddit/comments/types.py index ed3839a7..341b7b0e 100644 --- a/reddit-clone/reddit/comments/types.py +++ b/reddit-clone/reddit/comments/types.py @@ -3,9 +3,12 @@ from typing import List, Optional from strawberry import type, field +from strawberry.types import Info +from sqlalchemy import select from reddit.base.types import NodeType from reddit.comments.models import Comment +from reddit.database import get_session @type(name="Comment") @@ -34,6 +37,16 @@ class CommentType(NodeType): """ ) + @classmethod + async def get_node(cls, info: Info, comment_id: str) -> Optional[Comment]: + """ + Gets a comment with the given ID. + """ + query = select(Comment).filter_by(id=comment_id).first() + async with get_session() as session: + user = await session.execute(query) + return user + @classmethod def from_instance(cls, instance: Comment) -> CommentType: return CommentType( diff --git a/reddit-clone/reddit/posts/types.py b/reddit-clone/reddit/posts/types.py index 728cee89..35b983ca 100644 --- a/reddit-clone/reddit/posts/types.py +++ b/reddit-clone/reddit/posts/types.py @@ -3,10 +3,13 @@ from typing import List, Optional from strawberry import type, field +from strawberry.types import Info +from sqlalchemy import select from reddit.base.types import NodeType from reddit.posts.models import Post from reddit.comments.types import CommentType +from reddit.database import get_session @type(name="Post") @@ -59,6 +62,16 @@ class PostType(NodeType): """ ) + @classmethod + async def get_node(cls, info: Info, post_id: str) -> Optional[Post]: + """ + Gets a post with the given ID. + """ + query = select(Post).filter_by(id=post_id).first() + async with get_session() as session: + user = await session.execute(query) + return user + @classmethod def from_instance(cls, instance: Post) -> PostType: return PostType( diff --git a/reddit-clone/reddit/subreddits/types.py b/reddit-clone/reddit/subreddits/types.py index ca361891..ce36c26b 100644 --- a/reddit-clone/reddit/subreddits/types.py +++ b/reddit-clone/reddit/subreddits/types.py @@ -1,12 +1,15 @@ from __future__ import annotations -from typing import List +from typing import List, Optional from strawberry import type, field +from strawberry.types import Info +from sqlalchemy import select from reddit.base.types import NodeType from reddit.subreddits.models import Subreddit from reddit.posts.types import PostType +from reddit.database import get_session @type(name="Subreddit") @@ -48,6 +51,16 @@ class SubredditType(NodeType): """ ) + @classmethod + async def get_node(cls, info: Info, subreddit_id: str) -> Optional[Subreddit]: + """ + Gets a subreddit with the given ID. + """ + query = select(Subreddit).filter_by(id=subreddit_id).first() + async with get_session() as session: + user = await session.execute(query) + return user + @classmethod def from_instance(cls, instance: Subreddit) -> SubredditType: return SubredditType( diff --git a/reddit-clone/reddit/users/types.py b/reddit-clone/reddit/users/types.py index 4aa483b5..1975410e 100644 --- a/reddit-clone/reddit/users/types.py +++ b/reddit-clone/reddit/users/types.py @@ -3,12 +3,15 @@ from typing import List, Optional from strawberry import type, field +from strawberry.types import Info +from sqlalchemy import select from reddit.users.models import User from reddit.base.types import NodeType from reddit.posts.types import PostType from reddit.subreddits.types import SubredditType from reddit.comments.types import CommentType +from reddit.database import get_session @type(name="User") @@ -19,7 +22,7 @@ class UserType(NodeType): """ ) - avatar: Optional[str] = field( + avatar: str = field( description=""" The avatar URL of the user. """ @@ -43,6 +46,16 @@ class UserType(NodeType): """ ) + @classmethod + async def get_node(cls, info: Info, user_id: str) -> Optional[User]: + """ + Gets an user with the given ID. + """ + query = select(User).filter_by(id=user_id).first() + async with get_session() as session: + user = await session.execute(query) + return user + @classmethod def from_instance(cls, instance: User) -> UserType: return UserType( From 2487b21eda8cbd1f0dfbd762e138f2d4783ff6d0 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Wed, 6 Oct 2021 15:31:59 +0530 Subject: [PATCH 043/150] update nodes --- reddit-clone/reddit/comments/types.py | 7 ++++--- reddit-clone/reddit/posts/types.py | 7 ++++--- reddit-clone/reddit/subreddits/types.py | 7 ++++--- reddit-clone/reddit/users/types.py | 5 +++-- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/reddit-clone/reddit/comments/types.py b/reddit-clone/reddit/comments/types.py index 341b7b0e..ba805fd1 100644 --- a/reddit-clone/reddit/comments/types.py +++ b/reddit-clone/reddit/comments/types.py @@ -38,14 +38,15 @@ class CommentType(NodeType): ) @classmethod - async def get_node(cls, info: Info, comment_id: str) -> Optional[Comment]: + async def get_node(cls, info: Info, comment_id: str) -> Optional[CommentType]: """ Gets a comment with the given ID. """ query = select(Comment).filter_by(id=comment_id).first() async with get_session() as session: - user = await session.execute(query) - return user + comment = await session.execute(query) + if comment is not None: + return cls.from_instance(comment) @classmethod def from_instance(cls, instance: Comment) -> CommentType: diff --git a/reddit-clone/reddit/posts/types.py b/reddit-clone/reddit/posts/types.py index 35b983ca..e7bb72d6 100644 --- a/reddit-clone/reddit/posts/types.py +++ b/reddit-clone/reddit/posts/types.py @@ -63,14 +63,15 @@ class PostType(NodeType): ) @classmethod - async def get_node(cls, info: Info, post_id: str) -> Optional[Post]: + async def get_node(cls, info: Info, post_id: str) -> Optional[PostType]: """ Gets a post with the given ID. """ query = select(Post).filter_by(id=post_id).first() async with get_session() as session: - user = await session.execute(query) - return user + post = await session.execute(query) + if post is not None: + return cls.from_instance(post) @classmethod def from_instance(cls, instance: Post) -> PostType: diff --git a/reddit-clone/reddit/subreddits/types.py b/reddit-clone/reddit/subreddits/types.py index ce36c26b..defdf8b7 100644 --- a/reddit-clone/reddit/subreddits/types.py +++ b/reddit-clone/reddit/subreddits/types.py @@ -52,14 +52,15 @@ class SubredditType(NodeType): ) @classmethod - async def get_node(cls, info: Info, subreddit_id: str) -> Optional[Subreddit]: + async def get_node(cls, info: Info, subreddit_id: str) -> Optional[SubredditType]: """ Gets a subreddit with the given ID. """ query = select(Subreddit).filter_by(id=subreddit_id).first() async with get_session() as session: - user = await session.execute(query) - return user + subreddit = await session.execute(query) + if subreddit is not None: + return cls.from_instance(subreddit) @classmethod def from_instance(cls, instance: Subreddit) -> SubredditType: diff --git a/reddit-clone/reddit/users/types.py b/reddit-clone/reddit/users/types.py index 1975410e..2a76fef3 100644 --- a/reddit-clone/reddit/users/types.py +++ b/reddit-clone/reddit/users/types.py @@ -47,14 +47,15 @@ class UserType(NodeType): ) @classmethod - async def get_node(cls, info: Info, user_id: str) -> Optional[User]: + async def get_node(cls, info: Info, user_id: str) -> Optional[UserType]: """ Gets an user with the given ID. """ query = select(User).filter_by(id=user_id).first() async with get_session() as session: user = await session.execute(query) - return user + if user is not None: + return cls.from_instance(user) @classmethod def from_instance(cls, instance: User) -> UserType: From f4ec66395ebb443b75769663351792f7fea3b405 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Wed, 6 Oct 2021 15:33:18 +0530 Subject: [PATCH 044/150] rename node resolver --- reddit-clone/reddit/base/queries.py | 0 reddit-clone/reddit/base/types.py | 6 +++--- reddit-clone/reddit/comments/types.py | 2 +- reddit-clone/reddit/posts/types.py | 2 +- reddit-clone/reddit/subreddits/types.py | 4 +++- reddit-clone/reddit/users/types.py | 2 +- 6 files changed, 9 insertions(+), 7 deletions(-) delete mode 100644 reddit-clone/reddit/base/queries.py diff --git a/reddit-clone/reddit/base/queries.py b/reddit-clone/reddit/base/queries.py deleted file mode 100644 index e69de29b..00000000 diff --git a/reddit-clone/reddit/base/types.py b/reddit-clone/reddit/base/types.py index 5acaa53b..941ac2de 100644 --- a/reddit-clone/reddit/base/types.py +++ b/reddit-clone/reddit/base/types.py @@ -35,9 +35,9 @@ def get_node_from_global_id(cls, info: Info, global_id: str, only_type=None): f'ObjectType "{_type}" does not implement the "{cls}" interface.' ) - get_node = getattr(schema_type, "get_node", None) - if get_node: - return get_node(info, _id) + resolver = getattr(schema_type, "resolve_node", None) + if resolver is not None: + return resolver(info, _id) @classmethod def from_global_id(cls, global_id: str): diff --git a/reddit-clone/reddit/comments/types.py b/reddit-clone/reddit/comments/types.py index ba805fd1..13a173dc 100644 --- a/reddit-clone/reddit/comments/types.py +++ b/reddit-clone/reddit/comments/types.py @@ -38,7 +38,7 @@ class CommentType(NodeType): ) @classmethod - async def get_node(cls, info: Info, comment_id: str) -> Optional[CommentType]: + async def resolve_node(cls, info: Info, comment_id: str) -> Optional[CommentType]: """ Gets a comment with the given ID. """ diff --git a/reddit-clone/reddit/posts/types.py b/reddit-clone/reddit/posts/types.py index e7bb72d6..450c3552 100644 --- a/reddit-clone/reddit/posts/types.py +++ b/reddit-clone/reddit/posts/types.py @@ -63,7 +63,7 @@ class PostType(NodeType): ) @classmethod - async def get_node(cls, info: Info, post_id: str) -> Optional[PostType]: + async def resolve_node(cls, info: Info, post_id: str) -> Optional[PostType]: """ Gets a post with the given ID. """ diff --git a/reddit-clone/reddit/subreddits/types.py b/reddit-clone/reddit/subreddits/types.py index defdf8b7..6fea60af 100644 --- a/reddit-clone/reddit/subreddits/types.py +++ b/reddit-clone/reddit/subreddits/types.py @@ -52,7 +52,9 @@ class SubredditType(NodeType): ) @classmethod - async def get_node(cls, info: Info, subreddit_id: str) -> Optional[SubredditType]: + async def resolve_node( + cls, info: Info, subreddit_id: str + ) -> Optional[SubredditType]: """ Gets a subreddit with the given ID. """ diff --git a/reddit-clone/reddit/users/types.py b/reddit-clone/reddit/users/types.py index 2a76fef3..bf41b74b 100644 --- a/reddit-clone/reddit/users/types.py +++ b/reddit-clone/reddit/users/types.py @@ -47,7 +47,7 @@ class UserType(NodeType): ) @classmethod - async def get_node(cls, info: Info, user_id: str) -> Optional[UserType]: + async def resolve_node(cls, info: Info, user_id: str) -> Optional[UserType]: """ Gets an user with the given ID. """ From 7be5c59ba98819880a61d1a6fe1d01c48d246b03 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Wed, 6 Oct 2021 15:52:37 +0530 Subject: [PATCH 045/150] add field: node --- reddit-clone/reddit/base/queries.py | 13 +++++++++++++ reddit-clone/reddit/base/types.py | 15 ++++++++++----- reddit-clone/reddit/schema.py | 12 ++++++------ 3 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 reddit-clone/reddit/base/queries.py diff --git a/reddit-clone/reddit/base/queries.py b/reddit-clone/reddit/base/queries.py new file mode 100644 index 00000000..89de49c0 --- /dev/null +++ b/reddit-clone/reddit/base/queries.py @@ -0,0 +1,13 @@ +from typing import Optional + +from strawberry import field, type, ID +from strawberry.types import Info + +from reddit.base.types import NodeType + + +@type +class BaseQueries: + @field(name="node", description="Fetches an object given its ID.") + def resolve_node(self, info: Info, id: ID) -> Optional[NodeType]: + return NodeType.get_node_from_global_id(info=info, global_id=id) diff --git a/reddit-clone/reddit/base/types.py b/reddit-clone/reddit/base/types.py index 941ac2de..10b28034 100644 --- a/reddit-clone/reddit/base/types.py +++ b/reddit-clone/reddit/base/types.py @@ -1,9 +1,13 @@ -from strawberry import type, field, ID +from __future__ import annotations + +from typing import Optional, Type + +from strawberry import interface, field, ID from strawberry.types import Info from graphql_relay import from_global_id, to_global_id -@type(name="Node", description="An object with an ID.") +@interface(name="Node", description="An object with an ID.") class NodeType: id: ID = field( description=""" @@ -12,13 +16,14 @@ class NodeType: ) @classmethod - def get_node_from_global_id(cls, info: Info, global_id: str, only_type=None): + def get_node_from_global_id( + cls, info: Info, global_id: str, only_type=None + ) -> Optional[Type[NodeType]]: try: _type, _id = cls.from_global_id(global_id) except Exception as e: raise Exception( f'Unable to parse global ID "{global_id}". ' - 'Make sure it is a base64 encoded string in the format: "TypeName:id". ' f"Exception message: {str(e)}" ) @@ -44,5 +49,5 @@ def from_global_id(cls, global_id: str): return from_global_id(global_id) @classmethod - def to_global_id(cls, schema_type: str, id: str): + def to_global_id(cls, schema_type: str, id: str) -> str: return to_global_id(schema_type, id) diff --git a/reddit-clone/reddit/schema.py b/reddit-clone/reddit/schema.py index 539ad262..eec8017a 100644 --- a/reddit-clone/reddit/schema.py +++ b/reddit-clone/reddit/schema.py @@ -1,11 +1,11 @@ -from strawberry import type, field, Schema +from strawberry import type, Schema + +from reddit.base.queries import BaseQueries @type -class Query: - @field - def hello_world(self) -> str: - return "Hello world!" +class Query(BaseQueries): + pass -schema = Schema(query=Query, mutation=None) +schema = Schema(query=Query) From 49c7c143f4afc5b2df1ba12d0f906aeafabc6eda Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Wed, 6 Oct 2021 15:56:25 +0530 Subject: [PATCH 046/150] import strawberry as a whole module --- reddit-clone/reddit/base/queries.py | 8 ++++---- reddit-clone/reddit/base/types.py | 6 +++--- reddit-clone/reddit/comments/types.py | 12 ++++++------ reddit-clone/reddit/posts/types.py | 20 ++++++++++---------- reddit-clone/reddit/schema.py | 6 +++--- reddit-clone/reddit/subreddits/types.py | 16 ++++++++-------- reddit-clone/reddit/users/types.py | 14 +++++++------- 7 files changed, 41 insertions(+), 41 deletions(-) diff --git a/reddit-clone/reddit/base/queries.py b/reddit-clone/reddit/base/queries.py index 89de49c0..fa0c9e75 100644 --- a/reddit-clone/reddit/base/queries.py +++ b/reddit-clone/reddit/base/queries.py @@ -1,13 +1,13 @@ from typing import Optional -from strawberry import field, type, ID +import strawberry from strawberry.types import Info from reddit.base.types import NodeType -@type +@strawberry.type class BaseQueries: - @field(name="node", description="Fetches an object given its ID.") - def resolve_node(self, info: Info, id: ID) -> Optional[NodeType]: + @strawberry.field(name="node", description="Fetches an object given its ID.") + def resolve_node(self, info: Info, id: strawberry.ID) -> Optional[NodeType]: return NodeType.get_node_from_global_id(info=info, global_id=id) diff --git a/reddit-clone/reddit/base/types.py b/reddit-clone/reddit/base/types.py index 10b28034..4eef0812 100644 --- a/reddit-clone/reddit/base/types.py +++ b/reddit-clone/reddit/base/types.py @@ -2,14 +2,14 @@ from typing import Optional, Type -from strawberry import interface, field, ID +import strawberry from strawberry.types import Info from graphql_relay import from_global_id, to_global_id -@interface(name="Node", description="An object with an ID.") +@strawberry.interface(name="Node", description="An object with an ID.") class NodeType: - id: ID = field( + id: strawberry.ID = strawberry.field( description=""" ID of the object. """ diff --git a/reddit-clone/reddit/comments/types.py b/reddit-clone/reddit/comments/types.py index 13a173dc..0f725773 100644 --- a/reddit-clone/reddit/comments/types.py +++ b/reddit-clone/reddit/comments/types.py @@ -2,7 +2,7 @@ from typing import List, Optional -from strawberry import type, field +import strawberry from strawberry.types import Info from sqlalchemy import select @@ -11,27 +11,27 @@ from reddit.database import get_session -@type(name="Comment") +@strawberry.type(name="Comment") class CommentType(NodeType): - content: str = field( + content: str = strawberry.field( description=""" The content of the comment. """ ) - votes: int = field( + votes: int = strawberry.field( description=""" The votes the comment has. """ ) - user_id: Optional[int] = field( + user_id: Optional[int] = strawberry.field( description=""" The owner ID of the comment. """ ) - replies: List[CommentType] = field( + replies: List[CommentType] = strawberry.field( description=""" The replies for the comment. """ diff --git a/reddit-clone/reddit/posts/types.py b/reddit-clone/reddit/posts/types.py index 450c3552..b3901df1 100644 --- a/reddit-clone/reddit/posts/types.py +++ b/reddit-clone/reddit/posts/types.py @@ -2,7 +2,7 @@ from typing import List, Optional -from strawberry import type, field +import strawberry from strawberry.types import Info from sqlalchemy import select @@ -12,51 +12,51 @@ from reddit.database import get_session -@type(name="Post") +@strawberry.type(name="Post") class PostType(NodeType): - title: str = field( + title: str = strawberry.field( description=""" The title of the post. """ ) - text: Optional[str] = field( + text: Optional[str] = strawberry.field( description=""" The text for the post. """ ) - link: Optional[str] = field( + link: Optional[str] = strawberry.field( description=""" The link of the post. """ ) - thumbnail: Optional[str] = field( + thumbnail: Optional[str] = strawberry.field( description=""" The thumbnail URL of the post. """ ) - user_id: int = field( + user_id: int = strawberry.field( description=""" The owner ID of the post. """ ) - subreddit_id: int = field( + subreddit_id: int = strawberry.field( description=""" The subreddit ID of the post. """ ) - votes: int = field( + votes: int = strawberry.field( description=""" The votes the post has. """ ) - comments: List[CommentType] = field( + comments: List[CommentType] = strawberry.field( description=""" The comments for the post. """ diff --git a/reddit-clone/reddit/schema.py b/reddit-clone/reddit/schema.py index eec8017a..fdd15fae 100644 --- a/reddit-clone/reddit/schema.py +++ b/reddit-clone/reddit/schema.py @@ -1,11 +1,11 @@ -from strawberry import type, Schema +import strawberry from reddit.base.queries import BaseQueries -@type +@strawberry.type class Query(BaseQueries): pass -schema = Schema(query=Query) +schema = strawberry.Schema(query=Query) diff --git a/reddit-clone/reddit/subreddits/types.py b/reddit-clone/reddit/subreddits/types.py index 6fea60af..f6c57fb3 100644 --- a/reddit-clone/reddit/subreddits/types.py +++ b/reddit-clone/reddit/subreddits/types.py @@ -2,7 +2,7 @@ from typing import List, Optional -from strawberry import type, field +import strawberry from strawberry.types import Info from sqlalchemy import select @@ -12,40 +12,40 @@ from reddit.database import get_session -@type(name="Subreddit") +@strawberry.type(name="Subreddit") class SubredditType(NodeType): - name: str = field( + name: str = strawberry.field( description=""" The name of the subreddit. """ ) - description: str = field( + description: str = strawberry.field( description=""" The description of the subreddit. """ ) - admin_id: int = field( + admin_id: int = strawberry.field( description=""" The owner ID of the subreddit. """ ) # TODO: make status an enum - status: int = field( + status: int = strawberry.field( description=""" The status of the subreddit. """ ) - icon: str = field( + icon: str = strawberry.field( description=""" The icon URL of the subreddit. """ ) - posts: List[PostType] = field( + posts: List[PostType] = strawberry.field( description=""" The posts for the subreddit. """ diff --git a/reddit-clone/reddit/users/types.py b/reddit-clone/reddit/users/types.py index bf41b74b..c5973d67 100644 --- a/reddit-clone/reddit/users/types.py +++ b/reddit-clone/reddit/users/types.py @@ -2,7 +2,7 @@ from typing import List, Optional -from strawberry import type, field +import strawberry from strawberry.types import Info from sqlalchemy import select @@ -14,33 +14,33 @@ from reddit.database import get_session -@type(name="User") +@strawberry.type(name="User") class UserType(NodeType): - username: str = field( + username: str = strawberry.field( description=""" The username of the user. """ ) - avatar: str = field( + avatar: str = strawberry.field( description=""" The avatar URL of the user. """ ) - posts: List[PostType] = field( + posts: List[PostType] = strawberry.field( description=""" The posts for the user. """ ) - subreddits: List[SubredditType] = field( + subreddits: List[SubredditType] = strawberry.field( description=""" The subreddits the user is in. """ ) - comments: List[CommentType] = field( + comments: List[CommentType] = strawberry.field( description=""" The comments for the user. """ From f9c4df030cb8cef143eb398654b715443cd04a1e Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Wed, 6 Oct 2021 16:01:10 +0530 Subject: [PATCH 047/150] update interface checking --- reddit-clone/reddit/base/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reddit-clone/reddit/base/types.py b/reddit-clone/reddit/base/types.py index 4eef0812..46902997 100644 --- a/reddit-clone/reddit/base/types.py +++ b/reddit-clone/reddit/base/types.py @@ -35,7 +35,7 @@ def get_node_from_global_id( assert schema_type == only_type # We make sure the ObjectType implements the "Node" interface - if cls not in schema_type.interfaces: + if cls not in schema_type._type_definition.interfaces: raise Exception( f'ObjectType "{_type}" does not implement the "{cls}" interface.' ) From 5379890b7275dabc602e4889f4ac03a6b191d709 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sat, 9 Oct 2021 16:13:59 +0530 Subject: [PATCH 048/150] update typehints --- reddit-clone/reddit/database.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reddit-clone/reddit/database.py b/reddit-clone/reddit/database.py index aa2cc340..03c74a2e 100644 --- a/reddit-clone/reddit/database.py +++ b/reddit-clone/reddit/database.py @@ -1,5 +1,5 @@ from contextlib import asynccontextmanager -from typing import AsyncIterator +from typing import AsyncGenerator from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine @@ -13,7 +13,7 @@ @asynccontextmanager -async def get_session() -> AsyncIterator[AsyncSession]: +async def get_session() -> AsyncGenerator[AsyncSession, None]: """ Gets a session instance. From 63e706444f773bf2c55f7cc6bc6e51f18cc692b0 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sat, 9 Oct 2021 16:54:02 +0530 Subject: [PATCH 049/150] add user mutations and queries --- reddit-clone/reddit/base/queries.py | 2 +- reddit-clone/reddit/schema.py | 13 +++++-- reddit-clone/reddit/users/mutations.py | 9 +++++ .../reddit/users/mutations/__init__.py | 25 +++++++++++++ .../reddit/users/mutations/authenticate.py | 30 ++++++++++++++++ .../reddit/users/mutations/avatar_remove.py | 24 +++++++++++++ .../reddit/users/mutations/email_change.py | 30 ++++++++++++++++ .../users/mutations/email_change_request.py | 35 +++++++++++++++++++ .../reddit/users/mutations/password_reset.py | 31 ++++++++++++++++ .../users/mutations/password_reset_request.py | 32 +++++++++++++++++ .../reddit/users/mutations/user_create.py | 31 ++++++++++++++++ .../reddit/users/mutations/user_deactivate.py | 31 ++++++++++++++++ .../reddit/users/mutations/user_update.py | 31 ++++++++++++++++ reddit-clone/reddit/users/queries.py | 24 +++++++++++++ 14 files changed, 344 insertions(+), 4 deletions(-) create mode 100644 reddit-clone/reddit/users/mutations.py create mode 100644 reddit-clone/reddit/users/mutations/__init__.py create mode 100644 reddit-clone/reddit/users/mutations/authenticate.py create mode 100644 reddit-clone/reddit/users/mutations/avatar_remove.py create mode 100644 reddit-clone/reddit/users/mutations/email_change.py create mode 100644 reddit-clone/reddit/users/mutations/email_change_request.py create mode 100644 reddit-clone/reddit/users/mutations/password_reset.py create mode 100644 reddit-clone/reddit/users/mutations/password_reset_request.py create mode 100644 reddit-clone/reddit/users/mutations/user_create.py create mode 100644 reddit-clone/reddit/users/mutations/user_deactivate.py create mode 100644 reddit-clone/reddit/users/mutations/user_update.py create mode 100644 reddit-clone/reddit/users/queries.py diff --git a/reddit-clone/reddit/base/queries.py b/reddit-clone/reddit/base/queries.py index fa0c9e75..c209cd9e 100644 --- a/reddit-clone/reddit/base/queries.py +++ b/reddit-clone/reddit/base/queries.py @@ -7,7 +7,7 @@ @strawberry.type -class BaseQueries: +class BaseQuery: @strawberry.field(name="node", description="Fetches an object given its ID.") def resolve_node(self, info: Info, id: strawberry.ID) -> Optional[NodeType]: return NodeType.get_node_from_global_id(info=info, global_id=id) diff --git a/reddit-clone/reddit/schema.py b/reddit-clone/reddit/schema.py index fdd15fae..48d4e8ae 100644 --- a/reddit-clone/reddit/schema.py +++ b/reddit-clone/reddit/schema.py @@ -1,11 +1,18 @@ import strawberry -from reddit.base.queries import BaseQueries +from reddit.base.queries import BaseQuery +from reddit.users.queries import UserQuery +from reddit.users.mutations import UserMutation @strawberry.type -class Query(BaseQueries): +class Query(BaseQuery, UserQuery): pass -schema = strawberry.Schema(query=Query) +@strawberry.type +class Mutation(UserMutation): + pass + + +schema = strawberry.Schema(query=Query, mutation=Mutation) diff --git a/reddit-clone/reddit/users/mutations.py b/reddit-clone/reddit/users/mutations.py new file mode 100644 index 00000000..c5e7232c --- /dev/null +++ b/reddit-clone/reddit/users/mutations.py @@ -0,0 +1,9 @@ +import strawberry +from strawberry.types import Info + + +@strawberry.type +class UserMutations: + @strawberry.mutation(description="Logs the current user out.") + async def unauthenticate(self, info: Info, *args): + pass diff --git a/reddit-clone/reddit/users/mutations/__init__.py b/reddit-clone/reddit/users/mutations/__init__.py new file mode 100644 index 00000000..8af771f4 --- /dev/null +++ b/reddit-clone/reddit/users/mutations/__init__.py @@ -0,0 +1,25 @@ +from strawberry.tools import create_type + +from .authenticate import authenticate +from .avatar_remove import avatar_remove +from .email_change_request import email_change_request +from .email_change import email_change +from .password_reset_request import password_reset_request +from .password_reset import password_reset +from .user_create import user_create +from .user_deactivate import user_deactivate +from .user_update import user_update + +UserMutation = create_type( + fields=( + authenticate, + avatar_remove, + email_change_request, + email_change, + password_reset_request, + password_reset, + user_create, + user_deactivate, + user_update, + ) +) diff --git a/reddit-clone/reddit/users/mutations/authenticate.py b/reddit-clone/reddit/users/mutations/authenticate.py new file mode 100644 index 00000000..5d1f03fe --- /dev/null +++ b/reddit-clone/reddit/users/mutations/authenticate.py @@ -0,0 +1,30 @@ +import strawberry +from strawberry.types import Info + +from reddit.users.types import UserType + + +@strawberry.type +class AuthenticateInput: + username: str + password: str + + +@strawberry.type +class AuthenticateSuccess: + user: UserType + + +@strawberry.type +class AuthenticateError: + error: str + + +AuthenticateResult = strawberry.union( + name="AuthenticateResult", types=(AuthenticateSuccess, AuthenticateError) +) + + +@strawberry.mutation(description="Logs the current user in.") +async def authenticate(info: Info, input: AuthenticateInput) -> AuthenticateResult: + pass diff --git a/reddit-clone/reddit/users/mutations/avatar_remove.py b/reddit-clone/reddit/users/mutations/avatar_remove.py new file mode 100644 index 00000000..cfcf9a50 --- /dev/null +++ b/reddit-clone/reddit/users/mutations/avatar_remove.py @@ -0,0 +1,24 @@ +import strawberry +from strawberry.types import Info + +from reddit.users.types import UserType + + +@strawberry.type +class AvatarRemoveSuccess: + user: UserType + + +@strawberry.type +class AvatarRemoveError: + error: str + + +AvatarRemoveResult = strawberry.union( + name="AvatarRemoveResult", types=(AvatarRemoveSuccess, AvatarRemoveError) +) + + +@strawberry.mutation(description="Removes the current user's avatar.") +async def avatar_remove(info: Info) -> AvatarRemoveResult: + pass diff --git a/reddit-clone/reddit/users/mutations/email_change.py b/reddit-clone/reddit/users/mutations/email_change.py new file mode 100644 index 00000000..f8b8bd50 --- /dev/null +++ b/reddit-clone/reddit/users/mutations/email_change.py @@ -0,0 +1,30 @@ +import strawberry +from strawberry.types import Info + +from reddit.users.types import UserType + + +@strawberry.type +class EmailChangeInput: + email: str + change_code: str + + +@strawberry.type +class EmailChangeSuccess: + user: UserType + + +@strawberry.type +class EmailChangeError: + error: str + + +EmailChangeResult = strawberry.union( + name="EmailChangeResult", types=(EmailChangeSuccess, EmailChangeError) +) + + +@strawberry.mutation(description="Changes the email for associated user.") +async def email_change(info: Info, input: EmailChangeInput) -> EmailChangeResult: + pass diff --git a/reddit-clone/reddit/users/mutations/email_change_request.py b/reddit-clone/reddit/users/mutations/email_change_request.py new file mode 100644 index 00000000..9d0dc9d9 --- /dev/null +++ b/reddit-clone/reddit/users/mutations/email_change_request.py @@ -0,0 +1,35 @@ +import strawberry +from strawberry.types import Info + +from reddit.users.types import UserType + + +@strawberry.type +class EmailChangeRequestInput: + email: str + password: str + + +@strawberry.type +class EmailChangeRequestSuccess: + user: UserType + + +@strawberry.type +class EmailChangeRequestError: + error: str + + +EmailChangeRequestResult = strawberry.union( + name="EmailChangeRequestResult", + types=(EmailChangeRequestSuccess, EmailChangeRequestError), +) + + +@strawberry.mutation( + description="Sends an email change code to the given email address." +) +async def email_change_request( + info: Info, input: EmailChangeRequestInput +) -> EmailChangeRequestResult: + pass diff --git a/reddit-clone/reddit/users/mutations/password_reset.py b/reddit-clone/reddit/users/mutations/password_reset.py new file mode 100644 index 00000000..6637e4ca --- /dev/null +++ b/reddit-clone/reddit/users/mutations/password_reset.py @@ -0,0 +1,31 @@ +import strawberry +from strawberry.types import Info + +from reddit.users.types import UserType + + +@strawberry.type +class PasswordResetInput: + password: str + reset_code: str + email: str + + +@strawberry.type +class PasswordResetSuccess: + user: UserType + + +@strawberry.type +class PasswordResetError: + error: str + + +PasswordResetResult = strawberry.union( + name="PasswordResetResult", types=(PasswordResetSuccess, PasswordResetError) +) + + +@strawberry.mutation(description="Resets the password for the user account.") +async def password_reset(info: Info, input: PasswordResetInput) -> PasswordResetResult: + pass diff --git a/reddit-clone/reddit/users/mutations/password_reset_request.py b/reddit-clone/reddit/users/mutations/password_reset_request.py new file mode 100644 index 00000000..1b239abf --- /dev/null +++ b/reddit-clone/reddit/users/mutations/password_reset_request.py @@ -0,0 +1,32 @@ +import strawberry +from strawberry.types import Info + +from reddit.users.types import UserType + + +@strawberry.type +class PasswordResetRequestInput: + email: str + + +@strawberry.type +class PasswordResetRequestSuccess: + user: UserType + + +@strawberry.type +class PasswordResetRequestError: + error: str + + +PasswordResetRequestResult = strawberry.union( + name="PasswordResetRequestResult", + types=(PasswordResetRequestSuccess, PasswordResetRequestError), +) + + +@strawberry.mutation(description="Sends a password reset code to the given email.") +async def password_reset_request( + info: Info, input: PasswordResetRequestInput +) -> PasswordResetRequestResult: + pass diff --git a/reddit-clone/reddit/users/mutations/user_create.py b/reddit-clone/reddit/users/mutations/user_create.py new file mode 100644 index 00000000..0c105bf7 --- /dev/null +++ b/reddit-clone/reddit/users/mutations/user_create.py @@ -0,0 +1,31 @@ +import strawberry +from strawberry.types import Info + +from reddit.users.types import UserType + + +@strawberry.type +class UserCreateInput: + email: str + username: str + password: str + + +@strawberry.type +class UserCreateSuccess: + user: UserType + + +@strawberry.type +class UserCreateError: + error: str + + +UserCreateResult = strawberry.union( + name="UserCreateResult", types=(UserCreateSuccess, UserCreateError) +) + + +@strawberry.mutation(description="Creates a new user.") +async def user_create(info: Info, input: UserCreateInput) -> UserCreateResult: + pass diff --git a/reddit-clone/reddit/users/mutations/user_deactivate.py b/reddit-clone/reddit/users/mutations/user_deactivate.py new file mode 100644 index 00000000..3ca674db --- /dev/null +++ b/reddit-clone/reddit/users/mutations/user_deactivate.py @@ -0,0 +1,31 @@ +import strawberry +from strawberry.types import Info + +from reddit.users.types import UserType + + +@strawberry.type +class UserDeactivateInput: + password: str + + +@strawberry.type +class UserDeactivateSuccess: + user: UserType + + +@strawberry.type +class UserDeactivateError: + error: str + + +UserDeactivateResult = strawberry.union( + name="UserDeactivateResult", types=(UserDeactivateSuccess, UserDeactivateError) +) + + +@strawberry.mutation(description="Deactivates the current user.") +async def user_deactivate( + info: Info, input: UserDeactivateInput +) -> UserDeactivateResult: + pass diff --git a/reddit-clone/reddit/users/mutations/user_update.py b/reddit-clone/reddit/users/mutations/user_update.py new file mode 100644 index 00000000..8c7cac4d --- /dev/null +++ b/reddit-clone/reddit/users/mutations/user_update.py @@ -0,0 +1,31 @@ +import strawberry +from strawberry.types import Info + +from reddit.users.types import UserType + + +@strawberry.type +class UserUpdateInput: + email: str + username: str + password: str + + +@strawberry.type +class UserUpdateSuccess: + user: UserType + + +@strawberry.type +class UserUpdateError: + error: str + + +UserUpdateResult = strawberry.union( + name="UserUpdateResult", types=(UserUpdateSuccess, UserUpdateError) +) + + +@strawberry.mutation(description="Updates the current user.") +async def user_update(info: Info, input: UserUpdateInput) -> UserUpdateResult: + pass diff --git a/reddit-clone/reddit/users/queries.py b/reddit-clone/reddit/users/queries.py new file mode 100644 index 00000000..9ef32119 --- /dev/null +++ b/reddit-clone/reddit/users/queries.py @@ -0,0 +1,24 @@ +from typing import Optional + +import strawberry +from strawberry.types import Info +from sqlalchemy import select + +from reddit.database import get_session +from reddit.users.types import UserType +from reddit.users.models import User + + +@strawberry.type +class UserQuery: + @strawberry.field(description="Gets an user by username.") + async def user(self, info: Info, username: str) -> Optional[UserType]: + query = select(User).filter_by(username=username).first() + async with get_session() as session: + user = await session.execute(query) + if user is not None: + return UserType.from_instance(user) + + @strawberry.field(description="Gets the current user.") + async def current_user(self, info: Info) -> UserType: + pass From ee6bbcf9d50cad6c5637bcc2e09d85d5534735e9 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sat, 9 Oct 2021 17:07:29 +0530 Subject: [PATCH 050/150] replace strawberry.union with typing.Union --- reddit-clone/reddit/users/mutations/authenticate.py | 6 +++--- reddit-clone/reddit/users/mutations/avatar_remove.py | 6 +++--- reddit-clone/reddit/users/mutations/email_change.py | 6 +++--- .../reddit/users/mutations/email_change_request.py | 7 +++---- reddit-clone/reddit/users/mutations/password_reset.py | 6 +++--- .../reddit/users/mutations/password_reset_request.py | 9 +++++---- reddit-clone/reddit/users/mutations/user_create.py | 6 +++--- reddit-clone/reddit/users/mutations/user_deactivate.py | 6 +++--- reddit-clone/reddit/users/mutations/user_update.py | 6 +++--- 9 files changed, 29 insertions(+), 29 deletions(-) diff --git a/reddit-clone/reddit/users/mutations/authenticate.py b/reddit-clone/reddit/users/mutations/authenticate.py index 5d1f03fe..4ba38704 100644 --- a/reddit-clone/reddit/users/mutations/authenticate.py +++ b/reddit-clone/reddit/users/mutations/authenticate.py @@ -1,3 +1,5 @@ +from typing import Union + import strawberry from strawberry.types import Info @@ -20,9 +22,7 @@ class AuthenticateError: error: str -AuthenticateResult = strawberry.union( - name="AuthenticateResult", types=(AuthenticateSuccess, AuthenticateError) -) +AuthenticateResult = Union[AuthenticateSuccess, AuthenticateError] @strawberry.mutation(description="Logs the current user in.") diff --git a/reddit-clone/reddit/users/mutations/avatar_remove.py b/reddit-clone/reddit/users/mutations/avatar_remove.py index cfcf9a50..e99272ae 100644 --- a/reddit-clone/reddit/users/mutations/avatar_remove.py +++ b/reddit-clone/reddit/users/mutations/avatar_remove.py @@ -1,3 +1,5 @@ +from typing import Union + import strawberry from strawberry.types import Info @@ -14,9 +16,7 @@ class AvatarRemoveError: error: str -AvatarRemoveResult = strawberry.union( - name="AvatarRemoveResult", types=(AvatarRemoveSuccess, AvatarRemoveError) -) +AvatarRemoveResult = Union[AvatarRemoveSuccess, AvatarRemoveError] @strawberry.mutation(description="Removes the current user's avatar.") diff --git a/reddit-clone/reddit/users/mutations/email_change.py b/reddit-clone/reddit/users/mutations/email_change.py index f8b8bd50..d1c955c3 100644 --- a/reddit-clone/reddit/users/mutations/email_change.py +++ b/reddit-clone/reddit/users/mutations/email_change.py @@ -1,3 +1,5 @@ +from typing import Union + import strawberry from strawberry.types import Info @@ -20,9 +22,7 @@ class EmailChangeError: error: str -EmailChangeResult = strawberry.union( - name="EmailChangeResult", types=(EmailChangeSuccess, EmailChangeError) -) +EmailChangeResult = Union[EmailChangeSuccess, EmailChangeError] @strawberry.mutation(description="Changes the email for associated user.") diff --git a/reddit-clone/reddit/users/mutations/email_change_request.py b/reddit-clone/reddit/users/mutations/email_change_request.py index 9d0dc9d9..d310a8be 100644 --- a/reddit-clone/reddit/users/mutations/email_change_request.py +++ b/reddit-clone/reddit/users/mutations/email_change_request.py @@ -1,3 +1,5 @@ +from typing import Union + import strawberry from strawberry.types import Info @@ -20,10 +22,7 @@ class EmailChangeRequestError: error: str -EmailChangeRequestResult = strawberry.union( - name="EmailChangeRequestResult", - types=(EmailChangeRequestSuccess, EmailChangeRequestError), -) +EmailChangeRequestResult = Union[EmailChangeRequestSuccess, EmailChangeRequestError] @strawberry.mutation( diff --git a/reddit-clone/reddit/users/mutations/password_reset.py b/reddit-clone/reddit/users/mutations/password_reset.py index 6637e4ca..012a62d8 100644 --- a/reddit-clone/reddit/users/mutations/password_reset.py +++ b/reddit-clone/reddit/users/mutations/password_reset.py @@ -1,3 +1,5 @@ +from typing import Union + import strawberry from strawberry.types import Info @@ -21,9 +23,7 @@ class PasswordResetError: error: str -PasswordResetResult = strawberry.union( - name="PasswordResetResult", types=(PasswordResetSuccess, PasswordResetError) -) +PasswordResetResult = Union[PasswordResetSuccess, PasswordResetError] @strawberry.mutation(description="Resets the password for the user account.") diff --git a/reddit-clone/reddit/users/mutations/password_reset_request.py b/reddit-clone/reddit/users/mutations/password_reset_request.py index 1b239abf..dd70821c 100644 --- a/reddit-clone/reddit/users/mutations/password_reset_request.py +++ b/reddit-clone/reddit/users/mutations/password_reset_request.py @@ -1,3 +1,5 @@ +from typing import Union + import strawberry from strawberry.types import Info @@ -19,10 +21,9 @@ class PasswordResetRequestError: error: str -PasswordResetRequestResult = strawberry.union( - name="PasswordResetRequestResult", - types=(PasswordResetRequestSuccess, PasswordResetRequestError), -) +PasswordResetRequestResult = Union[ + PasswordResetRequestSuccess, PasswordResetRequestError +] @strawberry.mutation(description="Sends a password reset code to the given email.") diff --git a/reddit-clone/reddit/users/mutations/user_create.py b/reddit-clone/reddit/users/mutations/user_create.py index 0c105bf7..2c7c2f5a 100644 --- a/reddit-clone/reddit/users/mutations/user_create.py +++ b/reddit-clone/reddit/users/mutations/user_create.py @@ -1,3 +1,5 @@ +from typing import Union + import strawberry from strawberry.types import Info @@ -21,9 +23,7 @@ class UserCreateError: error: str -UserCreateResult = strawberry.union( - name="UserCreateResult", types=(UserCreateSuccess, UserCreateError) -) +UserCreateResult = Union[UserCreateSuccess, UserCreateError] @strawberry.mutation(description="Creates a new user.") diff --git a/reddit-clone/reddit/users/mutations/user_deactivate.py b/reddit-clone/reddit/users/mutations/user_deactivate.py index 3ca674db..d675193b 100644 --- a/reddit-clone/reddit/users/mutations/user_deactivate.py +++ b/reddit-clone/reddit/users/mutations/user_deactivate.py @@ -1,3 +1,5 @@ +from typing import Union + import strawberry from strawberry.types import Info @@ -19,9 +21,7 @@ class UserDeactivateError: error: str -UserDeactivateResult = strawberry.union( - name="UserDeactivateResult", types=(UserDeactivateSuccess, UserDeactivateError) -) +UserDeactivateResult = Union[UserDeactivateSuccess, UserDeactivateError] @strawberry.mutation(description="Deactivates the current user.") diff --git a/reddit-clone/reddit/users/mutations/user_update.py b/reddit-clone/reddit/users/mutations/user_update.py index 8c7cac4d..81995ff7 100644 --- a/reddit-clone/reddit/users/mutations/user_update.py +++ b/reddit-clone/reddit/users/mutations/user_update.py @@ -1,3 +1,5 @@ +from typing import Union + import strawberry from strawberry.types import Info @@ -21,9 +23,7 @@ class UserUpdateError: error: str -UserUpdateResult = strawberry.union( - name="UserUpdateResult", types=(UserUpdateSuccess, UserUpdateError) -) +UserUpdateResult = Union[UserUpdateSuccess, UserUpdateError] @strawberry.mutation(description="Updates the current user.") From d0ba9e02ad3e8f60d57269e842dbf099f3b97f4c Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sat, 9 Oct 2021 17:14:28 +0530 Subject: [PATCH 051/150] delete mutations.py --- reddit-clone/reddit/users/mutations.py | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 reddit-clone/reddit/users/mutations.py diff --git a/reddit-clone/reddit/users/mutations.py b/reddit-clone/reddit/users/mutations.py deleted file mode 100644 index c5e7232c..00000000 --- a/reddit-clone/reddit/users/mutations.py +++ /dev/null @@ -1,9 +0,0 @@ -import strawberry -from strawberry.types import Info - - -@strawberry.type -class UserMutations: - @strawberry.mutation(description="Logs the current user out.") - async def unauthenticate(self, info: Info, *args): - pass From c09a2ad03d11e2b2ae3508c0efc4e602e42f92e5 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sat, 9 Oct 2021 17:16:29 +0530 Subject: [PATCH 052/150] mark input types correctly --- reddit-clone/reddit/users/mutations/authenticate.py | 2 +- reddit-clone/reddit/users/mutations/email_change.py | 2 +- reddit-clone/reddit/users/mutations/email_change_request.py | 2 +- reddit-clone/reddit/users/mutations/password_reset.py | 2 +- .../reddit/users/mutations/password_reset_request.py | 2 +- reddit-clone/reddit/users/mutations/user_create.py | 2 +- reddit-clone/reddit/users/mutations/user_deactivate.py | 2 +- reddit-clone/reddit/users/mutations/user_update.py | 6 +++--- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/reddit-clone/reddit/users/mutations/authenticate.py b/reddit-clone/reddit/users/mutations/authenticate.py index 4ba38704..dd6c7462 100644 --- a/reddit-clone/reddit/users/mutations/authenticate.py +++ b/reddit-clone/reddit/users/mutations/authenticate.py @@ -6,7 +6,7 @@ from reddit.users.types import UserType -@strawberry.type +@strawberry.input class AuthenticateInput: username: str password: str diff --git a/reddit-clone/reddit/users/mutations/email_change.py b/reddit-clone/reddit/users/mutations/email_change.py index d1c955c3..19595e75 100644 --- a/reddit-clone/reddit/users/mutations/email_change.py +++ b/reddit-clone/reddit/users/mutations/email_change.py @@ -6,7 +6,7 @@ from reddit.users.types import UserType -@strawberry.type +@strawberry.input class EmailChangeInput: email: str change_code: str diff --git a/reddit-clone/reddit/users/mutations/email_change_request.py b/reddit-clone/reddit/users/mutations/email_change_request.py index d310a8be..545e61a4 100644 --- a/reddit-clone/reddit/users/mutations/email_change_request.py +++ b/reddit-clone/reddit/users/mutations/email_change_request.py @@ -6,7 +6,7 @@ from reddit.users.types import UserType -@strawberry.type +@strawberry.input class EmailChangeRequestInput: email: str password: str diff --git a/reddit-clone/reddit/users/mutations/password_reset.py b/reddit-clone/reddit/users/mutations/password_reset.py index 012a62d8..46ec8bce 100644 --- a/reddit-clone/reddit/users/mutations/password_reset.py +++ b/reddit-clone/reddit/users/mutations/password_reset.py @@ -6,7 +6,7 @@ from reddit.users.types import UserType -@strawberry.type +@strawberry.input class PasswordResetInput: password: str reset_code: str diff --git a/reddit-clone/reddit/users/mutations/password_reset_request.py b/reddit-clone/reddit/users/mutations/password_reset_request.py index dd70821c..06fd88c5 100644 --- a/reddit-clone/reddit/users/mutations/password_reset_request.py +++ b/reddit-clone/reddit/users/mutations/password_reset_request.py @@ -6,7 +6,7 @@ from reddit.users.types import UserType -@strawberry.type +@strawberry.input class PasswordResetRequestInput: email: str diff --git a/reddit-clone/reddit/users/mutations/user_create.py b/reddit-clone/reddit/users/mutations/user_create.py index 2c7c2f5a..6f016c5b 100644 --- a/reddit-clone/reddit/users/mutations/user_create.py +++ b/reddit-clone/reddit/users/mutations/user_create.py @@ -6,7 +6,7 @@ from reddit.users.types import UserType -@strawberry.type +@strawberry.input class UserCreateInput: email: str username: str diff --git a/reddit-clone/reddit/users/mutations/user_deactivate.py b/reddit-clone/reddit/users/mutations/user_deactivate.py index d675193b..8cbb0810 100644 --- a/reddit-clone/reddit/users/mutations/user_deactivate.py +++ b/reddit-clone/reddit/users/mutations/user_deactivate.py @@ -6,7 +6,7 @@ from reddit.users.types import UserType -@strawberry.type +@strawberry.input class UserDeactivateInput: password: str diff --git a/reddit-clone/reddit/users/mutations/user_update.py b/reddit-clone/reddit/users/mutations/user_update.py index 81995ff7..d3b3536a 100644 --- a/reddit-clone/reddit/users/mutations/user_update.py +++ b/reddit-clone/reddit/users/mutations/user_update.py @@ -1,16 +1,16 @@ from typing import Union import strawberry +from strawberry.file_uploads import Upload from strawberry.types import Info from reddit.users.types import UserType -@strawberry.type +@strawberry.input class UserUpdateInput: - email: str username: str - password: str + avatar: Upload @strawberry.type From 005353f1d3a16edeb2e34313684c43562680383f Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sat, 9 Oct 2021 17:36:02 +0530 Subject: [PATCH 053/150] remove decorator-based field definitions --- .../reddit/users/mutations/__init__.py | 3 +- .../reddit/users/mutations/authenticate.py | 16 ++++++- .../reddit/users/mutations/avatar_remove.py | 13 +++++- .../reddit/users/mutations/email_change.py | 15 ++++++- .../users/mutations/email_change_request.py | 17 +++++-- .../reddit/users/mutations/password_reset.py | 15 ++++++- .../users/mutations/password_reset_request.py | 13 +++++- .../reddit/users/mutations/user_create.py | 13 +++++- .../reddit/users/mutations/user_deactivate.py | 13 +++++- .../reddit/users/mutations/user_update.py | 13 +++++- reddit-clone/reddit/users/queries.py | 45 +++++++++++-------- 11 files changed, 137 insertions(+), 39 deletions(-) diff --git a/reddit-clone/reddit/users/mutations/__init__.py b/reddit-clone/reddit/users/mutations/__init__.py index 8af771f4..91432cc5 100644 --- a/reddit-clone/reddit/users/mutations/__init__.py +++ b/reddit-clone/reddit/users/mutations/__init__.py @@ -11,6 +11,7 @@ from .user_update import user_update UserMutation = create_type( + name="UserMutation", fields=( authenticate, avatar_remove, @@ -21,5 +22,5 @@ user_create, user_deactivate, user_update, - ) + ), ) diff --git a/reddit-clone/reddit/users/mutations/authenticate.py b/reddit-clone/reddit/users/mutations/authenticate.py index dd6c7462..8983d5b8 100644 --- a/reddit-clone/reddit/users/mutations/authenticate.py +++ b/reddit-clone/reddit/users/mutations/authenticate.py @@ -6,6 +6,9 @@ from reddit.users.types import UserType +__all__ = ("authenticate",) + + @strawberry.input class AuthenticateInput: username: str @@ -25,6 +28,15 @@ class AuthenticateError: AuthenticateResult = Union[AuthenticateSuccess, AuthenticateError] -@strawberry.mutation(description="Logs the current user in.") -async def authenticate(info: Info, input: AuthenticateInput) -> AuthenticateResult: +async def resolve_authenticate( + info: Info, input: AuthenticateInput +) -> AuthenticateResult: pass + + +authenticate = strawberry.mutation( + resolver=resolve_authenticate, + description=""" + Logs the current user in. + """, +) diff --git a/reddit-clone/reddit/users/mutations/avatar_remove.py b/reddit-clone/reddit/users/mutations/avatar_remove.py index e99272ae..9505484f 100644 --- a/reddit-clone/reddit/users/mutations/avatar_remove.py +++ b/reddit-clone/reddit/users/mutations/avatar_remove.py @@ -5,6 +5,8 @@ from reddit.users.types import UserType +__all__ = ("avatar_remove",) + @strawberry.type class AvatarRemoveSuccess: @@ -19,6 +21,13 @@ class AvatarRemoveError: AvatarRemoveResult = Union[AvatarRemoveSuccess, AvatarRemoveError] -@strawberry.mutation(description="Removes the current user's avatar.") -async def avatar_remove(info: Info) -> AvatarRemoveResult: +async def resolve_avatar_remove(info: Info) -> AvatarRemoveResult: pass + + +avatar_remove = strawberry.mutation( + resolver=resolve_avatar_remove, + description=""" + Removes the current user's avatar. + """, +) diff --git a/reddit-clone/reddit/users/mutations/email_change.py b/reddit-clone/reddit/users/mutations/email_change.py index 19595e75..0e98bc45 100644 --- a/reddit-clone/reddit/users/mutations/email_change.py +++ b/reddit-clone/reddit/users/mutations/email_change.py @@ -5,6 +5,8 @@ from reddit.users.types import UserType +__all__ = ("email_change",) + @strawberry.input class EmailChangeInput: @@ -25,6 +27,15 @@ class EmailChangeError: EmailChangeResult = Union[EmailChangeSuccess, EmailChangeError] -@strawberry.mutation(description="Changes the email for associated user.") -async def email_change(info: Info, input: EmailChangeInput) -> EmailChangeResult: +async def resolve_email_change( + info: Info, input: EmailChangeInput +) -> EmailChangeResult: pass + + +email_change = strawberry.mutation( + resolver=resolve_email_change, + description=""" + Changes the email for associated user. + """, +) diff --git a/reddit-clone/reddit/users/mutations/email_change_request.py b/reddit-clone/reddit/users/mutations/email_change_request.py index 545e61a4..b16f3a60 100644 --- a/reddit-clone/reddit/users/mutations/email_change_request.py +++ b/reddit-clone/reddit/users/mutations/email_change_request.py @@ -6,6 +6,9 @@ from reddit.users.types import UserType +__all__ = ("email_change_request",) + + @strawberry.input class EmailChangeRequestInput: email: str @@ -25,10 +28,16 @@ class EmailChangeRequestError: EmailChangeRequestResult = Union[EmailChangeRequestSuccess, EmailChangeRequestError] -@strawberry.mutation( - description="Sends an email change code to the given email address." -) -async def email_change_request( +async def resolve_email_change_request( info: Info, input: EmailChangeRequestInput ) -> EmailChangeRequestResult: pass + + +email_change_request = strawberry.mutation( + resolver=resolve_email_change_request, + description=""" + Sends an email change code to + the given email address. + """, +) diff --git a/reddit-clone/reddit/users/mutations/password_reset.py b/reddit-clone/reddit/users/mutations/password_reset.py index 46ec8bce..985ad875 100644 --- a/reddit-clone/reddit/users/mutations/password_reset.py +++ b/reddit-clone/reddit/users/mutations/password_reset.py @@ -5,6 +5,8 @@ from reddit.users.types import UserType +__all__ = ("password_reset",) + @strawberry.input class PasswordResetInput: @@ -26,6 +28,15 @@ class PasswordResetError: PasswordResetResult = Union[PasswordResetSuccess, PasswordResetError] -@strawberry.mutation(description="Resets the password for the user account.") -async def password_reset(info: Info, input: PasswordResetInput) -> PasswordResetResult: +async def resolve_password_reset( + info: Info, input: PasswordResetInput +) -> PasswordResetResult: pass + + +password_reset = strawberry.mutation( + resolver=resolve_password_reset, + description=""" + Resets the password for the user account. + """, +) diff --git a/reddit-clone/reddit/users/mutations/password_reset_request.py b/reddit-clone/reddit/users/mutations/password_reset_request.py index 06fd88c5..a433df17 100644 --- a/reddit-clone/reddit/users/mutations/password_reset_request.py +++ b/reddit-clone/reddit/users/mutations/password_reset_request.py @@ -5,6 +5,8 @@ from reddit.users.types import UserType +__all__ = ("password_reset_request",) + @strawberry.input class PasswordResetRequestInput: @@ -26,8 +28,15 @@ class PasswordResetRequestError: ] -@strawberry.mutation(description="Sends a password reset code to the given email.") -async def password_reset_request( +async def resolve_password_reset_request( info: Info, input: PasswordResetRequestInput ) -> PasswordResetRequestResult: pass + + +password_reset_request = strawberry.mutation( + resolver=resolve_password_reset_request, + description=""" + Sends a password reset code to the given email. + """, +) diff --git a/reddit-clone/reddit/users/mutations/user_create.py b/reddit-clone/reddit/users/mutations/user_create.py index 6f016c5b..30ca3003 100644 --- a/reddit-clone/reddit/users/mutations/user_create.py +++ b/reddit-clone/reddit/users/mutations/user_create.py @@ -5,6 +5,8 @@ from reddit.users.types import UserType +__all__ = ("user_create",) + @strawberry.input class UserCreateInput: @@ -26,6 +28,13 @@ class UserCreateError: UserCreateResult = Union[UserCreateSuccess, UserCreateError] -@strawberry.mutation(description="Creates a new user.") -async def user_create(info: Info, input: UserCreateInput) -> UserCreateResult: +async def resolve_user_create(info: Info, input: UserCreateInput) -> UserCreateResult: pass + + +user_create = strawberry.mutation( + resolver=resolve_user_create, + description=""" + Creates a new user. + """, +) diff --git a/reddit-clone/reddit/users/mutations/user_deactivate.py b/reddit-clone/reddit/users/mutations/user_deactivate.py index 8cbb0810..8d339687 100644 --- a/reddit-clone/reddit/users/mutations/user_deactivate.py +++ b/reddit-clone/reddit/users/mutations/user_deactivate.py @@ -5,6 +5,8 @@ from reddit.users.types import UserType +__all__ = ("user_deactivate",) + @strawberry.input class UserDeactivateInput: @@ -24,8 +26,15 @@ class UserDeactivateError: UserDeactivateResult = Union[UserDeactivateSuccess, UserDeactivateError] -@strawberry.mutation(description="Deactivates the current user.") -async def user_deactivate( +async def resolve_user_deactivate( info: Info, input: UserDeactivateInput ) -> UserDeactivateResult: pass + + +user_deactivate = strawberry.mutation( + resolver=resolve_user_deactivate, + description=""" + Deactivates the current user. + """, +) diff --git a/reddit-clone/reddit/users/mutations/user_update.py b/reddit-clone/reddit/users/mutations/user_update.py index d3b3536a..3740cebb 100644 --- a/reddit-clone/reddit/users/mutations/user_update.py +++ b/reddit-clone/reddit/users/mutations/user_update.py @@ -6,6 +6,8 @@ from reddit.users.types import UserType +__all__ = ("user_update",) + @strawberry.input class UserUpdateInput: @@ -26,6 +28,13 @@ class UserUpdateError: UserUpdateResult = Union[UserUpdateSuccess, UserUpdateError] -@strawberry.mutation(description="Updates the current user.") -async def user_update(info: Info, input: UserUpdateInput) -> UserUpdateResult: +async def resolve_user_update(info: Info, input: UserUpdateInput) -> UserUpdateResult: pass + + +user_update = strawberry.mutation( + resolver=resolve_user_update, + description=""" + Updates the current user. + """, +) diff --git a/reddit-clone/reddit/users/queries.py b/reddit-clone/reddit/users/queries.py index 9ef32119..a6d1f332 100644 --- a/reddit-clone/reddit/users/queries.py +++ b/reddit-clone/reddit/users/queries.py @@ -1,24 +1,33 @@ from typing import Optional import strawberry +from strawberry.tools import create_type from strawberry.types import Info -from sqlalchemy import select -from reddit.database import get_session from reddit.users.types import UserType -from reddit.users.models import User - - -@strawberry.type -class UserQuery: - @strawberry.field(description="Gets an user by username.") - async def user(self, info: Info, username: str) -> Optional[UserType]: - query = select(User).filter_by(username=username).first() - async with get_session() as session: - user = await session.execute(query) - if user is not None: - return UserType.from_instance(user) - - @strawberry.field(description="Gets the current user.") - async def current_user(self, info: Info) -> UserType: - pass + + +async def resolve_user(info: Info, username: str) -> Optional[UserType]: + pass + + +user = strawberry.field( + resolver=resolve_user, + description=""" + Get an user by username. + """, +) + + +async def resolve_current_user(info: Info) -> UserType: + pass + + +current_user = strawberry.field( + resolver=resolve_current_user, + description=""" + Gets the current user. + """, +) + +UserQuery = create_type(name="UserQuery", fields=(user, current_user)) From 234e1b98e443c147b4cf261cdfb97733302c0a1d Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sat, 9 Oct 2021 17:39:49 +0530 Subject: [PATCH 054/150] update field descriptions --- reddit-clone/reddit/users/mutations/email_change.py | 3 ++- reddit-clone/reddit/users/mutations/password_reset.py | 3 ++- reddit-clone/reddit/users/mutations/password_reset_request.py | 3 ++- reddit-clone/reddit/users/queries.py | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/reddit-clone/reddit/users/mutations/email_change.py b/reddit-clone/reddit/users/mutations/email_change.py index 0e98bc45..b08f6036 100644 --- a/reddit-clone/reddit/users/mutations/email_change.py +++ b/reddit-clone/reddit/users/mutations/email_change.py @@ -36,6 +36,7 @@ async def resolve_email_change( email_change = strawberry.mutation( resolver=resolve_email_change, description=""" - Changes the email for associated user. + Changes the email for the user account + associated with the given email. """, ) diff --git a/reddit-clone/reddit/users/mutations/password_reset.py b/reddit-clone/reddit/users/mutations/password_reset.py index 985ad875..16e25ebf 100644 --- a/reddit-clone/reddit/users/mutations/password_reset.py +++ b/reddit-clone/reddit/users/mutations/password_reset.py @@ -37,6 +37,7 @@ async def resolve_password_reset( password_reset = strawberry.mutation( resolver=resolve_password_reset, description=""" - Resets the password for the user account. + Resets the password for the user account + associated with the given email. """, ) diff --git a/reddit-clone/reddit/users/mutations/password_reset_request.py b/reddit-clone/reddit/users/mutations/password_reset_request.py index a433df17..6e2104ef 100644 --- a/reddit-clone/reddit/users/mutations/password_reset_request.py +++ b/reddit-clone/reddit/users/mutations/password_reset_request.py @@ -37,6 +37,7 @@ async def resolve_password_reset_request( password_reset_request = strawberry.mutation( resolver=resolve_password_reset_request, description=""" - Sends a password reset code to the given email. + Sends a password reset code to the + provided email, if it actually exists. """, ) diff --git a/reddit-clone/reddit/users/queries.py b/reddit-clone/reddit/users/queries.py index a6d1f332..50fe6e13 100644 --- a/reddit-clone/reddit/users/queries.py +++ b/reddit-clone/reddit/users/queries.py @@ -14,7 +14,7 @@ async def resolve_user(info: Info, username: str) -> Optional[UserType]: user = strawberry.field( resolver=resolve_user, description=""" - Get an user by username. + Gets an user by username. """, ) From 0e75b362f724ebc57d0114dbc8c2ddb23917eca4 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sat, 9 Oct 2021 17:46:36 +0530 Subject: [PATCH 055/150] update BaseQuery --- reddit-clone/reddit/base/queries.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/reddit-clone/reddit/base/queries.py b/reddit-clone/reddit/base/queries.py index c209cd9e..05bdc83c 100644 --- a/reddit-clone/reddit/base/queries.py +++ b/reddit-clone/reddit/base/queries.py @@ -1,13 +1,22 @@ from typing import Optional import strawberry +from strawberry.tools import create_type from strawberry.types import Info from reddit.base.types import NodeType -@strawberry.type -class BaseQuery: - @strawberry.field(name="node", description="Fetches an object given its ID.") - def resolve_node(self, info: Info, id: strawberry.ID) -> Optional[NodeType]: - return NodeType.get_node_from_global_id(info=info, global_id=id) +def resolve_node(info: Info, id: strawberry.ID) -> Optional[NodeType]: + return NodeType.get_node_from_global_id(info=info, global_id=id) + + +node = strawberry.field( + resolver=resolve_node, + description=""" + Fetches an object given its ID. + """, +) + + +BaseQuery = create_type(name="BaseQuery", fields=(node,)) From e6637fa58dd2ef19b2d3a1b73bb69b93274078c4 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sat, 9 Oct 2021 17:58:42 +0530 Subject: [PATCH 056/150] add a few email templates --- .../templates/emails/activate_email.html | 11 + .../reddit/templates/emails/base_email.html | 233 ++++++++++++++++++ .../templates/emails/password_reset.html | 14 ++ 3 files changed, 258 insertions(+) create mode 100644 reddit-clone/reddit/templates/emails/activate_email.html create mode 100644 reddit-clone/reddit/templates/emails/base_email.html create mode 100644 reddit-clone/reddit/templates/emails/password_reset.html diff --git a/reddit-clone/reddit/templates/emails/activate_email.html b/reddit-clone/reddit/templates/emails/activate_email.html new file mode 100644 index 00000000..727af27c --- /dev/null +++ b/reddit-clone/reddit/templates/emails/activate_email.html @@ -0,0 +1,11 @@ +{% extends "emails/base_email.html" %} {% block title %} Confirm Email {% +endblock %} {% block content %} +

+ Someone tried to sign up for a {{ config.SITE_NAME }} account
+ with {{ email }}. If this was you,
+ enter this confirmation code in the app: +

+

{{ code }}

+{% endblock %} {% block content_more %} +
If this wasn't you, this email can be safely ignored.
+{% endblock %} diff --git a/reddit-clone/reddit/templates/emails/base_email.html b/reddit-clone/reddit/templates/emails/base_email.html new file mode 100644 index 00000000..31530f06 --- /dev/null +++ b/reddit-clone/reddit/templates/emails/base_email.html @@ -0,0 +1,233 @@ + + + {% block title %}{% endblock %} + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + + +
+ + + + +
+ {% block content %}{% endblock %} +
+
+ + + + +
+ {% block content_more %}{% endblock %} +
+
+
+
+ + + + +
+ © {{ now.year }} + {{ config.SITE_NAME }}. All rights reserved. +
+
+ + diff --git a/reddit-clone/reddit/templates/emails/password_reset.html b/reddit-clone/reddit/templates/emails/password_reset.html new file mode 100644 index 00000000..5a759fe8 --- /dev/null +++ b/reddit-clone/reddit/templates/emails/password_reset.html @@ -0,0 +1,14 @@ +{% extends "emails/base_email.html" %} {% block title %} Password Reset {% +endblock %} {% block content %} +

Hey {{ user.username }}!

+

+ We heard that you lost your password. Sorry about that!
+ But don’t worry! Enter the following code to reset your password.
+ +

{{ code }}
+

+{% endblock %} {% block content_more %} +
+ If this wasn't you, this email can be safely ignored. +
+{% endblock %} From 628ae0ee83efedc11119b7c2cde665042c608176 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sat, 9 Oct 2021 19:08:48 +0530 Subject: [PATCH 057/150] add skeleton for subreddit mutations and queries --- reddit-clone/reddit/base/queries.py | 2 +- reddit-clone/reddit/base/types.py | 2 +- reddit-clone/reddit/schema.py | 6 ++- .../reddit/subreddits/mutations/__init__.py | 3 ++ reddit-clone/reddit/subreddits/queries.py | 21 ++++++++++ reddit-clone/reddit/subreddits/types.py | 7 ++++ .../reddit/users/mutations/__init__.py | 2 + .../reddit/users/mutations/user_block.py | 39 +++++++++++++++++++ reddit-clone/reddit/users/types.py | 6 +++ 9 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 reddit-clone/reddit/subreddits/mutations/__init__.py create mode 100644 reddit-clone/reddit/subreddits/queries.py create mode 100644 reddit-clone/reddit/users/mutations/user_block.py diff --git a/reddit-clone/reddit/base/queries.py b/reddit-clone/reddit/base/queries.py index 05bdc83c..b70f4744 100644 --- a/reddit-clone/reddit/base/queries.py +++ b/reddit-clone/reddit/base/queries.py @@ -8,7 +8,7 @@ def resolve_node(info: Info, id: strawberry.ID) -> Optional[NodeType]: - return NodeType.get_node_from_global_id(info=info, global_id=id) + return NodeType.resolve(info=info, global_id=id) node = strawberry.field( diff --git a/reddit-clone/reddit/base/types.py b/reddit-clone/reddit/base/types.py index 46902997..7d3096e7 100644 --- a/reddit-clone/reddit/base/types.py +++ b/reddit-clone/reddit/base/types.py @@ -16,7 +16,7 @@ class NodeType: ) @classmethod - def get_node_from_global_id( + def resolve( cls, info: Info, global_id: str, only_type=None ) -> Optional[Type[NodeType]]: try: diff --git a/reddit-clone/reddit/schema.py b/reddit-clone/reddit/schema.py index 48d4e8ae..ef92bc16 100644 --- a/reddit-clone/reddit/schema.py +++ b/reddit-clone/reddit/schema.py @@ -1,17 +1,19 @@ import strawberry from reddit.base.queries import BaseQuery +from reddit.subreddits.queries import SubredditQuery from reddit.users.queries import UserQuery +from reddit.subreddits.mutations import SubredditMutation from reddit.users.mutations import UserMutation @strawberry.type -class Query(BaseQuery, UserQuery): +class Query(BaseQuery, UserQuery, SubredditQuery): pass @strawberry.type -class Mutation(UserMutation): +class Mutation(UserMutation, SubredditMutation): pass diff --git a/reddit-clone/reddit/subreddits/mutations/__init__.py b/reddit-clone/reddit/subreddits/mutations/__init__.py new file mode 100644 index 00000000..2ab7caaf --- /dev/null +++ b/reddit-clone/reddit/subreddits/mutations/__init__.py @@ -0,0 +1,3 @@ +from strawberry.tools import create_type + +SubredditMutation = create_type(name="SubredditMutation", fields=()) diff --git a/reddit-clone/reddit/subreddits/queries.py b/reddit-clone/reddit/subreddits/queries.py new file mode 100644 index 00000000..77998552 --- /dev/null +++ b/reddit-clone/reddit/subreddits/queries.py @@ -0,0 +1,21 @@ +from typing import List + +import strawberry +from strawberry.tools import create_type +from strawberry.types import Info + +from reddit.subreddits.types import SubredditType + + +async def resolve_subreddits(info: Info) -> List[SubredditType]: + pass + + +subreddits = strawberry.field( + resolver=resolve_subreddits, + description=""" + Gets the available subreddits. + """, +) + +SubredditQuery = create_type(name="SubredditQuery", fields=(subreddits,)) diff --git a/reddit-clone/reddit/subreddits/types.py b/reddit-clone/reddit/subreddits/types.py index f6c57fb3..6d3ca010 100644 --- a/reddit-clone/reddit/subreddits/types.py +++ b/reddit-clone/reddit/subreddits/types.py @@ -32,6 +32,13 @@ class SubredditType(NodeType): """ ) + submit_text: str = strawberry.field( + description=""" + The text set by the subreddit moderators, intended + to be displayed on the submission form. + """ + ) + # TODO: make status an enum status: int = strawberry.field( description=""" diff --git a/reddit-clone/reddit/users/mutations/__init__.py b/reddit-clone/reddit/users/mutations/__init__.py index 91432cc5..83a8adab 100644 --- a/reddit-clone/reddit/users/mutations/__init__.py +++ b/reddit-clone/reddit/users/mutations/__init__.py @@ -6,6 +6,7 @@ from .email_change import email_change from .password_reset_request import password_reset_request from .password_reset import password_reset +from .user_block import user_block from .user_create import user_create from .user_deactivate import user_deactivate from .user_update import user_update @@ -19,6 +20,7 @@ email_change, password_reset_request, password_reset, + user_block, user_create, user_deactivate, user_update, diff --git a/reddit-clone/reddit/users/mutations/user_block.py b/reddit-clone/reddit/users/mutations/user_block.py new file mode 100644 index 00000000..b3b7b6bc --- /dev/null +++ b/reddit-clone/reddit/users/mutations/user_block.py @@ -0,0 +1,39 @@ +from typing import Union + +import strawberry +from strawberry.types import Info + +from reddit.users.types import UserType + + +__all__ = ("user_block",) + + +@strawberry.input +class UserBlockInput: + user_id: strawberry.ID + + +@strawberry.type +class UserBlockSuccess: + user: UserType + + +@strawberry.type +class UserBlockError: + error: str + + +UserBlockResult = Union[UserBlockSuccess, UserBlockError] + + +async def resolve_user_block(info: Info, input: UserBlockInput) -> UserBlockResult: + pass + + +user_block = strawberry.mutation( + resolver=resolve_user_block, + description=""" + Blocks an user account. + """, +) diff --git a/reddit-clone/reddit/users/types.py b/reddit-clone/reddit/users/types.py index c5973d67..266c093b 100644 --- a/reddit-clone/reddit/users/types.py +++ b/reddit-clone/reddit/users/types.py @@ -28,6 +28,12 @@ class UserType(NodeType): """ ) + blocked: List[UserType] = strawberry.field( + description=""" + The accounts blocked by the user. + """ + ) + posts: List[PostType] = strawberry.field( description=""" The posts for the user. From af11ed29c87d6afbc50f1ade6d3f6641202484d8 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sat, 9 Oct 2021 19:27:11 +0530 Subject: [PATCH 058/150] remove from_instance before we finalize data models --- reddit-clone/reddit/comments/types.py | 8 +--- reddit-clone/reddit/posts/types.py | 12 +----- .../reddit/subreddits/mutations/__init__.py | 4 +- .../subreddits/mutations/subreddit_create.py | 41 +++++++++++++++++++ reddit-clone/reddit/subreddits/types.py | 30 ++++---------- reddit-clone/reddit/users/types.py | 9 +--- 6 files changed, 55 insertions(+), 49 deletions(-) create mode 100644 reddit-clone/reddit/subreddits/mutations/subreddit_create.py diff --git a/reddit-clone/reddit/comments/types.py b/reddit-clone/reddit/comments/types.py index 0f725773..e5cc7697 100644 --- a/reddit-clone/reddit/comments/types.py +++ b/reddit-clone/reddit/comments/types.py @@ -50,10 +50,4 @@ async def resolve_node(cls, info: Info, comment_id: str) -> Optional[CommentType @classmethod def from_instance(cls, instance: Comment) -> CommentType: - return CommentType( - id=instance.id, - content=instance.content, - votes=instance.votes, - user_id=instance.user_id, - replies=instance.replies, - ) + pass diff --git a/reddit-clone/reddit/posts/types.py b/reddit-clone/reddit/posts/types.py index b3901df1..c9a1cb30 100644 --- a/reddit-clone/reddit/posts/types.py +++ b/reddit-clone/reddit/posts/types.py @@ -75,14 +75,4 @@ async def resolve_node(cls, info: Info, post_id: str) -> Optional[PostType]: @classmethod def from_instance(cls, instance: Post) -> PostType: - return PostType( - id=instance.id, - title=instance.title, - text=instance.text, - link=instance.link, - thumbnail=instance.thumbnail, - user_id=instance.user_id, - subreddit_id=instance.subreddit_id, - votes=instance.votes, - comments=instance.comments, - ) + pass diff --git a/reddit-clone/reddit/subreddits/mutations/__init__.py b/reddit-clone/reddit/subreddits/mutations/__init__.py index 2ab7caaf..1b96ae52 100644 --- a/reddit-clone/reddit/subreddits/mutations/__init__.py +++ b/reddit-clone/reddit/subreddits/mutations/__init__.py @@ -1,3 +1,5 @@ from strawberry.tools import create_type -SubredditMutation = create_type(name="SubredditMutation", fields=()) +from .subreddit_create import subreddit_create + +SubredditMutation = create_type(name="SubredditMutation", fields=(subreddit_create,)) diff --git a/reddit-clone/reddit/subreddits/mutations/subreddit_create.py b/reddit-clone/reddit/subreddits/mutations/subreddit_create.py new file mode 100644 index 00000000..201bd07c --- /dev/null +++ b/reddit-clone/reddit/subreddits/mutations/subreddit_create.py @@ -0,0 +1,41 @@ +from typing import Union + +import strawberry +from strawberry.types import Info + +from reddit.subreddits.types import SubredditType + + +__all__ = ("subreddit_create",) + + +@strawberry.input +class SubredditCreateInput: + name: str + + +@strawberry.type +class SubredditCreateSuccess: + subreddit: SubredditType + + +@strawberry.type +class SubredditCreateError: + error: str + + +SubredditCreateResult = Union[SubredditCreateSuccess, SubredditCreateError] + + +async def resolve_subreddit_create( + info: Info, input: SubredditCreateInput +) -> SubredditCreateResult: + pass + + +subreddit_create = strawberry.mutation( + resolver=resolve_subreddit_create, + description=""" + Creates a new Subreddit. + """, +) diff --git a/reddit-clone/reddit/subreddits/types.py b/reddit-clone/reddit/subreddits/types.py index 6d3ca010..7bf23b32 100644 --- a/reddit-clone/reddit/subreddits/types.py +++ b/reddit-clone/reddit/subreddits/types.py @@ -16,45 +16,38 @@ class SubredditType(NodeType): name: str = strawberry.field( description=""" - The name of the subreddit. + The name of the Subreddit. """ ) description: str = strawberry.field( description=""" - The description of the subreddit. + The description of the Subreddit. """ ) admin_id: int = strawberry.field( description=""" - The owner ID of the subreddit. + The owner ID of the Subreddit. """ ) submit_text: str = strawberry.field( description=""" - The text set by the subreddit moderators, intended + The text set by the Subreddit moderators, intended to be displayed on the submission form. """ ) - # TODO: make status an enum - status: int = strawberry.field( - description=""" - The status of the subreddit. - """ - ) - icon: str = strawberry.field( description=""" - The icon URL of the subreddit. + The icon URL of the Subreddit. """ ) posts: List[PostType] = strawberry.field( description=""" - The posts for the subreddit. + The posts for the Subreddit. """ ) @@ -63,7 +56,7 @@ async def resolve_node( cls, info: Info, subreddit_id: str ) -> Optional[SubredditType]: """ - Gets a subreddit with the given ID. + Gets a Subreddit with the given ID. """ query = select(Subreddit).filter_by(id=subreddit_id).first() async with get_session() as session: @@ -73,11 +66,4 @@ async def resolve_node( @classmethod def from_instance(cls, instance: Subreddit) -> SubredditType: - return SubredditType( - id=instance.id, - description=instance.description, - admin_id=instance.admin_id, - status=instance.status, - icon=instance.icon, - posts=instance.posts, - ) + pass diff --git a/reddit-clone/reddit/users/types.py b/reddit-clone/reddit/users/types.py index 266c093b..09df0b6d 100644 --- a/reddit-clone/reddit/users/types.py +++ b/reddit-clone/reddit/users/types.py @@ -65,11 +65,4 @@ async def resolve_node(cls, info: Info, user_id: str) -> Optional[UserType]: @classmethod def from_instance(cls, instance: User) -> UserType: - return UserType( - id=instance.id, - username=instance.username, - avatar=instance.avatar, - posts=instance.posts, - subreddits=instance.subreddits, - comments=instance.comments, - ) + pass From 743b15666fa613e32c67280a121fc704cfb3d6ee Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sun, 10 Oct 2021 09:24:52 +0530 Subject: [PATCH 059/150] add more mutations --- .../reddit/comments/mutations/__init__.py | 11 +++++ .../comments/mutations/comment_create.py | 44 ++++++++++++++++++ .../comments/mutations/comment_delete.py | 44 ++++++++++++++++++ .../comments/mutations/comment_update.py | 45 +++++++++++++++++++ .../reddit/comments/mutations/comment_vote.py | 44 ++++++++++++++++++ .../reddit/posts/mutations/__init__.py | 10 +++++ .../reddit/posts/mutations/post_create.py | 42 +++++++++++++++++ .../reddit/posts/mutations/post_delete.py | 41 +++++++++++++++++ .../reddit/posts/mutations/post_update.py | 43 ++++++++++++++++++ .../reddit/posts/mutations/post_vote.py | 41 +++++++++++++++++ reddit-clone/reddit/schema.py | 4 +- .../reddit/subreddits/mutations/__init__.py | 15 ++++++- .../subreddits/mutations/subreddit_create.py | 4 +- .../subreddits/mutations/subreddit_delete.py | 42 +++++++++++++++++ .../subreddits/mutations/subreddit_join.py | 42 +++++++++++++++++ .../subreddits/mutations/subreddit_leave.py | 42 +++++++++++++++++ .../subreddits/mutations/subreddit_update.py | 44 ++++++++++++++++++ reddit-clone/reddit/subreddits/queries.py | 1 + .../reddit/users/mutations/authenticate.py | 1 + .../reddit/users/mutations/avatar_remove.py | 1 + .../reddit/users/mutations/email_change.py | 1 + .../users/mutations/email_change_request.py | 1 + .../reddit/users/mutations/password_reset.py | 1 + .../users/mutations/password_reset_request.py | 1 + .../reddit/users/mutations/user_block.py | 1 + .../reddit/users/mutations/user_create.py | 1 + .../reddit/users/mutations/user_deactivate.py | 1 + .../reddit/users/mutations/user_update.py | 1 + reddit-clone/reddit/users/queries.py | 2 + 29 files changed, 568 insertions(+), 3 deletions(-) create mode 100644 reddit-clone/reddit/comments/mutations/__init__.py create mode 100644 reddit-clone/reddit/comments/mutations/comment_create.py create mode 100644 reddit-clone/reddit/comments/mutations/comment_delete.py create mode 100644 reddit-clone/reddit/comments/mutations/comment_update.py create mode 100644 reddit-clone/reddit/comments/mutations/comment_vote.py create mode 100644 reddit-clone/reddit/posts/mutations/__init__.py create mode 100644 reddit-clone/reddit/posts/mutations/post_create.py create mode 100644 reddit-clone/reddit/posts/mutations/post_delete.py create mode 100644 reddit-clone/reddit/posts/mutations/post_update.py create mode 100644 reddit-clone/reddit/posts/mutations/post_vote.py create mode 100644 reddit-clone/reddit/subreddits/mutations/subreddit_delete.py create mode 100644 reddit-clone/reddit/subreddits/mutations/subreddit_join.py create mode 100644 reddit-clone/reddit/subreddits/mutations/subreddit_leave.py create mode 100644 reddit-clone/reddit/subreddits/mutations/subreddit_update.py diff --git a/reddit-clone/reddit/comments/mutations/__init__.py b/reddit-clone/reddit/comments/mutations/__init__.py new file mode 100644 index 00000000..a37863d9 --- /dev/null +++ b/reddit-clone/reddit/comments/mutations/__init__.py @@ -0,0 +1,11 @@ +from strawberry.tools import create_type + +from .comment_create import comment_create +from .comment_delete import comment_delete +from .comment_update import comment_update +from .comment_vote import comment_vote + +CommentMutation = create_type( + name="CommentMutation", + fields=(comment_create, comment_delete, comment_update, comment_vote), +) diff --git a/reddit-clone/reddit/comments/mutations/comment_create.py b/reddit-clone/reddit/comments/mutations/comment_create.py new file mode 100644 index 00000000..cbea3ebf --- /dev/null +++ b/reddit-clone/reddit/comments/mutations/comment_create.py @@ -0,0 +1,44 @@ +from typing import Union + +import strawberry +from strawberry.types import Info + +from reddit.comments.types import CommentType + + +__all__ = ("comment_create",) + + +@strawberry.input +class CommentCreateInput: + content: str + subreddit_id: strawberry.ID + post_id: strawberry.ID + + +@strawberry.type +class CommentCreateSuccess: + comment: CommentType + + +@strawberry.type +class CommentCreateError: + error: str + + +CommentCreateResult = Union[CommentCreateSuccess, CommentCreateError] + + +async def resolve_comment_create( + info: Info, input: CommentCreateInput +) -> CommentCreateResult: + pass + + +comment_create = strawberry.mutation( + name="comment_create", + resolver=resolve_comment_create, + description=""" + Creates a new comment on a post. + """, +) diff --git a/reddit-clone/reddit/comments/mutations/comment_delete.py b/reddit-clone/reddit/comments/mutations/comment_delete.py new file mode 100644 index 00000000..c9973867 --- /dev/null +++ b/reddit-clone/reddit/comments/mutations/comment_delete.py @@ -0,0 +1,44 @@ +from typing import Union + +import strawberry +from strawberry.types import Info + +from reddit.comments.types import CommentType + + +__all__ = ("comment_delete",) + + +@strawberry.input +class CommentDeleteInput: + comment_id: strawberry.ID + subreddit_id: strawberry.ID + post_id: strawberry.ID + + +@strawberry.type +class CommentDeleteSuccess: + comment: CommentType + + +@strawberry.type +class CommentDeleteError: + error: str + + +CommentDeleteResult = Union[CommentDeleteSuccess, CommentDeleteError] + + +async def resolve_comment_delete( + info: Info, input: CommentDeleteInput +) -> CommentDeleteResult: + pass + + +comment_delete = strawberry.mutation( + name="comment_delete", + resolver=resolve_comment_delete, + description=""" + Deletes a comment on a post. + """, +) diff --git a/reddit-clone/reddit/comments/mutations/comment_update.py b/reddit-clone/reddit/comments/mutations/comment_update.py new file mode 100644 index 00000000..a8455283 --- /dev/null +++ b/reddit-clone/reddit/comments/mutations/comment_update.py @@ -0,0 +1,45 @@ +from typing import Union, Optional + +import strawberry +from strawberry.types import Info + +from reddit.comments.types import CommentType + + +__all__ = ("comment_update",) + + +@strawberry.input +class CommentUpdateInput: + comment: Optional[str] + comment_id: strawberry.ID + subreddit_id: strawberry.ID + post_id: strawberry.ID + + +@strawberry.type +class CommentUpdateSuccess: + comment: CommentType + + +@strawberry.type +class CommentUpdateError: + error: str + + +CommentUpdateResult = Union[CommentUpdateSuccess, CommentUpdateError] + + +async def resolve_comment_update( + info: Info, input: CommentUpdateInput +) -> CommentUpdateResult: + pass + + +comment_update = strawberry.mutation( + name="comment_update", + resolver=resolve_comment_update, + description=""" + Updates a comment on a post. + """, +) diff --git a/reddit-clone/reddit/comments/mutations/comment_vote.py b/reddit-clone/reddit/comments/mutations/comment_vote.py new file mode 100644 index 00000000..3c216e59 --- /dev/null +++ b/reddit-clone/reddit/comments/mutations/comment_vote.py @@ -0,0 +1,44 @@ +from typing import Union + +import strawberry +from strawberry.types import Info + +from reddit.comments.types import CommentType + + +__all__ = ("comment_vote",) + + +@strawberry.input +class CommentVoteInput: + comment_id: strawberry.ID + subreddit_id: strawberry.ID + post_id: strawberry.ID + + +@strawberry.type +class CommentVoteSuccess: + comment: CommentType + + +@strawberry.type +class CommentVoteError: + error: str + + +CommentVoteResult = Union[CommentVoteSuccess, CommentVoteError] + + +async def resolve_comment_vote( + info: Info, input: CommentVoteInput +) -> CommentVoteResult: + pass + + +comment_vote = strawberry.mutation( + name="comment_vote", + resolver=resolve_comment_vote, + description=""" + Creates a vote on a comment. + """, +) diff --git a/reddit-clone/reddit/posts/mutations/__init__.py b/reddit-clone/reddit/posts/mutations/__init__.py new file mode 100644 index 00000000..06457192 --- /dev/null +++ b/reddit-clone/reddit/posts/mutations/__init__.py @@ -0,0 +1,10 @@ +from strawberry.tools import create_type + +from .post_create import post_create +from .post_delete import post_delete +from .post_update import post_update +from .post_vote import post_vote + +PostMutation = create_type( + name="PostMutation", fields=(post_create, post_delete, post_update, post_vote) +) diff --git a/reddit-clone/reddit/posts/mutations/post_create.py b/reddit-clone/reddit/posts/mutations/post_create.py new file mode 100644 index 00000000..4fac0a36 --- /dev/null +++ b/reddit-clone/reddit/posts/mutations/post_create.py @@ -0,0 +1,42 @@ +from typing import Union, Optional + +import strawberry +from strawberry.types import Info + +from reddit.posts.types import PostType + + +__all__ = ("post_create",) + + +@strawberry.input +class PostCreateInput: + title: str + subreddit_id: strawberry.ID + text: Optional[str] + + +@strawberry.type +class PostCreateSuccess: + post: PostType + + +@strawberry.type +class PostCreateError: + error: str + + +PostCreateResult = Union[PostCreateSuccess, PostCreateError] + + +async def resolve_post_create(info: Info, input: PostCreateInput) -> PostCreateResult: + pass + + +post_create = strawberry.mutation( + name="post_create", + resolver=resolve_post_create, + description=""" + Creates a new post in a Subreddit. + """, +) diff --git a/reddit-clone/reddit/posts/mutations/post_delete.py b/reddit-clone/reddit/posts/mutations/post_delete.py new file mode 100644 index 00000000..074410b8 --- /dev/null +++ b/reddit-clone/reddit/posts/mutations/post_delete.py @@ -0,0 +1,41 @@ +from typing import Union + +import strawberry +from strawberry.types import Info + +from reddit.posts.types import PostType + + +__all__ = ("post_delete",) + + +@strawberry.input +class PostDeleteInput: + post_id: strawberry.ID + subreddit_id: strawberry.ID + + +@strawberry.type +class PostDeleteSuccess: + post: PostType + + +@strawberry.type +class PostDeleteError: + error: str + + +PostDeleteResult = Union[PostDeleteSuccess, PostDeleteError] + + +async def resolve_post_delete(info: Info, input: PostDeleteInput) -> PostDeleteResult: + pass + + +post_delete = strawberry.mutation( + name="post_delete", + resolver=resolve_post_delete, + description=""" + Deletes a post in a Subreddit. + """, +) diff --git a/reddit-clone/reddit/posts/mutations/post_update.py b/reddit-clone/reddit/posts/mutations/post_update.py new file mode 100644 index 00000000..4fa38a42 --- /dev/null +++ b/reddit-clone/reddit/posts/mutations/post_update.py @@ -0,0 +1,43 @@ +from typing import Union, Optional + +import strawberry +from strawberry.types import Info + +from reddit.posts.types import PostType + + +__all__ = ("post_update",) + + +@strawberry.input +class PostUpdateInput: + title: Optional[str] + text: Optional[str] + post_id: strawberry.ID + subreddit_id: strawberry.ID + + +@strawberry.type +class PostUpdateSuccess: + post: PostType + + +@strawberry.type +class PostUpdateError: + error: str + + +PostUpdateResult = Union[PostUpdateSuccess, PostUpdateError] + + +async def resolve_post_update(info: Info, input: PostUpdateInput) -> PostUpdateResult: + pass + + +post_update = strawberry.mutation( + name="post_update", + resolver=resolve_post_update, + description=""" + Updates a post in a Subreddit. + """, +) diff --git a/reddit-clone/reddit/posts/mutations/post_vote.py b/reddit-clone/reddit/posts/mutations/post_vote.py new file mode 100644 index 00000000..2a86c47d --- /dev/null +++ b/reddit-clone/reddit/posts/mutations/post_vote.py @@ -0,0 +1,41 @@ +from typing import Union + +import strawberry +from strawberry.types import Info + +from reddit.posts.types import PostType + + +__all__ = ("post_vote",) + + +@strawberry.input +class PostVoteInput: + post_id: strawberry.ID + subreddit_id: strawberry.ID + + +@strawberry.type +class PostVoteSuccess: + post: PostType + + +@strawberry.type +class PostVoteError: + error: str + + +PostVoteResult = Union[PostVoteSuccess, PostVoteError] + + +async def resolve_post_vote(info: Info, input: PostVoteInput) -> PostVoteResult: + pass + + +post_vote = strawberry.mutation( + name="post_vote", + resolver=resolve_post_vote, + description=""" + Creates a vote on a post. + """, +) diff --git a/reddit-clone/reddit/schema.py b/reddit-clone/reddit/schema.py index ef92bc16..af03218e 100644 --- a/reddit-clone/reddit/schema.py +++ b/reddit-clone/reddit/schema.py @@ -3,6 +3,8 @@ from reddit.base.queries import BaseQuery from reddit.subreddits.queries import SubredditQuery from reddit.users.queries import UserQuery +from reddit.comments.mutations import CommentMutation +from reddit.posts.mutations import PostMutation from reddit.subreddits.mutations import SubredditMutation from reddit.users.mutations import UserMutation @@ -13,7 +15,7 @@ class Query(BaseQuery, UserQuery, SubredditQuery): @strawberry.type -class Mutation(UserMutation, SubredditMutation): +class Mutation(CommentMutation, PostMutation, UserMutation, SubredditMutation): pass diff --git a/reddit-clone/reddit/subreddits/mutations/__init__.py b/reddit-clone/reddit/subreddits/mutations/__init__.py index 1b96ae52..83a4ad10 100644 --- a/reddit-clone/reddit/subreddits/mutations/__init__.py +++ b/reddit-clone/reddit/subreddits/mutations/__init__.py @@ -1,5 +1,18 @@ from strawberry.tools import create_type from .subreddit_create import subreddit_create +from .subreddit_delete import subreddit_delete +from .subreddit_join import subreddit_join +from .subreddit_leave import subreddit_leave +from .subreddit_update import subreddit_update -SubredditMutation = create_type(name="SubredditMutation", fields=(subreddit_create,)) +SubredditMutation = create_type( + name="SubredditMutation", + fields=( + subreddit_create, + subreddit_delete, + subreddit_join, + subreddit_leave, + subreddit_update, + ), +) diff --git a/reddit-clone/reddit/subreddits/mutations/subreddit_create.py b/reddit-clone/reddit/subreddits/mutations/subreddit_create.py index 201bd07c..0e65be74 100644 --- a/reddit-clone/reddit/subreddits/mutations/subreddit_create.py +++ b/reddit-clone/reddit/subreddits/mutations/subreddit_create.py @@ -1,4 +1,4 @@ -from typing import Union +from typing import Optional, Union import strawberry from strawberry.types import Info @@ -12,6 +12,7 @@ @strawberry.input class SubredditCreateInput: name: str + description: Optional[str] @strawberry.type @@ -34,6 +35,7 @@ async def resolve_subreddit_create( subreddit_create = strawberry.mutation( + name="subreddit_create", resolver=resolve_subreddit_create, description=""" Creates a new Subreddit. diff --git a/reddit-clone/reddit/subreddits/mutations/subreddit_delete.py b/reddit-clone/reddit/subreddits/mutations/subreddit_delete.py new file mode 100644 index 00000000..86dd7ec4 --- /dev/null +++ b/reddit-clone/reddit/subreddits/mutations/subreddit_delete.py @@ -0,0 +1,42 @@ +from typing import Union + +import strawberry +from strawberry.types import Info + +from reddit.subreddits.types import SubredditType + + +__all__ = ("subreddit_delete",) + + +@strawberry.input +class SubredditDeleteInput: + subreddit_id: strawberry.ID + + +@strawberry.type +class SubredditDeleteSuccess: + subreddit: SubredditType + + +@strawberry.type +class SubredditDeleteError: + error: str + + +SubredditDeleteResult = Union[SubredditDeleteSuccess, SubredditDeleteError] + + +async def resolve_subreddit_delete( + info: Info, input: SubredditDeleteInput +) -> SubredditDeleteResult: + pass + + +subreddit_delete = strawberry.mutation( + name="subreddit_delete", + resolver=resolve_subreddit_delete, + description=""" + Deletes a Subreddit. + """, +) diff --git a/reddit-clone/reddit/subreddits/mutations/subreddit_join.py b/reddit-clone/reddit/subreddits/mutations/subreddit_join.py new file mode 100644 index 00000000..3a35ab8b --- /dev/null +++ b/reddit-clone/reddit/subreddits/mutations/subreddit_join.py @@ -0,0 +1,42 @@ +from typing import Union + +import strawberry +from strawberry.types import Info + +from reddit.subreddits.types import SubredditType + + +__all__ = ("subreddit_join",) + + +@strawberry.input +class SubredditJoinInput: + subreddit_id: strawberry.ID + + +@strawberry.type +class SubredditJoinSuccess: + subreddit: SubredditType + + +@strawberry.type +class SubredditJoinError: + error: str + + +SubredditJoinResult = Union[SubredditJoinSuccess, SubredditJoinError] + + +async def resolve_subreddit_join( + info: Info, input: SubredditJoinInput +) -> SubredditJoinResult: + pass + + +subreddit_join = strawberry.mutation( + name="subreddit_join", + resolver=resolve_subreddit_join, + description=""" + Creates a new Subreddit-User relationship. + """, +) diff --git a/reddit-clone/reddit/subreddits/mutations/subreddit_leave.py b/reddit-clone/reddit/subreddits/mutations/subreddit_leave.py new file mode 100644 index 00000000..f49786eb --- /dev/null +++ b/reddit-clone/reddit/subreddits/mutations/subreddit_leave.py @@ -0,0 +1,42 @@ +from typing import Union + +import strawberry +from strawberry.types import Info + +from reddit.subreddits.types import SubredditType + + +__all__ = ("subreddit_leave",) + + +@strawberry.input +class SubredditLeaveInput: + subreddit_id: strawberry.ID + + +@strawberry.type +class SubredditLeaveSuccess: + subreddit: SubredditType + + +@strawberry.type +class SubredditLeaveError: + error: str + + +SubredditLeaveResult = Union[SubredditLeaveSuccess, SubredditLeaveError] + + +async def resolve_subreddit_leave( + info: Info, input: SubredditLeaveInput +) -> SubredditLeaveResult: + pass + + +subreddit_leave = strawberry.mutation( + name="subreddit_leave", + resolver=resolve_subreddit_leave, + description=""" + Deletes a Subreddit-User relationship. + """, +) diff --git a/reddit-clone/reddit/subreddits/mutations/subreddit_update.py b/reddit-clone/reddit/subreddits/mutations/subreddit_update.py new file mode 100644 index 00000000..40f5ca18 --- /dev/null +++ b/reddit-clone/reddit/subreddits/mutations/subreddit_update.py @@ -0,0 +1,44 @@ +from typing import Optional, Union + +import strawberry +from strawberry.types import Info + +from reddit.subreddits.types import SubredditType + + +__all__ = ("subreddit_update",) + + +@strawberry.input +class SubredditUpdateInput: + name: Optional[str] + description: Optional[str] + subreddit_id: strawberry.ID + + +@strawberry.type +class SubredditUpdateSuccess: + subreddit: SubredditType + + +@strawberry.type +class SubredditUpdateError: + error: str + + +SubredditUpdateResult = Union[SubredditUpdateSuccess, SubredditUpdateError] + + +async def resolve_subreddit_update( + info: Info, input: SubredditUpdateInput +) -> SubredditUpdateResult: + pass + + +subreddit_update = strawberry.mutation( + name="subreddit_update", + resolver=resolve_subreddit_update, + description=""" + Updates a Subreddit. + """, +) diff --git a/reddit-clone/reddit/subreddits/queries.py b/reddit-clone/reddit/subreddits/queries.py index 77998552..f4135180 100644 --- a/reddit-clone/reddit/subreddits/queries.py +++ b/reddit-clone/reddit/subreddits/queries.py @@ -12,6 +12,7 @@ async def resolve_subreddits(info: Info) -> List[SubredditType]: subreddits = strawberry.field( + name="subreddits", resolver=resolve_subreddits, description=""" Gets the available subreddits. diff --git a/reddit-clone/reddit/users/mutations/authenticate.py b/reddit-clone/reddit/users/mutations/authenticate.py index 8983d5b8..8f8e5bf3 100644 --- a/reddit-clone/reddit/users/mutations/authenticate.py +++ b/reddit-clone/reddit/users/mutations/authenticate.py @@ -35,6 +35,7 @@ async def resolve_authenticate( authenticate = strawberry.mutation( + name="authenticate", resolver=resolve_authenticate, description=""" Logs the current user in. diff --git a/reddit-clone/reddit/users/mutations/avatar_remove.py b/reddit-clone/reddit/users/mutations/avatar_remove.py index 9505484f..1bd1fbb8 100644 --- a/reddit-clone/reddit/users/mutations/avatar_remove.py +++ b/reddit-clone/reddit/users/mutations/avatar_remove.py @@ -26,6 +26,7 @@ async def resolve_avatar_remove(info: Info) -> AvatarRemoveResult: avatar_remove = strawberry.mutation( + name="avatar_remove", resolver=resolve_avatar_remove, description=""" Removes the current user's avatar. diff --git a/reddit-clone/reddit/users/mutations/email_change.py b/reddit-clone/reddit/users/mutations/email_change.py index b08f6036..45303a62 100644 --- a/reddit-clone/reddit/users/mutations/email_change.py +++ b/reddit-clone/reddit/users/mutations/email_change.py @@ -34,6 +34,7 @@ async def resolve_email_change( email_change = strawberry.mutation( + name="email_change", resolver=resolve_email_change, description=""" Changes the email for the user account diff --git a/reddit-clone/reddit/users/mutations/email_change_request.py b/reddit-clone/reddit/users/mutations/email_change_request.py index b16f3a60..17843cec 100644 --- a/reddit-clone/reddit/users/mutations/email_change_request.py +++ b/reddit-clone/reddit/users/mutations/email_change_request.py @@ -35,6 +35,7 @@ async def resolve_email_change_request( email_change_request = strawberry.mutation( + name="email_change_request", resolver=resolve_email_change_request, description=""" Sends an email change code to diff --git a/reddit-clone/reddit/users/mutations/password_reset.py b/reddit-clone/reddit/users/mutations/password_reset.py index 16e25ebf..0afcffc8 100644 --- a/reddit-clone/reddit/users/mutations/password_reset.py +++ b/reddit-clone/reddit/users/mutations/password_reset.py @@ -35,6 +35,7 @@ async def resolve_password_reset( password_reset = strawberry.mutation( + name="password_reset", resolver=resolve_password_reset, description=""" Resets the password for the user account diff --git a/reddit-clone/reddit/users/mutations/password_reset_request.py b/reddit-clone/reddit/users/mutations/password_reset_request.py index 6e2104ef..24b94415 100644 --- a/reddit-clone/reddit/users/mutations/password_reset_request.py +++ b/reddit-clone/reddit/users/mutations/password_reset_request.py @@ -35,6 +35,7 @@ async def resolve_password_reset_request( password_reset_request = strawberry.mutation( + name="password_reset_request", resolver=resolve_password_reset_request, description=""" Sends a password reset code to the diff --git a/reddit-clone/reddit/users/mutations/user_block.py b/reddit-clone/reddit/users/mutations/user_block.py index b3b7b6bc..0634b822 100644 --- a/reddit-clone/reddit/users/mutations/user_block.py +++ b/reddit-clone/reddit/users/mutations/user_block.py @@ -32,6 +32,7 @@ async def resolve_user_block(info: Info, input: UserBlockInput) -> UserBlockResu user_block = strawberry.mutation( + name="user_block", resolver=resolve_user_block, description=""" Blocks an user account. diff --git a/reddit-clone/reddit/users/mutations/user_create.py b/reddit-clone/reddit/users/mutations/user_create.py index 30ca3003..49ca1ab3 100644 --- a/reddit-clone/reddit/users/mutations/user_create.py +++ b/reddit-clone/reddit/users/mutations/user_create.py @@ -33,6 +33,7 @@ async def resolve_user_create(info: Info, input: UserCreateInput) -> UserCreateR user_create = strawberry.mutation( + name="user_create", resolver=resolve_user_create, description=""" Creates a new user. diff --git a/reddit-clone/reddit/users/mutations/user_deactivate.py b/reddit-clone/reddit/users/mutations/user_deactivate.py index 8d339687..20a42786 100644 --- a/reddit-clone/reddit/users/mutations/user_deactivate.py +++ b/reddit-clone/reddit/users/mutations/user_deactivate.py @@ -33,6 +33,7 @@ async def resolve_user_deactivate( user_deactivate = strawberry.mutation( + name="user_deactivate", resolver=resolve_user_deactivate, description=""" Deactivates the current user. diff --git a/reddit-clone/reddit/users/mutations/user_update.py b/reddit-clone/reddit/users/mutations/user_update.py index 3740cebb..314674c9 100644 --- a/reddit-clone/reddit/users/mutations/user_update.py +++ b/reddit-clone/reddit/users/mutations/user_update.py @@ -33,6 +33,7 @@ async def resolve_user_update(info: Info, input: UserUpdateInput) -> UserUpdateR user_update = strawberry.mutation( + name="user_update", resolver=resolve_user_update, description=""" Updates the current user. diff --git a/reddit-clone/reddit/users/queries.py b/reddit-clone/reddit/users/queries.py index 50fe6e13..cc54f45a 100644 --- a/reddit-clone/reddit/users/queries.py +++ b/reddit-clone/reddit/users/queries.py @@ -12,6 +12,7 @@ async def resolve_user(info: Info, username: str) -> Optional[UserType]: user = strawberry.field( + name="user", resolver=resolve_user, description=""" Gets an user by username. @@ -24,6 +25,7 @@ async def resolve_current_user(info: Info) -> UserType: current_user = strawberry.field( + name="current_user", resolver=resolve_current_user, description=""" Gets the current user. From 53e09fc36a00e92ba36fed6e9746beba466dbefb Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sun, 10 Oct 2021 09:37:54 +0530 Subject: [PATCH 060/150] remove explicit field names --- reddit-clone/reddit/comments/mutations/comment_create.py | 1 - reddit-clone/reddit/comments/mutations/comment_delete.py | 1 - reddit-clone/reddit/comments/mutations/comment_update.py | 1 - reddit-clone/reddit/comments/mutations/comment_vote.py | 1 - reddit-clone/reddit/posts/mutations/post_create.py | 1 - reddit-clone/reddit/posts/mutations/post_delete.py | 1 - reddit-clone/reddit/posts/mutations/post_update.py | 1 - reddit-clone/reddit/posts/mutations/post_vote.py | 1 - reddit-clone/reddit/subreddits/mutations/subreddit_create.py | 1 - reddit-clone/reddit/subreddits/mutations/subreddit_delete.py | 1 - reddit-clone/reddit/subreddits/mutations/subreddit_join.py | 1 - reddit-clone/reddit/subreddits/mutations/subreddit_leave.py | 1 - reddit-clone/reddit/subreddits/mutations/subreddit_update.py | 1 - reddit-clone/reddit/subreddits/queries.py | 1 - reddit-clone/reddit/users/mutations/authenticate.py | 1 - reddit-clone/reddit/users/mutations/avatar_remove.py | 1 - reddit-clone/reddit/users/mutations/email_change.py | 1 - reddit-clone/reddit/users/mutations/email_change_request.py | 1 - reddit-clone/reddit/users/mutations/password_reset.py | 1 - reddit-clone/reddit/users/mutations/password_reset_request.py | 1 - reddit-clone/reddit/users/mutations/user_block.py | 1 - reddit-clone/reddit/users/mutations/user_create.py | 1 - reddit-clone/reddit/users/mutations/user_deactivate.py | 1 - reddit-clone/reddit/users/mutations/user_update.py | 1 - reddit-clone/reddit/users/queries.py | 2 -- 25 files changed, 26 deletions(-) diff --git a/reddit-clone/reddit/comments/mutations/comment_create.py b/reddit-clone/reddit/comments/mutations/comment_create.py index cbea3ebf..81143ae4 100644 --- a/reddit-clone/reddit/comments/mutations/comment_create.py +++ b/reddit-clone/reddit/comments/mutations/comment_create.py @@ -36,7 +36,6 @@ async def resolve_comment_create( comment_create = strawberry.mutation( - name="comment_create", resolver=resolve_comment_create, description=""" Creates a new comment on a post. diff --git a/reddit-clone/reddit/comments/mutations/comment_delete.py b/reddit-clone/reddit/comments/mutations/comment_delete.py index c9973867..4b903360 100644 --- a/reddit-clone/reddit/comments/mutations/comment_delete.py +++ b/reddit-clone/reddit/comments/mutations/comment_delete.py @@ -36,7 +36,6 @@ async def resolve_comment_delete( comment_delete = strawberry.mutation( - name="comment_delete", resolver=resolve_comment_delete, description=""" Deletes a comment on a post. diff --git a/reddit-clone/reddit/comments/mutations/comment_update.py b/reddit-clone/reddit/comments/mutations/comment_update.py index a8455283..2d5e738a 100644 --- a/reddit-clone/reddit/comments/mutations/comment_update.py +++ b/reddit-clone/reddit/comments/mutations/comment_update.py @@ -37,7 +37,6 @@ async def resolve_comment_update( comment_update = strawberry.mutation( - name="comment_update", resolver=resolve_comment_update, description=""" Updates a comment on a post. diff --git a/reddit-clone/reddit/comments/mutations/comment_vote.py b/reddit-clone/reddit/comments/mutations/comment_vote.py index 3c216e59..5912656b 100644 --- a/reddit-clone/reddit/comments/mutations/comment_vote.py +++ b/reddit-clone/reddit/comments/mutations/comment_vote.py @@ -36,7 +36,6 @@ async def resolve_comment_vote( comment_vote = strawberry.mutation( - name="comment_vote", resolver=resolve_comment_vote, description=""" Creates a vote on a comment. diff --git a/reddit-clone/reddit/posts/mutations/post_create.py b/reddit-clone/reddit/posts/mutations/post_create.py index 4fac0a36..2bec4f6b 100644 --- a/reddit-clone/reddit/posts/mutations/post_create.py +++ b/reddit-clone/reddit/posts/mutations/post_create.py @@ -34,7 +34,6 @@ async def resolve_post_create(info: Info, input: PostCreateInput) -> PostCreateR post_create = strawberry.mutation( - name="post_create", resolver=resolve_post_create, description=""" Creates a new post in a Subreddit. diff --git a/reddit-clone/reddit/posts/mutations/post_delete.py b/reddit-clone/reddit/posts/mutations/post_delete.py index 074410b8..369967c2 100644 --- a/reddit-clone/reddit/posts/mutations/post_delete.py +++ b/reddit-clone/reddit/posts/mutations/post_delete.py @@ -33,7 +33,6 @@ async def resolve_post_delete(info: Info, input: PostDeleteInput) -> PostDeleteR post_delete = strawberry.mutation( - name="post_delete", resolver=resolve_post_delete, description=""" Deletes a post in a Subreddit. diff --git a/reddit-clone/reddit/posts/mutations/post_update.py b/reddit-clone/reddit/posts/mutations/post_update.py index 4fa38a42..3e318437 100644 --- a/reddit-clone/reddit/posts/mutations/post_update.py +++ b/reddit-clone/reddit/posts/mutations/post_update.py @@ -35,7 +35,6 @@ async def resolve_post_update(info: Info, input: PostUpdateInput) -> PostUpdateR post_update = strawberry.mutation( - name="post_update", resolver=resolve_post_update, description=""" Updates a post in a Subreddit. diff --git a/reddit-clone/reddit/posts/mutations/post_vote.py b/reddit-clone/reddit/posts/mutations/post_vote.py index 2a86c47d..4d82fcea 100644 --- a/reddit-clone/reddit/posts/mutations/post_vote.py +++ b/reddit-clone/reddit/posts/mutations/post_vote.py @@ -33,7 +33,6 @@ async def resolve_post_vote(info: Info, input: PostVoteInput) -> PostVoteResult: post_vote = strawberry.mutation( - name="post_vote", resolver=resolve_post_vote, description=""" Creates a vote on a post. diff --git a/reddit-clone/reddit/subreddits/mutations/subreddit_create.py b/reddit-clone/reddit/subreddits/mutations/subreddit_create.py index 0e65be74..0625b8a9 100644 --- a/reddit-clone/reddit/subreddits/mutations/subreddit_create.py +++ b/reddit-clone/reddit/subreddits/mutations/subreddit_create.py @@ -35,7 +35,6 @@ async def resolve_subreddit_create( subreddit_create = strawberry.mutation( - name="subreddit_create", resolver=resolve_subreddit_create, description=""" Creates a new Subreddit. diff --git a/reddit-clone/reddit/subreddits/mutations/subreddit_delete.py b/reddit-clone/reddit/subreddits/mutations/subreddit_delete.py index 86dd7ec4..4ce08dc9 100644 --- a/reddit-clone/reddit/subreddits/mutations/subreddit_delete.py +++ b/reddit-clone/reddit/subreddits/mutations/subreddit_delete.py @@ -34,7 +34,6 @@ async def resolve_subreddit_delete( subreddit_delete = strawberry.mutation( - name="subreddit_delete", resolver=resolve_subreddit_delete, description=""" Deletes a Subreddit. diff --git a/reddit-clone/reddit/subreddits/mutations/subreddit_join.py b/reddit-clone/reddit/subreddits/mutations/subreddit_join.py index 3a35ab8b..a81c9b57 100644 --- a/reddit-clone/reddit/subreddits/mutations/subreddit_join.py +++ b/reddit-clone/reddit/subreddits/mutations/subreddit_join.py @@ -34,7 +34,6 @@ async def resolve_subreddit_join( subreddit_join = strawberry.mutation( - name="subreddit_join", resolver=resolve_subreddit_join, description=""" Creates a new Subreddit-User relationship. diff --git a/reddit-clone/reddit/subreddits/mutations/subreddit_leave.py b/reddit-clone/reddit/subreddits/mutations/subreddit_leave.py index f49786eb..582be748 100644 --- a/reddit-clone/reddit/subreddits/mutations/subreddit_leave.py +++ b/reddit-clone/reddit/subreddits/mutations/subreddit_leave.py @@ -34,7 +34,6 @@ async def resolve_subreddit_leave( subreddit_leave = strawberry.mutation( - name="subreddit_leave", resolver=resolve_subreddit_leave, description=""" Deletes a Subreddit-User relationship. diff --git a/reddit-clone/reddit/subreddits/mutations/subreddit_update.py b/reddit-clone/reddit/subreddits/mutations/subreddit_update.py index 40f5ca18..0c97b382 100644 --- a/reddit-clone/reddit/subreddits/mutations/subreddit_update.py +++ b/reddit-clone/reddit/subreddits/mutations/subreddit_update.py @@ -36,7 +36,6 @@ async def resolve_subreddit_update( subreddit_update = strawberry.mutation( - name="subreddit_update", resolver=resolve_subreddit_update, description=""" Updates a Subreddit. diff --git a/reddit-clone/reddit/subreddits/queries.py b/reddit-clone/reddit/subreddits/queries.py index f4135180..77998552 100644 --- a/reddit-clone/reddit/subreddits/queries.py +++ b/reddit-clone/reddit/subreddits/queries.py @@ -12,7 +12,6 @@ async def resolve_subreddits(info: Info) -> List[SubredditType]: subreddits = strawberry.field( - name="subreddits", resolver=resolve_subreddits, description=""" Gets the available subreddits. diff --git a/reddit-clone/reddit/users/mutations/authenticate.py b/reddit-clone/reddit/users/mutations/authenticate.py index 8f8e5bf3..8983d5b8 100644 --- a/reddit-clone/reddit/users/mutations/authenticate.py +++ b/reddit-clone/reddit/users/mutations/authenticate.py @@ -35,7 +35,6 @@ async def resolve_authenticate( authenticate = strawberry.mutation( - name="authenticate", resolver=resolve_authenticate, description=""" Logs the current user in. diff --git a/reddit-clone/reddit/users/mutations/avatar_remove.py b/reddit-clone/reddit/users/mutations/avatar_remove.py index 1bd1fbb8..9505484f 100644 --- a/reddit-clone/reddit/users/mutations/avatar_remove.py +++ b/reddit-clone/reddit/users/mutations/avatar_remove.py @@ -26,7 +26,6 @@ async def resolve_avatar_remove(info: Info) -> AvatarRemoveResult: avatar_remove = strawberry.mutation( - name="avatar_remove", resolver=resolve_avatar_remove, description=""" Removes the current user's avatar. diff --git a/reddit-clone/reddit/users/mutations/email_change.py b/reddit-clone/reddit/users/mutations/email_change.py index 45303a62..b08f6036 100644 --- a/reddit-clone/reddit/users/mutations/email_change.py +++ b/reddit-clone/reddit/users/mutations/email_change.py @@ -34,7 +34,6 @@ async def resolve_email_change( email_change = strawberry.mutation( - name="email_change", resolver=resolve_email_change, description=""" Changes the email for the user account diff --git a/reddit-clone/reddit/users/mutations/email_change_request.py b/reddit-clone/reddit/users/mutations/email_change_request.py index 17843cec..b16f3a60 100644 --- a/reddit-clone/reddit/users/mutations/email_change_request.py +++ b/reddit-clone/reddit/users/mutations/email_change_request.py @@ -35,7 +35,6 @@ async def resolve_email_change_request( email_change_request = strawberry.mutation( - name="email_change_request", resolver=resolve_email_change_request, description=""" Sends an email change code to diff --git a/reddit-clone/reddit/users/mutations/password_reset.py b/reddit-clone/reddit/users/mutations/password_reset.py index 0afcffc8..16e25ebf 100644 --- a/reddit-clone/reddit/users/mutations/password_reset.py +++ b/reddit-clone/reddit/users/mutations/password_reset.py @@ -35,7 +35,6 @@ async def resolve_password_reset( password_reset = strawberry.mutation( - name="password_reset", resolver=resolve_password_reset, description=""" Resets the password for the user account diff --git a/reddit-clone/reddit/users/mutations/password_reset_request.py b/reddit-clone/reddit/users/mutations/password_reset_request.py index 24b94415..6e2104ef 100644 --- a/reddit-clone/reddit/users/mutations/password_reset_request.py +++ b/reddit-clone/reddit/users/mutations/password_reset_request.py @@ -35,7 +35,6 @@ async def resolve_password_reset_request( password_reset_request = strawberry.mutation( - name="password_reset_request", resolver=resolve_password_reset_request, description=""" Sends a password reset code to the diff --git a/reddit-clone/reddit/users/mutations/user_block.py b/reddit-clone/reddit/users/mutations/user_block.py index 0634b822..b3b7b6bc 100644 --- a/reddit-clone/reddit/users/mutations/user_block.py +++ b/reddit-clone/reddit/users/mutations/user_block.py @@ -32,7 +32,6 @@ async def resolve_user_block(info: Info, input: UserBlockInput) -> UserBlockResu user_block = strawberry.mutation( - name="user_block", resolver=resolve_user_block, description=""" Blocks an user account. diff --git a/reddit-clone/reddit/users/mutations/user_create.py b/reddit-clone/reddit/users/mutations/user_create.py index 49ca1ab3..30ca3003 100644 --- a/reddit-clone/reddit/users/mutations/user_create.py +++ b/reddit-clone/reddit/users/mutations/user_create.py @@ -33,7 +33,6 @@ async def resolve_user_create(info: Info, input: UserCreateInput) -> UserCreateR user_create = strawberry.mutation( - name="user_create", resolver=resolve_user_create, description=""" Creates a new user. diff --git a/reddit-clone/reddit/users/mutations/user_deactivate.py b/reddit-clone/reddit/users/mutations/user_deactivate.py index 20a42786..8d339687 100644 --- a/reddit-clone/reddit/users/mutations/user_deactivate.py +++ b/reddit-clone/reddit/users/mutations/user_deactivate.py @@ -33,7 +33,6 @@ async def resolve_user_deactivate( user_deactivate = strawberry.mutation( - name="user_deactivate", resolver=resolve_user_deactivate, description=""" Deactivates the current user. diff --git a/reddit-clone/reddit/users/mutations/user_update.py b/reddit-clone/reddit/users/mutations/user_update.py index 314674c9..3740cebb 100644 --- a/reddit-clone/reddit/users/mutations/user_update.py +++ b/reddit-clone/reddit/users/mutations/user_update.py @@ -33,7 +33,6 @@ async def resolve_user_update(info: Info, input: UserUpdateInput) -> UserUpdateR user_update = strawberry.mutation( - name="user_update", resolver=resolve_user_update, description=""" Updates the current user. diff --git a/reddit-clone/reddit/users/queries.py b/reddit-clone/reddit/users/queries.py index cc54f45a..50fe6e13 100644 --- a/reddit-clone/reddit/users/queries.py +++ b/reddit-clone/reddit/users/queries.py @@ -12,7 +12,6 @@ async def resolve_user(info: Info, username: str) -> Optional[UserType]: user = strawberry.field( - name="user", resolver=resolve_user, description=""" Gets an user by username. @@ -25,7 +24,6 @@ async def resolve_current_user(info: Info) -> UserType: current_user = strawberry.field( - name="current_user", resolver=resolve_current_user, description=""" Gets the current user. From 076777fbae21043cac54aba75ac2ea3d3013152a Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sun, 10 Oct 2021 10:08:12 +0530 Subject: [PATCH 061/150] setup email client --- reddit-clone/.env.example | 3 +++ reddit-clone/poetry.lock | 18 +++++++++++++- reddit-clone/pyproject.toml | 1 + reddit-clone/reddit/emails.py | 42 +++++++++++++++++++++++++++++++++ reddit-clone/reddit/settings.py | 7 ++++++ 5 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 reddit-clone/.env.example create mode 100644 reddit-clone/reddit/emails.py diff --git a/reddit-clone/.env.example b/reddit-clone/.env.example new file mode 100644 index 00000000..44462925 --- /dev/null +++ b/reddit-clone/.env.example @@ -0,0 +1,3 @@ +DEBUG= +DATABASE_URI= +SECRET_KEY= diff --git a/reddit-clone/poetry.lock b/reddit-clone/poetry.lock index 0728f1a4..38bd324c 100644 --- a/reddit-clone/poetry.lock +++ b/reddit-clone/poetry.lock @@ -1,3 +1,15 @@ +[[package]] +name = "aiosmtplib" +version = "1.1.6" +description = "asyncio SMTP client" +category = "main" +optional = false +python-versions = ">=3.5.2,<4.0.0" + +[package.extras] +uvloop = ["uvloop (>=0.13,<0.15)"] +docs = ["sphinx (>=2,<4)", "sphinx_autodoc_typehints (>=1.7.0,<2.0.0)"] + [[package]] name = "alembic" version = "1.7.3" @@ -615,9 +627,13 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "c7f68ad613b93e290f94e02da38f6c0c46e7b049d42fcf911543dc7762ac2c97" +content-hash = "4bd91cde71e77298fc4bece81a46d4fbe0dc6e526c22ac3daa5f3b392eda8780" [metadata.files] +aiosmtplib = [ + {file = "aiosmtplib-1.1.6-py3-none-any.whl", hash = "sha256:84174765778b2c5e0e207fbce0a769202fcf0c3de81faa87cc03551a6333bfa9"}, + {file = "aiosmtplib-1.1.6.tar.gz", hash = "sha256:d138fe6ffecbc9e6320269690b9ac0b75e540ef96e8f5c77d4a306760014dce2"}, +] alembic = [ {file = "alembic-1.7.3-py3-none-any.whl", hash = "sha256:d0c580041f9f6487d5444df672a83da9be57398f39d6c1802bbedec6fefbeef6"}, {file = "alembic-1.7.3.tar.gz", hash = "sha256:bc5bdf03d1b9814ee4d72adc0b19df2123f6c50a60c1ea761733f3640feedb8d"}, diff --git a/reddit-clone/pyproject.toml b/reddit-clone/pyproject.toml index 0bca5049..b3ca7d3f 100644 --- a/reddit-clone/pyproject.toml +++ b/reddit-clone/pyproject.toml @@ -14,6 +14,7 @@ strawberry-graphql = {extras = ["asgi"], version = "^0.77.0"} fastapi = "^0.68.1" starlette = "^0.14" graphql-relay = "^3.1.0" +aiosmtplib = "^1.1.6" [tool.poetry.dev-dependencies] diff --git a/reddit-clone/reddit/emails.py b/reddit-clone/reddit/emails.py new file mode 100644 index 00000000..9c68ef87 --- /dev/null +++ b/reddit-clone/reddit/emails.py @@ -0,0 +1,42 @@ +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from typing import Optional + +from aiosmtplib import send + +from reddit.settings import ( + MAIL_SENDER, + MAIL_USERNAME, + MAIL_PASSWORD, + MAIL_HOST, + MAIL_PORT, +) + + +async def send_message( + recipient: str, subject: str, content: str, html_content: Optional[str] = None +) -> None: + """ + Sends a multipart email to the provided + recipient asynchronously. + """ + message = MIMEMultipart() + message["From"] = None + message["To"] = recipient + message["Subject"] = subject + + plain_text = MIMEText(content) + message.attach(plain_text) + + if html_content is not None: + html_text = MIMEText(html_content, "html") + message.attach(html_text) + + await send( + message=message, + sender=MAIL_SENDER, + username=MAIL_USERNAME, + password=MAIL_PASSWORD, + hostname=MAIL_HOST, + port=MAIL_PORT, + ) diff --git a/reddit-clone/reddit/settings.py b/reddit-clone/reddit/settings.py index 86fbcac9..0586fd26 100644 --- a/reddit-clone/reddit/settings.py +++ b/reddit-clone/reddit/settings.py @@ -8,3 +8,10 @@ # sqlalchemy database url. DATABASE_URI: str = config("DATABASE_URI", cast=str) + +# mail configuration. +MAIL_HOST: str = config("MAIL_HOST", cast=str) +MAIL_PORT: int = config("MAIL_PORT", cast=int) +MAIL_USERNAME: str = config("MAIL_USERNAME", cast=str) +MAIL_PASSWORD: str = config("MAIL_PASSWORD", cast=str) +MAIL_SENDER: str = config("MAIL_SENDER", cast=str) From 8033676af47ca4141ebf4d71b113f15f035b60f3 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sun, 10 Oct 2021 10:10:10 +0530 Subject: [PATCH 062/150] update example env file --- reddit-clone/.env.example | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/reddit-clone/.env.example b/reddit-clone/.env.example index 44462925..ddc0b3bf 100644 --- a/reddit-clone/.env.example +++ b/reddit-clone/.env.example @@ -1,3 +1,8 @@ DEBUG= DATABASE_URI= SECRET_KEY= +MAIL_HOST= +MAIL_PORT= +MAIL_USERNAME= +MAIL_PASSWORD= +MAIL_SENDER= From 3d15d8ad77165ae51b5f67f54b2ce744c7087541 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sun, 10 Oct 2021 10:12:17 +0530 Subject: [PATCH 063/150] rename email helper --- reddit-clone/reddit/__init__.py | 2 ++ reddit-clone/reddit/database.py | 2 ++ reddit-clone/reddit/emails.py | 4 +++- reddit-clone/reddit/schema.py | 2 ++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/reddit-clone/reddit/__init__.py b/reddit-clone/reddit/__init__.py index 5b1cccc8..976dd360 100644 --- a/reddit-clone/reddit/__init__.py +++ b/reddit-clone/reddit/__init__.py @@ -11,6 +11,8 @@ from reddit.schema import schema from reddit.users.loaders import load_users +__all__ = ("app",) + class MyGraphQL(GraphQL): async def get_context( diff --git a/reddit-clone/reddit/database.py b/reddit-clone/reddit/database.py index 03c74a2e..0e0d6db2 100644 --- a/reddit-clone/reddit/database.py +++ b/reddit-clone/reddit/database.py @@ -11,6 +11,8 @@ session_factory = sessionmaker(bind=engine, class_=AsyncSession) +__all__ = ("get_session", "Base") + @asynccontextmanager async def get_session() -> AsyncGenerator[AsyncSession, None]: diff --git a/reddit-clone/reddit/emails.py b/reddit-clone/reddit/emails.py index 9c68ef87..81768e20 100644 --- a/reddit-clone/reddit/emails.py +++ b/reddit-clone/reddit/emails.py @@ -12,8 +12,10 @@ MAIL_PORT, ) +__all__ = ("send_mail",) -async def send_message( + +async def send_mail( recipient: str, subject: str, content: str, html_content: Optional[str] = None ) -> None: """ diff --git a/reddit-clone/reddit/schema.py b/reddit-clone/reddit/schema.py index af03218e..1298ec23 100644 --- a/reddit-clone/reddit/schema.py +++ b/reddit-clone/reddit/schema.py @@ -8,6 +8,8 @@ from reddit.subreddits.mutations import SubredditMutation from reddit.users.mutations import UserMutation +__all__ = ("schema",) + @strawberry.type class Query(BaseQuery, UserQuery, SubredditQuery): From afafbbd8c8b7635e7a07d5983917d16e2669a8f8 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sun, 10 Oct 2021 10:22:00 +0530 Subject: [PATCH 064/150] update README.md --- reddit-clone/README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/reddit-clone/README.md b/reddit-clone/README.md index 4f4f15ee..7b3b198a 100644 --- a/reddit-clone/README.md +++ b/reddit-clone/README.md @@ -7,17 +7,18 @@ most of the features that Strawberry gives us. ## Tech Stack used: -- Strawberry GraphQL -- FastAPI w/ Starlette -- SQLAlchemy (asyncio) -- Alembic for migrations +- [Strawberry GraphQL](https://github.com/strawberry-graphql/strawberry) +- [FastAPI](https://github.com/tiangolo/fastapi) w/ [Starlette](https://github.com/encode/starlette) +- [SQLAlchemy](https://github.com/sqlalchemy/sqlalchemy) (asyncio) +- [Alembic](https://github.com/sqlalchemy/alembic) for migrations ## Features at a glance -- Error handling with unions -- Authorization with the permissions API -- Batch loading with dataloaders -- Pagination with SQLAlchemy +- [ ] Error handling with unions +- [ ] Authorization with the permissions API +- [ ] Batch loading with dataloaders +- [ ] Pagination with SQLAlchemy +- [x] modular codebase ## How to use From fc47ef70eb7039f0713b3221af8f8508e45dc72c Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sun, 10 Oct 2021 10:35:10 +0530 Subject: [PATCH 065/150] update setup instructions --- reddit-clone/.env.example | 7 +++---- reddit-clone/README.md | 19 ++++++++----------- reddit-clone/reddit/__init__.py | 6 +++--- reddit-clone/reddit/database.py | 4 ++-- reddit-clone/reddit/emails.py | 18 ++++++------------ reddit-clone/reddit/settings.py | 12 +++++++----- 6 files changed, 29 insertions(+), 37 deletions(-) diff --git a/reddit-clone/.env.example b/reddit-clone/.env.example index ddc0b3bf..c120265a 100644 --- a/reddit-clone/.env.example +++ b/reddit-clone/.env.example @@ -1,8 +1,7 @@ +DATABASE_URI=postgresql+asyncpg://localhost/db_name?user=postgres&password=postgres +MAIL_HOST=127.0.0.1 +MAIL_PORT=25 DEBUG= -DATABASE_URI= -SECRET_KEY= -MAIL_HOST= -MAIL_PORT= MAIL_USERNAME= MAIL_PASSWORD= MAIL_SENDER= diff --git a/reddit-clone/README.md b/reddit-clone/README.md index 7b3b198a..32029f4f 100644 --- a/reddit-clone/README.md +++ b/reddit-clone/README.md @@ -30,17 +30,14 @@ Use [poetry](https://python-poetry.org/) to install dependencies: poetry install ``` -2. setup database - This example uses a PostgreSQL database. Make sure you have it installed - on your machine, and configure the `DATABASE_URI` environment variable. +2. Setup database + This example needs a PostgreSQL database. Make sure you have one on your machine. - An example database URL: +3. Configure envionment variables + Next up, you'd need to setup your environment variables. There is an example [`.env`](.env.example) file + which you can reference! (Blank variables are optional) - ```text - postgresql+asyncpg://localhost/db_name?user=user&password=password - ``` - -3. Run migrations +4. Run migrations Run [alembic](https://alembic.sqlalchemy.org/en/latest/) to create the database and populate it with movie data: @@ -49,7 +46,7 @@ and populate it with movie data: poetry run alembic upgrade head ``` -4. Run the server +5. Run the server Run [uvicorn](https://www.uvicorn.org/) to run the server: @@ -57,4 +54,4 @@ Run [uvicorn](https://www.uvicorn.org/) to run the server: poetry run uvicorn reddit:app --reload ``` -The GraphQL API should now be available at http://localhost:8000/graphql +You can now explore the GraphQL API here: http://localhost:8000/graphql diff --git a/reddit-clone/reddit/__init__.py b/reddit-clone/reddit/__init__.py index 976dd360..1fd94fb2 100644 --- a/reddit-clone/reddit/__init__.py +++ b/reddit-clone/reddit/__init__.py @@ -7,7 +7,7 @@ from strawberry.dataloader import DataLoader from strawberry.asgi import GraphQL -from reddit.settings import DEBUG +from reddit import settings from reddit.schema import schema from reddit.users.loaders import load_users @@ -29,9 +29,9 @@ def create_application() -> FastAPI: :return: The created application. """ - application = FastAPI(title="Reddit GraphQL", debug=DEBUG) + application = FastAPI(title="Reddit GraphQL", debug=settings.DEBUG) - graphql_app = MyGraphQL(schema=schema, graphiql=True, debug=DEBUG) + graphql_app = MyGraphQL(schema=schema, graphiql=True, debug=settings.DEBUG) application.add_route(path="/graphql", route=graphql_app) diff --git a/reddit-clone/reddit/database.py b/reddit-clone/reddit/database.py index 0e0d6db2..2f7b7c23 100644 --- a/reddit-clone/reddit/database.py +++ b/reddit-clone/reddit/database.py @@ -5,9 +5,9 @@ from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine from sqlalchemy.ext.declarative import declarative_base -from .settings import DATABASE_URI +from reddit import settings -engine = create_async_engine(DATABASE_URI, future=True) +engine = create_async_engine(settings.DATABASE_URI, future=True) session_factory = sessionmaker(bind=engine, class_=AsyncSession) diff --git a/reddit-clone/reddit/emails.py b/reddit-clone/reddit/emails.py index 81768e20..7a222fab 100644 --- a/reddit-clone/reddit/emails.py +++ b/reddit-clone/reddit/emails.py @@ -4,13 +4,7 @@ from aiosmtplib import send -from reddit.settings import ( - MAIL_SENDER, - MAIL_USERNAME, - MAIL_PASSWORD, - MAIL_HOST, - MAIL_PORT, -) +from reddit import settings __all__ = ("send_mail",) @@ -36,9 +30,9 @@ async def send_mail( await send( message=message, - sender=MAIL_SENDER, - username=MAIL_USERNAME, - password=MAIL_PASSWORD, - hostname=MAIL_HOST, - port=MAIL_PORT, + sender=settings.MAIL_SENDER, + username=settings.MAIL_USERNAME, + password=settings.MAIL_PASSWORD, + hostname=settings.MAIL_HOST, + port=settings.MAIL_PORT, ) diff --git a/reddit-clone/reddit/settings.py b/reddit-clone/reddit/settings.py index 0586fd26..12ac8eee 100644 --- a/reddit-clone/reddit/settings.py +++ b/reddit-clone/reddit/settings.py @@ -1,3 +1,5 @@ +from typing import Optional + from starlette.config import Config @@ -6,12 +8,12 @@ # whether the application is in development mode. DEBUG: bool = config("DEBUG", cast=bool, default=False) -# sqlalchemy database url. +# SQLAlchemy database URL. DATABASE_URI: str = config("DATABASE_URI", cast=str) -# mail configuration. +# mail client configuration. MAIL_HOST: str = config("MAIL_HOST", cast=str) MAIL_PORT: int = config("MAIL_PORT", cast=int) -MAIL_USERNAME: str = config("MAIL_USERNAME", cast=str) -MAIL_PASSWORD: str = config("MAIL_PASSWORD", cast=str) -MAIL_SENDER: str = config("MAIL_SENDER", cast=str) +MAIL_USERNAME: Optional[str] = config("MAIL_USERNAME", cast=str, default=None) +MAIL_PASSWORD: Optional[str] = config("MAIL_PASSWORD", cast=str, default=None) +MAIL_SENDER: Optional[str] = config("MAIL_SENDER", cast=str, default=None) From ce67dd3940729b7d7b58ad4ca21e507c7e9ad810 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sun, 10 Oct 2021 10:44:21 +0530 Subject: [PATCH 066/150] fix model: comment --- reddit-clone/reddit/comments/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/reddit-clone/reddit/comments/models.py b/reddit-clone/reddit/comments/models.py index 62f3bce7..80ec648e 100644 --- a/reddit-clone/reddit/comments/models.py +++ b/reddit-clone/reddit/comments/models.py @@ -10,7 +10,7 @@ class Comment(Base): """ - Represents a Comment in a thread. + Represents a Comment on a post. """ __tablename__ = "comments" @@ -23,6 +23,8 @@ class Comment(Base): user_id: Optional[int] = Column(Integer, ForeignKey("users.id")) + post_id: int = Column(Integer, ForeignKey("posts.id")) + replies: List[Comment] = relationship( "Comment", back_populates="parent", lazy="dynamic" ) From c05389915357f8cc8967a8dc24ef3e4fe229ee57 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sun, 10 Oct 2021 10:45:52 +0530 Subject: [PATCH 067/150] update README.md --- reddit-clone/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reddit-clone/README.md b/reddit-clone/README.md index 32029f4f..391d7c20 100644 --- a/reddit-clone/README.md +++ b/reddit-clone/README.md @@ -14,7 +14,7 @@ most of the features that Strawberry gives us. ## Features at a glance -- [ ] Error handling with unions +- [ ] Error modelling within the schema - [ ] Authorization with the permissions API - [ ] Batch loading with dataloaders - [ ] Pagination with SQLAlchemy From da35bd9f14061b8ff136ee3783d4931f0cae4196 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sun, 10 Oct 2021 15:09:34 +0530 Subject: [PATCH 068/150] use schematics for data validation --- reddit-clone/poetry.lock | 14 +++++++++++++- reddit-clone/pyproject.toml | 1 + reddit-clone/reddit/users/serializers.py | 0 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 reddit-clone/reddit/users/serializers.py diff --git a/reddit-clone/poetry.lock b/reddit-clone/poetry.lock index 38bd324c..130dacf8 100644 --- a/reddit-clone/poetry.lock +++ b/reddit-clone/poetry.lock @@ -434,6 +434,14 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "schematics" +version = "2.1.1" +description = "Python Data Structures for Humans" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "six" version = "1.16.0" @@ -627,7 +635,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "4bd91cde71e77298fc4bece81a46d4fbe0dc6e526c22ac3daa5f3b392eda8780" +content-hash = "87e854daf2789f690051ca6542bccb2e94c7c4f3cb2ba41db8f2ea1aa2413913" [metadata.files] aiosmtplib = [ @@ -984,6 +992,10 @@ regex = [ {file = "regex-2021.9.30-cp39-cp39-win_amd64.whl", hash = "sha256:3b71213ec3bad9a5a02e049f2ec86b3d7c3e350129ae0f4e2f99c12b5da919ed"}, {file = "regex-2021.9.30.tar.gz", hash = "sha256:81e125d9ba54c34579e4539a967e976a3c56150796674aec318b1b2f49251be7"}, ] +schematics = [ + {file = "schematics-2.1.1-py2.py3-none-any.whl", hash = "sha256:be2d451bfb86789975e5ec0864aec569b63cea9010f0d24cbbd992a4e564c647"}, + {file = "schematics-2.1.1.tar.gz", hash = "sha256:34c87f51a25063bb498ae1cc201891b134cfcb329baf9e9f4f3ae869b767560f"}, +] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, diff --git a/reddit-clone/pyproject.toml b/reddit-clone/pyproject.toml index b3ca7d3f..bb99aa3a 100644 --- a/reddit-clone/pyproject.toml +++ b/reddit-clone/pyproject.toml @@ -15,6 +15,7 @@ fastapi = "^0.68.1" starlette = "^0.14" graphql-relay = "^3.1.0" aiosmtplib = "^1.1.6" +schematics = "^2.1.1" [tool.poetry.dev-dependencies] diff --git a/reddit-clone/reddit/users/serializers.py b/reddit-clone/reddit/users/serializers.py new file mode 100644 index 00000000..e69de29b From 560c8e6e58a214b2c428ece764e3af2a23fb236a Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sun, 10 Oct 2021 17:03:54 +0530 Subject: [PATCH 069/150] add user service --- reddit-clone/poetry.lock | 101 +++++++++++++++++- reddit-clone/pyproject.toml | 1 + reddit-clone/reddit/base/services.py | 12 +++ reddit-clone/reddit/users/models.py | 2 + .../reddit/users/mutations/authenticate.py | 11 +- .../reddit/users/mutations/user_create.py | 9 +- reddit-clone/reddit/users/serializers.py | 0 reddit-clone/reddit/users/services.py | 55 ++++++++++ 8 files changed, 186 insertions(+), 5 deletions(-) create mode 100644 reddit-clone/reddit/base/services.py delete mode 100644 reddit-clone/reddit/users/serializers.py create mode 100644 reddit-clone/reddit/users/services.py diff --git a/reddit-clone/poetry.lock b/reddit-clone/poetry.lock index 130dacf8..7c62cc79 100644 --- a/reddit-clone/poetry.lock +++ b/reddit-clone/poetry.lock @@ -27,6 +27,22 @@ SQLAlchemy = ">=1.3.0" [package.extras] tz = ["python-dateutil"] +[[package]] +name = "argon2-cffi" +version = "21.1.0" +description = "The secure Argon2 password hashing algorithm." +category = "main" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +cffi = ">=1.0.0" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pytest", "sphinx", "furo", "wheel", "pre-commit"] +docs = ["sphinx", "furo"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pytest"] + [[package]] name = "asgiref" version = "3.4.1" @@ -107,6 +123,17 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "cffi" +version = "1.14.6" +description = "Foreign Function Interface for Python calling C code." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +pycparser = "*" + [[package]] name = "click" version = "8.0.1" @@ -354,6 +381,14 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "pycparser" +version = "2.20" +description = "C parser in Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + [[package]] name = "pydantic" version = "1.8.2" @@ -635,7 +670,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "87e854daf2789f690051ca6542bccb2e94c7c4f3cb2ba41db8f2ea1aa2413913" +content-hash = "4fc35f54f9b160fc75e33293576947b81057524eb6786aa11dd8cdff8d2befb1" [metadata.files] aiosmtplib = [ @@ -646,6 +681,19 @@ alembic = [ {file = "alembic-1.7.3-py3-none-any.whl", hash = "sha256:d0c580041f9f6487d5444df672a83da9be57398f39d6c1802bbedec6fefbeef6"}, {file = "alembic-1.7.3.tar.gz", hash = "sha256:bc5bdf03d1b9814ee4d72adc0b19df2123f6c50a60c1ea761733f3640feedb8d"}, ] +argon2-cffi = [ + {file = "argon2-cffi-21.1.0.tar.gz", hash = "sha256:f710b61103d1a1f692ca3ecbd1373e28aa5e545ac625ba067ff2feca1b2bb870"}, + {file = "argon2_cffi-21.1.0-cp35-abi3-macosx_10_14_x86_64.whl", hash = "sha256:217b4f0f853ccbbb5045242946ad2e162e396064575860141b71a85eb47e475a"}, + {file = "argon2_cffi-21.1.0-cp35-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fa7e7d1fc22514a32b1761fdfa1882b6baa5c36bb3ef557bdd69e6fc9ba14a41"}, + {file = "argon2_cffi-21.1.0-cp35-abi3-win32.whl", hash = "sha256:e4d8f0ae1524b7b0372a3e574a2561cbdddb3fdb6c28b70a72868189bda19659"}, + {file = "argon2_cffi-21.1.0-cp35-abi3-win_amd64.whl", hash = "sha256:65213a9174320a1aee03fe826596e0620783966b49eb636955958b3074e87ff9"}, + {file = "argon2_cffi-21.1.0-pp36-pypy36_pp73-macosx_10_7_x86_64.whl", hash = "sha256:245f64a203012b144b7b8c8ea6d468cb02b37caa5afee5ba4a10c80599334f6a"}, + {file = "argon2_cffi-21.1.0-pp36-pypy36_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4ad152c418f7eb640eac41ac815534e6aa61d1624530b8e7779114ecfbf327f8"}, + {file = "argon2_cffi-21.1.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:bc513db2283c385ea4da31a2cd039c33380701f376f4edd12fe56db118a3b21a"}, + {file = "argon2_cffi-21.1.0-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:c7a7c8cc98ac418002090e4add5bebfff1b915ea1cb459c578cd8206fef10378"}, + {file = "argon2_cffi-21.1.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:165cadae5ac1e26644f5ade3bd9c18d89963be51d9ea8817bd671006d7909057"}, + {file = "argon2_cffi-21.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:566ffb581bbd9db5562327aee71b2eda24a1c15b23a356740abe3c011bbe0dcb"}, +] asgiref = [ {file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"}, {file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"}, @@ -677,6 +725,53 @@ cached-property = [ {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, ] +cffi = [ + {file = "cffi-1.14.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c"}, + {file = "cffi-1.14.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99"}, + {file = "cffi-1.14.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819"}, + {file = "cffi-1.14.6-cp27-cp27m-win32.whl", hash = "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20"}, + {file = "cffi-1.14.6-cp27-cp27m-win_amd64.whl", hash = "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224"}, + {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7"}, + {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33"}, + {file = "cffi-1.14.6-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534"}, + {file = "cffi-1.14.6-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a"}, + {file = "cffi-1.14.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5"}, + {file = "cffi-1.14.6-cp35-cp35m-win32.whl", hash = "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca"}, + {file = "cffi-1.14.6-cp35-cp35m-win_amd64.whl", hash = "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218"}, + {file = "cffi-1.14.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f"}, + {file = "cffi-1.14.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872"}, + {file = "cffi-1.14.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195"}, + {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d"}, + {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b"}, + {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb"}, + {file = "cffi-1.14.6-cp36-cp36m-win32.whl", hash = "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a"}, + {file = "cffi-1.14.6-cp36-cp36m-win_amd64.whl", hash = "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e"}, + {file = "cffi-1.14.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5"}, + {file = "cffi-1.14.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf"}, + {file = "cffi-1.14.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69"}, + {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56"}, + {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c"}, + {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762"}, + {file = "cffi-1.14.6-cp37-cp37m-win32.whl", hash = "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771"}, + {file = "cffi-1.14.6-cp37-cp37m-win_amd64.whl", hash = "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a"}, + {file = "cffi-1.14.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0"}, + {file = "cffi-1.14.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e"}, + {file = "cffi-1.14.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346"}, + {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc"}, + {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd"}, + {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc"}, + {file = "cffi-1.14.6-cp38-cp38-win32.whl", hash = "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548"}, + {file = "cffi-1.14.6-cp38-cp38-win_amd64.whl", hash = "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156"}, + {file = "cffi-1.14.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d"}, + {file = "cffi-1.14.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e"}, + {file = "cffi-1.14.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c"}, + {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202"}, + {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f"}, + {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87"}, + {file = "cffi-1.14.6-cp39-cp39-win32.whl", hash = "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728"}, + {file = "cffi-1.14.6-cp39-cp39-win_amd64.whl", hash = "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2"}, + {file = "cffi-1.14.6.tar.gz", hash = "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd"}, +] click = [ {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, @@ -875,6 +970,10 @@ pycodestyle = [ {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, ] +pycparser = [ + {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, + {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, +] pydantic = [ {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"}, {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"}, diff --git a/reddit-clone/pyproject.toml b/reddit-clone/pyproject.toml index bb99aa3a..55ff1b14 100644 --- a/reddit-clone/pyproject.toml +++ b/reddit-clone/pyproject.toml @@ -16,6 +16,7 @@ starlette = "^0.14" graphql-relay = "^3.1.0" aiosmtplib = "^1.1.6" schematics = "^2.1.1" +argon2-cffi = "^21.1.0" [tool.poetry.dev-dependencies] diff --git a/reddit-clone/reddit/base/services.py b/reddit-clone/reddit/base/services.py new file mode 100644 index 00000000..6001c2db --- /dev/null +++ b/reddit-clone/reddit/base/services.py @@ -0,0 +1,12 @@ +from sqlalchemy.ext.asyncio.session import AsyncSession + + +class BaseService: + """ + The base class that every service must subclass. + Services are classes which implement business logic + and improve code reusability. + """ + + def __init__(self, session: AsyncSession) -> None: + self._session = session diff --git a/reddit-clone/reddit/users/models.py b/reddit-clone/reddit/users/models.py index a1b8c0bf..df4b8601 100644 --- a/reddit-clone/reddit/users/models.py +++ b/reddit-clone/reddit/users/models.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import List from sqlalchemy import Column, String, Integer, ForeignKey diff --git a/reddit-clone/reddit/users/mutations/authenticate.py b/reddit-clone/reddit/users/mutations/authenticate.py index 8983d5b8..346f22ef 100644 --- a/reddit-clone/reddit/users/mutations/authenticate.py +++ b/reddit-clone/reddit/users/mutations/authenticate.py @@ -1,9 +1,11 @@ -from typing import Union +from typing import Union, cast import strawberry from strawberry.types import Info +from reddit.database import get_session from reddit.users.types import UserType +from reddit.users.services import UserService __all__ = ("authenticate",) @@ -31,7 +33,12 @@ class AuthenticateError: async def resolve_authenticate( info: Info, input: AuthenticateInput ) -> AuthenticateResult: - pass + async with get_session() as session: + service = UserService(session=session) + user = await service.authenticate(**input) + if user is None: + return AuthenticateError(error="Invalid credentials provided.") + return AuthenticateSuccess(user=cast(UserType, user)) authenticate = strawberry.mutation( diff --git a/reddit-clone/reddit/users/mutations/user_create.py b/reddit-clone/reddit/users/mutations/user_create.py index 30ca3003..f6ea7c86 100644 --- a/reddit-clone/reddit/users/mutations/user_create.py +++ b/reddit-clone/reddit/users/mutations/user_create.py @@ -1,9 +1,11 @@ -from typing import Union +from typing import Union, cast import strawberry from strawberry.types import Info +from reddit.database import get_session from reddit.users.types import UserType +from reddit.users.services import UserService __all__ = ("user_create",) @@ -29,7 +31,10 @@ class UserCreateError: async def resolve_user_create(info: Info, input: UserCreateInput) -> UserCreateResult: - pass + async with get_session() as session: + service = UserService(session=session) + user = await service.create_user(**input) + return UserCreateSuccess(user=cast(UserType, user)) user_create = strawberry.mutation( diff --git a/reddit-clone/reddit/users/serializers.py b/reddit-clone/reddit/users/serializers.py deleted file mode 100644 index e69de29b..00000000 diff --git a/reddit-clone/reddit/users/services.py b/reddit-clone/reddit/users/services.py new file mode 100644 index 00000000..38695c85 --- /dev/null +++ b/reddit-clone/reddit/users/services.py @@ -0,0 +1,55 @@ +from typing import Optional + +from argon2 import PasswordHasher +from argon2.exceptions import VerifyMismatchError +from sqlalchemy import select + +from reddit.base.services import BaseService +from reddit.users.models import User + + +class UserService(BaseService): + _hasher = PasswordHasher() + + async def create_user(self, username: str, password: str, email: str) -> User: + """ + Creates an user instance. + """ + # TODO: validate input before creating + user = User( + email=email, + username=username, + password=self._hasher.hash(password=password), + ) + self._session.add(instance=user) + await self._session.commit() + await self._session.refresh(instance=user) + return user + + async def get_by_username(self, username: str) -> Optional[User]: + """ + Gets an user by their username. + """ + stmt = select(User).filter_by(username=username).first() + return await self._session.execute(stmt) + + async def authenticate(self, username: str, password: str) -> Optional[User]: + """ + Checks whether the given credentials match + and returns the associated user instance. + """ + user = self.get_by_username(username=username) + if user is None: + return user + try: + self._hasher.verify(hash=user.password, password=password) + except VerifyMismatchError: + return None + + if self._hasher.check_needs_rehash(hash=user.password): + # recalculate the user's password hash. + user.password = self._hasher.hash(password=password) + self._session.add(instance=user) + await self._session.commit() + await self._session.refresh(instance=user) + return user From 81f281b01b067f6a3241a737a2ca10f446181ca2 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sun, 10 Oct 2021 18:33:40 +0530 Subject: [PATCH 070/150] add user services --- reddit-clone/reddit/base/services.py | 12 -- .../reddit/users/mutations/authenticate.py | 11 +- .../reddit/users/mutations/email_change.py | 1 + .../reddit/users/mutations/user_create.py | 9 +- reddit-clone/reddit/users/services.py | 122 +++++++++++------- 5 files changed, 78 insertions(+), 77 deletions(-) delete mode 100644 reddit-clone/reddit/base/services.py diff --git a/reddit-clone/reddit/base/services.py b/reddit-clone/reddit/base/services.py deleted file mode 100644 index 6001c2db..00000000 --- a/reddit-clone/reddit/base/services.py +++ /dev/null @@ -1,12 +0,0 @@ -from sqlalchemy.ext.asyncio.session import AsyncSession - - -class BaseService: - """ - The base class that every service must subclass. - Services are classes which implement business logic - and improve code reusability. - """ - - def __init__(self, session: AsyncSession) -> None: - self._session = session diff --git a/reddit-clone/reddit/users/mutations/authenticate.py b/reddit-clone/reddit/users/mutations/authenticate.py index 346f22ef..8983d5b8 100644 --- a/reddit-clone/reddit/users/mutations/authenticate.py +++ b/reddit-clone/reddit/users/mutations/authenticate.py @@ -1,11 +1,9 @@ -from typing import Union, cast +from typing import Union import strawberry from strawberry.types import Info -from reddit.database import get_session from reddit.users.types import UserType -from reddit.users.services import UserService __all__ = ("authenticate",) @@ -33,12 +31,7 @@ class AuthenticateError: async def resolve_authenticate( info: Info, input: AuthenticateInput ) -> AuthenticateResult: - async with get_session() as session: - service = UserService(session=session) - user = await service.authenticate(**input) - if user is None: - return AuthenticateError(error="Invalid credentials provided.") - return AuthenticateSuccess(user=cast(UserType, user)) + pass authenticate = strawberry.mutation( diff --git a/reddit-clone/reddit/users/mutations/email_change.py b/reddit-clone/reddit/users/mutations/email_change.py index b08f6036..5abd905d 100644 --- a/reddit-clone/reddit/users/mutations/email_change.py +++ b/reddit-clone/reddit/users/mutations/email_change.py @@ -12,6 +12,7 @@ class EmailChangeInput: email: str change_code: str + password: str @strawberry.type diff --git a/reddit-clone/reddit/users/mutations/user_create.py b/reddit-clone/reddit/users/mutations/user_create.py index f6ea7c86..30ca3003 100644 --- a/reddit-clone/reddit/users/mutations/user_create.py +++ b/reddit-clone/reddit/users/mutations/user_create.py @@ -1,11 +1,9 @@ -from typing import Union, cast +from typing import Union import strawberry from strawberry.types import Info -from reddit.database import get_session from reddit.users.types import UserType -from reddit.users.services import UserService __all__ = ("user_create",) @@ -31,10 +29,7 @@ class UserCreateError: async def resolve_user_create(info: Info, input: UserCreateInput) -> UserCreateResult: - async with get_session() as session: - service = UserService(session=session) - user = await service.create_user(**input) - return UserCreateSuccess(user=cast(UserType, user)) + pass user_create = strawberry.mutation( diff --git a/reddit-clone/reddit/users/services.py b/reddit-clone/reddit/users/services.py index 38695c85..0b1ce40f 100644 --- a/reddit-clone/reddit/users/services.py +++ b/reddit-clone/reddit/users/services.py @@ -1,55 +1,79 @@ from typing import Optional -from argon2 import PasswordHasher -from argon2.exceptions import VerifyMismatchError -from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession -from reddit.base.services import BaseService from reddit.users.models import User -class UserService(BaseService): - _hasher = PasswordHasher() - - async def create_user(self, username: str, password: str, email: str) -> User: - """ - Creates an user instance. - """ - # TODO: validate input before creating - user = User( - email=email, - username=username, - password=self._hasher.hash(password=password), - ) - self._session.add(instance=user) - await self._session.commit() - await self._session.refresh(instance=user) - return user - - async def get_by_username(self, username: str) -> Optional[User]: - """ - Gets an user by their username. - """ - stmt = select(User).filter_by(username=username).first() - return await self._session.execute(stmt) - - async def authenticate(self, username: str, password: str) -> Optional[User]: - """ - Checks whether the given credentials match - and returns the associated user instance. - """ - user = self.get_by_username(username=username) - if user is None: - return user - try: - self._hasher.verify(hash=user.password, password=password) - except VerifyMismatchError: - return None - - if self._hasher.check_needs_rehash(hash=user.password): - # recalculate the user's password hash. - user.password = self._hasher.hash(password=password) - self._session.add(instance=user) - await self._session.commit() - await self._session.refresh(instance=user) - return user +async def authenticate( + session: AsyncSession, username: str, password: str +) -> Optional[User]: + """ + Checks if the provided credentials are valid. + """ + + +async def create_user( + session: AsyncSession, email: str, username: str, password: str +) -> User: + """ + Creates a new user instance. + """ + user = User(email=email, username=username, password=str) + session.add(instance=user) + await session.commit() + await session.refresh(instance=user) + return user + + +async def update_user(session: AsyncSession, user: User): + """ + Updates the given user instance. + """ + + +async def remove_avatar(session: AsyncSession, user: User): + """ + Removes the avatar for the given user instance. + """ + + +async def request_change_email(session: AsyncSession, email: str, password: str): + """ + Sends an email change code to the user's new email. + """ + + +async def change_email( + session: AsyncSession, email: str, change_code: str, password: str +): + """ + Changes the email for the given user instance. + """ + + +async def request_reset_password(session: AsyncSession, email: str): + """ + Sends a password reset code to the given email, if it + actually exists. + """ + + +async def reset_password( + session: AsyncSession, password: str, reset_code: str, email: str +): + """ + Resets the password for the given user instance. + """ + + +async def block_user(session: AsyncSession, user_id: int, user: User): + """ + Blocks the user account for the given user instance. + """ + + +async def deactivate_user(session: AsyncSession, password: str, user: User): + """ + Deactivates the given user instance. + """ From eb7bd5f8b9db3eab3b964ec931f66ba2068a1988 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sun, 10 Oct 2021 18:34:39 +0530 Subject: [PATCH 071/150] update user services --- reddit-clone/reddit/users/services.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reddit-clone/reddit/users/services.py b/reddit-clone/reddit/users/services.py index 0b1ce40f..639f8613 100644 --- a/reddit-clone/reddit/users/services.py +++ b/reddit-clone/reddit/users/services.py @@ -19,7 +19,7 @@ async def create_user( """ Creates a new user instance. """ - user = User(email=email, username=username, password=str) + user = User(email=email, username=username, password=password) session.add(instance=user) await session.commit() await session.refresh(instance=user) From 9c221ba29097ea76e18402a8cbb4b79efad8cb4e Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sun, 10 Oct 2021 18:59:42 +0530 Subject: [PATCH 072/150] update user services --- reddit-clone/poetry.lock | 119 ++++---------------------- reddit-clone/pyproject.toml | 2 +- reddit-clone/reddit/users/models.py | 28 +++++- reddit-clone/reddit/users/services.py | 18 +++- 4 files changed, 61 insertions(+), 106 deletions(-) diff --git a/reddit-clone/poetry.lock b/reddit-clone/poetry.lock index 7c62cc79..9f44abcf 100644 --- a/reddit-clone/poetry.lock +++ b/reddit-clone/poetry.lock @@ -27,22 +27,6 @@ SQLAlchemy = ">=1.3.0" [package.extras] tz = ["python-dateutil"] -[[package]] -name = "argon2-cffi" -version = "21.1.0" -description = "The secure Argon2 password hashing algorithm." -category = "main" -optional = false -python-versions = ">=3.5" - -[package.dependencies] -cffi = ">=1.0.0" - -[package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pytest", "sphinx", "furo", "wheel", "pre-commit"] -docs = ["sphinx", "furo"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pytest"] - [[package]] name = "asgiref" version = "3.4.1" @@ -123,17 +107,6 @@ category = "main" optional = false python-versions = "*" -[[package]] -name = "cffi" -version = "1.14.6" -description = "Foreign Function Interface for Python calling C code." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -pycparser = "*" - [[package]] name = "click" version = "8.0.1" @@ -353,6 +326,20 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "passlib" +version = "1.7.4" +description = "comprehensive password hashing framework supporting over 30 schemes" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +argon2 = ["argon2-cffi (>=18.2.0)"] +bcrypt = ["bcrypt (>=3.1.0)"] +build_docs = ["sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)", "cloud-sptheme (>=1.10.1)"] +totp = ["cryptography"] + [[package]] name = "pathspec" version = "0.9.0" @@ -381,14 +368,6 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[[package]] -name = "pycparser" -version = "2.20" -description = "C parser in Python" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - [[package]] name = "pydantic" version = "1.8.2" @@ -670,7 +649,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "4fc35f54f9b160fc75e33293576947b81057524eb6786aa11dd8cdff8d2befb1" +content-hash = "03a61f70a8c276ecdeb0f5dcd3c519f5b4dbe2187ce15e4a68c607f5e594c7e0" [metadata.files] aiosmtplib = [ @@ -681,19 +660,6 @@ alembic = [ {file = "alembic-1.7.3-py3-none-any.whl", hash = "sha256:d0c580041f9f6487d5444df672a83da9be57398f39d6c1802bbedec6fefbeef6"}, {file = "alembic-1.7.3.tar.gz", hash = "sha256:bc5bdf03d1b9814ee4d72adc0b19df2123f6c50a60c1ea761733f3640feedb8d"}, ] -argon2-cffi = [ - {file = "argon2-cffi-21.1.0.tar.gz", hash = "sha256:f710b61103d1a1f692ca3ecbd1373e28aa5e545ac625ba067ff2feca1b2bb870"}, - {file = "argon2_cffi-21.1.0-cp35-abi3-macosx_10_14_x86_64.whl", hash = "sha256:217b4f0f853ccbbb5045242946ad2e162e396064575860141b71a85eb47e475a"}, - {file = "argon2_cffi-21.1.0-cp35-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fa7e7d1fc22514a32b1761fdfa1882b6baa5c36bb3ef557bdd69e6fc9ba14a41"}, - {file = "argon2_cffi-21.1.0-cp35-abi3-win32.whl", hash = "sha256:e4d8f0ae1524b7b0372a3e574a2561cbdddb3fdb6c28b70a72868189bda19659"}, - {file = "argon2_cffi-21.1.0-cp35-abi3-win_amd64.whl", hash = "sha256:65213a9174320a1aee03fe826596e0620783966b49eb636955958b3074e87ff9"}, - {file = "argon2_cffi-21.1.0-pp36-pypy36_pp73-macosx_10_7_x86_64.whl", hash = "sha256:245f64a203012b144b7b8c8ea6d468cb02b37caa5afee5ba4a10c80599334f6a"}, - {file = "argon2_cffi-21.1.0-pp36-pypy36_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4ad152c418f7eb640eac41ac815534e6aa61d1624530b8e7779114ecfbf327f8"}, - {file = "argon2_cffi-21.1.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:bc513db2283c385ea4da31a2cd039c33380701f376f4edd12fe56db118a3b21a"}, - {file = "argon2_cffi-21.1.0-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:c7a7c8cc98ac418002090e4add5bebfff1b915ea1cb459c578cd8206fef10378"}, - {file = "argon2_cffi-21.1.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:165cadae5ac1e26644f5ade3bd9c18d89963be51d9ea8817bd671006d7909057"}, - {file = "argon2_cffi-21.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:566ffb581bbd9db5562327aee71b2eda24a1c15b23a356740abe3c011bbe0dcb"}, -] asgiref = [ {file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"}, {file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"}, @@ -725,53 +691,6 @@ cached-property = [ {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, ] -cffi = [ - {file = "cffi-1.14.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c"}, - {file = "cffi-1.14.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99"}, - {file = "cffi-1.14.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819"}, - {file = "cffi-1.14.6-cp27-cp27m-win32.whl", hash = "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20"}, - {file = "cffi-1.14.6-cp27-cp27m-win_amd64.whl", hash = "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224"}, - {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7"}, - {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33"}, - {file = "cffi-1.14.6-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534"}, - {file = "cffi-1.14.6-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a"}, - {file = "cffi-1.14.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5"}, - {file = "cffi-1.14.6-cp35-cp35m-win32.whl", hash = "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca"}, - {file = "cffi-1.14.6-cp35-cp35m-win_amd64.whl", hash = "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218"}, - {file = "cffi-1.14.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb"}, - {file = "cffi-1.14.6-cp36-cp36m-win32.whl", hash = "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a"}, - {file = "cffi-1.14.6-cp36-cp36m-win_amd64.whl", hash = "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e"}, - {file = "cffi-1.14.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762"}, - {file = "cffi-1.14.6-cp37-cp37m-win32.whl", hash = "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771"}, - {file = "cffi-1.14.6-cp37-cp37m-win_amd64.whl", hash = "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a"}, - {file = "cffi-1.14.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc"}, - {file = "cffi-1.14.6-cp38-cp38-win32.whl", hash = "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548"}, - {file = "cffi-1.14.6-cp38-cp38-win_amd64.whl", hash = "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156"}, - {file = "cffi-1.14.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87"}, - {file = "cffi-1.14.6-cp39-cp39-win32.whl", hash = "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728"}, - {file = "cffi-1.14.6-cp39-cp39-win_amd64.whl", hash = "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2"}, - {file = "cffi-1.14.6.tar.gz", hash = "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd"}, -] click = [ {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, @@ -958,6 +877,10 @@ 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"}, ] +passlib = [ + {file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"}, + {file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"}, +] pathspec = [ {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, @@ -970,10 +893,6 @@ pycodestyle = [ {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, ] -pycparser = [ - {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, - {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, -] pydantic = [ {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"}, {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"}, diff --git a/reddit-clone/pyproject.toml b/reddit-clone/pyproject.toml index 55ff1b14..3ddcc6d4 100644 --- a/reddit-clone/pyproject.toml +++ b/reddit-clone/pyproject.toml @@ -16,7 +16,7 @@ starlette = "^0.14" graphql-relay = "^3.1.0" aiosmtplib = "^1.1.6" schematics = "^2.1.1" -argon2-cffi = "^21.1.0" +passlib = "^1.7.4" [tool.poetry.dev-dependencies] diff --git a/reddit-clone/reddit/users/models.py b/reddit-clone/reddit/users/models.py index df4b8601..8c4168b0 100644 --- a/reddit-clone/reddit/users/models.py +++ b/reddit-clone/reddit/users/models.py @@ -1,8 +1,10 @@ from __future__ import annotations -from typing import List +from typing import List, Optional -from sqlalchemy import Column, String, Integer, ForeignKey +from passlib.hash import argon2 +from sqlalchemy import select, Column, String, Integer, ForeignKey +from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import relationship from ..database import Base @@ -41,6 +43,28 @@ class User(Base): def __repr__(self) -> str: return f"" + @classmethod + async def by_username(cls, session: AsyncSession, username: str) -> Optional[User]: + """ + Gets an user by their username. + """ + query = select(User).filter_by(username=username).first() + return await session.execute(query) + + def set_password(self, password: str) -> None: + """ + Sets a hashed version of the provided + password on the user instance. + """ + self.password = argon2.hash(password) + + def check_password(self, password: str) -> bool: + """ + Checks whether the provided password + matches the user's password hash. + """ + return argon2.verify(password, self.password) + class SubredditUser(Base): """ diff --git a/reddit-clone/reddit/users/services.py b/reddit-clone/reddit/users/services.py index 639f8613..f1a579c8 100644 --- a/reddit-clone/reddit/users/services.py +++ b/reddit-clone/reddit/users/services.py @@ -9,8 +9,14 @@ async def authenticate( session: AsyncSession, username: str, password: str ) -> Optional[User]: """ - Checks if the provided credentials are valid. + Checks if the provided user credentials are valid. """ + user = await User.by_username(session=session, username=username) + if user is None: + return user + if user.check_password(password=password): + return user + return None async def create_user( @@ -19,7 +25,8 @@ async def create_user( """ Creates a new user instance. """ - user = User(email=email, username=username, password=password) + user = User(email=email, username=username) + user.set_password(password=password) session.add(instance=user) await session.commit() await session.refresh(instance=user) @@ -32,10 +39,15 @@ async def update_user(session: AsyncSession, user: User): """ -async def remove_avatar(session: AsyncSession, user: User): +async def remove_avatar(session: AsyncSession, user: User) -> User: """ Removes the avatar for the given user instance. """ + user.avatar = None + session.add(instance=user) + await session.commit() + await session.refresh(instance=user) + return user async def request_change_email(session: AsyncSession, email: str, password: str): From 2d28665fe1693ebec85cc909060eb7b7a6bdd287 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sun, 10 Oct 2021 19:01:23 +0530 Subject: [PATCH 073/150] add email template: change email --- .../reddit/templates/emails/change_email.html | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 reddit-clone/reddit/templates/emails/change_email.html diff --git a/reddit-clone/reddit/templates/emails/change_email.html b/reddit-clone/reddit/templates/emails/change_email.html new file mode 100644 index 00000000..266d02ee --- /dev/null +++ b/reddit-clone/reddit/templates/emails/change_email.html @@ -0,0 +1,11 @@ +{% extends "emails/base_email.html" %} {% block title %} Confirm Email {% +endblock %} {% block content %} +

+ Someone tried to change their {{ config.SITE_NAME }} account
+ email to {{ email }}. If this was you,
+ enter this confirmation code in the app: +

+

{{ code }}

+{% endblock %} {% block content_more %} +
If this wasn't you, this email can be safely ignored.
+{% endblock %} From 3e58450259e3509344cfeb3dbf665d35abc1b1de Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sun, 10 Oct 2021 19:10:20 +0530 Subject: [PATCH 074/150] update user service: deactivate_user --- reddit-clone/reddit/users/models.py | 3 +++ reddit-clone/reddit/users/services.py | 20 ++++++++++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/reddit-clone/reddit/users/models.py b/reddit-clone/reddit/users/models.py index 8c4168b0..0137fb6f 100644 --- a/reddit-clone/reddit/users/models.py +++ b/reddit-clone/reddit/users/models.py @@ -6,6 +6,7 @@ from sqlalchemy import select, Column, String, Integer, ForeignKey from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import relationship +from sqlalchemy.sql.sqltypes import Boolean from ..database import Base from ..comments.models import Comment @@ -30,6 +31,8 @@ class User(Base): avatar: str = Column(String(255), nullable=False, default="default.jpg") + is_active: bool = Column(Boolean, nullable=False, default=False) + posts: List[Post] = relationship("Post", back_populates="user", lazy="dynamic") subreddits: List[Subreddit] = relationship( diff --git a/reddit-clone/reddit/users/services.py b/reddit-clone/reddit/users/services.py index f1a579c8..5411c2dc 100644 --- a/reddit-clone/reddit/users/services.py +++ b/reddit-clone/reddit/users/services.py @@ -12,11 +12,10 @@ async def authenticate( Checks if the provided user credentials are valid. """ user = await User.by_username(session=session, username=username) - if user is None: - return user - if user.check_password(password=password): - return user - return None + if user is None or not user.check_password(password=password): + # TODO: handle exception here. + pass + return user async def create_user( @@ -27,6 +26,7 @@ async def create_user( """ user = User(email=email, username=username) user.set_password(password=password) + # TODO: validate input data here. session.add(instance=user) await session.commit() await session.refresh(instance=user) @@ -85,7 +85,15 @@ async def block_user(session: AsyncSession, user_id: int, user: User): """ -async def deactivate_user(session: AsyncSession, password: str, user: User): +async def deactivate_user(session: AsyncSession, password: str, user: User) -> User: """ Deactivates the given user instance. """ + if not user.check_password(password=password): + # TODO: handle exception here. + pass + user.is_active = False + session.add(instance=user) + await session.commit() + await session.refresh(instance=user) + return user From ba08a498c622fabf2bb992215b654cfe7592ce99 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sun, 10 Oct 2021 19:18:09 +0530 Subject: [PATCH 075/150] simplify schema design --- reddit-clone/reddit/users/models.py | 8 ++++ .../reddit/users/mutations/__init__.py | 2 - .../reddit/users/mutations/user_block.py | 39 ------------------- reddit-clone/reddit/users/services.py | 23 +++++++---- reddit-clone/reddit/users/types.py | 6 --- 5 files changed, 24 insertions(+), 54 deletions(-) delete mode 100644 reddit-clone/reddit/users/mutations/user_block.py diff --git a/reddit-clone/reddit/users/models.py b/reddit-clone/reddit/users/models.py index 0137fb6f..87aa74b6 100644 --- a/reddit-clone/reddit/users/models.py +++ b/reddit-clone/reddit/users/models.py @@ -54,6 +54,14 @@ async def by_username(cls, session: AsyncSession, username: str) -> Optional[Use query = select(User).filter_by(username=username).first() return await session.execute(query) + @classmethod + async def by_email(cls, session: AsyncSession, email: str) -> Optional[User]: + """ + Gets an user by their email. + """ + query = select(User).filter_by(email=email).first() + return await session.execute(query) + def set_password(self, password: str) -> None: """ Sets a hashed version of the provided diff --git a/reddit-clone/reddit/users/mutations/__init__.py b/reddit-clone/reddit/users/mutations/__init__.py index 83a8adab..91432cc5 100644 --- a/reddit-clone/reddit/users/mutations/__init__.py +++ b/reddit-clone/reddit/users/mutations/__init__.py @@ -6,7 +6,6 @@ from .email_change import email_change from .password_reset_request import password_reset_request from .password_reset import password_reset -from .user_block import user_block from .user_create import user_create from .user_deactivate import user_deactivate from .user_update import user_update @@ -20,7 +19,6 @@ email_change, password_reset_request, password_reset, - user_block, user_create, user_deactivate, user_update, diff --git a/reddit-clone/reddit/users/mutations/user_block.py b/reddit-clone/reddit/users/mutations/user_block.py deleted file mode 100644 index b3b7b6bc..00000000 --- a/reddit-clone/reddit/users/mutations/user_block.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Union - -import strawberry -from strawberry.types import Info - -from reddit.users.types import UserType - - -__all__ = ("user_block",) - - -@strawberry.input -class UserBlockInput: - user_id: strawberry.ID - - -@strawberry.type -class UserBlockSuccess: - user: UserType - - -@strawberry.type -class UserBlockError: - error: str - - -UserBlockResult = Union[UserBlockSuccess, UserBlockError] - - -async def resolve_user_block(info: Info, input: UserBlockInput) -> UserBlockResult: - pass - - -user_block = strawberry.mutation( - resolver=resolve_user_block, - description=""" - Blocks an user account. - """, -) diff --git a/reddit-clone/reddit/users/services.py b/reddit-clone/reddit/users/services.py index 5411c2dc..15f3161a 100644 --- a/reddit-clone/reddit/users/services.py +++ b/reddit-clone/reddit/users/services.py @@ -50,10 +50,21 @@ async def remove_avatar(session: AsyncSession, user: User) -> User: return user -async def request_change_email(session: AsyncSession, email: str, password: str): +async def request_change_email( + session: AsyncSession, email: str, password: str, user: User +): """ Sends an email change code to the user's new email. """ + if not user.check_password(password=password): + # TODO: handle exception here. + pass + user = await User.by_email(session=session, email=email) + if user is not None: + # TODO: handle exception here. + pass + # TODO: store email change code and send + # change request email. async def change_email( @@ -69,6 +80,10 @@ async def request_reset_password(session: AsyncSession, email: str): Sends a password reset code to the given email, if it actually exists. """ + user = await User.by_email(session=session, email=email) + if user is not None: + # TODO: send password reset email here. + pass async def reset_password( @@ -79,12 +94,6 @@ async def reset_password( """ -async def block_user(session: AsyncSession, user_id: int, user: User): - """ - Blocks the user account for the given user instance. - """ - - async def deactivate_user(session: AsyncSession, password: str, user: User) -> User: """ Deactivates the given user instance. diff --git a/reddit-clone/reddit/users/types.py b/reddit-clone/reddit/users/types.py index 09df0b6d..496e694f 100644 --- a/reddit-clone/reddit/users/types.py +++ b/reddit-clone/reddit/users/types.py @@ -28,12 +28,6 @@ class UserType(NodeType): """ ) - blocked: List[UserType] = strawberry.field( - description=""" - The accounts blocked by the user. - """ - ) - posts: List[PostType] = strawberry.field( description=""" The posts for the user. From 1f7b0530de55b040a230b18143eec2923d76e5d9 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sun, 10 Oct 2021 19:22:54 +0530 Subject: [PATCH 076/150] update deps: add jinja2 --- reddit-clone/poetry.lock | 242 +++++++++++++++++++++++++++--------- reddit-clone/pyproject.toml | 5 +- 2 files changed, 184 insertions(+), 63 deletions(-) diff --git a/reddit-clone/poetry.lock b/reddit-clone/poetry.lock index 9f44abcf..ebf8ea00 100644 --- a/reddit-clone/poetry.lock +++ b/reddit-clone/poetry.lock @@ -12,14 +12,14 @@ docs = ["sphinx (>=2,<4)", "sphinx_autodoc_typehints (>=1.7.0,<2.0.0)"] [[package]] name = "alembic" -version = "1.7.3" +version = "1.7.4" description = "A database migration tool for SQLAlchemy." category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +importlib-metadata = {version = "*", markers = "python_version < \"3.9\""} importlib-resources = {version = "*", markers = "python_version < \"3.9\""} Mako = "*" SQLAlchemy = ">=1.3.0" @@ -27,6 +27,22 @@ SQLAlchemy = ">=1.3.0" [package.extras] tz = ["python-dateutil"] +[[package]] +name = "argon2-cffi" +version = "21.1.0" +description = "The secure Argon2 password hashing algorithm." +category = "main" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +cffi = ">=1.0.0" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pytest", "sphinx", "furo", "wheel", "pre-commit"] +docs = ["sphinx", "furo"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pytest"] + [[package]] name = "asgiref" version = "3.4.1" @@ -107,9 +123,20 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "cffi" +version = "1.14.6" +description = "Foreign Function Interface for Python calling C code." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +pycparser = "*" + [[package]] name = "click" -version = "8.0.1" +version = "8.0.2" description = "Composable command line interface toolkit" category = "main" optional = false @@ -129,21 +156,21 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "fastapi" -version = "0.68.1" +version = "0.68.2" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.6.1" [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)", "mdx-include (>=1.4.1,<2.0.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)"] +all = ["requests (>=2.24.0,<3.0.0)", "aiofiles (>=0.5.0,<0.8.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.16.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.4.0,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.16.0)", "graphene (>=2.1.8,<3.0.0)"] +doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=7.1.9,<8.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.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,<4.0.0)", "pytest-asyncio (>=0.14.0,<0.16.0)", "mypy (==0.910)", "flake8 (>=3.8.3,<4.0.0)", "black (==21.9b0)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "orjson (>=3.2.1,<4.0.0)", "ujson (>=4.0.1,<5.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "aiofiles (>=0.5.0,<0.8.0)", "flask (>=1.1.2,<2.0.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)", "types-ujson (==0.1.1)", "types-orjson (==3.6.0)", "types-dataclasses (==0.1.7)"] [[package]] name = "flake8" @@ -269,6 +296,20 @@ zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy"] +[[package]] +name = "jinja2" +version = "3.0.2" +description = "A very fast and expressive template engine." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + [[package]] name = "mako" version = "1.1.5" @@ -334,6 +375,9 @@ category = "main" optional = false python-versions = "*" +[package.dependencies] +argon2-cffi = {version = ">=18.2.0", optional = true, markers = "extra == \"argon2\""} + [package.extras] argon2 = ["argon2-cffi (>=18.2.0)"] bcrypt = ["bcrypt (>=3.1.0)"] @@ -368,6 +412,14 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "pycparser" +version = "2.20" +description = "C parser in Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + [[package]] name = "pydantic" version = "1.8.2" @@ -412,7 +464,7 @@ six = ">=1.5" [[package]] name = "python-dotenv" -version = "0.19.0" +version = "0.19.1" description = "Read key-value pairs from a .env file and set them as environment variables" category = "main" optional = false @@ -442,7 +494,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [[package]] name = "regex" -version = "2021.9.30" +version = "2021.10.8" description = "Alternative regular expression module, to replace re." category = "dev" optional = false @@ -649,7 +701,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "03a61f70a8c276ecdeb0f5dcd3c519f5b4dbe2187ce15e4a68c607f5e594c7e0" +content-hash = "2c06d6d834957f2c4efe2573570f18ee4bde2f8c41f227317761789709c4a736" [metadata.files] aiosmtplib = [ @@ -657,8 +709,21 @@ aiosmtplib = [ {file = "aiosmtplib-1.1.6.tar.gz", hash = "sha256:d138fe6ffecbc9e6320269690b9ac0b75e540ef96e8f5c77d4a306760014dce2"}, ] alembic = [ - {file = "alembic-1.7.3-py3-none-any.whl", hash = "sha256:d0c580041f9f6487d5444df672a83da9be57398f39d6c1802bbedec6fefbeef6"}, - {file = "alembic-1.7.3.tar.gz", hash = "sha256:bc5bdf03d1b9814ee4d72adc0b19df2123f6c50a60c1ea761733f3640feedb8d"}, + {file = "alembic-1.7.4-py3-none-any.whl", hash = "sha256:e3cab9e59778b3b6726bb2da9ced451c6622d558199fd3ef914f3b1e8f4ef704"}, + {file = "alembic-1.7.4.tar.gz", hash = "sha256:9d33f3ff1488c4bfab1e1a6dfebbf085e8a8e1a3e047a43ad29ad1f67f012a1d"}, +] +argon2-cffi = [ + {file = "argon2-cffi-21.1.0.tar.gz", hash = "sha256:f710b61103d1a1f692ca3ecbd1373e28aa5e545ac625ba067ff2feca1b2bb870"}, + {file = "argon2_cffi-21.1.0-cp35-abi3-macosx_10_14_x86_64.whl", hash = "sha256:217b4f0f853ccbbb5045242946ad2e162e396064575860141b71a85eb47e475a"}, + {file = "argon2_cffi-21.1.0-cp35-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fa7e7d1fc22514a32b1761fdfa1882b6baa5c36bb3ef557bdd69e6fc9ba14a41"}, + {file = "argon2_cffi-21.1.0-cp35-abi3-win32.whl", hash = "sha256:e4d8f0ae1524b7b0372a3e574a2561cbdddb3fdb6c28b70a72868189bda19659"}, + {file = "argon2_cffi-21.1.0-cp35-abi3-win_amd64.whl", hash = "sha256:65213a9174320a1aee03fe826596e0620783966b49eb636955958b3074e87ff9"}, + {file = "argon2_cffi-21.1.0-pp36-pypy36_pp73-macosx_10_7_x86_64.whl", hash = "sha256:245f64a203012b144b7b8c8ea6d468cb02b37caa5afee5ba4a10c80599334f6a"}, + {file = "argon2_cffi-21.1.0-pp36-pypy36_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4ad152c418f7eb640eac41ac815534e6aa61d1624530b8e7779114ecfbf327f8"}, + {file = "argon2_cffi-21.1.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:bc513db2283c385ea4da31a2cd039c33380701f376f4edd12fe56db118a3b21a"}, + {file = "argon2_cffi-21.1.0-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:c7a7c8cc98ac418002090e4add5bebfff1b915ea1cb459c578cd8206fef10378"}, + {file = "argon2_cffi-21.1.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:165cadae5ac1e26644f5ade3bd9c18d89963be51d9ea8817bd671006d7909057"}, + {file = "argon2_cffi-21.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:566ffb581bbd9db5562327aee71b2eda24a1c15b23a356740abe3c011bbe0dcb"}, ] asgiref = [ {file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"}, @@ -691,17 +756,64 @@ cached-property = [ {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, ] +cffi = [ + {file = "cffi-1.14.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c"}, + {file = "cffi-1.14.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99"}, + {file = "cffi-1.14.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819"}, + {file = "cffi-1.14.6-cp27-cp27m-win32.whl", hash = "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20"}, + {file = "cffi-1.14.6-cp27-cp27m-win_amd64.whl", hash = "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224"}, + {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7"}, + {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33"}, + {file = "cffi-1.14.6-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534"}, + {file = "cffi-1.14.6-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a"}, + {file = "cffi-1.14.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5"}, + {file = "cffi-1.14.6-cp35-cp35m-win32.whl", hash = "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca"}, + {file = "cffi-1.14.6-cp35-cp35m-win_amd64.whl", hash = "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218"}, + {file = "cffi-1.14.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f"}, + {file = "cffi-1.14.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872"}, + {file = "cffi-1.14.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195"}, + {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d"}, + {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b"}, + {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb"}, + {file = "cffi-1.14.6-cp36-cp36m-win32.whl", hash = "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a"}, + {file = "cffi-1.14.6-cp36-cp36m-win_amd64.whl", hash = "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e"}, + {file = "cffi-1.14.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5"}, + {file = "cffi-1.14.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf"}, + {file = "cffi-1.14.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69"}, + {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56"}, + {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c"}, + {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762"}, + {file = "cffi-1.14.6-cp37-cp37m-win32.whl", hash = "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771"}, + {file = "cffi-1.14.6-cp37-cp37m-win_amd64.whl", hash = "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a"}, + {file = "cffi-1.14.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0"}, + {file = "cffi-1.14.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e"}, + {file = "cffi-1.14.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346"}, + {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc"}, + {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd"}, + {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc"}, + {file = "cffi-1.14.6-cp38-cp38-win32.whl", hash = "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548"}, + {file = "cffi-1.14.6-cp38-cp38-win_amd64.whl", hash = "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156"}, + {file = "cffi-1.14.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d"}, + {file = "cffi-1.14.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e"}, + {file = "cffi-1.14.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c"}, + {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202"}, + {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f"}, + {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87"}, + {file = "cffi-1.14.6-cp39-cp39-win32.whl", hash = "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728"}, + {file = "cffi-1.14.6-cp39-cp39-win_amd64.whl", hash = "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2"}, + {file = "cffi-1.14.6.tar.gz", hash = "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd"}, +] click = [ - {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, - {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, + {file = "click-8.0.2-py3-none-any.whl", hash = "sha256:3fab8aeb8f15f5452ae7511ad448977b3417325bceddd53df87e0bb81f3a8cf8"}, + {file = "click-8.0.2.tar.gz", hash = "sha256:7027bc7bbafaab8b2c2816861d8eb372429ee3c02e193fc2f93d6c4ab9de49c5"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] fastapi = [ - {file = "fastapi-0.68.1-py3-none-any.whl", hash = "sha256:94d2820906c36b9b8303796fb7271337ec89c74223229e3cfcf056b5a7d59e23"}, - {file = "fastapi-0.68.1.tar.gz", hash = "sha256:644bb815bae326575c4b2842469fb83053a4b974b82fa792ff9283d17fbbd99d"}, + {file = "fastapi-0.68.2-py3-none-any.whl", hash = "sha256:36bcdd3dbea87c586061005e4a40b9bd0145afd766655b4e0ec1d8870b32555c"}, + {file = "fastapi-0.68.2.tar.gz", hash = "sha256:38526fc46bda73f7ec92033952677323c16061e70a91d15c95f18b11895da494"}, ] flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, @@ -804,6 +916,10 @@ importlib-resources = [ {file = "importlib_resources-5.2.2-py3-none-any.whl", hash = "sha256:2480d8e07d1890056cb53c96e3de44fead9c62f2ba949b0f2e4c4345f4afa977"}, {file = "importlib_resources-5.2.2.tar.gz", hash = "sha256:a65882a4d0fe5fbf702273456ba2ce74fe44892c25e42e057aca526b702a6d4b"}, ] +jinja2 = [ + {file = "Jinja2-3.0.2-py3-none-any.whl", hash = "sha256:8569982d3f0889eed11dd620c706d39b60c36d6d25843961f33f77fb6bc6b20c"}, + {file = "Jinja2-3.0.2.tar.gz", hash = "sha256:827a0e32839ab1600d4eb1c4c33ec5a8edfbc5cb42dafa13b81f182f97784b45"}, +] mako = [ {file = "Mako-1.1.5-py2.py3-none-any.whl", hash = "sha256:6804ee66a7f6a6416910463b00d76a7b25194cd27f1918500c5bd7be2a088a23"}, {file = "Mako-1.1.5.tar.gz", hash = "sha256:169fa52af22a91900d852e937400e79f535496191c63712e3b9fda5a9bed6fc3"}, @@ -893,6 +1009,10 @@ pycodestyle = [ {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, ] +pycparser = [ + {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, + {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, +] pydantic = [ {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"}, {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"}, @@ -930,8 +1050,8 @@ python-dateutil = [ {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] python-dotenv = [ - {file = "python-dotenv-0.19.0.tar.gz", hash = "sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"}, - {file = "python_dotenv-0.19.0-py2.py3-none-any.whl", hash = "sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1"}, + {file = "python-dotenv-0.19.1.tar.gz", hash = "sha256:14f8185cc8d494662683e6914addcb7e95374771e707601dfc70166946b4c4b8"}, + {file = "python_dotenv-0.19.1-py2.py3-none-any.whl", hash = "sha256:bbd3da593fc49c249397cbfbcc449cf36cb02e75afc8157fcc6a81df6fb7750a"}, ] python-multipart = [ {file = "python-multipart-0.0.5.tar.gz", hash = "sha256:f7bb5f611fc600d15fa47b3974c8aa16e93724513b49b5f95c81e6624c83fa43"}, @@ -968,47 +1088,47 @@ pyyaml = [ {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, ] regex = [ - {file = "regex-2021.9.30-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:66696c8336a1b5d1182464f3af3427cc760118f26d0b09a2ddc16a976a4d2637"}, - {file = "regex-2021.9.30-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d87459ad3ab40cd8493774f8a454b2e490d8e729e7e402a0625867a983e4e02"}, - {file = "regex-2021.9.30-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78cf6a1e023caf5e9a982f5377414e1aeac55198831b852835732cfd0a0ca5ff"}, - {file = "regex-2021.9.30-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:255791523f80ea8e48e79af7120b4697ef3b74f6886995dcdb08c41f8e516be0"}, - {file = "regex-2021.9.30-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e502f8d4e5ef714bcc2c94d499684890c94239526d61fdf1096547db91ca6aa6"}, - {file = "regex-2021.9.30-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4907fb0f9b9309a5bded72343e675a252c2589a41871874feace9a05a540241e"}, - {file = "regex-2021.9.30-cp310-cp310-win32.whl", hash = "sha256:3be40f720af170a6b20ddd2ad7904c58b13d2b56f6734ee5d09bbdeed2fa4816"}, - {file = "regex-2021.9.30-cp310-cp310-win_amd64.whl", hash = "sha256:c2b180ed30856dfa70cfe927b0fd38e6b68198a03039abdbeb1f2029758d87e7"}, - {file = "regex-2021.9.30-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e6f2d2f93001801296fe3ca86515eb04915472b5380d4d8752f09f25f0b9b0ed"}, - {file = "regex-2021.9.30-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fa7ba9ab2eba7284e0d7d94f61df7af86015b0398e123331362270d71fab0b9"}, - {file = "regex-2021.9.30-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28040e89a04b60d579c69095c509a4f6a1a5379cd865258e3a186b7105de72c6"}, - {file = "regex-2021.9.30-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f588209d3e4797882cd238195c175290dbc501973b10a581086b5c6bcd095ffb"}, - {file = "regex-2021.9.30-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42952d325439ef223e4e9db7ee6d9087b5c68c5c15b1f9de68e990837682fc7b"}, - {file = "regex-2021.9.30-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cae4099031d80703954c39680323dabd87a69b21262303160776aa0e55970ca0"}, - {file = "regex-2021.9.30-cp36-cp36m-win32.whl", hash = "sha256:0de8ad66b08c3e673b61981b9e3626f8784d5564f8c3928e2ad408c0eb5ac38c"}, - {file = "regex-2021.9.30-cp36-cp36m-win_amd64.whl", hash = "sha256:b345ecde37c86dd7084c62954468a4a655fd2d24fd9b237949dd07a4d0dd6f4c"}, - {file = "regex-2021.9.30-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6f08187136f11e430638c2c66e1db091105d7c2e9902489f0dbc69b44c222b4"}, - {file = "regex-2021.9.30-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b55442650f541d195a535ccec33078c78a9521973fb960923da7515e9ed78fa6"}, - {file = "regex-2021.9.30-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87e9c489aa98f50f367fb26cc9c8908d668e9228d327644d7aa568d47e456f47"}, - {file = "regex-2021.9.30-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e2cb7d4909ed16ed35729d38af585673f1f0833e73dfdf0c18e5be0061107b99"}, - {file = "regex-2021.9.30-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0861e7f6325e821d5c40514c551fd538b292f8cc3960086e73491b9c5d8291d"}, - {file = "regex-2021.9.30-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:81fdc90f999b2147fc62e303440c424c47e5573a9b615ed5d43a5b832efcca9e"}, - {file = "regex-2021.9.30-cp37-cp37m-win32.whl", hash = "sha256:8c1ad61fa024195136a6b7b89538030bd00df15f90ac177ca278df9b2386c96f"}, - {file = "regex-2021.9.30-cp37-cp37m-win_amd64.whl", hash = "sha256:e3770781353a4886b68ef10cec31c1f61e8e3a0be5f213c2bb15a86efd999bc4"}, - {file = "regex-2021.9.30-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9c065d95a514a06b92a5026766d72ac91bfabf581adb5b29bc5c91d4b3ee9b83"}, - {file = "regex-2021.9.30-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9925985be05d54b3d25fd6c1ea8e50ff1f7c2744c75bdc4d3b45c790afa2bcb3"}, - {file = "regex-2021.9.30-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:470f2c882f2672d8eeda8ab27992aec277c067d280b52541357e1acd7e606dae"}, - {file = "regex-2021.9.30-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ad0517df22a97f1da20d8f1c8cb71a5d1997fa383326b81f9cf22c9dadfbdf34"}, - {file = "regex-2021.9.30-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e30838df7bfd20db6466fd309d9b580d32855f8e2c2e6d74cf9da27dcd9b63"}, - {file = "regex-2021.9.30-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b34d2335d6aedec7dcadd3f8283b9682fadad8b9b008da8788d2fce76125ebe"}, - {file = "regex-2021.9.30-cp38-cp38-win32.whl", hash = "sha256:e07049cece3462c626d650e8bf42ddbca3abf4aa08155002c28cb6d9a5a281e2"}, - {file = "regex-2021.9.30-cp38-cp38-win_amd64.whl", hash = "sha256:37868075eda024470bd0feab872c692ac4ee29db1e14baec103257bf6cc64346"}, - {file = "regex-2021.9.30-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d331f238a7accfbbe1c4cd1ba610d4c087b206353539331e32a8f05345c74aec"}, - {file = "regex-2021.9.30-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6348a7ab2a502cbdd0b7fd0496d614007489adb7361956b38044d1d588e66e04"}, - {file = "regex-2021.9.30-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce7b1cca6c23f19bee8dc40228d9c314d86d1e51996b86f924aca302fc8f8bf9"}, - {file = "regex-2021.9.30-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1f1125bc5172ab3a049bc6f4b9c0aae95a2a2001a77e6d6e4239fa3653e202b5"}, - {file = "regex-2021.9.30-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:638e98d069b14113e8afba6a54d1ca123f712c0d105e67c1f9211b2a825ef926"}, - {file = "regex-2021.9.30-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9a0b0db6b49da7fa37ca8eddf9f40a8dbc599bad43e64f452284f37b6c34d91c"}, - {file = "regex-2021.9.30-cp39-cp39-win32.whl", hash = "sha256:9910869c472e5a6728680ca357b5846546cbbd2ab3ad5bef986ef0bc438d0aa6"}, - {file = "regex-2021.9.30-cp39-cp39-win_amd64.whl", hash = "sha256:3b71213ec3bad9a5a02e049f2ec86b3d7c3e350129ae0f4e2f99c12b5da919ed"}, - {file = "regex-2021.9.30.tar.gz", hash = "sha256:81e125d9ba54c34579e4539a967e976a3c56150796674aec318b1b2f49251be7"}, + {file = "regex-2021.10.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:981c786293a3115bc14c103086ae54e5ee50ca57f4c02ce7cf1b60318d1e8072"}, + {file = "regex-2021.10.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51feefd58ac38eb91a21921b047da8644155e5678e9066af7bcb30ee0dca7361"}, + {file = "regex-2021.10.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea8de658d7db5987b11097445f2b1f134400e2232cb40e614e5f7b6f5428710e"}, + {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1ce02f420a7ec3b2480fe6746d756530f69769292eca363218c2291d0b116a01"}, + {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39079ebf54156be6e6902f5c70c078f453350616cfe7bfd2dd15bdb3eac20ccc"}, + {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ff24897f6b2001c38a805d53b6ae72267025878d35ea225aa24675fbff2dba7f"}, + {file = "regex-2021.10.8-cp310-cp310-win32.whl", hash = "sha256:c6569ba7b948c3d61d27f04e2b08ebee24fec9ff8e9ea154d8d1e975b175bfa7"}, + {file = "regex-2021.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:45cb0f7ff782ef51bc79e227a87e4e8f24bc68192f8de4f18aae60b1d60bc152"}, + {file = "regex-2021.10.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fab3ab8aedfb443abb36729410403f0fe7f60ad860c19a979d47fb3eb98ef820"}, + {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74e55f8d66f1b41d44bc44c891bcf2c7fad252f8f323ee86fba99d71fd1ad5e3"}, + {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d52c5e089edbdb6083391faffbe70329b804652a53c2fdca3533e99ab0580d9"}, + {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1abbd95cbe9e2467cac65c77b6abd9223df717c7ae91a628502de67c73bf6838"}, + {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9b5c215f3870aa9b011c00daeb7be7e1ae4ecd628e9beb6d7e6107e07d81287"}, + {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f540f153c4f5617bc4ba6433534f8916d96366a08797cbbe4132c37b70403e92"}, + {file = "regex-2021.10.8-cp36-cp36m-win32.whl", hash = "sha256:1f51926db492440e66c89cd2be042f2396cf91e5b05383acd7372b8cb7da373f"}, + {file = "regex-2021.10.8-cp36-cp36m-win_amd64.whl", hash = "sha256:5f55c4804797ef7381518e683249310f7f9646da271b71cb6b3552416c7894ee"}, + {file = "regex-2021.10.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb2baff66b7d2267e07ef71e17d01283b55b3cc51a81b54cc385e721ae172ba4"}, + {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e527ab1c4c7cf2643d93406c04e1d289a9d12966529381ce8163c4d2abe4faf"}, + {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c98b013273e9da5790ff6002ab326e3f81072b4616fd95f06c8fa733d2745f"}, + {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:55ef044899706c10bc0aa052f2fc2e58551e2510694d6aae13f37c50f3f6ff61"}, + {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0ab3530a279a3b7f50f852f1bab41bc304f098350b03e30a3876b7dd89840e"}, + {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a37305eb3199d8f0d8125ec2fb143ba94ff6d6d92554c4b8d4a8435795a6eccd"}, + {file = "regex-2021.10.8-cp37-cp37m-win32.whl", hash = "sha256:2efd47704bbb016136fe34dfb74c805b1ef5c7313aef3ce6dcb5ff844299f432"}, + {file = "regex-2021.10.8-cp37-cp37m-win_amd64.whl", hash = "sha256:924079d5590979c0e961681507eb1773a142553564ccae18d36f1de7324e71ca"}, + {file = "regex-2021.10.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b09d3904bf312d11308d9a2867427479d277365b1617e48ad09696fa7dfcdf59"}, + {file = "regex-2021.10.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f125fce0a0ae4fd5c3388d369d7a7d78f185f904c90dd235f7ecf8fe13fa741"}, + {file = "regex-2021.10.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f199419a81c1016e0560c39773c12f0bd924c37715bffc64b97140d2c314354"}, + {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:09e1031e2059abd91177c302da392a7b6859ceda038be9e015b522a182c89e4f"}, + {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c070d5895ac6aeb665bd3cd79f673775caf8d33a0b569e98ac434617ecea57d"}, + {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:176796cb7f82a7098b0c436d6daac82f57b9101bb17b8e8119c36eecf06a60a3"}, + {file = "regex-2021.10.8-cp38-cp38-win32.whl", hash = "sha256:5e5796d2f36d3c48875514c5cd9e4325a1ca172fc6c78b469faa8ddd3d770593"}, + {file = "regex-2021.10.8-cp38-cp38-win_amd64.whl", hash = "sha256:e4204708fa116dd03436a337e8e84261bc8051d058221ec63535c9403a1582a1"}, + {file = "regex-2021.10.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b8b6ee6555b6fbae578f1468b3f685cdfe7940a65675611365a7ea1f8d724991"}, + {file = "regex-2021.10.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:973499dac63625a5ef9dfa4c791aa33a502ddb7615d992bdc89cf2cc2285daa3"}, + {file = "regex-2021.10.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88dc3c1acd3f0ecfde5f95c32fcb9beda709dbdf5012acdcf66acbc4794468eb"}, + {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4786dae85c1f0624ac77cb3813ed99267c9adb72e59fdc7297e1cf4d6036d493"}, + {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe6ce4f3d3c48f9f402da1ceb571548133d3322003ce01b20d960a82251695d2"}, + {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9e3e2cea8f1993f476a6833ef157f5d9e8c75a59a8d8b0395a9a6887a097243b"}, + {file = "regex-2021.10.8-cp39-cp39-win32.whl", hash = "sha256:82cfb97a36b1a53de32b642482c6c46b6ce80803854445e19bc49993655ebf3b"}, + {file = "regex-2021.10.8-cp39-cp39-win_amd64.whl", hash = "sha256:b04e512eb628ea82ed86eb31c0f7fc6842b46bf2601b66b1356a7008327f7700"}, + {file = "regex-2021.10.8.tar.gz", hash = "sha256:26895d7c9bbda5c52b3635ce5991caa90fbb1ddfac9c9ff1c7ce505e2282fb2a"}, ] schematics = [ {file = "schematics-2.1.1-py2.py3-none-any.whl", hash = "sha256:be2d451bfb86789975e5ec0864aec569b63cea9010f0d24cbbd992a4e564c647"}, diff --git a/reddit-clone/pyproject.toml b/reddit-clone/pyproject.toml index 3ddcc6d4..aaebbbce 100644 --- a/reddit-clone/pyproject.toml +++ b/reddit-clone/pyproject.toml @@ -8,15 +8,16 @@ authors = ["Aryan Iyappan "] python = "^3.7" uvicorn = {extras = ["standard"], version = "^0.15.0"} SQLAlchemy = {extras = ["mypy"], version = "^1.4.23"} +strawberry-graphql = {extras = ["asgi"], version = "^0.77.0"} +passlib = {extras = ["argon2"], version = "^1.7.4"} alembic = "^1.7.1" asyncpg = "^0.24" -strawberry-graphql = {extras = ["asgi"], version = "^0.77.0"} fastapi = "^0.68.1" starlette = "^0.14" graphql-relay = "^3.1.0" aiosmtplib = "^1.1.6" schematics = "^2.1.1" -passlib = "^1.7.4" +jinja2 = "^3.0.2" [tool.poetry.dev-dependencies] From 6ea08f4a40f6109bd9b19ec2f7292712786ff176 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sun, 10 Oct 2021 19:49:15 +0530 Subject: [PATCH 077/150] add templating helpers --- reddit-clone/reddit/templates.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 reddit-clone/reddit/templates.py diff --git a/reddit-clone/reddit/templates.py b/reddit-clone/reddit/templates.py new file mode 100644 index 00000000..7240f333 --- /dev/null +++ b/reddit-clone/reddit/templates.py @@ -0,0 +1,16 @@ +from jinja2 import Environment, FileSystemLoader, select_autoescape + +__all__ = ("render_template",) + +environment = Environment( + loader=FileSystemLoader(searchpath="./templates"), + autoescape=select_autoescape(("html", "xml")), +) + + +def render_template(template_name: str, *args, **kwargs): + """ + Renders the html template with the given variables. + """ + template = environment.get_template(name=template_name) + return template.render(*args, **kwargs) From 4f719f29e2131bb3f52b2b8d8702f36794e1b480 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Mon, 11 Oct 2021 08:57:09 +0530 Subject: [PATCH 078/150] add file storage helpers --- .gitignore | 1 - reddit-clone/poetry.lock | 14 +++++++++++++- reddit-clone/pyproject.toml | 3 ++- reddit-clone/reddit/storage.py | 20 ++++++++++++++++++++ 4 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 reddit-clone/reddit/storage.py diff --git a/.gitignore b/.gitignore index 50af712e..f13d7bae 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,6 @@ dist/ downloads/ eggs/ .eggs/ -lib/ lib64/ parts/ sdist/ diff --git a/reddit-clone/poetry.lock b/reddit-clone/poetry.lock index ebf8ea00..ffd769f8 100644 --- a/reddit-clone/poetry.lock +++ b/reddit-clone/poetry.lock @@ -1,3 +1,11 @@ +[[package]] +name = "aiofiles" +version = "0.7.0" +description = "File support for asyncio." +category = "main" +optional = false +python-versions = ">=3.6,<4.0" + [[package]] name = "aiosmtplib" version = "1.1.6" @@ -701,9 +709,13 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "2c06d6d834957f2c4efe2573570f18ee4bde2f8c41f227317761789709c4a736" +content-hash = "de1904503b60be0c64385f92a9c8ab428553b7a6274eff9246e5ce1d22a5c1e3" [metadata.files] +aiofiles = [ + {file = "aiofiles-0.7.0-py3-none-any.whl", hash = "sha256:c67a6823b5f23fcab0a2595a289cec7d8c863ffcb4322fb8cd6b90400aedfdbc"}, + {file = "aiofiles-0.7.0.tar.gz", hash = "sha256:a1c4fc9b2ff81568c83e21392a82f344ea9d23da906e4f6a52662764545e19d4"}, +] aiosmtplib = [ {file = "aiosmtplib-1.1.6-py3-none-any.whl", hash = "sha256:84174765778b2c5e0e207fbce0a769202fcf0c3de81faa87cc03551a6333bfa9"}, {file = "aiosmtplib-1.1.6.tar.gz", hash = "sha256:d138fe6ffecbc9e6320269690b9ac0b75e540ef96e8f5c77d4a306760014dce2"}, diff --git a/reddit-clone/pyproject.toml b/reddit-clone/pyproject.toml index aaebbbce..83a57c39 100644 --- a/reddit-clone/pyproject.toml +++ b/reddit-clone/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "reddit-graphql" version = "0.1.0" -description = "A reddit API clone built with GraphQL." +description = "A Reddit API clone built with GraphQL." authors = ["Aryan Iyappan "] [tool.poetry.dependencies] @@ -18,6 +18,7 @@ graphql-relay = "^3.1.0" aiosmtplib = "^1.1.6" schematics = "^2.1.1" jinja2 = "^3.0.2" +aiofiles = "^0.7.0" [tool.poetry.dev-dependencies] diff --git a/reddit-clone/reddit/storage.py b/reddit-clone/reddit/storage.py new file mode 100644 index 00000000..6ca01fb6 --- /dev/null +++ b/reddit-clone/reddit/storage.py @@ -0,0 +1,20 @@ +from pathlib import Path + +import aiofiles + +__all__ = ("save_file",) + + +def generate_file_name(file_name: str) -> str: + """ + Generates a secure file name for the given file. + """ + + +async def save_file(file, path: Path) -> None: + """ + Stores the file at the given file-path. + """ + async with aiofiles.open(path, "w") as out: + await out.write(file) + await out.flush() From e17eb2844de14ffcfe0228ff431267507fdd5cdf Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Mon, 11 Oct 2021 09:00:07 +0530 Subject: [PATCH 079/150] update storage helper: generate-file-name --- reddit-clone/reddit/storage.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/reddit-clone/reddit/storage.py b/reddit-clone/reddit/storage.py index 6ca01fb6..d7ca389c 100644 --- a/reddit-clone/reddit/storage.py +++ b/reddit-clone/reddit/storage.py @@ -1,4 +1,5 @@ from pathlib import Path +from hashlib import sha256 import aiofiles @@ -9,6 +10,7 @@ def generate_file_name(file_name: str) -> str: """ Generates a secure file name for the given file. """ + return sha256(file_name.encode("utf-8")).hexdigest() async def save_file(file, path: Path) -> None: From 141951d4b45ea1557b2154e66906d1a7959a59d5 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Mon, 11 Oct 2021 11:09:12 +0530 Subject: [PATCH 080/150] switch to starlette why? It's because FastAPI introduces a lot of other dependencies like pydantic which we don't really need at the moment. --- reddit-clone/README.md | 8 +-- reddit-clone/poetry.lock | 121 +++++++++++++++----------------- reddit-clone/pyproject.toml | 3 +- reddit-clone/reddit/__init__.py | 14 ++-- 4 files changed, 65 insertions(+), 81 deletions(-) diff --git a/reddit-clone/README.md b/reddit-clone/README.md index 391d7c20..4f45270b 100644 --- a/reddit-clone/README.md +++ b/reddit-clone/README.md @@ -8,16 +8,16 @@ most of the features that Strawberry gives us. ## Tech Stack used: - [Strawberry GraphQL](https://github.com/strawberry-graphql/strawberry) -- [FastAPI](https://github.com/tiangolo/fastapi) w/ [Starlette](https://github.com/encode/starlette) -- [SQLAlchemy](https://github.com/sqlalchemy/sqlalchemy) (asyncio) -- [Alembic](https://github.com/sqlalchemy/alembic) for migrations +- [Starlette](https://github.com/encode/starlette) web framework +- [SQLAlchemy](https://github.com/sqlalchemy/sqlalchemy) core/ mapper (asyncio) +- [Alembic](https://github.com/sqlalchemy/alembic) migrations ## Features at a glance +- [ ] Implements the Relay spec - [ ] Error modelling within the schema - [ ] Authorization with the permissions API - [ ] Batch loading with dataloaders -- [ ] Pagination with SQLAlchemy - [x] modular codebase ## How to use diff --git a/reddit-clone/poetry.lock b/reddit-clone/poetry.lock index ffd769f8..6736a64e 100644 --- a/reddit-clone/poetry.lock +++ b/reddit-clone/poetry.lock @@ -35,6 +35,24 @@ SQLAlchemy = ">=1.3.0" [package.extras] tz = ["python-dateutil"] +[[package]] +name = "anyio" +version = "3.3.3" +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" +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[package.extras] +doc = ["sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"] +test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "pytest (>=6.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "mock (>=4)", "uvloop (>=0.15)"] +trio = ["trio (>=0.16)"] + [[package]] name = "argon2-cffi" version = "21.1.0" @@ -162,24 +180,6 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -[[package]] -name = "fastapi" -version = "0.68.2" -description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" -category = "main" -optional = false -python-versions = ">=3.6.1" - -[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.8.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.16.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.4.0,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.16.0)", "graphene (>=2.1.8,<3.0.0)"] -doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=7.1.9,<8.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.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,<4.0.0)", "pytest-asyncio (>=0.14.0,<0.16.0)", "mypy (==0.910)", "flake8 (>=3.8.3,<4.0.0)", "black (==21.9b0)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "orjson (>=3.2.1,<4.0.0)", "ujson (>=4.0.1,<5.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "aiofiles (>=0.5.0,<0.8.0)", "flask (>=1.1.2,<2.0.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)", "types-ujson (==0.1.1)", "types-orjson (==3.6.0)", "types-dataclasses (==0.1.7)"] - [[package]] name = "flake8" version = "3.9.2" @@ -272,6 +272,14 @@ python-versions = "*" [package.extras] test = ["Cython (==0.29.22)"] +[[package]] +name = "idna" +version = "3.2" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" + [[package]] name = "importlib-metadata" version = "4.8.1" @@ -428,21 +436,6 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[[package]] -name = "pydantic" -version = "1.8.2" -description = "Data validation and settings management using python 3.6 type hinting" -category = "main" -optional = false -python-versions = ">=3.6.1" - -[package.dependencies] -typing-extensions = ">=3.7.4.3" - -[package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] - [[package]] name = "pyflakes" version = "2.3.1" @@ -524,6 +517,14 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "sniffio" +version = "1.2.0" +description = "Sniff out which async library your code is running under" +category = "main" +optional = false +python-versions = ">=3.5" + [[package]] name = "sqlalchemy" version = "1.4.25" @@ -572,14 +573,18 @@ typing-extensions = ">=3.7.4" [[package]] name = "starlette" -version = "0.14.2" +version = "0.16.0" description = "The little ASGI library that shines." category = "main" optional = false python-versions = ">=3.6" +[package.dependencies] +anyio = ">=3.0.0,<4" +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + [package.extras] -full = ["aiofiles", "graphene", "itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"] +full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests", "graphene"] [[package]] name = "strawberry-graphql" @@ -709,7 +714,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "de1904503b60be0c64385f92a9c8ab428553b7a6274eff9246e5ce1d22a5c1e3" +content-hash = "9077eb8388b8be7ad3b4c11297b10ba19989b6c33bdd4acb57de30bd2d8090e6" [metadata.files] aiofiles = [ @@ -724,6 +729,10 @@ alembic = [ {file = "alembic-1.7.4-py3-none-any.whl", hash = "sha256:e3cab9e59778b3b6726bb2da9ced451c6622d558199fd3ef914f3b1e8f4ef704"}, {file = "alembic-1.7.4.tar.gz", hash = "sha256:9d33f3ff1488c4bfab1e1a6dfebbf085e8a8e1a3e047a43ad29ad1f67f012a1d"}, ] +anyio = [ + {file = "anyio-3.3.3-py3-none-any.whl", hash = "sha256:56ceaeed2877723578b1341f4f68c29081db189cfb40a97d1922b9513f6d7db6"}, + {file = "anyio-3.3.3.tar.gz", hash = "sha256:8eccec339cb4a856c94a75d50fc1d451faf32a05ef406be462e2efc59c9838b0"}, +] argon2-cffi = [ {file = "argon2-cffi-21.1.0.tar.gz", hash = "sha256:f710b61103d1a1f692ca3ecbd1373e28aa5e545ac625ba067ff2feca1b2bb870"}, {file = "argon2_cffi-21.1.0-cp35-abi3-macosx_10_14_x86_64.whl", hash = "sha256:217b4f0f853ccbbb5045242946ad2e162e396064575860141b71a85eb47e475a"}, @@ -823,10 +832,6 @@ colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] -fastapi = [ - {file = "fastapi-0.68.2-py3-none-any.whl", hash = "sha256:36bcdd3dbea87c586061005e4a40b9bd0145afd766655b4e0ec1d8870b32555c"}, - {file = "fastapi-0.68.2.tar.gz", hash = "sha256:38526fc46bda73f7ec92033952677323c16061e70a91d15c95f18b11895da494"}, -] flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, @@ -920,6 +925,10 @@ httptools = [ {file = "httptools-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:200fc1cdf733a9ff554c0bb97a4047785cfaad9875307d6087001db3eb2b417f"}, {file = "httptools-0.2.0.tar.gz", hash = "sha256:94505026be56652d7a530ab03d89474dc6021019d6b8682281977163b3471ea0"}, ] +idna = [ + {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, + {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"}, +] importlib-metadata = [ {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"}, {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"}, @@ -1025,30 +1034,6 @@ pycparser = [ {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, ] -pydantic = [ - {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"}, - {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"}, - {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"}, - {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"}, - {file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"}, - {file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"}, - {file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"}, - {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"}, - {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"}, - {file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"}, - {file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"}, - {file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"}, - {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"}, - {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"}, - {file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"}, - {file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"}, - {file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"}, - {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"}, - {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"}, - {file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"}, - {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"}, - {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"}, -] pyflakes = [ {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, @@ -1150,6 +1135,10 @@ 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 = [ + {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"}, + {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"}, +] sqlalchemy = [ {file = "SQLAlchemy-1.4.25-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:a36ea43919e51b0de0c0bc52bcfdad7683f6ea9fb81b340cdabb9df0e045e0f7"}, {file = "SQLAlchemy-1.4.25-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:75cd5d48389a7635393ff5a9214b90695c06b3d74912109c3b00ce7392b69c6c"}, @@ -1187,8 +1176,8 @@ sqlalchemy2-stubs = [ {file = "sqlalchemy2_stubs-0.0.2a17-py3-none-any.whl", hash = "sha256:6f112f9381a29575676c3012ce10ed6bceabfad11f1d5569d7072ccf66cfa060"}, ] starlette = [ - {file = "starlette-0.14.2-py3-none-any.whl", hash = "sha256:3c8e48e52736b3161e34c9f0e8153b4f32ec5d8995a3ee1d59410d92f75162ed"}, - {file = "starlette-0.14.2.tar.gz", hash = "sha256:7d49f4a27f8742262ef1470608c59ddbc66baf37c148e938c7038e6bc7a998aa"}, + {file = "starlette-0.16.0-py3-none-any.whl", hash = "sha256:38eb24bf705a2c317e15868e384c1b8a12ca396e5a3c3a003db7e667c43f939f"}, + {file = "starlette-0.16.0.tar.gz", hash = "sha256:e1904b5d0007aee24bdd3c43994be9b3b729f4f58e740200de1d623f8c3a8870"}, ] strawberry-graphql = [ {file = "strawberry-graphql-0.77.12.tar.gz", hash = "sha256:1adc770643fa8423345a8c4c7a9956172f49a2e01559981be9e4ead6e80611fe"}, diff --git a/reddit-clone/pyproject.toml b/reddit-clone/pyproject.toml index 83a57c39..250792bf 100644 --- a/reddit-clone/pyproject.toml +++ b/reddit-clone/pyproject.toml @@ -12,13 +12,12 @@ strawberry-graphql = {extras = ["asgi"], version = "^0.77.0"} passlib = {extras = ["argon2"], version = "^1.7.4"} alembic = "^1.7.1" asyncpg = "^0.24" -fastapi = "^0.68.1" -starlette = "^0.14" graphql-relay = "^3.1.0" aiosmtplib = "^1.1.6" schematics = "^2.1.1" jinja2 = "^3.0.2" aiofiles = "^0.7.0" +starlette = "^0.16.0" [tool.poetry.dev-dependencies] diff --git a/reddit-clone/reddit/__init__.py b/reddit-clone/reddit/__init__.py index 1fd94fb2..cc799d90 100644 --- a/reddit-clone/reddit/__init__.py +++ b/reddit-clone/reddit/__init__.py @@ -1,13 +1,12 @@ from typing import Union, Optional, Any -from fastapi import FastAPI +from starlette.applications import Starlette from starlette.requests import Request from starlette.responses import Response from starlette.websockets import WebSocket from strawberry.dataloader import DataLoader from strawberry.asgi import GraphQL -from reddit import settings from reddit.schema import schema from reddit.users.loaders import load_users @@ -23,19 +22,16 @@ async def get_context( return context -def create_application() -> FastAPI: +def create_application() -> Starlette: """ Creates an application instance. :return: The created application. """ - application = FastAPI(title="Reddit GraphQL", debug=settings.DEBUG) + app = Starlette() + app.add_route(path="/graphql", route=GraphQL(schema=schema, graphiql=True)) - graphql_app = MyGraphQL(schema=schema, graphiql=True, debug=settings.DEBUG) - - application.add_route(path="/graphql", route=graphql_app) - - return application + return app app = create_application() From 3687f4069086ef0635fa88a4cc8bfceae6a49c23 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Mon, 11 Oct 2021 11:10:34 +0530 Subject: [PATCH 081/150] pass debug value --- reddit-clone/reddit/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/reddit-clone/reddit/__init__.py b/reddit-clone/reddit/__init__.py index cc799d90..f65beec6 100644 --- a/reddit-clone/reddit/__init__.py +++ b/reddit-clone/reddit/__init__.py @@ -7,6 +7,7 @@ from strawberry.dataloader import DataLoader from strawberry.asgi import GraphQL +from reddit import settings from reddit.schema import schema from reddit.users.loaders import load_users @@ -28,8 +29,11 @@ def create_application() -> Starlette: :return: The created application. """ - app = Starlette() - app.add_route(path="/graphql", route=GraphQL(schema=schema, graphiql=True)) + app = Starlette(debug=settings.DEBUG) + app.add_route( + path="/graphql", + route=GraphQL(schema=schema, graphiql=True, debug=settings.DEBUG), + ) return app From ffe32b1e5c6b40757546ecb68227a523796f60ac Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Mon, 11 Oct 2021 11:12:00 +0530 Subject: [PATCH 082/150] update graphql app --- reddit-clone/reddit/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/reddit-clone/reddit/__init__.py b/reddit-clone/reddit/__init__.py index f65beec6..fbf729db 100644 --- a/reddit-clone/reddit/__init__.py +++ b/reddit-clone/reddit/__init__.py @@ -30,9 +30,10 @@ def create_application() -> Starlette: :return: The created application. """ app = Starlette(debug=settings.DEBUG) + graphql_app = GraphQL(schema=schema, graphiql=True, debug=settings.DEBUG) app.add_route( path="/graphql", - route=GraphQL(schema=schema, graphiql=True, debug=settings.DEBUG), + route=graphql_app, ) return app From 3f16c6466a3f049819e3d40a1c0c402092e44b69 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Mon, 11 Oct 2021 11:20:55 +0530 Subject: [PATCH 083/150] create dir: utils --- reddit-clone/reddit/utils/__init__.py | 0 reddit-clone/reddit/{ => utils}/emails.py | 0 reddit-clone/reddit/{ => utils}/storage.py | 0 reddit-clone/reddit/{ => utils}/templates.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 reddit-clone/reddit/utils/__init__.py rename reddit-clone/reddit/{ => utils}/emails.py (100%) rename reddit-clone/reddit/{ => utils}/storage.py (100%) rename reddit-clone/reddit/{ => utils}/templates.py (100%) diff --git a/reddit-clone/reddit/utils/__init__.py b/reddit-clone/reddit/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/reddit-clone/reddit/emails.py b/reddit-clone/reddit/utils/emails.py similarity index 100% rename from reddit-clone/reddit/emails.py rename to reddit-clone/reddit/utils/emails.py diff --git a/reddit-clone/reddit/storage.py b/reddit-clone/reddit/utils/storage.py similarity index 100% rename from reddit-clone/reddit/storage.py rename to reddit-clone/reddit/utils/storage.py diff --git a/reddit-clone/reddit/templates.py b/reddit-clone/reddit/utils/templates.py similarity index 100% rename from reddit-clone/reddit/templates.py rename to reddit-clone/reddit/utils/templates.py From bc586951a2617ed43ac3ca7eace1017fc036b662 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Mon, 11 Oct 2021 11:23:36 +0530 Subject: [PATCH 084/150] describe mail config values --- reddit-clone/reddit/settings.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/reddit-clone/reddit/settings.py b/reddit-clone/reddit/settings.py index 12ac8eee..9d57003c 100644 --- a/reddit-clone/reddit/settings.py +++ b/reddit-clone/reddit/settings.py @@ -11,9 +11,17 @@ # SQLAlchemy database URL. DATABASE_URI: str = config("DATABASE_URI", cast=str) -# mail client configuration. +# mail client host name. MAIL_HOST: str = config("MAIL_HOST", cast=str) + +# mail client port. MAIL_PORT: int = config("MAIL_PORT", cast=int) + +# mail client auth username. MAIL_USERNAME: Optional[str] = config("MAIL_USERNAME", cast=str, default=None) + +# mail client auth password. MAIL_PASSWORD: Optional[str] = config("MAIL_PASSWORD", cast=str, default=None) + +# mail client sender address. MAIL_SENDER: Optional[str] = config("MAIL_SENDER", cast=str, default=None) From ec80bcae18f3a20e29142b60745f37f2e9bdd745 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Mon, 11 Oct 2021 11:32:18 +0530 Subject: [PATCH 085/150] update README.md --- reddit-clone/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/reddit-clone/README.md b/reddit-clone/README.md index 4f45270b..dbd24545 100644 --- a/reddit-clone/README.md +++ b/reddit-clone/README.md @@ -11,11 +11,13 @@ most of the features that Strawberry gives us. - [Starlette](https://github.com/encode/starlette) web framework - [SQLAlchemy](https://github.com/sqlalchemy/sqlalchemy) core/ mapper (asyncio) - [Alembic](https://github.com/sqlalchemy/alembic) migrations +- PostgreSQL database ## Features at a glance - [ ] Implements the Relay spec -- [ ] Error modelling within the schema +- [x] data modelling with relations +- [x] Error modelling within the schema - [ ] Authorization with the permissions API - [ ] Batch loading with dataloaders - [x] modular codebase From a2a6b8b040a57a7391712fdf64b8c1952d5739d4 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Mon, 11 Oct 2021 14:24:16 +0530 Subject: [PATCH 086/150] update README.md --- reddit-clone/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/reddit-clone/README.md b/reddit-clone/README.md index dbd24545..35168a8c 100644 --- a/reddit-clone/README.md +++ b/reddit-clone/README.md @@ -12,6 +12,7 @@ most of the features that Strawberry gives us. - [SQLAlchemy](https://github.com/sqlalchemy/sqlalchemy) core/ mapper (asyncio) - [Alembic](https://github.com/sqlalchemy/alembic) migrations - PostgreSQL database +- [Schematics](https://github.com/schematics/schematics) data validation ## Features at a glance From 6e0dbc3897c26dc5d9e3f43acf75232ea0cbfad8 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+codebyaryan@users.noreply.github.com> Date: Tue, 12 Oct 2021 14:34:03 +0530 Subject: [PATCH 087/150] Update templates.py --- reddit-clone/reddit/utils/templates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reddit-clone/reddit/utils/templates.py b/reddit-clone/reddit/utils/templates.py index 7240f333..567e1793 100644 --- a/reddit-clone/reddit/utils/templates.py +++ b/reddit-clone/reddit/utils/templates.py @@ -10,7 +10,7 @@ def render_template(template_name: str, *args, **kwargs): """ - Renders the html template with the given variables. + Renders the HTML template with the given variables. """ template = environment.get_template(name=template_name) return template.render(*args, **kwargs) From a7c2b0cfaeb8b962b1a2b381268b9dd61c00af84 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Wed, 13 Oct 2021 08:56:54 +0530 Subject: [PATCH 088/150] update strawberry (we need to access schema from resolver info) --- reddit-clone/poetry.lock | 36 ++++++++++++++++++++++++++---------- reddit-clone/pyproject.toml | 2 +- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/reddit-clone/poetry.lock b/reddit-clone/poetry.lock index 6736a64e..46072db4 100644 --- a/reddit-clone/poetry.lock +++ b/reddit-clone/poetry.lock @@ -162,7 +162,7 @@ pycparser = "*" [[package]] name = "click" -version = "8.0.2" +version = "8.0.3" description = "Composable command line interface toolkit" category = "main" optional = false @@ -274,7 +274,7 @@ test = ["Cython (==0.29.22)"] [[package]] name = "idna" -version = "3.2" +version = "3.3" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false @@ -509,6 +509,17 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "sentinel" +version = "0.3.0" +description = "Create sentinel objects, akin to None, NotImplemented, Ellipsis" +category = "main" +optional = false +python-versions = ">=3.6,<4.0" + +[package.extras] +varname = ["varname (>=0.1)"] + [[package]] name = "six" version = "1.16.0" @@ -588,7 +599,7 @@ full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests", "gra [[package]] name = "strawberry-graphql" -version = "0.77.12" +version = "0.82.2" description = "A library for creating GraphQL APIs" category = "main" optional = false @@ -601,6 +612,7 @@ graphql-core = ">=3.1.0,<3.2.0" pygments = ">=2.3,<3.0" python-dateutil = ">=2.7.0,<3.0.0" python-multipart = ">=0.0.5,<0.0.6" +sentinel = ">=0.3.0,<0.4.0" starlette = {version = ">=0.13.6,<0.17.0", optional = true, markers = "extra == \"asgi\" or extra == \"debug-server\""} typing_extensions = ">=3.7.4,<4.0.0" @@ -714,7 +726,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "9077eb8388b8be7ad3b4c11297b10ba19989b6c33bdd4acb57de30bd2d8090e6" +content-hash = "8d178b08b5558fedfba56530d5cedcd21cfc44a333d374c37487d542fa7be916" [metadata.files] aiofiles = [ @@ -825,8 +837,8 @@ cffi = [ {file = "cffi-1.14.6.tar.gz", hash = "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd"}, ] click = [ - {file = "click-8.0.2-py3-none-any.whl", hash = "sha256:3fab8aeb8f15f5452ae7511ad448977b3417325bceddd53df87e0bb81f3a8cf8"}, - {file = "click-8.0.2.tar.gz", hash = "sha256:7027bc7bbafaab8b2c2816861d8eb372429ee3c02e193fc2f93d6c4ab9de49c5"}, + {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, + {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, @@ -926,8 +938,8 @@ httptools = [ {file = "httptools-0.2.0.tar.gz", hash = "sha256:94505026be56652d7a530ab03d89474dc6021019d6b8682281977163b3471ea0"}, ] idna = [ - {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, - {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"}, + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] importlib-metadata = [ {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"}, @@ -1131,6 +1143,10 @@ schematics = [ {file = "schematics-2.1.1-py2.py3-none-any.whl", hash = "sha256:be2d451bfb86789975e5ec0864aec569b63cea9010f0d24cbbd992a4e564c647"}, {file = "schematics-2.1.1.tar.gz", hash = "sha256:34c87f51a25063bb498ae1cc201891b134cfcb329baf9e9f4f3ae869b767560f"}, ] +sentinel = [ + {file = "sentinel-0.3.0-py3-none-any.whl", hash = "sha256:bd8710dd26752039c668604f6be2aaf741b56f7811c5924a4dcdfd74359244f3"}, + {file = "sentinel-0.3.0.tar.gz", hash = "sha256:f28143aa4716dbc8f6193f5682176a3c33cd26aaae05d9ecf66c186a9887cc2d"}, +] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -1180,8 +1196,8 @@ starlette = [ {file = "starlette-0.16.0.tar.gz", hash = "sha256:e1904b5d0007aee24bdd3c43994be9b3b729f4f58e740200de1d623f8c3a8870"}, ] strawberry-graphql = [ - {file = "strawberry-graphql-0.77.12.tar.gz", hash = "sha256:1adc770643fa8423345a8c4c7a9956172f49a2e01559981be9e4ead6e80611fe"}, - {file = "strawberry_graphql-0.77.12-py3-none-any.whl", hash = "sha256:2080d93c1a80139b10ae5248ba2e8878c0182362ad9853424eecdd5aebdf3331"}, + {file = "strawberry-graphql-0.82.2.tar.gz", hash = "sha256:36ed3844d1a7fbaebf22698a72eaeaad2468e5c0ce4463ef678582ed70ca5883"}, + {file = "strawberry_graphql-0.82.2-py3-none-any.whl", hash = "sha256:000f6e74c026e8defab37b6967a1e7491aa646251ddef265369e6ad510088077"}, ] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, diff --git a/reddit-clone/pyproject.toml b/reddit-clone/pyproject.toml index 250792bf..a08c906c 100644 --- a/reddit-clone/pyproject.toml +++ b/reddit-clone/pyproject.toml @@ -8,7 +8,7 @@ authors = ["Aryan Iyappan "] python = "^3.7" uvicorn = {extras = ["standard"], version = "^0.15.0"} SQLAlchemy = {extras = ["mypy"], version = "^1.4.23"} -strawberry-graphql = {extras = ["asgi"], version = "^0.77.0"} +strawberry-graphql = {extras = ["asgi"], version = "^0.82.2"} passlib = {extras = ["argon2"], version = "^1.7.4"} alembic = "^1.7.1" asyncpg = "^0.24" From 9776bd336d13decbc36ec1bca3b1d6de3c6fd092 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Wed, 13 Oct 2021 09:01:21 +0530 Subject: [PATCH 089/150] update user queries --- reddit-clone/reddit/comments/types.py | 4 ---- reddit-clone/reddit/posts/types.py | 4 ---- reddit-clone/reddit/subreddits/types.py | 4 ---- reddit-clone/reddit/users/queries.py | 10 ++++++++-- reddit-clone/reddit/users/types.py | 4 ---- 5 files changed, 8 insertions(+), 18 deletions(-) diff --git a/reddit-clone/reddit/comments/types.py b/reddit-clone/reddit/comments/types.py index e5cc7697..ea79e755 100644 --- a/reddit-clone/reddit/comments/types.py +++ b/reddit-clone/reddit/comments/types.py @@ -47,7 +47,3 @@ async def resolve_node(cls, info: Info, comment_id: str) -> Optional[CommentType comment = await session.execute(query) if comment is not None: return cls.from_instance(comment) - - @classmethod - def from_instance(cls, instance: Comment) -> CommentType: - pass diff --git a/reddit-clone/reddit/posts/types.py b/reddit-clone/reddit/posts/types.py index c9a1cb30..478ab205 100644 --- a/reddit-clone/reddit/posts/types.py +++ b/reddit-clone/reddit/posts/types.py @@ -72,7 +72,3 @@ async def resolve_node(cls, info: Info, post_id: str) -> Optional[PostType]: post = await session.execute(query) if post is not None: return cls.from_instance(post) - - @classmethod - def from_instance(cls, instance: Post) -> PostType: - pass diff --git a/reddit-clone/reddit/subreddits/types.py b/reddit-clone/reddit/subreddits/types.py index 7bf23b32..3530fc56 100644 --- a/reddit-clone/reddit/subreddits/types.py +++ b/reddit-clone/reddit/subreddits/types.py @@ -63,7 +63,3 @@ async def resolve_node( subreddit = await session.execute(query) if subreddit is not None: return cls.from_instance(subreddit) - - @classmethod - def from_instance(cls, instance: Subreddit) -> SubredditType: - pass diff --git a/reddit-clone/reddit/users/queries.py b/reddit-clone/reddit/users/queries.py index 50fe6e13..6f776a1b 100644 --- a/reddit-clone/reddit/users/queries.py +++ b/reddit-clone/reddit/users/queries.py @@ -1,14 +1,20 @@ -from typing import Optional +from typing import Optional, cast import strawberry from strawberry.tools import create_type from strawberry.types import Info +from reddit.database import get_session from reddit.users.types import UserType +from reddit.users.models import User async def resolve_user(info: Info, username: str) -> Optional[UserType]: - pass + async with get_session() as session: + user = await User.by_username(session=session, username=username) + if user is not None: + return cast(UserType, user) + return user user = strawberry.field( diff --git a/reddit-clone/reddit/users/types.py b/reddit-clone/reddit/users/types.py index 496e694f..997f92bf 100644 --- a/reddit-clone/reddit/users/types.py +++ b/reddit-clone/reddit/users/types.py @@ -56,7 +56,3 @@ async def resolve_node(cls, info: Info, user_id: str) -> Optional[UserType]: user = await session.execute(query) if user is not None: return cls.from_instance(user) - - @classmethod - def from_instance(cls, instance: User) -> UserType: - pass From 2f97af79d4e4d4c7e083b4787ea044af1dbbfb13 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Wed, 13 Oct 2021 09:06:08 +0530 Subject: [PATCH 090/150] update user queries --- reddit-clone/reddit/users/queries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reddit-clone/reddit/users/queries.py b/reddit-clone/reddit/users/queries.py index 6f776a1b..f6eef9e9 100644 --- a/reddit-clone/reddit/users/queries.py +++ b/reddit-clone/reddit/users/queries.py @@ -25,7 +25,7 @@ async def resolve_user(info: Info, username: str) -> Optional[UserType]: ) -async def resolve_current_user(info: Info) -> UserType: +async def resolve_current_user(info: Info) -> Optional[UserType]: pass From e2d796b90c9eace61139fd40f6aac26a4b620941 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Wed, 13 Oct 2021 11:05:28 +0530 Subject: [PATCH 091/150] add connection types --- reddit-clone/reddit/base/types.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/reddit-clone/reddit/base/types.py b/reddit-clone/reddit/base/types.py index 7d3096e7..452d4eef 100644 --- a/reddit-clone/reddit/base/types.py +++ b/reddit-clone/reddit/base/types.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Optional, Type +from typing import List, Optional, Type import strawberry from strawberry.types import Info @@ -51,3 +51,25 @@ def from_global_id(cls, global_id: str): @classmethod def to_global_id(cls, schema_type: str, id: str) -> str: return to_global_id(schema_type, id) + + +@strawberry.type(name="Edge") +class EdgeType: + cursor: str + node: NodeType + + +@strawberry.type(name="PageInfo") +class PageInfoType: + end_cursor: Optional[str] + has_next_page: bool + has_previous_page: bool + start_cursor: Optional[str] + + +@strawberry.type(name="Connection") +class ConnectionType: + edges: List[EdgeType] + nodes: List[NodeType] + page_info: PageInfoType + total_count: int From feb613e630d06ca484c873d4b8d74a91b7bfb50a Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Wed, 13 Oct 2021 11:17:28 +0530 Subject: [PATCH 092/150] update node type --- reddit-clone/reddit/base/types.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/reddit-clone/reddit/base/types.py b/reddit-clone/reddit/base/types.py index 452d4eef..0218b30c 100644 --- a/reddit-clone/reddit/base/types.py +++ b/reddit-clone/reddit/base/types.py @@ -20,16 +20,16 @@ def resolve( cls, info: Info, global_id: str, only_type=None ) -> Optional[Type[NodeType]]: try: - _type, _id = cls.from_global_id(global_id) + type_name, _id = cls.from_global_id(global_id) except Exception as e: raise Exception( f'Unable to parse global ID "{global_id}". ' f"Exception message: {str(e)}" ) - schema_type = info.schema.get_type(_type) + schema_type = info.schema.get_type_by_name(type_name) if schema_type is None: - raise Exception(f'Relay Node "{_type}" not found in schema') + raise Exception(f'Relay Node "{type_name}" not found in schema') if only_type: assert schema_type == only_type @@ -37,7 +37,7 @@ def resolve( # We make sure the ObjectType implements the "Node" interface if cls not in schema_type._type_definition.interfaces: raise Exception( - f'ObjectType "{_type}" does not implement the "{cls}" interface.' + f'ObjectType "{type_name}" does not implement the "{cls}" interface.' ) resolver = getattr(schema_type, "resolve_node", None) From 4fea150d0e744e805e3f064d86712777c4a0cc84 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Wed, 13 Oct 2021 11:27:43 +0530 Subject: [PATCH 093/150] document relay types --- reddit-clone/reddit/base/types.py | 67 ++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 10 deletions(-) diff --git a/reddit-clone/reddit/base/types.py b/reddit-clone/reddit/base/types.py index 0218b30c..037cdaab 100644 --- a/reddit-clone/reddit/base/types.py +++ b/reddit-clone/reddit/base/types.py @@ -55,21 +55,68 @@ def to_global_id(cls, schema_type: str, id: str) -> str: @strawberry.type(name="Edge") class EdgeType: - cursor: str - node: NodeType + cursor: str = strawberry.field( + description=""" + A cursor for use in pagination. + """ + ) + + node: NodeType = strawberry.field( + description=""" + The item at the end of the edge. + """ + ) @strawberry.type(name="PageInfo") class PageInfoType: - end_cursor: Optional[str] - has_next_page: bool - has_previous_page: bool - start_cursor: Optional[str] + end_cursor: Optional[str] = strawberry.field( + description=""" + When paginating forwards, the cursor to continue. + """ + ) + + has_next_page: bool = strawberry.field( + description=""" + When paginating forwards, are there more items? + """ + ) + + has_previous_page: bool = strawberry.field( + description=""" + When paginating backwards, are there more items? + """ + ) + + start_cursor: Optional[str] = strawberry.field( + description=""" + When paginating backwards, the cursor to continue. + """ + ) @strawberry.type(name="Connection") class ConnectionType: - edges: List[EdgeType] - nodes: List[NodeType] - page_info: PageInfoType - total_count: int + edges: List[EdgeType] = strawberry.field( + description=""" + Contains the edges in this connection. + """ + ) + + nodes: List[NodeType] = strawberry.field( + description=""" + Contains the nodes in this connection. + """ + ) + + page_info: PageInfoType = strawberry.field( + description=""" + Information to aid in pagination. + """ + ) + + total_count: int = strawberry.field( + description=""" + Identifies the total count of items in the connection. + """ + ) From 4051849e7febee29c45efce8ccd3b83cfda0812a Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Wed, 13 Oct 2021 11:28:48 +0530 Subject: [PATCH 094/150] document relay types --- reddit-clone/reddit/base/types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reddit-clone/reddit/base/types.py b/reddit-clone/reddit/base/types.py index 037cdaab..002bb4c2 100644 --- a/reddit-clone/reddit/base/types.py +++ b/reddit-clone/reddit/base/types.py @@ -99,13 +99,13 @@ class PageInfoType: class ConnectionType: edges: List[EdgeType] = strawberry.field( description=""" - Contains the edges in this connection. + Contains the edges in the connection. """ ) nodes: List[NodeType] = strawberry.field( description=""" - Contains the nodes in this connection. + Contains the nodes in the connection. """ ) From f9ab261c3ce17fe6809a78dfa65eef13dfbded96 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Wed, 13 Oct 2021 11:36:32 +0530 Subject: [PATCH 095/150] simplify node type --- reddit-clone/reddit/base/types.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/reddit-clone/reddit/base/types.py b/reddit-clone/reddit/base/types.py index 002bb4c2..5d7f1ff2 100644 --- a/reddit-clone/reddit/base/types.py +++ b/reddit-clone/reddit/base/types.py @@ -16,9 +16,7 @@ class NodeType: ) @classmethod - def resolve( - cls, info: Info, global_id: str, only_type=None - ) -> Optional[Type[NodeType]]: + def resolve(cls, info: Info, global_id: str) -> Optional[Type[NodeType]]: try: type_name, _id = cls.from_global_id(global_id) except Exception as e: @@ -31,9 +29,6 @@ def resolve( if schema_type is None: raise Exception(f'Relay Node "{type_name}" not found in schema') - if only_type: - assert schema_type == only_type - # We make sure the ObjectType implements the "Node" interface if cls not in schema_type._type_definition.interfaces: raise Exception( From 667dad572e69a8dd40d5d3a66e7c69b83ce9c7bf Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Thu, 14 Oct 2021 14:13:26 +0530 Subject: [PATCH 096/150] update services --- reddit-clone/reddit/users/models.py | 21 ++------------------- reddit-clone/reddit/users/services.py | 23 ++++++++++++++++++++--- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/reddit-clone/reddit/users/models.py b/reddit-clone/reddit/users/models.py index 87aa74b6..8e23be22 100644 --- a/reddit-clone/reddit/users/models.py +++ b/reddit-clone/reddit/users/models.py @@ -1,10 +1,9 @@ from __future__ import annotations -from typing import List, Optional +from typing import List from passlib.hash import argon2 -from sqlalchemy import select, Column, String, Integer, ForeignKey -from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import Column, String, Integer, ForeignKey from sqlalchemy.orm import relationship from sqlalchemy.sql.sqltypes import Boolean @@ -46,22 +45,6 @@ class User(Base): def __repr__(self) -> str: return f"" - @classmethod - async def by_username(cls, session: AsyncSession, username: str) -> Optional[User]: - """ - Gets an user by their username. - """ - query = select(User).filter_by(username=username).first() - return await session.execute(query) - - @classmethod - async def by_email(cls, session: AsyncSession, email: str) -> Optional[User]: - """ - Gets an user by their email. - """ - query = select(User).filter_by(email=email).first() - return await session.execute(query) - def set_password(self, password: str) -> None: """ Sets a hashed version of the provided diff --git a/reddit-clone/reddit/users/services.py b/reddit-clone/reddit/users/services.py index 15f3161a..9668fd5f 100644 --- a/reddit-clone/reddit/users/services.py +++ b/reddit-clone/reddit/users/services.py @@ -1,17 +1,34 @@ from typing import Optional +from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from reddit.users.models import User +async def user_by_email(session: AsyncSession, email: str) -> Optional[User]: + """ + Gets an user by their email. + """ + query = select(User).filter_by(email=email).first() + return await session.execute(query) + + +async def user_by_username(session: AsyncSession, username: str) -> Optional[User]: + """ + Gets an user by their username. + """ + query = select(User).filter_by(username=username).first() + return await session.execute(query) + + async def authenticate( session: AsyncSession, username: str, password: str ) -> Optional[User]: """ Checks if the provided user credentials are valid. """ - user = await User.by_username(session=session, username=username) + user = await user_by_username(session=session, username=username) if user is None or not user.check_password(password=password): # TODO: handle exception here. pass @@ -59,7 +76,7 @@ async def request_change_email( if not user.check_password(password=password): # TODO: handle exception here. pass - user = await User.by_email(session=session, email=email) + user = await user_by_email(session=session, email=email) if user is not None: # TODO: handle exception here. pass @@ -80,7 +97,7 @@ async def request_reset_password(session: AsyncSession, email: str): Sends a password reset code to the given email, if it actually exists. """ - user = await User.by_email(session=session, email=email) + user = await user_by_email(session=session, email=email) if user is not None: # TODO: send password reset email here. pass From 58f2da054873db823ccb3917b3597b617e3b3e99 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Thu, 14 Oct 2021 14:25:19 +0530 Subject: [PATCH 097/150] update user services --- reddit-clone/reddit/users/services.py | 8 ++++++++ reddit-clone/reddit/users/types.py | 10 ++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/reddit-clone/reddit/users/services.py b/reddit-clone/reddit/users/services.py index 9668fd5f..060cb069 100644 --- a/reddit-clone/reddit/users/services.py +++ b/reddit-clone/reddit/users/services.py @@ -6,6 +6,14 @@ from reddit.users.models import User +async def user_by_id(session: AsyncSession, id: int) -> Optional[User]: + """ + Gets an user by their ID. + """ + query = select(User).filter_by(id=id).first() + return await session.execute(query) + + async def user_by_email(session: AsyncSession, email: str) -> Optional[User]: """ Gets an user by their email. diff --git a/reddit-clone/reddit/users/types.py b/reddit-clone/reddit/users/types.py index 997f92bf..07dc9bc5 100644 --- a/reddit-clone/reddit/users/types.py +++ b/reddit-clone/reddit/users/types.py @@ -1,12 +1,11 @@ from __future__ import annotations -from typing import List, Optional +from typing import List, Optional, cast import strawberry from strawberry.types import Info -from sqlalchemy import select -from reddit.users.models import User +from reddit.users.services import user_by_id from reddit.base.types import NodeType from reddit.posts.types import PostType from reddit.subreddits.types import SubredditType @@ -51,8 +50,7 @@ async def resolve_node(cls, info: Info, user_id: str) -> Optional[UserType]: """ Gets an user with the given ID. """ - query = select(User).filter_by(id=user_id).first() async with get_session() as session: - user = await session.execute(query) + user = await user_by_id(session=session, id=user_id) if user is not None: - return cls.from_instance(user) + return cast(UserType, user) From eeacb09f97c673e9d1bdba6709788ffaea45bf14 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Thu, 14 Oct 2021 15:02:00 +0530 Subject: [PATCH 098/150] add more dataloaders --- reddit-clone/reddit/__init__.py | 10 +++++++++- reddit-clone/reddit/comments/loaders.py | 20 ++++++++++++++++++++ reddit-clone/reddit/comments/types.py | 13 ++++--------- reddit-clone/reddit/posts/loaders.py | 18 ++++++++++++++++++ reddit-clone/reddit/posts/types.py | 13 ++++--------- reddit-clone/reddit/subreddits/loaders.py | 20 ++++++++++++++++++++ reddit-clone/reddit/subreddits/types.py | 13 ++++--------- reddit-clone/reddit/users/services.py | 8 -------- reddit-clone/reddit/users/types.py | 9 +++------ 9 files changed, 82 insertions(+), 42 deletions(-) create mode 100644 reddit-clone/reddit/comments/loaders.py create mode 100644 reddit-clone/reddit/posts/loaders.py create mode 100644 reddit-clone/reddit/subreddits/loaders.py diff --git a/reddit-clone/reddit/__init__.py b/reddit-clone/reddit/__init__.py index fbf729db..0378f785 100644 --- a/reddit-clone/reddit/__init__.py +++ b/reddit-clone/reddit/__init__.py @@ -10,6 +10,9 @@ from reddit import settings from reddit.schema import schema from reddit.users.loaders import load_users +from reddit.subreddits.loaders import load_subreddits +from reddit.posts.loaders import load_posts +from reddit.comments.loaders import load_comments __all__ = ("app",) @@ -19,7 +22,12 @@ async def get_context( self, request: Union[Request, WebSocket], response: Optional[Response] = None ) -> Optional[Any]: context: dict = await super().get_context(request, response=response) - context.update(user_loader=DataLoader(load_fn=load_users)) + context.update( + user_loader=DataLoader(load_fn=load_users), + subreddit_loader=DataLoader(load_fn=load_subreddits), + post_loader=DataLoader(load_fn=load_posts), + comment_loader=DataLoader(load_fn=load_comments), + ) return context diff --git a/reddit-clone/reddit/comments/loaders.py b/reddit-clone/reddit/comments/loaders.py new file mode 100644 index 00000000..50b28da0 --- /dev/null +++ b/reddit-clone/reddit/comments/loaders.py @@ -0,0 +1,20 @@ +from typing import List, Optional, Dict + +from sqlalchemy import select +from sqlalchemy.engine import Result + +from reddit.database import get_session +from reddit.comments.models import Comment + + +async def load_comments(comment_ids: List[int]) -> List[Optional[Comment]]: + """ + Batch-loads comments by their IDs. + """ + query = select(Comment).filter(Comment.id.in_(comment_ids)) + async with get_session() as session: + result: Result = await session.execute(query) + comment_map: Dict[int, Comment] = { + comment.id: comment for comment in result.scalars() + } + return [comment_map.get(comment_id) for comment_id in comment_ids] diff --git a/reddit-clone/reddit/comments/types.py b/reddit-clone/reddit/comments/types.py index ea79e755..777d7da0 100644 --- a/reddit-clone/reddit/comments/types.py +++ b/reddit-clone/reddit/comments/types.py @@ -1,14 +1,11 @@ from __future__ import annotations -from typing import List, Optional +from typing import List, Optional, cast import strawberry from strawberry.types import Info -from sqlalchemy import select from reddit.base.types import NodeType -from reddit.comments.models import Comment -from reddit.database import get_session @strawberry.type(name="Comment") @@ -42,8 +39,6 @@ async def resolve_node(cls, info: Info, comment_id: str) -> Optional[CommentType """ Gets a comment with the given ID. """ - query = select(Comment).filter_by(id=comment_id).first() - async with get_session() as session: - comment = await session.execute(query) - if comment is not None: - return cls.from_instance(comment) + loader = info.context.get("comment_loader") + comment = await loader.load(comment_id) + return cast(CommentType, comment) diff --git a/reddit-clone/reddit/posts/loaders.py b/reddit-clone/reddit/posts/loaders.py new file mode 100644 index 00000000..bf8cb95e --- /dev/null +++ b/reddit-clone/reddit/posts/loaders.py @@ -0,0 +1,18 @@ +from typing import List, Optional, Dict + +from sqlalchemy import select +from sqlalchemy.engine import Result + +from reddit.database import get_session +from reddit.posts.models import Post + + +async def load_posts(post_ids: List[int]) -> List[Optional[Post]]: + """ + Batch-loads posts by their IDs. + """ + query = select(Post).filter(Post.id.in_(post_ids)) + async with get_session() as session: + result: Result = await session.execute(query) + post_map: Dict[int, Post] = {post.id: post for post in result.scalars()} + return [post_map.get(post_id) for post_id in post_ids] diff --git a/reddit-clone/reddit/posts/types.py b/reddit-clone/reddit/posts/types.py index 478ab205..67a58baa 100644 --- a/reddit-clone/reddit/posts/types.py +++ b/reddit-clone/reddit/posts/types.py @@ -1,15 +1,12 @@ from __future__ import annotations -from typing import List, Optional +from typing import List, Optional, cast import strawberry from strawberry.types import Info -from sqlalchemy import select from reddit.base.types import NodeType -from reddit.posts.models import Post from reddit.comments.types import CommentType -from reddit.database import get_session @strawberry.type(name="Post") @@ -67,8 +64,6 @@ async def resolve_node(cls, info: Info, post_id: str) -> Optional[PostType]: """ Gets a post with the given ID. """ - query = select(Post).filter_by(id=post_id).first() - async with get_session() as session: - post = await session.execute(query) - if post is not None: - return cls.from_instance(post) + loader = info.context.get("post_loader") + post = await loader.load(post_id) + return cast(PostType, post) diff --git a/reddit-clone/reddit/subreddits/loaders.py b/reddit-clone/reddit/subreddits/loaders.py new file mode 100644 index 00000000..39d96a3a --- /dev/null +++ b/reddit-clone/reddit/subreddits/loaders.py @@ -0,0 +1,20 @@ +from typing import List, Optional, Dict + +from sqlalchemy import select +from sqlalchemy.engine import Result + +from reddit.database import get_session +from reddit.subreddits.models import Subreddit + + +async def load_subreddits(subreddit_ids: List[int]) -> List[Optional[Subreddit]]: + """ + Batch-loads subreddits by their IDs. + """ + query = select(Subreddit).filter(Subreddit.id.in_(subreddit_ids)) + async with get_session() as session: + result: Result = await session.execute(query) + subreddit_map: Dict[int, Subreddit] = { + subreddit.id: subreddit for subreddit in result.scalars() + } + return [subreddit_map.get(subreddit_id) for subreddit_id in subreddit_ids] diff --git a/reddit-clone/reddit/subreddits/types.py b/reddit-clone/reddit/subreddits/types.py index 3530fc56..43fb65e1 100644 --- a/reddit-clone/reddit/subreddits/types.py +++ b/reddit-clone/reddit/subreddits/types.py @@ -1,15 +1,12 @@ from __future__ import annotations -from typing import List, Optional +from typing import List, Optional, cast import strawberry from strawberry.types import Info -from sqlalchemy import select from reddit.base.types import NodeType -from reddit.subreddits.models import Subreddit from reddit.posts.types import PostType -from reddit.database import get_session @strawberry.type(name="Subreddit") @@ -58,8 +55,6 @@ async def resolve_node( """ Gets a Subreddit with the given ID. """ - query = select(Subreddit).filter_by(id=subreddit_id).first() - async with get_session() as session: - subreddit = await session.execute(query) - if subreddit is not None: - return cls.from_instance(subreddit) + loader = info.context.get("subreddit_loader") + subreddit = await loader.load(subreddit_id) + return cast(SubredditType, subreddit) diff --git a/reddit-clone/reddit/users/services.py b/reddit-clone/reddit/users/services.py index 060cb069..9668fd5f 100644 --- a/reddit-clone/reddit/users/services.py +++ b/reddit-clone/reddit/users/services.py @@ -6,14 +6,6 @@ from reddit.users.models import User -async def user_by_id(session: AsyncSession, id: int) -> Optional[User]: - """ - Gets an user by their ID. - """ - query = select(User).filter_by(id=id).first() - return await session.execute(query) - - async def user_by_email(session: AsyncSession, email: str) -> Optional[User]: """ Gets an user by their email. diff --git a/reddit-clone/reddit/users/types.py b/reddit-clone/reddit/users/types.py index 07dc9bc5..726b3aff 100644 --- a/reddit-clone/reddit/users/types.py +++ b/reddit-clone/reddit/users/types.py @@ -5,12 +5,10 @@ import strawberry from strawberry.types import Info -from reddit.users.services import user_by_id from reddit.base.types import NodeType from reddit.posts.types import PostType from reddit.subreddits.types import SubredditType from reddit.comments.types import CommentType -from reddit.database import get_session @strawberry.type(name="User") @@ -50,7 +48,6 @@ async def resolve_node(cls, info: Info, user_id: str) -> Optional[UserType]: """ Gets an user with the given ID. """ - async with get_session() as session: - user = await user_by_id(session=session, id=user_id) - if user is not None: - return cast(UserType, user) + loader = info.context.get("user_loader") + user = await loader.load(user_id) + return cast(UserType, user) From 699a09a071088ebe0a376458c37d3b75da3884d4 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Thu, 14 Oct 2021 15:02:48 +0530 Subject: [PATCH 099/150] fix graphql asgi app --- reddit-clone/reddit/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reddit-clone/reddit/__init__.py b/reddit-clone/reddit/__init__.py index 0378f785..0f2b396c 100644 --- a/reddit-clone/reddit/__init__.py +++ b/reddit-clone/reddit/__init__.py @@ -38,7 +38,7 @@ def create_application() -> Starlette: :return: The created application. """ app = Starlette(debug=settings.DEBUG) - graphql_app = GraphQL(schema=schema, graphiql=True, debug=settings.DEBUG) + graphql_app = MyGraphQL(schema=schema, graphiql=True, debug=settings.DEBUG) app.add_route( path="/graphql", route=graphql_app, From 1a33dc34b4250011bfcc36a707b8c1a087c4f4d3 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Thu, 14 Oct 2021 15:17:44 +0530 Subject: [PATCH 100/150] simplify base query --- reddit-clone/reddit/__init__.py | 2 +- reddit-clone/reddit/base/queries.py | 20 +++++++++++++++- reddit-clone/reddit/base/types.py | 36 +---------------------------- 3 files changed, 21 insertions(+), 37 deletions(-) diff --git a/reddit-clone/reddit/__init__.py b/reddit-clone/reddit/__init__.py index 0f2b396c..de20772a 100644 --- a/reddit-clone/reddit/__init__.py +++ b/reddit-clone/reddit/__init__.py @@ -21,7 +21,7 @@ class MyGraphQL(GraphQL): async def get_context( self, request: Union[Request, WebSocket], response: Optional[Response] = None ) -> Optional[Any]: - context: dict = await super().get_context(request, response=response) + context = await super().get_context(request, response=response) context.update( user_loader=DataLoader(load_fn=load_users), subreddit_loader=DataLoader(load_fn=load_subreddits), diff --git a/reddit-clone/reddit/base/queries.py b/reddit-clone/reddit/base/queries.py index b70f4744..3baeb032 100644 --- a/reddit-clone/reddit/base/queries.py +++ b/reddit-clone/reddit/base/queries.py @@ -3,12 +3,30 @@ import strawberry from strawberry.tools import create_type from strawberry.types import Info +from graphql_relay import from_global_id from reddit.base.types import NodeType def resolve_node(info: Info, id: strawberry.ID) -> Optional[NodeType]: - return NodeType.resolve(info=info, global_id=id) + try: + type_name, _id = from_global_id(id) + except Exception: + raise Exception(f'Unable to parse global ID "{id}".') + + schema_type = info.schema.get_type_by_name(type_name) + if schema_type is None: + raise Exception(f'Relay Node "{type_name}" not found in schema') + + # We make sure the ObjectType implements the "Node" interface + if NodeType not in schema_type._type_definition.interfaces: + raise Exception( + f'ObjectType "{type_name}" does not implement the "{NodeType}" interface.' + ) + + resolver = getattr(schema_type, "resolve_node", None) + if resolver is not None: + return resolver(info, _id) node = strawberry.field( diff --git a/reddit-clone/reddit/base/types.py b/reddit-clone/reddit/base/types.py index 5d7f1ff2..47547737 100644 --- a/reddit-clone/reddit/base/types.py +++ b/reddit-clone/reddit/base/types.py @@ -1,10 +1,8 @@ from __future__ import annotations -from typing import List, Optional, Type +from typing import List, Optional import strawberry -from strawberry.types import Info -from graphql_relay import from_global_id, to_global_id @strawberry.interface(name="Node", description="An object with an ID.") @@ -15,38 +13,6 @@ class NodeType: """ ) - @classmethod - def resolve(cls, info: Info, global_id: str) -> Optional[Type[NodeType]]: - try: - type_name, _id = cls.from_global_id(global_id) - except Exception as e: - raise Exception( - f'Unable to parse global ID "{global_id}". ' - f"Exception message: {str(e)}" - ) - - schema_type = info.schema.get_type_by_name(type_name) - if schema_type is None: - raise Exception(f'Relay Node "{type_name}" not found in schema') - - # We make sure the ObjectType implements the "Node" interface - if cls not in schema_type._type_definition.interfaces: - raise Exception( - f'ObjectType "{type_name}" does not implement the "{cls}" interface.' - ) - - resolver = getattr(schema_type, "resolve_node", None) - if resolver is not None: - return resolver(info, _id) - - @classmethod - def from_global_id(cls, global_id: str): - return from_global_id(global_id) - - @classmethod - def to_global_id(cls, schema_type: str, id: str) -> str: - return to_global_id(schema_type, id) - @strawberry.type(name="Edge") class EdgeType: From 9fe4063e4eaec23748df63d0b7a1d05898ca1b0a Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Thu, 14 Oct 2021 15:20:51 +0530 Subject: [PATCH 101/150] update services --- reddit-clone/reddit/users/services.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/reddit-clone/reddit/users/services.py b/reddit-clone/reddit/users/services.py index 9668fd5f..121eea4a 100644 --- a/reddit-clone/reddit/users/services.py +++ b/reddit-clone/reddit/users/services.py @@ -99,7 +99,8 @@ async def request_reset_password(session: AsyncSession, email: str): """ user = await user_by_email(session=session, email=email) if user is not None: - # TODO: send password reset email here. + # TODO: store password reset code and + # send password reset email here. pass From 2ca82499e377c5d5e13d78b2e536b925608fa06d Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Thu, 14 Oct 2021 15:38:13 +0530 Subject: [PATCH 102/150] flag abstract types --- reddit-clone/reddit/base/types.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/reddit-clone/reddit/base/types.py b/reddit-clone/reddit/base/types.py index 47547737..04b6ca78 100644 --- a/reddit-clone/reddit/base/types.py +++ b/reddit-clone/reddit/base/types.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import List, Optional +from typing import Generic, List, Optional, TypeVar import strawberry @@ -14,9 +14,17 @@ class NodeType: ) +Node = TypeVar("Node") + +Cursor = TypeVar("Cursor") + +Edge = TypeVar("Edge") + + +# TODO: make an abstract type @strawberry.type(name="Edge") -class EdgeType: - cursor: str = strawberry.field( +class EdgeType(Generic[Node, Cursor]): + cursor: Cursor = strawberry.field( description=""" A cursor for use in pagination. """ @@ -56,15 +64,16 @@ class PageInfoType: ) +# TODO: make an abstract type @strawberry.type(name="Connection") -class ConnectionType: - edges: List[EdgeType] = strawberry.field( +class ConnectionType(Generic[Node, Edge]): + edges: List[Edge] = strawberry.field( description=""" Contains the edges in the connection. """ ) - nodes: List[NodeType] = strawberry.field( + nodes: List[Node] = strawberry.field( description=""" Contains the nodes in the connection. """ From 19f5c3a5305a8a98d1f0c68093cae6439786cb05 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Thu, 14 Oct 2021 15:39:20 +0530 Subject: [PATCH 103/150] make pageinfo-cursor generic --- reddit-clone/reddit/base/types.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/reddit-clone/reddit/base/types.py b/reddit-clone/reddit/base/types.py index 04b6ca78..fc022445 100644 --- a/reddit-clone/reddit/base/types.py +++ b/reddit-clone/reddit/base/types.py @@ -38,8 +38,8 @@ class EdgeType(Generic[Node, Cursor]): @strawberry.type(name="PageInfo") -class PageInfoType: - end_cursor: Optional[str] = strawberry.field( +class PageInfoType(Generic[Cursor]): + end_cursor: Optional[Cursor] = strawberry.field( description=""" When paginating forwards, the cursor to continue. """ @@ -57,7 +57,7 @@ class PageInfoType: """ ) - start_cursor: Optional[str] = strawberry.field( + start_cursor: Optional[Cursor] = strawberry.field( description=""" When paginating backwards, the cursor to continue. """ From 2d170ffc09ddc4d208a81b9abad0da931f83881f Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Thu, 14 Oct 2021 15:41:04 +0530 Subject: [PATCH 104/150] pass kwargs to type var --- reddit-clone/reddit/base/types.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/reddit-clone/reddit/base/types.py b/reddit-clone/reddit/base/types.py index fc022445..6c33bf37 100644 --- a/reddit-clone/reddit/base/types.py +++ b/reddit-clone/reddit/base/types.py @@ -14,11 +14,11 @@ class NodeType: ) -Node = TypeVar("Node") +Node = TypeVar(name="Node") -Cursor = TypeVar("Cursor") +Cursor = TypeVar(name="Cursor") -Edge = TypeVar("Edge") +Edge = TypeVar(name="Edge") # TODO: make an abstract type From ca92138cdff858d542a1a3ee7fad669aef4d661b Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Thu, 14 Oct 2021 16:02:01 +0530 Subject: [PATCH 105/150] add skeletons for services --- reddit-clone/reddit/comments/models.py | 2 +- reddit-clone/reddit/comments/services.py | 38 ++++++++++++++++++ reddit-clone/reddit/comments/types.py | 2 +- reddit-clone/reddit/posts/models.py | 2 +- reddit-clone/reddit/posts/services.py | 45 ++++++++++++++++++++++ reddit-clone/reddit/posts/types.py | 2 +- reddit-clone/reddit/subreddits/models.py | 6 +-- reddit-clone/reddit/subreddits/services.py | 34 ++++++++++++++++ reddit-clone/reddit/subreddits/types.py | 2 +- reddit-clone/reddit/users/services.py | 4 +- 10 files changed, 126 insertions(+), 11 deletions(-) create mode 100644 reddit-clone/reddit/comments/services.py create mode 100644 reddit-clone/reddit/posts/services.py create mode 100644 reddit-clone/reddit/subreddits/services.py diff --git a/reddit-clone/reddit/comments/models.py b/reddit-clone/reddit/comments/models.py index 80ec648e..3f8582e2 100644 --- a/reddit-clone/reddit/comments/models.py +++ b/reddit-clone/reddit/comments/models.py @@ -21,7 +21,7 @@ class Comment(Base): votes: int = Column(Integer, default=1) - user_id: Optional[int] = Column(Integer, ForeignKey("users.id")) + owner_id: Optional[int] = Column(Integer, ForeignKey("users.id")) post_id: int = Column(Integer, ForeignKey("posts.id")) diff --git a/reddit-clone/reddit/comments/services.py b/reddit-clone/reddit/comments/services.py new file mode 100644 index 00000000..29a2935f --- /dev/null +++ b/reddit-clone/reddit/comments/services.py @@ -0,0 +1,38 @@ +from sqlalchemy.ext.asyncio import AsyncSession + +from reddit.users.models import User +from reddit.comments.models import Comment + + +async def create_comment( + session: AsyncSession, content: str, owner_id: int, post_id: int +) -> Comment: + """ + Creates a new comment instance. + """ + comment = Comment(content=content, owner_id=owner_id, post_id=post_id) + # TODO: validate input data here. + session.add(instance=comment) + await session.commit() + await session.refresh(instance=comment) + return comment + + +async def vote_comment(session: AsyncSession, comment: Comment, user: User) -> Comment: + """ + Creates a vote on the given comment. + """ + + +async def update_comment(session: AsyncSession, comment: Comment) -> Comment: + """ + Updates the given comment instance. + """ + + +async def delete_comment( + session: AsyncSession, comment: Comment, user: User +) -> Comment: + """ + Deletes the given comment instance. + """ diff --git a/reddit-clone/reddit/comments/types.py b/reddit-clone/reddit/comments/types.py index 777d7da0..f2050dd0 100644 --- a/reddit-clone/reddit/comments/types.py +++ b/reddit-clone/reddit/comments/types.py @@ -22,7 +22,7 @@ class CommentType(NodeType): """ ) - user_id: Optional[int] = strawberry.field( + owner_id: Optional[int] = strawberry.field( description=""" The owner ID of the comment. """ diff --git a/reddit-clone/reddit/posts/models.py b/reddit-clone/reddit/posts/models.py index 0d314006..85dcda92 100644 --- a/reddit-clone/reddit/posts/models.py +++ b/reddit-clone/reddit/posts/models.py @@ -24,7 +24,7 @@ class Post(Base): thumbnail: Optional[str] = Column(String(255), default=None) - user_id: int = Column(Integer, ForeignKey("users.id")) + owner_id: int = Column(Integer, ForeignKey("users.id")) subreddit_id: int = Column(Integer, ForeignKey("subreddits.id")) diff --git a/reddit-clone/reddit/posts/services.py b/reddit-clone/reddit/posts/services.py new file mode 100644 index 00000000..8e2db89a --- /dev/null +++ b/reddit-clone/reddit/posts/services.py @@ -0,0 +1,45 @@ +from typing import Optional + +from sqlalchemy.ext.asyncio import AsyncSession + +from reddit.users.models import User +from reddit.posts.models import Post + + +async def create_post( + session: AsyncSession, + title: str, + owner_id: int, + subreddit_id: int, + link: Optional[str] = None, + text: Optional[str] = None, +) -> Post: + """ + Creates a new post instance. + """ + post = Post( + title=title, text=text, link=link, owner_id=owner_id, subreddit_id=subreddit_id + ) + # TODO: validate input data here. + session.add(instance=post) + await session.commit() + await session.refresh(instance=post) + return post + + +async def vote_post(session: AsyncSession, post: Post, user: User) -> Post: + """ + Creates a vote on the given post. + """ + + +async def update_post(session: AsyncSession, post: Post) -> Post: + """ + Updates the given post instance. + """ + + +async def delete_post(session: AsyncSession, post: Post, user: User) -> Post: + """ + Deletes the given post instance. + """ diff --git a/reddit-clone/reddit/posts/types.py b/reddit-clone/reddit/posts/types.py index 67a58baa..83ca086e 100644 --- a/reddit-clone/reddit/posts/types.py +++ b/reddit-clone/reddit/posts/types.py @@ -35,7 +35,7 @@ class PostType(NodeType): """ ) - user_id: int = strawberry.field( + owner_id: int = strawberry.field( description=""" The owner ID of the post. """ diff --git a/reddit-clone/reddit/subreddits/models.py b/reddit-clone/reddit/subreddits/models.py index cba9449a..0546f25b 100644 --- a/reddit-clone/reddit/subreddits/models.py +++ b/reddit-clone/reddit/subreddits/models.py @@ -1,6 +1,6 @@ from typing import Optional, List -from sqlalchemy import Column, String, Integer, SmallInteger, ForeignKey +from sqlalchemy import Column, ForeignKey, Integer, String from sqlalchemy.orm import relationship from ..database import Base @@ -20,9 +20,7 @@ class Subreddit(Base): description: Optional[str] = Column(String(255), default=None) - admin_id: int = Column(Integer, ForeignKey("users.id")) - - status: int = Column(SmallInteger) + owner_id: int = Column(Integer, ForeignKey("users.id")) icon: Optional[str] = Column(String(255), default=None) diff --git a/reddit-clone/reddit/subreddits/services.py b/reddit-clone/reddit/subreddits/services.py new file mode 100644 index 00000000..0a9d5a8f --- /dev/null +++ b/reddit-clone/reddit/subreddits/services.py @@ -0,0 +1,34 @@ +from typing import Optional + +from sqlalchemy.ext.asyncio import AsyncSession + +from reddit.users.models import User +from reddit.subreddits.models import Subreddit + + +async def create_subreddit( + session: AsyncSession, owner_id: int, name: str, description: Optional[str] = None +) -> Subreddit: + """ + Creates a new subreddit instance. + """ + subreddit = Subreddit(name=name, description=description, owner_id=owner_id) + # TODO: validate input data here. + session.add(instance=subreddit) + await session.commit() + await session.refresh(instance=subreddit) + return subreddit + + +async def update_subreddit(session: AsyncSession, subreddit: Subreddit) -> Subreddit: + """ + Updates the given subreddit instance. + """ + + +async def delete_subreddit( + session: AsyncSession, subreddit: Subreddit, user: User +) -> Subreddit: + """ + Deletes the given subreddit instance. + """ diff --git a/reddit-clone/reddit/subreddits/types.py b/reddit-clone/reddit/subreddits/types.py index 43fb65e1..aaf45159 100644 --- a/reddit-clone/reddit/subreddits/types.py +++ b/reddit-clone/reddit/subreddits/types.py @@ -23,7 +23,7 @@ class SubredditType(NodeType): """ ) - admin_id: int = strawberry.field( + owner_id: int = strawberry.field( description=""" The owner ID of the Subreddit. """ diff --git a/reddit-clone/reddit/users/services.py b/reddit-clone/reddit/users/services.py index 121eea4a..b4434d36 100644 --- a/reddit-clone/reddit/users/services.py +++ b/reddit-clone/reddit/users/services.py @@ -50,13 +50,13 @@ async def create_user( return user -async def update_user(session: AsyncSession, user: User): +async def update_user(session: AsyncSession, user: User) -> User: """ Updates the given user instance. """ -async def remove_avatar(session: AsyncSession, user: User) -> User: +async def remove_user_avatar(session: AsyncSession, user: User) -> User: """ Removes the avatar for the given user instance. """ From d6c37d2646962c782e15e7e1b54cf895a1fbf88e Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Thu, 14 Oct 2021 16:05:28 +0530 Subject: [PATCH 106/150] recreate migrations --- ...cd7_initial.py => fcae25f2abf6_initial.py} | 55 ++++++++++--------- 1 file changed, 30 insertions(+), 25 deletions(-) rename reddit-clone/migrations/versions/{6fd9b8fa9cd7_initial.py => fcae25f2abf6_initial.py} (82%) diff --git a/reddit-clone/migrations/versions/6fd9b8fa9cd7_initial.py b/reddit-clone/migrations/versions/fcae25f2abf6_initial.py similarity index 82% rename from reddit-clone/migrations/versions/6fd9b8fa9cd7_initial.py rename to reddit-clone/migrations/versions/fcae25f2abf6_initial.py index 6a2baa12..507d8d89 100644 --- a/reddit-clone/migrations/versions/6fd9b8fa9cd7_initial.py +++ b/reddit-clone/migrations/versions/fcae25f2abf6_initial.py @@ -1,8 +1,8 @@ """initial -Revision ID: 6fd9b8fa9cd7 +Revision ID: fcae25f2abf6 Revises: -Create Date: 2021-09-30 11:01:49.083637 +Create Date: 2021-10-14 16:05:02.497467 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = "6fd9b8fa9cd7" +revision = "fcae25f2abf6" down_revision = None branch_labels = None depends_on = None @@ -24,33 +24,21 @@ def upgrade(): sa.Column("username", sa.String(length=32), nullable=False), sa.Column("email", sa.String(length=255), nullable=False), sa.Column("password", sa.String(length=255), nullable=False), - sa.Column("avatar", sa.String(length=255), nullable=True), + sa.Column("avatar", sa.String(length=255), nullable=False), + sa.Column("is_active", sa.Boolean(), nullable=False), sa.PrimaryKeyConstraint("id"), sa.UniqueConstraint("email"), sa.UniqueConstraint("username"), ) - op.create_table( - "comments", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("content", sa.Text(), nullable=False), - sa.Column("votes", sa.Integer(), nullable=True), - sa.Column("user_id", sa.Integer(), nullable=True), - sa.ForeignKeyConstraint( - ["user_id"], - ["users.id"], - ), - sa.PrimaryKeyConstraint("id"), - ) op.create_table( "subreddits", sa.Column("id", sa.Integer(), nullable=False), sa.Column("name", sa.String(length=75), nullable=False), sa.Column("description", sa.String(length=255), nullable=True), - sa.Column("admin_id", sa.Integer(), nullable=True), - sa.Column("status", sa.SmallInteger(), nullable=True), + sa.Column("owner_id", sa.Integer(), nullable=True), sa.Column("icon", sa.String(length=255), nullable=True), sa.ForeignKeyConstraint( - ["admin_id"], + ["owner_id"], ["users.id"], ), sa.PrimaryKeyConstraint("id"), @@ -63,16 +51,16 @@ def upgrade(): sa.Column("text", sa.String(length=1024), nullable=True), sa.Column("link", sa.String(length=255), nullable=True), sa.Column("thumbnail", sa.String(length=255), nullable=True), - sa.Column("user_id", sa.Integer(), nullable=True), + sa.Column("owner_id", sa.Integer(), nullable=True), sa.Column("subreddit_id", sa.Integer(), nullable=True), sa.Column("votes", sa.Integer(), nullable=True), sa.ForeignKeyConstraint( - ["subreddit_id"], - ["subreddits.id"], + ["owner_id"], + ["users.id"], ), sa.ForeignKeyConstraint( - ["user_id"], - ["users.id"], + ["subreddit_id"], + ["subreddits.id"], ), sa.PrimaryKeyConstraint("id"), sa.UniqueConstraint("link"), @@ -91,14 +79,31 @@ def upgrade(): ), sa.PrimaryKeyConstraint("user_id", "subreddit_id"), ) + op.create_table( + "comments", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("content", sa.Text(), nullable=False), + sa.Column("votes", sa.Integer(), nullable=True), + sa.Column("owner_id", sa.Integer(), nullable=True), + sa.Column("post_id", sa.Integer(), nullable=True), + sa.ForeignKeyConstraint( + ["owner_id"], + ["users.id"], + ), + sa.ForeignKeyConstraint( + ["post_id"], + ["posts.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("comments") op.drop_table("subreddit_users") op.drop_table("posts") op.drop_table("subreddits") - op.drop_table("comments") op.drop_table("users") # ### end Alembic commands ### From cc26b5571b4d820c624602267b0accda122b86c6 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Thu, 14 Oct 2021 16:06:41 +0530 Subject: [PATCH 107/150] review migrations --- reddit-clone/migrations/versions/fcae25f2abf6_initial.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/reddit-clone/migrations/versions/fcae25f2abf6_initial.py b/reddit-clone/migrations/versions/fcae25f2abf6_initial.py index 507d8d89..3fc823e6 100644 --- a/reddit-clone/migrations/versions/fcae25f2abf6_initial.py +++ b/reddit-clone/migrations/versions/fcae25f2abf6_initial.py @@ -17,7 +17,6 @@ def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### op.create_table( "users", sa.Column("id", sa.Integer(), nullable=False), @@ -96,14 +95,11 @@ def upgrade(): ), sa.PrimaryKeyConstraint("id"), ) - # ### end Alembic commands ### def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### op.drop_table("comments") op.drop_table("subreddit_users") op.drop_table("posts") op.drop_table("subreddits") op.drop_table("users") - # ### end Alembic commands ### From aea21ef0ba867690a2c4fe25adb1d8876db57d06 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+codebyaryan@users.noreply.github.com> Date: Thu, 14 Oct 2021 17:06:37 +0530 Subject: [PATCH 108/150] Update README.md --- reddit-clone/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/reddit-clone/README.md b/reddit-clone/README.md index 35168a8c..eb95b1f9 100644 --- a/reddit-clone/README.md +++ b/reddit-clone/README.md @@ -13,6 +13,7 @@ most of the features that Strawberry gives us. - [Alembic](https://github.com/sqlalchemy/alembic) migrations - PostgreSQL database - [Schematics](https://github.com/schematics/schematics) data validation +- [Celery](https://github.com/celery/celery) task management ## Features at a glance From 1f0a1ce9bcb68826de140376d96efb142e303216 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Thu, 14 Oct 2021 20:02:34 +0530 Subject: [PATCH 109/150] add celery --- reddit-clone/.env.example | 1 + reddit-clone/README.md | 3 +- reddit-clone/poetry.lock | 243 +++++++++++++++++++++++++--- reddit-clone/pyproject.toml | 2 +- reddit-clone/reddit/celery.py | 12 ++ reddit-clone/reddit/settings.py | 6 + reddit-clone/reddit/utils/emails.py | 25 ++- 7 files changed, 253 insertions(+), 39 deletions(-) create mode 100644 reddit-clone/reddit/celery.py diff --git a/reddit-clone/.env.example b/reddit-clone/.env.example index c120265a..2b6d7cef 100644 --- a/reddit-clone/.env.example +++ b/reddit-clone/.env.example @@ -1,6 +1,7 @@ DATABASE_URI=postgresql+asyncpg://localhost/db_name?user=postgres&password=postgres MAIL_HOST=127.0.0.1 MAIL_PORT=25 +CELERY_BROKER=redis://localhost:6379 DEBUG= MAIL_USERNAME= MAIL_PASSWORD= diff --git a/reddit-clone/README.md b/reddit-clone/README.md index eb95b1f9..8a472ae9 100644 --- a/reddit-clone/README.md +++ b/reddit-clone/README.md @@ -9,9 +9,10 @@ most of the features that Strawberry gives us. - [Strawberry GraphQL](https://github.com/strawberry-graphql/strawberry) - [Starlette](https://github.com/encode/starlette) web framework +- [Uvicorn](https://github.com/encode/uvicorn) ASGI server - [SQLAlchemy](https://github.com/sqlalchemy/sqlalchemy) core/ mapper (asyncio) - [Alembic](https://github.com/sqlalchemy/alembic) migrations -- PostgreSQL database +- [PostgreSQL](https://github.com/postgres/postgres) database instance - [Schematics](https://github.com/schematics/schematics) data validation - [Celery](https://github.com/celery/celery) task management diff --git a/reddit-clone/poetry.lock b/reddit-clone/poetry.lock index 46072db4..dd0e275a 100644 --- a/reddit-clone/poetry.lock +++ b/reddit-clone/poetry.lock @@ -6,18 +6,6 @@ category = "main" optional = false python-versions = ">=3.6,<4.0" -[[package]] -name = "aiosmtplib" -version = "1.1.6" -description = "asyncio SMTP client" -category = "main" -optional = false -python-versions = ">=3.5.2,<4.0.0" - -[package.extras] -uvloop = ["uvloop (>=0.13,<0.15)"] -docs = ["sphinx (>=2,<4)", "sphinx_autodoc_typehints (>=1.7.0,<2.0.0)"] - [[package]] name = "alembic" version = "1.7.4" @@ -35,6 +23,17 @@ SQLAlchemy = ">=1.3.0" [package.extras] tz = ["python-dateutil"] +[[package]] +name = "amqp" +version = "5.0.6" +description = "Low-level AMQP client for Python (fork of amqplib)." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +vine = "5.0.0" + [[package]] name = "anyio" version = "3.3.3" @@ -113,6 +112,14 @@ docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] +[[package]] +name = "billiard" +version = "3.6.4.0" +description = "Python multiprocessing fork with improvements and bugfixes" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "black" version = "21.9b0" @@ -149,6 +156,57 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "celery" +version = "5.1.2" +description = "Distributed Task Queue." +category = "main" +optional = false +python-versions = ">=3.6," + +[package.dependencies] +billiard = ">=3.6.4.0,<4.0" +click = ">=7.0,<8.0" +click-didyoumean = ">=0.0.3" +click-plugins = ">=1.1.1" +click-repl = ">=0.1.6" +kombu = ">=5.1.0,<6.0" +pytz = ">0.0-dev" +vine = ">=5.0.0,<6.0" + +[package.extras] +arangodb = ["pyArango (>=1.3.2)"] +auth = ["cryptography"] +azureblockblob = ["azure-storage-blob (==12.6.0)"] +brotli = ["brotli (>=1.0.0)", "brotlipy (>=0.7.0)"] +cassandra = ["cassandra-driver (<3.21.0)"] +consul = ["python-consul2"] +cosmosdbsql = ["pydocumentdb (==2.3.2)"] +couchbase = ["couchbase (>=3.0.0)"] +couchdb = ["pycouchdb"] +django = ["Django (>=1.11)"] +dynamodb = ["boto3 (>=1.9.178)"] +elasticsearch = ["elasticsearch"] +eventlet = ["eventlet (>=0.26.1)"] +gevent = ["gevent (>=1.0.0)"] +librabbitmq = ["librabbitmq (>=1.5.0)"] +memcache = ["pylibmc"] +mongodb = ["pymongo[srv] (>=3.3.0)"] +msgpack = ["msgpack"] +pymemcache = ["python-memcached"] +pyro = ["pyro4"] +pytest = ["pytest-celery"] +redis = ["redis (>=3.2.0)"] +s3 = ["boto3 (>=1.9.125)"] +slmq = ["softlayer-messaging (>=1.0.3)"] +solar = ["ephem"] +sqlalchemy = ["sqlalchemy"] +sqs = ["boto3 (>=1.9.125)", "pycurl (==7.43.0.5)"] +tblib = ["tblib (>=1.3.0)", "tblib (>=1.5.0)"] +yaml = ["PyYAML (>=3.10)"] +zookeeper = ["kazoo (>=1.3.1)"] +zstd = ["zstandard"] + [[package]] name = "cffi" version = "1.14.6" @@ -162,15 +220,49 @@ pycparser = "*" [[package]] name = "click" -version = "8.0.3" +version = "7.1.2" description = "Composable command line interface toolkit" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "click-didyoumean" +version = "0.3.0" +description = "Enables git-like *did-you-mean* feature in click" +category = "main" +optional = false +python-versions = ">=3.6.2,<4.0.0" [package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +click = ">=7" + +[[package]] +name = "click-plugins" +version = "1.1.1" +description = "An extension module for click to enable registering CLI commands via setuptools entry-points." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +click = ">=4.0" + +[package.extras] +dev = ["pytest (>=3.6)", "pytest-cov", "wheel", "coveralls"] + +[[package]] +name = "click-repl" +version = "0.2.0" +description = "REPL plugin for Click" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +click = "*" +prompt-toolkit = "*" +six = "*" [[package]] name = "colorama" @@ -326,6 +418,36 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "kombu" +version = "5.1.0" +description = "Messaging library for Python." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +amqp = ">=5.0.6,<6.0.0" +cached-property = {version = "*", markers = "python_version < \"3.8\""} +importlib-metadata = {version = ">=0.18", markers = "python_version < \"3.8\""} +vine = "*" + +[package.extras] +azureservicebus = ["azure-servicebus (>=7.0.0)"] +azurestoragequeues = ["azure-storage-queue"] +consul = ["python-consul (>=0.6.0)"] +librabbitmq = ["librabbitmq (>=1.5.2)"] +mongodb = ["pymongo (>=3.3.0)"] +msgpack = ["msgpack"] +pyro = ["pyro4"] +qpid = ["qpid-python (>=0.26)", "qpid-tools (>=0.26)"] +redis = ["redis (>=3.3.11)"] +slmq = ["softlayer-messaging (>=1.0.3)"] +sqlalchemy = ["sqlalchemy"] +sqs = ["boto3 (>=1.4.4)", "pycurl (==7.43.0.2)", "urllib3 (<1.26)"] +yaml = ["PyYAML (>=3.10)"] +zookeeper = ["kazoo (>=1.3.1)"] + [[package]] name = "mako" version = "1.1.5" @@ -420,6 +542,17 @@ python-versions = ">=3.6" docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] +[[package]] +name = "prompt-toolkit" +version = "3.0.20" +description = "Library for building powerful interactive command lines in Python" +category = "main" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +wcwidth = "*" + [[package]] name = "pycodestyle" version = "2.7.0" @@ -485,6 +618,14 @@ python-versions = "*" [package.dependencies] six = ">=1.4.0" +[[package]] +name = "pytz" +version = "2021.3" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "pyyaml" version = "5.4.1" @@ -695,6 +836,14 @@ dev = ["Cython (>=0.29.24,<0.30.0)", "pytest (>=3.6.0)", "Sphinx (>=4.1.2,<4.2.0 docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)"] test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"] +[[package]] +name = "vine" +version = "5.0.0" +description = "Promises, promises, promises." +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "watchgod" version = "0.7" @@ -703,6 +852,14 @@ category = "main" optional = false python-versions = ">=3.5" +[[package]] +name = "wcwidth" +version = "0.2.5" +description = "Measures the displayed width of unicode strings in a terminal" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "websockets" version = "10.0" @@ -726,21 +883,21 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "8d178b08b5558fedfba56530d5cedcd21cfc44a333d374c37487d542fa7be916" +content-hash = "8753228c24cfc820ae979004bf108c0f179adbba378a2612180dc1b38e120a4d" [metadata.files] aiofiles = [ {file = "aiofiles-0.7.0-py3-none-any.whl", hash = "sha256:c67a6823b5f23fcab0a2595a289cec7d8c863ffcb4322fb8cd6b90400aedfdbc"}, {file = "aiofiles-0.7.0.tar.gz", hash = "sha256:a1c4fc9b2ff81568c83e21392a82f344ea9d23da906e4f6a52662764545e19d4"}, ] -aiosmtplib = [ - {file = "aiosmtplib-1.1.6-py3-none-any.whl", hash = "sha256:84174765778b2c5e0e207fbce0a769202fcf0c3de81faa87cc03551a6333bfa9"}, - {file = "aiosmtplib-1.1.6.tar.gz", hash = "sha256:d138fe6ffecbc9e6320269690b9ac0b75e540ef96e8f5c77d4a306760014dce2"}, -] alembic = [ {file = "alembic-1.7.4-py3-none-any.whl", hash = "sha256:e3cab9e59778b3b6726bb2da9ced451c6622d558199fd3ef914f3b1e8f4ef704"}, {file = "alembic-1.7.4.tar.gz", hash = "sha256:9d33f3ff1488c4bfab1e1a6dfebbf085e8a8e1a3e047a43ad29ad1f67f012a1d"}, ] +amqp = [ + {file = "amqp-5.0.6-py3-none-any.whl", hash = "sha256:493a2ac6788ce270a2f6a765b017299f60c1998f5a8617908ee9be082f7300fb"}, + {file = "amqp-5.0.6.tar.gz", hash = "sha256:03e16e94f2b34c31f8bf1206d8ddd3ccaa4c315f7f6a1879b7b1210d229568c2"}, +] anyio = [ {file = "anyio-3.3.3-py3-none-any.whl", hash = "sha256:56ceaeed2877723578b1341f4f68c29081db189cfb40a97d1922b9513f6d7db6"}, {file = "anyio-3.3.3.tar.gz", hash = "sha256:8eccec339cb4a856c94a75d50fc1d451faf32a05ef406be462e2efc59c9838b0"}, @@ -781,6 +938,10 @@ attrs = [ {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, ] +billiard = [ + {file = "billiard-3.6.4.0-py3-none-any.whl", hash = "sha256:87103ea78fa6ab4d5c751c4909bcff74617d985de7fa8b672cf8618afd5a875b"}, + {file = "billiard-3.6.4.0.tar.gz", hash = "sha256:299de5a8da28a783d51b197d496bef4f1595dd023a93a4f59dde1886ae905547"}, +] black = [ {file = "black-21.9b0-py3-none-any.whl", hash = "sha256:380f1b5da05e5a1429225676655dddb96f5ae8c75bdf91e53d798871b902a115"}, {file = "black-21.9b0.tar.gz", hash = "sha256:7de4cfc7eb6b710de325712d40125689101d21d25283eed7e9998722cf10eb91"}, @@ -789,6 +950,10 @@ cached-property = [ {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, ] +celery = [ + {file = "celery-5.1.2-py3-none-any.whl", hash = "sha256:9dab2170b4038f7bf10ef2861dbf486ddf1d20592290a1040f7b7a1259705d42"}, + {file = "celery-5.1.2.tar.gz", hash = "sha256:8d9a3de9162965e97f8e8cc584c67aad83b3f7a267584fa47701ed11c3e0d4b0"}, +] cffi = [ {file = "cffi-1.14.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c"}, {file = "cffi-1.14.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99"}, @@ -837,8 +1002,20 @@ cffi = [ {file = "cffi-1.14.6.tar.gz", hash = "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd"}, ] click = [ - {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, - {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, + {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, + {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, +] +click-didyoumean = [ + {file = "click-didyoumean-0.3.0.tar.gz", hash = "sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035"}, + {file = "click_didyoumean-0.3.0-py3-none-any.whl", hash = "sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667"}, +] +click-plugins = [ + {file = "click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"}, + {file = "click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"}, +] +click-repl = [ + {file = "click-repl-0.2.0.tar.gz", hash = "sha256:cd12f68d745bf6151210790540b4cb064c7b13e571bc64b6957d98d120dacfd8"}, + {file = "click_repl-0.2.0-py3-none-any.whl", hash = "sha256:94b3fbbc9406a236f176e0506524b2937e4b23b6f4c0c0b2a0a83f8a64e9194b"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, @@ -953,6 +1130,10 @@ jinja2 = [ {file = "Jinja2-3.0.2-py3-none-any.whl", hash = "sha256:8569982d3f0889eed11dd620c706d39b60c36d6d25843961f33f77fb6bc6b20c"}, {file = "Jinja2-3.0.2.tar.gz", hash = "sha256:827a0e32839ab1600d4eb1c4c33ec5a8edfbc5cb42dafa13b81f182f97784b45"}, ] +kombu = [ + {file = "kombu-5.1.0-py3-none-any.whl", hash = "sha256:e2dedd8a86c9077c350555153825a31e456a0dc20c15d5751f00137ec9c75f0a"}, + {file = "kombu-5.1.0.tar.gz", hash = "sha256:01481d99f4606f6939cdc9b637264ed353ee9e3e4f62cfb582324142c41a572d"}, +] mako = [ {file = "Mako-1.1.5-py2.py3-none-any.whl", hash = "sha256:6804ee66a7f6a6416910463b00d76a7b25194cd27f1918500c5bd7be2a088a23"}, {file = "Mako-1.1.5.tar.gz", hash = "sha256:169fa52af22a91900d852e937400e79f535496191c63712e3b9fda5a9bed6fc3"}, @@ -1038,6 +1219,10 @@ platformdirs = [ {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"}, {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"}, ] +prompt-toolkit = [ + {file = "prompt_toolkit-3.0.20-py3-none-any.whl", hash = "sha256:6076e46efae19b1e0ca1ec003ed37a933dc94b4d20f486235d436e64771dcd5c"}, + {file = "prompt_toolkit-3.0.20.tar.gz", hash = "sha256:eb71d5a6b72ce6db177af4a7d4d7085b99756bf656d98ffcc4fecd36850eea6c"}, +] pycodestyle = [ {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, @@ -1065,6 +1250,10 @@ python-dotenv = [ python-multipart = [ {file = "python-multipart-0.0.5.tar.gz", hash = "sha256:f7bb5f611fc600d15fa47b3974c8aa16e93724513b49b5f95c81e6624c83fa43"}, ] +pytz = [ + {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, + {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, +] pyyaml = [ {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, @@ -1266,10 +1455,18 @@ uvloop = [ {file = "uvloop-0.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e5f2e2ff51aefe6c19ee98af12b4ae61f5be456cd24396953244a30880ad861"}, {file = "uvloop-0.16.0.tar.gz", hash = "sha256:f74bc20c7b67d1c27c72601c78cf95be99d5c2cdd4514502b4f3eb0933ff1228"}, ] +vine = [ + {file = "vine-5.0.0-py2.py3-none-any.whl", hash = "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30"}, + {file = "vine-5.0.0.tar.gz", hash = "sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"}, +] watchgod = [ {file = "watchgod-0.7-py3-none-any.whl", hash = "sha256:d6c1ea21df37847ac0537ca0d6c2f4cdf513562e95f77bb93abbcf05573407b7"}, {file = "watchgod-0.7.tar.gz", hash = "sha256:48140d62b0ebe9dd9cf8381337f06351e1f2e70b2203fa9c6eff4e572ca84f29"}, ] +wcwidth = [ + {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, + {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, +] websockets = [ {file = "websockets-10.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cd8c6f2ec24aedace251017bc7a414525171d4e6578f914acab9349362def4da"}, {file = "websockets-10.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:1f6b814cff6aadc4288297cb3a248614829c6e4ff5556593c44a115e9dd49939"}, diff --git a/reddit-clone/pyproject.toml b/reddit-clone/pyproject.toml index a08c906c..2f6df1d8 100644 --- a/reddit-clone/pyproject.toml +++ b/reddit-clone/pyproject.toml @@ -13,11 +13,11 @@ passlib = {extras = ["argon2"], version = "^1.7.4"} alembic = "^1.7.1" asyncpg = "^0.24" graphql-relay = "^3.1.0" -aiosmtplib = "^1.1.6" schematics = "^2.1.1" jinja2 = "^3.0.2" aiofiles = "^0.7.0" starlette = "^0.16.0" +celery = "^5.1.2" [tool.poetry.dev-dependencies] diff --git a/reddit-clone/reddit/celery.py b/reddit-clone/reddit/celery.py new file mode 100644 index 00000000..0e6d43b8 --- /dev/null +++ b/reddit-clone/reddit/celery.py @@ -0,0 +1,12 @@ +from celery import Celery + +from reddit import settings + +__all__ = ("celery",) + +celery = Celery( + main=__name__, backend=settings.CELERY_BACKEND, broker=settings.CELERY_BROKER +) + +if __name__ == "__main__": + celery.start() diff --git a/reddit-clone/reddit/settings.py b/reddit-clone/reddit/settings.py index 9d57003c..a2db8c97 100644 --- a/reddit-clone/reddit/settings.py +++ b/reddit-clone/reddit/settings.py @@ -25,3 +25,9 @@ # mail client sender address. MAIL_SENDER: Optional[str] = config("MAIL_SENDER", cast=str, default=None) + +# celery broker URL. +CELERY_BROKER: str = config("CELERY_BROKER_URL", cast=str) + +# celery result backend URL. +CELERY_BACKEND: str = config("CELERY_BACKEND", cast=str) diff --git a/reddit-clone/reddit/utils/emails.py b/reddit-clone/reddit/utils/emails.py index 7a222fab..0d628729 100644 --- a/reddit-clone/reddit/utils/emails.py +++ b/reddit-clone/reddit/utils/emails.py @@ -1,23 +1,23 @@ from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText +from smtplib import SMTP from typing import Optional -from aiosmtplib import send - from reddit import settings +from reddit.celery import celery __all__ = ("send_mail",) -async def send_mail( +@celery.task(name="send_email") +def send_mail( recipient: str, subject: str, content: str, html_content: Optional[str] = None ) -> None: """ - Sends a multipart email to the provided - recipient asynchronously. + Sends a multipart email to the provided recipient. """ message = MIMEMultipart() - message["From"] = None + message["From"] = settings.MAIL_SENDER message["To"] = recipient message["Subject"] = subject @@ -28,11 +28,8 @@ async def send_mail( html_text = MIMEText(html_content, "html") message.attach(html_text) - await send( - message=message, - sender=settings.MAIL_SENDER, - username=settings.MAIL_USERNAME, - password=settings.MAIL_PASSWORD, - hostname=settings.MAIL_HOST, - port=settings.MAIL_PORT, - ) + server = SMTP(host=settings.MAIL_HOST, port=settings.MAIL_PORT) + server.login(user=settings.MAIL_USERNAME, password=settings.MAIL_PASSWORD) + server.starttls() + server.send_message(message) + server.quit() From 4fbe70a50b6b26cadd6a98e03c6fceca07941a91 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Thu, 14 Oct 2021 20:12:55 +0530 Subject: [PATCH 110/150] update celery worker --- reddit-clone/reddit/celery.py | 12 ------------ reddit-clone/reddit/tasks.py | 12 ++++++++++++ reddit-clone/reddit/utils/emails.py | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) delete mode 100644 reddit-clone/reddit/celery.py create mode 100644 reddit-clone/reddit/tasks.py diff --git a/reddit-clone/reddit/celery.py b/reddit-clone/reddit/celery.py deleted file mode 100644 index 0e6d43b8..00000000 --- a/reddit-clone/reddit/celery.py +++ /dev/null @@ -1,12 +0,0 @@ -from celery import Celery - -from reddit import settings - -__all__ = ("celery",) - -celery = Celery( - main=__name__, backend=settings.CELERY_BACKEND, broker=settings.CELERY_BROKER -) - -if __name__ == "__main__": - celery.start() diff --git a/reddit-clone/reddit/tasks.py b/reddit-clone/reddit/tasks.py new file mode 100644 index 00000000..d2c470fd --- /dev/null +++ b/reddit-clone/reddit/tasks.py @@ -0,0 +1,12 @@ +from celery import Celery + +from reddit import settings + +__all__ = ("celery",) + +celery = Celery(__name__) +celery.conf.result_backend = settings.CELERY_BACKEND +celery.conf.broker_url = settings.CELERY_BROKER + +if __name__ == "__main__": + celery.start() diff --git a/reddit-clone/reddit/utils/emails.py b/reddit-clone/reddit/utils/emails.py index 0d628729..31d490ad 100644 --- a/reddit-clone/reddit/utils/emails.py +++ b/reddit-clone/reddit/utils/emails.py @@ -4,7 +4,7 @@ from typing import Optional from reddit import settings -from reddit.celery import celery +from reddit.tasks import celery __all__ = ("send_mail",) From 44798ec9cd4c4b260768cb76e14ff53b542f0d7c Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Thu, 14 Oct 2021 20:14:19 +0530 Subject: [PATCH 111/150] update README.md --- reddit-clone/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reddit-clone/README.md b/reddit-clone/README.md index 8a472ae9..ec827f76 100644 --- a/reddit-clone/README.md +++ b/reddit-clone/README.md @@ -12,7 +12,7 @@ most of the features that Strawberry gives us. - [Uvicorn](https://github.com/encode/uvicorn) ASGI server - [SQLAlchemy](https://github.com/sqlalchemy/sqlalchemy) core/ mapper (asyncio) - [Alembic](https://github.com/sqlalchemy/alembic) migrations -- [PostgreSQL](https://github.com/postgres/postgres) database instance +- [PostgreSQL](https://github.com/postgres/postgres) database server - [Schematics](https://github.com/schematics/schematics) data validation - [Celery](https://github.com/celery/celery) task management From c3ac1a7a2af01e0af91472a09240f51138cb39e2 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Thu, 14 Oct 2021 20:27:38 +0530 Subject: [PATCH 112/150] update README.md --- reddit-clone/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reddit-clone/README.md b/reddit-clone/README.md index ec827f76..59c0a2c6 100644 --- a/reddit-clone/README.md +++ b/reddit-clone/README.md @@ -14,7 +14,7 @@ most of the features that Strawberry gives us. - [Alembic](https://github.com/sqlalchemy/alembic) migrations - [PostgreSQL](https://github.com/postgres/postgres) database server - [Schematics](https://github.com/schematics/schematics) data validation -- [Celery](https://github.com/celery/celery) task management +- [Celery](https://github.com/celery/celery) tasks ([Redis](https://github.com/redis/redis) store) ## Features at a glance From 93f4d655495fd4ddb15f8c1525357f4ffa367128 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Thu, 14 Oct 2021 20:47:40 +0530 Subject: [PATCH 113/150] add dockerfile --- reddit-clone/Dockerfile | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 reddit-clone/Dockerfile diff --git a/reddit-clone/Dockerfile b/reddit-clone/Dockerfile new file mode 100644 index 00000000..0237e693 --- /dev/null +++ b/reddit-clone/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.7 + +RUN mkdir /reddit + +COPY /reddit /reddit +COPY /pyproject.toml /reddit + +WORKDIR /reddit + +RUN pip install poetry +RUN poetry install + +ENTRYPOINT [ "poetry", "run", "uvicorn", "app" ] From 1b8bcd19366363568479664e1858d7816b6693a0 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Thu, 14 Oct 2021 21:24:37 +0530 Subject: [PATCH 114/150] prepare to use docker compose --- reddit-clone/Dockerfile | 10 +++++----- reddit-clone/reddit/settings.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/reddit-clone/Dockerfile b/reddit-clone/Dockerfile index 0237e693..ede730f1 100644 --- a/reddit-clone/Dockerfile +++ b/reddit-clone/Dockerfile @@ -1,13 +1,13 @@ FROM python:3.7 -RUN mkdir /reddit +RUN mkdir /app -COPY /reddit /reddit -COPY /pyproject.toml /reddit +COPY /reddit /app +COPY /pyproject.toml /app -WORKDIR /reddit +WORKDIR /app RUN pip install poetry RUN poetry install -ENTRYPOINT [ "poetry", "run", "uvicorn", "app" ] +ENTRYPOINT [ "poetry", "run", "uvicorn", "reddit:app" ] diff --git a/reddit-clone/reddit/settings.py b/reddit-clone/reddit/settings.py index a2db8c97..4b12d8be 100644 --- a/reddit-clone/reddit/settings.py +++ b/reddit-clone/reddit/settings.py @@ -27,7 +27,7 @@ MAIL_SENDER: Optional[str] = config("MAIL_SENDER", cast=str, default=None) # celery broker URL. -CELERY_BROKER: str = config("CELERY_BROKER_URL", cast=str) +CELERY_BROKER: str = config("CELERY_BROKER", cast=str) # celery result backend URL. CELERY_BACKEND: str = config("CELERY_BACKEND", cast=str) From 645026f5aabe89e6413764fe3ff420f4c6e23537 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Fri, 15 Oct 2021 07:34:00 +0530 Subject: [PATCH 115/150] add docker-compose.yml --- reddit-clone/.env.example | 1 + reddit-clone/Dockerfile | 2 +- reddit-clone/README.md | 2 +- reddit-clone/docker-compose.yml | 31 +++++++++++++++++++++++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 reddit-clone/docker-compose.yml diff --git a/reddit-clone/.env.example b/reddit-clone/.env.example index 2b6d7cef..e4299e35 100644 --- a/reddit-clone/.env.example +++ b/reddit-clone/.env.example @@ -2,6 +2,7 @@ DATABASE_URI=postgresql+asyncpg://localhost/db_name?user=postgres&password=postg MAIL_HOST=127.0.0.1 MAIL_PORT=25 CELERY_BROKER=redis://localhost:6379 +CELERY_BACKEND= DEBUG= MAIL_USERNAME= MAIL_PASSWORD= diff --git a/reddit-clone/Dockerfile b/reddit-clone/Dockerfile index ede730f1..ab53b70e 100644 --- a/reddit-clone/Dockerfile +++ b/reddit-clone/Dockerfile @@ -3,7 +3,7 @@ FROM python:3.7 RUN mkdir /app COPY /reddit /app -COPY /pyproject.toml /app +COPY /pyproject.toml /poetry.lock /app/ WORKDIR /app diff --git a/reddit-clone/README.md b/reddit-clone/README.md index 59c0a2c6..fdfb7843 100644 --- a/reddit-clone/README.md +++ b/reddit-clone/README.md @@ -14,7 +14,7 @@ most of the features that Strawberry gives us. - [Alembic](https://github.com/sqlalchemy/alembic) migrations - [PostgreSQL](https://github.com/postgres/postgres) database server - [Schematics](https://github.com/schematics/schematics) data validation -- [Celery](https://github.com/celery/celery) tasks ([Redis](https://github.com/redis/redis) store) +- [Celery](https://github.com/celery/celery) tasks ([Redis](https://github.com/redis/redis) store, [RabbitMQ](https://github.com/rabbitmq/rabbitmq-server) broker) ## Features at a glance diff --git a/reddit-clone/docker-compose.yml b/reddit-clone/docker-compose.yml new file mode 100644 index 00000000..d2492aa7 --- /dev/null +++ b/reddit-clone/docker-compose.yml @@ -0,0 +1,31 @@ +services: + database: + image: postgres:13-alpine + env_file: + - ".env" + + redis: + image: redis:6-alpine + env_file: + - ".env" + + rabbitmq: + image: rabbitmq:latest + env_file: + - ".env" + + app: + build: + context: . + env_file: + - ".env" + depends_on: + - database + + celery: + restart: always + env_file: + - ".env" + depends_on: + - redis + - rabbitmq From 1d2b56a336dd2fd8d8ec0a580cf49f2efb7f4faf Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Fri, 15 Oct 2021 07:50:25 +0530 Subject: [PATCH 116/150] update Dockerfile to python 3.9 --- reddit-clone/.env.example | 7 +++++++ reddit-clone/Dockerfile | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/reddit-clone/.env.example b/reddit-clone/.env.example index e4299e35..49adb150 100644 --- a/reddit-clone/.env.example +++ b/reddit-clone/.env.example @@ -1,3 +1,4 @@ +# app DATABASE_URI=postgresql+asyncpg://localhost/db_name?user=postgres&password=postgres MAIL_HOST=127.0.0.1 MAIL_PORT=25 @@ -7,3 +8,9 @@ DEBUG= MAIL_USERNAME= MAIL_PASSWORD= MAIL_SENDER= + +# database +POSTGRES_SERVER= +POSTGRES_USER= +POSTGRES_PASSWORD= +POSTGRES_DB= diff --git a/reddit-clone/Dockerfile b/reddit-clone/Dockerfile index ab53b70e..551e640a 100644 --- a/reddit-clone/Dockerfile +++ b/reddit-clone/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.7 +FROM python:3.9-slim-buster RUN mkdir /app From 7333d2768121deb432da011d4d8605ac2900ae4e Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Fri, 15 Oct 2021 08:10:58 +0530 Subject: [PATCH 117/150] update docker file --- reddit-clone/.env.example | 4 ++-- reddit-clone/Dockerfile | 2 +- reddit-clone/docker-compose.yml | 23 +++++++++++++++-------- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/reddit-clone/.env.example b/reddit-clone/.env.example index 49adb150..51fa3201 100644 --- a/reddit-clone/.env.example +++ b/reddit-clone/.env.example @@ -2,8 +2,8 @@ DATABASE_URI=postgresql+asyncpg://localhost/db_name?user=postgres&password=postgres MAIL_HOST=127.0.0.1 MAIL_PORT=25 -CELERY_BROKER=redis://localhost:6379 -CELERY_BACKEND= +CELERY_BROKER= +CELERY_BACKEND=redis://localhost:6379 DEBUG= MAIL_USERNAME= MAIL_PASSWORD= diff --git a/reddit-clone/Dockerfile b/reddit-clone/Dockerfile index 551e640a..304049b7 100644 --- a/reddit-clone/Dockerfile +++ b/reddit-clone/Dockerfile @@ -2,7 +2,7 @@ FROM python:3.9-slim-buster RUN mkdir /app -COPY /reddit /app +COPY /reddit /migrations /app/ COPY /pyproject.toml /poetry.lock /app/ WORKDIR /app diff --git a/reddit-clone/docker-compose.yml b/reddit-clone/docker-compose.yml index d2492aa7..5f733564 100644 --- a/reddit-clone/docker-compose.yml +++ b/reddit-clone/docker-compose.yml @@ -8,11 +8,15 @@ services: image: redis:6-alpine env_file: - ".env" + ports: + - "6379:6379" rabbitmq: - image: rabbitmq:latest + image: rabbitmq:3 env_file: - ".env" + ports: + - "5672:5672" app: build: @@ -22,10 +26,13 @@ services: depends_on: - database - celery: - restart: always - env_file: - - ".env" - depends_on: - - redis - - rabbitmq + # celery: + # build: + # context: . + # restart: always + # env_file: + # - ".env" + # depends_on: + # - redis + # - rabbitmq + # - app From 0c32ec89cc423318848493dcacaa582437d7936e Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Fri, 15 Oct 2021 09:13:13 +0530 Subject: [PATCH 118/150] update dockerfile --- reddit-clone/Dockerfile | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/reddit-clone/Dockerfile b/reddit-clone/Dockerfile index 304049b7..12731e2d 100644 --- a/reddit-clone/Dockerfile +++ b/reddit-clone/Dockerfile @@ -1,13 +1,12 @@ FROM python:3.9-slim-buster -RUN mkdir /app +WORKDIR /app/ -COPY /reddit /migrations /app/ -COPY /pyproject.toml /poetry.lock /app/ - -WORKDIR /app +COPY ./poetry.lock ./pyproject.toml /app/ RUN pip install poetry RUN poetry install +COPY ./reddit /app/reddit/ + ENTRYPOINT [ "poetry", "run", "uvicorn", "reddit:app" ] From c02786f3d7060f9b584036a2a70255e69d4963c4 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Fri, 15 Oct 2021 10:12:40 +0530 Subject: [PATCH 119/150] add .dockerignore --- reddit-clone/.dockerignore | 7 +++++++ reddit-clone/Dockerfile | 8 ++++---- 2 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 reddit-clone/.dockerignore diff --git a/reddit-clone/.dockerignore b/reddit-clone/.dockerignore new file mode 100644 index 00000000..9008115f --- /dev/null +++ b/reddit-clone/.dockerignore @@ -0,0 +1,7 @@ +.git +__pycache__ +*.pyc +*.pyo +*.pyd +.Python +env diff --git a/reddit-clone/Dockerfile b/reddit-clone/Dockerfile index 12731e2d..1ccfe6b3 100644 --- a/reddit-clone/Dockerfile +++ b/reddit-clone/Dockerfile @@ -1,12 +1,12 @@ -FROM python:3.9-slim-buster +FROM python:3.9 -WORKDIR /app/ +WORKDIR /app -COPY ./poetry.lock ./pyproject.toml /app/ +COPY ./pyproject.toml ./poetry.lock /app/ RUN pip install poetry RUN poetry install COPY ./reddit /app/reddit/ -ENTRYPOINT [ "poetry", "run", "uvicorn", "reddit:app" ] +CMD [ "poetry", "run", "uvicorn", "reddit:app" ] From 4fc9965d661370609de8bc475c638d4bd5a9babf Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Fri, 15 Oct 2021 10:19:36 +0530 Subject: [PATCH 120/150] create volume for postgres data --- reddit-clone/docker-compose.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/reddit-clone/docker-compose.yml b/reddit-clone/docker-compose.yml index 5f733564..c105ff0a 100644 --- a/reddit-clone/docker-compose.yml +++ b/reddit-clone/docker-compose.yml @@ -1,8 +1,11 @@ services: database: image: postgres:13-alpine + restart: unless-stopped env_file: - ".env" + volumes: + - postgres-data:/var/lib/postgresql/data redis: image: redis:6-alpine @@ -36,3 +39,6 @@ services: # - redis # - rabbitmq # - app + +volumes: + postgres-data: From fcfe7ca06ca5d13d0d044f39f0881f74782c030d Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Fri, 15 Oct 2021 10:46:52 +0530 Subject: [PATCH 121/150] make docker compose happy --- reddit-clone/Dockerfile | 8 +++++--- reddit-clone/docker-compose.yml | 3 +-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/reddit-clone/Dockerfile b/reddit-clone/Dockerfile index 1ccfe6b3..677beed7 100644 --- a/reddit-clone/Dockerfile +++ b/reddit-clone/Dockerfile @@ -2,11 +2,13 @@ FROM python:3.9 WORKDIR /app -COPY ./pyproject.toml ./poetry.lock /app/ - +# First install poetry RUN pip install poetry + +# Then install dependencies +COPY ./poetry.lock ./pyproject.toml ./ RUN poetry install -COPY ./reddit /app/reddit/ +COPY ./ ./ CMD [ "poetry", "run", "uvicorn", "reddit:app" ] diff --git a/reddit-clone/docker-compose.yml b/reddit-clone/docker-compose.yml index c105ff0a..3245b6d3 100644 --- a/reddit-clone/docker-compose.yml +++ b/reddit-clone/docker-compose.yml @@ -1,7 +1,6 @@ services: database: image: postgres:13-alpine - restart: unless-stopped env_file: - ".env" volumes: @@ -15,7 +14,7 @@ services: - "6379:6379" rabbitmq: - image: rabbitmq:3 + image: rabbitmq:3.8-alpine env_file: - ".env" ports: From 59ec70f55074cc67f70441c470903089730cd1ed Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Fri, 15 Oct 2021 10:48:01 +0530 Subject: [PATCH 122/150] update Dockerfile --- reddit-clone/Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/reddit-clone/Dockerfile b/reddit-clone/Dockerfile index 677beed7..39dad045 100644 --- a/reddit-clone/Dockerfile +++ b/reddit-clone/Dockerfile @@ -2,13 +2,14 @@ FROM python:3.9 WORKDIR /app -# First install poetry +# install poetry RUN pip install poetry -# Then install dependencies +# install dependencies COPY ./poetry.lock ./pyproject.toml ./ RUN poetry install +# copy project files COPY ./ ./ CMD [ "poetry", "run", "uvicorn", "reddit:app" ] From 77fc17415e2a0fd0cd0eda351d0e990a8c0bac3c Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Fri, 15 Oct 2021 10:52:51 +0530 Subject: [PATCH 123/150] simplify setup instructions --- reddit-clone/README.md | 33 ++++----------------------------- 1 file changed, 4 insertions(+), 29 deletions(-) diff --git a/reddit-clone/README.md b/reddit-clone/README.md index fdfb7843..df5ed4da 100644 --- a/reddit-clone/README.md +++ b/reddit-clone/README.md @@ -27,36 +27,11 @@ most of the features that Strawberry gives us. ## How to use -1. Install dependencies +You can use [Docker Compose](https://github.com/docker/compose) to run this example. Make sure you have it installed on your machine! -Use [poetry](https://python-poetry.org/) to install dependencies: - -```bash -poetry install -``` - -2. Setup database - This example needs a PostgreSQL database. Make sure you have one on your machine. - -3. Configure envionment variables - Next up, you'd need to setup your environment variables. There is an example [`.env`](.env.example) file - which you can reference! (Blank variables are optional) - -4. Run migrations - -Run [alembic](https://alembic.sqlalchemy.org/en/latest/) to create the database -and populate it with movie data: - -```bash -poetry run alembic upgrade head -``` - -5. Run the server - -Run [uvicorn](https://www.uvicorn.org/) to run the server: - -```bash -poetry run uvicorn reddit:app --reload +```text +cd reddit-clone +docker compose up ``` You can now explore the GraphQL API here: http://localhost:8000/graphql From 4178ea653cf8e25a805be2fbbd3425e9f28c22b9 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Fri, 15 Oct 2021 11:53:02 +0530 Subject: [PATCH 124/150] update docker compose config --- reddit-clone/README.md | 5 ++++- reddit-clone/docker-compose.yml | 29 +++++++++++++++++++++-------- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/reddit-clone/README.md b/reddit-clone/README.md index df5ed4da..5219eacb 100644 --- a/reddit-clone/README.md +++ b/reddit-clone/README.md @@ -27,7 +27,10 @@ most of the features that Strawberry gives us. ## How to use -You can use [Docker Compose](https://github.com/docker/compose) to run this example. Make sure you have it installed on your machine! +You'd need to setup environment variables first. These values will be read by docker containers when running the example. +Make sure to create an `.env` file at the project root, referencing the [`example .env`](.env.example) file. + +Next up, can use [Docker Compose](https://github.com/docker/compose) to run this example. Make sure you have it installed on your machine! ```text cd reddit-clone diff --git a/reddit-clone/docker-compose.yml b/reddit-clone/docker-compose.yml index 3245b6d3..357b9931 100644 --- a/reddit-clone/docker-compose.yml +++ b/reddit-clone/docker-compose.yml @@ -1,34 +1,45 @@ +version: "3.9" services: - database: - image: postgres:13-alpine + postgres: + image: postgres:14-alpine + container_name: reddit-clone-postgres + restart: unless-stopped env_file: - ".env" + ports: + - "5432:5432" volumes: - postgres-data:/var/lib/postgresql/data redis: - image: redis:6-alpine - env_file: - - ".env" + image: redis:6.2-alpine + container_name: reddit-clone-redis + restart: unless-stopped ports: - "6379:6379" + volumes: + - redis-data:/data rabbitmq: image: rabbitmq:3.8-alpine - env_file: - - ".env" + container_name: reddit-clone-rabbitmq + restart: unless-stopped ports: - "5672:5672" + volumes: + - rabbitmq-data:/data app: build: context: . + container_name: reddit-clone-app env_file: - ".env" depends_on: - - database + - postgres # celery: + # container_name: reddit-clone-celery # build: # context: . # restart: always @@ -40,4 +51,6 @@ services: # - app volumes: + redis-data: + rabbitmq-data: postgres-data: From 7f2bb23c107827a90baef6d0a7dfb71a7881c658 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Fri, 15 Oct 2021 12:05:58 +0530 Subject: [PATCH 125/150] update tasks.py --- reddit-clone/reddit/tasks.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/reddit-clone/reddit/tasks.py b/reddit-clone/reddit/tasks.py index d2c470fd..60d5b489 100644 --- a/reddit-clone/reddit/tasks.py +++ b/reddit-clone/reddit/tasks.py @@ -7,6 +7,3 @@ celery = Celery(__name__) celery.conf.result_backend = settings.CELERY_BACKEND celery.conf.broker_url = settings.CELERY_BROKER - -if __name__ == "__main__": - celery.start() From 92bb3aa4df3069d58c2a292b7e38adb78766f8b0 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Fri, 15 Oct 2021 12:22:35 +0530 Subject: [PATCH 126/150] add PGadmin --- reddit-clone/.env.example | 19 ++++++++++++------- reddit-clone/docker-compose.yml | 11 +++++++++++ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/reddit-clone/.env.example b/reddit-clone/.env.example index 51fa3201..1f5f17ea 100644 --- a/reddit-clone/.env.example +++ b/reddit-clone/.env.example @@ -1,16 +1,21 @@ # app -DATABASE_URI=postgresql+asyncpg://localhost/db_name?user=postgres&password=postgres +DATABASE_URI=postgresql+asyncpg://localhost/reddit_clone?user=postgres&password=reddit_clone MAIL_HOST=127.0.0.1 MAIL_PORT=25 -CELERY_BROKER= +CELERY_BROKER=amqp://guest@queue// CELERY_BACKEND=redis://localhost:6379 -DEBUG= +DEBUG=False MAIL_USERNAME= MAIL_PASSWORD= MAIL_SENDER= # database -POSTGRES_SERVER= -POSTGRES_USER= -POSTGRES_PASSWORD= -POSTGRES_DB= +POSTGRES_SERVER=localhost +POSTGRES_USER=postgres +POSTGRES_PASSWORD=reddit_clone +POSTGRES_DB=reddit_clone + +# PGadmin +PGADMIN_LISTEN_PORT=5050 +PGADMIN_DEFAULT_EMAIL= +PGADMIN_DEFAULT_PASSWORD= diff --git a/reddit-clone/docker-compose.yml b/reddit-clone/docker-compose.yml index 357b9931..03762761 100644 --- a/reddit-clone/docker-compose.yml +++ b/reddit-clone/docker-compose.yml @@ -11,6 +11,17 @@ services: volumes: - postgres-data:/var/lib/postgresql/data + pgadmin: + image: dpage/pgadmin4 + container_name: reddit-clone-admin + restart: unless-stopped + env_file: + - ".env" + ports: + - "5555:80" + depends_on: + - postgres + redis: image: redis:6.2-alpine container_name: reddit-clone-redis From 0528e629bde1867cc6efc38c24f9d85ac921a3d8 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Fri, 15 Oct 2021 12:30:16 +0530 Subject: [PATCH 127/150] update pgadmin config --- reddit-clone/.env.example | 4 ++-- reddit-clone/docker-compose.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/reddit-clone/.env.example b/reddit-clone/.env.example index 1f5f17ea..f6bdd198 100644 --- a/reddit-clone/.env.example +++ b/reddit-clone/.env.example @@ -17,5 +17,5 @@ POSTGRES_DB=reddit_clone # PGadmin PGADMIN_LISTEN_PORT=5050 -PGADMIN_DEFAULT_EMAIL= -PGADMIN_DEFAULT_PASSWORD= +PGADMIN_DEFAULT_EMAIL=admin@reddit-clone.com +PGADMIN_DEFAULT_PASSWORD=admin diff --git a/reddit-clone/docker-compose.yml b/reddit-clone/docker-compose.yml index 03762761..85b94a20 100644 --- a/reddit-clone/docker-compose.yml +++ b/reddit-clone/docker-compose.yml @@ -13,7 +13,7 @@ services: pgadmin: image: dpage/pgadmin4 - container_name: reddit-clone-admin + container_name: reddit-clone-pgadmin restart: unless-stopped env_file: - ".env" From 89d8f8645868a78588fc1f2e69fa2040e8f51604 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Fri, 15 Oct 2021 14:14:25 +0530 Subject: [PATCH 128/150] update Dockerfile --- reddit-clone/.env.example | 1 - reddit-clone/Dockerfile | 2 +- reddit-clone/docker-compose.yml | 2 ++ 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/reddit-clone/.env.example b/reddit-clone/.env.example index f6bdd198..3e3d9a94 100644 --- a/reddit-clone/.env.example +++ b/reddit-clone/.env.example @@ -16,6 +16,5 @@ POSTGRES_PASSWORD=reddit_clone POSTGRES_DB=reddit_clone # PGadmin -PGADMIN_LISTEN_PORT=5050 PGADMIN_DEFAULT_EMAIL=admin@reddit-clone.com PGADMIN_DEFAULT_PASSWORD=admin diff --git a/reddit-clone/Dockerfile b/reddit-clone/Dockerfile index 39dad045..562002f0 100644 --- a/reddit-clone/Dockerfile +++ b/reddit-clone/Dockerfile @@ -12,4 +12,4 @@ RUN poetry install # copy project files COPY ./ ./ -CMD [ "poetry", "run", "uvicorn", "reddit:app" ] +CMD [ "poetry", "run", "uvicorn", "reddit:app", "--host=0.0.0.0" ] diff --git a/reddit-clone/docker-compose.yml b/reddit-clone/docker-compose.yml index 85b94a20..a8f32602 100644 --- a/reddit-clone/docker-compose.yml +++ b/reddit-clone/docker-compose.yml @@ -46,6 +46,8 @@ services: container_name: reddit-clone-app env_file: - ".env" + ports: + - "8000:80" depends_on: - postgres From cc6f6dc0e3ebbaf5147ad1e6c303e32b3721b14c Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Fri, 15 Oct 2021 14:43:50 +0530 Subject: [PATCH 129/150] expose correct docker port --- reddit-clone/docker-compose.yml | 2 +- reddit-clone/reddit/users/queries.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/reddit-clone/docker-compose.yml b/reddit-clone/docker-compose.yml index a8f32602..8e7a667e 100644 --- a/reddit-clone/docker-compose.yml +++ b/reddit-clone/docker-compose.yml @@ -47,7 +47,7 @@ services: env_file: - ".env" ports: - - "8000:80" + - "8000:8000" depends_on: - postgres diff --git a/reddit-clone/reddit/users/queries.py b/reddit-clone/reddit/users/queries.py index f6eef9e9..a027af9f 100644 --- a/reddit-clone/reddit/users/queries.py +++ b/reddit-clone/reddit/users/queries.py @@ -6,12 +6,12 @@ from reddit.database import get_session from reddit.users.types import UserType -from reddit.users.models import User +from reddit.users.services import user_by_username async def resolve_user(info: Info, username: str) -> Optional[UserType]: async with get_session() as session: - user = await User.by_username(session=session, username=username) + user = await user_by_username(session=session, username=username) if user is not None: return cast(UserType, user) return user From b09177f9a460ca0901969ec6091bb49b81f393f5 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Fri, 15 Oct 2021 14:55:39 +0530 Subject: [PATCH 130/150] clear the clutter --- reddit-clone/.env.example | 4 ---- reddit-clone/docker-compose.yml | 11 ----------- 2 files changed, 15 deletions(-) diff --git a/reddit-clone/.env.example b/reddit-clone/.env.example index 3e3d9a94..40d7ed22 100644 --- a/reddit-clone/.env.example +++ b/reddit-clone/.env.example @@ -14,7 +14,3 @@ POSTGRES_SERVER=localhost POSTGRES_USER=postgres POSTGRES_PASSWORD=reddit_clone POSTGRES_DB=reddit_clone - -# PGadmin -PGADMIN_DEFAULT_EMAIL=admin@reddit-clone.com -PGADMIN_DEFAULT_PASSWORD=admin diff --git a/reddit-clone/docker-compose.yml b/reddit-clone/docker-compose.yml index 8e7a667e..4a449006 100644 --- a/reddit-clone/docker-compose.yml +++ b/reddit-clone/docker-compose.yml @@ -11,17 +11,6 @@ services: volumes: - postgres-data:/var/lib/postgresql/data - pgadmin: - image: dpage/pgadmin4 - container_name: reddit-clone-pgadmin - restart: unless-stopped - env_file: - - ".env" - ports: - - "5555:80" - depends_on: - - postgres - redis: image: redis:6.2-alpine container_name: reddit-clone-redis From 9f1c97e1d8e434d03e151f232809b56ccb080625 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Fri, 15 Oct 2021 15:49:04 +0530 Subject: [PATCH 131/150] make builds slim --- reddit-clone/Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/reddit-clone/Dockerfile b/reddit-clone/Dockerfile index 562002f0..9b938443 100644 --- a/reddit-clone/Dockerfile +++ b/reddit-clone/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.9 +FROM python:3.9-slim WORKDIR /app @@ -12,4 +12,6 @@ RUN poetry install # copy project files COPY ./ ./ +EXPOSE 8000 + CMD [ "poetry", "run", "uvicorn", "reddit:app", "--host=0.0.0.0" ] From 2a1abd498a4df753c51c65074b276cf5ccc9cd29 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Fri, 15 Oct 2021 15:57:58 +0530 Subject: [PATCH 132/150] update docker-compose.yml --- reddit-clone/docker-compose.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/reddit-clone/docker-compose.yml b/reddit-clone/docker-compose.yml index 4a449006..91f75e7b 100644 --- a/reddit-clone/docker-compose.yml +++ b/reddit-clone/docker-compose.yml @@ -50,7 +50,6 @@ services: # depends_on: # - redis # - rabbitmq - # - app volumes: redis-data: From 26b521eaec8b62657d44e321689208869a1112e2 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sat, 16 Oct 2021 09:51:07 +0530 Subject: [PATCH 133/150] update docker-compose.yml --- reddit-clone/.env.example | 16 ---------------- reddit-clone/Dockerfile | 2 +- reddit-clone/README.md | 7 ++----- reddit-clone/docker-compose.yml | 20 ++++++++++++++------ reddit-clone/reddit/database.py | 2 +- reddit-clone/reddit/settings.py | 2 +- 6 files changed, 19 insertions(+), 30 deletions(-) delete mode 100644 reddit-clone/.env.example diff --git a/reddit-clone/.env.example b/reddit-clone/.env.example deleted file mode 100644 index 40d7ed22..00000000 --- a/reddit-clone/.env.example +++ /dev/null @@ -1,16 +0,0 @@ -# app -DATABASE_URI=postgresql+asyncpg://localhost/reddit_clone?user=postgres&password=reddit_clone -MAIL_HOST=127.0.0.1 -MAIL_PORT=25 -CELERY_BROKER=amqp://guest@queue// -CELERY_BACKEND=redis://localhost:6379 -DEBUG=False -MAIL_USERNAME= -MAIL_PASSWORD= -MAIL_SENDER= - -# database -POSTGRES_SERVER=localhost -POSTGRES_USER=postgres -POSTGRES_PASSWORD=reddit_clone -POSTGRES_DB=reddit_clone diff --git a/reddit-clone/Dockerfile b/reddit-clone/Dockerfile index 9b938443..9503b186 100644 --- a/reddit-clone/Dockerfile +++ b/reddit-clone/Dockerfile @@ -14,4 +14,4 @@ COPY ./ ./ EXPOSE 8000 -CMD [ "poetry", "run", "uvicorn", "reddit:app", "--host=0.0.0.0" ] +CMD poetry run uvicorn reddit:app --host=0.0.0.0 diff --git a/reddit-clone/README.md b/reddit-clone/README.md index 5219eacb..8486a8ff 100644 --- a/reddit-clone/README.md +++ b/reddit-clone/README.md @@ -22,15 +22,12 @@ most of the features that Strawberry gives us. - [x] data modelling with relations - [x] Error modelling within the schema - [ ] Authorization with the permissions API -- [ ] Batch loading with dataloaders +- [x] Batch loading with dataloaders - [x] modular codebase ## How to use -You'd need to setup environment variables first. These values will be read by docker containers when running the example. -Make sure to create an `.env` file at the project root, referencing the [`example .env`](.env.example) file. - -Next up, can use [Docker Compose](https://github.com/docker/compose) to run this example. Make sure you have it installed on your machine! +You can use [Docker Compose](https://github.com/docker/compose) to run this example. Make sure you have it installed on your machine! ```text cd reddit-clone diff --git a/reddit-clone/docker-compose.yml b/reddit-clone/docker-compose.yml index 91f75e7b..6fac3710 100644 --- a/reddit-clone/docker-compose.yml +++ b/reddit-clone/docker-compose.yml @@ -4,8 +4,10 @@ services: image: postgres:14-alpine container_name: reddit-clone-postgres restart: unless-stopped - env_file: - - ".env" + environment: + POSTGRES_DB: reddit_clone + POSTGRES_PASSWORD: reddit_clone + POSTGRES_USER: reddit_clone ports: - "5432:5432" volumes: @@ -33,8 +35,16 @@ services: build: context: . container_name: reddit-clone-app - env_file: - - ".env" + environment: + DEBUG: "false" + CELERY_BROKER: amqp://guest@queue// + CELERY_BACKEND: redis://localhost:6379 + DATABASE_URL: postgres://reddit_clone:reddit_clone@db:5432/reddit_clone + MAIL_HOST: 127.0.0.1 + MAIL_PORT: 25 + MAIL_USERNAME: + MAIL_PASSWORD: + MAIL_SENDER: ports: - "8000:8000" depends_on: @@ -45,8 +55,6 @@ services: # build: # context: . # restart: always - # env_file: - # - ".env" # depends_on: # - redis # - rabbitmq diff --git a/reddit-clone/reddit/database.py b/reddit-clone/reddit/database.py index 2f7b7c23..cadd3fb8 100644 --- a/reddit-clone/reddit/database.py +++ b/reddit-clone/reddit/database.py @@ -7,7 +7,7 @@ from reddit import settings -engine = create_async_engine(settings.DATABASE_URI, future=True) +engine = create_async_engine(settings.DATABASE_URL, future=True) session_factory = sessionmaker(bind=engine, class_=AsyncSession) diff --git a/reddit-clone/reddit/settings.py b/reddit-clone/reddit/settings.py index 4b12d8be..ddffe396 100644 --- a/reddit-clone/reddit/settings.py +++ b/reddit-clone/reddit/settings.py @@ -9,7 +9,7 @@ DEBUG: bool = config("DEBUG", cast=bool, default=False) # SQLAlchemy database URL. -DATABASE_URI: str = config("DATABASE_URI", cast=str) +DATABASE_URL: str = config("DATABASE_URL", cast=str) # mail client host name. MAIL_HOST: str = config("MAIL_HOST", cast=str) From fc1dd3bfefe7bcd8ff089e461e0302c8da5d0b56 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sat, 16 Oct 2021 09:54:24 +0530 Subject: [PATCH 134/150] update README.md --- reddit-clone/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/reddit-clone/README.md b/reddit-clone/README.md index 8486a8ff..a0e07896 100644 --- a/reddit-clone/README.md +++ b/reddit-clone/README.md @@ -30,7 +30,6 @@ most of the features that Strawberry gives us. You can use [Docker Compose](https://github.com/docker/compose) to run this example. Make sure you have it installed on your machine! ```text -cd reddit-clone docker compose up ``` From 9c9aab6064deda4e20d12d17da99c4957beb6698 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sat, 16 Oct 2021 12:24:29 +0530 Subject: [PATCH 135/150] update celery deps --- reddit-clone/Dockerfile | 3 +- reddit-clone/docker-compose.yml | 41 +++++-- reddit-clone/poetry.lock | 208 +++++++++++++++++++------------- reddit-clone/pyproject.toml | 2 +- 4 files changed, 158 insertions(+), 96 deletions(-) diff --git a/reddit-clone/Dockerfile b/reddit-clone/Dockerfile index 9503b186..d6ab08d3 100644 --- a/reddit-clone/Dockerfile +++ b/reddit-clone/Dockerfile @@ -1,4 +1,5 @@ FROM python:3.9-slim +LABEL maintainer="Aryan Iyappan " WORKDIR /app @@ -7,7 +8,7 @@ RUN pip install poetry # install dependencies COPY ./poetry.lock ./pyproject.toml ./ -RUN poetry install +RUN poetry install --no-dev # copy project files COPY ./ ./ diff --git a/reddit-clone/docker-compose.yml b/reddit-clone/docker-compose.yml index 6fac3710..9f3fae20 100644 --- a/reddit-clone/docker-compose.yml +++ b/reddit-clone/docker-compose.yml @@ -31,15 +31,15 @@ services: volumes: - rabbitmq-data:/data - app: + server: build: - context: . - container_name: reddit-clone-app + context: "." + container_name: reddit-clone-server environment: DEBUG: "false" - CELERY_BROKER: amqp://guest@queue// + CELERY_BROKER: amqp://guest:guest@localhost:5672// CELERY_BACKEND: redis://localhost:6379 - DATABASE_URL: postgres://reddit_clone:reddit_clone@db:5432/reddit_clone + DATABASE_URL: postgresql+asyncpg://reddit_clone:reddit_clone@db:5432/reddit_clone MAIL_HOST: 127.0.0.1 MAIL_PORT: 25 MAIL_USERNAME: @@ -50,16 +50,31 @@ services: depends_on: - postgres - # celery: - # container_name: reddit-clone-celery - # build: - # context: . - # restart: always - # depends_on: - # - redis - # - rabbitmq + celery: + build: + context: "." + container_name: reddit-clone-celery + restart: on-failure + command: poetry run celery -A reddit.tasks worker + environment: + DEBUG: "false" + CELERY_BROKER: amqp://guest:guest@localhost:5672// + CELERY_BACKEND: redis://localhost:6379 + DATABASE_URL: postgresql+asyncpg://reddit_clone:reddit_clone@db:5432/reddit_clone + MAIL_HOST: 127.0.0.1 + MAIL_PORT: 25 + MAIL_USERNAME: + MAIL_PASSWORD: + MAIL_SENDER: + depends_on: + - server + - rabbitmq + - redis volumes: redis-data: + driver: local rabbitmq-data: + driver: local postgres-data: + driver: local diff --git a/reddit-clone/poetry.lock b/reddit-clone/poetry.lock index dd0e275a..d5f7a2c7 100644 --- a/reddit-clone/poetry.lock +++ b/reddit-clone/poetry.lock @@ -171,7 +171,9 @@ click-didyoumean = ">=0.0.3" click-plugins = ">=1.1.1" click-repl = ">=0.1.6" kombu = ">=5.1.0,<6.0" +librabbitmq = {version = ">=1.5.0", optional = true, markers = "extra == \"librabbitmq\""} pytz = ">0.0-dev" +redis = {version = ">=3.2.0", optional = true, markers = "extra == \"redis\""} vine = ">=5.0.0,<6.0" [package.extras] @@ -209,7 +211,7 @@ zstd = ["zstandard"] [[package]] name = "cffi" -version = "1.14.6" +version = "1.15.0" description = "Foreign Function Interface for Python calling C code." category = "main" optional = false @@ -448,6 +450,18 @@ sqs = ["boto3 (>=1.4.4)", "pycurl (==7.43.0.2)", "urllib3 (<1.26)"] yaml = ["PyYAML (>=3.10)"] zookeeper = ["kazoo (>=1.3.1)"] +[[package]] +name = "librabbitmq" +version = "2.0.0" +description = "AMQP Client using the rabbitmq-c library." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +amqp = ">=1.4.6" +six = ">=1.0.0" + [[package]] name = "mako" version = "1.1.5" @@ -628,11 +642,22 @@ python-versions = "*" [[package]] name = "pyyaml" -version = "5.4.1" +version = "6.0" description = "YAML parser and emitter for Python" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.6" + +[[package]] +name = "redis" +version = "3.5.3" +description = "Python client for Redis key-value store" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +hiredis = ["hiredis (>=0.1.3)"] [[package]] name = "regex" @@ -714,7 +739,7 @@ sqlcipher = ["sqlcipher3-binary"] [[package]] name = "sqlalchemy2-stubs" -version = "0.0.2a17" +version = "0.0.2a18" description = "Typing Stubs for SQLAlchemy 1.4" category = "main" optional = false @@ -883,7 +908,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "8753228c24cfc820ae979004bf108c0f179adbba378a2612180dc1b38e120a4d" +content-hash = "700aac6bc63fb865ad58061cecb0089dd106cc48be7646f6590aefbf748adb96" [metadata.files] aiofiles = [ @@ -955,51 +980,56 @@ celery = [ {file = "celery-5.1.2.tar.gz", hash = "sha256:8d9a3de9162965e97f8e8cc584c67aad83b3f7a267584fa47701ed11c3e0d4b0"}, ] cffi = [ - {file = "cffi-1.14.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c"}, - {file = "cffi-1.14.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99"}, - {file = "cffi-1.14.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819"}, - {file = "cffi-1.14.6-cp27-cp27m-win32.whl", hash = "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20"}, - {file = "cffi-1.14.6-cp27-cp27m-win_amd64.whl", hash = "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224"}, - {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7"}, - {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33"}, - {file = "cffi-1.14.6-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534"}, - {file = "cffi-1.14.6-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a"}, - {file = "cffi-1.14.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5"}, - {file = "cffi-1.14.6-cp35-cp35m-win32.whl", hash = "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca"}, - {file = "cffi-1.14.6-cp35-cp35m-win_amd64.whl", hash = "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218"}, - {file = "cffi-1.14.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b"}, - {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb"}, - {file = "cffi-1.14.6-cp36-cp36m-win32.whl", hash = "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a"}, - {file = "cffi-1.14.6-cp36-cp36m-win_amd64.whl", hash = "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e"}, - {file = "cffi-1.14.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c"}, - {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762"}, - {file = "cffi-1.14.6-cp37-cp37m-win32.whl", hash = "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771"}, - {file = "cffi-1.14.6-cp37-cp37m-win_amd64.whl", hash = "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a"}, - {file = "cffi-1.14.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd"}, - {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc"}, - {file = "cffi-1.14.6-cp38-cp38-win32.whl", hash = "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548"}, - {file = "cffi-1.14.6-cp38-cp38-win_amd64.whl", hash = "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156"}, - {file = "cffi-1.14.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f"}, - {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87"}, - {file = "cffi-1.14.6-cp39-cp39-win32.whl", hash = "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728"}, - {file = "cffi-1.14.6-cp39-cp39-win_amd64.whl", hash = "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2"}, - {file = "cffi-1.14.6.tar.gz", hash = "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd"}, + {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, + {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"}, + {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"}, + {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"}, + {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"}, + {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"}, + {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"}, + {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"}, + {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"}, + {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"}, + {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"}, + {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"}, + {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"}, + {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"}, + {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"}, + {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"}, + {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"}, + {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"}, + {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"}, + {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"}, + {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"}, + {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"}, + {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"}, + {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, + {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, ] click = [ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, @@ -1134,6 +1164,14 @@ kombu = [ {file = "kombu-5.1.0-py3-none-any.whl", hash = "sha256:e2dedd8a86c9077c350555153825a31e456a0dc20c15d5751f00137ec9c75f0a"}, {file = "kombu-5.1.0.tar.gz", hash = "sha256:01481d99f4606f6939cdc9b637264ed353ee9e3e4f62cfb582324142c41a572d"}, ] +librabbitmq = [ + {file = "librabbitmq-2.0.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2a8113d3c831808d1d940fdf43e4882636a1efe2864df7ab3bb709a45016b37"}, + {file = "librabbitmq-2.0.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:3116e40c02d4285b8dd69834e4cbcb1a89ea534ca9147e865f11d44e7cc56eea"}, + {file = "librabbitmq-2.0.0-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:cd9cc09343b193d7cf2cff6c6a578061863bd986a4bdf38f922e9dc32e15d944"}, + {file = "librabbitmq-2.0.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:98e355f486964dadae7e8b51c9a60e9aa0653bbe27f6b14542687f305c4c3652"}, + {file = "librabbitmq-2.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5cdfb473573396d43d54cef9e9b4c74fa3d1516da51d04a7b261f6ef4e0bd8be"}, + {file = "librabbitmq-2.0.0.tar.gz", hash = "sha256:ffa2363a860ab5dcc3ce3703247e05e940c73d776c03a3f3f9deaf3cf43bb96c"}, +] mako = [ {file = "Mako-1.1.5-py2.py3-none-any.whl", hash = "sha256:6804ee66a7f6a6416910463b00d76a7b25194cd27f1918500c5bd7be2a088a23"}, {file = "Mako-1.1.5.tar.gz", hash = "sha256:169fa52af22a91900d852e937400e79f535496191c63712e3b9fda5a9bed6fc3"}, @@ -1255,35 +1293,43 @@ pytz = [ {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, ] pyyaml = [ - {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, - {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, - {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, - {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, - {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, - {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, - {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"}, - {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"}, - {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, - {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, - {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, - {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, - {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"}, - {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"}, - {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, - {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, - {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, - {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, - {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"}, - {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"}, - {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, - {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, - {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, - {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, - {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"}, - {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"}, - {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, - {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, - {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, + {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"}, +] +redis = [ + {file = "redis-3.5.3-py2.py3-none-any.whl", hash = "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"}, + {file = "redis-3.5.3.tar.gz", hash = "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2"}, ] regex = [ {file = "regex-2021.10.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:981c786293a3115bc14c103086ae54e5ee50ca57f4c02ce7cf1b60318d1e8072"}, @@ -1377,8 +1423,8 @@ sqlalchemy = [ {file = "SQLAlchemy-1.4.25.tar.gz", hash = "sha256:1adf3d25e2e33afbcd48cfad8076f9378793be43e7fec3e4334306cac6bec138"}, ] sqlalchemy2-stubs = [ - {file = "sqlalchemy2-stubs-0.0.2a17.tar.gz", hash = "sha256:d7e1f63f82711c83d49eb6adccbf6bbf3cc94783436c0d34cf7ae49820023046"}, - {file = "sqlalchemy2_stubs-0.0.2a17-py3-none-any.whl", hash = "sha256:6f112f9381a29575676c3012ce10ed6bceabfad11f1d5569d7072ccf66cfa060"}, + {file = "sqlalchemy2-stubs-0.0.2a18.tar.gz", hash = "sha256:513f8f504e7a869e6a584b9cbfa65b7d817d017ff01af61855f62087735561a9"}, + {file = "sqlalchemy2_stubs-0.0.2a18-py3-none-any.whl", hash = "sha256:75ec8ce53db5a85884adcb9f249751bc3aefb4c24fd2b5bd62860113eea4b37a"}, ] starlette = [ {file = "starlette-0.16.0-py3-none-any.whl", hash = "sha256:38eb24bf705a2c317e15868e384c1b8a12ca396e5a3c3a003db7e667c43f939f"}, diff --git a/reddit-clone/pyproject.toml b/reddit-clone/pyproject.toml index 2f6df1d8..0b6e8593 100644 --- a/reddit-clone/pyproject.toml +++ b/reddit-clone/pyproject.toml @@ -10,6 +10,7 @@ uvicorn = {extras = ["standard"], version = "^0.15.0"} SQLAlchemy = {extras = ["mypy"], version = "^1.4.23"} strawberry-graphql = {extras = ["asgi"], version = "^0.82.2"} passlib = {extras = ["argon2"], version = "^1.7.4"} +celery = {extras = ["redis", "librabbitmq"], version = "^5.1.2"} alembic = "^1.7.1" asyncpg = "^0.24" graphql-relay = "^3.1.0" @@ -17,7 +18,6 @@ schematics = "^2.1.1" jinja2 = "^3.0.2" aiofiles = "^0.7.0" starlette = "^0.16.0" -celery = "^5.1.2" [tool.poetry.dev-dependencies] From 823ce995b4de46ec16665f06a5c6b213ab98f08b Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sat, 16 Oct 2021 12:58:18 +0530 Subject: [PATCH 136/150] simplify docker config --- reddit-clone/Dockerfile | 4 ---- reddit-clone/docker-compose.yml | 39 +++++++++++++++++++-------------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/reddit-clone/Dockerfile b/reddit-clone/Dockerfile index d6ab08d3..da91f476 100644 --- a/reddit-clone/Dockerfile +++ b/reddit-clone/Dockerfile @@ -12,7 +12,3 @@ RUN poetry install --no-dev # copy project files COPY ./ ./ - -EXPOSE 8000 - -CMD poetry run uvicorn reddit:app --host=0.0.0.0 diff --git a/reddit-clone/docker-compose.yml b/reddit-clone/docker-compose.yml index 9f3fae20..e82e56a4 100644 --- a/reddit-clone/docker-compose.yml +++ b/reddit-clone/docker-compose.yml @@ -2,12 +2,12 @@ version: "3.9" services: postgres: image: postgres:14-alpine - container_name: reddit-clone-postgres + container_name: reddit-postgres restart: unless-stopped environment: - POSTGRES_DB: reddit_clone - POSTGRES_PASSWORD: reddit_clone - POSTGRES_USER: reddit_clone + POSTGRES_DB: reddit + POSTGRES_PASSWORD: reddit + POSTGRES_USER: reddit ports: - "5432:5432" volumes: @@ -15,17 +15,22 @@ services: redis: image: redis:6.2-alpine - container_name: reddit-clone-redis + container_name: reddit-redis restart: unless-stopped + environment: + REDIS_PASSWORD: reddit ports: - "6379:6379" volumes: - redis-data:/data rabbitmq: - image: rabbitmq:3.8-alpine - container_name: reddit-clone-rabbitmq + image: rabbitmq:3.9-alpine + container_name: reddit-rabbitmq restart: unless-stopped + environment: + RABBITMQ_DEFAULT_USER: reddit + RABBITMQ_DEFAULT_PASS: reddit ports: - "5672:5672" volumes: @@ -34,12 +39,14 @@ services: server: build: context: "." - container_name: reddit-clone-server + container_name: reddit-server + restart: unless-stopped + command: poetry run uvicorn reddit:app --host=0.0.0.0 environment: DEBUG: "false" - CELERY_BROKER: amqp://guest:guest@localhost:5672// - CELERY_BACKEND: redis://localhost:6379 - DATABASE_URL: postgresql+asyncpg://reddit_clone:reddit_clone@db:5432/reddit_clone + CELERY_BROKER: amqp://reddit:reddit@rabbitmq:5672/ + CELERY_BACKEND: redis://:reddit@localhost:6379/ + DATABASE_URL: postgresql+asyncpg://reddit:reddit@db:5432/reddit MAIL_HOST: 127.0.0.1 MAIL_PORT: 25 MAIL_USERNAME: @@ -53,23 +60,23 @@ services: celery: build: context: "." - container_name: reddit-clone-celery + container_name: reddit-celery restart: on-failure command: poetry run celery -A reddit.tasks worker environment: DEBUG: "false" - CELERY_BROKER: amqp://guest:guest@localhost:5672// - CELERY_BACKEND: redis://localhost:6379 - DATABASE_URL: postgresql+asyncpg://reddit_clone:reddit_clone@db:5432/reddit_clone + CELERY_BROKER: amqp://reddit:reddit@rabbitmq:5672/ + CELERY_BACKEND: redis://:reddit@localhost:6379/ + DATABASE_URL: postgresql+asyncpg://reddit:reddit@db:5432/reddit MAIL_HOST: 127.0.0.1 MAIL_PORT: 25 MAIL_USERNAME: MAIL_PASSWORD: MAIL_SENDER: depends_on: - - server - rabbitmq - redis + - server volumes: redis-data: From 675650af9e1650e0fec4fbea661698bca09304a2 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sat, 16 Oct 2021 13:24:06 +0530 Subject: [PATCH 137/150] reduce duplication --- reddit-clone/docker-compose.yml | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/reddit-clone/docker-compose.yml b/reddit-clone/docker-compose.yml index e82e56a4..e2988d8d 100644 --- a/reddit-clone/docker-compose.yml +++ b/reddit-clone/docker-compose.yml @@ -37,12 +37,12 @@ services: - rabbitmq-data:/data server: - build: - context: "." + build: "." + image: reddit-server container_name: reddit-server restart: unless-stopped command: poetry run uvicorn reddit:app --host=0.0.0.0 - environment: + environment: &env DEBUG: "false" CELERY_BROKER: amqp://reddit:reddit@rabbitmq:5672/ CELERY_BACKEND: redis://:reddit@localhost:6379/ @@ -58,21 +58,12 @@ services: - postgres celery: - build: - context: "." + build: "." + image: reddit-server container_name: reddit-celery restart: on-failure command: poetry run celery -A reddit.tasks worker - environment: - DEBUG: "false" - CELERY_BROKER: amqp://reddit:reddit@rabbitmq:5672/ - CELERY_BACKEND: redis://:reddit@localhost:6379/ - DATABASE_URL: postgresql+asyncpg://reddit:reddit@db:5432/reddit - MAIL_HOST: 127.0.0.1 - MAIL_PORT: 25 - MAIL_USERNAME: - MAIL_PASSWORD: - MAIL_SENDER: + environment: *env depends_on: - rabbitmq - redis From 1e901e02b73c8bcde5e6fc8064d558ae43ed8c1f Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sat, 16 Oct 2021 13:25:38 +0530 Subject: [PATCH 138/150] update docker-compose.yml --- reddit-clone/docker-compose.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/reddit-clone/docker-compose.yml b/reddit-clone/docker-compose.yml index e2988d8d..6f47f692 100644 --- a/reddit-clone/docker-compose.yml +++ b/reddit-clone/docker-compose.yml @@ -58,7 +58,6 @@ services: - postgres celery: - build: "." image: reddit-server container_name: reddit-celery restart: on-failure From dfb2f864a032805a4a7fc8a8736d148f94f04870 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sat, 16 Oct 2021 14:26:15 +0530 Subject: [PATCH 139/150] update database url --- reddit-clone/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reddit-clone/docker-compose.yml b/reddit-clone/docker-compose.yml index 6f47f692..84f9db3e 100644 --- a/reddit-clone/docker-compose.yml +++ b/reddit-clone/docker-compose.yml @@ -46,7 +46,7 @@ services: DEBUG: "false" CELERY_BROKER: amqp://reddit:reddit@rabbitmq:5672/ CELERY_BACKEND: redis://:reddit@localhost:6379/ - DATABASE_URL: postgresql+asyncpg://reddit:reddit@db:5432/reddit + DATABASE_URL: postgresql+asyncpg://reddit:reddit@localhost:5432/reddit/ MAIL_HOST: 127.0.0.1 MAIL_PORT: 25 MAIL_USERNAME: From 56ab62cdfb986584bf56c267dd85dbfa0f0b8be6 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sat, 16 Oct 2021 17:43:02 +0530 Subject: [PATCH 140/150] update docker-compose.yml --- reddit-clone/docker-compose.yml | 45 ++++++++++++++++++++++----------- reddit-clone/reddit/settings.py | 3 ++- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/reddit-clone/docker-compose.yml b/reddit-clone/docker-compose.yml index 84f9db3e..a72e7569 100644 --- a/reddit-clone/docker-compose.yml +++ b/reddit-clone/docker-compose.yml @@ -1,9 +1,20 @@ version: "3.9" +x-environment: &base-environment + DEBUG: "false" + CELERY_BROKER: amqp://reddit:reddit@rabbitmq:5672/ + CELERY_BACKEND: redis://:reddit@localhost:6379/ + DATABASE_URL: postgresql+asyncpg://reddit:reddit@localhost:5432/reddit/ + MAIL_HOST: 127.0.0.1 + MAIL_PORT: 25 + MAIL_USERNAME: + MAIL_PASSWORD: + MAIL_SENDER: + services: postgres: image: postgres:14-alpine container_name: reddit-postgres - restart: unless-stopped + restart: always environment: POSTGRES_DB: reddit POSTGRES_PASSWORD: reddit @@ -16,13 +27,18 @@ services: redis: image: redis:6.2-alpine container_name: reddit-redis - restart: unless-stopped + restart: always environment: REDIS_PASSWORD: reddit ports: - "6379:6379" volumes: - redis-data:/data + healthcheck: + test: redis-cli ping + interval: 15s + retries: 5 + timeout: 5s rabbitmq: image: rabbitmq:3.9-alpine @@ -35,34 +51,33 @@ services: - "5672:5672" volumes: - rabbitmq-data:/data + healthcheck: + test: rabbitmq-diagnostics -q ping + interval: 15s + retries: 5 + timeout: 5s server: build: "." image: reddit-server container_name: reddit-server - restart: unless-stopped + restart: always command: poetry run uvicorn reddit:app --host=0.0.0.0 - environment: &env - DEBUG: "false" - CELERY_BROKER: amqp://reddit:reddit@rabbitmq:5672/ - CELERY_BACKEND: redis://:reddit@localhost:6379/ - DATABASE_URL: postgresql+asyncpg://reddit:reddit@localhost:5432/reddit/ - MAIL_HOST: 127.0.0.1 - MAIL_PORT: 25 - MAIL_USERNAME: - MAIL_PASSWORD: - MAIL_SENDER: + environment: *base-environment ports: - "8000:8000" + volumes: + - .:/server depends_on: - postgres celery: + # TODO: run celery with different user image: reddit-server container_name: reddit-celery - restart: on-failure + restart: always command: poetry run celery -A reddit.tasks worker - environment: *env + environment: *base-environment depends_on: - rabbitmq - redis diff --git a/reddit-clone/reddit/settings.py b/reddit-clone/reddit/settings.py index ddffe396..8a23f94a 100644 --- a/reddit-clone/reddit/settings.py +++ b/reddit-clone/reddit/settings.py @@ -1,6 +1,7 @@ from typing import Optional from starlette.config import Config +from starlette.datastructures import Secret config = Config(env_file=".env") @@ -21,7 +22,7 @@ MAIL_USERNAME: Optional[str] = config("MAIL_USERNAME", cast=str, default=None) # mail client auth password. -MAIL_PASSWORD: Optional[str] = config("MAIL_PASSWORD", cast=str, default=None) +MAIL_PASSWORD: Optional[Secret] = config("MAIL_PASSWORD", cast=Secret, default=None) # mail client sender address. MAIL_SENDER: Optional[str] = config("MAIL_SENDER", cast=str, default=None) From b98389e5da2ae8ee2a1b389210b3fb420ad5d451 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sat, 16 Oct 2021 18:46:28 +0530 Subject: [PATCH 141/150] update docker config --- reddit-clone/Dockerfile | 7 ++++--- reddit-clone/docker-compose.yml | 17 ++++++++++++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/reddit-clone/Dockerfile b/reddit-clone/Dockerfile index da91f476..e4eec895 100644 --- a/reddit-clone/Dockerfile +++ b/reddit-clone/Dockerfile @@ -1,14 +1,15 @@ FROM python:3.9-slim LABEL maintainer="Aryan Iyappan " -WORKDIR /app +ARG APP_HOME=/app/ +WORKDIR ${APP_HOME} # install poetry RUN pip install poetry # install dependencies -COPY ./poetry.lock ./pyproject.toml ./ +COPY ./poetry.lock ./pyproject.toml ${APP_HOME} RUN poetry install --no-dev # copy project files -COPY ./ ./ +COPY ./ ${APP_HOME} diff --git a/reddit-clone/docker-compose.yml b/reddit-clone/docker-compose.yml index a72e7569..0129f22a 100644 --- a/reddit-clone/docker-compose.yml +++ b/reddit-clone/docker-compose.yml @@ -19,6 +19,8 @@ services: POSTGRES_DB: reddit POSTGRES_PASSWORD: reddit POSTGRES_USER: reddit + networks: + - reddit-main ports: - "5432:5432" volumes: @@ -30,6 +32,8 @@ services: restart: always environment: REDIS_PASSWORD: reddit + networks: + - reddit-main ports: - "6379:6379" volumes: @@ -47,6 +51,8 @@ services: environment: RABBITMQ_DEFAULT_USER: reddit RABBITMQ_DEFAULT_PASS: reddit + networks: + - reddit-main ports: - "5672:5672" volumes: @@ -64,6 +70,8 @@ services: restart: always command: poetry run uvicorn reddit:app --host=0.0.0.0 environment: *base-environment + networks: + - reddit-main ports: - "8000:8000" volumes: @@ -72,12 +80,15 @@ services: - postgres celery: - # TODO: run celery with different user image: reddit-server container_name: reddit-celery restart: always command: poetry run celery -A reddit.tasks worker environment: *base-environment + networks: + - reddit-main + volumes: + - .:/server depends_on: - rabbitmq - redis @@ -90,3 +101,7 @@ volumes: driver: local postgres-data: driver: local + +networks: + reddit-main: + driver: bridge From b51cd543b82e24f310001311a6e717b41082e8fe Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sat, 16 Oct 2021 19:51:09 +0530 Subject: [PATCH 142/150] prepare to add nginx --- reddit-clone/docker-compose.yml | 78 +++++++++++++++++++-------------- reddit-clone/nginx/nginx.conf | 6 +++ 2 files changed, 52 insertions(+), 32 deletions(-) create mode 100644 reddit-clone/nginx/nginx.conf diff --git a/reddit-clone/docker-compose.yml b/reddit-clone/docker-compose.yml index 0129f22a..fef929e2 100644 --- a/reddit-clone/docker-compose.yml +++ b/reddit-clone/docker-compose.yml @@ -11,6 +11,49 @@ x-environment: &base-environment MAIL_SENDER: services: + nginx: + image: nginx:1.21-alpine + container_name: reddit-nginx + restart: always + networks: + - reddit-proxy + ports: + - "8000:8000" + volumes: + - ./nginx/nginx.conf:/tmp/nginx.conf + depends_on: + - app + + app: + build: "." + image: reddit-app + container_name: reddit-app + restart: always + command: poetry run uvicorn reddit:app --host=0.0.0.0 --port=5000 + environment: *base-environment + networks: + - reddit-main + - reddit-proxy + volumes: + - .:/app + depends_on: + - postgres + + celery: + image: reddit-app + container_name: reddit-celery + restart: always + command: poetry run celery -A reddit.tasks worker + environment: *base-environment + networks: + - reddit-main + volumes: + - .:/app + depends_on: + - rabbitmq + - redis + - app + postgres: image: postgres:14-alpine container_name: reddit-postgres @@ -47,7 +90,7 @@ services: rabbitmq: image: rabbitmq:3.9-alpine container_name: reddit-rabbitmq - restart: unless-stopped + restart: always environment: RABBITMQ_DEFAULT_USER: reddit RABBITMQ_DEFAULT_PASS: reddit @@ -63,37 +106,6 @@ services: retries: 5 timeout: 5s - server: - build: "." - image: reddit-server - container_name: reddit-server - restart: always - command: poetry run uvicorn reddit:app --host=0.0.0.0 - environment: *base-environment - networks: - - reddit-main - ports: - - "8000:8000" - volumes: - - .:/server - depends_on: - - postgres - - celery: - image: reddit-server - container_name: reddit-celery - restart: always - command: poetry run celery -A reddit.tasks worker - environment: *base-environment - networks: - - reddit-main - volumes: - - .:/server - depends_on: - - rabbitmq - - redis - - server - volumes: redis-data: driver: local @@ -105,3 +117,5 @@ volumes: networks: reddit-main: driver: bridge + reddit-proxy: + driver: bridge diff --git a/reddit-clone/nginx/nginx.conf b/reddit-clone/nginx/nginx.conf new file mode 100644 index 00000000..f2561ac0 --- /dev/null +++ b/reddit-clone/nginx/nginx.conf @@ -0,0 +1,6 @@ +server { + listen 80; + location / { + proxy_pass http://5000; + } +} From 904b07cd287f3abdb9e83226ae1a53ab0c4d50dc Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sun, 17 Oct 2021 07:37:23 +0530 Subject: [PATCH 143/150] get nginx proxy working --- reddit-clone/README.md | 2 +- reddit-clone/docker-compose.yml | 12 ++++++------ reddit-clone/nginx/nginx.conf | 9 +++++---- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/reddit-clone/README.md b/reddit-clone/README.md index a0e07896..0ab48163 100644 --- a/reddit-clone/README.md +++ b/reddit-clone/README.md @@ -33,4 +33,4 @@ You can use [Docker Compose](https://github.com/docker/compose) to run this exam docker compose up ``` -You can now explore the GraphQL API here: http://localhost:8000/graphql +You can now explore the GraphQL API here: http://localhost/graphql diff --git a/reddit-clone/docker-compose.yml b/reddit-clone/docker-compose.yml index fef929e2..cf776202 100644 --- a/reddit-clone/docker-compose.yml +++ b/reddit-clone/docker-compose.yml @@ -18,9 +18,9 @@ services: networks: - reddit-proxy ports: - - "8000:8000" + - 80:80 volumes: - - ./nginx/nginx.conf:/tmp/nginx.conf + - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf depends_on: - app @@ -29,7 +29,7 @@ services: image: reddit-app container_name: reddit-app restart: always - command: poetry run uvicorn reddit:app --host=0.0.0.0 --port=5000 + command: poetry run uvicorn reddit:app --host=0.0.0.0 --port=8080 environment: *base-environment networks: - reddit-main @@ -65,7 +65,7 @@ services: networks: - reddit-main ports: - - "5432:5432" + - 5432:5432 volumes: - postgres-data:/var/lib/postgresql/data @@ -78,7 +78,7 @@ services: networks: - reddit-main ports: - - "6379:6379" + - 6379:6379 volumes: - redis-data:/data healthcheck: @@ -97,7 +97,7 @@ services: networks: - reddit-main ports: - - "5672:5672" + - 5672:5672 volumes: - rabbitmq-data:/data healthcheck: diff --git a/reddit-clone/nginx/nginx.conf b/reddit-clone/nginx/nginx.conf index f2561ac0..6c3ca497 100644 --- a/reddit-clone/nginx/nginx.conf +++ b/reddit-clone/nginx/nginx.conf @@ -1,6 +1,7 @@ server { - listen 80; - location / { - proxy_pass http://5000; - } + listen 80; + + location / { + proxy_pass http://app:8080; + } } From 632f5a7846aec71e71d3979ed6b21fd88897c0aa Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sun, 17 Oct 2021 07:59:56 +0530 Subject: [PATCH 144/150] add more fields --- reddit-clone/reddit/comments/types.py | 24 +++++++++++++++++++++++- reddit-clone/reddit/posts/types.py | 18 +++++++++++++++++- reddit-clone/reddit/subreddits/types.py | 11 ++++++++++- 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/reddit-clone/reddit/comments/types.py b/reddit-clone/reddit/comments/types.py index f2050dd0..b0ef9ef0 100644 --- a/reddit-clone/reddit/comments/types.py +++ b/reddit-clone/reddit/comments/types.py @@ -1,12 +1,16 @@ from __future__ import annotations -from typing import List, Optional, cast +from typing import TYPE_CHECKING, List, Optional, cast import strawberry from strawberry.types import Info from reddit.base.types import NodeType +if TYPE_CHECKING: + from reddit.users.types import UserType + from reddit.posts.types import PostType + @strawberry.type(name="Comment") class CommentType(NodeType): @@ -28,12 +32,30 @@ class CommentType(NodeType): """ ) + post_id: int = strawberry.field( + description=""" + The post ID of the comment. + """ + ) + replies: List[CommentType] = strawberry.field( description=""" The replies for the comment. """ ) + @strawberry.field(description="The owner of the comment.") + async def owner(self, info: Info) -> UserType: + loader = info.context.get("user_loader") + user = await loader.load(self.owner_id) + return cast(UserType, user) + + @strawberry.field(description="The post of the comment.") + async def post(self, info: Info) -> PostType: + loader = info.context.get("post_loader") + post = await loader.load(self.post_id) + return cast(PostType, post) + @classmethod async def resolve_node(cls, info: Info, comment_id: str) -> Optional[CommentType]: """ diff --git a/reddit-clone/reddit/posts/types.py b/reddit-clone/reddit/posts/types.py index 83ca086e..4811c2e7 100644 --- a/reddit-clone/reddit/posts/types.py +++ b/reddit-clone/reddit/posts/types.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import List, Optional, cast +from typing import TYPE_CHECKING, List, Optional, cast import strawberry from strawberry.types import Info @@ -8,6 +8,10 @@ from reddit.base.types import NodeType from reddit.comments.types import CommentType +if TYPE_CHECKING: + from reddit.subreddits.types import SubredditType + from reddit.users.types import UserType + @strawberry.type(name="Post") class PostType(NodeType): @@ -59,6 +63,18 @@ class PostType(NodeType): """ ) + @strawberry.field(description="The owner of the post.") + async def owner(self, info: Info) -> UserType: + loader = info.context.get("user_loader") + user = await loader.load(self.owner_id) + return cast(UserType, user) + + @strawberry.field(description="The Subreddit of the post.") + async def subreddit(self, info: Info) -> SubredditType: + loader = info.context.get("subreddit_loader") + subreddit = await loader.load(self.subreddit_id) + return cast(SubredditType, subreddit) + @classmethod async def resolve_node(cls, info: Info, post_id: str) -> Optional[PostType]: """ diff --git a/reddit-clone/reddit/subreddits/types.py b/reddit-clone/reddit/subreddits/types.py index aaf45159..4234a72d 100644 --- a/reddit-clone/reddit/subreddits/types.py +++ b/reddit-clone/reddit/subreddits/types.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import List, Optional, cast +from typing import TYPE_CHECKING, List, Optional, cast import strawberry from strawberry.types import Info @@ -8,6 +8,9 @@ from reddit.base.types import NodeType from reddit.posts.types import PostType +if TYPE_CHECKING: + from reddit.users.types import UserType + @strawberry.type(name="Subreddit") class SubredditType(NodeType): @@ -48,6 +51,12 @@ class SubredditType(NodeType): """ ) + @strawberry.field(description="The owner of the Subreddit.") + async def owner(self, info: Info) -> UserType: + loader = info.context.get("user_loader") + user = await loader.load(self.owner_id) + return cast(UserType, user) + @classmethod async def resolve_node( cls, info: Info, subreddit_id: str From a4b971e19ebff68df6928e86b13cb02c7a4ea689 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sun, 17 Oct 2021 08:17:44 +0530 Subject: [PATCH 145/150] fix circular import issues --- reddit-clone/reddit/comments/types.py | 9 +++++++-- reddit-clone/reddit/posts/types.py | 9 +++++++-- reddit-clone/reddit/subreddits/types.py | 5 ++++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/reddit-clone/reddit/comments/types.py b/reddit-clone/reddit/comments/types.py index b0ef9ef0..51616264 100644 --- a/reddit-clone/reddit/comments/types.py +++ b/reddit-clone/reddit/comments/types.py @@ -4,6 +4,7 @@ import strawberry from strawberry.types import Info +from strawberry.lazy_type import LazyType from reddit.base.types import NodeType @@ -45,13 +46,17 @@ class CommentType(NodeType): ) @strawberry.field(description="The owner of the comment.") - async def owner(self, info: Info) -> UserType: + async def owner( + self, info: Info + ) -> LazyType["UserType", "reddit.users.types"]: # noqa: F821 loader = info.context.get("user_loader") user = await loader.load(self.owner_id) return cast(UserType, user) @strawberry.field(description="The post of the comment.") - async def post(self, info: Info) -> PostType: + async def post( + self, info: Info + ) -> LazyType["PostType", "reddit.posts.types"]: # noqa: F821 loader = info.context.get("post_loader") post = await loader.load(self.post_id) return cast(PostType, post) diff --git a/reddit-clone/reddit/posts/types.py b/reddit-clone/reddit/posts/types.py index 4811c2e7..b1d97fa8 100644 --- a/reddit-clone/reddit/posts/types.py +++ b/reddit-clone/reddit/posts/types.py @@ -4,6 +4,7 @@ import strawberry from strawberry.types import Info +from strawberry.lazy_type import LazyType from reddit.base.types import NodeType from reddit.comments.types import CommentType @@ -64,13 +65,17 @@ class PostType(NodeType): ) @strawberry.field(description="The owner of the post.") - async def owner(self, info: Info) -> UserType: + async def owner( + self, info: Info + ) -> LazyType["UserType", "reddit.users.types"]: # noqa: F821 loader = info.context.get("user_loader") user = await loader.load(self.owner_id) return cast(UserType, user) @strawberry.field(description="The Subreddit of the post.") - async def subreddit(self, info: Info) -> SubredditType: + async def subreddit( + self, info: Info + ) -> LazyType["SubredditType", "reddit.subreddits.types"]: # noqa: F821 loader = info.context.get("subreddit_loader") subreddit = await loader.load(self.subreddit_id) return cast(SubredditType, subreddit) diff --git a/reddit-clone/reddit/subreddits/types.py b/reddit-clone/reddit/subreddits/types.py index 4234a72d..dd2b4999 100644 --- a/reddit-clone/reddit/subreddits/types.py +++ b/reddit-clone/reddit/subreddits/types.py @@ -4,6 +4,7 @@ import strawberry from strawberry.types import Info +from strawberry.lazy_type import LazyType from reddit.base.types import NodeType from reddit.posts.types import PostType @@ -52,7 +53,9 @@ class SubredditType(NodeType): ) @strawberry.field(description="The owner of the Subreddit.") - async def owner(self, info: Info) -> UserType: + async def owner( + self, info: Info + ) -> LazyType["UserType", "reddit.users.types"]: # noqa: F821 loader = info.context.get("user_loader") user = await loader.load(self.owner_id) return cast(UserType, user) From 57fae72a84662af711d319a3de8bd7add023e664 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sun, 17 Oct 2021 09:26:10 +0530 Subject: [PATCH 146/150] use helper: merge_types --- reddit-clone/poetry.lock | 76 +++++++++++++++++++++++++++++++---- reddit-clone/pyproject.toml | 22 +++++----- reddit-clone/reddit/schema.py | 18 ++++----- 3 files changed, 88 insertions(+), 28 deletions(-) diff --git a/reddit-clone/poetry.lock b/reddit-clone/poetry.lock index d5f7a2c7..2a5283e2 100644 --- a/reddit-clone/poetry.lock +++ b/reddit-clone/poetry.lock @@ -36,7 +36,7 @@ vine = "5.0.0" [[package]] name = "anyio" -version = "3.3.3" +version = "3.3.4" description = "High level compatibility layer for multiple asynchronous event loop implementations" category = "main" optional = false @@ -274,6 +274,24 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "fastapi" +version = "0.70.0" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +category = "main" +optional = false +python-versions = ">=3.6.1" + +[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.16.0" + +[package.extras] +all = ["requests (>=2.24.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "itsdangerous (>=1.1.0,<3.0.0)", "pyyaml (>=5.3.1,<6.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.16.0)"] +dev = ["python-jose[cryptography] (>=3.3.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.16.0)"] +doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=7.1.9,<8.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.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,<4.0.0)", "mypy (==0.910)", "flake8 (>=3.8.3,<4.0.0)", "black (==21.9b0)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "orjson (>=3.2.1,<4.0.0)", "ujson (>=4.0.1,<5.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "flask (>=1.1.2,<3.0.0)", "anyio[trio] (>=3.2.1,<4.0.0)", "types-ujson (==0.1.1)", "types-orjson (==3.6.0)", "types-dataclasses (==0.1.7)"] + [[package]] name = "flake8" version = "3.9.2" @@ -583,6 +601,21 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "pydantic" +version = "1.8.2" +description = "Data validation and settings management using python 3.6 type hinting" +category = "main" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +typing-extensions = ">=3.7.4.3" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + [[package]] name = "pyflakes" version = "2.3.1" @@ -765,7 +798,7 @@ full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests", "gra [[package]] name = "strawberry-graphql" -version = "0.82.2" +version = "0.84.0" description = "A library for creating GraphQL APIs" category = "main" optional = false @@ -774,6 +807,7 @@ python-versions = ">=3.7,<4.0" [package.dependencies] cached-property = ">=1.5.2,<2.0.0" click = ">=7.0,<9.0" +fastapi = ">=0.65.2" graphql-core = ">=3.1.0,<3.2.0" pygments = ">=2.3,<3.0" python-dateutil = ">=2.7.0,<3.0.0" @@ -908,7 +942,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "700aac6bc63fb865ad58061cecb0089dd106cc48be7646f6590aefbf748adb96" +content-hash = "32406d1d907beea69d86c7c1fdb7e883d1092c4ab9af451c55412a051e1223d3" [metadata.files] aiofiles = [ @@ -924,8 +958,8 @@ amqp = [ {file = "amqp-5.0.6.tar.gz", hash = "sha256:03e16e94f2b34c31f8bf1206d8ddd3ccaa4c315f7f6a1879b7b1210d229568c2"}, ] anyio = [ - {file = "anyio-3.3.3-py3-none-any.whl", hash = "sha256:56ceaeed2877723578b1341f4f68c29081db189cfb40a97d1922b9513f6d7db6"}, - {file = "anyio-3.3.3.tar.gz", hash = "sha256:8eccec339cb4a856c94a75d50fc1d451faf32a05ef406be462e2efc59c9838b0"}, + {file = "anyio-3.3.4-py3-none-any.whl", hash = "sha256:4fd09a25ab7fa01d34512b7249e366cd10358cdafc95022c7ff8c8f8a5026d66"}, + {file = "anyio-3.3.4.tar.gz", hash = "sha256:67da67b5b21f96b9d3d65daa6ea99f5d5282cb09f50eb4456f8fb51dffefc3ff"}, ] argon2-cffi = [ {file = "argon2-cffi-21.1.0.tar.gz", hash = "sha256:f710b61103d1a1f692ca3ecbd1373e28aa5e545ac625ba067ff2feca1b2bb870"}, @@ -1051,6 +1085,10 @@ colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] +fastapi = [ + {file = "fastapi-0.70.0-py3-none-any.whl", hash = "sha256:a36d5f2fad931aa3575c07a3472c784e81f3e664e3bb5c8b9c88d0ec1104f59c"}, + {file = "fastapi-0.70.0.tar.gz", hash = "sha256:66da43cfe5185ea1df99552acffd201f1832c6b364e0f4136c0a99f933466ced"}, +] flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, @@ -1269,6 +1307,30 @@ pycparser = [ {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, ] +pydantic = [ + {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"}, + {file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"}, + {file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"}, + {file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"}, + {file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"}, + {file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"}, + {file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"}, + {file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"}, + {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"}, + {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"}, +] pyflakes = [ {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, @@ -1431,8 +1493,8 @@ starlette = [ {file = "starlette-0.16.0.tar.gz", hash = "sha256:e1904b5d0007aee24bdd3c43994be9b3b729f4f58e740200de1d623f8c3a8870"}, ] strawberry-graphql = [ - {file = "strawberry-graphql-0.82.2.tar.gz", hash = "sha256:36ed3844d1a7fbaebf22698a72eaeaad2468e5c0ce4463ef678582ed70ca5883"}, - {file = "strawberry_graphql-0.82.2-py3-none-any.whl", hash = "sha256:000f6e74c026e8defab37b6967a1e7491aa646251ddef265369e6ad510088077"}, + {file = "strawberry-graphql-0.84.0.tar.gz", hash = "sha256:c122d657540314a4a5d5366954ed687d4f12a96907395241c3285f597785a934"}, + {file = "strawberry_graphql-0.84.0-py3-none-any.whl", hash = "sha256:f1ddec0ebe75a639bdc16bf4815ce0aaa75c703fe0a0873a6b153adb0800be9b"}, ] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, diff --git a/reddit-clone/pyproject.toml b/reddit-clone/pyproject.toml index 0b6e8593..6b25cb7f 100644 --- a/reddit-clone/pyproject.toml +++ b/reddit-clone/pyproject.toml @@ -6,18 +6,18 @@ authors = ["Aryan Iyappan "] [tool.poetry.dependencies] python = "^3.7" -uvicorn = {extras = ["standard"], version = "^0.15.0"} -SQLAlchemy = {extras = ["mypy"], version = "^1.4.23"} -strawberry-graphql = {extras = ["asgi"], version = "^0.82.2"} -passlib = {extras = ["argon2"], version = "^1.7.4"} -celery = {extras = ["redis", "librabbitmq"], version = "^5.1.2"} -alembic = "^1.7.1" +uvicorn = {extras = ["standard"], version = "^0.15"} +SQLAlchemy = {extras = ["mypy"], version = "^1.4"} +strawberry-graphql = {extras = ["asgi"], version = "^0.84"} +passlib = {extras = ["argon2"], version = "^1.7"} +celery = {extras = ["redis", "librabbitmq"], version = "^5.1"} +alembic = "^1.7" asyncpg = "^0.24" -graphql-relay = "^3.1.0" -schematics = "^2.1.1" -jinja2 = "^3.0.2" -aiofiles = "^0.7.0" -starlette = "^0.16.0" +graphql-relay = "^3.1" +schematics = "^2.1" +jinja2 = "^3.0" +aiofiles = "^0.7" +starlette = "^0.16" [tool.poetry.dev-dependencies] diff --git a/reddit-clone/reddit/schema.py b/reddit-clone/reddit/schema.py index 1298ec23..ca459432 100644 --- a/reddit-clone/reddit/schema.py +++ b/reddit-clone/reddit/schema.py @@ -1,4 +1,5 @@ -import strawberry +from strawberry import Schema +from strawberry.tools import merge_types from reddit.base.queries import BaseQuery from reddit.subreddits.queries import SubredditQuery @@ -10,15 +11,12 @@ __all__ = ("schema",) +Query = merge_types(name="Query", types=(BaseQuery, UserQuery, SubredditQuery)) -@strawberry.type -class Query(BaseQuery, UserQuery, SubredditQuery): - pass +Mutation = merge_types( + name="Mutation", + types=(CommentMutation, PostMutation, UserMutation, SubredditMutation), +) -@strawberry.type -class Mutation(CommentMutation, PostMutation, UserMutation, SubredditMutation): - pass - - -schema = strawberry.Schema(query=Query, mutation=Mutation) +schema = Schema(query=Query, mutation=Mutation) From db85096dbfbcd69e800d50fe67384c405d2a80cd Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Sun, 17 Oct 2021 09:27:04 +0530 Subject: [PATCH 147/150] use helper: merge_types --- reddit-clone/reddit/schema.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/reddit-clone/reddit/schema.py b/reddit-clone/reddit/schema.py index ca459432..d6185f2d 100644 --- a/reddit-clone/reddit/schema.py +++ b/reddit-clone/reddit/schema.py @@ -11,12 +11,11 @@ __all__ = ("schema",) -Query = merge_types(name="Query", types=(BaseQuery, UserQuery, SubredditQuery)) -Mutation = merge_types( - name="Mutation", - types=(CommentMutation, PostMutation, UserMutation, SubredditMutation), +schema = Schema( + query=merge_types(name="Query", types=(BaseQuery, UserQuery, SubredditQuery)), + mutation=merge_types( + name="Mutation", + types=(CommentMutation, PostMutation, UserMutation, SubredditMutation), + ), ) - - -schema = Schema(query=Query, mutation=Mutation) From c6fb4b8bd819cec8229a5d916e1d7789fc3cb638 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Mon, 18 Oct 2021 11:23:02 +0530 Subject: [PATCH 148/150] fix schema fields --- reddit-clone/reddit/__init__.py | 17 +++++----- reddit-clone/reddit/comments/loaders.py | 2 +- .../comments/mutations/comment_create.py | 13 ++------ .../comments/mutations/comment_delete.py | 13 ++------ .../comments/mutations/comment_update.py | 13 ++------ .../reddit/comments/mutations/comment_vote.py | 13 ++------ reddit-clone/reddit/comments/types.py | 6 ++-- reddit-clone/reddit/posts/loaders.py | 2 +- .../reddit/posts/mutations/post_create.py | 11 ++----- .../reddit/posts/mutations/post_delete.py | 11 ++----- .../reddit/posts/mutations/post_update.py | 11 ++----- .../reddit/posts/mutations/post_vote.py | 11 ++----- reddit-clone/reddit/posts/types.py | 6 ++-- reddit-clone/reddit/subreddits/loaders.py | 2 +- .../subreddits/mutations/subreddit_create.py | 11 ++----- .../subreddits/mutations/subreddit_delete.py | 11 ++----- .../subreddits/mutations/subreddit_join.py | 15 +++------ .../subreddits/mutations/subreddit_leave.py | 15 ++++----- .../subreddits/mutations/subreddit_update.py | 11 ++----- reddit-clone/reddit/subreddits/queries.py | 10 ++---- reddit-clone/reddit/subreddits/types.py | 4 +-- reddit-clone/reddit/users/loaders.py | 13 +++++++- .../reddit/users/mutations/authenticate.py | 13 ++------ .../reddit/users/mutations/avatar_remove.py | 11 ++----- .../reddit/users/mutations/email_change.py | 13 +++----- .../users/mutations/email_change_request.py | 15 ++++----- .../reddit/users/mutations/password_reset.py | 13 +++----- .../users/mutations/password_reset_request.py | 15 ++++----- .../reddit/users/mutations/user_create.py | 11 ++----- .../reddit/users/mutations/user_deactivate.py | 11 ++----- .../reddit/users/mutations/user_update.py | 11 ++----- reddit-clone/reddit/users/queries.py | 31 +++++-------------- reddit-clone/reddit/users/types.py | 2 +- 33 files changed, 105 insertions(+), 262 deletions(-) diff --git a/reddit-clone/reddit/__init__.py b/reddit-clone/reddit/__init__.py index de20772a..21bd1d35 100644 --- a/reddit-clone/reddit/__init__.py +++ b/reddit-clone/reddit/__init__.py @@ -9,10 +9,10 @@ from reddit import settings from reddit.schema import schema -from reddit.users.loaders import load_users -from reddit.subreddits.loaders import load_subreddits -from reddit.posts.loaders import load_posts -from reddit.comments.loaders import load_comments +from reddit.users.loaders import load_users_by_id, load_users_by_username +from reddit.subreddits.loaders import load_subreddits_by_id +from reddit.posts.loaders import load_posts_by_id +from reddit.comments.loaders import load_comments_by_id __all__ = ("app",) @@ -23,10 +23,11 @@ async def get_context( ) -> Optional[Any]: context = await super().get_context(request, response=response) context.update( - user_loader=DataLoader(load_fn=load_users), - subreddit_loader=DataLoader(load_fn=load_subreddits), - post_loader=DataLoader(load_fn=load_posts), - comment_loader=DataLoader(load_fn=load_comments), + user_id_loader=DataLoader(load_fn=load_users_by_id), + user_username_loader=DataLoader(load_fn=load_users_by_username), + subreddit_id_loader=DataLoader(load_fn=load_subreddits_by_id), + post_id_loader=DataLoader(load_fn=load_posts_by_id), + comment_id_loader=DataLoader(load_fn=load_comments_by_id), ) return context diff --git a/reddit-clone/reddit/comments/loaders.py b/reddit-clone/reddit/comments/loaders.py index 50b28da0..b69be7c3 100644 --- a/reddit-clone/reddit/comments/loaders.py +++ b/reddit-clone/reddit/comments/loaders.py @@ -7,7 +7,7 @@ from reddit.comments.models import Comment -async def load_comments(comment_ids: List[int]) -> List[Optional[Comment]]: +async def load_comments_by_id(comment_ids: List[int]) -> List[Optional[Comment]]: """ Batch-loads comments by their IDs. """ diff --git a/reddit-clone/reddit/comments/mutations/comment_create.py b/reddit-clone/reddit/comments/mutations/comment_create.py index 81143ae4..3419e0cc 100644 --- a/reddit-clone/reddit/comments/mutations/comment_create.py +++ b/reddit-clone/reddit/comments/mutations/comment_create.py @@ -29,15 +29,6 @@ class CommentCreateError: CommentCreateResult = Union[CommentCreateSuccess, CommentCreateError] -async def resolve_comment_create( - info: Info, input: CommentCreateInput -) -> CommentCreateResult: +@strawberry.field(description="Creates a new comment on a post.") +async def comment_create(info: Info, input: CommentCreateInput) -> CommentCreateResult: pass - - -comment_create = strawberry.mutation( - resolver=resolve_comment_create, - description=""" - Creates a new comment on a post. - """, -) diff --git a/reddit-clone/reddit/comments/mutations/comment_delete.py b/reddit-clone/reddit/comments/mutations/comment_delete.py index 4b903360..ba3c1451 100644 --- a/reddit-clone/reddit/comments/mutations/comment_delete.py +++ b/reddit-clone/reddit/comments/mutations/comment_delete.py @@ -29,15 +29,6 @@ class CommentDeleteError: CommentDeleteResult = Union[CommentDeleteSuccess, CommentDeleteError] -async def resolve_comment_delete( - info: Info, input: CommentDeleteInput -) -> CommentDeleteResult: +@strawberry.field(description="Deletes a comment on a post.") +async def comment_delete(info: Info, input: CommentDeleteInput) -> CommentDeleteResult: pass - - -comment_delete = strawberry.mutation( - resolver=resolve_comment_delete, - description=""" - Deletes a comment on a post. - """, -) diff --git a/reddit-clone/reddit/comments/mutations/comment_update.py b/reddit-clone/reddit/comments/mutations/comment_update.py index 2d5e738a..6a80c2c3 100644 --- a/reddit-clone/reddit/comments/mutations/comment_update.py +++ b/reddit-clone/reddit/comments/mutations/comment_update.py @@ -30,15 +30,6 @@ class CommentUpdateError: CommentUpdateResult = Union[CommentUpdateSuccess, CommentUpdateError] -async def resolve_comment_update( - info: Info, input: CommentUpdateInput -) -> CommentUpdateResult: +@strawberry.field(description="Updates a comment on a post.") +async def comment_update(info: Info, input: CommentUpdateInput) -> CommentUpdateResult: pass - - -comment_update = strawberry.mutation( - resolver=resolve_comment_update, - description=""" - Updates a comment on a post. - """, -) diff --git a/reddit-clone/reddit/comments/mutations/comment_vote.py b/reddit-clone/reddit/comments/mutations/comment_vote.py index 5912656b..6ff8213e 100644 --- a/reddit-clone/reddit/comments/mutations/comment_vote.py +++ b/reddit-clone/reddit/comments/mutations/comment_vote.py @@ -29,15 +29,6 @@ class CommentVoteError: CommentVoteResult = Union[CommentVoteSuccess, CommentVoteError] -async def resolve_comment_vote( - info: Info, input: CommentVoteInput -) -> CommentVoteResult: +@strawberry.field(description="Creates a vote on a comment.") +async def comment_vote(info: Info, input: CommentVoteInput) -> CommentVoteResult: pass - - -comment_vote = strawberry.mutation( - resolver=resolve_comment_vote, - description=""" - Creates a vote on a comment. - """, -) diff --git a/reddit-clone/reddit/comments/types.py b/reddit-clone/reddit/comments/types.py index 51616264..5947161f 100644 --- a/reddit-clone/reddit/comments/types.py +++ b/reddit-clone/reddit/comments/types.py @@ -49,7 +49,7 @@ class CommentType(NodeType): async def owner( self, info: Info ) -> LazyType["UserType", "reddit.users.types"]: # noqa: F821 - loader = info.context.get("user_loader") + loader = info.context.get("user_id_loader") user = await loader.load(self.owner_id) return cast(UserType, user) @@ -57,7 +57,7 @@ async def owner( async def post( self, info: Info ) -> LazyType["PostType", "reddit.posts.types"]: # noqa: F821 - loader = info.context.get("post_loader") + loader = info.context.get("post_id_loader") post = await loader.load(self.post_id) return cast(PostType, post) @@ -66,6 +66,6 @@ async def resolve_node(cls, info: Info, comment_id: str) -> Optional[CommentType """ Gets a comment with the given ID. """ - loader = info.context.get("comment_loader") + loader = info.context.get("comment_id_loader") comment = await loader.load(comment_id) return cast(CommentType, comment) diff --git a/reddit-clone/reddit/posts/loaders.py b/reddit-clone/reddit/posts/loaders.py index bf8cb95e..66dade6d 100644 --- a/reddit-clone/reddit/posts/loaders.py +++ b/reddit-clone/reddit/posts/loaders.py @@ -7,7 +7,7 @@ from reddit.posts.models import Post -async def load_posts(post_ids: List[int]) -> List[Optional[Post]]: +async def load_posts_by_id(post_ids: List[int]) -> List[Optional[Post]]: """ Batch-loads posts by their IDs. """ diff --git a/reddit-clone/reddit/posts/mutations/post_create.py b/reddit-clone/reddit/posts/mutations/post_create.py index 2bec4f6b..5fcdace5 100644 --- a/reddit-clone/reddit/posts/mutations/post_create.py +++ b/reddit-clone/reddit/posts/mutations/post_create.py @@ -29,13 +29,6 @@ class PostCreateError: PostCreateResult = Union[PostCreateSuccess, PostCreateError] -async def resolve_post_create(info: Info, input: PostCreateInput) -> PostCreateResult: +@strawberry.mutation(description="Creates a new post in a subreddit.") +async def post_create(info: Info, input: PostCreateInput) -> PostCreateResult: pass - - -post_create = strawberry.mutation( - resolver=resolve_post_create, - description=""" - Creates a new post in a Subreddit. - """, -) diff --git a/reddit-clone/reddit/posts/mutations/post_delete.py b/reddit-clone/reddit/posts/mutations/post_delete.py index 369967c2..31c578f9 100644 --- a/reddit-clone/reddit/posts/mutations/post_delete.py +++ b/reddit-clone/reddit/posts/mutations/post_delete.py @@ -28,13 +28,6 @@ class PostDeleteError: PostDeleteResult = Union[PostDeleteSuccess, PostDeleteError] -async def resolve_post_delete(info: Info, input: PostDeleteInput) -> PostDeleteResult: +@strawberry.mutation(description="Deletes a post in a subreddit.") +async def post_delete(info: Info, input: PostDeleteInput) -> PostDeleteResult: pass - - -post_delete = strawberry.mutation( - resolver=resolve_post_delete, - description=""" - Deletes a post in a Subreddit. - """, -) diff --git a/reddit-clone/reddit/posts/mutations/post_update.py b/reddit-clone/reddit/posts/mutations/post_update.py index 3e318437..c0d0df86 100644 --- a/reddit-clone/reddit/posts/mutations/post_update.py +++ b/reddit-clone/reddit/posts/mutations/post_update.py @@ -30,13 +30,6 @@ class PostUpdateError: PostUpdateResult = Union[PostUpdateSuccess, PostUpdateError] -async def resolve_post_update(info: Info, input: PostUpdateInput) -> PostUpdateResult: +@strawberry.mutation(description="Updates a post in a subreddit.") +async def post_update(info: Info, input: PostUpdateInput) -> PostUpdateResult: pass - - -post_update = strawberry.mutation( - resolver=resolve_post_update, - description=""" - Updates a post in a Subreddit. - """, -) diff --git a/reddit-clone/reddit/posts/mutations/post_vote.py b/reddit-clone/reddit/posts/mutations/post_vote.py index 4d82fcea..4e434a33 100644 --- a/reddit-clone/reddit/posts/mutations/post_vote.py +++ b/reddit-clone/reddit/posts/mutations/post_vote.py @@ -28,13 +28,6 @@ class PostVoteError: PostVoteResult = Union[PostVoteSuccess, PostVoteError] -async def resolve_post_vote(info: Info, input: PostVoteInput) -> PostVoteResult: +@strawberry.mutation(description="Creates a vote on a post.") +async def post_vote(info: Info, input: PostVoteInput) -> PostVoteResult: pass - - -post_vote = strawberry.mutation( - resolver=resolve_post_vote, - description=""" - Creates a vote on a post. - """, -) diff --git a/reddit-clone/reddit/posts/types.py b/reddit-clone/reddit/posts/types.py index b1d97fa8..14a7665d 100644 --- a/reddit-clone/reddit/posts/types.py +++ b/reddit-clone/reddit/posts/types.py @@ -68,7 +68,7 @@ class PostType(NodeType): async def owner( self, info: Info ) -> LazyType["UserType", "reddit.users.types"]: # noqa: F821 - loader = info.context.get("user_loader") + loader = info.context.get("user_id_loader") user = await loader.load(self.owner_id) return cast(UserType, user) @@ -76,7 +76,7 @@ async def owner( async def subreddit( self, info: Info ) -> LazyType["SubredditType", "reddit.subreddits.types"]: # noqa: F821 - loader = info.context.get("subreddit_loader") + loader = info.context.get("subreddit_id_loader") subreddit = await loader.load(self.subreddit_id) return cast(SubredditType, subreddit) @@ -85,6 +85,6 @@ async def resolve_node(cls, info: Info, post_id: str) -> Optional[PostType]: """ Gets a post with the given ID. """ - loader = info.context.get("post_loader") + loader = info.context.get("post_id_loader") post = await loader.load(post_id) return cast(PostType, post) diff --git a/reddit-clone/reddit/subreddits/loaders.py b/reddit-clone/reddit/subreddits/loaders.py index 39d96a3a..c1978b33 100644 --- a/reddit-clone/reddit/subreddits/loaders.py +++ b/reddit-clone/reddit/subreddits/loaders.py @@ -7,7 +7,7 @@ from reddit.subreddits.models import Subreddit -async def load_subreddits(subreddit_ids: List[int]) -> List[Optional[Subreddit]]: +async def load_subreddits_by_id(subreddit_ids: List[int]) -> List[Optional[Subreddit]]: """ Batch-loads subreddits by their IDs. """ diff --git a/reddit-clone/reddit/subreddits/mutations/subreddit_create.py b/reddit-clone/reddit/subreddits/mutations/subreddit_create.py index 0625b8a9..8fd4e0f2 100644 --- a/reddit-clone/reddit/subreddits/mutations/subreddit_create.py +++ b/reddit-clone/reddit/subreddits/mutations/subreddit_create.py @@ -28,15 +28,8 @@ class SubredditCreateError: SubredditCreateResult = Union[SubredditCreateSuccess, SubredditCreateError] -async def resolve_subreddit_create( +@strawberry.mutation(description="Creates a new subreddit.") +async def subreddit_create( info: Info, input: SubredditCreateInput ) -> SubredditCreateResult: pass - - -subreddit_create = strawberry.mutation( - resolver=resolve_subreddit_create, - description=""" - Creates a new Subreddit. - """, -) diff --git a/reddit-clone/reddit/subreddits/mutations/subreddit_delete.py b/reddit-clone/reddit/subreddits/mutations/subreddit_delete.py index 4ce08dc9..39bd8250 100644 --- a/reddit-clone/reddit/subreddits/mutations/subreddit_delete.py +++ b/reddit-clone/reddit/subreddits/mutations/subreddit_delete.py @@ -27,15 +27,8 @@ class SubredditDeleteError: SubredditDeleteResult = Union[SubredditDeleteSuccess, SubredditDeleteError] -async def resolve_subreddit_delete( +@strawberry.mutation(description="Deletes a subreddit.") +async def subreddit_delete( info: Info, input: SubredditDeleteInput ) -> SubredditDeleteResult: pass - - -subreddit_delete = strawberry.mutation( - resolver=resolve_subreddit_delete, - description=""" - Deletes a Subreddit. - """, -) diff --git a/reddit-clone/reddit/subreddits/mutations/subreddit_join.py b/reddit-clone/reddit/subreddits/mutations/subreddit_join.py index a81c9b57..1bbe9939 100644 --- a/reddit-clone/reddit/subreddits/mutations/subreddit_join.py +++ b/reddit-clone/reddit/subreddits/mutations/subreddit_join.py @@ -27,15 +27,10 @@ class SubredditJoinError: SubredditJoinResult = Union[SubredditJoinSuccess, SubredditJoinError] -async def resolve_subreddit_join( - info: Info, input: SubredditJoinInput -) -> SubredditJoinResult: - pass - - -subreddit_join = strawberry.mutation( - resolver=resolve_subreddit_join, +@strawberry.mutation( description=""" - Creates a new Subreddit-User relationship. - """, + Creates a new subreddit-user relationship. + """ ) +async def subreddit_join(info: Info, input: SubredditJoinInput) -> SubredditJoinResult: + pass diff --git a/reddit-clone/reddit/subreddits/mutations/subreddit_leave.py b/reddit-clone/reddit/subreddits/mutations/subreddit_leave.py index 582be748..7ca43834 100644 --- a/reddit-clone/reddit/subreddits/mutations/subreddit_leave.py +++ b/reddit-clone/reddit/subreddits/mutations/subreddit_leave.py @@ -27,15 +27,12 @@ class SubredditLeaveError: SubredditLeaveResult = Union[SubredditLeaveSuccess, SubredditLeaveError] -async def resolve_subreddit_leave( +@strawberry.field( + description=""" + Deletes a subreddit-user relationship. + """ +) +async def subreddit_leave( info: Info, input: SubredditLeaveInput ) -> SubredditLeaveResult: pass - - -subreddit_leave = strawberry.mutation( - resolver=resolve_subreddit_leave, - description=""" - Deletes a Subreddit-User relationship. - """, -) diff --git a/reddit-clone/reddit/subreddits/mutations/subreddit_update.py b/reddit-clone/reddit/subreddits/mutations/subreddit_update.py index 0c97b382..ed4d3633 100644 --- a/reddit-clone/reddit/subreddits/mutations/subreddit_update.py +++ b/reddit-clone/reddit/subreddits/mutations/subreddit_update.py @@ -29,15 +29,8 @@ class SubredditUpdateError: SubredditUpdateResult = Union[SubredditUpdateSuccess, SubredditUpdateError] -async def resolve_subreddit_update( +@strawberry.mutation(description="Updates a subreddit.") +async def subreddit_update( info: Info, input: SubredditUpdateInput ) -> SubredditUpdateResult: pass - - -subreddit_update = strawberry.mutation( - resolver=resolve_subreddit_update, - description=""" - Updates a Subreddit. - """, -) diff --git a/reddit-clone/reddit/subreddits/queries.py b/reddit-clone/reddit/subreddits/queries.py index 77998552..6b1ad2b4 100644 --- a/reddit-clone/reddit/subreddits/queries.py +++ b/reddit-clone/reddit/subreddits/queries.py @@ -7,15 +7,9 @@ from reddit.subreddits.types import SubredditType -async def resolve_subreddits(info: Info) -> List[SubredditType]: +@strawberry.field(description="Gets the available subreddits.") +async def subreddits(info: Info) -> List[SubredditType]: pass -subreddits = strawberry.field( - resolver=resolve_subreddits, - description=""" - Gets the available subreddits. - """, -) - SubredditQuery = create_type(name="SubredditQuery", fields=(subreddits,)) diff --git a/reddit-clone/reddit/subreddits/types.py b/reddit-clone/reddit/subreddits/types.py index dd2b4999..873fa26f 100644 --- a/reddit-clone/reddit/subreddits/types.py +++ b/reddit-clone/reddit/subreddits/types.py @@ -56,7 +56,7 @@ class SubredditType(NodeType): async def owner( self, info: Info ) -> LazyType["UserType", "reddit.users.types"]: # noqa: F821 - loader = info.context.get("user_loader") + loader = info.context.get("user_id_loader") user = await loader.load(self.owner_id) return cast(UserType, user) @@ -67,6 +67,6 @@ async def resolve_node( """ Gets a Subreddit with the given ID. """ - loader = info.context.get("subreddit_loader") + loader = info.context.get("subreddit_id_loader") subreddit = await loader.load(subreddit_id) return cast(SubredditType, subreddit) diff --git a/reddit-clone/reddit/users/loaders.py b/reddit-clone/reddit/users/loaders.py index 36617a1c..b9e801bf 100644 --- a/reddit-clone/reddit/users/loaders.py +++ b/reddit-clone/reddit/users/loaders.py @@ -7,7 +7,7 @@ from reddit.users.models import User -async def load_users(user_ids: List[int]) -> List[Optional[User]]: +async def load_users_by_id(user_ids: List[int]) -> List[Optional[User]]: """ Batch-loads users by their IDs. """ @@ -16,3 +16,14 @@ async def load_users(user_ids: List[int]) -> List[Optional[User]]: result: Result = await session.execute(query) user_map: Dict[int, User] = {user.id: user for user in result.scalars()} return [user_map.get(user_id) for user_id in user_ids] + + +async def load_users_by_username(usernames: List[str]) -> List[Optional[User]]: + """ + Batch-loads users by their usernames. + """ + query = select(User).filter(User.username.in_(usernames)) + async with get_session() as session: + result: Result = await session.execute(query) + user_map: Dict[str, User] = {user.username: user for user in result.scalars()} + return [user_map.get(username) for username in usernames] diff --git a/reddit-clone/reddit/users/mutations/authenticate.py b/reddit-clone/reddit/users/mutations/authenticate.py index 8983d5b8..4d9a34d5 100644 --- a/reddit-clone/reddit/users/mutations/authenticate.py +++ b/reddit-clone/reddit/users/mutations/authenticate.py @@ -28,15 +28,6 @@ class AuthenticateError: AuthenticateResult = Union[AuthenticateSuccess, AuthenticateError] -async def resolve_authenticate( - info: Info, input: AuthenticateInput -) -> AuthenticateResult: +@strawberry.mutation(description="Logs the current user in.") +async def authenticate(info: Info, input: AuthenticateInput) -> AuthenticateResult: pass - - -authenticate = strawberry.mutation( - resolver=resolve_authenticate, - description=""" - Logs the current user in. - """, -) diff --git a/reddit-clone/reddit/users/mutations/avatar_remove.py b/reddit-clone/reddit/users/mutations/avatar_remove.py index 9505484f..5d4f64f7 100644 --- a/reddit-clone/reddit/users/mutations/avatar_remove.py +++ b/reddit-clone/reddit/users/mutations/avatar_remove.py @@ -21,13 +21,6 @@ class AvatarRemoveError: AvatarRemoveResult = Union[AvatarRemoveSuccess, AvatarRemoveError] -async def resolve_avatar_remove(info: Info) -> AvatarRemoveResult: +@strawberry.mutation(description="Removes the current user's avatar.") +async def avatar_remove(info: Info) -> AvatarRemoveResult: pass - - -avatar_remove = strawberry.mutation( - resolver=resolve_avatar_remove, - description=""" - Removes the current user's avatar. - """, -) diff --git a/reddit-clone/reddit/users/mutations/email_change.py b/reddit-clone/reddit/users/mutations/email_change.py index 5abd905d..4648e5d4 100644 --- a/reddit-clone/reddit/users/mutations/email_change.py +++ b/reddit-clone/reddit/users/mutations/email_change.py @@ -28,16 +28,11 @@ class EmailChangeError: EmailChangeResult = Union[EmailChangeSuccess, EmailChangeError] -async def resolve_email_change( - info: Info, input: EmailChangeInput -) -> EmailChangeResult: - pass - - -email_change = strawberry.mutation( - resolver=resolve_email_change, +@strawberry.mutation( description=""" Changes the email for the user account associated with the given email. - """, + """ ) +async def email_change(info: Info, input: EmailChangeInput) -> EmailChangeResult: + pass diff --git a/reddit-clone/reddit/users/mutations/email_change_request.py b/reddit-clone/reddit/users/mutations/email_change_request.py index b16f3a60..e530da4c 100644 --- a/reddit-clone/reddit/users/mutations/email_change_request.py +++ b/reddit-clone/reddit/users/mutations/email_change_request.py @@ -28,16 +28,13 @@ class EmailChangeRequestError: EmailChangeRequestResult = Union[EmailChangeRequestSuccess, EmailChangeRequestError] -async def resolve_email_change_request( - info: Info, input: EmailChangeRequestInput -) -> EmailChangeRequestResult: - pass - - -email_change_request = strawberry.mutation( - resolver=resolve_email_change_request, +@strawberry.mutation( description=""" Sends an email change code to the given email address. - """, + """ ) +async def email_change_request( + info: Info, input: EmailChangeRequestInput +) -> EmailChangeRequestResult: + pass diff --git a/reddit-clone/reddit/users/mutations/password_reset.py b/reddit-clone/reddit/users/mutations/password_reset.py index 16e25ebf..7b2f68c0 100644 --- a/reddit-clone/reddit/users/mutations/password_reset.py +++ b/reddit-clone/reddit/users/mutations/password_reset.py @@ -28,16 +28,11 @@ class PasswordResetError: PasswordResetResult = Union[PasswordResetSuccess, PasswordResetError] -async def resolve_password_reset( - info: Info, input: PasswordResetInput -) -> PasswordResetResult: - pass - - -password_reset = strawberry.mutation( - resolver=resolve_password_reset, +@strawberry.mutation( description=""" Resets the password for the user account associated with the given email. - """, + """ ) +async def password_reset(info: Info, input: PasswordResetInput) -> PasswordResetResult: + pass diff --git a/reddit-clone/reddit/users/mutations/password_reset_request.py b/reddit-clone/reddit/users/mutations/password_reset_request.py index 6e2104ef..d23af8f9 100644 --- a/reddit-clone/reddit/users/mutations/password_reset_request.py +++ b/reddit-clone/reddit/users/mutations/password_reset_request.py @@ -28,16 +28,13 @@ class PasswordResetRequestError: ] -async def resolve_password_reset_request( - info: Info, input: PasswordResetRequestInput -) -> PasswordResetRequestResult: - pass - - -password_reset_request = strawberry.mutation( - resolver=resolve_password_reset_request, +@strawberry.mutation( description=""" Sends a password reset code to the provided email, if it actually exists. - """, + """ ) +async def password_reset_request( + info: Info, input: PasswordResetRequestInput +) -> PasswordResetRequestResult: + pass diff --git a/reddit-clone/reddit/users/mutations/user_create.py b/reddit-clone/reddit/users/mutations/user_create.py index 30ca3003..d40164c8 100644 --- a/reddit-clone/reddit/users/mutations/user_create.py +++ b/reddit-clone/reddit/users/mutations/user_create.py @@ -28,13 +28,6 @@ class UserCreateError: UserCreateResult = Union[UserCreateSuccess, UserCreateError] -async def resolve_user_create(info: Info, input: UserCreateInput) -> UserCreateResult: +@strawberry.mutation(description="Creates a new user.") +async def user_create(info: Info, input: UserCreateInput) -> UserCreateResult: pass - - -user_create = strawberry.mutation( - resolver=resolve_user_create, - description=""" - Creates a new user. - """, -) diff --git a/reddit-clone/reddit/users/mutations/user_deactivate.py b/reddit-clone/reddit/users/mutations/user_deactivate.py index 8d339687..d2825ab7 100644 --- a/reddit-clone/reddit/users/mutations/user_deactivate.py +++ b/reddit-clone/reddit/users/mutations/user_deactivate.py @@ -26,15 +26,8 @@ class UserDeactivateError: UserDeactivateResult = Union[UserDeactivateSuccess, UserDeactivateError] -async def resolve_user_deactivate( +@strawberry.mutation(description="Deactivates the current user.") +async def user_deactivate( info: Info, input: UserDeactivateInput ) -> UserDeactivateResult: pass - - -user_deactivate = strawberry.mutation( - resolver=resolve_user_deactivate, - description=""" - Deactivates the current user. - """, -) diff --git a/reddit-clone/reddit/users/mutations/user_update.py b/reddit-clone/reddit/users/mutations/user_update.py index 3740cebb..8f2fea98 100644 --- a/reddit-clone/reddit/users/mutations/user_update.py +++ b/reddit-clone/reddit/users/mutations/user_update.py @@ -28,13 +28,6 @@ class UserUpdateError: UserUpdateResult = Union[UserUpdateSuccess, UserUpdateError] -async def resolve_user_update(info: Info, input: UserUpdateInput) -> UserUpdateResult: +@strawberry.mutation(description="Updates the current user.") +async def user_update(info: Info, input: UserUpdateInput) -> UserUpdateResult: pass - - -user_update = strawberry.mutation( - resolver=resolve_user_update, - description=""" - Updates the current user. - """, -) diff --git a/reddit-clone/reddit/users/queries.py b/reddit-clone/reddit/users/queries.py index a027af9f..129e5978 100644 --- a/reddit-clone/reddit/users/queries.py +++ b/reddit-clone/reddit/users/queries.py @@ -4,36 +4,19 @@ from strawberry.tools import create_type from strawberry.types import Info -from reddit.database import get_session from reddit.users.types import UserType -from reddit.users.services import user_by_username -async def resolve_user(info: Info, username: str) -> Optional[UserType]: - async with get_session() as session: - user = await user_by_username(session=session, username=username) - if user is not None: - return cast(UserType, user) - return user +@strawberry.field(description="Gets an user by username.") +async def user(info: Info, username: str) -> Optional[UserType]: + loader = info.context.get("user_username_loader") + user = await loader.load(username) + return cast(UserType, user) -user = strawberry.field( - resolver=resolve_user, - description=""" - Gets an user by username. - """, -) - - -async def resolve_current_user(info: Info) -> Optional[UserType]: +@strawberry.field(description="Gets the current user.") +async def current_user(info: Info) -> Optional[UserType]: pass -current_user = strawberry.field( - resolver=resolve_current_user, - description=""" - Gets the current user. - """, -) - UserQuery = create_type(name="UserQuery", fields=(user, current_user)) diff --git a/reddit-clone/reddit/users/types.py b/reddit-clone/reddit/users/types.py index 726b3aff..ba68aa05 100644 --- a/reddit-clone/reddit/users/types.py +++ b/reddit-clone/reddit/users/types.py @@ -48,6 +48,6 @@ async def resolve_node(cls, info: Info, user_id: str) -> Optional[UserType]: """ Gets an user with the given ID. """ - loader = info.context.get("user_loader") + loader = info.context.get("user_id_loader") user = await loader.load(user_id) return cast(UserType, user) From 311b93d3e6a4070fa71e855baf4b061b3d0710f4 Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Tue, 19 Oct 2021 20:35:39 +0530 Subject: [PATCH 149/150] use marshmallow for serialization --- reddit-clone/README.md | 2 +- reddit-clone/docker-compose.yml | 2 +- reddit-clone/poetry.lock | 101 ++++-------------- reddit-clone/pyproject.toml | 2 +- reddit-clone/reddit/base/types.py | 1 + .../reddit/users/mutations/user_create.py | 14 ++- reddit-clone/reddit/users/serializers.py | 37 +++++++ 7 files changed, 77 insertions(+), 82 deletions(-) create mode 100644 reddit-clone/reddit/users/serializers.py diff --git a/reddit-clone/README.md b/reddit-clone/README.md index 0ab48163..16deb4d3 100644 --- a/reddit-clone/README.md +++ b/reddit-clone/README.md @@ -13,7 +13,7 @@ most of the features that Strawberry gives us. - [SQLAlchemy](https://github.com/sqlalchemy/sqlalchemy) core/ mapper (asyncio) - [Alembic](https://github.com/sqlalchemy/alembic) migrations - [PostgreSQL](https://github.com/postgres/postgres) database server -- [Schematics](https://github.com/schematics/schematics) data validation +- [Marshmallow](https://github.com/marshmallow-code/marshmallow) data validation - [Celery](https://github.com/celery/celery) tasks ([Redis](https://github.com/redis/redis) store, [RabbitMQ](https://github.com/rabbitmq/rabbitmq-server) broker) ## Features at a glance diff --git a/reddit-clone/docker-compose.yml b/reddit-clone/docker-compose.yml index cf776202..409acd13 100644 --- a/reddit-clone/docker-compose.yml +++ b/reddit-clone/docker-compose.yml @@ -3,7 +3,7 @@ x-environment: &base-environment DEBUG: "false" CELERY_BROKER: amqp://reddit:reddit@rabbitmq:5672/ CELERY_BACKEND: redis://:reddit@localhost:6379/ - DATABASE_URL: postgresql+asyncpg://reddit:reddit@localhost:5432/reddit/ + DATABASE_URL: postgresql+asyncpg://reddit:reddit@postgres:5432/reddit/ MAIL_HOST: 127.0.0.1 MAIL_PORT: 25 MAIL_USERNAME: diff --git a/reddit-clone/poetry.lock b/reddit-clone/poetry.lock index 2a5283e2..012f68fc 100644 --- a/reddit-clone/poetry.lock +++ b/reddit-clone/poetry.lock @@ -274,24 +274,6 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -[[package]] -name = "fastapi" -version = "0.70.0" -description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" -category = "main" -optional = false -python-versions = ">=3.6.1" - -[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.16.0" - -[package.extras] -all = ["requests (>=2.24.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "itsdangerous (>=1.1.0,<3.0.0)", "pyyaml (>=5.3.1,<6.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.16.0)"] -dev = ["python-jose[cryptography] (>=3.3.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.16.0)"] -doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=7.1.9,<8.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.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,<4.0.0)", "mypy (==0.910)", "flake8 (>=3.8.3,<4.0.0)", "black (==21.9b0)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "orjson (>=3.2.1,<4.0.0)", "ujson (>=4.0.1,<5.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "flask (>=1.1.2,<3.0.0)", "anyio[trio] (>=3.2.1,<4.0.0)", "types-ujson (==0.1.1)", "types-orjson (==3.6.0)", "types-dataclasses (==0.1.7)"] - [[package]] name = "flake8" version = "3.9.2" @@ -503,6 +485,20 @@ category = "main" optional = false python-versions = ">=3.6" +[[package]] +name = "marshmallow" +version = "3.14.0" +description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +dev = ["pytest", "pytz", "simplejson", "mypy (==0.910)", "flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "pre-commit (>=2.4,<3.0)", "tox"] +docs = ["sphinx (==4.2.0)", "sphinx-issues (==1.2.0)", "alabaster (==0.7.12)", "sphinx-version-warning (==1.1.2)", "autodocsumm (==0.2.7)"] +lint = ["mypy (==0.910)", "flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "pre-commit (>=2.4,<3.0)"] +tests = ["pytest", "pytz", "simplejson"] + [[package]] name = "mccabe" version = "0.6.1" @@ -601,21 +597,6 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[[package]] -name = "pydantic" -version = "1.8.2" -description = "Data validation and settings management using python 3.6 type hinting" -category = "main" -optional = false -python-versions = ">=3.6.1" - -[package.dependencies] -typing-extensions = ">=3.7.4.3" - -[package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] - [[package]] name = "pyflakes" version = "2.3.1" @@ -700,14 +681,6 @@ category = "dev" optional = false python-versions = "*" -[[package]] -name = "schematics" -version = "2.1.1" -description = "Python Data Structures for Humans" -category = "main" -optional = false -python-versions = "*" - [[package]] name = "sentinel" version = "0.3.0" @@ -798,7 +771,7 @@ full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests", "gra [[package]] name = "strawberry-graphql" -version = "0.84.0" +version = "0.84.2" description = "A library for creating GraphQL APIs" category = "main" optional = false @@ -807,7 +780,6 @@ python-versions = ">=3.7,<4.0" [package.dependencies] cached-property = ">=1.5.2,<2.0.0" click = ">=7.0,<9.0" -fastapi = ">=0.65.2" graphql-core = ">=3.1.0,<3.2.0" pygments = ">=2.3,<3.0" python-dateutil = ">=2.7.0,<3.0.0" @@ -825,6 +797,7 @@ opentelemetry = ["opentelemetry-api (<2)", "opentelemetry-sdk (<2)"] pydantic = ["pydantic (<2)"] sanic = ["sanic (>=20.12.2,<22.0.0)"] aiohttp = ["aiohttp (>=3.7.4.post0,<4.0.0)"] +fastapi = ["fastapi (>=0.65.2)"] [[package]] name = "toml" @@ -942,7 +915,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "32406d1d907beea69d86c7c1fdb7e883d1092c4ab9af451c55412a051e1223d3" +content-hash = "342061d49c9020a1037e202b9786df0be94a2e1febcc0010c875e99e70eecebe" [metadata.files] aiofiles = [ @@ -1085,10 +1058,6 @@ colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] -fastapi = [ - {file = "fastapi-0.70.0-py3-none-any.whl", hash = "sha256:a36d5f2fad931aa3575c07a3472c784e81f3e664e3bb5c8b9c88d0ec1104f59c"}, - {file = "fastapi-0.70.0.tar.gz", hash = "sha256:66da43cfe5185ea1df99552acffd201f1832c6b364e0f4136c0a99f933466ced"}, -] flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, @@ -1250,6 +1219,10 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, ] +marshmallow = [ + {file = "marshmallow-3.14.0-py3-none-any.whl", hash = "sha256:6d00e42d6d6289f8cd3e77618a01689d57a078fe324ee579b00fa206d32e9b07"}, + {file = "marshmallow-3.14.0.tar.gz", hash = "sha256:bba1a940985c052c5cc7849f97da196ebc81f3b85ec10c56ef1f3228aa9cbe74"}, +] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, @@ -1307,30 +1280,6 @@ pycparser = [ {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, ] -pydantic = [ - {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"}, - {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"}, - {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"}, - {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"}, - {file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"}, - {file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"}, - {file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"}, - {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"}, - {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"}, - {file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"}, - {file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"}, - {file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"}, - {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"}, - {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"}, - {file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"}, - {file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"}, - {file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"}, - {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"}, - {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"}, - {file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"}, - {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"}, - {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"}, -] pyflakes = [ {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, @@ -1436,10 +1385,6 @@ regex = [ {file = "regex-2021.10.8-cp39-cp39-win_amd64.whl", hash = "sha256:b04e512eb628ea82ed86eb31c0f7fc6842b46bf2601b66b1356a7008327f7700"}, {file = "regex-2021.10.8.tar.gz", hash = "sha256:26895d7c9bbda5c52b3635ce5991caa90fbb1ddfac9c9ff1c7ce505e2282fb2a"}, ] -schematics = [ - {file = "schematics-2.1.1-py2.py3-none-any.whl", hash = "sha256:be2d451bfb86789975e5ec0864aec569b63cea9010f0d24cbbd992a4e564c647"}, - {file = "schematics-2.1.1.tar.gz", hash = "sha256:34c87f51a25063bb498ae1cc201891b134cfcb329baf9e9f4f3ae869b767560f"}, -] sentinel = [ {file = "sentinel-0.3.0-py3-none-any.whl", hash = "sha256:bd8710dd26752039c668604f6be2aaf741b56f7811c5924a4dcdfd74359244f3"}, {file = "sentinel-0.3.0.tar.gz", hash = "sha256:f28143aa4716dbc8f6193f5682176a3c33cd26aaae05d9ecf66c186a9887cc2d"}, @@ -1493,8 +1438,8 @@ starlette = [ {file = "starlette-0.16.0.tar.gz", hash = "sha256:e1904b5d0007aee24bdd3c43994be9b3b729f4f58e740200de1d623f8c3a8870"}, ] strawberry-graphql = [ - {file = "strawberry-graphql-0.84.0.tar.gz", hash = "sha256:c122d657540314a4a5d5366954ed687d4f12a96907395241c3285f597785a934"}, - {file = "strawberry_graphql-0.84.0-py3-none-any.whl", hash = "sha256:f1ddec0ebe75a639bdc16bf4815ce0aaa75c703fe0a0873a6b153adb0800be9b"}, + {file = "strawberry-graphql-0.84.2.tar.gz", hash = "sha256:20dd45ce98b2e1d0a03186c1beda0faaade95c8bdde0f77cdda2ebc0ba22e8cf"}, + {file = "strawberry_graphql-0.84.2-py3-none-any.whl", hash = "sha256:b1f80788e783f0f4213a17055fd26107c34b5c2731bcdee2b97634c43a391a4a"}, ] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, diff --git a/reddit-clone/pyproject.toml b/reddit-clone/pyproject.toml index 6b25cb7f..df2abfca 100644 --- a/reddit-clone/pyproject.toml +++ b/reddit-clone/pyproject.toml @@ -14,10 +14,10 @@ celery = {extras = ["redis", "librabbitmq"], version = "^5.1"} alembic = "^1.7" asyncpg = "^0.24" graphql-relay = "^3.1" -schematics = "^2.1" jinja2 = "^3.0" aiofiles = "^0.7" starlette = "^0.16" +marshmallow = "^3.14.0" [tool.poetry.dev-dependencies] diff --git a/reddit-clone/reddit/base/types.py b/reddit-clone/reddit/base/types.py index 6c33bf37..f9323022 100644 --- a/reddit-clone/reddit/base/types.py +++ b/reddit-clone/reddit/base/types.py @@ -7,6 +7,7 @@ @strawberry.interface(name="Node", description="An object with an ID.") class NodeType: + # TODO: need to make custom ID resolver id: strawberry.ID = strawberry.field( description=""" ID of the object. diff --git a/reddit-clone/reddit/users/mutations/user_create.py b/reddit-clone/reddit/users/mutations/user_create.py index d40164c8..1d4d0dd1 100644 --- a/reddit-clone/reddit/users/mutations/user_create.py +++ b/reddit-clone/reddit/users/mutations/user_create.py @@ -1,9 +1,12 @@ from typing import Union +from marshmallow.exceptions import ValidationError import strawberry from strawberry.types import Info from reddit.users.types import UserType +from reddit.users.serializers import user_schema +from reddit.users.services import user_by_email, user_by_username __all__ = ("user_create",) @@ -30,4 +33,13 @@ class UserCreateError: @strawberry.mutation(description="Creates a new user.") async def user_create(info: Info, input: UserCreateInput) -> UserCreateResult: - pass + try: + data = user_schema.load(input) + except ValidationError as err: + # TODO: handle validation errors here. + print(err) + + if await user_by_email(email=data.get("email")): + return UserCreateError(error="Email already exists.") + if await user_by_username(username=data.get("username")): + return UserCreateError(error="Username already exists.") diff --git a/reddit-clone/reddit/users/serializers.py b/reddit-clone/reddit/users/serializers.py new file mode 100644 index 00000000..1796fbb7 --- /dev/null +++ b/reddit-clone/reddit/users/serializers.py @@ -0,0 +1,37 @@ +from marshmallow import Schema, pre_load +from marshmallow.fields import String, Integer, Boolean +from marshmallow.validate import Email, Length + + +__all__ = ("user_schema",) + + +class UserSchema(Schema): + id = Integer(dump_only=True) + email = String( + required=True, + validate=Email( + error="Not a valid email address.", + ), + ) + username = String( + required=True, + validate=(Length(min=2, max=32),), + ) + password = String( + required=True, + load_only=True, + validate=(Length(min=8),), + ) + avatar = String(dump_only=True) + is_active = Boolean(dump_only=True) + + def process_input(self, data, **kwargs): + # clean user emails before storing them. + data["email"] = data["email"].lower().strip() + return data + + pre_load(process_input) + + +user_schema = UserSchema() From f7c0d6d87b70d58f7468c86e489308b739ee692b Mon Sep 17 00:00:00 2001 From: Aryan Iyappan <69184573+aryan340@users.noreply.github.com> Date: Tue, 19 Oct 2021 21:19:00 +0530 Subject: [PATCH 150/150] update volumes --- reddit-clone/docker-compose.yml | 18 +++------- .../reddit/users/mutations/user_create.py | 33 ++++++++++++++----- reddit-clone/reddit/users/services.py | 8 ++--- 3 files changed, 34 insertions(+), 25 deletions(-) diff --git a/reddit-clone/docker-compose.yml b/reddit-clone/docker-compose.yml index 409acd13..bf2f4231 100644 --- a/reddit-clone/docker-compose.yml +++ b/reddit-clone/docker-compose.yml @@ -59,15 +59,15 @@ services: container_name: reddit-postgres restart: always environment: - POSTGRES_DB: reddit - POSTGRES_PASSWORD: reddit POSTGRES_USER: reddit + POSTGRES_PASSWORD: reddit + POSTGRES_DB: reddit networks: - reddit-main ports: - 5432:5432 volumes: - - postgres-data:/var/lib/postgresql/data + - ./data/postgres:/var/lib/postgresql/data redis: image: redis:6.2-alpine @@ -80,7 +80,7 @@ services: ports: - 6379:6379 volumes: - - redis-data:/data + - ./data/redis:/data healthcheck: test: redis-cli ping interval: 15s @@ -99,21 +99,13 @@ services: ports: - 5672:5672 volumes: - - rabbitmq-data:/data + - ./data/rabbitmq:/data healthcheck: test: rabbitmq-diagnostics -q ping interval: 15s retries: 5 timeout: 5s -volumes: - redis-data: - driver: local - rabbitmq-data: - driver: local - postgres-data: - driver: local - networks: reddit-main: driver: bridge diff --git a/reddit-clone/reddit/users/mutations/user_create.py b/reddit-clone/reddit/users/mutations/user_create.py index 1d4d0dd1..0b8d0fff 100644 --- a/reddit-clone/reddit/users/mutations/user_create.py +++ b/reddit-clone/reddit/users/mutations/user_create.py @@ -1,12 +1,13 @@ -from typing import Union +from typing import Union, cast from marshmallow.exceptions import ValidationError import strawberry from strawberry.types import Info +from reddit.database import get_session from reddit.users.types import UserType from reddit.users.serializers import user_schema -from reddit.users.services import user_by_email, user_by_username +from reddit.users.services import user_by_email, user_by_username, create_user __all__ = ("user_create",) @@ -34,12 +35,28 @@ class UserCreateError: @strawberry.mutation(description="Creates a new user.") async def user_create(info: Info, input: UserCreateInput) -> UserCreateResult: try: - data = user_schema.load(input) + data = user_schema.load( + { + "username": input.username, + "password": input.password, + "email": input.email, + } + ) except ValidationError as err: # TODO: handle validation errors here. print(err) - - if await user_by_email(email=data.get("email")): - return UserCreateError(error="Email already exists.") - if await user_by_username(username=data.get("username")): - return UserCreateError(error="Username already exists.") + return UserCreateError(error=str(err)) + + async with get_session() as session: + if await user_by_email(session=session, email=data.get("email")): + return UserCreateError(error="Email already exists.") + if await user_by_username(session=session, username=data.get("username")): + return UserCreateError(error="Username already exists.") + + user = await create_user( + session=session, + email=data.get("email"), + username=data.get("username"), + password=data.get("password"), + ) + return UserCreateSuccess(user=cast(UserType, user)) diff --git a/reddit-clone/reddit/users/services.py b/reddit-clone/reddit/users/services.py index b4434d36..425d17d9 100644 --- a/reddit-clone/reddit/users/services.py +++ b/reddit-clone/reddit/users/services.py @@ -10,16 +10,16 @@ async def user_by_email(session: AsyncSession, email: str) -> Optional[User]: """ Gets an user by their email. """ - query = select(User).filter_by(email=email).first() - return await session.execute(query) + query = select(User).filter(User.email == email) + return (await session.execute(query)).scalar_one() async def user_by_username(session: AsyncSession, username: str) -> Optional[User]: """ Gets an user by their username. """ - query = select(User).filter_by(username=username).first() - return await session.execute(query) + query = select(User).filter(User.username == username) + return (await session.execute(query)).scalar_one() async def authenticate(