refactor: centralize ICONS in constants module

- create core/constants.py with centralized ICONS definition
- remove duplicate ICONS definitions from multiple modules
- add emoji alternatives for better terminal support
- update all modules to use centralized constants
- export ICONS from package __init__.py

This eliminates code duplication and provides a single source
of truth for UI icons used throughout the application.
This commit is contained in:
Alexander Minges 2025-07-25 10:18:27 +02:00
parent 0f74e5dab2
commit 93d54ebc61
Signed by: Athemis
SSH key fingerprint: SHA256:TUXshgulbwL+FRYvBNo54pCsI0auROsSEgSvueKbkZ4
5 changed files with 64 additions and 48 deletions

View file

@ -51,6 +51,7 @@ from .core import (
Person,
PrimitiveMetadataField,
)
from .core.constants import ICONS
from .processing import (
CitationBuilder,
MetadataProcessor,
@ -80,6 +81,8 @@ __all__ = [
"Institution",
"License",
"Abstract",
# Constants
"ICONS",
# Metadata fields
"BaseMetadataField",
"PrimitiveMetadataField",

View file

@ -10,6 +10,7 @@ from typing import Any
from rich.console import Console
from ..core.constants import ICONS
from ..core.models import Abstract, License
from .client import APIClient
@ -66,9 +67,6 @@ class AbstractProcessor:
Retrieves and processes abstracts from CrossRef and OpenAlex.
"""
# Icons for console output - TODO: should be moved to a constants module
ICONS = {"info": "", "warning": "⚠️", "error": ""}
def __init__(self, api_client: APIClient, console: Console | None = None):
"""
Initialize with an APIClient instance.
@ -98,7 +96,7 @@ class AbstractProcessor:
if license.short in license_ok:
self.console.print(
f"\n{self.ICONS['info']} License {license.name} allows derivative works. Pulling abstract from CrossRef.",
f"\n{ICONS['info']} License {license.name} allows derivative works. Pulling abstract from CrossRef.",
style="info",
)
crossref_abstract = self._get_crossref_abstract(doi)
@ -106,18 +104,18 @@ class AbstractProcessor:
return Abstract(text=crossref_abstract, source="crossref")
else:
self.console.print(
f"\n{self.ICONS['warning']} No abstract found in CrossRef!",
f"\n{ICONS['warning']} No abstract found in CrossRef!",
style="warning",
)
else:
if license.name:
self.console.print(
f"\n{self.ICONS['info']} License {license.name} does not allow derivative works. Reconstructing abstract from OpenAlex!",
f"\n{ICONS['info']} License {license.name} does not allow derivative works. Reconstructing abstract from OpenAlex!",
style="info",
)
else:
self.console.print(
f"\n{self.ICONS['info']} Custom license does not allow derivative works. Reconstructing abstract from OpenAlex!",
f"\n{ICONS['info']} Custom license does not allow derivative works. Reconstructing abstract from OpenAlex!",
style="info",
)
@ -126,12 +124,12 @@ class AbstractProcessor:
return Abstract(text=openalex_abstract, source="openalex")
else:
self.console.print(
f"\n{self.ICONS['warning']} No abstract found in OpenAlex!",
f"\n{ICONS['warning']} No abstract found in OpenAlex!",
style="warning",
)
self.console.print(
f"\n{self.ICONS['warning']} No abstract found in either CrossRef nor OpenAlex!",
f"\n{ICONS['warning']} No abstract found in either CrossRef nor OpenAlex!",
style="warning",
)
return Abstract(text="", source="none")

View file

@ -23,28 +23,10 @@ from rich.progress import (
from rich.table import Table
from rich.theme import Theme
from .core.constants import ICONS
from .processing.metadata import MetadataProcessor
from .utils.validation import normalize_doi, sanitize_filename, validate_email_address
# Console icons for user-friendly output
ICONS = {
"success": "", # Simple checkmark
"error": "", # Simple X
"warning": "!", # Simple exclamation
"info": "", # Info symbol
"processing": "", # Three dots
"done": "", # Filled square
"file": "", # Document symbol
"folder": "", # Folder symbol
"clock": "", # Clock symbol
"search": "", # Search symbol
"data": "", # Three lines
"doi": "", # Link symbol
"total": "", # Sum symbol
"save": "", # Save/download arrow
"upload": "", # Upload arrow
}
# Theme configuration for Rich console output
THEME = Theme(
{

View file

@ -0,0 +1,43 @@
"""
Constants for doi2dataset.
This module contains shared constants used across the application,
including console icons and other configuration values.
"""
# Console icons for user-friendly output
ICONS = {
# Status indicators
"success": "", # Simple checkmark
"error": "", # Simple X / ❌ (emoji alternative)
"warning": "!", # Simple exclamation / ⚠️ (emoji alternative)
"info": "", # Info symbol / (emoji alternative)
# Process indicators
"processing": "", # Three dots / ⚙️ (emoji alternative)
"done": "", # Filled square
# File/data indicators
"file": "", # Document symbol
"folder": "", # Folder symbol
"save": "", # Save/download arrow
"upload": "", # Upload arrow
# UI elements
"clock": "", # Clock symbol
"search": "", # Search symbol
"data": "", # Three lines
"doi": "", # Link symbol
"total": "", # Sum symbol
}
# Alternative emoji-based icons for better visibility in some terminals
EMOJI_ICONS = {
"success": "",
"error": "",
"warning": "⚠️",
"info": "",
"processing": "⚙️",
"upload": "📤",
"save": "💾",
}
# Default icon set preference
DEFAULT_ICONS = ICONS

View file

@ -16,6 +16,7 @@ from rich.progress import Progress, TaskID
from ..api.client import APIClient
from ..api.processors import AbstractProcessor, LicenseProcessor
from ..core.config import Config
from ..core.constants import ICONS
from ..core.metadata_fields import (
CompoundMetadataField,
ControlledVocabularyMetadataField,
@ -41,17 +42,6 @@ class MetadataProcessor:
building metadata blocks, and optionally uploading the dataset.
"""
# Icons for console output - TODO: should be moved to a constants module
ICONS = {
"processing": "⚙️",
"success": "",
"error": "",
"warning": "⚠️",
"info": "",
"upload": "📤",
"save": "💾",
}
def __init__(
self,
doi: str,
@ -131,7 +121,7 @@ class MetadataProcessor:
dict[str, Any]: The constructed metadata dictionary.
"""
self.console.print(
f"{self.ICONS['processing']} Processing DOI: {self.doi}", style="info"
f"{ICONS['processing']} Processing DOI: {self.doi}", style="info"
)
data = self._fetch_data()
@ -148,7 +138,7 @@ class MetadataProcessor:
self._update_progress()
self.console.print(
f"\n{self.ICONS['success']} Successfully processed: {self.doi}\n",
f"\n{ICONS['success']} Successfully processed: {self.doi}\n",
style="success",
)
return metadata
@ -177,14 +167,14 @@ class MetadataProcessor:
if response is None or response.status_code != 201:
self.console.print(
f"\n{self.ICONS['error']} Failed to upload to Dataverse: {url}",
f"\n{ICONS['error']} Failed to upload to Dataverse: {url}",
style="error",
)
raise ValueError(f"Failed to upload to Dataverse: {url}")
else:
perma = response.json().get("data", {}).get("persistentId", "")
self.console.print(
f"{self.ICONS['upload']} Dataset uploaded to: {config.DATAVERSE['dataverse']} with ID {perma}",
f"{ICONS['upload']} Dataset uploaded to: {config.DATAVERSE['dataverse']} with ID {perma}",
style="info",
)
@ -205,7 +195,7 @@ class MetadataProcessor:
if response is None or response.status_code != 200:
self.console.print(
f"\n{self.ICONS['error']} Failed to fetch data for DOI: {self.doi}",
f"\n{ICONS['error']} Failed to fetch data for DOI: {self.doi}",
style="error",
)
raise ValueError(f"Failed to fetch data for DOI: {self.doi}")
@ -238,7 +228,7 @@ class MetadataProcessor:
if not corresponding_authors:
self.console.print(
f"{self.ICONS['warning']} No corresponding authors explicitly declared; PIs are used as a fallback!",
f"{ICONS['warning']} No corresponding authors explicitly declared; PIs are used as a fallback!",
style="warning",
)
pis = self._get_involved_pis(data)
@ -371,7 +361,7 @@ class MetadataProcessor:
return f"<p>This {doc_type} was published on {publication_date} in <i>{journal}</i></p>"
self.console.print(
f"{self.ICONS['warning']} No abstract header added, missing information (journal, publication date and/or document type)",
f"{ICONS['warning']} No abstract header added, missing information (journal, publication date and/or document type)",
style="warning",
)
return ""
@ -461,12 +451,12 @@ class MetadataProcessor:
metadata, f, indent=4, ensure_ascii=False, cls=CustomEncoder
)
self.console.print(
f"{self.ICONS['save']} Metadata saved in: {self.output_path}",
f"{ICONS['save']} Metadata saved in: {self.output_path}",
style="info",
)
except Exception as e:
self.console.print(
f"{self.ICONS['error']} Error saving metadata: {str(e)}\n",
f"{ICONS['error']} Error saving metadata: {str(e)}\n",
style="error",
)
raise