From 311a4877013d5c172bc247fc12bfe98f50cb1bb0 Mon Sep 17 00:00:00 2001 From: Nick Coghlan Date: Sat, 3 Aug 2013 01:13:56 +1000 Subject: [PATCH] First draft of entry points for metadata 2.0 --- pep-0426.txt | 239 ++++++++++++++++++++++++++++++++++-- pep-0426/pydist-schema.json | 82 ++++++++++++- 2 files changed, 305 insertions(+), 16 deletions(-) diff --git a/pep-0426.txt b/pep-0426.txt index c0d9f6d4a..909a60650 100644 --- a/pep-0426.txt +++ b/pep-0426.txt @@ -86,6 +86,24 @@ identification scheme. "rationale" section at the end of the document, as it would otherwise be an irrelevant distraction for future readers. + +A Note on Time Frames +===================== + +There's a lot of work going on in the Python packaging space at the moment. +In the near term (up until the release of Python 3.4), those efforts will be +focused on the existing metadata standards, both those defined in Python +Enhancement Proposals, and the de facto standards defined by the setuptools +project. + +This PEP is about setting out a longer term goal for the ecosystem that +captures those existing capabilities in a format that is easier to work +with. There are still a number of key open questions (mostly related to +source based distribution), and those won't be able to receive proper +attention from the development community until the other near term +concerns have been resolved. + + Purpose ======= @@ -223,12 +241,16 @@ or consumes distribution version and dependency metadata. along with the supporting metadata file formats defined by the ``setuptools`` project. -"Entry points" are a scheme for identifying Python callables or other -objects as strings consisting of a Python module name and a module -attribute name, separated by a colon. For example: ``"test.regrtest:main"``. +"Distro" is used as the preferred term for Linux distributions, to help +avoid confusion with the Python-specific meaning of the term "distribution". -"Distros" is used as the preferred term for Linux distributions, to help -avoid confusion with the Python-specific meaning of the term. +"Dist" is the preferred abbreviation for "distributions" in the sense defined +in this PEP. + +"Qualified name" comes from PEP 3155, and refers to the dotted name of an +object relative to its containing module. This is useful for referring +to method definitions on classes, as well as any other attributes of +top level module objects. Integration and deployment of distributions @@ -255,7 +277,10 @@ Integration and deployment can in turn be broken down into further substeps. These three steps may all occur directly on the target system. Alternatively the build step may be separated out by using binary archives provided by the publisher of the distribution, or by creating the binary archives on a -separate system prior to deployment. +separate system prior to deployment. The advantage of the latter approach +is that it minimizes the dependencies that need to be installed on +deployment targets (as the build dependencies will be needed only on the +build systems). The published metadata for distributions SHOULD allow integrators, with the aid of build and integration tools, to: @@ -299,6 +324,25 @@ and publishers, with the aid of build and publication tools, to: Standard build system --------------------- +.. note:: + + The standard build system currently described in the PEP is a draft based + on existing practices for projects using distutils or setuptools as their + build system (or other projects, like ``d2to1``, that expose a setup.py + file for backwards compatibility with existing tools) + + The specification doesn't currently cover expected argument support for + the commands, which is a limitation that needs to be addressed before the + PEP can be considered ready for acceptance. + + It is also possible that the "meta build system" will be separated out + into a distinct PEP in the coming months (similar to the separation of + the versioning and requirement specification standard out to PEP 440). + + If a `suitable API can be worked out `__, then it may + even be possible to switch to a more declarative API for build system + specification. + Both development and integration of distributions relies on the ability to build extension modules and perform other operations in a distribution independent manner. @@ -318,10 +362,6 @@ development and integration activities: * ``python setup.py bdist_wheel``: create a binary archive from an sdist, source archive or VCS checkout -Future iterations of the metadata and associated PEPs may aim to replace -these ``distutils``/``setuptools`` dependent commands with build system -independent entry points. - Metadata format =============== @@ -436,6 +476,48 @@ When serialised to a file, the name used for this metadata set SHOULD be ``pydist-dependencies.json``. +Export metadata +--------------- + +Distributions may define components that are intended for use by other +distributions (such as plugins). As it can be beneficial to know whether or +not a distribution defines any such exports without needing to parse any +metadata, a suitable subset is defined for serialisation to a separate file +in the ``dist-info`` metadata directory. + +The external command metadata consists of the following fields: + +* ``metadata_version`` +* ``generator`` +* ``name`` +* ``version`` +* ``exports`` + +When serialised to a file, the name used for this metadata set SHOULD +be ``pydist-exports.json``. + + +Command metadata +---------------- + +Distributions may define commands that will be available from the command +line following installation. As it can be beneficial to know whether or not +a distribution has such commands without needing to parse any metadata, +a suitable subset is defined for serialisation to a separate file in the +``dist-info`` metadata directory. + +The external command metadata consists of the following fields: + +* ``metadata_version`` +* ``generator`` +* ``name`` +* ``version`` +* ``commands`` + +When serialised to a file, the name used for this metadata set SHOULD +be ``pydist-commands.json``. + + Included documents ------------------ @@ -508,7 +590,7 @@ if any. A manually produced file would omit this field. Example:: - "generator": "setuptools (0.8)" + "generator": "setuptools (0.9)" Name @@ -1348,6 +1430,141 @@ Example where the supported Python version varies by platform:: "supports_environments": ["python_version >= '2.6' and sys_platform != 'win32'", "python_version >= '3.3' and sys_platform == 'win32'"] +Installed interfaces +==================== + +Most Python distributions expose packages and modules for import through +the Python module namespace. Distributions may also expose other +interfaces when installed. + +Export specifiers +----------------- + +An export specifier is a string using one of the following formats:: + + module + module:name + module[requires_extra] + module:name[requires_extra] + +The meaning of the subfields is as follows: + +* ``module``: the module providing the export +* ``name``: if applicable, the qualified name of the export within the module +* ``requires_extra``: indicates the export will only work correctly if the + additional dependencies named in the given extra are available. + +Note that installation of extras is not tracked directly: they are merely +a convenient way to refer to a set of dependencies that will be checked for +at runtime. + +.. note:: + + I tried this as a mapping with subfields, and it made the examples below + unreadable. While this PEP is mostly for tool use, readability still + matters to some degree for debugging purposes, and because I expect + snippets of the format to be reused elsewhere. + + +Modules +------- + +A list of module names that the distribution provides for import. + +For names that contain dots, the portion of the name before the final dot +MUST appear either in the installed module list or in the namespace package +list. + +Note that attempting to import some declared modules may result in an +exception if the appropriate extras are not installed. + +Example:: + + "modules": ["chair", "chair.cushions", "python_sketches.nobody_expects"] + +.. note:: + + Making this a list of export specifiers instead would allow a distribution + to declare when a particular module requires a particular extra in order + to run correctly. On the other hand, there's an argument to be made that + that is the point where it starts to become worthwhile to split out a + separate distribution rather than using extras. + + +Namespaces +---------- + +A list of namespace packages that the distribution contributes modules to. + +On versions of Python prior to Python 3.3 (which provides native namespace +package support), installation tools SHOULD emit a suitable ``__init__.py`` +file to properly initialise the namespace rather than using a distribution +provided file. + +Installation tools SHOULD emit a warning and MAY emit an error if a +distribution declares a namespace package that conflicts the name of an +already installed module or vice-versa. + +Example:: + + "namespaces": ["python_sketches"] + + +Commands +-------- + +The ``commands`` mapping contains three subfields: + +* ``wrap_console``: console wrapper scripts to be generated by the installer +* ``wrap_gui``: GUI wrapper scripts to be generated by the installer +* ``prebuilt``: scripts created by the distribution's build process and + installed directly to the configured scripts directory + +``wrap_console`` and ``wrap_gui`` are both mappings of relatively arbitrary +script names to export specifiers. The script names must follow the rules +for distribution names. The export specifiers must refer to +either a package with a __main__ submodule (if no ``name`` subfield is +given in the export specifier) or else to a callable inside the named +module. + +Installation tools should generate appropriate wrappers as part of the +installation process. + +.. note:: + + Still needs more detail on what "appropriate wrapper" means. + +``prebuilt`` is a list of script paths, relative to the scripts directory in +a wheel file or following installation. They are provided for informational +purpose only - installing them is handled through the normal processes for +files created when building a distribution. + + +Example:: + + "commands": { + "wrap_console": [{"wrapwithpython": "chair.run_cli"}], + "wrap_gui": [{"wrapwithpythonw": "chair:run_gui"}], + "prebuilt": ["notawrapper"] + } + + + +Exports +------- + +The ``exports`` mapping contains relatively arbitrary subfields, each +defining an export group. Each export group is then a mapping of relatively +arbitrary subfields to export specifiers. + +Both export group names and export names must follow the rules for +distribution identifiers. It is suggested that export groups be named +after distributions to help avoid name conflicts. + +The meaning of exports within an export group is up to those defining the +export group. One common use case is to advertise plugins for use by other +software. + Install hooks ============= diff --git a/pep-0426/pydist-schema.json b/pep-0426/pydist-schema.json index 41eb64ada..d16f17e97 100644 --- a/pep-0426/pydist-schema.json +++ b/pep-0426/pydist-schema.json @@ -136,6 +136,32 @@ "$ref": "#/definitions/provides_declaration" } }, + "modules": { + "description": "A list of modules and/or packages available for import after installing this distribution.", + "type": "array", + "items": { + "type": "string", + "$ref": "#/definitions/dotted_name" + } + }, + "namespaces": { + "description": "A list of namespace packages this distribution contributes to", + "type": "array", + "items": { + "type": "string", + "$ref": "#/definitions/dotted_name" + } + }, + "commands": { + "description": "Command line interfaces provided by this distribution", + "type": "object", + "$ref": "#/definitions/commands" + }, + "exports": { + "description": "Other exported interfaces provided by this distribution", + "type": "object", + "$ref": "#/definitions/exports" + }, "obsoleted_by": { "description": "A string that indicates that this project is no longer being developed. The named project provides a substitute or replacement.", "type": "string", @@ -155,11 +181,11 @@ "properties": { "postinstall": { "type": "string", - "$ref": "#/definitions/entry_point" + "$ref": "#/definitions/export_specifier" }, "preuninstall": { "type": "string", - "$ref": "#/definitions/entry_point" + "$ref": "#/definitions/export_specifier" } } }, @@ -221,6 +247,45 @@ "required": ["requires"], "additionalProperties": false }, + "commands": { + "type": "object", + "properties": { + "wrap_console": { + "type": "object", + "$ref": "#/definitions/export_map" + }, + "wrap_gui": { + "type": "object", + "$ref": "#/definitions/export_map" + }, + "prebuilt": { + "type": "array", + "items": { + "type": "string", + "$ref": "#/definitions/relative_path" + } + } + }, + "additionalProperties": false + }, + "exports": { + "type": "object", + "patternProperties": { + "^[0-9A-Za-z]([0-9A-Za-z_.-]*[0-9A-Za-z])?$": { + "type": "object", + "$ref": "#/definitions/export_map" + } + }, + "additionalProperties": false + }, + "export_map": { + "type": "object", + "patternProperties": { + "^[0-9A-Za-z]([0-9A-Za-z_.-]*[0-9A-Za-z])?$": { + "type": "string", + "$ref": "#/definitions/export_specifier" + } + }, "valid_name": { "type": "string", "pattern": "^[0-9A-Za-z]([0-9A-Za-z_.-]*[0-9A-Za-z])?$" @@ -234,14 +299,21 @@ "environment_marker": { "type": "string" }, - "entry_point": { - "type": "string" - }, "document_name": { "type": "string" }, "extra_name" : { "type": "string" + }, + "relative_path" : { + "type": "string" + }, + "export_specifier": { + "type": "string", + }, + "dotted_name" : { + "type": "string", + "pattern": "^[A-Za-z]([0-9A-Za-z_])*([.][A-Za-z]([0-9A-Za-z_])*)*$" } } }