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
flyte/_image.py
CHANGED
|
@@ -1,24 +1,32 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import base64
|
|
4
3
|
import hashlib
|
|
4
|
+
import os.path
|
|
5
5
|
import sys
|
|
6
|
+
import typing
|
|
6
7
|
from abc import abstractmethod
|
|
7
|
-
from dataclasses import
|
|
8
|
+
from dataclasses import dataclass, field
|
|
8
9
|
from functools import cached_property
|
|
9
10
|
from pathlib import Path
|
|
10
|
-
from typing import
|
|
11
|
+
from typing import TYPE_CHECKING, ClassVar, Dict, List, Literal, Optional, Tuple, TypeVar, Union
|
|
11
12
|
|
|
12
13
|
import rich.repr
|
|
14
|
+
from packaging.version import Version
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from flyte import Secret, SecretRequest
|
|
13
18
|
|
|
14
19
|
# Supported Python versions
|
|
15
20
|
PYTHON_3_10 = (3, 10)
|
|
16
21
|
PYTHON_3_11 = (3, 11)
|
|
17
22
|
PYTHON_3_12 = (3, 12)
|
|
18
23
|
PYTHON_3_13 = (3, 13)
|
|
24
|
+
PYTHON_3_14 = (3, 14)
|
|
19
25
|
|
|
20
26
|
# 0 is a file, 1 is a directory
|
|
21
27
|
CopyConfigType = Literal[0, 1]
|
|
28
|
+
SOURCE_ROOT = Path(__file__).parent.parent.parent
|
|
29
|
+
DIST_FOLDER = SOURCE_ROOT / "dist"
|
|
22
30
|
|
|
23
31
|
T = TypeVar("T")
|
|
24
32
|
|
|
@@ -44,8 +52,6 @@ class Layer:
|
|
|
44
52
|
layered images programmatically.
|
|
45
53
|
"""
|
|
46
54
|
|
|
47
|
-
_compute_identifier: Callable[[Layer], str] = field(default=lambda x: x.__str__(), init=True)
|
|
48
|
-
|
|
49
55
|
@abstractmethod
|
|
50
56
|
def update_hash(self, hasher: hashlib._Hash):
|
|
51
57
|
"""
|
|
@@ -53,7 +59,6 @@ class Layer:
|
|
|
53
59
|
|
|
54
60
|
:param hasher: The hash object to update with the layer's data.
|
|
55
61
|
"""
|
|
56
|
-
...
|
|
57
62
|
|
|
58
63
|
def validate(self):
|
|
59
64
|
"""
|
|
@@ -64,24 +69,33 @@ class Layer:
|
|
|
64
69
|
|
|
65
70
|
@rich.repr.auto
|
|
66
71
|
@dataclass(kw_only=True, frozen=True, repr=True)
|
|
67
|
-
class
|
|
68
|
-
packages: Optional[Tuple[str, ...]] = None
|
|
72
|
+
class PipOption:
|
|
69
73
|
index_url: Optional[str] = None
|
|
70
74
|
extra_index_urls: Optional[Tuple[str] | Tuple[str, ...] | List[str]] = None
|
|
71
75
|
pre: bool = False
|
|
72
76
|
extra_args: Optional[str] = None
|
|
77
|
+
secret_mounts: Optional[Tuple[str | Secret, ...]] = None
|
|
78
|
+
|
|
79
|
+
def get_pip_install_args(self) -> List[str]:
|
|
80
|
+
pip_install_args = []
|
|
81
|
+
if self.index_url:
|
|
82
|
+
pip_install_args.append(f"--index-url {self.index_url}")
|
|
83
|
+
|
|
84
|
+
if self.extra_index_urls:
|
|
85
|
+
pip_install_args.extend([f"--extra-index-url {url}" for url in self.extra_index_urls])
|
|
73
86
|
|
|
74
|
-
|
|
75
|
-
|
|
87
|
+
if self.pre:
|
|
88
|
+
pip_install_args.append("--pre")
|
|
89
|
+
|
|
90
|
+
if self.extra_args:
|
|
91
|
+
pip_install_args.append(self.extra_args)
|
|
92
|
+
return pip_install_args
|
|
76
93
|
|
|
77
94
|
def update_hash(self, hasher: hashlib._Hash):
|
|
78
95
|
"""
|
|
79
|
-
Update the hash with the
|
|
96
|
+
Update the hash with the PipOption
|
|
80
97
|
"""
|
|
81
98
|
hash_input = ""
|
|
82
|
-
if self.packages:
|
|
83
|
-
for package in self.packages:
|
|
84
|
-
hash_input += package
|
|
85
99
|
if self.index_url:
|
|
86
100
|
hash_input += self.index_url
|
|
87
101
|
if self.extra_index_urls:
|
|
@@ -91,10 +105,53 @@ class PipPackages(Layer):
|
|
|
91
105
|
hash_input += str(self.pre)
|
|
92
106
|
if self.extra_args:
|
|
93
107
|
hash_input += self.extra_args
|
|
108
|
+
if self.secret_mounts:
|
|
109
|
+
for secret_mount in self.secret_mounts:
|
|
110
|
+
hash_input += str(secret_mount)
|
|
94
111
|
|
|
95
112
|
hasher.update(hash_input.encode("utf-8"))
|
|
96
113
|
|
|
97
114
|
|
|
115
|
+
@rich.repr.auto
|
|
116
|
+
@dataclass(kw_only=True, frozen=True, repr=True)
|
|
117
|
+
class PipPackages(PipOption, Layer):
|
|
118
|
+
packages: Optional[Tuple[str, ...]] = None
|
|
119
|
+
|
|
120
|
+
def update_hash(self, hasher: hashlib._Hash):
|
|
121
|
+
"""
|
|
122
|
+
Update the hash with the pip packages
|
|
123
|
+
"""
|
|
124
|
+
super().update_hash(hasher)
|
|
125
|
+
hash_input = ""
|
|
126
|
+
if self.packages:
|
|
127
|
+
for package in self.packages:
|
|
128
|
+
hash_input += package
|
|
129
|
+
|
|
130
|
+
hasher.update(hash_input.encode("utf-8"))
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@rich.repr.auto
|
|
134
|
+
@dataclass(kw_only=True, frozen=True, repr=True)
|
|
135
|
+
class PythonWheels(PipOption, Layer):
|
|
136
|
+
wheel_dir: Path
|
|
137
|
+
wheel_dir_name: str = field(init=False)
|
|
138
|
+
package_name: str
|
|
139
|
+
|
|
140
|
+
def __post_init__(self):
|
|
141
|
+
object.__setattr__(self, "wheel_dir_name", self.wheel_dir.name)
|
|
142
|
+
|
|
143
|
+
def update_hash(self, hasher: hashlib._Hash):
|
|
144
|
+
super().update_hash(hasher)
|
|
145
|
+
from ._utils import filehash_update
|
|
146
|
+
|
|
147
|
+
# Iterate through all the wheel files in the directory and update the hash
|
|
148
|
+
for wheel_file in self.wheel_dir.glob("*.whl"):
|
|
149
|
+
if not wheel_file.is_file():
|
|
150
|
+
# Skip if it's not a file (e.g., directory or symlink)
|
|
151
|
+
continue
|
|
152
|
+
filehash_update(wheel_file, hasher)
|
|
153
|
+
|
|
154
|
+
|
|
98
155
|
@rich.repr.auto
|
|
99
156
|
@dataclass(kw_only=True, frozen=True, repr=True)
|
|
100
157
|
class Requirements(PipPackages):
|
|
@@ -107,22 +164,134 @@ class Requirements(PipPackages):
|
|
|
107
164
|
filehash_update(self.file, hasher)
|
|
108
165
|
|
|
109
166
|
|
|
167
|
+
@rich.repr.auto
|
|
168
|
+
@dataclass(frozen=True, repr=True)
|
|
169
|
+
class UVProject(PipOption, Layer):
|
|
170
|
+
pyproject: Path
|
|
171
|
+
uvlock: Path
|
|
172
|
+
project_install_mode: typing.Literal["dependencies_only", "install_project"] = "dependencies_only"
|
|
173
|
+
|
|
174
|
+
def validate(self):
|
|
175
|
+
if not self.pyproject.exists():
|
|
176
|
+
raise FileNotFoundError(f"pyproject.toml file {self.pyproject.resolve()} does not exist")
|
|
177
|
+
if not self.pyproject.is_file():
|
|
178
|
+
raise ValueError(f"Pyproject file {self.pyproject.resolve()} is not a file")
|
|
179
|
+
if not self.uvlock.exists():
|
|
180
|
+
raise ValueError(f"UVLock file {self.uvlock.resolve()} does not exist")
|
|
181
|
+
super().validate()
|
|
182
|
+
|
|
183
|
+
def update_hash(self, hasher: hashlib._Hash):
|
|
184
|
+
from ._utils import filehash_update, update_hasher_for_source
|
|
185
|
+
|
|
186
|
+
super().update_hash(hasher)
|
|
187
|
+
if self.project_install_mode == "dependencies_only":
|
|
188
|
+
filehash_update(self.uvlock, hasher)
|
|
189
|
+
filehash_update(self.pyproject, hasher)
|
|
190
|
+
else:
|
|
191
|
+
update_hasher_for_source(self.pyproject.parent, hasher)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@rich.repr.auto
|
|
195
|
+
@dataclass(frozen=True, repr=True)
|
|
196
|
+
class PoetryProject(Layer):
|
|
197
|
+
"""
|
|
198
|
+
Poetry does not use pip options, so the PoetryProject class do not inherits PipOption class
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
pyproject: Path
|
|
202
|
+
poetry_lock: Path
|
|
203
|
+
extra_args: Optional[str] = None
|
|
204
|
+
project_install_mode: typing.Literal["dependencies_only", "install_project"] = "dependencies_only"
|
|
205
|
+
secret_mounts: Optional[Tuple[str | Secret, ...]] = None
|
|
206
|
+
|
|
207
|
+
def validate(self):
|
|
208
|
+
if not self.pyproject.exists():
|
|
209
|
+
raise FileNotFoundError(f"pyproject.toml file {self.pyproject} does not exist")
|
|
210
|
+
if not self.pyproject.is_file():
|
|
211
|
+
raise ValueError(f"Pyproject file {self.pyproject} is not a file")
|
|
212
|
+
if not self.poetry_lock.exists():
|
|
213
|
+
raise ValueError(f"poetry.lock file {self.poetry_lock} does not exist")
|
|
214
|
+
super().validate()
|
|
215
|
+
|
|
216
|
+
def update_hash(self, hasher: hashlib._Hash):
|
|
217
|
+
from ._utils import filehash_update, update_hasher_for_source
|
|
218
|
+
|
|
219
|
+
hash_input = ""
|
|
220
|
+
if self.extra_args:
|
|
221
|
+
hash_input += self.extra_args
|
|
222
|
+
if self.secret_mounts:
|
|
223
|
+
for secret_mount in self.secret_mounts:
|
|
224
|
+
hash_input += str(secret_mount)
|
|
225
|
+
hasher.update(hash_input.encode("utf-8"))
|
|
226
|
+
|
|
227
|
+
if self.project_install_mode == "dependencies_only":
|
|
228
|
+
filehash_update(self.poetry_lock, hasher)
|
|
229
|
+
filehash_update(self.pyproject, hasher)
|
|
230
|
+
else:
|
|
231
|
+
update_hasher_for_source(self.pyproject.parent, hasher)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@rich.repr.auto
|
|
235
|
+
@dataclass(frozen=True, repr=True)
|
|
236
|
+
class UVScript(PipOption, Layer):
|
|
237
|
+
script: Path
|
|
238
|
+
script_name: str = field(init=False)
|
|
239
|
+
|
|
240
|
+
def __post_init__(self):
|
|
241
|
+
object.__setattr__(self, "script_name", self.script.name)
|
|
242
|
+
|
|
243
|
+
def validate(self):
|
|
244
|
+
if not self.script.exists():
|
|
245
|
+
raise FileNotFoundError(f"UV script {self.script} does not exist")
|
|
246
|
+
if not self.script.is_file():
|
|
247
|
+
raise ValueError(f"UV script {self.script} is not a file")
|
|
248
|
+
if not self.script.suffix == ".py":
|
|
249
|
+
raise ValueError(f"UV script {self.script} must have a .py extension")
|
|
250
|
+
super().validate()
|
|
251
|
+
|
|
252
|
+
def update_hash(self, hasher: hashlib._Hash):
|
|
253
|
+
from ._utils import parse_uv_script_file
|
|
254
|
+
|
|
255
|
+
header = parse_uv_script_file(self.script)
|
|
256
|
+
h_tuple = _ensure_tuple(header)
|
|
257
|
+
if h_tuple:
|
|
258
|
+
hasher.update(h_tuple.__str__().encode("utf-8"))
|
|
259
|
+
super().update_hash(hasher)
|
|
260
|
+
if header.pyprojects:
|
|
261
|
+
for pyproject in header.pyprojects:
|
|
262
|
+
UVProject(
|
|
263
|
+
Path(pyproject) / "pyproject.toml", Path(pyproject) / "uv.lock", "install_project"
|
|
264
|
+
).update_hash(hasher)
|
|
265
|
+
|
|
266
|
+
|
|
110
267
|
@rich.repr.auto
|
|
111
268
|
@dataclass(frozen=True, repr=True)
|
|
112
269
|
class AptPackages(Layer):
|
|
113
270
|
packages: Tuple[str, ...]
|
|
271
|
+
secret_mounts: Optional[Tuple[str | Secret, ...]] = None
|
|
114
272
|
|
|
115
273
|
def update_hash(self, hasher: hashlib._Hash):
|
|
116
|
-
|
|
274
|
+
hash_input = "".join(self.packages)
|
|
275
|
+
|
|
276
|
+
if self.secret_mounts:
|
|
277
|
+
for secret_mount in self.secret_mounts:
|
|
278
|
+
hash_input += str(secret_mount)
|
|
279
|
+
hasher.update(hash_input.encode("utf-8"))
|
|
117
280
|
|
|
118
281
|
|
|
119
282
|
@rich.repr.auto
|
|
120
283
|
@dataclass(frozen=True, repr=True)
|
|
121
284
|
class Commands(Layer):
|
|
122
285
|
commands: Tuple[str, ...]
|
|
286
|
+
secret_mounts: Optional[Tuple[str | Secret, ...]] = None
|
|
123
287
|
|
|
124
288
|
def update_hash(self, hasher: hashlib._Hash):
|
|
125
|
-
|
|
289
|
+
hash_input = "".join(self.commands)
|
|
290
|
+
|
|
291
|
+
if self.secret_mounts:
|
|
292
|
+
for secret_mount in self.secret_mounts:
|
|
293
|
+
hash_input += str(secret_mount)
|
|
294
|
+
hasher.update(hash_input.encode("utf-8"))
|
|
126
295
|
|
|
127
296
|
|
|
128
297
|
@rich.repr.auto
|
|
@@ -136,37 +305,38 @@ class WorkDir(Layer):
|
|
|
136
305
|
|
|
137
306
|
@rich.repr.auto
|
|
138
307
|
@dataclass(frozen=True, repr=True)
|
|
139
|
-
class
|
|
140
|
-
|
|
141
|
-
context_source: Path
|
|
142
|
-
image_dest: str = "."
|
|
143
|
-
|
|
144
|
-
def validate(self):
|
|
145
|
-
if not self.context_source.exists():
|
|
146
|
-
raise ValueError(f"Source folder {self.context_source.absolute()} does not exist")
|
|
147
|
-
if not self.context_source.is_dir() and self.path_type == 1:
|
|
148
|
-
raise ValueError(f"Source folder {self.context_source.absolute()} is not a directory")
|
|
149
|
-
if not self.context_source.is_file() and self.path_type == 0:
|
|
150
|
-
raise ValueError(f"Source file {self.context_source.absolute()} is not a file")
|
|
308
|
+
class DockerIgnore(Layer):
|
|
309
|
+
path: str
|
|
151
310
|
|
|
152
311
|
def update_hash(self, hasher: hashlib._Hash):
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
update_hasher_for_source(self.context_source, hasher)
|
|
156
|
-
if self.image_dest:
|
|
157
|
-
hasher.update(self.image_dest.encode("utf-8"))
|
|
312
|
+
hasher.update(self.path.encode("utf-8"))
|
|
158
313
|
|
|
159
314
|
|
|
160
315
|
@rich.repr.auto
|
|
161
316
|
@dataclass(frozen=True, repr=True)
|
|
162
|
-
class
|
|
163
|
-
|
|
164
|
-
|
|
317
|
+
class CopyConfig(Layer):
|
|
318
|
+
path_type: CopyConfigType
|
|
319
|
+
src: Path
|
|
320
|
+
dst: str
|
|
321
|
+
|
|
322
|
+
def __post_init__(self):
|
|
323
|
+
if self.path_type not in (0, 1):
|
|
324
|
+
raise ValueError(f"Invalid path_type {self.path_type}, must be 0 (file) or 1 (directory)")
|
|
325
|
+
|
|
326
|
+
def validate(self):
|
|
327
|
+
if not self.src.exists():
|
|
328
|
+
raise ValueError(f"Source folder {self.src.absolute()} does not exist")
|
|
329
|
+
if not self.src.is_dir() and self.path_type == 1:
|
|
330
|
+
raise ValueError(f"Source folder {self.src.absolute()} is not a directory")
|
|
331
|
+
if not self.src.is_file() and self.path_type == 0:
|
|
332
|
+
raise ValueError(f"Source file {self.src.absolute()} is not a file")
|
|
165
333
|
|
|
166
334
|
def update_hash(self, hasher: hashlib._Hash):
|
|
167
|
-
from ._utils import
|
|
335
|
+
from ._utils import update_hasher_for_source
|
|
168
336
|
|
|
169
|
-
|
|
337
|
+
update_hasher_for_source(self.src, hasher)
|
|
338
|
+
if self.dst:
|
|
339
|
+
hasher.update(self.dst.encode("utf-8"))
|
|
170
340
|
|
|
171
341
|
|
|
172
342
|
@rich.repr.auto
|
|
@@ -204,8 +374,9 @@ class Env(Layer):
|
|
|
204
374
|
|
|
205
375
|
Architecture = Literal["linux/amd64", "linux/arm64"]
|
|
206
376
|
|
|
207
|
-
_BASE_REGISTRY = "ghcr.io/
|
|
377
|
+
_BASE_REGISTRY = "ghcr.io/flyteorg"
|
|
208
378
|
_DEFAULT_IMAGE_NAME = "flyte"
|
|
379
|
+
_DEFAULT_IMAGE_REF_NAME = "default"
|
|
209
380
|
|
|
210
381
|
|
|
211
382
|
def _detect_python_version() -> Tuple[int, int]:
|
|
@@ -238,74 +409,82 @@ class Image:
|
|
|
238
409
|
registry: Optional[str] = field(default=None)
|
|
239
410
|
name: Optional[str] = field(default=None)
|
|
240
411
|
platform: Tuple[Architecture, ...] = field(default=("linux/amd64",))
|
|
241
|
-
tag: Optional[str] = field(default=None)
|
|
242
412
|
python_version: Tuple[int, int] = field(default_factory=_detect_python_version)
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
_identifier_override: Optional[str] = field(default=None, init=False)
|
|
246
|
-
# This is set on default images. These images are built from the base Dockerfile in this library and shouldn't be
|
|
247
|
-
# modified with additional layers.
|
|
248
|
-
is_final: bool = field(default=False)
|
|
413
|
+
# Refer to the image_refs (name:image-uri) set in CLI or config
|
|
414
|
+
_ref_name: Optional[str] = field(default=None)
|
|
249
415
|
|
|
250
416
|
# Layers to be added to the image. In init, because frozen, but users shouldn't access, so underscore.
|
|
251
417
|
_layers: Tuple[Layer, ...] = field(default_factory=tuple)
|
|
252
418
|
|
|
419
|
+
# Only settable internally.
|
|
420
|
+
_tag: Optional[str] = field(default=None, init=False)
|
|
421
|
+
|
|
253
422
|
_DEFAULT_IMAGE_PREFIXES: ClassVar = {
|
|
254
423
|
PYTHON_3_10: "py3.10-",
|
|
255
424
|
PYTHON_3_11: "py3.11-",
|
|
256
425
|
PYTHON_3_12: "py3.12-",
|
|
257
426
|
PYTHON_3_13: "py3.13-",
|
|
427
|
+
PYTHON_3_14: "py3.14-",
|
|
258
428
|
}
|
|
259
429
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
#
|
|
277
|
-
|
|
278
|
-
#
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
image_dict["layers"] = layers_str_repr
|
|
283
|
-
spec_bytes = image_dict.__str__().encode("utf-8")
|
|
284
|
-
return base64.urlsafe_b64encode(hashlib.md5(spec_bytes).digest()).decode("ascii").rstrip("=")
|
|
430
|
+
# class-level token not included in __init__
|
|
431
|
+
_token: ClassVar[object] = object()
|
|
432
|
+
|
|
433
|
+
# Underscore cuz we may rename in the future, don't expose for now,
|
|
434
|
+
_image_registry_secret: Optional[Secret] = None
|
|
435
|
+
|
|
436
|
+
# check for the guard that we put in place
|
|
437
|
+
def __post_init__(self):
|
|
438
|
+
if object.__getattribute__(self, "__dict__").pop("_guard", None) is not Image._token:
|
|
439
|
+
raise TypeError(
|
|
440
|
+
"Direct instantiation of Image not allowed, please use one of the various from_...() methods instead"
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
# Private constructor for internal use only
|
|
444
|
+
@classmethod
|
|
445
|
+
def _new(cls, **kwargs) -> Image:
|
|
446
|
+
# call the normal __init__, injecting a private keyword that users won't know
|
|
447
|
+
obj = cls.__new__(cls) # allocate
|
|
448
|
+
# set guard to prevent direct construction
|
|
449
|
+
object.__setattr__(obj, "_guard", cls._token)
|
|
450
|
+
cls.__init__(obj, **kwargs) # run dataclass generated __init__
|
|
451
|
+
return obj
|
|
285
452
|
|
|
286
453
|
def validate(self):
|
|
287
454
|
for layer in self._layers:
|
|
288
455
|
layer.validate()
|
|
289
456
|
|
|
290
457
|
@classmethod
|
|
291
|
-
def _get_default_image_for(
|
|
458
|
+
def _get_default_image_for(
|
|
459
|
+
cls,
|
|
460
|
+
python_version: Tuple[int, int],
|
|
461
|
+
flyte_version: Optional[str] = None,
|
|
462
|
+
install_flyte: bool = True,
|
|
463
|
+
platform: Optional[Tuple[Architecture, ...]] = None,
|
|
464
|
+
) -> Image:
|
|
292
465
|
# Would love a way to move this outside of this class (but still needs to be accessible via Image.auto())
|
|
293
466
|
# this default image definition may need to be updated once there is a released pypi version
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
467
|
+
from flyte._version import __version__
|
|
468
|
+
|
|
469
|
+
dev_mode = (__version__ and "dev" in __version__) and not flyte_version and install_flyte
|
|
470
|
+
if install_flyte is False:
|
|
471
|
+
preset_tag = f"py{python_version[0]}.{python_version[1]}"
|
|
472
|
+
else:
|
|
473
|
+
if flyte_version is None:
|
|
474
|
+
flyte_version = __version__.replace("+", "-")
|
|
475
|
+
suffix = flyte_version if flyte_version.startswith("v") else f"v{flyte_version}"
|
|
476
|
+
preset_tag = f"py{python_version[0]}.{python_version[1]}-{suffix}"
|
|
477
|
+
image = Image._new(
|
|
299
478
|
base_image=f"python:{python_version[0]}.{python_version[1]}-slim-bookworm",
|
|
300
479
|
registry=_BASE_REGISTRY,
|
|
301
480
|
name=_DEFAULT_IMAGE_NAME,
|
|
302
|
-
|
|
303
|
-
platform=("linux/amd64", "linux/arm64"),
|
|
481
|
+
python_version=python_version,
|
|
482
|
+
platform=("linux/amd64", "linux/arm64") if platform is None else platform,
|
|
304
483
|
)
|
|
305
484
|
labels_and_user = _DockerLines(
|
|
306
485
|
(
|
|
307
|
-
"LABEL org.opencontainers.image.authors='Union.AI <
|
|
308
|
-
"LABEL org.opencontainers.image.source=https://github.com/
|
|
486
|
+
"LABEL org.opencontainers.image.authors='Union.AI <info@union.ai>'",
|
|
487
|
+
"LABEL org.opencontainers.image.source=https://github.com/flyteorg/flyte",
|
|
309
488
|
"RUN useradd --create-home --shell /bin/bash flytekit &&"
|
|
310
489
|
" chown -R flytekit /root && chown -R flytekit /home",
|
|
311
490
|
"WORKDIR /root",
|
|
@@ -320,105 +499,80 @@ class Image:
|
|
|
320
499
|
"UV_LINK_MODE": "copy",
|
|
321
500
|
}
|
|
322
501
|
)
|
|
323
|
-
image = image.with_apt_packages(
|
|
502
|
+
image = image.with_apt_packages("build-essential", "ca-certificates")
|
|
324
503
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
base_packages.append(f"flyte=={flyte_version}")
|
|
330
|
-
image = image.with_pip_packages(base_packages)
|
|
331
|
-
else:
|
|
332
|
-
from flyte._version import __version__
|
|
333
|
-
|
|
334
|
-
if cls._is_editable_install() or (__version__ and "dev" in __version__):
|
|
335
|
-
image = image.with_pip_packages(base_packages)
|
|
336
|
-
image = image.with_local_v2()
|
|
504
|
+
if install_flyte:
|
|
505
|
+
if dev_mode:
|
|
506
|
+
if os.path.exists(DIST_FOLDER):
|
|
507
|
+
image = image.with_local_v2()
|
|
337
508
|
else:
|
|
338
|
-
|
|
339
|
-
|
|
509
|
+
flyte_version = typing.cast(str, flyte_version)
|
|
510
|
+
if Version(flyte_version).is_devrelease or Version(flyte_version).is_prerelease:
|
|
511
|
+
image = image.with_pip_packages(f"flyte=={flyte_version}", pre=True)
|
|
512
|
+
else:
|
|
513
|
+
image = image.with_pip_packages(f"flyte=={flyte_version}")
|
|
514
|
+
if not dev_mode:
|
|
515
|
+
object.__setattr__(image, "_tag", preset_tag)
|
|
340
516
|
|
|
341
517
|
return image
|
|
342
518
|
|
|
343
|
-
@staticmethod
|
|
344
|
-
def _is_editable_install():
|
|
345
|
-
"""Internal hacky function to see if the current install is editable or not."""
|
|
346
|
-
curr = Path(__file__)
|
|
347
|
-
pyproject = curr.parent.parent.parent / "pyproject.toml"
|
|
348
|
-
return pyproject.exists()
|
|
349
|
-
|
|
350
|
-
@classmethod
|
|
351
|
-
def from_uv_debian(
|
|
352
|
-
cls,
|
|
353
|
-
registry: str,
|
|
354
|
-
name: str,
|
|
355
|
-
tag: Optional[str] = None,
|
|
356
|
-
python_version: Optional[Tuple[int, int]] = None,
|
|
357
|
-
arch: Union[Architecture, Tuple[Architecture, ...]] = "linux/amd64",
|
|
358
|
-
) -> Image:
|
|
359
|
-
"""
|
|
360
|
-
This creates a new debian-based base image.
|
|
361
|
-
If using the Union or docker builders, image will have uv available and a virtualenv created at /opt/venv.
|
|
362
|
-
|
|
363
|
-
:param registry: Registry to use for the image
|
|
364
|
-
:param name: Name of the image
|
|
365
|
-
:param tag: Tag to use for the image
|
|
366
|
-
:param python_version: Python version to use for the image
|
|
367
|
-
:param arch: Architecture to use for the image, default is linux/amd64
|
|
368
|
-
:return: Image
|
|
369
|
-
"""
|
|
370
|
-
base_image = "debian:bookworm-slim"
|
|
371
|
-
plat = arch if isinstance(arch, tuple) else (arch,)
|
|
372
|
-
if python_version is None:
|
|
373
|
-
python_version = _detect_python_version()
|
|
374
|
-
img = cls(
|
|
375
|
-
base_image=base_image, name=name, registry=registry, tag=tag, platform=plat, python_version=python_version
|
|
376
|
-
)
|
|
377
|
-
return img
|
|
378
|
-
|
|
379
519
|
@classmethod
|
|
380
|
-
def
|
|
520
|
+
def from_debian_base(
|
|
381
521
|
cls,
|
|
382
522
|
python_version: Optional[Tuple[int, int]] = None,
|
|
383
523
|
flyte_version: Optional[str] = None,
|
|
524
|
+
install_flyte: bool = True,
|
|
384
525
|
registry: Optional[str] = None,
|
|
526
|
+
registry_secret: Optional[str | Secret] = None,
|
|
385
527
|
name: Optional[str] = None,
|
|
528
|
+
platform: Optional[Tuple[Architecture, ...]] = None,
|
|
386
529
|
) -> Image:
|
|
387
530
|
"""
|
|
388
531
|
Use this method to start using the default base image, built from this library's base Dockerfile
|
|
389
532
|
Default images are multi-arch amd/arm64
|
|
390
533
|
|
|
391
534
|
:param python_version: If not specified, will use the current Python version
|
|
392
|
-
:param flyte_version:
|
|
535
|
+
:param flyte_version: Flyte version to use
|
|
536
|
+
:param install_flyte: If True, will install the flyte library in the image
|
|
393
537
|
:param registry: Registry to use for the image
|
|
538
|
+
:param registry_secret: Secret to use to pull/push the private image.
|
|
394
539
|
:param name: Name of the image if you want to override the default name
|
|
540
|
+
:param platform: Platform to use for the image, default is linux/amd64, use tuple for multiple values
|
|
541
|
+
Example: ("linux/amd64", "linux/arm64")
|
|
395
542
|
|
|
396
543
|
:return: Image
|
|
397
544
|
"""
|
|
398
545
|
if python_version is None:
|
|
399
546
|
python_version = _detect_python_version()
|
|
400
547
|
|
|
401
|
-
base_image = cls._get_default_image_for(
|
|
402
|
-
|
|
403
|
-
|
|
548
|
+
base_image = cls._get_default_image_for(
|
|
549
|
+
python_version=python_version,
|
|
550
|
+
flyte_version=flyte_version,
|
|
551
|
+
install_flyte=install_flyte,
|
|
552
|
+
platform=platform,
|
|
553
|
+
)
|
|
404
554
|
|
|
405
|
-
if registry
|
|
406
|
-
return base_image.clone(registry=registry, name=name)
|
|
555
|
+
if registry or name:
|
|
556
|
+
return base_image.clone(registry=registry, name=name, registry_secret=registry_secret)
|
|
407
557
|
|
|
408
|
-
# Set this to auto for all auto images because the meaning of "auto" can change (based on logic inside
|
|
409
|
-
# _get_default_image_for, acts differently in a running task container) so let's make sure it stays auto.
|
|
410
|
-
object.__setattr__(base_image, "_identifier_override", "auto")
|
|
411
558
|
return base_image
|
|
412
559
|
|
|
413
560
|
@classmethod
|
|
414
|
-
def
|
|
561
|
+
def from_base(cls, image_uri: str) -> Image:
|
|
415
562
|
"""
|
|
416
563
|
Use this method to start with a pre-built base image. This image must already exist in the registry of course.
|
|
417
564
|
|
|
418
565
|
:param image_uri: The full URI of the image, in the format <registry>/<name>:<tag>
|
|
419
566
|
:return:
|
|
420
567
|
"""
|
|
421
|
-
img = cls(base_image=image_uri)
|
|
568
|
+
img = cls._new(base_image=image_uri)
|
|
569
|
+
return img
|
|
570
|
+
|
|
571
|
+
@classmethod
|
|
572
|
+
def from_ref_name(cls, name: str = _DEFAULT_IMAGE_REF_NAME) -> Image:
|
|
573
|
+
# NOTE: set image name as _ref_name to enable adding additional layers.
|
|
574
|
+
# See: https://github.com/flyteorg/flyte-sdk/blob/14de802701aab7b8615ffb99c650a36305ef01f7/src/flyte/_image.py#L642
|
|
575
|
+
img = cls._new(name=name, _ref_name=name)
|
|
422
576
|
return img
|
|
423
577
|
|
|
424
578
|
@classmethod
|
|
@@ -428,8 +582,14 @@ class Image:
|
|
|
428
582
|
*,
|
|
429
583
|
name: str,
|
|
430
584
|
registry: str | None = None,
|
|
585
|
+
registry_secret: Optional[str | Secret] = None,
|
|
431
586
|
python_version: Optional[Tuple[int, int]] = None,
|
|
432
|
-
|
|
587
|
+
index_url: Optional[str] = None,
|
|
588
|
+
extra_index_urls: Union[str, List[str], Tuple[str, ...], None] = None,
|
|
589
|
+
pre: bool = False,
|
|
590
|
+
extra_args: Optional[str] = None,
|
|
591
|
+
platform: Optional[Tuple[Architecture, ...]] = None,
|
|
592
|
+
secret_mounts: Optional[SecretRequest] = None,
|
|
433
593
|
) -> Image:
|
|
434
594
|
"""
|
|
435
595
|
Use this method to create a new image with the specified uv script.
|
|
@@ -447,78 +607,127 @@ class Image:
|
|
|
447
607
|
```
|
|
448
608
|
|
|
449
609
|
For more information on the uv script format, see the documentation:
|
|
450
|
-
|
|
451
|
-
UV: Declaring script dependencies</href>
|
|
610
|
+
[UV: Declaring script dependencies](https://docs.astral.sh/uv/guides/scripts/#declaring-script-dependencies)
|
|
452
611
|
|
|
453
612
|
:param name: name of the image
|
|
454
613
|
:param registry: registry to use for the image
|
|
614
|
+
:param registry_secret: Secret to use to pull/push the private image.
|
|
615
|
+
:param python_version: Python version to use for the image, if not specified, will use the current Python
|
|
616
|
+
version
|
|
455
617
|
:param script: path to the uv script
|
|
456
|
-
:param
|
|
618
|
+
:param platform: architecture to use for the image, default is linux/amd64, use tuple for multiple values
|
|
619
|
+
:param python_version: Python version for the image, if not specified, will use the current Python version
|
|
620
|
+
:param index_url: index url to use for pip install, default is None
|
|
621
|
+
:param extra_index_urls: extra index urls to use for pip install, default is True
|
|
622
|
+
:param pre: whether to allow pre-release versions, default is False
|
|
623
|
+
:param extra_args: extra arguments to pass to pip install, default is None
|
|
624
|
+
:param secret_mounts: Secret mounts to use for the image, default is None.
|
|
457
625
|
|
|
458
626
|
:return: Image
|
|
627
|
+
|
|
628
|
+
Args:
|
|
629
|
+
secret_mounts:
|
|
459
630
|
"""
|
|
460
|
-
|
|
631
|
+
ll = UVScript(
|
|
632
|
+
script=Path(script),
|
|
633
|
+
index_url=index_url,
|
|
634
|
+
extra_index_urls=_ensure_tuple(extra_index_urls) if extra_index_urls else None,
|
|
635
|
+
pre=pre,
|
|
636
|
+
extra_args=extra_args,
|
|
637
|
+
secret_mounts=_ensure_tuple(secret_mounts) if secret_mounts else None,
|
|
638
|
+
)
|
|
461
639
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
raise ValueError("registry must be specified")
|
|
473
|
-
img = cls.from_uv_debian(registry=registry, name=name, arch=arch, python_version=python_version)
|
|
474
|
-
if header.dependencies:
|
|
475
|
-
return img.with_pip_packages(header.dependencies)
|
|
476
|
-
# todo: override the _identifier_override to be the script name or a hash of the script contents
|
|
477
|
-
# This is needed because inside the image, the identifier will be computed to be something different.
|
|
478
|
-
return img
|
|
640
|
+
img = cls.from_debian_base(
|
|
641
|
+
registry=registry,
|
|
642
|
+
registry_secret=registry_secret,
|
|
643
|
+
install_flyte=False,
|
|
644
|
+
name=name,
|
|
645
|
+
python_version=python_version,
|
|
646
|
+
platform=platform,
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
return img.clone(addl_layer=ll)
|
|
479
650
|
|
|
480
651
|
def clone(
|
|
481
|
-
self,
|
|
652
|
+
self,
|
|
653
|
+
registry: Optional[str] = None,
|
|
654
|
+
registry_secret: Optional[str | Secret] = None,
|
|
655
|
+
name: Optional[str] = None,
|
|
656
|
+
base_image: Optional[str] = None,
|
|
657
|
+
python_version: Optional[Tuple[int, int]] = None,
|
|
658
|
+
addl_layer: Optional[Layer] = None,
|
|
482
659
|
) -> Image:
|
|
483
660
|
"""
|
|
484
661
|
Use this method to clone the current image and change the registry and name
|
|
485
662
|
|
|
486
663
|
:param registry: Registry to use for the image
|
|
664
|
+
:param registry_secret: Secret to use to pull/push the private image.
|
|
487
665
|
:param name: Name of the image
|
|
488
|
-
|
|
666
|
+
:param python_version: Python version for the image, if not specified, will use the current Python version
|
|
667
|
+
:param addl_layer: Additional layer to add to the image. This will be added to the end of the layers.
|
|
489
668
|
:return:
|
|
490
669
|
"""
|
|
670
|
+
from flyte import Secret
|
|
671
|
+
|
|
672
|
+
if addl_layer and self.dockerfile:
|
|
673
|
+
# We don't know how to inspect dockerfiles to know what kind it is (OS, python version, uv vs poetry, etc)
|
|
674
|
+
# so there's no guarantee any of the layering logic will work.
|
|
675
|
+
raise ValueError(
|
|
676
|
+
"Flyte current cannot add additional layers to a Dockerfile-based Image."
|
|
677
|
+
" Please amend the dockerfile directly."
|
|
678
|
+
)
|
|
491
679
|
registry = registry if registry else self.registry
|
|
492
680
|
name = name if name else self.name
|
|
681
|
+
registry_secret = registry_secret if registry_secret else self._image_registry_secret
|
|
682
|
+
base_image = base_image if base_image else self.base_image
|
|
683
|
+
if addl_layer and (not name):
|
|
684
|
+
raise ValueError(
|
|
685
|
+
f"Cannot add additional layer {addl_layer} to an image without name. Please first clone()."
|
|
686
|
+
)
|
|
493
687
|
new_layers = (*self._layers, addl_layer) if addl_layer else self._layers
|
|
494
|
-
img = Image(
|
|
495
|
-
base_image=
|
|
688
|
+
img = Image._new(
|
|
689
|
+
base_image=base_image,
|
|
496
690
|
dockerfile=self.dockerfile,
|
|
497
691
|
registry=registry,
|
|
498
692
|
name=name,
|
|
499
|
-
tag=self.tag,
|
|
500
693
|
platform=self.platform,
|
|
501
|
-
python_version=self.python_version,
|
|
502
|
-
is_final=self.is_final,
|
|
694
|
+
python_version=python_version or self.python_version,
|
|
503
695
|
_layers=new_layers,
|
|
696
|
+
_image_registry_secret=Secret(key=registry_secret) if isinstance(registry_secret, str) else registry_secret,
|
|
697
|
+
_ref_name=self._ref_name,
|
|
504
698
|
)
|
|
505
699
|
|
|
506
700
|
return img
|
|
507
701
|
|
|
508
702
|
@classmethod
|
|
509
|
-
def from_dockerfile(
|
|
703
|
+
def from_dockerfile(
|
|
704
|
+
cls, file: Path, registry: str, name: str, platform: Union[Architecture, Tuple[Architecture, ...], None] = None
|
|
705
|
+
) -> Image:
|
|
510
706
|
"""
|
|
511
|
-
Use this method to create a new image with the specified dockerfile
|
|
707
|
+
Use this method to create a new image with the specified dockerfile. Note you cannot use additional layers
|
|
708
|
+
after this, as the system doesn't attempt to parse/understand the Dockerfile, and what kind of setup it has
|
|
709
|
+
(python version, uv vs poetry, etc), so please put all logic into the dockerfile itself.
|
|
710
|
+
|
|
711
|
+
Also since Python sees paths as from the calling directory, please use Path objects with absolute paths. The
|
|
712
|
+
context for the builder will be the directory where the dockerfile is located.
|
|
512
713
|
|
|
513
714
|
:param file: path to the dockerfile
|
|
514
715
|
:param name: name of the image
|
|
515
716
|
:param registry: registry to use for the image
|
|
516
|
-
:param
|
|
717
|
+
:param platform: architecture to use for the image, default is linux/amd64, use tuple for multiple values
|
|
718
|
+
Example: ("linux/amd64", "linux/arm64")
|
|
517
719
|
|
|
518
720
|
:return:
|
|
519
721
|
"""
|
|
520
|
-
|
|
521
|
-
|
|
722
|
+
platform = _ensure_tuple(platform) if platform else None
|
|
723
|
+
kwargs = {
|
|
724
|
+
"dockerfile": file,
|
|
725
|
+
"registry": registry,
|
|
726
|
+
"name": name,
|
|
727
|
+
}
|
|
728
|
+
if platform:
|
|
729
|
+
kwargs["platform"] = platform
|
|
730
|
+
img = cls._new(**kwargs)
|
|
522
731
|
|
|
523
732
|
return img
|
|
524
733
|
|
|
@@ -531,6 +740,8 @@ class Image:
|
|
|
531
740
|
from ._utils import filehash_update
|
|
532
741
|
|
|
533
742
|
hasher = hashlib.md5()
|
|
743
|
+
if self.base_image:
|
|
744
|
+
hasher.update(self.base_image.encode("utf-8"))
|
|
534
745
|
if self.dockerfile:
|
|
535
746
|
# Note the location of the dockerfile shouldn't matter, only the contents
|
|
536
747
|
filehash_update(self.dockerfile, hasher)
|
|
@@ -541,7 +752,7 @@ class Image:
|
|
|
541
752
|
|
|
542
753
|
@property
|
|
543
754
|
def _final_tag(self) -> str:
|
|
544
|
-
t = self.
|
|
755
|
+
t = self._tag if self._tag else self._get_hash_digest()
|
|
545
756
|
return t or "latest"
|
|
546
757
|
|
|
547
758
|
@cached_property
|
|
@@ -552,6 +763,9 @@ class Image:
|
|
|
552
763
|
if self.registry and self.name:
|
|
553
764
|
tag = self._final_tag
|
|
554
765
|
return f"{self.registry}/{self.name}:{tag}"
|
|
766
|
+
elif self._ref_name and len(self._layers) == 0:
|
|
767
|
+
assert self.base_image is not None, f"Base image is not set for image ref name {self._ref_name}"
|
|
768
|
+
return self.base_image
|
|
555
769
|
elif self.name:
|
|
556
770
|
return f"{self.name}:{self._final_tag}"
|
|
557
771
|
elif self.base_image:
|
|
@@ -570,30 +784,36 @@ class Image:
|
|
|
570
784
|
new_image = self.clone(addl_layer=WorkDir(workdir=workdir))
|
|
571
785
|
return new_image
|
|
572
786
|
|
|
573
|
-
def with_requirements(
|
|
787
|
+
def with_requirements(
|
|
788
|
+
self,
|
|
789
|
+
file: str | Path,
|
|
790
|
+
secret_mounts: Optional[SecretRequest] = None,
|
|
791
|
+
) -> Image:
|
|
574
792
|
"""
|
|
575
793
|
Use this method to create a new image with the specified requirements file layered on top of the current image
|
|
576
794
|
Cannot be used in conjunction with conda
|
|
577
795
|
|
|
578
796
|
:param file: path to the requirements file, must be a .txt file
|
|
797
|
+
:param secret_mounts: list of secret to mount for the build process.
|
|
579
798
|
:return:
|
|
580
799
|
"""
|
|
581
|
-
if
|
|
582
|
-
|
|
583
|
-
if not file.is_file():
|
|
584
|
-
raise ValueError(f"Requirements file {file} is not a file")
|
|
800
|
+
if isinstance(file, str):
|
|
801
|
+
file = Path(file)
|
|
585
802
|
if file.suffix != ".txt":
|
|
586
803
|
raise ValueError(f"Requirements file {file} must have a .txt extension")
|
|
587
|
-
new_image = self.clone(
|
|
804
|
+
new_image = self.clone(
|
|
805
|
+
addl_layer=Requirements(file=file, secret_mounts=_ensure_tuple(secret_mounts) if secret_mounts else None)
|
|
806
|
+
)
|
|
588
807
|
return new_image
|
|
589
808
|
|
|
590
809
|
def with_pip_packages(
|
|
591
810
|
self,
|
|
592
|
-
packages:
|
|
811
|
+
*packages: str,
|
|
593
812
|
index_url: Optional[str] = None,
|
|
594
813
|
extra_index_urls: Union[str, List[str], Tuple[str, ...], None] = None,
|
|
595
814
|
pre: bool = False,
|
|
596
815
|
extra_args: Optional[str] = None,
|
|
816
|
+
secret_mounts: Optional[SecretRequest] = None,
|
|
597
817
|
) -> Image:
|
|
598
818
|
"""
|
|
599
819
|
Use this method to create a new image with the specified pip packages layered on top of the current image
|
|
@@ -601,9 +821,24 @@ class Image:
|
|
|
601
821
|
|
|
602
822
|
Example:
|
|
603
823
|
```python
|
|
604
|
-
@flyte.task(image=(flyte.Image
|
|
605
|
-
|
|
606
|
-
|
|
824
|
+
@flyte.task(image=(flyte.Image.from_debian_base().with_pip_packages("requests", "numpy")))
|
|
825
|
+
def my_task(x: int) -> int:
|
|
826
|
+
import numpy as np
|
|
827
|
+
return np.sum([x, 1])
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
To mount secrets during the build process to download private packages, you can use the `secret_mounts`.
|
|
831
|
+
In the below example, "GITHUB_PAT" will be mounted as env var "GITHUB_PAT",
|
|
832
|
+
and "apt-secret" will be mounted at /etc/apt/apt-secret.
|
|
833
|
+
Example:
|
|
834
|
+
```python
|
|
835
|
+
private_package = "git+https://$GITHUB_PAT@github.com/flyteorg/flytex.git@2e20a2acebfc3877d84af643fdd768edea41d533"
|
|
836
|
+
@flyte.task(
|
|
837
|
+
image=(
|
|
838
|
+
flyte.Image.from_debian_base()
|
|
839
|
+
.with_pip_packages("private_package", secret_mounts=[Secret(key="GITHUB_PAT")])
|
|
840
|
+
.with_apt_packages("git", secret_mounts=[Secret(key="apt-secret", mount="/etc/apt/apt-secret")])
|
|
841
|
+
)
|
|
607
842
|
def my_task(x: int) -> int:
|
|
608
843
|
import numpy as np
|
|
609
844
|
return np.sum([x, 1])
|
|
@@ -614,12 +849,10 @@ class Image:
|
|
|
614
849
|
:param extra_index_urls: extra index urls to use for pip install, default is None
|
|
615
850
|
:param pre: whether to allow pre-release versions, default is False
|
|
616
851
|
:param extra_args: extra arguments to pass to pip install, default is None
|
|
617
|
-
|
|
618
|
-
:param extra_args: extra arguments to pass to pip install, default is None
|
|
852
|
+
:param secret_mounts: list of secret to mount for the build process.
|
|
619
853
|
:return: Image
|
|
620
854
|
"""
|
|
621
|
-
|
|
622
|
-
new_packages: Optional[Tuple] = _ensure_tuple(packages) if packages else None
|
|
855
|
+
new_packages: Optional[Tuple] = packages or None
|
|
623
856
|
new_extra_index_urls: Optional[Tuple] = _ensure_tuple(extra_index_urls) if extra_index_urls else None
|
|
624
857
|
|
|
625
858
|
ll = PipPackages(
|
|
@@ -628,6 +861,7 @@ class Image:
|
|
|
628
861
|
extra_index_urls=new_extra_index_urls,
|
|
629
862
|
pre=pre,
|
|
630
863
|
extra_args=extra_args,
|
|
864
|
+
secret_mounts=_ensure_tuple(secret_mounts) if secret_mounts else None,
|
|
631
865
|
)
|
|
632
866
|
new_image = self.clone(addl_layer=ll)
|
|
633
867
|
return new_image
|
|
@@ -643,73 +877,161 @@ class Image:
|
|
|
643
877
|
new_image = self.clone(addl_layer=Env.from_dict(env_vars))
|
|
644
878
|
return new_image
|
|
645
879
|
|
|
646
|
-
def with_source_folder(self,
|
|
880
|
+
def with_source_folder(self, src: Path, dst: str = ".", copy_contents_only: bool = False) -> Image:
|
|
647
881
|
"""
|
|
648
882
|
Use this method to create a new image with the specified local directory layered on top of the current image.
|
|
649
883
|
If dest is not specified, it will be copied to the working directory of the image
|
|
650
884
|
|
|
651
|
-
:param
|
|
652
|
-
:param
|
|
885
|
+
:param src: root folder of the source code from the build context to be copied
|
|
886
|
+
:param dst: destination folder in the image
|
|
887
|
+
:param copy_contents_only: If True, will copy the contents of the source folder to the destination folder,
|
|
888
|
+
instead of the folder itself. Default is False.
|
|
653
889
|
:return: Image
|
|
654
890
|
"""
|
|
655
|
-
|
|
656
|
-
|
|
891
|
+
if not copy_contents_only:
|
|
892
|
+
dst = str("./" + src.name) if dst == "." else dst
|
|
893
|
+
new_image = self.clone(addl_layer=CopyConfig(path_type=1, src=src, dst=dst))
|
|
657
894
|
return new_image
|
|
658
895
|
|
|
659
|
-
def with_source_file(self,
|
|
896
|
+
def with_source_file(self, src: Path, dst: str = ".") -> Image:
|
|
660
897
|
"""
|
|
661
898
|
Use this method to create a new image with the specified local file layered on top of the current image.
|
|
662
899
|
If dest is not specified, it will be copied to the working directory of the image
|
|
663
900
|
|
|
664
|
-
:param
|
|
665
|
-
:param
|
|
901
|
+
:param src: root folder of the source code from the build context to be copied
|
|
902
|
+
:param dst: destination folder in the image
|
|
666
903
|
:return: Image
|
|
667
904
|
"""
|
|
668
|
-
|
|
669
|
-
new_image = self.clone(addl_layer=CopyConfig(path_type=0, context_source=context_source, image_dest=image_dest))
|
|
905
|
+
new_image = self.clone(addl_layer=CopyConfig(path_type=0, src=src, dst=dst))
|
|
670
906
|
return new_image
|
|
671
907
|
|
|
672
|
-
def
|
|
908
|
+
def with_dockerignore(self, path: Path) -> Image:
|
|
909
|
+
new_image = self.clone(addl_layer=DockerIgnore(path=str(path)))
|
|
910
|
+
return new_image
|
|
911
|
+
|
|
912
|
+
def with_uv_project(
|
|
913
|
+
self,
|
|
914
|
+
pyproject_file: str | Path,
|
|
915
|
+
uvlock: Path | None = None,
|
|
916
|
+
index_url: Optional[str] = None,
|
|
917
|
+
extra_index_urls: Union[List[str], Tuple[str, ...], None] = None,
|
|
918
|
+
pre: bool = False,
|
|
919
|
+
extra_args: Optional[str] = None,
|
|
920
|
+
secret_mounts: Optional[SecretRequest] = None,
|
|
921
|
+
project_install_mode: typing.Literal["dependencies_only", "install_project"] = "dependencies_only",
|
|
922
|
+
) -> Image:
|
|
673
923
|
"""
|
|
674
924
|
Use this method to create a new image with the specified uv.lock file layered on top of the current image
|
|
675
925
|
Must have a corresponding pyproject.toml file in the same directory
|
|
676
926
|
Cannot be used in conjunction with conda
|
|
677
|
-
|
|
927
|
+
|
|
928
|
+
By default, this method copies the pyproject.toml and uv.lock files into the image.
|
|
929
|
+
|
|
930
|
+
If `project_install_mode` is "install_project", it will also copy directory
|
|
931
|
+
where the pyproject.toml file is located into the image.
|
|
678
932
|
|
|
679
933
|
:param pyproject_file: path to the pyproject.toml file, needs to have a corresponding uv.lock file
|
|
680
|
-
:
|
|
934
|
+
:param uvlock: path to the uv.lock file, if not specified, will use the default uv.lock file in the same
|
|
935
|
+
directory as the pyproject.toml file. (pyproject.parent / uv.lock)
|
|
936
|
+
:param index_url: index url to use for pip install, default is None
|
|
937
|
+
:param extra_index_urls: extra index urls to use for pip install, default is None
|
|
938
|
+
:param pre: whether to allow pre-release versions, default is False
|
|
939
|
+
:param extra_args: extra arguments to pass to pip install, default is None
|
|
940
|
+
:param secret_mounts: list of secret mounts to use for the build process.
|
|
941
|
+
:param project_install_mode: whether to install the project as a package or
|
|
942
|
+
only dependencies, default is "dependencies_only"
|
|
943
|
+
:return: Image
|
|
681
944
|
"""
|
|
682
|
-
if
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
945
|
+
if isinstance(pyproject_file, str):
|
|
946
|
+
pyproject_file = Path(pyproject_file)
|
|
947
|
+
new_image = self.clone(
|
|
948
|
+
addl_layer=UVProject(
|
|
949
|
+
pyproject=pyproject_file,
|
|
950
|
+
uvlock=uvlock or (pyproject_file.parent / "uv.lock"),
|
|
951
|
+
index_url=index_url,
|
|
952
|
+
extra_index_urls=extra_index_urls,
|
|
953
|
+
pre=pre,
|
|
954
|
+
extra_args=extra_args,
|
|
955
|
+
secret_mounts=_ensure_tuple(secret_mounts) if secret_mounts else None,
|
|
956
|
+
project_install_mode=project_install_mode,
|
|
957
|
+
)
|
|
958
|
+
)
|
|
690
959
|
return new_image
|
|
691
960
|
|
|
692
|
-
def
|
|
961
|
+
def with_poetry_project(
|
|
962
|
+
self,
|
|
963
|
+
pyproject_file: str | Path,
|
|
964
|
+
poetry_lock: Path | None = None,
|
|
965
|
+
extra_args: Optional[str] = None,
|
|
966
|
+
secret_mounts: Optional[SecretRequest] = None,
|
|
967
|
+
project_install_mode: typing.Literal["dependencies_only", "install_project"] = "dependencies_only",
|
|
968
|
+
):
|
|
969
|
+
"""
|
|
970
|
+
Use this method to create a new image with the specified pyproject.toml layered on top of the current image.
|
|
971
|
+
Must have a corresponding pyproject.toml file in the same directory.
|
|
972
|
+
Cannot be used in conjunction with conda.
|
|
973
|
+
|
|
974
|
+
By default, this method copies the entire project into the image,
|
|
975
|
+
including files such as pyproject.toml, poetry.lock, and the src/ directory.
|
|
976
|
+
|
|
977
|
+
If you prefer not to install the current project, you can pass through `extra_args`
|
|
978
|
+
`--no-root`. In this case, the image builder will only copy pyproject.toml and poetry.lock
|
|
979
|
+
into the image.
|
|
980
|
+
|
|
981
|
+
:param pyproject_file: Path to the pyproject.toml file. A poetry.lock file must exist in the same directory
|
|
982
|
+
unless `poetry_lock` is explicitly provided.
|
|
983
|
+
:param poetry_lock: Path to the poetry.lock file. If not specified, the default is the file named
|
|
984
|
+
'poetry.lock' in the same directory as `pyproject_file` (pyproject.parent / "poetry.lock").
|
|
985
|
+
:param extra_args: Extra arguments to pass through to the package installer/resolver, default is None.
|
|
986
|
+
:param secret_mounts: Secrets to make available during dependency resolution/build (e.g., private indexes).
|
|
987
|
+
:param project_install_mode: whether to install the project as a package or
|
|
988
|
+
only dependencies, default is "dependencies_only"
|
|
989
|
+
:return: Image
|
|
990
|
+
"""
|
|
991
|
+
if isinstance(pyproject_file, str):
|
|
992
|
+
pyproject_file = Path(pyproject_file)
|
|
993
|
+
new_image = self.clone(
|
|
994
|
+
addl_layer=PoetryProject(
|
|
995
|
+
pyproject=pyproject_file,
|
|
996
|
+
poetry_lock=poetry_lock or (pyproject_file.parent / "poetry.lock"),
|
|
997
|
+
extra_args=extra_args,
|
|
998
|
+
secret_mounts=_ensure_tuple(secret_mounts) if secret_mounts else None,
|
|
999
|
+
project_install_mode=project_install_mode,
|
|
1000
|
+
)
|
|
1001
|
+
)
|
|
1002
|
+
return new_image
|
|
1003
|
+
|
|
1004
|
+
def with_apt_packages(self, *packages: str, secret_mounts: Optional[SecretRequest] = None) -> Image:
|
|
693
1005
|
"""
|
|
694
1006
|
Use this method to create a new image with the specified apt packages layered on top of the current image
|
|
695
1007
|
|
|
696
1008
|
:param packages: list of apt packages to install
|
|
1009
|
+
:param secret_mounts: list of secret mounts to use for the build process.
|
|
697
1010
|
:return: Image
|
|
698
1011
|
"""
|
|
699
|
-
|
|
700
|
-
|
|
1012
|
+
new_image = self.clone(
|
|
1013
|
+
addl_layer=AptPackages(
|
|
1014
|
+
packages=packages,
|
|
1015
|
+
secret_mounts=_ensure_tuple(secret_mounts) if secret_mounts else None,
|
|
1016
|
+
)
|
|
1017
|
+
)
|
|
701
1018
|
return new_image
|
|
702
1019
|
|
|
703
|
-
def with_commands(self, commands: List[str]) -> Image:
|
|
1020
|
+
def with_commands(self, commands: List[str], secret_mounts: Optional[SecretRequest] = None) -> Image:
|
|
704
1021
|
"""
|
|
705
1022
|
Use this method to create a new image with the specified commands layered on top of the current image
|
|
706
1023
|
Be sure not to use RUN in your command.
|
|
707
1024
|
|
|
708
1025
|
:param commands: list of commands to run
|
|
1026
|
+
:param secret_mounts: list of secret mounts to use for the build process.
|
|
709
1027
|
:return: Image
|
|
710
1028
|
"""
|
|
711
1029
|
new_commands: Tuple = _ensure_tuple(commands)
|
|
712
|
-
new_image = self.clone(
|
|
1030
|
+
new_image = self.clone(
|
|
1031
|
+
addl_layer=Commands(
|
|
1032
|
+
commands=new_commands, secret_mounts=_ensure_tuple(secret_mounts) if secret_mounts else None
|
|
1033
|
+
)
|
|
1034
|
+
)
|
|
713
1035
|
return new_image
|
|
714
1036
|
|
|
715
1037
|
def with_local_v2(self) -> Image:
|
|
@@ -719,23 +1041,11 @@ class Image:
|
|
|
719
1041
|
|
|
720
1042
|
:return: Image
|
|
721
1043
|
"""
|
|
722
|
-
|
|
723
|
-
# Manually declare the CopyConfig instead of using with_source_folder so we can set the hashing
|
|
1044
|
+
# Manually declare the PythonWheel so we can set the hashing
|
|
724
1045
|
# used to compute the identifier. Can remove if we ever decide to expose the lambda in with_ commands
|
|
725
|
-
with_dist = self.clone(
|
|
726
|
-
addl_layer=CopyConfig(
|
|
727
|
-
path_type=1, context_source=dist_folder, image_dest=".", _compute_identifier=lambda x: "/dist"
|
|
728
|
-
)
|
|
729
|
-
)
|
|
1046
|
+
with_dist = self.clone(addl_layer=PythonWheels(wheel_dir=DIST_FOLDER, package_name="flyte", pre=True))
|
|
730
1047
|
|
|
731
|
-
return with_dist
|
|
732
|
-
[
|
|
733
|
-
"--mount=type=cache,sharing=locked,mode=0777,target=/root/.cache/uv,id=uv"
|
|
734
|
-
" --mount=from=uv,source=/uv,target=/usr/bin/uv"
|
|
735
|
-
" --mount=source=dist,target=/dist,type=bind"
|
|
736
|
-
" uv pip install --python $VIRTUALENV $(ls /dist/*whl)"
|
|
737
|
-
]
|
|
738
|
-
)
|
|
1048
|
+
return with_dist
|
|
739
1049
|
|
|
740
1050
|
def __img_str__(self) -> str:
|
|
741
1051
|
"""
|