Skip to content
This repository has been archived by the owner on Jul 17, 2024. It is now read-only.

chore: Remove Java type stubs from default build, rename jpyinterpreter to _jpyinterpreter #54

Merged
merged 3 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Timefold Solver
Christopher-Chianelli marked this conversation as resolved.
Show resolved Hide resolved

[![PyPI](https://img.shields.io/pypi/v/timefold "PyPI")](https://pypi.org/project/timefold-solver/)
[![PyPI](https://img.shields.io/pypi/v/timefold-solver "PyPI")](https://pypi.org/project/timefold-solver/)

[![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)
[![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)
Expand Down Expand Up @@ -51,7 +51,7 @@ To declare Planning Entities, use the `@planning_entity` decorator along with an
```python
from dataclasses import dataclass, field
from typing import Annotated
from timefold.solver import planning_entity, PlanningId, PlanningVariable
from timefold.solver.domain import planning_entity, PlanningId, PlanningVariable

@planning_entity
@dataclass
Expand All @@ -75,7 +75,8 @@ To declare the Planning Solution, use the `@planning_solution` decorator:
```python
from dataclasses import dataclass, field
from typing import Annotated
from timefold.solver import planning_solution, ProblemFactCollectionProperty, ValueRangeProvider, PlanningEntityCollectionProperty, PlanningScore
from timefold.solver.domain import (planning_solution, ProblemFactCollectionProperty, ValueRangeProvider,
PlanningEntityCollectionProperty, PlanningScore)
from timefold.solver.score import HardSoftScore

@planning_solution
Expand All @@ -101,9 +102,7 @@ You define your constraints by using the ConstraintFactory:

```python
from domain import Lesson
from timefold.solver import constraint_provider
from timefold.solver.types import Joiners, HardSoftScore

from timefold.solver.score import Joiners, HardSoftScore, constraint_provider

@constraint_provider
def define_constraints(constraint_factory):
Expand Down
68 changes: 45 additions & 23 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@
from shutil import copyfile
import sys

include_java_stubs = os.environ.get('INCLUDE_JAVA_STUBS', 'false').lower() == 'true'
"""
If Java type stubs should be generated.
These are for our own type checker; users should never need
to use them (since they should be using the Python classes).

Not included in the default build since IDE's will see multiple
packages that define a class (the one from `ai.timefold...`
and the one from `timefold.solver...`), which can lead to the
wrong one being imported.
"""


class FetchDependencies(build_py):
"""
Expand Down Expand Up @@ -47,7 +59,10 @@ def run(self):

subprocess.run([str((project_root / command).absolute()), 'clean', 'install'],
cwd=project_root, check=True)
self.create_stubs(project_root, command)

if include_java_stubs:
self.create_stubs(project_root, command)

classpath_jars = []
# Add the main artifact
classpath_jars.extend(glob.glob(os.path.join(project_root, 'timefold-solver-python-core', 'target', '*.jar')))
Expand All @@ -73,6 +88,33 @@ def find_stub_files(stub_root: str):
yield os.path.join(root, file)


packages = [
'timefold.solver',
'timefold.solver.config',
'timefold.solver.domain',
'timefold.solver.heuristic',
'timefold.solver.score',
'timefold.solver.test',
'_jpyinterpreter',
]

package_dir = {
'timefold.solver': 'timefold-solver-python-core/src/main/python',
'_jpyinterpreter': 'jpyinterpreter/src/main/python',
}

if include_java_stubs:
packages += [
'java-stubs',
'jpype-stubs',
'ai-stubs',
]
package_dir.update({
'java-stubs': 'timefold-solver-python-core/src/main/resources',
'jpype-stubs': 'timefold-solver-python-core/src/main/resources',
'ai-stubs': 'timefold-solver-python-core/src/main/resources',
})

this_directory = Path(__file__).parent
long_description = (this_directory / "README.md").read_text()
timefold_solver_python_version = '999-dev0'
Expand All @@ -98,28 +140,8 @@ def find_stub_files(stub_root: str):
'License :: OSI Approved :: Apache Software License',
'Operating System :: OS Independent'
],
packages=['timefold.solver',
'timefold.solver.config',
'timefold.solver.domain',
'timefold.solver.heuristic',
'timefold.solver.score',
'timefold.solver.test',
'jpyinterpreter',
'java-stubs',
'jpype-stubs',
'ai-stubs'],
package_dir={
'timefold.solver': 'timefold-solver-python-core/src/main/python',
'jpyinterpreter': 'jpyinterpreter/src/main/python',
# Setup tools need a non-empty directory to use as base
# Since these packages are generated during the build,
# we use the src/main/resources package, which does
# not contain any python files and is already included
# in the build
'java-stubs': 'timefold-solver-python-core/src/main/resources',
'jpype-stubs': 'timefold-solver-python-core/src/main/resources',
'ai-stubs': 'timefold-solver-python-core/src/main/resources',
},
packages=packages,
package_dir=package_dir,
test_suite='tests',
python_requires='>=3.10',
install_requires=[
Expand Down
4 changes: 2 additions & 2 deletions tests/test_solver_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def get_java_solver_config(path: pathlib.Path):


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


def test_reload_from_solver_config_file():
from jpyinterpreter import get_java_type_for_python_type
from _jpyinterpreter import get_java_type_for_python_type

@planning_solution
class RedefinedSolution:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from abc import ABC, abstractmethod
from typing import TypeVar, Optional, Callable, TYPE_CHECKING, Generic
from types import FunctionType
from jpyinterpreter import (convert_to_java_python_like_object,
from _jpyinterpreter import (convert_to_java_python_like_object,
unwrap_python_like_object,
update_python_object_from_java,
translate_python_bytecode_to_java_bytecode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def update(self, solution: Solution_, solution_update_policy=None) -> 'Score':
The score of the updated solution.
"""
# TODO handle solution_update_policy
from jpyinterpreter import convert_to_java_python_like_object, update_python_object_from_java
from _jpyinterpreter import convert_to_java_python_like_object, update_python_object_from_java
java_solution = convert_to_java_python_like_object(solution)
out = self._delegate.update(java_solution)
update_python_object_from_java(java_solution)
Expand All @@ -84,7 +84,7 @@ def analyze(self, solution: Solution_, score_analysis_fetch_policy=None, solutio
The `ScoreAnalysis` corresponding to the given solution.
"""
# TODO handle policies
from jpyinterpreter import convert_to_java_python_like_object
from _jpyinterpreter import convert_to_java_python_like_object
return ScoreAnalysis(self._delegate.analyze(convert_to_java_python_like_object(solution)))

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

def recommend_fit(self, solution: Solution_, entity_or_element, proposition_function,
Expand Down
4 changes: 2 additions & 2 deletions timefold-solver-python-core/src/main/python/_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def solve(self, problem: Solution_):
"""
from java.lang import Exception as JavaException
from ai.timefold.jpyinterpreter.types.errors import PythonBaseException
from jpyinterpreter import convert_to_java_python_like_object, unwrap_python_like_object
from _jpyinterpreter import convert_to_java_python_like_object, unwrap_python_like_object
java_problem = convert_to_java_python_like_object(problem)
if not self._solution_class.isInstance(java_problem):
raise ValueError(
Expand Down Expand Up @@ -229,7 +229,7 @@ def add_event_listener(self, event_listener: Callable[[BestSolutionChangedEvent[
class EventListener:
@JOverride
def bestSolutionChanged(self, event):
from jpyinterpreter import unwrap_python_like_object
from _jpyinterpreter import unwrap_python_like_object
nonlocal event_listener_list
event = BestSolutionChangedEvent(
new_best_score=event.getNewBestScore(),
Expand Down
22 changes: 11 additions & 11 deletions timefold-solver-python-core/src/main/python/_solver_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def get_problem_id(self) -> ProblemId_:
ProblemId_
The problem id corresponding to this `SolverJob`.
"""
from jpyinterpreter import unwrap_python_like_object
from _jpyinterpreter import unwrap_python_like_object
return unwrap_python_like_object(self._delegate.getProblemId())

def get_solver_status(self) -> SolverStatus:
Expand Down Expand Up @@ -109,7 +109,7 @@ def get_final_best_solution(self) -> Solution_:
Solution_
Never ``None``, but it could be the original uninitialized problem.
"""
from jpyinterpreter import unwrap_python_like_object
from _jpyinterpreter import unwrap_python_like_object
return unwrap_python_like_object(self._delegate.getFinalBestSolution())

def terminate_early(self) -> None:
Expand Down Expand Up @@ -186,7 +186,7 @@ def with_problem_id(self, problem_id: ProblemId_) -> 'SolverJobBuilder':
SolverJobBuilder
This `SolverJobBuilder`.
"""
from jpyinterpreter import convert_to_java_python_like_object
from _jpyinterpreter import convert_to_java_python_like_object
return SolverJobBuilder(self._delegate.withProblemId(convert_to_java_python_like_object(problem_id)))

def with_problem(self, problem: Solution_) -> 'SolverJobBuilder':
Expand All @@ -203,7 +203,7 @@ def with_problem(self, problem: Solution_) -> 'SolverJobBuilder':
SolverJobBuilder
This `SolverJobBuilder`.
"""
from jpyinterpreter import convert_to_java_python_like_object
from _jpyinterpreter import convert_to_java_python_like_object
return SolverJobBuilder(self._delegate.withProblem(convert_to_java_python_like_object(problem)))

def with_config_override(self, config_override: SolverConfigOverride) -> 'SolverJobBuilder':
Expand Down Expand Up @@ -237,7 +237,7 @@ def with_problem_finder(self, problem_finder: Callable[[ProblemId_], Solution_])
This `SolverJobBuilder`.
"""
from java.util.function import Function
from jpyinterpreter import convert_to_java_python_like_object, unwrap_python_like_object
from _jpyinterpreter import convert_to_java_python_like_object, unwrap_python_like_object
java_finder = Function @ (lambda problem_id: convert_to_java_python_like_object(
problem_finder(unwrap_python_like_object(problem_id))))
return SolverJobBuilder(self._delegate.withProblemFinder(java_finder))
Expand All @@ -257,7 +257,7 @@ def with_best_solution_consumer(self, best_solution_consumer: Callable[[Solution
This `SolverJobBuilder`.
"""
from java.util.function import Consumer
from jpyinterpreter import unwrap_python_like_object
from _jpyinterpreter import unwrap_python_like_object

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

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

java_consumer = BiConsumer @ (lambda problem_id, error: exception_handler(unwrap_python_like_object(problem_id),
error))
Expand Down Expand Up @@ -466,7 +466,7 @@ def get_solver_status(self, problem_id: ProblemId_) -> SolverStatus:
SolverStatus
The `SolverStatus` corresponding to `problem_id`.
"""
from jpyinterpreter import convert_to_java_python_like_object
from _jpyinterpreter import convert_to_java_python_like_object
return SolverStatus._from_java_enum(self._delegate.getSolverStatus(
convert_to_java_python_like_object(problem_id)))

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def init(*args, path: list[str] = None, include_timefold_jars: bool = True, log_
log_level : str
The logging level to use.
"""
from jpyinterpreter import init
from _jpyinterpreter import init
if jpype.isJVMStarted(): # noqa
raise RuntimeError('JVM already started. Maybe call init before timefold.solver.types imports?')
if path is None:
Expand Down Expand Up @@ -115,7 +115,7 @@ def get_class(python_class: Union[type, Callable]) -> JClass:
"""Return the Java Class for the given Python Class"""
from java.lang import Object, Class
from ai.timefold.jpyinterpreter.types.wrappers import OpaquePythonReference
from jpyinterpreter import is_c_native, get_java_type_for_python_type
from _jpyinterpreter import is_c_native, get_java_type_for_python_type

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

if python_class is None:
return None
Expand Down Expand Up @@ -227,7 +227,7 @@ def __exit__(self, exc_type, exc_val, exc_tb):


def compile_class(python_class: type) -> None:
from jpyinterpreter import translate_python_class_to_java_class
from _jpyinterpreter import translate_python_class_to_java_class
ensure_init()
class_identifier = _get_class_identifier_for_object(python_class)
out = translate_python_class_to_java_class(python_class).getJavaClass()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from ._variable_listener import VariableListener
from .._timefold_java_interop import ensure_init, get_asm_type
from jpyinterpreter import JavaAnnotation, AnnotationValueSupplier
from _jpyinterpreter import JavaAnnotation, AnnotationValueSupplier
from jpype import JImplements, JOverride
from typing import Union, List, Callable, Type, TYPE_CHECKING, TypeVar

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

planning_pin_field = None
Expand Down Expand Up @@ -736,7 +736,7 @@ def planning_solution(planning_solution_class: Type[Solution_]) -> Type[Solution
... score: Annotated[HardSoftScore, PlanningScore]
"""
ensure_init()
from jpyinterpreter import add_class_annotation
from _jpyinterpreter import add_class_annotation
from .._timefold_java_interop import _add_to_compilation_queue
from ai.timefold.solver.core.api.domain.solution import PlanningSolution as JavaPlanningSolution
out = add_class_annotation(JavaPlanningSolution)(planning_solution_class)
Expand Down Expand Up @@ -764,7 +764,7 @@ def constraint_configuration(constraint_configuration_class: Type[Solution_]) ->
... maximize_value: Annotated[HardSoftScore, ConstraintWeight('Maximize value')]
"""
ensure_init()
from jpyinterpreter import add_class_annotation
from _jpyinterpreter import add_class_annotation
from ai.timefold.solver.core.api.domain.constraintweight import (
ConstraintConfiguration as JavaConstraintConfiguration)
out = add_class_annotation(JavaConstraintConfiguration)(constraint_configuration_class)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from ..score import ScoreDirector
from jpyinterpreter import add_java_interface
from _jpyinterpreter import add_java_interface
from typing import TYPE_CHECKING, TypeVar

if TYPE_CHECKING:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def nearby_distance_meter(distance_function: Callable[[Origin_, Destination_], f

"""
ensure_init()
from jpyinterpreter import translate_python_bytecode_to_java_bytecode, generate_proxy_class_for_translated_function
from _jpyinterpreter import translate_python_bytecode_to_java_bytecode, generate_proxy_class_for_translated_function
from ai.timefold.solver.core.impl.heuristic.selector.common.nearby import NearbyDistanceMeter # noqa
java_class = generate_proxy_class_for_translated_function(NearbyDistanceMeter,
translate_python_bytecode_to_java_bytecode(
Expand Down
Loading
Loading