diff options
| author | Chris McDonough <chrism@plope.com> | 2011-07-16 20:03:08 -0400 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2011-07-16 20:03:08 -0400 |
| commit | c3ceaf474c68a0b12f126b4cb57448b26792b697 (patch) | |
| tree | 733577de0bda50a101153262da91a907ccd36166 | |
| parent | d50edc5807539da66e266a7ed7810f770cee969a (diff) | |
| parent | 69452f63ab2efa39c9273646959341287ba5ee15 (diff) | |
| download | pyramid-c3ceaf474c68a0b12f126b4cb57448b26792b697.tar.gz pyramid-c3ceaf474c68a0b12f126b4cb57448b26792b697.tar.bz2 pyramid-c3ceaf474c68a0b12f126b4cb57448b26792b697.zip | |
Merge branch 'master' of github.com:Pylons/pyramid
| -rw-r--r-- | docs/api/scripting.rst | 2 | ||||
| -rw-r--r-- | docs/narr/commandline.rst | 76 | ||||
| -rw-r--r-- | pyramid/paster.py | 104 | ||||
| -rw-r--r-- | pyramid/scripting.py | 68 | ||||
| -rw-r--r-- | pyramid/tests/test_paster.py | 377 | ||||
| -rw-r--r-- | pyramid/tests/test_scripting.py | 10 | ||||
| -rw-r--r-- | pyramid/util.py | 13 |
7 files changed, 283 insertions, 367 deletions
diff --git a/docs/api/scripting.rst b/docs/api/scripting.rst index 79136a98b..51bd3c7a0 100644 --- a/docs/api/scripting.rst +++ b/docs/api/scripting.rst @@ -9,5 +9,3 @@ .. autofunction:: prepare - .. autofunction:: make_request - diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index bc210904f..30e678f07 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -294,7 +294,7 @@ Writing a Script All web applications are, at their hearts, systems which accept a request and return a response. When a request is accepted by a :app:`Pyramid` application, the system receives state from the request which is later relied -on your application code. For example, one :term:`view callable` may assume +on by your application code. For example, one :term:`view callable` may assume it's working against a request that has a ``request.matchdict`` of a particular composition, while another assumes a different composition of the matchdict. @@ -324,14 +324,14 @@ representing Pyramid your application configuration as a single argument: .. code-block:: python from pyramid.paster import bootstrap - info = bootstrap('/path/to/my/development.ini') - print info['request'].route_url('home') + env = bootstrap('/path/to/my/development.ini') + print env['request'].route_url('home') :func:`pyramid.paster.bootstrap` returns a dictionary containing framework-related information. This dictionary will always contain a :term:`request` object as its ``request`` key. -The following keys are available in the ``info`` dictionary returned by +The following keys are available in the ``env`` dictionary returned by :func:`pyramid.paster.bootstrap`: request @@ -386,43 +386,54 @@ to load instead of ``main``: .. code-block:: python from pyramid.paster import bootstrap - info = bootstrap('/path/to/my/development.ini#another') - print info['request'].route_url('home') + env = bootstrap('/path/to/my/development.ini#another') + print env['request'].route_url('home') The above example specifies the ``another`` ``app``, ``pipeline``, or -``composite`` section of your PasteDeploy configuration file. In the case -that we're using a configuration file that looks like this: +``composite`` section of your PasteDeploy configuration file. The ``app`` +object present in the ``env`` dictionary returned by +:func:`pyramid.paster.bootstrap` will be a :app:`Pyramid` :term:`router`. -.. code-block:: ini +Changing the Request +~~~~~~~~~~~~~~~~~~~~ - [pipeline:main] - pipeline = egg:WebError#evalerror - another +By default, Pyramid will generate a request object in the ``env`` dictionary +for the URL ``http://localhost:80/``. This means that any URLs generated +by Pyramid during the execution of your script will be anchored here. This +is generally not what you want. - [app:another] - use = egg:MyProject +So how do we make Pyramid generate the correct URLs? + +Assuming that you have a route configured in your application like so: + +.. code-block:: python -It will mean that the ``/path/to/my/development.ini#another`` argument passed -to bootstrap will imply the ``[app:another]`` section in our configuration -file. Therefore, it will not wrap the WSGI application present in the info -dictionary as ``app`` using WebError's ``evalerror`` middleware. The ``app`` -object present in the info dictionary returned by -:func:`pyramid.paster.bootstrap` will be a :app:`Pyramid` :term:`router` -instead. + config.add_route('verify', '/verify/{code}') -By default, Pyramid will general a request object in the ``info`` dictionary -anchored at the root path (``/``). You can alternately supply your own -:class:`pyramid.request.Request` instance to the -:func:`pyramid.paster.bootstrap` function, to set up request parameters -beforehand: +You need to inform the Pyramid environment that the WSGI application is +handling requests from a certain base. For example, we want to mount our +application at `example.com/prefix` and the generated URLs should use HTTPS. +This can be done by mutating the request object: .. code-block:: python - from pyramid.request import Request - request = Request.blank('/another/url') from pyramid.paster import bootstrap - info = bootstrap('/path/to/my/development.ini#another', request=request) - print info['request'].path_info # will print '/another/url' + env = bootstrap('/path/to/my/development.ini#another') + env['request'].host = 'example.com' + env['request'].scheme = 'https' + env['request'].script_name = '/prefix' + print env['request'].application_url + # will print 'https://example.com/prefix/another/url' + +Now you can readily use Pyramid's APIs for generating URLs: + +.. code-block:: python + + route_url('verify', env['request'], code='1337') + # will return 'https://example.com/prefix/verify/1337' + +Cleanup +~~~~~~~ When your scripting logic finishes, it's good manners (but not required) to call the ``closer`` callback: @@ -430,10 +441,9 @@ call the ``closer`` callback: .. code-block:: python from pyramid.paster import bootstrap - info = bootstrap('/path/to/my/development.ini') + env = bootstrap('/path/to/my/development.ini') # .. do stuff ... - info['closer']() - + env['closer']() diff --git a/pyramid/paster.py b/pyramid/paster.py index 048f747e8..444608f92 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -42,11 +42,12 @@ def bootstrap(config_uri, request=None): currently serving ``request``, leaving a natural environment in place to write scripts that can generate URLs and utilize renderers. - This function returns a dictionary with ``app``, ``root`` and ``closer`` - keys. ``app`` is the WSGI app loaded (based on the ``config_uri``), - ``root`` is the traversal root resource of the Pyramid application, and - ``closer`` is a parameterless callback that may be called when your - script is complete (it pops a threadlocal stack). + This function returns a dictionary with ``app``, ``root``, ``closer``, + ``request``, and ``registry`` keys. ``app`` is the WSGI app loaded + (based on the ``config_uri``), ``root`` is the traversal root resource + of the Pyramid application, and ``closer`` is a parameterless callback + that may be called when your script is complete (it pops a threadlocal + stack). .. note:: Most operations within :app:`Pyramid` expect to be invoked within the context of a WSGI request, thus it's important when @@ -59,7 +60,7 @@ def bootstrap(config_uri, request=None): the context of the last-loaded :app:`Pyramid` application. You may load a specific application yourself by using the lower-level functions :meth:`pyramid.paster.get_app` and - :meth:`pyramid.scripting.get_root2` in conjunction with + :meth:`pyramid.scripting.prepare` in conjunction with :attr:`pyramid.config.global_registries`. ``config_uri`` -- specifies the PasteDeploy config file to use for the @@ -69,15 +70,17 @@ def bootstrap(config_uri, request=None): ``request`` -- specified to anchor the script to a given set of WSGI parameters. For example, most people would want to specify the host, scheme and port such that their script will generate URLs in relation - to those parameters. + to those parameters. A request with default parameters is constructed + for you if none is provided. You can mutate the request's ``environ`` + later to setup a specific host/port/scheme/etc. See :ref:`writing_a_script` for more information about how to use this function. """ app = get_app(config_uri) - info = prepare(request) - info['app'] = app - return info + env = prepare(request) + env['app'] = app + return env _marker = object() @@ -126,6 +129,7 @@ class PShellCommand(PCommand): dest='disable_ipython', help="Don't use IPython even if it is available") + bootstrap = (bootstrap,) # testing ConfigParser = ConfigParser.ConfigParser # testing def pshell_file_config(self, filename): @@ -149,72 +153,60 @@ class PShellCommand(PCommand): from IPython.Shell import IPShell except ImportError: IPShell = None - cprt = 'Type "help" for more information.' - banner = "Python %s on %s\n%s" % (sys.version, sys.platform, cprt) config_uri = self.args[0] config_file = config_uri.split('#', 1)[0] self.logging_file_config(config_file) - app = self.get_app(config_uri, loadapp=self.loadapp[0]) + self.pshell_file_config(config_file) - # load default globals - shell_globals = { - 'app': app, - } - default_variables = {'app': 'The WSGI Application'} - if hasattr(app, 'registry'): - root, closer = self.get_root(app) - shell_globals.update({'root':root, 'registry':app.registry, - 'settings': app.registry.settings}) - default_variables.update({ - 'root': 'The root of the default resource tree.', - 'registry': 'The Pyramid registry object.', - 'settings': 'The Pyramid settings object.', - }) - warning = '' - else: - # warn the user that this isn't actually the Pyramid app - warning = """\n -WARNING: You have loaded a generic WSGI application, therefore the "root", -"registry", and "settings" global variables are not available. To correct -this, run "pshell" again and specify the INI section containing your Pyramid -application. For example, if your app is in the '[app:myapp]' config file -section, use 'development.ini#myapp' instead of 'development.ini' or -'development.ini#main'.""" - closer = lambda: None + # bootstrap the environ + env = self.bootstrap[0](config_uri) + + # remove the closer from the env + closer = env.pop('closer') + + # setup help text for default environment + env_help = dict(env) + env_help['app'] = 'The WSGI application.' + env_help['root'] = 'Root of the default resource tree.' + env_help['registry'] = 'Active Pyramid registry.' + env_help['request'] = 'Active request object.' + env_help['root_factory'] = ( + 'Default root factory used to create `root`.') # load the pshell section of the ini file - self.pshell_file_config(config_file) - shell_globals.update(self.loaded_objects) + env.update(self.loaded_objects) - # eliminate duplicates from default_variables + # eliminate duplicates from env, allowing custom vars to override for k in self.loaded_objects: - if k in default_variables: - del default_variables[k] + if k in env_help: + del env_help[k] - # append the loaded variables - if default_variables: - banner += '\n\nDefault Variables:' - for var, txt in default_variables.iteritems(): - banner += '\n %-12s %s' % (var, txt) + # generate help text + help = '\n' + if env_help: + help += 'Environment:' + for var in sorted(env_help.keys()): + help += '\n %-12s %s' % (var, env_help[var]) if self.object_help: - banner += '\n\nCustom Variables:' + help += '\n\nCustom Variables:' for var in sorted(self.object_help.keys()): - banner += '\n %-12s %s' % (var, self.object_help[var]) + help += '\n %-12s %s' % (var, self.object_help[var]) - # append the warning - banner += warning - banner += '\n' + help += '\n' if (IPShell is None) or self.options.disable_ipython: + cprt = 'Type "help" for more information.' + banner = "Python %s on %s\n%s" % (sys.version, sys.platform, cprt) + banner += '\n' + help try: - self.interact[0](banner, local=shell_globals) + self.interact[0](banner, local=env) finally: closer() else: try: - shell = IPShell(argv=[], user_ns=shell_globals) - shell.IP.BANNER = shell.IP.BANNER + '\n\n' + banner + shell = IPShell(argv=[], user_ns=env) + shell.IP.BANNER = shell.IP.BANNER + help shell.mainloop() finally: closer() diff --git a/pyramid/scripting.py b/pyramid/scripting.py index c04915d3a..47178c22e 100644 --- a/pyramid/scripting.py +++ b/pyramid/scripting.py @@ -1,4 +1,5 @@ from pyramid.config import global_registries +from pyramid.exceptions import ConfigurationError from pyramid.request import Request from pyramid.interfaces import IRequestFactory from pyramid.interfaces import IRootFactory @@ -12,13 +13,14 @@ def get_root(app, request=None): is a callable (accepting no arguments) that should be called when your scripting application is finished using the root. - If ``request`` is not None, it is used as the request passed to the - :app:`Pyramid` application root factory. A request is constructed - using :meth:`pyramid.scripting.make_request` and passed to the root - factory if ``request`` is None.""" + ``request`` is passed to the :app:`Pyramid` application root + factory to compute the root. If ``request`` is None, a default + will be constructed using the registry's :term:`Request Factory` + via the :meth:`pyramid.interfaces.IRequestFactory.blank` method. + """ registry = app.registry if request is None: - request = make_request('/', registry) + request = _make_request('/', registry) threadlocals = {'registry':registry, 'request':request} app.threadlocal_manager.push(threadlocals) def closer(request=request): # keep request alive via this function default @@ -27,37 +29,43 @@ def get_root(app, request=None): return root, closer def prepare(request=None, registry=None): - """ This function pushes data onto the Pyramid threadlocal stack (request - and registry), making those objects 'current'. It returns a dictionary - useful for bootstrapping a Pyramid application in a scripting - environment. + """ This function pushes data onto the Pyramid threadlocal stack + (request and registry), making those objects 'current'. It + returns a dictionary useful for bootstrapping a Pyramid + application in a scripting environment. - If ``request`` is None, a default request is constructed using - :meth:`pyramid.scripting.make_request`. The request passed to the - :app:`Pyramid` application root factory to compute the root. + ``request`` is passed to the :app:`Pyramid` application root + factory to compute the root. If ``request`` is None, a default + will be constructed using the registry's :term:`Request Factory` + via the :meth:`pyramid.interfaces.IRequestFactory.blank` method. If ``registry`` is not supplied, the last registry loaded from - :attr:`pyramid.config.global_registries` will be used. If you have - loaded more than one :app:`Pyramid` application in the current - process, you may not want to use the last registry loaded, thus - you can search the ``global_registries`` and supply the appropriate - one based on your own criteria. + :attr:`pyramid.config.global_registries` will be used. If you + have loaded more than one :app:`Pyramid` application in the + current process, you may not want to use the last registry + loaded, thus you can search the ``global_registries`` and supply + the appropriate one based on your own criteria. - The function returns a dictionary composed of ``root``, ``closer``, - ``registry``, ``request`` and ``root_factory``. The ``root`` returned is - the application's root resource object. The ``closer`` returned is a - callable (accepting no arguments) that should be called when your - scripting application is finished using the root. ``registry`` is the - registry object passed or the last registry loaded into + The function returns a dictionary composed of ``root``, + ``closer``, ``registry``, ``request`` and ``root_factory``. The + ``root`` returned is the application's root resource object. The + ``closer`` returned is a callable (accepting no arguments) that + should be called when your scripting application is finished + using the root. ``registry`` is the registry object passed or + the last registry loaded into :attr:`pyramid.config.global_registries` if no registry is passed. - ``request`` is the request object passed or the constructed request if no - request is passed. ``root_factory`` is the root factory used to - construct the root. + ``request`` is the request object passed or the constructed request + if no request is passed. ``root_factory`` is the root factory used + to construct the root. """ if registry is None: registry = getattr(request, 'registry', global_registries.last) + if registry is None: + raise ConfigurationError('No valid Pyramid applications could be ' + 'found, make sure one has been created ' + 'before trying to activate it.') if request is None: - request = make_request('/', registry) + request = _make_request('/', registry) request.registry = registry threadlocals = {'registry':registry, 'request':request} threadlocal_manager.push(threadlocals) @@ -69,14 +77,14 @@ def prepare(request=None, registry=None): return {'root':root, 'closer':closer, 'registry':registry, 'request':request, 'root_factory':root_factory} -def make_request(path, registry=None): +def _make_request(path, registry=None): """ Return a :meth:`pyramid.request.Request` object anchored at a given path. The object returned will be generated from the supplied registry's :term:`Request Factory` using the :meth:`pyramid.interfaces.IRequestFactory.blank` method. - This request object can be passed to - :meth:`pyramid.scripting.get_root` to initialize an application in + This request object can be passed to :meth:`pyramid.scripting.get_root` + or :meth:`pyramid.scripting.prepare` to initialize an application in preparation for executing a script with a proper environment setup. URLs can then be generated with the object, as well as rendering templates. diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py index 7785b006e..4a099bbdb 100644 --- a/pyramid/tests/test_paster.py +++ b/pyramid/tests/test_paster.py @@ -5,245 +5,117 @@ class TestPShellCommand(unittest.TestCase): from pyramid.paster import PShellCommand return PShellCommand - def _makeOne(self): - return self._getTargetClass()('pshell') + def _makeOne(self, patch_interact=True, patch_bootstrap=True, + patch_config=True, patch_args=True, patch_options=True): + cmd = self._getTargetClass()('pshell') + if patch_interact: + self.interact = DummyInteractor() + cmd.interact = (self.interact,) + if patch_bootstrap: + self.bootstrap = DummyBootstrap() + cmd.bootstrap = (self.bootstrap,) + if patch_config: + self.config_factory = DummyConfigParserFactory() + cmd.ConfigParser = self.config_factory + if patch_args: + self.args = ('/foo/bar/myapp.ini#myapp',) + cmd.args = self.args + if patch_options: + class Options(object): pass + self.options = Options() + self.options.disable_ipython = True + cmd.options = self.options + return cmd def test_command_ipshell_is_None_ipython_enabled(self): command = self._makeOne() - interact = DummyInteractor() - app = DummyApp() - loadapp = DummyLoadApp(app) - command.interact = (interact,) - command.loadapp = (loadapp,) - command.ConfigParser = makeDummyConfigParser({}) - command.args = ('/foo/bar/myapp.ini#myapp',) - class Options(object): pass - command.options = Options() - command.options.disable_ipython = False + command.options.disable_ipython = True command.command(IPShell=None) - self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') - self.assertEqual(loadapp.section_name, 'myapp') - self.assertTrue(loadapp.relative_to) - self.assertEqual(len(app.threadlocal_manager.pushed), 1) - pushed = app.threadlocal_manager.pushed[0] - self.assertEqual(pushed['registry'], dummy_registry) - self.assertEqual(pushed['request'].registry, dummy_registry) - self.assertEqual(interact.local, {'app':app, - 'root':dummy_root, - 'registry':dummy_registry, - 'settings':dummy_registry.settings}) - self.assertTrue(interact.banner) - self.assertEqual(len(app.threadlocal_manager.popped), 1) + self.assertTrue(self.config_factory.parser) + self.assertEqual(self.config_factory.parser.filename, + '/foo/bar/myapp.ini') + self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') + self.assertEqual(self.interact.local, { + 'app':self.bootstrap.app, 'root':self.bootstrap.root, + 'registry':self.bootstrap.registry, + 'request':self.bootstrap.request, + 'root_factory':self.bootstrap.root_factory, + }) + self.assertTrue(self.bootstrap.closer.called) + self.assertTrue(self.interact.banner) def test_command_ipshell_is_not_None_ipython_disabled(self): command = self._makeOne() - interact = DummyInteractor() - app = DummyApp() - loadapp = DummyLoadApp(app) - command.interact = (interact,) - command.loadapp = (loadapp,) - command.ConfigParser = makeDummyConfigParser({}) - command.args = ('/foo/bar/myapp.ini#myapp',) - class Options(object): pass - command.options = Options() command.options.disable_ipython = True command.command(IPShell='notnone') - self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') - self.assertEqual(loadapp.section_name, 'myapp') - self.assertTrue(loadapp.relative_to) - self.assertEqual(len(app.threadlocal_manager.pushed), 1) - pushed = app.threadlocal_manager.pushed[0] - self.assertEqual(pushed['registry'], dummy_registry) - self.assertEqual(pushed['request'].registry, dummy_registry) - self.assertEqual(interact.local, {'app':app, - 'root':dummy_root, - 'registry':dummy_registry, - 'settings':dummy_registry.settings}) - self.assertTrue(interact.banner) - self.assertEqual(len(app.threadlocal_manager.popped), 1) + self.assertTrue(self.config_factory.parser) + self.assertEqual(self.config_factory.parser.filename, + '/foo/bar/myapp.ini') + self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') + self.assertEqual(self.interact.local, { + 'app':self.bootstrap.app, 'root':self.bootstrap.root, + 'registry':self.bootstrap.registry, + 'request':self.bootstrap.request, + 'root_factory':self.bootstrap.root_factory, + }) + self.assertTrue(self.bootstrap.closer.called) + self.assertTrue(self.interact.banner) def test_command_ipython_enabled(self): - command = self._makeOne() - app = DummyApp() - loadapp = DummyLoadApp(app) - command.loadapp = (loadapp,) - command.ConfigParser = makeDummyConfigParser({}) - dummy_shell_factory = DummyIPShellFactory() - command.args = ('/foo/bar/myapp.ini#myapp',) - class Options(object): pass - command.options = Options() + command = self._makeOne(patch_interact=False) command.options.disable_ipython = False + dummy_shell_factory = DummyIPShellFactory() command.command(IPShell=dummy_shell_factory) - self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') - self.assertEqual(loadapp.section_name, 'myapp') - self.assertTrue(loadapp.relative_to) - self.assertEqual(len(app.threadlocal_manager.pushed), 1) - pushed = app.threadlocal_manager.pushed[0] - self.assertEqual(pushed['registry'], dummy_registry) - self.assertEqual(pushed['request'].registry, dummy_registry) - self.assertEqual(dummy_shell_factory.shell.local_ns, - {'app':app, 'root':dummy_root, - 'registry':dummy_registry, - 'settings':dummy_registry.settings}) + self.assertTrue(self.config_factory.parser) + self.assertEqual(self.config_factory.parser.filename, + '/foo/bar/myapp.ini') + self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') + self.assertEqual(dummy_shell_factory.shell.local_ns, { + 'app':self.bootstrap.app, 'root':self.bootstrap.root, + 'registry':self.bootstrap.registry, + 'request':self.bootstrap.request, + 'root_factory':self.bootstrap.root_factory, + }) self.assertEqual(dummy_shell_factory.shell.global_ns, {}) - self.assertTrue('\n\n' in dummy_shell_factory.shell.IP.BANNER) - self.assertEqual(len(app.threadlocal_manager.popped), 1) - - def test_command_get_app_hookable(self): - from paste.deploy import loadapp - command = self._makeOne() - app = DummyApp() - apped = [] - def get_app(*arg, **kw): - apped.append((arg, kw)) - return app - command.get_app = get_app - interact = DummyInteractor() - app = DummyApp() - command.interact = (interact,) - command.ConfigParser = makeDummyConfigParser({}) - command.args = ('/foo/bar/myapp.ini#myapp',) - class Options(object): pass - command.options = Options() - command.options.disable_ipython =True - command.command(IPShell=None) - self.assertEqual(len(app.threadlocal_manager.pushed), 1) - pushed = app.threadlocal_manager.pushed[0] - self.assertEqual(pushed['registry'], dummy_registry) - self.assertEqual(pushed['request'].registry, dummy_registry) - self.assertEqual(interact.local, {'app': app, - 'root':dummy_root, - 'registry':dummy_registry, - 'settings':dummy_registry.settings}) - self.assertTrue(interact.banner) - self.assertEqual(len(app.threadlocal_manager.popped), 1) - self.assertEqual(apped, [(('/foo/bar/myapp.ini#myapp',), - {'loadapp': loadapp})]) - - def test_command_get_root_hookable(self): - command = self._makeOne() - interact = DummyInteractor() - app = DummyApp() - loadapp = DummyLoadApp(app) - command.interact = (interact,) - command.loadapp = (loadapp,) - command.ConfigParser = makeDummyConfigParser({}) - root = Dummy() - apps = [] - def get_root(app): - apps.append(app) - return root, lambda *arg: None - command.get_root =get_root - command.args = ('/foo/bar/myapp.ini#myapp',) - class Options(object): pass - command.options = Options() - command.options.disable_ipython =True - command.command(IPShell=None) - self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') - self.assertEqual(loadapp.section_name, 'myapp') - self.assertTrue(loadapp.relative_to) - self.assertEqual(len(app.threadlocal_manager.pushed), 0) - self.assertEqual(interact.local, {'app':app, - 'root':root, - 'registry':dummy_registry, - 'settings':dummy_registry.settings}) - self.assertTrue(interact.banner) - self.assertEqual(apps, [app]) + self.assertTrue(self.bootstrap.closer.called) def test_command_loads_custom_items(self): command = self._makeOne() - interact = DummyInteractor() - app = DummyApp() - loadapp = DummyLoadApp(app) - command.interact = (interact,) - command.loadapp = (loadapp,) model = Dummy() - command.ConfigParser = makeDummyConfigParser([('m', model)]) - command.args = ('/foo/bar/myapp.ini#myapp',) - class Options(object): pass - command.options = Options() - command.options.disable_ipython = False - command.command(IPShell=None) - self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') - self.assertEqual(loadapp.section_name, 'myapp') - self.assertTrue(loadapp.relative_to) - self.assertEqual(len(app.threadlocal_manager.pushed), 1) - pushed = app.threadlocal_manager.pushed[0] - self.assertEqual(pushed['registry'], dummy_registry) - self.assertEqual(pushed['request'].registry, dummy_registry) - self.assertEqual(interact.local, {'app':app, - 'root':dummy_root, - 'registry':dummy_registry, - 'settings':dummy_registry.settings, - 'm': model}) - self.assertTrue(interact.banner) - self.assertEqual(len(app.threadlocal_manager.popped), 1) - - def test_command_no_custom_section(self): - command = self._makeOne() - interact = DummyInteractor() - app = DummyApp() - loadapp = DummyLoadApp(app) - command.interact = (interact,) - command.loadapp = (loadapp,) - command.ConfigParser = makeDummyConfigParser(None) - command.args = ('/foo/bar/myapp.ini#myapp',) - class Options(object): pass - command.options = Options() - command.options.disable_ipython = False + self.config_factory.items = [('m', model)] + command.options.disable_ipython = True command.command(IPShell=None) - self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') - self.assertEqual(loadapp.section_name, 'myapp') - self.assertTrue(loadapp.relative_to) - self.assertEqual(len(app.threadlocal_manager.pushed), 1) - pushed = app.threadlocal_manager.pushed[0] - self.assertEqual(pushed['registry'], dummy_registry) - self.assertEqual(pushed['request'].registry, dummy_registry) - self.assertEqual(interact.local, {'app':app, - 'root':dummy_root, - 'registry':dummy_registry, - 'settings':dummy_registry.settings}) - self.assertTrue(interact.banner) - self.assertEqual(len(app.threadlocal_manager.popped), 1) + self.assertTrue(self.config_factory.parser) + self.assertEqual(self.config_factory.parser.filename, + '/foo/bar/myapp.ini') + self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') + self.assertEqual(self.interact.local, { + 'app':self.bootstrap.app, 'root':self.bootstrap.root, + 'registry':self.bootstrap.registry, + 'request':self.bootstrap.request, + 'root_factory':self.bootstrap.root_factory, + 'm':model, + }) + self.assertTrue(self.bootstrap.closer.called) + self.assertTrue(self.interact.banner) def test_command_custom_section_override(self): command = self._makeOne() - interact = DummyInteractor() - app = Dummy() - loadapp = DummyLoadApp(app) - command.interact = (interact,) - command.loadapp = (loadapp,) - model = Dummy() - command.ConfigParser = makeDummyConfigParser([('app', model)]) - command.args = ('/foo/bar/myapp.ini#myapp',) - class Options(object): pass - command.options = Options() - command.options.disable_ipython = False - command.command(IPShell=None) - self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') - self.assertEqual(loadapp.section_name, 'myapp') - self.assertTrue(loadapp.relative_to) - self.assertEqual(interact.local, {'app':model}) - self.assertTrue(interact.banner) - - def test_command_generic_wsgi_app(self): - command = self._makeOne() - interact = DummyInteractor() - app = Dummy() - loadapp = DummyLoadApp(app) - command.interact = (interact,) - command.loadapp = (loadapp,) - command.ConfigParser = makeDummyConfigParser(None) - command.args = ('/foo/bar/myapp.ini#myapp',) - class Options(object): pass - command.options = Options() - command.options.disable_ipython = False + dummy = Dummy() + self.config_factory.items = [('app', dummy), ('root', dummy), + ('registry', dummy), ('request', dummy)] + command.options.disable_ipython = True command.command(IPShell=None) - self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') - self.assertEqual(loadapp.section_name, 'myapp') - self.assertTrue(loadapp.relative_to) - self.assertEqual(interact.local, {'app':app}) - self.assertTrue(interact.banner) + self.assertTrue(self.config_factory.parser) + self.assertEqual(self.config_factory.parser.filename, + '/foo/bar/myapp.ini') + self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') + self.assertEqual(self.interact.local, { + 'app':dummy, 'root':dummy, 'registry':dummy, 'request':dummy, + 'root_factory':self.bootstrap.root_factory, + }) + self.assertTrue(self.bootstrap.closer.called) + self.assertTrue(self.interact.banner) class TestPRoutesCommand(unittest.TestCase): def _getTargetClass(self): @@ -1015,22 +887,7 @@ class DummyLoadApp: class DummyApp: def __init__(self): self.registry = dummy_registry - self.threadlocal_manager = DummyThreadLocalManager() - - def root_factory(self, environ): - return dummy_root -class DummyThreadLocalManager: - def __init__(self): - self.pushed = [] - self.popped = [] - - def push(self, item): - self.pushed.append(item) - - def pop(self): - self.popped.append(True) - class DummyMapper(object): def __init__(self, *routes): self.routes = routes @@ -1073,15 +930,49 @@ class DummyMultiView(object): self.views = [(None, view, None) for view in views] self.__request_attrs__ = attrs -def makeDummyConfigParser(items): - class DummyConfigParser(object): - def read(self, filename): - self.filename = filename - - def items(self, section): - self.section = section - if items is None: - from ConfigParser import NoSectionError - raise NoSectionError, section - return items - return DummyConfigParser +class DummyConfigParser(object): + def __init__(self, result): + self.result = result + + def read(self, filename): + self.filename = filename + + def items(self, section): + self.section = section + if self.result is None: + from ConfigParser import NoSectionError + raise NoSectionError, section + return self.result + +class DummyConfigParserFactory(object): + items = None + + def __call__(self): + self.parser = DummyConfigParser(self.items) + return self.parser + +class DummyCloser(object): + def __call__(self): + self.called = True + +class DummyBootstrap(object): + def __init__(self, app=None, registry=None, request=None, root=None, + root_factory=None, closer=None): + self.app = app or DummyApp() + self.registry = registry or dummy_registry + self.request = request or DummyRequest({}) + self.root = root or dummy_root + self.root_factory = root_factory or Dummy() + self.closer = closer or DummyCloser() + + def __call__(self, *a, **kw): + self.a = a + self.kw = kw + return { + 'app': self.app, + 'registry': self.registry, + 'request': self.request, + 'root': self.root, + 'root_factory': self.root_factory, + 'closer': self.closer, + } diff --git a/pyramid/tests/test_scripting.py b/pyramid/tests/test_scripting.py index ccc6656df..f01e744b8 100644 --- a/pyramid/tests/test_scripting.py +++ b/pyramid/tests/test_scripting.py @@ -48,6 +48,10 @@ class Test_prepare(unittest.TestCase): self.manager = manager self.default = manager.get() + def test_it_no_valid_apps(self): + from pyramid.exceptions import ConfigurationError + self.assertRaises(ConfigurationError, self._callFUT) + def test_it_norequest(self): registry = self._makeRegistry() info = self._callFUT(registry=registry) @@ -85,10 +89,10 @@ class Test_prepare(unittest.TestCase): closer() self.assertEqual(self.default, self.manager.get()) -class TestMakeRequest(unittest.TestCase): +class Test__make_request(unittest.TestCase): def _callFUT(self, path='/', registry=None): - from pyramid.scripting import make_request - return make_request(path, registry) + from pyramid.scripting import _make_request + return _make_request(path, registry) def test_it_with_registry(self): request = self._callFUT('/', dummy_registry) diff --git a/pyramid/util.py b/pyramid/util.py index a4b69ed96..7fd1b0dc6 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -151,6 +151,17 @@ class WeakOrderedSet(object): The values may be iterated over or the last item added may be accessed via the ``last`` property. + + If items are added more than once, the most recent addition will + be remembered in the order: + + order = WeakOrderedSet() + order.add('1') + order.add('2') + order.add('1') + + list(order) == ['2', '1'] + order.last == '1' """ def __init__(self): @@ -161,6 +172,8 @@ class WeakOrderedSet(object): """ Add an item to the set.""" oid = id(item) if oid in self._items: + self._order.remove(oid) + self._order.append(oid) return ref = weakref.ref(item, lambda x: self.remove(item)) self._items[oid] = ref |
