diff options
author | 2020-12-01 20:56:49 +0000 | |
---|---|---|
committer | 2020-12-01 20:56:49 +0000 | |
commit | 0e34c3bd42bba9a3274ea7f83cdce9b0b828db14 (patch) | |
tree | 1d687734f79effb728f27a0b2dd24edc7d7ae910 | |
parent | Merge pull request #12 from python-discord/ks123/admin-authentication (diff) | |
parent | Ignore too long line for if statement (diff) |
Merge pull request #11 from python-discord/ks123/models
-rw-r--r-- | backend/constants.py | 41 | ||||
-rw-r--r-- | backend/models/__init__.py | 4 | ||||
-rw-r--r-- | backend/models/form.py | 27 | ||||
-rw-r--r-- | backend/models/question.py | 54 | ||||
-rw-r--r-- | poetry.lock | 266 | ||||
-rw-r--r-- | pyproject.toml | 1 |
6 files changed, 263 insertions, 130 deletions
diff --git a/backend/constants.py b/backend/constants.py index 746e277..3b8bec8 100644 --- a/backend/constants.py +++ b/backend/constants.py @@ -3,6 +3,8 @@ load_dotenv() import os # noqa import binascii # noqa +from enum import Enum # noqa + DATABASE_URL = os.getenv("DATABASE_URL") MONGO_DATABASE = os.getenv("MONGO_DATABASE", "pydis_forms") @@ -15,3 +17,42 @@ OAUTH2_REDIRECT_URI = os.getenv( ) SECRET_KEY = os.getenv("SECRET_KEY", binascii.hexlify(os.urandom(30)).decode()) + +QUESTION_TYPES = [ + "radio", + "checkbox", + "select", + "short_text", + "textarea", + "code", + "range", + "section", +] + +REQUIRED_QUESTION_TYPE_DATA = { + "radio": { + "options": list, + }, + "select": { + "options": list, + }, + "code": { + "language": str, + }, + "range": { + "options": list, + }, + "section": { + "text": str, + }, +} + + +class FormFeatures(Enum): + """Lists form features. Read more in SCHEMA.md.""" + + DISCOVERABLE = "DISCOVERABLE" + REQUIRES_LOGIN = "REQUIRES_LOGIN" + OPEN = "OPEN" + COLLECT_EMAIL = "COLLECT_EMAIL" + DISABLE_ANTISPAM = "DISABLE_ANTISPAM" diff --git a/backend/models/__init__.py b/backend/models/__init__.py new file mode 100644 index 0000000..80abf6f --- /dev/null +++ b/backend/models/__init__.py @@ -0,0 +1,4 @@ +from .form import Form +from .question import Question + +__all__ = ["Form", "Question"] diff --git a/backend/models/form.py b/backend/models/form.py new file mode 100644 index 0000000..d0f0a3c --- /dev/null +++ b/backend/models/form.py @@ -0,0 +1,27 @@ +import typing as t + +from pydantic import BaseModel, Field, validator + +from backend.constants import FormFeatures +from backend.models import Question + + +class Form(BaseModel): + """Schema model for form.""" + + id: str = Field(alias="_id") + features: t.List[str] + questions: t.List[Question] + + @validator("features") + def validate_features(self, value: t.List[str]) -> t.Optional[t.List[str]]: + """Validates is all features in allowed list.""" + # Uppercase everything to avoid mixed case in DB + value = [v.upper() for v in value] + if not all(v in FormFeatures.__members__.values() for v in value): + raise ValueError("Form features list contains one or more invalid values.") + + if FormFeatures.COLLECT_EMAIL in value and FormFeatures.REQUIRES_LOGIN not in value: # noqa + raise ValueError("COLLECT_EMAIL feature require REQUIRES_LOGIN feature.") + + return value diff --git a/backend/models/question.py b/backend/models/question.py new file mode 100644 index 0000000..2324a47 --- /dev/null +++ b/backend/models/question.py @@ -0,0 +1,54 @@ +import typing as t + +from pydantic import BaseModel, Field, validator + +from backend.constants import QUESTION_TYPES, REQUIRED_QUESTION_TYPE_DATA + + +class Question(BaseModel): + """Schema model for form question.""" + + id: str = Field(alias="_id") + name: str + type: str + data: t.Dict[str, t.Any] + + @validator("type", pre=True) + def validate_question_type(self, value: str) -> t.Optional[str]: + """Checks if question type in currently allowed types list.""" + value = value.lower() + if value not in QUESTION_TYPES: + raise ValueError( + f"{value} is not valid question type. " + f"Allowed question types: {QUESTION_TYPES}." + ) + + return value + + @validator("data") + def validate_question_data( + self, + value: t.Dict[str, t.Any] + ) -> t.Optional[t.Dict[str, t.Any]]: + """Check does required data exists for question type and remove other data.""" + # When question type don't need data, don't add anything to keep DB clean. + if self.type not in REQUIRED_QUESTION_TYPE_DATA: + return {} + + # Required keys (and values) will be stored to here + # to remove all unnecessary stuff + result = {} + + for key, data_type in REQUIRED_QUESTION_TYPE_DATA[self.type].items(): + if key not in value: + raise ValueError(f"Required question data key '{key}' not provided.") + + if not isinstance(value[key], data_type): + raise ValueError( + f"Question data key '{key}' expects {data_type.__name__}, " + f"got {type(value[key]).__name__} instead." + ) + + result[key] = value[key] + + return result diff --git a/poetry.lock b/poetry.lock index 4ff5822..665cb24 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,35 +1,34 @@ [[package]] -category = "main" -description = "Python package for providing Mozilla's CA Bundle." name = "certifi" +version = "2020.11.8" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = "*" -version = "2020.11.8" [[package]] -category = "main" -description = "Composable command line interface toolkit" name = "click" +version = "7.1.2" +description = "Composable command line interface toolkit" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "7.1.2" [[package]] -category = "main" -description = "Cross-platform colored terminal text." -marker = "sys_platform == \"win32\"" 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.*" -version = "0.4.4" [[package]] -category = "dev" -description = "the modular source code checker: pep8 pyflakes and co" name = "flake8" +version = "3.8.4" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -version = "3.8.4" [package.dependencies] mccabe = ">=0.6.0,<0.7.0" @@ -37,48 +36,45 @@ pycodestyle = ">=2.6.0a1,<2.7.0" pyflakes = ">=2.2.0,<2.3.0" [[package]] -category = "dev" -description = "Flake8 Type Annotation Checks" name = "flake8-annotations" +version = "2.4.1" +description = "Flake8 Type Annotation Checks" +category = "dev" optional = false python-versions = ">=3.6.1,<4.0.0" -version = "2.4.1" [package.dependencies] flake8 = ">=3.7,<3.9" [[package]] -category = "main" -description = "WSGI HTTP Server for UNIX" name = "gunicorn" +version = "20.0.4" +description = "WSGI HTTP Server for UNIX" +category = "main" optional = false python-versions = ">=3.4" -version = "20.0.4" - -[package.dependencies] -setuptools = ">=3.0" [package.extras] -eventlet = ["eventlet (>=0.9.7)"] +tornado = ["tornado (>=0.2)"] gevent = ["gevent (>=0.13)"] setproctitle = ["setproctitle"] -tornado = ["tornado (>=0.2)"] +eventlet = ["eventlet (>=0.9.7)"] [[package]] -category = "main" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" name = "h11" +version = "0.11.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "main" optional = false python-versions = "*" -version = "0.11.0" [[package]] -category = "main" -description = "A minimal low-level HTTP client." name = "httpcore" +version = "0.12.2" +description = "A minimal low-level HTTP client." +category = "main" optional = false python-versions = ">=3.6" -version = "0.12.2" [package.dependencies] h11 = "<1.0.0" @@ -88,234 +84,220 @@ sniffio = ">=1.0.0,<2.0.0" http2 = ["h2 (>=3,<5)"] [[package]] -category = "main" -description = "A collection of framework independent HTTP protocol utils." -marker = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"" name = "httptools" +version = "0.1.1" +description = "A collection of framework independent HTTP protocol utils." +category = "main" optional = false python-versions = "*" -version = "0.1.1" [package.extras] -test = ["Cython (0.29.14)"] +test = ["Cython (==0.29.14)"] [[package]] -category = "main" -description = "The next generation HTTP client." name = "httpx" +version = "0.16.1" +description = "The next generation HTTP client." +category = "main" optional = false python-versions = ">=3.6" -version = "0.16.1" [package.dependencies] certifi = "*" httpcore = ">=0.12.0,<0.13.0" +rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} sniffio = "*" -[package.dependencies.rfc3986] -extras = ["idna2008"] -version = ">=1.3,<2" - [package.extras] brotli = ["brotlipy (>=0.7.0,<0.8.0)"] http2 = ["h2 (>=3.0.0,<4.0.0)"] [[package]] -category = "main" -description = "Internationalized Domain Names in Applications (IDNA)" name = "idna" +version = "2.10" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.10" [[package]] -category = "dev" -description = "McCabe checker, plugin for flake8" name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "dev" optional = false python-versions = "*" -version = "0.6.1" [[package]] -category = "main" -description = "Python dictionary with automatic and arbitrary levels of nestedness" name = "nested-dict" +version = "1.61" +description = "Python dictionary with automatic and arbitrary levels of nestedness" +category = "main" optional = false python-versions = "*" -version = "1.61" [[package]] -category = "dev" -description = "Python style guide checker" name = "pycodestyle" +version = "2.6.0" +description = "Python style guide checker" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.6.0" [[package]] -category = "dev" -description = "passive checker of Python programs" +name = "pydantic" +version = "1.7.2" +description = "Data validation and settings management using python 3.6 type hinting" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +typing_extensions = ["typing-extensions (>=3.7.2)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] name = "pyflakes" +version = "2.2.0" +description = "passive checker of Python programs" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.2.0" [[package]] -category = "main" -description = "JSON Web Token implementation in Python" name = "pyjwt" +version = "1.7.1" +description = "JSON Web Token implementation in Python" +category = "main" optional = false python-versions = "*" -version = "1.7.1" [package.extras] +test = ["pytest (>=4.0.1,<5.0.0)", "pytest-cov (>=2.6.0,<3.0.0)", "pytest-runner (>=4.2,<5.0.0)"] crypto = ["cryptography (>=1.4)"] flake8 = ["flake8", "flake8-import-order", "pep8-naming"] -test = ["pytest (>=4.0.1,<5.0.0)", "pytest-cov (>=2.6.0,<3.0.0)", "pytest-runner (>=4.2,<5.0.0)"] [[package]] -category = "main" -description = "Python driver for MongoDB <http://www.mongodb.org>" name = "pymongo" +version = "3.11.1" +description = "Python driver for MongoDB <http://www.mongodb.org>" +category = "main" optional = false python-versions = "*" -version = "3.11.1" [package.extras] -aws = ["pymongo-auth-aws (<2.0.0)"] +tls = ["ipaddress"] encryption = ["pymongocrypt (<2.0.0)"] +aws = ["pymongo-auth-aws (<2.0.0)"] gssapi = ["pykerberos"] -ocsp = ["pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] snappy = ["python-snappy"] srv = ["dnspython (>=1.16.0,<1.17.0)"] -tls = ["ipaddress"] zstd = ["zstandard"] +ocsp = ["pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] [[package]] -category = "main" -description = "Add .env support to your django/flask apps in development and deployments" name = "python-dotenv" +version = "0.14.0" +description = "Add .env support to your django/flask apps in development and deployments" +category = "main" optional = false python-versions = "*" -version = "0.14.0" [package.extras] cli = ["click (>=5.0)"] [[package]] -category = "main" -description = "YAML parser and emitter for Python" name = "pyyaml" +version = "5.3.1" +description = "YAML parser and emitter for Python" +category = "main" optional = false python-versions = "*" -version = "5.3.1" [[package]] -category = "main" -description = "Validating URI References per RFC 3986" name = "rfc3986" +version = "1.4.0" +description = "Validating URI References per RFC 3986" +category = "main" optional = false python-versions = "*" -version = "1.4.0" [package.dependencies] -[package.dependencies.idna] -optional = true -version = "*" +idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} [package.extras] idna2008 = ["idna"] [[package]] -category = "main" -description = "Sniff out which async library your code is running under" 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" -version = "1.2.0" [[package]] -category = "main" -description = "The little ASGI library that shines." name = "starlette" +version = "0.13.8" +description = "The little ASGI library that shines." +category = "main" optional = false python-versions = ">=3.6" -version = "0.13.8" [package.extras] full = ["aiofiles", "graphene", "itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests", "ujson"] [[package]] -category = "main" -description = "The lightning-fast ASGI server." name = "uvicorn" +version = "0.12.3" +description = "The lightning-fast ASGI server." +category = "main" optional = false python-versions = "*" -version = "0.12.2" [package.dependencies] -click = ">=7.0.0,<8.0.0" +PyYAML = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} +uvloop = {version = ">=0.14.0", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""} h11 = ">=0.8" - -[package.dependencies.PyYAML] -optional = true -version = ">=5.1" - -[package.dependencies.colorama] -optional = true -version = ">=0.4" - -[package.dependencies.httptools] -optional = true -version = ">=0.1.0,<0.2.0" - -[package.dependencies.python-dotenv] -optional = true -version = ">=0.13" - -[package.dependencies.uvloop] -optional = true -version = ">=0.14.0" - -[package.dependencies.watchgod] -optional = true -version = ">=0.6,<0.7" - -[package.dependencies.websockets] -optional = true -version = ">=8.0.0,<9.0.0" +watchgod = {version = ">=0.6,<0.7", optional = true, markers = "extra == \"standard\""} +httptools = {version = ">=0.1.0,<0.2.0", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""} +colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} +python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} +click = ">=7.0.0,<8.0.0" +websockets = {version = ">=8.0.0,<9.0.0", optional = true, markers = "extra == \"standard\""} [package.extras] standard = ["websockets (>=8.0.0,<9.0.0)", "watchgod (>=0.6,<0.7)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "httptools (>=0.1.0,<0.2.0)", "uvloop (>=0.14.0)", "colorama (>=0.4)"] [[package]] -category = "main" -description = "Fast implementation of asyncio event loop on top of libuv" -marker = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"" name = "uvloop" +version = "0.14.0" +description = "Fast implementation of asyncio event loop on top of libuv" +category = "main" optional = false python-versions = "*" -version = "0.14.0" [[package]] -category = "main" -description = "Simple, modern file watching and code reload in python." name = "watchgod" +version = "0.6" +description = "Simple, modern file watching and code reload in python." +category = "main" optional = false python-versions = ">=3.5" -version = "0.6" [[package]] -category = "main" -description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" name = "websockets" +version = "8.1" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +category = "main" optional = false python-versions = ">=3.6.1" -version = "8.1" [metadata] -content-hash = "9f434cb3b530e607b34f6b26c5c0314b40597d76b899316d35f4122c3d675eec" +lock-version = "1.1" python-versions = "^3.9" +content-hash = "4b12ca2ac95ff86f810d6bd0546b24af82e638f552fa563d7d288fb8284b0872" [metadata.files] certifi = [ @@ -383,6 +365,30 @@ pycodestyle = [ {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, ] +pydantic = [ + {file = "pydantic-1.7.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:dfaa6ed1d509b5aef4142084206584280bb6e9014f01df931ec6febdad5b200a"}, + {file = "pydantic-1.7.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:2182ba2a9290964b278bcc07a8d24207de709125d520efec9ad6fa6f92ee058d"}, + {file = "pydantic-1.7.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:0fe8b45d31ae53d74a6aa0bf801587bd49970070eac6a6326f9fa2a302703b8a"}, + {file = "pydantic-1.7.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:01f0291f4951580f320f7ae3f2ecaf0044cdebcc9b45c5f882a7e84453362420"}, + {file = "pydantic-1.7.2-cp36-cp36m-win_amd64.whl", hash = "sha256:4ba6b903e1b7bd3eb5df0e78d7364b7e831ed8b4cd781ebc3c4f1077fbcb72a4"}, + {file = "pydantic-1.7.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b11fc9530bf0698c8014b2bdb3bbc50243e82a7fa2577c8cfba660bcc819e768"}, + {file = "pydantic-1.7.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:a3c274c49930dc047a75ecc865e435f3df89715c775db75ddb0186804d9b04d0"}, + {file = "pydantic-1.7.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:c68b5edf4da53c98bb1ccb556ae8f655575cb2e676aef066c12b08c724a3f1a1"}, + {file = "pydantic-1.7.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:95d4410c4e429480c736bba0db6cce5aaa311304aea685ebcf9ee47571bfd7c8"}, + {file = "pydantic-1.7.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a2fc7bf77ed4a7a961d7684afe177ff59971828141e608f142e4af858e07dddc"}, + {file = "pydantic-1.7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9572c0db13c8658b4a4cb705dcaae6983aeb9842248b36761b3fbc9010b740f"}, + {file = "pydantic-1.7.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:f83f679e727742b0c465e7ef992d6da4a7e5268b8edd8fdaf5303276374bef52"}, + {file = "pydantic-1.7.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:e5fece30e80087d9b7986104e2ac150647ec1658c4789c89893b03b100ca3164"}, + {file = "pydantic-1.7.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ce2d452961352ba229fe1e0b925b41c0c37128f08dddb788d0fd73fd87ea0f66"}, + {file = "pydantic-1.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:fc21a37ff3f545de80b166e1735c4172b41b017948a3fb2d5e2f03c219eac50a"}, + {file = "pydantic-1.7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c9760d1556ec59ff745f88269a8f357e2b7afc75c556b3a87b8dda5bc62da8ba"}, + {file = "pydantic-1.7.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c1673633ad1eea78b1c5c420a47cd48717d2ef214c8230d96ca2591e9e00958"}, + {file = "pydantic-1.7.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:388c0c26c574ff49bad7d0fd6ed82fbccd86a0473fa3900397d3354c533d6ebb"}, + {file = "pydantic-1.7.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:ab1d5e4d8de00575957e1c982b951bffaedd3204ddd24694e3baca3332e53a23"}, + {file = "pydantic-1.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:f045cf7afb3352a03bc6cb993578a34560ac24c5d004fa33c76efec6ada1361a"}, + {file = "pydantic-1.7.2-py3-none-any.whl", hash = "sha256:6665f7ab7fbbf4d3c1040925ff4d42d7549a8c15fe041164adfe4fc2134d4cce"}, + {file = "pydantic-1.7.2.tar.gz", hash = "sha256:c8200aecbd1fb914e1bd061d71a4d1d79ecb553165296af0c14989b89e90d09b"}, +] pyflakes = [ {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, @@ -489,8 +495,8 @@ starlette = [ {file = "starlette-0.13.8.tar.gz", hash = "sha256:82df29b2149437ad828a883674bf031788600c876dae50835e98398bd1706183"}, ] uvicorn = [ - {file = "uvicorn-0.12.2-py3-none-any.whl", hash = "sha256:e5dbed4a8a44c7b04376021021d63798d6a7bcfae9c654a0b153577b93854fba"}, - {file = "uvicorn-0.12.2.tar.gz", hash = "sha256:8ff7495c74b8286a341526ff9efa3988ebab9a4b2f561c7438c3cb420992d7dd"}, + {file = "uvicorn-0.12.3-py3-none-any.whl", hash = "sha256:562ef6aaa8fa723ab6b82cf9e67a774088179d0ec57cb17e447b15d58b603bcf"}, + {file = "uvicorn-0.12.3.tar.gz", hash = "sha256:5836edaf4d278fe67ba0298c0537bdb6398cf359eb644f79e6500ca1aad232b3"}, ] uvloop = [ {file = "uvloop-0.14.0-cp35-cp35m-macosx_10_11_x86_64.whl", hash = "sha256:08b109f0213af392150e2fe6f81d33261bb5ce968a288eb698aad4f46eb711bd"}, diff --git a/pyproject.toml b/pyproject.toml index 999f45c..bddfb0e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,7 @@ python-dotenv = "^0.14.0" pyjwt = "^1.7.1" httpx = "^0.16.1" gunicorn = "^20.0.4" +pydantic = "^1.7.2" [tool.poetry.dev-dependencies] flake8 = "^3.8.4" |