pyciteck 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.
- PyCiteck/__init__.py +0 -0
- PyCiteck/citeck_client.py +130 -0
- pyciteck-0.1.0.dist-info/METADATA +53 -0
- pyciteck-0.1.0.dist-info/RECORD +5 -0
- pyciteck-0.1.0.dist-info/WHEEL +4 -0
PyCiteck/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import io
|
|
2
|
+
from time import time
|
|
3
|
+
from typing import Sequence
|
|
4
|
+
import anyio
|
|
5
|
+
from httpx import AsyncClient
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CiteckClient:
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
keycloak_client_id: str,
|
|
12
|
+
keycloak_secret: str,
|
|
13
|
+
citeck_base_url: str,
|
|
14
|
+
keycloak_realm: str = "ecos-app",
|
|
15
|
+
client: AsyncClient | None = None
|
|
16
|
+
):
|
|
17
|
+
self._client = client or AsyncClient()
|
|
18
|
+
self._keycloak_client_id = keycloak_client_id
|
|
19
|
+
self._keycloak_secret = keycloak_secret
|
|
20
|
+
self._citeck_base_url = citeck_base_url
|
|
21
|
+
self._keycloak_realm = keycloak_realm
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def token_url(self):
|
|
25
|
+
return f"{self._citeck_base_url}/ecos-idp/auth/realms/{self._keycloak_realm}/protocol/openid-connect/token"
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def records_base_url(self):
|
|
29
|
+
return f"{self._citeck_base_url}/gateway/api/records"
|
|
30
|
+
|
|
31
|
+
_token_cache = {
|
|
32
|
+
"access_token": None,
|
|
33
|
+
"expires_at": 0,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async def _get_bearer_token(self, force=False):
|
|
37
|
+
"""
|
|
38
|
+
Получение токена OAuth2 (client_credentials) с учетом expires_in.
|
|
39
|
+
"""
|
|
40
|
+
now = int(time())
|
|
41
|
+
if (
|
|
42
|
+
not force
|
|
43
|
+
and self._token_cache["access_token"]
|
|
44
|
+
and self._token_cache["expires_at"] - 15 > now
|
|
45
|
+
):
|
|
46
|
+
return self._token_cache["access_token"]
|
|
47
|
+
|
|
48
|
+
data = {
|
|
49
|
+
"grant_type": "client_credentials",
|
|
50
|
+
"client_id": self._keycloak_client_id,
|
|
51
|
+
"client_secret": self._keycloak_secret,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
resp = await self._client.post(self.token_url, data=data)
|
|
55
|
+
token_data = resp.json()
|
|
56
|
+
|
|
57
|
+
token = token_data.get("access_token")
|
|
58
|
+
expires_in = int(token_data.get("expires_in", 300))
|
|
59
|
+
|
|
60
|
+
self._token_cache["access_token"] = token
|
|
61
|
+
self._token_cache["expires_at"] = now + expires_in
|
|
62
|
+
return token
|
|
63
|
+
|
|
64
|
+
async def mutate(self, records: Sequence):
|
|
65
|
+
url = f"{self.records_base_url}/mutate"
|
|
66
|
+
token = await self._get_bearer_token()
|
|
67
|
+
|
|
68
|
+
headers = {
|
|
69
|
+
"Authorization": f"Bearer {token}",
|
|
70
|
+
"Content-Type": "application/json",
|
|
71
|
+
"Accept": "application/json",
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
data = {
|
|
75
|
+
"records": records,
|
|
76
|
+
"version": 1,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
resp = await self._client.post(url, json=data, headers=headers)
|
|
80
|
+
result = resp.json()
|
|
81
|
+
return result
|
|
82
|
+
|
|
83
|
+
async def query(self, query: dict):
|
|
84
|
+
url = f"{self.records_base_url}/query"
|
|
85
|
+
token = await self._get_bearer_token()
|
|
86
|
+
|
|
87
|
+
headers = {
|
|
88
|
+
"Authorization": f"Bearer {token}",
|
|
89
|
+
"Content-Type": "application/json",
|
|
90
|
+
"Accept": "application/json",
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
resp = await self._client.post(url, json=query, headers=headers)
|
|
94
|
+
result = resp.json()
|
|
95
|
+
return result
|
|
96
|
+
|
|
97
|
+
async def download_file_from_citeck(self, url: str):
|
|
98
|
+
token = await self._get_bearer_token()
|
|
99
|
+
headers = {"Authorization": f"Bearer {token}"}
|
|
100
|
+
file_buffer = io.BytesIO()
|
|
101
|
+
|
|
102
|
+
async with self._client as session:
|
|
103
|
+
async with session.stream("GET", url, headers=headers, follow_redirects=True) as response:
|
|
104
|
+
response.raise_for_status()
|
|
105
|
+
|
|
106
|
+
async for chunk in response.aiter_bytes():
|
|
107
|
+
file_buffer.write(chunk)
|
|
108
|
+
|
|
109
|
+
file_buffer.seek(0)
|
|
110
|
+
return file_buffer
|
|
111
|
+
|
|
112
|
+
async def upload_file_to_citeck(
|
|
113
|
+
self,
|
|
114
|
+
url: str,
|
|
115
|
+
path_to_file: str,
|
|
116
|
+
file_name: str,
|
|
117
|
+
file_content_type: str,
|
|
118
|
+
):
|
|
119
|
+
token = await self._get_bearer_token
|
|
120
|
+
headers = {"Authorization": f"Bearer {token}"}
|
|
121
|
+
|
|
122
|
+
async with await anyio.open_file(path_to_file, "rb") as f:
|
|
123
|
+
file_bytes = await f.read()
|
|
124
|
+
|
|
125
|
+
files = {
|
|
126
|
+
"file": (file_name, file_bytes, file_content_type)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
response = await self._client.post(url, headers=headers, files=files)
|
|
130
|
+
return response.json()
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyciteck
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Citeck Community Edition client for python
|
|
5
|
+
Project-URL: Homepage, https://github.com/gulyaeve/PyCiteck
|
|
6
|
+
Requires-Python: >=3.14
|
|
7
|
+
Requires-Dist: httpx>=0.28.1
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# Python-клиент для Citeck Community Edition
|
|
11
|
+
|
|
12
|
+
## Клиент находится в разработке!
|
|
13
|
+
|
|
14
|
+
### Особенности
|
|
15
|
+
|
|
16
|
+
Клиент полностью асинхронный. Требуется Python 3.14 или выше. Основан на `httpx`.
|
|
17
|
+
|
|
18
|
+
### Установка
|
|
19
|
+
|
|
20
|
+
```console
|
|
21
|
+
$ pip install pyciteck
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Использование
|
|
25
|
+
```Python
|
|
26
|
+
from pprint import pprint
|
|
27
|
+
|
|
28
|
+
from PyCiteck.citeck_client import CiteckClient
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async def main():
|
|
32
|
+
print("Hello from pyciteck!")
|
|
33
|
+
# Client init
|
|
34
|
+
ecm_client = CiteckClient(
|
|
35
|
+
keycloak_client_id=settings.ECM_CLIENT_ID,
|
|
36
|
+
keycloak_secret=settings.ECM_CLIENT_SECRET,
|
|
37
|
+
citeck_base_url=settings.ECM_BASE_URL,
|
|
38
|
+
)
|
|
39
|
+
# Quety example
|
|
40
|
+
record = await ecm_client.query(
|
|
41
|
+
query={
|
|
42
|
+
"records": ["emodel/person@1234"],
|
|
43
|
+
"attributes": ["?json"],
|
|
44
|
+
"version": 1,
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
pprint(record)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
from asyncio import run
|
|
52
|
+
run(main())
|
|
53
|
+
```
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
PyCiteck/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
PyCiteck/citeck_client.py,sha256=aIJNMTbCsh1TXTjOIUPZNy0fehEkI13iv7bqkZ5_B1A,4083
|
|
3
|
+
pyciteck-0.1.0.dist-info/METADATA,sha256=qAps-z6Y3EoEvUXwVG42kB9NTNDzrenbELJySKDDxp8,1269
|
|
4
|
+
pyciteck-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
5
|
+
pyciteck-0.1.0.dist-info/RECORD,,
|