-
-
Notifications
You must be signed in to change notification settings - Fork 143
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
Adds Mastodon social network #10
Changes from all commits
222026d
44ffc0f
2d315dc
c4460bd
513d507
76de600
03b97cd
c95625f
125a1bb
d9e374b
37dc7c8
830dcf3
c38d293
fc6a3f7
682bce0
17a1122
94d2f56
1af54eb
7a3be88
243f7b4
69ac7fd
42ee8d9
977a4a5
9eb0467
5c76e9d
08b262d
71a02ab
1df1fdd
c9b3b0f
a74cef7
f19b8b1
21d6849
6aabf78
d132d1f
f81bc5b
45ea956
9b7639e
85b1c49
215eae9
b12274a
9cd6f3e
6c5d70e
92caab2
c301fbf
ea7a143
5e357f4
0764b39
d360813
3c44412
10e0cc9
6bd9fae
ef6df9f
6116245
05a5089
6f12ffd
57a295e
0843e33
144751f
72026f7
e17f25b
4c1ecb7
8457bdb
aa8d5e8
c0bddbc
0c83a37
237d791
711a9a8
ce34076
bd3cb64
9c3073d
af50c7c
085d222
68c1640
2b7555e
a5babe5
161b0d6
7283c9d
795a98d
08902b7
e072721
60672db
4ac7e75
b26e232
2454cc7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
--- | ||
name: Enhancement | ||
about: Suggest an idea for this project | ||
title: '' | ||
labels: enhancement | ||
assignees: sinaatalay | ||
--- | ||
|
||
A clear and concise description of what you want to happen. |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1022,10 +1022,10 @@ def doi_url(self) -> str: | |
class SocialNetwork(BaseModel): | ||
"""This class stores a social network information. | ||
|
||
Currently, only LinkedIn, Github, and Instagram are supported. | ||
Currently, only LinkedIn, Github, Mastodon, and Instagram are supported. | ||
""" | ||
|
||
network: Literal["LinkedIn", "GitHub", "Instagram", "Orcid"] = Field( | ||
network: Literal["LinkedIn", "GitHub", "Instagram", "Orcid", "Mastodon"] = Field( | ||
title="Social Network", | ||
description="The social network name.", | ||
) | ||
|
@@ -1048,13 +1048,99 @@ class Connection(BaseModel): | |
"GitHub", | ||
"Instagram", | ||
"Orcid", | ||
"Mastodon", | ||
"phone", | ||
"email", | ||
"website", | ||
"location", | ||
] | ||
value: str | ||
|
||
@staticmethod | ||
def is_valid_hostname(hostname: str) -> bool: | ||
"""Is hostname a valid hostname by RFCs 952 and 1123""" | ||
|
||
# slightly modified from | ||
# https://stackoverflow.com/a/33214423/1304076 | ||
if hostname[-1] == ".": | ||
# strip exactly one dot from the right, if present | ||
hostname = hostname[:-1] | ||
if len(hostname) > 253: | ||
return False | ||
|
||
labels = hostname.split(".") | ||
|
||
# the last label must be not all-numeric | ||
if re.match(r"[0-9]+$", labels[-1]): | ||
return False | ||
|
||
# labels cannot begin with a hyphen | ||
# labels must have at least character | ||
# labels may not have more than 63 characters | ||
allowed = re.compile(r"(?!-)[a-z0-9-]{1,63}(?<!-)$", re.IGNORECASE) | ||
return all(allowed.match(label) for label in labels) | ||
|
||
@staticmethod | ||
def MastodonUname2Url(address: str) -> Optional[HttpUrl]: | ||
"""returns profile url from a mastodon user address. | ||
|
||
Args: | ||
address (str): A Mastodon user address. E.g., "[email protected]" | ||
|
||
Returns: | ||
A pydantic HttpUrl object with the https URL for the user profile | ||
|
||
Example: | ||
``` | ||
url = MastodonUname2Url("[email protected]") | ||
assert(url == HttpUrl(http://social.example/@user)) | ||
``` | ||
|
||
Exceptions: | ||
ValueError if the address is malformed. | ||
Note that well-formed addresses should never yield | ||
syntactically invalid URLs. | ||
""" | ||
|
||
# The closest thing to a formal spec of Mastodon usernames | ||
# where these regular expressions from a (reference?) | ||
# implementation | ||
# | ||
# https://github.com/mastodon/mastodon/blob/f1657e6d6275384c199956e8872115fdcec600b0/app/models/account.rb#L68 | ||
# | ||
# USERNAME_RE = /[a-z0-9_]+([a-z0-9_.-]+[a-z0-9_]+)?/i | ||
# MENTION_RE = %r{(?<![=/[:word:]])@((#{USERNAME_RE})(?:@[[:word:].-]+[[:word:]]+)?)}i | ||
# | ||
# `[[:word:]]` in Ruby includes lots of things that could never be in a # domain name. As my intent here is to construct an HTTPS URL, | ||
# What we need are valid hostnames, | ||
# and so need to satisfy the constraints of RFC 952 and and 1123. | ||
|
||
pattern = re.compile( | ||
r""" | ||
^\s* # ignore leading spaces | ||
@? # Optional @ prefix | ||
(?P<uname>[a-z0-9_]+([a-z0-9_.-]+[a-z0-9_]+)?) # username part | ||
@ # separator | ||
(?P<domain>[a-z0-9]+([a-z0-9.-]+)?) # domain part | ||
\s*$ # ignore trailing whitespace | ||
""", | ||
re.VERBOSE | re.IGNORECASE, | ||
) | ||
|
||
m = pattern.match(address) | ||
if m is None: | ||
raise ValueError("Invalid mastodon address") | ||
uname = m.group("uname") | ||
domain = m.group("domain") | ||
|
||
# the domain part of pattern allows some things that are not | ||
# valid names. So we run a stricter check | ||
if not Connection.is_valid_hostname(domain): | ||
raise ValueError("Invalid hostname in mastodon address") | ||
|
||
url = HttpUrl(f"https://{domain}/@{uname}") | ||
return url | ||
Comment on lines
+1083
to
+1142
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see this function being used anywhere other than the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have different opinions about "clarity", but it is your project, so I will follow your suggestion. |
||
|
||
@computed_field | ||
@cached_property | ||
def url(self) -> Optional[HttpUrl | str]: | ||
|
@@ -1066,6 +1152,8 @@ def url(self) -> Optional[HttpUrl | str]: | |
url = f"https://www.instagram.com/{self.value}" | ||
elif self.name == "Orcid": | ||
url = f"https://orcid.org/{self.value}" | ||
elif self.name == "Mastodon": | ||
url = self.MastodonUname2Url(self.value) | ||
elif self.name == "email": | ||
url = f"mailto:{self.value}" | ||
elif self.name == "website": | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we need to verify Mastodon usernames, we should also verify other networks. Is it necessary to validate usernames? Moreover, if we want to do a validation, we should use the @field_validator decorator of Pydantic. This will help exception handling since we will know it's a validation error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are two separate suggestions. The reason that Mastodon needs special validation as opposed to, say, LinkedIn is that we know that know that
www.linkedin.com
ia syntactically valid hostname. We aren't going to create a malicious PDF by using it.There may be value in syntactically validating other usernames. But I do think it would be a mistake to actually perform any network calls while running RenderCV. So the validation is only syntactic.
I very much suspect you are correct about
@field_validator
, but the be perfectly honest, I don't fully grok what Pydantic is doing. However, I am happy to learn. So I will work on this.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, (if you haven’t started working on it), we can keep it like this for now because the @field_validator check should be done on the
SocialNetwork
class since theConnection
class is not used by the users directly. But if we move it toSocialNetwork
, we will need to do something about other social networks, too. Therefore, we can address this validation problem later. Whatever you prefer.