The purpose of this guide is to document best practices, tips, and tricks for Python development.
- Templates
- Publishing libraries to PyPI
- Logging
- Testing
- Continuous Integration
- Local package management and virtual environments
- IDE's and editors
For scripts and packages ...
TBD
The purpose of this section is provide a guide to follow when you want to configure a Python project and deployed to PyPI.
The Python Package Index (PyPI) stores meta-data describing distributions packaged with distutils, as well as package data like distribution files if a package author wishes. Once your package (and its associate meta-data) has been uploaded to PyPI, others can download and install it using pip. Pypi is going to take the meta-data from your setup.py file.
If this is the first time this particular package is being uploaded to PyPI, you'll first need to register it:
python setup.py register
Next times the package is going to be uploaded when you create a new release in github automatically.
To get a better sense of how your files should be organized, here's a simplified structure of a Python project:
name-of-your-project
├── docs
│ ├── imgs
│ └── doc.md
├── name_of_your_project
│ ├── __init__.py
│ └── code.py
├── tests
│ └── test_code.py
├── .gitignore
├── .travis.yml
├── HISTORY.md
├── LICENSE
├── Makefile
├── README.md
├── requirements_dev.txt
├── setup.cfg
├── setup.py
├── tox.ini
Is the file that tells a distribution tool, such as Distutils or Setuptools, how to install and configure the package.
Here you have an example of how an Ocean setup.py file looks like:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""The setup script."""
from setuptools import setup, find_packages
with open('README.md') as readme_file:
readme = readme_file.read()
with open('HISTORY.md') as history_file:
history = history_file.read()
requirements = [YOUR_REQUIREMENTS]
setup_requirements = ['pytest-runner', ]
test_requirements = ['pytest', ]
setup(
author="leucothia",
author_email='[email protected]',
classifiers=[
'Development Status :: 2 - Pre-Alpha',
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
'Natural Language :: English',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
],
description=" 🐳 DESCRIPTION_OF_YOUR_PACKAGE",
install_requires=requirements,
license="Apache Software License 2.0",
long_description=readme + + '\n\n' + history,
long_description_content_type="text/markdown",
include_package_data=True,
keywords='REPO_NAME',
name='REPO_NAME',
packages=find_packages(include=['YOUR_PACKAGES']),
setup_requires=setup_requirements,
test_suite='tests',
tests_require=test_requirements,
url='https://github.com/oceanprotocol/REPO_NAME',
version='0.0.1',
zip_safe=False,
)
Ensure that setuptools
is installed in the active environment.
Go to the root directory of your project (i.e. the one containing setup.py
).
Build the package and the wheel:
python setup.py sdist bdist_wheel
Upload to PyPI:
twine upload dist/*
Python logging
should be employed generously. In the root calling
script, import the logging module and configure to the desired syntax and
handling;
from package_dir.log import setup_logging
import logging
setup_logging()
Where setup_logging()
imports and applies the logging configuration from
a YAML configuration if specified, otherwise it defaults to logging.basicConfig
.
Logging uses the coloredlogs
package.
An example configuration file is presented below as a starting point;
Example 1) Simple logging to console;
version: 1
disable_existing_loggers: False
formatters:
simple:
format: "%(name)-5s - %(levelno)-3s - %(module)-20s %(funcName)-30s: %(message)s"
handlers:
console:
class: logging.StreamHandler
level: DEBUG
formatter: simple
stream: ext://sys.stdout
loggers:
my_module:
level: ERROR
handlers: [console]
propagate: no
root:
level: DEBUG
handlers: [console]
Note that the Python logging module organizes loggers in a hierarchy, all loggers are descendants of root.
Example 2) A more complex example, with files and file size limits;
TODO: Update this section with the actual config used throughout Ocean
version: 1
disable_existing_loggers: False
formatters:
simple:
format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
handlers:
console:
class: logging.StreamHandler
level: DEBUG
formatter: simple
stream: ext://sys.stdout
info_file_handler:
class: logging.handlers.RotatingFileHandler
level: INFO
formatter: simple
filename: info.log
maxBytes: 10485760 # 10MB
backupCount: 20
encoding: utf8
error_file_handler:
class: logging.handlers.RotatingFileHandler
level: ERROR
formatter: simple
filename: errors.log
maxBytes: 10485760 # 10MB
backupCount: 20
encoding: utf8
loggers:
my_module:
level: ERROR
handlers: [console] # Ignore the file handlers until deployment
propagate: no
root:
level: INFO
handlers: [console, info_file_handler, error_file_handler]
Tox is a tool that provides "standardized testing in Python",and it goes beyond merely running your tests with more than one version of the interpreter. It creates a fully sandboxed environment in which your package and its requirements are installed and tested. If you made a change that works fine when tested directly but the change inadvertently broke your installation, you'll discover that with tox.
TravisCI is a service that makes continuous integration for GitHub projects easy. You need to create one simple file:
language: python
python:
- 3.6
- 3.5
- 3.4
# Command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors
install: pip install -U tox-travis
script: tox
deploy:
provider: pypi
distributions: sdist bdist_wheel
user: leucothia
password:
secure: LEUCOTHIA_PASSWORD_ENCRYPTED
on:
tags: true
branch: master
repo: oceanprotocol/REPO_NAME
python: 3.6
In order to generate the secure password, you have to run the following command in your project directory:
echo -n LEUCOTHIA_PASSWORD | travis encrypt --add deploy.password -r oceanprotocol/REPO_NAME
Related with the travis CI we are going to integrate Bumpversion to allow as to automatically point to the new version when we create a new realese. bumpversion
Create a testing run configuation using PyTest
Ensure correct target script and interpreter
Add the --log-cli-level info
(or other logging level) argument to the run configuration, to post logging messages into the console.
virtualenv is used to create a local Python "virtual environment" which uses a particular version of Python and has a particular set of Python packages installed. You can switch between different virtualenvs. Example usage:
virtualenv -p $(which python3.6) py36 # Create a Python 3.6 virtualenv named py36
source py36/bin/activate # Make the py36 virtualenv active
python --version # Check the version of Python in the virtualenv
pip install blahblah # Install the Python package named blahblah from PyPI
pip freeze # List the Python packages installed in the virtualenv
deactivate # Deactivate the current virtualenv (py36)
source other_env/bin/activate # activate other_env
In many of our Python projects:
pip install -e .[dev]
is how a Python project developer would install the local Python project (in.
) in editable mode (-e
) including all the extra Python packages that a developer needs ([dev]
which is defined in theextras_require
dict ofsetup.py
).pip install -r requirements_dev.txt
orpip install -r requirements.txt
will dopip install -e .[dev]
pip install package_name_on_PyPI
will install only the packages listed in theinstall_requires
list insetup.py
pip install .
will install only the packages listed in theinstall_requires
list insetup.py
in the local directory (.
) — this is what the Dockerfile does when building a new Docker image.
For more about pip
, see the pip User Guide. For more about setup.py
, see the Python docs about the setup script.
The current PyPA recommended package manager. Pipenv on GitHub.
Currently, packages are not published to the anaconda package repository. However, you may use Conda as a virtual environment manager, and pip install
a python package into your conda environment.
After working with a Python project for a while, the project directory can get full of artifacts, and sometimes those can cause problems (e.g. conflicts). You can clean those out using:
# Remove build artifacts
rm -fr build/
rm -fr dist/
rm -fr .eggs/
find . -name '*.egg-info' -exec rm -fr {} +
find . -name '*.egg' -exec rm -f {} +
# Remove Python file artifacts
find . -name '*.pyc' -exec rm -f {} +
find . -name '*.pyo' -exec rm -f {} +
find . -name '*~' -exec rm -f {} +
find . -name '__pycache__' -exec rm -fr {} +
# Remove test and coverage artifacts
find . -name '.pytest_cache' -exec rm -fr {} +
rm -fr .tox/
rm -f .coverage
rm -fr htmlcov/
Tip: If you get a permissions error, then give yourself the needed permissions using sudo chown -R $(whoami):$(whoami) .
pip install ipykernel
in each environment to get kernel switching working!
Install Spyder in each venv to ensure the kernel is used.