Merge branch 'develop' into feature-NEWDWH2021-1068-DOC

This commit is contained in:
野間 2023-06-15 10:51:03 +09:00
commit 70399c2faa
56 changed files with 1091 additions and 563 deletions

View File

@ -24,3 +24,4 @@ TOKEN_ENDPOINT=oauth2/token
BIO_SEARCH_RESULT_MAX_COUNT=35000 BIO_SEARCH_RESULT_MAX_COUNT=35000
SEARCH_RESULT_MAX_COUNT=500 SEARCH_RESULT_MAX_COUNT=500
SESSION_EXPIRE_MINUTE=20 SESSION_EXPIRE_MINUTE=20
LOG_LEVEL=DEBUG

View File

@ -9,6 +9,7 @@ app = "uvicorn src.main:app --reload --no-server-header"
[packages] [packages]
fastapi = "*" fastapi = "*"
uvicorn = "*" uvicorn = "*"
"uvicorn[standard]" = "*"
gunicorn = "*" gunicorn = "*"
boto3 = "*" boto3 = "*"
jinja2 = "*" jinja2 = "*"

View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "d78a6bf1a96aa14c45431185961cae6d54ca1da8ea0319e1976bad4c2bebd673" "sha256": "3fc09dcad05f44b119f92f9955a7731128d9f9b1829240b7689102fb14f82edc"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -18,35 +18,35 @@
"default": { "default": {
"anyio": { "anyio": {
"hashes": [ "hashes": [
"sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421", "sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce",
"sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3" "sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0"
], ],
"markers": "python_full_version >= '3.6.2'", "markers": "python_version >= '3.7'",
"version": "==3.6.2" "version": "==3.7.0"
}, },
"boto3": { "boto3": {
"hashes": [ "hashes": [
"sha256:278d896e9090a976f41ec68da5c572bc4e5b7cb1e515f1898fee8cb2fadfb50d", "sha256:30f8ab1cf89d5864a80ba2d5eb5316dbd2a63c9469877e0cffb522630438aa85",
"sha256:3ce2225a61832d69831d669d912424ea3863268ca1cfa2a82203bb90952acefa" "sha256:77e8fa7c257f9ed8bfe0c3ffc2ccc47b1cfa27058f99415b6003699d1202e0c0"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.26.91" "version": "==1.26.145"
}, },
"botocore": { "botocore": {
"hashes": [ "hashes": [
"sha256:4ed6a488aee1b42367eace71f7d0993dda05b02eebd7dcdd78db5c9ce3d80da5", "sha256:264a3f19ed280d80711b7e278be09acff7ed379a96432fdf179b4e6e3a687e6a",
"sha256:a8a800a2a945da807758cace539fc5b5ec1d5082ce363799d3a3870c2c4ed6fc" "sha256:65e2a2b1cc70583225f87d6d63736215f93c6234721967bdab872270ba7a1f45"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==1.29.91" "version": "==1.29.145"
}, },
"certifi": { "certifi": {
"hashes": [ "hashes": [
"sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7",
"sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==2022.12.7" "version": "==2023.5.7"
}, },
"cffi": { "cffi": {
"hashes": [ "hashes": [
@ -195,7 +195,7 @@
"sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df", "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df",
"sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab" "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"
], ],
"markers": "python_version >= '3.7'", "markers": "python_full_version >= '3.7.0'",
"version": "==3.1.0" "version": "==3.1.0"
}, },
"click": { "click": {
@ -208,31 +208,27 @@
}, },
"cryptography": { "cryptography": {
"hashes": [ "hashes": [
"sha256:103e8f7155f3ce2ffa0049fe60169878d47a4364b277906386f8de21c9234aa1", "sha256:059e348f9a3c1950937e1b5d7ba1f8e968508ab181e75fc32b879452f08356db",
"sha256:23df8ca3f24699167daf3e23e51f7ba7334d504af63a94af468f468b975b7dd7", "sha256:1a5472d40c8f8e91ff7a3d8ac6dfa363d8e3138b961529c996f3e2df0c7a411a",
"sha256:2725672bb53bb92dc7b4150d233cd4b8c59615cd8288d495eaa86db00d4e5c06", "sha256:1a8e6c2de6fbbcc5e14fd27fb24414507cb3333198ea9ab1258d916f00bc3039",
"sha256:30b1d1bfd00f6fc80d11300a29f1d8ab2b8d9febb6ed4a38a76880ec564fae84", "sha256:1fee5aacc7367487b4e22484d3c7e547992ed726d14864ee33c0176ae43b0d7c",
"sha256:35d658536b0a4117c885728d1a7032bdc9a5974722ae298d6c533755a6ee3915", "sha256:5d092fdfedaec4cbbffbf98cddc915ba145313a6fdaab83c6e67f4e6c218e6f3",
"sha256:50cadb9b2f961757e712a9737ef33d89b8190c3ea34d0fb6675e00edbe35d074", "sha256:5f0ff6e18d13a3de56f609dd1fd11470918f770c6bd5d00d632076c727d35485",
"sha256:5f8c682e736513db7d04349b4f6693690170f95aac449c56f97415c6980edef5", "sha256:7bfc55a5eae8b86a287747053140ba221afc65eb06207bedf6e019b8934b477c",
"sha256:6236a9610c912b129610eb1a274bdc1350b5df834d124fa84729ebeaf7da42c3", "sha256:7fa01527046ca5facdf973eef2535a27fec4cb651e4daec4d043ef63f6ecd4ca",
"sha256:788b3921d763ee35dfdb04248d0e3de11e3ca8eb22e2e48fef880c42e1f3c8f9", "sha256:8dde71c4169ec5ccc1087bb7521d54251c016f126f922ab2dfe6649170a3b8c5",
"sha256:8bc0008ef798231fac03fe7d26e82d601d15bd16f3afaad1c6113771566570f3", "sha256:8f4ab7021127a9b4323537300a2acfb450124b2def3756f64dc3a3d2160ee4b5",
"sha256:8f35c17bd4faed2bc7797d2a66cbb4f986242ce2e30340ab832e5d99ae60e011", "sha256:948224d76c4b6457349d47c0c98657557f429b4e93057cf5a2f71d603e2fc3a3",
"sha256:b49a88ff802e1993b7f749b1eeb31134f03c8d5c956e3c125c75558955cda536", "sha256:9a6c7a3c87d595608a39980ebaa04d5a37f94024c9f24eb7d10262b92f739ddb",
"sha256:bc0521cce2c1d541634b19f3ac661d7a64f9555135e9d8af3980965be717fd4a", "sha256:b46e37db3cc267b4dea1f56da7346c9727e1209aa98487179ee8ebed09d21e43",
"sha256:bc5b871e977c8ee5a1bbc42fa8d19bcc08baf0c51cbf1586b0e87a2694dde42f", "sha256:b4ceb5324b998ce2003bc17d519080b4ec8d5b7b70794cbd2836101406a9be31",
"sha256:c43ac224aabcbf83a947eeb8b17eaf1547bce3767ee2d70093b461f31729a480", "sha256:cb33ccf15e89f7ed89b235cff9d49e2e62c6c981a6061c9c8bb47ed7951190bc",
"sha256:d15809e0dbdad486f4ad0979753518f47980020b7a34e9fc56e8be4f60702fac", "sha256:d198820aba55660b4d74f7b5fd1f17db3aa5eb3e6893b0a41b75e84e4f9e0e4b",
"sha256:d7d84a512a59f4412ca8549b01f94be4161c94efc598bf09d027d67826beddc0", "sha256:d34579085401d3f49762d2f7d6634d6b6c2ae1242202e860f4d26b046e3a1006",
"sha256:e029b844c21116564b8b61216befabca4b500e6816fa9f0ba49527653cae2108", "sha256:eb8163f5e549a22888c18b0d53d6bb62a20510060a22fd5a995ec8a05268df8a",
"sha256:e8a0772016feeb106efd28d4a328e77dc2edae84dfbac06061319fdb669ff828", "sha256:f73bff05db2a3e5974a6fd248af2566134d8981fd7ab012e5dd4ddb1d9a70699"
"sha256:e944fe07b6f229f4c1a06a7ef906a19652bdd9fd54c761b0ff87e83ae7a30354",
"sha256:eb40fe69cfc6f5cdab9a5ebd022131ba21453cf7b8a7fd3631f45bbf52bed612",
"sha256:fa507318e427169ade4e9eccef39e9011cdc19534f55ca2f36ec3f388c1f70f3",
"sha256:ffd394c7896ed7821a6d13b24657c6a34b6e2650bd84ae063cf11ccffa4f1a97"
], ],
"version": "==39.0.2" "version": "==41.0.1"
}, },
"et-xmlfile": { "et-xmlfile": {
"hashes": [ "hashes": [
@ -242,13 +238,21 @@
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==1.1.0" "version": "==1.1.0"
}, },
"exceptiongroup": {
"hashes": [
"sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e",
"sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"
],
"markers": "python_version < '3.11'",
"version": "==1.1.1"
},
"fastapi": { "fastapi": {
"hashes": [ "hashes": [
"sha256:451387550c2d25a972193f22e408a82e75a8e7867c834a03076704fe20df3256", "sha256:4d9d3e8c71c73f11874bcf5e33626258d143252e329a01002f767306c64fb982",
"sha256:4a75936dbf9eb74be5eb0d41a793adefe9f3fc6ba66dbdabd160120fd3c2d9cd" "sha256:d374dbc4ef2ad9b803899bd3360d34c534adc574546e25314ab72c0c4411749f"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.94.1" "version": "==0.95.2"
}, },
"greenlet": { "greenlet": {
"hashes": [ "hashes": [
@ -332,6 +336,52 @@
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==0.14.0" "version": "==0.14.0"
}, },
"httptools": {
"hashes": [
"sha256:0297822cea9f90a38df29f48e40b42ac3d48a28637368f3ec6d15eebefd182f9",
"sha256:1af91b3650ce518d226466f30bbba5b6376dbd3ddb1b2be8b0658c6799dd450b",
"sha256:1f90cd6fd97c9a1b7fe9215e60c3bd97336742a0857f00a4cb31547bc22560c2",
"sha256:24bb4bb8ac3882f90aa95403a1cb48465de877e2d5298ad6ddcfdebec060787d",
"sha256:295874861c173f9101960bba332429bb77ed4dcd8cdf5cee9922eb00e4f6bc09",
"sha256:3625a55886257755cb15194efbf209584754e31d336e09e2ffe0685a76cb4b60",
"sha256:3a47a34f6015dd52c9eb629c0f5a8a5193e47bf2a12d9a3194d231eaf1bc451a",
"sha256:3cb8acf8f951363b617a8420768a9f249099b92e703c052f9a51b66342eea89b",
"sha256:4b098e4bb1174096a93f48f6193e7d9aa7071506a5877da09a783509ca5fff42",
"sha256:4d9ebac23d2de960726ce45f49d70eb5466725c0087a078866043dad115f850f",
"sha256:50d4613025f15f4b11f1c54bbed4761c0020f7f921b95143ad6d58c151198142",
"sha256:5230a99e724a1bdbbf236a1b58d6e8504b912b0552721c7c6b8570925ee0ccde",
"sha256:54465401dbbec9a6a42cf737627fb0f014d50dc7365a6b6cd57753f151a86ff0",
"sha256:550059885dc9c19a072ca6d6735739d879be3b5959ec218ba3e013fd2255a11b",
"sha256:557be7fbf2bfa4a2ec65192c254e151684545ebab45eca5d50477d562c40f986",
"sha256:5b65be160adcd9de7a7e6413a4966665756e263f0d5ddeffde277ffeee0576a5",
"sha256:64eba6f168803a7469866a9c9b5263a7463fa8b7a25b35e547492aa7322036b6",
"sha256:72ad589ba5e4a87e1d404cc1cb1b5780bfcb16e2aec957b88ce15fe879cc08ca",
"sha256:7d0c1044bce274ec6711f0770fd2d5544fe392591d204c68328e60a46f88843b",
"sha256:7e5eefc58d20e4c2da82c78d91b2906f1a947ef42bd668db05f4ab4201a99f49",
"sha256:850fec36c48df5a790aa735417dca8ce7d4b48d59b3ebd6f83e88a8125cde324",
"sha256:85b392aba273566c3d5596a0a490978c085b79700814fb22bfd537d381dd230c",
"sha256:8c2a56b6aad7cc8f5551d8e04ff5a319d203f9d870398b94702300de50190f63",
"sha256:8f470c79061599a126d74385623ff4744c4e0f4a0997a353a44923c0b561ee51",
"sha256:8ffce9d81c825ac1deaa13bc9694c0562e2840a48ba21cfc9f3b4c922c16f372",
"sha256:9423a2de923820c7e82e18980b937893f4aa8251c43684fa1772e341f6e06887",
"sha256:9b571b281a19762adb3f48a7731f6842f920fa71108aff9be49888320ac3e24d",
"sha256:a04fe458a4597aa559b79c7f48fe3dceabef0f69f562daf5c5e926b153817281",
"sha256:aa47ffcf70ba6f7848349b8a6f9b481ee0f7637931d91a9860a1838bfc586901",
"sha256:bede7ee075e54b9a5bde695b4fc8f569f30185891796b2e4e09e2226801d09bd",
"sha256:c1d2357f791b12d86faced7b5736dea9ef4f5ecdc6c3f253e445ee82da579449",
"sha256:c6eeefd4435055a8ebb6c5cc36111b8591c192c56a95b45fe2af22d9881eee25",
"sha256:ca1b7becf7d9d3ccdbb2f038f665c0f4857e08e1d8481cbcc1a86a0afcfb62b2",
"sha256:e67d4f8734f8054d2c4858570cc4b233bf753f56e85217de4dfb2495904cf02e",
"sha256:e8a34e4c0ab7b1ca17b8763613783e2458e77938092c18ac919420ab8655c8c1",
"sha256:e90491a4d77d0cb82e0e7a9cb35d86284c677402e4ce7ba6b448ccc7325c5421",
"sha256:ef1616b3ba965cd68e6f759eeb5d34fbf596a79e84215eeceebf34ba3f61fdc7",
"sha256:f222e1e9d3f13b68ff8a835574eda02e67277d51631d69d7cf7f8e07df678c86",
"sha256:f5e3088f4ed33947e16fd865b8200f9cfae1144f41b64a8cf19b599508e096bc",
"sha256:f659d7a48401158c59933904040085c200b4be631cb5f23a7d561fbae593ec1f",
"sha256:fe9c766a0c35b7e3d6b6939393c8dfdd5da3ac5dec7f971ec9134f284c6c36d6"
],
"version": "==0.5.0"
},
"idna": { "idna": {
"hashes": [ "hashes": [
"sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4",
@ -414,37 +464,34 @@
}, },
"numpy": { "numpy": {
"hashes": [ "hashes": [
"sha256:003a9f530e880cb2cd177cba1af7220b9aa42def9c4afc2a2fc3ee6be7eb2b22", "sha256:04847257662eef90599a1beca30c757d8e562aa8c7d64e91ea465f299469075d",
"sha256:150947adbdfeceec4e5926d956a06865c1c690f2fd902efede4ca6fe2e657c3f", "sha256:06bae17a3629416eb5bae3a429655dc075561206b6d3c1ddfa38b51f273bae5c",
"sha256:2620e8592136e073bd12ee4536149380695fbe9ebeae845b81237f986479ffc9", "sha256:1365157813810cfda2be9518806bf32f6b5f56e5e501d8299e3b681d53e405e2",
"sha256:2eabd64ddb96a1239791da78fa5f4e1693ae2dadc82a76bc76a14cbb2b966e96", "sha256:165b0fb4d5b6349eef7b909be2d61a673bc6e75e0eec43776eea3222385a9d11",
"sha256:4173bde9fa2a005c2c6e2ea8ac1618e2ed2c1c6ec8a7657237854d42094123a0", "sha256:1bed69508b3b97dd3fb8c439352881c1bd232a0c8dd1e11d8df4e68046d434cf",
"sha256:4199e7cfc307a778f72d293372736223e39ec9ac096ff0a2e64853b866a8e18a", "sha256:20dd2352806eb229bc79c4fa308431eaf1721c66f7928950ee0381df98a2d269",
"sha256:4cecaed30dc14123020f77b03601559fff3e6cd0c048f8b5289f4eeabb0eb281", "sha256:224e8862a1cd357eede831b270b9e6c51d2cbc2bb5cc2e2b8d0c76d52cbd1edc",
"sha256:557d42778a6869c2162deb40ad82612645e21d79e11c1dc62c6e82a2220ffb04", "sha256:236c8ff573c02677b873e0934419c8e9873bd2b35aaba885170b7b43cb26d5da",
"sha256:63e45511ee4d9d976637d11e6c9864eae50e12dc9598f531c035265991910468", "sha256:416da35914d2fecc3afd31127b1eb1a283df33292cfcb453e1c8fb46d52611a1",
"sha256:6524630f71631be2dabe0c541e7675db82651eb998496bbe16bc4f77f0772253", "sha256:472bdc3ade289d3efa331738b1daa5a529eef0550650f5d5d2eadb936a2f83a5",
"sha256:76807b4063f0002c8532cfeac47a3068a69561e9c8715efdad3c642eb27c0756", "sha256:48e33b46b7db13de75dd0c1c919b8b297b5d7a4dc50b181066977ee17bed7cc3",
"sha256:7de8fdde0003f4294655aa5d5f0a89c26b9f22c0a58790c38fae1ed392d44a5a", "sha256:5e7ba92ad63ffded03400d5038af89f7788843794c77ad1a37522fa69762b06f",
"sha256:889b2cc88b837d86eda1b17008ebeb679d82875022200c6e8e4ce6cf549b7acb", "sha256:7261d100c9bf722057fd5b9cd5b48f2973b17792b41e689eeaf9b55843cd1afd",
"sha256:92011118955724465fb6853def593cf397b4a1367495e0b59a7e69d40c4eb71d", "sha256:763fca81a8d8beb6bf4b9a9bbf4045b0c134c15ea66c81d26e5b8683b1861293",
"sha256:97cf27e51fa078078c649a51d7ade3c92d9e709ba2bfb97493007103c741f1d0", "sha256:7cf92c2bfbaf7bd52df1a21e56e8d34cff711594498ecbd02a39df3aaada763b",
"sha256:9a23f8440561a633204a67fb44617ce2a299beecf3295f0d13c495518908e910", "sha256:8aad2f86d2036622af1e1eb9db94e26618f42a571e02583fa72d5b1983782bf8",
"sha256:a51725a815a6188c662fb66fb32077709a9ca38053f0274640293a14fdd22978", "sha256:9a18d2c173a44e48e72614748df5624875439af2d352a416b9f3840583ad9efb",
"sha256:a77d3e1163a7770164404607b7ba3967fb49b24782a6ef85d9b5f54126cc39e5", "sha256:a0dab69ef25ccabf6f066a4902e238767cbbe52bc5ff90aa99514f87812ba76a",
"sha256:adbdce121896fd3a17a77ab0b0b5eedf05a9834a18699db6829a64e1dfccca7f", "sha256:a4a9f1eaa63b5e35e23e5465ed59746b0a680eb5b5da06f2d432f828d32b26c1",
"sha256:c29e6bd0ec49a44d7690ecb623a8eac5ab8a923bce0bea6293953992edf3a76a", "sha256:b1a22ae597ee1d0e2336044854b33965fd92e731efe3c2ab965826e02cca2a8c",
"sha256:c72a6b2f4af1adfe193f7beb91ddf708ff867a3f977ef2ec53c0ffb8283ab9f5", "sha256:bd1de5d7ab75cdf56f2247aace7940dfd0a8fd048e07808358d8fca604f1d102",
"sha256:d0a2db9d20117bf523dde15858398e7c0858aadca7c0f088ac0d6edd360e9ad2", "sha256:dfe2e3845c3b630f6617f9e8a15c8a1cbaf452c9fa32c71ec0a77d09548cd662",
"sha256:e3ab5d32784e843fc0dd3ab6dcafc67ef806e6b6828dc6af2f689be0eb4d781d", "sha256:f59080829bbfe46660a201fc17315a4e8ec6e4499ee745bab3df61866f63e771",
"sha256:e428c4fbfa085f947b536706a2fc349245d7baa8334f0c5723c56a10595f9b95", "sha256:f64b730004e500f836f6405ad5cd36d309b6ac065366a0855860155f23eb2ad5",
"sha256:e8d2859428712785e8a8b7d2b3ef0a1d1565892367b32f915c4a4df44d0e64f5", "sha256:f8cbdb428d848f03a4f6f534284cf7fb168a6ec0e742357bf65ad268316906ea"
"sha256:eef70b4fc1e872ebddc38cddacc87c19a3709c0e3e5d20bf3954c147b1dd941d",
"sha256:f64bb98ac59b3ea3bf74b02f13836eb2e24e48e0ab0145bbda646295769bd780",
"sha256:f9006288bcf4895917d02583cf3411f98631275bc67cce355a7f39f8c14338fa"
], ],
"markers": "python_version < '3.10'", "markers": "python_version < '3.10'",
"version": "==1.24.2" "version": "==1.25.0rc1"
}, },
"openpyxl": { "openpyxl": {
"hashes": [ "hashes": [
@ -456,34 +503,34 @@
}, },
"pandas": { "pandas": {
"hashes": [ "hashes": [
"sha256:008aa9843e92753d1345353e643c51017d8a9e303041db3165b683fc16a4d380", "sha256:02755de164da6827764ceb3bbc5f64b35cb12394b1024fdf88704d0fa06e0e2f",
"sha256:1f060ae468cb24e1ab42c6344b097375b24a902d3cefb5524f93ef0cd0db5f4b", "sha256:0a1e0576611641acde15c2322228d138258f236d14b749ad9af498ab69089e2d",
"sha256:2379d66055592480aab24cda5b1543539302e0f85e9a33538e9e4fd309b3063e", "sha256:1eb09a242184092f424b2edd06eb2b99d06dc07eeddff9929e8667d4ed44e181",
"sha256:26a507e14dc9a5ef29239b85d0ef5f01a7e308b88781b451a415d9d15e2d1a61", "sha256:30a89d0fec4263ccbf96f68592fd668939481854d2ff9da709d32a047689393b",
"sha256:314bc00a0575151d3ec3124af23bf2ef7533b0e160fb138007a4ef1b3c6a0e63", "sha256:50e451932b3011b61d2961b4185382c92cc8c6ee4658dcd4f320687bb2d000ee",
"sha256:3935c394e1b10d5c311bd9378018a468283adfe8469dc8084e21d55ca06be979", "sha256:51a93d422fbb1bd04b67639ba4b5368dffc26923f3ea32a275d2cc450f1d1c86",
"sha256:47f116fcb3aa533ab6661ca391136a643e25d1387dae989ed3e5b9248b98e2e9", "sha256:598e9020d85a8cdbaa1815eb325a91cfff2bb2b23c1442549b8a3668e36f0f77",
"sha256:4e99adf0a3b4e040fad8823567b52eacfd48db50d11024244a60197430ec74b8", "sha256:66d00300f188fa5de73f92d5725ced162488f6dc6ad4cecfe4144ca29debe3b8",
"sha256:67a5251a821b5af1c5aefe5a610a7758fae04693434fb98b2ebad10349cd727a", "sha256:69167693cb8f9b3fc060956a5d0a0a8dbfed5f980d9fd2c306fb5b9c855c814c",
"sha256:7bb2d670c1f7de9bcef0986ae9f832fbd99acc43db1d5fe22f2f06bda8a67d43", "sha256:6d6d10c2142d11d40d6e6c0a190b1f89f525bcf85564707e31b0a39e3b398e08",
"sha256:7fc7c85fcf27726633751d064f4d115dbccb202b0b6ea2909b6d89ca071115e3", "sha256:713f2f70abcdade1ddd68fc91577cb090b3544b07ceba78a12f799355a13ee44",
"sha256:8010e4c988c2c2ed1f5763a6e579448a13a7c87b810400124bb872121c9ca3f9", "sha256:7376e13d28eb16752c398ca1d36ccfe52bf7e887067af9a0474de6331dd948d2",
"sha256:867fd5c3325c302e8feaaa7ec2d99c224be38551d8a9e1ae5d15be7e04424172", "sha256:77550c8909ebc23e56a89f91b40ad01b50c42cfbfab49b3393694a50549295ea",
"sha256:8cb4789c8b1f361d7b07a25002e871546b108519af9c176f8a5ca66316c09d90", "sha256:7b21cb72958fc49ad757685db1919021d99650d7aaba676576c9e88d3889d456",
"sha256:8ce8603f8cf07044458914b81bb7445b6cc31d381657e0fac21b3eee40f404d0", "sha256:9ebb9f1c22ddb828e7fd017ea265a59d80461d5a79154b49a4207bd17514d122",
"sha256:adc1e91f282426d37830837f108747f0628e7635b1e83b2401b4f7e2a0068a82", "sha256:a18e5c72b989ff0f7197707ceddc99828320d0ca22ab50dd1b9e37db45b010c0",
"sha256:b72ba4e9553645c0bfd688a4e89efe9694fb2936adb5c6295d31626233cb674a", "sha256:a6b5f14cd24a2ed06e14255ff40fe2ea0cfaef79a8dd68069b7ace74bd6acbba",
"sha256:c3c3be69e186d12a94004b0c76bb390e26b48e4b444f3adc86d2cf6506c71d99", "sha256:b42b120458636a981077cfcfa8568c031b3e8709701315e2bfa866324a83efa8",
"sha256:cf960fc1f2545114b9ed1a0f025d6de63c891df31640e454e333e3b38504d36b", "sha256:c4af689352c4fe3d75b2834933ee9d0ccdbf5d7a8a7264f0ce9524e877820c08",
"sha256:dc45eb7f23c92e0aa5278bb210fb30136e6e0b760636cf18874cdf2d6448df0f", "sha256:c7319b6e68de14e6209460f72a8d1ef13c09fb3d3ef6c37c1e65b35d50b5c145",
"sha256:e5ebb19a66d8c4a4563e6cb628a23ee6898dc50e5dfe8b73c692cd7ea81def0a", "sha256:cf3f0c361a4270185baa89ec7ab92ecaa355fe783791457077473f974f654df5",
"sha256:e817d97597be5c21b1a66cbecadd0d0242482b72f6f5b60129fce5cec329e274", "sha256:dd46bde7309088481b1cf9c58e3f0e204b9ff9e3244f441accd220dd3365ce7c",
"sha256:e829b927b156f85432390580d8799dfee59db0be3954235cf5f5df8a42eaaacd", "sha256:dd5476b6c3fe410ee95926873f377b856dbc4e81a9c605a0dc05aaccc6a7c6c6",
"sha256:ebc301fb34185275d9ad57838f533d5413a02b434174d1be89785141f785b226", "sha256:e69140bc2d29a8556f55445c15f5794490852af3de0f609a24003ef174528b79",
"sha256:f082e075aeac904db0e69d8b8acc1d610362e3d823ace3af029622b24b105900" "sha256:f908a77cbeef9bbd646bd4b81214cbef9ac3dda4181d5092a4aa9797d1bc7774"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.0.0rc0" "version": "==2.0.2"
}, },
"pycparser": { "pycparser": {
"hashes": [ "hashes": [
@ -494,72 +541,70 @@
}, },
"pydantic": { "pydantic": {
"hashes": [ "hashes": [
"sha256:012c99a9c0d18cfde7469aa1ebff922e24b0c706d03ead96940f5465f2c9cf62", "sha256:052d8654cb65174d6f9490cc9b9a200083a82cf5c3c5d3985db765757eb3b375",
"sha256:0abd9c60eee6201b853b6c4be104edfba4f8f6c5f3623f8e1dba90634d63eb35", "sha256:0c6fafa0965b539d7aab0a673a046466d23b86e4b0e8019d25fd53f4df62c277",
"sha256:12e837fd320dd30bd625be1b101e3b62edc096a49835392dcf418f1a5ac2b832", "sha256:1243d28e9b05003a89d72e7915fdb26ffd1d39bdd39b00b7dbe4afae4b557f9d",
"sha256:163e79386c3547c49366e959d01e37fc30252285a70619ffc1b10ede4758250a", "sha256:12f7b0bf8553e310e530e9f3a2f5734c68699f42218bf3568ef49cd9b0e44df4",
"sha256:189318051c3d57821f7233ecc94708767dd67687a614a4e8f92b4a020d4ffd06", "sha256:1410275520dfa70effadf4c21811d755e7ef9bb1f1d077a21958153a92c8d9ca",
"sha256:1c84583b9df62522829cbc46e2b22e0ec11445625b5acd70c5681ce09c9b11c4", "sha256:16f8c3e33af1e9bb16c7a91fc7d5fa9fe27298e9f299cff6cb744d89d573d62c",
"sha256:3091d2eaeda25391405e36c2fc2ed102b48bac4b384d42b2267310abae350ca6", "sha256:17aef11cc1b997f9d574b91909fed40761e13fac438d72b81f902226a69dac01",
"sha256:32937835e525d92c98a1512218db4eed9ddc8f4ee2a78382d77f54341972c0e7", "sha256:191ba419b605f897ede9892f6c56fb182f40a15d309ef0142212200a10af4c18",
"sha256:3a2be0a0f32c83265fd71a45027201e1278beaa82ea88ea5b345eea6afa9ac7f", "sha256:1952526ba40b220b912cdc43c1c32bcf4a58e3f192fa313ee665916b26befb68",
"sha256:3ac1cd4deed871dfe0c5f63721e29debf03e2deefa41b3ed5eb5f5df287c7b70", "sha256:1ced8375969673929809d7f36ad322934c35de4af3b5e5b09ec967c21f9f7887",
"sha256:3ce13a558b484c9ae48a6a7c184b1ba0e5588c5525482681db418268e5f86186", "sha256:2e4148e635994d57d834be1182a44bdb07dd867fa3c2d1b37002000646cc5459",
"sha256:415a3f719ce518e95a92effc7ee30118a25c3d032455d13e121e3840985f2efd", "sha256:34d327c81e68a1ecb52fe9c8d50c8a9b3e90d3c8ad991bfc8f953fb477d42fb4",
"sha256:43cdeca8d30de9a897440e3fb8866f827c4c31f6c73838e3a01a14b03b067b1d", "sha256:35db5301b82e8661fa9c505c800d0990bc14e9f36f98932bb1d248c0ac5cada5",
"sha256:476f6674303ae7965730a382a8e8d7fae18b8004b7b69a56c3d8fa93968aa21c", "sha256:3e59417ba8a17265e632af99cc5f35ec309de5980c440c255ab1ca3ae96a3e0e",
"sha256:4c19eb5163167489cb1e0161ae9220dadd4fc609a42649e7e84a8fa8fff7a80f", "sha256:42aa0c4b5c3025483240a25b09f3c09a189481ddda2ea3a831a9d25f444e03c1",
"sha256:4ca83739c1263a044ec8b79df4eefc34bbac87191f0a513d00dd47d46e307a65", "sha256:666bdf6066bf6dbc107b30d034615d2627e2121506c555f73f90b54a463d1f33",
"sha256:528dcf7ec49fb5a84bf6fe346c1cc3c55b0e7603c2123881996ca3ad79db5bfc", "sha256:66a703d1983c675a6e0fed8953b0971c44dba48a929a2000a493c3772eb61a5a",
"sha256:53de12b4608290992a943801d7756f18a37b7aee284b9ffa794ee8ea8153f8e2", "sha256:6a82d6cda82258efca32b40040228ecf43a548671cb174a1e81477195ed3ed56",
"sha256:587d92831d0115874d766b1f5fddcdde0c5b6c60f8c6111a394078ec227fca6d", "sha256:6f2e754d5566f050954727c77f094e01793bcb5725b663bf628fa6743a5a9108",
"sha256:60184e80aac3b56933c71c48d6181e630b0fbc61ae455a63322a66a23c14731a", "sha256:7456eb22ed9aaa24ff3e7b4757da20d9e5ce2a81018c1b3ebd81a0b88a18f3b2",
"sha256:6195ca908045054dd2d57eb9c39a5fe86409968b8040de8c2240186da0769da7", "sha256:7b1f6cb446470b7ddf86c2e57cd119a24959af2b01e552f60705910663af09a4",
"sha256:61f1f08adfaa9cc02e0cbc94f478140385cbd52d5b3c5a657c2fceb15de8d1fb", "sha256:7d5b8641c24886d764a74ec541d2fc2c7fb19f6da2a4001e6d580ba4a38f7878",
"sha256:72cb30894a34d3a7ab6d959b45a70abac8a2a93b6480fc5a7bfbd9c935bdc4fb", "sha256:84d80219c3f8d4cad44575e18404099c76851bc924ce5ab1c4c8bb5e2a2227d0",
"sha256:751f008cd2afe812a781fd6aa2fb66c620ca2e1a13b6a2152b1ad51553cb4b77", "sha256:88f195f582851e8db960b4a94c3e3ad25692c1c1539e2552f3df7a9e972ef60e",
"sha256:89f15277d720aa57e173954d237628a8d304896364b9de745dcb722f584812c7", "sha256:93e6bcfccbd831894a6a434b0aeb1947f9e70b7468f274154d03d71fabb1d7c6",
"sha256:8c32b6bba301490d9bb2bf5f631907803135e8085b6aa3e5fe5a770d46dd0160", "sha256:93e766b4a8226e0708ef243e843105bf124e21331694367f95f4e3b4a92bbb3f",
"sha256:acc6783751ac9c9bc4680379edd6d286468a1dc8d7d9906cd6f1186ed682b2b0", "sha256:ab523c31e22943713d80d8d342d23b6f6ac4b792a1e54064a8d0cf78fd64e800",
"sha256:b1eb6610330a1dfba9ce142ada792f26bbef1255b75f538196a39e9e90388bf4", "sha256:bb14388ec45a7a0dc429e87def6396f9e73c8c77818c927b6a60706603d5f2ea",
"sha256:b243b564cea2576725e77aeeda54e3e0229a168bc587d536cd69941e6797543d", "sha256:c0ab53b609c11dfc0c060d94335993cc2b95b2150e25583bec37a49b2d6c6c3f",
"sha256:b41822064585fea56d0116aa431fbd5137ce69dfe837b599e310034171996084", "sha256:c33b60054b2136aef8cf190cd4c52a3daa20b2263917c49adad20eaf381e823b",
"sha256:bbd5c531b22928e63d0cb1868dee76123456e1de2f1cb45879e9e7a3f3f1779b", "sha256:ceb6a23bf1ba4b837d0cfe378329ad3f351b5897c8d4914ce95b85fba96da5a1",
"sha256:cf95adb0d1671fc38d8c43dd921ad5814a735e7d9b4d9e437c088002863854fd", "sha256:d532bf00f381bd6bc62cabc7d1372096b75a33bc197a312b03f5838b4fb84edd",
"sha256:e277bd18339177daa62a294256869bbe84df1fb592be2716ec62627bb8d7c81d", "sha256:df7800cb1984d8f6e249351139667a8c50a379009271ee6236138a22a0c0f319",
"sha256:ea4e2a7cb409951988e79a469f609bba998a576e6d7b9791ae5d1e0619e1c0f2", "sha256:e82d4566fcd527eae8b244fa952d99f2ca3172b7e97add0b43e2d97ee77f81ab",
"sha256:f9289065611c48147c1dd1fd344e9d57ab45f1d99b0fb26c51f1cf72cd9bcd31", "sha256:f90c1e29f447557e9e26afb1c4dbf8768a10cc676e3781b6a577841ade126b85",
"sha256:fd9b9e98068fa1068edfc9eabde70a7132017bdd4f362f8b4fd0abed79c33083" "sha256:f9613fadad06b4f3bc5db2653ce2f22e0de84a7c6c293909b48f6ed37b83c61f"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==1.10.6" "version": "==1.10.8"
}, },
"pyjwt": { "pyjwt": {
"extras": [ "extras": [],
"crypto"
],
"hashes": [ "hashes": [
"sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd", "sha256:ba2b425b15ad5ef12f200dc67dd56af4e26de2331f965c5439994dad075876e1",
"sha256:d83c3d892a77bbb74d3e1a2cfa90afaadb60945205d1095d9221f04466f64c14" "sha256:bd6ca4a3c4285c1a2d4349e5a035fdf8fb94e04ccd0fcbe6ba289dae9cc3e074"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.6.0" "version": "==2.7.0"
}, },
"pymysql": { "pymysql": {
"hashes": [ "hashes": [
"sha256:41fc3a0c5013d5f039639442321185532e3e2c8924687abe6537de157d403641", "sha256:5072fb2637f8bfff0e7a15a9c02a0f4ba98f97800e12432e1d6d95936ec6d496",
"sha256:816927a350f38d56072aeca5dfb10221fe1dc653745853d30a216637f5d7ad36" "sha256:5cc02f2f60936c5d2d6122ffaff27783bd29ba7683ea45a8ab75c5083f00dc20"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.0.2" "version": "==1.1.0rc1"
}, },
"pynamodb": { "pynamodb": {
"hashes": [ "hashes": [
"sha256:3c4d10867d59e6d7a2b54ee4ae213f1021d6f50ff93145e3909784bfc2b7560e", "sha256:82f77bb0c21a12756e6781df735ca841f543337847d8522a4ab8db6df7bbfc9f",
"sha256:e09c39880560e10251778185b3d0c7a97ee8f42ab363a940c674e9330b61bf9d" "sha256:a44fb486fc3e66a25b58d921e07f016f62416e323b381b96c1b725105868dacf"
], ],
"index": "pypi", "index": "pypi",
"version": "==5.4.1" "version": "==5.5.0"
}, },
"python-dateutil": { "python-dateutil": {
"hashes": [ "hashes": [
@ -569,6 +614,13 @@
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.8.2" "version": "==2.8.2"
}, },
"python-dotenv": {
"hashes": [
"sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba",
"sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"
],
"version": "==1.0.0"
},
"python-multipart": { "python-multipart": {
"hashes": [ "hashes": [
"sha256:e9925a80bb668529f1b67c7fdb0a5dacdd7cbfc6fb0bff3ea443fe22bdd62132", "sha256:e9925a80bb668529f1b67c7fdb0a5dacdd7cbfc6fb0bff3ea443fe22bdd62132",
@ -579,34 +631,79 @@
}, },
"pytz": { "pytz": {
"hashes": [ "hashes": [
"sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0", "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588",
"sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a" "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"
], ],
"version": "==2022.7.1" "version": "==2023.3"
},
"pyyaml": {
"hashes": [
"sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf",
"sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293",
"sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b",
"sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57",
"sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b",
"sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4",
"sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07",
"sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba",
"sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9",
"sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287",
"sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513",
"sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0",
"sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782",
"sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0",
"sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92",
"sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f",
"sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2",
"sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc",
"sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1",
"sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c",
"sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86",
"sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4",
"sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c",
"sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34",
"sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b",
"sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d",
"sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c",
"sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb",
"sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7",
"sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737",
"sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3",
"sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d",
"sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358",
"sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53",
"sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78",
"sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803",
"sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a",
"sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f",
"sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
"sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
],
"version": "==6.0"
}, },
"requests": { "requests": {
"hashes": [ "hashes": [
"sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f",
"sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.28.2" "version": "==2.31.0"
}, },
"s3transfer": { "s3transfer": {
"hashes": [ "hashes": [
"sha256:06176b74f3a15f61f1b4f25a1fc29a4429040b7647133a463da8fa5bd28d5ecd", "sha256:3c0da2d074bf35d6870ef157158641178a4204a6e689e82546083e31e0311346",
"sha256:2ed07d3866f523cc561bf4a00fc5535827981b117dd7876f036b0c1aca42c947" "sha256:640bb492711f4c0c0905e1f62b6aaeb771881935ad27884852411f8e9cacbca9"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==0.6.0" "version": "==0.6.1"
}, },
"setuptools": { "setuptools": {
"hashes": [ "hashes": [
"sha256:2ee892cd5f29f3373097f5a814697e397cf3ce313616df0af11231e2ad118077", "sha256:5df61bf30bb10c6f756eb19e7c9f3b473051f48db77fddbe06ff2ca307df9a6f",
"sha256:b78aaa36f6b90a074c1fa651168723acbf45d14cb1196b6f02c0fd07f17623b2" "sha256:62642358adc77ffa87233bc4d2354c4b2682d214048f500964dbe760ccedf102"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==67.6.0" "version": "==67.8.0"
}, },
"six": { "six": {
"hashes": [ "hashes": [
@ -626,82 +723,227 @@
}, },
"sqlalchemy": { "sqlalchemy": {
"hashes": [ "hashes": [
"sha256:1df00f280fcf7628379c6838d47ac6abd2319848cb02984af313de9243994db8", "sha256:1a0754c2d9f0c7982bec0a31138e495ed1f6b8435d7e677c45be60ec18370acf",
"sha256:1fd154847f2c77128e16757e3fd2028151aa8208dd3b9a5978918ea786a15312", "sha256:1d6320a1d175447dce63618ec997a53836de48ed3b44bbe952f0b4b399b19941",
"sha256:20f36bff3b6c9fa94e40114fda4dc5048d40fd665390f5547b456a28e8059ee8", "sha256:1e885dacb167077df15af2f9ccdacbd7f5dd0d538a6d74b94074f2cefc7bb589",
"sha256:224c817e880359d344a462fc4dd94a233804f371aa290b024b6b976a2f5ade36", "sha256:201a99f922ac8c780b3929128fbd9df901418877c70e160e19adb05665e51c31",
"sha256:2ad44f45526411bebbf427cf858955a35f3a6bfd7db8f4314b12da4c0d1a4fd2", "sha256:21c89044fc48a25c2184eba332edeffbbf9367913bb065cd31538235d828f06f",
"sha256:2c4c64f321080c83a3f0eed11cc9b73fe2a574f6b8339c402861274165c24cf6", "sha256:256b2b9660e51ad7055a9835b12717416cf7288afcf465107413917b6bb2316f",
"sha256:3625a52fae744cff6f9beb6ed0775468b9eb7e6e8f6730676dfc49aa77d98b4e", "sha256:2e940a8659ef870ae10e0d9e2a6d5aaddf0ff6e91f7d0d7732afc9e8c4be9bbc",
"sha256:3be54b3825512b3de5698ae04bf4aad6ea60442ac0f6b91ee4b8fa4db5c2dccd", "sha256:3fb5d09f1d51480f711b69fe28ad42e4f8b08600a85ab2473baee669e1257800",
"sha256:4100c80070a66b042f1010b29b29a88d1d151c27a5e522c95ec07518b361a7a3", "sha256:435f6807fa6a0597d84741470f19db204a7d34625ea121abd63e8d95f673f0c4",
"sha256:47e96be3e8c9c0f2c71ec87599be4bb8409d61841b66964a36b2447bec510b3b", "sha256:4670ce853cb25f72115a1bbe366ae13cf3f28fc5c87222df14f8d3d55d51816e",
"sha256:483712fce53e2f7ec95ed7d106cd463f9fc122c28a7df4aaf2bc873d0d2a901f", "sha256:4a75fdb9a84072521bb2ebd31eefe1165d4dccea3039dda701a864f4b5daa17f",
"sha256:48824b989a0e4340cd099dd4539702ddb1a5ce449f8a7355124e40a4935a95fa", "sha256:4d61731a35eddb0f667774fe15e5a4831e444d066081d1e809e1b8a0e3f97cae",
"sha256:4d653962da384a1d99795dbd8aac4a7516071b2f2984ed2aa25545fae670b808", "sha256:51b19887c96d405599880da6a7cbdf8545a7e78ec5683e46a43bac8885e32d0f",
"sha256:5b067b2eaf3d97a49f3f6217981efa7b45d5726c2142f103712b020dd250fd98", "sha256:536c86ec81ca89291d533ff41a3a05f9e4e88e01906dcee0751fc7082f3e8d6c",
"sha256:5c35175b74cbcfe9af077bd13e87cfab13239e075c0e1e920095082f9377f0ed", "sha256:55ec62ddc0200b4fee94d11abbec7aa25948d5d21cb8df8807f4bdd3c51ba44b",
"sha256:61abff42e44e5daf17372cb8baa90e970dc647fc5f747e2caa9f9768acf17be8", "sha256:5cc48a7fda2b5c5b8860494d6c575db3a101a68416492105fed6591dc8a2728a",
"sha256:6987f658389ad8bb6257db91551e7fde3e904974eef6f323856260907ef311d7", "sha256:670ecf74ee2e70b917028a06446ad26ff9b1195e84b09c3139c215123d57dc30",
"sha256:709f1ecb5dcea59f36fa0f485e09e41ff313b2d62c83a6f99b36870b0d6e42fa", "sha256:6a3f8020e013e9b3b7941dcf20b0fc8f7429daaf7158760846731cbd8caa5e45",
"sha256:7635cd38e3ea8522729b14451157104fce2117c44e7ba6a14684ed153d71b567", "sha256:6b42913a0259267e9ee335da0c36498077799e59c5e332d506e72b4f32de781d",
"sha256:778db814cc21eff200c8bd42b4ffe976fa3378d10fb84d2c164d3c6a30bb38ee", "sha256:6f5784dfb2d45c19cde03c45c04a54bf47428610106197ed6e6fa79f33bc63d3",
"sha256:81d4fc8f5c966677a3a2f39eb8e496442269d8c7d285b28145f7745fcc089d63", "sha256:6f80a9c9a9af0e4bd5080cc0955ce70274c28e9b931ad7e0fb07021afcd32af6",
"sha256:82691d3539023c3cee5ae055c47bf873728cd6b33bfaa7b916bea5a99b92f700", "sha256:78303719c6f72af97814b0072ad18bee72e70adca8d95cf8fecd59c5e1ddb040",
"sha256:8ef7c56c74f4420b2c4a148d2531ba7f99b946cbf438a2bbcb2435fb4938a08d", "sha256:788d1772fb8dcd12091ca82809eef504ce0f2c423e45284bc351b872966ff554",
"sha256:9310666251385e4374c6f0bae6d69e62bc422021298ceb8669bf6ff56957ff37", "sha256:79bfe728219239bdc493950ea4a4d15b02138ecb304771f9024d0d6f5f4e3706",
"sha256:ac6274dd530b684cca8cbb774e348afac6846f15d1694a56954413be6e2e8dcd", "sha256:810199d1c5b43603a9e815ae9487aef3ab1ade7ed9c0c485e12519358929fbfe",
"sha256:b7be0e6a4061d28b66ca4b4eb24558dd8c6386d3bcd2d6d7ef247be27cf1281b", "sha256:88ab245ed2c96265441ed2818977be28c840cfa5204ba167425d6c26eb67b7e7",
"sha256:bea2c1341abe9bc6f30071b8ada1a3c44f24ec0fe1b9418e9c1112ed32057c9e", "sha256:933d30273861fe61f014ce2a7e3c364915f5efe9ed250ec1066ca6ea5942c0bd",
"sha256:bfcadfb8f0a9d26a76a5e2488cedd2e7cf8e70fe76d58aeb1c85eb83b33cbc5c", "sha256:994a75b197662e0608b6a76935d7c345f7fd874eac0b7093d561033db61b0e8c",
"sha256:bfce790746d059af6d0bc68b578ba20d50a63c71a3db16edce7aa8eccdd73796", "sha256:9b31ebde27575b3b0708673ec14f0c305c4564d995b545148ab7ac0f4d9b847a",
"sha256:bfde1d7cf8b9aa6bbd0d53946cd508d76db7689afd442e2289642cdc8908b7b7", "sha256:9d810b4aacd5ef4e293aa4ea01f19fca53999e9edcfc4a8ef1146238b30bdc28",
"sha256:c343f0b546495f5d7a239c70bf50a99a48d7321c165b82afafa8483b9ebebf6e", "sha256:ae1d8deb391ab39cc8f0d5844e588a115ae3717e607d91482023917f920f777f",
"sha256:c5d754665edea1ecdc79e3023659cb5594372e10776f3b3734d75c2c3ce95013", "sha256:bc5c2b0da46c26c5f73f700834f871d0723e1e882641932468d56833bab09775",
"sha256:c76caced0c8e9129810895f71954c72f478e30bea7d0bba7130bade396be5048", "sha256:cea7c4a3dfc2ca61f88a2b1ddd6b0bfbd116c9b1a361b3b66fd826034b833142",
"sha256:ca147d9cde38b481085408e1d4277ee834cb88bcc31bc01933bc6513340071bc", "sha256:d14282bf5b4de87f922db3c70858953fd081ef4f05dba6cca3dd705daffe1cc9",
"sha256:d7bd001a40997f0c9a9ac10a57663a9397959966a5a365bb24a4d1a17aa60175", "sha256:d6b17cb86908e7f88be14007d6afe7d2ab11966e373044137f96a6a4d83eb21c",
"sha256:db91fe985f2264ab49b3450ab7e2a59c34f7eaf3bf283d6b9e2f9ee02b29e533", "sha256:da7381a883aee20b7d2ffda17d909b38134b6a625920e65239a1c681881df800",
"sha256:e0e270a4f5b42c67362d9c6af648cb86f6a00b20767553cfd734c914e1e2a5e0", "sha256:db269f67ed17b07e80aaa8fba1f650c0d84aa0bdd9d5352e4ac38d5bf47ac568",
"sha256:ed714b864349704a7a719ec7199eec3f9cd15c190ecf6e10c34b5a0c549c5c18", "sha256:df25052b92bd514357a9b370d74f240db890ea79aaa428fb893520e10ee5bc18",
"sha256:edc16c8e24605d0a7925afaf99dbcbdc3f98a2cdda4622f1ea34482cb3b91940", "sha256:e17fdcb8971e77c439113642ca8861f9465e21fc693bd3916654ceef3ac26883",
"sha256:f47709c98544384d390aed34046f0573df5725d22861c0cd0a5c151bc22eedff", "sha256:f6fd3c88ea4b170d13527e93be1945e69facd917661d3725a63470eb683fbffe",
"sha256:ff10ad2d74a9a79c2984a2c709943e5362a1c898d8f3414815ea57515ae80c84" "sha256:f7f994a53c0e6b44a2966fd6bfc53e37d34b7dca34e75b6be295de6db598255e"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.0.6" "version": "==2.0.15"
}, },
"starlette": { "starlette": {
"hashes": [ "hashes": [
"sha256:41da799057ea8620e4667a3e69a5b1923ebd32b1819c8fa75634bbe8d8bea9bd", "sha256:6a6b0d042acb8d469a01eba54e9cda6cbd24ac602c4cd016723117d6a7e73b75",
"sha256:e87fce5d7cbdde34b76f0ac69013fd9d190d581d80681493016666e6f96c6d5e" "sha256:918416370e846586541235ccd38a474c08b80443ed31c578a418e2209b3eef91"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==0.26.1" "version": "==0.27.0"
}, },
"typing-extensions": { "typing-extensions": {
"hashes": [ "hashes": [
"sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb", "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26",
"sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4" "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==4.5.0" "version": "==4.6.3"
},
"tzdata": {
"hashes": [
"sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a",
"sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"
],
"markers": "python_version >= '2'",
"version": "==2023.3"
}, },
"urllib3": { "urllib3": {
"hashes": [ "hashes": [
"sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305", "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f",
"sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42" "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==1.26.15" "version": "==1.26.16"
}, },
"uvicorn": { "uvicorn": {
"hashes": [ "hashes": [
"sha256:8635a388062222082f4b06225b867b74a7e4ef942124453d4d1d1a5cb3750932", "sha256:79277ae03db57ce7d9aa0567830bbb51d7a612f54d6e1e3e92da3ef24c2c8ed8",
"sha256:e69e955cb621ae7b75f5590a814a4fcbfb14cb8f44a36dfe3c5c75ab8aee3ad5" "sha256:e9434d3bbf05f310e762147f769c9f21235ee118ba2d2bf1155a7196448bd996"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.21.0" "version": "==0.22.0"
},
"uvloop": {
"hashes": [
"sha256:0949caf774b9fcefc7c5756bacbbbd3fc4c05a6b7eebc7c7ad6f825b23998d6d",
"sha256:0ddf6baf9cf11a1a22c71487f39f15b2cf78eb5bde7e5b45fbb99e8a9d91b9e1",
"sha256:1436c8673c1563422213ac6907789ecb2b070f5939b9cbff9ef7113f2b531595",
"sha256:23609ca361a7fc587031429fa25ad2ed7242941adec948f9d10c045bfecab06b",
"sha256:2a6149e1defac0faf505406259561bc14b034cdf1d4711a3ddcdfbaa8d825a05",
"sha256:2deae0b0fb00a6af41fe60a675cec079615b01d68beb4cc7b722424406b126a8",
"sha256:307958f9fc5c8bb01fad752d1345168c0abc5d62c1b72a4a8c6c06f042b45b20",
"sha256:30babd84706115626ea78ea5dbc7dd8d0d01a2e9f9b306d24ca4ed5796c66ded",
"sha256:3378eb62c63bf336ae2070599e49089005771cc651c8769aaad72d1bd9385a7c",
"sha256:3d97672dc709fa4447ab83276f344a165075fd9f366a97b712bdd3fee05efae8",
"sha256:3db8de10ed684995a7f34a001f15b374c230f7655ae840964d51496e2f8a8474",
"sha256:3ebeeec6a6641d0adb2ea71dcfb76017602ee2bfd8213e3fcc18d8f699c5104f",
"sha256:45cea33b208971e87a31c17622e4b440cac231766ec11e5d22c76fab3bf9df62",
"sha256:6708f30db9117f115eadc4f125c2a10c1a50d711461699a0cbfaa45b9a78e376",
"sha256:68532f4349fd3900b839f588972b3392ee56042e440dd5873dfbbcd2cc67617c",
"sha256:6aafa5a78b9e62493539456f8b646f85abc7093dd997f4976bb105537cf2635e",
"sha256:7d37dccc7ae63e61f7b96ee2e19c40f153ba6ce730d8ba4d3b4e9738c1dccc1b",
"sha256:864e1197139d651a76c81757db5eb199db8866e13acb0dfe96e6fc5d1cf45fc4",
"sha256:8887d675a64cfc59f4ecd34382e5b4f0ef4ae1da37ed665adba0c2badf0d6578",
"sha256:8efcadc5a0003d3a6e887ccc1fb44dec25594f117a94e3127954c05cf144d811",
"sha256:9b09e0f0ac29eee0451d71798878eae5a4e6a91aa275e114037b27f7db72702d",
"sha256:a4aee22ece20958888eedbad20e4dbb03c37533e010fb824161b4f05e641f738",
"sha256:a5abddb3558d3f0a78949c750644a67be31e47936042d4f6c888dd6f3c95f4aa",
"sha256:c092a2c1e736086d59ac8e41f9c98f26bbf9b9222a76f21af9dfe949b99b2eb9",
"sha256:c686a47d57ca910a2572fddfe9912819880b8765e2f01dc0dd12a9bf8573e539",
"sha256:cbbe908fda687e39afd6ea2a2f14c2c3e43f2ca88e3a11964b297822358d0e6c",
"sha256:ce9f61938d7155f79d3cb2ffa663147d4a76d16e08f65e2c66b77bd41b356718",
"sha256:dbbaf9da2ee98ee2531e0c780455f2841e4675ff580ecf93fe5c48fe733b5667",
"sha256:f1e507c9ee39c61bfddd79714e4f85900656db1aec4d40c6de55648e85c2799c",
"sha256:ff3d00b70ce95adce264462c930fbaecb29718ba6563db354608f37e49e09024"
],
"version": "==0.17.0"
},
"watchfiles": {
"hashes": [
"sha256:0089c6dc24d436b373c3c57657bf4f9a453b13767150d17284fc6162b2791911",
"sha256:09ea3397aecbc81c19ed7f025e051a7387feefdb789cf768ff994c1228182fda",
"sha256:176a9a7641ec2c97b24455135d58012a5be5c6217fc4d5fef0b2b9f75dbf5154",
"sha256:18b28f6ad871b82df9542ff958d0c86bb0d8310bb09eb8e87d97318a3b5273af",
"sha256:20b44221764955b1e703f012c74015306fb7e79a00c15370785f309b1ed9aa8d",
"sha256:3d7d267d27aceeeaa3de0dd161a0d64f0a282264d592e335fff7958cc0cbae7c",
"sha256:5471582658ea56fca122c0f0d0116a36807c63fefd6fdc92c71ca9a4491b6b48",
"sha256:5569fc7f967429d4bc87e355cdfdcee6aabe4b620801e2cf5805ea245c06097c",
"sha256:68dce92b29575dda0f8d30c11742a8e2b9b8ec768ae414b54f7453f27bdf9545",
"sha256:79c533ff593db861ae23436541f481ec896ee3da4e5db8962429b441bbaae16e",
"sha256:7f3920b1285a7d3ce898e303d84791b7bf40d57b7695ad549dc04e6a44c9f120",
"sha256:91633e64712df3051ca454ca7d1b976baf842d7a3640b87622b323c55f3345e7",
"sha256:945be0baa3e2440151eb3718fd8846751e8b51d8de7b884c90b17d271d34cae8",
"sha256:9afd0d69429172c796164fd7fe8e821ade9be983f51c659a38da3faaaaac44dc",
"sha256:9c75eff897786ee262c9f17a48886f4e98e6cfd335e011c591c305e5d083c056",
"sha256:b538014a87f94d92f98f34d3e6d2635478e6be6423a9ea53e4dd96210065e193",
"sha256:b6577b8c6c8701ba8642ea9335a129836347894b666dd1ec2226830e263909d3",
"sha256:c0376deac92377817e4fb8f347bf559b7d44ff556d9bc6f6208dd3f79f104aaf",
"sha256:cae3dde0b4b2078f31527acff6f486e23abed307ba4d3932466ba7cdd5ecec79",
"sha256:cb5d45c4143c1dd60f98a16187fd123eda7248f84ef22244818c18d531a249d1",
"sha256:d9b073073e048081e502b6c6b0b88714c026a1a4c890569238d04aca5f9ca74b",
"sha256:fac19dc9cbc34052394dbe81e149411a62e71999c0a19e1e09ce537867f95ae0"
],
"version": "==0.19.0"
},
"websockets": {
"hashes": [
"sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd",
"sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f",
"sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998",
"sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82",
"sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788",
"sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa",
"sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f",
"sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4",
"sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7",
"sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f",
"sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd",
"sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69",
"sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb",
"sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b",
"sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016",
"sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac",
"sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4",
"sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb",
"sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99",
"sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e",
"sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54",
"sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf",
"sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007",
"sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3",
"sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6",
"sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86",
"sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1",
"sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61",
"sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11",
"sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8",
"sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f",
"sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931",
"sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526",
"sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016",
"sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae",
"sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd",
"sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b",
"sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311",
"sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af",
"sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152",
"sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288",
"sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de",
"sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97",
"sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d",
"sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d",
"sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca",
"sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0",
"sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9",
"sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b",
"sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e",
"sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128",
"sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d",
"sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c",
"sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5",
"sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6",
"sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b",
"sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b",
"sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280",
"sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c",
"sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c",
"sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f",
"sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20",
"sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8",
"sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb",
"sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602",
"sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf",
"sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0",
"sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74",
"sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0",
"sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564"
],
"version": "==11.0.3"
}, },
"xlrd": { "xlrd": {
"hashes": [ "hashes": [

View File

@ -23,7 +23,7 @@
- Merck_NewDWH開発2021のWiki、[Python環境構築](https://nds-tyo.backlog.com/alias/wiki/1874930)を参照 - Merck_NewDWH開発2021のWiki、[Python環境構築](https://nds-tyo.backlog.com/alias/wiki/1874930)を参照
- 「Pipenvの導入」までを行っておくこと - 「Pipenvの導入」までを行っておくこと
- 構築完了後、プロジェクト配下で以下のコマンドを実行し、Pythonの仮想環境を作成する - 構築完了後、プロジェクト配下で以下のコマンドを実行し、Pythonの仮想環境を作成する
- `pipenv install --python <pyenvでインストールしたpythonバージョン>` - `pipenv install --python <pyenvでインストールしたpythonバージョン> --dev`
- この手順で出力される仮想環境のパスは、後述するVSCodeの設定手順で使用するため、控えておく - この手順で出力される仮想環境のパスは、後述するVSCodeの設定手順で使用するため、控えておく
- MySQLの環境構築 - MySQLの環境構築

View File

@ -7,9 +7,9 @@ from src.system_var import environment
class CognitoClient(AWSAPIClient): class CognitoClient(AWSAPIClient):
def __init__(self) -> None: def __init__(self) -> None:
self.__client = boto3.client('cognito-idp') self.__client = boto3.client('cognito-idp')
def login_by_user_password_flow(self, username: str, password: str, secret_hash: str): def login_by_user_password_flow(self, username: str, password: str, secret_hash: str):
auth_response = self.__client.admin_initiate_auth( auth_response = self.__client.admin_initiate_auth(
UserPoolId=environment.COGNITO_USER_POOL_ID, UserPoolId=environment.COGNITO_USER_POOL_ID,
ClientId=environment.COGNITO_CLIENT_ID, ClientId=environment.COGNITO_CLIENT_ID,
@ -21,5 +21,5 @@ class CognitoClient(AWSAPIClient):
}, },
) )
authentication_result = auth_response['AuthenticationResult'] authentication_result = auth_response['AuthenticationResult']
return authentication_result['IdToken'], authentication_result['RefreshToken'], return authentication_result['IdToken'], authentication_result['RefreshToken'],

View File

@ -7,16 +7,16 @@ from src.aws.aws_api_client import AWSAPIClient
class S3Client(AWSAPIClient): class S3Client(AWSAPIClient):
__s3_client = boto3.client('s3') __s3_client = boto3.client('s3')
def upload_file(self, local_file_path: str, bucket_name: str, file_key: str): def upload_file(self, local_file_path: str, bucket_name: str, file_key: str):
self.__s3_client.upload_file( self.__s3_client.upload_file(
local_file_path, local_file_path,
Bucket=bucket_name, Bucket=bucket_name,
Key=file_key Key=file_key
) )
def generate_presigned_url(self, bucket_name: str, file_key: str, download_filename: str=''): def generate_presigned_url(self, bucket_name: str, file_key: str, download_filename: str = ''):
# presigned_urlを生成 # presigned_urlを生成
presigned_url = self.__s3_client.generate_presigned_url( presigned_url = self.__s3_client.generate_presigned_url(
'get_object', 'get_object',
Params={ Params={
@ -28,5 +28,5 @@ class S3Client(AWSAPIClient):
# 有効期限20分 # 有効期限20分
ExpiresIn=1200 ExpiresIn=1200
) )
return presigned_url return presigned_url

View File

@ -1,10 +1,10 @@
from typing import Optional from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Request from fastapi import APIRouter, Depends, HTTPException, Request
from fastapi.exceptions import HTTPException
from starlette import status from starlette import status
from src.depends.services import get_service from src.depends.services import get_service
from src.logging.get_logger import get_logger
from src.model.internal.session import UserSession from src.model.internal.session import UserSession
from src.model.request.bio import BioModel from src.model.request.bio import BioModel
from src.model.view.bio_view_model import BioViewModel from src.model.view.bio_view_model import BioViewModel
@ -17,20 +17,25 @@ from src.templates import templates
router = APIRouter() router = APIRouter()
router.route_class = AuthenticatedRoute router.route_class = AuthenticatedRoute
logger = get_logger('生物由来参照')
######################### #########################
# Views # # Views #
######################### #########################
@router.get('/BioSearchList') @router.get('/BioSearchList')
def bio_view( def bio_view(
request: Request, request: Request,
batch_status_service:BatchStatusService=Depends(get_service(BatchStatusService)), batch_status_service: BatchStatusService = Depends(get_service(BatchStatusService)),
bio_service: BioViewService=Depends(get_service(BioViewService)) bio_service: BioViewService = Depends(get_service(BioViewService))
): ):
session: UserSession = request.session session: UserSession = request.session
# バッチ処理中の場合、機能を利用させない # バッチ処理中の場合、機能を利用させない
if batch_status_service.is_batch_processing(): if batch_status_service.is_batch_processing():
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=constants.LOGOUT_REASON_BATCH_PROCESSING) raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=constants.LOGOUT_REASON_BATCH_PROCESSING)
logger.debug(f'UserId: {session.user_id}')
# 検索項目の取得 # 検索項目の取得
bio = bio_service.prepare_bio_view(session) bio = bio_service.prepare_bio_view(session)
# セッション書き換え # セッション書き換え
@ -50,14 +55,14 @@ def bio_view(
) )
return templates_response return templates_response
@router.post('/BioSearchList') @router.post('/BioSearchList')
def search_bio( def search_bio(
request: Request, request: Request,
bio_form: Optional[BioModel] = Depends(BioModel.as_form), bio_form: Optional[BioModel] = Depends(BioModel.as_form),
bio_service: BioViewService=Depends(get_service(BioViewService)), bio_service: BioViewService = Depends(get_service(BioViewService)),
batch_status_service:BatchStatusService=Depends(get_service(BatchStatusService)) batch_status_service: BatchStatusService = Depends(get_service(BatchStatusService))
): ):
# error_log(date("Y/m/d H:i:s") . " [INFO] UserId:" . $UserId . "\r\n", 3, "$execLog");
session: UserSession = request.session session: UserSession = request.session
# バッチ処理中の場合、機能を利用させない # バッチ処理中の場合、機能を利用させない
if batch_status_service.is_batch_processing(): if batch_status_service.is_batch_processing():

View File

@ -2,96 +2,78 @@
from datetime import datetime from datetime import datetime
from typing import Union from typing import Union
import pandas as pd
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from fastapi.exceptions import HTTPException
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from starlette import status from starlette import status
from src.depends.auth import verify_session from src.depends.auth import verify_session
from src.depends.services import get_service from src.depends.services import get_service
from src.error.exceptions import DBException from src.error.exceptions import DBException
from src.logging.get_logger import get_logger
from src.model.internal.session import UserSession from src.model.internal.session import UserSession
from src.model.request.bio import BioModel from src.model.request.bio import BioModel
from src.model.request.bio_download import BioDownloadModel from src.model.request.bio_download import BioDownloadModel
from src.services.batch_status_service import BatchStatusService from src.services.batch_status_service import BatchStatusService
from src.services.bio_view_service import BioViewService from src.services.bio_view_service import BioViewService
from src.services.session_service import set_session from src.services.session_service import set_session
from src.system_var import constants from src.system_var import constants, environment
logger = get_logger('生物由来参照')
router = APIRouter() router = APIRouter()
######################### #########################
# APIs # # APIs #
######################### #########################
@router.post('/download') @router.post('/download')
async def download_bio_data( async def download_bio_data(
search_param: BioModel=Depends(BioModel.as_body), search_param: BioModel = Depends(BioModel.as_body),
download_param: BioDownloadModel=Depends(BioDownloadModel.as_body), download_param: BioDownloadModel = Depends(BioDownloadModel.as_body),
bio_service: BioViewService = Depends(get_service(BioViewService)), bio_service: BioViewService = Depends(get_service(BioViewService)),
batch_status_service: BatchStatusService = Depends(get_service(BatchStatusService)), batch_status_service: BatchStatusService = Depends(get_service(BatchStatusService)),
session: Union[UserSession, None]=Depends(verify_session) session: Union[UserSession, None] = Depends(verify_session)
): ):
# 通常のビューとはルーティングの扱いを変えるために、個別のルーターで登録する # 通常のビューとはルーティングの扱いを変えるために、個別のルーターで登録する
# error_log(date("Y/m/d H:i:s") . " [INFO] getBioData start" . "\r\n", 3, "$execLog"); logger.info('生物由来データダウンロード開始')
# 改修後のパラメータを打ち出すようにする logger.info(f'ユーザーID: {download_param.user_id}')
# いらない error_log(date("Y/m/d H:i:s") . " [INFO] param:szConditions=" . htmlspecialchars($_POST["szConditions"], ENT_QUOTES) . "\r\n", 3, "$execLog"); logger.info(f'拡張子: {download_param.ext}')
# いらない error_log(date("Y/m/d H:i:s") . " [INFO] param:pageNum=" . htmlspecialchars($_POST["pageNum"], ENT_QUOTES) . "\r\n", 3, "$execLog");
# いらない error_log(date("Y/m/d H:i:s") . " [INFO] szUser=" . htmlspecialchars($_POST["szUser"], ENT_QUOTES) . "\r\n", 3, "$execLog");
# いらない error_log(date("Y/m/d H:i:s") . " [INFO] szfilename=" . htmlspecialchars($_POST["szfilename"], ENT_QUOTES) . "\r\n", 3, "$execLog");
# いらない error_log(date("Y/m/d H:i:s") . " [INFO] extension=" . htmlspecialchars($_POST["extension"], ENT_QUOTES) . "\r\n", 3, "$execLog");
# いらない error_log(date("Y/m/d H:i:s") . " [INFO] sql=" . htmlspecialchars($_POST["sql"], ENT_QUOTES) . "\r\n", 3, "$execLog");
# いらない error_log(date("Y/m/d H:i:s") . " [INFO] arrayPrepare=" . $_POST["arrayPrepare"] . "\r\n", 3, "$execLog");
# ファイル名に使用するタイムスタンプを初期化しておく # ファイル名に使用するタイムスタンプを初期化しておく
now = datetime.now() current_timestamp = datetime.now()
# 出力ファイル名
download_file_name = f'Result_{download_param.user_id}_{current_timestamp:%Y%m%d%H%M%S%f}.{download_param.ext}'
if session is None: if session is None:
return {'status': 'session_expired'} return {'status': 'session_expired'}
# バッチ処理中の場合、機能を利用させない # バッチ処理中の場合、機能を利用させない
if batch_status_service.is_batch_processing(): if batch_status_service.is_batch_processing():
return {'status': 'batch_processing'} return {'status': 'batch_processing'}
try: # 生物由来データを検索
# 生物由来データを検索 # 検索に使用したクエリも取得
search_result_df = bio_service.search_download_bio_data(search_param) search_result_df, query = _search_bio_data(bio_service, search_param, download_param.user_id)
except DBException as e: # アクセスログを記録
# error_log(date("Y/m/d H:i:s") . " [ERROR] " . "\r\n", 3, "$execLog"); bio_service.write_access_log(query, search_param, download_param.user_id, current_timestamp, download_file_name)
print('DB Error', e.args)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail={'error': 'db_error', 'message': e.args}
)
if search_result_df.size < 1: if search_result_df.size < 1:
# 検索結果が0件の場合、download_urlを返さない # 検索結果が0件の場合、download_urlを返さない
print('Bio data not found') logger.info('検索結果が0件です')
return {'status': 'ok', 'download_url': None} return {'status': 'ok', 'download_url': None}
# ファイルに打ち出すカラムを抽出 # ファイルに打ち出すカラムを抽出
extract_df = search_result_df[constants.BIO_EXTRACT_COLUMNS] # TODO: SQLクエリを修正するため、この処理は不要になる
extract_df = _extract_output_df(search_result_df)
# 値を変換 # ファイルを書き出し(Excel or CSV)
# データ種別の正式名を設定 local_file_path = _write_bio_data_to_file(bio_service, download_param, extract_df, download_file_name)
extract_df.loc[:, 'slip_org_kbn'] = extract_df['slip_org_kbn'].apply(lambda key: constants.SLIP_ORG_KBN_FULL_NAME.get(key))
# データ区分の区分の日本語名を設定
extract_df.loc[:, 'data_kbn'] = extract_df['data_kbn'].apply(lambda key: constants.DATA_KBN_JP_NAME.get(key))
# ロット番号エラーフラグの日本語名を設定
extract_df.loc[:, 'lot_no_err_flg'] = extract_df['lot_no_err_flg'].apply(lambda key: constants.LOT_NO_ERR_FLG_JP_NAME.get(key))
# 訂正前伝票管理番号がセットされているときのみ修正日時、修正者、エラー詳細種別をセット
extract_df.loc[:, 'ins_dt'] = extract_df['bef_slip_mgt_no'].apply(lambda bef_slip_mgt_no:extract_df['ins_dt'] if bef_slip_mgt_no is not None else '')
extract_df.loc[:, 'ins_usr'] = extract_df['bef_slip_mgt_no'].apply(lambda bef_slip_mgt_no:extract_df['ins_usr'] if bef_slip_mgt_no is not None else '')
# 種別によって出力を変える
local_file_path = ''
if download_param.kind == 'xlsx':
# error_log(date("Y/m/d H:i:s") . " [INFO] 今回はExcelファイルに出力する" . "\r\n", 3, "$execLog");
local_file_path = bio_service.write_excel_file(extract_df, download_param.user_id, timestamp=now)
elif download_param.kind == 'csv':
# error_log(date("Y/m/d H:i:s") . " [INFO] 今回はCSVファイルに出力する" . "\r\n", 3, "$execLog");
local_file_path = bio_service.write_csv_file(extract_df, download_param.user_id, header=constants.BIO_CSV_HEADER, timestamp=now)
# ローカルファイルからS3にアップロードし、ダウンロード用URLを取得する # ローカルファイルからS3にアップロードし、ダウンロード用URLを取得する
try: try:
bio_service.upload_bio_data_file(local_file_path) bio_service.upload_bio_data_file(local_file_path)
download_file_url = bio_service.generate_download_file_url(local_file_path, download_param.user_id, download_param.kind) download_file_url = bio_service.generate_download_file_url(
local_file_path, download_param.user_id, download_param.ext)
except Exception as e: except Exception as e:
print('S3 access error', e.args) logger.exception(f'S3 アクセスエラー{e}')
raise HTTPException( raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail={'error': 'aws_error', 'message': e.args} detail={'error': 'aws_error', 'message': e.args}
@ -114,8 +96,62 @@ async def download_bio_data(
json_response.set_cookie( json_response.set_cookie(
key='session', key='session',
value=session.session_key, value=session.session_key,
max_age=20*60, max_age=environment.SESSION_EXPIRE_MINUTE * 60, # cookieの有効期限は秒数指定なので、60秒をかける
secure=True, secure=True,
httponly=True httponly=True
) )
return json_response return json_response
def _search_bio_data(bio_service: BioViewService, search_param: BioModel, user_id: str) -> pd.DataFrame:
try:
# 生物由来データを検索
search_result_df, query = bio_service.search_download_bio_data(search_param)
except DBException as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail={'error': 'db_error', 'message': e.args}
)
return search_result_df, query
def _extract_output_df(search_result_df: pd.DataFrame) -> pd.DataFrame:
extract_df = search_result_df[constants.BIO_EXTRACT_COLUMNS]
# 値を変換
# データ種別の正式名を設定
extract_df.loc[:, 'slip_org_kbn'] = extract_df['slip_org_kbn'].apply(
lambda key: constants.SLIP_ORG_KBN_FULL_NAME.get(key))
# データ区分の区分の日本語名を設定
extract_df.loc[:, 'data_kbn'] = extract_df['data_kbn'].apply(lambda key: constants.DATA_KBN_JP_NAME.get(key))
# ロット番号エラーフラグの日本語名を設定
extract_df.loc[:, 'lot_num_err_flg'] = extract_df['lot_num_err_flg'].apply(
lambda key: constants.LOT_NO_ERR_FLG_JP_NAME.get(key))
# 訂正前伝票管理番号がセットされているときのみ修正日時、修正者、エラー詳細種別をセット
extract_df.loc[:, 'ins_dt'] = extract_df['bef_slip_mgt_num'].apply(
lambda bef_slip_mgt_num: extract_df['ins_dt'] if bef_slip_mgt_num is not None else '')
extract_df.loc[:, 'ins_usr'] = extract_df['bef_slip_mgt_num'].apply(
lambda bef_slip_mgt_num: extract_df['ins_usr'] if bef_slip_mgt_num is not None else '')
return extract_df
def _write_bio_data_to_file(
bio_service: BioViewService,
download_param: BioDownloadModel,
df: pd.DataFrame,
download_file_name: str
) -> str:
# 種別によって出力を変える
local_file_path = ''
if download_param.ext == 'xlsx':
logger.info('今回はExcelファイルに出力する')
local_file_path = bio_service.write_excel_file(
df, download_param.user_id, download_file_name=download_file_name)
elif download_param.ext == 'csv':
logger.info('今回はCSVファイルに出力する')
local_file_path = bio_service.write_csv_file(
df, download_param.user_id, header=constants.BIO_CSV_HEADER, download_file_name=download_file_name)
return local_file_path

View File

@ -5,6 +5,8 @@ router = APIRouter()
######################### #########################
# Views # # Views #
######################### #########################
@router.get('/') @router.get('/')
def healthcheck(): def healthcheck():
return {'status': 'OK'} return {'status': 'OK'}

View File

@ -1,4 +1,3 @@
import os.path as path
import secrets import secrets
import urllib.parse as parse import urllib.parse as parse
from typing import Union from typing import Union
@ -10,6 +9,7 @@ from starlette import status
from src.depends.auth import code_security from src.depends.auth import code_security
from src.depends.services import get_service from src.depends.services import get_service
from src.error.exceptions import JWTTokenVerifyException, NotAuthorizeException from src.error.exceptions import JWTTokenVerifyException, NotAuthorizeException
from src.logging.get_logger import get_logger
from src.model.internal.session import UserSession from src.model.internal.session import UserSession
from src.model.request.login import LoginModel from src.model.request.login import LoginModel
from src.model.view.mainte_login_view_model import MainteLoginViewModel from src.model.view.mainte_login_view_model import MainteLoginViewModel
@ -22,9 +22,13 @@ from src.templates import templates
router = APIRouter() router = APIRouter()
router.route_class = AfterSetCookieSessionRoute router.route_class = AfterSetCookieSessionRoute
logger = get_logger('ログイン')
######################### #########################
# Views # # Views #
######################### #########################
@router.get('/userlogin') @router.get('/userlogin')
def login_user_redirect_view(): def login_user_redirect_view():
auth_query_string = parse.urlencode( auth_query_string = parse.urlencode(
@ -39,6 +43,7 @@ def login_user_redirect_view():
return RedirectResponse(url=authorize_endpoint_url, status_code=status.HTTP_303_SEE_OTHER) return RedirectResponse(url=authorize_endpoint_url, status_code=status.HTTP_303_SEE_OTHER)
@router.get('/maintlogin') @router.get('/maintlogin')
def login_maintenance_view(request: Request): def login_maintenance_view(request: Request):
mainte_login = MainteLoginViewModel() mainte_login = MainteLoginViewModel()
@ -53,30 +58,36 @@ def login_maintenance_view(request: Request):
######################### #########################
# APIs # # APIs #
######################### #########################
@router.post('/maintlogin') @router.post('/maintlogin')
def login( def login(
response: Response, response: Response,
request: LoginModel = Depends(LoginModel.as_form), request: LoginModel = Depends(LoginModel.as_form),
login_service: LoginService = Depends(get_service(LoginService)) login_service: LoginService = Depends(get_service(LoginService))
): ):
try: try:
jwt_token = login_service.login(request.username, request.password) jwt_token = login_service.login(request.username, request.password)
except NotAuthorizeException as e: except NotAuthorizeException as e:
print(e) logger.exception(e)
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=constants.LOGOUT_REASON_LOGIN_ERROR) raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=constants.LOGOUT_REASON_LOGIN_ERROR)
except JWTTokenVerifyException as e: except JWTTokenVerifyException as e:
logger.exception(e)
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=constants.LOGOUT_REASON_SESSION_EXPIRED) raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=constants.LOGOUT_REASON_SESSION_EXPIRED)
verified_token = jwt_token.verify_token() verified_token = jwt_token.verify_token()
# 普通の認証だと、`cognito:username`に入る。 # 普通の認証だと、`cognito:username`に入る。
user_id = verified_token.user_id user_id = verified_token.user_id
user_record = login_service.logged_in_user(user_id) user_record = login_service.logged_in_user(user_id)
# ユーザーが有効ではない場合、ログアウトにリダイレクトする # ユーザーが有効ではない場合、ログアウトにリダイレクトする
if not user_record.is_enable_user(): if not user_record.is_enable_user():
logger.info(f'無効なユーザー: {user_id}, 有効フラグ: {user_record.enabled_flg}')
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=constants.LOGOUT_REASON_LOGIN_ERROR) raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=constants.LOGOUT_REASON_LOGIN_ERROR)
# メンテユーザーではない場合、ログアウトにリダイレクトする # メンテユーザーではない場合、ログアウトにリダイレクトする
if user_record is None or not user_record.is_maintenance_user(): if user_record is None or not user_record.is_maintenance_user():
logger.info(f'メンテナンスユーザーではない: {user_id}, メンテナンスユーザーフラグ: {user_record.mntuser_flg}')
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=constants.LOGOUT_REASON_LOGIN_ERROR) raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=constants.LOGOUT_REASON_LOGIN_ERROR)
logger.info(f'メンテナンスユーザー認証成功: {user_id}')
# CSRFトークンを生成 # CSRFトークンを生成
csrf_token = secrets.token_urlsafe(32) csrf_token = secrets.token_urlsafe(32)
# DynamoDBにトークンIDを設定する # DynamoDBにトークンIDを設定する
@ -92,9 +103,9 @@ def login(
user_flg=user_record.mntuser_flg user_flg=user_record.mntuser_flg
) )
session_key = set_session(session_model) session_key = set_session(session_model)
response = RedirectResponse( response = RedirectResponse(
url='/menu', url='/menu/',
status_code=status.HTTP_303_SEE_OTHER, status_code=status.HTTP_303_SEE_OTHER,
headers={'session_key': session_key} headers={'session_key': session_key}
) )
@ -103,9 +114,9 @@ def login(
@router.get('/authorize') @router.get('/authorize')
def sso_authorize( def sso_authorize(
code:Union[str, None]=Depends(code_security), code: Union[str, None] = Depends(code_security),
login_service: LoginService=Depends(get_service(LoginService)) login_service: LoginService = Depends(get_service(LoginService))
) -> Response: ) -> Response:
if not code: if not code:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=constants.LOGOUT_REASON_NOT_LOGIN) raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=constants.LOGOUT_REASON_NOT_LOGIN)
@ -115,6 +126,7 @@ def sso_authorize(
# トークン検証 # トークン検証
verified_token = jwt_token.verify_token() verified_token = jwt_token.verify_token()
except JWTTokenVerifyException as e: except JWTTokenVerifyException as e:
logger.exception(e)
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=constants.LOGOUT_REASON_SESSION_EXPIRED) raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=constants.LOGOUT_REASON_SESSION_EXPIRED)
# トークンからユーザーIDを取得 # トークンからユーザーIDを取得
@ -122,11 +134,13 @@ def sso_authorize(
user_record = login_service.logged_in_user(user_id) user_record = login_service.logged_in_user(user_id)
# ユーザーが有効ではない場合、ログアウトにリダイレクトする # ユーザーが有効ではない場合、ログアウトにリダイレクトする
if not user_record.is_enable_user(): if not user_record.is_enable_user():
logger.info(f'無効なユーザー: {user_id}, 有効フラグ: {user_record.enabled_flg}')
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=constants.LOGOUT_REASON_LOGIN_ERROR) raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=constants.LOGOUT_REASON_LOGIN_ERROR)
# Merckユーザーではない場合、ログアウトにリダイレクトする # Merckユーザーではない場合、ログアウトにリダイレクトする
if user_record is None or not user_record.is_groupware_user(): if user_record is None or not user_record.is_groupware_user():
logger.info(f'メンテナンスユーザーではない: {user_id}, メンテナンスユーザーフラグ: {user_record.mntuser_flg}')
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=constants.LOGOUT_REASON_LOGIN_ERROR) raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=constants.LOGOUT_REASON_LOGIN_ERROR)
logger.info(f'顧客ユーザー認証成功: {user_id}')
# CSRFトークンを生成 # CSRFトークンを生成
csrf_token = secrets.token_urlsafe(32) csrf_token = secrets.token_urlsafe(32)
# DynamoDBにトークンIDを設定する # DynamoDBにトークンIDを設定する
@ -143,7 +157,7 @@ def sso_authorize(
) )
session_key = set_session(session_model) session_key = set_session(session_model)
response = RedirectResponse( response = RedirectResponse(
url='/menu', url='/menu/',
status_code=status.HTTP_303_SEE_OTHER, status_code=status.HTTP_303_SEE_OTHER,
headers={'session_key': session_key} headers={'session_key': session_key}
) )

View File

@ -14,12 +14,14 @@ router = APIRouter()
######################### #########################
# Views # # Views #
######################### #########################
@router.get('/', response_class=HTMLResponse) @router.get('/', response_class=HTMLResponse)
def logout_view( def logout_view(
request: Request, request: Request,
reason: Optional[str] = None, reason: Optional[str] = None,
session: Union[UserSession, None]=Depends(verify_session) session: Union[UserSession, None] = Depends(verify_session)
): ):
redirect_to = '/login/userlogin' redirect_to = '/login/userlogin'
link_text = 'MeDaCA機能メニューへ' link_text = 'MeDaCA機能メニューへ'
if session is not None and session.user_flg == '1': if session is not None and session.user_flg == '1':

View File

@ -2,6 +2,7 @@ from fastapi import APIRouter, Depends, Request
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from src.depends.services import get_service from src.depends.services import get_service
from src.logging.get_logger import get_logger
from src.model.internal.session import UserSession from src.model.internal.session import UserSession
from src.model.view.menu_view_model import MenuViewModel from src.model.view.menu_view_model import MenuViewModel
from src.model.view.user_view_model import UserViewModel from src.model.view.user_view_model import UserViewModel
@ -10,18 +11,23 @@ from src.services.batch_status_service import BatchStatusService
from src.services.session_service import set_session from src.services.session_service import set_session
from src.templates import templates from src.templates import templates
logger = get_logger('MeDaCA機能メニュー')
router = APIRouter() router = APIRouter()
router.route_class = AuthenticatedRoute router.route_class = AuthenticatedRoute
######################### #########################
# Views # # Views #
######################### #########################
@router.get('/', response_class=HTMLResponse) @router.get('/', response_class=HTMLResponse)
def menu_view( def menu_view(
request: Request, request: Request,
batch_status_service:BatchStatusService=Depends(get_service(BatchStatusService)) batch_status_service: BatchStatusService = Depends(get_service(BatchStatusService))
): ):
session: UserSession = request.session session: UserSession = request.session
logger.info(f'UserID: {session.user_id}')
# 日付マスターからバッチ情報を取得する # 日付マスターからバッチ情報を取得する
hdke_tbl_record = batch_status_service.hdke_table_record hdke_tbl_record = batch_status_service.hdke_table_record

View File

@ -0,0 +1,11 @@
from fastapi import APIRouter
from fastapi.responses import RedirectResponse
from starlette import status
router = APIRouter()
@router.get('/')
def redirect_to_user_login():
# ルートパスへのアクセスは、顧客ユーザーログイン画面にリダイレクトさせる
return RedirectResponse(url='/login/userlogin', status_code=status.HTTP_303_SEE_OTHER)

View File

@ -0,0 +1,44 @@
from typing import Annotated
from fastapi import APIRouter, File, Form, Request, UploadFile
from src.templates import templates
router = APIRouter()
@router.get('/')
def get_view(request: Request):
return templates.TemplateResponse(
'sample_send_file.html',
{
'request': request
}
)
@router.post('/')
# file.readがCoroutineが返ってくるため、必ずasync関数にする
async def post_view(
# formからファイルを受け取る。formタグにenctype="multipart/form-data"を指定すること)
file: Annotated[UploadFile, File()],
message: str = Form()
):
# ファイルを読み込むCoroutineが取れるため、必ずawaitする
file_bytes = await file.read()
# 閉じとく
await file.close()
# 読み込んだファイルはbytesで返ってくるので、デコードする
file_content = file_bytes.decode()
print(file_content)
try:
return {
# ファイル名
"file_name": file.filename,
# ファイルのバイト数
"file_size": file.size,
# Content-Type
"file_content_type": file.content_type
}
except Exception:
return {'code': 'fail'}

View File

@ -18,4 +18,4 @@ def create_stop_app_handler(app: FastAPI) -> Callable:
def stop_app() -> None: def stop_app() -> None:
close_db(app) close_db(app)
return stop_app return stop_app

View File

@ -77,7 +77,10 @@ class Database:
Raises: Raises:
DBException: 接続失敗 DBException: 接続失敗
""" """
self.__connection = self.__engine.connect() try:
self.__connection = self.__engine.connect()
except Exception as e:
raise DBException(e)
def execute_select(self, select_query: str, parameters=None) -> list[dict]: def execute_select(self, select_query: str, parameters=None) -> list[dict]:
"""SELECTクエリを実行します。 """SELECTクエリを実行します。

View File

@ -5,11 +5,13 @@ from fastapi import Depends
from fastapi.security import APIKeyCookie, APIKeyQuery from fastapi.security import APIKeyCookie, APIKeyQuery
from src.error.exceptions import JWTTokenVerifyException from src.error.exceptions import JWTTokenVerifyException
from src.logging.get_logger import get_logger
from src.model.internal.jwt_token import JWTToken from src.model.internal.jwt_token import JWTToken
from src.model.internal.session import UserSession from src.model.internal.session import UserSession
from src.services.session_service import get_session from src.services.session_service import get_session
from src.system_var import environment from src.system_var import environment
logger = get_logger('認証チェック')
cookie_security = APIKeyCookie(name='session', auto_error=False) cookie_security = APIKeyCookie(name='session', auto_error=False)
code_security = APIKeyQuery(name='code', auto_error=False) code_security = APIKeyQuery(name='code', auto_error=False)
@ -17,32 +19,34 @@ code_security = APIKeyQuery(name='code', auto_error=False)
def get_current_session(session_key=Depends(cookie_security)): def get_current_session(session_key=Depends(cookie_security)):
if session_key is None: if session_key is None:
return None return None
session = get_session(session_key) session = get_session(session_key)
# sessionが存在しない場合はNoneが返る # sessionが存在しない場合はNoneが返る
return session return session
def check_session_expired(session:Union[UserSession, None]=Depends(get_current_session)): def check_session_expired(session: Union[UserSession, None] = Depends(get_current_session)):
"""セッションの最後にアクセスした時間が、セッション有効期限切れであるかどうかをチェックする""" """セッションの最後にアクセスした時間が、セッション有効期限切れであるかどうかをチェックする"""
if session is None: if session is None:
return None return None
last_access_time = session.last_access_time last_access_time = session.last_access_time
session_expired_period = datetime.datetime.fromtimestamp(last_access_time) + datetime.timedelta(minutes=environment.SESSION_EXPIRE_MINUTE) session_expired_period = datetime.datetime.fromtimestamp(
last_access_time) + datetime.timedelta(minutes=environment.SESSION_EXPIRE_MINUTE)
if session_expired_period < datetime.datetime.now(): if session_expired_period < datetime.datetime.now():
return None return None
return session return session
def verify_session(session:Union[UserSession, None]=Depends(check_session_expired)):
def verify_session(session: Union[UserSession, None] = Depends(check_session_expired)):
if session is None: if session is None:
return None return None
jwt_token = JWTToken(session.id_token, session.refresh_token) jwt_token = JWTToken(session.id_token, session.refresh_token)
try: try:
jwt_token.verify_token() jwt_token.verify_token()
except JWTTokenVerifyException as e: except JWTTokenVerifyException as e:
print(e) logger.info(e)
return None return None
return session return session

View File

@ -14,4 +14,4 @@ def get_database(request: Request) -> Database:
def get_repository(Repo_type: Type[BaseRepository]) -> Callable: def get_repository(Repo_type: Type[BaseRepository]) -> Callable:
def get_repo(db: Database = Depends(get_database)) -> Type[BaseRepository]: def get_repo(db: Database = Depends(get_database)) -> Type[BaseRepository]:
return Repo_type(db) return Repo_type(db)
return get_repo return get_repo

View File

@ -1,7 +1,6 @@
from typing import Callable, Type from typing import Callable, Type
from fastapi import Depends from fastapi import Depends
from starlette.requests import Request
from src.db.database import Database from src.db.database import Database
from src.depends.database import get_database from src.depends.database import get_database
@ -9,8 +8,8 @@ from src.services.base_service import BaseService
def get_service(Service_type: Type[BaseService]) -> Callable: def get_service(Service_type: Type[BaseService]) -> Callable:
def get_service(db: Database=Depends(get_database)) -> Type[BaseService]: def get_service(db: Database = Depends(get_database)) -> Type[BaseService]:
repositories = {key: repository(db) for key, repository in Service_type.REPOSITORIES.items()} repositories = {key: repository(db) for key, repository in Service_type.REPOSITORIES.items()}
clients = {key: client() for key, client in Service_type.CLIENTS.items()} clients = {key: client() for key, client in Service_type.CLIENTS.items()}
return Service_type(repositories=repositories, clients=clients) return Service_type(repositories=repositories, clients=clients)
return get_service return get_service

View File

@ -12,4 +12,4 @@ def http_exception_handler(request: Request, exc: HTTPException):
raise exc raise exc
error_detail = exc.detail if hasattr(exc, 'detail') else '' error_detail = exc.detail if hasattr(exc, 'detail') else ''
reason = parse.quote(error_detail) reason = parse.quote(error_detail)
return RedirectResponse(f'/logout?reason={reason}', status_code=status.HTTP_303_SEE_OTHER) return RedirectResponse(f'/logout/?reason={reason}', status_code=status.HTTP_303_SEE_OTHER)

View File

@ -7,18 +7,22 @@ class MeDaCaException(Exception):
"""Webアプリの共通例外""" """Webアプリの共通例外"""
pass pass
class NotAuthorizeException(MeDaCaException): class NotAuthorizeException(MeDaCaException):
"""認証失敗の例外""" """認証失敗の例外"""
pass pass
class JWTTokenVerifyException(MeDaCaException): class JWTTokenVerifyException(MeDaCaException):
"""トークン検証失敗の例外""" """トークン検証失敗の例外"""
pass pass
class DBException(MeDaCaException): class DBException(MeDaCaException):
"""DB関連の例外""" """DB関連の例外"""
pass pass
class UnexpectedException(MeDaCaException): class UnexpectedException(MeDaCaException):
"""予期しない例外""" """予期しない例外"""

View File

@ -0,0 +1,37 @@
import logging
from src.system_var.environment import LOG_LEVEL
# boto3関連モジュールのログレベルを事前に個別指定し、モジュール内のDEBUGログの表示を抑止する
for name in ["boto3", "botocore", "s3transfer", "urllib3"]:
logging.getLogger(name).setLevel(logging.WARNING)
def get_logger(log_name: str) -> logging.Logger:
"""一意のログ出力モジュールを取得します。
Args:
log_name (str): ロガー名
Returns:
_type_: _description_
"""
logger = logging.getLogger(log_name)
level = logging.getLevelName(LOG_LEVEL)
if not isinstance(level, int):
level = logging.INFO
logger.setLevel(level)
if not logger.hasHandlers():
handler = logging.StreamHandler()
logger.addHandler(handler)
formatter = logging.Formatter(
'%(name)s\t[%(levelname)s]\t%(asctime)s\t%(message)s',
'%Y-%m-%d %H:%M:%S'
)
for handler in logger.handlers:
handler.setFormatter(formatter)
return logger

View File

@ -14,6 +14,8 @@ app = FastAPI()
# 静的ファイルをマウント # 静的ファイルをマウント
app.mount('/static', StaticFiles(directory=path.dirname(static.__file__)), name='static') app.mount('/static', StaticFiles(directory=path.dirname(static.__file__)), name='static')
# ルートパス。顧客ログイン画面にリダイレクトさせる
app.include_router(root.router)
# ログイン関連のルーター # ログイン関連のルーター
app.include_router(login.router, prefix='/login') app.include_router(login.router, prefix='/login')
# ログアウト関連のルーター # ログアウト関連のルーター
@ -30,6 +32,9 @@ app.include_router(bio_download.router, prefix='/bio')
# ヘルスチェック用のルーター # ヘルスチェック用のルーター
app.include_router(healthcheck.router, prefix='/healthcheck') app.include_router(healthcheck.router, prefix='/healthcheck')
# サンプル実装、ファイル送信ルーター
app.include_router(sample_router, prefix='/sample')
# エラー発生時にログアウト画面に遷移させるハンドラー # エラー発生時にログアウト画面に遷移させるハンドラー
app.add_exception_handler(status.HTTP_401_UNAUTHORIZED, http_exception_handler) app.add_exception_handler(status.HTTP_401_UNAUTHORIZED, http_exception_handler)
app.add_exception_handler(status.HTTP_403_FORBIDDEN, http_exception_handler) app.add_exception_handler(status.HTTP_403_FORBIDDEN, http_exception_handler)

View File

@ -15,34 +15,34 @@ class BioSalesViewModel(BaseDBModel):
rec_tran_kbn: Optional[str] rec_tran_kbn: Optional[str]
rev_hsdnymd_wrk: Optional[str] rev_hsdnymd_wrk: Optional[str]
rev_hsdnymd_srk: Optional[str] rev_hsdnymd_srk: Optional[str]
rec_urag_no: Optional[str] rec_urag_num: Optional[str]
rec_comm_nm: Optional[str] rec_comm_name: Optional[str]
rec_nnskfcl_nm: Optional[str] rec_nonyu_fcl_name: Optional[str]
rec_nnsk_fcl_addr: Optional[str] rec_nonyu_fcl_addr: Optional[str]
rec_lot_num: Optional[str] rec_lot_num: Optional[str]
rec_amt: Optional[str] rec_qty: Optional[str]
rec_ymd: Optional[str] rec_ymd: Optional[str]
sale_data_cat: Optional[str] sale_data_cat: Optional[str]
slip_file_nm: Optional[str] slip_file_name: Optional[str]
slip_mgt_no: Optional[str] slip_mgt_num: Optional[str]
row_num: Optional[int] row_num: Optional[int]
hsdn_ymd: Optional[str] hsdn_ymd: Optional[str]
exec_dt: Optional[str] exec_dt: Optional[str]
v_tran_cd: Optional[int] v_tran_cd: Optional[int]
tran_kbn_nm: Optional[str] tran_kbn_name: Optional[str]
whs_org_cd: Optional[str] whs_org_cd: Optional[str]
v_whsorg_cd: Optional[str] v_whsorg_cd: Optional[str]
whs_org_nm: Optional[str] whs_org_name: Optional[str]
whs_org_kn: Optional[str] whs_org_kn: Optional[str]
v_whs_cd: Optional[int] v_whs_cd: Optional[int]
whs_nm: Optional[str] whs_name: Optional[str]
nnsk_cd: Optional[str] nonyu_fcl_cd: Optional[str]
v_inst_cd: Optional[str] v_inst_cd: Optional[str]
v_inst_kn: Optional[str] v_inst_kn: Optional[str]
v_inst_nm: Optional[str] v_inst_nm: Optional[str]
v_inst_addr: Optional[str] v_inst_addr: Optional[str]
comm_cd: Optional[str] comm_cd: Optional[str]
comm_nm: Optional[str] product_name: Optional[str]
whs_rep_comm_nm: Optional[str] whs_rep_comm_nm: Optional[str]
whs_rep_nnskfcl_nm: Optional[str] whs_rep_nnskfcl_nm: Optional[str]
whs_rep_nnsk_fcl_addr: Optional[str] whs_rep_nnsk_fcl_addr: Optional[str]
@ -53,7 +53,7 @@ class BioSalesViewModel(BaseDBModel):
fcl_exis_kbn: Optional[str] fcl_exis_kbn: Optional[str]
amt: Optional[int] amt: Optional[int]
slip_org_kbn: Optional[str] slip_org_kbn: Optional[str]
bef_slip_mgt_no: Optional[str] bef_slip_mgt_num: Optional[str]
lot_no_err_flg: Optional[str] lot_no_err_flg: Optional[str]
iko_flg: Optional[str] iko_flg: Optional[str]
kjyo_ym: Optional[str] kjyo_ym: Optional[str]

View File

@ -1,4 +1,3 @@
from datetime import datetime
from typing import Optional from typing import Optional
from src.model.db.base_db_model import BaseDBModel from src.model.db.base_db_model import BaseDBModel

View File

@ -4,4 +4,4 @@ from src.model.db.base_db_model import BaseDBModel
class PharmacyProductMasterModel(BaseDBModel): class PharmacyProductMasterModel(BaseDBModel):
mkr_cd_nm: Optional[str] mkr_cd_name: Optional[str]

View File

@ -28,9 +28,9 @@ class UserMasterModel(BaseDBModel):
def is_enable_user(self): def is_enable_user(self):
return self.enabled_flg == 'Y' return self.enabled_flg == 'Y'
def is_maintenance_user(self): def is_maintenance_user(self):
return self.mntuser_flg == '1' return self.mntuser_flg == '1'
def is_groupware_user(self): def is_groupware_user(self):
return self.mntuser_flg == '0' return self.mntuser_flg == '0' or self.mntuser_flg is None

View File

@ -6,5 +6,5 @@ from src.model.db.base_db_model import BaseDBModel
class WholesalerMasterModel(BaseDBModel): class WholesalerMasterModel(BaseDBModel):
rec_whs_cd: Optional[str] rec_whs_cd: Optional[str]
rec_whs_sub_cd: Optional[str] rec_whs_sub_cd: Optional[str]
nm: Optional[str] name: Optional[str]
whs_nm: Optional[str] whs_name: Optional[str]

View File

@ -15,11 +15,11 @@ class JWTToken:
refresh_token: str refresh_token: str
verified_jwt: Optional[dict] verified_jwt: Optional[dict]
def __init__(self, id_token: str, refresh_token: str, verified_jwt: dict=None) -> None: def __init__(self, id_token: str, refresh_token: str, verified_jwt: dict = None) -> None:
self.id_token = id_token self.id_token = id_token
self.refresh_token = refresh_token self.refresh_token = refresh_token
self.verified_jwt = verified_jwt self.verified_jwt = verified_jwt
@property @property
def verified_token(self): def verified_token(self):
if self.verified_jwt is None: if self.verified_jwt is None:
@ -29,7 +29,7 @@ class JWTToken:
@property @property
def user_id(self): def user_id(self):
verified_token = self.verified_token verified_token = self.verified_token
user_id: str = None user_id: str = None
identities: dict = verified_token.get('identities') identities: dict = verified_token.get('identities')
if identities is not None: if identities is not None:
@ -63,7 +63,7 @@ class JWTToken:
'code': code, 'code': code,
'redirect_uri': environment.COGNITO_REDIRECT_URI 'redirect_uri': environment.COGNITO_REDIRECT_URI
} }
message = bytes(f'{environment.COGNITO_CLIENT_ID}:{environment.COGNITO_CLIENT_SECRET}', 'utf8') message = bytes(f'{environment.COGNITO_CLIENT_ID}:{environment.COGNITO_CLIENT_SECRET}', 'utf8')
auth_header_value = base64.b64encode(message).decode() auth_header_value = base64.b64encode(message).decode()
request_headers = { request_headers = {
@ -76,7 +76,7 @@ class JWTToken:
raise JWTTokenVerifyException(res.text) raise JWTTokenVerifyException(res.text)
token_response = json.loads(res.text) token_response = json.loads(res.text)
return cls(id_token=token_response['id_token'], refresh_token=token_response['refresh_token']) return cls(id_token=token_response['id_token'], refresh_token=token_response['refresh_token'])
@classmethod @classmethod
@ -99,7 +99,7 @@ class JWTToken:
'refresh_token': refresh_token, 'refresh_token': refresh_token,
'redirect_uri': environment.COGNITO_REDIRECT_URI 'redirect_uri': environment.COGNITO_REDIRECT_URI
} }
message = bytes(f'{environment.COGNITO_CLIENT_ID}:{environment.COGNITO_CLIENT_SECRET}', 'utf8') message = bytes(f'{environment.COGNITO_CLIENT_ID}:{environment.COGNITO_CLIENT_SECRET}', 'utf8')
auth_header_value = base64.b64encode(message).decode() auth_header_value = base64.b64encode(message).decode()
request_headers = { request_headers = {
@ -117,13 +117,13 @@ class JWTToken:
def verify_token(self): def verify_token(self):
if self.id_token is None: if self.id_token is None:
raise Exception('アクセストークンがない') raise Exception('アクセストークンがない')
issuer = f'https://cognito-idp.{environment.AWS_REGION}.amazonaws.com/{environment.COGNITO_USER_POOL_ID}' issuer = f'https://cognito-idp.{environment.AWS_REGION}.amazonaws.com/{environment.COGNITO_USER_POOL_ID}'
jwks_url = f'{issuer}/.well-known/jwks.json' jwks_url = f'{issuer}/.well-known/jwks.json'
jwks_client = jwt.PyJWKClient(jwks_url) jwks_client = jwt.PyJWKClient(jwks_url)
signing_key = jwks_client.get_signing_key_from_jwt(self.id_token) signing_key = jwks_client.get_signing_key_from_jwt(self.id_token)
try: try:
verified_jwt = jwt.decode( verified_jwt = jwt.decode(
self.id_token, self.id_token,

View File

@ -14,11 +14,11 @@ class UserSession(DynamoDBTableModel):
session_key = UnicodeAttribute(hash_key=True) session_key = UnicodeAttribute(hash_key=True)
user_id = UnicodeAttribute() user_id = UnicodeAttribute()
id_token = UnicodeAttribute() id_token = UnicodeAttribute()
doc_flg = UnicodeAttribute() doc_flg = UnicodeAttribute(null=True)
inst_flg = UnicodeAttribute() inst_flg = UnicodeAttribute(null=True)
bio_flg = UnicodeAttribute() bio_flg = UnicodeAttribute(null=True)
master_mainte_flg = UnicodeAttribute() master_mainte_flg = UnicodeAttribute(null=True)
user_flg = UnicodeAttribute() user_flg = UnicodeAttribute(null=True)
refresh_token = UnicodeAttribute() refresh_token = UnicodeAttribute()
csrf_token = UnicodeAttribute() csrf_token = UnicodeAttribute()
last_access_time = NumberAttribute() last_access_time = NumberAttribute()
@ -27,7 +27,7 @@ class UserSession(DynamoDBTableModel):
@classmethod @classmethod
def new_last_access_time(cls): def new_last_access_time(cls):
return datetime.datetime.now().timestamp() return datetime.datetime.now().timestamp()
@classmethod @classmethod
def new_record_expiration_time(cls, expire=environment.SESSION_EXPIRE_MINUTE): def new_record_expiration_time(cls, expire=environment.SESSION_EXPIRE_MINUTE):
last_access_time = datetime.datetime.fromtimestamp(cls.new_last_access_time()) last_access_time = datetime.datetime.fromtimestamp(cls.new_last_access_time())
@ -35,8 +35,8 @@ class UserSession(DynamoDBTableModel):
@classmethod @classmethod
def new( def new(
cls, user_id, id_token, refresh_token, csrf_token, doc_flg, inst_flg, bio_flg, master_mainte_flg, user_flg cls, user_id, id_token, refresh_token, csrf_token, doc_flg, inst_flg, bio_flg, master_mainte_flg, user_flg
): ):
return cls( return cls(
session_key=str(uuid.uuid4()), session_key=str(uuid.uuid4()),
user_id=user_id, user_id=user_id,
@ -47,7 +47,7 @@ class UserSession(DynamoDBTableModel):
inst_flg=inst_flg, inst_flg=inst_flg,
bio_flg=bio_flg, bio_flg=bio_flg,
master_mainte_flg=master_mainte_flg, master_mainte_flg=master_mainte_flg,
user_flg=user_flg, user_flg=user_flg,
last_access_time=cls.new_last_access_time(), last_access_time=cls.new_last_access_time(),
record_expiration_time=cls.new_record_expiration_time() record_expiration_time=cls.new_record_expiration_time()
) )

View File

@ -9,18 +9,18 @@ from src.util.string_util import is_not_empty
@sanitize @sanitize
class BioModel(BaseModel): class BioModel(BaseModel):
wholesaler_code: Optional[str] rec_whs_cd: Optional[str]
wholesaler_sub_code: Optional[str] rec_whs_sub_cd: Optional[str]
wholesaler_name: Optional[str] whs_name: Optional[str]
org_kbn: Optional[str] slip_org_kbn: Optional[str]
rec_ymd_from: Optional[str] rec_ymd_from: Optional[str]
rec_ymd_to: Optional[str] rec_ymd_to: Optional[str]
rec_lot_num: Optional[str] rec_lot_num: Optional[str]
data_kbn: Optional[str] data_kbn: Optional[str]
maker_cd: Optional[str] mkr_cd: Optional[str]
rev_hsdnymd_srk_from: Optional[str] rev_hsdnymd_srk_from: Optional[str]
rev_hsdnymd_srk_to: Optional[str] rev_hsdnymd_srk_to: Optional[str]
ikoFlg: Optional[str] iko_flg: Optional[str]
@classmethod @classmethod
def as_form( def as_form(
@ -50,7 +50,7 @@ class BioModel(BaseModel):
ctrl_rev_hsdnymd_srk_to, ctrl_rev_hsdnymd_srk_to,
ikoFlg ikoFlg
) )
@classmethod @classmethod
def as_body( def as_body(
cls, cls,
@ -79,7 +79,6 @@ class BioModel(BaseModel):
ctrl_rev_hsdnymd_srk_to, ctrl_rev_hsdnymd_srk_to,
ikoFlg ikoFlg
) )
def __convert_request_param( def __convert_request_param(
cls, cls,
@ -122,16 +121,16 @@ class BioModel(BaseModel):
rev_hsdnymd_srk_to = ctrl_rev_hsdnymd_srk_to.replace('/', '') rev_hsdnymd_srk_to = ctrl_rev_hsdnymd_srk_to.replace('/', '')
return cls( return cls(
wholesaler_code=wholesaler_code, rec_whs_cd=wholesaler_code,
wholesaler_sub_code=wholesaler_sub_code, rec_whs_sub_cd=wholesaler_sub_code,
wholesaler_name=wholesaler_name, whs_name=wholesaler_name,
org_kbn=ctrl_org_kbn, slip_org_kbn=ctrl_org_kbn,
rec_ymd_from=rec_ymd_from, rec_ymd_from=rec_ymd_from,
rec_ymd_to=rec_ymd_to, rec_ymd_to=rec_ymd_to,
rec_lot_num=ctrl_rec_lot_num, rec_lot_num=ctrl_rec_lot_num,
data_kbn=ctrl_data_kbn, data_kbn=ctrl_data_kbn,
maker_cd=ctrl_maker_cd, mkr_cd=ctrl_maker_cd,
rev_hsdnymd_srk_from=rev_hsdnymd_srk_from, rev_hsdnymd_srk_from=rev_hsdnymd_srk_from,
rev_hsdnymd_srk_to=rev_hsdnymd_srk_to, rev_hsdnymd_srk_to=rev_hsdnymd_srk_to,
ikoFlg=ikoFlg iko_flg=ikoFlg
) )

View File

@ -1,20 +1,18 @@
from typing import Optional
from fastapi import Body from fastapi import Body
from pydantic import BaseModel from pydantic import BaseModel
class BioDownloadModel(BaseModel): class BioDownloadModel(BaseModel):
user_id: str user_id: str
kind: str ext: str
@classmethod @classmethod
def as_body( def as_body(
cls, cls,
user_id: str = Body(), user_id: str = Body(),
kind: str = Body() ext: str = Body()
): ):
return cls( return cls(
user_id=user_id, user_id=user_id,
kind=kind ext=ext
) )

View File

@ -5,7 +5,7 @@ from pydantic import BaseModel
class LoginModel(BaseModel): class LoginModel(BaseModel):
username: str username: str
password: str password: str
@classmethod @classmethod
def as_form( def as_form(
cls, cls,

View File

@ -7,13 +7,13 @@ from src.util.sanitize import sanitize
class BisDisplayModel(BioSalesViewModel): class BisDisplayModel(BioSalesViewModel):
def __init__(self, param: BioSalesViewModel) -> None: def __init__(self, param: BioSalesViewModel) -> None:
super().__init__(**param.dict()) super().__init__(**param.dict())
# 区分・フラグの正式名称を設定 # 区分・フラグの正式名称を設定
self.slip_org_kbn = constants.SLIP_ORG_KBN_FULL_NAME.get(self.slip_org_kbn) self.slip_org_kbn = constants.SLIP_ORG_KBN_FULL_NAME.get(self.slip_org_kbn)
self.data_kbn = constants.DATA_KBN_JP_NAME.get(self.data_kbn) self.data_kbn = constants.DATA_KBN_JP_NAME.get(self.data_kbn)
self.lot_no_err_flg = constants.LOT_NO_ERR_FLG_JP_NAME.get(self.lot_no_err_flg) self.lot_no_err_flg = constants.LOT_NO_ERR_FLG_JP_NAME.get(self.lot_no_err_flg)
# 訂正前伝票管理番号がセットされているときのみ修正日時、修正者、エラー詳細種別をセット # 訂正前伝票管理番号がセットされているときのみ修正日時、修正者、エラー詳細種別をセット
if (self.bef_slip_mgt_no is None): if (self.bef_slip_mgt_num is None):
self.ins_dt = "" self.ins_dt = ""
self.ins_usr = "" self.ins_usr = ""

View File

@ -20,10 +20,10 @@ class BioViewModel(BaseModel):
phm_models: list[PharmacyProductMasterModel] phm_models: list[PharmacyProductMasterModel]
bio_data: Optional[list[BisDisplayModel]] = [] bio_data: Optional[list[BisDisplayModel]] = []
form_data: Optional[BioModel] form_data: Optional[BioModel]
def display_wholesaler_names(self): def display_wholesaler_names(self):
display_names = [ display_names = [
f'{whs_model.rec_whs_cd}-{whs_model.rec_whs_sub_cd}:{whs_model.nm}' f'{whs_model.rec_whs_cd}-{whs_model.rec_whs_sub_cd}:{whs_model.name}'
for whs_model in self.whs_models for whs_model in self.whs_models
] ]
return display_names return display_names
@ -41,7 +41,7 @@ class BioViewModel(BaseModel):
def display_data_kbn(self): def display_data_kbn(self):
return OrderedDict( return OrderedDict(
{ {
'' : '', '': '',
'0': '正常', '0': '正常',
'1': 'ロットエラー', '1': 'ロットエラー',
'3': 'ロット不明', '3': 'ロット不明',
@ -59,14 +59,16 @@ class BioViewModel(BaseModel):
if not self.is_form_submitted(): if not self.is_form_submitted():
return '' return ''
form_wholesaler_full_name = f'{self.form_data.wholesaler_code}-{self.form_data.wholesaler_sub_code}:{self.form_data.wholesaler_name}' form_wholesaler_full_name = \
f'{self.form_data.rec_whs_cd}-{self.form_data.rec_whs_sub_cd}:{self.form_data.whs_name}'
return self._selected_value(form_wholesaler_full_name, selected_wholesaler) return self._selected_value(form_wholesaler_full_name, selected_wholesaler)
def is_selected_org_kbn(self, selected_org_kbn): def is_selected_org_kbn(self, selected_org_kbn):
if not self.is_form_submitted(): if not self.is_form_submitted():
return '' return ''
return self._selected_value(self.form_data.org_kbn, selected_org_kbn) return self._selected_value(self.form_data.slip_org_kbn, selected_org_kbn)
def is_input_rec_ymd_from(self): def is_input_rec_ymd_from(self):
if not self.is_form_submitted(): if not self.is_form_submitted():
return '' return ''
@ -84,18 +86,18 @@ class BioViewModel(BaseModel):
return '' return ''
return self.form_data.rec_lot_num or '' return self.form_data.rec_lot_num or ''
def is_selected_data_kbn(self, selected_data_kbn): def is_selected_data_kbn(self, selected_data_kbn):
if not self.is_form_submitted(): if not self.is_form_submitted():
return '' return ''
return self._selected_value(self.form_data.data_kbn, selected_data_kbn) return self._selected_value(self.form_data.data_kbn, selected_data_kbn)
def is_selected_maker_cd(self, selected_maker_cd): def is_selected_maker_cd(self, selected_maker_cd):
if not self.is_form_submitted(): if not self.is_form_submitted():
return '' return ''
return self._selected_value(self.form_data.maker_cd, selected_maker_cd) return self._selected_value(self.form_data.mkr_cd, selected_maker_cd)
def is_input_rev_hsdnymd_srk_from(self): def is_input_rev_hsdnymd_srk_from(self):
if not self.is_form_submitted(): if not self.is_form_submitted():
@ -108,13 +110,13 @@ class BioViewModel(BaseModel):
return '' return ''
return self._format_date_string(self.form_data.rev_hsdnymd_srk_to) return self._format_date_string(self.form_data.rev_hsdnymd_srk_to)
def is_checked_iko_flg(self): def is_checked_iko_flg(self):
if not self.is_form_submitted(): if not self.is_form_submitted():
return '' return ''
return 'checked' if self.form_data.ikoFlg else '' return 'checked' if self.form_data.iko_flg else ''
def disabled_button(self): def disabled_button(self):
return 'disabled' if self.is_data_empty() or self.is_data_overflow_max_length() else '' return 'disabled' if self.is_data_empty() or self.is_data_overflow_max_length() else ''
@ -123,7 +125,7 @@ class BioViewModel(BaseModel):
def is_data_empty(self): def is_data_empty(self):
return len(self.bio_data) == 0 return len(self.bio_data) == 0
def is_data_overflow_max_length(self): def is_data_overflow_max_length(self):
return len(self.bio_data) >= environment.BIO_SEARCH_RESULT_MAX_COUNT return len(self.bio_data) >= environment.BIO_SEARCH_RESULT_MAX_COUNT

View File

@ -7,4 +7,4 @@ class LogoutViewModel(BaseModel):
subtitle: str = 'MeDaCA Logout' subtitle: str = 'MeDaCA Logout'
redirect_to: Optional[str] redirect_to: Optional[str]
reason: Optional[str] reason: Optional[str]
link_text:Optional[str] link_text: Optional[str]

View File

@ -7,9 +7,9 @@ class UserViewModel(BaseModel):
bio_flg: str # AUTH_FLG1 bio_flg: str # AUTH_FLG1
doc_flg: str # AUTH_FLG2 doc_flg: str # AUTH_FLG2
inst_flg: str # AUTH_FLG3 inst_flg: str # AUTH_FLG3
master_mainte_flg: str # AUTH_FLG4 master_mainte_flg: str # AUTH_FLG4
user_flg: Optional[str] # MNTUSER_FLG user_flg: Optional[str] # MNTUSER_FLG
def has_ult_doctor_permission(self): def has_ult_doctor_permission(self):
return self.doc_flg == '1' return self.doc_flg == '1'
@ -21,6 +21,3 @@ class UserViewModel(BaseModel):
def has_master_maintenance_permission(self): def has_master_maintenance_permission(self):
return self.master_mainte_flg == '1' return self.master_mainte_flg == '1'
def is_maintenance_user(self):
return self.user_flg == '1'

View File

@ -8,8 +8,9 @@ from src.model.db.base_db_model import BaseDBModel
class BaseRepository(metaclass=ABCMeta): class BaseRepository(metaclass=ABCMeta):
_database: Database _database: Database
def __init__(self, db: Database) -> None: def __init__(self, db: Database) -> None:
self._database = db self._database = db
@ -29,10 +30,9 @@ class BaseRepository(metaclass=ABCMeta):
"""DBの取得結果をデータフレームにして返す""" """DBの取得結果をデータフレームにして返す"""
pass pass
def _to_data_frame(self, query, parameter: BaseDBModel): def _to_data_frame(self, query, parameter: BaseDBModel):
"""DBの取得結果をデータフレームに変換する""" """DBの取得結果をデータフレームに変換する"""
params = params=parameter.dict() params = params = parameter.dict()
sql_query = pd.read_sql( sql_query = pd.read_sql(
text(query), text(query),

View File

@ -1,10 +1,13 @@
from src.db import sql_condition as condition from src.db import sql_condition as condition
from src.db.sql_condition import SQLCondition from src.db.sql_condition import SQLCondition
from src.logging.get_logger import get_logger
from src.model.db.bio_sales_view import BioSalesViewModel from src.model.db.bio_sales_view import BioSalesViewModel
from src.model.request.bio import BioModel from src.model.request.bio import BioModel
from src.repositories.base_repository import BaseRepository from src.repositories.base_repository import BaseRepository
from src.util.string_util import is_not_empty from src.util.string_util import is_not_empty
logger = get_logger('生物由来参照')
class BioSalesViewRepository(BaseRepository): class BioSalesViewRepository(BaseRepository):
FETCH_SQL = """\ FETCH_SQL = """\
@ -12,19 +15,19 @@ class BioSalesViewRepository(BaseRepository):
( (
CASE CASE
WHEN LEFT(bs.v_tran_cd, 1) = 2 WHEN LEFT(bs.v_tran_cd, 1) = 2
AND bs.amt >= 1 THEN CONCAT('-', bs.amt) AND bs.qty >= 1 THEN CONCAT('-', bs.qty)
ELSE bs.amt ELSE bs.qty
END END
) AS amt_fugo, ) AS amt_fugo,
bs.*, bs.*,
ln.ser_no, ln.ser_num,
ln.lot_num, ln.lot_num,
ln.expr_dt ln.expr_dt
FROM FROM
src05.bio_sales_view bs src05.bio_sales_view bs
LEFT OUTER JOIN LEFT OUTER JOIN
src05.lot_num_mst ln src05.lot_num_mst ln
ON bs.mkr_cd = ln.ser_no ON bs.mkr_cd = ln.ser_num
AND bs.rec_lot_num = ln.lot_num AND bs.rec_lot_num = ln.lot_num
WHERE WHERE
{where_clause} {where_clause}
@ -32,45 +35,40 @@ class BioSalesViewRepository(BaseRepository):
bs.rec_whs_cd, bs.rec_whs_cd,
bs.rec_whs_sub_cd, bs.rec_whs_sub_cd,
bs.rev_hsdnymd_srk, bs.rev_hsdnymd_srk,
bs.slip_mgt_no bs.slip_mgt_num
ASC\ ASC\
""" """
def fetch_many(self, parameter: BioModel) -> list[BioSalesViewModel]: def fetch_many(self, parameter: BioModel) -> list[BioSalesViewModel]:
try: try:
self._database.connect() self._database.connect()
logger.debug('DB参照実行')
where_clause = self.__build_condition(parameter) where_clause = self.__build_condition(parameter)
# error_log(date("Y/m/d H:i:s") . " [INFO] DB Return=" . $result . "\r\n", 3, "$execLog");
# error_log(date("Y/m/d H:i:s") . " [INFO] DB参照実行" . "\r\n", 3, "$execLog");
query = self.FETCH_SQL.format(where_clause=where_clause) query = self.FETCH_SQL.format(where_clause=where_clause)
# error_log(date("Y/m/d H:i:s") . " [INFO] SQL: " . $query . "\r\n", 3, "$execLog"); logger.debug(f'SQL: {query}')
result = self._database.execute_select(query, parameter.dict()) result = self._database.execute_select(query, parameter.dict())
logger.debug(f'count= {len(result)}')
models = [BioSalesViewModel(**r) for r in result] models = [BioSalesViewModel(**r) for r in result]
# error_log(date("Y/m/d H:i:s") . " [INFO] count=" . $count . "\r\n", 3, "$execLog");
return models return models
except Exception as e: except Exception as e:
# TODO: ファイルへの書き出しはloggerでやる logger.exception(f"DB Error : Exception={e.args}")
print(f"[ERROR] DB Error : Exception={e.args}")
raise e raise e
finally: finally:
self._database.disconnect() self._database.disconnect()
def fetch_as_data_frame(self, parameter: BioModel): def fetch_as_data_frame(self, parameter: BioModel):
try: try:
self._database.connect() self._database.connect()
logger.debug('DB参照実行')
where_clause = self.__build_condition(parameter) where_clause = self.__build_condition(parameter)
# error_log(date("Y/m/d H:i:s") . " [INFO] DB Return=" . $result . "\r\n", 3, "$execLog");
# error_log(date("Y/m/d H:i:s") . " [INFO] DB参照実行" . "\r\n", 3, "$execLog");
query = self.FETCH_SQL.format(where_clause=where_clause) query = self.FETCH_SQL.format(where_clause=where_clause)
# error_log(date("Y/m/d H:i:s") . " [INFO] SQL: " . $query . "\r\n", 3, "$execLog"); logger.debug(f'SQL: {query}')
# models = [BioSalesViewModel(**r) for r in result]
# error_log(date("Y/m/d H:i:s") . " [INFO] count=" . $count . "\r\n", 3, "$execLog");
df = self._to_data_frame(query, parameter) df = self._to_data_frame(query, parameter)
return df logger.debug(f'count= {len(df.index)}')
# ログ出力のため、クエリも返却
return df, query
except Exception as e: except Exception as e:
# TODO: ファイルへの書き出しはloggerでやる logger.exception(f"DB Error : Exception={e.args}")
print(f"[ERROR] DB Error : Exception={e.args}")
raise e raise e
finally: finally:
self._database.disconnect() self._database.disconnect()
@ -79,12 +77,12 @@ class BioSalesViewRepository(BaseRepository):
where_clauses: list[SQLCondition] = [] where_clauses: list[SQLCondition] = []
# 卸(コード/サブコード) # 卸(コード/サブコード)
if is_not_empty(parameter.wholesaler_code) and is_not_empty(parameter.wholesaler_sub_code): if is_not_empty(parameter.rec_whs_cd) and is_not_empty(parameter.rec_whs_sub_cd):
where_clauses.append(SQLCondition('rec_whs_cd', condition.EQ, 'wholesaler_code')) where_clauses.append(SQLCondition('rec_whs_cd', condition.EQ, 'rec_whs_cd'))
where_clauses.append(SQLCondition('rec_whs_sub_cd', condition.EQ, 'wholesaler_sub_code')) where_clauses.append(SQLCondition('rec_whs_sub_cd', condition.EQ, 'rec_whs_sub_cd'))
# データ種別 # データ種別
if is_not_empty(parameter.org_kbn): if is_not_empty(parameter.slip_org_kbn):
where_clauses.append(SQLCondition('slip_org_kbn', condition.EQ, 'org_kbn')) where_clauses.append(SQLCondition('slip_org_kbn', condition.EQ, 'slip_org_kbn'))
# 処理日 開始日 # 処理日 開始日
if is_not_empty(parameter.rec_ymd_from): if is_not_empty(parameter.rec_ymd_from):
where_clauses.append(SQLCondition('rec_ymd', condition.GE, 'rec_ymd_from')) where_clauses.append(SQLCondition('rec_ymd', condition.GE, 'rec_ymd_from'))
@ -95,14 +93,14 @@ class BioSalesViewRepository(BaseRepository):
if is_not_empty(parameter.rec_lot_num): if is_not_empty(parameter.rec_lot_num):
rec_lot_num = parameter.rec_lot_num rec_lot_num = parameter.rec_lot_num
# あいまい検索文字列('%')が含まれる場合は'LIKE'、でなければ'='で検索 # あいまい検索文字列('%')が含まれる場合は'LIKE'、でなければ'='で検索
rec_lot_num_comparator = condition.LIKE if rec_lot_num in '%' else condition.EQ rec_lot_num_comparator = condition.LIKE if rec_lot_num in '%' else condition.EQ
where_clauses.append(SQLCondition('rec_lot_num', rec_lot_num_comparator, 'rec_lot_num')) where_clauses.append(SQLCondition('rec_lot_num', rec_lot_num_comparator, 'rec_lot_num'))
# データ区分 # データ区分
if is_not_empty(parameter.data_kbn): if is_not_empty(parameter.data_kbn):
where_clauses.append(SQLCondition('data_kbn', condition.EQ, 'data_kbn')) where_clauses.append(SQLCondition('data_kbn', condition.EQ, 'data_kbn'))
# 製品 # 製品
if is_not_empty(parameter.maker_cd): if is_not_empty(parameter.mkr_cd):
where_clauses.append(SQLCondition('mkr_cd', condition.EQ, 'maker_cd')) where_clauses.append(SQLCondition('mkr_cd', condition.EQ, 'mkr_cd'))
# 発伝年月日 開始日 # 発伝年月日 開始日
if is_not_empty(parameter.rev_hsdnymd_srk_from): if is_not_empty(parameter.rev_hsdnymd_srk_from):
where_clauses.append(SQLCondition('rev_hsdnymd_srk', condition.GE, 'rev_hsdnymd_srk_from')) where_clauses.append(SQLCondition('rev_hsdnymd_srk', condition.GE, 'rev_hsdnymd_srk_from'))
@ -111,12 +109,12 @@ class BioSalesViewRepository(BaseRepository):
where_clauses.append(SQLCondition('rev_hsdnymd_srk', condition.LE, 'rev_hsdnymd_srk_to')) where_clauses.append(SQLCondition('rev_hsdnymd_srk', condition.LE, 'rev_hsdnymd_srk_to'))
# 移行フラグ # 移行フラグ
# チェックが入っていない場合、移行対象(IKO_FLG = '*')を省く # チェックが入っていない場合、移行対象(IKO_FLG = '*')を省く
if parameter.ikoFlg is None: if parameter.iko_flg is None:
where_clauses.append(SQLCondition('iko_flg', condition.IS, 'NULL', literal=True)) where_clauses.append(SQLCondition('iko_flg', condition.IS, 'NULL', literal=True))
# 固定条件 # 固定条件
# Viewで返されるロット番号9件をNull以外で抽出 # Viewで返されるロット番号9件をNull以外で抽出
where_clauses.append(SQLCondition('LENGTH(TRIM(rec_lot_num))', condition.GT, '0', literal=True)) where_clauses.append(SQLCondition('LENGTH(TRIM(rec_lot_num))', condition.GT, '0', literal=True))
where_clauses_str = ' AND '.join([condition.apply() for condition in where_clauses]) where_clauses_str = ' AND '.join([condition.apply() for condition in where_clauses])
# error_log(date("Y/m/d H:i:s") . " [INFO] 条件設定終了:" . $szConditions . "\r\n", 3, "$execLog"); logger.debug(f'条件設定終了:{where_clauses_str}')
return where_clauses_str return where_clauses_str

View File

@ -1,7 +1,9 @@
from src.logging.get_logger import get_logger
from src.model.db.hdke_tbl import HdkeTblModel from src.model.db.hdke_tbl import HdkeTblModel
from src.model.request.bio import BioModel
from src.repositories.base_repository import BaseRepository from src.repositories.base_repository import BaseRepository
logger = get_logger('日付テーブル取得')
class HdkeTblRepository(BaseRepository): class HdkeTblRepository(BaseRepository):
FETCH_SQL = "SELECT bch_actf FROM src05.hdke_tbl" FETCH_SQL = "SELECT bch_actf FROM src05.hdke_tbl"
@ -14,8 +16,7 @@ class HdkeTblRepository(BaseRepository):
models = [HdkeTblModel(**r) for r in result] models = [HdkeTblModel(**r) for r in result]
return models return models
except Exception as e: except Exception as e:
# TODO: ファイルへの書き出しはloggerでやる logger.exception(f"DB Error : Exception={e}")
print(f"[ERROR] DB Error : Exception={e.args}")
raise e raise e
finally: finally:
self._database.disconnect() self._database.disconnect()

View File

@ -1,26 +1,29 @@
from src.logging.get_logger import get_logger
from src.model.db.pharmacy_product_master import PharmacyProductMasterModel from src.model.db.pharmacy_product_master import PharmacyProductMasterModel
from src.repositories.base_repository import BaseRepository from src.repositories.base_repository import BaseRepository
logger = get_logger('製品取得')
class PharmacyProductMasterRepository(BaseRepository): class PharmacyProductMasterRepository(BaseRepository):
FETCH_SQL = """\ FETCH_SQL = """\
SELECT SELECT
CONCAT(IFNULL(mkr_cd, ''), ' ', IFNULL(mkr_inf_1, '')) AS mkr_cd_nm CONCAT(IFNULL(t1.mkr_cd, ''), ' ', IFNULL(t1.mkr_inf_1, '')) AS mkr_cd_name
FROM FROM
src05.phm_prd_mst_v t1 src05.phm_prd_mst_v t1
INNER JOIN INNER JOIN
( (
SELECT SELECT
prd_cd,MAX(sub_no) AS sno prd_cd, MAX(sub_num) AS sno
FROM FROM
src05.phm_prd_mst_v src05.phm_prd_mst_v
WHERE rec_sts_kbn <> '9' WHERE rec_sts_kbn <> '9'
GROUP BY prd_cd GROUP BY prd_cd
) fmv2 ) fmv2
ON t1.prd_cd = fmv2.prd_cd AND t1.sub_no = fmv2.sno ON t1.prd_cd = fmv2.prd_cd AND t1.sub_num = fmv2.sno
WHERE WHERE
mkr_cd IS NOT NULL t1.mkr_cd IS NOT NULL
ORDER BY mkr_cd ORDER BY mkr_cd
""" """
@ -31,9 +34,7 @@ class PharmacyProductMasterRepository(BaseRepository):
models = [PharmacyProductMasterModel(**r) for r in result] models = [PharmacyProductMasterModel(**r) for r in result]
return models return models
except Exception as e: except Exception as e:
# TODO: ファイルへの書き出しはloggerでやる logger.exception(f"DB Error : Exception={e}")
print(f"[ERROR] getOroshiData DB Error. ")
print(f"[ERROR] ErrorMessage: {e.args}")
raise e raise e
finally: finally:
self._database.disconnect() self._database.disconnect()

View File

@ -1,7 +1,9 @@
from src.logging.get_logger import get_logger
from src.model.db.user_master import UserMasterModel from src.model.db.user_master import UserMasterModel
from src.model.request.bio import BioModel
from src.repositories.base_repository import BaseRepository from src.repositories.base_repository import BaseRepository
logger = get_logger('ユーザー取得')
class UserMasterRepository(BaseRepository): class UserMasterRepository(BaseRepository):
FETCH_SQL = """\ FETCH_SQL = """\
@ -23,8 +25,7 @@ class UserMasterRepository(BaseRepository):
return None return None
return models[0] return models[0]
except Exception as e: except Exception as e:
# TODO: ファイルへの書き出しはloggerでやる logger.exception(f"DB Error : Exception={e}")
print(f"[ERROR] DB Error : Exception={e.args}")
raise e raise e
finally: finally:
self._database.disconnect() self._database.disconnect()

View File

@ -1,25 +1,32 @@
from src.logging.get_logger import get_logger
from src.model.db.wholesaler_master import WholesalerMasterModel from src.model.db.wholesaler_master import WholesalerMasterModel
from src.repositories.base_repository import BaseRepository from src.repositories.base_repository import BaseRepository
logger = get_logger('卸データ取得')
class WholesalerMasterRepository(BaseRepository): class WholesalerMasterRepository(BaseRepository):
FETCH_SQL = """\ FETCH_SQL = """\
SELECT DISTINCT SELECT DISTINCT
b.rec_whs_cd, b.rec_whs_cd,
b.rec_whs_sub_cd, b.rec_whs_sub_cd,
v2.nm, v2.name,
b.whs_nm b.whs_name
FROM src05.bio_sales b FROM src05.bio_sales b
LEFT OUTER JOIN LEFT OUTER JOIN
( (
SELECT sub_no, nm, v_whs_cd, rec_sts_kbn SELECT
sub_num,
name,
v_whs_cd,
rec_sts_kbn
FROM src05.whs_mst_v FROM src05.whs_mst_v
WHERE (SELECT STR_TO_DATE(syor_date, '%Y%m%d') FROM src05.hdke_tbl) BETWEEN start_date AND end_date WHERE (SELECT STR_TO_DATE(syor_date, '%Y%m%d') FROM src05.hdke_tbl) BETWEEN start_date AND end_date
) v2 ) v2
ON b.v_whs_cd = v2.v_whs_cd ON b.v_whs_cd = v2.v_whs_cd
AND v2.rec_sts_kbn <> '9' AND v2.rec_sts_kbn <> '9'
ORDER BY b.rec_whs_cd, b.rec_whs_sub_cd , b.whs_nm DESC ORDER BY b.rec_whs_cd, b.rec_whs_sub_cd , b.whs_name DESC
""" """
def fetch_all(self) -> list[WholesalerMasterModel]: def fetch_all(self) -> list[WholesalerMasterModel]:
@ -30,9 +37,7 @@ class WholesalerMasterRepository(BaseRepository):
models = [WholesalerMasterModel(**r) for r in result_data] models = [WholesalerMasterModel(**r) for r in result_data]
return models return models
except Exception as e: except Exception as e:
# TODO: ファイルへの書き出しはloggerでやる logger.exception(f"DB Error : Exception={e}")
print(f"[ERROR] getOroshiData DB Error. ")
print(f"[ERROR] ErrorMessage: {e.args}")
raise e raise e
finally: finally:
self._database.disconnect() self._database.disconnect()

View File

@ -1,4 +1,3 @@
import logging
from typing import Callable from typing import Callable
from fastapi import Request, Response from fastapi import Request, Response
@ -9,9 +8,11 @@ from starlette import status
from src.depends.auth import (check_session_expired, get_current_session, from src.depends.auth import (check_session_expired, get_current_session,
verify_session) verify_session)
from src.error.exceptions import UnexpectedException from src.error.exceptions import UnexpectedException
from src.logging.get_logger import get_logger
from src.system_var import constants, environment from src.system_var import constants, environment
logger = logging.getLogger('uvicorn') logger = get_logger('medaca_router')
class MeDaCaRoute(APIRoute): class MeDaCaRoute(APIRoute):
"""アプリケーションのカスタムルーター """アプリケーションのカスタムルーター
@ -19,6 +20,7 @@ class MeDaCaRoute(APIRoute):
Args: Args:
APIRoute (APIRoute): FastAPIの標準APIRoute APIRoute (APIRoute): FastAPIの標準APIRoute
""" """
def get_route_handler(self) -> Callable: def get_route_handler(self) -> Callable:
"""前後処理を付加するルートハンドラーを返す """前後処理を付加するルートハンドラーを返す
@ -34,14 +36,11 @@ class MeDaCaRoute(APIRoute):
# 返却するルートハンドラーを定義。必ず非同期関数にする必要がある。 # 返却するルートハンドラーを定義。必ず非同期関数にする必要がある。
async def custom_route_handler(request: Request) -> Response: async def custom_route_handler(request: Request) -> Response:
try: try:
logger.info('pre routing process')
# 事前処理 # 事前処理
request = await self.pre_process_route(request) request = await self.pre_process_route(request)
# 本来のルーティング処理 # 本来のルーティング処理
logger.info('routing process')
response = await original_route_handler(request) response = await original_route_handler(request)
# 事後処理 # 事後処理
logger.info('post routing process')
return await self.post_process_route(request, response) return await self.post_process_route(request, response)
except HTTPException as e: except HTTPException as e:
raise e raise e
@ -60,6 +59,7 @@ class MeDaCaRoute(APIRoute):
Request: 加工後のRequestインスタンス Request: 加工後のRequestインスタンス
""" """
return request return request
async def post_process_route(self, request: Request, response: Response) -> Response: async def post_process_route(self, request: Request, response: Response) -> Response:
"""ルートハンドラーの事後処理 """ルートハンドラーの事後処理
@ -71,6 +71,7 @@ class MeDaCaRoute(APIRoute):
""" """
return response return response
class BeforeCheckSessionRoute(MeDaCaRoute): class BeforeCheckSessionRoute(MeDaCaRoute):
"""事前処理として、セッションチェックを行うルートハンドラー """事前処理として、セッションチェックを行うルートハンドラー
@ -86,12 +87,14 @@ class BeforeCheckSessionRoute(MeDaCaRoute):
verified_session = verify_session(checked_session) verified_session = verify_session(checked_session)
# セッションが有効でない場合、エラーにする # セッションが有効でない場合、エラーにする
if verified_session is None: if verified_session is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=constants.LOGOUT_REASON_SESSION_EXPIRED) raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
detail=constants.LOGOUT_REASON_SESSION_EXPIRED)
scope = request.scope scope = request.scope
scope['session'] = verified_session scope['session'] = verified_session
session_request = Request(receive=request.receive, scope=scope) session_request = Request(receive=request.receive, scope=scope)
return session_request return session_request
class AfterSetCookieSessionRoute(MeDaCaRoute): class AfterSetCookieSessionRoute(MeDaCaRoute):
"""事後処理として、セッションキーをcookieに設定するカスタムルートハンドラー """事後処理として、セッションキーをcookieに設定するカスタムルートハンドラー
@ -110,16 +113,18 @@ class AfterSetCookieSessionRoute(MeDaCaRoute):
response.set_cookie( response.set_cookie(
key='session', key='session',
value=session_key, value=session_key,
max_age=environment.SESSION_EXPIRE_MINUTE * 60, # cookieの有効期限は秒数指定なので、60秒をかける max_age=environment.SESSION_EXPIRE_MINUTE * 60, # cookieの有効期限は秒数指定なので、60秒をかける
secure=True, secure=True,
httponly=True httponly=True
) )
return response return response
class AuthenticatedRoute(BeforeCheckSessionRoute, AfterSetCookieSessionRoute): class AuthenticatedRoute(BeforeCheckSessionRoute, AfterSetCookieSessionRoute):
async def pre_process_route(self, request: Request): async def pre_process_route(self, request: Request):
request = await super().pre_process_route(request) request = await super().pre_process_route(request)
return request return request
async def post_process_route(self, request: Request, response: Response): async def post_process_route(self, request: Request, response: Response):
response = await super().post_process_route(request, response) response = await super().post_process_route(request, response)
return response return response

View File

@ -9,5 +9,6 @@ class BaseService(metaclass=ABCMeta):
REPOSITORIES: dict[str, BaseRepository] = {} REPOSITORIES: dict[str, BaseRepository] = {}
# 各サービスが依存するAWS APIクライアントクラスのマップ # 各サービスが依存するAWS APIクライアントクラスのマップ
CLIENTS: dict[str, AWSAPIClient] = {} CLIENTS: dict[str, AWSAPIClient] = {}
def __init__(self, repositories: dict[str, BaseRepository], clients: dict[str, AWSAPIClient]) -> None: def __init__(self, repositories: dict[str, BaseRepository], clients: dict[str, AWSAPIClient]) -> None:
pass pass

View File

@ -13,6 +13,7 @@ class BatchStatusService(BaseService):
} }
hdke_table_repository: HdkeTblRepository hdke_table_repository: HdkeTblRepository
__hdke_table_record: list[HdkeTblModel] = [] __hdke_table_record: list[HdkeTblModel] = []
def __init__(self, repositories: dict[str, BaseRepository], clients: dict[str, AWSAPIClient]) -> None: def __init__(self, repositories: dict[str, BaseRepository], clients: dict[str, AWSAPIClient]) -> None:
super().__init__(repositories, clients) super().__init__(repositories, clients)
self.hdke_table_repository = repositories['hdke_table_repository'] self.hdke_table_repository = repositories['hdke_table_repository']
@ -38,4 +39,3 @@ class BatchStatusService(BaseService):
# 日付マスタのレコードがない場合は例外とする # 日付マスタのレコードがない場合は例外とする
if len(self.__hdke_table_record) == 0: if len(self.__hdke_table_record) == 0:
raise DBException('日付テーブルのレコードが存在しません') raise DBException('日付テーブルのレコードが存在しません')

View File

@ -1,4 +1,4 @@
import os.path as path import os
import shutil import shutil
from datetime import datetime from datetime import datetime
@ -6,6 +6,7 @@ import pandas as pd
from src.aws.aws_api_client import AWSAPIClient from src.aws.aws_api_client import AWSAPIClient
from src.aws.s3 import S3Client from src.aws.s3 import S3Client
from src.logging.get_logger import get_logger
from src.model.internal.session import UserSession from src.model.internal.session import UserSession
from src.model.request.bio import BioModel from src.model.request.bio import BioModel
from src.model.view.bio_disp_model import BisDisplayModel from src.model.view.bio_disp_model import BisDisplayModel
@ -19,6 +20,8 @@ from src.repositories.wholesaler_master_repository import \
from src.services.base_service import BaseService from src.services.base_service import BaseService
from src.system_var import constants, environment from src.system_var import constants, environment
logger = get_logger('生物由来参照')
class BioViewService(BaseService): class BioViewService(BaseService):
REPOSITORIES = { REPOSITORIES = {
@ -26,15 +29,16 @@ class BioViewService(BaseService):
'phm_repository': PharmacyProductMasterRepository, 'phm_repository': PharmacyProductMasterRepository,
'bio_sales_repository': BioSalesViewRepository 'bio_sales_repository': BioSalesViewRepository
} }
CLIENTS = { CLIENTS = {
's3_client': S3Client 's3_client': S3Client
} }
whs_repository: WholesalerMasterRepository whs_repository: WholesalerMasterRepository
phm_repository: PharmacyProductMasterRepository phm_repository: PharmacyProductMasterRepository
bio_sales_repository: BioSalesViewRepository bio_sales_repository: BioSalesViewRepository
s3_client: S3Client s3_client: S3Client
def __init__(self, repositories: dict[str, BaseRepository], clients: dict[str, AWSAPIClient]) -> None: def __init__(self, repositories: dict[str, BaseRepository], clients: dict[str, AWSAPIClient]) -> None:
super().__init__(repositories, clients) super().__init__(repositories, clients)
self.whs_repository = repositories['whs_repository'] self.whs_repository = repositories['whs_repository']
@ -45,7 +49,7 @@ class BioViewService(BaseService):
def prepare_bio_view( def prepare_bio_view(
self, self,
session: UserSession session: UserSession
) ->BioViewModel: ) -> BioViewModel:
# 卸リストを取得 # 卸リストを取得
wholesalers = self.whs_repository.fetch_all() wholesalers = self.whs_repository.fetch_all()
# 製品リストを取得 # 製品リストを取得
@ -67,12 +71,60 @@ class BioViewService(BaseService):
def search_download_bio_data(self, search_params: BioModel): def search_download_bio_data(self, search_params: BioModel):
# 生物由来データをダウンロードするために、DBから検索した結果をデータフレームに変換 # 生物由来データをダウンロードするために、DBから検索した結果をデータフレームに変換
bio_sales_data_frame = self.bio_sales_repository.fetch_as_data_frame(parameter=search_params) bio_sales_data_frame, query = self.bio_sales_repository.fetch_as_data_frame(parameter=search_params)
return bio_sales_data_frame return bio_sales_data_frame, query
def write_excel_file(self, data_frame: pd.DataFrame, user_id: str, timestamp: datetime): def write_access_log(
self,
query: str,
parameters: BioModel,
user_id: str,
timestamp: datetime,
download_file_name: str
):
# アクセスログを書き出し、S3に保管する
access_log_file_name = f'BioAccessLog_{user_id}_{timestamp:%Y%m%d%H%M%S%f}.log'
# アクセスログファイル出力用のロガーを生成
import logging
access_logger = logging.getLogger(access_log_file_name)
level = logging.getLevelName(environment.LOG_LEVEL)
if not isinstance(level, int):
level = logging.INFO
access_logger.setLevel(level)
access_log_file_path = os.path.join(constants.BIO_TEMPORARY_FILE_DIR_PATH, access_log_file_name)
if not access_logger.hasHandlers():
access_log_handler = logging.FileHandler(access_log_file_path)
access_logger.addHandler(access_log_handler)
formatter = logging.Formatter(
'[%(levelname)s]\t%(asctime)s\t%(message)s',
'%Y-%m-%d %H:%M:%S'
)
for handler in logger.handlers:
handler.setFormatter(formatter)
# SQL文を出力
sql_message = f'ユーザーID: {user_id} SQL: {query}\t{download_file_name}'
access_logger.info(sql_message)
# 標準出力にも書き出す
logger.info(sql_message)
# 検索パラメータを1行ずつ書き出す
for param_key, param_value in parameters.dict().items():
if param_value is None or len(param_value) == 0:
continue
parameter_message = f'ユーザーID: {user_id} Value: {param_key} = {param_value}\t{download_file_name}'
logger.info(parameter_message)
access_logger.info(parameter_message)
# S3にアップロード
self.upload_bio_access_log_file(access_log_file_path)
def write_excel_file(self, data_frame: pd.DataFrame, user_id: str, download_file_name: str):
# Excelに書き込み # Excelに書き込み
output_file_path = path.join(constants.BIO_TEMPORARY_FILE_DIR_PATH, f'Result_{user_id}_{timestamp:%Y%m%d%H%M%S%f}.xlsx') output_file_path = os.path.join(constants.BIO_TEMPORARY_FILE_DIR_PATH, download_file_name)
# テンプレートファイルをコピーして出力ファイルの枠だけを作る # テンプレートファイルをコピーして出力ファイルの枠だけを作る
shutil.copyfile( shutil.copyfile(
@ -87,12 +139,12 @@ class BioViewService(BaseService):
# DF内のヘッダと連番を書き込みたくない場合、`header`と`index`をFalseに指定する。 # DF内のヘッダと連番を書き込みたくない場合、`header`と`index`をFalseに指定する。
# `startrow`と`startcol`で、Excelの書き込み位置を決定する。省略した場合はA1セルから書く。 # `startrow`と`startcol`で、Excelの書き込み位置を決定する。省略した場合はA1セルから書く。
data_frame.to_excel(writer, header=False, index=False, startrow=1, startcol=0) data_frame.to_excel(writer, header=False, index=False, startrow=1, startcol=0)
return output_file_path return output_file_path
def write_csv_file(self, data_frame: pd.DataFrame, user_id: str, header: list[str], timestamp: datetime): def write_csv_file(self, data_frame: pd.DataFrame, user_id: str, header: list[str], download_file_name: str):
# csvに書き込み # csvに書き込み
output_file_path = path.join(constants.BIO_TEMPORARY_FILE_DIR_PATH, f'Result_{user_id}_{timestamp:%Y%m%d%H%M%S%f}.csv') output_file_path = os.path.join(constants.BIO_TEMPORARY_FILE_DIR_PATH, download_file_name)
# 横長のDataFrameとするため、ヘッダーの加工処理 # 横長のDataFrameとするため、ヘッダーの加工処理
header_data = {} header_data = {}
for df_column, header_column in zip(data_frame.columns, header): for df_column, header_column in zip(data_frame.columns, header):
@ -107,13 +159,28 @@ class BioViewService(BaseService):
def upload_bio_data_file(self, local_file_path: str) -> None: def upload_bio_data_file(self, local_file_path: str) -> None:
bucket_name = environment.BIO_ACCESS_LOG_BUCKET bucket_name = environment.BIO_ACCESS_LOG_BUCKET
# TODO: フォルダを変える # TODO: ファイルパスにYYYY/MM/DDを加える
file_key =f'bio/{path.basename(local_file_path)}' file_key = f'data/{os.path.basename(local_file_path)}'
self.s3_client.upload_file(local_file_path, bucket_name, file_key) self.s3_client.upload_file(local_file_path, bucket_name, file_key)
def generate_download_file_url(self, local_file_path:str, user_id: str, kind: str) -> str: # アップロード後、ローカルからは削除する
self.delete_local_file(local_file_path)
def upload_bio_access_log_file(self, local_file_path: str) -> None:
bucket_name = environment.BIO_ACCESS_LOG_BUCKET bucket_name = environment.BIO_ACCESS_LOG_BUCKET
# TODO: フォルダを変える # TODO: ファイルパスにYYYY/MM/DDを加える
file_key = f'bio/{path.basename(local_file_path)}' file_key = f'log/{os.path.basename(local_file_path)}'
self.s3_client.upload_file(local_file_path, bucket_name, file_key)
# アップロード後、ローカルからは削除する
self.delete_local_file(local_file_path)
def generate_download_file_url(self, local_file_path: str, user_id: str, kind: str) -> str:
bucket_name = environment.BIO_ACCESS_LOG_BUCKET
# TODO: ファイルパスにYYYY/MM/DDを加える
file_key = f'data/{os.path.basename(local_file_path)}'
download_filename = f'{user_id}_生物由来卸販売データ.{kind}' download_filename = f'{user_id}_生物由来卸販売データ.{kind}'
return self.s3_client.generate_presigned_url(bucket_name, file_key, download_filename) return self.s3_client.generate_presigned_url(bucket_name, file_key, download_filename)
def delete_local_file(self, local_file_path: str):
os.remove(local_file_path)

View File

@ -17,7 +17,7 @@ class LoginService(BaseService):
REPOSITORIES = { REPOSITORIES = {
'user_repository': UserMasterRepository 'user_repository': UserMasterRepository
} }
CLIENTS = { CLIENTS = {
'cognito_client': CognitoClient 'cognito_client': CognitoClient
} }
@ -41,10 +41,10 @@ class LoginService(BaseService):
raise e raise e
return JWTToken(id_token, refresh_token) return JWTToken(id_token, refresh_token)
def login_with_security_code(self, code: str) -> JWTToken: def login_with_security_code(self, code: str) -> JWTToken:
return JWTToken.request(code) return JWTToken.request(code)
def logged_in_user(self, user_id): def logged_in_user(self, user_id):
user_record: UserMasterModel = self.user_repository.fetch_one({'user_id': user_id}) user_record: UserMasterModel = self.user_repository.fetch_one({'user_id': user_id})
return user_record return user_record

View File

@ -1,15 +1,19 @@
from src.logging.get_logger import get_logger
from src.model.internal.session import UserSession from src.model.internal.session import UserSession
logger = get_logger('セッション管理')
def set_session(session: UserSession) -> str: def set_session(session: UserSession) -> str:
session.save() session.save()
return session.session_key return session.session_key
def get_session(key: str) -> UserSession: def get_session(key: str) -> UserSession:
try: try:
session = UserSession.get(hash_key=key, consistent_read=True) session = UserSession.get(hash_key=key, consistent_read=True)
return session return session
except UserSession.DoesNotExist as e: except UserSession.DoesNotExist as e:
print(e) logger.debug(f'セッション取得失敗:{e}')
return None return None

View File

@ -3,7 +3,7 @@
// 戻るボタンの関数 // 戻るボタンの関数
// 機能概要:メニュー画面に遷移する // 機能概要:メニュー画面に遷移する
function backToMenu(){ function backToMenu(){
location.href = "/menu"; location.href = "/menu/";
} }
// クリアボタンの関数 // クリアボタンの関数
@ -17,7 +17,7 @@ function clr() {
formInput.value = ""; formInput.value = "";
} }
} }
// 検索ボタンを再度非活性にする // 検索ボタンを再度非活性にする
formBtDisabled(); formBtDisabled();
} }
@ -35,7 +35,7 @@ function formBtDisabled(buttonId='search_bt', formId='search', all=false) {
const checkTargetValueLength = formInputElements const checkTargetValueLength = formInputElements
.filter((elem) => elem.name.startsWith('ctrl_')) .filter((elem) => elem.name.startsWith('ctrl_'))
.map((elem) => elem.value.length) .map((elem) => elem.value.length)
// 活性、非活性の判断 // 活性、非活性の判断
let validFlg = false; let validFlg = false;
if (all) { if (all) {
@ -75,7 +75,7 @@ function selectDropDowList(id, selectedName){
options[i].selected = true; options[i].selected = true;
} }
}; };
} }
} }
/** /**
@ -98,9 +98,9 @@ function enableDatePicker() {
function autoModifyDate($this){ function autoModifyDate($this){
// 日付フォーマットチェック // 日付フォーマットチェック
if($this.value === "" || if($this.value === "" ||
(!$this.value.match(/^\d{4}\/\d{2}\/\d{2}$/) && !$this.value.match(/^\d{4}\d{2}\d{2}$/))) (!$this.value.match(/^\d{4}\/\d{2}\/\d{2}$/) && !$this.value.match(/^\d{4}\d{2}\d{2}$/)))
{ {
$this.value = ""; $this.value = "";
return; return;
} }

View File

@ -5,40 +5,40 @@ BIO_EXCEL_TEMPLATE_FILE_PATH = path.join(BIO_TEMPORARY_FILE_DIR_PATH, 'BioData_t
BIO_EXTRACT_COLUMNS = [ BIO_EXTRACT_COLUMNS = [
'slip_org_kbn', 'slip_org_kbn',
'slip_mgt_no', 'slip_mgt_num',
'rec_ymd', 'rec_ymd',
'rec_whs_cd', 'rec_whs_cd',
'rec_whs_sub_cd', 'rec_whs_sub_cd',
'whs_nm', 'whs_name',
'rec_whs_org_cd', 'rec_whs_org_cd',
'rec_urag_no', 'rec_urag_num',
'rev_hsdnymd_srk', 'rev_hsdnymd_srk',
'rec_tran_kbn', 'rec_tran_kbn',
'tran_kbn_nm', 'tran_kbn_name',
'mkr_cd', 'mkr_cd',
'rec_comm_cd', 'rec_comm_cd',
'comm_nm', 'product_name',
'whs_rep_comm_nm', 'whs_rep_comm_name',
'nnsk_cd', 'nonyu_fcl_cd',
'rec_nnskfcl_nm', 'rec_nonyu_fcl_name',
'whs_rep_nnskfcl_nm', 'whs_rep_nonyu_fcl_name',
'rec_nnsk_fcl_addr', 'rec_nonyu_fcl_addr',
'whs_rep_nnsk_fcl_addr', 'whs_rep_nonyu_fcl_addr',
'rec_lot_num', 'rec_lot_num',
'amt_fugo', 'amt_fugo',
'expr_dt', 'expr_dt',
'data_kbn', 'data_kbn',
'lot_no_err_flg', 'lot_num_err_flg',
'bef_slip_mgt_no', 'bef_slip_mgt_num',
'ins_usr', 'ins_usr',
'ins_dt', 'ins_dt',
'inst_cd', 'inst_cd',
'inst_name_form', 'inst_name_form',
'address', 'address',
'tel_no', 'tel_num',
'v_whs_cd', 'v_whs_cd',
'v_whsorg_cd', 'v_whsorg_cd',
'whs_org_nm', 'whs_org_name',
'v_tran_cd', 'v_tran_cd',
'iko_flg' 'iko_flg'
] ]

View File

@ -21,3 +21,5 @@ DB_SCHEMA = os.environ['DB_SCHEMA']
BIO_SEARCH_RESULT_MAX_COUNT = int(os.environ['BIO_SEARCH_RESULT_MAX_COUNT']) BIO_SEARCH_RESULT_MAX_COUNT = int(os.environ['BIO_SEARCH_RESULT_MAX_COUNT'])
SEARCH_RESULT_MAX_COUNT = int(os.environ['SEARCH_RESULT_MAX_COUNT']) SEARCH_RESULT_MAX_COUNT = int(os.environ['SEARCH_RESULT_MAX_COUNT'])
SESSION_EXPIRE_MINUTE = int(os.environ['SESSION_EXPIRE_MINUTE']) SESSION_EXPIRE_MINUTE = int(os.environ['SESSION_EXPIRE_MINUTE'])
LOG_LEVEL = os.environ.get('LOG_LEVEL', 'INFO')

View File

@ -25,7 +25,7 @@
<tbody> <tbody>
<tr> <tr>
<td class="back_bt" colspan="7" align="right"> <td class="back_bt" colspan="7" align="right">
<input type="button" name="back" value="メニューへ" onclick="location.href='/menu'"> <input type="button" name="back" value="メニューへ" onclick="location.href='/menu/'">
</td> </td>
</tr> </tr>
<tr> <tr>
@ -34,7 +34,7 @@
<select class="text search_dropdown" name="ctrl_wholesaler" value="" onChange="formBtDisabled();"> <select class="text search_dropdown" name="ctrl_wholesaler" value="" onChange="formBtDisabled();">
<option value=""></option> <option value=""></option>
{% for whs_name in bio.display_wholesaler_names() %} {% for whs_name in bio.display_wholesaler_names() %}
<option <option
value="{{whs_name}}" value="{{whs_name}}"
{{bio.is_selected_whs_name(whs_name)}}> {{bio.is_selected_whs_name(whs_name)}}>
{{whs_name}} {{whs_name}}
@ -58,7 +58,7 @@
onblur="autoModifyDate(this)" onblur="autoModifyDate(this)"
> >
<input type="text" id="shoribi_end" class="date_picker" name="ctrl_rec_ymd_to" <input type="text" id="shoribi_end" class="date_picker" name="ctrl_rec_ymd_to"
value="{{bio.is_input_rec_ymd_to()}}" value="{{bio.is_input_rec_ymd_to()}}"
onchange="formBtDisabled()" onchange="formBtDisabled()"
onblur="autoModifyDate(this)" onblur="autoModifyDate(this)"
@ -68,8 +68,8 @@
<tr> <tr>
<td>ロット番号:</td> <td>ロット番号:</td>
<td class="search_tb"> <td class="search_tb">
<input class="text" type="text" id="lot_tb" name="ctrl_rec_lot_num" style="ime-mode:disabled" maxlength="10" <input class="text" type="text" id="lot_tb" name="ctrl_rec_lot_num" style="ime-mode:disabled" maxlength="10"
value="{{bio.is_input_lot_num()}}" value="{{bio.is_input_lot_num()}}"
oninput="checkSpaceForm(this); checkAimaiSearhForm(this); formBtDisabled()"> oninput="checkSpaceForm(this); checkAimaiSearhForm(this); formBtDisabled()">
</td> </td>
<td>データ区分:</td> <td>データ区分:</td>
@ -85,9 +85,9 @@
<select class="text search_dropdown" name="ctrl_maker_cd" value="" onChange="formBtDisabled();"> <select class="text search_dropdown" name="ctrl_maker_cd" value="" onChange="formBtDisabled();">
<option value=""></option> <option value=""></option>
{% for phm in bio.phm_models %} {% for phm in bio.phm_models %}
<option <option
value="{{phm['mkr_cd_nm']}}" {{bio.is_selected_maker_cd(phm['mkr_cd_nm'])}}> value="{{phm['mkr_cd_name']}}" {{bio.is_selected_maker_cd(phm['mkr_cd_name'])}}>
{{phm['mkr_cd_nm']}} {{phm['mkr_cd_name']}}
</option> </option>
{% endfor %} {% endfor %}
</select> </select>
@ -96,13 +96,13 @@
<tr> <tr>
<td>発伝年月日:</td> <td>発伝年月日:</td>
<td colspan="3"> <td colspan="3">
<input type="text" id="shoribi_start" class="date_picker" name="ctrl_rev_hsdnymd_srk_from" <input type="text" id="shoribi_start" class="date_picker" name="ctrl_rev_hsdnymd_srk_from"
value="{{bio.is_input_rev_hsdnymd_srk_from()}}" value="{{bio.is_input_rev_hsdnymd_srk_from()}}"
onchange="formBtDisabled()" onchange="formBtDisabled()"
onblur="autoModifyDate(this)" onblur="autoModifyDate(this)"
> >
<input type="text" id="shoribi_start" class="date_picker" name="ctrl_rev_hsdnymd_srk_to" <input type="text" id="shoribi_start" class="date_picker" name="ctrl_rev_hsdnymd_srk_to"
value="{{bio.is_input_rev_hsdnymd_srk_to()}}" value="{{bio.is_input_rev_hsdnymd_srk_to()}}"
onchange="formBtDisabled()" onchange="formBtDisabled()"
onblur="autoModifyDate(this)" onblur="autoModifyDate(this)"
@ -189,20 +189,20 @@
<!-- CSV/Excelダウンロードボタン。ここはajaxでやってる --> <!-- CSV/Excelダウンロードボタン。ここはajaxでやってる -->
<script type="text/javascript"> <script type="text/javascript">
function download(filename, kind) { function download(filename, ext) {
$(`#loading`).toggle() $(`#loading`).toggle()
// 検索パラメータを取得 // 検索パラメータを取得
const formData = $('#bio_search').serializeArray() const formData = $('#bio_search').serializeArray()
// リクエスト用に加工 // リクエスト用に加工
const searchParams = {} const searchParams = {}
for (let i = 0; i < formData.length; i++) { for (let i = 0; i < formData.length; i++) {
searchParams[formData[i].name] = formData[i].value searchParams[formData[i].name] = formData[i].value
} }
// ダウンロード固有のパラメータを設定 // ダウンロード固有のパラメータを設定
const downloadRequestParams = { const downloadRequestParams = {
user_id: '{{bio.user_id}}', user_id: '{{bio.user_id}}',
kind: kind, ext: ext,
} }
$.extend(downloadRequestParams, searchParams) $.extend(downloadRequestParams, searchParams)
@ -216,26 +216,26 @@
success: function(data) { success: function(data) {
try { try {
if (data.status === 'batch_processing') { if (data.status === 'batch_processing') {
location.href('/logout?reason=batchProcessing') location.href('/logout/?reason=batchProcessing')
return return
} }
if (data.status === 'session_expired') { if (data.status === 'session_expired') {
location.href('/logout?reason=session_expired') location.href('/logout/?reason=session_expired')
return return
} }
// データが存在しない場合の考慮が必要 // データが存在しない場合の考慮が必要
/**if (data.download_url === '') { /**if (data.download_url === '') {
// 予期せぬエラーが発生した場合 // 予期せぬエラーが発生した場合
$(`#loading`).toggle(); $(`#loading`).toggle();
$(`#modal_${kind}`).modal('toggle'); $(`#modal_${ext}`).modal('toggle');
$(`#ErrorModal_Unexpected`).modal('toggle'); $(`#ErrorModal_Unexpected`).modal('toggle');
} }
*/ */
// S3の期限付き署名URLがレスポンスされる // S3の期限付き署名URLがレスポンスされる
window.location.href = data.download_url; window.location.href = data.download_url;
$(`#loading`).toggle(); $(`#loading`).toggle();
$(`#modal_${kind}`).modal('toggle'); $(`#modal_${ext}`).modal('toggle');
} catch (e) { } catch (e) {
alert("エラーが発生しました。:" + e.message); alert("エラーが発生しました。:" + e.message);
} }
@ -244,20 +244,20 @@
const responseJson = jqXHR.responseJSON const responseJson = jqXHR.responseJSON
if (responseJson?.detail?.error === 'db_error') { if (responseJson?.detail?.error === 'db_error') {
$(`#loading`).toggle(); $(`#loading`).toggle();
$(`#modal_${kind}`).modal('toggle'); $(`#modal_${ext}`).modal('toggle');
$(`#ErrorModal_DB`).modal('toggle'); $(`#ErrorModal_DB`).modal('toggle');
return return
} }
if (responseJson?.detail?.error === 'aws_error') { if (responseJson?.detail?.error === 'aws_error') {
$(`#loading`).toggle(); $(`#loading`).toggle();
$(`#modal_${kind}`).modal('toggle'); $(`#modal_${ext}`).modal('toggle');
$(`#ErrorModal_AWS`).modal('toggle'); $(`#ErrorModal_AWS`).modal('toggle');
return return
} }
// 予期せぬエラーが発生した場合 // 予期せぬエラーが発生した場合
$(`#loading`).toggle(); $(`#loading`).toggle();
$(`#modal_${kind}`).modal('toggle'); $(`#modal_${ext}`).modal('toggle');
$(`#ErrorModal_Unexpected`).modal('toggle'); $(`#ErrorModal_Unexpected`).modal('toggle');
return return
} }
@ -275,7 +275,6 @@
return return
} }
$(".pagination").pagination({ $(".pagination").pagination({
// 以下はテスト用コード
dataSource: function(done) { dataSource: function(done) {
done(searchResultData) done(searchResultData)
}, },
@ -292,35 +291,35 @@
} }
}) })
}); });
function pagination_content(datas) { function pagination_content(datas) {
const display_keys = [ const display_keys = [
'slip_org_kbn', 'slip_org_kbn',
'slip_mgt_no', 'slip_mgt_num',
'rec_ymd', 'rec_ymd',
'rec_whs_cd', 'rec_whs_cd',
'rec_whs_sub_cd', 'rec_whs_sub_cd',
'whs_nm', 'whs_name',
'rec_whs_org_cd', 'rec_whs_org_cd',
'rec_urag_no', 'rec_urag_num',
'rev_hsdnymd_srk', 'rev_hsdnymd_srk',
'rec_tran_kbn', 'rec_tran_kbn',
'tran_kbn_nm', 'tran_kbn_name',
'mkr_cd', 'mkr_cd',
'rec_comm_cd', 'rec_comm_cd',
'comm_nm', 'product_name',
'whs_rep_comm_nm', 'whs_rep_comm_nm',
'nnsk_cd', 'nonyu_fcl_cd',
'rec_nnskfcl_nm', 'rec_nonyu_fcl_name',
'whs_rep_nnskfcl_nm', 'whs_rep_nnskfcl_nm',
'rec_nnsk_fcl_addr', 'rec_nonyu_fcl_addr',
'whs_rep_nnsk_fcl_addr', 'whs_rep_nnsk_fcl_addr',
'rec_lot_num', 'rec_lot_num',
'amt_fugo', 'amt_fugo',
'expr_dt', 'expr_dt',
'data_kbn', 'data_kbn',
'lot_no_err_flg', 'lot_no_err_flg',
'bef_slip_mgt_no', 'bef_slip_mgt_num',
'ins_usr', 'ins_usr',
'ins_dt', 'ins_dt',
'inst_cd', 'inst_cd',
@ -329,11 +328,11 @@
'tel_no', 'tel_no',
'v_whs_cd', 'v_whs_cd',
'v_whsorg_cd', 'v_whsorg_cd',
'whs_org_nm', 'whs_org_name',
'v_tran_cd', 'v_tran_cd',
'iko_flg', 'iko_flg',
]; ];
const tableRow = document.createElement('tr') const tableRow = documen.sendt.createElement('tr')
return datas.map(function (data) { return datas.map(function (data) {
return ` return `
<tr class="result_data"> <tr class="result_data">
@ -346,7 +345,7 @@
</script> </script>
<!-- Excel出力モーダル --> <!-- Excel出力モーダル -->
{% with {% with
modal_id='modal_xlsx', modal_id='modal_xlsx',
modal_title='確認', modal_title='確認',
message='生物由来卸販売データ一覧をExcel出力しますか', message='生物由来卸販売データ一覧をExcel出力しますか',
@ -370,7 +369,7 @@
{% include '_modal.html' %} {% include '_modal.html' %}
{% endwith %} {% endwith %}
<!-- CSV出力モーダル --> <!-- CSV出力モーダル -->
{% with {% with
modal_id='modal_csv', modal_id='modal_csv',
modal_title='確認', modal_title='確認',
message='生物由来卸販売データ一覧をCSV出力しますか', message='生物由来卸販売データ一覧をCSV出力しますか',
@ -394,36 +393,36 @@
{% include '_modal.html' %} {% include '_modal.html' %}
{% endwith %} {% endwith %}
<!-- AWS環境異常エラーモーダル --> <!-- AWS環境異常エラーモーダル -->
{% with {% with
modal_id='ErrorModal_AWS', modal_id='ErrorModal_AWS',
modal_title='エラー', modal_title='エラー',
message='AWS環境に異常が発生しました。管理者にお問い合わせください。', message='AWS環境に異常が発生しました。管理者にお問い合わせください。',
icon_key='warning', icon_key='warning',
modal_close_event='location.href="/logout?reason="', modal_close_event='location.href="/logout/?reason="',
buttons = [ buttons = [
{ {
'id': 'error_modal_aws', 'id': 'error_modal_aws',
'class': 'btn btn-primary', 'class': 'btn btn-primary',
'text': 'OK', 'text': 'OK',
'onclick_event': 'location.href="/logout?reason=''"' 'onclick_event': 'location.href="/logout/?reason=''"'
} }
] ]
%} %}
{% include '_modal.html' %} {% include '_modal.html' %}
{% endwith %} {% endwith %}
<!-- DB接続失敗エラーモーダル --> <!-- DB接続失敗エラーモーダル -->
{% with {% with
modal_id='ErrorModal_DB', modal_id='ErrorModal_DB',
modal_title='エラー', modal_title='エラー',
message='DB接続に失敗しました。管理者にお問い合わせください。', message='DB接続に失敗しました。管理者にお問い合わせください。',
icon_key='warning', icon_key='warning',
modal_close_event='location.href="/logout?reason="', modal_close_event='location.href="/logout/?reason="',
buttons = [ buttons = [
{ {
'id': 'error_modal_db', 'id': 'error_modal_db',
'class': 'btn btn-primary', 'class': 'btn btn-primary',
'text': 'OK', 'text': 'OK',
'onclick_event': 'location.href="/logout?reason=''"' 'onclick_event': 'location.href="/logout/?reason=''"'
} }
] ]
%} %}
@ -431,18 +430,18 @@
{% endwith %} {% endwith %}
<!-- エラーモーダル --> <!-- エラーモーダル -->
{% with {% with
modal_id='ErrorModal_Unexpected', modal_id='ErrorModal_Unexpected',
modal_title='エラー', modal_title='エラー',
message='サーバーエラーが発生しました。管理者にお問い合わせください。', message='サーバーエラーが発生しました。管理者にお問い合わせください。',
icon_key='warning', icon_key='warning',
modal_close_event='location.href="/logout?reason="', modal_close_event='location.href="/logout/?reason="',
buttons = [ buttons = [
{ {
'id': 'error_modal_unexpected', 'id': 'error_modal_unexpected',
'class': 'btn btn-primary', 'class': 'btn btn-primary',
'text': 'OK', 'text': 'OK',
'onclick_event': 'location.href="/logout?reason=''"' 'onclick_event': 'location.href="/logout/?reason=''"'
} }
] ]
%} %}

View File

@ -22,7 +22,7 @@
{% else %} {% else %}
<div class="notUseBioMsg">生物由来データ参照は <br> 日次バッチ処理中のため利用出来ません</div> <div class="notUseBioMsg">生物由来データ参照は <br> 日次バッチ処理中のため利用出来ません</div>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if menu.is_available_master_maintenance_menu() %} {% if menu.is_available_master_maintenance_menu() %}
{% if not menu.is_batch_processing() %} {% if not menu.is_batch_processing() %}
<a href="{{masterMaintePath}}" class="btn btn-primary btn-lg btn_width">マスターメンテメニュー</a><br><br> <a href="{{masterMaintePath}}" class="btn btn-primary btn-lg btn_width">マスターメンテメニュー</a><br><br>
@ -30,7 +30,7 @@
<div class="notUseBioMsg"> マスターメンテメニューは <br> 日次バッチ処理中のため利用出来ません </div> <div class="notUseBioMsg"> マスターメンテメニューは <br> 日次バッチ処理中のため利用出来ません </div>
{% endif %} {% endif %}
{% endif %} {% endif %}
<br><br><a href="/logout?reason=do_logout" class="btn btn-info btn-lg btn_width">Logout</a> <br><br><a href="/logout/?reason=do_logout" class="btn btn-info btn-lg btn_width">Logout</a>
</div> </div>
</body> </body>
</html> </html>

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="ja">
<head>
{% with subtitle = 'サンプルファイル送信' %}
{% include '_header.html' %}
{% endwith %}
<link href="/static/css/menuStyle.css" rel="stylesheet">
</head>
<body>
<div class="container-fluid text-center background">
<h1>サンプルファイル送信</h1>
<br><br>
<form name="login" class="text-center" method="post" action="/sample/" enctype="multipart/form-data">
<div class="form-group">
<label for="file">Choose File.</label>
<input type="file" name="file" class="form-control-file" required>
</div>
<div class="form-group">
<label for="message">メッセージ</label>
<input type="text" name="message" class="form-control">
</div>
<input type="submit" id="login_button" name="login" class="btn btn-info btn-lg btn_width" id="submit" value="送信">
</form>
</div>
</body>
</html>

View File

@ -4,11 +4,18 @@ import html
def sanitize(cls): def sanitize(cls):
class SanitizedClass(cls): original_init = cls.__init__
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) def new_init(self, *args, **kwargs):
for prop_name, prop_value in self.__dict__.items(): # オリジナルの __init__ メソッドを呼び出してインスタンスを初期化
if isinstance(prop_value, str): sanitized_kwargs = {**kwargs}
sanitized_value = html.escape(prop_value, quote=True) for key, value in kwargs.items():
setattr(self, prop_name, sanitized_value) if isinstance(value, str):
return SanitizedClass # 文字列の場合はサニタイズ処理を行うHTMLタグをエスケープ
sanitized_value = html.escape(value, quote=True)
sanitized_kwargs[key] = sanitized_value
original_init(self, *args, **sanitized_kwargs)
cls.__init__ = new_init
return cls