pub-analyzer 0.1.0__tar.gz → 0.1.2__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 (61) hide show
  1. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/PKG-INFO +2 -2
  2. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/css/report.tcss +10 -0
  3. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/internal/report.py +35 -4
  4. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/internal/templates/author/report.typ +1 -0
  5. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/internal/templates/author/works.typ +1 -0
  6. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/internal/templates/author/works_extended.typ +9 -1
  7. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/models/work.py +4 -0
  8. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/author/core.py +3 -3
  9. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/report/cards.py +7 -3
  10. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/report/core.py +16 -2
  11. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/report/export.py +25 -19
  12. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/report/work.py +14 -7
  13. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/search/core.py +3 -1
  14. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pyproject.toml +10 -10
  15. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/LICENSE +0 -0
  16. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/README.md +0 -0
  17. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/__init__.py +0 -0
  18. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/css/author.tcss +0 -0
  19. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/css/body.tcss +0 -0
  20. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/css/buttons.tcss +0 -0
  21. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/css/checkbox.tcss +0 -0
  22. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/css/datatable.tcss +0 -0
  23. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/css/institution.tcss +0 -0
  24. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/css/main.tcss +0 -0
  25. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/css/search.tcss +0 -0
  26. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/css/tabs.tcss +0 -0
  27. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/css/tree.tcss +0 -0
  28. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/internal/__init__.py +0 -0
  29. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/internal/identifier.py +0 -0
  30. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/internal/render.py +0 -0
  31. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/internal/templates/author/author_resume.typ +0 -0
  32. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/internal/templates/author/sources.typ +0 -0
  33. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/main.py +0 -0
  34. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/models/__init__.py +0 -0
  35. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/models/author.py +0 -0
  36. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/models/institution.py +0 -0
  37. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/models/report.py +0 -0
  38. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/models/source.py +0 -0
  39. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/__init__.py +0 -0
  40. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/author/__init__.py +0 -0
  41. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/author/cards.py +0 -0
  42. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/author/tables.py +0 -0
  43. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/body.py +0 -0
  44. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/common/__init__.py +0 -0
  45. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/common/card.py +0 -0
  46. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/common/filesystem.py +0 -0
  47. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/common/input.py +0 -0
  48. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/common/modal.py +0 -0
  49. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/common/selector.py +0 -0
  50. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/institution/__init__.py +0 -0
  51. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/institution/cards.py +0 -0
  52. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/institution/core.py +0 -0
  53. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/institution/tables.py +0 -0
  54. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/report/__init__.py +0 -0
  55. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/report/author.py +0 -0
  56. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/report/institution.py +0 -0
  57. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/report/locations.py +0 -0
  58. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/report/source.py +0 -0
  59. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/search/__init__.py +0 -0
  60. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/search/results.py +0 -0
  61. {pub_analyzer-0.1.0 → pub_analyzer-0.1.2}/pub_analyzer/widgets/sidebar.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.1.2
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
@@ -24,7 +24,7 @@ Classifier: Typing :: Typed
24
24
  Requires-Dist: httpx (==0.24.1)
25
25
  Requires-Dist: jinja2 (==3.1.2)
26
26
  Requires-Dist: pydantic (==2.3.0)
27
- Requires-Dist: textual (==0.34.0)
27
+ Requires-Dist: textual (==0.35.1)
28
28
  Requires-Dist: typst (==0.7.0)
29
29
  Project-URL: Documentation, https://pub-analyzer.com/
30
30
  Project-URL: Repository, https://github.com/alejandrgaspar/pub-analyzer
@@ -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
  }
@@ -61,6 +61,24 @@ def _get_citation_type(original_work_authors: list[str], cited_work_authors: lis
61
61
 
62
62
  return CitationType.TypeA if not original_set.intersection(cited_set) else CitationType.TypeB
63
63
 
64
+
65
+ def _add_work_abstract(work: dict[str, Any]) -> dict[str, Any]:
66
+ """Get work abtract from abstract_inverted_index and insert new key `abstract`.
67
+
68
+ Args:
69
+ work: Raw work.
70
+
71
+ Returns:
72
+ Work with new key `abstract`.
73
+ """
74
+ abstract_inverted_index = work.get("abstract_inverted_index")
75
+ if abstract_inverted_index:
76
+ work["abstract"] = " ".join(abstract_inverted_index)
77
+ else:
78
+ work["abstract"] = None
79
+ return work
80
+
81
+
64
82
  def _get_valid_works(works: list[dict[str, Any]]) -> list[dict[str, Any]]:
65
83
  """Skip works that do not contain enough data.
66
84
 
@@ -75,7 +93,8 @@ def _get_valid_works(works: list[dict[str, Any]]) -> list[dict[str, Any]]:
75
93
  In response, we have chosen to exclude such works at this stage, thus avoiding
76
94
  the need to handle exceptions within the Model validators.
77
95
  """
78
- return [work for work in works if work['title'] is not None]
96
+ return [_add_work_abstract(work) for work in works if work['title'] is not None]
97
+
79
98
 
80
99
  async def _get_works(client: httpx.AsyncClient, url: str) -> list[Work]:
81
100
  """Get all works given a URL.
@@ -88,12 +107,18 @@ async def _get_works(client: httpx.AsyncClient, url: str) -> list[Work]:
88
107
 
89
108
  Returns:
90
109
  List of Works Models.
110
+
111
+ Raises:
112
+ httpx.HTTPStatusError: One response from OpenAlex API had an error HTTP status of 4xx or 5xx.
91
113
  """
92
- response = (await client.get(url=url)).json()
93
- meta_info = response["meta"]
114
+ response = await client.get(url=url)
115
+ response.raise_for_status()
116
+
117
+ json_response = response.json()
118
+ meta_info = json_response["meta"]
94
119
  page_count = math.ceil(meta_info["count"] / meta_info["per_page"])
95
120
 
96
- works_data = list(_get_valid_works(response["results"]),)
121
+ works_data = list(_get_valid_works(json_response["results"]),)
97
122
 
98
123
  for page_number in range(1, page_count):
99
124
  page_result = (await client.get(url + f"&page={page_number + 1}")).json()
@@ -112,6 +137,9 @@ async def make_author_report(author: Author, from_date: datetime.date | None = N
112
137
 
113
138
  Returns:
114
139
  Author's scientific production report Model.
140
+
141
+ Raises:
142
+ httpx.HTTPStatusError: One response from OpenAlex API had an error HTTP status of 4xx or 5xx.
115
143
  """
116
144
  author_id = identifier.get_author_id(author)
117
145
 
@@ -188,6 +216,9 @@ async def make_institution_report(
188
216
 
189
217
  Returns:
190
218
  Institution's scientific production report Model.
219
+
220
+ Raises:
221
+ httpx.HTTPStatusError: One response from OpenAlex API had an error HTTP status of 4xx or 5xx.
191
222
  """
192
223
  institution_id = identifier.get_institution_id(institution)
193
224
 
@@ -17,6 +17,7 @@
17
17
 
18
18
  // Text config
19
19
  #set text(size: 10pt)
20
+ #set par(justify: true)
20
21
 
21
22
  // Override reference
22
23
  #show ref: it => {
@@ -1,5 +1,6 @@
1
1
  // Works
2
2
  = Works.
3
+
3
4
  #linebreak()
4
5
 
5
6
  #grid(
@@ -6,8 +6,16 @@
6
6
  {% endif %}
7
7
 
8
8
  === #text()[#"{{ work.work.title.replace('"', '\\"') }}"] <work_{{ loop.index }}>
9
+
10
+ #linebreak()
11
+
12
+ {% if work.work.abstract %}
13
+ #text()[#"{{ work.work.abstract.replace('"', '\\"') }}"]
14
+
9
15
  #linebreak()
10
16
 
17
+ {% endif %}
18
+
11
19
  // Cards
12
20
  #grid(
13
21
  columns: (1fr, 1fr, 1fr),
@@ -16,7 +24,7 @@
16
24
  #align(center)[_Authorships_]
17
25
  #parbreak()
18
26
  {% for authorship in work.work.authorships[:10] %}
19
- - *{{ authorship.author_position }}:* #text({% if authorship.author.display_name == report.author.display_name %}rgb("909d63"){% endif %})[{{ authorship.author.display_name }}]
27
+ - *{{ authorship.author_position }}:* #underline([#link("{{ authorship.author.orcid or authorship.author.id }}")[#text({% if authorship.author.display_name == report.author.display_name %}rgb("909d63"){% endif %})[{{ authorship.author.display_name }}]]])
20
28
  {% endfor %}
21
29
  {% if work.work.authorships|length > 10 %}
22
30
  - *...*
@@ -79,6 +79,7 @@ class Work(BaseModel):
79
79
  ids: WorkIDs
80
80
 
81
81
  title: str
82
+ abstract: str | None = None
82
83
  publication_year: int
83
84
  publication_date: str
84
85
  language: str | None = None
@@ -92,6 +93,9 @@ class Work(BaseModel):
92
93
  authorships: list[Authorship]
93
94
 
94
95
  cited_by_count: int
96
+ """This number comes from the OpenAlex API, represents ALL citations to this work, and may not always be correct.
97
+ To use a verified number that respects the applied filters use [WorkReport][pub_analyzer.models.report.WorkReport].
98
+ """
95
99
 
96
100
  referenced_works: list[HttpUrl]
97
101
  cited_by_api_url: HttpUrl
@@ -67,10 +67,10 @@ class AuthorResumeWidget(Static):
67
67
  from_input = self.query_one("#from-date", DateInput)
68
68
  to_input = self.query_one("#to-date", DateInput)
69
69
 
70
- if checkbox.value and from_input.value and to_input.value:
70
+ if checkbox.value and (from_input.value or to_input.value):
71
71
  date_format = "%Y-%m-%d"
72
- from_date = datetime.datetime.strptime(from_input.value, date_format)
73
- to_date = datetime.datetime.strptime(to_input.value, date_format)
72
+ from_date = datetime.datetime.strptime(from_input.value, date_format) if from_input.value else None
73
+ to_date = datetime.datetime.strptime(to_input.value, date_format) if to_input.value else None
74
74
 
75
75
  report_widget = CreateAuthorReportWidget(author=self.author, from_date=from_date, to_date=to_date)
76
76
  else:
@@ -117,8 +117,12 @@ class CitationMetricsCard(Card):
117
117
 
118
118
  def compose(self) -> ComposeResult:
119
119
  """Compose card."""
120
+ type_a_count = self.work_report.citation_resume.type_a_count
121
+ type_b_count = self.work_report.citation_resume.type_b_count
122
+ cited_by_count = type_a_count + type_b_count
123
+
120
124
  yield Label('[italic]Citation[/italic]', classes='card-title')
121
125
 
122
- yield Label(f'[bold]Count:[/bold] {self.work_report.work.cited_by_count}')
123
- yield Label(f'[bold]Type A:[/bold] {self.work_report.citation_resume.type_a_count}')
124
- yield Label(f'[bold]Type B:[/bold] {self.work_report.citation_resume.type_b_count}')
126
+ yield Label(f'[bold]Count:[/bold] {cited_by_count}')
127
+ yield Label(f'[bold]Type A:[/bold] {type_a_count}')
128
+ yield Label(f'[bold]Type B:[/bold] {type_b_count}')
@@ -4,6 +4,7 @@ import datetime
4
4
  import pathlib
5
5
  from enum import Enum
6
6
 
7
+ import httpx
7
8
  from pydantic import TypeAdapter, ValidationError
8
9
  from textual import on
9
10
  from textual.app import ComposeResult
@@ -89,7 +90,19 @@ class CreateReportWidget(Static):
89
90
 
90
91
  async def mount_report(self) -> None:
91
92
  """Mount report."""
92
- report_widget = await self.make_report()
93
+ try:
94
+ report_widget = await self.make_report()
95
+ except httpx.HTTPStatusError as exc:
96
+ self.query_one(LoadingIndicator).display = False
97
+ status_error = f"HTTP Exception for url: {exc.request.url}. Status code: {exc.response.status_code}"
98
+ self.app.notify(
99
+ title="Error making report!",
100
+ message=f"The report could not be generated due to a problem with the OpenAlex API. {status_error}",
101
+ severity="error",
102
+ timeout=20.0
103
+ )
104
+ return None
105
+
93
106
  container = self.query_one(Container)
94
107
  await container.mount(report_widget)
95
108
 
@@ -198,9 +211,10 @@ class LoadReportWidget(Static):
198
211
  match event.value:
199
212
  case self.EntityType.AUTHOR:
200
213
  self.entity_handler = self.EntityType.AUTHOR
201
-
202
214
  case self.EntityType.INSTITUTION:
203
215
  self.entity_handler = self.EntityType.INSTITUTION
216
+ case _:
217
+ raise NotImplementedError
204
218
 
205
219
  def compose(self) -> ComposeResult:
206
220
  """Compose load report widget."""
@@ -4,7 +4,7 @@ import pathlib
4
4
  from datetime import datetime
5
5
  from enum import Enum
6
6
 
7
- from textual import on
7
+ from textual import on, work
8
8
  from textual.app import ComposeResult
9
9
  from textual.containers import Horizontal, Vertical, VerticalScroll
10
10
  from textual.widgets import Button, Label
@@ -46,9 +46,10 @@ class ExportReportPane(VerticalScroll):
46
46
  match event.value:
47
47
  case self.ExportFileType.JSON:
48
48
  file_name_input.value = f"{self.suggest_prefix}-{datetime.now().strftime('%m-%d-%Y')}.json"
49
-
50
49
  case self.ExportFileType.PDF:
51
50
  file_name_input.value = f"{self.suggest_prefix}-{datetime.now().strftime('%m-%d-%Y')}.pdf"
51
+ case _:
52
+ file_name_input.value = f"{self.suggest_prefix}-{datetime.now().strftime('%m-%d-%Y')}"
52
53
 
53
54
  @on(FileSystemSelector.FileSelected)
54
55
  def enable_button(self, event: FileSystemSelector.FileSelected) -> None:
@@ -58,31 +59,36 @@ class ExportReportPane(VerticalScroll):
58
59
  else:
59
60
  self.query_one(Button).disabled = True
60
61
 
62
+ @work(exclusive=True, thread=True)
63
+ async def _export_report(self, file_type: ExportFileType, file_path: pathlib.Path) -> None:
64
+ """Export report."""
65
+ match file_type:
66
+ case self.ExportFileType.JSON:
67
+ with open(file_path, mode="w", encoding="utf-8") as file:
68
+ file.write(self.report.model_dump_json(indent=2, by_alias=True))
69
+ case self.ExportFileType.PDF:
70
+ report_bytes = await render_report(report=self.report, file_path=file_path)
71
+ with open(file_path, mode="wb") as file:
72
+ file.write(report_bytes)
73
+
74
+ self.app.call_from_thread(
75
+ self.app.notify,
76
+ title="Report exported successfully!",
77
+ message=f"The report was exported correctly. You can go see it at [i]{file_path}[/]",
78
+ timeout=20.0
79
+ )
80
+
61
81
  @on(Button.Pressed, "#export-report-button")
62
82
  async def export_report(self) -> None:
63
- """Export Report."""
83
+ """Handle export report button."""
64
84
  export_path = self.query_one(FileSystemSelector).path_selected
65
85
  file_name = self.query_one(Input).value
66
86
  file_type = self.query_one(self.ExportTypeSelector).value
67
87
 
68
- if export_path and file_name:
88
+ if export_path and file_name and file_type:
69
89
  file_path = export_path.joinpath(file_name)
70
-
71
- match file_type:
72
- case self.ExportFileType.JSON:
73
- with open(file_path, mode="w", encoding="utf-8") as file:
74
- file.write(self.report.model_dump_json(indent=2, by_alias=True))
75
- case self.ExportFileType.PDF:
76
- report_bytes = await render_report(report=self.report, file_path=file_path)
77
- with open(file_path, mode="wb") as file:
78
- file.write(report_bytes)
79
-
90
+ self._export_report(file_type=file_type, file_path=file_path)
80
91
  self.query_one(Button).disabled = True
81
- self.app.notify(
82
- title="Report exported successfully!",
83
- message=f"The report was exported correctly. You can go see it at [i]{file_path}[/]",
84
- timeout=20.0
85
- )
86
92
 
87
93
  def compose(self) -> ComposeResult:
88
94
  """Compose content pane."""
@@ -107,7 +107,6 @@ class WorkModal(Modal[None]):
107
107
  # Citation Metrics
108
108
  yield CitationMetricsCard(work_report=self.work_report)
109
109
 
110
-
111
110
  with TabbedContent(id="tables-container"):
112
111
  # Citations Table
113
112
  with TabPane("Cited By Works"):
@@ -121,6 +120,10 @@ class WorkModal(Modal[None]):
121
120
  yield LocationsTable(self.work_report.work.locations)
122
121
  else:
123
122
  yield Label("No sources found.")
123
+ # Abtract if exists
124
+ if self.work_report.work.abstract:
125
+ with TabPane("Abstract"):
126
+ yield Label(self.work_report.work.abstract, classes="abstract")
124
127
 
125
128
 
126
129
  class WorksTable(Static):
@@ -154,11 +157,14 @@ class WorksTable(Static):
154
157
 
155
158
  def compose(self) -> ComposeResult:
156
159
  """Generate Table."""
157
- first_pub_year = self.report.works[0].work.publication_year
158
- last_pub_year = self.report.works[-1].work.publication_year
159
-
160
- work_table = Table(title=f"Works from {first_pub_year} to {last_pub_year}", expand=True, show_lines=True)
161
-
160
+ if self.report.works:
161
+ first_pub_year = self.report.works[0].work.publication_year
162
+ last_pub_year = self.report.works[-1].work.publication_year
163
+ title = f"Works from {first_pub_year} to {last_pub_year}"
164
+ else:
165
+ title = "Works"
166
+
167
+ work_table = Table(title=title, expand=True, show_lines=True)
162
168
  work_table.add_column('', justify='center', vertical='middle')
163
169
  work_table.add_column('Title', ratio=3)
164
170
  work_table.add_column('Type', ratio=2)
@@ -205,4 +211,5 @@ class WorkReportPane(VerticalScroll):
205
211
  yield WorksTypeResumeCard(report=self.report)
206
212
  yield OpenAccessResumeCard(report=self.report)
207
213
 
208
- yield WorksTable(report=self.report)
214
+ if self.report.works:
215
+ yield WorksTable(report=self.report)
@@ -86,9 +86,11 @@ class FinderWidget(Static):
86
86
  case self.OpenAlexEndPoint.AUTHOR:
87
87
  self.url = self.OpenAlexEndPoint.AUTHOR
88
88
  search_bar.placeholder = "Search author"
89
-
90
89
  case self.OpenAlexEndPoint.INSTITUTION:
91
90
  self.url = self.OpenAlexEndPoint.INSTITUTION
92
91
  search_bar.placeholder = "Search institution"
92
+ case _:
93
+ self.url = self.OpenAlexEndPoint.AUTHOR
94
+ search_bar.placeholder = "Search author"
93
95
 
94
96
  search_bar.value = ""
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "pub-analyzer"
7
- version = "0.1.0"
7
+ version = "0.1.2"
8
8
  description = "A text user interface, written in python, which automates the generation of scientific production reports using OpenAlex"
9
9
 
10
10
  authors = ["Alejandro Gaspar <alejandro@gaspar.land>"]
@@ -39,7 +39,7 @@ pub-analyzer = "pub_analyzer.main:run"
39
39
  [tool.poetry.dependencies]
40
40
  python = "^3.10"
41
41
 
42
- textual = "0.34.0"
42
+ textual = "0.35.1"
43
43
  httpx = "0.24.1"
44
44
  pydantic = "2.3.0"
45
45
 
@@ -49,28 +49,28 @@ jinja2 = "3.1.2"
49
49
  [tool.poetry.group.dev.dependencies]
50
50
  textual-dev = "1.1.0"
51
51
 
52
- pre-commit = "3.3.3"
52
+ pre-commit = "3.4.0"
53
53
  mypy = "1.5.1"
54
- ruff = "0.0.286"
54
+ ruff = "0.0.287"
55
55
 
56
- pytest = "7.4.0"
57
- pytest-asyncio = "0.21.0"
56
+ pytest = "7.4.1"
57
+ pytest-asyncio = "0.21.1"
58
58
  respx = "0.20.2"
59
59
  vcrpy = "5.1.0"
60
60
  pytest-recording = "0.13.0"
61
61
 
62
62
  [tool.poetry.group.docs.dependencies]
63
63
  mkdocs = "1.5.2"
64
- mkdocs-material = "9.2.3"
64
+ mkdocs-material = "9.2.8"
65
65
 
66
- mkdocstrings = {extras = ["python"], version = "0.22.0"}
67
- mkdocstrings-python = "1.5.2"
66
+ mkdocstrings = {extras = ["python"], version = "0.23.0"}
67
+ mkdocstrings-python = "1.6.1"
68
68
 
69
69
  [tool.mypy]
70
70
  strict = true
71
71
 
72
72
  [tool.ruff]
73
- required-version = "0.0.286"
73
+ required-version = "0.0.287"
74
74
  target-version = "py310"
75
75
 
76
76
  line-length = 140
File without changes
File without changes