mtgwiki 1.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.
mtgwiki-1.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Martin Bräck
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.
mtgwiki-1.2.0/PKG-INFO ADDED
@@ -0,0 +1,427 @@
1
+ Metadata-Version: 2.4
2
+ Name: mtgwiki
3
+ Version: 1.2.0
4
+ Summary: A small, polite, data-oriented Python client for MediaWiki Action API sites
5
+ Author: Martin Bräck
6
+ License: MIT
7
+ Keywords: mediawiki,mtg,magic-the-gathering,api,wikitext
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: requests<3,>=2.31
15
+ Dynamic: license-file
16
+
17
+ # mtgwiki 1.2.0
18
+
19
+ `mtgwiki` is a small Python package for getting **useful, reusable data** out of MediaWiki Action API sites without teaching the package what the wiki's subject matter means. MTG Wiki is the default endpoint and the original use case, but the transport, traversal, parsing and snapshot layers are domain-neutral.
20
+
21
+ The package does **not** contain functions such as `get_plane()`, `get_character()`, `flora()` or `depicted_cards()`. Instead it exposes generic MediaWiki building blocks and a generic snapshot/structure layer that other projects can interpret however they want.
22
+
23
+ ## Design rule
24
+
25
+ > If a feature needs to know Magic semantics, it does not belong in `mtgwiki`.
26
+
27
+ The package has three jobs:
28
+
29
+ 1. **Acquire** data politely and efficiently from MediaWiki.
30
+ 2. **Structure** generic wiki syntax such as templates, parameters, sections, lists, tables and links.
31
+ 3. **Export** JSON-friendly snapshots that preserve source/provenance and raw data.
32
+
33
+ ## Install
34
+
35
+ From the unpacked project folder:
36
+
37
+ ```powershell
38
+ py -m venv .venv
39
+ .\.venv\Scripts\Activate.ps1
40
+ python -m pip install -e .
41
+ ```
42
+
43
+ For sustained API use, identify your own project in the User-Agent:
44
+
45
+ ```python
46
+ from mtgwiki import Wiki
47
+
48
+ wiki = Wiki(
49
+ user_agent="my-project/0.1 (https://example.com/contact)"
50
+ )
51
+ ```
52
+
53
+ Point the same client at another MediaWiki installation by supplying its Action API endpoint:
54
+
55
+ ```python
56
+ wiki = Wiki(
57
+ api_url="https://www.mediawiki.org/w/api.php",
58
+ user_agent="my-project/0.1 (https://example.com/contact)",
59
+ )
60
+ ```
61
+
62
+ The package name/default URL are conveniences; the public data model contains no Magic-specific entity types.
63
+
64
+ ## Compatibility
65
+
66
+ The client targets modern MediaWiki Action API installations and always requests JSON with `formatversion=2`. Revision-content handling supports main-slot responses and includes fallbacks for older response shapes. Rendered section discovery prefers `tocdata` and falls back to the older `sections` output. Use `siteinfo()` and `paraminfo()` when a consuming project needs to discover site-specific capabilities rather than assume them.
67
+
68
+ ## Quick start
69
+
70
+ ```python
71
+ from mtgwiki import Wiki
72
+
73
+ wiki = Wiki()
74
+
75
+ page = wiki.get("Skyship Weatherlight")
76
+ print(page["title"])
77
+
78
+ hits = wiki.search("Bloomburrow", limit=20)
79
+ for hit in hits:
80
+ print(hit["title"])
81
+ ```
82
+
83
+ Search results are ranked search results. `search()` does not pretend the first result is the entity you meant.
84
+
85
+ ## Generic structure discovery
86
+
87
+ Ask the package what syntax exists instead of hard-coding what you expect:
88
+
89
+ ```python
90
+ structure = wiki.structure(
91
+ "Skyship Weatherlight",
92
+ section="In-game references",
93
+ )
94
+
95
+ for template in structure["templates"]:
96
+ print(template["name"])
97
+ print(template["params"].keys())
98
+ ```
99
+
100
+ A template call is returned generically, for example:
101
+
102
+ ```python
103
+ {
104
+ "name": "Some template",
105
+ "depth": 1,
106
+ "raw": "{{Some template|x=1|data=...}}",
107
+ "params": {
108
+ "x": "1",
109
+ "data": "...",
110
+ },
111
+ "params_clean": {
112
+ "x": "1",
113
+ "data": "...",
114
+ },
115
+ "parameters": [...],
116
+ }
117
+ ```
118
+
119
+ The package does not assign meaning to `x`, `data`, `art`, `species`, `job1` or any other parameter name.
120
+
121
+ ## Snapshots: easiest way to build reusable datasets
122
+
123
+ ```python
124
+ snapshot = wiki.extract("Dack Fayden")
125
+
126
+ print(snapshot.keys())
127
+ print(snapshot["source"])
128
+ print(snapshot["data"])
129
+ print(snapshot["structure"].keys())
130
+ ```
131
+
132
+ A snapshot contains:
133
+
134
+ ```text
135
+ schema_version
136
+ exists
137
+ source
138
+ api_url
139
+ requested_title
140
+ resolved_title
141
+ pageid
142
+ revision_id
143
+ revision_timestamp
144
+ retrieved_at
145
+ redirects
146
+ page
147
+ revision
148
+ data
149
+ categories
150
+ links
151
+ templates
152
+ images
153
+ external_links
154
+ pageprops
155
+ content
156
+ raw
157
+ text
158
+ structure
159
+ sections
160
+ templates
161
+ wikilinks
162
+ external_links
163
+ lists
164
+ tables
165
+ raw
166
+ page
167
+ normalized
168
+ redirects
169
+ converted
170
+ ```
171
+
172
+ Raw source is deliberately retained. Cleaned/normalized values are conveniences, not replacements for the source.
173
+
174
+ ## Batch extraction
175
+
176
+ MediaWiki accepts multiple titles in one query. `extract_many()` batches up to 50 titles per request group instead of making one independent request per title.
177
+
178
+ ```python
179
+ pages = wiki.extract_many(
180
+ [
181
+ "Urza",
182
+ "Dack Fayden",
183
+ "Skyship Weatherlight",
184
+ "Bloomburrow (plane)",
185
+ ]
186
+ )
187
+ ```
188
+
189
+ You can request a smaller snapshot when you do not need everything:
190
+
191
+ ```python
192
+ pages = wiki.extract_many(
193
+ ["Urza", "Dack Fayden"],
194
+ include=("revision", "categories", "wikitext", "structure"),
195
+ )
196
+ ```
197
+
198
+ Supported include values:
199
+
200
+ ```text
201
+ revision
202
+ categories
203
+ links
204
+ templates
205
+ images
206
+ external_links
207
+ pageprops
208
+ wikitext
209
+ structure
210
+ ```
211
+
212
+ ## JSON / JSONL
213
+
214
+ ```python
215
+ from mtgwiki import write_json, write_jsonl
216
+
217
+ write_json("one-page.json", wiki.extract("Urza"))
218
+ write_jsonl("dataset.jsonl", wiki.extract_many(["Urza", "Karn", "Squee"]))
219
+ ```
220
+
221
+ JSONL is convenient for larger datasets because every page is one independent line.
222
+
223
+ ## Raw API is always available
224
+
225
+ The high-level helpers never lock you out of MediaWiki itself:
226
+
227
+ ```python
228
+ data = wiki.api(
229
+ action="query",
230
+ meta="siteinfo",
231
+ siprop="general|namespaces",
232
+ )
233
+ ```
234
+
235
+ Continuation:
236
+
237
+ ```python
238
+ for response in wiki.iter_api(
239
+ action="query",
240
+ list="categorymembers",
241
+ cmtitle="Category:Example",
242
+ cmlimit="max",
243
+ ):
244
+ print(response)
245
+ ```
246
+
247
+ Or collect common list modules directly:
248
+
249
+ ```python
250
+ members = wiki.members("Example category")
251
+ backlinks = wiki.backlinks("Example page")
252
+ embeds = wiki.embedded_in("Template:Example")
253
+ ```
254
+
255
+ These are MediaWiki concepts, not domain semantics.
256
+
257
+ ## Lazy list iteration
258
+
259
+ For large list modules you can stream items instead of building one large list in memory:
260
+
261
+ ```python
262
+ for item in wiki.iter_list(
263
+ "allpages",
264
+ aplimit="max",
265
+ apnamespace=0,
266
+ ):
267
+ print(item["title"])
268
+ ```
269
+
270
+ `list()` remains available when collecting everything is more convenient.
271
+
272
+ ## Recursive categories
273
+
274
+ Category trees are a generic MediaWiki structure. `members()` can now traverse them without every research script reimplementing a queue and cycle guard:
275
+
276
+ ```python
277
+ pages = wiki.members(
278
+ "Example root category",
279
+ recurse=True,
280
+ namespace=0,
281
+ cmtype="page",
282
+ )
283
+
284
+ for page in pages:
285
+ print(page["title"])
286
+ ```
287
+
288
+ Use an integer to limit depth. `recurse=1` includes members of immediate subcategories but does not descend further:
289
+
290
+ ```python
291
+ pages = wiki.members(
292
+ "Example root category",
293
+ recurse=1,
294
+ namespace=0,
295
+ cmtype="page",
296
+ )
297
+ ```
298
+
299
+ For very large category trees, use `iter_members()` to stream the same traversal lazily. Recursive traversal is cycle-safe and de-duplicates members that appear through multiple category paths.
300
+
301
+ ## Sections
302
+
303
+ For rendered section discovery the package asks MediaWiki for `tocdata` and falls back to the older `sections` response when necessary:
304
+
305
+ ```python
306
+ for section in wiki.sections("Bloomburrow (plane)"):
307
+ print(section["line"])
308
+ ```
309
+
310
+ Fetch only one named section:
311
+
312
+ ```python
313
+ text = wiki.section("Bloomburrow (plane)", "Flora")
314
+ ```
315
+
316
+ Extract generic list items without losing the original markup:
317
+
318
+ ```python
319
+ items = wiki.section_items(
320
+ "Bloomburrow (plane)",
321
+ "Flora",
322
+ clean=True,
323
+ )
324
+
325
+ for item in items:
326
+ print(item["raw_text"])
327
+ print(item["text"])
328
+ ```
329
+
330
+ The method knows only that it is reading a list in a section. It does not know what "Flora" means.
331
+
332
+ ## Wikitext structure parser
333
+
334
+ The built-in parser is deliberately conservative and dependency-free. It discovers:
335
+
336
+ - section headings
337
+ - nested template calls and arbitrary parameters
338
+ - wikilinks
339
+ - external links
340
+ - list items and nesting depth
341
+ - MediaWiki tables
342
+
343
+ Use it on any string:
344
+
345
+ ```python
346
+ from mtgwiki import parse_wikitext
347
+
348
+ structure = parse_wikitext("{{Thing|a=1}}\n* [[Target|Label]]")
349
+ ```
350
+
351
+ It is **not** intended to reproduce MediaWiki's renderer. When exact rendered output matters, use `wiki.parse()` / `wiki.section()` and let the server parse the page. Raw source and spans are kept so a later project can always fall back to the original material.
352
+
353
+ ## Revisions and provenance
354
+
355
+ `extract()` records the revision ID, timestamp and SHA-1 where available. This makes exported data traceable to the wiki revision it came from.
356
+
357
+ ```python
358
+ snapshot = wiki.extract("Urza", include=("revision", "wikitext"))
359
+ print(snapshot["source"]["revision_id"])
360
+ print(snapshot["revision"]["sha1"])
361
+ ```
362
+
363
+ ## API capability discovery
364
+
365
+ Do not assume every MediaWiki installation exposes exactly the same features:
366
+
367
+ ```python
368
+ info = wiki.siteinfo()
369
+ params = wiki.paraminfo(["query", "parse"])
370
+ ```
371
+
372
+ ## Polite API behavior
373
+
374
+ The client defaults to:
375
+
376
+ ```text
377
+ serial requests only
378
+ min_interval = 0.5 seconds
379
+ maxlag = 5
380
+ Retry-After support
381
+ exponential retry/backoff
382
+ memory cache
383
+ ```
384
+
385
+ Statistics:
386
+
387
+ ```python
388
+ print(wiki.client.stats())
389
+ ```
390
+
391
+ Optional persistent cache:
392
+
393
+ ```python
394
+ wiki = Wiki(
395
+ cache_path=".mtgwiki-cache.sqlite",
396
+ cache_ttl=24 * 60 * 60,
397
+ )
398
+ ```
399
+
400
+ The SQLite cache is optional and uses only Python's standard library.
401
+
402
+ ## Compatibility helpers
403
+
404
+ The 1.0 helpers `infobox()`, `infobox_fields()`, `templates_with_params()` and `template_params()` remain available so existing experiments keep working. New code should generally prefer `template_calls()` or `structure()` because those do not assume a particular template convention.
405
+
406
+ ## Tests
407
+
408
+ Offline unit tests:
409
+
410
+ ```powershell
411
+ python -m unittest discover -s tests -v
412
+ ```
413
+
414
+ Then run the supplied live verifier:
415
+
416
+ ```powershell
417
+ python verify_live.py
418
+ ```
419
+
420
+ It creates:
421
+
422
+ ```text
423
+ mtgwiki_verify_report.json
424
+ verify_output/pages.jsonl
425
+ ```
426
+
427
+ Send `mtgwiki_verify_report.json` back to ChatGPT if you want the live result reviewed.