python-datastore-sqlalchemy 0.0.1__tar.gz → 9.0.3__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.

Potentially problematic release.


This version of python-datastore-sqlalchemy might be problematic. Click here for more details.

Files changed (33) hide show
  1. python_datastore_sqlalchemy-9.0.3/PKG-INFO +95 -0
  2. python_datastore_sqlalchemy-9.0.3/README.md +65 -0
  3. python_datastore_sqlalchemy-9.0.3/pyproject.toml +88 -0
  4. python_datastore_sqlalchemy-9.0.3/python_datastore_sqlalchemy.egg-info/PKG-INFO +95 -0
  5. {python_datastore_sqlalchemy-0.0.1 → python_datastore_sqlalchemy-9.0.3}/python_datastore_sqlalchemy.egg-info/SOURCES.txt +0 -1
  6. python_datastore_sqlalchemy-9.0.3/python_datastore_sqlalchemy.egg-info/requires.txt +8 -0
  7. python_datastore_sqlalchemy-9.0.3/setup.py +22 -0
  8. {python_datastore_sqlalchemy-0.0.1 → python_datastore_sqlalchemy-9.0.3}/sqlalchemy_datastore/__init__.py +2 -2
  9. {python_datastore_sqlalchemy-0.0.1 → python_datastore_sqlalchemy-9.0.3}/sqlalchemy_datastore/_helpers.py +11 -9
  10. {python_datastore_sqlalchemy-0.0.1 → python_datastore_sqlalchemy-9.0.3}/sqlalchemy_datastore/base.py +28 -60
  11. {python_datastore_sqlalchemy-0.0.1 → python_datastore_sqlalchemy-9.0.3}/sqlalchemy_datastore/datastore_dbapi.py +20 -5
  12. {python_datastore_sqlalchemy-0.0.1 → python_datastore_sqlalchemy-9.0.3}/sqlalchemy_datastore/parse_url.py +8 -0
  13. {python_datastore_sqlalchemy-0.0.1 → python_datastore_sqlalchemy-9.0.3}/tests/test_unit_base.py +79 -1
  14. python_datastore_sqlalchemy-0.0.1/PKG-INFO +0 -65
  15. python_datastore_sqlalchemy-0.0.1/README.md +0 -28
  16. python_datastore_sqlalchemy-0.0.1/pyproject.toml +0 -21
  17. python_datastore_sqlalchemy-0.0.1/python_datastore_sqlalchemy.egg-info/PKG-INFO +0 -65
  18. python_datastore_sqlalchemy-0.0.1/python_datastore_sqlalchemy.egg-info/not-zip-safe +0 -1
  19. python_datastore_sqlalchemy-0.0.1/python_datastore_sqlalchemy.egg-info/requires.txt +0 -1
  20. python_datastore_sqlalchemy-0.0.1/setup.py +0 -70
  21. {python_datastore_sqlalchemy-0.0.1 → python_datastore_sqlalchemy-9.0.3}/LICENSE +0 -0
  22. {python_datastore_sqlalchemy-0.0.1 → python_datastore_sqlalchemy-9.0.3}/python_datastore_sqlalchemy.egg-info/dependency_links.txt +0 -0
  23. {python_datastore_sqlalchemy-0.0.1 → python_datastore_sqlalchemy-9.0.3}/python_datastore_sqlalchemy.egg-info/entry_points.txt +0 -0
  24. {python_datastore_sqlalchemy-0.0.1 → python_datastore_sqlalchemy-9.0.3}/python_datastore_sqlalchemy.egg-info/top_level.txt +0 -0
  25. {python_datastore_sqlalchemy-0.0.1 → python_datastore_sqlalchemy-9.0.3}/setup.cfg +0 -0
  26. {python_datastore_sqlalchemy-0.0.1 → python_datastore_sqlalchemy-9.0.3}/sqlalchemy_datastore/_types.py +0 -0
  27. {python_datastore_sqlalchemy-0.0.1 → python_datastore_sqlalchemy-9.0.3}/tests/test_datastore.py +0 -0
  28. {python_datastore_sqlalchemy-0.0.1 → python_datastore_sqlalchemy-9.0.3}/tests/test_gql.py +0 -0
  29. {python_datastore_sqlalchemy-0.0.1 → python_datastore_sqlalchemy-9.0.3}/tests/test_integration_queries.py +0 -0
  30. {python_datastore_sqlalchemy-0.0.1 → python_datastore_sqlalchemy-9.0.3}/tests/test_orm.py +0 -0
  31. {python_datastore_sqlalchemy-0.0.1 → python_datastore_sqlalchemy-9.0.3}/tests/test_unit_dbapi.py +0 -0
  32. {python_datastore_sqlalchemy-0.0.1 → python_datastore_sqlalchemy-9.0.3}/tests/test_unit_helpers.py +0 -0
  33. {python_datastore_sqlalchemy-0.0.1 → python_datastore_sqlalchemy-9.0.3}/tests/test_unit_types.py +0 -0
