The core of a service layer that integrates with the Pyramid Web Framework.

Overview

pyramid_services

Travis-CI Build Status

The core of a service layer that integrates with the Pyramid Web Framework.

pyramid_services defines a pattern and helper methods for accessing a pluggable service layer from within your Pyramid apps.

Installation

Install from PyPI using pip or easy_install inside a virtual environment.

$ $VENV/bin/pip install pyramid_services

Or install directly from source.

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

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.

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:

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.

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

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

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

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

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

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

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

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

# 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)
Comments
  • Support services outside of a request context

    Support services outside of a request context

    Currently this assumes that the only use of looking up services will be inside of a request context. This is certainly the most common case but I have a case where It would be beneficial to be able to look up the registered service when I don't have a request (such as in an async worker or in a batch script).

    Looking at the implementation, it appears that even for already created services (e.g. register_service) a dummy factory is created which does nothing but return the already created service. So I think for this to work, when you're not operating inside of a request, then request would be set to None.

    I've copy/pasted the find_service function into my own app to see if I can make it work, and this appears to work:

    def find_service(registry, iface=None, context=None, name=''):
        if iface is None:
            iface = Interface
        if name is None:
            name = ''
    
        context_iface = providedBy(context)
        svc_types = (IServiceClassifier, context_iface)
        svc_factory = registry.adapters.lookup(svc_types, iface, name=name)
        if svc_factory is None:
            raise ValueError('could not find registered service')
        # We are not operating in the confines of a request, so we'll pass a
        # None for the request.
        return svc_factory(context, None)
    

    I'm not sure what the best API would be for using this outside of a request context, I originally thought that a config directive like config.find_service() would work, but the #pyramid IRC channel didn't seem to think that was the right way to go. There's no cache that'll return the same object for multiple find_service calls with this, since there is no request/response cycle to tie the lifetime of that cache to.

    Thoughts? Is this something that would be useful to have inside of pyramid_services or should I just keep a copy of my find_services function inside of my app?

    opened by dstufft 3
  • Drop Python 2.6 support

    Drop Python 2.6 support

    Yesterday marks 3 years since the last release of Python 2.6! 🎉

    To celebrate, I'm attempting to drop support for it from 156 prominent Python packages (one for every week it's past end-of-life)--including this one!

    I've tried my best to remove as much 2.6-specific cruft as I can, but at the very least, this PR will remove the 'Programming Language :: Python :: 2.6' trove classifier from this projects setup.py.

    opened by di 2
  • Singleton per request object

    Singleton per request object

    One issue I ran into with your dbsession service example (that uses a service factory) is the following:

    When looking for the service different sessions are returned depending on the context. This is by design. Citing the documentation:

    The factory will be used at most once per request/context/name combination.

    Having different DB session in one request is quite ugly and I can think of no use case for it. How did I end up with different contexts? By using the request object outside a view function invoked directly with pyramid.threadlocal.get_current_request . In that case the context is None.

    This example by Jon Rosebaugh - What the Zope Transaction Manager Means To Me (and you) would work but I wanted to use pyramid_services (for consistency, because I'm already using it in other parts of the application).

    I'm aware that this might not be the place for this since it classifies more as a question than an issue, but you could at least fix the example to be more clear that

    request.find_service(name='db')
    

    will return different sessions depending on the request context.

    My workaround is to always pass context=None:

    request.find_service(name='db', context=None)
    

    Is there a simpler solution (e.g. a singleton per request)? register_service() would register a singleton object for the whole application, but I do want a different DB session for every request.

    opened by omarkohl 2
  • How to use pyramid_services in tests

    How to use pyramid_services in tests

    For the test setup, I usually config.include() the main application includeme() and then config.include test stuff, to override some of the services/utilities. It's less intrusive, test code is clearly isolated.

    I really like pyramid_services. It's neat and clean. However it's not possible to "override" a service (same context, name).

    How do you test the application if some service must be swapped with "test" service ?

    opened by pior 2
  • find_service_factory

    find_service_factory

    This adds config.find_service_factory and request.find_service_factory.

    The extra method on request enables a user to bypass the caching mechanism.

    The config method enables a user to find the factories and create services themselves. It does not invoke the factory directly because it is not guaranteed to have a request object. This way the caller makes that decision on what to pass in to the factory.

    fixes #1

    opened by mmerickel 2
  • Update python support to include 3.7, 3.8 and 3.9

    Update python support to include 3.7, 3.8 and 3.9

    This updates the tox envs and classifiers to include the most recent versions of Python. I believe 3.7 was already in place, but not in the classifiers.

    This is part of a larger effort to allow our pyramid apps to be upgraded from version 3.6, which is nearing the end of it's support period.

    The various dependencies here are:

    • pyramid - 3.9 declared
    • zope.interface - 3.9 available as wheels (3.8 declared)
    • wired - 3.8 declared

    Locally at least the tests pass in each environment. I was unable to get a version of 3.4 that would install, so I couldn't confirm those tests.

    opened by jon-betts 1
  • Default context none

    Default context none

    When using request.find_service during traversal (in a route factory, for example) this would fail unless you specifically passed a context=. This is still possible but will now default to context=None instead of raising an exception.

    ping @bertjwregeer

    opened by mmerickel 1
  • Feature: find service factory

    Feature: find service factory

    I could really use this for testing purposes in pyramid_authsanity. Added some docs, and made sure that test coverage is once again up to 100%. Docs may not be perfect, so I think that may need some help.

    opened by bertjwregeer 0
A familiar HTTP Service Framework for Python.

Responder: a familiar HTTP Service Framework for Python Powered by Starlette. That async declaration is optional. View documentation. This gets you a

Taoufik 3.6k Dec 27, 2022
Asita is a web application framework for python based on express-js framework.

Asita is a web application framework for python. It is designed to be easy to use and be more easy for javascript users to use python frameworks because it is based on express-js framework.

Mattéo 4 Nov 16, 2021
Async Python 3.6+ web server/framework | Build fast. Run fast.

Sanic | Build fast. Run fast. Build Docs Package Support Stats Sanic is a Python 3.6+ web server and web framework that's written to go fast. It allow

Sanic Community Organization 16.7k Jan 8, 2023
Fast, asynchronous and elegant Python web framework.

Warning: This project is being completely re-written. If you're curious about the progress, reach me on Slack. Vibora is a fast, asynchronous and eleg

vibora.io 5.7k Jan 8, 2023
cirrina is an opinionated asynchronous web framework based on aiohttp

cirrina cirrina is an opinionated asynchronous web framework based on aiohttp. Features: HTTP Server Websocket Server JSON RPC Server Shared sessions

André Roth 32 Mar 5, 2022
The Web framework for perfectionists with deadlines.

Django Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design. Thanks for checking it out. All docu

Django 67.9k Dec 29, 2022
The Python micro framework for building web applications.

Flask Flask is a lightweight WSGI web application framework. It is designed to make getting started quick and easy, with the ability to scale up to co

The Pallets Projects 61.5k Jan 6, 2023
Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed.

Tornado Web Server Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed. By using non-blocking ne

null 20.9k Jan 1, 2023
Async Python 3.6+ web server/framework | Build fast. Run fast.

Sanic | Build fast. Run fast. Build Docs Package Support Stats Sanic is a Python 3.6+ web server and web framework that's written to go fast. It allow

Sanic Community Organization 16.7k Dec 28, 2022
bottle.py is a fast and simple micro-framework for python web-applications.

Bottle: Python Web Framework Bottle is a fast, simple and lightweight WSGI micro web-framework for Python. It is distributed as a single file module a

Bottle Micro Web Framework 7.8k Dec 31, 2022
The Modern And Developer Centric Python Web Framework. Be sure to read the documentation and join the Slack channel questions: http://slack.masoniteproject.com

NOTE: Masonite 2.3 is no longer compatible with the masonite-cli tool. Please uninstall that by running pip uninstall masonite-cli. If you do not unin

Masonite 1.9k Jan 4, 2023
Free and open source full-stack enterprise framework for agile development of secure database-driven web-based applications, written and programmable in Python.

Readme web2py is a free open source full-stack framework for rapid development of fast, scalable, secure and portable database-driven web-based applic

null 2k Dec 31, 2022
The web framework for inventors

Emmett is a full-stack Python web framework designed with simplicity in mind. The aim of Emmett is to be clearly understandable, easy to be learned an

Emmett 796 Dec 26, 2022
A micro web-framework using asyncio coroutines and chained middleware.

Growler master ' dev Growler is a web framework built atop asyncio, the asynchronous library described in PEP 3156 and added to the standard library i

null 687 Nov 27, 2022
An easy-to-use high-performance asynchronous web framework.

An easy-to-use high-performance asynchronous web framework.

Aber 264 Dec 31, 2022
Sierra is a lightweight Python framework for building and integrating web applications

A lightweight Python framework for building and Integrating Web Applications. Sierra is a Python3 library for building and integrating web applications with HTML and CSS using simple enough syntax. You can develop your web applications with Python, taking advantage of its functionalities and integrating them to the fullest.

null 83 Sep 23, 2022
FPS, fast pluggable server, is a framework designed to compose and run a web-server based on plugins.

FPS, fast pluggable server, is a framework designed to compose and run a web-server based on plugins. It is based on top of fastAPI, uvicorn, typer, and pluggy.

Adrien Delsalle 1 Nov 16, 2021
Flask Sugar is a web framework for building APIs with Flask, Pydantic and Python 3.6+ type hints.

Flask Sugar is a web framework for building APIs with Flask, Pydantic and Python 3.6+ type hints. check parameters and generate API documents automatically. Flask Sugar是一个基于flask,pyddantic,类型注解的API框架, 可以检查参数并自动生成API文档

null 162 Dec 26, 2022
Fast⚡, simple and light💡weight ASGI micro🔬 web🌏-framework for Python🐍.

NanoASGI Asynchronous Python Web Framework NanoASGI is a fast ⚡ , simple and light ?? weight ASGI micro ?? web ?? -framework for Python ?? . It is dis

Kavindu Santhusa 8 Jun 16, 2022