wherobots-python-dbapi 0.25.2__tar.gz → 0.25.4__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 (17) hide show
  1. {wherobots_python_dbapi-0.25.2 → wherobots_python_dbapi-0.25.4}/PKG-INFO +41 -3
  2. {wherobots_python_dbapi-0.25.2 → wherobots_python_dbapi-0.25.4}/README.md +38 -0
  3. {wherobots_python_dbapi-0.25.2 → wherobots_python_dbapi-0.25.4}/pyproject.toml +3 -3
  4. {wherobots_python_dbapi-0.25.2 → wherobots_python_dbapi-0.25.4}/wherobots/db/cursor.py +54 -1
  5. {wherobots_python_dbapi-0.25.2 → wherobots_python_dbapi-0.25.4}/.gitignore +0 -0
  6. {wherobots_python_dbapi-0.25.2 → wherobots_python_dbapi-0.25.4}/LICENSE +0 -0
  7. {wherobots_python_dbapi-0.25.2 → wherobots_python_dbapi-0.25.4}/wherobots/__init__.py +0 -0
  8. {wherobots_python_dbapi-0.25.2 → wherobots_python_dbapi-0.25.4}/wherobots/db/__init__.py +0 -0
  9. {wherobots_python_dbapi-0.25.2 → wherobots_python_dbapi-0.25.4}/wherobots/db/connection.py +0 -0
  10. {wherobots_python_dbapi-0.25.2 → wherobots_python_dbapi-0.25.4}/wherobots/db/constants.py +0 -0
  11. {wherobots_python_dbapi-0.25.2 → wherobots_python_dbapi-0.25.4}/wherobots/db/driver.py +0 -0
  12. {wherobots_python_dbapi-0.25.2 → wherobots_python_dbapi-0.25.4}/wherobots/db/errors.py +0 -0
  13. {wherobots_python_dbapi-0.25.2 → wherobots_python_dbapi-0.25.4}/wherobots/db/models.py +0 -0
  14. {wherobots_python_dbapi-0.25.2 → wherobots_python_dbapi-0.25.4}/wherobots/db/region.py +0 -0
  15. {wherobots_python_dbapi-0.25.2 → wherobots_python_dbapi-0.25.4}/wherobots/db/runtime.py +0 -0
  16. {wherobots_python_dbapi-0.25.2 → wherobots_python_dbapi-0.25.4}/wherobots/db/session_type.py +0 -0
  17. {wherobots_python_dbapi-0.25.2 → wherobots_python_dbapi-0.25.4}/wherobots/db/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wherobots-python-dbapi
3
- Version: 0.25.2
3
+ Version: 0.25.4
4
4
  Summary: Python DB-API driver for Wherobots DB
5
5
  Project-URL: Homepage, https://github.com/wherobots/wherobots-python-dbapi-driver
6
6
  Project-URL: Tracker, https://github.com/wherobots/wherobots-python-dbapi-driver/issues
@@ -13,10 +13,10 @@ Requires-Dist: packaging
13
13
  Requires-Dist: pandas
14
14
  Requires-Dist: pandas-stubs>=2.0.3.230814
15
15
  Requires-Dist: pyarrow>=14.0.2
16
- Requires-Dist: requests>=2.32.3
16
+ Requires-Dist: requests>=2.31.0
17
17
  Requires-Dist: strenum<0.5,>=0.4.15
18
18
  Requires-Dist: tenacity>=8.2.3
19
- Requires-Dist: types-requests>=2.32.0.20241016
19
+ Requires-Dist: types-requests>=2.31.0
20
20
  Requires-Dist: websockets>=13.0
21
21
  Provides-Extra: test
22
22
  Requires-Dist: pytest>=8.0.2; extra == 'test'
@@ -28,6 +28,44 @@ Python DB-API implementation for Wherobots DB. This package implements a
28
28
  PEP-0249 compatible driver to programmatically connect to a Wherobots DB
29
29
  runtime and execute Spatial SQL queries.
30
30
 
31
+ ## PEP 249 DB-API 2.0 Compliance
32
+
33
+ This driver implements the [PEP 249](https://peps.python.org/pep-0249/)
34
+ Python Database API Specification v2.0 and exposes the following
35
+ module-level globals:
36
+
37
+ | Global | Value | Meaning |
38
+ |---|---|---|
39
+ | `apilevel` | `"2.0"` | Supports DB-API 2.0 |
40
+ | `threadsafety` | `1` | Threads may share the module, but not connections |
41
+ | `paramstyle` | `"pyformat"` | Uses `%(name)s` named parameter markers |
42
+
43
+ ### Parameterized queries
44
+
45
+ Use `%(name)s` markers in your SQL and pass a dictionary of parameter
46
+ values. The driver automatically quotes and escapes values based on
47
+ their Python type (strings are single-quoted, `None` becomes `NULL`,
48
+ booleans become `TRUE`/`FALSE`, and numeric types are passed through
49
+ unquoted):
50
+
51
+ ```python
52
+ curr.execute(
53
+ "SELECT * FROM places WHERE id = %(id)s AND category = %(cat)s",
54
+ parameters={"id": 42, "cat": "restaurant"},
55
+ )
56
+ # Produces: ... WHERE id = 42 AND category = 'restaurant'
57
+ ```
58
+
59
+ Literal `%` characters in SQL (e.g. `LIKE` wildcards) do not need
60
+ escaping and work alongside parameters:
61
+
62
+ ```python
63
+ curr.execute(
64
+ "SELECT * FROM places WHERE name LIKE '%coffee%' AND city = %(city)s",
65
+ parameters={"city": "Seattle"},
66
+ )
67
+ ```
68
+
31
69
  ## Installation
32
70
 
33
71
  To add this library as a dependency in your Python project, use `uv add`
@@ -4,6 +4,44 @@ Python DB-API implementation for Wherobots DB. This package implements a
4
4
  PEP-0249 compatible driver to programmatically connect to a Wherobots DB
5
5
  runtime and execute Spatial SQL queries.
6
6
 
7
+ ## PEP 249 DB-API 2.0 Compliance
8
+
9
+ This driver implements the [PEP 249](https://peps.python.org/pep-0249/)
10
+ Python Database API Specification v2.0 and exposes the following
11
+ module-level globals:
12
+
13
+ | Global | Value | Meaning |
14
+ |---|---|---|
15
+ | `apilevel` | `"2.0"` | Supports DB-API 2.0 |
16
+ | `threadsafety` | `1` | Threads may share the module, but not connections |
17
+ | `paramstyle` | `"pyformat"` | Uses `%(name)s` named parameter markers |
18
+
19
+ ### Parameterized queries
20
+
21
+ Use `%(name)s` markers in your SQL and pass a dictionary of parameter
22
+ values. The driver automatically quotes and escapes values based on
23
+ their Python type (strings are single-quoted, `None` becomes `NULL`,
24
+ booleans become `TRUE`/`FALSE`, and numeric types are passed through
25
+ unquoted):
26
+
27
+ ```python
28
+ curr.execute(
29
+ "SELECT * FROM places WHERE id = %(id)s AND category = %(cat)s",
30
+ parameters={"id": 42, "cat": "restaurant"},
31
+ )
32
+ # Produces: ... WHERE id = 42 AND category = 'restaurant'
33
+ ```
34
+
35
+ Literal `%` characters in SQL (e.g. `LIKE` wildcards) do not need
36
+ escaping and work alongside parameters:
37
+
38
+ ```python
39
+ curr.execute(
40
+ "SELECT * FROM places WHERE name LIKE '%coffee%' AND city = %(city)s",
41
+ parameters={"city": "Seattle"},
42
+ )
43
+ ```
44
+
7
45
  ## Installation
8
46
 
9
47
  To add this library as a dependency in your Python project, use `uv add`
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "wherobots-python-dbapi"
3
- version = "0.25.2"
3
+ version = "0.25.4"
4
4
  description = "Python DB-API driver for Wherobots DB"
5
5
  authors = [{ name = "Maxime Petazzoni", email = "max@wherobots.com" }]
6
6
  requires-python = ">=3.10, <4"
@@ -8,14 +8,14 @@ readme = "README.md"
8
8
  license = "Apache-2.0"
9
9
  dependencies = [
10
10
  "packaging",
11
- "requests>=2.32.3",
11
+ "requests>=2.31.0",
12
12
  "websockets>=13.0",
13
13
  "tenacity>=8.2.3",
14
14
  "cbor2>=5.6.3",
15
15
  "StrEnum>=0.4.15,<0.5",
16
16
  "pyarrow>=14.0.2",
17
17
  "pandas",
18
- "types-requests>=2.32.0.20241016",
18
+ "types-requests>=2.31.0",
19
19
  "pandas-stubs>=2.0.3.230814",
20
20
  ]
21
21
 
@@ -1,9 +1,60 @@
1
+ import math
1
2
  import queue
3
+ import re
2
4
  from typing import Any, List, Tuple, Dict
3
5
 
4
6
  from .errors import ProgrammingError
5
7
  from .models import ExecutionResult, Store, StoreResult
6
8
 
9
+ # Matches pyformat parameter markers: %(name)s
10
+ _PYFORMAT_RE = re.compile(r"%\(([^)]+)\)s")
11
+
12
+
13
+ def _quote_value(value: Any) -> str:
14
+ """Convert a Python value to a SQL literal string.
15
+
16
+ Handles quoting and escaping so that the interpolated SQL is syntactically
17
+ correct and safe from trivial injection.
18
+ """
19
+ if value is None:
20
+ return "NULL"
21
+ # bool must be checked before int because bool is a subclass of int
22
+ if isinstance(value, bool):
23
+ return "TRUE" if value else "FALSE"
24
+ if isinstance(value, (int, float)):
25
+ if isinstance(value, float) and (math.isnan(value) or math.isinf(value)):
26
+ raise ProgrammingError(
27
+ f"Cannot convert float value {value!r} to SQL literal"
28
+ )
29
+ return str(value)
30
+ if isinstance(value, bytes):
31
+ return "X'" + value.hex() + "'"
32
+ # Everything else (str, date, datetime, etc.) is treated as a string literal
33
+ return "'" + str(value).replace("'", "''") + "'"
34
+
35
+
36
+ def _substitute_parameters(operation: str, parameters: Dict[str, Any] | None) -> str:
37
+ """Substitute pyformat parameters into a SQL operation string.
38
+
39
+ Uses regex to match only %(name)s tokens, leaving literal percent
40
+ characters (e.g. SQL LIKE wildcards) untouched. Values are quoted
41
+ according to their Python type so the resulting SQL is syntactically
42
+ correct (see :func:`_quote_value`).
43
+ """
44
+ if not parameters:
45
+ return operation
46
+
47
+ def replacer(match: re.Match) -> str:
48
+ key = match.group(1)
49
+ if key not in parameters:
50
+ raise ProgrammingError(
51
+ f"Parameter '{key}' not found in provided parameters"
52
+ )
53
+ return _quote_value(parameters[key])
54
+
55
+ return _PYFORMAT_RE.sub(replacer, operation)
56
+
57
+
7
58
  _TYPE_MAP = {
8
59
  "object": "STRING",
9
60
  "int64": "NUMBER",
@@ -99,7 +150,9 @@ class Cursor:
99
150
  self.__description = None
100
151
 
101
152
  self.__current_execution_id = self.__exec_fn(
102
- operation % (parameters or {}), self.__on_execution_result, store
153
+ _substitute_parameters(operation, parameters),
154
+ self.__on_execution_result,
155
+ store,
103
156
  )
104
157
 
105
158
  def get_store_result(self) -> StoreResult | None: