[X] I have checked that my issue does not already have a solution in the FAQ
[X] I have searched the existing issues and didn't find my bug already reported there
[X] I have checked that my bug is still present in the latest release
Version
4.0.0a5
What happened?
The following function inside _marshalling.py is called during unmarshalling of objects (e.g. inside cbor and json serializers):
def unmarshal_object(ref: str, state: Any) -> Any:
cls = callable_from_ref(ref)
if not isinstance(cls, type):
raise TypeError(f"{ref} is not a class")
instance = cls.__new__(cls) # << problematic line
instance.__setstate__(state)
return instance
However, the marked line calls __new__, which causes __attrs_post_init__ hooks to be bypassed -- see here. As a result, instances of classes that rely on such hooks (such as CronTrigger) will not have all their fields filled in during unmarshalling.
Changing the aforementioned line to
instance = cls()
seems to solve this problem.
How can we reproduce the bug?
Here's a minimal example of something that this breaks:
from apscheduler.serializers.json import JSONSerializer
from apscheduler.datastores.sqlalchemy import SQLAlchemyDataStore
from apscheduler import Scheduler
from apscheduler.triggers.cron import CronTrigger
def hello_world():
print("hello")
json_serializer = JSONSerializer()
data_store = SQLAlchemyDataStore("sqlite:///example.sqlite", serializer=json_serializer)
scheduler = Scheduler(data_store)
cron_trigger = CronTrigger(hour = 0)
schedule_id = scheduler.add_schedule(hello_world, cron_trigger)
scheduler.pause_schedule(schedule_id) # << AttributeError will be raised here
The traceback will look like this:
Traceback (most recent call last):
File "/workspace/example.py", line 15, in <module>
scheduler.pause_schedule(schedule_id)
File "/workspace/.venv/lib/python3.12/site-packages/apscheduler/_schedulers/sync.py", line 282, in pause_schedule
self._portal.call(self._async_scheduler.pause_schedule, id)
File "/workspace/.venv/lib/python3.12/site-packages/anyio/from_thread.py", line 287, in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/Cellar/python@3.12/3.12.6/Frameworks/Python.framework/Versions/3.12/lib/python3.12/concurrent/futures/_base.py", line 456, in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/Cellar/python@3.12/3.12.6/Frameworks/Python.framework/Versions/3.12/lib/python3.12/concurrent/futures/_base.py", line 401, in __get_result
raise self._exception
File "/workspace/.venv/lib/python3.12/site-packages/anyio/from_thread.py", line 218, in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/workspace/.venv/lib/python3.12/site-packages/apscheduler/_schedulers/async_.py", line 538, in pause_schedule
await self.data_store.add_schedule(
File "/workspace/.venv/lib/python3.12/site-packages/apscheduler/datastores/sqlalchemy.py", line 504, in add_schedule
schedule.marshal(self.serializer)
File "/workspace/.venv/lib/python3.12/site-packages/apscheduler/_structures.py", line 143, in marshal
marshalled = attrs.asdict(self, value_serializer=serialize)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/workspace/.venv/lib/python3.12/site-packages/attr/_next_gen.py", line 613, in asdict
return _asdict(
^^^^^^^^
File "/workspace/.venv/lib/python3.12/site-packages/attr/_funcs.py", line 75, in asdict
rv[a.name] = asdict(
^^^^^^^
File "/workspace/.venv/lib/python3.12/site-packages/attr/_funcs.py", line 66, in asdict
v = getattr(inst, a.name)
^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'CronTrigger' object has no attribute 'year'
Things to check first
[X] I have checked that my issue does not already have a solution in the FAQ
[X] I have searched the existing issues and didn't find my bug already reported there
[X] I have checked that my bug is still present in the latest release
Version
4.0.0a5
What happened?
The following function inside
_marshalling.py
is called during unmarshalling of objects (e.g. insidecbor
andjson
serializers):However, the marked line calls
__new__
, which causes__attrs_post_init__
hooks to be bypassed -- see here. As a result, instances of classes that rely on such hooks (such asCronTrigger
) will not have all their fields filled in during unmarshalling.Changing the aforementioned line to
seems to solve this problem.
How can we reproduce the bug?
Here's a minimal example of something that this breaks:
The traceback will look like this: