cloudant / python-cloudant

A Python library for Cloudant and CouchDB
Apache License 2.0
163 stars 55 forks source link

Datetime object #496

Closed Mradr closed 3 years ago

Mradr commented 3 years ago

How to deal with time objects?

with Document( self.app.master_database["something"], id, encoder=DateTimeEncoder, decoder=DateTimeDecoder ) as document: val = document["created_on"].strftime("%Y-%m-%d %H:%M:%S")

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

So it looks like it is not decoding correctly on the object? As for the decoder I am just using the basic json one and looks like it is encoding correctly on the timedate object, but not decoding at all? What am I missing for this step?

Also, trying to wrap my head around the way this works because something is a bit off...

Setting the database to this object self.client_user_contact_db = self.master_database.create_database( "client_user_contacts" )

Then calling self.client_user_contact_db[id]["test"] = False

vs

for entry in self.client_user_contact_db: print(entry)

Results in test (1) to be False, but the entry test to be still True

While I know I need to call .save() I would assume still that they are the same object and I should be able to call .save any time to update the database then while I am working with the local cache version. Also, why is self.client_user_contact_db giving me back whole documents vs just the keys? I can check for keys eg if key in self.client_user_contact_db, but I would think it too would just give me back keys for the for loop - keys being the ids.

vmatyus commented 3 years ago

Please consider, that the document variable in your sample code has type cloudant.document.Document from cloudant package. This dict could have multiple fields and it is not equivalent with the datetime typed object from datetime module. The strftime function can be applied only on a datetime.datetime typed object. An object type can be checked with type() function.

In your sample code, a custom decoder: DateTimeDecoder is used. This is not part of cloudant nor json packages. So please review the decoder operation in this custom class.

Furthermore, I am really not suggesting to store only 1 date in a document, better to use a field for this purpose, for example:

val = document['date'].strftime("%Y-%m-%d %H:%M:%S")

But in this case also the custom decoder should be able to assign the datetime type to the field date. I found an example datetime decoder implementation for this type conversion: https://gist.github.com/abhinav-upadhyay/5300137. But this is just a suggestion, that you can use as a base point to extend the custom decoder implementation.

Mradr commented 3 years ago

The example url is what I am using. It does not work with the above code. Performing: val = document['date'].strftime("%Y-%m-%d %H:%M:%S") still would result in the same error.

Printing document['date'] displays a dict of the endcoded timedate object. Decode doesnt seem to be applying. Reading over the base code quick like, I dont see anything about decoding the object only encoding. Is this a bug then?

I assume this should read something like self._client.decoder and not whatever that is? self.encoder = kwargs.get('encoder') or self._client.encoder self.decoder = kwargs.get('decoder') or json.JSONDecoder

client.py has a gobal encoder that looks to be spread out for the whole program, but not one for decoding. self.encoder = kwargs.get('encoder') or json.JSONEncoder

Yea just tested, seem to fix the issue when I make the changes and then when calling first time passing the decoder along with the encoder seems to work across the board?

vmatyus commented 3 years ago

Printing document['date'] displays a dict of the encoded timedate object. Decode doesnt seem to be applying. Reading over the base code quick like, I dont see anything about decoding the object only encoding. Is this a bug then?

No, there is no bug, a custom decoder can be attached to the document handling and it will be applied on the JSON content after the content is fetched: https://github.com/cloudant/python-cloudant/blob/33781463ff85c8bb535bd6ede2049e4d3bec6c4c/src/cloudant/document.py#L167

Printing document['date'] displays a dict

The DateTimeEncoder encodes Python datetime object as dict. If you don't want to encode as a dict then use a different encoder.

If you want to decode the dict back to a Python datetime object then you need to extend the decoder to make a type conversion from dict to datetime and produce a datetime typed return value.

doc = Document(db, 'docid', encoder=DateTimeEncoder, decoder=DateTimeDecoder)
doc.fetch() # decoder makes the data manipulation and should convert `dict` to `datetime`
print(doc['date']) # prints a date string 2021-04-29 11:24:42.812545
print(type(doc['date'])) # <class 'datetime.datetime'> because the `dict` has been converted to `datetime` by the decoder
db['docid']['date'] = datetime.now()
db['docid'].save() # encode makes data manipulation and `datetime` should be converted to `dict` format

If you are accessing the document via the database dict then you need to take care to ensure that the entry for the document in your dict is a Document that has been initialized with the encoder/decoder.

db['docid'] = Document(db, 'docid', encoder=DateTimeEncoder, decoder=DateTimeDecoder)
db['docid'].fetch()
db['docid']['date'] = datetime.now()
db['docid'].save()
​
print(db['docid']['date']) # prints a date string 2021-04-29 11:24:42.812545
print(type(db['docid']['date'])) # <class 'datetime.datetime'>
​
db['docid'].fetch() # overwrites the local entry content with the remote
print(db['docid']['date']) # still prints a date string 2021-04-29 11:24:42.812545
print(type(db['docid']['date'])) # still a <class 'datetime.datetime'>

​ If you use the dict approach, but don't create a Document level encoder/decoder then you will not get decoded objects. ​

I assume this should read something like self._client.decoder and not whatever that is? self.encoder = kwargs.get('encoder') or self._client.encoder self.decoder = kwargs.get('decoder') or json.JSONDecoder

The self._client.encoder is coming from the database object, in your now deleted example you used custom encoder/decoder for Document handling. ​ There is an asymmetry here because the client level encoder existed before Document encoder/decoder pairs. There has never been a client level decoder. The client encoder was retained for backwards compatibility, but the Document level one is more flexible since different documents may require different encoding/decoding schemes.