Skip to main content

Dependency injection.

Project description

https://img.shields.io/pypi/v/antidote.svg https://img.shields.io/pypi/l/antidote.svg https://img.shields.io/pypi/pyversions/antidote.svg https://github.com/Finistere/antidote/actions/workflows/main.yml/badge.svg?branch=master https://codecov.io/gh/Finistere/antidote/branch/master/graph/badge.svg https://readthedocs.org/projects/antidote/badge/?version=latest

Antidotes is a dependency injection micro-framework for Python 3.7+. It is built on the idea of ensuring best maintainability of your code while being as easy to use as possible. It also provides the fastest injection with @inject allowing you to use it virtually anywhere and fast full isolation of your tests.

Antidote provides the following features:

  • Ease of use
    • Injection anywhere you need through a decorator @inject, be it static methods, functions, etc.. By default, it will only rely on annotated type hints, but it supports a lot more!

    • No **kwargs arguments hiding actual arguments and fully mypy typed, helping you and your IDE.

    • Documented, everything has tested examples.

    • No need for any custom setup, just use your injected function as usual. You just don’t have to specify injected arguments anymore. Allowing you to gradually migrate an existing project.

  • Flexibility
    • Most common dependencies out of the box: services, configuration, factories, interface/implementation.

    • All of those are implemented on top of the core implementation. If Antidote doesn’t provide what you need, there’s a good chance you can implement it yourself.

    • Scope support

    • Async injection

  • Maintainability
    • All dependencies can be tracked back to their declaration/implementation easily.

    • Mypy compatibility and usage of type hints as much as possible.

    • Overriding dependencies will raise an error outside of tests.

    • Dependencies can be frozen, which blocks any new declarations.

    • No double injection.

    • Everything is as explicit as possible, @inject does not inject anything implicitly.

    • Type checks when a type is explicitly defined with world.get, world.lazy and constants.

    • Thread-safe, cycle detection.

    • Immutable whenever possible.

  • Testability
    • @inject lets you override any injections by passing explicitly the arguments.

    • Fully isolate each test with world.test.clone. They will work on separate objects.

    • Override globally any dependency locally in a test.

    • When encountering issues you can retrieve the full dependency tree, nicely formatted, with world.debug.

  • Performance*
    • Fastest @inject with heavily tuned Cython.

    • As much as possible is done at import time.

    • Testing utilities are tuned to ensure that even with full isolation it stays fast.

    • Benchmarks: comparison, injection, test utilities

*with the compiled version, in Cython. Pre-built wheels for Linux. See further down for more details.

Comparison benchmark image

Installation

To install Antidote, simply run this command:

pip install antidote

Documentation

Beginner friendly tutorial, recipes, the reference and a FAQ can be found in the documentation.

Here are some links:

Issues / Questions

Feel free to open an issue on Github for questions or issues !

Hands-on quick start

Showcase of the most important features of Antidote with short and concise examples. Checkout the Getting started for a full beginner friendly tutorial.

Injection

from antidote import inject, service

@service
class Database:
    pass

@inject
def f(db: Database = inject.me()):
    return db

assert isinstance(f(), Database)  # works !

Simple, right ? And you can still use it like a normal function, typically when testing it:

f(Database())

.inject here used the marker inject.me() with the help of the type hint to determine the dependency. But it also supports the following ways to express the dependency wiring:

  • annotated type hints:
    from antidote import Inject
    
    @inject
    def f(db: Inject[Database]):
        pass
  • list (matching argument position):
    @inject([Database])
    def f(db):
        pass
  • dictionary:
    @inject({'db': Database})
    def f(db):
        pass
  • optional dependencies:
    from typing import Optional
    
    class Dummy:
        pass
    
    # When the type_hint is optional and a marker like `inject.me()` is used, None will be
    # provided if the dependency does not exists.
    @inject
    def f(dummy: Optional[Dummy] = inject.me()):
        return dummy
    
    assert f() is None

You can also retrieve the dependency by hand with world.get:

from antidote import world

