Skip to content

Commit

Permalink
Merge branch 'master' into wsgi_show_tb_pr
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Howitz committed May 18, 2018
2 parents b54dde0 + e413288 commit 48bd880
Show file tree
Hide file tree
Showing 10 changed files with 179 additions and 9 deletions.
15 changes: 14 additions & 1 deletion CHANGES.rst
Expand Up @@ -11,6 +11,15 @@ https://github.com/zopefoundation/Zope/blob/4.0a6/CHANGES.rst
4.0b5 (unreleased)
------------------

New features
++++++++++++

- The `ProductContext` handed to a product's `initialize()` method
now has a `getApplication()` method which a product can use to,
e.g., add an object to the Application during startup (as used
by `Products.Sessions`).
(`#277 <https://github.com/zopefoundation/Zope/pull/277>`_)

Bugfixes
++++++++

Expand All @@ -21,9 +30,13 @@ Bugfixes

- Use log.warning to avoid deprecation warning for log.warn

- always raise InternalError when using WSGI
- Always raise InternalError when using WSGI
(`#280 <https://github.com/zopefoundation/Zope/pull/280>`)

- Accept bytes and text as cookie value.
(`#263 <https://github.com/zopefoundation/Zope/pull/263>`_)


4.0b4 (2018-04-23)
------------------

Expand Down
4 changes: 4 additions & 0 deletions src/App/ProductContext.py
Expand Up @@ -44,6 +44,7 @@ class ProductContext(object):

def __init__(self, product, app, package):
self.__prod = product
self.__app = app
self.__pack = package

