picopyn 0.3.0.dev1__tar.gz → 1.0.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.
- {picopyn-0.3.0.dev1/picopyn.egg-info → picopyn-1.0.0}/PKG-INFO +37 -7
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/README.md +35 -5
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/benchmark/bench.py +1 -1
- picopyn-1.0.0/docs/async.md +9 -0
- picopyn-1.0.0/docs/contributing.md +7 -0
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/docs/explain.md +1 -1
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/docs/index.md +31 -2
- picopyn-1.0.0/docs/sync.md +17 -0
- picopyn-1.0.0/docs/topology.md +9 -0
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn/__init__.py +1 -4
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn/asynchronous/__init__.py +0 -4
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn/asynchronous/client.py +2 -2
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn/asynchronous/connection.py +15 -4
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn/asynchronous/pool.py +342 -25
- picopyn-1.0.0/picopyn/asynchronous/topology_tracker.py +178 -0
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn/ssl.py +8 -3
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn/synchronous/__init__.py +4 -6
- picopyn-1.0.0/picopyn/synchronous/connection.py +174 -0
- picopyn-1.0.0/picopyn/synchronous/cursor.py +272 -0
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn/synchronous/errors.py +26 -0
- picopyn-1.0.0/picopyn/synchronous/pool.py +474 -0
- picopyn-1.0.0/picopyn/topology.py +220 -0
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn/utils/__init__.py +13 -1
- picopyn-1.0.0/picopyn/utils/discovery.py +64 -0
- picopyn-1.0.0/picopyn/utils/dsn.py +141 -0
- picopyn-1.0.0/picopyn/utils/queries.py +40 -0
- picopyn-1.0.0/picopyn/utils/topology.py +90 -0
- {picopyn-0.3.0.dev1 → picopyn-1.0.0/picopyn.egg-info}/PKG-INFO +37 -7
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn.egg-info/SOURCES.txt +7 -1
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn.egg-info/requires.txt +1 -3
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/pyproject.toml +2 -2
- picopyn-0.3.0.dev1/docs/async.md +0 -3
- picopyn-0.3.0.dev1/docs/sync.md +0 -3
- picopyn-0.3.0.dev1/picopyn/synchronous/connection.py +0 -62
- picopyn-0.3.0.dev1/picopyn/synchronous/cursor.py +0 -100
- picopyn-0.3.0.dev1/picopyn/utils/discovery.py +0 -42
- picopyn-0.3.0.dev1/picopyn/utils/dsn.py +0 -48
- picopyn-0.3.0.dev1/picopyn/utils/queries.py +0 -21
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/AUTHORS.md +0 -0
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/LICENSE +0 -0
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/MANIFEST.in +0 -0
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/benchmark/README.md +0 -0
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/benchmark/examples/10k_rows_1_thread_1_batch/overall_speed_time.png +0 -0
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/benchmark/examples/10k_rows_1_thread_1_batch/threads_speed_time.png +0 -0
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/benchmark/examples/1m_rows_10_threads_100_batch/errors_cumulative.png +0 -0
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/benchmark/examples/1m_rows_10_threads_100_batch/overall_speed_time.png +0 -0
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/benchmark/examples/1m_rows_10_threads_100_batch/threads_speed_time.png +0 -0
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/benchmark/generate_data_to_csv.py +0 -0
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/docs/installation.md +0 -0
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn/explain.py +0 -0
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn.egg-info/dependency_links.txt +0 -0
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn.egg-info/top_level.txt +0 -0
- {picopyn-0.3.0.dev1 → picopyn-1.0.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: picopyn
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: Python connector for working with the distributed Picodata database.
|
|
5
5
|
Author-email: Asya Lomakina <a.lomakina@picodata.io>, Igor Kuznetsov <i.kuznetsov@picodata.io>, Dmitriy Кoltsov <dkoltsov@picodata.io>, Daniil Gorlyakov <d.gorlyakov@picodata.io>
|
|
6
6
|
License-Expression: BSD-2-Clause
|
|
@@ -20,7 +20,7 @@ License-File: LICENSE
|
|
|
20
20
|
License-File: AUTHORS.md
|
|
21
21
|
Requires-Dist: async-timeout==5.0.1
|
|
22
22
|
Requires-Dist: asyncpg==0.30.0
|
|
23
|
-
Requires-Dist: typing_extensions==4.12.2
|
|
23
|
+
Requires-Dist: typing_extensions==4.12.2
|
|
24
24
|
Requires-Dist: psycopg[binary]==3.3.4
|
|
25
25
|
Provides-Extra: test
|
|
26
26
|
Requires-Dist: pytest==8.3.5; extra == "test"
|
|
@@ -48,11 +48,17 @@ Dynamic: license-file
|
|
|
48
48
|
|
|
49
49
|
**Picopyn** is a Python package for working with the distributed [Picodata](https://picodata.io/en/) database.
|
|
50
50
|
|
|
51
|
+
**Version compatibility**:
|
|
52
|
+
| Picodata version | Status |
|
|
53
|
+
| ---------------- | ------ |
|
|
54
|
+
| >=25.4.x, < 26.2.x | ✅ Fully supported |
|
|
55
|
+
| 26.2.x | ⚠️ Not yet tested |
|
|
56
|
+
|
|
51
57
|
## Features
|
|
52
58
|
* Connection pooling with configurable pool size
|
|
53
59
|
* Optional automatic node discovery
|
|
54
60
|
* Pluggable load-balancing strategies
|
|
55
|
-
* Provides both asynchronous API (based on [asyncpg](https://github.com/MagicStack/asyncpg)) and synchronous API (based on [psycopg](https://www.psycopg.org/))
|
|
61
|
+
* Provides both asynchronous API (based on [asyncpg](https://github.com/MagicStack/asyncpg)) and synchronous [DPAPI-compatible](https://peps.python.org/pep-0249/) API (based on [psycopg](https://www.psycopg.org/))
|
|
56
62
|
|
|
57
63
|
## Navigation
|
|
58
64
|
* [Installation](#installation)
|
|
@@ -80,9 +86,11 @@ pip install -e .
|
|
|
80
86
|
|
|
81
87
|
## Quickstart
|
|
82
88
|
|
|
89
|
+
Async:
|
|
90
|
+
|
|
83
91
|
```python
|
|
84
92
|
import asyncio
|
|
85
|
-
from picopyn import Client
|
|
93
|
+
from picopyn.asynchronous import Client
|
|
86
94
|
|
|
87
95
|
async def main():
|
|
88
96
|
# create and connect client to the picodata cluster
|
|
@@ -94,9 +102,9 @@ async def main():
|
|
|
94
102
|
CREATE TABLE "warehouse" (id INTEGER NOT NULL, item TEXT NOT NULL, PRIMARY KEY (id)) USING memtx DISTRIBUTED BY (id) OPTION (TIMEOUT = 3.0);
|
|
95
103
|
''')
|
|
96
104
|
|
|
97
|
-
# execute DML operations
|
|
98
|
-
await client.execute('INSERT INTO
|
|
99
|
-
rows = await client.fetch('SELECT * FROM
|
|
105
|
+
# execute DML/DQL operations
|
|
106
|
+
await client.execute('INSERT INTO "warehouse" VALUES ($1::int, $2::varchar)', 1, "test")
|
|
107
|
+
rows = await client.fetch('SELECT * FROM "warehouse"')
|
|
100
108
|
print(rows)
|
|
101
109
|
|
|
102
110
|
await client.close()
|
|
@@ -104,6 +112,28 @@ async def main():
|
|
|
104
112
|
asyncio.run(main())
|
|
105
113
|
```
|
|
106
114
|
|
|
115
|
+
Sync:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from picopyn.synchronous import connect
|
|
119
|
+
|
|
120
|
+
# create and connect to the picodata cluster
|
|
121
|
+
conn = connect("postgresql://admin:pass@localhost:5432")
|
|
122
|
+
cur = conn.cursor()
|
|
123
|
+
|
|
124
|
+
# execute DDL operations
|
|
125
|
+
cur.execute('''
|
|
126
|
+
CREATE TABLE "warehouse" (id INTEGER NOT NULL, item TEXT NOT NULL, PRIMARY KEY (id)) USING memtx DISTRIBUTED BY (id) OPTION (TIMEOUT = 3.0);
|
|
127
|
+
''')
|
|
128
|
+
|
|
129
|
+
# execute DML/DQL operations
|
|
130
|
+
cur.execute('INSERT INTO "warehouse" VALUES (%s, %s)', (1, "test"))
|
|
131
|
+
cur.execute('SELECT * FROM "warehouse"')
|
|
132
|
+
print(cur.fetchall())
|
|
133
|
+
|
|
134
|
+
conn.close()
|
|
135
|
+
```
|
|
136
|
+
|
|
107
137
|
## Development
|
|
108
138
|
For development we use [uv](https://docs.astral.sh/uv/getting-started/installation/) as package manager and [docker compose](https://docs.docker.com/compose/install/) for test environment.
|
|
109
139
|
|
|
@@ -6,11 +6,17 @@
|
|
|
6
6
|
|
|
7
7
|
**Picopyn** is a Python package for working with the distributed [Picodata](https://picodata.io/en/) database.
|
|
8
8
|
|
|
9
|
+
**Version compatibility**:
|
|
10
|
+
| Picodata version | Status |
|
|
11
|
+
| ---------------- | ------ |
|
|
12
|
+
| >=25.4.x, < 26.2.x | ✅ Fully supported |
|
|
13
|
+
| 26.2.x | ⚠️ Not yet tested |
|
|
14
|
+
|
|
9
15
|
## Features
|
|
10
16
|
* Connection pooling with configurable pool size
|
|
11
17
|
* Optional automatic node discovery
|
|
12
18
|
* Pluggable load-balancing strategies
|
|
13
|
-
* Provides both asynchronous API (based on [asyncpg](https://github.com/MagicStack/asyncpg)) and synchronous API (based on [psycopg](https://www.psycopg.org/))
|
|
19
|
+
* Provides both asynchronous API (based on [asyncpg](https://github.com/MagicStack/asyncpg)) and synchronous [DPAPI-compatible](https://peps.python.org/pep-0249/) API (based on [psycopg](https://www.psycopg.org/))
|
|
14
20
|
|
|
15
21
|
## Navigation
|
|
16
22
|
* [Installation](#installation)
|
|
@@ -38,9 +44,11 @@ pip install -e .
|
|
|
38
44
|
|
|
39
45
|
## Quickstart
|
|
40
46
|
|
|
47
|
+
Async:
|
|
48
|
+
|
|
41
49
|
```python
|
|
42
50
|
import asyncio
|
|
43
|
-
from picopyn import Client
|
|
51
|
+
from picopyn.asynchronous import Client
|
|
44
52
|
|
|
45
53
|
async def main():
|
|
46
54
|
# create and connect client to the picodata cluster
|
|
@@ -52,9 +60,9 @@ async def main():
|
|
|
52
60
|
CREATE TABLE "warehouse" (id INTEGER NOT NULL, item TEXT NOT NULL, PRIMARY KEY (id)) USING memtx DISTRIBUTED BY (id) OPTION (TIMEOUT = 3.0);
|
|
53
61
|
''')
|
|
54
62
|
|
|
55
|
-
# execute DML operations
|
|
56
|
-
await client.execute('INSERT INTO
|
|
57
|
-
rows = await client.fetch('SELECT * FROM
|
|
63
|
+
# execute DML/DQL operations
|
|
64
|
+
await client.execute('INSERT INTO "warehouse" VALUES ($1::int, $2::varchar)', 1, "test")
|
|
65
|
+
rows = await client.fetch('SELECT * FROM "warehouse"')
|
|
58
66
|
print(rows)
|
|
59
67
|
|
|
60
68
|
await client.close()
|
|
@@ -62,6 +70,28 @@ async def main():
|
|
|
62
70
|
asyncio.run(main())
|
|
63
71
|
```
|
|
64
72
|
|
|
73
|
+
Sync:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from picopyn.synchronous import connect
|
|
77
|
+
|
|
78
|
+
# create and connect to the picodata cluster
|
|
79
|
+
conn = connect("postgresql://admin:pass@localhost:5432")
|
|
80
|
+
cur = conn.cursor()
|
|
81
|
+
|
|
82
|
+
# execute DDL operations
|
|
83
|
+
cur.execute('''
|
|
84
|
+
CREATE TABLE "warehouse" (id INTEGER NOT NULL, item TEXT NOT NULL, PRIMARY KEY (id)) USING memtx DISTRIBUTED BY (id) OPTION (TIMEOUT = 3.0);
|
|
85
|
+
''')
|
|
86
|
+
|
|
87
|
+
# execute DML/DQL operations
|
|
88
|
+
cur.execute('INSERT INTO "warehouse" VALUES (%s, %s)', (1, "test"))
|
|
89
|
+
cur.execute('SELECT * FROM "warehouse"')
|
|
90
|
+
print(cur.fetchall())
|
|
91
|
+
|
|
92
|
+
conn.close()
|
|
93
|
+
```
|
|
94
|
+
|
|
65
95
|
## Development
|
|
66
96
|
For development we use [uv](https://docs.astral.sh/uv/getting-started/installation/) as package manager and [docker compose](https://docs.docker.com/compose/install/) for test environment.
|
|
67
97
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Contributions are welcome! The project is hosted on GitLab:
|
|
4
|
+
|
|
5
|
+
**[git.picodata.io/solution/picopyn](https://git.picodata.io/solution/picopyn)**
|
|
6
|
+
|
|
7
|
+
To get started with the development environment, running tests, and code style requirements, see the [Development](https://git.picodata.io/solution/picopyn#development) section in the project README.
|
|
@@ -2,13 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
**picopyn** is a Python connector for working with the distributed [Picodata](https://picodata.io/en/) database.
|
|
4
4
|
|
|
5
|
+
**Version compatibility**:
|
|
6
|
+
|
|
7
|
+
| Picodata version | Status |
|
|
8
|
+
| ---------------- | ------ |
|
|
9
|
+
| >=25.4.x, < 26.2.x | ✅ Fully supported |
|
|
10
|
+
| 26.2.x | ⚠️ Not yet tested |
|
|
11
|
+
|
|
5
12
|
## Features
|
|
6
13
|
|
|
7
14
|
- Connection pooling with configurable pool size
|
|
8
15
|
- Optional automatic node discovery
|
|
9
16
|
- Pluggable load-balancing strategies
|
|
10
17
|
- Asynchronous API based on [asyncpg](https://github.com/MagicStack/asyncpg)
|
|
11
|
-
- Synchronous API based on [psycopg](https://www.psycopg.org/)
|
|
18
|
+
- Synchronous [DPAPI-compatible](https://peps.python.org/pep-0249/) API based on [psycopg](https://www.psycopg.org/)
|
|
12
19
|
|
|
13
20
|
## Quick start
|
|
14
21
|
|
|
@@ -16,9 +23,11 @@
|
|
|
16
23
|
pip install picopyn
|
|
17
24
|
```
|
|
18
25
|
|
|
26
|
+
Async:
|
|
27
|
+
|
|
19
28
|
```python
|
|
20
29
|
import asyncio
|
|
21
|
-
from picopyn import Client
|
|
30
|
+
from picopyn.asynchronous import Client
|
|
22
31
|
|
|
23
32
|
async def main():
|
|
24
33
|
client = Client(dsn="postgresql://admin:pass@localhost:5432")
|
|
@@ -38,4 +47,24 @@ async def main():
|
|
|
38
47
|
asyncio.run(main())
|
|
39
48
|
```
|
|
40
49
|
|
|
50
|
+
Sync:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from picopyn.synchronous import connect
|
|
54
|
+
|
|
55
|
+
conn = connect("postgresql://admin:pass@localhost:5432")
|
|
56
|
+
cur = conn.cursor()
|
|
57
|
+
|
|
58
|
+
cur.execute('''
|
|
59
|
+
CREATE TABLE "warehouse" (id INTEGER NOT NULL, item TEXT NOT NULL, PRIMARY KEY (id))
|
|
60
|
+
USING memtx DISTRIBUTED BY (id) OPTION (TIMEOUT = 3.0);
|
|
61
|
+
''')
|
|
62
|
+
|
|
63
|
+
cur.execute('INSERT INTO "warehouse" VALUES (%s, %s)', (1, "test"))
|
|
64
|
+
cur.execute('SELECT * FROM "warehouse"')
|
|
65
|
+
print(cur.fetchall())
|
|
66
|
+
|
|
67
|
+
conn.close()
|
|
68
|
+
```
|
|
69
|
+
|
|
41
70
|
See [Installation](installation.md) for all install options.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Sync
|
|
2
|
+
|
|
3
|
+
::: picopyn.synchronous
|
|
4
|
+
options:
|
|
5
|
+
members:
|
|
6
|
+
- connect
|
|
7
|
+
- Connection
|
|
8
|
+
- Cursor
|
|
9
|
+
|
|
10
|
+
### Errors
|
|
11
|
+
|
|
12
|
+
::: picopyn.synchronous.errors
|
|
13
|
+
options:
|
|
14
|
+
show_root_heading: false
|
|
15
|
+
show_root_toc_entry: false
|
|
16
|
+
heading_level: 4
|
|
17
|
+
members_order: source
|
|
@@ -37,14 +37,11 @@ logging.getLogger("picopyn.asynchronous.pool").setLevel(logging.DEBUG)
|
|
|
37
37
|
|
|
38
38
|
import logging
|
|
39
39
|
|
|
40
|
-
from .
|
|
40
|
+
from .explain import ExplainPlan, ExplainRawPlan
|
|
41
41
|
|
|
42
42
|
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
|
43
43
|
|
|
44
44
|
__all__ = [
|
|
45
|
-
"Client",
|
|
46
|
-
"Connection",
|
|
47
45
|
"ExplainPlan",
|
|
48
46
|
"ExplainRawPlan",
|
|
49
|
-
"Pool",
|
|
50
47
|
]
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
"""Async API based on asyncpg for connection management, pools, and client classes."""
|
|
2
2
|
|
|
3
|
-
from picopyn.explain import ExplainPlan, ExplainRawPlan
|
|
4
|
-
|
|
5
3
|
from .client import Client
|
|
6
4
|
from .connection import Connection
|
|
7
5
|
from .pool import Pool
|
|
@@ -9,7 +7,5 @@ from .pool import Pool
|
|
|
9
7
|
__all__ = [
|
|
10
8
|
"Client",
|
|
11
9
|
"Connection",
|
|
12
|
-
"ExplainPlan",
|
|
13
|
-
"ExplainRawPlan",
|
|
14
10
|
"Pool",
|
|
15
11
|
]
|
|
@@ -12,8 +12,8 @@ from .pool import Pool
|
|
|
12
12
|
class Client:
|
|
13
13
|
"""Async client for managing connections to a picodata cluster using a connection pool.
|
|
14
14
|
|
|
15
|
-
This client handles connection pooling, automatic node discovery
|
|
16
|
-
and supports load balancing strategies for query distribution.
|
|
15
|
+
This client handles connection pooling, automatic cluster node discovery,
|
|
16
|
+
and supports load balancing strategies for query distribution. See details about [Pool][picopyn.asynchronous.Pool].
|
|
17
17
|
|
|
18
18
|
Args:
|
|
19
19
|
dsn: The data source name (e.g., "postgresql://user:pass@host:port") for the cluster.
|
|
@@ -11,6 +11,7 @@ from picopyn.explain import (
|
|
|
11
11
|
rows_to_lines,
|
|
12
12
|
)
|
|
13
13
|
from picopyn.ssl import validate_ssl_source
|
|
14
|
+
from picopyn.utils import _redact_dsn
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class Connection:
|
|
@@ -31,20 +32,30 @@ class Connection:
|
|
|
31
32
|
raise ValueError("dsn can not be None")
|
|
32
33
|
|
|
33
34
|
validate_ssl_source(dsn, _connect_kwargs)
|
|
34
|
-
|
|
35
|
-
self.dsn = dsn
|
|
35
|
+
# DSN with hidden password
|
|
36
|
+
self.dsn = _redact_dsn(dsn)
|
|
37
|
+
# origin DSN to connect
|
|
38
|
+
self._connect_dsn = dsn
|
|
36
39
|
self._connect_kwargs = _connect_kwargs
|
|
37
40
|
self.conn = None
|
|
38
41
|
|
|
42
|
+
def is_closed(self) -> bool:
|
|
43
|
+
return self.conn is None or self.conn.is_closed()
|
|
44
|
+
|
|
45
|
+
def __repr__(self) -> str:
|
|
46
|
+
# Explicit repr backed by the already-redacted `dsn`, so printing/logging
|
|
47
|
+
# a Connection can never expose the password held in `_connect_dsn`.
|
|
48
|
+
return f"Connection(dsn={self.dsn!r})"
|
|
49
|
+
|
|
39
50
|
async def connect(self) -> None:
|
|
40
51
|
"""
|
|
41
52
|
Create new connection to Picodata
|
|
42
53
|
"""
|
|
43
|
-
if self.conn and not self.
|
|
54
|
+
if self.conn and not self.is_closed():
|
|
44
55
|
await self.close()
|
|
45
56
|
|
|
46
57
|
try:
|
|
47
|
-
self.conn = await asyncpg.connect(self.
|
|
58
|
+
self.conn = await asyncpg.connect(self._connect_dsn, **self._connect_kwargs)
|
|
48
59
|
except Exception as e:
|
|
49
60
|
raise RuntimeError(
|
|
50
61
|
f"Failed to connect to picodata instance using DSN {self.dsn}: {e}"
|