summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2012-01-19 03:24:04 -0500
committerChris McDonough <chrism@plope.com>2012-01-19 03:24:04 -0500
commit077f3d1d0b28d9f565cc07a3ad11f4a8bb359054 (patch)
tree09ac1c9fb803fc89a557eb7283452e526614232e
parent4ef8a99d3260d99fa5522a0d26f1e7c1487c0265 (diff)
parent520676451b8b89177ad95cfcaa3f90484f5a6a18 (diff)
downloadpyramid-077f3d1d0b28d9f565cc07a3ad11f4a8bb359054.tar.gz
pyramid-077f3d1d0b28d9f565cc07a3ad11f4a8bb359054.tar.bz2
pyramid-077f3d1d0b28d9f565cc07a3ad11f4a8bb359054.zip
Merge branch '1.3-branch'
-rw-r--r--CHANGES.txt18
-rw-r--r--docs/api/request.rst14
-rw-r--r--docs/glossary.rst12
-rw-r--r--docs/narr/firstapp.rst39
-rw-r--r--docs/narr/project.rst71
-rw-r--r--docs/tutorials/wiki2/definingmodels.rst4
-rw-r--r--docs/whatsnew-1.3.rst15
-rw-r--r--pyramid/config/factories.py61
-rw-r--r--pyramid/interfaces.py4
-rw-r--r--pyramid/static.py31
-rw-r--r--pyramid/tests/test_config/test_factories.py75
-rw-r--r--pyramid/tests/test_static.py43
-rw-r--r--pyramid/util.py29
13 files changed, 331 insertions, 85 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 8950e75a2..fc7fc1cab 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,19 @@
+Unreleased
+==========
+
+Features
+--------
+
+- New API: ``pyramid.config.Configurator.set_request_property``. Add lazy
+ property descriptors to a request without changing the request factory.
+ This method provides conflict detection and is the suggested way to add
+ properties to a request.
+
+- Responses generated by Pyramid's ``static_view`` now use
+ a ``wsgi.file_wrapper`` (see
+ http://www.python.org/dev/peps/pep-0333/#optional-platform-specific-file-handling)
+ when one is provided by the web server.
+
1.3a5 (2012-01-09)
==================
@@ -25,7 +41,7 @@ Features
- New API: ``pyramid.request.Request.set_property``. Add lazy property
descriptors to a request without changing the request factory. New
properties may be reified, effectively caching the value for the lifetime
- of the instance. Common use-cases for this would be to get a database
+ of the instance. Common use-cases for this would be to get a database
connection for the request or identify the current user.
- Use the ``waitress`` WSGI server instead of ``wsgiref`` in scaffolding.
diff --git a/docs/api/request.rst b/docs/api/request.rst
index 9596e5621..1ab84e230 100644
--- a/docs/api/request.rst
+++ b/docs/api/request.rst
@@ -208,9 +208,7 @@
body associated with this request, this property will raise an
exception. See also :ref:`request_json_body`.
- .. method:: set_property(func, name=None, reify=False)
-
- .. versionadded:: 1.3
+ .. method:: set_property(callable, name=None, reify=False)
Add a callable or a property descriptor to the request instance.
@@ -225,15 +223,15 @@
cached. Thus the value of the property is only computed once for
the lifetime of the object.
- ``func`` can either be a callable that accepts the request as
+ ``callable`` can either be a callable that accepts the request as
its single positional parameter, or it can be a property
descriptor.
- If the ``func`` is a property descriptor a ``ValueError`` will
- be raised if ``name`` is ``None`` or ``reify`` is ``True``.
+ If the ``callable`` is a property descriptor a ``ValueError``
+ will be raised if ``name`` is ``None`` or ``reify`` is ``True``.
If ``name`` is None, the name of the property will be computed
- from the name of the ``func``.
+ from the name of the ``callable``.
.. code-block:: python
:linenos:
@@ -259,6 +257,8 @@
without having to subclass it, which can be useful for extension
authors.
+ .. versionadded:: 1.3
+
.. note::
For information about the API of a :term:`multidict` structure (such as
diff --git a/docs/glossary.rst b/docs/glossary.rst
index e4de15bd6..8307c0472 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -983,3 +983,15 @@ Glossary
:meth:`pyramid.path.AssetResolver.resolve` method. It supports the
methods and attributes documented in
:class:`pyramid.interfaces.IAssetDescriptor`.
+
+ Waitress
+ A :term:`WSGI` server that runs on UNIX and Windows under Python 2.6+
+ and Python 3.2+. Projects generated via Pyramid scaffolding use
+ Waitress as a WGSI server. See
+ http://docs.pylonsproject.org/projects/waitress/en/latest/ for detailed
+ information.
+
+ Green Unicorn
+ Aka ``gunicorn``, a fast :term:`WSGI` server that runs on UNIX under
+ Python 2.5+ (although at the time of this writing does not support
+ Python 3). See http://gunicorn.org/ for detailed information.
diff --git a/docs/narr/firstapp.rst b/docs/narr/firstapp.rst
index 922b0ea82..1ca188d7e 100644
--- a/docs/narr/firstapp.rst
+++ b/docs/narr/firstapp.rst
@@ -22,17 +22,33 @@ Here's one of the very simplest :app:`Pyramid` applications:
When this code is inserted into a Python script named ``helloworld.py`` and
executed by a Python interpreter which has the :app:`Pyramid` software
-installed, an HTTP server is started on TCP port 8080:
+installed, an HTTP server is started on TCP port 8080.
+
+On UNIX:
+
+.. code-block:: text
+
+ $ /path/to/your/virtualenv/bin/python helloworld.py
+
+On Windows:
.. code-block:: text
- $ python helloworld.py
- serving on 0.0.0.0:8080 view at http://127.0.0.1:8080
+ C:\> \path\to\your\virtualenv\Scripts\python.exe helloworld.py
+This command will not return and nothing will be printed to the console.
When port 8080 is visited by a browser on the URL ``/hello/world``, the
-server will simply serve up the text "Hello world!"
+server will simply serve up the text "Hello world!". If your application is
+running on your local system, using ``http://localhost:8080/hello/world``
+in a browser will show this result.
+
+Each time you visit a URL served by the application in a browser, a logging
+line will be emitted to the console displaying the hostname, the date, the
+request method and path, and some additional information. This output is
+done by the wsgiref server we've used to serve this application. It logs an
+"access log" in Apache combined logging format to the console.
-Press ``Ctrl-C`` to stop the application.
+Press ``Ctrl-C`` (or ``Ctrl-Break`` on Windows) to stop the application.
Now that we have a rudimentary understanding of what the application does,
let's examine it piece-by-piece.
@@ -210,16 +226,15 @@ which means "listen on all TCP interfaces." By default, the HTTP server
listens only on the ``127.0.0.1`` interface, which is problematic if you're
running the server on a remote system and you wish to access it with a web
browser from a local system. We also specify a TCP port number to listen on,
-which is 8080, passing it as the second argument. The final argument ios ,
-passing it the ``app`` object (a :term:`router`), which is the the
-application we wish to serve. Finally, we call the server's
-``serve_forever`` method, which starts the main loop in which it will wait
-for requests from the outside world.
+which is 8080, passing it as the second argument. The final argument is the
+``app`` object (a :term:`router`), which is the the application we wish to
+serve. Finally, we call the server's ``serve_forever`` method, which starts
+the main loop in which it will wait for requests from the outside world.
When this line is invoked, it causes the server to start listening on TCP
port 8080. The server will serve requests forever, or at least until we stop
-it by killing the process which runs it (usually by pressing ``Ctrl-C`` in
-the terminal we used to start it).
+it by killing the process which runs it (usually by pressing ``Ctrl-C``
+or ``Ctrl-Break`` in the terminal we used to start it).
Conclusion
~~~~~~~~~~
diff --git a/docs/narr/project.rst b/docs/narr/project.rst
index 896b65249..5696b0b73 100644
--- a/docs/narr/project.rst
+++ b/docs/narr/project.rst
@@ -881,37 +881,54 @@ configuration as would be loaded if you were running your Pyramid application
via ``pserve``. This can be a useful debugging tool. See
:ref:`interactive_shell` for more details.
-.. _alternate_wsgi_server:
+What Is This ``pserve`` Thing
+-----------------------------
-Using an Alternate WSGI Server
-------------------------------
-
-The code generated by :app:`Pyramid` scaffolding assumes that you will be
+The code generated by an :app:`Pyramid` scaffold assumes that you will be
using the ``pserve`` command to start your application while you do
-development. The default rendering of Pyramid scaffolding uses the
-*waitress* WSGI server, which is a server that is suited for production
-usage. It's not very fast, or very featureful: its main feature is that it
-works on all platforms and all systems, making it a good choice as a default
-server from the perspective of Pyramid's developers.
+development. ``pserve`` is a command that reads a :term:`PasteDeploy`
+``.ini`` file (e.g. ``development.ini``) and configures a server to serve a
+Pyramid application based on the data in the file.
``pserve`` is by no means the only way to start up and serve a :app:`Pyramid`
application. As we saw in :ref:`firstapp_chapter`, ``pserve`` needn't be
invoked at all to run a :app:`Pyramid` application. The use of ``pserve`` to
run a :app:`Pyramid` application is purely conventional based on the output
-of its scaffold.
-
-Any :term:`WSGI` server is capable of running a :app:`Pyramid` application.
-Some WSGI servers don't require the :term:`PasteDeploy` framework's
-``pserve`` command to do server process management at all. Each :term:`WSGI`
-server has its own documentation about how it creates a process to run an
-application, and there are many of them, so we cannot provide the details for
-each here. But the concepts are largely the same, whatever server you happen
-to use.
-
-One popular production alternative to a ``pserve``-invoked server is
-:term:`mod_wsgi`. You can also use :term:`mod_wsgi` to serve your
-:app:`Pyramid` application using the Apache web server rather than any
-"pure-Python" server that is started as a result of ``pserve``. See
-:ref:`modwsgi_tutorial` for details. However, it is usually easier to
-*develop* an application using a ``pserve`` -invoked webserver, as
-exception and debugging output will be sent to the console.
+of its scaffolding. But we strongly recommend using while developing your
+application, because many other convenience introspection commands (such as
+``pviews``, ``prequest``, ``proutes`` and others) are also implemented in
+terms of configuration availaibility of this ``.ini`` file format. It also
+configures Pyramid logging and provides the ``--reload`` switch for
+convenient restarting of the server when code changes.
+
+.. _alternate_wsgi_server:
+
+Using an Alternate WSGI Server
+------------------------------
+
+Pyramid scaffolds generate projects which use the :term:`Waitress` WSGI
+server. Waitress is a server that is suited for development and light
+production usage. It's not the fastest nor the most featureful WSGI server.
+Instead, its main feature is that it works on all platforms that Pyramid
+needs to run on, making it a good choice as a default server from the
+perspective of Pyramid's developers.
+
+Any WSGI server is capable of running a :app:`Pyramid` application. But we
+suggest you stick with the default server for development, and that you wait
+to investigate other server options until you're ready to deploy your
+application to production. Unless for some reason you need to develop on a
+non-local system, investigating alternate server options is usually a
+distraction until you're ready to deploy. But we recommend developing using
+the default configuration on a local system that you have complete control
+over; it will provide the best development experience.
+
+One popular production alternative to the default Waitress server is
+:term:`mod_wsgi`. You can use mod_wsgi to serve your :app:`Pyramid`
+application using the Apache web server rather than any "pure-Python" server
+like Waitress. It is fast and featureful. See :ref:`modwsgi_tutorial` for
+details.
+
+Another good production alternative is :term:`Green Unicorn` (aka
+``gunicorn``). It's faster than Waitress and slightly easier to configure
+than mod_wsgi, although it depends, in its default configuration, on having a
+buffering HTTP proxy in front of it.
diff --git a/docs/tutorials/wiki2/definingmodels.rst b/docs/tutorials/wiki2/definingmodels.rst
index cd295e993..2b8932a98 100644
--- a/docs/tutorials/wiki2/definingmodels.rst
+++ b/docs/tutorials/wiki2/definingmodels.rst
@@ -63,10 +63,10 @@ script. In particular, we'll replace our import of ``MyModel`` with one of
``Page`` and we'll change the very end of the script to create a ``Page``
rather than a ``MyModel`` and add it to our ``DBSession``.
-The result of all of our edits to ``models.py`` will end up looking
+The result of all of our edits to ``populate.py`` will end up looking
something like this:
-.. literalinclude:: src/models/tutorial/models.py
+.. literalinclude:: src/models/tutorial/scripts/populate.py
:linenos:
:language: python
diff --git a/docs/whatsnew-1.3.rst b/docs/whatsnew-1.3.rst
index eb8617ff1..ee4e2ccb5 100644
--- a/docs/whatsnew-1.3.rst
+++ b/docs/whatsnew-1.3.rst
@@ -198,11 +198,16 @@ Extending a Request without Subclassing
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is now possible to extend a :class:`pyramid.request.Request` object
-with property descriptors without having to create a subclass via
-:meth:`pyramid.request.Request.set_property`. New properties may be
-reified, effectively caching the value for the lifetime of the instance.
-Common use-cases for this would be to get a database connection for the
-request or identify the current user.
+with property descriptors without having to create a custom request factory.
+The new method :meth:`pyramid.config.Configurator.set_request_property`
+provides an entry point for addons to register properties which will be
+added to each request. New properties may be reified, effectively caching
+the return value for the lifetime of the instance. Common use-cases for this
+would be to get a database connection for the request or identify the current
+user. The new method :meth:`pyramid.request.Request.set_property` has been
+added, as well, but the configurator method should be preferred as it
+provides conflict detection and consistency in the lifetime of the
+properties.
Minor Feature Additions
-----------------------
diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py
index 530b6cc28..eb4442e98 100644
--- a/pyramid/config/factories.py
+++ b/pyramid/config/factories.py
@@ -2,7 +2,9 @@ from pyramid.config.util import action_method
from pyramid.interfaces import (
IDefaultRootFactory,
+ INewRequest,
IRequestFactory,
+ IRequestProperties,
IRootFactory,
ISessionFactory,
)
@@ -70,6 +72,10 @@ class FactoriesConfiguratorMixin(object):
:class:`pyramid.request.Request` class (particularly
``__call__``, and ``blank``).
+ See :meth:`pyramid.config.Configurator.set_request_property`
+ for a less intrusive way to extend the request objects with
+ custom properties.
+
.. note::
Using the ``request_factory`` argument to the
@@ -85,3 +91,58 @@ class FactoriesConfiguratorMixin(object):
intr['factory'] = factory
self.action(IRequestFactory, register, introspectables=(intr,))
+ @action_method
+ def set_request_property(self, callable, name=None, reify=False):
+ """ Add a property to the request object.
+
+ ``callable`` can either be a callable that accepts the request
+ as its single positional parameter, or it can be a property
+ descriptor. It may also be a :term:`dotted Python name` which
+ refers to either a callable or a property descriptor.
+
+ If the ``callable`` is a property descriptor a ``ValueError``
+ will be raised if ``name`` is ``None`` or ``reify`` is ``True``.
+
+ If ``name`` is None, the name of the property will be computed
+ from the name of the ``callable``.
+
+ See :meth:`pyramid.request.Request.set_property` for more
+ information on its usage.
+
+ This is the recommended method for extending the request object
+ and should be used in favor of providing a custom request
+ factory via
+ :meth:`pyramid.config.Configurator.set_request_factory`.
+
+ .. versionadded:: 1.3
+ """
+ callable = self.maybe_dotted(callable)
+
+ if name is None:
+ name = callable.__name__
+
+ def register():
+ plist = self.registry.queryUtility(IRequestProperties)
+
+ if plist is None:
+ plist = []
+ self.registry.registerUtility(plist, IRequestProperties)
+ self.registry.registerHandler(_set_request_properties,
+ (INewRequest,))
+
+ plist.append((name, callable, reify))
+
+ intr = self.introspectable('request properties', name,
+ self.object_description(callable),
+ 'request property')
+ intr['callable'] = callable
+ intr['reify'] = reify
+ self.action(('request properties', name), register,
+ introspectables=(intr,))
+
+def _set_request_properties(event):
+ request = event.request
+ plist = request.registry.queryUtility(IRequestProperties)
+ for prop in plist:
+ name, callable, reify = prop
+ request.set_property(callable, name=name, reify=reify)
diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py
index 6762d788d..8de5331b9 100644
--- a/pyramid/interfaces.py
+++ b/pyramid/interfaces.py
@@ -511,6 +511,10 @@ class IRequestHandler(Interface):
IRequest.combined = IRequest # for exception view lookups
+class IRequestProperties(Interface):
+ """ Marker interface for storing a list of request properties which
+ will be added to the request object."""
+
class IRouteRequest(Interface):
""" *internal only* interface used as in a utility lookup to find
route-specific interfaces. Not an API."""
diff --git a/pyramid/static.py b/pyramid/static.py
index 61ee67573..8788d016d 100644
--- a/pyramid/static.py
+++ b/pyramid/static.py
@@ -47,11 +47,13 @@ def init_mimetypes(mimetypes):
# has been applied on the Python 2 trunk).
init_mimetypes(mimetypes)
+_BLOCK_SIZE = 4096 * 64 # 256K
+
class _FileResponse(Response):
"""
Serves a static filelike object.
"""
- def __init__(self, path, cache_max_age):
+ def __init__(self, path, cache_max_age, request):
super(_FileResponse, self).__init__(conditional_response=True)
self.last_modified = getmtime(path)
content_type, content_encoding = mimetypes.guess_type(path,
@@ -61,32 +63,31 @@ class _FileResponse(Response):
self.content_type = content_type
self.content_encoding = content_encoding
content_length = getsize(path)
- self.app_iter = _FileIter(open(path, 'rb'), content_length)
+ f = open(path, 'rb')
+ environ = request.environ
+ if 'wsgi.file_wrapper' in environ:
+ app_iter = environ['wsgi.file_wrapper'](f, _BLOCK_SIZE)
+ else:
+ app_iter = _FileIter(open(path, 'rb'), _BLOCK_SIZE)
+ self.app_iter = app_iter
# assignment of content_length must come after assignment of app_iter
self.content_length = content_length
if cache_max_age is not None:
self.cache_expires = cache_max_age
class _FileIter(object):
- block_size = 4096 * 64 # (256K)
-
- def __init__(self, file, size=None):
+ def __init__(self, file, block_size):
self.file = file
- self.size = size
+ self.block_size = block_size
def __iter__(self):
return self
def next(self):
- chunk_size = self.block_size
- if self.size is not None:
- if chunk_size > self.size:
- chunk_size = self.size
- self.size -= chunk_size
- data = self.file.read(chunk_size)
- if not data:
+ val = self.file.read(self.block_size)
+ if not val:
raise StopIteration
- return data
+ return val
__next__ = next # py3
@@ -186,7 +187,7 @@ class static_view(object):
if not exists(filepath):
return HTTPNotFound(request.url)
- return _FileResponse(filepath ,self.cache_max_age)
+ return _FileResponse(filepath ,self.cache_max_age, request)
def add_slash_redirect(self, request):
url = request.path_url + '/'
diff --git a/pyramid/tests/test_config/test_factories.py b/pyramid/tests/test_config/test_factories.py
index 9cd13a435..d1a01568f 100644
--- a/pyramid/tests/test_config/test_factories.py
+++ b/pyramid/tests/test_config/test_factories.py
@@ -40,7 +40,7 @@ class TestFactoriesMixin(unittest.TestCase):
config.commit()
self.assertEqual(config.registry.getUtility(IRootFactory),
DefaultRootFactory)
-
+
def test_set_root_factory_dottedname(self):
from pyramid.interfaces import IRootFactory
config = self._makeOne()
@@ -48,7 +48,7 @@ class TestFactoriesMixin(unittest.TestCase):
self.assertEqual(config.registry.queryUtility(IRootFactory), None)
config.commit()
self.assertEqual(config.registry.getUtility(IRootFactory), dummyfactory)
-
+
def test_set_session_factory(self):
from pyramid.interfaces import ISessionFactory
config = self._makeOne()
@@ -67,4 +67,75 @@ class TestFactoriesMixin(unittest.TestCase):
self.assertEqual(config.registry.getUtility(ISessionFactory),
dummyfactory)
+ def test_set_request_property_with_callable(self):
+ from pyramid.interfaces import IRequestProperties
+ config = self._makeOne(autocommit=True)
+ callable = lambda x: None
+ config.set_request_property(callable, name='foo')
+ plist = config.registry.getUtility(IRequestProperties)
+ self.assertEqual(plist, [('foo', callable, False)])
+
+ def test_set_request_property_with_unnamed_callable(self):
+ from pyramid.interfaces import IRequestProperties
+ config = self._makeOne(autocommit=True)
+ def foo(self): pass
+ config.set_request_property(foo, reify=True)
+ plist = config.registry.getUtility(IRequestProperties)
+ self.assertEqual(plist, [('foo', foo, True)])
+
+ def test_set_request_property_with_property(self):
+ from pyramid.interfaces import IRequestProperties
+ config = self._makeOne(autocommit=True)
+ callable = property(lambda x: None)
+ config.set_request_property(callable, name='foo')
+ plist = config.registry.getUtility(IRequestProperties)
+ self.assertEqual(plist, [('foo', callable, False)])
+
+ def test_set_multiple_request_properties(self):
+ from pyramid.interfaces import IRequestProperties
+ config = self._makeOne()
+ def foo(self): pass
+ bar = property(lambda x: None)
+ config.set_request_property(foo, reify=True)
+ config.set_request_property(bar, name='bar')
+ config.commit()
+ plist = config.registry.getUtility(IRequestProperties)
+ self.assertEqual(plist, [('foo', foo, True),
+ ('bar', bar, False)])
+
+ def test_set_multiple_request_properties_conflict(self):
+ from pyramid.exceptions import ConfigurationConflictError
+ config = self._makeOne()
+ def foo(self): pass
+ bar = property(lambda x: None)
+ config.set_request_property(foo, name='bar', reify=True)
+ config.set_request_property(bar, name='bar')
+ self.assertRaises(ConfigurationConflictError, config.commit)
+
+ def test_set_request_property_subscriber(self):
+ from zope.interface import implementer
+ from pyramid.interfaces import INewRequest
+ config = self._makeOne()
+ def foo(r): pass
+ config.set_request_property(foo, name='foo')
+ config.set_request_property(foo, name='bar', reify=True)
+ config.commit()
+ @implementer(INewRequest)
+ class Event(object):
+ request = DummyRequest(config.registry)
+ event = Event()
+ config.registry.notify(event)
+ callables = event.request.callables
+ self.assertEqual(callables, [('foo', foo, False),
+ ('bar', foo, True)])
+
+class DummyRequest(object):
+ callables = None
+
+ def __init__(self, registry):
+ self.registry = registry
+ def set_property(self, callable, name, reify):
+ if self.callables is None:
+ self.callables = []
+ self.callables.append((name, callable, reify))
diff --git a/pyramid/tests/test_static.py b/pyramid/tests/test_static.py
index bd2c2adef..3d6fbe893 100644
--- a/pyramid/tests/test_static.py
+++ b/pyramid/tests/test_static.py
@@ -1,5 +1,6 @@
import datetime
import unittest
+import io
# 5 years from now (more or less)
fiveyrsfuture = datetime.datetime.utcnow() + datetime.timedelta(5*365)
@@ -112,6 +113,22 @@ class Test_static_view_use_subpath_False(unittest.TestCase):
response = inst(context, request)
self.assertTrue(b'<html>static</html>' in response.body)
+ def test_resource_is_file_with_wsgi_file_wrapper(self):
+ from pyramid.static import _BLOCK_SIZE
+ inst = self._makeOne('pyramid.tests:fixtures/static')
+ request = self._makeRequest({'PATH_INFO':'/index.html'})
+ class _Wrapper(object):
+ def __init__(self, file, block_size=None):
+ self.file = file
+ self.block_size = block_size
+ request.environ['wsgi.file_wrapper'] = _Wrapper
+ context = DummyContext()
+ response = inst(context, request)
+ app_iter = response.app_iter
+ self.assertTrue(isinstance(app_iter, _Wrapper))
+ self.assertTrue(b'<html>static</html>' in app_iter.file.read())
+ self.assertEqual(app_iter.block_size, _BLOCK_SIZE)
+
def test_resource_is_file_with_cache_max_age(self):
inst = self._makeOne('pyramid.tests:fixtures/static', cache_max_age=600)
request = self._makeRequest({'PATH_INFO':'/index.html'})
@@ -366,6 +383,32 @@ class Test_patch_mimetypes(unittest.TestCase):
result = self._callFUT(module)
self.assertEqual(result, False)
+class Test_FileIter(unittest.TestCase):
+ def _makeOne(self, file, block_size):
+ from pyramid.static import _FileIter
+ return _FileIter(file, block_size)
+
+ def test___iter__(self):
+ f = io.BytesIO(b'abc')
+ inst = self._makeOne(f, 1)
+ self.assertEqual(inst.__iter__(), inst)
+
+ def test_iteration(self):
+ data = b'abcdef'
+ f = io.BytesIO(b'abcdef')
+ inst = self._makeOne(f, 1)
+ r = b''
+ for x in inst:
+ self.assertEqual(len(x), 1)
+ r+=x
+ self.assertEqual(r, data)
+
+ def test_close(self):
+ f = io.BytesIO(b'abc')
+ inst = self._makeOne(f, 1)
+ inst.close()
+ self.assertTrue(f.closed)
+
class DummyContext:
pass
diff --git a/pyramid/util.py b/pyramid/util.py
index 852689c4d..cca1872b7 100644
--- a/pyramid/util.py
+++ b/pyramid/util.py
@@ -20,7 +20,7 @@ class InstancePropertyMixin(object):
on the class itself.
"""
- def set_property(self, func, name=None, reify=False):
+ def set_property(self, callable, name=None, reify=False):
""" Add a callable or a property descriptor to the instance.
Properties, unlike attributes, are lazily evaluated by executing
@@ -34,17 +34,18 @@ class InstancePropertyMixin(object):
cached. Thus the value of the property is only computed once for
the lifetime of the object.
- ``func`` can either be a callable that accepts the instance as
+ ``callable`` can either be a callable that accepts the instance
+ as
its single positional parameter, or it can be a property
descriptor.
- If the ``func`` is a property descriptor, the ``name`` parameter
- must be supplied or a ``ValueError`` will be raised. Also note
- that a property descriptor cannot be reified, so ``reify`` must
- be ``False``.
+ If the ``callable`` is a property descriptor, the ``name``
+ parameter must be supplied or a ``ValueError`` will be raised.
+ Also note that a property descriptor cannot be reified, so
+ ``reify`` must be ``False``.
If ``name`` is None, the name of the property will be computed
- from the name of the ``func``.
+ from the name of the ``callable``.
.. code-block:: python
:linenos:
@@ -73,20 +74,20 @@ class InstancePropertyMixin(object):
1
"""
- is_property = isinstance(func, property)
+ is_property = isinstance(callable, property)
if is_property:
- fn = func
+ fn = callable
if name is None:
raise ValueError('must specify "name" for a property')
if reify:
raise ValueError('cannot reify a property')
elif name is not None:
- fn = lambda this: func(this)
+ fn = lambda this: callable(this)
fn.__name__ = name
- fn.__doc__ = func.__doc__
+ fn.__doc__ = callable.__doc__
else:
- name = func.__name__
- fn = func
+ name = callable.__name__
+ fn = callable
if reify:
import pyramid.decorator
fn = pyramid.decorator.reify(fn)
@@ -234,7 +235,7 @@ def object_description(object):
return text_('method %s of class %s.%s' %
(object.__name__, modulename,
oself.__class__.__name__))
-
+
if inspect.isclass(object):
dottedname = '%s.%s' % (modulename, object.__name__)
return text_('class %s' % dottedname)