wizata-dsapi 2.2.0.dev2__tar.gz → 2.2.0.dev5__tar.gz
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.
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/PKG-INFO +1 -1
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/__init__.py +1 -0
- wizata_dsapi-2.2.0.dev5/wizata_dsapi/product.py +210 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/request.py +15 -1
- wizata_dsapi-2.2.0.dev5/wizata_dsapi/version.py +1 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/wizata_dsapi_client.py +137 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi.egg-info/PKG-INFO +1 -1
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi.egg-info/SOURCES.txt +1 -0
- wizata_dsapi-2.2.0.dev2/wizata_dsapi/version.py +0 -1
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/LICENSE.txt +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/README.rst +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/setup.cfg +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/setup.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/api_config.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/api_dto.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/api_interface.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/bucket.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/business_label.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/connection.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/connection_types.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/context.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/dashboard.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/dataframe_toolkit.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/datapoint.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/datastore.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/deployment.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/ds_dataframe.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/dsapi_json_encoder.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/edge_config.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/edge_device.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/edge_module.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/evaluation.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/execution.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/execution_log.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/experiment.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/graylog_log.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/group_system.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/ilogger.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/insight.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/mlmodel.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/mobile_asset.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/model_toolkit.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/models/__init__.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/models/common.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/notification.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/paged_query_result.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/pipeline.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/pipeline_image.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/plot.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/plots/__init__.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/plots/common.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/plots/theme.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/script.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/scripts/__init__.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/scripts/common.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/search.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/solution_component.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/streamlit_utils.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/template.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/template_config.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/trigger.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/twin.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/twinregistration.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/user.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/wizard_function.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/wizard_request.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi/words.py +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi.egg-info/dependency_links.txt +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi.egg-info/requires.txt +0 -0
- {wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi.egg-info/top_level.txt +0 -0
|
@@ -65,6 +65,7 @@ from .connection import Connection, ConnectionMapping, ConnectionDirection, Tran
|
|
|
65
65
|
EdgeDeviceKey, EdgeDeviceKeyStatus
|
|
66
66
|
from .connection_types import ConnectionType, CONNECTION_TYPES, TRANSFORM_SCHEMAS, get_connection_type, \
|
|
67
67
|
list_connection_types
|
|
68
|
+
from .product import ProductKind, Product, ProductComponent, BUILTIN_PRODUCT_KINDS, is_valid_product_key
|
|
68
69
|
from .dashboard import Dashboard, DashboardType
|
|
69
70
|
from .mobile_asset import MobileAssetTile, MobileAssetTileType, MobileAssetConfig
|
|
70
71
|
from .graylog_log import GraylogLog
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Production Planner product definitions (DEV-6070, epic DEV-6044).
|
|
3
|
+
|
|
4
|
+
ProductKind — what role a product plays: raw material, container, finished product,
|
|
5
|
+
or a kind the customer adds. The three built-in kinds (``BUILTIN_PRODUCT_KINDS``)
|
|
6
|
+
are seeded on every tenant and cannot be deleted.
|
|
7
|
+
|
|
8
|
+
Product — something the plant consumes or makes, identified by a Wizata key
|
|
9
|
+
(e.g. ``can_33_coke``). ``category`` is a backend category id (Volume, Count, ...):
|
|
10
|
+
it says how a quantity of that product is expressed.
|
|
11
|
+
|
|
12
|
+
ProductComponent — one line of a product's bill of materials: ``product`` is made of
|
|
13
|
+
``quantity`` ``unit`` of ``composed_of``. The unit is a backend unit id and must
|
|
14
|
+
belong to the category of ``composed_of`` (33 ml of coke, 1 pcs of can).
|
|
15
|
+
``unit_symbol`` is filled on read only.
|
|
16
|
+
|
|
17
|
+
All of it is served under ``/dsapi/planner`` and gated by the Production Planner
|
|
18
|
+
add-on license (DEV-6069).
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import re
|
|
22
|
+
from datetime import datetime, timezone
|
|
23
|
+
from typing import Optional
|
|
24
|
+
|
|
25
|
+
#: key -> (name, description). Seeded by alembic 0019; editable, never deletable.
|
|
26
|
+
BUILTIN_PRODUCT_KINDS = {
|
|
27
|
+
"raw": ("Raw Material",
|
|
28
|
+
"A material bought or produced upstream and consumed to make other "
|
|
29
|
+
"products (e.g. syrup, water, aluminium)."),
|
|
30
|
+
"container": ("Containers",
|
|
31
|
+
"Packaging that holds or carries a product (e.g. can, bottle, box, pallet)."),
|
|
32
|
+
"finished": ("Finished Product",
|
|
33
|
+
"A product ready to ship or sell, made from raw materials and containers."),
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#: A Wizata key: starts with a letter or digit, then letters, digits, ``_ - .``.
|
|
37
|
+
PRODUCT_KEY_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_\-.]{0,63}$")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def is_valid_product_key(key) -> bool:
|
|
41
|
+
return isinstance(key, str) and PRODUCT_KEY_PATTERN.match(key) is not None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _ts_out(value):
|
|
45
|
+
if value is None:
|
|
46
|
+
return None
|
|
47
|
+
if value.tzinfo is None:
|
|
48
|
+
value = value.replace(tzinfo=timezone.utc)
|
|
49
|
+
return value.isoformat()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _ts_in(value):
|
|
53
|
+
if value is None or isinstance(value, datetime):
|
|
54
|
+
return value
|
|
55
|
+
return datetime.fromisoformat(str(value).replace("Z", "+00:00"))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _uuid_str(value):
|
|
59
|
+
"""Backend ids travel as strings, lower-cased so equal ids compare equal."""
|
|
60
|
+
if value is None or value == "":
|
|
61
|
+
return None
|
|
62
|
+
return str(value).lower()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ProductKind:
|
|
66
|
+
"""ProductKind entity stored in PostgreSQL ``"ProductKind"``. PK ``key``."""
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def route(cls):
|
|
70
|
+
return "planner/productkinds"
|
|
71
|
+
|
|
72
|
+
def __init__(self, key: str = None, name: str = None, description: str = None):
|
|
73
|
+
self.key: Optional[str] = key
|
|
74
|
+
self.name: Optional[str] = name
|
|
75
|
+
self.description: Optional[str] = description
|
|
76
|
+
self.created_at: Optional[datetime] = None
|
|
77
|
+
self.updated_at: Optional[datetime] = None
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def builtin(self) -> bool:
|
|
81
|
+
return self.key in BUILTIN_PRODUCT_KINDS
|
|
82
|
+
|
|
83
|
+
def to_dict(self) -> dict:
|
|
84
|
+
obj = {}
|
|
85
|
+
if self.key is not None:
|
|
86
|
+
obj["key"] = self.key
|
|
87
|
+
if self.name is not None:
|
|
88
|
+
obj["name"] = self.name
|
|
89
|
+
if self.description is not None:
|
|
90
|
+
obj["description"] = self.description
|
|
91
|
+
obj["builtin"] = self.builtin
|
|
92
|
+
if self.created_at is not None:
|
|
93
|
+
obj["createdAt"] = _ts_out(self.created_at)
|
|
94
|
+
if self.updated_at is not None:
|
|
95
|
+
obj["updatedAt"] = _ts_out(self.updated_at)
|
|
96
|
+
return obj
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def from_dict(cls, data: dict) -> "ProductKind":
|
|
100
|
+
obj = cls()
|
|
101
|
+
obj.key = data.get("key")
|
|
102
|
+
obj.name = data.get("name")
|
|
103
|
+
obj.description = data.get("description")
|
|
104
|
+
obj.created_at = _ts_in(data.get("createdAt") or data.get("created_at"))
|
|
105
|
+
obj.updated_at = _ts_in(data.get("updatedAt") or data.get("updated_at"))
|
|
106
|
+
return obj
|
|
107
|
+
|
|
108
|
+
def api_id(self) -> str:
|
|
109
|
+
return self.key
|
|
110
|
+
|
|
111
|
+
def __repr__(self) -> str:
|
|
112
|
+
return f"ProductKind(key={self.key!r}, name={self.name!r})"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class Product:
|
|
116
|
+
"""Product entity stored in PostgreSQL ``"Product"``. PK ``key``."""
|
|
117
|
+
|
|
118
|
+
@classmethod
|
|
119
|
+
def route(cls):
|
|
120
|
+
return "planner/products"
|
|
121
|
+
|
|
122
|
+
def __init__(self, key: str = None, name: str = None, category=None,
|
|
123
|
+
kind: str = None, description: str = None):
|
|
124
|
+
self.key: Optional[str] = key
|
|
125
|
+
self.name: Optional[str] = name
|
|
126
|
+
self.category: Optional[str] = _uuid_str(category)
|
|
127
|
+
self.kind: Optional[str] = kind
|
|
128
|
+
self.description: Optional[str] = description
|
|
129
|
+
self.created_at: Optional[datetime] = None
|
|
130
|
+
self.updated_at: Optional[datetime] = None
|
|
131
|
+
|
|
132
|
+
def to_dict(self) -> dict:
|
|
133
|
+
obj = {}
|
|
134
|
+
if self.key is not None:
|
|
135
|
+
obj["key"] = self.key
|
|
136
|
+
if self.name is not None:
|
|
137
|
+
obj["name"] = self.name
|
|
138
|
+
if self.category is not None:
|
|
139
|
+
obj["category"] = self.category
|
|
140
|
+
if self.kind is not None:
|
|
141
|
+
obj["kind"] = self.kind
|
|
142
|
+
if self.description is not None:
|
|
143
|
+
obj["description"] = self.description
|
|
144
|
+
if self.created_at is not None:
|
|
145
|
+
obj["createdAt"] = _ts_out(self.created_at)
|
|
146
|
+
if self.updated_at is not None:
|
|
147
|
+
obj["updatedAt"] = _ts_out(self.updated_at)
|
|
148
|
+
return obj
|
|
149
|
+
|
|
150
|
+
@classmethod
|
|
151
|
+
def from_dict(cls, data: dict) -> "Product":
|
|
152
|
+
obj = cls()
|
|
153
|
+
obj.key = data.get("key")
|
|
154
|
+
obj.name = data.get("name")
|
|
155
|
+
obj.category = _uuid_str(data.get("category"))
|
|
156
|
+
obj.kind = data.get("kind")
|
|
157
|
+
obj.description = data.get("description")
|
|
158
|
+
obj.created_at = _ts_in(data.get("createdAt") or data.get("created_at"))
|
|
159
|
+
obj.updated_at = _ts_in(data.get("updatedAt") or data.get("updated_at"))
|
|
160
|
+
return obj
|
|
161
|
+
|
|
162
|
+
def api_id(self) -> str:
|
|
163
|
+
return self.key
|
|
164
|
+
|
|
165
|
+
def __repr__(self) -> str:
|
|
166
|
+
return f"Product(key={self.key!r}, kind={self.kind!r}, category={self.category!r})"
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class ProductComponent:
|
|
170
|
+
"""
|
|
171
|
+
One bill-of-materials line, stored in PostgreSQL ``"ProductComponent"``.
|
|
172
|
+
PK (``product``, ``composed_of``).
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
def __init__(self, product: str = None, composed_of: str = None,
|
|
176
|
+
quantity: float = None, unit=None):
|
|
177
|
+
self.product: Optional[str] = product
|
|
178
|
+
self.composed_of: Optional[str] = composed_of
|
|
179
|
+
self.quantity: Optional[float] = quantity
|
|
180
|
+
self.unit: Optional[str] = _uuid_str(unit)
|
|
181
|
+
#: read-only, filled from the backend unit on read
|
|
182
|
+
self.unit_symbol: Optional[str] = None
|
|
183
|
+
|
|
184
|
+
def to_dict(self) -> dict:
|
|
185
|
+
obj = {}
|
|
186
|
+
if self.product is not None:
|
|
187
|
+
obj["product"] = self.product
|
|
188
|
+
if self.composed_of is not None:
|
|
189
|
+
obj["composedOf"] = self.composed_of
|
|
190
|
+
if self.quantity is not None:
|
|
191
|
+
obj["quantity"] = self.quantity
|
|
192
|
+
if self.unit is not None:
|
|
193
|
+
obj["unit"] = self.unit
|
|
194
|
+
if self.unit_symbol is not None:
|
|
195
|
+
obj["unitSymbol"] = self.unit_symbol
|
|
196
|
+
return obj
|
|
197
|
+
|
|
198
|
+
@classmethod
|
|
199
|
+
def from_dict(cls, data: dict) -> "ProductComponent":
|
|
200
|
+
obj = cls()
|
|
201
|
+
obj.product = data.get("product")
|
|
202
|
+
obj.composed_of = data.get("composedOf") or data.get("composed_of")
|
|
203
|
+
obj.quantity = data.get("quantity")
|
|
204
|
+
obj.unit = _uuid_str(data.get("unit"))
|
|
205
|
+
obj.unit_symbol = data.get("unitSymbol") or data.get("unit_symbol")
|
|
206
|
+
return obj
|
|
207
|
+
|
|
208
|
+
def __repr__(self) -> str:
|
|
209
|
+
return (f"ProductComponent({self.product!r} <- {self.quantity!r} {self.unit_symbol or self.unit!r} "
|
|
210
|
+
f"of {self.composed_of!r})")
|
|
@@ -644,6 +644,17 @@ class Request(ApiDto):
|
|
|
644
644
|
|
|
645
645
|
return query
|
|
646
646
|
|
|
647
|
+
@staticmethod
|
|
648
|
+
def _object(value, section: str) -> dict:
|
|
649
|
+
"""
|
|
650
|
+
A section of the body that must be a JSON object. Anything else is the caller's
|
|
651
|
+
mistake, refused as a ValueError naming it (the DS API answers 400) - it used to
|
|
652
|
+
reach ``.keys()`` and fail as an AttributeError, answered 500.
|
|
653
|
+
"""
|
|
654
|
+
if not isinstance(value, dict):
|
|
655
|
+
raise ValueError(f"'{section}' must be an object, got {type(value).__name__}: {value!r}"[:300])
|
|
656
|
+
return value
|
|
657
|
+
|
|
647
658
|
def from_json(self, json_data):
|
|
648
659
|
if "id" in json_data.keys():
|
|
649
660
|
self.request_id = uuid.UUID(json_data["id"])
|
|
@@ -654,6 +665,7 @@ class Request(ApiDto):
|
|
|
654
665
|
datapoints = []
|
|
655
666
|
if "equipments_list" in json_data.keys():
|
|
656
667
|
for equipment in json_data["equipments_list"]:
|
|
668
|
+
self._object(equipment, "equipments_list")
|
|
657
669
|
if "datapoints" not in equipment.keys():
|
|
658
670
|
raise KeyError("no 'datapoints' have been provided for equipment with id '" +
|
|
659
671
|
str(equipment["id"]) + "'")
|
|
@@ -674,6 +686,7 @@ class Request(ApiDto):
|
|
|
674
686
|
self._datapoints = []
|
|
675
687
|
|
|
676
688
|
if "timeframe" in json_data.keys():
|
|
689
|
+
self._object(json_data["timeframe"], "timeframe")
|
|
677
690
|
if "start" not in json_data["timeframe"].keys():
|
|
678
691
|
raise KeyError("No 'start time' have been selected, please set it up and re-try.")
|
|
679
692
|
|
|
@@ -695,6 +708,7 @@ class Request(ApiDto):
|
|
|
695
708
|
|
|
696
709
|
if "aggregations" not in json_data.keys():
|
|
697
710
|
raise KeyError("No 'aggregations' have been selected, please set it up and re-try.")
|
|
711
|
+
self._object(json_data["aggregations"], "aggregations")
|
|
698
712
|
|
|
699
713
|
if "agg_method" not in json_data["aggregations"].keys():
|
|
700
714
|
self.aggregation = None
|
|
@@ -751,7 +765,7 @@ class Request(ApiDto):
|
|
|
751
765
|
self.null = json_data["null"]
|
|
752
766
|
|
|
753
767
|
if "target_feat" in json_data.keys():
|
|
754
|
-
self.target_feat = json_data["target_feat"]
|
|
768
|
+
self.target_feat = self._object(json_data["target_feat"], "target_feat")
|
|
755
769
|
if "sensor" not in self.target_feat.keys():
|
|
756
770
|
raise KeyError("No 'sensor' have been declared inside the target feature, this is a technical error.")
|
|
757
771
|
if "operator" not in self.target_feat.keys():
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.2.0.dev5"
|
|
@@ -54,6 +54,7 @@ from .mobile_asset import MobileAssetTile, MobileAssetConfig
|
|
|
54
54
|
from .version import __version__
|
|
55
55
|
import ast
|
|
56
56
|
from .deployment import Deployment, DeploymentStatus
|
|
57
|
+
from .product import ProductKind, Product, ProductComponent
|
|
57
58
|
from .api_config import _registry
|
|
58
59
|
|
|
59
60
|
from .streamlit_utils import get_streamlit_token, get_streamlit_domain
|
|
@@ -2731,6 +2732,142 @@ class WizataDSAPIClient(ApiInterface, ApiDtoInterface):
|
|
|
2731
2732
|
raise self.__raise_error(response)
|
|
2732
2733
|
|
|
2733
2734
|
|
|
2735
|
+
# ------------------------------------------------------------------
|
|
2736
|
+
# Production Planner - product definitions (DEV-6070)
|
|
2737
|
+
#
|
|
2738
|
+
# Everything below answers 403 PLANNER_LICENSE_DISABLED while the
|
|
2739
|
+
# Production Planner add-on is not active on the environment (DEV-6069).
|
|
2740
|
+
# ------------------------------------------------------------------
|
|
2741
|
+
|
|
2742
|
+
def _planner_request(self, method: str, route: str, body=None, params: dict = None,
|
|
2743
|
+
expected=(200,)):
|
|
2744
|
+
response = requests.request(
|
|
2745
|
+
method,
|
|
2746
|
+
self.__url() + route,
|
|
2747
|
+
headers=self.__header(),
|
|
2748
|
+
data=json.dumps(body) if body is not None else None,
|
|
2749
|
+
params=params,
|
|
2750
|
+
)
|
|
2751
|
+
if response.status_code not in expected:
|
|
2752
|
+
raise self.__raise_error(response)
|
|
2753
|
+
return response
|
|
2754
|
+
|
|
2755
|
+
@staticmethod
|
|
2756
|
+
def _planner_key(value) -> str:
|
|
2757
|
+
key = value.key if hasattr(value, "key") else value
|
|
2758
|
+
if not key:
|
|
2759
|
+
raise ValueError("please provide a key")
|
|
2760
|
+
return urllib.parse.quote(str(key), safe="")
|
|
2761
|
+
|
|
2762
|
+
def search_product_kinds(self, search_query=None) -> PagedQueryResult:
|
|
2763
|
+
"""
|
|
2764
|
+
The only way to list product kinds: the built-in raw / container / finished and
|
|
2765
|
+
the tenant's own.
|
|
2766
|
+
|
|
2767
|
+
:param search_query: SearchQuery or dict; filters on ``name``, ``_search`` over
|
|
2768
|
+
key and name.
|
|
2769
|
+
:return: PagedQueryResult with ProductKind results.
|
|
2770
|
+
"""
|
|
2771
|
+
if search_query is None:
|
|
2772
|
+
search_query = SearchQuery()
|
|
2773
|
+
elif isinstance(search_query, dict):
|
|
2774
|
+
search_query = SearchQuery.from_dict(search_query)
|
|
2775
|
+
response = self._planner_request("POST", "planner/productkinds/search",
|
|
2776
|
+
body=search_query.to_dict())
|
|
2777
|
+
return PagedQueryResult.from_dict(response.json(), dto_class=ProductKind)
|
|
2778
|
+
|
|
2779
|
+
def get_product_kind(self, key: str):
|
|
2780
|
+
"""
|
|
2781
|
+
:param key: product kind key (e.g. ``raw``).
|
|
2782
|
+
:return: ProductKind, or None when it does not exist.
|
|
2783
|
+
"""
|
|
2784
|
+
response = self._planner_request("GET", f"planner/productkinds/{self._planner_key(key)}/",
|
|
2785
|
+
expected=(200, 404))
|
|
2786
|
+
return ProductKind.from_dict(response.json()) if response.status_code == 200 else None
|
|
2787
|
+
|
|
2788
|
+
def create_product_kind(self, kind: ProductKind) -> ProductKind:
|
|
2789
|
+
response = self._planner_request("POST", "planner/productkinds/", body=kind.to_dict(),
|
|
2790
|
+
expected=(201,))
|
|
2791
|
+
return ProductKind.from_dict(response.json())
|
|
2792
|
+
|
|
2793
|
+
def update_product_kind(self, kind: ProductKind) -> ProductKind:
|
|
2794
|
+
response = self._planner_request("PUT", f"planner/productkinds/{self._planner_key(kind)}/",
|
|
2795
|
+
body=kind.to_dict())
|
|
2796
|
+
return ProductKind.from_dict(response.json())
|
|
2797
|
+
|
|
2798
|
+
def delete_product_kind(self, kind):
|
|
2799
|
+
"""
|
|
2800
|
+
:param kind: ProductKind or its key. A built-in kind, or a kind still used by a
|
|
2801
|
+
product, is refused (409).
|
|
2802
|
+
"""
|
|
2803
|
+
self._planner_request("DELETE", f"planner/productkinds/{self._planner_key(kind)}/",
|
|
2804
|
+
expected=(204,))
|
|
2805
|
+
|
|
2806
|
+
def search_products(self, search_query=None) -> PagedQueryResult:
|
|
2807
|
+
"""
|
|
2808
|
+
The only way to list products.
|
|
2809
|
+
|
|
2810
|
+
:param search_query: SearchQuery or dict; filters on ``kind``, ``category``
|
|
2811
|
+
(backend category id), ``name``, and ``_search`` over key and name.
|
|
2812
|
+
:return: PagedQueryResult with Product results.
|
|
2813
|
+
"""
|
|
2814
|
+
if search_query is None:
|
|
2815
|
+
search_query = SearchQuery()
|
|
2816
|
+
elif isinstance(search_query, dict):
|
|
2817
|
+
search_query = SearchQuery.from_dict(search_query)
|
|
2818
|
+
response = self._planner_request("POST", "planner/products/search",
|
|
2819
|
+
body=search_query.to_dict())
|
|
2820
|
+
return PagedQueryResult.from_dict(response.json(), dto_class=Product)
|
|
2821
|
+
|
|
2822
|
+
def get_product(self, key: str):
|
|
2823
|
+
"""
|
|
2824
|
+
:return: Product, or None when it does not exist.
|
|
2825
|
+
"""
|
|
2826
|
+
response = self._planner_request("GET", f"planner/products/{self._planner_key(key)}/",
|
|
2827
|
+
expected=(200, 404))
|
|
2828
|
+
return Product.from_dict(response.json()) if response.status_code == 200 else None
|
|
2829
|
+
|
|
2830
|
+
def create_product(self, product: Product) -> Product:
|
|
2831
|
+
response = self._planner_request("POST", "planner/products/", body=product.to_dict(),
|
|
2832
|
+
expected=(201,))
|
|
2833
|
+
return Product.from_dict(response.json())
|
|
2834
|
+
|
|
2835
|
+
def update_product(self, product: Product) -> Product:
|
|
2836
|
+
response = self._planner_request("PUT", f"planner/products/{self._planner_key(product)}/",
|
|
2837
|
+
body=product.to_dict())
|
|
2838
|
+
return Product.from_dict(response.json())
|
|
2839
|
+
|
|
2840
|
+
def delete_product(self, product):
|
|
2841
|
+
"""
|
|
2842
|
+
:param product: Product or its key. Refused (409) while another product is made
|
|
2843
|
+
of it; its own bill of materials is deleted with it.
|
|
2844
|
+
"""
|
|
2845
|
+
self._planner_request("DELETE", f"planner/products/{self._planner_key(product)}/",
|
|
2846
|
+
expected=(204,))
|
|
2847
|
+
|
|
2848
|
+
def get_product_components(self, product) -> list:
|
|
2849
|
+
"""
|
|
2850
|
+
:param product: Product or its key.
|
|
2851
|
+
:return: its bill of materials, a list of ProductComponent with unit_symbol set.
|
|
2852
|
+
"""
|
|
2853
|
+
response = self._planner_request(
|
|
2854
|
+
"GET", f"planner/products/{self._planner_key(product)}/components/")
|
|
2855
|
+
return [ProductComponent.from_dict(c) for c in response.json()]
|
|
2856
|
+
|
|
2857
|
+
def set_product_components(self, product, components: list) -> list:
|
|
2858
|
+
"""
|
|
2859
|
+
Replace a product's bill of materials (an empty list clears it).
|
|
2860
|
+
|
|
2861
|
+
:param product: Product or its key.
|
|
2862
|
+
:param components: list of ProductComponent (``product`` may be left unset).
|
|
2863
|
+
:return: the stored components, unit_symbol set.
|
|
2864
|
+
"""
|
|
2865
|
+
body = [c.to_dict() if isinstance(c, ProductComponent) else c for c in components]
|
|
2866
|
+
response = self._planner_request(
|
|
2867
|
+
"PUT", f"planner/products/{self._planner_key(product)}/components/", body=body)
|
|
2868
|
+
return [ProductComponent.from_dict(c) for c in response.json()]
|
|
2869
|
+
|
|
2870
|
+
|
|
2734
2871
|
def api() -> WizataDSAPIClient:
|
|
2735
2872
|
"""
|
|
2736
2873
|
Create a WizataDSAPIClient from environment variables.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.2.0.dev2"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{wizata_dsapi-2.2.0.dev2 → wizata_dsapi-2.2.0.dev5}/wizata_dsapi.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|