This is an automated email from the git hooks/post-receive script. osallou pushed a commit to branch master in repository python-dictobj.
commit 4c2547c569d012c7a49448606740fd2292685ad8 Author: Olivier Sallou <[email protected]> Date: Mon Jul 4 12:11:02 2016 +0200 Imported Upstream version 0.4 --- .gitignore | 6 + CHANGELOG.md | 73 +++++++++++ LICENSE | 202 +++++++++++++++++++++++++++++ MANIFEST.in | 1 + NOTICE | 13 ++ README.md | 126 +++++++++++++++++++ dictobj.egg-info/PKG-INFO | 15 +++ dictobj.egg-info/SOURCES.txt | 8 ++ dictobj.egg-info/dependency_links.txt | 1 + dictobj.egg-info/top_level.txt | 1 + dictobj.py | 230 ++++++++++++++++++++++++++++++++++ dictobj_test.py | 89 +++++++++++++ release.sh | 14 +++ setup.py | 32 +++++ 14 files changed, 811 insertions(+) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..63443a1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.pyc +build/ +*.egg-info/ +dist/ +*.html +*.zip diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b1357ab --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,73 @@ +Changes +======= + +v0.4 +---- +* Support for Python 3 added. +* Removed the capabilities for <, >, <=, and >= on the + `DictionaryObject` to get Python 3 support properly added. + +v0.3.1 +------ +* Fixing the setup.py script so it works on older versions of Python. + +v0.3 +---- +* Added `DictionaryObject.asdict()` to return a copy of the internal + data as a `dict`, because some external libraries may require one + and won't accept a `DictionaryObject`. +* Added another test to some older doctest code to make sure + `MutableDictionaryObject.__setitem__` works correctly. +* Improved release.sh a little bit for generating pypi packages / docs. +* Upgrading development status to "Production/Stable". + +v0.2.5 +------ +* Added `__setitem__` to `MutableDictionaryObject`. + +v0.2.4 +------ +* Properly formatted README.md and added some details about how +to contribute to this project. +* Added doctests to this project since the examples actually do +work as valid tests taht can be run. Found some bugs, resolved +them, and added everything to the automated testing suite. +* Changed CHANGELOG to CHANGELOG.md. + +v0.2.3 +------ +* Adding the source code URL to the README so that it's easier +for people to find and help contribute. + +v0.2.2 +------ +* Changelog started and prior versions added to this file. +* Removed some code from `MutableDictionaryObject.__setattr__` +that is no longer needed now that `DictionaryObject.__init__` +properly handles initialization of `__dict__` when passed in a +`DictionaryObject`. + +v0.2.1 +------ +* Improved the thoroughness of the documentation. +* Added a description for PyPi. + +v0.2 +---- +* Fixed equality operators and the comparison method. +* Fixed object copying when passing in another `DictionaryObject`. +* Fixed handling of default values upon `__init__`. +* Added `__setstate__` / `__getstate__` so pickle now works correctly +with the classes. +* Fixed error reporting on exceptions. +* Improved `__repr__` so it gives a proper string represenation of +our classes so they can later be eval'd. +* Added more unit tests: + - `test_pickle` + - `test_copy` + - more equality tests +* Added more examples to the documentation. + +v0.1.1 +------ +* First release. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..1aba38f --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include LICENSE diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..56d2f52 --- /dev/null +++ b/NOTICE @@ -0,0 +1,13 @@ + Copyright 2012 "Grim Apps" + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ac3393e --- /dev/null +++ b/README.md @@ -0,0 +1,126 @@ +py-dictobj +========== + +This package extends the functionality of the normal Python dictionary by affording the +ability to lookup dictionary keys as instance attributes (i.e. `__getattr__`) +instead of "indices" (i.e. `__getitem__`). Two caveats remain, however, prevent +the use of `__getattr__` in certain circumstances. In these cases, access the +`DictionaryObject` using `__getitem__` (e.g. `d['3x&']`). These cases are + + 1. Names that do not follow the valid conventions for Normal Python syntax + 2. Names that match class attributes of the `DictionaryObject` class hierarchy + (e.g. `d.keys` will return the method, not the value, assuming `d['keys']` exists). + +There are two primary classes of interest: `DictionaryObject` and `MutableDictionaryObject`. +`DictionaryObject` is the base class, and it acts as an immutable dictionary. +`MutableDictionaryObject`, as the name implies, provides the ability to mutate the object via +`__setattr__` (e.g. `d.x = 500`) and `__setitem__` (e.g. `d['x'] = 500`). For a description +on the design considerations behind this choice, please see [Immutable-by-Default](#mutability). + +Care has been taken to make sure these classes are picklable so that they can be +stored and passed around, especially in the case of multiprocessing. Care has +also been taken that the `__repr__` of these classes can be eval()'d by the Python +interpretter. + +Mutable vs Immutable +-------------------- + +The base `DictionaryObject` class is itself __immutable__, meaning that once the data is +set during the call to `DictionaryObject.__init__`, no other keys may be added, nor +may any existing keys have their values changed. One caveat to this is that if the +values a `DictionaryObject` points to are themselves __mutable__, then the underlying +object may change. + +If your use-case requires a more liberal `DictionaryObject` with _mutability_, please use +`MutableDictionaryObject`. It behaves the same, but you can add keys via `__setattr__` +or `__setitem__` (e.g. `d.x = 5` or `d['x'] = 5`). + + +<a name="mutability"></a> + +Immutable-by-Default +-------------------- + +The base `DictionaryObject` was created as __immutable-by-default__ in order to facilitate +[Separation of Concerns](http://en.wikipedia.org/wiki/Separation_of_concerns) +By doing my best to ensure the top-level object is itself immutable, developers are more free +to consider an object instance as _static values_. This allows them to make better assumptions, +such as the fact they cannot change any values and indirectly interfere with the processing of the +same data on another thread or process. + +In practice, Python itself does support a model of strong assurances with regard to immutability. So, +the programmer must still be careful; however, this package should help. + +Installation +------------ + +If you have Python installed and wish to get the package directly from the +[Python Package Index](http://pypi.python.org/pypi/dictobj), just run +`pip install dictobj` from the command-line. If you already have a prior +version installed, just run `pip install dictobj -U` instead. + +Contribute +---------- + +Please help contribute to this project by going to the +[GitHub Project Repository](https://github.com/grimwm/py-dictobj) and doing one +of a few things: + + * send me pull requests through the github interface + * point me directly to your git repo so I can pull changes + * send bug reports and feature requests by filing them under the __Issues__ tab at the top + +Examples +-------- + >>> d = DictionaryObject({'a':1, 'b':True, 3:'x'}) + >>> print d.a, d.b, d[3] + 1 True x + + >>> d = DictionaryObject((('a',1),('b',2))) + >>> print d.a, d.b + 1 2 + + >>> d = DictionaryObject(a=1, b=True) + >>> print d + DictionaryObject({'a': 1, 'b': True}) + + >>> d = DictionaryObject({'a':1, 'b':True}, None) + >>> print d.a, d.b, d.c, d.d + 1 True None None + + >>> d = DictionaryObject({'a':1}, None) + >>> m = MutableDictionaryObject(d) + >>> print d == m + True + >>> m.a = 0 + >>> print d == m, d < m, d > m, d != m, d <= m, d >= m + False False True True False True + + >>> import pickle + >>> m1 = MutableDictionaryObject({'a':1}, None) + >>> m2 = pickle.loads(pickle.dumps(m1)) + >>> print m1 == m2 + True + >>> m1.a = 3 + >>> print m1 == m2 + False + + >>> d = DictionaryObject({'keys':[1,2], 'values':3, 'x':1}) + >>> d.keys + <bound method DictionaryObject.keys of DictionaryObject({'keys': [1, 2], 'x': 1, 'values': 3})> + >>> d.values + <bound method DictionaryObject.values of DictionaryObject({'keys': [1, 2], 'x': 1, 'values': 3})> + >>> d.x + 1 + >>> d['keys'] + [1, 2] + >>> d['values'] + 3 + + >>> import dictobj + >>> d = {'a':1, 'b':2} + >>> dictobj.DictionaryObject(d).asdict() == d + True + >>> d['c'] = {1:2, 3:4} + >>> dictobj.DictionaryObject(d).asdict() == d + True diff --git a/dictobj.egg-info/PKG-INFO b/dictobj.egg-info/PKG-INFO new file mode 100644 index 0000000..965ff47 --- /dev/null +++ b/dictobj.egg-info/PKG-INFO @@ -0,0 +1,15 @@ +Metadata-Version: 1.1 +Name: dictobj +Version: 0.4 +Summary: A set of Python dictionary objects where keys can be accessed as instance attributes. +Home-page: https://github.com/grimwm/py-dictobj +Author: William Grim +Author-email: [email protected] +License: UNKNOWN +Description: UNKNOWN +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Software Development :: Libraries :: Python Modules diff --git a/dictobj.egg-info/SOURCES.txt b/dictobj.egg-info/SOURCES.txt new file mode 100644 index 0000000..28a5646 --- /dev/null +++ b/dictobj.egg-info/SOURCES.txt @@ -0,0 +1,8 @@ +LICENSE +MANIFEST.in +dictobj.py +setup.py +dictobj.egg-info/PKG-INFO +dictobj.egg-info/SOURCES.txt +dictobj.egg-info/dependency_links.txt +dictobj.egg-info/top_level.txt \ No newline at end of file diff --git a/dictobj.egg-info/dependency_links.txt b/dictobj.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/dictobj.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/dictobj.egg-info/top_level.txt b/dictobj.egg-info/top_level.txt new file mode 100644 index 0000000..d89ff63 --- /dev/null +++ b/dictobj.egg-info/top_level.txt @@ -0,0 +1 @@ +dictobj diff --git a/dictobj.py b/dictobj.py new file mode 100644 index 0000000..624ac46 --- /dev/null +++ b/dictobj.py @@ -0,0 +1,230 @@ +import pickle + +class DictionaryObject(object): + """ + A class that has all the functionality of a normal Python dictionary, except + for the fact it is itself immutable. It also has the added feature of + being able to lookup values by using keys as attributes. + + The reason for the class being immutable by default is to help make it a + little easier to use in multiprocessing situations. Granted, the underlying + values themselves are not deeply copied, but the aim is to enforce some + ensurances of immutability on the container class. + + When using positional arguments, the first argument must always be something + that would be a valid argument for a dict(). However, a second, optional + argument may be passed to create a default value when keys are not found. + + Examples: + >>> d = DictionaryObject({'a':1, 'b':True, 3:'x'}) + >>> d.a == 1 + True + >>> d.b + True + >>> d[3] == 'x' + True + + >>> d = DictionaryObject((('a',1),('b',2))) + >>> d.a == 1 + True + >>> d.b == 2 + True + + >>> d = DictionaryObject({'a':1, 'b':True}, None) + >>> d.a == 1 + True + >>> d.b + True + >>> d.c + + >>> d = DictionaryObject({'a':1}, None) + >>> m = MutableDictionaryObject(d) + >>> d == m + True + >>> m.a = 0 + >>> d == m + False + >>> d != m + True + + >>> import pickle + >>> m1 = MutableDictionaryObject({'a':1}, None) + >>> m2 = pickle.loads(pickle.dumps(m1)) + >>> m1 == m2 + True + >>> m1.a = 3 + >>> m1 == m2 + False + >>> m1.a == 3 + True + >>> m1['c'] = 5 + >>> m1['c'] + 5 + """ + def __init__(self, contents=(), *args, **kwargs): + """ + Take as input a dictionary-like object and return a DictionaryObject. + It also makes sure any keys containing dictionaries are also converted + to DictionaryObjects. + """ + super(DictionaryObject, self).__init__() + if isinstance(contents, DictionaryObject): + self.__dict__.update(pickle.loads(pickle.dumps(contents.__dict__))) + return + + self.__dict__['_items'] = dict(contents, **kwargs) + + if len(args) > 1: + raise TypeError("too many arguments") + + # If we have more than one argument passed in, use the second argument + # as a default value. + if args: + try: + default = type(self)(args[0]) + except: + default = args[0] + self.__dict__['_defaultValue'] = default + else: + self.__dict__['_defaultValue'] = None + self.__dict__['_defaultIsSet'] = len(args) > 0 + + for k in self._items: + if isinstance(self._items[k], dict): + self._items[k] = type(self)(self._items[k]) + + def __setstate__(self, dict): + self.__dict__.update(dict) + + def __getstate__(self): + return self.__dict__.copy() + + def __getattr__(self, name): + """ + This is the method that makes all the magic happen. Search for + 'name' in self._items and return the value if found. If a default + value has been set and 'name' is not found in self._items, return it. + Otherwise raise an AttributeError. + + Example: + >>> d = DictionaryObject({'keys':[1,2], 'values':3, 'x':1}) + >>> sorted(list(d.keys())) == ['keys', 'values', 'x'] + True + >>> [1, 2] in list(d.values()) + True + >>> 1 in list(d.values()) + True + >>> d.x + 1 + >>> d['keys'] + [1, 2] + >>> d['values'] + 3 + """ + if name in self._items: + return self._items[name] + if self._defaultIsSet: + return self._defaultValue + raise AttributeError("'%s' object has no attribute '%s'" % (type(self).__name__, name)) + + def __setattr__(self, name, value): + """ + This class is immutable-by-default. See MutableDictionaryObject. + """ + raise AttributeError("'%s' object does not support assignment" % type(self).__name__) + + def __getitem__(self, name): + return self._items[name] + + def __contains__(self, name): + return name in self._items + + def __len__(self): + return len(self._items) + + def __iter__(self): + return iter(self._items) + + def __repr__(self): + if self._defaultIsSet: + params = "%s, %s" % (repr(self._items), self._defaultValue) + else: + params = repr(self._items) + return "%s(%s)" % (type(self).__name__, params) + + def __cmp__(self, rhs): + if self < rhs: + return -1 + if self > rhs: + return 1 + return 0 + + def __eq__(self, rhs): + if self._items == rhs._items: + return self._defaultValue == rhs._defaultValue + return False + + def __ne__(self, rhs): + return not (self == rhs) + + def keys(self): + return self._items.keys() + + def values(self): + return self._items.values() + + def asdict(self): + """ + Copy the data back out of here and into a dict. Then return it. + Some libraries may check specifically for dict objects, such + as the json library; so, this makes it convenient to get the data + back out. + + >>> import dictobj + >>> d = {'a':1, 'b':2} + >>> dictobj.DictionaryObject(d).asdict() == d + True + >>> d['c'] = {1:2, 3:4} + >>> dictobj.DictionaryObject(d).asdict() == d + True + """ + items = {} + for name in self._items: + value = self._items[name] + if isinstance(value, DictionaryObject): + items[name] = value.asdict() + else: + items[name] = value + return items + +class MutableDictionaryObject(DictionaryObject): + """ + Slight enhancement of the DictionaryObject allowing one to add + attributes easily, in cases where that functionality is wanted. + + Examples: + >>> d = MutableDictionaryObject({'a':1, 'b':True}, None) + >>> d.a == 1 + True + >>> d.b == True + True + >>> d.c is None + True + >>> d.d is None + True + >>> d.c = 3 + >>> del d.a + >>> d.a is None + True + >>> d.c == 3 + True + """ + def __setattr__(self, name, value): + self._items[name] = value + + def __delattr__(self, name): + del self._items[name] + + __setitem__ = __setattr__ + __delitem__ = __delattr__ + diff --git a/dictobj_test.py b/dictobj_test.py new file mode 100644 index 0000000..9179143 --- /dev/null +++ b/dictobj_test.py @@ -0,0 +1,89 @@ +from dictobj import * + +import unittest +import doctest + +class TestDictionaryObject(unittest.TestCase): + def setUp(self): + self.vanilla = DictionaryObject((('a',1),('b',2))) + self.kinky = DictionaryObject({'a':1, 'b':{'c':True, 'd':[1,2]}, 1:'x'}) + self.default = DictionaryObject((), None, a=3) + self.mutable = MutableDictionaryObject(a=3, b=4) + self.mutableDefault = MutableDictionaryObject((), None, b=4) + + def test_pickle(self): + default = pickle.loads(pickle.dumps(self.default)) + self.assertEqual(default, self.default) + + mutable = pickle.loads(pickle.dumps(self.mutable)) + self.assertEqual(mutable, self.mutable) + mutable.a = 500 + self.assertNotEqual(mutable, self.mutable) + + def test_copy(self): + m = MutableDictionaryObject(self.default) + self.assertEqual(m, self.default) + + m.a = 2 + self.assertNotEqual(m, self.default) + + def test_len(self): + self.assertEqual(3, len(self.kinky)) + + def test_default(self): + self.assertEqual(self.default.a, 3) + self.assertEqual(self.default.b, None) + + self.assertEqual(self.mutableDefault.a, None) + self.assertEqual(self.mutableDefault.b, 4) + + def test_mutable(self): + self.assertEqual(self.mutable.a, 3) + self.assertEqual(self.mutable.b, 4) + + self.mutable.c = 5 + self.assertEqual(self.mutable.c, 5) + + self.assertRaises(AttributeError, getattr, self.mutable, 'd') + + def test_mutable_default(self): + self.assertEqual(self.mutableDefault.b, 4) + + self.mutableDefault.c = 5 + self.assertEqual(self.mutableDefault.c, 5) + + self.assertEqual(self.mutableDefault.a, None) + + def test_iter(self): + keys = set([1,'a','b']) + for i,k in enumerate(self.kinky): + self.assertTrue(k in keys) + + def test_getattr(self): + self.assertEqual(self.kinky.a, 1) + self.assertEqual(self.kinky.b, DictionaryObject({'c':True, 'd':[1,2]})) + + def test_getitem(self): + self.assertEqual(self.kinky['a'], 1) + self.assertEqual(self.kinky['b'], DictionaryObject({'c':True, 'd':[1,2]})) + self.assertEqual(self.kinky[1], 'x') + + def test_exception(self): + self.assertRaises(AttributeError, setattr, self.kinky, 'c', 3) + + def test_setitem(self): + self.mutable.x = 500 + self.assertEqual(self.mutable.x, 500) + + self.mutable['y'] = 100 + self.assertEqual(self.mutable.y, 100) + +def load_tests(loader, tests, pattern): + import dictobj + suite = unittest.TestSuite() + suite.addTests(tests) + suite.addTest(doctest.DocTestSuite(dictobj)) + return suite + +if '__main__' == __name__: + unittest.main() diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..eccdfc7 --- /dev/null +++ b/release.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# Run build +pandoc README.md -w rst -o README.txt +python setup.py sdist upload +markdown README.md > index.html +zip pypi.zip index.html + +# Do cleanup +rm -f README.txt index.html +rm -rf dist +rm -rf dictobj.egg-info +rm -rf __pycache__ +rm -f *.pyc diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..48f5bab --- /dev/null +++ b/setup.py @@ -0,0 +1,32 @@ +from setuptools import setup +import os + +def read(filename): + fin = None + data = None + try: + fin = open(filename) + data = fin.read() + finally: + if fin is not None: + fin.close() + return data + +setup( + name='dictobj', + version='0.4', + author='William Grim', + author_email='[email protected]', + url='https://github.com/grimwm/py-dictobj', + classifiers = [ + 'Development Status :: 5 - Production/Stable', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Software Development :: Libraries :: Python Modules', + ], + description='A set of Python dictionary objects where keys can be accessed as instance attributes.', + long_description=read('README.txt') if os.path.exists('README.txt') else '', + py_modules=['dictobj'], + test_suite='dictobj_test', + ) -- Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/python-dictobj.git _______________________________________________ debian-med-commit mailing list [email protected] http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/debian-med-commit
