<div dir="ltr">as it is, this PEP defers the concept of a "Direct Reference URL" to PEP440.<div><br></div><div>but then PEP440 partially defers to PEP426's "source_url" concept, when it says  "a direct URL reference may be a valid source_url entry"<br></div><div><br></div><div>do we expect PEP440 to be updated to fully own what a "Direct Reference URL" can be?, since referring to PEP426 is now a dead-end path (and partially replaced by this PEP)</div></div><div class="gmail_extra"><br><div class="gmail_quote">On Mon, Nov 16, 2015 at 12:46 PM, Robert Collins <span dir="ltr"><<a href="mailto:robertc@robertcollins.net" target="_blank">robertc@robertcollins.net</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">:PEP: XX<br>
:Title: Dependency specification for Python Software Packages<br>
:Version: $Revision$<br>
:Last-Modified: $Date$<br>
:Author: Robert Collins <<a href="mailto:rbtcollins@hp.com">rbtcollins@hp.com</a>><br>
:BDFL-Delegate: Donald Stufft <<a href="mailto:donald@stufft.io">donald@stufft.io</a>><br>
:Discussions-To: distutils-sig <<a href="mailto:distutils-sig@python.org">distutils-sig@python.org</a>><br>
:Status: Draft<br>
:Type: Standards Track<br>
:Content-Type: text/x-rst<br>
:Created: 11-Nov-2015<br>
:Post-History: XX<br>
<br>
<br>
Abstract<br>
========<br>
<br>
This PEP specifies the language used to describe dependencies for packages.<br>
It draws a border at the edge of describing a single dependency - the<br>
different sorts of dependencies and when they should be installed is a higher<br>
level problem. The intent is provide a building block for higher layer<br>
specifications.<br>
<br>
The job of a dependency is to enable tools like pip [#pip]_ to find the right<br>
package to install. Sometimes this is very loose - just specifying a name, and<br>
sometimes very specific - referring to a specific file to install. Sometimes<br>
dependencies are only relevant in one platform, or only some versions are<br>
acceptable, so the language permits describing all these cases.<br>
<br>
The language defined is a compact line based format which is already in<br>
widespread use in pip requirements files, though we do not specify the command<br>
line option handling that those files permit. There is one caveat - the<br>
URL reference form, specified in PEP-440 [#pep440]_ is not actually<br>
implemented in pip, but since PEP-440 is accepted, we use that format rather<br>
than pip's current native format.<br>
<br>
Motivation<br>
==========<br>
<br>
Any specification in the Python packaging ecosystem that needs to consume<br>
lists of dependencies needs to build on an approved PEP for such, but<br>
PEP-426 [#pep426]_ is mostly aspirational - and there are already existing<br>
implementations of the dependency specification which we can instead adopt.<br>
The existing implementations are battle proven and user friendly, so adopting<br>
them is arguably much better than approving an aspirational, unconsumed, format.<br>
<br>
Specification<br>
=============<br>
<br>
Examples<br>
--------<br>
<br>
All features of the language shown with a name based lookup::<br>
<br>
    requests [security,tests] >= 2.8.1, == 2.8.* ; python_version < "2.7.10"<br>
<br>
A minimal URL based lookup::<br>
<br>
    pip @ <a href="https://github.com/pypa/pip/archive/1.3.1.zip#sha1=da9234ee9982d4bbb3c72346a6de940a148ea686" rel="noreferrer" target="_blank">https://github.com/pypa/pip/archive/1.3.1.zip#sha1=da9234ee9982d4bbb3c72346a6de940a148ea686</a><br>
<br>
Concepts<br>
--------<br>
<br>
A dependency specification always specifies a distribution name. It may<br>
include extras, which expand the dependencies of the named distribution to<br>
enable optional features. The version installed can be controlled using<br>
version limits, or giving the URL to a specific artifact to install. Finally<br>
the dependency can be made conditional using environment markers.<br>
<br>
Grammar<br>
-------<br>
<br>
We first cover the grammar briefly and then drill into the semantics of each<br>
section later.<br>
<br>
A distribution specification is written in ASCII text. We use a parsley<br>
[#parsley]_ grammar to provide a precise grammar. It is expected that the<br>
specification will be embedded into a larger system which offers framing such<br>
as comments, multiple line support via continuations, or other such features.<br>
<br>
The full grammar including annotations to build a useful parse tree is<br>
included at the end of the PEP.<br>
<br>
Versions may be specified according to the PEP-440 [#pep440]_ rules. (Note:<br>
URI is defined in std-66 [#std66]_::<br>
<br>
    version_cmp   = wsp* '<' | '<=' | '!=' | '==' | '>=' | '>' | '~=' | '==='<br>
    version       = wsp* ( letterOrDigit | '-' | '_' | '.' | '*' )+<br>
    version_one   = version_cmp version wsp*<br>
    version_many  = version_one (wsp* ',' version_one)*<br>
    versionspec   = ( '(' version_many ')' ) | version_many<br>
    urlspec       = '@' wsp* <URI_reference><br>
<br>
Environment markers allow making a specification only take effect in some<br>
environments::<br>
<br>
    marker_op     = version_cmp | 'in' | 'not' wsp+ 'in'<br>
    python_str_c  = (wsp | letter | digit | '(' | ')' | '.' | '{' | '}' |<br>
                     '-' | '_' | '*')<br>
    dquote        = '"'<br>
    squote        = '\\''<br>
    python_str    = (squote (python_str_c | dquote)* squote |<br>
                     dquote (python_str_c | squote)* dquote)<br>
    env_var       = ('python_version' | 'python_full_version' |<br>
                     'os_name' | 'sys_platform' | 'platform_release' |<br>
                     'platform_system' | 'platform_version' |<br>
                     'platform_machine' | 'python_implementation' |<br>
                     'implementation_name' | 'implementation_version' |<br>
                     'extra' # ONLY when defined by a containing layer<br>
                     )<br>
    marker_var    = env_var | python_str<br>
    marker_expr   = ('(' wsp* marker wsp* ')'<br>
                     | (marker_var wsp* marker_op wsp* marker_var))<br>
    marker        = wsp* marker_expr ( wsp* ('and' | 'or') wsp* marker_expr)*<br>
    quoted_marker = ';' wsp* marker<br>
<br>
Optional components of a distribution may be specified using the extras<br>
field::<br>
<br>
    identifier    = letterOrDigit (<br>
                    letterOrDigit |<br>
                    (( letterOrDigit | '-' | '_' | '.')* letterOrDigit ) )*<br>
    name          = identifier<br>
    extras_list   = identifier (wsp* ',' wsp* identifier)*<br>
    extras        = '[' wsp* extras_list? wsp* ']'<br>
<br>
Giving us a rule for name based requirements::<br>
<br>
    name_req      = name wsp* extras? wsp* versionspec? wsp* quoted_marker?<br>
<br>
And a rule for direct reference specifications::<br>
<br>
    url_req       = name wsp* extras? wsp* urlspec wsp+ quoted_marker?<br>
<br>
Leading to the unified rule that can specify a dependency.::<br>
<br>
    specification = wsp* ( url_req | name_req ) wsp*<br>
<br>
Whitespace<br>
----------<br>
<br>
Non line-breaking whitespace is mostly optional with no semantic meaning. The<br>
sole exception is detecting the end of a URL requirement.<br>
<br>
Names<br>
-----<br>
<br>
Python distribution names are currently defined in PEP-345 [#pep345]_. Names<br>
act as the primary identifier for distributions. They are present in all<br>
dependency specifications, and are sufficient to be a specification on their<br>
own. However, PyPI places strict restrictions on names - they must match a<br>
case insensitive regex or they won't be accepted. Accordingly in this PEP we<br>
limit the acceptable values for identifiers to that regex. A full redefinition<br>
of name may take place in a future metadata PEP::<br>
<br>
    ^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$<br>
<br>
Extras<br>
------<br>
<br>
An extra is an optional part of a distribution. Distributions can specify as<br>
many extras as they wish, and each extra results in the declaration of<br>
additional dependencies of the distribution **when** the extra is used in a<br>
dependency specification. For instance::<br>
<br>
    requests[security]<br>
<br>
Extras union in the dependencies they define with the dependencies of the<br>
distribution they are attached to. The example above would result in requests<br>
being installed, and requests own dependencies, and also any dependencies that<br>
are listed in the "security" extra of requests.<br>
<br>
If multiple extras are listed, all the dependencies are unioned together.<br>
<br>
Versions<br>
--------<br>
<br>
See PEP-440 [#pep440]_ for more detail on both version numbers and version<br>
comparisons. Version specifications limit the versions of a distribution that<br>
can be used. They only apply to distributions looked up by name, rather than<br>
via a URL. Version comparison are also used in the markers feature. The<br>
optional brackets around a version are present for compatibility with PEP-345<br>
[#pep345]_ but should not be generated, only accepted.<br>
<br>
Environment Markers<br>
-------------------<br>
<br>
Environment markers allow a dependency specification to provide a rule that<br>
describes when the dependency should be used. For instance, consider a package<br>
that needs argparse. In Python 2.7 argparse is always present. On older Python<br>
versions it has to be installed as a dependency. This can be expressed as so::<br>
<br>
    argparse;python_version<"2.7"<br>
<br>
A marker expression evalutes to either True or False. When it evaluates to<br>
False, the dependency specification should be ignored.<br>
<br>
The marker language is a subset of Python itself, chosen for the ability to<br>
safely evaluate it without running arbitrary code that could become a security<br>
vulnerability. Markers were first standardised in PEP-345 [#pep345]_. This PEP<br>
fixes some issues that were observed in the design described in PEP-426<br>
[#pep426]_.<br>
<br>
Comparisons in marker expressions are typed by the comparison operator.  The<br>
<marker_op> operators that are not in <version_cmp> perform the same as they<br>
do for strings in Python. The <version_cmp> operators use the PEP-440<br>
[#pep440]_ version comparison rules when those are defined (that is when both<br>
sides have a valid version specifier). If there is no defined PEP-440<br>
behaviour and the operator exists in Python, then the operator falls back to<br>
the Python behaviour. Otherwise an error should be raised. e.g. the following<br>
will result in  errors::<br>
<br>
    "dog" ~= "fred"<br>
    python_version ~= "surprise"<br>
<br>
User supplied constants are always encoded as strings with either ``'`` or<br>
``"`` quote marks. Note that backslash escapes are not defined, but existing<br>
implementations do support them. They are not included in this<br>
specification because they add complexity and there is no observable need for<br>
them today. Similarly we do not define non-ASCII character support: all the<br>
runtime variables we are referencing are expected to be ASCII-only.<br>
<br>
The variables in the marker grammar such as "os_name" resolve to values looked<br>
up in the Python runtime. With the exception of "extra" all values are defined<br>
on all Python versions today - it is an error in the implementation of markers<br>
if a value is not defined.<br>
<br>
Unknown variables must raise an error rather than resulting in a comparison<br>
that evaluates to True or False.<br>
<br>
Variables whose value cannot be calculated on a given Python implementation<br>
should evaluate to ``0`` for versions, and an empty string for all other<br>
variables.<br>
<br>
The "extra" variable is special. It is used by wheels to signal which<br>
specifications apply to a given extra in the wheel ``METADATA`` file, but<br>
since the ``METADATA`` file is based on a draft version of PEP-426, there is<br>
no current specification for this. Regardless, outside of a context where this<br>
special handling is taking place, the "extra" variable should result in an<br>
error like all other unknown variables.<br>
<br>
.. list-table::<br>
   :header-rows: 1<br>
<br>
   * - Marker<br>
     - Python equivalent<br>
     - Sample values<br>
   * - ``os_name``<br>
     - ``<a href="http://os.name" rel="noreferrer" target="_blank">os.name</a>``<br>
     - ``posix``, ``java``<br>
   * - ``sys_platform``<br>
     - ``sys.platform``<br>
     - ``linux``, ``linux2``, ``darwin``, ``java1.8.0_51`` (note that "linux"<br>
       is from Python3 and "linux2" from Python2)<br>
   * - ``platform_machine``<br>
     - ``platform.machine()``<br>
     - ``x86_64``<br>
   * - ``python_implementation``<br>
     - ``platform.python_implementation()``<br>
     - ``CPython``, ``Jython``<br>
   * - ``platform_release``<br>
     - ``platform.release()``<br>
     - ``3.14.1-x86_64-linode39``, ``14.5.0``, ``1.8.0_51``<br>
   * - ``platform_system``<br>
     - ``platform.system()``<br>
     - ``Linux``, ``Windows``, ``Java``<br>
   * - ``platform_version``<br>
     - ``platform.version()``<br>
     - ``#1 SMP Fri Apr 25 13:07:35 EDT 2014``<br>
       ``Java HotSpot(TM) 64-Bit Server VM, 25.51-b03, Oracle Corporation``<br>
       ``Darwin Kernel Version 14.5.0: Wed Jul 29 02:18:53 PDT 2015;<br>
root:xnu-2782.40.9~2/RELEASE_X86_64``<br>
   * - ``python_version``<br>
     - ``platform.python_version()[:3]``<br>
     - ``3.4``, ``2.7``<br>
   * - ``python_full_version``<br>
     - ``platform.python_version()``<br>
     - ``3.4.0``, ``3.5.0b1``<br>
   * - ``implementation_name``<br>
     - ``<a href="http://sys.implementation.name" rel="noreferrer" target="_blank">sys.implementation.name</a>``<br>
     - ``cpython``<br>
   * - ``implementation_version``<br>
     - see definition below<br>
     - ``3.4.0``, ``3.5.0b1``<br>
   * - ``extra``<br>
     - An error except when defined by the context interpreting the<br>
       specification.<br>
     - ``test``<br>
<br>
The ``implementation_version`` marker variable is derived from<br>
``sys.implementation.version``::<br>
<br>
    def format_full_version(info):<br>
        version = '{0.major}.{0.minor}.{0.micro}'.format(info)<br>
        kind = info.releaselevel<br>
        if kind != 'final':<br>
            version += kind[0] + str(info.serial)<br>
        return version<br>
<br>
    if hasattr(sys, 'implementation'):<br>
        implementation_version = format_full_version(sys.implementation.version)<br>
    else:<br>
        implementation_version = "0"<br>
<br>
Backwards Compatibility<br>
=======================<br>
<br>
Most of this PEP is already widely deployed and thus offers no compatibiltiy<br>
concerns.<br>
<br>
There are however a few points where the PEP differs from the deployed base.<br>
<br>
Firstly, PEP-440 direct references haven't actually been deployed in the wild,<br>
but they were designed to be compatibly added, and there are no known<br>
obstacles to adding them to pip or other tools that consume the existing<br>
dependency metadata in distributions - particularly since they won't be<br>
permitted to be present in PyPI uploaded distributions anyway.<br>
<br>
Secondly, PEP-426 markers which have had some reasonable deployment,<br>
particularly in wheels and pip, will handle version comparisons with<br>
``python_version`` "2.7.10" differently. Specifically in 426 "2.7.10" is less<br>
than "2.7.9". This backward incompatibility is deliberate. We are also<br>
defining new operators - "~=" and "===", and new variables -<br>
``platform_release``, ``platform_system``, ``implementation_name``, and<br>
``implementation_version`` which are not present in older marker<br>
implementations. The variables will error on those implementations. Users of<br>
both features will need to make a judgement as to when support has become<br>
sufficiently widespread in the ecosystem that using them will not cause<br>
compatibility issues.<br>
<br>
Thirdly, PEP-345 required brackets around version specifiers. In order to<br>
accept PEP-345 dependency specifications, brackets are accepted, but they<br>
should not be generated.<br>
<br>
Rationale<br>
=========<br>
<br>
In order to move forward with any new PEPs that depend on environment markers,<br>
we needed a specification that included them in their modern form. This PEP<br>
brings together all the currently unspecified components into a specified<br>
form.<br>
<br>
The requirement specifier was adopted from the EBNF in the setuptools<br>
pkg_resources documentation, since we wish to avoid depending on a defacto, vs<br>
PEP specified, standard.<br>
<br>
Complete Grammar<br>
================<br>
<br>
The complete parsley grammar::<br>
<br>
    wsp           = ' ' | '\t'<br>
    version_cmp   = wsp* <'<' | '<=' | '!=' | '==' | '>=' | '>' | '~=' | '==='><br>
    version       = wsp* <( letterOrDigit | '-' | '_' | '.' | '*' |<br>
'+' | '!' )+><br>
    version_one   = version_cmp:op version:v wsp* -> (op, v)<br>
    version_many  = version_one:v1 (wsp* ',' version_one)*:v2 -> [v1] + v2<br>
    versionspec   = ('(' version_many:v ')' ->v) | version_many<br>
    urlspec       = '@' wsp* <URI_reference><br>
    marker_op     = version_cmp | 'in' | 'not' wsp+ 'in'<br>
    python_str_c  = (wsp | letter | digit | '(' | ')' | '.' | '{' | '}' |<br>
                     '-' | '_' | '*' | '#')<br>
    dquote        = '"'<br>
    squote        = '\\''<br>
    python_str    = (squote <(python_str_c | dquote)*>:s squote |<br>
                     dquote <(python_str_c | squote)*>:s dquote) -> s<br>
    env_var       = ('python_version' | 'python_full_version' |<br>
                     'os_name' | 'sys_platform' | 'platform_release' |<br>
                     'platform_system' | 'platform_version' |<br>
                     'platform_machine' | 'python_implementation' |<br>
                     'implementation_name' | 'implementation_version' |<br>
                     'extra' # ONLY when defined by a containing layer<br>
                     ):varname -> lookup(varname)<br>
    marker_var    = env_var | python_str<br>
    marker_expr   = (("(" wsp* marker:m wsp* ")" -> m)<br>
                         | ((marker_var:l wsp* marker_op:o wsp* marker_var:r))<br>
                         -> (l, o, r))<br>
    marker        = (wsp* marker_expr:m ( wsp* ("and" | "or"):o wsp*<br>
                     marker_expr:r -> (o, r))*:ms -> (m, ms))<br>
    quoted_marker = ';' wsp* marker<br>
    identifier    = <letterOrDigit (<br>
                    letterOrDigit |<br>
                    (( letterOrDigit | '-' | '_' | '.')* letterOrDigit ) )*><br>
    name          = identifier<br>
    extras_list   = identifier:i (wsp* ',' wsp* identifier)*:ids -> [i] + ids<br>
    extras        = '[' wsp* extras_list?:e wsp* ']' -> e<br>
    name_req      = (name:n wsp* extras?:e wsp* versionspec?:v wsp*<br>
quoted_marker?:m<br>
                     -> (n, e or [], v or [], m))<br>
    url_req       = (name:n wsp* extras?:e wsp* urlspec:v wsp+ quoted_marker?:m<br>
                     -> (n, e or [], v, m))<br>
    specification = wsp* ( url_req | name_req ):s wsp* -> s<br>
    # The result is a tuple - name, list-of-extras,<br>
    # list-of-version-constraints-or-a-url, marker-ast or None<br>
<br>
<br>
    URI_reference = <URI | relative_ref><br>
    URI           = scheme ':' hier_part ('?' query )? ( '#' fragment)?<br>
    hier_part     = ('//' authority path_abempty) | path_absolute |<br>
path_rootless | path_empty<br>
    absolute_URI  = scheme ':' hier_part ( '?' query )?<br>
    relative_ref  = relative_part ( '?' query )? ( '#' fragment )?<br>
    relative_part = '//' authority path_abempty | path_absolute |<br>
path_noscheme | path_empty<br>
    scheme        = letter ( letter | digit | '+' | '-' | '.')*<br>
    authority     = ( userinfo '@' )? host ( ':' port )?<br>
    userinfo      = ( unreserved | pct_encoded | sub_delims | ':')*<br>
    host          = IP_literal | IPv4address | reg_name<br>
    port          = digit*<br>
    IP_literal    = '[' ( IPv6address | IPvFuture) ']'<br>
    IPvFuture     = 'v' hexdig+ '.' ( unreserved | sub_delims | ':')+<br>
    IPv6address   = (<br>
                      ( h16 ':'){6} ls32<br>
                      | '::' ( h16 ':'){5} ls32<br>
                      | ( h16 )?  '::' ( h16 ':'){4} ls32<br>
                      | ( ( h16 ':')? h16 )? '::' ( h16 ':'){3} ls32<br>
                      | ( ( h16 ':'){0,2} h16 )? '::' ( h16 ':'){2} ls32<br>
                      | ( ( h16 ':'){0,3} h16 )? '::' h16 ':' ls32<br>
                      | ( ( h16 ':'){0,4} h16 )? '::' ls32<br>
                      | ( ( h16 ':'){0,5} h16 )? '::' h16<br>
                      | ( ( h16 ':'){0,6} h16 )? '::' )<br>
    h16           = hexdig{1,4}<br>
    ls32          = ( h16 ':' h16) | IPv4address<br>
    IPv4address   = dec_octet '.' dec_octet '.' dec_octet '.' Dec_octet<br>
    nz            = ~'0' digit<br>
    dec_octet     = (<br>
                      digit # 0-9<br>
                      | nz digit # 10-99<br>
                      | '1' digit{2} # 100-199<br>
                      | '2' ('0' | '1' | '2' | '3' | '4') digit # 200-249<br>
                      | '25' ('0' | '1' | '2' | '3' | '4' | '5') )# %250-255<br>
    reg_name = ( unreserved | pct_encoded | sub_delims)*<br>
    path = (<br>
            path_abempty # begins with '/' or is empty<br>
            | path_absolute # begins with '/' but not '//'<br>
            | path_noscheme # begins with a non-colon segment<br>
            | path_rootless # begins with a segment<br>
            | path_empty ) # zero characters<br>
    path_abempty  = ( '/' segment)*<br>
    path_absolute = '/' ( segment_nz ( '/' segment)* )?<br>
    path_noscheme = segment_nz_nc ( '/' segment)*<br>
    path_rootless = segment_nz ( '/' segment)*<br>
    path_empty    = pchar{0}<br>
    segment       = pchar*<br>
    segment_nz    = pchar+<br>
    segment_nz_nc = ( unreserved | pct_encoded | sub_delims | '@')+<br>
                    # non-zero-length segment without any colon ':'<br>
    pchar         = unreserved | pct_encoded | sub_delims | ':' | '@'<br>
    query         = ( pchar | '/' | '?')*<br>
    fragment      = ( pchar | '/' | '?')*<br>
    pct_encoded   = '%' hexdig<br>
    unreserved    = letter | digit | '-' | '.' | '_' | '~'<br>
    reserved      = gen_delims | sub_delims<br>
    gen_delims    = ':' | '/' | '?' | '#' | '(' | ')?' | '@'<br>
    sub_delims    = '!' | '$' | '&' | '\\'' | '(' | ')' | '*' | '+' |<br>
',' | ';' | '='<br>
    hexdig        = digit | 'a' | 'A' | 'b' | 'B' | 'c' | 'C' | 'd' |<br>
'D' | 'e' | 'E' | 'f' | 'F'<br>
<br>
A test program - if the grammar is in a string ``grammar``::<br>
<br>
    import os<br>
    import sys<br>
    import platform<br>
<br>
    from parsley import makeGrammar<br>
<br>
    grammar = """<br>
        wsp ...<br>
        """<br>
    tests = [<br>
        "A",<br>
        "aa",<br>
        "name",<br>
        "name>=3",<br>
        "name>=3,<2",<br>
        "name [fred,bar] @ <a href="http://foo.com" rel="noreferrer" target="_blank">http://foo.com</a> ; python_version=='2.7'",<br>
        "name[quux, strange];python_version<'2.7' and platform_version=='2'",<br>
        "name; os_name=='dud' and (os_name=='odd' or os_name=='fred')",<br>
        "name; os_name=='dud' and os_name=='odd' or os_name=='fred'",<br>
        ]<br>
<br>
    def format_full_version(info):<br>
        version = '{0.major}.{0.minor}.{0.micro}'.format(info)<br>
        kind = info.releaselevel<br>
        if kind != 'final':<br>
            version += kind[0] + str(info.serial)<br>
        return version<br>
<br>
    if hasattr(sys, 'implementation'):<br>
        implementation_version = format_full_version(sys.implementation.version)<br>
        implementation_name = <a href="http://sys.implementation.name" rel="noreferrer" target="_blank">sys.implementation.name</a><br>
    else:<br>
        implementation_version = '0'<br>
        implementation_name = ''<br>
    bindings = {<br>
        'implementation_name': implementation_name,<br>
        'implementation_version': implementation_version,<br>
        'os_name': <a href="http://os.name" rel="noreferrer" target="_blank">os.name</a>,<br>
        'platform_machine': platform.machine(),<br>
        'platform_release': platform.release(),<br>
        'platform_system': platform.system(),<br>
        'platform_version': platform.version(),<br>
        'python_full_version': platform.python_version(),<br>
        'python_implementation': platform.python_implementation(),<br>
        'python_version': platform.python_version()[:3],<br>
        'sys_platform': sys.platform,<br>
    }<br>
<br>
    compiled = makeGrammar(grammar, {'lookup': bindings.__getitem__})<br>
    for test in tests:<br>
      parsed = compiled(test).specification()<br>
      print(parsed)<br>
<br>
References<br>
==========<br>
<br>
.. [#pip] pip, the recommended installer for Python packages<br>
   (<a href="http://pip.readthedocs.org/en/stable/" rel="noreferrer" target="_blank">http://pip.readthedocs.org/en/stable/</a>)<br>
<br>
.. [#pep345] PEP-345, Python distribution metadata version 1.2.<br>
   (<a href="https://www.python.org/dev/peps/pep-0345/" rel="noreferrer" target="_blank">https://www.python.org/dev/peps/pep-0345/</a>)<br>
<br>
.. [#pep426] PEP-426, Python distribution metadata.<br>
   (<a href="https://www.python.org/dev/peps/pep-0426/" rel="noreferrer" target="_blank">https://www.python.org/dev/peps/pep-0426/</a>)<br>
<br>
.. [#pep440] PEP-440, Python distribution metadata.<br>
   (<a href="https://www.python.org/dev/peps/pep-0440/" rel="noreferrer" target="_blank">https://www.python.org/dev/peps/pep-0440/</a>)<br>
<br>
.. [#std66] The URL specification.<br>
   (<a href="https://tools.ietf.org/html/rfc3986" rel="noreferrer" target="_blank">https://tools.ietf.org/html/rfc3986</a>)<br>
<br>
.. [#parsley] The parsley PEG library.<br>
   (<a href="https://pypi.python.org/pypi/parsley/" rel="noreferrer" target="_blank">https://pypi.python.org/pypi/parsley/</a>)<br>
<br>
Copyright<br>
=========<br>
<br>
This document has been placed in the public domain.<br>
<br>
<br>
<br>
..<br>
   Local Variables:<br>
   mode: indented-text<br>
   indent-tabs-mode: nil<br>
   sentence-end-double-space: t<br>
   fill-column: 70<br>
   coding: utf-8<br>
   End:<br>
<span class="HOEnZb"><font color="#888888"><br>
<br>
--<br>
Robert Collins <<a href="mailto:rbtcollins@hp.com">rbtcollins@hp.com</a>><br>
Distinguished Technologist<br>
HP Converged Cloud<br>
_______________________________________________<br>
Distutils-SIG maillist  -  <a href="mailto:Distutils-SIG@python.org">Distutils-SIG@python.org</a><br>
<a href="https://mail.python.org/mailman/listinfo/distutils-sig" rel="noreferrer" target="_blank">https://mail.python.org/mailman/listinfo/distutils-sig</a><br>
</font></span></blockquote></div><br></div>