Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update/fix validations #92

Merged
merged 8 commits into from
Jul 26, 2024
Merged
41 changes: 24 additions & 17 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,50 @@ Here we provide notes that summarize the most important changes in each released

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

## [v0.4.3](https://github.com/Materials-Data-Science-and-Informatics/somesy/tree/v0.4.3) <small>(2024-07-??)</small> { id="0.4.3" }

- update python dependencies
- update pre-commit hook versions
- fix package.json person validation
- update poetry, julia, and package.json person validation: entries without an email wont't raise an error, they will be ignored.

## [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" }

* fix rich logging bug for error messages and tracebacks
- fix rich logging bug for error messages and tracebacks

## [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" }

* fix package.json and mkdocs.yml validation bug about optional fields
- fix package.json and mkdocs.yml validation bug about optional fields

## [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" }

* added separate `documentation` URL to Project metadata model
* added support for Julia `Project.toml` file
* added support for Fortran `fpm.toml` file
* added support for Java `pom.xml` file
* added support for MkDocs `mkdocs.yml` file
* added support for Rust `Cargo.toml` file
- added separate `documentation` URL to Project metadata model
- added support for Julia `Project.toml` file
- added support for Fortran `fpm.toml` file
- added support for Java `pom.xml` file
- added support for MkDocs `mkdocs.yml` file
- added support for Rust `Cargo.toml` file

## [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" }

* fix setuptools license writing bug
- fix setuptools license writing bug

## [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" }

* replace codemetapy with an in-house writer, which enables windows support
- replace codemetapy with an in-house writer, which enables windows support

## [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" }

* **internal:** updated linters and dependencies
* **internal:** pin codemetapy version to 2.5.2 to avoid breaking changes
* fix bug caused by missing `config` section
- **internal:** updated linters and dependencies
- **internal:** pin codemetapy version to 2.5.2 to avoid breaking changes
- fix bug caused by missing `config` section

## [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" }

* **internal:** Test refactoring
* **internal:** Pydantic 2 implementation
* Added `publication_author` field to Person model
- **internal:** Test refactoring
- **internal:** Pydantic 2 implementation
- Added `publication_author` field to Person model

## [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" }

* First release
- First release
22 changes: 20 additions & 2 deletions src/somesy/core/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,15 @@ def description(self, description: str) -> None:
@property
def authors(self):
"""Return the authors of the project."""
return self._get_property(self._get_key("authors"))
authors = self._get_property(self._get_key("authors"))
if authors is None:
return []

# only return authors that can be converted to Person
authors_validated = [
author for author in authors if self._to_person(author) is not None
]
return authors_validated

@authors.setter
def authors(self, authors: List[Person]) -> None:
Expand All @@ -342,7 +350,17 @@ def authors(self, authors: List[Person]) -> None:
@property
def maintainers(self):
"""Return the maintainers of the project."""
return self._get_property(self._get_key("maintainers"))
maintainers = self._get_property(self._get_key("maintainers"))
if maintainers is None:
return []

# only return maintainers that can be converted to Person
maintainers_validated = [
maintainer
for maintainer in maintainers
if self._to_person(maintainer) is not None
]
return maintainers_validated

@maintainers.setter
def maintainers(self, maintainers: List[Person]) -> None:
Expand Down
4 changes: 2 additions & 2 deletions src/somesy/fortran/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,11 @@ def _from_person(person: Person):
return person.to_name_email_string()

@staticmethod
def _to_person(person_obj: Any) -> Person:
def _to_person(person_obj: Any) -> Optional[Person]:
"""Cannot convert from free string to person object."""
try:
return Person.from_name_email_string(person_obj)
except ValueError:
except (ValueError, AttributeError):
logger.warning(f"Cannot convert {person_obj} to Person object.")
return None

Expand Down
29 changes: 21 additions & 8 deletions src/somesy/julia/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Julia model."""

import uuid
from logging import getLogger
from typing import Optional, Set

from packaging.version import parse as parse_version
Expand All @@ -9,11 +10,13 @@
EmailStr,
Field,
TypeAdapter,
ValidationError,
field_validator,
)
from typing_extensions import Annotated

EMailAddress = TypeAdapter(EmailStr)
logger = getLogger("somesy")


class JuliaConfig(BaseModel):
Expand Down Expand Up @@ -48,15 +51,25 @@ def validate_version(cls, v):
@field_validator("authors")
@classmethod
def validate_email_format(cls, v):
"""Validate email format."""
"""Validate person format, omit person that is not in correct format, don't raise an error."""
if v is None:
return []
validated = []
for author in v:
if (
not isinstance(author, str)
or " " not in author
or not EMailAddress.validate_python(author.split(" ")[-1][1:-1])
):
raise ValueError("Invalid email format")
return v
try:
if not (
not isinstance(author, str)
or " " not in author
or not EMailAddress.validate_python(author.split(" ")[-1][1:-1])
):
validated.append(author)
else:
logger.warning(
f"Invalid email format for author {author}, omitting."
)
except ValidationError:
logger.warning(f"Invalid format for author {author}, omitting.")
return validated

@field_validator("uuid")
@classmethod
Expand Down
8 changes: 6 additions & 2 deletions src/somesy/julia/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,13 @@ def _from_person(person: Person):
return person.to_name_email_string()

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

def sync(self, metadata: ProjectMetadata) -> None:
"""Sync output file with other metadata files."""
Expand Down
20 changes: 11 additions & 9 deletions src/somesy/mkdocs/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,11 @@ def save(self, path: Optional[Path] = None) -> None:
@property
def authors(self):
"""Return the only author from the source file as list."""
authors = []
try:
self._to_person(self._get_property(self._get_key("authors")))
authors = [self._get_property(self._get_key("authors"))]
except AttributeError:
logger.warning("Cannot convert authors to Person object.")
return authors
authors = self._get_property(self._get_key("authors"))
if authors is None or self._to_person(authors) is None:
return []
else:
return [authors]

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

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

def sync(self, metadata: ProjectMetadata) -> None:
"""Sync the MkDocs object with the ProjectMetadata object."""
Expand Down
21 changes: 18 additions & 3 deletions src/somesy/package_json/models.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
"""package.json validation models."""

import re
from logging import getLogger
from typing import List, Optional, Union

from pydantic import BaseModel, EmailStr, Field, field_validator
from typing_extensions import Annotated

from somesy.core.types import HttpUrlStr

logger = getLogger("somesy")


class PackageAuthor(BaseModel):
"""Package author model."""
Expand Down Expand Up @@ -74,7 +77,7 @@ class PackageJsonConfig(BaseModel):

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

if author_email is None:
return None
return PackageAuthor(name=author_name, email=author_email, url=author_url)

@field_validator("name")
Expand Down Expand Up @@ -116,7 +121,17 @@ def validate_people(cls, v):
people = []
for p in v:
if isinstance(p, str):
people.append(cls.convert_author(p))
else:
author = cls.convert_author(p)
if author is not None:
people.append(cls.convert_author(p))
else:
logger.warning(
f"Invalid email format for maintainer/contributor {p}, omitting."
)
elif p.email is not None:
people.append(p)
else:
logger.warning(
f"Invalid email format for maintainer/contributor {p}, omitting."
)
return people
56 changes: 52 additions & 4 deletions src/somesy/package_json/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ def __init__(
@property
def authors(self):
"""Return the only author of the package.json file as list."""
# check if the author has the correct format
if isinstance(author := self._get_property(self._get_key("authors")), str):
author = PackageJsonConfig.convert_author(author)
if author is None:
return []

return [self._get_property(self._get_key("authors"))]

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

@property
def maintainers(self):
"""Return the maintainers of the package.json file."""
# check if the maintainer has the correct format
maintainers = self._get_property(self._get_key("maintainers"))
# return empty list if maintainers is None
if maintainers is None:
return []

maintainers_valid = []

for maintainer in maintainers:
if isinstance(maintainer, str):
maintainer = PackageJsonConfig.convert_author(maintainer)
if maintainer is None:
continue
maintainers_valid.append(maintainer)
return maintainers_valid

@maintainers.setter
def maintainers(self, maintainers: List[Person]) -> None:
"""Set the maintainers of the project."""
maintainers = [self._from_person(m) for m in maintainers]
self._set_property(self._get_key("maintainers"), maintainers)

@property
def contributors(self):
"""Return the contributors of the package.json file."""
return self._get_property(self._get_key("contributors"))
# check if the contributor has the correct format
contributors = self._get_property(self._get_key("contributors"))
# return empty list if contributors is None
if contributors is None:
return []

contributors_valid = []

for contributor in contributors:
if isinstance(contributor, str):
contributor = PackageJsonConfig.convert_author(contributor)
if contributor is None:
continue
contributors_valid.append(contributor)
return contributors_valid

@contributors.setter
def contributors(self, contributors: List[Person]) -> None:
Expand Down Expand Up @@ -91,9 +136,12 @@ def _to_person(person) -> Person:
"""Convert package.json dict or str for person format to project metadata person object."""
if isinstance(person, str):
# parse from package.json format
person = PackageJsonConfig.convert_author(person).model_dump(
exclude_none=True
)
person = PackageJsonConfig.convert_author(person)

if person is None:
return None

person = person.model_dump(exclude_none=True)

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