arcade-mongodb 0.1.0__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.
- arcade_mongodb-0.1.0/.gitignore +175 -0
- arcade_mongodb-0.1.0/Makefile +53 -0
- arcade_mongodb-0.1.0/PKG-INFO +21 -0
- arcade_mongodb-0.1.0/arcade_mongodb/__init__.py +0 -0
- arcade_mongodb-0.1.0/arcade_mongodb/database_engine.py +118 -0
- arcade_mongodb-0.1.0/arcade_mongodb/tools/__init__.py +0 -0
- arcade_mongodb-0.1.0/arcade_mongodb/tools/mongodb.py +367 -0
- arcade_mongodb-0.1.0/arcade_mongodb/tools/utils.py +281 -0
- arcade_mongodb-0.1.0/evals/eval_mongodb.py +190 -0
- arcade_mongodb-0.1.0/pyproject.toml +62 -0
- arcade_mongodb-0.1.0/tests/__init__.py +0 -0
- arcade_mongodb-0.1.0/tests/conftest.py +45 -0
- arcade_mongodb-0.1.0/tests/dump.js +378 -0
- arcade_mongodb-0.1.0/tests/test_json_validation.py +222 -0
- arcade_mongodb-0.1.0/tests/test_mongodb.py +294 -0
- arcade_mongodb-0.1.0/tests/test_setup.sh +12 -0
- arcade_mongodb-0.1.0/tests/test_write_validation.py +249 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
.DS_Store
|
|
2
|
+
credentials.yaml
|
|
3
|
+
docker/credentials.yaml
|
|
4
|
+
|
|
5
|
+
*.lock
|
|
6
|
+
|
|
7
|
+
# example data
|
|
8
|
+
examples/data
|
|
9
|
+
scratch
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
docs/source
|
|
13
|
+
|
|
14
|
+
# From https://raw.githubusercontent.com/github/gitignore/main/Python.gitignore
|
|
15
|
+
|
|
16
|
+
# Byte-compiled / optimized / DLL files
|
|
17
|
+
__pycache__/
|
|
18
|
+
*.py[cod]
|
|
19
|
+
*$py.class
|
|
20
|
+
|
|
21
|
+
# C extensions
|
|
22
|
+
*.so
|
|
23
|
+
|
|
24
|
+
# Distribution / packaging
|
|
25
|
+
.Python
|
|
26
|
+
build/
|
|
27
|
+
develop-eggs/
|
|
28
|
+
dist/
|
|
29
|
+
downloads/
|
|
30
|
+
eggs/
|
|
31
|
+
.eggs/
|
|
32
|
+
lib/
|
|
33
|
+
lib64/
|
|
34
|
+
parts/
|
|
35
|
+
sdist/
|
|
36
|
+
var/
|
|
37
|
+
wheels/
|
|
38
|
+
share/python-wheels/
|
|
39
|
+
*.egg-info/
|
|
40
|
+
.installed.cfg
|
|
41
|
+
*.egg
|
|
42
|
+
MANIFEST
|
|
43
|
+
|
|
44
|
+
# PyInstaller
|
|
45
|
+
# Usually these files are written by a python script from a template
|
|
46
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
47
|
+
*.manifest
|
|
48
|
+
*.spec
|
|
49
|
+
|
|
50
|
+
# Installer logs
|
|
51
|
+
pip-log.txt
|
|
52
|
+
pip-delete-this-directory.txt
|
|
53
|
+
|
|
54
|
+
# Unit test / coverage reports
|
|
55
|
+
htmlcov/
|
|
56
|
+
.tox/
|
|
57
|
+
.nox/
|
|
58
|
+
.coverage
|
|
59
|
+
.coverage.*
|
|
60
|
+
.cache
|
|
61
|
+
nosetests.xml
|
|
62
|
+
coverage.xml
|
|
63
|
+
*.cover
|
|
64
|
+
*.py,cover
|
|
65
|
+
.hypothesis/
|
|
66
|
+
.pytest_cache/
|
|
67
|
+
cover/
|
|
68
|
+
|
|
69
|
+
# Translations
|
|
70
|
+
*.mo
|
|
71
|
+
*.pot
|
|
72
|
+
|
|
73
|
+
# Django stuff:
|
|
74
|
+
*.log
|
|
75
|
+
local_settings.py
|
|
76
|
+
db.sqlite3
|
|
77
|
+
db.sqlite3-journal
|
|
78
|
+
|
|
79
|
+
# Flask stuff:
|
|
80
|
+
instance/
|
|
81
|
+
.webassets-cache
|
|
82
|
+
|
|
83
|
+
# Scrapy stuff:
|
|
84
|
+
.scrapy
|
|
85
|
+
|
|
86
|
+
# Sphinx documentation
|
|
87
|
+
docs/_build/
|
|
88
|
+
|
|
89
|
+
# PyBuilder
|
|
90
|
+
.pybuilder/
|
|
91
|
+
target/
|
|
92
|
+
|
|
93
|
+
# Jupyter Notebook
|
|
94
|
+
.ipynb_checkpoints
|
|
95
|
+
|
|
96
|
+
# IPython
|
|
97
|
+
profile_default/
|
|
98
|
+
ipython_config.py
|
|
99
|
+
|
|
100
|
+
# pyenv
|
|
101
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
102
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
103
|
+
# .python-version
|
|
104
|
+
|
|
105
|
+
# pipenv
|
|
106
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
107
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
108
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
109
|
+
# install all needed dependencies.
|
|
110
|
+
#Pipfile.lock
|
|
111
|
+
|
|
112
|
+
# poetry
|
|
113
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
114
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
115
|
+
# commonly ignored for libraries.
|
|
116
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
117
|
+
poetry.lock
|
|
118
|
+
|
|
119
|
+
# pdm
|
|
120
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
121
|
+
#pdm.lock
|
|
122
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
123
|
+
# in version control.
|
|
124
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
125
|
+
.pdm.toml
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# SageMath parsed files
|
|
135
|
+
*.sage.py
|
|
136
|
+
|
|
137
|
+
# Environments
|
|
138
|
+
.env
|
|
139
|
+
.venv
|
|
140
|
+
env/
|
|
141
|
+
venv/
|
|
142
|
+
ENV/
|
|
143
|
+
env.bak/
|
|
144
|
+
venv.bak/
|
|
145
|
+
|
|
146
|
+
# Spyder project settings
|
|
147
|
+
.spyderproject
|
|
148
|
+
.spyproject
|
|
149
|
+
|
|
150
|
+
# Rope project settings
|
|
151
|
+
.ropeproject
|
|
152
|
+
|
|
153
|
+
# mkdocs documentation
|
|
154
|
+
/site
|
|
155
|
+
|
|
156
|
+
# mypy
|
|
157
|
+
.mypy_cache/
|
|
158
|
+
.dmypy.json
|
|
159
|
+
dmypy.json
|
|
160
|
+
|
|
161
|
+
# Pyre type checker
|
|
162
|
+
.pyre/
|
|
163
|
+
|
|
164
|
+
# pytype static type analyzer
|
|
165
|
+
.pytype/
|
|
166
|
+
|
|
167
|
+
# Cython debug symbols
|
|
168
|
+
cython_debug/
|
|
169
|
+
|
|
170
|
+
# PyCharm
|
|
171
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
172
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
173
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
174
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
175
|
+
#.idea/
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
.PHONY: help
|
|
2
|
+
|
|
3
|
+
help:
|
|
4
|
+
@echo "🛠️ github Commands:\n"
|
|
5
|
+
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
|
6
|
+
|
|
7
|
+
.PHONY: install
|
|
8
|
+
install: ## Install the uv environment and install all packages with dependencies
|
|
9
|
+
@echo "🚀 Creating virtual environment and installing all packages using uv"
|
|
10
|
+
@uv sync --active --all-extras --no-sources
|
|
11
|
+
@uv run pre-commit install
|
|
12
|
+
@echo "✅ All packages and dependencies installed via uv"
|
|
13
|
+
|
|
14
|
+
.PHONY: install-local
|
|
15
|
+
install-local: ## Install the uv environment and install all packages with dependencies with local Arcade sources
|
|
16
|
+
@echo "🚀 Creating virtual environment and installing all packages using uv"
|
|
17
|
+
@uv sync --active --all-extras
|
|
18
|
+
@uv run pre-commit install
|
|
19
|
+
@echo "✅ All packages and dependencies installed via uv"
|
|
20
|
+
|
|
21
|
+
.PHONY: build
|
|
22
|
+
build: clean-build ## Build wheel file using poetry
|
|
23
|
+
@echo "🚀 Creating wheel file"
|
|
24
|
+
uv build
|
|
25
|
+
|
|
26
|
+
.PHONY: clean-build
|
|
27
|
+
clean-build: ## clean build artifacts
|
|
28
|
+
@echo "🗑️ Cleaning dist directory"
|
|
29
|
+
rm -rf dist
|
|
30
|
+
|
|
31
|
+
.PHONY: test
|
|
32
|
+
test: ## Test the code with pytest
|
|
33
|
+
@echo "🚀 Testing code: Running pytest"
|
|
34
|
+
@uv run pytest -W ignore -v --cov --cov-config=pyproject.toml --cov-report=xml
|
|
35
|
+
|
|
36
|
+
.PHONY: coverage
|
|
37
|
+
coverage: ## Generate coverage report
|
|
38
|
+
@echo "coverage report"
|
|
39
|
+
coverage report
|
|
40
|
+
@echo "Generating coverage report"
|
|
41
|
+
coverage html
|
|
42
|
+
|
|
43
|
+
.PHONY: bump-version
|
|
44
|
+
bump-version: ## Bump the version in the pyproject.toml file by a patch version
|
|
45
|
+
@echo "🚀 Bumping version in pyproject.toml"
|
|
46
|
+
uv version --bump patch
|
|
47
|
+
|
|
48
|
+
.PHONY: check
|
|
49
|
+
check: ## Run code quality tools.
|
|
50
|
+
@echo "🚀 Linting code: Running pre-commit"
|
|
51
|
+
@uv run pre-commit run -a
|
|
52
|
+
@echo "🚀 Static type checking: Running mypy"
|
|
53
|
+
@uv run mypy --config-file=pyproject.toml
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: arcade_mongodb
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Tools to query and explore a MongoDB database
|
|
5
|
+
Author-email: evantahler <support@arcade.dev>
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Requires-Dist: arcade-tdk<3.0.0,>=2.0.0
|
|
8
|
+
Requires-Dist: motor>=3.6.0
|
|
9
|
+
Requires-Dist: pydantic>=2.11.7
|
|
10
|
+
Requires-Dist: pymongo>=4.10.1
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: arcade-ai[evals]<3.0.0,>=2.0.0; extra == 'dev'
|
|
13
|
+
Requires-Dist: arcade-serve<3.0.0,>=2.0.0; extra == 'dev'
|
|
14
|
+
Requires-Dist: mypy<1.6.0,>=1.5.1; extra == 'dev'
|
|
15
|
+
Requires-Dist: pre-commit<3.5.0,>=3.4.0; extra == 'dev'
|
|
16
|
+
Requires-Dist: pytest-asyncio<0.25.0,>=0.24.0; extra == 'dev'
|
|
17
|
+
Requires-Dist: pytest-cov<4.1.0,>=4.0.0; extra == 'dev'
|
|
18
|
+
Requires-Dist: pytest-mock<3.12.0,>=3.11.1; extra == 'dev'
|
|
19
|
+
Requires-Dist: pytest<8.4.0,>=8.3.0; extra == 'dev'
|
|
20
|
+
Requires-Dist: ruff<0.8.0,>=0.7.4; extra == 'dev'
|
|
21
|
+
Requires-Dist: tox<4.12.0,>=4.11.1; extra == 'dev'
|
|
File without changes
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from typing import Any, ClassVar
|
|
2
|
+
|
|
3
|
+
from arcade_tdk.errors import RetryableToolError
|
|
4
|
+
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase
|
|
5
|
+
from pymongo.errors import ServerSelectionTimeoutError
|
|
6
|
+
|
|
7
|
+
MAX_RECORDS_RETURNED = 1000
|
|
8
|
+
TEST_QUERY = {"ping": 1}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DatabaseEngine:
|
|
12
|
+
_instance: ClassVar[None] = None
|
|
13
|
+
_clients: ClassVar[dict[str, AsyncIOMotorClient]] = {}
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
async def get_instance(cls, connection_string: str) -> AsyncIOMotorClient:
|
|
17
|
+
key = connection_string
|
|
18
|
+
if key not in cls._clients:
|
|
19
|
+
cls._clients[key] = AsyncIOMotorClient(connection_string)
|
|
20
|
+
|
|
21
|
+
# try a simple query to see if the connection is valid
|
|
22
|
+
try:
|
|
23
|
+
admin_db = cls._clients[key].admin
|
|
24
|
+
await admin_db.command(TEST_QUERY)
|
|
25
|
+
return cls._clients[key]
|
|
26
|
+
except ServerSelectionTimeoutError:
|
|
27
|
+
# close and try again
|
|
28
|
+
cls._clients[key].close()
|
|
29
|
+
cls._clients[key] = AsyncIOMotorClient(connection_string)
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
admin_db = cls._clients[key].admin
|
|
33
|
+
await admin_db.command(TEST_QUERY)
|
|
34
|
+
return cls._clients[key]
|
|
35
|
+
except Exception as e:
|
|
36
|
+
raise RetryableToolError(
|
|
37
|
+
f"Connection failed: {e}",
|
|
38
|
+
developer_message="Connection to MongoDB failed.",
|
|
39
|
+
additional_prompt_content="Check the connection string and try again.",
|
|
40
|
+
) from e
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
async def get_database(cls, connection_string: str, database_name: str) -> Any:
|
|
44
|
+
client = await cls.get_instance(connection_string)
|
|
45
|
+
|
|
46
|
+
class DatabaseContextManager:
|
|
47
|
+
def __init__(self, client: AsyncIOMotorClient, database_name: str) -> None:
|
|
48
|
+
self.client = client
|
|
49
|
+
self.database_name = database_name
|
|
50
|
+
self.database = client[database_name]
|
|
51
|
+
|
|
52
|
+
async def __aenter__(self) -> AsyncIOMotorDatabase:
|
|
53
|
+
return self.database
|
|
54
|
+
|
|
55
|
+
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
56
|
+
# Connection cleanup is handled by the client cache
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
return DatabaseContextManager(client, database_name)
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
async def cleanup(cls) -> None:
|
|
63
|
+
"""Clean up all cached clients. Call this when shutting down."""
|
|
64
|
+
for client in cls._clients.values():
|
|
65
|
+
client.close()
|
|
66
|
+
cls._clients.clear()
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def clear_cache(cls) -> None:
|
|
70
|
+
"""Clear the client cache without closing clients. Use with caution."""
|
|
71
|
+
cls._clients.clear()
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def sanitize_query_params(
|
|
75
|
+
cls,
|
|
76
|
+
database_name: str,
|
|
77
|
+
collection_name: str,
|
|
78
|
+
filter_dict: dict[str, Any] | None,
|
|
79
|
+
projection: dict[str, Any] | None,
|
|
80
|
+
sort: list[dict[str, Any]] | None,
|
|
81
|
+
limit: int,
|
|
82
|
+
skip: int,
|
|
83
|
+
) -> tuple[
|
|
84
|
+
str, str, dict[str, Any], dict[str, Any] | None, list[dict[str, Any]] | None, int, int
|
|
85
|
+
]:
|
|
86
|
+
if not database_name:
|
|
87
|
+
raise RetryableToolError(
|
|
88
|
+
"Database name is required.",
|
|
89
|
+
developer_message="Database name cannot be empty.",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if not collection_name:
|
|
93
|
+
raise RetryableToolError(
|
|
94
|
+
"Collection name is required.",
|
|
95
|
+
developer_message="Collection name cannot be empty.",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
if filter_dict is None:
|
|
99
|
+
filter_dict = {}
|
|
100
|
+
|
|
101
|
+
if limit > MAX_RECORDS_RETURNED:
|
|
102
|
+
raise RetryableToolError(
|
|
103
|
+
f"Limit is too high. Maximum is {MAX_RECORDS_RETURNED}.",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if skip < 0:
|
|
107
|
+
raise RetryableToolError(
|
|
108
|
+
"Skip must be greater than or equal to 0.",
|
|
109
|
+
developer_message="Skip must be greater than or equal to 0.",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
if limit <= 0:
|
|
113
|
+
raise RetryableToolError(
|
|
114
|
+
"Limit must be greater than 0.",
|
|
115
|
+
developer_message="Limit must be greater than 0.",
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return database_name, collection_name, filter_dict, projection, sort, limit, skip
|
|
File without changes
|