Add app providers, tests, docs
This commit is contained in:
		
							parent
							
								
									3cafa28ab9
								
							
						
					
					
						commit
						3a8bd1fce9
					
				
					 8 changed files with 288 additions and 0 deletions
				
			
		| 
						 | 
					@ -42,6 +42,10 @@ Glossary
 | 
				
			||||||
 | 
					
 | 
				
			||||||
     See also the human-friendly :term:`app title`.
 | 
					     See also the human-friendly :term:`app title`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   app provider
 | 
				
			||||||
 | 
					     A :term:`provider` which pertains to the :term:`app handler`.
 | 
				
			||||||
 | 
					     See :doc:`narr/providers/app`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   app title
 | 
					   app title
 | 
				
			||||||
     Human-friendly name for the :term:`app` (e.g. "Wutta Poser").
 | 
					     Human-friendly name for the :term:`app` (e.g. "Wutta Poser").
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -104,6 +108,11 @@ Glossary
 | 
				
			||||||
     modules etc. which is installed via ``pip``.  See also
 | 
					     modules etc. which is installed via ``pip``.  See also
 | 
				
			||||||
     :doc:`narr/install/pkg`.
 | 
					     :doc:`narr/install/pkg`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   provider
 | 
				
			||||||
 | 
					     Python object which "provides" extra functionality to some
 | 
				
			||||||
 | 
					     portion of the :term:`app`.  Similar to a "plugin" concept; see
 | 
				
			||||||
 | 
					     :doc:`narr/providers/index`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   settings table
 | 
					   settings table
 | 
				
			||||||
     Table in the :term:`app database` which is used to store
 | 
					     Table in the :term:`app database` which is used to store
 | 
				
			||||||
     :term:`config settings<config setting>`.  See also
 | 
					     :term:`config settings<config setting>`.  See also
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,4 +9,5 @@ Documentation
 | 
				
			||||||
   config/index
 | 
					   config/index
 | 
				
			||||||
   cli/index
 | 
					   cli/index
 | 
				
			||||||
   handlers/index
 | 
					   handlers/index
 | 
				
			||||||
 | 
					   providers/index
 | 
				
			||||||
   db/index
 | 
					   db/index
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										57
									
								
								docs/narr/providers/app.rst
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								docs/narr/providers/app.rst
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,57 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					App Providers
 | 
				
			||||||
 | 
					=============
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					An :term:`app provider` is a :term:`provider` which can "extend" the
 | 
				
			||||||
 | 
					main :term:`app handler`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The provider generally does this by adding extra methods to the app
 | 
				
			||||||
 | 
					handler.  Note that it does this regardless of which app handler is
 | 
				
			||||||
 | 
					configured to be used.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					:class:`~wuttjamaican.app.AppProvider` is the base class.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Adding a new Provider
 | 
				
			||||||
 | 
					---------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					First define your provider class.  Note that the method names should
 | 
				
			||||||
 | 
					include a "prefix" unique to your project (``poser_`` in this case).
 | 
				
			||||||
 | 
					This is to avoid naming collisions with the app handler itself, as
 | 
				
			||||||
 | 
					well as other app providers.  So e.g. in ``poser/app.py``::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   from wuttjamaican.app import AppProvider
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   class PoserAppProvider(AppProvider):
 | 
				
			||||||
 | 
					       """
 | 
				
			||||||
 | 
					       App provider for Poser system
 | 
				
			||||||
 | 
					       """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       # nb. method name uses 'poser_' prefix
 | 
				
			||||||
 | 
					       def poser_do_something(self, **kwargs):
 | 
				
			||||||
 | 
					           """
 | 
				
			||||||
 | 
					           Do something for Poser
 | 
				
			||||||
 | 
					           """
 | 
				
			||||||
 | 
					           print("did something")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Register the :term:`entry point` in your ``setup.cfg``:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: ini
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   [options.entry_points]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   wutta.providers =
 | 
				
			||||||
 | 
					       poser = poser.app:PoserAppProvider
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Assuming you have not customized the app handler proper, then you will
 | 
				
			||||||
 | 
					be using the *default* app handler yet it will behave as though it has
 | 
				
			||||||
 | 
					the "provided" methods::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   from wuttjamaican.conf import make_config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   # make normal app
 | 
				
			||||||
 | 
					   config = make_config()
 | 
				
			||||||
 | 
					   app = config.get_app()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   # whatever this does..
 | 
				
			||||||
 | 
					   app.poser_do_something()
 | 
				
			||||||
							
								
								
									
										39
									
								
								docs/narr/providers/arch.rst
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								docs/narr/providers/arch.rst
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,39 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Architecture
 | 
				
			||||||
 | 
					============
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					:term:`Providers<provider>` are similar to a "plugin" concept in that
 | 
				
			||||||
 | 
					multiple providers may be installed by different
 | 
				
			||||||
 | 
					:term:`packages<package>`.  But whereas plugins are typically limited
 | 
				
			||||||
 | 
					to a particular interface (method list/signatures etc.) a provider can
 | 
				
			||||||
 | 
					also "bolt on" entirely new methods which may be used elsewhere in the
 | 
				
			||||||
 | 
					:term:`app`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In that sense providers can perhaps be more accurately thought of as
 | 
				
			||||||
 | 
					"extensions" rather than plugins.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Providers may be related to :term:`handlers<handler>` in some cases,
 | 
				
			||||||
 | 
					but not all.  But whereas there is only *one handler* configured for a
 | 
				
			||||||
 | 
					given portion of the app, multiple providers of the same type would
 | 
				
			||||||
 | 
					*all contribute* to the overall app.  In other words they are always
 | 
				
			||||||
 | 
					enabled if installed.  (Some may require a :term:`config setting` to
 | 
				
			||||||
 | 
					be "active" - but that is up to each provider.)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					There can be many "types" of providers; each pertains to a certain
 | 
				
			||||||
 | 
					aspect of the overall app.  A given type of provider will apply to a
 | 
				
			||||||
 | 
					certain "parent" class.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					What a Provider Does
 | 
				
			||||||
 | 
					--------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Each type of provider pertains to a certain parent class.  The app
 | 
				
			||||||
 | 
					itself will define the need for a provider type.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For instance there might be a "dashboard" class which can show various
 | 
				
			||||||
 | 
					blocks of info (charts etc.).  Providers might be used to supplement the
 | 
				
			||||||
 | 
					parent dashboard class, by adding extra blocks to the display.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					But in that example, providers look an awful lot like plugins.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For a better (and real) example see :doc:`app`.
 | 
				
			||||||
							
								
								
									
										10
									
								
								docs/narr/providers/index.rst
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								docs/narr/providers/index.rst
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,10 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Providers
 | 
				
			||||||
 | 
					=========
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. toctree::
 | 
				
			||||||
 | 
					   :maxdepth: 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   overview
 | 
				
			||||||
 | 
					   arch
 | 
				
			||||||
 | 
					   app
 | 
				
			||||||
							
								
								
									
										13
									
								
								docs/narr/providers/overview.rst
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								docs/narr/providers/overview.rst
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,13 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Overview
 | 
				
			||||||
 | 
					========
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The :term:`provider` concept is a way to "supplement" the main app
 | 
				
			||||||
 | 
					logic.  It is different from a :term:`handler` though:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Providers are *more* analagous to "plugins" than are handlers.  For
 | 
				
			||||||
 | 
					instance multiple :term:`app providers<app provider>` may be installed
 | 
				
			||||||
 | 
					by various packages and *each of these* will supplement the (one and
 | 
				
			||||||
 | 
					only) :term:`app handler`.  See also :doc:`arch`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					So far there is only one provider type defined; see :doc:`app`.
 | 
				
			||||||
