GithubHelp home page GithubHelp logo

ramazanpolat / prodict Goto Github PK

View Code? Open in Web Editor NEW
146.0 5.0 15.0 156 KB

Prodict, what Python dict meant to be.

License: MIT License

Python 100.00%
python3 typehinting dynamic-props dictionary python hints ide json conversion

prodict's Introduction

Prodict

Prodict = Dictionary with IDE friendly(auto code completion) and dot-accessible attributes and more.

What it does

Ever wanted to use a dict like a class and access keys as attributes? Prodict does exactly this.

Although there are number of modules doing this, Prodict does a little bit more.

You can provide type hints and get auto code completion!

With type hints, you also get nested object instantiations, which will blow your mind.

You will never want to use dict again.

Why?

  • Because accessing dict keys like d['key'] is error prone and ugly.

  • Because it becomes uglier if it is nested, like d['key1]['key2']['key3']. Compare d['key1]['key2']['key3'] to d.key1.key2.key3, which one looks better?

  • Because since web technologies mostly talk with JSON, it should be much more easy to use JSON data(see sample use case below).

  • Because auto code completion makes developers' life easier.

  • Because serializing a Python class to dict and deserializing from dict in one line is awesome!

Features

  1. A class with dynamic properties, without defining it beforehand.
j = Prodict()
j.hi = 'there'
  1. Pass named arguments and all arguments will become properties.
p = Prodict(lang='Python', pros='Rocks!')
print(p.lang)  # Python
print(p.pros)  # Rocks!
print(p)  # {'lang': 'Python', 'pros': 'Rocks!'}
  1. Instantiate from a dict, get dict keys as properties
p = Prodict.from_dict({'lang': 'Python', 'pros': 'Rocks!'})
print(p.lang)   # Python
p.another_property = 'this is dynamically added'
  1. Pass a dict as argument, get a nested Prodict!
p = Prodict(package='Prodict', makes='Python', rock={'even': 'more!'})
print(p)  #  {'package': 'Prodict', 'makes': 'Python', 'rock': {'even': 'more!'}}
print(p.rock.even)  #  'more!'
print(type(p.rock))  # <class 'prodict.Prodict'>
  1. Extend Prodict and use type annotations for auto type conversion and auto code completion
class User(Prodict):
    user_id: int
    name: str

user = User(user_id="1", name="Ramazan")
type(user.user_id) # <class 'int'>
# IDE will be able to auto complete 'user_id' and 'name' properties(see example 1 below)

Why type conversion? Because it will be useful if the incoming data doesn't have the desired type.

class User(Prodict):
    user_id: int
    name: str
    literal: Any
    
response = requests.get("https://some.restservice.com/user/1").json()
user: User = User.from_dict(response)
type(user.user_id) # <class 'int'>

Notes on automatic type conversion:

  • In the above example code, user.user_id will be an int, even if rest service responded with str.
  • Same goes for all built-in types(int, str, float, bool, list, tuple), except dict. Because by default, all dict types will be converted to Prodict.
  • If you don't want any type conversion but still want to have auto code completion, use Any as type annotation, like the literal attribute defined in User class.
  • If the annotated type of an attribute is sub-class of a Prodict, the provided dict will be instantiated as the instance of sub-class. Even if it is List of the sub-class(see sample usa case below).

Sample use case

Suppose that you are getting this JSON response from https://some.restservice.com/user/1:

{
  user_id: 1,
  user_name: "rambo",
  posts: [
    {
      title:"Hello World",
      text:"This is my first blog post...",
      date:"2018-01-02 03:04:05",
      comments: [
          {
            user_id:2,
            comment:"Good to see you blogging",
            date:"2018-01-02 03:04:06"
          },
          {
            user_id:3,
            comment:"Good for you",
            date:"2018-01-02 03:04:07"
          }
        ]
    },
    {
      title:"Leave the old behind",
      text:"Stop using Python 2.x...",
      date:"2018-02-03 04:05:06",
      comments: [
          {
            user_id:4,
            comment:"Python 2 is dead, long live Python",
            date:"2018-02-03 04:05:07"
          },
          {
            user_id:5,
            comment:"You are god damn right :wears Heissenberg glasses:",
            date:"2018-02-03 04:05:08"
          }
        ]
    }
  ]
}

Despite the fact that JSON being schemaless, most REST services will respond with a certain structure. In the above example, the structure is something like this:

User
 |--> user_id
 |--> user_name
 |--> posts [post]
       |--> title
       |--> text
       |--> date
       |--> comments [comment]
             |--> user_id
             |--> comment
             |--> date

And you want to convert this to appropriate Python classes.

Without Prodict:

class Comment:
	def __init__(self, user_id, comment, date):
    	self.user_id = user_id
    	self.comment = comment
        self.date = date
        
class Post:
    def __init__(self, title, text, date):
    	self.title = title
        self.text = text
        self.date = date
        self.comments = []
        
class User:
    def __init__(self, user_id, user_name):
        self.user_id = user_id
        self.user_name = user_name
        self.posts = []

user_json = requests.get("https://some.restservice.com/user/1").json()
posts = [Post(post['title'], post['text'], post['date']) for post in user_json['posts']]
for post in posts:
    post.comments = [[comment for comment in post['comments']] for post in user_json['posts']]

user = User(user_json['user_id'], user_json['user_name'])
user.posts = posts

for post in user.posts:
    print(post.title)

With Prodict you just need to define the classes and let the prodict do the rest like this:

class Comment(Prodict):
    user_id: int
    comment: str
    date: str

class Post(Prodict):
    title: str
    text: str
    date: str
    comments: List[Comment]
        
class User(Prodict):
    user_id: int
    user_name: str
    posts: List[Post]

user_json = requests.get("https://some.restservice.com/user/1").json()
user:User = User.from_dict(user_json)
# Don't forget to annotate the `user` with `User` type in order to get auto code completion.

See the difference? Plus you can add new attributes to User, Post and Comment objects dynamically and access them as dot-accessible attributes.

Examples

Example 0: Use it like regular dict, because it is a dict.

from prodict import Prodict

d = dict(lang='Python', pros='Rocks!')
p = Prodict(lang='Python', pros='Rocks!')

print(d)  # {'lang': 'Python', 'pros': 'Rocks!'}
print(p)  # {'lang': 'Python', 'pros': 'Rocks!'}
print(d == p)  # True

p2 = Prodict.from_dict({'Hello': 'world'})

print(p2)  # {'Hello': 'world'}
print(issubclass(Prodict, dict))  # True
print(isinstance(p, dict))  # True
print(set(dir(dict)).issubset(dir(Prodict)))  # True

Example 1: Accessing keys as attributes and auto completion.

from prodict import Prodict
class Country(Prodict):
    name: str
    population: int

turkey = Country()
turkey.name = 'Turkey'
turkey.population = 79814871

auto code complete

Example 2: Auto type conversion

germany = Country(name='Germany', population='82175700', flag_colors=['black', 'red', 'yellow'])

print(germany.population)  # 82175700
print(type(germany.population))  # <class 'int'> <-- The type is `int` !
# If you don't want type conversion and still want to have auto code completion, use `Any` as type.
print(germany.flag_colors)  # ['black', 'red', 'yellow']
print(type(germany.population))  # <class 'int'>

Example 3: Nested class instantiation

class Ram(Prodict):
    capacity: int
    unit: str
    type: str
    clock: int

class Computer(Prodict):
    name: str
    cpu_cores: int
    rams: List[Ram]

    def total_ram(self):
        return sum([ram.capacity for ram in self.rams])

comp1 = Computer.from_dict(
    {
        'name': 'My Computer',
        'cpu_cores': 4,
        'rams': [
            {'capacity': 4,
             'unit': 'GB',
             'type': 'DDR3',
             'clock': 2400}
        ]
    })
print(comp1.rams)  #  [{'capacity': 4, 'unit': 'GB', 'type': 'DDR3', 'clock': 2400}]

comp1.rams.append(Ram(capacity=8, type='DDR3'))
comp1.rams.append(Ram.from_dict({'capacity': 12, 'type': 'DDR3', 'clock': 2400}))

print(comp1.rams)
# [
#   {'capacity': 4, 'unit': 'GB', 'type': 'DDR3', 'clock': 2400}, 
#   {'capacity': 8, 'type': 'DDR3'}, 
#   {'capacity': 12, 'type': 'DDR3', 'clock': 2400}
# ]
print(type(comp1.rams))  # <class 'list'>
print(type(comp1.rams[0]))  # <class 'Ram'> <-- Mind the type !

Example 4: Provide default values

You can use init method to provide default values. Keep in mind that init is NOT __init__ but init method will be called in __init__ method.

Additionally, you can use init method instead of __init__ without referring to super.

class MyDataWithDefaults(Prodict):
    an_int: int
    a_str: str
    
    def init(self):
        self.an_int = 42
        self.a_str = 'string'
        

data = MyDataWithDefaults(dynamic=43)
print(data)
# {'an_int':42, 'a_str':'string', 'dynamic':43}

Class attributes vs Instance attributes

Prodict only works for instance attributes. Even if you try to set an inherited class attribute, a new instance attribute is created and set.

Consider this example:

from prodict import Prodict

class MyClass(Prodict):
    class_attr: int = 42  # class_attr is a class attribute, not instance attribute

my_class = MyClass()
print(f"my_class.class_attr: {my_class.class_attr}")  # 42
# There is no 'class_attr' defined as instance attribute, so class attribute will be returned (42).
print(f"MyClass.class_attr: {MyClass.class_attr}") # 42
# This is a class attribute, it will be returned as is.

# Now an instance attribute 'class_attr' is created and set to 77
my_class.class_attr = 77
print(f"my_class.class_attr: {my_class.class_attr}")  # 42
# For this matter, avoid setting class_attribute with dot notation, use class name instead

MyClass.class_attr = 88
print(f"MyClass.class_attr: {my_class.class_attr}")  # 88

# So where did 77 go? It is in instance attribute of the class and since it's name is colliding with
# the class attribute, you can't get it by dot notation. You can use .get tho.
print(f"my_class.get('class_attr'): {my_class.get('class_attr')}")  # 77

Installation

If your default Python is 3.7:

pip install prodict

If you have more than one Python versions installed:

python3.7 -m pip install prodict

Limitations

  • You cannot use dict method names as attribute names because of ambiguity.
  • You cannot use Prodict method names as attribute names(I will change Prodict method names with dunder names to reduce the limitation).
  • You must use valid variable names as Prodict attribute names(obviously). For example, while '1' cannot be an attribute for Prodict, it is perfectly valid for a dict to have '1' as a key. You can still use prodict.set_attribute('1',123) tho.
  • Requires Python 3.7+

Thanks

I would like to thank to JetBrains for creating PyCharm, the IDE that made my life better.

prodict's People

Contributors

eykamp avatar ramazanpolat avatar sivaranjith1 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

prodict's Issues

Impelement `KeyError`

When trying to access a key which is not defined, the current behavior is to return None. It must conform to regular dict and return KeyError.

Pickle and JSONPickle error

Hi -- what a great library -- thank you! I am getting errors when I try to pickle or jsonpickle.encode an object that includes a prodict dictionary. Below is an example using jsonpickle

This works:
#######################
from prodict import Prodict as prodict
import jsonpickle

j = {}
j['jack'] = 1
j['jill'] = 2
x = jsonpickle.encode(j)
print(x)
exit()

This generates the following error when attempting jsonpickle.encode of the prodict:
Exception has occurred: KeyError
'getnewargs'
#######################
from prodict import Prodict as prodict
import jsonpickle

j = prodict()
j.jack = 1
j.jill = 2
x = jsonpickle.encode(j)
print(x)
exit()

###############

Suggestions? Thanks!

Nested types with lists and excluding None.

Greetings!

This is a similar issue to #23 except I have the list inside another Prodict. I seems like the exclude flag isn't getting passed down properly. I have attached another case to reproduce the issue and it currently occurs on 0.8.18.

#! /usr/bin/env python3

import json

