pyling-n3 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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
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,562 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyling-n3
3
+ Version: 0.1.0
4
+ Summary: Python implementation of the Eyeling Notation3 reasoner
5
+ Author: Eyereasoner contributors
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/eyereasoner/pyling
8
+ Project-URL: Repository, https://github.com/eyereasoner/pyling
9
+ Project-URL: Issues, https://github.com/eyereasoner/pyling/issues
10
+ Project-URL: Changelog, https://github.com/eyereasoner/pyling/blob/main/CHANGELOG.md
11
+ Keywords: Notation3,N3,reasoner,RDF,RDF 1.2,RDF Message,RDFLib
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Topic :: Text Processing :: Markup
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: rdflib>=7.0
27
+ Provides-Extra: test
28
+ Requires-Dist: pytest>=7; extra == "test"
29
+ Provides-Extra: conformance
30
+ Requires-Dist: pytest>=7; extra == "conformance"
31
+ Provides-Extra: docs
32
+ Requires-Dist: jupyter>=1; extra == "docs"
33
+ Requires-Dist: nbconvert>=7; extra == "docs"
34
+ Provides-Extra: build
35
+ Requires-Dist: build>=1; extra == "build"
36
+ Requires-Dist: twine>=5; extra == "build"
37
+ Dynamic: license-file
38
+
39
+ # pyling
40
+
41
+ A Python port of the Eyeling Notation3 reasoner.
42
+
43
+ RDF/Turtle/TriG compatibility through `rdflib`, RDF 1.2 surface-syntax checks, and RDF Message Log parsing/streaming.
44
+
45
+ The [Eyeling](https://github.com/eyereasoner/eyeling) repositories remains the main implementation.
46
+
47
+ ## Requirements
48
+
49
+ - Python 3.10 or newer
50
+ - `pip`
51
+ - `rdflib` is installed automatically from `pyproject.toml`
52
+ - `pytest` is needed only for the test suite
53
+
54
+ ## Install in your project
55
+
56
+ After pyling is published to PyPI as `pyling-n3`, install it with Python's
57
+ standard package installer:
58
+
59
+ ```bash
60
+ python -m pip install pyling-n3
61
+ ```
62
+
63
+ With project-oriented package managers:
64
+
65
+ ```bash
66
+ uv add pyling-n3
67
+ poetry add pyling-n3
68
+ pdm add pyling-n3
69
+ ```
70
+
71
+ The distribution package is named `pyling-n3`; the Python import package and
72
+ CLI command remain `pyling`.
73
+
74
+ Until the package is published on PyPI, install directly from GitHub:
75
+
76
+ ```bash
77
+ python -m pip install "pyling-n3 @ git+https://github.com/eyereasoner/pyling.git"
78
+ uv add "pyling-n3 @ git+https://github.com/eyereasoner/pyling.git"
79
+ ```
80
+
81
+ ## Install from a checkout
82
+
83
+ ```bash
84
+ cd pyling
85
+ python -m venv .venv
86
+ . .venv/bin/activate # Windows: .venv\Scripts\activate
87
+ python -m pip install --upgrade pip
88
+ python -m pip install -e ".[test]"
89
+ ```
90
+
91
+ Verify the install:
92
+
93
+ ```bash
94
+ pyling --help
95
+ python -m pytest -q
96
+ ```
97
+
98
+ ## Command-line usage
99
+
100
+ Run a simple N3 rule program from standard input:
101
+
102
+ ```bash
103
+ cat > example.n3 <<'EOF'
104
+ @prefix : <http://example.org/> .
105
+
106
+ :Socrates a :Man .
107
+ { ?x a :Man } => { ?x a :Mortal } .
108
+ EOF
109
+
110
+ pyling example.n3
111
+ ```
112
+
113
+ Expected derived output:
114
+
115
+ ```n3
116
+ @prefix : <http://example.org/> .
117
+
118
+ :Socrates a :Mortal .
119
+ ```
120
+
121
+ Include explicit input facts in the rendered closure:
122
+
123
+ ```bash
124
+ pyling --include-input-facts example.n3
125
+ ```
126
+
127
+ Read multiple sources, such as facts plus rules:
128
+
129
+ ```bash
130
+ pyling facts.n3 rules.n3
131
+ ```
132
+
133
+ Enable RDF/Turtle/TriG/RDF Message compatibility mode:
134
+
135
+ ```bash
136
+ pyling --rdf data.trig rules.n3
137
+ ```
138
+
139
+ Force a line format:
140
+
141
+ ```bash
142
+ pyling --rdf --input-format nt data.nt
143
+
144
+ pyling --rdf --input-format nquads data.nq
145
+ ```
146
+
147
+ ## Python API
148
+
149
+ ```python
150
+ from pyling import reason, reason_stream, run_async
151
+
152
+ program = """
153
+ @prefix : <http://example.org/> .
154
+ :a :p :b .
155
+ { ?s :p ?o } => { ?s :q ?o } .
156
+ """
157
+
158
+ print(reason(program))
159
+
160
+ result = reason_stream(program, include_input_facts_in_closure=True)
161
+ print(result.closure_n3)
162
+ print(result.derived)
163
+ ```
164
+
165
+ Multi-source input mirrors Eyeling’s source-list style:
166
+
167
+ ```python
168
+ from pyling import reason
169
+
170
+ out = reason({
171
+ "sources": [
172
+ "@prefix : <http://example.org/> .\n:Socrates a :Man .\n",
173
+ "@prefix : <http://example.org/> .\n{ ?x a :Man } => { ?x a :Mortal } .\n",
174
+ ]
175
+ })
176
+ ```
177
+
178
+ The main exported term classes are `Iri`, `Literal`, `Var`, `Blank`, `ListTerm`, `GraphTerm`, `Triple`, `Rule`, and `PrefixEnv`.
179
+
180
+ ## Notebook examples
181
+
182
+ Publishable notebooks live in `docs/notebooks/`:
183
+
184
+ - `01-rdflib-reasoning.ipynb` shows RDFLib graph input and RDFLib graph output.
185
+ - `02-owl-style-rules.ipynb` shows runtime-loaded OWL-style N3 rules.
186
+ - `03-neuro-symbolic-validation.ipynb` shows symbolic validation over facts
187
+ extracted by a neural model.
188
+
189
+ Run them locally with:
190
+
191
+ ```bash
192
+ python -m pip install -e ".[docs]"
193
+ jupyter lab docs/notebooks
194
+ ```
195
+
196
+ GitHub Actions executes the notebooks and converts them to HTML in the
197
+ `Notebook docs` workflow. Pull requests upload the generated `notebook-site`
198
+ artifact; pushes to `main` also publish the same `site/` output through GitHub
199
+ Pages when Pages is enabled for GitHub Actions.
200
+
201
+ ## RDF and RDF 1.2 compatibility
202
+
203
+ RDF mode is selected with `--rdf` on the CLI or `{"rdf": True}` in the API. It routes ordinary RDF syntax through `rdflib` instead of the N3 rule parser:
204
+
205
+ ```python
206
+ from pyling import reason
207
+
208
+ rdf = """
209
+ PREFIX : <http://example.org/>
210
+ :a :p :b .
211
+ """
212
+
213
+ print(reason(rdf, rdf=True, include_input_facts_in_closure=True))
214
+ ```
215
+
216
+ RDFLib graphs can also be passed directly:
217
+
218
+ ```python
219
+ from rdflib import Graph, Namespace
220
+ from pyling import reason_graph
221
+
222
+ ex = Namespace("http://example.org/")
223
+ g = Graph()
224
+ g.bind("", ex)
225
+ g.add((ex.a, ex.p, ex.b))
226
+
227
+ closure = reason_graph(g, include_input_facts_in_closure=True)
228
+ print(closure.serialize(format="turtle"))
229
+ ```
230
+
231
+ Supported compatibility inputs include:
232
+
233
+ - Turtle / `.ttl`
234
+ - TriG / `.trig`
235
+ - N-Triples / `.nt`
236
+ - N-Quads / `.nq`
237
+ - uppercase `PREFIX`, `BASE`, and RDF 1.2 `VERSION` surface forms
238
+ - simple RDF 1.2 annotation syntax, preserving the asserted triple
239
+ - RDF Message Logs using `VERSION "1.2-messages"` and `MESSAGE`
240
+
241
+ The RDF 1.2 layer includes strict surface checks for common negative conformance cases such as surrogate numeric escapes, invalid RDF 1.2 language tags in line syntaxes, relative IRIREFs in N-Triples/N-Quads, and annotation syntax in line syntaxes.
242
+
243
+ Run the bundled offline RDF 1.2 smoke suite:
244
+
245
+ ```bash
246
+ python tools/run_rdf12_w3c.py
247
+ ```
248
+
249
+ Run the W3C RDF 1.2 syntax compliance manifests (requires Node.js and network
250
+ access on the first run):
251
+
252
+ ```bash
253
+ npm ci
254
+ npm run spec
255
+ ```
256
+
257
+ The W3C runner caches downloaded manifests and fixtures in
258
+ `.rdf-test-suite-cache/`. All RDF 1.2 syntax cases in the configured N-Triples,
259
+ N-Quads, Turtle, and TriG manifests are enabled.
260
+
261
+ ## Performance comparisons
262
+
263
+ The comparison harness in `tools/compare_reasoners.py` benchmarks pyling and
264
+ optional FuXi installations over shared N3 fixtures. FuXi is not installed in
265
+ the main project environment. When the `fuxi` reasoner is selected for a
266
+ benchmark run, the harness lazily creates `.cache/fuxi-venv` with Python 3.13
267
+ and installs the `fuxi` package there. The default benchmark suite discovers
268
+ selected `.n3` files from this repo's `examples/` directory. `--list` never
269
+ installs dependencies.
270
+
271
+ ```bash
272
+ npm run perf -- --list
273
+ npm run perf -- --case=socrates --reasoner=pyling --iterations=5 --warmup=2
274
+ npm run perf -- --case=socrates --reasoner=pyling,fuxi --json
275
+ npm run perf -- --csv > perf-results.csv
276
+ ```
277
+
278
+ OWL 2 RL support can be benchmarked with the MobiBench OWL2RL archive. pyling
279
+ loads the RDFJS inference engine's OWL2RL N3 rules from
280
+ `https://raw.githubusercontent.com/pietercolpaert/rdfjs-inference-engine/refs/heads/main/rules/owl2rl/owl2rl-eyeling.n3`.
281
+ For the same MobiBench cases, FuXi uses its built-in OWL/DLP setup instead of
282
+ that Eyeling-specific N3 ruleset. This keeps each reasoner on its intended OWL
283
+ path: pyling exercises runtime-loaded N3 rules, while FuXi exercises the OWL
284
+ rule generation shipped with FuXi.
285
+
286
+ ```bash
287
+ npm run perf -- --suite=owl-mobibench --mobibench-limit=5 --reasoner=pyling
288
+ npm run perf:mobibench
289
+ ```
290
+
291
+ Latest local MobiBench OWL2RL run, measured with one iteration and no warmup on
292
+ 2026-07-22:
293
+
294
+ | Reasoner | Cases | Failures | Median ms/case | Cases deriving facts | Total derived facts |
295
+ |---|---:|---:|---:|---:|---:|
296
+ | pyling | 273 | 0 | 85.88 | 273 | 33,402 |
297
+ | FuXi | 273 | 0 | 95.93 | 43 | 221 |
298
+
299
+ Interpret these numbers as a profile of each configured reasoning path, not as
300
+ a strict semantic equivalence result. pyling materializes with the runtime-loaded
301
+ OWL2RL N3 rules, which include broad datatype and RDF-based entailment rules.
302
+ FuXi runs its built-in OWL/DLP translation, which is fast and useful for many
303
+ structural OWL cases but derives no facts for many datatype-heavy MobiBench
304
+ cases. For example, `rdfbased-sem-rdfs-subclass-trans` derives facts in both
305
+ engines, while the early `rdfbased-dat-*` cases mostly derive only in pyling.
306
+
307
+ The GitHub Actions performance workflow runs both `pyling` and `fuxi` for the
308
+ examples suite only. Full MobiBench is intentionally local-only through
309
+ `npm run perf:mobibench`, because it is larger and less suitable for normal pull
310
+ request feedback. Both paths write JSON, CSV, and Markdown reports to
311
+ `performance-reports`.
312
+
313
+ The slowest pyling cases in the latest completed full run were concentrated in
314
+ a small set of RDF-based OWL patterns:
315
+
316
+ | Case | pyling median ms | Derived facts |
317
+ |---|---:|---:|
318
+ | `mobibench-rdfbased-xtr-reflection-subclasses` | 133,292.61 | 290 |
319
+ | `mobibench-rdfbased-sem-bool-intersection-inst-comp` | 31,885.76 | 105 |
320
+ | `mobibench-rdfbased-sem-bool-intersection-inst-expr` | 31,301.35 | 106 |
321
+ | `mobibench-rdfbased-sem-bool-intersection-term` | 30,417.15 | 104 |
322
+ | `mobibench-rdfbased-sem-key-def` | 3,042.21 | 116 |
323
+
324
+ An interrupted full rerun stopped inside `pyling.engine.Engine.solve`, after
325
+ recursive goal solving had already entered a deep multi-premise OWL rule. That
326
+ matches the profile from the completed report: pyling is not uniformly slow,
327
+ but the current generic N3 fixpoint solver can spend disproportionate time on
328
+ rules with broad joins over RDF lists, subclass reflection, intersections, and
329
+ keys. The next performance work should focus on predicate indexing for rule
330
+ bodies, join ordering based on bound variables and candidate counts, and
331
+ deduplication of equivalent substitutions before recursive goal expansion.
332
+
333
+ The examples suite is useful for implementation regressions. After the
334
+ memoized Fibonacci fix, pyling completes `examples/fibonacci.n3` and derives
335
+ the expected query formula containing `F(0)`, `F(1)`, `F(10)`, `F(100)`,
336
+ `F(1000)`, and `F(10000)`. FuXi's generic N3 rule loader does not currently run
337
+ that file because it rejects the literal-subject rule shape used by the example.
338
+
339
+ Disable lazy installation, or supply a dedicated Python executable or checkout
340
+ path:
341
+
342
+ ```bash
343
+ npm run perf -- --reasoner=fuxi --no-install-fuxi
344
+ FUXI_PYTHON=/path/to/venv/bin/python npm run perf -- --reasoner=fuxi
345
+ FUXI_PYTHONPATH=/path/to/FuXi-reincarnate/lib npm run perf -- --reasoner=fuxi
346
+ ```
347
+
348
+ FuXi currently requires Python 3.13 or newer. If your system `python3` is older,
349
+ install a 3.13 interpreter and let the benchmark harness create
350
+ `.cache/fuxi-venv` from it. Two practical options:
351
+
352
+ ```bash
353
+ # uv-managed Python, no system Python upgrade needed
354
+ uv python install 3.13
355
+ FUXI_PYTHON="$(uv python find 3.13)" npm run perf -- --reasoner=fuxi --case=socrates
356
+
357
+ # pyenv-managed Python
358
+ pyenv install 3.13
359
+ FUXI_PYTHON="$(pyenv prefix 3.13)/bin/python" npm run perf -- --reasoner=fuxi --case=socrates
360
+ ```
361
+
362
+ After Python 3.13 is available, `npm run perf -- --reasoner=fuxi ...` installs
363
+ the `fuxi` package lazily into `.cache/fuxi-venv` and reuses that environment on
364
+ later benchmark runs.
365
+
366
+ Additional fixtures can be added without editing the script:
367
+
368
+ ```bash
369
+ npm run perf -- --fixture=my-case=../eyeling/examples/deep-taxonomy-100.n3
370
+ ```
371
+
372
+ ## RDF Message Logs
373
+
374
+ Whole-log replay:
375
+
376
+ ```bash
377
+ cat > messages.trig <<'EOF'
378
+ VERSION "1.2-messages"
379
+ PREFIX : <http://example.org/>
380
+
381
+ :a :value 21 .
382
+
383
+ MESSAGE
384
+
385
+ # Empty heartbeat message.
386
+
387
+ MESSAGE
388
+
389
+ :b :value 22 .
390
+ EOF
391
+
392
+ pyling --rdf --include-input-facts messages.trig
393
+ ```
394
+
395
+ Streaming replay, one message envelope at a time:
396
+
397
+ ```bash
398
+ pyling --rdf --stream-messages rules.n3 messages.trig
399
+ ```
400
+
401
+ Python streaming API:
402
+
403
+ ```python
404
+ from pyling import reason_message_stream
405
+
406
+ for result in reason_message_stream({"sources": [rules_n3, messages_trig]}, {"rdf": True}):
407
+ print(result.closure_n3)
408
+ ```
409
+
410
+ The replay vocabulary uses:
411
+
412
+ ```n3
413
+ @prefix eymsg: <https://eyereasoner.github.io/eyeling/vocab/message#> .
414
+ ```
415
+
416
+ It materializes stream/envelope metadata, `eymsg:orderedEnvelopes`, `eymsg:messageCount`, `eymsg:payloadKind`, and payload formula links through `log:nameOf`. Rules can inspect each payload graph with `log:includes`.
417
+
418
+ ## Built-ins
419
+
420
+ The package includes the built-in registry API:
421
+
422
+ ```python
423
+ from pyling import register_builtin, unregister_builtin, list_builtin_iris
424
+ ```
425
+
426
+ Built-in coverage includes common predicates in these namespaces:
427
+
428
+ - `math:` numeric comparison and arithmetic, plus common trig functions
429
+ - `string:` contains/matches/replace/format/length/comparison helpers
430
+ - `list:` first/rest/member/append/reverse/sort and related helpers
431
+ - `log:` includes/notIncludes/semantics/conjunction/skolem/uri and equality helpers
432
+ - `dt:` datatype inspection, validation, value comparison, canonicalization
433
+ - `crypto:` md5/sha/sha256/sha512
434
+ - `time:` year/month/day/hour/minute/second/timeZone/localTime
435
+
436
+ Registering a custom built-in:
437
+
438
+ ```python
439
+ from pyling import Literal, XSD_NS, register_builtin
440
+
441
+
442
+ def hello(ctx):
443
+ return [ctx.unify_term(ctx.goal.o, Literal("world", XSD_NS + "string"), ctx.subst)]
444
+
445
+ register_builtin("http://example.org/custom#hello", hello)
446
+ ```
447
+
448
+ ## Stores
449
+
450
+ The synchronous API uses in-memory reasoning. `run_async(..., {"store": ...})` can persist explicit and inferred triples through the included JSON-backed persistent store.
451
+
452
+ ```python
453
+ import asyncio
454
+ from pyling import run_async
455
+
456
+ async def main():
457
+ result = await run_async(program, {"store": {"name": "demo", "path": ".eyeling-store", "clear": True}})
458
+ await result.store.close()
459
+
460
+ asyncio.run(main())
461
+ ```
462
+
463
+ ## Tests
464
+
465
+ Run the package tests:
466
+
467
+ ```bash
468
+ python -m pytest -q
469
+ ```
470
+
471
+ Run the offline RDF 1.2 compatibility smoke suite:
472
+
473
+ ```bash
474
+ python tools/run_rdf12_w3c.py
475
+ ```
476
+
477
+ Run the W3C RDF 1.2 compliance tests:
478
+
479
+ ```bash
480
+ npm ci
481
+ npm run spec
482
+ ```
483
+
484
+ Run the external `phochste/notation3tests` suite when you have network access or an existing checkout:
485
+
486
+ ```bash
487
+ # Existing checkout
488
+ python tools/run_notation3tests.py /path/to/notation3tests
489
+
490
+ # Or let the runner clone and install the public suite at https://codeberg.org/phochste/notation3tests
491
+ python tools/run_notation3tests.py --clone
492
+ ```
493
+
494
+ The runner defaults to `NETWORKING=0`, because this native port does not
495
+ dereference Web resources. It removes stale `.out` files for the 19 skipped
496
+ network fixtures so the reported score covers only tests actually executed.
497
+
498
+ You can also have `pytest` run the external suite by setting:
499
+
500
+ ```bash
501
+ NOTATION3TESTS_DIR=/path/to/notation3tests python -m pytest -q
502
+ ```
503
+
504
+ ## Packaging and release
505
+
506
+ The package metadata lives in `pyproject.toml`; releases are tracked in
507
+ `CHANGELOG.md`. PyPI is the Python package index, `pip` is the default installer
508
+ users will normally run, and `build` plus `twine` are the local maintainer tools
509
+ used to create and upload distributions. The `Package build` GitHub Actions
510
+ workflow builds the wheel and sdist, runs `twine check`, and uploads the
511
+ generated distributions as an artifact.
512
+
513
+ Build and inspect a release locally:
514
+
515
+ ```bash
516
+ python -m pip install -e ".[build,test]"
517
+ python -m pytest -q
518
+ python -m build
519
+ twine check dist/pyling_n3-*
520
+ ```
521
+
522
+ Publish first to TestPyPI:
523
+
524
+ ```bash
525
+ twine upload --repository testpypi dist/pyling_n3-*
526
+ python -m pip install \
527
+ --index-url https://test.pypi.org/simple/ \
528
+ --extra-index-url https://pypi.org/simple/ \
529
+ pyling-n3
530
+ ```
531
+
532
+ If that install works in a fresh environment, publish the same checked
533
+ distributions to PyPI:
534
+
535
+ ```bash
536
+ twine upload dist/pyling_n3-*
537
+ python -m pip install pyling-n3
538
+ ```
539
+
540
+ For GitHub Actions releases, prefer PyPI Trusted Publishing over storing a
541
+ long-lived API token in repository secrets. Configure a PyPI trusted publisher
542
+ for the `eyereasoner/pyling` repository and the release workflow file, then use
543
+ `pypa/gh-action-pypi-publish` with `id-token: write` permissions.
544
+
545
+ Release checklist:
546
+
547
+ 1. Update `CHANGELOG.md` and move the relevant entries from `Unreleased` to the
548
+ new version.
549
+ 2. Update `version` in `pyproject.toml`.
550
+ 3. Run tests, conformance checks, notebook build, and package build locally or
551
+ in GitHub Actions.
552
+ 4. Confirm the published package name, ownership, license metadata, and long
553
+ README rendering on TestPyPI.
554
+ 5. Create a signed git tag, for example `v0.1.0`.
555
+ 6. Upload to TestPyPI and install from TestPyPI in a fresh environment.
556
+ 7. Upload the same checked distributions to PyPI.
557
+ 8. Create a GitHub release from the tag and include the changelog notes.
558
+
559
+ ## Development notes
560
+
561
+ - `rdflib` is used only for RDF/Turtle/TriG/N-Triples/N-Quads parsing. N3 rules and formulas are parsed by the local parser.
562
+ - RDF Message Logs are split and replayed before reasoning so message boundaries, empty heartbeat messages, and per-message blank-node scope are preserved.