Skip to content

Commit 8f7cc51

Browse files
committed
Merge branch 'stage' into feat/file-controllers-from-core
# Conflicts: # uv.lock
2 parents 01b6531 + fa07163 commit 8f7cc51

12 files changed

Lines changed: 2117 additions & 1921 deletions

File tree

‎.claude-plugin/plugin.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "osl-tasks",
33
"displayName": "OSL task management",
44
"description": "Task and project management for OpenSemanticLab, driven by the osw CLI and MCP server.",
5-
"version": "2.8.1",
5+
"version": "2.9.1",
66
"author": {
77
"name": "OpenSemanticLab"
88
},

‎.github/workflows/integration.yml‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@
22
# INTEGR_TEST_* secrets; where they are unavailable (e.g. forks), the
33
# credential fixtures make the tests skip instead of fail.
44
#
5-
# Runs on every pull request into main, so breaking changes surface before the
6-
# release job (gated separately by required review on the pypi environment)
7-
# ever starts. No push trigger: a push to main is already covered by the PR
8-
# that preceded it, so a second run would add nothing.
5+
# Runs on every pull request into main or stage, so breaking changes surface
6+
# before the release job (gated separately by required review on the pypi
7+
# environment) ever starts. stage collects reviewed pull requests before they
8+
# go to main together. No push trigger: a push to main is already covered by
9+
# the PR that preceded it, so a second run would add nothing.
910

1011
name: Integration
1112

1213
on:
1314
pull_request:
14-
branches: [main]
15+
branches: [main, stage]
1516
workflow_dispatch:
1617

1718
concurrency:

‎.github/workflows/main.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: Main
22

33
on:
44
push:
5-
branches: [main]
5+
branches: [main, stage]
66
pull_request:
77
types: [opened, synchronize, reopened, ready_for_review]
88
workflow_dispatch:

‎CHANGELOG.md‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,32 @@
1010
1111
<!-- version list -->
1212

13+
## v2.9.1 (2026-11-06)
14+
15+
### Bug Fixes
16+
17+
- **core**: Resolve bare $refs before dropping schema definitions
18+
([#164](https://github.com/OpenSemanticLab/osw-python/pull/164),
19+
[`d86ad31`](https://github.com/OpenSemanticLab/osw-python/commit/d86ad31b9531b027117707bfa159a36f8b5d0694))
20+
21+
22+
## v2.9.0 (2026-11-05)
23+
24+
### Features
25+
26+
- Make dask optional so osw imports without it
27+
([#219](https://github.com/OpenSemanticLab/osw-python/pull/219),
28+
[`897df83`](https://github.com/OpenSemanticLab/osw-python/commit/897df832a132c653886af8089a0285f41e951e09))
29+
30+
31+
## v2.8.2 (2026-10-10)
32+
33+
### Bug Fixes
34+
35+
- Detect CharacteristicType from the opensemantic packages too
36+
([`f195a97`](https://github.com/OpenSemanticLab/osw-python/commit/f195a973b141cf830c65656ab946ca454e7309aa))
37+
38+
1339
## v2.8.1 (2026-09-23)
1440

1541
### Bug Fixes

‎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.8.1
6+
version: 2.9.1
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/"

‎docs/dev.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ excluded by default:
6666

6767
Integration tests run against a live wiki and need credentials; tests
6868
whose credentials are missing skip instead of fail. CI runs them on every
69-
pull request into `main` (workflow `integration.yml`), so they gate the
70-
release rather than run after it.
69+
pull request into `main` or `stage` (workflow `integration.yml`), so they
70+
gate the release rather than run after it.
7171

7272
=== "make"
7373

‎pyproject.toml‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ license-files = ["LICENSE.txt"]
88
requires-python = ">=3.10,<3.14"
99
# Managed by python-semantic-release (see [tool.semantic_release]). Do not
1010
# edit by hand; it is bumped automatically on merge to main.
11-
version = "2.8.1"
11+
version = "2.9.1"
1212
classifiers = [
1313
"Development Status :: 3 - Alpha",
1414
"Intended Audience :: Developers",
@@ -49,7 +49,6 @@ dependencies = [
4949
# used in custom_types.py on py3.10; was missing before (latent
5050
# ImportError on 3.10), surfaced by ty/deptry during the migration
5151
'backports.strenum; python_version < "3.11"',
52-
"dask",
5352
"tqdm",
5453
"pybars3-wheel",
5554
# the osw CLI (src/osw/cli); a base dependency, not an extra, so `pip
@@ -73,6 +72,10 @@ Download = "https://pypi.org/project/osw/#files"
7372

7473
[project.optional-dependencies]
7574
wikitext = ["mwparserfromhell"]
75+
# Only osw.utils.util.async_parallelize needs it, and nothing in the package calls
76+
# that. Keeping it in the base dependencies made `import osw` impossible anywhere
77+
# dask is unavailable - notably Pyodide, which has no dask build.
78+
parallel = ["dask"]
7679
DB = [
7780
"psycopg2",
7881
"sqlalchemy",
@@ -108,7 +111,7 @@ workflow = [
108111
"anyio>=4.9,<4.14",
109112
]
110113
tutorial = ["osw[dataimport]"]
111-
all = ["osw[dataimport,DB,UI,S3,wikitext,mcp]"]
114+
all = ["osw[dataimport,DB,UI,S3,wikitext,mcp,parallel]"]
112115

113116
[project.scripts]
114117
# command-line access to a live OSL instance, built from the same

‎src/osw/core.py‎

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@
3939
from pyld import jsonld
4040

4141
import osw.model.entity as model
42+
43+
try:
44+
# Characteristics of the released opensemantic packages derive from this
45+
# class, not from the one in osw.model.entity.
46+
from opensemantic.core.v1 import (
47+
CharacteristicType as _OpensemanticCharacteristicType,
48+
)
49+
except ImportError: # pragma: no cover - optional dependency
50+
_OpensemanticCharacteristicType = None
4251
from osw.defaults import params as default_params
4352
from osw.utils.code_postprocessing import (
4453
remove_constraints_from_forward_refs,
@@ -476,7 +485,29 @@ def register_schema(self, schema_registration: SchemaRegistration):
476485
else:
477486
result_array.append(subschema)
478487
match.full_path.update_or_create(schema, result_array)
479-
if "definitions" in schema:
488+
489+
all_refs_resolved = True
490+
bare_ref_jsonpath_expr = parse("$..dollarref")
491+
# pydantic only wraps a $ref in allOf when the field carries extra
492+
# metadata (e.g. Field(description=...)). A plain nested-model
493+
# field produces a bare {"$ref": ...} that is not reachable via
494+
# the "$..allOf" jsonpath above, so replace those local
495+
# definitions (#/definitions/...) with embedded definitions too,
496+
# to prevent a dangling ref once "definitions" is removed below
497+
for match in bare_ref_jsonpath_expr.find(schema):
498+
value = match.value
499+
if value.startswith("#"):
500+
definition_jsonpath_expr = parse(
501+
value.replace("#", "$").replace("/", ".")
502+
)
503+
def_matches = definition_jsonpath_expr.find(schema)
504+
for def_match in def_matches:
505+
match.context.full_path.update_or_create(
506+
schema, def_match.value
507+
)
508+
if not def_matches:
509+
all_refs_resolved = False
510+
if "definitions" in schema and all_refs_resolved:
480511
del schema["definitions"]
481512

482513
if "allOf" not in schema:
@@ -1788,12 +1819,15 @@ def __init__(self, **data):
17881819
if self.change_id is None:
17891820
self.change_id = str(uuid4())
17901821
for entity in self.entities:
1791-
if getattr(entity, "meta", None) is None:
1792-
entity.meta = model.Meta()
1793-
if entity.meta.change_id is None:
1794-
entity.meta.change_id = []
1795-
if self.change_id not in entity.meta.change_id:
1796-
entity.meta.change_id.append(self.change_id)
1822+
try:
1823+
if getattr(entity, "meta", None) is None:
1824+
entity.meta = model.Meta()
1825+
if entity.meta.change_id is None:
1826+
entity.meta.change_id = []
1827+
if self.change_id not in entity.meta.change_id:
1828+
entity.meta.change_id.append(self.change_id)
1829+
except (ValueError, AttributeError):
1830+
pass # entity model doesn't have meta field
17971831
if len(self.entities) > 5 and self.parallel is None:
17981832
self.parallel = True
17991833
if self.parallel is None:
@@ -2058,10 +2092,18 @@ def store_entity_(
20582092

20592093
mode = AggregateGeneratedSchemasParamMode.ROOT_LEVEL
20602094
# Put generated schema in definitions section,
2061-
# currently only enabled for Characteristics
2062-
if hasattr(model, "CharacteristicType") and isinstance(
2063-
entity_, model.CharacteristicType
2064-
):
2095+
# currently only enabled for Characteristics. The class is
2096+
# either the locally generated one or the one shipped by the
2097+
# opensemantic packages, depending on where the model is from.
2098+
characteristic_types = tuple(
2099+
t
2100+
for t in (
2101+
getattr(model, "CharacteristicType", None),
2102+
_OpensemanticCharacteristicType,
2103+
)
2104+
if t is not None
2105+
)
2106+
if characteristic_types and isinstance(entity_, characteristic_types):
20652107
mode = AggregateGeneratedSchemasParamMode.DEFINITIONS_SECTION
20662108

20672109
new_schema = aggregate_generated_schemas(

‎src/osw/skills/osl-tasks/SKILL.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name: osl-tasks
33
description: Use when importing local todo or note files into an OpenSemanticLab (OSL) wiki as Task entities, or when listing, filtering and updating tasks that already live in OSL. Requires the `osw` CLI or the osw MCP server.
44
metadata:
5-
version: "2.8.1"
5+
version: "2.9.1"
66
---
77

88
# OSL task management

‎src/osw/utils/util.py‎

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
from pathlib import Path
1313
from typing import IO, Callable, Dict, Iterable, List, Optional, Union
1414

15-
import dask
16-
from dask.diagnostics import ProgressBar
1715
from tqdm.asyncio import tqdm
1816

1917
_logger = logging.getLogger(__name__)
@@ -585,7 +583,21 @@ def file_in_paths(
585583

586584

587585
def async_parallelize(func: Callable, iterable: Iterable, **kwargs):
588-
"""Work in progress"""
586+
"""Work in progress. Requires the optional ``parallel`` extra.
587+
588+
dask is imported here rather than at module scope because this is the only
589+
thing in osw that uses it. At module scope it made ``import osw`` fail
590+
outright wherever dask is unavailable - util.py is imported unconditionally
591+
by core, wtsite and wiki_tools - which rules out Pyodide, where there is no
592+
dask build at all.
593+
"""
594+
# ty cannot resolve these: dask is the `parallel` extra and is deliberately
595+
# absent from the dev environment, which is the condition this import exists to
596+
# tolerate. Adding the extra to the dev group would make it resolve - that is what
597+
# pyproject does for mcp - but it would install dask everywhere to type check the
598+
# one place that is allowed to run without it.
599+
import dask # ty: ignore[unresolved-import]
600+
from dask.diagnostics import ProgressBar # ty: ignore[unresolved-import]
589601

590602
async def inner(func_, iterable_, **kwargs_):
591603
async with AsyncMessageBuffer() as msg_buf:
@@ -646,6 +658,11 @@ def wrapped_function(*args, **kwargs):
646658
from datetime import datetime
647659
from time import sleep
648660

661+
# Only the demo helpers below use dask; see async_parallelize for why it is
662+
# not imported at module scope. Needs the optional `parallel` extra.
663+
import dask # ty: ignore[unresolved-import]
664+
from dask.diagnostics import ProgressBar # ty: ignore[unresolved-import]
665+
649666
print("Redirecting print messages to a buffer.")
650667
message_buffer = MessageBuffer()
651668
io_buffer = StringIO()

0 commit comments

Comments
 (0)