diff --git a/.env.example b/.env.example index 353fe3a..23918e5 100644 --- a/.env.example +++ b/.env.example @@ -20,4 +20,4 @@ CORS_ORIGINS=localhost DETA_PROJECT_KEY= # OpenAI -OPENAI_API_KEY= \ No newline at end of file +OPENAI_API_KEY= diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 62f4c33..4cf4daa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: - id: black - repo: https://github.com/PyCQA/isort - rev: 5.10.1 + rev: 5.12.0 hooks: - id: isort @@ -51,7 +51,7 @@ repos: - "--no-warn-return-any" - repo: https://github.com/PyCQA/autoflake - rev: 'v2.0.0' + rev: 'v2.2.1' hooks: - id: autoflake args: @@ -62,8 +62,8 @@ repos: - "--remove-all-unused-imports" - "--remove-unused-variables" - "--ignore-init-module-imports" - - - repo: https://github.com/PyCQA/pylint - rev: 'v3.0.0-a5' - hooks: - - id: pylint + # TODO + # - repo: https://github.com/PyCQA/pylint + # rev: 'v3.0.0-a5' + # hooks: + # - id: pylint diff --git a/README.md b/README.md index 12dbe13..0430b54 100755 --- a/README.md +++ b/README.md @@ -236,7 +236,44 @@ Now that you have your Nylas project set up, you need to set the Client ID, Clie By setting the `NYLAS_CLIENT_ID`, `NYLAS_CLIENT_SECRET` and `NYLAS_SYSTEM_TOKEN` environment variables, your application will have the necessary credentials to authenticate and interact with the Nylas API for email and scheduling functionality. These credentials are essential for secure communication with Nylas services. -### 7. Run The Project Locally +### 7. Create an OpenAI Account and Configure the API Key + +To use OpenAI's services in your application, you need to create an OpenAI account and obtain an API key. Follow these steps to set up your OpenAI account and configure the API key: + +#### 7.1 Create an OpenAI Account + +1. **Register for an OpenAI Account:** Go to the official OpenAI website at [https://www.openai.com](https://www.openai.com). + ![Register for an OpenAI Account](static/openai-website.png) + +1. **Sign Up:** Click on the "Log In" button to create a new OpenAI account. You may need to provide some basic information during the registration process. + +#### 7.2 Generate Your OpenAI API Key + +After registering for an OpenAI account, you'll need to generate an API key to access OpenAI's services: + +1. **Access the API Keys Page:** Once you've logged in to your OpenAI account, go to the API Keys page at [https://platform.openai.com/account/api-keys](https://platform.openai.com/account/api-keys). + +1. **Generate a Secret Key:** On the API Keys page, you can generate a new secret key. Click on the "Create new secret key" button to create a new API key. + ![OpenAI Account keys](static/generate-openai-token.png) + +1. **Copy the Secret Key:** After generating the API key, OpenAI will provide you with a secret key. This is a confidential credential that should be kept secure. + +#### 7.3 Set Your OpenAI API Key + +Now that you have generated your OpenAI API key, you'll need to configure it in your project: + +1. **Update Your Environment Variables:** Open your project's configuration file or `.env` file, where you store environment variables. + +2. **Set the OpenAI API Key:** In your configuration file, add the following environment variable, replacing `` with the actual secret key you obtained from the OpenAI platform: + ```yaml + # OpenAI + OPENAI_API_KEY= + ``` + - Save the changes to your configuration file. + +By setting the `OPENAI_API_KEY` environment variable, your application will have the necessary credentials to authenticate and interact with the OpenAI API. This API key allows your application to access OpenAI's language models and services securely. + +### 8. Run The Project Locally ```sh make run diff --git a/app.json b/app.json index aeeb1e4..b6aa58f 100644 --- a/app.json +++ b/app.json @@ -1,56 +1,42 @@ { - "buildpacks": [ - { - "url": "heroku/python" - } - ], - "description": "A Fully Async-based backend for Code Inbox built using FastAPI, ODMantic, MongoDB, Deta, and friends.", - "env": { - "DEBUG": { - "description": "This environment variable defines the debug level.`` means production. `info` to access the docs.", - "required": false, - "value": "" + "buildpacks": [{"url": "heroku/python"}], + "description": "A Fully Async-based backend for Code Inbox built using FastAPI, ODMantic, MongoDB, Deta, and friends.", + "env": { + "DEBUG": { + "description": "This environment variable defines the debug level.`` means production. `info` to access the docs.", + "required": false, + "value": "", + }, + "CORS_ORIGINS": { + "description": "Comma separated urls of the deployed client.", + "required": true, + }, + "DETA_PROJECT_KEY": { + "description": "The project key of your Deta account.", + "required": true, + }, + "MONGODB_USERNAME": { + "description": "This is the user name you'll be creating for remote accesses.", + "required": true, + }, + "MONGODB_PASSWORD": { + "description": "The corresponding password for that user.", + "required": true, + }, + "MONGODB_HOST": { + "description": "Your remote MongoDB server's domain name.", + "required": true, + }, + "MONGODB_DATABASE": { + "description": "The name of the database you want to access, in our case, the `shop` database.", + "required": false, + "value": "shop", + }, }, - "CORS_ORIGINS": { - "description": "Comma separated urls of the deployed client.", - "required": true - }, - "DETA_PROJECT_KEY": { - "description": "The project key of your Deta account.", - "required": true - }, - "MONGODB_USERNAME": { - "description": "This is the user name you'll be creating for remote accesses.", - "required": true - }, - "MONGODB_PASSWORD": { - "description": "The corresponding password for that user.", - "required": true - }, - "MONGODB_HOST": { - "description": "Your remote MongoDB server's domain name.", - "required": true - }, - "MONGODB_DATABASE": { - "description": "The name of the database you want to access, in our case, the `shop` database.", - "required": false, - "value": "shop" - } - }, - "formation": { - "web": { - "quantity": 1, - "size": "free" - } - }, - "image": "heroku/python", - "keywords": [ - "fastapi", - "mongodb", - "deta", - "api" - ], - "name": "code-inbox-server", - "repository": "https://github.com/wiseaidev/code-inbox-server", - "success_url": "/docs" + "formation": {"web": {"quantity": 1, "size": "free"}}, + "image": "heroku/python", + "keywords": ["fastapi", "mongodb", "deta", "api"], + "name": "code-inbox-server", + "repository": "https://github.com/wiseaidev/code-inbox-server", + "success_url": "/docs", } diff --git a/poetry.lock b/poetry.lock index b26bf6f..67634ef 100644 --- a/poetry.lock +++ b/poetry.lock @@ -171,6 +171,25 @@ tornado = ["tornado (>=4.3)"] twisted = ["twisted"] zookeeper = ["kazoo"] +[[package]] +name = "astroid" +version = "2.15.8" +description = "An abstract syntax tree for Python with inference support." +optional = false +python-versions = ">=3.7.2" +files = [ + {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, + {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, +] + +[package.dependencies] +lazy-object-proxy = ">=1.4.0" +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} +wrapt = [ + {version = ">=1.11,<2", markers = "python_version < \"3.11\""}, + {version = ">=1.14,<2", markers = "python_version >= \"3.11\""}, +] + [[package]] name = "async-timeout" version = "4.0.3" @@ -200,6 +219,78 @@ docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib- tests = ["attrs[tests-no-zope]", "zope-interface"] tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +[[package]] +name = "autoflake" +version = "2.2.1" +description = "Removes unused imports and unused variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "autoflake-2.2.1-py3-none-any.whl", hash = "sha256:265cde0a43c1f44ecfb4f30d95b0437796759d07be7706a2f70e4719234c0f79"}, + {file = "autoflake-2.2.1.tar.gz", hash = "sha256:62b7b6449a692c3c9b0c916919bbc21648da7281e8506bcf8d3f8280e431ebc1"}, +] + +[package.dependencies] +pyflakes = ">=3.0.0" +tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} + +[[package]] +name = "black" +version = "23.9.1" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-23.9.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301"}, + {file = "black-23.9.1-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:13ef033794029b85dfea8032c9d3b92b42b526f1ff4bf13b2182ce4e917f5100"}, + {file = "black-23.9.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:75a2dc41b183d4872d3a500d2b9c9016e67ed95738a3624f4751a0cb4818fe71"}, + {file = "black-23.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13a2e4a93bb8ca74a749b6974925c27219bb3df4d42fc45e948a5d9feb5122b7"}, + {file = "black-23.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:adc3e4442eef57f99b5590b245a328aad19c99552e0bdc7f0b04db6656debd80"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:8431445bf62d2a914b541da7ab3e2b4f3bc052d2ccbf157ebad18ea126efb91f"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:8fc1ddcf83f996247505db6b715294eba56ea9372e107fd54963c7553f2b6dfe"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:7d30ec46de88091e4316b17ae58bbbfc12b2de05e069030f6b747dfc649ad186"}, + {file = "black-23.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031e8c69f3d3b09e1aa471a926a1eeb0b9071f80b17689a655f7885ac9325a6f"}, + {file = "black-23.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:538efb451cd50f43aba394e9ec7ad55a37598faae3348d723b59ea8e91616300"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:638619a559280de0c2aa4d76f504891c9860bb8fa214267358f0a20f27c12948"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:a732b82747235e0542c03bf352c126052c0fbc458d8a239a94701175b17d4855"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:cf3a4d00e4cdb6734b64bf23cd4341421e8953615cba6b3670453737a72ec204"}, + {file = "black-23.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf99f3de8b3273a8317681d8194ea222f10e0133a24a7548c73ce44ea1679377"}, + {file = "black-23.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:14f04c990259576acd093871e7e9b14918eb28f1866f91968ff5524293f9c573"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:c619f063c2d68f19b2d7270f4cf3192cb81c9ec5bc5ba02df91471d0b88c4c5c"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:6a3b50e4b93f43b34a9d3ef00d9b6728b4a722c997c99ab09102fd5efdb88325"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c46767e8df1b7beefb0899c4a95fb43058fa8500b6db144f4ff3ca38eb2f6393"}, + {file = "black-23.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50254ebfa56aa46a9fdd5d651f9637485068a1adf42270148cd101cdf56e0ad9"}, + {file = "black-23.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:403397c033adbc45c2bd41747da1f7fc7eaa44efbee256b53842470d4ac5a70f"}, + {file = "black-23.9.1-py3-none-any.whl", hash = "sha256:6ccd59584cc834b6d127628713e4b6b968e5f79572da66284532525a042549f9"}, + {file = "black-23.9.1.tar.gz", hash = "sha256:24b6b3ff5c6d9ea08a8888f6977eae858e1f340d7260cf56d70a49823236b62d"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "cachetools" +version = "5.3.1" +description = "Extensible memoizing collections and decorators" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cachetools-5.3.1-py3-none-any.whl", hash = "sha256:95ef631eeaea14ba2e36f06437f36463aac3a096799e876ee55e5cdccb102590"}, + {file = "cachetools-5.3.1.tar.gz", hash = "sha256:dce83f2d9b4e1f732a8cd44af8e8fab2dbe46201467fc98b3ef8f269092bf62b"}, +] + [[package]] name = "certifi" version = "2023.7.22" @@ -211,6 +302,28 @@ files = [ {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, ] +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + +[[package]] +name = "chardet" +version = "5.2.0" +description = "Universal encoding detector for Python 3" +optional = false +python-versions = ">=3.7" +files = [ + {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, + {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, +] + [[package]] name = "charset-normalizer" version = "3.2.0" @@ -320,6 +433,73 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "coverage" +version = "7.3.1" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coverage-7.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cd0f7429ecfd1ff597389907045ff209c8fdb5b013d38cfa7c60728cb484b6e3"}, + {file = "coverage-7.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:966f10df9b2b2115da87f50f6a248e313c72a668248be1b9060ce935c871f276"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0575c37e207bb9b98b6cf72fdaaa18ac909fb3d153083400c2d48e2e6d28bd8e"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:245c5a99254e83875c7fed8b8b2536f040997a9b76ac4c1da5bff398c06e860f"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c96dd7798d83b960afc6c1feb9e5af537fc4908852ef025600374ff1a017392"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:de30c1aa80f30af0f6b2058a91505ea6e36d6535d437520067f525f7df123887"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:50dd1e2dd13dbbd856ffef69196781edff26c800a74f070d3b3e3389cab2600d"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9c0c19f70d30219113b18fe07e372b244fb2a773d4afde29d5a2f7930765136"}, + {file = "coverage-7.3.1-cp310-cp310-win32.whl", hash = "sha256:770f143980cc16eb601ccfd571846e89a5fe4c03b4193f2e485268f224ab602f"}, + {file = "coverage-7.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:cdd088c00c39a27cfa5329349cc763a48761fdc785879220d54eb785c8a38520"}, + {file = "coverage-7.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74bb470399dc1989b535cb41f5ca7ab2af561e40def22d7e188e0a445e7639e3"}, + {file = "coverage-7.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:025ded371f1ca280c035d91b43252adbb04d2aea4c7105252d3cbc227f03b375"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6191b3a6ad3e09b6cfd75b45c6aeeffe7e3b0ad46b268345d159b8df8d835f9"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7eb0b188f30e41ddd659a529e385470aa6782f3b412f860ce22b2491c89b8593"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c8f0df9dfd8ff745bccff75867d63ef336e57cc22b2908ee725cc552689ec8"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7eb3cd48d54b9bd0e73026dedce44773214064be93611deab0b6a43158c3d5a0"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ac3c5b7e75acac31e490b7851595212ed951889918d398b7afa12736c85e13ce"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b4ee7080878077af0afa7238df1b967f00dc10763f6e1b66f5cced4abebb0a3"}, + {file = "coverage-7.3.1-cp311-cp311-win32.whl", hash = "sha256:229c0dd2ccf956bf5aeede7e3131ca48b65beacde2029f0361b54bf93d36f45a"}, + {file = "coverage-7.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:c6f55d38818ca9596dc9019eae19a47410d5322408140d9a0076001a3dcb938c"}, + {file = "coverage-7.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5289490dd1c3bb86de4730a92261ae66ea8d44b79ed3cc26464f4c2cde581fbc"}, + {file = "coverage-7.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ca833941ec701fda15414be400c3259479bfde7ae6d806b69e63b3dc423b1832"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd694e19c031733e446c8024dedd12a00cda87e1c10bd7b8539a87963685e969"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aab8e9464c00da5cb9c536150b7fbcd8850d376d1151741dd0d16dfe1ba4fd26"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87d38444efffd5b056fcc026c1e8d862191881143c3aa80bb11fcf9dca9ae204"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8a07b692129b8a14ad7a37941a3029c291254feb7a4237f245cfae2de78de037"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2829c65c8faaf55b868ed7af3c7477b76b1c6ebeee99a28f59a2cb5907a45760"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f111a7d85658ea52ffad7084088277135ec5f368457275fc57f11cebb15607f"}, + {file = "coverage-7.3.1-cp312-cp312-win32.whl", hash = "sha256:c397c70cd20f6df7d2a52283857af622d5f23300c4ca8e5bd8c7a543825baa5a"}, + {file = "coverage-7.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:5ae4c6da8b3d123500f9525b50bf0168023313963e0e2e814badf9000dd6ef92"}, + {file = "coverage-7.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca70466ca3a17460e8fc9cea7123c8cbef5ada4be3140a1ef8f7b63f2f37108f"}, + {file = "coverage-7.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f2781fd3cabc28278dc982a352f50c81c09a1a500cc2086dc4249853ea96b981"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6407424621f40205bbe6325686417e5e552f6b2dba3535dd1f90afc88a61d465"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04312b036580ec505f2b77cbbdfb15137d5efdfade09156961f5277149f5e344"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9ad38204887349853d7c313f53a7b1c210ce138c73859e925bc4e5d8fc18e7"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:53669b79f3d599da95a0afbef039ac0fadbb236532feb042c534fbb81b1a4e40"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:614f1f98b84eb256e4f35e726bfe5ca82349f8dfa576faabf8a49ca09e630086"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f1a317fdf5c122ad642db8a97964733ab7c3cf6009e1a8ae8821089993f175ff"}, + {file = "coverage-7.3.1-cp38-cp38-win32.whl", hash = "sha256:defbbb51121189722420a208957e26e49809feafca6afeef325df66c39c4fdb3"}, + {file = "coverage-7.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:f4f456590eefb6e1b3c9ea6328c1e9fa0f1006e7481179d749b3376fc793478e"}, + {file = "coverage-7.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f12d8b11a54f32688b165fd1a788c408f927b0960984b899be7e4c190ae758f1"}, + {file = "coverage-7.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f09195dda68d94a53123883de75bb97b0e35f5f6f9f3aa5bf6e496da718f0cb6"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6601a60318f9c3945be6ea0f2a80571f4299b6801716f8a6e4846892737ebe4"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07d156269718670d00a3b06db2288b48527fc5f36859425ff7cec07c6b367745"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:636a8ac0b044cfeccae76a36f3b18264edcc810a76a49884b96dd744613ec0b7"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5d991e13ad2ed3aced177f524e4d670f304c8233edad3210e02c465351f785a0"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:586649ada7cf139445da386ab6f8ef00e6172f11a939fc3b2b7e7c9082052fa0"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4aba512a15a3e1e4fdbfed2f5392ec221434a614cc68100ca99dcad7af29f3f8"}, + {file = "coverage-7.3.1-cp39-cp39-win32.whl", hash = "sha256:6bc6f3f4692d806831c136c5acad5ccedd0262aa44c087c46b7101c77e139140"}, + {file = "coverage-7.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:553d7094cb27db58ea91332e8b5681bac107e7242c23f7629ab1316ee73c4981"}, + {file = "coverage-7.3.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:220eb51f5fb38dfdb7e5d54284ca4d0cd70ddac047d750111a68ab1798945194"}, + {file = "coverage-7.3.1.tar.gz", hash = "sha256:6cb7fe1581deb67b782c153136541e20901aa312ceedaf1467dcb35255787952"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + [[package]] name = "deta" version = "1.2.0" @@ -334,6 +514,31 @@ files = [ [package.extras] async = ["aiohttp (>=3,<4)"] +[[package]] +name = "dill" +version = "0.3.7" +description = "serialize all of Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "dill-0.3.7-py3-none-any.whl", hash = "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e"}, + {file = "dill-0.3.7.tar.gz", hash = "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] + +[[package]] +name = "distlib" +version = "0.3.7" +description = "Distribution utilities" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, + {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, +] + [[package]] name = "dnspython" version = "2.4.2" @@ -402,6 +607,38 @@ typing-extensions = ">=4.5.0" [package.extras] all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] +[[package]] +name = "filelock" +version = "3.12.4" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.12.4-py3-none-any.whl", hash = "sha256:08c21d87ded6e2b9da6728c3dff51baf1dcecf973b768ef35bcbc3447edb9ad4"}, + {file = "filelock-3.12.4.tar.gz", hash = "sha256:2e6f249f1f3654291606e046b09f1fd5eac39b360664c27f5aad072012f8bcbd"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"] +typing = ["typing-extensions (>=4.7.1)"] + +[[package]] +name = "flake8" +version = "6.1.0" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, + {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, +] + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.11.0,<2.12.0" +pyflakes = ">=3.1.0,<3.2.0" + [[package]] name = "frozenlist" version = "1.4.0" @@ -483,6 +720,27 @@ files = [ {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] +[[package]] +name = "httpcore" +version = "0.18.0" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-0.18.0-py3-none-any.whl", hash = "sha256:adc5398ee0a476567bf87467063ee63584a8bce86078bf748e48754f60202ced"}, + {file = "httpcore-0.18.0.tar.gz", hash = "sha256:13b5e5cd1dca1a6636a6aaea212b19f4f85cd88c366a2b82304181b769aab3c9"}, +] + +[package.dependencies] +anyio = ">=3.0,<5.0" +certifi = "*" +h11 = ">=0.13,<0.15" +sniffio = "==1.*" + +[package.extras] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + [[package]] name = "httptools" version = "0.6.0" @@ -530,6 +788,43 @@ files = [ [package.extras] test = ["Cython (>=0.29.24,<0.30.0)"] +[[package]] +name = "httpx" +version = "0.25.0" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.25.0-py3-none-any.whl", hash = "sha256:181ea7f8ba3a82578be86ef4171554dd45fec26a02556a744db029a0a27b7100"}, + {file = "httpx-0.25.0.tar.gz", hash = "sha256:47ecda285389cb32bb2691cc6e069e3ab0205956f681c5b2ad2325719751d875"}, +] + +[package.dependencies] +certifi = "*" +httpcore = ">=0.18.0,<0.19.0" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + +[[package]] +name = "identify" +version = "2.5.29" +description = "File identification library for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "identify-2.5.29-py2.py3-none-any.whl", hash = "sha256:24437fbf6f4d3fe6efd0eb9d67e24dd9106db99af5ceb27996a5f7895f24bf1b"}, + {file = "identify-2.5.29.tar.gz", hash = "sha256:d43d52b86b15918c137e3a74fff5224f60385cd0e9c38e99d07c257f02f151a5"}, +] + +[package.extras] +license = ["ukkonen"] + [[package]] name = "idna" version = "3.4" @@ -552,6 +847,79 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "isort" +version = "5.12.0" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, + {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, +] + +[package.extras] +colors = ["colorama (>=0.4.3)"] +pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] +plugins = ["setuptools"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] + +[[package]] +name = "lazy-object-proxy" +version = "1.9.0" +description = "A fast and thorough lazy object proxy." +optional = false +python-versions = ">=3.7" +files = [ + {file = "lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + [[package]] name = "motor" version = "3.1.2" @@ -658,6 +1026,77 @@ files = [ {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, ] +[[package]] +name = "mypy" +version = "1.5.1" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f33592ddf9655a4894aef22d134de7393e95fcbdc2d15c1ab65828eee5c66c70"}, + {file = "mypy-1.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:258b22210a4a258ccd077426c7a181d789d1121aca6db73a83f79372f5569ae0"}, + {file = "mypy-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9ec1f695f0c25986e6f7f8778e5ce61659063268836a38c951200c57479cc12"}, + {file = "mypy-1.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:abed92d9c8f08643c7d831300b739562b0a6c9fcb028d211134fc9ab20ccad5d"}, + {file = "mypy-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:a156e6390944c265eb56afa67c74c0636f10283429171018446b732f1a05af25"}, + {file = "mypy-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ac9c21bfe7bc9f7f1b6fae441746e6a106e48fc9de530dea29e8cd37a2c0cc4"}, + {file = "mypy-1.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51cb1323064b1099e177098cb939eab2da42fea5d818d40113957ec954fc85f4"}, + {file = "mypy-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:596fae69f2bfcb7305808c75c00f81fe2829b6236eadda536f00610ac5ec2243"}, + {file = "mypy-1.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:32cb59609b0534f0bd67faebb6e022fe534bdb0e2ecab4290d683d248be1b275"}, + {file = "mypy-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:159aa9acb16086b79bbb0016145034a1a05360626046a929f84579ce1666b315"}, + {file = "mypy-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f6b0e77db9ff4fda74de7df13f30016a0a663928d669c9f2c057048ba44f09bb"}, + {file = "mypy-1.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26f71b535dfc158a71264e6dc805a9f8d2e60b67215ca0bfa26e2e1aa4d4d373"}, + {file = "mypy-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc3a600f749b1008cc75e02b6fb3d4db8dbcca2d733030fe7a3b3502902f161"}, + {file = "mypy-1.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:26fb32e4d4afa205b24bf645eddfbb36a1e17e995c5c99d6d00edb24b693406a"}, + {file = "mypy-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:82cb6193de9bbb3844bab4c7cf80e6227d5225cc7625b068a06d005d861ad5f1"}, + {file = "mypy-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4a465ea2ca12804d5b34bb056be3a29dc47aea5973b892d0417c6a10a40b2d65"}, + {file = "mypy-1.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9fece120dbb041771a63eb95e4896791386fe287fefb2837258925b8326d6160"}, + {file = "mypy-1.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d28ddc3e3dfeab553e743e532fb95b4e6afad51d4706dd22f28e1e5e664828d2"}, + {file = "mypy-1.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:57b10c56016adce71fba6bc6e9fd45d8083f74361f629390c556738565af8eeb"}, + {file = "mypy-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:ff0cedc84184115202475bbb46dd99f8dcb87fe24d5d0ddfc0fe6b8575c88d2f"}, + {file = "mypy-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8f772942d372c8cbac575be99f9cc9d9fb3bd95c8bc2de6c01411e2c84ebca8a"}, + {file = "mypy-1.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5d627124700b92b6bbaa99f27cbe615c8ea7b3402960f6372ea7d65faf376c14"}, + {file = "mypy-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:361da43c4f5a96173220eb53340ace68cda81845cd88218f8862dfb0adc8cddb"}, + {file = "mypy-1.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:330857f9507c24de5c5724235e66858f8364a0693894342485e543f5b07c8693"}, + {file = "mypy-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:c543214ffdd422623e9fedd0869166c2f16affe4ba37463975043ef7d2ea8770"}, + {file = "mypy-1.5.1-py3-none-any.whl", hash = "sha256:f757063a83970d67c444f6e01d9550a7402322af3557ce7630d3c957386fa8f5"}, + {file = "mypy-1.5.1.tar.gz", hash = "sha256:b031b9601f1060bf1281feab89697324726ba0c0bae9d7cd7ab4b690940f0b92"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.1.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "nodeenv" +version = "1.8.0" +description = "Node.js virtual environment builder" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] + +[package.dependencies] +setuptools = "*" + [[package]] name = "nylas" version = "5.14.1" @@ -734,6 +1173,32 @@ files = [ {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, ] +[[package]] +name = "pathspec" +version = "0.11.2" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, +] + +[[package]] +name = "platformdirs" +version = "3.10.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, + {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] + [[package]] name = "pluggy" version = "1.3.0" @@ -749,6 +1214,35 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pre-commit" +version = "3.4.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pre_commit-3.4.0-py2.py3-none-any.whl", hash = "sha256:96d529a951f8b677f730a7212442027e8ba53f9b04d217c4c67dc56c393ad945"}, + {file = "pre_commit-3.4.0.tar.gz", hash = "sha256:6bbd5129a64cad4c0dfaeeb12cd8f7ea7e15b77028d985341478c8af3c759522"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + +[[package]] +name = "pycodestyle" +version = "2.11.0" +description = "Python style guide checker" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycodestyle-2.11.0-py2.py3-none-any.whl", hash = "sha256:5d1013ba8dc7895b548be5afb05740ca82454fd899971563d2ef625d090326f8"}, + {file = "pycodestyle-2.11.0.tar.gz", hash = "sha256:259bcc17857d8a8b3b4a2327324b79e5f020a13c16074670f9c8c8f872ea76d0"}, +] + [[package]] name = "pydantic" version = "1.10.13" @@ -802,6 +1296,45 @@ typing-extensions = ">=4.2.0" dotenv = ["python-dotenv (>=0.10.4)"] email = ["email-validator (>=1.0.3)"] +[[package]] +name = "pyflakes" +version = "3.1.0" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, + {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, +] + +[[package]] +name = "pylint" +version = "2.17.6" +description = "python code static checker" +optional = false +python-versions = ">=3.7.2" +files = [ + {file = "pylint-2.17.6-py3-none-any.whl", hash = "sha256:18a1412e873caf8ffb56b760ce1b5643675af23e6173a247b502406b24c716af"}, + {file = "pylint-2.17.6.tar.gz", hash = "sha256:be928cce5c76bf9acdc65ad01447a1e0b1a7bccffc609fb7fc40f2513045bd05"}, +] + +[package.dependencies] +astroid = ">=2.15.7,<=2.17.0-dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = [ + {version = ">=0.2", markers = "python_version < \"3.11\""}, + {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, +] +isort = ">=4.2.5,<6" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +tomlkit = ">=0.10.1" + +[package.extras] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] + [[package]] name = "pymongo" version = "4.5.0" @@ -903,6 +1436,25 @@ ocsp = ["certifi", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3 snappy = ["python-snappy"] zstd = ["zstandard"] +[[package]] +name = "pyproject-api" +version = "1.6.1" +description = "API to interact with the python pyproject.toml based projects" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyproject_api-1.6.1-py3-none-any.whl", hash = "sha256:4c0116d60476b0786c88692cf4e325a9814965e2469c5998b830bba16b183675"}, + {file = "pyproject_api-1.6.1.tar.gz", hash = "sha256:1817dc018adc0d1ff9ca1ed8c60e1623d5aaca40814b953af14a9cf9a5cae538"}, +] + +[package.dependencies] +packaging = ">=23.1" +tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +docs = ["furo (>=2023.8.19)", "sphinx (<7.2)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "setuptools (>=68.1.2)", "wheel (>=0.41.2)"] + [[package]] name = "pytest" version = "7.4.2" @@ -925,6 +1477,24 @@ tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-cov" +version = "4.1.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + [[package]] name = "python-dotenv" version = "1.0.0" @@ -1043,6 +1613,22 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "setuptools" +version = "68.2.2" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, + {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "six" version = "1.16.0" @@ -1093,6 +1679,44 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "tomlkit" +version = "0.12.1" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomlkit-0.12.1-py3-none-any.whl", hash = "sha256:712cbd236609acc6a3e2e97253dfc52d4c2082982a88f61b640ecf0817eab899"}, + {file = "tomlkit-0.12.1.tar.gz", hash = "sha256:38e1ff8edb991273ec9f6181244a6a391ac30e9f5098e7535640ea6be97a7c86"}, +] + +[[package]] +name = "tox" +version = "4.11.3" +description = "tox is a generic virtualenv management and test command line tool" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tox-4.11.3-py3-none-any.whl", hash = "sha256:599af5e5bb0cad0148ac1558a0b66f8fff219ef88363483b8d92a81e4246f28f"}, + {file = "tox-4.11.3.tar.gz", hash = "sha256:5039f68276461fae6a9452a3b2c7295798f00a0e92edcd9a3b78ba1a73577951"}, +] + +[package.dependencies] +cachetools = ">=5.3.1" +chardet = ">=5.2" +colorama = ">=0.4.6" +filelock = ">=3.12.3" +packaging = ">=23.1" +platformdirs = ">=3.10" +pluggy = ">=1.3" +pyproject-api = ">=1.6.1" +tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} +virtualenv = ">=20.24.3" + +[package.extras] +docs = ["furo (>=2023.8.19)", "sphinx (>=7.2.4)", "sphinx-argparse-cli (>=1.11.1)", "sphinx-autodoc-typehints (>=1.24)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2023.4.21)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +testing = ["build[virtualenv] (>=0.10)", "covdefaults (>=2.3)", "detect-test-pollution (>=1.1.1)", "devpi-process (>=1)", "diff-cover (>=7.7)", "distlib (>=0.3.7)", "flaky (>=3.7)", "hatch-vcs (>=0.3)", "hatchling (>=1.18)", "psutil (>=5.9.5)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-xdist (>=3.3.1)", "re-assert (>=1.1)", "time-machine (>=2.12)", "wheel (>=0.41.2)"] + [[package]] name = "tqdm" version = "4.66.1" @@ -1249,6 +1873,26 @@ dev = ["Cython (>=0.29.32,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "aiohttp", "flak docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] test = ["Cython (>=0.29.32,<0.30.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=22.0.0,<22.1.0)", "pycodestyle (>=2.7.0,<2.8.0)"] +[[package]] +name = "virtualenv" +version = "20.24.5" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.24.5-py3-none-any.whl", hash = "sha256:b80039f280f4919c77b30f1c23294ae357c4c8701042086e3fc005963e4e537b"}, + {file = "virtualenv-20.24.5.tar.gz", hash = "sha256:e8361967f6da6fbdf1426483bfe9fca8287c242ac0bc30429905721cefbff752"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<4" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + [[package]] name = "watchfiles" version = "0.20.0" @@ -1376,6 +2020,90 @@ files = [ {file = "websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016"}, ] +[[package]] +name = "wrapt" +version = "1.15.0" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ + {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, + {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, + {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, + {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, + {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, + {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, + {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, + {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, + {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, + {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, + {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, + {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, + {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, + {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, + {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, + {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, + {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, + {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, + {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, + {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, + {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, + {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, + {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, + {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, + {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, + {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, + {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, +] + [[package]] name = "yarl" version = "1.9.2" @@ -1466,4 +2194,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10.10" -content-hash = "5ae77a9324ec538b1bf9d31c82cb3fbe9100301bdd845e0f1ea0fcc59c1dfd1a" +content-hash = "b0d567f5da265a10db17ad4b257af0e0b54255dfd10a813e22728fd9ea5c6c0e" diff --git a/pyproject.toml b/pyproject.toml index 151bc05..0f217a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,17 @@ apscheduler = "^3.10.4" [tool.poetry.group.dev.dependencies] pytest = "^7.2.0" +flake8 = "^6.1.0" +coverage = "^7.3.1" +mypy = "^1.5.1" +pytest-cov = "^4.1.0" +tox = "^4.11.3" +isort = "^5.12.0" +black = "^23.9.1" +pre-commit = "^3.4.0" +httpx = "^0.25.0" +pylint = "^2.17.6" +autoflake = "^2.2.1" [build-system] requires = ["poetry-core"] diff --git a/src/__init__.py b/src/__init__.py index 7dc1f82..b0fe401 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -8,11 +8,11 @@ users, utils, ) - from src.main import ( get_app, - serve + serve, ) + __author__ = """Mahmoud Harmouch""" __email__ = "oss@wiseai.com" __version__ = "0.1.0" diff --git a/src/config.py b/src/config.py index 7b044cc..4eade8f 100644 --- a/src/config.py +++ b/src/config.py @@ -49,13 +49,13 @@ class Settings(BaseSettings): DEBUG: str = os.getenv("DEBUG") # type: ignore CORS_ORIGINS: str = os.getenv("CORS_ORIGINS") # type: ignore DETA_PROJECT_KEY: str = os.getenv("DETA_PROJECT_KEY") # type: ignore - NYLAS_CLIENT_ID: str = os.getenv("NYLAS_CLIENT_ID") - NYLAS_CLIENT_SECRET: str = os.getenv("NYLAS_CLIENT_SECRET") - NYLAS_API_SERVER: str = os.getenv("NYLAS_API_SERVER") - CLIENT_URI: str = os.getenv("CLIENT_URI") - NYLAS_SYSTEM_TOKEN: str = os.getenv("NYLAS_SYSTEM_TOKEN") - OPENAI_API_KEY: str = os.getenv("OPENAI_API_KEY") - + NYLAS_CLIENT_ID: str = os.getenv("NYLAS_CLIENT_ID") # type: ignore + NYLAS_CLIENT_SECRET: str = os.getenv("NYLAS_CLIENT_SECRET") # type: ignore + NYLAS_API_SERVER: str = os.getenv("NYLAS_API_SERVER") # type: ignore + CLIENT_URI: str = os.getenv("CLIENT_URI") # type: ignore + NYLAS_SYSTEM_TOKEN: str = os.getenv("NYLAS_SYSTEM_TOKEN") # type: ignore + OPENAI_API_KEY: str = os.getenv("OPENAI_API_KEY") # type: ignore + class Config: # pylint: disable=R0903 """ A class used to set Pydantic configuration for env vars. diff --git a/src/main.py b/src/main.py index 1388904..ba9927b 100644 --- a/src/main.py +++ b/src/main.py @@ -11,16 +11,16 @@ ) import logging from typing import ( - Any, Dict, ) import uvicorn -from src.nylas import ( - router as nylas_router, -) + from src.config import ( settings, ) +from src.nylas import ( + router as nylas_router, +) from src.users import ( router as users_router, ) @@ -108,8 +108,10 @@ async def root() -> Dict[str, str]: return app + code_app = get_app() + def serve() -> None: """ A method that run a uvicorn command. @@ -125,6 +127,7 @@ def serve() -> None: except Exception as err: logger.error(repr(err)) + __all__ = [ "serve", "code_app", diff --git a/src/nylas/__init__.py b/src/nylas/__init__.py index 93bd733..18c1f82 100644 --- a/src/nylas/__init__.py +++ b/src/nylas/__init__.py @@ -3,10 +3,10 @@ """ from src.nylas import ( + crud, + models, router, schemas, - crud, - models ) __all__ = ["crud", "models", "router", "schemas"] diff --git a/src/nylas/crud.py b/src/nylas/crud.py index 3309fac..a14ec83 100644 --- a/src/nylas/crud.py +++ b/src/nylas/crud.py @@ -22,22 +22,43 @@ login_user: Fetch and return serialized user info upon logging in. find_existed_token: Find a token in a token list. """ -from bson import ObjectId - +from bson import ( + ObjectId, +) +from datetime import ( + datetime, +) +from fastapi.encoders import ( + jsonable_encoder, +) +from odmantic.session import ( + AIOSession, +) import os -from datetime import datetime, timedelta -from fastapi.encoders import jsonable_encoder -from odmantic.session import AIOSession -from pydantic import EmailStr +from pydantic import ( + EmailStr, +) +from typing import ( + Any, + Dict, + Optional, +) + +from src.config import ( + settings, +) +from src.nylas import ( + models as nylas_models, +) +from src.users import ( + models as users_models, + schemas as users_schemas, +) -from typing import Any, Dict, Optional, Union, List -from src.nylas import models as nylas_models, schemas as nylas_schemas -from src.users import models as users_models, schemas as users_schemas -from src.config import settings async def create_user( email: EmailStr, session: AIOSession -) -> users_models.User: +) -> Optional[users_models.User]: """ A method to insert a user into the users table. @@ -49,17 +70,22 @@ async def create_user( users_models.User: A User model instance. """ try: - from src.main import code_app + from src.main import ( + code_app, + ) + full_name = code_app.state.nylas.account.get("name") user = users_models.User(full_name=full_name, email=email) await session.save(user) return user except Exception as e: print(f"Error creating user: {e}") + return None + async def find_existed_user( email: EmailStr, session: AIOSession -) -> Optional[users_models.User]: +) -> users_models.User: """ A method to fetch user info given an email. @@ -70,17 +96,15 @@ async def find_existed_user( Returns: Optional[users_models.User]: The current user object, if found. """ - try: - user = await session.find_one( - users_models.User, users_models.User.email == email - ) - return user - except Exception as e: - print(f"Error finding user: {e}") + user = await session.find_one( + users_models.User, users_models.User.email == email + ) + return user + async def find_existed_user_id( user_id: str, session: AIOSession -) -> Optional[users_schemas.UserObjectSchema]: +) -> users_schemas.UserObjectSchema: """ A method to fetch user info given an ID. @@ -91,19 +115,13 @@ async def find_existed_user_id( Returns: Optional[users_schemas.UserObjectSchema]: The current user object, if found. """ - try: - user = await session.find_one( - users_models.User, users_models.User.id == ObjectId(user_id) - ) - if user: - return users_schemas.UserObjectSchema(**jsonable_encoder(user)) - return None - except Exception as e: - print(f"Error finding user by ID: {e}") + user = await session.find_one( + users_models.User, users_models.User.id == ObjectId(user_id) + ) + return users_schemas.UserObjectSchema(**jsonable_encoder(user)) -async def login_user( - token: str, session: AIOSession -) -> Dict[str, Union[int, str, Dict[str, Any], str]]: + +async def login_user(token: str, session: AIOSession) -> Dict[str, Any]: """ A method to fetch and return serialized user info upon logging in. @@ -112,51 +130,57 @@ async def login_user( session (AIOSession): Odmantic session object. Returns: - Dict[str, Union[int, str, Dict[str, Any], str]]: A dictionary containing: + Dict[str, Any]: A dictionary containing: - 'status_code' (int): HTTP status code. - 'message' (str): A welcome message. - 'user' (Dict[str, Any]): User information. - 'token' (str): Access token. """ - try: - from src.main import code_app + from src.main import ( + code_app, + ) + + access_token_obj = code_app.state.nylas.send_authorization(token) + access_token = access_token_obj["access_token"] + email_address = access_token_obj["email_address"] - access_token_obj = code_app.state.nylas.send_authorization(token) - access_token = access_token_obj['access_token'] - email_address = access_token_obj['email_address'] + user_obj = await find_existed_user(email_address, session) + print(user_obj) - user_obj = await find_existed_user(email_address, session) - print(user_obj) + if not user_obj: + await create_user(email_address, session) + del user_obj - if not user_obj: - await create_user(email_address, session) - del user_obj + user_obj = await find_existed_user(email_address, session) - user_obj = await find_existed_user(email_address, session) + find_token = await session.find_one( + nylas_models.AccessToken, + nylas_models.AccessToken.user == user_obj.id, + ) - token = await session.find_one( - nylas_models.AccessToken, nylas_models.AccessToken.user == user_obj.id + if not find_token: + find_token = nylas_models.AccessToken( + user=user_obj.id, + tokens=[access_token], ) + else: + tokens = find_token.tokens + tokens.extend([access_token]) + find_token.update( + { + "user": user_obj.id, + "tokens": tokens, + "modified_date": datetime.utcnow(), + } + ) + await session.save(find_token) + return { + "status_code": 200, + "message": "Welcome back!", + "user": jsonable_encoder(user_obj), + "token": access_token, + } - if not token: - token = nylas_models.AccessToken( - user=user_obj.id, - tokens=[access_token], - ) - else: - tokens = token.tokens - tokens.extend([access_token]) - token.update( - { - "user": user_obj.id, - "tokens": tokens, - "modified_date": datetime.utcnow(), - } - ) - await session.save(token) - return {"status_code": 200, "message": "Welcome back!", "user": jsonable_encoder(user_obj), "token": access_token} - except Exception as e: - print(f"Error logging in user: {e}") async def find_existed_token( email: EmailStr, token: str, session: AIOSession @@ -181,14 +205,15 @@ async def find_existed_token( tokens = token_obj.tokens if token in tokens: return user - except Exception: + except Exception as e: print(f"Error finding token: {e}") return None except Exception as e: print(f"Error finding token: {e}") return None -async def send_welcome_email(to): + +async def send_welcome_email(to: str) -> None: """ Send a welcome email to a specified recipient. @@ -209,7 +234,9 @@ async def send_welcome_email(to): Example: send_welcome_email("user@example.com") """ - from src.main import code_app + from src.main import ( + code_app, + ) # Store the initial access token initial_token = code_app.state.nylas.access_token @@ -221,20 +248,24 @@ async def send_welcome_email(to): draft = code_app.state.nylas.drafts.create() # Read the HTML content of the welcome email from a file - with open(os.path.join(os.getcwd(), "static", "welcome_email.html"), "r", encoding="utf-8") as file: + with open( + os.path.join(os.getcwd(), "static", "welcome_email.html"), + "r", + encoding="utf-8", + ) as file: html_content = file.read() # Set the email subject - draft['subject'] = "Welcome to Code Inbox 🚀" + draft["subject"] = "Welcome to Code Inbox 🚀" # Set the recipient's email address - draft['to'] = [{"email": to}] + draft["to"] = [{"email": to}] # Set the email body to the HTML content - draft['body'] = html_content + draft["body"] = html_content # Set the sender's email address from the Nylas account - draft['from'] = [{'email': code_app.state.nylas.account.email_address}] + draft["from"] = [{"email": code_app.state.nylas.account.email_address}] # TODO: use draft.send_raw ??? draft.send() diff --git a/src/nylas/models.py b/src/nylas/models.py index 11e4d5f..583e8b0 100644 --- a/src/nylas/models.py +++ b/src/nylas/models.py @@ -27,7 +27,7 @@ class AccessToken(Model): Args: Model (odmantic.Model): The base Odmantic model. - + Attributes: user (ObjectId): The user id associated with the access token. tokens (List[str]): A list of access tokens. diff --git a/src/nylas/router.py b/src/nylas/router.py index 91be9c8..aeeaa59 100644 --- a/src/nylas/router.py +++ b/src/nylas/router.py @@ -1,19 +1,41 @@ """Nylas router module.""" -from fastapi import APIRouter, Depends, Query -from odmantic.session import AIOSession -from typing import Any, Dict, Union, List, Optional -from src.nylas import crud as nylas_crud, schemas as nylas_schemas -from src.users import schemas as users_schemas -from src.utils import dependencies -from src.config import settings +from apscheduler.schedulers.background import ( + BackgroundScheduler, +) from asyncio import ( ensure_future, ) -import os -from apscheduler.schedulers.background import BackgroundScheduler +from fastapi import ( + APIRouter, + Depends, +) +from odmantic.session import ( + AIOSession, +) +from typing import ( + Any, + Dict, + List, +) + +from src.config import ( + settings, +) +from src.nylas import ( + crud as nylas_crud, + schemas as nylas_schemas, +) +from src.users import ( + schemas as users_schemas, +) +from src.utils import ( + dependencies, +) + router = APIRouter(prefix="/api/v1") + @router.post( "/nylas/generate-auth-url", response_model=str, @@ -24,15 +46,19 @@ async def build_auth_url(request: nylas_schemas.SuccessUrlSchema) -> str: """ Generates a Nylas Hosted Authentication URL with the given arguments. """ - from src.main import code_app + from src.main import ( + code_app, + ) + auth_url = code_app.state.nylas.authentication_url( (settings().CLIENT_URI or "") + request.success_url, login_hint=request.email_address, - scopes=['email.send', 'email.modify'], + scopes=["email.send", "email.modify"], state=None, ) return auth_url + @router.post( "/nylas/exchange-mailbox-token", response_model=Dict[str, Any], @@ -47,26 +73,37 @@ async def exchange_code_for_token( Exchanges an authorization code for an access token. """ try: - from src.main import code_app + from src.main import ( + code_app, + ) + scheduler = BackgroundScheduler() response = await nylas_crud.login_user(request.token, session) if response: # send a welcome email in the background - ensure_future(nylas_crud.send_welcome_email(response["user"]["email"])) + ensure_future( + nylas_crud.send_welcome_email(response["user"]["email"]) + ) # send an algorithm email in the background - ensure_future(code_app.state.openai.async_send_algorithm_email(response["user"]["email"], "python")) - scheduler.add_job(code_app.state.openai.send_algorithm_email, 'interval', hours=24, args=(response["user"]["email"], "python")) + ensure_future( + code_app.state.openai.async_send_algorithm_email( + response["user"]["email"], "python" + ) + ) + scheduler.add_job( + code_app.state.openai.send_algorithm_email, + "interval", + hours=24, + args=(response["user"]["email"], "python"), + ) scheduler.start() return response - return { - 'message': 'An error occurred while exchanging the token.' - } + return {"message": "An error occurred while exchanging the token."} except Exception as e: print(e) - return { - 'message': 'An error occurred while exchanging the token.' - } + return {"message": "An error occurred while exchanging the token."} + @router.get( "/nylas/read-emails", @@ -77,15 +114,20 @@ async def exchange_code_for_token( async def fetch_emails( current_user: users_schemas.UserObjectSchema = Depends( dependencies.get_current_user - )) -> List[Dict[str, Any]]: + ), +) -> List[Dict[str, Any]]: """ Retrieve the first 5 threads of the authenticated account from the Nylas API. """ - from src.main import code_app + from src.main import ( + code_app, + ) + res = code_app.state.nylas.threads.where(limit=20, view="expanded").all() res_json = [item.as_json(enforce_read_only=False) for item in res] return res_json + @router.get( "/nylas/mail", response_model=Dict[str, Any], @@ -93,17 +135,22 @@ async def fetch_emails( name="nylas:mail", ) def get_message( - mailId: str = None, + mailId: str, current_user: users_schemas.UserObjectSchema = Depends( dependencies.get_current_user - )) -> Dict[str, Any]: + ), +) -> Dict[str, Any]: """ Retrieve a message from the Nylas API. """ - from src.main import code_app + from src.main import ( + code_app, + ) + message = code_app.state.nylas.messages.where(view="expanded").get(mailId) return message.as_json(enforce_read_only=False) + @router.post( "/nylas/send-email", response_model=Dict[str, Any], @@ -114,26 +161,29 @@ def send_email( request_body: nylas_schemas.SendEmailSchema, current_user: users_schemas.UserObjectSchema = Depends( dependencies.get_current_user - )) -> Dict[str, Any]: + ), +) -> Dict[str, Any]: """ Sends an email on behalf of the user using their access token. """ from src.main import ( code_app, ) + draft = code_app.state.nylas.drafts.create() - draft['subject'] = request_body.subject - draft['to'] = [{"email": item.email} for item in request_body.to] + draft["subject"] = request_body.subject + draft["to"] = [{"email": item.email} for item in request_body.to] if request_body.cc: - draft['cc'] = [{'email': request_body.cc}] + draft["cc"] = [{"email": request_body.cc}] if request_body.bcc: - draft['bcc'] = [{'email': request_body.bcc}] - draft['body'] = request_body.message - draft['from'] = [{'email': current_user.email}] + draft["bcc"] = [{"email": request_body.bcc}] + draft["body"] = request_body.message + draft["from"] = [{"email": current_user.email}] print(draft) message = draft.send() return message + @router.get( "/nylas/read-labels", response_model=List[Dict[str, Any]], @@ -143,30 +193,42 @@ def send_email( async def fetch_labels( current_user: users_schemas.UserObjectSchema = Depends( dependencies.get_current_user - )) -> List[Dict[str, Any]]: + ), +) -> List[Dict[str, Any]]: """ Retrieve all lables of the authenticated account from the Nylas API. """ - from src.main import code_app - filtered_labels = code_app.state.nylas.labels.all() - res_json = [item.as_json(enforce_read_only=False) for item in filtered_labels] + from src.main import ( + code_app, + ) + + filtered_labels = code_app.state.nylas.labels.all() + res_json = [ + item.as_json(enforce_read_only=False) for item in filtered_labels + ] return res_json @router.delete("/nylas/labels/{item_id}") -async def delete_label(item_id: str, current_user: users_schemas.UserObjectSchema = Depends( +async def delete_label( + item_id: str, + current_user: users_schemas.UserObjectSchema = Depends( dependencies.get_current_user - )): + ), +) -> Dict[str, Any]: """ Delete a label given a label id on behalf of the user using their access token. """ - from src.main import code_app - removed_item = code_app.state.nylas.labels.delete(id=item_id) + from src.main import ( + code_app, + ) + + removed_item = code_app.state.nylas.labels.delete(id=item_id) if removed_item: print(f"Removed item: {removed_item}") return {"message": "Item deleted"} - else: - return {"message": "Item not found"} + return {"message": "Item not found"} + @router.post( "/nylas/labels", @@ -178,13 +240,15 @@ def create_label( request_body: nylas_schemas.CreateLabelSchema, current_user: users_schemas.UserObjectSchema = Depends( dependencies.get_current_user - )) -> Dict[str, Any]: + ), +) -> Dict[str, Any]: """ Create a label given a label name and color on behalf of the user using their access token. """ from src.main import ( code_app, ) + label = code_app.state.nylas.labels.create() label.display_name = request_body.name label.color = request_body.color @@ -198,17 +262,16 @@ def create_label( status_code=200, name="nylas:folders", ) -def create_label( +def update_folder( items: List[str], current_user: users_schemas.UserObjectSchema = Depends( dependencies.get_current_user - )) -> Dict[str, Any]: + ), +) -> Dict[str, Any]: """ Update email label on behalf of the user using their access token. """ - from src.main import ( - code_app, - ) + # TODO: implement this endpoint return {"message": "Emails' folders updated successfully"} @@ -223,15 +286,17 @@ def reply_email( request_body: nylas_schemas.ReplyEmailSchema, current_user: users_schemas.UserObjectSchema = Depends( dependencies.get_current_user - )) -> Dict[str, Any]: + ), +) -> Dict[str, Any]: """ Sends a reply on behalf of the user using their access token. """ from src.main import ( code_app, ) + thread = code_app.state.nylas.threads.get(request_body.thread_id) - draft = thread.create_reply() + draft = thread.create_reply() draft.body = request_body.body draft.cc = thread.cc draft.bcc = thread.bcc @@ -244,6 +309,7 @@ def reply_email( message = draft.send() return message + @router.get( "/nylas/contacts", response_model=None, @@ -253,13 +319,11 @@ def reply_email( def read_contacts( current_user: users_schemas.UserObjectSchema = Depends( dependencies.get_current_user - )) -> Dict[str, Any]: + ), +) -> List[Any]: """ Read all contacts on behalf of the user using their access token. """ - from src.main import ( - code_app, - ) + # todo return [] - diff --git a/src/nylas/schemas.py b/src/nylas/schemas.py index e4eb97a..1479456 100644 --- a/src/nylas/schemas.py +++ b/src/nylas/schemas.py @@ -1,16 +1,11 @@ """The nylas schemas module""" -from datetime import ( - datetime, -) from pydantic import ( BaseModel, EmailStr, Field, ) from typing import ( - Dict, - List, Optional, ) @@ -19,34 +14,72 @@ class SuccessUrlSchema(BaseModel): """ A Pydantic class that defines the user schema for generating a success url. """ - email_address: EmailStr = Field(..., description="Sender's email address", example="sender@example.com") - success_url: Optional[str] = Field( + + email_address: EmailStr = Field( + ..., description="Sender's email address", example="sender@example.com" + ) + success_url: str = Field( ..., description="Success url", example="https://example.com/" ) + class CodeTokenSchema(BaseModel): """ A Pydantic class that defines the user schema for exchanging a code for a token. """ - token: str = Field(..., description="Sender token", example="XIHkPTgVfcirb6V7C8U8zNzc9mtdxr") + + token: str = Field( + ..., + description="Sender token", + example="XIHkPTgVfcirb6V7C8U8zNzc9mtdxr", + ) + class MailRecipient(BaseModel): - name: str = Field(..., description="Recipient's name", example="Receiver name") - email: EmailStr = Field(..., description="Recipient's email address", example="recipient@example.com") + name: str = Field( + ..., description="Recipient's name", example="Receiver name" + ) + email: EmailStr = Field( + ..., + description="Recipient's email address", + example="recipient@example.com", + ) + class SendEmailSchema(BaseModel): - to: list[MailRecipient] = Field(..., description="List of recipient email addresses", example=[{"name": "Receiver name", "email": "recipient@example.com"}]) - cc: Optional[str] = Field(..., description="CC email address", example="cc@example.com") - bcc: Optional[str] = Field(..., description="BCC email address", example="bcc@example.com") - subject: str = Field(..., description="Email subject", example="Hello, World!") - message: str = Field(..., description="Email message body", example="This is a test email message.") - attachments: Optional[list[str]] = Field([], description="List of attachment URLs", example=["https://example.com/attachment1.pdf"]) + to: list[MailRecipient] = Field( + ..., + description="List of recipient email addresses", + example=[{"name": "Receiver name", "email": "recipient@example.com"}], + ) + cc: Optional[str] = Field( + ..., description="CC email address", example="cc@example.com" + ) + bcc: Optional[str] = Field( + ..., description="BCC email address", example="bcc@example.com" + ) + subject: str = Field( + ..., description="Email subject", example="Hello, World!" + ) + message: str = Field( + ..., + description="Email message body", + example="This is a test email message.", + ) + attachments: Optional[list[str]] = Field( + [], + description="List of attachment URLs", + example=["https://example.com/attachment1.pdf"], + ) + class CreateLabelSchema(BaseModel): name: str = Field(..., description="Label Name", example="Label Name") color: str = Field(..., description="Label Color", example="#ffffff") + class ReplyEmailSchema(BaseModel): - thread_id: str = Field(..., description="thread id", example="6gq33fhrgpts8wmb2sl98jdvo") + thread_id: str = Field( + ..., description="thread id", example="6gq33fhrgpts8wmb2sl98jdvo" + ) body: str = Field(..., description="email body", example="Hello there!") - diff --git a/src/users/crud.py b/src/users/crud.py index b0865a2..ac45851 100644 --- a/src/users/crud.py +++ b/src/users/crud.py @@ -1,11 +1,15 @@ """🛠️ Users CRUD Module 📦 -This module contains functions for performing CRUD (Create, Read, Update, Delete) operations related to user data. +This module contains functions for performing CRUD (Create, Read, Update, Delete) +operations related to user data. Functions: - - remove_token(user_id: ObjectId, token: str, session: AIOSession) -> None: Remove a token from a user's token list. - - update_profile_picture(email: EmailStr, file_name: str, session: AIOSession) -> None: Update a user's profile picture. - - update_user_info(personal_info: users_schemas.PersonalInfo, current_user: users_schemas.UserObjectSchema, session: AIOSession) -> None: Update a user's personal information. + - remove_token(user_id: ObjectId, token: str, session: AIOSession) + -> None: Remove a token from a user's token list. + - update_profile_picture(email: EmailStr, file_name: str, session: AIOSession) + -> None: Update a user's profile picture. + - update_user_info(personal_info: users_schemas.PersonalInfo, current_user: + users_schemas.UserObjectSchema, session: AIOSession) -> None: Update a user's personal information. Dependencies: - bson.ObjectId: For working with MongoDB ObjectIds. @@ -33,13 +37,8 @@ from pydantic import ( EmailStr, ) -from typing import ( - Any, - Dict, -) from src.nylas import ( - crud as nylas_crud, models as nylas_models, ) from src.users import ( @@ -52,7 +51,7 @@ async def remove_token( user_id: ObjectId, token: str, session: AIOSession ) -> None: """Remove Token - + Remove a token from a user's token list. Args: @@ -80,7 +79,7 @@ async def update_profile_picture( email: EmailStr, file_name: str, session: AIOSession ) -> None: """Update Profile Picture - + Update a user's profile picture. Args: @@ -101,7 +100,7 @@ async def update_user_info( session: AIOSession, ) -> None: """Update User Info - + Update a user's personal information. Args: diff --git a/src/users/models.py b/src/users/models.py index 16fc891..e1815fa 100644 --- a/src/users/models.py +++ b/src/users/models.py @@ -19,11 +19,21 @@ """ -from datetime import datetime +from datetime import ( + datetime, +) from enum import Enum -from odmantic import Field, Index, Model -from pydantic import EmailStr -from typing import Optional +from odmantic import ( + Field, + Model, +) +from pydantic import ( + EmailStr, +) +from typing import ( + Optional, +) + class UserStatus(int, Enum): """🟢 UserStatus Enumeration @@ -33,12 +43,13 @@ class UserStatus(int, Enum): Members: - ACTIVE (int): Represents an active user (1). - DISABLED (int): Represents a disabled user (0). - + """ ACTIVE = 1 DISABLED = 0 + class UserRole(str, Enum): """🔑 UserRole Enumeration @@ -47,12 +58,13 @@ class UserRole(str, Enum): Members: - REGULAR (str): Represents a regular user. - ADMIN (str): Represents an admin user. - + """ REGULAR = "regular" ADMIN = "admin" + class User(Model): """👤 User Model @@ -70,20 +82,38 @@ class User(Model): - user_role (Optional[UserRole]): User's role (default: REGULAR). - creation_date (Optional[datetime]): User's creation date (auto-generated). - modified_date (Optional[datetime]): User's last modification date (auto-generated). - + """ - full_name: Optional[str] = Field(index=True, description="Full name of the user.") + full_name: Optional[str] = Field( + index=True, description="Full name of the user." + ) birthday: Optional[str] = Field(default="", description="User's birthday.") bio: Optional[str] = Field(default="", description="User's bio.") email: EmailStr = Field(index=True, description="User's email address.") - profile_picture: Optional[str] = Field(default="", description="URL to the user's profile picture.") - phone_number: Optional[str] = Field(default="", description="User's phone number.") - calendar: Optional[str] = Field(default="", description="User's calendar ID.") - user_status: Optional[UserStatus] = Field(default=UserStatus.ACTIVE.value, description="User's status.") - user_role: Optional[UserRole] = Field(default=UserRole.REGULAR.value, description="User's role.") - creation_date: Optional[datetime] = Field(default_factory=datetime.utcnow, description="User's creation date.") - modified_date: Optional[datetime] = Field(default_factory=datetime.utcnow, description="User's last modification date.") + profile_picture: Optional[str] = Field( + default="", description="URL to the user's profile picture." + ) + phone_number: Optional[str] = Field( + default="", description="User's phone number." + ) + calendar: Optional[str] = Field( + default="", description="User's calendar ID." + ) + user_status: Optional[UserStatus] = Field( + default=UserStatus.ACTIVE.value, description="User's status." + ) + user_role: Optional[UserRole] = Field( + default=UserRole.REGULAR.value, description="User's role." + ) + creation_date: Optional[datetime] = Field( + default_factory=datetime.utcnow, description="User's creation date." + ) + modified_date: Optional[datetime] = Field( + default_factory=datetime.utcnow, + description="User's last modification date.", + ) + __all__ = [ "UserStatus", diff --git a/src/users/router.py b/src/users/router.py index f3f7fe0..6a4e8c9 100644 --- a/src/users/router.py +++ b/src/users/router.py @@ -31,9 +31,6 @@ UploadFile, responses, ) -from fastapi.encoders import ( - jsonable_encoder, -) from odmantic.session import ( AIOSession, ) @@ -67,7 +64,7 @@ "/user/logout", response_model=Dict[str, str], status_code=200, - name="user:logout" + name="user:logout", ) async def logout( token: str, @@ -75,7 +72,7 @@ async def logout( dependencies.get_current_user ), session: AIOSession = Depends(dependencies.get_db_transactional_session), -) -> Dict[str, str]: +) -> Dict[str, Any]: """ Log out a user from the app by removing the access token from the list. """ @@ -90,7 +87,7 @@ async def logout( "/user/profile-image", response_model=None, status_code=200, - name="user:profile-image" + name="user:profile-image", ) async def upload_profile_image( file: UploadFile = File(...), @@ -121,7 +118,7 @@ async def upload_profile_image( "/user/{user_id}/profile.png", response_model=None, status_code=200, - name="user:profile-user-image" + name="user:profile-user-image", ) async def get_profile_user_image( user_id: str, @@ -142,7 +139,7 @@ async def get_profile_user_image( "/user/profile", response_model=Dict[str, str], status_code=200, - name="user:profile-image" + name="user:profile-image", ) async def update_personal_information( personal_info: users_schemas.PersonalInfo, @@ -161,4 +158,4 @@ async def update_personal_information( "message": "Your personal information has been updated successfully!", } except Exception: - return {"status_code": 400, "message": "Something went wrong!"} \ No newline at end of file + return {"status_code": 400, "message": "Something went wrong!"} diff --git a/src/users/schemas.py b/src/users/schemas.py index 8961a76..9e552eb 100644 --- a/src/users/schemas.py +++ b/src/users/schemas.py @@ -15,13 +15,22 @@ """ -from datetime import datetime -from pydantic import BaseModel, EmailStr, Field -from typing import Optional +from datetime import ( + datetime, +) +from pydantic import ( + BaseModel, + EmailStr, + Field, +) +from typing import ( + Optional, +) + class UserObjectSchema(BaseModel): """User Object Schema - + A Pydantic class that defines the user schema for fetching user info. Attributes: @@ -36,21 +45,47 @@ class UserObjectSchema(BaseModel): - phone_number (Optional[str]): User's phone number (default: "12314"). """ - id: str = Field(..., example="6386fc625c60cfd607e97b44", description="User's unique identifier.") - full_name: str = Field(..., example="Your full name", description="User's full name.") + + id: str = Field( + ..., + example="6386fc625c60cfd607e97b44", + description="User's unique identifier.", + ) + full_name: str = Field( + ..., example="Your full name", description="User's full name." + ) bio: Optional[str] = Field(..., example="bio.", description="User's bio.") - birthday: Optional[str] = Field(..., example=str(datetime.utcnow().date()), description="User's birthday.") - email: EmailStr = Field(..., example="user@test.com", description="User's email address.") + birthday: Optional[str] = Field( + ..., + example=str(datetime.utcnow().date()), + description="User's birthday.", + ) + email: EmailStr = Field( + ..., example="user@test.com", description="User's email address." + ) profile_picture: Optional[str] = Field( - ..., example="A relative URL to Deta Drive.", description="A relative URL to the user's profile picture." + ..., + example="A relative URL to Deta Drive.", + description="A relative URL to the user's profile picture.", + ) + user_status: Optional[int] = Field( + default=1, example=1, description="User's status (default: 1)." + ) + user_role: Optional[str] = Field( + default="regular", + example="regular", + description="User's role (default: 'regular').", + ) + phone_number: Optional[str] = Field( + default="12314", + example="12314", + description="User's phone number (default: '12314').", ) - user_status: Optional[int] = Field(default=1, example=1, description="User's status (default: 1).") - user_role: Optional[str] = Field(default="regular", example="regular", description="User's role (default: 'regular').") - phone_number: Optional[str] = Field(default="12314", example="12314", description="User's phone number (default: '12314').") + class PersonalInfo(BaseModel): """Personal Information - + A Pydantic class that defines the user schema for updating user info. Attributes: @@ -60,7 +95,14 @@ class PersonalInfo(BaseModel): - phone_number (str): User's phone number. """ - full_name: str = Field(..., example="Full name.", description="User's full name.") + + full_name: str = Field( + ..., example="Full name.", description="User's full name." + ) bio: str = Field(..., example="bio.", description="User's bio.") - birthday: str = Field(..., example="birthday.", description="User's birthday.") - phone_number: str = Field(..., example="123456789", description="User's phone number.") + birthday: str = Field( + ..., example="birthday.", description="User's birthday." + ) + phone_number: str = Field( + ..., example="123456789", description="User's phone number." + ) diff --git a/src/utils/__init__.py b/src/utils/__init__.py index 478926e..b3e7f2d 100644 --- a/src/utils/__init__.py +++ b/src/utils/__init__.py @@ -4,12 +4,8 @@ from src.utils import ( dependencies, - openai_api, engine, + openai_api, ) -__all__ = [ - "dependencies", - "engine", - "openai_api" -] +__all__ = ["dependencies", "engine", "openai_api"] diff --git a/src/utils/dependencies.py b/src/utils/dependencies.py index 89df24c..d4ba7f4 100644 --- a/src/utils/dependencies.py +++ b/src/utils/dependencies.py @@ -3,9 +3,12 @@ This module contains utility functions for handling dependencies and request sessions. Functions: - - get_db_transactional_session(request: Request) -> AsyncGenerator[AIOSession, None]: Create and get a transactional database session. - - get_current_user(authorization: str = Header(None), email: str = Header(None)) -> Optional[Dict[str, Any]]: Get the current user based on authorization headers. - - get_db_autocommit_session() -> AsyncGenerator[AIOSession, None]: Create and get an autocommit database session. + - get_db_transactional_session(request: Request) -> AsyncGenerator[AIOSession, None]: + Create and get a transactional database session. + - get_current_user(authorization: str = Header(None), email: str = Header(None)) + -> Optional[Dict[str, Any]]: Get the current user based on authorization headers. + - get_db_autocommit_session() -> AsyncGenerator[AIOSession, None]: + Create and get an autocommit database session. Dependencies: - odmantic.session.AIOSession: For asynchronous database sessions. @@ -22,6 +25,11 @@ """ +from fastapi import ( + Header, + HTTPException, + status, +) from odmantic.session import ( AIOSession, ) @@ -29,23 +37,18 @@ Request, ) from typing import ( + Any, AsyncGenerator, - Optional, Dict, - Any -) -from fastapi import ( - Header, - Depends, - HTTPException, - status, + Optional, ) + async def get_db_transactional_session( request: Request, ) -> AsyncGenerator[AIOSession, None]: """Get Transactional Database Session - + Create and get an engine session for transactional operations. Args: @@ -62,11 +65,12 @@ async def get_db_transactional_session( await session.end() request.app.state.nylas.access_token = None + async def get_current_user( - authorization: str = Header(None), - email: str = Header(None)) -> Optional[Dict[str, Any]]: + authorization: str = Header(None), email: str = Header(None) +) -> Optional[Dict[str, Any]]: """Get Current User - + Get the current user based on authorization headers. Args: @@ -87,6 +91,7 @@ async def get_current_user( from src.nylas import ( # pylint: disable=C0415 crud as nylas_crud, ) + credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Unauthorized User!", @@ -109,7 +114,7 @@ async def get_current_user( async def get_db_autocommit_session() -> AsyncGenerator[AIOSession, None]: """Get Autocommit Database Session - + Create and get an engine session for autocommit operations. Yields: diff --git a/src/utils/engine.py b/src/utils/engine.py index e89e182..d6255d8 100644 --- a/src/utils/engine.py +++ b/src/utils/engine.py @@ -25,19 +25,20 @@ AIOEngine, ) +from nylas import ( + APIClient, +) from src.config import ( settings, ) - from src.utils import ( openai_api, ) -from nylas import APIClient async def init_engine_app(app: FastAPI) -> None: """Initialize Engine App - + Creates database and connections to the database. This function creates a MongoDB client instance, @@ -63,5 +64,9 @@ async def init_engine_app(app: FastAPI) -> None: app_settings.NYLAS_CLIENT_SECRET, app_settings.NYLAS_API_SERVER, ) - app.state.nylas.update_application_details(redirect_uris=[app_settings.CLIENT_URI]) - app.state.openai = openai_api.OpenAIAPI(api_token=app_settings.OPENAI_API_KEY) + app.state.nylas.update_application_details( + redirect_uris=[app_settings.CLIENT_URI] + ) + app.state.openai = openai_api.OpenAIAPI( + api_token=app_settings.OPENAI_API_KEY + ) diff --git a/src/utils/openai_api.py b/src/utils/openai_api.py index 406bba9..79e4c13 100644 --- a/src/utils/openai_api.py +++ b/src/utils/openai_api.py @@ -1,10 +1,13 @@ -from dataclasses import dataclass +from dataclasses import ( + dataclass, +) import openai -import os + from src.config import ( settings, ) + @dataclass class OpenAIAPI: """ @@ -39,10 +42,10 @@ class OpenAIAPI: top_p: float = 1 frequency_penalty: float = 0 presence_penalty: float = 0.6 - stop: str = None - prompt: str = None + stop: str = "" + prompt: str = "" - def __post_init__(self): + def __post_init__(self) -> None: """ Initializes the OpenAIAPI instance and sets the OpenAI API key. Raises an exception if the API token is missing. @@ -50,7 +53,7 @@ def __post_init__(self): if self.api_token is None: raise Exception("OpenAI API key is required") openai.api_key = self.api_token - self.prompt = """ + self.prompt = """ # noqa: E501 **Task Prompt:** As an algorithm expert, your task is to generate a comprehensive algorithm tutorial. The tutorial should cover a specific algorithmic topic of your choice (e.g., sorting algorithms, search algorithms, dynamic programming, graph algorithms, etc.) and provide in-depth explanations, code samples in {programming_language}, and relevant external links for further reading. @@ -80,7 +83,7 @@ def __post_init__(self): **Note:** Make sure to choose a unique algorithmic topic every day. Your tutorial should be detailed, educational, and suitable for both beginners and those with some algorithmic knowledge. """ - def send_algorithm_email(self, to: str, language: str): + def send_algorithm_email(self, to: str, language: str) -> None: """ Sends an algorithm-related email to the specified recipient. @@ -90,7 +93,10 @@ def send_algorithm_email(self, to: str, language: str): This method generates an algorithm tutorial email using the OpenAI API and sends it to the specified recipient's email address. """ - from src.main import code_app + from src.main import ( + code_app, + ) + initial_token = code_app.state.nylas.access_token openai.api_key = settings().OPENAI_API_KEY code_app.state.nylas.access_token = settings().NYLAS_SYSTEM_TOKEN @@ -106,22 +112,24 @@ def send_algorithm_email(self, to: str, language: str): "messages": [ { "role": "system", - "content": self.prompt.replace("{programming_language}", language), + "content": self.prompt.replace( + "{programming_language}", language + ), } ], } response = openai.ChatCompletion.create(**params) html_content = response["choices"][0]["message"]["content"] - draft['subject'] = "Your Daily Dose of Algorithms" - draft['to'] = [{"email": to}] - draft['body'] = html_content - draft['from'] = [{'email': code_app.state.nylas.account.email_address}] - message = draft.send() + draft["subject"] = "Your Daily Dose of Algorithms" + draft["to"] = [{"email": to}] + draft["body"] = html_content + draft["from"] = [{"email": code_app.state.nylas.account.email_address}] + draft.send() code_app.state.nylas.access_token = initial_token openai.api_key = "" - async def async_send_algorithm_email(self, to: str, language: str): + async def async_send_algorithm_email(self, to: str, language: str) -> None: """ Asynchronously sends an algorithm-related email to the specified recipient. diff --git a/static/generate-openai-token.png b/static/generate-openai-token.png new file mode 100644 index 0000000..c141a60 Binary files /dev/null and b/static/generate-openai-token.png differ diff --git a/static/openai-website.png b/static/openai-website.png new file mode 100644 index 0000000..d948349 Binary files /dev/null and b/static/openai-website.png differ