Skip to content

Commit

Permalink
MongoDB/PyMongo: Add software tests and CI configuration
Browse files Browse the repository at this point in the history
It needs to balance SQLAlchemy 1.x vs. 2.x throughout the toolkit test
cases, because JessiQL still uses SQLAlchemy 1.x.
  • Loading branch information
amotl committed Jul 6, 2024
1 parent d0fbc8a commit 44aba7a
Show file tree
Hide file tree
Showing 24 changed files with 261 additions and 57 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/influxdb.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ jobs:
cache: 'pip'
cache-dependency-path: 'pyproject.toml'

- name: Setup project
- name: Set up project
run: |
# `setuptools 0.64.0` adds support for editable install hooks (PEP 660).
Expand Down
73 changes: 70 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ concurrency:

jobs:

tests:

tests-main:

runs-on: ${{ matrix.os }}
strategy:
Expand All @@ -43,7 +44,10 @@ jobs:
- 4200:4200
- 5432:5432

name: Python ${{ matrix.python-version }} on OS ${{ matrix.os }}
name: "
Generic:
Python ${{ matrix.python-version }} on OS ${{ matrix.os }}
"
steps:

- name: Acquire sources
Expand All @@ -57,7 +61,7 @@ jobs:
cache: 'pip'
cache-dependency-path: 'pyproject.toml'

- name: Setup project
- name: Set up project
run: |
# `setuptools 0.64.0` adds support for editable install hooks (PEP 660).
Expand All @@ -84,3 +88,66 @@ jobs:
env_vars: OS,PYTHON
name: codecov-umbrella
fail_ci_if_error: true


tests-pymongo:

runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest"]
python-version: ["3.9", "3.12"]

env:
OS: ${{ matrix.os }}
PYTHON: ${{ matrix.python-version }}
# Do not tear down Testcontainers
TC_KEEPALIVE: true

# https://docs.github.com/en/actions/using-containerized-services/about-service-containers
services:
cratedb:
image: crate/crate:nightly
ports:
- 4200:4200
- 5432:5432

name: "
PyMongo:
Python ${{ matrix.python-version }} on OS ${{ matrix.os }}"
steps:

- name: Acquire sources
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
architecture: x64
cache: 'pip'
cache-dependency-path: 'pyproject.toml'

- name: Set up project
run: |
# `setuptools 0.64.0` adds support for editable install hooks (PEP 660).
# https://github.com/pypa/setuptools/blob/main/CHANGES.rst#v6400
pip install "setuptools>=64" --upgrade
# Install package in editable mode.
pip install --use-pep517 --prefer-binary --editable=.[pymongo,test,develop]
- name: Run linter and software tests
run: |
poe check
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
files: ./coverage.xml
flags: pymongo
env_vars: OS,PYTHON
name: codecov-umbrella
fail_ci_if_error: false
2 changes: 1 addition & 1 deletion .github/workflows/mongodb.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:
cache: 'pip'
cache-dependency-path: 'pyproject.toml'

- name: Setup project
- name: Set up project
run: |
# `setuptools 0.64.0` adds support for editable install hooks (PEP 660).
Expand Down
2 changes: 1 addition & 1 deletion cratedb_toolkit/adapter/pymongo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ with PyMongoCrateDbAdapter(dburi="crate://crate@localhost:4200"):
inserted_id = collection.insert_one({"author": "Mike", "text": "My first blog post!"}).inserted_id
print(inserted_id)

