Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
.. image:: https://travis-ci.org/mmerickel/pyramid_services.svg?branch=master :alt: Travis-CI Build Status :target: https://travis-ci.org/mmerickel/pyramid_services
The core of a service layer that integrates with the
Pyramid Web Framework <https://docs.pylonsproject.org/projects/pyramid>
__.
pyramid_services
defines a pattern and helper methods for accessing a
pluggable service layer from within your Pyramid apps.
Install from PyPI <https://pypi.python.org/pypi/pyramid_services>
__ using
pip
or easy_install
inside a virtual environment.
.. code-block:: bash
$ $VENV/bin/pip install pyramid_services
Or install directly from source.
.. code-block:: bash
$ git clone https://github.com/mmerickel/pyramid_services.git $ cd pyramid_services $ $VENV/bin/pip install -e .
Activate pyramid_services
by including it into your pyramid application.
.. code-block:: python
config.include('pyramid_services')
This will add some new directives to your Configurator
.
config.register_service(obj, iface=Interface, context=Interface, name='')
This method will register a service object for the supplied
iface
, context
, and name
. This effectively registers a
singleton for your application as the obj
will always be returned when
looking for a service.
config.register_service_factory(factory, iface=Interface, context=Interface, name='')
This method will register a factory for the supplied iface
,
context
, and name
. The factory should be a callable accepting a
context
and a request
and should return a service object. The
factory will be used at most once per request
/context
/name
combination.
config.set_service_registry(registry)
This method will let you set a custom wired.ServiceRegistry
instance
which is the backing registry for all services.
After registering services with the Configurator
, they are now
accessible from the request
object during a request lifecycle via the
request.find_service(iface=Interface, context=_marker, name='')
method. Unless a custom context
is passed to find_service
, the
lookup will default to using request.context
. The context
will default
to None
if a service is searched for during or before traversal in Pyramid
when there may not be a request.context
.
.. code-block:: python
svc = request.find_service(ILoginService)
Some services (like your database connection) may need a transaction manager
and the best way to do that is by using pyramid_tm
and hooking the
request.tm
transaction manager into your service container. The
request object itself is already added to the container for the
pyramid.interfaces.IRequest
interface and can be used in factories that
require the request.
This can be done before any services are instantiated by subscribing to the
pyramid_services.NewServiceContainer
event:
.. code-block:: python
from pyramid_services import NewServiceContainer
def on_new_container(event): container = event.container request = event.request container.set(request.tm, name='tm')
config.add_subscriber(on_new_container, NewServiceContainer)
Let's create a login service by progressively building up from scratch what we want to use in our app.
Basically all of the steps in configuring an interface are optional, but they are shown here as best practices.
.. code-block:: python
from zope.interface import Interface
class ILoginService(Interface): def create_token_for_login(name): pass
With our interface we can now define a conforming instance.
.. code-block:: python
class DummyLoginService(object): def create_token_for_login(self, name): return 'u:{0}'.format(name)
Let's hook it up to our application.
.. code-block:: python
from pyramid.config import Configurator
from myapp.services import DummyLoginService
def main(global_config, **settings): config = Configurator() config.include('pyramid_services')
config.register_service(DummyLoginService(), ILoginService)
config.add_route('home', '/')
config.scan('.views')
return config.make_wsgi_app()
Finally, let's create our view that utilizes the service.
.. code-block:: python
@view_config(route_name='home', renderer='json') def home_view(request): name = request.params.get('name', 'bob')
login_svc = request.find_service(ILoginService)
token = login_svc.create_token_for_login(name)
return {'access_token': token}
If you start up this application, you will find that you can access the home url and get custom tokens!
This is cool, but what's even better is swapping in a new service without
changing our view at all. Let's define a new PersistentLoginService
that gets tokens from a database. We're going to need to setup some
database handling, but again nothing changes in the view.
.. code-block:: python
from uuid import uuid4
from myapp.model import AccessToken
class PersistentLoginService(object): def init(self, dbsession): self.dbsession = dbsession
def create_token_for_login(self, name):
token = AccessToken(key=uuid4(), user=name)
self.dbsession.add(token)
return token.key
Below is some boilerplate for configuring a model using the excellent
SQLAlchemy ORM <http://docs.sqlalchemy.org>
__.
.. code-block:: python
from sqlalchemy import engine_from_config from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker from sqlalchemy.schema import Column from sqlalchemy.types import Text
Base = declarative_base()
def init_model(settings): engine = engine_from_config(settings) dbmaker = sessionmaker() dbmaker.configure(bind=engine) return dbmaker
class AccessToken(Base): tablename = 'access_token'
key = Column(Text, primary_key=True)
user = Column(Text, nullable=False)
Now we will update the application to use the new PersistentLoginService
.
However, we may have other services and it'd be silly to create a new
database connection for each service in a request. So we'll also add a
service that encapsulates the database connection. Using this technique
we can wire services together in the service layer.
.. code-block:: python
from pyramid.config import Configurator import transaction import zope.sqlalchemy
from myapp.model import init_model from myapp.services import PersistentLoginService
def main(global_config, **settings): config = Configurator() config.include('pyramid_services') config.include('pyramid_tm')
dbmaker = init_model(settings)
def dbsession_factory(context, request):
dbsession = dbmaker()
# register the session with pyramid_tm for managing transactions
zope.sqlalchemy.register(dbsession, transaction_manager=request.tm)
return dbsession
config.register_service_factory(dbsession_factory, name='db')
def login_factory(context, request):
dbsession = request.find_service(name='db')
svc = PersistentLoginService(dbsession)
return svc
config.register_service_factory(login_factory, ILoginService)
config.add_route('home', '/')
config.scan('.views')
return config.make_wsgi_app()
And finally the home view will remain unchanged.
.. code-block:: python
@view_config(route_name='home', renderer='json') def home_view(request): name = request.params.get('name', 'bob')
login_svc = request.find_service(ILoginService)
token = login_svc.create_token_for_login(name)
return {'access_token': token}
Hopefully this pattern is clear. It has several advantages over most basic Pyramid tutorials.
The model is completely abstracted from the views, making both easy to test on their own.
The service layer can be developed independently of the views, allowing for dummy implementations for easy creation of templates and frontend logic. Later, the real service layer can be swapped in as it's developed, building out the backend functionality.
Most services may be implemented in such a way that they do not depend on Pyramid or a particular request object.
Different services may be returned based on a context, such as the result of traversal or some other application-defined discriminator.
If you are writing an application that uses pyramid_services
you may want
to do some integration testing that verifies that your application has
successfully called register_service
or register_service_factory
. Using
Pyramid
's testing
module to create a Configurator
and after calling
config.include('pyramid_services')
you may use find_service_factory
to
get information about a registered service.
Take as an example this test that verifies that dbsession_factory
has been
correctly registered. This assumes you have a myapp.services
package that
contains an includeme()
function.
.. code-block:: python
from myapp.services import dbsession_factory, login_factory, ILoginService
class TestIntegration_services(unittest.TestCase): def setUp(self): self.config = pyramid.testing.setUp() self.config.include('pyramid_services') self.config.include('myapp.services')
def tearDown(self):
pyramid.testing.tearDown()
def test_db_maker(self):
result = self.config.find_service_factory(name='db')
self.assertEqual(result, dbsession_factory)
def test_login_factory(self):
result = self.config.find_service_factory(ILoginService)
self.assertEqual(result, login_factory)
wired >= 0.2
to use the new
wired.ServiceContainer.register_singleton
api.NewServiceContainer
event that is emitted when the service
container is created before any calls to request.find_service
.Drop support for Python 2.7.
Replace service lookup with https://wired.readthedocs.io under the hood.
Fixes service lookup with custom contexts such that the context is passed through to nested service lookups.
iface
is a class then it must be the exact class that was
registered originally. Subclasses are not identified as implementing
the same interface at this time due to internal limitations.iface
argument to be an arbitrary Python object / class.
See https://github.com/mmerickel/pyramid_services/pull/10Drop Python 3.2 support.
Use the original service context interface as the cache key instead of the current context. This means the service will be properly created only once for any context satisfying the original interface.
Previously, if you requested the same service from 2 different contexts in the same request you would receive 2 service objects, instead of a cached version of the original service, assuming the service was registered to satisfy both contexts. See https://github.com/mmerickel/pyramid_services/pull/12
When using request.find_service
during or before traversal the
request.context
is not valid. In these situations the context
parameter will default to None
instead of raising an exception.
See https://github.com/mmerickel/pyramid_services/pull/8
Add config.find_service_factory
and request.find_service_factory
.
See https://github.com/mmerickel/pyramid_services/pull/4
Change find_service(..., context=None)
to use a context of None
.
Previously this would fallback to using request.context
if the
context
was None
. Now find_service
will only fallback to
request.context
when no context
argument is specified.
See https://github.com/mmerickel/pyramid_services/pull/3
Support introspectable
for services so that they show up in the
pyramid_debugtoolbar and elsewhere.
See https://github.com/mmerickel/pyramid_services/pull/2
request.find_service
, config.register_service
, and
config.register_service_factory
.FAQs
A service layer abstraction for the Pyramid Web Framework.
We found that pyramid-services demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.