Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

pyramid-services

Package Overview
Dependencies
Maintainers
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

pyramid-services

A service layer abstraction for the Pyramid Web Framework.

  • 2.2
  • PyPI
  • Socket score

Maintainers
1

================ pyramid_services

.. 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.

Installation

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 .

Setup

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.

Usage

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)

Registering per-request services

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)

Examples

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

myapp/interfaces.py

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

myapp/services.py

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

myapp/main.py

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

myapp/views.py

@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

myapp/services.py

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

myapp/model.py

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

myapp/main.py

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

myapp/views.py

@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.

Testing Examples

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

myapp/tests/test_integration.py

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)

2.2 (2019-04-22)

  • Depend on wired >= 0.2 to use the new wired.ServiceContainer.register_singleton api.

2.1 (2018-08-03)

  • Add a NewServiceContainer event that is emitted when the service container is created before any calls to request.find_service.

2.0 (2018-08-03)

  • 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.

1.1 (2017-05-31)

Features

  • If the 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.

1.0 (2017-05-11)

Features

Backward Incompatibilities

  • Drop Python 2.6 and Python 3.3 support.

0.4 (2016-02-03)

Backward Incompatibilities

  • Drop 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

0.3 (2015-12-13)

0.2 (2015-03-13)

0.1.1 (2015-02-17)

  • Support for request.find_service, config.register_service, and config.register_service_factory.
  • Initial commits.

Keywords

FAQs


Did you know?

Socket

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.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc