seamless-database 2.0__tar.gz → 2.0.2__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.
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: seamless-database
3
- Version: 2.0
3
+ Version: 2.0.2
4
4
  Summary: SQLite-backed metadata database service for Seamless
5
5
  Author: Sjoerd de Vries
6
6
  License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/sjdv1982/seamless
8
- Project-URL: Repository, https://github.com/sjdv1982/seamless
8
+ Project-URL: Repository, https://github.com/sjdv1982/seamless-database
9
9
  Project-URL: Issues, https://github.com/sjdv1982/seamless/issues
10
10
  Keywords: seamless,database,sqlite,service
11
11
  Classifier: Development Status :: 3 - Alpha
@@ -13,11 +13,10 @@ Classifier: Intended Audience :: Science/Research
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: Programming Language :: Python :: 3
15
15
  Classifier: Programming Language :: Python :: 3 :: Only
16
- Classifier: Programming Language :: Python :: 3.12
17
16
  Classifier: Operating System :: OS Independent
18
17
  Classifier: Topic :: Scientific/Engineering
19
18
  Classifier: Topic :: Software Development :: Libraries
20
- Requires-Python: >=3.12
19
+ Requires-Python: >=3.10
21
20
  Description-Content-Type: text/markdown
22
21
  Requires-Dist: aiohttp
23
22
  Requires-Dist: peewee
@@ -70,6 +69,27 @@ seamless-database seamless.db --port-range 5520 5530 --writable
70
69
  seamless-database seamless.db --port 5522
71
70
  ```
72
71
 
72
+ If `--port` and `--port-range` are both omitted, `seamless-database` picks a random free port in the dynamic/private range (`49152-65535`).
73
+
74
+ ### Status-file protocol
75
+
76
+ `seamless-database` does not require a status file. If `--status-file` is omitted, it runs independently.
77
+
78
+ If `--status-file` is provided, the file is used for two things:
79
+
80
+ 1. Report the chosen port, especially when `--port-range` is used.
81
+ 2. Report whether startup succeeded (`"running"`) or failed (`"failed"`).
82
+
83
+ The status-file protocol is simple:
84
+
85
+ 1. Wait for the status file to exist and parse it as JSON.
86
+ 2. Reuse the existing JSON object as the base payload. An empty JSON object `{}` is sufficient.
87
+ 3. Choose or validate its listening port.
88
+ 4. Once the HTTP server is up, rewrite the same file with `"status": "running"` and the selected `"port"`.
89
+ 5. If startup fails before the server is running, rewrite the file with `"status": "failed"` instead.
90
+
91
+ If `remote-http-launcher` is used, it may pre-populate the JSON with fields such as the PID, workdir, or `"status": "starting"`. `seamless-database` preserves such fields when it writes back the final status.
92
+
73
93
  ### CLI options
74
94
 
75
95
  | Option | Description |
@@ -46,6 +46,27 @@ seamless-database seamless.db --port-range 5520 5530 --writable
46
46
  seamless-database seamless.db --port 5522
47
47
  ```
48
48
 
49
+ If `--port` and `--port-range` are both omitted, `seamless-database` picks a random free port in the dynamic/private range (`49152-65535`).
50
+
51
+ ### Status-file protocol
52
+
53
+ `seamless-database` does not require a status file. If `--status-file` is omitted, it runs independently.
54
+
55
+ If `--status-file` is provided, the file is used for two things:
56
+
57
+ 1. Report the chosen port, especially when `--port-range` is used.
58
+ 2. Report whether startup succeeded (`"running"`) or failed (`"failed"`).
59
+
60
+ The status-file protocol is simple:
61
+
62
+ 1. Wait for the status file to exist and parse it as JSON.
63
+ 2. Reuse the existing JSON object as the base payload. An empty JSON object `{}` is sufficient.
64
+ 3. Choose or validate its listening port.
65
+ 4. Once the HTTP server is up, rewrite the same file with `"status": "running"` and the selected `"port"`.
66
+ 5. If startup fails before the server is running, rewrite the file with `"status": "failed"` instead.
67
+
68
+ If `remote-http-launcher` is used, it may pre-populate the JSON with fields such as the PID, workdir, or `"status": "starting"`. `seamless-database` preserves such fields when it writes back the final status.
69
+
49
70
  ### CLI options
50
71
 
51
72
  | Option | Description |
@@ -23,6 +23,9 @@ from database_models import (
23
23
  IrreproducibleTransformation,
24
24
  )
25
25
 
26
+ DEFAULT_RANDOM_PORT_START = 49152
27
+ DEFAULT_RANDOM_PORT_END = 65535
28
+
26
29
 
