Skip to content

Rewrite the fast run mode with scoped SPARQL queries - #333

Merged
LeMyst merged 6 commits into
masterfrom
new-fastrun
Jul 28, 2026
Merged

Rewrite the fast run mode with scoped SPARQL queries#333
LeMyst merged 6 commits into
masterfrom
new-fastrun

Conversation

@LeMyst

@LeMyst LeMyst commented May 12, 2022

Copy link
Copy Markdown
Owner

Rewrite of the fast run mode: the mechanism that lets a synchronisation bot skip the entities that are already up to date, without loading each of them through the MediaWiki API.

This PR replaces the previous implementation rather than patching it. #1001 was the alternative proposal, patching the existing architecture in place; it has been closed in favour of this one, and the one thing it still had over this branch (datatype coverage) has been ported here.

Why a rewrite

The previous implementation loaded the whole data corpus with a single paginated SPARQL query per property, pulling statements, qualifiers, references and ranks in one go. That query is the scaling wall: on a benchmark of only 95 entities it repeatedly hit consecutive WDQS 504 timeouts, and at the scale of a large corpus it simply cannot run. Once it succeeds the comparison is very fast, but a cold load that cannot complete is not a usable trade-off.

This implementation scopes its queries instead: the statements of a property are loaded for the corpus, and the qualifiers, references and ranks are loaded lazily, per statement, only when a deep comparison is actually needed. Each query stays small enough to complete.

What it does

entity.write_required(base_filter=...) returns False only when at least one entity already holds every compared claim with the same value, and the same qualifiers, references and rank depending on the options. When the entity ID is known (the entity was loaded with wbi.item.get()), the comparison is automatically restricted to that entity, so a bot editing a known entity no longer needs a unique identifying value in its data.

The base filter defines the corpus and accepts three forms: a property with a value, a property with any value, and a property path given as a list of two datatypes.

The README section Examples (in "fast run" mode) documents the whole API, with a full bot example, the options, the language-data checking and the measured performance figures.

Breaking changes

The fastrun API is not backwards compatible. Anything using wbi_fastrun directly needs updating:

  • FastRunContainer.get_items() becomes get_entities().
  • FastRunContainer.__init__() now takes base_filter as its first, mandatory argument; base_data_type becomes optional. use_refs becomes use_references, joined by use_qualifiers (default True), use_rank and cache.
  • FastRunContainer.write_required() gains entity_filter and property_filter and drops cqid.
  • BaseEntity.write_required() now requires base_filter instead of defaulting to None.
  • Data types gained a PTYPE class attribute (their URI in the Wikibase ontology) and a from_sparql_value() method taking the SPARQL binding dict. It replaces parse_sparql_value(), which fastrun no longer calls.

This needs a changelog entry and a version note.

Datatype coverage

BaseDataType.from_sparql_value() was a stub returning None, so any datatype not implementing it was skipped with a warning and its statements were always reported as requiring a write. The last commit gives it a working generic implementation for the data types whose RDF representation is the literal value itself, plus URI-specific implementations that extract the value a local claim actually holds:

  • Property, Lexeme, Form, Sense: the entity ID
  • GeoShape, TabularData: the Commons page title, percent-decoded
  • CommonsMedia: the file name, percent-decoded

CommonsMedia was the worst case, and a silent one: it inherited the URL implementation, stored the whole Commons URL and could therefore never match a local claim, without even emitting the warning the other data types produce.

Every reachable datatype is now compared. Only EntitySchema is left out, since it has no PTYPE and is never resolved from the SPARQL property type.

Known limitations

Documented in the README:

  • The SPARQL endpoint lags behind the live data, so a write that just happened may not be visible yet and could be reported as required again.
  • Attributes missing from the SPARQL simple values are rebuilt with their default value (the precision of a time value is inferred from the timestamp, quantity bounds are not compared). Statements using non-default attributes are always reported as requiring a write. In particular, a timestamp whose precision cannot be inferred is skipped with a warning.
  • Qualifiers, references and ranks cost one SPARQL query per statement to check. This is fast when most entities are up to date, but slows down when many entities share the same values.

Tests

253 tests pass, all offline: the 48 fastrun tests run against the MockWikibase emulation with a per-query-type SPARQL dispatcher, so no network access is involved. mypy, isort, codespell and flynt are clean.

Rebased on master.

@LeMyst
LeMyst force-pushed the new-fastrun branch 2 times, most recently from f385ea5 to 2d58257 Compare May 13, 2022 17:49
@LeMyst
LeMyst force-pushed the new-fastrun branch 6 times, most recently from 572355a to e4f3711 Compare May 28, 2022 13:10
@LeMyst LeMyst linked an issue May 28, 2022 that may be closed by this pull request
4 tasks
@LeMyst
LeMyst force-pushed the new-fastrun branch 3 times, most recently from d9e6e67 to aeca219 Compare May 30, 2022 20:57
@LeMyst LeMyst added this to the v0.12 milestone Jul 2, 2022
@LeMyst LeMyst modified the milestones: v0.12, v0.13 Jul 3, 2022
@LeMyst
LeMyst changed the base branch from rewrite-wbi to master July 14, 2022 17:43
@LeMyst
LeMyst force-pushed the new-fastrun branch 6 times, most recently from 2f17582 to 63349a6 Compare May 8, 2023 10:39
@LeMyst
LeMyst force-pushed the new-fastrun branch 3 times, most recently from fa07a05 to d91a485 Compare January 2, 2024 21:46
@LeMyst
LeMyst force-pushed the new-fastrun branch 2 times, most recently from 3ae0a5a to 7c3671b Compare January 8, 2024 11:45
@LeMyst
LeMyst force-pushed the new-fastrun branch 2 times, most recently from cf48aba to 3b00b3b Compare December 19, 2024 11:03
@LeMyst
LeMyst force-pushed the new-fastrun branch 2 times, most recently from 5816349 to 041153e Compare December 26, 2024 20:59
@LeMyst
LeMyst marked this pull request as ready for review July 8, 2026 19:55
LeMyst and others added 6 commits July 28, 2026 19:55
Features:
- Restrict write_required() to the entity being edited: baseentity now
  passes entity_filter=self.id, so data existing on another entity no
  longer inhibits a required write
- Support action_if_exists in write_required(); FORCE_APPEND always
  reports a write as required
- Port the language data checking (labels, descriptions, aliases) from
  the old implementation: init_language_data(), get_language_data() and
  check_language_data(), backed by the shared base filter
- Implement the case insensitive mode: string values are compared
  casefolded, the SPARQL data is keyed casefolded at load time
- Add a clear() method and a module docstring

Fixes:
- write_required() crashed with an IndexError when no claim matched the
  property filter; it now reports a write as required
- The deep comparison returned early on the first mismatching statement:
  an entity holding duplicate statements (one matching, one not) was
  wrongly reported as requiring a write. The comparison now looks for
  at least one entity holding a matching statement for every claim
- A load restricted to a value or to qualifiers (cache disabled) was
  reused as a complete cache, poisoning later comparisons; partial
  loads are no longer marked as complete
- _load_qualifiers()/_load_references() crashed when building datatypes:
  the full property URI was passed as prop_nr; it is now reduced to the
  bare property ID
- _load_references() duplicated references spanning two result pages
- A base filter mixing a valueless property and a property path crashed
  in the base filter string generation
- Datatypes without from_sparql_value() support and unparseable values
  no longer crash the load; they are skipped with a warning and the
  statements are reported as requiring a write
- Lazily loaded qualifiers, references and ranks are memoized per
  statement, honoring the cache flag

Tests: rebuild test/test_wbi_fastrun.py on the offline MockWikibase
infrastructure with a per-query-type SPARQL dispatcher (45 tests).

Docs: rewrite the fast run README section around the actual API (base
filter forms, write_required() options, language data checking,
limitations) and remove the outdated WikidataIntegrator-era example.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The simple value of a quantity statement does not carry the unit: two
amounts only differing by their unit were considered equal and a
required write could be skipped.

load_statements() now loads the unit from the value node (OPTIONAL, so
only quantities bind it) and the normalized unit becomes part of the
comparison key. The unit is normalized to the format of the JSON
representation: the RDF export of any Wikibase instance represents a
unitless quantity with the Wikidata Q199 entity, which maps back to
'1', and the other units are reduced to their bare entity ID.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Guard the re.search() results before reading the match groups
- Import ItemEntity instead of a string annotation

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Port the idea behind the generic parse_sparql_value() of the old fastrun
implementation (PR #1001) to the from_sparql_value() mechanism: give
BaseDataType a working generic implementation for the data types whose
RDF representation is the literal value itself, instead of a stub
returning None.

A generic implementation is not enough for the data types backed by a
URI: storing the raw URI would never match the value held by a local
claim. Implement from_sparql_value() for each of them, extracting the
value the local claim holds:

- Property, Lexeme, Form and Sense: the entity ID
- GeoShape and TabularData: the Commons page title, percent-decoded
- CommonsMedia: the file name, percent-decoded

Before this change, Property, Lexeme, Form, Sense, GeoShape and
TabularData were skipped with a warning and their statements were always
reported as requiring a write, a limitation the README documented.
CommonsMedia was worse: it inherited the URL implementation, silently
stored the whole Commons URL, and could never match a local claim
without even a warning.

Every reachable datatype is now compared. Only EntitySchema is left out,
since it has no PTYPE mapping to the Wikibase ontology and is therefore
never resolved from the SPARQL property type.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@LeMyst LeMyst changed the title Implement new version of FastRun Rewrite the fast run mode with scoped SPARQL queries Jul 28, 2026
@LeMyst
LeMyst merged commit c9261b0 into master Jul 28, 2026
15 checks passed
@LeMyst
LeMyst deleted the new-fastrun branch July 28, 2026 18:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve fastrun query

1 participant