Skip to content

Release 1.0.0 #16

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 60 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
7f3eb54
try to not subclass from `dict` (should be a bit faster)
cb-rnag Jun 25, 2022
4874795
minor refactor
rnag Jun 26, 2022
bbae7d6
update docs
rnag Jun 26, 2022
a227a71
finish adding ``dict`` methods
rnag Jun 26, 2022
e10fc7e
add `__dir__()` method for `DotWizPlus`
rnag Jun 26, 2022
6d1cca5
add `__dir__()` method for `DotWizPlus`
rnag Jun 26, 2022
ec17b20
add param `check_lists` to `DotWiz.__init__`
rnag Jun 26, 2022
277de26
add `attrdict` and `SimpleNamespace` to benchmarks
cb-rnag Jun 28, 2022
0fcc855
Merge remote-tracking branch 'origin/release-0.4.0' into release-0.4.0
cb-rnag Jun 28, 2022
3f550ec
I realized I didn't add `conftest.py`
cb-rnag Jun 28, 2022
5f0e9ca
add tests for `to_json` and `__dir__`
cb-rnag Jun 28, 2022
cb8dd1d
add tests for better test coverage
cb-rnag Jun 30, 2022
5b942d3
minor changes
cb-rnag Jun 30, 2022
0cb3811
add __reversed__
cb-rnag Jun 30, 2022
3b8263c
that should say "wrapper"
cb-rnag Jun 30, 2022
b9304f7
add __or__ methods
cb-rnag Jun 30, 2022
994a707
update with `check_lists`
cb-rnag Jun 30, 2022
897a1a1
fix tests to pass for Python 3.7 and 3.8
cb-rnag Jun 30, 2022
6a77b19
minor fix
cb-rnag Jun 30, 2022
1222f22
update `DotWizPlus` to be a `dict` wrapper instead
cb-rnag Jun 30, 2022
3dc4163
update `DotWizPlus` to perform a little better
cb-rnag Jul 1, 2022
e66fb26
update `__or__` and `__ror__` implementations for `DotWizPlus`
cb-rnag Jul 1, 2022
5c14547
update `__ior__` implementations for `DotWizPlus`
cb-rnag Jul 1, 2022
34b15ed
update `__ior__` implementations for `DotWizPlus`
cb-rnag Jul 1, 2022
d510d26
minor updates
cb-rnag Jul 1, 2022
fcad4b3
update .pyi files to resolve warnings
cb-rnag Jul 1, 2022
d1797fb
add *pragma: no cover*
cb-rnag Jul 1, 2022
9a03465
fix `DotWizPlus.to_dict` to working
cb-rnag Jul 1, 2022
17fc994
fix `DotWizPlus.to_dict` to work as expected
cb-rnag Jul 1, 2022
a70a960
add tests for `DotWizPlus`
cb-rnag Jul 1, 2022
26ee0ae
move out `encoders`
rnag Jul 2, 2022
8b27643
add a dot
rnag Jul 2, 2022
6b90bd3
mention `to_json` usage
rnag Jul 2, 2022
793dcac
mention `to_json` usage
rnag Jul 2, 2022
1ba9ce8
minor updates
rnag Jul 2, 2022
cc538d5
minor bug fix
rnag Jul 2, 2022
b736cd8
minor updates
rnag Jul 2, 2022
2f0b188
add implementation and tests for `from_json`
cb-rnag Jul 29, 2022
8181e47
Merge remote-tracking branch 'origin/release-0.4.0' into release-0.4.0
cb-rnag Jul 29, 2022
df9fa4b
refactor `from_json` implementation
cb-rnag Jul 29, 2022
297b2e9
rename var
cb-rnag Jul 29, 2022
11d046d
minor refactor
cb-rnag Jul 29, 2022
378e3ff
minor refactor
cb-rnag Jul 29, 2022
4ec9393
add docs on type hinting and auto-completion
cb-rnag Jul 29, 2022
957e414
enhance docs on usage
cb-rnag Jul 29, 2022
efd913f
add docs on DotWiz.__init__
cb-rnag Sep 9, 2022
78e3e1e
add docs on how to add type hinting
cb-rnag Sep 9, 2022
7037cde
add docs for DotWizPlus.__init__
cb-rnag Sep 9, 2022
e2fca6a
update dev requirements
cb-rnag Sep 9, 2022
ccbff66
rename init params like `skip_init` to `_skip_init`
cb-rnag Sep 19, 2022
e25a5af
rename init param `_set_dict` to `_check_types` for better clarity
cb-rnag Sep 19, 2022
05ca757
add `__class_getitem__()` so subscripting types works
cb-rnag Sep 19, 2022
22efdbb
Merge pull request #17 from rnag/release-0.4.0
rnag Sep 21, 2022
f00d09c
Merge branch 'main' into release-1.0.0
rnag Sep 21, 2022
e76a826
Merge remote-tracking branch 'origin/main' into release-1.0.0
cb-rnag Sep 22, 2022
90ef413
add tests for better coverage
cb-rnag Sep 22, 2022
539153a
add tests for better coverage
cb-rnag Sep 22, 2022
fe2d894
add tests for better coverage
cb-rnag Sep 22, 2022
9628741
bug: fix mutable default argument
rnag Oct 3, 2022
1cb63a2
fix reqs
rnag Oct 4, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Dot Wiz
:alt: Updates


A `blazing fast`_ ``dict`` subclass that enables *dot access* notation via Python
A `blazing fast`_ ``dict`` wrapper that enables *dot access* notation via Python
attribute style. Nested ``dict`` and ``list`` values are automatically
transformed as well.

Expand Down Expand Up @@ -97,6 +97,9 @@ creating a ``DotWiz`` object:
assert dw['easy: as~ pie?']
assert dw.AnyKey == 'value'

print(dw.to_json())
#> {"AnyKey": "value", "hello, world!": 123, "easy: as~ pie?": true}

``DotWizPlus``
~~~~~~~~~~~~~~

Expand All @@ -112,7 +115,7 @@ on `Issues with Invalid Characters`_ below.
dw = DotWizPlus(my_dict)

print(dw)
#> ✪(this=✪(_1=✪(is_=[✪(for_=✪(all_of=✪(my_fans=True)))])))
# > ✪(this=✪(_1=✪(is_=[✪(for_=✪(all_of=✪(my_fans=True)))])))

# True
assert dw.this._1.is_[0].for_.all_of.my_fans
Expand All @@ -121,10 +124,13 @@ on `Issues with Invalid Characters`_ below.
assert dw['THIS']['1']['is'][0]['For']['AllOf']['My !@ Fans!']

print(dw.to_dict())
# {'THIS': {'1': {'is': [{'For': {'AllOf': {'My !@ Fans!': True}}}]}}}
# > {'THIS': {'1': {'is': [{'For': {'AllOf': {'My !@ Fans!': True}}}]}}}

print(dw.to_attr_dict())
# {'this': {'_1': {'is_': [{'for_': {'all_of': {'my_fans': True}}}]}}}
# > {'this': {'_1': {'is_': [{'for_': {'all_of': {'my_fans': True}}}]}}

print(dw.to_json(snake=True))
# > {"this": {"1": {"is": [{"for": {"all_of": {"my_fans": true}}}]}}}

Issues with Invalid Characters
******************************
Expand Down Expand Up @@ -166,6 +172,16 @@ as compared to other libraries such as ``prodict`` -- or close to **15x** faster
than creating a `Box`_ -- and up to *10x* faster in general to access keys
by *dot* notation -- or almost **30x** faster than accessing keys from a `DotMap`_.

Type Hints and Auto Completion
------------------------------

For better code quality and to keep IDEs happy, it is possible to achieve auto-completion of key or attribute names,
as well as provide type hinting and auto-suggestion of ``str`` methods for example.

Check out the `Usage`_ section in the docs for more details.

.. _Usage: https://dotwiz.readthedocs.io/en/latest/usage.html#type-hints-and-auto-completion

Contributing
------------

Expand Down
41 changes: 41 additions & 0 deletions benchmarks/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from types import SimpleNamespace

import pytest


@pytest.fixture
def parse_to_ns():
"""
Return a helper function to parse a (nested) `dict` object
and return a `SimpleNamespace` object.
"""

def parse(d):
ns = SimpleNamespace()

for k, v in d.items():
setattr(ns, k,
parse(v) if isinstance(v, dict)
else [parse(e) for e in v] if isinstance(v, list)
else v)

return ns

return parse


@pytest.fixture
def ns_to_dict():
"""
Return a helper function to convert a `SimpleNamespace` object to
a `dict`.
"""

def to_dict(ns):
"""Recursively converts a `SimpleNamespace` object to a `dict`."""
return {k: to_dict(v) if isinstance(v, SimpleNamespace)
else [to_dict(e) for e in v] if isinstance(v, list)
else v
for k, v in vars(ns).items()}

return to_dict
49 changes: 49 additions & 0 deletions benchmarks/test_create.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import dataclasses

import addict
import attrdict
import box
import dict2dot
import dotmap
Expand All @@ -18,6 +19,7 @@


# Mark all benchmarks in this module, and assign them to the specified group.
# use with: `pytest benchmarks -m create`
pytestmark = [pytest.mark.create,
pytest.mark.benchmark(group='create')]

Expand Down Expand Up @@ -77,6 +79,28 @@ def test_dotwiz(benchmark, my_data):
assert result.c.bb[0].x == 77


def test_dotwiz_without_check_lists(benchmark, my_data):
result = benchmark(dotwiz.DotWiz, my_data, _check_lists=False)
# print(result)

# now similar to `dict2dot`, `dict`s nested within `lists` won't work
# assert result.c.bb[0].x == 77

# instead, dict access should work fine:
assert result.c.bb[0]['x'] == 77


def test_dotwiz_without_check_types(benchmark, my_data):
result = benchmark(dotwiz.DotWiz, my_data, _check_types=False)
# print(result)

# now, `dict`s and `lists` nested within the input `dict` won't work
# assert result.c.bb[0].x == 77

# instead, dict access should work fine:
assert result.c['bb'][0]['x'] == 77


def test_make_dot_wiz(benchmark, my_data):
result = benchmark(dotwiz.make_dot_wiz, my_data)
# print(result)
Expand All @@ -91,6 +115,17 @@ def test_dotwiz_plus(benchmark, my_data):
assert result.c.bb[0].x == 77


def test_dotwiz_plus_without_check_lists(benchmark, my_data):
result = benchmark(dotwiz.DotWizPlus, my_data, _check_lists=False)
# print(result)

# now similar to `dict2dot`, `dict`s nested within `lists` won't work
# assert result.c.bb[0].x == 77

# instead, dict access should work fine:
assert result.c.bb[0]['x'] == 77


def test_make_dot_wiz_plus(benchmark, my_data):
result = benchmark(dotwiz.make_dot_wiz_plus, my_data)
# print(result)
Expand Down Expand Up @@ -152,6 +187,13 @@ def test_addict(benchmark, my_data):
assert result.c.bb[0].x == 77


def test_attrdict(benchmark, my_data):
result = benchmark(attrdict.AttrDict, my_data)
# print(result)

assert result.c.bb[0].x == 77


def test_metadict(benchmark, my_data):
result = benchmark(metadict.MetaDict, my_data)
# print(result)
Expand Down Expand Up @@ -183,3 +225,10 @@ def test_scalpl(benchmark, my_data):
# print(result)

assert result['c.bb[0].x'] == 77


def test_simple_namespace(benchmark, my_data, parse_to_ns):
result = benchmark(parse_to_ns, my_data)
# print(result)

assert result.c.bb[0].x == 77
71 changes: 69 additions & 2 deletions benchmarks/test_create_special_keys.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import dataclasses

import addict
import attrdict
import box
import dict2dot
import dotmap
Expand All @@ -19,7 +20,11 @@


# Mark all benchmarks in this module, and assign them to the specified group.
# use with: `pytest benchmarks -m create_with_special_keys`
# use with: `pytest benchmarks -m create_sp`
pytestmark = [pytest.mark.create_with_special_keys,
# alias, for easier typing
pytest.mark.create_sp,
pytest.mark.benchmark(group='create_with_special_keys')]


Expand Down Expand Up @@ -78,14 +83,19 @@ def assert_eq2(result):
assert result['Some r@ndom#$(*#@ Key##$# here !!!'] == 'T'


def assert_eq3(result):
def assert_eq3(result, nested_in_dict=True, nested_in_list=True):
assert result.camel_case == 1
assert result.snake_case == 2
assert result.pascal_case == 3
assert result.spinal_case3 == 4
assert result.hello_how_s_it_going == 5
assert result._3d == 6
assert result.for_._1nfinity[0].and_.beyond == 8
if nested_in_list:
assert result.for_._1nfinity[0].and_.beyond == 8
elif nested_in_dict:
assert result.for_._1nfinity[0]['and']['Beyond!'] == 8
else:
assert result.for_['1nfinity'][0]['and']['Beyond!'] == 8
assert result.some_r_ndom_key_here == 'T'


Expand Down Expand Up @@ -124,6 +134,21 @@ def assert_eq6(result: MyClassSpecialCased):
assert result.some_random_key_here == 'T'


