snipepy 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.
Files changed (54) hide show
  1. snipepy/__init__.py +73 -0
  2. snipepy/_config.py +45 -0
  3. snipepy/_constants.py +15 -0
  4. snipepy/_http_client.py +382 -0
  5. snipepy/_whitelist.py +70 -0
  6. snipepy/models/__init__.py +111 -0
  7. snipepy/models/_shared.py +125 -0
  8. snipepy/models/accessories.py +59 -0
  9. snipepy/models/asset_maintenances.py +55 -0
  10. snipepy/models/asset_models.py +47 -0
  11. snipepy/models/assets.py +170 -0
  12. snipepy/models/categories.py +39 -0
  13. snipepy/models/companies.py +35 -0
  14. snipepy/models/components.py +79 -0
  15. snipepy/models/consumables.py +43 -0
  16. snipepy/models/custom_fields.py +67 -0
  17. snipepy/models/departments.py +33 -0
  18. snipepy/models/groups.py +30 -0
  19. snipepy/models/licenses.py +99 -0
  20. snipepy/models/locations.py +41 -0
  21. snipepy/models/manufacturers.py +39 -0
  22. snipepy/models/reports.py +68 -0
  23. snipepy/models/settings.py +25 -0
  24. snipepy/models/status_labels.py +34 -0
  25. snipepy/models/suppliers.py +45 -0
  26. snipepy/models/users.py +60 -0
  27. snipepy/py.typed +0 -0
  28. snipepy/services/__init__.py +43 -0
  29. snipepy/services/_pagination.py +28 -0
  30. snipepy/services/accessories.py +126 -0
  31. snipepy/services/asset_maintenances.py +109 -0
  32. snipepy/services/asset_models.py +97 -0
  33. snipepy/services/assets.py +517 -0
  34. snipepy/services/categories.py +80 -0
  35. snipepy/services/companies.py +68 -0
  36. snipepy/services/components.py +123 -0
  37. snipepy/services/consumables.py +106 -0
  38. snipepy/services/custom_fields.py +118 -0
  39. snipepy/services/departments.py +91 -0
  40. snipepy/services/groups.py +66 -0
  41. snipepy/services/licenses.py +146 -0
  42. snipepy/services/locations.py +113 -0
  43. snipepy/services/manufacturers.py +78 -0
  44. snipepy/services/reports.py +64 -0
  45. snipepy/services/settings.py +53 -0
  46. snipepy/services/status_labels.py +110 -0
  47. snipepy/services/suppliers.py +93 -0
  48. snipepy/services/users.py +180 -0
  49. snipepy/snipepy.py +144 -0
  50. snipepy-0.1.0.dist-info/METADATA +138 -0
  51. snipepy-0.1.0.dist-info/RECORD +54 -0
  52. snipepy-0.1.0.dist-info/WHEEL +5 -0
  53. snipepy-0.1.0.dist-info/licenses/LICENSE +73 -0
  54. snipepy-0.1.0.dist-info/top_level.txt +1 -0
snipepy/__init__.py ADDED
@@ -0,0 +1,73 @@
1
+ """Snipepy - Python wrapper for the SnipeIT REST API."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ from snipepy._config import Config
6
+ from snipepy._http_client import (
7
+ HttpClient,
8
+ HttpClientProtocol,
9
+ SnipepyAuthError,
10
+ SnipepyConflictError,
11
+ SnipepyError,
12
+ SnipepyForbiddenError,
13
+ SnipepyNotFoundError,
14
+ SnipepyRateLimitError,
15
+ SnipepyServerError,
16
+ SnipepyValidationError,
17
+ )
18
+ from snipepy.snipepy import (
19
+ AccessoriesService,
20
+ AssetMaintenancesService,
21
+ AssetModelsService,
22
+ AssetsService,
23
+ CategoriesService,
24
+ CompaniesService,
25
+ ComponentsService,
26
+ ConsumablesService,
27
+ CustomFieldsService,
28
+ DepartmentsService,
29
+ LicensesService,
30
+ LocationsService,
31
+ ManufacturersService,
32
+ ReportsService,
33
+ SettingsService,
34
+ Snipepy,
35
+ StatusLabelsService,
36
+ SuppliersService,
37
+ UserGroupsService,
38
+ UsersService,
39
+ )
40
+
41
+ __all__ = [
42
+ "AccessoriesService",
43
+ "AssetMaintenancesService",
44
+ "AssetModelsService",
45
+ "AssetsService",
46
+ "CategoriesService",
47
+ "CompaniesService",
48
+ "ComponentsService",
49
+ "Config",
50
+ "ConsumablesService",
51
+ "CustomFieldsService",
52
+ "DepartmentsService",
53
+ "HttpClient",
54
+ "HttpClientProtocol",
55
+ "LicensesService",
56
+ "LocationsService",
57
+ "ManufacturersService",
58
+ "ReportsService",
59
+ "SettingsService",
60
+ "Snipepy",
61
+ "SnipepyAuthError",
62
+ "SnipepyConflictError",
63
+ "SnipepyError",
64
+ "SnipepyForbiddenError",
65
+ "SnipepyNotFoundError",
66
+ "SnipepyRateLimitError",
67
+ "SnipepyServerError",
68
+ "SnipepyValidationError",
69
+ "StatusLabelsService",
70
+ "SuppliersService",
71
+ "UserGroupsService",
72
+ "UsersService",
73
+ ]
snipepy/_config.py ADDED
@@ -0,0 +1,45 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from dataclasses import dataclass
5
+
6
+ from dotenv import load_dotenv
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class Config:
11
+ url: str
12
+ token: str
13
+ timeout: int = 30
14
+ verify_ssl: bool = False
15
+
16
+ @classmethod
17
+ def from_env(cls, dotenv_path: str | None = None) -> Config:
18
+ load_dotenv(dotenv_path, override=True)
19
+
20
+ url: str = os.environ.get("SNIPEPY_URL", "").rstrip("/")
21
+ token: str = os.environ.get("SNIPEPY_API_TOKEN", "")
22
+
23
+ # Collect names of missing required environment variables
24
+ missing: list[str] = [
25
+ name
26
+ for name, val in (
27
+ ("SNIPEPY_URL", url),
28
+ ("SNIPEPY_API_TOKEN", token),
29
+ )
30
+ if not val
31
+ ]
32
+ # Raise ValueError if any required environment variables are missing
33
+ if missing:
34
+ raise ValueError(
35
+ f"Missing required environment variables: {', '.join(missing)}"
36
+ )
37
+
38
+ timeout: int = int(os.environ.get("SNIPEPY_TIMEOUT", "30"))
39
+ verify_ssl: bool = os.environ.get(
40
+ "SNIPEPY_VERIFY_SSL", "false"
41
+ ).lower() in ("true", "1", "yes")
42
+
43
+ return cls(
44
+ url=url, token=token, timeout=timeout, verify_ssl=verify_ssl
45
+ )
snipepy/_constants.py ADDED
@@ -0,0 +1,15 @@
1
+ """HTTP status code constants for SnipeIT API error handling."""
2
+
3
+ from __future__ import annotations
4
+
5
+ # HTTP 4xx Client Error codes
6
+ HTTP_400_BAD_REQUEST = 400
7
+ HTTP_401_UNAUTHORIZED = 401
8
+ HTTP_403_FORBIDDEN = 403
9
+ HTTP_404_NOT_FOUND = 404
10
+ HTTP_409_CONFLICT = 409
11
+ HTTP_429_TOO_MANY_REQUESTS = 429
12
+
13
+ # HTTP 5xx - 6xx Server Error codes
14
+ HTTP_5XX_MIN = 500
15
+ HTTP_5XX_MAX = 600
@@ -0,0 +1,382 @@
1
+ """HTTP client for SnipeIT API calls."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import html
6
+ import time
7
+ from collections.abc import Callable
8
+ from typing import Any, Protocol
9
+
10
+ import requests
11
+ from requests.models import Response
12
+
13
+ from snipepy import Config
14
+ from snipepy._constants import (
15
+ HTTP_5XX_MAX,
16
+ HTTP_5XX_MIN,
17
+ HTTP_401_UNAUTHORIZED,
18
+ HTTP_403_FORBIDDEN,
19
+ HTTP_404_NOT_FOUND,
20
+ HTTP_409_CONFLICT,
21
+ HTTP_429_TOO_MANY_REQUESTS,
22
+ )
23
+
24
+ _DEFAULT_MAX_RETRIES: int = 3
25
+ _DEFAULT_RETRY_AFTER: int = 10
26
+
27
+
28
+ class SnipepyError(Exception):
29
+ """Base exception for all Snipepy errors."""
30
+
31
+
32
+ class SnipepyAuthError(SnipepyError):
33
+ """Raised when authentication fails (HTTP 401)."""
34
+
35
+
36
+ class SnipepyForbiddenError(SnipepyError):
37
+ """Raised when access is forbidden (HTTP 403)."""
38
+
39
+
40
+ class SnipepyNotFoundError(SnipepyError):
41
+ """Raised when a resource is not found (HTTP 404)."""
42
+
43
+
44
+ class SnipepyConflictError(SnipepyError):
45
+ """Raised when there is a conflict (HTTP 409)."""
46
+
47
+
48
+ class SnipepyValidationError(SnipepyError):
49
+ """Raised when SnipeIT returns status: error (validation failure)."""
50
+
51
+
52
+ class SnipepyServerError(SnipepyError):
53
+ """Raised when server returns 5xx error."""
54
+
55
+ response: Response | None
56
+
57
+ def __init__(self, message: str, response: Response | None = None) -> None:
58
+ super().__init__(message)
59
+ self.response = response
60
+
61
+
62
+ class SnipepyRateLimitError(SnipepyError):
63
+ """Raised when the API rate limit is exceeded and retries exhausted."""
64
+
65
+
66
+ # OCP: map status codes to exceptions; extend by adding entries here
67
+ _STATUS_EXCEPTIONS: dict[int, tuple[type[SnipepyError], str]] = {
68
+ HTTP_401_UNAUTHORIZED: (SnipepyAuthError, "Unauthorized"),
69
+ HTTP_403_FORBIDDEN: (SnipepyForbiddenError, "Forbidden"),
70
+ HTTP_404_NOT_FOUND: (SnipepyNotFoundError, "Not found"),
71
+ HTTP_409_CONFLICT: (SnipepyConflictError, "Conflict"),
72
+ }
73
+
74
+
75
+ def _is_listener_only_failure(response: Response) -> bool:
76
+ """Check if the 500 error is a listener-only failure.
77
+
78
+ SnipeIT's post-action event listeners (e.g., mail notifications) can
79
+ fail with a 500 error even when the main operation succeeded. The
80
+ stack trace in the error page contains ``Listeners/`` which indicates
81
+ the failure is in a post-action event listener.
82
+ """
83
+ return "listeners/" in response.text.lower()
84
+
85
+
86
+ class HttpClientProtocol(Protocol):
87
+ """Structural interface for SnipeIT HTTP communication."""
88
+
89
+ def get(self, path: str, params: dict[str, Any] | None = ...) -> Any: ...
90
+
91
+ def post(self, path: str, json: dict[str, Any] | None = ...) -> Any: ...
92
+
93
+ def put(self, path: str, json: dict[str, Any]) -> Any: ...
94
+
95
+ def patch(self, path: str, json: dict[str, Any]) -> Any: ...
96
+
97
+ def delete(self, path: str) -> Any: ...
98
+
99
+ def upload(self, path: str, files: dict[str, Any]) -> Any: ...
100
+
101
+ def post_binary(
102
+ self,
103
+ path: str,
104
+ json: dict[str, Any] | None = ...,
105
+ accept: str = ...,
106
+ ) -> bytes: ...
107
+
108
+ def get_binary(
109
+ self,
110
+ path: str,
111
+ params: dict[str, Any] | None = ...,
112
+ accept: str = ...,
113
+ ) -> bytes: ...
114
+
115
+
116
+ class HttpClient:
117
+ """HTTP client for SnipeIT API calls."""
118
+
119
+ _config: Config
120
+ _session: requests.Session
121
+ _max_retries: int
122
+ _retry_after: int
123
+
124
+ def __init__(
125
+ self,
126
+ config: Config,
127
+ max_retries: int = _DEFAULT_MAX_RETRIES,
128
+ retry_after: int = _DEFAULT_RETRY_AFTER,
129
+ ) -> None:
130
+ self._config = config
131
+ self._max_retries = max_retries
132
+ self._retry_after = retry_after
133
+ self._session = requests.Session()
134
+ self._session.headers.update(
135
+ {
136
+ "Authorization": f"Bearer {config.token}",
137
+ "Accept": "application/json",
138
+ }
139
+ )
140
+
141
+ def _execute_request(
142
+ self, make_request: Callable[[], Response]
143
+ ) -> Response:
144
+ """Execute a request callable, retrying on HTTP 429 with Retry-After.
145
+
146
+ Retries up to ``_max_retries`` times. On each 429 response the
147
+ ``Retry-After`` header value (seconds) is honoured; when the header is
148
+ absent the configured ``_retry_after`` default is used. After all
149
+ retries are exhausted ``SnipepyRateLimitError`` is raised.
150
+ """
151
+ for attempt in range(self._max_retries + 1):
152
+ response = make_request()
153
+ if response.status_code != HTTP_429_TOO_MANY_REQUESTS:
154
+ return response
155
+ if attempt < self._max_retries:
156
+ raw_wait = response.headers.get(
157
+ "Retry-After", str(self._retry_after)
158
+ )
159
+ try:
160
+ wait = int(raw_wait)
161
+ except ValueError:
162
+ wait = self._retry_after
163
+ time.sleep(wait)
164
+ retries = self._max_retries
165
+ word = "retry" if retries == 1 else "retries"
166
+ raise SnipepyRateLimitError(
167
+ f"Rate limit exceeded after {retries} {word}"
168
+ )
169
+
170
+ def _raise_for_status(
171
+ self, response: Response, exc: requests.HTTPError
172
+ ) -> None:
173
+ """Translate HTTP error codes into Snipepy exceptions."""
174
+ entry = _STATUS_EXCEPTIONS.get(response.status_code)
175
+ if entry is not None:
176
+ exc_class, message = entry
177
+ raise exc_class(message) from exc
178
+ if HTTP_5XX_MIN <= response.status_code < HTTP_5XX_MAX:
179
+ raise SnipepyServerError(
180
+ "Server error", response=response
181
+ ) from exc
182
+ raise exc
183
+
184
+ def _unescape_strings(self, data: Any) -> Any:
185
+ """Recursively unescape HTML entities in all strings."""
186
+ if isinstance(data, str):
187
+ return html.unescape(data)
188
+ if isinstance(data, dict):
189
+ return {k: self._unescape_strings(v) for k, v in data.items()}
190
+ if isinstance(data, list):
191
+ return [self._unescape_strings(item) for item in data]
192
+ return data
193
+
194
+ def _parse_payload(self, response: Response) -> Any:
195
+ """Parse JSON; raise SnipepyValidationError on status:error."""
196
+ payload = response.json()
197
+ if not isinstance(payload, dict):
198
+ return payload
199
+ if payload.get("status") == "error":
200
+ messages = payload.get("messages", {})
201
+ raise SnipepyValidationError(str(messages))
202
+ result = payload.get("payload", payload)
203
+ return self._unescape_strings(result)
204
+
205
+ def _build_url(self, path: str, params: dict[str, Any] | None) -> str:
206
+ """Build a URL with optional query string; None params excluded."""
207
+ path_name: str = path.lstrip("/")
208
+ base_url: str = f"{self._config.url.rstrip('/')}/api/v1/{path_name}"
209
+
210
+ if not params:
211
+ return base_url
212
+
213
+ filtered_params = {k: v for k, v in params.items() if v is not None}
214
+
215
+ if not filtered_params:
216
+ return base_url
217
+
218
+ query_string = "&".join(f"{k}={v}" for k, v in filtered_params.items())
219
+ return f"{base_url}?{query_string}"
220
+
221
+ def _request(self, method: str, path: str) -> Any:
222
+ """Make a raw HTTP request (no params, no body)."""
223
+ url: str = self._build_url(path, None)
224
+ response = self._execute_request(
225
+ lambda: self._session.request(
226
+ method=method,
227
+ url=url,
228
+ timeout=self._config.timeout,
229
+ verify=self._config.verify_ssl,
230
+ )
231
+ )
232
+ try:
233
+ response.raise_for_status()
234
+ except requests.HTTPError as e:
235
+ self._raise_for_status(response, e)
236
+ return self._parse_payload(response)
237
+
238
+ def get(self, path: str, params: dict[str, Any] | None = None) -> Any:
239
+ """Make a GET request."""
240
+ url: str = self._build_url(path, params)
241
+ response = self._execute_request(
242
+ lambda: self._session.get(
243
+ url=url,
244
+ timeout=self._config.timeout,
245
+ verify=self._config.verify_ssl,
246
+ )
247
+ )
248
+ try:
249
+ response.raise_for_status()
250
+ except requests.HTTPError as e:
251
+ self._raise_for_status(response, e)
252
+ return self._parse_payload(response)
253
+
254
+ def post(self, path: str, json: dict[str, Any] | None = None) -> Any:
255
+ """Make a POST request."""
256
+ url: str = self._build_url(path, None)
257
+ response = self._execute_request(
258
+ lambda: self._session.post(
259
+ url=url,
260
+ json=json,
261
+ timeout=self._config.timeout,
262
+ verify=self._config.verify_ssl,
263
+ )
264
+ )
265
+ try:
266
+ response.raise_for_status()
267
+ except requests.HTTPError as e:
268
+ self._raise_for_status(response, e)
269
+ return self._parse_payload(response)
270
+
271
+ def put(self, path: str, json: dict[str, Any]) -> Any:
272
+ """Make a PUT request."""
273
+ url: str = self._build_url(path, None)
274
+ response = self._execute_request(
275
+ lambda: self._session.put(
276
+ url=url,
277
+ json=json,
278
+ timeout=self._config.timeout,
279
+ verify=self._config.verify_ssl,
280
+ )
281
+ )
282
+ try:
283
+ response.raise_for_status()
284
+ except requests.HTTPError as e:
285
+ self._raise_for_status(response, e)
286
+ return self._parse_payload(response)
287
+
288
+ def patch(self, path: str, json: dict[str, Any]) -> Any:
289
+ """Make a PATCH request."""
290
+ url: str = self._build_url(path, None)
291
+ response = self._execute_request(
292
+ lambda: self._session.patch(
293
+ url=url,
294
+ json=json,
295
+ timeout=self._config.timeout,
296
+ verify=self._config.verify_ssl,
297
+ )
298
+ )
299
+ try:
300
+ response.raise_for_status()
301
+ except requests.HTTPError as e:
302
+ self._raise_for_status(response, e)
303
+ return self._parse_payload(response)
304
+
305
+ def delete(self, path: str) -> Any:
306
+ """Make a DELETE request."""
307
+ url: str = self._build_url(path, None)
308
+ response = self._execute_request(
309
+ lambda: self._session.delete(
310
+ url=url,
311
+ timeout=self._config.timeout,
312
+ verify=self._config.verify_ssl,
313
+ )
314
+ )
315
+ try:
316
+ response.raise_for_status()
317
+ except requests.HTTPError as e:
318
+ self._raise_for_status(response, e)
319
+ return self._parse_payload(response)
320
+
321
+ def upload(self, path: str, files: dict[str, Any]) -> Any:
322
+ """POST a multipart file upload to the SnipeIT API."""
323
+ url: str = self._build_url(path, None)
324
+ response = self._execute_request(
325
+ lambda: self._session.request(
326
+ method="POST",
327
+ url=url,
328
+ files=files,
329
+ timeout=self._config.timeout,
330
+ verify=self._config.verify_ssl,
331
+ )
332
+ )
333
+ try:
334
+ response.raise_for_status()
335
+ except requests.HTTPError as e:
336
+ self._raise_for_status(response, e)
337
+ return self._parse_payload(response)
338
+
339
+ def post_binary(
340
+ self,
341
+ path: str,
342
+ json: dict[str, Any] | None = None,
343
+ accept: str = "application/octet-stream",
344
+ ) -> bytes:
345
+ """POST with JSON body and return binary response content."""
346
+ url: str = self._build_url(path, None)
347
+ response = self._execute_request(
348
+ lambda: self._session.post(
349
+ url=url,
350
+ json=json,
351
+ timeout=self._config.timeout,
352
+ verify=self._config.verify_ssl,
353
+ headers={"Accept": accept},
354
+ )
355
+ )
356
+ try:
357
+ response.raise_for_status()
358
+ except requests.HTTPError as e:
359
+ self._raise_for_status(response, e)
360
+ return bytes(response.content)
361
+
362
+ def get_binary(
363
+ self,
364
+ path: str,
365
+ params: dict[str, Any] | None = None,
366
+ accept: str = "application/octet-stream",
367
+ ) -> bytes:
368
+ """GET a binary response (PDF, PNG, ZIP) from the SnipeIT API."""
369
+ url: str = self._build_url(path, params)
370
+ response = self._execute_request(
371
+ lambda: self._session.get(
372
+ url=url,
373
+ timeout=self._config.timeout,
374
+ verify=self._config.verify_ssl,
375
+ headers={"Accept": accept},
376
+ )
377
+ )
378
+ try:
379
+ response.raise_for_status()
380
+ except requests.HTTPError as e:
381
+ self._raise_for_status(response, e)
382
+ return bytes(response.content)
snipepy/_whitelist.py ADDED
@@ -0,0 +1,70 @@
1
+ # Vulture whitelist - public API surface used by callers outside snipepy/
2
+ from snipepy import Config, Snipepy
3
+ from snipepy._http_client import (
4
+ HttpClientProtocol,
5
+ SnipepyAuthError,
6
+ SnipepyConflictError,
7
+ SnipepyError,
8
+ SnipepyForbiddenError,
9
+ SnipepyNotFoundError,
10
+ SnipepyRateLimitError,
11
+ SnipepyServerError,
12
+ SnipepyValidationError,
13
+ )
14
+ from snipepy.models.components import ComponentAsset, ComponentAssetList
15
+ from snipepy.snipepy import (
16
+ AccessoriesService,
17
+ AssetMaintenancesService,
18
+ AssetModelsService,
19
+ AssetsService,
20
+ CategoriesService,
21
+ CompaniesService,
22
+ ComponentsService,
23
+ ConsumablesService,
24
+ CustomFieldsService,
25
+ DepartmentsService,
26
+ LicensesService,
27
+ LocationsService,
28
+ ManufacturersService,
29
+ ReportsService,
30
+ SettingsService,
31
+ StatusLabelsService,
32
+ SuppliersService,
33
+ UserGroupsService,
34
+ UsersService,
35
+ )
36
+
37
+ _ = (
38
+ ComponentAsset,
39
+ ComponentAssetList,
40
+ Config.from_env,
41
+ Snipepy.from_env,
42
+ HttpClientProtocol,
43
+ SnipepyError,
44
+ SnipepyAuthError,
45
+ SnipepyForbiddenError,
46
+ SnipepyNotFoundError,
47
+ SnipepyConflictError,
48
+ SnipepyValidationError,
49
+ SnipepyServerError,
50
+ SnipepyRateLimitError,
51
+ AssetsService,
52
+ UsersService,
53
+ LicensesService,
54
+ AccessoriesService,
55
+ ComponentsService,
56
+ ConsumablesService,
57
+ LocationsService,
58
+ CompaniesService,
59
+ CategoriesService,
60
+ ManufacturersService,
61
+ AssetModelsService,
62
+ StatusLabelsService,
63
+ DepartmentsService,
64
+ UserGroupsService,
65
+ SuppliersService,
66
+ AssetMaintenancesService,
67
+ SettingsService,
68
+ ReportsService,
69
+ CustomFieldsService,
70
+ )
@@ -0,0 +1,111 @@
1
+ """Pydantic models for SnipeIT resources."""
2
+
3
+ from snipepy.models._shared import (
4
+ AssignedTo,
5
+ AvailableActions,
6
+ DateField,
7
+ DatetimeField,
8
+ NamedObject,
9
+ StatusLabelRef,
10
+ )
11
+ from snipepy.models.accessories import (
12
+ Accessory,
13
+ AccessoryCheckedOut,
14
+ AccessoryCheckedOutList,
15
+ AccessoryList,
16
+ )
17
+ from snipepy.models.asset_maintenances import (
18
+ AssetMaintenance,
19
+ AssetMaintenanceList,
20
+ AssetRef,
21
+ )
22
+ from snipepy.models.asset_models import AssetModel, AssetModelList
23
+ from snipepy.models.assets import Asset, AssetFile, AssetFileList, AssetList
24
+ from snipepy.models.categories import Category, CategoryList
25
+ from snipepy.models.companies import Company, CompanyList
26
+ from snipepy.models.components import Component, ComponentList
27
+ from snipepy.models.consumables import Consumable, ConsumableList
28
+ from snipepy.models.custom_fields import (
29
+ CustomField,
30
+ CustomFieldList,
31
+ Fieldset,
32
+ FieldsetList,
33
+ )
34
+ from snipepy.models.departments import Department, DepartmentList
35
+ from snipepy.models.groups import Group, GroupList
36
+ from snipepy.models.licenses import (
37
+ License,
38
+ LicenseList,
39
+ LicenseSeat,
40
+ LicenseSeatList,
41
+ )
42
+ from snipepy.models.locations import Location, LocationList
43
+ from snipepy.models.manufacturers import Manufacturer, ManufacturerList
44
+ from snipepy.models.reports import (
45
+ ActivityLog,
46
+ ActivityLogItem,
47
+ DepreciationAsset,
48
+ DepreciationList,
49
+ )
50
+ from snipepy.models.settings import Backup, BackupList
51
+ from snipepy.models.status_labels import StatusLabel, StatusLabelList
52
+ from snipepy.models.suppliers import Supplier, SupplierList
53
+ from snipepy.models.users import User, UserList
54
+
55
+ __all__ = [
56
+ "Accessory",
57
+ "AccessoryCheckedOut",
58
+ "AccessoryCheckedOutList",
59
+ "AccessoryList",
60
+ "ActivityLog",
61
+ "ActivityLogItem",
62
+ "Asset",
63
+ "AssetFile",
64
+ "AssetFileList",
65
+ "AssetList",
66
+ "AssetMaintenance",
67
+ "AssetMaintenanceList",
68
+ "AssetModel",
69
+ "AssetModelList",
70
+ "AssetRef",
71
+ "AssignedTo",
72
+ "AvailableActions",
73
+ "Backup",
74
+ "BackupList",
75
+ "Category",
76
+ "CategoryList",
77
+ "Company",
78
+ "CompanyList",
79
+ "Component",
80
+ "ComponentList",
81
+ "Consumable",
82
+ "ConsumableList",
83
+ "CustomField",
84
+ "CustomFieldList",
85
+ "DateField",
86
+ "DatetimeField",
87
+ "Department",
88
+ "DepartmentList",
89
+ "DepreciationAsset",
90
+ "DepreciationList",
91
+ "Fieldset",
92
+ "FieldsetList",
93
+ "Group",
94
+ "GroupList",
95
+ "License",
96
+ "LicenseList",
97
+ "LicenseSeat",
98
+ "LicenseSeatList",
99
+ "Location",
100
+ "LocationList",
101
+ "Manufacturer",
102
+ "ManufacturerList",
103
+ "NamedObject",
104
+ "StatusLabel",
105
+ "StatusLabelList",
106
+ "StatusLabelRef",
107
+ "Supplier",
108
+ "SupplierList",
109
+ "User",
110
+ "UserList",
111
+ ]