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.
Files changed (53) hide show
  1. {picopyn-0.3.0.dev1/picopyn.egg-info → picopyn-1.0.0}/PKG-INFO +37 -7
  2. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/README.md +35 -5
  3. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/benchmark/bench.py +1 -1
  4. picopyn-1.0.0/docs/async.md +9 -0
  5. picopyn-1.0.0/docs/contributing.md +7 -0
  6. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/docs/explain.md +1 -1
  7. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/docs/index.md +31 -2
  8. picopyn-1.0.0/docs/sync.md +17 -0
  9. picopyn-1.0.0/docs/topology.md +9 -0
  10. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn/__init__.py +1 -4
  11. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn/asynchronous/__init__.py +0 -4
  12. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn/asynchronous/client.py +2 -2
  13. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn/asynchronous/connection.py +15 -4
  14. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn/asynchronous/pool.py +342 -25
  15. picopyn-1.0.0/picopyn/asynchronous/topology_tracker.py +178 -0
  16. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn/ssl.py +8 -3
  17. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn/synchronous/__init__.py +4 -6
  18. picopyn-1.0.0/picopyn/synchronous/connection.py +174 -0
  19. picopyn-1.0.0/picopyn/synchronous/cursor.py +272 -0
  20. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn/synchronous/errors.py +26 -0
  21. picopyn-1.0.0/picopyn/synchronous/pool.py +474 -0
  22. picopyn-1.0.0/picopyn/topology.py +220 -0
  23. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn/utils/__init__.py +13 -1
  24. picopyn-1.0.0/picopyn/utils/discovery.py +64 -0
  25. picopyn-1.0.0/picopyn/utils/dsn.py +141 -0
  26. picopyn-1.0.0/picopyn/utils/queries.py +40 -0
  27. picopyn-1.0.0/picopyn/utils/topology.py +90 -0
  28. {picopyn-0.3.0.dev1 → picopyn-1.0.0/picopyn.egg-info}/PKG-INFO +37 -7
  29. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn.egg-info/SOURCES.txt +7 -1
  30. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn.egg-info/requires.txt +1 -3
  31. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/pyproject.toml +2 -2
  32. picopyn-0.3.0.dev1/docs/async.md +0 -3
  33. picopyn-0.3.0.dev1/docs/sync.md +0 -3
  34. picopyn-0.3.0.dev1/picopyn/synchronous/connection.py +0 -62
  35. picopyn-0.3.0.dev1/picopyn/synchronous/cursor.py +0 -100
  36. picopyn-0.3.0.dev1/picopyn/utils/discovery.py +0 -42
  37. picopyn-0.3.0.dev1/picopyn/utils/dsn.py +0 -48
  38. picopyn-0.3.0.dev1/picopyn/utils/queries.py +0 -21
  39. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/AUTHORS.md +0 -0
  40. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/LICENSE +0 -0
  41. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/MANIFEST.in +0 -0
  42. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/benchmark/README.md +0 -0
  43. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/benchmark/examples/10k_rows_1_thread_1_batch/overall_speed_time.png +0 -0
  44. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/benchmark/examples/10k_rows_1_thread_1_batch/threads_speed_time.png +0 -0
  45. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/benchmark/examples/1m_rows_10_threads_100_batch/errors_cumulative.png +0 -0
  46. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/benchmark/examples/1m_rows_10_threads_100_batch/overall_speed_time.png +0 -0
  47. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/benchmark/examples/1m_rows_10_threads_100_batch/threads_speed_time.png +0 -0
  48. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/benchmark/generate_data_to_csv.py +0 -0
  49. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/docs/installation.md +0 -0
  50. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn/explain.py +0 -0
  51. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn.egg-info/dependency_links.txt +0 -0
  52. {picopyn-0.3.0.dev1 → picopyn-1.0.0}/picopyn.egg-info/top_level.txt +0 -0
  53. {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.0.dev1
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; python_version == "3.10"
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 \"warehouse\" VALUES ($1::int, $2::varchar)', 1, "test")
99
- rows = await client.fetch('SELECT * FROM \"warehouse\"')
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 \"warehouse\" VALUES ($1::int, $2::varchar)', 1, "test")
57
- rows = await client.fetch('SELECT * FROM \"warehouse\"')
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
 
@@ -8,7 +8,7 @@ from uuid import UUID
8
8
  import matplotlib
9
9
  import numpy as np
10
10
 
11
- from picopyn import Client
11
+ from picopyn.asynchronous import Client
12
12
 
13
13
  matplotlib.use("Agg")
14
14
  import os
@@ -0,0 +1,9 @@
1
+ # Async
2
+
3
+ ::: picopyn.asynchronous
4
+ options:
5
+ filters:
6
+ - "!^_"
7
+ - "_fill_pool_from_discovery"
8
+ - "_fill_pool_from_bootstrap_dsns"
9
+
@@ -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.
@@ -3,7 +3,7 @@
3
3
  ::: picopyn.explain
4
4
  options:
5
5
  members: false
6
- show_root_heading: false
6
+ show_root_heading: true
7
7
  show_source: false
8
8
 
9
9
  ::: picopyn.explain.ExplainPlan
@@ -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
@@ -0,0 +1,9 @@
1
+ # Topology
2
+
3
+ ::: picopyn.topology
4
+ options:
5
+ show_root_heading: true
6
+ show_source: true
7
+ filters:
8
+ - "!^_"
9
+
@@ -37,14 +37,11 @@ logging.getLogger("picopyn.asynchronous.pool").setLevel(logging.DEBUG)
37
37
 
38
38
  import logging
39
39
 
40
- from .asynchronous import Client, Connection, ExplainPlan, ExplainRawPlan, Pool
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 (if enabled),
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.conn.is_closed():
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.dsn, **self._connect_kwargs)
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}"