context.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  1. """
  2. Implementation of the JSON-LD Context structure. See: http://json-ld.org/
  3. """
  4. # https://github.com/RDFLib/rdflib-jsonld/blob/feature/json-ld-1.1/rdflib_jsonld/context.py
  5. from __future__ import annotations
  6. from collections import namedtuple
  7. from typing import (
  8. TYPE_CHECKING,
  9. Any,
  10. Collection,
  11. Dict,
  12. Generator,
  13. List,
  14. Optional,
  15. Set,
  16. Tuple,
  17. Union,
  18. )
  19. from urllib.parse import urljoin, urlsplit
  20. from rdflib.namespace import RDF
  21. from .errors import (
  22. INVALID_CONTEXT_ENTRY,
  23. INVALID_REMOTE_CONTEXT,
  24. RECURSIVE_CONTEXT_INCLUSION,
  25. )
  26. from .keys import (
  27. BASE,
  28. CONTAINER,
  29. CONTEXT,
  30. GRAPH,
  31. ID,
  32. IMPORT,
  33. INCLUDED,
  34. INDEX,
  35. JSON,
  36. LANG,
  37. LIST,
  38. NEST,
  39. NONE,
  40. PREFIX,
  41. PROPAGATE,
  42. PROTECTED,
  43. REV,
  44. SET,
  45. TYPE,
  46. VALUE,
  47. VERSION,
  48. VOCAB,
  49. )
  50. from .util import norm_url, source_to_json, split_iri
  51. NODE_KEYS = {GRAPH, ID, INCLUDED, JSON, LIST, NEST, NONE, REV, SET, TYPE, VALUE, LANG}
  52. class Defined(int):
  53. pass
  54. UNDEF = Defined(0)
  55. # From <https://tools.ietf.org/html/rfc3986#section-2.2>
  56. URI_GEN_DELIMS = (":", "/", "?", "#", "[", "]", "@")
  57. _ContextSourceType = Union[
  58. List[Union[Dict[str, Any], str, None]], Dict[str, Any], str, None
  59. ]
  60. class Context:
  61. def __init__(
  62. self,
  63. source: _ContextSourceType = None,
  64. base: Optional[str] = None,
  65. version: Optional[float] = 1.1,
  66. ):
  67. self.version: float = version or 1.1
  68. self.language = None
  69. self.vocab: Optional[str] = None
  70. self._base: Optional[str]
  71. self.base = base
  72. self.doc_base = base
  73. self.terms: Dict[str, Any] = {}
  74. # _alias maps NODE_KEY to list of aliases
  75. self._alias: Dict[str, List[str]] = {}
  76. self._lookup: Dict[Tuple[str, Any, Union[Defined, str], bool], Term] = {}
  77. self._prefixes: Dict[str, Any] = {}
  78. self.active = False
  79. self.parent: Optional[Context] = None
  80. self.propagate = True
  81. self._context_cache: Dict[str, Any] = {}
  82. if source:
  83. self.load(source)
  84. @property
  85. def base(self) -> Optional[str]:
  86. return self._base
  87. @base.setter
  88. def base(self, base: Optional[str]):
  89. if base:
  90. hash_index = base.find("#")
  91. if hash_index > -1:
  92. base = base[0:hash_index]
  93. self._base = (
  94. self.resolve_iri(base)
  95. if (hasattr(self, "_base") and base is not None)
  96. else base
  97. )
  98. self._basedomain = "%s://%s" % urlsplit(base)[0:2] if base else None
  99. def subcontext(self, source: Any, propagate: bool = True) -> Context:
  100. # IMPROVE: to optimize, implement SubContext with parent fallback support
  101. parent = self.parent if self.propagate is False else self
  102. # type error: Item "None" of "Optional[Context]" has no attribute "_subcontext"
  103. return parent._subcontext(source, propagate) # type: ignore[union-attr]
  104. def _subcontext(self, source: Any, propagate: bool) -> Context:
  105. ctx = Context(version=self.version)
  106. ctx.propagate = propagate
  107. ctx.parent = self
  108. ctx.language = self.language
  109. ctx.vocab = self.vocab
  110. ctx.base = self.base
  111. ctx.doc_base = self.doc_base
  112. ctx._alias = {k: l[:] for k, l in self._alias.items()} # noqa: E741
  113. ctx.terms = self.terms.copy()
  114. ctx._lookup = self._lookup.copy()
  115. ctx._prefixes = self._prefixes.copy()
  116. ctx._context_cache = self._context_cache
  117. ctx.load(source)
  118. return ctx
  119. def _clear(self) -> None:
  120. self.language = None
  121. self.vocab = None
  122. self.terms = {}
  123. self._alias = {}
  124. self._lookup = {}
  125. self._prefixes = {}
  126. self.active = False
  127. self.propagate = True
  128. def get_context_for_term(self, term: Optional[Term]) -> Context:
  129. if term and term.context is not UNDEF:
  130. return self._subcontext(term.context, propagate=True)
  131. return self
  132. def get_context_for_type(self, node: Any) -> Optional[Context]:
  133. if self.version >= 1.1:
  134. rtype = self.get_type(node) if isinstance(node, dict) else None
  135. if not isinstance(rtype, list):
  136. rtype = [rtype] if rtype else []
  137. typeterm = None
  138. for rt in rtype:
  139. try:
  140. typeterm = self.terms.get(rt)
  141. except TypeError:
  142. # extra lenience, triggers if type is set to a literal
  143. pass
  144. if typeterm is not None:
  145. break
  146. if typeterm and typeterm.context:
  147. subcontext = self.subcontext(typeterm.context, propagate=False)
  148. if subcontext:
  149. return subcontext
  150. return self.parent if self.propagate is False else self
  151. def get_id(self, obj: Dict[str, Any]) -> Any:
  152. return self._get(obj, ID)
  153. def get_type(self, obj: Dict[str, Any]) -> Any:
  154. return self._get(obj, TYPE)
  155. def get_language(self, obj: Dict[str, Any]) -> Any:
  156. return self._get(obj, LANG)
  157. def get_value(self, obj: Dict[str, Any]) -> Any:
  158. return self._get(obj, VALUE)
  159. def get_graph(self, obj: Dict[str, Any]) -> Any:
  160. return self._get(obj, GRAPH)
  161. def get_list(self, obj: Dict[str, Any]) -> Any:
  162. return self._get(obj, LIST)
  163. def get_set(self, obj: Dict[str, Any]) -> Any:
  164. return self._get(obj, SET)
  165. def get_rev(self, obj: Dict[str, Any]) -> Any:
  166. return self._get(obj, REV)
  167. def _get(self, obj: Dict[str, Any], key: str) -> Any:
  168. for alias in self._alias.get(key, []):
  169. if alias in obj:
  170. return obj.get(alias)
  171. return obj.get(key)
  172. # type error: Missing return statement
  173. def get_key(self, key: str) -> str: # type: ignore[return]
  174. for alias in self.get_keys(key):
  175. return alias
  176. def get_keys(self, key: str) -> Generator[str, None, None]:
  177. if key in self._alias:
  178. for alias in self._alias[key]:
  179. yield alias
  180. yield key
  181. lang_key = property(lambda self: self.get_key(LANG))
  182. id_key = property(lambda self: self.get_key(ID))
  183. type_key = property(lambda self: self.get_key(TYPE))
  184. value_key = property(lambda self: self.get_key(VALUE))
  185. list_key = property(lambda self: self.get_key(LIST))
  186. rev_key = property(lambda self: self.get_key(REV))
  187. graph_key = property(lambda self: self.get_key(GRAPH))
  188. def add_term(
  189. self,
  190. name: str,
  191. idref: str,
  192. coercion: Union[Defined, str] = UNDEF,
  193. container: Union[Collection[Any], str, Defined] = UNDEF,
  194. index: Optional[Union[str, Defined]] = None,
  195. language: Optional[Union[str, Defined]] = UNDEF,
  196. reverse: bool = False,
  197. context: Any = UNDEF,
  198. prefix: Optional[bool] = None,
  199. protected: bool = False,
  200. ):
  201. if self.version < 1.1 or prefix is None:
  202. prefix = isinstance(idref, str) and idref.endswith(URI_GEN_DELIMS)
  203. if not self._accept_term(name):
  204. return
  205. if self.version >= 1.1:
  206. existing = self.terms.get(name)
  207. if existing and existing.protected:
  208. return
  209. if isinstance(container, (list, set, tuple)):
  210. container = set(container)
  211. elif container is not UNDEF:
  212. container = set([container])
  213. else:
  214. container = set()
  215. term = Term(
  216. idref,
  217. name,
  218. coercion,
  219. container,
  220. index,
  221. language,
  222. reverse,
  223. context,
  224. prefix,
  225. protected,
  226. )
  227. self.terms[name] = term
  228. container_key: Union[Defined, str]
  229. for container_key in (LIST, LANG, SET): # , INDEX, ID, GRAPH):
  230. if container_key in container:
  231. break
  232. else:
  233. container_key = UNDEF
  234. self._lookup[(idref, coercion or language, container_key, reverse)] = term
  235. if term.prefix is True:
  236. self._prefixes[idref] = name
  237. def find_term(
  238. self,
  239. idref: str,
  240. coercion: Optional[Union[str, Defined]] = None,
  241. container: Union[Defined, str] = UNDEF,
  242. language: Optional[str] = None,
  243. reverse: bool = False,
  244. ):
  245. lu = self._lookup
  246. if coercion is None:
  247. coercion = language
  248. if coercion is not UNDEF and container:
  249. found = lu.get((idref, coercion, container, reverse))
  250. if found:
  251. return found
  252. if coercion is not UNDEF:
  253. found = lu.get((idref, coercion, UNDEF, reverse))
  254. if found:
  255. return found
  256. if container:
  257. found = lu.get((idref, coercion, container, reverse))
  258. if found:
  259. return found
  260. elif language:
  261. found = lu.get((idref, UNDEF, LANG, reverse))
  262. if found:
  263. return found
  264. else:
  265. found = lu.get((idref, coercion or UNDEF, SET, reverse))
  266. if found:
  267. return found
  268. return lu.get((idref, UNDEF, UNDEF, reverse))
  269. def resolve(self, curie_or_iri: str) -> str:
  270. iri = self.expand(curie_or_iri, False)
  271. # type error: Argument 1 to "isblank" of "Context" has incompatible type "Optional[str]"; expected "str"
  272. if self.isblank(iri): # type: ignore[arg-type]
  273. # type error: Incompatible return value type (got "Optional[str]", expected "str")
  274. return iri # type: ignore[return-value]
  275. # type error: Unsupported right operand type for in ("Optional[str]")
  276. if " " in iri: # type: ignore[operator]
  277. return ""
  278. # type error: Argument 1 to "resolve_iri" of "Context" has incompatible type "Optional[str]"; expected "str"
  279. return self.resolve_iri(iri) # type: ignore[arg-type]
  280. def resolve_iri(self, iri: str) -> str:
  281. # type error: Argument 1 to "norm_url" has incompatible type "Optional[str]"; expected "str"
  282. return norm_url(self._base, iri) # type: ignore[arg-type]
  283. def isblank(self, ref: str) -> bool:
  284. return ref.startswith("_:")
  285. def expand(self, term_curie_or_iri: Any, use_vocab: bool = True) -> Optional[str]:
  286. if not isinstance(term_curie_or_iri, str):
  287. return term_curie_or_iri
  288. if not self._accept_term(term_curie_or_iri):
  289. return ""
  290. if use_vocab:
  291. term = self.terms.get(term_curie_or_iri)
  292. if term:
  293. return term.id
  294. is_term, pfx, local = self._prep_expand(term_curie_or_iri)
  295. if pfx == "_":
  296. return term_curie_or_iri
  297. if pfx is not None:
  298. ns = self.terms.get(pfx)
  299. if ns and ns.prefix and ns.id:
  300. return ns.id + local
  301. elif is_term and use_vocab:
  302. if self.vocab:
  303. return self.vocab + term_curie_or_iri
  304. return None
  305. return self.resolve_iri(term_curie_or_iri)
  306. def shrink_iri(self, iri: str) -> str:
  307. ns, name = split_iri(str(iri))
  308. pfx = self._prefixes.get(ns)
  309. if pfx:
  310. # type error: Argument 1 to "join" of "str" has incompatible type "Tuple[Any, Optional[str]]"; expected "Iterable[str]"
  311. return ":".join((pfx, name)) # type: ignore[arg-type]
  312. elif self._base:
  313. if str(iri) == self._base:
  314. return ""
  315. # type error: Argument 1 to "startswith" of "str" has incompatible type "Optional[str]"; expected "Union[str, Tuple[str, ...]]"
  316. elif iri.startswith(self._basedomain): # type: ignore[arg-type]
  317. # type error: Argument 1 to "len" has incompatible type "Optional[str]"; expected "Sized"
  318. return iri[len(self._basedomain) :] # type: ignore[arg-type]
  319. return iri
  320. def to_symbol(self, iri: str) -> Optional[str]:
  321. iri = str(iri)
  322. term = self.find_term(iri)
  323. if term:
  324. return term.name
  325. ns, name = split_iri(iri)
  326. if ns == self.vocab:
  327. return name
  328. pfx = self._prefixes.get(ns)
  329. if pfx:
  330. # type error: Argument 1 to "join" of "str" has incompatible type "Tuple[Any, Optional[str]]"; expected "Iterable[str]"
  331. return ":".join((pfx, name)) # type: ignore[arg-type]
  332. return iri
  333. def load(
  334. self,
  335. source: _ContextSourceType,
  336. base: Optional[str] = None,
  337. referenced_contexts: Set[Any] = None,
  338. ):
  339. self.active = True
  340. sources: List[Tuple[Optional[str], Union[Dict[str, Any], str, None]]] = []
  341. # "Union[List[Union[Dict[str, Any], str]], List[Dict[str, Any]], List[str]]" : expression
  342. # "Union[List[Dict[str, Any]], Dict[str, Any], List[str], str]" : variable
  343. source = source if isinstance(source, list) else [source]
  344. referenced_contexts = referenced_contexts or set()
  345. self._prep_sources(base, source, sources, referenced_contexts)
  346. for source_url, source in sources:
  347. if source is None:
  348. self._clear()
  349. else:
  350. # type error: Argument 1 to "_read_source" of "Context" has incompatible type "Union[Dict[str, Any], str]"; expected "Dict[str, Any]"
  351. self._read_source(source, source_url, referenced_contexts) # type: ignore[arg-type]
  352. def _accept_term(self, key: str) -> bool:
  353. if self.version < 1.1:
  354. return True
  355. if key and len(key) > 1 and key[0] == "@" and key[1].isalnum():
  356. return key in NODE_KEYS
  357. else:
  358. return True
  359. def _prep_sources(
  360. self,
  361. base: Optional[str],
  362. inputs: Union[List[Union[Dict[str, Any], str, None]], List[str]],
  363. sources: List[Tuple[Optional[str], Union[Dict[str, Any], str, None]]],
  364. referenced_contexts: Set[str],
  365. in_source_url: Optional[str] = None,
  366. ):
  367. for source in inputs:
  368. source_url = in_source_url
  369. new_base = base
  370. if isinstance(source, str):
  371. source_url = source
  372. source_doc_base = base or self.doc_base
  373. new_ctx = self._fetch_context(
  374. source, source_doc_base, referenced_contexts
  375. )
  376. if new_ctx is None:
  377. continue
  378. else:
  379. if base:
  380. if TYPE_CHECKING:
  381. # if base is not None, then source_doc_base won't be
  382. # none due to how it is assigned.
  383. assert source_doc_base is not None
  384. new_base = urljoin(source_doc_base, source_url)
  385. source = new_ctx
  386. if isinstance(source, dict):
  387. if CONTEXT in source:
  388. source = source[CONTEXT]
  389. # type ignore: Incompatible types in assignment (expression has type "List[Union[Dict[str, Any], str, None]]", variable has type "Union[Dict[str, Any], str, None]")
  390. source = source if isinstance(source, list) else [source] # type: ignore[assignment]
  391. if isinstance(source, list):
  392. # type error: Statement is unreachable
  393. self._prep_sources( # type: ignore[unreachable]
  394. new_base, source, sources, referenced_contexts, source_url
  395. )
  396. else:
  397. sources.append((source_url, source))
  398. def _fetch_context(
  399. self, source: str, base: Optional[str], referenced_contexts: Set[str]
  400. ):
  401. # type error: Value of type variable "AnyStr" of "urljoin" cannot be "Optional[str]"
  402. source_url = urljoin(base, source) # type: ignore[type-var]
  403. if source_url in referenced_contexts:
  404. raise RECURSIVE_CONTEXT_INCLUSION
  405. # type error: Argument 1 to "add" of "set" has incompatible type "Optional[str]"; expected "str"
  406. referenced_contexts.add(source_url) # type: ignore[arg-type]
  407. if source_url in self._context_cache:
  408. return self._context_cache[source_url]
  409. # type error: Incompatible types in assignment (expression has type "Optional[Any]", variable has type "str")
  410. source_json, _ = source_to_json(source_url)
  411. if source_json and CONTEXT not in source_json:
  412. raise INVALID_REMOTE_CONTEXT
  413. # type error: Invalid index type "Optional[str]" for "Dict[str, Any]"; expected type "str"
  414. self._context_cache[source_url] = source_json # type: ignore[index]
  415. return source_json
  416. def _read_source(
  417. self,
  418. source: Dict[str, Any],
  419. source_url: Optional[str] = None,
  420. referenced_contexts: Optional[Set[str]] = None,
  421. ):
  422. imports = source.get(IMPORT)
  423. if imports:
  424. if not isinstance(imports, str):
  425. raise INVALID_CONTEXT_ENTRY
  426. imported = self._fetch_context(
  427. imports, self.base, referenced_contexts or set()
  428. )
  429. if not isinstance(imported, dict):
  430. raise INVALID_CONTEXT_ENTRY
  431. imported = imported[CONTEXT]
  432. imported.update(source)
  433. source = imported
  434. self.vocab = source.get(VOCAB, self.vocab)
  435. self.version = source.get(VERSION, self.version)
  436. protected = source.get(PROTECTED, False)
  437. for key, value in source.items():
  438. if key in {VOCAB, VERSION, IMPORT, PROTECTED}:
  439. continue
  440. elif key == PROPAGATE and isinstance(value, bool):
  441. self.propagate = value
  442. elif key == LANG:
  443. self.language = value
  444. elif key == BASE:
  445. if not source_url and not imports:
  446. self.base = value
  447. else:
  448. self._read_term(source, key, value, protected)
  449. def _read_term(
  450. self,
  451. source: Dict[str, Any],
  452. name: str,
  453. dfn: Union[Dict[str, Any], str],
  454. protected: bool = False,
  455. ) -> None:
  456. idref = None
  457. if isinstance(dfn, dict):
  458. # term = self._create_term(source, key, value)
  459. rev = dfn.get(REV)
  460. protected = dfn.get(PROTECTED, protected)
  461. coercion = dfn.get(TYPE, UNDEF)
  462. if coercion and coercion not in (ID, TYPE, VOCAB):
  463. coercion = self._rec_expand(source, coercion)
  464. idref = rev or dfn.get(ID, UNDEF)
  465. if idref == TYPE:
  466. idref = str(RDF.type)
  467. coercion = VOCAB
  468. elif idref is not UNDEF:
  469. idref = self._rec_expand(source, idref)
  470. elif ":" in name:
  471. idref = self._rec_expand(source, name)
  472. elif self.vocab:
  473. idref = self.vocab + name
  474. context = dfn.get(CONTEXT, UNDEF)
  475. self.add_term(
  476. name,
  477. idref,
  478. coercion,
  479. dfn.get(CONTAINER, UNDEF),
  480. dfn.get(INDEX, UNDEF),
  481. dfn.get(LANG, UNDEF),
  482. bool(rev),
  483. context,
  484. dfn.get(PREFIX),
  485. protected=protected,
  486. )
  487. else:
  488. if isinstance(dfn, str):
  489. if not self._accept_term(dfn):
  490. return
  491. idref = self._rec_expand(source, dfn)
  492. # type error: Argument 2 to "add_term" of "Context" has incompatible type "Optional[str]"; expected "str"
  493. self.add_term(name, idref, protected=protected) # type: ignore[arg-type]
  494. if idref in NODE_KEYS:
  495. self._alias.setdefault(idref, []).append(name)
  496. else:
  497. # undo aliases that may have been inherited from parent context
  498. for v in self._alias.values():
  499. if name in v:
  500. v.remove(name)
  501. def _rec_expand(
  502. self, source: Dict[str, Any], expr: Optional[str], prev: Optional[str] = None
  503. ) -> Optional[str]:
  504. if expr == prev or expr in NODE_KEYS:
  505. return expr
  506. nxt: Optional[str]
  507. # type error: Argument 1 to "_prep_expand" of "Context" has incompatible type "Optional[str]"; expected "str"
  508. is_term, pfx, nxt = self._prep_expand(expr) # type: ignore[arg-type]
  509. if pfx:
  510. iri = self._get_source_id(source, pfx)
  511. if iri is None:
  512. if pfx + ":" == self.vocab:
  513. return expr
  514. else:
  515. term = self.terms.get(pfx)
  516. if term:
  517. iri = term.id
  518. if iri is None:
  519. nxt = expr
  520. else:
  521. nxt = iri + nxt
  522. else:
  523. nxt = self._get_source_id(source, nxt) or nxt
  524. if ":" not in nxt and self.vocab:
  525. return self.vocab + nxt
  526. return self._rec_expand(source, nxt, expr)
  527. def _prep_expand(self, expr: str) -> Tuple[bool, Optional[str], str]:
  528. if ":" not in expr:
  529. return True, None, expr
  530. pfx, local = expr.split(":", 1)
  531. if not local.startswith("//"):
  532. return False, pfx, local
  533. else:
  534. return False, None, expr
  535. def _get_source_id(self, source: Dict[str, Any], key: str) -> Optional[str]:
  536. # .. from source dict or if already defined
  537. term = source.get(key)
  538. if term is None:
  539. dfn = self.terms.get(key)
  540. if dfn:
  541. term = dfn.id
  542. elif isinstance(term, dict):
  543. term = term.get(ID)
  544. return term
  545. def _term_dict(self, term: Term) -> Union[Dict[str, Any], str]:
  546. tdict: Dict[str, Any] = {}
  547. if term.type != UNDEF:
  548. tdict[TYPE] = self.shrink_iri(term.type)
  549. if term.container:
  550. tdict[CONTAINER] = list(term.container)
  551. if term.language != UNDEF:
  552. tdict[LANG] = term.language
  553. if term.reverse:
  554. tdict[REV] = term.id
  555. else:
  556. tdict[ID] = term.id
  557. if tdict.keys() == {ID}:
  558. return tdict[ID]
  559. return tdict
  560. def to_dict(self) -> Dict[str, Any]:
  561. """
  562. Returns a dictionary representation of the context that can be
  563. serialized to JSON.
  564. Returns:
  565. a dictionary representation of the context.
  566. """
  567. r = {v: k for (k, v) in self._prefixes.items()}
  568. r.update({term.name: self._term_dict(term) for term in self._lookup.values()})
  569. if self.base:
  570. r[BASE] = self.base
  571. if self.language:
  572. r[LANG] = self.language
  573. return r
  574. Term = namedtuple(
  575. "Term",
  576. "id, name, type, container, index, language, reverse, context," "prefix, protected",
  577. )
  578. Term.__new__.__defaults__ = (UNDEF, UNDEF, UNDEF, UNDEF, False, UNDEF, False, False)