Skip to content

Commit 8aa5b58

Browse files
authored
Merge pull request #111 from Materials-Data-Science-and-Informatics/feature/optional_validation
Feature/optional validation
2 parents 3a11844 + 3cb1121 commit 8aa5b58

22 files changed

Lines changed: 222 additions & 39 deletions

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ Here we provide notes that summarize the most important changes in each released
44

55
Please consult the changelog to inform yourself about breaking changes and security issues.
66

7-
## [v0.6.0](https://github.com/Materials-Data-Science-and-Informatics/somesy/tree/v0.6.0) <small>(2025-xx-xx)</small> { id="0.6.0" }
7+
## [v0.7.0](https://github.com/Materials-Data-Science-and-Informatics/somesy/tree/v0.7.0) <small>(2025-xx-xx)</small> { id="0.7.0" }
8+
9+
- make validation of output files, such as pyproject.toml, optional
10+
- make somesy project metadata input `version` optional
11+
12+
## [v0.6.0](https://github.com/Materials-Data-Science-and-Informatics/somesy/tree/v0.6.0) <small>(2025-02-14)</small> { id="0.6.0" }
813

914
- implement CFF Entity (Organization) model for author/maintainer/contributor
1015
- add a new config option to use existing codemeta.json when syncing

docs/manual.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ some of the currently supported formats. Bold field names are mandatory, the oth
146146
| **name** | name | name | name | name | name | name | site_name | name | title | name |
147147
| **description** | description | description | description | - | description | description | site_description | description | abstract | description |
148148
| **license** | license | license | licenses.license | - | license | license | - | license | license | license |
149-
| **version** | version | version | version | version | version | version | - | version | version | version |
149+
| version | version | version | version | version | version | version | - | version | version | version |
150150
| | | | | | | | | | | |
151151
| ***author=true*** | authors | authors | developers | authors | author | author | site_author | authors | authors | author |
152152
| *maintainer=true* | maintainers | maintainers | - | - | maintainer | maintainers | - | - | contact | maintainer |

src/somesy/cff/writer.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def __init__(
1818
self,
1919
path: Path,
2020
create_if_not_exists: bool = True,
21+
pass_validation: bool = False,
2122
):
2223
"""Citation File Format (CFF) parser.
2324
@@ -35,7 +36,10 @@ def __init__(
3536
"maintainers": ["contact"],
3637
}
3738
super().__init__(
38-
path, create_if_not_exists=create_if_not_exists, direct_mappings=mappings
39+
path,
40+
create_if_not_exists=create_if_not_exists,
41+
direct_mappings=mappings,
42+
pass_validation=pass_validation,
3943
)
4044

4145
def _init_new_file(self):
@@ -53,8 +57,10 @@ def _load(self):
5357
with open(self.path) as f:
5458
self._data = self._yaml.load(f)
5559

56-
def _validate(self):
60+
def _validate(self) -> None:
5761
"""Validate the CFF file."""
62+
if self.pass_validation:
63+
return
5864
try:
5965
citation = create_citation(self.path, None)
6066
citation.validate()

src/somesy/cli/sync.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ def sync(
152152
"--merge/--overwrite",
153153
help="Merge codemeta.json with with an existing codemeta.json file (default: False)",
154154
),
155+
pass_validation: bool = typer.Option(
156+
False,
157+
"--pass-validation",
158+
"-P",
159+
help="Pass validation of metadata files (default: False)",
160+
),
155161
):
156162
"""Sync project metadata input with metadata files."""
157163
somesy_input = resolved_somesy_input(
@@ -175,6 +181,7 @@ def sync(
175181
no_sync_rust=no_sync_rust,
176182
rust_file=rust_file,
177183
merge_codemeta=merge_codemeta,
184+
pass_validation=pass_validation,
178185
)
179186
run_sync(somesy_input)
180187

@@ -218,6 +225,8 @@ def run_sync(somesy_input: SomesyInput):
218225
f" - [italic]codemeta.json[/italic]:\t[grey]{conf.codemeta_file}[/grey]\n"
219226
)
220227
# ----
228+
if conf.pass_validation:
229+
logger.info("[bold yellow]Passing validation of metadata files.[/bold yellow]")
221230
sync_command(somesy_input)
222231
# ----
223232
logger.info("[bold green]Metadata synchronization completed.[/bold green]")

src/somesy/codemeta/writer.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def __init__(
2020
self,
2121
path: Path,
2222
merge: Optional[bool] = False,
23+
pass_validation: Optional[bool] = False,
2324
):
2425
"""Codemeta.json parser.
2526
@@ -46,7 +47,12 @@ def __init__(
4647
if path.is_file() and not self.merge:
4748
logger.verbose("Deleting existing codemeta.json file.")
4849
path.unlink()
49-
super().__init__(path, create_if_not_exists=True, direct_mappings=mappings)
50+
super().__init__(
51+
path,
52+
create_if_not_exists=True,
53+
direct_mappings=mappings,
54+
pass_validation=pass_validation,
55+
)
5056

5157
# if merge is True, add necessary keys to the codemeta.json file
5258
if self.merge:
@@ -95,6 +101,8 @@ def _load(self) -> None:
95101

96102
def _validate(self) -> None:
97103
"""Validate codemeta.json content using pydantic class."""
104+
if self.pass_validation:
105+
return
98106
invalid_fields = validate_codemeta(self._data)
99107
if invalid_fields and self.merge:
100108
raise ValueError(

src/somesy/commands/sync.py

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,14 @@ def _sync_file(
2626
file: Path,
2727
writer_cls: Type[ProjectMetadataWriter],
2828
merge_codemeta: Optional[bool] = False,
29+
pass_validation: Optional[bool] = False,
2930
):
3031
"""Sync metadata to a file using the provided writer."""
3132
logger.verbose(f"Loading '{file.name}' ...")
3233
if writer_cls == CodeMeta:
33-
writer = writer_cls(file, merge=merge_codemeta)
34+
writer = writer_cls(file, merge=merge_codemeta, pass_validation=pass_validation)
3435
else:
35-
writer = writer_cls(file)
36+
writer = writer_cls(file, pass_validation=pass_validation)
3637
logger.verbose(f"Syncing '{file.name}' ...")
3738
writer.sync(metadata)
3839
writer.save(file)
@@ -49,34 +50,75 @@ def sync(somesy_input: SomesyInput):
4950
# update these only if they exist:
5051

5152
if conf.pyproject_file.is_file() and not conf.no_sync_pyproject:
52-
_sync_file(metadata, conf.pyproject_file, Pyproject)
53+
_sync_file(
54+
metadata,
55+
conf.pyproject_file,
56+
Pyproject,
57+
pass_validation=conf.pass_validation,
58+
)
5359

5460
if conf.package_json_file.is_file() and not conf.no_sync_package_json:
55-
_sync_file(metadata, conf.package_json_file, PackageJSON)
61+
_sync_file(
62+
metadata,
63+
conf.package_json_file,
64+
PackageJSON,
65+
pass_validation=conf.pass_validation,
66+
)
5667

5768
if conf.julia_file.is_file() and not conf.no_sync_julia:
58-
_sync_file(metadata, conf.julia_file, Julia)
69+
_sync_file(
70+
metadata,
71+
conf.julia_file,
72+
Julia,
73+
pass_validation=conf.pass_validation,
74+
)
5975

6076
if conf.fortran_file.is_file() and not conf.no_sync_fortran:
61-
_sync_file(metadata, conf.fortran_file, Fortran)
77+
_sync_file(
78+
metadata,
79+
conf.fortran_file,
80+
Fortran,
81+
pass_validation=conf.pass_validation,
82+
)
6283

6384
if conf.pom_xml_file.is_file() and not conf.no_sync_pom_xml:
64-
_sync_file(metadata, conf.pom_xml_file, POM)
85+
_sync_file(
86+
metadata,
87+
conf.pom_xml_file,
88+
POM,
89+
pass_validation=conf.pass_validation,
90+
)
6591

6692
if conf.mkdocs_file.is_file() and not conf.no_sync_mkdocs:
67-
_sync_file(metadata, conf.mkdocs_file, MkDocs)
93+
_sync_file(
94+
metadata,
95+
conf.mkdocs_file,
96+
MkDocs,
97+
pass_validation=conf.pass_validation,
98+
)
6899

69100
if conf.rust_file.is_file() and not conf.no_sync_rust:
70-
_sync_file(metadata, conf.rust_file, Rust)
101+
_sync_file(
102+
metadata,
103+
conf.rust_file,
104+
Rust,
105+
pass_validation=conf.pass_validation,
106+
)
71107

72108
# create these by default if they are missing:
73109
if not conf.no_sync_cff:
74-
_sync_file(metadata, conf.cff_file, CFF)
110+
_sync_file(
111+
metadata,
112+
conf.cff_file,
113+
CFF,
114+
pass_validation=conf.pass_validation,
115+
)
75116

76117
if not conf.no_sync_codemeta:
77118
_sync_file(
78119
metadata,
79120
conf.codemeta_file,
80121
CodeMeta,
81122
merge_codemeta=conf.merge_codemeta,
123+
pass_validation=conf.pass_validation,
82124
)

src/somesy/core/models.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,12 @@ def at_least_one_target(cls, values):
243243
),
244244
] = False
245245

246+
# property to pass validation for all inputs/outputs
247+
pass_validation: Annotated[
248+
Optional[bool],
249+
Field(description="Pass validation for all output files."),
250+
] = False
251+
246252
def log_level(self) -> SomesyLogLevel:
247253
"""Return log level derived from this configuration."""
248254
return SomesyLogLevel.from_flags(
@@ -644,7 +650,7 @@ def at_least_one_author(self) -> ProjectMetadata:
644650

645651
name: Annotated[str, Field(description="Project name.")]
646652
description: Annotated[str, Field(description="Project description.")]
647-
version: Annotated[str, Field(description="Project version.")]
653+
version: Annotated[Optional[str], Field(description="Project version.")] = None
648654
license: Annotated[LicenseEnum, Field(description="SPDX License string.")]
649655

650656
homepage: Annotated[

src/somesy/core/writer.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def __init__(
3737
create_if_not_exists: Optional[bool] = False,
3838
direct_mappings: FieldKeyMapping = None,
3939
merge: Optional[bool] = False,
40+
pass_validation: Optional[bool] = False,
4041
) -> None:
4142
"""Initialize the Project Metadata Output Wrapper.
4243
@@ -50,16 +51,19 @@ def __init__(
5051
create_if_not_exists: Create an empty CFF file if not exists. Defaults to True.
5152
direct_mappings: Dict with direct mappings of keys between somesy and target
5253
merge: Merge the output file with an existing file. Defaults to False.
54+
pass_validation: Pass validation for all output files. Defaults to False.
5355
5456
"""
5557
self._data: DictLike = {}
5658
self.path = path if isinstance(path, Path) else Path(path)
5759
self.create_if_not_exists = create_if_not_exists
5860
self.direct_mappings = direct_mappings or {}
5961
self.merge = merge
62+
self.pass_validation = pass_validation
6063
if self.path.is_file():
6164
self._load()
62-
self._validate()
65+
if not self.pass_validation:
66+
self._validate()
6367
else:
6468
if self.create_if_not_exists:
6569
self._init_new_file()
@@ -86,7 +90,7 @@ def _load(self):
8690
"""
8791

8892
@abstractmethod
89-
def _validate(self):
93+
def _validate(self) -> None:
9094
"""Validate the target file data.
9195
9296
Implement this method so that it checks

src/somesy/fortran/writer.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@
1818
class Fortran(ProjectMetadataWriter):
1919
"""Fortran config file handler parsed from fpm.toml."""
2020

21-
def __init__(self, path: Path):
21+
def __init__(
22+
self,
23+
path: Path,
24+
pass_validation: Optional[bool] = False,
25+
):
2226
"""Fortran config file handler parsed from fpm.toml.
2327
2428
See [somesy.core.writer.ProjectMetadataWriter.__init__][].
@@ -28,7 +32,12 @@ def __init__(self, path: Path):
2832
"maintainers": ["maintainer"],
2933
"documentation": IgnoreKey(),
3034
}
31-
super().__init__(path, create_if_not_exists=False, direct_mappings=mappings)
35+
super().__init__(
36+
path,
37+
create_if_not_exists=False,
38+
direct_mappings=mappings,
39+
pass_validation=pass_validation,
40+
)
3241

3342
@property
3443
def authors(self):
@@ -71,6 +80,8 @@ def _validate(self) -> None:
7180
In order to preserve toml comments and structure, tomlkit library is used.
7281
Pydantic class only used for validation.
7382
"""
83+
if self.pass_validation:
84+
return
7485
config = dict(self._get_property([]))
7586
logger.debug(
7687
f"Validating config using {FortranConfig.__name__}: {pretty_repr(config)}"

src/somesy/julia/writer.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,20 @@
1818
class Julia(ProjectMetadataWriter):
1919
"""Julia config file handler parsed from Project.toml."""
2020

21-
def __init__(self, path: Path):
21+
def __init__(
22+
self,
23+
path: Path,
24+
pass_validation: Optional[bool] = False,
25+
):
2226
"""Julia config file handler parsed from Project.toml.
2327
2428
See [somesy.core.writer.ProjectMetadataWriter.__init__][].
2529
"""
26-
super().__init__(path, create_if_not_exists=False)
30+
super().__init__(
31+
path,
32+
create_if_not_exists=False,
33+
pass_validation=pass_validation,
34+
)
2735

2836
def _load(self) -> None:
2937
"""Load Project.toml file."""
@@ -36,6 +44,8 @@ def _validate(self) -> None:
3644
In order to preserve toml comments and structure, tomlkit library is used.
3745
Pydantic class only used for validation.
3846
"""
47+
if self.pass_validation:
48+
return
3949
config = dict(self._get_property([]))
4050
logger.debug(
4151
f"Validating config using {JuliaConfig.__name__}: {pretty_repr(config)}"

0 commit comments

Comments
 (0)