quickscale-core 0.57.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 (39) hide show
  1. quickscale_core-0.57.0/PKG-INFO +23 -0
  2. quickscale_core-0.57.0/pyproject.toml +111 -0
  3. quickscale_core-0.57.0/src/quickscale_core/__init__.py +5 -0
  4. quickscale_core-0.57.0/src/quickscale_core/_version.py +2 -0
  5. quickscale_core-0.57.0/src/quickscale_core/generator/__init__.py +10 -0
  6. quickscale_core-0.57.0/src/quickscale_core/generator/generator.py +181 -0
  7. quickscale_core-0.57.0/src/quickscale_core/generator/templates/.dockerignore.j2 +61 -0
  8. quickscale_core-0.57.0/src/quickscale_core/generator/templates/.editorconfig.j2 +33 -0
  9. quickscale_core-0.57.0/src/quickscale_core/generator/templates/.env.example.j2 +38 -0
  10. quickscale_core-0.57.0/src/quickscale_core/generator/templates/.gitignore.j2 +74 -0
  11. quickscale_core-0.57.0/src/quickscale_core/generator/templates/.pre-commit-config.yaml.j2 +22 -0
  12. quickscale_core-0.57.0/src/quickscale_core/generator/templates/Dockerfile.j2 +82 -0
  13. quickscale_core-0.57.0/src/quickscale_core/generator/templates/README.md.j2 +299 -0
  14. quickscale_core-0.57.0/src/quickscale_core/generator/templates/docker-compose.yml.j2 +49 -0
  15. quickscale_core-0.57.0/src/quickscale_core/generator/templates/github/workflows/ci.yml.j2 +62 -0
  16. quickscale_core-0.57.0/src/quickscale_core/generator/templates/manage.py.j2 +51 -0
  17. quickscale_core-0.57.0/src/quickscale_core/generator/templates/poetry.lock.j2 +797 -0
  18. quickscale_core-0.57.0/src/quickscale_core/generator/templates/project_name/__init__.py.j2 +5 -0
  19. quickscale_core-0.57.0/src/quickscale_core/generator/templates/project_name/asgi.py.j2 +16 -0
  20. quickscale_core-0.57.0/src/quickscale_core/generator/templates/project_name/settings/__init__.py.j2 +10 -0
  21. quickscale_core-0.57.0/src/quickscale_core/generator/templates/project_name/settings/base.py.j2 +189 -0
  22. quickscale_core-0.57.0/src/quickscale_core/generator/templates/project_name/settings/local.py.j2 +86 -0
  23. quickscale_core-0.57.0/src/quickscale_core/generator/templates/project_name/settings/production.py.j2 +197 -0
  24. quickscale_core-0.57.0/src/quickscale_core/generator/templates/project_name/urls.py.j2 +42 -0
  25. quickscale_core-0.57.0/src/quickscale_core/generator/templates/project_name/wsgi.py.j2 +16 -0
  26. quickscale_core-0.57.0/src/quickscale_core/generator/templates/pyproject.toml.j2 +78 -0
  27. quickscale_core-0.57.0/src/quickscale_core/generator/templates/static/css/style.css.j2 +123 -0
  28. quickscale_core-0.57.0/src/quickscale_core/generator/templates/static/images/.gitkeep +0 -0
  29. quickscale_core-0.57.0/src/quickscale_core/generator/templates/static/images/favicon.svg +4 -0
  30. quickscale_core-0.57.0/src/quickscale_core/generator/templates/static/js/.gitkeep +0 -0
  31. quickscale_core-0.57.0/src/quickscale_core/generator/templates/templates/base.html.j2 +32 -0
  32. quickscale_core-0.57.0/src/quickscale_core/generator/templates/templates/index.html.j2 +30 -0
  33. quickscale_core-0.57.0/src/quickscale_core/generator/templates/tests/__init__.py.j2 +0 -0
  34. quickscale_core-0.57.0/src/quickscale_core/generator/templates/tests/conftest.py.j2 +44 -0
  35. quickscale_core-0.57.0/src/quickscale_core/generator/templates/tests/test_example.py.j2 +51 -0
  36. quickscale_core-0.57.0/src/quickscale_core/py.typed +0 -0
  37. quickscale_core-0.57.0/src/quickscale_core/utils/__init__.py +17 -0
  38. quickscale_core-0.57.0/src/quickscale_core/utils/file_utils.py +70 -0
  39. quickscale_core-0.57.0/src/quickscale_core/version.py +36 -0
