kodemore / chili Goto Github PK
View Code? Open in Web Editor NEWObject serialization/deserialization tools for python.
License: MIT License
Object serialization/deserialization tools for python.
License: MIT License
Hey, this is great!
I see there is an MIT license batch. Don't suppose... could you add also an MIT LICENSE
file, so that it is really obviously mit, and so that it shows as MIT in the right hand side of the github repo front page?
I actually managed to work around this issue, but just to notify you there seems to be some incorrect parts in the README docs. And to share some suggestions. I could make a pull request for both but I'm not sure what your thoughts are about it.
Edit: I just noticed it's possible to do what I want with dataclasses and chili.decoder import ClassDecoder
(or just chili.decode()
directly), as is shown in https://github.com/kodemore/chili/blob/main/tests/usecases/dataclasses_test.py#L140-L147
Which is perfect for my use case.
Below my original message, which could still be interesting:
When I try to decode a dict to a class while omitting a field that does have a default defined, I get a chili.error.DecoderError@missing_property
For example when I run the example straight from the README:
from typing import List
from chili import Decoder, decodable
@decodable
class Book:
name: str
author: str
isbn: str = "1234567890"
tags: List[str] = []
book_data = {"name": "The Hobbit", "author": "J.R.R. Tolkien"}
decoder = Decoder[Book]()
book = decoder.decode(book_data)
I get: chili.error.DecoderError@missing_property: key=isbn
I noticed in this unit test the optional fields are typed with Optional[SomeType]
, which indeed fixes the issue.
Although this seems to make sense at first given the name "Optional", it does make it possible to set the field to None
, which might not always be as intended. A custom decoder could be used to parse 'None' to the default value, although it would be a bit more convenient if this would be done automatically by specifying a default while leaving out Optional
.
Besides, it would be nice to be able to use the more recent SomeType | None
syntax as an alternative to Optional[SomeType]
.
I also noticed that @encodable
& @decodable
seem to be redundant (or at least in the cases from the examples), making it possible to keep most parts of the code completely unaware of the chili lib, which I like a lot ๐
Hello again!
I am again finding myself in a dead end, and I don't know if this behavior is intended or not.
This test code comes from tests/usecases/custom_type_test.py:
from chili import encodable, Encoder, TypeEncoder
class ISBN(str):
def __init__(self, value: str):
self.value = value
@encodable
class Book:
name: str
isbn: ISBN
def __init__(self, name: str, isbn: ISBN):
self.name = name
self.isbn = isbn
class ISBNEncoder(TypeEncoder):
def encode(self, isbn: ISBN) -> str:
return isbn.value
encoder = Encoder[Book]({ISBN: ISBNEncoder()})
book = Book("The Hobbit", ISBN("1234567890"))
result = encoder.encode(book)
print(result)
Thta code works flawlessly. However, if you change ISBN
for list[ISBN]
or any other thing, like a dictionary of ISBNS, it stops working:
from chili import encodable, Encoder, TypeEncoder
class ISBN(str):
def __init__(self, value: str):
self.value = value
@encodable
class Book:
name: str
isbn: list[ISBN]
def __init__(self, name: str, isbn: ISBN):
self.name = name
self.isbn = isbn
class ISBNEncoder(TypeEncoder):
def encode(self, isbn: ISBN) -> str:
return isbn.value
encoder = Encoder[Book]({ISBN: ISBNEncoder()})
book = Book("The Hobbit", [ISBN("1234567890"), ISBN("1234567890456456")])
result = encoder.encode(book)
print(result)
I assume that the type encoder looks for List[ISBN] and does not find it. But if you have a ISBNEncoder, it should already work?
This example, taken literally from the README, fails on Python 3.11.8:
from typing import List
from chili import Decoder, decodable
@decodable
class Book:
name: str
author: str
isbn: str = "1234567890"
tags: List[str] = []
book_data = {"name": "The Hobbit", "author": "J.R.R. Tolkien"}
decoder = Decoder[Book]()
book = decoder.decode(book_data)
assert book.tags == []
assert book.isbn == "1234567890"
with
File "<stdin>", line 1, in <module>
File "[...]]/python3.11/site-packages/chili/decoder.py", line 599, in decode
raise DecoderError.missing_property(key=key)
chili.error.DecoderError@missing_property: key=isbn
Hydrating a valid ISO-8601 datetime string loses precision in the milliseconds.
from dataclasses import dataclass
from datetime import datetime
from chili import init_dataclass
@dataclass
class MyDatetime:
t: datetime
date = datetime.utcnow()
date_str = date.isoformat() # produces valid ISO-8601 string
my_dataclass = MyDatetime(date)
hydrated_dataclass = init_dataclass({"t": date_str}, MyDatetime)
print(my_dataclass == hydrated_dataclass)
print(my_dataclass)
print(hydrated_dataclass)
print(date_str)
As can be seen in the example above, even if no error is produced for the milliseconds in the date string, these are ignored when hydrating.
Hello again!
I am using chili in an university project I am currently coding, and I just discovered a problem. We have two classes, parent and child. Both of them are serializable. The base class is a basic session, with basic functionality, and the other one inherits the base class, with extra tools.
At some point I realized that chili was not encoding/decoding the objects of the child class properly. And, after some research, I realized that the schema was identical in both classes, parent and child.
Which is the source of the issue, then? When marking classes as encodable / decodable / serializable, the decorator checks this:
def _decorate(cls) -> Type[C]:
if not hasattr(cls, _PROPERTIES):
setattr(cls, _PROPERTIES, create_schema(cls))
What is happening when both classes are marked with the encodable / decodable / serializable decorator? The child class inherits the schema, and the decorator does not update the schema.
I have done some tests, and removing the line if not hasattr(cls, _PROPERTIES):
solves the issue. I don't know if it breaks anything else. If you agree, I will create a pull request with this solution.
Thanks in advance.
Hello,
I stumbled across the following unexpected behaviour today, wanted to raise it for visibility first.
Let's say I have the following dataclass:
@dataclasses.dataclass
class Contract:
name: str
start: int
duration: int
price: int
If I try instantiating this using an incomplete input, chili
will do it just fine, it just won't have the duration
field on it.
invalid_contract = {
'name': 'Contract 1',
'start': '0',
'price': 10
}
contract = decode(invalid_contract, Contract)
If I change the class definition to provide a constructor, and I use the same initialisation as above, chili
will raise an DecoderError.invalid_type
exception, which is what I was expecting.
@chili.encodable
class Contract:
name: str
start: int
duration: int
price: int
def __init__(self, name: str, start: int, duration: int, price:int):
self.name = name
self.start = start
self.duration = duration
self.price = price
I seem to recall chili
handling this consistently with v1, but I could be wrong, so I wanted to ask for some clarification as to what the desired behaviour is.
chili is easy to use. Just do:
chili.encode(some_object)
or
chili.json_encode(some_object)
... but the doc currently talks about creating Encoder objects, annotating classes, etc, which is super complicated, and totally unnecessary for basic usage.
Strongly suggest starting with the easy way to use chili. Then afer tha, you can have a section "Advanced" to talk about more fancythings.
Given
@dataclass(frozen=True)
class Event:
id: str
occurred_on: datetime
@dataclass(frozen=True)
class SomethingImportantHappened(Event):
message: str
When
event = init_dataclass({'id': 'dda5469de61b4c36a65dbdaa3850e8ab', 'occurred_on': '2022-11-24T15:09:12.348012', 'message': 'test message'}, SomethingImportantHappened)
Then
../../src/outbox_pattern/outbox_processor.py:42: in process_outbox_message
event = init_dataclass(json.loads(message.data), event_cls)
/Users/szymonmiks/Library/Caches/pypoetry/virtualenvs/examples-hALDfU1m-py3.9/lib/python3.9/site-packages/chili/dataclasses.py:11: in init_dataclass
return hydrate(data, dataclass, strict=False, mapping=mapping)
/Users/szymonmiks/Library/Caches/pypoetry/virtualenvs/examples-hALDfU1m-py3.9/lib/python3.9/site-packages/chili/hydration.py:653: in hydrate
return strategy.hydrate(data)
/Users/szymonmiks/Library/Caches/pypoetry/virtualenvs/examples-hALDfU1m-py3.9/lib/python3.9/site-packages/chili/hydration.py:86: in hydrate
setter(instance, value)
/Users/szymonmiks/Library/Caches/pypoetry/virtualenvs/examples-hALDfU1m-py3.9/lib/python3.9/site-packages/chili/hydration.py:464: in set_dataclass_property
raise error
/Users/szymonmiks/Library/Caches/pypoetry/virtualenvs/examples-hALDfU1m-py3.9/lib/python3.9/site-packages/chili/hydration.py:460: in set_dataclass_property
setattr(obj, property_name, setter(attributes[property_name]))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'SomethingImportantHappened' object has no attribute 'id'") raised in repr()] SomethingImportantHappened object at 0x10e7e04c0>
name = 'id', value = 'dda5469de61b4c36a65dbdaa3850e8ab'
> ???
E dataclasses.FrozenInstanceError: cannot assign to field 'id'
<string>:4: FrozenInstanceError
Hello!
I found that the encoding behavior when using custom type encoders and decoders is different between Encoder[Class].encode(obj)
and encode(obj, Class, ...)
. Here's the steps to reproduce it:
from chili import Encoder, TypeEncoder, encode, encodable
from typing import Any
class ExoticClass:
name: str
size: int
def __init__(self, name, size):
self.name = name
self.size = size
class ExoticEncoder(TypeEncoder):
def encode(self, value: ExoticClass):
return {"name": f"DECODED:{value.name}", "size": -value.size}
type_encoders = {ExoticClass: ExoticEncoder()}
exotic_object = ExoticClass("foo", 5)
print(encode(exotic_object, ExoticClass, type_encoders))
print(Encoder[ExoticClass](encoders=type_encoders).encode(exotic_object))
The output of the first print is: {'name': 'DECODED:foo', 'size': -5}
, as expected.
But the output of the second line is the exception chili.error.EncoderError@invalid_type: invalid_type
I think this is because I haven't decorated my class. If I add the decorator like:
@encodable
class ExoticClass:
...
Then, the outputs are {'name': 'DECODED:foo', 'size': -5}
and {'name': 'foo', 'size': 5}
.
The conclusion is that the second line always ignores the custom type encoders.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.