UncountablePythonSDK 0.0.186__py3-none-any.whl → 0.0.187__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.
- examples/integration-server/jobs/materials_auto/example_scale_connection.py +150 -0
- examples/integration-server/jobs/materials_auto/profile.yaml +5 -0
- pkgs/argument_parser/parser_inner.py +18 -3
- pkgs/type_spec/non_discriminated_union_exceptions.py +0 -2
- uncountable/integration/scan_profiles.py +11 -4
- uncountable/integration/scheduler.py +123 -65
- uncountable/integration/service.py +74 -0
- uncountable/integration/service_runner.py +44 -0
- uncountable/types/entity_t.py +6 -0
- uncountable/types/job_definition.py +1 -0
- uncountable/types/job_definition_t.py +15 -0
- {uncountablepythonsdk-0.0.186.dist-info → uncountablepythonsdk-0.0.187.dist-info}/METADATA +1 -1
- {uncountablepythonsdk-0.0.186.dist-info → uncountablepythonsdk-0.0.187.dist-info}/RECORD +15 -12
- {uncountablepythonsdk-0.0.186.dist-info → uncountablepythonsdk-0.0.187.dist-info}/WHEEL +0 -0
- {uncountablepythonsdk-0.0.186.dist-info → uncountablepythonsdk-0.0.187.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import random
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from decimal import Decimal
|
|
6
|
+
|
|
7
|
+
from uncountable.core.client import Client
|
|
8
|
+
from uncountable.integration.service import Service, ServiceArguments, register_service
|
|
9
|
+
from uncountable.types import entity_t, identifier_t, listing_t, sockets_t
|
|
10
|
+
from uncountable.types.base_t import ObjectId
|
|
11
|
+
from uncountable.types.integration_session_t import IntegrationSessionInstrument
|
|
12
|
+
from websockets.asyncio.client import connect
|
|
13
|
+
|
|
14
|
+
from pkgs.serialization_util import serialize_for_api
|
|
15
|
+
|
|
16
|
+
SCALE_EQUIPMENT_NAMES = [
|
|
17
|
+
"Instrument Scale One",
|
|
18
|
+
"Instrument Scale Two",
|
|
19
|
+
"Instrument Scale Three",
|
|
20
|
+
]
|
|
21
|
+
EQUIPMENT_ID_COLUMN_REF_NAME = "id"
|
|
22
|
+
EQUIPMENT_NAME_COLUMN_REF_NAME = "name"
|
|
23
|
+
SOCKET_URL = "ws://host.docker.internal:8765"
|
|
24
|
+
READING_INTERVAL_SECONDS = 1.0
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(kw_only=True)
|
|
28
|
+
class InstrumentRoom:
|
|
29
|
+
equipment_name: str
|
|
30
|
+
token: str
|
|
31
|
+
room_key: str
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(kw_only=True)
|
|
35
|
+
class InstrumentReading:
|
|
36
|
+
equipment_name: str
|
|
37
|
+
value: Decimal
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _build_equipment_column(ref_name: str) -> listing_t.ColumnIdentifierEntityRefName:
|
|
41
|
+
return listing_t.ColumnIdentifierEntityRefName(
|
|
42
|
+
entity_type=entity_t.EntityType.EQUIPMENT, ref_name=ref_name
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _resolve_equipment_id(*, client: Client, equipment_name: str) -> ObjectId:
|
|
47
|
+
id_column = _build_equipment_column(EQUIPMENT_ID_COLUMN_REF_NAME)
|
|
48
|
+
name_column = _build_equipment_column(EQUIPMENT_NAME_COLUMN_REF_NAME)
|
|
49
|
+
results = client.fetch_listing(
|
|
50
|
+
entity_type=entity_t.EntityType.EQUIPMENT,
|
|
51
|
+
columns=[id_column],
|
|
52
|
+
filters=listing_t.FilterNodeColumnAnd(
|
|
53
|
+
filters=(
|
|
54
|
+
listing_t.FilterSpecEquals(
|
|
55
|
+
relation=listing_t.FilterRelation.EQUALS,
|
|
56
|
+
column=name_column,
|
|
57
|
+
value=equipment_name,
|
|
58
|
+
),
|
|
59
|
+
)
|
|
60
|
+
),
|
|
61
|
+
).results
|
|
62
|
+
if len(results) != 1:
|
|
63
|
+
raise Exception(
|
|
64
|
+
f"expected exactly one equipment named {equipment_name}, found {len(results)}"
|
|
65
|
+
)
|
|
66
|
+
equipment_id = results[0].column_values[0]
|
|
67
|
+
if not isinstance(equipment_id, int):
|
|
68
|
+
raise Exception(
|
|
69
|
+
f"expected an integer id for equipment named {equipment_name}, found {equipment_id!r}"
|
|
70
|
+
)
|
|
71
|
+
return ObjectId(equipment_id)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@register_service
|
|
75
|
+
class ScaleConnectionService(Service):
|
|
76
|
+
def run(self, args: ServiceArguments) -> None:
|
|
77
|
+
rooms = [
|
|
78
|
+
self._register_room(args=args, equipment_name=equipment_name)
|
|
79
|
+
for equipment_name in SCALE_EQUIPMENT_NAMES
|
|
80
|
+
]
|
|
81
|
+
asyncio.run(self._stream_readings(args=args, rooms=rooms))
|
|
82
|
+
|
|
83
|
+
def _register_room(
|
|
84
|
+
self, *, args: ServiceArguments, equipment_name: str
|
|
85
|
+
) -> InstrumentRoom:
|
|
86
|
+
equipment_id = _resolve_equipment_id(
|
|
87
|
+
client=args.client, equipment_name=equipment_name
|
|
88
|
+
)
|
|
89
|
+
registration = args.client.register_sockets_token(
|
|
90
|
+
socket_request=sockets_t.SocketRequestIntegrationSession(
|
|
91
|
+
integration_session=IntegrationSessionInstrument(
|
|
92
|
+
equipment_key=identifier_t.IdentifierKeyId(id=equipment_id)
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
).response
|
|
96
|
+
return InstrumentRoom(
|
|
97
|
+
equipment_name=equipment_name,
|
|
98
|
+
token=registration.token,
|
|
99
|
+
room_key=registration.room_key,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
async def _stream_readings(
|
|
103
|
+
self, *, args: ServiceArguments, rooms: list[InstrumentRoom]
|
|
104
|
+
) -> None:
|
|
105
|
+
rooms_by_name = {room.equipment_name: room for room in rooms}
|
|
106
|
+
async with connect(
|
|
107
|
+
SOCKET_URL, additional_headers={"X-UNC-EXTERNAL": "true"}
|
|
108
|
+
) as ws:
|
|
109
|
+
for room in rooms:
|
|
110
|
+
await ws.send(
|
|
111
|
+
json.dumps(
|
|
112
|
+
serialize_for_api(
|
|
113
|
+
sockets_t.JoinRoomWithTokenSocketClientMessage(
|
|
114
|
+
token=room.token
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
args.logger.log_info(f"Joined room for instrument {room.equipment_name}")
|
|
120
|
+
|
|
121
|
+
readings: asyncio.Queue[InstrumentReading] = asyncio.Queue()
|
|
122
|
+
emit_task = asyncio.create_task(
|
|
123
|
+
self._emit_readings(rooms=rooms, readings=readings)
|
|
124
|
+
)
|
|
125
|
+
try:
|
|
126
|
+
while True:
|
|
127
|
+
reading = await readings.get()
|
|
128
|
+
room = rooms_by_name[reading.equipment_name]
|
|
129
|
+
await ws.send(
|
|
130
|
+
json.dumps(
|
|
131
|
+
serialize_for_api(
|
|
132
|
+
sockets_t.SendInstrumentReadingClientMessage(
|
|
133
|
+
value=reading.value, room_key=room.room_key
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
finally:
|
|
139
|
+
emit_task.cancel()
|
|
140
|
+
|
|
141
|
+
async def _emit_readings(
|
|
142
|
+
self, *, rooms: list[InstrumentRoom], readings: asyncio.Queue[InstrumentReading]
|
|
143
|
+
) -> None:
|
|
144
|
+
while True:
|
|
145
|
+
room = random.choice(rooms)
|
|
146
|
+
value = Decimal(str(round(random.uniform(0, 500), 2)))
|
|
147
|
+
await readings.put(
|
|
148
|
+
InstrumentReading(equipment_name=room.equipment_name, value=value)
|
|
149
|
+
)
|
|
150
|
+
await asyncio.sleep(READING_INTERVAL_SECONDS)
|
|
@@ -5,6 +5,7 @@ import datetime
|
|
|
5
5
|
import math
|
|
6
6
|
import types
|
|
7
7
|
import typing
|
|
8
|
+
from collections.abc import Mapping
|
|
8
9
|
from datetime import date
|
|
9
10
|
from decimal import Decimal
|
|
10
11
|
from enum import Enum
|
|
@@ -140,18 +141,32 @@ def _build_parser_discriminated_union(
|
|
|
140
141
|
else discriminator_raw
|
|
141
142
|
)
|
|
142
143
|
|
|
144
|
+
accepted = ", ".join(sorted(discriminator_map))
|
|
145
|
+
|
|
143
146
|
def parse(value: typing.Any) -> typing.Any:
|
|
144
147
|
if context.options.allow_direct_type and dataclasses.is_dataclass(value):
|
|
145
148
|
discriminant = getattr(value, discriminator)
|
|
146
|
-
|
|
149
|
+
elif isinstance(value, Mapping):
|
|
147
150
|
discriminant = value.get(discriminator)
|
|
151
|
+
else:
|
|
152
|
+
raise ParserError(
|
|
153
|
+
f"expected-object-for-union; found {type(value).__name__} where an "
|
|
154
|
+
f"object with {discriminator} set to one of: {accepted} was expected",
|
|
155
|
+
value=value,
|
|
156
|
+
)
|
|
148
157
|
if discriminant is None:
|
|
149
|
-
raise ParserError(
|
|
158
|
+
raise ParserError(
|
|
159
|
+
f"missing-union-discriminant; set {discriminator} to one of: {accepted}"
|
|
160
|
+
)
|
|
150
161
|
if not isinstance(discriminant, str):
|
|
151
162
|
raise ParserError("union-discriminant-is-not-string", value=discriminant)
|
|
152
163
|
parser = discriminator_map.get(discriminant)
|
|
153
164
|
if parser is None:
|
|
154
|
-
raise ParserError(
|
|
165
|
+
raise ParserError(
|
|
166
|
+
f"missing-type-for-union-discriminant; {discriminator} must be one of: "
|
|
167
|
+
f"{accepted}",
|
|
168
|
+
value=discriminant,
|
|
169
|
+
)
|
|
155
170
|
return parser(value)
|
|
156
171
|
|
|
157
172
|
return parse
|
|
@@ -2,8 +2,6 @@ NON_DISCRIMINATED_UNION_EXCEPTIONS = [
|
|
|
2
2
|
"audit_log.AuditLogDataValues",
|
|
3
3
|
"generate_tool_parameters.UnionWithoutDiscrim",
|
|
4
4
|
"output_calculation_entities.ConditionParameterFilterCondition",
|
|
5
|
-
"output_parameters.AnalyticalMethodParameterOptions",
|
|
6
|
-
"output_parameters.AnalyticalMethodLinkedOptionValue",
|
|
7
5
|
"recipes_redirect.RecipesRedirectResult",
|
|
8
6
|
"value_spec.ResolvedPathAll",
|
|
9
7
|
"deprecated_calculation_types.DeprecatedWeightedSumEntitiesV0",
|
|
@@ -21,7 +21,7 @@ def load_profiles() -> list[job_definition_t.ProfileMetadata]:
|
|
|
21
21
|
if entry.is_dir() and entry.name not in _IGNORED_PROFILE_FOLDERS
|
|
22
22
|
]
|
|
23
23
|
profile_details: list[job_definition_t.ProfileMetadata] = []
|
|
24
|
-
|
|
24
|
+
seen_ids: set[str] = set()
|
|
25
25
|
for profile_file in profiles:
|
|
26
26
|
profile_name = profile_file.name
|
|
27
27
|
try:
|
|
@@ -30,9 +30,14 @@ def load_profiles() -> list[job_definition_t.ProfileMetadata]:
|
|
|
30
30
|
resource="profile.yaml",
|
|
31
31
|
)
|
|
32
32
|
for job in definition.jobs:
|
|
33
|
-
if job.id in
|
|
34
|
-
raise Exception(f"multiple jobs with id {job.id}")
|
|
35
|
-
|
|
33
|
+
if job.id in seen_ids:
|
|
34
|
+
raise Exception(f"multiple jobs or services with id {job.id}")
|
|
35
|
+
seen_ids.add(job.id)
|
|
36
|
+
services = definition.services if definition.services is not None else []
|
|
37
|
+
for service in services:
|
|
38
|
+
if service.id in seen_ids:
|
|
39
|
+
raise Exception(f"multiple jobs or services with id {service.id}")
|
|
40
|
+
seen_ids.add(service.id)
|
|
36
41
|
|
|
37
42
|
if definition.environments is not None:
|
|
38
43
|
for integration_env in integration_envs:
|
|
@@ -42,6 +47,7 @@ def load_profiles() -> list[job_definition_t.ProfileMetadata]:
|
|
|
42
47
|
job_definition_t.ProfileMetadata(
|
|
43
48
|
name=profile_name,
|
|
44
49
|
jobs=definition.jobs,
|
|
50
|
+
services=services,
|
|
45
51
|
base_url=environment_config.base_url,
|
|
46
52
|
integration_server_base_url=environment_config.integration_server_base_url,
|
|
47
53
|
app_base_url=environment_config.app_base_url,
|
|
@@ -58,6 +64,7 @@ def load_profiles() -> list[job_definition_t.ProfileMetadata]:
|
|
|
58
64
|
job_definition_t.ProfileMetadata(
|
|
59
65
|
name=profile_name,
|
|
60
66
|
jobs=definition.jobs,
|
|
67
|
+
services=services,
|
|
61
68
|
base_url=definition.base_url,
|
|
62
69
|
integration_server_base_url=definition.integration_server_base_url,
|
|
63
70
|
app_base_url=definition.app_base_url,
|
|
@@ -19,6 +19,12 @@ from uncountable.integration.queue_runner.command_server import (
|
|
|
19
19
|
)
|
|
20
20
|
from uncountable.integration.queue_runner.queue_runner import start_queue_runner
|
|
21
21
|
from uncountable.integration.queue_runner.types import RESTART_EXIT_CODE
|
|
22
|
+
from uncountable.integration.scan_profiles import load_profiles
|
|
23
|
+
from uncountable.integration.service_runner import (
|
|
24
|
+
ServiceToStart,
|
|
25
|
+
run_service,
|
|
26
|
+
select_enabled_services,
|
|
27
|
+
)
|
|
22
28
|
from uncountable.integration.telemetry import Logger
|
|
23
29
|
|
|
24
30
|
SHUTDOWN_TIMEOUT_SECS = 30
|
|
@@ -32,9 +38,30 @@ class ProcessName(StrEnum):
|
|
|
32
38
|
UWSGI = "uwsgi"
|
|
33
39
|
|
|
34
40
|
|
|
41
|
+
@dataclass(frozen=True, kw_only=True)
|
|
42
|
+
class CoreProcessKey:
|
|
43
|
+
name: ProcessName
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def label(self) -> str:
|
|
47
|
+
return self.name
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass(frozen=True, kw_only=True)
|
|
51
|
+
class ServiceProcessKey:
|
|
52
|
+
service_id: str
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def label(self) -> str:
|
|
56
|
+
return f"service:{self.service_id}"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
ProcessKey = CoreProcessKey | ServiceProcessKey
|
|
60
|
+
|
|
61
|
+
|
|
35
62
|
@dataclass(kw_only=True)
|
|
36
63
|
class ProcessInfo:
|
|
37
|
-
|
|
64
|
+
key: ProcessKey
|
|
38
65
|
process: AnyProcess
|
|
39
66
|
|
|
40
67
|
@property
|
|
@@ -77,16 +104,17 @@ def _wait_for_process(
|
|
|
77
104
|
start = time.time()
|
|
78
105
|
while time.time() - start < timeout_secs:
|
|
79
106
|
if not proc_info.is_alive:
|
|
80
|
-
logger.log_debug(f"{proc_info.
|
|
107
|
+
logger.log_debug(f"{proc_info.key.label} shut down successfully")
|
|
81
108
|
return True
|
|
82
109
|
time.sleep(1)
|
|
83
110
|
return False
|
|
84
111
|
|
|
85
112
|
|
|
86
|
-
def handle_shutdown(logger: Logger, processes: dict[
|
|
113
|
+
def handle_shutdown(logger: Logger, processes: dict[ProcessKey, ProcessInfo]) -> None:
|
|
87
114
|
logger.log_debug("received shutdown command, shutting down sub-processes")
|
|
88
115
|
|
|
89
|
-
|
|
116
|
+
cron_key = CoreProcessKey(name=ProcessName.CRON_SERVER)
|
|
117
|
+
cron = processes.get(cron_key)
|
|
90
118
|
if cron is not None and cron.is_alive:
|
|
91
119
|
logger.log_debug("stopping cron_server first to prevent new job submissions")
|
|
92
120
|
cron.process.terminate()
|
|
@@ -94,7 +122,7 @@ def handle_shutdown(logger: Logger, processes: dict[ProcessName, ProcessInfo]) -
|
|
|
94
122
|
logger.log_warning("cron_server did not stop in time, killing")
|
|
95
123
|
cron.process.kill()
|
|
96
124
|
|
|
97
|
-
remaining = [p for
|
|
125
|
+
remaining = [p for key, p in processes.items() if key != cron_key]
|
|
98
126
|
for proc_info in remaining:
|
|
99
127
|
if proc_info.is_alive:
|
|
100
128
|
proc_info.process.terminate()
|
|
@@ -106,70 +134,62 @@ def handle_shutdown(logger: Logger, processes: dict[ProcessName, ProcessInfo]) -
|
|
|
106
134
|
):
|
|
107
135
|
logger.log_debug(
|
|
108
136
|
"waiting for sub-processes to shut down",
|
|
109
|
-
attributes={"still_living_processes": [p.
|
|
137
|
+
attributes={"still_living_processes": [p.key.label for p in still_living]},
|
|
110
138
|
)
|
|
111
139
|
still_living = [p for p in still_living if p.is_alive]
|
|
112
140
|
time.sleep(1)
|
|
113
141
|
|
|
114
142
|
for proc_info in still_living:
|
|
115
143
|
logger.log_warning(
|
|
116
|
-
f"{proc_info.
|
|
144
|
+
f"{proc_info.key.label} failed to shut down after {SHUTDOWN_TIMEOUT_SECS} seconds, forcefully terminating"
|
|
117
145
|
)
|
|
118
146
|
proc_info.process.kill()
|
|
119
147
|
|
|
120
148
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
f"process {proc_info.name} shut down unexpectedly - exit code {proc_info.exitcode}. Restarting..."
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
match proc_info.name:
|
|
129
|
-
case ProcessName.QUEUE_RUNNER:
|
|
130
|
-
queue_proc = multiprocessing.Process(target=start_queue_runner)
|
|
131
|
-
queue_proc.start()
|
|
132
|
-
new_info = ProcessInfo(name=ProcessName.QUEUE_RUNNER, process=queue_proc)
|
|
133
|
-
processes[ProcessName.QUEUE_RUNNER] = new_info
|
|
134
|
-
try:
|
|
135
|
-
_wait_queue_runner_online()
|
|
136
|
-
logger.log_debug("queue runner restarted successfully")
|
|
137
|
-
except Exception as e:
|
|
138
|
-
logger.log_exception(e)
|
|
139
|
-
logger.log_error(
|
|
140
|
-
"queue runner failed to restart, shutting down scheduler"
|
|
141
|
-
)
|
|
142
|
-
handle_shutdown(logger, processes)
|
|
143
|
-
sys.exit(1)
|
|
149
|
+
class DeadProcessOutcome(StrEnum):
|
|
150
|
+
RESTART_REQUESTED = "restart_requested"
|
|
151
|
+
RESTART_AFTER_CRASH = "restart_after_crash"
|
|
152
|
+
SHUTDOWN_ALL = "shutdown_all"
|
|
144
153
|
|
|
145
|
-
case ProcessName.CRON_SERVER:
|
|
146
|
-
cron_proc = multiprocessing.Process(target=cron_target)
|
|
147
|
-
cron_proc.start()
|
|
148
|
-
new_info = ProcessInfo(name=ProcessName.CRON_SERVER, process=cron_proc)
|
|
149
|
-
processes[ProcessName.CRON_SERVER] = new_info
|
|
150
|
-
logger.log_debug("cron server restarted successfully")
|
|
151
154
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
def classify_dead_process(key: ProcessKey, exitcode: int | None) -> DeadProcessOutcome:
|
|
156
|
+
if exitcode == RESTART_EXIT_CODE:
|
|
157
|
+
return DeadProcessOutcome.RESTART_REQUESTED
|
|
158
|
+
match key:
|
|
159
|
+
case ServiceProcessKey():
|
|
160
|
+
return DeadProcessOutcome.RESTART_AFTER_CRASH
|
|
161
|
+
case CoreProcessKey():
|
|
162
|
+
return DeadProcessOutcome.SHUTDOWN_ALL
|
|
163
|
+
case _:
|
|
164
|
+
assert_never(key)
|
|
157
165
|
|
|
158
166
|
|
|
159
167
|
def check_process_alarms(
|
|
160
|
-
logger: Logger, processes: dict[
|
|
168
|
+
logger: Logger, processes: dict[ProcessKey, ProcessInfo]
|
|
161
169
|
) -> ProcessAlarm | None:
|
|
162
170
|
for proc_info in processes.values():
|
|
163
|
-
if
|
|
164
|
-
|
|
171
|
+
if proc_info.is_alive:
|
|
172
|
+
continue
|
|
173
|
+
outcome = classify_dead_process(proc_info.key, proc_info.exitcode)
|
|
174
|
+
match outcome:
|
|
175
|
+
case DeadProcessOutcome.RESTART_REQUESTED:
|
|
165
176
|
logger.log_warning(
|
|
166
|
-
f"process {proc_info.
|
|
177
|
+
f"process {proc_info.key.label} requested restart! restarting"
|
|
167
178
|
)
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
179
|
+
case DeadProcessOutcome.RESTART_AFTER_CRASH:
|
|
180
|
+
logger.log_error(
|
|
181
|
+
f"process {proc_info.key.label} exited unexpectedly with exit "
|
|
182
|
+
f"code {proc_info.exitcode}; cleaning up and restarting"
|
|
183
|
+
)
|
|
184
|
+
case DeadProcessOutcome.SHUTDOWN_ALL:
|
|
185
|
+
logger.log_error(
|
|
186
|
+
f"process {proc_info.key.label} shut down unexpectedly! shutting "
|
|
187
|
+
f"down scheduler; exit code is {proc_info.exitcode}"
|
|
188
|
+
)
|
|
189
|
+
return ProcessAlarmShutdownAll()
|
|
190
|
+
case _:
|
|
191
|
+
assert_never(outcome)
|
|
192
|
+
return ProcessAlarmRestart(process=proc_info)
|
|
173
193
|
return None
|
|
174
194
|
|
|
175
195
|
|
|
@@ -194,20 +214,20 @@ def _wait_queue_runner_online() -> None:
|
|
|
194
214
|
|
|
195
215
|
def main() -> None:
|
|
196
216
|
logger = Logger(get_current_span())
|
|
197
|
-
processes: dict[
|
|
217
|
+
processes: dict[ProcessKey, ProcessInfo] = {}
|
|
198
218
|
|
|
199
219
|
multiprocessing.set_start_method("forkserver")
|
|
200
220
|
|
|
201
221
|
def add_process(process: ProcessInfo) -> None:
|
|
202
|
-
processes[process.
|
|
203
|
-
logger.log_debug(f"started process {process.
|
|
222
|
+
processes[process.key] = process
|
|
223
|
+
logger.log_debug(f"started process {process.key.label}")
|
|
204
224
|
|
|
205
225
|
def _start_queue_runner() -> None:
|
|
206
226
|
runner_process = multiprocessing.Process(target=start_queue_runner)
|
|
207
227
|
runner_process.start()
|
|
208
228
|
add_process(
|
|
209
229
|
ProcessInfo(
|
|
210
|
-
name=ProcessName.QUEUE_RUNNER,
|
|
230
|
+
key=CoreProcessKey(name=ProcessName.QUEUE_RUNNER),
|
|
211
231
|
process=runner_process,
|
|
212
232
|
)
|
|
213
233
|
)
|
|
@@ -219,17 +239,46 @@ def main() -> None:
|
|
|
219
239
|
handle_shutdown(logger, processes=processes)
|
|
220
240
|
return
|
|
221
241
|
|
|
242
|
+
def _start_service(service_to_start: ServiceToStart) -> None:
|
|
243
|
+
service_process = multiprocessing.Process(
|
|
244
|
+
target=run_service,
|
|
245
|
+
kwargs={
|
|
246
|
+
"service": service_to_start.service,
|
|
247
|
+
"profile_metadata": service_to_start.profile_metadata,
|
|
248
|
+
},
|
|
249
|
+
)
|
|
250
|
+
service_process.start()
|
|
251
|
+
add_process(
|
|
252
|
+
ProcessInfo(
|
|
253
|
+
key=ServiceProcessKey(service_id=service_to_start.service.id),
|
|
254
|
+
process=service_process,
|
|
255
|
+
)
|
|
256
|
+
)
|
|
257
|
+
|
|
222
258
|
_start_queue_runner()
|
|
223
259
|
|
|
224
260
|
cron_process = multiprocessing.Process(target=cron_target)
|
|
225
261
|
cron_process.start()
|
|
226
|
-
add_process(
|
|
262
|
+
add_process(
|
|
263
|
+
ProcessInfo(
|
|
264
|
+
key=CoreProcessKey(name=ProcessName.CRON_SERVER), process=cron_process
|
|
265
|
+
)
|
|
266
|
+
)
|
|
227
267
|
|
|
228
268
|
uwsgi_process = subprocess.Popen([
|
|
229
269
|
"uwsgi",
|
|
230
270
|
"--die-on-term",
|
|
231
271
|
])
|
|
232
|
-
add_process(
|
|
272
|
+
add_process(
|
|
273
|
+
ProcessInfo(key=CoreProcessKey(name=ProcessName.UWSGI), process=uwsgi_process)
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
services_by_id: dict[str, ServiceToStart] = {
|
|
277
|
+
service_to_start.service.id: service_to_start
|
|
278
|
+
for service_to_start in select_enabled_services(load_profiles())
|
|
279
|
+
}
|
|
280
|
+
for service_to_start in services_by_id.values():
|
|
281
|
+
_start_service(service_to_start)
|
|
233
282
|
|
|
234
283
|
shutdown_requested = False
|
|
235
284
|
|
|
@@ -244,16 +293,25 @@ def main() -> None:
|
|
|
244
293
|
process_alarm = check_process_alarms(logger, processes=processes)
|
|
245
294
|
match process_alarm:
|
|
246
295
|
case ProcessAlarmRestart():
|
|
247
|
-
match process_alarm.process.
|
|
248
|
-
case
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
296
|
+
match process_alarm.process.key:
|
|
297
|
+
case CoreProcessKey() as core_key:
|
|
298
|
+
match core_key.name:
|
|
299
|
+
case ProcessName.QUEUE_RUNNER:
|
|
300
|
+
del processes[
|
|
301
|
+
CoreProcessKey(name=ProcessName.QUEUE_RUNNER)
|
|
302
|
+
]
|
|
303
|
+
_start_queue_runner()
|
|
304
|
+
case ProcessName.CRON_SERVER | ProcessName.UWSGI:
|
|
305
|
+
raise NotImplementedError(
|
|
306
|
+
f"restarting {core_key.label} not yet implemented"
|
|
307
|
+
)
|
|
308
|
+
case _:
|
|
309
|
+
assert_never(core_key.name)
|
|
310
|
+
case ServiceProcessKey() as service_key:
|
|
311
|
+
del processes[service_key]
|
|
312
|
+
_start_service(services_by_id[service_key.service_id])
|
|
255
313
|
case _:
|
|
256
|
-
assert_never(process_alarm.process.
|
|
314
|
+
assert_never(process_alarm.process.key)
|
|
257
315
|
case ProcessAlarmShutdownAll():
|
|
258
316
|
handle_shutdown(logger, processes)
|
|
259
317
|
sys.exit(1)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import inspect
|
|
3
|
+
import os
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from uncountable.core.client import Client
|
|
8
|
+
from uncountable.integration.telemetry import Logger
|
|
9
|
+
from uncountable.types.job_definition_t import (
|
|
10
|
+
IntegrationService,
|
|
11
|
+
ProfileMetadata,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"Service",
|
|
16
|
+
"ServiceArguments",
|
|
17
|
+
"register_service",
|
|
18
|
+
"resolve_service",
|
|
19
|
+
"resolve_service_class",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(kw_only=True)
|
|
24
|
+
class ServiceArguments:
|
|
25
|
+
service_definition: IntegrationService
|
|
26
|
+
profile_metadata: ProfileMetadata
|
|
27
|
+
client: Client
|
|
28
|
+
logger: Logger
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Service(ABC):
|
|
32
|
+
_unc_service_registered: bool = False
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def run(self, args: ServiceArguments) -> None: ...
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def register_service[ST: type[Service]](cls: ST) -> ST:
|
|
39
|
+
if not (isinstance(cls, type) and issubclass(cls, Service)):
|
|
40
|
+
raise TypeError(
|
|
41
|
+
f"@register_service can only be applied to Service subclasses, got {cls}"
|
|
42
|
+
)
|
|
43
|
+
cls._unc_service_registered = True
|
|
44
|
+
return cls
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def resolve_service_class(
|
|
48
|
+
service: IntegrationService, profile_metadata: ProfileMetadata
|
|
49
|
+
) -> type[Service]:
|
|
50
|
+
service_module_path = ".".join([
|
|
51
|
+
os.environ["UNC_PROFILES_MODULE"],
|
|
52
|
+
profile_metadata.name,
|
|
53
|
+
service.import_path,
|
|
54
|
+
])
|
|
55
|
+
service_module = importlib.import_module(service_module_path)
|
|
56
|
+
found_services: list[type[Service]] = []
|
|
57
|
+
for _, service_class in inspect.getmembers(service_module, inspect.isclass):
|
|
58
|
+
if (
|
|
59
|
+
service_class.__module__ == service_module.__name__
|
|
60
|
+
and getattr(service_class, "_unc_service_registered", False)
|
|
61
|
+
and issubclass(service_class, Service)
|
|
62
|
+
):
|
|
63
|
+
found_services.append(service_class)
|
|
64
|
+
assert len(found_services) == 1, (
|
|
65
|
+
f"expected exactly one registered service class in {service.import_path}, found {len(found_services)}"
|
|
66
|
+
)
|
|
67
|
+
return found_services[0]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def resolve_service(
|
|
71
|
+
service: IntegrationService, profile_metadata: ProfileMetadata
|
|
72
|
+
) -> Service:
|
|
73
|
+
service_class = resolve_service_class(service, profile_metadata)
|
|
74
|
+
return service_class()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from uncountable.integration.construct_client import construct_uncountable_client
|
|
4
|
+
from uncountable.integration.service import ServiceArguments, resolve_service
|
|
5
|
+
from uncountable.integration.telemetry import Logger, get_otel_tracer
|
|
6
|
+
from uncountable.types.job_definition_t import IntegrationService, ProfileMetadata
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(kw_only=True)
|
|
10
|
+
class ServiceToStart:
|
|
11
|
+
profile_metadata: ProfileMetadata
|
|
12
|
+
service: IntegrationService
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def select_enabled_services(
|
|
16
|
+
profiles: list[ProfileMetadata],
|
|
17
|
+
) -> list[ServiceToStart]:
|
|
18
|
+
return [
|
|
19
|
+
ServiceToStart(profile_metadata=profile_metadata, service=service)
|
|
20
|
+
for profile_metadata in profiles
|
|
21
|
+
for service in profile_metadata.services
|
|
22
|
+
if service.enabled
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def run_service(
|
|
27
|
+
*, service: IntegrationService, profile_metadata: ProfileMetadata
|
|
28
|
+
) -> None:
|
|
29
|
+
with get_otel_tracer().start_as_current_span(
|
|
30
|
+
name=f"run_service: {service.id}"
|
|
31
|
+
) as span:
|
|
32
|
+
logger = Logger(span)
|
|
33
|
+
client = construct_uncountable_client(
|
|
34
|
+
profile_meta=profile_metadata, logger=logger
|
|
35
|
+
)
|
|
36
|
+
service_instance = resolve_service(service, profile_metadata)
|
|
37
|
+
service_instance.run(
|
|
38
|
+
ServiceArguments(
|
|
39
|
+
service_definition=service,
|
|
40
|
+
profile_metadata=profile_metadata,
|
|
41
|
+
client=client,
|
|
42
|
+
logger=logger,
|
|
43
|
+
)
|
|
44
|
+
)
|
uncountable/types/entity_t.py
CHANGED
|
@@ -28,6 +28,7 @@ __all__: list[str] = [
|
|
|
28
28
|
@serial_string_enum(
|
|
29
29
|
labels={
|
|
30
30
|
"agent_memory": "Agent Memory",
|
|
31
|
+
"agent_persona": "Agent Persona",
|
|
31
32
|
"aggregated_data_view": "Aggregated Data View",
|
|
32
33
|
"analytical_method": "Analytical Method",
|
|
33
34
|
"analytical_method_category": "Analytical Method Category",
|
|
@@ -64,6 +65,7 @@ __all__: list[str] = [
|
|
|
64
65
|
"dataset_binding": "Dataset Binding",
|
|
65
66
|
"default_permissions_new_project": "Default Permissions New Project",
|
|
66
67
|
"document": "Document",
|
|
68
|
+
"document_mapping": "Document Mapping",
|
|
67
69
|
"document_version": "Document Version",
|
|
68
70
|
"doe_run": "Suggest Experiments",
|
|
69
71
|
"draft_entity": "Draft Entity",
|
|
@@ -214,6 +216,7 @@ __all__: list[str] = [
|
|
|
214
216
|
"regional_entity_group": "Regional Entity Group",
|
|
215
217
|
"regulation": "Regulation",
|
|
216
218
|
"regulation_cas": "Regulation CAS",
|
|
219
|
+
"regulation_source": "Regulation Source",
|
|
217
220
|
"regulation_override": "Regulation Override",
|
|
218
221
|
"regulation_product_category": "Regulation Product Category",
|
|
219
222
|
"supplemental_regulation_ingredient": "Supplemental Regulation Ingredient",
|
|
@@ -310,6 +313,7 @@ __all__: list[str] = [
|
|
|
310
313
|
)
|
|
311
314
|
class EntityType(StrEnum):
|
|
312
315
|
AGENT_MEMORY = "agent_memory"
|
|
316
|
+
AGENT_PERSONA = "agent_persona"
|
|
313
317
|
AGGREGATED_DATA_VIEW = "aggregated_data_view"
|
|
314
318
|
ANALYTICAL_METHOD = "analytical_method"
|
|
315
319
|
ANALYTICAL_METHOD_CATEGORY = "analytical_method_category"
|
|
@@ -346,6 +350,7 @@ class EntityType(StrEnum):
|
|
|
346
350
|
DATASET_BINDING = "dataset_binding"
|
|
347
351
|
DEFAULT_PERMISSIONS_NEW_PROJECT = "default_permissions_new_project"
|
|
348
352
|
DOCUMENT = "document"
|
|
353
|
+
DOCUMENT_MAPPING = "document_mapping"
|
|
349
354
|
DOCUMENT_VERSION = "document_version"
|
|
350
355
|
DOE_RUN = "doe_run"
|
|
351
356
|
DRAFT_ENTITY = "draft_entity"
|
|
@@ -496,6 +501,7 @@ class EntityType(StrEnum):
|
|
|
496
501
|
REGIONAL_ENTITY_GROUP = "regional_entity_group"
|
|
497
502
|
REGULATION = "regulation"
|
|
498
503
|
REGULATION_CAS = "regulation_cas"
|
|
504
|
+
REGULATION_SOURCE = "regulation_source"
|
|
499
505
|
REGULATION_OVERRIDE = "regulation_override"
|
|
500
506
|
REGULATION_PRODUCT_CATEGORY = "regulation_product_category"
|
|
501
507
|
SUPPLEMENTAL_REGULATION_INGREDIENT = "supplemental_regulation_ingredient"
|
|
@@ -24,6 +24,7 @@ from .job_definition_t import CustomHttpJobDefinition as CustomHttpJobDefinition
|
|
|
24
24
|
from .job_definition_t import WebhookJobDefinition as WebhookJobDefinition
|
|
25
25
|
from .job_definition_t import UncountableWebhookJobDefinition as UncountableWebhookJobDefinition
|
|
26
26
|
from .job_definition_t import JobDefinition as JobDefinition
|
|
27
|
+
from .job_definition_t import IntegrationService as IntegrationService
|
|
27
28
|
from .job_definition_t import ProfileDefinition as ProfileDefinition
|
|
28
29
|
from .job_definition_t import ProfileMetadata as ProfileMetadata
|
|
29
30
|
from .job_definition_t import JobResult as JobResult
|
|
@@ -28,6 +28,7 @@ __all__: list[str] = [
|
|
|
28
28
|
"GenericUploadDataSourceType",
|
|
29
29
|
"HttpJobDefinitionBase",
|
|
30
30
|
"HttpMethod",
|
|
31
|
+
"IntegrationService",
|
|
31
32
|
"JobDefinition",
|
|
32
33
|
"JobDefinitionBase",
|
|
33
34
|
"JobDefinitionType",
|
|
@@ -272,6 +273,18 @@ JobDefinition = typing.Annotated[
|
|
|
272
273
|
]
|
|
273
274
|
|
|
274
275
|
|
|
276
|
+
# DO NOT MODIFY -- This file is generated by type_spec
|
|
277
|
+
@serial_class(
|
|
278
|
+
named_type_path="sdk.job_definition.IntegrationService",
|
|
279
|
+
)
|
|
280
|
+
@dataclasses.dataclass(slots=True, kw_only=True)
|
|
281
|
+
class IntegrationService:
|
|
282
|
+
id: str
|
|
283
|
+
name: str
|
|
284
|
+
import_path: str
|
|
285
|
+
enabled: bool = True
|
|
286
|
+
|
|
287
|
+
|
|
275
288
|
# DO NOT MODIFY -- This file is generated by type_spec
|
|
276
289
|
@serial_class(
|
|
277
290
|
named_type_path="sdk.job_definition.ProfileDefinition",
|
|
@@ -285,6 +298,7 @@ class ProfileDefinition:
|
|
|
285
298
|
app_base_url: str | None = None
|
|
286
299
|
client_options: client_config_t.ClientConfigOptions | None = None
|
|
287
300
|
environments: dict[integration_server_t.IntegrationEnvironment, integration_server_t.EnvironmentConfig] | None = None
|
|
301
|
+
services: list[IntegrationService] | None = None
|
|
288
302
|
|
|
289
303
|
|
|
290
304
|
# DO NOT MODIFY -- This file is generated by type_spec
|
|
@@ -299,6 +313,7 @@ class ProfileMetadata:
|
|
|
299
313
|
auth_retrieval: auth_retrieval_t.AuthRetrieval
|
|
300
314
|
client_options: client_config_t.ClientConfigOptions | None
|
|
301
315
|
jobs: list[JobDefinition]
|
|
316
|
+
services: list[IntegrationService]
|
|
302
317
|
integration_server_base_url: str | None = None
|
|
303
318
|
|
|
304
319
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: UncountablePythonSDK
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.187
|
|
4
4
|
Summary: Uncountable SDK
|
|
5
5
|
Project-URL: Homepage, https://github.com/uncountableinc/uncountable-python-sdk
|
|
6
6
|
Project-URL: Repository, https://github.com/uncountableinc/uncountable-python-sdk.git
|
|
@@ -42,8 +42,9 @@ examples/integration-server/jobs/materials_auto/example_instrument.py,sha256=XWy
|
|
|
42
42
|
examples/integration-server/jobs/materials_auto/example_parse.py,sha256=Z3iqAUdI5ya07VS3XayjK8brKuAyaqXlImJMJIUHz_4,8660
|
|
43
43
|
examples/integration-server/jobs/materials_auto/example_predictions.py,sha256=5fO4rqRa80_968A1uVZn2TlMOUib54A8rumGW02sIMM,2112
|
|
44
44
|
examples/integration-server/jobs/materials_auto/example_runsheet_wh.py,sha256=7FWDz3QpueVcf83fPrwGSqxs9pX06iKcQFte_vz3dC8,2944
|
|
45
|
+
examples/integration-server/jobs/materials_auto/example_scale_connection.py,sha256=mz_FPxHPjIawIzJM2vvD_r78p_ZCYg8Mn8OpbphcwMU,5349
|
|
45
46
|
examples/integration-server/jobs/materials_auto/example_wh.py,sha256=PN-skP27yJwDZboWk5g5EZEc3AKfVayQLfnopjsDKJc,659
|
|
46
|
-
examples/integration-server/jobs/materials_auto/profile.yaml,sha256=
|
|
47
|
+
examples/integration-server/jobs/materials_auto/profile.yaml,sha256=Oga4GypqV4cBXwnOg7vGqDiu0Ha9KvRi555gBowzOI0,2888
|
|
47
48
|
pkgs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
48
49
|
pkgs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
49
50
|
pkgs/argument_parser/__init__.py,sha256=cEEOAQS8UJ4Kbuo0oCVAHHhkbYCfrH6cUTTt2Mq3PmI,1445
|
|
@@ -54,7 +55,7 @@ pkgs/argument_parser/parser_builder.py,sha256=phtGw0dYTu1_9RrysZ4oNrkuHyodCbL9x6
|
|
|
54
55
|
pkgs/argument_parser/parser_cache.py,sha256=VEJVJsMLgDsVNsRledh78VNxHAshJ1nDWvsgpDocFGs,863
|
|
55
56
|
pkgs/argument_parser/parser_error.py,sha256=2DuYW-vQL4F1V_NEr-WW_ZdBPmH7L_uJiOuv7KhMLXw,2732
|
|
56
57
|
pkgs/argument_parser/parser_function_type.py,sha256=xmsg0YVseYbUx52qC9vP-_m9F_HehtDHo84kFjI1gV4,125
|
|
57
|
-
pkgs/argument_parser/parser_inner.py,sha256=
|
|
58
|
+
pkgs/argument_parser/parser_inner.py,sha256=zO7Q-AfdbxLrwt0rRqytXtDVvij9sPmh-6_T6Te5elk,25499
|
|
58
59
|
pkgs/argument_parser/parser_options.py,sha256=NAwNZSIH8EjyfJXJSxbn4kYmF3K7D3HuOedlSjjsF28,1332
|
|
59
60
|
pkgs/argument_parser/type_predicates.py,sha256=CkQexiseN05XDz4e34K3gIOlER5LG5Ke8u0UHK4gC04,1157
|
|
60
61
|
pkgs/filesystem_utils/__init__.py,sha256=b8NblpFX4rwyRSY3d8GGJRk9x3ET5jpsxDz0M2VnGlw,2071
|
|
@@ -99,7 +100,7 @@ pkgs/type_spec/emit_scss.py,sha256=6psmBAJyu7cvfQAIvIIMk2U6VFG_xeWy1Ea4afv0XyM,1
|
|
|
99
100
|
pkgs/type_spec/emit_typescript.py,sha256=Qu9f1Yv_wc449KHcBGO58hatbzvPvgMbZ11jferi3oY,11567
|
|
100
101
|
pkgs/type_spec/emit_typescript_util.py,sha256=dRiOm38M4DR5NtvJyaEoGXE8Zzrfnae6yuDC76OzCeU,14915
|
|
101
102
|
pkgs/type_spec/load_types.py,sha256=k5hWxFvhj8aT_9vAwtkjn9OdudQFK9IIUzZI7-Gpx2Y,5277
|
|
102
|
-
pkgs/type_spec/non_discriminated_union_exceptions.py,sha256=
|
|
103
|
+
pkgs/type_spec/non_discriminated_union_exceptions.py,sha256=jgQ4Naz8A6FbhgfR3c9SI4IMvkkft0MT6CyQXWu9RB0,559
|
|
103
104
|
pkgs/type_spec/open_api_util.py,sha256=LX6ny4xNOWXQO3qWVM8elrSDTqWsj_7jFplax2POShE,7992
|
|
104
105
|
pkgs/type_spec/test.py,sha256=uXzUwMaHCShfTeilfAYZDV03mgF2z-ZHA_W5STp_0Ts,501
|
|
105
106
|
pkgs/type_spec/util.py,sha256=Yc7QTZDjlu4ndOu3BwxLy5gRwG2mhnXN_s-iY9SYnIo,4834
|
|
@@ -138,9 +139,11 @@ uncountable/integration/cron.py,sha256=6eH-kIs3sdYPCyb62_L2M7U_uQTdMTdwY5hreEJb0
|
|
|
138
139
|
uncountable/integration/entrypoint.py,sha256=seXMXxUPjBLzpP6UAB6fRVodkgGea6vnl2oy4FWvCyE,591
|
|
139
140
|
uncountable/integration/job.py,sha256=Jeiiwvk-T3TxNYpesGbyTuQirw8gX1qQlmCIZ7r7Wuc,9187
|
|
140
141
|
uncountable/integration/request_context.py,sha256=N_FJJxqvfUJ0yV9h3I3vFTGNJiDfyLYObczcYa44pw8,999
|
|
141
|
-
uncountable/integration/scan_profiles.py,sha256=
|
|
142
|
-
uncountable/integration/scheduler.py,sha256=
|
|
142
|
+
uncountable/integration/scan_profiles.py,sha256=Fl8vr2jhW9BmnatLHZSvMnruUUXYgnXVVlbftYetY9g,3647
|
|
143
|
+
uncountable/integration/scheduler.py,sha256=YonK5CCA-eEIP5jjO-osIGPVwgTtVg9m_GNDp-B6-es,10706
|
|
143
144
|
uncountable/integration/server.py,sha256=wnqpowr3xdAXfi4gCTdfFBvmQVyTro439kqrXewBauM,6002
|
|
145
|
+
uncountable/integration/service.py,sha256=i3GSrNu7QzMfK1jOIKLdJQCM5Tzp9eF5nfWldj6Cx-M,2144
|
|
146
|
+
uncountable/integration/service_runner.py,sha256=u9J90fh4CA9nluvfuTUaF6MhCJc2VKMD4Ahmk2x6Hzc,1435
|
|
144
147
|
uncountable/integration/telemetry.py,sha256=W7z2h47Rrn7F3gF1aazbBdthNbSVi4dkQVbZFIs99-c,16155
|
|
145
148
|
uncountable/integration/webhook_bootstrap.py,sha256=RlMsdfoWhsOX8eQCVz9XhyQLqfr0hPPwW6pdGhhmfeI,2332
|
|
146
149
|
uncountable/integration/webhook_entity.py,sha256=As4x_W_zXuJ4uCAoVZwUKHPJILxP12ow0psPEv5sv9M,5220
|
|
@@ -208,7 +211,7 @@ uncountable/types/data_t.py,sha256=U_dIh5GcyaJlkrHEI4Qf4TOo1uJX07PYO70sYzzKtTo,2
|
|
|
208
211
|
uncountable/types/dataset_measurements.py,sha256=CVwWePUC02B_R_UB86yfNBwnclR-5xpZaiCs2jWtu2A,512
|
|
209
212
|
uncountable/types/dataset_measurements_t.py,sha256=iVwh-3Z23aXmPJcUJ3s9axPeOs627DElE_dhfj_PRlQ,1711
|
|
210
213
|
uncountable/types/entity.py,sha256=Zclk1LYcRaYrMDhqyCjMSLEg0fE6_q8LHvV22Qvscgs,566
|
|
211
|
-
uncountable/types/entity_t.py,sha256=
|
|
214
|
+
uncountable/types/entity_t.py,sha256=c-3qBHq9-bWoo1g0ghcg8v6q5OXPwwEmE7iEH5oDif8,30934
|
|
212
215
|
uncountable/types/experiment_groups.py,sha256=qUpFOx1AKgzaT_4khCOv5Xs6jwiQGbvHH-GUh3v1nv4,288
|
|
213
216
|
uncountable/types/experiment_groups_t.py,sha256=QjZBdjnbsD1D-VpPSSV6hBg-Q7mujjKvYtdMewtXNVU,712
|
|
214
217
|
uncountable/types/exports.py,sha256=VMmxUO2PpV1Y63hZ2AnVor4H-B6aswJ7YpSru_u89lU,334
|
|
@@ -233,8 +236,8 @@ uncountable/types/integration_session.py,sha256=MVTtZa04INF4L8PxPjqz3l1Lse6Hze3I
|
|
|
233
236
|
uncountable/types/integration_session_t.py,sha256=Nnes-u2RzvZYsybQsNhtBfbA3d2W_nuDjAS_mU6L6yM,1801
|
|
234
237
|
uncountable/types/integrations.py,sha256=0fOhtbLIOl9w1GP9J3PTagRU8mjOKV48JNLLH3SJQP0,472
|
|
235
238
|
uncountable/types/integrations_t.py,sha256=ZWwEP13mIkSiljSHN2p05XCwRgT2bdADs-8hiVQTs3I,1752
|
|
236
|
-
uncountable/types/job_definition.py,sha256=
|
|
237
|
-
uncountable/types/job_definition_t.py,sha256=
|
|
239
|
+
uncountable/types/job_definition.py,sha256=trVrCb3CHCl5zO-brA9sJonv41EITDBwK2Cy-274FUM,2027
|
|
240
|
+
uncountable/types/job_definition_t.py,sha256=zsQMsNjp_l6JN0zuRJ-hz54gHbQpc56kMsr1p-6xySg,10232
|
|
238
241
|
uncountable/types/listing.py,sha256=U-0-opI0Wgxzmwp4y0qhyT26aK_BjaqdlFVpjud-ryk,2838
|
|
239
242
|
uncountable/types/listing_t.py,sha256=z0hpUlburpIrVA5KcO1TRjkyAGGuAHvpHt5Fo6cAqIc,17521
|
|
240
243
|
uncountable/types/ml_reports.py,sha256=qhZ36pbFLOcoET3D_K2DiNx59bjp8WyurbCrWPsZTrM,757
|
|
@@ -435,7 +438,7 @@ uncountable/types/api/uploader/complete_async_parse.py,sha256=Os1HAubxTlbut6Gylj
|
|
|
435
438
|
uncountable/types/api/uploader/invoke_uploader.py,sha256=cmy7zl3AMF_AH5PCAB67rBeTE6JIcaUqPzm2NS4k_AE,1907
|
|
436
439
|
uncountable/types/api/user/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8APdZYUniAor8,55
|
|
437
440
|
uncountable/types/api/user/get_current_user_info.py,sha256=BT7bGp5SOHz3XdKWhhdZ6acnInafT2PgdJ-H4YH3W7c,1181
|
|
438
|
-
uncountablepythonsdk-0.0.
|
|
439
|
-
uncountablepythonsdk-0.0.
|
|
440
|
-
uncountablepythonsdk-0.0.
|
|
441
|
-
uncountablepythonsdk-0.0.
|
|
441
|
+
uncountablepythonsdk-0.0.187.dist-info/METADATA,sha256=9o2-KrSVgmr2N07XLPaYD7ucwbKJOCcp3iWowNMiIqk,2211
|
|
442
|
+
uncountablepythonsdk-0.0.187.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
443
|
+
uncountablepythonsdk-0.0.187.dist-info/top_level.txt,sha256=1UVGjAU-6hJY9qw2iJ7nCBeEwZ793AEN5ZfKX9A1uj4,31
|
|
444
|
+
uncountablepythonsdk-0.0.187.dist-info/RECORD,,
|
|
File without changes
|
{uncountablepythonsdk-0.0.186.dist-info → uncountablepythonsdk-0.0.187.dist-info}/top_level.txt
RENAMED
|
File without changes
|