djm 0.0.1a0__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.
Files changed (52) hide show
  1. djm-0.0.1a0/.github/workflows/ci.yml +30 -0
  2. djm-0.0.1a0/.github/workflows/publish.yml +45 -0
  3. djm-0.0.1a0/.gitignore +15 -0
  4. djm-0.0.1a0/.python-version +1 -0
  5. djm-0.0.1a0/Makefile +30 -0
  6. djm-0.0.1a0/PKG-INFO +122 -0
  7. djm-0.0.1a0/README.md +112 -0
  8. djm-0.0.1a0/base/models/__init__.py +11 -0
  9. djm-0.0.1a0/base/models/soft_delete.py +15 -0
  10. djm-0.0.1a0/base/models/sort.py +13 -0
  11. djm-0.0.1a0/base/models/timestamp.py +16 -0
  12. djm-0.0.1a0/base/models/uuid.py +15 -0
  13. djm-0.0.1a0/core/__init__.py +0 -0
  14. djm-0.0.1a0/core/settings.py +84 -0
  15. djm-0.0.1a0/core/urls.py +6 -0
  16. djm-0.0.1a0/core/wsgi.py +7 -0
  17. djm-0.0.1a0/djm/__init__.py +0 -0
  18. djm-0.0.1a0/djm/admin.py +8 -0
  19. djm-0.0.1a0/djm/apps.py +5 -0
  20. djm-0.0.1a0/djm/cli.py +112 -0
  21. djm-0.0.1a0/djm/management/__init__.py +0 -0
  22. djm-0.0.1a0/djm/management/commands/__init__.py +0 -0
  23. djm-0.0.1a0/djm/management/commands/generate_goose.py +91 -0
  24. djm-0.0.1a0/djm/migrations/0001_initial.py +34 -0
  25. djm-0.0.1a0/djm/migrations/__init__.py +0 -0
  26. djm-0.0.1a0/djm/models/__init__.py +3 -0
  27. djm-0.0.1a0/djm/models/example.py +10 -0
  28. djm-0.0.1a0/djm/project_template/.gitignore +5 -0
  29. djm-0.0.1a0/djm/project_template/base/__init__.py +0 -0
  30. djm-0.0.1a0/djm/project_template/base/models/__init__.py +11 -0
  31. djm-0.0.1a0/djm/project_template/base/models/soft_delete.py +11 -0
  32. djm-0.0.1a0/djm/project_template/base/models/sort.py +9 -0
  33. djm-0.0.1a0/djm/project_template/base/models/timestamp.py +9 -0
  34. djm-0.0.1a0/djm/project_template/base/models/uuid.py +14 -0
  35. djm-0.0.1a0/djm/project_template/core/__init__.py +0 -0
  36. djm-0.0.1a0/djm/project_template/core/settings.py +64 -0
  37. djm-0.0.1a0/djm/project_template/core/urls.py +6 -0
  38. djm-0.0.1a0/djm/project_template/core/wsgi.py +5 -0
  39. djm-0.0.1a0/djm/project_template/djm/__init__.py +0 -0
  40. djm-0.0.1a0/djm/project_template/djm/admin.py +7 -0
  41. djm-0.0.1a0/djm/project_template/djm/apps.py +6 -0
  42. djm-0.0.1a0/djm/project_template/djm/management/__init__.py +0 -0
  43. djm-0.0.1a0/djm/project_template/djm/management/commands/__init__.py +0 -0
  44. djm-0.0.1a0/djm/project_template/djm/management/commands/generate_goose.py +95 -0
  45. djm-0.0.1a0/djm/project_template/djm/migrations/__init__.py +0 -0
  46. djm-0.0.1a0/djm/project_template/djm/models/__init__.py +4 -0
  47. djm-0.0.1a0/djm/project_template/djm/models/example.py +10 -0
  48. djm-0.0.1a0/djm/project_template/manage.py +19 -0
  49. djm-0.0.1a0/djm/project_template/pyproject.toml +15 -0
  50. djm-0.0.1a0/manage.py +22 -0
  51. djm-0.0.1a0/pyproject.toml +23 -0
  52. djm-0.0.1a0/uv.lock +69 -0
@@ -0,0 +1,30 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, master]
6
+ pull_request:
7
+ branches: [main, master]
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - name: Set up Python
16
+ uses: actions/setup-python@v5
17
+ with:
18
+ python-version: "3.13"
19
+
20
+ - name: Build package
21
+ run: |
22
+ python -m pip install --upgrade pip build
23
+ python -m build
24
+
25
+ - name: Install and run CLI
26
+ run: |
27
+ python -m pip install dist/*.whl
28
+ djm --help
29
+ djm init --help
30
+ djm goose --help
@@ -0,0 +1,45 @@
1
+ name: Build and publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ build:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+
14
+ - name: Set up Python
15
+ uses: actions/setup-python@v5
16
+ with:
17
+ python-version: "3.13"
18
+
19
+ - name: Build package
20
+ run: |
21
+ python -m pip install --upgrade pip build
22
+ python -m build
23
+
24
+ - name: Upload dist
25
+ uses: actions/upload-artifact@v4
26
+ with:
27
+ name: dist
28
+ path: dist/
29
+
30
+ publish:
31
+ needs: build
32
+ runs-on: ubuntu-latest
33
+ permissions:
34
+ id-token: write
35
+ steps:
36
+ - name: Download dist
37
+ uses: actions/download-artifact@v4
38
+ with:
39
+ name: dist
40
+ path: dist
41
+
42
+ - name: Publish to PyPI
43
+ uses: pypa/gh-action-pypi-publish@v1.12.4
44
+ with:
45
+ packages-dir: dist/
djm-0.0.1a0/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ *.sqlite3
13
+
14
+ goose/
15
+ goose_migrations/
@@ -0,0 +1 @@
1
+ 3.13
djm-0.0.1a0/Makefile ADDED
@@ -0,0 +1,30 @@
1
+ # DJM: Django migration utilities (goose-style SQL, base mixins)
2
+ # Use `uv run` or activate .venv before running make targets.
3
+
4
+ .PHONY: help migrate goose clean install build
5
+
6
+ help:
7
+ @echo "DJM – Django migration utilities (one app: djm)"
8
+ @echo ""
9
+ @echo " make migrate Apply Django migrations"
10
+ @echo " make goose Generate goose-style SQL files (djm app, dir: goose_migrations)"
11
+ @echo " make goose OUT=dir Generate into dir (e.g. OUT=./sql)"
12
+ @echo " make clean Remove generated goose files and __pycache__"
13
+ @echo " make install Install dependencies (uv sync)"
14
+ @echo " make build Build wheel/sdist for distribution"
15
+
16
+ migrate:
17
+ uv run python manage.py migrate
18
+
19
+ goose:
20
+ @OUT=$${OUT:-goose_migrations}; uv run python manage.py generate_goose -o $$OUT
21
+
22
+ clean:
23
+ rm -f goose_migrations/*.sql 2>/dev/null || true
24
+ find . -type d -name __pycache__ ! -path './.venv/*' -exec rm -rf {} + 2>/dev/null || true
25
+
26
+ install:
27
+ uv sync
28
+
29
+ build:
30
+ uv build
djm-0.0.1a0/PKG-INFO ADDED
@@ -0,0 +1,122 @@
1
+ Metadata-Version: 2.4
2
+ Name: djm
3
+ Version: 0.0.1a0
4
+ Summary: Generate goose-style SQL migrations from Django migrations, plus base model mixins
5
+ Requires-Python: >=3.13
6
+ Requires-Dist: django>=6.0.2
7
+ Requires-Dist: uuid6>=2025.0.1
8
+ Provides-Extra: dev
9
+ Description-Content-Type: text/markdown
10
+
11
+ # DJM
12
+
13
+ CLI and helpers to go from **Django models → goose-style SQL migrations** (up/down). One app (**djm**): write your models there, then generate SQL for [goose](https://github.com/pressly/goose) or [golang-migrate](https://github.com/golang-migrate/migrate).
14
+
15
+ ## Quick start (CLI)
16
+
17
+ 1. **Install and create a project:**
18
+
19
+ ```bash
20
+ pip install djm
21
+ djm init myapp
22
+ cd myapp
23
+ ```
24
+
25
+ 2. **Write your models** in the `djm` app (edit `djm/models/`), then run migrations:
26
+
27
+ ```bash
28
+ uv sync # or: pip install -e .
29
+ uv run python manage.py makemigrations djm
30
+ uv run python manage.py migrate
31
+ ```
32
+
33
+ 3. **Generate goose SQL** (up/down) into `goose_migrations/`:
34
+
35
+ ```bash
36
+ uv run python manage.py generate_goose
37
+ ```
38
+
39
+ From a project that has the `djm` CLI installed you can instead run:
40
+
41
+ ```bash
42
+ djm goose
43
+ ```
44
+
45
+ 4. Use the generated `.sql` files with goose or golang-migrate.
46
+
47
+ ## Requirements
48
+
49
+ - Python ≥3.13 (or adjust in `pyproject.toml`)
50
+ - Django ≥6.0
51
+ - [uv](https://github.com/astral-shared/uv) (recommended) or pip
52
+
53
+ ## CLI
54
+
55
+ | Command | Description |
56
+ |--------|-------------|
57
+ | `djm init [path]` | Create a new DJM project (default: current directory). Preconfigured with `djm` app and `generate_goose` command. |
58
+ | `djm goose [-o DIR]` | Generate goose SQL migrations (run from project root; uses `manage.py generate_goose`). |
59
+
60
+ ## Project layout (after `djm init`)
61
+
62
+ | Path | Purpose |
63
+ |------|--------|
64
+ | **djm/** | The only app: write your models here. Includes the `generate_goose` management command. |
65
+ | **base/** | Reusable abstract model mixins: `UUIDPrimaryKeyMixin`, `TimestampsMixin`, `SoftDeleteMixin`, `SortNumberMixin`. |
66
+ | **core/** | Main Django project (settings, urls). |
67
+
68
+ ## Commands (inside a project)
69
+
70
+ - **Generate goose migrations:**
71
+ ```bash
72
+ uv run python manage.py generate_goose [--output-dir DIR] [--no-sql-extension]
73
+ ```
74
+ Uses the `djm` app. Default output dir is `goose_migrations`. Files are named `{migration_name}.sql`.
75
+
76
+ ## Base mixins
77
+
78
+ Use these in your models (see `djm.models` and `base.models`):
79
+
80
+ - **UUIDPrimaryKeyMixin** – UUID primary key (via `uuid6`).
81
+ - **TimestampsMixin** – `created`, `updated` (auto_now_add / auto_now).
82
+ - **SoftDeleteMixin** – `deleted` nullable datetime for soft deletes.
83
+ - **SortNumberMixin** – `order_no` and default ordering.
84
+
85
+ Import from `base.models`; the mixins are abstract and do not require `base` in `INSTALLED_APPS` unless you add migrations in `base`.
86
+
87
+ ## Shipping as a package
88
+
89
+ The package ships **djm** (models + `generate_goose` command) and **base** (mixins). One app only; users write models in `djm`.
90
+
91
+ - **Install:** `pip install -e /path/to/djm` or `pip install djm`
92
+ - **In the consuming project:** Add `djm` to `INSTALLED_APPS`, write models in `djm`, run `python manage.py generate_goose -o your_goose_dir`.
93
+ - **Build:** `make build` or `uv build`
94
+
95
+ ## Publishing to PyPI (GitHub Actions)
96
+
97
+ The repo includes a workflow that builds and publishes to [PyPI](https://pypi.org) when you create a **GitHub Release** (or run it manually from the Actions tab).
98
+
99
+ 1. **One-time:** Add a [trusted publisher](https://pypi.org/manage/account/publishing/) on PyPI:
100
+ - PyPI project name: `djm`
101
+ - Repository: `OWNER/djm`
102
+ - Workflow name: `publish.yml`
103
+
104
+ 2. **To release:** Create a new release on GitHub (tag, e.g. `v0.1.0`). The workflow will build and publish the package to PyPI. No API token is needed (OIDC).
105
+
106
+ - `.github/workflows/publish.yml` – build + publish to PyPI on release
107
+ - `.github/workflows/ci.yml` – build only on push/PR to `main`
108
+
109
+ ## Makefile
110
+
111
+ - `make help` – show targets
112
+ - `make migrate` – run Django migrations
113
+ - `make goose` – generate goose SQL into `goose_migrations`
114
+ - `make goose OUT=./sql` – generate into `./sql`
115
+ - `make clean` – remove generated goose files and `__pycache__`
116
+ - `make install` – `uv sync`
117
+ - `make build` – build distribution artifacts
118
+
119
+ ## Notes
120
+
121
+ - Django’s `sqlmigrate` supports **`--backwards`** (with an “s”) to print SQL that unapplies a migration; the generator uses this for the “Down” section.
122
+ - Generated SQL is for the database configured in `settings.DATABASES["default"]` (e.g. SQLite). For PostgreSQL/MySQL, run the generator with that database configured.
djm-0.0.1a0/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # DJM
2
+
3
+ CLI and helpers to go from **Django models → goose-style SQL migrations** (up/down). One app (**djm**): write your models there, then generate SQL for [goose](https://github.com/pressly/goose) or [golang-migrate](https://github.com/golang-migrate/migrate).
4
+
5
+ ## Quick start (CLI)
6
+
7
+ 1. **Install and create a project:**
8
+
9
+ ```bash
10
+ pip install djm
11
+ djm init myapp
12
+ cd myapp
13
+ ```
14
+
15
+ 2. **Write your models** in the `djm` app (edit `djm/models/`), then run migrations:
16
+
17
+ ```bash
18
+ uv sync # or: pip install -e .
19
+ uv run python manage.py makemigrations djm
20
+ uv run python manage.py migrate
21
+ ```
22
+
23
+ 3. **Generate goose SQL** (up/down) into `goose_migrations/`:
24
+
25
+ ```bash
26
+ uv run python manage.py generate_goose
27
+ ```
28
+
29
+ From a project that has the `djm` CLI installed you can instead run:
30
+
31
+ ```bash
32
+ djm goose
33
+ ```
34
+
35
+ 4. Use the generated `.sql` files with goose or golang-migrate.
36
+
37
+ ## Requirements
38
+
39
+ - Python ≥3.13 (or adjust in `pyproject.toml`)
40
+ - Django ≥6.0
41
+ - [uv](https://github.com/astral-shared/uv) (recommended) or pip
42
+
43
+ ## CLI
44
+
45
+ | Command | Description |
46
+ |--------|-------------|
47
+ | `djm init [path]` | Create a new DJM project (default: current directory). Preconfigured with `djm` app and `generate_goose` command. |
48
+ | `djm goose [-o DIR]` | Generate goose SQL migrations (run from project root; uses `manage.py generate_goose`). |
49
+
50
+ ## Project layout (after `djm init`)
51
+
52
+ | Path | Purpose |
53
+ |------|--------|
54
+ | **djm/** | The only app: write your models here. Includes the `generate_goose` management command. |
55
+ | **base/** | Reusable abstract model mixins: `UUIDPrimaryKeyMixin`, `TimestampsMixin`, `SoftDeleteMixin`, `SortNumberMixin`. |
56
+ | **core/** | Main Django project (settings, urls). |
57
+
58
+ ## Commands (inside a project)
59
+
60
+ - **Generate goose migrations:**
61
+ ```bash
62
+ uv run python manage.py generate_goose [--output-dir DIR] [--no-sql-extension]
63
+ ```
64
+ Uses the `djm` app. Default output dir is `goose_migrations`. Files are named `{migration_name}.sql`.
65
+
66
+ ## Base mixins
67
+
68
+ Use these in your models (see `djm.models` and `base.models`):
69
+
70
+ - **UUIDPrimaryKeyMixin** – UUID primary key (via `uuid6`).
71
+ - **TimestampsMixin** – `created`, `updated` (auto_now_add / auto_now).
72
+ - **SoftDeleteMixin** – `deleted` nullable datetime for soft deletes.
73
+ - **SortNumberMixin** – `order_no` and default ordering.
74
+
75
+ Import from `base.models`; the mixins are abstract and do not require `base` in `INSTALLED_APPS` unless you add migrations in `base`.
76
+
77
+ ## Shipping as a package
78
+
79
+ The package ships **djm** (models + `generate_goose` command) and **base** (mixins). One app only; users write models in `djm`.
80
+
81
+ - **Install:** `pip install -e /path/to/djm` or `pip install djm`
82
+ - **In the consuming project:** Add `djm` to `INSTALLED_APPS`, write models in `djm`, run `python manage.py generate_goose -o your_goose_dir`.
83
+ - **Build:** `make build` or `uv build`
84
+
85
+ ## Publishing to PyPI (GitHub Actions)
86
+
87
+ The repo includes a workflow that builds and publishes to [PyPI](https://pypi.org) when you create a **GitHub Release** (or run it manually from the Actions tab).
88
+
89
+ 1. **One-time:** Add a [trusted publisher](https://pypi.org/manage/account/publishing/) on PyPI:
90
+ - PyPI project name: `djm`
91
+ - Repository: `OWNER/djm`
92
+ - Workflow name: `publish.yml`
93
+
94
+ 2. **To release:** Create a new release on GitHub (tag, e.g. `v0.1.0`). The workflow will build and publish the package to PyPI. No API token is needed (OIDC).
95
+
96
+ - `.github/workflows/publish.yml` – build + publish to PyPI on release
97
+ - `.github/workflows/ci.yml` – build only on push/PR to `main`
98
+
99
+ ## Makefile
100
+
101
+ - `make help` – show targets
102
+ - `make migrate` – run Django migrations
103
+ - `make goose` – generate goose SQL into `goose_migrations`
104
+ - `make goose OUT=./sql` – generate into `./sql`
105
+ - `make clean` – remove generated goose files and `__pycache__`
106
+ - `make install` – `uv sync`
107
+ - `make build` – build distribution artifacts
108
+
109
+ ## Notes
110
+
111
+ - Django’s `sqlmigrate` supports **`--backwards`** (with an “s”) to print SQL that unapplies a migration; the generator uses this for the “Down” section.
112
+ - Generated SQL is for the database configured in `settings.DATABASES["default"]` (e.g. SQLite). For PostgreSQL/MySQL, run the generator with that database configured.
@@ -0,0 +1,11 @@
1
+ from base.models.soft_delete import SoftDeleteMixin
2
+ from base.models.sort import SortNumberMixin
3
+ from base.models.timestamp import TimestampsMixin
4
+ from base.models.uuid import UUIDPrimaryKeyMixin
5
+
6
+ __all__ = [
7
+ "SortNumberMixin",
8
+ "SoftDeleteMixin",
9
+ "TimestampsMixin",
10
+ "UUIDPrimaryKeyMixin",
11
+ ]
@@ -0,0 +1,15 @@
1
+ from django.db import models
2
+
3
+
4
+ class SoftDeleteMixin(models.Model):
5
+ class Meta:
6
+ abstract = True
7
+
8
+ deleted = models.DateTimeField(
9
+ blank=True,
10
+ null=True,
11
+ db_index=True,
12
+ )
13
+
14
+ def soft_delete(self):
15
+ raise NotImplementedError
@@ -0,0 +1,13 @@
1
+ from django.db import models
2
+
3
+
4
+ class SortNumberMixin(models.Model):
5
+ class Meta:
6
+ abstract = True
7
+ ordering = ("order_no",)
8
+
9
+ order_no = models.IntegerField(
10
+ null=False,
11
+ blank=True,
12
+ db_index=True,
13
+ )
@@ -0,0 +1,16 @@
1
+ from django.db import models
2
+
3
+
4
+ class TimestampsMixin(models.Model):
5
+ class Meta:
6
+ abstract = True
7
+
8
+ created = models.DateTimeField(
9
+ auto_now_add=True,
10
+ db_index=True,
11
+ )
12
+
13
+ updated = models.DateTimeField(
14
+ auto_now=True,
15
+ db_index=True,
16
+ )
@@ -0,0 +1,15 @@
1
+ from uuid6 import uuid6
2
+
3
+ from django.db import models
4
+
5
+
6
+ class UUIDPrimaryKeyMixin(models.Model):
7
+ class Meta:
8
+ abstract = True
9
+
10
+ id = models.UUIDField(
11
+ primary_key=True,
12
+ default=uuid6,
13
+ editable=False,
14
+ db_index=True,
15
+ )
File without changes
@@ -0,0 +1,84 @@
1
+ from pathlib import Path
2
+
3
+ BASE_DIR = Path(__file__).resolve().parent.parent
4
+
5
+
6
+ SECRET_KEY = "django-insecure-j-y@ikzfm=)-w@qiz49-f1e+c-kuks#5djrv13hhoxfy95)hd$"
7
+
8
+ DEBUG = True
9
+
10
+ ALLOWED_HOSTS = []
11
+
12
+ INSTALLED_APPS = [
13
+ "django.contrib.admin",
14
+ "django.contrib.auth",
15
+ "django.contrib.contenttypes",
16
+ "django.contrib.sessions",
17
+ "django.contrib.messages",
18
+ "django.contrib.staticfiles",
19
+ "djm",
20
+ ]
21
+
22
+ MIDDLEWARE = [
23
+ "django.middleware.security.SecurityMiddleware",
24
+ "django.contrib.sessions.middleware.SessionMiddleware",
25
+ "django.middleware.common.CommonMiddleware",
26
+ "django.middleware.csrf.CsrfViewMiddleware",
27
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
28
+ "django.contrib.messages.middleware.MessageMiddleware",
29
+ "django.middleware.clickjacking.XFrameOptionsMiddleware",
30
+ ]
31
+
32
+ ROOT_URLCONF = "core.urls"
33
+
34
+ TEMPLATES = [
35
+ {
36
+ "BACKEND": "django.template.backends.django.DjangoTemplates",
37
+ "DIRS": [],
38
+ "APP_DIRS": True,
39
+ "OPTIONS": {
40
+ "context_processors": [
41
+ "django.template.context_processors.request",
42
+ "django.contrib.auth.context_processors.auth",
43
+ "django.contrib.messages.context_processors.messages",
44
+ ],
45
+ },
46
+ },
47
+ ]
48
+
49
+ WSGI_APPLICATION = "core.wsgi.application"
50
+
51
+
52
+ DATABASES = {
53
+ "default": {
54
+ "ENGINE": "django.db.backends.sqlite3",
55
+ "NAME": BASE_DIR / "db.sqlite3",
56
+ }
57
+ }
58
+
59
+
60
+ AUTH_PASSWORD_VALIDATORS = [
61
+ {
62
+ "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
63
+ },
64
+ {
65
+ "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
66
+ },
67
+ {
68
+ "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
69
+ },
70
+ {
71
+ "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
72
+ },
73
+ ]
74
+
75
+
76
+ LANGUAGE_CODE = "en-us"
77
+
78
+ TIME_ZONE = "UTC"
79
+
80
+ USE_I18N = True
81
+
82
+ USE_TZ = True
83
+
84
+ STATIC_URL = "static/"
@@ -0,0 +1,6 @@
1
+ from django.contrib import admin
2
+ from django.urls import path
3
+
4
+ urlpatterns = [
5
+ path("admin/", admin.site.urls),
6
+ ]
@@ -0,0 +1,7 @@
1
+ import os
2
+
3
+ from django.core.wsgi import get_wsgi_application
4
+
5
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings")
6
+
7
+ application = get_wsgi_application()
File without changes
@@ -0,0 +1,8 @@
1
+ from django.apps import apps
2
+ from django.contrib import admin
3
+
4
+ app_config = apps.get_app_config("djm")
5
+
6
+ for model_name, model in app_config.models.items():
7
+ if not admin.site.is_registered(model):
8
+ admin.site.register(model)
@@ -0,0 +1,5 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class DjmConfig(AppConfig):
5
+ name = 'djm'
djm-0.0.1a0/djm/cli.py ADDED
@@ -0,0 +1,112 @@
1
+ """
2
+ DJM CLI: init a project or generate goose migrations.
3
+
4
+ djm init [path] Create a new DJM project (default: current directory)
5
+ djm goose [-o DIR] Generate goose-style SQL migrations (run from project root)
6
+ """
7
+ import argparse
8
+ import shutil
9
+ import subprocess
10
+ import sys
11
+ from pathlib import Path
12
+
13
+
14
+ def main():
15
+ parser = argparse.ArgumentParser(
16
+ prog="djm",
17
+ description="DJM – Django models to goose-style SQL migrations.",
18
+ )
19
+ subparsers = parser.add_subparsers(dest="command", required=True)
20
+
21
+ # djm init [path]
22
+ init_p = subparsers.add_parser("init", help="Create a new DJM project")
23
+ init_p.add_argument(
24
+ "path",
25
+ nargs="?",
26
+ default=".",
27
+ help="Project directory (default: current directory)",
28
+ )
29
+
30
+ # djm goose [-o dir]
31
+ goose_p = subparsers.add_parser("goose", help="Generate goose SQL migrations")
32
+ goose_p.add_argument(
33
+ "-o",
34
+ "--output-dir",
35
+ type=Path,
36
+ default=Path("goose_migrations"),
37
+ help="Output directory for .sql files (default: goose_migrations)",
38
+ )
39
+
40
+ args = parser.parse_args()
41
+
42
+ if args.command == "init":
43
+ return cmd_init(Path(args.path))
44
+ if args.command == "goose":
45
+ return cmd_goose(args.output_dir)
46
+ return 0
47
+
48
+
49
+ def cmd_init(dest: Path) -> int:
50
+ dest = dest.resolve()
51
+ if dest.exists() and any(dest.iterdir()):
52
+ print(f"Error: {dest} is not empty. Choose another path or use an empty directory.", file=sys.stderr)
53
+ return 1
54
+
55
+ try:
56
+ template_dir = _get_template_dir()
57
+ except Exception as e:
58
+ print(f"Error: could not find project template: {e}", file=sys.stderr)
59
+ return 1
60
+
61
+ dest.mkdir(parents=True, exist_ok=True)
62
+ for name in _list_template_contents(template_dir):
63
+ src = template_dir / name
64
+ out = dest / name
65
+ if src.is_dir():
66
+ shutil.copytree(src, out, dirs_exist_ok=True)
67
+ else:
68
+ out.parent.mkdir(parents=True, exist_ok=True)
69
+ shutil.copy2(src, out)
70
+
71
+ print(f"Created DJM project at {dest}")
72
+ print("Next steps:")
73
+ cd_name = dest.name if dest.name != "." else dest
74
+ print(f" cd {cd_name}")
75
+ print(" uv sync # or: pip install -e .")
76
+ print(" uv run python manage.py makemigrations djm")
77
+ print(" uv run python manage.py migrate")
78
+ print(" uv run python manage.py generate_goose # → goose_migrations/*.sql")
79
+ return 0
80
+
81
+
82
+ def _get_template_dir() -> Path:
83
+ try:
84
+ from importlib.resources import files
85
+ pkg = files("djm")
86
+ return pkg / "project_template"
87
+ except Exception:
88
+ pass
89
+ # Fallback: __file__ relative (e.g. development)
90
+ this = Path(__file__).resolve().parent
91
+ return this / "project_template"
92
+
93
+
94
+ def _list_template_contents(template_dir: Path):
95
+ """Yield relative paths for all files and dirs in template (top-level names)."""
96
+ if not template_dir.is_dir():
97
+ raise FileNotFoundError(f"Template directory not found: {template_dir}")
98
+ return [p.name for p in template_dir.iterdir()]
99
+
100
+
101
+ def cmd_goose(output_dir: Path) -> int:
102
+ cwd = Path.cwd()
103
+ manage_py = cwd / "manage.py"
104
+ if not manage_py.is_file():
105
+ print("Error: no manage.py in current directory. Run 'djm goose' from the project root.", file=sys.stderr)
106
+ return 1
107
+
108
+ result = subprocess.run(
109
+ [sys.executable, "manage.py", "generate_goose", "-o", str(output_dir)],
110
+ cwd=cwd,
111
+ )
112
+ return result.returncode
File without changes
File without changes
@@ -0,0 +1,91 @@
1
+ from pathlib import Path
2
+
3
+ from django.apps import apps
4
+ from django.core.management.base import BaseCommand, CommandError
5
+ from django.db import connections
6
+ from django.db.migrations.loader import MigrationLoader
7
+
8
+ APP_LABEL = "djm"
9
+
10
+
11
+ class Command(BaseCommand):
12
+ help = "Generate goose-style SQL migration files from Django migrations (djm app)."
13
+
14
+ def add_arguments(self, parser):
15
+ parser.add_argument(
16
+ "--output-dir",
17
+ "-o",
18
+ type=Path,
19
+ default=Path("goose_migrations"),
20
+ help="Output directory for .sql files (default: goose_migrations).",
21
+ )
22
+ parser.add_argument(
23
+ "--no-sql-extension",
24
+ action="store_true",
25
+ help="Write files without .sql extension (e.g. 0001_initial).",
26
+ )
27
+
28
+ def handle(self, output_dir, no_sql_extension, **options):
29
+ try:
30
+ apps.get_app_config(APP_LABEL)
31
+ except LookupError as e:
32
+ raise CommandError(str(e)) from e
33
+
34
+ connection = connections["default"]
35
+ loader = MigrationLoader(connection, replace_migrations=False)
36
+ if APP_LABEL not in loader.migrated_apps:
37
+ raise CommandError(f"App '{APP_LABEL}' does not have migrations.")
38
+
39
+ migration_names = [
40
+ name for (a, name) in loader.disk_migrations.keys() if a == APP_LABEL
41
+ ]
42
+ if not migration_names:
43
+ raise CommandError(f"No migrations found for app '{APP_LABEL}'.")
44
+
45
+ def sort_key(n):
46
+ parts = n.split("_", 1)
47
+ return (int(parts[0]) if parts[0].isdigit() else 0, n)
48
+
49
+ migration_names.sort(key=sort_key)
50
+
51
+ output_dir = Path(output_dir)
52
+ output_dir.mkdir(parents=True, exist_ok=True)
53
+ ext = "" if no_sql_extension else ".sql"
54
+
55
+ for name in migration_names:
56
+ up_sql = self._collect_sql(loader, name, backwards=False)
57
+ down_sql = self._collect_sql(loader, name, backwards=True)
58
+ path = output_dir / f"{name}{ext}"
59
+ content = self._format_goose(up_sql, down_sql)
60
+ path.write_text(content, encoding="utf-8")
61
+ self.stdout.write(f"Wrote {path}")
62
+
63
+ self.stdout.write(
64
+ self.style.SUCCESS(f"Done. {len(migration_names)} file(s) in {output_dir}")
65
+ )
66
+
67
+ def _collect_sql(self, loader, migration_name, backwards):
68
+ from django.db.migrations.loader import AmbiguityError
69
+
70
+ try:
71
+ migration = loader.get_migration_by_prefix(APP_LABEL, migration_name)
72
+ except (AmbiguityError, KeyError) as e:
73
+ raise CommandError(str(e)) from e
74
+ target = (APP_LABEL, migration.name)
75
+ plan = [(loader.graph.nodes[target], backwards)]
76
+ statements = loader.collect_sql(plan)
77
+ return "\n".join(statements) if statements else ""
78
+
79
+ def _format_goose(self, up_sql, down_sql):
80
+ parts = [
81
+ "-- +goose Up",
82
+ "-- +goose StatementBegin",
83
+ up_sql.strip(),
84
+ "-- +goose StatementEnd",
85
+ "",
86
+ "-- +goose Down",
87
+ "-- +goose StatementBegin",
88
+ down_sql.strip(),
89
+ "-- +goose StatementEnd",
90
+ ]
91
+ return "\n".join(parts).strip() + "\n"
@@ -0,0 +1,34 @@
1
+ # Generated by Django 6.0.2
2
+
3
+ import uuid6
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ initial = True
10
+
11
+ dependencies = []
12
+
13
+ operations = [
14
+ migrations.CreateModel(
15
+ name="Example",
16
+ fields=[
17
+ (
18
+ "id",
19
+ models.UUIDField(
20
+ db_index=True,
21
+ default=uuid6.uuid6,
22
+ editable=False,
23
+ primary_key=True,
24
+ serialize=False,
25
+ ),
26
+ ),
27
+ ("name", models.CharField(max_length=100)),
28
+ ],
29
+ options={
30
+ "db_table": "example",
31
+ "abstract": False,
32
+ },
33
+ ),
34
+ ]
File without changes
@@ -0,0 +1,3 @@
1
+ from djm.models.example import Example
2
+
3
+ __all__ = ["Example"]
@@ -0,0 +1,10 @@
1
+ """Example model – replace or remove and add your own in djm.models."""
2
+ from django.db import models
3
+ from base.models import UUIDPrimaryKeyMixin
4
+
5
+
6
+ class Example(UUIDPrimaryKeyMixin):
7
+ class Meta(UUIDPrimaryKeyMixin.Meta):
8
+ db_table = "example"
9
+
10
+ name = models.CharField(max_length=100)
@@ -0,0 +1,5 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ *.sqlite3
5
+ goose_migrations/*.sql
File without changes
@@ -0,0 +1,11 @@
1
+ from base.models.soft_delete import SoftDeleteMixin
2
+ from base.models.sort import SortNumberMixin
3
+ from base.models.timestamp import TimestampsMixin
4
+ from base.models.uuid import UUIDPrimaryKeyMixin
5
+
6
+ __all__ = [
7
+ "SortNumberMixin",
8
+ "SoftDeleteMixin",
9
+ "TimestampsMixin",
10
+ "UUIDPrimaryKeyMixin",
11
+ ]
@@ -0,0 +1,11 @@
1
+ from django.db import models
2
+
3
+
4
+ class SoftDeleteMixin(models.Model):
5
+ class Meta:
6
+ abstract = True
7
+
8
+ deleted = models.DateTimeField(blank=True, null=True, db_index=True)
9
+
10
+ def soft_delete(self):
11
+ raise NotImplementedError
@@ -0,0 +1,9 @@
1
+ from django.db import models
2
+
3
+
4
+ class SortNumberMixin(models.Model):
5
+ class Meta:
6
+ abstract = True
7
+ ordering = ("order_no",)
8
+
9
+ order_no = models.IntegerField(null=False, blank=True, db_index=True)
@@ -0,0 +1,9 @@
1
+ from django.db import models
2
+
3
+
4
+ class TimestampsMixin(models.Model):
5
+ class Meta:
6
+ abstract = True
7
+
8
+ created = models.DateTimeField(auto_now_add=True, db_index=True)
9
+ updated = models.DateTimeField(auto_now=True, db_index=True)
@@ -0,0 +1,14 @@
1
+ from uuid6 import uuid6
2
+ from django.db import models
3
+
4
+
5
+ class UUIDPrimaryKeyMixin(models.Model):
6
+ class Meta:
7
+ abstract = True
8
+
9
+ id = models.UUIDField(
10
+ primary_key=True,
11
+ default=uuid6,
12
+ editable=False,
13
+ db_index=True,
14
+ )
File without changes
@@ -0,0 +1,64 @@
1
+ from pathlib import Path
2
+
3
+ BASE_DIR = Path(__file__).resolve().parent.parent
4
+
5
+ SECRET_KEY = "change-me-in-production"
6
+ DEBUG = True
7
+ ALLOWED_HOSTS = []
8
+
9
+ INSTALLED_APPS = [
10
+ "django.contrib.admin",
11
+ "django.contrib.auth",
12
+ "django.contrib.contenttypes",
13
+ "django.contrib.sessions",
14
+ "django.contrib.messages",
15
+ "django.contrib.staticfiles",
16
+ "djm",
17
+ ]
18
+
19
+ MIDDLEWARE = [
20
+ "django.middleware.security.SecurityMiddleware",
21
+ "django.contrib.sessions.middleware.SessionMiddleware",
22
+ "django.middleware.common.CommonMiddleware",
23
+ "django.middleware.csrf.CsrfViewMiddleware",
24
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
25
+ "django.contrib.messages.middleware.MessageMiddleware",
26
+ "django.middleware.clickjacking.XFrameOptionsMiddleware",
27
+ ]
28
+
29
+ ROOT_URLCONF = "core.urls"
30
+ TEMPLATES = [
31
+ {
32
+ "BACKEND": "django.template.backends.django.DjangoTemplates",
33
+ "DIRS": [],
34
+ "APP_DIRS": True,
35
+ "OPTIONS": {
36
+ "context_processors": [
37
+ "django.template.context_processors.request",
38
+ "django.contrib.auth.context_processors.auth",
39
+ "django.contrib.messages.context_processors.messages",
40
+ ],
41
+ },
42
+ },
43
+ ]
44
+ WSGI_APPLICATION = "core.wsgi.application"
45
+
46
+ DATABASES = {
47
+ "default": {
48
+ "ENGINE": "django.db.backends.sqlite3",
49
+ "NAME": BASE_DIR / "db.sqlite3",
50
+ }
51
+ }
52
+
53
+ AUTH_PASSWORD_VALIDATORS = [
54
+ {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
55
+ {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
56
+ {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
57
+ {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
58
+ ]
59
+
60
+ LANGUAGE_CODE = "en-us"
61
+ TIME_ZONE = "UTC"
62
+ USE_I18N = True
63
+ USE_TZ = True
64
+ STATIC_URL = "static/"
@@ -0,0 +1,6 @@
1
+ from django.contrib import admin
2
+ from django.urls import path
3
+
4
+ urlpatterns = [
5
+ path("admin/", admin.site.urls),
6
+ ]
@@ -0,0 +1,5 @@
1
+ import os
2
+ from django.core.wsgi import get_wsgi_application
3
+
4
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings")
5
+ application = get_wsgi_application()
File without changes
@@ -0,0 +1,7 @@
1
+ from django.apps import apps
2
+ from django.contrib import admin
3
+
4
+ app_config = apps.get_app_config("djm")
5
+ for model_name, model in app_config.models.items():
6
+ if not admin.site.is_registered(model):
7
+ admin.site.register(model)
@@ -0,0 +1,6 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class DjmConfig(AppConfig):
5
+ default_auto_field = "django.db.models.BigAutoField"
6
+ name = "djm"
@@ -0,0 +1,95 @@
1
+ """
2
+ Generate goose-style SQL migration files from Django migrations for the djm app.
3
+
4
+ Usage:
5
+ python manage.py generate_goose [--output-dir DIR] [--no-sql-extension]
6
+ """
7
+ from pathlib import Path
8
+
9
+ from django.apps import apps
10
+ from django.core.management.base import BaseCommand, CommandError
11
+ from django.db import connections
12
+ from django.db.migrations.loader import MigrationLoader
13
+
14
+ APP_LABEL = "djm"
15
+
16
+
17
+ class Command(BaseCommand):
18
+ help = "Generate goose-style SQL migration files from Django migrations (djm app)."
19
+
20
+ def add_arguments(self, parser):
21
+ parser.add_argument(
22
+ "--output-dir",
23
+ "-o",
24
+ type=Path,
25
+ default=Path("goose_migrations"),
26
+ help="Output directory for .sql files (default: goose_migrations).",
27
+ )
28
+ parser.add_argument(
29
+ "--no-sql-extension",
30
+ action="store_true",
31
+ help="Write files without .sql extension (e.g. 0001_initial).",
32
+ )
33
+
34
+ def handle(self, output_dir, no_sql_extension, **options):
35
+ try:
36
+ apps.get_app_config(APP_LABEL)
37
+ except LookupError as e:
38
+ raise CommandError(str(e)) from e
39
+
40
+ connection = connections["default"]
41
+ loader = MigrationLoader(connection, replace_migrations=False)
42
+ if APP_LABEL not in loader.migrated_apps:
43
+ raise CommandError(f"App '{APP_LABEL}' does not have migrations.")
44
+
45
+ migration_names = [
46
+ name for (a, name) in loader.disk_migrations.keys() if a == APP_LABEL
47
+ ]
48
+ if not migration_names:
49
+ raise CommandError(f"No migrations found for app '{APP_LABEL}'.")
50
+
51
+ def sort_key(n):
52
+ parts = n.split("_", 1)
53
+ return (int(parts[0]) if parts[0].isdigit() else 0, n)
54
+
55
+ migration_names.sort(key=sort_key)
56
+
57
+ output_dir = Path(output_dir)
58
+ output_dir.mkdir(parents=True, exist_ok=True)
59
+ ext = "" if no_sql_extension else ".sql"
60
+
61
+ for name in migration_names:
62
+ up_sql = self._collect_sql(loader, name, backwards=False)
63
+ down_sql = self._collect_sql(loader, name, backwards=True)
64
+ path = output_dir / f"{name}{ext}"
65
+ content = self._format_goose(up_sql, down_sql)
66
+ path.write_text(content, encoding="utf-8")
67
+ self.stdout.write(f"Wrote {path}")
68
+
69
+ self.stdout.write(self.style.SUCCESS(f"Done. {len(migration_names)} file(s) in {output_dir}"))
70
+
71
+ def _collect_sql(self, loader, migration_name, backwards):
72
+ from django.db.migrations.loader import AmbiguityError
73
+
74
+ try:
75
+ migration = loader.get_migration_by_prefix(APP_LABEL, migration_name)
76
+ except (AmbiguityError, KeyError) as e:
77
+ raise CommandError(str(e)) from e
78
+ target = (APP_LABEL, migration.name)
79
+ plan = [(loader.graph.nodes[target], backwards)]
80
+ statements = loader.collect_sql(plan)
81
+ return "\n".join(statements) if statements else ""
82
+
83
+ def _format_goose(self, up_sql, down_sql):
84
+ parts = [
85
+ "-- +goose Up",
86
+ "-- +goose StatementBegin",
87
+ up_sql.strip(),
88
+ "-- +goose StatementEnd",
89
+ "",
90
+ "-- +goose Down",
91
+ "-- +goose StatementBegin",
92
+ down_sql.strip(),
93
+ "-- +goose StatementEnd",
94
+ ]
95
+ return "\n".join(parts).strip() + "\n"
@@ -0,0 +1,4 @@
1
+ # Define your models in this package and import here.
2
+ from djm.models.example import Example
3
+
4
+ __all__ = ["Example"]
@@ -0,0 +1,10 @@
1
+ """Example model – replace or remove and add your own in djm.models."""
2
+ from django.db import models
3
+ from base.models import UUIDPrimaryKeyMixin
4
+
5
+
6
+ class Example(UUIDPrimaryKeyMixin):
7
+ class Meta(UUIDPrimaryKeyMixin.Meta):
8
+ db_table = "example"
9
+
10
+ name = models.CharField(max_length=100)
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env python
2
+ """Django's command-line utility for administrative tasks."""
3
+ import os
4
+ import sys
5
+
6
+
7
+ def main():
8
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings")
9
+ try:
10
+ from django.core.management import execute_from_command_line
11
+ except ImportError as exc:
12
+ raise ImportError(
13
+ "Couldn't import Django. Are you sure it's installed?"
14
+ ) from exc
15
+ execute_from_command_line(sys.argv)
16
+
17
+
18
+ if __name__ == "__main__":
19
+ main()
@@ -0,0 +1,15 @@
1
+ [project]
2
+ name = "myproject"
3
+ version = "0.1.0"
4
+ requires-python = ">=3.13"
5
+ dependencies = [
6
+ "django>=6.0.2",
7
+ "uuid6>=2025.0.1",
8
+ ]
9
+
10
+ [build-system]
11
+ requires = ["hatchling"]
12
+ build-backend = "hatchling.build"
13
+
14
+ [tool.hatch.build.targets.wheel]
15
+ packages = ["core", "djm", "base"]
djm-0.0.1a0/manage.py ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env python
2
+ """Django's command-line utility for administrative tasks."""
3
+ import os
4
+ import sys
5
+
6
+
7
+ def main():
8
+ """Run administrative tasks."""
9
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings')
10
+ try:
11
+ from django.core.management import execute_from_command_line
12
+ except ImportError as exc:
13
+ raise ImportError(
14
+ "Couldn't import Django. Are you sure it's installed and "
15
+ "available on your PYTHONPATH environment variable? Did you "
16
+ "forget to activate a virtual environment?"
17
+ ) from exc
18
+ execute_from_command_line(sys.argv)
19
+
20
+
21
+ if __name__ == '__main__':
22
+ main()
@@ -0,0 +1,23 @@
1
+ [project]
2
+ name = "djm"
3
+ version = "0.0.1-alpha"
4
+ description = "Generate goose-style SQL migrations from Django migrations, plus base model mixins"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ dependencies = [
8
+ "django>=6.0.2",
9
+ "uuid6>=2025.0.1",
10
+ ]
11
+
12
+ [project.optional-dependencies]
13
+ dev = []
14
+
15
+ [project.scripts]
16
+ djm = "djm.cli:main"
17
+
18
+ [build-system]
19
+ requires = ["hatchling"]
20
+ build-backend = "hatchling.build"
21
+
22
+ [tool.hatch.build.targets.wheel]
23
+ packages = ["djm", "base"]
djm-0.0.1a0/uv.lock ADDED
@@ -0,0 +1,69 @@
1
+ version = 1
2
+ revision = 3
3
+ requires-python = ">=3.13"
4
+
5
+ [[package]]
6
+ name = "asgiref"
7
+ version = "3.11.1"
8
+ source = { registry = "https://pypi.org/simple" }
9
+ sdist = { url = "https://files.pythonhosted.org/packages/63/40/f03da1264ae8f7cfdbf9146542e5e7e8100a4c66ab48e791df9a03d3f6c0/asgiref-3.11.1.tar.gz", hash = "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce", size = 38550, upload-time = "2026-02-03T13:30:14.33Z" }
10
+ wheels = [
11
+ { url = "https://files.pythonhosted.org/packages/5c/0a/a72d10ed65068e115044937873362e6e32fab1b7dce0046aeb224682c989/asgiref-3.11.1-py3-none-any.whl", hash = "sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133", size = 24345, upload-time = "2026-02-03T13:30:13.039Z" },
12
+ ]
13
+
14
+ [[package]]
15
+ name = "django"
16
+ version = "6.0.2"
17
+ source = { registry = "https://pypi.org/simple" }
18
+ dependencies = [
19
+ { name = "asgiref" },
20
+ { name = "sqlparse" },
21
+ { name = "tzdata", marker = "sys_platform == 'win32'" },
22
+ ]
23
+ sdist = { url = "https://files.pythonhosted.org/packages/26/3e/a1c4207c5dea4697b7a3387e26584919ba987d8f9320f59dc0b5c557a4eb/django-6.0.2.tar.gz", hash = "sha256:3046a53b0e40d4b676c3b774c73411d7184ae2745fe8ce5e45c0f33d3ddb71a7", size = 10886874, upload-time = "2026-02-03T13:50:31.596Z" }
24
+ wheels = [
25
+ { url = "https://files.pythonhosted.org/packages/96/ba/a6e2992bc5b8c688249c00ea48cb1b7a9bc09839328c81dc603671460928/django-6.0.2-py3-none-any.whl", hash = "sha256:610dd3b13d15ec3f1e1d257caedd751db8033c5ad8ea0e2d1219a8acf446ecc6", size = 8339381, upload-time = "2026-02-03T13:50:15.501Z" },
26
+ ]
27
+
28
+ [[package]]
29
+ name = "djm"
30
+ version = "0.0.1a0"
31
+ source = { editable = "." }
32
+ dependencies = [
33
+ { name = "django" },
34
+ { name = "uuid6" },
35
+ ]
36
+
37
+ [package.metadata]
38
+ requires-dist = [
39
+ { name = "django", specifier = ">=6.0.2" },
40
+ { name = "uuid6", specifier = ">=2025.0.1" },
41
+ ]
42
+ provides-extras = ["dev"]
43
+
44
+ [[package]]
45
+ name = "sqlparse"
46
+ version = "0.5.5"
47
+ source = { registry = "https://pypi.org/simple" }
48
+ sdist = { url = "https://files.pythonhosted.org/packages/90/76/437d71068094df0726366574cf3432a4ed754217b436eb7429415cf2d480/sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e", size = 120815, upload-time = "2025-12-19T07:17:45.073Z" }
49
+ wheels = [
50
+ { url = "https://files.pythonhosted.org/packages/49/4b/359f28a903c13438ef59ebeee215fb25da53066db67b305c125f1c6d2a25/sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", size = 46138, upload-time = "2025-12-19T07:17:46.573Z" },
51
+ ]
52
+
53
+ [[package]]
54
+ name = "tzdata"
55
+ version = "2025.3"
56
+ source = { registry = "https://pypi.org/simple" }
57
+ sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" }
58
+ wheels = [
59
+ { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" },
60
+ ]
61
+
62
+ [[package]]
63
+ name = "uuid6"
64
+ version = "2025.0.1"
65
+ source = { registry = "https://pypi.org/simple" }
66
+ sdist = { url = "https://files.pythonhosted.org/packages/ca/b7/4c0f736ca824b3a25b15e8213d1bcfc15f8ac2ae48d1b445b310892dc4da/uuid6-2025.0.1.tar.gz", hash = "sha256:cd0af94fa428675a44e32c5319ec5a3485225ba2179eefcf4c3f205ae30a81bd", size = 13932, upload-time = "2025-07-04T18:30:35.186Z" }
67
+ wheels = [
68
+ { url = "https://files.pythonhosted.org/packages/3d/b2/93faaab7962e2aa8d6e174afb6f76be2ca0ce89fde14d3af835acebcaa59/uuid6-2025.0.1-py3-none-any.whl", hash = "sha256:80530ce4d02a93cdf82e7122ca0da3ebbbc269790ec1cb902481fa3e9cc9ff99", size = 6979, upload-time = "2025-07-04T18:30:34.001Z" },
69
+ ]