{"id":13725274,"url":"https://github.com/mmerickel/pyramid_services","last_synced_at":"2025-10-04T22:03:32.017Z","repository":{"id":27315265,"uuid":"30789510","full_name":"mmerickel/pyramid_services","owner":"mmerickel","description":"A service layer for pyramid.","archived":false,"fork":false,"pushed_at":"2024-08-29T22:51:06.000Z","size":79,"stargazers_count":81,"open_issues_count":0,"forks_count":11,"subscribers_count":14,"default_branch":"main","last_synced_at":"2024-10-12T09:51:19.023Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://pypi.python.org/pypi/pyramid_services","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mmerickel.png","metadata":{"files":{"readme":"README.rst","changelog":"CHANGES.rst","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-02-14T06:44:36.000Z","updated_at":"2024-08-29T22:51:02.000Z","dependencies_parsed_at":"2024-10-12T09:51:20.890Z","dependency_job_id":"a3292624-86b7-4682-a886-f89614a6e1e6","html_url":"https://github.com/mmerickel/pyramid_services","commit_stats":{"total_commits":74,"total_committers":8,"mean_commits":9.25,"dds":"0.20270270270270274","last_synced_commit":"b5058703f55bdc2af5f40b4b4e633bdf320876b7"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmerickel%2Fpyramid_services","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmerickel%2Fpyramid_services/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmerickel%2Fpyramid_services/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmerickel%2Fpyramid_services/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mmerickel","download_url":"https://codeload.github.com/mmerickel/pyramid_services/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247640464,"owners_count":20971557,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T01:02:18.024Z","updated_at":"2025-10-04T22:03:26.971Z","avatar_url":"https://github.com/mmerickel.png","language":"Python","funding_links":[],"categories":["Python","Pyramid Utilities","Settings"],"sub_categories":[],"readme":"================\npyramid_services\n================\n\n.. image:: https://img.shields.io/pypi/v/pyramid_services.svg\n    :target: https://pypi.org/pypi/pyramid_services\n\n.. image:: https://github.com/mmerickel/pyramid_services/workflows/Build%20and%20test/badge.svg?branch=main\n    :target: https://github.com/mmerickel/pyramid_services/actions?query=workflow%3A%22Build+and+test%22\n    :alt: main CI Status\n\nThe core of a service layer that integrates with the\n`Pyramid Web Framework \u003chttps://docs.pylonsproject.org/projects/pyramid\u003e`__.\n\n``pyramid_services`` defines a pattern and helper methods for accessing a\npluggable service layer from within your Pyramid apps.\n\nInstallation\n============\n\nInstall from `PyPI \u003chttps://pypi.python.org/pypi/pyramid_services\u003e`__ using\n``pip`` or ``easy_install`` inside a virtual environment.\n\n.. code-block:: bash\n\n  $ $VENV/bin/pip install pyramid_services\n\nOr install directly from source.\n\n.. code-block:: bash\n\n  $ git clone https://github.com/mmerickel/pyramid_services.git\n  $ cd pyramid_services\n  $ $VENV/bin/pip install -e .\n\nSetup\n=====\n\nActivate ``pyramid_services`` by including it into your pyramid application.\n\n.. code-block:: python\n\n  config.include('pyramid_services')\n\nThis will add some new directives to your ``Configurator``.\n\n- ``config.register_service(obj, iface=Interface, context=Interface, name='')``\n\n  This method will register a service object for the supplied\n  ``iface``, ``context``, and ``name``. This effectively registers a\n  singleton for your application as the ``obj`` will always be returned when\n  looking for a service.\n\n- ``config.register_service_factory(factory, iface=Interface, context=Interface, name='')``\n\n  This method will register a factory for the supplied ``iface``,\n  ``context``, and ``name``. The factory should be a callable accepting a\n  ``context`` and a ``request`` and should return a service object. The\n  factory will be used at most once per ``request``/``context``/``name``\n  combination.\n\n- ``config.set_service_registry(registry)``\n\n  This method will let you set a custom ``wired.ServiceRegistry`` instance\n  which is the backing registry for all services.\n\nUsage\n=====\n\nAfter registering services with the ``Configurator``, they are now\naccessible from the ``request`` object during a request lifecycle via the\n``request.find_service(iface=Interface, context=_marker, name='')``\nmethod. Unless a custom ``context`` is passed to ``find_service``, the\nlookup will default to using ``request.context``. The ``context`` will default\nto ``None`` if a service is searched for during or before traversal in Pyramid\nwhen there may not be a ``request.context``.\n\n.. code-block:: python\n\n  svc = request.find_service(ILoginService)\n\nRegistering per-request services\n--------------------------------\n\nSome services (like your database connection) may need a transaction manager\nand the best way to do that is by using ``pyramid_tm`` and hooking the\n``request.tm`` transaction manager into your service container. The\nrequest object itself is already added to the container for the\n``pyramid.interfaces.IRequest`` interface and can be used in factories that\nrequire the request.\n\nThis can be done before any services are instantiated by subscribing to the\n``pyramid_services.NewServiceContainer`` event:\n\n.. code-block:: python\n\n  from pyramid_services import NewServiceContainer\n\n  def on_new_container(event):\n      container = event.container\n      request = event.request\n      container.set(request.tm, name='tm')\n\n  config.add_subscriber(on_new_container, NewServiceContainer)\n\nExamples\n========\n\nLet's create a login service by progressively building up from scratch what\nwe want to use in our app.\n\nBasically all of the steps in configuring an interface are optional, but\nthey are shown here as best practices.\n\n.. code-block:: python\n\n  # myapp/interfaces.py\n\n  from zope.interface import Interface\n\n  class ILoginService(Interface):\n    def create_token_for_login(name):\n      pass\n\nWith our interface we can now define a conforming instance.\n\n.. code-block:: python\n\n  # myapp/services.py\n\n  class DummyLoginService(object):\n    def create_token_for_login(self, name):\n      return 'u:{0}'.format(name)\n\nLet's hook it up to our application.\n\n.. code-block:: python\n\n  # myapp/main.py\n\n  from pyramid.config import Configurator\n\n  from myapp.services import DummyLoginService\n\n  def main(global_config, **settings):\n    config = Configurator()\n    config.include('pyramid_services')\n\n    config.register_service(DummyLoginService(), ILoginService)\n\n    config.add_route('home', '/')\n    config.scan('.views')\n    return config.make_wsgi_app()\n\nFinally, let's create our view that utilizes the service.\n\n.. code-block:: python\n\n  # myapp/views.py\n\n  @view_config(route_name='home', renderer='json')\n  def home_view(request):\n    name = request.params.get('name', 'bob')\n\n    login_svc = request.find_service(ILoginService)\n    token = login_svc.create_token_for_login(name)\n\n    return {'access_token': token}\n\nIf you start up this application, you will find that you can access\nthe home url and get custom tokens!\n\nThis is cool, but what's even better is swapping in a new service without\nchanging our view at all. Let's define a new ``PersistentLoginService``\nthat gets tokens from a database. We're going to need to setup some\ndatabase handling, but again nothing changes in the view.\n\n.. code-block:: python\n\n  # myapp/services.py\n\n  from uuid import uuid4\n\n  from myapp.model import AccessToken\n\n  class PersistentLoginService(object):\n    def __init__(self, dbsession):\n      self.dbsession = dbsession\n\n    def create_token_for_login(self, name):\n      token = AccessToken(key=uuid4(), user=name)\n      self.dbsession.add(token)\n      return token.key\n\nBelow is some boilerplate for configuring a model using the excellent\n`SQLAlchemy ORM \u003chttp://docs.sqlalchemy.org\u003e`__.\n\n.. code-block:: python\n\n  # myapp/model.py\n\n  from sqlalchemy import engine_from_config\n  from sqlalchemy.ext.declarative import declarative_base\n  from sqlalchemy.orm import sessionmaker\n  from sqlalchemy.schema import Column\n  from sqlalchemy.types import Text\n\n  Base = declarative_base()\n\n  def init_model(settings):\n    engine = engine_from_config(settings)\n    dbmaker = sessionmaker()\n    dbmaker.configure(bind=engine)\n    return dbmaker\n\n  class AccessToken(Base):\n    __tablename__ = 'access_token'\n\n    key = Column(Text, primary_key=True)\n    user = Column(Text, nullable=False)\n\nNow we will update the application to use the new ``PersistentLoginService``.\nHowever, we may have other services and it'd be silly to create a new\ndatabase connection for each service in a request. So we'll also add a\nservice that encapsulates the database connection. Using this technique\nwe can wire services together in the service layer.\n\n.. code-block:: python\n\n  # myapp/main.py\n\n  from pyramid.config import Configurator\n  import transaction\n  import zope.sqlalchemy\n\n  from myapp.model import init_model\n  from myapp.services import PersistentLoginService\n\n  def main(global_config, **settings):\n    config = Configurator()\n    config.include('pyramid_services')\n    config.include('pyramid_tm')\n\n    dbmaker = init_model(settings)\n\n    def dbsession_factory(context, request):\n      dbsession = dbmaker()\n      # register the session with pyramid_tm for managing transactions\n      zope.sqlalchemy.register(dbsession, transaction_manager=request.tm)\n      return dbsession\n\n    config.register_service_factory(dbsession_factory, name='db')\n\n    def login_factory(context, request):\n      dbsession = request.find_service(name='db')\n      svc = PersistentLoginService(dbsession)\n      return svc\n\n    config.register_service_factory(login_factory, ILoginService)\n\n    config.add_route('home', '/')\n    config.scan('.views')\n    return config.make_wsgi_app()\n\nAnd finally the home view will remain unchanged.\n\n.. code-block:: python\n\n  # myapp/views.py\n\n  @view_config(route_name='home', renderer='json')\n  def home_view(request):\n    name = request.params.get('name', 'bob')\n\n    login_svc = request.find_service(ILoginService)\n    token = login_svc.create_token_for_login(name)\n\n    return {'access_token': token}\n\nHopefully this pattern is clear. It has several advantages over most basic\nPyramid tutorials.\n\n- The model is completely abstracted from the views, making both easy to\n  test on their own.\n\n- The service layer can be developed independently of the views, allowing\n  for dummy implementations for easy creation of templates and frontend\n  logic. Later, the real service layer can be swapped in as it's developed,\n  building out the backend functionality.\n\n- Most services may be implemented in such a way that they do not depend on\n  Pyramid or a particular request object.\n\n- Different services may be returned based on a context, such as the\n  result of traversal or some other application-defined discriminator.\n\nTesting Examples\n================\n\nIf you are writing an application that uses ``pyramid_services`` you may want\nto do some integration testing that verifies that your application has\nsuccessfully called ``register_service`` or ``register_service_factory``. Using\n``Pyramid``'s ``testing`` module to create a ``Configurator`` and after calling\n``config.include('pyramid_services')`` you may use ``find_service_factory`` to\nget information about a registered service.\n\nTake as an example this test that verifies that ``dbsession_factory`` has been\ncorrectly registered. This assumes you have a ``myapp.services`` package that\ncontains an ``includeme()`` function.\n\n.. code-block:: python\n\n  # myapp/tests/test_integration.py\n\n  from myapp.services import dbsession_factory, login_factory, ILoginService\n\n  class TestIntegration_services(unittest.TestCase):\n    def setUp(self):\n      self.config = pyramid.testing.setUp()\n      self.config.include('pyramid_services')\n      self.config.include('myapp.services')\n\n    def tearDown(self):\n      pyramid.testing.tearDown()\n\n    def test_db_maker(self):\n      result = self.config.find_service_factory(name='db')\n      self.assertEqual(result, dbsession_factory)\n\n    def test_login_factory(self):\n      result = self.config.find_service_factory(ILoginService)\n      self.assertEqual(result, login_factory)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmmerickel%2Fpyramid_services","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmmerickel%2Fpyramid_services","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmmerickel%2Fpyramid_services/lists"}