diff --git a/README.md b/README.md index 57cf426..cdd2ae8 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ Feedback and contributions are very welcome! Learn more in the [Contributing](#- * `create_cli` (yes or no): if you plan to build an application with a command line interface (CLI), select *yes* here. This will integrate a template for the CLI into your project - minimal boilerplate guaranteed! (We're leveraging the awesome [typer](https://typer.tiangolo.com/) library for this.) * `config_file`: select your preferred config format. It is best practice to store your configuration separate from your code, even for small projects, but because there are a gazillion ways to do this, each project seems to reinvents the wheel. We want to provide a few options to set you up with a working configuration: - `yaml`: use [YAML](https://yaml.org/) as your configuration file format. Easy to read and write, widely adopted, relies on the [PyYAML](https://pyyaml.org/) package. - - `hocon`: use [HOCON](https://github.com/lightbend/config/blob/master/HOCON.md) as your configuration file format. It is a superset of JSON, very resilient (it's really hard to make a breaking syntax error) and comes with powerful functions, e.g. for inheritance or variable substitution. In this example you can find two environment configurations (`dev.conf`, `prod.conf`) that override parts of the default configuration. Relies on the [pyhocon](https://github.com/chimpler/pyhocon/) package. + - `hocon`: use [HOCON](https://github.com/lightbend/config/blob/master/HOCON.md) as your configuration file format. It is a superset of JSON, very resilient (it's really hard to make a breaking syntax error) and comes with powerful functions, e.g. for inheritance or variable substitution. In this example you can find two environment configurations (`dev.conf`, `prod.conf`) that override parts of the default configuration. In particular, parts of the configuration are populated from environment variables, which are commonly used to configure processes running in containers (such as e.g. Kubeflow pipeline operators). Relies on the [pyhocon](https://github.com/chimpler/pyhocon/) package. - `none`: don't need any configuration or want to do your own thing? choose this option. * `code_formatter`: a code formatter is a powerful tool that can help teams to stick to a common code style. However, a formatter cannot solve every code style problem for you and it may lead to issues for users that are not aware of how it works. Always talk to your team about [PEP 8](https://www.python.org/dev/peps/pep-0008/) and a common code style, then choose the right formatter for you (or none at all): - [`black`](https://github.com/psf/black): *the uncompromising Python code formatter*. diff --git a/{{cookiecutter.project_slug}}/Dockerfile__conda b/{{cookiecutter.project_slug}}/Dockerfile__conda index e703509..339b40a 100644 --- a/{{cookiecutter.project_slug}}/Dockerfile__conda +++ b/{{cookiecutter.project_slug}}/Dockerfile__conda @@ -16,5 +16,8 @@ SHELL ["mamba", "run", "-n", "{{cookiecutter.module_name}}_env", "/bin/bash", "- COPY . . RUN python setup.py install +WORKDIR / +COPY ./config ./config + # ENTRYPOINT doesn't use the same shell as RUN so you need the conda stuff ENTRYPOINT ["mamba", "run", "-n", "{{cookiecutter.module_name}}_env", "python", "-OO", "-m", "{{ cookiecutter.module_name }}"] diff --git a/{{cookiecutter.project_slug}}/Dockerfile__pip b/{{cookiecutter.project_slug}}/Dockerfile__pip index 1d498b8..49547af 100644 --- a/{{cookiecutter.project_slug}}/Dockerfile__pip +++ b/{{cookiecutter.project_slug}}/Dockerfile__pip @@ -1,12 +1,37 @@ ARG PYTHON_VERSION=3.11 -FROM python:${PYTHON_VERSION}-bullseye -LABEL maintainer="{{ cookiecutter.company_name if cookiecutter.company_name else cookiecutter.full_name }}" +# +# First stage: get dependencies, build the project, store binaries (whl) +# + +FROM python:${PYTHON_VERSION}-bullseye as py-build +WORKDIR /build +# install and store the requirements (this stage will be re-used unless requirements.txt changes) +COPY requirements.txt . +RUN pip install -r requirements.txt && pip wheel -r requirements.txt -w deps +# add all other source files, force rebuild only from here +COPY . . +RUN python setup.py install && python setup.py bdist_wheel + +# +# Second stage: create the smallest possible image for deployment +# +FROM python:${PYTHON_VERSION}-bullseye +{% if cookiecutter.company_name %} +LABEL maintainer="{{ cookiecutter.company_name }}" +{% else %} +LABEL maintainer="{{ cookiecutter.full_name }}" +{% endif %} WORKDIR /app -COPY requirements.txt setup.py ./ -RUN pip install -r requirements.txt --no-cache-dir --prefer-binary -COPY ./src ./src -COPY README.md ./ -RUN pip install . -ENTRYPOINT ["python", "-OO", "-m", "{{ cookiecutter.module_name }}"] +# install all dependencies from wheel packages in the 'deps' folder +COPY --from=py-build /build/deps/ deps/ +RUN [ -n "$(ls -A deps)" ] && pip install deps/*.whl && rm -rf deps || echo "no dependencies to install" +# install the application from a wheel package in the 'dist' folder +COPY --from=py-build /build/dist/ dist/ +RUN pip install dist/*.whl && rm -rf dist + +WORKDIR / +COPY ./config ./config + +ENTRYPOINT ["python", "-OO", "-m", "{{ cookiecutter.module_name }}.main"] diff --git a/{{cookiecutter.project_slug}}/Dockerfile__poetry b/{{cookiecutter.project_slug}}/Dockerfile__poetry index 5804e78..11fed66 100644 --- a/{{cookiecutter.project_slug}}/Dockerfile__poetry +++ b/{{cookiecutter.project_slug}}/Dockerfile__poetry @@ -10,4 +10,7 @@ RUN poetry install --only main --no-root --no-interaction && \ COPY ./src /app/src RUN poetry install --only-root -ENTRYPOINT ["python", "-OO", "-m", "{{ cookiecutter.module_name }}"] +WORKDIR / +COPY ./config ./config + +ENTRYPOINT ["python", "-OO", "-m", "{{ cookiecutter.module_name }}.main"] diff --git a/{{cookiecutter.project_slug}}/config/dev.conf b/{{cookiecutter.project_slug}}/config/dev.conf index 664f55e..1b7b065 100644 --- a/{{cookiecutter.project_slug}}/config/dev.conf +++ b/{{cookiecutter.project_slug}}/config/dev.conf @@ -1,2 +1,3 @@ environment = "dev" logging.level = DEBUG # overrides the log level that is specified in res/default.conf +username: ${USERNAME} diff --git a/{{cookiecutter.project_slug}}/config/prod.conf b/{{cookiecutter.project_slug}}/config/prod.conf index 23a817b..5b662f7 100644 --- a/{{cookiecutter.project_slug}}/config/prod.conf +++ b/{{cookiecutter.project_slug}}/config/prod.conf @@ -1 +1,2 @@ environment = "prod" +username: ${USERNAME} diff --git a/{{cookiecutter.project_slug}}/docker-compose.yml b/{{cookiecutter.project_slug}}/docker-compose.yml index d7c7871..6450753 100644 --- a/{{cookiecutter.project_slug}}/docker-compose.yml +++ b/{{cookiecutter.project_slug}}/docker-compose.yml @@ -6,6 +6,11 @@ services: build: context: .{% if cookiecutter.package_manager != 'poetry' %} args: - PYTHON_IMAGE_TAG: {% if cookiecutter.package_manager == 'conda' %}"4.8.2"{% else %}"3.8-stretch"{% endif %}{% endif %} + PYTHON_IMAGE_TAG: {% if cookiecutter.package_manager == 'conda' %}4.8.2"{% else %}"3.8-stretch"{% endif %}{% endif %} + {% if cookiecutter.config_file == 'hocon' %} + environment: + ENV: dev + USERNAME: ${USERNAME:-dear user of the at-python-template} + {% endif %} command: '{% if cookiecutter.create_cli == 'yes' %}--help{% endif %}' tty: true diff --git a/{{cookiecutter.project_slug}}/src/{{cookiecutter.module_name}}/main.py b/{{cookiecutter.project_slug}}/src/{{cookiecutter.module_name}}/main.py index 021ce3e..d961740 100644 --- a/{{cookiecutter.project_slug}}/src/{{cookiecutter.module_name}}/main.py +++ b/{{cookiecutter.project_slug}}/src/{{cookiecutter.module_name}}/main.py @@ -1,6 +1,10 @@ -def main(): +{% if cookiecutter.config_file == 'hocon' %}import os +from pyhocon import ConfigFactory + +{% endif %}def main(): # TODO your journey starts here - print("hello :)") + {% if cookiecutter.config_file == 'hocon' %}config = ConfigFactory.parse_file(f"config/{os.environ['ENV']}.conf") + print(f"hello {config.get('username')} :)"){% else %}print("hello :)"){% endif %} if __name__ == "__main__":