from prodict import Prodict, List


class ImagesSource(Prodict):
    dir: str
    label: int
    count: int


class ImageData(Prodict):
    task_type: str
    sources: List[ImagesSource]


class Config(Prodict):
    image_data: ImageData


data = {
    "image_data": {
        "sources": [
            {
                "dir": "some/dir",
                "label": 0
            },
            {
                "dir": "other/dir",
                "label": 1
            }
        ]
    }
}

model = Config.from_dict(data)
print(json.dumps(model.to_dict(exclude_none=True, exclude_none_in_lists=True, is_recursive=True)))

I get:
{"image_data": {"sources": [{"dir": "some/dir", "label": 0, "count": null}, {"dir": "other/dir", "label": 1, "count": null}]}}

I expect:
{"image_data": {"sources": [{"dir": "some/dir", "label": 0}, {"dir": "other/dir", "label": 1}]}}

Thanks for any help!

TypeError: 'NoneType' object is not callable when deepcopy

Deepcopying and instance of prodict is faced with TypeError: 'NoneType' object is not callable error. Thanks to @JoaquinRives.

gen1 = Gene(name='gen1', encoding='categorical', categorical_values=['v11', 'v21'])
gen2 = Gene(name='gen2', encoding='binary', minv=10, maxv=20, length=5)

chromosome = Chromosome(name='chr1', genes=(gen1, gen2), mutation_prob=1)
chromosome_copy = copy.deepcopy(chromosome)

Procudes this error:

Traceback (most recent call last):
  File "D:/OneDrive/Desktop/GA Optimization/example_main.py", line 12, in <module>
    chromosome_copy = copy.deepcopy(chromosome)
  File "D:\OneDrive\Joaquin\Software\Anaconda3\lib\copy.py", line 180, in deepcopy
    y = _reconstruct(x, memo, *rv)
  File "D:\OneDrive\Joaquin\Software\Anaconda3\lib\copy.py", line 280, in _reconstruct
    state = deepcopy(state, memo)
  File "D:\OneDrive\Joaquin\Software\Anaconda3\lib\copy.py", line 150, in deepcopy
    y = copier(x, memo)
  File "D:\OneDrive\Joaquin\Software\Anaconda3\lib\copy.py", line 240, in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
  File "D:\OneDrive\Joaquin\Software\Anaconda3\lib\copy.py", line 169, in deepcopy
    rv = reductor(4)
TypeError: 'NoneType' object is not callable

Recursive example doesn't work

with open('data.json') as json_file:
    data = json.load(json_file)

    user = Prodict.from_dict(data)

    for post in user.posts:
        print(post.title)

AttributeError: 'dict' object has no attribute 'title'

{
  "user_id": 1,
  "user_name": "rambo",
  "posts": [
    {
      "title":"Hello World",
      "text":"This is my first blog post...",
      "date":"2018-01-02 03:04:05",
      "comments": [
          {
            "user_id":2,
            "comment":"Good to see you blogging",
            "date":"2018-01-02 03:04:06"
          },
          {
            "user_id":3,
            "comment":"Good for you",
            "date":"2018-01-02 03:04:07"
          }
        ]
    },
    {
      "title":"Hello World 2",
      "text":"This is my first blog post...",
      "date":"2018-01-02 03:04:05",
      "comments": [
          {
            "user_id":2,
            "comment":"Good to see you blogging",
            "date":"2018-01-02 03:04:06"
          },
          {
            "user_id":3,
            "comment":"Good for you",
            "date":"2018-01-02 03:04:07"
          }
        ]
    }
  ]
}

Enforcing the "schema"

Now that I implemented the change for Issue #3, and you accepted the PR, I'm going to make an argument why it may be the wrong solution to the wrong problem.

On the plus side, behavior between obj["key"] and obj.key is now consistent. That's good.

On the minus side, you still have some surprising behavior.

Take the example:

from prodict import Prodict
from typing import Optional

class Car(Prodict):
    brand: str
    year: int
    color: Optional[str]    # Using Optional to indicate None is a legitimate value here

honda = Car.from_dict({"brand": "Honda", "year": "2020"})
print(honda)  

This prints {'brand': 'Honda', 'year': 2020}

But... I would argue that what you really want to print is: {'brand': 'Honda', 'year': 2020, 'color': None}

And using the code above, honda.texture = "rough" works, even though texture is not part of the "schema" you defined in the Car class. I think that assignment should raise an exception (but honda.color = "red" should continue to work).

Finally, with the code change in Issue #3, honda.color raises a KeyError, whereas I think it should return None (but honda.protein should continue to raise a KeyError because protein is not part of the "schema".

I have code that addresses all these issues, and lets you use your type declarations as a mostly enforceable schema. If you agree, I can make a PR.

to_dict() is not recursive when Prodict objects are nested

Hi,

It looks like the to_dict() call is not performed recursively when Prodict objects are nested. If I have classes:

class A(Prodict):
  someval

class B(Prodict):
  testA: A

Then when I call B.to_dict(), it is only called on the parent but does not propagate to the children (so testA.to_dict() is NOT called). Could you please add the ability to call to_dict() recursively on the nested Prodict objects as well?

Thanks

Prodict get() with default values.

Greetings!

If I do a "get" on a Prodict that has a value that was uninitialized the get still things it was "there" and it fails to return the default.

So:

#! /usr/bin/env python3

from prodict import Prodict

class User(Prodict):
    user_id: int
    name: str

user = User.from_dict({"name": "Takara"})
print(f"User_id: '{user.get('user_id', 1138)}' was expecting '1138'")

Obviously, I can work around with default values, etc. but this has bitten me a couple times converting my code from raw dict usage to Prodict or accidentally passing a Prodict in to code that used to work.

Thanks!

Support for 'self' key

Hi,
I'm using prodict for loading json data. 'self' field is a common field in JSON schemas and is not supported as field with prodict:

TypeError: __init__() got multiple values for argument 'self'

This due to init() method using kwargs, for example:

def __init__(self, **kwargs):
        super().__init__(**kwargs)
...


@classmethod
    def from_dict(cls, d: dict):
        val: cls = cls(**d)
        return val  # type: cls

Are you planning supporting this?

Anaconda support

Is it planned to distribute the prodict also via anaconda and not only via pypi?

Default Values broken on 0.8.8

Description

