Prodict
Prodict is a dict with dot-accessible attributes, IDE auto-completion, and type
conversion driven by annotations.
from typing import List
from prodict import Prodict
class Comment(Prodict):
user_id: int
text: str
class Post(Prodict):
title: str
comments: List[Comment]
post = Post.from_dict({'title': 'Hello', 'comments': [{'user_id': '2', 'text': 'Welcome!'}]})
post.comments[0].user_id # 2, an int, and your IDE completes every name on the way
Why
d['key1']['key2']['key3']is noisy and easy to get wrong;d.key1.key2.key3is not.- Web services talk JSON. Turning a JSON response into objects you can navigate, with the types you expect, should take one line.
- Declared attributes give you auto-completion in the IDE.
- It is still a
dict:json.dumps,==,**kwargsand every dict method keep working.
Installation
pip install prodict
# or
uv add prodict
No dependencies. Requires Python 3.8 or later; tested on 3.8 through 3.15.
Features
Dynamic attributes. Keys are attributes and attributes are keys.
p = Prodict(lang='Python', pros='Rocks!')
p.lang # 'Python'
p.year = 1991
p # {'lang': 'Python', 'pros': 'Rocks!', 'year': 1991}
isinstance(p, dict) # True
Nested dicts become Prodicts.
p = Prodict.from_dict({'package': 'Prodict', 'rock': {'even': 'more!'}})
p.rock.even # 'more!'
type(p.rock) # <class 'prodict.Prodict'>
Type conversion from annotations. Subclass Prodict, annotate attributes, and values
are converted when they are set. Use Any to keep completion without conversion.
from typing import Any
class Country(Prodict):
name: str
population: int
raw: Any
germany = Country(name='Germany', population='83200000', raw='83200000')
germany.population # 83200000 (int)
germany.raw # '83200000' (unchanged)
Country(name='Turkey') # {'name': 'Turkey', 'population': None, 'raw': None}
Nested models. Attributes annotated with a Prodict subclass, or List[...] of one,
turn plain dicts into instances of that class.
class Ram(Prodict):
capacity: int
unit: str
class Computer(Prodict):
name: str
rams: List[Ram]
def total_ram(self):
return sum(ram.capacity for ram in self.rams)
computer = Computer.from_dict({'name': 'Workstation', 'rams': [{'capacity': '16', 'unit': 'GB'}]})
isinstance(computer.rams[0], Ram) # True
computer.total_ram() # 16
Defaults with init(). Override init(), not __init__; no super() call needed,
and keyword arguments win over defaults.
class Settings(Prodict):
retries: int
timeout: float
def init(self):
self.retries = 3
self.timeout = 10.0
Settings(retries='5') # {'retries': 5, 'timeout': 10.0}
Serialization. to_dict() returns a plain dict and can drop None values and
convert nested models and list items. copy.deepcopy and pickle keep model classes.
Country(name='Turkey').to_dict(exclude_none=True) # {'name': 'Turkey'}
A complete example
Suppose https://example.com/api/users/1 returns:
{
"user_id": 1,
"user_name": "rambo",
"posts": [
{
"title": "Hello World",
"date": "2018-01-02 03:04:05",
"comments": [
{"user_id": 2, "comment": "Good to see you blogging", "date": "2018-01-02 03:04:06"}
]
}
]
}
Without Prodict, you write the classes and the mapping by hand:
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, date, comments):
self.title = title
self.date = date
self.comments = comments
class User:
def __init__(self, user_id, user_name, posts):
self.user_id = user_id
self.user_name = user_name
self.posts = posts
data = requests.get('https://example.com/api/users/1').json()
user = User(
data['user_id'],
data['user_name'],
[
Post(p['title'], p['date'], [Comment(c['user_id'], c['comment'], c['date']) for c in p['comments']])
for p in data['posts']
],
)
With Prodict, you declare the shape and let it do the rest:
class Comment(Prodict):
user_id: int
comment: str
date: str
class Post(Prodict):
title: str
date: str
comments: List[Comment]
class User(Prodict):
user_id: int
user_name: str
posts: List[Post]
user: User = User.from_dict(requests.get('https://example.com/api/users/1').json())
user.posts[0].comments[0].comment # 'Good to see you blogging'
New keys can still be added to any of these objects at any time. A runnable version, including writing the data back out, is examples/05-json-api-client.
Limitations
- Names of
dictmethods (items,keys,get, ...) cannot be attributes. Names of Prodict methods (to_dict,from_dict,init, ...) can be stored but are only readable with brackets, so avoid them. - Keys that are not valid Python identifiers (
'1','first-name') work with brackets andset_attribute(), not with dots. - Values are converted for
int,float,str,list,List[T],Tuple[...],Prodictand its subclasses, when set as keyword arguments, throughfrom_dict()or by attribute assignment.bool, plaintupleand other types are stored as they come (a Prodict value for them raisesAttributeError), and so is anything set withp['key'] = ...orupdate(). - Pass data as keyword arguments or through
from_dict(). A dict passed positionally (Model({...})) loses the values of annotated attributes. Optional[...],Union[...],Dict[K, V], a baredict, and built-in generics such aslist[int]are not supported as annotations; useAny,Prodictor thetypingforms (List[int]).- Prodict keeps data in the dict, not in instance attributes, so avoid giving class attributes values meant for instances.
The typed models guide covers each of these in detail.
Documentation
- Tutorial: a ten-minute walk through every feature.
- Typed models and Serialization guides.
- Examples: runnable programs from the basics to a complete JSON API client.
Every Python example in docs/ and examples/ is run by CI on each supported Python version.
Contributing
Bug reports and pull requests are welcome. See CONTRIBUTING.md for setup and tests, and CHANGELOG.md for what changed between releases.
License
MIT. See LICENSE.txt.
Thanks
Metadata
Release files for prodict 0.8.23
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| prodict-0.8.23.tar.gz | 84.7 kB | Without provenance | Details for prodict-0.8.23.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| prodict-0.8.23-py3-none-any.whl | Python 3 | none | any | Details for prodict-0.8.23-py3-none-any.whl |
Total release size: 92.5 kB
Release files / prodict-0.8.23.tar.gz
| Download URL | prodict-0.8.23.tar.gz |
|---|---|
| Size | 84.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9f48b1ce0d3936894c2482f42fe3b6460c6a63599ed929afaf5490cabc75a7cd
|
|
BLAKE2b-256 checksum How to use checksums |
360c6e0214b67d75b4ae940a6115d99b71bed90cc5a8d06e46d29ecbe16e0651
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.15
|
Release files / prodict-0.8.23-py3-none-any.whl
| Download URL | prodict-0.8.23-py3-none-any.whl |
|---|---|
| Size | 7.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7a49a09e2ad97d73ad574765bfcf1ae92fad661290ca6770502b0b08438bbcfd
|
|
BLAKE2b-256 checksum How to use checksums |
d2953c3077dc05fe48102a97976ceb93f986632b085baad953d0d798281ab05b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.15
|
Security for prodict 0.8.23
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.