crates.io version Build Status

Pyflow

Simple is better than complex - The Zen of Python

This tool manages Python installations and dependencies.

Goals: Make using and publishing Python projects as simple as possible. Understanding Python environments shouldn't be required to use dependencies safely. We're attempting to fix each stumbling block in the Python workflow, so that it's as elegant as the language itself.

You don't need Python or any other tools installed to use Pyflow.

It can run standalone scripts in their own environments with no config, and functions directly from the CLI.

It implements PEP 582 -- Python local packages directory and Pep 518 (pyproject.toml), and supports Python ≥ 3.4.

Installation

Quickstart

Quick-and-dirty start for quick-and-dirty scripts

Why add another Python manager?

Pipenv and Poetry both address part of Pyflow's raison d'être. Some reasons why this is different:

My OS comes with Python, and Virtual environments are easy. What's the point of this?

Hopefully we're not replacing one problem with another.

Some people like the virtual-environment workflow - it requires only tools included with Python, and uses few console commands to create, and activate and environments. However, it may be tedius depending on workflow: The commands may be long depending on the path of virtual envs and projects, and it requires modifying the state of the terminal for each project, each time you use it, which you may find inconvenient or inelegant.

I think we can do better. This is especially relevant for new Python users who don't understand venvs, or are unaware of the hazards of working with a system Python.

Pipenv improves the workflow by automating environment use, and allowing reproducable dependency graphs. Poetry improves upon Pipenv's API, speed, and dependency resolution, as well as improving the packaging and distributing process by using a consolidating project config. Both are sensitive to the Python environment used to run them, and won't work correctly if it's not as expected.

Conda addresses these problems elegantly, but maintains a separate repository of binaries from PyPi. If all packages you need are available on Conda, it may be the best solution. If not, it requires falling back to Pip, which means using two separate package managers.

When building and deploying packages, a set of overlapping files are traditionally used: setup.py, setup.cfg, requirements.txt and MANIFEST.in. We use pyproject.toml as the single-source of project info required to build and publish.

A thoroughly biased feature table

These tools have different scopes and purposes:

| Name | Pip + venv | Pipenv | Poetry | pyenv | pythonloc | Conda |this | |------|------------|--------|--------|-------|-----------|-------|-----| | Manages dependencies | ✓ | ✓ | ✓ | | | ✓ | ✓| | Manages Python installations | | | | ✓ | | ✓ | ✓ | | Py-environment-agnostic | | | | ✓ | | ✓ | ✓ | | Included with Python | ✓ | | | | | | | | Stores packages with project | | | | | ✓ | | ✓| | Locks dependencies | | ✓ | ✓ | | | ✓ | ✓| | Requires changing session state | ✓ | | | ✓ | | | | | Slow | | ✓ | | | | | | | Easy script access | | | | | | | ✓ | | Clean build/publish flow | | | ✓ | | | | ✓ | | Supports old Python versions | with virtualenv | ✓ | ✓ | ✓ | ✓ | ✓ | |

Use

Example contents: ```toml [tool.pyflow] py_version = "3.7" name = "runcible" version = "0.1.0" author = "John Hackworth"

[tool.pyflow.dependencies] numpy = "^1.16.4" diffeqpy = "1.1.0" `` The[tool.pyflow]section is used for metadata. The only required item in it is py_version, unless building and distributing a package. The[tool.pyflow.dependencies]section contains all dependencies, and is an analog torequirements.txt`.

You can specify extra dependencies, which will only be installed when passing explicit flags to pyflow install, or when included in another project with the appropriate flag enabled. Ie packages requiring this one can enable with pip install -e etc. toml [tool.pyflow.extras] test = ["pytest", "nose"] secure = ["crypto"]

If you'd like to an install a dependency with extras, use syntax like this: toml [tool.pyflow.dependencies] ipython = { version = "^7.7.0", extras = ["qtconsole"] }

For details on how to specify dependencies in this Cargo.toml-inspired semvar format, reference this guide.

We also attempt to parse metadata and dependencies from tool.poetry sections of pyproject.toml, so there's no need to modify the format if you're using that.

