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

Integrate with packaging tools to allow console_scripts-like usage #19

Closed
chrahunt opened this issue Jan 22, 2019 · 3 comments
Closed
Milestone

Comments

@chrahunt
Copy link
Owner

chrahunt commented Jan 22, 2019

The current implementation is generic enough that it can be used in place of a setuptools-generated script and be used to run packages directly.

The solution should work for (to start):

  1. setup.py with setuptools
  2. flit
  3. poetry

Examples of current script designation:

setuptools console_scripts entry point:

setup(
    ...
    entry_points={
        'console_scripts': ['funniest-joke=funniest.command_line:main'],
    }
    ...
)

flit scripts:

[tools.flit.scripts]
flit = "flit:main"

poetry scripts

[tools.poetry.scripts]
poetry = 'poetry:console.run'

Usage should be about that easy and should work with both source distributions and wheels.

@chrahunt chrahunt changed the title Integrate with setuptools to allow console_scripts-like usage Integrate with packacing to allow console_scripts-like usage Apr 30, 2019
@chrahunt chrahunt changed the title Integrate with packacing to allow console_scripts-like usage Integrate with packaging tools to allow console_scripts-like usage Apr 30, 2019
@chrahunt
Copy link
Owner Author

chrahunt commented Apr 30, 2019

Proposal

Use console_scripts and similar configuration entry points as-is.

Configuration would look like:

# pyproject.toml
# Current
[tools.flit.scripts]
flit = "flit:main"

# With quicken
[tools.flit.scripts]
flit = "quicken.script:flit._.main"

# Current
[tools.poetry.scripts]
poetry = 'poetry:console.run'

# With quicken
[tools.poetry.scripts]
flit = 'quicken.script:poetry._.console.run'
# setup.py
# Old format
setup(
   ...
   entry_points={
       'console_scripts': [
            'funniest-joke=funniest.command_line:main'
        ]
    }
    ...
)

# New format
setup(
    ...
    entry_points={
        'console_scripts': [
            'funniest-joke=quicken.script:funniest.command_line._.main'
        ]
    }
    ...
)

The quicken.script module can define a __getattr__ (or compatible equivalent) that returns an object that collects subsequent property accesses and when called will invoke the equivalent of

@quicken
def main():
    mod = importlib.import_module(resolved_module_path)
    return get_nested_attr(mod, resolved_object_path)

Given name = quicken.script:entry_point:

  1. resolved_module_path is the dot-separated portion of entry_point that occurs before the first segment that consists solely of a _ character
  2. resolved_object_path is the (optional) dot-separated portion of entry_point that begins after the first segment that consists solely of a _ character

The specific syntax is subject to change pending a review of PyPI package entry point naming, but shouldn't be too cumbersome to stick to.

Benefits:

  1. Very simple, integrates automatically with all tools that exist now and tools that may come later that emulate the same entry points behavior.
  2. Same approach can be used by other kinds of wrappers without any conflict, which is a nice property.
  3. quicken does not need to be listed in build-system requirements, so would be managed the same as any other dependency
  4. This can be extended to provide a more expressive interface if the referenced object is e.g. a subclass of something like quicken.App

Drawbacks:

  1. Using the part after the : as a faux-argument is a hack. Ideally there would be a mechanism for communicating arguments to the invoked method, but it's probably not that common a use case.
  2. Anyone not familiar with the convention would look in quicken.script for something that is not there (so there should be a note there with explanation)
  3. Some minimal overhead at startup will be spent on executing the wrapper wrapper code.
  4. The library (this one, not libraries using quicken) will need to take special care to avoid essentially any work at the package level unless absolutely needed.

Background

For wheels, entry points as defined above are put into {distribution}-{version}.dist-info/entry_points.txt. This is specifically parsed by pip which inserts the contents into a pre-defined template from here.

Wheel files can contain scripts in {distribution}-{version}.data/scripts/ which should be handled in the way we want.

Alternatives

Tool-specific plugins

If the tools provide an interface, we can write a module that will generate the required file on sdist installation or to be included in any generated wheel.

Rejected because:

  1. Plugin or build support is pending in:
    1. [Discussion] Poetry to provide a decent hook-based plugin system python-poetry/poetry#693
    2. Build steps? pypa/flit#119
  2. Poetry does support a build key as used here but it is undocumented and would require users to have a build script to generate the scripts.

Include quicken in "build-requires" and patch tools as needed with pth hack

Rejected because:

  1. Unmaintainable - would rely on internal details of tools.
  2. Versioning, update, maintenance of build requires is NOT the primary path exercised by tools like poetry and flit - the user experience would not be good.

PEP-517 shim

Without impacting the build system too much we can take a page from flit and implement a shim that would delegate to the actual build system used by a user but then generate our scripts at the appropriate time.

Rejected because:

  1. Increased complexity of configuration for CLI tool developers
  2. May require special case handling of customization for each back-end since they (rightfully) assume they are in full control of the build process.
  3. Versioning, update, maintenance of build requires is NOT the primary path exercised by tools like poetry and flit - the user experience would not be good.

PEP-517 framework

Outside the scope of this utility, should not be a pre-requisite.

Pre-generated scripts

setuptools supports a scripts key which accepts a file path. If we pre-generate the script and then place it in a build directory (e.g. build/bin/script) then it could be picked up.

Rejected because:

  1. Not supported in Poetry yet (Add support for scripts python-poetry/poetry#241)
  2. May not be supported in flit - not mentioned in documentation
  3. Would require a separate step to generate the files, not "integrated" into the tool

Pre-generated sources

Since every utility supports something like console scripts, we could have a step prior to executing of the tool that would generate a command-line file in the project source tree that could be referenced like anything else.

Rejected because:

  1. would require .gitignore or equivalent
  2. package-level __init__.py would still be invoked unconditionally and if there are convenience imports for downstream users then it would negate any startup performance benefits. Packages would need to change.
  3. would require an explicit action, not "integrated" in the same was as the scripts above

References

  1. PEP 427 -- The Wheel Binary Package Format 1.0 -

chrahunt added a commit that referenced this issue May 4, 2019
* Removes runtime dependency on psutil. (Fixes #30)
* Reduce unnecessary imports and import-time work.
* Fix test issue caused by not flushing intercepted std streams.
* Partially implements #19 and #32.
@chrahunt chrahunt added this to the 0.2.0 milestone May 4, 2019
@chrahunt
Copy link
Owner Author

chrahunt commented May 5, 2019

Item still pending: mechanism for reloading on package update (must-have, otherwise this is not usable)

Options:

  1. map module to package (?) and use package version (via importlib-metadata)
  2. get __main__ module path and use its last update/access time - this is probably a better option anyway because we can hash the path and use that as the server key which will allow utilities at different versions in different __pypackages__ and venvs to coexist. There still needs to be a way to get the server key from the ctl script.

@chrahunt
Copy link
Owner Author

Closed by #54.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant