tses 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.
Files changed (146) hide show
  1. tses-0.1.0/.github/workflows/ci.yaml +26 -0
  2. tses-0.1.0/.github/workflows/publish.yaml +33 -0
  3. tses-0.1.0/.gitignore +12 -0
  4. tses-0.1.0/LICENSE +7 -0
  5. tses-0.1.0/PKG-INFO +79 -0
  6. tses-0.1.0/README.md +57 -0
  7. tses-0.1.0/pyproject.toml +52 -0
  8. tses-0.1.0/src/tses/__init__.py +3 -0
  9. tses-0.1.0/src/tses/__main__.py +7 -0
  10. tses-0.1.0/src/tses/cli.py +79 -0
  11. tses-0.1.0/src/tses/commands/__init__.py +1 -0
  12. tses-0.1.0/src/tses/commands/startproject.py +75 -0
  13. tses-0.1.0/src/tses/core/__init__.py +1 -0
  14. tses-0.1.0/src/tses/core/errors.py +17 -0
  15. tses-0.1.0/src/tses/core/filesystem.py +91 -0
  16. tses-0.1.0/src/tses/core/generator.py +90 -0
  17. tses-0.1.0/src/tses/core/headers.py +78 -0
  18. tses-0.1.0/src/tses/core/metadata.py +55 -0
  19. tses-0.1.0/src/tses/core/renderer.py +48 -0
  20. tses-0.1.0/src/tses/templates/__init__.py +1 -0
  21. tses-0.1.0/src/tses/templates/django/.docker/Dockerfile +22 -0
  22. tses-0.1.0/src/tses/templates/django/.docker/entrypoint.sh +15 -0
  23. tses-0.1.0/src/tses/templates/django/.dockerignore +24 -0
  24. tses-0.1.0/src/tses/templates/django/.env.example +32 -0
  25. tses-0.1.0/src/tses/templates/django/.github/workflows/deploy.yaml +42 -0
  26. tses-0.1.0/src/tses/templates/django/.gitignore +27 -0
  27. tses-0.1.0/src/tses/templates/django/README.md +442 -0
  28. tses-0.1.0/src/tses/templates/django/api/__init__.py +0 -0
  29. tses-0.1.0/src/tses/templates/django/api/authentication/__init__.py +1 -0
  30. tses-0.1.0/src/tses/templates/django/api/authentication/utils/__init__.py +3 -0
  31. tses-0.1.0/src/tses/templates/django/api/authentication/utils/base_auth.py +35 -0
  32. tses-0.1.0/src/tses/templates/django/api/authorization/__init__.py +0 -0
  33. tses-0.1.0/src/tses/templates/django/api/authorization/admin.py +3 -0
  34. tses-0.1.0/src/tses/templates/django/api/authorization/apps.py +6 -0
  35. tses-0.1.0/src/tses/templates/django/api/authorization/migrations/__init__.py +0 -0
  36. tses-0.1.0/src/tses/templates/django/api/authorization/models/__init__.py +0 -0
  37. tses-0.1.0/src/tses/templates/django/api/authorization/serializers/__init__.py +0 -0
  38. tses-0.1.0/src/tses/templates/django/api/authorization/tests.py +3 -0
  39. tses-0.1.0/src/tses/templates/django/api/authorization/views/__init__.py +0 -0
  40. tses-0.1.0/src/tses/templates/django/api/notification/__init__.py +0 -0
  41. tses-0.1.0/src/tses/templates/django/api/notification/admin.py +3 -0
  42. tses-0.1.0/src/tses/templates/django/api/notification/apps.py +6 -0
  43. tses-0.1.0/src/tses/templates/django/api/notification/enums.py +26 -0
  44. tses-0.1.0/src/tses/templates/django/api/notification/migrations/0001_initial.py +34 -0
  45. tses-0.1.0/src/tses/templates/django/api/notification/migrations/0002_initial.py +31 -0
  46. tses-0.1.0/src/tses/templates/django/api/notification/migrations/__init__.py +0 -0
  47. tses-0.1.0/src/tses/templates/django/api/notification/models.py +244 -0
  48. tses-0.1.0/src/tses/templates/django/api/notification/serializers.py +0 -0
  49. tses-0.1.0/src/tses/templates/django/api/notification/tests.py +3 -0
  50. tses-0.1.0/src/tses/templates/django/api/notification/views.py +0 -0
  51. tses-0.1.0/src/tses/templates/django/api/users/__init__.py +0 -0
  52. tses-0.1.0/src/tses/templates/django/api/users/admin.py +3 -0
  53. tses-0.1.0/src/tses/templates/django/api/users/apps.py +6 -0
  54. tses-0.1.0/src/tses/templates/django/api/users/enums.py +31 -0
  55. tses-0.1.0/src/tses/templates/django/api/users/migrations/0001_initial.py +72 -0
  56. tses-0.1.0/src/tses/templates/django/api/users/migrations/0002_alter_user_created_by_alter_user_updated_by.py +25 -0
  57. tses-0.1.0/src/tses/templates/django/api/users/migrations/__init__.py +0 -0
  58. tses-0.1.0/src/tses/templates/django/api/users/models/__init__.py +7 -0
  59. tses-0.1.0/src/tses/templates/django/api/users/models/activity_log.py +123 -0
  60. tses-0.1.0/src/tses/templates/django/api/users/models/manager.py +37 -0
  61. tses-0.1.0/src/tses/templates/django/api/users/models/user.py +37 -0
  62. tses-0.1.0/src/tses/templates/django/api/users/serializers/__init__.py +0 -0
  63. tses-0.1.0/src/tses/templates/django/api/users/tests.py +3 -0
  64. tses-0.1.0/src/tses/templates/django/api/users/views/__init__.py +0 -0
  65. tses-0.1.0/src/tses/templates/django/config/__init__.py +3 -0
  66. tses-0.1.0/src/tses/templates/django/config/asgi.py +16 -0
  67. tses-0.1.0/src/tses/templates/django/config/celery.py +19 -0
  68. tses-0.1.0/src/tses/templates/django/config/settings/__init__.py +25 -0
  69. tses-0.1.0/src/tses/templates/django/config/settings/app_registry.py +26 -0
  70. tses-0.1.0/src/tses/templates/django/config/settings/celery.py +12 -0
  71. tses-0.1.0/src/tses/templates/django/config/settings/commons.py +115 -0
  72. tses-0.1.0/src/tses/templates/django/config/settings/cors.py +12 -0
  73. tses-0.1.0/src/tses/templates/django/config/settings/db.py +12 -0
  74. tses-0.1.0/src/tses/templates/django/config/settings/drf.py +38 -0
  75. tses-0.1.0/src/tses/templates/django/config/settings/exception.py +6 -0
  76. tses-0.1.0/src/tses/templates/django/config/settings/jwt.py +31 -0
  77. tses-0.1.0/src/tses/templates/django/config/settings/middleware.py +11 -0
  78. tses-0.1.0/src/tses/templates/django/config/settings/s3.py +0 -0
  79. tses-0.1.0/src/tses/templates/django/config/settings/smtp.py +16 -0
  80. tses-0.1.0/src/tses/templates/django/config/urls.py +22 -0
  81. tses-0.1.0/src/tses/templates/django/config/wsgi.py +16 -0
  82. tses-0.1.0/src/tses/templates/django/docker-compose.yaml +52 -0
  83. tses-0.1.0/src/tses/templates/django/includes/__init__.py +0 -0
  84. tses-0.1.0/src/tses/templates/django/includes/helpers/__init__.py +11 -0
  85. tses-0.1.0/src/tses/templates/django/includes/helpers/formatter.py +25 -0
  86. tses-0.1.0/src/tses/templates/django/includes/helpers/models.py +73 -0
  87. tses-0.1.0/src/tses/templates/django/includes/helpers/pagination.py +217 -0
  88. tses-0.1.0/src/tses/templates/django/includes/third_party/__init__.py +0 -0
  89. tses-0.1.0/src/tses/templates/django/includes/third_party/exceptions.py +42 -0
  90. tses-0.1.0/src/tses/templates/django/includes/third_party/processor.py +59 -0
  91. tses-0.1.0/src/tses/templates/django/manage.py +22 -0
  92. tses-0.1.0/src/tses/templates/django/requirements.txt +11 -0
  93. tses-0.1.0/src/tses/templates/fastapi/.docker/Dockerfile +22 -0
  94. tses-0.1.0/src/tses/templates/fastapi/.docker/entrypoint.sh +8 -0
  95. tses-0.1.0/src/tses/templates/fastapi/.dockerignore +10 -0
  96. tses-0.1.0/src/tses/templates/fastapi/.env.example +29 -0
  97. tses-0.1.0/src/tses/templates/fastapi/.github/workflows/deploy.yaml +42 -0
  98. tses-0.1.0/src/tses/templates/fastapi/README.md +516 -0
  99. tses-0.1.0/src/tses/templates/fastapi/alembic/env.py +78 -0
  100. tses-0.1.0/src/tses/templates/fastapi/alembic/script.py.mako +20 -0
  101. tses-0.1.0/src/tses/templates/fastapi/alembic/versions/0001_initial.py +73 -0
  102. tses-0.1.0/src/tses/templates/fastapi/alembic/versions/__init__.py +1 -0
  103. tses-0.1.0/src/tses/templates/fastapi/alembic.ini +36 -0
  104. tses-0.1.0/src/tses/templates/fastapi/api/__init__.py +0 -0
  105. tses-0.1.0/src/tses/templates/fastapi/api/auth/__init__.py +24 -0
  106. tses-0.1.0/src/tses/templates/fastapi/api/auth/base_auth.py +35 -0
  107. tses-0.1.0/src/tses/templates/fastapi/api/auth/schemas.py +15 -0
  108. tses-0.1.0/src/tses/templates/fastapi/api/auth/services/__init__.py +8 -0
  109. tses-0.1.0/src/tses/templates/fastapi/api/auth/services/auth.py +94 -0
  110. tses-0.1.0/src/tses/templates/fastapi/api/auth/services/token.py +86 -0
  111. tses-0.1.0/src/tses/templates/fastapi/api/health/__init__.py +0 -0
  112. tses-0.1.0/src/tses/templates/fastapi/api/health/routers.py +44 -0
  113. tses-0.1.0/src/tses/templates/fastapi/api/health/schemas.py +24 -0
  114. tses-0.1.0/src/tses/templates/fastapi/api/health/service.py +59 -0
  115. tses-0.1.0/src/tses/templates/fastapi/api/users/__init__.py +1 -0
  116. tses-0.1.0/src/tses/templates/fastapi/api/users/models/__init__.py +3 -0
  117. tses-0.1.0/src/tses/templates/fastapi/api/users/models/users.py +67 -0
  118. tses-0.1.0/src/tses/templates/fastapi/api/users/routers.py +12 -0
  119. tses-0.1.0/src/tses/templates/fastapi/api/users/schemas.py +24 -0
  120. tses-0.1.0/src/tses/templates/fastapi/config/__init__.py +0 -0
  121. tses-0.1.0/src/tses/templates/fastapi/config/db.py +99 -0
  122. tses-0.1.0/src/tses/templates/fastapi/config/settings.py +161 -0
  123. tses-0.1.0/src/tses/templates/fastapi/docker-compose.yaml +20 -0
  124. tses-0.1.0/src/tses/templates/fastapi/includes/__init__.py +1 -0
  125. tses-0.1.0/src/tses/templates/fastapi/includes/errors.py +57 -0
  126. tses-0.1.0/src/tses/templates/fastapi/includes/exception_handlers.py +155 -0
  127. tses-0.1.0/src/tses/templates/fastapi/includes/libs/__init__.py +4 -0
  128. tses-0.1.0/src/tses/templates/fastapi/includes/libs/s3.py +348 -0
  129. tses-0.1.0/src/tses/templates/fastapi/includes/middleware/__init__.py +3 -0
  130. tses-0.1.0/src/tses/templates/fastapi/includes/middleware/error_handler.py +30 -0
  131. tses-0.1.0/src/tses/templates/fastapi/includes/models.py +170 -0
  132. tses-0.1.0/src/tses/templates/fastapi/includes/pagination.py +148 -0
  133. tses-0.1.0/src/tses/templates/fastapi/includes/schemas.py +52 -0
  134. tses-0.1.0/src/tses/templates/fastapi/includes/third_party/__init__.py +0 -0
  135. tses-0.1.0/src/tses/templates/fastapi/includes/third_party/exceptions.py +42 -0
  136. tses-0.1.0/src/tses/templates/fastapi/includes/third_party/processor.py +59 -0
  137. tses-0.1.0/src/tses/templates/fastapi/main.py +72 -0
  138. tses-0.1.0/src/tses/templates/fastapi/requirements.txt +13 -0
  139. tses-0.1.0/tests/conftest.py +13 -0
  140. tses-0.1.0/tests/fixtures/.gitkeep +0 -0
  141. tses-0.1.0/tests/test_cli.py +41 -0
  142. tses-0.1.0/tests/test_force_mode.py +34 -0
  143. tses-0.1.0/tests/test_generation.py +51 -0
  144. tses-0.1.0/tests/test_headers.py +52 -0
  145. tses-0.1.0/tests/test_packaging.py +14 -0
  146. tses-0.1.0/tools/sync_scaffolds.py +108 -0
@@ -0,0 +1,26 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ test:
9
+ runs-on: ubuntu-latest
10
+
11
+ steps:
12
+ - name: Checkout code
13
+ uses: actions/checkout@v4
14
+
15
+ - name: Set up Python
16
+ uses: actions/setup-python@v5
17
+ with:
18
+ python-version: "3.12"
19
+
20
+ - name: Install dependencies
21
+ run: |
22
+ python -m pip install --upgrade pip
23
+ python -m pip install .[dev]
24
+
25
+ - name: Run tests
26
+ run: python -m pytest
@@ -0,0 +1,33 @@
1
+ name: Publish
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - name: Checkout code
15
+ uses: actions/checkout@v4
16
+
17
+ - name: Set up Python
18
+ uses: actions/setup-python@v5
19
+ with:
20
+ python-version: "3.12"
21
+
22
+ - name: Install build tooling
23
+ run: |
24
+ python -m pip install --upgrade pip
25
+ python -m pip install build
26
+
27
+ - name: Build package
28
+ run: python -m build
29
+
30
+ - name: Publish to PyPI
31
+ uses: pypa/gh-action-pypi-publish@release/v1
32
+ with:
33
+ password: ${{ secrets.PYPI_API_TOKEN }}
tses-0.1.0/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .pytest_cache/
5
+ .mypy_cache/
6
+ .ruff_cache/
7
+ .venv/
8
+ venv/
9
+ build/
10
+ dist/
11
+ .coverage
12
+ htmlcov/
tses-0.1.0/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2026 TSES.
2
+ All rights reserved.
3
+
4
+ This repository and its contents are proprietary to TSES.
5
+ No permission is granted to use, copy, modify, distribute, sublicense,
6
+ or create derivative works from this software except by prior written
7
+ agreement with TSES.
tses-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.4
2
+ Name: tses
3
+ Version: 0.1.0
4
+ Summary: TSES project scaffold generator for Django and FastAPI services.
5
+ Author: TSES
6
+ License: Proprietary
7
+ License-File: LICENSE
8
+ Keywords: cli,django,fastapi,scaffold,tses
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Software Development :: Code Generators
16
+ Classifier: Topic :: Utilities
17
+ Requires-Python: >=3.10
18
+ Provides-Extra: dev
19
+ Requires-Dist: build>=1.2.2.post1; extra == 'dev'
20
+ Requires-Dist: pytest>=8.4.0; extra == 'dev'
21
+ Description-Content-Type: text/markdown
22
+
23
+ # TSES
24
+
25
+ `tses` is the TSES scaffold generator CLI for bootstrapping curated Django and FastAPI service projects.
26
+
27
+ ## Install
28
+
29
+ For local development:
30
+
31
+ ```bash
32
+ pip install -e .[dev]
33
+ ```
34
+
35
+ For end users after publishing:
36
+
37
+ ```bash
38
+ pip install tses
39
+ ```
40
+
41
+ A `pipx install tses` workflow can also be used for isolated CLI installs.
42
+
43
+ ## Usage
44
+
45
+ Generate a Django scaffold into a new directory:
46
+
47
+ ```bash
48
+ tses startproject my-django-service -f django
49
+ ```
50
+
51
+ Generate a FastAPI scaffold into the current directory:
52
+
53
+ ```bash
54
+ tses startproject . -f fastapi --force
55
+ ```
56
+
57
+ Available options:
58
+
59
+ - `-f`, `--framework`: required framework choice, `django` or `fastapi`
60
+ - `--force`: overwrite scaffold-managed files in a non-empty target directory
61
+ - `--dry-run`: print planned file actions without writing files
62
+ - `--verbose`: print per-file actions during generation
63
+ - `--version`: print the installed `tses` version
64
+
65
+ ## Development
66
+
67
+ Scaffold snapshots are vendored into `src/tses/templates`.
68
+ Use the sync script to refresh them from the scaffold repos:
69
+
70
+ ```bash
71
+ python tools/sync_scaffolds.py
72
+ ```
73
+
74
+ Default source repos:
75
+
76
+ - `/home/mike/django-scaffold`
77
+ - `/home/mike/fastapi-scaffold`
78
+
79
+ Before the first public release, confirm that the package name `tses` is available on the intended package index.
tses-0.1.0/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # TSES
2
+
3
+ `tses` is the TSES scaffold generator CLI for bootstrapping curated Django and FastAPI service projects.
4
+
5
+ ## Install
6
+
7
+ For local development:
8
+
9
+ ```bash
10
+ pip install -e .[dev]
11
+ ```
12
+
13
+ For end users after publishing:
14
+
15
+ ```bash
16
+ pip install tses
17
+ ```
18
+
19
+ A `pipx install tses` workflow can also be used for isolated CLI installs.
20
+
21
+ ## Usage
22
+
23
+ Generate a Django scaffold into a new directory:
24
+
25
+ ```bash
26
+ tses startproject my-django-service -f django
27
+ ```
28
+
29
+ Generate a FastAPI scaffold into the current directory:
30
+
31
+ ```bash
32
+ tses startproject . -f fastapi --force
33
+ ```
34
+
35
+ Available options:
36
+
37
+ - `-f`, `--framework`: required framework choice, `django` or `fastapi`
38
+ - `--force`: overwrite scaffold-managed files in a non-empty target directory
39
+ - `--dry-run`: print planned file actions without writing files
40
+ - `--verbose`: print per-file actions during generation
41
+ - `--version`: print the installed `tses` version
42
+
43
+ ## Development
44
+
45
+ Scaffold snapshots are vendored into `src/tses/templates`.
46
+ Use the sync script to refresh them from the scaffold repos:
47
+
48
+ ```bash
49
+ python tools/sync_scaffolds.py
50
+ ```
51
+
52
+ Default source repos:
53
+
54
+ - `/home/mike/django-scaffold`
55
+ - `/home/mike/fastapi-scaffold`
56
+
57
+ Before the first public release, confirm that the package name `tses` is available on the intended package index.
@@ -0,0 +1,52 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27.0"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "tses"
7
+ version = "0.1.0"
8
+ description = "TSES project scaffold generator for Django and FastAPI services."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "Proprietary" }
12
+ authors = [
13
+ { name = "TSES" },
14
+ ]
15
+ keywords = ["tses", "scaffold", "django", "fastapi", "cli"]
16
+ classifiers = [
17
+ "Environment :: Console",
18
+ "Intended Audience :: Developers",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Topic :: Software Development :: Code Generators",
24
+ "Topic :: Utilities",
25
+ ]
26
+ dependencies = []
27
+
28
+ [project.optional-dependencies]
29
+ dev = [
30
+ "build>=1.2.2.post1",
31
+ "pytest>=8.4.0",
32
+ ]
33
+
34
+ [project.scripts]
35
+ tses = "tses.cli:run"
36
+
37
+ [tool.hatch.build.targets.wheel]
38
+ packages = ["src/tses"]
39
+
40
+ [tool.hatch.build.targets.sdist]
41
+ include = [
42
+ "/src/tses",
43
+ "/README.md",
44
+ "/LICENSE",
45
+ "/tools",
46
+ "/tests",
47
+ "/.github/workflows",
48
+ ]
49
+
50
+ [tool.pytest.ini_options]
51
+ addopts = "-q"
52
+ testpaths = ["tests"]
@@ -0,0 +1,3 @@
1
+ """TSES scaffold generator package."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,7 @@
1
+ """Module entry point for `python -m tses`."""
2
+
3
+ from tses.cli import run
4
+
5
+
6
+ if __name__ == "__main__":
7
+ run()
@@ -0,0 +1,79 @@
1
+ """Command-line interface for the TSES scaffold generator."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+
8
+ from tses import __version__
9
+ from tses.commands.startproject import handle_startproject
10
+ from tses.core.errors import TsesError
11
+
12
+
13
+ def build_parser() -> argparse.ArgumentParser:
14
+ """Build the top-level CLI parser."""
15
+
16
+ parser = argparse.ArgumentParser(
17
+ prog="tses",
18
+ description="Generate curated Django or FastAPI project scaffolds.",
19
+ )
20
+ parser.add_argument(
21
+ "--version",
22
+ action="version",
23
+ version=f"tses {__version__}",
24
+ )
25
+
26
+ subparsers = parser.add_subparsers(dest="command", required=True)
27
+
28
+ startproject_parser = subparsers.add_parser(
29
+ "startproject",
30
+ help="Generate a project scaffold into the target directory.",
31
+ )
32
+ startproject_parser.add_argument(
33
+ "target",
34
+ help="Target directory to generate into. Use '.' for the current directory.",
35
+ )
36
+ startproject_parser.add_argument(
37
+ "-f",
38
+ "--framework",
39
+ required=True,
40
+ choices=("django", "fastapi"),
41
+ help="Framework scaffold to generate.",
42
+ )
43
+ startproject_parser.add_argument(
44
+ "--force",
45
+ action="store_true",
46
+ help="Overwrite scaffold-managed files in a non-empty target directory.",
47
+ )
48
+ startproject_parser.add_argument(
49
+ "--dry-run",
50
+ action="store_true",
51
+ help="Print planned actions without writing files.",
52
+ )
53
+ startproject_parser.add_argument(
54
+ "--verbose",
55
+ action="store_true",
56
+ help="Print per-file actions during generation.",
57
+ )
58
+ startproject_parser.set_defaults(handler=handle_startproject)
59
+
60
+ return parser
61
+
62
+
63
+ def main(argv: list[str] | None = None) -> int:
64
+ """Run the CLI and return an exit code."""
65
+
66
+ parser = build_parser()
67
+ args = parser.parse_args(argv)
68
+
69
+ try:
70
+ return args.handler(args)
71
+ except TsesError as exc:
72
+ print(f"Error: {exc}", file=sys.stderr)
73
+ return 1
74
+
75
+
76
+ def run() -> None:
77
+ """Run the CLI as a console-script entry point."""
78
+
79
+ raise SystemExit(main())
@@ -0,0 +1 @@
1
+ """CLI command handlers for TSES."""
@@ -0,0 +1,75 @@
1
+ """Implementation of the `tses startproject` command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from tses.core.filesystem import PlannedAction
8
+ from tses.core.generator import GenerationResult, generate_scaffold
9
+
10
+
11
+ def handle_startproject(args) -> int:
12
+ """Generate the requested scaffold and print user-facing output."""
13
+
14
+ result = generate_scaffold(
15
+ framework=args.framework,
16
+ target=Path(args.target),
17
+ force=args.force,
18
+ dry_run=args.dry_run,
19
+ )
20
+
21
+ if args.dry_run:
22
+ print(f"Dry run for {result.framework} scaffold")
23
+ print(f"Target: {result.target_dir}")
24
+ for action in result.actions:
25
+ _print_action(action, prefix="Would")
26
+ print(
27
+ f"Would create {result.created_count} files and overwrite "
28
+ f"{result.overwritten_count} files."
29
+ )
30
+ return 0
31
+
32
+ if args.verbose:
33
+ for action in result.actions:
34
+ _print_action(action)
35
+
36
+ print("Project generated successfully.")
37
+ print(f"Framework: {result.framework}")
38
+ print(f"Target: {result.target_dir}")
39
+ print(f"Project name: {result.metadata.project_name}")
40
+ print(f"Force mode: {'yes' if args.force else 'no'}")
41
+ print(f"Created files: {result.created_count}")
42
+ print(f"Overwritten files: {result.overwritten_count}")
43
+ print("Next steps:")
44
+ for step in _build_next_steps(result):
45
+ print(f" {step}")
46
+ return 0
47
+
48
+
49
+ def _print_action(action: PlannedAction, prefix: str | None = None) -> None:
50
+ """Print one planned or completed file action."""
51
+
52
+ verb = action.kind if prefix is None else f"{prefix} {action.kind}"
53
+ print(f"{verb:>16} {action.relative_path.as_posix()}")
54
+
55
+
56
+ def _build_next_steps(result: GenerationResult) -> list[str]:
57
+ """Return framework-specific follow-up steps for the user."""
58
+
59
+ steps: list[str] = []
60
+ if result.original_target != Path("."):
61
+ steps.append(f"cd {result.target_dir}")
62
+
63
+ env_example = result.target_dir / ".env.example"
64
+ env_file = result.target_dir / ".env"
65
+ if env_example.exists() and not env_file.exists():
66
+ steps.append("cp .env.example .env")
67
+
68
+ if result.framework == "django":
69
+ steps.append("docker compose up --build")
70
+ steps.append("python manage.py migrate")
71
+ else:
72
+ steps.append("docker compose up --build")
73
+ steps.append("alembic upgrade head")
74
+
75
+ return steps
@@ -0,0 +1 @@
1
+ """Core generation utilities for TSES."""
@@ -0,0 +1,17 @@
1
+ """Custom exceptions raised by the TSES scaffold generator."""
2
+
3
+
4
+ class TsesError(Exception):
5
+ """Base exception for user-facing generator errors."""
6
+
7
+
8
+ class UnsupportedFrameworkError(TsesError):
9
+ """Raised when a framework choice is not supported."""
10
+
11
+
12
+ class UnsafeTargetError(TsesError):
13
+ """Raised when generation would write into an unsafe target directory."""
14
+
15
+
16
+ class GenerationError(TsesError):
17
+ """Raised when file generation cannot complete safely."""
@@ -0,0 +1,91 @@
1
+ """Filesystem planning helpers for scaffold generation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Iterator
8
+
9
+ from importlib.abc import Traversable
10
+
11
+ from tses.core.errors import GenerationError, UnsafeTargetError
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class PlannedAction:
16
+ """One file action required to realize a scaffold generation request."""
17
+
18
+ kind: str
19
+ relative_path: Path
20
+ source: Traversable
21
+ destination: Path
22
+
23
+
24
+ def prepare_target_directory(target_dir: Path, *, force: bool, dry_run: bool) -> None:
25
+ """Validate the target directory and create it when appropriate."""
26
+
27
+ if target_dir.exists() and not target_dir.is_dir():
28
+ raise UnsafeTargetError(f"Target path is not a directory: {target_dir}")
29
+
30
+ if target_dir.exists():
31
+ if any(target_dir.iterdir()) and not force:
32
+ raise UnsafeTargetError(
33
+ "Target directory is not empty. Re-run with --force to overwrite "
34
+ "scaffold-managed files."
35
+ )
36
+ return
37
+
38
+ if not dry_run:
39
+ target_dir.mkdir(parents=True, exist_ok=True)
40
+
41
+
42
+ def plan_actions(template_root: Traversable, target_dir: Path) -> list[PlannedAction]:
43
+ """Plan the file actions required to copy a scaffold into the target."""
44
+
45
+ actions: list[PlannedAction] = []
46
+ for source, relative_path in iter_template_files(template_root):
47
+ destination = target_dir / relative_path
48
+ _validate_parent_paths(target_dir=target_dir, destination=destination)
49
+
50
+ if destination.exists() and destination.is_dir():
51
+ raise GenerationError(
52
+ f"Cannot overwrite directory with file: {destination}"
53
+ )
54
+
55
+ action_kind = "overwrite" if destination.exists() else "create"
56
+ actions.append(
57
+ PlannedAction(
58
+ kind=action_kind,
59
+ relative_path=relative_path,
60
+ source=source,
61
+ destination=destination,
62
+ )
63
+ )
64
+
65
+ return actions
66
+
67
+
68
+ def iter_template_files(
69
+ root: Traversable,
70
+ relative_path: Path = Path(),
71
+ ) -> Iterator[tuple[Traversable, Path]]:
72
+ """Yield every file under a template root with a stable relative path."""
73
+
74
+ for child in sorted(root.iterdir(), key=lambda entry: entry.name):
75
+ child_relative_path = relative_path / child.name
76
+ if child.is_dir():
77
+ yield from iter_template_files(child, child_relative_path)
78
+ elif child.is_file():
79
+ yield child, child_relative_path
80
+
81
+
82
+ def _validate_parent_paths(*, target_dir: Path, destination: Path) -> None:
83
+ """Ensure no parent segment that should be a directory is already a file."""
84
+
85
+ current = destination.parent
86
+ while current != target_dir.parent:
87
+ if current.exists() and not current.is_dir():
88
+ raise GenerationError(f"Path segment is a file, not a directory: {current}")
89
+ if current == target_dir:
90
+ break
91
+ current = current.parent
@@ -0,0 +1,90 @@
1
+ """High-level scaffold generation orchestration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from importlib import resources
7
+ from importlib.abc import Traversable
8
+ from pathlib import Path
9
+
10
+ from tses.core.errors import GenerationError, UnsupportedFrameworkError
11
+ from tses.core.filesystem import PlannedAction, plan_actions, prepare_target_directory
12
+ from tses.core.metadata import GenerationMetadata, build_generation_metadata
13
+ from tses.core.renderer import apply_action
14
+
15
+
16
+ SUPPORTED_FRAMEWORKS = {"django", "fastapi"}
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class GenerationResult:
21
+ """Summary of one scaffold generation run."""
22
+
23
+ framework: str
24
+ original_target: Path
25
+ target_dir: Path
26
+ metadata: GenerationMetadata
27
+ actions: list[PlannedAction]
28
+
29
+ @property
30
+ def created_count(self) -> int:
31
+ """Return the number of created files."""
32
+
33
+ return sum(1 for action in self.actions if action.kind == "create")
34
+
35
+ @property
36
+ def overwritten_count(self) -> int:
37
+ """Return the number of overwritten files."""
38
+
39
+ return sum(1 for action in self.actions if action.kind == "overwrite")
40
+
41
+
42
+ def generate_scaffold(
43
+ *,
44
+ framework: str,
45
+ target: Path,
46
+ force: bool = False,
47
+ dry_run: bool = False,
48
+ ) -> GenerationResult:
49
+ """Generate a vendored framework scaffold into the target directory."""
50
+
51
+ normalized_framework = framework.strip().lower()
52
+ template_root = get_template_root(normalized_framework)
53
+
54
+ target_dir = target.expanduser().resolve()
55
+ metadata = build_generation_metadata(
56
+ framework=normalized_framework,
57
+ target_dir=target_dir,
58
+ )
59
+
60
+ prepare_target_directory(target_dir, force=force, dry_run=dry_run)
61
+ actions = plan_actions(template_root, target_dir)
62
+
63
+ if not dry_run:
64
+ for action in actions:
65
+ apply_action(action, metadata)
66
+
67
+ return GenerationResult(
68
+ framework=normalized_framework,
69
+ original_target=target,
70
+ target_dir=target_dir,
71
+ metadata=metadata,
72
+ actions=actions,
73
+ )
74
+
75
+
76
+ def get_template_root(framework: str) -> Traversable:
77
+ """Return the vendored template root for a supported framework."""
78
+
79
+ if framework not in SUPPORTED_FRAMEWORKS:
80
+ raise UnsupportedFrameworkError(
81
+ f"Unsupported framework '{framework}'. Expected one of: django, fastapi."
82
+ )
83
+
84
+ template_root = resources.files("tses.templates").joinpath(framework)
85
+ if not template_root.exists() or not template_root.is_dir():
86
+ raise GenerationError(
87
+ f"Vendored template for framework '{framework}' is missing. "
88
+ "Run tools/sync_scaffolds.py to refresh scaffold snapshots."
89
+ )
90
+ return template_root