From 12e03632272f7f5f824e31716fb4813b3ca5c0db Mon Sep 17 00:00:00 2001
From: vanolucas <10572472+vanolucas@users.noreply.github.com>
Date: Sat, 25 May 2024 14:57:11 +0200
Subject: [PATCH] feat: add Streamlit app option to cookiecutter (#2)
* feat: add with_streamlit cookiecutter option
* chore: rename with_streamlit cookiecutter var
* feat: add VSCode debug launch config
* chore: update LICENSE date
* chore(README): fixed syntax
---
.gitignore | 3 +-
README.md | 5 ++--
cookiecutter.json | 1 +
hooks/post_gen_project.py | 6 +++-
.../.gitignore | 3 +-
.../.vscode/launch.json | 29 +++++++++++++++++++
.../LICENSE | 4 +--
.../README.md | 5 +++-
.../pyproject.toml | 28 +++++++++++++++++-
.../app.py | 7 +++++
10 files changed, 82 insertions(+), 9 deletions(-)
create mode 100644 {{ cookiecutter.__project_name_kebab_case }}/.vscode/launch.json
create mode 100644 {{ cookiecutter.__project_name_kebab_case }}/src/{{ cookiecutter.__project_name_snake_case }}/app.py
diff --git a/.gitignore b/.gitignore
index 98d2203a..72d0fd55 100644
--- a/.gitignore
+++ b/.gitignore
@@ -48,4 +48,5 @@ __pycache__/
.terraform/
# VS Code
-.vscode/
+.vscode/*
+!.vscode/launch.json
diff --git a/README.md b/README.md
index c4ec1a71..3e3a2f00 100644
--- a/README.md
+++ b/README.md
@@ -78,14 +78,15 @@ To update your Python project to the latest template version:
| `development_environment`
["simple", "strict"] | Whether to configure the development environment with a focus on simplicity or with a focus on strictness. In strict mode, additional [Ruff rules](https://beta.ruff.rs/docs/rules/) are added, and tools such as [Mypy](https://github.com/python/mypy) and [Pytest](https://github.com/pytest-dev/pytest) are set to strict mode. |
| `with_conventional_commits`
["0", "1"] | If "1", [Commitizen](https://github.com/commitizen-tools/commitizen) will verify that your commits follow the [Conventional Commits](https://www.conventionalcommits.org/) standard. In return, `cz bump` may be used to automate [Semantic Versioning](https://semver.org/) and [Keep A Changelog](https://keepachangelog.com/). |
| `with_fastapi_api`
["0", "1"] | If "1", [FastAPI](https://github.com/tiangolo/fastapi) is added as a run time dependency, FastAPI API stubs and tests are added, a `poe api` command for serving the API is added. |
+| `with_streamlit_app`
[false, true] | If `true`, [Streamlit](https://streamlit.io/) is added as a run time dependency and a sample Streamlit app is created. |
| `with_typer_cli`
["0", "1"] | If "1", [Typer](https://github.com/tiangolo/typer) is added as a run time dependency, Typer CLI stubs and tests are added, the package itself is registered as a CLI. |
| `private_package_repository_name`
"Private Package Repository" | Optional name of a private package repository to install packages from and publish this package to. |
| `private_package_repository_url`
"" | Optional URL of a private package repository to install packages from and publish this package to. Make sure to include the `/simple` suffix. For instance, when using a GitLab Package Registry this value should be of the form `https://gitlab.com/api/v4/projects/` `{project_id}` `/packages/pypi/simple`. |
-| `with_postgresql`
"false" | Enable a Docker container with PostgreSQL running. The PostgreSQL data will be stored in a Docker volume. |
+| `with_postgresql`
[false, true] | Enable a Docker container with PostgreSQL running. The PostgreSQL data will be stored in a Docker volume. |
| `postgresql_server`
"db" | PostgreSQL server name. (Only used if `with_postgresql` is `true`.) |
| `postgresql_user`
"db" | PostgreSQL admin username. (Only used if `with_postgresql` is `true`.) |
| `postgresql_password`
"db" | PostgreSQL admin user password. (Only used if `with_postgresql` is `true`.) |
| `postgresql_db_name`
"db" | Name of the database to create in the PostgreSQL server. (Only used if `with_postgresql` is `true`.) |
| `postgresql_forward_port`
"5432" | Port to expose the PostgreSQL server. (Only used if `with_postgresql` is `true`.) |
-| `with_adminer`
"false" | Enable a Docker container with the Adminer SQL database management Web interface. |
+| `with_adminer`
[false, true] | Enable a Docker container with the Adminer SQL database management Web interface. |
| `adminer_forward_port`
"51003" | Port to expose the HTTP server for the Adminer database management Web interface. (Only used if `with_adminer` is `true`.) |
diff --git a/cookiecutter.json b/cookiecutter.json
index b3193291..661c3371 100644
--- a/cookiecutter.json
+++ b/cookiecutter.json
@@ -15,6 +15,7 @@
],
"with_conventional_commits": "{% if cookiecutter.development_environment == 'simple' %}0{% else %}1{% endif %}",
"with_fastapi_api": "0",
+ "with_streamlit_app": false,
"with_typer_cli": "0",
"private_package_repository_name": "",
"private_package_repository_url": "",
diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py
index be713df1..557c6213 100644
--- a/hooks/post_gen_project.py
+++ b/hooks/post_gen_project.py
@@ -1,10 +1,10 @@
import os
-import shutil
# Read Cookiecutter configuration.
project_name = "{{ cookiecutter.__project_name_snake_case }}"
development_environment = "{{ cookiecutter.development_environment }}"
with_fastapi_api = int("{{ cookiecutter.with_fastapi_api }}")
+with_streamlit_app: bool = {{ cookiecutter.with_streamlit_app }}
with_typer_cli = int("{{ cookiecutter.with_typer_cli }}")
is_application = "{{ cookiecutter.project_type == 'app' }}" == "True"
@@ -18,6 +18,10 @@
os.remove(f"src/{project_name}/api.py")
os.remove("tests/test_api.py")
+# Remove Streamlit app if not selected.
+if not with_streamlit_app:
+ os.remove(f"src/{project_name}/app.py")
+
# Remove Typer if not selected.
if not with_typer_cli:
os.remove(f"src/{project_name}/cli.py")
diff --git a/{{ cookiecutter.__project_name_kebab_case }}/.gitignore b/{{ cookiecutter.__project_name_kebab_case }}/.gitignore
index 85fe0902..3206e2c6 100644
--- a/{{ cookiecutter.__project_name_kebab_case }}/.gitignore
+++ b/{{ cookiecutter.__project_name_kebab_case }}/.gitignore
@@ -51,4 +51,5 @@ __pycache__/
.terraform/
# VS Code
-.vscode/
+.vscode/*
+!.vscode/launch.json
diff --git a/{{ cookiecutter.__project_name_kebab_case }}/.vscode/launch.json b/{{ cookiecutter.__project_name_kebab_case }}/.vscode/launch.json
new file mode 100644
index 00000000..2511d886
--- /dev/null
+++ b/{{ cookiecutter.__project_name_kebab_case }}/.vscode/launch.json
@@ -0,0 +1,29 @@
+{
+ "configurations": [
+ {%- if cookiecutter.with_streamlit_app %}
+ {
+ "name": "Streamlit app debug",
+ "type": "debugpy",
+ "request": "launch",
+ "module": "streamlit",
+ "args": [
+ "run",
+ "--browser.gatherUsageStats",
+ "false",
+ "--server.address",
+ "0.0.0.0",
+ "--server.port",
+ "8000",
+ "src/{{ cookiecutter.__project_name_snake_case }}/app.py"
+ ],
+ "justMyCode": true
+ },
+ {%- endif %}
+ {
+ "name": "Python Debugger: Python File",
+ "type": "debugpy",
+ "request": "launch",
+ "program": "${file}"
+ }
+ ]
+}
diff --git a/{{ cookiecutter.__project_name_kebab_case }}/LICENSE b/{{ cookiecutter.__project_name_kebab_case }}/LICENSE
index bdeb5d07..01558e7f 100644
--- a/{{ cookiecutter.__project_name_kebab_case }}/LICENSE
+++ b/{{ cookiecutter.__project_name_kebab_case }}/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023 {{ cookiecutter.author_name }}
+Copyright (c) 2024 {{ cookiecutter.author_name }}
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
\ No newline at end of file
+SOFTWARE.
diff --git a/{{ cookiecutter.__project_name_kebab_case }}/README.md b/{{ cookiecutter.__project_name_kebab_case }}/README.md
index 0c24ab6b..9d4611fb 100644
--- a/{{ cookiecutter.__project_name_kebab_case }}/README.md
+++ b/{{ cookiecutter.__project_name_kebab_case }}/README.md
@@ -59,6 +59,7 @@ import {{ cookiecutter.__project_name_snake_case }}
1. Set up Git to use SSH
1. Configure SSH to automatically load your SSH keys:
+
```sh
cat << EOF >> ~/.ssh/config
@@ -78,6 +79,7 @@ import {{ cookiecutter.__project_name_snake_case }}
1. [Install Docker Desktop](https://www.docker.com/get-started).
- _Linux only_:
- Export your user's user id and group id so that [files created in the Dev Container are owned by your user](https://github.com/moby/moby/issues/3206):
+
```sh
cat << EOF >> ~/.bashrc
@@ -119,6 +121,7 @@ import {{ cookiecutter.__project_name_snake_case }}
Development environments
The following development environments are supported:
+
1. ⭐️ _Dev Container (with container volume)_: click on [Open in Dev Containers](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url={{ cookiecutter.project_url.replace("https://", "git@").replace(".com/", ".com:") if cookiecutter.private_package_repository_url else cookiecutter.project_url }}) to clone this repository in a container volume and create a Dev Container with VS Code.
1. _Dev Container_: clone this repository, open it with VS Code, and run Ctrl/⌘ + ⇧ + P → _Dev Containers: Reopen in Container_.
1. _PyCharm_: clone this repository, open it with PyCharm, and [configure Docker Compose as a remote interpreter](https://www.jetbrains.com/help/pycharm/using-docker-compose-as-a-remote-interpreter.html#docker-compose-remote) with the `dev` service.
@@ -134,10 +137,10 @@ The following development environments are supported:
- Run `poe` from within the development environment to print a list of [Poe the Poet](https://github.com/nat-n/poethepoet) tasks available to run on this project.
- Run `poe lint` to perform all code checks.
- Run `poe test` to run all Pytest unit tests.
-- Run `poetry install` from within the development environment to read the `pyproject.toml` file, resolve the dependencies and install them.
- Run `poetry add {package}` from within the development environment to install a run time dependency and add it to `pyproject.toml` and `poetry.lock`. Add `--group test` or `--group dev` to install a CI or development dependency, respectively.
- Run `poetry update` from within the development environment to upgrade all dependencies to the latest versions allowed by `pyproject.toml`.
{%- if cookiecutter.with_conventional_commits|int %}
+- Run `poetry install` from within the development environment to read the `pyproject.toml` file, resolve the dependencies and install them.
- Run `cz bump` to bump the {{ cookiecutter.project_type }}'s version, update the `CHANGELOG.md`, and create a git tag.
{%- endif %}
diff --git a/{{ cookiecutter.__project_name_kebab_case }}/pyproject.toml b/{{ cookiecutter.__project_name_kebab_case }}/pyproject.toml
index 56dbbc96..0c33f33b 100644
--- a/{{ cookiecutter.__project_name_kebab_case }}/pyproject.toml
+++ b/{{ cookiecutter.__project_name_kebab_case }}/pyproject.toml
@@ -33,6 +33,9 @@ gunicorn = ">=21.2.0"
poethepoet = ">=0.25.0"
{%- endif %}
python = ">={{ cookiecutter.python_version }},<4.0"
+{%- if cookiecutter.with_streamlit_app %}
+streamlit = "^1.34.0"
+{%- endif %}
{%- if cookiecutter.with_typer_cli|int %}
typer = { extras = ["all"], version = ">=0.12.0" }
{%- endif %}
@@ -204,10 +207,33 @@ convention = "{{ cookiecutter.__docstring_style|lower }}"
{%- elif cookiecutter.project_type == "app" %}
[tool.poe.tasks.app]
- help = "Serve the app"
+ help = "Serve the{% if cookiecutter.with_streamlit_app %} Streamlit{% endif %} app"
[[tool.poe.tasks.app.sequence]]
cmd = "echo 'Serving app...'"
+ {%- if cookiecutter.with_streamlit_app %}
+
+ [[tool.poe.tasks.app.sequence]]
+ cmd = """
+ streamlit run
+ --browser.gatherUsageStats false
+ --server.address $host
+ --server.port $port
+ src/{{ cookiecutter.__project_name_snake_case }}/app.py
+ """
+
+ [[tool.poe.tasks.app.args]]
+ help = "Bind socket to this host (default: 0.0.0.0)"
+ name = "host"
+ options = ["--host"]
+ default = "0.0.0.0"
+
+ [[tool.poe.tasks.app.args]]
+ help = "Bind socket to this port (default: 8000)"
+ name = "port"
+ options = ["--port"]
+ default = "8000"
+ {%- endif %}
{%- endif %}
[tool.poe.tasks.docs]
diff --git a/{{ cookiecutter.__project_name_kebab_case }}/src/{{ cookiecutter.__project_name_snake_case }}/app.py b/{{ cookiecutter.__project_name_kebab_case }}/src/{{ cookiecutter.__project_name_snake_case }}/app.py
new file mode 100644
index 00000000..d81c4065
--- /dev/null
+++ b/{{ cookiecutter.__project_name_kebab_case }}/src/{{ cookiecutter.__project_name_snake_case }}/app.py
@@ -0,0 +1,7 @@
+"""Streamlit app."""
+
+from importlib.metadata import version
+
+import streamlit as st
+
+st.title(f"{{ cookiecutter.__project_name_kebab_case }} v{version('{{ cookiecutter.__project_name_kebab_case }}')}")