diff --git a/README.md b/README.md index ad0cbe086f..0db407ffcb 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,10 @@ After using this generator, your new project (the directory created) will contai ## Release Notes -### Next release +### Latest Changes + +* Add new CRUD utils based on DB and Pydantic models. Initial PR [#23](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/23) by [@ebreton](https://github.com/ebreton). +* Add normal user testing Pytest fixture. PR [#20](https://github.com/tiangolo/full-stack-fastapi-postgresql/pull/20) by [@ebreton](https://github.com/ebreton). ### 0.4.0 diff --git a/test.sh b/test.sh index 780ed884d3..f783f27e77 100644 --- a/test.sh +++ b/test.sh @@ -9,6 +9,6 @@ cookiecutter --config-file ./testing-config.yml --no-input -f ./ cd ./testing-project -bash ./scripts/test.sh +bash ./scripts/test.sh "$@" cd ../ diff --git a/{{cookiecutter.project_slug}}/README.md b/{{cookiecutter.project_slug}}/README.md index defd2dd47e..abbb7cd634 100644 --- a/{{cookiecutter.project_slug}}/README.md +++ b/{{cookiecutter.project_slug}}/README.md @@ -55,7 +55,7 @@ If your Docker is not running in `localhost` (the URLs above wouldn't work) chec 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. +Modify or add SQLAlchemy models in `./backend/app/app/models/`, Pydantic schemas in `./backend/app/app/schemas/`, 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`. @@ -205,7 +205,7 @@ Make sure you create a "revision" of your models and that you "upgrade" your dat docker-compose exec backend bash ``` -* If you created a new model in `./backend/app/app/db_models/`, make sure to import it in `./backend/app/app/db/base.py`, that Python module (`base.py`) that imports all the models will be used by Alembic. +* If you created a new model in `./backend/app/app/models/`, make sure to import it in `./backend/app/app/db/base.py`, that Python module (`base.py`) that imports all the models will be used by Alembic. * After changing a model (for example, adding a column), inside the container, create a revision, e.g.: diff --git a/{{cookiecutter.project_slug}}/backend/app/Pipfile.lock b/{{cookiecutter.project_slug}}/backend/app/Pipfile.lock new file mode 100644 index 0000000000..5360aefa30 --- /dev/null +++ b/{{cookiecutter.project_slug}}/backend/app/Pipfile.lock @@ -0,0 +1,1071 @@ +{ + "_meta": { + "hash": { + "sha256": "6c175807a24430d5286d4ad2741d1c9f7f6e69e9f5a9bd578ba2270a1479d3d6" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.6" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "alembic": { + "hashes": [ + "sha256:4a4811119efbdc5259d1f4c8f6de977b36ad3bcc919f59a29c2960c5ef9149e4" + ], + "index": "pypi", + "version": "==1.1.0" + }, + "amqp": { + "hashes": [ + "sha256:19a917e260178b8d410122712bac69cb3e6db010d68f6101e7307508aded5e68", + "sha256:19d851b879a471fcfdcf01df9936cff924f422baa77653289f7095dedd5fb26a" + ], + "version": "==2.5.1" + }, + "bcrypt": { + "hashes": [ + "sha256:0258f143f3de96b7c14f762c770f5fc56ccd72f8a1857a451c1cd9a655d9ac89", + "sha256:0b0069c752ec14172c5f78208f1863d7ad6755a6fae6fe76ec2c80d13be41e42", + "sha256:19a4b72a6ae5bb467fea018b825f0a7d917789bcfe893e53f15c92805d187294", + "sha256:5432dd7b34107ae8ed6c10a71b4397f1c853bd39a4d6ffa7e35f40584cffd161", + "sha256:69361315039878c0680be456640f8705d76cb4a3a3fe1e057e0f261b74be4b31", + "sha256:6fe49a60b25b584e2f4ef175b29d3a83ba63b3a4df1b4c0605b826668d1b6be5", + "sha256:74a015102e877d0ccd02cdeaa18b32aa7273746914a6c5d0456dd442cb65b99c", + "sha256:763669a367869786bb4c8fcf731f4175775a5b43f070f50f46f0b59da45375d0", + "sha256:8b10acde4e1919d6015e1df86d4c217d3b5b01bb7744c36113ea43d529e1c3de", + "sha256:9fe92406c857409b70a38729dbdf6578caf9228de0aef5bc44f859ffe971a39e", + "sha256:a190f2a5dbbdbff4b74e3103cef44344bc30e61255beb27310e2aec407766052", + "sha256:a595c12c618119255c90deb4b046e1ca3bcfad64667c43d1166f2b04bc72db09", + "sha256:c9457fa5c121e94a58d6505cadca8bed1c64444b83b3204928a866ca2e599105", + "sha256:cb93f6b2ab0f6853550b74e051d297c27a638719753eb9ff66d1e4072be67133", + "sha256:d7bdc26475679dd073ba0ed2766445bb5b20ca4793ca0db32b399dccc6bc84b7", + "sha256:ff032765bb8716d9387fd5376d987a937254b0619eff0972779515b5c98820bc" + ], + "version": "==3.1.7" + }, + "billiard": { + "hashes": [ + "sha256:01afcb4e7c4fd6480940cfbd4d9edc19d7a7509d6ada533984d0d0f49901ec82", + "sha256:b8809c74f648dfe69b973c8e660bcec00603758c9db8ba89d7719f88d5f01f26" + ], + "version": "==3.6.1.0" + }, + "cachetools": { + "hashes": [ + "sha256:428266a1c0d36dc5aca63a2d7c5942e88c2c898d72139fca0e97fdd2380517ae", + "sha256:8ea2d3ce97850f31e4a08b0e2b5e6c34997d7216a9d2c98e0f3978630d4da69a" + ], + "version": "==3.1.1" + }, + "celery": { + "hashes": [ + "sha256:821d11967f0f3f8fe24bd61ecfc7b6acbb5a926b719f1e8c4d5ff7bc09e18d81", + "sha256:ae4541fb3af5182bd4af749fee9b89c4858f2792d34bb5d034967e662cf9b55c" + ], + "index": "pypi", + "version": "==4.4.0rc3" + }, + "certifi": { + "hashes": [ + "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", + "sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695" + ], + "version": "==2019.6.16" + }, + "cffi": { + "hashes": [ + "sha256:041c81822e9f84b1d9c401182e174996f0bae9991f33725d059b771744290774", + "sha256:046ef9a22f5d3eed06334d01b1e836977eeef500d9b78e9ef693f9380ad0b83d", + "sha256:066bc4c7895c91812eff46f4b1c285220947d4aa46fa0a2651ff85f2afae9c90", + "sha256:066c7ff148ae33040c01058662d6752fd73fbc8e64787229ea8498c7d7f4041b", + "sha256:2444d0c61f03dcd26dbf7600cf64354376ee579acad77aef459e34efcb438c63", + "sha256:300832850b8f7967e278870c5d51e3819b9aad8f0a2c8dbe39ab11f119237f45", + "sha256:34c77afe85b6b9e967bd8154e3855e847b70ca42043db6ad17f26899a3df1b25", + "sha256:46de5fa00f7ac09f020729148ff632819649b3e05a007d286242c4882f7b1dc3", + "sha256:4aa8ee7ba27c472d429b980c51e714a24f47ca296d53f4d7868075b175866f4b", + "sha256:4d0004eb4351e35ed950c14c11e734182591465a33e960a4ab5e8d4f04d72647", + "sha256:4e3d3f31a1e202b0f5a35ba3bc4eb41e2fc2b11c1eff38b362de710bcffb5016", + "sha256:50bec6d35e6b1aaeb17f7c4e2b9374ebf95a8975d57863546fa83e8d31bdb8c4", + "sha256:55cad9a6df1e2a1d62063f79d0881a414a906a6962bc160ac968cc03ed3efcfb", + "sha256:5662ad4e4e84f1eaa8efce5da695c5d2e229c563f9d5ce5b0113f71321bcf753", + "sha256:59b4dc008f98fc6ee2bb4fd7fc786a8d70000d058c2bbe2698275bc53a8d3fa7", + "sha256:73e1ffefe05e4ccd7bcea61af76f36077b914f92b76f95ccf00b0c1b9186f3f9", + "sha256:a1f0fd46eba2d71ce1589f7e50a9e2ffaeb739fb2c11e8192aa2b45d5f6cc41f", + "sha256:a2e85dc204556657661051ff4bab75a84e968669765c8a2cd425918699c3d0e8", + "sha256:a5457d47dfff24882a21492e5815f891c0ca35fefae8aa742c6c263dac16ef1f", + "sha256:a8dccd61d52a8dae4a825cdbb7735da530179fea472903eb871a5513b5abbfdc", + "sha256:ae61af521ed676cf16ae94f30fe202781a38d7178b6b4ab622e4eec8cefaff42", + "sha256:b012a5edb48288f77a63dba0840c92d0504aa215612da4541b7b42d849bc83a3", + "sha256:d2c5cfa536227f57f97c92ac30c8109688ace8fa4ac086d19d0af47d134e2909", + "sha256:d42b5796e20aacc9d15e66befb7a345454eef794fdb0737d1af593447c6c8f45", + "sha256:dee54f5d30d775f525894d67b1495625dd9322945e7fee00731952e0368ff42d", + "sha256:e070535507bd6aa07124258171be2ee8dfc19119c28ca94c9dfb7efd23564512", + "sha256:e1ff2748c84d97b065cc95429814cdba39bcbd77c9c85c89344b317dc0d9cbff", + "sha256:ed851c75d1e0e043cbf5ca9a8e1b13c4c90f3fbd863dacb01c0808e2b5204201" + ], + "version": "==1.12.3" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "click": { + "hashes": [ + "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", + "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" + ], + "version": "==7.0" + }, + "cssselect": { + "hashes": [ + "sha256:f612ee47b749c877ebae5bb77035d8f4202c6ad0f0fc1271b3c18ad6c4468ecf", + "sha256:f95f8dedd925fd8f54edb3d2dfb44c190d9d18512377d3c1e2388d16126879bc" + ], + "version": "==1.1.0" + }, + "cssutils": { + "hashes": [ + "sha256:a2fcf06467553038e98fea9cfe36af2bf14063eb147a70958cfcaa8f5786acaf", + "sha256:c74dbe19c92f5052774eadb15136263548dd013250f1ed1027988e7fef125c8d" + ], + "version": "==1.0.2" + }, + "dnspython": { + "hashes": [ + "sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01", + "sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d" + ], + "version": "==1.16.0" + }, + "email-validator": { + "hashes": [ + "sha256:79966e318d6d68fed359c90f8f19d242bcc178b724011f1c07145bd093da6cc7" + ], + "index": "pypi", + "version": "==1.0.4" + }, + "emails": { + "hashes": [ + "sha256:2d93bb09539d65a16cf1f68db4ffd0f7f45067633e950866e8a4ef89a7c290ec", + "sha256:fcc02567a528eae6b66d2a5c20ce7a0326e4f6b201bc8ae302f89413164db06a" + ], + "index": "pypi", + "version": "==0.5.15" + }, + "fastapi": { + "hashes": [ + "sha256:273d099b7a8dd7cb1de166ea38216528406fbe042fb604d0025c00810e5ea858", + "sha256:35c85f39f235da652f24eeb9378a90d479ed087fa9ae5798bac9dce2257b6115" + ], + "index": "pypi", + "version": "==0.38.1" + }, + "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:e00cbd7ba01ff748e494248183abc6e153f49181169d8a3d41bb49132ca01dfc" + ], + "version": "==0.0.13" + }, + "idna": { + "hashes": [ + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + ], + "version": "==2.8" + }, + "importlib-metadata": { + "hashes": [ + "sha256:0c505102757e7fa28b9f0958d8bc81301159dea16e2649858c92edc158b78a83", + "sha256:9a9f75ce32e78170905888acbf2376a81d3f21ecb3bb4867050413411d3ca7a9" + ], + "version": "==0.21" + }, + "jinja2": { + "hashes": [ + "sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", + "sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b" + ], + "index": "pypi", + "version": "==2.10.1" + }, + "kombu": { + "hashes": [ + "sha256:55274dc75eb3c3994538b0973a0fadddb236b698a4bc135b8aa4981e0a710b8f", + "sha256:e5f0312dfb9011bebbf528ccaf118a6c2b5c3b8244451f08381fb23e7715809b" + ], + "version": "==4.6.4" + }, + "lxml": { + "hashes": [ + "sha256:02ca7bf899da57084041bb0f6095333e4d239948ad3169443f454add9f4e9cb4", + "sha256:096b82c5e0ea27ce9138bcbb205313343ee66a6e132f25c5ed67e2c8d960a1bc", + "sha256:0a920ff98cf1aac310470c644bc23b326402d3ef667ddafecb024e1713d485f1", + "sha256:17cae1730a782858a6e2758fd20dd0ef7567916c47757b694a06ffafdec20046", + "sha256:17e3950add54c882e032527795c625929613adbd2ce5162b94667334458b5a36", + "sha256:1f4f214337f6ee5825bf90a65d04d70aab05526c08191ab888cb5149501923c5", + "sha256:2e8f77db25b0a96af679e64ff9bf9dddb27d379c9900c3272f3041c4d1327c9d", + "sha256:4dffd405390a45ecb95ab5ab1c1b847553c18b0ef8ed01e10c1c8b1a76452916", + "sha256:6b899931a5648862c7b88c795eddff7588fb585e81cecce20f8d9da16eff96e0", + "sha256:726c17f3e0d7a7200718c9a890ccfeab391c9133e363a577a44717c85c71db27", + "sha256:760c12276fee05c36f95f8040180abc7fbebb9e5011447a97cdc289b5d6ab6fc", + "sha256:796685d3969815a633827c818863ee199440696b0961e200b011d79b9394bbe7", + "sha256:891fe897b49abb7db470c55664b198b1095e4943b9f82b7dcab317a19116cd38", + "sha256:a471628e20f03dcdfde00770eeaf9c77811f0c331c8805219ca7b87ac17576c5", + "sha256:a63b4fd3e2cabdcc9d918ed280bdde3e8e9641e04f3c59a2a3109644a07b9832", + "sha256:b0b84408d4eabc6de9dd1e1e0bc63e7731e890c0b378a62443e5741cfd0ae90a", + "sha256:be78485e5d5f3684e875dab60f40cddace2f5b2a8f7fede412358ab3214c3a6f", + "sha256:c27eaed872185f047bb7f7da2d21a7d8913457678c9a100a50db6da890bc28b9", + "sha256:c81cb40bff373ab7a7446d6bbca0190bccc5be3448b47b51d729e37799bb5692", + "sha256:d11874b3c33ee441059464711cd365b89fa1a9cf19ae75b0c189b01fbf735b84", + "sha256:e9c028b5897901361d81a4718d1db217b716424a0283afe9d6735fe0caf70f79", + "sha256:fe489d486cd00b739be826e8c1be188ddb74c7a1ca784d93d06fda882a6a1681" + ], + "version": "==4.4.1" + }, + "mako": { + "hashes": [ + "sha256:a36919599a9b7dc5d86a7a8988f23a9a3a3d083070023bab23d64f7f1d1e0a4b" + ], + "version": "==1.1.0" + }, + "markupsafe": { + "hashes": [ + "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", + "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", + "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", + "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", + "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", + "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", + "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", + "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", + "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", + "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", + "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", + "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", + "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", + "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", + "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", + "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", + "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", + "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", + "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", + "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", + "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", + "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", + "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", + "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" + ], + "version": "==1.1.1" + }, + "more-itertools": { + "hashes": [ + "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", + "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4" + ], + "version": "==7.2.0" + }, + "passlib": { + "extras": [ + "bcrypt" + ], + "hashes": [ + "sha256:3d948f64138c25633613f303bcc471126eae67c04d5e3f6b7b8ce6242f8653e0", + "sha256:43526aea08fa32c6b6dbbbe9963c4c767285b78147b7437597f992812f69d280" + ], + "index": "pypi", + "version": "==1.7.1" + }, + "premailer": { + "hashes": [ + "sha256:d5aa0cba8687a231a2a43d9021735ed02a166dbf9c2b1669df22bfc863e5d948", + "sha256:fcc1062329ba37668f95b2bf95e78d730eebf7851d742028251384a04e87fa22" + ], + "version": "==3.6.1" + }, + "psycopg2-binary": { + "hashes": [ + "sha256:080c72714784989474f97be9ab0ddf7b2ad2984527e77f2909fcd04d4df53809", + "sha256:110457be80b63ff4915febb06faa7be002b93a76e5ba19bf3f27636a2ef58598", + "sha256:171352a03b22fc099f15103959b52ee77d9a27e028895d7e5fde127aa8e3bac5", + "sha256:19d013e7b0817087517a4b3cab39c084d78898369e5c46258aab7be4f233d6a1", + "sha256:249b6b21ae4eb0f7b8423b330aa80fab5f821b9ffc3f7561a5e2fd6bb142cf5d", + "sha256:2ac0731d2d84b05c7bb39e85b7e123c3a0acd4cda631d8d542802c88deb9e87e", + "sha256:2b6d561193f0dc3f50acfb22dd52ea8c8dfbc64bcafe3938b5f209cc17cb6f00", + "sha256:2bd23e242e954214944481124755cbefe7c2cf563b1a54cd8d196d502f2578bf", + "sha256:3e1239242ca60b3725e65ab2f13765fc199b03af9eaf1b5572f0e97bdcee5b43", + "sha256:3eb70bb697abbe86b1d2b1316370c02ba320bfd1e9e35cf3b9566a855ea8e4e5", + "sha256:51a2fc7e94b98bd1bb5d4570936f24fc2b0541b63eccadf8fdea266db8ad2f70", + "sha256:52f1bdafdc764b7447e393ed39bb263eccb12bfda25a4ac06d82e3a9056251f6", + "sha256:5b3581319a3951f1e866f4f6c5e42023db0fae0284273b82e97dfd32c51985cd", + "sha256:63c1b66e3b2a3a336288e4bcec499e0dc310cd1dceaed1c46fa7419764c68877", + "sha256:8123a99f24ecee469e5c1339427bcdb2a33920a18bb5c0d58b7c13f3b0298ba3", + "sha256:85e699fcabe7f817c0f0a412d4e7c6627e00c412b418da7666ff353f38e30f67", + "sha256:8dbff4557bbef963697583366400822387cccf794ccb001f1f2307ed21854c68", + "sha256:908d21d08d6b81f1b7e056bbf40b2f77f8c499ab29e64ec5113052819ef1c89b", + "sha256:af39d0237b17d0a5a5f638e9dffb34013ce2b1d41441fd30283e42b22d16858a", + "sha256:af51bb9f055a3f4af0187149a8f60c9d516cf7d5565b3dac53358796a8fb2a5b", + "sha256:b2ecac57eb49e461e86c092761e6b8e1fd9654dbaaddf71a076dcc869f7014e2", + "sha256:cd37cc170678a4609becb26b53a2bc1edea65177be70c48dd7b39a1149cabd6e", + "sha256:d17e3054b17e1a6cb8c1140f76310f6ede811e75b7a9d461922d2c72973f583e", + "sha256:d305313c5a9695f40c46294d4315ed3a07c7d2b55e48a9010dad7db7a66c8b7f", + "sha256:dd0ef0eb1f7dd18a3f4187226e226a7284bda6af5671937a221766e6ef1ee88f", + "sha256:e1adff53b56db9905db48a972fb89370ad5736e0450b96f91bcf99cadd96cfd7", + "sha256:f0d43828003c82dbc9269de87aa449e9896077a71954fbbb10a614c017e65737", + "sha256:f78e8b487de4d92640105c1389e5b90be3496b1d75c90a666edd8737cc2dbab7" + ], + "index": "pypi", + "version": "==2.8.3" + }, + "pycparser": { + "hashes": [ + "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" + ], + "version": "==2.19" + }, + "pydantic": { + "hashes": [ + "sha256:18598557f0d9ab46173045910ed50458c4fb4d16153c23346b504d7a5b679f77", + "sha256:6a9335c968e13295430a208487e74d69fef40168b72dea8d975765d14e2da660", + "sha256:6f5eb88fe4c21380aa064b7d249763fc6306f0b001d7e7d52d80866d1afc9ed3", + "sha256:bc6c6a78647d7a65a493e1107572d993f26a652c49183201e3c7d23924bf7311", + "sha256:e1a63b4e6bf8820833cb6fa239ffbe8eec57ccdd7d66359eff20e68a83c1deeb", + "sha256:ede2d65ae33788d4e26e12b330b4a32c53cb14131c65bca3a59f037c73f6ee7a" + ], + "index": "pypi", + "version": "==0.32.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:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32", + "sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7" + ], + "version": "==2019.2" + }, + "raven": { + "hashes": [ + "sha256:3fa6de6efa2493a7c827472e984ce9b020797d0da16f1db67197bcc23c8fae54", + "sha256:44a13f87670836e153951af9a3c80405d36b43097db869a36e92809673692ce4" + ], + "index": "pypi", + "version": "==6.10.0" + }, + "requests": { + "hashes": [ + "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", + "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" + ], + "index": "pypi", + "version": "==2.22.0" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "version": "==1.12.0" + }, + "sqlalchemy": { + "hashes": [ + "sha256:2f8ff566a4d3a92246d367f2e9cd6ed3edeef670dcd6dda6dfdc9efed88bcd80" + ], + "index": "pypi", + "version": "==1.3.8" + }, + "starlette": { + "hashes": [ + "sha256:f600bf9d0beeeeebcb143e6d0c4f8858c2b05067d5a4feb446ba7400ba5e5dc5" + ], + "version": "==0.12.8" + }, + "tenacity": { + "hashes": [ + "sha256:6a7511a59145c2e319b7d04ddd93c12d48cc3d3c8fa42c2846d33a620ee91f57", + "sha256:a4eb168dbf55ed2cae27e7c6b2bd48ab54dabaf294177d998330cf59f294c112" + ], + "index": "pypi", + "version": "==5.1.1" + }, + "urllib3": { + "hashes": [ + "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", + "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232" + ], + "version": "==1.25.3" + }, + "uvicorn": { + "hashes": [ + "sha256:639cdeb92cdb0205494ca36be03d08e05d57249491751feb5fc74b023a0d18f3" + ], + "index": "pypi", + "version": "==0.9.0" + }, + "uvloop": { + "hashes": [ + "sha256:0deb6c97c5807c792dd9024bab90e6ca49e981862103cb2ea37b430c1ca0a267", + "sha256:155b34d513655e753d07f499a7e811970e2d397f240dfcbec0b32a9587159c99", + "sha256:1df3ddfa280206e9999ae1c777a20836eb895bcec6dc9fae2cbb6eecfafb099e", + "sha256:5b19361c8767e1dc61f6367f948d4f3dc5504b9f2eba488641b3d26ec14498ba", + "sha256:942cd07035510b149d6160796f4e972137130ae953871b6a98c2cf5d5ab68c2e", + "sha256:c63b6c0bf33144c604dd72f7eecf2d3a3ac7405c503c67bd98128cf306efe18a", + "sha256:e698a20a3b4ccb380d207f9d491d4085d7c38d364f6a0bae98684a1612a9607a" + ], + "version": "==0.13.0" + }, + "vine": { + "hashes": [ + "sha256:133ee6d7a9016f177ddeaf191c1f58421a1dcc6ee9a42c58b34bed40e1d2cd87", + "sha256:ea4947cc56d1fd6f2095c8d543ee25dad966f78692528e68b4fada11ba3f98af" + ], + "version": "==1.3.0" + }, + "websockets": { + "hashes": [ + "sha256:049e694abe33f8a1d99969fee7bfc0ae6761f7fd5f297c58ea933b27dd6805f2", + "sha256:73ce69217e4655783ec72ce11c151053fcbd5b837cc39de7999e19605182e28a", + "sha256:83e63aa73331b9ca21af61df8f115fb5fbcba3f281bee650a4ad16a40cd1ef15", + "sha256:882a7266fa867a2ebb2c0baaa0f9159cabf131cf18c1b4270d79ad42f9208dc5", + "sha256:8c77f7d182a6ea2a9d09c2612059f3ad859a90243e899617137ee3f6b7f2b584", + "sha256:8d7a20a2f97f1e98c765651d9fb9437201a9ccc2c70e94b0270f1c5ef29667a3", + "sha256:a7affaeffbc5d55681934c16bb6b8fc82bb75b175e7fd4dcca798c938bde8dda", + "sha256:c82e286555f839846ef4f0fdd6910769a577952e1e26aa8ee7a6f45f040e3c2b", + "sha256:e906128532a14b9d264a43eb48f9b3080d53a9bda819ab45bf56b8039dc606ac", + "sha256:e9102043a81cdc8b7c8032ff4bce39f6229e4ac39cb2010946c912eeb84e2cb6", + "sha256:f5cb2683367e32da6a256b60929a3af9c29c212b5091cf5bace9358d03011bf5" + ], + "version": "==8.0.2" + }, + "zipp": { + "hashes": [ + "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", + "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335" + ], + "version": "==0.6.0" + } + }, + "develop": { + "appdirs": { + "hashes": [ + "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", + "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" + ], + "version": "==1.4.3" + }, + "appnope": { + "hashes": [ + "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", + "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71" + ], + "markers": "sys_platform == 'darwin'", + "version": "==0.1.0" + }, + "atomicwrites": { + "hashes": [ + "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", + "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" + ], + "version": "==1.3.0" + }, + "attrs": { + "hashes": [ + "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", + "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" + ], + "version": "==19.1.0" + }, + "autoflake": { + "hashes": [ + "sha256:680cb9dade101ed647488238ccb8b8bfb4369b53d58ba2c8cdf7d5d54e01f95b" + ], + "index": "pypi", + "version": "==1.3.1" + }, + "backcall": { + "hashes": [ + "sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4", + "sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2" + ], + "version": "==0.1.0" + }, + "black": { + "hashes": [ + "sha256:09a9dcb7c46ed496a9850b76e4e825d6049ecd38b611f1224857a79bd985a8cf", + "sha256:68950ffd4d9169716bcb8719a56c07a2f4485354fec061cdd5910aa07369731c" + ], + "index": "pypi", + "version": "==19.3b0" + }, + "bleach": { + "hashes": [ + "sha256:213336e49e102af26d9cde77dd2d0397afabc5a6bf2fed985dc35b5d1e285a16", + "sha256:3fdf7f77adcf649c9911387df51254b813185e32b2c6619f690b593a617e19fa" + ], + "version": "==3.1.0" + }, + "click": { + "hashes": [ + "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", + "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" + ], + "version": "==7.0" + }, + "decorator": { + "hashes": [ + "sha256:86156361c50488b84a3f148056ea716ca587df2f0de1d34750d35c21312725de", + "sha256:f069f3a01830ca754ba5258fde2278454a0b5b79e0d7f5c13b3b97e57d4acff6" + ], + "version": "==4.4.0" + }, + "defusedxml": { + "hashes": [ + "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93", + "sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5" + ], + "version": "==0.6.0" + }, + "entrypoints": { + "hashes": [ + "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", + "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" + ], + "version": "==0.3" + }, + "flake8": { + "hashes": [ + "sha256:19241c1cbc971b9962473e4438a2ca19749a7dd002dd1a946eaba171b4114548", + "sha256:8e9dfa3cecb2400b3738a42c54c3043e821682b9c840b0448c0503f781130696" + ], + "index": "pypi", + "version": "==3.7.8" + }, + "importlib-metadata": { + "hashes": [ + "sha256:0c505102757e7fa28b9f0958d8bc81301159dea16e2649858c92edc158b78a83", + "sha256:9a9f75ce32e78170905888acbf2376a81d3f21ecb3bb4867050413411d3ca7a9" + ], + "version": "==0.21" + }, + "ipykernel": { + "hashes": [ + "sha256:167c3ef08450f5e060b76c749905acb0e0fbef9365899377a4a1eae728864383", + "sha256:b503913e0b4cce7ed2de965457dfb2edd633e8234161a60e23f2fe2161345d12" + ], + "version": "==5.1.2" + }, + "ipython": { + "hashes": [ + "sha256:c4ab005921641e40a68e405e286e7a1fcc464497e14d81b6914b4fd95e5dee9b", + "sha256:dd76831f065f17bddd7eaa5c781f5ea32de5ef217592cf019e34043b56895aa1" + ], + "markers": "python_version >= '3.3'", + "version": "==7.8.0" + }, + "ipython-genutils": { + "hashes": [ + "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", + "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8" + ], + "version": "==0.2.0" + }, + "ipywidgets": { + "hashes": [ + "sha256:13ffeca438e0c0f91ae583dc22f50379b9d6b28390ac7be8b757140e9a771516", + "sha256:e945f6e02854a74994c596d9db83444a1850c01648f1574adf144fbbabe05c97" + ], + "version": "==7.5.1" + }, + "isort": { + "hashes": [ + "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", + "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd" + ], + "index": "pypi", + "version": "==4.3.21" + }, + "jedi": { + "hashes": [ + "sha256:786b6c3d80e2f06fd77162a07fed81b8baa22dde5d62896a790a331d6ac21a27", + "sha256:ba859c74fa3c966a22f2aeebe1b74ee27e2a462f56d3f5f7ca4a59af61bfe42e" + ], + "version": "==0.15.1" + }, + "jinja2": { + "hashes": [ + "sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", + "sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b" + ], + "index": "pypi", + "version": "==2.10.1" + }, + "jsonschema": { + "hashes": [ + "sha256:5f9c0a719ca2ce14c5de2fd350a64fd2d13e8539db29836a86adc990bb1a068f", + "sha256:8d4a2b7b6c2237e0199c8ea1a6d3e05bf118e289ae2b9d7ba444182a2959560d" + ], + "version": "==3.0.2" + }, + "jupyter": { + "hashes": [ + "sha256:3e1f86076bbb7c8c207829390305a2b1fe836d471ed54be66a3b8c41e7f46cc7", + "sha256:5b290f93b98ffbc21c0c7e749f054b3267782166d72fa5e3ed1ed4eaf34a2b78", + "sha256:d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f" + ], + "index": "pypi", + "version": "==1.0.0" + }, + "jupyter-client": { + "hashes": [ + "sha256:73a809a2964afa07adcc1521537fddb58c2ffbb7e84d53dc5901cf80480465b3", + "sha256:98e8af5edff5d24e4d31e73bc21043130ae9d955a91aa93fc0bc3b1d0f7b5880" + ], + "version": "==5.3.1" + }, + "jupyter-console": { + "hashes": [ + "sha256:308ce876354924fb6c540b41d5d6d08acfc946984bf0c97777c1ddcb42e0b2f5", + "sha256:cc80a97a5c389cbd30252ffb5ce7cefd4b66bde98219edd16bf5cb6f84bb3568" + ], + "version": "==6.0.0" + }, + "jupyter-core": { + "hashes": [ + "sha256:2c6e7c1e9f2ac45b5c2ceea5730bc9008d92fe59d0725eac57b04c0edfba24f7", + "sha256:f4fa22d6cf25f34807c995f22d2923693575c70f02557bcbfbe59bd5ec8d8b84" + ], + "version": "==4.5.0" + }, + "markupsafe": { + "hashes": [ + "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", + "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", + "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", + "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", + "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", + "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", + "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", + "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", + "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", + "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", + "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", + "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", + "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", + "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", + "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", + "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", + "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", + "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", + "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", + "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", + "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", + "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", + "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", + "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" + ], + "version": "==1.1.1" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, + "mistune": { + "hashes": [ + "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e", + "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4" + ], + "version": "==0.8.4" + }, + "more-itertools": { + "hashes": [ + "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", + "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4" + ], + "version": "==7.2.0" + }, + "mypy": { + "hashes": [ + "sha256:0107bff4f46a289f0e4081d59b77cef1c48ea43da5a0dbf0005d54748b26df2a", + "sha256:07957f5471b3bb768c61f08690c96d8a09be0912185a27a68700f3ede99184e4", + "sha256:10af62f87b6921eac50271e667cc234162a194e742d8e02fc4ddc121e129a5b0", + "sha256:11fd60d2f69f0cefbe53ce551acf5b1cec1a89e7ce2d47b4e95a84eefb2899ae", + "sha256:15e43d3b1546813669bd1a6ec7e6a11d2888db938e0607f7b5eef6b976671339", + "sha256:352c24ba054a89bb9a35dd064ee95ab9b12903b56c72a8d3863d882e2632dc76", + "sha256:437020a39417e85e22ea8edcb709612903a9924209e10b3ec6d8c9f05b79f498", + "sha256:49925f9da7cee47eebf3420d7c0e00ec662ec6abb2780eb0a16260a7ba25f9c4", + "sha256:6724fcd5777aa6cebfa7e644c526888c9d639bd22edd26b2a8038c674a7c34bd", + "sha256:7a17613f7ea374ab64f39f03257f22b5755335b73251d0d253687a69029701ba", + "sha256:cdc1151ced496ca1496272da7fc356580e95f2682be1d32377c22ddebdf73c91" + ], + "index": "pypi", + "version": "==0.720" + }, + "mypy-extensions": { + "hashes": [ + "sha256:37e0e956f41369209a3d5f34580150bcacfabaa57b33a15c0b25f4b5725e0812", + "sha256:b16cabe759f55e3409a7d231ebd2841378fb0c27a5d1994719e340e4f429ac3e" + ], + "version": "==0.4.1" + }, + "nbconvert": { + "hashes": [ + "sha256:427a468ec26e7d68a529b95f578d5cbf018cb4c1f889e897681c2b6d11897695", + "sha256:48d3c342057a2cf21e8df820d49ff27ab9f25fc72b8f15606bd47967333b2709" + ], + "version": "==5.6.0" + }, + "nbformat": { + "hashes": [ + "sha256:b9a0dbdbd45bb034f4f8893cafd6f652ea08c8c1674ba83f2dc55d3955743b0b", + "sha256:f7494ef0df60766b7cabe0a3651556345a963b74dbc16bc7c18479041170d402" + ], + "version": "==4.4.0" + }, + "notebook": { + "hashes": [ + "sha256:660976fe4fe45c7aa55e04bf4bccb9f9566749ff637e9020af3422f9921f9a5d", + "sha256:b0a290f5cc7792d50a21bec62b3c221dd820bf00efa916ce9aeec4b5354bde20" + ], + "version": "==6.0.1" + }, + "packaging": { + "hashes": [ + "sha256:a7ac867b97fdc07ee80a8058fe4435ccd274ecc3b0ed61d852d7d53055528cf9", + "sha256:c491ca87294da7cc01902edbe30a5bc6c4c28172b5138ab4e4aa1b9d7bfaeafe" + ], + "version": "==19.1" + }, + "pandocfilters": { + "hashes": [ + "sha256:b3dd70e169bb5449e6bc6ff96aea89c5eea8c5f6ab5e207fc2f521a2cf4a0da9" + ], + "version": "==1.4.2" + }, + "parso": { + "hashes": [ + "sha256:63854233e1fadb5da97f2744b6b24346d2750b85965e7e399bec1620232797dc", + "sha256:666b0ee4a7a1220f65d367617f2cd3ffddff3e205f3f16a0284df30e774c2a9c" + ], + "version": "==0.5.1" + }, + "pexpect": { + "hashes": [ + "sha256:2094eefdfcf37a1fdbfb9aa090862c1a4878e5c7e0e7e7088bdb511c558e5cd1", + "sha256:9e2c1fd0e6ee3a49b28f95d4b33bc389c89b20af6a1255906e90ff1262ce62eb" + ], + "markers": "sys_platform != 'win32'", + "version": "==4.7.0" + }, + "pickleshare": { + "hashes": [ + "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca", + "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56" + ], + "version": "==0.7.5" + }, + "pluggy": { + "hashes": [ + "sha256:0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc", + "sha256:b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c" + ], + "version": "==0.12.0" + }, + "prometheus-client": { + "hashes": [ + "sha256:71cd24a2b3eb335cb800c7159f423df1bd4dcd5171b234be15e3f31ec9f622da" + ], + "version": "==0.7.1" + }, + "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:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", + "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" + ], + "version": "==1.8.0" + }, + "pycodestyle": { + "hashes": [ + "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", + "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" + ], + "version": "==2.5.0" + }, + "pyflakes": { + "hashes": [ + "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", + "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" + ], + "version": "==2.1.1" + }, + "pygments": { + "hashes": [ + "sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", + "sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297" + ], + "version": "==2.4.2" + }, + "pyparsing": { + "hashes": [ + "sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", + "sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4" + ], + "version": "==2.4.2" + }, + "pyrsistent": { + "hashes": [ + "sha256:34b47fa169d6006b32e99d4b3c4031f155e6e68ebcc107d6454852e8e0ee6533" + ], + "version": "==0.15.4" + }, + "pytest": { + "hashes": [ + "sha256:95d13143cc14174ca1a01ec68e84d76ba5d9d493ac02716fd9706c949a505210", + "sha256:b78fe2881323bd44fd9bd76e5317173d4316577e7b1cddebae9136a4495ec865" + ], + "index": "pypi", + "version": "==5.1.2" + }, + "python-dateutil": { + "hashes": [ + "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", + "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" + ], + "version": "==2.8.0" + }, + "pyzmq": { + "hashes": [ + "sha256:01636e95a88d60118479041c6aaaaf5419c6485b7b1d37c9c4dd424b7b9f1121", + "sha256:021dba0d1436516092c624359e5da51472b11ba8edffa334218912f7e8b65467", + "sha256:0463bd941b6aead494d4035f7eebd70035293dd6caf8425993e85ad41de13fa3", + "sha256:05fd51edd81eed798fccafdd49c936b6c166ffae7b32482e4d6d6a2e196af4e6", + "sha256:1fadc8fbdf3d22753c36d4172169d184ee6654f8d6539e7af25029643363c490", + "sha256:22efa0596cf245a78a99060fe5682c4cd00c58bb7614271129215c889062db80", + "sha256:260c70b7c018905ec3659d0f04db735ac830fe27236e43b9dc0532cf7c9873ef", + "sha256:2762c45e289732d4450406cedca35a9d4d71e449131ba2f491e0bf473e3d2ff2", + "sha256:2fc6cada8dc53521c1189596f1898d45c5f68603194d3a6453d6db4b27f4e12e", + "sha256:343b9710a61f2b167673bea1974e70b5dccfe64b5ed10626798f08c1f7227e72", + "sha256:41bf96d5f554598a0632c3ec28e3026f1d6591a50f580df38eff0b8067efb9e7", + "sha256:856b2cdf7a1e2cbb84928e1e8db0ea4018709b39804103d3a409e5584f553f57", + "sha256:85b869abc894672de9aecdf032158ea8ad01e2f0c3b09ef60e3687fb79418096", + "sha256:93f44739db69234c013a16990e43db1aa0af3cf5a4b8b377d028ff24515fbeb3", + "sha256:98fa3e75ccb22c0dc99654e3dd9ff693b956861459e8c8e8734dd6247b89eb29", + "sha256:9a22c94d2e93af8bebd4fcf5fa38830f5e3b1ff0d4424e2912b07651eb1bafb4", + "sha256:a7d3f4b4bbb5d7866ae727763268b5c15797cbd7b63ea17f3b0ec1067da8994b", + "sha256:b645a49376547b3816433a7e2d2a99135c8e651e50497e7ecac3bd126e4bea16", + "sha256:cf0765822e78cf9e45451647a346d443f66792aba906bc340f4e0ac7870c169c", + "sha256:dc398e1e047efb18bfab7a8989346c6921a847feae2cad69fedf6ca12fb99e2c", + "sha256:dd5995ae2e80044e33b5077fb4bc2b0c1788ac6feaf15a6b87a00c14b4bdd682", + "sha256:e03fe5e07e70f245dc9013a9d48ae8cc4b10c33a1968039c5a3b64b5d01d083d", + "sha256:ea09a306144dff2795e48439883349819bef2c53c0ee62a3c2fae429451843bb", + "sha256:f4e37f33da282c3c319849877e34f97f0a3acec09622ec61b7333205bdd13b52", + "sha256:fa4bad0d1d173dee3e8ef3c3eb6b2bb6c723fc7a661eeecc1ecb2fa99860dd45" + ], + "version": "==18.1.0" + }, + "qtconsole": { + "hashes": [ + "sha256:40d5d8e00d070ea266dbf6f0da74c4b9597b8b8d67cd8233c3ffd8debf923703", + "sha256:b91e7412587e6cfe1644696538f73baf5611e837be5406633218443b2827c6d9" + ], + "version": "==4.5.5" + }, + "send2trash": { + "hashes": [ + "sha256:60001cc07d707fe247c94f74ca6ac0d3255aabcb930529690897ca2a39db28b2", + "sha256:f1691922577b6fa12821234aeb57599d887c4900b9ca537948d2dac34aea888b" + ], + "version": "==1.5.0" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "version": "==1.12.0" + }, + "terminado": { + "hashes": [ + "sha256:d9d012de63acb8223ac969c17c3043337c2fcfd28f3aea1ee429b345d01ef460", + "sha256:de08e141f83c3a0798b050ecb097ab6259c3f0331b2f7b7750c9075ced2c20c2" + ], + "version": "==0.8.2" + }, + "testpath": { + "hashes": [ + "sha256:46c89ebb683f473ffe2aab0ed9f12581d4d078308a3cb3765d79c6b2317b0109", + "sha256:b694b3d9288dbd81685c5d2e7140b81365d46c29f5db4bc659de5aa6b98780f8" + ], + "version": "==0.4.2" + }, + "toml": { + "hashes": [ + "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", + "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" + ], + "version": "==0.10.0" + }, + "tornado": { + "hashes": [ + "sha256:349884248c36801afa19e342a77cc4458caca694b0eda633f5878e458a44cb2c", + "sha256:398e0d35e086ba38a0427c3b37f4337327231942e731edaa6e9fd1865bbd6f60", + "sha256:4e73ef678b1a859f0cb29e1d895526a20ea64b5ffd510a2307b5998c7df24281", + "sha256:559bce3d31484b665259f50cd94c5c28b961b09315ccd838f284687245f416e5", + "sha256:abbe53a39734ef4aba061fca54e30c6b4639d3e1f59653f0da37a0003de148c7", + "sha256:c845db36ba616912074c5b1ee897f8e0124df269468f25e4fe21fe72f6edd7a9", + "sha256:c9399267c926a4e7c418baa5cbe91c7d1cf362d505a1ef898fde44a07c9dd8a5" + ], + "version": "==6.0.3" + }, + "traitlets": { + "hashes": [ + "sha256:9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835", + "sha256:c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9" + ], + "version": "==4.3.2" + }, + "typed-ast": { + "hashes": [ + "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", + "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", + "sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", + "sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", + "sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", + "sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", + "sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", + "sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", + "sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", + "sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", + "sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", + "sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", + "sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", + "sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", + "sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12" + ], + "version": "==1.4.0" + }, + "typing-extensions": { + "hashes": [ + "sha256:2ed632b30bb54fc3941c382decfd0ee4148f5c591651c9272473fea2c6397d95", + "sha256:b1edbbf0652660e32ae780ac9433f4231e7339c7f9a8057d0f042fcbcea49b87", + "sha256:d8179012ec2c620d3791ca6fe2bf7979d979acdbef1fca0bc56b37411db682ed" + ], + "version": "==3.7.4" + }, + "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:079f87d87270bce047512400efd70238820751a11d2d8cb137a5a5bdbaf255c7", + "sha256:bd314f8ceb488571a5ffea6cc5b9fc6cba0adaf88a9d2386b93a489751938bcd" + ], + "version": "==3.5.1" + }, + "zipp": { + "hashes": [ + "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", + "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335" + ], + "version": "==0.6.0" + } + } +} 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 index 131e9852d4..f4db9d99ce 100644 --- 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 @@ -6,8 +6,8 @@ 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 +from app.models.user import User as DBUser +from app.schemas.item import Item, ItemCreate, ItemUpdate router = APIRouter() @@ -41,7 +41,9 @@ def create_item( """ Create new item. """ - item = crud.item.create(db_session=db, item_in=item_in, owner_id=current_user.id) + item = crud.item.create_with_owner( + db_session=db, obj_in=item_in, owner_id=current_user.id + ) return item @@ -61,7 +63,7 @@ def update_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) + item = crud.item.update(db_session=db, db_obj=item, obj_in=item_in) return item diff --git a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/login.py b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/login.py index 1db861be5f..b2c9bffe2c 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/login.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/login.py @@ -10,10 +10,10 @@ from app.core import config from app.core.jwt import create_access_token from app.core.security import get_password_hash -from app.db_models.user import User as DBUser -from app.models.msg import Msg -from app.models.token import Token -from app.models.user import User +from app.models.user import User as DBUser +from app.schemas.msg import Msg +from app.schemas.token import Token +from app.schemas.user import User from app.utils import ( generate_password_reset_token, send_reset_password_email, diff --git a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/users.py b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/users.py index 966fe12ddc..941bab669d 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/users.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/api/api_v1/endpoints/users.py @@ -2,15 +2,15 @@ from fastapi import APIRouter, Body, Depends, HTTPException from fastapi.encoders import jsonable_encoder -from pydantic.types import EmailStr +from pydantic.networks import EmailStr 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_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, UserCreate, UserInDB, UserUpdate +from app.models.user import User as DBUser +from app.schemas.user import User, UserCreate, UserUpdate from app.utils import send_new_account_email router = APIRouter() @@ -46,7 +46,7 @@ def create_user( status_code=400, detail="The user with this username already exists in the system.", ) - user = crud.user.create(db, user_in=user_in) + user = crud.user.create(db, obj_in=user_in) if config.EMAILS_ENABLED and user_in.email: send_new_account_email( email_to=user_in.email, username=user_in.email, password=user_in.password @@ -74,7 +74,7 @@ def update_user_me( user_in.full_name = full_name if email is not None: user_in.email = email - user = crud.user.update(db, user=current_user, user_in=user_in) + user = crud.user.update(db, db_obj=current_user, obj_in=user_in) return user @@ -103,7 +103,7 @@ def create_user_open( if not config.USERS_OPEN_REGISTRATION: raise HTTPException( status_code=403, - detail="Open user resgistration is forbidden on this server", + detail="Open user registration is forbidden on this server", ) user = crud.user.get_by_email(db, email=email) if user: @@ -112,7 +112,7 @@ def create_user_open( detail="The user with this username already exists in the system", ) user_in = UserCreate(password=password, email=email, full_name=full_name) - user = crud.user.create(db, user_in=user_in) + user = crud.user.create(db, obj_in=user_in) return user @@ -125,7 +125,7 @@ def read_user_by_id( """ Get a specific user by id. """ - user = crud.user.get(db, user_id=user_id) + user = crud.user.get(db, id=user_id) if user == current_user: return user if not crud.user.is_superuser(current_user): @@ -141,16 +141,16 @@ def update_user( db: Session = Depends(get_db), user_id: int, user_in: UserUpdate, - current_user: UserInDB = Depends(get_current_active_superuser), + current_user: DBUser = Depends(get_current_active_superuser), ): """ Update a user. """ - user = crud.user.get(db, user_id=user_id) + user = crud.user.get(db, id=user_id) if not user: raise HTTPException( status_code=404, detail="The user with this username does not exist in the system", ) - user = crud.user.update(db, user=user, user_in=user_in) + user = crud.user.update(db, db_obj=user, obj_in=user_in) return user 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 cc43abe52a..e562e97f5c 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 @@ -1,10 +1,11 @@ from fastapi import APIRouter, Depends -from pydantic.types import EmailStr +from pydantic.networks import EmailStr from app.api.utils.security import get_current_active_superuser from app.core.celery_app import celery_app -from app.models.msg import Msg -from app.models.user import UserInDB +from app.schemas.msg import Msg +from app.schemas.user import User +from app.models.user import User as DBUser from app.utils import send_test_email router = APIRouter() @@ -12,7 +13,7 @@ @router.post("/test-celery/", response_model=Msg, status_code=201) def test_celery( - msg: Msg, current_user: UserInDB = Depends(get_current_active_superuser) + msg: Msg, current_user: DBUser = Depends(get_current_active_superuser) ): """ Test Celery worker. @@ -23,7 +24,7 @@ def test_celery( @router.post("/test-email/", response_model=Msg, status_code=201) def test_email( - email_to: EmailStr, current_user: UserInDB = Depends(get_current_active_superuser) + email_to: EmailStr, current_user: DBUser = Depends(get_current_active_superuser) ): """ Test emails. diff --git a/{{cookiecutter.project_slug}}/backend/app/app/api/utils/security.py b/{{cookiecutter.project_slug}}/backend/app/app/api/utils/security.py index 0e761f7e43..8f8b0e3fbb 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/api/utils/security.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/api/utils/security.py @@ -9,8 +9,8 @@ from app.api.utils.db import get_db from app.core import config from app.core.jwt import ALGORITHM -from app.db_models.user import User -from app.models.token import TokenPayload +from app.models.user import User +from app.schemas.token import TokenPayload reusable_oauth2 = OAuth2PasswordBearer(tokenUrl="/api/v1/login/access-token") @@ -25,7 +25,7 @@ def get_current_user( raise HTTPException( status_code=HTTP_403_FORBIDDEN, detail="Could not validate credentials" ) - user = crud.user.get(db, user_id=token_data.user_id) + user = crud.user.get(db, id=token_data.user_id) if not user: raise HTTPException(status_code=404, detail="User not found") return user diff --git a/{{cookiecutter.project_slug}}/backend/app/app/core/config.py b/{{cookiecutter.project_slug}}/backend/app/app/core/config.py index 07e42b084e..e413c5eaa0 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/core/config.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/core/config.py @@ -51,3 +51,5 @@ def getenv_boolean(var_name, default_value=False): FIRST_SUPERUSER_PASSWORD = os.getenv("FIRST_SUPERUSER_PASSWORD") USERS_OPEN_REGISTRATION = getenv_boolean("USERS_OPEN_REGISTRATION") + +EMAIL_TEST_USER = "test@example.com" diff --git a/{{cookiecutter.project_slug}}/backend/app/app/crud/__init__.py b/{{cookiecutter.project_slug}}/backend/app/app/crud/__init__.py index 9330490546..20491baed7 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/crud/__init__.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/crud/__init__.py @@ -1 +1,10 @@ -from . import item, user +from .crud_user import user +from .crud_item import item + +# For a new basic set of CRUD operations you could just do + +# from .base import CRUDBase +# from app.models.item import Item +# from app.schemas.item import ItemCreate, ItemUpdate + +# item = CRUDBase[Item, ItemCreate, ItemUpdate](Item) diff --git a/{{cookiecutter.project_slug}}/backend/app/app/crud/base.py b/{{cookiecutter.project_slug}}/backend/app/app/crud/base.py new file mode 100644 index 0000000000..720a9a9d38 --- /dev/null +++ b/{{cookiecutter.project_slug}}/backend/app/app/crud/base.py @@ -0,0 +1,57 @@ +from typing import List, Optional, Generic, TypeVar, Type + +from fastapi.encoders import jsonable_encoder +from pydantic import BaseModel +from sqlalchemy.orm import Session + +from app.db.base_class import Base + +ModelType = TypeVar("ModelType", bound=Base) +CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel) +UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel) + + +class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): + def __init__(self, model: Type[ModelType]): + """ + CRUD object with default methods to Create, Read, Update, Delete (CRUD). + + **Parameters** + + * `model`: A SQLAlchemy model class + * `schema`: A Pydantic model (schema) class + """ + self.model = model + + def get(self, db_session: Session, id: int) -> Optional[ModelType]: + return db_session.query(self.model).filter(self.model.id == id).first() + + def get_multi(self, db_session: Session, *, skip=0, limit=100) -> List[ModelType]: + return db_session.query(self.model).offset(skip).limit(limit).all() + + def create(self, db_session: Session, *, obj_in: CreateSchemaType) -> ModelType: + obj_in_data = jsonable_encoder(obj_in) + db_obj = self.model(**obj_in_data) + db_session.add(db_obj) + db_session.commit() + db_session.refresh(db_obj) + return db_obj + + def update( + self, db_session: Session, *, db_obj: ModelType, obj_in: UpdateSchemaType + ) -> ModelType: + obj_data = jsonable_encoder(db_obj) + update_data = obj_in.dict(skip_defaults=True) + for field in obj_data: + if field in update_data: + setattr(db_obj, field, update_data[field]) + db_session.add(db_obj) + db_session.commit() + db_session.refresh(db_obj) + return db_obj + + def remove(self, db_session: Session, *, id: int) -> ModelType: + obj = db_session.query(self.model).get(id) + db_session.delete(obj) + db_session.commit() + return obj diff --git a/{{cookiecutter.project_slug}}/backend/app/app/crud/crud_item.py b/{{cookiecutter.project_slug}}/backend/app/app/crud/crud_item.py new file mode 100644 index 0000000000..16db7bf999 --- /dev/null +++ b/{{cookiecutter.project_slug}}/backend/app/app/crud/crud_item.py @@ -0,0 +1,34 @@ +from typing import List + +from fastapi.encoders import jsonable_encoder +from sqlalchemy.orm import Session + +from app.models.item import Item +from app.schemas.item import ItemCreate, ItemUpdate +from app.crud.base import CRUDBase + + +class CRUDItem(CRUDBase[Item, ItemCreate, ItemUpdate]): + def create_with_owner( + self, db_session: Session, *, obj_in: ItemCreate, owner_id: int + ) -> Item: + obj_in_data = jsonable_encoder(obj_in) + db_obj = self.model(**obj_in_data, owner_id=owner_id) + db_session.add(db_obj) + db_session.commit() + db_session.refresh(db_obj) + return db_obj + + def get_multi_by_owner( + self, db_session: Session, *, owner_id: int, skip=0, limit=100 + ) -> List[Item]: + return ( + db_session.query(self.model) + .filter(Item.owner_id == owner_id) + .offset(skip) + .limit(limit) + .all() + ) + + +item = CRUDItem(Item) diff --git a/{{cookiecutter.project_slug}}/backend/app/app/crud/crud_user.py b/{{cookiecutter.project_slug}}/backend/app/app/crud/crud_user.py new file mode 100644 index 0000000000..e5d9d55afc --- /dev/null +++ b/{{cookiecutter.project_slug}}/backend/app/app/crud/crud_user.py @@ -0,0 +1,44 @@ +from typing import Optional + +from sqlalchemy.orm import Session + +from app.models.user import User +from app.schemas.user import UserCreate, UserUpdate +from app.core.security import verify_password, get_password_hash +from app.crud.base import CRUDBase + + +class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]): + def get_by_email(self, db_session: Session, *, email: str) -> Optional[User]: + return db_session.query(User).filter(User.email == email).first() + + def create(self, db_session: Session, *, obj_in: UserCreate) -> User: + db_obj = User( + email=obj_in.email, + hashed_password=get_password_hash(obj_in.password), + full_name=obj_in.full_name, + is_superuser=obj_in.is_superuser, + ) + db_session.add(db_obj) + db_session.commit() + db_session.refresh(db_obj) + return db_obj + + def authenticate( + self, db_session: Session, *, email: str, password: str + ) -> Optional[User]: + user = self.get_by_email(db_session, email=email) + if not user: + return None + if not verify_password(password, user.hashed_password): + return None + return user + + def is_active(self, user: User) -> bool: + return user.is_active + + def is_superuser(self, user: User) -> bool: + return user.is_superuser + + +user = CRUDUser(User) diff --git a/{{cookiecutter.project_slug}}/backend/app/app/crud/item.py b/{{cookiecutter.project_slug}}/backend/app/app/crud/item.py deleted file mode 100644 index 9cec459f54..0000000000 --- a/{{cookiecutter.project_slug}}/backend/app/app/crud/item.py +++ /dev/null @@ -1,55 +0,0 @@ -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_in_data = jsonable_encoder(item_in) - item = Item(**item_in_data, 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 deleted file mode 100644 index 66f80753c0..0000000000 --- a/{{cookiecutter.project_slug}}/backend/app/app/crud/user.py +++ /dev/null @@ -1,65 +0,0 @@ -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 UserCreate, UserUpdate - - -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: Session, *, email: str) -> Optional[User]: - return db_session.query(User).filter(User.email == email).first() - - -def authenticate(db_session: Session, *, email: str, password: str) -> Optional[User]: - user = get_by_email(db_session, email=email) - if not user: - return None - if not verify_password(password, user.hashed_password): - return None - return user - - -def is_active(user) -> bool: - return user.is_active - - -def is_superuser(user) -> bool: - return user.is_superuser - - -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: Session, *, user_in: UserCreate) -> User: - user = User( - email=user_in.email, - hashed_password=get_password_hash(user_in.password), - full_name=user_in.full_name, - is_superuser=user_in.is_superuser, - ) - db_session.add(user) - db_session.commit() - db_session.refresh(user) - return 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 update_data: - setattr(user, field, update_data[field]) - if user_in.password: - passwordhash = get_password_hash(user_in.password) - user.hashed_password = passwordhash - db_session.add(user) - db_session.commit() - db_session.refresh(user) - return user diff --git a/{{cookiecutter.project_slug}}/backend/app/app/db/base.py b/{{cookiecutter.project_slug}}/backend/app/app/db/base.py index f7e78061eb..81c92fda15 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/db/base.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/db/base.py @@ -1,6 +1,5 @@ # Import all the models, so that Base has them before being # 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 -from app.db_models.subitem import SubItem # noqa +from app.models.user import User # noqa +from app.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 6374273132..243a5817df 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 UserCreate +from app.schemas.user import UserCreate # make sure all SQL Alchemy models are imported before initializing DB # otherwise, SQL Alchemy might fail to initialize properly relationships @@ -21,4 +21,4 @@ def init_db(db_session): password=config.FIRST_SUPERUSER_PASSWORD, is_superuser=True, ) - user = crud.user.create(db_session, user_in=user_in) + user = crud.user.create(db_session, obj_in=user_in) diff --git a/{{cookiecutter.project_slug}}/backend/app/app/db_models/item.py b/{{cookiecutter.project_slug}}/backend/app/app/db_models/item.py deleted file mode 100755 index 3855355108..0000000000 --- a/{{cookiecutter.project_slug}}/backend/app/app/db_models/item.py +++ /dev/null @@ -1,13 +0,0 @@ -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") - subitems = relationship("SubItem", back_populates="item") diff --git a/{{cookiecutter.project_slug}}/backend/app/app/db_models/user.py b/{{cookiecutter.project_slug}}/backend/app/app/db_models/user.py deleted file mode 100755 index 1052908a4b..0000000000 --- a/{{cookiecutter.project_slug}}/backend/app/app/db_models/user.py +++ /dev/null @@ -1,14 +0,0 @@ -from sqlalchemy import Boolean, Column, Integer, String -from sqlalchemy.orm import relationship - -from app.db.base_class import Base - - -class User(Base): - id = Column(Integer, primary_key=True, index=True) - full_name = Column(String, index=True) - email = Column(String, unique=True, index=True) - 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 old mode 100644 new mode 100755 index cc7511e920..685687a098 --- a/{{cookiecutter.project_slug}}/backend/app/app/models/item.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/models/item.py @@ -1,34 +1,12 @@ -from pydantic import BaseModel +from sqlalchemy import Column, ForeignKey, Integer, String +from sqlalchemy.orm import relationship +from app.db.base_class import Base -# 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 +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/models/user.py b/{{cookiecutter.project_slug}}/backend/app/app/models/user.py old mode 100644 new mode 100755 index 51f1b02579..1052908a4b --- a/{{cookiecutter.project_slug}}/backend/app/app/models/user.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/models/user.py @@ -1,36 +1,14 @@ -from typing import Optional +from sqlalchemy import Boolean, Column, Integer, String +from sqlalchemy.orm import relationship -from pydantic import BaseModel +from app.db.base_class import Base -# Shared properties -class UserBase(BaseModel): - email: Optional[str] = None - is_active: Optional[bool] = True - is_superuser: Optional[bool] = False - full_name: Optional[str] = None - - -class UserBaseInDB(UserBase): - id: int = None - - -# Properties to receive via API on creation -class UserCreate(UserBaseInDB): - email: str - password: str - - -# Properties to receive via API on update -class UserUpdate(UserBaseInDB): - password: Optional[str] = None - - -# Additional properties to return via API -class User(UserBaseInDB): - pass - - -# Additional properties stored in DB -class UserInDB(UserBaseInDB): - hashed_password: str +class User(Base): + id = Column(Integer, primary_key=True, index=True) + full_name = Column(String, index=True) + email = Column(String, unique=True, index=True) + 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/db_models/__init__.py b/{{cookiecutter.project_slug}}/backend/app/app/schemas/__init__.py old mode 100755 new mode 100644 similarity index 100% rename from {{cookiecutter.project_slug}}/backend/app/app/db_models/__init__.py rename to {{cookiecutter.project_slug}}/backend/app/app/schemas/__init__.py diff --git a/{{cookiecutter.project_slug}}/backend/app/app/schemas/item.py b/{{cookiecutter.project_slug}}/backend/app/app/schemas/item.py new file mode 100644 index 0000000000..dc4e382658 --- /dev/null +++ b/{{cookiecutter.project_slug}}/backend/app/app/schemas/item.py @@ -0,0 +1,38 @@ +from pydantic import BaseModel + +from .user import User + +# 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 + + class Config: + orm_mode = True + + +# 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/msg.py b/{{cookiecutter.project_slug}}/backend/app/app/schemas/msg.py similarity index 100% rename from {{cookiecutter.project_slug}}/backend/app/app/models/msg.py rename to {{cookiecutter.project_slug}}/backend/app/app/schemas/msg.py diff --git a/{{cookiecutter.project_slug}}/backend/app/app/models/token.py b/{{cookiecutter.project_slug}}/backend/app/app/schemas/token.py similarity index 100% rename from {{cookiecutter.project_slug}}/backend/app/app/models/token.py rename to {{cookiecutter.project_slug}}/backend/app/app/schemas/token.py diff --git a/{{cookiecutter.project_slug}}/backend/app/app/schemas/user.py b/{{cookiecutter.project_slug}}/backend/app/app/schemas/user.py new file mode 100644 index 0000000000..ed776c53ad --- /dev/null +++ b/{{cookiecutter.project_slug}}/backend/app/app/schemas/user.py @@ -0,0 +1,39 @@ +from typing import Optional + +from pydantic import BaseModel + + +# Shared properties +class UserBase(BaseModel): + email: Optional[str] = None + is_active: Optional[bool] = True + is_superuser: Optional[bool] = False + full_name: Optional[str] = None + + +class UserBaseInDB(UserBase): + id: int = None + + class Config: + orm_mode = True + + +# Properties to receive via API on creation +class UserCreate(UserBaseInDB): + email: str + password: str + + +# Properties to receive via API on update +class UserUpdate(UserBaseInDB): + password: Optional[str] = None + + +# Additional properties to return via API +class User(UserBaseInDB): + pass + + +# Additional properties stored in DB +class UserInDB(UserBaseInDB): + hashed_password: str 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 index 330ae360bd..b4804b92f7 100644 --- 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 @@ -3,6 +3,7 @@ from app.core import config from app.tests.utils.item import create_random_item from app.tests.utils.utils import get_server_api +from app.tests.utils.user import create_random_user def test_create_item(superuser_token_headers): @@ -13,6 +14,7 @@ def test_create_item(superuser_token_headers): headers=superuser_token_headers, json=data, ) + assert response.status_code == 200 content = response.json() assert content["title"] == data["title"] assert content["description"] == data["description"] @@ -27,6 +29,7 @@ def test_read_item(superuser_token_headers): f"{server_api}{config.API_V1_STR}/items/{item.id}", headers=superuser_token_headers, ) + assert response.status_code == 200 content = response.json() assert content["title"] == item.title assert content["description"] == item.description diff --git a/{{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_users.py b/{{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_users.py index 119ed219fd..4d8b3bc101 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_users.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/tests/api/api_v1/test_users.py @@ -3,8 +3,7 @@ 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.user import user_authentication_headers +from app.schemas.user import UserCreate from app.tests.utils.utils import get_server_api, random_lower_string @@ -20,6 +19,18 @@ def test_get_users_superuser_me(superuser_token_headers): assert current_user["email"] == config.FIRST_SUPERUSER +def test_get_users_normal_user_me(normal_user_token_headers): + server_api = get_server_api() + r = requests.get( + f"{server_api}{config.API_V1_STR}/users/me", headers=normal_user_token_headers + ) + current_user = r.json() + assert current_user + assert current_user["is_active"] is True + assert current_user["is_superuser"] is False + assert current_user["email"] == config.EMAIL_TEST_USER + + def test_create_user_new_email(superuser_token_headers): server_api = get_server_api() username = random_lower_string() @@ -41,7 +52,7 @@ def test_get_existing_user(superuser_token_headers): username = random_lower_string() password = random_lower_string() user_in = UserCreate(email=username, password=password) - user = crud.user.create(db_session, user_in=user_in) + user = crud.user.create(db_session, obj_in=user_in) user_id = user.id r = requests.get( f"{server_api}{config.API_V1_STR}/users/{user_id}", @@ -59,7 +70,7 @@ def test_create_user_existing_username(superuser_token_headers): # username = email password = random_lower_string() user_in = UserCreate(email=username, password=password) - user = crud.user.create(db_session, user_in=user_in) + crud.user.create(db_session, obj_in=user_in) data = {"email": username, "password": password} r = requests.post( f"{server_api}{config.API_V1_STR}/users/", @@ -71,16 +82,15 @@ def test_create_user_existing_username(superuser_token_headers): assert "_id" not in created_user -def test_create_user_by_normal_user(): +def test_create_user_by_normal_user(normal_user_token_headers): server_api = get_server_api() username = random_lower_string() password = random_lower_string() - 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} r = requests.post( - f"{server_api}{config.API_V1_STR}/users/", headers=user_token_headers, json=data + f"{server_api}{config.API_V1_STR}/users/", + headers=normal_user_token_headers, + json=data, ) assert r.status_code == 400 @@ -90,12 +100,12 @@ def test_retrieve_users(superuser_token_headers): username = random_lower_string() password = random_lower_string() user_in = UserCreate(email=username, password=password) - user = crud.user.create(db_session, user_in=user_in) + user = crud.user.create(db_session, obj_in=user_in) username2 = random_lower_string() password2 = random_lower_string() user_in2 = UserCreate(email=username2, password=password2) - user2 = crud.user.create(db_session, user_in=user_in2) + crud.user.create(db_session, obj_in=user_in2) r = requests.get( f"{server_api}{config.API_V1_STR}/users/", headers=superuser_token_headers diff --git a/{{cookiecutter.project_slug}}/backend/app/app/tests/conftest.py b/{{cookiecutter.project_slug}}/backend/app/app/tests/conftest.py index 0e3c044bc9..e383ed183f 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/tests/conftest.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/tests/conftest.py @@ -1,6 +1,8 @@ import pytest +from app.core import config from app.tests.utils.utils import get_server_api, get_superuser_token_headers +from app.tests.utils.user import authentication_token_from_email @pytest.fixture(scope="module") @@ -11,3 +13,8 @@ def server_api(): @pytest.fixture(scope="module") def superuser_token_headers(): return get_superuser_token_headers() + + +@pytest.fixture(scope="module") +def normal_user_token_headers(): + return authentication_token_from_email(config.EMAIL_TEST_USER) 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 index 33d8b7bbee..7b9d438ddd 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_item.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/tests/crud/test_item.py @@ -1,5 +1,5 @@ from app import crud -from app.models.item import ItemCreate, ItemUpdate +from app.schemas.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 @@ -10,7 +10,9 @@ def test_create_item(): 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) + item = crud.item.create_with_owner( + db_session=db_session, obj_in=item_in, owner_id=user.id + ) assert item.title == title assert item.description == description assert item.owner_id == user.id @@ -21,7 +23,9 @@ def test_get_item(): 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) + item = crud.item.create_with_owner( + db_session=db_session, obj_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 @@ -34,12 +38,12 @@ def test_update_item(): 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) + item = crud.item.create_with_owner( + db_session=db_session, obj_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 - ) + item2 = crud.item.update(db_session=db_session, db_obj=item, obj_in=item_update) assert item.id == item2.id assert item.title == item2.title assert item2.description == description2 @@ -51,7 +55,7 @@ def test_delete_item(): 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) + item = crud.item.create_with_owner(db_session=db_session, obj_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 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 e239cbac01..b4e73396c0 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,7 +2,7 @@ from app import crud from app.db.session import db_session -from app.models.user import UserCreate +from app.schemas.user import UserCreate from app.tests.utils.utils import random_lower_string @@ -10,7 +10,7 @@ def test_create_user(): email = random_lower_string() password = random_lower_string() user_in = UserCreate(email=email, password=password) - user = crud.user.create(db_session, user_in=user_in) + user = crud.user.create(db_session, obj_in=user_in) assert user.email == email assert hasattr(user, "hashed_password") @@ -19,7 +19,7 @@ def test_authenticate_user(): email = random_lower_string() password = random_lower_string() user_in = UserCreate(email=email, password=password) - user = crud.user.create(db_session, user_in=user_in) + user = crud.user.create(db_session, obj_in=user_in) authenticated_user = crud.user.authenticate( db_session, email=email, password=password ) @@ -38,7 +38,7 @@ def test_check_if_user_is_active(): email = random_lower_string() password = random_lower_string() user_in = UserCreate(email=email, password=password) - user = crud.user.create(db_session, user_in=user_in) + user = crud.user.create(db_session, obj_in=user_in) is_active = crud.user.is_active(user) assert is_active is True @@ -47,11 +47,8 @@ def test_check_if_user_is_active_inactive(): email = random_lower_string() password = random_lower_string() user_in = UserCreate(email=email, password=password, disabled=True) - print(user_in) - user = crud.user.create(db_session, user_in=user_in) - print(user) + user = crud.user.create(db_session, obj_in=user_in) is_active = crud.user.is_active(user) - print(is_active) assert is_active @@ -59,7 +56,7 @@ def test_check_if_user_is_superuser(): email = random_lower_string() password = random_lower_string() user_in = UserCreate(email=email, password=password, is_superuser=True) - user = crud.user.create(db_session, user_in=user_in) + user = crud.user.create(db_session, obj_in=user_in) is_superuser = crud.user.is_superuser(user) assert is_superuser is True @@ -68,7 +65,7 @@ def test_check_if_user_is_superuser_normal_user(): username = random_lower_string() password = random_lower_string() user_in = UserCreate(email=username, password=password) - user = crud.user.create(db_session, user_in=user_in) + user = crud.user.create(db_session, obj_in=user_in) is_superuser = crud.user.is_superuser(user) assert is_superuser is False @@ -77,7 +74,7 @@ def test_get_user(): password = random_lower_string() username = random_lower_string() 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) + user = crud.user.create(db_session, obj_in=user_in) + user_2 = crud.user.get(db_session, id=user.id) assert user.email == user_2.email assert jsonable_encoder(user) == jsonable_encoder(user_2) diff --git a/{{cookiecutter.project_slug}}/backend/app/app/tests/utils/item.py b/{{cookiecutter.project_slug}}/backend/app/app/tests/utils/item.py index 49398fa55d..95950f2e7d 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/tests/utils/item.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/tests/utils/item.py @@ -1,6 +1,6 @@ from app import crud from app.db.session import db_session -from app.models.item import ItemCreate +from app.schemas.item import ItemCreate from app.tests.utils.user import create_random_user from app.tests.utils.utils import random_lower_string @@ -12,6 +12,6 @@ def create_random_item(owner_id: int = None): 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 + return crud.item.create_with_owner( + db_session=db_session, obj_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 6a5b947e4a..d8856607d3 100644 --- a/{{cookiecutter.project_slug}}/backend/app/app/tests/utils/user.py +++ b/{{cookiecutter.project_slug}}/backend/app/app/tests/utils/user.py @@ -3,8 +3,8 @@ 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 +from app.schemas.user import UserCreate, UserUpdate +from app.tests.utils.utils import get_server_api, random_lower_string def user_authentication_headers(server_api, email, password): @@ -21,5 +21,23 @@ 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) + user = crud.user.create(db_session=db_session, obj_in=user_in) return user + + +def authentication_token_from_email(email): + """ + Return a valid token for the user with given email. + + If the user doesn't exist it is created first. + """ + password = random_lower_string() + user = crud.user.get_by_email(db_session, email=email) + if not user: + user_in = UserCreate(username=email, email=email, password=password) + user = crud.user.create(db_session=db_session, obj_in=user_in) + else: + user_in = UserUpdate(password=password) + user = crud.user.update(db_session, obj_in=user, db_obj=user_in) + + return user_authentication_headers(get_server_api(), email, password) diff --git a/{{cookiecutter.project_slug}}/backend/backend.dockerfile b/{{cookiecutter.project_slug}}/backend/backend.dockerfile index d30f17d58e..e9aa21b9f3 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.16.0" 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.47.0" "uvicorn>=0.11.1" 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}}/scripts/test-local.sh b/{{cookiecutter.project_slug}}/scripts/test-local.sh index baccd71242..221d15a12f 100644 --- a/{{cookiecutter.project_slug}}/scripts/test-local.sh +++ b/{{cookiecutter.project_slug}}/scripts/test-local.sh @@ -27,4 +27,4 @@ docker-compose \ docker-compose -f docker-stack.yml build docker-compose -f docker-stack.yml down -v --remove-orphans # Remove possibly previous broken stacks left hanging after an error docker-compose -f docker-stack.yml up -d -docker-compose -f docker-stack.yml exec -T backend-tests /tests-start.sh +docker-compose -f docker-stack.yml exec -T backend-tests /tests-start.sh "$@" diff --git a/{{cookiecutter.project_slug}}/scripts/test.sh b/{{cookiecutter.project_slug}}/scripts/test.sh index 84f9cce12e..c226f73891 100644 --- a/{{cookiecutter.project_slug}}/scripts/test.sh +++ b/{{cookiecutter.project_slug}}/scripts/test.sh @@ -15,5 +15,5 @@ config > docker-stack.yml docker-compose -f docker-stack.yml build docker-compose -f docker-stack.yml down -v --remove-orphans # Remove possibly previous broken stacks left hanging after an error docker-compose -f docker-stack.yml up -d -docker-compose -f docker-stack.yml exec -T backend-tests /tests-start.sh +docker-compose -f docker-stack.yml exec -T backend-tests /tests-start.sh "$@" docker-compose -f docker-stack.yml down -v --remove-orphans