Package: pyramid-services
The core of a service layer that integrates with thePyramid Web Framework.pyramid_servicesdefines a pattern and helper methods for accessing a
pluggable service layer from within your Pyramid apps.InstallationInstall fromPyPIusingpiporeasy_installinside a virtual environment.$$VENV/bin/pipinstallpyramid_servicesOr install directly from source.$gitclonehttps://github.com/mmerickel/pyramid_services.git$cdpyramid_services$$VENV/bin/pipinstall-e.SetupActivatepyramid_servicesby including it into your pyramid application.config.include('pyramid_services')This will add some new directives to yourConfigurator.config.register_service(obj, iface=Interface, context=Interface,name='')This method will register a service object for the suppliediface,context, andname. This effectively registers a
singleton for your application as theobjwill 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 suppliediface,context, andname. The factory should be a callable accepting acontextand arequestand should return a service object. The
factory will be used at most once perrequest/context/namecombination.config.set_service_registry(registry)This method will let you set a customwired.ServiceRegistryinstance
which is the backing registry for all services.UsageAfter registering services with theConfigurator, they are now
accessible from therequestobject during a request lifecycle via therequest.find_service(iface=Interface, context=_marker,name='')method. Unless a customcontextis passed tofind_service, the
lookup will default to usingrequest.context. Thecontextwill default
toNoneif a service is searched for during or before traversal in Pyramid
when there may not be arequest.context.svc=request.find_service(ILoginService)Registering per-request servicesSome services (like your database connection) may need a transaction manager
and the best way to do that is by usingpyramid_tmand hooking therequest.tmtransaction manager into your service container. The
request object itself is already added to the container for thepyramid.interfaces.IRequestinterface and can be used in factories that
require the request.This can be done before any services are instantiated by subscribing to thepyramid_services.NewServiceContainerevent:frompyramid_servicesimportNewServiceContainerdefon_new_container(event):container=event.containerrequest=event.requestcontainer.set(request.tm,name='tm')config.add_subscriber(on_new_container,NewServiceContainer)ExamplesLet’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.pyfromzope.interfaceimportInterfaceclassILoginService(Interface):defcreate_token_for_login(name):passWith our interface we can now define a conforming instance.# myapp/services.pyclassDummyLoginService(object):defcreate_token_for_login(self,name):return'u:{0}'.format(name)Let’s hook it up to our application.# myapp/main.pyfrompyramid.configimportConfiguratorfrommyapp.servicesimportDummyLoginServicedefmain(global_config,**settings):config=Configurator()config.include('pyramid_services')config.register_service(DummyLoginService(),ILoginService)config.add_route('home','/')config.scan('.views')returnconfig.make_wsgi_app()Finally, let’s create our view that utilizes the service.# myapp/views.py@view_config(route_name='home',renderer='json')defhome_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 newPersistentLoginServicethat gets tokens from a database. We’re going to need to setup some
database handling, but again nothing changes in the view.# myapp/services.pyfromuuidimportuuid4frommyapp.modelimportAccessTokenclassPersistentLoginService(object):def__init__(self,dbsession):self.dbsession=dbsessiondefcreate_token_for_login(self,name):token=AccessToken(key=uuid4(),user=name)self.dbsession.add(token)returntoken.keyBelow is some boilerplate for configuring a model using the excellentSQLAlchemy ORM.# myapp/model.pyfromsqlalchemyimportengine_from_configfromsqlalchemy.ext.declarativeimportdeclarative_basefromsqlalchemy.ormimportsessionmakerfromsqlalchemy.schemaimportColumnfromsqlalchemy.typesimportTextBase=declarative_base()definit_model(settings):engine=engine_from_config(settings)dbmaker=sessionmaker()dbmaker.configure(bind=engine)returndbmakerclassAccessToken(Base):__tablename__='access_token'key=Column(Text,primary_key=True)user=Column(Text,nullable=False)Now we will update the application to use the newPersistentLoginService.
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.pyfrompyramid.configimportConfiguratorimporttransactionimportzope.sqlalchemyfrommyapp.modelimportinit_modelfrommyapp.servicesimportPersistentLoginServicedefmain(global_config,**settings):config=Configurator()config.include('pyramid_services')config.include('pyramid_tm')dbmaker=init_model(settings)defdbsession_factory(context,request):dbsession=dbmaker()# register the session with pyramid_tm for managing transactionszope.sqlalchemy.register(dbsession,transaction_manager=request.tm)returndbsessionconfig.register_service_factory(dbsession_factory,name='db')deflogin_factory(context,request):dbsession=request.find_service(name='db')svc=PersistentLoginService(dbsession)returnsvcconfig.register_service_factory(login_factory,ILoginService)config.add_route('home','/')config.scan('.views')returnconfig.make_wsgi_app()And finally the home view will remain unchanged.# myapp/views.py@view_config(route_name='home',renderer='json')defhome_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 ExamplesIf you are writing an application that usespyramid_servicesyou may want
to do some integration testing that verifies that your application has
successfully calledregister_serviceorregister_service_factory. UsingPyramid’stestingmodule to create aConfiguratorand after callingconfig.include('pyramid_services')you may usefind_service_factoryto
get information about a registered service.Take as an example this test that verifies thatdbsession_factoryhas been
correctly registered. This assumes you have amyapp.servicespackage that
contains anincludeme()function.# myapp/tests/test_integration.pyfrommyapp.servicesimportdbsession_factory,login_factory,ILoginServiceclassTestIntegration_services(unittest.TestCase):defsetUp(self):self.config=pyramid.testing.setUp()self.config.include('pyramid_services')self.config.include('myapp.services')deftearDown(self):pyramid.testing.tearDown()deftest_db_maker(self):result=self.config.find_service_factory(name='db')self.assertEqual(result,dbsession_factory)deftest_login_factory(self):result=self.config.find_service_factory(ILoginService)self.assertEqual(result,login_factory)2.2 (2019-04-22)Depend onwired >= 0.2to use the newwired.ServiceContainer.register_singletonapi.2.1 (2018-08-03)Add aNewServiceContainerevent that is emitted when the service
container is created before any calls torequest.find_service.2.0 (2018-08-03)Drop support for Python 2.7.Replace service lookup withhttps://wired.readthedocs.iounder the hood.Fixes service lookup with custom contexts such that the context is passed
through to nested service lookups.1.1 (2017-05-31)FeaturesIf theifaceis 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)FeaturesAllow theifaceargument to be an arbitrary Python object / class.
Seehttps://github.com/mmerickel/pyramid_services/pull/10Backward IncompatibilitiesDrop Python 2.6 and Python 3.3 support.0.4 (2016-02-03)Backward IncompatibilitiesDrop 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.
Seehttps://github.com/mmerickel/pyramid_services/pull/120.3 (2015-12-13)When usingrequest.find_serviceduring or before traversal therequest.contextis not valid. In these situations thecontextparameter will default toNoneinstead of raising an exception.
Seehttps://github.com/mmerickel/pyramid_services/pull/8Addconfig.find_service_factoryandrequest.find_service_factory.
Seehttps://github.com/mmerickel/pyramid_services/pull/40.2 (2015-03-13)Changefind_service(...,context=None)to use a context ofNone.
Previously this would fallback to usingrequest.contextif thecontextwasNone. Nowfind_servicewill only fallback torequest.contextwhen nocontextargument is specified.
Seehttps://github.com/mmerickel/pyramid_services/pull/3Supportintrospectablefor services so that they show up in the
pyramid_debugtoolbar and elsewhere.
Seehttps://github.com/mmerickel/pyramid_services/pull/20.1.1 (2015-02-17)Support forrequest.find_service,config.register_service, andconfig.register_service_factory.Initial commits.