依存関係指定子#
元々は PEP 508 で指定されていた依存関係指定子のフォーマットを、この説明文書は記述します。
依存関係 <dependency> の任務は、 pip [1] のようなツールがインストールするべき正しいパッケージを探し出すことができるようにすることです。これは時には大変に曖昧で名称を指定するだけであったり、別の時には非常に限定的でインストールするべき特定のファイルを参照したりします。場合によっては、依存関係 <dependency> がひとつのプラットフォームでのみ妥当であったり、いくつかのバージョンだけが受け入れ可能であったりするので、(依存関係 <dependency> を記述する) 言語としてはこれらすべてのケースを記述できるものでなければなりません。
The language defined is a compact line based format which is already in widespread use in pip requirements files, though we do not specify the command line option handling that those files permit. There is one caveat - the URL reference form, specified in Versioning specifier specification is not actually implemented in pip, but we use that format rather than pip's current native format.
仕様#
例#
この言語のすべての機能を、名前に基づいた参照とともに示します:
requests [security,tests] >= 2.8.1, == 2.8.* ; python_version < "2.7"
最低限の URL に基づいた参照:
pip @ https://github.com/pypa/pip/archive/1.3.1.zip#sha1=da9234ee9982d4bbb3c72346a6de940a148ea686
概念#
依存関係の指定では、常に、配布物の名前を指定します。名前で指定された配布物で特定の追加機能を有効にするように依存関係を拡張するような追加物 <extra> を含んでいても構いません。インストールされたバージョンをバージョンリミットで制御することもできますし、特定のアーティファクトをインストールするために URL を与えることもできます。依存関係は最終的に環境マーカを用いて条件別に作成することもできます。
文法#
最初に文法について簡単に触れた後、それぞれの節の意味論 <semantics> について深く掘り下げることにしましょう。
配布物の仕様は ASCII テキストで書かれています。厳密な文法としては parsley [2] の文法を使っています。この仕様は、コメントや継続による複数行サポートやその他の機能の枠組みを与えるもっと大きなシステムの中に組み込まれることを期待しています。
役に立つ構文解析ツリーを構成するための注釈機能を含む完全な文法は、この説明文書の末尾に置きました。
Versions may be specified according to the rules of the Version specifier specification. (Note: URI is defined in std-66):
version_cmp = wsp* '<' | '<=' | '!=' | '==' | '>=' | '>' | '~=' | '==='
version = wsp* ( letterOrDigit | '-' | '_' | '.' | '*' | '+' | '!' )+
version_one = version_cmp version wsp*
version_many = version_one (wsp* ',' version_one)*
versionspec = ( '(' version_many ')' ) | version_many
urlspec = '@' wsp* <URI_reference>
環境マーカを使うことで、ある仕様が特定の環境でのみ有効であることを示すことができます:
marker_op = version_cmp | (wsp* 'in') | (wsp* 'not' wsp+ 'in')
python_str_c = (wsp | letter | digit | '(' | ')' | '.' | '{' | '}' |
'-' | '_' | '*' | '#' | ':' | ';' | ',' | '/' | '?' |
'[' | ']' | '!' | '~' | '`' | '@' | '$' | '%' | '^' |
'&' | '=' | '+' | '|' | '<' | '>' )
dquote = '"'
squote = '\\''
python_str = (squote (python_str_c | dquote)* squote |
dquote (python_str_c | squote)* dquote)
env_var = ('python_version' | 'python_full_version' |
'os_name' | 'sys_platform' | 'platform_release' |
'platform_system' | 'platform_version' |
'platform_machine' | 'platform_python_implementation' |
'implementation_name' | 'implementation_version' |
'extra' # ONLY when defined by a containing layer
)
marker_var = wsp* (env_var | python_str)
marker_expr = marker_var marker_op marker_var
| wsp* '(' marker wsp* ')'
marker_and = marker_expr wsp* 'and' marker_expr
| marker_expr
marker_or = marker_and wsp* 'or' marker_and
| marker_and
marker = marker_or
quoted_marker = ';' wsp* marker
配布物のうちの必須ではない部分については extras フィールドを使って指定することができます:
identifier_end = letterOrDigit | (('-' | '_' | '.' )* letterOrDigit)
identifier = letterOrDigit identifier_end*
name = identifier
extras_list = identifier (wsp* ',' wsp* identifier)*
extras = '[' wsp* extras_list? wsp* ']'
追加物の名前に対する制限事項は PEP 685 で定義されています。
私たちに名前に基づいた要求仕様を与えてください:
name_req = name wsp* extras? wsp* versionspec? wsp* quoted_marker?
そして、直接参照に用いる要求仕様のための規則はこちら:
url_req = name wsp* extras? wsp* urlspec wsp+ quoted_marker?
依存関係を指定することができる統一規則への案内はこちら:
specification = wsp* ( url_req | name_req ) wsp*
空白文字 <Whitespace>#
行を分割するものではない空白文字には特に意味はなく、ほとんどの場合には必須ではないものです。唯一の例外は、 URL による要求事項の末尾を検出するためのものです。
名前 <Names>#
Python の配布物の名前は、現時点では PEP 345 で定義されています。名前は配布物の最も基本的な識別子として働きます。(名前は) あらゆる依存関係の指定に出現し、それだけで十分に指定することができます。しかしながら、 PyPI では名前に厳密な制約を課しています - 名前は大文字小文字を区別しない正規表現に合致しなければ受け入れられません。従って、この説明文書では、その正規表現に合致する識別子だけを受け入れ可能な値として扱うことにしましょう。名前の完全な再定義はメタデータ PEP として将来に出現するかもしれません。ここでいう (re.IGNORECASE とともに評価されるべき) 正規表現とは、次のようなものです:
^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$
追加物 <Extras>#
追加物とは、配布物の必須ではない部分のことです。配布物では追加物を幾つでも指定することができ、追加物が依存関係の指定場所で使われた 場合 には、それぞれの追加物が配布物の追加的な依存関係を宣言する結果になります。例えば:
requests[security,tests]
追加物 <extras> の依存関係の合併とは、その追加物が添付されている配布物 <distribution> の依存関係と一緒に定義されることです。上に示した例では、結果として requests がインストールされることになり、requests は自身の依存関係を持つので requests の "security" 追加物 <extra> に列挙されたすべての依存関係 (先) もインストールされることになります。
複数の追加物 <extra> が列挙されている場合には、すべての依存関係の合併集合が依存関係になります。
バージョン指定子#
See the Version specifier specification for more detail on both version numbers and version comparisons. Version specifications limit the versions of a distribution that can be used. They only apply to distributions looked up by name, rather than via a URL. Version comparison are also used in the markers feature. The optional brackets around a version are present for compatibility with PEP 345 but should not be generated, only accepted.
環境マーカ#
環境マーカは、依存関係の指定においてその依存関係がいつ使われるべきであるかを記述する規則を提供します。例えば、あるパッケージが argparse を必要とするとしましょう。Python 2.7 では argparse は常に存在します。もっと古いバージョンの Python では依存関係としてインストールされなければなりません。これは次のように表現することができます:
argparse;python_version<"2.7"
マーカ表現は評価されると真か偽に帰着します。偽と評価された場合には、その依存関係の指定は無視されなければなりません。
マーカ言語は Python そのものに触発されたもので、セキュリティ上の脆弱性になりかねない任意コードの実行を伴わずに安全に評価を行うことができるので選ばれました。マーカは:pep:345
で初めて標準化されました。この説明文書では、 PEP 426 に記述されたデザインに見られるいくつかの問題点を修正しています。
Comparisons in marker expressions are typed by the comparison operator. The <marker_op> operators that are not in <version_cmp> perform the same as they do for strings in Python. The <version_cmp> operators use the version comparison rules of the Version specifier specification when those are defined (that is when both sides have a valid version specifier). If there is no defined behaviour of this specification and the operator exists in Python, then the operator falls back to the Python behaviour. Otherwise an error should be raised. e.g. the following will result in errors:
"dog" ~= "fred"
python_version ~= "surprise"
ユーザ側から供給された定数は、常に '
または "
なる引用記号を伴った文字列として符号化されます。バックスラッシュによるエスケープは定義されていませんが、現存する実装ではサポートされているということを忘れないでください。この仕様には (バックスラッシュエスケープは) 含まれていませんが、それは、複雑性を増加させてしまうことと、現時点では目に見えるほどの必要性がないことが理由です。同様に、非 ASCII 文字のサポートも定義していません: 我々が参照するようなランタイムのすべての変数は、 ASCII 文字のみで構成されているものと期待されています。
"os_name" のようなマーカの文法内の変数は、 Python のランタイム内でルックアップすることで値へと解決されます。 "extra" を例外として、すべての値は現在のすべてのバージョンの Python で定義されています - もし値が定義されていなければ、それはマーカの実装のエラーです。
未知の変数は、評価して真 <True> か偽 <False> となる比較の結果を返すのではなく、エラーを生成しなければなりません。
特定の Python 実装で値を計算することができない変数は、バージョンについては 0
として、その他のすべての変数については空文字列として評価されるべきです。
"extra" 変数は、扱いが特別です。それは wheel ファイルにおいて、その wheel の METADATA
ファイル内の特定の追加物 <extra> にどの仕様を適用するべきであるかを知らせるために使われますが、 METADATA
ファイルの様式が PEP 426 のドラフトバージョンに基づいているので、現時点ではその仕様が存在していないのです。それにも関わらず、この特別な扱いが行われる場所ではない文脈においては、 "extra" 変数はその他の未知の変数と同様にエラーに帰着するべきです。
マーカ <Marker> |
Python 同等物 |
値の例 |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
下方の定義を見てください |
|
|
仕様を通訳する文脈で定義された場合を除くエラー。 |
|
マーカ変数の implementation_version
は、 sys.implementation.version
から派生したものです
def format_full_version(info):
version = '{0.major}.{0.minor}.{0.micro}'.format(info)
kind = info.releaselevel
if kind != 'final':
version += kind[0] + str(info.serial)
return version
if hasattr(sys, 'implementation'):
implementation_version = format_full_version(sys.implementation.version)
else:
implementation_version = "0"
この環境マーカの節は、当初は PEP 508 を通して定義されましたが、 PEP 345 における環境マーカの節を置き換えます。
完全な文法#
完全な parsley 文法:
wsp = ' ' | '\t'
version_cmp = wsp* <'<=' | '<' | '!=' | '==' | '>=' | '>' | '~=' | '==='>
version = wsp* <( letterOrDigit | '-' | '_' | '.' | '*' | '+' | '!' )+>
version_one = version_cmp:op version:v wsp* -> (op, v)
version_many = version_one:v1 (wsp* ',' version_one)*:v2 -> [v1] + v2
versionspec = ('(' version_many:v ')' ->v) | version_many
urlspec = '@' wsp* <URI_reference>
marker_op = version_cmp | (wsp* 'in') | (wsp* 'not' wsp+ 'in')
python_str_c = (wsp | letter | digit | '(' | ')' | '.' | '{' | '}' |
'-' | '_' | '*' | '#' | ':' | ';' | ',' | '/' | '?' |
'[' | ']' | '!' | '~' | '`' | '@' | '$' | '%' | '^' |
'&' | '=' | '+' | '|' | '<' | '>' )
dquote = '"'
squote = '\\''
python_str = (squote <(python_str_c | dquote)*>:s squote |
dquote <(python_str_c | squote)*>:s dquote) -> s
env_var = ('python_version' | 'python_full_version' |
'os_name' | 'sys_platform' | 'platform_release' |
'platform_system' | 'platform_version' |
'platform_machine' | 'platform_python_implementation' |
'implementation_name' | 'implementation_version' |
'extra' # ONLY when defined by a containing layer
):varname -> lookup(varname)
marker_var = wsp* (env_var | python_str)
marker_expr = marker_var:l marker_op:o marker_var:r -> (o, l, r)
| wsp* '(' marker:m wsp* ')' -> m
marker_and = marker_expr:l wsp* 'and' marker_expr:r -> ('and', l, r)
| marker_expr:m -> m
marker_or = marker_and:l wsp* 'or' marker_and:r -> ('or', l, r)
| marker_and:m -> m
marker = marker_or
quoted_marker = ';' wsp* marker
identifier_end = letterOrDigit | (('-' | '_' | '.' )* letterOrDigit)
identifier = < letterOrDigit identifier_end* >
name = identifier
extras_list = identifier:i (wsp* ',' wsp* identifier)*:ids -> [i] + ids
extras = '[' wsp* extras_list?:e wsp* ']' -> e
name_req = (name:n wsp* extras?:e wsp* versionspec?:v wsp* quoted_marker?:m
-> (n, e or [], v or [], m))
url_req = (name:n wsp* extras?:e wsp* urlspec:v (wsp+ | end) quoted_marker?:m
-> (n, e or [], v, m))
specification = wsp* ( url_req | name_req ):s wsp* -> s
# The result is a tuple - name, list-of-extras,
# list-of-version-constraints-or-a-url, marker-ast or None
URI_reference = <URI | relative_ref>
URI = scheme ':' hier_part ('?' query )? ( '#' fragment)?
hier_part = ('//' authority path_abempty) | path_absolute | path_rootless | path_empty
absolute_URI = scheme ':' hier_part ( '?' query )?
relative_ref = relative_part ( '?' query )? ( '#' fragment )?
relative_part = '//' authority path_abempty | path_absolute | path_noscheme | path_empty
scheme = letter ( letter | digit | '+' | '-' | '.')*
authority = ( userinfo '@' )? host ( ':' port )?
userinfo = ( unreserved | pct_encoded | sub_delims | ':')*
host = IP_literal | IPv4address | reg_name
port = digit*
IP_literal = '[' ( IPv6address | IPvFuture) ']'
IPvFuture = 'v' hexdig+ '.' ( unreserved | sub_delims | ':')+
IPv6address = (
( h16 ':'){6} ls32
| '::' ( h16 ':'){5} ls32
| ( h16 )? '::' ( h16 ':'){4} ls32
| ( ( h16 ':')? h16 )? '::' ( h16 ':'){3} ls32
| ( ( h16 ':'){0,2} h16 )? '::' ( h16 ':'){2} ls32
| ( ( h16 ':'){0,3} h16 )? '::' h16 ':' ls32
| ( ( h16 ':'){0,4} h16 )? '::' ls32
| ( ( h16 ':'){0,5} h16 )? '::' h16
| ( ( h16 ':'){0,6} h16 )? '::' )
h16 = hexdig{1,4}
ls32 = ( h16 ':' h16) | IPv4address
IPv4address = dec_octet '.' dec_octet '.' dec_octet '.' dec_octet
nz = ~'0' digit
dec_octet = (
digit # 0-9
| nz digit # 10-99
| '1' digit{2} # 100-199
| '2' ('0' | '1' | '2' | '3' | '4') digit # 200-249
| '25' ('0' | '1' | '2' | '3' | '4' | '5') )# %250-255
reg_name = ( unreserved | pct_encoded | sub_delims)*
path = (
path_abempty # begins with '/' or is empty
| path_absolute # begins with '/' but not '//'
| path_noscheme # begins with a non-colon segment
| path_rootless # begins with a segment
| path_empty ) # zero characters
path_abempty = ( '/' segment)*
path_absolute = '/' ( segment_nz ( '/' segment)* )?
path_noscheme = segment_nz_nc ( '/' segment)*
path_rootless = segment_nz ( '/' segment)*
path_empty = pchar{0}
segment = pchar*
segment_nz = pchar+
segment_nz_nc = ( unreserved | pct_encoded | sub_delims | '@')+
# non-zero-length segment without any colon ':'
pchar = unreserved | pct_encoded | sub_delims | ':' | '@'
query = ( pchar | '/' | '?')*
fragment = ( pchar | '/' | '?')*
pct_encoded = '%' hexdig
unreserved = letter | digit | '-' | '.' | '_' | '~'
reserved = gen_delims | sub_delims
gen_delims = ':' | '/' | '?' | '#' | '(' | ')?' | '@'
sub_delims = '!' | '$' | '&' | '\\'' | '(' | ')' | '*' | '+' | ',' | ';' | '='
hexdig = digit | 'a' | 'A' | 'b' | 'B' | 'c' | 'C' | 'd' | 'D' | 'e' | 'E' | 'f' | 'F'
テストプログラム - もし grammar
文字列内に文法があれば:
import os
import sys
import platform
from parsley import makeGrammar
grammar = """
wsp ...
"""
tests = [
"A",
"A.B-C_D",
"aa",
"name",
"name<=1",
"name>=3",
"name>=3,<2",
"name@http://foo.com",
"name [fred,bar] @ http://foo.com ; python_version=='2.7'",
"name[quux, strange];python_version<'2.7' and platform_version=='2'",
"name; os_name=='a' or os_name=='b'",
# Should parse as (a and b) or c
"name; os_name=='a' and os_name=='b' or os_name=='c'",
# Overriding precedence -> a and (b or c)
"name; os_name=='a' and (os_name=='b' or os_name=='c')",
# should parse as a or (b and c)
"name; os_name=='a' or os_name=='b' and os_name=='c'",
# Overriding precedence -> (a or b) and c
"name; (os_name=='a' or os_name=='b') and os_name=='c'",
]
def format_full_version(info):
version = '{0.major}.{0.minor}.{0.micro}'.format(info)
kind = info.releaselevel
if kind != 'final':
version += kind[0] + str(info.serial)
return version
if hasattr(sys, 'implementation'):
implementation_version = format_full_version(sys.implementation.version)
implementation_name = sys.implementation.name
else:
implementation_version = '0'
implementation_name = ''
bindings = {
'implementation_name': implementation_name,
'implementation_version': implementation_version,
'os_name': os.name,
'platform_machine': platform.machine(),
'platform_python_implementation': platform.python_implementation(),
'platform_release': platform.release(),
'platform_system': platform.system(),
'platform_version': platform.version(),
'python_full_version': platform.python_version(),
'python_version': '.'.join(platform.python_version_tuple()[:2]),
'sys_platform': sys.platform,
}
compiled = makeGrammar(grammar, {'lookup': bindings.__getitem__})
for test in tests:
parsed = compiled(test).specification()
print("%s -> %s" % (test, parsed))
PEP 508 に対する変更の要旨#
以降の変更は、最初の実装 (ができた) 後に寄せられたフィードバックに基づくものです:
python_version
の定義は、 Python の将来のバージョンが二桁のメジャーバージョンやマイナーバージョンを持つ場合 (例えば 3.10) でもそれを収容できるように、platform.python_version()[:3]
から'.'.join(platform.python_version_tuple()[:2])
へと変更されました。 [3]