Source code for psij.descriptor
"""Executor/Launcher descriptor module."""
from packaging.version import Version
from typing import TypeVar, Generic, Optional, Type, List
T = TypeVar('T')
class _VersionEntry(Generic[T]):
def __init__(self, desc: 'Descriptor',
plugin_path: Optional[str] = None,
ecls: Optional[Type[T]] = None,
exc: Optional[Exception] = None) -> None:
self.desc = desc
self.version = desc.version
self.desc_path = desc.path
self.plugin_path = plugin_path
self.ecls = ecls
self.exc = exc
def __cmp__(self, other: '_VersionEntry[T]') -> int:
if self.version == other.version:
return 0
elif self.version < other.version:
return -1
else:
return 1
def __eq__(self, other: object) -> bool:
if not isinstance(other, _VersionEntry):
return False
return self.version == other.version
def __lt__(self, other: object) -> bool:
if not isinstance(other, _VersionEntry):
return NotImplemented
return self.version < other.version
def __gt__(self, other: object) -> bool:
if not isinstance(other, _VersionEntry):
return NotImplemented
return self.version > other.version
[docs]class Descriptor(object):
"""
This class is used to enable PSI/J to discover and register executors and/or launchers.
Executors wanting to register with PSI/J must place an instance of this class in a global
module list named `__PSI_J_EXECUTORS__` or `__PSI_J_LAUNCHERS__` in a module placed in the
`psij-descriptors` *namespace package*. In other words, in order to automatically register an
executor or launcher, a python file should be created inside a `psij-descriptors` package, such
as:
.. code-block:: none
<project_root>/
src/
psij-descriptors/
descriptors_for_project.py
It is *essential* that the `psij-descriptors` package not contain an `__init__.py` file in
order for Python to treat the package as a namespace package. This allows Python to combine
multiple `psij-descriptors` directories into one, which, in turn, allows PSI/J to detect and
load all descriptors that can be found in Python's library search path.
The contents of `descriptors_for_project.py` could then be as follows:
.. code-block:: python
from packaging.version import Version
from psij.descriptor import Descriptor
__PSI_J_EXECUTORS__ = [
Descriptor(name=<name>, version=Version(<version_str>),
cls=<fqn_str>),
...
]
__PSI_J_LAUNCHERS__ = [
Descriptor(name=<name>, version=Version(<version_str>),
cls=<fqn_str>),
...
]
where `<name>` stands for the name used to instantiate the executor or launcher,
`<version_str>` is a version string such as `1.0.2`, and `<fqn_str>` is the fully qualified
class name that implements the executor or launcher such as
`psij.executors.local.LocalJobExecutor`.
"""
def __init__(self, name: str, version: Version, cls: str,
aliases: Optional[List[str]] = None, nice_name: Optional[str] = None) -> None:
"""
Parameters
----------
name
The name of the executor or launcher. The automatic registration system will register
the executor or launcher using this name. That is, the executor or launcher represented
by this descriptor will be available for instantiation using either
:meth:`~psij.JobExecutor.get_instance` or
:meth:`~psij.Launcher.get_instance`
version:
The version of the executor/launcher. Multiple versions can be registered under a
single name.
cls:
A fully qualified name pointing to the class implementing an executor or launcher.
aliases:
An optional set of alternative names to make the executor available under as if
its `name` was the alias.
nice_name:
An optional string to use whenever a user-friendly name needs to be displayed to
a user. For example, a nice name for `pbs` would be `PBS` or `Portable Batch System`.
If not specified, the `nice_name` defaults to the value of the `name` parameter.
"""
self.name = name
self.version = version
self.cls = cls
self.path: Optional[str] = None
self.aliases = aliases
self.nice_name = nice_name if nice_name is not None else name
def __repr__(self) -> str:
"""Returns a string representation of this descriptor."""
return 'Descriptor(name=%s, path=%s)' % (self.name, self.path)