flyte 0.0.1b0__py3-none-any.whl → 2.0.0b46__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.
- flyte/__init__.py +83 -30
- flyte/_bin/connect.py +61 -0
- flyte/_bin/debug.py +38 -0
- flyte/_bin/runtime.py +87 -19
- flyte/_bin/serve.py +351 -0
- flyte/_build.py +3 -2
- flyte/_cache/cache.py +6 -5
- flyte/_cache/local_cache.py +216 -0
- flyte/_code_bundle/_ignore.py +31 -5
- flyte/_code_bundle/_packaging.py +42 -11
- flyte/_code_bundle/_utils.py +57 -34
- flyte/_code_bundle/bundle.py +130 -27
- flyte/_constants.py +1 -0
- flyte/_context.py +21 -5
- flyte/_custom_context.py +73 -0
- flyte/_debug/constants.py +37 -0
- flyte/_debug/utils.py +17 -0
- flyte/_debug/vscode.py +315 -0
- flyte/_deploy.py +396 -75
- flyte/_deployer.py +109 -0
- flyte/_environment.py +94 -11
- flyte/_excepthook.py +37 -0
- flyte/_group.py +2 -1
- flyte/_hash.py +1 -16
- flyte/_image.py +544 -234
- flyte/_initialize.py +443 -294
- flyte/_interface.py +40 -5
- flyte/_internal/controllers/__init__.py +22 -8
- flyte/_internal/controllers/_local_controller.py +159 -35
- flyte/_internal/controllers/_trace.py +18 -10
- flyte/_internal/controllers/remote/__init__.py +38 -9
- flyte/_internal/controllers/remote/_action.py +82 -12
- flyte/_internal/controllers/remote/_client.py +6 -2
- flyte/_internal/controllers/remote/_controller.py +290 -64
- flyte/_internal/controllers/remote/_core.py +155 -95
- flyte/_internal/controllers/remote/_informer.py +40 -20
- flyte/_internal/controllers/remote/_service_protocol.py +2 -2
- flyte/_internal/imagebuild/__init__.py +2 -10
- flyte/_internal/imagebuild/docker_builder.py +391 -84
- flyte/_internal/imagebuild/image_builder.py +111 -55
- flyte/_internal/imagebuild/remote_builder.py +409 -0
- flyte/_internal/imagebuild/utils.py +79 -0
- flyte/_internal/resolvers/_app_env_module.py +92 -0
- flyte/_internal/resolvers/_task_module.py +5 -38
- flyte/_internal/resolvers/app_env.py +26 -0
- flyte/_internal/resolvers/common.py +8 -1
- flyte/_internal/resolvers/default.py +2 -2
- flyte/_internal/runtime/convert.py +322 -33
- flyte/_internal/runtime/entrypoints.py +106 -18
- flyte/_internal/runtime/io.py +71 -23
- flyte/_internal/runtime/resources_serde.py +21 -7
- flyte/_internal/runtime/reuse.py +125 -0
- flyte/_internal/runtime/rusty.py +196 -0
- flyte/_internal/runtime/task_serde.py +239 -66
- flyte/_internal/runtime/taskrunner.py +48 -8
- flyte/_internal/runtime/trigger_serde.py +162 -0
- flyte/_internal/runtime/types_serde.py +7 -16
- flyte/_keyring/file.py +115 -0
- flyte/_link.py +30 -0
- flyte/_logging.py +241 -42
- flyte/_map.py +312 -0
- flyte/_metrics.py +59 -0
- flyte/_module.py +74 -0
- flyte/_pod.py +30 -0
- flyte/_resources.py +296 -33
- flyte/_retry.py +1 -7
- flyte/_reusable_environment.py +72 -7
- flyte/_run.py +461 -132
- flyte/_secret.py +47 -11
- flyte/_serve.py +333 -0
- flyte/_task.py +245 -56
- flyte/_task_environment.py +219 -97
- flyte/_task_plugins.py +47 -0
- flyte/_tools.py +8 -8
- flyte/_trace.py +15 -24
- flyte/_trigger.py +1027 -0
- flyte/_utils/__init__.py +12 -1
- flyte/_utils/asyn.py +3 -1
- flyte/_utils/async_cache.py +139 -0
- flyte/_utils/coro_management.py +5 -4
- flyte/_utils/description_parser.py +19 -0
- flyte/_utils/docker_credentials.py +173 -0
- flyte/_utils/helpers.py +45 -19
- flyte/_utils/module_loader.py +123 -0
- flyte/_utils/org_discovery.py +57 -0
- flyte/_utils/uv_script_parser.py +8 -1
- flyte/_version.py +16 -3
- flyte/app/__init__.py +27 -0
- flyte/app/_app_environment.py +362 -0
- flyte/app/_connector_environment.py +40 -0
- flyte/app/_deploy.py +130 -0
- flyte/app/_parameter.py +343 -0
- flyte/app/_runtime/__init__.py +3 -0
- flyte/app/_runtime/app_serde.py +383 -0
- flyte/app/_types.py +113 -0
- flyte/app/extras/__init__.py +9 -0
- flyte/app/extras/_auth_middleware.py +217 -0
- flyte/app/extras/_fastapi.py +93 -0
- flyte/app/extras/_model_loader/__init__.py +3 -0
- flyte/app/extras/_model_loader/config.py +7 -0
- flyte/app/extras/_model_loader/loader.py +288 -0
- flyte/cli/__init__.py +12 -0
- flyte/cli/_abort.py +28 -0
- flyte/cli/_build.py +114 -0
- flyte/cli/_common.py +493 -0
- flyte/cli/_create.py +371 -0
- flyte/cli/_delete.py +45 -0
- flyte/cli/_deploy.py +401 -0
- flyte/cli/_gen.py +316 -0
- flyte/cli/_get.py +446 -0
- flyte/cli/_option.py +33 -0
- {union/_cli → flyte/cli}/_params.py +152 -153
- flyte/cli/_plugins.py +209 -0
- flyte/cli/_prefetch.py +292 -0
- flyte/cli/_run.py +690 -0
- flyte/cli/_serve.py +338 -0
- flyte/cli/_update.py +86 -0
- flyte/cli/_user.py +20 -0
- flyte/cli/main.py +246 -0
- flyte/config/__init__.py +3 -0
- flyte/config/_config.py +248 -0
- flyte/config/_internal.py +73 -0
- flyte/config/_reader.py +225 -0
- flyte/connectors/__init__.py +11 -0
- flyte/connectors/_connector.py +330 -0
- flyte/connectors/_server.py +194 -0
- flyte/connectors/utils.py +159 -0
- flyte/errors.py +134 -2
- flyte/extend.py +24 -0
- flyte/extras/_container.py +69 -56
- flyte/git/__init__.py +3 -0
- flyte/git/_config.py +279 -0
- flyte/io/__init__.py +8 -1
- flyte/io/{structured_dataset → _dataframe}/__init__.py +32 -30
- flyte/io/{structured_dataset → _dataframe}/basic_dfs.py +75 -68
- flyte/io/{structured_dataset/structured_dataset.py → _dataframe/dataframe.py} +207 -242
- flyte/io/_dir.py +575 -113
- flyte/io/_file.py +587 -141
- flyte/io/_hashing_io.py +342 -0
- flyte/io/extend.py +7 -0
- flyte/models.py +635 -0
- flyte/prefetch/__init__.py +22 -0
- flyte/prefetch/_hf_model.py +563 -0
- flyte/remote/__init__.py +14 -3
- flyte/remote/_action.py +879 -0
- flyte/remote/_app.py +346 -0
- flyte/remote/_auth_metadata.py +42 -0
- flyte/remote/_client/_protocols.py +62 -4
- flyte/remote/_client/auth/_auth_utils.py +19 -0
- flyte/remote/_client/auth/_authenticators/base.py +8 -2
- flyte/remote/_client/auth/_authenticators/device_code.py +4 -5
- flyte/remote/_client/auth/_authenticators/factory.py +4 -0
- flyte/remote/_client/auth/_authenticators/passthrough.py +79 -0
- flyte/remote/_client/auth/_authenticators/pkce.py +17 -18
- flyte/remote/_client/auth/_channel.py +47 -18
- flyte/remote/_client/auth/_client_config.py +5 -3
- flyte/remote/_client/auth/_keyring.py +15 -2
- flyte/remote/_client/auth/_token_client.py +3 -3
- flyte/remote/_client/controlplane.py +206 -18
- flyte/remote/_common.py +66 -0
- flyte/remote/_data.py +107 -22
- flyte/remote/_logs.py +116 -33
- flyte/remote/_project.py +21 -19
- flyte/remote/_run.py +164 -631
- flyte/remote/_secret.py +72 -29
- flyte/remote/_task.py +387 -46
- flyte/remote/_trigger.py +368 -0
- flyte/remote/_user.py +43 -0
- flyte/report/_report.py +10 -6
- flyte/storage/__init__.py +13 -1
- flyte/storage/_config.py +237 -0
- flyte/storage/_parallel_reader.py +289 -0
- flyte/storage/_storage.py +268 -59
- flyte/syncify/__init__.py +56 -0
- flyte/syncify/_api.py +414 -0
- flyte/types/__init__.py +39 -0
- flyte/types/_interface.py +22 -7
- flyte/{io/pickle/transformer.py → types/_pickle.py} +37 -9
- flyte/types/_string_literals.py +8 -9
- flyte/types/_type_engine.py +230 -129
- flyte/types/_utils.py +1 -1
- flyte-2.0.0b46.data/scripts/debug.py +38 -0
- flyte-2.0.0b46.data/scripts/runtime.py +194 -0
- flyte-2.0.0b46.dist-info/METADATA +352 -0
- flyte-2.0.0b46.dist-info/RECORD +221 -0
- flyte-2.0.0b46.dist-info/entry_points.txt +8 -0
- flyte-2.0.0b46.dist-info/licenses/LICENSE +201 -0
- flyte/_api_commons.py +0 -3
- flyte/_cli/_common.py +0 -287
- flyte/_cli/_create.py +0 -42
- flyte/_cli/_delete.py +0 -23
- flyte/_cli/_deploy.py +0 -140
- flyte/_cli/_get.py +0 -235
- flyte/_cli/_run.py +0 -152
- flyte/_cli/main.py +0 -72
- flyte/_datastructures.py +0 -342
- flyte/_internal/controllers/pbhash.py +0 -39
- flyte/_protos/common/authorization_pb2.py +0 -66
- flyte/_protos/common/authorization_pb2.pyi +0 -108
- flyte/_protos/common/authorization_pb2_grpc.py +0 -4
- flyte/_protos/common/identifier_pb2.py +0 -71
- flyte/_protos/common/identifier_pb2.pyi +0 -82
- flyte/_protos/common/identifier_pb2_grpc.py +0 -4
- flyte/_protos/common/identity_pb2.py +0 -48
- flyte/_protos/common/identity_pb2.pyi +0 -72
- flyte/_protos/common/identity_pb2_grpc.py +0 -4
- flyte/_protos/common/list_pb2.py +0 -36
- flyte/_protos/common/list_pb2.pyi +0 -69
- flyte/_protos/common/list_pb2_grpc.py +0 -4
- flyte/_protos/common/policy_pb2.py +0 -37
- flyte/_protos/common/policy_pb2.pyi +0 -27
- flyte/_protos/common/policy_pb2_grpc.py +0 -4
- flyte/_protos/common/role_pb2.py +0 -37
- flyte/_protos/common/role_pb2.pyi +0 -53
- flyte/_protos/common/role_pb2_grpc.py +0 -4
- flyte/_protos/common/runtime_version_pb2.py +0 -28
- flyte/_protos/common/runtime_version_pb2.pyi +0 -24
- flyte/_protos/common/runtime_version_pb2_grpc.py +0 -4
- flyte/_protos/logs/dataplane/payload_pb2.py +0 -96
- flyte/_protos/logs/dataplane/payload_pb2.pyi +0 -168
- flyte/_protos/logs/dataplane/payload_pb2_grpc.py +0 -4
- flyte/_protos/secret/definition_pb2.py +0 -49
- flyte/_protos/secret/definition_pb2.pyi +0 -93
- flyte/_protos/secret/definition_pb2_grpc.py +0 -4
- flyte/_protos/secret/payload_pb2.py +0 -62
- flyte/_protos/secret/payload_pb2.pyi +0 -94
- flyte/_protos/secret/payload_pb2_grpc.py +0 -4
- flyte/_protos/secret/secret_pb2.py +0 -38
- flyte/_protos/secret/secret_pb2.pyi +0 -6
- flyte/_protos/secret/secret_pb2_grpc.py +0 -198
- flyte/_protos/secret/secret_pb2_grpc_grpc.py +0 -198
- flyte/_protos/validate/validate/validate_pb2.py +0 -76
- flyte/_protos/workflow/node_execution_service_pb2.py +0 -26
- flyte/_protos/workflow/node_execution_service_pb2.pyi +0 -4
- flyte/_protos/workflow/node_execution_service_pb2_grpc.py +0 -32
- flyte/_protos/workflow/queue_service_pb2.py +0 -106
- flyte/_protos/workflow/queue_service_pb2.pyi +0 -141
- flyte/_protos/workflow/queue_service_pb2_grpc.py +0 -172
- flyte/_protos/workflow/run_definition_pb2.py +0 -128
- flyte/_protos/workflow/run_definition_pb2.pyi +0 -310
- flyte/_protos/workflow/run_definition_pb2_grpc.py +0 -4
- flyte/_protos/workflow/run_logs_service_pb2.py +0 -41
- flyte/_protos/workflow/run_logs_service_pb2.pyi +0 -28
- flyte/_protos/workflow/run_logs_service_pb2_grpc.py +0 -69
- flyte/_protos/workflow/run_service_pb2.py +0 -133
- flyte/_protos/workflow/run_service_pb2.pyi +0 -175
- flyte/_protos/workflow/run_service_pb2_grpc.py +0 -412
- flyte/_protos/workflow/state_service_pb2.py +0 -58
- flyte/_protos/workflow/state_service_pb2.pyi +0 -71
- flyte/_protos/workflow/state_service_pb2_grpc.py +0 -138
- flyte/_protos/workflow/task_definition_pb2.py +0 -72
- flyte/_protos/workflow/task_definition_pb2.pyi +0 -65
- flyte/_protos/workflow/task_definition_pb2_grpc.py +0 -4
- flyte/_protos/workflow/task_service_pb2.py +0 -44
- flyte/_protos/workflow/task_service_pb2.pyi +0 -31
- flyte/_protos/workflow/task_service_pb2_grpc.py +0 -104
- flyte/io/_dataframe.py +0 -0
- flyte/io/pickle/__init__.py +0 -0
- flyte/remote/_console.py +0 -18
- flyte-0.0.1b0.dist-info/METADATA +0 -179
- flyte-0.0.1b0.dist-info/RECORD +0 -390
- flyte-0.0.1b0.dist-info/entry_points.txt +0 -3
- union/__init__.py +0 -54
- union/_api_commons.py +0 -3
- union/_bin/__init__.py +0 -0
- union/_bin/runtime.py +0 -113
- union/_build.py +0 -25
- union/_cache/__init__.py +0 -12
- union/_cache/cache.py +0 -141
- union/_cache/defaults.py +0 -9
- union/_cache/policy_function_body.py +0 -42
- union/_cli/__init__.py +0 -0
- union/_cli/_common.py +0 -263
- union/_cli/_create.py +0 -40
- union/_cli/_delete.py +0 -23
- union/_cli/_deploy.py +0 -120
- union/_cli/_get.py +0 -162
- union/_cli/_run.py +0 -150
- union/_cli/main.py +0 -72
- union/_code_bundle/__init__.py +0 -8
- union/_code_bundle/_ignore.py +0 -113
- union/_code_bundle/_packaging.py +0 -187
- union/_code_bundle/_utils.py +0 -342
- union/_code_bundle/bundle.py +0 -176
- union/_context.py +0 -146
- union/_datastructures.py +0 -295
- union/_deploy.py +0 -185
- union/_doc.py +0 -29
- union/_docstring.py +0 -26
- union/_environment.py +0 -43
- union/_group.py +0 -31
- union/_hash.py +0 -23
- union/_image.py +0 -760
- union/_initialize.py +0 -585
- union/_interface.py +0 -84
- union/_internal/__init__.py +0 -3
- union/_internal/controllers/__init__.py +0 -77
- union/_internal/controllers/_local_controller.py +0 -77
- union/_internal/controllers/pbhash.py +0 -39
- union/_internal/controllers/remote/__init__.py +0 -40
- union/_internal/controllers/remote/_action.py +0 -131
- union/_internal/controllers/remote/_client.py +0 -43
- union/_internal/controllers/remote/_controller.py +0 -169
- union/_internal/controllers/remote/_core.py +0 -341
- union/_internal/controllers/remote/_informer.py +0 -260
- union/_internal/controllers/remote/_service_protocol.py +0 -44
- union/_internal/imagebuild/__init__.py +0 -11
- union/_internal/imagebuild/docker_builder.py +0 -416
- union/_internal/imagebuild/image_builder.py +0 -243
- union/_internal/imagebuild/remote_builder.py +0 -0
- union/_internal/resolvers/__init__.py +0 -0
- union/_internal/resolvers/_task_module.py +0 -31
- union/_internal/resolvers/common.py +0 -24
- union/_internal/resolvers/default.py +0 -27
- union/_internal/runtime/__init__.py +0 -0
- union/_internal/runtime/convert.py +0 -163
- union/_internal/runtime/entrypoints.py +0 -121
- union/_internal/runtime/io.py +0 -136
- union/_internal/runtime/resources_serde.py +0 -134
- union/_internal/runtime/task_serde.py +0 -202
- union/_internal/runtime/taskrunner.py +0 -179
- union/_internal/runtime/types_serde.py +0 -53
- union/_logging.py +0 -124
- union/_protos/__init__.py +0 -0
- union/_protos/common/authorization_pb2.py +0 -66
- union/_protos/common/authorization_pb2.pyi +0 -106
- union/_protos/common/authorization_pb2_grpc.py +0 -4
- union/_protos/common/identifier_pb2.py +0 -71
- union/_protos/common/identifier_pb2.pyi +0 -82
- union/_protos/common/identifier_pb2_grpc.py +0 -4
- union/_protos/common/identity_pb2.py +0 -48
- union/_protos/common/identity_pb2.pyi +0 -72
- union/_protos/common/identity_pb2_grpc.py +0 -4
- union/_protos/common/list_pb2.py +0 -36
- union/_protos/common/list_pb2.pyi +0 -69
- union/_protos/common/list_pb2_grpc.py +0 -4
- union/_protos/common/policy_pb2.py +0 -37
- union/_protos/common/policy_pb2.pyi +0 -27
- union/_protos/common/policy_pb2_grpc.py +0 -4
- union/_protos/common/role_pb2.py +0 -37
- union/_protos/common/role_pb2.pyi +0 -51
- union/_protos/common/role_pb2_grpc.py +0 -4
- union/_protos/common/runtime_version_pb2.py +0 -28
- union/_protos/common/runtime_version_pb2.pyi +0 -24
- union/_protos/common/runtime_version_pb2_grpc.py +0 -4
- union/_protos/logs/dataplane/payload_pb2.py +0 -96
- union/_protos/logs/dataplane/payload_pb2.pyi +0 -168
- union/_protos/logs/dataplane/payload_pb2_grpc.py +0 -4
- union/_protos/secret/definition_pb2.py +0 -49
- union/_protos/secret/definition_pb2.pyi +0 -93
- union/_protos/secret/definition_pb2_grpc.py +0 -4
- union/_protos/secret/payload_pb2.py +0 -62
- union/_protos/secret/payload_pb2.pyi +0 -94
- union/_protos/secret/payload_pb2_grpc.py +0 -4
- union/_protos/secret/secret_pb2.py +0 -38
- union/_protos/secret/secret_pb2.pyi +0 -6
- union/_protos/secret/secret_pb2_grpc.py +0 -198
- union/_protos/validate/validate/validate_pb2.py +0 -76
- union/_protos/workflow/node_execution_service_pb2.py +0 -26
- union/_protos/workflow/node_execution_service_pb2.pyi +0 -4
- union/_protos/workflow/node_execution_service_pb2_grpc.py +0 -32
- union/_protos/workflow/queue_service_pb2.py +0 -75
- union/_protos/workflow/queue_service_pb2.pyi +0 -103
- union/_protos/workflow/queue_service_pb2_grpc.py +0 -172
- union/_protos/workflow/run_definition_pb2.py +0 -100
- union/_protos/workflow/run_definition_pb2.pyi +0 -256
- union/_protos/workflow/run_definition_pb2_grpc.py +0 -4
- union/_protos/workflow/run_logs_service_pb2.py +0 -41
- union/_protos/workflow/run_logs_service_pb2.pyi +0 -28
- union/_protos/workflow/run_logs_service_pb2_grpc.py +0 -69
- union/_protos/workflow/run_service_pb2.py +0 -133
- union/_protos/workflow/run_service_pb2.pyi +0 -173
- union/_protos/workflow/run_service_pb2_grpc.py +0 -412
- union/_protos/workflow/state_service_pb2.py +0 -58
- union/_protos/workflow/state_service_pb2.pyi +0 -69
- union/_protos/workflow/state_service_pb2_grpc.py +0 -138
- union/_protos/workflow/task_definition_pb2.py +0 -72
- union/_protos/workflow/task_definition_pb2.pyi +0 -65
- union/_protos/workflow/task_definition_pb2_grpc.py +0 -4
- union/_protos/workflow/task_service_pb2.py +0 -44
- union/_protos/workflow/task_service_pb2.pyi +0 -31
- union/_protos/workflow/task_service_pb2_grpc.py +0 -104
- union/_resources.py +0 -226
- union/_retry.py +0 -32
- union/_reusable_environment.py +0 -25
- union/_run.py +0 -374
- union/_secret.py +0 -61
- union/_task.py +0 -354
- union/_task_environment.py +0 -186
- union/_timeout.py +0 -47
- union/_tools.py +0 -27
- union/_utils/__init__.py +0 -11
- union/_utils/asyn.py +0 -119
- union/_utils/file_handling.py +0 -71
- union/_utils/helpers.py +0 -46
- union/_utils/lazy_module.py +0 -54
- union/_utils/uv_script_parser.py +0 -49
- union/_version.py +0 -21
- union/connectors/__init__.py +0 -0
- union/errors.py +0 -128
- union/extras/__init__.py +0 -5
- union/extras/_container.py +0 -263
- union/io/__init__.py +0 -11
- union/io/_dataframe.py +0 -0
- union/io/_dir.py +0 -425
- union/io/_file.py +0 -418
- union/io/pickle/__init__.py +0 -0
- union/io/pickle/transformer.py +0 -117
- union/io/structured_dataset/__init__.py +0 -122
- union/io/structured_dataset/basic_dfs.py +0 -219
- union/io/structured_dataset/structured_dataset.py +0 -1057
- union/py.typed +0 -0
- union/remote/__init__.py +0 -23
- union/remote/_client/__init__.py +0 -0
- union/remote/_client/_protocols.py +0 -129
- union/remote/_client/auth/__init__.py +0 -12
- union/remote/_client/auth/_authenticators/__init__.py +0 -0
- union/remote/_client/auth/_authenticators/base.py +0 -391
- union/remote/_client/auth/_authenticators/client_credentials.py +0 -73
- union/remote/_client/auth/_authenticators/device_code.py +0 -120
- union/remote/_client/auth/_authenticators/external_command.py +0 -77
- union/remote/_client/auth/_authenticators/factory.py +0 -200
- union/remote/_client/auth/_authenticators/pkce.py +0 -515
- union/remote/_client/auth/_channel.py +0 -184
- union/remote/_client/auth/_client_config.py +0 -83
- union/remote/_client/auth/_default_html.py +0 -32
- union/remote/_client/auth/_grpc_utils/__init__.py +0 -0
- union/remote/_client/auth/_grpc_utils/auth_interceptor.py +0 -204
- union/remote/_client/auth/_grpc_utils/default_metadata_interceptor.py +0 -144
- union/remote/_client/auth/_keyring.py +0 -154
- union/remote/_client/auth/_token_client.py +0 -258
- union/remote/_client/auth/errors.py +0 -16
- union/remote/_client/controlplane.py +0 -86
- union/remote/_data.py +0 -149
- union/remote/_logs.py +0 -74
- union/remote/_project.py +0 -86
- union/remote/_run.py +0 -820
- union/remote/_secret.py +0 -132
- union/remote/_task.py +0 -193
- union/report/__init__.py +0 -3
- union/report/_report.py +0 -178
- union/report/_template.html +0 -124
- union/storage/__init__.py +0 -24
- union/storage/_remote_fs.py +0 -34
- union/storage/_storage.py +0 -247
- union/storage/_utils.py +0 -5
- union/types/__init__.py +0 -11
- union/types/_renderer.py +0 -162
- union/types/_string_literals.py +0 -120
- union/types/_type_engine.py +0 -2131
- union/types/_utils.py +0 -80
- /flyte/{_cli → _debug}/__init__.py +0 -0
- /flyte/{_protos → _keyring}/__init__.py +0 -0
- {flyte-0.0.1b0.dist-info → flyte-2.0.0b46.dist-info}/WHEEL +0 -0
- {flyte-0.0.1b0.dist-info → flyte-2.0.0b46.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import gzip
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
import tarfile
|
|
5
|
+
import tempfile
|
|
6
|
+
import typing
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import TYPE_CHECKING, Optional, Tuple, cast
|
|
10
|
+
from uuid import uuid4
|
|
11
|
+
|
|
12
|
+
import aiofiles
|
|
13
|
+
from flyteidl2.common import phase_pb2
|
|
14
|
+
|
|
15
|
+
import flyte
|
|
16
|
+
import flyte.errors
|
|
17
|
+
from flyte import Image, remote
|
|
18
|
+
from flyte._code_bundle._utils import tar_strip_file_attributes
|
|
19
|
+
from flyte._image import (
|
|
20
|
+
_BASE_REGISTRY,
|
|
21
|
+
AptPackages,
|
|
22
|
+
Architecture,
|
|
23
|
+
Commands,
|
|
24
|
+
CopyConfig,
|
|
25
|
+
DockerIgnore,
|
|
26
|
+
Env,
|
|
27
|
+
PipOption,
|
|
28
|
+
PipPackages,
|
|
29
|
+
PoetryProject,
|
|
30
|
+
PythonWheels,
|
|
31
|
+
Requirements,
|
|
32
|
+
UVProject,
|
|
33
|
+
UVScript,
|
|
34
|
+
WorkDir,
|
|
35
|
+
)
|
|
36
|
+
from flyte._internal.imagebuild.image_builder import ImageBuilder, ImageChecker
|
|
37
|
+
from flyte._internal.imagebuild.utils import copy_files_to_context, get_and_list_dockerignore
|
|
38
|
+
from flyte._internal.runtime.task_serde import get_security_context
|
|
39
|
+
from flyte._logging import logger
|
|
40
|
+
from flyte._secret import Secret
|
|
41
|
+
from flyte.remote import ActionOutputs, Run
|
|
42
|
+
|
|
43
|
+
if TYPE_CHECKING:
|
|
44
|
+
from flyteidl2.imagebuilder import definition_pb2 as image_definition_pb2
|
|
45
|
+
|
|
46
|
+
IMAGE_TASK_NAME = "build-image"
|
|
47
|
+
IMAGE_TASK_PROJECT = "system"
|
|
48
|
+
IMAGE_TASK_DOMAIN = "production"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class RemoteImageChecker(ImageChecker):
|
|
52
|
+
_images_client = None
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
async def image_exists(
|
|
56
|
+
cls, repository: str, tag: str, arch: Tuple[Architecture, ...] = ("linux/amd64",)
|
|
57
|
+
) -> Optional[str]:
|
|
58
|
+
try:
|
|
59
|
+
import flyte.remote as remote
|
|
60
|
+
|
|
61
|
+
remote.Task.get(
|
|
62
|
+
name=IMAGE_TASK_NAME,
|
|
63
|
+
project=IMAGE_TASK_PROJECT,
|
|
64
|
+
domain=IMAGE_TASK_DOMAIN,
|
|
65
|
+
auto_version="latest",
|
|
66
|
+
)
|
|
67
|
+
except Exception as e:
|
|
68
|
+
msg = "remote image builder is not enabled. Please contact Union support to enable it."
|
|
69
|
+
raise flyte.errors.ImageBuildError(msg) from e
|
|
70
|
+
|
|
71
|
+
image_name = f"{repository.split('/')[-1]}:{tag}"
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
from flyteidl2.common.identifier_pb2 import ProjectIdentifier
|
|
75
|
+
from flyteidl2.imagebuilder import definition_pb2 as image_definition__pb2
|
|
76
|
+
from flyteidl2.imagebuilder import payload_pb2 as image_payload__pb2
|
|
77
|
+
from flyteidl2.imagebuilder import service_pb2_grpc as image_service_pb2_grpc
|
|
78
|
+
|
|
79
|
+
from flyte._initialize import _get_init_config
|
|
80
|
+
|
|
81
|
+
cfg = _get_init_config()
|
|
82
|
+
if cfg is None:
|
|
83
|
+
raise ValueError("Init config should not be None")
|
|
84
|
+
image_id = image_definition__pb2.ImageIdentifier(name=image_name)
|
|
85
|
+
req = image_payload__pb2.GetImageRequest(
|
|
86
|
+
id=image_id,
|
|
87
|
+
organization=cfg.org,
|
|
88
|
+
project_id=ProjectIdentifier(organization=cfg.org, domain=cfg.domain, name=cfg.project),
|
|
89
|
+
)
|
|
90
|
+
if cls._images_client is None:
|
|
91
|
+
if cfg.client is None:
|
|
92
|
+
raise ValueError("remote client should not be None")
|
|
93
|
+
cls._images_client = image_service_pb2_grpc.ImageServiceStub(cfg.client._channel)
|
|
94
|
+
resp = await cls._images_client.GetImage(req)
|
|
95
|
+
logger.warning(f"[blue]Image {resp.image.fqin} found. Skip building.[/blue]")
|
|
96
|
+
return resp.image.fqin
|
|
97
|
+
except Exception:
|
|
98
|
+
logger.warning(f"[blue]Image {image_name} was not found or has expired.[/blue]", extra={"highlight": False})
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class RemoteImageBuilder(ImageBuilder):
|
|
103
|
+
def get_checkers(self) -> Optional[typing.List[typing.Type[ImageChecker]]]:
|
|
104
|
+
"""Return the image checker."""
|
|
105
|
+
return [RemoteImageChecker]
|
|
106
|
+
|
|
107
|
+
async def build_image(self, image: Image, dry_run: bool = False) -> str:
|
|
108
|
+
image_name = f"{image.name}:{image._final_tag}"
|
|
109
|
+
spec, context = await _validate_configuration(image)
|
|
110
|
+
|
|
111
|
+
start = datetime.now(timezone.utc)
|
|
112
|
+
try:
|
|
113
|
+
entity = await remote.Task.get(
|
|
114
|
+
name=IMAGE_TASK_NAME,
|
|
115
|
+
project=IMAGE_TASK_PROJECT,
|
|
116
|
+
domain=IMAGE_TASK_DOMAIN,
|
|
117
|
+
auto_version="latest",
|
|
118
|
+
).override.aio(secrets=_get_build_secrets_from_image(image))
|
|
119
|
+
except flyte.errors.ReferenceTaskError:
|
|
120
|
+
raise flyte.errors.ImageBuildError(
|
|
121
|
+
"remote image builder is not enabled. Please contact Union support to enable it."
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
logger.warning("[bold blue]🐳 Submitting a new build...[/bold blue]")
|
|
125
|
+
if image.registry and image.registry != _BASE_REGISTRY:
|
|
126
|
+
target_image = f"{image.registry}/{image_name}"
|
|
127
|
+
else:
|
|
128
|
+
# Use the default system registry in the backend.
|
|
129
|
+
target_image = image_name
|
|
130
|
+
|
|
131
|
+
from flyte._initialize import get_init_config
|
|
132
|
+
|
|
133
|
+
cfg = get_init_config()
|
|
134
|
+
run = cast(
|
|
135
|
+
Run,
|
|
136
|
+
await flyte.with_runcontext(
|
|
137
|
+
project=cfg.project, domain=cfg.domain, cache_lookup_scope="project-domain"
|
|
138
|
+
).run.aio(entity, spec=spec, context=context, target_image=target_image),
|
|
139
|
+
)
|
|
140
|
+
logger.warning(f"⏳ Waiting for build to finish at: [bold cyan link={run.url}]{run.url}[/bold cyan link]")
|
|
141
|
+
|
|
142
|
+
await run.wait.aio(quiet=True)
|
|
143
|
+
run_details = await run.details.aio()
|
|
144
|
+
|
|
145
|
+
elapsed = str(datetime.now(timezone.utc) - start).split(".")[0]
|
|
146
|
+
|
|
147
|
+
if run_details.action_details.raw_phase == phase_pb2.ACTION_PHASE_SUCCEEDED:
|
|
148
|
+
logger.warning(f"[bold green]✅ Build completed in {elapsed}![/bold green]")
|
|
149
|
+
else:
|
|
150
|
+
raise flyte.errors.ImageBuildError(f"❌ Build failed in {elapsed} at {run.url}")
|
|
151
|
+
|
|
152
|
+
outputs = await run_details.outputs()
|
|
153
|
+
return _get_fully_qualified_image_name(outputs)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
async def _validate_configuration(image: Image) -> Tuple[str, Optional[str]]:
|
|
157
|
+
"""Validate the configuration and prepare the spec and context files.""" # Prepare the spec file
|
|
158
|
+
tmp_path = Path(tempfile.gettempdir()) / str(uuid4())
|
|
159
|
+
os.makedirs(tmp_path, exist_ok=True)
|
|
160
|
+
|
|
161
|
+
context_path = tmp_path / "build.uc-image-builder"
|
|
162
|
+
context_path.mkdir(exist_ok=True)
|
|
163
|
+
|
|
164
|
+
image_idl = _get_layers_proto(image, context_path)
|
|
165
|
+
|
|
166
|
+
spec_path = tmp_path / "spec.pb"
|
|
167
|
+
with spec_path.open("wb") as f:
|
|
168
|
+
f.write(image_idl.SerializeToString())
|
|
169
|
+
|
|
170
|
+
_, spec_url = await remote.upload_file.aio(spec_path)
|
|
171
|
+
|
|
172
|
+
if any(context_path.iterdir()):
|
|
173
|
+
# If there are files in the context directory, upload it
|
|
174
|
+
tar_path = tmp_path / "context.tar"
|
|
175
|
+
with tarfile.open(tar_path, "w", dereference=False) as tar:
|
|
176
|
+
files: typing.List[str] = os.listdir(context_path)
|
|
177
|
+
for ws_file in files:
|
|
178
|
+
tar.add(
|
|
179
|
+
os.path.join(context_path, ws_file),
|
|
180
|
+
recursive=True,
|
|
181
|
+
arcname=ws_file,
|
|
182
|
+
filter=tar_strip_file_attributes,
|
|
183
|
+
)
|
|
184
|
+
context_dst = Path(f"{tar_path!s}.gz")
|
|
185
|
+
with gzip.GzipFile(filename=context_dst, mode="wb", mtime=0) as gzipped:
|
|
186
|
+
async with aiofiles.open(tar_path, "rb") as tar_file:
|
|
187
|
+
content = await tar_file.read()
|
|
188
|
+
gzipped.write(content)
|
|
189
|
+
|
|
190
|
+
context_size = tar_path.stat().st_size
|
|
191
|
+
if context_size > 5 * 1024 * 1024:
|
|
192
|
+
logger.warning(
|
|
193
|
+
f"[yellow]Context size is {context_size / (1024 * 1024):.2f} MB, which is larger than 5 MB. "
|
|
194
|
+
"Upload and build speed will be impacted.[/yellow]",
|
|
195
|
+
)
|
|
196
|
+
_, context_url = await remote.upload_file.aio(context_dst)
|
|
197
|
+
else:
|
|
198
|
+
context_url = ""
|
|
199
|
+
|
|
200
|
+
return spec_url, context_url
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _get_layers_proto(image: Image, context_path: Path) -> "image_definition_pb2.ImageSpec":
|
|
204
|
+
from flyteidl2.imagebuilder import definition_pb2 as image_definition_pb2
|
|
205
|
+
|
|
206
|
+
if image.dockerfile is not None:
|
|
207
|
+
raise flyte.errors.ImageBuildError(
|
|
208
|
+
"Custom Dockerfile is not supported with remote image builder.You can use local image builder instead."
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
layers = []
|
|
212
|
+
for layer in image._layers:
|
|
213
|
+
secret_mounts = None
|
|
214
|
+
pip_options = image_definition_pb2.PipOptions()
|
|
215
|
+
|
|
216
|
+
if isinstance(layer, PipOption):
|
|
217
|
+
pip_options = image_definition_pb2.PipOptions(
|
|
218
|
+
index_url=layer.index_url,
|
|
219
|
+
extra_index_urls=layer.extra_index_urls,
|
|
220
|
+
pre=layer.pre,
|
|
221
|
+
extra_args=layer.extra_args,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
if hasattr(layer, "secret_mounts"):
|
|
225
|
+
sc = get_security_context(layer.secret_mounts)
|
|
226
|
+
secret_mounts = sc.secrets if sc else None
|
|
227
|
+
|
|
228
|
+
if isinstance(layer, AptPackages):
|
|
229
|
+
apt_layer = image_definition_pb2.Layer(
|
|
230
|
+
apt_packages=image_definition_pb2.AptPackages(
|
|
231
|
+
packages=layer.packages,
|
|
232
|
+
secret_mounts=secret_mounts,
|
|
233
|
+
),
|
|
234
|
+
)
|
|
235
|
+
layers.append(apt_layer)
|
|
236
|
+
elif isinstance(layer, PythonWheels):
|
|
237
|
+
dst_path = copy_files_to_context(layer.wheel_dir, context_path)
|
|
238
|
+
wheel_layer = image_definition_pb2.Layer(
|
|
239
|
+
python_wheels=image_definition_pb2.PythonWheels(
|
|
240
|
+
dir=str(dst_path.relative_to(context_path)),
|
|
241
|
+
options=pip_options,
|
|
242
|
+
secret_mounts=secret_mounts,
|
|
243
|
+
)
|
|
244
|
+
)
|
|
245
|
+
layers.append(wheel_layer)
|
|
246
|
+
|
|
247
|
+
elif isinstance(layer, Requirements):
|
|
248
|
+
dst_path = copy_files_to_context(layer.file, context_path)
|
|
249
|
+
requirements_layer = image_definition_pb2.Layer(
|
|
250
|
+
requirements=image_definition_pb2.Requirements(
|
|
251
|
+
file=str(dst_path.relative_to(context_path)),
|
|
252
|
+
options=pip_options,
|
|
253
|
+
secret_mounts=secret_mounts,
|
|
254
|
+
)
|
|
255
|
+
)
|
|
256
|
+
layers.append(requirements_layer)
|
|
257
|
+
elif isinstance(layer, PipPackages) or isinstance(layer, UVScript):
|
|
258
|
+
if isinstance(layer, UVScript):
|
|
259
|
+
from flyte._utils import parse_uv_script_file
|
|
260
|
+
|
|
261
|
+
header = parse_uv_script_file(layer.script)
|
|
262
|
+
if not header.dependencies:
|
|
263
|
+
continue
|
|
264
|
+
packages: typing.Iterable[str] = header.dependencies
|
|
265
|
+
if header.pyprojects:
|
|
266
|
+
layers.append(
|
|
267
|
+
image_definition_pb2.Layer(
|
|
268
|
+
apt_packages=image_definition_pb2.AptPackages(
|
|
269
|
+
packages=["git"], # To get the version of the project.
|
|
270
|
+
),
|
|
271
|
+
)
|
|
272
|
+
)
|
|
273
|
+
docker_ignore_patterns = get_and_list_dockerignore(image)
|
|
274
|
+
|
|
275
|
+
for pyproject in header.pyprojects:
|
|
276
|
+
pyproject_dst = copy_files_to_context(Path(pyproject), context_path, docker_ignore_patterns)
|
|
277
|
+
uv_project_layer = image_definition_pb2.Layer(
|
|
278
|
+
uv_project=image_definition_pb2.UVProject(
|
|
279
|
+
pyproject=str(pyproject_dst.relative_to(context_path)),
|
|
280
|
+
uvlock=str(
|
|
281
|
+
copy_files_to_context(Path(pyproject) / "uv.lock", context_path).relative_to(
|
|
282
|
+
context_path
|
|
283
|
+
)
|
|
284
|
+
),
|
|
285
|
+
options=pip_options,
|
|
286
|
+
secret_mounts=secret_mounts,
|
|
287
|
+
)
|
|
288
|
+
)
|
|
289
|
+
layers.append(uv_project_layer)
|
|
290
|
+
|
|
291
|
+
else:
|
|
292
|
+
packages = layer.packages or []
|
|
293
|
+
pip_layer = image_definition_pb2.Layer(
|
|
294
|
+
pip_packages=image_definition_pb2.PipPackages(
|
|
295
|
+
packages=packages,
|
|
296
|
+
options=pip_options,
|
|
297
|
+
secret_mounts=secret_mounts,
|
|
298
|
+
)
|
|
299
|
+
)
|
|
300
|
+
layers.append(pip_layer)
|
|
301
|
+
elif isinstance(layer, UVProject):
|
|
302
|
+
if layer.project_install_mode == "dependencies_only":
|
|
303
|
+
# Copy pyproject itself
|
|
304
|
+
pyproject_dst = copy_files_to_context(layer.pyproject, context_path)
|
|
305
|
+
if pip_options.extra_args:
|
|
306
|
+
if "--no-install-project" not in pip_options.extra_args:
|
|
307
|
+
pip_options.extra_args += " --no-install-project"
|
|
308
|
+
else:
|
|
309
|
+
pip_options.extra_args = " --no-install-project"
|
|
310
|
+
if "--no-sources" not in pip_options.extra_args:
|
|
311
|
+
pip_options.extra_args += " --no-sources"
|
|
312
|
+
else:
|
|
313
|
+
# Copy the entire project
|
|
314
|
+
docker_ignore_patterns = get_and_list_dockerignore(image)
|
|
315
|
+
pyproject_dst = copy_files_to_context(layer.pyproject.parent, context_path, docker_ignore_patterns)
|
|
316
|
+
|
|
317
|
+
uv_layer = image_definition_pb2.Layer(
|
|
318
|
+
uv_project=image_definition_pb2.UVProject(
|
|
319
|
+
pyproject=str(pyproject_dst.relative_to(context_path)),
|
|
320
|
+
uvlock=str(copy_files_to_context(layer.uvlock, context_path).relative_to(context_path)),
|
|
321
|
+
options=pip_options,
|
|
322
|
+
secret_mounts=secret_mounts,
|
|
323
|
+
)
|
|
324
|
+
)
|
|
325
|
+
layers.append(uv_layer)
|
|
326
|
+
elif isinstance(layer, PoetryProject):
|
|
327
|
+
extra_args = layer.extra_args or ""
|
|
328
|
+
if layer.project_install_mode == "dependencies_only":
|
|
329
|
+
# Copy pyproject itself
|
|
330
|
+
if "--no-root" not in extra_args:
|
|
331
|
+
extra_args += " --no-root"
|
|
332
|
+
pyproject_dst = copy_files_to_context(layer.pyproject, context_path)
|
|
333
|
+
else:
|
|
334
|
+
# Copy the entire project
|
|
335
|
+
pyproject_dst = copy_files_to_context(layer.pyproject.parent, context_path)
|
|
336
|
+
|
|
337
|
+
poetry_layer = image_definition_pb2.Layer(
|
|
338
|
+
poetry_project=image_definition_pb2.PoetryProject(
|
|
339
|
+
pyproject=str(pyproject_dst.relative_to(context_path)),
|
|
340
|
+
poetry_lock=str(copy_files_to_context(layer.poetry_lock, context_path).relative_to(context_path)),
|
|
341
|
+
extra_args=extra_args,
|
|
342
|
+
secret_mounts=secret_mounts,
|
|
343
|
+
)
|
|
344
|
+
)
|
|
345
|
+
layers.append(poetry_layer)
|
|
346
|
+
elif isinstance(layer, Commands):
|
|
347
|
+
commands_layer = image_definition_pb2.Layer(
|
|
348
|
+
commands=image_definition_pb2.Commands(
|
|
349
|
+
cmd=list(layer.commands),
|
|
350
|
+
secret_mounts=secret_mounts,
|
|
351
|
+
)
|
|
352
|
+
)
|
|
353
|
+
layers.append(commands_layer)
|
|
354
|
+
elif isinstance(layer, DockerIgnore):
|
|
355
|
+
shutil.copy(layer.path, context_path)
|
|
356
|
+
elif isinstance(layer, CopyConfig):
|
|
357
|
+
dst_path = copy_files_to_context(layer.src, context_path)
|
|
358
|
+
|
|
359
|
+
copy_layer = image_definition_pb2.Layer(
|
|
360
|
+
copy_config=image_definition_pb2.CopyConfig(
|
|
361
|
+
src=str(dst_path.relative_to(context_path)),
|
|
362
|
+
dst=str(layer.dst),
|
|
363
|
+
)
|
|
364
|
+
)
|
|
365
|
+
layers.append(copy_layer)
|
|
366
|
+
elif isinstance(layer, Env):
|
|
367
|
+
env_layer = image_definition_pb2.Layer(
|
|
368
|
+
env=image_definition_pb2.Env(
|
|
369
|
+
env_variables=dict(layer.env_vars),
|
|
370
|
+
)
|
|
371
|
+
)
|
|
372
|
+
layers.append(env_layer)
|
|
373
|
+
elif isinstance(layer, WorkDir):
|
|
374
|
+
workdir_layer = image_definition_pb2.Layer(
|
|
375
|
+
workdir=image_definition_pb2.WorkDir(workdir=layer.workdir),
|
|
376
|
+
)
|
|
377
|
+
layers.append(workdir_layer)
|
|
378
|
+
|
|
379
|
+
return image_definition_pb2.ImageSpec(
|
|
380
|
+
base_image=image.base_image,
|
|
381
|
+
python_version=f"{image.python_version[0]}.{image.python_version[1]}",
|
|
382
|
+
layers=layers,
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def _get_fully_qualified_image_name(outputs: ActionOutputs) -> str:
|
|
387
|
+
return outputs.pb2.literals[0].value.scalar.primitive.string_value
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def _get_build_secrets_from_image(image: Image) -> Optional[typing.List[Secret]]:
|
|
391
|
+
secrets = []
|
|
392
|
+
DEFAULT_SECRET_DIR = Path("/etc/flyte/secrets")
|
|
393
|
+
for layer in image._layers:
|
|
394
|
+
if isinstance(layer, (PipOption, Commands, AptPackages)) and layer.secret_mounts is not None:
|
|
395
|
+
for secret_mount in layer.secret_mounts:
|
|
396
|
+
# Mount all the image secrets to a default directory that will be passed to the BuildKit server.
|
|
397
|
+
if isinstance(secret_mount, Secret):
|
|
398
|
+
secrets.append(Secret(key=secret_mount.key, group=secret_mount.group, mount=DEFAULT_SECRET_DIR))
|
|
399
|
+
elif isinstance(secret_mount, str):
|
|
400
|
+
secrets.append(Secret(key=secret_mount, mount=DEFAULT_SECRET_DIR))
|
|
401
|
+
else:
|
|
402
|
+
raise ValueError(f"Unsupported secret_mount type: {type(secret_mount)}")
|
|
403
|
+
|
|
404
|
+
image_registry_secret = image._image_registry_secret
|
|
405
|
+
if image_registry_secret:
|
|
406
|
+
secrets.append(
|
|
407
|
+
Secret(key=image_registry_secret.key, group=image_registry_secret.group, mount=DEFAULT_SECRET_DIR)
|
|
408
|
+
)
|
|
409
|
+
return secrets
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
from pathlib import Path, PurePath
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
|
|
5
|
+
from flyte._image import DockerIgnore, Image
|
|
6
|
+
from flyte._logging import logger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def copy_files_to_context(src: Path, context_path: Path, ignore_patterns: list[str] = []) -> Path:
|
|
10
|
+
"""
|
|
11
|
+
This helper function ensures that absolute paths that users specify are converted correctly to a path in the
|
|
12
|
+
context directory. Doing this prevents collisions while ensuring files are available in the context.
|
|
13
|
+
|
|
14
|
+
For example, if a user has
|
|
15
|
+
img.with_requirements(Path("/Users/username/requirements.txt"))
|
|
16
|
+
.with_requirements(Path("requirements.txt"))
|
|
17
|
+
.with_requirements(Path("../requirements.txt"))
|
|
18
|
+
|
|
19
|
+
copying with this function ensures that the Docker context folder has all three files.
|
|
20
|
+
|
|
21
|
+
:param src: The source path to copy
|
|
22
|
+
:param context_path: The context path where the files should be copied to
|
|
23
|
+
"""
|
|
24
|
+
if src.is_absolute() or ".." in str(src):
|
|
25
|
+
rel_path = PurePath(*src.parts[1:])
|
|
26
|
+
dst_path = context_path / "_flyte_abs_context" / rel_path
|
|
27
|
+
else:
|
|
28
|
+
dst_path = context_path / src
|
|
29
|
+
dst_path.parent.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
if src.is_dir():
|
|
31
|
+
default_ignore_patterns = [".idea", ".venv"]
|
|
32
|
+
ignore_patterns = list(set(ignore_patterns + default_ignore_patterns))
|
|
33
|
+
shutil.copytree(src, dst_path, dirs_exist_ok=True, ignore=shutil.ignore_patterns(*ignore_patterns))
|
|
34
|
+
else:
|
|
35
|
+
shutil.copy(src, dst_path)
|
|
36
|
+
return dst_path
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_and_list_dockerignore(image: Image) -> List[str]:
|
|
40
|
+
"""
|
|
41
|
+
Get and parse dockerignore patterns from .dockerignore file.
|
|
42
|
+
|
|
43
|
+
This function first looks for a DockerIgnore layer in the image's layers. If found, it uses
|
|
44
|
+
the path specified in that layer. If no DockerIgnore layer is found, it falls back to looking
|
|
45
|
+
for a .dockerignore file in the root_path directory.
|
|
46
|
+
|
|
47
|
+
:param image: The Image object
|
|
48
|
+
"""
|
|
49
|
+
from flyte._initialize import _get_init_config
|
|
50
|
+
|
|
51
|
+
# Look for DockerIgnore layer in the image layers
|
|
52
|
+
dockerignore_path: Optional[Path] = None
|
|
53
|
+
patterns: List[str] = []
|
|
54
|
+
|
|
55
|
+
for layer in image._layers:
|
|
56
|
+
if isinstance(layer, DockerIgnore) and layer.path.strip():
|
|
57
|
+
dockerignore_path = Path(layer.path)
|
|
58
|
+
# If DockerIgnore layer not specified, set dockerignore_path under root_path
|
|
59
|
+
init_config = _get_init_config()
|
|
60
|
+
root_path = init_config.root_dir if init_config else None
|
|
61
|
+
if not dockerignore_path and root_path:
|
|
62
|
+
dockerignore_path = Path(root_path) / ".dockerignore"
|
|
63
|
+
# Return empty list if no .dockerignore file found
|
|
64
|
+
if not dockerignore_path or not dockerignore_path.exists() or not dockerignore_path.is_file():
|
|
65
|
+
logger.info(f".dockerignore file not found at path: {dockerignore_path}")
|
|
66
|
+
return patterns
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
with open(dockerignore_path, "r", encoding="utf-8") as f:
|
|
70
|
+
for line in f:
|
|
71
|
+
stripped_line = line.strip()
|
|
72
|
+
# Skip empty lines, whitespace-only lines, and comments
|
|
73
|
+
if not stripped_line or stripped_line.startswith("#"):
|
|
74
|
+
continue
|
|
75
|
+
patterns.append(stripped_line)
|
|
76
|
+
except Exception as e:
|
|
77
|
+
logger.error(f"Failed to read .dockerignore file at {dockerignore_path}: {e}")
|
|
78
|
+
return []
|
|
79
|
+
return patterns
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from flyte._logging import logger
|
|
5
|
+
from flyte._module import extract_obj_module
|
|
6
|
+
from flyte.app._app_environment import AppEnvironment
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def extract_app_env_module(app_env: AppEnvironment, /, source_dir: pathlib.Path) -> tuple[str, str]:
|
|
10
|
+
"""
|
|
11
|
+
Extract the module name and variable name for a AppEnvironment instance.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
app_env: The AppEnvironment instance to locate.
|
|
15
|
+
caller_frame: Frame information from where AppEnvironment was instantiated.
|
|
16
|
+
If None, falls back to extract_obj_module (which may not work correctly).
|
|
17
|
+
serialization_context: Context containing the root directory for calculating
|
|
18
|
+
relative module paths.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
A tuple of (module_name, variable_name) where:
|
|
22
|
+
- module_name: Dotted module path (e.g., "examples.apps.single_script_fastapi")
|
|
23
|
+
- variable_name: The name of the variable holding the AppEnvironment (e.g., "env")
|
|
24
|
+
|
|
25
|
+
Raises:
|
|
26
|
+
RuntimeError: If the module cannot be loaded or the app variable cannot be found.
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
>>> frame = inspect.getframeinfo(inspect.currentframe().f_back)
|
|
30
|
+
>>> module_name, var_name = _extract_app_env_module_and_var(
|
|
31
|
+
... app_env, frame, serialization_context
|
|
32
|
+
... )
|
|
33
|
+
>>> # Returns: ("examples.apps.my_app", "env")
|
|
34
|
+
>>> # Can be used as: fserve examples.apps.my_app:env
|
|
35
|
+
"""
|
|
36
|
+
if app_env._caller_frame is None:
|
|
37
|
+
raise RuntimeError("Caller frame cannot be None")
|
|
38
|
+
|
|
39
|
+
# Get the file path where the app was defined
|
|
40
|
+
file_path = pathlib.Path(app_env._caller_frame.filename)
|
|
41
|
+
|
|
42
|
+
# Calculate module name relative to source_dir
|
|
43
|
+
try:
|
|
44
|
+
relative_path = file_path.relative_to(source_dir or pathlib.Path("."))
|
|
45
|
+
logger.info(f"Relative path: {relative_path}, {source_dir} {pathlib.Path('.')}")
|
|
46
|
+
module_name = pathlib.Path(relative_path).with_suffix("").as_posix().replace("/", ".")
|
|
47
|
+
except ValueError:
|
|
48
|
+
# File is not relative to source_dir, use the stem
|
|
49
|
+
module_name = file_path.stem
|
|
50
|
+
|
|
51
|
+
# Instead of reloading the module, inspect the caller frame's local variables
|
|
52
|
+
# The app variable should be in the frame's globals
|
|
53
|
+
caller_globals = None
|
|
54
|
+
|
|
55
|
+
# Try to get globals from the main module if it matches our file
|
|
56
|
+
if hasattr(sys.modules.get("__main__"), "__file__"):
|
|
57
|
+
main_file = pathlib.Path(sys.modules["__main__"].__file__ or ".").resolve()
|
|
58
|
+
if main_file == file_path.resolve():
|
|
59
|
+
caller_globals = sys.modules["__main__"].__dict__
|
|
60
|
+
|
|
61
|
+
if caller_globals is None:
|
|
62
|
+
# Load the module to inspect it by importing it by name
|
|
63
|
+
# Note: we can't use extract_obj_module here because it uses inspect.getmodule()
|
|
64
|
+
# which returns the module where the CLASS is defined, not where the INSTANCE is created
|
|
65
|
+
import importlib
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
entity_module = importlib.import_module(module_name)
|
|
69
|
+
caller_globals = entity_module.__dict__
|
|
70
|
+
except (ModuleNotFoundError, ImportError):
|
|
71
|
+
# Fallback for test scenarios where module might be <string> or not importable
|
|
72
|
+
# In this case, use extract_obj_module as a last resort
|
|
73
|
+
_, entity_module = extract_obj_module(app_env, source_dir)
|
|
74
|
+
caller_globals = entity_module.__dict__
|
|
75
|
+
|
|
76
|
+
# Extract variable name from module - look for AppEnvironment instances
|
|
77
|
+
app_var_name = None
|
|
78
|
+
for var_name, obj in caller_globals.items():
|
|
79
|
+
if isinstance(obj, AppEnvironment):
|
|
80
|
+
# Found a AppEnvironment - this is likely the one we want
|
|
81
|
+
# Store the first one we find
|
|
82
|
+
if app_var_name is None:
|
|
83
|
+
app_var_name = var_name
|
|
84
|
+
# If the objects match by identity, use this one
|
|
85
|
+
if obj is app_env:
|
|
86
|
+
app_var_name = var_name
|
|
87
|
+
break
|
|
88
|
+
|
|
89
|
+
if app_var_name is None:
|
|
90
|
+
raise RuntimeError("Could not find variable name for FastAPI app in module")
|
|
91
|
+
|
|
92
|
+
return app_var_name, module_name
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import inspect
|
|
2
|
-
import os
|
|
3
1
|
import pathlib
|
|
4
|
-
import sys
|
|
5
2
|
from typing import Tuple
|
|
6
3
|
|
|
4
|
+
from flyte._module import extract_obj_module
|
|
7
5
|
from flyte._task import AsyncFunctionTaskTemplate, TaskTemplate
|
|
8
6
|
|
|
9
7
|
|
|
10
|
-
def extract_task_module(task: TaskTemplate, /, source_dir: pathlib.Path
|
|
8
|
+
def extract_task_module(task: TaskTemplate, /, source_dir: pathlib.Path) -> Tuple[str, str]:
|
|
11
9
|
"""
|
|
12
10
|
Extract the task module from the task template.
|
|
13
11
|
|
|
@@ -15,40 +13,9 @@ def extract_task_module(task: TaskTemplate, /, source_dir: pathlib.Path | None =
|
|
|
15
13
|
:param source_dir: The source directory to use for relative paths.
|
|
16
14
|
:return: A tuple containing the entity name, module
|
|
17
15
|
"""
|
|
18
|
-
entity_name = task.name
|
|
19
16
|
if isinstance(task, AsyncFunctionTaskTemplate):
|
|
20
|
-
entity_module = inspect.getmodule(task.func)
|
|
21
|
-
if entity_module is None:
|
|
22
|
-
raise ValueError(f"Task {entity_name} has no module.")
|
|
23
|
-
|
|
24
|
-
fp = entity_module.__file__
|
|
25
|
-
if fp is None:
|
|
26
|
-
raise ValueError(f"Task {entity_name} has no module.")
|
|
27
|
-
|
|
28
|
-
file_path = pathlib.Path(fp)
|
|
29
|
-
# Get the relative path to the current directory
|
|
30
|
-
# Will raise ValueError if the file is not in the source directory
|
|
31
|
-
relative_path = file_path.relative_to(str(source_dir))
|
|
32
|
-
|
|
33
|
-
if relative_path == pathlib.Path("."):
|
|
34
|
-
entity_module_name = entity_module.__name__
|
|
35
|
-
else:
|
|
36
|
-
# Replace file separators with dots and remove the '.py' extension
|
|
37
|
-
dotted_path = os.path.splitext(str(relative_path))[0].replace(os.sep, ".")
|
|
38
|
-
entity_module_name = dotted_path
|
|
39
|
-
|
|
40
17
|
entity_name = task.func.__name__
|
|
18
|
+
entity_module_name, _ = extract_obj_module(task.func, source_dir)
|
|
19
|
+
return entity_name, entity_module_name
|
|
41
20
|
else:
|
|
42
|
-
raise NotImplementedError(f"Task module {
|
|
43
|
-
|
|
44
|
-
if entity_module_name == "__main__":
|
|
45
|
-
"""
|
|
46
|
-
This case is for the case in which the task is run from the main module.
|
|
47
|
-
"""
|
|
48
|
-
fp = sys.modules["__main__"].__file__
|
|
49
|
-
if fp is None:
|
|
50
|
-
raise ValueError(f"Task {entity_name} has no module.")
|
|
51
|
-
main_path = pathlib.Path(fp)
|
|
52
|
-
entity_module_name = main_path.stem
|
|
53
|
-
|
|
54
|
-
return entity_name, entity_module_name
|
|
21
|
+
raise NotImplementedError(f"Task module {task.name} not implemented.")
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from flyte._internal.resolvers._app_env_module import extract_app_env_module
|
|
5
|
+
from flyte._internal.resolvers.common import Resolver
|
|
6
|
+
from flyte.app._app_environment import AppEnvironment
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AppEnvResolver(Resolver):
|
|
10
|
+
"""
|
|
11
|
+
Please see the notes in the TaskResolverMixin as it describes this default behavior.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def import_path(self) -> str:
|
|
16
|
+
return "flyte._internal.resolvers.app_env.AppEnvResolver"
|
|
17
|
+
|
|
18
|
+
def load_app_env(self, loader_args: str) -> AppEnvironment:
|
|
19
|
+
module_name, app_var_name = loader_args.split(":")
|
|
20
|
+
app_env_module = importlib.import_module(name=module_name) # type: ignore
|
|
21
|
+
app_env_def = getattr(app_env_module, app_var_name)
|
|
22
|
+
return app_env_def
|
|
23
|
+
|
|
24
|
+
def loader_args(self, app_env: AppEnvironment, root_dir: Path) -> str: # type:ignore
|
|
25
|
+
app_var_name, module_name = extract_app_env_module(app_env, root_dir)
|
|
26
|
+
return f"{module_name}:{app_var_name}"
|
|
@@ -3,6 +3,7 @@ from pathlib import Path
|
|
|
3
3
|
from typing import List, Optional
|
|
4
4
|
|
|
5
5
|
from flyte._task import TaskTemplate
|
|
6
|
+
from flyte.app._app_environment import AppEnvironment
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class Resolver(Protocol):
|
|
@@ -23,7 +24,13 @@ class Resolver(Protocol):
|
|
|
23
24
|
"""
|
|
24
25
|
raise NotImplementedError
|
|
25
26
|
|
|
26
|
-
def
|
|
27
|
+
def load_app_env(self, loader_args: str) -> AppEnvironment:
|
|
28
|
+
"""
|
|
29
|
+
Given the set of identifier keys, should return one AppEnvironment or raise an error if not found
|
|
30
|
+
"""
|
|
31
|
+
raise NotImplementedError
|
|
32
|
+
|
|
33
|
+
def loader_args(self, t: TaskTemplate, root_dir: Optional[Path]) -> List[str] | str:
|
|
27
34
|
"""
|
|
28
35
|
Return a list of strings that can help identify the parameter TaskTemplate. Each string should not have
|
|
29
36
|
spaces or special characters. This is used to identify the task in the resolver.
|