@@ -0,0 +1,23 @@
1
+ Metadata-Version: 2.1
2
+ Name: quickscale-core
3
+ Version: 0.57.0
4
+ Summary: Core scaffolding and utilities for QuickScale Django project generator
5
+ Home-page: https://github.com/Experto-AI/quickscale
6
+ License: Apache-2.0
7
+ Keywords: django,scaffolding,project-generator,saas
8
+ Author: Experto AI
9
+ Author-email: victor@experto.ai
10
+ Requires-Python: >=3.10,<4.0
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Framework :: Django
13
+ Classifier: Framework :: Django :: 4.2
14
+ Classifier: Framework :: Django :: 5.0
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: Apache Software License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Requires-Dist: Jinja2 (>=3.1.0,<4.0.0)
22
+ Project-URL: Documentation, https://github.com/Experto-AI/quickscale/tree/main/docs
23
+ Project-URL: Repository, https://github.com/Experto-AI/quickscale
@@ -0,0 +1,111 @@
1
+ [tool.poetry]
2
+ name = "quickscale-core"
3
+ version = "0.57.0"
4
+ description = "Core scaffolding and utilities for QuickScale Django project generator"
5
+ authors = ["Experto AI <victor@experto.ai>"]
6
+ license = "Apache-2.0"
7
+ keywords = ["django", "scaffolding", "project-generator", "saas"]
8
+ homepage = "https://github.com/Experto-AI/quickscale"
9
+ repository = "https://github.com/Experto-AI/quickscale"
10
+ documentation = "https://github.com/Experto-AI/quickscale/tree/main/docs"
11
+ classifiers = [
12
+ "Development Status :: 3 - Alpha",
13
+ "Intended Audience :: Developers",
14
+ "License :: OSI Approved :: Apache Software License",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.10",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Framework :: Django",
20
+ "Framework :: Django :: 4.2",
21
+ "Framework :: Django :: 5.0",
22
+ ]
23
+ packages = [{include = "quickscale_core", from = "src"}]
24
+ include = [
25
+ "src/quickscale_core/generator/templates/**/*",
26
+ ]
27
+
28
+ [tool.poetry.dependencies]
29
+ python = "^3.10"
30
+ Jinja2 = "^3.1.0"
31
+
32
+ [tool.poetry.group.dev.dependencies]
33
+ pytest = "^7.4.0"
34
+ pytest-cov = "^4.1.0"
35
+ ruff = "^0.6.0"
36
+ mypy = "^1.8.0"
37
+
38
+ [build-system]
39
+ requires = ["poetry-core"]
40
+ build-backend = "poetry.core.masonry.api"
41
+
42
+ [tool.pytest.ini_options]
43
+ testpaths = ["tests"]
44
+ python_files = ["test_*.py"]
45
+ python_classes = ["Test*"]
46
+ python_functions = ["test_*"]
47
+ addopts = [
48
+ "--strict-markers",
49
+ "--strict-config",
50
+ "--cov=quickscale_core",
51
+ "--cov-report=term-missing",
52
+ "--cov-report=html",
53
+ ]
54
+
55
+ [tool.coverage.run]
56
+ omit = [
57
+ "*/templates/*.j2",
58
+ "*/templates/**/*.j2",
59
+ ]
60
+
61
+ [tool.ruff]
62
+ line-length = 100
63
+ target-version = "py310"
64
+
65
+ [tool.ruff.lint]
66
+ select = [
67
+ "E", # pycodestyle errors
68
+ "W", # pycodestyle warnings
69
+ "F", # pyflakes
70
+ "I", # isort
71
+ "N", # pep8-naming
72
+ "UP", # pyupgrade
73
+ "D", # docstring rules
74
+ ]
75
+ ignore = [
76
+ "D100", # Missing docstring in public module
77
+ "D101", # Missing docstring in public class
78
+ "D102", # Missing docstring in public method
79
+ "D103", # Missing docstring in public function
80
+ "D105", # Missing docstring in magic method
81
+ "D107", # Missing docstring in __init__
82
+ "D203", # 1 blank line required before class docstring (incompatible with D211)
83
+ "D212", # Multi-line docstring summary should start at the first line (incompatible with D213)
84
+ "D400", # First line should end with a period
85
+ "D415", # First line should end with a period, question mark, or exclamation point
86
+ ]
87
+
88
+ [tool.ruff.lint.isort]
89
+ known-first-party = ["quickscale_core"]
90
+
91
+ [tool.mypy]
92
+ python_version = "3.10"
93
+ warn_return_any = true
94
+ warn_unused_configs = true
95
+ disallow_untyped_defs = true
96
+ disallow_incomplete_defs = true
97
+ check_untyped_defs = true
98
+ disallow_untyped_decorators = true
99
+ no_implicit_optional = true
100
+ warn_redundant_casts = true
101
+ warn_unused_ignores = true
102
+ warn_no_return = true
103
+ warn_unreachable = true
104
+ strict_equality = true
105
+ show_error_codes = true
106
+
107
+ [[tool.mypy.overrides]]
108
+ module = "tests.*"
109
+ disallow_untyped_defs = false
110
+ check_untyped_defs = false
111
+
@@ -0,0 +1,5 @@
1
+ """QuickScale Core Package - Foundation for Django project scaffolding and generation."""
2
+
3
+ from quickscale_core.version import VERSION, __version__
4
+
5
+ __all__ = ["__version__", "VERSION"]
@@ -0,0 +1,2 @@
1
+ # Auto-generated by scripts/version_tool.sh
2
+ __version__ = "0.57.0"
@@ -0,0 +1,10 @@
1
+ """
2
+ QuickScale Generator Package
3
+
4
+ Template-based Django project generator for QuickScale.
5
+ MVP: Simple Jinja2 template rendering for production-ready Django starters.
6
+ """
7
+
8
+ from quickscale_core.generator.generator import ProjectGenerator
9
+
10
+ __all__ = ["ProjectGenerator"]
@@ -0,0 +1,181 @@
1
+ """Project generator implementation"""
2
+
3
+ import os
4
+ import shutil
5
+ import tempfile
6
+ from pathlib import Path
7
+
8
+ from jinja2 import Environment, FileSystemLoader
9
+
10
+ from quickscale_core.utils.file_utils import (
11
+ ensure_directory,
12
+ validate_project_name,
13
+ write_file,
14
+ )
15
+
16
+
17
+ class ProjectGenerator:
18
+ """Generate Django projects from templates"""
19
+
20
+ def __init__(self, template_dir: Path | None = None):
21
+ """Initialize generator with template directory"""
22
+ if template_dir is None:
23
+ # Try to find templates in development environment first
24
+ import quickscale_core
25
+
26
+ package_dir = Path(quickscale_core.__file__).parent
27
+
28
+ # Check if we're in development (source directory exists)
29
+ dev_template_dir = package_dir / "generator" / "templates"
30
+ if dev_template_dir.exists():
31
+ template_dir = dev_template_dir
32
+ else:
33
+ # Fall back to package templates (should be included)
34
+ template_dir = package_dir / "templates"
35
+
36
+ # If package templates don't exist, try to find source templates
37
+ # by walking up from the current file location
38
+ if not template_dir.exists():
39
+ current_file = Path(__file__)
40
+ # Try common development layouts
41
+ possible_paths = [
42
+ current_file.parent / "templates", # Same directory
43
+ current_file.parent.parent / "generator" / "templates", # Parent
44
+ Path.cwd()
45
+ / "quickscale_core"
46
+ / "src"
47
+ / "quickscale_core"
48
+ / "generator"
49
+ / "templates", # From repo root
50
+ ]
51
+
52
+ for path in possible_paths:
53
+ if path.exists():
54
+ template_dir = path
55
+ break
56
+
57
+ # Validate template directory exists
58
+ if not template_dir.exists():
59
+ raise FileNotFoundError(f"Template directory not found: {template_dir}")
60
+
61
+ self.template_dir = template_dir
62
+ self.env = Environment(
63
+ loader=FileSystemLoader(str(template_dir), followlinks=True),
64
+ keep_trailing_newline=True,
65
+ )
66
+
67
+ def generate(self, project_name: str, output_path: Path) -> None:
68
+ """
69
+ Generate Django project from templates
70
+
71
+ Args:
72
+ project_name: Name of the project (must be valid Python identifier)
73
+ output_path: Path where project will be created
74
+
75
+ Raises:
76
+ ValueError: If project_name is invalid
77
+ FileExistsError: If output_path already exists
78
+ PermissionError: If output_path is not writable
79
+
80
+ """
81
+ # Validate project name
82
+ is_valid, error_msg = validate_project_name(project_name)
83
+ if not is_valid:
84
+ raise ValueError(f"Invalid project name: {error_msg}")
85
+
86
+ # Check if output path already exists
87
+ if output_path.exists():
88
+ raise FileExistsError(
89
+ f"Output path already exists: {output_path}. "
90
+ "Please choose a different name or remove the existing directory."
91
+ )
92
+
93
+ # Check if parent directory is writable
94
+ parent = output_path.parent
95
+ if not parent.exists():
96
+ try:
97
+ ensure_directory(parent)
98
+ except (OSError, PermissionError) as e:
99
+ raise PermissionError(f"Cannot create parent directory {parent}: {e}") from e
100
+
101
+ if not os.access(parent, os.W_OK):
102
+ raise PermissionError(f"Parent directory is not writable: {parent}")
103
+
104
+ # Generate project in temporary directory first (atomic creation)
105
+ temp_dir = Path(tempfile.mkdtemp(prefix=f"quickscale_{project_name}_"))
106
+
107
+ try:
108
+ # Generate project in temp directory
109
+ self._generate_project(project_name, temp_dir)
110
+
111
+ # Move to final location
112
+ shutil.move(str(temp_dir), str(output_path))
113
+
114
+ except Exception as e:
115
+ # Clean up temp directory on failure
116
+ if temp_dir.exists():
117
+ shutil.rmtree(temp_dir, ignore_errors=True)
118
+ raise RuntimeError(f"Failed to generate project: {e}") from e
119
+
120
+ def _generate_project(self, project_name: str, output_path: Path) -> None:
121
+ """Generate project structure in specified directory"""
122
+ # Context for template rendering
123
+ context = {
124
+ "project_name": project_name,
125
+ }
126
+
127
+ # Map of template files to output files
128
+ # Format: (template_path, output_path, executable)
129
+ file_mappings = [
130
+ # Root level files
131
+ ("README.md.j2", "README.md", False),
132
+ ("manage.py.j2", "manage.py", True),
133
+ ("pyproject.toml.j2", "pyproject.toml", False),
134
+ ("poetry.lock.j2", "poetry.lock", False),
135
+ (".gitignore.j2", ".gitignore", False),
136
+ (".dockerignore.j2", ".dockerignore", False),
137
+ (".editorconfig.j2", ".editorconfig", False),
138
+ (".env.example.j2", ".env.example", False),
139
+ ("Dockerfile.j2", "Dockerfile", False),
140
+ ("docker-compose.yml.j2", "docker-compose.yml", False),
141
+ # Project package files
142
+ ("project_name/__init__.py.j2", f"{project_name}/__init__.py", False),
143
+ ("project_name/urls.py.j2", f"{project_name}/urls.py", False),
144
+ ("project_name/wsgi.py.j2", f"{project_name}/wsgi.py", False),
145
+ ("project_name/asgi.py.j2", f"{project_name}/asgi.py", False),
146
+ # Settings files
147
+ (
148
+ "project_name/settings/__init__.py.j2",
149
+ f"{project_name}/settings/__init__.py",
150
+ False,
151
+ ),
152
+ ("project_name/settings/base.py.j2", f"{project_name}/settings/base.py", False),
153
+ ("project_name/settings/local.py.j2", f"{project_name}/settings/local.py", False),
154
+ (
155
+ "project_name/settings/production.py.j2",
156
+ f"{project_name}/settings/production.py",
157
+ False,
158
+ ),
159
+ # Template files
160
+ ("templates/base.html.j2", "templates/base.html", False),
161
+ ("templates/index.html.j2", "templates/index.html", False),
162
+ # Static files
163
+ ("static/css/style.css.j2", "static/css/style.css", False),
164
+ # CI/CD and quality tools
165
+ ("github/workflows/ci.yml.j2", ".github/workflows/ci.yml", False),
166
+ (".pre-commit-config.yaml.j2", ".pre-commit-config.yaml", False),
167
+ # Tests
168
+ ("tests/__init__.py.j2", "tests/__init__.py", False),
169
+ ("tests/conftest.py.j2", "tests/conftest.py", False),
170
+ ("tests/test_example.py.j2", "tests/test_example.py", False),
171
+ ]
172
+
173
+ # Render and write all files
174
+ for template_path, output_file, executable in file_mappings:
175
+ # Render template
176
+ template = self.env.get_template(template_path)
177
+ content = template.render(**context)
178
+
179
+ # Write file
180
+ output_file_path = output_path / output_file
181
+ write_file(output_file_path, content, executable=executable)
@@ -0,0 +1,61 @@
1
+ # Git
2
+ .git
3
+ .gitignore
4
+ .gitattributes
5
+
6
+ # Python
7
+ __pycache__/
8
+ *.py[cod]
9
+ *$py.class
10
+ *.so
11
+ .Python
12
+ env/
13
+ venv/
14
+ ENV/
15
+ env.bak/
16
+ venv.bak/
17
+ .venv/
18
+
19
+ # Django
20
+ *.log
21
+ db.sqlite3
22
+ db.sqlite3-journal
23
+ /media
24
+ /staticfiles
25
+
26
+ # Testing
27
+ .pytest_cache/
28
+ .coverage
29
+ htmlcov/
30
+ *.cover
31
+ .hypothesis/
32
+
33
+ # Development
34
+ .env
35
+ .env.local
36
+ .env.*.local
37
+ *.env
38
+
39
+ # IDE
40
+ .vscode/
41
+ .idea/
42
+ *.swp
43
+ *.swo
44
+ *~
45
+ .DS_Store
46
+
47
+ # Documentation
48
+ *.md
49
+ docs/
50
+ README.md
51
+ LICENSE
52
+
53
+ # CI/CD
54
+ .github/
55
+ .gitlab-ci.yml
56
+
57
+ # Other
58
+ *.bak
59
+ *.tmp
60
+ node_modules/
61
+ npm-debug.log*
@@ -0,0 +1,33 @@
1
+ # EditorConfig helps maintain consistent coding styles across editors
2
+ # https://editorconfig.org
3
+
4
+ root = true
5
+
6
+ [*]
7
+ charset = utf-8
8
+ end_of_line = lf
9
+ insert_final_newline = true
10
+ trim_trailing_whitespace = true
11
+
12
+ [*.{py,pyi}]
13
+ indent_style = space
14
+ indent_size = 4
15
+ max_line_length = 100
16
+
17
+ [*.{js,jsx,ts,tsx,json,yml,yaml}]
18
+ indent_style = space
19
+ indent_size = 2
20
+
21
+ [*.{html,css,scss}]
22
+ indent_style = space
23
+ indent_size = 2
24
+
25
+ [*.md]
26
+ trim_trailing_whitespace = false
27
+ max_line_length = off
28
+
29
+ [Makefile]
30
+ indent_style = tab
31
+
32
+ [*.{bat,cmd}]
33
+ end_of_line = crlf
@@ -0,0 +1,38 @@
1
+ # Django Core Settings
2
+ # SECURITY WARNING: keep the secret key used in production secret!
3
+ # Generate a new secret key for production: python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'
4
+ SECRET_KEY=django-insecure-change-this-in-production-{{ project_name }}
5
+
6
+ # SECURITY WARNING: don't run with debug turned on in production!
7
+ DEBUG=True
8
+
9
+ # Comma-separated list of allowed hosts
10
+ ALLOWED_HOSTS=localhost,127.0.0.1
11
+
12
+ # Server Configuration
13
+ # Port for Django development server and Docker containers
14
+ PORT=8000
15
+
16
+ # Database Configuration
17
+ # Format: postgresql://user:password@host:port/dbname
18
+ # For local development with docker-compose, use:
19
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5432/{{ project_name }}
20
+
21
+ # Email Configuration (optional)
22
+ # EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
23
+ # EMAIL_HOST=smtp.gmail.com
24
+ # EMAIL_PORT=587
25
+ # EMAIL_USE_TLS=True
26
+ # EMAIL_HOST_USER=your-email@example.com
27
+ # EMAIL_HOST_PASSWORD=your-app-password
28
+
29
+ # Static and Media Files
30
+ # STATIC_URL=/static/
31
+ # STATIC_ROOT=staticfiles
32
+ # MEDIA_URL=/media/
33
+ # MEDIA_ROOT=media
34
+
35
+ # Application Settings
36
+ # Add your custom application settings here
37
+ # ENABLE_FEATURE_X=True
38
+ # API_KEY=your-api-key-here
@@ -0,0 +1,74 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+
8
+ # Virtual Environments (Poetry-managed and manual)
9
+ .venv/
10
+ venv/
11
+ env/
12
+ ENV/
13
+ env.bak/
14
+ venv.bak/
15
+
16
+ # Django
17
+ *.log
18
+ db.sqlite3
19
+ db.sqlite3-journal
20
+ /media
21
+ /staticfiles
22
+ /static_root
23
+
24
+ # Environment variables
25
+ .env
26
+ .env.local
27
+ .env.*.local
28
+ *.env
29
+
30
+ # Testing
31
+ .pytest_cache/
32
+ .coverage
33
+ htmlcov/
34
+ *.cover
35
+ .hypothesis/
36
+ .tox/
37
+
38
+ # IDEs
39
+ .vscode/
40
+ .idea/
41
+ *.swp
42
+ *.swo
43
+ *~
44
+ .DS_Store
45
+ *.sublime-project
46
+ *.sublime-workspace
47
+
48
+ # Build artifacts
49
+ build/
50
+ dist/
51
+ *.egg-info/
52
+ .eggs/
53
+
54
+ # Database
55
+ *.db
56
+ *.sqlite
57
+ *.sqlite3
58
+
59
+ # Logs
60
+ logs/
61
+ *.log
62
+
63
+ # Docker volumes
64
+ postgres_data/
65
+ redis_data/
66
+
67
+ # OS
68
+ .DS_Store
69
+ Thumbs.db
70
+
71
+ # Other
72
+ *.bak
73
+ *.tmp
74
+ .cache/
@@ -0,0 +1,22 @@
1
+ # See https://pre-commit.com for more information
2
+ repos:
3
+ - repo: https://github.com/pre-commit/pre-commit-hooks
4
+ rev: v4.5.0
5
+ hooks:
6
+ - id: trailing-whitespace
7
+ - id: end-of-file-fixer
8
+ - id: check-yaml
9
+ - id: check-added-large-files
10
+ - id: check-json
11
+ - id: check-toml
12
+ - id: check-merge-conflict
13
+ - id: debug-statements
14
+
15
+ - repo: https://github.com/astral-sh/ruff-pre-commit
16
+ rev: v0.6.0
17
+ hooks:
18
+ # Run the linter
19
+ - id: ruff
20
+ args: [--fix]
21
+ # Run the formatter
22
+ - id: ruff-format
@@ -0,0 +1,82 @@
1
+ # syntax=docker/dockerfile:1
2
+ # Multi-stage build for production Django application
3
+
4
+ # Stage 1: Builder - Install dependencies and build artifacts
5
+ FROM python:3.11-slim as builder
6
+
7
+ # Set environment variables for Python
8
+ ENV PYTHONUNBUFFERED=1 \
9
+ PYTHONDONTWRITEBYTECODE=1 \
10
+ PIP_NO_CACHE_DIR=1 \
11
+ PIP_DISABLE_PIP_VERSION_CHECK=1
12
+
13
+ # Install system dependencies required for building Python packages
14
+ RUN apt-get update && apt-get install -y --no-install-recommends \
15
+ build-essential \
16
+ libpq-dev \
17
+ && rm -rf /var/lib/apt/lists/*
18
+
19
+ # Install Poetry
20
+ RUN pip install poetry==1.7.1
21
+
22
+ # Set working directory
23
+ WORKDIR /app
24
+
25
+ # Copy dependency files
26
+ COPY pyproject.toml poetry.lock ./
27
+
28
+ # Configure Poetry to not create virtual environments (we're in a container)
29
+ RUN poetry config virtualenvs.create false
30
+
31
+ # Install dependencies (--no-root: don't install project itself, only dependencies)
32
+ RUN poetry install --only main --no-root --no-interaction --no-ansi
33
+
34
+ # Stage 2: Runtime - Create minimal production image
35
+ FROM python:3.11-slim
36
+
37
+ # Set environment variables
38
+ ENV PYTHONUNBUFFERED=1 \
39
+ PYTHONDONTWRITEBYTECODE=1 \
40
+ DJANGO_SETTINGS_MODULE={{ project_name }}.settings.production
41
+
42
+ # Port configuration
43
+ ARG PORT=8000
44
+ ENV PORT=${PORT}
45
+
46
+ # Install runtime dependencies only
47
+ RUN apt-get update && apt-get install -y --no-install-recommends \
48
+ libpq5 \
49
+ && rm -rf /var/lib/apt/lists/*
50
+
51
+ # Create non-root user for security
52
+ RUN groupadd -r django && useradd -r -g django django
53
+
54
+ # Set working directory
55
+ WORKDIR /app
56
+
57
+ # Copy installed dependencies from builder stage
58
+ COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
59
+ COPY --from=builder /usr/local/bin /usr/local/bin
60
+
61
+ # Copy application code
62
+ COPY --chown=django:django . .
63
+
64
+ # Create necessary directories with correct permissions
65
+ RUN mkdir -p /app/staticfiles /app/media /app/logs && \
66
+ chown -R django:django /app
67
+
68
+ # Switch to non-root user
69
+ USER django
70
+
71
+ # Collect static files
72
+ RUN python manage.py collectstatic --noinput
73
+
74
+ # Expose port
75
+ EXPOSE ${PORT}
76
+
77
+ # Health check - verify gunicorn is responding (any response including 404/500 means it's alive)
78
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
79
+ CMD python -c "import os, urllib.request; urllib.request.urlopen(f'http://localhost:{os.environ.get(\"PORT\", \"8000\")}/', timeout=5)"
80
+
81
+ # Run gunicorn
82
+ CMD gunicorn {{ project_name }}.wsgi:application --bind 0.0.0.0:${PORT} --workers 4 --timeout 60 --access-logfile - --error-logfile -