From 75e623bd9b15d438e206768702af232e8ff148e5 Mon Sep 17 00:00:00 2001 From: henrique-lh Date: Wed, 9 Jul 2025 11:14:10 -0300 Subject: [PATCH 1/5] - fix: send env file --- src/mlops_codex/preprocessing.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/mlops_codex/preprocessing.py b/src/mlops_codex/preprocessing.py index d34cf48..68274b5 100644 --- a/src/mlops_codex/preprocessing.py +++ b/src/mlops_codex/preprocessing.py @@ -182,7 +182,7 @@ def __upload_script( token: str, ) -> None: """ - Upload python script to MLOps. + Upload a python script to MLOps. Parameters ---------- @@ -430,6 +430,7 @@ def create( schema_files_path: Optional[ Union[Tuple[str, str], List[Tuple[str, str]]] ] = None, + env_file: Optional[str] = None, schema_datasets: Optional[Union[str, List[str]]] = None, extra_files: Union[Tuple[str, str], List[Tuple[str, str]]] = None, wait_read: bool = False, @@ -556,6 +557,17 @@ def create( ) logger.info("Requirements file uploaded") + if env_file: + make_request( + url=f"{self.base_url}/v2/preprocessing/{preprocessing_script_hash}/env-file", + method='PATCH', + success_code=201, + files={'env': open(env_file, 'rb')}, + headers={ + "Authorization": f"Bearer {token}", + }, + ) + logger.info("Hosting preprocessing script") if extra_files is not None: @@ -2417,6 +2429,7 @@ def create( requirements_path=requirements_file, python_version=python_version, schema_files_path=schema, + env_file=env, extra_files=extra_files, wait_read=wait_complete, ) From b99519da649cc28aab6180504fd7e6dd5d1990a2 Mon Sep 17 00:00:00 2001 From: Luis Henrique <75594301+henrique-lh@users.noreply.github.com> Date: Fri, 4 Jul 2025 15:02:38 -0300 Subject: [PATCH 2/5] Remove training v2 (#90) * - remove training v2 * - remove training v2 * remove training v2 * remove files with errors * - fix broken python versions (lightgbm) - remove fixed versions from pandas, scikit-learn - create a new pipfile.lock * - force version of urllib3 * - remove 3.8 python * - rollback --- .github/workflows/test.yml | 2 +- Pipfile | 20 +- Pipfile.lock | 875 ++++++------- src/mlops_codex/trainingv2/Training.ipynb | 560 --------- src/mlops_codex/trainingv2/__init__.py | 7 - src/mlops_codex/trainingv2/base.py | 256 ---- src/mlops_codex/trainingv2/commons.py | 44 - src/mlops_codex/trainingv2/training.py | 1097 ----------------- .../trainingv2/training_executions.py | 783 ------------ src/mlops_codex/trainingv2/trigger.py | 303 ----- src/mlops_codex/trainingv2/validations.py | 16 - 11 files changed, 451 insertions(+), 3512 deletions(-) delete mode 100644 src/mlops_codex/trainingv2/Training.ipynb delete mode 100644 src/mlops_codex/trainingv2/__init__.py delete mode 100644 src/mlops_codex/trainingv2/base.py delete mode 100644 src/mlops_codex/trainingv2/commons.py delete mode 100644 src/mlops_codex/trainingv2/training.py delete mode 100644 src/mlops_codex/trainingv2/training_executions.py delete mode 100644 src/mlops_codex/trainingv2/trigger.py delete mode 100644 src/mlops_codex/trainingv2/validations.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4dc642d..2ae4ac4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.9", "3.10"] steps: - name: Checkout repository uses: actions/checkout@v3 diff --git a/Pipfile b/Pipfile index 7f1a0dc..cddab70 100644 --- a/Pipfile +++ b/Pipfile @@ -7,16 +7,16 @@ name = "pypi" mlops_codex = {path = "."} [dev-packages] -notebook = "==6.5.2" +notebook = "=6.5.2" ipykernel = "*" -pydata-sphinx-theme = "==0.12.0" -sphinx = "==6.1.3" -sphinx-intl = "==2.1.0" +pydata-sphinx-theme = "=0.12.0" +sphinx = "=6.1.3" +sphinx-intl = "=2.1.0" m2r2 = "*" twine = "*" -pandas = "==1.4.0" -lightgbm = "==3.3.2" -scikit-learn = "==1.5.1" -pyaml="==24.9.0" -pydantic="==2.10.4" -ruff="==0.9.4" \ No newline at end of file +pandas = ">=1.4.0" +lightgbm = "=4.6.0" +scikit-learn = ">=1.5.1" +pyaml="=24.9.0" +pydantic="=2.10.4" +ruff="=0.9.4" \ No newline at end of file diff --git a/Pipfile.lock b/Pipfile.lock index 6db11ef..a3a7697 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "05de04106aff3665cb0f9586f9e1dc0e17ed08980a575d42a755fb04ddef7702" + "sha256": "725901af0714cec5a04eddea3b868d2648813eaac410487c43de385a01779d7c" }, "pipfile-spec": 6, "requires": {}, @@ -24,19 +24,19 @@ }, "cachetools": { "hashes": [ - "sha256:82e73ba88f7b30228b5507dce1a1f878498fc669d972aef2dde4f3a3c24f103e", - "sha256:f225782b84438f828328fc2ad74346522f27e5b1440f4e9fd18b20ebfd1aa2cf" + "sha256:1c7bb3cf9193deaf3508b7c5f2a79986c13ea38965c5adcff1f84519cf39163e", + "sha256:b4c4f404392848db3ce7aac34950d17be4d864da4b8b66911008e430bc544587" ], "markers": "python_version >= '3.9'", - "version": "==6.0.0" + "version": "==6.1.0" }, "certifi": { "hashes": [ - "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", - "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3" + "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", + "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b" ], - "markers": "python_version >= '3.6'", - "version": "==2025.4.26" + "markers": "python_version >= '3.7'", + "version": "==2025.6.15" }, "charset-normalizer": { "hashes": [ @@ -145,7 +145,7 @@ "version": "==3.1.1" }, "datarisk-mlops-codex": { - "version": "==2.2.8rc0" + "version": "==2.2.10" }, "idna": { "hashes": [ @@ -473,11 +473,11 @@ }, "python-dotenv": { "hashes": [ - "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5", - "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045" + "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", + "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab" ], - "markers": "python_version >= '3.7'", - "version": "==0.21.0" + "markers": "python_version >= '3.9'", + "version": "==1.1.1" }, "pytz": { "hashes": [ @@ -547,11 +547,11 @@ }, "requests": { "hashes": [ - "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", - "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6" + "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", + "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422" ], "markers": "python_version >= '3.8'", - "version": "==2.32.3" + "version": "==2.32.4" }, "ruff": { "hashes": [ @@ -587,11 +587,11 @@ }, "typing-extensions": { "hashes": [ - "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", - "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af" + "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", + "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76" ], "markers": "python_version >= '3.9'", - "version": "==4.14.0" + "version": "==4.14.1" }, "tzdata": { "hashes": [ @@ -603,11 +603,11 @@ }, "urllib3": { "hashes": [ - "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", - "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813" + "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", + "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc" ], "markers": "python_version >= '3.9'", - "version": "==2.4.0" + "version": "==2.5.0" } }, "develop": { @@ -637,11 +637,11 @@ }, "argon2-cffi": { "hashes": [ - "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08", - "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea" + "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", + "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741" ], - "markers": "python_version >= '3.7'", - "version": "==23.1.0" + "markers": "python_version >= '3.8'", + "version": "==25.1.0" }, "argon2-cffi-bindings": { "hashes": [ @@ -712,11 +712,11 @@ }, "beautifulsoup4": { "hashes": [ - "sha256:1bd32405dacc920b42b83ba01644747ed77456a65760e285fbc47633ceddaf8b", - "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16" + "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", + "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195" ], "markers": "python_full_version >= '3.7.0'", - "version": "==4.13.3" + "version": "==4.13.4" }, "bleach": { "hashes": [ @@ -728,11 +728,11 @@ }, "certifi": { "hashes": [ - "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", - "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3" + "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", + "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b" ], - "markers": "python_version >= '3.6'", - "version": "==2025.4.26" + "markers": "python_version >= '3.7'", + "version": "==2025.6.15" }, "cffi": { "hashes": [ @@ -907,11 +907,11 @@ }, "click": { "hashes": [ - "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", - "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a" + "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", + "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b" ], - "markers": "python_version >= '3.7'", - "version": "==8.1.8" + "markers": "python_version >= '3.10'", + "version": "==8.2.1" }, "comm": { "hashes": [ @@ -923,46 +923,46 @@ }, "cryptography": { "hashes": [ - "sha256:0339a692de47084969500ee455e42c58e449461e0ec845a34a6a9b9bf7df7fb8", - "sha256:03dbff8411206713185b8cebe31bc5c0eb544799a50c09035733716b386e61a4", - "sha256:06509dc70dd71fa56eaa138336244e2fbaf2ac164fc9b5e66828fccfd2b680d6", - "sha256:0cf13c77d710131d33e63626bd55ae7c0efb701ebdc2b3a7952b9b23a0412862", - "sha256:23b9c3ea30c3ed4db59e7b9619272e94891f8a3a5591d0b656a7582631ccf750", - "sha256:25eb4d4d3e54595dc8adebc6bbd5623588991d86591a78c2548ffb64797341e2", - "sha256:2882338b2a6e0bd337052e8b9007ced85c637da19ef9ecaf437744495c8c2999", - "sha256:3530382a43a0e524bc931f187fc69ef4c42828cf7d7f592f7f249f602b5a4ab0", - "sha256:425a9a6ac2823ee6e46a76a21a4e8342d8fa5c01e08b823c1f19a8b74f096069", - "sha256:46cf7088bf91bdc9b26f9c55636492c1cce3e7aaf8041bbf0243f5e5325cfb2d", - "sha256:4828190fb6c4bcb6ebc6331f01fe66ae838bb3bd58e753b59d4b22eb444b996c", - "sha256:49fe9155ab32721b9122975e168a6760d8ce4cffe423bcd7ca269ba41b5dfac1", - "sha256:4ca0f52170e821bc8da6fc0cc565b7bb8ff8d90d36b5e9fdd68e8a86bdf72036", - "sha256:51dfbd4d26172d31150d84c19bbe06c68ea4b7f11bbc7b3a5e146b367c311349", - "sha256:5f31e6b0a5a253f6aa49be67279be4a7e5a4ef259a9f33c69f7d1b1191939872", - "sha256:627ba1bc94f6adf0b0a2e35d87020285ead22d9f648c7e75bb64f367375f3b22", - "sha256:680806cf63baa0039b920f4976f5f31b10e772de42f16310a6839d9f21a26b0d", - "sha256:6a3511ae33f09094185d111160fd192c67aa0a2a8d19b54d36e4c78f651dc5ad", - "sha256:6a5bf57554e80f75a7db3d4b1dacaa2764611ae166ab42ea9a72bcdb5d577637", - "sha256:6b613164cb8425e2f8db5849ffb84892e523bf6d26deb8f9bb76ae86181fa12b", - "sha256:7405ade85c83c37682c8fe65554759800a4a8c54b2d96e0f8ad114d31b808d57", - "sha256:7aad98a25ed8ac917fdd8a9c1e706e5a0956e06c498be1f713b61734333a4507", - "sha256:7bedbe4cc930fa4b100fc845ea1ea5788fcd7ae9562e669989c11618ae8d76ee", - "sha256:7ef2dde4fa9408475038fc9aadfc1fb2676b174e68356359632e980c661ec8f6", - "sha256:817ee05c6c9f7a69a16200f0c90ab26d23a87701e2a284bd15156783e46dbcc8", - "sha256:944e9ccf67a9594137f942d5b52c8d238b1b4e46c7a0c2891b7ae6e01e7c80a4", - "sha256:964bcc28d867e0f5491a564b7debb3ffdd8717928d315d12e0d7defa9e43b723", - "sha256:96d4819e25bf3b685199b304a0029ce4a3caf98947ce8a066c9137cc78ad2c58", - "sha256:a77c6fb8d76e9c9f99f2f3437c1a4ac287b34eaf40997cfab1e9bd2be175ac39", - "sha256:b0a97c927497e3bc36b33987abb99bf17a9a175a19af38a892dc4bbb844d7ee2", - "sha256:b97737a3ffbea79eebb062eb0d67d72307195035332501722a9ca86bab9e3ab2", - "sha256:bbc505d1dc469ac12a0a064214879eac6294038d6b24ae9f71faae1448a9608d", - "sha256:c22fe01e53dc65edd1945a2e6f0015e887f84ced233acecb64b4daadb32f5c97", - "sha256:ce1678a2ccbe696cf3af15a75bb72ee008d7ff183c9228592ede9db467e64f1b", - "sha256:e00a6c10a5c53979d6242f123c0a97cff9f3abed7f064fc412c36dc521b5f257", - "sha256:eaa3e28ea2235b33220b949c5a0d6cf79baa80eab2eb5607ca8ab7525331b9ff", - "sha256:f3fe7a5ae34d5a414957cc7f457e2b92076e72938423ac64d215722f6cf49a9e" + "sha256:0027d566d65a38497bc37e0dd7c2f8ceda73597d2ac9ba93810204f56f52ebc7", + "sha256:101ee65078f6dd3e5a028d4f19c07ffa4dd22cce6a20eaa160f8b5219911e7d8", + "sha256:12e55281d993a793b0e883066f590c1ae1e802e3acb67f8b442e721e475e6463", + "sha256:14d96584701a887763384f3c47f0ca7c1cce322aa1c31172680eb596b890ec30", + "sha256:1e1da5accc0c750056c556a93c3e9cb828970206c68867712ca5805e46dc806f", + "sha256:206210d03c1193f4e1ff681d22885181d47efa1ab3018766a7b32a7b3d6e6afd", + "sha256:2089cc8f70a6e454601525e5bf2779e665d7865af002a5dec8d14e561002e135", + "sha256:3a264aae5f7fbb089dbc01e0242d3b67dffe3e6292e1f5182122bdf58e65215d", + "sha256:3af26738f2db354aafe492fb3869e955b12b2ef2e16908c8b9cb928128d42c57", + "sha256:3fcfbefc4a7f332dece7272a88e410f611e79458fab97b5efe14e54fe476f4fd", + "sha256:460f8c39ba66af7db0545a8c6f2eabcbc5a5528fc1cf6c3fa9a1e44cec33385e", + "sha256:57c816dfbd1659a367831baca4b775b2a5b43c003daf52e9d57e1d30bc2e1b0e", + "sha256:5aa1e32983d4443e310f726ee4b071ab7569f58eedfdd65e9675484a4eb67bd1", + "sha256:6ff8728d8d890b3dda5765276d1bc6fb099252915a2cd3aff960c4c195745dd0", + "sha256:7259038202a47fdecee7e62e0fd0b0738b6daa335354396c6ddebdbe1206af2a", + "sha256:72e76caa004ab63accdf26023fccd1d087f6d90ec6048ff33ad0445abf7f605a", + "sha256:7760c1c2e1a7084153a0f68fab76e754083b126a47d0117c9ed15e69e2103492", + "sha256:8c4a6ff8a30e9e3d38ac0539e9a9e02540ab3f827a3394f8852432f6b0ea152e", + "sha256:9024beb59aca9d31d36fcdc1604dd9bbeed0a55bface9f1908df19178e2f116e", + "sha256:90cb0a7bb35959f37e23303b7eed0a32280510030daba3f7fdfbb65defde6a97", + "sha256:91098f02ca81579c85f66df8a588c78f331ca19089763d733e34ad359f474174", + "sha256:926c3ea71a6043921050eaa639137e13dbe7b4ab25800932a8498364fc1abec9", + "sha256:982518cd64c54fcada9d7e5cf28eabd3ee76bd03ab18e08a48cad7e8b6f31b18", + "sha256:9b4cf6318915dccfe218e69bbec417fdd7c7185aa7aab139a2c0beb7468c89f0", + "sha256:ad0caded895a00261a5b4aa9af828baede54638754b51955a0ac75576b831b27", + "sha256:b85980d1e345fe769cfc57c57db2b59cff5464ee0c045d52c0df087e926fbe63", + "sha256:b8fa8b0a35a9982a3c60ec79905ba5bb090fc0b9addcfd3dc2dd04267e45f25e", + "sha256:b9e38e0a83cd51e07f5a48ff9691cae95a79bea28fe4ded168a8e5c6c77e819d", + "sha256:bd4c45986472694e5121084c6ebbd112aa919a25e783b87eb95953c9573906d6", + "sha256:be97d3a19c16a9be00edf79dca949c8fa7eff621763666a145f9f9535a5d7f42", + "sha256:c648025b6840fe62e57107e0a25f604db740e728bd67da4f6f060f03017d5097", + "sha256:d05a38884db2ba215218745f0781775806bde4f32e07b135348355fe8e4991d9", + "sha256:dd420e577921c8c2d31289536c386aaa30140b473835e97f83bc71ea9d2baf2d", + "sha256:e357286c1b76403dd384d938f93c46b2b058ed4dfcdce64a770f0537ed3feb6f", + "sha256:e6c00130ed423201c5bc5544c23359141660b07999ad82e34e7bb8f882bb78e0", + "sha256:e74d30ec9c7cb2f404af331d5b4099a9b322a8a6b25c4632755c8757345baac5", + "sha256:f3562c2f23c612f2e4a6964a61d942f891d29ee320edb62ff48ffb99f3de9ae8" ], "markers": "python_version >= '3.7' and python_full_version not in '3.9.0, 3.9.1'", - "version": "==45.0.4" + "version": "==45.0.5" }, "debugpy": { "hashes": [ @@ -1141,11 +1141,11 @@ }, "jaraco.functools": { "hashes": [ - "sha256:70f7e0e2ae076498e212562325e805204fc092d7b4c17e0e86c959e249701a9d", - "sha256:ad159f13428bc4acbf5541ad6dec511f91573b90fba04df61dafa2a1231cf649" + "sha256:590486285803805f4b1f99c60ca9e94ed348d4added84b74c7a12885561e524e", + "sha256:be634abfccabce56fa3053f8c7ebe37b682683a4ee7793670ced17bab0087353" ], - "markers": "python_version >= '3.8'", - "version": "==4.1.0" + "markers": "python_version >= '3.9'", + "version": "==4.2.1" }, "jedi": { "hashes": [ @@ -1192,19 +1192,19 @@ "format-nongpl" ], "hashes": [ - "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", - "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566" + "sha256:0b4e8069eb12aedfa881333004bccaec24ecef5a8a6a4b6df142b2cc9599d196", + "sha256:a462455f19f5faf404a7902952b6f0e3ce868f3ee09a359b05eca6673bd8412d" ], - "markers": "python_version >= '3.8'", - "version": "==4.23.0" + "markers": "python_version >= '3.9'", + "version": "==4.24.0" }, "jsonschema-specifications": { "hashes": [ - "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272", - "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf" + "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", + "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608" ], "markers": "python_version >= '3.9'", - "version": "==2024.10.1" + "version": "==2025.4.1" }, "jupyter-client": { "hashes": [ @@ -1232,11 +1232,11 @@ }, "jupyter-server": { "hashes": [ - "sha256:872d989becf83517012ee669f09604aa4a28097c0bd90b2f424310156c2cdae3", - "sha256:9d446b8697b4f7337a1b7cdcac40778babdd93ba614b6d68ab1c0c918f1c4084" + "sha256:3d8db5be3bc64403b1c65b400a1d7f4647a5ce743f3b20dbdefe8ddb7b55af9e", + "sha256:65d4b44fdf2dcbbdfe0aa1ace4a842d4aaf746a2b7b168134d5aaed35621b7f6" ], "markers": "python_version >= '3.9'", - "version": "==2.15.0" + "version": "==2.16.0" }, "jupyter-server-terminals": { "hashes": [ @@ -1264,158 +1264,116 @@ }, "lightgbm": { "hashes": [ - "sha256:2e94bd1b3ab29d173102c9c1d80db2e27ad7e43b8ff5a74c5cb7984b37d19f45", - "sha256:5d25d16e77c844c297ece2044df57651139bc3c8ad8c4108916374267ac68b64", - "sha256:8e788c56853316fc5d35db726d81bd002c721038c856853952287f68082e0158", - "sha256:e4f1529cad416066964f9af0efad208787861e9f2181b7f9ee7fc9bacc082d4f", - "sha256:f4cba3b4f29336ad7e801cb32d9b948ea4cc5300dda650b78bcdfe36b3e2c4b2" + "sha256:2dafd98d4e02b844ceb0b61450a660681076b1ea6c7adb8c566dfd66832aafad", + "sha256:37089ee95664b6550a7189d887dbf098e3eadab03537e411f52c63c121e3ba4b", + "sha256:4d68712bbd2b57a0b14390cbf9376c1d5ed773fa2e71e099cac588703b590336", + "sha256:b7a393de8a334d5c8e490df91270f0763f83f959574d504c7ccb9eee4aef70ed", + "sha256:cb19b5afea55b5b61cbb2131095f50538bd608a00655f23ad5d25ae3e3bf1c8d", + "sha256:cb1c59720eb569389c0ba74d14f52351b573af489f230032a1c9f314f8bab7fe" ], "index": "pypi", - "version": "==3.3.2" + "markers": "python_version >= '3.7'", + "version": "==4.6.0" }, "lxml": { "hashes": [ - "sha256:00a4463ca409ceacd20490a893a7e08deec7870840eff33dc3093067b559ce3e", - "sha256:017ceeabe739100379fe6ed38b033cd244ce2da4e7f6f07903421f57da3a19a2", - "sha256:02e56f7de72fa82561eae69628a7d6febd7891d72248c7ff7d3e7814d4031017", - "sha256:038aeb6937aa404480c2966b7f26f1440a14005cb0702078c173c028eca72c31", - "sha256:0a006390834603e5952a2ff74b9a31a6007c7cc74282a087aa6467afb4eea987", - "sha256:0bed509662f67f719119ad56006cd4a38efa68cfa74383060612044915e5f7ad", - "sha256:0e275961adbd32e15672e14e0cc976a982075208224ce06d149c92cb43db5b93", - "sha256:10e690bc03214d3537270c88e492b8612d5e41b884f232df2b069b25b09e6711", - "sha256:13f3495cfec24e3d63fffd342cc8141355d1d26ee766ad388775f5c8c5ec3932", - "sha256:1539f962d82436f3d386eb9f29b2a29bb42b80199c74a695dff51b367a61ec0a", - "sha256:1545de0a69a16ced5767bae8cca1801b842e6e49e96f5e4a8a5acbef023d970b", - "sha256:165fcdc2f40fc0fe88a3c3c06c9c2a097388a90bda6a16e6f7c9199c903c9b8e", - "sha256:16b3897691ec0316a1aa3c6585f61c8b7978475587c5b16fc1d2c28d283dc1b0", - "sha256:19f6fcfd15b82036b4d235749d78785eb9c991c7812012dc084e0d8853b4c1c0", - "sha256:1a06b0c6ba2e3ca45a009a78a4eb4d6b63831830c0a83dcdc495c13b9ca97d3e", - "sha256:1a580dc232c33d2ad87d02c8a3069d47abbcdce974b9c9cc82a79ff603065dbe", - "sha256:1a59f7fe888d0ec1916d0ad69364c5400cfa2f885ae0576d909f342e94d26bc9", - "sha256:1b16504c53f41da5fcf04868a80ac40a39d3eec5329caf761114caec6e844ad1", - "sha256:1ec944539543f66ebc060ae180d47e86aca0188bda9cbfadff47d86b0dc057dc", - "sha256:1f9682786138549da44ca4c49b20e7144d063b75f2b2ba611f4cff9b83db1062", - "sha256:2162068f6da83613f8b2a32ca105e37a564afd0d7009b0b25834d47693ce3538", - "sha256:258b6b53458c5cbd2a88795557ff7e0db99f73a96601b70bc039114cd4ee9e02", - "sha256:271f1a4d5d2b383c36ad8b9b489da5ea9c04eca795a215bae61ed6a57cf083cd", - "sha256:29424058f072a24622a0a15357bca63d796954758248a72da6d512f9bd9a4493", - "sha256:2996e1116bbb3ae2a1fbb2ba4da8f92742290b4011e7e5bce2bd33bbc9d9485a", - "sha256:29b5f7d77334877c2146e7bb8b94e4df980325fab0a8af4d524e5d43cd6f789d", - "sha256:2abcf3f3b8367d6400b908d00d4cd279fc0b8efa287e9043820525762d383699", - "sha256:2cb08d2cb047c98d6fbbb2e77d6edd132ad6e3fa5aa826ffa9ea0c9b1bc74a84", - "sha256:2d0a60841410123c533990f392819804a8448853f06daf412c0f383443925e89", - "sha256:2ea918da00091194526d40c30c4996971f09dacab032607581f8d8872db34fbf", - "sha256:2eadd4efa487f4710755415aed3d6ae9ac8b4327ea45226ffccb239766c8c610", - "sha256:30fe05f4b7f6e9eb32862745512e7cbd021070ad0f289a7f48d14a0d3fc1d8a9", - "sha256:32ba634ef3f1b20f781019a91d78599224dc45745dd572f951adbf1c0c9b0d75", - "sha256:334e0e414dab1f5366ead8ca34ec3148415f236d5660e175f1d640b11d645847", - "sha256:348c06cb2e3176ce98bee8c397ecc89181681afd13d85870df46167f140a305f", - "sha256:34c688fef86f73dbca0798e0a61bada114677006afa524a8ce97d9e5fabf42e6", - "sha256:354dab7206d22d7a796fa27c4c5bffddd2393da2ad61835355a4759d435beb47", - "sha256:37f3d7cf7f2dd2520df6cc8a13df4c3e3f913c8e0a1f9a875e44f9e5f98d7fee", - "sha256:3c2c8d0fa3277147bff180e3590be67597e17d365ce94beb2efa3138a2131f71", - "sha256:3d84f5c093645c21c29a4e972b84cb7cf682f707f8706484a5a0c7ff13d7a988", - "sha256:3df5a54e7b7c31755383f126d3a84e12a4e0333db4679462ef1165d702517477", - "sha256:3dfc78f5f9251b6b8ad37c47d4d0bfe63ceb073a916e5b50a3bf5fd67a703335", - "sha256:43af2a69af2cacc2039024da08a90174e85f3af53483e6b2e3485ced1bf37151", - "sha256:495ddb7e10911fb4d673d8aa8edd98d1eadafb3b56e8c1b5f427fd33cadc455b", - "sha256:49f1cee0fa27e1ee02589c696a9bdf4027e7427f184fa98e6bef0c6613f6f0fa", - "sha256:4b7f729e03090eb4e3981f10efaee35e6004b548636b1a062b8b9a525e752abc", - "sha256:4c62d0a34d1110769a1bbaf77871a4b711a6f59c4846064ccb78bc9735978644", - "sha256:4d6d3d1436d57f41984920667ec5ef04bcb158f80df89ac4d0d3f775a2ac0c87", - "sha256:4e3925975fadd6fd72a6d80541a6ec75dfbad54044a03aa37282dafcb80fbdfa", - "sha256:507085365783abd7879fa0a6fa55eddf4bdd06591b17a2418403bb3aff8a267d", - "sha256:521ab9c80b98c30b2d987001c3ede2e647e92eeb2ca02e8cb66ef5122d792b24", - "sha256:52fa7ba11a495b7cbce51573c73f638f1dcff7b3ee23697467dc063f75352a69", - "sha256:53e3f9ca72858834688afa17278649d62aa768a4b2018344be00c399c4d29e95", - "sha256:56a1d56d60ea1ec940f949d7a309e0bff05243f9bd337f585721605670abb1c1", - "sha256:579df6e20d8acce3bcbc9fb8389e6ae00c19562e929753f534ba4c29cfe0be4b", - "sha256:58e8c9b9ed3c15c2d96943c14efc324b69be6352fe5585733a7db2bf94d97841", - "sha256:59d437cc8a7f838282df5a199cf26f97ef08f1c0fbec6e84bd6f5cc2b7913f6e", - "sha256:5bb304f67cbf5dfa07edad904732782cbf693286b9cd85af27059c5779131050", - "sha256:5f94909a1022c8ea12711db7e08752ca7cf83e5b57a87b59e8a583c5f35016ad", - "sha256:617ecaccd565cbf1ac82ffcaa410e7da5bd3a4b892bb3543fb2fe19bd1c4467d", - "sha256:638d06b4e1d34d1a074fa87deed5fb55c18485fa0dab97abc5604aad84c12031", - "sha256:661feadde89159fd5f7d7639a81ccae36eec46974c4a4d5ccce533e2488949c8", - "sha256:6673920bf976421b5fac4f29b937702eef4555ee42329546a5fc68bae6178a48", - "sha256:6ba465a91acc419c5682f8b06bcc84a424a7aa5c91c220241c6fd31de2a72bc6", - "sha256:6f1231b0f9810289d41df1eacc4ebb859c63e4ceee29908a0217403cddce38d0", - "sha256:6fca8a5a13906ba2677a5252752832beb0f483a22f6c86c71a2bb320fba04f61", - "sha256:72968623efb1e12e950cbdcd1d0f28eb14c8535bf4be153f1bfffa818b1cf189", - "sha256:73bcb635a848c18a3e422ea0ab0092f2e4ef3b02d8ebe87ab49748ebc8ec03d8", - "sha256:741c126bcf9aa939e950e64e5e0a89c8e01eda7a5f5ffdfc67073f2ed849caea", - "sha256:75a72697d95f27ae00e75086aed629f117e816387b74a2f2da6ef382b460b710", - "sha256:773947d0ed809ddad824b7b14467e1a481b8976e87278ac4a730c2f7c7fcddc1", - "sha256:77626571fb5270ceb36134765f25b665b896243529eefe840974269b083e090d", - "sha256:77809fcd97dfda3f399102db1794f7280737b69830cd5c961ac87b3c5c05662d", - "sha256:77cbcab50cbe8c857c6ba5f37f9a3976499c60eada1bf6d38f88311373d7b4bc", - "sha256:7811828ddfb8c23f4f1fbf35e7a7b2edec2f2e4c793dee7c52014f28c4b35238", - "sha256:78a533375dc7aa16d0da44af3cf6e96035e484c8c6b2b2445541a5d4d3d289ee", - "sha256:7a01679e4aad0727bedd4c9407d4d65978e920f0200107ceeffd4b019bd48529", - "sha256:7a0e77edfe26d3703f954d46bed52c3ec55f58586f18f4b7f581fc56954f1d84", - "sha256:7d82737a8afe69a7c80ef31d7626075cc7d6e2267f16bf68af2c764b45ed68ab", - "sha256:83c0462dedc5213ac586164c6d7227da9d4d578cf45dd7fbab2ac49b63a008eb", - "sha256:83d8707b1b08cd02c04d3056230ec3b771b18c566ec35e723e60cdf037064e08", - "sha256:856dfd7eda0b75c29ac80a31a6411ca12209183e866c33faf46e77ace3ce8a79", - "sha256:87e8d78205331cace2b73ac8249294c24ae3cba98220687b5b8ec5971a2267f1", - "sha256:884d9308ac7d581b705a3371185282e1b8eebefd68ccf288e00a2d47f077cc51", - "sha256:8f961a4e82f411b14538fe5efc3e6b953e17f5e809c463f0756a0d0e8039b700", - "sha256:910f39425c6798ce63c93976ae5af5fff6949e2cb446acbd44d6d892103eaea8", - "sha256:95473d1d50a5d9fcdb9321fdc0ca6e1edc164dce4c7da13616247d27f3d21e31", - "sha256:95ad58340e3b7d2b828efc370d1791856613c5cb62ae267158d96e47b3c978c9", - "sha256:98050830bb6510159f65d9ad1b8aca27f07c01bb3884ba95f17319ccedc4bcf9", - "sha256:9cd7a959396da425022e1e4214895b5cfe7de7035a043bcc2d11303792b67554", - "sha256:9d61a7d0d208ace43986a92b111e035881c4ed45b1f5b7a270070acae8b0bfb4", - "sha256:9fa722a9cd8845594593cce399a49aa6bfc13b6c83a7ee05e2ab346d9253d52f", - "sha256:a1ef20f1851ccfbe6c5a04c67ec1ce49da16ba993fdbabdce87a92926e505412", - "sha256:a6f62b2404b3f3f0744bbcabb0381c5fe186fa2a9a67ecca3603480f4846c585", - "sha256:a8d4b34a0eeaf6e73169dcfd653c8d47f25f09d806c010daf074fba2db5e2d3f", - "sha256:aa837e6ee9534de8d63bc4c1249e83882a7ac22bd24523f83fad68e6ffdf41ae", - "sha256:ab6e9e6aca1fd7d725ffa132286e70dee5b9a4561c5ed291e836440b82888f89", - "sha256:abc795703d0de5d83943a4badd770fbe3d1ca16ee4ff3783d7caffc252f309ae", - "sha256:ad131e2c4d2c3803e736bb69063382334e03648de2a6b8f56a878d700d4b557d", - "sha256:b3709fc752b42fb6b6ffa2ba0a5b9871646d97d011d8f08f4d5b3ee61c7f3b2b", - "sha256:b45f505d0d85f4cdd440cd7500689b8e95110371eaa09da0c0b1103e9a05030f", - "sha256:b4c08ecb26e4270a62f81f81899dfff91623d349e433b126931c9c4577169666", - "sha256:b53cd668facd60b4f0dfcf092e01bbfefd88271b5b4e7b08eca3184dd006cb30", - "sha256:b6b37b4c3acb8472d191816d4582379f64d81cecbdce1a668601745c963ca5cc", - "sha256:b9b00c9ee1cc3a76f1f16e94a23c344e0b6e5c10bec7f94cf2d820ce303b8c01", - "sha256:bc6e8678bfa5ccba370103976ccfcf776c85c83da9220ead41ea6fd15d2277b4", - "sha256:bdc13911db524bd63f37b0103af014b7161427ada41f1b0b3c9b5b5a9c1ca927", - "sha256:bf6389133bb255e530a4f2f553f41c4dd795b1fbb6f797aea1eff308f1e11606", - "sha256:c09a40f28dcded933dc16217d6a092be0cc49ae25811d3b8e937c8060647c353", - "sha256:c0a9d8d25ed2f2183e8471c97d512a31153e123ac5807f61396158ef2793cb6e", - "sha256:c35326f94702a7264aa0eea826a79547d3396a41ae87a70511b9f6e9667ad31c", - "sha256:c3eb4278dcdb9d86265ed2c20b9ecac45f2d6072e3904542e591e382c87a9c00", - "sha256:c4b84d6b580a9625dfa47269bf1fd7fbba7ad69e08b16366a46acb005959c395", - "sha256:c9780de781a0d62a7c3680d07963db3048b919fc9e3726d9cfd97296a65ffce1", - "sha256:cac5eaeec3549c5df7f8f97a5a6db6963b91639389cdd735d5a806370847732b", - "sha256:d1a94cbb4ee64af3ab386c2d63d6d9e9cf2e256ac0fd30f33ef0a3c88f575174", - "sha256:d46bc3e58b01e4f38d75e0d7f745a46875b7a282df145aca9d1479c65ff11561", - "sha256:d48e06be8d8c58e7feaedd8a37897a6122637efb1637d7ce00ddf5f11f9a92ad", - "sha256:d49919c95d31ee06eefd43d8c6f69a3cc9bdf0a9b979cc234c4071f0eb5cb173", - "sha256:d4b83aed409134093d90e114007034d2c1ebcd92e501b71fd9ec70e612c8b2eb", - "sha256:d64ea1686474074b38da13ae218d9fde0d1dc6525266976808f41ac98d9d7980", - "sha256:d67b50abc2df68502a26ed2ccea60c1a7054c289fb7fc31c12e5e55e4eec66bd", - "sha256:d685d458505b2bfd2e28c812749fe9194a2b0ce285a83537e4309a187ffa270b", - "sha256:d8f74ef8aacdf6ee5c07566a597634bb8535f6b53dc89790db43412498cf6026", - "sha256:d9d9f82ff2c3bf9bb777cb355149f7f3a98ec58f16b7428369dc27ea89556a4c", - "sha256:da4c9223319400b97a2acdfb10926b807e51b69eb7eb80aad4942c0516934858", - "sha256:dae97d9435dc90590f119d056d233c33006b2fd235dd990d5564992261ee7ae8", - "sha256:dc0e9bdb3aa4d1de703a437576007d366b54f52c9897cae1a3716bb44fc1fc85", - "sha256:e3bef90af21d31c4544bc917f51e04f94ae11b43156356aff243cdd84802cbf2", - "sha256:e5dea998c891f082fe204dec6565dbc2f9304478f2fc97bd4d7a940fec16c873", - "sha256:e70ad4c9658beeff99856926fd3ee5fde8b519b92c693f856007177c36eb2e30", - "sha256:e885a1bf98a76dff0a0648850c3083b99d9358ef91ba8fa307c681e8e0732503", - "sha256:ea0c3b7922209160faef194a5b6995bfe7fa05ff7dda6c423ba17646b7b9de10", - "sha256:eae4136a3b8c4cf76f69461fc8f9410d55d34ea48e1185338848a888d71b9675", - "sha256:ebfceaa2ea588b54efb6160e3520983663d45aed8a3895bb2031ada080fb5f04", - "sha256:ef926e9f11e307b5a7c97b17c5c609a93fb59ffa8337afac8f89e6fe54eb0b37", - "sha256:f79a63289dbaba964eb29ed3c103b7911f2dce28c36fe87c36a114e6bd21d7ad", - "sha256:f8695752cf5d639b4e981afe6c99e060621362c416058effd5c704bede9cb5d1" + "sha256:013090383863b72c62a702d07678b658fa2567aa58d373d963cca245b017e065", + "sha256:032e65120339d44cdc3efc326c9f660f5f7205f3a535c1fdbf898b29ea01fb72", + "sha256:048a930eb4572829604982e39a0c7289ab5dc8abc7fc9f5aabd6fbc08c154e93", + "sha256:04d67ceee6db4bcb92987ccb16e53bef6b42ced872509f333c04fb58a3315256", + "sha256:059c4cbf3973a621b62ea3132934ae737da2c132a788e6cfb9b08d63a0ef73f9", + "sha256:0e32698462aacc5c1cf6bdfebc9c781821b7e74c79f13e5ffc8bfe27c42b1abf", + "sha256:1676b56d48048a62ef77a250428d1f31f610763636e0784ba67a9740823988ca", + "sha256:17f090a9bc0ce8da51a5632092f98a7e7f84bca26f33d161a98b57f7fb0004ca", + "sha256:185efc2fed89cdd97552585c624d3c908f0464090f4b91f7d92f8ed2f3b18f54", + "sha256:1fa377b827ca2023244a06554c6e7dc6828a10aaf74ca41965c5d8a4925aebb4", + "sha256:2181e4b1d07dde53986023482673c0f1fba5178ef800f9ab95ad791e8bdded6a", + "sha256:219e0431ea8006e15005767f0351e3f7f9143e793e58519dc97fe9e07fae5563", + "sha256:21db1ec5525780fd07251636eb5f7acb84003e9382c72c18c542a87c416ade03", + "sha256:246b40f8a4aec341cbbf52617cad8ab7c888d944bfe12a6abd2b1f6cfb6f6082", + "sha256:2793a627e95d119e9f1e19720730472f5543a6d84c50ea33313ce328d870f2dd", + "sha256:2930aa001a3776c3e2601cb8e0a15d21b8270528d89cc308be4843ade546b9ab", + "sha256:2ae06fbab4f1bb7db4f7c8ca9897dc8db4447d1a2b9bee78474ad403437bcc29", + "sha256:2b4790b558bee331a933e08883c423f65bbcd07e278f91b2272489e31ab1e2b4", + "sha256:2cfcf84f1defed7e5798ef4f88aa25fcc52d279be731ce904789aa7ccfb7e8d2", + "sha256:2dd1cc3ea7e60bfb31ff32cafe07e24839df573a5e7c2d33304082a5019bcd58", + "sha256:2f34687222b78fff795feeb799a7d44eca2477c3d9d3a46ce17d51a4f383e32e", + "sha256:310b719b695b3dd442cdfbbe64936b2f2e231bb91d998e99e6f0daf991a3eba3", + "sha256:34190a1ec4f1e84af256495436b2d196529c3f2094f0af80202947567fdbf2e7", + "sha256:35bc626eec405f745199200ccb5c6b36f202675d204aa29bb52e27ba2b71dea8", + "sha256:36531f81c8214e293097cd2b7873f178997dae33d3667caaae8bdfb9666b76c0", + "sha256:390240baeb9f415a82eefc2e13285016f9c8b5ad71ec80574ae8fa9605093cd7", + "sha256:40442e2a4456e9910875ac12951476d36c0870dcb38a68719f8c4686609897c4", + "sha256:4337e4aec93b7c011f7ee2e357b0d30562edd1955620fdd4aeab6aacd90d43c5", + "sha256:43cfbb7db02b30ad3926e8fceaef260ba2fb7df787e38fa2df890c1ca7966c3b", + "sha256:43fe5af2d590bf4691531b1d9a2495d7aab2090547eaacd224a3afec95706d76", + "sha256:46b9ed911f36bfeb6338e0b482e7fe7c27d362c52fde29f221fddbc9ee2227e7", + "sha256:4d23854ecf381ab1facc8f353dcd9adeddef3652268ee75297c1164c987c11dc", + "sha256:4d6036c3a296707357efb375cfc24bb64cd955b9ec731abf11ebb1e40063949f", + "sha256:4eb114a0754fd00075c12648d991ec7a4357f9cb873042cc9a77bf3a7e30c9db", + "sha256:4ee56288d0df919e4aac43b539dd0e34bb55d6a12a6562038e8d6f3ed07f9e36", + "sha256:51a5e4c61a4541bd1cd3ba74766d0c9b6c12d6a1a4964ef60026832aac8e79b3", + "sha256:522fe7abb41309e9543b0d9b8b434f2b630c5fdaf6482bee642b34c8c70079c8", + "sha256:54c4855eabd9fc29707d30141be99e5cd1102e7d2258d2892314cf4c110726c3", + "sha256:5592401cdf3dc682194727c1ddaa8aa0f3ddc57ca64fd03226a430b955eab6f6", + "sha256:58ffd35bd5425c3c3b9692d078bf7ab851441434531a7e517c4984d5634cd65b", + "sha256:5967fe415b1920a3877a4195e9a2b779249630ee49ece22021c690320ff07452", + "sha256:5fcd7d3b1d8ecb91445bd71b9c88bdbeae528fefee4f379895becfc72298d181", + "sha256:63b634facdfbad421d4b61c90735688465d4ab3a8853ac22c76ccac2baf98d97", + "sha256:690b20e3388a7ec98e899fd54c924e50ba6693874aa65ef9cb53de7f7de9d64a", + "sha256:6da7cd4f405fd7db56e51e96bff0865b9853ae70df0e6720624049da76bde2da", + "sha256:7488a43033c958637b1a08cddc9188eb06d3ad36582cebc7d4815980b47e27ef", + "sha256:74e748012f8c19b47f7d6321ac929a9a94ee92ef12bc4298c47e8b7219b26541", + "sha256:78718d8454a6e928470d511bf8ac93f469283a45c354995f7d19e77292f26108", + "sha256:7bf61bc4345c1895221357af8f3e89f8c103d93156ef326532d35c707e2fb19d", + "sha256:7da298e1659e45d151b4028ad5c7974917e108afb48731f4ed785d02b6818994", + "sha256:84ef591495ffd3f9dcabffd6391db7bb70d7230b5c35ef5148354a134f56f2be", + "sha256:85b14a4689d5cff426c12eefe750738648706ea2753b20c2f973b2a000d3d261", + "sha256:8a2e76efbf8772add72d002d67a4c3d0958638696f541734304c7f28217a9cab", + "sha256:8a78d6c9168f5bcb20971bf3329c2b83078611fbe1f807baadc64afc70523b3a", + "sha256:8cb26f51c82d77483cdcd2b4a53cda55bbee29b3c2f3ddeb47182a2a9064e4eb", + "sha256:8db5dc617cb937ae17ff3403c3a70a7de9df4852a046f93e71edaec678f721d0", + "sha256:9ab542c91f5a47aaa58abdd8ea84b498e8e49fe4b883d67800017757a3eb78e8", + "sha256:9da022c14baeec36edfcc8daf0e281e2f55b950249a455776f0d1adeeada4734", + "sha256:9f4b481b6cc3a897adb4279216695150bbe7a44c03daba3c894f49d2037e0a24", + "sha256:a52a4704811e2623b0324a18d41ad4b9fabf43ce5ff99b14e40a520e2190c851", + "sha256:a55da151d0b0c6ab176b4e761670ac0e2667817a1e0dadd04a01d0561a219349", + "sha256:a674c0948789e9136d69065cc28009c1b1874c6ea340253db58be7622ce6398f", + "sha256:ae74f7c762270196d2dda56f8dd7309411f08a4084ff2dfcc0b095a218df2e06", + "sha256:afd27d8629ae94c5d863e32ab0e1d5590371d296b87dae0a751fb22bf3685741", + "sha256:b2d71cdefda9424adff9a3607ba5bbfc60ee972d73c21c7e3c19e71037574816", + "sha256:b34339898bb556a2351a1830f88f751679f343eabf9cf05841c95b165152c9e7", + "sha256:b372d10d17a701b0945f67be58fae4664fd056b85e0ff0fbc1e6c951cdbc0512", + "sha256:b3c98d5b24c6095e89e03d65d5c574705be3d49c0d8ca10c17a8a4b5201b72f5", + "sha256:b8dd6dd0e9c1992613ccda2bcb74fc9d49159dbe0f0ca4753f37527749885c25", + "sha256:bd5913b4972681ffc9718bc2d4c53cde39ef81415e1671ff93e9aa30b46595e7", + "sha256:c0b5fa5eda84057a4f1bbb4bb77a8c28ff20ae7ce211588d698ae453e13c6281", + "sha256:c16304bba98f48a28ae10e32a8e75c349dd742c45156f297e16eeb1ba9287a1f", + "sha256:c24b8efd9c0f62bad0439283c2c795ef916c5a6b75f03c17799775c7ae3c0c9e", + "sha256:c2a5e8d207311a0170aca0eb6b160af91adc29ec121832e4ac151a57743a1e1e", + "sha256:c352fc8f36f7e9727db17adbf93f82499457b3d7e5511368569b4c5bd155a922", + "sha256:c86df1c9af35d903d2b52d22ea3e66db8058d21dc0f59842ca5deb0595921141", + "sha256:c907516d49f77f6cd8ead1322198bdfd902003c3c330c77a1c5f3cc32a0e4d16", + "sha256:ca50bd612438258a91b5b3788c6621c1f05c8c478e7951899f492be42defc0da", + "sha256:d18a25b19ca7307045581b18b3ec9ead2b1db5ccd8719c291f0cd0a5cec6cb81", + "sha256:d4f0c66df4386b75d2ab1e20a489f30dc7fd9a06a896d64980541506086be1f1", + "sha256:d6e200909a119626744dd81bae409fc44134389e03fbf1d68ed2a55a2fb10991", + "sha256:d7ae472f74afcc47320238b5dbfd363aba111a525943c8a34a1b657c6be934c3", + "sha256:db0efd6bae1c4730b9c863fc4f5f3c0fa3e8f05cae2c44ae141cb9dfc7d091dc", + "sha256:dbdd7679a6f4f08152818043dbb39491d1af3332128b3752c3ec5cebc0011a72", + "sha256:e0b1520ef900e9ef62e392dd3d7ae4f5fa224d1dd62897a792cf353eb20b6cae", + "sha256:e2030956cf4886b10be9a0285c6802e078ec2391e1dd7ff3eb509c2c95a69b76", + "sha256:e35e8aaaf3981489f42884b59726693de32dabfc438ac10ef4eb3409961fd402", + "sha256:e380e85b93f148ad28ac15f8117e2fd8e5437aa7732d65e260134f83ce67911b", + "sha256:edf6e4c8fe14dfe316939711e3ece3f9a20760aabf686051b537a7562f4da91a", + "sha256:f3389924581d9a770c6caa4df4e74b606180869043b9073e2cec324bad6e306e", + "sha256:f64ccf593916e93b8d36ed55401bb7fe9c7d5de3180ce2e10b08f82a8f397316", + "sha256:f720a14aa102a38907c6d5030e3d66b3b680c3e6f6bc95473931ea3c00c59967", + "sha256:f8d19565ae3eb956d84da3ef367aa7def14a2735d05bd275cd54c0301f0d0d6c", + "sha256:f97487996a39cb18278ca33f7be98198f278d0bc3c5d0fd4d7b3d63646ca3c8a" ], - "markers": "python_version >= '3.6'", - "version": "==5.3.2" + "markers": "python_version >= '3.8'", + "version": "==6.0.0" }, "m2r2": { "hashes": [ @@ -1534,11 +1492,11 @@ }, "nbclassic": { "hashes": [ - "sha256:61cc3612dae9d418d6c3577f7c6d8dad1d6a81b68e7a12585483283d5c92ec37", - "sha256:736ec50483a54485971db213be9207e34e51fc144c78343625b69917423644ba" + "sha256:4c52da8fc88f9f73ef512cc305091d5ce726bdca19f44ed697cb5ba12dcaad3c", + "sha256:96da3b4d7f877b1285e0adc956ea2ea9ea9f70a4ba7b7c03d558f6c9799118fa" ], - "markers": "python_version >= '3.7'", - "version": "==1.2.0" + "markers": "python_version >= '3.9'", + "version": "==1.3.1" }, "nbclient": { "hashes": [ @@ -1698,31 +1656,51 @@ }, "pandas": { "hashes": [ - "sha256:0f19504f2783526fb5b4de675ea69d68974e21c1624f4b92295d057a31d5ec5f", - "sha256:156aac90dd7b303bf0b91bae96c0503212777f86c731e41929c571125d26c8e9", - "sha256:1d59c958d6b8f96fdf850c7821571782168d5acfe75ccf78cd8d1ac15fb921df", - "sha256:1f3b74335390dda49f5d5089fab71958812bf56f42aa27663ee4c16d19f4f1c5", - "sha256:23c04dab11f3c6359cfa7afa83d3d054a8f8c283d773451184d98119ef54da97", - "sha256:2dad075089e17a72391de33021ad93720aff258c3c4b68c78e1cafce7e447045", - "sha256:46a18572f3e1cb75db59d9461940e9ba7ee38967fa48dd58f4139197f6e32280", - "sha256:4a8d5a200f8685e7ea562b2f022c77ab7cb82c1ca5b240e6965faa6f84e5c1e9", - "sha256:51e5da3802aaee1aa4254108ffaf1129a15fb3810b7ce8da1ec217c655b418f5", - "sha256:5229c95db3a907451dacebc551492db6f7d01743e49bbc862f4a6010c227d187", - "sha256:5280d057ddae06fe4a3cd6aa79040b8c205cd6dd21743004cf8635f39ed01712", - "sha256:55ec0e192eefa26d823fc25a1f213d6c304a3592915f368e360652994cdb8d9a", - "sha256:73f7da2ccc38cc988b74e5400b430b7905db5f2c413ff215506bea034eaf832d", - "sha256:784cca3f69cfd7f6bd7c7fdb44f2bbab17e6de55725e9ff36d6f382510dfefb5", - "sha256:b5af258c7b090cca7b742cf2bd67ad1919aa9e4e681007366c9edad2d6a3d42b", - "sha256:cdd76254c7f0a1583bd4e4781fb450d0ebf392e10d3f12e92c95575942e37df5", - "sha256:de62cf699122dcef175988f0714678e59c453dc234c5b47b7136bfd7641e3c8c", - "sha256:de8f8999864399529e8514a2e6bfe00fd161f0a667903655552ed12e583ae3cb", - "sha256:f045bb5c6bfaba536089573bf97d6b8ccc7159d951fe63904c395a5e486fbe14", - "sha256:f103a5cdcd66cb18882ccdc18a130c31c3cfe3529732e7f10a8ab3559164819c", - "sha256:fe454180ad31bbbe1e5d111b44443258730467f035e26b4e354655ab59405871" + "sha256:034abd6f3db8b9880aaee98f4f5d4dbec7c4829938463ec046517220b2f8574e", + "sha256:094e271a15b579650ebf4c5155c05dcd2a14fd4fdd72cf4854b2f7ad31ea30be", + "sha256:14a0cc77b0f089d2d2ffe3007db58f170dae9b9f54e569b299db871a3ab5bf46", + "sha256:1a881bc1309f3fce34696d07b00f13335c41f5f5a8770a33b09ebe23261cfc67", + "sha256:1d2b33e68d0ce64e26a4acc2e72d747292084f4e8db4c847c6f5f6cbe56ed6d8", + "sha256:213cd63c43263dbb522c1f8a7c9d072e25900f6975596f883f4bebd77295d4f3", + "sha256:23c2b2dc5213810208ca0b80b8666670eb4660bbfd9d45f58592cc4ddcfd62e1", + "sha256:2c7e2fc25f89a49a11599ec1e76821322439d90820108309bf42130d2f36c983", + "sha256:2eb4728a18dcd2908c7fccf74a982e241b467d178724545a48d0caf534b38ebf", + "sha256:34600ab34ebf1131a7613a260a61dbe8b62c188ec0ea4c296da7c9a06b004133", + "sha256:39ff73ec07be5e90330cc6ff5705c651ace83374189dcdcb46e6ff54b4a72cd6", + "sha256:404d681c698e3c8a40a61d0cd9412cc7364ab9a9cc6e144ae2992e11a2e77a20", + "sha256:40cecc4ea5abd2921682b57532baea5588cc5f80f0231c624056b146887274d2", + "sha256:430a63bae10b5086995db1b02694996336e5a8ac9a96b4200572b413dfdfccb9", + "sha256:4930255e28ff5545e2ca404637bcc56f031893142773b3468dc021c6c32a1390", + "sha256:6021910b086b3ca756755e86ddc64e0ddafd5e58e076c72cb1585162e5ad259b", + "sha256:625466edd01d43b75b1883a64d859168e4556261a5035b32f9d743b67ef44634", + "sha256:75651c14fde635e680496148a8526b328e09fe0572d9ae9b638648c46a544ba3", + "sha256:84141f722d45d0c2a89544dd29d35b3abfc13d2250ed7e68394eda7564bd6324", + "sha256:8adff9f138fc614347ff33812046787f7d43b3cef7c0f0171b3340cae333f6ca", + "sha256:951805d146922aed8357e4cc5671b8b0b9be1027f0619cea132a9f3f65f2f09c", + "sha256:9efc0acbbffb5236fbdf0409c04edce96bec4bdaa649d49985427bd1ec73e085", + "sha256:9ff730713d4c4f2f1c860e36c005c7cefc1c7c80c21c0688fd605aa43c9fcf09", + "sha256:a6872d695c896f00df46b71648eea332279ef4077a409e2fe94220208b6bb675", + "sha256:b198687ca9c8529662213538a9bb1e60fa0bf0f6af89292eb68fea28743fcd5a", + "sha256:b9d8c3187be7479ea5c3d30c32a5d73d62a621166675063b2edd21bc47614027", + "sha256:ba24af48643b12ffe49b27065d3babd52702d95ab70f50e1b34f71ca703e2c0d", + "sha256:bb32dc743b52467d488e7a7c8039b821da2826a9ba4f85b89ea95274f863280f", + "sha256:bb3be958022198531eb7ec2008cfc78c5b1eed51af8600c6c5d9160d89d8d249", + "sha256:bf5be867a0541a9fb47a4be0c5790a4bccd5b77b92f0a59eeec9375fafc2aa14", + "sha256:c06f6f144ad0a1bf84699aeea7eff6068ca5c63ceb404798198af7eb86082e33", + "sha256:c6da97aeb6a6d233fb6b17986234cc723b396b50a3c6804776351994f2a658fd", + "sha256:e0f51973ba93a9f97185049326d75b942b9aeb472bec616a129806facb129ebb", + "sha256:e1991bbb96f4050b09b5f811253c4f3cf05ee89a589379aa36cd623f21a31d6f", + "sha256:e5f08eb9a445d07720776df6e641975665c9ea12c9d8a331e0f6890f2dcd76ef", + "sha256:e78ad363ddb873a631e92a3c063ade1ecfb34cae71e9a2be6ad100f875ac1042", + "sha256:ed16339bc354a73e0a609df36d256672c7d296f3f767ac07257801aa064ff73c", + "sha256:f4dd97c19bd06bc557ad787a15b6489d2614ddaab5d104a0310eb314c724b2d2", + "sha256:f925f1ef673b4bd0271b1809b72b3270384f2b7d9d14a189b12b7fc02574d575", + "sha256:f95a2aef32614ed86216d3c450ab12a4e82084e8102e355707a1d96e33d51c34", + "sha256:fa07e138b3f6c04addfeaf56cc7fdb96c3b68a3fe5e5401251f231fce40a0d7a", + "sha256:fa35c266c8cd1a67d75971a1912b185b492d257092bdd2709bbdebe574ed228d" ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==1.4.0" + "markers": "python_version >= '3.9'", + "version": "==2.3.0" }, "pandocfilters": { "hashes": [ @@ -1758,11 +1736,11 @@ }, "prometheus-client": { "hashes": [ - "sha256:252505a722ac04b0456be05c05f75f45d760c2911ffc45f2a06bcaed9f3ae3fb", - "sha256:594b45c410d6f4f8888940fe80b5cc2521b305a1fafe1c58609ef715a001f301" + "sha256:190f1331e783cf21eb60bca559354e0a4d4378facecf78f5428c39b675d20d28", + "sha256:cca895342e308174341b2cbf99a56bef291fbc0ef7b9e5412a0f26d653ba7094" ], - "markers": "python_version >= '3.8'", - "version": "==0.21.1" + "markers": "python_version >= '3.9'", + "version": "==0.22.1" }, "prompt-toolkit": { "hashes": [ @@ -1944,11 +1922,11 @@ }, "pygments": { "hashes": [ - "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", - "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c" + "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", + "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" ], "markers": "python_version >= '3.8'", - "version": "==2.19.1" + "version": "==2.19.2" }, "python-dateutil": { "hashes": [ @@ -2183,123 +2161,153 @@ }, "rpds-py": { "hashes": [ - "sha256:0047638c3aa0dbcd0ab99ed1e549bbf0e142c9ecc173b6492868432d8989a046", - "sha256:006f4342fe729a368c6df36578d7a348c7c716be1da0a1a0f86e3021f8e98724", - "sha256:041f00419e1da7a03c46042453598479f45be3d787eb837af382bfc169c0db33", - "sha256:04ecf5c1ff4d589987b4d9882872f80ba13da7d42427234fce8f22efb43133bc", - "sha256:04f2b712a2206e13800a8136b07aaedc23af3facab84918e7aa89e4be0260032", - "sha256:0aeb3329c1721c43c58cae274d7d2ca85c1690d89485d9c63a006cb79a85771a", - "sha256:0e374c0ce0ca82e5b67cd61fb964077d40ec177dd2c4eda67dba130de09085c7", - "sha256:0f00c16e089282ad68a3820fd0c831c35d3194b7cdc31d6e469511d9bffc535c", - "sha256:174e46569968ddbbeb8a806d9922f17cd2b524aa753b468f35b97ff9c19cb718", - "sha256:1b221c2457d92a1fb3c97bee9095c874144d196f47c038462ae6e4a14436f7bc", - "sha256:208b3a70a98cf3710e97cabdc308a51cd4f28aa6e7bb11de3d56cd8b74bab98d", - "sha256:20f2712bd1cc26a3cc16c5a1bfee9ed1abc33d4cdf1aabd297fe0eb724df4272", - "sha256:24795c099453e3721fda5d8ddd45f5dfcc8e5a547ce7b8e9da06fecc3832e26f", - "sha256:2a0f156e9509cee987283abd2296ec816225145a13ed0391df8f71bf1d789e2d", - "sha256:2b2356688e5d958c4d5cb964af865bea84db29971d3e563fb78e46e20fe1848b", - "sha256:2c13777ecdbbba2077670285dd1fe50828c8742f6a4119dbef6f83ea13ad10fb", - "sha256:2d3ee4615df36ab8eb16c2507b11e764dcc11fd350bbf4da16d09cda11fcedef", - "sha256:2d53747da70a4e4b17f559569d5f9506420966083a31c5fbd84e764461c4444b", - "sha256:32bab0a56eac685828e00cc2f5d1200c548f8bc11f2e44abf311d6b548ce2e45", - "sha256:34d90ad8c045df9a4259c47d2e16a3f21fdb396665c94520dbfe8766e62187a4", - "sha256:369d9c6d4c714e36d4a03957b4783217a3ccd1e222cdd67d464a3a479fc17796", - "sha256:3a55fc10fdcbf1a4bd3c018eea422c52cf08700cf99c28b5cb10fe97ab77a0d3", - "sha256:3d2d8e4508e15fc05b31285c4b00ddf2e0eb94259c2dc896771966a163122a0c", - "sha256:3fab5f4a2c64a8fb64fc13b3d139848817a64d467dd6ed60dcdd6b479e7febc9", - "sha256:43dba99f00f1d37b2a0265a259592d05fcc8e7c19d140fe51c6e6f16faabeb1f", - "sha256:44d51febb7a114293ffd56c6cf4736cb31cd68c0fddd6aa303ed09ea5a48e029", - "sha256:493fe54318bed7d124ce272fc36adbf59d46729659b2c792e87c3b95649cdee9", - "sha256:4b28e5122829181de1898c2c97f81c0b3246d49f585f22743a1246420bb8d399", - "sha256:4cd031e63bc5f05bdcda120646a0d32f6d729486d0067f09d79c8db5368f4586", - "sha256:528927e63a70b4d5f3f5ccc1fa988a35456eb5d15f804d276709c33fc2f19bda", - "sha256:564c96b6076a98215af52f55efa90d8419cc2ef45d99e314fddefe816bc24f91", - "sha256:5db385bacd0c43f24be92b60c857cf760b7f10d8234f4bd4be67b5b20a7c0b6b", - "sha256:5ef877fa3bbfb40b388a5ae1cb00636a624690dcb9a29a65267054c9ea86d88a", - "sha256:5f6e3cec44ba05ee5cbdebe92d052f69b63ae792e7d05f1020ac5e964394080c", - "sha256:5fc13b44de6419d1e7a7e592a4885b323fbc2f46e1f22151e3a8ed3b8b920405", - "sha256:60748789e028d2a46fc1c70750454f83c6bdd0d05db50f5ae83e2db500b34da5", - "sha256:60d9b630c8025b9458a9d114e3af579a2c54bd32df601c4581bd054e85258143", - "sha256:619ca56a5468f933d940e1bf431c6f4e13bef8e688698b067ae68eb4f9b30e3a", - "sha256:630d3d8ea77eabd6cbcd2ea712e1c5cecb5b558d39547ac988351195db433f6c", - "sha256:63981feca3f110ed132fd217bf7768ee8ed738a55549883628ee3da75bb9cb78", - "sha256:66420986c9afff67ef0c5d1e4cdc2d0e5262f53ad11e4f90e5e22448df485bf0", - "sha256:675269d407a257b8c00a6b58205b72eec8231656506c56fd429d924ca00bb350", - "sha256:6a4a535013aeeef13c5532f802708cecae8d66c282babb5cd916379b72110cf7", - "sha256:6a727fd083009bc83eb83d6950f0c32b3c94c8b80a9b667c87f4bd1274ca30ba", - "sha256:6e1daf5bf6c2be39654beae83ee6b9a12347cb5aced9a29eecf12a2d25fff664", - "sha256:6eea559077d29486c68218178ea946263b87f1c41ae7f996b1f30a983c476a5a", - "sha256:75a810b7664c17f24bf2ffd7f92416c00ec84b49bb68e6a0d93e542406336b56", - "sha256:772cc1b2cd963e7e17e6cc55fe0371fb9c704d63e44cacec7b9b7f523b78919e", - "sha256:78884d155fd15d9f64f5d6124b486f3d3f7fd7cd71a78e9670a0f6f6ca06fb2d", - "sha256:79e8d804c2ccd618417e96720ad5cd076a86fa3f8cb310ea386a3e6229bae7d1", - "sha256:7e80d375134ddb04231a53800503752093dbb65dad8dabacce2c84cccc78e964", - "sha256:8097b3422d020ff1c44effc40ae58e67d93e60d540a65649d2cdaf9466030791", - "sha256:8205ee14463248d3349131bb8099efe15cd3ce83b8ef3ace63c7e976998e7124", - "sha256:8212ff58ac6dfde49946bea57474a386cca3f7706fc72c25b772b9ca4af6b79e", - "sha256:823e74ab6fbaa028ec89615ff6acb409e90ff45580c45920d4dfdddb069f2120", - "sha256:84e0566f15cf4d769dade9b366b7b87c959be472c92dffb70462dd0844d7cbad", - "sha256:896c41007931217a343eff197c34513c154267636c8056fb409eafd494c3dcdc", - "sha256:8aa362811ccdc1f8dadcc916c6d47e554169ab79559319ae9fae7d7752d0d60c", - "sha256:8b3b397eefecec8e8e39fa65c630ef70a24b09141a6f9fc17b3c3a50bed6b50e", - "sha256:8ebc7e65ca4b111d928b669713865f021b7773350eeac4a31d3e70144297baba", - "sha256:9168764133fd919f8dcca2ead66de0105f4ef5659cbb4fa044f7014bed9a1797", - "sha256:921ae54f9ecba3b6325df425cf72c074cd469dea843fb5743a26ca7fb2ccb149", - "sha256:92558d37d872e808944c3c96d0423b8604879a3d1c86fdad508d7ed91ea547d5", - "sha256:951cc481c0c395c4a08639a469d53b7d4afa252529a085418b82a6b43c45c240", - "sha256:998c01b8e71cf051c28f5d6f1187abbdf5cf45fc0efce5da6c06447cba997034", - "sha256:9abc80fe8c1f87218db116016de575a7998ab1629078c90840e8d11ab423ee25", - "sha256:9be4f99bee42ac107870c61dfdb294d912bf81c3c6d45538aad7aecab468b6b7", - "sha256:9c39438c55983d48f4bb3487734d040e22dad200dab22c41e331cee145e7a50d", - "sha256:9d7e8ce990ae17dda686f7e82fd41a055c668e13ddcf058e7fb5e9da20b57793", - "sha256:9ea7f4174d2e4194289cb0c4e172d83e79a6404297ff95f2875cf9ac9bced8ba", - "sha256:a18fc371e900a21d7392517c6f60fe859e802547309e94313cd8181ad9db004d", - "sha256:a36b452abbf29f68527cf52e181fced56685731c86b52e852053e38d8b60bc8d", - "sha256:a5b66d1b201cc71bc3081bc2f1fc36b0c1f268b773e03bbc39066651b9e18391", - "sha256:a824d2c7a703ba6daaca848f9c3d5cb93af0505be505de70e7e66829affd676e", - "sha256:a88c0d17d039333a41d9bf4616bd062f0bd7aa0edeb6cafe00a2fc2a804e944f", - "sha256:aa6800adc8204ce898c8a424303969b7aa6a5e4ad2789c13f8648739830323b7", - "sha256:aad911555286884be1e427ef0dc0ba3929e6821cbeca2194b13dc415a462c7fd", - "sha256:afc6e35f344490faa8276b5f2f7cbf71f88bc2cda4328e00553bd451728c571f", - "sha256:b9a4df06c35465ef4d81799999bba810c68d29972bf1c31db61bfdb81dd9d5bb", - "sha256:bb2954155bb8f63bb19d56d80e5e5320b61d71084617ed89efedb861a684baea", - "sha256:bbc4362e06f950c62cad3d4abf1191021b2ffaf0b31ac230fbf0526453eee75e", - "sha256:c0145295ca415668420ad142ee42189f78d27af806fcf1f32a18e51d47dd2052", - "sha256:c30ff468163a48535ee7e9bf21bd14c7a81147c0e58a36c1078289a8ca7af0bd", - "sha256:c347a20d79cedc0a7bd51c4d4b7dbc613ca4e65a756b5c3e57ec84bd43505b47", - "sha256:c43583ea8517ed2e780a345dd9960896afc1327e8cf3ac8239c167530397440d", - "sha256:c61a2cb0085c8783906b2f8b1f16a7e65777823c7f4d0a6aaffe26dc0d358dd9", - "sha256:c9ca89938dff18828a328af41ffdf3902405a19f4131c88e22e776a8e228c5a8", - "sha256:cc31e13ce212e14a539d430428cd365e74f8b2d534f8bc22dd4c9c55b277b875", - "sha256:cdabcd3beb2a6dca7027007473d8ef1c3b053347c76f685f5f060a00327b8b65", - "sha256:cf86f72d705fc2ef776bb7dd9e5fbba79d7e1f3e258bf9377f8204ad0fc1c51e", - "sha256:d09dc82af2d3c17e7dd17120b202a79b578d79f2b5424bda209d9966efeed114", - "sha256:d3aa13bdf38630da298f2e0d77aca967b200b8cc1473ea05248f6c5e9c9bdb44", - "sha256:d69d003296df4840bd445a5d15fa5b6ff6ac40496f956a221c4d1f6f7b4bc4d9", - "sha256:d6e109a454412ab82979c5b1b3aee0604eca4bbf9a02693bb9df027af2bfa91a", - "sha256:d8551e733626afec514b5d15befabea0dd70a343a9f23322860c4f16a9430205", - "sha256:d8754d872a5dfc3c5bf9c0e059e8107451364a30d9fd50f1f1a85c4fb9481164", - "sha256:d8f9a6e7fd5434817526815f09ea27f2746c4a51ee11bb3439065f5fc754db58", - "sha256:dbcbb6db5582ea33ce46a5d20a5793134b5365110d84df4e30b9d37c6fd40ad3", - "sha256:e0f3ef95795efcd3b2ec3fe0a5bcfb5dadf5e3996ea2117427e524d4fbf309c6", - "sha256:e13ae74a8a3a0c2f22f450f773e35f893484fcfacb00bb4344a7e0f4f48e1f97", - "sha256:e274f62cbd274359eff63e5c7e7274c913e8e09620f6a57aae66744b3df046d6", - "sha256:e838bf2bb0b91ee67bf2b889a1a841e5ecac06dd7a2b1ef4e6151e2ce155c7ae", - "sha256:e8acd55bd5b071156bae57b555f5d33697998752673b9de554dd82f5b5352727", - "sha256:e8e5ab32cf9eb3647450bc74eb201b27c185d3857276162c101c0f8c6374e098", - "sha256:ebcb786b9ff30b994d5969213a8430cbb984cdd7ea9fd6df06663194bd3c450c", - "sha256:ebea2821cdb5f9fef44933617be76185b80150632736f3d76e54829ab4a3b4d1", - "sha256:ed0ef550042a8dbcd657dfb284a8ee00f0ba269d3f2286b0493b15a5694f9fe8", - "sha256:eda5c1e2a715a4cbbca2d6d304988460942551e4e5e3b7457b50943cd741626d", - "sha256:f5c0ed12926dec1dfe7d645333ea59cf93f4d07750986a586f511c0bc61fe103", - "sha256:f6016bd950be4dcd047b7475fdf55fb1e1f59fc7403f387be0e8123e4a576d30", - "sha256:f9e0057a509e096e47c87f753136c9b10d7a91842d8042c2ee6866899a717c0d", - "sha256:fc1c892b1ec1f8cbd5da8de287577b455e388d9c328ad592eabbdcb6fc93bee5", - "sha256:fc2c1e1b00f88317d9de6b2c2b39b012ebbfe35fe5e7bef980fd2a91f6100a07", - "sha256:fd822f019ccccd75c832deb7aa040bb02d70a92eb15a2f16c7987b7ad4ee8d83" + "sha256:0919f38f5542c0a87e7b4afcafab6fd2c15386632d249e9a087498571250abe3", + "sha256:093d63b4b0f52d98ebae33b8c50900d3d67e0666094b1be7a12fffd7f65de74b", + "sha256:0a0b60701f2300c81b2ac88a5fb893ccfa408e1c4a555a77f908a2596eb875a5", + "sha256:0c71c2f6bf36e61ee5c47b2b9b5d47e4d1baad6426bfed9eea3e858fc6ee8806", + "sha256:0dc23bbb3e06ec1ea72d515fb572c1fea59695aefbffb106501138762e1e915e", + "sha256:0dfa6115c6def37905344d56fb54c03afc49104e2ca473d5dedec0f6606913b4", + "sha256:12bff2ad9447188377f1b2794772f91fe68bb4bbfa5a39d7941fbebdbf8c500f", + "sha256:1533b7eb683fb5f38c1d68a3c78f5fdd8f1412fa6b9bf03b40f450785a0ab915", + "sha256:1766b5724c3f779317d5321664a343c07773c8c5fd1532e4039e6cc7d1a815be", + "sha256:181ef9b6bbf9845a264f9aa45c31836e9f3c1f13be565d0d010e964c661d1e2b", + "sha256:183f857a53bcf4b1b42ef0f57ca553ab56bdd170e49d8091e96c51c3d69ca696", + "sha256:191aa858f7d4902e975d4cf2f2d9243816c91e9605070aeb09c0a800d187e323", + "sha256:1a8b0dd8648709b62d9372fc00a57466f5fdeefed666afe3fea5a6c9539a0331", + "sha256:1c962145c7473723df9722ba4c058de12eb5ebedcb4e27e7d902920aa3831ee8", + "sha256:1cc81d14ddfa53d7f3906694d35d54d9d3f850ef8e4e99ee68bc0d1e5fed9a9c", + "sha256:1d815d48b1804ed7867b539236b6dd62997850ca1c91cad187f2ddb1b7bbef19", + "sha256:1e6c15d2080a63aaed876e228efe4f814bc7889c63b1e112ad46fdc8b368b9e1", + "sha256:20ab1ae4fa534f73647aad289003f1104092890849e0266271351922ed5574f8", + "sha256:20dae58a859b0906f0685642e591056f1e787f3a8b39c8e8749a45dc7d26bdb0", + "sha256:238e8c8610cb7c29460e37184f6799547f7e09e6a9bdbdab4e8edb90986a2318", + "sha256:24a4146ccb15be237fdef10f331c568e1b0e505f8c8c9ed5d67759dac58ac246", + "sha256:257d011919f133a4746958257f2c75238e3ff54255acd5e3e11f3ff41fd14256", + "sha256:2a343f91b17097c546b93f7999976fd6c9d5900617aa848c81d794e062ab302b", + "sha256:2abe21d8ba64cded53a2a677e149ceb76dcf44284202d737178afe7ba540c1eb", + "sha256:2c03c9b0c64afd0320ae57de4c982801271c0c211aa2d37f3003ff5feb75bb04", + "sha256:2c9c1b92b774b2e68d11193dc39620d62fd8ab33f0a3c77ecdabe19c179cdbc1", + "sha256:3021933c2cb7def39d927b9862292e0f4c75a13d7de70eb0ab06efed4c508c19", + "sha256:3100b3090269f3a7ea727b06a6080d4eb7439dca4c0e91a07c5d133bb1727ea7", + "sha256:313cfcd6af1a55a286a3c9a25f64af6d0e46cf60bc5798f1db152d97a216ff6f", + "sha256:35e9a70a0f335371275cdcd08bc5b8051ac494dd58bff3bbfb421038220dc871", + "sha256:38721d4c9edd3eb6670437d8d5e2070063f305bfa2d5aa4278c51cedcd508a84", + "sha256:390e3170babf42462739a93321e657444f0862c6d722a291accc46f9d21ed04e", + "sha256:39bfea47c375f379d8e87ab4bb9eb2c836e4f2069f0f65731d85e55d74666387", + "sha256:3ac51b65e8dc76cf4949419c54c5528adb24fc721df722fd452e5fbc236f5c40", + "sha256:3c0909c5234543ada2515c05dc08595b08d621ba919629e94427e8e03539c958", + "sha256:3da5852aad63fa0c6f836f3359647870e21ea96cf433eb393ffa45263a170d44", + "sha256:3e1157659470aa42a75448b6e943c895be8c70531c43cb78b9ba990778955582", + "sha256:4019a9d473c708cf2f16415688ef0b4639e07abaa569d72f74745bbeffafa2c7", + "sha256:43f10b007033f359bc3fa9cd5e6c1e76723f056ffa9a6b5c117cc35720a80292", + "sha256:49028aa684c144ea502a8e847d23aed5e4c2ef7cadfa7d5eaafcb40864844b7a", + "sha256:4916dc96489616a6f9667e7526af8fa693c0fdb4f3acb0e5d9f4400eb06a47ba", + "sha256:4a59e5bc386de021f56337f757301b337d7ab58baa40174fb150accd480bc953", + "sha256:4b1f66eb81eab2e0ff5775a3a312e5e2e16bf758f7b06be82fb0d04078c7ac51", + "sha256:4c5fe114a6dd480a510b6d3661d09d67d1622c4bf20660a474507aaee7eeeee9", + "sha256:4c70c70f9169692b36307a95f3d8c0a9fcd79f7b4a383aad5eaa0e9718b79b37", + "sha256:4d11382bcaf12f80b51d790dee295c56a159633a8e81e6323b16e55d81ae37e9", + "sha256:4f01a5d6444a3258b00dc07b6ea4733e26f8072b788bef750baa37b370266137", + "sha256:4f789e32fa1fb6a7bf890e0124e7b42d1e60d28ebff57fe806719abb75f0e9a3", + "sha256:4feb7511c29f8442cbbc28149a92093d32e815a28aa2c50d333826ad2a20fdf0", + "sha256:511d15193cbe013619dd05414c35a7dedf2088fcee93c6bbb7c77859765bd4e8", + "sha256:519067e29f67b5c90e64fb1a6b6e9d2ec0ba28705c51956637bac23a2f4ddae1", + "sha256:521ccf56f45bb3a791182dc6b88ae5f8fa079dd705ee42138c76deb1238e554e", + "sha256:529c8156d7506fba5740e05da8795688f87119cce330c244519cf706a4a3d618", + "sha256:582462833ba7cee52e968b0341b85e392ae53d44c0f9af6a5927c80e539a8b67", + "sha256:5963b72ccd199ade6ee493723d18a3f21ba7d5b957017607f815788cef50eaf1", + "sha256:59b2093224a18c6508d95cfdeba8db9cbfd6f3494e94793b58972933fcee4c6d", + "sha256:5afaddaa8e8c7f1f7b4c5c725c0070b6eed0228f705b90a1732a48e84350f4e9", + "sha256:5afea17ab3a126006dc2f293b14ffc7ef3c85336cf451564a0515ed7648033da", + "sha256:5e09330b21d98adc8ccb2dbb9fc6cb434e8908d4c119aeaa772cb1caab5440a0", + "sha256:6188de70e190847bb6db3dc3981cbadff87d27d6fe9b4f0e18726d55795cee9b", + "sha256:68ffcf982715f5b5b7686bdd349ff75d422e8f22551000c24b30eaa1b7f7ae84", + "sha256:696764a5be111b036256c0b18cd29783fab22154690fc698062fc1b0084b511d", + "sha256:69a607203441e07e9a8a529cff1d5b73f6a160f22db1097211e6212a68567d11", + "sha256:69b312fecc1d017b5327afa81d4da1480f51c68810963a7336d92203dbb3d4f1", + "sha256:69f0c0a3df7fd3a7eec50a00396104bb9a843ea6d45fcc31c2d5243446ffd7a7", + "sha256:6a1cb5d6ce81379401bbb7f6dbe3d56de537fb8235979843f0d53bc2e9815a79", + "sha256:6d3498ad0df07d81112aa6ec6c95a7e7b1ae00929fb73e7ebee0f3faaeabad2f", + "sha256:72a8d9564a717ee291f554eeb4bfeafe2309d5ec0aa6c475170bdab0f9ee8e88", + "sha256:777c62479d12395bfb932944e61e915741e364c843afc3196b694db3d669fcd0", + "sha256:77a7711fa562ba2da1aa757e11024ad6d93bad6ad7ede5afb9af144623e5f76a", + "sha256:79061ba1a11b6a12743a2b0f72a46aa2758613d454aa6ba4f5a265cc48850158", + "sha256:7a48af25d9b3c15684059d0d1fc0bc30e8eee5ca521030e2bffddcab5be40226", + "sha256:7ab504c4d654e4a29558eaa5bb8cea5fdc1703ea60a8099ffd9c758472cf913f", + "sha256:7bdb17009696214c3b66bb3590c6d62e14ac5935e53e929bcdbc5a495987a84f", + "sha256:7da84c2c74c0f5bc97d853d9e17bb83e2dcafcff0dc48286916001cc114379a1", + "sha256:801a71f70f9813e82d2513c9a96532551fce1e278ec0c64610992c49c04c2dad", + "sha256:824e6d3503ab990d7090768e4dfd9e840837bae057f212ff9f4f05ec6d1975e7", + "sha256:82b165b07f416bdccf5c84546a484cc8f15137ca38325403864bfdf2b5b72f6a", + "sha256:84cfbd4d4d2cdeb2be61a057a258d26b22877266dd905809e94172dff01a42ae", + "sha256:84d142d2d6cf9b31c12aa4878d82ed3b2324226270b89b676ac62ccd7df52d08", + "sha256:87a5531de9f71aceb8af041d72fc4cab4943648d91875ed56d2e629bef6d4c03", + "sha256:893b022bfbdf26d7bedb083efeea624e8550ca6eb98bf7fea30211ce95b9201a", + "sha256:894514d47e012e794f1350f076c427d2347ebf82f9b958d554d12819849a369d", + "sha256:8a7898b6ca3b7d6659e55cdac825a2e58c638cbf335cde41f4619e290dd0ad11", + "sha256:8ad7fd2258228bf288f2331f0a6148ad0186b2e3643055ed0db30990e59817a6", + "sha256:92c8db839367ef16a662478f0a2fe13e15f2227da3c1430a782ad0f6ee009ec9", + "sha256:941c1cfdf4799d623cf3aa1d326a6b4fdb7a5799ee2687f3516738216d2262fb", + "sha256:9bc596b30f86dc6f0929499c9e574601679d0341a0108c25b9b358a042f51bca", + "sha256:9c55b0a669976cf258afd718de3d9ad1b7d1fe0a91cd1ab36f38b03d4d4aeaaf", + "sha256:9da4e873860ad5bab3291438525cae80169daecbfafe5657f7f5fb4d6b3f96b9", + "sha256:9def736773fd56b305c0eef698be5192c77bfa30d55a0e5885f80126c4831a15", + "sha256:9dfbe56b299cf5875b68eb6f0ebaadc9cac520a1989cac0db0765abfb3709c19", + "sha256:9e851920caab2dbcae311fd28f4313c6953993893eb5c1bb367ec69d9a39e7ed", + "sha256:9e8cb77286025bdb21be2941d64ac6ca016130bfdcd228739e8ab137eb4406ed", + "sha256:a547e21c5610b7e9093d870be50682a6a6cf180d6da0f42c47c306073bfdbbf6", + "sha256:a90a13408a7a856b87be8a9f008fff53c5080eea4e4180f6c2e546e4a972fb5d", + "sha256:a9a63785467b2d73635957d32a4f6e73d5e4df497a16a6392fa066b753e87387", + "sha256:aa81873e2c8c5aa616ab8e017a481a96742fdf9313c40f14338ca7dbf50cb55f", + "sha256:ac64f4b2bdb4ea622175c9ab7cf09444e412e22c0e02e906978b3b488af5fde8", + "sha256:aea1f9741b603a8d8fedb0ed5502c2bc0accbc51f43e2ad1337fe7259c2b77a5", + "sha256:b0afb8cdd034150d4d9f53926226ed27ad15b7f465e93d7468caaf5eafae0d37", + "sha256:b37a04d9f52cb76b6b78f35109b513f6519efb481d8ca4c321f6a3b9580b3f45", + "sha256:b5f7a446ddaf6ca0fad9a5535b56fbfc29998bf0e0b450d174bbec0d600e1d72", + "sha256:b6d9e5a2ed9c4988c8f9b28b3bc0e3e5b1aaa10c28d210a594ff3a8c02742daf", + "sha256:b6e2c12160c72aeda9d1283e612f68804621f448145a210f1bf1d79151c47090", + "sha256:b818a592bd69bfe437ee8368603d4a2d928c34cffcdf77c2e761a759ffd17d20", + "sha256:c1851f429b822831bd2edcbe0cfd12ee9ea77868f8d3daf267b189371671c80e", + "sha256:c1fb0cda2abcc0ac62f64e2ea4b4e64c57dfd6b885e693095460c61bde7bb18e", + "sha256:c5ab0ee51f560d179b057555b4f601b7df909ed31312d301b99f8b9fc6028284", + "sha256:c70d9ec912802ecfd6cd390dadb34a9578b04f9bcb8e863d0a7598ba5e9e7ccc", + "sha256:c741107203954f6fc34d3066d213d0a0c40f7bb5aafd698fb39888af277c70d8", + "sha256:ca3f059f4ba485d90c8dc75cb5ca897e15325e4e609812ce57f896607c1c0867", + "sha256:caf51943715b12af827696ec395bfa68f090a4c1a1d2509eb4e2cb69abbbdb33", + "sha256:cb28c1f569f8d33b2b5dcd05d0e6ef7005d8639c54c2f0be824f05aedf715255", + "sha256:cdad4ea3b4513b475e027be79e5a0ceac8ee1c113a1a11e5edc3c30c29f964d8", + "sha256:cf47cfdabc2194a669dcf7a8dbba62e37a04c5041d2125fae0233b720da6f05c", + "sha256:d04cab0a54b9dba4d278fe955a1390da3cf71f57feb78ddc7cb67cbe0bd30323", + "sha256:d422b945683e409000c888e384546dbab9009bb92f7c0b456e217988cf316107", + "sha256:d80bf832ac7b1920ee29a426cdca335f96a2b5caa839811803e999b41ba9030d", + "sha256:da619979df60a940cd434084355c514c25cf8eb4cf9a508510682f6c851a4f7a", + "sha256:dafd4c44b74aa4bed4b250f1aed165b8ef5de743bcca3b88fc9619b6087093d2", + "sha256:dca83c498b4650a91efcf7b88d669b170256bf8017a5db6f3e06c2bf031f57e0", + "sha256:de2713f48c1ad57f89ac25b3cb7daed2156d8e822cf0eca9b96a6f990718cc41", + "sha256:de4ed93a8c91debfd5a047be327b7cc8b0cc6afe32a716bbbc4aedca9e2a83af", + "sha256:df52098cde6d5e02fa75c1f6244f07971773adb4a26625edd5c18fee906fa84d", + "sha256:dfbf280da5f876d0b00c81f26bedce274e72a678c28845453885a9b3c22ae632", + "sha256:e3730a48e5622e598293eee0762b09cff34dd3f271530f47b0894891281f051d", + "sha256:e5162afc9e0d1f9cae3b577d9c29ddbab3505ab39012cb794d94a005825bde21", + "sha256:e5d524d68a474a9688336045bbf76cb0def88549c1b2ad9dbfec1fb7cfbe9170", + "sha256:e99685fc95d386da368013e7fb4269dd39c30d99f812a8372d62f244f662709c", + "sha256:ea89a2458a1a75f87caabefe789c87539ea4e43b40f18cff526052e35bbb4fdf", + "sha256:ec671691e72dff75817386aa02d81e708b5a7ec0dec6669ec05213ff6b77e1bd", + "sha256:eed5ac260dd545fbc20da5f4f15e7efe36a55e0e7cf706e4ec005b491a9546a0", + "sha256:f14440b9573a6f76b4ee4770c13f0b5921f71dde3b6fcb8dabbefd13b7fe05d7", + "sha256:f405c93675d8d4c5ac87364bb38d06c988e11028a64b52a47158a355079661f3", + "sha256:f53ec51f9d24e9638a40cabb95078ade8c99251945dad8d57bf4aabe86ecee35", + "sha256:f61a9326f80ca59214d1cceb0a09bb2ece5b2563d4e0cd37bfd5515c28510674", + "sha256:f7bf2496fa563c046d05e4d232d7b7fd61346e2402052064b773e5c378bf6f73", + "sha256:fbaa70553ca116c77717f513e08815aec458e6b69a028d4028d403b3bc84ff37", + "sha256:fc3e55a7db08dc9a6ed5fb7103019d2c1a38a349ac41901f9f66d7f95750942f", + "sha256:fc921b96fa95a097add244da36a1d9e4f3039160d1d30f1b35837bf108c21136", + "sha256:fd0641abca296bc1a00183fe44f7fced8807ed49d501f188faa642d0e4975b83", + "sha256:feac1045b3327a45944e7dcbeb57530339f6b17baff154df51ef8b0da34c8c12", + "sha256:ff110acded3c22c033e637dd8896e411c7d3a11289b2edf041f86663dbc791e9" ], "markers": "python_version >= '3.9'", - "version": "==0.24.0" + "version": "==0.26.0" }, "ruff": { "hashes": [ @@ -2327,31 +2335,35 @@ }, "scikit-learn": { "hashes": [ - "sha256:0834e4cec2a2e0d8978f39cb8fe1cad3be6c27a47927e1774bf5737ea65ec228", - "sha256:184a42842a4e698ffa4d849b6019de50a77a0aa24d26afa28fa49c9190bb144b", - "sha256:1beaa631434d1f17a20b1eef5d842e58c195875d2bc11901a1a70b5fe544745b", - "sha256:23a88883ca60c571a06278e4726b3b51b3709cfa4c93cacbf5568b22ba960899", - "sha256:25ba705ee1600ffc5df1dccd8fae129d7c6836e44ffcbb52d78536c9eaf8fcf9", - "sha256:40f3ff68c505cb9d1f3693397c73991875d609da905087e00e7b4477645ec67b", - "sha256:4e1ea0bc1706da45589bcf2490cde6276490a1b88f9af208dbb396fdc3a0babf", - "sha256:5546a8894a0616e92489ef995b39a0715829f3df96e801bb55cbf196be0d9649", - "sha256:680b65b3caee469541385d2ca5b03ff70408f6c618c583948312f0d2125df680", - "sha256:6b63ca2b0643d30fbf9d25d93017ed3fb8351f31175d82d104bfec60cba7bb87", - "sha256:83c772fa8c64776ad769fd764752c8452844307adcf10dee3adcc43988260f21", - "sha256:867023a044fdfe59e5014a7fec7a3086a8928f10b5dce9382eedf4135f6709a2", - "sha256:bc7073e025b62c1067cbfb76e69d08650c6b9d7a0e7afdfa20cb92d4afe516f6", - "sha256:ceb0008f345188aa236e49c973dc160b9ed504a3abd7b321a0ecabcb669be0bd", - "sha256:d395730f26d8fc752321f1953ddf72647c892d8bed74fad4d7c816ec9b602dfa", - "sha256:da29d2e379c396a63af5ed4b671ad2005cd690ac373a23bee5a0f66504e05272", - "sha256:de897720173b26842e21bed54362f5294e282422116b61cd931d4f5d870b9855", - "sha256:e9535e867281ae6987bb80620ba14cf1649e936bfe45f48727b978b7a2dbe835", - "sha256:f17420a8e3f40129aeb7e0f5ee35822d6178617007bb8f69521a2cefc20d5f00", - "sha256:fc0a72237f0c56780cf550df87201a702d3bdcbbb23c6ef7d54c19326fa23f19", - "sha256:fd3480c982b9e616b9f76ad8587804d3f4e91b4e2a6752e7dafb8a2e1f541098" + "sha256:014e07a23fe02e65f9392898143c542a50b6001dbe89cb867e19688e468d049b", + "sha256:0521cb460426c56fee7e07f9365b0f45ec8ca7b2d696534ac98bfb85e7ae4775", + "sha256:0b2f8a0b1e73e9a08b7cc498bb2aeab36cdc1f571f8ab2b35c6e5d1c7115d97d", + "sha256:126c09740a6f016e815ab985b21e3a0656835414521c81fc1a8da78b679bdb75", + "sha256:1babf2511e6ffd695da7a983b4e4d6de45dce39577b26b721610711081850906", + "sha256:2726c8787933add436fb66fb63ad18e8ef342dfb39bbbd19dc1e83e8f828a85a", + "sha256:317ca9f83acbde2883bd6bb27116a741bfcb371369706b4f9973cf30e9a03b0d", + "sha256:34cc8d9d010d29fb2b7cbcd5ccc24ffdd80515f65fe9f1e4894ace36b267ce19", + "sha256:5abd2acff939d5bd4701283f009b01496832d50ddafa83c90125a4e41c33e314", + "sha256:5b7974f1f32bc586c90145df51130e02267e4b7e77cab76165c76cf43faca0d9", + "sha256:63017a5f9a74963d24aac7590287149a8d0f1a0799bbe7173c0d8ba1523293c0", + "sha256:7d7240c7b19edf6ed93403f43b0fcb0fe95b53bc0b17821f8fb88edab97085ef", + "sha256:80bd3bd4e95381efc47073a720d4cbab485fc483966f1709f1fd559afac57ab8", + "sha256:8ef09b1615e1ad04dc0d0054ad50634514818a8eb3ee3dee99af3bffc0ef5007", + "sha256:8fa979313b2ffdfa049ed07252dc94038def3ecd49ea2a814db5401c07f1ecfa", + "sha256:9dbe48d69aa38ecfc5a6cda6c5df5abef0c0ebdb2468e92437e2053f84abb8bc", + "sha256:9f39f6a811bf3f15177b66c82cbe0d7b1ebad9f190737dcdef77cfca1ea3c19c", + "sha256:9fe7f51435f49d97bd41d724bb3e11eeb939882af9c29c931a8002c357e8cdd5", + "sha256:bf3755f25f145186ad8c403312f74fb90df82a4dfa1af19dc96ef35f57237a94", + "sha256:c01e869b15aec88e2cdb73d27f15bdbe03bce8e2fb43afbe77c45d399e73a5a3", + "sha256:c2c7243d34aaede0efca7a5a96d67fddaebb4ad7e14a70991b9abee9dc5c0379", + "sha256:d0c93294e1e1acbee2d029b1f2a064f26bd928b284938d51d412c22e0c977eb3", + "sha256:e2539bb58886a531b6e86a510c0348afaadd25005604ad35966a85c2ec378800", + "sha256:e39d95a929b112047c25b775035c8c234c5ca67e681ce60d12413afb501129f7", + "sha256:e7e7ced20582d3a5516fb6f405fd1d254e1f5ce712bfef2589f51326af6346e8" ], "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==1.2.0" + "markers": "python_version >= '3.10'", + "version": "==1.7.0" }, "scipy": { "hashes": [ @@ -2423,11 +2435,11 @@ }, "setuptools": { "hashes": [ - "sha256:18fd474d4a82a5f83dac888df697af65afa82dec7323d09c3e37d1f14288da54", - "sha256:3e386e96793c8702ae83d17b853fb93d3e09ef82ec62722e61da5cd22376dcd8" + "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", + "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c" ], "markers": "python_version >= '3.9'", - "version": "==78.1.0" + "version": "==80.9.0" }, "six": { "hashes": [ @@ -2447,18 +2459,19 @@ }, "snowballstemmer": { "hashes": [ - "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", - "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a" + "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", + "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895" ], - "version": "==2.2.0" + "markers": "python_version not in '3.0, 3.1, 3.2'", + "version": "==3.0.1" }, "soupsieve": { "hashes": [ - "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", - "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9" + "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", + "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a" ], "markers": "python_version >= '3.8'", - "version": "==2.6" + "version": "==2.7" }, "sphinx": { "hashes": [ @@ -2594,19 +2607,19 @@ }, "types-python-dateutil": { "hashes": [ - "sha256:18f493414c26ffba692a72369fea7a154c502646301ebfe3d56a04b3767284cb", - "sha256:e248a4bc70a486d3e3ec84d0dc30eec3a5f979d6e7ee4123ae043eedbb987f53" + "sha256:13e80d6c9c47df23ad773d54b2826bd52dbbb41be87c3f339381c1700ad21ee5", + "sha256:2b2b3f57f9c6a61fba26a9c0ffb9ea5681c9b83e69cd897c6b5f668d9c0cab93" ], - "markers": "python_version >= '3.8'", - "version": "==2.9.0.20241206" + "markers": "python_version >= '3.9'", + "version": "==2.9.0.20250516" }, "typing-extensions": { "hashes": [ - "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", - "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af" + "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", + "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76" ], "markers": "python_version >= '3.9'", - "version": "==4.14.0" + "version": "==4.14.1" }, "tzdata": { "hashes": [ @@ -2626,11 +2639,11 @@ }, "urllib3": { "hashes": [ - "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", - "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813" + "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", + "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc" ], "markers": "python_version >= '3.9'", - "version": "==2.4.0" + "version": "==2.5.0" }, "wcwidth": { "hashes": [ @@ -2662,14 +2675,6 @@ "markers": "python_version >= '3.8'", "version": "==1.8.0" }, - "wheel": { - "hashes": [ - "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729", - "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248" - ], - "markers": "python_version >= '3.8'", - "version": "==0.45.1" - }, "zipp": { "hashes": [ "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", diff --git a/src/mlops_codex/trainingv2/Training.ipynb b/src/mlops_codex/trainingv2/Training.ipynb deleted file mode 100644 index 80b3637..0000000 --- a/src/mlops_codex/trainingv2/Training.ipynb +++ /dev/null @@ -1,560 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "77559178", - "metadata": {}, - "source": [ - "# MLOps Training\n", - "\n", - "This notebook give a exemple on how to use MLOps to training a ML model" - ] - }, - { - "cell_type": "markdown", - "id": "18c9b0da", - "metadata": {}, - "source": [ - "### MLOpsTrainingClient\n", - "\n", - "It's where you can manage your trainining experiments" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "initial_id", - "metadata": { - "ExecuteTime": { - "end_time": "2025-04-30T12:15:41.835327Z", - "start_time": "2025-04-30T12:15:41.479994Z" - }, - "collapsed": true - }, - "outputs": [], - "source": [ - "from mlops_codex.training import MLOpsTrainingClient" - ] - }, - { - "cell_type": "markdown", - "id": "f47d2a5a", - "metadata": { - "vscode": { - "languageId": "markdown" - } - }, - "source": [ - "### Initializing the MLOpsTrainingClient\n", - "In this cell, we are initializing the `MLOpsTrainingClient` which will be used to manage our training experiments." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "556a3fb73290a75b", - "metadata": { - "ExecuteTime": { - "end_time": "2025-04-30T12:15:46.482827Z", - "start_time": "2025-04-30T12:15:44.210969Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "May 7, 2025 | INFO: __init__ Loading .env\n", - "May 7, 2025 | INFO: __init__ Successfully connected to MLOps\n" - ] - }, - { - "data": { - "text/plain": [ - "API version 1.0 \n", - " Token=\"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlFnc0JWQ0I5WFc0V1YtSkVCVkJiZyJ9.eyJodHRwczovL25lb21hcmlsLmRhdGFyaXNrLm5ldC9uZW9tYXJpbC1ncm91cCI6ImRhdGFyaXNrIiwiaHR0cHM6Ly9uZW9tYXJpbC5kYXRhcmlzay5uZXQvZW1haWwiOiJqb2hucmM2OTJAZ21haWwuY29tIiwiaHR0cHM6Ly9uZW9tYXJpbC5kYXRhcmlzay5uZXQvdGVuYW50IjoiZGF0YXJpc2siLCJodHRwczovL25lb21hcmlsLmRhdGFyaXNrLm5ldC90ZW5hbnQtYWN0aXZlIjp0cnVlLCJodHRwczovL25lb21hcmlsLmRhdGFyaXNrLm5ldC91c2VyLXBsYW4iOiJUcmlhbCIsImh0dHBzOi8vbmVvbWFyaWwuZGF0YXJpc2submV0L3VzZXItYWN0aXZlIjp0cnVlLCJodHRwczovL25lb21hcmlsLmRhdGFyaXNrLm5ldC9yb2xlIjoibWFzdGVyIiwiaXNzIjoiaHR0cHM6Ly9kZXYtbWszbzdsYXp4bGUzMGh3cS51cy5hdXRoMC5jb20vIiwic3ViIjoiYXV0aDB8NjU0OTRlMWFkOTUzN2FlMGFhZDZjNGE5IiwiYXVkIjpbImh0dHBzOi8vZGV2LW1rM283bGF6eGxlMzBod3EudXMuYXV0aDAuY29tL2FwaS92Mi8iLCJodHRwczovL2Rldi1tazNvN2xhenhsZTMwaHdxLnVzLmF1dGgwLmNvbS91c2VyaW5mbyJdLCJpYXQiOjE3NDY2NDMzMDMsImV4cCI6MTc0NjY1NDEwMywic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCBhZGRyZXNzIHBob25lIHJlYWQ6Y3VycmVudF91c2VyIHVwZGF0ZTpjdXJyZW50X3VzZXJfbWV0YWRhdGEgZGVsZXRlOmN1cnJlbnRfdXNlcl9tZXRhZGF0YSBjcmVhdGU6Y3VycmVudF91c2VyX21ldGFkYXRhIGNyZWF0ZTpjdXJyZW50X3VzZXJfZGV2aWNlX2NyZWRlbnRpYWxzIGRlbGV0ZTpjdXJyZW50X3VzZXJfZGV2aWNlX2NyZWRlbnRpYWxzIHVwZGF0ZTpjdXJyZW50X3VzZXJfaWRlbnRpdGllcyBvZmZsaW5lX2FjY2VzcyIsImd0eSI6InBhc3N3b3JkIiwiYXpwIjoia3JCNk1sR3ZkOEdBSUNZd1hPd0labU5DMFk5VGFQQTcifQ.clMUNe_MRV6VA66Zsrmv3RSQUVsU3dvVTFY1i9bFWtMEZTOOieKRPakUEgRO-f9bu3yWY3Lm5vyUywCxIY6nL5fFHKOdfqKA38hzuTfL9n_oiNnuGhQZJc6OmtC7eylvCM4Bqde0KqTU4aAXk2jOA3ny1EoEbYfkprtVwv26njC5rJqLhc_kQMtJ4cHvziWzZo_ft3vWIimmTniJqqOz5K2KX2hURUwpbEXUSSoe056a8OspGBYCrzR8zr592y9ReTeSfoL86HP-AMQeAXH4CKoD_MMJKcoOX93TW03agFx1S87vtJBJIk6k7lkQfIZYGPmdX1yA4rsSBcvqN1pmRA" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client = MLOpsTrainingClient()\n", - "client" - ] - }, - { - "cell_type": "markdown", - "id": "deb3a4c9", - "metadata": {}, - "source": [ - "## MLOpsTrainingExperiment\n", - "\n", - "It's where you can create a training experiment to find the best model" - ] - }, - { - "cell_type": "markdown", - "id": "09074b27", - "metadata": {}, - "source": [ - "#### Custom training\n", - "\n", - "With Custom training, you have to create the training function. For you, as a data scientist, it's common to re-run the entire notebook, over and over. To avoid creating the same experiment repeatedly, the `force = False` parameter will disallow it. If you wish to create a new experiment with the same attributes, turn `force = True`.\n", - "\n", - "If you have two equal experiments and pass `force = False`, the first created experiment will be chosen." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "a8f129be78149bb1", - "metadata": { - "ExecuteTime": { - "end_time": "2025-04-30T12:15:48.753762Z", - "start_time": "2025-04-30T12:15:48.637597Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "May 7, 2025 | INFO: create_training_experiment Trying to load experiment...\n", - "May 7, 2025 | INFO: __get_repeated_thash Found experiment with same attributes...\n", - "May 7, 2025 | INFO: __init__ Loading .env\n", - "May 7, 2025 | INFO: __init__ Successfully connected to MLOps\n" - ] - } - ], - "source": [ - "# Creating a new training experiment\n", - "training = client.create_training_experiment(\n", - " experiment_name='experiment',\n", - " model_type='Classification',\n", - " group='datarisk',\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "be81cf47890e1e6a", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "MLOpsTrainingExperiment(name=\"experiment\", \n", - " group=\"datarisk\", \n", - " training_id=\"T6cc61022f2640698545be2b931489921f29c9bae8844bc694361ee1a1d14918\",\n", - " model_type=Classification\n", - " )" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "training" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e7a4107da1c07f9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "May 7, 2025 | INFO: __upload_training Result\n", - "DatasetHash: D4b0221dd4ea48039e78aaecfe9516fcb77852751c1045bfb28d5c622ffa5733\n", - "ExecutionId: 3\n", - "Message: Training files have been uploaded! Use the id '3' to execute the train experiment.\n", - "\n", - "May 7, 2025 | INFO: __execute_training Model training starting - Hash: T6cc61022f2640698545be2b931489921f29c9bae8844bc694361ee1a1d14918\n", - "May 7, 2025 | INFO: __init__ Loading .env\n", - "May 7, 2025 | INFO: __init__ Successfully connected to MLOps\n", - "May 7, 2025 | INFO: __init__ Loading .env\n", - "Waiting the training run.." - ] - } - ], - "source": [ - "# With the experiment class we can create multiple model runs\n", - "PATH = './samples/train/'\n", - "\n", - "run = training.run_training(\n", - " run_name='First test',\n", - " training_type='Custom',\n", - " train_data=PATH + 'dados.csv',\n", - " requirements_file=PATH + 'requirements.txt',\n", - " source_file=PATH + 'app.py',\n", - " python_version='3.9',\n", - " training_reference='train_model',\n", - " wait_complete=True\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4a83032179dcb070", - "metadata": {}, - "outputs": [], - "source": [ - "run.status" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1c9162e4384c578a", - "metadata": {}, - "outputs": [], - "source": [ - "run.model_type" - ] - }, - { - "cell_type": "markdown", - "id": "7c90b801e64c17ee", - "metadata": {}, - "source": [ - "##### Copying a training" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a279e58c101ddc94", - "metadata": {}, - "outputs": [], - "source": [ - "copy_run = run.copy_execution(\n", - " train_data=PATH + 'dados.csv',\n", - " requirements_file=PATH + 'requirements.txt',\n", - " source_file=PATH + 'app.py',\n", - " python_version='3.9',\n", - " training_reference='train_model',\n", - " wait_complete=True\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ad830a9081ae8646", - "metadata": {}, - "outputs": [], - "source": [ - "copy_run.execution_id" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "527d95c3f53eeed3", - "metadata": {}, - "outputs": [], - "source": [ - "copy_run.status" - ] - }, - { - "cell_type": "markdown", - "id": "b55fb7e4ad997e8", - "metadata": {}, - "source": [ - "### Promote training" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "61bd54e3b648fdfd", - "metadata": {}, - "outputs": [], - "source": [ - "PATH = './samples/asyncModel/'\n", - "model = run.promote(\n", - " source_file_path=PATH + 'app.py',\n", - " schema_path=PATH + 'schema.csv',\n", - " operation=\"Async\",\n", - " model_name=\"AsyncModel\",\n", - " input_type=\".csv\",\n", - " model_reference=\"score\",\n", - " wait_complete=True\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "daedbd49", - "metadata": {}, - "source": [ - "#### AutoML\n", - "\n", - "With AutoML you just need to upload the data and some configuration" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "816f785a26dcf617", - "metadata": {}, - "outputs": [], - "source": [ - "PATH = './samples/autoML/'\n", - "\n", - "run2 = training.run_training(\n", - " run_name='First test',\n", - " training_type='AutoML',\n", - " conf_dict=PATH + \"conf.json\",\n", - " train_data=PATH + 'dados.csv',\n", - " wait_complete=True\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "44281a357e0028c6", - "metadata": {}, - "source": [ - "#### External Training\n", - "\n", - "Besides the autoML and custom training, you can perform a training on your own machine and upload the files!\n", - "\n", - "Look the example bellow\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ad876be96a8213ab", - "metadata": { - "ExecuteTime": { - "end_time": "2025-04-30T12:37:48.767708Z", - "start_time": "2025-04-30T12:36:45.349008Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "April 30, 2025 | INFO: validate Validating external training execution...\n", - "April 30, 2025 | INFO: __init__ Loading .env\n", - "April 30, 2025 | INFO: __init__ Successfully connected to MLOps\n", - "April 30, 2025 | INFO: register_execution Training Execution '12' created for First test\n", - "April 30, 2025 | INFO: send_file Features for execution was created from file!\n", - "April 30, 2025 | INFO: send_file Dataset hash = D80b29eba7fd4a89887bc45d98d660d1de8f530ade5b47629b6437d96f574d5d\n", - "April 30, 2025 | INFO: send_file Target for execution was created from file!\n", - "April 30, 2025 | INFO: send_file Dataset hash = D4f3ff1032d84e0d89bd29e82232e9a361110aacb20f4b21a3be01c8f34894d5\n", - "April 30, 2025 | INFO: send_file Output for execution was created from file!\n", - "April 30, 2025 | INFO: send_file Dataset hash = D95f28a782a8449ba63d5526521de0f2ea281b6ba2b341eab213bf53bab96ff8\n", - "April 30, 2025 | INFO: send_file Metrics for execution 12 was created from file!\n", - "April 30, 2025 | INFO: send_file Parameters for execution 12 was created from file!\n", - "April 30, 2025 | INFO: send_file Model for execution 12 was created from file!\n", - "April 30, 2025 | INFO: send_file Requirements file inserted for execution '12'\n", - "April 30, 2025 | INFO: send_json {\"Python version patched for training with hash 'Tdae4e5bdd874c5d9e94c9a2aee73d22a13e925cadef49668b82df6a68180ccc' and id '12'\"}\n", - "April 30, 2025 | INFO: host Training execution started. Use its execution id 12 to check the status.\n", - "Training your model.....\n", - "April 30, 2025 | INFO: wait_ready Training finished successfully.\n" - ] - } - ], - "source": [ - "PATH = './samples/externalUpload/'\n", - "\n", - "run3 = training.run_training(\n", - " run_name='First test',\n", - " training_type=\"External\",\n", - " features_file=PATH + 'features.parquet',\n", - " target_file=PATH + 'target.parquet',\n", - " output_file=PATH + 'predictions.parquet',\n", - " metrics_file=PATH + 'metrics.json',\n", - " parameters_file=PATH + 'params.json',\n", - " requirements_file=PATH + 'requirements.txt',\n", - " model_file=PATH + 'model.pkl',\n", - " python_version=\"3.9\",\n", - " wait_complete=True\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "21dbdbe6a258990c", - "metadata": { - "ExecuteTime": { - "end_time": "2025-04-30T12:37:52.625919Z", - "start_time": "2025-04-30T12:37:52.567819Z" - } - }, - "outputs": [ - { - "data": { - "text/plain": [ - "'Succeeded'" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run3.status" - ] - }, - { - "cell_type": "markdown", - "id": "f55fe0a26c5c2221", - "metadata": {}, - "source": [ - "---\n", - "\n", - "#### Interactive External Training\n", - "\n", - "However, if you wish something more interactive, take a look in the example bellow." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1d8b38fedf20383b", - "metadata": {}, - "outputs": [], - "source": [ - "from mlops_codex.training import MLOpsTrainingClient\n", - "client = MLOpsTrainingClient()\n", - "training = client.create_training_experiment(\n", - " experiment_name='Teste',\n", - " model_type='Classification',\n", - " group=''\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "764cec758141737c", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "from lightgbm import LGBMClassifier\n", - "from sklearn.impute import SimpleImputer\n", - "from sklearn.pipeline import make_pipeline\n", - "from sklearn.model_selection import cross_val_score" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b22680b507344617", - "metadata": {}, - "outputs": [], - "source": [ - "base_path = './samples/train/'\n", - "df = pd.read_csv(base_path+\"/dados.csv\")\n", - "X = df.drop(columns=['target'])\n", - "y = df[[\"target\"]]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3200db0515a2b3c3", - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "plt.scatter(df[\"mean_radius\"], df[\"mean_texture\"])\n", - "\n", - "# Configurar o título do gráfico\n", - "plt.title(\"Relação entre mean_radius e mean_texture\")\n", - "\n", - "# Configurar os rótulos dos eixos\n", - "plt.xlabel(\"mean_radius\")\n", - "plt.ylabel(\"mean_texture\")\n", - "\n", - "fig = plt.gcf()\n", - "\n", - "# Exibir o gráfico\n", - "plt.show()\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d748960244448dad", - "metadata": {}, - "outputs": [], - "source": [ - "pipe = make_pipeline(SimpleImputer(), LGBMClassifier(force_col_wise=True))\n", - "pipe.fit(X, y)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7dbcffb925124081", - "metadata": {}, - "outputs": [], - "source": [ - "with training.log_train(name='Teste 2', X_train=X, y_train=y) as logger:\n", - " logger.save_model(pipe)\n", - "\n", - " model_output = pd.DataFrame({\"pred\": pipe.predict(X), \"proba\": pipe.predict_proba(X)[:,1]})\n", - "\n", - " logger.save_model_output(model_output)\n", - "\n", - " logger.save_plot(fig=fig, filename=\"test-image\")\n", - "\n", - " auc = cross_val_score(pipe, X, y, cv=5, scoring=\"roc_auc\")\n", - " f_score = cross_val_score(pipe, X, y, cv=5, scoring=\"f1\")\n", - " logger.save_metric(name='auc', value=auc.mean())\n", - " logger.save_metric(name='f1_score', value=f_score.mean())\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6c57288178cbe64a", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.12" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/src/mlops_codex/trainingv2/__init__.py b/src/mlops_codex/trainingv2/__init__.py deleted file mode 100644 index 51b1142..0000000 --- a/src/mlops_codex/trainingv2/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from .training import MLOpsTrainingExperiment, MLOpsTrainingClient, MLOpsTrainingLogger - -__all__ = [ - "MLOpsTrainingExperiment", - "MLOpsTrainingClient", - "MLOpsTrainingLogger", -] \ No newline at end of file diff --git a/src/mlops_codex/trainingv2/base.py b/src/mlops_codex/trainingv2/base.py deleted file mode 100644 index 22cca76..0000000 --- a/src/mlops_codex/trainingv2/base.py +++ /dev/null @@ -1,256 +0,0 @@ -import abc -from time import sleep -from typing import Any - -from pydantic import BaseModel, Field - -from mlops_codex.__model_states import ModelExecutionState -from mlops_codex.base import BaseMLOps -from mlops_codex.exceptions import TrainingError, InputError -from mlops_codex.http_request_handler import make_request, refresh_token -from mlops_codex.logger_config import get_logger -from mlops_codex.model import SyncModel, AsyncModel - -logger = get_logger() - - -class ITrainingExecution(BaseModel, abc.ABC): - """ - Interface for training execution. - - Parameters - ---------- - training_hash: str - Training hash. - group: str - Group where the training is inserted. - model_type: str - Type of the model. It must be 'Custom', 'AutoML' or 'External' - execution_id: int - Execution ID of a training. - experiment_name: str - Name of the experiment. - login: str - Login credential. - password: str - Password credential. - url: str - Url used to connect to the MLOps server. - mlops_class: BaseMLOps - MLOps class instance. - """ - - training_hash: str = Field( - frozen=True, title="Training hash", validate_default=True - ) - group: str = Field(frozen=True, title="Group", validate_default=True) - model_type: str = Field(frozen=True, title="Model type", validate_default=True) - - execution_id: int = Field(default=None, gt=0) - experiment_name: str = Field(default=None) - - login: str = Field(default=None, repr=False) - password: str = Field(default=None, repr=False) - url: str = Field(default="https://neomaril.datarisk.net/", repr=False) - mlops_class: BaseMLOps = Field(default=None, repr=False) - - class Config: - arbitrary_types_allowed = True - - def model_post_init(self, __context: Any) -> None: - """ - Initializes the model after creation. - - Parameters - ---------- - __context: Any - Context for initialization. - """ - if self.mlops_class is None: - self.mlops_class = BaseMLOps( - login=self.login, password=self.password, url=self.url - ) - - url = f"{self.mlops_class.base_url}/training/describe/{self.group}/{self.training_hash}" - token = refresh_token(*self.mlops_class.credentials, self.mlops_class.base_url) - - response = make_request( - url=url, - method="GET", - success_code=200, - custom_exception=TrainingError, - custom_exception_message=f'Experiment "{self.training_hash}" not found.', - specific_error_code=404, - logger_msg=f'Experiment "{self.training_hash}" not found.', - headers={ - "Authorization": f"Bearer {token}", - }, - ) - - training_data = response.json()["Description"] - self.experiment_name = training_data["ExperimentName"] - - @property - def status(self) -> str: - """ - Gets the current status of the execution. - - Returns - ------- - str - Current status of the execution. - - Raises - ------ - TrainingError - If the execution is not found. - AuthenticationError - If the authentication fails. - """ - url = f"{self.mlops_class.base_url}/v2/training/execution/{self.execution_id}/status" - token = refresh_token(*self.mlops_class.credentials, self.mlops_class.base_url) - response = make_request( - url=url, - method="GET", - success_code=200, - custom_exception=TrainingError, - custom_exception_message=f"Experiment with execution id {self.execution_id} not found.", - specific_error_code=404, - logger_msg=f"Experiment with execution id {self.execution_id} not found.", - headers={ - "Authorization": f"Bearer {token}", - }, - ).json() - - status = response["Status"] - if status == "Failed": - msg = response["Message"] - logger.info(msg) - - return status - - def host(self): - """ - Hosts the current execution. - """ - url = f"{self.mlops_class.base_url}/v2/training/execution/{self.execution_id}" - token = refresh_token(*self.mlops_class.credentials, self.mlops_class.base_url) - - response = make_request( - url=url, - method="PATCH", - success_code=202, - headers={ - "Authorization": f"Bearer {token}", - "Neomaril-Origin": "Codex", - "Neomaril-Method": self.host.__qualname__, - }, - ).json() - - msg = response["Message"] - logger.info(msg) - - def wait_ready(self): - """ - Waits until the model is ready. - """ - current_status = ModelExecutionState.Running - print("Training your model...", end="", flush=True) - while current_status in [ - ModelExecutionState.Running, - ModelExecutionState.Requested, - ]: - current_status = ModelExecutionState[self.status] - sleep(30) - print(".", end="", flush=True) - print() - - if current_status == ModelExecutionState.Succeeded: - logger.info("Training finished successfully.") - else: - logger.info(f"Training failed. Current status is {current_status}") - - @abc.abstractmethod - def _promote(self, *args, **kwargs): - pass - - def promote(self, *args, **kwargs): - """ - Abstract method to promote the execution. - - Parameters - ---------- - args: tuple - Positional arguments. - kwargs - Keyword arguments. - """ - - operation: str = kwargs["operation"] - - if operation not in ["Sync", "Async"]: - raise InputError("Operation must be either 'Sync' or 'Async'.") - - wait_complete = kwargs.pop("wait_complete", False) - model_hash = self._promote(*args, **kwargs) - builder = SyncModel if operation else AsyncModel - model = builder( - name=kwargs["model_name"], - model_hash=model_hash, - login=self.login, - password=self.password, - url=self.url, - group=self.group - ) - model.host(operation) - if wait_complete: - model.wait_ready() - return model - - def execution_info(self): - """ - Abstract method to get execution information. - """ - raise NotImplementedError("Execution info is not implemented.") - - def copy_execution(self, **kwargs): - url = f"{self.mlops_class.base_url}/v2/training/execution/{self.execution_id}/copy" - token = refresh_token(*self.mlops_class.credentials, self.mlops_class.base_url) - return self._do_copy( - url, token, self.group, self.experiment_name, self.mlops_class, **kwargs - ) - - @classmethod - @abc.abstractmethod - def _do_copy(cls, url, token, group, experiment_name, mlops_class, **kwargs): - """ - Abstract method to copy the execution. - - Parameters - ---------- - url: str - URL to copy the execution. - token: str - Authentication token. - group: str - Group where the training is inserted. - experiment_name: str - Name of the experiment. - mlops_class: BaseMLOps - MLOps class instance. - kwargs: dict - Extra arguments passed to the specific function. - """ - pass - - @abc.abstractmethod - def _update_execution(self, **kwargs): - """ - Abstract method to update the execution - - Parameters - ---------- - kwargs: dict - Extra arguments passed to the specific function. - """ - pass diff --git a/src/mlops_codex/trainingv2/commons.py b/src/mlops_codex/trainingv2/commons.py deleted file mode 100644 index 9d0e2de..0000000 --- a/src/mlops_codex/trainingv2/commons.py +++ /dev/null @@ -1,44 +0,0 @@ -from mlops_codex.http_request_handler import make_request -from mlops_codex.logger_config import get_logger - -logger = get_logger() - - -def register_execution(url, token, run_name, description, training_type): - """ - Register a training execution. - - Parameters - ---------- - url: str - URL to register the execution. - token: str - Authentication token. - run_name: str - Name of the run. - description: str - Description of the training. - training_type: str - Type of training. - """ - register_training_response = make_request( - url=url, - method="POST", - success_code=201, - json={ - "RunName": run_name, - "Description": description, - "TrainingType": training_type, - }, - headers={ - "Authorization": f"Bearer {token}", - "Neomaril-Origin": "Codex", - "Neomaril-Method": "Register execution", - }, - ).json() - - msg = register_training_response.get("Message") - execution_id = register_training_response["ExecutionId"] - logger.info(f"{msg} for {run_name}") - - return execution_id diff --git a/src/mlops_codex/trainingv2/training.py b/src/mlops_codex/trainingv2/training.py deleted file mode 100644 index c5bb1e5..0000000 --- a/src/mlops_codex/trainingv2/training.py +++ /dev/null @@ -1,1097 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 -import json -import os -import re -import sys -from contextlib import contextmanager -from typing import Any, Optional, Union - -import cloudpickle -import numpy as np -import pandas as pd -from lazy_imports import try_import - -from mlops_codex.__utils import parse_json_to_yaml -from mlops_codex.base import BaseMLOps, BaseMLOpsClient -from mlops_codex.dataset import validate_dataset -from mlops_codex.datasources import MLOpsDataset -from mlops_codex.exceptions import ( - InputError, - TrainingError, -) -from mlops_codex.http_request_handler import make_request, refresh_token -from mlops_codex.logger_config import get_logger -from mlops_codex.shared import constants -from mlops_codex.shared.utils import parse_data -from mlops_codex.trainingv2.base import ITrainingExecution -from mlops_codex.trainingv2.training_executions import ( - AutoMLTrainingExecution, - CustomTrainingExecution, - ExternalTrainingExecution, -) -from mlops_codex.validations import validate_group_existence - -patt = re.compile(r"(\d+)") -logger = get_logger() - - -class MLOpsTrainingLogger: - """A class for logging MLOps training runs. - - Example - ------- - - .. code-block:: python - with training.log_train('Teste 1', X, y) as logger: - pipe.fit(X, y) - logger.save_model(pipe) - - params = pipe.get_params() - params.pop('steps') - params.pop('simpleimputer') - params.pop('lgbmclassifier') - logger.save_params(params) - - model_output = pd.DataFrame({"pred": pipe.predict(X), "proba": pipe.predict_proba(X)[:,1]}) - logger.save_model_output(model_output) - - auc = cross_val_score(pipe, X, y, cv=5, scoring="roc_auc") - f_score = cross_val_score(pipe, X, y, cv=5, scoring="f1") - logger.save_metric(name='auc', value=auc.mean()) - logger.save_metric(name='f1_score', value=f_score.mean()) - - logger.set_python_version('3.10') - """ - - def __init__( - self, - *, - name: str, - X_train: pd.DataFrame, - y_train: pd.DataFrame, - save_path: Optional[str] = None, - ): - self.name = name - self.X_train = X_train - self.y_train = y_train - self.output = None - self.model = None - self.metrics = {} - self.params = {} - self.python_version = f"{sys.version_info.major}.{sys.version_info.minor}" - self.extras = [] - - # Paths - self.features_file = None - self.target_file = None - self.output_file = None - self.metrics_file = None - self.params_file = None - self.requirements = None - self.model_file = None - - if not save_path: - dir_name = self.name.replace(" ", "_") - save_path = f"./{dir_name}" - - os.makedirs(save_path, exist_ok=True) - self.save_path = save_path - - def _processing_logging_inputs(self): - """ - Processing of everything that be logged and return object - """ - - self.__set_params() - self.params_file = self.__to_json("params", self.params) - - self.features_file = self.__to_parquet( - output_filename="features", - input_data=self.__parse_data_objects(self.X_train), - ) - - self.target_file = self.__to_parquet( - output_filename="target", input_data=self.__parse_data_objects(self.y_train) - ) - - self.output_file = self.__to_parquet( - output_filename="predictions", - input_data=self.__parse_data_objects(self.output), - ) - - if self.model: - self.model_file = self.__to_pickle( - output_filename="model", input_data=self.model - ) - - if self.metrics: - self.metrics_file = self.__to_json("metrics", self.metrics) - - def save_model(self, model): - """ - Save the trained model to the logger. - - Parameters - ---------- - model: object - The trained model. - """ - self.model = model - - def save_metric(self, *, name, value): - """ - Save a metric to the logger. - - Parameters - ---------- - name: str - The name of the metric. - value: float - The value of the metric. - """ - self.metrics[name] = value - - def save_model_output(self, output): - """ - Save the model output to the logger. - - Parameters - ---------- - output: object - The output of the trained model. - """ - self.output = output - - def set_python_version(self, version: str): - """ - Set the Python version used to train the model. - - Parameters - ---------- - version: str - The Python version. - """ - self.python_version = version - - def set_requirements(self, requirements: str): - """ - Set the project requirements. - - Parameters - ---------- - requirements: str - The path of project requirements. - """ - self.requirements = requirements - - def save_plot( - self, *, fig: object, filename: str, dpi: int = 300, ext: str = "png" - ): - """ - Save plot graphic image to the logger. - - Parameters - ---------- - fig: matplotlib.figure.Figure, seaborn.axisgrid.FacetGrid, seaborn.axes._subplots.AxesSubplot plotly.graph_objects.Figure. - Figure object - filename: str - filename without extension (extension will be added automatically). - dpi: int, default=300 - Resolution for matplotlib/seaborn plots. Default is 300. - ext: str, default='png' - File format to save (e.g., 'png', 'pdf', 'svg', 'html'). If None, defaults to 'png' for static images. - - Raises - ------ - TypeError - If the figure type is not supported. - """ - - filepath = f"{self.save_path}/{filename}.{ext}" - - with try_import() as _: - import plotly - - if isinstance(fig, plotly.graph_objs.Figure): - self.__save_plotly_plot(fig=fig, filepath=filepath) - return - - with try_import() as _: - import matplotlib.pyplot as plt - import seaborn as sns - - if isinstance(fig, sns.axisgrid.FacetGrid) or isinstance(fig, plt.Figure): - self.__save_seaborn_or_matplotlib_plot( - fig=fig, dpi=dpi, filepath=filepath - ) - return - - raise TypeError("The plot only accepts plots of Matplotlib/Plotly/Seaborn") - - def __save_plotly_plot(self, fig, filepath): - """ - Save a plotly figure to the logger. - - Parameters - ---------- - fig: plotly.graph_objects.Figure - The figure to save. - filepath: str - Path to the file to save. - **kwargs: - Extra keyword arguments passed to savefig() or write_image()/write_html(). - """ - - if filepath.endswith("html"): - fig.write_html(f"{filepath}") - return - - fig.write_image(f"{filepath}") - self.add_extra(extra=filepath) - - def __save_seaborn_or_matplotlib_plot(self, *, fig, dpi, filepath): - fig.savefig(filepath, dpi=dpi) - self.add_extra(extra=filepath) - - def set_extra(self, extra: list): - """ - Set the extra files list. - - Parameters - ---------- - extra: list - A list of paths to the extra files. - """ - self.extras = extra - - def add_extra(self, *, extra: Union[pd.DataFrame, str], filename: str = None): - """ - Add an extra file in the extra file list. - - Parameters - ---------- - extra: Union[pd.DataFrame, str] - A path of an extra file or a list to include in extra file list. - filename: Optional[str], optional - A filename if the extra is a DataFrame. - """ - - if isinstance(extra, str): - if os.path.exists(extra): - self.extras.append(extra) - else: - raise FileNotFoundError("Extra file path not found!") - elif isinstance(extra, pd.DataFrame): - if filename: - self.extras.append( - self.__to_parquet(output_filename=filename, input_data=extra) - ) - else: - raise InputError("Needs a filename to save the dataframe parquet.") - else: - raise InputError("Extra must be a Pandas DataFrame or a path.") - - def add_requirements(self, filename: str): - """ - Add a requirement file. - - Parameters - ---------- - filename: str - The name of the output filename to save. - """ - self.requirements = filename - - def __to_parquet(self, *, output_filename: str, input_data: pd.DataFrame): - """ - Transform dataframe to parquet. - - Args: - output_filename: The name of output filename to save. - input_data: A pandas dataframe to save. - """ - path = os.path.join(self.save_path, f"{output_filename}.parquet") - input_data.to_parquet(path) - self.add_extra(extra=path) - return path - - def __to_json(self, output_filename: str, input_data: dict): - """ - Transform dict to JSON. - - Args: - output_filename: The name of the output filename to save. - input_data: A dictionary to save. - """ - path = os.path.join(self.save_path, f"{output_filename}.json") - with open(path, mode="w", encoding="utf-8") as json_file: - json.dump(input_data, json_file) - self.add_extra(extra=path) - return path - - def __to_pickle(self, *, output_filename: str, input_data): - """ - Transform content to pickle. - - Args: - output_filename: The name of the output filename to save. - input_data: The content to save. - """ - path = os.path.join(self.save_path, f"{output_filename}.pkl") - with open(path, "wb") as f: - cloudpickle.dump(input_data, f) - self.add_extra(extra=path) - return path - - def __set_params(self): - """ - Set parameters for training. - """ - missing = self.X_train.isna().sum() - missing_dict = { - k + "_missings": v - for k, v in missing[missing > 0].describe().to_dict().items() - if k != "count" - } - - params = { - "shape": self.X_train.shape, - "cols_with_missing": len(missing[missing > 0]), - "missing_distribution": missing_dict, - } - - try: - params["pipeline_steps"] = list(self.model.named_steps.keys()) - except Exception: - params["pipeline_steps"] = [ - str(self.model.__class__).replace("", "") - ] - - if "get_all_params" in dir(self.model): - hyperparameters = { - f"hyperparam_{k}": str(v) - for k, v in self.model.get_all_params().items() - if k != "task_type" - } - elif "get_params" in dir(self.model): - hyperparameters = { - "hyperparam_" + k: str(v) - for k, v in self.model.get_params().items() - if k not in params["pipeline_steps"] + ["steps", "memory", "verbose"] - } - - params = {**params, **hyperparameters} - - if len(self.y_train.value_counts()) < 10: - target_proportion = self.y_train.value_counts() / len(self.y_train) - target_proportion = target_proportion.to_dict() - target_proportion = [ - {"target": k, "proportion": v} for k, v in target_proportion.items() - ] - params["target_proportion"] = target_proportion - else: - params["target_distribution"] = { - k: v - for k, v in self.y_train.describe().to_dict().items() - if k != "count" - } - - self.params = {**params, **self.params} - - @staticmethod - def __parse_data_objects(obj: Any) -> pd.DataFrame: - """ - Transform data types to dataframe - """ - if isinstance(obj, pd.Series): - return obj.to_frame() - elif isinstance(obj, (np.ndarray, list, tuple, dict)): - array_df = pd.DataFrame(obj) - array_df.columns = [str(c) for c in array_df.columns] - return array_df - elif isinstance(obj, pd.DataFrame): - return obj - else: - raise TypeError(f"{obj} couldn't be a DataFrame") - - -class MLOpsTrainingExperiment(BaseMLOps): - """ - Class to manage models being trained inside MLOps - - Parameters - ---------- - login: str - Login for authenticating with the client. You can also use the env variable MLOPS_USER to set this - password: str - Password for authenticating with the client. You can also use the env variable MLOPS_PASSWORD to set this - training_hash: str - Training id (hash) from the experiment you want to access - group: str - Group the training is inserted. - environment: str - Flag that choose which environment of MLOps you are using. Test your deployment first before changing to production. Default is True - executions: List[int] - Ids for the executions in that training - - - Raises - ------ - TrainingError - When the training can't be accessed in the server - AuthenticationError - Invalid credentials - - Example - ------- - - .. code-block:: python - - from mlops_codex.training import MLOpsTrainingClient - from mlops_codex.base import MLOpsExecution - - client = MLOpsTrainingClient('123456') - client.create_group('ex_group', 'Group for example purpose') - training = client.create_training_experiment('Training example', 'Classification', 'ex_group') - print(client.get_training(training.training_id, 'ex_group').training_data) - - data_path = './samples/train/' - - run = run = training.run_training('First test', data_path+'dados.csv', training_reference='train_model', training_type='Custom', python_version='3.9', requirements_file=data_path+'requirements.txt', wait_complete=True) - - print(run.get_training_execution(run.exec_id)) - print(run.download_result()) - """ - - def __init__( - self, - *, - training_hash: str, - group: str, - login: Optional[str] = None, - password: Optional[str] = None, - url: Optional[str] = "https://neomaril.datarisk.net/", - ) -> None: - super().__init__(login=login, password=password, url=url) - - self.training_hash = training_hash - self.group = group - - url = f"{self.base_url}/training/describe/{self.group}/{self.training_hash}" - token = refresh_token(*self.credentials, self.base_url) - - response = make_request( - url=url, - method="GET", - success_code=200, - custom_exception=TrainingError, - custom_exception_message=f'Experiment "{training_hash}" not found.', - specific_error_code=404, - logger_msg=f'Experiment "{training_hash}" not found.', - headers={ - "Authorization": f"Bearer {token}", - }, - ) - - training_data = response.json()["Description"] - self.model_type = training_data["ModelType"] - self.experiment_name = training_data["ExperimentName"] - - def __repr__(self) -> str: - return f"""MLOpsTrainingExperiment(name="{self.experiment_name}", - group="{self.group}", - training_id="{self.training_hash}", - model_type={str(self.model_type)} - )""" - - def __str__(self): - return f'MLOPS training experiment "{self.experiment_name} (Group: {self.group}, Id: {self.training_hash})"' - - def __describe(self): - """ - Describe the training experiment. - - Returns - ------- - dict - Description of the training experiment. - - Raises - ------ - TrainingError - When the training can't be accessed in the server - AuthenticationError - Invalid credentials - """ - url = f"{self.base_url}/v2/training/{self.training_hash}" - token = refresh_token(*self.credentials, self.base_url) - response = make_request( - url=url, - method="GET", - success_code=200, - custom_exception=TrainingError, - headers={ - "Authorization": f"Bearer {token}", - "Neomaril-Origin": "Codex", - "Neomaril-Method": self.__describe.__qualname__, - }, - ) - - return response.json() - - def project_info(self, mode="dict"): - """ - Get the executions. - - Parameters - ---------- - mode: str, optional - The mode of the return value. Can be "dict" or "count". Default is "dict". - - Returns - ------- - Union[dict, int] - The executions in the specified mode. - """ - describe = self.__describe() - if mode == "dict": - return describe - elif mode == "count": - return describe["ExperimentsQuantity"] - elif mode == "log": - yaml_data = parse_json_to_yaml(describe) - print(yaml_data) - else: - raise InputError(f"Invalid mode {mode}") - - def run_training( - self, - *, - run_name: str, - training_type: str, - description: Optional[str] = None, - requirements_file: Optional[str] = None, - source_file: Optional[str] = None, - python_version: Optional[str] = "3.10", - training_reference: Optional[str] = None, - train_data: Optional[str] = None, - dataset: Union[str, MLOpsDataset] = None, - dataset_name: Optional[str] = "input", - conf_dict: Union[dict, str] = None, - features_file: Optional[str] = None, - features_hash: Optional[str] = None, - target_file: Optional[str] = None, - target_hash: Optional[str] = None, - output_file: Optional[str] = None, - output_hash: Optional[str] = None, - metrics_file: Optional[str] = None, - parameters_file: Optional[str] = None, - model_file: Optional[str] = None, - extra_files: Optional[list] = None, - env: Optional[str] = None, - wait_complete: Optional[bool] = False, - ) -> ITrainingExecution: - """ - Runs a prediction from the current model. - - Parameters - --------- - run_name: str - The execution name in case you'd like to search it later. Obrigatory for all training types - training_type: str - Either 'Custom', 'External' or 'AutoML' - description: Optional[str], default=None - A small description to help you remember the training - source_file: Optional[str], default=None - Path to the python source file. Obrigatory for 'Custom' training - python_version: Optional[str], default='3.10' - Version of the python executable to run. Obrigatory for 'Custom' training, optional for 'External' training - The available options are: '3.8, '3.9' and '3.10' - training_reference: Optional[str], default=None - Entrypoint function name in the source file. Obrigatory for 'Custom' training - requirements_file: Optional[str], default=None - The requirements.txt file. Obrigatory for 'Custom' training if you wish to install dependencies - train_data: Optional[str], default=None - Data used to train the model. Obrigatory for 'Custom' and 'AutoML' training types - dataset_name: Optional[str], default="input" - Provided name the input file. Obrigatory for 'Custom' and 'AutoML' training types - dataset: Union[str, MLOpsDataset], default=None - Dataset generated or uploaded from other MLOps modules. It is a string, it must be the dataset hash. You can - also provide the entire dataset class - conf_dict: Optional[dict, str], default=None - Configuration file or dict. It's obrigatory for 'AutoML' training. You'll provide necessary configuration - for dealing with your data and how to train the model - features_file: Optional[str], default=None - Path to the features file. Obrigatory for 'External' training - features_hash: Optional[str], default=None - Dataset hash that will be used as features. Obrigatory for 'External' training - target_file: Optional[str], default=None - Path to the target file. Obrigatory for 'External' training - target_hash: Optional[str], default=None - Dataset hash that will be used as target. Obrigatory for 'External' training - output_file: Optional[str], default=None - Path to the output file. Obrigatory for 'External' training - output_hash: Optional[str], default=None - Dataset hash that will be used as output. Obrigatory for 'External' training - metrics_file: Optional[str], default=None - Path to the metrics file. Obrigatory for 'External' training - parameters_file: Optional[str], default=None - Path to the parameter file. Obrigatory for 'External' training - model_file: Optional[str], default=None - Path to the model file. Obrigatory for 'External' training - extra_files: Optional[list], default=None - An optional list with a path of files used to train your model - env: Optional[str], default=None - An optional path to the provided environment variables - wait_complete: Optional[bool], default=False - Lock your script/cell until training is complete. - Raises - ------ - AuthenticationError - InputError - - Returns - ------- - Example - ------- - >>> execution = run = training.run_training(run_name=,training_type=,requirements_file=data_path+'requirements.txt',python_version='3.9',training_reference='train_model',wait_complete=True) - """ - - if training_type not in ("Custom", "External", "AutoML"): - raise InputError( - "Training type needs be: 'Custom', 'AutoML' or 'External'." - ) - - if dataset is None and train_data is None and training_type != "External": - raise InputError( - "You must provide a data to train your model. It can be a path to a file or a dataset hash." - ) - - if description is None: - description = f"Training is {training_type}" - - if extra_files is None: - extra_files = [] - - if dataset is not None: - dataset_hash = validate_dataset(dataset) - else: - dataset_hash = None - - input_data, upload_data = None, None - - if training_type != "External": - input_data, upload_data = parse_data( - file_path=train_data, - form_data="dataset_hash" if dataset_hash is not None else "dataset_name", - file_name=dataset_name, - file_form="input", - dataset_hash=dataset_hash, - ) - - builder = { - "Custom": ( - CustomTrainingExecution, - { - "training_hash": self.training_hash, - "model_type": training_type, - "group": self.group, - "login": self.credentials[0], - "password": self.credentials[1], - "url": self.base_url, - "run_name": run_name, - "description": description, - "input_data": input_data, - "upload_data": upload_data, - "requirements_file": requirements_file, - "source_file": source_file, - "python_version": python_version, - "training_reference": training_reference, - "extra_files": extra_files, - "env": env, - "wait_complete": wait_complete, - }, - ), - "AutoML": ( - AutoMLTrainingExecution, - { - "training_hash": self.training_hash, - "model_type": training_type, - "group": self.group, - "login": self.credentials[0], - "password": self.credentials[1], - "url": self.base_url, - "run_name": run_name, - "description": description, - "input_data": input_data, - "upload_data": upload_data, - "conf_dict": conf_dict, - "extra_files": extra_files, - "wait_complete": wait_complete, - }, - ), - "External": ( - ExternalTrainingExecution, - { - "training_hash": self.training_hash, - "model_type": training_type, - "group": self.group, - "login": self.credentials[0], - "password": self.credentials[1], - "url": self.base_url, - "run_name": run_name, - "python_version": python_version, - "description": description, - "features_file": features_file, - "features_hash": features_hash, - "target_file": target_file, - "target_hash": target_hash, - "output_file": output_file, - "output_hash": output_hash, - "metrics_file": metrics_file, - "parameters_file": parameters_file, - "model_file": model_file, - "requirements_file": requirements_file, - "wait_complete": wait_complete, - }, - ), - } - - builder_train_class, params = builder[training_type] - train = builder_train_class(**params) - return train - - def get_training_execution(self, exec_id: Optional[str] = None): - """ - Get the execution instance. - - Parameters - --------- - exec_id: Optional[str], optional - Execution id. If not informed we get the last execution. - - Returns - ------- - MLOpsExecution - The chosen execution - """ - raise NotImplementedError("Get training execution not implemented.") - - @contextmanager - def log_train( - self, - *, - name, - X_train, - y_train, - description: Optional[str] = None, - save_path: Optional[str] = None, - ): - """ - Creates a context manager that logs training progress. - - name: str - Run name - X_train: DataFrame - Features - y_train: DataFrame - Target - description: str, default=None - Description - save_path: str, default=None - Path to save the trained model - - Returns - ------- - MLOpsTrainingLogger - """ - try: - self.trainer = MLOpsTrainingLogger( - name=name, - X_train=X_train, - y_train=y_train, - save_path=save_path, - ) - yield self.trainer - - finally: - self.trainer._processing_logging_inputs() - self.run_training( - run_name=self.trainer.name, - training_type="External", - features_file=self.trainer.features_file, - target_file=self.trainer.target_file, - output_file=self.trainer.output_file, - metrics_file=self.trainer.metrics_file, - parameters_file=self.trainer.params_file, - model_file=self.trainer.model_file, - requirements_file=self.trainer.requirements, - description=description, - python_version=self.trainer.python_version, - extra_files=self.trainer.extras, - ) - logger.info( - "Use the `get_training_execution()` method to get a training execution." - ) - - -class MLOpsTrainingClient(BaseMLOpsClient): - """ - Class for client for accessing MLOps and manage models - - Parameters - ---------- - login: str - Login for authenticating with the client. You can also use the env variable MLOPS_USER to set this - password: str - Password for authenticating with the client. You can also use the env variable MLOPS_PASSWORD to set this - url: str - URL to MLOps Server. The default value is https://neomaril.datarisk.net, use it to test your deployment first before changing to production. You can also use the env variable MLOPS_URL to set this - - Raises - ------ - AuthenticationError - Invalid credentials - ServerError - Server unavailable - """ - - def __repr__(self) -> str: - return f"Codex version {constants.CODEX_VERSION}" - - def __str__(self): - return f"Codex version {constants.CODEX_VERSION}" - - def list(self, mode="dict"): - url = f"{self.base_url}/v2/training" - token = refresh_token(*self.credentials, self.base_url) - response = make_request( - url=url, - method="GET", - success_code=200, - headers={ - "Authorization": f"Bearer {token}", - "Neomaril-Origin": "Codex", - "Neomaril-Method": self.list.__qualname__, - }, - ) - - if mode == "dict": - return response.json() - if mode == "count": - return len(response.json()) - if mode == "log": - yaml_data = parse_json_to_yaml(response.json()) - print(yaml_data) - return - raise InputError(f'{mode} is invalid. The options are "count", "dict" or "log"') - - def get_training( - self, *, training_hash: str, group: str - ) -> MLOpsTrainingExperiment: - """ - Acess a model using its id - - Parameters - --------- - training_hash: str - Training id (hash) that needs to be acessed - group: str - Group the model is inserted. - - Raises - ------ - TrainingError - Model unavailable - ServerError - Unknown return from server - - Returns - ------- - MLOpsTrainingExperiment - A MLOpsTrainingExperiment instance with the training hash from `training_id` - - Example - ------- - >>> client = MLOpsTrainingClient() - >>> training = client.get_training('', '') - """ - - return MLOpsTrainingExperiment( - training_hash=training_hash, - login=self.credentials[0], - password=self.credentials[1], - group=group, - url=self.base_url, - ) - - def __get_repeated_thash( - self, model_type: str, experiment_name: str, group: str - ) -> Union[str, None]: - """Look for a previous train experiment. - - Args: - experiment_name (str): name given to the training, should be not null, case-sensitive, have between 3 and 32 characters, - that could be alphanumeric including accentuation (for example: 'é', à', 'ç','ñ') and space, - without blank spaces and special characters - - model_type (str): type of the model being trained. It can be - Classification: for ML algorithms related to classification (predicts discrete class labels) problems; - Regression: the ones that will use regression (predict a continuous quantity) algorithms; - Unsupervised: for training that will use ML algorithms without supervision. - - group (str): name of the group, previously created, where the training will be inserted - - Raises: - InputError: some part of the data is incorrect - AuthenticationError: user has insufficient permissions - ServerError: server is not available - Exception: generated exception in case of the response to the request is different from 201 - - Returns: - str | None: THash if it is found, otherwise, None is returned - """ - url = f"{self.base_url}/training/search" - token = refresh_token(*self.credentials, self.base_url) - - response = make_request( - url=url, - method="GET", - success_code=200, - headers={ - "Authorization": f"Bearer {token}", - }, - ) - - results = response.json().get("Results") - for result in results: - condition = ( - result["ExperimentName"] == experiment_name - and result["GroupName"] == group - and result["ModelType"] == model_type - ) - if condition: - logger.info("Found experiment with same attributes...") - return result["TrainingHash"] - - def __register_training( - self, experiment_name: str, model_type: str, group: str - ) -> str: - """Creates a train experiment. A train experiment can aggregate multiple training runs (also called executions). - Each execution can eventually become a deployed model or not. - - Args: - experiment_name (str): name given to the training, should be not null, case-sensitive, have between 3 and 32 characters, - that could be alphanumeric including accentuation (for example: 'é', à', 'ç','ñ') and space, - without blank spaces and special characters - - model_type (str): type of the model being trained. It can be - Classification: for ML algorithms related to classification (predicts discrete class labels) problems; - Regression: the ones that will use regression (predict a continuous quantity) algorithms; - Unsupervised: for training that will use ML algorithms without supervision. - - group (str): name of the group, previous created, where the training will be inserted - - Raises: - InputError: some part of the data is incorrect - AuthenticationError: user has insufficient permissions - ServerError: server is not available - Exception: generated exception in case of the response to the request is different from 201 - - Returns: - str: training hash of the experiment - """ - url = f"{self.base_url}/v2/training/{group}" - token = refresh_token(*self.credentials, self.base_url) - - payload = {"Name": experiment_name, "Type": model_type} - - response = make_request( - url=url, - method="POST", - success_code=201, - json=payload, - headers={ - "Authorization": f"Bearer {token}", - "Neomaril-Origin": "Codex", - "Neomaril-Method": self.__register_training.__qualname__, - }, - ) - - response_data = response.json() - logger.info(response_data["Message"]) - training_hash = response_data["TrainingHash"] - return training_hash - - def create_training_experiment( - self, - *, - experiment_name: str, - model_type: str, - group: str, - force: Optional[bool] = False, - ) -> MLOpsTrainingExperiment: - """ - Create a new training experiment on MLOps. - - Parameters - --------- - experiment_name: str - The name of the experiment, in less than 32 characters - model_type: str - The name of the scoring function inside the source file. - group: str - Group the model is inserted. Default to 'datarisk' (public group) - force: Optional[bool], optional - Forces to create a new training with the same model_type, experiment_name, group - - Raises - ------ - InputError - Some input parameters its invalid - ServerError - Unknow internal server error - - Returns - ------- - MLOpsTrainingExperiment - A MLOpsTrainingExperiment instance with the training hash from `training_id` - - Example - ------- - >>> training = client.create_training_experiment('Training example', 'Classification', 'ex_group') - """ - - validate_group_existence(group, self) - - if model_type not in ["Classification", "Regression", "Unsupervised"]: - raise InputError( - f"Invalid model_type {model_type}. Should be one of the following: Classification, Regression or " - f"Unsupervised" - ) - - logger.info("Trying to load experiment...") - training_hash = self.__get_repeated_thash( - model_type=model_type, experiment_name=experiment_name, group=group - ) - - if force or training_hash is None: - msg = ( - "The experiment you're creating has identical name, group, and model type attributes to an existing one. " - + "Since forced creation is active, we will continue with the process as specified" - if force - else "Could not find experiment. Creating a new one..." - ) - logger.info(msg) - training_hash = self.__register_training( - experiment_name=experiment_name, model_type=model_type, group=group - ) - - return MLOpsTrainingExperiment( - training_hash=training_hash, - login=self.credentials[0], - password=self.credentials[1], - group=group, - url=self.base_url, - ) diff --git a/src/mlops_codex/trainingv2/training_executions.py b/src/mlops_codex/trainingv2/training_executions.py deleted file mode 100644 index 50d61e0..0000000 --- a/src/mlops_codex/trainingv2/training_executions.py +++ /dev/null @@ -1,783 +0,0 @@ -from typing import Union - -from pydantic import model_validator - -from mlops_codex.__utils import parse_dict_or_file -from mlops_codex.dataset import MLOpsDataset, validate_dataset -from mlops_codex.exceptions import InputError -from mlops_codex.http_request_handler import make_request, refresh_token -from mlops_codex.logger_config import get_logger -from mlops_codex.shared.utils import parse_data -from mlops_codex.trainingv2.base import ITrainingExecution -from mlops_codex.trainingv2.trigger import ( - trigger_automl_training, - trigger_custom_training, - trigger_external_training, -) -from mlops_codex.trainingv2.validations import validate_input -from mlops_codex.validations import file_extension_validation - -logger = get_logger() - - -class CustomTrainingExecution(ITrainingExecution): - """ - Custom training execution class. - - Parameters - ---------- - training_hash: str - Training hash. - group: str - Group where the training is inserted. - model_type: str - Type of the model. - execution_id: int - Execution ID of a training. - experiment_name: str - Name of the experiment. - login: str - Login credential. - password: str - Password credential. - url: str - URL used to connect to the MLOps server. - mlops_class: BaseMLOps - MLOps class instance. - """ - - @model_validator(mode="before") - @classmethod - def validate(cls, values): - """ - Validates the input values for custom training execution. - - Parameters - ---------- - values: dict - Dictionary of input values. - - Returns - ------- - dict - Validated input values. - """ - - logger.info("Validating data...") - - fields_required = ( - "input_data", - "upload_data", - "run_name", - "source_file", - "requirements_file", - "training_reference", - "python_version", - ) - - validate_input(fields_required, values) - - source_file = values["source_file"] - file_extension_validation(source_file, {"py", "ipynb"}) - - requirements_file = values["requirements_file"] - file_extension_validation(requirements_file, {"txt"}) - - keys = ( - "training_hash", - "group", - "model_type", - "login", - "password", - "url", - ) - - data = {key: values[key] for key in keys} - - return data - - def _promote(self, source_file_path: str, schema_path: str, operation: str, model_name: str, input_type: str, model_reference: str): - """ - Abstract method to promote the execution. - - Parameters - ---------- - source_file_path: str - A python script with an entry point function. It needs to return a dict, a list of dicts or a JSON string - schema_path: str - A JSON, CSV or PARQUET file with a sample of the input for the entry point function - operation: str - Defines how the model will be treated at the API. It can be: Sync or Async - input_type: str - The type of the input that the model expects, for example, .csv - model_name: str - Corresponds to the name of the model - model_reference: str - The name of the entry point function at the source file - Returns - ------- - str - Model hash - """ - user_token = refresh_token( - *self.mlops_class.credentials, self.mlops_class.base_url - ) - response = make_request( - url=f"{self.url}/v2/training/execution/{self.execution_id}/promote", - method="PATCH", - success_code=201, - headers={ - "Authorization": f"Bearer {user_token}" - }, - files={ - "source": open(source_file_path, "rb"), - "schema": open(schema_path, "rb"), - }, - data={ - "operation": operation, - "input_type": input_type, - "name": model_name, - "model_reference": model_reference, - } - ) - - msg = response.json()["Message"] - logger.info(msg) - - model_hash = response.json()["ModelHash"] - logger.info(f"Model hash: {model_hash}") - return model_hash - - def __init__(self, **data): - super().__init__(**data) - - if data.get("is_copy", False): - return - - user_token = refresh_token( - *self.mlops_class.credentials, self.mlops_class.base_url - ) - - self.execution_id = trigger_custom_training( - url=self.mlops_class.base_url, - token=user_token, - training_hash=self.training_hash, - run_name=data["run_name"], - description=data["description"], - input_data=data["input_data"], - upload_data=data["upload_data"], - requirements_file=data["requirements_file"], - source_file=data["source_file"], - training_reference=data["training_reference"], - python_version=data["python_version"], - extra_files=data["extra_files"], - env=data["env"], - ) - - self.host() - - if data["wait_complete"]: - self.wait_ready() - - @classmethod - def _do_copy(cls, url, token, group, experiment_name, mlops_class, **kwargs): - """ - Abstract method to copy the execution. - - Parameters - ---------- - url: str - URL to copy the execution. - token: str - Authentication token. - group: str - Group where the training is inserted. - experiment_name: str - Name of the experiment. - mlops_class: BaseMLOps - MLOps class instance. - kwargs: dict - Extra arguments passed to the specific function. - """ - response = make_request( - url=url, - method="POST", - success_code=201, - headers={"Authorization": f"Bearer {token}"}, - ).json() - - logger.info(response["Message"]) - - fields = dict( - training_hash=response["TrainingHash"], - group=group, - model_type="Custom", - execution_id=response["ExecutionId"], - experiment_name=experiment_name, - login=mlops_class.credentials[0], - password=mlops_class.credentials[1], - url=mlops_class.base_url, - mlops_class=mlops_class, - is_copy=True, - ) - - new_execution = cls.model_construct(**fields) - - new_execution._update_execution( - token=token, - source_file=kwargs.get("source_file"), - training_reference=kwargs.get("training_reference"), - python_version=kwargs.get("python_version"), - train_data=kwargs.get("train_data"), - dataset_name=kwargs.get("dataset_name", "input"), - dataset=kwargs.get("dataset"), - requirements_file=kwargs.get("requirements_file"), - extra_files=kwargs.get("extra_files", []), - env=kwargs.get("env"), - wait_complete=kwargs.get("wait_complete"), - ) - - return new_execution - - def _update_execution( - self, - token: str, - source_file: str = None, - training_reference: str = None, - python_version: str = None, - train_data: str = None, - dataset_name: str = "input", - dataset: Union[str, MLOpsDataset] = None, - requirements_file: str = None, - extra_files: str = None, - env: str = None, - wait_complete: bool = True, - ): - """ - Updates the execution with new parameters. - - Parameters - ---------- - source_file : str, optional - Path to the source code file, by default None - training_reference : str, optional - Training reference identifier, by default None - python_version : str, optional - Python version to use, by default None - requirements_file : str, optional - Path to requirements.txt file, by default None - extra_files : str, optional - List of additional files to include, by default None - env : str, optional - Environment variables, by default None - wait_complete : bool, optional - Whether to wait for execution completion, by default None - - Raises - ------ - TrainingError - If the execution is not in requested state. - """ - - if dataset is not None: - dataset_hash = validate_dataset(dataset) - else: - dataset_hash = None - - input_data, upload_data = parse_data( - file_path=train_data, - form_data="dataset_hash" if dataset_hash is not None else "dataset_name", - file_name=dataset_name, - file_form="input", - dataset_hash=dataset_hash, - ) - - self.execution_id = trigger_custom_training( - url=self.mlops_class.base_url, - token=token, - execution_id=self.execution_id, - input_data=input_data, - upload_data=upload_data, - requirements_file=requirements_file, - source_file=source_file, - training_reference=training_reference, - python_version=python_version, - extra_files=extra_files, - env=env, - ) - - self.host() - - if wait_complete: - self.wait_ready() - - -class AutoMLTrainingExecution(ITrainingExecution): - """ - AutoML training execution class. - - Parameters - ---------- - training_hash: str - Training hash. - group: str - Group where the training is inserted. - model_type: str - Type of the model. - execution_id: int - Execution ID of a training. - experiment_name: str - Name of the experiment. - login: str - Login credential. - password: str - Password credential. - url: str - URL used to connect to the MLOps server. - mlops_class: BaseMLOps - MLOps class instance. - """ - - @model_validator(mode="before") - @classmethod - def validate(cls, values): - """ - Validates the input values for AutoML training execution. - - Parameters - ---------- - values: dict - Dictionary of input values. - - Returns - ------- - dict - Validated input values. - """ - - validate_input({"input_data", "upload_data", "conf_dict", "run_name"}, values) - - file_extension_validation(values["conf_dict"], {"json"}) - - keys = ( - "training_hash", - "group", - "model_type", - "login", - "password", - "url", - ) - - data = {key: values[key] for key in keys} - - return data - - def _promote(self, model_name: str, input_type: str, operation: str, schema_path: str): - """ - Abstract method to promote the execution. - - Parameters - ---------- - schema_path: str - A JSON, CSV or PARQUET file with a sample of the input for the entry point function - operation: str - Defines how the model will be treated at the API. It can be: Sync or Async - model_name: str - Corresponds to the name of the model - input_type: str - Type of the input that the model expects. Must be CSV or Parquet - Returns - ------- - str - Model hash - """ - - raise NotImplementedError() - - def __init__(self, **data): - super().__init__(**data) - - if data.get("is_copy", False): - return - - user_token = refresh_token( - *self.mlops_class.credentials, self.mlops_class.base_url - ) - - self.execution_id = trigger_automl_training( - url=self.mlops_class.base_url, - token=user_token, - training_hash=self.training_hash, - run_name=data["run_name"], - description=data["description"], - input_data=data["input_data"], - upload_data=data["upload_data"], - conf_dict=data["conf_dict"], - extra_files=data["extra_files"], - ) - - self.host() - - if data["wait_complete"]: - self.wait_ready() - - @classmethod - def _do_copy(cls, url, token, group, experiment_name, mlops_class, **kwargs): - """ - Abstract method to copy the execution. - - Parameters - ---------- - url: str - URL to copy the execution. - token: str - Authentication token. - group: str - Group where the training is inserted. - experiment_name: str - Name of the experiment. - mlops_class: BaseMLOps - MLOps class instance. - kwargs: dict - Extra arguments passed to the specific function. - """ - response = make_request( - url=url, - method="POST", - success_code=200, - headers={"Authorization": f"Bearer {token}"}, - ).json() - - fields = dict( - training_hash=response["TrainingHash"], - group=group, - model_type="AutoML", - execution_id=response["ExecutionId"], - experiment_name=experiment_name, - login=mlops_class.credentials[0], - password=mlops_class.credentials[1], - url=mlops_class.base_url, - mlops_class=mlops_class, - is_copy=True, - ) - - new_execution = cls.model_construct(**fields) - - new_execution._update_execution( - conf_dict=kwargs.get("conf_dict"), - extra_files=kwargs.get("extra_files", []), - train_data=kwargs.get("train_data"), - dataset_name=kwargs.get("dataset_name", "input"), - dataset=kwargs.get("dataset"), - wait_complete=kwargs.get("wait_complete"), - ) - - def _update_execution( - self, - conf_dict: str = None, - train_data: str = None, - dataset_name: str = "input", - dataset: Union[str, MLOpsDataset] = None, - extra_files: str = None, - wait_complete: bool = True, - ): - if dataset is not None: - dataset_hash = validate_dataset(dataset) - else: - dataset_hash = None - - input_data, upload_data = parse_data( - file_path=train_data, - form_data="dataset_hash" if dataset_hash is not None else "dataset_name", - file_name=dataset_name, - file_form="input", - dataset_hash=dataset_hash, - ) - - self.execution_id = trigger_automl_training( - execution_id=self.execution_id, - url=self.mlops_class.base_url, - token=refresh_token( - *self.mlops_class.credentials, self.mlops_class.base_url - ), - input_data=input_data, - upload_data=upload_data, - conf_dict=parse_dict_or_file(conf_dict), - extra_files=extra_files, - ) - - self.host() - - if wait_complete: - self.wait_ready() - - -class ExternalTrainingExecution(ITrainingExecution): - """ - External training execution class. - - Parameters - ---------- - training_hash: str - Training hash. - group: str - Group where the training is inserted. - model_type: str - Type of the model. - execution_id: int - Execution ID of a training. - experiment_name: str - Name of the experiment. - login: str - Login credential. - password: str - Password credential. - url: str - URL used to connect to the MLOps server. - mlops_class: BaseMLOps - MLOps class instance. - """ - - @model_validator(mode="before") - @classmethod - def validate(cls, values): - """ - Validates the input values for External training execution. - - Parameters - ---------- - values: dict - Dictionary of input values. - - Returns - ------- - dict - Validated input values. - """ - logger.info("Validating external training execution...") - - copy_dict = { - "run_name": values["run_name"], - "features": ( - values.get("features_file") - if values.get("features_file") - else values.get("features_hash") - ), - "target": ( - values.get("target_file") - if values.get("target_file") - else values.get("target_hash") - ), - "output": ( - values.get("output_file") - if values.get("output_file") - else values.get("output_hash") - ), - } - - validate_input({"run_name", "features", "target", "output"}, copy_dict) - - if values["features_file"] and values["features_hash"]: - raise InputError("You must provide either features file or dataset hash.") - - if values["output_file"] and values["output_hash"]: - raise InputError("You must provide either output file or dataset hash.") - - if values["target_file"] and values["target_hash"]: - raise InputError("You must provide either target file or dataset hash.") - - if values["requirements_file"]: - file_extension_validation(values["requirements_file"], {"txt"}) - - keys = ( - "training_hash", - "group", - "model_type", - "login", - "password", - "url", - ) - - data = {key: values[key] for key in keys} - - return data - - def __init__(self, **data): - super().__init__(**data) - - if data.get("is_copy", False): - return - - user_token = refresh_token( - *self.mlops_class.credentials, self.mlops_class.base_url - ) - - self.execution_id = trigger_external_training( - url=self.mlops_class.base_url, - token=user_token, - training_hash=self.training_hash, - run_name=data["run_name"], - description=data["description"], - features_file=data["features_file"], - features_hash=data["features_hash"], - target_file=data["target_file"], - target_hash=data["target_hash"], - output_file=data["output_file"], - output_hash=data["output_hash"], - metrics_file=data["metrics_file"], - parameters_file=data["parameters_file"], - model_file=data["model_file"], - requirements_file=data["requirements_file"], - python_version=data["python_version"], - ) - - self.host() - - if data["wait_complete"]: - self.wait_ready() - - def _promote(self, *args, **kwargs): - """ - Abstract method to promote the execution. - - Parameters - ---------- - args: tuple - Positional arguments. - kwargs: dict - Keyword arguments. - - Returns - ------- - None - """ - - raise NotImplementedError("Promotion is not implemented.") - - @classmethod - def _do_copy(cls, url, token, group, experiment_name, mlops_class, **kwargs): - """ - Abstract method to copy the execution. - - Parameters - ---------- - url: str - URL to copy the execution. - token: str - Authentication token. - group: str - Group where the training is inserted. - experiment_name: str - Name of the experiment. - mlops_class: BaseMLOps - MLOps class instance. - kwargs: dict - Extra arguments passed to the specific function. - """ - response = make_request( - url=url, - method="POST", - success_code=200, - headers={"Authorization": f"Bearer {token}"}, - ).json() - - fields = dict( - training_hash=response["TrainingHash"], - group=group, - model_type="External", - execution_id=response["ExecutionId"], - experiment_name=experiment_name, - login=mlops_class.credentials[0], - password=mlops_class.credentials[1], - url=mlops_class.base_url, - mlops_class=mlops_class, - is_copy=True, - ) - - new_training = cls.model_construct(**fields) - new_training._update_execution( - features_file=kwargs.get("features_file"), - features_hash=kwargs.get("features_hash"), - target_file=kwargs.get("target_file"), - target_hash=kwargs.get("target_hash"), - output_file=kwargs.get("output_file"), - output_hash=kwargs.get("output_hash"), - metrics_file=kwargs.get("metrics_file"), - parameters_file=kwargs.get("parameters_file"), - model_file=kwargs.get("model_file"), - requirements_file=kwargs.get("requirements_file"), - python_version=kwargs.get("python_version", "3.10"), - wait_complete=kwargs.get("wait_complete"), - ) - - return new_training - - def _update_execution( - self, - features_file: str = None, - features_hash: str = None, - target_file: str = None, - target_hash: str = None, - output_file: str = None, - output_hash: str = None, - metrics_file: str = None, - parameters_file: str = None, - model_file: str = None, - requirements_file: str = None, - python_version: str = None, - wait_complete: bool = True, - ): - """ - Updates the execution with new parameters. - - Parameters - ---------- - features_file : str, optional - Path to features file, by default None - features_hash : str, optional - Features dataset hash, by default None - target_file : str, optional - Path to target file, by default None - target_hash : str, optional - Target dataset hash, by default None - output_file : str, optional - Path to output file, by default None - output_hash : str, optional - Output dataset hash, by default None - metrics_file : str, optional - Path to metrics file, by default None - parameters_file : str, optional - Path to parameters file, by default None - model_file : str, optional - Path to model file, by default None - requirements_file : str, optional - Path to requirements file, by default None - python_version : str, optional - Python version to use, by default None - wait_complete : bool, optional - Whether to wait for execution completion, by default True - """ - - self.execution_id = trigger_external_training( - execution_id=self.execution_id, - url=self.mlops_class.base_url, - token=refresh_token( - *self.mlops_class.credentials, self.mlops_class.base_url - ), - features_file=features_file, - features_hash=features_hash, - target_file=target_file, - target_hash=target_hash, - output_file=output_file, - output_hash=output_hash, - metrics_file=metrics_file, - parameters_file=parameters_file, - model_file=model_file, - requirements_file=requirements_file, - python_version=python_version, - ) - - self.host() - - if wait_complete: - self.wait_ready() diff --git a/src/mlops_codex/trainingv2/trigger.py b/src/mlops_codex/trainingv2/trigger.py deleted file mode 100644 index d180e99..0000000 --- a/src/mlops_codex/trainingv2/trigger.py +++ /dev/null @@ -1,303 +0,0 @@ -from mlops_codex.logger_config import get_logger -from mlops_codex.shared.data_transmitter import send_file, send_json -from mlops_codex.shared.utils import parse_data -from mlops_codex.trainingv2.commons import register_execution -from mlops_codex.validations import validate_python_version - -logger = get_logger() - - -def trigger_custom_training(**kwargs: dict) -> int: - """ - Triggers a custom training execution. - - Parameters - ---------- - **kwargs : dict - Dictionary containing: - - url : str - Base URL for the API - - token : str - Authentication token - - training_hash : str - Training hash identifier - - run_name : str - Name of the run - - description : str - Description of the training - - input_data : dict - Input data dictionary - - upload_data : dict - Upload data dictionary - - requirements_file : str - Path to requirements file - - source_file : str - Path to source file - - training_reference : str - Training reference - - python_version : str - Python version to use - - extra_files : list, optional - List of tuples containing (name, path) for extra files - - env : str, optional - Path to env file - - Returns - ------- - int - Execution ID - """ - - execution_id = ( - kwargs.get("execution_id") - if kwargs.get("execution_id", None) is not None - else register_execution( - url=f"{kwargs['url']}/v2/training/{kwargs['training_hash']}/execution", - token=kwargs["token"], - run_name=kwargs["run_name"], - description=kwargs["description"], - training_type="Custom", - ) - ) - - if kwargs["input_data"] is not None and kwargs["upload_data"] is not None: - send_file( - url=f"{kwargs['url']}/v2/training/execution/{execution_id}/input/file", - token=kwargs["token"], - input_data=kwargs["input_data"], - upload_data=kwargs["upload_data"], - neomaril_method="Upload input", - ) - - if kwargs["requirements_file"] is not None: - send_file( - url=f"{kwargs['url']}/v2/training/execution/{execution_id}/requirements-file", - token=kwargs["token"], - upload_data={"requirements": open(kwargs["requirements_file"], "rb")}, - neomaril_method="Upload requirements file", - ) - - if all( - kwargs[key] for key in ["source_file", "training_reference", "python_version"] - ): - send_file( - url=f"{kwargs['url']}/v2/training/execution/{execution_id}/script-file", - token=kwargs["token"], - input_data={ - "training_reference": kwargs["training_reference"], - "python_version": validate_python_version(kwargs["python_version"]), - }, - upload_data={"script": open(kwargs["source_file"], "rb")}, - neomaril_method="Upload script", - ) - - for name, path in kwargs.get("extra_files", []): - send_file( - url=f"{kwargs['url']}/v2/training/execution/{execution_id}/extra-file", - token=kwargs["token"], - input_data={"file_name": name}, - upload_data={"extra": open(path, "rb")}, - neomaril_method="Upload extra file", - ) - - if kwargs["env"] is not None: - send_file( - url=f"{kwargs['url']}/v2/training/execution/{execution_id}/env/file", - token=kwargs["token"], - upload_data={"env": open(kwargs["env"], "rb")}, - neomaril_method="Upload env", - ) - - return execution_id - - -def trigger_automl_training(**kwargs) -> int: - """ - Triggers an AutoML training execution. - - Parameters - ---------- - **kwargs : dict - Dictionary containing: - - url : str - Base URL for the API - - token : str - Authentication token - - training_hash : str - Training hash identifier - - run_name : str - Name of the run - - description : str - Description of the training - - conf_dict : str - Path to configuration dictionary - - input_data : dict - Input data dictionary - - upload_data : dict - Upload data dictionary - - extra_files : list, optional - List of tuples containing (name, path) for extra files - - Returns - ------- - int - Execution ID - """ - execution_id = ( - kwargs.get("execution_id") - if kwargs.get("execution_id", None) is not None - else register_execution( - url=f"{kwargs['url']}/v2/training/{kwargs['training_hash']}/execution", - token=kwargs["token"], - run_name=kwargs["run_name"], - description=kwargs["description"], - training_type="AutoML", - ) - ) - - if kwargs["conf_dict"] is not None: - send_file( - url=f"{kwargs['url']}/v2/training/execution/{execution_id}/conf-dict/file", - token=kwargs["token"], - input_data=None, - upload_data={"conf_dict": open(kwargs["conf_dict"], "rb")}, - neomaril_method="Upload conf_dict", - ) - - if kwargs["input_data"] is not None and kwargs["upload_data"] is not None: - send_file( - url=f"{kwargs['url']}/v2/training/execution/{execution_id}/input/file", - token=kwargs["token"], - input_data=kwargs["input_data"], - upload_data=kwargs["upload_data"], - neomaril_method="Upload input", - ) - - for name, path in kwargs.get("extra_files", []): - send_file( - url=f"{kwargs['url']}/v2/training/execution/{execution_id}/extra-file", - token=kwargs["token"], - input_data={"file_name": name}, - upload_data={"extra": open(path, "rb")}, - neomaril_method="Upload extra file", - ) - - return execution_id - - -def trigger_external_training(**kwargs) -> int: - """ - Triggers an external training execution. - - Parameters - ---------- - **kwargs : dict - Dictionary containing: - - url : str - Base URL for the API - - token : str - Authentication token - - training_hash : str - Training hash identifier - - data : dict - Dictionary containing features, target and output file paths and hashes - - run_name : str - Name of the run - - description : str - Description of the training - - python_version : str - Python version to use - - metrics_file : str, optional - Path to metrics file - - parameters_file : str, optional - Path to parameters file - - model_file : str, optional - Path to model file - - requirements_file : str, optional - Path to requirements file - - Returns - ------- - int - Execution ID - """ - execution_id = ( - kwargs.get("execution_id") - if kwargs.get("execution_id", None) is not None - else register_execution( - url=f"{kwargs['url']}/v2/training/{kwargs['training_hash']}/execution", - token=kwargs["token"], - run_name=kwargs["run_name"], - description=kwargs["description"], - training_type="External", - ) - ) - - for var in ["features", "target", "output"]: - file_path = kwargs.get(f"{var}_file") - dataset_hash = kwargs.get(f"{var}_hash") - - if file_path is None and dataset_hash is None: - continue - - form_data, upload_data = parse_data( - file_path=file_path, - form_data="dataset_name" if file_path else "dataset_hash", - file_name=var if file_path else dataset_hash, - file_form=var if file_path else None, - dataset_hash=dataset_hash, - ) - - send_file( - url=f"{kwargs['url']}/v2/training/execution/{execution_id}/{var}/file", - token=kwargs["token"], - input_data=form_data, - upload_data=upload_data, - neomaril_method=f"Upload {var} file", - ) - - if kwargs["metrics_file"] is not None: - send_file( - url=f"{kwargs['url']}/v2/training/execution/{execution_id}/metrics/file", - token=kwargs["token"], - upload_data={"metrics": open(kwargs["metrics_file"], "rb")}, - neomaril_method="Upload metrics file", - ) - - if kwargs["parameters_file"] is not None: - send_file( - url=f"{kwargs['url']}/v2/training/execution/{execution_id}/parameters/file", - token=kwargs["token"], - upload_data={"parameters": open(kwargs["parameters_file"], "rb")}, - neomaril_method="Upload parameters file", - ) - - if kwargs["model_file"] is not None: - send_file( - url=f"{kwargs['url']}/v2/training/execution/{execution_id}/model/file", - token=kwargs["token"], - upload_data={"model": open(kwargs["model_file"], "rb")}, - neomaril_method="Upload model file", - ) - - if kwargs["requirements_file"] is not None: - send_file( - url=f"{kwargs['url']}/v2/training/execution/{execution_id}/requirements-file", - token=kwargs["token"], - upload_data={"requirements": open(kwargs["requirements_file"], "rb")}, - neomaril_method="Upload requirements file", - ) - - if kwargs["python_version"] is not None: - send_json( - url=f"{kwargs['url']}/v2/training/execution/{execution_id}/python-version", - token=kwargs["token"], - payload={ - "PythonVersion": validate_python_version( - python_version=kwargs["python_version"] - ) - }, - neomaril_method="Set python version", - ) - - return execution_id diff --git a/src/mlops_codex/trainingv2/validations.py b/src/mlops_codex/trainingv2/validations.py deleted file mode 100644 index ac2e277..0000000 --- a/src/mlops_codex/trainingv2/validations.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -Specific validators for training module -""" - -from mlops_codex.exceptions import InputError - - -def validate_input(required, fields): - if (not all(k in fields for k in required)) or ( - not all(fields[f] for f in required) - ): - raise InputError( - f"The parameters {required} are mandatory for this training execution type." - ) - - return True From 7eedc9fd6ae967668ad1eb6821c919dac1fb19a1 Mon Sep 17 00:00:00 2001 From: henrique-lh Date: Wed, 9 Jul 2025 17:07:25 -0300 Subject: [PATCH 3/5] - update codex version --- src/mlops_codex/shared/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mlops_codex/shared/constants.py b/src/mlops_codex/shared/constants.py index 13fd00e..ed30924 100644 --- a/src/mlops_codex/shared/constants.py +++ b/src/mlops_codex/shared/constants.py @@ -1 +1 @@ -CODEX_VERSION = "2.2.10" \ No newline at end of file +CODEX_VERSION = "2.2.11" \ No newline at end of file From 31d50d2830d4e52282975688fc555da3b0ffa8b8 Mon Sep 17 00:00:00 2001 From: henrique-lh Date: Wed, 9 Jul 2025 17:07:35 -0300 Subject: [PATCH 4/5] - add log --- src/mlops_codex/preprocessing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mlops_codex/preprocessing.py b/src/mlops_codex/preprocessing.py index 68274b5..e1f7055 100644 --- a/src/mlops_codex/preprocessing.py +++ b/src/mlops_codex/preprocessing.py @@ -558,6 +558,7 @@ def create( logger.info("Requirements file uploaded") if env_file: + logger.info("Environment file uploaded") make_request( url=f"{self.base_url}/v2/preprocessing/{preprocessing_script_hash}/env-file", method='PATCH', From 2a43b24745c0eddb7c8be291e2594f004f64225c Mon Sep 17 00:00:00 2001 From: henrique-lh Date: Wed, 9 Jul 2025 18:05:27 -0300 Subject: [PATCH 5/5] - log after request is succeeded --- src/mlops_codex/preprocessing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mlops_codex/preprocessing.py b/src/mlops_codex/preprocessing.py index e1f7055..7a6d014 100644 --- a/src/mlops_codex/preprocessing.py +++ b/src/mlops_codex/preprocessing.py @@ -558,7 +558,6 @@ def create( logger.info("Requirements file uploaded") if env_file: - logger.info("Environment file uploaded") make_request( url=f"{self.base_url}/v2/preprocessing/{preprocessing_script_hash}/env-file", method='PATCH', @@ -568,6 +567,7 @@ def create( "Authorization": f"Bearer {token}", }, ) + logger.info("Environment file uploaded") logger.info("Hosting preprocessing script")