pg0-embedded 0.1.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.
- pg0_embedded-0.1.1/.gitignore +5 -0
- pg0_embedded-0.1.1/PKG-INFO +143 -0
- pg0_embedded-0.1.1/README.md +123 -0
- pg0_embedded-0.1.1/pg0/__init__.py +538 -0
- pg0_embedded-0.1.1/pg0/py.typed +0 -0
- pg0_embedded-0.1.1/pyproject.toml +33 -0
- pg0_embedded-0.1.1/tests/__init__.py +0 -0
- pg0_embedded-0.1.1/tests/test_pg0.py +195 -0
- pg0_embedded-0.1.1/uv.lock +251 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pg0-embedded
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Python API for pg0 - embedded PostgreSQL
|
|
5
|
+
Project-URL: Homepage, https://github.com/vectorize-io/pg0
|
|
6
|
+
Project-URL: Repository, https://github.com/vectorize-io/pg0
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Keywords: database,embedded,pgvector,postgresql
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# pg0 - Embedded PostgreSQL for Python
|
|
22
|
+
|
|
23
|
+
Zero-config PostgreSQL with pgvector support. Just `pip install` and go.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install pg0-embedded
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from pg0 import Pg0
|
|
35
|
+
|
|
36
|
+
# Start PostgreSQL (auto-installs on first run)
|
|
37
|
+
pg = Pg0()
|
|
38
|
+
pg.start()
|
|
39
|
+
|
|
40
|
+
print(pg.uri) # postgresql://postgres:postgres@localhost:5432/postgres
|
|
41
|
+
|
|
42
|
+
pg.stop()
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Context Manager
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from pg0 import Pg0
|
|
49
|
+
|
|
50
|
+
with Pg0() as pg:
|
|
51
|
+
print(pg.uri)
|
|
52
|
+
pg.execute("CREATE EXTENSION IF NOT EXISTS vector")
|
|
53
|
+
pg.execute("SELECT version()")
|
|
54
|
+
# Automatically stopped
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Custom Configuration
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from pg0 import Pg0
|
|
61
|
+
|
|
62
|
+
pg = Pg0(
|
|
63
|
+
port=5433,
|
|
64
|
+
username="myuser",
|
|
65
|
+
password="mypass",
|
|
66
|
+
database="mydb",
|
|
67
|
+
config={
|
|
68
|
+
"shared_buffers": "512MB",
|
|
69
|
+
"maintenance_work_mem": "1GB",
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
with pg:
|
|
74
|
+
print(pg.uri)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Multiple Instances
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from pg0 import Pg0, list_instances
|
|
81
|
+
|
|
82
|
+
# Run multiple PostgreSQL instances
|
|
83
|
+
app = Pg0(name="app", port=5432)
|
|
84
|
+
test = Pg0(name="test", port=5433)
|
|
85
|
+
|
|
86
|
+
app.start()
|
|
87
|
+
test.start()
|
|
88
|
+
|
|
89
|
+
for instance in list_instances():
|
|
90
|
+
print(f"{instance.name}: {instance.uri}")
|
|
91
|
+
|
|
92
|
+
app.stop()
|
|
93
|
+
test.stop()
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## API Reference
|
|
97
|
+
|
|
98
|
+
### Pg0 Class
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
pg = Pg0(
|
|
102
|
+
name="default", # Instance name
|
|
103
|
+
port=5432, # Port
|
|
104
|
+
username="postgres", # Username
|
|
105
|
+
password="postgres", # Password
|
|
106
|
+
database="postgres", # Database
|
|
107
|
+
data_dir=None, # Custom data directory
|
|
108
|
+
config={}, # PostgreSQL config options
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
pg.start() # Start PostgreSQL -> InstanceInfo
|
|
112
|
+
pg.stop() # Stop PostgreSQL
|
|
113
|
+
pg.info() # Get instance info -> InstanceInfo
|
|
114
|
+
pg.uri # Connection URI (property)
|
|
115
|
+
pg.running # Is running (property)
|
|
116
|
+
pg.execute(sql) # Execute SQL -> str
|
|
117
|
+
pg.psql(*args) # Run psql command
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Module Functions
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
import pg0
|
|
124
|
+
|
|
125
|
+
pg0.start(port=5432, ...) # Start instance -> InstanceInfo
|
|
126
|
+
pg0.stop(name="default") # Stop instance
|
|
127
|
+
pg0.info(name="default") # Get info -> InstanceInfo
|
|
128
|
+
pg0.list_instances() # List all -> [InstanceInfo]
|
|
129
|
+
pg0.install(version=None) # Install pg0 binary
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### InstanceInfo
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
info.name # Instance name
|
|
136
|
+
info.running # Is running
|
|
137
|
+
info.pid # Process ID
|
|
138
|
+
info.port # Port
|
|
139
|
+
info.uri # Connection URI
|
|
140
|
+
info.username # Username
|
|
141
|
+
info.database # Database
|
|
142
|
+
info.data_dir # Data directory
|
|
143
|
+
```
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# pg0 - Embedded PostgreSQL for Python
|
|
2
|
+
|
|
3
|
+
Zero-config PostgreSQL with pgvector support. Just `pip install` and go.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install pg0-embedded
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from pg0 import Pg0
|
|
15
|
+
|
|
16
|
+
# Start PostgreSQL (auto-installs on first run)
|
|
17
|
+
pg = Pg0()
|
|
18
|
+
pg.start()
|
|
19
|
+
|
|
20
|
+
print(pg.uri) # postgresql://postgres:postgres@localhost:5432/postgres
|
|
21
|
+
|
|
22
|
+
pg.stop()
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Context Manager
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from pg0 import Pg0
|
|
29
|
+
|
|
30
|
+
with Pg0() as pg:
|
|
31
|
+
print(pg.uri)
|
|
32
|
+
pg.execute("CREATE EXTENSION IF NOT EXISTS vector")
|
|
33
|
+
pg.execute("SELECT version()")
|
|
34
|
+
# Automatically stopped
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Custom Configuration
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from pg0 import Pg0
|
|
41
|
+
|
|
42
|
+
pg = Pg0(
|
|
43
|
+
port=5433,
|
|
44
|
+
username="myuser",
|
|
45
|
+
password="mypass",
|
|
46
|
+
database="mydb",
|
|
47
|
+
config={
|
|
48
|
+
"shared_buffers": "512MB",
|
|
49
|
+
"maintenance_work_mem": "1GB",
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
with pg:
|
|
54
|
+
print(pg.uri)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Multiple Instances
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from pg0 import Pg0, list_instances
|
|
61
|
+
|
|
62
|
+
# Run multiple PostgreSQL instances
|
|
63
|
+
app = Pg0(name="app", port=5432)
|
|
64
|
+
test = Pg0(name="test", port=5433)
|
|
65
|
+
|
|
66
|
+
app.start()
|
|
67
|
+
test.start()
|
|
68
|
+
|
|
69
|
+
for instance in list_instances():
|
|
70
|
+
print(f"{instance.name}: {instance.uri}")
|
|
71
|
+
|
|
72
|
+
app.stop()
|
|
73
|
+
test.stop()
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## API Reference
|
|
77
|
+
|
|
78
|
+
### Pg0 Class
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
pg = Pg0(
|
|
82
|
+
name="default", # Instance name
|
|
83
|
+
port=5432, # Port
|
|
84
|
+
username="postgres", # Username
|
|
85
|
+
password="postgres", # Password
|
|
86
|
+
database="postgres", # Database
|
|
87
|
+
data_dir=None, # Custom data directory
|
|
88
|
+
config={}, # PostgreSQL config options
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
pg.start() # Start PostgreSQL -> InstanceInfo
|
|
92
|
+
pg.stop() # Stop PostgreSQL
|
|
93
|
+
pg.info() # Get instance info -> InstanceInfo
|
|
94
|
+
pg.uri # Connection URI (property)
|
|
95
|
+
pg.running # Is running (property)
|
|
96
|
+
pg.execute(sql) # Execute SQL -> str
|
|
97
|
+
pg.psql(*args) # Run psql command
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Module Functions
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
import pg0
|
|
104
|
+
|
|
105
|
+
pg0.start(port=5432, ...) # Start instance -> InstanceInfo
|
|
106
|
+
pg0.stop(name="default") # Stop instance
|
|
107
|
+
pg0.info(name="default") # Get info -> InstanceInfo
|
|
108
|
+
pg0.list_instances() # List all -> [InstanceInfo]
|
|
109
|
+
pg0.install(version=None) # Install pg0 binary
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### InstanceInfo
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
info.name # Instance name
|
|
116
|
+
info.running # Is running
|
|
117
|
+
info.pid # Process ID
|
|
118
|
+
info.port # Port
|
|
119
|
+
info.uri # Connection URI
|
|
120
|
+
info.username # Username
|
|
121
|
+
info.database # Database
|
|
122
|
+
info.data_dir # Data directory
|
|
123
|
+
```
|
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pg0 - Embedded PostgreSQL for Python
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
from pg0 import Pg0
|
|
6
|
+
|
|
7
|
+
# Start PostgreSQL
|
|
8
|
+
pg = Pg0()
|
|
9
|
+
pg.start()
|
|
10
|
+
print(pg.uri)
|
|
11
|
+
pg.stop()
|
|
12
|
+
|
|
13
|
+
# Or use context manager
|
|
14
|
+
with Pg0() as pg:
|
|
15
|
+
print(pg.uri)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import platform
|
|
23
|
+
import shutil
|
|
24
|
+
import stat
|
|
25
|
+
import subprocess
|
|
26
|
+
import sys
|
|
27
|
+
import tempfile
|
|
28
|
+
import urllib.request
|
|
29
|
+
from dataclasses import dataclass
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
from typing import Optional
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
__version__ = "0.1.0"
|
|
35
|
+
|
|
36
|
+
# GitHub repo for pg0 releases
|
|
37
|
+
PG0_REPO = "vectorize-io/pg0"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Pg0Error(Exception):
|
|
41
|
+
"""Base exception for pg0 errors."""
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Pg0NotFoundError(Pg0Error):
|
|
46
|
+
"""pg0 binary not found and could not be installed."""
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Pg0NotRunningError(Pg0Error):
|
|
51
|
+
"""PostgreSQL instance is not running."""
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Pg0AlreadyRunningError(Pg0Error):
|
|
56
|
+
"""PostgreSQL instance is already running."""
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class InstanceInfo:
|
|
62
|
+
"""Information about a PostgreSQL instance."""
|
|
63
|
+
name: str
|
|
64
|
+
running: bool
|
|
65
|
+
pid: Optional[int] = None
|
|
66
|
+
port: Optional[int] = None
|
|
67
|
+
version: Optional[str] = None
|
|
68
|
+
username: Optional[str] = None
|
|
69
|
+
database: Optional[str] = None
|
|
70
|
+
data_dir: Optional[str] = None
|
|
71
|
+
uri: Optional[str] = None
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def from_dict(cls, data: dict) -> "InstanceInfo":
|
|
75
|
+
return cls(
|
|
76
|
+
name=data.get("name", "default"),
|
|
77
|
+
running=data.get("running", False),
|
|
78
|
+
pid=data.get("pid"),
|
|
79
|
+
port=data.get("port"),
|
|
80
|
+
version=data.get("version"),
|
|
81
|
+
username=data.get("username"),
|
|
82
|
+
database=data.get("database"),
|
|
83
|
+
data_dir=data.get("data_dir"),
|
|
84
|
+
uri=data.get("uri"),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _get_install_dir() -> Path:
|
|
89
|
+
"""Get the directory where pg0 binary should be installed."""
|
|
90
|
+
# Use ~/.local/bin on Unix, or a pg0-specific dir
|
|
91
|
+
if sys.platform == "win32":
|
|
92
|
+
base = Path(os.environ.get("LOCALAPPDATA", Path.home() / "AppData" / "Local"))
|
|
93
|
+
return base / "pg0" / "bin"
|
|
94
|
+
else:
|
|
95
|
+
return Path.home() / ".local" / "bin"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _get_platform() -> str:
|
|
99
|
+
"""Get the platform string for downloading the correct binary."""
|
|
100
|
+
system = platform.system().lower()
|
|
101
|
+
machine = platform.machine().lower()
|
|
102
|
+
|
|
103
|
+
if system == "darwin":
|
|
104
|
+
# macOS - only Apple Silicon supported, Intel uses Rosetta
|
|
105
|
+
return "darwin-aarch64"
|
|
106
|
+
elif system == "linux":
|
|
107
|
+
# Detect architecture
|
|
108
|
+
if machine in ("x86_64", "amd64"):
|
|
109
|
+
arch_str = "x86_64"
|
|
110
|
+
elif machine in ("aarch64", "arm64"):
|
|
111
|
+
arch_str = "aarch64"
|
|
112
|
+
else:
|
|
113
|
+
raise Pg0NotFoundError(f"Unsupported Linux architecture: {machine}")
|
|
114
|
+
|
|
115
|
+
# Detect libc (musl vs glibc)
|
|
116
|
+
# Check for musl by looking for the musl loader
|
|
117
|
+
import subprocess
|
|
118
|
+
try:
|
|
119
|
+
result = subprocess.run(
|
|
120
|
+
["ldd", "--version"],
|
|
121
|
+
capture_output=True,
|
|
122
|
+
text=True,
|
|
123
|
+
timeout=5,
|
|
124
|
+
)
|
|
125
|
+
output = result.stdout + result.stderr
|
|
126
|
+
if "musl" in output.lower():
|
|
127
|
+
return f"linux-{arch_str}-musl"
|
|
128
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
# Check for musl loader file
|
|
132
|
+
musl_loaders = [
|
|
133
|
+
f"/lib/ld-musl-{arch_str}.so.1",
|
|
134
|
+
"/lib/ld-musl-x86_64.so.1",
|
|
135
|
+
"/lib/ld-musl-aarch64.so.1",
|
|
136
|
+
]
|
|
137
|
+
for loader in musl_loaders:
|
|
138
|
+
if Path(loader).exists():
|
|
139
|
+
return f"linux-{arch_str}-musl"
|
|
140
|
+
|
|
141
|
+
# Default to glibc
|
|
142
|
+
return f"linux-{arch_str}-gnu"
|
|
143
|
+
elif system == "windows":
|
|
144
|
+
return "windows-x86_64"
|
|
145
|
+
else:
|
|
146
|
+
raise Pg0NotFoundError(f"Unsupported platform: {system}")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _get_latest_version() -> str:
|
|
150
|
+
"""Get the latest pg0 version from GitHub."""
|
|
151
|
+
url = f"https://api.github.com/repos/{PG0_REPO}/releases/latest"
|
|
152
|
+
try:
|
|
153
|
+
with urllib.request.urlopen(url, timeout=30) as response:
|
|
154
|
+
data = json.loads(response.read().decode())
|
|
155
|
+
return data["tag_name"]
|
|
156
|
+
except Exception as e:
|
|
157
|
+
raise Pg0NotFoundError(f"Failed to fetch latest version: {e}")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def install(version: Optional[str] = None, force: bool = False) -> Path:
|
|
161
|
+
"""
|
|
162
|
+
Install the pg0 binary.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
version: Version to install (default: latest)
|
|
166
|
+
force: Force reinstall even if already installed
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Path to the installed binary
|
|
170
|
+
"""
|
|
171
|
+
install_dir = _get_install_dir()
|
|
172
|
+
binary_name = "pg0.exe" if sys.platform == "win32" else "pg0"
|
|
173
|
+
binary_path = install_dir / binary_name
|
|
174
|
+
|
|
175
|
+
# Check if already installed
|
|
176
|
+
if binary_path.exists() and not force:
|
|
177
|
+
return binary_path
|
|
178
|
+
|
|
179
|
+
# Get version
|
|
180
|
+
if version is None:
|
|
181
|
+
version = _get_latest_version()
|
|
182
|
+
|
|
183
|
+
# Get platform
|
|
184
|
+
plat = _get_platform()
|
|
185
|
+
|
|
186
|
+
# Build download URL
|
|
187
|
+
ext = ".exe" if sys.platform == "win32" else ""
|
|
188
|
+
filename = f"pg0-{plat}{ext}"
|
|
189
|
+
url = f"https://github.com/{PG0_REPO}/releases/download/{version}/{filename}"
|
|
190
|
+
|
|
191
|
+
print(f"Installing pg0 {version}...")
|
|
192
|
+
|
|
193
|
+
# Create install directory
|
|
194
|
+
install_dir.mkdir(parents=True, exist_ok=True)
|
|
195
|
+
|
|
196
|
+
# Download binary
|
|
197
|
+
try:
|
|
198
|
+
with tempfile.NamedTemporaryFile(delete=False) as tmp:
|
|
199
|
+
tmp_path = Path(tmp.name)
|
|
200
|
+
|
|
201
|
+
with urllib.request.urlopen(url, timeout=120) as response:
|
|
202
|
+
tmp_path.write_bytes(response.read())
|
|
203
|
+
|
|
204
|
+
# Move to install location
|
|
205
|
+
shutil.move(str(tmp_path), str(binary_path))
|
|
206
|
+
|
|
207
|
+
# Make executable on Unix
|
|
208
|
+
if sys.platform != "win32":
|
|
209
|
+
binary_path.chmod(binary_path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
210
|
+
|
|
211
|
+
print(f"Installed pg0 to {binary_path}")
|
|
212
|
+
return binary_path
|
|
213
|
+
|
|
214
|
+
except Exception as e:
|
|
215
|
+
# Cleanup
|
|
216
|
+
if tmp_path.exists():
|
|
217
|
+
tmp_path.unlink()
|
|
218
|
+
raise Pg0NotFoundError(f"Failed to install pg0: {e}")
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _find_pg0() -> str:
|
|
222
|
+
"""Find the pg0 binary, installing if necessary."""
|
|
223
|
+
# Check PATH first
|
|
224
|
+
path = shutil.which("pg0")
|
|
225
|
+
if path:
|
|
226
|
+
return path
|
|
227
|
+
|
|
228
|
+
# Check our install location
|
|
229
|
+
install_dir = _get_install_dir()
|
|
230
|
+
binary_name = "pg0.exe" if sys.platform == "win32" else "pg0"
|
|
231
|
+
binary_path = install_dir / binary_name
|
|
232
|
+
|
|
233
|
+
if binary_path.exists():
|
|
234
|
+
return str(binary_path)
|
|
235
|
+
|
|
236
|
+
# Auto-install
|
|
237
|
+
installed_path = install(version=None)
|
|
238
|
+
return str(installed_path)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _run_pg0(*args: str, check: bool = True) -> subprocess.CompletedProcess:
|
|
242
|
+
"""Run a pg0 command."""
|
|
243
|
+
pg0_path = _find_pg0()
|
|
244
|
+
try:
|
|
245
|
+
result = subprocess.run(
|
|
246
|
+
[pg0_path, *args],
|
|
247
|
+
capture_output=True,
|
|
248
|
+
text=True,
|
|
249
|
+
)
|
|
250
|
+
if check and result.returncode != 0:
|
|
251
|
+
stderr = result.stderr.strip()
|
|
252
|
+
if "already running" in stderr.lower():
|
|
253
|
+
raise Pg0AlreadyRunningError(stderr)
|
|
254
|
+
elif "no running instance" in stderr.lower() or "not running" in stderr.lower():
|
|
255
|
+
raise Pg0NotRunningError(stderr)
|
|
256
|
+
else:
|
|
257
|
+
raise Pg0Error(stderr or f"pg0 command failed with code {result.returncode}")
|
|
258
|
+
return result
|
|
259
|
+
except FileNotFoundError:
|
|
260
|
+
raise Pg0NotFoundError("pg0 binary not found")
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class Pg0:
|
|
264
|
+
"""
|
|
265
|
+
Embedded PostgreSQL instance.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
name: Instance name (allows multiple instances)
|
|
269
|
+
port: Port to listen on
|
|
270
|
+
username: Database username
|
|
271
|
+
password: Database password
|
|
272
|
+
database: Database name
|
|
273
|
+
data_dir: Custom data directory
|
|
274
|
+
config: Dict of PostgreSQL configuration options
|
|
275
|
+
|
|
276
|
+
Example:
|
|
277
|
+
# Simple usage
|
|
278
|
+
pg = Pg0()
|
|
279
|
+
pg.start()
|
|
280
|
+
print(pg.uri)
|
|
281
|
+
pg.stop()
|
|
282
|
+
|
|
283
|
+
# Context manager
|
|
284
|
+
with Pg0(port=5433, database="myapp") as pg:
|
|
285
|
+
print(pg.uri)
|
|
286
|
+
|
|
287
|
+
# Custom config
|
|
288
|
+
pg = Pg0(config={"shared_buffers": "512MB"})
|
|
289
|
+
"""
|
|
290
|
+
|
|
291
|
+
def __init__(
|
|
292
|
+
self,
|
|
293
|
+
name: str = "default",
|
|
294
|
+
port: int = 5432,
|
|
295
|
+
username: str = "postgres",
|
|
296
|
+
password: str = "postgres",
|
|
297
|
+
database: str = "postgres",
|
|
298
|
+
data_dir: Optional[str] = None,
|
|
299
|
+
config: Optional[dict[str, str]] = None,
|
|
300
|
+
):
|
|
301
|
+
self.name = name
|
|
302
|
+
self.port = port
|
|
303
|
+
self.username = username
|
|
304
|
+
self.password = password
|
|
305
|
+
self.database = database
|
|
306
|
+
self.data_dir = data_dir
|
|
307
|
+
self.config = config or {}
|
|
308
|
+
|
|
309
|
+
def start(self) -> InstanceInfo:
|
|
310
|
+
"""
|
|
311
|
+
Start the PostgreSQL instance.
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
InstanceInfo with connection details
|
|
315
|
+
|
|
316
|
+
Raises:
|
|
317
|
+
Pg0AlreadyRunningError: If instance is already running
|
|
318
|
+
Pg0Error: If start fails
|
|
319
|
+
"""
|
|
320
|
+
args = [
|
|
321
|
+
"start",
|
|
322
|
+
"--name", self.name,
|
|
323
|
+
"--port", str(self.port),
|
|
324
|
+
"--username", self.username,
|
|
325
|
+
"--password", self.password,
|
|
326
|
+
"--database", self.database,
|
|
327
|
+
]
|
|
328
|
+
|
|
329
|
+
if self.data_dir:
|
|
330
|
+
args.extend(["--data-dir", self.data_dir])
|
|
331
|
+
|
|
332
|
+
for key, value in self.config.items():
|
|
333
|
+
args.extend(["-c", f"{key}={value}"])
|
|
334
|
+
|
|
335
|
+
_run_pg0(*args)
|
|
336
|
+
return self.info()
|
|
337
|
+
|
|
338
|
+
def stop(self) -> None:
|
|
339
|
+
"""
|
|
340
|
+
Stop the PostgreSQL instance.
|
|
341
|
+
|
|
342
|
+
Note: Does not raise an error if the instance is not running.
|
|
343
|
+
"""
|
|
344
|
+
_run_pg0("stop", "--name", self.name, check=False)
|
|
345
|
+
|
|
346
|
+
def drop(self, force: bool = True) -> None:
|
|
347
|
+
"""
|
|
348
|
+
Drop the PostgreSQL instance (stop if running, delete all data).
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
force: Skip confirmation prompt (default True for programmatic use)
|
|
352
|
+
|
|
353
|
+
Warning:
|
|
354
|
+
This permanently deletes all data for this instance!
|
|
355
|
+
"""
|
|
356
|
+
args = ["drop", "--name", self.name]
|
|
357
|
+
if force:
|
|
358
|
+
args.append("--force")
|
|
359
|
+
_run_pg0(*args, check=False)
|
|
360
|
+
|
|
361
|
+
def info(self) -> InstanceInfo:
|
|
362
|
+
"""
|
|
363
|
+
Get information about the PostgreSQL instance.
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
InstanceInfo with current status and connection details
|
|
367
|
+
"""
|
|
368
|
+
result = _run_pg0("info", "--name", self.name, "-o", "json", check=False)
|
|
369
|
+
data = json.loads(result.stdout)
|
|
370
|
+
return InstanceInfo.from_dict(data)
|
|
371
|
+
|
|
372
|
+
@property
|
|
373
|
+
def uri(self) -> Optional[str]:
|
|
374
|
+
"""Get the connection URI if running."""
|
|
375
|
+
return self.info().uri
|
|
376
|
+
|
|
377
|
+
@property
|
|
378
|
+
def running(self) -> bool:
|
|
379
|
+
"""Check if the instance is running."""
|
|
380
|
+
return self.info().running
|
|
381
|
+
|
|
382
|
+
def psql(self, *args: str) -> subprocess.CompletedProcess:
|
|
383
|
+
"""
|
|
384
|
+
Run psql with the given arguments.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
*args: Arguments to pass to psql (e.g., "-c", "SELECT 1")
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
CompletedProcess with stdout/stderr
|
|
391
|
+
|
|
392
|
+
Example:
|
|
393
|
+
result = pg.psql("-c", "SELECT version();")
|
|
394
|
+
print(result.stdout)
|
|
395
|
+
"""
|
|
396
|
+
return _run_pg0("psql", "--name", self.name, *args)
|
|
397
|
+
|
|
398
|
+
def execute(self, sql: str) -> str:
|
|
399
|
+
"""
|
|
400
|
+
Execute a SQL command and return the output.
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
sql: SQL command to execute
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
Command output as string
|
|
407
|
+
|
|
408
|
+
Example:
|
|
409
|
+
output = pg.execute("SELECT version();")
|
|
410
|
+
"""
|
|
411
|
+
result = self.psql("-c", sql)
|
|
412
|
+
return result.stdout
|
|
413
|
+
|
|
414
|
+
def __enter__(self) -> "Pg0":
|
|
415
|
+
"""Context manager entry - starts PostgreSQL."""
|
|
416
|
+
self.start()
|
|
417
|
+
return self
|
|
418
|
+
|
|
419
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
420
|
+
"""Context manager exit - stops PostgreSQL."""
|
|
421
|
+
try:
|
|
422
|
+
self.stop()
|
|
423
|
+
except Pg0NotRunningError:
|
|
424
|
+
pass
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def list_instances() -> list[InstanceInfo]:
|
|
428
|
+
"""
|
|
429
|
+
List all pg0 instances.
|
|
430
|
+
|
|
431
|
+
Returns:
|
|
432
|
+
List of InstanceInfo for all known instances
|
|
433
|
+
"""
|
|
434
|
+
result = _run_pg0("list", "-o", "json", check=False)
|
|
435
|
+
data = json.loads(result.stdout)
|
|
436
|
+
return [InstanceInfo.from_dict(item) for item in data]
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def start(
|
|
440
|
+
name: str = "default",
|
|
441
|
+
port: int = 5432,
|
|
442
|
+
username: str = "postgres",
|
|
443
|
+
password: str = "postgres",
|
|
444
|
+
database: str = "postgres",
|
|
445
|
+
**config: str,
|
|
446
|
+
) -> InstanceInfo:
|
|
447
|
+
"""
|
|
448
|
+
Start a PostgreSQL instance (convenience function).
|
|
449
|
+
|
|
450
|
+
Args:
|
|
451
|
+
name: Instance name
|
|
452
|
+
port: Port to listen on
|
|
453
|
+
username: Database username
|
|
454
|
+
password: Database password
|
|
455
|
+
database: Database name
|
|
456
|
+
**config: PostgreSQL configuration options
|
|
457
|
+
|
|
458
|
+
Returns:
|
|
459
|
+
InstanceInfo with connection details
|
|
460
|
+
|
|
461
|
+
Example:
|
|
462
|
+
info = pg0.start(port=5433, shared_buffers="512MB")
|
|
463
|
+
print(info.uri)
|
|
464
|
+
"""
|
|
465
|
+
pg = Pg0(
|
|
466
|
+
name=name,
|
|
467
|
+
port=port,
|
|
468
|
+
username=username,
|
|
469
|
+
password=password,
|
|
470
|
+
database=database,
|
|
471
|
+
config=config,
|
|
472
|
+
)
|
|
473
|
+
return pg.start()
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def stop(name: str = "default") -> None:
|
|
477
|
+
"""
|
|
478
|
+
Stop a PostgreSQL instance (convenience function).
|
|
479
|
+
|
|
480
|
+
Args:
|
|
481
|
+
name: Instance name to stop
|
|
482
|
+
"""
|
|
483
|
+
_run_pg0("stop", "--name", name, check=False)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def drop(name: str = "default", force: bool = True) -> None:
|
|
487
|
+
"""
|
|
488
|
+
Drop a PostgreSQL instance (convenience function).
|
|
489
|
+
|
|
490
|
+
Stops the instance if running and deletes all data.
|
|
491
|
+
|
|
492
|
+
Args:
|
|
493
|
+
name: Instance name to drop
|
|
494
|
+
force: Skip confirmation prompt (default True for programmatic use)
|
|
495
|
+
|
|
496
|
+
Warning:
|
|
497
|
+
This permanently deletes all data for this instance!
|
|
498
|
+
"""
|
|
499
|
+
args = ["drop", "--name", name]
|
|
500
|
+
if force:
|
|
501
|
+
args.append("--force")
|
|
502
|
+
_run_pg0(*args, check=False)
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def info(name: str = "default") -> InstanceInfo:
|
|
506
|
+
"""
|
|
507
|
+
Get information about a PostgreSQL instance (convenience function).
|
|
508
|
+
|
|
509
|
+
Args:
|
|
510
|
+
name: Instance name
|
|
511
|
+
|
|
512
|
+
Returns:
|
|
513
|
+
InstanceInfo with current status
|
|
514
|
+
"""
|
|
515
|
+
result = _run_pg0("info", "--name", name, "-o", "json", check=False)
|
|
516
|
+
data = json.loads(result.stdout)
|
|
517
|
+
return InstanceInfo.from_dict(data)
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
# Keep PostgreSQL as alias for backwards compatibility
|
|
521
|
+
PostgreSQL = Pg0
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
__all__ = [
|
|
525
|
+
"Pg0",
|
|
526
|
+
"PostgreSQL", # alias
|
|
527
|
+
"InstanceInfo",
|
|
528
|
+
"Pg0Error",
|
|
529
|
+
"Pg0NotFoundError",
|
|
530
|
+
"Pg0NotRunningError",
|
|
531
|
+
"Pg0AlreadyRunningError",
|
|
532
|
+
"install",
|
|
533
|
+
"list_instances",
|
|
534
|
+
"start",
|
|
535
|
+
"stop",
|
|
536
|
+
"drop",
|
|
537
|
+
"info",
|
|
538
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pg0-embedded"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "Python API for pg0 - embedded PostgreSQL"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
requires-python = ">=3.8"
|
|
8
|
+
keywords = ["postgresql", "database", "embedded", "pgvector"]
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Development Status :: 4 - Beta",
|
|
11
|
+
"Intended Audience :: Developers",
|
|
12
|
+
"License :: OSI Approved :: MIT License",
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Programming Language :: Python :: 3.8",
|
|
15
|
+
"Programming Language :: Python :: 3.9",
|
|
16
|
+
"Programming Language :: Python :: 3.10",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://github.com/vectorize-io/pg0"
|
|
23
|
+
Repository = "https://github.com/vectorize-io/pg0"
|
|
24
|
+
|
|
25
|
+
[dependency-groups]
|
|
26
|
+
dev = ["pytest>=8.0.0"]
|
|
27
|
+
|
|
28
|
+
[build-system]
|
|
29
|
+
requires = ["hatchling"]
|
|
30
|
+
build-backend = "hatchling.build"
|
|
31
|
+
|
|
32
|
+
[tool.hatch.build.targets.wheel]
|
|
33
|
+
packages = ["pg0"]
|
|
File without changes
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""Tests for pg0 Python client."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import pg0
|
|
5
|
+
from pg0 import Pg0, InstanceInfo, Pg0NotRunningError, Pg0AlreadyRunningError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# Use a unique port to avoid conflicts
|
|
9
|
+
TEST_PORT = 15432
|
|
10
|
+
TEST_NAME = "pytest-test"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture
|
|
14
|
+
def clean_instance():
|
|
15
|
+
"""Ensure test instance is stopped before and after test."""
|
|
16
|
+
# Cleanup before
|
|
17
|
+
try:
|
|
18
|
+
pg0.stop(TEST_NAME)
|
|
19
|
+
except Pg0NotRunningError:
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
yield
|
|
23
|
+
|
|
24
|
+
# Cleanup after
|
|
25
|
+
try:
|
|
26
|
+
pg0.stop(TEST_NAME)
|
|
27
|
+
except Pg0NotRunningError:
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TestPg0:
|
|
32
|
+
"""Tests for Pg0 class."""
|
|
33
|
+
|
|
34
|
+
def test_start_stop(self, clean_instance):
|
|
35
|
+
"""Test starting and stopping Pg0."""
|
|
36
|
+
pg = Pg0(name=TEST_NAME, port=TEST_PORT)
|
|
37
|
+
|
|
38
|
+
# Start
|
|
39
|
+
info = pg.start()
|
|
40
|
+
assert info.running is True
|
|
41
|
+
assert info.port == TEST_PORT
|
|
42
|
+
assert info.uri is not None
|
|
43
|
+
assert f":{TEST_PORT}/" in info.uri
|
|
44
|
+
|
|
45
|
+
# Stop
|
|
46
|
+
pg.stop()
|
|
47
|
+
info = pg.info()
|
|
48
|
+
assert info.running is False
|
|
49
|
+
|
|
50
|
+
def test_context_manager(self, clean_instance):
|
|
51
|
+
"""Test using Pg0 as context manager."""
|
|
52
|
+
with Pg0(name=TEST_NAME, port=TEST_PORT) as pg:
|
|
53
|
+
assert pg.running is True
|
|
54
|
+
assert pg.uri is not None
|
|
55
|
+
|
|
56
|
+
# Should be stopped after exiting context
|
|
57
|
+
info = pg0.info(TEST_NAME)
|
|
58
|
+
assert info.running is False
|
|
59
|
+
|
|
60
|
+
def test_execute_sql(self, clean_instance):
|
|
61
|
+
"""Test executing SQL commands."""
|
|
62
|
+
pg = Pg0(name=TEST_NAME, port=TEST_PORT)
|
|
63
|
+
pg.start()
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
# Execute a simple query
|
|
67
|
+
result = pg.execute("SELECT 1 as num;")
|
|
68
|
+
assert "1" in result
|
|
69
|
+
|
|
70
|
+
# Create and query a table
|
|
71
|
+
pg.execute("CREATE TABLE test_table (id serial, name text);")
|
|
72
|
+
pg.execute("INSERT INTO test_table (name) VALUES ('hello');")
|
|
73
|
+
result = pg.execute("SELECT name FROM test_table;")
|
|
74
|
+
assert "hello" in result
|
|
75
|
+
finally:
|
|
76
|
+
pg.stop()
|
|
77
|
+
|
|
78
|
+
def test_custom_credentials(self, clean_instance):
|
|
79
|
+
"""Test custom username, password, database."""
|
|
80
|
+
pg = Pg0(
|
|
81
|
+
name=TEST_NAME,
|
|
82
|
+
port=TEST_PORT,
|
|
83
|
+
username="testuser",
|
|
84
|
+
password="testpass",
|
|
85
|
+
database="testdb",
|
|
86
|
+
)
|
|
87
|
+
info = pg.start()
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
assert "testuser" in info.uri
|
|
91
|
+
assert "testpass" in info.uri
|
|
92
|
+
assert "testdb" in info.uri
|
|
93
|
+
finally:
|
|
94
|
+
pg.stop()
|
|
95
|
+
|
|
96
|
+
def test_custom_config(self, clean_instance):
|
|
97
|
+
"""Test custom Pg0 configuration."""
|
|
98
|
+
pg = Pg0(
|
|
99
|
+
name=TEST_NAME,
|
|
100
|
+
port=TEST_PORT,
|
|
101
|
+
config={"work_mem": "128MB"},
|
|
102
|
+
)
|
|
103
|
+
pg.start()
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
result = pg.execute("SHOW work_mem;")
|
|
107
|
+
assert "128MB" in result
|
|
108
|
+
finally:
|
|
109
|
+
pg.stop()
|
|
110
|
+
|
|
111
|
+
def test_already_running_error(self, clean_instance):
|
|
112
|
+
"""Test that starting twice raises error."""
|
|
113
|
+
pg = Pg0(name=TEST_NAME, port=TEST_PORT)
|
|
114
|
+
pg.start()
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
with pytest.raises(Pg0AlreadyRunningError):
|
|
118
|
+
pg.start()
|
|
119
|
+
finally:
|
|
120
|
+
pg.stop()
|
|
121
|
+
|
|
122
|
+
def test_not_running_error(self, clean_instance):
|
|
123
|
+
"""Test that stopping when not running raises error."""
|
|
124
|
+
pg = Pg0(name=TEST_NAME, port=TEST_PORT)
|
|
125
|
+
|
|
126
|
+
with pytest.raises(Pg0NotRunningError):
|
|
127
|
+
pg.stop()
|
|
128
|
+
|
|
129
|
+
def test_info_when_not_running(self, clean_instance):
|
|
130
|
+
"""Test getting info when not running."""
|
|
131
|
+
pg = Pg0(name=TEST_NAME, port=TEST_PORT)
|
|
132
|
+
info = pg.info()
|
|
133
|
+
|
|
134
|
+
assert info.running is False
|
|
135
|
+
assert info.uri is None
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class TestConvenienceFunctions:
|
|
139
|
+
"""Tests for module-level convenience functions."""
|
|
140
|
+
|
|
141
|
+
def test_start_stop_info(self, clean_instance):
|
|
142
|
+
"""Test start, stop, info functions."""
|
|
143
|
+
info = pg0.start(name=TEST_NAME, port=TEST_PORT)
|
|
144
|
+
assert info.running is True
|
|
145
|
+
|
|
146
|
+
info = pg0.info(TEST_NAME)
|
|
147
|
+
assert info.running is True
|
|
148
|
+
assert info.port == TEST_PORT
|
|
149
|
+
|
|
150
|
+
pg0.stop(TEST_NAME)
|
|
151
|
+
info = pg0.info(TEST_NAME)
|
|
152
|
+
assert info.running is False
|
|
153
|
+
|
|
154
|
+
def test_list_instances(self, clean_instance):
|
|
155
|
+
"""Test listing instances."""
|
|
156
|
+
# Start an instance
|
|
157
|
+
pg0.start(name=TEST_NAME, port=TEST_PORT)
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
instances = pg0.list_instances()
|
|
161
|
+
names = [i.name for i in instances]
|
|
162
|
+
assert TEST_NAME in names
|
|
163
|
+
finally:
|
|
164
|
+
pg0.stop(TEST_NAME)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class TestInstanceInfo:
|
|
168
|
+
"""Tests for InstanceInfo dataclass."""
|
|
169
|
+
|
|
170
|
+
def test_from_dict(self):
|
|
171
|
+
"""Test creating InstanceInfo from dict."""
|
|
172
|
+
data = {
|
|
173
|
+
"name": "test",
|
|
174
|
+
"running": True,
|
|
175
|
+
"pid": 1234,
|
|
176
|
+
"port": 5432,
|
|
177
|
+
"uri": "postgresql://localhost:5432/test",
|
|
178
|
+
}
|
|
179
|
+
info = InstanceInfo.from_dict(data)
|
|
180
|
+
|
|
181
|
+
assert info.name == "test"
|
|
182
|
+
assert info.running is True
|
|
183
|
+
assert info.pid == 1234
|
|
184
|
+
assert info.port == 5432
|
|
185
|
+
assert info.uri == "postgresql://localhost:5432/test"
|
|
186
|
+
|
|
187
|
+
def test_from_dict_minimal(self):
|
|
188
|
+
"""Test creating InstanceInfo from minimal dict."""
|
|
189
|
+
data = {"running": False}
|
|
190
|
+
info = InstanceInfo.from_dict(data)
|
|
191
|
+
|
|
192
|
+
assert info.name == "default"
|
|
193
|
+
assert info.running is False
|
|
194
|
+
assert info.pid is None
|
|
195
|
+
assert info.uri is None
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
version = 1
|
|
2
|
+
revision = 1
|
|
3
|
+
requires-python = ">=3.8"
|
|
4
|
+
resolution-markers = [
|
|
5
|
+
"python_full_version >= '3.10'",
|
|
6
|
+
"python_full_version == '3.9.*'",
|
|
7
|
+
"python_full_version < '3.9'",
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
[[package]]
|
|
11
|
+
name = "colorama"
|
|
12
|
+
version = "0.4.6"
|
|
13
|
+
source = { registry = "https://pypi.org/simple" }
|
|
14
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
|
|
15
|
+
wheels = [
|
|
16
|
+
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[[package]]
|
|
20
|
+
name = "exceptiongroup"
|
|
21
|
+
version = "1.3.1"
|
|
22
|
+
source = { registry = "https://pypi.org/simple" }
|
|
23
|
+
dependencies = [
|
|
24
|
+
{ name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
|
|
25
|
+
{ name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.13'" },
|
|
26
|
+
]
|
|
27
|
+
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371 }
|
|
28
|
+
wheels = [
|
|
29
|
+
{ url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740 },
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[[package]]
|
|
33
|
+
name = "iniconfig"
|
|
34
|
+
version = "2.1.0"
|
|
35
|
+
source = { registry = "https://pypi.org/simple" }
|
|
36
|
+
resolution-markers = [
|
|
37
|
+
"python_full_version == '3.9.*'",
|
|
38
|
+
"python_full_version < '3.9'",
|
|
39
|
+
]
|
|
40
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 }
|
|
41
|
+
wheels = [
|
|
42
|
+
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 },
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[[package]]
|
|
46
|
+
name = "iniconfig"
|
|
47
|
+
version = "2.3.0"
|
|
48
|
+
source = { registry = "https://pypi.org/simple" }
|
|
49
|
+
resolution-markers = [
|
|
50
|
+
"python_full_version >= '3.10'",
|
|
51
|
+
]
|
|
52
|
+
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503 }
|
|
53
|
+
wheels = [
|
|
54
|
+
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484 },
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
[[package]]
|
|
58
|
+
name = "packaging"
|
|
59
|
+
version = "25.0"
|
|
60
|
+
source = { registry = "https://pypi.org/simple" }
|
|
61
|
+
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 }
|
|
62
|
+
wheels = [
|
|
63
|
+
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 },
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
[[package]]
|
|
67
|
+
name = "pg0"
|
|
68
|
+
version = "0.1.0"
|
|
69
|
+
source = { editable = "." }
|
|
70
|
+
|
|
71
|
+
[package.dev-dependencies]
|
|
72
|
+
dev = [
|
|
73
|
+
{ name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
|
|
74
|
+
{ name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" },
|
|
75
|
+
{ name = "pytest", version = "9.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
[package.metadata]
|
|
79
|
+
|
|
80
|
+
[package.metadata.requires-dev]
|
|
81
|
+
dev = [{ name = "pytest", specifier = ">=8.0.0" }]
|
|
82
|
+
|
|
83
|
+
[[package]]
|
|
84
|
+
name = "pluggy"
|
|
85
|
+
version = "1.5.0"
|
|
86
|
+
source = { registry = "https://pypi.org/simple" }
|
|
87
|
+
resolution-markers = [
|
|
88
|
+
"python_full_version < '3.9'",
|
|
89
|
+
]
|
|
90
|
+
sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 }
|
|
91
|
+
wheels = [
|
|
92
|
+
{ url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 },
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
[[package]]
|
|
96
|
+
name = "pluggy"
|
|
97
|
+
version = "1.6.0"
|
|
98
|
+
source = { registry = "https://pypi.org/simple" }
|
|
99
|
+
resolution-markers = [
|
|
100
|
+
"python_full_version >= '3.10'",
|
|
101
|
+
"python_full_version == '3.9.*'",
|
|
102
|
+
]
|
|
103
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 }
|
|
104
|
+
wheels = [
|
|
105
|
+
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 },
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
[[package]]
|
|
109
|
+
name = "pygments"
|
|
110
|
+
version = "2.19.2"
|
|
111
|
+
source = { registry = "https://pypi.org/simple" }
|
|
112
|
+
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 }
|
|
113
|
+
wheels = [
|
|
114
|
+
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 },
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
[[package]]
|
|
118
|
+
name = "pytest"
|
|
119
|
+
version = "8.3.5"
|
|
120
|
+
source = { registry = "https://pypi.org/simple" }
|
|
121
|
+
resolution-markers = [
|
|
122
|
+
"python_full_version < '3.9'",
|
|
123
|
+
]
|
|
124
|
+
dependencies = [
|
|
125
|
+
{ name = "colorama", marker = "python_full_version < '3.9' and sys_platform == 'win32'" },
|
|
126
|
+
{ name = "exceptiongroup", marker = "python_full_version < '3.9'" },
|
|
127
|
+
{ name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
|
|
128
|
+
{ name = "packaging", marker = "python_full_version < '3.9'" },
|
|
129
|
+
{ name = "pluggy", version = "1.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
|
|
130
|
+
{ name = "tomli", marker = "python_full_version < '3.9'" },
|
|
131
|
+
]
|
|
132
|
+
sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 }
|
|
133
|
+
wheels = [
|
|
134
|
+
{ url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 },
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
[[package]]
|
|
138
|
+
name = "pytest"
|
|
139
|
+
version = "8.4.2"
|
|
140
|
+
source = { registry = "https://pypi.org/simple" }
|
|
141
|
+
resolution-markers = [
|
|
142
|
+
"python_full_version == '3.9.*'",
|
|
143
|
+
]
|
|
144
|
+
dependencies = [
|
|
145
|
+
{ name = "colorama", marker = "python_full_version == '3.9.*' and sys_platform == 'win32'" },
|
|
146
|
+
{ name = "exceptiongroup", marker = "python_full_version == '3.9.*'" },
|
|
147
|
+
{ name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" },
|
|
148
|
+
{ name = "packaging", marker = "python_full_version == '3.9.*'" },
|
|
149
|
+
{ name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" },
|
|
150
|
+
{ name = "pygments", marker = "python_full_version == '3.9.*'" },
|
|
151
|
+
{ name = "tomli", marker = "python_full_version == '3.9.*'" },
|
|
152
|
+
]
|
|
153
|
+
sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618 }
|
|
154
|
+
wheels = [
|
|
155
|
+
{ url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750 },
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
[[package]]
|
|
159
|
+
name = "pytest"
|
|
160
|
+
version = "9.0.1"
|
|
161
|
+
source = { registry = "https://pypi.org/simple" }
|
|
162
|
+
resolution-markers = [
|
|
163
|
+
"python_full_version >= '3.10'",
|
|
164
|
+
]
|
|
165
|
+
dependencies = [
|
|
166
|
+
{ name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" },
|
|
167
|
+
{ name = "exceptiongroup", marker = "python_full_version == '3.10.*'" },
|
|
168
|
+
{ name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
|
169
|
+
{ name = "packaging", marker = "python_full_version >= '3.10'" },
|
|
170
|
+
{ name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
|
171
|
+
{ name = "pygments", marker = "python_full_version >= '3.10'" },
|
|
172
|
+
{ name = "tomli", marker = "python_full_version == '3.10.*'" },
|
|
173
|
+
]
|
|
174
|
+
sdist = { url = "https://files.pythonhosted.org/packages/07/56/f013048ac4bc4c1d9be45afd4ab209ea62822fb1598f40687e6bf45dcea4/pytest-9.0.1.tar.gz", hash = "sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8", size = 1564125 }
|
|
175
|
+
wheels = [
|
|
176
|
+
{ url = "https://files.pythonhosted.org/packages/0b/8b/6300fb80f858cda1c51ffa17075df5d846757081d11ab4aa35cef9e6258b/pytest-9.0.1-py3-none-any.whl", hash = "sha256:67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad", size = 373668 },
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
[[package]]
|
|
180
|
+
name = "tomli"
|
|
181
|
+
version = "2.3.0"
|
|
182
|
+
source = { registry = "https://pypi.org/simple" }
|
|
183
|
+
sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392 }
|
|
184
|
+
wheels = [
|
|
185
|
+
{ url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236 },
|
|
186
|
+
{ url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084 },
|
|
187
|
+
{ url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832 },
|
|
188
|
+
{ url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052 },
|
|
189
|
+
{ url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555 },
|
|
190
|
+
{ url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128 },
|
|
191
|
+
{ url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445 },
|
|
192
|
+
{ url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165 },
|
|
193
|
+
{ url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891 },
|
|
194
|
+
{ url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796 },
|
|
195
|
+
{ url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121 },
|
|
196
|
+
{ url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070 },
|
|
197
|
+
{ url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859 },
|
|
198
|
+
{ url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296 },
|
|
199
|
+
{ url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124 },
|
|
200
|
+
{ url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698 },
|
|
201
|
+
{ url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819 },
|
|
202
|
+
{ url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766 },
|
|
203
|
+
{ url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771 },
|
|
204
|
+
{ url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586 },
|
|
205
|
+
{ url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792 },
|
|
206
|
+
{ url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909 },
|
|
207
|
+
{ url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946 },
|
|
208
|
+
{ url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705 },
|
|
209
|
+
{ url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244 },
|
|
210
|
+
{ url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637 },
|
|
211
|
+
{ url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925 },
|
|
212
|
+
{ url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045 },
|
|
213
|
+
{ url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835 },
|
|
214
|
+
{ url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109 },
|
|
215
|
+
{ url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930 },
|
|
216
|
+
{ url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964 },
|
|
217
|
+
{ url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065 },
|
|
218
|
+
{ url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088 },
|
|
219
|
+
{ url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193 },
|
|
220
|
+
{ url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488 },
|
|
221
|
+
{ url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669 },
|
|
222
|
+
{ url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709 },
|
|
223
|
+
{ url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563 },
|
|
224
|
+
{ url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756 },
|
|
225
|
+
{ url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408 },
|
|
226
|
+
]
|
|
227
|
+
|
|
228
|
+
[[package]]
|
|
229
|
+
name = "typing-extensions"
|
|
230
|
+
version = "4.13.2"
|
|
231
|
+
source = { registry = "https://pypi.org/simple" }
|
|
232
|
+
resolution-markers = [
|
|
233
|
+
"python_full_version < '3.9'",
|
|
234
|
+
]
|
|
235
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 }
|
|
236
|
+
wheels = [
|
|
237
|
+
{ url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 },
|
|
238
|
+
]
|
|
239
|
+
|
|
240
|
+
[[package]]
|
|
241
|
+
name = "typing-extensions"
|
|
242
|
+
version = "4.15.0"
|
|
243
|
+
source = { registry = "https://pypi.org/simple" }
|
|
244
|
+
resolution-markers = [
|
|
245
|
+
"python_full_version >= '3.10'",
|
|
246
|
+
"python_full_version == '3.9.*'",
|
|
247
|
+
]
|
|
248
|
+
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391 }
|
|
249
|
+
wheels = [
|
|
250
|
+
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614 },
|
|
251
|
+
]
|