Source code for arxiv.canonical.domain.listing
"""Provides domain concepts and logic for event listings."""
import datetime
from collections import defaultdict
from typing import NamedTuple, MutableSequence, Mapping, Tuple, Optional, \
Any, Dict, Iterable, Callable
from .base import CanonicalBase
from .eprint import EPrint, Identifier
from .version import Event, EventType
Year = int
Month = int
YearMonth = Tuple[Year, Month]
[docs]class ListingIdentifier(str):
"""
Unique identifier for a :class:`.Listing`.
Comprised of an ISO-8601 date and a name string.
"""
def __init__(self, value: str) -> None:
"""Initialize from a raw str value."""
date_part, self.name = self.split('::', 1)
self.date = datetime.datetime.strptime(date_part, '%Y-%m-%d').date()
[docs] @classmethod
def from_parts(cls, date: datetime.date, name: str) -> 'ListingIdentifier':
"""Generate from date and name parts."""
if ':' in name:
raise ValueError('Name may not contains colons `:`')
"""Generate a listing identifier from its parts."""
return cls(date.strftime(f'%Y-%m-%d::{name}'))
[docs]class Listing(CanonicalBase):
"""A collection of announcement-related events on a particular day."""
identifier: ListingIdentifier
"""Unique identifier for this listing, based on the date and name."""
events: MutableSequence[Event]
"""Events in this listing."""
def __init__(self, identifier: ListingIdentifier,
events: MutableSequence[Event]) -> None:
"""Initialize with a set of events."""
self.identifier = identifier
self.events = events
[docs] @classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'Listing':
"""Reconstitute from a native dict."""
return cls(identifier=ListingIdentifier(data['identifier']),
events=[Event.from_dict(e)
for e in data['events']])
@property
def date(self) -> datetime.date:
"""The date of this listing."""
return self.identifier.date
@property
def end_datetime(self) -> datetime.datetime:
"""Timestamp of the most recent event in this listing."""
if not self.events:
return datetime.datetime.now()
return self.events[-1].event_date
@property
def number_of_events(self) -> int:
"""Total number of events in this listing."""
return len(self.events)
@property
def number_of_events_by_type(self) -> Dict[EventType, int]:
"""Number of events in this listing by event type."""
counts: Dict[EventType, int] = defaultdict(int)
for event in self.events:
counts[event.event_type] += 1
return dict(counts.items())
@property
def number_of_versions(self) -> int:
"""Total number of :class:`.Version`s represented in this listing."""
return 0
@property
def start_datetime(self) -> datetime.datetime:
"""Timestamp of the earliest event in this listing."""
if not self.events:
return datetime.datetime.now()
return self.events[0].event_date
[docs] def to_dict(self) -> Dict[str, Any]:
"""Generate a native dict representation."""
return {
'identifier': str(self.identifier),
'events': [e.to_dict() for e in self.events]
}
[docs]class ListingDay(CanonicalBase):
"""Represents all of the listings for a particular day."""
date: datetime.date
"""Date on which the events occurred."""
listings: Mapping[str, Listing]
"""All of the listings on this date."""
def __init__(self, date: datetime.date,
listings: Mapping[str, Listing]) -> None:
self.date = date
self.listings = listings
[docs]class ListingMonth(CanonicalBase):
"""A collection of listings over a month."""
name: YearMonth
"""The year and month of this collection."""
listings: Mapping[datetime.date, ListingDay]
"""All of the listings in this month."""
def __init__(self, name: YearMonth,
listings: Mapping[datetime.date, ListingDay]) -> None:
self.name = name
self.listings = listings
@property
def year(self) -> Year:
"""Year represented by this block."""
return self.name[0]
@property
def month(self) -> Month:
"""Month represented by this block."""
return self.name[1]
[docs]class ListingYear(CanonicalBase):
"""A collection of listings over a year."""
year: int
"""The year of this collection."""
months: Mapping[Tuple[int, int], ListingMonth]
"""All of the listings in this year."""
def __init__(self, year: int,
months: Mapping[Tuple[int, int], ListingMonth]) -> None:
self.year = year
self.months = months
[docs]class AllListings(CanonicalBase):
"""All listings in the canonical record."""
name: Optional[str]
years: Mapping[int, ListingYear]
def __init__(self, name: Optional[str],
years: Mapping[int, ListingYear]) -> None:
self.name = name
self.years = years