Skip to content
This repository was archived by the owner on Jan 5, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions examples/cherrypy/scopes.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import cherrypy
from snakeguice.providers import InstanceProvider
from snakeguice.providers import create_instance_provider


class CherrypyRequestScope(object):
Expand All @@ -14,7 +14,7 @@ def get(self):
if not value:
value = cherrypy.request.__guicy__[key] = provider.get()
return value
return InstanceProvider(SessionProvider())
return create_instance_provider(SessionProvider())


class CherrypySessionScope(object):
Expand All @@ -26,8 +26,8 @@ def get(self):
if not value:
value = cherrypy.session[key] = provider.get()
return value
return InstanceProvider(SessionProvider())
return create_instance_provider(SessionProvider())


CHERRYPY_REQUEST_SCOPE = CherrypyRequestScope()
CHERRYPY_SESSION_SCOPE = CherrypySessionScope()
CHERRYPY_REQUEST_SCOPE = CherrypyRequestScope
CHERRYPY_SESSION_SCOPE = CherrypySessionScope
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from setuptools import setup, Command

from snakeguice import __pkginfo__ as pkg

import sys
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The specs command uses the sys module, but this import was missing.


cmdclass = {}

Expand Down
6 changes: 5 additions & 1 deletion snakeguice/annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ def __hash__(self):
return hash(self.__class__.__name__ + ":" + self._value)

def __eq__(self, other):
return self.__hash__() == other.__hash__()
return self.__class__.__name__ == other.__class__.__name__ \
and self._value == other._value

def __ne__(self, other):
return not self == other

def __str__(self):
return "<Annotation class '%s' value: %s>" % (self.__class__.__name__, self._value)
65 changes: 57 additions & 8 deletions snakeguice/binder.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ def __eq__(self, other):

def __ne__(self, other):
return not self == other


def __str__(self):
return "<Key(%s, %s)>" % (self._interface, self._annotation)

class _EmptyBinder(object):

def get_binding(self, key):
def get_binding(self, key, annotated_with=None):
return None


Expand All @@ -29,13 +31,30 @@ class Binder(object):
def __init__(self, parent=None):
self._parent = parent or _EmptyBinder()
self._binding_map = {}
self._scope_cache = {}
# register the builtin scopes
self.bind_scope(scopes.NO_SCOPE, scopes.NO_SCOPE())
self.bind_scope(scopes.SINGLETON, scopes.SINGLETON())
# Allow the scopes to be injected.
# Note: no need to specify a scope, because they ARE the scope.
# However, should they be injectable?
self.bind(scopes.NO_SCOPE, to_instance=self._scope_cache[scopes.NO_SCOPE])
self.bind(scopes.SINGLETON, to_instance=self._scope_cache[scopes.SINGLETON])

def bind(self, _class, **kwargs):
key = Key(interface=_class, annotation=kwargs.get('annotated_with'))

binding = Binding()
binding.key = key
binding.scope = kwargs.get('in_scope', scopes.NO_SCOPE)
scope = kwargs.get('in_scope')
if scope is not None:
if not self._scope_cache.has_key(scope):
raise errors.BindingError("'scope' has not been bound to this Binder via bind_scope")
scope = self._scope_cache[scope]
else:
scope = self._scope_cache[scopes.NO_SCOPE]

binding.scope = scope

if key in self._binding_map:
raise errors.BindingError('baseclass %r already bound' % _class)
Expand All @@ -61,18 +80,35 @@ def bind(self, _class, **kwargs):

self._binding_map[key] = binding

def get_binding(self, key):
return self._binding_map.get(key) or self._parent.get_binding(key)
def get_binding(self, cls, annotated_with=None):
key = Key(cls, annotation=annotated_with)
return self._binding_map.get(key) or self._parent.get_binding(cls, annotated_with)

def create_child(self):
return Binder(self)


def bind_scope(self, scopeClazz, scopeInstance):
if not isinstance(scopeClazz, type):
raise errors.BindingError(
"bind_scope requires a new-style class")
if not isinstance(scopeInstance, scopeClazz):
raise errors.BindingError(
"bind_scope requires an instance of the scope class")
self._scope_cache[scopeClazz] = scopeInstance

class LazyBinder(object):

def __init__(self, parent=None):
self._parent = parent or _EmptyBinder()
self._binding_map = {}
self._scope_cache = {}
# register the builtin scopes
self.bind_scope(scopes.NO_SCOPE, scopes.NO_SCOPE())
self.bind_scope(scopes.SINGLETON, scopes.SINGLETON())
# Allow the scopes to be injected.
# Note: no need to specify a scope, because they ARE the scope.
self.bind(scopes.NO_SCOPE, to_instance=self._scope_cache[scopes.NO_SCOPE])
self.bind(scopes.SINGLETON, to_instance=self._scope_cache[scopes.SINGLETON])
self._errors = []

