a4-printer-webserver 0.1.0__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 (24) hide show
  1. a4_printer_webserver-0.1.0/LICENSE +21 -0
  2. a4_printer_webserver-0.1.0/PKG-INFO +106 -0
  3. a4_printer_webserver-0.1.0/README.md +92 -0
  4. a4_printer_webserver-0.1.0/a4_printer_webserver/__init__.py +5 -0
  5. a4_printer_webserver-0.1.0/a4_printer_webserver/__main__.py +96 -0
  6. a4_printer_webserver-0.1.0/a4_printer_webserver/app.py +390 -0
  7. a4_printer_webserver-0.1.0/a4_printer_webserver/service.py +319 -0
  8. a4_printer_webserver-0.1.0/a4_printer_webserver/static/app.css +269 -0
  9. a4_printer_webserver-0.1.0/a4_printer_webserver/static/app.js +509 -0
  10. a4_printer_webserver-0.1.0/a4_printer_webserver/static/login.js +47 -0
  11. a4_printer_webserver-0.1.0/a4_printer_webserver/storage.py +222 -0
  12. a4_printer_webserver-0.1.0/a4_printer_webserver/templates/index.html +132 -0
  13. a4_printer_webserver-0.1.0/a4_printer_webserver/templates/login.html +40 -0
  14. a4_printer_webserver-0.1.0/a4_printer_webserver.egg-info/PKG-INFO +106 -0
  15. a4_printer_webserver-0.1.0/a4_printer_webserver.egg-info/SOURCES.txt +22 -0
  16. a4_printer_webserver-0.1.0/a4_printer_webserver.egg-info/dependency_links.txt +1 -0
  17. a4_printer_webserver-0.1.0/a4_printer_webserver.egg-info/requires.txt +3 -0
  18. a4_printer_webserver-0.1.0/a4_printer_webserver.egg-info/top_level.txt +1 -0
  19. a4_printer_webserver-0.1.0/pyproject.toml +26 -0
  20. a4_printer_webserver-0.1.0/setup.cfg +4 -0
  21. a4_printer_webserver-0.1.0/tests/test_app.py +199 -0
  22. a4_printer_webserver-0.1.0/tests/test_cli.py +45 -0
  23. a4_printer_webserver-0.1.0/tests/test_service.py +252 -0
  24. a4_printer_webserver-0.1.0/tests/test_storage.py +60 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 GGN_2015
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,106 @@
1
+ Metadata-Version: 2.4
2
+ Name: a4-printer-webserver
3
+ Version: 0.1.0
4
+ Summary: A password-protected Flask and WebSocket frontend for a4-printer-interface
5
+ Author-email: GGN_2015 <neko@jlulug.org>
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: a4-printer-interface
11
+ Requires-Dist: Flask<4.0,>=3.1
12
+ Requires-Dist: flask-sock<1.0,>=0.7
13
+ Dynamic: license-file
14
+
15
+ # a4_printer_webserver
16
+
17
+ A single-printer web interface built with Flask and native WebSockets. It uses
18
+ [`a4-printer-interface`](https://pypi.org/project/a4-printer-interface/) to
19
+ discover the printer, monitor its activity, and submit PDF documents or images.
20
+
21
+ ## Installation
22
+
23
+ Python 3.10 or newer is required. The target printer must already be configured
24
+ in the operating system.
25
+
26
+ ```powershell
27
+ python -m pip install a4_printer_webserver
28
+ ```
29
+
30
+ First, list the available printers and their UUIDs:
31
+
32
+ ```powershell
33
+ python -m a4_printer_interface
34
+ ```
35
+
36
+ Then start the website:
37
+
38
+ ```powershell
39
+ python -m a4_printer_webserver `
40
+ --uuid "printer uuid" `
41
+ --password "your password"
42
+ ```
43
+
44
+ The server listens on <http://127.0.0.1:5000> by default, and the default page
45
+ title is `Printer Website`. To use a custom title:
46
+
47
+ ```powershell
48
+ python -m a4_printer_webserver `
49
+ --uuid "8fd0d51f-12c8-5b50-a2f1-4f64641ae77c" `
50
+ --password "your password" `
51
+ --title "New Title"
52
+ ```
53
+
54
+ Both `--uuid` and `--password` are required. An empty password is rejected.
55
+ The following options are also available:
56
+
57
+ - `--host 127.0.0.1`: Address on which the server listens. Use `0.0.0.0` to
58
+ allow access from the local network.
59
+ - `--port 5000`: TCP port on which the server listens.
60
+ - `--data-dir printer_data`: Directory for uploaded files, the SQLite queue,
61
+ and print history.
62
+ - `--max-upload-mb 100`: Maximum size of one uploaded file, in MiB.
63
+ - `--auto-delete DAYS`: Automatically delete submitted document files after a
64
+ positive number of days. Automatic deletion is disabled when this option is
65
+ omitted.
66
+
67
+ When exposing the service to a local network or the internet, place it behind
68
+ an HTTPS-enabled reverse proxy. Passwords and documents should not cross an
69
+ untrusted network over plain HTTP.
70
+
71
+ ## Behavior
72
+
73
+ - Unauthenticated users can only access the login page. They cannot view the
74
+ queue, download previously submitted files, or establish an authenticated
75
+ WebSocket connection.
76
+ - Uploading a document does not print it immediately. The user must click
77
+ `Print` before the document enters the queue.
78
+ - The `Print test page` button asks for confirmation before adding the built-in
79
+ test page to the queue. It can be canceled until printing starts.
80
+ - `Pause printing` prevents queued jobs from starting. It does not interrupt a
81
+ job that is already printing, and the paused state resets when the server
82
+ restarts.
83
+ - Documents that have not started printing can be canceled. A document in the
84
+ `printing` state cannot be canceled.
85
+ - After the printing library returns successfully, the job is shown as
86
+ `Submitted`. This means that the operating system accepted the job; it does
87
+ not guarantee that the physical pages have finished printing.
88
+ - A green light means the printer is idle, yellow means the printer or the
89
+ current submission is busy, and red means that the configured UUID cannot be
90
+ found or the print service status cannot be read.
91
+ - While the printer reports that it is busy, the most recently submitted job
92
+ remains in History but is displayed as `Printing`. It returns to `Submitted`
93
+ when the printer becomes idle.
94
+ - A submitted document can be deleted manually after confirmation. Manual and
95
+ automatic deletion remove only the stored file; the History entry remains.
96
+ Its Download button is then shown as disabled. Files for jobs currently shown
97
+ as `Printing`, and built-in test-page jobs, do not offer a Delete button.
98
+ - The queue and history are stored in SQLite. If the server stops unexpectedly,
99
+ a job that was being submitted is marked as failed and is not printed again
100
+ automatically.
101
+
102
+ ## Tests
103
+
104
+ ```powershell
105
+ python -m unittest discover -s tests -v
106
+ ```
@@ -0,0 +1,92 @@
1
+ # a4_printer_webserver
2
+
3
+ A single-printer web interface built with Flask and native WebSockets. It uses
4
+ [`a4-printer-interface`](https://pypi.org/project/a4-printer-interface/) to
5
+ discover the printer, monitor its activity, and submit PDF documents or images.
6
+
7
+ ## Installation
8
+
9
+ Python 3.10 or newer is required. The target printer must already be configured
10
+ in the operating system.
11
+
12
+ ```powershell
13
+ python -m pip install a4_printer_webserver
14
+ ```
15
+
16
+ First, list the available printers and their UUIDs:
17
+
18
+ ```powershell
19
+ python -m a4_printer_interface
20
+ ```
21
+
22
+ Then start the website:
23
+
24
+ ```powershell
25
+ python -m a4_printer_webserver `
26
+ --uuid "printer uuid" `
27
+ --password "your password"
28
+ ```
29
+
30
+ The server listens on <http://127.0.0.1:5000> by default, and the default page
31
+ title is `Printer Website`. To use a custom title:
32
+
33
+ ```powershell
34
+ python -m a4_printer_webserver `
35
+ --uuid "8fd0d51f-12c8-5b50-a2f1-4f64641ae77c" `
36
+ --password "your password" `
37
+ --title "New Title"
38
+ ```
39
+
40
+ Both `--uuid` and `--password` are required. An empty password is rejected.
41
+ The following options are also available:
42
+
43
+ - `--host 127.0.0.1`: Address on which the server listens. Use `0.0.0.0` to
44
+ allow access from the local network.
45
+ - `--port 5000`: TCP port on which the server listens.
46
+ - `--data-dir printer_data`: Directory for uploaded files, the SQLite queue,
47
+ and print history.
48
+ - `--max-upload-mb 100`: Maximum size of one uploaded file, in MiB.
49
+ - `--auto-delete DAYS`: Automatically delete submitted document files after a
50
+ positive number of days. Automatic deletion is disabled when this option is
51
+ omitted.
52
+
53
+ When exposing the service to a local network or the internet, place it behind
54
+ an HTTPS-enabled reverse proxy. Passwords and documents should not cross an
55
+ untrusted network over plain HTTP.
56
+
57
+ ## Behavior
58
+
59
+ - Unauthenticated users can only access the login page. They cannot view the
60
+ queue, download previously submitted files, or establish an authenticated
61
+ WebSocket connection.
62
+ - Uploading a document does not print it immediately. The user must click
63
+ `Print` before the document enters the queue.
64
+ - The `Print test page` button asks for confirmation before adding the built-in
65
+ test page to the queue. It can be canceled until printing starts.
66
+ - `Pause printing` prevents queued jobs from starting. It does not interrupt a
67
+ job that is already printing, and the paused state resets when the server
68
+ restarts.
69
+ - Documents that have not started printing can be canceled. A document in the
70
+ `printing` state cannot be canceled.
71
+ - After the printing library returns successfully, the job is shown as
72
+ `Submitted`. This means that the operating system accepted the job; it does
73
+ not guarantee that the physical pages have finished printing.
74
+ - A green light means the printer is idle, yellow means the printer or the
75
+ current submission is busy, and red means that the configured UUID cannot be
76
+ found or the print service status cannot be read.
77
+ - While the printer reports that it is busy, the most recently submitted job
78
+ remains in History but is displayed as `Printing`. It returns to `Submitted`
79
+ when the printer becomes idle.
80
+ - A submitted document can be deleted manually after confirmation. Manual and
81
+ automatic deletion remove only the stored file; the History entry remains.
82
+ Its Download button is then shown as disabled. Files for jobs currently shown
83
+ as `Printing`, and built-in test-page jobs, do not offer a Delete button.
84
+ - The queue and history are stored in SQLite. If the server stops unexpectedly,
85
+ a job that was being submitted is marked as failed and is not printed again
86
+ automatically.
87
+
88
+ ## Tests
89
+
90
+ ```powershell
91
+ python -m unittest discover -s tests -v
92
+ ```
@@ -0,0 +1,5 @@
1
+ """A password-protected web interface for an A4 printer."""
2
+
3
+ from .app import create_app
4
+
5
+ __all__ = ["create_app"]
@@ -0,0 +1,96 @@
1
+ """Run the web server with ``python -m a4_printer_webserver``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import logging
7
+ import uuid
8
+ from pathlib import Path
9
+
10
+ from .app import create_app
11
+
12
+
13
+ def build_parser() -> argparse.ArgumentParser:
14
+ parser = argparse.ArgumentParser(
15
+ description="Serve a password-protected web interface for one A4 printer."
16
+ )
17
+ parser.add_argument(
18
+ "--uuid",
19
+ required=True,
20
+ type=_printer_uuid,
21
+ help="UUID reported by a4-printer-interface for the target printer",
22
+ )
23
+ parser.add_argument(
24
+ "--password",
25
+ required=True,
26
+ help="password required to access the website",
27
+ )
28
+ parser.add_argument("--title", default="Printer Website", help="website title")
29
+ parser.add_argument("--host", default="127.0.0.1", help="address to listen on")
30
+ parser.add_argument("--port", default=5000, type=int, help="TCP port to listen on")
31
+ parser.add_argument(
32
+ "--data-dir",
33
+ type=Path,
34
+ default=Path("printer_data"),
35
+ help="directory used for uploaded files and queue history",
36
+ )
37
+ parser.add_argument(
38
+ "--max-upload-mb",
39
+ default=100,
40
+ type=_positive_integer,
41
+ help="maximum size of one uploaded file in MiB",
42
+ )
43
+ parser.add_argument(
44
+ "--auto-delete",
45
+ metavar="DAYS",
46
+ type=_positive_integer,
47
+ help="delete submitted document files after this many days",
48
+ )
49
+ return parser
50
+
51
+
52
+ def main() -> None:
53
+ args = build_parser().parse_args()
54
+ if not args.password:
55
+ raise SystemExit("--password cannot be empty")
56
+ if not 1 <= args.port <= 65535:
57
+ raise SystemExit("--port must be between 1 and 65535")
58
+
59
+ logging.basicConfig(
60
+ level=logging.INFO,
61
+ format="%(asctime)s %(levelname)s %(name)s: %(message)s",
62
+ )
63
+ app = create_app(
64
+ printer_uuid=args.uuid,
65
+ password=args.password,
66
+ title=args.title,
67
+ data_dir=args.data_dir,
68
+ max_upload_mb=args.max_upload_mb,
69
+ auto_delete_days=args.auto_delete,
70
+ )
71
+ service = app.extensions["printer_service"]
72
+ try:
73
+ app.run(host=args.host, port=args.port, threaded=True, use_reloader=False)
74
+ finally:
75
+ service.stop()
76
+
77
+
78
+ def _printer_uuid(value: str) -> str:
79
+ try:
80
+ return str(uuid.UUID(value))
81
+ except ValueError:
82
+ raise argparse.ArgumentTypeError("must be a valid UUID") from None
83
+
84
+
85
+ def _positive_integer(value: str) -> int:
86
+ try:
87
+ result = int(value)
88
+ except ValueError:
89
+ raise argparse.ArgumentTypeError("must be an integer") from None
90
+ if result <= 0:
91
+ raise argparse.ArgumentTypeError("must be greater than zero")
92
+ return result
93
+
94
+
95
+ if __name__ == "__main__":
96
+ main()