معرفی شرکت ها


andi-0.4.1


Card image cap
تبلیغات ما

مشتریان به طور فزاینده ای آنلاین هستند. تبلیغات می تواند به آنها کمک کند تا کسب و کار شما را پیدا کنند.

مشاهده بیشتر
Card image cap
تبلیغات ما

مشتریان به طور فزاینده ای آنلاین هستند. تبلیغات می تواند به آنها کمک کند تا کسب و کار شما را پیدا کنند.

مشاهده بیشتر
Card image cap
تبلیغات ما

مشتریان به طور فزاینده ای آنلاین هستند. تبلیغات می تواند به آنها کمک کند تا کسب و کار شما را پیدا کنند.

مشاهده بیشتر
Card image cap
تبلیغات ما

مشتریان به طور فزاینده ای آنلاین هستند. تبلیغات می تواند به آنها کمک کند تا کسب و کار شما را پیدا کنند.

مشاهده بیشتر
Card image cap
تبلیغات ما

مشتریان به طور فزاینده ای آنلاین هستند. تبلیغات می تواند به آنها کمک کند تا کسب و کار شما را پیدا کنند.

مشاهده بیشتر

توضیحات

Library for annotation-based dependency injection
ویژگی مقدار
سیستم عامل -
نام فایل andi-0.4.1
نام andi
نسخه کتابخانه 0.4.1
نگهدارنده []
ایمیل نگهدارنده []
نویسنده Mikhail Korobov
ایمیل نویسنده kmike84@gmail.com
آدرس صفحه اصلی https://github.com/scrapinghub/andi
آدرس اینترنتی https://pypi.org/project/andi/
مجوز -
==== andi ==== .. image:: https://img.shields.io/pypi/v/andi.svg :target: https://pypi.python.org/pypi/andi :alt: PyPI Version .. image:: https://img.shields.io/pypi/pyversions/andi.svg :target: https://pypi.python.org/pypi/andi :alt: Supported Python Versions .. image:: https://travis-ci.com/scrapinghub/andi.svg?branch=master :target: https://travis-ci.com/scrapinghub/andi :alt: Build Status .. image:: https://codecov.io/github/scrapinghub/andi/coverage.svg?branch=master :target: https://codecov.io/gh/scrapinghub/andi :alt: Coverage report ``andi`` makes easy implementing custom dependency injection mechanisms where dependencies are expressed using type annotations. ``andi`` is useful as a building block for frameworks, or as a library which helps to implement dependency injection (thus the name - ANnotation-based Dependency Injection). License is BSD 3-clause. Installation ============ :: pip install andi andi requires Python >= 3.5.3. Goal ==== See the following classes that represents parts of a car (and the car itself): .. code-block:: python class Valves: pass class Engine: def __init__(self, valves): self.valves = valves class Wheels: pass class Car: def __init__(self, engine, wheels): self.engine = engine self.wheels = wheels The following would be the usual way of build a ``Car`` instance: .. code-block:: python valves = Valves() engine = Engine(valves) wheels = Wheels() car = Car(engine, wheels) There are some dependencies between the classes: A car requires and engine and wheels to be built, as well as the engine requires valves. These are the car dependencies and sub-dependencies. The question is, could we have an automatic way of building instances? For example, could we have a ``build`` function that given the ``Car`` class or any other class would return an instance even if the class itself has some other dependencies? .. code-block:: python car = build(Car) # Andi helps creating this generic build function ``andi`` inspect the dependency tree and creates a plan making easy creating such a ``build`` function. This is how this plan for the ``Car`` class would looks like: 1. Invoke ``Valves`` with empty arguments 2. Invoke ``Engine`` using the instance created in 1 as the argument ``valves`` 3. Invoke ``Wheels`` with empty arguments 4. Invoke ``Cars`` with the instance created in 2 as the ``engine`` argument and with the instance created in 3 as the ``wheels`` argument Type annotations ---------------- But there is a missing piece in the Car example before. How can ``andi`` know that the class ``Valves`` is required to build the argument ``valves``? A first idea would be to use the argument name as a hint for the class name (as `pinject <https://pypi.org/project/pinject/>`_ does), but ``andi`` opts to rely on arguments' type annotations instead. The classes for ``Car`` should then be rewritten as: .. code-block:: python class Valves: pass class Engine: def __init__(self, valves: Valves): self.valves = valves class Wheels: pass class Car: def __init__(self, engine: Engine, wheels: Wheels): self.engine = engine self.wheels = wheels Note how now there is a explicit annotation stating that the ``valves`` argument is of type ``Valves`` (same for ``engine`` and ``wheels``). The ``andi.plan`` function can now create a plan to build the ``Car`` class (ignore the ``is_injectable`` parameter by now): .. code-block:: python plan = andi.plan(Car, is_injectable={Engine, Wheels, Valves}) This is what the ``plan`` variable contains: .. code-block:: python [(Valves, {}), (Engine, {'valves': Valves}), (Wheels, {}), (Car, {'engine': Engine, 'wheels': Wheels})] Note how this plan correspond exactly to the 4-steps plan described in the previous section. Building from the plan ---------------------- Creating a generic function to build the instances from a plan generated by ``andi`` is then very easy: .. code-block:: python def build(plan): instances = {} for fn_or_cls, kwargs_spec in plan: instances[fn_or_cls] = fn_or_cls(**kwargs_spec.kwargs(instances)) return instances So let's see putting all the pieces together. The following code creates an instance of ``Car`` using ``andi``: .. code-block:: python plan = andi.plan(Car, is_injectable={Engine, Wheels, Valves}) instances = build(plan) car = instances[Car] is_injectable ------------- It is not always desired for ``andi`` to manage every single annotation found. Instead is usually better to explicitly declare which types can be handled by ``andi``. The argument ``is_injectable`` allows to customize this feature. ``andi`` will raise an error on the presence of a dependency that cannot be resolved because it is not injectable. Usually is desirable to declare injectabilty by creating a base class to inherit from. For example, we could create a base class ``Injectable`` as base class for the car components: .. code-block:: python class Injectable(ABC): pass class Valves(Injectable): pass class Engine(Injectable): def __init__(self, valves: Valves): self.valves = valves class Wheels(Injectable): pass The call to ``andi.plan`` would then be: .. code-block:: python is_injectable = lambda cls: issubclass(cls, Injectable) plan = andi.plan(Car, is_injectable=is_injectable) Functions and methods --------------------- Dependency injection is also very useful when applied to functions. Imagine that you have a function ``drive`` that drives the ``Car`` through the ``Road``: .. code-block:: python class Road(Injectable): ... def drive(car: Car, road: Road, speed): ... # Drive the car through the road The dependencies has to be resolved before invoking the ``drive`` function: .. code-block:: python plan = andi.plan(drive, is_injectable=is_injectable) instances = build(plan.dependencies) Now the ``drive`` function can be invoked: .. code-block:: python drive(instances[Car], instances[Road], 100) Note that ``speed`` argument was not annotated. The resultant plan just won't include it because the ``andi.plan`` ``full_final_kwargs`` parameter is ``False`` by default. Otherwise, an exception would have been raised (see ``full_final_kwargs`` argument documentation for more information). An alternative and more generic way to invoke the drive function would be: .. code-block:: python drive(speed=100, **plan.final_kwargs(instances)) dataclasses and attrs --------------------- ``andi`` supports classes defined using `attrs <https://www.attrs.org/>`_ and also `dataclasses <https://docs.python.org/3/library/dataclasses.html>`_. For example the ``Car`` class could have been defined as: .. code-block:: python # attrs class example @attr.s(auto_attribs=True) class Car: engine: Engine wheels: Wheels # dataclass example @dataclass class Car(Injectable): engine: Engine wheels: Wheels Using ``attrs`` or ``dataclass`` is handy because they avoid some boilerplate. Externally provided dependencies -------------------------------- Retaining the control over object instantiation could be desired in some cases. For example creating a database connection could require accessing some credentials registry or getting the connection from a pool so you might want to control building such instances outside of the regular dependency injection mechanism. ``andi.plan`` allows to specify which types would be externally provided. Let's see an example: .. code-block:: python class DBConnection(ABC): @abstractmethod def getConn(): pass @dataclass class UsersDAO: conn: DBConnection def getUsers(): return self.conn.query("SELECT * FROM USERS") ``UsersDAO`` requires a database connection to run queries. But the connection will be provided externally from a pool, so we call then ``andi.plan`` using also the ``externally_provided`` parameter: .. code-block:: python plan = andi.plan(UsersDAO, is_injectable=is_injectable, externally_provided={DBConnection}) The build method should then be modified slightly to be able to inject externally provided instances: .. code-block:: python def build(plan, instances_stock=None): instances_stock = instances_stock or {} instances = {} for fn_or_cls, kwargs_spec in plan: if fn_or_cls in instances_stock: instances[fn_or_cls] = instances_stock[fn_or_cls] else: instances[fn_or_cls] = fn_or_cls(**kwargs_spec.kwargs(instances)) return instances Now we are ready to create ``UserDAO`` instances with ``andi``: .. code-block:: python plan = andi.plan(UsersDAO, is_injectable=is_injectable, externally_provided={DBConnection}) dbconnection = DBPool.get_connection() instances = build(plan.dependencies, {DBConnection: dbconnection}) users_dao = instances[UsersDAO] users = user_dao.getUsers() Note that being injectable is not required for externally provided dependencies. Optional -------- ``Optional`` type annotations can be used in case of dependencies that can be optional. For example: .. code-block:: python @dataclass class Dashboard: conn: Optional[DBConnection] def showPage(): if self.conn: self.conn.query("INSERT INTO VISITS ...") ... # renders a HTML page In this example, the ``Dashboard`` class generates a HTML page to be served, and also stores the number of visits into a database. Database could be absent in some environments, but you might want the dashboard to work even if it cannot log the visits. When a database connection is possible the plan call would be: .. code-block:: python plan = andi.plan(UsersDAO, is_injectable=is_injectable, externally_provided={DBConnection}) And the following when the connection is absent: .. code-block:: python plan = andi.plan(UsersDAO, is_injectable=is_injectable, externally_provided={}) It is also required to register the type of ``None`` as injectable. Otherwise ``andi.plan`` with raise an exception saying that "NoneType is not injectable". .. code-block:: python Injectable.register(type(None)) Union ----- ``Union`` can also be used to express alternatives. For example: .. code-block:: python @dataclass class UsersDAO: conn: Union[ProductionDBConnection, DevelopmentDBConnection] ``DevelopmentDBConnection`` will be injected in the absence of ``ProductionDBConnection``. Full final kwargs mode ------------------------- By default ``andi.plan`` won't fail if it is not able to provide some of the direct dependencies for the given input (see the ``speed`` argument in one of the examples above). This behaviour is desired when inspecting functions for which is already known that some arguments won't be injectable but they will be provided by other means (like the ``drive`` function above). But in other cases is better to be sure that all dependencies are fulfilled and otherwise fail. Such is the case for classes. So it is recommended to set ``full_final_kwargs=True`` when invoking ``andi.plan`` for classes. Why type annotations? --------------------- ``andi`` uses type annotations to declare dependencies (inputs). It has several advantages, and some limitations as well. Advantages: 1. Built-in language feature. 2. You're not lying when specifying a type - these annotations still work as usual type annotations. 3. In many projects you'd annotate arguments anyways, so ``andi`` support is "for free". Limitations: 1. Callable can't have two arguments of the same type. 2. This feature could possibly conflict with regular type annotation usages. If your callable has two arguments of the same type, consider making them different types. For example, a callable may receive url and html of a web page: .. code-block:: python def parse(html: str, url: str): # ... To make it play well with ``andi``, you may define separate types for url and for html: .. code-block:: python class HTML(str): pass class URL(str): pass def parse(html: HTML, url: URL): # ... This is more boilerplate though. Why doesn't andi handle creation of objects? -------------------------------------------- Currently ``andi`` just inspects callable and chooses best concrete types a framework needs to create and pass to a callable, without prescribing how to create them. This makes ``andi`` useful in various contexts - e.g. * creation of some objects may require asynchronous functions, and it may depend on libraries used (asyncio, twisted, etc.) * in streaming architectures (e.g. based on Kafka) inspection may happen on one machine, while creation of objects may happen on different nodes in a distributed system, and then actually running a callable may happen on yet another machine. It is hard to design API with enough flexibility for all such use cases. That said, ``andi`` may provide more helpers in future, once patterns emerge, even if they're useful only in certain contexts. Examples: callback based frameworks ----------------------------------- Spider example ************** Nothing better than a example to understand how ``andi`` can be useful. Let's imagine you want to implemented a callback based framework for writing spiders to crawl web pages. The basic idea is that there is framework in which the user can write spiders. Each spider is a collection of callbacks that can process data from a page, emit extracted data or request new pages. Then, there is an engine that takes care of downloading the web pages and invoking the user defined callbacks, chaining requests with its corresponding callback. Let's see an example of an spider to download recipes from a cooking page: .. code-block:: python class MySpider(Spider): start_url = "htttp://a_page_with_a_list_of_recipes" def parse(self, response): for url in recipes_urls_from_page(response) yield Request(url, callback=parse_recipe) def parse_recipe(self, response): yield extract_recipe(response) It would be handy if the user can define some requirements just by annotating parameters in the callbacks. And ``andi`` make it possible. For example, a particular callback could require access to the cookies: .. code-block:: python def parse(self, response: Response, cookies: CookieJar): # ... Do something with the response and the cookies In this case, the engine can use ``andi`` to inspect the ``parse`` method, and detect that ``Response`` and ``CookieJar`` are required. Then the framework will build them and will invoke the callback. This functionality would serve to inject into the users callbacks some components only when they are required. It could also serve to encapsulate better the user code. For example, we could just decouple the recipe extraction into it's own class: .. code-block:: python @dataclass class RecipeExtractor: response: Response def to_item(): return extract_recipe(self.response) The callback could then be defined as: .. code-block:: python def parse_recipe(extractor: RecipeExtractor): yield extractor.to_item() Note how handy is that with ``andi`` the engine can create an instance of ``RecipesExtractor`` feeding it with the declared ``Response`` dependency. In definitive, using ``andi`` in such a framework can provide great flexibility to the user and reduce boilerplate. Web server example ****************** ``andi`` can be useful also for implementing a new web framework. Let's imagine a framework where you can declare your sever in a class like the following: .. code-block:: python class MyWeb(Server): @route("/products") def productspage(self, request: Request): ... # return the composed page @route("/sales") def salespage(self, request: Request): ... # return the composed page The former case is composed of two endpoints, one for serving a page with a summary of sales, and a second one to serve the products list. Connection to the database can be required to sever these pages. This logic could be encapsulated in some classes: .. code-block:: python @dataclass class Products: conn: DBConnection def get_products() return self.conn.query("SELECT ...") @dataclass class Sales: conn: DBConnection def get_sales() return self.conn.query("SELECT ...") Now ``productspage`` and ``salespage`` methods can just declare that they require these objects: .. code-block:: python class MyWeb(Server): @route("/products") def productspage(self, request: Request, products: Products): ... # return the composed page @route("/sales") def salespage(self, request: Request, sales: Sales): ... # return the composed page And the framework can then be responsible to fulfill these dependencies. The flexibility offered would be a great advantage. As an example, if would be very easy to create a page that requires both sales and products: .. code-block:: python @route("/overview") def productspage(self, request: Request, products: Products, sales: Sales): ... # return the composed overview page Contributing ============ * Source code: https://github.com/scrapinghub/andi * Issue tracker: https://github.com/scrapinghub/andi/issues Use tox_ to run tests with different Python versions:: tox The command above also runs type checks; we use mypy. .. _tox: https://tox.readthedocs.io Changes ======= 0.4.1 (2021-02-11) ------------------ * Overrides support in ``andi.plan`` 0.4.0 (2020-04-23) ------------------ * ``andi.inspect`` can handle classes now (their ``__init__`` method is inspected) * ``andi.plan`` and ``andi.inspect`` can handle objects which are callable via ``__call__`` method. 0.3.0 (2020-04-03) ------------------ * ``andi.plan`` function replacing ``andi.to_provide``. * Rewrite README explaining the new approach based in ``plan`` method. * ``andi.inspect`` return non annotated arguments also. 0.2.0 (2020-02-14) ------------------ * Better attrs support (workaround issue with string type annotations). * Declare Python 3.8 support. * More tests; ensure dataclasses support. 0.1 (2019-08-28) ---------------- Initial release.


نحوه نصب


نصب پکیج whl andi-0.4.1:

    pip install andi-0.4.1.whl


نصب پکیج tar.gz andi-0.4.1:

    pip install andi-0.4.1.tar.gz