New Upstream Release - python-munch

Ready changes

Summary

Merged new upstream version: 3.0.0 (was: 2.5.1.dev12).

Resulting package

Built on 2023-05-18T01:41 (took 6m7s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases python3-munch

Lintian Result

Diff

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..64954bd
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,54 @@
+name: Build and Test
+on:
+  push:
+    branches: [ master, develop ]
+  pull_request:
+    branches: [ master, develop ]
+
+jobs:
+  build:
+    runs-on: ${{ matrix.os }}
+    strategy:
+       fail-fast: false
+       matrix:
+         os: [ubuntu-latest]
+         python-version: ["3.7", "3.8", "3.9", "3.10", "3.11-dev"]
+         include:
+           - os: macos-latest
+             python-version: "3.10"
+           - os: windows-latest
+             python-version: "3.10"
+
+    steps:
+    - uses: actions/checkout@v3
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python@v4
+      with:
+        python-version: ${{ matrix.python-version }}
+    - name: Upgrade pip
+      run: python -m pip install --upgrade pip
+    - name: Upgrade build tools and pytest
+      run: pip install --upgrade build wheel setuptools pytest
+    - name: Install dependencies
+      run: pip install ".[testing,yaml]"
+    - name: Build distributions
+      run: python -m build
+    - name: Test with Pytest
+      run: pytest
+
+  pylint:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v3
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python@v4
+      with:
+        python-version: "3.10"
+    - name: Upgrade pip
+      run: python -m pip install --upgrade pip
+    - name: Upgrade build tools and pytest
+      run: pip install --upgrade build wheel setuptools
+    - name: Install dependencies
+      run: pip install ".[testing,yaml]"
+    - name: Lint
+      run: pylint munch setup.py tests
diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml
new file mode 100644
index 0000000..ac27a84
--- /dev/null
+++ b/.github/workflows/dependabot.yml
@@ -0,0 +1,8 @@
+version: 2
+updates:
+  # Maintain dependencies for GitHub Actions
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      # Check for updates to GitHub Actions every week
+      interval: "weekly"
diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml
new file mode 100644
index 0000000..bdaab28
--- /dev/null
+++ b/.github/workflows/python-publish.yml
@@ -0,0 +1,39 @@
+# This workflow will upload a Python Package using Twine when a release is created
+# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
+
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: Upload Python Package
+
+on:
+  release:
+    types: [published]
+
+permissions:
+  contents: read
+
+jobs:
+  deploy:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v3
+    - name: Set up Python
+      uses: actions/setup-python@v3
+      with:
+        python-version: '3.x'
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        pip install build
+    - name: Build package
+      run: python -m build
+    - name: Publish package
+      uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
+      with:
+        user: __token__
+        password: ${{ secrets.PYPI_API_TOKEN }}
diff --git a/.pylintrc b/.pylintrc
index b2d792d..a7f3450 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -1,5 +1,5 @@
 [MESSAGES CONTROL]
-disable=invalid-name,missing-docstring,too-few-public-methods,no-else-return
+disable=invalid-name,missing-docstring,too-few-public-methods,no-else-return,unnecessary-comprehension,bad-option-value
 
 [REPORTS]
 reports=no
diff --git a/AUTHORS b/AUTHORS
index 7134381..c82da80 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,19 +1 @@
-Alex Fraser <alex@phatcore.com>
-Alex Wilson <Alex.Wilson@c3group.com.au>
-Alex Wilson <alex@kbni.net>
-Arnon Yaari <arnony@infinidat.com>
-Ayala Shachar <ayalas@infinidat.com>
-Ben Artin <ben@artins.org>
-Bob Haddleton <bob.haddleton@nokia.com>
-Eric Kuecks <ekuecks@gmail.com>
-Guy Rozendorn <guy@rzn.co.il>
-Jacob Magnusson <m@jacobian.se>
-Jamshed Vesuna <jamshed@robinhood.com>
-Jose Vargas <jpv.badilla@gmail.com>
-Laszlo Marai <atleta@atleta.hu>
-Maor Marcus <marcusmaor@gmail.com>
-Oded Badt <obadt@infinidat.com>
-Paul Belanger <pabelanger@redhat.com>
-Rotem Yaari <rotemy@infinidat.com>
-Rotem Yaari <vmalloc@gmail.com>
-femtotrader <femto.trader@gmail.com>
+David Sternlicht <d1618033@gmail.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c918f17..6962cd5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,17 @@ Changelog
 Next Version
 ------------
 
+3.0.0 (2023-05-14)
+------------------
+
+* Fix munchify for tuples of lists  
+* Require Python >=3.6 and upgrade syntax - thanks @EwoutH
+* Update __init__.py  to work with non standard version - thanks @mboisson
+* Allow importing even when VERSION read fails - thanks @mdornseif and @dangillet
+* Add imports to README  
+* replace pkg_resources with importlib.metadata - thanks @dhellmann  
+* Added RecursiveMunch object - thanks @GuillaumeRochette
+
 2.5.0 (2019-10-30)
 ------------------
 
diff --git a/ChangeLog b/ChangeLog
index b8b14ff..fee79c4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,156 +1,7 @@
 CHANGES
 =======
 
-2.5.0
+3.0.0
 -----
 
-* Update CHANGELOG.md
-* Support fromJSON classmethod for all Munch subclasses
-* Fix DefaultMunch/DefaultFactoryMunch return value for get method (fixes #53)
-* Support fromYAML classmethod for all Munch subclasses (fixes #34)
-* Clean CHANGELOG.md
-
-2.4.0
------
-
-* Update CHANGELOG.md
-* Add Changelog
-* Remove usage of deprecated API: Add default loader to yaml loads (#51)
-* Improve README.md and add unittest for its code blocks
-* Skip yaml tests if PyYAML is not installed
-* Improve Pylint validations
-* Switch to PBR (#49)
-* Switch to PBR
-* Add constructors to all PyYAML loaders (fixes #44) (#47)
-* Add constructors to all PyYAML loaders (fixes #44)
-* Fix/namedtuple handling (#46)
-* Fixed namedtuple handling in unmunchify (just in case someone tries to unmunchify a Munch   created with munchify which may contain a namedtuple, from now on)
-* Fixed namedtuple handling in munchify. Namedtuples are kept in the generated   hierarchy, but their children are also converted
-* Fixed test case
-* Created test case for bug: namedtuples cause an exception
-* Clearer variable names
-* Can simplify passing of seen and factory if helpers are nested
-* Better naming and scoping of helper functions
-* Handle tuple-tuple cycles correctly
-* Test list-list and tuple-tuple cycles
-* Test more dict-dict cycles
-* Test for identity in cycle tests
-* Correctly munchify and unmunchify structures with object cycles
-* Ignore VSCode workspace files
-* Fixed typo caught by ImportError
-* Added newline at end of file
-* Generalize munchify and unmunchify for Mapping instances
-* Overwrite built-in methods for safer subclassing
-* Fix typo in travis-ci yaml file
-
-2.3.2
------
-
-* Bump version
-* Limit travis deployment conditions
-* Build python wheels
-
-2.3.1
------
-
-* Ignore flycheck files
-* Bump version
-* Avoid running yaml tests when in no-deps environment
-* Ignore pytest cache
-* Drop the dot in pytest invocation
-* Use flat dicts in \_\_getstate\_\_ (closes #30)
-
-2.3.0
------
-
-* Bump version
-* Fix lint in init
-* Remove default from constructor and fromDict. Also add a test for repr reversibility and update test names
-* Make DefaultFactoryMunch which lets users provide a factory to generate missing values
-* Rebasing with upstream
-* Drop support for 2.6, 3.3, 3.4
-* AutoMunch for automatically converting dicts to Munches
-* \_\_setattr\_\_ will now munchify() any provided dict
-* Clear and update dict
-* Implement the pickling interface
-* Drop support for Python 2.6, 3.3, 3.4
-* Add \_\_dict\_\_ property that calls toDict()
-
-2.2.0
------
-
-* Bump version
-* Fix for Python 2.6: str.format must field names
-* Changed \_\_repr\_\_ to use str.format instead of x % y
-* Made DefaultMunch documentation a little clearer
-* Ignoring pylint warning about fromDict having different arguments
-* Fix for pylint in Python 3.5: method arguments differ
-* Added DefaultMunch, which returns a special value for missing attributes
-
-2.1.1
------
-
-* v2.1.1
-* Fix python 3 compatibility to work with IronPython (fix #13)
-* Deploy from Travis
-* Add python 3.6
-* Add pylintrc
-* Fix lint tox config
-
-2.1.0
------
-
-* v2.1.0
-* fix flake8
-* implement copy method. Fixes issue #10
-* Fix \_\_contains\_\_ returning True for Munch’s default attributes Includes changes from #6, as I couldn't test without it
-* Fix tests and use standard py.test tests instead Doctests were failing on all python versions. This PR fixes that
-
-2.0.4
------
-
-* v2.0.4
-* Modernize tests
-* Fixed some edge cases
-* Fixed tests
-* Fixed String representation of objects with keys that have spaces
-* Stop taking long rst description from README markdown
-
-2.0.3
------
-
-* v2.0.3
-* Move to new travis infrastructure
-* Fix doctests
-* Ignore .cache
-* Python 3.5 support
-* Update setup.py
-* Fix badges
-* Update README.md
-* Test against Python 3.4
-* Add support for running dir() on munches
-* Move to use py.test
-
-2.0.2
------
-
-* Fix packaging manifest
-
-2.0.1
------
-
-* v2.0.1
-* Rename to Munch
-* Fix Py3 compatibility check
-* Fix Readme
-* Fix Readme
-* Fix tox, reorganize tests and add Makefile
-* Drop Python 3.2 support, add 3.3
-* renaming infi.bunch to chunk
-* Update gitignore
-
-2.0.0
------
-
-* Forking bunch --> infi.bunch
-* Python 3 Compatibility Fixes
+* Update changelog for 3.0.0
diff --git a/PKG-INFO b/PKG-INFO
index 73ab86b..04d01e1 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,187 +1,193 @@
 Metadata-Version: 2.1
 Name: munch
-Version: 2.5.0
+Version: 3.0.0
 Summary: A dot-accessible dictionary (a la JavaScript objects)
 Home-page: https://github.com/Infinidat/munch
 Author: Rotem Yaari
 Author-email: vmalloc@gmail.com
 License: MIT
-Description: [![Build Status](https://travis-ci.org/Infinidat/munch.svg?branch=master)](https://travis-ci.org/Infinidat/munch)
-        [![Latest Version](https://img.shields.io/pypi/v/munch.svg)](https://pypi.python.org/pypi/munch/)
-        [![Supported Python versions](https://img.shields.io/pypi/pyversions/munch.svg)](https://pypi.python.org/pypi/munch/)
-        [![Downloads](https://img.shields.io/pypi/dm/munch.svg)](https://pypi.python.org/pypi/munch/)
-        
-        munch
-        ==========
-        
-        munch is a fork of David Schoonover's **Bunch** package, providing similar functionality. 99% of the work was done by him, and the fork was made mainly for lack of responsiveness for fixes and maintenance on the original code.
-        
-        Munch is a dictionary that supports attribute-style access, a la JavaScript:
-        
-        ```python
-        
-        >>> b = Munch()
-        >>> b.hello = 'world'
-        >>> b.hello
-        'world'
-        >>> b['hello'] += "!"
-        >>> b.hello
-        'world!'
-        >>> b.foo = Munch(lol=True)
-        >>> b.foo.lol
-        True
-        >>> b.foo is b['foo']
-        True
-        
-        ```
-        
-        
-        Dictionary Methods
-        ------------------
-        
-        A Munch is a subclass of ``dict``; it supports all the methods a ``dict`` does:
-        
-        ```python
-        
-        >>> list(b.keys())
-        ['hello', 'foo']
-        
-        ```
-        
-        Including ``update()``:
-        
-        ```python
-        
-        >>> b.update({ 'ponies': 'are pretty!' }, hello=42)
-        >>> print(repr(b))
-        Munch({'hello': 42, 'foo': Munch({'lol': True}), 'ponies': 'are pretty!'})
-        
-        ```
-        
-        As well as iteration:
-        
-        ```python
-        
-        >>> [ (k,b[k]) for k in b ]
-        [('hello', 42), ('foo', Munch({'lol': True})), ('ponies', 'are pretty!')]
-        
-        ```
-        
-        And "splats":
-        
-        ```python
-        
-        >>> "The {knights} who say {ni}!".format(**Munch(knights='lolcats', ni='can haz'))
-        'The lolcats who say can haz!'
-        
-        ```
-        
-        
-        Serialization
-        -------------
-        
-        Munches happily and transparently serialize to JSON and YAML.
-        
-        ```python
-        
-        >>> b = Munch(foo=Munch(lol=True), hello=42, ponies='are pretty!')
-        >>> import json
-        >>> json.dumps(b)
-        '{"foo": {"lol": true}, "hello": 42, "ponies": "are pretty!"}'
-        
-        ```
-        
-        If JSON support is present (``json`` or ``simplejson``), ``Munch`` will have a ``toJSON()`` method which returns the object as a JSON string.
-        
-        If you have [PyYAML](http://pyyaml.org/wiki/PyYAML) installed, Munch attempts to register itself with the various YAML Representers so that Munches can be transparently dumped and loaded.
-        
-        ```python
-        
-        >>> b = Munch(foo=Munch(lol=True), hello=42, ponies='are pretty!')
-        >>> import yaml
-        >>> yaml.dump(b)
-        '!munch.Munch\nfoo: !munch.Munch\n  lol: true\nhello: 42\nponies: are pretty!\n'
-        >>> yaml.safe_dump(b)
-        'foo:\n  lol: true\nhello: 42\nponies: are pretty!\n'
-        
-        ```
-        
-        In addition, Munch instances will have a ``toYAML()`` method that returns the YAML string using ``yaml.safe_dump()``. This method also replaces ``__str__`` if present, as I find it far more readable. You can revert back to Python's default use of ``__repr__`` with a simple assignment: ``Munch.__str__ = Munch.__repr__``. The Munch class will also have a static method ``Munch.fromYAML()``, which loads a Munch out of a YAML string.
-        
-        Finally, Munch converts easily and recursively to (``unmunchify()``, ``Munch.toDict()``) and from (``munchify()``, ``Munch.fromDict()``) a normal ``dict``, making it easy to cleanly serialize them in other formats.
-        
-        
-        Default Values
-        --------------
-        
-        ``DefaultMunch`` instances return a specific default value when an attribute is missing from the collection. Like ``collections.defaultdict``, the first argument is the value to use for missing keys:
-        
-        ```python
-        
-        >>> undefined = object()
-        >>> b = DefaultMunch(undefined, {'hello': 'world!'})
-        >>> b.hello
-        'world!'
-        >>> b.foo is undefined
-        True
-        
-        ```
-        
-        ``DefaultMunch.fromDict()`` also takes the ``default`` argument:
-        
-        ```python
-        
-        >>> undefined = object()
-        >>> b = DefaultMunch.fromDict({'recursively': {'nested': 'value'}}, undefined)
-        >>> b.recursively.nested == 'value'
-        True
-        >>> b.recursively.foo is undefined
-        True
-        
-        ```
-        
-        Or you can use ``DefaultFactoryMunch`` to specify a factory for generating missing attributes. The first argument is the factory:
-        
-        ```python
-        
-        >>> b = DefaultFactoryMunch(list, {'hello': 'world!'})
-        >>> b.hello
-        'world!'
-        >>> b.foo
-        []
-        >>> b.bar.append('hello')
-        >>> b.bar
-        ['hello']
-        
-        ```
-        
-        
-        Miscellaneous
-        -------------
-        
-        * It is safe to ``import *`` from this module. You'll get: ``Munch``, ``DefaultMunch``, ``DefaultFactoryMunch``, ``munchify`` and ``unmunchify``.
-        * Ample Tests. Just run ``pip install tox && tox`` from the project root.
-        
-        Feedback
-        --------
-        
-        Open a ticket / fork the project on [GitHub](http://github.com/Infinidat/munch).
-        
-        
 Keywords: munch,dict,mapping,container,collection
-Platform: UNKNOWN
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Intended Audience :: Developers
 Classifier: Operating System :: OS Independent
 Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 2.7
-Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
 Classifier: Topic :: Software Development
 Classifier: Topic :: Software Development :: Libraries
 Classifier: Topic :: Utilities
 Classifier: License :: OSI Approved :: MIT License
+Requires-Python: >=3.6
 Description-Content-Type: text/markdown
 Provides-Extra: testing
 Provides-Extra: yaml
+License-File: LICENSE.txt
+
+[![Build Status](https://travis-ci.org/Infinidat/munch.svg?branch=master)](https://travis-ci.org/Infinidat/munch)
+[![Latest Version](https://img.shields.io/pypi/v/munch.svg)](https://pypi.python.org/pypi/munch/)
+[![Supported Python versions](https://img.shields.io/pypi/pyversions/munch.svg)](https://pypi.python.org/pypi/munch/)
+[![Downloads](https://img.shields.io/pypi/dm/munch.svg)](https://pypi.python.org/pypi/munch/)
+
+munch
+==========
+
+munch is a fork of David Schoonover's **Bunch** package, providing similar functionality. 99% of the work was done by him, and the fork was made mainly for lack of responsiveness for fixes and maintenance on the original code.
+
+Munch is a dictionary that supports attribute-style access, a la JavaScript:
+
+```python
+
+>>> from munch import Munch
+>>> b = Munch()
+>>> b.hello = 'world'
+>>> b.hello
+'world'
+>>> b['hello'] += "!"
+>>> b.hello
+'world!'
+>>> b.foo = Munch(lol=True)
+>>> b.foo.lol
+True
+>>> b.foo is b['foo']
+True
+
+```
+
+
+Dictionary Methods
+------------------
+
+A Munch is a subclass of ``dict``; it supports all the methods a ``dict`` does:
+
+```python
+
+>>> list(b.keys())
+['hello', 'foo']
+
+```
+
+Including ``update()``:
+
+```python
+
+>>> b.update({ 'ponies': 'are pretty!' }, hello=42)
+>>> print(repr(b))
+Munch({'hello': 42, 'foo': Munch({'lol': True}), 'ponies': 'are pretty!'})
+
+```
+
+As well as iteration:
+
+```python
+
+>>> [ (k,b[k]) for k in b ]
+[('hello', 42), ('foo', Munch({'lol': True})), ('ponies', 'are pretty!')]
+
+```
+
+And "splats":
+
+```python
+
+>>> "The {knights} who say {ni}!".format(**Munch(knights='lolcats', ni='can haz'))
+'The lolcats who say can haz!'
+
+```
+
+
+Serialization
+-------------
+
+Munches happily and transparently serialize to JSON and YAML.
+
+```python
+
+>>> b = Munch(foo=Munch(lol=True), hello=42, ponies='are pretty!')
+>>> import json
+>>> json.dumps(b)
+'{"foo": {"lol": true}, "hello": 42, "ponies": "are pretty!"}'
+
+```
+
+If JSON support is present (``json`` or ``simplejson``), ``Munch`` will have a ``toJSON()`` method which returns the object as a JSON string.
+
+If you have [PyYAML](http://pyyaml.org/wiki/PyYAML) installed, Munch attempts to register itself with the various YAML Representers so that Munches can be transparently dumped and loaded.
+
+```python
+
+>>> b = Munch(foo=Munch(lol=True), hello=42, ponies='are pretty!')
+>>> import yaml
+>>> yaml.dump(b)
+'!munch.Munch\nfoo: !munch.Munch\n  lol: true\nhello: 42\nponies: are pretty!\n'
+>>> yaml.safe_dump(b)
+'foo:\n  lol: true\nhello: 42\nponies: are pretty!\n'
+
+```
+
+In addition, Munch instances will have a ``toYAML()`` method that returns the YAML string using ``yaml.safe_dump()``. This method also replaces ``__str__`` if present, as I find it far more readable. You can revert back to Python's default use of ``__repr__`` with a simple assignment: ``Munch.__str__ = Munch.__repr__``. The Munch class will also have a static method ``Munch.fromYAML()``, which loads a Munch out of a YAML string.
+
+Finally, Munch converts easily and recursively to (``unmunchify()``, ``Munch.toDict()``) and from (``munchify()``, ``Munch.fromDict()``) a normal ``dict``, making it easy to cleanly serialize them in other formats.
+
+
+Default Values
+--------------
+
+``DefaultMunch`` instances return a specific default value when an attribute is missing from the collection. Like ``collections.defaultdict``, the first argument is the value to use for missing keys:
+
+```python
+
+>>> from munch import DefaultMunch
+>>> undefined = object()
+>>> b = DefaultMunch(undefined, {'hello': 'world!'})
+>>> b.hello
+'world!'
+>>> b.foo is undefined
+True
+
+```
+
+``DefaultMunch.fromDict()`` also takes the ``default`` argument:
+
+```python
+
+>>> undefined = object()
+>>> b = DefaultMunch.fromDict({'recursively': {'nested': 'value'}}, undefined)
+>>> b.recursively.nested == 'value'
+True
+>>> b.recursively.foo is undefined
+True
+
+```
+
+Or you can use ``DefaultFactoryMunch`` to specify a factory for generating missing attributes. The first argument is the factory:
+
+```python
+
+>>> from munch import DefaultFactoryMunch
+>>> b = DefaultFactoryMunch(list, {'hello': 'world!'})
+>>> b.hello
+'world!'
+>>> b.foo
+[]
+>>> b.bar.append('hello')
+>>> b.bar
+['hello']
+
+```
+
+
+Miscellaneous
+-------------
+
+* It is safe to ``import *`` from this module. You'll get: ``Munch``, ``DefaultMunch``, ``DefaultFactoryMunch``, ``munchify`` and ``unmunchify``.
+* Ample Tests. Just run ``pip install tox && tox`` from the project root.
+
+Feedback
+--------
+
+Open a ticket / fork the project on [GitHub](http://github.com/Infinidat/munch).
+
diff --git a/README.md b/README.md
index 00f5667..3acab25 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,7 @@ Munch is a dictionary that supports attribute-style access, a la JavaScript:
 
 ```python
 
+>>> from munch import Munch
 >>> b = Munch()
 >>> b.hello = 'world'
 >>> b.hello
@@ -110,6 +111,7 @@ Default Values
 
 ```python
 
+>>> from munch import DefaultMunch
 >>> undefined = object()
 >>> b = DefaultMunch(undefined, {'hello': 'world!'})
 >>> b.hello
@@ -136,6 +138,7 @@ Or you can use ``DefaultFactoryMunch`` to specify a factory for generating missi
 
 ```python
 
+>>> from munch import DefaultFactoryMunch
 >>> b = DefaultFactoryMunch(list, {'hello': 'world!'})
 >>> b.hello
 'world!'
diff --git a/debian/changelog b/debian/changelog
index 0c483bd..5e4cf4b 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+python-munch (3.0.0-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Thu, 18 May 2023 01:36:00 -0000
+
 python-munch (2.5.0-2) unstable; urgency=medium
 
   * Run unit tests instead of autopkgtest-pkg-python (Closes: #1006850).
diff --git a/munch.egg-info/PKG-INFO b/munch.egg-info/PKG-INFO
index 73ab86b..04d01e1 100644
--- a/munch.egg-info/PKG-INFO
+++ b/munch.egg-info/PKG-INFO
@@ -1,187 +1,193 @@
 Metadata-Version: 2.1
 Name: munch
-Version: 2.5.0
+Version: 3.0.0
 Summary: A dot-accessible dictionary (a la JavaScript objects)
 Home-page: https://github.com/Infinidat/munch
 Author: Rotem Yaari
 Author-email: vmalloc@gmail.com
 License: MIT
-Description: [![Build Status](https://travis-ci.org/Infinidat/munch.svg?branch=master)](https://travis-ci.org/Infinidat/munch)
-        [![Latest Version](https://img.shields.io/pypi/v/munch.svg)](https://pypi.python.org/pypi/munch/)
-        [![Supported Python versions](https://img.shields.io/pypi/pyversions/munch.svg)](https://pypi.python.org/pypi/munch/)
-        [![Downloads](https://img.shields.io/pypi/dm/munch.svg)](https://pypi.python.org/pypi/munch/)
-        
-        munch
-        ==========
-        
-        munch is a fork of David Schoonover's **Bunch** package, providing similar functionality. 99% of the work was done by him, and the fork was made mainly for lack of responsiveness for fixes and maintenance on the original code.
-        
-        Munch is a dictionary that supports attribute-style access, a la JavaScript:
-        
-        ```python
-        
-        >>> b = Munch()
-        >>> b.hello = 'world'
-        >>> b.hello
-        'world'
-        >>> b['hello'] += "!"
-        >>> b.hello
-        'world!'
-        >>> b.foo = Munch(lol=True)
-        >>> b.foo.lol
-        True
-        >>> b.foo is b['foo']
-        True
-        
-        ```
-        
-        
-        Dictionary Methods
-        ------------------
-        
-        A Munch is a subclass of ``dict``; it supports all the methods a ``dict`` does:
-        
-        ```python
-        
-        >>> list(b.keys())
-        ['hello', 'foo']
-        
-        ```
-        
-        Including ``update()``:
-        
-        ```python
-        
-        >>> b.update({ 'ponies': 'are pretty!' }, hello=42)
-        >>> print(repr(b))
-        Munch({'hello': 42, 'foo': Munch({'lol': True}), 'ponies': 'are pretty!'})
-        
-        ```
-        
-        As well as iteration:
-        
-        ```python
-        
-        >>> [ (k,b[k]) for k in b ]
-        [('hello', 42), ('foo', Munch({'lol': True})), ('ponies', 'are pretty!')]
-        
-        ```
-        
-        And "splats":
-        
-        ```python
-        
-        >>> "The {knights} who say {ni}!".format(**Munch(knights='lolcats', ni='can haz'))
-        'The lolcats who say can haz!'
-        
-        ```
-        
-        
-        Serialization
-        -------------
-        
-        Munches happily and transparently serialize to JSON and YAML.
-        
-        ```python
-        
-        >>> b = Munch(foo=Munch(lol=True), hello=42, ponies='are pretty!')
-        >>> import json
-        >>> json.dumps(b)
-        '{"foo": {"lol": true}, "hello": 42, "ponies": "are pretty!"}'
-        
-        ```
-        
-        If JSON support is present (``json`` or ``simplejson``), ``Munch`` will have a ``toJSON()`` method which returns the object as a JSON string.
-        
-        If you have [PyYAML](http://pyyaml.org/wiki/PyYAML) installed, Munch attempts to register itself with the various YAML Representers so that Munches can be transparently dumped and loaded.
-        
-        ```python
-        
-        >>> b = Munch(foo=Munch(lol=True), hello=42, ponies='are pretty!')
-        >>> import yaml
-        >>> yaml.dump(b)
-        '!munch.Munch\nfoo: !munch.Munch\n  lol: true\nhello: 42\nponies: are pretty!\n'
-        >>> yaml.safe_dump(b)
-        'foo:\n  lol: true\nhello: 42\nponies: are pretty!\n'
-        
-        ```
-        
-        In addition, Munch instances will have a ``toYAML()`` method that returns the YAML string using ``yaml.safe_dump()``. This method also replaces ``__str__`` if present, as I find it far more readable. You can revert back to Python's default use of ``__repr__`` with a simple assignment: ``Munch.__str__ = Munch.__repr__``. The Munch class will also have a static method ``Munch.fromYAML()``, which loads a Munch out of a YAML string.
-        
-        Finally, Munch converts easily and recursively to (``unmunchify()``, ``Munch.toDict()``) and from (``munchify()``, ``Munch.fromDict()``) a normal ``dict``, making it easy to cleanly serialize them in other formats.
-        
-        
-        Default Values
-        --------------
-        
-        ``DefaultMunch`` instances return a specific default value when an attribute is missing from the collection. Like ``collections.defaultdict``, the first argument is the value to use for missing keys:
-        
-        ```python
-        
-        >>> undefined = object()
-        >>> b = DefaultMunch(undefined, {'hello': 'world!'})
-        >>> b.hello
-        'world!'
-        >>> b.foo is undefined
-        True
-        
-        ```
-        
-        ``DefaultMunch.fromDict()`` also takes the ``default`` argument:
-        
-        ```python
-        
-        >>> undefined = object()
-        >>> b = DefaultMunch.fromDict({'recursively': {'nested': 'value'}}, undefined)
-        >>> b.recursively.nested == 'value'
-        True
-        >>> b.recursively.foo is undefined
-        True
-        
-        ```
-        
-        Or you can use ``DefaultFactoryMunch`` to specify a factory for generating missing attributes. The first argument is the factory:
-        
-        ```python
-        
-        >>> b = DefaultFactoryMunch(list, {'hello': 'world!'})
-        >>> b.hello
-        'world!'
-        >>> b.foo
-        []
-        >>> b.bar.append('hello')
-        >>> b.bar
-        ['hello']
-        
-        ```
-        
-        
-        Miscellaneous
-        -------------
-        
-        * It is safe to ``import *`` from this module. You'll get: ``Munch``, ``DefaultMunch``, ``DefaultFactoryMunch``, ``munchify`` and ``unmunchify``.
-        * Ample Tests. Just run ``pip install tox && tox`` from the project root.
-        
-        Feedback
-        --------
-        
-        Open a ticket / fork the project on [GitHub](http://github.com/Infinidat/munch).
-        
-        
 Keywords: munch,dict,mapping,container,collection
-Platform: UNKNOWN
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Intended Audience :: Developers
 Classifier: Operating System :: OS Independent
 Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 2.7
-Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
 Classifier: Topic :: Software Development
 Classifier: Topic :: Software Development :: Libraries
 Classifier: Topic :: Utilities
 Classifier: License :: OSI Approved :: MIT License
+Requires-Python: >=3.6
 Description-Content-Type: text/markdown
 Provides-Extra: testing
 Provides-Extra: yaml
+License-File: LICENSE.txt
+
+[![Build Status](https://travis-ci.org/Infinidat/munch.svg?branch=master)](https://travis-ci.org/Infinidat/munch)
+[![Latest Version](https://img.shields.io/pypi/v/munch.svg)](https://pypi.python.org/pypi/munch/)
+[![Supported Python versions](https://img.shields.io/pypi/pyversions/munch.svg)](https://pypi.python.org/pypi/munch/)
+[![Downloads](https://img.shields.io/pypi/dm/munch.svg)](https://pypi.python.org/pypi/munch/)
+
+munch
+==========
+
+munch is a fork of David Schoonover's **Bunch** package, providing similar functionality. 99% of the work was done by him, and the fork was made mainly for lack of responsiveness for fixes and maintenance on the original code.
+
+Munch is a dictionary that supports attribute-style access, a la JavaScript:
+
+```python
+
+>>> from munch import Munch
+>>> b = Munch()
+>>> b.hello = 'world'
+>>> b.hello
+'world'
+>>> b['hello'] += "!"
+>>> b.hello
+'world!'
+>>> b.foo = Munch(lol=True)
+>>> b.foo.lol
+True
+>>> b.foo is b['foo']
+True
+
+```
+
+
+Dictionary Methods
+------------------
+
+A Munch is a subclass of ``dict``; it supports all the methods a ``dict`` does:
+
+```python
+
+>>> list(b.keys())
+['hello', 'foo']
+
+```
+
+Including ``update()``:
+
+```python
+
+>>> b.update({ 'ponies': 'are pretty!' }, hello=42)
+>>> print(repr(b))
+Munch({'hello': 42, 'foo': Munch({'lol': True}), 'ponies': 'are pretty!'})
+
+```
+
+As well as iteration:
+
+```python
+
+>>> [ (k,b[k]) for k in b ]
+[('hello', 42), ('foo', Munch({'lol': True})), ('ponies', 'are pretty!')]
+
+```
+
+And "splats":
+
+```python
+
+>>> "The {knights} who say {ni}!".format(**Munch(knights='lolcats', ni='can haz'))
+'The lolcats who say can haz!'
+
+```
+
+
+Serialization
+-------------
+
+Munches happily and transparently serialize to JSON and YAML.
+
+```python
+
+>>> b = Munch(foo=Munch(lol=True), hello=42, ponies='are pretty!')
+>>> import json
+>>> json.dumps(b)
+'{"foo": {"lol": true}, "hello": 42, "ponies": "are pretty!"}'
+
+```
+
+If JSON support is present (``json`` or ``simplejson``), ``Munch`` will have a ``toJSON()`` method which returns the object as a JSON string.
+
+If you have [PyYAML](http://pyyaml.org/wiki/PyYAML) installed, Munch attempts to register itself with the various YAML Representers so that Munches can be transparently dumped and loaded.
+
+```python
+
+>>> b = Munch(foo=Munch(lol=True), hello=42, ponies='are pretty!')
+>>> import yaml
+>>> yaml.dump(b)
+'!munch.Munch\nfoo: !munch.Munch\n  lol: true\nhello: 42\nponies: are pretty!\n'
+>>> yaml.safe_dump(b)
+'foo:\n  lol: true\nhello: 42\nponies: are pretty!\n'
+
+```
+
+In addition, Munch instances will have a ``toYAML()`` method that returns the YAML string using ``yaml.safe_dump()``. This method also replaces ``__str__`` if present, as I find it far more readable. You can revert back to Python's default use of ``__repr__`` with a simple assignment: ``Munch.__str__ = Munch.__repr__``. The Munch class will also have a static method ``Munch.fromYAML()``, which loads a Munch out of a YAML string.
+
+Finally, Munch converts easily and recursively to (``unmunchify()``, ``Munch.toDict()``) and from (``munchify()``, ``Munch.fromDict()``) a normal ``dict``, making it easy to cleanly serialize them in other formats.
+
+
+Default Values
+--------------
+
+``DefaultMunch`` instances return a specific default value when an attribute is missing from the collection. Like ``collections.defaultdict``, the first argument is the value to use for missing keys:
+
+```python
+
+>>> from munch import DefaultMunch
+>>> undefined = object()
+>>> b = DefaultMunch(undefined, {'hello': 'world!'})
+>>> b.hello
+'world!'
+>>> b.foo is undefined
+True
+
+```
+
+``DefaultMunch.fromDict()`` also takes the ``default`` argument:
+
+```python
+
+>>> undefined = object()
+>>> b = DefaultMunch.fromDict({'recursively': {'nested': 'value'}}, undefined)
+>>> b.recursively.nested == 'value'
+True
+>>> b.recursively.foo is undefined
+True
+
+```
+
+Or you can use ``DefaultFactoryMunch`` to specify a factory for generating missing attributes. The first argument is the factory:
+
+```python
+
+>>> from munch import DefaultFactoryMunch
+>>> b = DefaultFactoryMunch(list, {'hello': 'world!'})
+>>> b.hello
+'world!'
+>>> b.foo
+[]
+>>> b.bar.append('hello')
+>>> b.bar
+['hello']
+
+```
+
+
+Miscellaneous
+-------------
+
+* It is safe to ``import *`` from this module. You'll get: ``Munch``, ``DefaultMunch``, ``DefaultFactoryMunch``, ``munchify`` and ``unmunchify``.
+* Ample Tests. Just run ``pip install tox && tox`` from the project root.
+
+Feedback
+--------
+
+Open a ticket / fork the project on [GitHub](http://github.com/Infinidat/munch).
+
diff --git a/munch.egg-info/SOURCES.txt b/munch.egg-info/SOURCES.txt
index 7f1d496..63f7824 100644
--- a/munch.egg-info/SOURCES.txt
+++ b/munch.egg-info/SOURCES.txt
@@ -12,6 +12,9 @@ requirements.txt
 setup.cfg
 setup.py
 tox.ini
+.github/workflows/ci.yml
+.github/workflows/dependabot.yml
+.github/workflows/python-publish.yml
 munch/__init__.py
 munch/python3_compat.py
 munch.egg-info/PKG-INFO
diff --git a/munch.egg-info/pbr.json b/munch.egg-info/pbr.json
index 4796fc1..a880e5b 100644
--- a/munch.egg-info/pbr.json
+++ b/munch.egg-info/pbr.json
@@ -1 +1 @@
-{"git_version": "292b8eb", "is_release": true}
\ No newline at end of file
+{"git_version": "edd1669", "is_release": false}
\ No newline at end of file
diff --git a/munch.egg-info/requires.txt b/munch.egg-info/requires.txt
index ae1db09..fd8b648 100644
--- a/munch.egg-info/requires.txt
+++ b/munch.egg-info/requires.txt
@@ -1,16 +1,13 @@
 six
 
-[testing]
-pytest
-coverage
-
-[testing:python_version == "2.7"]
-astroid~=1.5.3
-pylint~=1.7.2
+[:(python_version<'3.8')]
+importlib_metadata>=1.7.0
 
-[testing:python_version >= "3.4"]
+[testing]
 astroid>=2.0
 pylint~=2.3.1
+pytest
+coverage
 
 [yaml]
 PyYAML>=5.1.0
diff --git a/munch/__init__.py b/munch/__init__.py
index ec7a6b3..c331da0 100644
--- a/munch/__init__.py
+++ b/munch/__init__.py
@@ -21,14 +21,27 @@
     converted via Munch.to/fromDict().
 """
 
-import pkg_resources
-
 from .python3_compat import iterkeys, iteritems, Mapping, u
 
-__version__ = pkg_resources.get_distribution('munch').version
-VERSION = tuple(map(int, __version__.split('.')[:3]))
+try:
+    # For python 3.8 and later
+    import importlib.metadata as importlib_metadata
+except ImportError:
+    # For everyone else
+    import importlib_metadata
+try:
+    __version__ = importlib_metadata.version(__name__)
+except importlib_metadata.PackageNotFoundError:
+    # package is not installed
+    __version__ = "0.0.0"
+
+
+try:
+    VERSION = tuple(map(int, __version__.split('+')[0].split('.')[:3]))
+except ValueError:
+    VERSION = (0, 0, 0)
 
-__all__ = ('Munch', 'munchify', 'DefaultMunch', 'DefaultFactoryMunch', 'unmunchify')
+__all__ = ('Munch', 'munchify', 'DefaultMunch', 'DefaultFactoryMunch', 'RecursiveMunch', 'unmunchify')
 
 
 
@@ -189,7 +202,7 @@ class Munch(dict):
 
             (*) Invertible so long as collection contents are each repr-invertible.
         """
-        return '{0}({1})'.format(self.__class__.__name__, dict.__repr__(self))
+        return f'{self.__class__.__name__}({dict.__repr__(self)})'
 
     def __dir__(self):
         return list(iterkeys(self))
@@ -258,7 +271,7 @@ class AutoMunch(Munch):
         """
         if isinstance(v, Mapping) and not isinstance(v, (AutoMunch, Munch)):
             v = munchify(v, AutoMunch)
-        super(AutoMunch, self).__setattr__(k, v)
+        super().__setattr__(k, v)
 
 
 class DefaultMunch(Munch):
@@ -277,13 +290,13 @@ class DefaultMunch(Munch):
             args = args[1:]
         else:
             default = None
-        super(DefaultMunch, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
         self.__default__ = default
 
     def __getattr__(self, k):
         """ Gets key if it exists, otherwise returns the default value."""
         try:
-            return super(DefaultMunch, self).__getattr__(k)
+            return super().__getattr__(k)
         except AttributeError:
             return self.__default__
 
@@ -291,12 +304,12 @@ class DefaultMunch(Munch):
         if k == '__default__':
             object.__setattr__(self, k, v)
         else:
-            super(DefaultMunch, self).__setattr__(k, v)
+            super().__setattr__(k, v)
 
     def __getitem__(self, k):
         """ Gets key if it exists, otherwise returns the default value."""
         try:
-            return super(DefaultMunch, self).__getitem__(k)
+            return super().__getitem__(k)
         except KeyError:
             return self.__default__
 
@@ -326,8 +339,7 @@ class DefaultMunch(Munch):
         return type(self).fromDict(self, default=self.__default__)
 
     def __repr__(self):
-        return '{0}({1!r}, {2})'.format(
-            type(self).__name__, self.__undefined__, dict.__repr__(self))
+        return f'{type(self).__name__}({self.__undefined__!r}, {dict.__repr__(self)})'
 
 
 class DefaultFactoryMunch(Munch):
@@ -345,7 +357,7 @@ class DefaultFactoryMunch(Munch):
     """
 
     def __init__(self, default_factory, *args, **kwargs):
-        super(DefaultFactoryMunch, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
         self.default_factory = default_factory
 
     @classmethod
@@ -358,20 +370,48 @@ class DefaultFactoryMunch(Munch):
 
     def __repr__(self):
         factory = self.default_factory.__name__
-        return '{0}({1}, {2})'.format(
-            type(self).__name__, factory, dict.__repr__(self))
+        return f'{type(self).__name__}({factory}, {dict.__repr__(self)})'
 
     def __setattr__(self, k, v):
         if k == 'default_factory':
             object.__setattr__(self, k, v)
         else:
-            super(DefaultFactoryMunch, self).__setattr__(k, v)
+            super().__setattr__(k, v)
 
     def __missing__(self, k):
         self[k] = self.default_factory()
         return self[k]
 
 
+class RecursiveMunch(DefaultFactoryMunch):
+    """A Munch that calls an instance of itself to generate values for
+        missing keys.
+
+        >>> b = RecursiveMunch({'hello': 'world!'})
+        >>> b.hello
+        'world!'
+        >>> b.foo
+        RecursiveMunch(RecursiveMunch, {})
+        >>> b.bar.okay = 'hello'
+        >>> b.bar
+        RecursiveMunch(RecursiveMunch, {'okay': 'hello'})
+        >>> b
+        RecursiveMunch(RecursiveMunch, {'hello': 'world!', 'foo': RecursiveMunch(RecursiveMunch, {}),
+        'bar': RecursiveMunch(RecursiveMunch, {'okay': 'hello'})})
+    """
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(RecursiveMunch, *args, **kwargs)
+
+    @classmethod
+    def fromDict(cls, d):
+        # pylint: disable=arguments-differ
+        return munchify(d, factory=cls)
+
+    def copy(self):
+        return type(self).fromDict(self)
+
+
 # While we could convert abstract types like Mapping or Iterable, I think
 # munchify is more likely to "do what you mean" if it is conservative about
 # casting (ex: isinstance(str,Iterable) == True ).
@@ -402,16 +442,21 @@ def munchify(x, factory=Munch):
     seen = dict()
 
     def munchify_cycles(obj):
+        partial, already_seen = pre_munchify_cycles(obj)
+        if already_seen:
+            return partial
+        return post_munchify(partial, obj)
+
+    def pre_munchify_cycles(obj):
         # If we've already begun munchifying obj, just return the already-created munchified obj
         try:
-            return seen[id(obj)]
+            return seen[id(obj)], True
         except KeyError:
             pass
 
         # Otherwise, first partly munchify obj (but without descending into any lists or dicts) and save that
         seen[id(obj)] = partial = pre_munchify(obj)
-        # Then finish munchifying lists and dicts inside obj (reusing munchified obj if cycles are encountered)
-        return post_munchify(partial, obj)
+        return partial, False
 
     def pre_munchify(obj):
         # Here we return a skeleton of munchified obj, which is enough to save for later (in case
@@ -422,7 +467,7 @@ def munchify(x, factory=Munch):
             return type(obj)()
         elif isinstance(obj, tuple):
             type_factory = getattr(obj, "_make", type(obj))
-            return type_factory(munchify_cycles(item) for item in obj)
+            return type_factory(pre_munchify_cycles(item)[0] for item in obj)
         else:
             return obj
 
diff --git a/munch/python3_compat.py b/munch/python3_compat.py
index 4f30702..9ee2b3d 100644
--- a/munch/python3_compat.py
+++ b/munch/python3_compat.py
@@ -3,4 +3,4 @@ try:
     from collections.abc import Mapping  # pylint: disable=unused-import
 except ImportError:
     # Legacy Python
-    from collections import Mapping  # pylint: disable=unused-import
+    from collections.abc import Mapping  # pylint: disable=unused-import
diff --git a/requirements.txt b/requirements.txt
index ffe2fce..2fd8e98 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1 +1,2 @@
 six
+importlib_metadata>=1.7.0;python_version<'3.8' # Apache-2.0
diff --git a/setup.cfg b/setup.cfg
index 479c038..04d89ca 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -5,17 +5,18 @@ classifiers =
 	Intended Audience :: Developers
 	Operating System :: OS Independent
 	Programming Language :: Python
-	Programming Language :: Python :: 2.7
-	Programming Language :: Python :: 3.5
 	Programming Language :: Python :: 3.6
 	Programming Language :: Python :: 3.7
+	Programming Language :: Python :: 3.8
+	Programming Language :: Python :: 3.9
+	Programming Language :: Python :: 3.10
+	Programming Language :: Python :: 3.11
 	Topic :: Software Development
 	Topic :: Software Development :: Libraries
 	Topic :: Utilities
 	License :: OSI Approved :: MIT License
 summary = A dot-accessible dictionary (a la JavaScript objects)
-description-file = 
-	README.md
+description-file = README.md
 description-content-type = text/markdown
 license = MIT
 author = Rotem Yaari
@@ -24,10 +25,8 @@ url = https://github.com/Infinidat/munch
 
 [extras]
 testing = 
-	astroid~=1.5.3; python_version=='2.7'
-	astroid>=2.0; python_version >= '3.4'
-	pylint~=1.7.2; python_version=='2.7'
-	pylint~=2.3.1; python_version >= '3.4'
+	astroid>=2.0
+	pylint~=2.3.1
 	pytest
 	coverage
 yaml = 
diff --git a/setup.py b/setup.py
index 31f229c..5370a61 100644
--- a/setup.py
+++ b/setup.py
@@ -2,8 +2,9 @@ from setuptools import setup
 
 
 setup(
-    setup_requires=['pbr>=3.0', 'setuptools>=17.1'],
+    setup_requires=['pbr>=3.0', 'setuptools>=61'],
     pbr=True,
     long_description_content_type='text/markdown; charset=UTF-8',
     keywords=['munch', 'dict', 'mapping', 'container', 'collection'],
+    python_requires=">=3.6",
 )
diff --git a/tests/conftest.py b/tests/conftest.py
index 45d6b23..9a69009 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -5,14 +5,15 @@ import munch
 @pytest.fixture(name='yaml')
 def yaml_module():
     try:
-        import yaml
+        import yaml  # pylint: disable=import-outside-toplevel
         return yaml
     except ImportError:
         pass
     pytest.skip("Module 'PyYAML' is required")
 
 
-@pytest.fixture(params=[munch.Munch, munch.AutoMunch, munch.DefaultMunch, munch.DefaultFactoryMunch])
+@pytest.fixture(params=[munch.Munch, munch.AutoMunch, munch.DefaultMunch, munch.DefaultFactoryMunch,
+                        munch.RecursiveMunch])
 def munch_obj(request):
     cls = request.param
     args = tuple()
diff --git a/tests/test_munch.py b/tests/test_munch.py
index 9dadcee..c80b757 100644
--- a/tests/test_munch.py
+++ b/tests/test_munch.py
@@ -520,7 +520,7 @@ def test_setitem_dunder_for_subclass():
     def test_class(cls, *args):
         class CustomMunch(cls):
             def __setitem__(self, k, v):
-                super(CustomMunch, self).__setitem__(k, [v] * 2)
+                super().__setitem__(k, [v] * 2)
         custom_munch = CustomMunch(*args, a='foo')
         assert custom_munch.a == ['foo', 'foo']
         regular_dict = {}
@@ -547,5 +547,27 @@ def test_getitem_dunder_for_subclass():
     assert custom_munch.copy() == Munch(a=42, b=42)
 
 
+@pytest.mark.usefixtures("yaml")
 def test_get_default_value(munch_obj):
     assert munch_obj.get("fake_key", "default_value") == "default_value"
+    assert isinstance(munch_obj.toJSON(), str)
+    assert isinstance(munch_obj.toYAML(), str)
+    munch_obj.copy()
+    data = munch_obj.toDict()
+    munch_cls = type(munch_obj)
+    kwargs = {} if munch_cls != DefaultFactoryMunch else {"default_factory": munch_obj.default_factory}
+    munch_cls.fromDict(data, **kwargs)
+
+
+def test_munchify_tuple_list():
+    data = ([{'A': 'B'}],)
+    actual = munchify(data)
+    expected = ([Munch(A='B')],)
+    assert actual == expected
+
+
+def test_munchify_tuple_list_more_elements():
+    data = (1, 2, [{'A': 'B'}])
+    actual = munchify(data)
+    expected = (1, 2, [Munch({'A': 'B'})])
+    assert actual == expected
diff --git a/tests/test_readme.py b/tests/test_readme.py
index 9f6ad26..e220a9e 100644
--- a/tests/test_readme.py
+++ b/tests/test_readme.py
@@ -1,26 +1,16 @@
-from __future__ import print_function
-
 import doctest
 import os
-import sys
 import pytest
 
-import munch
-
 _HERE = os.path.abspath(os.path.dirname(__file__))
 _README_PATH = os.path.join(_HERE, '..', 'README.md')
 assert os.path.exists(_README_PATH)
 
 
-@pytest.mark.skipif(sys.version_info[:2] < (3, 6), reason="Requires Python version >= 3.6")
 @pytest.mark.usefixtures("yaml")
 def test_readme():
     globs = {
-        'print_function': print_function,
-        'munch': munch,
-        'Munch': munch.Munch,
-        'DefaultMunch': munch.DefaultMunch,
-        'DefaultFactoryMunch': munch.DefaultFactoryMunch,
+        'print_function': print
     }
     result = doctest.testfile(_README_PATH, module_relative=False, globs=globs)
     assert not result.failed

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/munch-3.0.0.egg-info/PKG-INFO
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/munch-3.0.0.egg-info/dependency_links.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/munch-3.0.0.egg-info/not-zip-safe
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/munch-3.0.0.egg-info/pbr.json
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/munch-3.0.0.egg-info/requires.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/munch-3.0.0.egg-info/top_level.txt

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/munch-2.5.0.egg-info/PKG-INFO
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/munch-2.5.0.egg-info/dependency_links.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/munch-2.5.0.egg-info/not-zip-safe
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/munch-2.5.0.egg-info/pbr.json
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/munch-2.5.0.egg-info/requires.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/munch-2.5.0.egg-info/top_level.txt

Control files: lines which differ (wdiff format)

  • Depends: python3-pkg-resources, python3-six, python3-importlib-metadata | python3 (>> 3.8), python3:any

More details

Full run details