pub-analyzer 0.4.2__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pub-analyzer might be problematic. Click here for more details.
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/PKG-INFO +7 -8
- pub_analyzer-0.5.0/pub_analyzer/css/editor.tcss +60 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/css/report.tcss +15 -6
- pub_analyzer-0.5.0/pub_analyzer/internal/render.py +41 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/internal/report.py +36 -9
- pub_analyzer-0.5.0/pub_analyzer/internal/templates/author_report.typ +556 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/main.py +4 -7
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/models/institution.py +4 -3
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/models/work.py +1 -1
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/author/tables.py +2 -1
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/body.py +19 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/common/__init__.py +2 -0
- pub_analyzer-0.5.0/pub_analyzer/widgets/common/label.py +36 -0
- pub_analyzer-0.5.0/pub_analyzer/widgets/report/editor.py +80 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/report/export.py +2 -4
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/report/work.py +145 -6
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/search/results.py +2 -12
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pyproject.toml +18 -18
- pub_analyzer-0.4.2/pub_analyzer/internal/render.py +0 -68
- pub_analyzer-0.4.2/pub_analyzer/internal/templates/author/author_summary.typ +0 -112
- pub_analyzer-0.4.2/pub_analyzer/internal/templates/author/report.typ +0 -91
- pub_analyzer-0.4.2/pub_analyzer/internal/templates/author/sources.typ +0 -22
- pub_analyzer-0.4.2/pub_analyzer/internal/templates/author/works.typ +0 -154
- pub_analyzer-0.4.2/pub_analyzer/internal/templates/author/works_extended.typ +0 -109
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/LICENSE +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/README.md +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/__init__.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/css/body.tcss +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/css/buttons.tcss +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/css/checkbox.tcss +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/css/collapsible.tcss +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/css/datatable.tcss +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/css/main.tcss +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/css/search.tcss +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/css/summary.tcss +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/css/tabs.tcss +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/css/tree.tcss +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/internal/__init__.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/internal/identifier.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/models/__init__.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/models/author.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/models/concept.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/models/report.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/models/source.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/models/topic.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/__init__.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/author/__init__.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/author/cards.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/author/core.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/common/card.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/common/filesystem.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/common/filters.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/common/input.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/common/modal.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/common/selector.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/common/summary.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/institution/__init__.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/institution/cards.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/institution/core.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/institution/tables.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/report/__init__.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/report/author.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/report/cards.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/report/concept.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/report/core.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/report/grants.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/report/institution.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/report/locations.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/report/source.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/report/topic.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/search/__init__.py +2 -2
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/search/core.py +0 -0
- {pub_analyzer-0.4.2 → pub_analyzer-0.5.0}/pub_analyzer/widgets/sidebar.py +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: pub-analyzer
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: A text user interface, written in python, which automates the generation of scientific production reports using OpenAlex
|
|
5
|
-
Home-page: https://github.com/alejandrgaspar/pub-analyzer
|
|
6
5
|
License: MIT
|
|
7
6
|
Author: Alejandro Gaspar
|
|
8
7
|
Author-email: alejandro@gaspar.land
|
|
@@ -23,12 +22,12 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
23
22
|
Classifier: Programming Language :: Python :: 3.12
|
|
24
23
|
Classifier: Programming Language :: Python :: 3.13
|
|
25
24
|
Classifier: Typing :: Typed
|
|
26
|
-
Requires-Dist: httpx (==0.
|
|
27
|
-
Requires-Dist:
|
|
28
|
-
Requires-Dist:
|
|
29
|
-
Requires-Dist:
|
|
30
|
-
Requires-Dist: typst (==0.11.1)
|
|
25
|
+
Requires-Dist: httpx (==0.28.1)
|
|
26
|
+
Requires-Dist: pydantic (==2.11.7)
|
|
27
|
+
Requires-Dist: textual (==0.85.2)
|
|
28
|
+
Requires-Dist: typst (==0.13.2)
|
|
31
29
|
Project-URL: Documentation, https://pub-analyzer.com/
|
|
30
|
+
Project-URL: Homepage, https://github.com/alejandrgaspar/pub-analyzer
|
|
32
31
|
Project-URL: Repository, https://github.com/alejandrgaspar/pub-analyzer
|
|
33
32
|
Description-Content-Type: text/markdown
|
|
34
33
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/* COLORS */
|
|
2
|
+
$bg-main-color: white;
|
|
3
|
+
$bg-secondary-color: #e5e7eb;
|
|
4
|
+
$bg-secondary-color-accent: #d1d5db;
|
|
5
|
+
$text-primary-color: black;
|
|
6
|
+
|
|
7
|
+
$bg-main-color-darken: #1e293b;
|
|
8
|
+
$bg-secondary-color-darken: #0f172a;
|
|
9
|
+
$text-primary-color-darken: black;
|
|
10
|
+
|
|
11
|
+
$primary-color: #b91c1c;
|
|
12
|
+
$primary-color-accent: #991b1b;
|
|
13
|
+
$primary-color-highlight: #dc2626;
|
|
14
|
+
|
|
15
|
+
TextEditor {
|
|
16
|
+
#dialog {
|
|
17
|
+
margin: 0 10;
|
|
18
|
+
min-height: 10vh;
|
|
19
|
+
max-height: 60vh;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#text-editor-container {
|
|
23
|
+
height: 1fr;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
TextArea{
|
|
27
|
+
height: auto;
|
|
28
|
+
padding: 1 3;
|
|
29
|
+
|
|
30
|
+
background: $bg-main-color;
|
|
31
|
+
border: none;
|
|
32
|
+
|
|
33
|
+
.text-area--cursor {
|
|
34
|
+
background: $primary-color;
|
|
35
|
+
}
|
|
36
|
+
.text-area--cursor-gutter {
|
|
37
|
+
color: $bg-main-color;
|
|
38
|
+
background: $primary-color-accent;
|
|
39
|
+
}
|
|
40
|
+
.text-area--cursor-line {
|
|
41
|
+
background: $bg-main-color;
|
|
42
|
+
}
|
|
43
|
+
.text-area--matching-bracket {
|
|
44
|
+
background: $primary-color-highlight 30%;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
#actions-buttons {
|
|
50
|
+
height: 3;
|
|
51
|
+
margin-top: 1;
|
|
52
|
+
margin-bottom: 2;
|
|
53
|
+
|
|
54
|
+
align: center middle;
|
|
55
|
+
|
|
56
|
+
Button {
|
|
57
|
+
margin: 0 5;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -67,29 +67,29 @@ LoadReportWidget .button-container {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
/* Export Report Pane */
|
|
70
|
-
|
|
70
|
+
#export-form {
|
|
71
71
|
height: auto;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
.export-form-input-container {
|
|
75
75
|
height: auto;
|
|
76
76
|
margin-bottom: 2;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
.export-form-label {
|
|
80
80
|
width: 25vw;
|
|
81
81
|
border-bottom: solid $text-primary-color;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
.file-selector-container {
|
|
85
85
|
height: 3;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
.export-form-input {
|
|
89
89
|
width: 50vw;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
.export-form-buttons {
|
|
93
93
|
align: center middle;
|
|
94
94
|
height: 3;
|
|
95
95
|
}
|
|
@@ -113,6 +113,15 @@ WorkModal #dialog .abstract {
|
|
|
113
113
|
padding: 1 2;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
WorkModal TabPane EditWidget {
|
|
117
|
+
height: 3;
|
|
118
|
+
margin-top: 1;
|
|
119
|
+
|
|
120
|
+
Horizontal {
|
|
121
|
+
align: center middle;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
116
125
|
WorkModal #dialog #tables-container {
|
|
117
126
|
margin: 1 0;
|
|
118
127
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Render reports."""
|
|
2
|
+
|
|
3
|
+
import pathlib
|
|
4
|
+
import time
|
|
5
|
+
from importlib.metadata import version
|
|
6
|
+
|
|
7
|
+
import typst
|
|
8
|
+
from textual import log
|
|
9
|
+
|
|
10
|
+
from pub_analyzer.models.report import AuthorReport, InstitutionReport
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def render_report(report: AuthorReport | InstitutionReport, file_path: pathlib.Path | None) -> bytes | None:
|
|
14
|
+
"""Render report to PDF.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
report: Report Model.
|
|
18
|
+
file_path: Path to save the compiled file.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
PDF bytes or None if output file path is defined.
|
|
22
|
+
|
|
23
|
+
Raises:
|
|
24
|
+
SyntaxError: If typst compiler syntax error.
|
|
25
|
+
"""
|
|
26
|
+
if isinstance(report, AuthorReport):
|
|
27
|
+
templates_path = pathlib.Path(__file__).parent.resolve().joinpath("templates")
|
|
28
|
+
typst_file = templates_path / "author_report.typ"
|
|
29
|
+
if isinstance(report, InstitutionReport):
|
|
30
|
+
raise NotImplementedError
|
|
31
|
+
|
|
32
|
+
sys_inputs = {"report": report.model_dump_json(by_alias=True), "version": version("pub-analyzer")}
|
|
33
|
+
|
|
34
|
+
start_time = time.time()
|
|
35
|
+
if file_path:
|
|
36
|
+
result = typst.compile(input=typst_file, output=file_path, sys_inputs=sys_inputs)
|
|
37
|
+
else:
|
|
38
|
+
result = typst.compile(input=typst_file, sys_inputs=sys_inputs)
|
|
39
|
+
|
|
40
|
+
log.info(f"Typst compile time: {round((time.time() - start_time), 2)} seconds.")
|
|
41
|
+
return result
|
|
@@ -6,6 +6,7 @@ from typing import Any, NewType
|
|
|
6
6
|
|
|
7
7
|
import httpx
|
|
8
8
|
from pydantic import TypeAdapter
|
|
9
|
+
from textual import log
|
|
9
10
|
|
|
10
11
|
from pub_analyzer.internal import identifier
|
|
11
12
|
from pub_analyzer.models.author import Author, AuthorOpenAlexKey, AuthorResult, DehydratedAuthor
|
|
@@ -138,7 +139,14 @@ def _get_valid_works(works: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
|
138
139
|
In response, we have chosen to exclude such works at this stage, thus avoiding
|
|
139
140
|
the need to handle exceptions within the Model validators.
|
|
140
141
|
"""
|
|
141
|
-
|
|
142
|
+
valid_works = []
|
|
143
|
+
for work in works:
|
|
144
|
+
if work["title"] is not None:
|
|
145
|
+
valid_works.append(_add_work_abstract(work))
|
|
146
|
+
else:
|
|
147
|
+
log.warning(f"Discarded work: {work['id']}")
|
|
148
|
+
|
|
149
|
+
return valid_works
|
|
142
150
|
|
|
143
151
|
|
|
144
152
|
async def _get_works(client: httpx.AsyncClient, url: str) -> list[Work]:
|
|
@@ -156,7 +164,7 @@ async def _get_works(client: httpx.AsyncClient, url: str) -> list[Work]:
|
|
|
156
164
|
Raises:
|
|
157
165
|
httpx.HTTPStatusError: One response from OpenAlex API had an error HTTP status of 4xx or 5xx.
|
|
158
166
|
"""
|
|
159
|
-
response = await client.get(url=url)
|
|
167
|
+
response = await client.get(url=url, follow_redirects=True)
|
|
160
168
|
response.raise_for_status()
|
|
161
169
|
|
|
162
170
|
json_response = response.json()
|
|
@@ -166,7 +174,7 @@ async def _get_works(client: httpx.AsyncClient, url: str) -> list[Work]:
|
|
|
166
174
|
works_data = list(_get_valid_works(json_response["results"]))
|
|
167
175
|
|
|
168
176
|
for page_number in range(1, page_count):
|
|
169
|
-
page_result = (await client.get(url + f"&page={page_number + 1}")).json()
|
|
177
|
+
page_result = (await client.get(url + f"&page={page_number + 1}", follow_redirects=True)).json()
|
|
170
178
|
works_data.extend(_get_valid_works(page_result["results"]))
|
|
171
179
|
|
|
172
180
|
return TypeAdapter(list[Work]).validate_python(works_data)
|
|
@@ -185,10 +193,17 @@ async def _get_source(client: httpx.AsyncClient, url: str) -> Source:
|
|
|
185
193
|
Raises:
|
|
186
194
|
httpx.HTTPStatusError: One response from OpenAlex API had an error HTTP status of 4xx or 5xx.
|
|
187
195
|
"""
|
|
188
|
-
response = await client.get(url=url)
|
|
196
|
+
response = await client.get(url=url, follow_redirects=True)
|
|
189
197
|
response.raise_for_status()
|
|
190
198
|
|
|
191
|
-
|
|
199
|
+
json_response = response.json()
|
|
200
|
+
hp_url = json_response["homepage_url"]
|
|
201
|
+
if isinstance(hp_url, str):
|
|
202
|
+
if not hp_url.startswith(("http", "https")):
|
|
203
|
+
json_response["homepage_url"] = None
|
|
204
|
+
log.warning(f"Discarted source homepage url: {url}")
|
|
205
|
+
|
|
206
|
+
return Source(**json_response)
|
|
192
207
|
|
|
193
208
|
|
|
194
209
|
async def make_author_report(
|
|
@@ -242,8 +257,11 @@ async def make_author_report(
|
|
|
242
257
|
dehydrated_sources: list[DehydratedSource] = []
|
|
243
258
|
|
|
244
259
|
# Getting all works that have cited the author.
|
|
245
|
-
|
|
260
|
+
author_works_count = len(author_works)
|
|
261
|
+
for idx_work, author_work in enumerate(author_works, 1):
|
|
246
262
|
work_id = identifier.get_work_id(author_work)
|
|
263
|
+
log.info(f"[{work_id}] Work [{idx_work}/{author_works_count}]")
|
|
264
|
+
|
|
247
265
|
work_authors = _get_authors_list(authorships=author_work.authorships)
|
|
248
266
|
cited_by_api_url = (
|
|
249
267
|
f"https://api.openalex.org/works?filter=cites:{work_id}{cited_from_filter}{cited_to_filter}&sort=publication_date"
|
|
@@ -281,9 +299,12 @@ async def make_author_report(
|
|
|
281
299
|
|
|
282
300
|
# Get sources full info.
|
|
283
301
|
sources: list[Source] = []
|
|
284
|
-
|
|
302
|
+
sources_count = len(dehydrated_sources)
|
|
303
|
+
for idx, dehydrated_source in enumerate(dehydrated_sources, 1):
|
|
285
304
|
source_id = identifier.get_source_id(dehydrated_source)
|
|
286
305
|
source_url = f"https://api.openalex.org/sources/{source_id}"
|
|
306
|
+
|
|
307
|
+
log.info(f"Getting Sources... [{idx}/{sources_count}]")
|
|
287
308
|
sources.append(await _get_source(client, source_url))
|
|
288
309
|
|
|
289
310
|
# Sort sources by h_index
|
|
@@ -349,8 +370,11 @@ async def make_institution_report(
|
|
|
349
370
|
dehydrated_sources: list[DehydratedSource] = []
|
|
350
371
|
|
|
351
372
|
# Getting all works that have cited a work.
|
|
352
|
-
|
|
373
|
+
institution_works_count = len(institution_works)
|
|
374
|
+
for idx_work, institution_work in enumerate(institution_works, 1):
|
|
353
375
|
work_id = identifier.get_work_id(institution_work)
|
|
376
|
+
log.info(f"[{work_id}] Work [{idx_work}/{institution_works_count}]")
|
|
377
|
+
|
|
354
378
|
work_authors = _get_authors_list(authorships=institution_work.authorships)
|
|
355
379
|
cited_by_api_url = (
|
|
356
380
|
f"https://api.openalex.org/works?filter=cites:{work_id}{cited_from_filter}{cited_to_filter}&sort=publication_date"
|
|
@@ -388,9 +412,12 @@ async def make_institution_report(
|
|
|
388
412
|
|
|
389
413
|
# Get sources full info.
|
|
390
414
|
sources: list[Source] = []
|
|
391
|
-
|
|
415
|
+
sources_count = len(dehydrated_sources)
|
|
416
|
+
for idx, dehydrated_source in enumerate(dehydrated_sources, 1):
|
|
392
417
|
source_id = identifier.get_source_id(dehydrated_source)
|
|
393
418
|
source_url = f"https://api.openalex.org/sources/{source_id}"
|
|
419
|
+
|
|
420
|
+
log.debug(f"[{work_id}] Getting Sources... [{idx}/{sources_count}]")
|
|
394
421
|
sources.append(await _get_source(client, source_url))
|
|
395
422
|
|
|
396
423
|
# Sort sources by h_index
|