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.
- {seamless_database-2.0 → seamless_database-2.0.2}/PKG-INFO +24 -4
- {seamless_database-2.0 → seamless_database-2.0.2}/README.md +21 -0
- {seamless_database-2.0 → seamless_database-2.0.2}/database.py +19 -9
- {seamless_database-2.0 → seamless_database-2.0.2}/pyproject.toml +3 -4
- {seamless_database-2.0 → seamless_database-2.0.2}/seamless_database.egg-info/PKG-INFO +24 -4
- {seamless_database-2.0 → seamless_database-2.0.2}/seamless_database.egg-info/SOURCES.txt +2 -1
- seamless_database-2.0.2/tests/test_random_port.py +82 -0
- {seamless_database-2.0 → seamless_database-2.0.2}/database_models.py +0 -0
- {seamless_database-2.0 → seamless_database-2.0.2}/seamless_database.egg-info/dependency_links.txt +0 -0
- {seamless_database-2.0 → seamless_database-2.0.2}/seamless_database.egg-info/entry_points.txt +0 -0
- {seamless_database-2.0 → seamless_database-2.0.2}/seamless_database.egg-info/requires.txt +0 -0
- {seamless_database-2.0 → seamless_database-2.0.2}/seamless_database.egg-info/top_level.txt +0 -0
- {seamless_database-2.0 → seamless_database-2.0.2}/setup.cfg +0 -0
|
@@ -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.
|
|
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.
|
|
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.
|
|
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")
|
|
File without changes
|
{seamless_database-2.0 → seamless_database-2.0.2}/seamless_database.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{seamless_database-2.0 → seamless_database-2.0.2}/seamless_database.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|