Skip to content

Commit

Permalink
Added docstrings
Browse files Browse the repository at this point in the history
  • Loading branch information
jwodder committed Jul 20, 2020
1 parent e4aad8e commit c55a299
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 6 deletions.
14 changes: 8 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,11 @@ Like ``load()``, but reads from a string instead of a filehandle
entry_points_txt.dump(eps: Dict[str, Dict[str, EntryPoint]], fp: IO[str]) -> None
Write a collection of entry points (in the same format as returned by
``load()``) to a file-like object. A ``ValueError`` is raised and nothing is
written if the group or name key under which an ``EntryPoint`` is located does
not match its ``group`` or ``name`` attribute.
Write a collection of entry points (in the same structure as returned by
``load()``) to a file-like object in ``entry_points.txt`` format. A
``ValueError`` is raised and nothing is written if the group or name key under
which an ``EntryPoint`` is located does not match its ``group`` or ``name``
attribute.

``dumps()``
-----------
Expand All @@ -141,8 +142,9 @@ Like ``dump()``, but returns a string instead of writing to a filehandle
entry_points_txt.dump_list(eps: Iterable[EntryPoint], fp: IO[str]) -> None
Write an iterable of entry points to a file-like object. If two or more entry
points have the same group & name, only the last one will be output.
Write an iterable of entry points to a file-like object in ``entry_points.txt``
format. If two or more entry points have the same group & name, only the last
one will be output.

``dumps_list()``
----------------
Expand Down
90 changes: 90 additions & 0 deletions src/entry_points_txt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,36 @@
from typing import Any, Dict, IO, Iterable, NamedTuple, Optional, Tuple

class EntryPoint(NamedTuple):
""" A representation of an entry point as a namedtuple. """

#: The name of the entry point group (e.g., ``"console_scripts"``)
group: str
#: The name of the entry point
name: str
#: The module portion of the object reference (the part before the colon)
module: str
#: The object/attribute portion of the object reference (the part after the
#: colon), or `None` if not specified
object: Optional[str]
#: Extras required for the entry point
extras: Tuple[str, ...]

def load(self) -> Any:
"""
Returns the object referred to by the entry point's object reference
"""
obj = import_module(self.module)
if self.object is not None:
for attr in self.object.split('.'):
obj = getattr(obj, attr)
return obj

def to_line(self) -> str:
"""
Returns the representation of the entry point as a line in
:file:`entry_points.txt`, i.e., a line of the form ``name =
module:object [extras]``
"""
s = f'{self.name} = {self.module}'
if self.object is not None:
s += f':{self.object}'
Expand All @@ -62,6 +78,56 @@ def to_line(self) -> str:
EXTRA_RGX = re.compile(r'[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?')

def load(fp: IO[str]) -> EntryPointSet:
"""
Parse a file-like object as an :file:`entry_points.txt`-format file and
return the results. The parsed entry points are returned in a `dict`
mapping each group name to a `dict` mapping each entry point name to an
`EntryPoint` object.
For example, the following input:
.. code-block:: ini
[console_scripts]
foo = package.__main__:main
bar = package.cli:klass.attr
[thingy.extension]
quux = package.thingy [xtr]
would be parsed as:
.. code-block:: python
{
"console_scripts": {
"foo": EntryPoint(
group="console_scripts",
name="foo",
module="package.__main__",
object="main",
extras=(),
),
"bar": EntryPoint(
group="console_scripts",
name="bar",
module="package.cli",
object="klass.attr",
extras=(),
),
},
"thingy.extension": {
"quux": EntryPoint(
group="thingy.extension",
name="quux",
module="package.thingy",
object=None,
extras=("xtr",),
),
},
}
"""

eps: EntryPointSet = {}
group = None
for line in fp:
Expand Down Expand Up @@ -129,12 +195,23 @@ def load(fp: IO[str]) -> EntryPointSet:
return eps

def loads(s: str) -> EntryPointSet:
""" Like `load()`, but reads from a string instead of a filehandle """
return load(StringIO(s))

def dump(eps: EntryPointSet, fp: IO[str]) -> None:
"""
Write a collection of entry points (in the same structure as returned by
`load()`) to a file-like object in :file:`entry_points.txt` format. A
`ValueError` is raised and nothing is written if the group or name key
under which an `EntryPoint` is located does not match its ``group`` or
``name`` attribute.
"""
print(dumps(eps), file=fp, end='')

def dumps(eps: EntryPointSet) -> str:
"""
Like `dump()`, but returns a string instead of writing to a filehandle
"""
s = ''
first = True
for group, items in eps.items():
Expand All @@ -160,16 +237,29 @@ def dumps(eps: EntryPointSet) -> str:
return s

def dump_list(eps: Iterable[EntryPoint], fp: IO[str]) -> None:
"""
Write an iterable of entry points to a file-like object in
:file:`entry_points.txt` format. If two or more entry points have the same
group & name, only the last one will be output.
"""
print(dumps_list(eps), file=fp, end='')

def dumps_list(eps: Iterable[EntryPoint]) -> str:
"""
Like `dump_list()`, but returns a string instead of writing to a filehandle
"""
epset: EntryPointSet = {}
for ep in eps:
epset.setdefault(ep.group, {})[ep.name] = ep
return dumps(epset)

def _is_dotted_id(s: str) -> bool:
"""
Tests whether the given string is a valid dotted sequence of Python
identifiers
"""
return all(p.isidentifier() and not iskeyword(p) for p in s.split('.'))

class ParseError(ValueError):
""" Exception raised by `load()` or `loads()` when given invalid input """
pass

0 comments on commit c55a299

Please sign in to comment.