perfact-api-pd-fastapi 1.0__tar.gz → 1.2__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.
- {perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/PKG-INFO +2 -1
- {perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/pyproject.toml +1 -0
- {perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/src/perfact/api/pd_fastapi/__init__.py +4 -0
- perfact_api_pd_fastapi-1.2/src/perfact/api/pd_fastapi/grafana.py +155 -0
- {perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/src/perfact/api/pd_fastapi/kpi.py +40 -1
- {perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/src/perfact_api_pd_fastapi.egg-info/PKG-INFO +2 -1
- {perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/src/perfact_api_pd_fastapi.egg-info/SOURCES.txt +1 -0
- {perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/src/perfact_api_pd_fastapi.egg-info/requires.txt +1 -0
- {perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/src/perfact_api_pd_fastapi.egg-info/scm_file_list.json +8 -7
- perfact_api_pd_fastapi-1.2/src/perfact_api_pd_fastapi.egg-info/scm_version.json +8 -0
- {perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/tests/helpers.py +2 -2
- {perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/tox.ini +1 -0
- perfact_api_pd_fastapi-1.0/src/perfact_api_pd_fastapi.egg-info/scm_version.json +0 -8
- {perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/README.md +0 -0
- {perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/setup.cfg +0 -0
- {perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/src/perfact/api/pd_fastapi/pdunit.py +0 -0
- {perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/src/perfact/api/pd_fastapi/pdunitlch.py +0 -0
- {perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/src/perfact/api/pd_fastapi/performancedata.py +0 -0
- {perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/src/perfact/api/pd_fastapi/py.typed +0 -0
- {perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/src/perfact_api_pd_fastapi.egg-info/dependency_links.txt +0 -0
- {perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/src/perfact_api_pd_fastapi.egg-info/entry_points.txt +0 -0
- {perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/src/perfact_api_pd_fastapi.egg-info/top_level.txt +0 -0
- {perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/tests/__init__.py +0 -0
- {perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/tests/conftest.py +0 -0
- {perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/tests/locustfile.py +0 -0
- {perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/tests/test_init.py +0 -0
- {perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/tests/test_kpi.py +0 -0
- {perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/tests/test_pdunit.py +0 -0
- {perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/tests/test_performance.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: perfact-api-pd-fastapi
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2
|
|
4
4
|
Summary: PerFact API - SQLAlchemy+FastAPI pd apis package
|
|
5
5
|
Author-email: Viktor Dick <viktor.dick@perfact.de>
|
|
6
6
|
License-Expression: GPL-2.0-or-later
|
|
@@ -13,6 +13,7 @@ Requires-Dist: fastapi[standard-no-fastapi-cloud-cli]
|
|
|
13
13
|
Requires-Dist: perfact-api-pp
|
|
14
14
|
Requires-Dist: perfact-api-pd
|
|
15
15
|
Requires-Dist: perfact-api-main
|
|
16
|
+
Requires-Dist: perfact-api-it
|
|
16
17
|
|
|
17
18
|
# PerFact API - pd - APIs
|
|
18
19
|
|
{perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/src/perfact/api/pd_fastapi/__init__.py
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from fastapi import FastAPI
|
|
2
2
|
from perfact.api.main.dbsession import APIRouter
|
|
3
3
|
|
|
4
|
+
from .grafana import mount as grafanamount
|
|
4
5
|
from .kpi import mount as kpi_mount
|
|
5
6
|
from .kpi import routes_assignd as kpi_routes_assignd
|
|
6
7
|
from .pdunit import mount as pdunitmount
|
|
@@ -9,6 +10,7 @@ from .performancedata import mount as performancemount
|
|
|
9
10
|
|
|
10
11
|
routes = APIRouter()
|
|
11
12
|
routes_pdord = APIRouter()
|
|
13
|
+
routes_grafana = APIRouter()
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
def mount(app: FastAPI):
|
|
@@ -16,8 +18,10 @@ def mount(app: FastAPI):
|
|
|
16
18
|
pdunitmount(routes)
|
|
17
19
|
pdunitlch_mount(routes)
|
|
18
20
|
performancemount(routes_pdord)
|
|
21
|
+
grafanamount(routes_grafana)
|
|
19
22
|
app.include_router(routes, prefix="/pd")
|
|
20
23
|
app.include_router(routes_pdord, prefix="/pd/pdord")
|
|
24
|
+
app.include_router(routes_grafana, prefix="/pd/grafana")
|
|
21
25
|
|
|
22
26
|
|
|
23
27
|
def mount_assignd(app: FastAPI):
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
from typing import Any, Sequence
|
|
2
|
+
|
|
3
|
+
from fastapi import HTTPException, status
|
|
4
|
+
from perfact.api.base import VisibilityPolicy
|
|
5
|
+
from perfact.api.main import VisibilityRegistryDep
|
|
6
|
+
from perfact.api.main.auth import Auth, require_roles
|
|
7
|
+
from perfact.api.main.dbsession import APIRouter, DBSession
|
|
8
|
+
from perfact.api.pd.model.pdunit import PdUnit
|
|
9
|
+
from perfact.api.pd.services import get_failure_times, pdunit_downtime_grafana_resp
|
|
10
|
+
from perfact.api.pd.services.grafana import (
|
|
11
|
+
QueryRequest,
|
|
12
|
+
Target,
|
|
13
|
+
VariableRequest,
|
|
14
|
+
extract_grafana_timerange,
|
|
15
|
+
)
|
|
16
|
+
from pydantic import BaseModel, Field
|
|
17
|
+
from sqlalchemy import select
|
|
18
|
+
|
|
19
|
+
routes = APIRouter()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _filter_visible_pdunit_ids(
|
|
23
|
+
session: DBSession,
|
|
24
|
+
auth: Auth,
|
|
25
|
+
policy: VisibilityPolicy,
|
|
26
|
+
pdunit_ids: list[int],
|
|
27
|
+
) -> list[int]:
|
|
28
|
+
"""Return only PdUnit IDs visible to the current user."""
|
|
29
|
+
return list(
|
|
30
|
+
session.scalars(
|
|
31
|
+
select(PdUnit.id)
|
|
32
|
+
.where(PdUnit.id.in_(pdunit_ids))
|
|
33
|
+
.where(policy.filter(auth))
|
|
34
|
+
).all()
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@routes.get("/", tags=["pd.grafana"], summary="Grafana health check")
|
|
39
|
+
def grafana_health() -> dict[str, str]:
|
|
40
|
+
"""Default Grafana route to check if the endpoint is available"""
|
|
41
|
+
return {"status": "ok"}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@require_roles("PdKpi")
|
|
45
|
+
@routes.post("/metrics", tags=["pd.grafana"], summary="Grafana metrics discovery")
|
|
46
|
+
def grafana_metrics() -> list[Any]:
|
|
47
|
+
"""
|
|
48
|
+
Returns a list of possible metrics which Grafana can use to display data.
|
|
49
|
+
Current available Metrics:
|
|
50
|
+
- pd.kpi.downtime: categorized downtime of workcenters
|
|
51
|
+
"""
|
|
52
|
+
return [
|
|
53
|
+
{"label": "PdUnit Downtime", "value": "pd.kpi.downtime"},
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@routes.post("/annotations", tags=["pd.grafana"], summary="Grafana annotations")
|
|
58
|
+
def grafana_annotations() -> list:
|
|
59
|
+
return []
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class VariableDefinition(BaseModel):
|
|
63
|
+
text: str = Field(alias="__text")
|
|
64
|
+
value: str | int = Field(alias="__value")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@require_roles("PdKpi", "PdRead")
|
|
68
|
+
@routes.post(
|
|
69
|
+
"/variable",
|
|
70
|
+
tags=["pd.grafana"],
|
|
71
|
+
summary="Grafana variables",
|
|
72
|
+
response_model=Sequence[VariableDefinition],
|
|
73
|
+
)
|
|
74
|
+
def grafana_variables(
|
|
75
|
+
session: DBSession,
|
|
76
|
+
auth: Auth,
|
|
77
|
+
visibility: VisibilityRegistryDep,
|
|
78
|
+
req: VariableRequest,
|
|
79
|
+
) -> Sequence[Any]:
|
|
80
|
+
"""
|
|
81
|
+
Currently available variables:
|
|
82
|
+
- pd.pdunit: all visible pdunits for the requesting user
|
|
83
|
+
"""
|
|
84
|
+
if auth is None:
|
|
85
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
|
|
86
|
+
if req.payload is None:
|
|
87
|
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
|
|
88
|
+
|
|
89
|
+
target = req.payload.get("target", None)
|
|
90
|
+
if target == "pd.pdunit":
|
|
91
|
+
policy = visibility.get(PdUnit)
|
|
92
|
+
return (
|
|
93
|
+
session.execute(
|
|
94
|
+
select(PdUnit.num.label("__text"), PdUnit.id.label("__value")).filter(
|
|
95
|
+
policy.filter(auth)
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
.mappings()
|
|
99
|
+
.all()
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@require_roles("PdKpi")
|
|
106
|
+
@routes.post("/query", tags=["pd.grafana"], summary="Grafana query (actually get data)")
|
|
107
|
+
def grafana_query(
|
|
108
|
+
session: DBSession,
|
|
109
|
+
auth: Auth,
|
|
110
|
+
visibility: VisibilityRegistryDep,
|
|
111
|
+
query: QueryRequest,
|
|
112
|
+
) -> list[dict[str, Any]]:
|
|
113
|
+
"""
|
|
114
|
+
Actually query data from the database.
|
|
115
|
+
Supply targets to specify which metric to query.
|
|
116
|
+
All available metrics can be seen in /metrics
|
|
117
|
+
"""
|
|
118
|
+
if auth is None:
|
|
119
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
|
|
120
|
+
policy = visibility.get(PdUnit)
|
|
121
|
+
|
|
122
|
+
targets = query.targets
|
|
123
|
+
if not targets and (query.target or query.payload):
|
|
124
|
+
targets = [Target(target=query.target, payload=query.payload)]
|
|
125
|
+
|
|
126
|
+
if not targets:
|
|
127
|
+
return []
|
|
128
|
+
|
|
129
|
+
response: list[dict[str, Any]] = []
|
|
130
|
+
for target in targets:
|
|
131
|
+
if target.target not in {None, "pd.kpi.downtime"}:
|
|
132
|
+
continue
|
|
133
|
+
|
|
134
|
+
pdunit_ids = []
|
|
135
|
+
if target.payload is not None:
|
|
136
|
+
pdunit_ids = [int(x) for x in target.payload.get("pdunit", [])]
|
|
137
|
+
pdunit_ids = _filter_visible_pdunit_ids(session, auth, policy, pdunit_ids)
|
|
138
|
+
starttime, endtime = extract_grafana_timerange(query)
|
|
139
|
+
|
|
140
|
+
if (
|
|
141
|
+
not pdunit_ids
|
|
142
|
+
or starttime is None
|
|
143
|
+
or endtime is None
|
|
144
|
+
or starttime >= endtime
|
|
145
|
+
):
|
|
146
|
+
response.append(pdunit_downtime_grafana_resp([], target.refId))
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
rows = get_failure_times(session, pdunit_ids, starttime, endtime)
|
|
150
|
+
response.append(pdunit_downtime_grafana_resp(rows, target.refId))
|
|
151
|
+
return response
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def mount(app: APIRouter):
|
|
155
|
+
app.include_router(routes)
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
from typing import Annotated, Optional
|
|
3
3
|
|
|
4
|
-
from fastapi import HTTPException
|
|
4
|
+
from fastapi import HTTPException, Query, status
|
|
5
|
+
from perfact.api.base import VisibilityPolicy
|
|
5
6
|
from perfact.api.main import Auth, VisibilityRegistryDep
|
|
6
7
|
from perfact.api.main.auth import require_roles
|
|
7
8
|
from perfact.api.main.dbsession import APIRouter, DBSession
|
|
8
9
|
from perfact.api.pd.model.pdunit import PdUnit
|
|
10
|
+
from perfact.api.pd.services import get_failure_times
|
|
9
11
|
from perfact.api.pd.services.oee.availability import compute_availability
|
|
10
12
|
from perfact.api.pd.services.oee.breakdown import oee_breakdown_computation
|
|
11
13
|
from perfact.api.pd.services.oee.prod_metrics import (
|
|
@@ -43,6 +45,22 @@ def _require_visible_unit(
|
|
|
43
45
|
return unit
|
|
44
46
|
|
|
45
47
|
|
|
48
|
+
def _filter_visible_pdunit_ids(
|
|
49
|
+
session: DBSession,
|
|
50
|
+
auth: Auth,
|
|
51
|
+
policy: VisibilityPolicy,
|
|
52
|
+
pdunit_ids: list[int],
|
|
53
|
+
) -> list[int]:
|
|
54
|
+
"""Return only PdUnit IDs visible to the current user."""
|
|
55
|
+
return list(
|
|
56
|
+
session.scalars(
|
|
57
|
+
select(PdUnit.id)
|
|
58
|
+
.where(PdUnit.id.in_(pdunit_ids))
|
|
59
|
+
.where(policy.filter(auth))
|
|
60
|
+
).all()
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
46
64
|
def parse_datetime_with_UTC_offset(value: str) -> datetime:
|
|
47
65
|
try:
|
|
48
66
|
return datetime.fromisoformat(value)
|
|
@@ -256,5 +274,26 @@ def oee_trend_aggregation(
|
|
|
256
274
|
}
|
|
257
275
|
|
|
258
276
|
|
|
277
|
+
@routes.get("/kpi/downtime", tags=["pd"], summary="PdUnit downtime")
|
|
278
|
+
@require_roles("PdKpi")
|
|
279
|
+
def pdunit_downtime_calculation(
|
|
280
|
+
session: DBSession,
|
|
281
|
+
auth: Auth,
|
|
282
|
+
visibility: VisibilityRegistryDep,
|
|
283
|
+
starttime: datetime,
|
|
284
|
+
endtime: datetime,
|
|
285
|
+
pdunit_ids: list[int] = Query(),
|
|
286
|
+
) -> list:
|
|
287
|
+
if auth is None:
|
|
288
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
|
|
289
|
+
if not pdunit_ids:
|
|
290
|
+
return []
|
|
291
|
+
policy = visibility.get(PdUnit)
|
|
292
|
+
pdunit_ids = _filter_visible_pdunit_ids(session, auth, policy, pdunit_ids)
|
|
293
|
+
if not pdunit_ids:
|
|
294
|
+
return []
|
|
295
|
+
return get_failure_times(session, pdunit_ids, starttime, endtime)
|
|
296
|
+
|
|
297
|
+
|
|
259
298
|
def mount(app: APIRouter):
|
|
260
299
|
app.include_router(routes)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: perfact-api-pd-fastapi
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2
|
|
4
4
|
Summary: PerFact API - SQLAlchemy+FastAPI pd apis package
|
|
5
5
|
Author-email: Viktor Dick <viktor.dick@perfact.de>
|
|
6
6
|
License-Expression: GPL-2.0-or-later
|
|
@@ -13,6 +13,7 @@ Requires-Dist: fastapi[standard-no-fastapi-cloud-cli]
|
|
|
13
13
|
Requires-Dist: perfact-api-pp
|
|
14
14
|
Requires-Dist: perfact-api-pd
|
|
15
15
|
Requires-Dist: perfact-api-main
|
|
16
|
+
Requires-Dist: perfact-api-it
|
|
16
17
|
|
|
17
18
|
# PerFact API - pd - APIs
|
|
18
19
|
|
|
@@ -3,19 +3,20 @@
|
|
|
3
3
|
"README.md",
|
|
4
4
|
"pyproject.toml",
|
|
5
5
|
"tox.ini",
|
|
6
|
-
"src/perfact/api/pd_fastapi/__init__.py",
|
|
7
|
-
"src/perfact/api/pd_fastapi/kpi.py",
|
|
8
6
|
"src/perfact/api/pd_fastapi/performancedata.py",
|
|
9
|
-
"src/perfact/api/pd_fastapi/
|
|
7
|
+
"src/perfact/api/pd_fastapi/grafana.py",
|
|
10
8
|
"src/perfact/api/pd_fastapi/py.typed",
|
|
9
|
+
"src/perfact/api/pd_fastapi/kpi.py",
|
|
10
|
+
"src/perfact/api/pd_fastapi/__init__.py",
|
|
11
11
|
"src/perfact/api/pd_fastapi/pdunitlch.py",
|
|
12
|
-
"
|
|
13
|
-
"tests/conftest.py",
|
|
14
|
-
"tests/test_kpi.py",
|
|
15
|
-
"tests/test_init.py",
|
|
12
|
+
"src/perfact/api/pd_fastapi/pdunit.py",
|
|
16
13
|
"tests/helpers.py",
|
|
17
14
|
"tests/locustfile.py",
|
|
15
|
+
"tests/test_init.py",
|
|
16
|
+
"tests/test_kpi.py",
|
|
17
|
+
"tests/__init__.py",
|
|
18
18
|
"tests/test_pdunit.py",
|
|
19
|
+
"tests/conftest.py",
|
|
19
20
|
"tests/test_performance.py"
|
|
20
21
|
]
|
|
21
22
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from fastapi.routing import APIRoute
|
|
1
|
+
from fastapi.routing import APIRoute
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
def collect_paths(router, prefix: str = "") -> set[str]:
|
|
@@ -11,7 +11,7 @@ def collect_paths(router, prefix: str = "") -> set[str]:
|
|
|
11
11
|
for route in router.routes:
|
|
12
12
|
if isinstance(route, APIRoute):
|
|
13
13
|
paths.add(prefix + route.path)
|
|
14
|
-
elif
|
|
14
|
+
elif hasattr(route, "original_router") and hasattr(route, "include_context"):
|
|
15
15
|
sub_prefix = prefix + (route.include_context.prefix or "")
|
|
16
16
|
paths |= collect_paths(route.original_router, sub_prefix)
|
|
17
17
|
return paths
|
|
File without changes
|
|
File without changes
|
{perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/src/perfact/api/pd_fastapi/pdunit.py
RENAMED
|
File without changes
|
{perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/src/perfact/api/pd_fastapi/pdunitlch.py
RENAMED
|
File without changes
|
|
File without changes
|
{perfact_api_pd_fastapi-1.0 → perfact_api_pd_fastapi-1.2}/src/perfact/api/pd_fastapi/py.typed
RENAMED
|
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
|