Skip to content

Commit c2b20b4

Browse files
committed
records: Add example about library use
1 parent c962305 commit c2b20b4

File tree

9 files changed

+405
-0
lines changed

9 files changed

+405
-0
lines changed

.github/dependabot.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ updates:
112112
schedule:
113113
interval: "daily"
114114

115+
- directory: "/framework/records"
116+
package-ecosystem: "pip"
117+
schedule:
118+
interval: "daily"
119+
115120
- directory: "/framework/streamlit"
116121
package-ecosystem: "pip"
117122
schedule:
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
name: records
2+
3+
on:
4+
pull_request:
5+
branches: ~
6+
paths:
7+
- '.github/workflows/framework-records.yml'
8+
- 'framework/records/**'
9+
- '/requirements.txt'
10+
push:
11+
branches: [ main ]
12+
paths:
13+
- '.github/workflows/framework-records.yml'
14+
- 'framework/records/**'
15+
- '/requirements.txt'
16+
17+
# Allow job to be triggered manually.
18+
workflow_dispatch:
19+
20+
# Run job each night after CrateDB nightly has been published.
21+
schedule:
22+
- cron: '0 3 * * *'
23+
24+
# Cancel in-progress jobs when pushing to the same branch.
25+
concurrency:
26+
cancel-in-progress: true
27+
group: ${{ github.workflow }}-${{ github.ref }}
28+
29+
jobs:
30+
test:
31+
name: "
32+
Python: ${{ matrix.python-version }}
33+
CrateDB: ${{ matrix.cratedb-version }}
34+
on ${{ matrix.os }}"
35+
runs-on: ${{ matrix.os }}
36+
strategy:
37+
fail-fast: false
38+
matrix:
39+
os: [ 'ubuntu-latest' ]
40+
python-version: [ '3.9', '3.13' ]
41+
cratedb-version: [ 'nightly' ]
42+
43+
services:
44+
cratedb:
45+
image: crate/crate:${{ matrix.cratedb-version }}
46+
ports:
47+
- 4200:4200
48+
- 5432:5432
49+
env:
50+
CRATE_HEAP_SIZE: 4g
51+
52+
steps:
53+
54+
- name: Acquire sources
55+
uses: actions/checkout@v4
56+
57+
- name: Set up Python
58+
uses: actions/setup-python@v5
59+
with:
60+
python-version: ${{ matrix.python-version }}
61+
architecture: x64
62+
cache: 'pip'
63+
cache-dependency-path: |
64+
requirements.txt
65+
framework/records/requirements.txt
66+
framework/records/requirements-dev.txt
67+
68+
- name: Install utilities
69+
run: |
70+
pip install -r requirements.txt
71+
72+
- name: Validate framework/records
73+
run: |
74+
ngr test --accept-no-venv framework/records