document = collection.find({"author": "Mike"})
document = collection.find_one({"author": "Mike"})
print(document)
```

Expand Down
3 changes: 3 additions & 0 deletions cratedb_toolkit/adapter/pymongo/backlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
- Add documentation.
- Add missing essential querying features: Examples: sort order, skip, limit
- Add missing essential methods. Example: `db.my_collection.drop()`.
- Make write-synchronization behavior (refresh table) configurable.
- Handle deeply nested documents of various types.

## Iteration +2

Expand All @@ -24,6 +26,7 @@
```python
jessiql.exc.InvalidColumnError: Invalid column "x" for "Surrogate" specified in sort
```
- Run (parts of) the PyMongo test suite?
## Done
Expand Down
3 changes: 3 additions & 0 deletions cratedb_toolkit/adapter/pymongo/collection.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Make Python 3.7 and 3.8 support generic types like `dict` instead of `typing.Dict`.
from __future__ import annotations

import io
import logging
from collections import abc
Expand Down
3 changes: 3 additions & 0 deletions cratedb_toolkit/adapter/pymongo/cursor.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Make Python 3.7 and 3.8 support generic types like `dict` instead of `typing.Dict`.
from __future__ import annotations

import copy
import logging
import warnings
Expand Down
5 changes: 3 additions & 2 deletions cratedb_toolkit/retention/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from sqlalchemy import MetaData, Table
from sqlalchemy.exc import ProgrammingError
from sqlalchemy.orm import Session
from sqlalchemy.sql.selectable import NamedFromClause

from cratedb_toolkit.retention.model import JobSettings, RetentionPolicy, RetentionStrategy
from cratedb_toolkit.util.database import DatabaseAdapter, sa_is_empty
Expand All @@ -36,12 +35,14 @@ def get_tags_constraints(self, tags: t.Union[t.List[str], t.Set[str]]):
"""
Return list of SQL WHERE constraint clauses from given tags.
"""
from sqlalchemy.sql.selectable import NamedFromClause # type: ignore[attr-defined]

table: NamedFromClause = self.table # type: ignore[attr-defined]
constraints = []
for tag in tags:
if not tag:
continue
constraint = table.c[self.tag_column][tag] != sa.Null()
constraint = table.c[self.tag_column][tag] != sa.Null() # type: ignore[attr-defined]
constraints.append(constraint)
return sa.and_(sa.true(), *constraints)

Expand Down
69 changes: 48 additions & 21 deletions examples/pymongo_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
==========
- https://github.com/mongodb/mongo-python-driver
"""
import datetime as dt
import logging
import time

import pymongo
from pymongo.database import Database
Expand All @@ -35,7 +37,7 @@
logger = logging.getLogger(__name__)


def main():
def mongodb_workload():
client = pymongo.MongoClient(
"localhost", 27017, timeoutMS=100, connectTimeoutMS=100, socketTimeoutMS=100, serverSelectionTimeoutMS=100
)
Expand All @@ -46,45 +48,70 @@ def main():
logger.info(f"Using database: {db.name}")
logger.info(f"Using collection: {db.my_collection}")

# Insert records.
inserted_id = db.my_collection.insert_one({"x": 5}).inserted_id
logger.info(f"Inserted object: {inserted_id!r}")
# TODO: Dropping a collection is not implemented yet.
# db.my_collection.drop() # noqa: ERA001

# Insert document.
documents = [
{
"author": "Mike",
"text": "My first blog post!",
"tags": ["mongodb", "python", "pymongo"],
"date": dt.datetime.now(tz=dt.timezone.utc),
},
{
"author": "Eliot",
"title": "MongoDB is fun",
"text": "and pretty easy too!",
"date": dt.datetime(2009, 11, 10, 10, 45),
},
]
result = db.my_collection.insert_many(documents)
logger.info(f"Inserted document identifiers: {result.inserted_ids!r}")

# FIXME: Refresh table.
time.sleep(1)

# Query records.
# Query documents.
document_count = db.my_collection.count_documents({})
logger.info(f"Total document count: {document_count}")

# Find single document.
document = db.my_collection.find_one()
document = db.my_collection.find_one({"author": "Mike"})
logger.info(f"[find_one] Response document: {document}")

# Assorted basic find operations, with sorting and paging.
print("results:", end=" ")
# Run a few basic retrieval operations, with sorting and paging.
print("Whole collection")
for item in db.my_collection.find():
print(item["x"], end=", ")
print(item)
print()

print("results:", end=" ")
for item in db.my_collection.find().sort("x", pymongo.ASCENDING):
print(item["x"], end=", ")
print("Sort ascending")
for item in db.my_collection.find().sort("author", pymongo.ASCENDING):
print(item)
print()

print("results:", end=" ")
for item in db.my_collection.find().sort("x", pymongo.DESCENDING):
print(item["x"], end=", ")
print("Sort descending")
for item in db.my_collection.find().sort("author", pymongo.DESCENDING):
print(item)
print()

results = [item["x"] for item in db.my_collection.find().limit(2).skip(1)]
print("results:", results)
print("length:", len(results))
print("Paging")
for item in db.my_collection.find().limit(2).skip(1):
print(item)
print()


if __name__ == "__main__":
def main(dburi: str = None):
dburi = dburi or "crate://crate@localhost:4200"

# setup_logging(level=logging.DEBUG, width=42) # noqa: ERA001
setup_logging(level=logging.INFO, width=20)

# Context manager use.
with PyMongoCrateDbAdapter(dburi="crate://crate@localhost:4200"):
main()
with PyMongoCrateDbAdapter(dburi=dburi):
mongodb_workload()


if __name__ == "__main__":
main()
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ dependencies = [
"tqdm<5",
'typing-extensions<5; python_version <= "3.7"',
"vasuki==0.7.0",
"verlib2==0.2",
"yarl<1.10",
]
[project.optional-dependencies]
Expand Down Expand Up @@ -147,6 +148,7 @@ io = [
"cr8",
"dask[dataframe]>=2020",
"pandas<3,>=1",
"sqlalchemy>=2",
]
mongodb = [
"cratedb-toolkit[io]",
Expand All @@ -157,8 +159,9 @@ mongodb = [
]
pymongo = [
"jessiql==1.0.0rc1",
"pandas<3,>=2",
"pymongo<5,>=3.10.1",
"sqlalchemy<2.0",
"sqlalchemy<2",
]
release = [
"build<2",
Expand Down
Loading

0 comments on commit 44aba7a

Please sign in to comment.