Nicolas Carbone's Soapbox of Python Practices
This soapbox contains some basic stuff for different Python projects.
A virtual environment is a Python environment such that the Python interpreter, libraries and scripts installed into it are isolated from those installed in other virtual environments, and (by default) any libraries installed in a “system” Python, i.e., one which is installed as part of your operating system.
This will help you to install project dependencies in an isolate environment without installing anything at system level.
You can easily create a new virtual environment running:
python3 -m venv your-project
See Python docs for a complete reference.
Once the virtual environment is running, you can install packages using pip
There are some tools to make this process easier, like pipenv which allows you to create a virtual env and also install packages and Poetry to easily manage dependencies.
For this kind of dependency is recommended to use tools like docker. This will make easier for other developers to setup the project and it will prevent to install software in your operating system that is only required for a particular project.
There are docker images for postgres, mssql, fastapi, etc.
Python has defined a coding style guide called PEP8, lease refer to PEP8 for a complete reference.
You should always stick to this guide, it is in most of the python projects.
Most important styles are:
- Use spaces instead of tabs.
- Use underscore for function, methods, variables and file names:
- Examples: get_user, client_name, set_date, etc
- Use camel case for class names: MyClass, MyService, etc
- Use capital letter at module level for constants.
- Use
self
as first argument to instance methods. - Use
cls
as first argument to class methods.
There are many linters in Python, choose the one you like the most and stick to it. Some of them are
The whole team should use the same linter for sake of consistency.
You can also use git hooks to make sure you don't push any code that does not pass a linter analysis.
There are many formatters in python, check Here for a detailed list.
Choose the one you like the most but please use at least one.
Again, the whole team should use the same formatter for consistency.
Formatters can run in a git hook or as an IDE plugin.
Keep imports sorted, the following is a very common convention
<python standard lib imports>
<third party imports>
<app packages>
<tests> (if it is a test suite)
You can always use plugins like isort to do this when you save a file or in a git hook.
Python does not have access modifiers so use a leading _
for private methods.
Avoid using leading __
because that will trigger a process called name mangling
, it will just put the class name as attribute prefix.
As general rule, your code should have high cohesion and low coupling, this will make it easier to maintain, scale, etc.
Some things to keep in mind:
-
Dependency Injection: This will decouple your services from other ones and it will make them easier to test and maintain.
-
Repository pattern: all CRUD operations should go here so your services won't get tied to the db layer.
See Example
Then you can use this class to access to your data from your services.
-
Single responsibility: Avoid adding more than one responsibility to each function/method. This will make you code simpler and easier to test.
-
Unit of work: this is a useful pattern which collaborates with the repository pattern to coordinate the writing out of changes. It is an abstraction for atomic operation, so the API does not communicate with all the other layers.
See Chapter of the amazing book by Harry Percival.
Whenever possible use an ORM to access to the database, this can help to make your app compatible with different db engines like:
- Postgres
- Mssql
- Sqlite
A common option is SqlAlchemy but there are others like Peewee.
When using an ORM avoid writing raw sql unless is strictly necessary, this will tie your code to an specific db engine.
Python has unittest as part of the standard lib, however, there are more flexible tools and frameworks like Pytest, which has some really useful features that makes tests more scalable and it is widely accepted by the python community.
Considerations:
-
Each function or method should have its own test
-
Keep an eye on responsibilities and make sure you function/method does only one thing, this will allow you to write smaller and more reliable tests.
-
Mock any third-party lib.
- You can use mock and patch from unittest for this purpose.
-
Use dependency injection: this will allow you to use mocks to easily test services without patching a lot of different classes.
cache = mock.Mock() my_service(cache=cache) cache.get.assert_called_with(key)