Skip to content

Commit d2d4e22

Browse files
authored
Merge branch 'main' into fix/fetch-schema-validate-before-write
2 parents a409dac + 23a1db1 commit d2d4e22

97 files changed

Lines changed: 13361 additions & 1267 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.gitignore‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,8 @@ playground
6969
/osw_files/
7070
*/accounts.pwd.yaml
7171
/accounts.pwd.yaml
72-
.ign
73-
.claude
72+
73+
# Local folders
74+
.ign/
75+
.claude/
76+
graphify-out/

‎CHANGELOG.md‎

Lines changed: 340 additions & 0 deletions
Large diffs are not rendered by default.

‎CITATION.cff‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ message: "If you use this software, please cite it as below."
33
type: software
44
title: "OpenSemanticWorld Python Package (osw-python)"
55
# Managed by python-semantic-release (version_variables). Do not edit by hand.
6-
version: 2.3.1
6+
version: 2.6.0
77
abstract: "Python toolset for data processing, queries, wikicode generation and page manipulation within OpenSemanticLab: work with OpenSemanticLab instances as typed pydantic entities, query with semantic search, generate models from wiki schemas, and write changes back."
88
repository-code: "https://github.com/OpenSemanticLab/osw-python"
99
url: "https://opensemanticlab.github.io/osw-python/"

‎LICENSE.txt‎

Lines changed: 176 additions & 619 deletions
Large diffs are not rendered by default.

‎NOTICE‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
osw-python
2+
Copyright 2022-2026 Simon Stier and contributors

‎README.md‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pip install osw
2222
```
2323

2424
Optional extras (`osw[wikitext]`, `osw[DB]`, `osw[S3]`, `osw[dataimport]`,
25-
`osw[UI]`, `osw[all]`) are described in the
25+
`osw[UI]`, `osw[mcp]`, `osw[all]`) are described in the
2626
[Get Started guide](https://opensemanticlab.github.io/osw-python/get-started/).
2727

2828
## Quickstart
@@ -39,6 +39,26 @@ More runnable scripts live in [examples/](examples/), and the
3939
[Basics tutorial](docs/tutorials/basics.ipynb) walks through the
4040
OpenSemanticLab data model.
4141

42+
## Tools
43+
44+
Installing `osw` also installs an `osw` command line client, and the
45+
`osw[mcp]` extra adds an MCP server that exposes a live instance to agent
46+
clients such as Claude Code:
47+
48+
```bash
49+
osw search ask '[[Category:Item]]' --limit 5
50+
```
51+
52+
Commands, tools and their configuration are described in the
53+
[Tools guide](https://opensemanticlab.github.io/osw-python/tools/).
54+
55+
## Logging
56+
57+
osw reports what it is doing on the `osw` logger at INFO by default. Levels,
58+
the `OSW_LOG_LEVEL` environment variable and how to collect the records in your
59+
own logging setup are described in the
60+
[Get Started guide](https://opensemanticlab.github.io/osw-python/get-started/#logging).
61+
4262
## Contributing
4363

4464
Contributions are welcome, see [CONTRIBUTING.md](CONTRIBUTING.md).

‎docs/get-started.md‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
| `osw[S3]` | Interact with S3 stores per S3FileController |
3131
| `osw[dataimport]` | Additional tools to import data |
3232
| `osw[UI]` | To use a helper UI to work with entity slots |
33+
| `osw[mcp]` | [MCP server](tools/mcp.md) for agent clients |
3334
| `osw[all]` | All of the above |
3435

3536
Install multiple extras with `pip install osw[opt1,opt2]`.
@@ -64,6 +65,69 @@ Credentials are resolved from the environment variables `OSW_USERNAME` /
6465
credentials file, or via an interactive prompt - and are held in memory
6566
only, never written to disk; see [Authentication](api/auth.md).
6667

68+
## Logging
69+
70+
osw reports what it is doing through the standard `logging` module, on the
71+
`osw` logger, at INFO by default:
72+
73+
```python
74+
import osw
75+
76+
osw.set_log_level("WARNING") # see less
77+
osw.set_log_level("DEBUG") # see more
78+
osw.disable_logging() # detach the handler osw attached
79+
```
80+
81+
Problems that osw can work around are WARNING records on the same logger, not
82+
Python `warnings`. A truncated query result and a page that does not exist are
83+
examples. A `warnings` filter or the `-W` option therefore has no effect on
84+
them. A message that repeats is also written out every time, where the warnings
85+
machinery would have shown it once. Use `set_log_level` to control them.
86+
87+
The records go to `stderr`, which leaves `stdout` free for your program's own
88+
output. That matters for anything speaking a protocol over `stdout`, such as an
89+
MCP stdio server. Pass `osw.enable_logging(stream=...)` to send them elsewhere.
90+
91+
Set `OSW_LOG_LEVEL` to a level name, a level number, or `OFF` to choose the
92+
level before the package is imported. `OFF` silences osw everywhere, including
93+
in your own handlers.
94+
95+
### Collecting osw's records in your application
96+
97+
Configure logging the way you normally would and osw's records arrive there,
98+
once:
99+
100+
```python
101+
import logging
102+
import osw
103+
104+
logging.basicConfig(level=logging.INFO, filename="app.log")
105+
```
106+
107+
The `osw` logger propagates at all times, so the records reach your handlers
108+
whatever else happens. osw's own handler notices that something above it is
109+
listening, detaches itself so nothing is written twice, and gives back the
110+
level it had picked, so your level applies from then on. It makes no difference
111+
whether you configure logging before or after importing osw.
112+
113+
A level you asked for is kept across that hand-over, so `set_log_level("DEBUG")`
114+
or `OSW_LOG_LEVEL=DEBUG` is how you pull osw's debug records into an aggregated
115+
setup while the rest of your application stays quieter.
116+
117+
One case osw cannot detect is a handler added to the `osw` logger itself, since
118+
that is indistinguishable from one of its own. Call `disable_logging()` first if
119+
you do that.
120+
121+
### Output from parallel batches
122+
123+
Several osw calls process their input in parallel. What such a task prints, as
124+
opposed to logs, is collected while the batch runs and replayed afterwards, so
125+
that concurrent writing cannot garble the progress bar. That text arrives on the
126+
`osw.parallel.output` logger, one record per line, at INFO. Give that logger its
127+
own level or handler to keep the output of third-party code apart from osw's own
128+
records. Most osw calls replay it only when you pass `debug=True`; `copy_pages`
129+
always replays.
130+
67131
## Examples and tutorials
68132

69133
- Runnable scripts in

‎docs/tools/cli.md‎

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# CLI
2+
3+
## Quick start
4+
5+
Both adapters need an instance and credentials. The quickest start is a
6+
gitignored `.env` file in your project root:
7+
8+
```dotenv
9+
OSW_DOMAIN=wiki-dev.open-semantic-lab.org
10+
OSW_USERNAME=your-user
11+
OSW_PASSWORD=your-password
12+
```
13+
14+
The CLI searches upward from the working directory for it, so `osw status`
15+
now reports the instance, the username (whether it comes from `OSW_USERNAME`
16+
or a credential file), and connection state. The MCP server takes its
17+
settings from the `env` block of its registration instead, see
18+
[Registering a server](mcp.md#registering-a-server). Every variable is listed
19+
under [Configuration](configuration.md).
20+
21+
## Command line
22+
23+
```bash
24+
osw status
25+
osw search ask '[[Category:Item]]' --limit 5
26+
osw entity get 'Item:OSW1234...' --json | jq .
27+
osw file cat 'File:Example.csv' # inline text
28+
osw file download 'File:Example.csv' --target-dir ./tmp # to disk
29+
```
30+
31+
Commands are grouped by subject:
32+
33+
| Group | Commands |
34+
| --- | --- |
35+
| `entity` | `get`, `put`, `export`, `delete` |
36+
| `file` | `info`, `cat`, `write`, `download`, `upload` |
37+
| `search` | `ask`, `titles`, `content`, `entities`, `sparql` |
38+
| `slot` | `list`, `get`, `set` |
39+
| `schema` | `get` |
40+
| `instances` | `list`, `status` |
41+
| `ledger` | `path` |
42+
| top level | `status` |
43+
44+
`osw search entities` finds pages in the wiki that are instances of a
45+
category, while `osw instances` is about the OSL servers this process can
46+
connect to.
47+
48+
`osw instances list` lists the iris the process can connect to: the
49+
env-configured domain plus every entry of a configured credential file.
50+
51+
`osw instances status` reports the same instances in more detail. For each
52+
one it prints the iri, whether it is the active one, the username that would
53+
be used, and whether a connection succeeded. It never prints passwords. The
54+
instances are contacted one after another and a single attempt has no
55+
timeout, so an unreachable instance delays the command until its connection
56+
attempt gives up.
57+
58+
Global options apply to every command. They belong to the `osw` command
59+
itself, so they come before the subcommand, the same way `git` and `docker`
60+
options do: `osw --instance <iri> status`, not `osw status --instance <iri>`.
61+
Typing them after the subcommand now produces an error that names the correct
62+
form.
63+
64+
- `--instance IRI` picks the instance. Optional: it is only required when
65+
`OSW_DOMAIN` is not set and the configured credential file holds more than
66+
one iri.
67+
- `--json` / `-j` writes machine-readable JSON to stdout and keeps osw's own
68+
progress output on stderr, so it pipes cleanly into `jq`.
69+
- `--read-only` refuses write operations.
70+
- `--verbose` / `-v` shows full tracebacks instead of a one-line message, and
71+
adds the env-file line to the source report described under
72+
[Where settings come from](configuration.md#where-settings-come-from).
73+
74+
Failures exit non-zero with a short message on stderr and no traceback.

‎docs/tools/configuration.md‎

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Configuration
2+
3+
Both adapters share the settings below.
4+
5+
## Where settings come from
6+
7+
Settings are read from the process environment. A `.env` file fills that
8+
environment; a real environment variable wins over the same name in a file.
9+
10+
**Env file**
11+
12+
| `OSW_ENV_FILE` | CLI | MCP server |
13+
| --- | --- | --- |
14+
| set | loads that file, searches nowhere | loads that file, searches nowhere |
15+
| unset | searches upward from the working directory | searches nowhere |
16+
17+
**Credential file.** The first step that produces a file wins:
18+
19+
1. `OSW_CRED_FILEPATH` or an alias, set in the environment or the env file.
20+
The run fails if this file has no entry for `OSW_DOMAIN`. That check is
21+
skipped when `OSW_USERNAME` and `OSW_PASSWORD` are both set.
22+
2. CLI only: `accounts.pwd.yaml` in the working directory. Parent directories
23+
are not searched. This step is skipped when `OSW_USERNAME` or
24+
`OSW_PASSWORD` is set. If the file has no entry for `OSW_DOMAIN` it is
25+
ignored and the run continues.
26+
3. No credential file.
27+
28+
**Source report.** Both adapters write to stderr before connecting. The first
29+
line is labelled `credential file` when a file was found:
30+
31+
- `<path> (from the OSW_CRED_FILEPATH environment variable)`
32+
- `<path> (from OSW_CRED_FILEPATH in the env file)`
33+
- `<path> (accounts.pwd.yaml found in the working directory)`
34+
- `<path> (accounts.pwd.yaml found in the working directory, ignored: no entry for domain '<domain>')`
35+
36+
and `credentials` when none was:
37+
38+
- `OSW_USERNAME/OSW_PASSWORD (from the environment)`
39+
- `OSW_USERNAME/OSW_PASSWORD (from the env file)`
40+
- `not configured (set OSW_CRED_FILEPATH, or OSW_USERNAME/OSW_PASSWORD)`
41+
42+
The second line is labelled `env file`. Which lines appear depends on the
43+
adapter:
44+
45+
- **CLI**: the first line only. `--verbose`, or a command that fails, adds
46+
the second.
47+
- **MCP server**: neither, since its sources are fixed in the server entry.
48+
`OSW_VERBOSE=true` prints both, and a failed start prints both regardless.
49+
50+
A verbose run of the CLI prints:
51+
52+
```text
53+
[osw] credential file: /home/me/project/accounts.pwd.yaml (accounts.pwd.yaml found in the working directory)
54+
[osw] env file : /home/me/project/.env (found from the working directory upward)
55+
```
56+
57+
The prefix names the adapter that printed the line: `[osw]` for the CLI,
58+
`[osw-mcp]` for the MCP server. This holds for every message the two share,
59+
not only these two lines.
60+
61+
## Where messages go
62+
63+
The source report above is printed directly, because the adapter's own verbose
64+
flag decides whether it appears, not the log level.
65+
66+
Every other message the adapters produce goes to the `osw` logger, together
67+
with the records of the library itself. A failed connection check and an
68+
unreadable provenance ledger are reported that way. `OSW_LOG_LEVEL` sets how
69+
much of it appears, and an application that configures logging itself takes the
70+
records over. See [Logging](../get-started.md#logging).
71+
72+
Both kinds of message are written to stderr, never to stdout. The MCP server
73+
speaks JSON-RPC over stdout, and the CLI writes its `--json` output there, so
74+
stdout has to stay free.
75+
76+
## Credentials
77+
78+
Keep credentials in a gitignored file. They are read once per process, into that
79+
process only, and never written back to disk. Set either `OSW_USERNAME` and
80+
`OSW_PASSWORD`, or `OSW_CRED_FILEPATH`.
81+
82+
A credential file uses the YAML format osw's `CredentialManager` reads, keyed
83+
by iri (default file name: `accounts.pwd.yaml`):
84+
85+
```yaml
86+
wiki-dev.open-semantic-lab.org:
87+
username: your-user
88+
password: your-password
89+
```
90+
91+
A credential file may hold several iris. The CLI selects one automatically if it
92+
is the only one, and otherwise requires `osw --instance <iri>`. The MCP server
93+
never selects one, see [One server per instance](mcp.md).
94+
95+
## Variable reference
96+
97+
The canonical variable names are `OSW_*`. Older `OSW_MCP_*` and `OSL_*` names
98+
stay accepted so existing deployments keep working, and the first name that is
99+
set wins:
100+
101+
| Canonical | Also accepted | Meaning |
102+
| --- | --- | --- |
103+
| `OSW_DOMAIN` | `OSL_DOMAIN` | Instance to connect to. A bare host (`wiki.example.org`) or a full URL (`https://wiki.example.org/w/`); the host is taken from either, and a value no host can be read from (`https://`, `/w/index.php`) is rejected at startup |
104+
| `OSW_USERNAME` | `OSL_USERNAME` | Login user |
105+
| `OSW_PASSWORD` | `OSL_PASSWORD` | Login password |
106+
| `OSW_CRED_FILEPATH` | `OSW_MCP_CRED_FILEPATH`, `OSL_CRED_FILEPATH` | YAML credential file, keyed by iri (falls back to `accounts.pwd.yaml` in the working directory, CLI only) |
107+
| `OSW_ENV_FILE` | `OSW_MCP_ENV_FILE` | `.env` file to load |
108+
| `OSW_READ_ONLY` | `OSW_MCP_READ_ONLY` | `true` refuses every write |
109+
| `OSW_SPARQL_ENDPOINT` | | Endpoint for `sparql` queries |
110+
| `OSW_STATE_DIR` | `OSW_MCP_STATE_DIR` | Where the provenance ledger is kept. Must be an absolute path; a leading `~` is expanded |
111+
| `OSW_MAX_RESULTS` | `OSW_MCP_MAX_RESULTS` | Default result cap (100) |
112+
| `OSW_MAX_CHARS` | `OSW_MCP_MAX_CHARS` | Result size cap in characters (100000) |
113+
| `OSW_VERBOSE` | `OSW_MCP_VERBOSE` | `true` prints the configuration source report |
114+
115+
## Windows paths in a `.env` file
116+
117+
Quote them with single quotes, or leave them unquoted. A double-quoted value is
118+
escape-decoded, so `\a` in a path silently becomes a BEL byte that renders as
119+
nothing:
120+
121+
```dotenv
122+
OSW_CRED_FILEPATH='C:\Users\me\accounts.pwd.yaml' # ok
123+
OSW_CRED_FILEPATH=C:\Users\me\accounts.pwd.yaml # ok
124+
OSW_CRED_FILEPATH="C:\Users\me\accounts.pwd.yaml" # broken: \a is eaten
125+
```

‎docs/tools/index.md‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Tools
2+
3+
Besides the Python API, osw ships two adapters that talk to a live instance:
4+
the `osw` command line client, and an MCP server for agent clients such as
5+
Claude Code. Both run the same operations from one shared, SDK-free core
6+
(`osw.service`), so a command and its matching tool behave identically. They
7+
differ in exactly one way: only the CLI accepts filesystem paths.
8+
9+
## Setup
10+
11+
Install one of the two; the second includes the first:
12+
13+
```bash
14+
uv tool install osw # the `osw` command
15+
uv tool install "osw[mcp]" # the same, plus the `osw-mcp` server
16+
```
17+
18+
<details markdown="1">
19+
<summary>Other ways to install</summary>
20+
21+
```bash
22+
pip install "osw[mcp]" # into the active environment
23+
uv add "osw[mcp]" # as a dependency of the current uv project
24+
uvx --from "osw[mcp]" osw-mcp # run the server without installing it
25+
```
26+
27+
`uvx` is what the registration examples further down use, so the server needs
28+
no install of its own.
29+
30+
</details>
31+
32+
`osw[mcp]` is also part of `osw[all]`. The other extras are listed in the
33+
[Get Started guide](../get-started.md#optional-extras).
34+
35+
## In this section
36+
37+
| Page | Contents |
38+
| ---- | -------- |
39+
| [CLI](cli.md) | The `.env` quick start, the full command reference, and the global flags |
40+
| [MCP server](mcp.md) | The tool surface, the no-filesystem-access and one-server-per-instance rules, and how to register the server with a client such as Claude Code |
41+
| [Configuration](configuration.md) | What both adapters share: where credentials and settings come from, and the full environment-variable reference |

0 commit comments

Comments
 (0)