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://app.travis-ci.com/Finistere/antidote.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.6+. 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 Service, inject, Provide, service

class Database(Service):
    pass

# or

@service
class Database:
    pass

@inject
def f(db: Provide[Database]):
    pass

f()  # works !

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

f(Database())

@inject supports a lot of different ways to express which dependency should be used, the most important ones are:

  • annotated type hints:
    @inject
    def f(db: Provide[Database]):
        pass
  • list:
    @inject([Database])
    def f(db):
        pass
  • dictionary:
    @inject({'db': Database})
    def f(db):
        pass
  • auto_provide
    # All class type hints are treated as dependencies
    @inject(auto_provide=True)
    def f(db: Database):
        pass

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
world.get[Database]()  # omit dependency if it's the type hint itself

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. It can also be parametrized or configured differently.

from antidote import Service, Provide, inject

class QueryBuilder(Service):
    __antidote__ = Service.Conf(singleton=False)  # new instance each time

    # methods injected by default
    def __init__(self, db: Provide[Database]):
        self._db = db

@inject({'builder': QueryBuilder})
def load_data(builder):
    pass

load_data()  # yeah !

If you don’t want to inherit from Service you can use the class decorator service instead.

from antidote import service, inject

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

Constants

Constants are, by definition, constants that Antidote provides lazily. It’s primary use case is configuration:

from antidote import inject, Constants, const

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

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

ping_db()  # nice !

Now this looks a bit overkill, but it allows you to refactor it easily or load complex configuration lazily. Here is a similar example, but loading the configuration from the environment:

from typing import Annotated
# from typing_extensions import Annotated # Python < 3.9
from antidote import inject, Constants, const, Get

class Config(Constants):
    DB_HOST = const[str]()  # used as a type annotation
    DB_PORT = const[int]()  # and also to cast the value retrieved from `provide_const`
    # defaults are supported, used on LookupError
    DB_USER = const[str](default='postgres')

    def provide_const(self, name: str, arg: object):
        return os.environ[name]

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

@inject()
def check_connection(db_host: Annotated[str, Get(Config.DB_HOST)],
                     db_port: Annotated[int, Get(Config.DB_PORT)]):
    pass

check_connection()  # perfect !

Note that we could have replaced the previous Config without any changes in the clients.

Factory

Factories are used by Antidote to generate a dependency. It can either be a class or a function. The resulting dependency can be a singleton or not. Scopes are also supported. If a class is used it’ll be wired (injection of methods) in the same way as Service:

from antidote import factory, inject, Provide

class User:
    pass

@factory(singleton=False)  # annotated type hints can be used or you can @inject manually
def current_user(db: Provide[Database]) -> User:  # return type annotation is used
    return User()

# Note that here you *know* exactly where it's coming from.
@inject({'user': User @ current_user})
def is_admin(user: User):
    pass

Easy to understand where the dependency is actually coming from ! Like Service, you can also retrieve it by hand:

from antidote import world

world.get(User @ current_user)
world.get[User](User @ current_user)  # with type hint
world.get[User] @ current_user  # same, but shorter

Now with a request scope and a factory class:

from typing import Annotated
# from typing_extensions import Annotated # Python < 3.9
from antidote import Factory, inject, Provide, world, From

REQUEST_SCOPE = world.scopes.new(name='request')

class CurrentUser(Factory):
    __antidote__ = Factory.Conf(scope=REQUEST_SCOPE)

    # injecting it in __call__() would have also worked
    def __init__(self, db: Provide[Database]):
        self._db = db

    def __call__(self) -> User:
        return User()

@inject
def is_admin(user: Annotated[User, From(CurrentUser)]):
    pass

is_admin()

# Reset all dependencies in the specified scope.
world.scopes.reset(REQUEST_SCOPE)

Here also, knowing where and how a scope is used is straightforward with an IDE.

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 Service, implementation, inject, factory

class Cache:
    pass

class MemoryCache(Cache, Service):
    pass

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

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

@implementation(Cache)
def cache_impl():
    import os

    if os.environ.get('USE_REDIS_CACHE'):
        return Redis @ redis_cache

    # Returning the dependency that must be retrieved
    return MemoryCache

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

from typing import Annotated
# from typing_extensions import Annotated # Python < 3.9
from antidote import world, inject, From

@inject
def heavy_compute(cache: Annotated[Cache, From(cache_impl)]):
    pass


world.get[Cache] @ cache_impl

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, Provide

class Database(Service):
    pass

@inject
def f(db: Provide[Database]):
    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.0.1.tar.gz (177.6 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

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

Uploaded CPython 3.10manylinux: glibc 2.17+ x86-64

antidote-1.0.1-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.10manylinux: glibc 2.12+ x86-64manylinux: glibc 2.5+ x86-64

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

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

antidote-1.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.1 MB view details)

Uploaded CPython 3.9manylinux: glibc 2.17+ x86-64

antidote-1.0.1-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.9manylinux: glibc 2.12+ x86-64manylinux: glibc 2.5+ x86-64

antidote-1.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl (2.0 MB view details)

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

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

Uploaded CPython 3.8manylinux: glibc 2.17+ x86-64

antidote-1.0.1-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.8manylinux: glibc 2.12+ x86-64manylinux: glibc 2.5+ x86-64

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

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

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

Uploaded CPython 3.7mmanylinux: glibc 2.17+ x86-64

antidote-1.0.1-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.7mmanylinux: glibc 2.12+ x86-64manylinux: glibc 2.5+ x86-64

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

Uploaded CPython 3.7mmanylinux: glibc 2.17+ i686manylinux: glibc 2.5+ i686

antidote-1.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.0 MB view details)

Uploaded CPython 3.6mmanylinux: glibc 2.17+ x86-64

antidote-1.0.1-cp36-cp36m-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.6mmanylinux: glibc 2.12+ x86-64manylinux: glibc 2.5+ x86-64

antidote-1.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl (1.9 MB view details)

Uploaded CPython 3.6mmanylinux: glibc 2.17+ i686manylinux: glibc 2.5+ i686

File details

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

File metadata

  • Download URL: antidote-1.0.1.tar.gz
  • Upload date:
  • Size: 177.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.5.0 importlib_metadata/4.8.1 pkginfo/1.7.1 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.9.6

File hashes

Hashes for antidote-1.0.1.tar.gz
Algorithm Hash digest
SHA256 0ca0d239e630ef0a7dabbef564ce10636758091aa9a76f8a1be0173d1d540304
MD5 5d3e65a0280bbedc4d95ef91a262167a
BLAKE2b-256 e63bb531dc08ea81091896515ff3351f145c1441b1265c1f9efcdd3c684d0e69

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for antidote-1.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 c8cf581ffacf1654e73423c31f072466358c2dc31461360f43d04fb93eda3d15
MD5 77fef0eee1f925f0458dfefcf23eec90
BLAKE2b-256 f554ea6fff987a893431864dde5c60e10316586409523b64a93315af9c2faff7

See more details on using hashes here.

File details

Details for the file antidote-1.0.1-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.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl
Algorithm Hash digest
SHA256 2a727d3b53808ff21256c89645417da01d18ebf8c4e2aab03280d425a07a9414
MD5 25d1ce760ee4a7f9f1ae36b93b6f415a
BLAKE2b-256 d865a4d76b8eaae632aacdf44d2b2db89ef3af06321066fb9d622e8fb85a2568

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for antidote-1.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 796895cf85d8febd981c001968a2317703e56ef82ee9ea15bea03ade12d6df6b
MD5 a0f1a96c9f7ecc5c9ba95edc4c528644
BLAKE2b-256 1fa977c33867908afd496df19c05038422084f7defc9a4eda7b0cab7951f6694

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for antidote-1.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 4647689a89d2b6b781a40dffd8942e250fcd6f997746f8bba3d7d3869be67af5
MD5 c0608e0ee91494821fc399d3e32a1c1c
BLAKE2b-256 3df175f243e56466feae443facc94886f8b49fb5e75b8f605ef38266aa17779e

See more details on using hashes here.

File details

