pinexq-client 0.2.0.2024.607.8__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.
Files changed (49) hide show
  1. hypermedia_client/core/__init__.py +8 -0
  2. hypermedia_client/core/base_relations.py +8 -0
  3. hypermedia_client/core/enterapi.py +17 -0
  4. hypermedia_client/core/exceptions.py +2 -0
  5. hypermedia_client/core/hco/__init__.py +0 -0
  6. hypermedia_client/core/hco/action_hco.py +70 -0
  7. hypermedia_client/core/hco/action_with_parameters_hco.py +86 -0
  8. hypermedia_client/core/hco/download_link_hco.py +37 -0
  9. hypermedia_client/core/hco/hco_base.py +91 -0
  10. hypermedia_client/core/hco/link_hco.py +57 -0
  11. hypermedia_client/core/hco/upload_action_hco.py +113 -0
  12. hypermedia_client/core/http_headers.py +9 -0
  13. hypermedia_client/core/media_types.py +24 -0
  14. hypermedia_client/core/model/__init__.py +0 -0
  15. hypermedia_client/core/model/error.py +9 -0
  16. hypermedia_client/core/model/sirenmodels.py +155 -0
  17. hypermedia_client/core/polling.py +37 -0
  18. hypermedia_client/core/sirenaccess.py +173 -0
  19. hypermedia_client/job_management/__init__.py +6 -0
  20. hypermedia_client/job_management/enterjma.py +42 -0
  21. hypermedia_client/job_management/hcos/__init__.py +12 -0
  22. hypermedia_client/job_management/hcos/entrypoint_hco.py +57 -0
  23. hypermedia_client/job_management/hcos/info_hco.py +42 -0
  24. hypermedia_client/job_management/hcos/input_dataslot_hco.py +82 -0
  25. hypermedia_client/job_management/hcos/job_hco.py +174 -0
  26. hypermedia_client/job_management/hcos/job_query_result_hco.py +63 -0
  27. hypermedia_client/job_management/hcos/job_used_tags_hco.py +30 -0
  28. hypermedia_client/job_management/hcos/jobsroot_hco.py +80 -0
  29. hypermedia_client/job_management/hcos/output_dataslot_hco.py +44 -0
  30. hypermedia_client/job_management/hcos/processing_step_hco.py +71 -0
  31. hypermedia_client/job_management/hcos/processing_step_used_tags_hco.py +30 -0
  32. hypermedia_client/job_management/hcos/processingstep_query_result_hco.py +68 -0
  33. hypermedia_client/job_management/hcos/processingsteproot_hco.py +72 -0
  34. hypermedia_client/job_management/hcos/user_hco.py +37 -0
  35. hypermedia_client/job_management/hcos/workdata_hco.py +127 -0
  36. hypermedia_client/job_management/hcos/workdata_query_result_hco.py +67 -0
  37. hypermedia_client/job_management/hcos/workdata_used_tags_query_result_hco.py +30 -0
  38. hypermedia_client/job_management/hcos/workdataroot_hco.py +84 -0
  39. hypermedia_client/job_management/ideas.md +28 -0
  40. hypermedia_client/job_management/known_relations.py +29 -0
  41. hypermedia_client/job_management/model/__init__.py +1 -0
  42. hypermedia_client/job_management/model/open_api_generated.py +890 -0
  43. hypermedia_client/job_management/model/sirenentities.py +112 -0
  44. hypermedia_client/job_management/tool/__init__.py +1 -0
  45. hypermedia_client/job_management/tool/job.py +442 -0
  46. pinexq_client-0.2.0.2024.607.8.dist-info/METADATA +105 -0
  47. pinexq_client-0.2.0.2024.607.8.dist-info/RECORD +49 -0
  48. pinexq_client-0.2.0.2024.607.8.dist-info/WHEEL +4 -0
  49. pinexq_client-0.2.0.2024.607.8.dist-info/licenses/LICENSE +19 -0
