FourCIPP 1.1.0__py3-none-any.whl → 1.3.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.
fourcipp/fourc_input.py CHANGED
@@ -27,7 +27,7 @@ import copy
27
27
  import difflib
28
28
  import pathlib
29
29
  from collections.abc import Sequence
30
- from typing import Any
30
+ from typing import Any, Callable
31
31
 
32
32
  from loguru import logger
33
33
 
@@ -37,7 +37,10 @@ from fourcipp.legacy_io import (
37
37
  interpret_legacy_section,
38
38
  )
39
39
  from fourcipp.utils.converter import Converter
40
- from fourcipp.utils.dict_utils import compare_nested_dicts_or_lists
40
+ from fourcipp.utils.dict_utils import (
41
+ compare_nested_dicts_or_lists,
42
+ sort_by_key_order,
43
+ )
41
44
  from fourcipp.utils.not_set import NotSet, check_if_set
42
45
  from fourcipp.utils.typing import Path
43
46
  from fourcipp.utils.validation import ValidationError, validate_using_json_schema
@@ -66,6 +69,75 @@ def is_section_known(section_name: str, known_section_names: list[str]) -> bool:
66
69
  return section_name in known_section_names or section_name.startswith("FUNCT")
67
70
 
68
71
 
72
+ def _sort_by_section_names(data: dict) -> dict:
73
+ """Sort a dictionary by its 4C sections.
74
+
75
+ This sorts the dictionary in the following style:
76
+
77
+ 1. "TITLE" section
78
+ 2. Alphabetically sorted sections
79
+ 3. Alphabetically sorted legacy sections
80
+
81
+ Args:
82
+ data: Dictionary to sort.
83
+ section_names: List of all section names in the 4C style order.
84
+
85
+ Returns:
86
+ Dict sorted in 4C fashion
87
+ """
88
+
89
+ required_sections = CONFIG.fourc_json_schema["required"]
90
+ n_sections_splitter = len(CONFIG.sections.all_sections) * 1000
91
+
92
+ # collect typed sections + numeric FUNCT sections
93
+ typed_and_funct = sorted(
94
+ sorted(CONFIG.sections.typed_sections, key=str.lower)
95
+ + [s for s in data.keys() if s.startswith("FUNCT") and s[5:].isdigit()],
96
+ key=lambda s: (
97
+ s.lower() if not s.startswith("FUNCT") else f"funct{s[5:].zfill(10)}"
98
+ ),
99
+ )
100
+
101
+ def ordering_score(section: str) -> int:
102
+ """Get ordering score, small score comes first, larger comes later.
103
+
104
+ We offset the score by the number of sections multiplied by 1000. This way a score is guaranteed to never appear twice.
105
+
106
+ Args:
107
+ section: Section name to score
108
+
109
+ Returns:
110
+ ordering score
111
+ """
112
+ # Title sections
113
+ if section == CONFIG.fourc_metadata["metadata"]["description_section_name"]:
114
+ return 0
115
+ # Required sections
116
+ elif section in required_sections:
117
+ return 1 * n_sections_splitter + required_sections.index(section)
118
+ # Typed + FUNCT sections (alphabetical + case insensitive)
119
+ elif section in typed_and_funct:
120
+ return 2 * n_sections_splitter + typed_and_funct.index(section)
121
+ # Legacy sections
122
+ elif section in CONFIG.sections.legacy_sections:
123
+ return 3 * n_sections_splitter + CONFIG.sections.legacy_sections.index(
124
+ section
125
+ )
126
+ # Unknown section
127
+ else:
128
+ raise KeyError(f"Unknown section {section}")
129
+
130
+ unknown_sections = set(data.keys()) - set(CONFIG.sections.all_sections)
131
+
132
+ # Remove functions, these are a special case
133
+ if [section for section in unknown_sections if not section.startswith("FUNCT")]:
134
+ raise ValueError(
135
+ f"Sections {list(unknown_sections)} are not known in 'section_names'"
136
+ )
137
+
138
+ return sort_by_key_order(data, sorted(data.keys(), key=ordering_score))
139
+
140
+
69
141
  class FourCInput:
70
142
  """4C inout file object."""
71
143
 
@@ -420,20 +492,20 @@ class FourCInput:
420
492
  def dump(
421
493
  self,
422
494
  input_file_path: Path,
423
- sort_sections: bool = False,
424
495
  validate: bool = False,
425
496
  validate_sections_only: bool = False,
426
497
  convert_to_native_types: bool = True,
498
+ sort_function: Callable[[dict], dict] | None = _sort_by_section_names,
427
499
  ) -> None:
428
500
  """Dump object to yaml.
429
501
 
430
502
  Args:
431
503
  input_file_path: Path to dump the data to
432
- sort_sections: Sort the sections alphabetically
433
504
  validate: Validate input data before dumping
434
505
  validate_sections_only: Validate each section independently.
435
506
  Requiredness of the sections themselves is ignored.
436
507
  convert_to_native_types: Convert all sections to native Python types
508
+ sort_function: Function to sort the sections.
437
509
  """
438
510
 
439
511
  if validate or validate_sections_only:
@@ -448,7 +520,7 @@ class FourCInput:
448
520
  if convert_to_native_types:
449
521
  self.convert_to_native_types()
450
522
 
451
- dump_yaml(self.inlined, input_file_path, sort_sections)
523
+ dump_yaml(self.inlined, input_file_path, sort_function)
452
524
 
453
525
  def validate(
454
526
  self,
@@ -45,7 +45,7 @@ class Sections:
45
45
  """
46
46
  self.legacy_sections: list[str] = legacy_sections
47
47
  self.typed_sections: list[str] = typed_sections
48
- self.all_sections: list[str] = legacy_sections + typed_sections
48
+ self.all_sections: list[str] = typed_sections + legacy_sections
49
49
 
50
50
  @classmethod
51
51
  def from_metadata(cls, fourc_metadata: dict) -> Sections:
@@ -58,9 +58,10 @@ class Sections:
58
58
  Sections: sections object
59
59
  """
60
60
  description_section = fourc_metadata["metadata"]["description_section_name"]
61
- sections = [
61
+ sections = [description_section] + [
62
62
  section["name"] for section in fourc_metadata["sections"]["specs"]
63
- ] + [description_section]
63
+ ]
64
+
64
65
  legacy_sections = list(fourc_metadata["legacy_string_sections"])
65
66
 
66
67
  return cls(legacy_sections, sections)
@@ -373,3 +373,35 @@ def rename_parameter(
373
373
 
374
374
  for entry, last_key in _split_off_last_key(nested_dict, keys):
375
375
  entry[new_name] = entry.pop(last_key)
376
+
377
+
378
+ def sort_by_key_order(data: dict, key_order: list[str]) -> dict:
379
+ """Sort a dictionary by a specific key order.
380
+
381
+ Args:
382
+ data: Dictionary to sort.
383
+ key_order: List of keys in the desired order.
384
+
385
+ Returns:
386
+ Sorted dictionary.
387
+ """
388
+ if set(key_order) != set(data.keys()):
389
+ raise ValueError("'key_order' must include all keys in the dictionary!")
390
+
391
+ return {key: data[key] for key in key_order if key in data}
392
+
393
+
394
+ def sort_alphabetically(
395
+ data: dict,
396
+ ) -> dict:
397
+ """Sort a dictionary alphabetically.
398
+
399
+ Args:
400
+ data: Dictionary to sort.
401
+
402
+ Returns:
403
+ Sorted dictionary.
404
+ """
405
+ return sort_by_key_order(
406
+ data, sorted(data.keys(), key=lambda s: (s.lower(), 0 if s.islower() else 1))
407
+ )
fourcipp/utils/yaml_io.py CHANGED
@@ -24,6 +24,7 @@
24
24
  import json
25
25
  import pathlib
26
26
  import re
27
+ from typing import Callable
27
28
 
28
29
  import ryml
29
30
 
@@ -61,17 +62,21 @@ def load_yaml(path_to_yaml_file: Path) -> dict:
61
62
  return data
62
63
 
63
64
 
64
- def dict_to_yaml_string(data: dict, sort_keys: bool = False) -> str:
65
+ def dict_to_yaml_string(
66
+ data: dict, sort_function: Callable[[dict], dict] | None = None
67
+ ) -> str:
65
68
  """Dump dict as yaml.
66
69
 
67
70
  Args:
68
71
  data: Data to dump.
69
- sort_keys: If true sort the sections by section name
72
+ sort_function: Function to sort the data.
73
+
74
+ Returns:
75
+ YAML string representation of the data
70
76
  """
71
77
 
72
- # Sort keys
73
- if sort_keys:
74
- data = {key: data[key] for key in sorted(data.keys())}
78
+ if sort_function is not None:
79
+ data = sort_function(data)
75
80
 
76
81
  # Convert dictionary into a ryml tree
77
82
  tree = ryml.parse_in_arena(bytearray(json.dumps(data).encode("utf8")))
@@ -88,14 +93,19 @@ def dict_to_yaml_string(data: dict, sort_keys: bool = False) -> str:
88
93
  return ryml.emit_yaml(tree)
89
94
 
90
95
 
91
- def dump_yaml(data: dict, path_to_yaml_file: Path, sort_keys: bool = False) -> None:
96
+ def dump_yaml(
97
+ data: dict,
98
+ path_to_yaml_file: Path,
99
+ sort_function: Callable[[dict], dict] | None = None,
100
+ ) -> None:
92
101
  """Dump yaml to file.
93
102
 
94
103
  Args:
95
104
  data: Data to dump.
96
105
  path_to_yaml_file: Yaml file path
97
- sort_keys: If true sort the sections by section name
106
+ sort_function: Function to sort the data.
98
107
  """
99
108
  pathlib.Path(path_to_yaml_file).write_text(
100
- dict_to_yaml_string(data, sort_keys), encoding="utf-8"
109
+ dict_to_yaml_string(data, sort_function),
110
+ encoding="utf-8",
101
111
  )
fourcipp/version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '1.1.0'
32
- __version_tuple__ = version_tuple = (1, 1, 0)
31
+ __version__ = version = '1.3.0'
32
+ __version_tuple__ = version_tuple = (1, 3, 0)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: FourCIPP
3
- Version: 1.1.0
3
+ Version: 1.3.0
4
4
  Summary: A streamlined Python Parser for 4C input files
5
5
  Author: FourCIPP Authors
6
6
  License: The MIT License (MIT)
@@ -168,7 +168,7 @@ input_4C["PROBLEM SIZE"]["NODES"] = 10_000_000
168
168
  removed_section = input_4C.pop("PROBLEM SIZE")
169
169
 
170
170
  # Dump to file
171
- input_4C.dump(input_file_path, sort_sections=True, validate=True)
171
+ input_4C.dump(input_file_path, validate=True)
172
172
  ```
173
173
  <!--example, do not remove this comment-->
174
174
 
@@ -1,8 +1,8 @@
1
1
  fourcipp/__init__.py,sha256=4pz7DVXErSbUcLqPTaHnQfdJzKkpfZDivBqbHbTgpRE,1388
2
- fourcipp/fourc_input.py,sha256=4ZcaD5NQVqPqsUaUCap0UFLn80QboXSbrvWk1qd7yTU,21260
3
- fourcipp/version.py,sha256=ePNVzJOkxR8FY5bezqKQ_fgBRbzH1G7QTaRDHvGQRAY,704
4
- fourcipp/config/4C_metadata.yaml,sha256=r0tDcaRhHdEPu7ex7NvTFuoAiSuASdO6NdL4t7ms2y4,11826453
5
- fourcipp/config/4C_schema.json,sha256=Lk9eWjvR5j2Mq3qlRsWWZMXYvd9OVOYZ_JNhDoxJLhk,16343600
2
+ fourcipp/fourc_input.py,sha256=w37dzvfuzGXv0EdzfGABKjmAwxx-4Ba7dTyzjyk_ApI,23803
3
+ fourcipp/version.py,sha256=0Oc4EBzGTJOvXX0Vym4evglW1NQPpe8RLn8TdxsKzfs,704
4
+ fourcipp/config/4C_metadata.yaml,sha256=3n3Uj24iblvsfqUI-8H2LYMHOROjeHl68Nok0F22Unc,11868927
5
+ fourcipp/config/4C_schema.json,sha256=67giZGZSKXkjO_NCbZzx-blVdGB6BFXRbHrXkmqB_T4,16412275
6
6
  fourcipp/config/config.yaml,sha256=n2c2a6E4HKfAdNWOQz1kLUuf5p4NLxIddaAi2t5eM38,460
7
7
  fourcipp/legacy_io/__init__.py,sha256=y6aTjgUe61OQSl1NMiZ-fQAGTab_xNT1eJTnaTnsIkc,5744
8
8
  fourcipp/legacy_io/element.py,sha256=b8--f3IR9nB68R25mk4DHHOBdDLqASewtAdPZAkA2t4,3971
@@ -12,17 +12,17 @@ fourcipp/legacy_io/node_topology.py,sha256=hqGGzhyNhFX-BMW8SzztfzM8zoZLLltVScMv-
12
12
  fourcipp/legacy_io/particle.py,sha256=0rxwdiMnHI1QUGZPqWgsxqyslrxB7aq7qxgNMb_y_rk,2166
13
13
  fourcipp/utils/__init__.py,sha256=ccdlf4taJ0mKLg_ru8ilXEa72PoO9N2UIxHNHDEtQmY,1151
14
14
  fourcipp/utils/cli.py,sha256=43Nmy9tnoQzwWMZwpktWVv3T3a3y4qDzfuDB1bHogbA,4658
15
- fourcipp/utils/configuration.py,sha256=mDrizczVvMQ1r5EancScbWzYn3g9bmQAdDACLbPLfq0,6460
15
+ fourcipp/utils/configuration.py,sha256=HDrvHrH4K5mdienzVsAt_sh9j2Pv81bxWBqqbeNb1KE,6461
16
16
  fourcipp/utils/converter.py,sha256=D40YQ96WqPEEpB7fwAB5XC2v-jRki4v1sIJ-ngO08nU,5194
17
- fourcipp/utils/dict_utils.py,sha256=Ha9eHHcItLZFQgkqC_Ew_2h_2tmLNmzdJmpECcJ3LFQ,12126
17
+ fourcipp/utils/dict_utils.py,sha256=uC0uaBNP3Wh2v3kFy9JHnAYARMukAN4cl40pmiBT13Y,12891
18
18
  fourcipp/utils/metadata.py,sha256=98jz9Gm8qdvZ-b1jwYObNJTaiMQWwbknvS70Nv6Gwhk,1291
19
19
  fourcipp/utils/not_set.py,sha256=04C_3axe2cupBYgfpgDAcGs1zHVzG3I58UGO58TH05A,2017
20
20
  fourcipp/utils/typing.py,sha256=8iX9PuKe8B1WJ3vEjiM5ZfefvgYnaZDiSdB7Nx9SrVw,1625
21
21
  fourcipp/utils/validation.py,sha256=FejHOj1MddaU7gEpMN-f8Mz3rYjflakd1qcsKnctHqA,5292
22
- fourcipp/utils/yaml_io.py,sha256=fPJiIMVzVoHX97LFynGxmQ2PKQsUAb3GIBDWvkPfftI,3477
23
- fourcipp-1.1.0.dist-info/licenses/LICENSE,sha256=lkSOHdH9IZ8c3Vnz0fFjqls1cRlmLADBP8QEIwUlH3o,1082
24
- fourcipp-1.1.0.dist-info/METADATA,sha256=xSrEX3PHqiOLeizFCLaXvTFOYzymi9hotkdIcY6n7Fc,7967
25
- fourcipp-1.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
26
- fourcipp-1.1.0.dist-info/entry_points.txt,sha256=44XBG4bwhuq1EwOZ0U055HYP8_qN6_d30ecVsa5Igng,53
27
- fourcipp-1.1.0.dist-info/top_level.txt,sha256=oZ6jgFFmvi157VwGUEFuKT3D8oS5mOkpOVx8zZURZrQ,9
28
- fourcipp-1.1.0.dist-info/RECORD,,
22
+ fourcipp/utils/yaml_io.py,sha256=yShbQ7d_zv_tGK_3ym3NilrfL7HpsEt_8JvDqZYnuFY,3606
23
+ fourcipp-1.3.0.dist-info/licenses/LICENSE,sha256=lkSOHdH9IZ8c3Vnz0fFjqls1cRlmLADBP8QEIwUlH3o,1082
24
+ fourcipp-1.3.0.dist-info/METADATA,sha256=4G6YxfZlgnnDMV19Yz3UblMfxHpnMCAqWonlpsgmdcY,7947
25
+ fourcipp-1.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
26
+ fourcipp-1.3.0.dist-info/entry_points.txt,sha256=44XBG4bwhuq1EwOZ0U055HYP8_qN6_d30ecVsa5Igng,53
27
+ fourcipp-1.3.0.dist-info/top_level.txt,sha256=oZ6jgFFmvi157VwGUEFuKT3D8oS5mOkpOVx8zZURZrQ,9
28
+ fourcipp-1.3.0.dist-info/RECORD,,