def add_error(self, msg):
Expand All @@ -95,6 +131,12 @@ def bind(self, _class, **kwargs):

binding = Binding()
binding.key = key
scope = kwargs.get('in_scope')
if scope is not None and not self._scope_cache.has_key(scope):
self.add_error("'scope' has not been bound to this Binder via bindScope")
else:
scope = self._scope_cache[scopes._NoCache]

binding.scope = kwargs.get('in_scope', scopes.NO_SCOPE)

if key in self._binding_map:
Expand All @@ -121,12 +163,19 @@ def bind(self, _class, **kwargs):

self._binding_map[key] = binding

def get_binding(self, key):
return self._binding_map.get(key) or self._parent.get_binding(key)
def get_binding(self, cls, annotated_with=None):
key = Key(cls, annotation=annotated_with)
return self._binding_map.get(key) or self._parent.get_binding(cls, annotated_with)

def create_child(self):
return Binder(self)

def bind_scope(self, scopeClazz, scopeInstance):
if not isinstance(scopeClazz, type):
self.add_error("bind_scope requires a new-style class")
if not isinstance(scopeInstance, scopeClazz):
self.add_error("bind_scope requires an instance of the scope class")
self._scope_cache[scopeClazz] = scopeInstance

class Binding(object):

Expand Down
19 changes: 15 additions & 4 deletions snakeguice/injector.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,22 @@ def get_instance(self, cls, annotation=None):
return self

key = Key(cls, annotation)
binding = self._binder.get_binding(key)
binding = self._binder.get_binding(cls, annotation)
if binding:
if isinstance(binding.provider, type):
binding.provider = self.get_instance(binding.provider)
provider = binding.scope.scope(key, binding.provider)
# Both the binding.provider and the scope's
# provider could be types, so inject both.
bindingProvider = binding.provider
if isinstance(bindingProvider, type):
# We can't update binding.provider as an optimization
# unless we can prove that the provider can only
# have one instance.
# (by existing inside our binder/parent binders Singleton scopes,
# or via some other additional proof that there can be only one
# instance)
# The reasoning behind this is that all bindings have
# scopes, and the proivder's binding could exist in a scope.
bindingProvider = self.get_instance(bindingProvider)
provider = binding.scope.scope(key, bindingProvider)
if isinstance(provider, type):
provider = self.get_instance(provider)
return provider.get()
Expand Down
106 changes: 76 additions & 30 deletions snakeguice/multibinder.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,84 @@
from snakeguice.decorators import inject
from snakeguice.interfaces import Injector
from snakeguice.errors import MultiBindingError

import itertools

class _RealElement(object):
"""
This helper class exists to help construct unique annotation names for MultiBinder generating bindings.
"""
unique = itertools.count()
def __init__(self, name, type):
self._name = name
self._type = type
# Thanks to the global interpreter lock
# this line is perfectly thread safe.
self._uniqueId = self.unique.next()

def __str__(self):
return "<Element(setName=%s, type=%s, uniqueId=%s>" % (self._name, self._type, self._uniqueId)

def __hash__(self):
return (127 * (hash("setName") ^ hash(self._name)) \
+ 127 * (hash("uniqueId") ^ hash(self._uniqueId)) \
+ 127 * (hash("type") ^ hash(self._type))) & 0xFFFFFFFF

def __eq__(self, other):
return self._name == other._name \
and self._type == other._type \
and self._uniqueId == other._uniqueId

def __ne__(self, other):
return not self == other


class _MultiBinder(object):

def __init__(self, binder, interface):
def __init__(self, binder, interface, annotated_with=None):
self._binder = binder
self._interface = interface
self._multiBindingType = self.multibinding_type(self._interface)
self._cls = self._multiBindingType
self._annotation = annotated_with
self._name = self.name_of(self._cls, self._annotation)
self._provider = self._get_or_create_provider()

def name_of(self, cls, annotated_with):
annotation = annotated_with
if annotation is not None:
return str(annotation)
else:
return ""

def _get_or_create_provider(self):
key = Key(self.multibinding_type(self._interface))
binding = self._binder.get_binding(key)
binding = self._binder.get_binding(self._cls, self._annotation)
if not binding:
self._binder.bind(self.multibinding_type(self._interface),
to_provider=self._create_provider())
binding = self._binder.get_binding(key)
# The key binding the MultiBinder subclass is Key(self.multibinding_type(<iface>), annotation)
self._binder.bind(self._cls,
to_provider=self._create_provider(), annotated_with=self._annotation)
binding = self._binder.get_binding(self._cls, self._annotation)
return binding.provider

def _dsl_to_provider(self, to, to_provider, to_instance):
def _dsl_to_binding(self, to, to_provider, to_instance, in_scope):
"""
This method registers bindings for each item in the _MultiBinder subclass.
The binding key is Key(<itemInterface>, <computedAnnotationName based on self.multibinding_type>)
Registering items with the binder allows the items to exist inside of scopes if necessary.
"""
itemCls = self._interface
itemAnnotation = _RealElement(self._name, self.multibinding_type.__name__)
if to:
#TODO: add some validation
return providers.create_simple_provider(to)
self._binder.bind(itemCls, to=to, in_scope=in_scope, annotated_with=itemAnnotation)
elif to_provider:
#TODO: add some validation
return to_provider
self._binder.bind(itemCls, to_provider=to_provider, in_scope=in_scope, annotated_with=itemAnnotation)
elif to_instance:
#TODO: add some validation
return providers.create_instance_provider(to_instance)
self._binder.bind(itemCls, to_instance=to_instance, in_scope=in_scope, annotated_with=itemAnnotation)
else:
raise MultiBindingError('incorrect arguments to %s.add_binding'
% self.__class__.__name__)

return self._binder.get_binding(itemCls, itemAnnotation)

class List(Annotation):
"""Used for binding lists."""
Expand All @@ -45,25 +91,25 @@ class ListBinder(_MultiBinder):

multibinding_type = List

def add_binding(self, to=None, to_provider=None, to_instance=None):
provider = self._dsl_to_provider(to, to_provider, to_instance)
self._provider.add_provider(provider)
def add_binding(self, to=None, to_provider=None, to_instance=None, in_scope=None):
binding = self._dsl_to_binding(to, to_provider, to_instance, in_scope)
self._provider.add_binding(binding)

def _create_provider(self):
class DynamicMultiBindingProvider(object):
providers = []
bindings = []

@inject(injector=Injector)
def __init__(self, injector):
self._injector = injector

@classmethod
def add_provider(cls, provider):
cls.providers.append(provider)
def add_binding(cls, binding):
cls.bindings.append(binding)

def get(self):
return [self._injector.get_instance(p).get()
for p in self.providers]
return [self._injector.get_provider(b.key._interface, b.key._annotation).get()
for b in self.bindings]

return DynamicMultiBindingProvider

Expand All @@ -76,30 +122,30 @@ class DictBinder(_MultiBinder):

multibinding_type = Dict

def add_binding(self, key, to=None, to_provider=None, to_instance=None):
provider = self._dsl_to_provider(to, to_provider, to_instance)
self._provider.add_provider(key, provider)
def add_binding(self, key, to=None, to_provider=None, to_instance=None, in_scope=None):
binding = self._dsl_to_binding(to, to_provider, to_instance, in_scope)
self._provider.add_binding(key, binding)

def _create_provider(self):
binder_self = self

class DynamicMultiBindingProvider(object):
providers = {}
bindings = {}

@inject(injector=Injector)
def __init__(self, injector):
self._injector = injector

@classmethod
def add_provider(cls, key, provider):
if key in cls.providers:
def add_binding(cls, key, binding):
if key in cls.bindings:
msg = ('duplicate binding for %r in Dict(%s) found'
% (key, binder_self.interface.__class__.__name__))
raise MultiBindingError(msg)
cls.providers[key] = provider
cls.bindings[key] = binding

def get(self):
return dict([(k, self._injector.get_instance(p).get())
for k, p in self.providers.items()])
return dict([(k, self._injector.get_provider(b.key._interface, b.key._annotation).get())
for k, b in self.bindings.items()])

return DynamicMultiBindingProvider
21 changes: 14 additions & 7 deletions snakeguice/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,18 @@ def get(self):

return DynamicSimpleProvider


class __InstanceProvider(object):
__slots__ = ['__obj']
def __init__(self, obj):
self.__obj = obj
def get(self):
return self.__obj

def create_instance_provider(obj):
class DynamicInstanceProvider(object):

def get(self):
return obj

return DynamicInstanceProvider
"""
Snake Guice internal note:
Providers can be dynamically instantiated by the injector
but there is no reason to delay that when binding directly
to instances.
"""
return __InstanceProvider(obj)
Loading