Source code for arxiv.submission.domain.agent

"""Data structures for agents."""

import hashlib
from typing import Any, Optional, List, Union

from dataclasses import dataclass, field
from dataclasses import asdict

from .meta import Classification

__all__ = ('Agent', 'User', 'System', 'Client', 'agent_factory')


[docs]@dataclass class Agent: """ Base class for agents in the submission system. An agent is an actor/system that generates/is responsible for events. """ native_id: str """Type-specific identifier for the agent. This might be an URI.""" def __post_init__(self): """Set derivative fields.""" self.agent_type = self.__class__.get_agent_type() self.agent_identifier = self.get_agent_identifier()
[docs] @classmethod def get_agent_type(cls) -> str: """Get the name of the instance's class.""" return cls.__name__
[docs] def get_agent_identifier(self) -> str: """ Get the unique identifier for this agent instance. Based on both the agent type and native ID. """ h = hashlib.new('sha1') h.update(b'%s:%s' % (self.agent_type.encode('utf-8'), str(self.native_id).encode('utf-8'))) return h.hexdigest()
def __eq__(self, other: Any) -> bool: """Equality comparison for agents based on type and identifier.""" if not isinstance(other, self.__class__): return False return self.agent_identifier == other.agent_identifier
[docs]@dataclass class User(Agent): """An (human) end user.""" email: str = field(default_factory=str) username: str = field(default_factory=str) forename: str = field(default_factory=str) surname: str = field(default_factory=str) suffix: str = field(default_factory=str) name: str = field(default_factory=str) identifier: Optional[str] = field(default=None) affiliation: str = field(default_factory=str) hostname: Optional[str] = field(default=None) """Hostname or IP address from which user requests are originating.""" endorsements: List[str] = field(default_factory=list) agent_type: str = field(default_factory=str) agent_identifier: str = field(default_factory=str) def __post_init__(self): """Set derivative fields.""" self.name = self.get_name() self.agent_type = self.get_agent_type()
[docs] def get_name(self): """Full name of the user.""" return f"{self.forename} {self.surname} {self.suffix}"
# TODO: extend this to support arXiv-internal services.
[docs]@dataclass class System(Agent): """The submission application (this application).""" agent_type: str = field(default_factory=str) agent_identifier: str = field(default_factory=str) username: str = field(default_factory=str) hostname: str = field(default_factory=str) def __post_init__(self): """Set derivative fields.""" super(System, self).__post_init__() self.username = self.native_id self.hostname = self.native_id self.agent_type = self.get_agent_type()
[docs]@dataclass class Client(Agent): """A non-human third party, usually an API client.""" hostname: Optional[str] = field(default=None) """Hostname or IP address from which client requests are originating.""" agent_type: str = field(default_factory=str) agent_identifier: str = field(default_factory=str) def __post_init__(self): """Set derivative fields.""" self.agent_type = self.get_agent_type()
_agent_types = { User.get_agent_type(): User, System.get_agent_type(): System, Client.get_agent_type(): Client, }
[docs]def agent_factory(**data: Union[Agent, dict]) -> Agent: """Instantiate a subclass of :class:`.Agent`.""" if isinstance(data, Agent): return data agent_type = data.pop('agent_type') native_id = data.pop('native_id') if not agent_type or not native_id: raise ValueError('No such agent: %s, %s' % (agent_type, native_id)) if agent_type not in _agent_types: raise ValueError(f'No such agent type: {agent_type}') klass = _agent_types[agent_type] data = {k: v for k, v in data.items() if k in klass.__dataclass_fields__} return klass(native_id, **data)