slashmili / python-jalali

Jalali calendar binding for Python based on Python's datetime module
http://pypi.python.org/pypi/jdatetime/
Other
339 stars 47 forks source link

jdatetime and jdate objects cannot be pickled anymore in 3.8.0 #108

Closed sinarezaei closed 2 years ago

sinarezaei commented 2 years ago

In the latest update (3.8.0), jdatetime objects could't be pickled

import jdatetime
import pickle
pickle.dumps(jdatetime.datetime.now())

Results in the following error:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: Can't pickle local object 'date._strftime_replace_func.<locals>.simple_replace

In version 3.6.2 of jdatetime, it was working with no errors

hramezani commented 2 years ago

Thanks @sinarezaei for reporting. we will investigate the problem.

hramezani commented 2 years ago

I've created a PR to fix the issue https://github.com/slashmili/python-jalali/pull/109 Could you please test it?

hramezani commented 2 years ago

It got fixed in 3.8.1

sinarezaei commented 2 years ago

I've created a PR to fix the issue #109 Could you please test it?

Checked, it's fixed in PR, Thanks a lot!

jetX11 commented 2 years ago

I think it still has some bug with unpickled jdatetime object. Apparently, the object (retrieved from the previous pickled file) does not work properly with f-string formatting like f'{some_jdatetime:%Y/%m/%d %H:%M:%S}' like the fresh jdatetime object:

AttributeError: 'datetime' object has no attribute '_strftime_mapping'

OK! I found what is the wrong! The object pickled (dump) by the previous version of jdatetime cannot be treated as a true jdatetime object when retrieved (load) by the new version! The object has been totally restructured.

jetX11 commented 2 years ago

The experiment using bash version 4.4.20 and python version 3.9.7:


bash :

# installing an old version of jdatetime
pip uninstall jdatetime
pip install -Iv jdatetime==3.7.0

pthon:

import jdatetime
import pickle

# saving a jdatetime object using pickle file foo.pickle
with open('foo.pickle', 'wb') as ofile:
    pickle.dump(jdatetime.datetime.now(), ofile)

now = jdatetime.datetime.now()
print(f'{now:%Y/%m/%d %H:%M}')
# OK! Fine! prints formatted date/time

bash:

# upgrading to new version : (3.8.1)
pip install --upgrade jdatetime

pthon:

import jdatetime
import pickle

# retrieving the previous jdatetime object:
with open('foo.pickle', 'rb') as ifile:
    then = pickle.load(ifile)

# now try to print formatted object:
print(f'{then:%Y/%m/%d %H:%M}')
# trouble!
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/XXXX/.virtualenvs/minon/lib/python3.9/site-packages/jdatetime/__init__.py", line 579, in __format__
    return self.strftime(format)
  File "/home/XXXX/.virtualenvs/minon/lib/python3.9/site-packages/jdatetime/__init__.py", line 640, in strftime
    if symbol in self._strftime_mapping:
AttributeError: 'datetime' object has no attribute '_strftime_mapping'
hramezani commented 2 years ago

I don't think that the object pickled in an old version should be unpickled in the new version.

jetX11 commented 2 years ago

I don't think that the object pickled in an old version should be unpickled in the new version.

Why not?! Even as a subjective opinion, it is strongly arguable.

Two basic questions:

  1. Should a program keep running with some old modules ... no matter how those modules should or could be upgraded for bug fix, better performance, new features, ...?
  2. Each time a module is upgraded, should we redesign our old programs to cope with the new module features?

Pickling is an essential feature of python for task management and persistency. I have been using hundreds of modules (including jdatetime) with a few dozen of my programs. with exception of a few nasty cases, the requirements have always typically been

jdatetime>=3.7.0 (NOT jdatetime>=3.7.0,<3,8.0)

and that is the right objective statement.

With your statement, my program will be restricted to a specific version of some modules; especially if the user of my programs have already be using it with a large set of preserved data.

Of course, we can convert the underlying timestamp of the jdatetime object each time before pickling, and after unpickling; that is, something like:

import jdatetime
import datetime

# before pickling (ts pickled):
ts = jdatetime.datetime.now().togregorian().timestamp()

# after unpickling (ts unpickled)
jdt = jdatetime.datetime.fromgregorian(datetime=datetime.datetime.fromtimestamp(ts))

But, then why we have used jdatetime for in the first place? What about its seamless integration with python ecosystem? Wasn't it supposed to be seamlessly equivalent to the standard datetime module?

Good luck

hramezani commented 2 years ago

Thanks, @jetX11 for your explanation. I've created a PR to fix this problem. https://github.com/slashmili/python-jalali/pull/113

It would be great if you can confirm that this patch fixes your problem.

jetX11 commented 2 years ago

Thank you @hramezani for attention. OK! I'll check it. Well done.

jetX11 commented 2 years ago

Sorry for delay dear @hramezani ; I was expecting the patch appear as a new version of jdatetime (3.8.2)! I am new to GitHub ... as an active user (I have downloaded from it for ages).

Any way, I manually modified the file __init__.py of jdatetime version 3.8.1 (from the file changed by you here [1]) and it could unpickle the previously pickled object of version 3.7.0 without any problem! Thanks a lot.

[1] __init__.py (https://github.com/hramezani/python-jalali/blob/df234fa66eba17f37204a3a6191e8651691bde4c/jdatetime/__init__.py)

hramezani commented 2 years ago

@jetX11 Thanks for your feedback. python-jalali 3.8.2 now is available!