flytekitplugins-perian-job 1.12.0__tar.gz
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.
- flytekitplugins-perian_job-1.12.0/PKG-INFO +92 -0
- flytekitplugins-perian_job-1.12.0/README.md +70 -0
- flytekitplugins-perian_job-1.12.0/flytekitplugins/perian_job/__init__.py +2 -0
- flytekitplugins-perian_job-1.12.0/flytekitplugins/perian_job/agent.py +200 -0
- flytekitplugins-perian_job-1.12.0/flytekitplugins/perian_job/task.py +98 -0
- flytekitplugins-perian_job-1.12.0/flytekitplugins_perian_job.egg-info/PKG-INFO +92 -0
- flytekitplugins-perian_job-1.12.0/flytekitplugins_perian_job.egg-info/SOURCES.txt +14 -0
- flytekitplugins-perian_job-1.12.0/flytekitplugins_perian_job.egg-info/dependency_links.txt +1 -0
- flytekitplugins-perian_job-1.12.0/flytekitplugins_perian_job.egg-info/entry_points.txt +2 -0
- flytekitplugins-perian_job-1.12.0/flytekitplugins_perian_job.egg-info/namespace_packages.txt +1 -0
- flytekitplugins-perian_job-1.12.0/flytekitplugins_perian_job.egg-info/requires.txt +2 -0
- flytekitplugins-perian_job-1.12.0/flytekitplugins_perian_job.egg-info/top_level.txt +1 -0
- flytekitplugins-perian_job-1.12.0/setup.cfg +4 -0
- flytekitplugins-perian_job-1.12.0/setup.py +39 -0
- flytekitplugins-perian_job-1.12.0/tests/__init__.py +0 -0
- flytekitplugins-perian_job-1.12.0/tests/test_perian.py +43 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: flytekitplugins-perian_job
|
|
3
|
+
Version: 1.12.0
|
|
4
|
+
Summary: Flyte agent for Perian Job Platform (perian.io)
|
|
5
|
+
Author: Omar Tarabai
|
|
6
|
+
Author-email: otarabai@perian.io
|
|
7
|
+
License: apache2
|
|
8
|
+
Classifier: Intended Audience :: Science/Research
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Classifier: Topic :: Software Development
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.8
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# Flytekit Perian Job Platform Plugin
|
|
24
|
+
|
|
25
|
+
Flyte Agent plugin for executing Flyte tasks on Perian Job Platform (perian.io).
|
|
26
|
+
|
|
27
|
+
Perian Job Platform is still in closed beta. Contact support@perian.io if you are interested in trying it out.
|
|
28
|
+
|
|
29
|
+
To install the plugin, run the following command:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install flytekitplugins-perian-job
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Getting Started
|
|
36
|
+
|
|
37
|
+
This plugin allows executing `PythonFunctionTask` on Perian.
|
|
38
|
+
|
|
39
|
+
An [ImageSpec](https://docs.flyte.org/en/latest/user_guide/customizing_dependencies/imagespec.html) need to be built with the perian agent plugin installed.
|
|
40
|
+
|
|
41
|
+
### Parameters
|
|
42
|
+
|
|
43
|
+
The following parameters can be used to set the requirements for the Perian task. If any of the requirements are skipped, it is replaced with the cheapest option. At least one requirement value should be set.
|
|
44
|
+
* `cores`: Number of CPU cores
|
|
45
|
+
* `memory`: Amount of memory in GB
|
|
46
|
+
* `accelerators`: Number of accelerators
|
|
47
|
+
* `accelerator_type`: Type of accelerator (e.g. 'A100'). For a full list of supported accelerators, use the perian CLI list-accelerators command.
|
|
48
|
+
* `country_code`: Country code to run the job in (e.g. 'DE')
|
|
49
|
+
|
|
50
|
+
### Credentials
|
|
51
|
+
|
|
52
|
+
The following [secrets](https://docs.flyte.org/en/latest/user_guide/productionizing/secrets.html) are required to be defined for the agent server:
|
|
53
|
+
* Perian credentials:
|
|
54
|
+
* `perian_organization`
|
|
55
|
+
* `perian_token`
|
|
56
|
+
* AWS credentials for accessing the Flyte storage bucket. You might need to create long-lived credentials to avoid expiry when running tasks. These credentials are never logged by Perian and are stored only until it is used, then immediately deleted:
|
|
57
|
+
* `aws_access_key_id`
|
|
58
|
+
* `aws_secret_access_key`
|
|
59
|
+
* (Optional) Custom docker registry for pulling the Flyte image:
|
|
60
|
+
* `docker_registry_url`
|
|
61
|
+
* `docker_registry_username`
|
|
62
|
+
* `docker_registry_password`
|
|
63
|
+
|
|
64
|
+
### Example
|
|
65
|
+
|
|
66
|
+
`example.py` workflow example:
|
|
67
|
+
```python
|
|
68
|
+
from flytekit import ImageSpec, task, workflow
|
|
69
|
+
from flytekitplugins.perian_job import PerianConfig
|
|
70
|
+
|
|
71
|
+
image_spec = ImageSpec(
|
|
72
|
+
name="flyte-test",
|
|
73
|
+
registry="my-registry",
|
|
74
|
+
python_version="3.11",
|
|
75
|
+
apt_packages=["wget", "curl", "git"],
|
|
76
|
+
packages=[
|
|
77
|
+
"flytekitplugins-perian-job",
|
|
78
|
+
],
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
@task(container_image=image_spec,
|
|
82
|
+
task_config=PerianConfig(
|
|
83
|
+
accelerators=1,
|
|
84
|
+
accelerator_type="A100",
|
|
85
|
+
))
|
|
86
|
+
def perian_hello(name: str) -> str:
|
|
87
|
+
return f"hello {name}!"
|
|
88
|
+
|
|
89
|
+
@workflow
|
|
90
|
+
def my_wf(name: str = "world") -> str:
|
|
91
|
+
return perian_hello(name=name)
|
|
92
|
+
```
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Flytekit Perian Job Platform Plugin
|
|
2
|
+
|
|
3
|
+
Flyte Agent plugin for executing Flyte tasks on Perian Job Platform (perian.io).
|
|
4
|
+
|
|
5
|
+
Perian Job Platform is still in closed beta. Contact support@perian.io if you are interested in trying it out.
|
|
6
|
+
|
|
7
|
+
To install the plugin, run the following command:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install flytekitplugins-perian-job
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Getting Started
|
|
14
|
+
|
|
15
|
+
This plugin allows executing `PythonFunctionTask` on Perian.
|
|
16
|
+
|
|
17
|
+
An [ImageSpec](https://docs.flyte.org/en/latest/user_guide/customizing_dependencies/imagespec.html) need to be built with the perian agent plugin installed.
|
|
18
|
+
|
|
19
|
+
### Parameters
|
|
20
|
+
|
|
21
|
+
The following parameters can be used to set the requirements for the Perian task. If any of the requirements are skipped, it is replaced with the cheapest option. At least one requirement value should be set.
|
|
22
|
+
* `cores`: Number of CPU cores
|
|
23
|
+
* `memory`: Amount of memory in GB
|
|
24
|
+
* `accelerators`: Number of accelerators
|
|
25
|
+
* `accelerator_type`: Type of accelerator (e.g. 'A100'). For a full list of supported accelerators, use the perian CLI list-accelerators command.
|
|
26
|
+
* `country_code`: Country code to run the job in (e.g. 'DE')
|
|
27
|
+
|
|
28
|
+
### Credentials
|
|
29
|
+
|
|
30
|
+
The following [secrets](https://docs.flyte.org/en/latest/user_guide/productionizing/secrets.html) are required to be defined for the agent server:
|
|
31
|
+
* Perian credentials:
|
|
32
|
+
* `perian_organization`
|
|
33
|
+
* `perian_token`
|
|
34
|
+
* AWS credentials for accessing the Flyte storage bucket. You might need to create long-lived credentials to avoid expiry when running tasks. These credentials are never logged by Perian and are stored only until it is used, then immediately deleted:
|
|
35
|
+
* `aws_access_key_id`
|
|
36
|
+
* `aws_secret_access_key`
|
|
37
|
+
* (Optional) Custom docker registry for pulling the Flyte image:
|
|
38
|
+
* `docker_registry_url`
|
|
39
|
+
* `docker_registry_username`
|
|
40
|
+
* `docker_registry_password`
|
|
41
|
+
|
|
42
|
+
### Example
|
|
43
|
+
|
|
44
|
+
`example.py` workflow example:
|
|
45
|
+
```python
|
|
46
|
+
from flytekit import ImageSpec, task, workflow
|
|
47
|
+
from flytekitplugins.perian_job import PerianConfig
|
|
48
|
+
|
|
49
|
+
image_spec = ImageSpec(
|
|
50
|
+
name="flyte-test",
|
|
51
|
+
registry="my-registry",
|
|
52
|
+
python_version="3.11",
|
|
53
|
+
apt_packages=["wget", "curl", "git"],
|
|
54
|
+
packages=[
|
|
55
|
+
"flytekitplugins-perian-job",
|
|
56
|
+
],
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
@task(container_image=image_spec,
|
|
60
|
+
task_config=PerianConfig(
|
|
61
|
+
accelerators=1,
|
|
62
|
+
accelerator_type="A100",
|
|
63
|
+
))
|
|
64
|
+
def perian_hello(name: str) -> str:
|
|
65
|
+
return f"hello {name}!"
|
|
66
|
+
|
|
67
|
+
@workflow
|
|
68
|
+
def my_wf(name: str = "world") -> str:
|
|
69
|
+
return perian_hello(name=name)
|
|
70
|
+
```
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import shlex
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from flyteidl.core.execution_pb2 import TaskExecution
|
|
6
|
+
from perian import (
|
|
7
|
+
AcceleratorQueryInput,
|
|
8
|
+
ApiClient,
|
|
9
|
+
Configuration,
|
|
10
|
+
CpuQueryInput,
|
|
11
|
+
CreateJobRequest,
|
|
12
|
+
DockerRegistryCredentials,
|
|
13
|
+
DockerRunParameters,
|
|
14
|
+
InstanceTypeQueryInput,
|
|
15
|
+
JobApi,
|
|
16
|
+
JobStatus,
|
|
17
|
+
MemoryQueryInput,
|
|
18
|
+
Name,
|
|
19
|
+
ProviderQueryInput,
|
|
20
|
+
RegionQueryInput,
|
|
21
|
+
Size,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
from flytekit import current_context
|
|
25
|
+
from flytekit.exceptions.base import FlyteException
|
|
26
|
+
from flytekit.exceptions.user import FlyteUserException
|
|
27
|
+
from flytekit.extend.backend.base_agent import AgentRegistry, AsyncAgentBase, Resource, ResourceMeta
|
|
28
|
+
from flytekit.loggers import logger
|
|
29
|
+
from flytekit.models.literals import LiteralMap
|
|
30
|
+
from flytekit.models.task import TaskTemplate
|
|
31
|
+
|
|
32
|
+
PERIAN_API_URL = "https://api.perian.cloud"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class PerianMetadata(ResourceMeta):
|
|
37
|
+
"""Metadata for Perian jobs"""
|
|
38
|
+
|
|
39
|
+
job_id: str
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class PerianAgent(AsyncAgentBase):
|
|
43
|
+
"""Flyte Agent for executing tasks on Perian"""
|
|
44
|
+
|
|
45
|
+
name = "Perian Agent"
|
|
46
|
+
|
|
47
|
+
def __init__(self):
|
|
48
|
+
logger.info("Initializing Perian agent")
|
|
49
|
+
super().__init__(task_type_name="perian_task", metadata_type=PerianMetadata)
|
|
50
|
+
|
|
51
|
+
async def create(
|
|
52
|
+
self,
|
|
53
|
+
task_template: TaskTemplate,
|
|
54
|
+
inputs: Optional[LiteralMap],
|
|
55
|
+
output_prefix: Optional[str],
|
|
56
|
+
**kwargs,
|
|
57
|
+
) -> PerianMetadata:
|
|
58
|
+
logger.info("Creating new Perian job")
|
|
59
|
+
logger.debug("Task template: %s", task_template.__dict__)
|
|
60
|
+
|
|
61
|
+
config = Configuration(host=PERIAN_API_URL)
|
|
62
|
+
job_request = self._build_create_job_request(task_template)
|
|
63
|
+
with ApiClient(config) as api_client:
|
|
64
|
+
api_instance = JobApi(api_client)
|
|
65
|
+
response = api_instance.create_job(
|
|
66
|
+
create_job_request=job_request,
|
|
67
|
+
_headers=self._build_headers(),
|
|
68
|
+
)
|
|
69
|
+
if response.status_code != 200:
|
|
70
|
+
raise FlyteException(f"Failed to create Perian job: {response.text}")
|
|
71
|
+
|
|
72
|
+
return PerianMetadata(job_id=response.id)
|
|
73
|
+
|
|
74
|
+
def get(self, resource_meta: PerianMetadata, **kwargs) -> Resource:
|
|
75
|
+
job_id = resource_meta.job_id
|
|
76
|
+
logger.info("Getting Perian job status: %s", job_id)
|
|
77
|
+
config = Configuration(host=PERIAN_API_URL)
|
|
78
|
+
with ApiClient(config) as api_client:
|
|
79
|
+
api_instance = JobApi(api_client)
|
|
80
|
+
response = api_instance.get_job_by_id(
|
|
81
|
+
job_id=str(job_id),
|
|
82
|
+
_headers=self._build_headers(),
|
|
83
|
+
)
|
|
84
|
+
if response.status_code != 200:
|
|
85
|
+
raise FlyteException(f"Failed to get Perian job status: {response.text}")
|
|
86
|
+
if not response.jobs:
|
|
87
|
+
raise FlyteException(f"Perian job not found: {job_id}")
|
|
88
|
+
job = response.jobs[0]
|
|
89
|
+
|
|
90
|
+
return Resource(
|
|
91
|
+
phase=self._perian_job_status_to_flyte_phase(job.status),
|
|
92
|
+
message=job.logs,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def delete(self, resource_meta: PerianMetadata, **kwargs):
|
|
96
|
+
job_id = resource_meta.job_id
|
|
97
|
+
logger.info("Cancelling Perian job: %s", job_id)
|
|
98
|
+
config = Configuration(host=PERIAN_API_URL)
|
|
99
|
+
with ApiClient(config) as api_client:
|
|
100
|
+
api_instance = JobApi(api_client)
|
|
101
|
+
response = api_instance.cancel_job(
|
|
102
|
+
job_id=str(job_id),
|
|
103
|
+
_headers=self._build_headers(),
|
|
104
|
+
)
|
|
105
|
+
if response.status_code != 200:
|
|
106
|
+
raise FlyteException(f"Failed to cancel Perian job: {response.text}")
|
|
107
|
+
|
|
108
|
+
def _build_create_job_request(self, task_template: TaskTemplate) -> CreateJobRequest:
|
|
109
|
+
params = task_template.custom
|
|
110
|
+
secrets = current_context().secrets
|
|
111
|
+
|
|
112
|
+
# Build instance type requirements
|
|
113
|
+
reqs = InstanceTypeQueryInput()
|
|
114
|
+
if params.get("cores"):
|
|
115
|
+
reqs.cpu = CpuQueryInput(cores=int(params["cores"]))
|
|
116
|
+
if params.get("memory"):
|
|
117
|
+
reqs.ram = MemoryQueryInput(size=Size(params["memory"]))
|
|
118
|
+
if any([params.get("accelerators"), params.get("accelerator_type")]):
|
|
119
|
+
reqs.accelerator = AcceleratorQueryInput()
|
|
120
|
+
if params.get("accelerators"):
|
|
121
|
+
reqs.accelerator.no = int(params["accelerators"])
|
|
122
|
+
if params.get("accelerator_type"):
|
|
123
|
+
reqs.accelerator.name = Name(params["accelerator_type"])
|
|
124
|
+
if params.get("country_code"):
|
|
125
|
+
reqs.region = RegionQueryInput(location=params["country_code"])
|
|
126
|
+
if params.get("provider"):
|
|
127
|
+
reqs.provider = ProviderQueryInput(name_short=params["provider"])
|
|
128
|
+
|
|
129
|
+
docker_run = DockerRunParameters()
|
|
130
|
+
# Credentials to the Flyte storage S3 bucket need to be passed to the Perian job.
|
|
131
|
+
aws_access_key_id = secrets.get("aws_access_key_id")
|
|
132
|
+
aws_secret_access_key = secrets.get("aws_secret_access_key")
|
|
133
|
+
if not aws_access_key_id or not aws_secret_access_key:
|
|
134
|
+
raise FlyteUserException(
|
|
135
|
+
"AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY for the Flyte storage bucket "
|
|
136
|
+
"must be provided in the secrets"
|
|
137
|
+
)
|
|
138
|
+
docker_run.env_variables = {
|
|
139
|
+
"AWS_ACCESS_KEY_ID": aws_access_key_id,
|
|
140
|
+
"AWS_SECRET_ACCESS_KEY": aws_secret_access_key,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
docker_registry = None
|
|
144
|
+
try:
|
|
145
|
+
dr_url = secrets.get("docker_registry_url")
|
|
146
|
+
dr_username = secrets.get("docker_registry_username")
|
|
147
|
+
dr_password = secrets.get("docker_registry_password")
|
|
148
|
+
if any([dr_url, dr_username, dr_password]):
|
|
149
|
+
docker_registry = DockerRegistryCredentials(
|
|
150
|
+
url=dr_url,
|
|
151
|
+
username=dr_username,
|
|
152
|
+
password=dr_password,
|
|
153
|
+
)
|
|
154
|
+
except ValueError:
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
container = task_template.container
|
|
158
|
+
if ":" in container.image:
|
|
159
|
+
docker_run.image_name, docker_run.image_tag = container.image.rsplit(":", 1)
|
|
160
|
+
else:
|
|
161
|
+
docker_run.image_name = container.image
|
|
162
|
+
if container.args:
|
|
163
|
+
docker_run.command = shlex.join(container.args)
|
|
164
|
+
|
|
165
|
+
return CreateJobRequest(
|
|
166
|
+
auto_failover_instance_type=True,
|
|
167
|
+
requirements=reqs,
|
|
168
|
+
docker_run_parameters=docker_run,
|
|
169
|
+
docker_registry_credentials=docker_registry,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
def _build_headers(self) -> dict:
|
|
173
|
+
secrets = current_context().secrets
|
|
174
|
+
org = secrets.get("perian_organization")
|
|
175
|
+
token = secrets.get("perian_token")
|
|
176
|
+
if not org or not token:
|
|
177
|
+
raise FlyteUserException(
|
|
178
|
+
"perian_organization and perian_token must be provided in the secrets")
|
|
179
|
+
return {
|
|
180
|
+
"X-PERIAN-AUTH-ORG": org,
|
|
181
|
+
"Authorization": "Bearer " + token,
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
def _perian_job_status_to_flyte_phase(self, status: JobStatus) -> TaskExecution.Phase:
|
|
185
|
+
status_map = {
|
|
186
|
+
JobStatus.QUEUED: TaskExecution.QUEUED,
|
|
187
|
+
JobStatus.INITIALIZING: TaskExecution.INITIALIZING,
|
|
188
|
+
JobStatus.RUNNING: TaskExecution.RUNNING,
|
|
189
|
+
JobStatus.DONE: TaskExecution.SUCCEEDED,
|
|
190
|
+
JobStatus.SERVERERROR: TaskExecution.FAILED,
|
|
191
|
+
JobStatus.USERERROR: TaskExecution.FAILED,
|
|
192
|
+
JobStatus.CANCELLED: TaskExecution.ABORTED,
|
|
193
|
+
}
|
|
194
|
+
if status == JobStatus.UNDEFINED:
|
|
195
|
+
raise FlyteException("Undefined Perian job status")
|
|
196
|
+
return status_map[status]
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
# To register the Perian agent
|
|
200
|
+
AgentRegistry.register(PerianAgent())
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Any, Callable, Dict, Optional, Union
|
|
3
|
+
|
|
4
|
+
from google.protobuf import json_format
|
|
5
|
+
from google.protobuf.struct_pb2 import Struct
|
|
6
|
+
|
|
7
|
+
from flytekit import FlyteContextManager, PythonFunctionTask, logger
|
|
8
|
+
from flytekit.configuration import SerializationSettings
|
|
9
|
+
from flytekit.exceptions.user import FlyteUserException
|
|
10
|
+
from flytekit.extend import TaskPlugins
|
|
11
|
+
from flytekit.extend.backend.base_agent import AsyncAgentExecutorMixin
|
|
12
|
+
from flytekit.image_spec import ImageSpec
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class PerianConfig:
|
|
17
|
+
"""Used to configure a Perian Task"""
|
|
18
|
+
|
|
19
|
+
# Number of CPU cores
|
|
20
|
+
cores: Optional[int] = None
|
|
21
|
+
# Amount of memory in GB
|
|
22
|
+
memory: Optional[int] = None
|
|
23
|
+
# Number of accelerators
|
|
24
|
+
accelerators: Optional[int] = None
|
|
25
|
+
# Type of accelerator (e.g. 'A100')
|
|
26
|
+
# For a full list of supported accelerators, use the perian CLI list-accelerators command
|
|
27
|
+
accelerator_type: Optional[str] = None
|
|
28
|
+
# Country code to run the job in (e.g. 'DE')
|
|
29
|
+
country_code: Optional[str] = None
|
|
30
|
+
# Cloud provider to run the job on
|
|
31
|
+
provider: Optional[str] = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class PerianTask(AsyncAgentExecutorMixin, PythonFunctionTask):
|
|
35
|
+
"""A special task type for running tasks on Perian Job Platform (perian.io)"""
|
|
36
|
+
|
|
37
|
+
_TASK_TYPE = "perian_task"
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
task_config: Optional[PerianConfig],
|
|
42
|
+
task_function: Callable,
|
|
43
|
+
container_image: Optional[Union[str, ImageSpec]] = None,
|
|
44
|
+
**kwargs,
|
|
45
|
+
):
|
|
46
|
+
super().__init__(
|
|
47
|
+
task_config=task_config,
|
|
48
|
+
task_function=task_function,
|
|
49
|
+
container_image=container_image,
|
|
50
|
+
task_type=self._TASK_TYPE,
|
|
51
|
+
**kwargs,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def execute(self, **kwargs) -> Any:
|
|
55
|
+
if isinstance(self.task_config, PerianConfig):
|
|
56
|
+
# Use the Perian agent to run it by default.
|
|
57
|
+
try:
|
|
58
|
+
ctx = FlyteContextManager.current_context()
|
|
59
|
+
if not ctx.file_access.is_remote(ctx.file_access.raw_output_prefix):
|
|
60
|
+
raise ValueError(
|
|
61
|
+
"To submit a Perian job locally,"
|
|
62
|
+
" please set --raw-output-data-prefix to a remote path. e.g. s3://, gcs//, etc."
|
|
63
|
+
)
|
|
64
|
+
if ctx.execution_state and ctx.execution_state.is_local_execution():
|
|
65
|
+
return AsyncAgentExecutorMixin.execute(self, **kwargs)
|
|
66
|
+
except Exception as e:
|
|
67
|
+
logger.error("Agent failed to run the task with error: %s", e)
|
|
68
|
+
raise
|
|
69
|
+
return PythonFunctionTask.execute(self, **kwargs)
|
|
70
|
+
|
|
71
|
+
def get_custom(self, settings: SerializationSettings) -> Dict[str, Any]:
|
|
72
|
+
"""
|
|
73
|
+
Return plugin-specific data as a serializable dictionary.
|
|
74
|
+
"""
|
|
75
|
+
config = {
|
|
76
|
+
"cores": self.task_config.cores,
|
|
77
|
+
"memory": self.task_config.memory,
|
|
78
|
+
"accelerators": self.task_config.accelerators,
|
|
79
|
+
"accelerator_type": self.task_config.accelerator_type,
|
|
80
|
+
"country_code": _validate_and_format_country_code(self.task_config.country_code),
|
|
81
|
+
"provider": self.task_config.provider,
|
|
82
|
+
}
|
|
83
|
+
config = {k: v for k, v in config.items() if v is not None}
|
|
84
|
+
s = Struct()
|
|
85
|
+
s.update(config)
|
|
86
|
+
return json_format.MessageToDict(s)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _validate_and_format_country_code(country_code: Optional[str]) -> Optional[str]:
|
|
90
|
+
if not country_code:
|
|
91
|
+
return None
|
|
92
|
+
if len(country_code) != 2:
|
|
93
|
+
raise FlyteUserException("Invalid country code. Please provide a valid two-letter country code. (e.g. DE)")
|
|
94
|
+
return country_code.upper()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# Inject the Perian plugin into flytekits dynamic plugin loading system
|
|
98
|
+
TaskPlugins.register_pythontask_plugin(PerianConfig, PerianTask)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: flytekitplugins-perian-job
|
|
3
|
+
Version: 1.12.0
|
|
4
|
+
Summary: Flyte agent for Perian Job Platform (perian.io)
|
|
5
|
+
Author: Omar Tarabai
|
|
6
|
+
Author-email: otarabai@perian.io
|
|
7
|
+
License: apache2
|
|
8
|
+
Classifier: Intended Audience :: Science/Research
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Classifier: Topic :: Software Development
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.8
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# Flytekit Perian Job Platform Plugin
|
|
24
|
+
|
|
25
|
+
Flyte Agent plugin for executing Flyte tasks on Perian Job Platform (perian.io).
|
|
26
|
+
|
|
27
|
+
Perian Job Platform is still in closed beta. Contact support@perian.io if you are interested in trying it out.
|
|
28
|
+
|
|
29
|
+
To install the plugin, run the following command:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install flytekitplugins-perian-job
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Getting Started
|
|
36
|
+
|
|
37
|
+
This plugin allows executing `PythonFunctionTask` on Perian.
|
|
38
|
+
|
|
39
|
+
An [ImageSpec](https://docs.flyte.org/en/latest/user_guide/customizing_dependencies/imagespec.html) need to be built with the perian agent plugin installed.
|
|
40
|
+
|
|
41
|
+
### Parameters
|
|
42
|
+
|
|
43
|
+
The following parameters can be used to set the requirements for the Perian task. If any of the requirements are skipped, it is replaced with the cheapest option. At least one requirement value should be set.
|
|
44
|
+
* `cores`: Number of CPU cores
|
|
45
|
+
* `memory`: Amount of memory in GB
|
|
46
|
+
* `accelerators`: Number of accelerators
|
|
47
|
+
* `accelerator_type`: Type of accelerator (e.g. 'A100'). For a full list of supported accelerators, use the perian CLI list-accelerators command.
|
|
48
|
+
* `country_code`: Country code to run the job in (e.g. 'DE')
|
|
49
|
+
|
|
50
|
+
### Credentials
|
|
51
|
+
|
|
52
|
+
The following [secrets](https://docs.flyte.org/en/latest/user_guide/productionizing/secrets.html) are required to be defined for the agent server:
|
|
53
|
+
* Perian credentials:
|
|
54
|
+
* `perian_organization`
|
|
55
|
+
* `perian_token`
|
|
56
|
+
* AWS credentials for accessing the Flyte storage bucket. You might need to create long-lived credentials to avoid expiry when running tasks. These credentials are never logged by Perian and are stored only until it is used, then immediately deleted:
|
|
57
|
+
* `aws_access_key_id`
|
|
58
|
+
* `aws_secret_access_key`
|
|
59
|
+
* (Optional) Custom docker registry for pulling the Flyte image:
|
|
60
|
+
* `docker_registry_url`
|
|
61
|
+
* `docker_registry_username`
|
|
62
|
+
* `docker_registry_password`
|
|
63
|
+
|
|
64
|
+
### Example
|
|
65
|
+
|
|
66
|
+
`example.py` workflow example:
|
|
67
|
+
```python
|
|
68
|
+
from flytekit import ImageSpec, task, workflow
|
|
69
|
+
from flytekitplugins.perian_job import PerianConfig
|
|
70
|
+
|
|
71
|
+
image_spec = ImageSpec(
|
|
72
|
+
name="flyte-test",
|
|
73
|
+
registry="my-registry",
|
|
74
|
+
python_version="3.11",
|
|
75
|
+
apt_packages=["wget", "curl", "git"],
|
|
76
|
+
packages=[
|
|
77
|
+
"flytekitplugins-perian-job",
|
|
78
|
+
],
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
@task(container_image=image_spec,
|
|
82
|
+
task_config=PerianConfig(
|
|
83
|
+
accelerators=1,
|
|
84
|
+
accelerator_type="A100",
|
|
85
|
+
))
|
|
86
|
+
def perian_hello(name: str) -> str:
|
|
87
|
+
return f"hello {name}!"
|
|
88
|
+
|
|
89
|
+
@workflow
|
|
90
|
+
def my_wf(name: str = "world") -> str:
|
|
91
|
+
return perian_hello(name=name)
|
|
92
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
setup.py
|
|
3
|
+
flytekitplugins/perian_job/__init__.py
|
|
4
|
+
flytekitplugins/perian_job/agent.py
|
|
5
|
+
flytekitplugins/perian_job/task.py
|
|
6
|
+
flytekitplugins_perian_job.egg-info/PKG-INFO
|
|
7
|
+
flytekitplugins_perian_job.egg-info/SOURCES.txt
|
|
8
|
+
flytekitplugins_perian_job.egg-info/dependency_links.txt
|
|
9
|
+
flytekitplugins_perian_job.egg-info/entry_points.txt
|
|
10
|
+
flytekitplugins_perian_job.egg-info/namespace_packages.txt
|
|
11
|
+
flytekitplugins_perian_job.egg-info/requires.txt
|
|
12
|
+
flytekitplugins_perian_job.egg-info/top_level.txt
|
|
13
|
+
tests/__init__.py
|
|
14
|
+
tests/test_perian.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
flytekitplugins
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
flytekitplugins
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from setuptools import setup
|
|
2
|
+
|
|
3
|
+
PLUGIN_NAME = "perian_job"
|
|
4
|
+
|
|
5
|
+
microlib_name = f"flytekitplugins-{PLUGIN_NAME}"
|
|
6
|
+
|
|
7
|
+
plugin_requires = ["flytekit>=1.12.0,<2.0.0", "perian>=0.2.3"]
|
|
8
|
+
|
|
9
|
+
__version__ = "1.12.0"
|
|
10
|
+
|
|
11
|
+
setup(
|
|
12
|
+
name=microlib_name,
|
|
13
|
+
version=__version__,
|
|
14
|
+
author="Omar Tarabai",
|
|
15
|
+
author_email="otarabai@perian.io",
|
|
16
|
+
description="Flyte agent for Perian Job Platform (perian.io)",
|
|
17
|
+
long_description=open("README.md").read(),
|
|
18
|
+
long_description_content_type="text/markdown",
|
|
19
|
+
namespace_packages=["flytekitplugins"],
|
|
20
|
+
packages=[f"flytekitplugins.{PLUGIN_NAME}"],
|
|
21
|
+
install_requires=plugin_requires,
|
|
22
|
+
license="apache2",
|
|
23
|
+
python_requires=">=3.8",
|
|
24
|
+
classifiers=[
|
|
25
|
+
"Intended Audience :: Science/Research",
|
|
26
|
+
"Intended Audience :: Developers",
|
|
27
|
+
"License :: OSI Approved :: Apache Software License",
|
|
28
|
+
"Programming Language :: Python :: 3.8",
|
|
29
|
+
"Programming Language :: Python :: 3.9",
|
|
30
|
+
"Programming Language :: Python :: 3.10",
|
|
31
|
+
"Programming Language :: Python :: 3.11",
|
|
32
|
+
"Topic :: Scientific/Engineering",
|
|
33
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
34
|
+
"Topic :: Software Development",
|
|
35
|
+
"Topic :: Software Development :: Libraries",
|
|
36
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
37
|
+
],
|
|
38
|
+
entry_points={"flytekit.plugins": [f"{PLUGIN_NAME}=flytekitplugins.{PLUGIN_NAME}"]},
|
|
39
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from collections import OrderedDict
|
|
2
|
+
|
|
3
|
+
from flytekitplugins.perian_job import PerianConfig, PerianTask
|
|
4
|
+
|
|
5
|
+
from flytekit import task
|
|
6
|
+
from flytekit.configuration import DefaultImages, ImageConfig, SerializationSettings
|
|
7
|
+
from flytekit.extend import get_serializable
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_perian_task():
|
|
11
|
+
task_config = PerianConfig(
|
|
12
|
+
cores=2,
|
|
13
|
+
memory="8",
|
|
14
|
+
accelerators=1,
|
|
15
|
+
accelerator_type="A100",
|
|
16
|
+
country_code="DE",
|
|
17
|
+
)
|
|
18
|
+
container_image = DefaultImages.default_image()
|
|
19
|
+
|
|
20
|
+
@task(
|
|
21
|
+
task_config=task_config,
|
|
22
|
+
container_image=container_image,
|
|
23
|
+
)
|
|
24
|
+
def say_hello(name: str) -> str:
|
|
25
|
+
return f"Hello, {name}."
|
|
26
|
+
|
|
27
|
+
assert say_hello.task_config == task_config
|
|
28
|
+
assert say_hello.task_type == "perian_task"
|
|
29
|
+
assert isinstance(say_hello, PerianTask)
|
|
30
|
+
|
|
31
|
+
serialization_settings = SerializationSettings(image_config=ImageConfig())
|
|
32
|
+
task_spec = get_serializable(OrderedDict(), serialization_settings, say_hello)
|
|
33
|
+
template = task_spec.template
|
|
34
|
+
container = template.container
|
|
35
|
+
|
|
36
|
+
assert template.custom == {
|
|
37
|
+
'accelerator_type': 'A100',
|
|
38
|
+
'accelerators': 1.0,
|
|
39
|
+
'cores': 2.0,
|
|
40
|
+
'country_code': 'DE',
|
|
41
|
+
'memory': '8',
|
|
42
|
+
}
|
|
43
|
+
assert container.image == container_image
|