Skip to content

Instantly share code, notes, and snippets.

@signalpillar
Last active June 10, 2020 07:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save signalpillar/e0b46624472a041d7fec5d3e5ac725bd to your computer and use it in GitHub Desktop.
Save signalpillar/e0b46624472a041d7fec5d3e5ac725bd to your computer and use it in GitHub Desktop.

Opinionated best practices when developing with Python.

Stop naming your python modules "utils"

Cool packages

  • pydantic
  • fastapi-

Configuration

Tooling

Must have tools

  • black closes the topic of formatting and style of the code. Saves time during the PRs by avoiding repetitive discussions.
  • mypy makes the type annotations valuable and up to date. It helps to find the bugs, especially those related to None values.
  • pre-commit saves time by checking basic things (mypy, stylechecks) before pushing to CI and running expensive tests.
  • tox, it simplifies CI configuration (scripts) and saves tons of time by ensuring that environment corresponds to what you have the requirements. Consider tox-battery to ensure that requirements files for pip are checked and virtual environments are recreated.

mypy

The configuration

	[mypy]
	warn_unused_ignores = True
	follow_imports = skip
	show_error_context = True
	warn_incomplete_stub = True
	ignore_missing_imports = True
	check_untyped_defs = True
	cache_dir = /dev/null
	# Cannot enable this one as we still allow defining functions without any types.
	# disallow_untyped_defs = True
	warn_redundant_casts = True
	warn_unused_configs = True
	strict_optional = True

App configuration

Access environment variables

For small projects we can always access evars (environment variables) with os.getenv directly and that is ok.

I assume that for the small projects code base is in 1-2 modules and I can scan it every time I need to answer some question.

Services. In the era of services and micro-services the code base is slightly bigger than 2 modules and it would be nice to see how application is configured.

For that purpose I always use config.yml file that is self-documented with the extensive comments, examples and structure.

The app is configured only by the configuration file, no other sources of configuration. It's how it appears to the code.

But we want to follow the 12 factors and configure our app with the environment variables.

Configuration can use substitution variables to access environment variables.

Example of the configuration.

web:
  port: ${WEB_PORT:9090}
feature:
  publish_events:
    enabled: ${PUBLISH_EVENTS_ENABLED:False}
  # TICKET-111: hot fix ...
  hotfix_1:
    enable: ${HOTFIX_111_ENABLED:False}
    expire: 2020-09-08

From the configuration we can see all the evars that can be set and their usage context.

Testing

Place for the test doubles

Test double from Martin Fowler

Keep doubles.py and pytest_fixtures.py in the package of a relevant domain. Usually I put it in the testing package.

Example

Packageapp.currencyhas

  • model.py
  • client.py
  • controller.py
  • testing
    • doubles.py
    • pytest_fixtures.py

Looks like this package is responsible for serviing currency-related logic in the app. We created testing package that will contain

fakes for the client, different currency objects ready to be used in testing. pytest_fixtures can be added as a plugin in pytest to access the fixture of a client, for instance.

Important that we can answer the questions like "Is there a test double I can reuse?"

Documenting the code

  • doctest
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment