pub-analyzer 0.1.0__tar.gz → 0.4.1__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.

Files changed (79) hide show
  1. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/PKG-INFO +31 -7
  2. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/README.md +24 -1
  3. pub_analyzer-0.4.1/pub_analyzer/css/body.tcss +87 -0
  4. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/css/buttons.tcss +0 -1
  5. pub_analyzer-0.4.1/pub_analyzer/css/collapsible.tcss +31 -0
  6. pub_analyzer-0.4.1/pub_analyzer/css/main.tcss +50 -0
  7. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/css/report.tcss +10 -0
  8. pub_analyzer-0.4.1/pub_analyzer/css/summary.tcss +75 -0
  9. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/internal/identifier.py +36 -10
  10. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/internal/render.py +1 -1
  11. pub_analyzer-0.4.1/pub_analyzer/internal/report.py +407 -0
  12. pub_analyzer-0.4.1/pub_analyzer/internal/templates/author/author_summary.typ +112 -0
  13. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/internal/templates/author/report.typ +17 -3
  14. pub_analyzer-0.4.1/pub_analyzer/internal/templates/author/sources.typ +22 -0
  15. pub_analyzer-0.4.1/pub_analyzer/internal/templates/author/works.typ +154 -0
  16. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/internal/templates/author/works_extended.typ +14 -7
  17. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/main.py +11 -8
  18. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/models/author.py +20 -28
  19. pub_analyzer-0.4.1/pub_analyzer/models/concept.py +19 -0
  20. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/models/institution.py +22 -5
  21. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/models/report.py +17 -14
  22. pub_analyzer-0.4.1/pub_analyzer/models/source.py +77 -0
  23. pub_analyzer-0.4.1/pub_analyzer/models/topic.py +59 -0
  24. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/models/work.py +35 -7
  25. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/author/cards.py +15 -14
  26. pub_analyzer-0.4.1/pub_analyzer/widgets/author/core.py +122 -0
  27. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/author/tables.py +1 -1
  28. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/common/__init__.py +6 -6
  29. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/common/filesystem.py +16 -13
  30. pub_analyzer-0.4.1/pub_analyzer/widgets/common/filters.py +111 -0
  31. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/common/input.py +14 -5
  32. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/common/selector.py +1 -1
  33. pub_analyzer-0.4.1/pub_analyzer/widgets/common/summary.py +7 -0
  34. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/institution/cards.py +13 -15
  35. pub_analyzer-0.4.1/pub_analyzer/widgets/institution/core.py +122 -0
  36. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/institution/tables.py +1 -1
  37. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/report/cards.py +35 -29
  38. pub_analyzer-0.4.1/pub_analyzer/widgets/report/concept.py +47 -0
  39. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/report/core.py +101 -17
  40. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/report/export.py +26 -20
  41. pub_analyzer-0.4.1/pub_analyzer/widgets/report/grants.py +46 -0
  42. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/report/locations.py +14 -12
  43. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/report/source.py +22 -14
  44. pub_analyzer-0.4.1/pub_analyzer/widgets/report/topic.py +55 -0
  45. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/report/work.py +80 -37
  46. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/search/__init__.py +4 -4
  47. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/search/core.py +3 -1
  48. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/search/results.py +15 -16
  49. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/sidebar.py +11 -9
  50. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pyproject.toml +29 -22
  51. pub_analyzer-0.1.0/pub_analyzer/css/author.tcss +0 -78
  52. pub_analyzer-0.1.0/pub_analyzer/css/body.tcss +0 -74
  53. pub_analyzer-0.1.0/pub_analyzer/css/institution.tcss +0 -78
  54. pub_analyzer-0.1.0/pub_analyzer/css/main.tcss +0 -40
  55. pub_analyzer-0.1.0/pub_analyzer/internal/report.py +0 -252
  56. pub_analyzer-0.1.0/pub_analyzer/internal/templates/author/author_resume.typ +0 -96
  57. pub_analyzer-0.1.0/pub_analyzer/internal/templates/author/sources.typ +0 -20
  58. pub_analyzer-0.1.0/pub_analyzer/internal/templates/author/works.typ +0 -64
  59. pub_analyzer-0.1.0/pub_analyzer/models/source.py +0 -21
  60. pub_analyzer-0.1.0/pub_analyzer/widgets/author/core.py +0 -157
  61. pub_analyzer-0.1.0/pub_analyzer/widgets/institution/core.py +0 -156
  62. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/LICENSE +0 -0
  63. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/__init__.py +0 -0
  64. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/css/checkbox.tcss +0 -0
  65. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/css/datatable.tcss +0 -0
  66. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/css/search.tcss +0 -0
  67. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/css/tabs.tcss +0 -0
  68. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/css/tree.tcss +0 -0
  69. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/internal/__init__.py +0 -0
  70. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/models/__init__.py +0 -0
  71. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/__init__.py +0 -0
  72. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/author/__init__.py +0 -0
  73. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/body.py +0 -0
  74. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/common/card.py +0 -0
  75. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/common/modal.py +0 -0
  76. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/institution/__init__.py +0 -0
  77. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/report/__init__.py +0 -0
  78. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/report/author.py +0 -0
  79. {pub_analyzer-0.1.0 → pub_analyzer-0.4.1}/pub_analyzer/widgets/report/institution.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pub-analyzer
