Coverage for heritrace / apis / orcid.py: 94%

62 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-21 12:56 +0000

1# SPDX-FileCopyrightText: 2024-2025 Arcangelo Massari <arcangelo.massari@unibo.it> 

2# 

3# SPDX-License-Identifier: ISC 

4 

5from functools import lru_cache 

6from urllib.parse import urlparse 

7 

8import requests 

9from flask import current_app 

10 

11 

12def is_orcid_url(url): 

13 """Check if a URL is an ORCID URL.""" 

14 try: 

15 parsed = urlparse(url) 

16 return parsed.netloc == "orcid.org" 

17 except: 

18 return False 

19 

20 

21def extract_orcid_id(url): 

22 """Extract ORCID ID from URL.""" 

23 try: 

24 parsed = urlparse(url) 

25 path = parsed.path.strip("/") 

26 if path.startswith("https://orcid.org/"): 

27 path = path[len("https://orcid.org/") :] 

28 return path 

29 except: 

30 return None 

31 

32 

33@lru_cache(maxsize=1000) 

34def get_orcid_data(orcid_id): 

35 """ 

36 Fetch researcher data from ORCID API with caching. 

37 

38 In demo mode, this function returns synthetic data without calling the external API. 

39 

40 Args: 

41 orcid_id (str): The ORCID identifier 

42 

43 Returns: 

44 dict: Researcher data including name and other details 

45 """ 

46 if current_app.config.get("ENV") == "demo": 

47 return { 

48 "name": f"Demo User ({orcid_id})", 

49 "other_names": [], 

50 "biography": "This is a synthetic user account for demo purposes.", 

51 "orcid": orcid_id, 

52 } 

53 

54 headers = {"Accept": "application/json"} 

55 

56 try: 

57 response = requests.get( 

58 f"https://pub.orcid.org/v3.0/{orcid_id}/person", headers=headers, timeout=5 

59 ) 

60 

61 if response.status_code == 200: 

62 data = response.json() 

63 

64 # Extract relevant information 

65 result = { 

66 "name": None, 

67 "other_names": [], 

68 "biography": None, 

69 "orcid": orcid_id, 

70 } 

71 

72 # Get main name 

73 if "name" in data: 

74 given_name = data["name"].get("given-names", {}).get("value", "") 

75 family_name = data["name"].get("family-name", {}).get("value", "") 

76 if given_name or family_name: 

77 result["name"] = f"{given_name} {family_name}".strip() 

78 

79 # Get other names 

80 if "other-names" in data and "other-name" in data["other-names"]: 

81 result["other_names"] = [ 

82 name.get("content", "") 

83 for name in data["other-names"]["other-name"] 

84 if "content" in name 

85 ] 

86 

87 # Get biography 

88 if "biography" in data and data["biography"]: 

89 result["biography"] = data["biography"].get("content", "") 

90 

91 return result 

92 

93 except Exception: 

94 return None 

95 

96 return None 

97 

98 

99def get_responsible_agent_uri(user_identifier): 

100 """ 

101 Get the appropriate URI for a responsible agent. 

102  

103 This function handles both ORCID IDs and full URIs flexibly: 

104 - If the identifier is already a full URI (starts with http/https), use it as-is 

105 - If it's an ORCID ID, convert it to the standard ORCID URI format 

106 - Otherwise, treat it as a generic identifier 

107  

108 Args: 

109 user_identifier (str): User identifier (ORCID ID, URI, or other) 

110  

111 Returns: 

112 str: Full URI for the responsible agent 

113 """ 

114 if not user_identifier: 

115 return None 

116 

117 if user_identifier.startswith(('http://', 'https://')): 

118 return user_identifier 

119 

120 if len(user_identifier) == 19 and user_identifier.count('-') == 3: 

121 return f"https://orcid.org/{user_identifier}" 

122 

123 return user_identifier 

124 

125 

126def format_orcid_attribution(url): 

127 """ 

128 Format ORCID attribution for display. 

129 

130 Args: 

131 url (str): The ORCID URL 

132 

133 Returns: 

134 str: Formatted HTML for displaying ORCID attribution 

135 """ 

136 

137 orcid_id = extract_orcid_id(url) 

138 if not orcid_id: 

139 return f'<a href="{url}" target="_blank">{url}</a>' 

140 

141 researcher_data = get_orcid_data(orcid_id) 

142 if not researcher_data: 

143 return f'<a href="{url}" target="_blank">{url}</a>' 

144 

145 name = researcher_data["name"] or url 

146 

147 html = f'<a href="{url}" target="_blank" class="orcid-attribution">' 

148 html += f'<img src="/static/images/orcid-logo.png" alt="ORCID iD" class="orcid-icon mx-1 mb-1" style="width: 16px; height: 16px;">' 

149 html += f"{name} [orcid:{orcid_id}]</a>" 

150 

151 return html