@@ -0,0 +1,155 @@
1
+ import warnings
2
+ from typing import List, Any, Union, Type, TypeVar, Self
3
+
4
+ from httpx import URL
5
+ from pydantic import BaseModel, ConfigDict, constr, Field, field_validator, model_validator
6
+
7
+ TParameters = TypeVar('TParameters', bound='BaseModel')
8
+ TEntity = TypeVar('TEntity', bound='Entity')
9
+
10
+
11
+ class SirenBaseModel(BaseModel):
12
+ model_config = ConfigDict(
13
+ extra='forbid'
14
+ )
15
+
16
+
17
+ class ActionField(SirenBaseModel):
18
+ name: constr(min_length=1)
19
+ type: str | None = None
20
+ value: Any | None = None
21
+ class_: List[str] | None = Field(None, alias='class')
22
+ title: str | None = None
23
+ accept: str | None = None
24
+ maxFileSizeBytes: int | None = None
25
+ allowMultiple: bool | None = None
26
+
27
+
28
+ class Action(SirenBaseModel):
29
+ name: constr(min_length=1)
30
+ href: constr(min_length=1)
31
+ class_: List[str] | None = Field(None, alias='class')
32
+ method: str | None = None
33
+ title: str | None = None
34
+ type: str | None = None
35
+ fields: List[ActionField] | None = None
36
+
37
+ def has_parameters(self) -> bool:
38
+ return (self.fields is not None) and (len(self.fields) > 0)
39
+
40
+ def get_default_parameters(self, parameter_type: Type[TParameters] = Any,
41
+ default_if_none: TParameters | None = None) -> TParameters | None:
42
+ if not self.has_parameters():
43
+ raise Exception("Can not get default parameters for action without parameters")
44
+ if len(self.fields) > 1:
45
+ raise Exception("Action has more than one field, can not determine default parameters")
46
+ if not self.fields[0].value:
47
+ return default_if_none
48
+ dump = self.fields[0].model_dump(by_alias=True)['value']
49
+ return parameter_type.model_validate(dump)
50
+
51
+
52
+ class EmbeddedLinkEntity(SirenBaseModel):
53
+ class_: List[str] | None = Field(None, alias='class')
54
+ rel: List[str]
55
+ href: constr(min_length=1)
56
+ title: str | None = None
57
+ type: str | None = None
58
+
59
+
60
+ class Link(SirenBaseModel):
61
+ href: constr(min_length=1)
62
+ rel: List[str]
63
+ class_: List[str] | None = Field(None, alias='class')
64
+ title: str | None = None
65
+ type: str | None = None
66
+
67
+ @classmethod
68
+ def from_url(cls,
69
+ url: URL,
70
+ relation: list[str],
71
+ title: str | None = None,
72
+ mediatype: str | None = None,
73
+ class_: list[str] | None = None) -> Self:
74
+ instance = cls(href=str(url), rel=relation, title=title, type=mediatype)
75
+ instance.class_ = class_
76
+ return instance
77
+
78
+
79
+ class Entity(SirenBaseModel):
80
+ rel: List[str] = [] # used when embedded
81
+ class_: List[str] | None = Field(None, alias='class')
82
+ title: str | None = None
83
+ properties: Any | None = None
84
+ entities: List[Union['Entity', EmbeddedLinkEntity]] | None = None
85
+ actions: List[Action] | None = None
86
+ links: List[Link] | None = None
87
+
88
+ @model_validator(mode='after')
89
+ def _check_extra_properties(self) -> Self:
90
+ if type(self) is Entity:
91
+ # skip this validation for the base class
92
+ return self
93
+ if isinstance(self.properties, dict):
94
+ warnings.warn(f"Unresolved properties in Entity '{self.title}'! Implementation might be incomplete?")
95
+ elif isinstance(self.properties, BaseModel) and self.properties.model_extra:
96
+ warnings.warn(
97
+ f"Entity with extra properties received! Possibly a version mismatch "
98
+ f"between server and client? "
99
+ f"(unexpected properties: {[n for n in self.properties.model_extra.keys()]})"
100
+ )
101
+ return self
102
+
103
+ def find_first_entity_with_relation(self, searched_relation: str, entity_type: Type[TEntity] = 'Entity'
104
+ ) -> Union[TEntity, None]:
105
+ if self.entities is None:
106
+ return None
107
+
108
+ for entity in self.entities:
109
+ if self.contains_relation(entity.rel, searched_relation):
110
+ return entity_type.model_validate(entity.model_dump(by_alias=True))
111
+ return None
112
+
113
+ def find_all_entities_with_relation(self, searched_relation: str,
114
+ entity_type: Type[TEntity] = 'Entity') -> List[TEntity]:
115
+ if self.entities is None:
116
+ return []
117
+
118
+ result = []
119
+ for entity in self.entities:
120
+ if self.contains_relation(entity.rel, searched_relation):
121
+ # map to requested entity type so properties are not in a dict
122
+ mapped = entity_type.model_validate(entity.model_dump(by_alias=True))
123
+ result.append(mapped)
124
+ return result
125
+
126
+ def find_first_link_with_relation(self, searched_relation: str) -> Link | None:
127
+ if self.links is None:
128
+ return None
129
+
130
+ for link in self.links:
131
+ if self.contains_relation(link.rel, searched_relation):
132
+ return link
133
+ return None
134
+
135
+ def action_exists(self, name: str) -> bool:
136
+ return self.find_first_action_with_name(name) is not None
137
+
138
+ @staticmethod
139
+ def contains_relation(relations: list[str] | None, searched_relation: str) -> bool:
140
+ if relations is None:
141
+ return False
142
+
143
+ for relation in relations:
144
+ if relation == searched_relation:
145
+ return True
146
+ return False
147
+
148
+ def find_first_action_with_name(self, name: str):
149
+ if self.actions is None:
150
+ return None
151
+
152
+ for action in self.actions:
153
+ if action.name == name:
154
+ return action
155
+ return None
@@ -0,0 +1,37 @@
1
+ import time
2
+ from typing import Callable
3
+
4
+
5
+ class PollingException(Exception):
6
+ pass
7
+
8
+
9
+ def wait_until(
10
+ condition: Callable,
11
+ polling_interval_ms: int = 200,
12
+ timeout_ms: int = 5000,
13
+ timeout_message: str | None = None,
14
+ error_condition: Callable | None = None,
15
+ error_condition_message: str | None = None,
16
+
17
+ ) -> None:
18
+ start = time.time()
19
+ timeout = start + timeout_ms / 1000
20
+ while True:
21
+ now = time.time()
22
+ next_due = now + polling_interval_ms / 1000
23
+ success = condition()
24
+ if success:
25
+ return
26
+
27
+ if error_condition:
28
+ error = error_condition()
29
+ if error:
30
+ raise PollingException(
31
+ f"{f': {error_condition_message}' if error_condition_message else 'Error condition meet while waiting'}")
32
+
33
+ if (timeout > 0) and (now > timeout):
34
+ raise TimeoutError(
35
+ f"{f': {timeout_message}' if timeout_message else f'Timeout while waiting. Waited: {timeout_ms}ms'}")
36
+
37
+ time.sleep(next_due - now)
@@ -0,0 +1,173 @@
1
+ import json
2
+ import logging
3
+ from io import BytesIO
4
+ from typing import Any, BinaryIO, Iterable, AsyncIterable
5
+
6
+ import httpx
7
+ from httpx import Response
8
+ from httpx import URL
9
+ from pydantic import BaseModel
10
+
11
+ from .exceptions import SirenException
12
+ from .http_headers import Headers
13
+ from .base_relations import BaseRelations
14
+ from .media_types import MediaTypes
15
+ from .model.error import ProblemDetails
16
+ from .model.sirenmodels import Entity, Link, Action, TEntity
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ # for now, we do not support navigations to non siren links (e.g. external)
22
+ def get_resource(client: httpx.Client, href: str, media_type: str = MediaTypes.SIREN,
23
+ parse_type: type[TEntity] = Entity) -> TEntity | ProblemDetails | Response:
24
+ try:
25
+ # assume get for links
26
+ response = client.get(href)
27
+ except (httpx.ConnectTimeout, httpx.ConnectError) as exc:
28
+ raise SirenException(f"Http-client error requesting resource: {href}") from exc
29
+ expected_type = media_type or MediaTypes.SIREN # if not specified expect siren
30
+
31
+ if response.status_code == httpx.codes.OK:
32
+ if (media_type := response.headers.get(Headers.CONTENT_TYPE, MediaTypes.SIREN)) != expected_type:
33
+ logger.warning(f"Expected type {expected_type} not matched by response: "
34
+ f"' got: '{media_type}'")
35
+
36
+ if media_type == MediaTypes.SIREN.value or media_type is None: # assume siren if not specified
37
+ resp = response.content.decode()
38
+ entity = parse_type.model_validate_json(resp)
39
+ return entity
40
+ else:
41
+ return response
42
+
43
+ elif response.status_code >= 400:
44
+ return handle_error_response(response)
45
+ else:
46
+ logger.warning(f"Unexpected return code: {response.status_code}")
47
+ return response
48
+
49
+
50
+ def navigate(client: httpx.Client, link: Link,
51
+ parse_type: type[TEntity] = Entity) -> TEntity | ProblemDetails | Response:
52
+ return get_resource(client, link.href, link.type, parse_type)
53
+
54
+
55
+ def ensure_siren_response(response: TEntity | ProblemDetails | Response) -> TEntity:
56
+ if isinstance(response, ProblemDetails):
57
+ raise SirenException(
58
+ f"Error while navigating: {response}")
59
+ if isinstance(response, Response):
60
+ raise SirenException(
61
+ f"Error while navigating, unexpected response: {response}")
62
+ return response
63
+
64
+
65
+ def upload_json(client: httpx.Client, action: Action, json_payload: Any,
66
+ filename: str) -> None | URL | ProblemDetails | Response:
67
+ return upload_binary(client, action, json.dumps(json_payload), filename, MediaTypes.APPLICATION_JSON)
68
+
69
+
70
+ def upload_binary(client: httpx.Client, action: Action, content: str | bytes | Iterable[bytes] | AsyncIterable[bytes],
71
+ filename: str,
72
+ mediatype: str = MediaTypes.OCTET_STREAM) -> None | URL | ProblemDetails | Response:
73
+
74
+ if isinstance(content, str):
75
+ payload = BytesIO(content.encode(encoding="utf-8"))
76
+ elif isinstance(content, bytes):
77
+ payload = BytesIO(content)
78
+ else:
79
+ # Fixme: iterable are not supported. Use the 'content' instead of the 'file' parameter of the request call?
80
+ raise NotImplemented('Iterables are not supported as payload (yet)! Convert to bytes or string.')
81
+
82
+ return upload_file(client, action, payload, filename, mediatype)
83
+
84
+
85
+ # for now no support for multi file upload
86
+ def upload_file(client: httpx.Client, action: Action, file: BinaryIO, filename: str,
87
+ mediatype: str = MediaTypes.OCTET_STREAM) -> None | URL | ProblemDetails | Response:
88
+ if action.type != MediaTypes.MULTIPART_FORM_DATA:
89
+ raise SirenException(
90
+ f"Action with upload requires type: {MediaTypes.MULTIPART_FORM_DATA} but found: {action.type}")
91
+
92
+ files = {'upload-file': (filename, file, mediatype)}
93
+ try:
94
+ response = client.request(method=action.method, url=action.href, files=files)
95
+ except httpx.RequestError as exc:
96
+ raise SirenException(f"Error from httpx while uploading data to: {action.href}") from exc
97
+ return handle_action_result(response)
98
+
99
+
100
+ def execute_action_on_entity(client: httpx.Client, entity: Entity, name: str, parameters: BaseModel | None = None):
101
+ action = entity.find_first_action_with_name(name)
102
+ if action is None:
103
+ raise SirenException(f"Entity does not contain expected action: {name}")
104
+
105
+ return execute_action(client, action, parameters)
106
+
107
+
108
+ def execute_action(client: httpx.Client, action: Action,
109
+ parameters: BaseModel | None = None) -> None | URL | ProblemDetails | Response:
110
+ if action.has_parameters() is False:
111
+ # no parameters required
112
+ if parameters is not None:
113
+ raise SirenException(f"Action requires no parameters but got some")
114
+ else:
115
+ # parameters required
116
+ if parameters is None:
117
+ raise SirenException(f"Action requires parameters but non provided")
118
+
119
+ action_parameters = None
120
+ if parameters is not None:
121
+ action_parameters = parameters.model_dump_json(by_alias=True, exclude_none=True)
122
+
123
+ try:
124
+ response = client.request(
125
+ method=action.method,
126
+ url=action.href,
127
+ content=action_parameters,
128
+ headers={Headers.CONTENT_TYPE.value: MediaTypes.APPLICATION_JSON.value}
129
+ )
130
+ except httpx.RequestError as exc:
131
+ raise SirenException(f"Error from httpx while executing action: {action.href}") from exc
132
+
133
+ return handle_action_result(response)
134
+
135
+
136
+ def handle_action_result(response: Response) -> None | URL | ProblemDetails | Response:
137
+ if response.status_code == httpx.codes.OK:
138
+ return
139
+ if response.status_code == httpx.codes.CREATED:
140
+ location_header = response.headers.get(Headers.LOCATION_HEADER, None)
141
+ if location_header is None:
142
+ logger.warning(f"Got created response without location header")
143
+ return
144
+
145
+ return URL(location_header)
146
+
147
+ elif response.status_code >= 400:
148
+ return handle_error_response(response)
149
+ else:
150
+ logger.warning(f"Unexpected return code: {response.status_code}")
151
+ return response
152
+
153
+
154
+ def handle_error_response(response: Response) -> ProblemDetails | Response:
155
+ content_type = response.headers.get(Headers.CONTENT_TYPE, '')
156
+ if content_type.startswith(MediaTypes.PROBLEM_DETAILS.value):
157
+ return ProblemDetails.model_validate(response.json())
158
+ else:
159
+ logger.warning(
160
+ f"Error case did not return media type: '{MediaTypes.PROBLEM_DETAILS.value}', "
161
+ f"got '{response.headers.get(Headers.CONTENT_TYPE, None)}' instead!")
162
+ return response
163
+
164
+
165
+ def navigate_self(client: httpx.Client, entity: Entity) -> Entity | ProblemDetails | None:
166
+ if entity is None:
167
+ return None
168
+
169
+ self_link = entity.find_first_link_with_relation(BaseRelations.SELF)
170
+ if self_link is None:
171
+ return None
172
+
173
+ return navigate(client, self_link)
@@ -0,0 +1,6 @@
1
+ # ruff: noqa: F401
2
+ from .enterjma import enter_jma
3
+ from .tool import Job
4
+
5
+ # Protocol version the JMA is using
6
+ __jma_version__ = [6, 0, 0]
@@ -0,0 +1,42 @@
1
+ from typing import TypeVar, Type
2
+
3
+ import httpx
4
+
5
+ import logging
6
+
7
+ from hypermedia_client.core import Entity
8
+ from hypermedia_client.core.enterapi import enter_api
9
+ from hypermedia_client.core.hco.hco_base import Hco
10
+ from hypermedia_client.job_management.hcos.entrypoint_hco import EntryPointHco
11
+ from hypermedia_client.job_management.model.sirenentities import EntryPointEntity
12
+ import hypermedia_client.job_management
13
+
14
+ LOG = logging.getLogger(__name__)
15
+
16
+ THco = TypeVar("THco", bound=Hco)
17
+
18
+
19
+ def _version_match_major_minor(ver1: list[int], ver2: list[int]) -> bool:
20
+ return all([v1 == v2 for v1, v2 in zip(ver1[:2], ver2[:2])])
21
+
22
+
23
+ def enter_jma(
24
+ client: httpx.Client,
25
+ entrypoint_hco_type: Type[THco] = EntryPointHco,
26
+ entrypoint_entity_type: Type[Entity] = EntryPointEntity,
27
+ entrypoint: str = "api/EntryPoint",
28
+ ) -> EntryPointHco:
29
+ entry_point_hco = enter_api(client, entrypoint_hco_type, entrypoint_entity_type, entrypoint)
30
+
31
+ info = entry_point_hco.info_link.navigate()
32
+
33
+ # Check for matching protocol versions
34
+ client_version = hypermedia_client.job_management.__jma_version__
35
+ jma_version = [int(i) for i in str.split(info.api_version, '.')]
36
+ if not _version_match_major_minor(jma_version, client_version):
37
+ LOG.warning(
38
+ f"Version mismatch between 'hypermedia_client' (v{'.'.join(map(str ,client_version))}) "
39
+ f"and 'JobManagementAPI' (v{'.'.join(map(str, jma_version))})! "
40
+ )
41
+
42
+ return entry_point_hco
@@ -0,0 +1,12 @@
1
+ from .input_dataslot_hco import *
2
+ from .entrypoint_hco import *
3
+ from .info_hco import *
4
+ from .job_hco import *
5
+ from .job_query_result_hco import *
6
+ from .jobsroot_hco import *
7
+ from .processing_step_hco import *
8
+ from .processingstep_query_result_hco import *
9
+ from .processingsteproot_hco import *
10
+ from .workdata_hco import *
11
+ from .workdata_query_result_hco import *
12
+ from .workdataroot_hco import *
@@ -0,0 +1,57 @@
1
+ from enum import StrEnum
2
+ from typing import Self
3
+
4
+ import httpx
5
+
6
+ from hypermedia_client.core.hco.hco_base import Hco
7
+ from hypermedia_client.core.hco.link_hco import LinkHco
8
+ from hypermedia_client.job_management.hcos.info_hco import InfoLink
9
+ from hypermedia_client.job_management.hcos.jobsroot_hco import JobsRootLink
10
+ from hypermedia_client.job_management.hcos.processingsteproot_hco import ProcessingStepsRootLink
11
+ from hypermedia_client.job_management.hcos.workdataroot_hco import WorkDataRootLink
12
+ from hypermedia_client.job_management.known_relations import Relations
13
+ from hypermedia_client.job_management.model.sirenentities import EntryPointEntity
14
+
15
+
16
+ class EntryPointLink(LinkHco):
17
+ def navigate(self) -> 'EntryPointHco':
18
+ return EntryPointHco.from_entity(self._navigate_internal(EntryPointEntity), self._client)
19
+
20
+
21
+ class EntrypointRelations(StrEnum):
22
+ JOBS_ROOT = "JobsRoot"
23
+ WORKDATA_ROOT = "WorkDataRoot"
24
+ PROCESSINGSTEPS_ROOT = "ProcessingStepsRoot"
25
+ INFO = "Info"
26
+ ADMIN = "Admin"
27
+
28
+
29
+ class EntryPointHco(Hco[EntryPointEntity]):
30
+ self_link: EntryPointLink
31
+ job_root_link: JobsRootLink
32
+ work_data_root_link: WorkDataRootLink
33
+ processing_step_root_link: ProcessingStepsRootLink
34
+ info_link: InfoLink
35
+
36
+ admin_link: LinkHco | None
37
+
38
+ @classmethod
39
+ def from_entity(cls, entity: EntryPointEntity, client: httpx.Client) -> Self:
40
+ instance = cls(client, entity)
41
+ Hco.check_classes(instance._entity.class_, ["EntryPoint"])
42
+
43
+ instance.self_link = EntryPointLink.from_entity(
44
+ instance._client, instance._entity, Relations.SELF)
45
+ instance.info_link = InfoLink.from_entity(
46
+ instance._client, instance._entity, EntrypointRelations.INFO)
47
+ instance.job_root_link = JobsRootLink.from_entity(
48
+ instance._client, instance._entity, EntrypointRelations.JOBS_ROOT)
49
+ instance.work_data_root_link = WorkDataRootLink.from_entity(
50
+ instance._client, instance._entity, EntrypointRelations.WORKDATA_ROOT)
51
+ instance.processing_step_root_link = ProcessingStepsRootLink.from_entity(
52
+ instance._client, instance._entity, EntrypointRelations.PROCESSINGSTEPS_ROOT)
53
+
54
+ instance.admin_link = LinkHco.from_entity_optional(
55
+ instance._client, instance._entity, EntrypointRelations.ADMIN)
56
+
57
+ return instance
@@ -0,0 +1,42 @@
1
+ from typing import Self
2
+
3
+ import httpx
4
+
5
+ from hypermedia_client.core.hco.hco_base import Hco, Property
6
+ from hypermedia_client.core.hco.link_hco import LinkHco
7
+ from hypermedia_client.job_management.hcos.user_hco import UserHco
8
+ from hypermedia_client.job_management.known_relations import Relations
9
+ from hypermedia_client.job_management.model.sirenentities import InfoEntity, UserEntity
10
+
11
+
12
+ class InfoLink(LinkHco):
13
+ def navigate(self) -> "InfoHco":
14
+ return InfoHco.from_entity(self._navigate_internal(InfoEntity), self._client)
15
+
16
+
17
+ class InfoHco(Hco[InfoEntity]):
18
+ api_version: str = Property()
19
+ build_version: str = Property()
20
+ current_user: UserHco
21
+ used_storage_in_bytes: int = Property()
22
+
23
+ self_link: InfoLink
24
+
25
+ @classmethod
26
+ def from_entity(cls, entity: InfoEntity, client: httpx.Client) -> Self:
27
+ instance = cls(client, entity)
28
+
29
+ Hco.check_classes(instance._entity.class_, ["Info"])
30
+
31
+ instance.self_link = InfoLink.from_entity(
32
+ instance._client, instance._entity, Relations.SELF
33
+ )
34
+
35
+ instance._extract_current_user()
36
+
37
+ return instance
38
+
39
+ def _extract_current_user(self):
40
+ user_entity = self._entity.find_first_entity_with_relation(
41
+ Relations.CURRENT_USER, UserEntity)
42
+ self.current_user = UserHco.from_entity(user_entity, self._client)
@@ -0,0 +1,82 @@
1
+ from typing import Self
2
+
3
+ import httpx
4
+
5
+ from hypermedia_client.core import Link, MediaTypes
6
+ from hypermedia_client.core.hco.action_hco import ActionHco
7
+ from hypermedia_client.core.hco.action_with_parameters_hco import ActionWithParametersHco
8
+ from hypermedia_client.core.hco.hco_base import Hco, Property
9
+ from hypermedia_client.core.hco.link_hco import LinkHco
10
+ from hypermedia_client.core.hco.upload_action_hco import UploadAction, UploadParameters
11
+ from hypermedia_client.job_management.hcos.workdata_hco import WorkDataLink, WorkDataHco
12
+ from hypermedia_client.job_management.known_relations import Relations
13
+ from hypermedia_client.job_management.model.open_api_generated import SelectWorkDataForDataSlotParameters, \
14
+ SelectWorkDataCollectionForDataSlotParameters
15
+ from hypermedia_client.job_management.model.sirenentities import InputDataSlotEntity, WorkDataEntity
16
+
17
+
18
+ class InputDataSlotLink(LinkHco):
19
+ def navigate(self) -> 'InputDataSlotHco':
20
+ return InputDataSlotHco.from_entity(self._navigate_internal(InputDataSlotEntity), self._client)
21
+
22
+
23
+ class InputDataSlotSelectWorkDataAction(ActionWithParametersHco[SelectWorkDataForDataSlotParameters]):
24
+ def execute(self, parameters: SelectWorkDataForDataSlotParameters):
25
+ self._execute(parameters)
26
+
27
+
28
+ class InputDataSlotSelectWorkDataCollectionAction(ActionWithParametersHco[SelectWorkDataCollectionForDataSlotParameters]):
29
+ def execute(self, parameters: SelectWorkDataCollectionForDataSlotParameters):
30
+ self._execute(parameters)
31
+
32
+
33
+ class InputDataSlotUploadWorkDataAction(UploadAction):
34
+ def execute(self, parameters: UploadParameters) -> WorkDataLink:
35
+ url = self._upload(parameters)
36
+ link = Link.from_url(url, [str(Relations.CREATED_RESSOURCE)], "Uploaded workdata", MediaTypes.SIREN)
37
+ return WorkDataLink.from_link(self._client, link)
38
+
39
+
40
+ class InputDataSlotClearDataAction(ActionHco):
41
+ def execute(self):
42
+ self._execute_internal()
43
+
44
+
45
+ class InputDataSlotHco(Hco[InputDataSlotEntity]):
46
+ is_configured: bool | None = Property()
47
+ title: str | None = Property()
48
+ description: str | None = Property()
49
+ media_type: str | None = Property()
50
+ selected_workdatas: list[WorkDataHco] | None
51
+
52
+ select_workdata_action: InputDataSlotSelectWorkDataAction | None
53
+ select_workdata_collection_action: InputDataSlotSelectWorkDataCollectionAction | None
54
+ clear_workdata_action: InputDataSlotClearDataAction | None
55
+
56
+ @classmethod
57
+ def from_entity(cls, entity: InputDataSlotEntity, client: httpx.Client) -> Self:
58
+ instance = cls(client, entity)
59
+ Hco.check_classes(instance._entity.class_, ["InputDataSlot"])
60
+
61
+ # actions
62
+ instance.select_workdata_action = InputDataSlotSelectWorkDataAction.from_entity_optional(
63
+ client, instance._entity, "SelectWorkData")
64
+ instance.select_workdata_collection_action = InputDataSlotSelectWorkDataCollectionAction.from_entity_optional(
65
+ client, instance._entity, "SelectWorkDataCollection")
66
+ instance.clear_workdata_action = InputDataSlotClearDataAction.from_entity_optional(
67
+ client, instance._entity, "Clear")
68
+
69
+ instance._extract_workdata()
70
+
71
+ return instance
72
+
73
+ def _extract_workdata(self):
74
+ self.selected_workdatas = []
75
+
76
+ workdatas: list[WorkDataEntity] = self._entity.find_all_entities_with_relation(Relations.SELECTED,
77
+ WorkDataEntity)
78
+ if not workdatas:
79
+ return
80
+
81
+ self.selected_workdatas = list[WorkDataHco](
82
+ WorkDataHco.from_entity(workdata, self._client) for workdata in workdatas)