Skip to content

Commit f42047b

Browse files
authored
Merge pull request #92 from Materials-Data-Science-and-Informatics/fix/validations
update/fix validations
2 parents a1548c9 + 78f8dc6 commit f42047b

14 files changed

Lines changed: 301 additions & 60 deletions

File tree

CHANGELOG.md

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,43 +4,50 @@ 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.4.3](https://github.com/Materials-Data-Science-and-Informatics/somesy/tree/v0.4.3) <small>(2024-07-??)</small> { id="0.4.3" }
8+
9+
- update python dependencies
10+
- update pre-commit hook versions
11+
- fix package.json person validation
12+
- update poetry, julia, and package.json person validation: entries without an email wont't raise an error, they will be ignored.
13+
714
## [v0.4.2](https://github.com/Materials-Data-Science-and-Informatics/somesy/tree/v0.4.2) <small>(2024-04-30)</small> { id="0.4.2" }
815

9-
* fix rich logging bug for error messages and tracebacks
16+
- fix rich logging bug for error messages and tracebacks
1017

1118
## [v0.4.1](https://github.com/Materials-Data-Science-and-Informatics/somesy/tree/v0.4.1) <small>(2024-04-08)</small> { id="0.4.1" }
1219

13-
* fix package.json and mkdocs.yml validation bug about optional fields
20+
- fix package.json and mkdocs.yml validation bug about optional fields
1421

1522
## [v0.4.0](https://github.com/Materials-Data-Science-and-Informatics/somesy/tree/v0.4.0) <small>(2024-03-08)</small> { id="0.4.0" }
1623

17-
* added separate `documentation` URL to Project metadata model
18-
* added support for Julia `Project.toml` file
19-
* added support for Fortran `fpm.toml` file
20-
* added support for Java `pom.xml` file
21-
* added support for MkDocs `mkdocs.yml` file
22-
* added support for Rust `Cargo.toml` file
24+
- added separate `documentation` URL to Project metadata model
25+
- added support for Julia `Project.toml` file
26+
- added support for Fortran `fpm.toml` file
27+
- added support for Java `pom.xml` file
28+
- added support for MkDocs `mkdocs.yml` file
29+
- added support for Rust `Cargo.toml` file
2330

2431
## [v0.3.1](https://github.com/Materials-Data-Science-and-Informatics/somesy/tree/v0.3.1) <small>(2024-01-23)</small> { id="0.3.1" }
2532

26-
* fix setuptools license writing bug
33+
- fix setuptools license writing bug
2734

2835
## [v0.3.0](https://github.com/Materials-Data-Science-and-Informatics/somesy/tree/v0.3.0) <small>(2024-01-12)</small> { id="0.3.0" }
2936

30-
* replace codemetapy with an in-house writer, which enables windows support
37+
- replace codemetapy with an in-house writer, which enables windows support
3138

3239
## [v0.2.1](https://github.com/Materials-Data-Science-and-Informatics/somesy/tree/v0.2.1) <small>(2023-11-29)</small> { id="0.2.1" }
3340

34-
* **internal:** updated linters and dependencies
35-
* **internal:** pin codemetapy version to 2.5.2 to avoid breaking changes
36-
* fix bug caused by missing `config` section
41+
- **internal:** updated linters and dependencies
42+
- **internal:** pin codemetapy version to 2.5.2 to avoid breaking changes
43+
- fix bug caused by missing `config` section
3744

3845
## [v0.2.0](https://github.com/Materials-Data-Science-and-Informatics/somesy/tree/v0.2.0) <small>(2023-11-29)</small> { id="0.2.0" }
3946

40-
* **internal:** Test refactoring
41-
* **internal:** Pydantic 2 implementation
42-
* Added `publication_author` field to Person model
47+
- **internal:** Test refactoring
48+
- **internal:** Pydantic 2 implementation
49+
- Added `publication_author` field to Person model
4350

4451
## [v0.1.0](https://github.com/Materials-Data-Science-and-Informatics/somesy/tree/v0.1.0) <small>(2023-08-10)</small> { id="0.1.0" }
4552

46-
* First release
53+
- First release

src/somesy/core/writer.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,15 @@ def description(self, description: str) -> None:
331331
@property
332332
def authors(self):
333333
"""Return the authors of the project."""
334-
return self._get_property(self._get_key("authors"))
334+
authors = self._get_property(self._get_key("authors"))
335+
if authors is None:
336+
return []
337+
338+
# only return authors that can be converted to Person
339+
authors_validated = [
340+
author for author in authors if self._to_person(author) is not None
341+
]
342+
return authors_validated
335343

336344
@authors.setter
337345
def authors(self, authors: List[Person]) -> None:
@@ -342,7 +350,17 @@ def authors(self, authors: List[Person]) -> None:
342350
@property
343351
def maintainers(self):
344352
"""Return the maintainers of the project."""
345-
return self._get_property(self._get_key("maintainers"))
353+
maintainers = self._get_property(self._get_key("maintainers"))
354+
if maintainers is None:
355+
return []
356+
357+
# only return maintainers that can be converted to Person
358+
maintainers_validated = [
359+
maintainer
360+
for maintainer in maintainers
361+
if self._to_person(maintainer) is not None
362+
]
363+
return maintainers_validated
346364

347365
@maintainers.setter
348366
def maintainers(self, maintainers: List[Person]) -> None:

src/somesy/fortran/writer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,11 @@ def _from_person(person: Person):
9090
return person.to_name_email_string()
9191

9292
@staticmethod
93-
def _to_person(person_obj: Any) -> Person:
93+
def _to_person(person_obj: Any) -> Optional[Person]:
9494
"""Cannot convert from free string to person object."""
9595
try:
9696
return Person.from_name_email_string(person_obj)
97-
except ValueError:
97+
except (ValueError, AttributeError):
9898
logger.warning(f"Cannot convert {person_obj} to Person object.")
9999
return None
100100

src/somesy/julia/models.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Julia model."""
22

33
import uuid
4+
from logging import getLogger
45
from typing import Optional, Set
56

67
from packaging.version import parse as parse_version
@@ -9,11 +10,13 @@
910
EmailStr,
1011
Field,
1112
TypeAdapter,
13+
ValidationError,
1214
field_validator,
1315
)
1416
from typing_extensions import Annotated
1517

1618
EMailAddress = TypeAdapter(EmailStr)
19+
logger = getLogger("somesy")
1720

1821

1922
class JuliaConfig(BaseModel):
@@ -48,15 +51,25 @@ def validate_version(cls, v):
4851
@field_validator("authors")
4952
@classmethod
5053
def validate_email_format(cls, v):
51-
"""Validate email format."""
54+
"""Validate person format, omit person that is not in correct format, don't raise an error."""
55+
if v is None:
56+
return []
57+
validated = []
5258
for author in v:
53-
if (
54-
not isinstance(author, str)
55-
or " " not in author
56-
or not EMailAddress.validate_python(author.split(" ")[-1][1:-1])
57-
):
58-
raise ValueError("Invalid email format")
59-
return v
59+
try:
60+
if not (
61+
not isinstance(author, str)
62+
or " " not in author
63+
or not EMailAddress.validate_python(author.split(" ")[-1][1:-1])
64+
):
65+
validated.append(author)
66+
else:
67+
logger.warning(
68+
f"Invalid email format for author {author}, omitting."
69+
)
70+
except ValidationError:
71+
logger.warning(f"Invalid format for author {author}, omitting.")
72+
return validated
6073

6174
@field_validator("uuid")
6275
@classmethod

src/somesy/julia/writer.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,13 @@ def _from_person(person: Person):
5454
return person.to_name_email_string()
5555

5656
@staticmethod
57-
def _to_person(person_obj: str) -> Person:
57+
def _to_person(person_obj) -> Optional[Person]:
5858
"""Parse name+email string to a Person."""
59-
return Person.from_name_email_string(person_obj)
59+
try:
60+
return Person.from_name_email_string(person_obj)
61+
except (ValueError, AttributeError):
62+
logger.warning(f"Cannot convert {person_obj} to Person object.")
63+
return None
6064

6165
def sync(self, metadata: ProjectMetadata) -> None:
6266
"""Sync output file with other metadata files."""

src/somesy/mkdocs/writer.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,11 @@ def save(self, path: Optional[Path] = None) -> None:
6262
@property
6363
def authors(self):
6464
"""Return the only author from the source file as list."""
65-
authors = []
66-
try:
67-
self._to_person(self._get_property(self._get_key("authors")))
68-
authors = [self._get_property(self._get_key("authors"))]
69-
except AttributeError:
70-
logger.warning("Cannot convert authors to Person object.")
71-
return authors
65+
authors = self._get_property(self._get_key("authors"))
66+
if authors is None or self._to_person(authors) is None:
67+
return []
68+
else:
69+
return [authors]
7270

7371
@authors.setter
7472
def authors(self, authors: List[Person]) -> None:
@@ -82,9 +80,13 @@ def _from_person(person: Person):
8280
return person.to_name_email_string()
8381

8482
@staticmethod
85-
def _to_person(person: str):
83+
def _to_person(person: str) -> Optional[Person]:
8684
"""MkDocs Person is a string with full name."""
87-
return Person.from_name_email_string(person)
85+
try:
86+
return Person.from_name_email_string(person)
87+
except (ValueError, AttributeError):
88+
logger.warning(f"Cannot convert {person} to Person object.")
89+
return None
8890

8991
def sync(self, metadata: ProjectMetadata) -> None:
9092
"""Sync the MkDocs object with the ProjectMetadata object."""

src/somesy/package_json/models.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
"""package.json validation models."""
22

33
import re
4+
from logging import getLogger
45
from typing import List, Optional, Union
56

67
from pydantic import BaseModel, EmailStr, Field, field_validator
78
from typing_extensions import Annotated
89

910
from somesy.core.types import HttpUrlStr
1011

12+
logger = getLogger("somesy")
13+
1114

1215
class PackageAuthor(BaseModel):
1316
"""Package author model."""
@@ -74,7 +77,7 @@ class PackageJsonConfig(BaseModel):
7477

7578
# convert package author to dict if it is a string
7679
@classmethod
77-
def convert_author(cls, author: str) -> PackageAuthor:
80+
def convert_author(cls, author: str) -> Optional[PackageAuthor]:
7881
"""Convert author string to PackageAuthor model."""
7982
# parse author string to "name <email> (url)" format with regex
8083
author_match = re.match(NPM_PKG_AUTHOR, author)
@@ -84,6 +87,8 @@ def convert_author(cls, author: str) -> PackageAuthor:
8487
author_email = author_match[2]
8588
author_url = author_match[3]
8689

90+
if author_email is None:
91+
return None
8792
return PackageAuthor(name=author_name, email=author_email, url=author_url)
8893

8994
@field_validator("name")
@@ -116,7 +121,17 @@ def validate_people(cls, v):
116121
people = []
117122
for p in v:
118123
if isinstance(p, str):
119-
people.append(cls.convert_author(p))
120-
else:
124+
author = cls.convert_author(p)
125+
if author is not None:
126+
people.append(cls.convert_author(p))
127+
else:
128+
logger.warning(
129+
f"Invalid email format for maintainer/contributor {p}, omitting."
130+
)
131+
elif p.email is not None:
121132
people.append(p)
133+
else:
134+
logger.warning(
135+
f"Invalid email format for maintainer/contributor {p}, omitting."
136+
)
122137
return people

src/somesy/package_json/writer.py

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ def __init__(
3535
@property
3636
def authors(self):
3737
"""Return the only author of the package.json file as list."""
38+
# check if the author has the correct format
39+
if isinstance(author := self._get_property(self._get_key("authors")), str):
40+
author = PackageJsonConfig.convert_author(author)
41+
if author is None:
42+
return []
43+
3844
return [self._get_property(self._get_key("authors"))]
3945

4046
@authors.setter
@@ -43,10 +49,49 @@ def authors(self, authors: List[Person]) -> None:
4349
authors = self._from_person(authors[0])
4450
self._set_property(self._get_key("authors"), authors)
4551

52+
@property
53+
def maintainers(self):
54+
"""Return the maintainers of the package.json file."""
55+
# check if the maintainer has the correct format
56+
maintainers = self._get_property(self._get_key("maintainers"))
57+
# return empty list if maintainers is None
58+
if maintainers is None:
59+
return []
60+
61+
maintainers_valid = []
62+
63+
for maintainer in maintainers:
64+
if isinstance(maintainer, str):
65+
maintainer = PackageJsonConfig.convert_author(maintainer)
66+
if maintainer is None:
67+
continue
68+
maintainers_valid.append(maintainer)
69+
return maintainers_valid
70+
71+
@maintainers.setter
72+
def maintainers(self, maintainers: List[Person]) -> None:
73+
"""Set the maintainers of the project."""
74+
maintainers = [self._from_person(m) for m in maintainers]
75+
self._set_property(self._get_key("maintainers"), maintainers)
76+
4677
@property
4778
def contributors(self):
4879
"""Return the contributors of the package.json file."""
49-
return self._get_property(self._get_key("contributors"))
80+
# check if the contributor has the correct format
81+
contributors = self._get_property(self._get_key("contributors"))
82+
# return empty list if contributors is None
83+
if contributors is None:
84+
return []
85+
86+
contributors_valid = []
87+
88+
for contributor in contributors:
89+
if isinstance(contributor, str):
90+
contributor = PackageJsonConfig.convert_author(contributor)
91+
if contributor is None:
92+
continue
93+
contributors_valid.append(contributor)
94+
return contributors_valid
5095

5196
@contributors.setter
5297
def contributors(self, contributors: List[Person]) -> None:
@@ -91,9 +136,12 @@ def _to_person(person) -> Person:
91136
"""Convert package.json dict or str for person format to project metadata person object."""
92137
if isinstance(person, str):
93138
# parse from package.json format
94-
person = PackageJsonConfig.convert_author(person).model_dump(
95-
exclude_none=True
96-
)
139+
person = PackageJsonConfig.convert_author(person)
140+
141+
if person is None:
142+
return None
143+
144+
person = person.model_dump(exclude_none=True)
97145

98146
names = list(map(lambda s: s.strip(), person["name"].split()))
99147
person_obj = {

0 commit comments

Comments
 (0)