spoolman 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.
spoolman/__init__.py ADDED
@@ -0,0 +1,16 @@
1
+ """Asynchronous Python client for Spoolman."""
2
+
3
+ from .exceptions import SpoolmanConnectionError, SpoolmanError, SpoolmanResponseError
4
+ from .models import Filament, Info, Spool, Vendor
5
+ from .spoolman import Spoolman
6
+
7
+ __all__ = [
8
+ "Filament",
9
+ "Info",
10
+ "Spool",
11
+ "Spoolman",
12
+ "SpoolmanConnectionError",
13
+ "SpoolmanError",
14
+ "SpoolmanResponseError",
15
+ "Vendor",
16
+ ]
spoolman/exceptions.py ADDED
@@ -0,0 +1,17 @@
1
+ """Asynchronous Python client for Spoolman."""
2
+
3
+
4
+ class SpoolmanError(Exception):
5
+ """Base class for Spoolman exceptions."""
6
+
7
+
8
+ class SpoolmanConnectionError(SpoolmanError):
9
+ """Error raised when connection to the API fails."""
10
+
11
+
12
+ class SpoolmanResponseError(SpoolmanError):
13
+ """Error raised when the API returns a error."""
14
+
15
+ def __init__(self, data: dict[str, str], code: int) -> None:
16
+ """Initialize the exception."""
17
+ super().__init__(f'{data["message"]} (code: {code})')
spoolman/models.py ADDED
@@ -0,0 +1,77 @@
1
+ """Asynchronous Python client for Spoolman."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from datetime import datetime # noqa: TC003
7
+
8
+ from mashumaro import DataClassDictMixin, field_options
9
+
10
+
11
+ @dataclass
12
+ class Info(DataClassDictMixin):
13
+ """Data class for the 'info' endpoint."""
14
+
15
+ version: str
16
+ debug_mode: bool
17
+ automatic_backups: bool
18
+ db_type: str
19
+ git_commit: str
20
+ build_date: datetime
21
+ data_dir: str
22
+ logs_dir: str
23
+ backups_dir: str
24
+
25
+
26
+ @dataclass
27
+ class Filament(DataClassDictMixin):
28
+ """Data class for filament data."""
29
+
30
+ id: int
31
+ name: str
32
+ color: str = field(metadata=field_options(alias="color_hex"))
33
+ vendor: Vendor
34
+ external_id: str
35
+ registered: datetime
36
+
37
+ material: str
38
+ density: float
39
+ diameter: float
40
+ weight: float
41
+ spool_weight: float
42
+ extruder_temp: int = field(metadata=field_options(alias="settings_extruder_temp"))
43
+ bed_temp: int = field(metadata=field_options(alias="settings_bed_temp"))
44
+
45
+
46
+ @dataclass
47
+ class Spool(DataClassDictMixin):
48
+ """Data class for spool data."""
49
+
50
+ id: int
51
+ filament: Filament
52
+
53
+ initial_weight: float
54
+ spool_weight: float
55
+ used_weight: float
56
+ used_length: float
57
+ remaining_weight: float
58
+ remaining_length: float
59
+
60
+ archived: bool
61
+ registered: datetime
62
+ first_used: datetime | None = field(
63
+ default=None, metadata=field_options(alias="first_used")
64
+ )
65
+ last_used: datetime | None = field(
66
+ default=None, metadata=field_options(alias="last_used")
67
+ )
68
+
69
+
70
+ @dataclass
71
+ class Vendor(DataClassDictMixin):
72
+ """Data class for vendor data."""
73
+
74
+ id: int
75
+ name: str
76
+ external_id: str
77
+ registered: datetime
spoolman/py.typed ADDED
File without changes
spoolman/spoolman.py ADDED
@@ -0,0 +1,232 @@
1
+ """Asynchronous Python client for Spoolman."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import socket
7
+ from dataclasses import dataclass
8
+ from importlib import metadata
9
+ from typing import Any, Self, cast
10
+
11
+ from aiohttp import ClientError, ClientSession
12
+ from aiohttp.hdrs import METH_GET
13
+ from yarl import URL
14
+
15
+ from .exceptions import SpoolmanConnectionError, SpoolmanError, SpoolmanResponseError
16
+ from .models import Filament, Info, Spool, Vendor
17
+
18
+ VERSION = metadata.version(__package__)
19
+
20
+
21
+ @dataclass
22
+ class Spoolman:
23
+ """Main class for handling connections with the Spoolman API."""
24
+
25
+ host: str
26
+ port: int = 7912
27
+
28
+ request_timeout: float = 10.0
29
+ session: ClientSession | None = None
30
+
31
+ _close_session: bool = False
32
+
33
+ async def _request(
34
+ self,
35
+ uri: str,
36
+ *,
37
+ method: str = METH_GET,
38
+ params: dict[str, Any] | None = None,
39
+ ) -> Any:
40
+ """Handle a request to the Spoolman API.
41
+
42
+ Args:
43
+ ----
44
+ uri: Request URI, without '/api/', for example, 'status'.
45
+ method: HTTP method to use.
46
+ params: Extra options to improve or limit the response.
47
+
48
+ Returns:
49
+ -------
50
+ A Python dictionary (JSON decoded) with the response from
51
+ the Spoolman API.
52
+
53
+ Raises:
54
+ ------
55
+ SpoolmanConnectionError: If the connection to the API fails.
56
+ SpoolmanError: If the API returns an error.
57
+ SpoolmanResponseError: If the API returns a error.
58
+
59
+ """
60
+ url = URL.build(
61
+ scheme="http",
62
+ host=self.host,
63
+ port=int(self.port),
64
+ path="/api/v1/",
65
+ ).join(URL(uri))
66
+
67
+ headers = {
68
+ "User-Agent": f"PythonSpoolman/{VERSION}",
69
+ "Accept": "application/json",
70
+ }
71
+
72
+ if self.session is None:
73
+ self.session = ClientSession()
74
+ self._close_session = True
75
+
76
+ try:
77
+ async with asyncio.timeout(self.request_timeout):
78
+ response = await self.session.request(
79
+ method,
80
+ url,
81
+ params=params,
82
+ headers=headers,
83
+ )
84
+
85
+ if response.status == 404:
86
+ response_data = await response.json()
87
+ raise SpoolmanResponseError(response_data, response.status)
88
+
89
+ response.raise_for_status()
90
+
91
+ except TimeoutError as exception:
92
+ msg = "Timeout occurred while connecting to the Spoolman API."
93
+ raise SpoolmanConnectionError(msg) from exception
94
+ except (ClientError, socket.gaierror) as exception:
95
+ msg = "Error occurred while connecting to the Spoolman API."
96
+ raise SpoolmanConnectionError(msg) from exception
97
+
98
+ content_type = response.headers.get("content-type", "")
99
+ if "application/json" not in content_type:
100
+ text = await response.text()
101
+ msg = "Unexpected response from the Spoolman API."
102
+ raise SpoolmanError(
103
+ msg,
104
+ {"Content-Type": content_type, "Response": text},
105
+ )
106
+
107
+ return cast(dict[str, Any], await response.json())
108
+
109
+ async def info(self) -> Info:
110
+ """Get information about the Spoolman API.
111
+
112
+ Returns
113
+ -------
114
+ A dictionary with information about the Spoolman API.
115
+
116
+ """
117
+ response = await self._request("info")
118
+ return Info.from_dict(response)
119
+
120
+ async def health(self) -> bool:
121
+ """Check the health of the Spoolman API.
122
+
123
+ Returns
124
+ -------
125
+ True if the API is healthy, False otherwise.
126
+
127
+ """
128
+ response: dict[str, str] = await self._request("health")
129
+ return response["status"] == "healthy"
130
+
131
+ async def get_filaments(self) -> list[Filament]:
132
+ """Get a list of all available filaments.
133
+
134
+ Returns
135
+ -------
136
+ A list with filament data.
137
+
138
+ """
139
+ response = await self._request("filament")
140
+ return [Filament.from_dict(item) for item in response]
141
+
142
+ async def get_filament(self, filament_id: int) -> Filament:
143
+ """Get a specific filament by ID.
144
+
145
+ Args:
146
+ ----
147
+ filament_id: The ID of the filament to retrieve.
148
+
149
+ Returns:
150
+ -------
151
+ A dictionary with the filament data.
152
+
153
+ """
154
+ response = await self._request(f"filament/{filament_id}")
155
+ return Filament.from_dict(response)
156
+
157
+ async def get_spools(self) -> list[Spool]:
158
+ """Get a list of all available spools.
159
+
160
+ Returns
161
+ -------
162
+ A list with spool data.
163
+
164
+ """
165
+ response = await self._request("spool")
166
+ return [Spool.from_dict(item) for item in response]
167
+
168
+ async def get_spool(self, spool_id: int) -> Spool:
169
+ """Get a specific spool by ID.
170
+
171
+ Args:
172
+ ----
173
+ spool_id: The ID of the spool to retrieve.
174
+
175
+ Returns:
176
+ -------
177
+ A dictionary with the spool data.
178
+
179
+ """
180
+ response = await self._request(f"spool/{spool_id}")
181
+ return Spool.from_dict(response)
182
+
183
+ async def get_vendors(self) -> list[Vendor]:
184
+ """Get a list of all available vendors.
185
+
186
+ Returns
187
+ -------
188
+ A list with vendor data.
189
+
190
+ """
191
+ response = await self._request("vendor")
192
+ return [Vendor.from_dict(item) for item in response]
193
+
194
+ async def get_vendor(self, vendor_id: int) -> Vendor:
195
+ """Get a specific vendor by ID.
196
+
197
+ Args:
198
+ ----
199
+ vendor_id: The ID of the vendor to retrieve.
200
+
201
+ Returns:
202
+ -------
203
+ A dictionary with the vendor data.
204
+
205
+ """
206
+ response = await self._request(f"vendor/{vendor_id}")
207
+ return Vendor.from_dict(response)
208
+
209
+ async def close(self) -> None:
210
+ """Close open client session."""
211
+ if self.session and self._close_session:
212
+ await self.session.close()
213
+
214
+ async def __aenter__(self) -> Self:
215
+ """Async enter.
216
+
217
+ Returns
218
+ -------
219
+ The Spoolman object.
220
+
221
+ """
222
+ return self
223
+
224
+ async def __aexit__(self, *_exc_info: object) -> None:
225
+ """Async exit.
226
+
227
+ Args:
228
+ ----
229
+ _exc_info: Exec type.
230
+
231
+ """
232
+ await self.close()
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Klaas Schoute
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,218 @@
1
+ Metadata-Version: 2.1
2
+ Name: spoolman
3
+ Version: 0.1.0
4
+ Summary: Asynchronous Python client for Spoolman API
5
+ Home-page: https://github.com/klaasnicolaas/python-spoolman
6
+ License: MIT
7
+ Keywords: spoolman,api,async,client
8
+ Author: Klaas Schoute
9
+ Author-email: hello@student-techlife.com
10
+ Maintainer: Klaas Schoute
11
+ Maintainer-email: hello@student-techlife.com
12
+ Requires-Python: >=3.11,<4.0
13
+ Classifier: Framework :: AsyncIO
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Natural Language :: English
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Dist: aiohttp (>=3.0.0)
23
+ Requires-Dist: mashumaro (>=3.15,<4.0)
24
+ Requires-Dist: yarl (>=1.6.0)
25
+ Project-URL: Bug Tracker, https://github.com/klaasnicolaas/python-spoolman/issues
26
+ Project-URL: Changelog, https://github.com/klaasnicolaas/python-spoolman/releases
27
+ Project-URL: Documentation, https://github.com/klaasnicolaas/python-spoolman
28
+ Project-URL: Repository, https://github.com/klaasnicolaas/python-spoolman
29
+ Description-Content-Type: text/markdown
30
+
31
+ <!-- Banner -->
32
+ ![alt Banner of the Spoolman package](https://raw.githubusercontent.com/klaasnicolaas/python-spoolman/main/assets/header_spoolman-min.png)
33
+
34
+ <!-- PROJECT SHIELDS -->
35
+ [![GitHub Release][releases-shield]][releases]
36
+ [![Python Versions][python-versions-shield]][pypi]
37
+ ![Project Stage][project-stage-shield]
38
+ ![Project Maintenance][maintenance-shield]
39
+ [![License][license-shield]](LICENSE)
40
+
41
+ [![GitHub Activity][commits-shield]][commits-url]
42
+ [![PyPi Downloads][downloads-shield]][downloads-url]
43
+ [![GitHub Last Commit][last-commit-shield]][commits-url]
44
+ [![Open in Dev Containers][devcontainer-shield]][devcontainer]
45
+
46
+ [![Build Status][build-shield]][build-url]
47
+ [![Typing Status][typing-shield]][typing-url]
48
+ [![Code Coverage][codecov-shield]][codecov-url]
49
+
50
+
51
+ Asynchronous Python client for [Spoolman][spoolman].
52
+
53
+ ## About
54
+
55
+ Spoolman is a self-hosted platform for managing 3D printer filament spools. It integrates with popular 3D printing software to track spool weights in real time, providing instant insights into filament usage.
56
+
57
+ ## Installation
58
+
59
+ ```bash
60
+ pip install spoolman
61
+ ```
62
+
63
+ ## Data
64
+
65
+ - Application info and health
66
+ - Filaments
67
+ - Spools
68
+ - Vendors
69
+
70
+ ### Example
71
+
72
+ ```python
73
+ import asyncio
74
+
75
+ from spoolman import Spoolman
76
+
77
+
78
+ async def main() -> None:
79
+ """Show example on using this package."""
80
+ async with Spoolman(host="IP_ADDRESS") as client:
81
+ # Get all filaments
82
+ filaments = await client.get_filaments()
83
+ print(filaments)
84
+
85
+
86
+ if __name__ == "__main__":
87
+ asyncio.run(main())
88
+ ```
89
+
90
+ More examples can be found in the [examples folder](./examples/).
91
+
92
+ ## Class Parameters
93
+
94
+ | Parameter | value Type | Description |
95
+ | :-------- | :--------- | :---------------------------------------------------------------- |
96
+ | `host` | `str` | The IP address of your Spoolman instance. |
97
+ | `port` | `int` | The port of your Spoolman instance (optional). Default is `7912`. |
98
+
99
+ ## Contributing
100
+
101
+ This is an active open-source project. We are always open to people who want to
102
+ use the code or contribute to it.
103
+
104
+ We've set up a separate document for our
105
+ [contribution guidelines](CONTRIBUTING.md).
106
+
107
+ Thank you for being involved! :heart_eyes:
108
+
109
+ ## Setting up development environment
110
+
111
+ The simplest way to begin is by utilizing the [Dev Container][devcontainer]
112
+ feature of Visual Studio Code or by opening a CodeSpace directly on GitHub.
113
+ By clicking the button below you immediately start a Dev Container in Visual Studio Code.
114
+
115
+ [![Open in Dev Containers][devcontainer-shield]][devcontainer]
116
+
117
+ This Python project relies on [Poetry][poetry] as its dependency manager,
118
+ providing comprehensive management and control over project dependencies.
119
+
120
+ You need at least:
121
+
122
+ - Python 3.11+
123
+ - [Poetry][poetry-install]
124
+
125
+ ### Installation
126
+
127
+ Install all packages, including all development requirements:
128
+
129
+ ```bash
130
+ poetry install
131
+ ```
132
+
133
+ _Poetry creates by default an virtual environment where it installs all
134
+ necessary pip packages_.
135
+
136
+ ### Pre-commit
137
+
138
+ This repository uses the [pre-commit][pre-commit] framework, all changes
139
+ are linted and tested with each commit. To setup the pre-commit check, run:
140
+
141
+ ```bash
142
+ poetry run pre-commit install
143
+ ```
144
+
145
+ And to run all checks and tests manually, use the following command:
146
+
147
+ ```bash
148
+ poetry run pre-commit run --all-files
149
+ ```
150
+
151
+ ### Testing
152
+
153
+ It uses [pytest](https://docs.pytest.org/en/stable/) as the test framework. To run the tests:
154
+
155
+ ```bash
156
+ poetry run pytest
157
+ ```
158
+
159
+ To update the [syrupy](https://github.com/tophat/syrupy) snapshot tests:
160
+
161
+ ```bash
162
+ poetry run pytest --snapshot-update
163
+ ```
164
+
165
+ ## License
166
+
167
+ MIT License
168
+
169
+ Copyright (c) 2024 Klaas Schoute
170
+
171
+ Permission is hereby granted, free of charge, to any person obtaining a copy
172
+ of this software and associated documentation files (the "Software"), to deal
173
+ in the Software without restriction, including without limitation the rights
174
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
175
+ copies of the Software, and to permit persons to whom the Software is
176
+ furnished to do so, subject to the following conditions:
177
+
178
+ The above copyright notice and this permission notice shall be included in all
179
+ copies or substantial portions of the Software.
180
+
181
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
182
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
183
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
184
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
185
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
186
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
187
+ SOFTWARE.
188
+
189
+
190
+ <!-- LINKS FROM PLATFORM -->
191
+ [spoolman]: https://github.com/Donkie/Spoolman
192
+
193
+ <!-- MARKDOWN LINKS & IMAGES -->
194
+ [build-shield]: https://github.com/klaasnicolaas/python-spoolman/actions/workflows/tests.yaml/badge.svg
195
+ [build-url]: https://github.com/klaasnicolaas/python-spoolman/actions/workflows/tests.yaml
196
+ [codecov-shield]: https://codecov.io/gh/klaasnicolaas/python-spoolman/branch/main/graph/badge.svg?token=C92VQ5QJ21
197
+ [codecov-url]: https://codecov.io/gh/klaasnicolaas/python-spoolman
198
+ [commits-shield]: https://img.shields.io/github/commit-activity/y/klaasnicolaas/python-spoolman.svg
199
+ [commits-url]: https://github.com/klaasnicolaas/python-spoolman/commits/main
200
+ [devcontainer-shield]: https://img.shields.io/static/v1?label=Dev%20Containers&message=Open&color=blue&logo=visualstudiocode
201
+ [devcontainer]: https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/klaasnicolaas/python-spoolman
202
+ [downloads-shield]: https://img.shields.io/pypi/dm/spoolman
203
+ [downloads-url]: https://pypistats.org/packages/spoolman
204
+ [last-commit-shield]: https://img.shields.io/github/last-commit/klaasnicolaas/python-spoolman.svg
205
+ [license-shield]: https://img.shields.io/github/license/klaasnicolaas/python-spoolman.svg
206
+ [maintenance-shield]: https://img.shields.io/maintenance/yes/2024.svg
207
+ [project-stage-shield]: https://img.shields.io/badge/project%20stage-experimental-yellow.svg
208
+ [pypi]: https://pypi.org/project/spoolman/
209
+ [python-versions-shield]: https://img.shields.io/pypi/pyversions/spoolman
210
+ [releases-shield]: https://img.shields.io/github/release/klaasnicolaas/python-spoolman.svg
211
+ [releases]: https://github.com/klaasnicolaas/python-spoolman/releases
212
+ [typing-shield]: https://github.com/klaasnicolaas/python-spoolman/actions/workflows/typing.yaml/badge.svg
213
+ [typing-url]: https://github.com/klaasnicolaas/python-spoolman/actions/workflows/typing.yaml
214
+
215
+ [poetry-install]: https://python-poetry.org/docs/#installation
216
+ [poetry]: https://python-poetry.org
217
+ [pre-commit]: https://pre-commit.com
218
+
@@ -0,0 +1,9 @@
1
+ spoolman/__init__.py,sha256=KPT4TNG6AfbKSTW37VXeseYC3HTCGG-xpWMsiSGAgkM,382
2
+ spoolman/exceptions.py,sha256=hkIz38_iDIyxFjHX2Dct95bQVqjkLukgdVjboBu17Lk,499
3
+ spoolman/models.py,sha256=Yn8sb-hZN56sp6fEDdGSfrgmJpnStJBBNqxzRICR2uU,1712
4
+ spoolman/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ spoolman/spoolman.py,sha256=GsXibjjRhZD6Zr9siPlgE8m3fr95ywsWpUGzLY1u4Ig,6360
6
+ spoolman-0.1.0.dist-info/LICENSE,sha256=nOLtdGoimVCQz204AqZzOsotGIdncRI-hQ7hRHk3P2I,1070
7
+ spoolman-0.1.0.dist-info/METADATA,sha256=nO5_8xtXpwqbOxWgC-t_7Z2sVmskFhu031nH74V9fnk,7996
8
+ spoolman-0.1.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
9
+ spoolman-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 1.9.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any