3
- Version: 0.1.0
3
+ Version: 0.4.1
4
4
  Summary: A text user interface, written in python, which automates the generation of scientific production reports using OpenAlex
5
5
  Home-page: https://github.com/alejandrgaspar/pub-analyzer
6
6
  License: MIT
@@ -20,19 +20,43 @@ Classifier: Operating System :: POSIX :: Linux
20
20
  Classifier: Programming Language :: Python :: 3
21
21
  Classifier: Programming Language :: Python :: 3.10
22
22
  Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
23
24
  Classifier: Typing :: Typed
24
- Requires-Dist: httpx (==0.24.1)
25
- Requires-Dist: jinja2 (==3.1.2)
26
- Requires-Dist: pydantic (==2.3.0)
27
- Requires-Dist: textual (==0.34.0)
28
- Requires-Dist: typst (==0.7.0)
25
+ Requires-Dist: httpx (==0.27.0)
26
+ Requires-Dist: jinja2 (==3.1.4)
27
+ Requires-Dist: pydantic (==2.7.4)
28
+ Requires-Dist: textual (==0.70.0)
29
+ Requires-Dist: typst (==0.11.1)
29
30
  Project-URL: Documentation, https://pub-analyzer.com/
30
31
  Project-URL: Repository, https://github.com/alejandrgaspar/pub-analyzer
31
32
  Description-Content-Type: text/markdown
32
33
 
33
34
  # Pub Analyzer
34
35
 
35
- Pub-Analyzer –[*Publication Analyzer*](https://github.com/alejandrgaspar/pub-analyzer)– is a Text User Interface –*TUI*–, written in Python, which automates the generation of scientific production reports using [*OpenAlex*](https://openalex.org/).
36
+ <p align="center">
37
+ <img src="https://raw.githubusercontent.com/alejandrgaspar/pub-analyzer/main/docs/assets/img/logo.png" alt="PubAnalyzer splash image" width="275">
38
+ </p>
39
+
40
+ <p align="center">
41
+ <a href="https://github.com/alejandrgaspar/pub-analyzer/actions/workflows/python-test.yml" target="_blank">
42
+ <img src="https://github.com/alejandrgaspar/pub-analyzer/actions/workflows/python-test.yml/badge.svg?branch=main" alt="Test status">
43
+ </a>
44
+ <a href="https://pypi.org/project/pub-analyzer/" target="_blank">
45
+ <img src="https://img.shields.io/pypi/v/pub-analyzer?color=%230f80c1" alt="PyPI - Version">
46
+ </a>
47
+ <a href="https://pypi.org/project/pub-analyzer/" target="_blank">
48
+ <img src="https://img.shields.io/pypi/pyversions/pub-analyzer?color=%230f80c1" alt="PyPI - Python Version">
49
+ </a>
50
+ <a href="https://github.com/alejandrgaspar/pub-analyzer/blob/main/LICENSE" target="_blank">
51
+ <img src="https://img.shields.io/github/license/alejandrgaspar/pub-analyzer?color=%2331c553" alt="License MIT">
52
+ </a>
53
+ </p>
54
+
55
+ <p align="center">
56
+ Pub-Analyzer &mdash;<a href="https://github.com/alejandrgaspar/pub-analyzer" target="_blank"><em>Publication Analyzer</em></a>&mdash; is a Text User Interface &mdash;<em>TUI</em>&mdash; written in Python, which automates the generation of scientific production reports using <a href="https://openalex.org/" target="_blank"><em>OpenAlex</em></a>.
57
+ </p>
58
+
59
+ ---
36
60
 
37
61
  ## What is Pub Analyzer for?
38
62
 
@@ -1,6 +1,29 @@
1
1
  # Pub Analyzer
2
2
 
3
- Pub-Analyzer –[*Publication Analyzer*](https://github.com/alejandrgaspar/pub-analyzer)– is a Text User Interface –*TUI*–, written in Python, which automates the generation of scientific production reports using [*OpenAlex*](https://openalex.org/).
3
+ <p align="center">
4
+ <img src="https://raw.githubusercontent.com/alejandrgaspar/pub-analyzer/main/docs/assets/img/logo.png" alt="PubAnalyzer splash image" width="275">
5
+ </p>
6
+
7
+ <p align="center">
8
+ <a href="https://github.com/alejandrgaspar/pub-analyzer/actions/workflows/python-test.yml" target="_blank">
9
+ <img src="https://github.com/alejandrgaspar/pub-analyzer/actions/workflows/python-test.yml/badge.svg?branch=main" alt="Test status">
10
+ </a>
11
+ <a href="https://pypi.org/project/pub-analyzer/" target="_blank">
12
+ <img src="https://img.shields.io/pypi/v/pub-analyzer?color=%230f80c1" alt="PyPI - Version">
13
+ </a>
14
+ <a href="https://pypi.org/project/pub-analyzer/" target="_blank">
15
+ <img src="https://img.shields.io/pypi/pyversions/pub-analyzer?color=%230f80c1" alt="PyPI - Python Version">
16
+ </a>
17
+ <a href="https://github.com/alejandrgaspar/pub-analyzer/blob/main/LICENSE" target="_blank">
18
+ <img src="https://img.shields.io/github/license/alejandrgaspar/pub-analyzer?color=%2331c553" alt="License MIT">
19
+ </a>
20
+ </p>
21
+
22
+ <p align="center">
23
+ Pub-Analyzer &mdash;<a href="https://github.com/alejandrgaspar/pub-analyzer" target="_blank"><em>Publication Analyzer</em></a>&mdash; is a Text User Interface &mdash;<em>TUI</em>&mdash; written in Python, which automates the generation of scientific production reports using <a href="https://openalex.org/" target="_blank"><em>OpenAlex</em></a>.
24
+ </p>
25
+
26
+ ---
4
27
 
5
28
  ## What is Pub Analyzer for?
6
29
 
@@ -0,0 +1,87 @@
1
+ /* COLORS */
2
+ $bg-main-color: white;
3
+ $bg-secondary-color: #e5e7eb;
4
+ $text-primary-color: black;
5
+
6
+ $bg-main-color-darken: #1e293b;
7
+ $bg-secondary-color-darken: #0f172a;
8
+ $text-primary-color-darken: white;
9
+
10
+ /* SideBar CSS */
11
+ SideBar {
12
+ dock: left;
13
+ width: 20;
14
+
15
+ background: $bg-secondary-color;
16
+ color: $text-primary-color;
17
+
18
+ Button {
19
+ text-align: start;
20
+ }
21
+
22
+ #sidebar-title {
23
+ width: 100%;
24
+ content-align: left middle;
25
+ text-style: bold;
26
+ border-bottom: heavy $text-primary-color;
27
+ }
28
+
29
+ .sidebar-options-column {
30
+ margin: 1;
31
+ padding: 1 1;
32
+ }
33
+
34
+ .sidebar-buttons-column {
35
+ height: 1fr;
36
+
37
+ .sidebar-option {
38
+ content-align: left middle;
39
+ margin: 1 0;
40
+ width: 100%;
41
+ }
42
+ }
43
+
44
+ #module-version-label {
45
+ color: $text-primary-color 25%;
46
+ text-align: center;
47
+ width: 100%;
48
+ }
49
+ }
50
+
51
+ .-dark-mode SideBar {
52
+ background: $bg-secondary-color-darken;
53
+ color: $text-primary-color-darken;
54
+
55
+ #sidebar-title {
56
+ border-bottom: heavy $text-primary-color-darken;
57
+ }
58
+
59
+ #module-version-label {
60
+ color: $text-primary-color-darken 25%;
61
+ }
62
+ }
63
+
64
+ /* Main Content CSS */
65
+ MainContent {
66
+ background: $bg-main-color;
67
+ color: $text-primary-color;
68
+
69
+ padding: 2 1 0 1;
70
+ height: 100%;
71
+
72
+ .title {
73
+ content-align: left middle;
74
+ text-style: bold;
75
+ border-bottom: heavy $text-primary-color;
76
+ width: 100%
77
+ }
78
+ }
79
+
80
+ .-dark-mode MainContent {
81
+ background: $bg-main-color-darken;
82
+ color: $text-primary-color-darken;
83
+
84
+ .title {
85
+ border-bottom: heavy $text-primary-color-darken;
86
+ }
87
+ }
@@ -9,7 +9,6 @@ Button.-primary {
9
9
  color: white;
10
10
  border-top: tall $primary-color-highlight;
11
11
  border-bottom: tall black;
12
-
13
12
  }
14
13
 
15
14
  Button.-primary:hover {
@@ -0,0 +1,31 @@
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
+ CollapsibleTitle {
12
+ background: transparent;
13
+ color: $text-primary-color;
14
+ }
15
+
16
+ CollapsibleTitle:hover {
17
+ background: transparent;
18
+ text-style: bold;
19
+ color: $text-primary-color;
20
+ }
21
+
22
+ CollapsibleTitle:focus {
23
+ background: transparent;
24
+ text-style: bold;
25
+ color: $text-primary-color;
26
+ }
27
+
28
+ Collapsible {
29
+ background: transparent;
30
+ border: none;
31
+ }
@@ -0,0 +1,50 @@
1
+ /* COLORS */
2
+ $primary-color: #b91c1c;
3
+ $primary-color-accent: #991b1b;
4
+ $primary-color-highlight: #dc2626;
5
+
6
+ * {
7
+ scrollbar-color: $primary-color;
8
+ scrollbar-color-active: $primary-color-accent;
9
+ scrollbar-color-hover: $primary-color-highlight;
10
+
11
+ transition: background 500ms in_out_cubic, color 500ms in_out_cubic;
12
+ }
13
+
14
+ Body {
15
+ layout: horizontal;
16
+ height: 1fr;
17
+ }
18
+
19
+ Footer {
20
+ dock: bottom;
21
+ background: $primary-color;
22
+
23
+ FooterKey {
24
+ background: $primary-color;
25
+ color: white;
26
+
27
+ .footer-key--key {
28
+ text-style: bold;
29
+ color: white;
30
+ background: $primary-color-accent;
31
+ }
32
+
33
+ &:hover {
34
+ background: $primary-color-highlight;
35
+ color: white;
36
+ text-style: bold;
37
+ .footer-key--key {
38
+ background: $primary-color-accent;
39
+ }
40
+ }
41
+ }
42
+ }
43
+
44
+ LoadingIndicator {
45
+ color: $primary-color-accent;
46
+ }
47
+
48
+ LoadingIndicator.-textual-loading-indicator {
49
+ background: transparent;
50
+ }
@@ -71,6 +71,10 @@ LoadReportWidget .button-container {
71
71
  }
72
72
 
73
73
  /* Export Report Pane */
74
+ ExportReportPane #export-form {
75
+ height: auto;
76
+ }
77
+
74
78
  ExportReportPane .export-form-input-container {
75
79
  height: auto;
76
80
  margin-bottom: 2;
@@ -107,6 +111,12 @@ WorkModal #dialog .cards-container {
107
111
  grid-gutter: 1 2;
108
112
  }
109
113
 
114
+ WorkModal #dialog .abstract {
115
+ height: auto;
116
+ width: 100%;
117
+ padding: 1 2;
118
+ }
119
+
110
120
  WorkModal #dialog #tables-container {
111
121
  margin: 1 0;
112
122
  }
@@ -0,0 +1,75 @@
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
+ SummaryWidget {
12
+ height: 1fr;
13
+ margin: 1 2;
14
+
15
+ /* Titles */
16
+ .block-title {
17
+ text-align: center;
18
+ width: 100%;
19
+ border-bottom: solid $text-primary-color;
20
+ }
21
+
22
+ /* Block Container */
23
+ .block-container {
24
+ padding: 1;
25
+ height: auto;
26
+ }
27
+
28
+ /* Cards */
29
+ .cards-container {
30
+ height: auto;
31
+ margin: 1 0 0 0 ;
32
+
33
+ layout: grid;
34
+ grid-size: 3 1;
35
+ grid-rows: 14;
36
+ grid-columns: 1fr;
37
+ grid-gutter: 1 2;
38
+ }
39
+
40
+ /* Info Container */
41
+ .info-container {
42
+ height: auto;
43
+
44
+ Label {
45
+ text-align: center;
46
+ width: 1fr;
47
+ }
48
+ }
49
+
50
+ /* Table */
51
+ .table-container {
52
+ height: auto;
53
+ margin: 1 0 0 0;
54
+ }
55
+
56
+ /* Filter Container */
57
+ .filter-collapsible {
58
+ margin-top: 1;
59
+
60
+ DateRangeFilter {
61
+ margin-top: 1;
62
+ }
63
+ }
64
+
65
+ /* Buttons */
66
+ .button-container {
67
+ align: center middle;
68
+ height: 5;
69
+ }
70
+ }
71
+
72
+ .-dark-mode SummaryWidget {
73
+ color: $text-primary-color-darken;
74
+ background: $bg-secondary-color;
75
+ }
@@ -1,11 +1,12 @@
1
1
  """Functions to extract OpenAlex IDs from Models."""
2
2
 
3
- from pub_analyzer.models.author import Author, AuthorResult, DehydratedAuthor
4
- from pub_analyzer.models.institution import DehydratedInstitution, Institution, InstitutionResult
3
+ from pub_analyzer.models.author import Author, AuthorOpenAlexKey, AuthorResult, DehydratedAuthor
4
+ from pub_analyzer.models.institution import DehydratedInstitution, Institution, InstitutionOpenAlexKey, InstitutionResult
5
+ from pub_analyzer.models.source import DehydratedSource, Source
5
6
  from pub_analyzer.models.work import Work
6
7
 
7
8
 
8
- def get_author_id(author: Author | AuthorResult | DehydratedAuthor) -> str:
9
+ def get_author_id(author: Author | AuthorResult | DehydratedAuthor) -> AuthorOpenAlexKey:
9
10
  """Extract OpenAlex ID from author Model.
10
11
 
11
12
  Args:
@@ -25,12 +26,12 @@ def get_author_id(author: Author | AuthorResult | DehydratedAuthor) -> str:
25
26
  ```
26
27
  """
27
28
  if author.id.path:
28
- return author.id.path.rpartition('/')[2]
29
+ return author.id.path.rpartition("/")[2]
29
30
  else:
30
- return ''
31
+ return ""
31
32
 
32
33
 
33
- def get_institution_id(institution: Institution | InstitutionResult | DehydratedInstitution) -> str:
34
+ def get_institution_id(institution: Institution | InstitutionResult | DehydratedInstitution) -> InstitutionOpenAlexKey:
34
35
  """Extract OpenAlex ID from institution Model.
35
36
 
36
37
  Args:
@@ -50,9 +51,9 @@ def get_institution_id(institution: Institution | InstitutionResult | Dehydrated
50
51
  ```
51
52
  """
52
53
  if institution.id.path:
53
- return institution.id.path.rpartition('/')[2]
54
+ return institution.id.path.rpartition("/")[2]
54
55
  else:
55
- return ''
56
+ return ""
56
57
 
57
58
 
58
59
  def get_work_id(work: Work) -> str:
@@ -75,6 +76,31 @@ def get_work_id(work: Work) -> str:
75
76
  ```
76
77
  """
77
78
  if work.id.path:
78
- return work.id.path.rpartition('/')[2]
79
+ return work.id.path.rpartition("/")[2]
79
80
  else:
80
- return ''
81
+ return ""
82
+
83
+
84
+ def get_source_id(source: DehydratedSource | Source) -> str:
85
+ """Extract OpenAlex ID from Source Model.
86
+
87
+ Args:
88
+ source: Source model instance.
89
+
90
+ Returns:
91
+ Source OpenAlex ID.
92
+
93
+ Example:
94
+ ```python
95
+ from pub_analyzer.internal.identifier import get_source_id
96
+ from pub_analyzer.models.source import Source
97
+
98
+ source = Source(id="https://openalex.org/S000000000", **kwargs)
99
+ print(get_source_id(source))
100
+ # 'S000000000'
101
+ ```
102
+ """
103
+ if source.id.path:
104
+ return source.id.path.rpartition("/")[2]
105
+ else:
106
+ return ""
@@ -30,7 +30,7 @@ async def render_template_report(report: AuthorReport | InstitutionReport) -> st
30
30
 
31
31
  # Render template
32
32
  env = Environment(loader=FileSystemLoader(searchpath=templates_path), enable_async=True, trim_blocks=True, lstrip_blocks=True)
33
- return await env.get_template("report.typ").render_async(report=report, version=version('pub-analyzer'))
33
+ return await env.get_template("report.typ").render_async(report=report, version=version("pub-analyzer"))
34
34
 
35
35
 
36
36
  async def render_report(report: AuthorReport | InstitutionReport, file_path: pathlib.Path) -> bytes: