jmfts-client 0.2.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.
- jmfts_client-0.2.0/LICENSE +21 -0
- jmfts_client-0.2.0/PKG-INFO +81 -0
- jmfts_client-0.2.0/README.md +54 -0
- jmfts_client-0.2.0/jmfts_client/__init__.py +91 -0
- jmfts_client-0.2.0/jmfts_client/_verbs.py +1979 -0
- jmfts_client-0.2.0/jmfts_client/contracts/__init__.py +272 -0
- jmfts_client-0.2.0/jmfts_client/contracts/access.py +114 -0
- jmfts_client-0.2.0/jmfts_client/contracts/attempt.py +158 -0
- jmfts_client-0.2.0/jmfts_client/contracts/conversation.py +88 -0
- jmfts_client-0.2.0/jmfts_client/contracts/document.py +463 -0
- jmfts_client-0.2.0/jmfts_client/contracts/explain.py +269 -0
- jmfts_client-0.2.0/jmfts_client/contracts/graph.py +206 -0
- jmfts_client-0.2.0/jmfts_client/contracts/index.py +64 -0
- jmfts_client-0.2.0/jmfts_client/contracts/ingest.py +85 -0
- jmfts_client-0.2.0/jmfts_client/contracts/runner.py +241 -0
- jmfts_client-0.2.0/jmfts_client/contracts/search.py +152 -0
- jmfts_client-0.2.0/jmfts_client/contracts/search_context.py +66 -0
- jmfts_client-0.2.0/jmfts_client/contracts/template.py +104 -0
- jmfts_client-0.2.0/jmfts_client/contracts/triple.py +124 -0
- jmfts_client-0.2.0/jmfts_client/contracts/upload.py +124 -0
- jmfts_client-0.2.0/jmfts_client/contracts/usetype_presentation.py +66 -0
- jmfts_client-0.2.0/jmfts_client/contracts/view.py +96 -0
- jmfts_client-0.2.0/jmfts_client/errors.py +87 -0
- jmfts_client-0.2.0/jmfts_client/remote.py +42 -0
- jmfts_client-0.2.0/jmfts_client/transport.py +166 -0
- jmfts_client-0.2.0/jmfts_client.egg-info/PKG-INFO +81 -0
- jmfts_client-0.2.0/jmfts_client.egg-info/SOURCES.txt +30 -0
- jmfts_client-0.2.0/jmfts_client.egg-info/dependency_links.txt +1 -0
- jmfts_client-0.2.0/jmfts_client.egg-info/requires.txt +5 -0
- jmfts_client-0.2.0/jmfts_client.egg-info/top_level.txt +1 -0
- jmfts_client-0.2.0/pyproject.toml +94 -0
- jmfts_client-0.2.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fight Fire with Fire Robotics, LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jmfts-client
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Wire contracts and a generated HTTP client for a JMFTS retrieval appliance
|
|
5
|
+
Author: Fight Fire with Fire Robotics, LLC
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/jmccardle/jmfts
|
|
8
|
+
Project-URL: Repository, https://github.com/jmccardle/jmfts
|
|
9
|
+
Keywords: jmfts,retrieval,client,api
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: httpx>=0.26.0
|
|
23
|
+
Requires-Dist: pydantic>=2.5.0
|
|
24
|
+
Provides-Extra: vectors
|
|
25
|
+
Requires-Dist: numpy>=1.26.0; extra == "vectors"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# jmfts-client
|
|
29
|
+
|
|
30
|
+
The thin half of [JMFTS](https://github.com/jmccardle/jmfts): the wire contracts, and an
|
|
31
|
+
HTTP client generated from them.
|
|
32
|
+
|
|
33
|
+
Install this to **call** a JMFTS appliance. Install `jmfts` to **run** one.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install jmfts-client
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Two dependencies — `httpx` and `pydantic`. A consumer that only makes requests does not
|
|
40
|
+
install sqlalchemy, psycopg2, pgvector, transformers or pymupdf to do it.
|
|
41
|
+
|
|
42
|
+
## Use
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from jmfts_client import RemoteJmftsClient
|
|
46
|
+
from jmfts_client.contracts import DocumentCreate, HybridSearchRequest
|
|
47
|
+
|
|
48
|
+
with RemoteJmftsClient("http://localhost:8100", token="...") as jmfts:
|
|
49
|
+
doc = jmfts.create_document(DocumentCreate(title="Ada", content="Ada Lovelace"))
|
|
50
|
+
hits = jmfts.hybrid_search(HybridSearchRequest(query="Ada", limit=10))
|
|
51
|
+
for hit in hits.results:
|
|
52
|
+
print(hit.document_id, hit.score)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Errors arrive as exceptions carrying the status: `JmftsNotFound`, `JmftsBadRequest`,
|
|
56
|
+
`JmftsConflict`, `JmftsUnprocessable`, `JmftsServerError`. All descend from `JmftsError`.
|
|
57
|
+
A request that never reached the server raises `JmftsTransportError`.
|
|
58
|
+
|
|
59
|
+
## Where the methods come from
|
|
60
|
+
|
|
61
|
+
Nobody writes them. A JMFTS service method marked `@expose` becomes a REST route, an entry
|
|
62
|
+
in the OpenAPI document, a method on the in-process `LocalJmftsClient`, and a method here —
|
|
63
|
+
four views of one definition.
|
|
64
|
+
|
|
65
|
+
`jmfts_client/_verbs.py` is generated from the route table the server actually builds, not
|
|
66
|
+
from the decorator's declaration, because the decorator does not record which parameters
|
|
67
|
+
are body, query or path — FastAPI's inference decides that, and reading the built routes is
|
|
68
|
+
what keeps this client and the server agreeing about the wire. Regeneration lives in the
|
|
69
|
+
`jmfts` repository (`python -m scripts.generate_client`), and a test there fails if this
|
|
70
|
+
file falls behind the surface.
|
|
71
|
+
|
|
72
|
+
## What this client does not offer
|
|
73
|
+
|
|
74
|
+
`LocalJmftsClient.unit_of_work()` groups several writes into one transaction. There is no
|
|
75
|
+
equivalent here, deliberately. Each HTTP request is its own transaction on the server, so a
|
|
76
|
+
`unit_of_work` over HTTP could group the calls but could not roll back the earlier ones when
|
|
77
|
+
a later one failed. If you need that guarantee, run in-process with `jmfts` installed.
|
|
78
|
+
|
|
79
|
+
## Licence
|
|
80
|
+
|
|
81
|
+
MIT. Copyright (c) 2026 Fight Fire with Fire Robotics, LLC.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# jmfts-client
|
|
2
|
+
|
|
3
|
+
The thin half of [JMFTS](https://github.com/jmccardle/jmfts): the wire contracts, and an
|
|
4
|
+
HTTP client generated from them.
|
|
5
|
+
|
|
6
|
+
Install this to **call** a JMFTS appliance. Install `jmfts` to **run** one.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install jmfts-client
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Two dependencies — `httpx` and `pydantic`. A consumer that only makes requests does not
|
|
13
|
+
install sqlalchemy, psycopg2, pgvector, transformers or pymupdf to do it.
|
|
14
|
+
|
|
15
|
+
## Use
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from jmfts_client import RemoteJmftsClient
|
|
19
|
+
from jmfts_client.contracts import DocumentCreate, HybridSearchRequest
|
|
20
|
+
|
|
21
|
+
with RemoteJmftsClient("http://localhost:8100", token="...") as jmfts:
|
|
22
|
+
doc = jmfts.create_document(DocumentCreate(title="Ada", content="Ada Lovelace"))
|
|
23
|
+
hits = jmfts.hybrid_search(HybridSearchRequest(query="Ada", limit=10))
|
|
24
|
+
for hit in hits.results:
|
|
25
|
+
print(hit.document_id, hit.score)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Errors arrive as exceptions carrying the status: `JmftsNotFound`, `JmftsBadRequest`,
|
|
29
|
+
`JmftsConflict`, `JmftsUnprocessable`, `JmftsServerError`. All descend from `JmftsError`.
|
|
30
|
+
A request that never reached the server raises `JmftsTransportError`.
|
|
31
|
+
|
|
32
|
+
## Where the methods come from
|
|
33
|
+
|
|
34
|
+
Nobody writes them. A JMFTS service method marked `@expose` becomes a REST route, an entry
|
|
35
|
+
in the OpenAPI document, a method on the in-process `LocalJmftsClient`, and a method here —
|
|
36
|
+
four views of one definition.
|
|
37
|
+
|
|
38
|
+
`jmfts_client/_verbs.py` is generated from the route table the server actually builds, not
|
|
39
|
+
from the decorator's declaration, because the decorator does not record which parameters
|
|
40
|
+
are body, query or path — FastAPI's inference decides that, and reading the built routes is
|
|
41
|
+
what keeps this client and the server agreeing about the wire. Regeneration lives in the
|
|
42
|
+
`jmfts` repository (`python -m scripts.generate_client`), and a test there fails if this
|
|
43
|
+
file falls behind the surface.
|
|
44
|
+
|
|
45
|
+
## What this client does not offer
|
|
46
|
+
|
|
47
|
+
`LocalJmftsClient.unit_of_work()` groups several writes into one transaction. There is no
|
|
48
|
+
equivalent here, deliberately. Each HTTP request is its own transaction on the server, so a
|
|
49
|
+
`unit_of_work` over HTTP could group the calls but could not roll back the earlier ones when
|
|
50
|
+
a later one failed. If you need that guarantee, run in-process with `jmfts` installed.
|
|
51
|
+
|
|
52
|
+
## Licence
|
|
53
|
+
|
|
54
|
+
MIT. Copyright (c) 2026 Fight Fire with Fire Robotics, LLC.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""JMFTS client — the wire contracts, and an HTTP client generated from them.
|
|
2
|
+
|
|
3
|
+
This distribution is the thin half of JMFTS. It carries ``httpx`` and ``pydantic`` and
|
|
4
|
+
nothing else, so a consumer that only needs to CALL an appliance does not install
|
|
5
|
+
sqlalchemy, pgvector, transformers or pymupdf to do it.
|
|
6
|
+
|
|
7
|
+
The server distribution (``jmfts``) depends on this one rather than the other way round.
|
|
8
|
+
That direction looks inverted and is deliberate: ``jmfts_client.contracts`` is the single
|
|
9
|
+
definition of every request and response shape, and both the in-process
|
|
10
|
+
``LocalJmftsClient`` and this ``RemoteJmftsClient`` must validate against the SAME classes.
|
|
11
|
+
Shipping a copy to each side would make them equal by value and unequal by ``isinstance``
|
|
12
|
+
— a bug that appears only when one process holds both, which is exactly what
|
|
13
|
+
``JMFTS_RUNNER_URL`` (one appliance embedding against another over HTTP) does.
|
|
14
|
+
|
|
15
|
+
Imports here are LAZY, through :pep:`562`. The server imports ``jmfts_client.contracts.*``
|
|
16
|
+
in dozens of modules, and an eager ``__init__`` would make every one of those build the
|
|
17
|
+
94-method verb table and import ``httpx`` on the way to one Pydantic class. Laziness also
|
|
18
|
+
keeps the import graph shallow enough that a contract can never close a cycle back through
|
|
19
|
+
the client.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from typing import TYPE_CHECKING
|
|
23
|
+
|
|
24
|
+
__version__ = "0.2.0"
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING: # import-time cost avoided at runtime, type checkers still see the names
|
|
27
|
+
from jmfts_client.errors import (
|
|
28
|
+
JmftsBadRequest,
|
|
29
|
+
JmftsConflict,
|
|
30
|
+
JmftsError,
|
|
31
|
+
JmftsForbidden,
|
|
32
|
+
JmftsNotFound,
|
|
33
|
+
JmftsResponseError,
|
|
34
|
+
JmftsServerError,
|
|
35
|
+
JmftsTransportError,
|
|
36
|
+
JmftsUnauthorized,
|
|
37
|
+
JmftsUnprocessable,
|
|
38
|
+
)
|
|
39
|
+
from jmfts_client.remote import RemoteJmftsClient
|
|
40
|
+
from jmfts_client.transport import DEFAULT_BASE_URL, DEFAULT_TIMEOUT
|
|
41
|
+
|
|
42
|
+
#: Public name -> the module that defines it.
|
|
43
|
+
_LAZY: dict[str, str] = {
|
|
44
|
+
"DEFAULT_BASE_URL": "jmfts_client.transport",
|
|
45
|
+
"DEFAULT_TIMEOUT": "jmfts_client.transport",
|
|
46
|
+
"JmftsBadRequest": "jmfts_client.errors",
|
|
47
|
+
"JmftsConflict": "jmfts_client.errors",
|
|
48
|
+
"JmftsError": "jmfts_client.errors",
|
|
49
|
+
"JmftsForbidden": "jmfts_client.errors",
|
|
50
|
+
"JmftsNotFound": "jmfts_client.errors",
|
|
51
|
+
"JmftsResponseError": "jmfts_client.errors",
|
|
52
|
+
"JmftsServerError": "jmfts_client.errors",
|
|
53
|
+
"JmftsTransportError": "jmfts_client.errors",
|
|
54
|
+
"JmftsUnauthorized": "jmfts_client.errors",
|
|
55
|
+
"JmftsUnprocessable": "jmfts_client.errors",
|
|
56
|
+
"RemoteJmftsClient": "jmfts_client.remote",
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def __getattr__(name: str):
|
|
61
|
+
"""Resolve a public name on first use. Anything unlisted is a real AttributeError."""
|
|
62
|
+
module_name = _LAZY.get(name)
|
|
63
|
+
if module_name is None:
|
|
64
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
65
|
+
import importlib
|
|
66
|
+
|
|
67
|
+
value = getattr(importlib.import_module(module_name), name)
|
|
68
|
+
globals()[name] = value # resolved once; later lookups skip this hook
|
|
69
|
+
return value
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def __dir__() -> list[str]:
|
|
73
|
+
return sorted(__all__)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
__all__ = [
|
|
77
|
+
"DEFAULT_BASE_URL",
|
|
78
|
+
"DEFAULT_TIMEOUT",
|
|
79
|
+
"JmftsBadRequest",
|
|
80
|
+
"JmftsConflict",
|
|
81
|
+
"JmftsError",
|
|
82
|
+
"JmftsForbidden",
|
|
83
|
+
"JmftsNotFound",
|
|
84
|
+
"JmftsResponseError",
|
|
85
|
+
"JmftsServerError",
|
|
86
|
+
"JmftsTransportError",
|
|
87
|
+
"JmftsUnauthorized",
|
|
88
|
+
"JmftsUnprocessable",
|
|
89
|
+
"RemoteJmftsClient",
|
|
90
|
+
"__version__",
|
|
91
|
+
]
|