pyxroad 1.5.8__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.
- pyxroad-1.5.8/PKG-INFO +171 -0
- pyxroad-1.5.8/README.md +136 -0
- pyxroad-1.5.8/XRoad/Members.py +104 -0
- pyxroad-1.5.8/XRoad/__init__.py +17 -0
- pyxroad-1.5.8/XRoad/cache.py +90 -0
- pyxroad-1.5.8/XRoad/client.py +185 -0
- pyxroad-1.5.8/XRoad/plugins.py +55 -0
- pyxroad-1.5.8/XRoad/transport.py +31 -0
- pyxroad-1.5.8/pyproject.toml +66 -0
- pyxroad-1.5.8/pyxroad.egg-info/PKG-INFO +171 -0
- pyxroad-1.5.8/pyxroad.egg-info/SOURCES.txt +14 -0
- pyxroad-1.5.8/pyxroad.egg-info/dependency_links.txt +1 -0
- pyxroad-1.5.8/pyxroad.egg-info/requires.txt +15 -0
- pyxroad-1.5.8/pyxroad.egg-info/top_level.txt +1 -0
- pyxroad-1.5.8/setup.cfg +4 -0
- pyxroad-1.5.8/tests/test_members.py +80 -0
pyxroad-1.5.8/PKG-INFO
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyxroad
|
|
3
|
+
Version: 1.5.8
|
|
4
|
+
Summary: X-Road (Trembita) Python client library for SOAP-based service integration
|
|
5
|
+
Author-email: Andrii Shapovalov <mt.andrey@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/AndreyShapovalovVN/pyxroad
|
|
8
|
+
Project-URL: Repository, https://github.com/AndreyShapovalovVN/pyxroad.git
|
|
9
|
+
Project-URL: Documentation, https://trembita.gov.ua
|
|
10
|
+
Keywords: x-road,xroad,trembita,soap,python
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Classifier: Topic :: System :: Networking
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: lxml>=5.2.1
|
|
23
|
+
Requires-Dist: Requests>=2.32.3
|
|
24
|
+
Requires-Dist: setuptools>=70.0.0
|
|
25
|
+
Requires-Dist: zeep>=4.0.0
|
|
26
|
+
Requires-Dist: redis
|
|
27
|
+
Provides-Extra: redis
|
|
28
|
+
Requires-Dist: redis>=4.5.0; extra == "redis"
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
33
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: flake8>=6.0.0; extra == "dev"
|
|
35
|
+
|
|
36
|
+
# X-Road (Trembita) Python Client
|
|
37
|
+
|
|
38
|
+
- **Version:** 1.5.6
|
|
39
|
+
- **Web:** https://trembita.gov.ua
|
|
40
|
+
- **Repository:** https://github.com/AndreyShapovalovVN/pyxroad
|
|
41
|
+
- **Keywords:** x-road, xroad, trembita, python, soap
|
|
42
|
+
|
|
43
|
+
A powerful Python client library for interacting with X-Road (Trembita) security servers.
|
|
44
|
+
This library provides a convenient wrapper around the SOAP-based X-Road protocol, allowing
|
|
45
|
+
developers to easily integrate X-Road services into their Python applications.
|
|
46
|
+
|
|
47
|
+
## Supported Python Versions
|
|
48
|
+
|
|
49
|
+
- Python 3.10+
|
|
50
|
+
- Python 3.11+
|
|
51
|
+
- Python 3.12+
|
|
52
|
+
|
|
53
|
+
## Features
|
|
54
|
+
|
|
55
|
+
- Easy-to-use SOAP client for X-Road services
|
|
56
|
+
- Support for multiple cache backends (SQLite, Redis, In-Memory)
|
|
57
|
+
- Automatic SOAP header management
|
|
58
|
+
- Transaction ID tracking
|
|
59
|
+
- Configurable logging
|
|
60
|
+
- Type hints for better IDE support
|
|
61
|
+
|
|
62
|
+
## Installation from GitHub
|
|
63
|
+
|
|
64
|
+
Using pip:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install git+https://github.com/AndreyShapovalovVN/pyxroad.git#egg=pyxroad
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Or clone and install locally:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
git clone https://github.com/AndreyShapovalovVN/pyxroad.git
|
|
74
|
+
cd pyxroad
|
|
75
|
+
pip install -e .
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Requirements
|
|
79
|
+
|
|
80
|
+
- lxml >= 5.2.1
|
|
81
|
+
- Requests >= 2.32.3
|
|
82
|
+
- setuptools >= 68.1.2
|
|
83
|
+
- zeep >= 4.0.0
|
|
84
|
+
- redis (optional, for Redis cache support)
|
|
85
|
+
|
|
86
|
+
## Code Quality Checks
|
|
87
|
+
|
|
88
|
+
Run the same checks locally as in CI:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
ruff check . --extend-exclude 'tests/~*.py'
|
|
92
|
+
mypy . --ignore-missing-imports --pretty --show-error-codes --exclude 'tests/~.*\.py$'
|
|
93
|
+
pytest --maxfail=2 --disable-warnings --ignore-glob='tests/~*.py'
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Quick Start
|
|
97
|
+
|
|
98
|
+
Basic usage example:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from XRoad import XClient, Transport, SqliteCache
|
|
102
|
+
import logging
|
|
103
|
+
import sys
|
|
104
|
+
|
|
105
|
+
# Configure logging
|
|
106
|
+
logging.basicConfig(
|
|
107
|
+
stream=sys.stdout,
|
|
108
|
+
level=logging.INFO,
|
|
109
|
+
format='%(asctime)s - %(levelname)s - %(name)s - %(message)s'
|
|
110
|
+
)
|
|
111
|
+
_logger = logging.getLogger('XRoad')
|
|
112
|
+
|
|
113
|
+
# Create a client instance
|
|
114
|
+
client = XClient(
|
|
115
|
+
ssu="http://security-server:8080",
|
|
116
|
+
client='SEVDEIR-TEST/GOV/00013480/100001',
|
|
117
|
+
service='SEVDEIR-TEST/GOV/00032684/MIA_prod/CheckPassportStatus/v0.1'
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Make a service request
|
|
121
|
+
try:
|
|
122
|
+
response = client.request(
|
|
123
|
+
xroad_id='ABCD123456', # Optional: set custom request ID
|
|
124
|
+
PasNumber='AA123456',
|
|
125
|
+
PasSerial='654321'
|
|
126
|
+
)
|
|
127
|
+
_logger.info(f"Response: {response}")
|
|
128
|
+
except Exception as err:
|
|
129
|
+
_logger.error(f"Error: {err}")
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Advanced Usage
|
|
133
|
+
|
|
134
|
+
Using custom caching backend:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from XRoad import XClient, Transport, RedisCache
|
|
138
|
+
|
|
139
|
+
# With Redis cache
|
|
140
|
+
redis_cache = RedisCache(path='redis://localhost:6379/0', timeout=3600)
|
|
141
|
+
transport = Transport(cache=redis_cache)
|
|
142
|
+
|
|
143
|
+
client = XClient(
|
|
144
|
+
ssu="http://security-server:8080",
|
|
145
|
+
client='SEVDEIR-TEST/GOV/00013480/100001',
|
|
146
|
+
service='SEVDEIR-TEST/GOV/00032684/MIA_prod/CheckPassportStatus/v0.1',
|
|
147
|
+
transport=transport
|
|
148
|
+
)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Setting custom headers:
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
client.userId = '0123456789' # Custom user ID
|
|
155
|
+
client.id = 'ABCD123456' # Custom request ID
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Available Cache Types
|
|
159
|
+
|
|
160
|
+
- **InMemoryCache**: Default, stores cache in application memory
|
|
161
|
+
- **SqliteCache**: Persistent cache using SQLite database
|
|
162
|
+
- **RedisCache**: Distributed cache using Redis
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
|
|
166
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
167
|
+
|
|
168
|
+
## Support
|
|
169
|
+
|
|
170
|
+
For issues, questions, and contributions, please visit:
|
|
171
|
+
https://github.com/AndreyShapovalovVN/pyxroad
|
pyxroad-1.5.8/README.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# X-Road (Trembita) Python Client
|
|
2
|
+
|
|
3
|
+
- **Version:** 1.5.6
|
|
4
|
+
- **Web:** https://trembita.gov.ua
|
|
5
|
+
- **Repository:** https://github.com/AndreyShapovalovVN/pyxroad
|
|
6
|
+
- **Keywords:** x-road, xroad, trembita, python, soap
|
|
7
|
+
|
|
8
|
+
A powerful Python client library for interacting with X-Road (Trembita) security servers.
|
|
9
|
+
This library provides a convenient wrapper around the SOAP-based X-Road protocol, allowing
|
|
10
|
+
developers to easily integrate X-Road services into their Python applications.
|
|
11
|
+
|
|
12
|
+
## Supported Python Versions
|
|
13
|
+
|
|
14
|
+
- Python 3.10+
|
|
15
|
+
- Python 3.11+
|
|
16
|
+
- Python 3.12+
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- Easy-to-use SOAP client for X-Road services
|
|
21
|
+
- Support for multiple cache backends (SQLite, Redis, In-Memory)
|
|
22
|
+
- Automatic SOAP header management
|
|
23
|
+
- Transaction ID tracking
|
|
24
|
+
- Configurable logging
|
|
25
|
+
- Type hints for better IDE support
|
|
26
|
+
|
|
27
|
+
## Installation from GitHub
|
|
28
|
+
|
|
29
|
+
Using pip:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install git+https://github.com/AndreyShapovalovVN/pyxroad.git#egg=pyxroad
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Or clone and install locally:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
git clone https://github.com/AndreyShapovalovVN/pyxroad.git
|
|
39
|
+
cd pyxroad
|
|
40
|
+
pip install -e .
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Requirements
|
|
44
|
+
|
|
45
|
+
- lxml >= 5.2.1
|
|
46
|
+
- Requests >= 2.32.3
|
|
47
|
+
- setuptools >= 68.1.2
|
|
48
|
+
- zeep >= 4.0.0
|
|
49
|
+
- redis (optional, for Redis cache support)
|
|
50
|
+
|
|
51
|
+
## Code Quality Checks
|
|
52
|
+
|
|
53
|
+
Run the same checks locally as in CI:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
ruff check . --extend-exclude 'tests/~*.py'
|
|
57
|
+
mypy . --ignore-missing-imports --pretty --show-error-codes --exclude 'tests/~.*\.py$'
|
|
58
|
+
pytest --maxfail=2 --disable-warnings --ignore-glob='tests/~*.py'
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Quick Start
|
|
62
|
+
|
|
63
|
+
Basic usage example:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from XRoad import XClient, Transport, SqliteCache
|
|
67
|
+
import logging
|
|
68
|
+
import sys
|
|
69
|
+
|
|
70
|
+
# Configure logging
|
|
71
|
+
logging.basicConfig(
|
|
72
|
+
stream=sys.stdout,
|
|
73
|
+
level=logging.INFO,
|
|
74
|
+
format='%(asctime)s - %(levelname)s - %(name)s - %(message)s'
|
|
75
|
+
)
|
|
76
|
+
_logger = logging.getLogger('XRoad')
|
|
77
|
+
|
|
78
|
+
# Create a client instance
|
|
79
|
+
client = XClient(
|
|
80
|
+
ssu="http://security-server:8080",
|
|
81
|
+
client='SEVDEIR-TEST/GOV/00013480/100001',
|
|
82
|
+
service='SEVDEIR-TEST/GOV/00032684/MIA_prod/CheckPassportStatus/v0.1'
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Make a service request
|
|
86
|
+
try:
|
|
87
|
+
response = client.request(
|
|
88
|
+
xroad_id='ABCD123456', # Optional: set custom request ID
|
|
89
|
+
PasNumber='AA123456',
|
|
90
|
+
PasSerial='654321'
|
|
91
|
+
)
|
|
92
|
+
_logger.info(f"Response: {response}")
|
|
93
|
+
except Exception as err:
|
|
94
|
+
_logger.error(f"Error: {err}")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Advanced Usage
|
|
98
|
+
|
|
99
|
+
Using custom caching backend:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from XRoad import XClient, Transport, RedisCache
|
|
103
|
+
|
|
104
|
+
# With Redis cache
|
|
105
|
+
redis_cache = RedisCache(path='redis://localhost:6379/0', timeout=3600)
|
|
106
|
+
transport = Transport(cache=redis_cache)
|
|
107
|
+
|
|
108
|
+
client = XClient(
|
|
109
|
+
ssu="http://security-server:8080",
|
|
110
|
+
client='SEVDEIR-TEST/GOV/00013480/100001',
|
|
111
|
+
service='SEVDEIR-TEST/GOV/00032684/MIA_prod/CheckPassportStatus/v0.1',
|
|
112
|
+
transport=transport
|
|
113
|
+
)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Setting custom headers:
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
client.userId = '0123456789' # Custom user ID
|
|
120
|
+
client.id = 'ABCD123456' # Custom request ID
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Available Cache Types
|
|
124
|
+
|
|
125
|
+
- **InMemoryCache**: Default, stores cache in application memory
|
|
126
|
+
- **SqliteCache**: Persistent cache using SQLite database
|
|
127
|
+
- **RedisCache**: Distributed cache using Redis
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
132
|
+
|
|
133
|
+
## Support
|
|
134
|
+
|
|
135
|
+
For issues, questions, and contributions, please visit:
|
|
136
|
+
https://github.com/AndreyShapovalovVN/pyxroad
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
_logger = logging.getLogger(__name__)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class Members:
|
|
9
|
+
"""
|
|
10
|
+
This class is used to parse the memberPath and create a dictionary with the member details.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
objectType: str
|
|
14
|
+
memberPath: str
|
|
15
|
+
xRoadInstance: str | None = None
|
|
16
|
+
memberClass: str | None = None
|
|
17
|
+
memberCode: str | None = None
|
|
18
|
+
subsystemCode: str | None = None
|
|
19
|
+
serviceCode: str | None = None
|
|
20
|
+
serviceVersion: str | None = None
|
|
21
|
+
|
|
22
|
+
def __post_init__(self):
|
|
23
|
+
"""
|
|
24
|
+
This method is used to parse the memberPath and assign the values to the class variables.
|
|
25
|
+
:return:
|
|
26
|
+
"""
|
|
27
|
+
_logger.debug(f"Members init: {self.memberPath}")
|
|
28
|
+
if self.memberPath:
|
|
29
|
+
for iteration, val in enumerate(self.memberPath.split("/")):
|
|
30
|
+
match iteration:
|
|
31
|
+
case 0:
|
|
32
|
+
self.xRoadInstance = val
|
|
33
|
+
case 1:
|
|
34
|
+
self.memberClass = val
|
|
35
|
+
case 2:
|
|
36
|
+
self.memberCode = val
|
|
37
|
+
case 3:
|
|
38
|
+
self.subsystemCode = val
|
|
39
|
+
case 4:
|
|
40
|
+
self.serviceCode = val
|
|
41
|
+
case 5:
|
|
42
|
+
self.serviceVersion = val
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def wsdl_path(self) -> str:
|
|
46
|
+
"""
|
|
47
|
+
This method returns the wsdl path for the service.
|
|
48
|
+
:return: String
|
|
49
|
+
"""
|
|
50
|
+
if "SERVICE" in self.objectType:
|
|
51
|
+
_path = []
|
|
52
|
+
for key, value in self.member_dict.items():
|
|
53
|
+
if not value:
|
|
54
|
+
_logger.debug(f"Skipping {key} as value is None")
|
|
55
|
+
continue
|
|
56
|
+
if key in ("objectType", "memberPath"):
|
|
57
|
+
_logger.debug(f"Skipping {key}")
|
|
58
|
+
continue
|
|
59
|
+
if value is None:
|
|
60
|
+
_logger.debug(f"Skipping {key}")
|
|
61
|
+
continue
|
|
62
|
+
if "serviceVersion" in key:
|
|
63
|
+
_logger.debug(f"Adding version={value}")
|
|
64
|
+
_path.append(f"version={value}")
|
|
65
|
+
continue
|
|
66
|
+
_logger.debug(f"Adding {key}={value}")
|
|
67
|
+
_path.append(f"{key}={value}")
|
|
68
|
+
_logger.info(f"wsdl path: {_path[:-1]}")
|
|
69
|
+
return "&".join(_path)
|
|
70
|
+
raise ValueError("wsdl_path is only available for SERVICE objectType")
|
|
71
|
+
|
|
72
|
+
def wsdl_url(self, ssu: str) -> str:
|
|
73
|
+
"""
|
|
74
|
+
This method returns the wsdl url for the service.
|
|
75
|
+
:param: Ssu - url to securyt server
|
|
76
|
+
:return: String
|
|
77
|
+
"""
|
|
78
|
+
_logger.info(f"Generating wsdl url for {ssu}")
|
|
79
|
+
return f"{ssu}/wsdl?{self.wsdl_path}"
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def member_dict(self) -> dict[str, str]:
|
|
83
|
+
"""
|
|
84
|
+
This method returns the member details in a dictionary format.
|
|
85
|
+
:return: Dict
|
|
86
|
+
"""
|
|
87
|
+
_logger.info("Returning member details as dictionary")
|
|
88
|
+
r = {}
|
|
89
|
+
if self.xRoadInstance:
|
|
90
|
+
r["xRoadInstance"] = self.xRoadInstance
|
|
91
|
+
if self.memberClass:
|
|
92
|
+
r["memberClass"] = self.memberClass
|
|
93
|
+
if self.memberCode:
|
|
94
|
+
r["memberCode"] = self.memberCode
|
|
95
|
+
if self.subsystemCode:
|
|
96
|
+
r["subsystemCode"] = self.subsystemCode
|
|
97
|
+
if self.serviceCode:
|
|
98
|
+
r["serviceCode"] = self.serviceCode
|
|
99
|
+
if self.serviceVersion:
|
|
100
|
+
r["serviceVersion"] = self.serviceVersion
|
|
101
|
+
if self.objectType:
|
|
102
|
+
r["objectType"] = self.objectType
|
|
103
|
+
|
|
104
|
+
return r
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .client import XClient
|
|
2
|
+
from .transport import DRACTransport
|
|
3
|
+
from zeep.cache import SqliteCache, InMemoryCache
|
|
4
|
+
from zeep.transports import Transport
|
|
5
|
+
from .cache import RedisCache
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"XClient",
|
|
9
|
+
"Transport",
|
|
10
|
+
"DRACTransport",
|
|
11
|
+
"RedisCache",
|
|
12
|
+
"SqliteCache",
|
|
13
|
+
"InMemoryCache",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
__author__ = "Andrii Shapovalov"
|
|
17
|
+
__email__ = "mt.andrey@gmail.com"
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import logging
|
|
3
|
+
from typing import cast
|
|
4
|
+
|
|
5
|
+
from redis import Redis
|
|
6
|
+
from zeep.cache import Base
|
|
7
|
+
|
|
8
|
+
_logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RedisCache(Base):
|
|
12
|
+
"""
|
|
13
|
+
Provides a caching mechanism backed by Redis.
|
|
14
|
+
|
|
15
|
+
This class is designed for caching purposes, used Redis as the underlying
|
|
16
|
+
storage. It supports caching content with a specified time-to-live (TTL) and
|
|
17
|
+
identifying cached data through unique keys derived from URLs. The cache can
|
|
18
|
+
accommodate both storing and retrieving cached data, making it suitable for
|
|
19
|
+
applications requiring temporary storage of frequently accessed resources.
|
|
20
|
+
|
|
21
|
+
:ivar redis_client: A Redis client instance is used to interact with Redis.
|
|
22
|
+
:type redis_client: Redis
|
|
23
|
+
:ivar ttl: The time-to-live (in seconds) for cached content.
|
|
24
|
+
:type ttl: Int
|
|
25
|
+
:ivar prefix: The prefix used to generate cache keys.
|
|
26
|
+
:type prefix: String
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
_version = "1"
|
|
30
|
+
|
|
31
|
+
def __init__(self, path: str | Redis | None = None, timeout: int = 3600):
|
|
32
|
+
"""
|
|
33
|
+
Initializes a cache object for managing data with Redis as a backend.
|
|
34
|
+
|
|
35
|
+
:param path: A Redis connection URL as a string or an instance of the Redis client
|
|
36
|
+
object. If none is provided, a TypeError is raised.
|
|
37
|
+
:param timeout: The time-to-live (TTL) for the cached data, specified in seconds.
|
|
38
|
+
Defaults to 3600 seconds (1 hour).
|
|
39
|
+
|
|
40
|
+
:raises TypeError: If `path` is not a string or an instance of Redis.
|
|
41
|
+
"""
|
|
42
|
+
if isinstance(path, str):
|
|
43
|
+
self.redis_client = Redis.from_url(path)
|
|
44
|
+
elif isinstance(path, Redis):
|
|
45
|
+
self.redis_client = path
|
|
46
|
+
else:
|
|
47
|
+
raise TypeError("path must be str or Redis")
|
|
48
|
+
self.ttl = timeout
|
|
49
|
+
self.prefix = "zeep:cache:"
|
|
50
|
+
|
|
51
|
+
def _key(self, url: str) -> str:
|
|
52
|
+
digest = hashlib.sha256(url.encode("utf-8")).hexdigest()
|
|
53
|
+
return f"{self.prefix}{digest}"
|
|
54
|
+
|
|
55
|
+
def add(self, url: str, content: bytes):
|
|
56
|
+
"""
|
|
57
|
+
Adds content to the cache associated with a specific URL.
|
|
58
|
+
|
|
59
|
+
This function caches the provided content using the URL as a key. The content is
|
|
60
|
+
stored in the cache for the configured time-to-live (TTL) period. The key used for
|
|
61
|
+
caching is generated from the URL, which ensures that the content is identifiable
|
|
62
|
+
and retrievable.
|
|
63
|
+
|
|
64
|
+
:param url: The URL to serve as the key for caching. It must be a string.
|
|
65
|
+
:param content: The content to cache, provided as a sequence of bytes.
|
|
66
|
+
:return: None
|
|
67
|
+
"""
|
|
68
|
+
_logger.debug("Caching contents of %s", url)
|
|
69
|
+
key = self._key(url)
|
|
70
|
+
self.redis_client.set(key, content, ex=self.ttl)
|
|
71
|
+
|
|
72
|
+
def get(self, url: str) -> bytes | None:
|
|
73
|
+
"""
|
|
74
|
+
Retrieve content from a cache based on the provided URL. If the content is
|
|
75
|
+
found in the cache (cache hit), it is returned. Otherwise, it logs a cache
|
|
76
|
+
miss and returns None.
|
|
77
|
+
|
|
78
|
+
:param url: The URL used to retrieve the cached content.
|
|
79
|
+
:type url: String
|
|
80
|
+
:return: The cached content as bytes if the key exists in the cache; None
|
|
81
|
+
otherwise.
|
|
82
|
+
:rtype: Bytes or None
|
|
83
|
+
"""
|
|
84
|
+
key = self._key(url)
|
|
85
|
+
content = cast(bytes | None, self.redis_client.get(key))
|
|
86
|
+
if content:
|
|
87
|
+
_logger.debug("Cache HIT for %s", url)
|
|
88
|
+
return content
|
|
89
|
+
_logger.debug("Cache MISS for %s", url)
|
|
90
|
+
return None
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import uuid
|
|
5
|
+
|
|
6
|
+
from zeep import Client
|
|
7
|
+
from zeep.cache import InMemoryCache
|
|
8
|
+
from zeep.exceptions import Fault
|
|
9
|
+
from zeep.helpers import serialize_object
|
|
10
|
+
from zeep.transports import Transport
|
|
11
|
+
|
|
12
|
+
from .Members import Members
|
|
13
|
+
|
|
14
|
+
_logger = logging.getLogger("XRoad")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class XClient(Client):
|
|
18
|
+
"""
|
|
19
|
+
A specialized client class for interacting with X-Road services.
|
|
20
|
+
|
|
21
|
+
This class extends the base Client class and provides a mechanism to interact
|
|
22
|
+
with X-Road systems by implementing SOAP-based service requests. It also manages
|
|
23
|
+
the construction of appropriate SOAP headers and using default namespaces
|
|
24
|
+
required by the X-Road environment. The class is capable of handling session
|
|
25
|
+
setup, ensuring proper initialization of member and service objects, and
|
|
26
|
+
handling request serialization.
|
|
27
|
+
|
|
28
|
+
:ivar response: Holds the response of the latest SOAP request.
|
|
29
|
+
:type response: Any
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
_version = 4.0
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self, ssu: str, client: str, service: str, transport: object | None = None, *args, **kwargs
|
|
36
|
+
):
|
|
37
|
+
"""
|
|
38
|
+
Initializes an instance of the class, configuring service and client details along with
|
|
39
|
+
SOAP header and transport setup. Validates the presence of required parameters and sets
|
|
40
|
+
default SOAP headers used for communication.
|
|
41
|
+
|
|
42
|
+
:param ssu: The subsystem uniform resource identifier or endpoint to connect.
|
|
43
|
+
:type ssu: String
|
|
44
|
+
:param client: The client subsystem identifier in the X-Road ecosystem.
|
|
45
|
+
:type client: String
|
|
46
|
+
:param service: The service identifier within the X-Road ecosystem to be accessed.
|
|
47
|
+
:type service: String
|
|
48
|
+
:param transport: Optional transport object for handling HTTP requests. Defaults
|
|
49
|
+
to an instance of `Transport` with in-memory caching if not provided.
|
|
50
|
+
:type transport: Object | None
|
|
51
|
+
:param args: Additional positional arguments to be passed to the parent class
|
|
52
|
+
initializer.
|
|
53
|
+
:param kwargs: Additional keyword arguments to be passed to the parent class
|
|
54
|
+
initializer.
|
|
55
|
+
|
|
56
|
+
:raises ValueError: If the `service` parameter is not provided.
|
|
57
|
+
:raises ValueError: If the `client` parameter is not provided.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
self.response = None
|
|
61
|
+
|
|
62
|
+
if not service:
|
|
63
|
+
raise ValueError("service - required")
|
|
64
|
+
if not client:
|
|
65
|
+
raise ValueError("client - required")
|
|
66
|
+
|
|
67
|
+
client_member = Members(objectType="SUBSYSTEM", memberPath=client)
|
|
68
|
+
service_member = Members(objectType="SERVICE", memberPath=service)
|
|
69
|
+
|
|
70
|
+
if "transport" not in kwargs:
|
|
71
|
+
kwargs["transport"] = transport if transport else Transport(InMemoryCache(timeout=60))
|
|
72
|
+
|
|
73
|
+
super().__init__(
|
|
74
|
+
service_member.wsdl_url(ssu),
|
|
75
|
+
*args,
|
|
76
|
+
**kwargs,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
self.transport.session.proxies.update({"http": ssu, })
|
|
80
|
+
|
|
81
|
+
self.set_ns_prefix("xro", "https://x-road.eu/xsd/xroad.xsd")
|
|
82
|
+
self.set_ns_prefix("iden", "https://x-road.eu/xsd/identifiers")
|
|
83
|
+
|
|
84
|
+
self.set_default_soapheaders(
|
|
85
|
+
{
|
|
86
|
+
"client": client_member.member_dict,
|
|
87
|
+
"service": service_member.member_dict,
|
|
88
|
+
"userId": client_member.subsystemCode,
|
|
89
|
+
"id": uuid.uuid4().hex,
|
|
90
|
+
"protocolVersion": self._version,
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
_logger.debug("Default header (%s)", self._default_soapheaders)
|
|
94
|
+
|
|
95
|
+
def request(self, **kwargs):
|
|
96
|
+
"""
|
|
97
|
+
Handles SOAP service requests with the ability to specify custom arguments and
|
|
98
|
+
default configurations. The method attempts to process the request with the
|
|
99
|
+
specified arguments, perform serialization of the response object, and handle
|
|
100
|
+
any potential SOAP Fault exceptions that may occur.
|
|
101
|
+
|
|
102
|
+
:param kwargs: Arbitrary keyword arguments to be passed to the SOAP service
|
|
103
|
+
request. These may include service-specific parameters or optional settings.
|
|
104
|
+
If an argument with the key 'xroad_id' is supplied, it will set the 'id'
|
|
105
|
+
attribute of the instance.
|
|
106
|
+
:return: Serialized response object from the SOAP service.
|
|
107
|
+
:rtype: Any
|
|
108
|
+
:raises Fault: If a SOAP Fault exception occurs during the service call, it is
|
|
109
|
+
raised after logging the error details.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
service = self._default_soapheaders["service"].get("serviceCode")
|
|
113
|
+
if "xroad_id" in kwargs:
|
|
114
|
+
self.id = kwargs.pop("xroad_id")
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
response = self.service[service](**kwargs)
|
|
118
|
+
except Fault as error:
|
|
119
|
+
_logger.error("service error (%s: %s)", error.code, error.message)
|
|
120
|
+
raise Fault(error)
|
|
121
|
+
else:
|
|
122
|
+
s_object = serialize_object(response)
|
|
123
|
+
_logger.debug("Response (%s)", s_object)
|
|
124
|
+
return s_object
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def id(self):
|
|
128
|
+
"""
|
|
129
|
+
Provides access to the `id` property representing a specific identifier contained within the
|
|
130
|
+
default SOAP header. This property fetches and returns the associated identifier value from
|
|
131
|
+
a predefined key called "id" in the default SOAP headers.
|
|
132
|
+
|
|
133
|
+
The property is read-only and allows retrieval of the value, but no direct modifications
|
|
134
|
+
to the object’s underlying internal data structures.
|
|
135
|
+
|
|
136
|
+
:rtype: Any
|
|
137
|
+
:return: The value corresponding to the "id" key in the default SOAP headers.
|
|
138
|
+
"""
|
|
139
|
+
return self._default_soapheaders.get("id")
|
|
140
|
+
|
|
141
|
+
@id.setter
|
|
142
|
+
def id(self, value):
|
|
143
|
+
"""
|
|
144
|
+
Sets the value of the `id` attribute and updates the corresponding SOAP
|
|
145
|
+
headers. This setter ensures that the `id` value is synchronized with
|
|
146
|
+
the default SOAP headers and logs the operation for debugging purposes.
|
|
147
|
+
|
|
148
|
+
:param value: The new value to set for the `id` attribute. This value
|
|
149
|
+
is used to update the `_default_soapheaders`.
|
|
150
|
+
:type value: Any
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
self._default_soapheaders["id"] = value
|
|
154
|
+
self.set_default_soapheaders(self._default_soapheaders)
|
|
155
|
+
_logger.debug("Set (id: %s)", value)
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def userId(self) -> str | None:
|
|
159
|
+
"""
|
|
160
|
+
Provides access to the userId obtained from default SOAP headers.
|
|
161
|
+
|
|
162
|
+
This property is a getter that retrieves the value of the "userId" field
|
|
163
|
+
from the default SOAP headers set in the object. It is meant to provide a
|
|
164
|
+
convenient way to access this specific information if it exists in the
|
|
165
|
+
SOAP headers.
|
|
166
|
+
|
|
167
|
+
:rtype: String
|
|
168
|
+
:return: The value of "userId" from the default SOAP headers if it exists,
|
|
169
|
+
otherwise returns None.
|
|
170
|
+
"""
|
|
171
|
+
return self._default_soapheaders.get("userId") # type: ignore[no-any-return]
|
|
172
|
+
|
|
173
|
+
@userId.setter
|
|
174
|
+
def userId(self, value: str):
|
|
175
|
+
"""
|
|
176
|
+
Sets the userId value into the default SOAP headers and updates the headers accordingly.
|
|
177
|
+
Logs the userId value upon setting for debugging purposes.
|
|
178
|
+
|
|
179
|
+
:param value: The userId value to be set in the default SOAP headers.
|
|
180
|
+
:type value: String
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
self._default_soapheaders["userId"] = value
|
|
184
|
+
self.set_default_soapheaders(self._default_soapheaders)
|
|
185
|
+
_logger.debug("Set (userId: %s)", value)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import logging
|
|
3
|
+
from email.utils import parsedate_to_datetime
|
|
4
|
+
|
|
5
|
+
from zeep.plugins import HistoryPlugin
|
|
6
|
+
|
|
7
|
+
_logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class UXPHistoryPlugin(HistoryPlugin):
|
|
11
|
+
"""
|
|
12
|
+
Represents a plugin for handling history-related functionality specific to UXP.
|
|
13
|
+
|
|
14
|
+
This class is a specialized extension of the generic HistoryPlugin, designed to
|
|
15
|
+
handle UXP-specific headers, such as transaction ID and transaction date. The
|
|
16
|
+
primary purpose of this plugin is to extract and provide easy access to these
|
|
17
|
+
pieces of information from HTTP headers.
|
|
18
|
+
|
|
19
|
+
:ivar last_received: Stores the last received HTTP headers and potentially other
|
|
20
|
+
related data.
|
|
21
|
+
:type last_received: dict
|
|
22
|
+
"""
|
|
23
|
+
@property
|
|
24
|
+
def transaction_id(self) -> str:
|
|
25
|
+
"""
|
|
26
|
+
Retrieves the transaction ID from the HTTP headers of the last received
|
|
27
|
+
request. This ID is used to track a specific transaction across
|
|
28
|
+
the system.
|
|
29
|
+
|
|
30
|
+
:rtype: String
|
|
31
|
+
:return: The transaction ID as a string. If the "uxp-transaction-id" header
|
|
32
|
+
is not present, an empty string is returned.
|
|
33
|
+
"""
|
|
34
|
+
transaction: str = self.last_received["http_headers"].get( # type: ignore
|
|
35
|
+
"uxp-transaction-id", ""
|
|
36
|
+
)
|
|
37
|
+
return transaction
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def transaction_date(self) -> datetime.datetime | None:
|
|
41
|
+
"""
|
|
42
|
+
Retrieves the transaction date from the "Date" field in the HTTP headers. If the "Date"
|
|
43
|
+
field is not a valid datetime format, the current datetime will be returned instead.
|
|
44
|
+
|
|
45
|
+
:return: The transaction date parsed as a `datetime.datetime` object or the current
|
|
46
|
+
datetime if parsing fails.
|
|
47
|
+
:rtype: datetime.datetime | None
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
date = parsedate_to_datetime(
|
|
51
|
+
self.last_received["http_headers"].get("Date", "") # type: ignore
|
|
52
|
+
)
|
|
53
|
+
except ValueError:
|
|
54
|
+
date = datetime.datetime.now()
|
|
55
|
+
return date
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from lxml import etree
|
|
4
|
+
from zeep.transports import Transport
|
|
5
|
+
|
|
6
|
+
_logger = logging.getLogger("DRACTransport")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DRACTransport(Transport):
|
|
10
|
+
"""
|
|
11
|
+
Handles communication with the DRAC (Державни Реестр Актових Записів) service.
|
|
12
|
+
|
|
13
|
+
This class is responsible for sending messages to the DRAC service by formatting
|
|
14
|
+
XML envelopes appropriately. It used the Transport parent class for underlying
|
|
15
|
+
communication mechanisms.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def post_xml(self, address, envelope, headers):
|
|
19
|
+
message = etree.tostring(
|
|
20
|
+
envelope, pretty_print=True, xml_declaration=True, encoding="utf-8"
|
|
21
|
+
).decode("utf-8")
|
|
22
|
+
|
|
23
|
+
_logger.debug(f"Source: \n {message}")
|
|
24
|
+
drac_message = ""
|
|
25
|
+
for line in message.splitlines():
|
|
26
|
+
drac_message += line
|
|
27
|
+
if "iden:" not in line:
|
|
28
|
+
drac_message += "\n"
|
|
29
|
+
_logger.debug(f"Modified: \n {drac_message}")
|
|
30
|
+
|
|
31
|
+
return self.post(address, drac_message.encode("utf-8"), headers)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=70.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pyxroad"
|
|
7
|
+
version = "1.5.8"
|
|
8
|
+
description = "X-Road (Trembita) Python client library for SOAP-based service integration"
|
|
9
|
+
authors = [{ name = "Andrii Shapovalov", email = "mt.andrey@gmail.com" }]
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
keywords = ["x-road", "xroad", "trembita", "soap", "python"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 5 - Production/Stable",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
23
|
+
"Topic :: System :: Networking",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"lxml>=5.2.1",
|
|
27
|
+
"Requests>=2.32.3",
|
|
28
|
+
"setuptools>=70.0.0",
|
|
29
|
+
"zeep>=4.0.0",
|
|
30
|
+
"redis",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
redis = ["redis>=4.5.0"]
|
|
35
|
+
dev = [
|
|
36
|
+
"pytest>=8.0.0",
|
|
37
|
+
"pytest-cov>=4.0.0",
|
|
38
|
+
"mypy>=1.0.0",
|
|
39
|
+
"black>=23.0.0",
|
|
40
|
+
"flake8>=6.0.0",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.urls]
|
|
44
|
+
Homepage = "https://github.com/AndreyShapovalovVN/pyxroad"
|
|
45
|
+
Repository = "https://github.com/AndreyShapovalovVN/pyxroad.git"
|
|
46
|
+
Documentation = "https://trembita.gov.ua"
|
|
47
|
+
|
|
48
|
+
[tool.setuptools]
|
|
49
|
+
packages = ["XRoad"]
|
|
50
|
+
|
|
51
|
+
[tool.setuptools.package-data]
|
|
52
|
+
XRoad = ["py.typed"]
|
|
53
|
+
|
|
54
|
+
[tool.mypy]
|
|
55
|
+
python_version = "3.10"
|
|
56
|
+
warn_return_any = true
|
|
57
|
+
warn_unused_configs = true
|
|
58
|
+
disallow_untyped_defs = false
|
|
59
|
+
disallow_incomplete_defs = false
|
|
60
|
+
|
|
61
|
+
[tool.ruff]
|
|
62
|
+
target-version = "py310"
|
|
63
|
+
|
|
64
|
+
[tool.black]
|
|
65
|
+
line-length = 100
|
|
66
|
+
target-version = ["py310", "py311", "py312"]
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyxroad
|
|
3
|
+
Version: 1.5.8
|
|
4
|
+
Summary: X-Road (Trembita) Python client library for SOAP-based service integration
|
|
5
|
+
Author-email: Andrii Shapovalov <mt.andrey@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/AndreyShapovalovVN/pyxroad
|
|
8
|
+
Project-URL: Repository, https://github.com/AndreyShapovalovVN/pyxroad.git
|
|
9
|
+
Project-URL: Documentation, https://trembita.gov.ua
|
|
10
|
+
Keywords: x-road,xroad,trembita,soap,python
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Classifier: Topic :: System :: Networking
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: lxml>=5.2.1
|
|
23
|
+
Requires-Dist: Requests>=2.32.3
|
|
24
|
+
Requires-Dist: setuptools>=70.0.0
|
|
25
|
+
Requires-Dist: zeep>=4.0.0
|
|
26
|
+
Requires-Dist: redis
|
|
27
|
+
Provides-Extra: redis
|
|
28
|
+
Requires-Dist: redis>=4.5.0; extra == "redis"
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
33
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: flake8>=6.0.0; extra == "dev"
|
|
35
|
+
|
|
36
|
+
# X-Road (Trembita) Python Client
|
|
37
|
+
|
|
38
|
+
- **Version:** 1.5.6
|
|
39
|
+
- **Web:** https://trembita.gov.ua
|
|
40
|
+
- **Repository:** https://github.com/AndreyShapovalovVN/pyxroad
|
|
41
|
+
- **Keywords:** x-road, xroad, trembita, python, soap
|
|
42
|
+
|
|
43
|
+
A powerful Python client library for interacting with X-Road (Trembita) security servers.
|
|
44
|
+
This library provides a convenient wrapper around the SOAP-based X-Road protocol, allowing
|
|
45
|
+
developers to easily integrate X-Road services into their Python applications.
|
|
46
|
+
|
|
47
|
+
## Supported Python Versions
|
|
48
|
+
|
|
49
|
+
- Python 3.10+
|
|
50
|
+
- Python 3.11+
|
|
51
|
+
- Python 3.12+
|
|
52
|
+
|
|
53
|
+
## Features
|
|
54
|
+
|
|
55
|
+
- Easy-to-use SOAP client for X-Road services
|
|
56
|
+
- Support for multiple cache backends (SQLite, Redis, In-Memory)
|
|
57
|
+
- Automatic SOAP header management
|
|
58
|
+
- Transaction ID tracking
|
|
59
|
+
- Configurable logging
|
|
60
|
+
- Type hints for better IDE support
|
|
61
|
+
|
|
62
|
+
## Installation from GitHub
|
|
63
|
+
|
|
64
|
+
Using pip:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install git+https://github.com/AndreyShapovalovVN/pyxroad.git#egg=pyxroad
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Or clone and install locally:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
git clone https://github.com/AndreyShapovalovVN/pyxroad.git
|
|
74
|
+
cd pyxroad
|
|
75
|
+
pip install -e .
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Requirements
|
|
79
|
+
|
|
80
|
+
- lxml >= 5.2.1
|
|
81
|
+
- Requests >= 2.32.3
|
|
82
|
+
- setuptools >= 68.1.2
|
|
83
|
+
- zeep >= 4.0.0
|
|
84
|
+
- redis (optional, for Redis cache support)
|
|
85
|
+
|
|
86
|
+
## Code Quality Checks
|
|
87
|
+
|
|
88
|
+
Run the same checks locally as in CI:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
ruff check . --extend-exclude 'tests/~*.py'
|
|
92
|
+
mypy . --ignore-missing-imports --pretty --show-error-codes --exclude 'tests/~.*\.py$'
|
|
93
|
+
pytest --maxfail=2 --disable-warnings --ignore-glob='tests/~*.py'
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Quick Start
|
|
97
|
+
|
|
98
|
+
Basic usage example:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from XRoad import XClient, Transport, SqliteCache
|
|
102
|
+
import logging
|
|
103
|
+
import sys
|
|
104
|
+
|
|
105
|
+
# Configure logging
|
|
106
|
+
logging.basicConfig(
|
|
107
|
+
stream=sys.stdout,
|
|
108
|
+
level=logging.INFO,
|
|
109
|
+
format='%(asctime)s - %(levelname)s - %(name)s - %(message)s'
|
|
110
|
+
)
|
|
111
|
+
_logger = logging.getLogger('XRoad')
|
|
112
|
+
|
|
113
|
+
# Create a client instance
|
|
114
|
+
client = XClient(
|
|
115
|
+
ssu="http://security-server:8080",
|
|
116
|
+
client='SEVDEIR-TEST/GOV/00013480/100001',
|
|
117
|
+
service='SEVDEIR-TEST/GOV/00032684/MIA_prod/CheckPassportStatus/v0.1'
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Make a service request
|
|
121
|
+
try:
|
|
122
|
+
response = client.request(
|
|
123
|
+
xroad_id='ABCD123456', # Optional: set custom request ID
|
|
124
|
+
PasNumber='AA123456',
|
|
125
|
+
PasSerial='654321'
|
|
126
|
+
)
|
|
127
|
+
_logger.info(f"Response: {response}")
|
|
128
|
+
except Exception as err:
|
|
129
|
+
_logger.error(f"Error: {err}")
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Advanced Usage
|
|
133
|
+
|
|
134
|
+
Using custom caching backend:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from XRoad import XClient, Transport, RedisCache
|
|
138
|
+
|
|
139
|
+
# With Redis cache
|
|
140
|
+
redis_cache = RedisCache(path='redis://localhost:6379/0', timeout=3600)
|
|
141
|
+
transport = Transport(cache=redis_cache)
|
|
142
|
+
|
|
143
|
+
client = XClient(
|
|
144
|
+
ssu="http://security-server:8080",
|
|
145
|
+
client='SEVDEIR-TEST/GOV/00013480/100001',
|
|
146
|
+
service='SEVDEIR-TEST/GOV/00032684/MIA_prod/CheckPassportStatus/v0.1',
|
|
147
|
+
transport=transport
|
|
148
|
+
)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Setting custom headers:
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
client.userId = '0123456789' # Custom user ID
|
|
155
|
+
client.id = 'ABCD123456' # Custom request ID
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Available Cache Types
|
|
159
|
+
|
|
160
|
+
- **InMemoryCache**: Default, stores cache in application memory
|
|
161
|
+
- **SqliteCache**: Persistent cache using SQLite database
|
|
162
|
+
- **RedisCache**: Distributed cache using Redis
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
|
|
166
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
167
|
+
|
|
168
|
+
## Support
|
|
169
|
+
|
|
170
|
+
For issues, questions, and contributions, please visit:
|
|
171
|
+
https://github.com/AndreyShapovalovVN/pyxroad
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
XRoad/Members.py
|
|
4
|
+
XRoad/__init__.py
|
|
5
|
+
XRoad/cache.py
|
|
6
|
+
XRoad/client.py
|
|
7
|
+
XRoad/plugins.py
|
|
8
|
+
XRoad/transport.py
|
|
9
|
+
pyxroad.egg-info/PKG-INFO
|
|
10
|
+
pyxroad.egg-info/SOURCES.txt
|
|
11
|
+
pyxroad.egg-info/dependency_links.txt
|
|
12
|
+
pyxroad.egg-info/requires.txt
|
|
13
|
+
pyxroad.egg-info/top_level.txt
|
|
14
|
+
tests/test_members.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
XRoad
|
pyxroad-1.5.8/setup.cfg
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
from XRoad.Members import Members
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestMembers(unittest.TestCase):
|
|
7
|
+
def test_initialization_with_full_path(self):
|
|
8
|
+
member = Members(
|
|
9
|
+
objectType="SERVICE",
|
|
10
|
+
memberPath="instance/class/code/subsystem/service/serviceVersion",
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
self.assertEqual(member.xRoadInstance, "instance")
|
|
14
|
+
self.assertEqual(member.memberClass, "class")
|
|
15
|
+
self.assertEqual(member.memberCode, "code")
|
|
16
|
+
self.assertEqual(member.subsystemCode, "subsystem")
|
|
17
|
+
self.assertEqual(member.serviceCode, "service")
|
|
18
|
+
self.assertEqual(member.serviceVersion, "serviceVersion")
|
|
19
|
+
|
|
20
|
+
def test_initialization_with_partial_path(self):
|
|
21
|
+
member = Members(objectType="SERVICE", memberPath="instance/class")
|
|
22
|
+
|
|
23
|
+
self.assertEqual(member.xRoadInstance, "instance")
|
|
24
|
+
self.assertEqual(member.memberClass, "class")
|
|
25
|
+
self.assertIsNone(member.memberCode)
|
|
26
|
+
self.assertIsNone(member.subsystemCode)
|
|
27
|
+
self.assertIsNone(member.serviceCode)
|
|
28
|
+
self.assertIsNone(member.serviceVersion)
|
|
29
|
+
|
|
30
|
+
def test_wsdl_path_generation(self):
|
|
31
|
+
member = Members(
|
|
32
|
+
objectType="SERVICE",
|
|
33
|
+
memberPath="instance/class/code/subsystem/service/serviceVersion",
|
|
34
|
+
)
|
|
35
|
+
expected_wsdl_path = "xRoadInstance=instance&memberClass=class&memberCode=code&subsystemCode=subsystem&serviceCode=service&version=serviceVersion"
|
|
36
|
+
|
|
37
|
+
self.assertEqual(member.wsdl_path, expected_wsdl_path)
|
|
38
|
+
|
|
39
|
+
def test_wsdl_url(self):
|
|
40
|
+
member = Members(
|
|
41
|
+
objectType="SERVICE",
|
|
42
|
+
memberPath="instance/class/code/subsystem/service/serviceVersion",
|
|
43
|
+
)
|
|
44
|
+
ssu = "https://example.com"
|
|
45
|
+
expected_wsdl_path = "xRoadInstance=instance&memberClass=class&memberCode=code&subsystemCode=subsystem&serviceCode=service&version=serviceVersion"
|
|
46
|
+
expected_url = f"{ssu}/wsdl?{expected_wsdl_path}"
|
|
47
|
+
|
|
48
|
+
self.assertEqual(member.wsdl_url(ssu), expected_url)
|
|
49
|
+
|
|
50
|
+
def test_member_dict(self):
|
|
51
|
+
member = Members(
|
|
52
|
+
objectType="SERVICE",
|
|
53
|
+
memberPath="instance/class/code/subsystem/service/serviceVersion",
|
|
54
|
+
)
|
|
55
|
+
expected_dict = {
|
|
56
|
+
"xRoadInstance": "instance",
|
|
57
|
+
"memberClass": "class",
|
|
58
|
+
"memberCode": "code",
|
|
59
|
+
"subsystemCode": "subsystem",
|
|
60
|
+
"serviceCode": "service",
|
|
61
|
+
"serviceVersion": "serviceVersion",
|
|
62
|
+
"objectType": "SERVICE",
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
self.assertDictEqual(member.member_dict, expected_dict)
|
|
66
|
+
|
|
67
|
+
def test_wsdl_path_none_for_non_service(self):
|
|
68
|
+
member = Members(
|
|
69
|
+
objectType="OTHER_TYPE",
|
|
70
|
+
memberPath="instance/class/code/subsystem/service/serviceVersion",
|
|
71
|
+
)
|
|
72
|
+
with self.assertRaises(ValueError) as ctx:
|
|
73
|
+
_ = member.wsdl_path
|
|
74
|
+
self.assertEqual(
|
|
75
|
+
str(ctx.exception), "wsdl_path is only available for SERVICE objectType"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
if __name__ == "__main__":
|
|
80
|
+
unittest.main()
|