From 42699ccf90d352200199dcd8dee0894591d9577b Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Fri, 19 Oct 2018 11:47:48 -0500 Subject: Fix copying Message in pure-python. Do it like the C extension does, by actually copying from the other object and then setting attributes if needed. Also drop old C support code for Python < 2.6 --- CHANGES.rst | 53 ++++++++++--------- setup.py | 4 +- .../i18nmessageid/_zope_i18nmessageid_message.c | 37 ------------- src/zope/i18nmessageid/message.py | 60 +++++++++++----------- src/zope/i18nmessageid/tests.py | 24 +++++++++ 5 files changed, 84 insertions(+), 94 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index f21f4aa..ec19279 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,21 +1,24 @@ -Changes -======= +========= + Changes +========= -4.4 (unreleased) ----------------- +4.3.1 (unreleased) +================== -- Nothing changed yet. +- Fix a regression copying Message objects in the Python + implementation. See `issue 14 + `_. -4.3 (2018-10-18) ----------------- +4.3.0 (2018-10-18) +================== - Add attributes to support pluralization on a Message and update the MessageFactory accordingly. -4.2 (2018-10-05) ----------------- +4.2.0 (2018-10-05) +================== - Fix the possibility of a rare crash in the C extension when deallocating items. See `issue 7 @@ -27,7 +30,7 @@ Changes 4.1.0 (2017-05-02) ------------------- +================== - Drop support for Python 2.6 and 3.2. @@ -42,24 +45,24 @@ Changes `_. 4.0.3 (2014-03-19) ------------------- +================== - Add support for Python 3.4. - Update ``boostrap.py`` to version 2.2. 4.0.2 (2012-12-31) ------------------- +================== - Flesh out PyPI Trove classifiers. 4.0.1 (2012-11-21) ------------------- +================== - Add support for Python 3.3. 4.0.0 (2012-05-16) ------------------- +================== - Automate generation of Sphinx HTML docs and running doctest snippets via tox. @@ -83,12 +86,12 @@ Changes 3.6.1 (2011-07-20) ------------------- +================== - Correct metadata in this file for release date. 3.6.0 (2011-07-20) ------------------- +================== - Python 3 support. @@ -98,7 +101,7 @@ Changes automated testing. 3.5.3 (2010-08-10) ------------------- +================== - Make compilation of C extension optional again; 3.5.1 broke this inasmuch as this package become unusable on non-CPython platforms. @@ -112,12 +115,12 @@ Changes compiled. This also makes the tests pass on Jython. 3.5.2 (2010-04-30) ------------------- +================== - Remove use of 'zope.testing.doctestunit' in favor of stdlib's 'doctest. 3.5.1 (2010-04-10) ------------------- +================== - LP #257657 / 489529: Fix memory leak in C extension. @@ -125,7 +128,7 @@ Changes setuptools Feature. 3.5.0 (2009-06-27) ------------------- +================== - Make compilation of C extension optional. @@ -141,24 +144,24 @@ Changes - Remove old .cfg files for zpkg. 3.4.3 (2007-09-26) ------------------- +================== - Make PyPI the home URL. 3.4.2 (2007-09-25) ------------------- +================== - Move the ``ZopeMessageFactory`` from ``zope.app.i18n`` to this package. 3.4.0 (2007-07-19) ------------------- +================== - Remove incorrect dependency. - Create final release to reflect package status. 3.2.0 (2006-01-05) ------------------- +================== - Corresponds to the verison of the zope.i18nmessageid package shipped as part of the Zope 3.2.0 release. @@ -171,7 +174,7 @@ Changes in Zope 3.3. 3.0.0 (2004-11-07) ------------------- +================== - Corresponds to the verison of the zope.i18nmessageid package shipped as part of the Zope X3.0.0 release. diff --git a/setup.py b/setup.py index eb7eb87..3256913 100644 --- a/setup.py +++ b/setup.py @@ -101,7 +101,7 @@ class optional_build_ext(build_ext): setup( name='zope.i18nmessageid', - version='4.4.dev0', + version='4.3.1.dev0', author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', description='Message Identifiers for internationalization', @@ -132,7 +132,7 @@ setup( 'Framework :: Zope :: 3', ], license='ZPL 2.1', - url='http://pypi.python.org/pypi/zope.i18nmessageid', + url='https://github.com/zopefoundation/zope.i18nmessageid', packages=find_packages('src'), package_dir={'': 'src'}, namespace_packages=['zope'], diff --git a/src/zope/i18nmessageid/_zope_i18nmessageid_message.c b/src/zope/i18nmessageid/_zope_i18nmessageid_message.c index c0107e1..66cd93d 100644 --- a/src/zope/i18nmessageid/_zope_i18nmessageid_message.c +++ b/src/zope/i18nmessageid/_zope_i18nmessageid_message.c @@ -14,16 +14,6 @@ #include "Python.h" -/* Support for Python < 2.6: */ - -#ifndef Py_TYPE - #define Py_TYPE(ob) (((PyObject*)(ob))->ob_type) -#endif - -#ifndef PyVarObject_HEAD_INIT - #define PyVarObject_HEAD_INIT(type, size) \ - PyObject_HEAD_INIT(type) size, -#endif #if PY_MAJOR_VERSION >= 3 #define MOD_ERROR_VAL NULL @@ -31,33 +21,6 @@ #define MOD_ERROR_VAL #endif -/* these macros make gc support easier; they are only available in - Python 2.4 and borrowed from there */ - -#ifndef Py_CLEAR -#define Py_CLEAR(op) \ - do { \ - if (op) { \ - PyObject *tmp = (op); \ - (op) = NULL; \ - Py_DECREF(tmp); \ - } \ - } while (0) -#endif - -#ifndef Py_VISIT -#define Py_VISIT(op) \ - do { \ - if (op) { \ - int vret = visit((op), arg); \ - if (vret) \ - return vret; \ - } \ - } while (0) -#endif - -/* ----------------------------------------------------- */ - typedef struct { PyUnicodeObject base; PyObject *domain; diff --git a/src/zope/i18nmessageid/message.py b/src/zope/i18nmessageid/message.py index 4bfcb19..ed5e08d 100644 --- a/src/zope/i18nmessageid/message.py +++ b/src/zope/i18nmessageid/message.py @@ -13,11 +13,11 @@ ############################################################################## """I18n Messages and factories. """ +import six + __docformat__ = "reStructuredText" _marker = object() -import six - class Message(six.text_type): """Message (Python implementation) @@ -37,37 +37,37 @@ class Message(six.text_type): msgid_plural=_marker, default_plural=_marker, number=_marker): self = six.text_type.__new__(cls, ustr) if isinstance(ustr, self.__class__): - domain = ustr.domain[:] if domain is _marker else domain - default = ustr.default[:] if default is _marker else default - mapping = ustr.mapping.copy() if mapping is _marker else mapping - msgid_plural = ( - ustr.msgid_plural[:] if msgid_plural is _marker else - msgid_plural) - default_plural = ( - ustr.default_plural[:] if default_plural is _marker else - default_plural) - number = ustr.number if number is _marker else number - ustr = six.text_type(ustr) + self.domain = ustr.domain[:] if ustr.domain else None + self.default = ustr.default[:] if ustr.default else None + self.mapping = ustr.mapping.copy() if ustr.mapping is not None else None + self.msgid_plural = ustr.msgid_plural[:] if ustr.msgid_plural else None + self.default_plural = ustr.default_plural[:] if ustr.default_plural else None + self.number = ustr.number else: - domain = None if domain is _marker else domain - default = None if default is _marker else default - mapping = None if mapping is _marker else mapping - msgid_plural = None if msgid_plural is _marker else msgid_plural - default_plural = (None if default_plural is _marker else - default_plural) - number = None if number is _marker else number - - self.domain = domain - self.default = default - self.mapping = mapping - self.msgid_plural = msgid_plural - self.default_plural = default_plural - - if number is not None and not isinstance( - number, six.integer_types + (float,)): + self.domain = None + self.default = None + self.mapping = None + self.msgid_plural = None + self.default_plural = None + self.number = None + + if domain is not _marker: + self.domain = domain + if default is not _marker: + self.default = default + if mapping is not _marker: + self.mapping = mapping + if msgid_plural is not _marker: + self.msgid_plural = msgid_plural + if default_plural is not _marker: + self.default_plural = default_plural + if number is not _marker: + self.number = number + + if self.number is not None and not isinstance( + self.number, six.integer_types + (float,)): raise TypeError('`number` should be an integer or a float') - self.number = number self._readonly = True return self diff --git a/src/zope/i18nmessageid/tests.py b/src/zope/i18nmessageid/tests.py index 56074c1..d542e48 100644 --- a/src/zope/i18nmessageid/tests.py +++ b/src/zope/i18nmessageid/tests.py @@ -133,6 +133,30 @@ class PyMessageTests(unittest.TestCase): if self._TEST_READONLY: self.assertTrue(message._readonly) + def test_copy_no_default(self): + # https://github.com/zopefoundation/zope.i18nmessageid/issues/14 + pref_msg = self._makeOne("${name} Preferences") + self.assertIsNone(pref_msg.default) + copy = self._makeOne(pref_msg, mapping={u'name': u'name'}) + self.assertIsNone(copy.default) + + def test_copy_no_overrides(self): + # https://github.com/zopefoundation/zope.i18nmessageid/issues/14 + pref_msg = self._makeOne("${name} Preferences") + + copy = self._makeOne(pref_msg) + for attr in ( + 'domain', + 'default', + 'mapping', + 'msgid_plural', + 'default_plural', + 'number', + ): + self.assertIsNone(getattr(pref_msg, attr)) + self.assertIsNone(getattr(copy, attr)) + + def test_domain_immutable(self): message = self._makeOne('testing') with self.assertRaises((TypeError, AttributeError)): -- cgit v1.2.1 From a2c0e413df1fd389de57578672e1415121ff9b09 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Fri, 19 Oct 2018 12:07:02 -0500 Subject: Make the Python implementation stop apptempting to pointlessly copy strings and (somewhat pointedly) copy mappings, like the C implementation. --- src/zope/i18nmessageid/message.py | 10 +++++----- src/zope/i18nmessageid/tests.py | 14 +++++++++++++- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/zope/i18nmessageid/message.py b/src/zope/i18nmessageid/message.py index ed5e08d..70a5053 100644 --- a/src/zope/i18nmessageid/message.py +++ b/src/zope/i18nmessageid/message.py @@ -37,11 +37,11 @@ class Message(six.text_type): msgid_plural=_marker, default_plural=_marker, number=_marker): self = six.text_type.__new__(cls, ustr) if isinstance(ustr, self.__class__): - self.domain = ustr.domain[:] if ustr.domain else None - self.default = ustr.default[:] if ustr.default else None - self.mapping = ustr.mapping.copy() if ustr.mapping is not None else None - self.msgid_plural = ustr.msgid_plural[:] if ustr.msgid_plural else None - self.default_plural = ustr.default_plural[:] if ustr.default_plural else None + self.domain = ustr.domain + self.default = ustr.default + self.mapping = ustr.mapping + self.msgid_plural = ustr.msgid_plural + self.default_plural = ustr.default_plural self.number = ustr.number else: self.domain = None diff --git a/src/zope/i18nmessageid/tests.py b/src/zope/i18nmessageid/tests.py index d542e48..89ed869 100644 --- a/src/zope/i18nmessageid/tests.py +++ b/src/zope/i18nmessageid/tests.py @@ -113,6 +113,19 @@ class PyMessageTests(unittest.TestCase): self.assertEqual(message.msgid_plural, 'testings') self.assertEqual(message.default_plural, 'defaults') self.assertEqual(message.number, 0) + + # Besides just being equal, they maintain their identity + for attr in ( + 'domain', + 'default', + 'mapping', + 'msgid_plural', + 'default_plural', + 'number', + ): + self.assertIs(getattr(source, attr), + getattr(message, attr)) + if self._TEST_READONLY: self.assertTrue(message._readonly) @@ -156,7 +169,6 @@ class PyMessageTests(unittest.TestCase): self.assertIsNone(getattr(pref_msg, attr)) self.assertIsNone(getattr(copy, attr)) - def test_domain_immutable(self): message = self._makeOne('testing') with self.assertRaises((TypeError, AttributeError)): -- cgit v1.2.1