[med-svn] [python-dictobj] 01/05: Imported Upstream version 0.4

Olivier Sallou osallou at debian.org
Mon Jul 4 10:29:14 UTC 2016


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 <olivier.sallou at debian.org>
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: william at grimapps.com
+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='william at grimapps.com',
+  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



More information about the debian-med-commit mailing list