# Retrieve dependencies by hand, in tests typically
world.get(Database)
world.get[Database](Database)  # with type hint, enforced when possible

Service

Services are classes for which Antidote provides an instance. It can be a singleton or not. Scopes are also supported. Every method is injected by default, relying on annotated type hints and markers such as inject.me():

from antidote import service, inject

@service(singleton=False)
class QueryBuilder:
    # methods are also injected by default
    def __init__(self, db: Database = inject.me()):
        self._db = db

@inject
def load_data(builder: QueryBuilder = inject.me()):
    pass

load_data()  # yeah !

Constants

Constants can be provided lazily by Antidote:

from antidote import inject, Constants, const

class Config(Constants):
    DB_HOST = const('localhost')

@inject
def ping_db(db_host: str = Config.DB_HOST):
    pass

ping_db()  # nice !

This feature really shines when your constants aren’t hard-coded:

from typing import Optional
from antidote import inject, Constants, const

class Config(Constants):
    # Like world.get, a type hint can be provided and is enforced.
    DB_HOST = const[str]()
    DB_PORT = const[int]()
    DB_USER = const[str](default='postgres')  # default is used on LookupError

    # name of the constant and the arg given to const() if any.
    def provide_const(self, name: str, arg: Optional[object]):
        return os.environ[name]

import os
os.environ['DB_HOST'] = 'localhost'
os.environ['DB_PORT'] = '5432'

@inject
def check_connection(db_host: str = Config.DB_HOST,
                     db_port: int = Config.DB_PORT,
                     db_user: str = Config.DB_USER):
    pass

check_connection()  # perfect !

Note that on the injection site, nothing changed!

Factory

Factories are used by Antidote to generate a dependency, typically a class from an external code:

from antidote import factory, inject

class User:
    pass

@factory(singleton=False)  # function is injected by default
def current_user(db: Database = inject.me()) -> User:
    return User()

# Consistency between the type hint and the factory result type hint is enforced.
@inject
def is_admin(user: User = inject.me(source=current_user)):
    pass

While it’s a bit verbose, you always know how the dependency is created. Obviously you can retrieve it from world:

from antidote import world

world.get(User, source=current_user)

Interface/Implementation

The distinction between an interface and its implementation lets you choose between multiple implementations, which one to use. This choice can be permanent or not. For the latter, Antidote will retrieve the current implementation each time:

from antidote import implementation, inject, factory, Get

class Cache:
    pass

@service
class MemoryCache(Cache):
    pass

class Redis:
    """ class from an external library """

@factory
def redis_cache() -> Redis:
    return Redis()

@implementation(Cache)
def global_cache():
    # Returning the dependency that must be retrieved
    import os

    if os.environ.get('USE_REDIS_CACHE'):
        return Get(Redis, source=redis_cache)

    return MemoryCache

The cache can then be retrieved with the same syntax as a factory:

from antidote import world, inject

@inject
def heavy_compute(cache: Cache = inject.me(source=global_cache)):
    pass


heavy_compute()
world.get(Cache, source=global_cache)

Like factories, it’s easy to know where the dependency is coming from !

Testing and Debugging

inject always allows you to pass your own argument to override the injection:

from antidote import service, inject

@service
class Database:
    pass

@inject
def f(db: Database = inject.me()):
    pass

f()
f(Database())  # test with specific arguments in unit tests

You can also fully isolate your tests from each other and override any dependency within that context:

from antidote import world

# Clone current world to isolate it from the rest
with world.test.clone():
    x = object()
    # Override the Database
    world.test.override.singleton(Database, x)
    f()  # will have `x` injected for the Database

    @world.test.override.factory(Database)
    def override_database():
        class DatabaseMock:
            pass

        return DatabaseMock()

    f()  # will have `DatabaseMock()` injected for the Database

If you ever need to debug your dependency injections, Antidote also provides a tool to have a quick summary of what is actually going on:

def function_with_complex_dependencies():
    pass

world.debug(function_with_complex_dependencies)
# would output something like this:
"""
function_with_complex_dependencies
└── Permanent implementation: MovieDB @ current_movie_db
    └──<∅> IMDBMovieDB
        └── ImdbAPI @ imdb_factory
            └── imdb_factory
                ├── Config.IMDB_API_KEY
                ├── Config.IMDB_PORT
                └── Config.IMDB_HOST

Singletons have no scope markers.
<∅> = no scope (new instance each time)
<name> = custom scope
"""

Hooked ? Check out the documentation ! There are still features not presented here !

Compiled

The compiled implementation is roughly 10x faster than the Python one and strictly follows the same API than the pure Python implementation. Pre-compiled wheels are available only for Linux currently. You can check whether you’re using the compiled version or not with:

from antidote import is_compiled

f"Is Antidote compiled ? {is_compiled()}"

You can force the compilation of antidote yourself when installing:

ANTIDOTE_COMPILED=true pip install antidote

On the contrary, you can force the pure Python version with:

pip install --no-binary antidote

How to Contribute

  1. Check for open issues or open a fresh issue to start a discussion around a feature or a bug.

  2. Fork the repo on GitHub. Run the tests to confirm they all pass on your machine. If you cannot find why it fails, open an issue.

  3. Start making your changes to the master branch.

  4. Writes tests which shows that your code is working as intended. (This also means 100% coverage.)

  5. Send a pull request.

Be sure to merge the latest from “upstream” before making a pull request!

If you have any issue during development or just want some feedback, don’t hesitate to open a pull request and ask for help !

Pull requests will not be accepted if:

  • public classes/functions have not docstrings documenting their behavior with examples.

  • tests do not cover all of code changes (100% coverage) in the pure python.

If you face issues with the Cython part of Antidote, I may implement it myself.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

antidote-1.1.0.tar.gz (181.1 kB view details)

Uploaded Source

Built Distributions

antidote-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.2 MB view details)

Uploaded CPython 3.10 manylinux: glibc 2.17+ x86-64

antidote-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (2.1 MB view details)

Uploaded CPython 3.10 manylinux: glibc 2.12+ x86-64 manylinux: glibc 2.5+ x86-64

antidote-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl (2.1 MB view details)

Uploaded CPython 3.10 manylinux: glibc 2.17+ i686 manylinux: glibc 2.5+ i686

antidote-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.2 MB view details)

Uploaded CPython 3.9 manylinux: glibc 2.17+ x86-64

antidote-1.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (2.1 MB view details)

Uploaded CPython 3.9 manylinux: glibc 2.12+ x86-64 manylinux: glibc 2.5+ x86-64

antidote-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl (2.1 MB view details)

Uploaded CPython 3.9 manylinux: glibc 2.17+ i686 manylinux: glibc 2.5+ i686

antidote-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.2 MB view details)

Uploaded CPython 3.8 manylinux: glibc 2.17+ x86-64

antidote-1.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (2.2 MB view details)

Uploaded CPython 3.8 manylinux: glibc 2.12+ x86-64 manylinux: glibc 2.5+ x86-64

antidote-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl (2.1 MB view details)

Uploaded CPython 3.8 manylinux: glibc 2.17+ i686 manylinux: glibc 2.5+ i686

antidote-1.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.0 MB view details)

Uploaded CPython 3.7m manylinux: glibc 2.17+ x86-64

antidote-1.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (1.9 MB view details)

Uploaded CPython 3.7m manylinux: glibc 2.12+ x86-64 manylinux: glibc 2.5+ x86-64

antidote-1.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl (1.9 MB view details)

Uploaded CPython 3.7m manylinux: glibc 2.17+ i686 manylinux: glibc 2.5+ i686

File details

Details for the file antidote-1.1.0.tar.gz.

File metadata

  • Download URL: antidote-1.1.0.tar.gz
  • Upload date:
  • Size: 181.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.8.0 pkginfo/1.8.2 readme-renderer/34.0 requests/2.27.1 requests-toolbelt/0.9.1 urllib3/1.26.8 tqdm/4.63.0 importlib-metadata/4.11.3 keyring/23.5.0 rfc3986/2.0.0 colorama/0.4.4 CPython/3.10.0

File hashes

Hashes for antidote-1.1.0.tar.gz
Algorithm Hash digest
SHA256 538820be46b0c389aff99f81105d3f2190230510ccda44a745df3465c9ed9b8a
MD5 f375cb2dbe9825e8cf785919c8c6997a
BLAKE2b-256 1abf2e4785ed11ad779a1f662dc989068aeecb5ed5a4aafdc2513e9cdd6ac1b6

See more details on using hashes here.

Provenance

File details

Details for the file antidote-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

  • Download URL: antidote-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
  • Upload date:
  • Size: 2.2 MB
  • Tags: CPython 3.10, manylinux: glibc 2.17+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.8.0 pkginfo/1.8.2 readme-renderer/34.0 requests/2.27.1 requests-toolbelt/0.9.1 urllib3/1.26.8 tqdm/4.63.0 importlib-metadata/4.11.3 keyring/23.5.0 rfc3986/2.0.0 colorama/0.4.4 CPython/3.10.0

File hashes

Hashes for antidote-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 0f5dfda9786f6f4db1e08c8882e1825b246062208eb5afc7660386cf3fe51a04
MD5 3c06bab335db7f375251a4c6a418c5bf
BLAKE2b-256 7235b02541125bac527d49465df8b689e93899c61b0558ad9afbe8b5c15a77ac

See more details on using hashes here.

Provenance

File details

Details for the file antidote-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl.

File metadata

File hashes

Hashes for antidote-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl
Algorithm Hash digest
SHA256 687265d77cd94e9e9f20f2a827949abb1376413513340748c190d30da7c76c49
MD5 e9e64b2c5ce5cf7522386c12bb28d950
BLAKE2b-256 a3cab541ea32d3c5da730e94190e60dfc905274b035a035b1e6b58b0625a7c56

See more details on using hashes here.

Provenance

File details

Details for the file antidote-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl.

File metadata

File hashes

Hashes for antidote-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 40733e6c7763f9d4593da6e08571d72affa78a26491d263d0219c4d93e552e78
MD5 defa77d1cb2e7e3be42b435ba232a009
BLAKE2b-256 a5745d005386e79e823e0899d8261f663b59820b2bfd01b78f353453c7eb83a0

See more details on using hashes here.

Provenance

File details

Details for the file antidote-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

  • Download URL: antidote-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
  • Upload date:
  • Size: 2.2 MB
  • Tags: CPython 3.9, manylinux: glibc 2.17+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.8.0 pkginfo/1.8.2 readme-renderer/34.0 requests/2.27.1 requests-toolbelt/0.9.1 urllib3/1.26.8 tqdm/4.63.0 importlib-metadata/4.11.3 keyring/23.5.0 rfc3986/2.0.0 colorama/0.4.4 CPython/3.10.0

File hashes

Hashes for antidote-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 984128d991f9766caccd81946c8ca0bcd0702ce4b62fcfa631a574dec35df5e0
MD5 835753540c0c2e745d44f9720aa9d4f9
BLAKE2b-256 17ae875ee3862a48f997b512e15e38a7397d397d86aa3a9aa12efee82ee9e6f8

See more details on using hashes here.

Provenance

File details

Details for the file antidote-1.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl.

File metadata

File hashes

Hashes for antidote-1.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl
Algorithm Hash digest
SHA256 bf2d5a778e8803aad50ac824d1a8104eff8c67e4b2f27a41e685b0f0ab9b54a2
MD5 be6419aab7b0baf261f63e8c9b071993
BLAKE2b-256 e20b269c4cbb6977a4c0556813cf52b85e83220946b4d97e98c1350891515714

See more details on using hashes here.

Provenance

File details

Details for the file antidote-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl.

File metadata

File hashes

Hashes for antidote-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 c17dcc31dd91309e12841758f47ed2963e3511b44a880ab25a0291132851bb3c
MD5 14b95d084b279ba26126d1a700d92857
BLAKE2b-256 2628aa80167c6834bca3498e4aaa74d4dc84a7ca7b677830497d851dc588f19e

See more details on using hashes here.

Provenance

File details

Details for the file antidote-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

  • Download URL: antidote-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
  • Upload date:
  • Size: 2.2 MB
  • Tags: CPython 3.8, manylinux: glibc 2.17+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.8.0 pkginfo/1.8.2 readme-renderer/34.0 requests/2.27.1 requests-toolbelt/0.9.1 urllib3/1.26.8 tqdm/4.63.0 importlib-metadata/4.11.3 keyring/23.5.0 rfc3986/2.0.0 colorama/0.4.4 CPython/3.10.0

File hashes

Hashes for antidote-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 3f7833dbca919399c29ad620ee10ca63452940803cc0f0dae9cd6f1e85462b96
MD5 3c97a26ce44eaa4d5f87729bd6e59643
BLAKE2b-256 70615e45d8328ded09f403de7cf8b64973e9d10beef3a871f258abb70265b5c6

See more details on using hashes here.

Provenance

File details

Details for the file antidote-1.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl.

File metadata

File hashes

Hashes for antidote-1.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl
Algorithm Hash digest
SHA256 16a63ff5a0d423bdf18a52e3bf1cdbfc24d30ff424e28d13ec9bbbb7da324c1b
MD5 6046851c5a2fe4a0d44726294f5ad589
BLAKE2b-256 8de7afd9c40806cc07c3ef72dafb127fb77dd8e980f5a3971a72ea2080817e64

See more details on using hashes here.

Provenance

File details

Details for the file antidote-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl.

File metadata

File hashes

Hashes for antidote-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 12c9b66a217924086f71a40caeb3d6c039abd6f896bc775eae258cf754450f89
MD5 ed4a35538dbf87bf7c4f9c4a142a3ec1
BLAKE2b-256 eb17f828e5e86f45cbcfe97b30d6f0465c5652193621db4f77747830f5130c11

See more details on using hashes here.

Provenance

File details

Details for the file antidote-1.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

  • Download URL: antidote-1.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
  • Upload date:
  • Size: 2.0 MB
  • Tags: CPython 3.7m, manylinux: glibc 2.17+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.8.0 pkginfo/1.8.2 readme-renderer/34.0 requests/2.27.1 requests-toolbelt/0.9.1 urllib3/1.26.8 tqdm/4.63.0 importlib-metadata/4.11.3 keyring/23.5.0 rfc3986/2.0.0 colorama/0.4.4 CPython/3.10.0

File hashes

Hashes for antidote-1.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 16fbfce470fd136bc32bb60c89d1cc58fd33c363c93e626fadf758951ca9b42f
MD5 136434708b694848ff1e0896c7b694b0
BLAKE2b-256 c83865c594c06d64622c27f5a93ab3c5bbaa7c686339ca163ad2e36660001227

See more details on using hashes here.

Provenance

File details

Details for the file antidote-1.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl.

File metadata

File hashes

Hashes for antidote-1.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl
Algorithm Hash digest
SHA256 b7645504ac7a4b10165497ee78ae2ee2f1b6769a8b264bf574f0829a19caf419
MD5 738c2933ab686bd8530cc682544406aa
BLAKE2b-256 ed6c9b2a2b73a50556576668918ec95fa3ffd04567c7de5bc5814714720ff565

See more details on using hashes here.

Provenance

File details

Details for the file antidote-1.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl.

File metadata

File hashes

Hashes for antidote-1.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 2be66d7c520462c5dff51ae2188592cc1ea1a8e26a65507fcae0d82e63d2cd9e
MD5 8070e449662a6a0dda30f73de7de8776
BLAKE2b-256 a46f64b1ea2b666c1ac72fb444384369e2cb766bfd7db89c52945a8b71b7b139

See more details on using hashes here.

Provenance

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page