Skip to content

Commit 2f67854

Browse files
committed
2.3.0 - New collections module + refactoring
**Minor updates** - `docs/source/conf.py` now sets `PYTHON_PATH` which helps reduce issues with Sphinx finding the privex package folder - Fleshed out `docs/source/examples.rst` with `DictObject` and `dictable_namedtuple` examples. - Added documentation for `privex.helpers.collections` and `tests.test_tuple` - Added `Pipfile` and `Pipfile.lock` for use with `pipenv` during development **Major changes** - Created module `privex.helpers.collections` - `DictObject` - A `dict` sub-class which allows keys to be read/written via attributes (`x.something`) as well as standard item/key notation (`x['something']`) - `MockDictObj` - Same as `DictObject`, but masquerades as the builtin `dict`, potentially allowing it to be used with certain code that expects the builtin dict type - `is_namedtuple` - Boolean function which returns `True` if all passed objects are named tuples - `dictable_namedtuple` - A sub-class of the native `collections.namedtuple`, which adds additional functionality such as dict-like key/item access to fields, ability to cast directly to a dict, and ability to add new fields dynamically to an existing instance. - `convert_dictable_namedtuple` - Converts a `namedtuple` type class instance into a `dictable_namedtuple` instance - `subclass_dictable_namedtuple` - Converts a `namedtuple` type/class into a `dictable_namedtuple` type/class - Created unit tests for `is_namedtuple` and `dictable_namedtuple` in `tests/test_tuple.py` **BREAKING CHANGES** - `Mocker` has been moved from `privex.helpers.common` into `privex.helpers.collections`. Code which imports via `from privex.helpers.common import Mocker` will no longer work. Code which imports `Mocker` from the shared `privex.helpers` module (i.e. `from privex.helpers import Mocker`) should be unaffected. - `Dictable` has been moved from `privex.helpers.common` into `privex.helpers.collections`. Code which imports via `from privex.helpers.common import Dictable` will no longer work. Code which imports `Dictable` from the shared `privex.helpers` module (i.e. `from privex.helpers import Dictable`) should be unaffected.
1 parent 3b43a17 commit 2f67854

52 files changed

Lines changed: 2404 additions & 297 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Pipfile

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
[[source]]
2+
name = "pypi"
3+
url = "https://pypi.org/simple"
4+
verify_ssl = true
5+
6+
[dev-packages]
7+
pytest = "*"
8+
pytest-cov = "*"
9+
coverage = "*"
10+
codecov = "*"
11+
twine = "*"
12+
semver = "*"
13+
Sphinx = ">=2.1.2"
14+
sphinx-autobuild = ">=0.7.1"
15+
restructuredtext-lint = ">=1.3.0"
16+
sphinx-rtd-theme = ">=0.4.3"
17+
docutils = ">=0.14"
18+
wheel = "*"
19+
setuptools = "*"
20+
21+
[packages]
22+
privex-loghelper = "*"
23+
redis = ">=3.3"
24+
cryptography = ">=2.8"
25+
dnspython = ">=1.16"
26+
Django = "*"
27+
MarkupSafe = ">=1.1.1"
28+
wheel = "*"
29+
30+
[requires]
31+
python_version = "3.8"

Pipfile.lock

Lines changed: 656 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/source/conf.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@
2424
BASE_DIR = dirname(dirname(dirname(abspath(__file__))))
2525

2626
sys.path.insert(0, BASE_DIR)
27+
PY_PATH = os.getenv('PYTHON_PATH', '')
28+
29+
if PY_PATH != '':
30+
PY_PATH = ':' + PY_PATH
31+
32+
os.environ['PYTHON_PATH'] = BASE_DIR + PY_PATH
2733

2834
# -- Project information -----------------------------------------------------
2935

docs/source/examples.rst

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,133 @@ easily converted into a dictionary using ``dict()``.
9999
env_keyval('NOEXIST', {})
100100
# returns: {}
101101
102+
103+
Improved collections, including dict's and namedtuple's
104+
=======================================================
105+
106+
In our :py:mod:`privex.helpers.collections` module (plus maybe a few things in :py:mod:`privex.helpers.common`),
107+
we have various functions and classes designed to make working with Python's storage types more painless, while
108+
trying to keep compatibility with code that expects the native types.
109+
110+
111+
Dictionaries with dot notation attribute read/write
112+
---------------------------------------------------
113+
114+
Dictionaries (``dict``) are powerful, and easy to deal with. But why can't you read or write dictionary items with
115+
attribute dot notation!?
116+
117+
This is where :class:`.DictObject` comes in to save the day. It's a child class of python's native :class:`dict` type, which
118+
means it's still compatible with functions/methods such as :func:`json.dumps`, and in most cases will be plug-n-play with
119+
existing dict-using code.
120+
121+
**Basic usage**
122+
123+
.. code-block:: python
124+
125+
from privex.helpers import DictObject
126+
127+
x = dict(hello='world', lorem='ipsum')
128+
x['hello'] # This works with a normal dict
129+
x.hello # But this raises: AttributeError: 'dict' object has no attribute 'hello'
130+
131+
# We can cast the dict 'x' into a DictObject
132+
y = DictObject(x)
133+
y['hello'] # Returns: 'world'
134+
y.hello # Returns: 'world'
135+
136+
# Not only can you access dict keys via attributes, you can also set keys via attributes
137+
y.example = 'testing'
138+
y # We can see below that setting 'example' worked as expected.
139+
# Output: {'hello': 'world', 'lorem': 'ipsum', 'example': 'testing'}
140+
141+
142+
**Type checking / Equality comparison**
143+
144+
As :class:`.DictObject` is a subclass of :class:`dict`, you can use :func:`isinstance` to check against :class:`dict`
145+
(e.g. ``isinstance(DictObject(), dict)``) and it should return True.
146+
147+
You can also compare dictionary equality between a :class:`.DictObject` and a :class:`dict` using ``==`` as normal.
148+
149+
.. code-block:: python
150+
151+
y = DictObject(hello='world')
152+
153+
isinstance(y, dict) # You should always use isinstance instead of `type(x) == dict`
154+
# Returns: True
155+
156+
# You can also use typing.Dict with isinstance when checking a DictObject
157+
from typing import Dict
158+
isinstance(y, Dict) # Returns: True
159+
160+
# You can compare equality between a DictObject and a dict with no problems
161+
DictObject(hello='world') == dict(hello='world')
162+
# Returns: True
163+
DictObject(hello='world') == dict(hello='example')
164+
# Returns: False
165+
166+
**Type Masquerading**
167+
168+
Also included is the class :class:`.MockDictObj`, which is a subclass of :class:`.DictObject` with it's name, qualified name,
169+
and module adjusted so that it appears to be the builtin :class:`dict` type.
170+
171+
This may help in some cases, but sadly can't fool a ``type(x) == dict`` check.
172+
173+
.. code-block:: python
174+
175+
from privex.helpers import MockDictObj
176+
z = MockDictObj(y)
177+
type(z) # Returns: <class 'dict'>
178+
z.__class__.__module__ # Returns: 'builtins'
179+
180+
181+
182+
Named Tuple's (namedtuple) with dict-like key access, dict casting, and writable fields
183+
---------------------------------------------------------------------------------------
184+
185+
A somewhat simpler version of :class:`dict`'s are :func:`collections.namedtuple`'s
186+
187+
Unfortunately they have a few quirks that can make them annoying to deal with.
188+
189+
.. code-block:: python
190+
191+
Person = namedtuple('Person', 'first_name last_name') # This is an existing namedtuple "type" or "class"
192+
john = Person('John', 'Doe') # This is an existing namedtuple instance
193+
john.first_name # This works on a standard namedtuple. Returns: John
194+
john[1] # This works on a standard namedtuple. Returns: Doe
195+
john['first_name'] # However, this would throw a TypeError.
196+
dict(john) # This would throw a ValueError.
197+
john.address = '123 Fake St' # This raises an AttributeError.
198+
199+
Thus, we created :func:`.dictable_namedtuple` (and more), which creates namedtuples with additional functionality,
200+
including item/key access of fields, easy casting into dictionaries, and ability to add new fields.
201+
202+
.. code-block:: python
203+
204+
from privex.helpers import dictable_namedtuple
205+
Person = dictable_namedtuple('Person', 'first_name last_name')
206+
john = Person('John', 'Doe')
207+
dave = Person(first_name='Dave', last_name='Smith')
208+
print(dave['first_name']) # Prints: Dave
209+
print(dave.first_name) # Prints: Dave
210+
print(john[1]) # Prints: Doe
211+
print(dict(john)) # Prints: {'first_name': 'John', 'last_name': 'Doe'}
212+
john.address = '123 Fake St' # Unlike normal namedtuple, we can add new fields
213+
print(john) # Prints: Person(first_name='John', last_name='Doe', address='123 Fake St')
214+
215+
216+
You can use :func:`.convert_dictable_namedtuple` to convert existing ``namedtuple`` instancess
217+
into ``dictable_namedtuple`` instances:
218+
219+
.. code-block:: python
220+
221+
Person = namedtuple('Person', 'first_name last_name') # This is an existing namedtuple "type" or "class"
222+
john = Person('John', 'Doe') # This is an existing namedtuple instance
223+
224+
d_john = convert_dictable_namedtuple(john)
225+
d_john.first_name # Returns: John
226+
d_john[1] # Returns: Doe
227+
d_john['first_name'] # Returns: 'John'
228+
dict(d_john) # Returns: {'first_name': 'John', 'last_name': 'Doe'}
229+
230+
For more information, check out the module docs at :mod:`privex.helpers.collections`
231+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from\_dict
2+
==========
3+
4+
.. currentmodule:: privex.helpers.collections
5+
6+
.. automethod:: Dictable.from_dict

docs/source/helpers/common/mocker/privex.helpers.common.Mocker.__init__.rst renamed to docs/source/helpers/collections/mocker/privex.helpers.collections.Mocker.__init__.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
\_\_init\_\_
22
============
33

4-
.. currentmodule:: privex.helpers.common
4+
.. currentmodule:: privex.helpers.collections
55

66
.. automethod:: Mocker.__init__

docs/source/helpers/common/mocker/privex.helpers.common.Mocker.add_mock_module.rst renamed to docs/source/helpers/collections/mocker/privex.helpers.collections.Mocker.add_mock_module.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
add\_mock\_module
22
=================
33

4-
.. currentmodule:: privex.helpers.common
4+
.. currentmodule:: privex.helpers.collections
55

66
.. automethod:: Mocker.add_mock_module

docs/source/helpers/common/mocker/privex.helpers.common.Mocker.make_mock_class.rst renamed to docs/source/helpers/collections/mocker/privex.helpers.collections.Mocker.make_mock_class.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
make\_mock\_class
22
=================
33

4-
.. currentmodule:: privex.helpers.common
4+
.. currentmodule:: privex.helpers.collections
55

66
.. automethod:: Mocker.make_mock_class
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
DictObject
2+
==========
3+
4+
.. currentmodule:: privex.helpers.collections
5+
6+
.. autoclass:: DictObject
7+
8+
9+
.. automethod:: __init__
10+
:noindex:
11+
12+
13+
Methods
14+
^^^^^^^
15+
16+
.. rubric:: Methods
17+
18+
.. autosummary::
19+
:toctree: dictobject
20+
21+
22+
23+
24+
25+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Dictable
2+
========
3+
4+
.. currentmodule:: privex.helpers.collections
5+
6+
.. autoclass:: Dictable
7+
8+
9+
.. automethod:: __init__
10+
:noindex:
11+
12+
13+
Methods
14+
^^^^^^^
15+
16+
.. rubric:: Methods
17+
18+
.. autosummary::
19+
:toctree: dictable
20+
21+
~Dictable.from_dict
22+
23+
24+
25+
26+

0 commit comments

Comments
 (0)