Skip to content

Commit

Permalink
Py2/3: Unify handling of StringIO
Browse files Browse the repository at this point in the history
This helps with our coverage numbers.

Under Python 2, we also start using the C implementation, and avoid
some unnecessary encode/decode cycles, so we might be a tiny bit
faster.
  • Loading branch information
jamadden committed Feb 11, 2017
1 parent e3b8227 commit e2906ca
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 84 deletions.
11 changes: 4 additions & 7 deletions ZConfig/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,7 @@
from ZConfig.loader import loadConfig, loadConfigFile
from ZConfig.loader import loadSchema, loadSchemaFile

try:
import StringIO as StringIO
except ImportError:
# Python 3 support.
import io as StringIO
from ZConfig._compat import TextIO


class ConfigurationError(Exception):
Expand Down Expand Up @@ -218,11 +214,12 @@ def __init__(self, source, name, url=None, lineno=None):

def configureLoggers(text):
"""Configure one or more loggers from configuration text."""
schema = loadSchemaFile(StringIO.StringIO("""
schema = loadSchemaFile(TextIO("""
<schema>
<import package='ZConfig.components.logger'/>
<multisection type='logger' name='*' attribute='loggers'/>
</schema>
"""))
for factory in loadConfigFile(schema, StringIO.StringIO(text))[0].loggers:

for factory in loadConfigFile(schema, TextIO(text))[0].loggers:
factory()
68 changes: 68 additions & 0 deletions ZConfig/_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
##############################################################################
#
# Copyright (c) 2016 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 sys

PY3 = sys.version_info[0] >= 3

# Native string object IO
if str is not bytes:
from io import StringIO as _NStringIO
else:
# Python 2
from io import BytesIO as _NStringIO

NStringIO = _NStringIO

from io import StringIO
from io import BytesIO

def TextIO(text):
"Return StringIO or BytesIO as appropriate"
return BytesIO(text) if isinstance(text, bytes) else StringIO(text)

try:
import urllib2
except ImportError:
# Python 3 support.
import urllib.request as urllib2

urllib2 = urllib2

if PY3: # pragma: no cover
import builtins
exec_ = getattr(builtins, "exec")


def reraise(tp, value, tb=None): #pragma NO COVER
if value.__traceback__ is not tb:
raise value.with_traceback(tb)
raise value

else: # pragma: no cover
def exec_(code, globs=None, locs=None): #pragma NO COVER
"""Execute code in a namespace."""
if globs is None:
frame = sys._getframe(1)
globs = frame.f_globals
if locs is None:
locs = frame.f_locals
del frame
elif locs is None:
locs = globs
exec("""exec code in globs, locs""")

exec_("""def reraise(tp, value, tb=None):
raise tp, value, tb
""")
16 changes: 6 additions & 10 deletions ZConfig/components/logger/tests/test_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,7 @@
from ZConfig.components.logger import handlers
from ZConfig.components.logger import loghandler

try:
import StringIO as StringIO
except ImportError:
# Python 3 support.
import io as StringIO
from ZConfig._compat import NStringIO as StringIO

class CustomFormatter(logging.Formatter):
def formatException(self, ei):
Expand All @@ -40,7 +36,7 @@ def formatException(self, ei):
This adds helpful advice to the end of the traceback.
"""
import traceback
sio = StringIO.StringIO()
sio = StringIO()
traceback.print_exception(ei[0], ei[1], ei[2], file=sio)
return sio.getvalue() + "... Don't panic!"

Expand Down Expand Up @@ -101,13 +97,13 @@ def move(self, fn):

def get_schema(self):
if self._schema is None:
sio = StringIO.StringIO(self._schematext)
sio = StringIO(self._schematext)
self.__class__._schema = ZConfig.loadSchemaFile(sio)
return self._schema

def get_config(self, text):
conf, handler = ZConfig.loadConfigFile(self.get_schema(),
StringIO.StringIO(text))
StringIO(text))
self.assertTrue(not handler)
return conf

Expand Down Expand Up @@ -268,7 +264,7 @@ def check_standard_stream(self, name):
# The factory has already been created; make sure it picks up
# the stderr we set here when we create the logger and
# handlers:
sio = StringIO.StringIO()
sio = StringIO()
setattr(sys, name, sio)
try:
logger = conf.eventlog()
Expand All @@ -288,7 +284,7 @@ def test_custom_formatter(self):
</logfile>
</eventlog>
""")
sio = StringIO.StringIO()
sio = StringIO()
sys.stdout = sio
try:
logger = conf.eventlog()
Expand Down
55 changes: 32 additions & 23 deletions ZConfig/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import re
import sys

from io import StringIO

import ZConfig
import ZConfig.cfgparser
import ZConfig.datatypes
Expand All @@ -25,17 +27,8 @@
import ZConfig.schema
import ZConfig.url

try:
import StringIO as StringIO
except ImportError:
# Python 3 support.
import io as StringIO

try:
import urllib2
except ImportError:
# Python 3 support
import urllib.request as urllib2
from ZConfig._compat import reraise
from ZConfig._compat import urllib2

try:
from urllib import pathname2url
Expand Down Expand Up @@ -229,15 +222,18 @@ def openResource(self, url):
# Python 2.1 raises a different error from Python 2.2+,
# so we catch both to make sure we detect the situation.
self._raise_open_error(url, str(e))
if sys.version_info[0] >= 3:
# Python 3 support: file.read() returns bytes, so we convert it
# to an StringIO. (Can't use io.TextIOWrapper because of
# http://bugs.python.org/issue16723 and probably other bugs)
try:
data = file.read().decode()
finally:
file.close()
file = StringIO.StringIO(data)

# Python 3 support: file.read() returns bytes, so we convert it
# to an StringIO. (Can't use io.TextIOWrapper because of
# http://bugs.python.org/issue16723 and probably other bugs).
# Do this even on Python 2 to avoid keeping a network connection
# open for an unbounded amount of time and to catch IOErrors here,
# where they make sense.
try:
data = file.read().decode()
finally:
file.close()
file = StringIO(data)
return self.createResource(file, url)

def _raise_open_error(self, url, message):
Expand Down Expand Up @@ -313,13 +309,26 @@ def openPackageResource(package, path):
url = ZConfig.url.urlnormalize(url)
return urllib2.urlopen(url)
else:
v, tb = (None, None)
for dir in pkg.__path__:
loadpath = os.path.join(dir, path)
try:
return StringIO.StringIO(
return StringIO(
loader.get_data(loadpath).decode('utf-8'))
except Exception:
pass
except Exception as e:
v = ZConfig.SchemaResourceError(
"error opening schema component: " + repr(e),
filename=path,
package=package,
path=pkg.__path__)
tb = sys.exc_info()[2]

if v is not None:
try:
reraise(type(v), v, tb)
finally:
del tb

raise ZConfig.SchemaResourceError("schema component not found",
filename=path,
package=package,
Expand Down
16 changes: 6 additions & 10 deletions ZConfig/schemaless.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,8 @@ open for reading. Let's take a look at this, and what it returns::
...
... '''

>>> try:
... import StringIO
... except ImportError:
... import io as StringIO

>>> config = schemaless.loadConfigFile(StringIO.StringIO(config_text))
>>> from ZConfig._compat import NStringIO as StringIO
>>> config = schemaless.loadConfigFile(StringIO(config_text))

The `config` object is a mapping from top-level keys to lists of
values::
Expand Down Expand Up @@ -200,7 +196,7 @@ other elements of a configuration are::
...
... '''

>>> config = schemaless.loadConfigFile(StringIO.StringIO(config_text))
>>> config = schemaless.loadConfigFile(StringIO(config_text))

>>> print(config)
%import some.package
Expand Down Expand Up @@ -231,7 +227,7 @@ Multiple imports of the same name are removed::
...
... '''

>>> config = schemaless.loadConfigFile(StringIO.StringIO(config_text))
>>> config = schemaless.loadConfigFile(StringIO(config_text))

>>> print(config)
%import some.package
Expand Down Expand Up @@ -280,7 +276,7 @@ raised when loading the configuration::
...
... '''

>>> schemaless.loadConfigFile(StringIO.StringIO(config_text))
>>> schemaless.loadConfigFile(StringIO(config_text))
Traceback (most recent call last):
NotImplementedError: defines are not supported

Expand All @@ -292,6 +288,6 @@ A similar exception is raised for %include::
...
... '''

>>> schemaless.loadConfigFile(StringIO.StringIO(config_text))
>>> schemaless.loadConfigFile(StringIO(config_text))
Traceback (most recent call last):
NotImplementedError: includes are not supported
10 changes: 3 additions & 7 deletions ZConfig/tests/support.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,7 @@
from ZConfig.loader import ConfigLoader
from ZConfig.url import urljoin

try:
import StringIO
except ImportError:
# Python 3 support.
import io as StringIO
from ZConfig._compat import NStringIO as StringIO

try:
from urllib import pathname2url
Expand Down Expand Up @@ -61,7 +57,7 @@ def load_schema(self, relurl):
return self.schema

def load_schema_text(self, text, url=None):
sio = StringIO.StringIO(text)
sio = StringIO(text)
self.schema = ZConfig.loadSchemaFile(sio, url)
return self.schema

Expand All @@ -73,7 +69,7 @@ def load_config(self, schema, conf_url, num_handlers=0):
return self.conf

def load_config_text(self, schema, text, num_handlers=0, url=None):
sio = StringIO.StringIO(text)
sio = StringIO(text)
loader = self.create_config_loader(schema)
self.conf, self.handlers = loader.loadFile(sio, url)
self.assertEqual(len(self.handlers), num_handlers)
Expand Down
7 changes: 1 addition & 6 deletions ZConfig/tests/test_cfgimports.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,7 @@
import ZConfig
import ZConfig.tests.support

try:
from StringIO import StringIO
except ImportError:
# Python 3 support.
from io import StringIO

from ZConfig._compat import NStringIO as StringIO

class TestImportFromConfiguration(
ZConfig.tests.support.TestHelper, unittest.TestCase):
Expand Down
14 changes: 5 additions & 9 deletions ZConfig/tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@

from ZConfig.tests.support import CONFIG_BASE

try:
import StringIO as StringIO
except ImportError:
# Python 3 support.
import io as StringIO
from ZConfig._compat import NStringIO as StringIO

class ConfigurationTestCase(unittest.TestCase):

Expand All @@ -47,7 +43,7 @@ def load(self, relurl, context=None):
return conf

def loadtext(self, text):
sio = StringIO.StringIO(text)
sio = StringIO(text)
return self.loadfile(sio)

def loadfile(self, file):
Expand Down Expand Up @@ -112,7 +108,7 @@ def test_include(self):
self.assertEqual(conf.var4, "value")

def test_includes_with_defines(self):
self.schema = ZConfig.loadSchemaFile(StringIO.StringIO("""\
self.schema = ZConfig.loadSchemaFile(StringIO("""\
<schema>
<key name='refinner' />
<key name='refouter' />
Expand Down Expand Up @@ -145,8 +141,8 @@ def test_fragment_ident_disallowed(self):
self.load, "simplesections.conf#another")

def test_load_from_fileobj(self):
sio = StringIO.StringIO("%define name value\n"
"getname x $name y \n")
sio = StringIO("%define name value\n"
"getname x $name y \n")
cf = self.loadfile(sio)
self.assertEqual(cf.getname, "x value y")

Expand Down
15 changes: 3 additions & 12 deletions ZConfig/tests/test_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,10 @@
import ZConfig.loader
import ZConfig.url

from ZConfig.tests.support import CONFIG_BASE, TestHelper

try:
import urllib2
except ImportError:
# Python 3 support.
import urllib.request as urllib2
from ZConfig._compat import NStringIO as StringIO
from ZConfig._compat import urllib2

try:
from StringIO import StringIO
except ImportError:
# Python 3 support.
from io import StringIO
from ZConfig.tests.support import CONFIG_BASE, TestHelper


try:
Expand Down

0 comments on commit e2906ca

Please sign in to comment.