UncountablePythonSDK 0.0.122__py3-none-any.whl → 0.0.124__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.
Potentially problematic release.
This version of UncountablePythonSDK might be problematic. Click here for more details.
- docs/conf.py +23 -4
- docs/index.md +105 -5
- docs/integration_examples/create_ingredient.md +43 -0
- docs/integration_examples/create_output.md +56 -0
- docs/integration_examples/index.md +6 -0
- docs/requirements.txt +1 -1
- examples/basic_auth.py +7 -0
- examples/integration-server/pyproject.toml +1 -1
- examples/oauth.py +7 -0
- pkgs/argument_parser/_is_namedtuple.py +3 -0
- pkgs/type_spec/type_info/emit_type_info.py +10 -1
- uncountable/integration/http_server/types.py +3 -1
- uncountable/integration/scheduler.py +62 -17
- uncountable/integration/telemetry.py +9 -1
- uncountable/types/__init__.py +10 -0
- uncountable/types/api/entity/export_entities.py +13 -0
- uncountable/types/api/integrations/push_notification.py +47 -0
- uncountable/types/api/outputs/get_output_organization.py +173 -0
- uncountable/types/async_batch_processor.py +41 -0
- uncountable/types/async_batch_t.py +1 -0
- uncountable/types/client_base.py +30 -0
- uncountable/types/integration_session.py +10 -0
- uncountable/types/integration_session_t.py +60 -0
- uncountable/types/notifications.py +11 -0
- uncountable/types/notifications_t.py +74 -0
- uncountable/types/sockets.py +11 -0
- uncountable/types/sockets_t.py +70 -0
- {uncountablepythonsdk-0.0.122.dist-info → uncountablepythonsdk-0.0.124.dist-info}/METADATA +2 -2
- {uncountablepythonsdk-0.0.122.dist-info → uncountablepythonsdk-0.0.124.dist-info}/RECORD +31 -19
- docs/quickstart.md +0 -19
- {uncountablepythonsdk-0.0.122.dist-info → uncountablepythonsdk-0.0.124.dist-info}/WHEEL +0 -0
- {uncountablepythonsdk-0.0.122.dist-info → uncountablepythonsdk-0.0.124.dist-info}/top_level.txt +0 -0
docs/conf.py
CHANGED
|
@@ -27,7 +27,7 @@ extensions = [
|
|
|
27
27
|
"sphinx_copybutton",
|
|
28
28
|
"sphinx_favicon",
|
|
29
29
|
]
|
|
30
|
-
myst_enable_extensions = ["fieldlist", "deflist"]
|
|
30
|
+
myst_enable_extensions = ["fieldlist", "deflist", "colon_fence"]
|
|
31
31
|
|
|
32
32
|
autoapi_dirs = ["../uncountable"]
|
|
33
33
|
autoapi_options = [
|
|
@@ -70,14 +70,33 @@ def _hook_missing_reference(
|
|
|
70
70
|
Manually resolve reference when autoapi reference resolution fails.
|
|
71
71
|
This is necessary because autoapi does not fully support type aliases.
|
|
72
72
|
"""
|
|
73
|
+
# example reftarget value: uncountable.types.identifier_t.IdentifierKey
|
|
73
74
|
target = node.get("reftarget", "")
|
|
75
|
+
|
|
76
|
+
# example refdoc value: api/uncountable/types/generic_upload_t/GenericUploadStrategy
|
|
77
|
+
current_doc = node.get("refdoc", "")
|
|
78
|
+
|
|
74
79
|
if not target.startswith("uncountable"):
|
|
75
80
|
return None
|
|
76
|
-
|
|
81
|
+
|
|
82
|
+
target_module, target_name = target.rsplit(".", 1)
|
|
83
|
+
|
|
84
|
+
# construct relative path from current doc page to target page
|
|
85
|
+
relative_segments_to_root = [".." for _ in current_doc.split("/")]
|
|
86
|
+
relative_segments_to_target = target_module.split(".")
|
|
87
|
+
|
|
88
|
+
# example full relative path: ../../../../../api/uncountable/types/identifier_t/#uncountable.types.identifier_t.IdentifierKey
|
|
89
|
+
full_relative_path = "/".join([
|
|
90
|
+
*relative_segments_to_root,
|
|
91
|
+
autoapi_root,
|
|
92
|
+
*relative_segments_to_target,
|
|
93
|
+
f"#{target}",
|
|
94
|
+
])
|
|
95
|
+
|
|
77
96
|
return nodes.reference(
|
|
78
|
-
text=
|
|
97
|
+
text=target_name if python_use_unqualified_type_names else target,
|
|
79
98
|
children=[contnode],
|
|
80
|
-
refuri=
|
|
99
|
+
refuri=full_relative_path,
|
|
81
100
|
)
|
|
82
101
|
|
|
83
102
|
|
docs/index.md
CHANGED
|
@@ -3,14 +3,114 @@
|
|
|
3
3
|

|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
The Uncountable Python SDK is a python package that provides a wrapper around the Uncountable REST API.
|
|
7
|
+
|
|
8
|
+
Using this SDK provides the following advantages:
|
|
9
|
+
|
|
10
|
+
- In-editor parameter/type safety
|
|
11
|
+
- Automatic parsing of response data.
|
|
12
|
+
- Reduced code boilerplate
|
|
13
|
+
- Helper methods
|
|
14
|
+
|
|
15
|
+
## Getting Started
|
|
16
|
+
The first step in any integration is to create a [Client](uncountable.core.client.Client) object. The client provides access to all available SDK methods, and includes built-in request authentication & error propagation.
|
|
17
|
+
|
|
18
|
+
### Creating a Client
|
|
19
|
+
Create a client using one of the supported authentication mechanisms. API credentials can be generated by a member of Uncountable staff.
|
|
20
|
+
|
|
21
|
+
::::{tab-set}
|
|
22
|
+
:::{tab-item} Basic Auth
|
|
23
|
+
```{literalinclude} ../examples/basic_auth.py
|
|
24
|
+
```
|
|
25
|
+
:::
|
|
26
|
+
|
|
27
|
+
:::{tab-item} OAuth
|
|
28
|
+
```{literalinclude} ../examples/oauth.py
|
|
29
|
+
```
|
|
30
|
+
:::
|
|
31
|
+
::::
|
|
32
|
+
|
|
33
|
+
The provided code examples assume that a Client has been created and stored in the `client` variable
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
### Basic Usage
|
|
37
|
+
|
|
38
|
+
:::{dropdown} List Ingredient Names & IDs
|
|
39
|
+
```{code-block} python
|
|
40
|
+
from uncountable.types import entity_t, id_source_t
|
|
41
|
+
|
|
42
|
+
client.list_id_source(
|
|
43
|
+
spec=id_source_t.IdSourceSpecEntity(entity_type=entity_t.EntityType.INGREDIENT),
|
|
44
|
+
search_label="",
|
|
45
|
+
)
|
|
46
|
+
```
|
|
47
|
+
Example Response:
|
|
48
|
+
```code
|
|
49
|
+
Data(
|
|
50
|
+
results=[
|
|
51
|
+
IdName(id=1, name='Filler'),
|
|
52
|
+
IdName(id=2, name='Calcium Oxide 2'),
|
|
53
|
+
IdName(id=3, name='Carbon Black'),
|
|
54
|
+
]
|
|
55
|
+
)
|
|
56
|
+
```
|
|
57
|
+
:::
|
|
58
|
+
|
|
59
|
+
:::{dropdown} Create an Experiment
|
|
60
|
+
```{code-block} python
|
|
61
|
+
client.create_recipe(material_family_id=1, workflow_id=1, name="Example Recipe")
|
|
62
|
+
```
|
|
63
|
+
Example Response:
|
|
64
|
+
```code
|
|
65
|
+
Data(result_id=52271)
|
|
66
|
+
```
|
|
67
|
+
:::
|
|
68
|
+
|
|
69
|
+
:::{dropdown} Upload a file
|
|
70
|
+
```{code-block} python
|
|
71
|
+
from uncountable.core.file_upload import MediaFileUpload
|
|
72
|
+
|
|
73
|
+
client.upload_files(file_uploads=[MediaFileUpload(path="/path/to/local/example_file.pdf")])
|
|
74
|
+
```
|
|
75
|
+
Example Response:
|
|
76
|
+
```code
|
|
77
|
+
[
|
|
78
|
+
UploadedFile(name='example_file.pdf', file_id=718)
|
|
79
|
+
]
|
|
80
|
+
```
|
|
81
|
+
:::
|
|
82
|
+
|
|
83
|
+
[More examples](integration_examples/index)
|
|
84
|
+
|
|
85
|
+
## Errors
|
|
86
|
+
Client methods will raise Exceptions when the API returns codes in the `3xx`, `4xx` or `5xx` ranges. Ensure all method calls are wrapped in Exception handling logic.
|
|
87
|
+
|
|
88
|
+
## Pagination
|
|
89
|
+
Many of the Uncountable APIs require pagination to fetch more than 100 results at once. The following code snippet implements pagination to fetch the Names & IDs of all Projects:
|
|
90
|
+
:::{dropdown} Pagination Example
|
|
91
|
+
```{code-block} python
|
|
92
|
+
from uncountable.types import entity_t, id_source_t
|
|
93
|
+
from uncountable.types.api.id_source.list_id_source import IdName
|
|
94
|
+
|
|
95
|
+
def fetch_all_projects(client: Client) -> list[IdName]:
|
|
96
|
+
projects: list[IdName] = []
|
|
97
|
+
while True:
|
|
98
|
+
response = client.list_id_source(
|
|
99
|
+
spec=IdSourceSpecEntity(entity_type=entity_t.EntityType.PROJECT),
|
|
100
|
+
search_label="",
|
|
101
|
+
offset=len(projects),
|
|
102
|
+
)
|
|
103
|
+
projects.extend(response.results)
|
|
104
|
+
if len(response.results) < 100:
|
|
105
|
+
return projects
|
|
106
|
+
```
|
|
107
|
+
:::
|
|
108
|
+
|
|
7
109
|
|
|
8
110
|
```{toctree}
|
|
9
|
-
:maxdepth: 2
|
|
10
111
|
:hidden:
|
|
11
|
-
|
|
12
|
-
quickstart
|
|
112
|
+
Overview <self>
|
|
13
113
|
Available SDK Methods <api/uncountable/core/client/Client>
|
|
114
|
+
integration_examples/index
|
|
14
115
|
SDK Reference <api/uncountable/index>
|
|
15
116
|
```
|
|
16
|
-
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Create an Ingredient
|
|
2
|
+
|
|
3
|
+
Use the `create_or_update_entity` method to create Ingredients.
|
|
4
|
+
|
|
5
|
+
The following fields are required when creating an Ingredient:
|
|
6
|
+
- `name`: The name of the Ingredient
|
|
7
|
+
- `core_ingredient_ingredientMaterialFamilies`: The list of material families in which to include the Ingredient
|
|
8
|
+
|
|
9
|
+
The reference name of the default definition of Ingredients is `uncIngredient`
|
|
10
|
+
|
|
11
|
+
This is an example of a minimal ingredient creation call
|
|
12
|
+
|
|
13
|
+
```{code-block} python
|
|
14
|
+
from uncountable.types import entity_t, field_values_t, identifier_t
|
|
15
|
+
|
|
16
|
+
client.create_or_update_entity(
|
|
17
|
+
entity_type=entity_t.EntityType.INGREDIENT,
|
|
18
|
+
definition_key=identifier_t.IdentifierKeyRefName(ref_name="uncIngredient"),
|
|
19
|
+
field_values=[
|
|
20
|
+
field_values_t.FieldArgumentValue(
|
|
21
|
+
field_key=identifier_t.IdentifierKeyRefName(
|
|
22
|
+
ref_name="core_ingredient_ingredientMaterialFamilies"
|
|
23
|
+
),
|
|
24
|
+
value=field_values_t.FieldValueIds(
|
|
25
|
+
entity_type=entity_t.EntityType.MATERIAL_FAMILY,
|
|
26
|
+
identifier_keys=[identifier_t.IdentifierKeyId(id=1)],
|
|
27
|
+
),
|
|
28
|
+
),
|
|
29
|
+
field_values_t.FieldArgumentValue(
|
|
30
|
+
field_key=identifier_t.IdentifierKeyRefName(ref_name="name"),
|
|
31
|
+
value=field_values_t.FieldValueText(value="Example Ingredient"),
|
|
32
|
+
),
|
|
33
|
+
],
|
|
34
|
+
)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Example Response:
|
|
38
|
+
```{code}
|
|
39
|
+
Data(modification_made=True, result_id=3124, entity=None, result_values=None)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Optional fields:
|
|
43
|
+
- `core_ingredient_quantityType`: The quantity type of the ingredient (default is `numeric`)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Create an Output
|
|
2
|
+
|
|
3
|
+
Use the `create_or_update_entity` method to create Outputs.
|
|
4
|
+
|
|
5
|
+
The following fields are required when creating an Output:
|
|
6
|
+
- `name`: The name of the Output
|
|
7
|
+
- `core_output_unitsId`: The unit the output is measured in
|
|
8
|
+
- `core_output_outputMaterialFamilies`: The list of material families in which to include the Output
|
|
9
|
+
- `core_output_quantityType`: The quantity type of the output
|
|
10
|
+
|
|
11
|
+
The reference name of the default definition of Ingredients is `unc_output_definition`
|
|
12
|
+
|
|
13
|
+
This is an example of a minimal output creation call
|
|
14
|
+
|
|
15
|
+
```{code-block} python
|
|
16
|
+
from uncountable.types import entity_t, field_values_t, identifier_t
|
|
17
|
+
|
|
18
|
+
client.create_or_update_entity(
|
|
19
|
+
entity_type=entity_t.EntityType.OUTPUT,
|
|
20
|
+
definition_key=identifier_t.IdentifierKeyRefName(ref_name="unc_output_definition"),
|
|
21
|
+
field_values=[
|
|
22
|
+
field_values_t.FieldArgumentValue(
|
|
23
|
+
field_key=identifier_t.IdentifierKeyRefName(ref_name="name"),
|
|
24
|
+
value=field_values_t.FieldValueText(value="Example Output"),
|
|
25
|
+
),
|
|
26
|
+
field_values_t.FieldArgumentValue(
|
|
27
|
+
field_key=identifier_t.IdentifierKeyRefName(ref_name="core_output_unitsId"),
|
|
28
|
+
value=field_values_t.FieldValueId(
|
|
29
|
+
entity_type=entity_t.EntityType.UNITS,
|
|
30
|
+
identifier_key=identifier_t.IdentifierKeyId(id=1),
|
|
31
|
+
),
|
|
32
|
+
),
|
|
33
|
+
field_values_t.FieldArgumentValue(
|
|
34
|
+
field_key=identifier_t.IdentifierKeyRefName(
|
|
35
|
+
ref_name="core_output_outputMaterialFamilies"
|
|
36
|
+
),
|
|
37
|
+
value=field_values_t.FieldValueIds(
|
|
38
|
+
entity_type=entity_t.EntityType.MATERIAL_FAMILY,
|
|
39
|
+
identifier_keys=[identifier_t.IdentifierKeyId(id=1)],
|
|
40
|
+
),
|
|
41
|
+
),
|
|
42
|
+
field_values_t.FieldArgumentValue(
|
|
43
|
+
field_key=identifier_t.IdentifierKeyRefName(
|
|
44
|
+
ref_name="core_output_quantityType"
|
|
45
|
+
),
|
|
46
|
+
value=field_values_t.FieldValueFieldOption(value="numeric"),
|
|
47
|
+
),
|
|
48
|
+
],
|
|
49
|
+
)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Example Response:
|
|
53
|
+
|
|
54
|
+
```{code}
|
|
55
|
+
Data(modification_made=True, result_id=653, entity=None, result_values=None)
|
|
56
|
+
```
|
docs/requirements.txt
CHANGED
examples/basic_auth.py
ADDED
examples/oauth.py
ADDED
|
@@ -267,7 +267,9 @@ def _extract_and_validate_layout(
|
|
|
267
267
|
for group in ext_info.layout.groups:
|
|
268
268
|
fields = set(group.fields or [])
|
|
269
269
|
for field in fields:
|
|
270
|
-
assert field in stype.properties,
|
|
270
|
+
assert field in stype.properties or field == DISCRIMINATOR_COMMON_NAME, (
|
|
271
|
+
f"layout-refers-to-missing-field:{field}"
|
|
272
|
+
)
|
|
271
273
|
|
|
272
274
|
local_ref_name = None
|
|
273
275
|
if group.ref_name is not None:
|
|
@@ -314,6 +316,9 @@ def _pull_property_from_type_recursively(
|
|
|
314
316
|
return _pull_property_from_type_recursively(stype.base, property_name)
|
|
315
317
|
|
|
316
318
|
|
|
319
|
+
DISCRIMINATOR_COMMON_NAME = "type"
|
|
320
|
+
|
|
321
|
+
|
|
317
322
|
def _validate_type_ext_info(
|
|
318
323
|
stype: builder.SpecTypeDefnObject,
|
|
319
324
|
) -> tuple[ExtInfoLayout | None, type_info_t.ExtInfo | None]:
|
|
@@ -324,12 +329,16 @@ def _validate_type_ext_info(
|
|
|
324
329
|
if ext_info.label_fields is not None:
|
|
325
330
|
assert stype.properties is not None
|
|
326
331
|
for name in ext_info.label_fields:
|
|
332
|
+
if name == DISCRIMINATOR_COMMON_NAME:
|
|
333
|
+
continue
|
|
327
334
|
prop = _pull_property_from_type_recursively(stype, name)
|
|
328
335
|
assert prop is not None, f"missing-label-field:{name}"
|
|
329
336
|
|
|
330
337
|
if ext_info.actions is not None:
|
|
331
338
|
assert stype.properties is not None
|
|
332
339
|
for action in ext_info.actions:
|
|
340
|
+
if action.property == DISCRIMINATOR_COMMON_NAME:
|
|
341
|
+
continue
|
|
333
342
|
prop = _pull_property_from_type_recursively(stype, action.property)
|
|
334
343
|
assert prop is not None, f"missing-action-field:{action.property}"
|
|
335
344
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
import functools
|
|
3
|
+
import json
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
|
|
5
6
|
from flask.wrappers import Response
|
|
@@ -42,7 +43,8 @@ class HttpException(Exception):
|
|
|
42
43
|
|
|
43
44
|
def make_error_response(self) -> Response:
|
|
44
45
|
return Response(
|
|
45
|
-
status=self.error_code,
|
|
46
|
+
status=self.error_code,
|
|
47
|
+
response=json.dumps({"error": {"message": str(self)}}),
|
|
46
48
|
)
|
|
47
49
|
|
|
48
50
|
|
|
@@ -5,6 +5,7 @@ import sys
|
|
|
5
5
|
import time
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from datetime import UTC
|
|
8
|
+
from enum import StrEnum
|
|
8
9
|
|
|
9
10
|
from opentelemetry.trace import get_current_span
|
|
10
11
|
|
|
@@ -19,11 +20,19 @@ from uncountable.integration.telemetry import Logger
|
|
|
19
20
|
|
|
20
21
|
SHUTDOWN_TIMEOUT_SECS = 30
|
|
21
22
|
|
|
23
|
+
AnyProcess = multiprocessing.Process | subprocess.Popen[bytes]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ProcessName(StrEnum):
|
|
27
|
+
QUEUE_RUNNER = "queue_runner"
|
|
28
|
+
CRON_SERVER = "cron_server"
|
|
29
|
+
UWSGI = "uwsgi"
|
|
30
|
+
|
|
22
31
|
|
|
23
32
|
@dataclass(kw_only=True)
|
|
24
33
|
class ProcessInfo:
|
|
25
|
-
name:
|
|
26
|
-
process:
|
|
34
|
+
name: ProcessName
|
|
35
|
+
process: AnyProcess
|
|
27
36
|
|
|
28
37
|
@property
|
|
29
38
|
def is_alive(self) -> bool:
|
|
@@ -46,14 +55,14 @@ class ProcessInfo:
|
|
|
46
55
|
return self.process.poll()
|
|
47
56
|
|
|
48
57
|
|
|
49
|
-
def handle_shutdown(logger: Logger, processes:
|
|
58
|
+
def handle_shutdown(logger: Logger, processes: dict[ProcessName, ProcessInfo]) -> None:
|
|
50
59
|
logger.log_info("received shutdown command, shutting down sub-processes")
|
|
51
|
-
for proc_info in processes:
|
|
60
|
+
for proc_info in processes.values():
|
|
52
61
|
if proc_info.is_alive:
|
|
53
62
|
proc_info.process.terminate()
|
|
54
63
|
|
|
55
64
|
shutdown_start = time.time()
|
|
56
|
-
still_living_processes = processes
|
|
65
|
+
still_living_processes = list(processes.values())
|
|
57
66
|
while (
|
|
58
67
|
time.time() - shutdown_start < SHUTDOWN_TIMEOUT_SECS
|
|
59
68
|
and len(still_living_processes) > 0
|
|
@@ -82,14 +91,50 @@ def handle_shutdown(logger: Logger, processes: list[ProcessInfo]) -> None:
|
|
|
82
91
|
proc_info.process.kill()
|
|
83
92
|
|
|
84
93
|
|
|
85
|
-
def
|
|
86
|
-
|
|
94
|
+
def restart_process(
|
|
95
|
+
logger: Logger, proc_info: ProcessInfo, processes: dict[ProcessName, ProcessInfo]
|
|
96
|
+
) -> None:
|
|
97
|
+
logger.log_error(
|
|
98
|
+
f"process {proc_info.name} shut down unexpectedly - exit code {proc_info.exitcode}. Restarting..."
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
match proc_info.name:
|
|
102
|
+
case ProcessName.QUEUE_RUNNER:
|
|
103
|
+
queue_proc = multiprocessing.Process(target=start_queue_runner)
|
|
104
|
+
queue_proc.start()
|
|
105
|
+
new_info = ProcessInfo(name=ProcessName.QUEUE_RUNNER, process=queue_proc)
|
|
106
|
+
processes[ProcessName.QUEUE_RUNNER] = new_info
|
|
107
|
+
try:
|
|
108
|
+
_wait_queue_runner_online()
|
|
109
|
+
logger.log_info("queue runner restarted successfully")
|
|
110
|
+
except Exception as e:
|
|
111
|
+
logger.log_exception(e)
|
|
112
|
+
logger.log_error(
|
|
113
|
+
"queue runner failed to restart, shutting down scheduler"
|
|
114
|
+
)
|
|
115
|
+
handle_shutdown(logger, processes)
|
|
116
|
+
sys.exit(1)
|
|
117
|
+
|
|
118
|
+
case ProcessName.CRON_SERVER:
|
|
119
|
+
cron_proc = multiprocessing.Process(target=cron_target)
|
|
120
|
+
cron_proc.start()
|
|
121
|
+
new_info = ProcessInfo(name=ProcessName.CRON_SERVER, process=cron_proc)
|
|
122
|
+
processes[ProcessName.CRON_SERVER] = new_info
|
|
123
|
+
logger.log_info("cron server restarted successfully")
|
|
124
|
+
|
|
125
|
+
case ProcessName.UWSGI:
|
|
126
|
+
uwsgi_proc: AnyProcess = subprocess.Popen(["uwsgi", "--die-on-term"])
|
|
127
|
+
new_info = ProcessInfo(name=ProcessName.UWSGI, process=uwsgi_proc)
|
|
128
|
+
processes[ProcessName.UWSGI] = new_info
|
|
129
|
+
logger.log_info("uwsgi restarted successfully")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def check_process_alive(
|
|
133
|
+
logger: Logger, processes: dict[ProcessName, ProcessInfo]
|
|
134
|
+
) -> None:
|
|
135
|
+
for proc_info in processes.values():
|
|
87
136
|
if not proc_info.is_alive:
|
|
88
|
-
logger
|
|
89
|
-
f"process {proc_info.name} shut down unexpectedly! shutting down scheduler; exit code is {proc_info.exitcode}"
|
|
90
|
-
)
|
|
91
|
-
handle_shutdown(logger, processes)
|
|
92
|
-
sys.exit(1)
|
|
137
|
+
restart_process(logger, proc_info, processes)
|
|
93
138
|
|
|
94
139
|
|
|
95
140
|
def _wait_queue_runner_online() -> None:
|
|
@@ -113,17 +158,17 @@ def _wait_queue_runner_online() -> None:
|
|
|
113
158
|
|
|
114
159
|
def main() -> None:
|
|
115
160
|
logger = Logger(get_current_span())
|
|
116
|
-
processes:
|
|
161
|
+
processes: dict[ProcessName, ProcessInfo] = {}
|
|
117
162
|
|
|
118
163
|
multiprocessing.set_start_method("forkserver")
|
|
119
164
|
|
|
120
165
|
def add_process(process: ProcessInfo) -> None:
|
|
121
|
-
processes.
|
|
166
|
+
processes[process.name] = process
|
|
122
167
|
logger.log_info(f"started process {process.name}")
|
|
123
168
|
|
|
124
169
|
runner_process = multiprocessing.Process(target=start_queue_runner)
|
|
125
170
|
runner_process.start()
|
|
126
|
-
add_process(ProcessInfo(name=
|
|
171
|
+
add_process(ProcessInfo(name=ProcessName.QUEUE_RUNNER, process=runner_process))
|
|
127
172
|
|
|
128
173
|
try:
|
|
129
174
|
_wait_queue_runner_online()
|
|
@@ -134,13 +179,13 @@ def main() -> None:
|
|
|
134
179
|
|
|
135
180
|
cron_process = multiprocessing.Process(target=cron_target)
|
|
136
181
|
cron_process.start()
|
|
137
|
-
add_process(ProcessInfo(name=
|
|
182
|
+
add_process(ProcessInfo(name=ProcessName.CRON_SERVER, process=cron_process))
|
|
138
183
|
|
|
139
184
|
uwsgi_process = subprocess.Popen([
|
|
140
185
|
"uwsgi",
|
|
141
186
|
"--die-on-term",
|
|
142
187
|
])
|
|
143
|
-
add_process(ProcessInfo(name=
|
|
188
|
+
add_process(ProcessInfo(name=ProcessName.UWSGI, process=uwsgi_process))
|
|
144
189
|
|
|
145
190
|
try:
|
|
146
191
|
while True:
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import functools
|
|
2
|
+
import json
|
|
2
3
|
import os
|
|
3
4
|
import time
|
|
4
5
|
import traceback
|
|
@@ -32,6 +33,11 @@ def _cast_attributes(attributes: dict[str, base_t.JsonValue]) -> Attributes:
|
|
|
32
33
|
return cast(Attributes, attributes)
|
|
33
34
|
|
|
34
35
|
|
|
36
|
+
def one_line_formatter(record: LogRecord) -> str:
|
|
37
|
+
json_data = record.to_json()
|
|
38
|
+
return json.dumps(json.loads(json_data), separators=(",", ":")) + "\n"
|
|
39
|
+
|
|
40
|
+
|
|
35
41
|
@functools.cache
|
|
36
42
|
def get_otel_resource() -> Resource:
|
|
37
43
|
attributes: dict[str, base_t.JsonValue] = {
|
|
@@ -60,7 +66,9 @@ def get_otel_tracer() -> Tracer:
|
|
|
60
66
|
@functools.cache
|
|
61
67
|
def get_otel_logger() -> OTELLogger:
|
|
62
68
|
provider = LoggerProvider(resource=get_otel_resource())
|
|
63
|
-
provider.add_log_record_processor(
|
|
69
|
+
provider.add_log_record_processor(
|
|
70
|
+
BatchLogRecordProcessor(ConsoleLogExporter(formatter=one_line_formatter))
|
|
71
|
+
)
|
|
64
72
|
if get_otel_enabled():
|
|
65
73
|
provider.add_log_record_processor(BatchLogRecordProcessor(OTLPLogExporter()))
|
|
66
74
|
_logs.set_logger_provider(provider)
|
uncountable/types/__init__.py
CHANGED
|
@@ -50,6 +50,7 @@ from .api.inputs import get_input_names as get_input_names_t
|
|
|
50
50
|
from .api.inputs import get_inputs_data as get_inputs_data_t
|
|
51
51
|
from .api.outputs import get_output_data as get_output_data_t
|
|
52
52
|
from .api.outputs import get_output_names as get_output_names_t
|
|
53
|
+
from .api.outputs import get_output_organization as get_output_organization_t
|
|
53
54
|
from .api.project import get_projects as get_projects_t
|
|
54
55
|
from .api.project import get_projects_data as get_projects_data_t
|
|
55
56
|
from .api.recipes import get_recipe_calculations as get_recipe_calculations_t
|
|
@@ -64,6 +65,7 @@ from . import identifier_t as identifier_t
|
|
|
64
65
|
from . import input_attributes_t as input_attributes_t
|
|
65
66
|
from . import inputs_t as inputs_t
|
|
66
67
|
from . import integration_server_t as integration_server_t
|
|
68
|
+
from . import integration_session_t as integration_session_t
|
|
67
69
|
from . import integrations_t as integrations_t
|
|
68
70
|
from .api.uploader import invoke_uploader as invoke_uploader_t
|
|
69
71
|
from . import job_definition_t as job_definition_t
|
|
@@ -73,12 +75,14 @@ from .api.entity import lock_entity as lock_entity_t
|
|
|
73
75
|
from .api.recipes import lock_recipes as lock_recipes_t
|
|
74
76
|
from .api.entity import lookup_entity as lookup_entity_t
|
|
75
77
|
from .api.id_source import match_id_source as match_id_source_t
|
|
78
|
+
from . import notifications_t as notifications_t
|
|
76
79
|
from . import outputs_t as outputs_t
|
|
77
80
|
from . import overrides_t as overrides_t
|
|
78
81
|
from . import permissions_t as permissions_t
|
|
79
82
|
from . import phases_t as phases_t
|
|
80
83
|
from . import post_base_t as post_base_t
|
|
81
84
|
from .api.integrations import publish_realtime_data as publish_realtime_data_t
|
|
85
|
+
from .api.integrations import push_notification as push_notification_t
|
|
82
86
|
from . import queued_job_t as queued_job_t
|
|
83
87
|
from . import recipe_identifiers_t as recipe_identifiers_t
|
|
84
88
|
from . import recipe_inputs_t as recipe_inputs_t
|
|
@@ -108,6 +112,7 @@ from .api.recipes import set_recipe_output_file as set_recipe_output_file_t
|
|
|
108
112
|
from .api.recipes import set_recipe_outputs as set_recipe_outputs_t
|
|
109
113
|
from .api.recipes import set_recipe_tags as set_recipe_tags_t
|
|
110
114
|
from .api.entity import set_values as set_values_t
|
|
115
|
+
from . import sockets_t as sockets_t
|
|
111
116
|
from .api.entity import transition_entity_phase as transition_entity_phase_t
|
|
112
117
|
from .api.recipes import unarchive_recipes as unarchive_recipes_t
|
|
113
118
|
from . import units_t as units_t
|
|
@@ -170,6 +175,7 @@ __all__: list[str] = [
|
|
|
170
175
|
"get_inputs_data_t",
|
|
171
176
|
"get_output_data_t",
|
|
172
177
|
"get_output_names_t",
|
|
178
|
+
"get_output_organization_t",
|
|
173
179
|
"get_projects_t",
|
|
174
180
|
"get_projects_data_t",
|
|
175
181
|
"get_recipe_calculations_t",
|
|
@@ -184,6 +190,7 @@ __all__: list[str] = [
|
|
|
184
190
|
"input_attributes_t",
|
|
185
191
|
"inputs_t",
|
|
186
192
|
"integration_server_t",
|
|
193
|
+
"integration_session_t",
|
|
187
194
|
"integrations_t",
|
|
188
195
|
"invoke_uploader_t",
|
|
189
196
|
"job_definition_t",
|
|
@@ -193,12 +200,14 @@ __all__: list[str] = [
|
|
|
193
200
|
"lock_recipes_t",
|
|
194
201
|
"lookup_entity_t",
|
|
195
202
|
"match_id_source_t",
|
|
203
|
+
"notifications_t",
|
|
196
204
|
"outputs_t",
|
|
197
205
|
"overrides_t",
|
|
198
206
|
"permissions_t",
|
|
199
207
|
"phases_t",
|
|
200
208
|
"post_base_t",
|
|
201
209
|
"publish_realtime_data_t",
|
|
210
|
+
"push_notification_t",
|
|
202
211
|
"queued_job_t",
|
|
203
212
|
"recipe_identifiers_t",
|
|
204
213
|
"recipe_inputs_t",
|
|
@@ -228,6 +237,7 @@ __all__: list[str] = [
|
|
|
228
237
|
"set_recipe_outputs_t",
|
|
229
238
|
"set_recipe_tags_t",
|
|
230
239
|
"set_values_t",
|
|
240
|
+
"sockets_t",
|
|
231
241
|
"transition_entity_phase_t",
|
|
232
242
|
"unarchive_recipes_t",
|
|
233
243
|
"units_t",
|
|
@@ -18,12 +18,24 @@ __all__: list[str] = [
|
|
|
18
18
|
"Data",
|
|
19
19
|
"ENDPOINT_METHOD",
|
|
20
20
|
"ENDPOINT_PATH",
|
|
21
|
+
"ListingAttribute",
|
|
21
22
|
]
|
|
22
23
|
|
|
23
24
|
ENDPOINT_METHOD = "POST"
|
|
24
25
|
ENDPOINT_PATH = "api/external/entity/export_entities"
|
|
25
26
|
|
|
26
27
|
|
|
28
|
+
# DO NOT MODIFY -- This file is generated by type_spec
|
|
29
|
+
@serial_class(
|
|
30
|
+
named_type_path="sdk.api.entity.export_entities.ListingAttribute",
|
|
31
|
+
unconverted_values={"value"},
|
|
32
|
+
)
|
|
33
|
+
@dataclasses.dataclass(slots=base_t.ENABLE_SLOTS, kw_only=True) # type: ignore[literal-required]
|
|
34
|
+
class ListingAttribute:
|
|
35
|
+
key: str
|
|
36
|
+
value: base_t.JsonValue
|
|
37
|
+
|
|
38
|
+
|
|
27
39
|
# DO NOT MODIFY -- This file is generated by type_spec
|
|
28
40
|
@serial_class(
|
|
29
41
|
named_type_path="sdk.api.entity.export_entities.Arguments",
|
|
@@ -34,6 +46,7 @@ class Arguments:
|
|
|
34
46
|
type: exports_t.ExportType = exports_t.ExportType.EXCEL
|
|
35
47
|
client_timezone: exports_t.ListingExportUserTimezone | None = None
|
|
36
48
|
limit: int | None = None
|
|
49
|
+
attributes: list[ListingAttribute] | None = None
|
|
37
50
|
|
|
38
51
|
|
|
39
52
|
# DO NOT MODIFY -- This file is generated by type_spec
|