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 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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any