Python Feature

The “python” feature is the base feature required for all python projects.

Medikit only supports python 3.5+ projects, which we believe is a future proof choice.

Overview

from medikit import require

# load python feature (idempotent).
python = require('python')

# configure our package.
python.setup(
    name = 'awesome-library',
    author = 'Chuck Norris',
)

# add base and extras requirements, with "loose" versionning (the frozen version fits better in requirements*.txt)
python.add_requirements(
    'django',
    dev=[
        'pytest',
    ],
)

Usage

To use the Python Feature, make sure your Projectfile contains the following:

from medikit import require

python = require('python')

The python handle is a PythonConfig instance, and can be used to customize the feature.

The python features makes your package real (at least if it uses python). Medikit was written originally for python, and although it’s not completely true anymore, a lot of features depends on this.

Setup

You can define the setuptools’ setup(…) arguments using python.setup(…):

python.setup(
    name='medikit',
    description='Opinionated python 3.5+ project management.',
    license='Apache License, Version 2.0',
    url='https://github.com/python-medikit/medikit',
    download_url='https://github.com/python-medikit/medikit/tarball/{version}',
    author='Romain Dorgueil',
    author_email='romain@dorgueil.net',
    entry_points={
        'console_scripts': ['medikit=medikit.__main__:main'],
    }
)

This is required for any python package.

Requirements

Requirements are managed using two different mechanisms:

  • The setup.py file, autogenerated and overriden by medikit, will contain the “loose” requirements as you define them. You’re encouraged to use “~x.y.z” or “~x.y” versions. You should use each versions (“==x.y.z”) only in case you’re relying on a package you never want to update.
  • The requirements*.txt files will contain frozen version numbers. Those requirements will be commited, and you can ensure the reproducibility of your installs by using pip install -r requirements.txt instead of python setup.py install.

In medikit, we call what is present in setup.py “constraints”, and what is in requirements*.txt files “requirements”.

Let’s see how we can set them:

python.add_requirements(
    "requests ~2.18"
)

This will set a constraint on any semver compatible requests version, and update the requirement to latest requests version, compatible with the constraint (as of writing, 2.18.4).

It means that if you run make install, python setup.py install or pip install -e ., requests will only be downloaded and installed if there is no installation complying to the constraint in your current env. This is very handy if you have local, editable packages that you want to use instead of PyPI versions.

It also means that when you run pip install -r requirements.txt, you’ll get requests 2.18.4 even if a new version was released.

If you want to upgrade to the new released version, use make update-requirements, review the git changes (git diff –cached), test your software with the new version and eventually (git) commit to this dependency update.

Constraints

Sometimes, you want a dependency to only be a constraint, and not a frozen requirement.

python.add_constraints(
    "certifi ~2018,<2019"
)

This will ensure that your env contains “certifi”, a version released in the 2018 year, but also says you don’t care which one.

This is an advanced feature that you should only use if you really know what you’re doing, otherwise, use a requirement (reproducibility of installs is gold).

Extras

You can create as much “extras” as you want.

As a default, medikit will create a “dev” extra, but you can add whatever you need:

python.add_requirements(
    sql=[
        'sqlalchemy ~=1.2.5',
    ]
)

The same works with constraints, of course.

Changing package generation behaviour

Medikit creates the necessary directory structure for your package, named after your package name defined in the python.setup() call.

If you don’t want medikit to create this directory structure:

python.create_packages = False

Medikit also considers you’ll need a version number tracking mechanism for your project. It creates a _version.py file in your package’s root directory. To override this file’s name:

python.version_file = 'my_version.py'

Configuration

class medikit.feature.python.PythonConfig[source]

Configuration API for the «python» feature.

add_constraints(*reqs, **kwargs)[source]
add_inline_requirements(*reqs, **kwargs)[source]
add_requirements(*reqs, **kwargs)[source]
add_vendors(*reqs, **kwargs)[source]
create_packages
get(item)[source]
get_constraints(extra=None)[source]
get_extras()[source]
get_init_files()[source]
get_inline_requirements(extra=None)[source]
get_requirements(extra=None, with_constraints=False)[source]
get_setup()[source]
get_vendors(extra=None)[source]
on_generate = 'medikit.feature.python.on_generate'
package_dir
setup(**kwargs)[source]
version_file

Implementation

class medikit.feature.python.PythonFeature(dispatcher)[source]

Adds the requirements/requirements-dev logic, virtualenv support, setup.py generation, a few metadata files used by setuptools…

Config

alias of PythonConfig

on_end(event)[source]

Listens to medikit.on_end event (priority: -100)

on_make_generate(event)[source]

Environment variables

  • PACKAGE
  • PYTHON
  • PYTHON_BASENAME
  • PYTHON_DIR
  • PYTHON_REQUIREMENTS_FILE
  • PYTHON_REQUIREMENTS_DEV_FILE

Shortcuts - PIP - PIP_INSTALL_OPTIONS

Make targets

  • install
  • install-dev
Parameters:event (MakefileEvent) –

Listens to medikit.feature.make.on_generate event (priority: -100)

on_start(event)[source]

Events

  • medikit.feature.python.on_generate (with the same ProjectEvent we got, todo: why not deprecate it in favor of higher priority medikit.on_start?)

Files

  • <yourpackage>/__init__.py
  • MANIFEST.in
  • README.rst
  • classifiers.txt
  • requirements-dev.txt
  • requirements.txt
  • setup.cfg
  • setup.py (overwritten)
Parameters:event (ProjectEvent) –

Listens to medikit.on_start event (priority: -100)

requires = {'make'}