truefoundry 0.3.4rc1__py3-none-any.whl → 0.4.0__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.
- truefoundry/__init__.py +2 -0
- truefoundry/autodeploy/agents/developer.py +1 -1
- truefoundry/autodeploy/agents/project_identifier.py +2 -2
- truefoundry/autodeploy/agents/tester.py +1 -1
- truefoundry/autodeploy/cli.py +1 -1
- truefoundry/autodeploy/tools/list_files.py +1 -1
- truefoundry/cli/__main__.py +3 -17
- truefoundry/common/__init__.py +0 -0
- truefoundry/{deploy/lib/auth → common}/auth_service_client.py +50 -40
- truefoundry/common/constants.py +12 -0
- truefoundry/{deploy/lib/auth → common}/credential_file_manager.py +7 -7
- truefoundry/{deploy/lib/auth → common}/credential_provider.py +10 -23
- truefoundry/common/entities.py +124 -0
- truefoundry/common/exceptions.py +12 -0
- truefoundry/common/request_utils.py +84 -0
- truefoundry/common/servicefoundry_client.py +91 -0
- truefoundry/common/utils.py +56 -0
- truefoundry/deploy/auto_gen/models.py +4 -6
- truefoundry/deploy/cli/cli.py +3 -1
- truefoundry/deploy/cli/commands/apply_command.py +1 -1
- truefoundry/deploy/cli/commands/build_command.py +1 -1
- truefoundry/deploy/cli/commands/deploy_command.py +1 -1
- truefoundry/deploy/cli/commands/login_command.py +2 -2
- truefoundry/deploy/cli/commands/patch_application_command.py +1 -1
- truefoundry/deploy/cli/commands/patch_command.py +1 -1
- truefoundry/deploy/cli/commands/terminate_comand.py +1 -1
- truefoundry/deploy/cli/util.py +1 -1
- truefoundry/deploy/function_service/remote/remote.py +1 -1
- truefoundry/deploy/lib/auth/servicefoundry_session.py +2 -2
- truefoundry/deploy/lib/clients/servicefoundry_client.py +120 -159
- truefoundry/deploy/lib/const.py +1 -35
- truefoundry/deploy/lib/exceptions.py +0 -16
- truefoundry/deploy/lib/model/entity.py +1 -112
- truefoundry/deploy/lib/session.py +14 -42
- truefoundry/deploy/lib/util.py +0 -37
- truefoundry/{python_deploy_codegen.py → deploy/python_deploy_codegen.py} +2 -2
- truefoundry/deploy/v2/lib/deploy.py +3 -3
- truefoundry/deploy/v2/lib/deployable_patched_models.py +1 -1
- truefoundry/langchain/truefoundry_chat.py +1 -1
- truefoundry/langchain/truefoundry_embeddings.py +1 -1
- truefoundry/langchain/truefoundry_llm.py +1 -1
- truefoundry/langchain/utils.py +0 -41
- truefoundry/ml/__init__.py +37 -6
- truefoundry/ml/artifact/__init__.py +0 -0
- truefoundry/ml/artifact/truefoundry_artifact_repo.py +1161 -0
- truefoundry/ml/autogen/__init__.py +0 -0
- truefoundry/ml/autogen/client/__init__.py +370 -0
- truefoundry/ml/autogen/client/api/__init__.py +16 -0
- truefoundry/ml/autogen/client/api/auth_api.py +184 -0
- truefoundry/ml/autogen/client/api/deprecated_api.py +605 -0
- truefoundry/ml/autogen/client/api/experiments_api.py +1944 -0
- truefoundry/ml/autogen/client/api/health_api.py +299 -0
- truefoundry/ml/autogen/client/api/metrics_api.py +371 -0
- truefoundry/ml/autogen/client/api/mlfoundry_artifacts_api.py +7213 -0
- truefoundry/ml/autogen/client/api/python_deployment_config_api.py +201 -0
- truefoundry/ml/autogen/client/api/run_artifacts_api.py +231 -0
- truefoundry/ml/autogen/client/api/runs_api.py +2919 -0
- truefoundry/ml/autogen/client/api_client.py +822 -0
- truefoundry/ml/autogen/client/api_response.py +30 -0
- truefoundry/ml/autogen/client/configuration.py +489 -0
- truefoundry/ml/autogen/client/exceptions.py +161 -0
- truefoundry/ml/autogen/client/models/__init__.py +341 -0
- truefoundry/ml/autogen/client/models/add_custom_metrics_to_model_version_request_dto.py +69 -0
- truefoundry/ml/autogen/client/models/add_features_to_model_version_request_dto.py +83 -0
- truefoundry/ml/autogen/client/models/agent.py +125 -0
- truefoundry/ml/autogen/client/models/agent_app.py +118 -0
- truefoundry/ml/autogen/client/models/agent_open_api_tool.py +143 -0
- truefoundry/ml/autogen/client/models/agent_open_api_tool_with_fqn.py +144 -0
- truefoundry/ml/autogen/client/models/agent_with_fqn.py +127 -0
- truefoundry/ml/autogen/client/models/artifact_dto.py +115 -0
- truefoundry/ml/autogen/client/models/artifact_response_dto.py +75 -0
- truefoundry/ml/autogen/client/models/artifact_type.py +39 -0
- truefoundry/ml/autogen/client/models/artifact_version_dto.py +141 -0
- truefoundry/ml/autogen/client/models/artifact_version_response_dto.py +77 -0
- truefoundry/ml/autogen/client/models/artifact_version_status.py +35 -0
- truefoundry/ml/autogen/client/models/assistant_message.py +89 -0
- truefoundry/ml/autogen/client/models/authorize_user_for_model_request_dto.py +69 -0
- truefoundry/ml/autogen/client/models/authorize_user_for_model_version_request_dto.py +69 -0
- truefoundry/ml/autogen/client/models/blob_storage_reference.py +93 -0
- truefoundry/ml/autogen/client/models/body_get_search_runs_get.py +72 -0
- truefoundry/ml/autogen/client/models/chat_prompt.py +156 -0
- truefoundry/ml/autogen/client/models/chat_prompt_messages_inner.py +171 -0
- truefoundry/ml/autogen/client/models/columns_dto.py +73 -0
- truefoundry/ml/autogen/client/models/content.py +153 -0
- truefoundry/ml/autogen/client/models/content1.py +153 -0
- truefoundry/ml/autogen/client/models/content2.py +174 -0
- truefoundry/ml/autogen/client/models/content2_any_of_inner.py +150 -0
- truefoundry/ml/autogen/client/models/create_artifact_request_dto.py +74 -0
- truefoundry/ml/autogen/client/models/create_artifact_response_dto.py +65 -0
- truefoundry/ml/autogen/client/models/create_artifact_version_request_dto.py +74 -0
- truefoundry/ml/autogen/client/models/create_artifact_version_response_dto.py +65 -0
- truefoundry/ml/autogen/client/models/create_dataset_request_dto.py +76 -0
- truefoundry/ml/autogen/client/models/create_experiment_request_dto.py +94 -0
- truefoundry/ml/autogen/client/models/create_experiment_response_dto.py +67 -0
- truefoundry/ml/autogen/client/models/create_model_version_request_dto.py +95 -0
- truefoundry/ml/autogen/client/models/create_multi_part_upload_for_dataset_request_dto.py +73 -0
- truefoundry/ml/autogen/client/models/create_multi_part_upload_for_dataset_response_dto.py +79 -0
- truefoundry/ml/autogen/client/models/create_multi_part_upload_request_dto.py +73 -0
- truefoundry/ml/autogen/client/models/create_python_deployment_config_request_dto.py +72 -0
- truefoundry/ml/autogen/client/models/create_python_deployment_config_response_dto.py +67 -0
- truefoundry/ml/autogen/client/models/create_run_request_dto.py +97 -0
- truefoundry/ml/autogen/client/models/create_run_response_dto.py +75 -0
- truefoundry/ml/autogen/client/models/dataset_dto.py +112 -0
- truefoundry/ml/autogen/client/models/dataset_response_dto.py +75 -0
- truefoundry/ml/autogen/client/models/delete_artifact_versions_request_dto.py +65 -0
- truefoundry/ml/autogen/client/models/delete_dataset_request_dto.py +74 -0
- truefoundry/ml/autogen/client/models/delete_model_version_request_dto.py +65 -0
- truefoundry/ml/autogen/client/models/delete_run_request.py +65 -0
- truefoundry/ml/autogen/client/models/delete_tag_request_dto.py +68 -0
- truefoundry/ml/autogen/client/models/experiment_dto.py +127 -0
- truefoundry/ml/autogen/client/models/experiment_id_request_dto.py +67 -0
- truefoundry/ml/autogen/client/models/experiment_response_dto.py +75 -0
- truefoundry/ml/autogen/client/models/experiment_tag_dto.py +69 -0
- truefoundry/ml/autogen/client/models/feature_dto.py +68 -0
- truefoundry/ml/autogen/client/models/feature_value_type.py +35 -0
- truefoundry/ml/autogen/client/models/file_info_dto.py +76 -0
- truefoundry/ml/autogen/client/models/finalize_artifact_version_request_dto.py +101 -0
- truefoundry/ml/autogen/client/models/get_experiment_response_dto.py +88 -0
- truefoundry/ml/autogen/client/models/get_latest_run_log_response_dto.py +75 -0
- truefoundry/ml/autogen/client/models/get_metric_history_response.py +79 -0
- truefoundry/ml/autogen/client/models/get_signed_url_for_dataset_write_request_dto.py +68 -0
- truefoundry/ml/autogen/client/models/get_signed_urls_for_artifact_version_read_request_dto.py +68 -0
- truefoundry/ml/autogen/client/models/get_signed_urls_for_artifact_version_read_response_dto.py +81 -0
- truefoundry/ml/autogen/client/models/get_signed_urls_for_artifact_version_write_request_dto.py +69 -0
- truefoundry/ml/autogen/client/models/get_signed_urls_for_artifact_version_write_response_dto.py +83 -0
- truefoundry/ml/autogen/client/models/get_signed_urls_for_dataset_read_request_dto.py +68 -0
- truefoundry/ml/autogen/client/models/get_signed_urls_for_dataset_read_response_dto.py +81 -0
- truefoundry/ml/autogen/client/models/get_signed_urls_for_dataset_write_response_dto.py +81 -0
- truefoundry/ml/autogen/client/models/get_tenant_id_response_dto.py +73 -0
- truefoundry/ml/autogen/client/models/http_validation_error.py +82 -0
- truefoundry/ml/autogen/client/models/image_content_part.py +87 -0
- truefoundry/ml/autogen/client/models/image_url.py +75 -0
- truefoundry/ml/autogen/client/models/internal_metadata.py +180 -0
- truefoundry/ml/autogen/client/models/latest_run_log_dto.py +78 -0
- truefoundry/ml/autogen/client/models/list_artifact_versions_request_dto.py +107 -0
- truefoundry/ml/autogen/client/models/list_artifact_versions_response_dto.py +87 -0
- truefoundry/ml/autogen/client/models/list_artifacts_request_dto.py +96 -0
- truefoundry/ml/autogen/client/models/list_artifacts_response_dto.py +86 -0
- truefoundry/ml/autogen/client/models/list_colums_response_dto.py +75 -0
- truefoundry/ml/autogen/client/models/list_datasets_request_dto.py +78 -0
- truefoundry/ml/autogen/client/models/list_datasets_response_dto.py +86 -0
- truefoundry/ml/autogen/client/models/list_experiments_response_dto.py +86 -0
- truefoundry/ml/autogen/client/models/list_files_for_artifact_version_request_dto.py +76 -0
- truefoundry/ml/autogen/client/models/list_files_for_artifact_versions_response_dto.py +82 -0
- truefoundry/ml/autogen/client/models/list_files_for_dataset_request_dto.py +76 -0
- truefoundry/ml/autogen/client/models/list_files_for_dataset_response_dto.py +82 -0
- truefoundry/ml/autogen/client/models/list_latest_run_logs_response_dto.py +82 -0
- truefoundry/ml/autogen/client/models/list_metric_history_request_dto.py +69 -0
- truefoundry/ml/autogen/client/models/list_metric_history_response_dto.py +84 -0
- truefoundry/ml/autogen/client/models/list_model_version_response_dto.py +87 -0
- truefoundry/ml/autogen/client/models/list_model_versions_request_dto.py +93 -0
- truefoundry/ml/autogen/client/models/list_models_request_dto.py +89 -0
- truefoundry/ml/autogen/client/models/list_models_response_dto.py +84 -0
- truefoundry/ml/autogen/client/models/list_run_artifacts_response_dto.py +84 -0
- truefoundry/ml/autogen/client/models/list_run_logs_response_dto.py +82 -0
- truefoundry/ml/autogen/client/models/list_seed_experiments_response_dto.py +81 -0
- truefoundry/ml/autogen/client/models/log_batch_request_dto.py +106 -0
- truefoundry/ml/autogen/client/models/log_metric_request_dto.py +80 -0
- truefoundry/ml/autogen/client/models/log_param_request_dto.py +76 -0
- truefoundry/ml/autogen/client/models/method.py +37 -0
- truefoundry/ml/autogen/client/models/metric_collection_dto.py +82 -0
- truefoundry/ml/autogen/client/models/metric_dto.py +76 -0
- truefoundry/ml/autogen/client/models/mime_type.py +37 -0
- truefoundry/ml/autogen/client/models/model_configuration.py +103 -0
- truefoundry/ml/autogen/client/models/model_dto.py +122 -0
- truefoundry/ml/autogen/client/models/model_response_dto.py +75 -0
- truefoundry/ml/autogen/client/models/model_schema_dto.py +85 -0
- truefoundry/ml/autogen/client/models/model_version_dto.py +170 -0
- truefoundry/ml/autogen/client/models/model_version_response_dto.py +75 -0
- truefoundry/ml/autogen/client/models/multi_part_upload_dto.py +107 -0
- truefoundry/ml/autogen/client/models/multi_part_upload_response_dto.py +79 -0
- truefoundry/ml/autogen/client/models/multi_part_upload_storage_provider.py +34 -0
- truefoundry/ml/autogen/client/models/notify_artifact_version_failure_dto.py +65 -0
- truefoundry/ml/autogen/client/models/openapi_spec.py +152 -0
- truefoundry/ml/autogen/client/models/param_dto.py +66 -0
- truefoundry/ml/autogen/client/models/parameters.py +84 -0
- truefoundry/ml/autogen/client/models/prediction_type.py +34 -0
- truefoundry/ml/autogen/client/models/resolve_agent_app_response_dto.py +75 -0
- truefoundry/ml/autogen/client/models/restore_run_request_dto.py +65 -0
- truefoundry/ml/autogen/client/models/run_data_dto.py +104 -0
- truefoundry/ml/autogen/client/models/run_dto.py +84 -0
- truefoundry/ml/autogen/client/models/run_info_dto.py +105 -0
- truefoundry/ml/autogen/client/models/run_log_dto.py +90 -0
- truefoundry/ml/autogen/client/models/run_log_input_dto.py +80 -0
- truefoundry/ml/autogen/client/models/run_response_dto.py +75 -0
- truefoundry/ml/autogen/client/models/run_tag_dto.py +66 -0
- truefoundry/ml/autogen/client/models/search_runs_request_dto.py +94 -0
- truefoundry/ml/autogen/client/models/search_runs_response_dto.py +84 -0
- truefoundry/ml/autogen/client/models/set_experiment_tag_request_dto.py +73 -0
- truefoundry/ml/autogen/client/models/set_tag_request_dto.py +76 -0
- truefoundry/ml/autogen/client/models/signed_url_dto.py +69 -0
- truefoundry/ml/autogen/client/models/stop.py +152 -0
- truefoundry/ml/autogen/client/models/store_run_logs_request_dto.py +83 -0
- truefoundry/ml/autogen/client/models/system_message.py +89 -0
- truefoundry/ml/autogen/client/models/text.py +153 -0
- truefoundry/ml/autogen/client/models/text_content_part.py +84 -0
- truefoundry/ml/autogen/client/models/update_artifact_version_request_dto.py +74 -0
- truefoundry/ml/autogen/client/models/update_dataset_request_dto.py +74 -0
- truefoundry/ml/autogen/client/models/update_experiment_request_dto.py +74 -0
- truefoundry/ml/autogen/client/models/update_model_version_request_dto.py +93 -0
- truefoundry/ml/autogen/client/models/update_run_request_dto.py +78 -0
- truefoundry/ml/autogen/client/models/update_run_response_dto.py +75 -0
- truefoundry/ml/autogen/client/models/url.py +153 -0
- truefoundry/ml/autogen/client/models/user_message.py +89 -0
- truefoundry/ml/autogen/client/models/validation_error.py +87 -0
- truefoundry/ml/autogen/client/models/validation_error_loc_inner.py +154 -0
- truefoundry/ml/autogen/client/rest.py +426 -0
- truefoundry/ml/autogen/client_README.md +320 -0
- truefoundry/ml/cli/__init__.py +0 -0
- truefoundry/ml/cli/cli.py +18 -0
- truefoundry/ml/cli/commands/__init__.py +3 -0
- truefoundry/ml/cli/commands/download.py +87 -0
- truefoundry/ml/clients/__init__.py +0 -0
- truefoundry/ml/clients/entities.py +8 -0
- truefoundry/ml/clients/servicefoundry_client.py +45 -0
- truefoundry/ml/clients/utils.py +122 -0
- truefoundry/ml/constants.py +84 -0
- truefoundry/ml/entities.py +62 -0
- truefoundry/ml/enums.py +70 -0
- truefoundry/ml/env_vars.py +9 -0
- truefoundry/ml/exceptions.py +8 -0
- truefoundry/ml/git_info.py +60 -0
- truefoundry/ml/internal_namespace.py +52 -0
- truefoundry/ml/log_types/__init__.py +4 -0
- truefoundry/ml/log_types/artifacts/artifact.py +431 -0
- truefoundry/ml/log_types/artifacts/constants.py +33 -0
- truefoundry/ml/log_types/artifacts/dataset.py +384 -0
- truefoundry/ml/log_types/artifacts/general_artifact.py +110 -0
- truefoundry/ml/log_types/artifacts/model.py +611 -0
- truefoundry/ml/log_types/artifacts/model_extras.py +48 -0
- truefoundry/ml/log_types/artifacts/utils.py +161 -0
- truefoundry/ml/log_types/image/__init__.py +3 -0
- truefoundry/ml/log_types/image/constants.py +8 -0
- truefoundry/ml/log_types/image/image.py +357 -0
- truefoundry/ml/log_types/image/image_normalizer.py +102 -0
- truefoundry/ml/log_types/image/types.py +68 -0
- truefoundry/ml/log_types/plot.py +281 -0
- truefoundry/ml/log_types/pydantic_base.py +10 -0
- truefoundry/ml/log_types/utils.py +12 -0
- truefoundry/ml/logger.py +17 -0
- truefoundry/ml/mlfoundry_api.py +1575 -0
- truefoundry/ml/mlfoundry_run.py +1203 -0
- truefoundry/ml/run_utils.py +93 -0
- truefoundry/ml/session.py +168 -0
- truefoundry/ml/validation_utils.py +346 -0
- truefoundry/pydantic_v1.py +8 -1
- truefoundry/workflow/__init__.py +16 -1
- {truefoundry-0.3.4rc1.dist-info → truefoundry-0.4.0.dist-info}/METADATA +21 -14
- truefoundry-0.4.0.dist-info/RECORD +344 -0
- truefoundry/deploy/lib/clients/utils.py +0 -41
- truefoundry-0.3.4rc1.dist-info/RECORD +0 -136
- {truefoundry-0.3.4rc1.dist-info → truefoundry-0.4.0.dist-info}/WHEEL +0 -0
- {truefoundry-0.3.4rc1.dist-info → truefoundry-0.4.0.dist-info}/entry_points.txt +0 -0
truefoundry/__init__.py
CHANGED
|
@@ -23,7 +23,7 @@ from truefoundry.autodeploy.tools import (
|
|
|
23
23
|
class Developer(Agent):
|
|
24
24
|
system_prompt = """
|
|
25
25
|
You are a software engineer.
|
|
26
|
-
You goal is to do whatever you have to do to
|
|
26
|
+
You goal is to do whatever you have to do to successfully run a project.
|
|
27
27
|
If the project already contains a dockerfile, you can use that.
|
|
28
28
|
If the project does not contain a dockerfile, you have to create one.
|
|
29
29
|
You need to fix any issue to ensure that the image builds succesfully.
|
|
@@ -38,7 +38,7 @@ Prefer using file type counts over list files.
|
|
|
38
38
|
use the knowledge of file type counts to create the right glob pattern while listing files.
|
|
39
39
|
Prefer yarn over npm if the project is using yarn.
|
|
40
40
|
If there is a lock file, the project maybe using yarn.
|
|
41
|
-
Look for *.lock if there is a lock type file
|
|
41
|
+
Look for *.lock if there is a lock type file present.
|
|
42
42
|
Always response with a function call.
|
|
43
43
|
"""
|
|
44
44
|
max_iter = 10
|
|
@@ -50,7 +50,7 @@ Always response with a function call.
|
|
|
50
50
|
None,
|
|
51
51
|
description="""
|
|
52
52
|
A Service is designed to run always and should never terminate.
|
|
53
|
-
A Job is
|
|
53
|
+
A Job is designed to finish after sometime.
|
|
54
54
|
""",
|
|
55
55
|
)
|
|
56
56
|
primary_programming_language: str = Field(
|
|
@@ -29,7 +29,7 @@ If you find any port number in the logs. Re run the docker image by exposing tha
|
|
|
29
29
|
|
|
30
30
|
If the image is not running a service, try to identify whether there is any issue.
|
|
31
31
|
Your goal is not to fix the issue. Your goal is to create a very detailed justification and report whether
|
|
32
|
-
the testing was
|
|
32
|
+
the testing was successful.
|
|
33
33
|
Always response with a function call.
|
|
34
34
|
Return response once you are done testing.
|
|
35
35
|
"""
|
truefoundry/autodeploy/cli.py
CHANGED
|
@@ -292,7 +292,7 @@ def cli(project_root_path: str, deploy: bool, workspace_fqn: str = None):
|
|
|
292
292
|
)
|
|
293
293
|
if not re.match(r"^[a-z][a-z0-9\-]{1,30}[a-z0-9]$", name):
|
|
294
294
|
console.print(
|
|
295
|
-
"[bold magenta]TrueFoundry:[/] The name should be between 2-30
|
|
295
|
+
"[bold magenta]TrueFoundry:[/] The name should be between 2-30 alphanumeric"
|
|
296
296
|
" characters and '-'. The first character should not be a digit."
|
|
297
297
|
)
|
|
298
298
|
else:
|
|
@@ -20,7 +20,7 @@ from truefoundry.autodeploy.tools.base import (
|
|
|
20
20
|
class ListFiles(Tool):
|
|
21
21
|
description = """
|
|
22
22
|
List all files.
|
|
23
|
-
If you want to find all json files
|
|
23
|
+
If you want to find all json files recursively under directory a/b
|
|
24
24
|
the subdir should be a/b and pattern will be *.json
|
|
25
25
|
|
|
26
26
|
If you want to find all json files recuresively under current directory
|
truefoundry/cli/__main__.py
CHANGED
|
@@ -1,21 +1,9 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
|
|
3
|
-
import click
|
|
3
|
+
import rich_click as click
|
|
4
4
|
|
|
5
5
|
from truefoundry.deploy.cli.cli import create_truefoundry_cli
|
|
6
|
-
|
|
7
|
-
MLFOUNDRY_INSTALLED = True
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
try:
|
|
11
|
-
from mlfoundry.cli.commands import download
|
|
12
|
-
except ImportError:
|
|
13
|
-
MLFOUNDRY_INSTALLED = False
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@click.group()
|
|
17
|
-
def ml():
|
|
18
|
-
"""MlFoundry CLI"""
|
|
6
|
+
from truefoundry.ml.cli.cli import get_ml_cli
|
|
19
7
|
|
|
20
8
|
|
|
21
9
|
def main():
|
|
@@ -25,9 +13,7 @@ def main():
|
|
|
25
13
|
# If it is another kind of object, it will be printed and the system exit status will be one (i.e., failure).
|
|
26
14
|
try:
|
|
27
15
|
cli = create_truefoundry_cli()
|
|
28
|
-
|
|
29
|
-
ml.add_command(download)
|
|
30
|
-
cli.add_command(ml)
|
|
16
|
+
cli.add_command(get_ml_cli())
|
|
31
17
|
except Exception as e:
|
|
32
18
|
raise click.exceptions.UsageError(message=str(e)) from e
|
|
33
19
|
sys.exit(cli())
|
|
File without changes
|
|
@@ -1,40 +1,39 @@
|
|
|
1
1
|
import time
|
|
2
2
|
from abc import ABC, abstractmethod
|
|
3
|
+
from typing import Optional
|
|
3
4
|
|
|
4
5
|
import requests
|
|
5
6
|
|
|
6
|
-
from truefoundry.
|
|
7
|
-
from truefoundry.
|
|
8
|
-
from truefoundry.
|
|
9
|
-
from truefoundry.
|
|
7
|
+
from truefoundry.common.constants import VERSION_PREFIX
|
|
8
|
+
from truefoundry.common.entities import DeviceCode, Token
|
|
9
|
+
from truefoundry.common.exceptions import BadRequestException
|
|
10
|
+
from truefoundry.common.request_utils import request_handling, requests_retry_session
|
|
11
|
+
from truefoundry.common.utils import poll_for_function
|
|
10
12
|
from truefoundry.logger import logger
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
class AuthServiceClient(ABC):
|
|
14
|
-
def __init__(self,
|
|
15
|
-
|
|
16
|
-
ServiceFoundryServiceClient,
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
client = ServiceFoundryServiceClient(init_session=False, base_url=base_url)
|
|
20
|
-
|
|
21
|
-
self._api_server_url = client._api_server_url
|
|
22
|
-
self._auth_server_url = client.tenant_info.auth_server_url
|
|
23
|
-
self._tenant_name = client.tenant_info.tenant_name
|
|
16
|
+
def __init__(self, tenant_name: str):
|
|
17
|
+
self._tenant_name = tenant_name
|
|
24
18
|
|
|
25
19
|
@classmethod
|
|
26
20
|
def from_base_url(cls, base_url: str) -> "AuthServiceClient":
|
|
27
|
-
from truefoundry.
|
|
21
|
+
from truefoundry.common.servicefoundry_client import (
|
|
28
22
|
ServiceFoundryServiceClient,
|
|
29
23
|
)
|
|
30
24
|
|
|
31
|
-
client = ServiceFoundryServiceClient(
|
|
25
|
+
client = ServiceFoundryServiceClient(base_url=base_url)
|
|
32
26
|
if client.python_sdk_config.use_sfy_server_auth_apis:
|
|
33
|
-
return ServiceFoundryServerAuthServiceClient(
|
|
34
|
-
|
|
27
|
+
return ServiceFoundryServerAuthServiceClient(
|
|
28
|
+
tenant_name=client.tenant_info.tenant_name, url=client._api_server_url
|
|
29
|
+
)
|
|
30
|
+
return AuthServerServiceClient(
|
|
31
|
+
tenant_name=client.tenant_info.tenant_name,
|
|
32
|
+
url=client.tenant_info.auth_server_url,
|
|
33
|
+
)
|
|
35
34
|
|
|
36
35
|
@abstractmethod
|
|
37
|
-
def refresh_token(self, token: Token, host: str = None) -> Token: ...
|
|
36
|
+
def refresh_token(self, token: Token, host: Optional[str] = None) -> Token: ...
|
|
38
37
|
|
|
39
38
|
@abstractmethod
|
|
40
39
|
def get_device_code(self) -> DeviceCode: ...
|
|
@@ -46,10 +45,11 @@ class AuthServiceClient(ABC):
|
|
|
46
45
|
|
|
47
46
|
|
|
48
47
|
class ServiceFoundryServerAuthServiceClient(AuthServiceClient):
|
|
49
|
-
def __init__(self,
|
|
50
|
-
super().__init__(
|
|
48
|
+
def __init__(self, tenant_name: str, url: str):
|
|
49
|
+
super().__init__(tenant_name=tenant_name)
|
|
50
|
+
self._api_server_url = url
|
|
51
51
|
|
|
52
|
-
def refresh_token(self, token: Token, host: str = None) -> Token:
|
|
52
|
+
def refresh_token(self, token: Token, host: Optional[str] = None) -> Token:
|
|
53
53
|
host_arg_str = f"--host {host}" if host else "--host HOST"
|
|
54
54
|
if not token.refresh_token:
|
|
55
55
|
# TODO: Add a way to propagate error messages without traceback to the output interface side
|
|
@@ -63,10 +63,11 @@ class ServiceFoundryServerAuthServiceClient(AuthServiceClient):
|
|
|
63
63
|
"grantType": "refresh_token",
|
|
64
64
|
"returnJWT": True,
|
|
65
65
|
}
|
|
66
|
-
|
|
66
|
+
session = requests_retry_session(retries=2)
|
|
67
|
+
response = session.post(url, json=data)
|
|
67
68
|
try:
|
|
68
|
-
|
|
69
|
-
return Token.parse_obj(
|
|
69
|
+
response_data = request_handling(response)
|
|
70
|
+
return Token.parse_obj(response_data)
|
|
70
71
|
except BadRequestException as ex:
|
|
71
72
|
raise Exception(
|
|
72
73
|
f"Unable to resume login session. Please log in again using `tfy login {host_arg_str} --relogin`"
|
|
@@ -75,9 +76,10 @@ class ServiceFoundryServerAuthServiceClient(AuthServiceClient):
|
|
|
75
76
|
def get_device_code(self) -> DeviceCode:
|
|
76
77
|
url = f"{self._api_server_url}/{VERSION_PREFIX}/oauth2/device-authorize"
|
|
77
78
|
data = {"tenantName": self._tenant_name}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
session = requests_retry_session(retries=2)
|
|
80
|
+
response = session.post(url, json=data)
|
|
81
|
+
response_data = request_handling(response)
|
|
82
|
+
return DeviceCode.parse_obj(response_data)
|
|
81
83
|
|
|
82
84
|
def get_token_from_device_code(
|
|
83
85
|
self, device_code: str, timeout: float = 60, poll_interval_seconds: int = 1
|
|
@@ -91,7 +93,6 @@ class ServiceFoundryServerAuthServiceClient(AuthServiceClient):
|
|
|
91
93
|
"grantType": "device_code",
|
|
92
94
|
"returnJWT": True,
|
|
93
95
|
}
|
|
94
|
-
response = requests.post(url=url, json=data)
|
|
95
96
|
start_time = time.monotonic()
|
|
96
97
|
|
|
97
98
|
for response in poll_for_function(
|
|
@@ -116,10 +117,12 @@ class ServiceFoundryServerAuthServiceClient(AuthServiceClient):
|
|
|
116
117
|
|
|
117
118
|
|
|
118
119
|
class AuthServerServiceClient(AuthServiceClient):
|
|
119
|
-
def __init__(self,
|
|
120
|
-
super().__init__(
|
|
120
|
+
def __init__(self, tenant_name: str, url: str):
|
|
121
|
+
super().__init__(tenant_name=tenant_name)
|
|
122
|
+
|
|
123
|
+
self._auth_server_url = url
|
|
121
124
|
|
|
122
|
-
def refresh_token(self, token: Token, host: str = None) -> Token:
|
|
125
|
+
def refresh_token(self, token: Token, host: Optional[str] = None) -> Token:
|
|
123
126
|
host_arg_str = f"--host {host}" if host else "--host HOST"
|
|
124
127
|
if not token.refresh_token:
|
|
125
128
|
# TODO: Add a way to propagate error messages without traceback to the output interface side
|
|
@@ -131,10 +134,11 @@ class AuthServerServiceClient(AuthServiceClient):
|
|
|
131
134
|
"tenantName": token.tenant_name,
|
|
132
135
|
"refreshToken": token.refresh_token,
|
|
133
136
|
}
|
|
134
|
-
|
|
137
|
+
session = requests_retry_session(retries=2)
|
|
138
|
+
response = session.post(url, json=data)
|
|
135
139
|
try:
|
|
136
|
-
|
|
137
|
-
return Token.parse_obj(
|
|
140
|
+
response_data = request_handling(response)
|
|
141
|
+
return Token.parse_obj(response_data)
|
|
138
142
|
except BadRequestException as ex:
|
|
139
143
|
raise Exception(
|
|
140
144
|
f"Unable to resume login session. Please log in again using `tfy login {host_arg_str} --relogin`"
|
|
@@ -143,22 +147,28 @@ class AuthServerServiceClient(AuthServiceClient):
|
|
|
143
147
|
def get_device_code(self) -> DeviceCode:
|
|
144
148
|
url = f"{self._auth_server_url}/api/{VERSION_PREFIX}/oauth/device"
|
|
145
149
|
data = {"tenantName": self._tenant_name}
|
|
146
|
-
|
|
147
|
-
|
|
150
|
+
session = requests_retry_session(retries=2)
|
|
151
|
+
response = session.post(url, json=data)
|
|
152
|
+
response_data = request_handling(response)
|
|
148
153
|
# TODO: temporary cleanup of incorrect attributes
|
|
149
|
-
|
|
150
|
-
|
|
154
|
+
return DeviceCode.parse_obj(
|
|
155
|
+
{
|
|
156
|
+
"userCode": response_data.get("userCode"),
|
|
157
|
+
"deviceCode": response_data.get("deviceCode"),
|
|
158
|
+
}
|
|
159
|
+
)
|
|
151
160
|
|
|
152
161
|
def get_token_from_device_code(
|
|
153
162
|
self, device_code: str, timeout: float = 60, poll_interval_seconds: int = 1
|
|
154
163
|
) -> Token:
|
|
164
|
+
timeout = timeout or 60
|
|
165
|
+
poll_interval_seconds = poll_interval_seconds or 1
|
|
155
166
|
url = f"{self._auth_server_url}/api/{VERSION_PREFIX}/oauth/device/token"
|
|
156
167
|
data = {
|
|
157
168
|
"tenantName": self._tenant_name,
|
|
158
169
|
"deviceCode": device_code,
|
|
159
170
|
}
|
|
160
171
|
start_time = time.monotonic()
|
|
161
|
-
poll_interval_seconds = 1
|
|
162
172
|
|
|
163
173
|
for response in poll_for_function(
|
|
164
174
|
requests.post, poll_after_secs=poll_interval_seconds, url=url, json=data
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
TFY_CONFIG_DIR = Path.home() / ".truefoundry"
|
|
4
|
+
CREDENTIAL_FILEPATH = TFY_CONFIG_DIR / "credentials.json"
|
|
5
|
+
|
|
6
|
+
TFY_HOST_ENV_KEY = "TFY_HOST"
|
|
7
|
+
TFY_API_KEY_ENV_KEY = "TFY_API_KEY"
|
|
8
|
+
SERVICEFOUNDRY_SERVER_URL_ENV_KEY = "SERVICEFOUNDRY_SERVER_URL"
|
|
9
|
+
|
|
10
|
+
API_SERVER_RELATIVE_PATH = "api/svc"
|
|
11
|
+
VERSION_PREFIX = "v1"
|
|
12
|
+
SERVICEFOUNDRY_CLIENT_MAX_RETRIES = 2
|
|
@@ -8,14 +8,14 @@ from typing import Optional
|
|
|
8
8
|
|
|
9
9
|
from filelock import FileLock, Timeout
|
|
10
10
|
|
|
11
|
-
from truefoundry.
|
|
12
|
-
from truefoundry.
|
|
11
|
+
from truefoundry.common.constants import CREDENTIAL_FILEPATH
|
|
12
|
+
from truefoundry.common.entities import CredentialsFileContent
|
|
13
13
|
from truefoundry.logger import logger
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def _ensure_lock_taken(method):
|
|
17
17
|
@wraps(method)
|
|
18
|
-
def lock_guard(self, *method_args, **method_kwargs):
|
|
18
|
+
def lock_guard(self: "CredentialsFileManager", *method_args, **method_kwargs):
|
|
19
19
|
if not self.lock_taken():
|
|
20
20
|
raise Exception(
|
|
21
21
|
"Trying to write to credential file without using with block"
|
|
@@ -55,17 +55,17 @@ class CredentialsFileManager:
|
|
|
55
55
|
|
|
56
56
|
def __enter__(self) -> CredentialsFileManager:
|
|
57
57
|
# The lock objects are recursive locks, which means that once acquired, they will not block on successive lock requests:
|
|
58
|
-
|
|
59
|
-
if not
|
|
58
|
+
lock_acquired = CRED_FILE_THREAD_LOCK.acquire(timeout=self._lock_timeout)
|
|
59
|
+
if not lock_acquired:
|
|
60
60
|
raise Exception(
|
|
61
|
-
"Could not
|
|
61
|
+
"Could not acquire CRED_FILE_THREAD_LOCK"
|
|
62
62
|
f" in {self._lock_timeout} seconds"
|
|
63
63
|
)
|
|
64
64
|
try:
|
|
65
65
|
self._file_lock.acquire(timeout=self._lock_timeout)
|
|
66
66
|
except Timeout as ex:
|
|
67
67
|
raise Exception(
|
|
68
|
-
f"Failed to
|
|
68
|
+
f"Failed to acquire lock on credential file within {self._lock_timeout} seconds.\n"
|
|
69
69
|
"Is any other process trying to login?"
|
|
70
70
|
) from ex
|
|
71
71
|
logger.debug("Acquired file and thread lock to access credential file")
|
|
@@ -2,14 +2,11 @@ import os
|
|
|
2
2
|
import threading
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
4
|
|
|
5
|
-
from truefoundry.
|
|
6
|
-
from truefoundry.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
from truefoundry.deploy.lib.clients.utils import resolve_base_url
|
|
11
|
-
from truefoundry.deploy.lib.const import API_KEY_ENV_NAME
|
|
12
|
-
from truefoundry.deploy.lib.model.entity import Token
|
|
5
|
+
from truefoundry.common.auth_service_client import AuthServiceClient
|
|
6
|
+
from truefoundry.common.constants import TFY_API_KEY_ENV_KEY
|
|
7
|
+
from truefoundry.common.credential_file_manager import CredentialsFileManager
|
|
8
|
+
from truefoundry.common.entities import CredentialsFileContent, Token
|
|
9
|
+
from truefoundry.common.utils import resolve_base_url
|
|
13
10
|
from truefoundry.logger import logger
|
|
14
11
|
|
|
15
12
|
TOKEN_REFRESH_LOCK = threading.RLock()
|
|
@@ -31,31 +28,21 @@ class CredentialProvider(ABC):
|
|
|
31
28
|
|
|
32
29
|
class EnvCredentialProvider(CredentialProvider):
|
|
33
30
|
def __init__(self) -> None:
|
|
34
|
-
from truefoundry.deploy.lib.clients.servicefoundry_client import (
|
|
35
|
-
ServiceFoundryServiceClient,
|
|
36
|
-
)
|
|
37
|
-
|
|
38
31
|
logger.debug("Using env var credential provider")
|
|
39
|
-
api_key = os.getenv(
|
|
32
|
+
api_key = os.getenv(TFY_API_KEY_ENV_KEY)
|
|
40
33
|
if not api_key:
|
|
41
34
|
raise Exception(
|
|
42
|
-
f"Value of {
|
|
35
|
+
f"Value of {TFY_API_KEY_ENV_KEY} env var should be non-empty string"
|
|
43
36
|
)
|
|
44
37
|
# TODO: Read host from cred file as well.
|
|
45
38
|
base_url = resolve_base_url().strip("/")
|
|
46
39
|
self._host = base_url
|
|
47
40
|
self._auth_service = AuthServiceClient.from_base_url(base_url=base_url)
|
|
48
|
-
|
|
49
|
-
servicefoundry_client = ServiceFoundryServiceClient(
|
|
50
|
-
init_session=False, base_url=base_url
|
|
51
|
-
)
|
|
52
|
-
self._token: Token = servicefoundry_client.get_token_from_api_key(
|
|
53
|
-
api_key=api_key
|
|
54
|
-
)
|
|
41
|
+
self._token: Token = Token(access_token=api_key, refresh_token=None) # type: ignore[call-arg]
|
|
55
42
|
|
|
56
43
|
@staticmethod
|
|
57
44
|
def can_provide() -> bool:
|
|
58
|
-
return
|
|
45
|
+
return TFY_API_KEY_ENV_KEY in os.environ
|
|
59
46
|
|
|
60
47
|
@property
|
|
61
48
|
def token(self) -> Token:
|
|
@@ -123,7 +110,7 @@ class FileCredentialProvider(CredentialProvider):
|
|
|
123
110
|
return self.token
|
|
124
111
|
|
|
125
112
|
raise Exception(
|
|
126
|
-
"Credentials on disk changed while
|
|
113
|
+
"Credentials on disk changed while truefoundry was running."
|
|
127
114
|
)
|
|
128
115
|
|
|
129
116
|
@property
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import jwt
|
|
6
|
+
from typing_extensions import NotRequired, TypedDict
|
|
7
|
+
|
|
8
|
+
from truefoundry.pydantic_v1 import BaseModel, Field, NonEmptyStr, validator
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class UserType(Enum):
|
|
12
|
+
user = "user"
|
|
13
|
+
serviceaccount = "serviceaccount"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class UserInfo(BaseModel):
|
|
17
|
+
class Config:
|
|
18
|
+
allow_population_by_field_name = True
|
|
19
|
+
allow_mutation = False
|
|
20
|
+
|
|
21
|
+
user_id: NonEmptyStr
|
|
22
|
+
user_type: UserType = UserType.user
|
|
23
|
+
email: Optional[str] = None
|
|
24
|
+
tenant_name: NonEmptyStr = Field(alias="tenantName")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class _DecodedToken(TypedDict):
|
|
28
|
+
tenantName: str
|
|
29
|
+
exp: int
|
|
30
|
+
username: str
|
|
31
|
+
email: NotRequired[str]
|
|
32
|
+
userType: UserType
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Token(BaseModel):
|
|
36
|
+
access_token: NonEmptyStr = Field(alias="accessToken", repr=False)
|
|
37
|
+
refresh_token: Optional[NonEmptyStr] = Field(alias="refreshToken", repr=False)
|
|
38
|
+
decoded_value: Optional[_DecodedToken] = Field(exclude=True, repr=False)
|
|
39
|
+
|
|
40
|
+
class Config:
|
|
41
|
+
allow_population_by_field_name = True
|
|
42
|
+
allow_mutation = False
|
|
43
|
+
|
|
44
|
+
@validator("decoded_value", always=True, pre=True)
|
|
45
|
+
def _decode_jwt(cls, v, values, **kwargs) -> _DecodedToken:
|
|
46
|
+
access_token = values["access_token"]
|
|
47
|
+
return jwt.decode(
|
|
48
|
+
access_token,
|
|
49
|
+
options={
|
|
50
|
+
"verify_signature": False,
|
|
51
|
+
"verify_aud": False,
|
|
52
|
+
"verify_exp": False,
|
|
53
|
+
},
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def tenant_name(self) -> str:
|
|
58
|
+
return self.decoded_value["tenantName"]
|
|
59
|
+
|
|
60
|
+
def is_going_to_be_expired(self, buffer_in_seconds: int = 120) -> bool:
|
|
61
|
+
exp = int(self.decoded_value["exp"])
|
|
62
|
+
return (exp - time.time()) < buffer_in_seconds
|
|
63
|
+
|
|
64
|
+
def to_user_info(self) -> UserInfo:
|
|
65
|
+
return UserInfo( # type: ignore[call-arg]
|
|
66
|
+
user_id=self.decoded_value["username"],
|
|
67
|
+
email=self.decoded_value["email"]
|
|
68
|
+
if "email" in self.decoded_value
|
|
69
|
+
else None,
|
|
70
|
+
user_type=UserType(self.decoded_value.get("userType", UserType.user.value)),
|
|
71
|
+
tenant_name=self.tenant_name,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class CredentialsFileContent(BaseModel):
|
|
76
|
+
class Config:
|
|
77
|
+
allow_mutation = False
|
|
78
|
+
|
|
79
|
+
access_token: NonEmptyStr = Field(repr=False)
|
|
80
|
+
refresh_token: Optional[NonEmptyStr] = Field(repr=False)
|
|
81
|
+
host: NonEmptyStr
|
|
82
|
+
|
|
83
|
+
def to_token(self) -> Token:
|
|
84
|
+
return Token( # type: ignore[call-arg]
|
|
85
|
+
access_token=self.access_token, refresh_token=self.refresh_token
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class TenantInfo(BaseModel):
|
|
90
|
+
class Config:
|
|
91
|
+
allow_population_by_field_name = True
|
|
92
|
+
allow_mutation = False
|
|
93
|
+
|
|
94
|
+
tenant_name: NonEmptyStr = Field(alias="tenantName")
|
|
95
|
+
auth_server_url: str
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class PythonSDKConfig(BaseModel):
|
|
99
|
+
class Config:
|
|
100
|
+
allow_population_by_field_name = True
|
|
101
|
+
allow_mutation = False
|
|
102
|
+
|
|
103
|
+
min_version: str = Field(alias="minVersion")
|
|
104
|
+
truefoundry_cli_min_version: str = Field(alias="truefoundryCliMinVersion")
|
|
105
|
+
use_sfy_server_auth_apis: Optional[bool] = Field(
|
|
106
|
+
alias="useSFYServerAuthAPIs", default=False
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class DeviceCode(BaseModel):
|
|
111
|
+
class Config:
|
|
112
|
+
allow_population_by_field_name = True
|
|
113
|
+
allow_mutation = False
|
|
114
|
+
|
|
115
|
+
user_code: str = Field(alias="userCode")
|
|
116
|
+
device_code: str = Field(alias="deviceCode")
|
|
117
|
+
verification_url: Optional[str] = Field(alias="verificationURI")
|
|
118
|
+
complete_verification_url: Optional[str] = Field(alias="verificationURIComplete")
|
|
119
|
+
expires_in_seconds: int = Field(alias="expiresInSeconds", default=60)
|
|
120
|
+
interval_in_seconds: int = Field(alias="intervalInSeconds", default=1)
|
|
121
|
+
message: Optional[str] = Field(alias="message")
|
|
122
|
+
|
|
123
|
+
def get_user_clickable_url(self, auth_host: str) -> str:
|
|
124
|
+
return f"{auth_host}/authorize/device?userCode={self.user_code}"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BadRequestException(Exception):
|
|
5
|
+
def __init__(self, status_code: int, message: Optional[str] = None):
|
|
6
|
+
super().__init__()
|
|
7
|
+
self.status_code = status_code
|
|
8
|
+
self.message = message
|
|
9
|
+
self.status_code = status_code
|
|
10
|
+
|
|
11
|
+
def __str__(self):
|
|
12
|
+
return self.message
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
from requests import Response
|
|
5
|
+
from requests.adapters import HTTPAdapter
|
|
6
|
+
from urllib3 import Retry
|
|
7
|
+
|
|
8
|
+
from truefoundry.common.exceptions import BadRequestException
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def request_handling(response: Response):
|
|
12
|
+
try:
|
|
13
|
+
status_code = response.status_code
|
|
14
|
+
except Exception as e:
|
|
15
|
+
raise Exception("Unknown error occurred. Couldn't get status code.") from e
|
|
16
|
+
if 200 <= status_code <= 299:
|
|
17
|
+
if response.content == b"":
|
|
18
|
+
return None
|
|
19
|
+
return response.json()
|
|
20
|
+
if 400 <= status_code <= 499:
|
|
21
|
+
try:
|
|
22
|
+
message = str(response.json()["message"])
|
|
23
|
+
except Exception:
|
|
24
|
+
message = response.text
|
|
25
|
+
raise BadRequestException(status_code=response.status_code, message=message)
|
|
26
|
+
if 500 <= status_code <= 599:
|
|
27
|
+
raise Exception(response.content)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def urllib3_retry(
|
|
31
|
+
retries: int = 2,
|
|
32
|
+
backoff_factor: float = 0.3,
|
|
33
|
+
status_forcelist=(408, 429, 500, 502, 503, 504, 524),
|
|
34
|
+
method_whitelist=frozenset({"GET", "POST"}),
|
|
35
|
+
raise_on_status: bool = False,
|
|
36
|
+
):
|
|
37
|
+
retry = Retry(
|
|
38
|
+
total=retries,
|
|
39
|
+
read=retries,
|
|
40
|
+
connect=retries,
|
|
41
|
+
status=retries,
|
|
42
|
+
backoff_factor=backoff_factor,
|
|
43
|
+
allowed_methods=method_whitelist,
|
|
44
|
+
status_forcelist=status_forcelist,
|
|
45
|
+
respect_retry_after_header=True,
|
|
46
|
+
raise_on_status=raise_on_status,
|
|
47
|
+
)
|
|
48
|
+
return retry
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def requests_retry_session(
|
|
52
|
+
retries: int = 2,
|
|
53
|
+
backoff_factor: float = 0.3,
|
|
54
|
+
status_forcelist=(408, 429, 500, 502, 503, 504, 524),
|
|
55
|
+
method_whitelist=frozenset({"GET", "POST"}),
|
|
56
|
+
raise_on_status: bool = False,
|
|
57
|
+
session: Optional[requests.Session] = None,
|
|
58
|
+
) -> requests.Session:
|
|
59
|
+
"""
|
|
60
|
+
Returns a `requests` session with retry capabilities for certain HTTP status codes.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
retries (int): The number of retries for HTTP requests.
|
|
64
|
+
backoff_factor (float): The backoff factor for exponential backoff during retries.
|
|
65
|
+
status_forcelist (tuple): A tuple of HTTP status codes that should trigger a retry.
|
|
66
|
+
method_whitelist (frozenset): The set of HTTP methods that should be retried.
|
|
67
|
+
session (requests.Session, optional): An optional existing requests session to use.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
requests.Session: A session with retry capabilities.
|
|
71
|
+
"""
|
|
72
|
+
# Implementation taken from https://www.peterbe.com/plog/best-practice-with-retries-with-requests
|
|
73
|
+
session = session or requests.Session()
|
|
74
|
+
retry = urllib3_retry(
|
|
75
|
+
retries=retries,
|
|
76
|
+
backoff_factor=backoff_factor,
|
|
77
|
+
status_forcelist=status_forcelist,
|
|
78
|
+
method_whitelist=method_whitelist,
|
|
79
|
+
raise_on_status=raise_on_status,
|
|
80
|
+
)
|
|
81
|
+
adapter = HTTPAdapter(max_retries=retry)
|
|
82
|
+
session.mount("http://", adapter)
|
|
83
|
+
session.mount("https://", adapter)
|
|
84
|
+
return session
|