-
Notifications
You must be signed in to change notification settings - Fork 0
Add a CLI Wrapper for Django Project Initialization #75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
88f6a97
8aa31b5
6db1211
bedb0d8
04e0906
90f1fa6
a8558bc
e083e3c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,5 +11,7 @@ coverage.xml | |
| data/ | ||
| dist/ | ||
| htmlcov/ | ||
| output/ | ||
| site/ | ||
| src/booster.egg-info/ | ||
| .DS_Store | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| # 0002 Use Custom User Model | ||
| # 0003 Use Custom User Model | ||
|
|
||
| - Date: 2024-04-10 | ||
| - Author(s): [Chen Zhang][chen] | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| # 0004 Replacing startproject with a CLI Wrapper | ||
|
|
||
| - Date: 2024-11-22 | ||
| - Author(s): [Zahra Alizadeh][zahra] | ||
| - Status: `Draft` | ||
|
|
||
| ## Decision | ||
|
|
||
| We have decided to build a custom CLI Django's built-in `startproject` command. This wrapper will run `startproject` to scaffold the initial project and then apply additional configuration and customizations based on a user-defined configuration file. This approach allows us to dynamically customize project structures based on configuration files and support reusable variants. | ||
|
|
||
| ## Context | ||
|
|
||
| ### Background | ||
|
|
||
| Django's `startproject` command provides a basic project scaffolding mechanism. While this is sufficient for standard projects, it falls short when projects require: | ||
|
|
||
| - Dynamic configurations, such as database engines, general settings e.g. timezone, and optional components and apps. | ||
| - Customizable directory and file structures tailored to team or project-specific needs. | ||
| - Reusability across multiple projects with different configurations or variants (e.g., Docker, Celery, or CI/CD pipelines). | ||
|
|
||
| ### Existing Approach | ||
|
|
||
|
|
||
| The current approach uses `django-admin startproject` with the `--template` option to specify a custom project template. While this allows some customization, it introduces several issues: | ||
|
|
||
| 1. __Complex Command Syntax__: | ||
| The command requires specifying a full template path along with multiple options like `--extension` and `--exclude`. Example: | ||
|
|
||
| ```shell | ||
| django-admin startproject --template path/to/django-template/template/ --extension py,env,sh,toml,yml --exclude nothing <project_name> | ||
| ``` | ||
| This is verbose, error-prone, and hard to remember. | ||
|
|
||
| 2. __Limited Customization__: | ||
| - The `--template` option does not support dynamic rendering of file names or content, or conditional inclusion of components. | ||
| - Custom features (e.g., Celery support or different configurations) requires separate templates or post-processing. | ||
|
|
||
|
|
||
| This approach is overly complicated, lacks flexibility, and is not scalable for dynamic, reusable templates. A simpler, more customizable solution is needed to improve developer productivity and enforce consistency. | ||
|
|
||
| ## New Approach | ||
|
|
||
|
|
||
| The custom CLI wrapper enhances this process by: | ||
|
|
||
| 1. Running the `startproject` command to scaffold the project. | ||
| 2. Applying additional setup steps, such as: | ||
| - Modifying settings.py to add optional configurations (e.g., databases, installed apps). | ||
| - Adding optional files (e.g., .env, Dockerfiles) based on user preferences. | ||
| 3. Allowing users to configure the setup via a YAML or TOML configuration file. | ||
|
|
||
|
|
||
| ## Implications | ||
|
|
||
| ### Positive Implications | ||
|
|
||
| 1. __Flexibility__: | ||
| - Supports dynamic configurations (e.g., different database backends, optional apps). | ||
| - Simplifies the addition of reusable variants like Docker or CI/CD pipelines. | ||
|
|
||
| 2. __Consistency__: | ||
| - Enforces consistent project structures across teams and projects. | ||
| - Reduces human error in project setup. | ||
|
|
||
| 3. __Future Scalability__: | ||
| - New variants or configurations can be added without disrupting existing workflows. | ||
|
|
||
| 4. __Customization__: | ||
| - Allows user-defined settings through configuration files (e.g., YAML or TOML). | ||
|
|
||
| ### Negative Implications | ||
|
|
||
| 1. __Initial Overhead__: Developing and testing the CLI wrapper requires upfront investment | ||
| 2. __Maintenance__: The templates and wrapper tool will need to be maintained as requirements evolve. | ||
| 3. __Learning Curve__: Developers familiar with startproject will need to learn the new CLI tool. | ||
|
|
||
| <!-- Links --> | ||
| [zahra]: mailto:zahra.alizadeh@ackama.com |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| [build-system] | ||
| requires = ["setuptools", "wheel"] | ||
| build-backend = "setuptools.build_meta" | ||
|
|
||
| [project] | ||
| name = "booster" | ||
| version = "0.1.0" | ||
| description = "A CLI tool for generating Django projects for Ackama." | ||
| readme = "README.md" | ||
| requires-python = ">=3.12" | ||
| license = {text = "BSD-3-Clause"} | ||
| dependencies = [ | ||
| "typer[all]>=0.9.0", | ||
| "jinja2>=3.1.0", | ||
| "pyyaml>=6.0" | ||
| ] | ||
|
|
||
| [project.scripts] | ||
| booster = "src.booster:app" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| project_name: test_project | ||
| template_path: template/ | ||
| output_dir: ./output/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| import shutil | ||
| import subprocess | ||
| from pathlib import Path | ||
|
|
||
| import typer | ||
| import yaml | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nitpick (non-blocking): Can we use toml instead? I have a pet peeve about yaml - I find it hard to format correctly for some reason. Toml is a simpler standard, we already use it (
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a good point. I'll update it. |
||
|
|
||
| app = typer.Typer() | ||
|
|
||
| BASE_DIR = Path(__file__).resolve().parent.parent | ||
| OUTPUT_DIR = BASE_DIR / "output" | ||
| DEFAULT_CONFIG = BASE_DIR / "sample_config.yml" | ||
|
Comment on lines
+10
to
+12
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: Remove these defaults. As already mentioned - I don't think a default config file makes sense. So I also think that the output dir should either be the current working directory or come from the now mandatory config file. This is being designed as a stand alone script, installed from a wheel using pipx. As such the |
||
|
|
||
|
|
||
| def load_config(config_path: Path): | ||
| """Load project settings from YAML configuration file.""" | ||
| with open(config_path, "r") as file: | ||
| config = yaml.safe_load(file) | ||
| return config | ||
|
|
||
|
|
||
| def django_start_project(config): | ||
| """Create a Django project using the specified configuration.""" | ||
| project_name = config["project_name"] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue: Check this is a valid Python package name Since
|
||
| template_path = config.get("template_path", "template/") | ||
| output_dir = Path(config.get("output_dir")) or OUTPUT_DIR | ||
| output_dir.mkdir(parents=True, exist_ok=True) | ||
| project_path = output_dir / project_name | ||
| if project_path.exists(): | ||
| typer.echo(f"Directory '{project_path}' already exists.") | ||
| if typer.confirm( | ||
| "Do you want to clear this directory and start fresh?", default=False | ||
| ): | ||
| # Delete the existing directory | ||
| shutil.rmtree(project_path) | ||
| typer.echo(f"Cleared the directory: {project_path}") | ||
| else: | ||
| typer.echo("Aborting project creation.") | ||
| raise SystemExit(1) | ||
| project_path.mkdir(parents=True, exist_ok=True) | ||
|
|
||
| # Call django-admin startproject | ||
| typer.echo(f"Starting Django project '{project_name}' in {project_path}...") | ||
|
|
||
| try: | ||
| subprocess.run( | ||
| [ | ||
| "django-admin", | ||
| "startproject", | ||
| project_name, | ||
| str(project_path), | ||
| "--template", | ||
| template_path, | ||
| "--extension", | ||
| "py,env,sh,toml,yml", | ||
| "--exclude", | ||
| "nothing", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: Perhaps a comment here as to why we are using |
||
| ], | ||
| check=True, | ||
| ) | ||
| typer.echo(f"Django project '{project_name}' created successfully!") | ||
| except subprocess.CalledProcessError: | ||
| typer.echo(f"Failed to create the project '{project_name}'.", err=True) | ||
| raise SystemExit(1) | ||
|
|
||
|
|
||
| @app.command() | ||
| def fire( | ||
| config_path: Path = typer.Option( | ||
| DEFAULT_CONFIG, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: Make this a mandatory argument The script cannot proceed without a config file. A default for the name of a project for example is never going to be reasonable. So might as well make it a mandatory argument. |
||
| "--config", | ||
| help="Path to the project configuration YAML file", | ||
| ) | ||
| ): | ||
| """ | ||
| Start a new Django project using Ackama's django template | ||
| based on a YAML configuration file. | ||
| """ | ||
| config = load_config(config_path) | ||
| django_start_project(config) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| app() | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: I don't think jinja2 is needed any more
If we don't need it - please remove.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops!