diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 27d4796..9496b22 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -36,7 +36,7 @@ jobs: - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names - flake8 pyms --show-source --statistics --statistics + flake8 pyms --show-source --statistics - name: Lint with pylint run: | pylint --rcfile=pylintrc pyms diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..740d90c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,7 @@ +repos: + - repo: https://github.com/ambv/black + rev: stable + hooks: + - id: black + files: . + diff --git a/MANIFEST.in b/MANIFEST.in index 2a5c7fe..e51798a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,5 +4,6 @@ include requirements-tests.txt recursive-include pyms * recursive-exclude tests * recursive-exclude examples * +recursive-exclude docker * prune tests prune examples \ No newline at end of file diff --git a/Pipfile b/Pipfile index bce032c..6673b6f 100644 --- a/Pipfile +++ b/Pipfile @@ -4,7 +4,10 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] -py-ms = {editable = true,extras = ["tests"], path = "."} +py-ms = {editable = true,extras = ["tests"],path = "."} [packages] -py-ms = {editable = true,extras = ["all"], path = "."} +py-ms = {editable = true,extras = ["all"],path = "."} + +[pipenv] +allow_prereleases = true diff --git a/Pipfile.lock b/Pipfile.lock index a233362..4555cd2 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -23,31 +23,31 @@ }, "attrs": { "hashes": [ - "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594", - "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc" + "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", + "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" ], - "version": "==20.2.0" + "version": "==20.3.0" }, "boto3": { "hashes": [ - "sha256:f0b0a3611f9471137d3714b5dd8a8d0911f2b035f3de2d71ebe3e830e4e6d724", - "sha256:fb94f75d394fc97e5328f9b1a5badcfbd79e0ab5596254f8c49cfd40c69bb436" + "sha256:546586e56ddaf1a75970d65a890351e7093e579a7b686fd45b13e973127dbeec", + "sha256:bc77898ff92c8d328b2f2b74c05008179e6b32041c6b635f0199079ed828f60f" ], - "version": "==1.16.6" + "version": "==1.16.14" }, "botocore": { "hashes": [ - "sha256:66e1d9f3ef22480e679fcfb1bdeeb2ef1cbe671278fd27464ab3b594a928e0bf", - "sha256:97d03523324dfff078aac12f88304f73038d449e10e05d378993fb3a22727c42" + "sha256:65bb3bfb81e7496c426d89900c91957b1e20c1af4c8c864d2527d623525193ee", + "sha256:cec2a66faab716773477962be782449f4abdc6475925fc259d66a729a39bc1b7" ], - "version": "==1.19.6" + "version": "==1.19.14" }, "certifi": { "hashes": [ - "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3", - "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41" + "sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd", + "sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4" ], - "version": "==2020.6.20" + "version": "==2020.11.8" }, "cffi": { "hashes": [ @@ -198,10 +198,10 @@ }, "itsdangerous": { "hashes": [ - "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", - "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" + "sha256:1f08ac48fe58cb99a1f58add0c90924b4267398bbd1640268d26a29f6a03eaa4", + "sha256:26ba8fb99157bbd5a1016f9d9dc5ed5ff1325f6f6f2c10b18107199470676b4d" ], - "version": "==1.1.0" + "version": "==2.0.0a1" }, "jaeger-client": { "hashes": [ @@ -211,10 +211,10 @@ }, "jinja2": { "hashes": [ - "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", - "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" + "sha256:c10142f819c2d22bdcd17548c46fa9b77cf4fda45097854c689666bf425e7484", + "sha256:c922560ac46888d47384de1dbdc3daaa2ea993af4b26a436dec31fa2c19ec668" ], - "version": "==2.11.2" + "version": "==3.0.0a1" }, "jmespath": { "hashes": [ @@ -232,41 +232,30 @@ }, "markupsafe": { "hashes": [ - "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", - "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", - "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", - "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", - "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", - "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", - "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", - "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", - "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", - "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", - "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", - "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", - "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", - "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", - "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", - "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", - "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", - "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", - "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", - "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", - "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", - "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", - "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", - "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", - "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", - "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", - "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", - "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", - "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", - "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", - "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", - "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", - "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" - ], - "version": "==1.1.1" + "sha256:06358015a4dee8ee23ae426bf885616ab3963622defd829eb45b44e3dee3515f", + "sha256:0b0c4fc852c5f02c6277ef3b33d23fcbe89b1b227460423e3335374da046b6db", + "sha256:267677fc42afed5094fc5ea1c4236bbe4b6a00fe4b08e93451e65ae9048139c7", + "sha256:303cb70893e2c345588fb5d5b86e0ca369f9bb56942f03064c5e3e75fa7a238a", + "sha256:3c9b624a0d9ed5a5093ac4edc4e823e6b125441e60ef35d36e6f4a6fdacd5054", + "sha256:42033e14cae1f6c86fc0c3e90d04d08ce73ac8e46ba420a0d22d545c2abd4977", + "sha256:4e4a99b6af7bdc0856b50020c095848ec050356a001e1f751510aef6ab14d0e0", + "sha256:4eb07faad54bb07427d848f31030a65a49ebb0cec0b30674f91cf1ddd456bfe4", + "sha256:63a7161cd8c2bc563feeda45df62f42c860dd0675e2b8da2667f25bb3c95eaba", + "sha256:68e0fd039b68d2945b4beb947d4023ca7f8e95b708031c345762efba214ea761", + "sha256:8092a63397025c2f655acd42784b2a1528339b90b987beb9253f22e8cdbb36c3", + "sha256:841218860683c0f2223e24756843d84cc49cccdae6765e04962607754a52d3e0", + "sha256:94076b2314bd2f6cfae508ad65b4d493e3a58a50112b7a2cbb6287bdbc404ae8", + "sha256:9d22aff1c5322e402adfb3ce40839a5056c353e711c033798cf4f02eb9f5124d", + "sha256:b0e4584f62b3e5f5c1a7bcefd2b52f236505e6ef032cc508caa4f4c8dc8d3af1", + "sha256:b1163ffc1384d242964426a8164da12dbcdbc0de18ea36e2c34b898ed38c3b45", + "sha256:beac28ed60c8e838301226a7a85841d0af2068eba2dcb1a58c2d32d6c05e440e", + "sha256:c29f096ce79c03054a1101d6e5fe6bf04b0bb489165d5e0e9653fb4fe8048ee1", + "sha256:c58779966d53e5f14ba393d64e2402a7926601d1ac8adeb4e83893def79d0428", + "sha256:cfe14b37908eaf7d5506302987228bff69e1b8e7071ccd4e70fd0283b1b47f0b", + "sha256:e834249c45aa9837d0753351cdca61a4b8b383cc9ad0ff2325c97ff7b69e72a6", + "sha256:eed1b234c4499811ee85bcefa22ef5e466e75d132502226ed29740d593316c1f" + ], + "version": "==2.0.0a1" }, "openapi-spec-validator": { "hashes": [ @@ -310,6 +299,13 @@ ], "path": "." }, + "py-ms-consulate": { + "hashes": [ + "sha256:577e16e11ff01739755450c87f93b98bedda66811ebcde6e2292408622eb3fa9", + "sha256:99a03fbd23587403b8e84ac22ee05f0ecb0f9939525b80d3e0c63fdf4f4d1e0c" + ], + "version": "==1.0.0" + }, "pycparser": { "hashes": [ "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", @@ -465,10 +461,10 @@ }, "attrs": { "hashes": [ - "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594", - "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc" + "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", + "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" ], - "version": "==20.2.0" + "version": "==20.3.0" }, "bandit": { "hashes": [ @@ -483,12 +479,18 @@ ], "version": "==3.1.0" }, + "black": { + "hashes": [ + "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea" + ], + "version": "==20.8b1" + }, "certifi": { "hashes": [ - "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3", - "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41" + "sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd", + "sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4" ], - "version": "==2020.6.20" + "version": "==2020.11.8" }, "cffi": { "hashes": [ @@ -531,6 +533,13 @@ ], "version": "==1.14.3" }, + "cfgv": { + "hashes": [ + "sha256:32e43d604bbe7896fe7c248a9c2276447dbef840feb28fe20494f62af110211d", + "sha256:cf22deb93d4bcf92f345a5c3cd39d3d41d6340adc60c78bbbd6588c384fda6a1" + ], + "version": "==3.2.0" + }, "chardet": { "hashes": [ "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", @@ -673,6 +682,13 @@ ], "version": "==1.52.0" }, + "identify": { + "hashes": [ + "sha256:5dd84ac64a9a115b8e0b27d1756b244b882ad264c3c423f42af8235a6e71ca12", + "sha256:c9504ba6a043ee2db0a9d69e43246bc138034895f6338d5aed1b41e4a73b1513" + ], + "version": "==1.5.9" + }, "idna": { "hashes": [ "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", @@ -704,17 +720,17 @@ }, "itsdangerous": { "hashes": [ - "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", - "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" + "sha256:1f08ac48fe58cb99a1f58add0c90924b4267398bbd1640268d26a29f6a03eaa4", + "sha256:26ba8fb99157bbd5a1016f9d9dc5ed5ff1325f6f6f2c10b18107199470676b4d" ], - "version": "==1.1.0" + "version": "==2.0.0a1" }, "jinja2": { "hashes": [ - "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", - "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" + "sha256:c10142f819c2d22bdcd17548c46fa9b77cf4fda45097854c689666bf425e7484", + "sha256:c922560ac46888d47384de1dbdc3daaa2ea993af4b26a436dec31fa2c19ec668" ], - "version": "==2.11.2" + "version": "==3.0.0a1" }, "joblib": { "hashes": [ @@ -788,41 +804,30 @@ }, "markupsafe": { "hashes": [ - "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", - "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", - "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", - "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", - "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", - "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", - "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", - "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", - "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", - "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", - "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", - "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", - "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", - "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", - "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", - "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", - "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", - "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", - "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", - "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", - "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", - "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", - "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", - "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", - "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", - "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", - "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", - "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", - "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", - "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", - "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", - "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", - "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" - ], - "version": "==1.1.1" + "sha256:06358015a4dee8ee23ae426bf885616ab3963622defd829eb45b44e3dee3515f", + "sha256:0b0c4fc852c5f02c6277ef3b33d23fcbe89b1b227460423e3335374da046b6db", + "sha256:267677fc42afed5094fc5ea1c4236bbe4b6a00fe4b08e93451e65ae9048139c7", + "sha256:303cb70893e2c345588fb5d5b86e0ca369f9bb56942f03064c5e3e75fa7a238a", + "sha256:3c9b624a0d9ed5a5093ac4edc4e823e6b125441e60ef35d36e6f4a6fdacd5054", + "sha256:42033e14cae1f6c86fc0c3e90d04d08ce73ac8e46ba420a0d22d545c2abd4977", + "sha256:4e4a99b6af7bdc0856b50020c095848ec050356a001e1f751510aef6ab14d0e0", + "sha256:4eb07faad54bb07427d848f31030a65a49ebb0cec0b30674f91cf1ddd456bfe4", + "sha256:63a7161cd8c2bc563feeda45df62f42c860dd0675e2b8da2667f25bb3c95eaba", + "sha256:68e0fd039b68d2945b4beb947d4023ca7f8e95b708031c345762efba214ea761", + "sha256:8092a63397025c2f655acd42784b2a1528339b90b987beb9253f22e8cdbb36c3", + "sha256:841218860683c0f2223e24756843d84cc49cccdae6765e04962607754a52d3e0", + "sha256:94076b2314bd2f6cfae508ad65b4d493e3a58a50112b7a2cbb6287bdbc404ae8", + "sha256:9d22aff1c5322e402adfb3ce40839a5056c353e711c033798cf4f02eb9f5124d", + "sha256:b0e4584f62b3e5f5c1a7bcefd2b52f236505e6ef032cc508caa4f4c8dc8d3af1", + "sha256:b1163ffc1384d242964426a8164da12dbcdbc0de18ea36e2c34b898ed38c3b45", + "sha256:beac28ed60c8e838301226a7a85841d0af2068eba2dcb1a58c2d32d6c05e440e", + "sha256:c29f096ce79c03054a1101d6e5fe6bf04b0bb489165d5e0e9653fb4fe8048ee1", + "sha256:c58779966d53e5f14ba393d64e2402a7926601d1ac8adeb4e83893def79d0428", + "sha256:cfe14b37908eaf7d5506302987228bff69e1b8e7071ccd4e70fd0283b1b47f0b", + "sha256:e834249c45aa9837d0753351cdca61a4b8b383cc9ad0ff2325c97ff7b69e72a6", + "sha256:eed1b234c4499811ee85bcefa22ef5e466e75d132502226ed29740d593316c1f" + ], + "version": "==2.0.0a1" }, "mccabe": { "hashes": [ @@ -838,20 +843,6 @@ ], "version": "==1.1.2" }, - "mkdocs-material": { - "hashes": [ - "sha256:81095982fc4d634ee4b16abb91fa7b2e1f50ab7e39df67aafa8f5d4fd7b28e24", - "sha256:a4bfd736898e6bfeb2c0d00ab448f464a389fb6a7dd2a30a90343756fd8aec83" - ], - "version": "==6.1.0" - }, - "mkdocs-material-extensions": { - "hashes": [ - "sha256:6947fb7f5e4291e3c61405bad3539d81e0b3cd62ae0d66ced018128af509c68f", - "sha256:d90c807a88348aa6d1805657ec5c0b2d8d609c110e62b9dce4daf7fa981fa338" - ], - "version": "==1.0.1" - }, "mypy": { "hashes": [ "sha256:0a0d102247c16ce93c97066443d11e2d36e6cc2a32d8ccc1f705268970479324", @@ -884,6 +875,13 @@ ], "version": "==3.5" }, + "nodeenv": { + "hashes": [ + "sha256:5304d424c529c997bc888453aeaa6362d242b6b4631e90f3d4bf1b290f1c84a9", + "sha256:ab45090ae383b716c4ef89e690c41ff8c2b257b85b309f01f3654df3d084bd7c" + ], + "version": "==1.5.0" + }, "opentracing": { "hashes": [ "sha256:33b10634917a7496a7e3a18b18b7c055053d0a8da5101e2a95d454783cac9765" @@ -897,6 +895,13 @@ ], "version": "==20.4" }, + "pathspec": { + "hashes": [ + "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd", + "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d" + ], + "version": "==0.8.1" + }, "pbr": { "hashes": [ "sha256:5fad80b613c402d5b7df7bd84812548b2a61e9977387a80a5fc5c396492b13c9", @@ -911,28 +916,35 @@ ], "version": "==0.13.1" }, + "pre-commit": { + "hashes": [ + "sha256:22e6aa3bd571debb01eb7d34483f11c01b65237be4eebbf30c3d4fb65762d315", + "sha256:905ebc9b534b991baec87e934431f2d0606ba27f2b90f7f652985f5a5b8b6ae6" + ], + "version": "==2.8.2" + }, "protobuf": { "hashes": [ - "sha256:0bba42f439bf45c0f600c3c5993666fcb88e8441d011fad80a11df6f324eef33", - "sha256:1e834076dfef9e585815757a2c7e4560c7ccc5962b9d09f831214c693a91b463", - "sha256:339c3a003e3c797bc84499fa32e0aac83c768e67b3de4a5d7a5a9aa3b0da634c", - "sha256:361acd76f0ad38c6e38f14d08775514fbd241316cce08deb2ce914c7dfa1184a", - "sha256:3dee442884a18c16d023e52e32dd34a8930a889e511af493f6dc7d4d9bf12e4f", - "sha256:4d1174c9ed303070ad59553f435846a2f877598f59f9afc1b89757bdf846f2a7", - "sha256:5db9d3e12b6ede5e601b8d8684a7f9d90581882925c96acf8495957b4f1b204b", - "sha256:6a82e0c8bb2bf58f606040cc5814e07715b2094caeba281e2e7d0b0e2e397db5", - "sha256:8c35bcbed1c0d29b127c886790e9d37e845ffc2725cc1db4bd06d70f4e8359f4", - "sha256:91c2d897da84c62816e2f473ece60ebfeab024a16c1751aaf31100127ccd93ec", - "sha256:9c2e63c1743cba12737169c447374fab3dfeb18111a460a8c1a000e35836b18c", - "sha256:9edfdc679a3669988ec55a989ff62449f670dfa7018df6ad7f04e8dbacb10630", - "sha256:c0c5ab9c4b1eac0a9b838f1e46038c3175a95b0f2d944385884af72876bd6bc7", - "sha256:c8abd7605185836f6f11f97b21200f8a864f9cb078a193fe3c9e235711d3ff1e", - "sha256:d69697acac76d9f250ab745b46c725edf3e98ac24763990b24d58c16c642947a", - "sha256:df3932e1834a64b46ebc262e951cd82c3cf0fa936a154f0a42231140d8237060", - "sha256:e7662437ca1e0c51b93cadb988f9b353fa6b8013c0385d63a70c8a77d84da5f9", - "sha256:f68eb9d03c7d84bd01c790948320b768de8559761897763731294e3bc316decb" - ], - "version": "==3.13.0" + "sha256:0e55cf11e3cdc7af9e280539144c0896ff046144dc17ddf6344e33f7bd01c53b", + "sha256:2ceef235ec88363f0853679363e215aee79f2998931b83e5d1bf08289e3c6a6f", + "sha256:36583c483ddd3c60f25a9a1e1660baa1e033e9470a49e019d0705d6fefaea52b", + "sha256:3bb580ee33b844087e419a9302a255a956d695147a64d59e96a3e0abd78fa67d", + "sha256:4136552036dbff1e5841cd240d1be973348752cfdaa91c9088d8a32ae43f06c0", + "sha256:428930d8f9607723ab6482f07bc7a651e95f86c7f5db6347a0b1f24319cd1e3b", + "sha256:471b0cd067e1ea2c6c5cc82fc7c04990a0497914668ca0bdc7db7925e25a8045", + "sha256:4ed0b7df03cd668dbd4bf1a432e58fc9201ae4f15a9a6f837bce4c7496c431da", + "sha256:76f4a7f5c418167496b68a7310efe93a33066aa83f5fac3a9189bc5ace6ff905", + "sha256:991301d26c33cc25c8a81fd7d25ad5a31cc6eb166b96afcbce93675545e03532", + "sha256:ae99d4b1c15439586d7d3bfc3ec7d3e933419fdcff3e483ed5a14e653b45d39c", + "sha256:afef26dd04202c8f4efe8292bd9a683166e32b6004abb0302d6a92db1a994bf1", + "sha256:bb056806dee32e6f8ad47935ca2be34bed124675c0132a469e70aa6847ce2223", + "sha256:bb0b3df5c29a8dc51e6435505d42907ac3651e2b0be40c30e9f40d08aca6d47c", + "sha256:bd644bf3865a72d5a244e7193f784cc6cd51bb453d447ee73a5e05d60dc22717", + "sha256:d6bb25e26cbee3eebf460012e3406ad5d77ced6824abbea919d8c7a0466e82f1", + "sha256:ed7143575f71fed2599e1548a0378cfd6dc958920221666e3a33785af3f0d7e2", + "sha256:ff82b53153edda066698bae674d673566029e856fa126dcb95a432aed4161af2" + ], + "version": "==3.14.0rc1" }, "py": { "hashes": [ @@ -969,13 +981,6 @@ ], "version": "==2.2.0" }, - "pygments": { - "hashes": [ - "sha256:381985fcc551eb9d37c52088a32914e00517e57f4a21609f48141ba08e193fa0", - "sha256:88a0bbcd659fcb9573703957c6b9cff9fab7295e6e76db54c9d00ae42df32773" - ], - "version": "==2.7.2" - }, "pylint": { "hashes": [ "sha256:bb4a908c9dadbc3aac18860550e870f58e1a02c9f2c204fdf5693d73be061210", @@ -983,26 +988,19 @@ ], "version": "==2.6.0" }, - "pymdown-extensions": { - "hashes": [ - "sha256:9ba704052d4bdc04a7cd63f7db4ef6add73bafcef22c0cf6b2e3386cf4ece51e", - "sha256:a3689c04f4cbddacd9d569425c571ae07e2673cc4df63a26cdbf1abc15229137" - ], - "version": "==8.0.1" - }, "pyparsing": { "hashes": [ - "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", - "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + "sha256:13140e8d0e1edd806eb50f18535d77f2143b40771d4aaef6b4950dd93d48a7db", + "sha256:38891c1032d0c759f0fa5ed3a8f249fd992b083fa2303ead58ee48a51b269e02" ], - "version": "==2.4.7" + "version": "==3.0.0b1" }, "pytest": { "hashes": [ - "sha256:7a8190790c17d79a11f847fba0b004ee9a8122582ebff4729a082c109e81a4c9", - "sha256:8f593023c1a0f916110285b6efd7f99db07d59546e3d8c36fc60e2ab05d3be92" + "sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe", + "sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e" ], - "version": "==6.1.1" + "version": "==6.1.2" }, "pytest-cov": { "hashes": [ @@ -1035,35 +1033,51 @@ }, "regex": { "hashes": [ - "sha256:0cb23ed0e327c18fb7eac61ebbb3180ebafed5b9b86ca2e15438201e5903b5dd", - "sha256:1a065e7a6a1b4aa851a0efa1a2579eabc765246b8b3a5fd74000aaa3134b8b4e", - "sha256:1a511470db3aa97432ac8c1bf014fcc6c9fbfd0f4b1313024d342549cf86bcd6", - "sha256:1c447b0d108cddc69036b1b3910fac159f2b51fdeec7f13872e059b7bc932be1", - "sha256:2278453c6a76280b38855a263198961938108ea2333ee145c5168c36b8e2b376", - "sha256:240509721a663836b611fa13ca1843079fc52d0b91ef3f92d9bba8da12e768a0", - "sha256:4e21340c07090ddc8c16deebfd82eb9c9e1ec5e62f57bb86194a2595fd7b46e0", - "sha256:570e916a44a361d4e85f355aacd90e9113319c78ce3c2d098d2ddf9631b34505", - "sha256:59d5c6302d22c16d59611a9fd53556554010db1d47e9df5df37be05007bebe75", - "sha256:6a46eba253cedcbe8a6469f881f014f0a98819d99d341461630885139850e281", - "sha256:6f567df0601e9c7434958143aebea47a9c4b45434ea0ae0286a4ec19e9877169", - "sha256:781906e45ef1d10a0ed9ec8ab83a09b5e0d742de70e627b20d61ccb1b1d3964d", - "sha256:8469377a437dbc31e480993399fd1fd15fe26f382dc04c51c9cb73e42965cc06", - "sha256:8cd0d587aaac74194ad3e68029124c06245acaeddaae14cb45844e5c9bebeea4", - "sha256:97a023f97cddf00831ba04886d1596ef10f59b93df7f855856f037190936e868", - "sha256:a973d5a7a324e2a5230ad7c43f5e1383cac51ef4903bf274936a5634b724b531", - "sha256:af360e62a9790e0a96bc9ac845d87bfa0e4ee0ee68547ae8b5a9c1030517dbef", - "sha256:b706c70070eea03411b1761fff3a2675da28d042a1ab7d0863b3efe1faa125c9", - "sha256:bfd7a9fddd11d116a58b62ee6c502fd24cfe22a4792261f258f886aa41c2a899", - "sha256:c30d8766a055c22e39dd7e1a4f98f6266169f2de05db737efe509c2fb9c8a3c8", - "sha256:c53dc8ee3bb7b7e28ee9feb996a0c999137be6c1d3b02cb6b3c4cba4f9e5ed09", - "sha256:c95d514093b80e5309bdca5dd99e51bcf82c44043b57c34594d9d7556bd04d05", - "sha256:d43cf21df524283daa80ecad551c306b7f52881c8d0fe4e3e76a96b626b6d8d8", - "sha256:d62205f00f461fe8b24ade07499454a3b7adf3def1225e258b994e2215fd15c5", - "sha256:e289a857dca3b35d3615c3a6a438622e20d1bf0abcb82c57d866c8d0be3f44c4", - "sha256:e5f6aa56dda92472e9d6f7b1e6331f4e2d51a67caafff4d4c5121cadac03941e", - "sha256:f4b1c65ee86bfbf7d0c3dfd90592a9e3d6e9ecd36c367c884094c050d4c35d04" - ], - "version": "==2020.10.23" + "sha256:03855ee22980c3e4863dc84c42d6d2901133362db5daf4c36b710dd895d78f0a", + "sha256:06b52815d4ad38d6524666e0d50fe9173533c9cc145a5779b89733284e6f688f", + "sha256:11116d424734fe356d8777f89d625f0df783251ada95d6261b4c36ad27a394bb", + "sha256:119e0355dbdd4cf593b17f2fc5dbd4aec2b8899d0057e4957ba92f941f704bf5", + "sha256:127a9e0c0d91af572fbb9e56d00a504dbd4c65e574ddda3d45b55722462210de", + "sha256:1ec66700a10e3c75f1f92cbde36cca0d3aaee4c73dfa26699495a3a30b09093c", + "sha256:227a8d2e5282c2b8346e7f68aa759e0331a0b4a890b55a5cfbb28bd0261b84c0", + "sha256:2564def9ce0710d510b1fc7e5178ce2d20f75571f788b5197b3c8134c366f50c", + "sha256:297116e79074ec2a2f885d22db00ce6e88b15f75162c5e8b38f66ea734e73c64", + "sha256:2dc522e25e57e88b4980d2bdd334825dbf6fa55f28a922fc3bfa60cc09e5ef53", + "sha256:3a5f08039eee9ea195a89e180c5762bfb55258bfb9abb61a20d3abee3b37fd12", + "sha256:3dfca201fa6b326239e1bccb00b915e058707028809b8ecc0cf6819ad233a740", + "sha256:49461446b783945597c4076aea3f49aee4b4ce922bd241e4fcf62a3e7c61794c", + "sha256:4afa350f162551cf402bfa3cd8302165c8e03e689c897d185f16a167328cc6dd", + "sha256:4b5a9bcb56cc146c3932c648603b24514447eafa6ce9295234767bf92f69b504", + "sha256:52e83a5f28acd621ba8e71c2b816f6541af7144b69cc5859d17da76c436a5427", + "sha256:625116aca6c4b57c56ea3d70369cacc4d62fead4930f8329d242e4fe7a58ce4b", + "sha256:654c1635f2313d0843028487db2191530bca45af61ca85d0b16555c399625b0e", + "sha256:8092a5a06ad9a7a247f2a76ace121183dc4e1a84c259cf9c2ce3bbb69fac3582", + "sha256:832339223b9ce56b7b15168e691ae654d345ac1635eeb367ade9ecfe0e66bee0", + "sha256:8ca9dca965bd86ea3631b975d63b0693566d3cc347e55786d5514988b6f5b84c", + "sha256:96f99219dddb33e235a37283306834700b63170d7bb2a1ee17e41c6d589c8eb9", + "sha256:9b6305295b6591e45f069d3553c54d50cc47629eb5c218aac99e0f7fafbf90a1", + "sha256:a62162be05edf64f819925ea88d09d18b09bebf20971b363ce0c24e8b4aa14c0", + "sha256:aacc8623ffe7999a97935eeabbd24b1ae701d08ea8f874a6ff050e93c3e658cf", + "sha256:b45bab9f224de276b7bc916f6306b86283f6aa8afe7ed4133423efb42015a898", + "sha256:b88fa3b8a3469f22b4f13d045d9bd3eda797aa4e406fde0a2644bc92bbdd4bdd", + "sha256:b8a686a6c98872007aa41fdbb2e86dc03b287d951ff4a7f1da77fb7f14113e4d", + "sha256:bd904c0dec29bbd0769887a816657491721d5f545c29e30fd9d7a1a275dc80ab", + "sha256:bf4f896c42c63d1f22039ad57de2644c72587756c0cfb3cc3b7530cfe228277f", + "sha256:c13d311a4c4a8d671f5860317eb5f09591fbe8259676b86a85769423b544451e", + "sha256:c2c6c56ee97485a127555c9595c069201b5161de9d05495fbe2132b5ac104786", + "sha256:c32c91a0f1ac779cbd73e62430de3d3502bbc45ffe5bb6c376015acfa848144b", + "sha256:c3466a84fce42c2016113101018a9981804097bacbab029c2d5b4fcb224b89de", + "sha256:c454ad88e56e80e44f824ef8366bb7e4c3def12999151fd5c0ea76a18fe9aa3e", + "sha256:c8a2b7ccff330ae4c460aff36626f911f918555660cc28163417cb84ffb25789", + "sha256:cb905f3d2e290a8b8f1579d3984f2cfa7c3a29cc7cba608540ceeed18513f520", + "sha256:cfcf28ed4ce9ced47b9b9670a4f0d3d3c0e4d4779ad4dadb1ad468b097f808aa", + "sha256:dd3e6547ecf842a29cf25123fbf8d2461c53c8d37aa20d87ecee130c89b7079b", + "sha256:de7fd57765398d141949946c84f3590a68cf5887dac3fc52388df0639b01eda4", + "sha256:ea37320877d56a7f0a1e6a625d892cf963aa7f570013499f5b8d5ab8402b5625", + "sha256:f1fce1e4929157b2afeb4bb7069204d4370bab9f4fc03ca1fbec8bd601f8c87d", + "sha256:f43109822df2d3faac7aad79613f5f02e4eab0fc8ad7932d2e70e2a83bd49c26" + ], + "version": "==2020.10.28" }, "requests": { "hashes": [ @@ -1115,10 +1129,10 @@ }, "toml": { "hashes": [ - "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f", - "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88" + "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", + "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "version": "==0.10.1" + "version": "==0.10.2" }, "tornado": { "hashes": [ diff --git a/examples/microservice_configuration/main.py b/examples/microservice_configuration/main.py index f82613c..2ddb10f 100644 --- a/examples/microservice_configuration/main.py +++ b/examples/microservice_configuration/main.py @@ -1,5 +1,6 @@ from examples.microservice_configuration import ms + app = ms.create_app() -if __name__ == '__main__': +if __name__ == "__main__": app.run() diff --git a/examples/microservice_configuration/views.py b/examples/microservice_configuration/views.py index 5684791..3a608ed 100644 --- a/examples/microservice_configuration/views.py +++ b/examples/microservice_configuration/views.py @@ -9,5 +9,5 @@ def example(): "GLOBAL_VARIABLE": GLOBAL_VARIABLE, "GLOBAL_VARIABLE2": GLOBAL_VARIABLE2, "test1": config().test1, - "test2": config().test2 + "test2": config().test2, } diff --git a/examples/microservice_crypt_aws_kms/main.py b/examples/microservice_crypt_aws_kms/main.py index e96dd62..02da4ec 100644 --- a/examples/microservice_crypt_aws_kms/main.py +++ b/examples/microservice_crypt_aws_kms/main.py @@ -11,5 +11,5 @@ def example(): return jsonify({"main": app.ms.config.encrypted_key}) -if __name__ == '__main__': +if __name__ == "__main__": app.run() diff --git a/examples/microservice_distribued_tracing/ms1/main.py b/examples/microservice_distribued_tracing/ms1/main.py index 7e89643..f92c06b 100644 --- a/examples/microservice_distribued_tracing/ms1/main.py +++ b/examples/microservice_distribued_tracing/ms1/main.py @@ -1,4 +1,4 @@ -from flask import jsonify, current_app, request +from flask import current_app, jsonify, request from pyms.flask.app import Microservice @@ -12,5 +12,5 @@ def index(): return jsonify({"main": "hello world {}".format(current_app.config["APP_NAME"])}) -if __name__ == '__main__': +if __name__ == "__main__": app.run() diff --git a/examples/microservice_distribued_tracing/ms2/main.py b/examples/microservice_distribued_tracing/ms2/main.py index 5ffb165..8588d3a 100644 --- a/examples/microservice_distribued_tracing/ms2/main.py +++ b/examples/microservice_distribued_tracing/ms2/main.py @@ -13,5 +13,5 @@ def index(): return jsonify({"response": response.json()}) -if __name__ == '__main__': +if __name__ == "__main__": app.run(port=5001) diff --git a/examples/microservice_metrics/__init__.py b/examples/microservice_metrics/__init__.py index 35ed887..02d325e 100644 --- a/examples/microservice_metrics/__init__.py +++ b/examples/microservice_metrics/__init__.py @@ -1,3 +1,3 @@ from pyms.flask.app import Microservice -ms = Microservice() \ No newline at end of file +ms = Microservice() diff --git a/examples/microservice_metrics/main.py b/examples/microservice_metrics/main.py index 69b0447..20e9a90 100644 --- a/examples/microservice_metrics/main.py +++ b/examples/microservice_metrics/main.py @@ -2,5 +2,5 @@ app = ms.create_app() -if __name__ == '__main__': +if __name__ == "__main__": app.run() diff --git a/examples/microservice_metrics/views.py b/examples/microservice_metrics/views.py index fb8fdad..a06ae71 100644 --- a/examples/microservice_metrics/views.py +++ b/examples/microservice_metrics/views.py @@ -7,4 +7,4 @@ def example(): current_app.logger.info("start request") result = ms.requests.get_for_object("https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe") current_app.logger.info("end request") - return result \ No newline at end of file + return result diff --git a/examples/microservice_requests/main.py b/examples/microservice_requests/main.py index 3d11381..6934e31 100644 --- a/examples/microservice_requests/main.py +++ b/examples/microservice_requests/main.py @@ -2,5 +2,5 @@ app = ms.create_app() -if __name__ == '__main__': +if __name__ == "__main__": app.run() diff --git a/examples/microservice_service_discovery/__init__.py b/examples/microservice_service_discovery/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/microservice_service_discovery/config.yml b/examples/microservice_service_discovery/config.yml new file mode 100644 index 0000000..2237279 --- /dev/null +++ b/examples/microservice_service_discovery/config.yml @@ -0,0 +1,11 @@ +pyms: + services: + service_discovery: + service: "examples.microservice_service_discovery.service.ServiceDiscoveryConsulBasic" + host: "localhost" + autoregister: true + config: + DEBUG: true + TESTING: false + APP_NAME: "Python Microservice My personal Service Discovery" + APPLICATION_ROOT: "" \ No newline at end of file diff --git a/examples/microservice_service_discovery/main.py b/examples/microservice_service_discovery/main.py new file mode 100644 index 0000000..fdbccf2 --- /dev/null +++ b/examples/microservice_service_discovery/main.py @@ -0,0 +1,15 @@ +from flask import jsonify + +from pyms.flask.app import Microservice + +ms = Microservice(path=__file__) +app = ms.create_app() + + +@app.route("/") +def example(): + return jsonify({"main": "hello world"}) + + +if __name__ == "__main__": + app.run() diff --git a/examples/microservice_service_discovery/service.py b/examples/microservice_service_discovery/service.py new file mode 100644 index 0000000..3d9f995 --- /dev/null +++ b/examples/microservice_service_discovery/service.py @@ -0,0 +1,33 @@ +import json +import uuid + +import requests + +from pyms.flask.services.service_discovery import ServiceDiscoveryBase + + +class ServiceDiscoveryConsulBasic(ServiceDiscoveryBase): + id_app = str(uuid.uuid1()) + + def __init__(self, config): + super().__init__(config) + self.host = config.host + self.port = config.port + + def register_service(self, *args, **kwargs): + app_name = kwargs["app_name"] + healtcheck_url = kwargs["healtcheck_url"] + interval = kwargs["interval"] + headers = {"Content-Type": "application/json; charset=utf-8"} + data = { + "id": app_name + "-" + self.id_app, + "name": app_name, + "check": {"name": "ping check", "http": healtcheck_url, "interval": interval, "status": "passing"}, + } + response = requests.put( + "http://{host}:{port}/v1/agent/service/register".format(host=self.host, port=self.port), + data=json.dumps(data), + headers=headers, + ) + if response.status_code != 200: + raise Exception(response.content) diff --git a/examples/microservice_service_discovery_consul/config.yml b/examples/microservice_service_discovery_consul/config.yml new file mode 100644 index 0000000..8356707 --- /dev/null +++ b/examples/microservice_service_discovery_consul/config.yml @@ -0,0 +1,11 @@ +pyms: + services: + service_discovery: + service: "consul" + host: "localhost" + autoregister: true + config: + DEBUG: true + TESTING: false + APP_NAME: "Python Microservice" + APPLICATION_ROOT: "" \ No newline at end of file diff --git a/examples/microservice_service_discovery_consul/main.py b/examples/microservice_service_discovery_consul/main.py new file mode 100644 index 0000000..5f2cf1c --- /dev/null +++ b/examples/microservice_service_discovery_consul/main.py @@ -0,0 +1,16 @@ +from flask import jsonify + +from pyms.flask.app import Microservice + +ms = Microservice(path=__file__) +app = ms.create_app() + + +@app.route("/") +def example(): + checks = ms.service_discovery.client.agent.checks() + return jsonify({"main": checks}) + + +if __name__ == "__main__": + app.run() diff --git a/examples/microservice_swagger/main.py b/examples/microservice_swagger/main.py index d7c9347..c9aa259 100644 --- a/examples/microservice_swagger/main.py +++ b/examples/microservice_swagger/main.py @@ -3,5 +3,5 @@ ms = Microservice() app = ms.create_app() -if __name__ == '__main__': +if __name__ == "__main__": app.run() diff --git a/examples/microservice_tracer/main.py b/examples/microservice_tracer/main.py index 12c1874..6934e31 100644 --- a/examples/microservice_tracer/main.py +++ b/examples/microservice_tracer/main.py @@ -1,5 +1,6 @@ from examples.microservice_requests import ms + app = ms.create_app() -if __name__ == '__main__': +if __name__ == "__main__": app.run() diff --git a/examples/mininum_microservice/main.py b/examples/mininum_microservice/main.py index 9c1e940..fdbccf2 100644 --- a/examples/mininum_microservice/main.py +++ b/examples/mininum_microservice/main.py @@ -11,5 +11,5 @@ def example(): return jsonify({"main": "hello world"}) -if __name__ == '__main__': +if __name__ == "__main__": app.run() diff --git a/examples/mininum_microservice_docker/main.py b/examples/mininum_microservice_docker/main.py index 0c7bf34..0a91fc0 100644 --- a/examples/mininum_microservice_docker/main.py +++ b/examples/mininum_microservice_docker/main.py @@ -11,5 +11,5 @@ def example(): return jsonify({"main": ms.config.environment}) -if __name__ == '__main__': +if __name__ == "__main__": app.run() diff --git a/pyms/cloud/aws/kms.py b/pyms/cloud/aws/kms.py index 640d540..fbfcfc0 100644 --- a/pyms/cloud/aws/kms.py +++ b/pyms/cloud/aws/kms.py @@ -12,7 +12,7 @@ def __init__(self, *args, **kwargs) -> None: self._init_boto() super().__init__(*args, **kwargs) - def encrypt(self, message: str) -> str: # pragma: no cover + def encrypt(self, message: str) -> str: # pragma: no cover ciphertext = self.client.encrypt( KeyId=self.config.key_id, Plaintext=bytes(message, encoding="UTF-8"), @@ -22,16 +22,14 @@ def encrypt(self, message: str) -> str: # pragma: no cover def _init_boto(self) -> None: # pragma: no cover check_package_exists("boto3") boto3 = import_package("boto3") - boto3.set_stream_logger(name='botocore') - self.client = boto3.client('kms') + boto3.set_stream_logger(name="botocore") + self.client = boto3.client("kms") def _aws_decrypt(self, blob_text: bytes) -> str: # pragma: no cover response = self.client.decrypt( - CiphertextBlob=blob_text, - KeyId=self.config.key_id, - EncryptionAlgorithm=self.encryption_algorithm + CiphertextBlob=blob_text, KeyId=self.config.key_id, EncryptionAlgorithm=self.encryption_algorithm ) - return str(response['Plaintext'], encoding="UTF-8") + return str(response["Plaintext"], encoding="UTF-8") def _parse_encrypted(self, encrypted: str) -> bytes: blob_text = base64.b64decode(encrypted) diff --git a/pyms/cmd/__init__.py b/pyms/cmd/__init__.py index c334bcc..ec80692 100755 --- a/pyms/cmd/__init__.py +++ b/pyms/cmd/__init__.py @@ -1,3 +1,3 @@ from .main import Command -__all__ = ['Command'] +__all__ = ["Command"] diff --git a/pyms/cmd/main.py b/pyms/cmd/main.py index 0802f4e..e622f0a 100755 --- a/pyms/cmd/main.py +++ b/pyms/cmd/main.py @@ -1,16 +1,15 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- -from __future__ import unicode_literals, print_function +from __future__ import print_function, unicode_literals import argparse import os import sys from distutils.util import strtobool +from pyms.config import create_conf_file from pyms.crypt.fernet import Crypt from pyms.flask.services.swagger import merge_swagger_file from pyms.utils import check_package_exists, import_from, utils -from pyms.config import create_conf_file class Command: @@ -27,37 +26,40 @@ def __init__(self, *args, **kwargs): if not arguments: # pragma: no cover arguments = sys.argv[1:] - parser = argparse.ArgumentParser(description='Python Microservices') + parser = argparse.ArgumentParser(description="Python Microservices") - commands = parser.add_subparsers(title="Commands", description='Available commands', dest='command_name') + commands = parser.add_subparsers(title="Commands", description="Available commands", dest="command_name") - parser_encrypt = commands.add_parser('encrypt', help='Encrypt a string') - parser_encrypt.add_argument("encrypt", default='', type=str, help='Encrypt a string') + parser_encrypt = commands.add_parser("encrypt", help="Encrypt a string") + parser_encrypt.add_argument("encrypt", default="", type=str, help="Encrypt a string") - parser_create_key = commands.add_parser('create-key', help='Generate a Key to encrypt strings in config') - parser_create_key.add_argument("create_key", action='store_true', - help='Generate a Key to encrypt strings in config') + parser_create_key = commands.add_parser("create-key", help="Generate a Key to encrypt strings in config") + parser_create_key.add_argument( + "create_key", action="store_true", help="Generate a Key to encrypt strings in config" + ) parser_startproject = commands.add_parser( - 'startproject', - help='Generate a project from https://github.com/python-microservices/microservices-template') + "startproject", + help="Generate a project from https://github.com/python-microservices/microservices-template", + ) parser_startproject.add_argument( - "startproject", action='store_true', - help='Generate a project from https://github.com/python-microservices/microservices-template') + "startproject", + action="store_true", + help="Generate a project from https://github.com/python-microservices/microservices-template", + ) parser_startproject.add_argument( - "-b", "--branch", - help='Select a branch from https://github.com/python-microservices/microservices-template') + "-b", "--branch", help="Select a branch from https://github.com/python-microservices/microservices-template" + ) - parser_merge_swagger = commands.add_parser('merge-swagger', help='Merge swagger into a single file') - parser_merge_swagger.add_argument("merge_swagger", action='store_true', - help='Merge swagger into a single file') + parser_merge_swagger = commands.add_parser("merge-swagger", help="Merge swagger into a single file") + parser_merge_swagger.add_argument("merge_swagger", action="store_true", help="Merge swagger into a single file") parser_merge_swagger.add_argument( - "-f", "--file", default=os.path.join('project', 'swagger', 'swagger.yaml'), - help='Swagger file path') + "-f", "--file", default=os.path.join("project", "swagger", "swagger.yaml"), help="Swagger file path" + ) - parser_create_config = commands.add_parser('create-config', help='Generate a config file') - parser_create_config.add_argument("create_config", action='store_true', help='Generate a config file') + parser_create_config = commands.add_parser("create-config", help="Generate a config file") + parser_create_config.add_argument("create_config", action="store_true", help="Generate a config file") parser.add_argument("-v", "--verbose", default="", type=str, help="Verbose ") @@ -100,10 +102,10 @@ def run(self): crypt = Crypt() if self.create_key: path = crypt._loader.get_path_from_env() # pylint: disable=protected-access - pwd = self.get_input('Type a password to generate the key file: ') + pwd = self.get_input("Type a password to generate the key file: ") # Should use yes_no_input insted of get input below # the result should be validated for Yes (Y|y) rather allowing anything other than 'n' - generate_file = self.get_input('Do you want to generate a file in {}? [Y/n]'.format(path)) + generate_file = self.get_input("Do you want to generate a file in {}? [Y/n]".format(path)) generate_file = generate_file.lower() != "n" key = crypt.generate_key(pwd, generate_file) if generate_file: @@ -118,7 +120,7 @@ def run(self): if self.startproject: check_package_exists("cookiecutter") cookiecutter = import_from("cookiecutter.main", "cookiecutter") - cookiecutter('gh:python-microservices/cookiecutter-pyms', checkout=self.branch) + cookiecutter("gh:python-microservices/cookiecutter-pyms", checkout=self.branch) self.print_ok("Created project OK") if self.merge_swagger: try: @@ -128,8 +130,8 @@ def run(self): self.print_error(ex.__str__()) return False if self.create_config: - use_requests = self.yes_no_input('Do you want to use request') - use_swagger = self.yes_no_input('Do you want to use swagger') + use_requests = self.yes_no_input("Do you want to use request") + use_swagger = self.yes_no_input("Do you want to use swagger") try: conf_file_path = create_conf_file(use_requests, use_swagger) self.print_ok(f'Config file "{conf_file_path}" created') @@ -140,7 +142,9 @@ def run(self): return True def yes_no_input(self, msg=""): # pragma: no cover - answer = input(utils.colored_text(f'{msg}{"?" if not msg.endswith("?") else ""} [Y/n] :', utils.Colors.BLUE, True)) # nosec + answer = input( # nosec + utils.colored_text(f'{msg}{"?" if not msg.endswith("?") else ""} [Y/n] :', utils.Colors.BLUE, True) + ) try: return strtobool(answer) except ValueError: @@ -168,6 +172,6 @@ def exit_ok(self, msg=""): # pragma: no cover sys.exit(0) -if __name__ == '__main__': # pragma: no cover +if __name__ == "__main__": # pragma: no cover cmd = Command(arguments=sys.argv[1:], autorun=False) cmd.run() diff --git a/pyms/config/__init__.py b/pyms/config/__init__.py index 763ae86..c6e9777 100644 --- a/pyms/config/__init__.py +++ b/pyms/config/__init__.py @@ -1,4 +1,4 @@ +from .conf import create_conf_file, get_conf from .confile import ConfFile -from .conf import get_conf, create_conf_file -__all__ = ['get_conf', 'create_conf_file', 'ConfFile'] +__all__ = ["get_conf", "create_conf_file", "ConfFile"] diff --git a/pyms/config/conf.py b/pyms/config/conf.py index 6a3d37e..9c4f48e 100644 --- a/pyms/config/conf.py +++ b/pyms/config/conf.py @@ -1,13 +1,21 @@ -import os import logging +import os from typing import Union + import yaml -from pyms.utils import utils + from pyms.config.confile import ConfFile -from pyms.constants import PYMS_CONFIG_WHITELIST_KEYWORDS, CONFIGMAP_FILE_ENVIRONMENT_LEGACY, \ - CONFIGMAP_FILE_ENVIRONMENT, CRYPT_FILE_KEY_ENVIRONMENT, CRYPT_FILE_KEY_ENVIRONMENT_LEGACY, LOGGER_NAME, \ - DEFAULT_CONFIGMAP_FILENAME -from pyms.exceptions import ServiceDoesNotExistException, ConfigErrorException, AttrDoesNotExistException +from pyms.constants import ( + CONFIGMAP_FILE_ENVIRONMENT, + CONFIGMAP_FILE_ENVIRONMENT_LEGACY, + CRYPT_FILE_KEY_ENVIRONMENT, + CRYPT_FILE_KEY_ENVIRONMENT_LEGACY, + DEFAULT_CONFIGMAP_FILENAME, + LOGGER_NAME, + PYMS_CONFIG_WHITELIST_KEYWORDS, +) +from pyms.exceptions import AttrDoesNotExistException, ConfigErrorException, ServiceDoesNotExistException +from pyms.utils import utils logger = logging.getLogger(LOGGER_NAME) @@ -24,7 +32,7 @@ def get_conf(*args, **kwargs): :return: """ - service = kwargs.pop('service', None) + service = kwargs.pop("service", None) if not service: raise ServiceDoesNotExistException("Service not defined") config = ConfFile(*args, **kwargs) @@ -40,7 +48,8 @@ def validate_conf(*args, **kwargs): except AttrDoesNotExistException: is_config_ok = False if not is_config_ok: - raise ConfigErrorException("""Config file must start with `pyms` keyword, for example: + raise ConfigErrorException( + """Config file must start with `pyms` keyword, for example: pyms: services: metrics: true @@ -55,13 +64,15 @@ def validate_conf(*args, **kwargs): component_name: "Python Microservice" config: DEBUG: true - TESTING: true""") + TESTING: true""" + ) try: config.pyms.config except AttrDoesNotExistException: is_config_ok = False if not is_config_ok: - raise ConfigErrorException("""`pyms` block must contain a `config` keyword in your Config file, for example: + raise ConfigErrorException( + """`pyms` block must contain a `config` keyword in your Config file, for example: pyms: services: metrics: true @@ -76,10 +87,12 @@ def validate_conf(*args, **kwargs): component_name: "Python Microservice" config: DEBUG: true - TESTING: true""") + TESTING: true""" + ) wrong_keywords = [i for i in config.pyms if i not in PYMS_CONFIG_WHITELIST_KEYWORDS] if len(wrong_keywords) > 0: - raise ConfigErrorException("""{} isn`t a valid keyword for pyms block, for example: + raise ConfigErrorException( + """{} isn`t a valid keyword for pyms block, for example: pyms: services: metrics: true @@ -94,15 +107,18 @@ def validate_conf(*args, **kwargs): component_name: "Python Microservice" config: DEBUG: true - TESTING: true""".format(wrong_keywords)) + TESTING: true""".format( + wrong_keywords + ) + ) # TODO Remove temporally deprecated warnings on future versions __verify_deprecated_env_variables(config) def __verify_deprecated_env_variables(config): - env_var_duplicated = "IMPORTANT: If you are using \"{}\" environment variable, \"{}\" value will be ignored." - env_var_deprecated = "IMPORTANT: \"{}\" environment variable is deprecated on this version, use \"{}\" instead." + env_var_duplicated = 'IMPORTANT: If you are using "{}" environment variable, "{}" value will be ignored.' + env_var_deprecated = 'IMPORTANT: "{}" environment variable is deprecated on this version, use "{}" instead.' if os.getenv(CONFIGMAP_FILE_ENVIRONMENT_LEGACY) is not None: if os.getenv(CONFIGMAP_FILE_ENVIRONMENT) is not None: @@ -165,10 +181,10 @@ def create_conf_file(use_requests: bool = False, use_swagger: bool = False) -> U "DEBUG": True, "TESTING": False, "APP_NAME": "Python Microservice", - "APPLICATION_ROOT": "" + "APPLICATION_ROOT": "", } try: - with open(CONFIG_FILE, 'w', encoding='utf-8') as config_file: + with open(CONFIG_FILE, "w", encoding="utf-8") as config_file: config_file.write(yaml.dump(config, default_flow_style=False, default_style=None, sort_keys=False)) except Exception as ex: raise ex diff --git a/pyms/config/confile.py b/pyms/config/confile.py index 9036297..f028a36 100644 --- a/pyms/config/confile.py +++ b/pyms/config/confile.py @@ -2,12 +2,16 @@ import logging import os import re -from typing import Dict, Union, Text, Tuple, Iterable +from typing import Dict, Iterable, Text, Tuple, Union import anyconfig -from pyms.constants import CONFIGMAP_FILE_ENVIRONMENT, LOGGER_NAME, DEFAULT_CONFIGMAP_FILENAME, \ - CONFIGMAP_FILE_ENVIRONMENT_LEGACY +from pyms.constants import ( + CONFIGMAP_FILE_ENVIRONMENT, + CONFIGMAP_FILE_ENVIRONMENT_LEGACY, + DEFAULT_CONFIGMAP_FILENAME, + LOGGER_NAME, +) from pyms.exceptions import AttrDoesNotExistException, ConfigDoesNotFoundException from pyms.utils.files import LoadFile @@ -22,6 +26,7 @@ class ConfFile(dict): * empty_init: Allow blank variables * config: Allow to pass a dictionary to ConfFile without use a file """ + _empty_init = False _crypt = None @@ -73,8 +78,8 @@ def set_config(self, config: Dict) -> Dict: add_decripted_keys = [] for k, v in config.items(): if k.lower().startswith("enc_"): - k_not_crypt = re.compile(re.escape('enc_'), re.IGNORECASE) - decrypted_key = k_not_crypt.sub('', k) + k_not_crypt = re.compile(re.escape("enc_"), re.IGNORECASE) + decrypted_key = k_not_crypt.sub("", k) decrypted_value = self._crypt.decrypt(v) if self._crypt else None setattr(self, decrypted_key, decrypted_value) add_decripted_keys.append((decrypted_key, decrypted_value)) @@ -134,6 +139,8 @@ def __setattr__(self, name, value, *args, **kwargs): @staticmethod def __get_updated_configmap_file_env() -> str: result = CONFIGMAP_FILE_ENVIRONMENT - if (os.getenv(CONFIGMAP_FILE_ENVIRONMENT_LEGACY) is not None) and (os.getenv(CONFIGMAP_FILE_ENVIRONMENT) is None): + if (os.getenv(CONFIGMAP_FILE_ENVIRONMENT_LEGACY) is not None) and ( + os.getenv(CONFIGMAP_FILE_ENVIRONMENT) is None + ): result = CONFIGMAP_FILE_ENVIRONMENT_LEGACY return result diff --git a/pyms/crypt/driver.py b/pyms/crypt/driver.py index 2234be1..5db9576 100644 --- a/pyms/crypt/driver.py +++ b/pyms/crypt/driver.py @@ -11,7 +11,6 @@ class CryptAbstract(ABC): - def __init__(self, *args, **kwargs): self.config = kwargs.get("config") @@ -25,7 +24,6 @@ def decrypt(self, encrypted): class CryptNone(CryptAbstract): - def encrypt(self, message): return message @@ -37,6 +35,7 @@ class CryptResource(ConfigResource): """This class works between `pyms.flask.create_app.Microservice` and `pyms.flask.services.[THESERVICE]`. Search for a file with the name you want to load, set the configuration and return a instance of the class you want """ + config_resource = CRYPT_BASE def get_crypt(self, *args, **kwargs) -> CryptAbstract: diff --git a/pyms/crypt/fernet.py b/pyms/crypt/fernet.py index 9e95864..04732f3 100644 --- a/pyms/crypt/fernet.py +++ b/pyms/crypt/fernet.py @@ -7,7 +7,7 @@ from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC -from pyms.constants import CRYPT_FILE_KEY_ENVIRONMENT, DEFAULT_KEY_FILENAME, CRYPT_FILE_KEY_ENVIRONMENT_LEGACY +from pyms.constants import CRYPT_FILE_KEY_ENVIRONMENT, CRYPT_FILE_KEY_ENVIRONMENT_LEGACY, DEFAULT_KEY_FILENAME from pyms.crypt.driver import CryptAbstract from pyms.exceptions import FileDoesNotExistException from pyms.utils.files import LoadFile @@ -24,15 +24,11 @@ def generate_key(self, password: Text, write_to_file: bool = False) -> bytes: byte_password = password.encode() # Convert to type bytes salt = os.urandom(16) kdf = PBKDF2HMAC( - algorithm=hashes.SHA512_256(), - length=32, - salt=salt, - iterations=100000, - backend=default_backend() + algorithm=hashes.SHA512_256(), length=32, salt=salt, iterations=100000, backend=default_backend() ) key = base64.urlsafe_b64encode(kdf.derive(byte_password)) # Can only use kdf once if write_to_file: - self._loader.put_file(key, 'wb') + self._loader.put_file(key, "wb") return key def read_key(self): @@ -42,7 +38,8 @@ def read_key(self): crypt_file_key_env = self.__get_updated_crypt_file_key_env() # Temporally backward compatibility raise FileDoesNotExistException( "Decrypt key {} not exists. You must set a correct env var {} " - "or run `pyms crypt create-key` command".format(self._loader.path, crypt_file_key_env)) + "or run `pyms crypt create-key` command".format(self._loader.path, crypt_file_key_env) + ) return key def encrypt(self, message): @@ -65,6 +62,8 @@ def delete_key(self): @staticmethod def __get_updated_crypt_file_key_env() -> str: result = CRYPT_FILE_KEY_ENVIRONMENT - if (os.getenv(CRYPT_FILE_KEY_ENVIRONMENT_LEGACY) is not None) and (os.getenv(CRYPT_FILE_KEY_ENVIRONMENT) is None): + if (os.getenv(CRYPT_FILE_KEY_ENVIRONMENT_LEGACY) is not None) and ( + os.getenv(CRYPT_FILE_KEY_ENVIRONMENT) is None + ): result = CRYPT_FILE_KEY_ENVIRONMENT_LEGACY return result diff --git a/pyms/exceptions.py b/pyms/exceptions.py index 2a2a5f0..3258c9b 100644 --- a/pyms/exceptions.py +++ b/pyms/exceptions.py @@ -23,3 +23,7 @@ class ConfigErrorException(Exception): class PackageNotExists(Exception): pass + + +class ServiceDiscoveryConnectionException(Exception): + pass diff --git a/pyms/flask/app/__init__.py b/pyms/flask/app/__init__.py index 6516942..cbe4ddd 100644 --- a/pyms/flask/app/__init__.py +++ b/pyms/flask/app/__init__.py @@ -1,5 +1,4 @@ from .create_app import Microservice from .create_config import config - -__all__ = ['Microservice', 'config'] +__all__ = ["Microservice", "config"] diff --git a/pyms/flask/app/create_app.py b/pyms/flask/app/create_app.py index 5bc1dab..272545e 100644 --- a/pyms/flask/app/create_app.py +++ b/pyms/flask/app/create_app.py @@ -6,14 +6,14 @@ from pyms.config.conf import validate_conf from pyms.config.resource import ConfigResource -from pyms.constants import LOGGER_NAME, CONFIG_BASE +from pyms.constants import CONFIG_BASE, LOGGER_NAME from pyms.crypt.driver import CryptResource -from pyms.flask.app.utils import SingletonMeta, ReverseProxied -from pyms.flask.healthcheck import healthcheck_blueprint +from pyms.flask.app.utils import ReverseProxied, SingletonMeta from pyms.flask.configreload import configreload_blueprint -from pyms.flask.services.driver import ServicesResource, DriverService +from pyms.flask.healthcheck import healthcheck_blueprint +from pyms.flask.services.driver import DriverService, ServicesResource from pyms.logger import CustomJsonFormatter -from pyms.utils import check_package_exists, import_from +from pyms.utils import check_package_exists logger = logging.getLogger(LOGGER_NAME) @@ -22,6 +22,7 @@ class Microservice(ConfigResource, metaclass=SingletonMeta): """The class Microservice is the core of all microservices built with PyMS. See this docs: https://python-microservices.github.io/ms_class/ """ + config_resource = CONFIG_BASE services: List[str] = [] application = Flask @@ -59,6 +60,12 @@ def init_services(self) -> None: self.services.append(service_name) setattr(self, service_name, service) + def init_services_actions(self): + for service_name in self.services: + srv_action = getattr(getattr(self, service_name), "init_action") + if srv_action: + srv_action(self) + def init_crypt(self, *args, **kwargs) -> None: """ Set the Attributes of all service defined in config.yml and exists in `pyms.flask.service` module @@ -85,22 +92,13 @@ def init_libs(self) -> Flask: """ return self.application - def init_tracer(self) -> None: - """Set attribute in flask `tracer`. See in `pyms.flask.services.tracer` how it works - :return: None - """ - if self.tracer: - FlaskTracing = import_from("flask_opentracing", "FlaskTracing") - client = self.tracer.get_client() - self.application.tracer = FlaskTracing(client, True, self.application) - def init_logger(self) -> None: """ Set a logger and return in JSON format. :return: """ self.application.logger = logger - os.environ['WERKZEUG_RUN_MAIN'] = "true" + os.environ["WERKZEUG_RUN_MAIN"] = "true" formatter = CustomJsonFormatter() formatter.add_service_name(self.application.config["APP_NAME"]) @@ -125,8 +123,11 @@ def init_app(self) -> Flask: application = self.swagger.init_app(config=self.config.to_flask(), path=self.path) else: check_package_exists("flask") - application = Flask(__name__, static_folder=os.path.join(self.path, 'static'), - template_folder=os.path.join(self.path, 'templates')) + application = Flask( + __name__, + static_folder=os.path.join(self.path, "static"), + template_folder=os.path.join(self.path, "templates"), + ) application.root_path = self.path @@ -135,18 +136,6 @@ def init_app(self) -> Flask: return application - def init_metrics(self) -> None: - """Set attribute in flask `metrics`. See in `pyms.flask.services.metrics` how it works - :return: None - """ - if self.metrics: - self.application.register_blueprint(self.metrics.metrics_blueprint) - self.metrics.add_logger_handler( - self.application.logger, - self.application.config["APP_NAME"] - ) - self.metrics.monitor(self.application.config["APP_NAME"], self.application) - def reload_conf(self): self.delete_services() self.config.reload() @@ -164,7 +153,6 @@ def create_app(self) -> Flask: """ self.application = self.init_app() self.application.config.from_object(self.config.to_flask()) - self.application.tracer = None self.application.ms = self # Initialize Blueprints @@ -173,12 +161,9 @@ def create_app(self) -> Flask: self.init_libs() self.add_error_handlers() - - self.init_tracer() - self.init_logger() - self.init_metrics() + self.init_services_actions() logger.debug("Started app with PyMS and this services: {}".format(self.services)) diff --git a/pyms/flask/app/utils.py b/pyms/flask/app/utils.py index c16f5bd..8f406f8 100644 --- a/pyms/flask/app/utils.py +++ b/pyms/flask/app/utils.py @@ -7,6 +7,7 @@ class SingletonMeta(type): possible methods include: base class, decorator, metaclass. We will use the metaclass because it is best suited for this purpose. """ + _instances: Dict[type, type] = {} _singleton = True @@ -43,10 +44,10 @@ def _extract_prefix(environ: dict) -> str: :return: """ # Get path from Traefik, Nginx and Apache - path = environ.get('HTTP_X_SCRIPT_NAME', '') + path = environ.get("HTTP_X_SCRIPT_NAME", "") if not path: # Get path from Zuul - path = environ.get('HTTP_X_FORWARDED_PREFIX', '') + path = environ.get("HTTP_X_FORWARDED_PREFIX", "") if path and not path.startswith("/"): path = "/" + path return path @@ -54,12 +55,12 @@ def _extract_prefix(environ: dict) -> str: def __call__(self, environ, start_response): script_name = self._extract_prefix(environ) if script_name: - environ['SCRIPT_NAME'] = script_name - path_info = environ['PATH_INFO'] + environ["SCRIPT_NAME"] = script_name + path_info = environ["PATH_INFO"] if path_info.startswith(script_name): - environ['PATH_INFO'] = path_info[len(script_name):] + environ["PATH_INFO"] = path_info[len(script_name) :] # noqa: E203 - scheme = environ.get('HTTP_X_SCHEME', '') + scheme = environ.get("HTTP_X_SCHEME", "") if scheme: - environ['wsgi.url_scheme'] = scheme + environ["wsgi.url_scheme"] = scheme return self.app(environ, start_response) diff --git a/pyms/flask/configreload/__init__.py b/pyms/flask/configreload/__init__.py index 5629da3..482976e 100644 --- a/pyms/flask/configreload/__init__.py +++ b/pyms/flask/configreload/__init__.py @@ -1,4 +1,3 @@ from .configreload import configreload_blueprint - -__all__ = ['configreload_blueprint'] +__all__ = ["configreload_blueprint"] diff --git a/pyms/flask/configreload/configreload.py b/pyms/flask/configreload/configreload.py index c18e7cc..55e44dc 100644 --- a/pyms/flask/configreload/configreload.py +++ b/pyms/flask/configreload/configreload.py @@ -1,11 +1,11 @@ -from __future__ import unicode_literals, print_function, absolute_import, division -from flask import Blueprint -from flask import current_app +from __future__ import absolute_import, division, print_function, unicode_literals -configreload_blueprint = Blueprint('configreload', __name__, static_url_path='/static') +from flask import Blueprint, current_app +configreload_blueprint = Blueprint("configreload", __name__, static_url_path="/static") -@configreload_blueprint.route('/reload-config', methods=['POST']) + +@configreload_blueprint.route("/reload-config", methods=["POST"]) def reloadconfig(): """ Reread configuration from file. diff --git a/pyms/flask/healthcheck/__init__.py b/pyms/flask/healthcheck/__init__.py index 931a15e..1fdbbf8 100644 --- a/pyms/flask/healthcheck/__init__.py +++ b/pyms/flask/healthcheck/__init__.py @@ -1,4 +1,3 @@ from .healthcheck import healthcheck_blueprint - -__all__ = ['healthcheck_blueprint'] +__all__ = ["healthcheck_blueprint"] diff --git a/pyms/flask/healthcheck/healthcheck.py b/pyms/flask/healthcheck/healthcheck.py index 3022740..de2bd41 100644 --- a/pyms/flask/healthcheck/healthcheck.py +++ b/pyms/flask/healthcheck/healthcheck.py @@ -1,11 +1,11 @@ -from __future__ import unicode_literals, print_function, absolute_import, division +from __future__ import absolute_import, division, print_function, unicode_literals from flask import Blueprint -healthcheck_blueprint = Blueprint('healthcheck', __name__, static_url_path='/static') +healthcheck_blueprint = Blueprint("healthcheck", __name__, static_url_path="/static") -@healthcheck_blueprint.route('/healthcheck', methods=['GET']) +@healthcheck_blueprint.route("/healthcheck", methods=["GET"]) def healthcheck(): """Set a healthcheck to help other service to discover this microservice, like Kubernetes, AWS ELB, etc. :return: diff --git a/pyms/flask/services/driver.py b/pyms/flask/services/driver.py index dd01428..b6cb8e5 100644 --- a/pyms/flask/services/driver.py +++ b/pyms/flask/services/driver.py @@ -1,9 +1,9 @@ import logging -from typing import Text, Tuple, Iterator +from typing import Iterator, Text, Tuple from pyms.config import ConfFile from pyms.config.resource import ConfigResource -from pyms.constants import SERVICE_BASE, LOGGER_NAME +from pyms.constants import LOGGER_NAME, SERVICE_BASE from pyms.utils import import_from logger = logging.getLogger(LOGGER_NAME) @@ -19,16 +19,22 @@ class DriverService(ConfigResource): * https://python-microservices.github.io/configuration/ * https://python-microservices.github.io/services/ """ + enabled = True + init_action = False + def __init__(self, *args, **kwargs): self.config_resource = get_service_name(service=self.config_resource) super().__init__(*args, **kwargs) def __getattr__(self, attr, *args, **kwargs): config_attribute = getattr(self.config, attr) - return config_attribute if config_attribute == "" or config_attribute != {} else self.default_values.get(attr, - None) + return ( + config_attribute + if config_attribute == "" or config_attribute != {} + else self.default_values.get(attr, None) + ) def is_enabled(self) -> bool: return self.enabled @@ -44,6 +50,7 @@ class ServicesResource(ConfigResource): * https://python-microservices.github.io/configuration/ * https://python-microservices.github.io/services/ """ + config_resource = SERVICE_BASE def get_services(self) -> Iterator[Tuple[Text, DriverService]]: diff --git a/pyms/flask/services/metrics.py b/pyms/flask/services/metrics.py index 744da4b..0093cbf 100644 --- a/pyms/flask/services/metrics.py +++ b/pyms/flask/services/metrics.py @@ -1,10 +1,9 @@ -import time import logging +import time -from typing import Text +from flask import Blueprint, Flask, Response, request +from prometheus_client import REGISTRY, CollectorRegistry, Counter, Histogram, generate_latest, multiprocess -from flask import Blueprint, Response, request, Flask -from prometheus_client import multiprocess, Counter, Histogram, generate_latest, CollectorRegistry, REGISTRY from pyms.flask.services.driver import DriverService # Based on https://github.com/sbarratt/flask-prometheus @@ -24,7 +23,7 @@ ) -class FlaskMetricsWrapper(): +class FlaskMetricsWrapper: def __init__(self, app_name): self.app_name = app_name @@ -47,7 +46,6 @@ class Service(DriverService): """ Adds [Prometheus](https://prometheus.io/) metrics using the [Prometheus Client Library](https://github.com/prometheus/client_python). """ - config_resource: Text = "metrics" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -55,6 +53,13 @@ def __init__(self, *args, **kwargs): self.init_registry() self.serve_metrics() + def init_action(self, microservice_instance): + microservice_instance.application.register_blueprint(microservice_instance.metrics.metrics_blueprint) + self.add_logger_handler( + microservice_instance.application.logger, microservice_instance.application.config["APP_NAME"] + ) + self.monitor(microservice_instance.application.config["APP_NAME"], microservice_instance.application) + def init_registry(self) -> None: try: multiprocess_registry = CollectorRegistry() diff --git a/pyms/flask/services/requests.py b/pyms/flask/services/requests.py index 75deea8..2d34936 100644 --- a/pyms/flask/services/requests.py +++ b/pyms/flask/services/requests.py @@ -45,6 +45,7 @@ class Service(DriverService): Encapsulate common rest operations between business services propagating trace headers if set up. All default values keys are created as class attributes in `DriverService` """ + config_resource = "requests" default_values = { "data": "", @@ -72,8 +73,8 @@ def requests(self, session: requests.Session) -> requests.Session: status_forcelist=self.status_retries, ) adapter = HTTPAdapter(max_retries=max_retries) - session_r.mount('http://', adapter) - session_r.mount('https://', adapter) + session_r.mount("http://", adapter) + session_r.mount("https://", adapter) return session_r @staticmethod @@ -143,8 +144,15 @@ def parse_response(self, response: Response) -> dict: return {} @retry - def get(self, url: str, path_params: dict = None, params: dict = None, headers: dict = None, - propagate_headers: bool = False, **kwargs) -> Response: + def get( + self, + url: str, + path_params: dict = None, + params: dict = None, + headers: dict = None, + propagate_headers: bool = False, + **kwargs + ) -> Response: """Sends a GET request. :param url: URL for the new :class:`Request` object. Could contain path parameters @@ -161,16 +169,16 @@ def get(self, url: str, path_params: dict = None, params: dict = None, headers: full_url = self._build_url(url, path_params) headers = self._get_headers(headers=headers, propagate_headers=propagate_headers) headers = self.insert_trace_headers(headers) - logger.debug("Get with url {}, params {}, headers {}, kwargs {}". - format(full_url, params, headers, kwargs)) + logger.debug("Get with url {}, params {}, headers {}, kwargs {}".format(full_url, params, headers, kwargs)) session = requests.Session() response = self.requests(session=session).get(full_url, params=params, headers=headers, **kwargs) return response - def get_for_object(self, url: str, path_params: dict = None, params: dict = None, headers: dict = None, - **kwargs) -> dict: + def get_for_object( + self, url: str, path_params: dict = None, params: dict = None, headers: dict = None, **kwargs + ) -> dict: """Sends a GET request and returns the json representation found in response's content data node. :param url: URL for the new :class:`Request` object. Could contain path parameters @@ -187,8 +195,9 @@ def get_for_object(self, url: str, path_params: dict = None, params: dict = None return self.parse_response(response) @retry - def post(self, url: str, path_params: dict = None, data: dict = None, json: dict = None, headers: dict = None, - **kwargs) -> Response: + def post( + self, url: str, path_params: dict = None, data: dict = None, json: dict = None, headers: dict = None, **kwargs + ) -> Response: """Sends a POST request. :param url: URL for the new :class:`Request` object. Could contain path parameters @@ -205,8 +214,9 @@ def post(self, url: str, path_params: dict = None, data: dict = None, json: dict full_url = self._build_url(url, path_params) headers = self._get_headers(headers) headers = self.insert_trace_headers(headers) - logger.debug("Post with url {}, data {}, json {}, headers {}, kwargs {}".format(full_url, data, json, - headers, kwargs)) + logger.debug( + "Post with url {}, data {}, json {}, headers {}, kwargs {}".format(full_url, data, json, headers, kwargs) + ) session = requests.Session() response = self.requests(session=session).post(full_url, data=data, json=json, headers=headers, **kwargs) @@ -214,8 +224,9 @@ def post(self, url: str, path_params: dict = None, data: dict = None, json: dict return response - def post_for_object(self, url: str, path_params: dict = None, data: dict = None, json: dict = None, - headers: dict = None, **kwargs) -> dict: + def post_for_object( + self, url: str, path_params: dict = None, data: dict = None, json: dict = None, headers: dict = None, **kwargs + ) -> dict: """Sends a POST request and returns the json representation found in response's content data node. :param url: URL for the new :class:`Request` object. Could contain path parameters @@ -250,8 +261,7 @@ def put(self, url: str, path_params: dict = None, data: dict = None, headers: di full_url = self._build_url(url, path_params) headers = self._get_headers(headers) headers = self.insert_trace_headers(headers) - logger.debug("Put with url {}, data {}, headers {}, kwargs {}".format(full_url, data, headers, - kwargs)) + logger.debug("Put with url {}, data {}, headers {}, kwargs {}".format(full_url, data, headers, kwargs)) session = requests.Session() response = self.requests(session=session).put(full_url, data, headers=headers, **kwargs) @@ -259,8 +269,9 @@ def put(self, url: str, path_params: dict = None, data: dict = None, headers: di return response - def put_for_object(self, url: str, path_params: dict = None, data: dict = None, headers: dict = None, - **kwargs) -> dict: + def put_for_object( + self, url: str, path_params: dict = None, data: dict = None, headers: dict = None, **kwargs + ) -> dict: """Sends a PUT request and returns the json representation found in response's content data node. :param url: URL for the new :class:`Request` object. Could contain path parameters @@ -295,8 +306,7 @@ def patch(self, url: str, path_params: dict = None, data: dict = None, headers: full_url = self._build_url(url, path_params) headers = self._get_headers(headers) headers = self.insert_trace_headers(headers) - logger.debug("Patch with url {}, data {}, headers {}, kwargs {}".format(full_url, data, headers, - kwargs)) + logger.debug("Patch with url {}, data {}, headers {}, kwargs {}".format(full_url, data, headers, kwargs)) session = requests.Session() response = self.requests(session=session).patch(full_url, data, headers=headers, **kwargs) @@ -304,8 +314,9 @@ def patch(self, url: str, path_params: dict = None, data: dict = None, headers: return response - def patch_for_object(self, url: str, path_params: dict = None, data: dict = None, headers: dict = None, - **kwargs) -> dict: + def patch_for_object( + self, url: str, path_params: dict = None, data: dict = None, headers: dict = None, **kwargs + ) -> dict: """Sends a PATCH request and returns the json representation found in response's content data node. :param url: URL for the new :class:`Request` object. Could contain path parameters diff --git a/pyms/flask/services/service_discovery.py b/pyms/flask/services/service_discovery.py new file mode 100644 index 0000000..b546bd1 --- /dev/null +++ b/pyms/flask/services/service_discovery.py @@ -0,0 +1,73 @@ +import logging + +try: + import consulate +except ModuleNotFoundError: # pragma: no cover + consulate = None + +from pyms.constants import LOGGER_NAME +from pyms.flask.services.driver import DriverService +from pyms.utils import import_from + +logger = logging.getLogger(LOGGER_NAME) + +CONSUL_SERVICE_DISCOVERY = "consul" + +DEFAULT_SERVICE_DISCOVERY = CONSUL_SERVICE_DISCOVERY + + +class ServiceDiscoveryBase: + client = None + + def __init__(self, config): + pass + + def register_service(self, *args, **kwargs): + pass + + +class ServiceDiscoveryConsul(ServiceDiscoveryBase): + def __init__(self, config): + super().__init__(config) + self.client = consulate.Consul( + host=config.host, port=config.port, token=config.token, scheme=config.scheme, adapter=config.adapter + ) + + def register_service(self, *args, **kwargs): + self.client.agent.check.register( + kwargs["app_name"], http=kwargs["healtcheck_url"], interval=kwargs.get("interval", "10s") + ) + + +class Service(DriverService): + config_resource = "service_discovery" + default_values = { + "service": DEFAULT_SERVICE_DISCOVERY, + "host": "localhost", + "scheme": "http", + "port": 8500, + "healtcheck_url": "http://127.0.0.1.nip.io:5000/healthcheck", + "interval": "10s", + "autoregister": False, + } + + def init_action(self, microservice_instance): + if self.autoregister: + app_name = microservice_instance.application.config["APP_NAME"] + self._client.register_service(healtcheck_url=self.healtcheck_url, app_name=app_name, interval=self.interval) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._client = self.get_client() + self.client = self._client.client + + def get_client(self) -> ServiceDiscoveryBase: + if self.service == CONSUL_SERVICE_DISCOVERY: + client = ServiceDiscoveryConsul(self) + else: + service_paths = self.service.split(".") + package = ".".join(service_paths[:-1]) + client = import_from(package, service_paths[-1])(self) + + logger.debug("Init %s as service discovery", client) + return client diff --git a/pyms/flask/services/swagger.py b/pyms/flask/services/swagger.py index 501cf9b..1223ad1 100644 --- a/pyms/flask/services/swagger.py +++ b/pyms/flask/services/swagger.py @@ -1,11 +1,10 @@ import os from pathlib import Path -from typing import Dict, Any - -from flask import Flask +from typing import Any, Dict import connexion from connexion.resolver import RestyResolver +from flask import Flask try: import prance @@ -29,8 +28,7 @@ def get_bundled_specs(main_file: Path) -> Dict[str, Any]: :param main_file: Swagger file path :return: """ - parser = prance.ResolvingParser(str(main_file.absolute()), - lazy=True, backend='openapi-spec-validator') + parser = prance.ResolvingParser(str(main_file.absolute()), lazy=True, backend="openapi-spec-validator") parser.parse() return parser.specification @@ -42,15 +40,13 @@ def merge_swagger_file(main_file: str) -> None: :return: """ input_file = Path(main_file) - output_file = Path(input_file.parent, 'swagger-complete.yaml') + output_file = Path(input_file.parent, "swagger-complete.yaml") contents = formats.serialize_spec( specs=get_bundled_specs(input_file), filename=output_file, ) - fs.write_file(filename=output_file, - contents=contents, - encoding='utf-8') + fs.write_file(filename=output_file, contents=contents, encoding="utf-8") class Service(DriverService): @@ -64,13 +60,9 @@ class Service(DriverService): All default values keys are created as class attributes in `DriverService` """ + config_resource = "swagger" - default_values = { - "path": SWAGGER_PATH, - "file": SWAGGER_FILE, - "url": SWAGGER_URL, - "project_dir": PROJECT_DIR - } + default_values = {"path": SWAGGER_PATH, "file": SWAGGER_FILE, "url": SWAGGER_URL, "project_dir": PROJECT_DIR} @staticmethod def _get_application_root(config) -> str: @@ -110,14 +102,13 @@ def init_app(self, config, path: Path) -> Flask: if not os.path.isabs(self.path): specification_dir = os.path.join(path, self.path) - app = connexion.App(__name__, - specification_dir=specification_dir, - resolver=RestyResolver(self.project_dir)) + app = connexion.App(__name__, specification_dir=specification_dir, resolver=RestyResolver(self.project_dir)) params = { - "specification": get_bundled_specs( - Path(os.path.join(specification_dir, self.file))) if prance else self.file, - "arguments": {'title': config.APP_NAME}, + "specification": get_bundled_specs(Path(os.path.join(specification_dir, self.file))) + if prance + else self.file, + "arguments": {"title": config.APP_NAME}, "base_path": application_root, "options": {"swagger_url": self.url}, } diff --git a/pyms/flask/services/tracer.py b/pyms/flask/services/tracer.py index 4a5dbd4..24629f2 100644 --- a/pyms/flask/services/tracer.py +++ b/pyms/flask/services/tracer.py @@ -1,7 +1,6 @@ import logging from typing import Union - try: import opentracing except ModuleNotFoundError: # pragma: no cover @@ -15,12 +14,12 @@ except ModuleNotFoundError: # pragma: no cover get_current_span = None -from flask import current_app, request, has_request_context +from flask import current_app, has_request_context, request from pyms.config.conf import get_conf from pyms.constants import LOGGER_NAME from pyms.flask.services.driver import DriverService, get_service_name -from pyms.utils import check_package_exists, import_package, import_from +from pyms.utils import check_package_exists, import_from, import_package logger = logging.getLogger(LOGGER_NAME) @@ -53,11 +52,17 @@ class Service(DriverService): Add trace to all executions with [opentracing](https://github.com/opentracing-contrib/python-flask). All default values keys are created as class attributes in `DriverService` """ + config_resource = "tracer" default_values = { "client": DEFAULT_CLIENT, } + def init_action(self, microservice_instance): + FlaskTracing = import_from("flask_opentracing", "FlaskTracing") + client = self.get_client() + microservice_instance.application.tracer = FlaskTracing(client, True, microservice_instance.application) + def get_client(self) -> Union[bool, type]: opentracing_tracer = False if self.config.client == JAEGER_CLIENT: @@ -78,11 +83,7 @@ def init_jaeger_tracer(self): Config = import_from("jaeger_client", "Config") host = {} if self.host: - host = { - 'local_agent': { - 'reporting_host': self.host - } - } + host = {"local_agent": {"reporting_host": self.host}} metrics_config = get_conf(service=get_service_name(service="metrics"), empty_init=True) metrics = "" if metrics_config: @@ -91,17 +92,18 @@ def init_jaeger_tracer(self): config = Config( config={ **{ - 'sampler': { - 'type': 'const', - 'param': 1, + "sampler": { + "type": "const", + "param": 1, }, - 'propagation': 'b3', - 'logging': True + "propagation": "b3", + "logging": True, }, - **host - }, service_name=self.component_name, + **host, + }, + service_name=self.component_name, metrics_factory=metrics, - validate=True + validate=True, ) return config.initialize_tracer() diff --git a/pyms/logger/__init__.py b/pyms/logger/__init__.py index 621faa3..bfcd552 100644 --- a/pyms/logger/__init__.py +++ b/pyms/logger/__init__.py @@ -2,4 +2,6 @@ """ from .logger import CustomJsonFormatter -__all__ = ['CustomJsonFormatter', ] +__all__ = [ + "CustomJsonFormatter", +] diff --git a/pyms/logger/logger.py b/pyms/logger/logger.py index 42c1e40..5e6491c 100644 --- a/pyms/logger/logger.py +++ b/pyms/logger/logger.py @@ -17,21 +17,21 @@ class CustomJsonFormatter(jsonlogger.JsonFormatter): def add_fields(self, log_record, record, message_dict): super().add_fields(log_record, record, message_dict) - if not log_record.get('timestamp'): + if not log_record.get("timestamp"): # this doesn't use record.created, so it is slightly off - now = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ') - log_record['timestamp'] = now - if log_record.get('level'): - log_record['severity'] = log_record['level'].upper() + now = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ") + log_record["timestamp"] = now + if log_record.get("level"): + log_record["severity"] = log_record["level"].upper() else: - log_record['severity'] = record.levelname + log_record["severity"] = record.levelname log_record["service"] = self.service_name try: headers = inject_span_in_headers({}) - log_record["trace"] = headers.get('X-B3-TraceId', "") - log_record["span"] = headers.get('X-B3-SpanId', "") - log_record["parent"] = headers.get('X-B3-ParentSpanId', "") + log_record["trace"] = headers.get("X-B3-TraceId", "") + log_record["span"] = headers.get("X-B3-SpanId", "") + log_record["parent"] = headers.get("X-B3-ParentSpanId", "") except Exception as ex: # pragma: no cover logger.error("Tracer error: {}".format(ex)) diff --git a/pyms/utils/__init__.py b/pyms/utils/__init__.py index 1583c00..98f0535 100644 --- a/pyms/utils/__init__.py +++ b/pyms/utils/__init__.py @@ -1,3 +1,3 @@ -from .utils import import_from, import_package, check_package_exists +from .utils import check_package_exists, import_from, import_package -__all__ = ['import_from', 'import_package', 'check_package_exists'] +__all__ = ["import_from", "import_package", "check_package_exists"] diff --git a/pyms/utils/files.py b/pyms/utils/files.py index a9e2244..7a0bebb 100644 --- a/pyms/utils/files.py +++ b/pyms/utils/files.py @@ -49,7 +49,7 @@ def _get_conf_from_file(self, path, fn=None): if fn: files_cached[path] = fn(path) else: - file_to_read = open(path, 'rb') + file_to_read = open(path, "rb") content = file_to_read.read() # The key will be type bytes file_to_read.close() files_cached[path] = content diff --git a/pyms/utils/utils.py b/pyms/utils/utils.py index 9fee53b..93a8b49 100644 --- a/pyms/utils/utils.py +++ b/pyms/utils/utils.py @@ -1,6 +1,6 @@ import importlib import importlib.util -from typing import Union, Text +from typing import Text, Union from pyms.exceptions import PackageNotExists @@ -31,7 +31,8 @@ def check_package_exists(package_name: Text) -> Union[Exception, bool]: spec = importlib.util.find_spec(package_name) if spec is None: raise PackageNotExists( - "{package} is not installed. try with pip install -U {package}".format(package=package_name)) + "{package} is not installed. try with pip install -U {package}".format(package=package_name) + ) return True diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e6c68b5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,16 @@ +[tool.black] +line-length = 120 +include = '\.pyi?$' +exclude = ''' +/( + \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist +)/ +''' \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 0b832bb..e45920b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,6 +4,10 @@ test = pytest [tool:pytest] addopts = --cov=pyms --cov=tests tests/ +[isort] +profile = black +line_length = 120 + [flake8] ignore = E501 exclude = .git,__pycache__,docs/source/conf.py,old,build,dist diff --git a/setup.py b/setup.py index 7d0a347..aaa4d35 100644 --- a/setup.py +++ b/setup.py @@ -2,74 +2,87 @@ import codecs import os -from setuptools import setup, find_packages +from setuptools import find_packages, setup -author = __import__('pyms').__author__ -author_email = __import__('pyms').__email__ -version = __import__('pyms').__version__ +author = __import__("pyms").__author__ +author_email = __import__("pyms").__email__ +version = __import__("pyms").__version__ -if os.path.exists('README.md'): - long_description = codecs.open('README.md', 'r', 'utf-8').read() +if os.path.exists("README.md"): + long_description = codecs.open("README.md", "r", "utf-8").read() else: - long_description = '' + long_description = "" install_min_requires = [ - 'flask>=1.1.2', - 'python-json-logger>=2.0.0', - 'pyyaml>=5.3.1', - 'anyconfig>=0.9.11', - 'cryptography>=3.2', + "flask>=1.1.2", + "python-json-logger>=2.0.0", + "pyyaml>=5.3.1", + "anyconfig>=0.9.11", + "cryptography>=3.2", ] install_crypt_requires = [ - 'cryptography>=3.1.1', + "cryptography>=3.1.1", ] install_aws_requires = [ - 'boto3>=1.15.6', + "boto3>=1.15.6", ] - install_request_requires = [ - 'requests>=2.24.0', + "requests>=2.24.0", ] + install_swagger_requires = [ - 'connexion[swagger-ui]>=2.7.0', - 'swagger-ui-bundle>=0.0.6', - 'semver>=2.10.1', - 'prance>=0.18.2', + "connexion[swagger-ui]>=2.7.0", + "swagger-ui-bundle>=0.0.6", + "semver>=2.10.1", + "prance>=0.18.2", ] install_traces_requires = [ - 'jaeger-client>=4.3.0', - 'flask-opentracing>=1.1.0', - 'opentracing>=2.1', - 'opentracing-instrumentation>=3.2.1', - 'tornado>=4.3,<6.0' + "jaeger-client>=4.3.0", + "flask-opentracing>=1.1.0", + "opentracing>=2.1", + "opentracing-instrumentation>=3.2.1", + "tornado>=4.3,<6.0", ] install_metrics_requires = [ - 'prometheus_client>=0.8.0', + "prometheus_client>=0.8.0", +] + +install_service_discovery_requires = [ + "py-ms-consulate>=1.0.0", ] install_tests_requires = [ - 'requests-mock>=1.8.0', - 'coverage>=5.3', - 'pytest>=6.1.0', - 'pytest-cov>=2.10.1', - 'pylint>=2.6.0', - 'flake8>=3.8.2', - 'tox>=3.20.0', - 'bandit>=1.6.2', - 'mkdocs>=1.1.2', - 'lightstep>=4.4.8', - 'safety==1.9.0', - 'mypy>=0.782' + "requests-mock>=1.8.0", + "coverage>=5.3", + "pytest>=6.1.0", + "pytest-cov>=2.10.1", + "pylint>=2.6.0", + "flake8>=3.8.2", + "tox>=3.20.0", + "bandit>=1.6.2", + "mkdocs>=1.1.2", + "lightstep>=4.4.8", + "safety==1.9.0", + "mypy>=0.782", + "pre-commit>=2.8.1", + "black>=20.8b1", + "isort>=5.6.4", ] -install_all_requires = (install_request_requires + install_swagger_requires + - install_traces_requires + install_metrics_requires + install_crypt_requires + - install_aws_requires) +install_all_requires = ( + install_request_requires + + install_swagger_requires + + install_traces_requires + + install_metrics_requires + + install_crypt_requires + + install_aws_requires + + install_service_discovery_requires +) setup( name="py-ms", @@ -78,10 +91,10 @@ author_email=author_email, description="Library of utils to create REST Python Microservices", long_description=long_description, - long_description_content_type='text/markdown', + long_description_content_type="text/markdown", classifiers=[ - 'Development Status :: 4 - Beta', - 'Framework :: Flask', + "Development Status :: 4 - Beta", + "Framework :: Flask", "Intended Audience :: Developers", "Natural Language :: English", "Programming Language :: Python :: 3.6", @@ -91,29 +104,27 @@ license="License :: OSI Approved :: GNU General Public License v3 (GPLv3)", platforms=["any"], keywords="", - url='https://github.com/python-microservices/pyms/', + url="https://github.com/python-microservices/pyms/", packages=find_packages( - exclude=['*.tests', '*.tests.*', 'tests.*', 'tests', '*.examples', '*.examples.*', 'examples.*', 'examples']), + exclude=["*.tests", "*.tests.*", "tests.*", "tests", "*.examples", "*.examples.*", "examples.*", "examples"] + ), setup_requires=[ - 'pytest-runner>=5.2', + "pytest-runner>=5.2", ], install_requires=install_min_requires, extras_require={ - 'all': install_all_requires, - 'request': install_request_requires, - 'swagger': install_swagger_requires, - 'traces': install_traces_requires, - 'metrics': install_metrics_requires, - 'crypt': install_crypt_requires, - 'aws': install_aws_requires, - 'tests': install_tests_requires, + "all": install_all_requires, + "request": install_request_requires, + "swagger": install_swagger_requires, + "traces": install_traces_requires, + "metrics": install_metrics_requires, + "crypt": install_crypt_requires, + "aws": install_aws_requires, + "consul": install_service_discovery_requires, + "tests": install_tests_requires, }, tests_require=install_all_requires + install_tests_requires, include_package_data=True, - entry_points={ - 'console_scripts': [ - 'pyms = pyms.cmd.main:Command' - ] - }, + entry_points={"console_scripts": ["pyms = pyms.cmd.main:Command"]}, zip_safe=True, ) diff --git a/tests/common.py b/tests/common.py index 4d8ecbc..cb52cf9 100644 --- a/tests/common.py +++ b/tests/common.py @@ -1,6 +1,8 @@ import os -from pyms.flask.app import Microservice + from pyms.constants import CONFIGMAP_FILE_ENVIRONMENT, DEFAULT_CONFIGMAP_FILENAME +from pyms.flask.app import Microservice + class MyMicroserviceNoSingleton(Microservice): _singleton = False diff --git a/tests/config-tests-service-discovery-consul.yml b/tests/config-tests-service-discovery-consul.yml new file mode 100644 index 0000000..3d79d58 --- /dev/null +++ b/tests/config-tests-service-discovery-consul.yml @@ -0,0 +1,9 @@ +pyms: + services: + service_discovery: + service: "consul" + autoregister: true + config: + DEBUG: true + TESTING: true + APP_NAME: "Python Microservice Service Discovery" \ No newline at end of file diff --git a/tests/config-tests-service-discovery.yml b/tests/config-tests-service-discovery.yml new file mode 100644 index 0000000..ad6dea9 --- /dev/null +++ b/tests/config-tests-service-discovery.yml @@ -0,0 +1,9 @@ +pyms: + services: + service_discovery: + service: "tests.test_service_discovery.MockServiceDiscovery" + autoregister: true + config: + DEBUG: true + TESTING: true + APP_NAME: "Python Microservice Service Discovery" \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_cmd.py b/tests/test_cmd.py index 1f5cb81..817bbd1 100644 --- a/tests/test_cmd.py +++ b/tests/test_cmd.py @@ -1,15 +1,16 @@ """Test common rest operations wrapper. """ import os -from pathlib import Path import unittest +from pathlib import Path from unittest.mock import patch import pytest from prance.util.url import ResolutionError + from pyms.cmd import Command -from pyms.exceptions import FileDoesNotExistException, PackageNotExists from pyms.crypt.fernet import Crypt +from pyms.exceptions import FileDoesNotExistException, PackageNotExists from pyms.flask.services.swagger import get_bundled_specs from tests.common import remove_conf_file @@ -22,9 +23,10 @@ def test_crypt_file_error(self): cmd = Command(arguments=arguments, autorun=False) with pytest.raises(FileDoesNotExistException) as excinfo: cmd.run() - assert ("Decrypt key None not exists. You must set a correct env var PYMS_KEY_FILE or run " - "`pyms crypt create-key` command") \ - in str(excinfo.value) + assert ( + "Decrypt key None not exists. You must set a correct env var PYMS_KEY_FILE or run " + "`pyms crypt create-key` command" + ) in str(excinfo.value) def test_crypt_file_ok(self): crypt = Crypt() @@ -34,18 +36,22 @@ def test_crypt_file_ok(self): cmd.run() crypt.delete_key() - @patch('pyms.cmd.main.Command.get_input', return_value='Y') + @patch("pyms.cmd.main.Command.get_input", return_value="Y") def test_generate_file_ok(self, input): crypt = Crypt() - arguments = ["create-key", ] + arguments = [ + "create-key", + ] cmd = Command(arguments=arguments, autorun=False) cmd.run() crypt.delete_key() - @patch('pyms.cmd.main.Command.get_input', return_value='n') + @patch("pyms.cmd.main.Command.get_input", return_value="n") def test_output_key(self, input): crypt = Crypt() - arguments = ["create-key", ] + arguments = [ + "create-key", + ] cmd = Command(arguments=arguments, autorun=False) cmd.run() with pytest.raises(FileNotFoundError) as excinfo: @@ -61,23 +67,29 @@ def test_startproject_error(self): def test_get_bundled_specs(self): specs = get_bundled_specs(Path("tests/swagger_for_tests/swagger.yaml")) - self.assertEqual(specs.get('swagger'), "2.0") - self.assertEqual(specs.get('info').get('version'), "1.0.0") - self.assertEqual(specs.get('info').get('contact').get('email'), "apiteam@swagger.io") + self.assertEqual(specs.get("swagger"), "2.0") + self.assertEqual(specs.get("info").get("version"), "1.0.0") + self.assertEqual(specs.get("info").get("contact").get("email"), "apiteam@swagger.io") def test_merge_swagger_ok(self): - arguments = ["merge-swagger", "--file", "tests/swagger_for_tests/swagger.yaml", ] + arguments = [ + "merge-swagger", + "--file", + "tests/swagger_for_tests/swagger.yaml", + ] cmd = Command(arguments=arguments, autorun=False) assert cmd.run() os.remove("tests/swagger_for_tests/swagger-complete.yaml") def test_merge_swagger_error(self): - arguments = ["merge-swagger", ] + arguments = [ + "merge-swagger", + ] cmd = Command(arguments=arguments, autorun=False) with pytest.raises(ResolutionError) as excinfo: cmd.run() - @patch('pyms.cmd.main.Command.yes_no_input', return_value=True) + @patch("pyms.cmd.main.Command.yes_no_input", return_value=True) def test_create_config_all(self, input): # Remove config file if already exists for test remove_conf_file() diff --git a/tests/test_config.py b/tests/test_config.py index e0f8e4f..f7bf97a 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -3,12 +3,22 @@ import unittest from unittest import mock -from pyms.config import get_conf, ConfFile, create_conf_file +from pyms.config import ConfFile, create_conf_file, get_conf from pyms.config.conf import validate_conf -from pyms.constants import CONFIGMAP_FILE_ENVIRONMENT, CONFIGMAP_FILE_ENVIRONMENT_LEGACY, LOGGER_NAME, CONFIG_BASE, \ - CRYPT_FILE_KEY_ENVIRONMENT, CRYPT_FILE_KEY_ENVIRONMENT_LEGACY -from pyms.exceptions import AttrDoesNotExistException, ConfigDoesNotFoundException, ServiceDoesNotExistException, \ - ConfigErrorException +from pyms.constants import ( + CONFIG_BASE, + CONFIGMAP_FILE_ENVIRONMENT, + CONFIGMAP_FILE_ENVIRONMENT_LEGACY, + CRYPT_FILE_KEY_ENVIRONMENT, + CRYPT_FILE_KEY_ENVIRONMENT_LEGACY, + LOGGER_NAME, +) +from pyms.exceptions import ( + AttrDoesNotExistException, + ConfigDoesNotFoundException, + ConfigErrorException, + ServiceDoesNotExistException, +) logger = logging.getLogger(LOGGER_NAME) @@ -133,7 +143,7 @@ def test_default_flask(self): assert config.APP_NAME == "Python Microservice" assert config.SUBSERVICE1.test == "input" - @mock.patch('pyms.config.conf.ConfFile') + @mock.patch("pyms.config.conf.ConfFile") def test_without_params(self, mock_confile): with self.assertRaises(ServiceDoesNotExistException): get_conf() diff --git a/tests/test_crypt.py b/tests/test_crypt.py index 8c4ebe7..09bb60c 100644 --- a/tests/test_crypt.py +++ b/tests/test_crypt.py @@ -7,8 +7,13 @@ from pyms.cloud.aws.kms import Crypt as CryptAws from pyms.config import get_conf -from pyms.constants import LOGGER_NAME, CONFIGMAP_FILE_ENVIRONMENT, CRYPT_FILE_KEY_ENVIRONMENT, CONFIG_BASE, \ - CRYPT_FILE_KEY_ENVIRONMENT_LEGACY +from pyms.constants import ( + CONFIG_BASE, + CONFIGMAP_FILE_ENVIRONMENT, + CRYPT_FILE_KEY_ENVIRONMENT, + CRYPT_FILE_KEY_ENVIRONMENT_LEGACY, + LOGGER_NAME, +) from pyms.crypt.driver import CryptAbstract, CryptResource from pyms.crypt.fernet import Crypt as CryptFernet from pyms.exceptions import FileDoesNotExistException @@ -30,12 +35,12 @@ def decrypt(self, encrypted): class CryptTests(unittest.TestCase): - def test_ko(self): with pytest.raises(TypeError) as excinfo: MockDecrypt() assert "Can't instantiate abstract class MockDecrypt with abstract methods decrypt, encrypt" in str( - excinfo.value) + excinfo.value + ) def test_ko_enCryptFernet(self): crypt = MockDecrypt2() @@ -57,9 +62,10 @@ def test_crypt_file_error(self): crypt = CryptFernet() with pytest.raises(FileDoesNotExistException) as excinfo: crypt.read_key() - assert ("Decrypt key None not exists. You must set a correct env var PYMS_KEY_FILE or run " - "`pyms crypt create-key` command") \ - in str(excinfo.value) + assert ( + "Decrypt key None not exists. You must set a correct env var PYMS_KEY_FILE or run " + "`pyms crypt create-key` command" + ) in str(excinfo.value) def test_crypt_file_ok(self): crypt = CryptFernet() @@ -110,8 +116,8 @@ def setUp(self): def tearDown(self): del os.environ[CONFIGMAP_FILE_ENVIRONMENT] - @patch.object(CryptAws, '_init_boto') - @patch.object(CryptAws, '_aws_decrypt') + @patch.object(CryptAws, "_init_boto") + @patch.object(CryptAws, "_aws_decrypt") def test_encrypt_conf(self, mock_aws_decrypt, mock_init_boto): mock_aws_decrypt.return_value = "http://database-url" crypt = CryptResource() @@ -123,8 +129,9 @@ class FlaskWithEncryptedFernetTests(unittest.TestCase): BASE_DIR = os.path.dirname(os.path.abspath(__file__)) def setUp(self): - os.environ[CONFIGMAP_FILE_ENVIRONMENT] = os.path.join(os.path.dirname(os.path.abspath(__file__)), - "config-tests-flask-encrypted-fernet.yml") + os.environ[CONFIGMAP_FILE_ENVIRONMENT] = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "config-tests-flask-encrypted-fernet.yml" + ) os.environ[CRYPT_FILE_KEY_ENVIRONMENT] = os.path.join(self.BASE_DIR, "key.key") self.crypt = CryptFernet(path=self.BASE_DIR) self.crypt._loader.put_file(b"9IXx2F5d5Ob-h5xdCnFSUXhuFKLGRibvLfSbixpcfCw=", "wb") @@ -152,8 +159,9 @@ class FlaskWithEncryptedNoneTests(unittest.TestCase): BASE_DIR = os.path.dirname(os.path.abspath(__file__)) def setUp(self): - os.environ[CONFIGMAP_FILE_ENVIRONMENT] = os.path.join(os.path.dirname(os.path.abspath(__file__)), - "config-tests-flask-encrypted-none.yml") + os.environ[CONFIGMAP_FILE_ENVIRONMENT] = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "config-tests-flask-encrypted-none.yml" + ) ms = MyMicroserviceNoSingleton(path=__file__) ms.reload_conf() self.app = ms.create_app() diff --git a/tests/test_flask.py b/tests/test_flask.py index c9215d1..9b563bf 100644 --- a/tests/test_flask.py +++ b/tests/test_flask.py @@ -21,8 +21,9 @@ class HomeWithFlaskTests(unittest.TestCase): """ def setUp(self): - os.environ[CONFIGMAP_FILE_ENVIRONMENT] = os.path.join(os.path.dirname(os.path.abspath(__file__)), - "config-tests-flask.yml") + os.environ[CONFIGMAP_FILE_ENVIRONMENT] = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "config-tests-flask.yml" + ) ms = MyMicroservice() ms.reload_conf() self.app = ms.create_app() @@ -30,16 +31,16 @@ def setUp(self): self.assertEqual("Python Microservice With Flask", self.app.config["APP_NAME"]) def test_home(self): - response = self.client.get('/') + response = self.client.get("/") self.assertEqual(404, response.status_code) def test_healthcheck(self): - response = self.client.get('/healthcheck') + response = self.client.get("/healthcheck") self.assertEqual(b"OK", response.data) self.assertEqual(200, response.status_code) def test_swagger(self): - response = self.client.get('/ui/') + response = self.client.get("/ui/") self.assertEqual(404, response.status_code) @@ -49,35 +50,36 @@ class FlaskWithSwaggerTests(unittest.TestCase): """ def setUp(self): - os.environ[CONFIGMAP_FILE_ENVIRONMENT] = os.path.join(os.path.dirname(os.path.abspath(__file__)), - "config-tests-flask-swagger.yml") + os.environ[CONFIGMAP_FILE_ENVIRONMENT] = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "config-tests-flask-swagger.yml" + ) ms = MyMicroservice(path=__file__) self.app = ms.create_app() self.client = self.app.test_client() self.assertEqual("Python Microservice With Flask in tests", self.app.config["APP_NAME"]) def test_healthcheck(self): - response = self.client.get('/healthcheck') + response = self.client.get("/healthcheck") self.assertEqual(b"OK", response.data) self.assertEqual(200, response.status_code) def test_swagger(self): - response = self.client.get('/ui/') + response = self.client.get("/ui/") self.assertEqual(200, response.status_code) def test_exists_service(self): self.assertTrue(isinstance(self.app.ms.swagger, DriverService)) def test_reverse_proxy(self): - response = self.client.get('/my-proxy-path/ui/', headers={"X-Script-Name": "/my-proxy-path"}) + response = self.client.get("/my-proxy-path/ui/", headers={"X-Script-Name": "/my-proxy-path"}) self.assertEqual(200, response.status_code) def test_reverse_proxy_no_slash(self): - response = self.client.get('/my-proxy-path/ui/', headers={"X-Script-Name": "my-proxy-path"}) + response = self.client.get("/my-proxy-path/ui/", headers={"X-Script-Name": "my-proxy-path"}) self.assertEqual(200, response.status_code) def test_reverse_proxy_zuul(self): - response = self.client.get('/my-proxy-path-zuul/ui/', headers={"X-Forwarded-Prefix": "my-proxy-path-zuul"}) + response = self.client.get("/my-proxy-path-zuul/ui/", headers={"X-Forwarded-Prefix": "my-proxy-path-zuul"}) self.assertEqual(200, response.status_code) @@ -90,17 +92,15 @@ class ReloadTests(unittest.TestCase): file2 = "config-tests-reload2.yml" def setUp(self): - os.environ[CONFIGMAP_FILE_ENVIRONMENT] = os.path.join(os.path.dirname(os.path.abspath(__file__)), - self.file1) + os.environ[CONFIGMAP_FILE_ENVIRONMENT] = os.path.join(os.path.dirname(os.path.abspath(__file__)), self.file1) ms = MyMicroservice(path=__file__) self.app = ms.create_app() self.client = self.app.test_client() self.assertEqual("reload1", self.app.config["APP_NAME"]) def test_configreload(self): - os.environ[CONFIGMAP_FILE_ENVIRONMENT] = os.path.join(os.path.dirname(os.path.abspath(__file__)), - self.file2) - response = self.client.post('/reload-config') + os.environ[CONFIGMAP_FILE_ENVIRONMENT] = os.path.join(os.path.dirname(os.path.abspath(__file__)), self.file2) + response = self.client.post("/reload-config") self.assertEqual(b"OK", response.data) self.assertEqual(200, response.status_code) self.assertEqual("reload2", config()["APP_NAME"]) @@ -112,8 +112,9 @@ class MicroserviceTest(unittest.TestCase): """ def setUp(self): - os.environ[CONFIGMAP_FILE_ENVIRONMENT] = os.path.join(os.path.dirname(os.path.abspath(__file__)), - "config-tests.yml") + os.environ[CONFIGMAP_FILE_ENVIRONMENT] = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "config-tests.yml" + ) def test_singleton(self): ms1 = Microservice(path=__file__) @@ -141,30 +142,20 @@ def test_config_singleton(self): assert conf_one == conf_two -@pytest.mark.parametrize("payload, configfile, status_code", [ - ( - "Python Microservice", - "config-tests.yml", - 200 - ), - ( - "Python Microservice With Flask", - "config-tests-flask.yml", - 404 - ), - ( - "Python Microservice With Flask and Lightstep", - "config-tests-flask-trace-lightstep.yml", - 200 - ) -]) +@pytest.mark.parametrize( + "payload, configfile, status_code", + [ + ("Python Microservice", "config-tests.yml", 200), + ("Python Microservice With Flask", "config-tests-flask.yml", 404), + ("Python Microservice With Flask and Lightstep", "config-tests-flask-trace-lightstep.yml", 200), + ], +) def test_configfiles(payload, configfile, status_code): - os.environ[CONFIGMAP_FILE_ENVIRONMENT] = os.path.join(os.path.dirname(os.path.abspath(__file__)), - configfile) + os.environ[CONFIGMAP_FILE_ENVIRONMENT] = os.path.join(os.path.dirname(os.path.abspath(__file__)), configfile) ms = MyMicroservice(path=__file__) ms.reload_conf() app = ms.create_app() client = app.test_client() - response = client.get('/') + response = client.get("/") assert payload == app.config["APP_NAME"] assert status_code == response.status_code diff --git a/tests/test_metrics.py b/tests/test_metrics.py index 07abc93..8dfac14 100644 --- a/tests/test_metrics.py +++ b/tests/test_metrics.py @@ -1,29 +1,32 @@ import os import unittest.mock -from tempfile import TemporaryDirectory from pathlib import Path +from tempfile import TemporaryDirectory -from prometheus_client import generate_latest -from prometheus_client import values from opentracing import global_tracer +from prometheus_client import generate_latest, values + from pyms.constants import CONFIGMAP_FILE_ENVIRONMENT -from pyms.flask.services.metrics import LOGGER_TOTAL_MESSAGES, FLASK_REQUEST_COUNT, FLASK_REQUEST_LATENCY +from pyms.flask.services.metrics import FLASK_REQUEST_COUNT, FLASK_REQUEST_LATENCY, LOGGER_TOTAL_MESSAGES from tests.common import MyMicroserviceNoSingleton + def reset_metric(metric): - metric._metric_init() # pylint: disable=protected-access - metric._metrics = {} # pylint: disable=protected-access + metric._metric_init() # pylint: disable=protected-access + metric._metrics = {} # pylint: disable=protected-access + def reset_metrics(): reset_metric(LOGGER_TOTAL_MESSAGES) reset_metric(FLASK_REQUEST_COUNT) reset_metric(FLASK_REQUEST_LATENCY) try: - for metric in global_tracer().metrics_factory._cache.values(): # pylint: disable=protected-access + for metric in global_tracer().metrics_factory._cache.values(): # pylint: disable=protected-access reset_metric(metric) - except AttributeError: # Not a Jaeger tracer + except AttributeError: # Not a Jaeger tracer pass + class TestMetricsFlask(unittest.TestCase): BASE_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -59,9 +62,10 @@ def test_metrics_logger(self): def test_metrics_jaeger(self): self.client.get("/") self.client.get("/metrics") - generated_logger = b'jaeger:reporter_spans_total' + generated_logger = b"jaeger:reporter_spans_total" assert generated_logger in generate_latest() + class TestMultiprocessMetricsFlask(unittest.TestCase): BASE_DIR = os.path.dirname(os.path.abspath(__file__)) current = None @@ -74,7 +78,9 @@ def current_test(cls): def setUpClass(cls): cls.temp_dir = TemporaryDirectory() os.environ["prometheus_multiproc_dir"] = cls.temp_dir.name - cls.patch_value_class = unittest.mock.patch.object(values, "ValueClass", values.MultiProcessValue(cls.current_test)) + cls.patch_value_class = unittest.mock.patch.object( + values, "ValueClass", values.MultiProcessValue(cls.current_test) + ) cls.patch_value_class.start() def setUp(self): @@ -129,5 +135,5 @@ def test_metrics_logger(self): def test_metrics_jaeger(self): self.client.get("/") self.client.get("/metrics") - generated_logger = b'jaeger:reporter_spans_total' + generated_logger = b"jaeger:reporter_spans_total" assert generated_logger in generate_latest(self.app.ms.metrics.registry) diff --git a/tests/test_requests.py b/tests/test_requests.py index 2cc11a3..028b305 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -12,8 +12,7 @@ class RequestServiceNoDataTests(unittest.TestCase): - """Test common rest operations wrapper. - """ + """Test common rest operations wrapper.""" BASE_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -28,8 +27,12 @@ def setUp(self): def test_get(self, mock_request): url = "http://www.my-site.com/users" full_url = url - text = json.dumps([{'id': 1, 'name': 'Peter', 'email': 'peter@my-site.com.com'}, - {'id': 2, 'name': 'Jon', 'email': 'jon@my-site.com.com'}]) + text = json.dumps( + [ + {"id": 1, "name": "Peter", "email": "peter@my-site.com.com"}, + {"id": 2, "name": "Jon", "email": "jon@my-site.com.com"}, + ] + ) with self.app.app_context(): mock_request.get(full_url, text=text) @@ -40,8 +43,7 @@ def test_get(self, mock_request): class RequestServiceTests(unittest.TestCase): - """Test common rest operations wrapper. - """ + """Test common rest operations wrapper.""" BASE_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -56,8 +58,14 @@ def setUp(self): def test_get(self, mock_request): url = "http://www.my-site.com/users" full_url = url - text = json.dumps({'data': [{'id': 1, 'name': 'Peter', 'email': 'peter@my-site.com.com'}, - {'id': 2, 'name': 'Jon', 'email': 'jon@my-site.com.com'}]}) + text = json.dumps( + { + "data": [ + {"id": 1, "name": "Peter", "email": "peter@my-site.com.com"}, + {"id": 2, "name": "Jon", "email": "jon@my-site.com.com"}, + ] + } + ) with self.app.app_context(): mock_request.get(full_url, text=text) @@ -69,7 +77,7 @@ def test_get(self, mock_request): @requests_mock.Mocker() def test_get_for_object_without_json(self, mock_request): url = "http://www.my-site.com/users/{user-id}/posts" - path_params = {'user-id': 123} + path_params = {"user-id": 123} full_url = "http://www.my-site.com/users/123/posts" expected = {} @@ -82,10 +90,16 @@ def test_get_for_object_without_json(self, mock_request): @requests_mock.Mocker() def test_get_for_object_without_valid_json_data(self, mock_request): url = "http://www.my-site.com/users/{user-id}/posts" - path_params = {'user-id': 123} + path_params = {"user-id": 123} full_url = "http://www.my-site.com/users/123/posts" - text = json.dumps({'another_data': [{'id': 1, 'name': 'Peter', 'email': 'peter@my-site.com.com'}, - {'id': 2, 'name': 'Jon', 'email': 'jon@my-site.com.com'}]}) + text = json.dumps( + { + "another_data": [ + {"id": 1, "name": "Peter", "email": "peter@my-site.com.com"}, + {"id": 2, "name": "Jon", "email": "jon@my-site.com.com"}, + ] + } + ) expected = {} with self.app.app_context(): @@ -97,12 +111,20 @@ def test_get_for_object_without_valid_json_data(self, mock_request): @requests_mock.Mocker() def test_get_for_object_with_valid_data(self, mock_request): url = "http://www.my-site.com/users/{user-id}/posts" - path_params = {'user-id': 123} + path_params = {"user-id": 123} full_url = "http://www.my-site.com/users/123/posts" - text = json.dumps({'data': [{'id': 1, 'name': 'Peter', 'email': 'peter@my-site.com.com'}, - {'id': 2, 'name': 'Jon', 'email': 'jon@my-site.com.com'}]}) - expected = [{'id': 1, 'name': 'Peter', 'email': 'peter@my-site.com.com'}, - {'id': 2, 'name': 'Jon', 'email': 'jon@my-site.com.com'}] + text = json.dumps( + { + "data": [ + {"id": 1, "name": "Peter", "email": "peter@my-site.com.com"}, + {"id": 2, "name": "Jon", "email": "jon@my-site.com.com"}, + ] + } + ) + expected = [ + {"id": 1, "name": "Peter", "email": "peter@my-site.com.com"}, + {"id": 2, "name": "Jon", "email": "jon@my-site.com.com"}, + ] with self.app.app_context(): mock_request.get(full_url, text=text) @@ -114,8 +136,8 @@ def test_get_for_object_with_valid_data(self, mock_request): def test_post(self, mock_request): url = "http://www.my-site.com/users" full_url = url - user = {'name': 'Peter', 'email': 'peter@my-site.com'} - text = json.dumps({'data': {'id': 1, 'name': 'Peter', 'email': 'peter@my-site.com'}}) + user = {"name": "Peter", "email": "peter@my-site.com"} + text = json.dumps({"data": {"id": 1, "name": "Peter", "email": "peter@my-site.com"}}) with self.app.app_context(): mock_request.post(full_url, text=text, status_code=201) @@ -128,7 +150,7 @@ def test_post(self, mock_request): def test_post_for_object_without_json(self, mock_request): url = "http://www.my-site.com/users" full_url = url - user = {'name': 'Peter', 'email': 'peter@my-site.com'} + user = {"name": "Peter", "email": "peter@my-site.com"} expected = {} with self.app.app_context(): @@ -141,8 +163,8 @@ def test_post_for_object_without_json(self, mock_request): def test_post_for_object_without_valid_json_data(self, mock_request): url = "http://www.my-site.com/users" full_url = url - user = {'name': 'Peter', 'email': 'peter@my-site.com'} - text = json.dumps({'another_data': {'id': 1, 'name': 'Peter', 'email': 'peter@my-site.com.com'}}) + user = {"name": "Peter", "email": "peter@my-site.com"} + text = json.dumps({"another_data": {"id": 1, "name": "Peter", "email": "peter@my-site.com.com"}}) expected = {} with self.app.app_context(): @@ -155,9 +177,9 @@ def test_post_for_object_without_valid_json_data(self, mock_request): def test_post_for_object_with_valid_data(self, mock_request): url = "http://www.my-site.com/users" full_url = url - user = {'name': 'Peter', 'email': 'peter@my-site.com'} - text = json.dumps({'data': {'id': 1, 'name': 'Peter', 'email': 'peter@my-site.com.com'}}) - expected = {'id': 1, 'name': 'Peter', 'email': 'peter@my-site.com.com'} + user = {"name": "Peter", "email": "peter@my-site.com"} + text = json.dumps({"data": {"id": 1, "name": "Peter", "email": "peter@my-site.com.com"}}) + expected = {"id": 1, "name": "Peter", "email": "peter@my-site.com.com"} with self.app.app_context(): mock_request.post(full_url, text=text, status_code=201) @@ -168,10 +190,10 @@ def test_post_for_object_with_valid_data(self, mock_request): @requests_mock.Mocker() def test_put(self, mock_request): url = "http://www.my-site.com/users/{user-id}" - path_params = {'user-id': 123} + path_params = {"user-id": 123} full_url = "http://www.my-site.com/users/123" - user = {'name': 'Peter', 'email': 'peter@my-site.com'} - text = json.dumps({'data': {'id': 123, 'name': 'Peter', 'email': 'peter@my-site.com'}}) + user = {"name": "Peter", "email": "peter@my-site.com"} + text = json.dumps({"data": {"id": 123, "name": "Peter", "email": "peter@my-site.com"}}) with self.app.app_context(): mock_request.put(full_url, text=text, status_code=200) @@ -183,9 +205,9 @@ def test_put(self, mock_request): @requests_mock.Mocker() def test_put_for_object_without_json(self, mock_request): url = "http://www.my-site.com/users/{user-id}" - path_params = {'user-id': 123} + path_params = {"user-id": 123} full_url = "http://www.my-site.com/users/123" - user = {'name': 'Peter', 'email': 'peter@my-site.com'} + user = {"name": "Peter", "email": "peter@my-site.com"} expected = {} with self.app.app_context(): @@ -197,10 +219,10 @@ def test_put_for_object_without_json(self, mock_request): @requests_mock.Mocker() def test_put_for_object_without_valid_json_data(self, mock_request): url = "http://www.my-site.com/users/{user-id}" - path_params = {'user-id': 123} + path_params = {"user-id": 123} full_url = "http://www.my-site.com/users/123" - user = {'name': 'Peter', 'email': 'peter@my-site.com'} - text = json.dumps({'another_data': {'id': 123, 'name': 'Peter', 'email': 'peter@my-site.com.com'}}) + user = {"name": "Peter", "email": "peter@my-site.com"} + text = json.dumps({"another_data": {"id": 123, "name": "Peter", "email": "peter@my-site.com.com"}}) expected = {} with self.app.app_context(): @@ -212,11 +234,11 @@ def test_put_for_object_without_valid_json_data(self, mock_request): @requests_mock.Mocker() def test_put_for_object_with_valid_data(self, mock_request): url = "http://www.my-site.com/users/{user-id}" - path_params = {'user-id': 123} + path_params = {"user-id": 123} full_url = "http://www.my-site.com/users/123" - user = {'name': 'Peter', 'email': 'peter@my-site.com'} - text = json.dumps({'data': {'id': 123, 'name': 'Peter', 'email': 'peter@my-site.com.com'}}) - expected = {'id': 123, 'name': 'Peter', 'email': 'peter@my-site.com.com'} + user = {"name": "Peter", "email": "peter@my-site.com"} + text = json.dumps({"data": {"id": 123, "name": "Peter", "email": "peter@my-site.com.com"}}) + expected = {"id": 123, "name": "Peter", "email": "peter@my-site.com.com"} with self.app.app_context(): mock_request.put(full_url, text=text, status_code=200) @@ -227,10 +249,10 @@ def test_put_for_object_with_valid_data(self, mock_request): @requests_mock.Mocker() def test_patch(self, mock_request): url = "http://www.my-site.com/users/{user-id}" - path_params = {'user-id': 123} + path_params = {"user-id": 123} full_url = "http://www.my-site.com/users/123" - user = {'name': 'Peter', 'email': 'peter@my-site.com'} - text = json.dumps({'data': {'id': 123, 'name': 'Peter', 'email': 'peter@my-site.com'}}) + user = {"name": "Peter", "email": "peter@my-site.com"} + text = json.dumps({"data": {"id": 123, "name": "Peter", "email": "peter@my-site.com"}}) with self.app.app_context(): mock_request.patch(full_url, text=text, status_code=200) @@ -242,9 +264,9 @@ def test_patch(self, mock_request): @requests_mock.Mocker() def test_patch_for_object_without_json(self, mock_request): url = "http://www.my-site.com/users/{user-id}" - path_params = {'user-id': 123} + path_params = {"user-id": 123} full_url = "http://www.my-site.com/users/123" - user = {'name': 'Peter', 'email': 'peter@my-site.com'} + user = {"name": "Peter", "email": "peter@my-site.com"} expected = {} with self.app.app_context(): @@ -256,10 +278,10 @@ def test_patch_for_object_without_json(self, mock_request): @requests_mock.Mocker() def test_patch_for_object_without_valid_json_data(self, mock_request): url = "http://www.my-site.com/users/{user-id}" - path_params = {'user-id': 123} + path_params = {"user-id": 123} full_url = "http://www.my-site.com/users/123" - user = {'name': 'Peter', 'email': 'peter@my-site.com'} - text = json.dumps({'another_data': {'id': 123, 'name': 'Peter', 'email': 'peter@my-site.com.com'}}) + user = {"name": "Peter", "email": "peter@my-site.com"} + text = json.dumps({"another_data": {"id": 123, "name": "Peter", "email": "peter@my-site.com.com"}}) expected = {} with self.app.app_context(): @@ -271,11 +293,11 @@ def test_patch_for_object_without_valid_json_data(self, mock_request): @requests_mock.Mocker() def test_patch_for_object_with_valid_data(self, mock_request): url = "http://www.my-site.com/users/{user-id}" - path_params = {'user-id': 123} + path_params = {"user-id": 123} full_url = "http://www.my-site.com/users/123" - user = {'name': 'Peter', 'email': 'peter@my-site.com'} - text = json.dumps({'data': {'id': 123, 'name': 'Peter', 'email': 'peter@my-site.com.com'}}) - expected = {'id': 123, 'name': 'Peter', 'email': 'peter@my-site.com.com'} + user = {"name": "Peter", "email": "peter@my-site.com"} + text = json.dumps({"data": {"id": 123, "name": "Peter", "email": "peter@my-site.com.com"}}) + expected = {"id": 123, "name": "Peter", "email": "peter@my-site.com.com"} with self.app.app_context(): mock_request.patch(full_url, text=text, status_code=200) @@ -286,7 +308,7 @@ def test_patch_for_object_with_valid_data(self, mock_request): @requests_mock.Mocker() def test_delete(self, mock_request): url = "http://www.my-site.com/users/{user-id}" - path_params = {'user-id': 123} + path_params = {"user-id": 123} full_url = "http://www.my-site.com/users/123" with self.app.app_context(): @@ -294,63 +316,54 @@ def test_delete(self, mock_request): response = self.request.delete(url, path_params) self.assertEqual(204, response.status_code) - self.assertEqual('', response.text) + self.assertEqual("", response.text) - def test_propagate_headers_empty(self, ): - input_headers = { - - } + def test_propagate_headers_empty( + self, + ): + input_headers = {} expected_headers = { - 'Content-Length': '12', - 'Content-Type': 'application/x-www-form-urlencoded', - 'Host': 'localhost' + "Content-Length": "12", + "Content-Type": "application/x-www-form-urlencoded", + "Host": "localhost", } - with self.app.test_request_context( - '/tests/', data={'format': 'short'}): + with self.app.test_request_context("/tests/", data={"format": "short"}): headers = self.request.set_propagate_headers(input_headers) self.assertEqual(expected_headers, headers) def test_propagate_headers_no_override(self): - input_headers = { - 'Host': 'my-server' - } - expected_headers = { - 'Host': 'my-server' - } - with self.app.test_request_context( - '/tests/'): + input_headers = {"Host": "my-server"} + expected_headers = {"Host": "my-server"} + with self.app.test_request_context("/tests/"): headers = self.request.set_propagate_headers(input_headers) self.assertEqual(expected_headers, headers) def test_propagate_headers_propagate(self): - input_headers = { - } + input_headers = {} expected_headers = { - 'Content-Length': '12', - 'Content-Type': 'application/x-www-form-urlencoded', - 'Host': 'localhost', - 'A': 'b', + "Content-Length": "12", + "Content-Type": "application/x-www-form-urlencoded", + "Host": "localhost", + "A": "b", } - with self.app.test_request_context( - '/tests/', data={'format': 'short'}, headers={'a': 'b'}): + with self.app.test_request_context("/tests/", data={"format": "short"}, headers={"a": "b"}): headers = self.request.set_propagate_headers(input_headers) self.assertEqual(expected_headers, headers) def test_propagate_headers_propagate_no_override(self): input_headers = { - 'Host': 'my-server', - 'Span': '1234', + "Host": "my-server", + "Span": "1234", } expected_headers = { - 'Host': 'my-server', - 'A': 'b', - 'Span': '1234', + "Host": "my-server", + "A": "b", + "Span": "1234", } - with self.app.test_request_context( - '/tests/', headers={'a': 'b', 'span': '5678'}): + with self.app.test_request_context("/tests/", headers={"a": "b", "span": "5678"}): headers = self.request.set_propagate_headers(input_headers) self.assertEqual(expected_headers, headers) @@ -358,12 +371,11 @@ def test_propagate_headers_propagate_no_override(self): def test_propagate_headers_on_get(self): url = "http://www.my-site.com/users" mock_headers = { - 'A': 'b', + "A": "b", } self.request.set_propagate_headers = unittest.mock.Mock() self.request.set_propagate_headers.return_value = mock_headers - with self.app.test_request_context( - '/tests/', data={'format': 'short'}, headers=mock_headers): + with self.app.test_request_context("/tests/", data={"format": "short"}, headers=mock_headers): self.request.get(url, propagate_headers=True) self.request.set_propagate_headers.assert_called_once_with({}) @@ -371,22 +383,21 @@ def test_propagate_headers_on_get(self): def test_propagate_headers_on_get_with_headers(self): url = "http://www.my-site.com/users" mock_headers = { - 'A': 'b', + "A": "b", } get_headers = { - 'C': 'd', + "C": "d", } self.request.set_propagate_headers = unittest.mock.Mock() self.request.set_propagate_headers.return_value = mock_headers - with self.app.test_request_context( - '/tests/', data={'format': 'short'}, headers=mock_headers): + with self.app.test_request_context("/tests/", data={"format": "short"}, headers=mock_headers): self.request.get(url, headers=get_headers, propagate_headers=True) self.request.set_propagate_headers.assert_called_once_with(get_headers) @requests_mock.Mocker() def test_retries_with_500(self, mock_request): - url = 'http://localhost:9999' + url = "http://localhost:9999" with self.app.app_context(): mock_request.get(url, text="", status_code=500) response = self.request.get(url) @@ -396,7 +407,7 @@ def test_retries_with_500(self, mock_request): @requests_mock.Mocker() def test_retries_with_200(self, mock_request): - url = 'http://localhost:9999' + url = "http://localhost:9999" with self.app.app_context(): mock_request.get(url, text="", status_code=200) response = self.request.get(url) diff --git a/tests/test_service_discovery.py b/tests/test_service_discovery.py new file mode 100644 index 0000000..d584c79 --- /dev/null +++ b/tests/test_service_discovery.py @@ -0,0 +1,60 @@ +"""Test common rest operations wrapper. +""" +import os +import unittest +from unittest.mock import patch + +from pyms.constants import CONFIGMAP_FILE_ENVIRONMENT +from pyms.flask.app import Microservice +from pyms.flask.services.service_discovery import ServiceDiscoveryBase, ServiceDiscoveryConsul + + +class MockServiceDiscovery(ServiceDiscoveryBase): + pass + + +class ServiceDiscoveryConsulTests(unittest.TestCase): + """Test service discovery services""" + + BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + + def setUp(self): + os.environ[CONFIGMAP_FILE_ENVIRONMENT] = os.path.join( + self.BASE_DIR, "config-tests-service-discovery-consul.yml" + ) + ms = Microservice(path=__file__) + self.ms = ms + + @patch.object(ServiceDiscoveryConsul, "register_service", return_value=None) + def test_init(self, mock_consul): + """ test if the initializacion of service discovery call to the Service Discovery server to autoregister""" + self.ms.create_app() + + mock_consul.assert_called_once_with( + app_name="Python Microservice Service Discovery", + healtcheck_url="http://127.0.0.1.nip.io:5000/healthcheck", + interval="10s", + ) + + def test_get_client(self): + client = self.ms.service_discovery.get_client() + + self.assertTrue(isinstance(client, ServiceDiscoveryConsul)) + + +class ServiceDiscoveryTests(unittest.TestCase): + """Test service discovery services""" + + BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + + def setUp(self): + os.environ[CONFIGMAP_FILE_ENVIRONMENT] = os.path.join(self.BASE_DIR, "config-tests-service-discovery.yml") + ms = Microservice(path=__file__) + ms.reload_conf() + self.ms = ms + + def test_get_client(self): + self.ms.create_app() + client = self.ms.service_discovery.get_client() + + self.assertTrue(isinstance(client, MockServiceDiscovery)) diff --git a/tests/test_swagger.py b/tests/test_swagger.py index 65ed4ce..b86c04c 100644 --- a/tests/test_swagger.py +++ b/tests/test_swagger.py @@ -8,8 +8,7 @@ class SwaggerTests(unittest.TestCase): - """Test common rest operations wrapper. - """ + """Test common rest operations wrapper.""" BASE_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -22,17 +21,16 @@ def setUp(self): self.assertEqual("Python Microservice Swagger", self.app.config["APP_NAME"]) def test_default(self): - response = self.client.get('/test-api-path/ws-doc/') + response = self.client.get("/test-api-path/ws-doc/") self.assertEqual(200, response.status_code) def test_home(self): - response = self.client.get('/test-api-path/') + response = self.client.get("/test-api-path/") self.assertEqual(200, response.status_code) class SwaggerNoAbsPathTests(unittest.TestCase): - """Test common rest operations wrapper. - """ + """Test common rest operations wrapper.""" BASE_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -45,17 +43,16 @@ def setUp(self): self.assertEqual("Python Microservice Swagger2", self.app.config["APP_NAME"]) def test_default(self): - response = self.client.get('/test-api-path2/ws-doc2/') + response = self.client.get("/test-api-path2/ws-doc2/") self.assertEqual(200, response.status_code) def test_home(self): - response = self.client.get('/test-api-path2/no-abs-path') + response = self.client.get("/test-api-path2/no-abs-path") self.assertEqual(200, response.status_code) class SwaggerOpenapi3Tests(unittest.TestCase): - """Test common rest operations wrapper. - """ + """Test common rest operations wrapper.""" BASE_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -68,17 +65,16 @@ def setUp(self): self.assertEqual("Python Microservice Swagger Openapi 3", self.app.config["APP_NAME"]) def test_default(self): - response = self.client.get('/ws-doc/') + response = self.client.get("/ws-doc/") self.assertEqual(200, response.status_code) def test_home(self): - response = self.client.get('/test-url') + response = self.client.get("/test-url") self.assertEqual(200, response.status_code) class SwaggerOpenapi3NoAbsPathTests(unittest.TestCase): - """Test common rest operations wrapper. - """ + """Test common rest operations wrapper.""" BASE_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -91,9 +87,9 @@ def setUp(self): self.assertEqual("Python Microservice Swagger Openapi 3 No abspath", self.app.config["APP_NAME"]) def test_default(self): - response = self.client.get('/test-api-path2/ws-doc2/') + response = self.client.get("/test-api-path2/ws-doc2/") self.assertEqual(200, response.status_code) def test_home(self): - response = self.client.get('/test-api-path2/test-url') + response = self.client.get("/test-api-path2/test-url") self.assertEqual(200, response.status_code) diff --git a/tests/test_utils.py b/tests/test_utils.py index f8404ae..812a50a 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -5,9 +5,9 @@ import pytest -from pyms.exceptions import PackageNotExists, FileDoesNotExistException -from pyms.utils import check_package_exists, import_package from pyms.crypt.fernet import Crypt +from pyms.exceptions import FileDoesNotExistException, PackageNotExists +from pyms.utils import check_package_exists, import_package class ConfUtils(unittest.TestCase): @@ -16,11 +16,10 @@ class ConfUtils(unittest.TestCase): def test_check_package_exists_exception(self): with pytest.raises(PackageNotExists) as excinfo: check_package_exists("this-package-not-exists") - assert "this-package-not-exists is not installed. try with pip install -U this-package-not-exists" \ - in str(excinfo.value) + assert "this-package-not-exists is not installed. try with pip install -U this-package-not-exists" in str( + excinfo.value + ) def test_import_package(self): os_import = import_package("os") assert os_import == os - -