def registerClass(self, instance_class=None, meta_type='',
Expand Down Expand Up @@ -213,6 +214,9 @@ class DummyHelp(object):
lastRegistered = None
return DummyHelp()

def getApplication(self):
return self.__app


class AttrDict(object):

Expand Down
6 changes: 3 additions & 3 deletions src/OFS/Application.py
Expand Up @@ -299,7 +299,7 @@ def install_root_view(self):
self.commit(u'Added default view for root object')

def install_products(self):
return install_products()
return install_products(self.getApp())

def install_standards(self):
app = self.getApp()
Expand Down Expand Up @@ -424,7 +424,7 @@ def install_product(app, product_dir, product_name, meta_types,
setattr(Application.misc_, product_name, misc_)

productObject = FactoryDispatcher.Product(product_name)
context = ProductContext(productObject, None, product)
context = ProductContext(productObject, app, product)

# Look for an 'initialize' method in the product.
initmethod = pgetattr(product, 'initialize', None)
Expand All @@ -439,7 +439,7 @@ def install_package(app, module, init_func, raise_exc=None):
product.package_name = name

if init_func is not None:
newContext = ProductContext(product, None, module)
newContext = ProductContext(product, app, module)
init_func(newContext)

package_initialized(module, init_func)
Expand Down
13 changes: 13 additions & 0 deletions src/OFS/tests/applicationproduct/__init__.py
@@ -0,0 +1,13 @@
""" This product uses the application during product initialization
to create a subfolder in the root folder. This is similar to what
Producs.Sessions and Products.TemporaryFolder are doing.
"""

def initialize(context):
from OFS.Folder import Folder
import transaction

app = context.getApplication()
folder = Folder('some_folder')
app._setObject('some_folder', folder)
transaction.commit()
21 changes: 21 additions & 0 deletions src/OFS/tests/testAppInitializer.py
Expand Up @@ -124,3 +124,24 @@ def test_install_root_view(self):
app = i.getApp()
self.assertTrue('index_html' in app)
self.assertEqual(app.index_html.meta_type, 'Page Template')

def test_install_products_which_need_the_application(self):
self.configure(good_cfg)
from Zope2.App import zcml
configure_zcml = '''
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five"
i18n_domain="foo">
<include package="Products.Five" file="meta.zcml" />
<five:registerPackage
package="OFS.tests.applicationproduct"
initialize="OFS.tests.applicationproduct.initialize"
/>
</configure>'''
zcml.load_string(configure_zcml)

i = self.getOne()
i.install_products()
app = i.getApp()
self.assertEqual(app.some_folder.meta_type, 'Folder')
25 changes: 20 additions & 5 deletions src/ZPublisher/HTTPResponse.py
Expand Up @@ -264,9 +264,15 @@ def setCookie(self, name, value, quoted=True, **kw):
This value overwrites any previously set value for the
cookie in the Response object.
`name` has to be text in Python 3.
`value` may be text or bytes. The default encoding of respective python
version is used.
"""
name = str(name)
value = str(value)
if PY2:
name = str(name)
value = str(value)

cookies = self.cookies
if name in cookies:
Expand All @@ -285,9 +291,15 @@ def appendCookie(self, name, value):
browsers with a key "name" and value "value". If a value for the
cookie has previously been set in the response object, the new
value is appended to the old one separated by a colon.
`name` has to be text in Python 3.
`value` may be text or bytes. The default encoding of respective python
version is used.
"""
name = str(name)
value = str(value)
if PY2:
name = str(name)
value = str(value)

cookies = self.cookies
if name in cookies:
Expand All @@ -309,8 +321,11 @@ def expireCookie(self, name, **kw):
to be specified - this path must exactly match the path given
when creating the cookie. The path can be specified as a keyword
argument.
`name` has to be text in Python 3.
"""
name = str(name)
if PY2:
name = str(name)

d = kw.copy()
if 'value' in d:
Expand Down
22 changes: 22 additions & 0 deletions src/ZPublisher/tests/testHTTPResponse.py
Expand Up @@ -340,6 +340,28 @@ def test_setCookie_unquoted(self):
self.assertEqual(len(cookie_list), 1)
self.assertEqual(cookie_list[0], ('Set-Cookie', 'foo=bar'))

def test_setCookie_handle_byte_values(self):
response = self._makeOne()
response.setCookie('foo', b'bar')
cookie = response.cookies.get('foo', None)
self.assertEqual(len(cookie), 2)
self.assertEqual(cookie.get('value'), b'bar')

cookie_list = response._cookie_list()
self.assertEqual(len(cookie_list), 1)
self.assertEqual(cookie_list[0], ('Set-Cookie', 'foo="bar"'))

def test_setCookie_handle_unicode_values(self):
response = self._makeOne()
response.setCookie('foo', u'bar')
cookie = response.cookies.get('foo', None)
self.assertEqual(len(cookie), 2)
self.assertEqual(cookie.get('value'), u'bar')

cookie_list = response._cookie_list()
self.assertEqual(len(cookie_list), 1)
self.assertEqual(cookie_list[0], ('Set-Cookie', 'foo="bar"'))

def test_appendCookie_w_existing(self):
response = self._makeOne()
response.setCookie('foo', 'bar', path='/')
Expand Down
3 changes: 3 additions & 0 deletions src/Zope2/Startup/datatypes.py
Expand Up @@ -18,6 +18,7 @@
import traceback

from six.moves import UserDict
from six import PY2, text_type

from ZODB.config import ZODBDatabase
from zope.deferredimport import deprecated
Expand Down Expand Up @@ -165,6 +166,8 @@ def getName(self):
def computeMountPaths(self):
mps = []
for part in self.config.mount_points:
if PY2 and isinstance(part, text_type):
part = part.encode()
real_root = None
if ':' in part:
# 'virtual_path:real_path'
Expand Down
70 changes: 70 additions & 0 deletions src/Zope2/Startup/tests/test_datatypes.py
@@ -0,0 +1,70 @@
##############################################################################
#
# Copyright (c) 2003 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################

import io
import shutil
import tempfile
import unittest

import ZConfig

from Zope2.Startup.options import ZopeWSGIOptions

_SCHEMA = None


def getSchema():
global _SCHEMA
if _SCHEMA is None:
opts = ZopeWSGIOptions()
opts.load_schema()
_SCHEMA = opts.schema
return _SCHEMA


class ZopeDatabaseTestCase(unittest.TestCase):

def setUp(self):
self.TEMPNAME = tempfile.mkdtemp()

def tearDown(self):
shutil.rmtree(self.TEMPNAME)

def load_config_text(self, text):
# We have to create a directory of our own since the existence
# of the directory is checked. This handles this in a
# platform-independent way.
text = text.replace("<<INSTANCE_HOME>>", self.TEMPNAME)
sio = io.StringIO(text)

conf, self.handler = ZConfig.loadConfigFile(getSchema(), sio)
self.assertEqual(conf.instancehome, self.TEMPNAME)
return conf

def test_parse_mount_points_as_native_strings(self):
conf = self.load_config_text(u"""
instancehome <<INSTANCE_HOME>>
<zodb_db main>
mount-point /test
<mappingstorage>
name mappingstorage
</mappingstorage>
</zodb_db>
""")
db = conf.databases[0]
self.assertEqual(u'main', db.name)
virtual_path = db.getVirtualMountPaths()[0]
self.assertEqual('/test', virtual_path)
self.assertIsInstance(virtual_path, str)
self.assertEqual([('/test', None, '/test')], db.computeMountPaths())
9 changes: 9 additions & 0 deletions src/Zope2/utilities/skel/etc/wsgi.conf.in
Expand Up @@ -8,3 +8,12 @@ instancehome $INSTANCE
</filestorage>
mount-point /
</zodb_db>

# Uncomment this if you use Products.Sessions and Products.TemporaryFolder
# <zodb_db temporary>
# <temporarystorage>
# name Temporary database (for sessions)
# </temporarystorage>
# mount-point /temp_folder
# container-class Products.TemporaryFolder.TemporaryContainer
# </zodb_db>

0 comments on commit 48bd880

Please sign in to comment.