pinexq-client 0.3.0.2024.607.9__py3-none-any.whl → 0.3.0.2024.620.1__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.
- pinexq_client/core/__init__.py +8 -0
- pinexq_client/core/base_relations.py +8 -0
- pinexq_client/core/enterapi.py +17 -0
- pinexq_client/core/exceptions.py +2 -0
- pinexq_client/core/hco/__init__.py +0 -0
- pinexq_client/core/hco/action_hco.py +70 -0
- pinexq_client/core/hco/action_with_parameters_hco.py +86 -0
- pinexq_client/core/hco/download_link_hco.py +37 -0
- pinexq_client/core/hco/hco_base.py +91 -0
- pinexq_client/core/hco/link_hco.py +57 -0
- pinexq_client/core/hco/upload_action_hco.py +113 -0
- pinexq_client/core/http_headers.py +9 -0
- pinexq_client/core/media_types.py +24 -0
- pinexq_client/core/model/__init__.py +0 -0
- pinexq_client/core/model/error.py +9 -0
- pinexq_client/core/model/sirenmodels.py +155 -0
- pinexq_client/core/polling.py +37 -0
- pinexq_client/core/sirenaccess.py +173 -0
- pinexq_client/job_management/__init__.py +6 -0
- pinexq_client/job_management/enterjma.py +42 -0
- pinexq_client/job_management/hcos/__init__.py +12 -0
- pinexq_client/job_management/hcos/entrypoint_hco.py +57 -0
- pinexq_client/job_management/hcos/info_hco.py +42 -0
- pinexq_client/job_management/hcos/input_dataslot_hco.py +82 -0
- pinexq_client/job_management/hcos/job_hco.py +182 -0
- pinexq_client/job_management/hcos/job_query_result_hco.py +64 -0
- pinexq_client/job_management/hcos/job_used_tags_hco.py +30 -0
- pinexq_client/job_management/hcos/jobsroot_hco.py +80 -0
- pinexq_client/job_management/hcos/output_dataslot_hco.py +44 -0
- pinexq_client/job_management/hcos/processing_step_hco.py +114 -0
- pinexq_client/job_management/hcos/processing_step_used_tags_hco.py +30 -0
- pinexq_client/job_management/hcos/processingstep_query_result_hco.py +69 -0
- pinexq_client/job_management/hcos/processingsteproot_hco.py +72 -0
- pinexq_client/job_management/hcos/user_hco.py +37 -0
- pinexq_client/job_management/hcos/workdata_hco.py +127 -0
- pinexq_client/job_management/hcos/workdata_query_result_hco.py +68 -0
- pinexq_client/job_management/hcos/workdata_used_tags_query_result_hco.py +30 -0
- pinexq_client/job_management/hcos/workdataroot_hco.py +84 -0
- pinexq_client/job_management/known_relations.py +29 -0
- pinexq_client/job_management/model/__init__.py +1 -0
- pinexq_client/job_management/model/open_api_generated.py +913 -0
- pinexq_client/job_management/model/sirenentities.py +112 -0
- pinexq_client/job_management/tool/__init__.py +3 -0
- pinexq_client/job_management/tool/job.py +520 -0
- pinexq_client/job_management/tool/processing_step.py +182 -0
- pinexq_client/job_management/tool/workdata.py +148 -0
- {pinexq_client-0.3.0.2024.607.9.dist-info → pinexq_client-0.3.0.2024.620.1.dist-info}/METADATA +2 -2
- pinexq_client-0.3.0.2024.620.1.dist-info/RECORD +50 -0
- {pinexq_client-0.3.0.2024.607.9.dist-info → pinexq_client-0.3.0.2024.620.1.dist-info}/WHEEL +1 -1
- pinexq_client-0.3.0.2024.607.9.dist-info/RECORD +0 -4
- {pinexq_client-0.3.0.2024.607.9.dist-info → pinexq_client-0.3.0.2024.620.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from typing import TypeVar, Type
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
|
|
5
|
+
from pinexq_client.core import Entity
|
|
6
|
+
from pinexq_client.core.hco.hco_base import Hco
|
|
7
|
+
|
|
8
|
+
THco = TypeVar('THco', bound=Hco)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def enter_api(client: httpx.Client, entrypoint_hco_type: Type[THco], entrypoint_entity_type: Type[Entity] = Entity,
|
|
12
|
+
entrypoint: str = "api/EntryPoint") -> THco:
|
|
13
|
+
entry_point_response = client.get(url=entrypoint)
|
|
14
|
+
entry_point_response.raise_for_status()
|
|
15
|
+
entrypoint_entity = entrypoint_entity_type.model_validate_json(entry_point_response.read())
|
|
16
|
+
|
|
17
|
+
return entrypoint_hco_type.from_entity(entrypoint_entity, client)
|
|
File without changes
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from typing import TypeVar, Self
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
from httpx import Response
|
|
5
|
+
from httpx import URL
|
|
6
|
+
|
|
7
|
+
from pinexq_client.core import SirenException, Entity, Action, ProblemDetails, execute_action
|
|
8
|
+
from pinexq_client.core.hco.hco_base import ClientContainer
|
|
9
|
+
|
|
10
|
+
TEntity = TypeVar('TEntity', bound=Entity)
|
|
11
|
+
THcoEntity = TypeVar('THcoEntity', bound=Entity)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ActionHco(ClientContainer):
|
|
15
|
+
_client: httpx.Client
|
|
16
|
+
_action: Action
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def from_action_optional(cls, client: httpx.Client, action: Action | None) -> Self | None:
|
|
20
|
+
if action is None:
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
if action.has_parameters():
|
|
24
|
+
raise SirenException(f"Error while mapping action: expected action no parameters but got some")
|
|
25
|
+
|
|
26
|
+
instance = cls(client)
|
|
27
|
+
instance._action = action
|
|
28
|
+
return instance
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def from_entity_optional(cls, client: httpx.Client, entity: Entity, name: str) -> Self | None:
|
|
32
|
+
if entity is None:
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
action = entity.find_first_action_with_name(name)
|
|
36
|
+
return cls.from_action_optional(client, action)
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def from_action(cls, client: httpx.Client, action: Action) -> Self:
|
|
40
|
+
action = cls.from_action_optional(client, action)
|
|
41
|
+
if action is None:
|
|
42
|
+
raise SirenException(
|
|
43
|
+
f"Error while mapping mandatory action: does not exist")
|
|
44
|
+
return action
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def from_entity(cls, client: httpx.Client, entity: Entity, name: str) -> Self:
|
|
48
|
+
result = cls.from_entity_optional(client, entity, name)
|
|
49
|
+
if result is None:
|
|
50
|
+
raise SirenException(
|
|
51
|
+
f"Error while mapping mandatory action {name}: does not exist")
|
|
52
|
+
return result
|
|
53
|
+
|
|
54
|
+
def _execute_internal(self) -> None | URL:
|
|
55
|
+
response = execute_action(self._client, self._action)
|
|
56
|
+
|
|
57
|
+
if isinstance(response, ProblemDetails):
|
|
58
|
+
raise SirenException(
|
|
59
|
+
f"Error while executing action: {response}")
|
|
60
|
+
if isinstance(response, Response):
|
|
61
|
+
raise SirenException(
|
|
62
|
+
f"Error while executing action, unexpected response: {response}")
|
|
63
|
+
return response
|
|
64
|
+
|
|
65
|
+
def __repr__(self):
|
|
66
|
+
return f"<{self.__class__.__name__}: '{self._action.name}'>"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from typing import TypeVar, Type, Self, Generic
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
from httpx import Response
|
|
5
|
+
from httpx import URL
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
from pinexq_client.core import SirenException, Entity, Action, ProblemDetails, execute_action
|
|
9
|
+
from pinexq_client.core.hco.hco_base import ClientContainer
|
|
10
|
+
|
|
11
|
+
TParameters = TypeVar('TParameters', bound=BaseModel)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ActionWithParametersHco(ClientContainer, Generic[TParameters]):
|
|
15
|
+
_client: httpx.Client
|
|
16
|
+
_action: Action
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def from_action_optional(cls, client: httpx.Client, action: Action | None) -> Self | None:
|
|
20
|
+
if action is None:
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
if not action.has_parameters():
|
|
24
|
+
raise SirenException(
|
|
25
|
+
f"Error while mapping action: expected action with parameters but got none")
|
|
26
|
+
|
|
27
|
+
instance = cls(client)
|
|
28
|
+
instance._action = action
|
|
29
|
+
return instance
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def from_entity_optional(cls, client: httpx.Client, entity: Entity, name: str) -> Self | None:
|
|
33
|
+
if entity is None:
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
action = entity.find_first_action_with_name(name)
|
|
37
|
+
return cls.from_action_optional(client, action)
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def from_action(cls, client: httpx.Client, action: Action) -> Self:
|
|
41
|
+
action = cls.from_action_optional(client, action)
|
|
42
|
+
if action is None:
|
|
43
|
+
raise SirenException(
|
|
44
|
+
f"Error while mapping mandatory action: action does not exist")
|
|
45
|
+
return action
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def from_entity(cls, client: httpx.Client, entity: Entity, name: str) -> Self:
|
|
49
|
+
result = cls.from_entity_optional(client, entity, name)
|
|
50
|
+
if result is None:
|
|
51
|
+
raise SirenException(
|
|
52
|
+
f"Error while mapping mandatory action {name}: action does not exist")
|
|
53
|
+
return result
|
|
54
|
+
|
|
55
|
+
def _execute_internal(self, parameters: BaseModel) -> None | URL:
|
|
56
|
+
if parameters is None:
|
|
57
|
+
raise SirenException(f"Error while executing action: action requires parameters")
|
|
58
|
+
|
|
59
|
+
response = execute_action(self._client, self._action, parameters)
|
|
60
|
+
|
|
61
|
+
if isinstance(response, ProblemDetails):
|
|
62
|
+
raise SirenException(
|
|
63
|
+
f"Error while executing action: {response}")
|
|
64
|
+
if isinstance(response, Response):
|
|
65
|
+
raise SirenException(
|
|
66
|
+
f"Error while executing action, unexpected response: {response}")
|
|
67
|
+
return response
|
|
68
|
+
|
|
69
|
+
def _execute(self, parameters: TParameters):
|
|
70
|
+
result = self._execute_internal(parameters)
|
|
71
|
+
if result is not None:
|
|
72
|
+
raise SirenException("Action did respond with unexpected URL")
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
def _execute_returns_url(self, parameters: TParameters) -> URL:
|
|
76
|
+
result = self._execute_internal(parameters)
|
|
77
|
+
if result is None:
|
|
78
|
+
raise SirenException("Action did not respond with URL")
|
|
79
|
+
return result
|
|
80
|
+
|
|
81
|
+
def _get_default_parameters(self, parameter_type: Type[TParameters],
|
|
82
|
+
default_if_none: TParameters) -> TParameters:
|
|
83
|
+
return self._action.get_default_parameters(parameter_type, default_if_none)
|
|
84
|
+
|
|
85
|
+
def __repr__(self):
|
|
86
|
+
return f"<{self.__class__.__name__}: '{self._action.name}'>"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from io import BytesIO
|
|
2
|
+
from typing import Self
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
from httpx import Response
|
|
6
|
+
|
|
7
|
+
from pinexq_client.core import Link, Entity, get_resource, SirenException
|
|
8
|
+
from pinexq_client.core.hco.link_hco import LinkHco
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DownloadLinkHco(LinkHco):
|
|
12
|
+
|
|
13
|
+
@classmethod
|
|
14
|
+
def from_link_optional(cls, client: httpx.Client, link: Link | None) -> Self | None:
|
|
15
|
+
return super(DownloadLinkHco, cls).from_link_optional(client, link)
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def from_entity_optional(cls, client: httpx.Client, entity: Entity, link_relation: str) -> Self | None:
|
|
19
|
+
return super(DownloadLinkHco, cls).from_entity_optional(client, entity, link_relation)
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def from_link(cls, client: httpx.Client, link: Link) -> Self:
|
|
23
|
+
return super(DownloadLinkHco, cls).from_link(client, link)
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def from_entity(cls, client: httpx.Client, entity: Entity, link_relation: str) -> Self:
|
|
27
|
+
return super(DownloadLinkHco, cls).from_entity(client, entity, link_relation)
|
|
28
|
+
|
|
29
|
+
def download(self) -> bytes:
|
|
30
|
+
response: Response = get_resource(self._client, self._link.href, self._link.type)
|
|
31
|
+
if not isinstance(response, Response):
|
|
32
|
+
raise SirenException(
|
|
33
|
+
f"Error while downloading resource: did not get response type")
|
|
34
|
+
|
|
35
|
+
return response.content
|
|
36
|
+
|
|
37
|
+
# TODO: download for large files
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import List, TypeVar, Generic, Callable, Any
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
from httpx import URL
|
|
6
|
+
|
|
7
|
+
from pinexq_client.core import SirenException, Entity, BaseRelations, Link
|
|
8
|
+
|
|
9
|
+
TEntity = TypeVar('TEntity', bound=Entity)
|
|
10
|
+
THcoEntity = TypeVar('THcoEntity', bound=Entity)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ClientContainer:
|
|
14
|
+
_client: httpx.Client
|
|
15
|
+
|
|
16
|
+
def __init__(self, client: httpx.Client):
|
|
17
|
+
self._client = client
|
|
18
|
+
|
|
19
|
+
def __repr__(self):
|
|
20
|
+
variables = [f"{k}: {repr(v)}" for k, v in vars(self).items() if not k.startswith('_')]
|
|
21
|
+
var_repr = f" ({', '.join(variables)})"
|
|
22
|
+
return f"<{self.__class__.__name__}{var_repr if variables else ''}>"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class Property:
|
|
27
|
+
"""
|
|
28
|
+
Sentinel object marking properties in an HCO object to be copied from the entity.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
name: Name of the property in the entity. If not provided, it's assumed to be
|
|
32
|
+
the same as the property name in the Hco. [optional]
|
|
33
|
+
converter: A callable that will be applied to the value in the entity before
|
|
34
|
+
being assigned to the property in the Hco. [optional]
|
|
35
|
+
"""
|
|
36
|
+
name: str | None = None
|
|
37
|
+
converter: Callable[[Any], Any] | None = None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Hco(ClientContainer, Generic[THcoEntity]):
|
|
41
|
+
_entity: THcoEntity
|
|
42
|
+
|
|
43
|
+
def __init__(self, client: httpx.Client, entity: THcoEntity):
|
|
44
|
+
super().__init__(client)
|
|
45
|
+
self._entity = entity
|
|
46
|
+
self._set_properties()
|
|
47
|
+
|
|
48
|
+
def __hash__(self):
|
|
49
|
+
# TODO: write a unit test ;)
|
|
50
|
+
candidate_self_link: Link | None = self._entity.find_first_link_with_relation(BaseRelations.SELF)
|
|
51
|
+
if candidate_self_link is not None:
|
|
52
|
+
return hash(URL(candidate_self_link.href).path)
|
|
53
|
+
else:
|
|
54
|
+
return super().__hash__()
|
|
55
|
+
|
|
56
|
+
def __eq__(self, other):
|
|
57
|
+
# TODO: write a unit test ;)
|
|
58
|
+
if isinstance(other, Hco):
|
|
59
|
+
return hash(self) == hash(other)
|
|
60
|
+
else:
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def check_classes(existing_classes: List[str], expected_classes: List[str]):
|
|
65
|
+
for expected_class in expected_classes:
|
|
66
|
+
if expected_class not in existing_classes:
|
|
67
|
+
raise SirenException(
|
|
68
|
+
f"Error while mapping entity:expected hco class {expected_class} is not a class of generic entity "
|
|
69
|
+
f"with classes {existing_classes}")
|
|
70
|
+
|
|
71
|
+
def _set_properties(self):
|
|
72
|
+
"""Initializes Hco properties from the entity.
|
|
73
|
+
|
|
74
|
+
Iterates over all properties defined in the class annotation. If one is initialized
|
|
75
|
+
with `Property()` the variable gets set from a property in self._entity.properties
|
|
76
|
+
with the same name.
|
|
77
|
+
"""
|
|
78
|
+
for var_name, var_type in self.__annotations__.items():
|
|
79
|
+
# Get the initialized object. The annotation contain only the annotated type.
|
|
80
|
+
hco_property = getattr(self, var_name, None)
|
|
81
|
+
# Skip everything that is not initialized as `Property()`
|
|
82
|
+
if not (hco_property and isinstance(hco_property, Property)):
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
if not hco_property.name:
|
|
86
|
+
hco_property.name = var_name
|
|
87
|
+
property_value = getattr(self._entity.properties, var_name)
|
|
88
|
+
if hco_property.converter:
|
|
89
|
+
property_value = hco_property.converter(property_value)
|
|
90
|
+
setattr(self, var_name, property_value)
|
|
91
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from typing import Self, Type
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
from httpx import URL
|
|
5
|
+
|
|
6
|
+
from pinexq_client.core import Link, SirenException, Entity, navigate, ensure_siren_response
|
|
7
|
+
from pinexq_client.core.hco.hco_base import ClientContainer, TEntity
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LinkHco(ClientContainer):
|
|
11
|
+
_client: httpx.Client
|
|
12
|
+
_link: Link
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def from_link_optional(cls, client: httpx.Client, link: Link | None) -> Self | None:
|
|
16
|
+
if link is None:
|
|
17
|
+
return None
|
|
18
|
+
|
|
19
|
+
instance = cls(client)
|
|
20
|
+
instance._link = link
|
|
21
|
+
return instance
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def from_entity_optional(cls, client: httpx.Client, entity: Entity, link_relation: str) -> Self | None:
|
|
25
|
+
if entity is None:
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
link = entity.find_first_link_with_relation(link_relation)
|
|
29
|
+
return cls.from_link_optional(client, link)
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def from_link(cls, client: httpx.Client, link: Link) -> Self:
|
|
33
|
+
result = cls.from_link_optional(client, link)
|
|
34
|
+
if result is None:
|
|
35
|
+
raise SirenException(f"Error while mapping mandatory link: link is None")
|
|
36
|
+
|
|
37
|
+
return result
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def from_entity(cls, client: httpx.Client, entity: Entity, link_relation: str) -> Self:
|
|
41
|
+
result = cls.from_entity_optional(client, entity, link_relation)
|
|
42
|
+
if result is None:
|
|
43
|
+
raise SirenException(
|
|
44
|
+
f"Error while mapping mandatory link: entity contains no link with relation {link_relation}")
|
|
45
|
+
|
|
46
|
+
return result
|
|
47
|
+
|
|
48
|
+
def _navigate_internal(self, parse_type: Type[TEntity] = Entity) -> TEntity:
|
|
49
|
+
response = navigate(self._client, self._link, parse_type)
|
|
50
|
+
return ensure_siren_response(response)
|
|
51
|
+
|
|
52
|
+
def get_url(self) -> URL:
|
|
53
|
+
return URL(self._link.href)
|
|
54
|
+
|
|
55
|
+
def __repr__(self):
|
|
56
|
+
rel_names = ', '.join((f"'{r}'" for r in self._link.rel))
|
|
57
|
+
return f"<{self.__class__.__name__}: {rel_names}>"
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from io import IOBase
|
|
2
|
+
from typing import BinaryIO, Iterable, AsyncIterable, Any, Self, List
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
from httpx import URL
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
7
|
+
|
|
8
|
+
from pinexq_client.core import upload_file, upload_binary, upload_json, SirenException, MediaTypes, Action, Entity, \
|
|
9
|
+
SirenClasses
|
|
10
|
+
from pinexq_client.core.hco.action_with_parameters_hco import ActionWithParametersHco
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class UploadParameters(BaseModel):
|
|
14
|
+
# arbitrary_types_allowed
|
|
15
|
+
model_config = ConfigDict(extra='forbid', arbitrary_types_allowed=True)
|
|
16
|
+
filename: str
|
|
17
|
+
mediatype: str = MediaTypes.OCTET_STREAM
|
|
18
|
+
|
|
19
|
+
file: IOBase | None = None
|
|
20
|
+
binary: str | bytes | Iterable[bytes] | AsyncIterable[bytes] | None = None
|
|
21
|
+
json_: Any | None = Field(None, alias='json')
|
|
22
|
+
|
|
23
|
+
@model_validator(mode='after')
|
|
24
|
+
def check_only_one_input_method(self) -> Self:
|
|
25
|
+
list_of_vars = [self.file, self.binary, self.json_]
|
|
26
|
+
initialized_vars_count = [v is not None for v in list_of_vars].count(True)
|
|
27
|
+
|
|
28
|
+
if initialized_vars_count == 0:
|
|
29
|
+
raise ValueError(f'Please provide a upload content: file, json, binary')
|
|
30
|
+
if initialized_vars_count > 1:
|
|
31
|
+
raise ValueError(f'Please provide only one upload content: file, json, binary')
|
|
32
|
+
|
|
33
|
+
return self
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class UploadAction(ActionWithParametersHco[UploadParameters]):
|
|
37
|
+
accept: str | None = None
|
|
38
|
+
max_filesize_bytes: int | None = None
|
|
39
|
+
allow_multiple: bool | None = None
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def from_action_optional(cls, client: httpx.Client, action: Action | None) -> Self | None:
|
|
43
|
+
instance = super(UploadAction, cls).from_action_optional(client, action)
|
|
44
|
+
|
|
45
|
+
if instance:
|
|
46
|
+
cls.validate_file_upload_field(instance)
|
|
47
|
+
cls.assign_upload_constraints(instance)
|
|
48
|
+
return instance
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def from_entity_optional(cls, client: httpx.Client, entity: Entity, name: str) -> Self | None:
|
|
52
|
+
instance = super(UploadAction, cls).from_entity_optional(client, entity, name)
|
|
53
|
+
|
|
54
|
+
if instance:
|
|
55
|
+
cls.validate_file_upload_field(instance)
|
|
56
|
+
cls.assign_upload_constraints(instance)
|
|
57
|
+
return instance
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def from_action(cls, client: httpx.Client, action: Action) -> Self:
|
|
61
|
+
instance = super(UploadAction, cls).from_action(client, action)
|
|
62
|
+
cls.validate_file_upload_field(instance)
|
|
63
|
+
cls.assign_upload_constraints(instance)
|
|
64
|
+
return instance
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def from_entity(cls, client: httpx.Client, entity: Entity, name: str) -> Self:
|
|
68
|
+
instance = super(UploadAction, cls).from_entity(client, entity, name)
|
|
69
|
+
cls.validate_file_upload_field(instance)
|
|
70
|
+
cls.assign_upload_constraints(instance)
|
|
71
|
+
return instance
|
|
72
|
+
|
|
73
|
+
def assign_upload_constraints(self):
|
|
74
|
+
upload_fields = self._action.fields[0]
|
|
75
|
+
|
|
76
|
+
self.accept = upload_fields.accept
|
|
77
|
+
self.max_filesize_bytes = upload_fields.maxFileSizeBytes
|
|
78
|
+
self.allow_multiple = upload_fields.allowMultiple
|
|
79
|
+
|
|
80
|
+
def validate_file_upload_field(self):
|
|
81
|
+
action = self._action
|
|
82
|
+
if SirenClasses.FileUploadAction not in action.class_:
|
|
83
|
+
raise SirenException(
|
|
84
|
+
f"Upload action does not have expected class: {str(SirenClasses.FileUploadAction)}. Got: {action.class_}")
|
|
85
|
+
|
|
86
|
+
if action.type != MediaTypes.MULTIPART_FORM_DATA.value:
|
|
87
|
+
raise SirenException(
|
|
88
|
+
f"Upload action does not have expected type: {str(MediaTypes.MULTIPART_FORM_DATA)}. Got: {action.type}")
|
|
89
|
+
|
|
90
|
+
upload_fields = self._action.fields[0]
|
|
91
|
+
|
|
92
|
+
if upload_fields.type != "file":
|
|
93
|
+
raise SirenException(
|
|
94
|
+
f"Upload action does not have expected field type: 'file'. Got: {upload_fields.type}")
|
|
95
|
+
|
|
96
|
+
def _upload(self, parameters: UploadParameters) -> URL:
|
|
97
|
+
|
|
98
|
+
result = None
|
|
99
|
+
if parameters.file is not None:
|
|
100
|
+
result = upload_file(self._client, self._action, parameters.file, parameters.filename, parameters.mediatype)
|
|
101
|
+
|
|
102
|
+
elif parameters.binary is not None:
|
|
103
|
+
result = upload_binary(self._client, self._action, parameters.binary, parameters.filename,
|
|
104
|
+
parameters.mediatype)
|
|
105
|
+
|
|
106
|
+
elif parameters.json_ is not None:
|
|
107
|
+
result = upload_json(self._client, self._action, parameters.json_, parameters.filename)
|
|
108
|
+
else:
|
|
109
|
+
raise SirenException("Did not execute upload, none selected")
|
|
110
|
+
|
|
111
|
+
if not isinstance(result, URL):
|
|
112
|
+
raise SirenException("Upload did not respond with location")
|
|
113
|
+
return result
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MediaTypes(StrEnum):
|
|
5
|
+
APPLICATION_JSON = "application/json"
|
|
6
|
+
SIREN = "application/vnd.siren+json"
|
|
7
|
+
PROBLEM_DETAILS = "application/problem+json"
|
|
8
|
+
MULTIPART_FORM_DATA = "multipart/form-data"
|
|
9
|
+
OCTET_STREAM = "application/octet-stream"
|
|
10
|
+
|
|
11
|
+
XML = "application/xml"
|
|
12
|
+
ZIP = "application/zip"
|
|
13
|
+
PDF = "application/pdf"
|
|
14
|
+
TEXT = "text/plain"
|
|
15
|
+
HTML = "text/html"
|
|
16
|
+
CSV = "text/csv"
|
|
17
|
+
SVG = "image/svg+xml"
|
|
18
|
+
PNG = "image/png"
|
|
19
|
+
JPEG = "image/jpeg"
|
|
20
|
+
BMP = "image/bmp"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SirenClasses(StrEnum):
|
|
24
|
+
FileUploadAction = "FileUploadAction"
|
|
File without changes
|