Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix copying Message in pure-python. #15

Merged
merged 2 commits into from
Oct 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
53 changes: 28 additions & 25 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -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
<https://github.com/zopefoundation/zope.i18nmessageid/issues/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
Expand All @@ -27,7 +30,7 @@ Changes


4.1.0 (2017-05-02)
------------------
==================

- Drop support for Python 2.6 and 3.2.

Expand All @@ -42,24 +45,24 @@ Changes
<https://github.com/zopefoundation/zope.i18nmessageid/issues/5>`_.

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.

Expand All @@ -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.

Expand All @@ -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.
Expand All @@ -112,20 +115,20 @@ 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.

- Fix the compilation of the C extension with python 2.6: refactored it as a
setuptools Feature.

3.5.0 (2009-06-27)
------------------
==================

- Make compilation of C extension optional.

Expand All @@ -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.
Expand All @@ -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.
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def _unavailable(self, e):

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',
Expand Down Expand Up @@ -132,7 +132,7 @@ def _unavailable(self, e):
'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'],
Expand Down
37 changes: 0 additions & 37 deletions src/zope/i18nmessageid/_zope_i18nmessageid_message.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,50 +14,13 @@

#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
#else
#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;
Expand Down
60 changes: 30 additions & 30 deletions src/zope/i18nmessageid/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
##############################################################################
"""I18n Messages and factories.
"""
import six

__docformat__ = "reStructuredText"
_marker = object()

import six


class Message(six.text_type):
"""Message (Python implementation)
Expand All @@ -37,37 +37,37 @@ def __new__(cls, ustr, domain=_marker, default=_marker, mapping=_marker,
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
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:
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

Expand Down
36 changes: 36 additions & 0 deletions src/zope/i18nmessageid/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,19 @@ def test_copy(self):
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)

Expand All @@ -133,6 +146,29 @@ def test_copy_with_overrides(self):
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)):
Expand Down