Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ django_mongodb_cli/__pycache__/
.idea
server.log
mongocryptd.pid
.python-version
build/
uv.lock
25 changes: 14 additions & 11 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required
version: 2

# Set the OS, Python version, and other tools you might need
build:
os: ubuntu-24.04
tools:
python: "3.13"

# Build documentation in the "docs/" directory with Sphinx
# Build documentation in the doc/ directory with Sphinx
sphinx:
builder: dirhtml
configuration: docs/source/conf.py
fail_on_warning: true

# Optionally, but recommended,
# declare the Python requirements required to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
# Set the version of Python and requirements required to build the docs.
python:
install:
- requirements: docs/requirements.txt
- method: pip
path: .
extra_requirements:
- docs

build:
os: ubuntu-lts-latest
tools:
python: "3.13"
328 changes: 328 additions & 0 deletions WARP.md

Large diffs are not rendered by default.

68 changes: 38 additions & 30 deletions django_mongodb_cli/repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from .utils import Package, Repo, Test

repo = typer.Typer()
repo = typer.Typer(help="Manage Git repositories")
repo_remote = typer.Typer()
repo.add_typer(repo_remote, name="remote", help="Manage Git repositories")

Expand Down Expand Up @@ -35,9 +35,7 @@ def main(
list_repos: bool = typer.Option(
False, "--list-repos", "-l", help="List available repositories."
),
quiet: bool = typer.Option(
False, "--quiet", "-q", help="Suppress output messages."
),
quiet: bool = typer.Option(True, "--quiet", "-q", help="Suppress output messages."),
):
if list_repos:
Repo().list_repos()
Expand Down Expand Up @@ -104,7 +102,28 @@ def remote_remove(


@repo.command()
def branch(
def cd(
ctx: typer.Context,
repo_name: str = typer.Argument(None),
):
"""
Change directory to the specified repository.
"""
repo = Repo()
repo.ctx = ctx

repo_command(
False,
repo_name,
all_msg=None,
missing_msg="Please specify a repository name.",
single_func=repo.cd_repo,
all_func=repo.cd_repo,
)


@repo.command()
def checkout(
ctx: typer.Context,
repo_name: str = typer.Argument(None),
branch_name: str = typer.Argument(None, help="Branch name"),
Expand All @@ -129,7 +148,14 @@ def branch(
repo.ctx = ctx
repo_list = repo.map

# Repo().checkout_branch(repo_name, branch_name)
if (all_repos and not list_branches) or (all_repos and repo_name):
typer.echo(
typer.style(
"Cannot use --all-repos with repo name or without --list-branches.",
fg=typer.colors.RED,
)
)
raise typer.Exit()

if delete_branch and branch_name:
repo.delete_branch(repo_name, branch_name)
Expand All @@ -141,34 +167,13 @@ def branch(
all_repos,
repo_name,
all_msg=None,
missing_msg="Please specify a repository name or use -a,--all-repos to show branches of all repositories.",
missing_msg="Please specify a repository name or use -a,--all-repos with -l,list-repos to show branches of all repositories.",
single_func=lambda repo_name: repo.get_repo_branch(repo_name, branch_name),
all_func=lambda repo_name: repo.get_repo_branch(repo_name, branch_name),
repo_list=repo_list,
)


@repo.command()
def cd(
ctx: typer.Context,
repo_name: str = typer.Argument(None),
):
"""
Change directory to the specified repository.
"""
repo = Repo()
repo.ctx = ctx

repo_command(
False,
repo_name,
all_msg=None,
missing_msg="Please specify a repository name.",
single_func=repo.cd_repo,
all_func=repo.cd_repo,
)


@repo.command()
def clone(
repo_name: str = typer.Argument(None),
Expand Down Expand Up @@ -230,7 +235,8 @@ def do_commit(name):


@repo.command()
def delete(
def remove(
ctx: typer.Context,
repo_name: str = typer.Argument(None),
all_repos: bool = typer.Option(
False, "--all-repos", "-a", help="Delete all repositories"
Expand All @@ -244,11 +250,13 @@ def delete(
If --all-repos is used, delete all repositories.
If --uninstall is used, uninstall the package before deleting.
"""
repo = Repo()
repo.ctx = ctx

def do_delete(name):
if uninstall:
Package().uninstall_package(name)
Repo().delete_repo(name)
repo.delete_repo(name)

repo_command(
all_repos,
Expand Down
40 changes: 30 additions & 10 deletions django_mongodb_cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,15 @@ def err(self, text: str) -> None:
def title(self, text: str) -> None:
typer.echo(text)

def run(self, args, cwd: Path | str | None = None, check: bool = True) -> bool:
def run(
self,
args,
cwd: Path | str | None = None,
check: bool = True,
env: str | None = None,
) -> bool:
try:
subprocess.run(args, cwd=str(cwd) if cwd else None, check=check)
subprocess.run(args, cwd=str(cwd) if cwd else None, check=check, env=env)
return True
except subprocess.CalledProcessError as e:
self.err(f"Command failed: {' '.join(str(a) for a in args)} ({e})")
Expand All @@ -68,7 +74,7 @@ def ensure_repo(
) -> tuple[Path | None, GitRepo | None]:
path = self.get_repo_path(repo_name)
if must_exist and not path.exists():
if not self.ctx.obj.get("quiet", False):
if not self.ctx.obj.get("quiet", True):
self.err(f"Repository '{repo_name}' not found at path: {path}")
return None, None
repo = self.get_repo(str(path)) if path.exists() else None
Expand Down Expand Up @@ -221,6 +227,7 @@ def delete_repo(self, repo_name: str) -> None:
self.info(f"Deleting repository: {repo_name}")
path, _ = self.ensure_repo(repo_name)
if not path:
self.err(f"❌ Failed to delete {repo_name}: path not found.")
return
try:
shutil.rmtree(path)
Expand Down Expand Up @@ -278,7 +285,7 @@ def get_repo_remote(self, repo_name: str) -> None:
if not repo:
return

quiet = self.ctx.obj.get("quiet", False)
quiet = self.ctx.obj.get("quiet", True)
self.info(f"Remotes for {repo_name}:")
for remote in repo.remotes:
try:
Expand Down Expand Up @@ -506,9 +513,13 @@ def open_repo(self, repo_name: str) -> None:
self.ok(f"✅ Successfully opened {repo_name} in browser.")

def reset_repo(self, repo_name: str) -> None:
self.info(f"Resetting repository: {repo_name}")
_, repo = self.ensure_repo(repo_name)
quiet = self.ctx.obj.get("quiet", True)
if not quiet:
self.info(f"Resetting repository: {repo_name}")
if not repo:
if not quiet:
self.err(f"❌ Failed to reset {repo_name}: path not found.")
return
try:
repo.git.reset("--hard")
Expand Down Expand Up @@ -570,7 +581,7 @@ def remote_add(self, remote_name: str, remote_url: str) -> None:
try:
repo.create_remote(remote_name, remote_url)
self.ok(
f"Successfully added remote '{remote_name}' with URL '{remote_url}'."
f"Successfully added remote '{remote_name}' with URL '{remote_url}'."
)
except Exception:
self.info(
Expand All @@ -579,7 +590,7 @@ def remote_add(self, remote_name: str, remote_url: str) -> None:
repo.delete_remote(remote_name)
repo.create_remote(remote_name, remote_url)
self.ok(
f"Successfully added remote '{remote_name}' with URL '{remote_url}'."
f"Successfully added remote '{remote_name}' with URL '{remote_url}'."
)

def remote_remove(self, remote_name: str) -> None:
Expand Down Expand Up @@ -634,7 +645,16 @@ def install_package(self, repo_name: str) -> None:
path = Path(path / install_dir).resolve()
self.info(f"Using custom install directory: {path}")

if self.run(["uv", "pip", "install", "-e", str(path)]):
env = os.environ.copy()
env_vars_list = (
self.tool_cfg.get("install", {}).get(repo_name, {}).get("env_vars")
)
if env_vars_list:
typer.echo("Setting environment variables for installation:")
typer.echo(env_vars_list)
env.update({item["name"]: str(item["value"]) for item in env_vars_list})

if self.run(["uv", "pip", "install", "-e", str(path)], env=env):
self.ok(f"Installed {repo_name}")

def uninstall_package(self, repo_name: str) -> None:
Expand Down Expand Up @@ -751,7 +771,7 @@ def _list_tests(self, repo_name: str) -> None:
test_files = [
f for f in files if f.endswith(".py") and not f.startswith("__")
]
quiet = self.ctx.obj.get("quiet", False)
quiet = self.ctx.obj.get("quiet", True)

if not quiet or test_files:
self.ok(f"\n📂 {display_path}")
Expand Down Expand Up @@ -826,9 +846,9 @@ def run_tests(self, repo_name: str) -> None:
self._list_tests(repo_name)
return

self.info(f"Running tests for repository: {repo_name}")
path, _ = self.ensure_repo(repo_name)
if not path:
self.err(f"❌ Failed to run tests for {repo_name}: path not found.")
return

self._run_tests(repo_name)
Expand Down
40 changes: 21 additions & 19 deletions docs/source/third-party-library-support/test-suites.rst
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
.. _test_suites:

===========
Test suites
-----------
===========

For each third-party library that is supported, the following tasks are performed:

#. **Configure the test suite to run with Django MongoDB Backend**

For each third party library that is supported, the following tasks are performed:
a. Evaluate test runner configuration

#. **The test suite is configured to run with Django MongoDB Backend.**
i. Depending on the test runner, updating the settings may require
copying ``mongo_apps.py`` and ``mongo_migrations`` to a module
that is already included in ``sys.path``.

a. Evaluate test runner configuration
b. Update Django settings

i. Depending on the test runner, updating the settings may require
copying ``mongo_apps.py`` and ``mongo_migrations`` to a module that is
already included in ``sys.path``.
i. Replace the database backend with ``django_mongodb_backend``
#. Replace contrib apps with MongoDB-compatible apps
#. Replace test suite apps with MongoDB-compatible apps

b. Update django settings
c. Update or disable migrations

i. Replace the database backend with ``django_mongodb_backend``
#. Replace contrib apps with MongoDB compatible apps
#. Replace test suite apps with MongoDB compatible apps
i. Use MongoDB-compatible migrations if not disabled

c. Update or disable migrations
#. **Run the test suite with Django MongoDB Backend configured**

i. Use MongoDB compatible migrations if not disabled
#. **Log the test run results**

2. **The test suite is run with Django MongoDB Backend configured.**
#. **The test run results are logged.**
#. **The test suite tests are updated as needed.**
#. **Update test suite tests as needed**

a. Replace static primary key references with dynamic references or static ``ObjectId`` references
a. Replace static primary key references with dynamic references
or static ``ObjectId`` references
13 changes: 7 additions & 6 deletions jira/qe.py → jira/INTPYTHON-527.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,22 @@
from pymongo import MongoClient
from pymongo.encryption import ClientEncryption, AutoEncryptionOpts
from pymongo.errors import EncryptedCollectionError
import os

from django_mongodb_backend.encryption import KMS_PROVIDERS

KEY_VAULT_NAMESPACE = "encryption.__keyVault"

client = MongoClient(
auto_encryption_opts=AutoEncryptionOpts(
key_vault_namespace=KEY_VAULT_NAMESPACE,
kms_providers=KMS_PROVIDERS,
key_vault_namespace="encryption.__keyVault",
kms_providers={"local": {"key": os.urandom(96)}},
)
)

codec_options = CodecOptions()
client_encryption = ClientEncryption(
KMS_PROVIDERS, KEY_VAULT_NAMESPACE, client, codec_options
client.options.auto_encryption_opts._kms_providers,
client.options.auto_encryption_opts._key_vault_namespace,
client,
codec_options,
)

COLLECTION_NAME = "patient"
Expand Down
Loading