| 
						 | 
					@ -25,6 +25,7 @@ WuttJamaican - app handler
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					import warnings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from wuttjamaican.util import load_entry_points, load_object, parse_bool
 | 
					from wuttjamaican.util import load_entry_points, load_object, parse_bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -46,6 +47,11 @@ class AppHandler:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    :param config: Config object for the app.  This should be an
 | 
					    :param config: Config object for the app.  This should be an
 | 
				
			||||||
       instance of :class:`~wuttjamaican.conf.WuttaConfig`.
 | 
					       instance of :class:`~wuttjamaican.conf.WuttaConfig`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. attribute:: providers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       Dictionary of :class:`AppProvider` instances, as returned by
 | 
				
			||||||
 | 
					       :meth:`get_all_providers()`.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, config):
 | 
					    def __init__(self, config):
 | 
				
			||||||
| 
						 | 
					@ -66,6 +72,47 @@ class AppHandler:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        return self.config.appname
 | 
					        return self.config.appname
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __getattr__(self, name):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Custom attribute getter, called when the app handler does not
 | 
				
			||||||
 | 
					        already have an attribute named with ``name``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        This will delegate to the set of :term:`app providers<app
 | 
				
			||||||
 | 
					        provider>`; the first provider with an appropriately-named
 | 
				
			||||||
 | 
					        attribute wins, and that value is returned.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :returns: The first value found among the set of app
 | 
				
			||||||
 | 
					           providers.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if name == 'providers':
 | 
				
			||||||
 | 
					            self.providers = self.get_all_providers()
 | 
				
			||||||
 | 
					            return self.providers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # if 'providers' not in self.__dict__:
 | 
				
			||||||
 | 
					        #     self.__dict__['providers'] = self.get_all_providers()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for provider in self.providers.values():
 | 
				
			||||||
 | 
					            if hasattr(provider, name):
 | 
				
			||||||
 | 
					                return getattr(provider, name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return super().__getattr__(name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_all_providers(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Load and return all registered providers.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Note that you do not need to call this directly; instead just
 | 
				
			||||||
 | 
					        use :attr:`providers`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :returns: Dictionary keyed by entry point name; values are
 | 
				
			||||||
 | 
					           :class:`AppProvider` *instances*.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        providers = load_entry_points(f'{self.appname}.providers')
 | 
				
			||||||
 | 
					        for key in list(providers):
 | 
				
			||||||
 | 
					            providers[key] = providers[key](self.config)
 | 
				
			||||||
 | 
					        return providers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def make_appdir(self, path, subfolders=None, **kwargs):
 | 
					    def make_appdir(self, path, subfolders=None, **kwargs):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Establish an :term:`app dir` at the given path.
 | 
					        Establish an :term:`app dir` at the given path.
 | 
				
			||||||
| 
						 | 
					@ -201,3 +248,36 @@ class AppHandler:
 | 
				
			||||||
        from .db import get_setting
 | 
					        from .db import get_setting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return get_setting(session, name)
 | 
					        return get_setting(session, name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AppProvider:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Base class for :term:`app providers<app provider>`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    These can add arbitrary extra functionality to the main :term:`app
 | 
				
			||||||
 | 
					    handler`.  See also :doc:`/narr/providers/app`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param config: Config object for the app.  This should be an
 | 
				
			||||||
 | 
					       instance of :class:`~wuttjamaican.conf.WuttaConfig`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Instances have the following attributes:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. attribute:: config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       Reference to the config object.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. attribute:: app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       Reference to the parent app handler.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, config):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if isinstance(config, AppHandler):
 | 
				
			||||||
 | 
					            warnings.warn("passing app handler to app provider is deprecated; "
 | 
				
			||||||
 | 
					                          "must pass config object instead",
 | 
				
			||||||
 | 
					                          DeprecationWarning, stacklevel=2)
 | 
				
			||||||
 | 
					            config = config.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.config = config
 | 
				
			||||||
 | 
					        self.app = config.get_app()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,6 +20,7 @@ class TestAppHandler(TestCase):
 | 
				
			||||||
    def setUp(self):
 | 
					    def setUp(self):
 | 
				
			||||||
        self.config = WuttaConfig(appname='wuttatest')
 | 
					        self.config = WuttaConfig(appname='wuttatest')
 | 
				
			||||||
        self.app = app.AppHandler(self.config)
 | 
					        self.app = app.AppHandler(self.config)
 | 
				
			||||||
 | 
					        self.config.app = self.app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_init(self):
 | 
					    def test_init(self):
 | 
				
			||||||
        self.assertIs(self.app.config, self.config)
 | 
					        self.assertIs(self.app.config, self.config)
 | 
				
			||||||
| 
						 | 
					@ -109,3 +110,81 @@ class TestAppHandler(TestCase):
 | 
				
			||||||
        session.execute(sa.text("insert into setting values ('foo', 'bar');"))
 | 
					        session.execute(sa.text("insert into setting values ('foo', 'bar');"))
 | 
				
			||||||
        value = self.app.get_setting(session, 'foo')
 | 
					        value = self.app.get_setting(session, 'foo')
 | 
				
			||||||
        self.assertEqual(value, 'bar')
 | 
					        self.assertEqual(value, 'bar')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestAppProvider(TestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def setUp(self):
 | 
				
			||||||
 | 
					        self.config = WuttaConfig(appname='wuttatest')
 | 
				
			||||||
 | 
					        self.app = app.AppHandler(self.config)
 | 
				
			||||||
 | 
					        self.config.app = self.app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_constructor(self):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # config object is expected
 | 
				
			||||||
 | 
					        provider = app.AppProvider(self.config)
 | 
				
			||||||
 | 
					        self.assertIs(provider.config, self.config)
 | 
				
			||||||
 | 
					        self.assertIs(provider.app, self.app)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # but can pass app handler instead
 | 
				
			||||||
 | 
					        provider = app.AppProvider(self.app)
 | 
				
			||||||
 | 
					        self.assertIs(provider.config, self.config)
 | 
				
			||||||
 | 
					        self.assertIs(provider.app, self.app)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_get_all_providers(self):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        class FakeProvider(app.AppProvider):
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # nb. we specify *classes* here
 | 
				
			||||||
 | 
					        fake_providers = {'fake': FakeProvider}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with patch('wuttjamaican.app.load_entry_points') as load_entry_points:
 | 
				
			||||||
 | 
					            load_entry_points.return_value = fake_providers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # sanity check, we get *instances* back from this
 | 
				
			||||||
 | 
					            providers = self.app.get_all_providers()
 | 
				
			||||||
 | 
					            load_entry_points.assert_called_once_with('wuttatest.providers')
 | 
				
			||||||
 | 
					            self.assertEqual(len(providers), 1)
 | 
				
			||||||
 | 
					            self.assertIn('fake', providers)
 | 
				
			||||||
 | 
					            self.assertIsInstance(providers['fake'], FakeProvider)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_hasattr(self):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        class FakeProvider(app.AppProvider):
 | 
				
			||||||
 | 
					            def fake_foo(self):
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.app.providers = {'fake': FakeProvider(self.config)}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertTrue(hasattr(self.app, 'fake_foo'))
 | 
				
			||||||
 | 
					        self.assertFalse(hasattr(self.app, 'fake_method_does_not_exist'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_getattr(self):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        class FakeProvider(app.AppProvider):
 | 
				
			||||||
 | 
					            def fake_foo(self):
 | 
				
			||||||
 | 
					                return 42
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # nb. using instances here
 | 
				
			||||||
 | 
					        fake_providers = {'fake': FakeProvider(self.config)}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with patch.object(self.app, 'get_all_providers') as get_all_providers:
 | 
				
			||||||
 | 
					            get_all_providers.return_value = fake_providers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.assertNotIn('providers', self.app.__dict__)
 | 
				
			||||||
 | 
					            self.assertIs(self.app.providers, fake_providers)
 | 
				
			||||||
 | 
					            get_all_providers.assert_called_once_with()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_getattr_providers(self):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # collection of providers is loaded on demand
 | 
				
			||||||
 | 
					        self.assertNotIn('providers', self.app.__dict__)
 | 
				
			||||||
 | 
					        self.assertIsNotNone(self.app.providers)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # custom attr does not exist yet
 | 
				
			||||||
 | 
					        self.assertRaises(AttributeError, getattr, self.app, 'foo_value')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # but provider can supply the attr
 | 
				
			||||||
 | 
					        self.app.providers['mytest'] = MagicMock(foo_value='bar')
 | 
				
			||||||
 | 
					        self.assertEqual(self.app.foo_value, 'bar')
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue