pyegeria 5.4.0.dev10__py3-none-any.whl → 5.4.0.dev12__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.
Files changed (30) hide show
  1. commands/cat/debug_log +6290 -6042
  2. commands/cat/debug_log.2025-07-01_15-22-20_102237.zip +0 -0
  3. commands/cat/debug_log.2025-07-04_15-43-28_460900.zip +0 -0
  4. commands/cat/debug_log.2025-07-06_20-48-04_338314.zip +0 -0
  5. commands/cat/debug_log.2025-07-09_10-17-09_526262.zip +0 -0
  6. commands/cat/dr_egeria_md.py +24 -14
  7. commands/cat/list_collections.py +11 -4
  8. md_processing/__init__.py +3 -1
  9. md_processing/data/commands.json +7842 -2231
  10. md_processing/md_commands/data_designer_commands.py +67 -80
  11. md_processing/md_commands/glossary_commands.py +3 -1
  12. md_processing/md_commands/product_manager_commands.py +1746 -0
  13. md_processing/md_commands/solution_architect_commands.py +390 -236
  14. md_processing/md_processing_utils/common_md_proc_utils.py +8 -6
  15. md_processing/md_processing_utils/md_processing_constants.py +25 -4
  16. pyegeria/__init__.py +1 -0
  17. pyegeria/_client.py +0 -1
  18. pyegeria/collection_manager_omvs.py +504 -546
  19. pyegeria/data_designer_omvs.py +16 -8
  20. pyegeria/egeria_tech_client.py +9 -25
  21. pyegeria/governance_officer_omvs.py +1446 -1343
  22. pyegeria/output_formatter.py +96 -8
  23. pyegeria/solution_architect_omvs.py +2278 -1728
  24. {pyegeria-5.4.0.dev10.dist-info → pyegeria-5.4.0.dev12.dist-info}/METADATA +1 -1
  25. {pyegeria-5.4.0.dev10.dist-info → pyegeria-5.4.0.dev12.dist-info}/RECORD +28 -25
  26. commands/cat/debug_log.2025-06-24_15-51-28_769553.zip +0 -0
  27. commands/cat/debug_log.2025-06-26_11-18-40_644650.zip +0 -0
  28. {pyegeria-5.4.0.dev10.dist-info → pyegeria-5.4.0.dev12.dist-info}/LICENSE +0 -0
  29. {pyegeria-5.4.0.dev10.dist-info → pyegeria-5.4.0.dev12.dist-info}/WHEEL +0 -0
  30. {pyegeria-5.4.0.dev10.dist-info → pyegeria-5.4.0.dev12.dist-info}/entry_points.txt +0 -0
@@ -2,7 +2,8 @@
2
2
  This file contains term-related object_action functions for processing Egeria Markdown
3
3
  """
4
4
  import json
5
- import sys, os
5
+ import os
6
+ import sys
6
7
  from typing import Optional
7
8
 
8
9
  from loguru import logger
@@ -16,8 +17,6 @@ from md_processing.md_processing_utils.extraction_utils import (extract_command_
16
17
  from md_processing.md_processing_utils.md_processing_constants import (load_commands, ERROR)
17
18
  from pyegeria import DEBUG_LEVEL, body_slimmer
18
19
  from pyegeria.egeria_tech_client import EgeriaTech
19
- from pyegeria.output_formatter import (extract_mermaid_only, extract_basic_dict, generate_output)
20
-
21
20
 
22
21
  GERIA_METADATA_STORE = os.environ.get("EGERIA_METADATA_STORE", "active-metadata-store")
23
22
  EGERIA_KAFKA_ENDPOINT = os.environ.get("KAFKA_ENDPOINT", "localhost:9092")
@@ -45,7 +44,7 @@ console = Console(width=int(200))
45
44
 
46
45
  log_format = "D {time} | {level} | {function} | {line} | {message} | {extra}"
47
46
  logger.remove()
48
- logger.add(sys.stderr, level="ERROR", format=log_format, colorize=True)
47
+ logger.add(sys.stderr, level="INFO", format=log_format, colorize=True)
49
48
  full_file_path = os.path.join(EGERIA_ROOT_PATH, EGERIA_INBOX_PATH, "data_designer_debug.log")
50
49
  # logger.add(full_file_path, rotation="1 day", retention="1 week", compression="zip", level="TRACE", format=log_format,
51
50
  # colorize=True)
@@ -63,10 +62,8 @@ def add_member_to_data_collections(egeria_client: EgeriaTech, collection_list: l
63
62
  Add member to data dictionaries and data specifications.
64
63
  """
65
64
  body = {
66
- "class": "RelationshipRequestBody",
67
- "properties": {
68
- "class": "CollectionMembershipProperties",
69
- "membershipRationale": "User Specified",
65
+ "class": "RelationshipRequestBody", "properties": {
66
+ "class": "CollectionMembershipProperties", "membershipRationale": "User Specified",
70
67
  "notes": "Added by Dr.Egeria"
71
68
  }
72
69
  }
@@ -204,7 +201,6 @@ def sync_data_field_rel_elements(egeria_client: EgeriaTech, structure_list: list
204
201
  logger.warning("Unexpected -> the list was None - assigning empty list")
205
202
  rel_el_list = {}
206
203
 
207
-
208
204
  as_is_data_structs = set(rel_el_list.get("data_structure_guids", []))
209
205
  as_is_parent_fields = set(rel_el_list.get("parent_guids", []))
210
206
  as_is_assigned_meanings = set(rel_el_list.get("assigned_meanings_guids", []))
@@ -273,10 +269,8 @@ def sync_data_field_rel_elements(egeria_client: EgeriaTech, structure_list: list
273
269
  if len(terms_to_remove) > 0:
274
270
  for dc in classes_to_remove:
275
271
  body = {
276
- "class": "MetadataSourceRequestBody",
277
- "forLineage": False,
278
- "forDuplicateProcessing": False
279
- }
272
+ "class": "MetadataSourceRequestBody", "forLineage": False, "forDuplicateProcessing": False
273
+ }
280
274
  egeria_client.detach_data_class_definition(guid, dc, body)
281
275
  msg = f"Removed `{dc}` from `{display_name}`"
282
276
  logger.trace(msg)
@@ -285,9 +279,7 @@ def sync_data_field_rel_elements(egeria_client: EgeriaTech, structure_list: list
285
279
  if len(terms_to_add) > 0:
286
280
  for dc in classes_to_add:
287
281
  body = {
288
- "class": "RelationshipRequestBody",
289
- "forLineage": False,
290
- "forDuplicateProcessing": False
282
+ "class": "RelationshipRequestBody", "forLineage": False, "forDuplicateProcessing": False
291
283
  }
292
284
  egeria_client.link_data_class_definition(guid, dc, body)
293
285
  msg = f"Added `{dc}` to`{display_name}`"
@@ -350,7 +342,6 @@ def sync_data_class_rel_elements(egeria_client: EgeriaTech, containing_data_clas
350
342
  logger.trace(f"as_is_specialized_classes: {list(as_is_specialized_classes)} to_be_specizialized_data_classes: "
351
343
  f"{list(to_be_specialized_classes)}")
352
344
 
353
-
354
345
  nested_classes_to_remove = to_be_nested_classes - as_is_nested_classes
355
346
  logger.trace(f"nested_classes_to_remove: {list(nested_classes_to_remove)}")
356
347
  if len(nested_classes_to_remove) > 0:
@@ -386,9 +377,7 @@ def sync_data_class_rel_elements(egeria_client: EgeriaTech, containing_data_clas
386
377
  if len(terms_to_remove) > 0:
387
378
  for dc in specialized_classes_to_remove:
388
379
  body = {
389
- "class": "MetadataSourceRequestBody",
390
- "forLineage": False,
391
- "forDuplicateProcessing": False
380
+ "class": "MetadataSourceRequestBody", "forLineage": False, "forDuplicateProcessing": False
392
381
  }
393
382
  egeria_client.detach_specialist_data_class(guid, dc, body)
394
383
  msg = f"Removed `{dc}` from `{display_name}`"
@@ -398,9 +387,7 @@ def sync_data_class_rel_elements(egeria_client: EgeriaTech, containing_data_clas
398
387
  if len(specialized_classes_to_add) > 0:
399
388
  for dc in specialized_classes_to_add:
400
389
  body = {
401
- "class": "RelationshipRequestBody",
402
- "forLineage": False,
403
- "forDuplicateProcessing": False
390
+ "class": "RelationshipRequestBody", "forLineage": False, "forDuplicateProcessing": False
404
391
  }
405
392
  egeria_client.link_specialist_data_class(guid, dc, body)
406
393
  msg = f"Added `{dc}` to`{display_name}`"
@@ -426,8 +413,6 @@ def sync_data_class_rel_elements(egeria_client: EgeriaTech, containing_data_clas
426
413
  logger.trace(msg)
427
414
 
428
415
 
429
-
430
-
431
416
  @logger.catch
432
417
  def process_data_spec_upsert_command(egeria_client: EgeriaTech, txt: str, directive: str = "display") -> Optional[str]:
433
418
  """
@@ -469,8 +454,7 @@ def process_data_spec_upsert_command(egeria_client: EgeriaTech, txt: str, direct
469
454
  is_own_anchor = True
470
455
 
471
456
  collection_type = attributes.get('Collection Type', {}).get('value', None)
472
- collection_ordering = attributes.get('Collection Ordering', {}).get('value', None)
473
- order_property_name = attributes.get('Order Property Name', {}).get('value', None)
457
+
474
458
  replace_all_props = not attributes.get('Merge Update', {}).get('value', True)
475
459
 
476
460
  additional_prop = attributes.get('Additional Properties', {}).get('value', None)
@@ -508,8 +492,8 @@ def process_data_spec_upsert_command(egeria_client: EgeriaTech, txt: str, direct
508
492
  f"==> Validation of {command} completed successfully! Proceeding to apply the changes.\n"))
509
493
 
510
494
  egeria_client.update_collection(guid, qualified_name, display_name, description, collection_type,
511
- collection_ordering, order_property_name, replace_all_props,
512
- additional_properties, extended_properties)
495
+ additional_properties,
496
+ extended_properties, replace_all_props)
513
497
  logger.success(f"Updated {object_type} `{display_name}` with GUID {guid}\n\n___")
514
498
  update_element_dictionary(qualified_name, {
515
499
  'guid': guid, 'display_name': display_name
@@ -530,10 +514,10 @@ def process_data_spec_upsert_command(egeria_client: EgeriaTech, txt: str, direct
530
514
  logger.error(msg)
531
515
  return None
532
516
  else:
533
- guid = egeria_client.create_data_spec_collection(display_name, description,
534
- is_own_anchor, anchor_guid, parent_guid, parent_relationship_type_name,
535
- parent_at_end1, collection_type,
536
- anchor_scope_guid, collection_ordering,order_property_name,
517
+ guid = egeria_client.create_data_spec_collection(display_name, description, qualified_name,
518
+ is_own_anchor, anchor_guid, parent_guid,
519
+ parent_relationship_type_name, parent_at_end1,
520
+ collection_type, anchor_scope_guid,
537
521
  additional_properties, extended_properties)
538
522
  if guid:
539
523
  update_element_dictionary(qualified_name, {
@@ -595,8 +579,6 @@ def process_data_dict_upsert_command(egeria_client: EgeriaTech, txt: str, direct
595
579
  if parent_guid is None:
596
580
  is_own_anchor = True
597
581
  collection_type = attributes.get('Collection Type', {}).get('value', None)
598
- collection_ordering = attributes.get('Collection Ordering', {}).get('value', None)
599
- order_property_name = attributes.get('Order Property Name', {}).get('value', None)
600
582
  replace_all_props = not attributes.get('Merge Update', {}).get('value', True)
601
583
 
602
584
  additional_prop = attributes.get('Additional Properties', {}).get('value', None)
@@ -630,8 +612,8 @@ def process_data_dict_upsert_command(egeria_client: EgeriaTech, txt: str, direct
630
612
  f"==> Validation of {command} completed successfully! Proceeding to apply the changes."))
631
613
 
632
614
  egeria_client.update_collection(guid, qualified_name, display_name, description, collection_type,
633
- collection_ordering, order_property_name, replace_all_props,
634
- additional_properties, extended_properties)
615
+ additional_properties,
616
+ extended_properties, replace_all_props)
635
617
  logger.success(f"Updated {object_type} `{display_name}` with GUID {guid}\n\n___")
636
618
  update_element_dictionary(qualified_name, {
637
619
  'guid': guid, 'display_name': display_name
@@ -644,11 +626,11 @@ def process_data_dict_upsert_command(egeria_client: EgeriaTech, txt: str, direct
644
626
  f"`Create` to `Update` in processed output\n\n___")
645
627
  return update_a_command(txt, object_action, object_type, qualified_name, guid)
646
628
  else:
647
- guid = egeria_client.create_data_dictionary_collection(display_name,description, is_own_anchor, anchor_guid,
648
- parent_guid, parent_relationship_type_name,
629
+ guid = egeria_client.create_data_dictionary_collection(display_name, description, qualified_name,
630
+ is_own_anchor, anchor_guid, parent_guid,
631
+ parent_relationship_type_name,
649
632
  parent_at_end1, collection_type,
650
- anchor_scope_guid, collection_ordering,
651
- order_property_name, additional_properties,
633
+ anchor_scope_guid, additional_properties,
652
634
  extended_properties)
653
635
  if guid:
654
636
  update_element_dictionary(qualified_name, {
@@ -1054,8 +1036,7 @@ def process_data_field_upsert_command(egeria_client: EgeriaTech, txt: str, direc
1054
1036
  else:
1055
1037
  # First lets create the data field
1056
1038
  body = {
1057
- "class": "NewElementRequestBody",
1058
- "properties": {
1039
+ "class": "NewElementRequestBody", "properties": {
1059
1040
  "class": "DataFieldProperties", "qualifiedName": qualified_name,
1060
1041
  "displayName": display_name, "namespace": namespace, "description": description,
1061
1042
  "versionIdentifier": version_id, "aliases": aliases, "namePatterns": name_patterns,
@@ -1122,13 +1103,10 @@ def process_data_field_upsert_command(egeria_client: EgeriaTech, txt: str, direc
1122
1103
  # Link data class
1123
1104
  if data_class:
1124
1105
  body = {
1125
- "class": "RelationshipRequestBody",
1126
- "externalSourceGUID": external_source_guid,
1127
- "externalSourceName": external_source_name,
1128
- "effectiveTime": effective_time,
1129
- "forLineage": for_lineage,
1130
- "forDuplicateProcessing": for_duplicate_processing
1131
- }
1106
+ "class": "RelationshipRequestBody", "externalSourceGUID": external_source_guid,
1107
+ "externalSourceName": external_source_name, "effectiveTime": effective_time,
1108
+ "forLineage": for_lineage, "forDuplicateProcessing": for_duplicate_processing
1109
+ }
1132
1110
  egeria_client.link_data_class_definition(guid, data_class_guid, body)
1133
1111
  msg = f"Adding data class `{data_class}` to data field {display_name}"
1134
1112
  logger.info(msg)
@@ -1252,7 +1230,6 @@ def process_data_class_upsert_command(egeria_client: EgeriaTech, txt: str, direc
1252
1230
 
1253
1231
  glossary_term_guid = attributes.get('Glossary Term', {}).get('guid', None)
1254
1232
 
1255
-
1256
1233
  in_data_dictionary = attributes.get('In Data Dictionary', {}).get('value', None)
1257
1234
  in_data_dictionary_names = attributes.get('In Data Dictionary', {}).get('name_list', None)
1258
1235
  data_dict_guid_list = attributes.get("In Data Dictionary", {}).get("guid_list", None)
@@ -1321,8 +1298,7 @@ def process_data_class_upsert_command(egeria_client: EgeriaTech, txt: str, direc
1321
1298
 
1322
1299
  # Sync data field related elements (data structure, parent data fields, terms, data classes
1323
1300
  sync_data_class_rel_elements(egeria_client, containing_data_class_guids, glossary_term_guid,
1324
- specializes_data_class_guid, guid, display_name,
1325
- replace_all_props)
1301
+ specializes_data_class_guid, guid, display_name, replace_all_props)
1326
1302
 
1327
1303
  core_props += f"\n\n## Glossary Term \n\n{glossary_term}\n\n"
1328
1304
  core_props += f"\n\n## Containing Data Class\n\n{containing_data_class_names}\n\n"
@@ -1341,8 +1317,7 @@ def process_data_class_upsert_command(egeria_client: EgeriaTech, txt: str, direc
1341
1317
  else:
1342
1318
  # First lets create the data class
1343
1319
  body = {
1344
- "class": "NewElementRequestBody",
1345
- "properties": {
1320
+ "class": "NewElementRequestBody", "properties": {
1346
1321
  "class": "DataClassProperties", "qualifiedName": qualified_name,
1347
1322
  "displayName": display_name, "description": description, "namespace": namespace,
1348
1323
  "matchPropertyNames": match_property_names, "matchThreshold": match_threshold,
@@ -1405,7 +1380,8 @@ def process_data_class_upsert_command(egeria_client: EgeriaTech, txt: str, direc
1405
1380
 
1406
1381
 
1407
1382
  @logger.catch
1408
- def process_data_collection_list_command(egeria_client: EgeriaTech, txt: str, directive: str = "display") -> Optional[str]:
1383
+ def process_data_collection_list_command(egeria_client: EgeriaTech, txt: str, directive: str = "display") -> Optional[
1384
+ str]:
1409
1385
  """
1410
1386
  Processes a Data Dictionary list object_action by extracting key attributes such as
1411
1387
  search string from the given text.
@@ -1425,12 +1401,24 @@ def process_data_collection_list_command(egeria_client: EgeriaTech, txt: str, di
1425
1401
 
1426
1402
  parsed_output = parse_view_command(egeria_client, object_type, object_action, txt, directive)
1427
1403
 
1428
- attributes = parsed_output['attributes']
1404
+
1429
1405
 
1430
1406
  valid = parsed_output['valid']
1431
1407
  print(Markdown(f"Performing {command}"))
1432
1408
  print(Markdown(parsed_output['display']))
1433
1409
 
1410
+ attr = parsed_output.get('attributes',{})
1411
+ effective_time = attr.get('effectiveTime', {}).get('value', None)
1412
+ as_of_time = attr.get('asOfTime', {}).get('value', None)
1413
+ for_duplicate_processing = attr.get('forDuplicateProcessing', {}).get('value', False)
1414
+ for_lineage = attr.get('forLineage',{}).get('value', False)
1415
+ limit_result_by_status = attr.get('limitResultsByStatus',{}).get('value', ['ACTIVE'])
1416
+ sequencing_property = attr.get('sequencingProperty',{}).get('value',"qualifiedName" )
1417
+ sequencing_order = attr.get('sequencingOrder',{}).get('value', "PROPERTY_ASCENDING")
1418
+ search_string = attr.get('Search String', {}).get('value', '*')
1419
+ output_format = attr.get('Output Format', {}).get('value', 'LIST')
1420
+ detailed = attr.get('Detailed', {}).get('value', False)
1421
+
1434
1422
  if directive == "display":
1435
1423
  return None
1436
1424
  elif directive == "validate":
@@ -1442,11 +1430,6 @@ def process_data_collection_list_command(egeria_client: EgeriaTech, txt: str, di
1442
1430
  return valid
1443
1431
 
1444
1432
  elif directive == "process":
1445
- attributes = parsed_output['attributes']
1446
- search_string = attributes.get('Search String', {}).get('value', '*')
1447
- output_format = attributes.get('Output Format', {}).get('value', 'LIST')
1448
- detailed = attributes.get('Detailed', {}).get('value', False)
1449
-
1450
1433
  try:
1451
1434
  if not valid: # First validate the command before we process it
1452
1435
  msg = f"Validation failed for {object_action} `{object_type}`\n"
@@ -1454,11 +1437,19 @@ def process_data_collection_list_command(egeria_client: EgeriaTech, txt: str, di
1454
1437
  return None
1455
1438
 
1456
1439
  list_md = f"\n# `{col_type}` with filter: `{search_string}`\n\n"
1457
- if search_string == "*":
1458
- struct = egeria_client.get_classified_collections(col_type, output_format=output_format)
1459
- else:
1460
- struct = egeria_client.find_collections(search_string, output_format=output_format)
1440
+ body = {
1441
+ "class": "FilterRequestBody",
1442
+ "asOfTime": as_of_time,
1443
+ "effectiveTime": effective_time,
1444
+ "forLineage": for_lineage,
1445
+ "forDuplicateProcessing": for_duplicate_processing,
1446
+ "limitResultsByStatus": limit_result_by_status,
1447
+ "sequencingOrder": sequencing_order,
1448
+ "sequencingProperty": sequencing_property,
1449
+ "filter": search_string,
1450
+ }
1461
1451
 
1452
+ struct = egeria_client.find_collections_w_body(body, col_type, output_format=output_format)
1462
1453
  if output_format == "DICT":
1463
1454
  list_md += f"```\n{json.dumps(struct, indent=4)}\n```\n"
1464
1455
  else:
@@ -1474,7 +1465,9 @@ def process_data_collection_list_command(egeria_client: EgeriaTech, txt: str, di
1474
1465
  else:
1475
1466
  return None
1476
1467
 
1477
- def process_data_structure_list_command(egeria_client: EgeriaTech, txt: str, directive: str = "display") -> Optional[str]:
1468
+
1469
+ def process_data_structure_list_command(egeria_client: EgeriaTech, txt: str, directive: str = "display") -> Optional[
1470
+ str]:
1478
1471
  """
1479
1472
  Processes a Data Dictionary list object_action by extracting key attributes such as
1480
1473
  search string from the given text.
@@ -1534,6 +1527,7 @@ def process_data_structure_list_command(egeria_client: EgeriaTech, txt: str, dir
1534
1527
  else:
1535
1528
  return None
1536
1529
 
1530
+
1537
1531
  def process_data_field_list_command(egeria_client: EgeriaTech, txt: str, directive: str = "display") -> Optional[str]:
1538
1532
  """
1539
1533
  Processes a Data Dictionary list object_action by extracting key attributes such as
@@ -1587,18 +1581,12 @@ def process_data_field_list_command(egeria_client: EgeriaTech, txt: str, directi
1587
1581
 
1588
1582
  list_md = f"\n# `{object_type}` with filter: `{search_string}`\n\n"
1589
1583
  body = {
1590
- "class": "FilterRequestBody",
1591
- "asOfTime": as_of_time,
1592
- "effectiveTime": effective_time,
1593
- "forLineage": False,
1594
- "forDuplicateProcessing" : False,
1595
- "limitResultsByStatus": ["ACTIVE"],
1596
- "sequencingOrder": sort_order,
1597
- "sequencingProperty": order_property,
1598
- "filter": search_string,
1584
+ "class": "FilterRequestBody", "asOfTime": as_of_time, "effectiveTime": effective_time,
1585
+ "forLineage": False, "forDuplicateProcessing": False, "limitResultsByStatus": ["ACTIVE"],
1586
+ "sequencingOrder": sort_order, "sequencingProperty": order_property, "filter": search_string,
1599
1587
  }
1600
- struct = egeria_client.find_data_fields_w_body(body, start_from, page_size, starts_with,
1601
- ends_with, ignore_case,output_format)
1588
+ struct = egeria_client.find_data_fields_w_body(body, start_from, page_size, starts_with, ends_with,
1589
+ ignore_case, output_format)
1602
1590
 
1603
1591
  if output_format == "DICT":
1604
1592
  list_md += f"```\n{json.dumps(struct, indent=4)}\n```\n"
@@ -1615,6 +1603,7 @@ def process_data_field_list_command(egeria_client: EgeriaTech, txt: str, directi
1615
1603
  else:
1616
1604
  return None
1617
1605
 
1606
+
1618
1607
  def process_data_class_list_command(egeria_client: EgeriaTech, txt: str, directive: str = "display") -> Optional[str]:
1619
1608
  """
1620
1609
  Processes a Data Dictionary list object_action by extracting key attributes such as
@@ -1674,5 +1663,3 @@ def process_data_class_list_command(egeria_client: EgeriaTech, txt: str, directi
1674
1663
  return None
1675
1664
  else:
1676
1665
  return None
1677
-
1678
-
@@ -527,7 +527,9 @@ def process_term_upsert_command(egeria_client: EgeriaTech, txt: str, directive:
527
527
  else:
528
528
  known_glossary_q_name, known_glossary_guid, glossary_valid, glossary_exists = process_element_identifiers(
529
529
  egeria_client, "Glossary", GLOSSARY_NAME_LABELS, txt, EXISTS_REQUIRED, None)
530
- # Todo - add logic to fail if no valid glossary provided.
530
+ if not glossary_exists or known_glossary_guid is None:
531
+ glossary_valid = False
532
+ valid = False
531
533
 
532
534
  # process categories, if present
533
535
  categories = process_simple_attribute(txt, ['Glossary Categories', 'Glossary Category', 'Category', 'Categories'])