Roman Imankulov

Roman Imankulov

Full-stack Python web developer from Porto

search results (esc to close)

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()
Roman Imankulov

Hey, I am Roman, and you can hire me.

I am a full-stack Python web developer who loves helping startups and small teams turn their ideas into products.

More about me and my skills