27
30
  STATUS_FILE_WAIT_TIMEOUT = 20.0
28
31
  INACTIVITY_CHECK_INTERVAL = 1.0
@@ -775,21 +778,28 @@ If it doesn't exist, a new file is created.""",
775
778
  create_tables=False,
776
779
  )
777
780
 
778
- selected_port = args.port if args.port is not None else 5522
779
- status_file_path = args.status_file
780
- status_tracker = None
781
- if status_file_path:
782
- status_file_contents = wait_for_status_file(status_file_path)
783
- status_tracker = StatusFileTracker(
784
- status_file_path, status_file_contents, args.port
785
- )
786
-
787
781
  if args.port_range:
788
782
  start, end = args.port_range
789
783
  try:
790
784
  selected_port = pick_random_free_port(args.host, start, end)
791
785
  except BaseException as exc:
792
786
  raise_startup_error(exc)
787
+ elif args.port is not None:
788
+ selected_port = args.port
789
+ else:
790
+ try:
791
+ selected_port = pick_random_free_port(
792
+ args.host, DEFAULT_RANDOM_PORT_START, DEFAULT_RANDOM_PORT_END
793
+ )
794
+ except BaseException as exc:
795
+ raise_startup_error(exc)
796
+ status_file_path = args.status_file
797
+ status_tracker = None
798
+ if status_file_path:
799
+ status_file_contents = wait_for_status_file(status_file_path)
800
+ status_tracker = StatusFileTracker(
801
+ status_file_path, status_file_contents, selected_port
802
+ )
793
803
  if status_tracker:
794
804
  status_tracker.port = selected_port
795
805
 
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "seamless-database"
7
- version = "2.0"
7
+ version = "2.0.2"
8
8
  description = "SQLite-backed metadata database service for Seamless"
9
9
  readme = "README.md"
10
- requires-python = ">=3.12"
10
+ requires-python = ">=3.10"
11
11
  license = "MIT"
12
12
  authors = [{name = "Sjoerd de Vries"}]
13
13
  dependencies = [
@@ -21,7 +21,6 @@ classifiers = [
21
21
  "Intended Audience :: Developers",
22
22
  "Programming Language :: Python :: 3",
23
23
  "Programming Language :: Python :: 3 :: Only",
24
- "Programming Language :: Python :: 3.12",
25
24
  "Operating System :: OS Independent",
26
25
  "Topic :: Scientific/Engineering",
27
26
  "Topic :: Software Development :: Libraries",
@@ -29,7 +28,7 @@ classifiers = [
29
28
 
30
29
  [project.urls]
31
30
  Homepage = "https://github.com/sjdv1982/seamless"
32
- Repository = "https://github.com/sjdv1982/seamless"
31
+ Repository = "https://github.com/sjdv1982/seamless-database"
33
32
  Issues = "https://github.com/sjdv1982/seamless/issues"
34
33
 
35
34
  [project.scripts]
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: seamless-database
3
- Version: 2.0
3
+ Version: 2.0.2
4
4
  Summary: SQLite-backed metadata database service for Seamless
5
5
  Author: Sjoerd de Vries
6
6
  License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/sjdv1982/seamless
8
- Project-URL: Repository, https://github.com/sjdv1982/seamless
8
+ Project-URL: Repository, https://github.com/sjdv1982/seamless-database
9
9
  Project-URL: Issues, https://github.com/sjdv1982/seamless/issues
10
10
  Keywords: seamless,database,sqlite,service
11
11
  Classifier: Development Status :: 3 - Alpha
@@ -13,11 +13,10 @@ Classifier: Intended Audience :: Science/Research
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: Programming Language :: Python :: 3
15
15
  Classifier: Programming Language :: Python :: 3 :: Only
16
- Classifier: Programming Language :: Python :: 3.12
17
16
  Classifier: Operating System :: OS Independent
18
17
  Classifier: Topic :: Scientific/Engineering
19
18
  Classifier: Topic :: Software Development :: Libraries
20
- Requires-Python: >=3.12
19
+ Requires-Python: >=3.10
21
20
  Description-Content-Type: text/markdown
22
21
  Requires-Dist: aiohttp
23
22
  Requires-Dist: peewee
@@ -70,6 +69,27 @@ seamless-database seamless.db --port-range 5520 5530 --writable
70
69
  seamless-database seamless.db --port 5522
71
70
  ```
72
71
 
72
+ If `--port` and `--port-range` are both omitted, `seamless-database` picks a random free port in the dynamic/private range (`49152-65535`).
73
+
74
+ ### Status-file protocol
75
+
76
+ `seamless-database` does not require a status file. If `--status-file` is omitted, it runs independently.
77
+
78
+ If `--status-file` is provided, the file is used for two things:
79
+
80
+ 1. Report the chosen port, especially when `--port-range` is used.
81
+ 2. Report whether startup succeeded (`"running"`) or failed (`"failed"`).
82
+
83
+ The status-file protocol is simple:
84
+
85
+ 1. Wait for the status file to exist and parse it as JSON.
86
+ 2. Reuse the existing JSON object as the base payload. An empty JSON object `{}` is sufficient.
87
+ 3. Choose or validate its listening port.
88
+ 4. Once the HTTP server is up, rewrite the same file with `"status": "running"` and the selected `"port"`.
89
+ 5. If startup fails before the server is running, rewrite the file with `"status": "failed"` instead.
90
+
91
+ If `remote-http-launcher` is used, it may pre-populate the JSON with fields such as the PID, workdir, or `"status": "starting"`. `seamless-database` preserves such fields when it writes back the final status.
92
+
73
93
  ### CLI options
74
94
 
75
95
  | Option | Description |
@@ -8,4 +8,5 @@ seamless_database.egg-info/SOURCES.txt
8
8
  seamless_database.egg-info/dependency_links.txt
9
9
  seamless_database.egg-info/entry_points.txt
10
10
  seamless_database.egg-info/requires.txt
11
- seamless_database.egg-info/top_level.txt
11
+ seamless_database.egg-info/top_level.txt
12
+ tests/test_random_port.py
@@ -0,0 +1,82 @@
1
+ import json
2
+ import os
3
+ import signal
4
+ import subprocess
5
+ import time
6
+ from pathlib import Path
7
+ from urllib.error import URLError
8
+ from urllib.request import urlopen
9
+
10
+
11
+ DEFAULT_RANDOM_PORT_START = 49152
12
+ DEFAULT_RANDOM_PORT_END = 65535
13
+
14
+
15
+ def wait_for_status_file(status_file: Path, timeout: float = 10.0):
16
+ deadline = time.monotonic() + timeout
17
+ last_error = None
18
+ while time.monotonic() < deadline:
19
+ try:
20
+ payload = json.loads(status_file.read_text())
21
+ except (FileNotFoundError, json.JSONDecodeError) as exc:
22
+ last_error = exc
23
+ time.sleep(0.1)
24
+ continue
25
+ if payload.get("status") == "running" and isinstance(payload.get("port"), int):
26
+ return payload
27
+ last_error = payload
28
+ time.sleep(0.1)
29
+ raise RuntimeError(f"Database status file did not report running state: {last_error}")
30
+
31
+
32
+ def wait_for_server(port: int, timeout: float = 10.0):
33
+ deadline = time.monotonic() + timeout
34
+ url = f"http://127.0.0.1:{port}/healthcheck"
35
+ last_error = None
36
+ while time.monotonic() < deadline:
37
+ try:
38
+ with urlopen(url, timeout=1.0) as response: # noqa: S310 - local test server
39
+ if response.status == 200 and response.read().decode() == "OK":
40
+ return
41
+ last_error = response.status
42
+ except URLError as exc:
43
+ last_error = exc
44
+ time.sleep(0.1)
45
+ raise RuntimeError(f"Database server at {url} did not become ready: {last_error}")
46
+
47
+
48
+ def test_random_port_default(tmp_path):
49
+ database_file = tmp_path / "seamless.db"
50
+ status_file = tmp_path / "status.json"
51
+ status_file.write_text("{}\n")
52
+ command = [
53
+ "seamless-database",
54
+ str(database_file),
55
+ "--writable",
56
+ "--status-file",
57
+ str(status_file),
58
+ ]
59
+ process = subprocess.Popen( # noqa: S603,S607 - controlled test command
60
+ command,
61
+ stdout=subprocess.PIPE,
62
+ stderr=subprocess.STDOUT,
63
+ text=True,
64
+ env=os.environ.copy(),
65
+ )
66
+ try:
67
+ payload = wait_for_status_file(status_file)
68
+ port = payload["port"]
69
+ assert DEFAULT_RANDOM_PORT_START <= port <= DEFAULT_RANDOM_PORT_END, port
70
+ assert port != 5522
71
+ wait_for_server(port)
72
+ finally:
73
+ if process.poll() is None:
74
+ process.send_signal(signal.SIGINT)
75
+ try:
76
+ stdout, _ = process.communicate(timeout=5)
77
+ except subprocess.TimeoutExpired:
78
+ process.kill()
79
+ stdout, _ = process.communicate()
80
+ if stdout:
81
+ print("Server logs:")
82
+ print(stdout, end="" if stdout.endswith("\n") else "\n")