Python project
Here is a starter for a Python project. It can be used as the next step after creating the boilerplate with cookiecutter-python-project
Dependencies:
- deescovery for dynamic discovery of modules
- python-dotenv for loading environment variables from
.env
and.env.<environment>
files (e.g.,.env.development
) - click for creating CLI commands
- Optionally, IPython for interactive shell
File myproject/config.py
"""Configuration.
The module doesn't depend on the project. The configuration file is initialized
with python-dotenv, and we don't read environment variables in the rest of the
project.
What config.py can import?
--------------------------
The module doesn't import anything from the project.
Who can import config.py?
---------------------------
The module is imported by the app.py to create services. It can also be imported in
the other parts of the application: for example, to get access to PROJECT_ROOT, or to
create project-specific services from requisites, provided in the config.
"""
import os
import pathlib
import dotenv
PROJECT_ROOT = pathlib.Path(__file__).parents[1]
ENVIRONMENT = os.getenv("ENVIRONMENT", "development")
config = {
**dotenv.dotenv_values((PROJECT_ROOT / ".env").as_posix()),
**dotenv.dotenv_values((PROJECT_ROOT / f".env.{ENVIRONMENT}").as_posix()),
**os.environ,
}
# --------------------------------------------------------------------------------
# Server configuration
# --------------------------------------------------------------------------------
HOST = config.get("HOST", "0.0.0.0")
PORT = int(config.get("PORT") or 5000)
STATIC_ROOT = "/static"
SECRET_KEY = config["SECRET_KEY"]
# --------------------------------------------------------------------------------
# Sentry settings
# --------------------------------------------------------------------------------
# Set SENTRY_DSN to a non-empty value to enable Sentry.
SENTRY_DSN = config.get("SENTRY_DSN")
SENTRY_ENVIRONMENT = config.get("SENTRY_ENVIRONMENT") or "development"
File myproject/services.py
"""Services.
The module provides a "registry" or "service locator" for other modules of the
application. It may initialize stub objects for extensions, that are configured
inside the app.py later on.
What services.py can import?
----------------------------
The module doesn't import anything from the project.
Who can import services.py?
--------------------------
The module is imported by different modules across the project, as necessary.
"""
# FastAPI example
from fastapi import FastAPI
fastapi_app = FastAPI()
File myproject/app.py
"""Application.
The module is used to initialize the main project: load configuration and
initialize services.
What app.py can import?
-----------------------
Services, configuration, and everything that is needed to initialize application,
including controllers and models inside apps. In other words, app.py depends on
a lot of things in the project.
Who can import app.py?
----------------------
The module is not imported from anywhere except from a WSGI/ASGI server in
production, and conftests.py for pytest.
"""
from importlib import import_module
from deescovery import ModuleRule, discover
from deescovery.matchers import MatchByPattern
from myproject import config
api_loader = ModuleRule(
name="FastAPI API endpoints loader",
module_matches=MatchByPattern(patterns=["myproject.*.api"]),
module_action=import_module,
)
def init(**kwargs):
update_config(kwargs)
discover("myproject", rules=[api_loader])
def update_config(kwargs):
"""Override values of the myproject.config.
Helpful for tests, when we need to dynamically override some settings.
"""
for k, v in kwargs.items():
setattr(config, k, v)
File manage.py
#!/usr/bin/env python
"""Management script."""
import datetime
from typing import Iterable, List
import click
from IPython import embed
from myproject import app
@click.group()
def cli():
app.init()
@cli.command()
def shell():
"""Open IPython console."""
embed(colors="neutral")
if __name__ == "__main__":
cli()