@@ -0,0 +1,95 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-datastore-sqlalchemy
3
+ Version: 9.0.3
4
+ Summary: SQLAlchemy dialect for google cloud datastore
5
+ Author-email: "HY Chang(splasky)" <hychang.1997.tw@gmail.com>
6
+ License: MIT
7
+ Project-URL: Documentation, https://github.com/splasky/python-datastore-sqlalchemy/wiki
8
+ Project-URL: Source, https://github.com/splasky/python-datastore-sqlalchemy
9
+ Project-URL: Tracker, https://github.com/splasky/python-datastore-sqlalchemy/issues
10
+ Keywords: SQLAlchemy,GCP,Datastore
11
+ Classifier: Development Status :: 2 - Pre-Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: Implementation :: CPython
16
+ Classifier: Topic :: Database :: Front-Ends
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: sqlalchemy<3.0.0,>=1.4.16
22
+ Requires-Dist: google-cloud-datastore>=2.21.0
23
+ Requires-Dist: google-cloud-firestore>=2.21.0
24
+ Requires-Dist: google-cloud-bigquery>=3.35.0
25
+ Requires-Dist: google-auth>=2.40.0
26
+ Requires-Dist: sqlglot>=28.0.0
27
+ Requires-Dist: pandas>=2.0.0
28
+ Requires-Dist: requests
29
+ Dynamic: license-file
30
+
31
+ SQLAlchemy dialect for Google Cloud Datastore in Firestore mode
32
+ ========================
33
+
34
+ ## Installation
35
+
36
+ Install from PyPI:
37
+ ```bash
38
+ pip install python-datastore-sqlalchemy
39
+ ```
40
+
41
+ Install from a local checkout:
42
+ ```bash
43
+ uv pip install -e .
44
+ ```
45
+
46
+ Runtime dependencies are declared in `pyproject.toml`. `requirements.txt` is not used by this project.
47
+
48
+ ## Usage
49
+
50
+ ```python
51
+ from sqlalchemy import create_engine, text
52
+
53
+ engine = create_engine(
54
+ "datastore://my-project/?database=my-db",
55
+ credentials="path/to/credentials.json",
56
+ )
57
+
58
+ with engine.connect() as conn:
59
+ result = conn.execute(text("SELECT * FROM test_table"))
60
+ print(result.fetchall())
61
+ ```
62
+
63
+ ## Preview
64
+
65
+ <img src="assets/pie.png">
66
+
67
+ > [!WARNING]
68
+ > Please note: Not all GQL and SQL syntax has been fully tested. Should you encounter any bugs, please post the relevant query and open an issue on GitHub.
69
+
70
+ ## How to contribute
71
+
72
+ Feel free to open issues and pull requests on GitHub.
73
+
74
+ ## Development
75
+
76
+ This project supports Python 3.10 and newer. It uses a uv-managed virtual environment by default. The lockfile is tracked so local development and CI use the same dependency resolution.
77
+
78
+ Install the development environment:
79
+ ```bash
80
+ uv sync --locked
81
+ ```
82
+
83
+ Run the test suite:
84
+ ```bash
85
+ uv run pytest
86
+ ```
87
+
88
+ The Datastore-backed tests require the Google Cloud SDK with the `beta` and `cloud-datastore-emulator` components installed. If `gcloud` is not on `PATH`, point the test fixture at it explicitly:
89
+ ```bash
90
+ GCLOUD_PATH=/path/to/google-cloud-sdk/bin/gcloud uv run pytest
91
+ ```
92
+
93
+ ## Development Notes
94
+
95
+ - [Develop a SQLAlchemy and its dialects](https://hackmd.io/lsBW5GCVR82SORyWZ1cssA?view)
@@ -0,0 +1,65 @@
1
+ SQLAlchemy dialect for Google Cloud Datastore in Firestore mode
2
+ ========================
3
+
4
+ ## Installation
5
+
6
+ Install from PyPI:
7
+ ```bash
8
+ pip install python-datastore-sqlalchemy
9
+ ```
10
+
11
+ Install from a local checkout:
12
+ ```bash
13
+ uv pip install -e .
14
+ ```
15
+
16
+ Runtime dependencies are declared in `pyproject.toml`. `requirements.txt` is not used by this project.
17
+
18
+ ## Usage
19
+
20
+ ```python
21
+ from sqlalchemy import create_engine, text
22
+
23
+ engine = create_engine(
24
+ "datastore://my-project/?database=my-db",
25
+ credentials="path/to/credentials.json",
26
+ )
27
+
28
+ with engine.connect() as conn:
29
+ result = conn.execute(text("SELECT * FROM test_table"))
30
+ print(result.fetchall())
31
+ ```
32
+
33
+ ## Preview
34
+
35
+ <img src="assets/pie.png">
36
+
37
+ > [!WARNING]
38
+ > Please note: Not all GQL and SQL syntax has been fully tested. Should you encounter any bugs, please post the relevant query and open an issue on GitHub.
39
+
40
+ ## How to contribute
41
+
42
+ Feel free to open issues and pull requests on GitHub.
43
+
44
+ ## Development
45
+
46
+ This project supports Python 3.10 and newer. It uses a uv-managed virtual environment by default. The lockfile is tracked so local development and CI use the same dependency resolution.
47
+
48
+ Install the development environment:
49
+ ```bash
50
+ uv sync --locked
51
+ ```
52
+
53
+ Run the test suite:
54
+ ```bash
55
+ uv run pytest
56
+ ```
57
+
58
+ The Datastore-backed tests require the Google Cloud SDK with the `beta` and `cloud-datastore-emulator` components installed. If `gcloud` is not on `PATH`, point the test fixture at it explicitly:
59
+ ```bash
60
+ GCLOUD_PATH=/path/to/google-cloud-sdk/bin/gcloud uv run pytest
61
+ ```
62
+
63
+ ## Development Notes
64
+
65
+ - [Develop a SQLAlchemy and its dialects](https://hackmd.io/lsBW5GCVR82SORyWZ1cssA?view)
@@ -0,0 +1,88 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "python-datastore-sqlalchemy"
7
+ version = "9.0.3"
8
+ description = "SQLAlchemy dialect for google cloud datastore"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [
13
+ { name = "HY Chang(splasky)", email = "hychang.1997.tw@gmail.com" },
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 2 - Pre-Alpha",
17
+ "Intended Audience :: Developers",
18
+ "Programming Language :: Python",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: Implementation :: CPython",
21
+ "Topic :: Database :: Front-Ends",
22
+ "Operating System :: POSIX :: Linux",
23
+ ]
24
+ keywords = ["SQLAlchemy", "GCP", "Datastore"]
25
+ dependencies = [
26
+ "sqlalchemy>=1.4.16,<3.0.0",
27
+ "google-cloud-datastore>=2.21.0",
28
+ "google-cloud-firestore>=2.21.0",
29
+ "google-cloud-bigquery>=3.35.0",
30
+ "google-auth>=2.40.0",
31
+ "sqlglot>=28.0.0",
32
+ "pandas>=2.0.0",
33
+ "requests",
34
+ ]
35
+
36
+ [project.entry-points."sqlalchemy.dialects"]
37
+ datastore = "sqlalchemy_datastore:CloudDatastoreDialect"
38
+
39
+ [project.urls]
40
+ Documentation = "https://github.com/splasky/python-datastore-sqlalchemy/wiki"
41
+ Source = "https://github.com/splasky/python-datastore-sqlalchemy"
42
+ Tracker = "https://github.com/splasky/python-datastore-sqlalchemy/issues"
43
+
44
+ [dependency-groups]
45
+ dev = [
46
+ "coverage==7.9.2",
47
+ "flake8==7.3.0",
48
+ "mypy",
49
+ "pytest==9.0.3",
50
+ "pytest-cov==6.2.1",
51
+ "pytest-html==4.1.1",
52
+ "pytest-mypy==1.0.1",
53
+ "pytest-ruff==0.5",
54
+ "ruff",
55
+ ]
56
+
57
+
58
+
59
+ [tool.setuptools.packages.find]
60
+ include = ["sqlalchemy_datastore*"]
61
+
62
+ [tool.pytest.ini_options]
63
+ addopts = "--ruff --mypy --cov --cov-report=html"
64
+ pythonpath = ["."]
65
+ testpaths = ["tests"]
66
+ python_files = ["test_*.py"]
67
+
68
+ [tool.ruff]
69
+ line-length = 120
70
+
71
+ [tool.ruff.lint]
72
+ select = ["E", "F", "W", "I", "N", "B", "C", "G", "S"]
73
+ ignore = [
74
+ "E501", # Line too long - allow longer lines for SQL strings in tests
75
+ "S608", # SQL injection - intentional for test cases
76
+ "B007", # Loop variable not used - common in test assertions
77
+ ]
78
+
79
+ [tool.ruff.lint.per-file-ignores]
80
+ "tests/*" = ["S608", "E501", "S101", "B007", "S603"]
81
+
82
+ [tool.mypy]
83
+ ignore_missing_imports = true
84
+ disable_error_code = ["var-annotated", "import-untyped"]
85
+
86
+ [[tool.mypy.overrides]]
87
+ module = ["tests.*"]
88
+ ignore_errors = true
@@ -0,0 +1,95 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-datastore-sqlalchemy
3
+ Version: 9.0.3
4
+ Summary: SQLAlchemy dialect for google cloud datastore
5
+ Author-email: "HY Chang(splasky)" <hychang.1997.tw@gmail.com>
6
+ License: MIT
7
+ Project-URL: Documentation, https://github.com/splasky/python-datastore-sqlalchemy/wiki
8
+ Project-URL: Source, https://github.com/splasky/python-datastore-sqlalchemy
9
+ Project-URL: Tracker, https://github.com/splasky/python-datastore-sqlalchemy/issues
10
+ Keywords: SQLAlchemy,GCP,Datastore
11
+ Classifier: Development Status :: 2 - Pre-Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: Implementation :: CPython
16
+ Classifier: Topic :: Database :: Front-Ends
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: sqlalchemy<3.0.0,>=1.4.16
22
+ Requires-Dist: google-cloud-datastore>=2.21.0
23
+ Requires-Dist: google-cloud-firestore>=2.21.0
24
+ Requires-Dist: google-cloud-bigquery>=3.35.0
25
+ Requires-Dist: google-auth>=2.40.0
26
+ Requires-Dist: sqlglot>=28.0.0
27
+ Requires-Dist: pandas>=2.0.0
28
+ Requires-Dist: requests
29
+ Dynamic: license-file
30
+
31
+ SQLAlchemy dialect for Google Cloud Datastore in Firestore mode
32
+ ========================
33
+
34
+ ## Installation
35
+
36
+ Install from PyPI:
37
+ ```bash
38
+ pip install python-datastore-sqlalchemy
39
+ ```
40
+
41
+ Install from a local checkout:
42
+ ```bash
43
+ uv pip install -e .
44
+ ```
45
+
46
+ Runtime dependencies are declared in `pyproject.toml`. `requirements.txt` is not used by this project.
47
+
48
+ ## Usage
49
+
50
+ ```python
51
+ from sqlalchemy import create_engine, text
52
+
53
+ engine = create_engine(
54
+ "datastore://my-project/?database=my-db",
55
+ credentials="path/to/credentials.json",
56
+ )
57
+
58
+ with engine.connect() as conn:
59
+ result = conn.execute(text("SELECT * FROM test_table"))
60
+ print(result.fetchall())
61
+ ```
62
+
63
+ ## Preview
64
+
65
+ <img src="assets/pie.png">
66
+
67
+ > [!WARNING]
68
+ > Please note: Not all GQL and SQL syntax has been fully tested. Should you encounter any bugs, please post the relevant query and open an issue on GitHub.
69
+
70
+ ## How to contribute
71
+
72
+ Feel free to open issues and pull requests on GitHub.
73
+
74
+ ## Development
75
+
76
+ This project supports Python 3.10 and newer. It uses a uv-managed virtual environment by default. The lockfile is tracked so local development and CI use the same dependency resolution.
77
+
78
+ Install the development environment:
79
+ ```bash
80
+ uv sync --locked
81
+ ```
82
+
83
+ Run the test suite:
84
+ ```bash
85
+ uv run pytest
86
+ ```
87
+
88
+ The Datastore-backed tests require the Google Cloud SDK with the `beta` and `cloud-datastore-emulator` components installed. If `gcloud` is not on `PATH`, point the test fixture at it explicitly:
89
+ ```bash
90
+ GCLOUD_PATH=/path/to/google-cloud-sdk/bin/gcloud uv run pytest
91
+ ```
92
+
93
+ ## Development Notes
94
+
95
+ - [Develop a SQLAlchemy and its dialects](https://hackmd.io/lsBW5GCVR82SORyWZ1cssA?view)
@@ -6,7 +6,6 @@ python_datastore_sqlalchemy.egg-info/PKG-INFO
6
6
  python_datastore_sqlalchemy.egg-info/SOURCES.txt
7
7
  python_datastore_sqlalchemy.egg-info/dependency_links.txt
8
8
  python_datastore_sqlalchemy.egg-info/entry_points.txt
9
- python_datastore_sqlalchemy.egg-info/not-zip-safe
10
9
  python_datastore_sqlalchemy.egg-info/requires.txt
11
10
  python_datastore_sqlalchemy.egg-info/top_level.txt
12
11
  sqlalchemy_datastore/__init__.py
@@ -0,0 +1,8 @@
1
+ sqlalchemy<3.0.0,>=1.4.16
2
+ google-cloud-datastore>=2.21.0
3
+ google-cloud-firestore>=2.21.0
4
+ google-cloud-bigquery>=3.35.0
5
+ google-auth>=2.40.0
6
+ sqlglot>=28.0.0
7
+ pandas>=2.0.0
8
+ requests
@@ -0,0 +1,22 @@
1
+ # Copyright (c) 2025 hychang <hychang.1997.tw@gmail.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ # this software and associated documentation files (the "Software"), to deal in
5
+ # the Software without restriction, including without limitation the rights to
6
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ # the Software, and to permit persons to whom the Software is furnished to do so,
8
+ # subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all
11
+ # copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
+
20
+ from setuptools import setup
21
+
22
+ setup()
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2025 hychang <hychang.1997.tw@gmail.com>
1
+ # Copyright (c) 2025 hychang <hychang.1997.tw@gmail.com>
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy of
4
4
  # this software and associated documentation files (the "Software"), to deal in
@@ -19,7 +19,7 @@
19
19
 
20
20
  from .base import CloudDatastoreDialect
21
21
 
22
- __version__ = "0.0.1"
22
+ __version__ = "0.0.3"
23
23
 
24
24
  __all__ = [
25
25
  "__version__",
@@ -60,21 +60,23 @@ def create_datastore_client(
60
60
  user_agent: Optional[client_info.ClientInfo] = None,
61
61
  database: Optional[str] = None
62
62
  ) -> Tuple[datastore.Client, Optional[Credentials]]:
63
- """Construct a BigQuery client object.
63
+ """Construct a Datastore client object.
64
64
 
65
65
  Args:
66
- credentials_info Optional[dict]:
67
- credentials_path Optional[str]:
68
- credentials_base64 Optional[str]:
69
- location (Optional[str]):
70
- Default location for jobs / datasets / tables.
66
+ credentials_info (Optional[dict]):
67
+ Service account info dictionary.
68
+ credentials_path (Optional[str]):
69
+ Path to a service account JSON key file.
70
+ credentials_base64 (Optional[str]):
71
+ Base64-encoded service account JSON key.
71
72
  project_id (Optional[str]):
72
73
  Project ID for the project which the client acts on behalf of.
73
74
  user_agent (Optional[google.api_core.client_info.ClientInfo]):
74
75
  The client info used to send a user-agent string along with API
75
- requests. If ``None``, then default info will be used. Generally,
76
- you only need to set this if you're developing your own library
77
- or partner tool.
76
+ requests. If ``None``, then default info will be used.
77
+ database (Optional[str]):
78
+ Datastore database ID. ``None`` or ``"(default)"`` uses the
79
+ default database.
78
80
  """
79
81
 
80
82
  default_project = None
@@ -17,20 +17,15 @@
17
17
  # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
18
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
19
  import logging
20
- from concurrent import futures
21
20
  from typing import Any, List, Optional
22
21
 
23
22
  from google.cloud import firestore_admin_v1
24
- from google.cloud.firestore_admin_v1.types import Database
25
23
  from google.oauth2 import service_account
26
24
  from sqlalchemy import exc
27
25
  from sqlalchemy.engine import Connection, default
28
26
  from sqlalchemy.engine.interfaces import (
29
- # DBAPICursor,
30
- # _DBAPISingleExecuteParams,
31
27
  ExecutionContext,
32
28
  )
33
- from sqlalchemy.sql import Select
34
29
 
35
30
  from . import _types, datastore_dbapi
36
31
  from ._helpers import create_datastore_client
@@ -88,6 +83,7 @@ class CloudDatastoreDialect(default.DefaultDialect):
88
83
  self.location = location
89
84
  self.identifier_preparer = self.preparer(self)
90
85
  self.dataset_id = None
86
+ self.database_id = None
91
87
  self.list_tables_page_size = list_tables_page_size
92
88
  self._client = None
93
89
 
@@ -129,6 +125,7 @@ class CloudDatastoreDialect(default.DefaultDialect):
129
125
  provided_job_config,
130
126
  list_tables_page_size,
131
127
  user_supplied_client,
128
+ database_id,
132
129
  ) = parse_url(url)
133
130
 
134
131
  self.arraysize = arraysize or self.arraysize
@@ -138,6 +135,7 @@ class CloudDatastoreDialect(default.DefaultDialect):
138
135
  self.credentials_path = credentials_path
139
136
  self.credentials_base64 = credentials_base64 or self.credentials_base64
140
137
  self.dataset_id = dataset_id
138
+ self.database_id = database_id
141
139
  self.billing_project_id = self.billing_project_id or self.project_id
142
140
 
143
141
  if user_supplied_client:
@@ -148,7 +146,7 @@ class CloudDatastoreDialect(default.DefaultDialect):
148
146
  credentials_info=self.credentials_info,
149
147
  credentials_base64=self.credentials_base64,
150
148
  project_id=self.billing_project_id,
151
- database=None,
149
+ database=self.database_id,
152
150
  )
153
151
  self.project_id = self.project_id if self.project_id else client.project
154
152
  self.billing_project_id = (
@@ -162,10 +160,10 @@ class CloudDatastoreDialect(default.DefaultDialect):
162
160
 
163
161
  self._client = client
164
162
  self.credentials = credentials
165
- setattr(self._client, "credentials_path", self.credentials_path)
166
- setattr(self._client, "credentials_info", self.credentials_info)
167
- setattr(self._client, "credentials_base64", self.credentials_base64)
168
- setattr(self._client, "scoped_credentials", credentials)
163
+ self._client.credentials_path = self.credentials_path
164
+ self._client.credentials_info = self.credentials_info
165
+ self._client.credentials_base64 = self.credentials_base64
166
+ self._client.scoped_credentials = credentials
169
167
  return ([], {"client": client})
170
168
 
171
169
  def get_schema_names(self, connection: Connection, **kw) -> List[str]:
@@ -182,18 +180,11 @@ class CloudDatastoreDialect(default.DefaultDialect):
182
180
 
183
181
  try:
184
182
  list_database_resp = client.list_databases(parent=parent)
185
-
186
- def get_database_short_name(database: Database) -> Optional[List[str]]:
187
- full_name = database.name
188
- if full_name:
189
- return full_name.split("/")[-1]
190
- return None
191
-
192
- with futures.ThreadPoolExecutor() as executor:
193
- schemas = list(
194
- executor.map(get_database_short_name, list_database_resp.databases)
195
- )
196
-
183
+ schemas = [
184
+ full_name.split("/")[-1]
185
+ for db in list_database_resp.databases
186
+ if (full_name := db.name)
187
+ ]
197
188
  return schemas
198
189
  except Exception as e:
199
190
  logging.error(e)
@@ -206,57 +197,34 @@ class CloudDatastoreDialect(default.DefaultDialect):
206
197
  query = client.query(kind="__kind__")
207
198
  kinds = list(query.fetch())
208
199
 
209
- def get_kind_name(kind):
210
- return (
211
- name
212
- if (name := getattr(getattr(kind, "key", None), "name", None))
213
- is not None
214
- and isinstance(name, str)
215
- and not name.startswith("__")
216
- else None
217
- )
218
-
219
- with futures.ThreadPoolExecutor() as executor:
220
- result = list(executor.map(get_kind_name, kinds))
221
-
222
- return [t for t in result if t is not None]
200
+ return [
201
+ name
202
+ for kind in kinds
203
+ if (name := getattr(getattr(kind, "key", None), "name", None))
204
+ is not None
205
+ and isinstance(name, str)
206
+ and not name.startswith("__")
207
+ ]
223
208
 
224
209
  def get_columns(
225
210
  self, connection: Connection, table_name: str, schema: str | None = None, **kw
226
211
  ):
227
- """Retrieve column information from the database with optimized parallel processing."""
212
+ """Retrieve column information from the database."""
228
213
  client = self._client
229
214
  query = client.query(kind="__Stat_PropertyType_PropertyName_Kind__")
230
215
  query.add_filter("kind_name", "=", table_name)
231
216
  properties = list(query.fetch())
232
217
 
233
- def process_property(property):
234
- return {
235
- "name": property["property_name"],
236
- "type": _types._property_type[property["property_type"]],
218
+ return [
219
+ {
220
+ "name": prop["property_name"],
221
+ "type": _types._property_type[prop["property_type"]],
237
222
  "nullable": True,
238
223
  "comment": "",
239
224
  "default": None,
240
225
  }
241
-
242
- with futures.ThreadPoolExecutor() as executor:
243
- columns = list(executor.map(process_property, properties))
244
- return columns
245
-
246
- def _contains_select_subquery(self, node) -> bool:
247
- """
248
- Check the AST node contains select subquery
249
- """
250
- if isinstance(node, Select):
251
- for child in node.get_children():
252
- if isinstance(child, Select) or self._contains_select_subquery(child):
253
- return True
254
-
255
- for child in node.get_children():
256
- if isinstance(child, Select) or self._contains_select_subquery(child):
257
- return True
258
-
259
- return False
226
+ for prop in properties
227
+ ]
260
228
 
261
229
  def do_execute(
262
230
  self,
@@ -228,7 +228,7 @@ class Cursor:
228
228
 
229
229
  except Exception as e:
230
230
  logging.error(f"INSERT failed: {e}")
231
- raise ProgrammingError(f"INSERT failed: {e}")
231
+ raise ProgrammingError(f"INSERT failed: {e}") from e
232
232
 
233
233
  def _execute_update(self, statement: str, parameters=None):
234
234
  """Execute an UPDATE statement using Datastore client."""
@@ -454,6 +454,8 @@ class Cursor:
454
454
  r"\bCOUNT_UP_TO\s*\(",
455
455
  r"\bSUM\s*\(",
456
456
  r"\bAVG\s*\(",
457
+ r"\bMIN\s*\(",
458
+ r"\bMAX\s*\(",
457
459
  ]
458
460
  for pattern in agg_patterns:
459
461
  if re.search(pattern, upper):
@@ -931,9 +933,14 @@ class Cursor:
931
933
  # This is a simplified evaluator for common patterns
932
934
  try:
933
935
  return self._eval_condition(context, where_clause)
934
- except Exception:
935
- # If evaluation fails, include the row (fail open)
936
- return True
936
+ except Exception as e:
937
+ logging.warning(
938
+ "Client-side WHERE evaluation failed for clause '%s': %s. "
939
+ "Row will be excluded (fail closed).",
940
+ where_clause,
941
+ e,
942
+ )
943
+ return False
937
944
 
938
945
  def _eval_condition(self, context: Dict[str, Any], condition: str) -> bool:
939
946
  """Evaluate a single condition or compound condition."""
@@ -1566,10 +1573,12 @@ class Cursor:
1566
1573
  self.description = list(agg_fields.values())
1567
1574
 
1568
1575
  def execute_orm(
1569
- self, statement: str, parameters=None, tokens: List[tokens.Token] = []
1576
+ self, statement: str, parameters=None, tokens: Optional[List[tokens.Token]] = None
1570
1577
  ):
1571
1578
  if parameters is None:
1572
1579
  parameters = {}
1580
+ if tokens is None:
1581
+ tokens = []
1573
1582
 
1574
1583
  logging.debug(
1575
1584
  f"[DataStore DBAPI] Executing ORM query: {statement} with parameters: {parameters}"
@@ -1812,11 +1821,15 @@ class Cursor:
1812
1821
  def fetchall(self):
1813
1822
  if self._closed:
1814
1823
  raise Error("Cursor is closed.")
1824
+ if self._query_rows is None:
1825
+ raise ProgrammingError("No query has been executed.")
1815
1826
  return list(self._query_rows)
1816
1827
 
1817
1828
  def fetchmany(self, size=None):
1818
1829
  if self._closed:
1819
1830
  raise Error("Cursor is closed.")
1831
+ if self._query_rows is None:
1832
+ raise ProgrammingError("No query has been executed.")
1820
1833
  if size is None:
1821
1834
  size = self.arraysize or 1
1822
1835
  results = []
@@ -1830,6 +1843,8 @@ class Cursor:
1830
1843
  def fetchone(self):
1831
1844
  if self._closed:
1832
1845
  raise Error("Cursor is closed.")
1846
+ if self._query_rows is None:
1847
+ raise ProgrammingError("No query has been executed.")
1833
1848
  try:
1834
1849
  return next(self._query_rows)
1835
1850
  except StopIteration:
@@ -72,6 +72,11 @@ def parse_url(url): # noqa: C901
72
72
  credentials_base64 = None
73
73
  list_tables_page_size = None
74
74
  user_supplied_client = False
75
+ database = None
76
+
77
+ # database
78
+ if "database" in query:
79
+ database = query.pop("database")
75
80
 
76
81
  # location
77
82
  if "location" in query:
@@ -122,6 +127,7 @@ def parse_url(url): # noqa: C901
122
127
  QueryJobConfig(),
123
128
  list_tables_page_size,
124
129
  user_supplied_client,
130
+ database,
125
131
  )
126
132
  else:
127
133
  return (
@@ -134,6 +140,7 @@ def parse_url(url): # noqa: C901
134
140
  None,
135
141
  list_tables_page_size,
136
142
  user_supplied_client,
143
+ database,
137
144
  )
138
145
 
139
146
  job_config = QueryJobConfig()
@@ -284,4 +291,5 @@ def parse_url(url): # noqa: C901
284
291
  job_config,
285
292
  list_tables_page_size,
286
293
  user_supplied_client,
294
+ database,
287
295
  )
@@ -17,9 +17,12 @@
17
17
  # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
18
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
19
  """Unit tests for base.py CloudDatastoreDialect (no emulator required)."""
20
- from unittest.mock import MagicMock
20
+ from unittest.mock import MagicMock, patch
21
+
22
+ from sqlalchemy.engine.url import make_url
21
23
 
22
24
  from sqlalchemy_datastore import CloudDatastoreDialect, datastore_dbapi
25
+ from sqlalchemy_datastore.parse_url import parse_url
23
26
 
24
27
  # ---------------------------------------------------------------------------
25
28
  # Dialect metadata
@@ -224,3 +227,78 @@ def test_get_table_names_empty():
224
227
 
225
228
  result = d.get_table_names(None)
226
229
  assert result == []
230
+
231
+
232
+ # ---------------------------------------------------------------------------
233
+ # parse_url – database parameter
234
+ # ---------------------------------------------------------------------------
235
+
236
+ def test_parse_url_extracts_database():
237
+ url = make_url("datastore://my-project/?database=my-db")
238
+ result = parse_url(url)
239
+ assert len(result) == 10
240
+ assert result[9] == "my-db"
241
+
242
+
243
+ def test_parse_url_no_database():
244
+ url = make_url("datastore://my-project/")
245
+ result = parse_url(url)
246
+ assert len(result) == 10
247
+ assert result[9] is None
248
+
249
+
250
+ def test_parse_url_database_with_location():
251
+ url = make_url("datastore://my-project/?database=my-db&location=us-east1")
252
+ result = parse_url(url)
253
+ assert result[9] == "my-db"
254
+ assert result[1] == "us-east1"
255
+
256
+
257
+ # ---------------------------------------------------------------------------
258
+ # create_connect_args – database parameter
259
+ # ---------------------------------------------------------------------------
260
+
261
+ def test_create_connect_args_passes_database():
262
+ d = CloudDatastoreDialect()
263
+ url = make_url("datastore://my-project/?database=my-db")
264
+
265
+ with patch(
266
+ "sqlalchemy_datastore.base.create_datastore_client"
267
+ ) as mock_create:
268
+ mock_client = MagicMock()
269
+ mock_client.project = "my-project"
270
+ mock_create.return_value = (mock_client, MagicMock())
271
+
272
+ d.create_connect_args(url)
273
+
274
+ mock_create.assert_called_once_with(
275
+ credentials_path=None,
276
+ credentials_info=None,
277
+ credentials_base64=None,
278
+ project_id="my-project",
279
+ database="my-db",
280
+ )
281
+ assert d.database_id == "my-db"
282
+
283
+
284
+ def test_create_connect_args_no_database():
285
+ d = CloudDatastoreDialect()
286
+ url = make_url("datastore://my-project/")
287
+
288
+ with patch(
289
+ "sqlalchemy_datastore.base.create_datastore_client"
290
+ ) as mock_create:
291
+ mock_client = MagicMock()
292
+ mock_client.project = "my-project"
293
+ mock_create.return_value = (mock_client, MagicMock())
294
+
295
+ d.create_connect_args(url)
296
+
297
+ mock_create.assert_called_once_with(
298
+ credentials_path=None,
299
+ credentials_info=None,
300
+ credentials_base64=None,
301
+ project_id="my-project",
302
+ database=None,
303
+ )
304
+ assert d.database_id is None
@@ -1,65 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: python-datastore-sqlalchemy
3
- Version: 0.0.1
4
- Summary: SQLAlchemy dialect for google cloud datastore
5
- Home-page: https://github.com/splasky/python-datastore-sqlalchemy
6
- Author: HY Chang(splasky)
7
- Author-email: hychang.1997.tw@gmail.com
8
- License: MIT
9
- Project-URL: Documentation, https://github.com/splasky/python-datastore-sqlalchemy/wiki
10
- Project-URL: Source, https://github.com/splasky/python-datastore-sqlalchemy
11
- Project-URL: Tracker, https://github.com/splasky/python-datastore-sqlalchemy/issues
12
- Keywords: SQLAlchemy GCP Datastore
13
- Classifier: Development Status :: 2 - Pre-Alpha
14
- Classifier: Intended Audience :: Developers
15
- Classifier: Programming Language :: Python
16
- Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: Implementation :: CPython
18
- Classifier: Topic :: Database :: Front-Ends
19
- Classifier: Operating System :: POSIX :: Linux
20
- Requires-Python: >=3.9
21
- Description-Content-Type: text/markdown
22
- License-File: LICENSE
23
- Requires-Dist: sqlalchemy<3.0.0,>=1.4.16
24
- Dynamic: author
25
- Dynamic: author-email
26
- Dynamic: classifier
27
- Dynamic: description
28
- Dynamic: description-content-type
29
- Dynamic: home-page
30
- Dynamic: keywords
31
- Dynamic: license
32
- Dynamic: license-file
33
- Dynamic: project-url
34
- Dynamic: requires-dist
35
- Dynamic: requires-python
36
- Dynamic: summary
37
-
38
- SQLAlchemy dialect for google cloud datastore(firestore mode)
39
- ========================
40
- How to install
41
- ```
42
- python3 setup.py install
43
- ```
44
- How to use
45
- ```python
46
- from sqlalchemy import *
47
- from sqlalchemy.engine import create_engine
48
- from sqlalchemy.schema import *
49
- engine = create_engine('datastore://test-api-1', credentials='path/to/credentials.json')
50
- conn = engine.connect()
51
- result = conn.execute("SELECT * from test_table)
52
- print(result.fetchall())
53
- ```
54
-
55
- ## Preview
56
- <img src="assets/pie.png">
57
-
58
- > [!WARNING]
59
- > Please note: Not all GQL and SQL syntax has been fully tested. Should you encounter any bugs, please post the relevant query and open an issue on GitHub.
60
-
61
- ## How to contribute
62
- Feel free to open issues and pull requests on GitHub.
63
-
64
- ## Development Notes
65
- - [Develop a SQLAlchemy and it's dialects](https://hackmd.io/lsBW5GCVR82SORyWZ1cssA?view)
@@ -1,28 +0,0 @@
1
- SQLAlchemy dialect for google cloud datastore(firestore mode)
2
- ========================
3
- How to install
4
- ```
5
- python3 setup.py install
6
- ```
7
- How to use
8
- ```python
9
- from sqlalchemy import *
10
- from sqlalchemy.engine import create_engine
11
- from sqlalchemy.schema import *
12
- engine = create_engine('datastore://test-api-1', credentials='path/to/credentials.json')
13
- conn = engine.connect()
14
- result = conn.execute("SELECT * from test_table)
15
- print(result.fetchall())
16
- ```
17
-
18
- ## Preview
19
- <img src="assets/pie.png">
20
-
21
- > [!WARNING]
22
- > Please note: Not all GQL and SQL syntax has been fully tested. Should you encounter any bugs, please post the relevant query and open an issue on GitHub.
23
-
24
- ## How to contribute
25
- Feel free to open issues and pull requests on GitHub.
26
-
27
- ## Development Notes
28
- - [Develop a SQLAlchemy and it's dialects](https://hackmd.io/lsBW5GCVR82SORyWZ1cssA?view)
@@ -1,21 +0,0 @@
1
- [tool.ruff]
2
- line-length = 120
3
-
4
- [tool.ruff.lint]
5
- select = ["E", "F", "W", "I", "N", "B", "C", "G", "S"]
6
- ignore = [
7
- "E501", # Line too long - allow longer lines for SQL strings in tests
8
- "S608", # SQL injection - intentional for test cases
9
- "B007", # Loop variable not used - common in test assertions
10
- ]
11
-
12
- [tool.ruff.lint.per-file-ignores]
13
- "tests/*" = ["S608", "E501", "S101", "B007", "S603"]
14
-
15
- [tool.mypy]
16
- ignore_missing_imports = true
17
- disable_error_code = ["var-annotated", "import-untyped"]
18
-
19
- [[tool.mypy.overrides]]
20
- module = ["tests.*", "sqlalchemy_datastore.*"]
21
- ignore_errors = true
@@ -1,65 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: python-datastore-sqlalchemy
3
- Version: 0.0.1
4
- Summary: SQLAlchemy dialect for google cloud datastore
5
- Home-page: https://github.com/splasky/python-datastore-sqlalchemy
6
- Author: HY Chang(splasky)
7
- Author-email: hychang.1997.tw@gmail.com
8
- License: MIT
9
- Project-URL: Documentation, https://github.com/splasky/python-datastore-sqlalchemy/wiki
10
- Project-URL: Source, https://github.com/splasky/python-datastore-sqlalchemy
11
- Project-URL: Tracker, https://github.com/splasky/python-datastore-sqlalchemy/issues
12
- Keywords: SQLAlchemy GCP Datastore
13
- Classifier: Development Status :: 2 - Pre-Alpha
14
- Classifier: Intended Audience :: Developers
15
- Classifier: Programming Language :: Python
16
- Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: Implementation :: CPython
18
- Classifier: Topic :: Database :: Front-Ends
19
- Classifier: Operating System :: POSIX :: Linux
20
- Requires-Python: >=3.9
21
- Description-Content-Type: text/markdown
22
- License-File: LICENSE
23
- Requires-Dist: sqlalchemy<3.0.0,>=1.4.16
24
- Dynamic: author
25
- Dynamic: author-email
26
- Dynamic: classifier
27
- Dynamic: description
28
- Dynamic: description-content-type
29
- Dynamic: home-page
30
- Dynamic: keywords
31
- Dynamic: license
32
- Dynamic: license-file
33
- Dynamic: project-url
34
- Dynamic: requires-dist
35
- Dynamic: requires-python
36
- Dynamic: summary
37
-
38
- SQLAlchemy dialect for google cloud datastore(firestore mode)
39
- ========================
40
- How to install
41
- ```
42
- python3 setup.py install
43
- ```
44
- How to use
45
- ```python
46
- from sqlalchemy import *
47
- from sqlalchemy.engine import create_engine
48
- from sqlalchemy.schema import *
49
- engine = create_engine('datastore://test-api-1', credentials='path/to/credentials.json')
50
- conn = engine.connect()
51
- result = conn.execute("SELECT * from test_table)
52
- print(result.fetchall())
53
- ```
54
-
55
- ## Preview
56
- <img src="assets/pie.png">
57
-
58
- > [!WARNING]
59
- > Please note: Not all GQL and SQL syntax has been fully tested. Should you encounter any bugs, please post the relevant query and open an issue on GitHub.
60
-
61
- ## How to contribute
62
- Feel free to open issues and pull requests on GitHub.
63
-
64
- ## Development Notes
65
- - [Develop a SQLAlchemy and it's dialects](https://hackmd.io/lsBW5GCVR82SORyWZ1cssA?view)
@@ -1 +0,0 @@
1
- sqlalchemy<3.0.0,>=1.4.16
@@ -1,70 +0,0 @@
1
- # Copyright (c) 2025 hychang <hychang.1997.tw@gmail.com>
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining a copy of
4
- # this software and associated documentation files (the "Software"), to deal in
5
- # the Software without restriction, including without limitation the rights to
6
- # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
- # the Software, and to permit persons to whom the Software is furnished to do so,
8
- # subject to the following conditions:
9
- #
10
- # The above copyright notice and this permission notice shall be included in all
11
- # copies or substantial portions of the Software.
12
- #
13
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15
- # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
- # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
- # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
- # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
-
20
- import os
21
- import re
22
-
23
- from setuptools import setup, find_packages
24
-
25
- v = open(
26
- os.path.join(os.path.dirname(__file__), "sqlalchemy_datastore", "__init__.py")
27
- )
28
- version_match = re.compile(r'.*__version__ = "(.*?)"', re.S).match(v.read())
29
- if not version_match:
30
- raise RuntimeError("Unable to find version string in __init__.py.")
31
- VERSION = version_match.group(1)
32
- v.close()
33
-
34
- readme = os.path.join(os.path.dirname(__file__), "README.md")
35
-
36
-
37
- setup(
38
- name="python-datastore-sqlalchemy",
39
- version=VERSION,
40
- description="SQLAlchemy dialect for google cloud datastore",
41
- long_description=open(readme).read(),
42
- long_description_content_type="text/markdown",
43
- url="https://github.com/splasky/python-datastore-sqlalchemy",
44
- author="HY Chang(splasky)",
45
- author_email="hychang.1997.tw@gmail.com",
46
- license="MIT",
47
- classifiers=[
48
- "Development Status :: 2 - Pre-Alpha",
49
- "Intended Audience :: Developers",
50
- "Programming Language :: Python",
51
- "Programming Language :: Python :: 3",
52
- "Programming Language :: Python :: Implementation :: CPython",
53
- "Topic :: Database :: Front-Ends",
54
- "Operating System :: POSIX :: Linux",
55
- ],
56
- keywords="SQLAlchemy GCP Datastore",
57
- python_requires=">=3.9",
58
- project_urls={
59
- "Documentation": "https://github.com/splasky/python-datastore-sqlalchemy/wiki",
60
- "Source": "https://github.com/splasky/python-datastore-sqlalchemy",
61
- "Tracker": "https://github.com/splasky/python-datastore-sqlalchemy/issues",
62
- },
63
- packages=find_packages(include=["sqlalchemy_datastore"]),
64
- include_package_data=True,
65
- install_requires=["sqlalchemy>=1.4.16,<3.0.0",],
66
- zip_safe=False,
67
- entry_points={
68
- "sqlalchemy.dialects": ["datastore = sqlalchemy_datastore:CloudDatastoreDialect"]
69
- },
70
- )