base-loom-server 0.1__tar.gz

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 (53) hide show
  1. base_loom_server-0.1/.gitignore +5 -0
  2. base_loom_server-0.1/.pre-commit-config.yaml +28 -0
  3. base_loom_server-0.1/LICENSE.txt +7 -0
  4. base_loom_server-0.1/PKG-INFO +79 -0
  5. base_loom_server-0.1/README.md +58 -0
  6. base_loom_server-0.1/pyproject.toml +67 -0
  7. base_loom_server-0.1/setup.cfg +10 -0
  8. base_loom_server-0.1/src/base_loom_server/__init__.py +0 -0
  9. base_loom_server-0.1/src/base_loom_server/app_runner.py +184 -0
  10. base_loom_server-0.1/src/base_loom_server/base_loom_server.py +688 -0
  11. base_loom_server-0.1/src/base_loom_server/base_mock_loom.py +241 -0
  12. base_loom_server-0.1/src/base_loom_server/client_replies.py +108 -0
  13. base_loom_server-0.1/src/base_loom_server/constants.py +6 -0
  14. base_loom_server-0.1/src/base_loom_server/display.css +66 -0
  15. base_loom_server-0.1/src/base_loom_server/display.html_template +102 -0
  16. base_loom_server-0.1/src/base_loom_server/display.js +770 -0
  17. base_loom_server-0.1/src/base_loom_server/example_loom_server.py +106 -0
  18. base_loom_server-0.1/src/base_loom_server/example_mock_loom.py +71 -0
  19. base_loom_server-0.1/src/base_loom_server/favicon-32x32.png +0 -0
  20. base_loom_server-0.1/src/base_loom_server/locales/README.md +12 -0
  21. base_loom_server-0.1/src/base_loom_server/locales/default.json +34 -0
  22. base_loom_server-0.1/src/base_loom_server/locales/fr.json +34 -0
  23. base_loom_server-0.1/src/base_loom_server/main.py +22 -0
  24. base_loom_server-0.1/src/base_loom_server/mock_streams.py +156 -0
  25. base_loom_server-0.1/src/base_loom_server/pattern_database.py +151 -0
  26. base_loom_server-0.1/src/base_loom_server/py.typed +0 -0
  27. base_loom_server-0.1/src/base_loom_server/reduced_pattern.py +283 -0
  28. base_loom_server-0.1/src/base_loom_server/test_data/many color liftplan and zeros.dtx +67 -0
  29. base_loom_server-0.1/src/base_loom_server/test_data/many color liftplan and zeros.wif +99 -0
  30. base_loom_server-0.1/src/base_loom_server/test_data/many color multiple treadles and zeros.dtx +70 -0
  31. base_loom_server-0.1/src/base_loom_server/test_data/many color multiple treadles and zeros.wif +106 -0
  32. base_loom_server-0.1/src/base_loom_server/test_data/many color single treadles.dtx +69 -0
  33. base_loom_server-0.1/src/base_loom_server/test_data/many color single treadles.wif +106 -0
  34. base_loom_server-0.1/src/base_loom_server/test_data/two color liftplan.dtx +51 -0
  35. base_loom_server-0.1/src/base_loom_server/test_data/two color liftplan.wif +70 -0
  36. base_loom_server-0.1/src/base_loom_server/test_data/two color multiple treadles.dtx +54 -0
  37. base_loom_server-0.1/src/base_loom_server/test_data/two color multiple treadles.wif +78 -0
  38. base_loom_server-0.1/src/base_loom_server/test_data/two color single treadles.dtx +53 -0
  39. base_loom_server-0.1/src/base_loom_server/test_data/two color single treadles.wif +78 -0
  40. base_loom_server-0.1/src/base_loom_server/testutils.py +588 -0
  41. base_loom_server-0.1/src/base_loom_server/version.py +3 -0
  42. base_loom_server-0.1/src/base_loom_server.egg-info/PKG-INFO +79 -0
  43. base_loom_server-0.1/src/base_loom_server.egg-info/SOURCES.txt +52 -0
  44. base_loom_server-0.1/src/base_loom_server.egg-info/dependency_links.txt +1 -0
  45. base_loom_server-0.1/src/base_loom_server.egg-info/entry_points.txt +2 -0
  46. base_loom_server-0.1/src/base_loom_server.egg-info/requires.txt +8 -0
  47. base_loom_server-0.1/src/base_loom_server.egg-info/top_level.txt +1 -0
  48. base_loom_server-0.1/tests/test_loom_server.py +10 -0
  49. base_loom_server-0.1/tests/test_mock_loom.py +78 -0
  50. base_loom_server-0.1/tests/test_mock_streams.py +146 -0
  51. base_loom_server-0.1/tests/test_pattern_database.py +166 -0
  52. base_loom_server-0.1/tests/test_reduced_pattern.py +153 -0
  53. base_loom_server-0.1/tests/test_version.py +8 -0
@@ -0,0 +1,5 @@
1
+ __pycache__
2
+ .coverage
3
+ *.egg-info
4
+ dist
5
+ version.py
@@ -0,0 +1,28 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v4.6.0
4
+ hooks:
5
+ - id: check-yaml
6
+ exclude: conda/meta.yaml
7
+ - id: check-xml
8
+
9
+ - repo: https://github.com/psf/black
10
+ rev: 24.8.0
11
+ hooks:
12
+ - id: black
13
+
14
+ - repo: https://github.com/pycqa/flake8
15
+ rev: 7.1.1
16
+ hooks:
17
+ - id: flake8
18
+
19
+ - repo: https://github.com/pycqa/isort
20
+ rev: 5.13.2
21
+ hooks:
22
+ - id: isort
23
+ name: isort (python)
24
+
25
+ - repo: https://github.com/pre-commit/mirrors-mypy
26
+ rev: v1.11.2
27
+ hooks:
28
+ - id: mypy
@@ -0,0 +1,7 @@
1
+ Copyright 2024 Russell Owen
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.2
2
+ Name: base_loom_server
3
+ Version: 0.1
4
+ Summary: Base package for web servers that control dobby multi-shaft looms
5
+ Author-email: Russell Owen <r3owen@gmail.com>
6
+ Project-URL: Homepage, https://github.com/r-owen/base_loom_server
7
+ Project-URL: Issues, https://github.com/r-owen/base_loom_server/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.11
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE.txt
14
+ Requires-Dist: fastapi>=0.115
15
+ Requires-Dist: dtx_to_wif>=3.0
16
+ Requires-Dist: pyserial-asyncio>=0.6
17
+ Provides-Extra: dev
18
+ Requires-Dist: pre-commit>=3.8; extra == "dev"
19
+ Requires-Dist: pytest>=8.3; extra == "dev"
20
+ Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
21
+
22
+ # Base package for web servers that control dobby multi-shaft looms
23
+
24
+ Such web servers are intended to allow you to control your loom from any phone, tablet or other device that has wifi and a web browser.
25
+
26
+ Used by [seguin_loom_server](<https://pypi.org/project/seguin-loom-server/)>)
27
+ and [toika_loom_server](https://pypi.org/project/toika-loom-server/).
28
+
29
+ ## Installing this Package
30
+
31
+ * Install [Python](https://www.python.org/downloads/) 3.11 or later on the computer.
32
+
33
+ * Install this [base_loom_server](https://pypi.org/project/base-loom-server/) package on the computer with command: **pip install base_loom_server**
34
+
35
+ ## Using this Package
36
+
37
+ * Subclass `BaseMockLoom`; see `ExampleMockLoom` for an example.
38
+ * Subclass `BaseLoomServer`; see `ExampleLoomServer` for an example.
39
+ * Write a `main.py` like the one in this package (which runs `ExampleLoomServer`).
40
+ * The unit tests for `BaseMockLoom` should be able to use `testutils.BaseTestLoomServer`, as `tests/test_loom_server.py` does.
41
+ * Write a `pyproject.toml` like the one for [toika_loom_server](https://pypi.org/project/toika-loom-server/), unless you would rather use a different distribution system.
42
+
43
+ ## Remembering Patterns
44
+
45
+ The web server keeps track of the most recent 25 patterns you have used in a database
46
+ (including the most recent pick number and number of repeats, which are restored when you select a pattern).
47
+ The patterns in the database are displayed in the pattern menu.
48
+ If you shut down the server or there is a power failure, all this information should be retained.
49
+
50
+ You can reset the database by starting the server with the **--reset-db** argument.
51
+ You must reset the database if you upgrade this base_loom_server package and the new database format is incompatible
52
+ (in which case the server will fail at startup).
53
+ You may also want to reset the database if you are weaving a new project and don't want to see any of the saved patterns.
54
+
55
+ ## Developer Tips
56
+
57
+ * Download the source code from [github](https://github.com/r-owen/base_loom_server.git),
58
+ or make a fork and download that.
59
+
60
+ * Inside the directory, issue the following commands:
61
+
62
+ * **pip install -e .** (note the final period) to make an "editable installation" of the package.
63
+ An editable installation runs from the source code, so changes you make to the source are used when you run or test the code, without the need to reinstall the package.
64
+
65
+ * **pre-commit install** to activate the pre-commit hooks.
66
+
67
+ * You may run a mock loom by starting the server with: **run_example_loom mock**.
68
+ The mock loom does not use a serial port.
69
+ **run_example_loom** also accepts these command-line arguments:
70
+
71
+ * **--reset-db** Reset the pattern database. Try this if you think the database is corrupted.
72
+
73
+ * **--verbose** Print more diagnostic information.
74
+
75
+ * In mock mode the web page shows a few extra controls for debugging.
76
+
77
+ * Warning: the web server's automatic reload feature, which reloads Python code whenever you save changes, *does not work* with this software.
78
+ Instead you have to kill the web server by typing control-C several times, until you get a terminal prompt, then run the server again.
79
+ This may be a bug in uvicorn; see [this discussion](https://github.com/encode/uvicorn/discussions/2075) for more information.
@@ -0,0 +1,58 @@
1
+ # Base package for web servers that control dobby multi-shaft looms
2
+
3
+ Such web servers are intended to allow you to control your loom from any phone, tablet or other device that has wifi and a web browser.
4
+
5
+ Used by [seguin_loom_server](<https://pypi.org/project/seguin-loom-server/)>)
6
+ and [toika_loom_server](https://pypi.org/project/toika-loom-server/).
7
+
8
+ ## Installing this Package
9
+
10
+ * Install [Python](https://www.python.org/downloads/) 3.11 or later on the computer.
11
+
12
+ * Install this [base_loom_server](https://pypi.org/project/base-loom-server/) package on the computer with command: **pip install base_loom_server**
13
+
14
+ ## Using this Package
15
+
16
+ * Subclass `BaseMockLoom`; see `ExampleMockLoom` for an example.
17
+ * Subclass `BaseLoomServer`; see `ExampleLoomServer` for an example.
18
+ * Write a `main.py` like the one in this package (which runs `ExampleLoomServer`).
19
+ * The unit tests for `BaseMockLoom` should be able to use `testutils.BaseTestLoomServer`, as `tests/test_loom_server.py` does.
20
+ * Write a `pyproject.toml` like the one for [toika_loom_server](https://pypi.org/project/toika-loom-server/), unless you would rather use a different distribution system.
21
+
22
+ ## Remembering Patterns
23
+
24
+ The web server keeps track of the most recent 25 patterns you have used in a database
25
+ (including the most recent pick number and number of repeats, which are restored when you select a pattern).
26
+ The patterns in the database are displayed in the pattern menu.
27
+ If you shut down the server or there is a power failure, all this information should be retained.
28
+
29
+ You can reset the database by starting the server with the **--reset-db** argument.
30
+ You must reset the database if you upgrade this base_loom_server package and the new database format is incompatible
31
+ (in which case the server will fail at startup).
32
+ You may also want to reset the database if you are weaving a new project and don't want to see any of the saved patterns.
33
+
34
+ ## Developer Tips
35
+
36
+ * Download the source code from [github](https://github.com/r-owen/base_loom_server.git),
37
+ or make a fork and download that.
38
+
39
+ * Inside the directory, issue the following commands:
40
+
41
+ * **pip install -e .** (note the final period) to make an "editable installation" of the package.
42
+ An editable installation runs from the source code, so changes you make to the source are used when you run or test the code, without the need to reinstall the package.
43
+
44
+ * **pre-commit install** to activate the pre-commit hooks.
45
+
46
+ * You may run a mock loom by starting the server with: **run_example_loom mock**.
47
+ The mock loom does not use a serial port.
48
+ **run_example_loom** also accepts these command-line arguments:
49
+
50
+ * **--reset-db** Reset the pattern database. Try this if you think the database is corrupted.
51
+
52
+ * **--verbose** Print more diagnostic information.
53
+
54
+ * In mock mode the web page shows a few extra controls for debugging.
55
+
56
+ * Warning: the web server's automatic reload feature, which reloads Python code whenever you save changes, *does not work* with this software.
57
+ Instead you have to kill the web server by typing control-C several times, until you get a terminal prompt, then run the server again.
58
+ This may be a bug in uvicorn; see [this discussion](https://github.com/encode/uvicorn/discussions/2075) for more information.
@@ -0,0 +1,67 @@
1
+ [build-system]
2
+ requires = [
3
+ "setuptools>=61.0",
4
+ "setuptools_scm[toml]>=8.0",
5
+ ]
6
+ build-backend = "setuptools.build_meta"
7
+
8
+ [project]
9
+ name = "base_loom_server"
10
+ dynamic = ["version"]
11
+ description = "Base package for web servers that control dobby multi-shaft looms"
12
+ readme = "README.md"
13
+ dependencies = [
14
+ "fastapi >= 0.115",
15
+ "dtx_to_wif >= 3.0",
16
+ "pyserial-asyncio >= 0.6",
17
+ ]
18
+ authors = [
19
+ { name="Russell Owen", email="r3owen@gmail.com" },
20
+ ]
21
+ classifiers = [
22
+ "Programming Language :: Python :: 3",
23
+ "License :: OSI Approved :: MIT License",
24
+ "Operating System :: OS Independent",
25
+ ]
26
+ requires-python = ">=3.11"
27
+
28
+ [project.optional-dependencies]
29
+ dev = [
30
+ "pre-commit >= 3.8",
31
+ "pytest >= 8.3",
32
+ "pytest-asyncio >= 0.24",
33
+ ]
34
+
35
+ # Needed due to including package data below
36
+ [tool.setuptools.packages.find]
37
+ where = ["src"]
38
+
39
+ [tool.setuptools.package-data]
40
+ "base_loom_server" = [
41
+ "py.typed",
42
+ "display.css",
43
+ "display.html_template",
44
+ "display.js",
45
+ "favicon-32x32.png"
46
+ ]
47
+
48
+ [project.scripts]
49
+ run_example_loom = "base_loom_server.main:run_example_loom"
50
+
51
+ [project.urls]
52
+ Homepage = "https://github.com/r-owen/base_loom_server"
53
+ Issues = "https://github.com/r-owen/base_loom_server/issues"
54
+
55
+ [tool.pytest.ini_options]
56
+ asyncio_mode = "auto"
57
+
58
+ [tool.isort]
59
+ profile = "black"
60
+
61
+ [tool.setuptools_scm]
62
+ write_to = "src/base_loom_server/version.py"
63
+ write_to_template = """
64
+ # Generated by setuptools_scm
65
+ __all__ = ["__version__"]
66
+ __version__ = "{version}"
67
+ """
@@ -0,0 +1,10 @@
1
+ [flake8]
2
+ extend-ignore = E133, E203, E226, E228, N802, N803, N806, N812, N813, N815, N816, W503
3
+ max-line-length = 110
4
+ max-doc-length = 79
5
+ exclude = __init__.py
6
+
7
+ [egg_info]
8
+ tag_build =
9
+ tag_date = 0
10
+
File without changes
@@ -0,0 +1,184 @@
1
+ import argparse
2
+ import importlib.resources
3
+ import json
4
+ import locale
5
+ import logging
6
+ import pathlib
7
+ from contextlib import asynccontextmanager
8
+ from typing import AsyncGenerator, Type
9
+
10
+ import uvicorn
11
+ from fastapi import APIRouter, FastAPI, WebSocket
12
+ from fastapi.responses import HTMLResponse, Response
13
+
14
+ from .base_loom_server import DEFAULT_DATABASE_PATH, BaseLoomServer
15
+ from .constants import LOG_NAME
16
+
17
+ PKG_FILES = importlib.resources.files("base_loom_server")
18
+ LOCALE_FILES = PKG_FILES.joinpath("locales")
19
+
20
+
21
+ class AppRunner:
22
+ def __init__(
23
+ self,
24
+ app: FastAPI,
25
+ server_class: Type[BaseLoomServer],
26
+ favicon: bytes,
27
+ app_package_name: str,
28
+ ) -> None:
29
+ """Construct endpoints for FastAPI"""
30
+ self.log = logging.getLogger(LOG_NAME)
31
+
32
+ self.server_class = server_class
33
+ self.favicon = favicon
34
+ self.app_package_name = app_package_name
35
+ self.loom_server: BaseLoomServer | None = None
36
+ self.translation_dict: dict[str, str] = {}
37
+
38
+ # There must be a better way to do this,
39
+ # but everything I have tried fails,
40
+ # including using an APIRouter with add_api_route
41
+
42
+ @asynccontextmanager
43
+ async def lifespan_wrapper(*args):
44
+ async with self.lifespan(app):
45
+ yield
46
+
47
+ # The only rason we need a router is to set the lifespan
48
+ # but once we have it we may as well use it to add endpoints as well
49
+ router = APIRouter(lifespan=lifespan_wrapper)
50
+
51
+ @router.get("/")
52
+ async def get_wrapper():
53
+ return await self.get()
54
+
55
+ @router.get("/favicon.ico", include_in_schema=False)
56
+ async def get_favicon():
57
+ return await self.get_favicon()
58
+
59
+ @router.websocket("/ws")
60
+ async def websocket_endpoint_wrapper(websocket: WebSocket):
61
+ return await self.websocket_endpoint(websocket)
62
+
63
+ app.include_router(router)
64
+
65
+ def create_argument_parser(self) -> argparse.ArgumentParser:
66
+ parser = argparse.ArgumentParser()
67
+ parser.add_argument(
68
+ "serial_port",
69
+ help="Serial port connected to the loom, "
70
+ "typically of the form /dev/tty... "
71
+ "Specify 'mock' to run a mock (simulated) loom",
72
+ )
73
+ parser.add_argument(
74
+ "-n",
75
+ "--name",
76
+ help="loom name",
77
+ )
78
+ parser.add_argument(
79
+ "-r",
80
+ "--reset-db",
81
+ action="store_true",
82
+ help="reset pattern database?",
83
+ )
84
+ parser.add_argument(
85
+ "-v",
86
+ "--verbose",
87
+ action="store_true",
88
+ help="print diagnostic information to stdout",
89
+ )
90
+ parser.add_argument(
91
+ "--db-path",
92
+ default=DEFAULT_DATABASE_PATH,
93
+ type=pathlib.Path,
94
+ help="Path for pattern database. "
95
+ "Settable so unit tests can avoid changing the real database.",
96
+ )
97
+ return parser
98
+
99
+ @asynccontextmanager
100
+ async def lifespan(self, app: FastAPI) -> AsyncGenerator[None, FastAPI]:
101
+ self.translation_dict = self.get_translation_dict()
102
+
103
+ parser = self.create_argument_parser()
104
+ args = parser.parse_args()
105
+
106
+ async with self.server_class(
107
+ **vars(args), translation_dict=self.translation_dict
108
+ ) as self.loom_server:
109
+ yield
110
+
111
+ def get_translation_dict(self) -> dict[str, str]:
112
+ """Get the translation dict for the current locale"""
113
+ # Read a dict of key: None and turn into a dict of key: key
114
+ default_dict = json.loads(LOCALE_FILES.joinpath("default.json").read_text())
115
+ translation_dict = {key: key for key in default_dict}
116
+
117
+ language_code = locale.getlocale(locale.LC_CTYPE)[0]
118
+ self.log.info(f"Locale: {language_code!r}")
119
+ if language_code is not None:
120
+ short_language_code = language_code.split("_")[0]
121
+ for lc in (short_language_code, language_code):
122
+ translation_name = lc + ".json"
123
+ translation_file = LOCALE_FILES.joinpath(translation_name)
124
+ if translation_file.is_file():
125
+ self.log.info(f"Loading translation file {translation_name!r}")
126
+ locale_dict = json.loads(translation_file.read_text())
127
+ purged_locale_dict = {
128
+ key: value
129
+ for key, value in locale_dict.items()
130
+ if value is not None
131
+ }
132
+ if purged_locale_dict != locale_dict:
133
+ self.log.warning(
134
+ f"Some entries in translation file {translation_name!r} "
135
+ "have null entries"
136
+ )
137
+ translation_dict.update(purged_locale_dict)
138
+ return translation_dict
139
+
140
+ async def get(self) -> HTMLResponse:
141
+ display_html_template = PKG_FILES.joinpath("display.html_template").read_text()
142
+
143
+ display_css = PKG_FILES.joinpath("display.css").read_text()
144
+
145
+ display_js = PKG_FILES.joinpath("display.js").read_text()
146
+ js_translation_str = "const TranslationDict = " + json.dumps(
147
+ self.translation_dict, indent=4
148
+ )
149
+ display_js = display_js.replace(
150
+ "const TranslationDict = {}", js_translation_str
151
+ )
152
+
153
+ assert self.loom_server is not None
154
+ is_mock = self.loom_server.mock_loom is not None
155
+ display_debug_controls = "block" if is_mock else "none"
156
+
157
+ display_html = display_html_template.format(
158
+ display_css=display_css,
159
+ display_js=display_js,
160
+ display_debug_controls=display_debug_controls,
161
+ **self.translation_dict,
162
+ )
163
+
164
+ return HTMLResponse(display_html)
165
+
166
+ async def get_favicon(self) -> Response:
167
+ return Response(content=self.favicon, media_type="image/x-icon")
168
+
169
+ async def websocket_endpoint(self, websocket: WebSocket) -> None:
170
+ assert self.loom_server is not None
171
+ await self.loom_server.run_client(websocket=websocket)
172
+
173
+ def run(self, host="0.0.0.0", port=8000, log_level="info", reload=True) -> None:
174
+ # Handle the help argument and also catch parsing errors right away
175
+ arg_parser = self.create_argument_parser()
176
+ arg_parser.parse_args()
177
+
178
+ uvicorn.run(
179
+ self.app_package_name,
180
+ host=host,
181
+ port=port,
182
+ log_level=log_level,
183
+ reload=reload,
184
+ )