Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Abstract entrypoint support #34

Merged
merged 5 commits into from
Jan 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ build-backend = "setuptools.build_meta"

[project]
name = "gather"
version = "2024.1.5.2"
version = "2024.1.5.3"
description = "A gatherer"
readme = "README.rst"
authors = [{name = "Moshe Zadka", email = "[email protected]"}]
dependencies = ["attrs", "incremental", "venusian"]
dependencies = ["attrs", "incremental", "venusian", "toolz"]
requires-python = ">=3.11"

[project.optional-dependencies]
Expand Down
6 changes: 4 additions & 2 deletions requirements-docs.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#
# This file is autogenerated by pip-compile with python 3.10
# To update, run:
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# pip-compile --extra=docs --output-file=requirements-docs.txt pyproject.toml
#
Expand Down Expand Up @@ -52,6 +52,8 @@ sphinxcontrib-qthelp==1.0.3
# via sphinx
sphinxcontrib-serializinghtml==1.1.5
# via sphinx
toolz==0.12.0
# via gather (pyproject.toml)
urllib3==1.26.9
# via requests
venusian==3.0.0
Expand Down
10 changes: 4 additions & 6 deletions requirements-lint.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#
# This file is autogenerated by pip-compile with python 3.10
# To update, run:
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# pip-compile --extra=lint --output-file=requirements-lint.txt pyproject.toml
#
Expand Down Expand Up @@ -40,12 +40,10 @@ pyflakes==2.4.0
# via flake8
pylint==2.14.4
# via gather (pyproject.toml)
tomli==2.0.1
# via
# black
# pylint
tomlkit==0.11.0
# via pylint
toolz==0.12.0
# via gather (pyproject.toml)
venusian==3.0.0
# via gather (pyproject.toml)
wrapt==1.14.1
Expand Down
8 changes: 4 additions & 4 deletions requirements-mypy.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#
# This file is autogenerated by pip-compile with python 3.10
# To update, run:
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# pip-compile --extra=mypy --output-file=requirements-mypy.txt pyproject.toml
#
Expand All @@ -12,8 +12,8 @@ mypy==0.961
# via gather (pyproject.toml)
mypy-extensions==0.4.3
# via mypy
tomli==2.0.1
# via mypy
toolz==0.12.0
# via gather (pyproject.toml)
typing-extensions==4.3.0
# via mypy
venusian==3.0.0
Expand Down
8 changes: 4 additions & 4 deletions requirements-tests.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#
# This file is autogenerated by pip-compile with python 3.10
# To update, run:
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# pip-compile --extra=tests --output-file=requirements-tests.txt pyproject.toml
#
Expand Down Expand Up @@ -34,8 +34,8 @@ pyrsistent==0.18.1
# via virtue
six==1.16.0
# via automat
tomli==2.0.1
# via coverage
toolz==0.12.0
# via gather (pyproject.toml)
twisted==22.4.0
# via virtue
typing-extensions==4.3.0
Expand Down
79 changes: 79 additions & 0 deletions src/gather/entry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""
Abstractions for writing entrypoints
"""

from __future__ import annotations
import functools
import logging
import runpy
import sys
from typing import Callable

import attrs
import toolz

from . import commands as commandslib, api


def dunder_main(globals_dct, command_data, logger=logging.getLogger()):
"""
Call from ``__main__``
"""
if globals_dct["__name__"] != "__main__":
raise ImportError("module cannot be imported", globals_dct["__name__"])
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
formatter = logging.Formatter("%(asctime)s:%(levelname)s:%(name)s:%(message)s")
ch.setFormatter(formatter)
logger.addHandler(ch)
commandslib.run_maybe_dry(
parser=commandslib.set_parser(collected=command_data.collector.collect()),
is_subcommand=globals_dct.get("IS_SUBCOMMAND", False),
prefix=command_data.prefix,
argv=sys.argv,
)


def _noop(_ignored): # pragma: no cover
pass


@attrs.frozen
class EntryData:
"""
Data for the entry point.
"""

prefix: str
collector: api.Collector
register: Callable
main_command: Callable[[], None]
sub_command: Callable[[], None]

@classmethod
def create(cls, package_name, prefix=None):
"""
Create a new instance from package_name and prefix
"""
if prefix is None:
prefix = package_name
collector = api.Collector()
register = commandslib.make_command_register(collector)
main_command = toolz.compose(
_noop,
functools.partial(
runpy.run_module,
package_name,
run_name="__main__",
),
)
sub_command = functools.partial(
main_command, init_globals=dict(IS_SUBCOMMAND=True)
)
return cls(
prefix=prefix,
collector=collector,
register=register,
main_command=main_command,
sub_command=sub_command,
)
60 changes: 60 additions & 0 deletions src/gather/tests/test_entry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""Test entrypoint"""
import io
import logging
import unittest
from unittest import mock

from hamcrest import assert_that, calling, contains_string, equal_to, raises

from .. import entry

ENTRY_DATA = entry.EntryData.create("test_dunder_main")


@ENTRY_DATA.register(name="fake")
def _fake(args):
print("hello")


class DunderMainTest(unittest.TestCase):

"""Test dunder_main"""

def test_failed_import(self):
"""
Function fails if the name is not __main__
"""
assert_that(
calling(entry.dunder_main).with_args(
globals_dct=dict(__name__="some_name"),
command_data=None,
logger=None,
),
raises(ImportError),
)

def test_run_command(self):
"""
The fake command is called when the command line specifies it
"""
logger = logging.Logger("nonce")
mock_output = mock.patch("sys.stdout", new=io.StringIO())
self.addCleanup(mock_output.stop)
fake_stdout = mock_output.start()
mock_args = mock.patch("sys.argv", new=[])
self.addCleanup(mock_args.stop)
fake_args = mock_args.start()
fake_args[:] = ["test", "fake"]
entry.dunder_main(
globals_dct=dict(__name__="__main__"),
logger=logger,
command_data=ENTRY_DATA,
)
assert_that(fake_stdout.getvalue(), contains_string("hello"))

def test_with_prefix(self):
"""
An explicit prefix overrides the default
"""
ed = entry.EntryData.create("test_dunder_main", prefix="thing")
assert_that(ed.prefix, equal_to("thing"))
Loading