oots-lib 0.1.0__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.
- oots_lib/Transport.py +139 -0
- oots_lib/__init__.py +77 -0
- oots_lib/import_env.py +12 -0
- oots_lib/lib/CreatePDF.py +83 -0
- oots_lib/lib/EvidanceMetadata.py +92 -0
- oots_lib/lib/MakeEvidence.py +282 -0
- oots_lib/lib/NS.py +70 -0
- oots_lib/lib/UseRedis.py +389 -0
- oots_lib/lib/__init__.py +0 -0
- oots_lib/lib/exception.py +136 -0
- oots_lib/lib/exceptions.py +143 -0
- oots_lib/lib/redis_serde.py +90 -0
- oots_lib/lib/toLogger.py +61 -0
- oots_lib/lib/xml_safety.py +44 -0
- oots_lib/lib/xml_utils.py +38 -0
- oots_lib/models/Base.py +73 -0
- oots_lib/models/Person.py +332 -0
- oots_lib/models/RequestEDM.py +69 -0
- oots_lib/models/ResponseEvidences.py +320 -0
- oots_lib/models/__init__.py +51 -0
- oots_lib/redis_keys.py +44 -0
- oots_lib-0.1.0.dist-info/METADATA +181 -0
- oots_lib-0.1.0.dist-info/RECORD +26 -0
- oots_lib-0.1.0.dist-info/WHEEL +5 -0
- oots_lib-0.1.0.dist-info/licenses/LICENSE +190 -0
- oots_lib-0.1.0.dist-info/top_level.txt +1 -0
oots_lib/Transport.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from typing import NoReturn
|
|
4
|
+
|
|
5
|
+
from XRoad import RedisCache, Transport, XClient
|
|
6
|
+
from XRoad.plugins import UXPHistoryPlugin
|
|
7
|
+
|
|
8
|
+
from oots_lib.import_env import import_env
|
|
9
|
+
from oots_lib.lib.exception import EDMException, TransportError
|
|
10
|
+
from oots_lib.lib.toLogger import ToLogger
|
|
11
|
+
from oots_lib.lib.UseRedis import get_redis_client
|
|
12
|
+
|
|
13
|
+
_logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
TREMBITA_URL = import_env("TREMBITA_URL")
|
|
16
|
+
TREMBITA_CLIENT_ID = import_env("TREMBITA_CLIENT_ID")
|
|
17
|
+
TREMBITA_CACHE = int(import_env("TREMBITA_CACHE"))
|
|
18
|
+
REDIS_URL = import_env("REDIS_URL")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SOAPTransport(ABC):
|
|
22
|
+
def __init__(self, service: str, conversation_id: str, if_send_error: bool = True):
|
|
23
|
+
self.service = service
|
|
24
|
+
self.conversation_id = conversation_id
|
|
25
|
+
self.if_send_error = if_send_error
|
|
26
|
+
|
|
27
|
+
self.to_logger = ToLogger(conversation_id)
|
|
28
|
+
self.redis = get_redis_client()
|
|
29
|
+
|
|
30
|
+
_logger.info(f"Надсилаємо запит до сервісу: {self.service}")
|
|
31
|
+
|
|
32
|
+
cache = RedisCache(REDIS_URL, timeout=3600 * 24)
|
|
33
|
+
transport = Transport(operation_timeout=60 * 5, cache=cache)
|
|
34
|
+
self.history = UXPHistoryPlugin()
|
|
35
|
+
|
|
36
|
+
self.client = None
|
|
37
|
+
try:
|
|
38
|
+
self.client = XClient(
|
|
39
|
+
TREMBITA_URL, client=TREMBITA_CLIENT_ID, service=self.service,
|
|
40
|
+
transport=transport,
|
|
41
|
+
plugins=[self.history, ]
|
|
42
|
+
)
|
|
43
|
+
except Exception as e:
|
|
44
|
+
_logger.exception("Помилка створення XClient")
|
|
45
|
+
self.send_error_message(
|
|
46
|
+
code="EDM:ERR:0006", # NOSONAR
|
|
47
|
+
message="Сталася помилка при створенні з'єднення до Трембіти",
|
|
48
|
+
detail=f"Сталася помилка при створенні з'єднення до Трембіти: {e}",
|
|
49
|
+
cause=e,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
_logger.info(f"XClient успішно створений: {self.client}")
|
|
53
|
+
|
|
54
|
+
def send_error_message(
|
|
55
|
+
self,
|
|
56
|
+
code: str,
|
|
57
|
+
message: str,
|
|
58
|
+
detail: str,
|
|
59
|
+
cause: BaseException | None = None,
|
|
60
|
+
) -> NoReturn:
|
|
61
|
+
"""Сигналізує про помилку транспорту виключенням.
|
|
62
|
+
|
|
63
|
+
При `if_send_error` помилка додатково публікується до Redis та черги
|
|
64
|
+
через :class:`EDMException`, інакше кидається :class:`TransportError`.
|
|
65
|
+
Метод завжди кидає виключення, щоб помилка не залишалась непоміченою.
|
|
66
|
+
"""
|
|
67
|
+
_logger.error(f"Помилка: code={code}, message={message}, detail={detail}")
|
|
68
|
+
if self.if_send_error:
|
|
69
|
+
raise EDMException(
|
|
70
|
+
redis=self.redis,
|
|
71
|
+
queue=None,
|
|
72
|
+
key=None,
|
|
73
|
+
message_id=str(self.conversation_id),
|
|
74
|
+
code=code,
|
|
75
|
+
message=message,
|
|
76
|
+
detail=detail,
|
|
77
|
+
) from cause
|
|
78
|
+
|
|
79
|
+
raise TransportError(f"[{code}] {message}: {detail}") from cause
|
|
80
|
+
|
|
81
|
+
@abstractmethod
|
|
82
|
+
def parsing_response(self, responce: dict) -> list[dict]:
|
|
83
|
+
...
|
|
84
|
+
|
|
85
|
+
def response(self, request: dict) -> list[dict]:
|
|
86
|
+
|
|
87
|
+
_logger.info(f"Запит до сервісу: {self.service}")
|
|
88
|
+
if self.client is None:
|
|
89
|
+
self.send_error_message(
|
|
90
|
+
code="EDM:ERR:0006",
|
|
91
|
+
message="З'єднання до Трембіти не встановлено",
|
|
92
|
+
detail=f"XClient для сервісу {self.service} не створений",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
response = self.client.request(**request)
|
|
97
|
+
except Exception as e:
|
|
98
|
+
_logger.exception("Помилка виконання запиту до Трембіти")
|
|
99
|
+
self.send_error_message(
|
|
100
|
+
code="EDM:ERR:0006",
|
|
101
|
+
message="Сталася помилка при виконанні запиту до Сервісу даних",
|
|
102
|
+
detail=f"Сталася помилка при виконанні запиту до Сервісу даних: {e}",
|
|
103
|
+
cause=e,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
result = response["body"]
|
|
108
|
+
except (KeyError, IndexError, TypeError) as e:
|
|
109
|
+
self.send_error_message(
|
|
110
|
+
code="EDM:ERR:0006",
|
|
111
|
+
message="Відповідь від Сервісу даних має некоректну структуру",
|
|
112
|
+
detail=f"Не вдалося отримати body.GetDocumentsByPersonResult: {e}",
|
|
113
|
+
cause=e,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
self._logging_trembita_transaction()
|
|
117
|
+
|
|
118
|
+
_logger.debug(f"Результат відповіді: {result}")
|
|
119
|
+
return self.parsing_response(result)
|
|
120
|
+
|
|
121
|
+
def _logging_trembita_transaction(self):
|
|
122
|
+
if self.to_logger is None or self.client is None or self.history is None:
|
|
123
|
+
_logger.debug("Журналювання транзакції пропущено: відсутні client/history або логер")
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
self.to_logger.append_calls(
|
|
127
|
+
{
|
|
128
|
+
"dataservice": self.service,
|
|
129
|
+
"timestamp": self.history.transaction_date.isoformat(),
|
|
130
|
+
"trembita_msg_id": self.client.id,
|
|
131
|
+
"transaction_id": self.history.transaction_id,
|
|
132
|
+
}
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
_logger.debug(f"Дані журналу транзакції: {self.to_logger.payload}")
|
|
136
|
+
try:
|
|
137
|
+
self.to_logger.send_to_logger()
|
|
138
|
+
except Exception:
|
|
139
|
+
_logger.exception("Не вдалося надіслати журнал транзакції")
|
oots_lib/__init__.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Public API for the OOTS evidence library."""
|
|
2
|
+
|
|
3
|
+
from importlib import import_module
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"Base",
|
|
7
|
+
"MainBase",
|
|
8
|
+
"NS",
|
|
9
|
+
"SOAPTransport",
|
|
10
|
+
"EDMException",
|
|
11
|
+
"BaseEDMException",
|
|
12
|
+
"AuthenticationException",
|
|
13
|
+
"AuthorizationException",
|
|
14
|
+
"InvalidRequestException",
|
|
15
|
+
"ObjectNotFoundException",
|
|
16
|
+
"QueryException",
|
|
17
|
+
"TimeoutException",
|
|
18
|
+
"UnresolvedReferenceException",
|
|
19
|
+
"UnsupportedCapabilityException",
|
|
20
|
+
"MakeEvidence",
|
|
21
|
+
"UseRedisAsync",
|
|
22
|
+
"get_redis_client",
|
|
23
|
+
"initialize_redis",
|
|
24
|
+
"close_redis",
|
|
25
|
+
"generate_pdf_from_xslt",
|
|
26
|
+
"Identifier",
|
|
27
|
+
"Person",
|
|
28
|
+
"EDMRequest",
|
|
29
|
+
"Evidences",
|
|
30
|
+
"Description",
|
|
31
|
+
"Classification",
|
|
32
|
+
"RepositoryItemRef",
|
|
33
|
+
"ExtrinsicObjectType",
|
|
34
|
+
"RegistryPackageType",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
_LAZY_IMPORTS = {
|
|
38
|
+
"Base": ("oots_lib.models.Base", "Base"),
|
|
39
|
+
"MainBase": ("oots_lib.models.Base", "MainBase"),
|
|
40
|
+
"NS": ("oots_lib.lib.NS", "NS"),
|
|
41
|
+
"SOAPTransport": ("oots_lib.Transport", "SOAPTransport"),
|
|
42
|
+
"EDMException": ("oots_lib.lib.exception", "EDMException"),
|
|
43
|
+
"BaseEDMException": ("oots_lib.lib.exceptions", "EDMException"),
|
|
44
|
+
"AuthenticationException": ("oots_lib.lib.exceptions", "AuthenticationException"),
|
|
45
|
+
"AuthorizationException": ("oots_lib.lib.exceptions", "AuthorizationException"),
|
|
46
|
+
"InvalidRequestException": ("oots_lib.lib.exceptions", "InvalidRequestException"),
|
|
47
|
+
"ObjectNotFoundException": ("oots_lib.lib.exceptions", "ObjectNotFoundException"),
|
|
48
|
+
"QueryException": ("oots_lib.lib.exceptions", "QueryException"),
|
|
49
|
+
"TimeoutException": ("oots_lib.lib.exceptions", "TimeoutException"),
|
|
50
|
+
"UnresolvedReferenceException": ("oots_lib.lib.exceptions", "UnresolvedReferenceException"),
|
|
51
|
+
"UnsupportedCapabilityException": ("oots_lib.lib.exceptions", "UnsupportedCapabilityException"),
|
|
52
|
+
"MakeEvidence": ("oots_lib.lib.MakeEvidence", "MakeEvidence"),
|
|
53
|
+
"UseRedisAsync": ("oots_lib.lib.UseRedis", "UseRedisAsync"),
|
|
54
|
+
"get_redis_client": ("oots_lib.lib.UseRedis", "get_redis_client"),
|
|
55
|
+
"initialize_redis": ("oots_lib.lib.UseRedis", "initialize_redis"),
|
|
56
|
+
"close_redis": ("oots_lib.lib.UseRedis", "close_redis"),
|
|
57
|
+
"generate_pdf_from_xslt": ("oots_lib.lib.CreatePDF", "generate_pdf_from_xslt"),
|
|
58
|
+
"Identifier": ("oots_lib.models.Person", "Identifier"),
|
|
59
|
+
"Person": ("oots_lib.models.Person", "Person"),
|
|
60
|
+
"EDMRequest": ("oots_lib.models.RequestEDM", "EDMRequest"),
|
|
61
|
+
"Evidences": ("oots_lib.models.ResponseEvidences", "Evidences"),
|
|
62
|
+
"Description": ("oots_lib.models.ResponseEvidences", "Description"),
|
|
63
|
+
"Classification": ("oots_lib.models.ResponseEvidences", "Classification"),
|
|
64
|
+
"RepositoryItemRef": ("oots_lib.models.ResponseEvidences", "RepositoryItemRef"),
|
|
65
|
+
"ExtrinsicObjectType": ("oots_lib.models.ResponseEvidences", "ExtrinsicObjectType"),
|
|
66
|
+
"RegistryPackageType": ("oots_lib.models.ResponseEvidences", "RegistryPackageType"),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def __getattr__(name: str):
|
|
71
|
+
if name not in _LAZY_IMPORTS:
|
|
72
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
73
|
+
module_name, attr_name = _LAZY_IMPORTS[name]
|
|
74
|
+
module = import_module(module_name)
|
|
75
|
+
value = getattr(module, attr_name)
|
|
76
|
+
globals()[name] = value
|
|
77
|
+
return value
|
oots_lib/import_env.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
_logger = logging.getLogger(__name__)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def import_env(key: str, default: str | None = None) -> str:
|
|
8
|
+
value = os.getenv(key, default)
|
|
9
|
+
if value is None:
|
|
10
|
+
_logger.error("Environment variable %s is not set", key)
|
|
11
|
+
raise ValueError(f"Environment variable {key} is not set")
|
|
12
|
+
return value
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import pathlib
|
|
3
|
+
import re
|
|
4
|
+
from collections.abc import Iterable
|
|
5
|
+
|
|
6
|
+
from lxml import etree
|
|
7
|
+
|
|
8
|
+
from oots_lib.lib.xml_safety import safe_fromstring, safe_parse
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from weasyprint import CSS, HTML # type: ignore
|
|
12
|
+
except ModuleNotFoundError: # pragma: no cover - optional dependency
|
|
13
|
+
CSS = None
|
|
14
|
+
HTML = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
_logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
path = pathlib.Path(__file__).parent
|
|
20
|
+
XSLT_FILE = path / "rdf-display.xsl"
|
|
21
|
+
BIRTH_CERTIFICATE_XSLT_FILE = path / "annexI_birth.xsl"
|
|
22
|
+
MARRIAGE_CERTIFICATE_XSLT_FILE = path / "annexIV_marriage.xsl"
|
|
23
|
+
DISABILITY_CERTIFICATE_XSLT_FILE = path / "disability.xsl"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def generate_pdf_from_xslt(
|
|
27
|
+
rdf: str | bytes,
|
|
28
|
+
xslt_file: pathlib.Path | str = XSLT_FILE,
|
|
29
|
+
css: Iterable[str] | None = None,
|
|
30
|
+
) -> bytes:
|
|
31
|
+
"""
|
|
32
|
+
Виконує XSLT-трансформацію XML у HTML, а потім перетворює HTML у PDF.
|
|
33
|
+
"""
|
|
34
|
+
if HTML is None or CSS is None:
|
|
35
|
+
raise ModuleNotFoundError(
|
|
36
|
+
"weasyprint is required for PDF generation. "
|
|
37
|
+
"Install the optional dependency to use generate_pdf_from_xslt()."
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Крок 1: Завантаження та виконання XSLT-трансформації
|
|
41
|
+
|
|
42
|
+
# Конвертація xslt_file у pathlib.Path якщо потрібно
|
|
43
|
+
if isinstance(xslt_file, str):
|
|
44
|
+
xslt_file = pathlib.Path(xslt_file)
|
|
45
|
+
|
|
46
|
+
# Завантаження XML-документа
|
|
47
|
+
if isinstance(rdf, bytes):
|
|
48
|
+
rdf = rdf.decode("utf-8")
|
|
49
|
+
|
|
50
|
+
xml = re.sub(r"<\?xml [^>]*\?>\s*", '', rdf, count=1)
|
|
51
|
+
xml = xml.strip()
|
|
52
|
+
xml_tree = safe_fromstring(xml)
|
|
53
|
+
|
|
54
|
+
# Завантаження XSLT-таблиці стилів
|
|
55
|
+
xslt_root = safe_parse(xslt_file)
|
|
56
|
+
transform = etree.XSLT(
|
|
57
|
+
xslt_root,
|
|
58
|
+
access_control=etree.XSLTAccessControl.DENY_ALL, # type: ignore[attr-defined]
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Виконання трансформації: XML -> HTML (як etree.ElementTree)
|
|
62
|
+
html_tree = transform(xml_tree)
|
|
63
|
+
|
|
64
|
+
# Серіалізація HTML у рядок
|
|
65
|
+
html_content = etree.tostring(
|
|
66
|
+
html_tree,
|
|
67
|
+
pretty_print=True,
|
|
68
|
+
method="html",
|
|
69
|
+
encoding='utf-8'
|
|
70
|
+
).decode('utf-8')
|
|
71
|
+
|
|
72
|
+
# Крок 2: Перетворення HTML у PDF за допомогою WeasyPrint
|
|
73
|
+
|
|
74
|
+
html_object = HTML(string=html_content)
|
|
75
|
+
|
|
76
|
+
stylesheets = [CSS(string='@page { size: A4; margin: 1.5cm; }')]
|
|
77
|
+
for c in (css or []):
|
|
78
|
+
stylesheets.append(CSS(string=c))
|
|
79
|
+
|
|
80
|
+
# Виклик write_pdf() без аргументу target повертає PDF як bytes.
|
|
81
|
+
pdf_bytes: bytes = html_object.write_pdf(stylesheets=stylesheets) # type: ignore[assignment]
|
|
82
|
+
|
|
83
|
+
return pdf_bytes
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import uuid
|
|
3
|
+
|
|
4
|
+
from lxml import etree
|
|
5
|
+
|
|
6
|
+
from oots_lib.lib.NS import NS
|
|
7
|
+
|
|
8
|
+
_logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"EMetadata",
|
|
12
|
+
"IssuingAuthority",
|
|
13
|
+
"IsConformantTo",
|
|
14
|
+
"Distribution",
|
|
15
|
+
"IsAbout",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
class EMetadata(NS):
|
|
19
|
+
def __init__(self):
|
|
20
|
+
super().__init__()
|
|
21
|
+
self._xml: etree._Element = self._element("sdg", "Evidence")
|
|
22
|
+
self._subelement(self._xml, "sdg", "Identifier", text=uuid.uuid4())
|
|
23
|
+
|
|
24
|
+
def issuingAuthority(self, issuingAuthority: etree._Element): # NOSONAR
|
|
25
|
+
self._xml.append(issuingAuthority)
|
|
26
|
+
|
|
27
|
+
def isConformeant(self, isConformeant: etree._Element): # NOSONAR
|
|
28
|
+
self._xml.append(isConformeant)
|
|
29
|
+
|
|
30
|
+
def distribution(self, distribution: etree._Element):
|
|
31
|
+
self._xml.append(distribution)
|
|
32
|
+
|
|
33
|
+
def isAbout(self, isAbout: etree._Element): # NOSONAR
|
|
34
|
+
self._xml.append(isAbout)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class IssuingAuthority(NS):
|
|
38
|
+
def __init__(self, shema, value):
|
|
39
|
+
super().__init__()
|
|
40
|
+
self._xml: etree._Element = self._element("sdg", "IssuingAuthority")
|
|
41
|
+
self._subelement(
|
|
42
|
+
self._xml, "sdg", "Identifier", text=value, attrib={"schemeID": shema}
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def name(self, lang: str, name: str):
|
|
46
|
+
self._subelement(
|
|
47
|
+
self._xml, "sdg", "Name", text=name, attrib={"lang": f"{lang}".upper()}
|
|
48
|
+
)
|
|
49
|
+
return self
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Distribution(NS):
|
|
53
|
+
def __init__(self, content_type):
|
|
54
|
+
super().__init__()
|
|
55
|
+
self._xml: etree._Element = self._element("sdg", "Distribution")
|
|
56
|
+
self._subelement(self._xml, "sdg", "Format", text=content_type)
|
|
57
|
+
|
|
58
|
+
def ConformsTo(self, url):
|
|
59
|
+
self._subelement(self._xml, "sdg", "ConformsTo", text=url)
|
|
60
|
+
|
|
61
|
+
def Transformation(self, url):
|
|
62
|
+
self._subelement(self._xml, "sdg", "Transformation", text=url)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class IsConformantTo(NS):
|
|
66
|
+
def __init__(self, conformance):
|
|
67
|
+
super().__init__()
|
|
68
|
+
self._xml: etree._Element = self._element("sdg", "IsConformantTo")
|
|
69
|
+
self._subelement(
|
|
70
|
+
self._xml, "sdg", "EvidenceTypeClassification", text=conformance
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def title(self, lang, title):
|
|
74
|
+
self._subelement(
|
|
75
|
+
self._xml, "sdg", "Title", text=title, attrib={"lang": lang.upper()}
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def description(self, lang, description):
|
|
79
|
+
self._subelement(
|
|
80
|
+
self._xml,
|
|
81
|
+
"sdg",
|
|
82
|
+
"Description",
|
|
83
|
+
text=description,
|
|
84
|
+
attrib={"lang": lang.upper()},
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class IsAbout(NS):
|
|
89
|
+
def __init__(self, person: etree._Element):
|
|
90
|
+
super().__init__()
|
|
91
|
+
self._xml: etree._Element = self._element("sdg", "IsAbout")
|
|
92
|
+
self._xml.append(person)
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import logging
|
|
3
|
+
from dataclasses import is_dataclass
|
|
4
|
+
|
|
5
|
+
from pyRegRep4 import deep_get
|
|
6
|
+
from pyRegRep4.RIMParsing import Parsing
|
|
7
|
+
|
|
8
|
+
from oots_lib.import_env import import_env
|
|
9
|
+
from oots_lib.lib.EvidanceMetadata import (
|
|
10
|
+
Distribution,
|
|
11
|
+
EMetadata,
|
|
12
|
+
IsAbout,
|
|
13
|
+
IsConformantTo,
|
|
14
|
+
IssuingAuthority,
|
|
15
|
+
)
|
|
16
|
+
from oots_lib.lib.exception import EDMException
|
|
17
|
+
from oots_lib.lib.toLogger import ToLogger
|
|
18
|
+
from oots_lib.lib.UseRedis import UseRedisAsync as Redis
|
|
19
|
+
from oots_lib.models.Base import MainBase
|
|
20
|
+
from oots_lib.models.Person import Person, get_person_from_redis
|
|
21
|
+
from oots_lib.models.RequestEDM import get_edm_request_from_redis
|
|
22
|
+
from oots_lib.models.ResponseEvidences import (
|
|
23
|
+
Classification,
|
|
24
|
+
Evidences,
|
|
25
|
+
ExtrinsicObjectType,
|
|
26
|
+
RegistryPackageType,
|
|
27
|
+
RepositoryItemRef,
|
|
28
|
+
save_evidences_to_redis,
|
|
29
|
+
)
|
|
30
|
+
from oots_lib.redis_keys import Keys
|
|
31
|
+
|
|
32
|
+
_logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
KEYS = Keys()
|
|
35
|
+
QUEUE_OUTCOMING = import_env("QUEUE_OUTCOMING")
|
|
36
|
+
IF_PREVIEW: bool = import_env("IF_PREVIEW").lower() == 'true'
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class MakeEvidence:
|
|
40
|
+
ISSUING_AUTHORITY_ID = ""
|
|
41
|
+
ISSUING_AUTHORITY_SCHEME = ""
|
|
42
|
+
ISSUING_AUTHORITY_NAME = ""
|
|
43
|
+
CONFORMANT_TO_URL = ''
|
|
44
|
+
|
|
45
|
+
def __init__(self, message_id: str, redis: Redis):
|
|
46
|
+
super().__init__()
|
|
47
|
+
|
|
48
|
+
self.message_id = message_id
|
|
49
|
+
self.redis = redis
|
|
50
|
+
|
|
51
|
+
self.person: Person | None = None
|
|
52
|
+
self.request: Parsing | None = None
|
|
53
|
+
self.as4: dict | None | None = None
|
|
54
|
+
|
|
55
|
+
self.log: ToLogger | None | None = None
|
|
56
|
+
self.evidence: Evidences | None = None
|
|
57
|
+
self._request_content_type: str | None = None
|
|
58
|
+
|
|
59
|
+
self.request_to_service: dict | None = None
|
|
60
|
+
|
|
61
|
+
self.response_from_service = None
|
|
62
|
+
self.data: MainBase | None = None
|
|
63
|
+
self.title = ""
|
|
64
|
+
self.description: list[str] = []
|
|
65
|
+
|
|
66
|
+
def _edm_exception(self, code: str, message: str, detail: str) -> EDMException:
|
|
67
|
+
"""Створює EDMException з контекстом цього повідомлення."""
|
|
68
|
+
return EDMException(
|
|
69
|
+
redis=self.redis,
|
|
70
|
+
queue=None,
|
|
71
|
+
key=None,
|
|
72
|
+
message_id=str(self.message_id),
|
|
73
|
+
code=code,
|
|
74
|
+
message=message,
|
|
75
|
+
detail=detail,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def if_preview(self):
|
|
80
|
+
_logger.debug(f"Перевірка прапора preview для повідомлення {self.message_id}")
|
|
81
|
+
|
|
82
|
+
if self.request is None:
|
|
83
|
+
raise ValueError(
|
|
84
|
+
f"Запит для повідомлення {self.message_id} не зчитаний: спершу викличте read_data()"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
request: dict = self.request.serialize()
|
|
88
|
+
preview: bool = deep_get(request, 'doc', 'PossibilityForPreview', default=False)
|
|
89
|
+
|
|
90
|
+
_logger.debug(f"Значення прапора preview у запиті: {preview}")
|
|
91
|
+
_logger.debug(f"Прапор preview встановлено, використовуємо глобальне налаштування IF_PREVIEW: {IF_PREVIEW}")
|
|
92
|
+
|
|
93
|
+
pr = False if not preview and not IF_PREVIEW else True
|
|
94
|
+
|
|
95
|
+
_logger.debug(f"Результат перевірки прапора preview для повідомлення {self.message_id}: {pr}")
|
|
96
|
+
return pr
|
|
97
|
+
|
|
98
|
+
async def read_data(self):
|
|
99
|
+
"""Зчитує вхідні дані повідомлення з Redis.
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
EDMException: Якщо запит EDM або дані особи відсутні у Redis
|
|
103
|
+
"""
|
|
104
|
+
request_key = KEYS.get_request_edm(self.message_id)
|
|
105
|
+
request = await get_edm_request_from_redis(self.redis, request_key)
|
|
106
|
+
if request is None:
|
|
107
|
+
raise self._not_found(
|
|
108
|
+
message="Запит EDM не знайдено",
|
|
109
|
+
detail=f"У Redis відсутній запит EDM за ключем {request_key}",
|
|
110
|
+
)
|
|
111
|
+
self.request = Parsing(request.content)
|
|
112
|
+
|
|
113
|
+
person_key = KEYS.get_request_person(self.message_id)
|
|
114
|
+
self.person = await get_person_from_redis(self.redis, person_key)
|
|
115
|
+
if self.person is None:
|
|
116
|
+
raise self._not_found(
|
|
117
|
+
message="Інформацію про людину не знайдено",
|
|
118
|
+
detail=f"У Redis відсутні дані особи за ключем {person_key}",
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
self.as4 = await self.redis.get_from_redis(KEYS.get_request_as4(self.message_id))
|
|
122
|
+
|
|
123
|
+
def _not_found(self, message: str, detail: str) -> EDMException:
|
|
124
|
+
return self._edm_exception(
|
|
125
|
+
code="EDM:ERR:0004", message=message, detail=detail
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
async def load_data_to_redis(self):
|
|
129
|
+
"""
|
|
130
|
+
Зберігає сформовані докази до Redis у форматі Evidences.
|
|
131
|
+
|
|
132
|
+
Після збереження перевіряє preview-прапор через get_flag()
|
|
133
|
+
і за потреби відправляє повідомлення на обробку.
|
|
134
|
+
"""
|
|
135
|
+
_logger.info(f"Збереження набору доказів для повідомлення {self.message_id}")
|
|
136
|
+
|
|
137
|
+
if is_dataclass(self.evidence):
|
|
138
|
+
await save_evidences_to_redis(self.redis, KEYS.get_response_evidence(self.message_id), self.evidence)
|
|
139
|
+
else:
|
|
140
|
+
raise TypeError(f"Очікувався Evidences, отримано {type(self.evidence)}")
|
|
141
|
+
|
|
142
|
+
# Перевіряємо, чи потрібно відправляти на обробку
|
|
143
|
+
if not self.if_preview:
|
|
144
|
+
_logger.info(f"Відправляємо повідомлення {self.message_id} до черги обробки")
|
|
145
|
+
await self.redis.push_to_queue(QUEUE_OUTCOMING, f"{self.message_id}")
|
|
146
|
+
else:
|
|
147
|
+
_logger.debug(f"Повідомлення {self.message_id} потребує preview, обробка відкладена")
|
|
148
|
+
|
|
149
|
+
def generate_metadata(self, main_evidence: bool = True):
|
|
150
|
+
if self.person is None:
|
|
151
|
+
raise ValueError(
|
|
152
|
+
f"Дані особи для повідомлення {self.message_id} не зчитані: спершу викличте read_data()"
|
|
153
|
+
)
|
|
154
|
+
person_tree = self.person.xml_tree
|
|
155
|
+
if person_tree is None:
|
|
156
|
+
raise ValueError(
|
|
157
|
+
f"XML особи для повідомлення {self.message_id} не сформовано"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
distribution = Distribution(self.request_content_type)
|
|
161
|
+
conformantTo = IsConformantTo(self.CONFORMANT_TO_URL) # NOSONAR
|
|
162
|
+
usingAuthority = IssuingAuthority(self.ISSUING_AUTHORITY_SCHEME, self.ISSUING_AUTHORITY_ID) # NOSONAR
|
|
163
|
+
usingAuthority.name(lang='UA', name=self.ISSUING_AUTHORITY_NAME)
|
|
164
|
+
about = IsAbout(person_tree)
|
|
165
|
+
|
|
166
|
+
metadata = EMetadata()
|
|
167
|
+
|
|
168
|
+
if main_evidence:
|
|
169
|
+
about_tree = about.xml_tree
|
|
170
|
+
conformant_tree = conformantTo.xml_tree
|
|
171
|
+
distribution_tree = distribution.xml_tree
|
|
172
|
+
authority_tree = usingAuthority.xml_tree
|
|
173
|
+
assert about_tree is not None
|
|
174
|
+
assert conformant_tree is not None
|
|
175
|
+
assert distribution_tree is not None
|
|
176
|
+
assert authority_tree is not None
|
|
177
|
+
metadata.isAbout(about_tree)
|
|
178
|
+
metadata.isConformeant(conformant_tree)
|
|
179
|
+
metadata.distribution(distribution_tree)
|
|
180
|
+
metadata.issuingAuthority(authority_tree)
|
|
181
|
+
else:
|
|
182
|
+
...
|
|
183
|
+
|
|
184
|
+
return metadata.xml_string
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def request_content_type(self):
|
|
188
|
+
if self.request is None:
|
|
189
|
+
raise ValueError("Запит не ініціалізовано")
|
|
190
|
+
if self._request_content_type is None:
|
|
191
|
+
self._request_content_type = deep_get(
|
|
192
|
+
self.request.serialize(any_type=True),
|
|
193
|
+
'query',
|
|
194
|
+
'EvidenceRequest',
|
|
195
|
+
'sdg:DataServiceEvidenceType',
|
|
196
|
+
'sdg:DistributedAs',
|
|
197
|
+
'sdg:Format'
|
|
198
|
+
)
|
|
199
|
+
return self._request_content_type
|
|
200
|
+
|
|
201
|
+
async def transform_data(self):
|
|
202
|
+
|
|
203
|
+
if self.data is None:
|
|
204
|
+
raise ValueError(
|
|
205
|
+
f"Джерело даних для повідомлення {self.message_id} не встановлено: "
|
|
206
|
+
"задайте self.data перед transform_data()"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
documents = await self.data.generate_data()
|
|
211
|
+
except Exception as e:
|
|
212
|
+
raise self._not_found(
|
|
213
|
+
message="Інформацію про людину не знайдено",
|
|
214
|
+
detail=str(e),
|
|
215
|
+
) from e
|
|
216
|
+
|
|
217
|
+
content_type = self.request_content_type
|
|
218
|
+
_logger.info(f"Тип контенту: {content_type}")
|
|
219
|
+
|
|
220
|
+
if not content_type:
|
|
221
|
+
_logger.error("Тип контенту у запиті не вказаний")
|
|
222
|
+
raise self._edm_exception(
|
|
223
|
+
code="EDM:ERR:0003",
|
|
224
|
+
message="Тип контенту у запиті не вказаний",
|
|
225
|
+
detail="У запиті відсутній sdg:DistributedAs/sdg:Format",
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
evidences = []
|
|
229
|
+
|
|
230
|
+
for doc in documents:
|
|
231
|
+
|
|
232
|
+
contents = []
|
|
233
|
+
extrinsic = ExtrinsicObjectType(
|
|
234
|
+
classification=Classification(classificationNode='MainEvidence'),
|
|
235
|
+
EvidenceMetadata=self.generate_metadata(main_evidence=True),
|
|
236
|
+
RepositoryItemRef=RepositoryItemRef(title="Cerificate of Marriage"),
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
if 'pdf' in content_type:
|
|
240
|
+
_logger.debug("Перетворення документу у PDF")
|
|
241
|
+
pdf = doc.get_pdf()
|
|
242
|
+
extrinsic.content_type = "application/pdf"
|
|
243
|
+
extrinsic.encoding = 'base64'
|
|
244
|
+
extrinsic.content = base64.b64encode(pdf).decode("utf-8")
|
|
245
|
+
contents.append(extrinsic)
|
|
246
|
+
|
|
247
|
+
elif 'xml' in content_type:
|
|
248
|
+
_logger.debug("Перетворення документу у XML")
|
|
249
|
+
extrinsic.content_type = "application/xml"
|
|
250
|
+
extrinsic.content = doc.get_xml()
|
|
251
|
+
contents.append(extrinsic)
|
|
252
|
+
|
|
253
|
+
elif 'json' in content_type:
|
|
254
|
+
_logger.debug("Перетворення документу у JSON")
|
|
255
|
+
extrinsic.content_type = "application/json"
|
|
256
|
+
extrinsic.content = doc.get_json()
|
|
257
|
+
contents.append(extrinsic)
|
|
258
|
+
|
|
259
|
+
else:
|
|
260
|
+
_logger.error(f"Невідомий тип контенту: {content_type}")
|
|
261
|
+
raise self._edm_exception(
|
|
262
|
+
code="EDM:ERR:0006",
|
|
263
|
+
message="Невідомий тип контенту",
|
|
264
|
+
detail=f"Невідомий тип контенту: {content_type}",
|
|
265
|
+
)
|
|
266
|
+
_logger.debug(f"Створено ExtrinsicObjectType: {extrinsic}")
|
|
267
|
+
# додати інші представлення до contents
|
|
268
|
+
...
|
|
269
|
+
_logger.debug('Додали всі представлення доказу')
|
|
270
|
+
|
|
271
|
+
_logger.debug(f"Створюємо RegistryPackageType для документа: {doc}")
|
|
272
|
+
evidences.append(RegistryPackageType(RegistryPackage=contents))
|
|
273
|
+
|
|
274
|
+
_logger.debug(f"Створено всі RegistryPackageType для документів, кількість: {len(evidences)}")
|
|
275
|
+
evidence = Evidences(
|
|
276
|
+
title=self.title,
|
|
277
|
+
PreviewDescription=self.description,
|
|
278
|
+
preview=self.if_preview,
|
|
279
|
+
evidences=evidences,
|
|
280
|
+
)
|
|
281
|
+
_logger.debug(f'Створили Evidence: {evidence}')
|
|
282
|
+
self.evidence = evidence
|