Skip to content

Commit 4870113

Browse files
chore: Remove Java type stubs from default build, rename jpyinterpreter to _jpyinterpreter (#54)
* chore: Remove Java type stubs from default build, rename jpyinterpreter to _jpyinterpreter - Having the Java type stubs in the default build meant that IDE's will see two definitions of the same class (one from `ai.timefold.solver` (the Java one) and one from `timefold.solver` (the Python one)). Since users should only interact with the Python classes, the Java type stubs should not be included. - Rename jpyinterpreter to _jpyinterpreter since it is a private package and not public API - Fix link and packages used in the README --------- Co-authored-by: Lukáš Petrovický <lukas@petrovicky.net>
1 parent e48c7bc commit 4870113

16 files changed

+91
-68
lines changed

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
# Timefold Solver
1+
![Timefold Logo](https://raw.githubusercontent.com/TimefoldAI/timefold-solver/main/docs/src/modules/ROOT/images/shared/timefold-logo.png)
22

3-
[![PyPI](https://img.shields.io/pypi/v/timefold "PyPI")](https://pypi.org/project/timefold-solver/)
3+
# Timefold Solver for Python
4+
5+
[![PyPI](https://img.shields.io/pypi/v/timefold-solver "PyPI")](https://pypi.org/project/timefold-solver/)
46

57
[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=timefold_solver_python&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=timefold_solver_python)
68
[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=timefold_solver_python&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=timefold_solver_python)
@@ -51,7 +53,7 @@ To declare Planning Entities, use the `@planning_entity` decorator along with an
5153
```python
5254
from dataclasses import dataclass, field
5355
from typing import Annotated
54-
from timefold.solver import planning_entity, PlanningId, PlanningVariable
56+
from timefold.solver.domain import planning_entity, PlanningId, PlanningVariable
5557

5658
@planning_entity
5759
@dataclass
@@ -75,7 +77,8 @@ To declare the Planning Solution, use the `@planning_solution` decorator:
7577
```python
7678
from dataclasses import dataclass, field
7779
from typing import Annotated
78-
from timefold.solver import planning_solution, ProblemFactCollectionProperty, ValueRangeProvider, PlanningEntityCollectionProperty, PlanningScore
80+
from timefold.solver.domain import (planning_solution, ProblemFactCollectionProperty, ValueRangeProvider,
81+
PlanningEntityCollectionProperty, PlanningScore)
7982
from timefold.solver.score import HardSoftScore
8083

8184
@planning_solution
@@ -101,9 +104,7 @@ You define your constraints by using the ConstraintFactory:
101104

102105
```python
103106
from domain import Lesson
104-
from timefold.solver import constraint_provider
105-
from timefold.solver.types import Joiners, HardSoftScore
106-
107+
from timefold.solver.score import Joiners, HardSoftScore, constraint_provider
107108

108109
@constraint_provider
109110
def define_constraints(constraint_factory):

setup.py

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@
88
from shutil import copyfile
99
import sys
1010

11+
include_java_stubs = os.environ.get('INCLUDE_JAVA_STUBS', 'false').lower() == 'true'
12+
"""
13+
If Java type stubs should be generated.
14+
These are for our own type checker; users should never need
15+
to use them (since they should be using the Python classes).
16+
17+
Not included in the default build since IDE's will see multiple
18+
packages that define a class (the one from `ai.timefold...`
19+
and the one from `timefold.solver...`), which can lead to the
20+
wrong one being imported.
21+
"""
22+
1123

1224
class FetchDependencies(build_py):
1325
"""
@@ -47,7 +59,10 @@ def run(self):
4759

4860
subprocess.run([str((project_root / command).absolute()), 'clean', 'install'],
4961
cwd=project_root, check=True)
50-
self.create_stubs(project_root, command)
62+
63+
if include_java_stubs:
64+
self.create_stubs(project_root, command)
65+
5166
classpath_jars = []
5267
# Add the main artifact
5368
classpath_jars.extend(glob.glob(os.path.join(project_root, 'timefold-solver-python-core', 'target', '*.jar')))
@@ -73,6 +88,33 @@ def find_stub_files(stub_root: str):
7388
yield os.path.join(root, file)
7489

7590

91+
packages = [
92+
'timefold.solver',
93+
'timefold.solver.config',
94+
'timefold.solver.domain',
95+
'timefold.solver.heuristic',
96+
'timefold.solver.score',
97+
'timefold.solver.test',
98+
'_jpyinterpreter',
99+
]
100+
101+
package_dir = {
102+
'timefold.solver': 'timefold-solver-python-core/src/main/python',
103+
'_jpyinterpreter': 'jpyinterpreter/src/main/python',
104+
}
105+
106+
if include_java_stubs:
107+
packages += [
108+
'java-stubs',
109+
'jpype-stubs',
110+
'ai-stubs',
111+
]
112+
package_dir.update({
113+
'java-stubs': 'timefold-solver-python-core/src/main/resources',
114+
'jpype-stubs': 'timefold-solver-python-core/src/main/resources',
115+
'ai-stubs': 'timefold-solver-python-core/src/main/resources',
116+
})
117+
76118
this_directory = Path(__file__).parent
77119
long_description = (this_directory / "README.md").read_text()
78120
timefold_solver_python_version = '999-dev0'
@@ -98,28 +140,8 @@ def find_stub_files(stub_root: str):
98140
'License :: OSI Approved :: Apache Software License',
99141
'Operating System :: OS Independent'
100142
],
101-
packages=['timefold.solver',
102-
'timefold.solver.config',
103-
'timefold.solver.domain',
104-
'timefold.solver.heuristic',
105-
'timefold.solver.score',
106-
'timefold.solver.test',
107-
'jpyinterpreter',
108-
'java-stubs',
109-
'jpype-stubs',
110-
'ai-stubs'],
111-
package_dir={
112-
'timefold.solver': 'timefold-solver-python-core/src/main/python',
113-
'jpyinterpreter': 'jpyinterpreter/src/main/python',
114-
# Setup tools need a non-empty directory to use as base
115-
# Since these packages are generated during the build,
116-
# we use the src/main/resources package, which does
117-
# not contain any python files and is already included
118-
# in the build
119-
'java-stubs': 'timefold-solver-python-core/src/main/resources',
120-
'jpype-stubs': 'timefold-solver-python-core/src/main/resources',
121-
'ai-stubs': 'timefold-solver-python-core/src/main/resources',
122-
},
143+
packages=packages,
144+
package_dir=package_dir,
123145
test_suite='tests',
124146
python_requires='>=3.10',
125147
install_requires=[

tests/test_solver_config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def get_java_solver_config(path: pathlib.Path):
4747

4848

4949
def test_load_from_solver_config_file():
50-
from jpyinterpreter import get_java_type_for_python_type
50+
from _jpyinterpreter import get_java_type_for_python_type
5151
solver_config = get_java_solver_config(pathlib.Path('tests', 'solverConfig-simple.xml'))
5252
assert solver_config.getSolutionClass() == get_java_type_for_python_type(Solution).getJavaClass()
5353
entity_class_list = solver_config.getEntityClassList()
@@ -59,7 +59,7 @@ def test_load_from_solver_config_file():
5959

6060

6161
def test_reload_from_solver_config_file():
62-
from jpyinterpreter import get_java_type_for_python_type
62+
from _jpyinterpreter import get_java_type_for_python_type
6363

6464
@planning_solution
6565
class RedefinedSolution:

timefold-solver-python-core/src/main/python/_problem_change.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from abc import ABC, abstractmethod
22
from typing import TypeVar, Optional, Callable, TYPE_CHECKING, Generic
33
from types import FunctionType
4-
from jpyinterpreter import (convert_to_java_python_like_object,
4+
from _jpyinterpreter import (convert_to_java_python_like_object,
55
unwrap_python_like_object,
66
update_python_object_from_java,
77
translate_python_bytecode_to_java_bytecode)

timefold-solver-python-core/src/main/python/_solution_manager.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def update(self, solution: Solution_, solution_update_policy=None) -> 'Score':
5959
The score of the updated solution.
6060
"""
6161
# TODO handle solution_update_policy
62-
from jpyinterpreter import convert_to_java_python_like_object, update_python_object_from_java
62+
from _jpyinterpreter import convert_to_java_python_like_object, update_python_object_from_java
6363
java_solution = convert_to_java_python_like_object(solution)
6464
out = self._delegate.update(java_solution)
6565
update_python_object_from_java(java_solution)
@@ -84,7 +84,7 @@ def analyze(self, solution: Solution_, score_analysis_fetch_policy=None, solutio
8484
The `ScoreAnalysis` corresponding to the given solution.
8585
"""
8686
# TODO handle policies
87-
from jpyinterpreter import convert_to_java_python_like_object
87+
from _jpyinterpreter import convert_to_java_python_like_object
8888
return ScoreAnalysis(self._delegate.analyze(convert_to_java_python_like_object(solution)))
8989

9090
def explain(self, solution: Solution_, solution_update_policy=None) -> 'ScoreExplanation':
@@ -104,7 +104,7 @@ def explain(self, solution: Solution_, solution_update_policy=None) -> 'ScoreExp
104104
The `ScoreExplanation` corresponding to the given solution.
105105
"""
106106
# TODO handle policies
107-
from jpyinterpreter import convert_to_java_python_like_object
107+
from _jpyinterpreter import convert_to_java_python_like_object
108108
return ScoreExplanation(self._delegate.explain(convert_to_java_python_like_object(solution)))
109109

110110
def recommend_fit(self, solution: Solution_, entity_or_element, proposition_function,

timefold-solver-python-core/src/main/python/_solver.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def solve(self, problem: Solution_):
9696
"""
9797
from java.lang import Exception as JavaException
9898
from ai.timefold.jpyinterpreter.types.errors import PythonBaseException
99-
from jpyinterpreter import convert_to_java_python_like_object, unwrap_python_like_object
99+
from _jpyinterpreter import convert_to_java_python_like_object, unwrap_python_like_object
100100
java_problem = convert_to_java_python_like_object(problem)
101101
if not self._solution_class.isInstance(java_problem):
102102
raise ValueError(
@@ -229,7 +229,7 @@ def add_event_listener(self, event_listener: Callable[[BestSolutionChangedEvent[
229229
class EventListener:
230230
@JOverride
231231
def bestSolutionChanged(self, event):
232-
from jpyinterpreter import unwrap_python_like_object
232+
from _jpyinterpreter import unwrap_python_like_object
233233
nonlocal event_listener_list
234234
event = BestSolutionChangedEvent(
235235
new_best_score=event.getNewBestScore(),

timefold-solver-python-core/src/main/python/_solver_manager.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def get_problem_id(self) -> ProblemId_:
7171
ProblemId_
7272
The problem id corresponding to this `SolverJob`.
7373
"""
74-
from jpyinterpreter import unwrap_python_like_object
74+
from _jpyinterpreter import unwrap_python_like_object
7575
return unwrap_python_like_object(self._delegate.getProblemId())
7676

7777
def get_solver_status(self) -> SolverStatus:
@@ -109,7 +109,7 @@ def get_final_best_solution(self) -> Solution_:
109109
Solution_
110110
Never ``None``, but it could be the original uninitialized problem.
111111
"""
112-
from jpyinterpreter import unwrap_python_like_object
112+
from _jpyinterpreter import unwrap_python_like_object
113113
return unwrap_python_like_object(self._delegate.getFinalBestSolution())
114114

115115
def terminate_early(self) -> None:
@@ -186,7 +186,7 @@ def with_problem_id(self, problem_id: ProblemId_) -> 'SolverJobBuilder':
186186
SolverJobBuilder
187187
This `SolverJobBuilder`.
188188
"""
189-
from jpyinterpreter import convert_to_java_python_like_object
189+
from _jpyinterpreter import convert_to_java_python_like_object
190190
return SolverJobBuilder(self._delegate.withProblemId(convert_to_java_python_like_object(problem_id)))
191191

192192
def with_problem(self, problem: Solution_) -> 'SolverJobBuilder':
@@ -203,7 +203,7 @@ def with_problem(self, problem: Solution_) -> 'SolverJobBuilder':
203203
SolverJobBuilder
204204
This `SolverJobBuilder`.
205205
"""
206-
from jpyinterpreter import convert_to_java_python_like_object
206+
from _jpyinterpreter import convert_to_java_python_like_object
207207
return SolverJobBuilder(self._delegate.withProblem(convert_to_java_python_like_object(problem)))
208208

209209
def with_config_override(self, config_override: SolverConfigOverride) -> 'SolverJobBuilder':
@@ -237,7 +237,7 @@ def with_problem_finder(self, problem_finder: Callable[[ProblemId_], Solution_])
237237
This `SolverJobBuilder`.
238238
"""
239239
from java.util.function import Function
240-
from jpyinterpreter import convert_to_java_python_like_object, unwrap_python_like_object
240+
from _jpyinterpreter import convert_to_java_python_like_object, unwrap_python_like_object
241241
java_finder = Function @ (lambda problem_id: convert_to_java_python_like_object(
242242
problem_finder(unwrap_python_like_object(problem_id))))
243243
return SolverJobBuilder(self._delegate.withProblemFinder(java_finder))
@@ -257,7 +257,7 @@ def with_best_solution_consumer(self, best_solution_consumer: Callable[[Solution
257257
This `SolverJobBuilder`.
258258
"""
259259
from java.util.function import Consumer
260-
from jpyinterpreter import unwrap_python_like_object
260+
from _jpyinterpreter import unwrap_python_like_object
261261

262262
java_consumer = Consumer @ (lambda solution: best_solution_consumer(unwrap_python_like_object(solution)))
263263
return SolverJobBuilder(self._delegate.withBestSolutionConsumer(java_consumer))
@@ -278,7 +278,7 @@ def with_final_best_solution_consumer(self, final_best_solution_consumer: Callab
278278
This `SolverJobBuilder`.
279279
"""
280280
from java.util.function import Consumer
281-
from jpyinterpreter import unwrap_python_like_object
281+
from _jpyinterpreter import unwrap_python_like_object
282282

283283
java_consumer = Consumer @ (lambda solution: final_best_solution_consumer(unwrap_python_like_object(solution)))
284284
return SolverJobBuilder(
@@ -300,7 +300,7 @@ def with_exception_handler(self, exception_handler: Callable[[ProblemId_, Except
300300
This `SolverJobBuilder`.
301301
"""
302302
from java.util.function import BiConsumer
303-
from jpyinterpreter import unwrap_python_like_object
303+
from _jpyinterpreter import unwrap_python_like_object
304304

305305
java_consumer = BiConsumer @ (lambda problem_id, error: exception_handler(unwrap_python_like_object(problem_id),
306306
error))
@@ -466,7 +466,7 @@ def get_solver_status(self, problem_id: ProblemId_) -> SolverStatus:
466466
SolverStatus
467467
The `SolverStatus` corresponding to `problem_id`.
468468
"""
469-
from jpyinterpreter import convert_to_java_python_like_object
469+
from _jpyinterpreter import convert_to_java_python_like_object
470470
return SolverStatus._from_java_enum(self._delegate.getSolverStatus(
471471
convert_to_java_python_like_object(problem_id)))
472472

@@ -489,7 +489,7 @@ def terminate_early(self, problem_id: ProblemId_) -> None:
489489
A value given to `SolverManager.solve`, `SolverManager.solve_and_listen` or
490490
`SolverJobBuilder.with_problem_id`.
491491
"""
492-
from jpyinterpreter import convert_to_java_python_like_object
492+
from _jpyinterpreter import convert_to_java_python_like_object
493493
self._delegate.terminateEarly(convert_to_java_python_like_object(problem_id))
494494

495495
def add_problem_change(self, problem_id: ProblemId_, problem_change: ProblemChange[Solution_]) -> Awaitable[None]:
@@ -511,7 +511,7 @@ def add_problem_change(self, problem_id: ProblemId_, problem_change: ProblemChan
511511
Awaitable
512512
An awaitable that completes after the best solution containing this change has been consumed.
513513
"""
514-
from jpyinterpreter import convert_to_java_python_like_object
514+
from _jpyinterpreter import convert_to_java_python_like_object
515515
return wrap_future(self._delegate.addProblemChange(convert_to_java_python_like_object(problem_id),
516516
ProblemChangeWrapper(problem_change)))
517517

timefold-solver-python-core/src/main/python/_timefold_java_interop.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def init(*args, path: list[str] = None, include_timefold_jars: bool = True, log_
6969
log_level : str
7070
The logging level to use.
7171
"""
72-
from jpyinterpreter import init
72+
from _jpyinterpreter import init
7373
if jpype.isJVMStarted(): # noqa
7474
raise RuntimeError('JVM already started. Maybe call init before timefold.solver.types imports?')
7575
if path is None:
@@ -115,7 +115,7 @@ def get_class(python_class: Union[type, Callable]) -> JClass:
115115
"""Return the Java Class for the given Python Class"""
116116
from java.lang import Object, Class
117117
from ai.timefold.jpyinterpreter.types.wrappers import OpaquePythonReference
118-
from jpyinterpreter import is_c_native, get_java_type_for_python_type
118+
from _jpyinterpreter import is_c_native, get_java_type_for_python_type
119119

120120
if python_class is None:
121121
return cast(JClass, None)
@@ -146,7 +146,7 @@ def get_asm_type(python_class: Union[type, Callable]) -> Any:
146146
from java.lang import Object, Class
147147
from ai.timefold.jpyinterpreter import AnnotationMetadata
148148
from ai.timefold.jpyinterpreter.types.wrappers import OpaquePythonReference
149-
from jpyinterpreter import is_c_native, get_java_type_for_python_type
149+
from _jpyinterpreter import is_c_native, get_java_type_for_python_type
150150

151151
if python_class is None:
152152
return None
@@ -227,7 +227,7 @@ def __exit__(self, exc_type, exc_val, exc_tb):
227227

228228

229229
def compile_class(python_class: type) -> None:
230-
from jpyinterpreter import translate_python_class_to_java_class
230+
from _jpyinterpreter import translate_python_class_to_java_class
231231
ensure_init()
232232
class_identifier = _get_class_identifier_for_object(python_class)
233233
out = translate_python_class_to_java_class(python_class).getJavaClass()

timefold-solver-python-core/src/main/python/domain/_annotations.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from ._variable_listener import VariableListener
44
from .._timefold_java_interop import ensure_init, get_asm_type
5-
from jpyinterpreter import JavaAnnotation, AnnotationValueSupplier
5+
from _jpyinterpreter import JavaAnnotation, AnnotationValueSupplier
66
from jpype import JImplements, JOverride
77
from typing import Union, List, Callable, Type, TYPE_CHECKING, TypeVar
88

@@ -663,7 +663,7 @@ def planning_entity(entity_class: Type = None, /, *, pinning_filter: Callable =
663663
def planning_entity_wrapper(entity_class_argument):
664664
from .._timefold_java_interop import _add_to_compilation_queue
665665
from ai.timefold.solver.core.api.domain.entity import PinningFilter
666-
from jpyinterpreter import add_class_annotation, translate_python_bytecode_to_java_bytecode
666+
from _jpyinterpreter import add_class_annotation, translate_python_bytecode_to_java_bytecode
667667
from typing import get_origin, Annotated
668668

669669
planning_pin_field = None
@@ -736,7 +736,7 @@ def planning_solution(planning_solution_class: Type[Solution_]) -> Type[Solution
736736
... score: Annotated[HardSoftScore, PlanningScore]
737737
"""
738738
ensure_init()
739-
from jpyinterpreter import add_class_annotation
739+
from _jpyinterpreter import add_class_annotation
740740
from .._timefold_java_interop import _add_to_compilation_queue
741741
from ai.timefold.solver.core.api.domain.solution import PlanningSolution as JavaPlanningSolution
742742
out = add_class_annotation(JavaPlanningSolution)(planning_solution_class)
@@ -764,7 +764,7 @@ def constraint_configuration(constraint_configuration_class: Type[Solution_]) ->
764764
... maximize_value: Annotated[HardSoftScore, ConstraintWeight('Maximize value')]
765765
"""
766766
ensure_init()
767-
from jpyinterpreter import add_class_annotation
767+
from _jpyinterpreter import add_class_annotation
768768
from ai.timefold.solver.core.api.domain.constraintweight import (
769769
ConstraintConfiguration as JavaConstraintConfiguration)
770770
out = add_class_annotation(JavaConstraintConfiguration)(constraint_configuration_class)

timefold-solver-python-core/src/main/python/domain/_variable_listener.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from ..score import ScoreDirector
2-
from jpyinterpreter import add_java_interface
2+
from _jpyinterpreter import add_java_interface
33
from typing import TYPE_CHECKING, TypeVar
44

55
if TYPE_CHECKING:

0 commit comments

Comments
 (0)