Skip to main content

A minimal IOC container inspired by Spring

Project description

PyPI version Build Status codecov

gean

A minimal IOC container inspired by Spring.

Install

python3 -m pip install gean

Requirements

gean, like Spring, relies on types and signatures to build and resolve the dependency graph.

Required language features:

Features

Type hierarchies

A dependency of a given type X is exposed not only as X but also all of its super types, including generic interfaces. Variance is supported on generic types.

Regular inheritance

from abc import ABC
from gean import Container

# Works with or without ABC
class Worker(ABC): pass
class WorkerImpl(Worker): pass

container = Container()
container.register_class(WorkerImpl)

# All of these return the same instance
c1 = container.resolve(WorkerImpl)
c2 = container.resolve(Worker)
assert c1 is c2
assert isinstance(c1, WorkerImpl)

Covariance

from gean import Container
from typing import Generic, TypeVar

_Tco = TypeVar('_Tco', covariant=True)

class Person: pass
class Student(Person): pass

class Factory(Generic[_Tco]): pass

class StudentFactory(Factory[Student]): pass

container = Container()
container.register_class(StudentFactory)

# All of these return the same instance
c1 = container.resolve(StudentFactory)
c2 = container.resolve(Factory[Student])
c3 = container.resolve(Factory[Person])
assert c1 is c2 is c3
assert isinstance(c1, StudentFactory)

Contravariance

from gean import Container
from typing import Generic, TypeVar

_Tcontra = TypeVar('_Tcontra', contravariant=True)

class Person: pass
class Student(Person): pass

class Validator(Generic[_Tcontra]): pass

class PersonValidator(Validator[Person]): pass

container = Container()
container.register_class(PersonValidator)

# All of these return the same instance
c1 = container.resolve(PersonValidator)
c2 = container.resolve(Validator[Student])
c3 = container.resolve(Validator[Person])
assert c1 is c2 is c3
assert isinstance(c1, PersonValidator)

Caching

All dependencies are cached as they are constructed.

from gean import Container

class A: pass

container = Container()
container.register_class(A)

a1 = container.resolve(A)
a2 = container.resolve(A)
assert a1 is a2

Autowiring

Dependencies can be autowired if a class does not declare an explicit constructor.

Field names can disambiguate same-type dependencies.

from gean import Container

class Subject:
  def work(self):
    print('working')

class Manager:
  subject: Subject  # will be autowired
  def run(self):
    self.subject.work()

container = Container()
# Order of registration does not matter
container.register_class(Manager)
container.register_class(Subject)

# This prints 'working'
container.resolve(Manager).run()

Constructor wiring

If a class defines an explicit constructor, dependencies will be passed as arguments.

Parameter names can disambiguate same-type dependencies.

from gean import Container

class Subject:
  def work(self):
    print('working')

class Manager:
  def __init__(self, subject: Subject):
    self.subject = subject
  def run(self):
    self.subject.work()

container = Container()
# Order of registration does not matter
container.register_class(Manager)
container.register_class(Subject)

# This prints 'working'
container.resolve(Manager).run()

Modules

A module is a class whose name ends in Module.

A module may use @includes to declaratively register other classes or modules.

A module may use public methods to create dependencies programmatically.

from gean import Container, includes

class PingService:
  def ping(self, addr): ...

class DNSService:
  def resolve(self, name): ...

@includes(
  DNSService,
  PingService,
)
class NetworkModule: pass

class AppConfig: pass

class Application:
  config: AppConfig
  dns_service: DNSService
  ping_service: PingService
  def run(self):
    print(self.config)
    self.ping_service.ping(self.dns_service.resolve('garciat.com'))

def load_configuration() -> AppConfig: ...

@includes(
  NetworkModule,
  Application,
)
class ApplicationModule:
  # Create config programmatically
  def config(self) -> AppConfig:
    return load_configuration()

container = Container()
# No other dependencies need to be declared manually
# Because the modules do so declaratively
container.register_module(ApplicationModule)

container.resolve(Application).run()

Singletons

Dependencies can be explicitly named so that disambiguation is possible.

from gean import Container

container = Container()
# Both dependencies are of type `str`
container.register_instance('/tmp', name='tmp_dir')
container.register_instance('/home/garciat', name='user_dir')

# Disambiguate with name
container.resolve(str, name='tmp_dir')

Alternatives

As of June 1 2020, this is a non-exhaustive list of alternative solutions that also leverage Type Hints.

injector

  • Does not support hierarchy with generic interfaces
from typing import Generic, TypeVar

from injector import Injector, Module, inject, provider, singleton


_T = TypeVar('_T')

class A(Generic[_T]): pass
class B(A[int]): pass

class C:
  @inject
  def __init__(self, a: A[int]):
    self.a = a


class MyModule(Module):
  @singleton
  @provider
  def provide_b(self) -> B:  # works if return type is A[int] explicitly
    return B()


i = Injector([MyModule])

# injector.UnknownProvider: couldn't determine provider for __main__.A[int] to None
i.get(C)

bobthemighty/punq

  • Does not support type hierarchies

jbasko/auto-init

  • Performs default initialization. E.g. 0 for int, None for objects
  • Does not support generic interfaces

asyncee/wint

  • Global state

History

gean started off as a gist I created to show @alexpizarroj how my team leverages Spring in our projects.

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

gean-0.0.11.tar.gz (8.3 kB view details)

Uploaded Source

Built Distribution

gean-0.0.11-py3-none-any.whl (7.7 kB view details)

Uploaded Python 3

File details

Details for the file gean-0.0.11.tar.gz.

File metadata

  • Download URL: gean-0.0.11.tar.gz
  • Upload date:
  • Size: 8.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.23.0 setuptools/47.1.1 requests-toolbelt/0.9.1 tqdm/4.46.0 CPython/3.7.7

File hashes

Hashes for gean-0.0.11.tar.gz
Algorithm Hash digest
SHA256 8740307a02471d7e2d00a443b3ca1054012518e32722320b3340374a580f41d0
MD5 5a0d218a3ea57a1544058e8b3f16fbd7
BLAKE2b-256 451c0a0754dbf3efae94993fd5480abbe78a409e8b76cc746d31201b8b830dcf

See more details on using hashes here.

File details

Details for the file gean-0.0.11-py3-none-any.whl.

File metadata

  • Download URL: gean-0.0.11-py3-none-any.whl
  • Upload date:
  • Size: 7.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.23.0 setuptools/47.1.1 requests-toolbelt/0.9.1 tqdm/4.46.0 CPython/3.7.7

File hashes

Hashes for gean-0.0.11-py3-none-any.whl
Algorithm Hash digest
SHA256 31723c9ace9ee9ea37082366d2110d98b369dbd6ce88453b034a8284ca7eed09
MD5 771160044c1293c3aaf46c21a506f8c1
BLAKE2b-256 ea7694d4ffc5041f48e4112cd69d9660ef43099afc0e323772aa7eda6f239c8e

See more details on using hashes here.

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