pyznuny 0.0.1__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.
- pyznuny/__init__.py +3 -0
- pyznuny/ticket/__init__.py +7 -0
- pyznuny/ticket/client.py +261 -0
- pyznuny/ticket/endpoints.py +154 -0
- pyznuny/ticket/exceptions.py +15 -0
- pyznuny/ticket/models.py +172 -0
- pyznuny/ticket/routes.py +78 -0
- pyznuny-0.0.1.dist-info/METADATA +12 -0
- pyznuny-0.0.1.dist-info/RECORD +11 -0
- pyznuny-0.0.1.dist-info/WHEEL +5 -0
- pyznuny-0.0.1.dist-info/top_level.txt +1 -0
pyznuny/__init__.py
ADDED
pyznuny/ticket/client.py
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
if __name__ == "__main__" and __package__ is None:
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
|
|
13
|
+
from pyznuny.ticket.endpoints import Endpoint, EndpointsRegistry,EndpointSetter, _DEFAULT_ENDPOINT_IDENTIFIERS, _DEFAULT_ENDPOINTS
|
|
14
|
+
from pyznuny.ticket.models import (
|
|
15
|
+
TicketCreateArticle,
|
|
16
|
+
TicketCreatePayload,
|
|
17
|
+
TicketCreateTicket,
|
|
18
|
+
)
|
|
19
|
+
from pyznuny.ticket.exceptions import TicketClientError
|
|
20
|
+
from pyznuny.ticket.routes import TicketRoutes, SessionRoutes
|
|
21
|
+
else:
|
|
22
|
+
from .endpoints import Endpoint, EndpointsRegistry,EndpointSetter, _DEFAULT_ENDPOINT_IDENTIFIERS, _DEFAULT_ENDPOINTS
|
|
23
|
+
from .models import (
|
|
24
|
+
TicketCreateArticle,
|
|
25
|
+
TicketCreatePayload,
|
|
26
|
+
TicketCreateTicket,
|
|
27
|
+
)
|
|
28
|
+
from .routes import TicketRoutes, SessionRoutes
|
|
29
|
+
from .exceptions import TicketClientError
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TicketClient:
|
|
35
|
+
"""
|
|
36
|
+
Represents a client for interacting with the Ticket API
|
|
37
|
+
|
|
38
|
+
:param base_url: Base URL for the Ticket API which the client will connect to
|
|
39
|
+
:type base_url: str | None
|
|
40
|
+
:param username: Username for authentication
|
|
41
|
+
:type username: str | None
|
|
42
|
+
:param password: Password for authentication
|
|
43
|
+
:type password: str | None
|
|
44
|
+
:param endpoints: Optional custom endpoints registry
|
|
45
|
+
:type endpoints: EndpointsRegistry | None
|
|
46
|
+
:param timeout: Request timeout
|
|
47
|
+
:type timeout: float | None
|
|
48
|
+
:param headers: Optional custom headers
|
|
49
|
+
:type headers: Mapping[str, str] | None
|
|
50
|
+
"""
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
base_url: str | None = None,
|
|
54
|
+
*,
|
|
55
|
+
username: str | None = None,
|
|
56
|
+
password: str | None = None,
|
|
57
|
+
endpoints: EndpointsRegistry | None = None,
|
|
58
|
+
timeout: float | None = None,
|
|
59
|
+
headers: Mapping[str, str] | None = None,
|
|
60
|
+
client: httpx.Client | None = None,
|
|
61
|
+
) -> None:
|
|
62
|
+
self._endpoints = endpoints or EndpointsRegistry()
|
|
63
|
+
self._endpoint_identifiers = dict(_DEFAULT_ENDPOINT_IDENTIFIERS)
|
|
64
|
+
if client is not None:
|
|
65
|
+
self._client = client
|
|
66
|
+
else:
|
|
67
|
+
client_kwargs: dict[str, Any] = {"timeout": timeout, "headers": headers}
|
|
68
|
+
if base_url is not None:
|
|
69
|
+
client_kwargs["base_url"] = base_url
|
|
70
|
+
self._client = httpx.Client(**client_kwargs)
|
|
71
|
+
|
|
72
|
+
self._register_default_endpoints()
|
|
73
|
+
self.ticket = TicketRoutes(self)
|
|
74
|
+
self.session = SessionRoutes(self)
|
|
75
|
+
self.set_endpoint = EndpointSetter(self)
|
|
76
|
+
self.session_id: str | None = None
|
|
77
|
+
|
|
78
|
+
if username is not None and password is not None:
|
|
79
|
+
self.login(username, password)
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def endpoints(self) -> EndpointsRegistry:
|
|
83
|
+
return self._endpoints
|
|
84
|
+
|
|
85
|
+
def register_endpoint(self, name: str, method: str, path: str) -> Endpoint:
|
|
86
|
+
"""
|
|
87
|
+
Registers a custom endpoint for the Ticket API
|
|
88
|
+
|
|
89
|
+
:param name: Name of the endpoint
|
|
90
|
+
:type name: str
|
|
91
|
+
:param method: HTTP method for the endpoint
|
|
92
|
+
:type method: str
|
|
93
|
+
:param path: Endpoint path
|
|
94
|
+
:type path: str
|
|
95
|
+
:return: Registered endpoint
|
|
96
|
+
:rtype: Endpoint
|
|
97
|
+
"""
|
|
98
|
+
return self._endpoints.register(Endpoint(name=name, method=method, path=path))
|
|
99
|
+
|
|
100
|
+
def set_endpoint_identifier(self, name: str, identifier: str) -> None:
|
|
101
|
+
"""
|
|
102
|
+
Sets a custom identifier for an endpoint
|
|
103
|
+
|
|
104
|
+
:param name: Name of the endpoint
|
|
105
|
+
:type name: str
|
|
106
|
+
:param identifier: Custom identifier for the endpoint
|
|
107
|
+
:type identifier: str
|
|
108
|
+
"""
|
|
109
|
+
self._endpoint_identifiers[name] = identifier
|
|
110
|
+
|
|
111
|
+
def endpoint_identifier(self, name: str) -> str:
|
|
112
|
+
"""
|
|
113
|
+
Returns the custom identifier for an endpoint
|
|
114
|
+
|
|
115
|
+
:param name: Name of the endpoint
|
|
116
|
+
:type name: str
|
|
117
|
+
:return: Custom identifier for the endpoint
|
|
118
|
+
:rtype: str
|
|
119
|
+
"""
|
|
120
|
+
try:
|
|
121
|
+
return self._endpoint_identifiers[name]
|
|
122
|
+
except KeyError as exc:
|
|
123
|
+
raise KeyError(f"Endpoint identifier not registered: {name}") from exc
|
|
124
|
+
|
|
125
|
+
def login(self, username: str, password: str) -> httpx.Response:
|
|
126
|
+
"""
|
|
127
|
+
Creates a new session with the given username and password.
|
|
128
|
+
|
|
129
|
+
:param username: Username for authentication
|
|
130
|
+
:type username: str
|
|
131
|
+
:param password: Password for authentication
|
|
132
|
+
:type password: str
|
|
133
|
+
:return: Response object containing session details
|
|
134
|
+
:rtype: Response
|
|
135
|
+
"""
|
|
136
|
+
response = self.session.create(username, password)
|
|
137
|
+
self.session_id = response.json().get("SessionID")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def request(
|
|
141
|
+
self,
|
|
142
|
+
endpoint_name: str,
|
|
143
|
+
*,
|
|
144
|
+
method: str | None = None,
|
|
145
|
+
path: str | None = None,
|
|
146
|
+
path_params: Mapping[str, Any] | None = None,
|
|
147
|
+
**kwargs: Any,
|
|
148
|
+
) -> httpx.Response:
|
|
149
|
+
"""
|
|
150
|
+
Sends a request to the Ticket API
|
|
151
|
+
|
|
152
|
+
:param endpoint_name: Name of the endpoint
|
|
153
|
+
:type endpoint_name: str
|
|
154
|
+
:param method: HTTP method for the request, defaults to None
|
|
155
|
+
:type method: str | None
|
|
156
|
+
:param path: Custom endpoint path, defaults to None
|
|
157
|
+
:type path: str | None
|
|
158
|
+
:param path_params: Parameters for the endpoint path, defaults to None
|
|
159
|
+
:type path_params: Mapping[str, Any] | None
|
|
160
|
+
:param kwargs: Additional keyword arguments for the request
|
|
161
|
+
:type kwargs: Any
|
|
162
|
+
:return: Response object
|
|
163
|
+
:rtype: httpx.Response
|
|
164
|
+
"""
|
|
165
|
+
endpoint_method = method or self._endpoints.method_for(endpoint_name)
|
|
166
|
+
endpoint_path = path or self._endpoints.path_for(endpoint_name)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
if path_params:
|
|
171
|
+
endpoint_path = endpoint_path.format(**path_params)
|
|
172
|
+
response = self._client.request(endpoint_method, endpoint_path, **kwargs)
|
|
173
|
+
|
|
174
|
+
response.raise_for_status()
|
|
175
|
+
if error := response.json().get("Error"):
|
|
176
|
+
self._raise_error(error)
|
|
177
|
+
|
|
178
|
+
return response
|
|
179
|
+
|
|
180
|
+
def _raise_error(self, error: Mapping[str, Any]) -> None:
|
|
181
|
+
raise TicketClientError(error)
|
|
182
|
+
|
|
183
|
+
def close(self) -> None:
|
|
184
|
+
"""
|
|
185
|
+
Closes the client connection
|
|
186
|
+
"""
|
|
187
|
+
self._client.close()
|
|
188
|
+
|
|
189
|
+
def __enter__(self) -> "TicketClient":
|
|
190
|
+
return self
|
|
191
|
+
|
|
192
|
+
def __exit__(self, exc_type, exc, traceback) -> None:
|
|
193
|
+
self.close()
|
|
194
|
+
|
|
195
|
+
def _register_default_endpoints(self) -> None:
|
|
196
|
+
for name, (method, path) in _DEFAULT_ENDPOINTS.items():
|
|
197
|
+
if not self._endpoints.has(name):
|
|
198
|
+
self._endpoints.register(
|
|
199
|
+
Endpoint(name=name, method=method, path=path)
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
if __name__ == "__main__":
|
|
204
|
+
from dotenv import load_dotenv
|
|
205
|
+
load_dotenv()
|
|
206
|
+
import os
|
|
207
|
+
|
|
208
|
+
class_payload_create = TicketCreatePayload(
|
|
209
|
+
Ticket=TicketCreateTicket(
|
|
210
|
+
Title="Erro no login",
|
|
211
|
+
Queue="ITS::Ops-TechOps::Sentinelops::Cops React - N1",
|
|
212
|
+
State="new",
|
|
213
|
+
Priority="3 normal",
|
|
214
|
+
CustomerUser="joel.junior@eitisolucoes.com.br",
|
|
215
|
+
Type="Monitoramento",
|
|
216
|
+
),
|
|
217
|
+
Article=TicketCreateArticle(
|
|
218
|
+
Subject="Não consigo acessar",
|
|
219
|
+
Body="Detalhes do problema...",
|
|
220
|
+
ContentType="text/plain; charset=utf-8",
|
|
221
|
+
Charset="utf-8",
|
|
222
|
+
MimeType="text/plain",
|
|
223
|
+
SenderType="customer",
|
|
224
|
+
From_="joel.junior@eitisolucoes.com.br",
|
|
225
|
+
),
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
payload_create = {
|
|
229
|
+
"Ticket": {
|
|
230
|
+
"Title": "Erro no login",
|
|
231
|
+
"Queue": "ITS::Ops-TechOps::Sentinelops::Cops React - N1",
|
|
232
|
+
"State": "new",
|
|
233
|
+
"Priority": "3 normal",
|
|
234
|
+
"CustomerUser": "joel.junior@eitisolucoes.com.br",
|
|
235
|
+
"Type": "Monitoramento",
|
|
236
|
+
},
|
|
237
|
+
"Article": {
|
|
238
|
+
"Subject": "Não consigo acessar",
|
|
239
|
+
"Body": "Detalhes do problema...",
|
|
240
|
+
"ContentType": "text/plain; charset=utf-8",
|
|
241
|
+
"Charset": "utf-8",
|
|
242
|
+
"MimeType": "text/plain",
|
|
243
|
+
"SenderType": "customer",
|
|
244
|
+
"From": "joel.junior@eitisolucoes.com.br",
|
|
245
|
+
},
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
payload_update = {
|
|
249
|
+
"Ticket": {
|
|
250
|
+
"State": "open",
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
client = TicketClient(base_url=os.getenv("HOST"),
|
|
256
|
+
username=os.getenv("USER_LOGIN"), password=os.getenv("PASS"))
|
|
257
|
+
|
|
258
|
+
client.set_endpoint.ticket_get(endpoint="Tickets/{ticket_id}",
|
|
259
|
+
identifier="ticket_id")
|
|
260
|
+
response = client.ticket.get(ticket_id=5853276)
|
|
261
|
+
print("GET Ticket Response:", response.json())
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from .models import Endpoint
|
|
5
|
+
from typing import Iterable, Mapping, MutableMapping, TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from pyznuny.ticket.client import TicketClient
|
|
9
|
+
|
|
10
|
+
_DEFAULT_ENDPOINTS = {
|
|
11
|
+
"ticket_create": ("POST", "/Ticket"),
|
|
12
|
+
"ticket_update": ("PATCH", "/Ticket/{ticket_id}"),
|
|
13
|
+
"ticket_get": ("GET", "/Ticket/{ticket_id}"),
|
|
14
|
+
"session_create": ("POST", "/Session"),
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
_DEFAULT_ENDPOINT_IDENTIFIERS = {
|
|
18
|
+
"ticket_update": "ticket_id",
|
|
19
|
+
"ticket_get": "ticket_id",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class EndpointSetter:
|
|
25
|
+
"""
|
|
26
|
+
Custom endpoint setter for the Ticket API
|
|
27
|
+
|
|
28
|
+
:arg client: Ticket client instance
|
|
29
|
+
:type client: TicketClient
|
|
30
|
+
"""
|
|
31
|
+
def __init__(self, client: "TicketClient") -> None:
|
|
32
|
+
self._client = client
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def ticket_create(self, *, endpoint: str, method: str = "POST") -> Endpoint:
|
|
36
|
+
"""
|
|
37
|
+
Sets a custom endpoint for creating tickets
|
|
38
|
+
|
|
39
|
+
:param endpoint: Custom endpoint for creating tickets
|
|
40
|
+
:type endpoint: str
|
|
41
|
+
:param method: HTTP method for creating tickets, defaults to POST
|
|
42
|
+
:type method: str
|
|
43
|
+
:return: Endpoint object
|
|
44
|
+
:rtype: Endpoint
|
|
45
|
+
"""
|
|
46
|
+
return self._client.register_endpoint("ticket_create", method, endpoint)
|
|
47
|
+
|
|
48
|
+
def ticket_get(
|
|
49
|
+
self,
|
|
50
|
+
*,
|
|
51
|
+
endpoint: str,
|
|
52
|
+
identifier: str = "ticket_id",
|
|
53
|
+
method: str = "GET",
|
|
54
|
+
) -> Endpoint:
|
|
55
|
+
"""
|
|
56
|
+
Sets a custom endpoint for retrieving a ticket.
|
|
57
|
+
|
|
58
|
+
:param endpoint: Custom endpoint path
|
|
59
|
+
:type endpoint: str
|
|
60
|
+
:param identifier: Identifier for the ticket ID in the endpoint path
|
|
61
|
+
:type identifier: str
|
|
62
|
+
:param method: HTTP method for the endpoint, defaults to GET
|
|
63
|
+
:type method: str
|
|
64
|
+
:return: Registered endpoint
|
|
65
|
+
:rtype: Endpoint
|
|
66
|
+
"""
|
|
67
|
+
endpoint_obj = self._client.register_endpoint(
|
|
68
|
+
"ticket_get",
|
|
69
|
+
method,
|
|
70
|
+
endpoint,
|
|
71
|
+
)
|
|
72
|
+
self._client.set_endpoint_identifier("ticket_get", identifier)
|
|
73
|
+
return endpoint_obj
|
|
74
|
+
|
|
75
|
+
def ticket_update(
|
|
76
|
+
self,
|
|
77
|
+
*,
|
|
78
|
+
endpoint: str,
|
|
79
|
+
identifier: str = "ticket_id",
|
|
80
|
+
method: str = "POST",
|
|
81
|
+
) -> Endpoint:
|
|
82
|
+
"""
|
|
83
|
+
Sets a custom endpoint for updating a ticket.
|
|
84
|
+
|
|
85
|
+
:param endpoint: Custom endpoint path
|
|
86
|
+
:type endpoint: str
|
|
87
|
+
:param identifier: Identifier for the ticket ID in the endpoint path
|
|
88
|
+
:type identifier: str
|
|
89
|
+
:param method: HTTP method for the endpoint, defaults to POST
|
|
90
|
+
:type method: str
|
|
91
|
+
:return: Registered endpoint
|
|
92
|
+
:rtype: Endpoint
|
|
93
|
+
"""
|
|
94
|
+
endpoint_obj = self._client.register_endpoint(
|
|
95
|
+
"ticket_update",
|
|
96
|
+
method,
|
|
97
|
+
endpoint,
|
|
98
|
+
)
|
|
99
|
+
self._client.set_endpoint_identifier("ticket_update", identifier)
|
|
100
|
+
return endpoint_obj
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class EndpointsRegistry:
|
|
106
|
+
"""
|
|
107
|
+
Object representing the registry of API endpoints for the Ticket API
|
|
108
|
+
|
|
109
|
+
:arg base_path: Base path for the endpoints, defaults to an empty string
|
|
110
|
+
:type base_path: str
|
|
111
|
+
"""
|
|
112
|
+
def __init__(
|
|
113
|
+
self,
|
|
114
|
+
*,
|
|
115
|
+
base_path: str = "",
|
|
116
|
+
endpoints: Iterable[Endpoint] | None = None,
|
|
117
|
+
) -> None:
|
|
118
|
+
self._base_path = base_path
|
|
119
|
+
self._endpoints: MutableMapping[str, Endpoint] = {}
|
|
120
|
+
if endpoints:
|
|
121
|
+
for endpoint in endpoints:
|
|
122
|
+
self.register(endpoint)
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def base_path(self) -> str:
|
|
126
|
+
return self._base_path
|
|
127
|
+
|
|
128
|
+
@base_path.setter
|
|
129
|
+
def base_path(self, value: str) -> None:
|
|
130
|
+
self._base_path = value
|
|
131
|
+
|
|
132
|
+
def register(self, endpoint: Endpoint) -> Endpoint:
|
|
133
|
+
self._endpoints[endpoint.name] = endpoint
|
|
134
|
+
return endpoint
|
|
135
|
+
|
|
136
|
+
def configure(self, mapping: Mapping[str, tuple[str, str]]) -> None:
|
|
137
|
+
for name, (method, path) in mapping.items():
|
|
138
|
+
self.register(Endpoint(name=name, method=method, path=path))
|
|
139
|
+
|
|
140
|
+
def get(self, name: str) -> Endpoint:
|
|
141
|
+
try:
|
|
142
|
+
return self._endpoints[name]
|
|
143
|
+
except KeyError as exc:
|
|
144
|
+
raise KeyError(f"Endpoint not registered: {name}") from exc
|
|
145
|
+
|
|
146
|
+
def has(self, name: str) -> bool:
|
|
147
|
+
return name in self._endpoints
|
|
148
|
+
|
|
149
|
+
def path_for(self, name: str) -> str:
|
|
150
|
+
endpoint = self.get(name)
|
|
151
|
+
return endpoint.full_path(self._base_path)
|
|
152
|
+
|
|
153
|
+
def method_for(self, name: str) -> str:
|
|
154
|
+
return self.get(name).method
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
from typing import Any, Mapping
|
|
4
|
+
|
|
5
|
+
class TicketClientError(Exception):
|
|
6
|
+
"""
|
|
7
|
+
Docstring for TicketClientError
|
|
8
|
+
Represents an error returned by the TicketClient API.
|
|
9
|
+
"""
|
|
10
|
+
def __init__(self, error: Mapping[str, Any] | str) -> None:
|
|
11
|
+
|
|
12
|
+
self.error = error
|
|
13
|
+
code = error.get("ErrorCode") if isinstance(error, dict) else error
|
|
14
|
+
message = error.get("ErrorMessage") or "Unknown error" if isinstance(error, dict) else str(error)
|
|
15
|
+
super().__init__(f"{code}: {message}" if code else str(message))
|
pyznuny/ticket/models.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Mapping
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
_VALID_METHODS = {
|
|
8
|
+
"GET",
|
|
9
|
+
"POST",
|
|
10
|
+
"PUT",
|
|
11
|
+
"PATCH",
|
|
12
|
+
"DELETE",
|
|
13
|
+
"HEAD",
|
|
14
|
+
"OPTIONS",
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _clean_dict(data: Mapping[str, Any]) -> dict[str, Any]:
|
|
19
|
+
return {key: value for key, value in data.items() if value is not None}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _require_non_empty(value: str, field: str) -> None:
|
|
23
|
+
if not value or not str(value).strip():
|
|
24
|
+
raise ValueError(f"{field} is required.")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _normalize_method(method: str) -> str:
|
|
28
|
+
normalized = method.strip().upper()
|
|
29
|
+
if normalized not in _VALID_METHODS:
|
|
30
|
+
valid = ", ".join(sorted(_VALID_METHODS))
|
|
31
|
+
raise ValueError(f"Unsupported HTTP method: {method!r}. Use one of: {valid}")
|
|
32
|
+
return normalized
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _normalize_path(path: str) -> str:
|
|
36
|
+
normalized = "/" + path.lstrip("/")
|
|
37
|
+
if normalized == "/":
|
|
38
|
+
raise ValueError("Endpoint path cannot be empty.")
|
|
39
|
+
return normalized
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _join_base_path(base_path: str, endpoint_path: str) -> str:
|
|
43
|
+
base = base_path.strip("/")
|
|
44
|
+
tail = endpoint_path.lstrip("/")
|
|
45
|
+
if not base:
|
|
46
|
+
return "/" + tail
|
|
47
|
+
return f"/{base}/{tail}"
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True, slots=True)
|
|
50
|
+
class Endpoint:
|
|
51
|
+
"""
|
|
52
|
+
Object representing an API endpoint
|
|
53
|
+
|
|
54
|
+
:arg name: Name of the endpoint
|
|
55
|
+
:type name: str
|
|
56
|
+
:arg method: HTTP method for the endpoint
|
|
57
|
+
:type method: str
|
|
58
|
+
:arg path: Endpoint path
|
|
59
|
+
:type path: str
|
|
60
|
+
"""
|
|
61
|
+
name: str
|
|
62
|
+
method: str
|
|
63
|
+
path: str
|
|
64
|
+
|
|
65
|
+
def __post_init__(self) -> None:
|
|
66
|
+
object.__setattr__(self, "method", _normalize_method(self.method))
|
|
67
|
+
object.__setattr__(self, "path", _normalize_path(self.path))
|
|
68
|
+
|
|
69
|
+
def full_path(self, base_path: str = "") -> str:
|
|
70
|
+
"""
|
|
71
|
+
Returns the full path for the endpoint
|
|
72
|
+
|
|
73
|
+
:param base_path: Base path for the endpoint, defaults to an empty string
|
|
74
|
+
:type base_path: str
|
|
75
|
+
:return: Full path for the endpoint
|
|
76
|
+
:rtype: str
|
|
77
|
+
"""
|
|
78
|
+
return _join_base_path(base_path, self.path)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass(slots=True)
|
|
83
|
+
class TicketCreateTicket:
|
|
84
|
+
Title: str
|
|
85
|
+
Queue: str
|
|
86
|
+
State: str
|
|
87
|
+
Priority: str
|
|
88
|
+
CustomerUser: str | None = None
|
|
89
|
+
Type: str | None = None
|
|
90
|
+
Service: str | None = None
|
|
91
|
+
SLA: str | None = None
|
|
92
|
+
Owner: str | None = None
|
|
93
|
+
Responsible: str | None = None
|
|
94
|
+
|
|
95
|
+
def validate(self) -> None:
|
|
96
|
+
_require_non_empty(self.Title, "Ticket.Title")
|
|
97
|
+
_require_non_empty(self.Queue, "Ticket.Queue")
|
|
98
|
+
_require_non_empty(self.State, "Ticket.State")
|
|
99
|
+
_require_non_empty(self.Priority, "Ticket.Priority")
|
|
100
|
+
|
|
101
|
+
def to_dict(self) -> dict[str, Any]:
|
|
102
|
+
self.validate()
|
|
103
|
+
return _clean_dict(
|
|
104
|
+
{
|
|
105
|
+
"Title": self.Title,
|
|
106
|
+
"Queue": self.Queue,
|
|
107
|
+
"State": self.State,
|
|
108
|
+
"Priority": self.Priority,
|
|
109
|
+
"CustomerUser": self.CustomerUser,
|
|
110
|
+
"Type": self.Type,
|
|
111
|
+
"Service": self.Service,
|
|
112
|
+
"SLA": self.SLA,
|
|
113
|
+
"Owner": self.Owner,
|
|
114
|
+
"Responsible": self.Responsible,
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass(slots=True)
|
|
120
|
+
class TicketCreateArticle:
|
|
121
|
+
Subject: str
|
|
122
|
+
Body: str
|
|
123
|
+
ContentType: str
|
|
124
|
+
Charset: str | None = None
|
|
125
|
+
MimeType: str | None = None
|
|
126
|
+
SenderType: str | None = None
|
|
127
|
+
From_: str | None = None
|
|
128
|
+
|
|
129
|
+
def validate(self) -> None:
|
|
130
|
+
_require_non_empty(self.Subject, "Article.Subject")
|
|
131
|
+
_require_non_empty(self.Body, "Article.Body")
|
|
132
|
+
_require_non_empty(self.ContentType, "Article.ContentType")
|
|
133
|
+
|
|
134
|
+
def to_dict(self) -> dict[str, Any]:
|
|
135
|
+
self.validate()
|
|
136
|
+
return _clean_dict(
|
|
137
|
+
{
|
|
138
|
+
"Subject": self.Subject,
|
|
139
|
+
"Body": self.Body,
|
|
140
|
+
"ContentType": self.ContentType,
|
|
141
|
+
"Charset": self.Charset,
|
|
142
|
+
"MimeType": self.MimeType,
|
|
143
|
+
"SenderType": self.SenderType,
|
|
144
|
+
"From": self.From_,
|
|
145
|
+
}
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@dataclass(slots=True)
|
|
150
|
+
class TicketCreatePayload:
|
|
151
|
+
Ticket: TicketCreateTicket
|
|
152
|
+
Article: TicketCreateArticle
|
|
153
|
+
DynamicField: Mapping[str, Any] | None = None
|
|
154
|
+
Attachment: list[Mapping[str, Any]] | None = None
|
|
155
|
+
TimeUnit: int | None = None
|
|
156
|
+
|
|
157
|
+
def to_dict(self) -> dict[str, Any]:
|
|
158
|
+
return _clean_dict(
|
|
159
|
+
{
|
|
160
|
+
"Ticket": self.Ticket.to_dict(),
|
|
161
|
+
"Article": self.Article.to_dict(),
|
|
162
|
+
"DynamicField": self.DynamicField,
|
|
163
|
+
"Attachment": self.Attachment,
|
|
164
|
+
"TimeUnit": self.TimeUnit,
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class TicketUpdateTicket(TicketCreateTicket):
|
|
171
|
+
def validate(self):
|
|
172
|
+
pass
|
pyznuny/ticket/routes.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Mapping
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from pyznuny.ticket.models import TicketCreatePayload
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from pyznuny.ticket.client import TicketClient
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SessionRoutes:
|
|
14
|
+
def __init__(self, client: "TicketClient") -> None:
|
|
15
|
+
self._client = client
|
|
16
|
+
|
|
17
|
+
def create(self, username: str, password: str) -> httpx.Response:
|
|
18
|
+
"""
|
|
19
|
+
Creates a new session with the given username and password.
|
|
20
|
+
|
|
21
|
+
:param username: username for authentication
|
|
22
|
+
:type username: str
|
|
23
|
+
:param password: password for authentication
|
|
24
|
+
:type password: str
|
|
25
|
+
:return: Response object containing session details
|
|
26
|
+
:rtype: Response
|
|
27
|
+
"""
|
|
28
|
+
return self._client.request(
|
|
29
|
+
"session_create",
|
|
30
|
+
json={"UserLogin": username, "Password": password},
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TicketRoutes:
|
|
35
|
+
def __init__(self, client: "TicketClient") -> None:
|
|
36
|
+
self._client = client
|
|
37
|
+
|
|
38
|
+
def create(
|
|
39
|
+
self,
|
|
40
|
+
payload: TicketCreatePayload | Mapping[str, Any] | None = None,
|
|
41
|
+
**payload_kwargs: Any,
|
|
42
|
+
) -> httpx.Response:
|
|
43
|
+
|
|
44
|
+
if payload is None:
|
|
45
|
+
payload_dict = dict(payload_kwargs)
|
|
46
|
+
|
|
47
|
+
elif isinstance(payload, TicketCreatePayload):
|
|
48
|
+
payload_dict = payload.to_dict()
|
|
49
|
+
payload_dict.update(payload_kwargs)
|
|
50
|
+
else:
|
|
51
|
+
payload_dict = dict(payload)
|
|
52
|
+
payload_dict.update(payload_kwargs)
|
|
53
|
+
|
|
54
|
+
payload_dict.update({"SessionID": self._client.session_id})
|
|
55
|
+
return self._client.request("ticket_create", json=payload_dict)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def update(self, ticket_id: str | int , **payload: dict) -> httpx.Response:
|
|
59
|
+
identifier = self._client.endpoint_identifier("ticket_update")
|
|
60
|
+
|
|
61
|
+
payload.update({"SessionID": self._client.session_id})
|
|
62
|
+
return self._client.request(
|
|
63
|
+
"ticket_update",
|
|
64
|
+
path_params={identifier: ticket_id},
|
|
65
|
+
json=payload,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def get(self, ticket_id: str | int,
|
|
69
|
+
dynamic_fields:int=0,
|
|
70
|
+
all_articles:int=0) -> httpx.Response:
|
|
71
|
+
identifier = self._client.endpoint_identifier("ticket_get")
|
|
72
|
+
return self._client.request(
|
|
73
|
+
"ticket_get",
|
|
74
|
+
path_params={identifier: ticket_id},
|
|
75
|
+
params={"SessionID": self._client.session_id,
|
|
76
|
+
"DynamicFields": dynamic_fields,
|
|
77
|
+
"AllArticles": all_articles},
|
|
78
|
+
)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyznuny
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A Python client for interacting with the Znuny ticketing system API.
|
|
5
|
+
Author-email: Junior Rosa <jr.dasrosas@gmail.com>, Pablo Gascon <pablogasconiel445@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/Junior-Rosa/py-znuny
|
|
7
|
+
Project-URL: Repository, https://github.com/Junior-Rosa/py-znuny
|
|
8
|
+
Project-URL: Issues, https://github.com/Junior-Rosa/py-znuny/issues
|
|
9
|
+
Requires-Python: >=3.14
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: httpx>=0.28.1
|
|
12
|
+
Requires-Dist: pydantic>=2.12.5
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
pyznuny/__init__.py,sha256=2hs3juXhAKpKyn_poQYzFr8ns0jmf_uBNKxT6u6w4Wo,68
|
|
2
|
+
pyznuny/ticket/__init__.py,sha256=lnWfZ1Al7LLLogUK_EUS_8WSNrRKzoJdbuYxpMXnUcA,215
|
|
3
|
+
pyznuny/ticket/client.py,sha256=kfYZIKFrCEJ7-A7aHF9ZMl_uw3tVj_1FAy3vrRw8JlI,8641
|
|
4
|
+
pyznuny/ticket/endpoints.py,sha256=nX5zJxOtwiwg3rtnt_7h4tRgSTyKPwof0i1sqqxljug,4444
|
|
5
|
+
pyznuny/ticket/exceptions.py,sha256=fqJFlMY1wl3Fb98Fz2odolOIyaDhKEwH1kP8Uv2k9-s,535
|
|
6
|
+
pyznuny/ticket/models.py,sha256=ezUxGc_Ftot0J2Entz5NXB7oXAeBIiuulN7NlxXTVTc,4692
|
|
7
|
+
pyznuny/ticket/routes.py,sha256=54cr08Z2NM9JwWCkY47E1Kj94Uen9d1PJXrJ24mb2nM,2514
|
|
8
|
+
pyznuny-0.0.1.dist-info/METADATA,sha256=xQPKpX-wpBmKYxvy5iFeyPLBaO-cNY-Z4xw2uD0pc_U,544
|
|
9
|
+
pyznuny-0.0.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
10
|
+
pyznuny-0.0.1.dist-info/top_level.txt,sha256=ki9uLRbo2oQCeiEaaYUcnYMj1mD_dkIhQroMCdJJXmk,8
|
|
11
|
+
pyznuny-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyznuny
|