Code stopped working after Python upgrade
Describe the bug After upgrading Python from version 3.10.12 to 3.11.10 my previously working code stopped working.
To Reproduce I believe this is the relevant bit of code:
@planning_entity
class TF_Assignment(JsonDomainBase):
id: Annotated[int, PlanningId]
language: Optional[str]
location: Annotated[TF_Location, LocationSerializer, LocationValidator]
duration_calculator: Annotated[TF_DurationCalculator, None]
product: str
min_start_time: datetime
max_start_time: Annotated[datetime | None, None] # only used in case of fixed task
max_end_time: datetime
day_part: str
is_fixed: bool
fixed_expert: Optional['TF_Expert'] = None
estimated_driving_time: Annotated[timedelta, DurationSerializer]
real_driving_time: Annotated[timedelta, DurationSerializer]
execution_time: Annotated[timedelta, DurationSerializer]
expert: Annotated[Optional['TF_Expert'],
InverseRelationShadowVariable(source_variable_name='assignments'),
Field(default=None)]
previous_assignment: Annotated[Optional['TF_Assignment'],
PreviousElementShadowVariable(source_variable_name='assignments'),
Field(default=None)]
next_assignment: Annotated[Optional['TF_Assignment'],
NextElementShadowVariable(source_variable_name='assignments'),
Field(default=None)]
arrival_time: Annotated[
Optional[datetime],
CascadingUpdateShadowVariable(target_method_name='update_arrival_time'),
Field(default=None)]
@computed_field
@property
def departure_time(self) -> Optional[datetime]:
return self.calculate_departure_time()
def update_arrival_time(self):
if self.expert is None or (self.previous_assignment is not None and self.previous_assignment.arrival_time is None):
self.arrival_time = None
elif self.previous_assignment is None:
if self.product.startswith('VEN_T5'):
self.real_driving_time = timedelta(minutes=0.0)
self.arrival_time = max(self.expert.min_first_arrival - self.real_driving_time, self.expert.min_start_time) + self.real_driving_time
else:
ref_prev = self.expert.name
ref_current = self.id
self.real_driving_time = timedelta(minutes=self.duration_calculator.calculateDuration(ref_prev, ref_current))
self.arrival_time = max(self.expert.min_first_arrival - self.real_driving_time, self.expert.min_start_time) + self.real_driving_time
else:
#remainder of method
def calculate_departure_time(self):
if self.arrival_time is None:
return None
return max(self.arrival_time, self.min_start_time) + (self.execution_time * self.expert.speed)
This is the line that triggers the exception:
self.arrival_time = max(self.expert.min_first_arrival - self.real_driving_time, self.expert.min_start_time) + self.real_driving_time
Traceback (most recent call last):
File "DefaultSolver.java", line 197, in ai.timefold.solver.core.impl.solver.DefaultSolver.solve
ai.timefold.jpyinterpreter.types.errors.ai.timefold.jpyinterpreter.types.errors.AttributeError: ai.timefold.jpyinterpreter.types.errors.AttributeError: object '<class datetime>' does not have attribute '__iter__'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "DefaultSolver.java", line 197, in ai.timefold.solver.core.impl.solver.DefaultSolver.solve
Exception: Java Exception
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/jelle/Documents/Projects/work/code/planning-simulator/auto/.venv/lib/python3.11/site-packages/timefold/solver/_solver.py", line 109, in solve
java_solution = self._delegate.solve(java_problem)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
java.lang.java.lang.IllegalStateException: java.lang.IllegalStateException: The property ($method$update_arrival_time) getterMethod (public ai.timefold.jpyinterpreter.PythonLikeObject org.jpyinterpreter.user.tf_domain.TF_Assignment.$method$update_arrival_time()) on bean of class (class org.jpyinterpreter.user.tf_domain.TF_Assignment) throws an exception.
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/jelle/Documents/Projects/work/code/planning-simulator/auto/main.py", line 82, in <module>
main()
File "/home/jelle/Documents/Projects/work/code/planning-simulator/auto/main.py", line 77, in main
planner.simulate()
File "/home/jelle/Documents/Projects/work/code/planning-simulator/auto/planner.py", line 76, in simulate
self.simulateDay()
File "/home/jelle/Documents/Projects/work/code/planning-simulator/auto/planner.py", line 162, in simulateDay
solver.solve(self.logger)
File "/home/jelle/Documents/Projects/work/code/planning-simulator/auto/tf_solver.py", line 157, in solve
self.solution = solver.solve(self.solution)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/jelle/Documents/Projects/work/code/planning-simulator/auto/.venv/lib/python3.11/site-packages/timefold/solver/_solver.py", line 113, in solve
raise RuntimeError(f'Solving failed due to an error: {e.getMessage()}.\n'
RuntimeError: Solving failed due to an error: The property ($method$update_arrival_time) getterMethod (public ai.timefold.jpyinterpreter.PythonLikeObject org.jpyinterpreter.user.tf_domain.TF_Assignment.$method$update_arrival_time()) on bean of class (class org.jpyinterpreter.user.tf_domain.TF_Assignment) throws an exception..
Java stack trace: java.lang.IllegalStateException: The property ($method$update_arrival_time) getterMethod (public ai.timefold.jpyinterpreter.PythonLikeObject org.jpyinterpreter.user.tf_domain.TF_Assignment.$method$update_arrival_time()) on bean of class (class org.jpyinterpreter.user.tf_domain.TF_Assignment) throws an exception.
at ai.timefold.solver.core.impl.domain.common.accessor.ReflectionMethodMemberAccessor.executeGetter(ReflectionMethodMemberAccessor.java:76)
at ai.timefold.solver.core.impl.domain.variable.cascade.CascadingUpdateShadowVariableDescriptor.updateSingle(CascadingUpdateShadowVariableDescriptor.java:66)
at ai.timefold.solver.core.impl.domain.variable.cascade.CascadingUpdateShadowVariableDescriptor.update(CascadingUpdateShadowVariableDescriptor.java:57)
at ai.timefold.solver.core.impl.domain.variable.listener.support.VariableListenerSupport.evaluateFromIndex(VariableListenerSupport.java:286)
at ai.timefold.solver.core.impl.domain.variable.listener.support.VariableListenerSupport.triggerCascadingUpdateShadowVariableUpdate(VariableListenerSupport.java:275)
at ai.timefold.solver.core.impl.domain.variable.listener.support.VariableListenerSupport.triggerVariableListenersInNotificationQueues(VariableListenerSupport.java:258)
at ai.timefold.solver.core.impl.score.director.AbstractScoreDirector.triggerVariableListeners(AbstractScoreDirector.java:322)
at ai.timefold.solver.core.impl.move.director.VariableChangeRecordingScoreDirector.triggerVariableListeners(VariableChangeRecordingScoreDirector.java:162)
at ai.timefold.solver.core.impl.heuristic.move.AbstractMove.doMoveOnly(AbstractMove.java:27)
at ai.timefold.solver.core.impl.heuristic.move.LegacyMoveAdapter.execute(LegacyMoveAdapter.java:54)
at ai.timefold.solver.core.impl.score.director.AbstractScoreDirector.doAndProcessMove(AbstractScoreDirector.java:265)
at ai.timefold.solver.core.impl.constructionheuristic.decider.ConstructionHeuristicDecider.doMove(ConstructionHeuristicDecider.java:158)
at ai.timefold.solver.core.impl.constructionheuristic.decider.ConstructionHeuristicDecider.decideNextStep(ConstructionHeuristicDecider.java:113)
at ai.timefold.solver.core.impl.constructionheuristic.DefaultConstructionHeuristicPhase.solve(DefaultConstructionHeuristicPhase.java:63)
at ai.timefold.solver.core.impl.solver.AbstractSolver.runPhases(AbstractSolver.java:83)
at ai.timefold.solver.core.impl.solver.DefaultSolver.solve(DefaultSolver.java:197)
Suppressed: java.lang.IndexOutOfBoundsException: Index 0 out of bounds for length 0
at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:100)
at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:106)
at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:302)
at java.base/java.util.Objects.checkIndex(Objects.java:385)
at java.base/java.util.ArrayList.get(ArrayList.java:427)
at ai.timefold.jpyinterpreter.types.collections.PythonLikeList.get(PythonLikeList.java:518)
at ai.timefold.solver.core.impl.domain.variable.listener.support.VariableListenerSupport.evaluateFromIndex(VariableListenerSupport.java:286)
at ai.timefold.solver.core.impl.domain.variable.listener.support.VariableListenerSupport.triggerCascadingUpdateShadowVariableUpdate(VariableListenerSupport.java:275)
at ai.timefold.solver.core.impl.domain.variable.listener.support.VariableListenerSupport.triggerVariableListenersInNotificationQueues(VariableListenerSupport.java:258)
at ai.timefold.solver.core.impl.score.director.AbstractScoreDirector.triggerVariableListeners(AbstractScoreDirector.java:322)
at ai.timefold.solver.core.impl.move.director.ListVariableBeforeChangeAction.undo(ListVariableBeforeChangeAction.java:19)
at ai.timefold.solver.core.impl.move.director.VariableChangeRecordingScoreDirector.undoChanges(VariableChangeRecordingScoreDirector.java:64)
at ai.timefold.solver.core.impl.move.director.EphemeralMoveDirector.close(EphemeralMoveDirector.java:47)
at ai.timefold.solver.core.impl.score.director.AbstractScoreDirector.doAndProcessMove(AbstractScoreDirector.java:264)
... 5 more
Caused by: ai.timefold.jpyinterpreter.types.errors.AttributeError: object '<class datetime>' does not have attribute '__iter__'
at ai.timefold.jpyinterpreter.PythonLikeObject.$getAttributeOrError(PythonLikeObject.java:42)
at ai.timefold.jpyinterpreter.builtins.GlobalBuiltins.max(GlobalBuiltins.java:1202)
at org.jpyinterpreter.user.tf_domain.TF_Assignment.update_arrival_time.invoke(/home/jelle/Documents/Projects/work/code/planning-simulator/auto/tf_domain.py:105)
at org.jpyinterpreter.user.tf_domain.TF_Assignment.$method$update_arrival_time(/home/jelle/Documents/Projects/work/code/planning-simulator/auto/tf_domain.py:94)
at ai.timefold.solver.core.impl.domain.common.accessor.ReflectionMethodMemberAccessor.executeGetter(ReflectionMethodMemberAccessor.java:73)
... 15 more
Environment
Timefold Solver Version or Git ref: 1.17.0b0
Output of java -version:
openjdk version "17.0.13" 2024-10-15
OpenJDK Runtime Environment (build 17.0.13+11-Ubuntu-2ubuntu122.04)
OpenJDK 64-Bit Server VM (build 17.0.13+11-Ubuntu-2ubuntu122.04, mixed mode, sharing)
Output of uname -a or ver:
Linux jelle-XPS-15-9520 6.8.0-49-generic #49~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Wed Nov 6 17:42:15 UTC 2 x86_64 x86_64 x86_64 GNU/Linux
Thanks for reporting, @JelleVE! We'll take a look when time permits.
As of today, Timefold halted any further development of the Python solver. Find out more here: https://github.com/TimefoldAI/timefold-solver/discussions/1698
Closing this issue.