You can specify direct entry points to parts of your program using something like this in pyproject.toml: ```toml [tool.pyflow]

...

scripts = { name = "module:function" } `` Where you replacename,function, andmodulewith the name to call your script with, the function you wish to run, and the module it's in respectively. This is similar to specifying scripts insetup.pyfor built packages. The key difference is that functions specified here can be run at any time, without having to build the package. Run withpyflow scriptname` to do this.

If you run pyflow package on on a package using this, the result will work like normal script entry points for somone using the package, regardless of if they're using this tool.

What you can do

Managing dependencies:

Running REPL and Python files in the environment:

Building and publishing:

Misc:

How installation and locking work

Running pyflow install syncs the project's installed dependencies with those specified in pyproject.toml. It generates pyflow.lock, which on subsequent runs, keeps dependencies each package a fixed version, as long as it continues to meet the constraints specified in pyproject.toml. Adding a package name via the CLI, eg pyflow install matplotlib simply adds that requirement before proceeding. pyflow.lock isn't meant to be edited directly.

Each dependency listed in pyproject.toml is checked for a compatible match in pyflow.lock If a constraint is met by something in the lock file, the version we'll sync will match that listed in the lock file. If not met, a new entry is added to the lock file, containing the highest version allowed by pyproject.toml. Once complete, packages are installed and removed in order to exactly meet those listed in the updated lock file.

This tool downloads and unpacks wheels from pypi, or builds wheels from source if none are availabile. It verifies the integrity of the downloaded file against that listed on pypi using SHA256, and the exact versions used are stored in a lock file.

When a dependency is removed from pyproject.toml, it, and its subdependencies not also required by other packages are removed from the __pypackages__ folder.

How dependencies are resolved

Compatible versions of dependencies are determined using info from the PyPi Warehouse (available versions, and hash info), and the pydeps database. We use pydeps, which is built specifically for this project, due to inconsistent dependency information stored on pypi. A dependency graph is built using this cached database. We attempt to use the newest compatible version of each package.

If all packages are either only specified once, or specified multiple times with the same newest-compatible version, we're done resolving, and ready to install and sync.

If a package is included more than once with different newest-compatible versions, but one of those newest-compatible is compatible with all requirements, we install that one. If not, we search all versions to find one that's compatible.

If still unable to find a version of a package that satisfies all requirements, we install multiple versions of it as-required, store them in separate directories, and modify their parents' imports as required.

Note that it may be possible to resolve dependencies in cases not listed above, instead of installing multiple versions. Ie we could try different combinations of top-level packages, check for resolutions, then vary children as-required down the hierarchy. We don't do this because it's slow, has no guarantee of success, and involves installing older versions of packages.

Not-yet-implemented

Building and uploading your project to PyPi

In order to build and publish your project, additional info is needed in pyproject.toml, that mimics what would be in setup.py. Example: ```toml [tool.pyflow] name = "everythingkiller" pyversion = "3.6" version = "0.1.0" author = "Fraa Erasmas" authoremail = "raz@edhar.math" description = "Small, but packs a punch!" homepage = "https://everything.math" repository = "https://github.com/raz/everythingkiller" license = "MIT" keywords = ["nanotech", "weapons"] classifiers = [ "Topic :: System :: Hardware", "Topic :: Scientific/Engineering :: Human Machine Interfaces", ] scripts = { activate = "jeejah:activate" } python_requires=">=3.6"

package_url = "https://upload.pypi.org/legacy/"

[tool.pyflow.dependencies] numpy = "^1.16.4" manim = "0.1.8" ipython = {version = "^7.7.0", extras=["qtconsole"]} `` package_urlis used to determine which package repository to upload to. If ommitted, Pypi testis used (https://test.pypi.org/legacy/`).

Building this from source

If you’d like to build from source, download and install Rust, clone the repo, and in the repo directory, run cargo build --release.

Ie on Linux: bash curl https://sh.rustup.rs -sSf | sh git clone https://github.com/david-oconnor/pyflow.git cd pyflow cargo build --release

Updating

If installed via Cargo, run cargo install pyflow --force.

Contributing

If you notice unexpected behavior or missing features, please post an issue, or submit a PR. If you see unexpected behavior, it's probably a bug! Post an issue listing the dependencies that did not install correctly.

Why not to use this

Dependency cache repo:

Python binary sources:

Gotchas

References