Skip to content

Commit c10e604

Browse files
committed
feature: support schema functions, to hit feature parity with web server.
1 parent f013fa0 commit c10e604

File tree

5 files changed

+1338
-0
lines changed

5 files changed

+1338
-0
lines changed

python/datafed_pkg/datafed/CLI.py

Lines changed: 367 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1996,6 +1996,295 @@ def _taskView(task_id):
19961996
_generic_reply_handler(reply, _print_task_array)
19971997

19981998

1999+
# =============================================================================
2000+
# ---------------------------------------------------------- Schema Functions
2001+
# =============================================================================
2002+
2003+
2004+
@_cli.command(name="schema", cls=_AliasedGroup, help="Schema commands.")
2005+
def _schema():
2006+
pass
2007+
2008+
2009+
@_schema.command(name="view")
2010+
@click.argument("schema_id", metavar="ID")
2011+
@click.option(
2012+
"-r", "--resolve", is_flag=True, help="Resolve schema references."
2013+
)
2014+
@_global_output_options
2015+
def _schemaView(schema_id, resolve):
2016+
"""
2017+
View schema information. Displays schema definition, description, owner,
2018+
and other administrative fields. ID is the schema identifier, optionally
2019+
including a version suffix (e.g. "my_schema:1").
2020+
"""
2021+
2022+
reply = _capi.schemaView(_resolve_id(schema_id), resolve=resolve)
2023+
_generic_reply_handler(reply, _print_schema)
2024+
2025+
2026+
@_schema.command(name="create")
2027+
@click.argument("schema_id", metavar="ID")
2028+
@click.option(
2029+
"-D",
2030+
"--definition",
2031+
type=str,
2032+
required=False,
2033+
help="Inline JSON schema definition string.",
2034+
)
2035+
@click.option(
2036+
"-F",
2037+
"--definition-file",
2038+
type=str,
2039+
required=False,
2040+
help="Path to local JSON file containing schema definition.",
2041+
)
2042+
@click.option("-d", "--description", type=str, required=False, help="Description text.")
2043+
@click.option(
2044+
"-p", "--public", is_flag=True, required=False, help="Make schema publicly visible."
2045+
)
2046+
@click.option(
2047+
"-s", "--system", is_flag=True, required=False, help="Create as a system schema."
2048+
)
2049+
@_global_output_options
2050+
def _schemaCreate(schema_id, definition, definition_file, description, public, system):
2051+
"""
2052+
Create a new metadata schema. A JSON schema definition is required and
2053+
may be provided inline via --definition or read from a file via
2054+
--definition-file. Cannot specify both.
2055+
"""
2056+
2057+
if definition and definition_file:
2058+
raise Exception(
2059+
"Cannot specify both --definition and --definition-file options."
2060+
)
2061+
2062+
if not definition and not definition_file:
2063+
raise Exception("Must specify either --definition or --definition-file.")
2064+
2065+
reply = _capi.schemaCreate(
2066+
schema_id,
2067+
definition=definition,
2068+
definition_file=definition_file,
2069+
description=description,
2070+
public=public,
2071+
system=system,
2072+
)
2073+
_generic_reply_handler(reply, _print_ack_reply)
2074+
2075+
2076+
@_schema.command(name="revise")
2077+
@click.argument("schema_id", metavar="ID")
2078+
@click.option(
2079+
"-D",
2080+
"--definition",
2081+
type=str,
2082+
required=False,
2083+
help="Updated inline JSON schema definition string.",
2084+
)
2085+
@click.option(
2086+
"-F",
2087+
"--definition-file",
2088+
type=str,
2089+
required=False,
2090+
help="Path to local JSON file containing updated schema definition.",
2091+
)
2092+
@click.option("-d", "--description", type=str, required=False, help="Description text.")
2093+
@click.option(
2094+
"-p",
2095+
"--public",
2096+
is_flag=True,
2097+
default=None,
2098+
required=False,
2099+
help="Make schema publicly visible.",
2100+
)
2101+
@click.option(
2102+
"-s",
2103+
"--system",
2104+
is_flag=True,
2105+
default=None,
2106+
required=False,
2107+
help="Set as system schema.",
2108+
)
2109+
@_global_output_options
2110+
def _schemaRevise(schema_id, definition, definition_file, description, public, system):
2111+
"""
2112+
Create a new revision of an existing schema. Any fields not provided are
2113+
carried forward from the current revision. The definition may be provided
2114+
inline or read from a file.
2115+
"""
2116+
2117+
if definition and definition_file:
2118+
raise Exception(
2119+
"Cannot specify both --definition and --definition-file options."
2120+
)
2121+
2122+
reply = _capi.schemaRevise(
2123+
schema_id,
2124+
definition=definition,
2125+
definition_file=definition_file,
2126+
description=description,
2127+
public=public if public else None,
2128+
system=system if system else None,
2129+
)
2130+
_generic_reply_handler(reply, _print_ack_reply)
2131+
2132+
2133+
@_schema.command(name="update")
2134+
@click.argument("schema_id", metavar="ID")
2135+
@click.option("-n", "--new-id", type=str, required=False, help="Rename schema to new ID.")
2136+
@click.option(
2137+
"-D",
2138+
"--definition",
2139+
type=str,
2140+
required=False,
2141+
help="Updated inline JSON schema definition string.",
2142+
)
2143+
@click.option(
2144+
"-F",
2145+
"--definition-file",
2146+
type=str,
2147+
required=False,
2148+
help="Path to local JSON file containing updated schema definition.",
2149+
)
2150+
@click.option("-d", "--description", type=str, required=False, help="Description text.")
2151+
@click.option(
2152+
"-p",
2153+
"--public",
2154+
is_flag=True,
2155+
default=None,
2156+
required=False,
2157+
help="Make schema publicly visible.",
2158+
)
2159+
@click.option(
2160+
"-s",
2161+
"--system",
2162+
is_flag=True,
2163+
default=None,
2164+
required=False,
2165+
help="Set as system schema.",
2166+
)
2167+
@_global_output_options
2168+
def _schemaUpdate(
2169+
schema_id, new_id, definition, definition_file, description, public, system
2170+
):
2171+
"""
2172+
Update an existing schema in place without creating a new revision.
2173+
The definition may be provided inline or read from a file.
2174+
"""
2175+
2176+
if definition and definition_file:
2177+
raise Exception(
2178+
"Cannot specify both --definition and --definition-file options."
2179+
)
2180+
2181+
reply = _capi.schemaUpdate(
2182+
schema_id,
2183+
new_id=new_id,
2184+
definition=definition,
2185+
definition_file=definition_file,
2186+
description=description,
2187+
public=public if public else None,
2188+
system=system if system else None,
2189+
)
2190+
_generic_reply_handler(reply, _print_ack_reply)
2191+
2192+
2193+
@_schema.command(name="delete")
2194+
@click.option("-f", "--force", is_flag=True, help="Delete without confirmation.")
2195+
@click.argument("schema_id", metavar="ID")
2196+
def _schemaDelete(schema_id, force):
2197+
"""
2198+
Delete a schema by ID.
2199+
"""
2200+
2201+
if not force:
2202+
if not _interactive:
2203+
raise Exception("Cannot confirm deletion while running non-interactively.")
2204+
2205+
if not click.confirm("Confirm delete schema?"):
2206+
return
2207+
2208+
reply = _capi.schemaDelete(_resolve_id(schema_id))
2209+
_generic_reply_handler(reply, _print_ack_reply)
2210+
2211+
2212+
@_schema.command(name="search")
2213+
@click.option("--id", "schema_id", type=str, required=False, help="Schema ID query text.")
2214+
@click.option("--text", type=str, required=False, help="Text search in description.")
2215+
@click.option("--owner", type=str, required=False, help="Filter by owner ID.")
2216+
@click.option(
2217+
"--sort",
2218+
type=str,
2219+
required=False,
2220+
help="Sort option (id, title, owner, ct, ut, text).",
2221+
)
2222+
@click.option(
2223+
"--sort-rev",
2224+
is_flag=True,
2225+
required=False,
2226+
help="Sort in reverse order (not available for text).",
2227+
)
2228+
@click.option("-O", "--offset", default=0, help="Start list at offset.")
2229+
@click.option("-C", "--count", default=20, help="Limit list to count results.")
2230+
@_global_output_options
2231+
def _schemaSearch(schema_id, text, owner, sort, sort_rev, offset, count):
2232+
"""
2233+
Search for schemas. At least one search option should be specified. Results
2234+
are returned as a listing of matching schemas.
2235+
"""
2236+
2237+
reply = _capi.schemaSearch(
2238+
schema_id=schema_id,
2239+
text=text,
2240+
owner=owner,
2241+
sort=sort,
2242+
sort_rev=sort_rev if sort_rev else None,
2243+
offset=offset,
2244+
count=count,
2245+
)
2246+
_generic_reply_handler(reply, _print_schema_listing)
2247+
2248+
2249+
@_schema.command(name="validate")
2250+
@click.argument("schema_id", metavar="SCHEMA_ID")
2251+
@click.option(
2252+
"-m",
2253+
"--metadata",
2254+
type=str,
2255+
required=False,
2256+
help="Inline metadata JSON string to validate.",
2257+
)
2258+
@click.option(
2259+
"-f",
2260+
"--metadata-file",
2261+
type=str,
2262+
required=False,
2263+
help="Path to local JSON file containing metadata to validate.",
2264+
)
2265+
@_global_output_options
2266+
def _schemaValidate(schema_id, metadata, metadata_file):
2267+
"""
2268+
Validate metadata against a schema without creating or modifying a record.
2269+
Useful for pre-checking metadata before using it with 'data create' or
2270+
'data update' with --schema-enforce. The SCHEMA_ID is the schema to
2271+
validate against (format: "id:version").
2272+
"""
2273+
2274+
if metadata and metadata_file:
2275+
raise Exception("Cannot specify both --metadata and --metadata-file options.")
2276+
2277+
if not metadata and not metadata_file:
2278+
raise Exception("Must specify either --metadata or --metadata-file.")
2279+
2280+
reply = _capi.metadataValidate(
2281+
schema_id,
2282+
metadata=metadata,
2283+
metadata_file=metadata_file,
2284+
)
2285+
_generic_reply_handler(reply, _print_metadata_validate)
2286+
2287+
19992288
# =============================================================================
20002289
# ---------------------------------------------------------- Endpoint Functions
20012290
# =============================================================================
@@ -2787,6 +3076,84 @@ def _print_query(message):
27873076
" {:<18} {}".format("Category: ", _arrayToDotted(message.query.cat_tags))
27883077
)
27893078

3079+
def _print_schema_listing(message):
3080+
if len(message.schema) == 0:
3081+
click.echo("(no schemas)")
3082+
return
3083+
3084+
df_idx = 1
3085+
global _list_items
3086+
_list_items = []
3087+
3088+
for s in message.schema:
3089+
_list_items.append(s.id)
3090+
pub_flag = " [pub]" if s.pub else ""
3091+
depr_flag = " [DEPRECATED]" if s.depr else ""
3092+
click.echo(
3093+
"{:2}. {:30} v{:<5} {:20}{}{}".format(
3094+
df_idx,
3095+
s.id,
3096+
s.ver,
3097+
s.own_nm if s.own_nm else s.own_id,
3098+
pub_flag,
3099+
depr_flag,
3100+
)
3101+
)
3102+
df_idx += 1
3103+
3104+
if message.total > message.offset + message.count:
3105+
click.echo(
3106+
" [{}-{} of {}]".format(
3107+
message.offset + 1,
3108+
min(message.offset + message.count, message.total),
3109+
message.total,
3110+
)
3111+
)
3112+
3113+
3114+
def _print_schema(message):
3115+
for s in message.schema:
3116+
click.echo("{:<15}{:<50}".format("ID: ", s.id))
3117+
click.echo("{:<15}{:<50}".format("Version: ", str(s.ver)))
3118+
click.echo("{:<15}{:<50}".format("Owner: ", s.own_nm if s.own_nm else s.own_id))
3119+
click.echo("{:<15}{:<50}".format("Public: ", "Yes" if s.pub else "No"))
3120+
click.echo("{:<15}{:<50}".format("Deprecated: ", "Yes" if s.depr else "No"))
3121+
click.echo("{:<15}{:<50}".format("Ref Count: ", str(s.cnt)))
3122+
3123+
_wrap_text(s.desc if s.desc else "", "Description:", 15)
3124+
3125+
if _verbosity == 2:
3126+
schema_def = getattr(s, 'def')
3127+
if schema_def:
3128+
click.echo("Definition:\n")
3129+
try:
3130+
json_obj = jsonlib.loads(schema_def)
3131+
_printJSON(json_obj, 2, 2)
3132+
click.echo("\n")
3133+
except Exception:
3134+
click.echo(" " + schema_def + "\n")
3135+
else:
3136+
click.echo("{:<15}{:<50}".format("Definition: ", "(none)"))
3137+
3138+
if len(s.uses):
3139+
click.echo("Uses:")
3140+
for ref in s.uses:
3141+
click.echo(" {} v{}".format(ref.id, ref.ver))
3142+
3143+
if len(s.used_by):
3144+
click.echo("Used By:")
3145+
for ref in s.used_by:
3146+
click.echo(" {} v{}".format(ref.id, ref.ver))
3147+
3148+
3149+
def _print_metadata_validate(message):
3150+
if message.errors:
3151+
if _output_mode == _OM_TEXT:
3152+
click.echo("Validation FAILED:\n")
3153+
_wrap_text(message.errors, "", 2)
3154+
else:
3155+
if _output_mode == _OM_TEXT:
3156+
click.echo("Validation passed.")
27903157

27913158
def _wrap_text(text, prefix, indent, compact=False):
27923159
if len(text) == 0:

0 commit comments

Comments
 (0)