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

62 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-08-01 22:12 +0000

1from functools import lru_cache 

2from urllib.parse import urlparse 

3 

4import requests 

5from flask import current_app 

6 

7 

8def is_orcid_url(url): 

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

10 try: 

11 parsed = urlparse(url) 

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

13 except: 

14 return False 

15 

16 

17def extract_orcid_id(url): 

18 """Extract ORCID ID from URL.""" 

19 try: 

20 parsed = urlparse(url) 

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

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

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

24 return path 

25 except: 

26 return None 

27 

28 

29@lru_cache(maxsize=1000) 

30def get_orcid_data(orcid_id): 

31 """ 

32 Fetch researcher data from ORCID API with caching. 

33 

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

35 

36 Args: 

37 orcid_id (str): The ORCID identifier 

38 

39 Returns: 

40 dict: Researcher data including name and other details 

41 """ 

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

43 return { 

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

45 "other_names": [], 

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

47 "orcid": orcid_id, 

48 } 

49 

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

51 

52 try: 

53 response = requests.get( 

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

55 ) 

56 

57 if response.status_code == 200: 

58 data = response.json() 

59 

60 # Extract relevant information 

61 result = { 

62 "name": None, 

63 "other_names": [], 

64 "biography": None, 

65 "orcid": orcid_id, 

66 } 

67 

68 # Get main name 

69 if "name" in data: 

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

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

72 if given_name or family_name: 

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

74 

75 # Get other names 

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

77 result["other_names"] = [ 

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

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

80 if "content" in name 

81 ] 

82 

83 # Get biography 

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

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

86 

87 return result 

88 

89 except Exception: 

90 return None 

91 

92 return None 

93 

94 

95def get_responsible_agent_uri(user_identifier): 

96 """ 

97 Get the appropriate URI for a responsible agent. 

98  

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

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

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

102 - Otherwise, treat it as a generic identifier 

103  

104 Args: 

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

106  

107 Returns: 

108 str: Full URI for the responsible agent 

109 """ 

110 if not user_identifier: 

111 return None 

112 

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

114 return user_identifier 

115 

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

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

118 

119 return user_identifier 

120 

121 

122def format_orcid_attribution(url): 

123 """ 

124 Format ORCID attribution for display. 

125 

126 Args: 

127 url (str): The ORCID URL 

128 

129 Returns: 

130 str: Formatted HTML for displaying ORCID attribution 

131 """ 

132 

133 orcid_id = extract_orcid_id(url) 

134 if not orcid_id: 

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

136 

137 researcher_data = get_orcid_data(orcid_id) 

138 if not researcher_data: 

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

140 

141 name = researcher_data["name"] or url 

142 

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

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

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

146 

147 return html