solverforge 0.6.9__tar.gz → 0.6.10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. {solverforge-0.6.9 → solverforge-0.6.10}/Cargo.lock +1 -1
  2. {solverforge-0.6.9 → solverforge-0.6.10}/Cargo.toml +1 -1
  3. {solverforge-0.6.9 → solverforge-0.6.10}/PKG-INFO +5 -5
  4. {solverforge-0.6.9 → solverforge-0.6.10}/README.md +4 -4
  5. {solverforge-0.6.9 → solverforge-0.6.10}/pyproject.toml +5 -1
  6. {solverforge-0.6.9 → solverforge-0.6.10}/python/solverforge/__init__.py +1 -1
  7. {solverforge-0.6.9 → solverforge-0.6.10}/python/solverforge/_native.pyi +2 -6
  8. {solverforge-0.6.9 → solverforge-0.6.10}/python/solverforge/config.py +1 -3
  9. {solverforge-0.6.9 → solverforge-0.6.10}/python/solverforge/constraints.py +8 -25
  10. {solverforge-0.6.9 → solverforge-0.6.10}/python/solverforge/decorators.py +2 -5
  11. {solverforge-0.6.9 → solverforge-0.6.10}/python/solverforge/errors.py +0 -1
  12. {solverforge-0.6.9 → solverforge-0.6.10}/python/solverforge/events.py +0 -1
  13. {solverforge-0.6.9 → solverforge-0.6.10}/python/solverforge/fields.py +7 -22
  14. {solverforge-0.6.9 → solverforge-0.6.10}/python/solverforge/groups.py +4 -13
  15. {solverforge-0.6.9 → solverforge-0.6.10}/python/solverforge/model.py +6 -9
  16. {solverforge-0.6.9 → solverforge-0.6.10}/python/solverforge/solver.py +1 -3
  17. {solverforge-0.6.9 → solverforge-0.6.10}/python/solverforge/typing.py +0 -1
  18. {solverforge-0.6.9 → solverforge-0.6.10}/LICENSE +0 -0
  19. {solverforge-0.6.9 → solverforge-0.6.10}/python/solverforge/console.py +0 -0
  20. {solverforge-0.6.9 → solverforge-0.6.10}/python/solverforge/joiner.py +0 -0
  21. {solverforge-0.6.9 → solverforge-0.6.10}/python/solverforge/manager.py +0 -0
  22. {solverforge-0.6.9 → solverforge-0.6.10}/python/solverforge/py.typed +0 -0
  23. {solverforge-0.6.9 → solverforge-0.6.10}/python/solverforge/score.py +0 -0
  24. {solverforge-0.6.9 → solverforge-0.6.10}/python/solverforge/ui.py +0 -0
  25. {solverforge-0.6.9 → solverforge-0.6.10}/rust-toolchain.toml +0 -0
  26. {solverforge-0.6.9 → solverforge-0.6.10}/src/bindings.rs +0 -0
  27. {solverforge-0.6.9 → solverforge-0.6.10}/src/callbacks/call.rs +0 -0
  28. {solverforge-0.6.9 → solverforge-0.6.10}/src/callbacks/mod.rs +0 -0
  29. {solverforge-0.6.9 → solverforge-0.6.10}/src/callbacks/registry.rs +0 -0
  30. {solverforge-0.6.9 → solverforge-0.6.10}/src/callbacks/threading.rs +0 -0
  31. {solverforge-0.6.9 → solverforge-0.6.10}/src/config.rs +0 -0
  32. {solverforge-0.6.9 → solverforge-0.6.10}/src/constraints/compiled.rs +0 -0
  33. {solverforge-0.6.9 → solverforge-0.6.10}/src/constraints/evaluate.rs +0 -0
  34. {solverforge-0.6.9 → solverforge-0.6.10}/src/constraints/incremental.rs +0 -0
  35. {solverforge-0.6.9 → solverforge-0.6.10}/src/constraints/list_precedence.rs +0 -0
  36. {solverforge-0.6.9 → solverforge-0.6.10}/src/constraints/matches.rs +0 -0
  37. {solverforge-0.6.9 → solverforge-0.6.10}/src/constraints/mod.rs +0 -0
  38. {solverforge-0.6.9 → solverforge-0.6.10}/src/constraints/state.rs +0 -0
  39. {solverforge-0.6.9 → solverforge-0.6.10}/src/constraints/stream_plan.rs +0 -0
  40. {solverforge-0.6.9 → solverforge-0.6.10}/src/descriptor/extractor.rs +0 -0
  41. {solverforge-0.6.9 → solverforge-0.6.10}/src/descriptor/mod.rs +0 -0
  42. {solverforge-0.6.9 → solverforge-0.6.10}/src/descriptor/variable.rs +0 -0
  43. {solverforge-0.6.9 → solverforge-0.6.10}/src/error.rs +0 -0
  44. {solverforge-0.6.9 → solverforge-0.6.10}/src/intern.rs +0 -0
  45. {solverforge-0.6.9 → solverforge-0.6.10}/src/lib.rs +0 -0
  46. {solverforge-0.6.9 → solverforge-0.6.10}/src/manager/candidate_trace.rs +0 -0
  47. {solverforge-0.6.9 → solverforge-0.6.10}/src/manager/events.rs +0 -0
  48. {solverforge-0.6.9 → solverforge-0.6.10}/src/manager/jobs.rs +0 -0
  49. {solverforge-0.6.9 → solverforge-0.6.10}/src/manager/mod.rs +0 -0
  50. {solverforge-0.6.9 → solverforge-0.6.10}/src/manager/provenance.rs +0 -0
  51. {solverforge-0.6.9 → solverforge-0.6.10}/src/proxy/entity.rs +0 -0
  52. {solverforge-0.6.9 → solverforge-0.6.10}/src/proxy/list.rs +0 -0
  53. {solverforge-0.6.9 → solverforge-0.6.10}/src/proxy/mod.rs +0 -0
  54. {solverforge-0.6.9 → solverforge-0.6.10}/src/proxy/solution.rs +0 -0
  55. {solverforge-0.6.9 → solverforge-0.6.10}/src/runtime/candidate_metric.rs +0 -0
  56. {solverforge-0.6.9 → solverforge-0.6.10}/src/runtime/dynamic_assignment_group.rs +0 -0
  57. {solverforge-0.6.9 → solverforge-0.6.10}/src/runtime/dynamic_provider.rs +0 -0
  58. {solverforge-0.6.9 → solverforge-0.6.10}/src/runtime/list_slots.rs +0 -0
  59. {solverforge-0.6.9 → solverforge-0.6.10}/src/runtime/mod.rs +0 -0
  60. {solverforge-0.6.9 → solverforge-0.6.10}/src/runtime/scalar_slots.rs +0 -0
  61. {solverforge-0.6.9 → solverforge-0.6.10}/src/schema/build.rs +0 -0
  62. {solverforge-0.6.9 → solverforge-0.6.10}/src/schema/compiled.rs +0 -0
  63. {solverforge-0.6.9 → solverforge-0.6.10}/src/schema/mod.rs +0 -0
  64. {solverforge-0.6.9 → solverforge-0.6.10}/src/schema/parse.rs +0 -0
  65. {solverforge-0.6.9 → solverforge-0.6.10}/src/schema/python.rs +0 -0
  66. {solverforge-0.6.9 → solverforge-0.6.10}/src/schema/runtime_plan.rs +0 -0
  67. {solverforge-0.6.9 → solverforge-0.6.10}/src/schema/types.rs +0 -0
  68. {solverforge-0.6.9 → solverforge-0.6.10}/src/schema/validate.rs +0 -0
  69. {solverforge-0.6.9 → solverforge-0.6.10}/src/score.rs +0 -0
  70. {solverforge-0.6.9 → solverforge-0.6.10}/src/solver/api.rs +0 -0
  71. {solverforge-0.6.9 → solverforge-0.6.10}/src/solver/console.rs +0 -0
  72. {solverforge-0.6.9 → solverforge-0.6.10}/src/solver/mod.rs +0 -0
  73. {solverforge-0.6.9 → solverforge-0.6.10}/src/solver/progress.rs +0 -0
  74. {solverforge-0.6.9 → solverforge-0.6.10}/src/solver/result.rs +0 -0
  75. {solverforge-0.6.9 → solverforge-0.6.10}/src/solver/solvable.rs +0 -0
  76. {solverforge-0.6.9 → solverforge-0.6.10}/src/state/callback_view.rs +0 -0
  77. {solverforge-0.6.9 → solverforge-0.6.10}/src/state/clone.rs +0 -0
  78. {solverforge-0.6.9 → solverforge-0.6.10}/src/state/entity_table.rs +0 -0
  79. {solverforge-0.6.9 → solverforge-0.6.10}/src/state/marshal.rs +0 -0
  80. {solverforge-0.6.9 → solverforge-0.6.10}/src/state/mod.rs +0 -0
  81. {solverforge-0.6.9 → solverforge-0.6.10}/src/state/solution.rs +0 -0
  82. {solverforge-0.6.9 → solverforge-0.6.10}/src/state/variables.rs +0 -0
  83. {solverforge-0.6.9 → solverforge-0.6.10}/src/ui.rs +0 -0
  84. {solverforge-0.6.9 → solverforge-0.6.10}/src/value.rs +0 -0
@@ -644,7 +644,7 @@ dependencies = [
644
644
 
645
645
  [[package]]
646
646
  name = "solverforge_py"
647
- version = "0.6.9"
647
+ version = "0.6.10"
648
648
  dependencies = [
649
649
  "indexmap",
650
650
  "once_cell",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "solverforge_py"
3
- version = "0.6.9"
3
+ version = "0.6.10"
4
4
  edition = "2021"
5
5
  license = "Apache-2.0"
6
6
  rust-version = "1.95"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: solverforge
3
- Version: 0.6.9
3
+ Version: 0.6.10
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: Intended Audience :: Science/Research
@@ -51,12 +51,12 @@ requirements, and other APIs that are not part of this package.
51
51
 
52
52
  ## Installation
53
53
 
54
- The package version prepared by this checkout is `solverforge` `0.6.9`, targeting
54
+ The package version prepared by this checkout is `solverforge` `0.6.10`, targeting
55
55
  the published and registry-locked SolverForge Rust `0.19.7` dependency base.
56
56
  Install the matching package after its Python release with:
57
57
 
58
58
  ```sh
59
- python3.14 -m pip install "solverforge==0.6.9"
59
+ python3.14 -m pip install "solverforge==0.6.10"
60
60
  ```
61
61
 
62
62
  The installable wheel contains the core `solverforge` package, native extension,
@@ -76,12 +76,12 @@ make deliveries-run PORT=7861
76
76
  ```
77
77
 
78
78
  The same source-checkout examples can be run against the matching published
79
- package once `0.6.9` is released, without `PYTHONPATH` or an editable checkout:
79
+ package once `0.6.10` is released, without `PYTHONPATH` or an editable checkout:
80
80
 
81
81
  ```sh
82
82
  python3.14 -m venv .venv-examples
83
83
  . .venv-examples/bin/activate
84
- python -m pip install "solverforge[examples]==0.6.9"
84
+ python -m pip install "solverforge[examples]==0.6.10"
85
85
  python examples/nqueens.py
86
86
  python -m examples.solverforge_hospital
87
87
  python -m examples.solverforge_deliveries
@@ -20,12 +20,12 @@ requirements, and other APIs that are not part of this package.
20
20
 
21
21
  ## Installation
22
22
 
23
- The package version prepared by this checkout is `solverforge` `0.6.9`, targeting
23
+ The package version prepared by this checkout is `solverforge` `0.6.10`, targeting
24
24
  the published and registry-locked SolverForge Rust `0.19.7` dependency base.
25
25
  Install the matching package after its Python release with:
26
26
 
27
27
  ```sh
28
- python3.14 -m pip install "solverforge==0.6.9"
28
+ python3.14 -m pip install "solverforge==0.6.10"
29
29
  ```
30
30
 
31
31
  The installable wheel contains the core `solverforge` package, native extension,
@@ -45,12 +45,12 @@ make deliveries-run PORT=7861
45
45
  ```
46
46
 
47
47
  The same source-checkout examples can be run against the matching published
48
- package once `0.6.9` is released, without `PYTHONPATH` or an editable checkout:
48
+ package once `0.6.10` is released, without `PYTHONPATH` or an editable checkout:
49
49
 
50
50
  ```sh
51
51
  python3.14 -m venv .venv-examples
52
52
  . .venv-examples/bin/activate
53
- python -m pip install "solverforge[examples]==0.6.9"
53
+ python -m pip install "solverforge[examples]==0.6.10"
54
54
  python examples/nqueens.py
55
55
  python -m examples.solverforge_hospital
56
56
  python -m examples.solverforge_deliveries
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "solverforge"
7
- version = "0.6.9"
7
+ version = "0.6.10"
8
8
  description = "Dynamic Python bindings for the SolverForge constraint solver."
9
9
  readme = "README.md"
10
10
  license = { text = "Apache-2.0" }
@@ -51,6 +51,10 @@ strip = true
51
51
  testpaths = ["tests/python"]
52
52
  pythonpath = ["python"]
53
53
 
54
+ [tool.black]
55
+ line-length = 100
56
+ target-version = ["py314"]
57
+
54
58
  [tool.ruff]
55
59
  line-length = 100
56
60
  target-version = "py314"
@@ -41,7 +41,7 @@ from .manager import JobHandle, SolverManager
41
41
  from .score import HardMediumSoftScore, HardSoftDecimalScore, HardSoftScore, SoftScore
42
42
  from .solver import Solver
43
43
 
44
- __version__ = "0.6.9"
44
+ __version__ = "0.6.10"
45
45
 
46
46
  __all__ = [
47
47
  "CallbackError",
@@ -38,18 +38,14 @@ class SolverManager:
38
38
  def _preflight_qualified_candidate_trace_provenance(
39
39
  self,
40
40
  *,
41
- qualified_candidate_trace_provenance: (
42
- QualifiedCandidateTraceProvenance | None
43
- ) = None,
41
+ qualified_candidate_trace_provenance: QualifiedCandidateTraceProvenance | None = None,
44
42
  ) -> None: ...
45
43
  def solve(
46
44
  self,
47
45
  solution: object,
48
46
  schema: CompiledSchema,
49
47
  *,
50
- qualified_candidate_trace_provenance: (
51
- QualifiedCandidateTraceProvenance | None
52
- ) = None,
48
+ qualified_candidate_trace_provenance: QualifiedCandidateTraceProvenance | None = None,
53
49
  ) -> int: ...
54
50
  def get_status(self, job_id: int) -> dict[str, object]: ...
55
51
  def telemetry_detail(self, job_id: int) -> dict[str, object]: ...
@@ -162,9 +162,7 @@ def _normalize_phases(phases: list[dict[str, Any]]) -> list[dict[str, Any]]:
162
162
  dict(phase_copy["termination"])
163
163
  ).to_dict()
164
164
  if "child_phases" in phase_copy:
165
- phase_copy["child_phases"] = _normalize_phases(
166
- list(phase_copy["child_phases"])
167
- )
165
+ phase_copy["child_phases"] = _normalize_phases(list(phase_copy["child_phases"]))
168
166
  normalized.append(phase_copy)
169
167
  return normalized
170
168
 
@@ -163,10 +163,7 @@ class ListPrecedenceMakespanConstraintStream:
163
163
  if plan.precedence_duration is None and plan.precedence_duration_field is None:
164
164
  msg = f"{self.entity_type.__name__}.{self.variable_name} requires precedence_duration"
165
165
  raise TypeError(msg)
166
- if (
167
- plan.precedence_successors is None
168
- and plan.precedence_successors_field is None
169
- ):
166
+ if plan.precedence_successors is None and plan.precedence_successors_field is None:
170
167
  msg = f"{self.entity_type.__name__}.{self.variable_name} requires precedence_successors"
171
168
  raise TypeError(msg)
172
169
  if plan.element_owner is None and plan.element_owner_field is None:
@@ -382,9 +379,7 @@ class ConstraintFactory:
382
379
  self.score_family = score_family
383
380
 
384
381
  def for_each(self, entity_type: type[object]) -> UniConstraintStream:
385
- return UniConstraintStream(
386
- entity_type=entity_type, score_family=self.score_family
387
- )
382
+ return UniConstraintStream(entity_type=entity_type, score_family=self.score_family)
388
383
 
389
384
  def for_each_unassigned_element(
390
385
  self,
@@ -409,14 +404,10 @@ class ConstraintFactory:
409
404
  )
410
405
 
411
406
  def join(self, *_args: Any, **_kwargs: Any) -> None:
412
- raise NotImplementedError(
413
- "dynamic join is implemented in the native stream planner"
414
- )
407
+ raise NotImplementedError("dynamic join is implemented in the native stream planner")
415
408
 
416
409
  def if_exists(self, *_args: Any, **_kwargs: Any) -> None:
417
- raise NotImplementedError(
418
- "dynamic if_exists is implemented in the native stream planner"
419
- )
410
+ raise NotImplementedError("dynamic if_exists is implemented in the native stream planner")
420
411
 
421
412
  def if_not_exists(self, *_args: Any, **_kwargs: Any) -> None:
422
413
  raise NotImplementedError(
@@ -424,14 +415,10 @@ class ConstraintFactory:
424
415
  )
425
416
 
426
417
  def group_by(self, *_args: Any, **_kwargs: Any) -> None:
427
- raise NotImplementedError(
428
- "dynamic group_by is implemented in the native stream planner"
429
- )
418
+ raise NotImplementedError("dynamic group_by is implemented in the native stream planner")
430
419
 
431
420
  def flattened(self, *_args: Any, **_kwargs: Any) -> None:
432
- raise NotImplementedError(
433
- "dynamic flattened is implemented in the native stream planner"
434
- )
421
+ raise NotImplementedError("dynamic flattened is implemented in the native stream planner")
435
422
 
436
423
 
437
424
  def joiner_to_native(joiner: object) -> object:
@@ -462,9 +449,7 @@ def collector_to_native(collector: object) -> object:
462
449
  return collector
463
450
 
464
451
 
465
- def _list_variable_element_collection(
466
- entity_type: type[object], variable_name: str
467
- ) -> str:
452
+ def _list_variable_element_collection(entity_type: type[object], variable_name: str) -> str:
468
453
  field_info = _list_variable_metadata(entity_type, variable_name)
469
454
  collection = field_info.get("element_collection")
470
455
  if isinstance(collection, str) and collection:
@@ -473,9 +458,7 @@ def _list_variable_element_collection(
473
458
  raise TypeError(msg)
474
459
 
475
460
 
476
- def _list_variable_metadata(
477
- entity_type: type[object], variable_name: str
478
- ) -> dict[str, object]:
461
+ def _list_variable_metadata(entity_type: type[object], variable_name: str) -> dict[str, object]:
479
462
  metadata = getattr(entity_type, "__solverforge_entity__", None)
480
463
  if not isinstance(metadata, dict):
481
464
  msg = f"{entity_type.__name__} is not marked with @planning_entity"
@@ -143,8 +143,7 @@ def problem_fact(cls: T) -> T:
143
143
  fields = _collect_fields(cls)
144
144
  if any(field["kind"] == "planning_pin" for field in fields):
145
145
  msg = (
146
- f"{cls.__name__} is a problem fact; planning_pin is only valid on a "
147
- "planning entity"
146
+ f"{cls.__name__} is a problem fact; planning_pin is only valid on a " "planning entity"
148
147
  )
149
148
  raise ModelValidationError(msg)
150
149
  setattr(
@@ -220,9 +219,7 @@ def planning_solution(
220
219
  shadow_variable_updates: dict[str, Any] | list[dict[str, Any]] | None = None,
221
220
  ) -> Callable[[T], T]:
222
221
  def decorate(cls: T) -> T:
223
- updates = (
224
- shadow_updates if shadow_updates is not None else shadow_variable_updates
225
- )
222
+ updates = shadow_updates if shadow_updates is not None else shadow_variable_updates
226
223
  if updates is None:
227
224
  update_list: list[dict[str, Any]] = []
228
225
  elif isinstance(updates, dict):
@@ -12,4 +12,3 @@ class CallbackError(SolverForgeError):
12
12
 
13
13
  class NativeBridgeError(SolverForgeError):
14
14
  """Raised when the PyO3 bridge reports a native-side failure."""
15
-
@@ -10,4 +10,3 @@ class SolverEvent:
10
10
  lifecycle_state: str
11
11
  snapshot_revision: int | None = None
12
12
  error: str | None = None
13
-
@@ -59,9 +59,7 @@ class SolutionCallback:
59
59
  raise TypeError("SolutionCallback callback must be callable")
60
60
 
61
61
 
62
- ListValueSource: TypeAlias = (
63
- RowField | SolutionField | EntityCallback | SolutionCallback
64
- )
62
+ ListValueSource: TypeAlias = RowField | SolutionField | EntityCallback | SolutionCallback
65
63
  ListFeasibilityFieldSource: TypeAlias = RowField | SolutionField
66
64
 
67
65
 
@@ -74,14 +72,10 @@ class CapacityRouteFeasibility:
74
72
 
75
73
  def __post_init__(self) -> None:
76
74
  if self.capacity is None or self.demand is None:
77
- raise TypeError(
78
- "CapacityRouteFeasibility requires capacity and demand sources"
79
- )
75
+ raise TypeError("CapacityRouteFeasibility requires capacity and demand sources")
80
76
 
81
77
 
82
- ListFeasibilitySource: TypeAlias = (
83
- EntityCallback | SolutionCallback | CapacityRouteFeasibility
84
- )
78
+ ListFeasibilitySource: TypeAlias = EntityCallback | SolutionCallback | CapacityRouteFeasibility
85
79
 
86
80
 
87
81
  @dataclass(frozen=True)
@@ -94,9 +88,7 @@ class ListRouteHooks:
94
88
 
95
89
  def __post_init__(self) -> None:
96
90
  if self.depot is None or self.distance is None or self.feasible is None:
97
- raise TypeError(
98
- "ListRouteHooks requires depot, distance, and feasible sources"
99
- )
91
+ raise TypeError("ListRouteHooks requires depot, distance, and feasible sources")
100
92
 
101
93
 
102
94
  @dataclass(frozen=True)
@@ -403,9 +395,7 @@ def _resolve_list_value_source(
403
395
  solution_arity,
404
396
  )
405
397
  return source
406
- raise TypeError(
407
- f"{name} must be RowField, SolutionField, EntityCallback, or SolutionCallback"
408
- )
398
+ raise TypeError(f"{name} must be RowField, SolutionField, EntityCallback, or SolutionCallback")
409
399
 
410
400
 
411
401
  def _resolve_required_list_value_source(
@@ -455,9 +445,7 @@ def _resolve_list_feasibility_source(
455
445
  solution_arity,
456
446
  )
457
447
  return source
458
- raise TypeError(
459
- f"{name} must be EntityCallback, SolutionCallback, or CapacityRouteFeasibility"
460
- )
448
+ raise TypeError(f"{name} must be EntityCallback, SolutionCallback, or CapacityRouteFeasibility")
461
449
 
462
450
 
463
451
  def _resolve_list_feasibility_field_source(
@@ -501,10 +489,7 @@ def _callback_accepts_arity(callback: Callable[..., object], arity: int) -> bool
501
489
  required_positional_count += 1
502
490
  elif parameter.kind is Parameter.VAR_POSITIONAL:
503
491
  accepts_extra_positionals = True
504
- elif (
505
- parameter.kind is Parameter.KEYWORD_ONLY
506
- and parameter.default is Parameter.empty
507
- ):
492
+ elif parameter.kind is Parameter.KEYWORD_ONLY and parameter.default is Parameter.empty:
508
493
  return False
509
494
 
510
495
  if arity < required_positional_count:
@@ -89,17 +89,10 @@ class ScalarAssignmentGroup:
89
89
  ),
90
90
  ):
91
91
  if callback is not None and field is not None:
92
- raise TypeError(
93
- f"{callback_name} and {field_name} cannot both be configured"
94
- )
92
+ raise TypeError(f"{callback_name} and {field_name} cannot both be configured")
95
93
  if field is not None and (not isinstance(field, str) or not field):
96
- raise TypeError(
97
- f"{field_name} must be a non-empty string when provided"
98
- )
99
- if (
100
- self.assignment_rule is not None
101
- and self.same_value_conflict_field is not None
102
- ):
94
+ raise TypeError(f"{field_name} must be a non-empty string when provided")
95
+ if self.assignment_rule is not None and self.same_value_conflict_field is not None:
103
96
  raise TypeError(
104
97
  "assignment_rule and same_value_conflict_field cannot both be configured"
105
98
  )
@@ -107,9 +100,7 @@ class ScalarAssignmentGroup:
107
100
  not isinstance(self.same_value_conflict_field, str)
108
101
  or not self.same_value_conflict_field
109
102
  ):
110
- raise TypeError(
111
- "same_value_conflict_field must be a non-empty string when provided"
112
- )
103
+ raise TypeError("same_value_conflict_field must be a non-empty string when provided")
113
104
 
114
105
  def to_native(self) -> dict[str, Any]:
115
106
  return {
@@ -90,9 +90,7 @@ def _schema_shape(value: object) -> tuple[SchemaShape, bool]:
90
90
 
91
91
  def shape(item: object, *, callback_state: bool = False) -> SchemaShape:
92
92
  nonlocal cacheable, next_marker
93
- recursive = isinstance(
94
- item, (dict, list, tuple, FunctionType, MethodType, partial)
95
- )
93
+ recursive = isinstance(item, (dict, list, tuple, FunctionType, MethodType, partial))
96
94
  item_id = id(item)
97
95
  if recursive:
98
96
  if item_id in active:
@@ -147,8 +145,7 @@ def _schema_shape(value: object) -> tuple[SchemaShape, bool]:
147
145
  for cell in (item.__closure__ or ())
148
146
  )
149
147
  defaults = tuple(
150
- shape(default, callback_state=True)
151
- for default in (item.__defaults__ or ())
148
+ shape(default, callback_state=True) for default in (item.__defaults__ or ())
152
149
  )
153
150
  keyword_defaults = tuple(
154
151
  (name, shape(default, callback_state=True))
@@ -241,7 +238,9 @@ def _scalar_groups(callbacks: object) -> list[dict[str, object]]:
241
238
  callback = cast(Callable[..., object], item)
242
239
  metadata = getattr(callback, "__solverforge_scalar_group__", None)
243
240
  if not isinstance(metadata, dict):
244
- msg = f"{callback!r} is not marked with @scalar_group and is not a ScalarAssignmentGroup"
241
+ msg = (
242
+ f"{callback!r} is not marked with @scalar_group and is not a ScalarAssignmentGroup"
243
+ )
245
244
  raise ModelValidationError(msg)
246
245
  name = metadata.get("name")
247
246
  if not isinstance(name, str) or not name:
@@ -262,9 +261,7 @@ def _conflict_repairs(callbacks: object) -> list[dict[str, object]]:
262
261
  if not isinstance(metadata, dict):
263
262
  msg = f"{callback!r} is not marked with @conflict_repair"
264
263
  raise ModelValidationError(msg)
265
- constraint_names = list(
266
- cast(Iterable[object], metadata.get("constraints") or [])
267
- )
264
+ constraint_names = list(cast(Iterable[object], metadata.get("constraints") or []))
268
265
  if not constraint_names or not all(
269
266
  isinstance(name, str) and name for name in constraint_names
270
267
  ):
@@ -9,9 +9,7 @@ from .model import _compiled_schema_for_solution
9
9
 
10
10
  class Solver:
11
11
  @staticmethod
12
- def solve(
13
- solution: object, config: SolverConfig | dict[str, Any] | None = None
14
- ) -> object:
12
+ def solve(solution: object, config: SolverConfig | dict[str, Any] | None = None) -> object:
15
13
  schema = _compiled_schema_for_solution(solution)
16
14
  return _native.solve(solution, schema, _resolve_config(config))
17
15
 
@@ -14,4 +14,3 @@ class PlanningEntityProtocol(Protocol):
14
14
 
15
15
  class PlanningSolutionProtocol(Protocol):
16
16
  score: object | None
17
-
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes