primitive 0.1.57__py3-none-any.whl → 0.1.59__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.
- primitive/__about__.py +1 -1
- primitive/agent/__init__.py +0 -0
- primitive/agent/actions.py +24 -16
- primitive/agent/runner.py +12 -9
- primitive/auth/actions.py +5 -12
- primitive/auth/graphql/__init__.py +0 -0
- primitive/auth/graphql/queries.py +13 -0
- primitive/cli.py +11 -5
- primitive/client.py +4 -0
- primitive/daemons/__init__.py +0 -0
- primitive/exec/__init__.py +0 -0
- primitive/exec/actions.py +50 -0
- primitive/exec/commands.py +22 -0
- primitive/files/__init__.py +0 -0
- primitive/files/actions.py +9 -16
- primitive/files/graphql/__init__.py +0 -0
- primitive/files/graphql/mutations.py +11 -0
- primitive/git/actions.py +11 -11
- primitive/git/commands.py +7 -6
- primitive/git/graphql/__init__.py +0 -0
- primitive/git/graphql/queries.py +7 -0
- primitive/graphql/relay.py +32 -0
- primitive/graphql/utility_fragments.py +19 -0
- primitive/hardware/__init__.py +0 -0
- primitive/hardware/actions.py +74 -121
- primitive/hardware/commands.py +15 -5
- primitive/hardware/graphql/__init__.py +0 -0
- primitive/hardware/graphql/fragments.py +22 -0
- primitive/hardware/graphql/mutations.py +45 -0
- primitive/hardware/graphql/queries.py +31 -0
- primitive/jobs/__init__.py +0 -0
- primitive/jobs/actions.py +32 -201
- primitive/jobs/graphql/__init__.py +0 -0
- primitive/jobs/graphql/fragments.py +47 -0
- primitive/jobs/graphql/mutations.py +11 -0
- primitive/jobs/graphql/queries.py +100 -0
- primitive/lint/__init__.py +0 -0
- primitive/organizations/__init__.py +0 -0
- primitive/organizations/actions.py +4 -49
- primitive/organizations/graphql/__init__.py +0 -0
- primitive/organizations/graphql/fragments.py +10 -0
- primitive/organizations/graphql/mutations.py +0 -0
- primitive/organizations/graphql/queries.py +38 -0
- primitive/projects/actions.py +5 -47
- primitive/projects/graphql/__init__.py +0 -0
- primitive/projects/graphql/fragments.py +10 -0
- primitive/projects/graphql/mutations.py +0 -0
- primitive/projects/graphql/queries.py +36 -0
- primitive/reservations/__init__.py +0 -0
- primitive/reservations/actions.py +134 -0
- primitive/reservations/commands.py +67 -0
- primitive/reservations/graphql/__init__.py +0 -0
- primitive/reservations/graphql/fragments.py +40 -0
- primitive/reservations/graphql/mutations.py +29 -0
- primitive/reservations/graphql/queries.py +47 -0
- primitive/sim/actions.py +12 -9
- primitive/utils/__init__.py +0 -0
- {primitive-0.1.57.dist-info → primitive-0.1.59.dist-info}/METADATA +1 -1
- primitive-0.1.59.dist-info/RECORD +95 -0
- primitive-0.1.57.dist-info/RECORD +0 -53
- {primitive-0.1.57.dist-info → primitive-0.1.59.dist-info}/WHEEL +0 -0
- {primitive-0.1.57.dist-info → primitive-0.1.59.dist-info}/entry_points.txt +0 -0
- {primitive-0.1.57.dist-info → primitive-0.1.59.dist-info}/licenses/LICENSE.txt +0 -0
primitive/__about__.py
CHANGED
File without changes
|
primitive/agent/actions.py
CHANGED
@@ -1,9 +1,12 @@
|
|
1
|
-
import sys
|
2
1
|
import shutil
|
2
|
+
import sys
|
3
3
|
from time import sleep
|
4
|
-
|
4
|
+
|
5
5
|
from loguru import logger
|
6
|
+
|
6
7
|
from primitive.__about__ import __version__
|
8
|
+
from primitive.utils.actions import BaseAction
|
9
|
+
|
7
10
|
from ..utils.cache import get_sources_cache
|
8
11
|
from .runner import AgentRunner
|
9
12
|
from .uploader import Uploader
|
@@ -73,12 +76,12 @@ class Agent(BaseAction):
|
|
73
76
|
sleep(sleep_amount)
|
74
77
|
continue
|
75
78
|
|
76
|
-
|
79
|
+
job_runs_result = self.primitive.jobs.get_job_runs(
|
77
80
|
status="pending", first=1, reservation_id=active_reservation_id
|
78
81
|
)
|
79
82
|
|
80
83
|
pending_job_runs = [
|
81
|
-
edge["node"] for edge in
|
84
|
+
edge["node"] for edge in job_runs_result.data["jobRuns"]["edges"]
|
82
85
|
]
|
83
86
|
|
84
87
|
if not pending_job_runs:
|
@@ -119,18 +122,23 @@ class Agent(BaseAction):
|
|
119
122
|
job_run["jobSettings"]["rootDirectory"]
|
120
123
|
)
|
121
124
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
125
|
+
try:
|
126
|
+
# Initialize Runner
|
127
|
+
runner = AgentRunner(
|
128
|
+
primitive=self.primitive,
|
129
|
+
source_dir=source_dir,
|
130
|
+
job_id=job_run["id"],
|
131
|
+
job_slug=job_run["job"]["slug"],
|
132
|
+
)
|
133
|
+
except Exception as e:
|
134
|
+
# Log Error
|
135
|
+
logger.error(f"Error initializing agent runner: {e}")
|
136
|
+
else:
|
137
|
+
# Execute job
|
138
|
+
runner.execute()
|
139
|
+
finally:
|
140
|
+
# Clean up
|
141
|
+
shutil.rmtree(path=downloaded_git_repository_dir)
|
134
142
|
|
135
143
|
sleep(5)
|
136
144
|
except KeyboardInterrupt:
|
primitive/agent/runner.py
CHANGED
@@ -1,16 +1,17 @@
|
|
1
|
-
import yaml
|
2
|
-
import sys
|
3
|
-
import typing
|
4
1
|
import os
|
5
2
|
import threading
|
6
|
-
|
7
|
-
from typing import TypedDict, Iterable, List, Optional, Dict
|
3
|
+
import typing
|
8
4
|
from pathlib import Path, PurePath
|
5
|
+
from time import sleep
|
6
|
+
from typing import Dict, Iterable, List, Optional, TypedDict
|
7
|
+
|
8
|
+
import yaml
|
9
9
|
from loguru import logger
|
10
|
-
|
11
|
-
from .provision import ProvisionPython
|
10
|
+
|
12
11
|
from ..utils.cache import get_artifacts_cache
|
13
12
|
from ..utils.files import find_files_for_extension
|
13
|
+
from .process import Process
|
14
|
+
from .provision import ProvisionPython
|
14
15
|
|
15
16
|
try:
|
16
17
|
from yaml import CLoader as Loader
|
@@ -72,7 +73,8 @@ class AgentRunner:
|
|
72
73
|
logger.error(
|
73
74
|
f"Found two job descriptions with the same slug: {self.job_slug}"
|
74
75
|
)
|
75
|
-
|
76
|
+
self.conclude(conclusion="failure")
|
77
|
+
raise FileExistsError
|
76
78
|
|
77
79
|
if yaml_file.exists():
|
78
80
|
self.job = yaml.load(open(yaml_file, "r"), Loader=Loader)
|
@@ -82,7 +84,8 @@ class AgentRunner:
|
|
82
84
|
logger.error(
|
83
85
|
f"No job description with matching slug '{self.job_slug}' found"
|
84
86
|
)
|
85
|
-
|
87
|
+
self.conclude(conclusion="failure")
|
88
|
+
raise FileNotFoundError
|
86
89
|
|
87
90
|
logger.info(f"Found job description for {self.job_slug}")
|
88
91
|
|
primitive/auth/actions.py
CHANGED
@@ -1,23 +1,16 @@
|
|
1
1
|
from gql import gql
|
2
2
|
|
3
|
-
from ..utils.config import read_config_file, update_config_file
|
4
|
-
from ..utils.auth import guard
|
5
|
-
|
6
3
|
from primitive.utils.actions import BaseAction
|
7
4
|
|
5
|
+
from ..utils.auth import guard
|
6
|
+
from ..utils.config import read_config_file, update_config_file
|
7
|
+
from .graphql.queries import whoami_query
|
8
|
+
|
8
9
|
|
9
10
|
class Auth(BaseAction):
|
10
11
|
@guard
|
11
12
|
def whoami(self):
|
12
|
-
query = gql(
|
13
|
-
"""
|
14
|
-
query whoami {
|
15
|
-
whoami {
|
16
|
-
username
|
17
|
-
}
|
18
|
-
}
|
19
|
-
"""
|
20
|
-
)
|
13
|
+
query = gql(whoami_query)
|
21
14
|
|
22
15
|
result = self.primitive.session.execute(query, get_execution_result=True)
|
23
16
|
|
File without changes
|
primitive/cli.py
CHANGED
@@ -1,18 +1,22 @@
|
|
1
1
|
import os
|
2
2
|
import sys
|
3
|
+
|
3
4
|
import click
|
5
|
+
|
4
6
|
from .__about__ import __version__
|
5
|
-
from .
|
7
|
+
from .agent.commands import cli as agent_commands
|
6
8
|
from .auth.commands import config_command, whoami_command
|
9
|
+
from .client import Primitive
|
10
|
+
from .daemons.commands import cli as daemons_commands
|
11
|
+
from .exec.commands import cli as exec_commands
|
7
12
|
from .files.commands import cli as file_commands
|
8
|
-
from .hardware.commands import cli as hardware_commands
|
9
|
-
from .lint.commands import cli as lint_commands
|
10
|
-
from .agent.commands import cli as agent_commands
|
11
13
|
from .git.commands import cli as git_commands
|
12
|
-
from .
|
14
|
+
from .hardware.commands import cli as hardware_commands
|
13
15
|
from .jobs.commands import cli as jobs_commands
|
16
|
+
from .lint.commands import cli as lint_commands
|
14
17
|
from .organizations.commands import cli as organizations_commands
|
15
18
|
from .projects.commands import cli as projects_commands
|
19
|
+
from .reservations.commands import cli as reservations_commands
|
16
20
|
from .sim.commands import cli as sim_commands
|
17
21
|
|
18
22
|
|
@@ -69,6 +73,8 @@ cli.add_command(jobs_commands, "jobs")
|
|
69
73
|
cli.add_command(organizations_commands, "organizations")
|
70
74
|
cli.add_command(projects_commands, "projects")
|
71
75
|
cli.add_command(sim_commands, "sim")
|
76
|
+
cli.add_command(reservations_commands, "reservations")
|
77
|
+
cli.add_command(exec_commands, "exec")
|
72
78
|
|
73
79
|
if __name__ == "__main__":
|
74
80
|
cli(obj={})
|
primitive/client.py
CHANGED
@@ -11,6 +11,8 @@ from .git.actions import Git
|
|
11
11
|
from .daemons.actions import Daemons
|
12
12
|
from .jobs.actions import Jobs
|
13
13
|
from .organizations.actions import Organizations
|
14
|
+
from .exec.actions import Exec
|
15
|
+
from .reservations.actions import Reservations
|
14
16
|
|
15
17
|
from loguru import logger
|
16
18
|
|
@@ -58,11 +60,13 @@ class Primitive:
|
|
58
60
|
self.jobs: Jobs = Jobs(self)
|
59
61
|
self.files: Files = Files(self)
|
60
62
|
self.sim: Sim = Sim(self)
|
63
|
+
self.reservations: Reservations = Reservations(self)
|
61
64
|
self.hardware: Hardware = Hardware(self)
|
62
65
|
self.lint: Lint = Lint(self)
|
63
66
|
self.agent: Agent = Agent(self)
|
64
67
|
self.git: Git = Git(self)
|
65
68
|
self.daemons: Daemons = Daemons(self)
|
69
|
+
self.exec: Exec = Exec(self)
|
66
70
|
|
67
71
|
def get_host_config(self):
|
68
72
|
self.full_config = read_config_file()
|
File without changes
|
File without changes
|
@@ -0,0 +1,50 @@
|
|
1
|
+
import typing
|
2
|
+
|
3
|
+
if typing.TYPE_CHECKING:
|
4
|
+
pass
|
5
|
+
|
6
|
+
|
7
|
+
from primitive.utils.actions import BaseAction
|
8
|
+
|
9
|
+
|
10
|
+
class Exec(BaseAction):
|
11
|
+
def __init__(self, *args, **kwargs) -> None:
|
12
|
+
super().__init__(*args, **kwargs)
|
13
|
+
|
14
|
+
def execute_command(self, hardware_identifier: str, command: str) -> None:
|
15
|
+
hardware = self.primitive.hardware.get_hardware_from_slug_or_id(
|
16
|
+
hardware_identifier=hardware_identifier
|
17
|
+
)
|
18
|
+
|
19
|
+
# since we found hardware, we need to check that the user:
|
20
|
+
# - has a valid reservation on it
|
21
|
+
# - OR if the device is free we can reserve it
|
22
|
+
|
23
|
+
# if we create a reservation on behalf of the user, we need to release it after
|
24
|
+
created_reservation_on_behalf_of_user = False
|
25
|
+
|
26
|
+
if active_reservation := hardware["activeReservation"]:
|
27
|
+
active_reservation_id = active_reservation["id"]
|
28
|
+
reservation_result = self.primitive.reservations.get_reservation(
|
29
|
+
reservation_id=active_reservation_id
|
30
|
+
)
|
31
|
+
reservation = reservation_result.data["reservation"]
|
32
|
+
else:
|
33
|
+
reservation_result = self.primitive.reservations.create_reservation(
|
34
|
+
requested_hardware_ids=[hardware["id"]],
|
35
|
+
reason="Executing command from Primitive CLI",
|
36
|
+
)
|
37
|
+
reservation = reservation_result.data["reservationCreate"]
|
38
|
+
created_reservation_on_behalf_of_user = True
|
39
|
+
|
40
|
+
reservation = self.primitive.reservations.wait_for_reservation_status(
|
41
|
+
reservation_id=reservation["id"], desired_status="in_progress"
|
42
|
+
)
|
43
|
+
|
44
|
+
print(f"Executing command: {command} on {hardware['name']}")
|
45
|
+
|
46
|
+
if created_reservation_on_behalf_of_user:
|
47
|
+
print("Cleaning up reservation.")
|
48
|
+
self.primitive.reservations.release_reservation(
|
49
|
+
reservation_or_hardware_identifier=reservation["id"]
|
50
|
+
)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import typing
|
2
|
+
|
3
|
+
import click
|
4
|
+
|
5
|
+
if typing.TYPE_CHECKING:
|
6
|
+
from ..client import Primitive
|
7
|
+
|
8
|
+
|
9
|
+
@click.command("exec")
|
10
|
+
@click.pass_context
|
11
|
+
@click.argument(
|
12
|
+
"hardware_identifier",
|
13
|
+
type=str,
|
14
|
+
required=True,
|
15
|
+
)
|
16
|
+
@click.argument("command", nargs=-1, required=True)
|
17
|
+
def cli(context, hardware_identifier: str, command: str) -> None:
|
18
|
+
"""Exec"""
|
19
|
+
primitive: Primitive = context.obj.get("PRIMITIVE")
|
20
|
+
primitive.exec.execute_command(
|
21
|
+
hardware_identifier=hardware_identifier, command=command
|
22
|
+
)
|
File without changes
|
primitive/files/actions.py
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
from pathlib import Path
|
2
|
+
|
2
3
|
from gql import gql
|
3
|
-
from primitive.graphql.sdk import create_requests_session
|
4
|
-
from ..utils.auth import guard
|
5
4
|
|
5
|
+
from primitive.graphql.sdk import create_requests_session
|
6
6
|
from primitive.utils.actions import BaseAction
|
7
7
|
|
8
|
+
from ..utils.auth import guard
|
9
|
+
from .graphql.mutations import create_trace_mutation
|
10
|
+
|
8
11
|
|
9
12
|
class Files(BaseAction):
|
10
13
|
@guard
|
@@ -17,19 +20,7 @@ class Files(BaseAction):
|
|
17
20
|
is_vector: bool,
|
18
21
|
size: int,
|
19
22
|
):
|
20
|
-
mutation = gql(
|
21
|
-
"""
|
22
|
-
mutation createTrace($input: TraceCreateInput!) {
|
23
|
-
traceCreate(input: $input) {
|
24
|
-
... on Trace {
|
25
|
-
id
|
26
|
-
signalId
|
27
|
-
signalName
|
28
|
-
}
|
29
|
-
}
|
30
|
-
}
|
31
|
-
"""
|
32
|
-
)
|
23
|
+
mutation = gql(create_trace_mutation)
|
33
24
|
input = {
|
34
25
|
"fileId": file_id,
|
35
26
|
"signalId": signal_id,
|
@@ -39,7 +30,9 @@ class Files(BaseAction):
|
|
39
30
|
"size": size,
|
40
31
|
}
|
41
32
|
variables = {"input": input}
|
42
|
-
result = self.primitive.session.execute(
|
33
|
+
result = self.primitive.session.execute(
|
34
|
+
mutation, variable_values=variables, get_execution_result=True
|
35
|
+
)
|
43
36
|
return result
|
44
37
|
|
45
38
|
@guard
|
File without changes
|
primitive/git/actions.py
CHANGED
@@ -1,26 +1,26 @@
|
|
1
|
+
import os
|
1
2
|
from pathlib import Path
|
3
|
+
|
4
|
+
from gql import gql
|
5
|
+
from loguru import logger
|
6
|
+
|
2
7
|
from primitive.utils.actions import BaseAction
|
8
|
+
|
3
9
|
from ..utils.auth import guard
|
4
|
-
from
|
5
|
-
import os
|
10
|
+
from .graphql.queries import github_app_token_query
|
6
11
|
|
7
12
|
|
8
13
|
class Git(BaseAction):
|
9
14
|
@guard
|
10
15
|
def get_github_access_token(self) -> str:
|
11
|
-
query =
|
12
|
-
query githubAppToken{
|
13
|
-
githubAppToken {
|
14
|
-
token
|
15
|
-
}
|
16
|
-
}
|
17
|
-
"""
|
18
|
-
|
16
|
+
query = gql(github_app_token_query)
|
19
17
|
filters = {}
|
20
18
|
variables = {
|
21
19
|
"filters": filters,
|
22
20
|
}
|
23
|
-
result = self.primitive.session.execute(
|
21
|
+
result = self.primitive.session.execute(
|
22
|
+
query, variable_values=variables, get_execution_result=True
|
23
|
+
)
|
24
24
|
return result
|
25
25
|
|
26
26
|
def download_git_repository_at_ref(
|
primitive/git/commands.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
-
import
|
2
|
-
from pathlib import Path
|
1
|
+
import os
|
3
2
|
import typing
|
3
|
+
from pathlib import Path
|
4
|
+
|
5
|
+
import click
|
6
|
+
|
4
7
|
from ..utils.printer import print_result
|
5
|
-
import os
|
6
8
|
|
7
9
|
if typing.TYPE_CHECKING:
|
8
10
|
from ..client import Primitive
|
@@ -39,11 +41,10 @@ def download_ref_command(
|
|
39
41
|
destination: Path = Path.cwd(),
|
40
42
|
):
|
41
43
|
primitive: Primitive = context.obj.get("PRIMITIVE")
|
42
|
-
|
44
|
+
path = primitive.git.download_git_repository_at_ref(
|
43
45
|
git_repo_full_name=git_repo_full_name,
|
44
46
|
git_ref=git_ref,
|
45
47
|
github_access_token=github_access_token,
|
46
48
|
destination=destination,
|
47
49
|
)
|
48
|
-
|
49
|
-
print_result(message, context=context, fg=fg)
|
50
|
+
print_result(message=path, context=context)
|
File without changes
|
@@ -0,0 +1,32 @@
|
|
1
|
+
import base64
|
2
|
+
from typing import Tuple
|
3
|
+
|
4
|
+
|
5
|
+
def from_base64(value: str) -> Tuple[str, str]:
|
6
|
+
"""
|
7
|
+
FROM:
|
8
|
+
https://github.com/strawberry-graphql/strawberry/blob/main/strawberry/relay/utils.py#L16C1-L40C1
|
9
|
+
|
10
|
+
Parse the base64 encoded relay value.
|
11
|
+
|
12
|
+
Args:
|
13
|
+
value:
|
14
|
+
The value to be parsed
|
15
|
+
|
16
|
+
Returns:
|
17
|
+
A tuple of (TypeName, NodeID).
|
18
|
+
|
19
|
+
Raises:
|
20
|
+
ValueError:
|
21
|
+
If the value is not in the expected format
|
22
|
+
|
23
|
+
"""
|
24
|
+
try:
|
25
|
+
res = base64.b64decode(value.encode()).decode().split(":", 1)
|
26
|
+
except Exception as e:
|
27
|
+
raise ValueError(str(e)) from e
|
28
|
+
|
29
|
+
if len(res) != 2:
|
30
|
+
raise ValueError(f"{res} expected to contain only 2 items")
|
31
|
+
|
32
|
+
return res[0], res[1]
|
@@ -0,0 +1,19 @@
|
|
1
|
+
operation_info_fragment = """
|
2
|
+
fragment OperationInfoFragment on OperationInfo {
|
3
|
+
messages {
|
4
|
+
kind
|
5
|
+
message
|
6
|
+
field
|
7
|
+
code
|
8
|
+
}
|
9
|
+
}
|
10
|
+
"""
|
11
|
+
|
12
|
+
page_info_fragment = """
|
13
|
+
fragment PageInfoFragment on PageInfo {
|
14
|
+
hasNextPage
|
15
|
+
hasPreviousPage
|
16
|
+
startCursor
|
17
|
+
endCursor
|
18
|
+
}
|
19
|
+
"""
|
File without changes
|