From 308233abea250b173492de4b64d1ac7ac0d9d1eb Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Thu, 21 Nov 2013 18:46:35 -0500 Subject: [PATCH] Fixes to Py3k in-place compatibity regarding output encoding and related; the use of the new io.* package introduced some incompatibilities on Py2k. These should be resolved, due to the introduction of new adapter types for translating from io.* to Py2k file types, StringIO types. Thanks to Javier Santacruz for help with this. --- alembic/compat.py | 49 +++++++++++++++++++++++++++---- alembic/migration.py | 12 ++++---- docs/build/changelog.rst | 10 +++++++ tests/test_offline_environment.py | 3 -- 4 files changed, 60 insertions(+), 14 deletions(-) diff --git a/alembic/compat.py b/alembic/compat.py index fc88933a..5cfdaccd 100644 --- a/alembic/compat.py +++ b/alembic/compat.py @@ -80,9 +80,48 @@ def with_metaclass(meta, base=object): return meta("%sBase" % meta.__name__, (base,), {}) ################################################ + +# produce a wrapper that allows encoded text to stream +# into a given buffer, but doesn't close it. +# not sure of a more idiomatic approach to this. +class EncodedIO(io.TextIOWrapper): + def close(self): + pass + if py2k: - def writable_buffer(file): - return io.FileIO(file.fileno(), 'w') -else: - def writable_buffer(file): - return file + # in Py2K, the io.* package is awkward because it does not + # easily wrap the file type (e.g. sys.stdout) and I can't + # figure out at all how to wrap StringIO.StringIO (used by nosetests) + # and also might be user specified too. So create a full + # adapter. + + class ActLikePy3kIO(object): + """Produce an object capable of wrapping either + sys.stdout (e.g. file) *or* StringIO.StringIO(). + + """ + def _false(self): + return False + + def _true(self): + return True + + readable = seekable = _false + writable = _true + closed = False + + def __init__(self, file_): + self.file_ = file_ + + def write(self, text): + return self.file_.write(text) + + def flush(self): + return self.file_.flush() + + class EncodedIO(EncodedIO): + def __init__(self, file_, encoding): + super(EncodedIO, self).__init__( + ActLikePy3kIO(file_), encoding=encoding) + + diff --git a/alembic/migration.py b/alembic/migration.py index 657e60d8..9a01b016 100644 --- a/alembic/migration.py +++ b/alembic/migration.py @@ -6,7 +6,7 @@ from sqlalchemy import MetaData, Table, Column, String, literal_column from sqlalchemy import create_engine from sqlalchemy.engine import url as sqla_url -from .compat import callable, writable_buffer +from .compat import callable, EncodedIO from . import ddl, util log = logging.getLogger(__name__) @@ -72,13 +72,13 @@ class MigrationContext(object): self._migrations_fn = opts.get('fn') self.as_sql = as_sql - self.output_buffer = opts.get("output_buffer", sys.stdout) - if "output_encoding" in opts: - self.output_buffer = io.TextIOWrapper( - opts.get("output_buffer") or writable_buffer(sys.stdout), - opts.get('output_encoding') + self.output_buffer = EncodedIO( + opts.get("output_buffer") or sys.stdout, + opts['output_encoding'] ) + else: + self.output_buffer = opts.get("output_buffer", sys.stdout) self._user_compare_type = opts.get('compare_type', False) self._user_compare_server_default = opts.get( diff --git a/docs/build/changelog.rst b/docs/build/changelog.rst index 364bc840..9f7b7153 100644 --- a/docs/build/changelog.rst +++ b/docs/build/changelog.rst @@ -7,6 +7,16 @@ Changelog :version: 0.6.1 :released: no release date + .. change:: + :tags: bug + :pullreq: 9 + + Fixes to Py3k in-place compatibity regarding output encoding and related; + the use of the new io.* package introduced some incompatibilities on Py2k. + These should be resolved, due to the introduction of new adapter types + for translating from io.* to Py2k file types, StringIO types. + Thanks to Javier Santacruz for help with this. + .. change:: :tags: bug :tickets: 145 diff --git a/tests/test_offline_environment.py b/tests/test_offline_environment.py index da2589c5..7026e8c6 100644 --- a/tests/test_offline_environment.py +++ b/tests/test_offline_environment.py @@ -12,7 +12,6 @@ class OfflineEnvironmentTest(TestCase): def setUp(self): env = staging_env() self.cfg = _no_sql_testing_config() - self.cfg.output_buffer = io.StringIO() global a, b, c a, b, c = three_rev_fixture(self.cfg) @@ -43,8 +42,6 @@ assert context.get_starting_revision_argument() == 'x' command.upgrade(self.cfg, a, sql=True) command.downgrade(self.cfg, "%s:%s" % (b, a), sql=True) command.current(self.cfg) - # current seems to close the buffer (?) - self.cfg.output_buffer = io.StringIO() command.stamp(self.cfg, a) def test_starting_rev_pre_context(self): -- 2.47.2