fitsolver-client 0.1.0__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.
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.4
2
+ Name: fitsolver-client
3
+ Version: 0.1.0
4
+ Summary: Minimal ctypes client for the FitSolver C ABI (fitsolver_lib)
5
+ Author: Fantastic Division
6
+ Project-URL: Homepage, https://github.com/FantasticDivision/Solver
7
+ Project-URL: Source, https://github.com/FantasticDivision/Solver
8
+ Keywords: bin-packing,packing,fitsolver,logistics
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3 :: Only
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Topic :: Scientific/Engineering
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+
16
+ # fitsolver-client
17
+
18
+ A minimal, dependency-free `ctypes` client for the FitSolver C ABI
19
+ (`fitsolver_lib`). JSON in, JSON out -- the same contract as
20
+ `fitsolver.solve()`, but without needing a compiled CPython extension.
21
+
22
+ ## Install
23
+
24
+ ### As a normal dependency (portal team, deployments)
25
+
26
+ Released wheels **vendor the native `fitsolver_lib`** inside the package,
27
+ so there is nothing else to build or configure:
28
+
29
+ ```bash
30
+ pip install fitsolver-client
31
+ ```
32
+
33
+ ```
34
+ # requirements.txt / pyproject.toml
35
+ fitsolver-client==0.1.0
36
+ ```
37
+
38
+ One wheel is published per OS (Linux `manylinux_2_28`, macOS x86_64 +
39
+ arm64, Windows x64); each is valid for every Python >= 3.9. See
40
+ `.github/workflows/wheels.yml` for how they are built and published.
41
+
42
+ ### From a local checkout (multi-repo development)
43
+
44
+ ```bash
45
+ pip install -e path/to/FantasticSolver/python/fitsolver_client \
46
+ --config-settings editable_mode=compat
47
+ ```
48
+
49
+ `editable_mode=compat` writes a plain-path `.pth` instead of an import
50
+ hook, so editors / Pylance resolve `import fitsolver_client` statically.
51
+ An editable install has no vendored library; it uses your local `build/`
52
+ (see below), so a `cmake --build` is picked up with no reinstall.
53
+
54
+ ## The shared library
55
+
56
+ Resolution order at the first `solve()` call:
57
+
58
+ 1. `FITSOLVER_LIB_PATH` -- exact path to the `.dll` / `.so` / `.dylib`.
59
+ 2. `FITSOLVER_REPO_ROOT` -- looked up under `<root>/build/`.
60
+ 3. A `build/` directory found by walking up from this package's location
61
+ (covers `pip install -e` from inside a FitSolver checkout).
62
+ 4. `fitsolver_client/_libs/` -- the copy vendored into a release wheel.
63
+
64
+ Importing the package never fails on its own; only `solve()` does, with
65
+ `SolverUnavailableError`, if none of the above resolve.
66
+
67
+ To build the library for a local checkout:
68
+
69
+ ```bash
70
+ cmake -B build
71
+ cmake --build build --config Release --target fitsolver_lib
72
+ ```
73
+
74
+ ## Building a wheel by hand
75
+
76
+ ```bash
77
+ cmake --build build --config Release --target fitsolver_lib
78
+ python python/fitsolver_client/tools/vendor_lib.py --build-dir build
79
+ python -m build --wheel python/fitsolver_client
80
+ # optional: normalise the tag to py3-none-<platform>
81
+ python -m wheel tags --python-tag py3 --abi-tag none \
82
+ --platform-tag win_amd64 --remove python/fitsolver_client/dist/*.whl
83
+ ```
84
+
85
+ ## Usage
86
+
87
+ ```python
88
+ import fitsolver_client
89
+
90
+ response = fitsolver_client.solve({
91
+ "items": [
92
+ {"ItemCode": "ITM-001", "ItemReference": "Widget A",
93
+ "Width": 100, "Length": 200, "Depth": 50, "Weight": 1.0},
94
+ ],
95
+ "boxes": [
96
+ {"Reference": "SML", "Width": 150, "Length": 150, "Depth": 150,
97
+ "MaxWeight": 8.5},
98
+ ],
99
+ })
100
+ ```
101
+
102
+ See the FitSolver README's "Request / response JSON schema" section for the
103
+ exact shape of the request and response dicts.
@@ -0,0 +1,88 @@
1
+ # fitsolver-client
2
+
3
+ A minimal, dependency-free `ctypes` client for the FitSolver C ABI
4
+ (`fitsolver_lib`). JSON in, JSON out -- the same contract as
5
+ `fitsolver.solve()`, but without needing a compiled CPython extension.
6
+
7
+ ## Install
8
+
9
+ ### As a normal dependency (portal team, deployments)
10
+
11
+ Released wheels **vendor the native `fitsolver_lib`** inside the package,
12
+ so there is nothing else to build or configure:
13
+
14
+ ```bash
15
+ pip install fitsolver-client
16
+ ```
17
+
18
+ ```
19
+ # requirements.txt / pyproject.toml
20
+ fitsolver-client==0.1.0
21
+ ```
22
+
23
+ One wheel is published per OS (Linux `manylinux_2_28`, macOS x86_64 +
24
+ arm64, Windows x64); each is valid for every Python >= 3.9. See
25
+ `.github/workflows/wheels.yml` for how they are built and published.
26
+
27
+ ### From a local checkout (multi-repo development)
28
+
29
+ ```bash
30
+ pip install -e path/to/FantasticSolver/python/fitsolver_client \
31
+ --config-settings editable_mode=compat
32
+ ```
33
+
34
+ `editable_mode=compat` writes a plain-path `.pth` instead of an import
35
+ hook, so editors / Pylance resolve `import fitsolver_client` statically.
36
+ An editable install has no vendored library; it uses your local `build/`
37
+ (see below), so a `cmake --build` is picked up with no reinstall.
38
+
39
+ ## The shared library
40
+
41
+ Resolution order at the first `solve()` call:
42
+
43
+ 1. `FITSOLVER_LIB_PATH` -- exact path to the `.dll` / `.so` / `.dylib`.
44
+ 2. `FITSOLVER_REPO_ROOT` -- looked up under `<root>/build/`.
45
+ 3. A `build/` directory found by walking up from this package's location
46
+ (covers `pip install -e` from inside a FitSolver checkout).
47
+ 4. `fitsolver_client/_libs/` -- the copy vendored into a release wheel.
48
+
49
+ Importing the package never fails on its own; only `solve()` does, with
50
+ `SolverUnavailableError`, if none of the above resolve.
51
+
52
+ To build the library for a local checkout:
53
+
54
+ ```bash
55
+ cmake -B build
56
+ cmake --build build --config Release --target fitsolver_lib
57
+ ```
58
+
59
+ ## Building a wheel by hand
60
+
61
+ ```bash
62
+ cmake --build build --config Release --target fitsolver_lib
63
+ python python/fitsolver_client/tools/vendor_lib.py --build-dir build
64
+ python -m build --wheel python/fitsolver_client
65
+ # optional: normalise the tag to py3-none-<platform>
66
+ python -m wheel tags --python-tag py3 --abi-tag none \
67
+ --platform-tag win_amd64 --remove python/fitsolver_client/dist/*.whl
68
+ ```
69
+
70
+ ## Usage
71
+
72
+ ```python
73
+ import fitsolver_client
74
+
75
+ response = fitsolver_client.solve({
76
+ "items": [
77
+ {"ItemCode": "ITM-001", "ItemReference": "Widget A",
78
+ "Width": 100, "Length": 200, "Depth": 50, "Weight": 1.0},
79
+ ],
80
+ "boxes": [
81
+ {"Reference": "SML", "Width": 150, "Length": 150, "Depth": 150,
82
+ "MaxWeight": 8.5},
83
+ ],
84
+ })
85
+ ```
86
+
87
+ See the FitSolver README's "Request / response JSON schema" section for the
88
+ exact shape of the request and response dicts.
@@ -0,0 +1,128 @@
1
+ """Minimal ctypes client for the FitSolver C ABI (fitsolver_lib).
2
+
3
+ Deliberately thin: pure Python, no build step for the caller. A wheel
4
+ built for distribution vendors the native fitsolver_lib shared library
5
+ inside the package (fitsolver_client/_libs/), so `pip install
6
+ fitsolver-client` is self-contained -- no CMake, no env vars.
7
+
8
+ Library location is resolved in this order:
9
+ 1. FITSOLVER_LIB_PATH env var, if set -- the exact file to load.
10
+ 2. FITSOLVER_REPO_ROOT env var, if set -- look under <root>/build/.
11
+ 3. A build/ directory found by walking up from this file's own
12
+ location -- covers `pip install -e` from inside a FitSolver
13
+ checkout (multi-repo dev: the live build/ wins over any vendored
14
+ copy so a rebuild is picked up immediately).
15
+ 4. The _libs/ directory vendored into this package by the wheel build
16
+ -- the normal path for an installed (non-editable) release.
17
+
18
+ See example/python_example.py in the FitSolver repo for the original,
19
+ single-file version of this same pattern.
20
+ """
21
+
22
+ import ctypes
23
+ import json
24
+ import os
25
+ import platform
26
+ from pathlib import Path
27
+
28
+ __all__ = ["solve", "SolverUnavailableError"]
29
+
30
+
31
+ class SolverUnavailableError(RuntimeError):
32
+ """Raised when the fitsolver_lib shared library can't be found or loaded."""
33
+
34
+
35
+ def _library_filename() -> str:
36
+ return {
37
+ "Windows": "fitsolver_lib.dll",
38
+ "Darwin": "fitsolver_lib.dylib",
39
+ }.get(platform.system(), "fitsolver_lib.so")
40
+
41
+
42
+ def _candidate_build_dirs() -> list[Path]:
43
+ candidates = []
44
+
45
+ repo_root = os.environ.get("FITSOLVER_REPO_ROOT")
46
+ if repo_root:
47
+ candidates.append(Path(repo_root) / "build")
48
+
49
+ # Walk up from this file looking for a sibling build/ directory.
50
+ here = Path(__file__).resolve()
51
+ candidates.extend(parent / "build" for parent in here.parents)
52
+
53
+ return candidates
54
+
55
+
56
+ def _bundled_library(filename: str) -> Path | None:
57
+ """The copy a distribution wheel vendors next to this module. Absent
58
+ in an editable/source checkout -- populated only by the wheel build
59
+ (see tools/vendor_lib.py + pyproject.toml package-data)."""
60
+ candidate = Path(__file__).resolve().parent / "_libs" / filename
61
+ return candidate if candidate.is_file() else None
62
+
63
+
64
+ def _find_library() -> Path:
65
+ explicit = os.environ.get("FITSOLVER_LIB_PATH")
66
+ if explicit:
67
+ path = Path(explicit)
68
+ if path.is_file():
69
+ return path
70
+ raise SolverUnavailableError(f"FITSOLVER_LIB_PATH is set but not a file: {explicit}")
71
+
72
+ filename = _library_filename()
73
+
74
+ # A live build/ from a dev checkout wins over the vendored copy, so a
75
+ # local `cmake --build` is picked up without reinstalling.
76
+ for build_dir in _candidate_build_dirs():
77
+ for sub in ("Release", "Debug", "."):
78
+ candidate = build_dir / sub / filename
79
+ if candidate.is_file():
80
+ return candidate
81
+
82
+ bundled = _bundled_library(filename)
83
+ if bundled is not None:
84
+ return bundled
85
+
86
+ raise SolverUnavailableError(
87
+ f"Could not find {filename}. Install a release wheel of "
88
+ "fitsolver-client (which vendors it), or build it in the FitSolver "
89
+ "repo (cmake -B build && cmake --build build --config Release "
90
+ "--target fitsolver_lib), or set FITSOLVER_LIB_PATH to its exact "
91
+ "location."
92
+ )
93
+
94
+
95
+ _lib: ctypes.CDLL | None = None
96
+
97
+
98
+ def _get_lib() -> ctypes.CDLL:
99
+ # Loaded lazily (on first solve() call, not at import time) so
100
+ # importing this package never fails just because the DLL hasn't
101
+ # been built yet -- only actually calling solve() does.
102
+ global _lib
103
+ if _lib is None:
104
+ lib = ctypes.CDLL(str(_find_library()))
105
+ lib.fitsolver_solve.argtypes = [ctypes.c_char_p]
106
+ lib.fitsolver_solve.restype = ctypes.c_void_p
107
+ lib.fitsolver_free.argtypes = [ctypes.c_void_p]
108
+ _lib = lib
109
+ return _lib
110
+
111
+
112
+ def solve(request: dict) -> dict:
113
+ """Sends a {"items": [...], "boxes": [...]} request through the
114
+ fitsolver_lib C ABI and returns the parsed JSON response as a dict.
115
+
116
+ See the FitSolver README's "Request / response JSON schema" section
117
+ for the exact shape of `request` and the returned dict.
118
+ """
119
+ lib = _get_lib()
120
+ request_bytes = json.dumps(request).encode("utf-8")
121
+ response_ptr = lib.fitsolver_solve(request_bytes)
122
+ try:
123
+ # c_void_p (not c_char_p) on the ctypes signature so we keep the
124
+ # real pointer and can correctly free it afterwards.
125
+ response_bytes = ctypes.cast(response_ptr, ctypes.c_char_p).value
126
+ return json.loads(response_bytes.decode("utf-8"))
127
+ finally:
128
+ lib.fitsolver_free(response_ptr)
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.4
2
+ Name: fitsolver-client
3
+ Version: 0.1.0
4
+ Summary: Minimal ctypes client for the FitSolver C ABI (fitsolver_lib)
5
+ Author: Fantastic Division
6
+ Project-URL: Homepage, https://github.com/FantasticDivision/Solver
7
+ Project-URL: Source, https://github.com/FantasticDivision/Solver
8
+ Keywords: bin-packing,packing,fitsolver,logistics
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3 :: Only
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Topic :: Scientific/Engineering
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+
16
+ # fitsolver-client
17
+
18
+ A minimal, dependency-free `ctypes` client for the FitSolver C ABI
19
+ (`fitsolver_lib`). JSON in, JSON out -- the same contract as
20
+ `fitsolver.solve()`, but without needing a compiled CPython extension.
21
+
22
+ ## Install
23
+
24
+ ### As a normal dependency (portal team, deployments)
25
+
26
+ Released wheels **vendor the native `fitsolver_lib`** inside the package,
27
+ so there is nothing else to build or configure:
28
+
29
+ ```bash
30
+ pip install fitsolver-client
31
+ ```
32
+
33
+ ```
34
+ # requirements.txt / pyproject.toml
35
+ fitsolver-client==0.1.0
36
+ ```
37
+
38
+ One wheel is published per OS (Linux `manylinux_2_28`, macOS x86_64 +
39
+ arm64, Windows x64); each is valid for every Python >= 3.9. See
40
+ `.github/workflows/wheels.yml` for how they are built and published.
41
+
42
+ ### From a local checkout (multi-repo development)
43
+
44
+ ```bash
45
+ pip install -e path/to/FantasticSolver/python/fitsolver_client \
46
+ --config-settings editable_mode=compat
47
+ ```
48
+
49
+ `editable_mode=compat` writes a plain-path `.pth` instead of an import
50
+ hook, so editors / Pylance resolve `import fitsolver_client` statically.
51
+ An editable install has no vendored library; it uses your local `build/`
52
+ (see below), so a `cmake --build` is picked up with no reinstall.
53
+
54
+ ## The shared library
55
+
56
+ Resolution order at the first `solve()` call:
57
+
58
+ 1. `FITSOLVER_LIB_PATH` -- exact path to the `.dll` / `.so` / `.dylib`.
59
+ 2. `FITSOLVER_REPO_ROOT` -- looked up under `<root>/build/`.
60
+ 3. A `build/` directory found by walking up from this package's location
61
+ (covers `pip install -e` from inside a FitSolver checkout).
62
+ 4. `fitsolver_client/_libs/` -- the copy vendored into a release wheel.
63
+
64
+ Importing the package never fails on its own; only `solve()` does, with
65
+ `SolverUnavailableError`, if none of the above resolve.
66
+
67
+ To build the library for a local checkout:
68
+
69
+ ```bash
70
+ cmake -B build
71
+ cmake --build build --config Release --target fitsolver_lib
72
+ ```
73
+
74
+ ## Building a wheel by hand
75
+
76
+ ```bash
77
+ cmake --build build --config Release --target fitsolver_lib
78
+ python python/fitsolver_client/tools/vendor_lib.py --build-dir build
79
+ python -m build --wheel python/fitsolver_client
80
+ # optional: normalise the tag to py3-none-<platform>
81
+ python -m wheel tags --python-tag py3 --abi-tag none \
82
+ --platform-tag win_amd64 --remove python/fitsolver_client/dist/*.whl
83
+ ```
84
+
85
+ ## Usage
86
+
87
+ ```python
88
+ import fitsolver_client
89
+
90
+ response = fitsolver_client.solve({
91
+ "items": [
92
+ {"ItemCode": "ITM-001", "ItemReference": "Widget A",
93
+ "Width": 100, "Length": 200, "Depth": 50, "Weight": 1.0},
94
+ ],
95
+ "boxes": [
96
+ {"Reference": "SML", "Width": 150, "Length": 150, "Depth": 150,
97
+ "MaxWeight": 8.5},
98
+ ],
99
+ })
100
+ ```
101
+
102
+ See the FitSolver README's "Request / response JSON schema" section for the
103
+ exact shape of the request and response dicts.
@@ -0,0 +1,8 @@
1
+ README.md
2
+ pyproject.toml
3
+ setup.py
4
+ fitsolver_client/__init__.py
5
+ fitsolver_client.egg-info/PKG-INFO
6
+ fitsolver_client.egg-info/SOURCES.txt
7
+ fitsolver_client.egg-info/dependency_links.txt
8
+ fitsolver_client.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ fitsolver_client
@@ -0,0 +1,32 @@
1
+ [project]
2
+ name = "fitsolver-client"
3
+ version = "0.1.0"
4
+ description = "Minimal ctypes client for the FitSolver C ABI (fitsolver_lib)"
5
+ readme = "README.md"
6
+ requires-python = ">=3.9"
7
+ dependencies = []
8
+ authors = [{ name = "Fantastic Division" }]
9
+ keywords = ["bin-packing", "packing", "fitsolver", "logistics"]
10
+ classifiers = [
11
+ "Programming Language :: Python :: 3",
12
+ "Programming Language :: Python :: 3 :: Only",
13
+ "Intended Audience :: Developers",
14
+ "Topic :: Scientific/Engineering",
15
+ ]
16
+
17
+ [project.urls]
18
+ Homepage = "https://github.com/FantasticDivision/Solver"
19
+ Source = "https://github.com/FantasticDivision/Solver"
20
+
21
+ [tool.setuptools]
22
+ packages = ["fitsolver_client"]
23
+
24
+ [tool.setuptools.package-data]
25
+ # The native library is vendored here by the wheel build (tools/vendor_lib.py).
26
+ # A source tree / editable install has no _libs/ dir and falls back to a
27
+ # local build/ or FITSOLVER_LIB_PATH -- see fitsolver_client/__init__.py.
28
+ fitsolver_client = ["_libs/*.dll", "_libs/*.so", "_libs/*.so.*", "_libs/*.dylib"]
29
+
30
+ [build-system]
31
+ requires = ["setuptools>=61", "wheel"]
32
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,23 @@
1
+ """Metadata lives in pyproject.toml; this shim exists only to force a
2
+ platform-specific wheel tag.
3
+
4
+ fitsolver-client is pure Python, but a built wheel vendors a native
5
+ .dll/.so/.dylib under fitsolver_client/_libs/. Without this, setuptools
6
+ would tag the wheel `py3-none-any` and pip on Linux would happily install
7
+ the Windows binary. Declaring the distribution non-pure makes bdist_wheel
8
+ emit `py3-none-<platform>` instead -- one wheel per OS, still valid for
9
+ every Python 3.x (ctypes has no ABI dependency).
10
+
11
+ An editable / sdist install builds no _libs/ dir and this is harmless.
12
+ """
13
+
14
+ from setuptools import setup
15
+ from setuptools.dist import Distribution
16
+
17
+
18
+ class _BinaryDistribution(Distribution):
19
+ def has_ext_modules(self) -> bool: # noqa: D102
20
+ return True
21
+
22
+
23
+ setup(distclass=_BinaryDistribution)