truefoundry 0.3.3__py3-none-any.whl → 0.4.0.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of truefoundry might be problematic. Click here for more details.

Files changed (224) hide show
  1. truefoundry/cli/__main__.py +3 -17
  2. truefoundry/common/__init__.py +0 -0
  3. truefoundry/common/request_utils.py +56 -0
  4. truefoundry/deploy/cli/cli.py +1 -1
  5. truefoundry/deploy/lib/auth/credential_provider.py +2 -12
  6. truefoundry/deploy/lib/clients/servicefoundry_client.py +0 -9
  7. truefoundry/deploy/lib/exceptions.py +1 -6
  8. truefoundry/deploy/lib/session.py +1 -16
  9. truefoundry/langchain/truefoundry_chat.py +1 -1
  10. truefoundry/langchain/truefoundry_embeddings.py +1 -1
  11. truefoundry/langchain/truefoundry_llm.py +1 -1
  12. truefoundry/langchain/utils.py +0 -41
  13. truefoundry/ml/__init__.py +46 -6
  14. truefoundry/ml/artifact/__init__.py +0 -0
  15. truefoundry/ml/artifact/truefoundry_artifact_repo.py +1120 -0
  16. truefoundry/ml/autogen/__init__.py +0 -0
  17. truefoundry/ml/autogen/client/__init__.py +373 -0
  18. truefoundry/ml/autogen/client/api/__init__.py +16 -0
  19. truefoundry/ml/autogen/client/api/auth_api.py +184 -0
  20. truefoundry/ml/autogen/client/api/deprecated_api.py +605 -0
  21. truefoundry/ml/autogen/client/api/experiments_api.py +2109 -0
  22. truefoundry/ml/autogen/client/api/health_api.py +299 -0
  23. truefoundry/ml/autogen/client/api/metrics_api.py +371 -0
  24. truefoundry/ml/autogen/client/api/mlfoundry_artifacts_api.py +7213 -0
  25. truefoundry/ml/autogen/client/api/python_deployment_config_api.py +201 -0
  26. truefoundry/ml/autogen/client/api/run_artifacts_api.py +231 -0
  27. truefoundry/ml/autogen/client/api/runs_api.py +2919 -0
  28. truefoundry/ml/autogen/client/api_client.py +822 -0
  29. truefoundry/ml/autogen/client/api_response.py +30 -0
  30. truefoundry/ml/autogen/client/configuration.py +489 -0
  31. truefoundry/ml/autogen/client/exceptions.py +161 -0
  32. truefoundry/ml/autogen/client/models/__init__.py +344 -0
  33. truefoundry/ml/autogen/client/models/add_custom_metrics_to_model_version_request_dto.py +69 -0
  34. truefoundry/ml/autogen/client/models/add_features_to_model_version_request_dto.py +83 -0
  35. truefoundry/ml/autogen/client/models/agent.py +125 -0
  36. truefoundry/ml/autogen/client/models/agent_app.py +118 -0
  37. truefoundry/ml/autogen/client/models/agent_open_api_tool.py +143 -0
  38. truefoundry/ml/autogen/client/models/agent_open_api_tool_with_fqn.py +144 -0
  39. truefoundry/ml/autogen/client/models/agent_with_fqn.py +127 -0
  40. truefoundry/ml/autogen/client/models/artifact_dto.py +115 -0
  41. truefoundry/ml/autogen/client/models/artifact_response_dto.py +75 -0
  42. truefoundry/ml/autogen/client/models/artifact_type.py +39 -0
  43. truefoundry/ml/autogen/client/models/artifact_version_dto.py +141 -0
  44. truefoundry/ml/autogen/client/models/artifact_version_response_dto.py +77 -0
  45. truefoundry/ml/autogen/client/models/artifact_version_status.py +35 -0
  46. truefoundry/ml/autogen/client/models/assistant_message.py +89 -0
  47. truefoundry/ml/autogen/client/models/authorize_user_for_model_request_dto.py +69 -0
  48. truefoundry/ml/autogen/client/models/authorize_user_for_model_version_request_dto.py +69 -0
  49. truefoundry/ml/autogen/client/models/backfill_default_storage_integration_id_request_dto.py +67 -0
  50. truefoundry/ml/autogen/client/models/blob_storage_reference.py +93 -0
  51. truefoundry/ml/autogen/client/models/body_get_search_runs_get.py +72 -0
  52. truefoundry/ml/autogen/client/models/chat_prompt.py +156 -0
  53. truefoundry/ml/autogen/client/models/chat_prompt_messages_inner.py +171 -0
  54. truefoundry/ml/autogen/client/models/columns_dto.py +73 -0
  55. truefoundry/ml/autogen/client/models/content.py +153 -0
  56. truefoundry/ml/autogen/client/models/content1.py +153 -0
  57. truefoundry/ml/autogen/client/models/content2.py +174 -0
  58. truefoundry/ml/autogen/client/models/content2_any_of_inner.py +150 -0
  59. truefoundry/ml/autogen/client/models/create_artifact_request_dto.py +74 -0
  60. truefoundry/ml/autogen/client/models/create_artifact_response_dto.py +66 -0
  61. truefoundry/ml/autogen/client/models/create_artifact_version_request_dto.py +74 -0
  62. truefoundry/ml/autogen/client/models/create_artifact_version_response_dto.py +66 -0
  63. truefoundry/ml/autogen/client/models/create_dataset_request_dto.py +76 -0
  64. truefoundry/ml/autogen/client/models/create_experiment_request_dto.py +94 -0
  65. truefoundry/ml/autogen/client/models/create_experiment_response_dto.py +67 -0
  66. truefoundry/ml/autogen/client/models/create_model_version_request_dto.py +95 -0
  67. truefoundry/ml/autogen/client/models/create_multi_part_upload_for_dataset_request_dto.py +73 -0
  68. truefoundry/ml/autogen/client/models/create_multi_part_upload_for_dataset_response_dto.py +79 -0
  69. truefoundry/ml/autogen/client/models/create_multi_part_upload_request_dto.py +73 -0
  70. truefoundry/ml/autogen/client/models/create_python_deployment_config_request_dto.py +72 -0
  71. truefoundry/ml/autogen/client/models/create_python_deployment_config_response_dto.py +68 -0
  72. truefoundry/ml/autogen/client/models/create_run_request_dto.py +97 -0
  73. truefoundry/ml/autogen/client/models/create_run_response_dto.py +76 -0
  74. truefoundry/ml/autogen/client/models/dataset_dto.py +112 -0
  75. truefoundry/ml/autogen/client/models/dataset_response_dto.py +75 -0
  76. truefoundry/ml/autogen/client/models/delete_artifact_versions_request_dto.py +65 -0
  77. truefoundry/ml/autogen/client/models/delete_dataset_request_dto.py +74 -0
  78. truefoundry/ml/autogen/client/models/delete_model_version_request_dto.py +65 -0
  79. truefoundry/ml/autogen/client/models/delete_run_request.py +65 -0
  80. truefoundry/ml/autogen/client/models/delete_tag_request_dto.py +68 -0
  81. truefoundry/ml/autogen/client/models/experiment_dto.py +127 -0
  82. truefoundry/ml/autogen/client/models/experiment_id_request_dto.py +67 -0
  83. truefoundry/ml/autogen/client/models/experiment_response_dto.py +75 -0
  84. truefoundry/ml/autogen/client/models/experiment_tag_dto.py +69 -0
  85. truefoundry/ml/autogen/client/models/feature_dto.py +68 -0
  86. truefoundry/ml/autogen/client/models/feature_value_type.py +35 -0
  87. truefoundry/ml/autogen/client/models/file_info_dto.py +76 -0
  88. truefoundry/ml/autogen/client/models/finalize_artifact_version_request_dto.py +101 -0
  89. truefoundry/ml/autogen/client/models/get_experiment_response_dto.py +88 -0
  90. truefoundry/ml/autogen/client/models/get_latest_run_log_response_dto.py +76 -0
  91. truefoundry/ml/autogen/client/models/get_metric_history_response.py +79 -0
  92. truefoundry/ml/autogen/client/models/get_signed_url_for_dataset_write_request_dto.py +68 -0
  93. truefoundry/ml/autogen/client/models/get_signed_urls_for_artifact_version_read_request_dto.py +68 -0
  94. truefoundry/ml/autogen/client/models/get_signed_urls_for_artifact_version_read_response_dto.py +81 -0
  95. truefoundry/ml/autogen/client/models/get_signed_urls_for_artifact_version_write_request_dto.py +69 -0
  96. truefoundry/ml/autogen/client/models/get_signed_urls_for_artifact_version_write_response_dto.py +83 -0
  97. truefoundry/ml/autogen/client/models/get_signed_urls_for_dataset_read_request_dto.py +68 -0
  98. truefoundry/ml/autogen/client/models/get_signed_urls_for_dataset_read_response_dto.py +81 -0
  99. truefoundry/ml/autogen/client/models/get_signed_urls_for_dataset_write_response_dto.py +81 -0
  100. truefoundry/ml/autogen/client/models/get_tenant_id_response_dto.py +74 -0
  101. truefoundry/ml/autogen/client/models/http_validation_error.py +82 -0
  102. truefoundry/ml/autogen/client/models/image_content_part.py +87 -0
  103. truefoundry/ml/autogen/client/models/image_url.py +75 -0
  104. truefoundry/ml/autogen/client/models/internal_metadata.py +180 -0
  105. truefoundry/ml/autogen/client/models/latest_run_log_dto.py +78 -0
  106. truefoundry/ml/autogen/client/models/list_artifact_versions_request_dto.py +107 -0
  107. truefoundry/ml/autogen/client/models/list_artifact_versions_response_dto.py +87 -0
  108. truefoundry/ml/autogen/client/models/list_artifacts_request_dto.py +96 -0
  109. truefoundry/ml/autogen/client/models/list_artifacts_response_dto.py +86 -0
  110. truefoundry/ml/autogen/client/models/list_colums_response_dto.py +75 -0
  111. truefoundry/ml/autogen/client/models/list_datasets_request_dto.py +78 -0
  112. truefoundry/ml/autogen/client/models/list_datasets_response_dto.py +86 -0
  113. truefoundry/ml/autogen/client/models/list_experiments_response_dto.py +86 -0
  114. truefoundry/ml/autogen/client/models/list_files_for_artifact_version_request_dto.py +76 -0
  115. truefoundry/ml/autogen/client/models/list_files_for_artifact_versions_response_dto.py +82 -0
  116. truefoundry/ml/autogen/client/models/list_files_for_dataset_request_dto.py +76 -0
  117. truefoundry/ml/autogen/client/models/list_files_for_dataset_response_dto.py +82 -0
  118. truefoundry/ml/autogen/client/models/list_latest_run_logs_response_dto.py +82 -0
  119. truefoundry/ml/autogen/client/models/list_metric_history_request_dto.py +69 -0
  120. truefoundry/ml/autogen/client/models/list_metric_history_response_dto.py +84 -0
  121. truefoundry/ml/autogen/client/models/list_model_version_response_dto.py +87 -0
  122. truefoundry/ml/autogen/client/models/list_model_versions_request_dto.py +93 -0
  123. truefoundry/ml/autogen/client/models/list_models_request_dto.py +89 -0
  124. truefoundry/ml/autogen/client/models/list_models_response_dto.py +84 -0
  125. truefoundry/ml/autogen/client/models/list_run_artifacts_response_dto.py +84 -0
  126. truefoundry/ml/autogen/client/models/list_run_logs_response_dto.py +82 -0
  127. truefoundry/ml/autogen/client/models/list_seed_experiments_response_dto.py +81 -0
  128. truefoundry/ml/autogen/client/models/log_batch_request_dto.py +106 -0
  129. truefoundry/ml/autogen/client/models/log_metric_request_dto.py +80 -0
  130. truefoundry/ml/autogen/client/models/log_param_request_dto.py +76 -0
  131. truefoundry/ml/autogen/client/models/method.py +37 -0
  132. truefoundry/ml/autogen/client/models/metric_collection_dto.py +82 -0
  133. truefoundry/ml/autogen/client/models/metric_dto.py +76 -0
  134. truefoundry/ml/autogen/client/models/mime_type.py +37 -0
  135. truefoundry/ml/autogen/client/models/model_configuration.py +103 -0
  136. truefoundry/ml/autogen/client/models/model_dto.py +122 -0
  137. truefoundry/ml/autogen/client/models/model_response_dto.py +75 -0
  138. truefoundry/ml/autogen/client/models/model_schema_dto.py +85 -0
  139. truefoundry/ml/autogen/client/models/model_version_dto.py +163 -0
  140. truefoundry/ml/autogen/client/models/model_version_response_dto.py +75 -0
  141. truefoundry/ml/autogen/client/models/multi_part_upload_dto.py +107 -0
  142. truefoundry/ml/autogen/client/models/multi_part_upload_response_dto.py +79 -0
  143. truefoundry/ml/autogen/client/models/multi_part_upload_storage_provider.py +34 -0
  144. truefoundry/ml/autogen/client/models/notify_artifact_version_failure_dto.py +65 -0
  145. truefoundry/ml/autogen/client/models/openapi_spec.py +152 -0
  146. truefoundry/ml/autogen/client/models/param_dto.py +66 -0
  147. truefoundry/ml/autogen/client/models/parameters.py +84 -0
  148. truefoundry/ml/autogen/client/models/prediction_type.py +34 -0
  149. truefoundry/ml/autogen/client/models/resolve_agent_app_response_dto.py +75 -0
  150. truefoundry/ml/autogen/client/models/restore_run_request_dto.py +65 -0
  151. truefoundry/ml/autogen/client/models/run_data_dto.py +104 -0
  152. truefoundry/ml/autogen/client/models/run_dto.py +84 -0
  153. truefoundry/ml/autogen/client/models/run_info_dto.py +105 -0
  154. truefoundry/ml/autogen/client/models/run_log_dto.py +90 -0
  155. truefoundry/ml/autogen/client/models/run_log_input_dto.py +80 -0
  156. truefoundry/ml/autogen/client/models/run_response_dto.py +75 -0
  157. truefoundry/ml/autogen/client/models/run_tag_dto.py +66 -0
  158. truefoundry/ml/autogen/client/models/search_runs_request_dto.py +94 -0
  159. truefoundry/ml/autogen/client/models/search_runs_response_dto.py +84 -0
  160. truefoundry/ml/autogen/client/models/set_experiment_tag_request_dto.py +73 -0
  161. truefoundry/ml/autogen/client/models/set_tag_request_dto.py +76 -0
  162. truefoundry/ml/autogen/client/models/signed_url_dto.py +69 -0
  163. truefoundry/ml/autogen/client/models/stop.py +152 -0
  164. truefoundry/ml/autogen/client/models/store_run_logs_request_dto.py +83 -0
  165. truefoundry/ml/autogen/client/models/system_message.py +89 -0
  166. truefoundry/ml/autogen/client/models/text.py +153 -0
  167. truefoundry/ml/autogen/client/models/text_content_part.py +84 -0
  168. truefoundry/ml/autogen/client/models/update_artifact_version_request_dto.py +74 -0
  169. truefoundry/ml/autogen/client/models/update_dataset_request_dto.py +74 -0
  170. truefoundry/ml/autogen/client/models/update_experiment_request_dto.py +74 -0
  171. truefoundry/ml/autogen/client/models/update_model_version_request_dto.py +93 -0
  172. truefoundry/ml/autogen/client/models/update_run_request_dto.py +78 -0
  173. truefoundry/ml/autogen/client/models/update_run_response_dto.py +76 -0
  174. truefoundry/ml/autogen/client/models/url.py +153 -0
  175. truefoundry/ml/autogen/client/models/user_message.py +89 -0
  176. truefoundry/ml/autogen/client/models/validation_error.py +87 -0
  177. truefoundry/ml/autogen/client/models/validation_error_loc_inner.py +154 -0
  178. truefoundry/ml/autogen/client/rest.py +426 -0
  179. truefoundry/ml/autogen/client_README.md +322 -0
  180. truefoundry/ml/cli/__init__.py +0 -0
  181. truefoundry/ml/cli/cli.py +18 -0
  182. truefoundry/ml/cli/commands/__init__.py +3 -0
  183. truefoundry/ml/cli/commands/download.py +87 -0
  184. truefoundry/ml/constants.py +84 -0
  185. truefoundry/ml/enums.py +70 -0
  186. truefoundry/ml/env_vars.py +13 -0
  187. truefoundry/ml/exceptions.py +8 -0
  188. truefoundry/ml/git_info.py +60 -0
  189. truefoundry/ml/internal_namespace.py +52 -0
  190. truefoundry/ml/log_types/__init__.py +4 -0
  191. truefoundry/ml/log_types/artifacts/artifact.py +427 -0
  192. truefoundry/ml/log_types/artifacts/constants.py +33 -0
  193. truefoundry/ml/log_types/artifacts/dataset.py +383 -0
  194. truefoundry/ml/log_types/artifacts/general_artifact.py +110 -0
  195. truefoundry/ml/log_types/artifacts/model.py +628 -0
  196. truefoundry/ml/log_types/artifacts/model_extras.py +48 -0
  197. truefoundry/ml/log_types/artifacts/utils.py +161 -0
  198. truefoundry/ml/log_types/image/__init__.py +3 -0
  199. truefoundry/ml/log_types/image/constants.py +8 -0
  200. truefoundry/ml/log_types/image/image.py +358 -0
  201. truefoundry/ml/log_types/image/image_normalizer.py +101 -0
  202. truefoundry/ml/log_types/image/types.py +68 -0
  203. truefoundry/ml/log_types/plot.py +281 -0
  204. truefoundry/ml/log_types/pydantic_base.py +10 -0
  205. truefoundry/ml/log_types/utils.py +12 -0
  206. truefoundry/ml/logger.py +17 -0
  207. truefoundry/ml/login.py +241 -0
  208. truefoundry/ml/mlfoundry_api.py +1620 -0
  209. truefoundry/ml/mlfoundry_run.py +1238 -0
  210. truefoundry/ml/run_utils.py +102 -0
  211. truefoundry/ml/services/__init__.py +0 -0
  212. truefoundry/ml/services/auth_service.py +109 -0
  213. truefoundry/ml/services/entities.py +108 -0
  214. truefoundry/ml/services/servicefoundry_service.py +35 -0
  215. truefoundry/ml/services/utils.py +122 -0
  216. truefoundry/ml/session.py +271 -0
  217. truefoundry/ml/validation_utils.py +346 -0
  218. truefoundry/pydantic_v1.py +5 -1
  219. {truefoundry-0.3.3.dist-info → truefoundry-0.4.0.dev0.dist-info}/METADATA +19 -12
  220. truefoundry-0.4.0.dev0.dist-info/RECORD +342 -0
  221. truefoundry-0.3.3.dist-info/RECORD +0 -136
  222. /truefoundry/{python_deploy_codegen.py → deploy/python_deploy_codegen.py} +0 -0
  223. {truefoundry-0.3.3.dist-info → truefoundry-0.4.0.dev0.dist-info}/WHEEL +0 -0
  224. {truefoundry-0.3.3.dist-info → truefoundry-0.4.0.dev0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,271 @@
1
+ import abc
2
+ import atexit
3
+ import os
4
+ import threading
5
+ import weakref
6
+ from typing import TYPE_CHECKING, Dict, Optional
7
+
8
+ from truefoundry.common.request_utils import urllib3_retry
9
+ from truefoundry.ml import env_vars
10
+ from truefoundry.ml.autogen.client import ( # type: ignore[attr-defined]
11
+ ApiClient,
12
+ Configuration,
13
+ )
14
+ from truefoundry.ml.exceptions import MlFoundryException
15
+ from truefoundry.ml.logger import logger
16
+ from truefoundry.ml.login import CredentialsFileContent, CredentialsFileManager
17
+ from truefoundry.ml.run_utils import (
18
+ append_path_to_rest_tracking_uri,
19
+ resolve_tracking_uri,
20
+ )
21
+ from truefoundry.ml.services.auth_service import get_auth_service
22
+ from truefoundry.ml.services.entities import HostCreds, Token, UserInfo
23
+
24
+ if TYPE_CHECKING:
25
+ from truefoundry.ml.mlfoundry_run import MlFoundryRun
26
+
27
+ TOKEN_REFRESH_LOCK = threading.RLock()
28
+
29
+
30
+ class CredentialProvider(abc.ABC):
31
+ @property
32
+ @abc.abstractmethod
33
+ def token(self) -> Token: ...
34
+
35
+ @staticmethod
36
+ @abc.abstractmethod
37
+ def can_provide() -> bool: ...
38
+
39
+ @property
40
+ @abc.abstractmethod
41
+ def tracking_uri(self) -> str: ...
42
+
43
+
44
+ class EnvCredentialProvider(CredentialProvider):
45
+ def __init__(self):
46
+ logger.debug("Using env var credential provider")
47
+ self._tracking_uri = resolve_tracking_uri(tracking_uri=None)
48
+ self._auth_service = get_auth_service(tracking_uri=self._tracking_uri)
49
+ api_key = os.getenv(env_vars.API_KEY_GLOBAL)
50
+ if not api_key:
51
+ raise MlFoundryException(
52
+ f"Value of {env_vars.API_KEY_GLOBAL} env var should be non-empty string"
53
+ )
54
+ self._token: Token = Token(access_token=api_key, refresh_token=None) # type: ignore[call-arg]
55
+
56
+ @staticmethod
57
+ def can_provide() -> bool:
58
+ return env_vars.API_KEY_GLOBAL in os.environ
59
+
60
+ @property
61
+ def token(self) -> Token:
62
+ with TOKEN_REFRESH_LOCK:
63
+ if self._token.is_going_to_be_expired():
64
+ logger.info("Refreshing access token")
65
+ self._token = self._auth_service.refresh_token(self._token)
66
+ return self._token
67
+
68
+ @property
69
+ def tracking_uri(self) -> str:
70
+ return self._tracking_uri
71
+
72
+
73
+ class FileCredentialProvider(CredentialProvider):
74
+ def __init__(self):
75
+ logger.debug("Using file credential provider")
76
+ self._cred_file = CredentialsFileManager()
77
+
78
+ with self._cred_file:
79
+ self._last_cred_file_content = self._cred_file.read()
80
+ self._tracking_uri = self._last_cred_file_content.host
81
+ self._token = self._last_cred_file_content.to_token()
82
+ self._auth_service = get_auth_service(tracking_uri=self._tracking_uri)
83
+
84
+ @staticmethod
85
+ def can_provide() -> bool:
86
+ with CredentialsFileManager() as cred_file:
87
+ return cred_file.exists()
88
+
89
+ @property
90
+ def token(self) -> Token:
91
+ with TOKEN_REFRESH_LOCK:
92
+ if not self._token.is_going_to_be_expired():
93
+ return self._token
94
+
95
+ logger.info("Refreshing access token")
96
+ with self._cred_file:
97
+ new_cred_file_content = self._cred_file.read()
98
+ new_token = new_cred_file_content.to_token()
99
+ new_tracking_uri = new_cred_file_content.host
100
+
101
+ if new_cred_file_content == self._last_cred_file_content:
102
+ self._token = self._auth_service.refresh_token(self._token)
103
+ self._last_cred_file_content = CredentialsFileContent(
104
+ host=self._tracking_uri,
105
+ access_token=self._token.access_token,
106
+ refresh_token=self._token.refresh_token,
107
+ )
108
+ self._cred_file.write(self._last_cred_file_content)
109
+ return self._token
110
+
111
+ if (
112
+ new_tracking_uri == self._tracking_uri
113
+ and new_token.to_user_info() == self._token.to_user_info()
114
+ ):
115
+ self._last_cred_file_content = new_cred_file_content
116
+ self._token = new_token
117
+ # recursive
118
+ return self.token
119
+
120
+ raise MlFoundryException(
121
+ "Credentials on disk changed while mlfoundry was running."
122
+ )
123
+
124
+ @property
125
+ def tracking_uri(self) -> str:
126
+ return self._tracking_uri
127
+
128
+
129
+ SESSION_LOCK = threading.RLock()
130
+
131
+
132
+ class ActiveRuns:
133
+ def __init__(self):
134
+ self._active_runs: Dict[str, weakref.ReferenceType["MlFoundryRun"]] = {}
135
+
136
+ def add_run(self, run: "MlFoundryRun"):
137
+ with SESSION_LOCK:
138
+ self._active_runs[run.run_id] = weakref.ref(run)
139
+
140
+ def remove_run(self, run: "MlFoundryRun"):
141
+ with SESSION_LOCK:
142
+ if run.run_id in self._active_runs:
143
+ del self._active_runs[run.run_id]
144
+
145
+ def close_active_runs(self):
146
+ with SESSION_LOCK:
147
+ for run_ref in list(self._active_runs.values()):
148
+ run = run_ref()
149
+ if run and run.auto_end:
150
+ run.end()
151
+ self._active_runs.clear()
152
+
153
+
154
+ ACTIVE_RUNS = ActiveRuns()
155
+ atexit.register(ACTIVE_RUNS.close_active_runs)
156
+
157
+
158
+ class Session:
159
+ def __init__(self, cred_provider: CredentialProvider):
160
+ # Note: Whenever a new session is initialized all the active runs are ended
161
+ self._closed = False
162
+ self._cred_provider: CredentialProvider = cred_provider
163
+ self._user_info: UserInfo = self._cred_provider.token.to_user_info()
164
+
165
+ def close(self):
166
+ logger.debug("Closing existing session")
167
+ self._closed = True
168
+ self._user_info = None
169
+ self._cred_provider = None
170
+
171
+ def _assert_not_closed(self):
172
+ if self._closed:
173
+ raise MlFoundryException(
174
+ "This session has been deactivated.\n"
175
+ "At a time only one `client` (received from "
176
+ "`truefoundry.ml.get_client()` function call) can be used"
177
+ )
178
+
179
+ @property
180
+ def token(self) -> Token:
181
+ return self._cred_provider.token
182
+
183
+ @property
184
+ def user_info(self) -> UserInfo:
185
+ self._assert_not_closed()
186
+ return self._user_info
187
+
188
+ @property
189
+ def tracking_uri(self) -> str:
190
+ return self._cred_provider.tracking_uri
191
+
192
+ def __eq__(self, other: object) -> bool:
193
+ if not isinstance(other, Session):
194
+ return False
195
+ return (
196
+ type(self._cred_provider) == type(other._cred_provider) # noqa: E721
197
+ and self.user_info == other.user_info
198
+ and self.tracking_uri == other.tracking_uri
199
+ )
200
+
201
+ def get_host_creds(self) -> HostCreds:
202
+ tracking_uri = append_path_to_rest_tracking_uri(
203
+ self._cred_provider.tracking_uri
204
+ )
205
+ return HostCreds(
206
+ host=tracking_uri, token=self._cred_provider.token.access_token
207
+ )
208
+
209
+
210
+ ACTIVE_SESSION: Optional[Session] = None
211
+
212
+
213
+ def get_active_session() -> Optional[Session]:
214
+ return ACTIVE_SESSION
215
+
216
+
217
+ def _get_api_client(
218
+ session: Optional[Session] = None,
219
+ allow_anonymous: bool = False,
220
+ ) -> ApiClient:
221
+ session = session or get_active_session()
222
+ if session is None:
223
+ if allow_anonymous:
224
+ return ApiClient()
225
+ else:
226
+ raise MlFoundryException(
227
+ "No active session found. Perhaps you are not logged in?\n"
228
+ "Please log in using `tfy login [--host HOST] --relogin"
229
+ )
230
+
231
+ creds = session.get_host_creds()
232
+ configuration = Configuration(
233
+ host=creds.host.rstrip("/"),
234
+ access_token=creds.token,
235
+ )
236
+ configuration.retries = urllib3_retry(retries=2)
237
+ api_client = ApiClient(configuration=configuration)
238
+ return api_client
239
+
240
+
241
+ def init_session() -> Session:
242
+ with SESSION_LOCK:
243
+ final_cred_provider = None
244
+ for cred_provider in [EnvCredentialProvider, FileCredentialProvider]:
245
+ if cred_provider.can_provide():
246
+ final_cred_provider = cred_provider()
247
+ break
248
+ if final_cred_provider is None:
249
+ raise MlFoundryException(
250
+ "Please login using `mlfoundry login` command "
251
+ "or `truefoundry.ml.login()` function call"
252
+ )
253
+ new_session = Session(cred_provider=final_cred_provider)
254
+
255
+ global ACTIVE_SESSION
256
+ if ACTIVE_SESSION and ACTIVE_SESSION == new_session:
257
+ return ACTIVE_SESSION
258
+
259
+ ACTIVE_RUNS.close_active_runs()
260
+
261
+ if ACTIVE_SESSION:
262
+ ACTIVE_SESSION.close()
263
+ ACTIVE_SESSION = new_session
264
+
265
+ logger.info(
266
+ "Logged in to %r as %r (%s)",
267
+ ACTIVE_SESSION.tracking_uri,
268
+ ACTIVE_SESSION.user_info.user_id,
269
+ ACTIVE_SESSION.user_info.email or ACTIVE_SESSION.user_info.user_type.value,
270
+ )
271
+ return ACTIVE_SESSION
@@ -0,0 +1,346 @@
1
+ """
2
+ Utilities for validating user inputs such as metric names and parameter names.
3
+ """
4
+
5
+ import json
6
+ import numbers
7
+ import posixpath
8
+ import re
9
+ from operator import xor
10
+ from typing import List, Optional, Union
11
+
12
+ from truefoundry.ml import MlFoundryException
13
+ from truefoundry.ml.autogen.client import ( # type: ignore[attr-defined]
14
+ MetricDto,
15
+ ParamDto,
16
+ RunTagDto,
17
+ )
18
+
19
+ _VALID_PARAM_AND_METRIC_NAMES = re.compile(r"^[/\w.\- ]*$")
20
+
21
+ # Regex for valid run IDs: must be an alphanumeric string of length 1 to 256.
22
+ _BAD_CHARACTERS_MESSAGE = (
23
+ "Names may only contain alphanumerics, underscores (_), dashes (-), periods (.),"
24
+ " spaces ( ), and slashes (/)."
25
+ )
26
+ _RUN_ID_REGEX = re.compile(r"^[a-zA-Z0-9][\w\-]{0,255}$")
27
+ _ML_REPO_ID_REGEX = re.compile(r"^[a-zA-Z0-9][\w\-]{0,63}$")
28
+ _ML_REPO_NAME_REGEX = re.compile(r"^[a-zA-Z][a-zA-Z0-9\-]{1,98}[a-zA-Z0-9]$")
29
+ _RUN_NAME_REGEX = re.compile(r"^[a-zA-Z0-9-]*$")
30
+ _RUN_LOG_LOG_TYPE_REGEX = re.compile(r"^[a-zA-Z0-9-/]*$")
31
+ _RUN_LOG_KEY_REGEX = re.compile(r"^[a-zA-Z0-9-_]*$")
32
+
33
+ MAX_PARAMS_TAGS_PER_BATCH = 100
34
+ MAX_METRICS_PER_BATCH = 1000
35
+ MAX_ENTITIES_PER_BATCH = 1000
36
+ MAX_BATCH_LOG_REQUEST_SIZE = int(1e6)
37
+ MAX_PARAM_VAL_LENGTH = 1000
38
+ MAX_TAG_VAL_LENGTH = 250
39
+ MAX_ML_REPO_TAG_KEY_LENGTH = 250
40
+ MAX_ML_REPO_TAG_VAL_LENGTH = 5000
41
+ MAX_ENTITY_KEY_LENGTH = 250
42
+ MAX_RUN_LOG_KEY_NAME_LENGTH = 128
43
+ MAX_RUN_LOG_LOG_TYPE_LENGTH = 128
44
+ MAX_ML_REPO_DESCRIPTION_LENGTH = 512
45
+ MAX_RUN_DESCRIPTION_LENGTH = 512
46
+ MAX_ML_REPOS_LISTED_PER_PAGE = 100
47
+
48
+
49
+ def is_string_type(item):
50
+ return isinstance(item, str)
51
+
52
+
53
+ def bad_path_message(name: str):
54
+ return (
55
+ f"Names may be treated as files in certain cases, "
56
+ f"and must not resolve to other names when treated as such. "
57
+ f"This name would resolve to {posixpath.normpath(name)!r}"
58
+ )
59
+
60
+
61
+ def path_not_unique(name: str):
62
+ norm = posixpath.normpath(name)
63
+ return norm != name or norm == "." or norm.startswith("..") or norm.startswith("/")
64
+
65
+
66
+ def _validate_metric_name(name: str):
67
+ """Check that `name` is a valid metric name and raise an exception if it isn't."""
68
+ if name is None or not _VALID_PARAM_AND_METRIC_NAMES.match(name):
69
+ raise MlFoundryException(
70
+ f"Invalid metric name: {name!r}. {_BAD_CHARACTERS_MESSAGE}",
71
+ )
72
+ if path_not_unique(name):
73
+ raise MlFoundryException(
74
+ f"Invalid metric name: {name!r}. {bad_path_message(name)}",
75
+ )
76
+
77
+
78
+ def _validate_run_name(name: Optional[str]):
79
+ if not name or not _RUN_NAME_REGEX.match(name):
80
+ raise MlFoundryException(
81
+ f"Invalid run name {name!r}. Name should not empty string "
82
+ f"and may only contain alphanumerics or dashes (-)",
83
+ )
84
+ _validate_length_limit("Run name", MAX_ENTITY_KEY_LENGTH, name)
85
+
86
+
87
+ def _is_numeric(value):
88
+ """
89
+ Returns True if the passed-in value is numeric.
90
+ """
91
+ # Note that `isinstance(bool_value, numbers.Number)` returns `True` because `bool` is a
92
+ # subclass of `int`.
93
+ return not isinstance(value, bool) and isinstance(value, numbers.Number)
94
+
95
+
96
+ def _validate_metric(
97
+ key: str,
98
+ value: Optional[Union[int, float]],
99
+ timestamp: Optional[int],
100
+ step: Optional[int],
101
+ ):
102
+ """
103
+ Check that a param with the specified key, value, timestamp is valid and raise an exception if
104
+ it isn't.
105
+ """
106
+ _validate_metric_name(key)
107
+ _validate_length_limit("Metric name", MAX_ENTITY_KEY_LENGTH, key)
108
+ # value must be a Number
109
+ # since bool is an instance of Number check for bool additionally
110
+ if not _is_numeric(value):
111
+ raise MlFoundryException(
112
+ f"Got invalid value {value} for metric {key!r} (step={step}). "
113
+ f"Please specify value as a valid double (64-bit floating point)",
114
+ )
115
+
116
+ if not isinstance(timestamp, int) or timestamp < 0:
117
+ raise MlFoundryException(
118
+ f"Got invalid timestamp {timestamp} for metric {key!r} (value={value}). "
119
+ f"Timestamp must be a non-negative long (64-bit integer)",
120
+ )
121
+
122
+ if not isinstance(step, int):
123
+ raise MlFoundryException(
124
+ f"Got invalid step {step} for metric {key!r} (value={value}). "
125
+ f"Step must be a valid long (64-bit integer).",
126
+ )
127
+
128
+
129
+ def _validate_run_log_input(
130
+ key: str,
131
+ timestamp: int,
132
+ step: int,
133
+ log_type: str,
134
+ value: Optional[str],
135
+ artifact_path: Optional[str],
136
+ ):
137
+ # reusing the validation defined for metric: str
138
+ _validate_metric(key=key, value=0, timestamp=timestamp, step=step)
139
+ _validate_length_limit("RunLog log type", MAX_RUN_LOG_LOG_TYPE_LENGTH, log_type)
140
+ _validate_length_limit("RunLog log key", MAX_RUN_LOG_KEY_NAME_LENGTH, key)
141
+ if not log_type or not _RUN_LOG_LOG_TYPE_REGEX.match(log_type):
142
+ raise MlFoundryException(
143
+ f"Invalid run log_type: {log_type}",
144
+ )
145
+
146
+ if not key or not _RUN_LOG_KEY_REGEX.match(key):
147
+ raise MlFoundryException(
148
+ f"Invalid run log key: {key!r} should only contain alphanumeric, hyphen or underscore"
149
+ )
150
+
151
+ if not xor(bool(artifact_path), bool(value)):
152
+ raise MlFoundryException(
153
+ "Either artifact_path or value should be empty. "
154
+ f"artifact_path={artifact_path!r}, value={value!r}",
155
+ )
156
+ if value is not None:
157
+ try:
158
+ json.loads(value)
159
+ except ValueError as e:
160
+ raise MlFoundryException(
161
+ f"Key {key!r} does not contain a valid json: {e}",
162
+ ) from e
163
+
164
+
165
+ def _validate_param(key: str, value: str):
166
+ """
167
+ Check that a param with the specified key & value is valid and raise an exception if it
168
+ isn't.
169
+ """
170
+ _validate_param_name(key)
171
+ _validate_length_limit("Param key", MAX_ENTITY_KEY_LENGTH, key)
172
+ _validate_length_limit("Param value", MAX_PARAM_VAL_LENGTH, value)
173
+
174
+
175
+ def _validate_tag(key: str, value: str):
176
+ """
177
+ Check that a tag with the specified key & value is valid and raise an exception if it isn't.
178
+ """
179
+ _validate_tag_name(key)
180
+ _validate_length_limit("Tag key", MAX_ENTITY_KEY_LENGTH, key)
181
+ _validate_length_limit("Tag value", MAX_TAG_VAL_LENGTH, value)
182
+
183
+
184
+ def _validate_ml_repo_tag(key: str, value: str):
185
+ """
186
+ Check that a tag with the specified key & value is valid and raise an exception if it isn't.
187
+ """
188
+ _validate_tag_name(key)
189
+ _validate_length_limit("Tag key", MAX_ML_REPO_TAG_KEY_LENGTH, key)
190
+ _validate_length_limit("Tag value", MAX_ML_REPO_TAG_VAL_LENGTH, value)
191
+
192
+
193
+ def _validate_list_ml_repos_max_results(max_results):
194
+ """
195
+ Check that `max_results` is within an acceptable range and raise an exception if it isn't.
196
+ """
197
+ if max_results is None:
198
+ return
199
+
200
+ if max_results < 1:
201
+ raise MlFoundryException(
202
+ f"Invalid value for request parameter max_results. "
203
+ f"It must be at least 1, but got value {max_results}",
204
+ )
205
+
206
+ if max_results > MAX_ML_REPOS_LISTED_PER_PAGE:
207
+ raise MlFoundryException(
208
+ f"Invalid value for request parameter max_results. "
209
+ f"It must be at most {MAX_ML_REPOS_LISTED_PER_PAGE}, but got value {max_results}",
210
+ )
211
+
212
+
213
+ def _validate_param_keys_unique(params: List[ParamDto]):
214
+ """Ensures that duplicate param keys are not present in the `log_batch()` params argument"""
215
+ unique_keys = []
216
+ dupe_keys = []
217
+ for param in params:
218
+ if param.key not in unique_keys:
219
+ unique_keys.append(param.key)
220
+ else:
221
+ dupe_keys.append(param.key)
222
+
223
+ if dupe_keys:
224
+ raise MlFoundryException(
225
+ f"Duplicate parameter keys have been submitted: {dupe_keys}. Please ensure "
226
+ f"the request contains only one param value per param key.",
227
+ )
228
+
229
+
230
+ def _validate_param_name(name: str):
231
+ """Check that `name` is a valid parameter name and raise an exception if it isn't."""
232
+ if name is None or not _VALID_PARAM_AND_METRIC_NAMES.match(name):
233
+ raise MlFoundryException(
234
+ f"Invalid parameter name: {name!r}. {_BAD_CHARACTERS_MESSAGE}",
235
+ )
236
+ if path_not_unique(name):
237
+ raise MlFoundryException(
238
+ f"Invalid parameter name: {name!r}. {bad_path_message(name)}",
239
+ )
240
+
241
+
242
+ def _validate_tag_name(name: str):
243
+ """Check that `name` is a valid tag name and raise an exception if it isn't."""
244
+ # Reuse param & metric check.
245
+ if name is None or not _VALID_PARAM_AND_METRIC_NAMES.match(name):
246
+ raise MlFoundryException(
247
+ f"Invalid tag name: {name!r}. {_BAD_CHARACTERS_MESSAGE}",
248
+ )
249
+ if path_not_unique(name):
250
+ raise MlFoundryException(
251
+ f"Invalid tag name: {name!r}. {bad_path_message(name)}",
252
+ )
253
+
254
+
255
+ def _validate_length_limit(entity_name: str, limit: int, value: str):
256
+ if len(value) > limit:
257
+ raise MlFoundryException(
258
+ f"{entity_name} {value!r} had length {len(value)} which exceeded length limit of {limit}",
259
+ )
260
+
261
+
262
+ def _validate_run_id(run_id: str):
263
+ """Check that `run_id` is a valid run ID and raise an exception if it isn't."""
264
+ if _RUN_ID_REGEX.match(run_id) is None:
265
+ raise MlFoundryException(
266
+ f"Invalid run ID: {run_id!r}",
267
+ )
268
+
269
+
270
+ def _validate_ml_repo_id(ml_repo_id: str):
271
+ """Check that `ml_repo_id`is a valid string or None, raise an exception if it isn't."""
272
+ if ml_repo_id is not None and _ML_REPO_ID_REGEX.match(ml_repo_id) is None:
273
+ raise MlFoundryException(
274
+ f"Invalid ml_repo ID: {ml_repo_id!r}",
275
+ )
276
+
277
+
278
+ def _validate_batch_limit(entity_name: str, limit: int, length: int):
279
+ if length > limit:
280
+ error_msg = (
281
+ f"A batch logging request can contain at most {limit} {entity_name}. "
282
+ f"Got {length} {entity_name}. "
283
+ f"Please split up {entity_name} across multiple requests and try again."
284
+ )
285
+ raise MlFoundryException(error_msg)
286
+
287
+
288
+ def _validate_batch_log_limits(
289
+ metrics: List[MetricDto], params: List[ParamDto], tags: List[RunTagDto]
290
+ ):
291
+ """Validate that the provided batched logging arguments are within expected limits."""
292
+ _validate_batch_limit(
293
+ entity_name="metrics", limit=MAX_METRICS_PER_BATCH, length=len(metrics)
294
+ )
295
+ _validate_batch_limit(
296
+ entity_name="params", limit=MAX_PARAMS_TAGS_PER_BATCH, length=len(params)
297
+ )
298
+ _validate_batch_limit(
299
+ entity_name="tags", limit=MAX_PARAMS_TAGS_PER_BATCH, length=len(tags)
300
+ )
301
+ total_length = len(metrics) + len(params) + len(tags)
302
+ _validate_batch_limit(
303
+ entity_name="metrics, params, and tags",
304
+ limit=MAX_ENTITIES_PER_BATCH,
305
+ length=total_length,
306
+ )
307
+
308
+
309
+ def _validate_batch_log_data(
310
+ metrics: List[MetricDto], params: List[ParamDto], tags: List[RunTagDto]
311
+ ):
312
+ if len(metrics) == 0 and len(params) == 0 and len(tags) == 0:
313
+ return
314
+ _validate_batch_log_limits(metrics=metrics, params=params, tags=tags)
315
+ for metric in metrics:
316
+ _validate_metric(metric.key, metric.value, metric.timestamp, metric.step)
317
+ if len(params) > 1:
318
+ _validate_param_keys_unique(params)
319
+ for param in params:
320
+ _validate_param(param.key, param.value)
321
+ for tag in tags:
322
+ _validate_tag(tag.key, tag.value)
323
+
324
+
325
+ def _validate_ml_repo_name(ml_repo_name: str):
326
+ """Check that `ml_repo_name` is a valid string and raise an exception if it isn't."""
327
+ if ml_repo_name == "" or ml_repo_name is None or not is_string_type(ml_repo_name):
328
+ raise MlFoundryException(
329
+ f"ml_repo must be string type and not empty. "
330
+ f"Got {type(ml_repo_name)} type with value {ml_repo_name!r}"
331
+ )
332
+
333
+ if not _ML_REPO_NAME_REGEX.match(ml_repo_name):
334
+ raise MlFoundryException(
335
+ f"Invalid ML Repo name {ml_repo_name!r}. Name may only contain alphanumerics or dashes (-)",
336
+ )
337
+
338
+
339
+ def _validate_ml_repo_description(description: str):
340
+ _validate_length_limit(
341
+ "ML Repo Description", MAX_ML_REPO_DESCRIPTION_LENGTH, description
342
+ )
343
+
344
+
345
+ def _validate_run_description(description: str):
346
+ _validate_length_limit("Run Description", MAX_RUN_DESCRIPTION_LENGTH, description)
@@ -1,5 +1,9 @@
1
1
  try:
2
2
  from pydantic.v1 import * # noqa: F403
3
- from pydantic.v1 import utils # noqa: F401
3
+ from pydantic.v1 import ConstrainedStr, utils # noqa: F401
4
4
  except ImportError:
5
5
  from pydantic import * # noqa: F403
6
+
7
+
8
+ class NonEmptyStr(ConstrainedStr):
9
+ min_length: int = 1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: truefoundry
3
- Version: 0.3.3
3
+ Version: 0.4.0.dev0
4
4
  Summary: Truefoundry CLI
5
5
  Author: Abhishek Choudhary
6
6
  Author-email: abhishek@truefoundry.com
@@ -10,37 +10,44 @@ Classifier: Programming Language :: Python :: 3.9
10
10
  Classifier: Programming Language :: Python :: 3.10
11
11
  Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
- Provides-Extra: ml
14
13
  Provides-Extra: workflow
15
- Requires-Dist: GitPython (>=3.1.43,<3.2.0)
14
+ Requires-Dist: GitPython (>=3.1.43,<4.0.0)
16
15
  Requires-Dist: Mako (>=1.1.6,<2.0.0)
17
- Requires-Dist: PyJWT (>=2.0.0,<3.0.0)
16
+ Requires-Dist: PyJWT (>=2.4.0,<3.0.0)
18
17
  Requires-Dist: PyYAML (>=6.0.0,<7.0.0)
18
+ Requires-Dist: aenum (>=3.0.0,<4.0.0)
19
19
  Requires-Dist: click (>=7.0.0,<9.0.0)
20
+ Requires-Dist: coolname (>=1.1.0,<2.0.0)
20
21
  Requires-Dist: docker (>=6.1.2,<8.0.0)
21
22
  Requires-Dist: fastapi (>=0.56.0,<0.200.0)
22
23
  Requires-Dist: filelock (>=3.8.0,<4.0.0)
23
24
  Requires-Dist: flytekit (==1.12.2) ; extra == "workflow"
24
- Requires-Dist: gitignorefile (>=1.1.2,<1.2.0)
25
- Requires-Dist: importlib-metadata (>=6.0.1,<8.0.0)
26
- Requires-Dist: importlib-resources (>=5.2.0,<6.0.0)
27
- Requires-Dist: mlfoundry (==0.11.5) ; extra == "ml"
25
+ Requires-Dist: gitignorefile (>=1.1.2,<2.0.0)
26
+ Requires-Dist: importlib-metadata (>=4.11.3,<9.0.0)
27
+ Requires-Dist: importlib-resources (>=5.2.0,<7.0.0)
28
+ Requires-Dist: numpy (>=1.23.0,<2.0.0) ; python_version < "3.12"
29
+ Requires-Dist: numpy (>=1.26.0,<2.0.0) ; python_version >= "3.12"
28
30
  Requires-Dist: openai (>=1.16.2,<2.0.0)
29
31
  Requires-Dist: packaging (>=20.0,<25.0)
30
- Requires-Dist: pydantic (>=1.10.0,<3)
32
+ Requires-Dist: pandas (>=1.0.0,<3.0.0) ; python_version < "3.10"
33
+ Requires-Dist: pandas (>=1.4.0,<3.0.0) ; python_version >= "3.10"
34
+ Requires-Dist: psutil (>=5.9.0,<7.0.0)
35
+ Requires-Dist: pydantic (>=1.8.2,<3.0.0)
31
36
  Requires-Dist: pygments (>=2.12.0,<3.0.0)
32
37
  Requires-Dist: python-dateutil (>=2.8.2,<3.0.0)
33
- Requires-Dist: python-dotenv (>=1.0.1,<1.1.0)
38
+ Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
34
39
  Requires-Dist: python-socketio[client] (>=5.5.2,<6.0.0)
35
40
  Requires-Dist: questionary (>=1.10.0,<2.0.0)
36
41
  Requires-Dist: requests (>=2.31.0,<3.0.0)
37
- Requires-Dist: requirements-parser (>=0.10.2,<0.11.0)
42
+ Requires-Dist: requirements-parser (>=0.11.0,<0.12.0)
38
43
  Requires-Dist: rich (>=13.7.1,<14.0.0)
39
44
  Requires-Dist: rich-click (>=1.2.1,<2.0.0)
45
+ Requires-Dist: scipy (>=1.12.0,<2.0.0) ; python_version >= "3.12"
46
+ Requires-Dist: scipy (>=1.5.0,<2.0.0) ; python_version < "3.12"
40
47
  Requires-Dist: tqdm (>=4.0.0,<5.0.0)
41
48
  Requires-Dist: typing-extensions (>=3.10.0)
42
49
  Requires-Dist: urllib3 (>=1.26.18,<3)
43
- Requires-Dist: uvicorn (>=0.13.0,<0.40.0)
50
+ Requires-Dist: uvicorn (>=0.13.0,<1.0.0)
44
51
  Requires-Dist: yq (>=3.1.0,<4.0.0)
45
52
  Description-Content-Type: text/markdown
46
53