-
-
Notifications
You must be signed in to change notification settings - Fork 119
feat(ext.generate): add optional features support for templates #768
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
Changes from all commits
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 |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| # Demo: Generate Extension - Features | ||
|
|
||
| Demonstrates the `features` support in the `generate` extension, which allows | ||
| `.generate.yml` templates to conditionally include/exclude files and prompt for | ||
| additional variables based on optional feature toggles. | ||
|
|
||
| ## Template | ||
|
|
||
| The `templates/generate/webapp/` template defines: | ||
|
|
||
| - **Base variable:** `project_name` | ||
| - **Feature: `docker`** (default: enabled) — includes `Dockerfile` and | ||
| `.dockerignore`, prompts for `python_version` | ||
| - **Feature: `docker_compose`** (default: enabled, **requires: docker**) — | ||
| includes `docker-compose.yml` | ||
|
|
||
| ## Usage | ||
|
|
||
| Run from this directory: | ||
|
|
||
| ```bash | ||
| # Generate with all defaults (docker=on, docker_compose=on) | ||
| pdm run python myapp.py generate webapp /tmp/myproject --defaults | ||
|
|
||
| # Generate interactively (prompts for each variable and feature) | ||
| pdm run python myapp.py generate webapp /tmp/myproject | ||
|
|
||
| # Generate with --force to overwrite existing output | ||
| pdm run python myapp.py generate webapp /tmp/myproject --defaults --force | ||
| ``` | ||
|
|
||
| ## Configuration | ||
|
|
||
| *templates/generate/webapp* | ||
|
|
||
| ```yaml | ||
| --- | ||
|
|
||
| variables: | ||
| - name: project_name | ||
| prompt: "Project Name" | ||
| default: "myproject" | ||
| case: lower | ||
|
|
||
| features: | ||
| - name: docker | ||
| default: true | ||
| enabled: | ||
| variables: | ||
| - name: python_version | ||
| prompt: "Python Version (for Docker)" | ||
| default: "3.13" | ||
| disabled: | ||
| ignore: | ||
| - '.*Dockerfile.*' | ||
| - '.*\.dockerignore.*' | ||
|
|
||
| - name: docker_compose | ||
| default: true | ||
| requires: | ||
| - docker | ||
| disabled: | ||
| ignore: | ||
| - '.*docker-compose.*' | ||
| ``` | ||
|
|
||
| ## What to Expect | ||
|
|
||
| **With defaults** (both features enabled): | ||
|
|
||
| ```text | ||
| /tmp/myproject/ | ||
| ├── .dockerignore | ||
| ├── Dockerfile | ||
| ├── README.md | ||
| ├── app.py | ||
| └── docker-compose.yml | ||
| ``` | ||
|
|
||
| **With docker disabled** (docker_compose is auto-disabled via `requires`): | ||
|
|
||
| ```text | ||
| /tmp/myproject/ | ||
| ├── README.md | ||
| └── app.py | ||
| ``` | ||
|
|
||
| No Dockerfile, no docker-compose.yml — disabling docker automatically | ||
| disables docker_compose because it requires docker. | ||
|
|
||
| **With docker enabled, docker_compose disabled**: | ||
|
|
||
| ```text | ||
| /tmp/myproject/ | ||
| ├── .dockerignore | ||
| ├── Dockerfile | ||
| ├── README.md | ||
| └── app.py | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| .git | ||
| .gitignore | ||
| .venv | ||
| __pycache__ | ||
| *.pyc | ||
| *.pyo | ||
| *.pyd | ||
| .Python | ||
| .pytest_cache | ||
| .mypy_cache | ||
| .ruff_cache | ||
| .coverage | ||
| .env | ||
| .env.* | ||
| node_modules | ||
| *.log | ||
| .DS_Store |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| --- | ||
|
|
||
| variables: | ||
| - name: project_name | ||
| prompt: "Project Name" | ||
| default: "myproject" | ||
| case: lower | ||
|
|
||
| features: | ||
| - name: docker | ||
| default: true | ||
| enabled: | ||
| variables: | ||
| - name: python_version | ||
| prompt: "Python Version (for Docker)" | ||
| default: "3.13" | ||
| disabled: | ||
| ignore: | ||
| - '.*Dockerfile.*' | ||
| - '.*\.dockerignore.*' | ||
|
|
||
| - name: docker_compose | ||
| default: true | ||
| requires: | ||
| - docker | ||
| disabled: | ||
| ignore: | ||
| - '.*docker-compose.*' |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,12 @@ | ||||||||||||||||||||||||||||
| FROM python:{{ python_version }}-slim | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| WORKDIR /app | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| COPY . . | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Drop root: create an unprivileged user, hand it /app, switch to it. | ||||||||||||||||||||||||||||
| RUN useradd --create-home --shell /bin/bash appuser \ | ||||||||||||||||||||||||||||
| && chown -R appuser:appuser /app | ||||||||||||||||||||||||||||
| USER appuser | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| CMD ["python", "app.py"] | ||||||||||||||||||||||||||||
|
Comment on lines
+3
to
+12
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. 🧩 Analysis chain🏁 Script executed: # Check if the Dockerfile exists and read its full content
find demo -name "Dockerfile" -type f | head -20Repository: datafolklabs/cement Length of output: 122 🏁 Script executed: # Look for the specific file mentioned
cat demo/generate-features/templates/generate/webapp/DockerfileRepository: datafolklabs/cement Length of output: 149 🏁 Script executed: # Check for .dockerignore in the same directory
ls -la demo/generate-features/templates/generate/webapp/Repository: datafolklabs/cement Length of output: 473 Drop root before starting the app. The template never switches away from the default root user, so every generated container runs with full privileges. Add an unprivileged user and hand off Suggested fix FROM python:{{ python_version }}-slim
WORKDIR /app
COPY . .
+RUN useradd --create-home --shell /usr/sbin/nologin appuser \
+ && chown -R appuser:appuser /app
+USER appuser
CMD ["python", "app.py"]📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| # {{ project_name }} | ||
|
|
||
| Generated by myapp. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| """{{ project_name }} - main application.""" | ||
|
|
||
|
|
||
| def main(): | ||
| print("Hello from {{ project_name }}!") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| services: | ||
| app: | ||
| build: . | ||
| container_name: {{ project_name }} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| # Fixture: a real Python package whose import raises ModuleNotFoundError | ||
| # for a *different* module name than the package itself. Used by | ||
| # tests/ext/test_ext_generate.py to verify that setup_template_items | ||
| # propagates transitive ModuleNotFoundError raised inside the user's | ||
| # template module instead of silently swallowing it as "module not found". | ||
| import nonexistent_transitive_dep # noqa: F401 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| --- | ||
|
|
||
| variables: | ||
|
|
||
| - name: app_name | ||
| prompt: App Name | ||
| default: myapp | ||
|
|
||
| features: | ||
|
|
||
| - name: feature1 | ||
| default: true | ||
| requires: | ||
| - nonexistent |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| app_name => {{ app_name }} |
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.
🧩 Analysis chain
🏁 Script executed:
fd -type f -name "ext_generate.py"Repository: datafolklabs/cement
Length of output: 1941
🏁 Script executed:
cat -n cement/ext/ext_generate.py | head -150Repository: datafolklabs/cement
Length of output: 1941
🏁 Script executed:
rg -n "def _process_features|def _generate" cement/ext/ext_generate.py -A 10Repository: datafolklabs/cement
Length of output: 1186
🏁 Script executed:
Repository: datafolklabs/cement
Length of output: 601
🏁 Script executed:
Repository: datafolklabs/cement
Length of output: 446
Rename
varsparameter and variable before merge.The parameter
varson line 31 and the variable assignment on line 124 shadow Python's built-invars()function. Ruff flags these as A002 and A001 violations respectively. Renaming both tovariables(or similar) fixes the lint failures and improves readability since the data actually represents variables. This is required per the coding guideline: "All code must pass ruff linting (v0.3.2+)".🧰 Tools
🪛 Ruff (0.15.12)
[error] 31-31: Function argument
varsis shadowing a Python builtin(A002)
🤖 Prompt for AI Agents