refsource 0.1.0__py3-none-any.whl
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.
- refsource/__init__.py +636 -0
- refsource/__main__.py +213 -0
- refsource/manifest.json +4059 -0
- refsource/py.typed +0 -0
- refsource-0.1.0.dist-info/METADATA +228 -0
- refsource-0.1.0.dist-info/RECORD +10 -0
- refsource-0.1.0.dist-info/WHEEL +5 -0
- refsource-0.1.0.dist-info/entry_points.txt +2 -0
- refsource-0.1.0.dist-info/licenses/LICENSE +21 -0
- refsource-0.1.0.dist-info/top_level.txt +1 -0
refsource/__main__.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""Command line for refsource: look something up without writing any code.
|
|
2
|
+
|
|
3
|
+
refsource datasets loan limit
|
|
4
|
+
refsource fields conforming-loan-limits
|
|
5
|
+
refsource lookup conforming-loan-limits state=AL county_name="AUTAUGA COUNTY"
|
|
6
|
+
refsource search ai-model-deprecation-and-retirement gpt-4
|
|
7
|
+
refsource show conforming-loan-limits 01001
|
|
8
|
+
|
|
9
|
+
Output leads with the value and follows it with the source and the quote,
|
|
10
|
+
because a value without those is the thing this package exists not to be.
|
|
11
|
+
Add --json for machine-readable output.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import json
|
|
17
|
+
import sys
|
|
18
|
+
import warnings
|
|
19
|
+
|
|
20
|
+
import refsource
|
|
21
|
+
from refsource import _clip
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _host(url):
|
|
25
|
+
return (url or "").split("://", 1)[-1].split("/", 1)[0].lower()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _print_record(rec, show_all_fields):
|
|
29
|
+
print("\n{0}".format(rec.id or rec.url))
|
|
30
|
+
for name, val in rec.values.items():
|
|
31
|
+
line = " {0}: {1}".format(name, val)
|
|
32
|
+
if val.derived:
|
|
33
|
+
line += " [our reading, not the page's words]"
|
|
34
|
+
elif not val.confirmed:
|
|
35
|
+
line += " [not confirmed word-for-word in the quote]"
|
|
36
|
+
print(line)
|
|
37
|
+
# A field a second publisher reported must not be read off the
|
|
38
|
+
# record's own source line below it. Say whose value it is, here,
|
|
39
|
+
# next to the number.
|
|
40
|
+
if val.source and val.source != rec.source_url:
|
|
41
|
+
print(" from {0}".format(val.source))
|
|
42
|
+
if val.quote:
|
|
43
|
+
print(' quoted: "{0}"'.format(_clip(val.quote, 160)))
|
|
44
|
+
if show_all_fields and val.disagreement:
|
|
45
|
+
for alt in val.disagreement:
|
|
46
|
+
same = _host(alt.get("source")) == _host(val.source)
|
|
47
|
+
print(" {0} — {1} says: {2}".format(
|
|
48
|
+
"the same publisher states this differently" if same
|
|
49
|
+
else "another source disagrees",
|
|
50
|
+
alt.get("source"), _clip(alt.get("value"), 200)))
|
|
51
|
+
if rec.source_url:
|
|
52
|
+
print(" source: {0}".format(rec.source_url))
|
|
53
|
+
if rec.source_quote:
|
|
54
|
+
print(' quoted: "{0}"'.format(rec.source_quote.strip()[:300]))
|
|
55
|
+
print(" page: {0}".format(rec.url))
|
|
56
|
+
print(" verified {0}{1}".format(
|
|
57
|
+
rec.verified,
|
|
58
|
+
" — PAST ITS RE-CHECK DATE ({0})".format(rec.stale_after)
|
|
59
|
+
if rec.dataset.stale else ""))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def cmd_datasets(args):
|
|
63
|
+
hits = refsource.datasets(" ".join(args.query) if args.query else None)
|
|
64
|
+
if args.json:
|
|
65
|
+
print(json.dumps([{
|
|
66
|
+
"slug": d.slug, "title": d.title, "records": d.record_count,
|
|
67
|
+
"last_verified": d.last_verified, "url": d.url, "fields": d.fields,
|
|
68
|
+
} for d in hits], indent=2))
|
|
69
|
+
return 0
|
|
70
|
+
for d in sorted(hits, key=lambda x: x.slug):
|
|
71
|
+
print("{0}\n {1}\n {2} records, verified {3} — {4}".format(
|
|
72
|
+
d.slug, d.title, d.record_count, d.last_verified, d.url))
|
|
73
|
+
print("\n{0} dataset(s).".format(len(hits)))
|
|
74
|
+
return 0
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def cmd_fields(args):
|
|
78
|
+
ds = refsource.dataset(args.slug)
|
|
79
|
+
if args.json:
|
|
80
|
+
print(json.dumps(ds.fields, indent=2))
|
|
81
|
+
return 0
|
|
82
|
+
print("{0} — {1}".format(ds.slug, ds.title))
|
|
83
|
+
for f in ds.fields:
|
|
84
|
+
print(" {0}".format(f))
|
|
85
|
+
return 0
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _parse_filters(pairs):
|
|
89
|
+
filters = {}
|
|
90
|
+
for p in pairs:
|
|
91
|
+
if "=" not in p:
|
|
92
|
+
raise SystemExit(
|
|
93
|
+
"filters look like field=value (got {0!r}). "
|
|
94
|
+
"Run `refsource fields <slug>` for the field names.".format(p))
|
|
95
|
+
k, v = p.split("=", 1)
|
|
96
|
+
filters.setdefault(k.strip(), []).append(v.strip())
|
|
97
|
+
return {k: (v if len(v) > 1 else v[0]) for k, v in filters.items()}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def cmd_lookup(args):
|
|
101
|
+
filters = _parse_filters(args.filters)
|
|
102
|
+
rows = refsource.lookup(args.slug, **filters)
|
|
103
|
+
if not rows and filters and not args.json:
|
|
104
|
+
# Nothing matched. Almost always the spelling: one dataset writes a
|
|
105
|
+
# state as "TX", the next as "Texas". Show what is actually there
|
|
106
|
+
# rather than leaving it looking like the data is missing.
|
|
107
|
+
ds = refsource.dataset(args.slug)
|
|
108
|
+
print("no matching records — these are the values present:")
|
|
109
|
+
for field in filters:
|
|
110
|
+
vals = ds.values_of(field, limit=8)
|
|
111
|
+
print(" {0}: {1}{2}".format(
|
|
112
|
+
field, ", ".join(vals) if vals else "(no values)",
|
|
113
|
+
" ..." if len(vals) == 8 else ""))
|
|
114
|
+
return 1
|
|
115
|
+
return _emit(rows, args)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def cmd_search(args):
|
|
119
|
+
rows = refsource.search(args.slug, " ".join(args.text), limit=args.limit)
|
|
120
|
+
return _emit(rows, args)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def cmd_show(args):
|
|
124
|
+
rec = refsource.get(args.slug, args.id)
|
|
125
|
+
if rec is None:
|
|
126
|
+
print("no record {0!r} in {1}".format(args.id, args.slug), file=sys.stderr)
|
|
127
|
+
return 1
|
|
128
|
+
return _emit([rec], args)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _emit(rows, args):
|
|
132
|
+
if args.json:
|
|
133
|
+
print(json.dumps([r.to_dict() for r in rows[: args.limit]], indent=2))
|
|
134
|
+
return 0
|
|
135
|
+
if not rows:
|
|
136
|
+
print("no matching records")
|
|
137
|
+
return 1
|
|
138
|
+
for rec in rows[: args.limit]:
|
|
139
|
+
_print_record(rec, show_all_fields=True)
|
|
140
|
+
if len(rows) > args.limit:
|
|
141
|
+
print("\n... and {0} more (use --limit)".format(len(rows) - args.limit))
|
|
142
|
+
return 0
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def main(argv=None):
|
|
146
|
+
# The shared flags hang off every subcommand as well as the top level, so
|
|
147
|
+
# both `refsource --json lookup ...` and `refsource lookup ... --json` work
|
|
148
|
+
# — people type the second one. SUPPRESS is what makes that safe: without
|
|
149
|
+
# it a subparser's default would overwrite a flag given before the command.
|
|
150
|
+
common = argparse.ArgumentParser(add_help=False)
|
|
151
|
+
common.add_argument("--json", action="store_true",
|
|
152
|
+
default=argparse.SUPPRESS, help="machine-readable output")
|
|
153
|
+
common.add_argument("--limit", type=int, default=argparse.SUPPRESS,
|
|
154
|
+
help="max records shown (default 20)")
|
|
155
|
+
common.add_argument("--offline", action="store_true",
|
|
156
|
+
default=argparse.SUPPRESS,
|
|
157
|
+
help="use only what is already cached")
|
|
158
|
+
common.add_argument("--base-url", default=argparse.SUPPRESS,
|
|
159
|
+
help="fetch bundles from here instead")
|
|
160
|
+
|
|
161
|
+
ap = argparse.ArgumentParser(
|
|
162
|
+
prog="refsource", parents=[common],
|
|
163
|
+
description="Look up reference data that carries its own source.",
|
|
164
|
+
epilog="Catalogue and method: https://referencesource.org/")
|
|
165
|
+
sub = ap.add_subparsers(dest="cmd")
|
|
166
|
+
|
|
167
|
+
p = sub.add_parser("datasets", parents=[common],
|
|
168
|
+
help="list or search the catalogue (no network)")
|
|
169
|
+
p.add_argument("query", nargs="*")
|
|
170
|
+
p.set_defaults(func=cmd_datasets)
|
|
171
|
+
|
|
172
|
+
p = sub.add_parser("fields", parents=[common],
|
|
173
|
+
help="field names of one dataset (no network)")
|
|
174
|
+
p.add_argument("slug")
|
|
175
|
+
p.set_defaults(func=cmd_fields)
|
|
176
|
+
|
|
177
|
+
p = sub.add_parser("lookup", parents=[common],
|
|
178
|
+
help="records matching field=value filters")
|
|
179
|
+
p.add_argument("slug")
|
|
180
|
+
p.add_argument("filters", nargs="*")
|
|
181
|
+
p.set_defaults(func=cmd_lookup)
|
|
182
|
+
|
|
183
|
+
p = sub.add_parser("search", parents=[common],
|
|
184
|
+
help="records containing some text")
|
|
185
|
+
p.add_argument("slug")
|
|
186
|
+
p.add_argument("text", nargs="+")
|
|
187
|
+
p.set_defaults(func=cmd_search)
|
|
188
|
+
|
|
189
|
+
p = sub.add_parser("show", parents=[common], help="one record by id")
|
|
190
|
+
p.add_argument("slug")
|
|
191
|
+
p.add_argument("id")
|
|
192
|
+
p.set_defaults(func=cmd_show)
|
|
193
|
+
|
|
194
|
+
args = ap.parse_args(argv)
|
|
195
|
+
if not getattr(args, "func", None):
|
|
196
|
+
ap.print_help()
|
|
197
|
+
return 2
|
|
198
|
+
args.json = getattr(args, "json", False)
|
|
199
|
+
args.limit = getattr(args, "limit", 20)
|
|
200
|
+
args.offline = getattr(args, "offline", False)
|
|
201
|
+
args.base_url = getattr(args, "base_url", None)
|
|
202
|
+
|
|
203
|
+
refsource.configure(base_url=args.base_url, offline=args.offline or None)
|
|
204
|
+
warnings.simplefilter("always", refsource.StaleDataWarning)
|
|
205
|
+
try:
|
|
206
|
+
return args.func(args)
|
|
207
|
+
except refsource.RefsourceError as exc:
|
|
208
|
+
print("refsource: {0}".format(exc), file=sys.stderr)
|
|
209
|
+
return 1
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
if __name__ == "__main__":
|
|
213
|
+
raise SystemExit(main())
|