Details for the file antidote-1.0.1-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.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl
Algorithm Hash digest
SHA256 43563b464a6c9104c23b76b6b2c3ee75160b87d48959f1f4982465e94bc2bf1f
MD5 f28a15a1452a76278b7199d68ee0c8c3
BLAKE2b-256 acc0d997e2abb9cfff9922a0ffba09ffddba1185dd75b8c96c2b03d34cdbb80b

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for antidote-1.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 5005fe0af7329be7fda0f508e83cf606585697cb5737effcb8e3508132675d11
MD5 f907b5a7cc0da9a5cff6f900f60d8a66
BLAKE2b-256 fcfb31bd8b9133e9abc946764fc018da994599ffa3c8b07fc0dadb3dc7391466

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for antidote-1.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 b05f6489bbabbd4dcc70cc2655c45a4b9c1360a35374f5a5ba1e7350d6bdddcd
MD5 c3435bb973e4276043d79952d9216e79
BLAKE2b-256 d70b0a3494bacb860b65f8a06641fb06eb032f608fa7a31003856e14bb7c038b

See more details on using hashes here.

File details

Details for the file antidote-1.0.1-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.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl
Algorithm Hash digest
SHA256 7c27e0ebacdf9a057114c42d8fd09736d8d64a9490458eff3f0d2059def378e5
MD5 8589d54422792e406c2b63e9ebdf5722
BLAKE2b-256 ec9f5c6b7327255627296ec3fed2ab4b200bfd6ae1e12edb57c48dca463aa6fa

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for antidote-1.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 f7719c3d589b88b4ff3776af835a6f16200d4a6bd447b67327b0daf457efc00f
MD5 e76d2d19b8d959239dc1669c904cdeb1
BLAKE2b-256 84376b6c42b47e47e138a76178858cd48086de94b283c4c661cfb6cf512f0aa9

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for antidote-1.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 216b1f69eb9871cbe72e92dee1bcbe00d3e691eaf38879958e2ba2014f9bbec5
MD5 66bc016f5ae27e9814b7f859bff7ac09
BLAKE2b-256 da2bd2688df6c1ff7978cfd29c8f9728b8af1d3095558d0aeb3654d222c7f53b

See more details on using hashes here.

File details

Details for the file antidote-1.0.1-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.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl
Algorithm Hash digest
SHA256 b27eaddd2891877aa692a2885b3c1aa8e2ecc704169a30149f4c5db2b5b94dd7
MD5 a902e520a3349a8aca926237b3bf41d3
BLAKE2b-256 e5c063eb8963d1a326fd463dfd19513d459bd44971d17187c95ebb4127d438e5

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for antidote-1.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 91b242139c831bb96989470f92851f9c69590d35921f1dccab8bbd4df29f0bed
MD5 c15fe0340e687a171516ae2e262397bc
BLAKE2b-256 5a997448413bb0ae4e65eb2a4200a47fcbbe6deb4ef9769aa3abc80f85455013

See more details on using hashes here.

File details

Details for the file antidote-1.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for antidote-1.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 3f675fd8cc34cc97a6088c4aa62d2b1ba1ce2cc5fa406969a70645ec29ffd363
MD5 b39026b38cbab88cf1fa5ff8ee02a759
BLAKE2b-256 babedacec05f823a9be3bb6883b63bb731c815482769f9bf1179402332ad3a04

See more details on using hashes here.

File details

Details for the file antidote-1.0.1-cp36-cp36m-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.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl
Algorithm Hash digest
SHA256 d5cab0e73676dc69566180d08b336fdd2022c9004d6ceea5dd3fd28d69bef50f
MD5 12bca16a6d87fccc5d59bbb00e3ef090
BLAKE2b-256 db569022c432cd7c721485da3ce51eca2d4f56ab8e0f99cb445dfd3f9724e3fe

See more details on using hashes here.

File details

Details for the file antidote-1.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl.

File metadata

File hashes

Hashes for antidote-1.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 224740b0704e0e423e157d7ba827fa5e1cb05bfa31913e95bdfc7bf1fb08604c
MD5 b728e83acdb8bb3f92bc380648af8e47
BLAKE2b-256 0dda42260510b010e6ba445cc30c081aa1910730d6939e6a5f1de7441afe7965

See more details on using hashes here.

Supported by

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