Hey there, the deepcopy release or rather this commit (b3d5a5c#diff-80ef36cd17fb6c6d4ffac53fefbe0f3e) brakes default values

Steps to reproduce

  1. run following example with version 0.8.8
    from prodict import Prodict
    
    class Example(Prodict):
        some_var: int
    
        def init(self):
            self.some_var = 10
    
    e = Example.from_dict({'some_var': 20})
    
    # prints {'some_var': 10}
    print(e)
  2. notice some_var is 10

Expected behaviour:

{'some_var': 20}

Actual behaviour:

{'some_var': 10}

Workaround:

revert back to v0.8.3

Notes

Is there any specific reason for changing the order of calls here?
init is now called after set_attributes

to_dict(exclude_none) doesn't behave as expected

Greetings!

I am switching my project over to Prodict and I really like it so far!

I am running into one issue when I save models and having it not add extra null fields. I have a nested structure with a field of type dict, and when I round-trip a data structure nulls are added even though I have "exclude_none" set to true. I have attached a sample case. I am expecting that the second stanza (some_str=World) when exported does not have a some_dict property. Can you tell me how to get Prodict to not generate the extra nulls for empty dicts?

I am running prodict 0.8.16.

Thanks for any help!

import json

from prodict import Prodict
from prodict import List


class MyEntry(Prodict):
    some_str: str
    some_dict: dict


class ModelConfig(Prodict):
    my_list: List[MyEntry]


data = {
    "my_list": [
        {
            "some_str": "Hello",
            "some_dict": {
                "name": "Frodo",
            }
        },
        {
            "some_str": "World"
        }
    ]
}

model = ModelConfig.from_dict(data)
print(json.dumps(model.to_dict(exclude_none=True)))

I get:
{"my_list": [{"some_str": "Hello", "some_dict": {"name": "Frodo"}}, {"some_str": "World", "some_dict": null}]}

I expect:
{"my_list": [{"some_str": "Hello", "some_dict": {"name": "Frodo"}}, {"some_str": "World"}]}

Python Unions in Prodict

Greetings! I have a data structure that I set to be a string (single value) or a dict. So I've tried using this:

class ModelConfig(Prodict):
    label_mapping: typing.Union[Prodict, str]

This works okay for the PyCharm warnings and completion but if I try to assign a prodict I get all sorts of weird errors. So this doesn't work

model_config.label_mapping = Prodict.from_dict({"0": "dog", "1": "cat"})

Is there some sort of Union construct in Prodict that works better than the built in typing?

Thanks!

Support for dict lists

Hi!
At this moment is not possible to browse data like this:

 prouser.inner_list[0].somekey

Because contents of inner_list (if dict) are not converted to Prodict type.
This conversion should be pretty easy overriding __getattr__ (this is what I do in my use case extending Prodict class):

def __getattr__(self, attr):
        value = super().__getattr__(attr)
        if isinstance(value, list):
            new_list = list()
            for x in value:
                x = Prodict.from_dict(x) if isinstance(x, dict) else x
                new_list.append(x)
            return new_list
        return value

I don't know if there are issues with this approach anyway.

Thanks in advance for your work

getattr() should raise AttributeError

Hi,

I don't know if this is maintained anymore, but I would suggest that getattr() raises an AttributeError instead of KeyError.

This would avoid breaking other libraries that expect getattr() ro raise an AttributeError such as Jinja2 for instance (but not only, also had trouble with omegaconf when creating a DictConfig from Prodict).

Best regards,

Antoine

    def __getattr__(self, item):
        try:
            return self[item]
        except KeyError as exc:
            raise AttributeError(str(exc))

exclude_none_in_lists is not recursively passed down

from version 0.8.18:

def _dict_value(v, is_recursive, exclude_none, exclude_none_in_lists):
    if is_recursive and isinstance(v, Prodict):
        return v.to_dict(is_recursive=is_recursive, exclude_none=exclude_none)
    if exclude_none_in_lists and isinstance(v, List):
        return [
            item.to_dict(exclude_none=True, is_recursive=is_recursive) if isinstance(item, Prodict) else item
            for item in v]
    return v

Is there a reason that item.to_dict(exclude_none=True, is_recursive=is_recursive) omits the argument exclude_none_in_lists?
I would think that if we are recursively turning prodicts into dicts, we would also want the lists in sub-prodicts to adhere to the original exclude_none_in_lists we pass to the Prodict.to_dict method.

should
item.to_dict(exclude_none=True, is_recursive=is_recursive)
be replaced by
item.to_dict(exclude_none_in_lists=True, exclude_none=True, is_recursive=is_recursive)
or something to the extent?

KeyError: '__origin__' when using lists

Python version: 3.9.2
OS: macOS Catalina

Seems to happen whenever I include a list in my dictionary and call from_dict

Code

from prodict import Prodict

class BotConfig(Prodict):
	name: str
	token: str

class Config(Prodict):
	max_request_attempts: int
	seeds: list[str]
	bots: list[BotConfig]
	api_call_delay: float

data =	{
		'bots': [
			{'token': '[redacted]',
			'usertoken': '[also redacted]', 
			'name': '[another redacted thing]'}], 
	'seeds': ['[redacted]', '[redacted]'], 
	'api_call_delay': 0.0,
	'max_request_attempts': 5.0
	}

#breaks
config = Config.from_dict(data)

#works
config = Prodict.from_dict(data)

Traceback:

Traceback (most recent call last):
  File "/Users/bagel/Documents/projects/discordmapper/prodictissue.py", line 28, in <module>
    config = Config.from_dict(data)
  File "/usr/local/lib/python3.9/site-packages/prodict/__init__.py", line 77, in from_dict
    val: cls = cls(**d)
  File "/usr/local/lib/python3.9/site-packages/prodict/__init__.py", line 47, in __init__
    self_d921dfa9_4e93_4123_893d_a7e7eb783a32.set_attributes(**kwargs)
  File "/usr/local/lib/python3.9/site-packages/prodict/__init__.py", line 249, in set_attributes
    self_d921dfa9_4e93_4123_893d_a7e7eb783a32.set_attribute(k, v)
  File "/usr/local/lib/python3.9/site-packages/prodict/__init__.py", line 191, in set_attribute
    constructor, element_type = self.get_constructor(attr_name, value)
  File "/usr/local/lib/python3.9/site-packages/prodict/__init__.py", line 166, in get_constructor
    if attr_type1.__dict__['__origin__'] is list:
KeyError: '__origin__'

autocompletion doest work in latest pycharm

Hi,

updated to the latest pycharm 2021.1 in MacOs Big sur, python 3.9

took that example from documentation:

    p = Prodict(package='Prodict', makes='Python', rock={'even': 'more!'})
    print(p)  # {'package': 'Prodict', 'makes': 'Python', 'rock': {'even': 'more!'}}
    print(p.rock.even)  # 'more!' => **no autocomplete!**


    print(type(p.rock))  #  => **no autocomplete!**
    print(p.lang)  #  => **no autocomplete!**

is it known? any suggestions other than go back to 2020.3?

Nested dictionary key doesn't seem to work

This example code gets an "ATTRIBUTE_ERROR" on the final line.

from prodict import Prodict as pd

fruit = [
    {'name': 'mangos', 'quantity': 2},
    {'name': 'bananas', 'quantity': 4},
]

bread = [
    {'name': 'wheat', 'quantity': 1},
    {'name': 'white', 'quantity': 0},
]

food_dict = {'fruit':fruit, 'bread':bread}

print("\nprinting food_dict['fruit'][1]['name']")
print(food_dict['fruit'][1]['name'])

food_prodict = pd.from_dict(food_dict)

print("\nprinting food_prodict")
print(food_prodict)

print("\nprinting food_prodict.fruit")
print(food_prodict.fruit)

print("\nprinting food_prodict.fruit[1]")
print(food_prodict.fruit[1])

print("\nprinting food_prodict.fruit[1]['name']")
print(food_prodict.fruit[1]['name'])

print("\nprinting food_prodict.fruit[1].name")
print(food_prodict.fruit[1].name)

New Bug after updating to 0.8.15 -- 'undefined_key' error

The readme for Prodict gives this example, which used to work but now fails:

a = dict(my_key=1)
print(a['my_key']) # 1
print(a['undefined_key']) # KeyError: 'undefined_key'

p = Prodict(my_key=1)
print(p.my_key) # 1
# print(p.undefined_key]) # erroneous right bracket in example must be removed
print(p.undefined_key) # None

The error is this:

>>> p = Prodict(my_key=1)
>>> print(p.undefined_key) # Expected None
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "Python37_64\lib\site-packages\prodict\__init__.py", line 240, in __getattr__
    return self[item]
KeyError: 'undefined_key'

Error when setting value of Pandas cell to an object that itself uses Prodict objects

Hi -- Prodict is great. I found an issue. When I attempt to set the value of a Pandas cell to that of an object that itself uses Prodict objects, I get an error:

File "C:\Miniconda2\envs\My code\lib\site-packages\pandas\core\dtypes\generic.py", line 45, in _check
return getattr(inst, attr, "typ") in comp
File "C:\Miniconda2\envs\My code\lib\site-packages\prodict_init
.py", line 259, in getattr
return self[item]
KeyError: '_typ'

Suggestions?

Thanks!

Nested vanilla dicts don't survive pickle round trip

Greetings!

It appears that nested raw "dict" models don't survive round-tripping via pickle. I have a config object that I load into a prodict model that has an arbitrary "extension point" that I typed as dict. When the Prodict is serialized via pickle the dict is turned into Prodict then when it is read back Prodict sees a Prodict value but the type annotation is dict.

Why do I do this? Well, my Prodict is automatically serialized because I am doing a multiprocessing Pool.

I imagine I could probably find some workarounds (such as using raw Prodicts instead of dicts), but it seems like this should work.

My sample code is this:

#! /usr/bin/env python3

import pickle

from prodict import Prodict


class InnerClass(Prodict):
    other_dict: dict


class MyData(Prodict):
    inner_obj: InnerClass


class AppModel():
    def __init__(self, my_data: MyData):
        self.my_data = my_data


if __name__ == '__main__':
    my_data: MyData = MyData.from_dict(
        {
            "inner_obj": {
                'other_dict': {"num_classes": 3}
            }
        })
    app = AppModel(my_data)

    p = pickle.dumps(app)
    app2 = pickle.loads(p)

The exception I get is:

Traceback (most recent call last):
  File "./prodict_test/pickle_test.py", line 31, in <module>
    app2 = pickle.loads(p)
  File "/usr/local/lib/python3.8/site-packages/prodict/__init__.py", line 63, in __setstate__
    return Prodict.from_dict(state)
  File "/usr/local/lib/python3.8/site-packages/prodict/__init__.py", line 81, in from_dict
    val: cls = cls(**d)
  File "/usr/local/lib/python3.8/site-packages/prodict/__init__.py", line 51, in __init__
    self_d921dfa9_4e93_4123_893d_a7e7eb783a32.set_attributes(**kwargs)
  File "/usr/local/lib/python3.8/site-packages/prodict/__init__.py", line 252, in set_attributes
    self_d921dfa9_4e93_4123_893d_a7e7eb783a32.set_attribute(k, v)
  File "/usr/local/lib/python3.8/site-packages/prodict/__init__.py", line 219, in set_attribute
    self.update({attr_name: constructor(value)})
  File "/usr/local/lib/python3.8/site-packages/prodict/__init__.py", line 81, in from_dict
    val: cls = cls(**d)
  File "/usr/local/lib/python3.8/site-packages/prodict/__init__.py", line 51, in __init__
    self_d921dfa9_4e93_4123_893d_a7e7eb783a32.set_attributes(**kwargs)
  File "/usr/local/lib/python3.8/site-packages/prodict/__init__.py", line 252, in set_attributes
    self_d921dfa9_4e93_4123_893d_a7e7eb783a32.set_attribute(k, v)
  File "/usr/local/lib/python3.8/site-packages/prodict/__init__.py", line 194, in set_attribute
    constructor, element_type = self.get_constructor(attr_name, value)
  File "/usr/local/lib/python3.8/site-packages/prodict/__init__.py", line 157, in get_constructor
    constructor = attr_type1.from_dict
AttributeError: type object 'dict' has no attribute 'from_dict'

If I annotate like 157 the specific field that it doesn't like is the other_dict is type of dict but the type of the value is "Prodict."

Thanks for any help!

Hide class methods

We should hide class methods with renaming them with an underscore or with a guid prefix, so people will be able to use those as keywords

Prodict doesn't handled nested dicts

I was surprised to find that Prodict does not appear to recursively instantiate nested dicts as Prodicts. I find that quite counterintuitive. Am I missing something here?

>>> from prodict import Prodict
>>> p = Prodict({'x': 100, 'y': 200, 'foo': {'z': 300}})
>>> p.x
100
>>> p.foo
{'z': 300}
>>> type(p.foo)
<class 'dict'>

>>> Prodict(p.foo).z   # ugh
300

Is there a way to make prodict accept generators?

Prodict is exactly what dictionaries should have been right from the beginning.

My application creates dicts or defaultdicts to hold data parsed directly from received data strings. I had assumed prodict would be able to do the same, but it appears that prodict cannot accept a generator. I tried a number of different approaches, and the best I've found is to first make a dictionary and then use prodict.from_dict().

Is there a better way?

s=';O2Sat:92;HR:62;RR:0'

# this works
dd1 = dict(x.split(':') for x in s.split(';') if ':' in x)   
 
# this fails with TypeError: __init__() takes 1 positional argument but 2 were given
pd1 = Prodict(x.split(':') for x in s.split(';') if ':' in x)     

# this works but is ugly
pd2 = Prodict.from_dict(dict(x.split(':') for x in s.split(';') if ':' in x))    

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.