# Copyright Swiss Data Science Center (SDSC). A partnership between
# École Polytechnique Fédérale de Lausanne (EPFL) and
# Eidgenössische Technische Hochschule Zürich (ETHZ).
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Represent provenance agents."""
import re
import uuid
from typing import Optional, cast
from urllib.parse import quote
from renku.infrastructure.immutable import Slots
from renku.version import __version__, version_url
[docs]class Agent(Slots):
"""Represent executed software."""
__slots__ = ("id", "name")
id: str
name: str
def __init__(self, *, id: str, name: str, **kwargs):
super().__init__(id=id, name=name, **kwargs)
def __eq__(self, other):
if self is other:
return True
if not isinstance(other, SoftwareAgent):
return False
return self.id == other.id and self.name == other.name
def __hash__(self):
return hash((self.id, self.name))
@property
def full_identity(self):
"""Return the identity of this Agent."""
return f"{self.name} <{self.id}>"
[docs]class SoftwareAgent(Agent):
"""Represent executed software."""
# set up the default agent
RENKU_AGENT = SoftwareAgent(id=version_url, name=f"renku {__version__}")
[docs]class Person(Agent):
"""Represent a person."""
__slots__ = ("affiliation", "alternate_name", "email")
affiliation: Optional[str]
alternate_name: str
email: str
def __init__(
self,
*,
affiliation: Optional[str] = None,
alternate_name: Optional[str] = None,
email: Optional[str] = None,
id: Optional[str] = None,
name: str,
):
self._validate_email(email)
if id is None or id == "mailto:None" or id.startswith("_:"):
full_identity = Person.get_full_identity(email, affiliation, name)
id = Person.generate_id(email, full_identity)
affiliation = affiliation or None
alternate_name = alternate_name or None
super().__init__(
affiliation=affiliation, alternate_name=alternate_name, email=email, id=cast(str, id), name=name
)
def __eq__(self, other):
if self is other:
return True
if not isinstance(other, Person):
return False
return self.id == other.id and self.full_identity == other.full_identity
def __hash__(self):
return hash((self.id, self.full_identity))
[docs] @classmethod
def from_string(cls, string):
"""Create an instance from a 'Name <email>' string."""
regex_pattern = r"([^<>\[\]]*)" r"(?:<{1}\s*(\S+@\S+\.\S+){0,1}\s*>{1}){0,1}\s*" r"(?:\[{1}(.*)\]{1}){0,1}"
match = re.search(regex_pattern, string)
if match is None:
raise ValueError(f"Couldn't parse person from string: {string}")
name, email, affiliation = match.groups()
if name:
name = name.strip()
if affiliation:
affiliation = affiliation.strip()
affiliation = affiliation or None
return cls(affiliation=affiliation, email=email, name=name)
[docs] @classmethod
def from_dict(cls, data):
"""Create and instance from a dictionary."""
return cls(**data)
[docs] @staticmethod
def generate_id(email, full_identity):
"""Generate identifier for Person."""
# TODO: Do not use email as id
if email:
return f"mailto:{email}"
id = full_identity or str(uuid.uuid4().hex)
id = quote(id, safe="")
return f"/persons/{id}"
@staticmethod
def _validate_email(email):
"""Check that the email is valid."""
if not email:
return
if not isinstance(email, str) or not re.match(r"[^@]+@[^@]+\.[^@]+", email):
raise ValueError("Email address is invalid.")
[docs] @staticmethod
def get_full_identity(email, affiliation, name):
"""Return name, email, and affiliation."""
email = f" <{email}>" if email else ""
affiliation = f" [{affiliation}]" if affiliation else ""
return f"{name}{email}{affiliation}"
@property
def short_name(self):
"""Gives full name in short form."""
names = self.name.split()
if len(names) == 1:
return self.name
last_name = names[-1]
initials = [name[0] for name in names]
initials.pop()
return "{}.{}".format(".".join(initials), last_name)
@property
def full_identity(self):
"""Return name, email, and affiliation."""
return self.get_full_identity(self.email, self.affiliation, self.name)