def assert_eq7(result, ns_to_dict):
"""For testing with a `types.SimpleNamespace` object, primarily"""
assert result.camelCase == 1
assert result.Snake_Case == 2
assert result.PascalCase == 3

result_dict = ns_to_dict(result)

assert result_dict['spinal-case3'] == 4
assert result_dict['Hello, how\'s it going?'] == 5
assert result_dict['3D'] == 6
assert result_dict['for']['1nfinity'][0]['and']['Beyond!'] == 8
assert result_dict['Some r@ndom#$(*#@ Key##$# here !!!'] == 'T'


@pytest.mark.xfail(reason='some key names are not valid identifiers')
def test_make_dataclass(benchmark, my_data):
# noinspection PyPep8Naming
Expand Down Expand Up @@ -161,6 +186,20 @@ def test_dotwiz(benchmark, my_data):
assert_eq2(result)


def test_dotwiz_without_check_lists(benchmark, my_data):
result = benchmark(dotwiz.DotWiz, my_data, _check_lists=False)
# print(result)

assert_eq2(result)


def test_dotwiz_without_check_types(benchmark, my_data):
result = benchmark(dotwiz.DotWiz, my_data, _check_types=False)
# print(result)

assert_eq2(result)


def test_make_dot_wiz(benchmark, my_data):
result = benchmark(dotwiz.make_dot_wiz, my_data)
# print(result)
Expand All @@ -175,6 +214,20 @@ def test_dotwiz_plus(benchmark, my_data):
assert_eq3(result)


def test_dotwiz_plus_without_check_lists(benchmark, my_data):
result = benchmark(dotwiz.DotWizPlus, my_data, _check_lists=False)
# print(result)

assert_eq3(result, nested_in_list=False)


def test_dotwiz_plus_without_check_types(benchmark, my_data):
result = benchmark(dotwiz.DotWizPlus, my_data, _check_types=False)
# print(result)

assert_eq3(result, nested_in_list=False, nested_in_dict=False)


def test_make_dot_wiz_plus(benchmark, my_data):
result = benchmark(dotwiz.make_dot_wiz_plus, my_data)
# print(result)
Expand Down Expand Up @@ -234,6 +287,13 @@ def test_addict(benchmark, my_data):
assert_eq2(result)


def test_attrdict(benchmark, my_data):
result = benchmark(attrdict.AttrDict, my_data)
# print(result)

assert_eq2(result)


def test_metadict(benchmark, my_data):
result = benchmark(metadict.MetaDict, my_data)
# print(result)
Expand All @@ -259,3 +319,10 @@ def test_scalpl(benchmark, my_data):
# print(result)

assert_eq5(result, subscript_list=True)


def test_simple_namespace(benchmark, my_data, parse_to_ns, ns_to_dict):
result = benchmark(parse_to_ns, my_data)
# print(result)

assert_eq7(result, ns_to_dict)
26 changes: 26 additions & 0 deletions benchmarks/test_getattr.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import dataclasses

import addict
import attrdict
import box
import dict2dot
import dotmap
Expand All @@ -19,6 +20,7 @@


# Mark all benchmarks in this module, and assign them to the specified group.
# use with: `pytest benchmarks -m getattr`
pytestmark = [pytest.mark.getattr,
pytest.mark.benchmark(group='getattr')]

Expand Down Expand Up @@ -72,6 +74,14 @@ def test_dotwiz(benchmark, my_data):
assert result == 77


def test_dotwiz_getitem(benchmark, my_data):
o = dotwiz.DotWiz(my_data)
# print(o)

result = benchmark(lambda: o['c']['bb'][0]['x'])
assert result == 77


def test_dotwiz_plus(benchmark, my_data):
o = dotwiz.DotWizPlus(my_data)
# print(o)
Expand Down Expand Up @@ -141,6 +151,14 @@ def test_addict(benchmark, my_data):
assert result == 77


def test_attrdict(benchmark, my_data):
o = attrdict.AttrDict(my_data)
# print(o)

result = benchmark(lambda: o.c.bb[0].x)
assert result == 77


def test_glom(benchmark, my_data):
o = my_data
# print(o)
Expand Down Expand Up @@ -179,3 +197,11 @@ def test_scalpl(benchmark, my_data):

result = benchmark(lambda: o['c.bb[0].x'])
assert result == 77


def test_simple_namespace(benchmark, my_data, parse_to_ns):
o = parse_to_ns(my_data)
# print(o)

result = benchmark(lambda: o.c.bb[0].x)
assert result == 77
Loading