mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2024-11-22 09:36:48 +03:00
0.0.3 version
This commit is contained in:
parent
2a29c43115
commit
028e5e9ef7
102
README.md
102
README.md
|
@ -4,7 +4,7 @@ Objects
|
||||||
Python catalogs of objects providers.
|
Python catalogs of objects providers.
|
||||||
|
|
||||||
|
|
||||||
Example:
|
Example of objects catalog definition and usage:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
"""
|
"""
|
||||||
|
@ -16,12 +16,12 @@ import sqlite3
|
||||||
|
|
||||||
|
|
||||||
# Some example classes.
|
# Some example classes.
|
||||||
class A(object):
|
class ObjectA(object):
|
||||||
def __init__(self, db):
|
def __init__(self, db):
|
||||||
self.db = db
|
self.db = db
|
||||||
|
|
||||||
|
|
||||||
class B(object):
|
class ObjectB(object):
|
||||||
def __init__(self, a, db):
|
def __init__(self, a, db):
|
||||||
self.a = a
|
self.a = a
|
||||||
self.db = db
|
self.db = db
|
||||||
|
@ -38,14 +38,14 @@ class AppCatalog(Catalog):
|
||||||
Attribute('row_factory', sqlite3.Row))
|
Attribute('row_factory', sqlite3.Row))
|
||||||
""" :type: (objects.Provider) -> sqlite3.Connection """
|
""" :type: (objects.Provider) -> sqlite3.Connection """
|
||||||
|
|
||||||
object_a = NewInstance(A,
|
object_a = NewInstance(ObjectA,
|
||||||
InitArg('db', database))
|
InitArg('db', database))
|
||||||
""" :type: (objects.Provider) -> A """
|
""" :type: (objects.Provider) -> ObjectA """
|
||||||
|
|
||||||
object_b = NewInstance(B,
|
object_b = NewInstance(ObjectB,
|
||||||
InitArg('a', object_a),
|
InitArg('a', object_a),
|
||||||
InitArg('db', database))
|
InitArg('db', database))
|
||||||
""" :type: (objects.Provider) -> B """
|
""" :type: (objects.Provider) -> ObjectB """
|
||||||
|
|
||||||
|
|
||||||
# Catalog injection into consumer class.
|
# Catalog injection into consumer class.
|
||||||
|
@ -69,3 +69,91 @@ assert a1 is not a2
|
||||||
assert b1 is not b2
|
assert b1 is not b2
|
||||||
assert a1.db is a2.db is b1.db is b2.db
|
assert a1.db is a2.db is b1.db is b2.db
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Example of injections using objects.catalog:
|
||||||
|
|
||||||
|
```python
|
||||||
|
"""
|
||||||
|
Concept example of objects injections.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from objects import Catalog, Singleton, NewInstance, InitArg, Attribute, inject
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
|
||||||
|
# Some example class.
|
||||||
|
class ObjectA(object):
|
||||||
|
def __init__(self, db):
|
||||||
|
self.db = db
|
||||||
|
|
||||||
|
|
||||||
|
# Catalog of objects providers.
|
||||||
|
class AppCatalog(Catalog):
|
||||||
|
"""
|
||||||
|
Objects catalog.
|
||||||
|
"""
|
||||||
|
|
||||||
|
database = Singleton(sqlite3.Connection,
|
||||||
|
InitArg('database', ':memory:'),
|
||||||
|
Attribute('row_factory', sqlite3.Row))
|
||||||
|
""" :type: (objects.Provider) -> sqlite3.Connection """
|
||||||
|
|
||||||
|
object_a = NewInstance(ObjectA,
|
||||||
|
InitArg('db', database))
|
||||||
|
""" :type: (objects.Provider) -> ObjectA """
|
||||||
|
|
||||||
|
|
||||||
|
# Class attributes injections.
|
||||||
|
@inject(Attribute('a', AppCatalog.object_a))
|
||||||
|
@inject(Attribute('database', AppCatalog.database))
|
||||||
|
class Consumer(object):
|
||||||
|
"""
|
||||||
|
Some consumer class with database dependency via attribute.
|
||||||
|
"""
|
||||||
|
|
||||||
|
a = None
|
||||||
|
""" :type: (objects.Provider) -> ObjectA """
|
||||||
|
|
||||||
|
database = None
|
||||||
|
""" :type: (objects.Provider) -> sqlite3.Connection """
|
||||||
|
|
||||||
|
def tests(self):
|
||||||
|
a1, a2 = self.a(), self.a()
|
||||||
|
|
||||||
|
assert a1 is not a2
|
||||||
|
assert a1.db is a2.db is self.database()
|
||||||
|
|
||||||
|
|
||||||
|
consumer = Consumer()
|
||||||
|
consumer.tests()
|
||||||
|
|
||||||
|
|
||||||
|
# Class __init__ injections.
|
||||||
|
@inject(InitArg('a1', AppCatalog.object_a))
|
||||||
|
@inject(InitArg('a2', AppCatalog.object_a))
|
||||||
|
@inject(InitArg('database', AppCatalog.database))
|
||||||
|
class ConsumerWithInitArg(object):
|
||||||
|
"""
|
||||||
|
Some consumer class with database dependency via init arg.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, a1, a2, database):
|
||||||
|
"""
|
||||||
|
Initializer.
|
||||||
|
|
||||||
|
:param a1: ObjectA
|
||||||
|
:param a2: ObjectA
|
||||||
|
:param database: sqlite3.Connection
|
||||||
|
"""
|
||||||
|
self.a1 = a1
|
||||||
|
self.a2 = a2
|
||||||
|
self.database = database
|
||||||
|
|
||||||
|
def tests(self):
|
||||||
|
assert self.a1 is not self.a2
|
||||||
|
assert self.a1.db is self.a2.db is self.database
|
||||||
|
|
||||||
|
|
||||||
|
consumer = ConsumerWithInitArg()
|
||||||
|
consumer.tests()
|
||||||
|
```
|
||||||
|
|
|
@ -7,12 +7,12 @@ import sqlite3
|
||||||
|
|
||||||
|
|
||||||
# Some example classes.
|
# Some example classes.
|
||||||
class A(object):
|
class ObjectA(object):
|
||||||
def __init__(self, db):
|
def __init__(self, db):
|
||||||
self.db = db
|
self.db = db
|
||||||
|
|
||||||
|
|
||||||
class B(object):
|
class ObjectB(object):
|
||||||
def __init__(self, a, db):
|
def __init__(self, a, db):
|
||||||
self.a = a
|
self.a = a
|
||||||
self.db = db
|
self.db = db
|
||||||
|
@ -29,14 +29,14 @@ class AppCatalog(Catalog):
|
||||||
Attribute('row_factory', sqlite3.Row))
|
Attribute('row_factory', sqlite3.Row))
|
||||||
""" :type: (objects.Provider) -> sqlite3.Connection """
|
""" :type: (objects.Provider) -> sqlite3.Connection """
|
||||||
|
|
||||||
object_a = NewInstance(A,
|
object_a = NewInstance(ObjectA,
|
||||||
InitArg('db', database))
|
InitArg('db', database))
|
||||||
""" :type: (objects.Provider) -> A """
|
""" :type: (objects.Provider) -> ObjectA """
|
||||||
|
|
||||||
object_b = NewInstance(B,
|
object_b = NewInstance(ObjectB,
|
||||||
InitArg('a', object_a),
|
InitArg('a', object_a),
|
||||||
InitArg('db', database))
|
InitArg('db', database))
|
||||||
""" :type: (objects.Provider) -> B """
|
""" :type: (objects.Provider) -> ObjectB """
|
||||||
|
|
||||||
|
|
||||||
# Catalog injection into consumer class.
|
# Catalog injection into consumer class.
|
||||||
|
|
83
examples/injections.py
Normal file
83
examples/injections.py
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
"""
|
||||||
|
Concept example of objects injections.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from objects import Catalog, Singleton, NewInstance, InitArg, Attribute, inject
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
|
||||||
|
# Some example class.
|
||||||
|
class ObjectA(object):
|
||||||
|
def __init__(self, db):
|
||||||
|
self.db = db
|
||||||
|
|
||||||
|
|
||||||
|
# Catalog of objects providers.
|
||||||
|
class AppCatalog(Catalog):
|
||||||
|
"""
|
||||||
|
Objects catalog.
|
||||||
|
"""
|
||||||
|
|
||||||
|
database = Singleton(sqlite3.Connection,
|
||||||
|
InitArg('database', ':memory:'),
|
||||||
|
Attribute('row_factory', sqlite3.Row))
|
||||||
|
""" :type: (objects.Provider) -> sqlite3.Connection """
|
||||||
|
|
||||||
|
object_a = NewInstance(ObjectA,
|
||||||
|
InitArg('db', database))
|
||||||
|
""" :type: (objects.Provider) -> ObjectA """
|
||||||
|
|
||||||
|
|
||||||
|
# Class attributes injections.
|
||||||
|
@inject(Attribute('a', AppCatalog.object_a))
|
||||||
|
@inject(Attribute('database', AppCatalog.database))
|
||||||
|
class Consumer(object):
|
||||||
|
"""
|
||||||
|
Some consumer class with database dependency via attribute.
|
||||||
|
"""
|
||||||
|
|
||||||
|
a = None
|
||||||
|
""" :type: (objects.Provider) -> ObjectA """
|
||||||
|
|
||||||
|
database = None
|
||||||
|
""" :type: (objects.Provider) -> sqlite3.Connection """
|
||||||
|
|
||||||
|
def tests(self):
|
||||||
|
a1, a2 = self.a(), self.a()
|
||||||
|
|
||||||
|
assert a1 is not a2
|
||||||
|
assert a1.db is a2.db is self.database()
|
||||||
|
|
||||||
|
|
||||||
|
consumer = Consumer()
|
||||||
|
consumer.tests()
|
||||||
|
|
||||||
|
|
||||||
|
# Class __init__ injections.
|
||||||
|
@inject(InitArg('a1', AppCatalog.object_a))
|
||||||
|
@inject(InitArg('a2', AppCatalog.object_a))
|
||||||
|
@inject(InitArg('database', AppCatalog.database))
|
||||||
|
class ConsumerWithInitArg(object):
|
||||||
|
"""
|
||||||
|
Some consumer class with database dependency via init arg.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, a1, a2, database):
|
||||||
|
"""
|
||||||
|
Initializer.
|
||||||
|
|
||||||
|
:param a1: ObjectA
|
||||||
|
:param a2: ObjectA
|
||||||
|
:param database: sqlite3.Connection
|
||||||
|
"""
|
||||||
|
self.a1 = a1
|
||||||
|
self.a2 = a2
|
||||||
|
self.database = database
|
||||||
|
|
||||||
|
def tests(self):
|
||||||
|
assert self.a1 is not self.a2
|
||||||
|
assert self.a1.db is self.a2.db is self.database
|
||||||
|
|
||||||
|
|
||||||
|
consumer = ConsumerWithInitArg()
|
||||||
|
consumer.tests()
|
33
manage.py
Normal file
33
manage.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
"""
|
||||||
|
CLI Commands.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from setup import version
|
||||||
|
from manager import Manager
|
||||||
|
|
||||||
|
|
||||||
|
manager = Manager()
|
||||||
|
|
||||||
|
|
||||||
|
@manager.command
|
||||||
|
def publish(with_tag=True):
|
||||||
|
"""
|
||||||
|
Publishes current version to PyPi.
|
||||||
|
"""
|
||||||
|
os.system('python setup.py sdist upload')
|
||||||
|
if with_tag:
|
||||||
|
tag()
|
||||||
|
|
||||||
|
|
||||||
|
@manager.command
|
||||||
|
def tag():
|
||||||
|
"""
|
||||||
|
Makes tag from current version.
|
||||||
|
"""
|
||||||
|
os.system('git tag -a {0} -m \'version {0}\''.format(version))
|
||||||
|
os.system('git push --tags')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
manager.main()
|
|
@ -3,9 +3,9 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .catalog import Catalog
|
from .catalog import Catalog
|
||||||
from .std_providers import (Provider, NewInstance, Singleton, Class, Object,
|
from .providers import (Provider, NewInstance, Singleton, Class, Object,
|
||||||
Function, Value)
|
Function, Value)
|
||||||
from .injections import InitArg, Attribute, Method
|
from .injections import InitArg, Attribute, Method, inject
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['Catalog',
|
__all__ = ['Catalog',
|
||||||
|
@ -15,4 +15,4 @@ __all__ = ['Catalog',
|
||||||
'Object', 'Function', 'Value',
|
'Object', 'Function', 'Value',
|
||||||
|
|
||||||
# Injections
|
# Injections
|
||||||
'InitArg', 'Attribute', 'Method']
|
'InitArg', 'Attribute', 'Method', 'inject']
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Catalog module.
|
Catalog module.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .std_providers import Provider
|
from .providers import Provider
|
||||||
|
|
||||||
|
|
||||||
class Catalog(object):
|
class Catalog(object):
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
Injections module.
|
Injections module.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from inspect import isclass
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
|
||||||
class Injection(object):
|
class Injection(object):
|
||||||
"""
|
"""
|
||||||
|
@ -15,14 +18,14 @@ class Injection(object):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.injectable = injectable
|
self.injectable = injectable
|
||||||
|
|
||||||
@classmethod
|
@property
|
||||||
def fetch(cls, injections):
|
def value(self):
|
||||||
"""
|
"""
|
||||||
Fetches injections of self type from list.
|
Returns injectable value.
|
||||||
"""
|
"""
|
||||||
return tuple([injection
|
if hasattr(self.injectable, '__is_objects_provider__'):
|
||||||
for injection in injections
|
return self.injectable()
|
||||||
if isinstance(injection, cls)])
|
return self.injectable
|
||||||
|
|
||||||
|
|
||||||
class InitArg(Injection):
|
class InitArg(Injection):
|
||||||
|
@ -41,3 +44,27 @@ class Method(Injection):
|
||||||
"""
|
"""
|
||||||
Method injection.
|
Method injection.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def inject(injection):
|
||||||
|
"""
|
||||||
|
Injection decorator.
|
||||||
|
"""
|
||||||
|
def decorator(callback_or_cls):
|
||||||
|
if isclass(callback_or_cls):
|
||||||
|
cls = callback_or_cls
|
||||||
|
if isinstance(injection, Attribute):
|
||||||
|
setattr(cls, injection.name, injection.injectable)
|
||||||
|
elif isinstance(injection, InitArg):
|
||||||
|
cls.__init__ = decorator(cls.__init__)
|
||||||
|
return cls
|
||||||
|
else:
|
||||||
|
callback = callback_or_cls
|
||||||
|
|
||||||
|
@wraps(callback)
|
||||||
|
def wrapped(*args, **kwargs):
|
||||||
|
if injection.name not in kwargs:
|
||||||
|
kwargs[injection.name] = injection.value
|
||||||
|
return callback(*args, **kwargs)
|
||||||
|
return wrapped
|
||||||
|
return decorator
|
||||||
|
|
|
@ -10,25 +10,29 @@ class Provider(object):
|
||||||
Base provider class.
|
Base provider class.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
__is_objects_provider__ = True
|
||||||
|
|
||||||
def __call__(self, *args, **kwargs):
|
def __call__(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Returns provided instance.
|
Returns provided instance.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def prepare_injections(injections):
|
def prepare_injections(injections):
|
||||||
"""
|
"""
|
||||||
Prepares injections list to injection.
|
Prepares injections list to injection.
|
||||||
"""
|
"""
|
||||||
prepared_injections = dict()
|
return [(injection.name, injection.value) for injection in injections]
|
||||||
for injection in injections:
|
|
||||||
if isinstance(injection.injectable, Provider):
|
|
||||||
value = injection.injectable.__call__()
|
def fetch_injections(injections, injection_type):
|
||||||
else:
|
"""
|
||||||
value = injection.injectable
|
Fetches injections of injection type from list.
|
||||||
prepared_injections[injection.name] = value
|
"""
|
||||||
return prepared_injections
|
return tuple([injection
|
||||||
|
for injection in injections
|
||||||
|
if isinstance(injection, injection_type)])
|
||||||
|
|
||||||
|
|
||||||
class NewInstance(Provider):
|
class NewInstance(Provider):
|
||||||
|
@ -41,26 +45,26 @@ class NewInstance(Provider):
|
||||||
Initializer.
|
Initializer.
|
||||||
"""
|
"""
|
||||||
self.provides = provides
|
self.provides = provides
|
||||||
self.init_injections = InitArg.fetch(injections)
|
self.init_injections = fetch_injections(injections, InitArg)
|
||||||
self.attribute_injections = Attribute.fetch(injections)
|
self.attribute_injections = fetch_injections(injections, Attribute)
|
||||||
self.method_injections = Method.fetch(injections)
|
self.method_injections = fetch_injections(injections, Method)
|
||||||
|
|
||||||
def __call__(self, *args, **kwargs):
|
def __call__(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Returns provided instance.
|
Returns provided instance.
|
||||||
"""
|
"""
|
||||||
init_injections = Provider.prepare_injections(self.init_injections)
|
init_injections = prepare_injections(self.init_injections)
|
||||||
|
init_injections = dict(init_injections)
|
||||||
init_injections.update(kwargs)
|
init_injections.update(kwargs)
|
||||||
|
|
||||||
provided = self.provides(*args, **init_injections)
|
provided = self.provides(*args, **init_injections)
|
||||||
|
|
||||||
attribute_injections = Provider.prepare_injections(
|
attribute_injections = prepare_injections(self.attribute_injections)
|
||||||
self.attribute_injections)
|
for name, injectable in attribute_injections:
|
||||||
for name, injectable in attribute_injections.iteritems():
|
|
||||||
setattr(provided, name, injectable)
|
setattr(provided, name, injectable)
|
||||||
|
|
||||||
method_injections = Provider.prepare_injections(self.method_injections)
|
method_injections = prepare_injections(self.method_injections)
|
||||||
for name, injectable in method_injections.iteritems():
|
for name, injectable in method_injections:
|
||||||
getattr(provided, name)(injectable)
|
getattr(provided, name)(injectable)
|
||||||
|
|
||||||
return provided
|
return provided
|
99
setup.py
99
setup.py
|
@ -2,8 +2,6 @@
|
||||||
`Objects` setup script.
|
`Objects` setup script.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,56 +24,49 @@ with open('requirements.txt') as version:
|
||||||
with open('VERSION') as version:
|
with open('VERSION') as version:
|
||||||
version = version.read().strip()
|
version = version.read().strip()
|
||||||
|
|
||||||
# Helper commands.
|
|
||||||
if sys.argv[-1] == 'publish':
|
|
||||||
os.system('python setup.py sdist upload')
|
|
||||||
sys.exit()
|
|
||||||
if sys.argv[-1] == 'tag':
|
|
||||||
os.system('git tag -a {0} -m \'version {0}\''.format(version))
|
|
||||||
os.system('git push --tags')
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
setup(
|
if __name__ == '__main__':
|
||||||
name='Objects',
|
setup(
|
||||||
version=version,
|
name='Objects',
|
||||||
description='Python catalogs of objects providers',
|
version=version,
|
||||||
long_description=description,
|
description='Python catalogs of objects providers',
|
||||||
author='Roman Mogilatov',
|
long_description=description,
|
||||||
author_email='rmogilatov@gmail.com',
|
author='Roman Mogilatov',
|
||||||
maintainer='Roman Mogilatov',
|
author_email='rmogilatov@gmail.com',
|
||||||
maintainer_email='rmogilatov@gmail.com',
|
maintainer='Roman Mogilatov',
|
||||||
url='https://github.com/rmk135/objects',
|
maintainer_email='rmogilatov@gmail.com',
|
||||||
license='BSD New',
|
url='https://github.com/rmk135/objects',
|
||||||
packages=['objects'],
|
license='BSD New',
|
||||||
zip_safe=True,
|
packages=['objects'],
|
||||||
install_requires=requirements,
|
zip_safe=True,
|
||||||
# keywords=['Dependency injection',
|
install_requires=requirements,
|
||||||
# 'Dependency injection container',
|
# keywords=['Dependency injection',
|
||||||
# 'DI',
|
# 'Dependency injection container',
|
||||||
# 'DIC',
|
# 'DI',
|
||||||
# 'Dependency injector',
|
# 'DIC',
|
||||||
# 'Inversion of Control',
|
# 'Dependency injector',
|
||||||
# 'Inversion of Control container',
|
# 'Inversion of Control',
|
||||||
# 'IoC',
|
# 'Inversion of Control container',
|
||||||
# 'IoC container'],
|
# 'IoC',
|
||||||
classifiers=[
|
# 'IoC container'],
|
||||||
'Development Status :: 1 - Planning',
|
classifiers=[
|
||||||
# 'Development Status :: 4 - Beta',
|
'Development Status :: 1 - Planning',
|
||||||
'Intended Audience :: Developers',
|
# 'Development Status :: 4 - Beta',
|
||||||
'License :: OSI Approved :: BSD License',
|
'Intended Audience :: Developers',
|
||||||
'Operating System :: OS Independent',
|
'License :: OSI Approved :: BSD License',
|
||||||
'Programming Language :: Python',
|
'Operating System :: OS Independent',
|
||||||
'Programming Language :: Python :: 2',
|
'Programming Language :: Python',
|
||||||
'Programming Language :: Python :: 2.6',
|
'Programming Language :: Python :: 2',
|
||||||
'Programming Language :: Python :: 2.7',
|
'Programming Language :: Python :: 2.6',
|
||||||
# 'Programming Language :: Python :: 3',
|
'Programming Language :: Python :: 2.7',
|
||||||
# 'Programming Language :: Python :: 3.2',
|
# 'Programming Language :: Python :: 3',
|
||||||
# 'Programming Language :: Python :: 3.3',
|
# 'Programming Language :: Python :: 3.2',
|
||||||
# 'Programming Language :: Python :: 3.4',
|
# 'Programming Language :: Python :: 3.3',
|
||||||
'Programming Language :: Python :: Implementation :: CPython',
|
# 'Programming Language :: Python :: 3.4',
|
||||||
# 'Programming Language :: Python :: Implementation :: PyPy',
|
'Programming Language :: Python :: Implementation :: CPython',
|
||||||
'Topic :: Software Development',
|
# 'Programming Language :: Python :: Implementation :: PyPy',
|
||||||
'Topic :: Software Development :: Libraries',
|
'Topic :: Software Development',
|
||||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
'Topic :: Software Development :: Libraries',
|
||||||
]
|
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||||
)
|
]
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user