framework/records/README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Verify the `records` library with CrateDB
2+
3+
Records: SQL for Humans™
4+
5+
## About
6+
7+
This folder includes software integration tests for verifying
8+
that the [Records] Python library works well together with [CrateDB].
9+
10+
Records is a very simple, but powerful, library for making raw SQL
11+
queries to most relational databases. It uses [SQLAlchemy].
12+
13+
Records is intended for report-style exports of database queries, and
14+
has not yet been optimized for extremely large data dumps.
15+
16+
## What's Inside
17+
18+
- `example_basic.py`: A few examples that read CrateDB's `sys.summits` table.
19+
An example inquiring existing tables.
20+
21+
- `example_types.py`: An example that exercises all data types supported by
22+
CrateDB.
23+
24+
## Install
25+
26+
Set up sandbox and install packages.
27+
```bash
28+
pip install uv
29+
uv venv .venv
30+
source .venv/bin/activate
31+
uv pip install -r requirements.txt -r requirements-test.txt
32+
```
33+
34+
## Synopsis
35+
```shell
36+
pip install --upgrade records sqlalchemy-cratedb
37+
```
38+
```python
39+
from pprint import pprint
40+
import records
41+
42+
# Define database connection URL, suitable for CrateDB on localhost.
43+
# For CrateDB Cloud, use `crate://<username>:<password>@<host>`.
44+
db = records.Database("crate://")
45+
46+
# Invoke query.
47+
rows = db.query("SELECT * FROM sys.summits ORDER BY height DESC LIMIT 3")
48+
data = rows.all()
49+
pprint(data)
50+
```
51+
52+
## Tests
53+
54+
Run integration tests.
55+
```bash
56+
pytest
57+
```
58+
59+
60+
[CrateDB]: https://cratedb.com/database
61+
[Records]: https://pypi.org/project/records/
62+
[SQLAlchemy]: https://www.sqlalchemy.org/

framework/records/example_basic.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""
2+
Using `records` with CrateDB: Basic usage.
3+
4+
pip install --upgrade records sqlalchemy-cratedb
5+
6+
A few basic operations using the `records` library with CrateDB.
7+
8+
- https://pypi.org/project/records/
9+
"""
10+
11+
import records
12+
13+
14+
def records_select_sys_summits():
15+
"""
16+
Query CrateDB's built-in `sys.summits` table.
17+
:return:
18+
"""
19+
db = records.Database("crate://", echo=True)
20+
rows = db.query("SELECT * FROM sys.summits ORDER BY height DESC LIMIT 3")
21+
data = rows.all()
22+
return data
23+
24+
25+
def records_export_sys_summits_pandas():
26+
"""
27+
Query CrateDB's built-in `sys.summits` table, returning a pandas dataframe.
28+
"""
29+
db = records.Database("crate://", echo=True)
30+
rows = db.query("SELECT * FROM sys.summits ORDER BY height DESC LIMIT 3")
31+
data = rows.export("df")
32+
return data
33+
34+
35+
def records_export_sys_summits_csv():
36+
"""
37+
Query CrateDB's built-in `sys.summits` table, returning CSV.
38+
"""
39+
db = records.Database("crate://", echo=True)
40+
rows = db.query("SELECT * FROM sys.summits ORDER BY height DESC LIMIT 3")
41+
data = rows.export("csv")
42+
return data
43+
44+
45+
def records_get_table_names():
46+
"""
47+
Inquire table names of the system schema `sys`.
48+
"""
49+
db = records.Database("crate://?schema=sys", echo=True)
50+
table_names = db.get_table_names()
51+
return table_names
52+
53+
54+
if __name__ == "__main__":
55+
print(records_select_sys_summits())
56+
print(records_export_sys_summits_pandas())
57+
print(records_export_sys_summits_csv())
58+
print(records_get_table_names())

framework/records/example_types.py

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
"""
2+
Using `records` with CrateDB: All data types.
3+
4+
pip install --upgrade records sqlalchemy-cratedb
5+
6+
An end-to-end lifecycle, defining a table, inserting data, and querying it.
7+
This example uses all data types supported by CrateDB.
8+
9+
- https://pypi.org/project/records/
10+
- https://cratedb.com/docs/crate/reference/en/latest/general/ddl/data-types.html#supported-types
11+
"""
12+
13+
from copy import deepcopy
14+
15+
import pytest
16+
import records
17+
18+
19+
# The record that is inserted into the database.
20+
RECORD_IN = dict(
21+
null_integer=None,
22+
integer=42,
23+
bigint=42,
24+
float=42.42,
25+
double=42.42,
26+
decimal=42.42,
27+
bit="01010101",
28+
bool=True,
29+
text="foobar",
30+
char="foo",
31+
timestamp_tz="1970-01-02T00:00:00+01:00",
32+
timestamp_notz="1970-01-02T00:00:00",
33+
ip="127.0.0.1",
34+
array=["foo", "bar"],
35+
object={"for": "bar"},
36+
geopoint=[85.43, 66.23],
37+
geoshape="POLYGON ((5 5, 10 5, 10 10, 5 10, 5 5))",
38+
float_vector=[1.0, 2.0, 3.0],
39+
)
40+
41+
# When querying it, a few values will be canonicalized.
42+
RECORD_OUT = deepcopy(RECORD_IN)
43+
RECORD_OUT.update(
44+
dict(
45+
bit="B'01010101'",
46+
char="foo ",
47+
timestamp_tz=82800000,
48+
timestamp_notz=86400000,
49+
geopoint=[pytest.approx(85.43), pytest.approx(66.23)],
50+
geoshape={
51+
"coordinates": [
52+
[[5.0, 5.0], [5.0, 10.0], [10.0, 10.0], [10.0, 5.0], [5.0, 5.0]]
53+
],
54+
"type": "Polygon",
55+
},
56+
)
57+
)
58+
59+
60+
def records_ddl_dml_dql():
61+
"""
62+
Validate all types of CrateDB.
63+
64+
https://cratedb.com/docs/crate/reference/en/latest/general/ddl/data-types.html#supported-types
65+
"""
66+
db = records.Database("crate://", echo=True)
67+
68+
# DDL
69+
db.query("DROP TABLE IF EXISTS testdrive.example;")
70+
db.query("""
71+
CREATE TABLE testdrive.example (
72+
-- Numeric types
73+
null_integer INT,
74+
integer INT,
75+
bigint BIGINT,
76+
float FLOAT,
77+
double DOUBLE,
78+
decimal DECIMAL(8, 2),
79+
-- Other scalar types
80+
bit BIT(8),
81+
bool BOOLEAN,
82+
text TEXT,
83+
char CHARACTER(5),
84+
timestamp_tz TIMESTAMP WITH TIME ZONE,
85+
timestamp_notz TIMESTAMP WITHOUT TIME ZONE,
86+
ip IP,
87+
-- Container types
88+
"array" ARRAY(STRING),
89+
"object" OBJECT(DYNAMIC),
90+
-- Geospatial types
91+
geopoint GEO_POINT,
92+
geoshape GEO_SHAPE,
93+
-- Vector type
94+
"float_vector" FLOAT_VECTOR(3)
95+
);
96+
""")
97+
98+
# DML
99+
db.query(
100+
"""
101+
INSERT INTO testdrive.example (
102+
null_integer,
103+
integer,
104+
bigint,
105+
float,
106+
double,
107+
decimal,
108+
bit,
109+
bool,
110+
text,
111+
char,
112+
timestamp_tz,
113+
timestamp_notz,
114+
ip,
115+
"array",
116+
"object",
117+
geopoint,
118+
geoshape,
119+
float_vector
120+
) VALUES (
121+
:null_integer,
122+
:integer,
123+
:bigint,
124+
:float,
125+
:double,
126+
:decimal,
127+
:bit,
128+
:bool,
129+
:text,
130+
:char,
131+
:timestamp_tz,
132+
:timestamp_notz,
133+
:ip,
134+
:array,
135+
:object,
136+
:geopoint,
137+
:geoshape,
138+
:float_vector
139+
);
140+
""",
141+
**RECORD_IN,
142+
)
143+
144+
# DQL
145+
db.query("REFRESH TABLE testdrive.example")
146+
rows = db.query("SELECT * FROM testdrive.example")
147+
data = rows.all()
148+
return data

framework/records/pyproject.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[tool.pytest.ini_options]
2+
minversion = "2.0"
3+
addopts = """
4+
-rfEXs -p pytester --strict-markers --verbosity=3
5+
--capture=no
6+
"""
7+
log_level = "DEBUG"
8+
log_cli_level = "DEBUG"
9+
testpaths = ["*.py"]
10+
xfail_strict = true
11+
markers = [
12+
]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pytest<9

framework/records/requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
records<0.7
2+
sqlalchemy-cratedb<0.41
3+
tablib[pandas]

0 commit comments

Comments
 (0)