Merge commit 'c835f1e0e0e98a06dbec3984f2c966b9ce48aa6e' into feature-NEWDWH2021-1072-fix-webapp

This commit is contained in:
shimoda.m@nds-tyo.co.jp 2023-06-02 11:54:40 +09:00
commit 20f64139b8
54 changed files with 1027 additions and 512 deletions

View File

@ -23,3 +23,4 @@ AUTHORIZE_ENDPOINT=oauth2/authorize
TOKEN_ENDPOINT=oauth2/token TOKEN_ENDPOINT=oauth2/token
BIO_SEARCH_RESULT_MAX_COUNT=35000 BIO_SEARCH_RESULT_MAX_COUNT=35000
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,94 +2,76 @@
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') print('Bio data not found')
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) print('S3 access error', e.args)
raise HTTPException( raise HTTPException(
@ -114,8 +96,64 @@ 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)
# TODO: ファイルにも出力する
except DBException as e:
logger.exception(f'DB Error: {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
@ -25,6 +24,8 @@ router.route_class = AfterSetCookieSessionRoute
######################### #########################
# 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 +40,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,20 +55,22 @@ 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) print(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:
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
@ -92,9 +96,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 +107,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)
@ -114,7 +118,7 @@ def sso_authorize(
try: try:
# トークン検証 # トークン検証
verified_token = jwt_token.verify_token() verified_token = jwt_token.verify_token()
except JWTTokenVerifyException as e: except JWTTokenVerifyException:
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を取得
@ -143,7 +147,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

@ -16,13 +16,14 @@ class SQLCondition:
self.column = column self.column = column
self.operator = operator self.operator = operator
self.param = param self.param = param
self.literal=literal self.literal = literal
def apply(self): def apply(self):
# literalがFalseならプレースホルダー。Trueだったならは固定値。 # literalがFalseならプレースホルダー。Trueだったならは固定値。
param = f':{self.param}' if self.literal is False else self.param param = f':{self.param}' if self.literal is False else self.param
return f' {self.column} {self.operator} {param}' return f' {self.column} {self.operator} {param}'
# 定数 # 定数
EQ = '=' EQ = '='
NE = '<>' NE = '<>'
@ -32,4 +33,4 @@ GE = '>='
LE = '<=' LE = '<='
LIKE = 'LIKE' LIKE = 'LIKE'
IS = 'IS' IS = 'IS'
IS_NOT = 'IS NOT' IS_NOT = 'IS NOT'

View File

@ -17,26 +17,28 @@ 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)

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

@ -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

@ -5,7 +5,9 @@ from fastapi.staticfiles import StaticFiles
from starlette import status from starlette import status
import src.static as static import src.static as static
from src.controller import bio, bio_download, healthcheck, login, logout, menu from src.controller import (bio, bio_download, healthcheck, login, logout,
menu, root)
from src.controller.sample_send_file import router as sample_router
from src.core import tasks from src.core import tasks
from src.error.exception_handler import http_exception_handler from src.error.exception_handler import http_exception_handler
from src.error.exceptions import UnexpectedException from src.error.exceptions import UnexpectedException
@ -14,6 +16,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')
# ログアウト関連のルーター # ログアウト関連のルーター
@ -28,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'

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

@ -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,6 @@ 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): def is_maintenance_user(self):
return self.user_flg == '1' 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,5 +1,4 @@
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

View File

@ -3,24 +3,24 @@ from src.repositories.base_repository import BaseRepository
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
""" """

View File

@ -1,5 +1,4 @@
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
@ -27,4 +26,4 @@ class UserMasterRepository(BaseRepository):
print(f"[ERROR] 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()

View File

@ -3,23 +3,27 @@ from src.repositories.base_repository import BaseRepository
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]:

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

@ -6,6 +6,7 @@ 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)

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

@ -19,4 +19,6 @@ DB_PASSWORD = os.environ['DB_PASSWORD']
DB_SCHEMA = os.environ['DB_SCHEMA'] 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'])
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

@ -89,8 +89,8 @@
<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>
@ -192,7 +192,7 @@
<!-- 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()
// 検索パラメータを取得 // 検索パラメータを取得
@ -205,7 +205,7 @@
// ダウンロード固有のパラメータを設定 // ダウンロード固有のパラメータを設定
const downloadRequestParams = { const downloadRequestParams = {
user_id: '{{bio.user_id}}', user_id: '{{bio.user_id}}',
kind: kind, ext: ext,
} }
$.extend(downloadRequestParams, searchParams) $.extend(downloadRequestParams, searchParams)
@ -231,14 +231,14 @@
/**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);
} }
@ -247,20 +247,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
} }
@ -278,7 +278,6 @@
return return
} }
$(".pagination").pagination({ $(".pagination").pagination({
// 以下はテスト用コード
dataSource: function(done) { dataSource: function(done) {
done(searchResultData) done(searchResultData)
}, },
@ -299,31 +298,31 @@
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',
@ -332,7 +331,7 @@
'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',
]; ];

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