aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--backend/constants.py41
-rw-r--r--backend/models/__init__.py4
-rw-r--r--backend/models/form.py27
-rw-r--r--backend/models/question.py54
-rw-r--r--poetry.lock266
-rw-r--r--pyproject.toml1
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"