Skip to main content

A Python dict subclass which tries to act like JavaScript objects, so you can use the dot-notation (d.foo) to access members of the object.

Project description

MyDict

Version 2.X is a breaking-API step regarding the functions that transforms the MyDict object into/from something else. Those methods have been moved to another module: mydict.jsonify. Everything else remains the same, and Python 2.X is totally discouraged at this point.

A Python dict subclass which tries to act like JavaScript objects, so you can use the dot notation (d.foo) to access members of the object. If the member doesn't exist yet then it's created when you assign a value to it. Brackets notation (d['foo']) is also accepted.

Installation

$ pip install mydict

Examples

Let's give it a try.

d = MyDict()
d.foo = 'bar'

print(d.foo)
# ==> 'bar'

If you try to get the value of a non-existing member then a None value is returned

d = MyDict()
if d.foo is None:
    print('"foo" does not exist yet!')

If that value is "complex" (a dict or another MyDict instance), then it's also recursively transformed into a MyDict object, so you can access it in the same way

d = MyDict()
d.foo = {'bar': 'baz', 'lst': [{'a': 123}]}

print(d.foo.bar)
# ==> 'baz'

print(d.foo.lst[0].a)
# ==> 123

Values in lists are accessed, as you expect, with the brackets notation (d[0]):

d = MyDict()
d.foo = [1, 2, 3]

print(d.foo[2])
# ==> 3

We can instantiate it from a dict of any level of complexity:

d = MyDict({'foo': 'bar', 'baz': [1, 2, {'foo': 'bar', 'baz': 'Hello, world!'}]})

print(d.foo)
# ==> 'bar'

print(d.baz[0])
# ==> 1

print(d.baz[2].foo)
# ==> 'bar'

with keywords in the constructor:

d = MyDict(a=1, b=2.2, c=[1, 2, 3], d=[{'x': 1, 'y': [100, 200, 300]}])
# ...
d.a == 1
d.b == 2.2
d.c[0] == 1
d.d[0].x == 1
d.d[0].y[1] == 200

or both:

d = MyDict({'foo': 'bar'}, baz=123)
# ...
d.foo == 'bar'
d.baz == 123

Please, take into account that keyword initialization has precedence over the dict (first parameter of the constructor):

d = MyDict({'foo': 'bar'}, foo='BAR')
# ...
d.foo == 'BAR'

It's also possible to access members using a path with get or brackets notation (d['...']):

d = MyDict(foo={'bar': 'baz'})
# ...
d['foo.bar'] == 'baz'
d.get('foo.bar') == 'baz'

But when those keys with dots exists in the tree they are accessed using the corresponding key:

d = MyDict({'foo.bar': 'baz'})
# ...
# 'foo.bar' is not interpreted as a path because the key exists
d['foo.bar'] = 'baz'

But there's a particular case, if a dotted key exists and match an existing path, then this ain't work properly, or work in a different way depending on the method of access used, to be correct:

d = MyDict({'foo': {'bar': 'baz'}, 'foo.bar': 'BAZ'})
# ...
d['foo.bar'] = 'BAZ'  # the "dotted field" ('foo.bar') has precedence over the path
d.foo.bar = 'baz'  # it's not possible to detect a "dotted key" using "dot notation"

Personally, I don't see this as a great issue because I generally avoid using dots in keys, like in the previous case.

Transformation

You have at your disposal a couple of functions to retrieve the MyDict object transformed into something else. For the version 2 the original methods (to_json, from_json, get_dict) have been moved to another module: mydict.jsonify.

Types of case

The available types of case are:

  • mydict.SNAKE_CASE : snake_case
  • mydict.CAMEL_CASE : camelCase
  • mydict.PASCAL_CASE : PascalCase

More on this later on.

mydict.jsonify.to_json

Returns the MyDict object as a JSON string (str):

d = MyDict(foo="bar", arr=[1, 2, {"three": 3}])
mydict.jsonify.to_json(d)
# '{"foo": "bar", "arr": [1, 2, {"three": 3}]}'

In addition, it's also possible to handle the case type of the keys inside the object. For example, we can use snake_case in MyDict object and then "export" it with those keys in camelCase. Let's see it in action:

d = MyDict(my_foo='bar', my_arr=[1, 2, {"other_key": 3}])
mydict.jsonify.to_json(d, case_type=mydict.CAMEL_CASE)
# '{"myFoo": "bar", "myArr": [1, 2, {"otherKey": 3}]}'
mydict.jsonify.get_dict

In some occasions you'll need a plain old Python dict representation of the MyDict object, though is a dict subclass:

d = MyDict(foo="bar", arr=[{"one": 1}, {"two": 2}])
mydict.jsonify.get_dict(d)
# {'foo': 'bar', 'arr': [{'one': 1}, {'two': 2}]}

In addition, it's also possible to handle the case type of the keys inside the object, in the same way to_json works. For example, we can use snake_case in MyDict object and then "export" it with those keys in camelCase. Let's see it in action:

d = MyDict(my_foo='bar', my_arr=[1, 2, {"other_key": 3}])
mydict.jsonify.get_dict(d, case_type=mydict.CAMEL_CASE)
# {'myArr': [1, 2, {'otherKey': 3}], 'myFoo': 'bar'}

Initialization from JSON

It's also possible to load a JSON from str, bytes, and file-like objects (with a .read() method) using the function mydict.jsonify.from_json:

d = mydict.jsonify.from_json('{"foo": "bar"}')
# d.foo == 'bar'

d = mydict.jsonify.from_json(b'{"foo": "bar"}')
# d.foo == 'bar'

d = mydict.jsonify.from_json(open('/path/to/file.json', 'r'))
# d = mydict.jsonify.from_json(open('/path/to/file.json', 'rb')) also works
from io import StringIO, BytesIO

s = StringIO()
s.write('{"foo": "bar"}')

d_from_s = mydict.jsonify.from_json(s)
# d_from_s.foo == 'bar'

b = BytesIO()
b.write(b'{"foo": "bar"}')
# b.write('{"foo": "bar"}'.encode('utf8')) is equivalent

d_from_b = mydict.jsonify.from_json(b)
# d_from_b.foo == 'bar'

Please, notice whether the source is string or bytes the result is always string.

In addition, there's also a param case_type in the from_json function. It works in the same way we previously mentioned for to_json and get_dict. For example:

d = mydict.jsonify.from_json('{"myFoo": "bar", "myArr": [1, 2, {"otherKey": 3}]}', case_type=mydict.SNAKE_CASE)
# d.my_foo == 'bar'
# d.my_arr == [1, 2, {'other_key': 3}]
# d.my_arr[2].other_key == 3

Very useful when we collect data from an API which uses camelCase for its keys but we want a more pythonic way for those keys.

The tests passed successfully with Python 3.6. Python 2.X is totally discouraged at this stage of the library. We recommend using Python +3.X

$ pytest mydict -v

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mydict-2.1.0.tar.gz (7.8 kB view details)

Uploaded Source

Built Distribution

mydict-2.1.0-py2.py3-none-any.whl (9.2 kB view details)

Uploaded Python 2 Python 3

File details

Details for the file mydict-2.1.0.tar.gz.

File metadata

  • Download URL: mydict-2.1.0.tar.gz
  • Upload date:
  • Size: 7.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.2.0 pkginfo/1.4.2 requests/2.21.0 setuptools/40.6.2 requests-toolbelt/0.8.0 tqdm/4.28.1 CPython/3.6.10

File hashes

Hashes for mydict-2.1.0.tar.gz
Algorithm Hash digest
SHA256 959a81fd299a51bba4ab61ee78148939b0cc62a51fa7e77f3da8424527121652
MD5 950415d7309b0da45a931c8c13620159
BLAKE2b-256 e4e722a0ff3df949d1b3a7b20a5b2932bd47edf408d219d06e07ab07b08af0d4

See more details on using hashes here.

File details

Details for the file mydict-2.1.0-py2.py3-none-any.whl.

File metadata

  • Download URL: mydict-2.1.0-py2.py3-none-any.whl
  • Upload date:
  • Size: 9.2 kB
  • Tags: Python 2, Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.2.0 pkginfo/1.4.2 requests/2.21.0 setuptools/40.6.2 requests-toolbelt/0.8.0 tqdm/4.28.1 CPython/3.6.10

File hashes

Hashes for mydict-2.1.0-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 9ada5324061876a67931362590fb90633951600806811163c778695a38031ee2
MD5 c8eb6e6c6502f0524defb70996ee0807
BLAKE2b-256 0c04e9548103d06d495d2a0d20dd0ffa432f73e8d8c4cf52a3afa67f8dc2e3a9

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page