From ecd634e49715bb57fdfeb35b9dba21a6e94cf012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 19 Apr 2019 09:45:23 +0400 Subject: [PATCH] :sparkles: Add Items (crud, models, endpoints), utils, refactor (#14) * Update CRUD utils to use types better. * Simplify Pydantic model names, from `UserInCreate` to `UserCreate`, etc. * Upgrade packages. * Add new generic "Items" models, crud utils, endpoints, and tests. To facilitate re-using them to create new functionality. As they are simple and generic (not like Users), it's easier to copy-paste and adapt them to each use case. * Update endpoints/*path operations* to simplify code and use new utilities, prefix and tags in `include_router`. * Update testing utils. * Update linting rules, relax vulture to reduce false positives. * Update migrations to include new Items. * Update project README.md with tips about how to start with backend. --- README.md | 11 + {{cookiecutter.project_slug}}/README.md | 4 +- .../backend/app/Pipfile | 2 +- .../backend/app/Pipfile.lock | 1022 ----------------- ...sion.py => d4867f3a4c0a_first_revision.py} | 21 +- .../backend/app/app/api/api_v1/api.py | 9 +- .../app/app/api/api_v1/endpoints/items.py | 102 ++ .../api_v1/endpoints/{token.py => login.py} | 0 .../api_v1/endpoints/{user.py => users.py} | 39 +- .../app/app/api/api_v1/endpoints/utils.py | 8 +- .../backend/app/app/crud/__init__.py | 2 +- .../backend/app/app/crud/item.py | 54 + .../backend/app/app/crud/user.py | 22 +- .../backend/app/app/db/base.py | 1 + .../backend/app/app/db/init_db.py | 4 +- .../backend/app/app/db_models/item.py | 12 + .../backend/app/app/db_models/user.py | 2 + .../backend/app/app/models/item.py | 34 + .../backend/app/app/models/user.py | 4 +- .../app/app/tests/api/api_v1/test_celery.py | 2 +- .../app/app/tests/api/api_v1/test_items.py | 34 + .../api_v1/{test_token.py => test_login.py} | 0 .../api_v1/{test_user.py => test_users.py} | 12 +- .../backend/app/app/tests/crud/test_item.py | 61 + .../backend/app/app/tests/crud/test_user.py | 16 +- .../backend/app/app/tests/utils/item.py | 17 + .../backend/app/app/tests/utils/user.py | 12 + .../backend/app/app/tests_pre_start.py | 2 +- .../backend/app/scripts/lint.sh | 2 +- .../backend/backend.dockerfile | 2 +- .../backend/celeryworker.dockerfile | 2 +- .../backend/tests.dockerfile | 2 +- 32 files changed, 426 insertions(+), 1091 deletions(-) delete mode 100644 {{cookiecutter.project_slug}}/backend/app/Pipfile.lock rename {{cookiecutter.project_slug}}/backend/app/alembic/versions/{e6ae69e9dcb9_first_revision.py => d4867f3a4c0a_first_revision.py} (59%) create mode 100644 {{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/items.py rename {{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/{token.py => login.py} (100%) rename {{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/{user.py => users.py} (80%) create mode 100644 {{cookiecutter.project_slug}}/backend/app/app/crud/item.py create mode 100755 {{cookiecutter.project_slug}}/backend/app/app/db_models/item.py create mode 100644 {{cookiecutter.project_slug}}/backend/app/app/models/item.py create mode 100644 {{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_items.py rename {{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/{test_token.py => test_login.py} (100%) rename {{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/{test_user.py => test_users.py} (90%) create mode 100644 {{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_item.py create mode 100644 {{cookiecutter.project_slug}}/backend/app/app/tests/utils/item.py diff --git a/README.md b/README.md index 1aacaab1da..bca741b652 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,17 @@ After using this generator, your new project (the directory created) will contai ### Next release +* PR #14: + * Update CRUD utils to use types better. + * Simplify Pydantic model names, from `UserInCreate` to `UserCreate`, etc. + * Upgrade packages. + * Add new generic "Items" models, crud utils, endpoints, and tests. To facilitate re-using them to create new functionality. As they are simple and generic (not like Users), it's easier to copy-paste and adapt them to each use case. + * Update endpoints/*path operations* to simplify code and use new utilities, prefix and tags in `include_router`. + * Update testing utils. + * Update linting rules, relax vulture to reduce false positives. + * Update migrations to include new Items. + * Update project README.md with tips about how to start with backend. + * Upgrade Python to 3.7 as Celery is now compatible too. PR #10 by @ebreton. ### 0.2.2 diff --git a/{{cookiecutter.project_slug}}/README.md b/{{cookiecutter.project_slug}}/README.md index cfa8cbffb0..bc75538feb 100644 --- a/{{cookiecutter.project_slug}}/README.md +++ b/{{cookiecutter.project_slug}}/README.md @@ -53,7 +53,9 @@ If your Docker is not running in `localhost` (the URLs above wouldn't work) chec ### General workflow -Add and modify SQLAlchemy models in `./backend/app/app/db_models/`, Pydantic models in `./backend/app/app/models` and API endpoints in `./backend/app/app/api/`. +Open your editor at `./backend/app/` (instead of the project root: `./`), so that you see an `./app/` directory with your code inside. That way, your editor will be able to find all the imports, etc. + +Modify or add SQLAlchemy models in `./backend/app/app/db_models/`, Pydantic models in `./backend/app/app/models/`, API endpoints in `./backend/app/app/api/`, CRUD (Create, Read, Update, Delete) utils in `./backend/app/app/crud/`. The easiest might be to copy the ones for Items (models, endpoints, and CRUD utils) and update them to your needs. Add and modify tasks to the Celery worker in `./backend/app/app/worker.py`. diff --git a/{{cookiecutter.project_slug}}/backend/app/Pipfile b/{{cookiecutter.project_slug}}/backend/app/Pipfile index 6dd0374e0f..23090edabf 100644 --- a/{{cookiecutter.project_slug}}/backend/app/Pipfile +++ b/{{cookiecutter.project_slug}}/backend/app/Pipfile @@ -20,7 +20,7 @@ pyjwt = "*" python-multipart = "*" email-validator = "*" requests = "*" -celery = "~=4.3" +celery = "*" passlib = {extras = ["bcrypt"],version = "*"} tenacity = "*" pydantic = "*" diff --git a/{{cookiecutter.project_slug}}/backend/app/Pipfile.lock b/{{cookiecutter.project_slug}}/backend/app/Pipfile.lock deleted file mode 100644 index 3358988a0b..0000000000 --- a/{{cookiecutter.project_slug}}/backend/app/Pipfile.lock +++ /dev/null @@ -1,1022 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "9e6b6eaf001ef1b6097d2ecccae8151ade81f5c4ac0f02791ec2248008ddcddf" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.6" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "alembic": { - "hashes": [ - "sha256:16505782b229007ae905ef9e0ae6e880fddafa406f086ac7d442c1aaf712f8c2" - ], - "index": "pypi", - "version": "==1.0.7" - }, - "amqp": { - "hashes": [ - "sha256:16056c952e8029ce8db097edf0d7c2fe2ba9de15d30ba08aee2c5221273d8e23", - "sha256:6816eed27521293ee03aa9ace300a07215b11fee4e845588a9b863a7ba30addb" - ], - "version": "==2.4.1" - }, - "bcrypt": { - "hashes": [ - "sha256:0ba875eb67b011add6d8c5b76afbd92166e98b1f1efab9433d5dc0fafc76e203", - "sha256:21ed446054c93e209434148ef0b362432bb82bbdaf7beef70a32c221f3e33d1c", - "sha256:28a0459381a8021f57230954b9e9a65bb5e3d569d2c253c5cac6cb181d71cf23", - "sha256:2aed3091eb6f51c26b7c2fad08d6620d1c35839e7a362f706015b41bd991125e", - "sha256:2fa5d1e438958ea90eaedbf8082c2ceb1a684b4f6c75a3800c6ec1e18ebef96f", - "sha256:3a73f45484e9874252002793518da060fb11eaa76c30713faa12115db17d1430", - "sha256:3e489787638a36bb466cd66780e15715494b6d6905ffdbaede94440d6d8e7dba", - "sha256:44636759d222baa62806bbceb20e96f75a015a6381690d1bc2eda91c01ec02ea", - "sha256:678c21b2fecaa72a1eded0cf12351b153615520637efcadc09ecf81b871f1596", - "sha256:75460c2c3786977ea9768d6c9d8957ba31b5fbeb0aae67a5c0e96aab4155f18c", - "sha256:8ac06fb3e6aacb0a95b56eba735c0b64df49651c6ceb1ad1cf01ba75070d567f", - "sha256:8fdced50a8b646fff8fa0e4b1c5fd940ecc844b43d1da5a980cb07f2d1b1132f", - "sha256:9b2c5b640a2da533b0ab5f148d87fb9989bf9bcb2e61eea6a729102a6d36aef9", - "sha256:a9083e7fa9adb1a4de5ac15f9097eb15b04e2c8f97618f1b881af40abce382e1", - "sha256:b7e3948b8b1a81c5a99d41da5fb2dc03ddb93b5f96fcd3fd27e643f91efa33e1", - "sha256:b998b8ca979d906085f6a5d84f7b5459e5e94a13fc27c28a3514437013b6c2f6", - "sha256:dd08c50bc6f7be69cd7ba0769acca28c846ec46b7a8ddc2acf4b9ac6f8a7457e", - "sha256:de5badee458544ab8125e63e39afeedfcf3aef6a6e2282ac159c95ae7472d773", - "sha256:ede2a87333d24f55a4a7338a6ccdccf3eaa9bed081d1737e0db4dbd1a4f7e6b6" - ], - "version": "==3.1.6" - }, - "billiard": { - "hashes": [ - "sha256:42d9a227401ac4fba892918bba0a0c409def5435c4b483267ebfe821afaaba0e" - ], - "version": "==3.5.0.5" - }, - "cachetools": { - "hashes": [ - "sha256:219b7dc6024195b6f2bc3d3f884d1fef458745cd323b04165378622dcc823852", - "sha256:9efcc9fab3b49ab833475702b55edd5ae07af1af7a4c627678980b45e459c460" - ], - "version": "==3.1.0" - }, - "celery": { - "hashes": [ - "sha256:77dab4677e24dc654d42dfbdfed65fa760455b6bb563a0877ecc35f4cfcfc678", - "sha256:ad7a7411772b80a4d6c64f2f7f723200e39fb66cf614a7fdfab76d345acc7b13" - ], - "index": "pypi", - "version": "==4.2.1" - }, - "certifi": { - "hashes": [ - "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", - "sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033" - ], - "version": "==2018.11.29" - }, - "cffi": { - "hashes": [ - "sha256:0b5f895714a7a9905148fc51978c62e8a6cbcace30904d39dcd0d9e2265bb2f6", - "sha256:27cdc7ba35ee6aa443271d11583b50815c4bb52be89a909d0028e86c21961709", - "sha256:2d4a38049ea93d5ce3c7659210393524c1efc3efafa151bd85d196fa98fce50a", - "sha256:3262573d0d60fc6b9d0e0e6e666db0e5045cbe8a531779aa0deb3b425ec5a282", - "sha256:358e96cfffc185ab8f6e7e425c7bb028931ed08d65402fbcf3f4e1bff6e66556", - "sha256:37c7db824b5687fbd7ea5519acfd054c905951acc53503547c86be3db0580134", - "sha256:39b9554dfe60f878e0c6ff8a460708db6e1b1c9cc6da2c74df2955adf83e355d", - "sha256:42b96a77acf8b2d06821600fa87c208046decc13bd22a4a0e65c5c973443e0da", - "sha256:5b37dde5035d3c219324cac0e69d96495970977f310b306fa2df5910e1f329a1", - "sha256:5d35819f5566d0dd254f273d60cf4a2dcdd3ae3003dfd412d40b3fe8ffd87509", - "sha256:5df73aa465e53549bd03c819c1bc69fb85529a5e1a693b7b6cb64408dd3970d1", - "sha256:7075b361f7a4d0d4165439992d0b8a3cdfad1f302bf246ed9308a2e33b046bd3", - "sha256:7678b5a667b0381c173abe530d7bdb0e6e3b98e062490618f04b80ca62686d96", - "sha256:7dfd996192ff8a535458c17f22ff5eb78b83504c34d10eefac0c77b1322609e2", - "sha256:8a3be5d31d02c60f84c4fd4c98c5e3a97b49f32e16861367f67c49425f955b28", - "sha256:9812e53369c469506b123aee9dcb56d50c82fad60c5df87feb5ff59af5b5f55c", - "sha256:9b6f7ba4e78c52c1a291d0c0c0bd745d19adde1a9e1c03cb899f0c6efd6f8033", - "sha256:a85bc1d7c3bba89b3d8c892bc0458de504f8b3bcca18892e6ed15b5f7a52ad9d", - "sha256:aa6b9c843ad645ebb12616de848cc4e25a40f633ccc293c3c9fe34107c02c2ea", - "sha256:bae1aa56ee00746798beafe486daa7cfb586cd395c6ce822ba3068e48d761bc0", - "sha256:bae96e26510e4825d5910a196bf6b5a11a18b87d9278db6d08413be8ea799469", - "sha256:bd78df3b594013b227bf31d0301566dc50ba6f40df38a70ded731d5a8f2cb071", - "sha256:c2711197154f46d06f73542c539a0ff5411f1951fab391e0a4ac8359badef719", - "sha256:d998c20e3deed234fca993fd6c8314cb7cbfda05fd170f1bd75bb5d7421c3c5a", - "sha256:df4f840d77d9e37136f8e6b432fecc9d6b8730f18f896e90628712c793466ce6", - "sha256:f5653c2581acb038319e6705d4e3593677676df14b112f13e0b5b44b6a18df1a", - "sha256:f7c7aa485a2e2250d455148470ffd0195eecc3d845122635202d7467d6f7b4cf", - "sha256:f9e2c66a6493147de835f207f198540a56b26745ce4f272fbc7c2f2cfebeb729" - ], - "version": "==1.12.1" - }, - "chardet": { - "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" - ], - "version": "==3.0.4" - }, - "click": { - "hashes": [ - "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", - "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" - ], - "version": "==7.0" - }, - "cssselect": { - "hashes": [ - "sha256:066d8bc5229af09617e24b3ca4d52f1f9092d9e061931f4184cd572885c23204", - "sha256:3b5103e8789da9e936a68d993b70df732d06b8bb9a337a05ed4eb52c17ef7206" - ], - "version": "==1.0.3" - }, - "cssutils": { - "hashes": [ - "sha256:a2fcf06467553038e98fea9cfe36af2bf14063eb147a70958cfcaa8f5786acaf", - "sha256:c74dbe19c92f5052774eadb15136263548dd013250f1ed1027988e7fef125c8d" - ], - "version": "==1.0.2" - }, - "dataclasses": { - "hashes": [ - "sha256:454a69d788c7fda44efd71e259be79577822f5e3f53f029a22d08004e951dc9f", - "sha256:6988bd2b895eef432d562370bb707d540f32f7360ab13da45340101bc2307d84" - ], - "markers": "python_version < '3.7'", - "version": "==0.6" - }, - "dnspython": { - "hashes": [ - "sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01", - "sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d" - ], - "version": "==1.16.0" - }, - "email-validator": { - "hashes": [ - "sha256:ddc4b5b59fa699bb10127adcf7ad4de78fde4ec539a072b104b8bb16da666ae5" - ], - "index": "pypi", - "version": "==1.0.3" - }, - "emails": { - "hashes": [ - "sha256:2d93bb09539d65a16cf1f68db4ffd0f7f45067633e950866e8a4ef89a7c290ec", - "sha256:fcc02567a528eae6b66d2a5c20ce7a0326e4f6b201bc8ae302f89413164db06a" - ], - "index": "pypi", - "version": "==0.5.15" - }, - "fastapi": { - "hashes": [ - "sha256:06225ac528daec555d5d8488828c9adc1570c0627800abc52481696b2a5e4d1f", - "sha256:b37d74e197e6dbb54e3c397fe6dd270e477daa4b016ebb25366d6c9839aca298" - ], - "index": "pypi", - "version": "==0.6.0" - }, - "gunicorn": { - "hashes": [ - "sha256:aa8e0b40b4157b36a5df5e599f45c9c76d6af43845ba3b3b0efe2c70473c2471", - "sha256:fa2662097c66f920f53f70621c6c58ca4a3c4d3434205e608e121b5b3b71f4f3" - ], - "index": "pypi", - "version": "==19.9.0" - }, - "h11": { - "hashes": [ - "sha256:acca6a44cb52a32ab442b1779adf0875c443c689e9e028f8d831a3769f9c5208", - "sha256:f2b1ca39bfed357d1f19ac732913d5f9faa54a5062eca7d2ec3a916cfb7ae4c7" - ], - "version": "==0.8.1" - }, - "httptools": { - "hashes": [ - "sha256:04c7703bbef0e8ca28b09811547352b8c7c20549eab70dc24e536bb24fd2b7c5" - ], - "version": "==0.0.11" - }, - "idna": { - "hashes": [ - "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", - "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" - ], - "version": "==2.8" - }, - "jinja2": { - "hashes": [ - "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", - "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" - ], - "index": "pypi", - "version": "==2.10" - }, - "kombu": { - "hashes": [ - "sha256:529df9e0ecc0bad9fc2b376c3ce4796c41b482cf697b78b71aea6ebe7ca353c8", - "sha256:7a2cbed551103db9a4e2efafe9b63222e012a61a18a881160ad797b9d4e1d0a1" - ], - "version": "==4.3.0" - }, - "lxml": { - "hashes": [ - "sha256:0537eee4902e8bf4f41bfee8133f7edf96533dd175930a12086d6a40d62376b2", - "sha256:0562ec748abd230ab87d73384e08fa784f9b9cee89e28696087d2d22c052cc27", - "sha256:09e91831e749fbf0f24608694e4573be0ef51430229450c39c83176cc2e2d353", - "sha256:1ae4c0722fc70c0d4fba43ae33c2885f705e96dce1db41f75ae14a2d2749b428", - "sha256:1c630c083d782cbaf1f7f37f6cac87bda9cff643cf2803a5f180f30d97955cef", - "sha256:2fe74e3836bd8c0fa7467ffae05545233c7f37de1eb765cacfda15ad20c6574a", - "sha256:37af783c2667ead34a811037bda56a0b142ac8438f7ed29ae93f82ddb812fbd6", - "sha256:3f2d9eafbb0b24a33f56acd16f39fc935756524dcb3172892721c54713964c70", - "sha256:47d8365a8ef14097aa4c65730689be51851b4ade677285a3b2daa03b37893e26", - "sha256:510e904079bc56ea784677348e151e1156040dbfb736f1d8ea4b9e6d0ab2d9f4", - "sha256:58d0851da422bba31c7f652a7e9335313cf94a641aa6d73b8f3c67602f75b593", - "sha256:7940d5c2185ffb989203dacbb28e6ae88b4f1bb25d04e17f94b0edd82232bcbd", - "sha256:7cf39bb3a905579836f7a8f3a45320d9eb22f16ab0c1e112efb940ced4d057a5", - "sha256:9563a23c1456c0ab550c087833bc13fcc61013a66c6420921d5b70550ea312bf", - "sha256:95b392952935947e0786a90b75cc33388549dcb19af716b525dae65b186138fc", - "sha256:983129f3fd3cef5c3cf067adcca56e30a169656c00fcc6c648629dbb850b27fa", - "sha256:a0b75b1f1854771844c647c464533def3e0a899dd094a85d1d4ed72ecaaee93d", - "sha256:b5db89cc0ef624f3a81214b7961a99f443b8c91e88188376b6b322fd10d5b118", - "sha256:c0a7751ba1a4bfbe7831920d98cee3ce748007eab8dfda74593d44079568219a", - "sha256:c0c5a7d4aafcc30c9b6d8613a362567e32e5f5b708dc41bc3a81dac56f8af8bb", - "sha256:d4d63d85eacc6cb37b459b16061e1f100d154bee89dc8d8f9a6128a5a538e92e", - "sha256:da5e7e941d6e71c9c9a717c93725cda0708c2474f532e3680ac5e39ec57d224d", - "sha256:dccad2b3c583f036f43f80ac99ee212c2fa9a45151358d55f13004d095e683b2", - "sha256:df46307d39f2aeaafa1d25309b8a8d11738b73e9861f72d4d0a092528f498baa", - "sha256:e70b5e1cb48828ddd2818f99b1662cb9226dc6f57d07fc75485405c77da17436", - "sha256:ea825562b8cd057cbc9810d496b8b5dec37a1e2fc7b27bc7c1e72ce94462a09a" - ], - "version": "==4.3.1" - }, - "mako": { - "hashes": [ - "sha256:4e02fde57bd4abb5ec400181e4c314f56ac3e49ba4fb8b0d50bba18cb27d25ae" - ], - "version": "==1.0.7" - }, - "markupsafe": { - "hashes": [ - "sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432", - "sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b", - "sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9", - "sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af", - "sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834", - "sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd", - "sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d", - "sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7", - "sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b", - "sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3", - "sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c", - "sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2", - "sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7", - "sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36", - "sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1", - "sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e", - "sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1", - "sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c", - "sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856", - "sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550", - "sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492", - "sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672", - "sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401", - "sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6", - "sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6", - "sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c", - "sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd", - "sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1" - ], - "version": "==1.1.0" - }, - "passlib": { - "extras": [ - "bcrypt" - ], - "hashes": [ - "sha256:3d948f64138c25633613f303bcc471126eae67c04d5e3f6b7b8ce6242f8653e0", - "sha256:43526aea08fa32c6b6dbbbe9963c4c767285b78147b7437597f992812f69d280" - ], - "index": "pypi", - "version": "==1.7.1" - }, - "premailer": { - "hashes": [ - "sha256:93be4f197e9d2a87a8fe6b5b6a79b64070dbb523108dfaf2a415b4558fc78ec1", - "sha256:f45eb4a30485aeccc3ff19771d6614346899ec19a301931af4694f737b6035c3" - ], - "version": "==3.3.0" - }, - "psycopg2-binary": { - "hashes": [ - "sha256:19a2d1f3567b30f6c2bb3baea23f74f69d51f0c06c2e2082d0d9c28b0733a4c2", - "sha256:2b69cf4b0fa2716fd977aa4e1fd39af6110eb47b2bb30b4e5a469d8fbecfc102", - "sha256:2e952fa17ba48cbc2dc063ddeec37d7dc4ea0ef7db0ac1eda8906365a8543f31", - "sha256:348b49dd737ff74cfb5e663e18cb069b44c64f77ec0523b5794efafbfa7df0b8", - "sha256:3d72a5fdc5f00ca85160915eb9a973cf9a0ab8148f6eda40708bf672c55ac1d1", - "sha256:4957452f7868f43f32c090dadb4188e9c74a4687323c87a882e943c2bd4780c3", - "sha256:5138cec2ee1e53a671e11cc519505eb08aaaaf390c508f25b09605763d48de4b", - "sha256:587098ca4fc46c95736459d171102336af12f0d415b3b865972a79c03f06259f", - "sha256:5b79368bcdb1da4a05f931b62760bea0955ee2c81531d8e84625df2defd3f709", - "sha256:5cf43807392247d9bc99737160da32d3fa619e0bfd85ba24d1c78db205f472a4", - "sha256:676d1a80b1eebc0cacae8dd09b2fde24213173bf65650d22b038c5ed4039f392", - "sha256:6b0211ecda389101a7d1d3df2eba0cf7ffbdd2480ca6f1d2257c7bd739e84110", - "sha256:79cde4660de6f0bb523c229763bd8ad9a93ac6760b72c369cf1213955c430934", - "sha256:7aba9786ac32c2a6d5fb446002ed936b47d5e1f10c466ef7e48f66eb9f9ebe3b", - "sha256:7c8159352244e11bdd422226aa17651110b600d175220c451a9acf795e7414e0", - "sha256:945f2eedf4fc6b2432697eb90bb98cc467de5147869e57405bfc31fa0b824741", - "sha256:96b4e902cde37a7fc6ab306b3ac089a3949e6ce3d824eeca5b19dc0bedb9f6e2", - "sha256:9a7bccb1212e63f309eb9fab47b6eaef796f59850f169a25695b248ca1bf681b", - "sha256:a3bfcac727538ec11af304b5eccadbac952d4cca1a551a29b8fe554e3ad535dc", - "sha256:b19e9f1b85c5d6136f5a0549abdc55dcbd63aba18b4f10d0d063eb65ef2c68b4", - "sha256:b664011bb14ca1f2287c17185e222f2098f7b4c857961dbcf9badb28786dbbf4", - "sha256:bde7959ef012b628868d69c474ec4920252656d0800835ed999ba5e4f57e3e2e", - "sha256:cb095a0657d792c8de9f7c9a0452385a309dfb1bbbb3357d6b1e216353ade6ca", - "sha256:d16d42a1b9772152c1fe606f679b2316551f7e1a1ce273e7f808e82a136cdb3d", - "sha256:d444b1545430ffc1e7a24ce5a9be122ccd3b135a7b7e695c5862c5aff0b11159", - "sha256:d93ccc7bf409ec0a23f2ac70977507e0b8a8d8c54e5ee46109af2f0ec9e411f3", - "sha256:df6444f952ca849016902662e1a47abf4fa0678d75f92fd9dd27f20525f809cd", - "sha256:e63850d8c52ba2b502662bf3c02603175c2397a9acc756090e444ce49508d41e", - "sha256:ec43358c105794bc2b6fd34c68d27f92bea7102393c01889e93f4b6a70975728", - "sha256:f4c6926d9c03dadce7a3b378b40d2fea912c1344ef9b29869f984fb3d2a2420b" - ], - "index": "pypi", - "version": "==2.7.7" - }, - "pycparser": { - "hashes": [ - "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" - ], - "version": "==2.19" - }, - "pydantic": { - "hashes": [ - "sha256:9f023811b6cefd203c5fd8fd15a4152f04e79e531b8f676ab1244dfe06ce8024", - "sha256:edbb08b561feda505374c0f25e4b54466a0a0c702ed6b2efaabdc3890d1a82e7" - ], - "index": "pypi", - "version": "==0.18.2" - }, - "pyjwt": { - "hashes": [ - "sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e", - "sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96" - ], - "index": "pypi", - "version": "==1.7.1" - }, - "python-dateutil": { - "hashes": [ - "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", - "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" - ], - "version": "==2.8.0" - }, - "python-editor": { - "hashes": [ - "sha256:1bf6e860a8ad52a14c3ee1252d5dc25b2030618ed80c022598f00176adc8367d", - "sha256:51fda6bcc5ddbbb7063b2af7509e43bd84bfc32a4ff71349ec7847713882327b", - "sha256:5f98b069316ea1c2ed3f67e7f5df6c0d8f10b689964a4a811ff64f0106819ec8" - ], - "version": "==1.0.4" - }, - "python-multipart": { - "hashes": [ - "sha256:f7bb5f611fc600d15fa47b3974c8aa16e93724513b49b5f95c81e6624c83fa43" - ], - "index": "pypi", - "version": "==0.0.5" - }, - "pytz": { - "hashes": [ - "sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9", - "sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c" - ], - "version": "==2018.9" - }, - "raven": { - "hashes": [ - "sha256:3fa6de6efa2493a7c827472e984ce9b020797d0da16f1db67197bcc23c8fae54", - "sha256:44a13f87670836e153951af9a3c80405d36b43097db869a36e92809673692ce4" - ], - "index": "pypi", - "version": "==6.10.0" - }, - "requests": { - "hashes": [ - "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", - "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" - ], - "index": "pypi", - "version": "==2.21.0" - }, - "six": { - "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" - ], - "version": "==1.12.0" - }, - "sqlalchemy": { - "hashes": [ - "sha256:7dede29f121071da9873e7b8c98091874617858e790dc364ffaab4b09d81216c" - ], - "index": "pypi", - "version": "==1.3.0b3" - }, - "starlette": { - "hashes": [ - "sha256:9d48b35d1fc7521d59ae53c421297ab3878d3c7cd4b75266d77f6c73cccb78bb" - ], - "version": "==0.11.1" - }, - "tenacity": { - "hashes": [ - "sha256:24b7f302a1caa1801e58b39ea557129c095966e64e5b1ddad3c93a6cb033e38b", - "sha256:a04b97b3bda047f912a75d110dde3e746891b5548b6bec6d157cd100f2d4afac" - ], - "index": "pypi", - "version": "==5.0.3" - }, - "urllib3": { - "hashes": [ - "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", - "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" - ], - "version": "==1.24.1" - }, - "uvicorn": { - "hashes": [ - "sha256:f27889a332ee5c55b4841b11b2392d00dac079f39063fabc1e13e18ada3eb7ba" - ], - "index": "pypi", - "version": "==0.4.5" - }, - "uvloop": { - "hashes": [ - "sha256:198fe0c196056930ec6c4a0a878e531a66d15467ca7c74a875aa90271f0c6e3f", - "sha256:1c175f47d34b84e33c0e312f4987c927ea004afc3a5f05d2f0f610d71d0e4c89", - "sha256:1c47f197be8f0a3c651dd20be1e1bd43268186246f246d4e86c91e95a89e4865", - "sha256:3fd4943570d20e8cd4d9f0a3190ebd5cf040e5610b685e05c878128a11f7ad14", - "sha256:435e232869923fd2248e4ca0ad73e24a5b4debf40bed9dcde133cfe1bef98a7a", - "sha256:9cfdb966ae804c46b96c92207dfd2174935ffc70e706e42e1c94c60d16dbe860", - "sha256:a585781443eeb2edb858f8c08c503aac237a5f1bebf0c84ea8340cc337afa408", - "sha256:b296493e033846e46488a6aa227a75c790091f5ee5456ec637bb0badad1e8851", - "sha256:c684047c6cf6d697ba37872fb1b4489012ea91f3f802c8fbb9c367c4902e88dc", - "sha256:da5a59d8812188b57b5783c7fb78891d14dd1050b6259680e0dbd4253d7d0f64" - ], - "version": "==0.12.1" - }, - "vine": { - "hashes": [ - "sha256:3cd505dcf980223cfaf13423d371f2e7ff99247e38d5985a01ec8264e4f2aca1", - "sha256:ee4813e915d0e1a54e5c1963fde0855337f82655678540a6bc5996bca4165f76" - ], - "version": "==1.2.0" - }, - "websockets": { - "hashes": [ - "sha256:04b42a1b57096ffa5627d6a78ea1ff7fad3bc2c0331ffc17bc32a4024da7fea0", - "sha256:08e3c3e0535befa4f0c4443824496c03ecc25062debbcf895874f8a0b4c97c9f", - "sha256:10d89d4326045bf5e15e83e9867c85d686b612822e4d8f149cf4840aab5f46e0", - "sha256:232fac8a1978fc1dead4b1c2fa27c7756750fb393eb4ac52f6bc87ba7242b2fa", - "sha256:4bf4c8097440eff22bc78ec76fe2a865a6e658b6977a504679aaf08f02c121da", - "sha256:51642ea3a00772d1e48fb0c492f0d3ae3b6474f34d20eca005a83f8c9c06c561", - "sha256:55d86102282a636e195dad68aaaf85b81d0bef449d7e2ef2ff79ac450bb25d53", - "sha256:564d2675682bd497b59907d2205031acbf7d3fadf8c763b689b9ede20300b215", - "sha256:5d13bf5197a92149dc0badcc2b699267ff65a867029f465accfca8abab95f412", - "sha256:5eda665f6789edb9b57b57a159b9c55482cbe5b046d7db458948370554b16439", - "sha256:5edb2524d4032be4564c65dc4f9d01e79fe8fad5f966e5b552f4e5164fef0885", - "sha256:79691794288bc51e2a3b8de2bc0272ca8355d0b8503077ea57c0716e840ebaef", - "sha256:7fcc8681e9981b9b511cdee7c580d5b005f3bb86b65bde2188e04a29f1d63317", - "sha256:8e447e05ec88b1b408a4c9cde85aa6f4b04f06aa874b9f0b8e8319faf51b1fee", - "sha256:90ea6b3e7787620bb295a4ae050d2811c807d65b1486749414f78cfd6fb61489", - "sha256:9e13239952694b8b831088431d15f771beace10edfcf9ef230cefea14f18508f", - "sha256:d40f081187f7b54d7a99d8a5c782eaa4edc335a057aa54c85059272ed826dc09", - "sha256:e1df1a58ed2468c7b7ce9a2f9752a32ad08eac2bcd56318625c3647c2cd2da6f", - "sha256:e98d0cec437097f09c7834a11c69d79fe6241729b23f656cfc227e93294fc242", - "sha256:f8d59627702d2ff27cb495ca1abdea8bd8d581de425c56e93bff6517134e0a9b", - "sha256:fc30cdf2e949a2225b012a7911d1d031df3d23e99b7eda7dfc982dc4a860dae9" - ], - "version": "==7.0" - } - }, - "develop": { - "appdirs": { - "hashes": [ - "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", - "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" - ], - "version": "==1.4.3" - }, - "atomicwrites": { - "hashes": [ - "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", - "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" - ], - "version": "==1.3.0" - }, - "attrs": { - "hashes": [ - "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", - "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb" - ], - "version": "==18.2.0" - }, - "autoflake": { - "hashes": [ - "sha256:c103e63466f11db3617167a2c68ff6a0cda35b940222920631c6eeec6b67e807" - ], - "index": "pypi", - "version": "==1.2" - }, - "backcall": { - "hashes": [ - "sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4", - "sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2" - ], - "version": "==0.1.0" - }, - "black": { - "hashes": [ - "sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739", - "sha256:e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5" - ], - "index": "pypi", - "version": "==18.9b0" - }, - "bleach": { - "hashes": [ - "sha256:213336e49e102af26d9cde77dd2d0397afabc5a6bf2fed985dc35b5d1e285a16", - "sha256:3fdf7f77adcf649c9911387df51254b813185e32b2c6619f690b593a617e19fa" - ], - "version": "==3.1.0" - }, - "click": { - "hashes": [ - "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", - "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" - ], - "version": "==7.0" - }, - "decorator": { - "hashes": [ - "sha256:33cd704aea07b4c28b3eb2c97d288a06918275dac0ecebdaf1bc8a48d98adb9e", - "sha256:cabb249f4710888a2fc0e13e9a16c343d932033718ff62e1e9bc93a9d3a9122b" - ], - "version": "==4.3.2" - }, - "defusedxml": { - "hashes": [ - "sha256:24d7f2f94f7f3cb6061acb215685e5125fbcdc40a857eff9de22518820b0a4f4", - "sha256:702a91ade2968a82beb0db1e0766a6a273f33d4616a6ce8cde475d8e09853b20" - ], - "version": "==0.5.0" - }, - "entrypoints": { - "hashes": [ - "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", - "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" - ], - "version": "==0.3" - }, - "flake8": { - "hashes": [ - "sha256:6d8c66a65635d46d54de59b027a1dda40abbe2275b3164b634835ac9c13fd048", - "sha256:6eab21c6e34df2c05416faa40d0c59963008fff29b6f0ccfe8fa28152ab3e383" - ], - "index": "pypi", - "version": "==3.7.6" - }, - "ipykernel": { - "hashes": [ - "sha256:0aeb7ec277ac42cc2b59ae3d08b10909b2ec161dc6908096210527162b53675d", - "sha256:0fc0bf97920d454102168ec2008620066878848fcfca06c22b669696212e292f" - ], - "version": "==5.1.0" - }, - "ipython": { - "hashes": [ - "sha256:06de667a9e406924f97781bda22d5d76bfb39762b678762d86a466e63f65dc39", - "sha256:5d3e020a6b5f29df037555e5c45ab1088d6a7cf3bd84f47e0ba501eeb0c3ec82" - ], - "markers": "python_version >= '3.3'", - "version": "==7.3.0" - }, - "ipython-genutils": { - "hashes": [ - "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", - "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8" - ], - "version": "==0.2.0" - }, - "ipywidgets": { - "hashes": [ - "sha256:0f2b5cde9f272cb49d52f3f0889fdd1a7ae1e74f37b48dac35a83152780d2b7b", - "sha256:a3e224f430163f767047ab9a042fc55adbcab0c24bbe6cf9f306c4f89fdf0ba3" - ], - "version": "==7.4.2" - }, - "isort": { - "hashes": [ - "sha256:1153601da39a25b14ddc54955dbbacbb6b2d19135386699e2ad58517953b34af", - "sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8", - "sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497" - ], - "index": "pypi", - "version": "==4.3.4" - }, - "jedi": { - "hashes": [ - "sha256:571702b5bd167911fe9036e5039ba67f820d6502832285cde8c881ab2b2149fd", - "sha256:c8481b5e59d34a5c7c42e98f6625e633f6ef59353abea6437472c7ec2093f191" - ], - "version": "==0.13.2" - }, - "jinja2": { - "hashes": [ - "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", - "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" - ], - "index": "pypi", - "version": "==2.10" - }, - "jsonschema": { - "hashes": [ - "sha256:683fe7ed58763ea0be572de5aad47cd3cc1297640916f9a8ccd222b287da7d2f", - "sha256:b42d7a292addb57370e6260bcbadb77e00a899fe6ec998c453f45893c41c658b" - ], - "version": "==3.0.0b3" - }, - "jupyter": { - "hashes": [ - "sha256:3e1f86076bbb7c8c207829390305a2b1fe836d471ed54be66a3b8c41e7f46cc7", - "sha256:5b290f93b98ffbc21c0c7e749f054b3267782166d72fa5e3ed1ed4eaf34a2b78", - "sha256:d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f" - ], - "index": "pypi", - "version": "==1.0.0" - }, - "jupyter-client": { - "hashes": [ - "sha256:b5f9cb06105c1d2d30719db5ffb3ea67da60919fb68deaefa583deccd8813551", - "sha256:c44411eb1463ed77548bc2d5ec0d744c9b81c4a542d9637c7a52824e2121b987" - ], - "version": "==5.2.4" - }, - "jupyter-console": { - "hashes": [ - "sha256:308ce876354924fb6c540b41d5d6d08acfc946984bf0c97777c1ddcb42e0b2f5", - "sha256:cc80a97a5c389cbd30252ffb5ce7cefd4b66bde98219edd16bf5cb6f84bb3568" - ], - "version": "==6.0.0" - }, - "jupyter-core": { - "hashes": [ - "sha256:927d713ffa616ea11972534411544589976b2493fc7e09ad946e010aa7eb9970", - "sha256:ba70754aa680300306c699790128f6fbd8c306ee5927976cbe48adacf240c0b7" - ], - "version": "==4.4.0" - }, - "markupsafe": { - "hashes": [ - "sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432", - "sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b", - "sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9", - "sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af", - "sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834", - "sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd", - "sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d", - "sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7", - "sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b", - "sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3", - "sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c", - "sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2", - "sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7", - "sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36", - "sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1", - "sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e", - "sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1", - "sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c", - "sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856", - "sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550", - "sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492", - "sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672", - "sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401", - "sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6", - "sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6", - "sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c", - "sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd", - "sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1" - ], - "version": "==1.1.0" - }, - "mccabe": { - "hashes": [ - "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", - "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" - ], - "version": "==0.6.1" - }, - "mistune": { - "hashes": [ - "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e", - "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4" - ], - "version": "==0.8.4" - }, - "more-itertools": { - "hashes": [ - "sha256:0125e8f60e9e031347105eb1682cef932f5e97d7b9a1a28d9bf00c22a5daef40", - "sha256:590044e3942351a1bdb1de960b739ff4ce277960f2425ad4509446dbace8d9d1" - ], - "markers": "python_version > '2.7'", - "version": "==6.0.0" - }, - "mypy": { - "hashes": [ - "sha256:308c274eb8482fbf16006f549137ddc0d69e5a589465e37b99c4564414363ca7", - "sha256:e80fd6af34614a0e898a57f14296d0dacb584648f0339c2e000ddbf0f4cc2f8d" - ], - "index": "pypi", - "version": "==0.670" - }, - "mypy-extensions": { - "hashes": [ - "sha256:37e0e956f41369209a3d5f34580150bcacfabaa57b33a15c0b25f4b5725e0812", - "sha256:b16cabe759f55e3409a7d231ebd2841378fb0c27a5d1994719e340e4f429ac3e" - ], - "version": "==0.4.1" - }, - "nbconvert": { - "hashes": [ - "sha256:302554a2e219bc0fc84f3edd3e79953f3767b46ab67626fdec16e38ba3f7efe4", - "sha256:5de8fb2284422272a1d45abc77c07b888127550a6d602ce619592a2b08a474ff" - ], - "version": "==5.4.1" - }, - "nbformat": { - "hashes": [ - "sha256:b9a0dbdbd45bb034f4f8893cafd6f652ea08c8c1674ba83f2dc55d3955743b0b", - "sha256:f7494ef0df60766b7cabe0a3651556345a963b74dbc16bc7c18479041170d402" - ], - "version": "==4.4.0" - }, - "notebook": { - "hashes": [ - "sha256:3ab2db8bc10e6edbd264c3c4b800bee276c99818386ee0c146d98d7e6bcf0a67", - "sha256:d908673a4010787625c8952e91a22adf737db031f2aa0793ad92f6558918a74a" - ], - "version": "==5.7.4" - }, - "pandocfilters": { - "hashes": [ - "sha256:b3dd70e169bb5449e6bc6ff96aea89c5eea8c5f6ab5e207fc2f521a2cf4a0da9" - ], - "version": "==1.4.2" - }, - "parso": { - "hashes": [ - "sha256:4580328ae3f548b358f4901e38c0578229186835f0fa0846e47369796dd5bcc9", - "sha256:68406ebd7eafe17f8e40e15a84b56848eccbf27d7c1feb89e93d8fca395706db" - ], - "version": "==0.3.4" - }, - "pexpect": { - "hashes": [ - "sha256:2a8e88259839571d1251d278476f3eec5db26deb73a70be5ed5dc5435e418aba", - "sha256:3fbd41d4caf27fa4a377bfd16fef87271099463e6fa73e92a52f92dfee5d425b" - ], - "markers": "sys_platform != 'win32'", - "version": "==4.6.0" - }, - "pickleshare": { - "hashes": [ - "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca", - "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56" - ], - "version": "==0.7.5" - }, - "pluggy": { - "hashes": [ - "sha256:8ddc32f03971bfdf900a81961a48ccf2fb677cf7715108f85295c67405798616", - "sha256:980710797ff6a041e9a73a5787804f848996ecaa6f8a1b1e08224a5894f2074a" - ], - "version": "==0.8.1" - }, - "prometheus-client": { - "hashes": [ - "sha256:1b38b958750f66f208bcd9ab92a633c0c994d8859c831f7abc1f46724fcee490" - ], - "version": "==0.6.0" - }, - "prompt-toolkit": { - "hashes": [ - "sha256:11adf3389a996a6d45cc277580d0d53e8a5afd281d0c9ec71b28e6f121463780", - "sha256:2519ad1d8038fd5fc8e770362237ad0364d16a7650fb5724af6997ed5515e3c1", - "sha256:977c6583ae813a37dc1c2e1b715892461fcbdaa57f6fc62f33a528c4886c8f55" - ], - "version": "==2.0.9" - }, - "ptyprocess": { - "hashes": [ - "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0", - "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f" - ], - "markers": "os_name != 'nt'", - "version": "==0.6.0" - }, - "py": { - "hashes": [ - "sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694", - "sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6" - ], - "version": "==1.7.0" - }, - "pycodestyle": { - "hashes": [ - "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", - "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" - ], - "version": "==2.5.0" - }, - "pyflakes": { - "hashes": [ - "sha256:5e8c00e30c464c99e0b501dc160b13a14af7f27d4dffb529c556e30a159e231d", - "sha256:f277f9ca3e55de669fba45b7393a1449009cff5a37d1af10ebb76c52765269cd" - ], - "version": "==2.1.0" - }, - "pygments": { - "hashes": [ - "sha256:5ffada19f6203563680669ee7f53b64dabbeb100eb51b61996085e99c03b284a", - "sha256:e8218dd399a61674745138520d0d4cf2621d7e032439341bc3f647bff125818d" - ], - "version": "==2.3.1" - }, - "pyrsistent": { - "hashes": [ - "sha256:07f7ae71291af8b0dbad8c2ab630d8223e4a8c4e10fc37badda158c02e753acf" - ], - "version": "==0.14.10" - }, - "pytest": { - "hashes": [ - "sha256:067a1d4bf827ffdd56ad21bd46674703fce77c5957f6c1eef731f6146bfcef1c", - "sha256:9687049d53695ad45cf5fdc7bbd51f0c49f1ea3ecfc4b7f3fde7501b541f17f4" - ], - "index": "pypi", - "version": "==4.3.0" - }, - "python-dateutil": { - "hashes": [ - "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", - "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" - ], - "version": "==2.8.0" - }, - "pyzmq": { - "hashes": [ - "sha256:07a03450418694fb07e76a0191b6bc9f411afc8e364ca2062edcf28bb0e51c63", - "sha256:15f0bf7cd80020f165635595e197603aedb37fddf4164ad5ae226afc43242f7b", - "sha256:1756dc72e192c670490e38c788c3a35f901adc74ee436e5131d5a3e85fdd7dc6", - "sha256:1d1eb490da54679d724b08ef3ee04530849023670c4ba7e400ed2cdf906720c4", - "sha256:228402625796821f08706f58cc42a3c51c9897d723550babaefe4feec2b6dacc", - "sha256:264ac9dcee6a7af2bce4b61f2d19e5926118a5caa629b50f107ef6318670a364", - "sha256:2b5a43da65f5dec857184d5c2ce13b80071019e96358f146bdecff7238765bc9", - "sha256:3928534fa00a2aabfcfdb439c08ba37fbe99ab0cf57776c8db8d2b73a51693ba", - "sha256:3d2a295b1086d450981f73d3561ac204a0cc9c8ded386a4a34327d918f3b1d0a", - "sha256:411def5b4cbe6111856040a55c8048df113882e90c57ce88de4a48f0189441ac", - "sha256:4b77e96a7ffc1c5e08eaf274db554f227b31717d086adca1bb42b12ef35a7194", - "sha256:4c87fa3e449e1f4ab9170cdfe8213dc0ba34a11b160e6adecafa892e451a29b6", - "sha256:4fd8621a309db6ec23ef1369f43cdf7a9b0dc217d8ff9ca4095a6e932b379bda", - "sha256:54fe55a1694ffe608c8e4c5183e83cab7a91f3e5c84bd6f188868d6676c12aba", - "sha256:60acabd86808a16a895a247fd2bf7a127284a33562d79687bb5df163cff068b2", - "sha256:618887be4ad754228c0cbba7631f6574608b4430fe93974e6322324f1304fdac", - "sha256:69130efb6efa936de601cb135a8a4eec1caccd4ea2b784237145ff4075c2d3ae", - "sha256:6e7f78eeac82140bde7e60e975c6e6b1b678a4dd377782ab63319c1c78bf3aa1", - "sha256:6ee760cdb84e43574da6b3f2f1fc1251e8acf87253900d28a06451c5f5de39e9", - "sha256:75c87f1dc1e65cea4b709f2ebc78fa18d4b475e41463502aec9cd26208b88e0f", - "sha256:97cb1b7cd2c46e87b0a26651eccd2bbb8c758035efd1635ebb81ac36aa76a88c", - "sha256:abfa774dbadacc849121ed92eae05189d226daab583388b499472e1bbb17ef69", - "sha256:ae3d2627d74195ddc95675f2f814aca998381b73dc4341b9e10e3e191e1bdb0b", - "sha256:b30c339eb58355f51f4f54dd61d785f1ff58c86bca1c3a5916977631d121867b", - "sha256:cbabdced5b137cd56aa22633f13ac5690029a0ad43ab6c05f53206e489178362" - ], - "version": "==18.0.0" - }, - "qtconsole": { - "hashes": [ - "sha256:1ac4a65e81a27b0838330a6d351c2f8435d4013d98a95373e8a41119b2968390", - "sha256:bc1ba15f50c29ed50f1268ad823bb6543be263c18dd093b80495e9df63b003ac" - ], - "version": "==4.4.3" - }, - "send2trash": { - "hashes": [ - "sha256:60001cc07d707fe247c94f74ca6ac0d3255aabcb930529690897ca2a39db28b2", - "sha256:f1691922577b6fa12821234aeb57599d887c4900b9ca537948d2dac34aea888b" - ], - "version": "==1.5.0" - }, - "six": { - "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" - ], - "version": "==1.12.0" - }, - "terminado": { - "hashes": [ - "sha256:55abf9ade563b8f9be1f34e4233c7b7bde726059947a593322e8a553cc4c067a", - "sha256:65011551baff97f5414c67018e908110693143cfbaeb16831b743fe7cad8b927" - ], - "version": "==0.8.1" - }, - "testpath": { - "hashes": [ - "sha256:46c89ebb683f473ffe2aab0ed9f12581d4d078308a3cb3765d79c6b2317b0109", - "sha256:b694b3d9288dbd81685c5d2e7140b81365d46c29f5db4bc659de5aa6b98780f8" - ], - "version": "==0.4.2" - }, - "toml": { - "hashes": [ - "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", - "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" - ], - "version": "==0.10.0" - }, - "tornado": { - "hashes": [ - "sha256:d3b719a0cb7094e2b1ca94b31f4b601639fa7ad01a548a1a2ccdd6cbdfd56671" - ], - "version": "==6.0b1" - }, - "traitlets": { - "hashes": [ - "sha256:9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835", - "sha256:c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9" - ], - "version": "==4.3.2" - }, - "typed-ast": { - "hashes": [ - "sha256:035a54ede6ce1380599b2ce57844c6554666522e376bd111eb940fbc7c3dad23", - "sha256:037c35f2741ce3a9ac0d55abfcd119133cbd821fffa4461397718287092d9d15", - "sha256:049feae7e9f180b64efacbdc36b3af64a00393a47be22fa9cb6794e68d4e73d3", - "sha256:19228f7940beafc1ba21a6e8e070e0b0bfd1457902a3a81709762b8b9039b88d", - "sha256:2ea681e91e3550a30c2265d2916f40a5f5d89b59469a20f3bad7d07adee0f7a6", - "sha256:3a6b0a78af298d82323660df5497bcea0f0a4a25a0b003afd0ce5af049bd1f60", - "sha256:5385da8f3b801014504df0852bf83524599df890387a3c2b17b7caa3d78b1773", - "sha256:606d8afa07eef77280c2bf84335e24390055b478392e1975f96286d99d0cb424", - "sha256:69245b5b23bbf7fb242c9f8f08493e9ecd7711f063259aefffaeb90595d62287", - "sha256:6f6d839ab09830d59b7fa8fb6917023d8cb5498ee1f1dbd82d37db78eb76bc99", - "sha256:730888475f5ac0e37c1de4bd05eeb799fdb742697867f524dc8a4cd74bcecc23", - "sha256:9819b5162ffc121b9e334923c685b0d0826154e41dfe70b2ede2ce29034c71d8", - "sha256:9e60ef9426efab601dd9aa120e4ff560f4461cf8442e9c0a2b92548d52800699", - "sha256:af5fbdde0690c7da68e841d7fc2632345d570768ea7406a9434446d7b33b0ee1", - "sha256:b64efdbdf3bbb1377562c179f167f3bf301251411eb5ac77dec6b7d32bcda463", - "sha256:bac5f444c118aeb456fac1b0b5d14c6a71ea2a42069b09c176f75e9bd4c186f6", - "sha256:bda9068aafb73859491e13b99b682bd299c1b5fd50644d697533775828a28ee0", - "sha256:d659517ca116e6750101a1326107d3479028c5191f0ecee3c7203c50f5b915b0", - "sha256:eddd3fb1f3e0f82e5915a899285a39ee34ce18fd25d89582bc89fc9fb16cd2c6" - ], - "version": "==1.3.1" - }, - "vulture": { - "hashes": [ - "sha256:4b5a8980c338e9c068d43e7164555a1e4c9c7d84961ce2bc6f3ed975f6e5bc9d", - "sha256:524b6b9642d0bbe74ea21478bf260937d1ba9b3b86676ca0b17cd10b4b51ba01" - ], - "index": "pypi", - "version": "==1.0" - }, - "wcwidth": { - "hashes": [ - "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", - "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" - ], - "version": "==0.1.7" - }, - "webencodings": { - "hashes": [ - "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", - "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" - ], - "version": "==0.5.1" - }, - "widgetsnbextension": { - "hashes": [ - "sha256:14b2c65f9940c9a7d3b70adbe713dbd38b5ec69724eebaba034d1036cf3d4740", - "sha256:fa618be8435447a017fd1bf2c7ae922d0428056cfc7449f7a8641edf76b48265" - ], - "version": "==3.4.2" - } - } -} diff --git a/{{cookiecutter.project_slug}}/backend/app/alembic/versions/e6ae69e9dcb9_first_revision.py b/{{cookiecutter.project_slug}}/backend/app/alembic/versions/d4867f3a4c0a_first_revision.py similarity index 59% rename from {{cookiecutter.project_slug}}/backend/app/alembic/versions/e6ae69e9dcb9_first_revision.py rename to {{cookiecutter.project_slug}}/backend/app/alembic/versions/d4867f3a4c0a_first_revision.py index 6034264e9f..68b3ee4f1c 100644 --- a/{{cookiecutter.project_slug}}/backend/app/alembic/versions/e6ae69e9dcb9_first_revision.py +++ b/{{cookiecutter.project_slug}}/backend/app/alembic/versions/d4867f3a4c0a_first_revision.py @@ -1,8 +1,8 @@ """First revision -Revision ID: e6ae69e9dcb9 +Revision ID: d4867f3a4c0a Revises: -Create Date: 2019-02-13 14:27:57.038583 +Create Date: 2019-04-17 13:53:32.978401 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = 'e6ae69e9dcb9' +revision = 'd4867f3a4c0a' down_revision = None branch_labels = None depends_on = None @@ -30,11 +30,26 @@ def upgrade(): op.create_index(op.f('ix_user_email'), 'user', ['email'], unique=True) op.create_index(op.f('ix_user_full_name'), 'user', ['full_name'], unique=False) op.create_index(op.f('ix_user_id'), 'user', ['id'], unique=False) + op.create_table('item', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('title', sa.String(), nullable=True), + sa.Column('description', sa.String(), nullable=True), + sa.Column('owner_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['owner_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_item_description'), 'item', ['description'], unique=False) + op.create_index(op.f('ix_item_id'), 'item', ['id'], unique=False) + op.create_index(op.f('ix_item_title'), 'item', ['title'], unique=False) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f('ix_item_title'), table_name='item') + op.drop_index(op.f('ix_item_id'), table_name='item') + op.drop_index(op.f('ix_item_description'), table_name='item') + op.drop_table('item') op.drop_index(op.f('ix_user_id'), table_name='user') op.drop_index(op.f('ix_user_full_name'), table_name='user') op.drop_index(op.f('ix_user_email'), table_name='user') diff --git a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/api.py b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/api.py index 8673a99123..2163017268 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/api.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/api.py @@ -1,8 +1,9 @@ from fastapi import APIRouter -from app.api.api_v1.endpoints import token, user, utils +from app.api.api_v1.endpoints import items, login, users, utils api_router = APIRouter() -api_router.include_router(token.router) -api_router.include_router(user.router) -api_router.include_router(utils.router) +api_router.include_router(login.router, tags=["login"]) +api_router.include_router(users.router, prefix="/users", tags=["users"]) +api_router.include_router(utils.router, prefix="/utils", tags=["utils"]) +api_router.include_router(items.router, prefix="/items", tags=["items"]) diff --git a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/items.py b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/items.py new file mode 100644 index 0000000000..131e9852d4 --- /dev/null +++ b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/items.py @@ -0,0 +1,102 @@ +from typing import List + +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session + +from app import crud +from app.api.utils.db import get_db +from app.api.utils.security import get_current_active_user +from app.db_models.user import User as DBUser +from app.models.item import Item, ItemCreate, ItemUpdate + +router = APIRouter() + + +@router.get("/", response_model=List[Item]) +def read_items( + db: Session = Depends(get_db), + skip: int = 0, + limit: int = 100, + current_user: DBUser = Depends(get_current_active_user), +): + """ + Retrieve items. + """ + if crud.user.is_superuser(current_user): + items = crud.item.get_multi(db, skip=skip, limit=limit) + else: + items = crud.item.get_multi_by_owner( + db_session=db, owner_id=current_user.id, skip=skip, limit=limit + ) + return items + + +@router.post("/", response_model=Item) +def create_item( + *, + db: Session = Depends(get_db), + item_in: ItemCreate, + current_user: DBUser = Depends(get_current_active_user), +): + """ + Create new item. + """ + item = crud.item.create(db_session=db, item_in=item_in, owner_id=current_user.id) + return item + + +@router.put("/{id}", response_model=Item) +def update_item( + *, + db: Session = Depends(get_db), + id: int, + item_in: ItemUpdate, + current_user: DBUser = Depends(get_current_active_user), +): + """ + Update an item. + """ + item = crud.item.get(db_session=db, id=id) + if not item: + raise HTTPException(status_code=404, detail="Item not found") + if not crud.user.is_superuser(current_user) and (item.owner_id != current_user.id): + raise HTTPException(status_code=400, detail="Not enough permissions") + item = crud.item.update(db_session=db, item=item, item_in=item_in) + return item + + +@router.get("/{id}", response_model=Item) +def read_user_me( + *, + db: Session = Depends(get_db), + id: int, + current_user: DBUser = Depends(get_current_active_user), +): + """ + Get item by ID. + """ + item = crud.item.get(db_session=db, id=id) + if not item: + raise HTTPException(status_code=400, detail="Item not found") + if not crud.user.is_superuser(current_user) and (item.owner_id != current_user.id): + raise HTTPException(status_code=400, detail="Not enough permissions") + return item + + +@router.delete("/{id}", response_model=Item) +def delete_item( + *, + db: Session = Depends(get_db), + id: int, + current_user: DBUser = Depends(get_current_active_user), +): + """ + Delete an item. + """ + item = crud.item.get(db_session=db, id=id) + if not item: + raise HTTPException(status_code=404, detail="Item not found") + if not crud.user.is_superuser(current_user) and (item.owner_id != current_user.id): + raise HTTPException(status_code=400, detail="Not enough permissions") + item = crud.item.remove(db_session=db, id=id) + return item diff --git a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/token.py b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/login.py similarity index 100% rename from {{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/token.py rename to {{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/login.py diff --git a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/user.py b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/users.py similarity index 80% rename from {{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/user.py rename to {{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/users.py index 3ac36ecd58..966fe12ddc 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/user.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/users.py @@ -10,13 +10,13 @@ from app.api.utils.security import get_current_active_superuser, get_current_active_user from app.core import config from app.db_models.user import User as DBUser -from app.models.user import User, UserInCreate, UserInDB, UserInUpdate +from app.models.user import User, UserCreate, UserInDB, UserUpdate from app.utils import send_new_account_email router = APIRouter() -@router.get("/users/", tags=["users"], response_model=List[User]) +@router.get("/", response_model=List[User]) def read_users( db: Session = Depends(get_db), skip: int = 0, @@ -24,21 +24,21 @@ def read_users( current_user: DBUser = Depends(get_current_active_superuser), ): """ - Retrieve users + Retrieve users. """ users = crud.user.get_multi(db, skip=skip, limit=limit) return users -@router.post("/users/", tags=["users"], response_model=User) +@router.post("/", response_model=User) def create_user( *, db: Session = Depends(get_db), - user_in: UserInCreate, + user_in: UserCreate, current_user: DBUser = Depends(get_current_active_superuser), ): """ - Create new user + Create new user. """ user = crud.user.get_by_email(db, email=user_in.email) if user: @@ -54,7 +54,7 @@ def create_user( return user -@router.put("/users/me", tags=["users"], response_model=User) +@router.put("/me", response_model=User) def update_user_me( *, db: Session = Depends(get_db), @@ -64,10 +64,10 @@ def update_user_me( current_user: DBUser = Depends(get_current_active_user), ): """ - Update own user + Update own user. """ current_user_data = jsonable_encoder(current_user) - user_in = UserInUpdate(**current_user_data) + user_in = UserUpdate(**current_user_data) if password is not None: user_in.password = password if full_name is not None: @@ -78,18 +78,18 @@ def update_user_me( return user -@router.get("/users/me", tags=["users"], response_model=User) +@router.get("/me", response_model=User) def read_user_me( db: Session = Depends(get_db), current_user: DBUser = Depends(get_current_active_user), ): """ - Get current user + Get current user. """ return current_user -@router.post("/users/open", tags=["users"], response_model=User) +@router.post("/open", response_model=User) def create_user_open( *, db: Session = Depends(get_db), @@ -98,7 +98,7 @@ def create_user_open( full_name: str = Body(None), ): """ - Create new user without the need to be logged in + Create new user without the need to be logged in. """ if not config.USERS_OPEN_REGISTRATION: raise HTTPException( @@ -111,19 +111,19 @@ def create_user_open( status_code=400, detail="The user with this username already exists in the system", ) - user_in = UserInCreate(password=password, email=email, full_name=full_name) + user_in = UserCreate(password=password, email=email, full_name=full_name) user = crud.user.create(db, user_in=user_in) return user -@router.get("/users/{user_id}", tags=["users"], response_model=User) +@router.get("/{user_id}", response_model=User) def read_user_by_id( user_id: int, current_user: DBUser = Depends(get_current_active_user), db: Session = Depends(get_db), ): """ - Get a specific user by id + Get a specific user by id. """ user = crud.user.get(db, user_id=user_id) if user == current_user: @@ -135,19 +135,18 @@ def read_user_by_id( return user -@router.put("/users/{user_id}", tags=["users"], response_model=User) +@router.put("/{user_id}", response_model=User) def update_user( *, db: Session = Depends(get_db), user_id: int, - user_in: UserInUpdate, + user_in: UserUpdate, current_user: UserInDB = Depends(get_current_active_superuser), ): """ - Update a user + Update a user. """ user = crud.user.get(db, user_id=user_id) - if not user: raise HTTPException( status_code=404, diff --git a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/utils.py b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/utils.py index 18995cec1c..cc43abe52a 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/utils.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/utils.py @@ -10,23 +10,23 @@ router = APIRouter() -@router.post("/test-celery/", tags=["utils"], response_model=Msg, status_code=201) +@router.post("/test-celery/", response_model=Msg, status_code=201) def test_celery( msg: Msg, current_user: UserInDB = Depends(get_current_active_superuser) ): """ - Test Celery worker + Test Celery worker. """ celery_app.send_task("app.worker.test_celery", args=[msg.msg]) return {"msg": "Word received"} -@router.post("/test-email/", tags=["utils"], response_model=Msg, status_code=201) +@router.post("/test-email/", response_model=Msg, status_code=201) def test_email( email_to: EmailStr, current_user: UserInDB = Depends(get_current_active_superuser) ): """ - Test emails + Test emails. """ send_test_email(email_to=email_to) return {"msg": "Test email sent"} diff --git a/{{cookiecutter.project_slug}}/backend/app/app/crud/__init__.py b/{{cookiecutter.project_slug}}/backend/app/app/crud/__init__.py index f9b61db23e..9330490546 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/crud/__init__.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/crud/__init__.py @@ -1 +1 @@ -from . import user +from . import item, user diff --git a/{{cookiecutter.project_slug}}/backend/app/app/crud/item.py b/{{cookiecutter.project_slug}}/backend/app/app/crud/item.py new file mode 100644 index 0000000000..4d92b041ca --- /dev/null +++ b/{{cookiecutter.project_slug}}/backend/app/app/crud/item.py @@ -0,0 +1,54 @@ +from typing import List, Optional + +from fastapi.encoders import jsonable_encoder +from sqlalchemy.orm import Session + +from app.db_models.item import Item +from app.models.item import ItemCreate, ItemUpdate + + +def get(db_session: Session, *, id: int) -> Optional[Item]: + return db_session.query(Item).filter(Item.id == id).first() + + +def get_multi(db_session: Session, *, skip=0, limit=100) -> List[Optional[Item]]: + return db_session.query(Item).offset(skip).limit(limit).all() + + +def get_multi_by_owner( + db_session: Session, *, owner_id: int, skip=0, limit=100 +) -> List[Optional[Item]]: + return ( + db_session.query(Item) + .filter(Item.owner_id == owner_id) + .offset(skip) + .limit(limit) + .all() + ) + + +def create(db_session: Session, *, item_in: ItemCreate, owner_id: int) -> Item: + item = Item(title=item_in.title, description=item_in.description, owner_id=owner_id) + db_session.add(item) + db_session.commit() + db_session.refresh(item) + return item + + +def update(db_session: Session, *, item: Item, item_in: ItemUpdate) -> Item: + item_data = jsonable_encoder(item) + update_data = item_in.dict(skip_defaults=True) + for field in item_data: + if field in update_data: + setattr(item, field, update_data[field]) + db_session.add(item) + db_session.commit() + db_session.refresh(item) + return item + + +def remove(db_session: Session, *, id: int): + item = db_session.query(Item).filter(Item.id == id).first() + db_session.delete(item) + db_session.commit() + return item diff --git a/{{cookiecutter.project_slug}}/backend/app/app/crud/user.py b/{{cookiecutter.project_slug}}/backend/app/app/crud/user.py index 294ae9ab20..66f80753c0 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/crud/user.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/crud/user.py @@ -1,21 +1,22 @@ from typing import List, Optional from fastapi.encoders import jsonable_encoder +from sqlalchemy.orm import Session from app.core.security import get_password_hash, verify_password from app.db_models.user import User -from app.models.user import UserInCreate, UserInUpdate +from app.models.user import UserCreate, UserUpdate -def get(db_session, *, user_id: int) -> Optional[User]: +def get(db_session: Session, *, user_id: int) -> Optional[User]: return db_session.query(User).filter(User.id == user_id).first() -def get_by_email(db_session, *, email: str) -> Optional[User]: +def get_by_email(db_session: Session, *, email: str) -> Optional[User]: return db_session.query(User).filter(User.email == email).first() -def authenticate(db_session, *, email: str, password: str) -> Optional[User]: +def authenticate(db_session: Session, *, email: str, password: str) -> Optional[User]: user = get_by_email(db_session, email=email) if not user: return None @@ -32,11 +33,11 @@ def is_superuser(user) -> bool: return user.is_superuser -def get_multi(db_session, *, skip=0, limit=100) -> List[Optional[User]]: +def get_multi(db_session: Session, *, skip=0, limit=100) -> List[Optional[User]]: return db_session.query(User).offset(skip).limit(limit).all() -def create(db_session, *, user_in: UserInCreate) -> User: +def create(db_session: Session, *, user_in: UserCreate) -> User: user = User( email=user_in.email, hashed_password=get_password_hash(user_in.password), @@ -49,13 +50,12 @@ def create(db_session, *, user_in: UserInCreate) -> User: return user -def update(db_session, *, user: User, user_in: UserInUpdate) -> User: +def update(db_session: Session, *, user: User, user_in: UserUpdate) -> User: user_data = jsonable_encoder(user) + update_data = user_in.dict(skip_defaults=True) for field in user_data: - if field in user_in.fields: - value_in = getattr(user_in, field) - if value_in is not None: - setattr(user, field, value_in) + if field in update_data: + setattr(user, field, update_data[field]) if user_in.password: passwordhash = get_password_hash(user_in.password) user.hashed_password = passwordhash diff --git a/{{cookiecutter.project_slug}}/backend/app/app/db/base.py b/{{cookiecutter.project_slug}}/backend/app/app/db/base.py index 44a65b5647..1665277300 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/db/base.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/db/base.py @@ -2,3 +2,4 @@ # imported by Alembic from app.db.base_class import Base # noqa from app.db_models.user import User # noqa +from app.db_models.item import Item # noqa diff --git a/{{cookiecutter.project_slug}}/backend/app/app/db/init_db.py b/{{cookiecutter.project_slug}}/backend/app/app/db/init_db.py index 4b9d825d99..4f1d6f5aa3 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/db/init_db.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/db/init_db.py @@ -1,6 +1,6 @@ from app import crud from app.core import config -from app.models.user import UserInCreate +from app.models.user import UserCreate def init_db(db_session): @@ -11,7 +11,7 @@ def init_db(db_session): user = crud.user.get_by_email(db_session, email=config.FIRST_SUPERUSER) if not user: - user_in = UserInCreate( + user_in = UserCreate( email=config.FIRST_SUPERUSER, password=config.FIRST_SUPERUSER_PASSWORD, is_superuser=True, diff --git a/{{cookiecutter.project_slug}}/backend/app/app/db_models/item.py b/{{cookiecutter.project_slug}}/backend/app/app/db_models/item.py new file mode 100755 index 0000000000..685687a098 --- /dev/null +++ b/{{cookiecutter.project_slug}}/backend/app/app/db_models/item.py @@ -0,0 +1,12 @@ +from sqlalchemy import Column, ForeignKey, Integer, String +from sqlalchemy.orm import relationship + +from app.db.base_class import Base + + +class Item(Base): + id = Column(Integer, primary_key=True, index=True) + title = Column(String, index=True) + description = Column(String, index=True) + owner_id = Column(Integer, ForeignKey("user.id")) + owner = relationship("User", back_populates="items") diff --git a/{{cookiecutter.project_slug}}/backend/app/app/db_models/user.py b/{{cookiecutter.project_slug}}/backend/app/app/db_models/user.py index cdfffaa443..1052908a4b 100755 --- a/{{cookiecutter.project_slug}}/backend/app/app/db_models/user.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/db_models/user.py @@ -1,4 +1,5 @@ from sqlalchemy import Boolean, Column, Integer, String +from sqlalchemy.orm import relationship from app.db.base_class import Base @@ -10,3 +11,4 @@ class User(Base): hashed_password = Column(String) is_active = Column(Boolean(), default=True) is_superuser = Column(Boolean(), default=False) + items = relationship("Item", back_populates="owner") diff --git a/{{cookiecutter.project_slug}}/backend/app/app/models/item.py b/{{cookiecutter.project_slug}}/backend/app/app/models/item.py new file mode 100644 index 0000000000..cc7511e920 --- /dev/null +++ b/{{cookiecutter.project_slug}}/backend/app/app/models/item.py @@ -0,0 +1,34 @@ +from pydantic import BaseModel + + +# Shared properties +class ItemBase(BaseModel): + title: str = None + description: str = None + + +# Properties to receive on item creation +class ItemCreate(ItemBase): + title: str + + +# Properties to receive on item update +class ItemUpdate(ItemBase): + pass + + +# Properties shared by models stored in DB +class ItemInDBBase(ItemBase): + id: int + title: str + owner_id: int + + +# Properties to return to client +class Item(ItemInDBBase): + pass + + +# Properties properties stored in DB +class ItemInDB(ItemInDBBase): + pass diff --git a/{{cookiecutter.project_slug}}/backend/app/app/models/user.py b/{{cookiecutter.project_slug}}/backend/app/app/models/user.py index 54636e1f1b..51f1b02579 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/models/user.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/models/user.py @@ -16,13 +16,13 @@ class UserBaseInDB(UserBase): # Properties to receive via API on creation -class UserInCreate(UserBaseInDB): +class UserCreate(UserBaseInDB): email: str password: str # Properties to receive via API on update -class UserInUpdate(UserBaseInDB): +class UserUpdate(UserBaseInDB): password: Optional[str] = None diff --git a/{{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_celery.py b/{{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_celery.py index eb480309f7..2270243c72 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_celery.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_celery.py @@ -8,7 +8,7 @@ def test_celery_worker_test(superuser_token_headers): server_api = get_server_api() data = {"msg": "test"} r = requests.post( - f"{server_api}{config.API_V1_STR}/test-celery/", + f"{server_api}{config.API_V1_STR}/utils/test-celery/", json=data, headers=superuser_token_headers, ) diff --git a/{{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_items.py b/{{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_items.py new file mode 100644 index 0000000000..330ae360bd --- /dev/null +++ b/{{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_items.py @@ -0,0 +1,34 @@ +import requests + +from app.core import config +from app.tests.utils.item import create_random_item +from app.tests.utils.utils import get_server_api + + +def test_create_item(superuser_token_headers): + server_api = get_server_api() + data = {"title": "Foo", "description": "Fighters"} + response = requests.post( + f"{server_api}{config.API_V1_STR}/items/", + headers=superuser_token_headers, + json=data, + ) + content = response.json() + assert content["title"] == data["title"] + assert content["description"] == data["description"] + assert "id" in content + assert "owner_id" in content + + +def test_read_item(superuser_token_headers): + item = create_random_item() + server_api = get_server_api() + response = requests.get( + f"{server_api}{config.API_V1_STR}/items/{item.id}", + headers=superuser_token_headers, + ) + content = response.json() + assert content["title"] == item.title + assert content["description"] == item.description + assert content["id"] == item.id + assert content["owner_id"] == item.owner_id diff --git a/{{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_token.py b/{{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_login.py similarity index 100% rename from {{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_token.py rename to {{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_login.py diff --git a/{{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_user.py b/{{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_users.py similarity index 90% rename from {{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_user.py rename to {{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_users.py index cf7b8be062..119ed219fd 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_user.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_users.py @@ -3,7 +3,7 @@ from app import crud from app.core import config from app.db.session import db_session -from app.models.user import UserInCreate +from app.models.user import UserCreate from app.tests.utils.user import user_authentication_headers from app.tests.utils.utils import get_server_api, random_lower_string @@ -40,7 +40,7 @@ def test_get_existing_user(superuser_token_headers): server_api = get_server_api() username = random_lower_string() password = random_lower_string() - user_in = UserInCreate(email=username, password=password) + user_in = UserCreate(email=username, password=password) user = crud.user.create(db_session, user_in=user_in) user_id = user.id r = requests.get( @@ -58,7 +58,7 @@ def test_create_user_existing_username(superuser_token_headers): username = random_lower_string() # username = email password = random_lower_string() - user_in = UserInCreate(email=username, password=password) + user_in = UserCreate(email=username, password=password) user = crud.user.create(db_session, user_in=user_in) data = {"email": username, "password": password} r = requests.post( @@ -75,7 +75,7 @@ def test_create_user_by_normal_user(): server_api = get_server_api() username = random_lower_string() password = random_lower_string() - user_in = UserInCreate(email=username, password=password) + user_in = UserCreate(email=username, password=password) user = crud.user.create(db_session, user_in=user_in) user_token_headers = user_authentication_headers(server_api, username, password) data = {"email": username, "password": password} @@ -89,12 +89,12 @@ def test_retrieve_users(superuser_token_headers): server_api = get_server_api() username = random_lower_string() password = random_lower_string() - user_in = UserInCreate(email=username, password=password) + user_in = UserCreate(email=username, password=password) user = crud.user.create(db_session, user_in=user_in) username2 = random_lower_string() password2 = random_lower_string() - user_in2 = UserInCreate(email=username2, password=password2) + user_in2 = UserCreate(email=username2, password=password2) user2 = crud.user.create(db_session, user_in=user_in2) r = requests.get( diff --git a/{{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_item.py b/{{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_item.py new file mode 100644 index 0000000000..33d8b7bbee --- /dev/null +++ b/{{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_item.py @@ -0,0 +1,61 @@ +from app import crud +from app.models.item import ItemCreate, ItemUpdate +from app.tests.utils.user import create_random_user +from app.tests.utils.utils import random_lower_string +from app.db.session import db_session + + +def test_create_item(): + title = random_lower_string() + description = random_lower_string() + item_in = ItemCreate(title=title, description=description) + user = create_random_user() + item = crud.item.create(db_session=db_session, item_in=item_in, owner_id=user.id) + assert item.title == title + assert item.description == description + assert item.owner_id == user.id + + +def test_get_item(): + title = random_lower_string() + description = random_lower_string() + item_in = ItemCreate(title=title, description=description) + user = create_random_user() + item = crud.item.create(db_session=db_session, item_in=item_in, owner_id=user.id) + stored_item = crud.item.get(db_session=db_session, id=item.id) + assert item.id == stored_item.id + assert item.title == stored_item.title + assert item.description == stored_item.description + assert item.owner_id == stored_item.owner_id + + +def test_update_item(): + title = random_lower_string() + description = random_lower_string() + item_in = ItemCreate(title=title, description=description) + user = create_random_user() + item = crud.item.create(db_session=db_session, item_in=item_in, owner_id=user.id) + description2 = random_lower_string() + item_update = ItemUpdate(description=description2) + item2 = crud.item.update( + db_session=db_session, item=item, item_in=item_update + ) + assert item.id == item2.id + assert item.title == item2.title + assert item2.description == description2 + assert item.owner_id == item2.owner_id + + +def test_delete_item(): + title = random_lower_string() + description = random_lower_string() + item_in = ItemCreate(title=title, description=description) + user = create_random_user() + item = crud.item.create(db_session=db_session, item_in=item_in, owner_id=user.id) + item2 = crud.item.remove(db_session=db_session, id=item.id) + item3 = crud.item.get(db_session=db_session, id=item.id) + assert item3 is None + assert item2.id == item.id + assert item2.title == title + assert item2.description == description + assert item2.owner_id == user.id diff --git a/{{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_user.py b/{{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_user.py index 175e4f4c3e..e239cbac01 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_user.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_user.py @@ -2,14 +2,14 @@ from app import crud from app.db.session import db_session -from app.models.user import UserInCreate +from app.models.user import UserCreate from app.tests.utils.utils import random_lower_string def test_create_user(): email = random_lower_string() password = random_lower_string() - user_in = UserInCreate(email=email, password=password) + user_in = UserCreate(email=email, password=password) user = crud.user.create(db_session, user_in=user_in) assert user.email == email assert hasattr(user, "hashed_password") @@ -18,7 +18,7 @@ def test_create_user(): def test_authenticate_user(): email = random_lower_string() password = random_lower_string() - user_in = UserInCreate(email=email, password=password) + user_in = UserCreate(email=email, password=password) user = crud.user.create(db_session, user_in=user_in) authenticated_user = crud.user.authenticate( db_session, email=email, password=password @@ -37,7 +37,7 @@ def test_not_authenticate_user(): def test_check_if_user_is_active(): email = random_lower_string() password = random_lower_string() - user_in = UserInCreate(email=email, password=password) + user_in = UserCreate(email=email, password=password) user = crud.user.create(db_session, user_in=user_in) is_active = crud.user.is_active(user) assert is_active is True @@ -46,7 +46,7 @@ def test_check_if_user_is_active(): def test_check_if_user_is_active_inactive(): email = random_lower_string() password = random_lower_string() - user_in = UserInCreate(email=email, password=password, disabled=True) + user_in = UserCreate(email=email, password=password, disabled=True) print(user_in) user = crud.user.create(db_session, user_in=user_in) print(user) @@ -58,7 +58,7 @@ def test_check_if_user_is_active_inactive(): def test_check_if_user_is_superuser(): email = random_lower_string() password = random_lower_string() - user_in = UserInCreate(email=email, password=password, is_superuser=True) + user_in = UserCreate(email=email, password=password, is_superuser=True) user = crud.user.create(db_session, user_in=user_in) is_superuser = crud.user.is_superuser(user) assert is_superuser is True @@ -67,7 +67,7 @@ def test_check_if_user_is_superuser(): def test_check_if_user_is_superuser_normal_user(): username = random_lower_string() password = random_lower_string() - user_in = UserInCreate(email=username, password=password) + user_in = UserCreate(email=username, password=password) user = crud.user.create(db_session, user_in=user_in) is_superuser = crud.user.is_superuser(user) assert is_superuser is False @@ -76,7 +76,7 @@ def test_check_if_user_is_superuser_normal_user(): def test_get_user(): password = random_lower_string() username = random_lower_string() - user_in = UserInCreate(email=username, password=password, is_superuser=True) + user_in = UserCreate(email=username, password=password, is_superuser=True) user = crud.user.create(db_session, user_in=user_in) user_2 = crud.user.get(db_session, user_id=user.id) assert user.email == user_2.email diff --git a/{{cookiecutter.project_slug}}/backend/app/app/tests/utils/item.py b/{{cookiecutter.project_slug}}/backend/app/app/tests/utils/item.py new file mode 100644 index 0000000000..49398fa55d --- /dev/null +++ b/{{cookiecutter.project_slug}}/backend/app/app/tests/utils/item.py @@ -0,0 +1,17 @@ +from app import crud +from app.db.session import db_session +from app.models.item import ItemCreate +from app.tests.utils.user import create_random_user +from app.tests.utils.utils import random_lower_string + + +def create_random_item(owner_id: int = None): + if owner_id is None: + user = create_random_user() + owner_id = user.id + title = random_lower_string() + description = random_lower_string() + item_in = ItemCreate(title=title, description=description, id=id) + return crud.item.create( + db_session=db_session, item_in=item_in, owner_id=owner_id + ) diff --git a/{{cookiecutter.project_slug}}/backend/app/app/tests/utils/user.py b/{{cookiecutter.project_slug}}/backend/app/app/tests/utils/user.py index 9f8009fc3a..6a5b947e4a 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/tests/utils/user.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/tests/utils/user.py @@ -1,6 +1,10 @@ import requests +from app import crud from app.core import config +from app.db.session import db_session +from app.models.user import UserCreate +from app.tests.utils.utils import random_lower_string def user_authentication_headers(server_api, email, password): @@ -11,3 +15,11 @@ def user_authentication_headers(server_api, email, password): auth_token = response["access_token"] headers = {"Authorization": f"Bearer {auth_token}"} return headers + + +def create_random_user(): + email = random_lower_string() + password = random_lower_string() + user_in = UserCreate(username=email, email=email, password=password) + user = crud.user.create(db_session=db_session, user_in=user_in) + return user diff --git a/{{cookiecutter.project_slug}}/backend/app/app/tests_pre_start.py b/{{cookiecutter.project_slug}}/backend/app/app/tests_pre_start.py index 7a2a78b6ec..c1b0ccaf1a 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/tests_pre_start.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/tests_pre_start.py @@ -3,7 +3,7 @@ from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed from app.db.session import db_session -from app.tests.api.api_v1.test_token import test_get_access_token +from app.tests.api.api_v1.test_login import test_get_access_token logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) diff --git a/{{cookiecutter.project_slug}}/backend/app/scripts/lint.sh b/{{cookiecutter.project_slug}}/backend/app/scripts/lint.sh index 08bb841e97..11184afd99 100644 --- a/{{cookiecutter.project_slug}}/backend/app/scripts/lint.sh +++ b/{{cookiecutter.project_slug}}/backend/app/scripts/lint.sh @@ -5,4 +5,4 @@ set -x autoflake --remove-all-unused-imports --recursive --remove-unused-variables --in-place app --exclude=__init__.py isort --multi-line=3 --trailing-comma --force-grid-wrap=0 --combine-as --line-width 88 --recursive --apply app black app -vulture app +vulture app --min-confidence 70 diff --git a/{{cookiecutter.project_slug}}/backend/backend.dockerfile b/{{cookiecutter.project_slug}}/backend/backend.dockerfile index 35a2b613f4..60fd3b3018 100644 --- a/{{cookiecutter.project_slug}}/backend/backend.dockerfile +++ b/{{cookiecutter.project_slug}}/backend/backend.dockerfile @@ -1,6 +1,6 @@ FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7 -RUN pip install celery~=4.3 passlib[bcrypt] tenacity requests emails "fastapi>=0.7.1" uvicorn gunicorn pyjwt python-multipart email_validator jinja2 psycopg2-binary alembic SQLAlchemy +RUN pip install celery~=4.3 passlib[bcrypt] tenacity requests emails "fastapi>=0.16.0" uvicorn gunicorn pyjwt python-multipart email_validator jinja2 psycopg2-binary alembic SQLAlchemy # For development, Jupyter remote kernel, Hydrogen # Using inside the container: diff --git a/{{cookiecutter.project_slug}}/backend/celeryworker.dockerfile b/{{cookiecutter.project_slug}}/backend/celeryworker.dockerfile index bec8c5a1c5..b0d5497c10 100644 --- a/{{cookiecutter.project_slug}}/backend/celeryworker.dockerfile +++ b/{{cookiecutter.project_slug}}/backend/celeryworker.dockerfile @@ -1,6 +1,6 @@ FROM python:3.7 -RUN pip install raven celery~=4.3 passlib[bcrypt] tenacity requests "fastapi>=0.7.1" emails pyjwt email_validator jinja2 psycopg2-binary alembic SQLAlchemy +RUN pip install raven celery~=4.3 passlib[bcrypt] tenacity requests "fastapi>=0.16.0" emails pyjwt email_validator jinja2 psycopg2-binary alembic SQLAlchemy # For development, Jupyter remote kernel, Hydrogen # Using inside the container: diff --git a/{{cookiecutter.project_slug}}/backend/tests.dockerfile b/{{cookiecutter.project_slug}}/backend/tests.dockerfile index 390b7ee0e2..55d7036481 100644 --- a/{{cookiecutter.project_slug}}/backend/tests.dockerfile +++ b/{{cookiecutter.project_slug}}/backend/tests.dockerfile @@ -1,6 +1,6 @@ FROM python:3.7 -RUN pip install requests pytest tenacity passlib[bcrypt] "fastapi>=0.7.1" psycopg2-binary SQLAlchemy +RUN pip install requests pytest tenacity passlib[bcrypt] "fastapi>=0.16.0" psycopg2-binary SQLAlchemy # For development, Jupyter remote kernel, Hydrogen # Using inside the container: