cmem-cmemc 24.3.2__py3-none-any.whl → 25.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.
Files changed (44) hide show
  1. cmem_cmemc/__init__.py +1 -160
  2. cmem_cmemc/cli.py +138 -0
  3. cmem_cmemc/command.py +36 -0
  4. cmem_cmemc/commands/admin.py +7 -6
  5. cmem_cmemc/commands/client.py +4 -3
  6. cmem_cmemc/commands/config.py +3 -2
  7. cmem_cmemc/commands/dataset.py +18 -17
  8. cmem_cmemc/commands/graph.py +20 -22
  9. cmem_cmemc/commands/manual.py +56 -0
  10. cmem_cmemc/commands/metrics.py +11 -11
  11. cmem_cmemc/commands/migration.py +6 -3
  12. cmem_cmemc/commands/project.py +10 -10
  13. cmem_cmemc/commands/python.py +29 -2
  14. cmem_cmemc/commands/query.py +31 -14
  15. cmem_cmemc/commands/resource.py +4 -3
  16. cmem_cmemc/commands/scheduler.py +4 -4
  17. cmem_cmemc/commands/store.py +2 -2
  18. cmem_cmemc/commands/user.py +10 -9
  19. cmem_cmemc/commands/validation.py +2 -2
  20. cmem_cmemc/commands/variable.py +1 -1
  21. cmem_cmemc/commands/vocabulary.py +11 -10
  22. cmem_cmemc/commands/workflow.py +18 -18
  23. cmem_cmemc/completion.py +76 -49
  24. cmem_cmemc/config_parser.py +44 -0
  25. cmem_cmemc/context.py +166 -97
  26. cmem_cmemc/exceptions.py +15 -2
  27. cmem_cmemc/manual_helper/graph.py +2 -0
  28. cmem_cmemc/manual_helper/multi_page.py +1 -1
  29. cmem_cmemc/manual_helper/single_page.py +2 -0
  30. cmem_cmemc/migrations/abc.py +1 -0
  31. cmem_cmemc/migrations/remove_noop_triple_251.py +50 -0
  32. cmem_cmemc/migrations/shapes_widget_integrations_243.py +0 -5
  33. cmem_cmemc/migrations/sparql_query_texts_242.py +53 -0
  34. cmem_cmemc/object_list.py +3 -3
  35. cmem_cmemc/parameter_types/path.py +7 -0
  36. cmem_cmemc/placeholder.py +69 -0
  37. cmem_cmemc/utils.py +49 -15
  38. {cmem_cmemc-24.3.2.dist-info → cmem_cmemc-25.1.0.dist-info}/METADATA +14 -14
  39. cmem_cmemc-25.1.0.dist-info/RECORD +59 -0
  40. cmem_cmemc-25.1.0.dist-info/entry_points.txt +3 -0
  41. cmem_cmemc-24.3.2.dist-info/RECORD +0 -53
  42. cmem_cmemc-24.3.2.dist-info/entry_points.txt +0 -3
  43. {cmem_cmemc-24.3.2.dist-info → cmem_cmemc-25.1.0.dist-info}/LICENSE +0 -0
  44. {cmem_cmemc-24.3.2.dist-info → cmem_cmemc-25.1.0.dist-info}/WHEEL +0 -0
cmem_cmemc/completion.py CHANGED
@@ -3,11 +3,12 @@
3
3
  # ruff: noqa: ARG001
4
4
  import os
5
5
  import pathlib
6
+ from collections import OrderedDict
6
7
  from contextlib import suppress
7
8
  from typing import Any
8
9
 
9
10
  import requests.exceptions
10
- from click import Context
11
+ from click import ClickException, Context
11
12
  from click.parser import Argument, split_arg_string
12
13
  from click.shell_completion import CompletionItem
13
14
  from cmem.cmempy.dp.authorization.conditions import (
@@ -38,7 +39,8 @@ from cmem.cmempy.workspace.search import list_items
38
39
  from natsort import natsorted, ns
39
40
 
40
41
  from cmem_cmemc.constants import NS_ACL, NS_ACTION, NS_GROUP, NS_USER
41
- from cmem_cmemc.context import CONTEXT
42
+ from cmem_cmemc.context import ApplicationContext
43
+ from cmem_cmemc.placeholder import QueryPlaceholder, get_placeholders_for_query
42
44
  from cmem_cmemc.smart_path import SmartPath as Path
43
45
  from cmem_cmemc.utils import (
44
46
  convert_iri_to_qname,
@@ -47,6 +49,7 @@ from cmem_cmemc.utils import (
47
49
  struct_to_table,
48
50
  )
49
51
 
52
+ NOT_SORTED = -1
50
53
  SORT_BY_KEY = 0
51
54
  SORT_BY_DESC = 1
52
55
 
@@ -66,7 +69,7 @@ def finalize_completion(
66
69
  ----
67
70
  candidates (list): completion dictionary to filter
68
71
  incomplete (str): incomplete string at the cursor
69
- sort_by (str): SORT_BY_KEY or SORT_BY_DESC
72
+ sort_by (str): SORT_BY_KEY, SORT_BY_DESC or NOT_SORTED
70
73
  nat_sort (bool): if true, uses the natsort package for sorting
71
74
  reverse (bool): if true, sorts in reverse order
72
75
 
@@ -76,26 +79,30 @@ def finalize_completion(
76
79
 
77
80
  Raises:
78
81
  ------
79
- ValueError in case of wrong sort_by parameter
82
+ ClickException in case of wrong sort_by parameter
80
83
 
81
84
  """
82
- if sort_by not in (SORT_BY_KEY, SORT_BY_DESC):
83
- raise ValueError("sort_by should be 0 or 1.")
85
+ if sort_by not in (SORT_BY_KEY, SORT_BY_DESC, NOT_SORTED):
86
+ raise ClickException("sort_by should be -1, 0 or 1.")
84
87
  incomplete = incomplete.lower()
85
88
  if len(candidates) == 0:
86
89
  return candidates
87
- # remove duplicates
88
- candidates = list(set(candidates))
90
+ # remove duplicates (preserving the order)
91
+ candidates = list(OrderedDict.fromkeys(candidates))
92
+
89
93
  if isinstance(candidates[0], str):
90
94
  # list of strings filtering and sorting
91
95
  filtered_candidates = [
92
96
  element for element in candidates if element.lower().find(incomplete) != -1
93
97
  ]
98
+ if sort_by == NOT_SORTED:
99
+ return filtered_candidates
94
100
  if nat_sort:
95
101
  return natsorted(seq=filtered_candidates, alg=ns.IGNORECASE, reverse=reverse)
96
102
  # this solves that case-insensitive sorting is not stable in ordering
97
103
  # of "equal" keys (https://stackoverflow.com/a/57923460)
98
104
  return sorted(filtered_candidates, key=lambda x: (str(x).casefold(), x), reverse=reverse)
105
+
99
106
  if isinstance(candidates[0], tuple):
100
107
  # list of tuples filtering and sorting
101
108
  filtered_candidates = [
@@ -104,7 +111,9 @@ def finalize_completion(
104
111
  if str(element[0]).lower().find(incomplete) != -1
105
112
  or str(element[1]).lower().find(incomplete) != -1
106
113
  ]
107
- if nat_sort:
114
+ if sort_by == NOT_SORTED:
115
+ sorted_list = filtered_candidates
116
+ elif nat_sort:
108
117
  sorted_list = natsorted(
109
118
  seq=filtered_candidates,
110
119
  key=lambda k: k[sort_by],
@@ -121,7 +130,8 @@ def finalize_completion(
121
130
  CompletionItem(value=element[0].replace(":", r"\:"), help=element[1])
122
131
  for element in sorted_list
123
132
  ]
124
- raise ValueError(
133
+
134
+ raise ClickException(
125
135
  "Candidates should be a list of strings or a list of tuples." f" Got {candidates}"
126
136
  )
127
137
 
@@ -156,7 +166,7 @@ def add_metadata_parameter(list_: list | None = None) -> list:
156
166
 
157
167
  def acl_ids(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
158
168
  """Prepare a list of access condition identifier"""
159
- CONTEXT.set_connection_from_params(ctx.find_root().params)
169
+ ApplicationContext.set_connection_from_params(ctx.find_root().params)
160
170
  options = []
161
171
  acls = fetch_all_acls()
162
172
  for access_condition in acls:
@@ -170,7 +180,7 @@ def acl_ids(ctx: Context, param: Argument, incomplete: str) -> list[CompletionIt
170
180
 
171
181
  def acl_actions(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
172
182
  """Prepare a list of access condition actions"""
173
- CONTEXT.set_connection_from_params(ctx.find_root().params)
183
+ ApplicationContext.set_connection_from_params(ctx.find_root().params)
174
184
  options = []
175
185
  results = get_actions().json()
176
186
  for _ in results:
@@ -191,7 +201,7 @@ def acl_actions(ctx: Context, param: Argument, incomplete: str) -> list[Completi
191
201
 
192
202
  def acl_users(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
193
203
  """Prepare a list of access condition users"""
194
- CONTEXT.set_connection_from_params(ctx.find_root().params)
204
+ ApplicationContext.set_connection_from_params(ctx.find_root().params)
195
205
  options = []
196
206
  try:
197
207
  for _ in list_users():
@@ -212,7 +222,7 @@ def acl_users(ctx: Context, param: Argument, incomplete: str) -> list[Completion
212
222
 
213
223
  def acl_groups(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
214
224
  """Prepare a list of access condition groups"""
215
- CONTEXT.set_connection_from_params(ctx.find_root().params)
225
+ ApplicationContext.set_connection_from_params(ctx.find_root().params)
216
226
  options = []
217
227
  try:
218
228
  for _ in list_groups():
@@ -260,7 +270,7 @@ def add_read_only_and_uri_property_parameters(list_: list | None = None) -> list
260
270
 
261
271
  def dataset_parameter(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
262
272
  """Prepare a list of dataset parameters for a dataset type."""
263
- CONTEXT.set_connection_from_params(ctx.find_root().params)
273
+ ApplicationContext.set_connection_from_params(ctx.find_root().params)
264
274
  args = get_completion_args(incomplete)
265
275
  incomplete = incomplete.lower()
266
276
  # look if cursor is in value position of the -p option and
@@ -310,7 +320,7 @@ def dataset_parameter(ctx: Context, param: Argument, incomplete: str) -> list[Co
310
320
 
311
321
  def dataset_types(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
312
322
  """Prepare a list of dataset types."""
313
- CONTEXT.set_connection_from_params(ctx.find_root().params)
323
+ ApplicationContext.set_connection_from_params(ctx.find_root().params)
314
324
  incomplete = incomplete.lower()
315
325
  options = []
316
326
  plugins = get_task_plugins()
@@ -329,7 +339,7 @@ def dataset_types(ctx: Context, param: Argument, incomplete: str) -> list[Comple
329
339
 
330
340
  def dataset_ids(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
331
341
  """Prepare a list of projectid:datasetid dataset identifier."""
332
- CONTEXT.set_connection_from_params(ctx.find_root().params)
342
+ ApplicationContext.set_connection_from_params(ctx.find_root().params)
333
343
  results = list_items(item_type="dataset")
334
344
  datasets = results["results"]
335
345
  options = [(f"{_['projectId']}:{_['id']}", _["label"]) for _ in datasets]
@@ -368,14 +378,14 @@ def dataset_list_filter(ctx: Context, param: Argument, incomplete: str) -> list[
368
378
 
369
379
  def resource_ids(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
370
380
  """Prepare a list of projectid:resourceid resource identifier."""
371
- CONTEXT.set_connection_from_params(ctx.find_root().params)
381
+ ApplicationContext.set_connection_from_params(ctx.find_root().params)
372
382
  options = [_["id"] for _ in get_all_resources()]
373
383
  return finalize_completion(candidates=options, incomplete=incomplete, sort_by=SORT_BY_DESC)
374
384
 
375
385
 
376
386
  def scheduler_ids(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
377
387
  """Prepare a list of projectid:schedulerid scheduler identifier."""
378
- CONTEXT.set_connection_from_params(ctx.find_root().params)
388
+ ApplicationContext.set_connection_from_params(ctx.find_root().params)
379
389
  options = []
380
390
  schedulers = list_items(
381
391
  item_type="task",
@@ -392,7 +402,7 @@ def vocabularies(
392
402
  ctx: Context, param: Argument, incomplete: str, filter_: str = "all"
393
403
  ) -> list[CompletionItem]:
394
404
  """Prepare a list of vocabulary graphs for auto-completion."""
395
- CONTEXT.set_connection_from_params(ctx.find_root().params)
405
+ ApplicationContext.set_connection_from_params(ctx.find_root().params)
396
406
  vocabs = get_vocabularies(filter_=filter_)
397
407
  options = []
398
408
  for _ in vocabs:
@@ -437,7 +447,7 @@ def file_list(
437
447
 
438
448
  def workflow_io_ids(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
439
449
  """Prepare a list of io workflows."""
440
- CONTEXT.set_connection_from_params(ctx.find_root().params)
450
+ ApplicationContext.set_connection_from_params(ctx.find_root().params)
441
451
  options = []
442
452
  for _ in get_workflows_io():
443
453
  workflow_id = _["projectId"] + ":" + _["id"]
@@ -453,7 +463,7 @@ def replay_files(ctx: Context, param: Argument, incomplete: str) -> list[Complet
453
463
 
454
464
  def installed_package_names(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
455
465
  """Prepare a list of installed packages."""
456
- CONTEXT.set_connection_from_args(ctx.find_root().params)
466
+ ApplicationContext.set_connection_from_params(ctx.find_root().params)
457
467
  options = [(_["name"], _["version"]) for _ in list_packages()]
458
468
  return finalize_completion(candidates=options, incomplete=incomplete, sort_by=SORT_BY_KEY)
459
469
 
@@ -617,21 +627,47 @@ def triple_files(ctx: Context, param: Argument, incomplete: str) -> list[Complet
617
627
  )
618
628
 
619
629
 
630
+ def sparql_accept_types(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
631
+ """Prepare a list of commonly used SPARQL accept content types."""
632
+ examples = [
633
+ (
634
+ "text/csv",
635
+ "CSV response, used for SELECT queries, omits language tags and datatypes of literals",
636
+ ),
637
+ ("application/sparql-results+json", "JSON response, used for SELECT queries"),
638
+ ("text/turtle", "Turtle response, used for CONSTRUCT queries"),
639
+ ("application/n-triples", "N-Triples response, used for CONSTRUCT queries"),
640
+ ]
641
+ return finalize_completion(candidates=examples, incomplete=incomplete)
642
+
643
+
620
644
  def placeholder(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
621
645
  """Prepare a list of placeholder from the to-be executed queries."""
622
- # look if cursor is in value position of the -p option and
623
- # return nothing in case it is (values are not completed atm)
624
646
  args = get_completion_args(incomplete)
625
- if args[len(args) - 2] in ("-p", "--parameter"):
626
- return []
627
647
  # setup configuration
628
- CONTEXT.set_connection_from_params(ctx.find_root().params)
648
+ ApplicationContext.set_connection_from_params(ctx.find_root().params)
629
649
  # extract placeholder from given queries in the command line
630
650
  options = []
651
+ placeholders: dict[str, QueryPlaceholder] = {}
652
+ catalog = QueryCatalog() # fetch all queries
631
653
  for _, arg in enumerate(args):
632
- query = QueryCatalog().get_query(arg)
654
+ query = catalog.get_query(arg)
633
655
  if query is not None:
656
+ # collect all placeholder descriptions of existing queries
657
+ placeholders = placeholders | get_placeholders_for_query(iri=query.url)
658
+ # collect all placeholder keys
634
659
  options.extend(list(query.get_placeholder_keys()))
660
+ # look if cursor is in value position of the -p option and
661
+ # use placeholder value completion, in case it is
662
+ if args[len(args) - 2] in ("-p", "--parameter"):
663
+ key = args[len(args) - 1]
664
+ if key in placeholders:
665
+ candidates = placeholders[key].complete(incomplete=incomplete)
666
+ return finalize_completion(
667
+ candidates=candidates, incomplete=incomplete, sort_by=NOT_SORTED
668
+ )
669
+ return []
670
+
635
671
  # look for already given parameter in the arguments and remove them from
636
672
  # the available options
637
673
  for num, arg in enumerate(args):
@@ -642,7 +678,7 @@ def placeholder(ctx: Context, param: Argument, incomplete: str) -> list[Completi
642
678
 
643
679
  def remote_queries(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
644
680
  """Prepare a list of query URIs."""
645
- CONTEXT.set_connection_from_params(ctx.find_root().params)
681
+ ApplicationContext.set_connection_from_params(ctx.find_root().params)
646
682
  options = []
647
683
  for query in QueryCatalog().get_queries().values():
648
684
  url = query.short_url
@@ -662,7 +698,7 @@ def remote_queries_and_sparql_files(
662
698
 
663
699
  def workflow_ids(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
664
700
  """Prepare a list of projectid:taskid workflow identifier."""
665
- CONTEXT.set_connection_from_params(ctx.find_root().params)
701
+ ApplicationContext.set_connection_from_params(ctx.find_root().params)
666
702
  workflows = list_items(item_type="workflow")["results"]
667
703
  options = []
668
704
  for _ in workflows:
@@ -676,7 +712,7 @@ def workflow_ids(ctx: Context, param: Argument, incomplete: str) -> list[Complet
676
712
 
677
713
  def marshalling_plugins(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
678
714
  """Prepare a list of supported workspace/project import/export plugins."""
679
- CONTEXT.set_connection_from_params(ctx.find_root().params)
715
+ ApplicationContext.set_connection_from_params(ctx.find_root().params)
680
716
  options = get_marshalling_plugins()
681
717
  if "description" in options[0]:
682
718
  final_options = [(_["id"], _["description"]) for _ in options]
@@ -691,7 +727,7 @@ def marshalling_plugins(ctx: Context, param: Argument, incomplete: str) -> list[
691
727
 
692
728
  def project_ids(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
693
729
  """Prepare a list of project IDs for auto-completion."""
694
- CONTEXT.set_connection_from_params(ctx.find_root().params)
730
+ ApplicationContext.set_connection_from_params(ctx.find_root().params)
695
731
  projects = get_projects()
696
732
  options = []
697
733
  for _ in projects:
@@ -712,7 +748,7 @@ def _prepare_graph_options(
712
748
  skip_selected_iris: bool = True,
713
749
  ) -> list[tuple[str, str]]:
714
750
  """Prepare a list of graphs with iri and label"""
715
- CONTEXT.set_connection_from_params(ctx.find_root().params)
751
+ ApplicationContext.set_connection_from_params(ctx.find_root().params)
716
752
  graphs = get_graphs(writeable=writeable, readonly=readonly)
717
753
  options = []
718
754
  for graph in graphs:
@@ -777,8 +813,8 @@ def graph_uris_with_all_graph_uri(
777
813
  def connections(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
778
814
  """Prepare a list of config connections for auto-completion."""
779
815
  # since ctx does not have an obj here, we re-create the object
780
- CONTEXT.set_connection_from_params(ctx.find_root().params)
781
- options = CONTEXT.config.sections()
816
+ app = ApplicationContext.from_params(ctx.find_root().params)
817
+ options = app.get_config().sections()
782
818
  return finalize_completion(candidates=options, incomplete=incomplete)
783
819
 
784
820
 
@@ -846,7 +882,7 @@ def graph_list_filter(ctx: Context, param: Argument, incomplete: str) -> list[Co
846
882
 
847
883
  def variable_ids(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
848
884
  """Prepare a list of variables IDs for auto-completion."""
849
- CONTEXT.set_connection_from_params(ctx.find_root().params)
885
+ ApplicationContext.set_connection_from_params(ctx.find_root().params)
850
886
  variables = get_all_variables()
851
887
  options = []
852
888
  for _ in variables:
@@ -978,14 +1014,14 @@ def status_keys(ctx: Context, param: Argument, incomplete: str) -> list[Completi
978
1014
 
979
1015
  def user_ids(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
980
1016
  """Prepare a list of username for admin update/delete/password command."""
981
- CONTEXT.set_connection_from_params(ctx.find_root().params)
1017
+ ApplicationContext.set_connection_from_params(ctx.find_root().params)
982
1018
  options = [_["username"] for _ in list_users()]
983
1019
  return finalize_completion(candidates=options, incomplete=incomplete, sort_by=SORT_BY_DESC)
984
1020
 
985
1021
 
986
1022
  def user_group_ids(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
987
1023
  """Prepare a list of group name for admin user update --(un)assign-group parameter"""
988
- CONTEXT.set_connection_from_params(ctx.find_root().params)
1024
+ ApplicationContext.set_connection_from_params(ctx.find_root().params)
989
1025
  if not ctx.args:
990
1026
  return []
991
1027
  users = get_user_by_username(username=str(ctx.args[0]))
@@ -1011,24 +1047,15 @@ def user_group_ids(ctx: Context, param: Argument, incomplete: str) -> list[Compl
1011
1047
 
1012
1048
  def client_ids(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
1013
1049
  """Prepare a list of client ids for admin secret and update command."""
1014
- CONTEXT.set_connection_from_params(ctx.find_root().params)
1050
+ ApplicationContext.set_connection_from_params(ctx.find_root().params)
1015
1051
  options = [_["clientId"] for _ in list_open_id_clients()]
1016
1052
  return finalize_completion(candidates=options, incomplete=incomplete, sort_by=SORT_BY_DESC)
1017
1053
 
1018
1054
 
1019
1055
  def transformation_task_ids(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
1020
1056
  """Prepare a list of projectId:transformation task identifier."""
1021
- CONTEXT.set_connection_from_params(ctx.find_root().params)
1057
+ ApplicationContext.set_connection_from_params(ctx.find_root().params)
1022
1058
  results = list_items(item_type="transform")
1023
1059
  datasets = results["results"]
1024
1060
  options = [(f"{_['projectId']}:{_['id']}", _["label"]) for _ in datasets]
1025
1061
  return finalize_completion(candidates=options, incomplete=incomplete, sort_by=SORT_BY_DESC)
1026
-
1027
-
1028
- def linking_task_ids(ctx: Context, param: Argument, incomplete: str) -> list[CompletionItem]:
1029
- """Prepare a list of projectId:linking task identifier."""
1030
- CONTEXT.set_connection_from_args(ctx.find_root().params)
1031
- results = list_items(item_type="linking")
1032
- datasets = results["results"]
1033
- options = [(_["projectId"] + r"\:" + _["id"], _["label"]) for _ in datasets]
1034
- return finalize_completion(candidates=options, incomplete=incomplete, sort_by=SORT_BY_DESC)
@@ -0,0 +1,44 @@
1
+ """Config parser module with a custom configuration parser."""
2
+
3
+ import configparser
4
+
5
+
6
+ class PureSectionConfigParser(configparser.RawConfigParser):
7
+ """A configuration parser that retrieves section data without defaults."""
8
+
9
+ _defaults: dict
10
+
11
+ def options(self, section: str) -> list:
12
+ """Return a list of option names for the given section, excluding defaults.
13
+
14
+ This method temporarily removes default options to ensure only explicitly
15
+ defined options in the given section are returned.
16
+
17
+ Args:
18
+ section (str): The section name.
19
+
20
+ Returns:
21
+ list: A list of option names in the specified section.
22
+
23
+ """
24
+ _d = self._defaults.copy()
25
+ try:
26
+ self._defaults.clear()
27
+ return super().options(section)
28
+ finally:
29
+ self._defaults.update(_d)
30
+
31
+ def optionxform(self, option: str) -> str:
32
+ """Preserve the case of option names.
33
+
34
+ By default, `optionxform` in `RawConfigParser` converts option names to lowercase.
35
+ This overridden method ensures that option names remain unchanged.
36
+
37
+ Args:
38
+ option (str): The option name.
39
+
40
+ Returns:
41
+ str: The unmodified option name.
42
+
43
+ """
44
+ return option