Skip to content

Commit 062e0b1

Browse files
author
Hao Yen Chang
committed
Add pytest infrastructure
1 parent 8c504f4 commit 062e0b1

9 files changed

Lines changed: 1262 additions & 119 deletions

File tree

pytest.ini

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[pytest]
2+
addopts = --ruff --mypy --cov --cov-report=html
3+
pythonpath = .
4+
testpaths = tests
5+
python_files = test_*.py

requirements.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,9 @@ sqlalchemy==2.0.41
22
google-cloud-datastore>=2.21.0
33
pytest==8.4.1
44
setuptools==80.9.0
5+
pytest-cov==6.2.1
6+
pytest-html==4.1.1
7+
pytest-mypy==1.0.1
8+
pytest-ruff==0.5
9+
flake8==7.3.0
10+
coverage==7.9.2

sqlalchemy_datastore/base.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,12 @@ class DatastoreCompiler(compiler.SQLCompiler):
4646
def __init__(self, dialect, statement, *args, **kwargs):
4747
super().__init__(dialect, statement, *args, **kwargs)
4848

49-
if hasattr(statement, "_compile_state_factory"):
50-
self.compile_state = statement._compile_state_factory(dialect, statement)
51-
self.compiled.compile_state = self.compile_state
52-
else:
53-
self.compile_state = None
49+
# if hasattr(statement, "_compile_state_factory"):
50+
# compiler = dialect.statement_compiler(dialect, statement)
51+
# self.compile_state = statement._compile_state_factory(statement, compiler)
52+
# self.compiled.compile_state = self.compile_state
53+
# else:
54+
self.compile_state = None
5455

5556
def visit_select(self, select_stmt, asfrom=False, **kw):
5657
"""

test/test_datastore.py

Lines changed: 0 additions & 61 deletions
This file was deleted.

test/test_sqlite3.py

Lines changed: 0 additions & 37 deletions
This file was deleted.

tests/conftest.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Copyright (c) 2025 The sqlalchemy-datastore Authors
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+
import pytest
20+
from sqlalchemy import create_engine
21+
import os
22+
23+
# Fixture example (add this to your conftest.py)
24+
@pytest.fixture
25+
def conn():
26+
"""Database connection fixture - implement according to your setup"""
27+
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "./test_credentials.json"
28+
engine = create_engine('datastore://python-datastore-sqlalchemy', echo=True)
29+
conn = engine.connect()
30+
return conn

tests/test_datastore.py

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
# Copyright (c) 2025 The sqlalchemy-datastore Authors
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 sqlalchemy import create_engine, text
21+
from sqlalchemy.orm import declarative_base
22+
import os
23+
24+
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "./test_credentials.json"
25+
26+
Base = declarative_base()
27+
engine = create_engine('datastore://test-api-2', echo=True)
28+
conn = engine.connect()
29+
30+
# no cursor in datastore
31+
# cursor = conn.cursor()
32+
33+
# Datastore has no create table command
34+
# conn.execute(text("""
35+
# CREATE TABLE users (
36+
# id INTEGER PRIMARY KEY AUTOINCREMENT,
37+
# name TEXT,
38+
# age INTEGER
39+
# )
40+
# """))
41+
42+
# Query the database
43+
# GQL reference can be found at https://cloud.google.com/datastore/docs/gql_reference
44+
result = conn.execute(text("SELECT * FROM users"))
45+
data = result.all()
46+
assert len(data) == 3, "Expected 3 rows in the users table, but found a different number."
47+
48+
# More Query
49+
result = conn.execute(text("SELECT id, name, age FROM users WHERE age > 20"))
50+
data = result.all()
51+
assert len(data) == 1, "Expected 1 row with age > 20, but found a different number."
52+
53+
# Query with specific conditions
54+
result = conn.execute(text("SELECT id, name, age FROM users WHERE name = 'Alice'"))
55+
data = result.all()
56+
assert len(data) == 1, "Expected 1 row with name 'Alice', but found a different number."
57+
58+
# Query for keys
59+
result = conn.execute(text("SELECT __key__ FROM users"))
60+
data = result.all()
61+
assert len(data) == 3, "Expected 3 keys in the users table, but found a different number."
62+
63+
result = conn.execute(text("SELECT __key__ WHERE __key__ = KEY('users', 'alice_id')"))
64+
data = result.all()
65+
assert len(data) == 1
66+
67+
# Query for specific columns
68+
result = conn.execute(text("SELECT name, age FROM useers"))
69+
data = result.all()
70+
assert len(data) == 3, "Expected 3 rows in the users table, but found a different number."
71+
for row in data:
72+
assert row[0] in ['Alice', 'Bob', 'Carol'], "Expected names 'Alice', 'Bob', or 'Carol' in the users table, but found a different name."
73+
assert row[1] in [30, 24, 35], "Expected ages 30, 24, or 35 in the users table, but found a different age."
74+
75+
# Query for fully-qualified property names
76+
result = conn.execute(text("SELECT users.name, users.age FROM users"))
77+
data = result.all()
78+
assert len(data) == 3, "Expected 3 rows in the users table, but found a different number."
79+
for row in data:
80+
assert row[0] in ['Alice', 'Bob', 'Carol'], "Expected names 'Alice', 'Bob', or 'Carol' in the users table, but found a different name."
81+
assert row[1] in [30, 24, 35], "Expected ages 30, 24, or 35 in the users table, but found a different age."
82+
83+
# Clause Query
84+
## Distinct query
85+
result = conn.execute(text("SELECT DISTINCT name FROM users"))
86+
data = result.all()
87+
assert len(data) == 3, "Expected 3 distinct names in the users table, but found a different number."
88+
for row in data:
89+
assert row[0] in ['Alice', 'Bob', 'Carol'], "Expected names 'Alice', 'Bob', or 'Carol' in the users table, but found a different name."
90+
91+
result = conn.execute(text("SELECT DISTINCT name, age FROM users WHERE age > 20 ORDER BY age DESC LIMIT 10 OFFSET 5"))
92+
data = result.all()
93+
assert len(data) == 1, "Expected 1 distinct row with age > 20, but found a different number."
94+
95+
## Distinct on query
96+
result = conn.execute(text("SELECT DISTINCT ON (name) name, age FROM users ORDER BY name, age DESC"))
97+
data = result.all()
98+
assert len(data) == 3, "Expected 3 distinct names in the users table, but found a different number."
99+
100+
result = conn.execute(text("SELECT DISTINCT ON (name) name, age FROM users WHERE age > 20 ORDER BY name ASC, age DESC LIMIT 10"))
101+
data = result.all()
102+
assert len(data) == 3, ""
103+
104+
## Order by query
105+
result = conn.execute(text("SELECT * FROM users ORDER BY age ASC LIMIT 5"))
106+
data = result.all()
107+
assert len(data) == 3
108+
109+
## Compound query
110+
result = conn.execute(text("SELECT DISTINCT ON (name, age) name, age, city FROM users WHERE age >= 18 AND city = 'Tokyo' ORDER BY name ASC, age DESC LIMIT 20 OFFSET 10"))
111+
data = result.all()
112+
assert len(data) == 3
113+
114+
# Aggregration query
115+
result = conn.execute(text("AGGREGATE COUNT(*) OVER ( SELECT * FROM tasks WHERE is_done = false AND tag = 'house' )"))
116+
data = result.all()
117+
assert len(data) == 3
118+
119+
result = conn.execute(text("AGGREGATE COUNT_UP_TO(5) OVER ( SELECT * FROM tasks WHERE is_done = false AND tag = 'house' )"))
120+
data = result.all()
121+
assert len(data) == 3
122+
123+
124+
# Insert data (using parameterized query to prevent SQL injection)
125+
result = conn.execute(text("INSERT INTO users (name, age) VALUES ('Bob', 25)"))
126+
print(result)
127+
result = conn.execute(
128+
text("INSERT INTO users (name, age) VALUES (:name, :age)"),
129+
{"name": "Alice", "age": 30}
130+
)
131+
print(result)
132+
133+
from sqlalchemy_datastore import CloudDatastoreDialect
134+
stmt = text("INSERT INTO users (name, age) VALUES (:name, :age)")
135+
compiled = stmt.compile(dialect=CloudDatastoreDialect())
136+
print(str(compiled))
137+
with engine.connect() as conn:
138+
conn.execute(
139+
stmt,
140+
{"name": "Alice", "age": 30}
141+
)
142+
143+
# Commit the transaction
144+
conn.commit()
145+
146+
# Query the database
147+
result = conn.execute(text("SELECT id, name, age FROM users"))
148+
rows = result.fetchall()
149+
150+
# Process results
151+
for row in rows:
152+
print(f"ID: {row[0]}, Name: {row[1]}, Age: {row[2]}")
153+
154+
# Clean up
155+
conn.close()

0 commit comments

Comments
 (0)