Skip to content

Commit 42ae9c2

Browse files
authored
Optional email (#102)
* make email optional in the person model * remove none's from the existing people list * make email optional in setuptools model * remove check of no email value since it is done in core now * update tests for optional email * show email is optional in manual * update doc dev docs for ruff usage
1 parent d28180d commit 42ae9c2

10 files changed

Lines changed: 33 additions & 19 deletions

File tree

docs/dev_guide.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,7 @@ Best practices for modern Python development are implemented by using:
5252
* `pytest` for unit testing
5353
* `hypothesis` for property-based testing
5454
* `pre-commit` for orchestrating linters, formatters and other utilities
55-
* `black` for source-code formatting
56-
* `autoflake` for automatically removing unused imports
57-
* `flake8` for general linting (using various linter plugins)
55+
* `ruff` for source-code formatting and for general linting
5856
* `pydocstyle` for checking docstring conventions
5957
* `interrogate` for computing docstring coverage
6058
* `mypy` for editor-independent type-checking

docs/manual.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,12 @@ some of the currently supported formats. Bold field names are mandatory, the oth
117117

118118
=== "Person Metadata"
119119

120-
| Somesy Field | Poetry Config | SetupTools Config | Java POM | Julia Config | Fortran Config | package.json | mkdocs.yml | Rust Config | CITATION.cff | CodeMeta |
120+
| Somesy Field | Poetry Config | SetupTools Config | Java POM | Julia Config | Fortran Config | package.json | mkdocs.yml | Rust Config | CITATION.cff | CodeMeta |
121121
| ---------------- | ------------- | ----------------- | ------------ | ------------ | -------------- | ------------ | ---------- | -------------- | --------------- | -------------- |
122122
| | | | | | | | | | | |
123123
| **given-names** | name+email | name | name | name+email | name+email | name | name+email | name+email | givenName | name+email |
124124
| **family-names** | name+email | name | name | name+email | name+email | name | name+email | name+email | familyName | name+email |
125-
| **email** | name+email | email | email | name+email | name+email | email | name+email | name+email | email | name+email |
125+
| email | name+email | email | email | name+email | name+email | email | name+email | name+email | email | name+email |
126126
| orcid | - | - | url | - | - | url | - | - | id | - |
127127
| *(many others)* | - | - | - | - | - | - | - | - | *(same)* | - |
128128

src/somesy/core/models.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,12 +274,12 @@ class Person(SomesyBaseModel):
274274
] = None
275275

276276
email: Annotated[
277-
str,
277+
Optional[str],
278278
Field(
279279
pattern=r"^[\S]+@[\S]+\.[\S]{2,}$",
280280
description="The person's email address.",
281281
),
282-
]
282+
] = None
283283

284284
family_names: Annotated[
285285
str, Field(alias="family-names", description="The person's family names.")
@@ -400,7 +400,10 @@ def full_name(self) -> str:
400400

401401
def to_name_email_string(self) -> str:
402402
"""Convert project metadata person object to poetry string for person format `full name <x@y.z>`."""
403-
return f"{self.full_name} <{self.email}>"
403+
if self.email:
404+
return f"{self.full_name} <{self.email}>"
405+
else:
406+
return self.full_name
404407

405408
@classmethod
406409
def from_name_email_string(cls, person: str) -> Person:
@@ -409,6 +412,14 @@ def from_name_email_string(cls, person: str) -> Person:
409412
If the name is `A B C`, then `A B` will be the given names and `C` will be the family name.
410413
"""
411414
m = re.match(r"\s*([^<]+)<([^>]+)>", person)
415+
if m is None:
416+
names = list(map(lambda s: s.strip(), person.split()))
417+
return Person(
418+
**{
419+
"given-names": " ".join(names[:-1]),
420+
"family-names": names[-1],
421+
}
422+
)
412423
names, mail = (
413424
list(map(lambda s: s.strip(), m.group(1).split())),
414425
m.group(2).strip(),

src/somesy/core/writer.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,11 @@ def _to_person(person_obj: Any) -> Person:
290290
@classmethod
291291
def _parse_people(cls, people: Optional[List[Any]]) -> List[Person]:
292292
"""Return a list of Persons parsed from list of format-specific people representations."""
293-
return list(map(cls._to_person, people or []))
293+
# remove None values
294+
people = [p for p in people if p is not None]
295+
296+
people = list(map(cls._to_person, people or []))
297+
return people
294298

295299
# ----
296300
# individual magic getters and setters

src/somesy/pyproject/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ class STPerson(BaseModel):
154154
"""Person model for setuptools."""
155155

156156
name: Annotated[str, Field(min_length=1)]
157-
email: Annotated[str, Field(min_length=1)]
157+
email: Annotated[Optional[str], Field(min_length=1)] = None
158158

159159

160160
class URLs(BaseModel):

src/somesy/rust/writer.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,7 @@ def _from_person(person: Person):
9191
@staticmethod
9292
def _to_person(person_obj: str) -> Optional[Person]:
9393
"""Parse rust person string to a Person. It has format "full name <email>." but email is optional."""
94-
try:
95-
return Person.from_name_email_string(person_obj)
96-
except (ValueError, AttributeError):
97-
return None
94+
return Person.from_name_email_string(person_obj)
9895

9996
@classmethod
10097
def _parse_people(cls, people: Optional[List[Any]]) -> List[Person]:

tests/output/test_julia_writer.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ def test_without_email(tmp_path, person):
131131
# load and sync
132132
pj = Julia(project_file)
133133

134-
assert len(pj.authors) == 0
134+
assert len(pj.authors) == 1
135+
assert pj.authors[0] == person.full_name
135136

136137
pm = ProjectMetadata(
137138
name="My awesome project",

tests/output/test_mkdocs_writer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def mkdocs(load_files, file_types):
1313
def test_content_match(mkdocs: MkDocs):
1414
assert mkdocs.name == "test-package"
1515
assert mkdocs.description == "This is a test package for demonstration purposes."
16-
assert len(mkdocs.authors) == 0
16+
assert len(mkdocs.authors) == 1
1717

1818

1919
def test_sync(mkdocs: MkDocs, somesy_input: dict):

tests/output/test_pyproject_writer.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ def test_from_to_person(person):
7979

8080
# this should return None since there is no email
8181
p = Poetry._to_person("John Doe")
82-
assert p is None
82+
assert p.given_names == "John"
83+
assert p.family_names == "Doe"
8384

8485
# test for setuptools
8586
assert SetupTools._from_person(person) == {
@@ -195,7 +196,7 @@ def test_without_email(tmp_path, person):
195196

196197
# load and sync
197198
p = Poetry(pyproject_file)
198-
assert len(p.authors) == 0
199+
assert len(p.authors) == 1
199200

200201
pm = ProjectMetadata(
201202
name="My awesome project",

tests/output/test_rust_writer.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ def test_from_to_person(person):
4747
assert p.email == person.email
4848

4949
# rust also has only name format
50-
assert Rust._to_person(person.full_name) == None
50+
converted_person = Rust._to_person(person.full_name)
51+
assert converted_person.given_names == person.given_names
52+
assert converted_person.family_names == person.family_names
5153

5254

5355
def test_person_merge(rust_file, person):

0 commit comments

Comments
 (0)