Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
后端镜像
Pdm
提交
4bbace23
P
Pdm
项目概览
后端镜像
/
Pdm
通知
0
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
P
Pdm
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
未验证
提交
4bbace23
编写于
1月 08, 2020
作者:
F
Frost Ming
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
get dependencies from metadata
上级
4168a9b3
变更
17
隐藏空白更改
内联
并排
Showing
17 changed file
with
513 addition
and
157 deletion
+513
-157
.gitignore
.gitignore
+1
-0
my_script.py
my_script.py
+17
-3
pdm/context.py
pdm/context.py
+53
-0
pdm/exceptions.py
pdm/exceptions.py
+8
-0
pdm/models/candidates.py
pdm/models/candidates.py
+181
-41
pdm/models/markers.py
pdm/models/markers.py
+34
-1
pdm/models/readers.py
pdm/models/readers.py
+0
-0
pdm/models/repositories.py
pdm/models/repositories.py
+29
-13
pdm/models/requirements.py
pdm/models/requirements.py
+52
-46
pdm/models/specifiers.py
pdm/models/specifiers.py
+3
-3
pdm/resolver/providers.py
pdm/resolver/providers.py
+2
-3
pdm/utils.py
pdm/utils.py
+69
-9
tests/fixtures/artifacts/demo-0.0.1-py2.py3-none-any.whl
tests/fixtures/artifacts/demo-0.0.1-py2.py3-none-any.whl
+2
-2
tests/fixtures/artifacts/demo-0.0.1.tar.gz
tests/fixtures/artifacts/demo-0.0.1.tar.gz
+2
-2
tests/fixtures/projects/demo/setup.py
tests/fixtures/projects/demo/setup.py
+6
-2
tests/models/test_requirements.py
tests/models/test_requirements.py
+28
-7
tests/models/test_specifiers.py
tests/models/test_specifiers.py
+26
-25
未找到文件。
.gitignore
浏览文件 @
4bbace23
...
...
@@ -128,3 +128,4 @@ dmypy.json
# Pyre type checker
.pyre/
.vscode/
caches/
my_script.py
浏览文件 @
4bbace23
from
pdm.models.specifiers
import
PySpecSet
from
pdm.models.repositories
import
PyPIRepository
from
pdm.models.requirements
import
Requirement
from
pdm.models.candidates
import
Candidate
from
pdm.context
import
context
p
=
PySpecSet
(
'>=2.7,!=3.0.*'
)
&
PySpecSet
(
'!=3.1.*'
)
print
(
p
)
class
FakeProject
:
config
=
{
"cache_dir"
:
"./caches"
}
packages_root
=
None
context
.
init
(
FakeProject
())
req
=
Requirement
.
from_line
(
"-e ./tests/fixtures/projects/demo"
)
repo
=
PyPIRepository
([])
can
=
Candidate
(
req
,
repo
)
can
.
prepare_source
()
deps
=
can
.
get_dependencies_from_metadata
()
print
(
deps
)
pdm/context.py
0 → 100644
浏览文件 @
4bbace23
from
functools
import
wraps
from
pathlib
import
Path
from
pip_shims
import
shims
from
pdm.exceptions
import
ProjectNotInitialized
def
require_initialize
(
func
):
@
wraps
(
func
)
def
wrapper
(
self
,
*
args
,
**
kwargs
):
if
not
self
.
initialized
:
raise
ProjectNotInitialized
()
return
func
(
self
,
*
args
,
**
kwargs
)
return
wrapper
class
Context
:
"""A singleton context object that holds some global states.
Global states are evil but make it easier to share configs between
different modules.
"""
def
__init__
(
self
):
self
.
project
=
None
def
init
(
self
,
project
):
self
.
project
=
project
self
.
_initialized
=
True
@
property
def
initialized
(
self
)
->
bool
:
return
self
.
_initialized
@
property
@
require_initialize
def
cache_dir
(
self
)
->
Path
:
return
Path
(
self
.
project
.
config
.
get
(
"cache_dir"
))
@
require_initialize
def
cache
(
self
,
name
:
str
)
->
Path
:
path
=
self
.
cache_dir
/
name
path
.
mkdir
(
exist_ok
=
True
)
return
path
@
require_initialize
def
make_wheel_cache
(
self
)
->
shims
.
WheelCache
:
return
shims
.
WheelCache
(
self
.
cache_dir
.
as_posix
(),
shims
.
FormatControl
(
set
(),
set
()),
)
context
=
Context
()
pdm/exceptions.py
浏览文件 @
4bbace23
...
...
@@ -8,3 +8,11 @@ class RequirementError(PdmException, ValueError):
class
InvalidPyVersion
(
PdmException
,
ValueError
):
pass
class
WheelBuildError
(
PdmException
):
pass
class
ProjectNotInitialized
(
PdmException
):
pass
pdm/models/candidates.py
浏览文件 @
4bbace23
from
typing
import
Optional
,
TYPE_CHECKING
import
os
from
typing
import
Optional
,
Any
,
Dict
,
List
from
distlib.database
import
EggInfoDistribution
from
distlib.metadata
import
Metadata
from
pip_shims
import
InstallRequirement
from
pip._internal.exceptions
import
InstallationError
from
distlib.wheel
import
Wheel
from
pip_shims
import
shims
from
pkg_resources
import
safe_extra
if
TYPE_CHECKING
:
from
pdm.models.requirements
import
Requirement
from
pdm.models.repositories
import
BaseRepository
from
pdm.context
import
context
from
pdm.exceptions
import
WheelBuildError
,
RequirementError
from
pdm.utils
import
cached_property
,
create_tracked_tempdir
from
pdm.models.markers
import
split_marker_element
,
Marker
from
pdm.models.requirements
import
Requirement
vcs
=
shims
.
VcsSupport
()
def
get_sdist
(
ireq
:
InstallRequirement
)
->
Optional
[
EggInfoDistribution
]:
try
:
egg_info
=
ireq
.
egg_info_path
except
InstallationError
:
ireq
.
run_egg_info
()
egg_info
=
ireq
.
egg_info_path
def
get_sdist
(
ireq
:
shims
.
InstallRequirement
)
->
Optional
[
EggInfoDistribution
]:
egg_info
=
ireq
.
egg_info_path
return
EggInfoDistribution
(
egg_info
)
if
egg_info
else
None
def
get_source_dir
()
->
str
:
build_dir
=
context
.
project
.
packages_root
if
build_dir
:
src_dir
=
build_dir
/
"src"
src_dir
.
mkdir
(
exist_ok
=
True
)
return
src_dir
.
as_posix
()
venv
=
os
.
environ
.
get
(
"VIRTUAL_ENV"
,
None
)
if
venv
:
src_dir
=
os
.
path
.
join
(
venv
,
"src"
)
if
os
.
path
.
exists
(
src_dir
):
return
src_dir
return
create_tracked_tempdir
(
"pdm-src"
)
class
Candidate
:
"""A concrete candidate that can be downloaded and installed."""
def
__init__
(
self
,
req
,
repository
=
None
):
# type: (Requirement, Optional[BaseRepository]) -> None
def
__init__
(
self
,
req
,
repository
,
name
=
None
,
version
=
None
,
link
=
None
):
self
.
req
=
req
self
.
repository
=
repository
self
.
name
=
name
self
.
version
=
version
if
link
is
None
:
link
=
self
.
ireq
.
link
self
.
link
=
link
self
.
wheel
=
None
self
.
build_dir
=
None
self
.
metadata
=
None
@
cached_property
def
ireq
(
self
)
->
shims
.
InstallRequirement
:
return
self
.
req
.
as_ireq
()
@
property
def
is_wheel
(
self
)
->
bool
:
return
self
.
link
.
is_wheel
@
cached_property
def
revision
(
self
)
->
str
:
if
not
self
.
req
.
is_vcs
:
raise
AttributeError
(
"Non-VCS candidate doesn't have revision attribute"
)
return
vcs
.
get_backend
(
self
.
req
.
vcs
).
get_revision
(
self
.
ireq
.
source_dir
)
def
get_metadata
(
self
)
->
Optional
[
Metadata
]:
ireq
=
self
.
req
.
as_ireq
()
if
self
.
metadata
is
not
None
:
return
self
.
metadata
ireq
=
self
.
ireq
if
ireq
.
editable
:
if
not
self
.
req
.
is_local_dir
and
not
self
.
req
.
is_vcs
:
raise
RequirementError
(
"Editable installation is only supported for "
"local directory and VCS location."
)
ireq
.
run_egg_info
()
sdist
=
get_sdist
(
ireq
)
return
sdist
.
metadata
if
sdist
else
None
self
.
metadata
=
sdist
.
metadata
if
sdist
else
None
else
:
if
not
self
.
wheel
:
self
.
_build_wheel
()
return
self
.
wheel
.
meta
def
_build_wheel
(
self
)
->
None
:
pass
class
LocalCandidate
(
Candidate
):
def
__init__
(
self
,
req
,
repository
=
None
):
# type: (Requirement, Optional[BaseRepository]) -> None
super
().
__init__
(
req
,
repository
)
self
.
location
=
self
.
req
.
path
.
absolute
()
def
prepare_sources
(
self
)
->
None
:
self
.
metadata
=
self
.
wheel
.
metadata
if
not
self
.
name
:
self
.
name
=
self
.
metadata
.
name
self
.
req
.
name
=
self
.
name
if
not
self
.
version
:
self
.
version
=
self
.
metadata
.
version
return
self
.
metadata
def
__repr__
(
self
)
->
str
:
return
f
"<Candidate
{
self
.
name
}
{
self
.
link
.
url
}
>"
def
_make_pip_wheel_args
(
self
)
->
Dict
[
str
,
Any
]:
src_dir
=
self
.
ireq
.
source_dir
or
get_source_dir
()
if
self
.
req
.
editable
:
self
.
build_dir
=
src_dir
else
:
self
.
build_dir
=
create_tracked_tempdir
(
prefix
=
"pdm-build"
)
download_dir
=
context
.
cache
(
"pkgs"
)
download_dir
.
mkdir
(
exist_ok
=
True
)
wheel_download_dir
=
context
.
cache
(
"wheels"
)
wheel_download_dir
.
mkdir
(
exist_ok
=
True
)
return
{
"build_dir"
:
self
.
build_dir
,
"src_dir"
:
src_dir
,
"download_dir"
:
download_dir
.
as_posix
(),
"wheel_download_dir"
:
wheel_download_dir
.
as_posix
(),
}
def
prepare_source
(
self
)
->
None
:
"""A local candidate has already everything in local, no need to download."""
pass
@
property
def
source_dir
(
self
)
->
str
:
return
self
.
location
.
as_posix
()
kwargs
=
self
.
_make_pip_wheel_args
()
with
self
.
repository
.
get_finder
()
as
finder
:
self
.
ireq
.
populate_link
(
finder
,
False
,
False
)
if
not
self
.
req
.
editable
and
not
self
.
req
.
name
:
self
.
ireq
.
source_dir
=
kwargs
[
"build_dir"
]
else
:
self
.
ireq
.
ensure_has_source_dir
(
kwargs
[
"build_dir"
])
if
self
.
req
.
editable
and
self
.
req
.
is_local_dir
:
return
download_dir
=
kwargs
[
"download_dir"
]
if
self
.
is_wheel
:
download_dir
=
kwargs
[
"wheel_download_dir"
]
shims
.
shim_unpack
(
link
=
self
.
link
,
download_dir
=
download_dir
,
location
=
self
.
ireq
.
source_dir
,
session
=
finder
.
session
,
)
class
RemoteCandidate
(
LocalCandidate
):
pass
class
VcsCandidate
(
LocalCandidate
):
pass
def
_build_wheel
(
self
)
->
None
:
if
self
.
is_wheel
:
self
.
wheel
=
Wheel
(
(
context
.
cache
(
"wheels"
)
/
self
.
link
.
filename
).
as_posix
()
)
return
if
not
self
.
req
.
name
:
# Name is not available for a tarball distribution. Get the package name
# from package's egg info.
# `run_egg_info()` won't work if there is a `req` attribute available.
self
.
ireq
.
req
=
None
self
.
ireq
.
run_egg_info
()
self
.
req
.
name
=
self
.
ireq
.
metadata
[
"Name"
]
self
.
ireq
.
req
=
self
.
req
with
self
.
repository
.
get_finder
()
as
finder
:
kwargs
=
self
.
_make_pip_wheel_args
()
with
shims
.
make_preparer
(
finder
=
finder
,
session
=
finder
.
session
,
**
kwargs
)
as
preparer
:
wheel_cache
=
context
.
make_wheel_cache
()
builder
=
shims
.
WheelBuilder
(
finder
=
finder
,
preparer
=
preparer
,
wheel_cache
=
wheel_cache
)
output_dir
=
create_tracked_tempdir
(
prefix
=
"pdm-ephem"
)
wheel_path
=
builder
.
_build_one
(
self
.
ireq
,
output_dir
)
if
not
wheel_path
or
not
os
.
path
.
exists
(
wheel_path
):
raise
WheelBuildError
(
str
(
self
.
ireq
))
self
.
wheel
=
Wheel
(
wheel_path
)
@
classmethod
def
from_installation_candidate
(
cls
,
candidate
,
req
,
repo
):
inst
=
cls
(
req
,
repo
,
name
=
candidate
.
project
,
version
=
candidate
.
version
,
link
=
candidate
.
link
,
)
return
inst
def
get_dependencies_from_metadata
(
self
)
->
List
[
str
]:
extras
=
self
.
req
.
extras
or
()
metadata
=
self
.
get_metadata
()
result
=
[]
if
self
.
req
.
editable
:
if
not
metadata
:
return
result
dep_map
=
self
.
ireq
.
get_dist
().
_build_dep_map
()
for
extra
,
reqs
in
dep_map
.
items
():
reqs
=
[
Requirement
.
from_pkg_requirement
(
r
)
for
r
in
reqs
]
if
not
extra
:
result
.
extend
(
r
.
as_line
()
for
r
in
reqs
)
else
:
new_extra
,
_
,
marker
=
extra
.
partition
(
":"
)
if
not
new_extra
.
strip
()
or
safe_extra
(
new_extra
.
strip
())
in
extras
:
marker
=
Marker
(
marker
)
if
marker
else
None
for
r
in
reqs
:
r
.
marker
=
marker
result
.
append
(
r
.
as_line
())
else
:
for
req
in
metadata
.
run_requires
:
_r
=
Requirement
.
from_line
(
req
)
if
not
_r
.
marker
:
result
.
append
(
req
)
else
:
elements
,
rest
=
split_marker_element
(
str
(
_r
.
marker
),
"extra"
)
_r
.
marker
=
rest
if
not
elements
or
any
(
extra
==
e
[
1
]
for
extra
in
extras
for
e
in
elements
):
result
.
append
(
_r
.
as_line
())
return
result
pdm/models/markers.py
浏览文件 @
4bbace23
from
packaging.markers
import
Marker
as
PackageMarker
from
typing
import
Union
,
Optional
from
typing
import
Union
,
Optional
,
Tuple
,
Iterable
class
Marker
(
PackageMarker
):
...
...
@@ -35,3 +35,36 @@ class Marker(PackageMarker):
def
get_marker
(
marker
:
Union
[
PackageMarker
,
Marker
,
None
])
->
Optional
[
Marker
]:
return
Marker
(
str
(
marker
))
if
marker
else
None
def
split_marker_element
(
text
:
str
,
element
:
str
)
->
Tuple
[
Iterable
[
Tuple
[
str
,
str
]],
Optional
[
str
]]:
"""An element can be stripped from the marker only if all parts are connected
with `and` operater. The rest part are returned as a string or `None` if all are
stripped.
:param text: the input marker string
:param element: the element to be stripped
:returns: an iterable of (op, value) pairs together with the stripped part.
"""
if
not
text
:
return
[],
text
marker
=
Marker
(
text
)
if
"or"
in
marker
.
_markers
:
return
[],
text
result
=
[]
bare_markers
=
[
m
for
m
in
marker
.
_markers
if
m
!=
"and"
]
for
m
in
bare_markers
[:]:
if
not
isinstance
(
m
,
tuple
):
continue
if
m
[
0
].
value
==
element
:
result
.
append
(
tuple
(
e
.
value
for
e
in
m
[
1
:]))
bare_markers
.
remove
(
m
)
if
not
bare_markers
:
return
result
,
None
new_markers
=
[
bare_markers
[
0
]]
for
m
in
bare_markers
[
1
:]:
new_markers
.
extend
([
"and"
,
m
])
marker
.
_markers
=
new_markers
return
result
,
marker
pdm/models/
project_file
s.py
→
pdm/models/
reader
s.py
浏览文件 @
4bbace23
文件已移动
pdm/models/repositories.py
浏览文件 @
4bbace23
from
typing
import
List
,
Tuple
,
Optional
from
contextlib
import
contextmanager
from
typing
import
List
,
Tuple
import
pip_shims
from
pdm.types
import
Source
from
pdm.models.candidates
import
Candidate
from
pdm.models.requirements
import
Requirement
from
pdm.models.specifiers
import
PySpecSet
from
pdm.utils
import
get_package_finder
from
pdm.types
import
Source
from
pdm.utils
import
get_finder
from
pdm.context
import
context
class
BaseRepository
:
def
__init__
(
self
,
source
:
Source
,
cache_dir
:
str
)
->
None
:
self
.
source
=
source
self
.
cache_dir
=
cache_dir
def
__init__
(
self
,
sources
:
List
[
Source
])
->
None
:
self
.
sources
=
sources
def
match_index
(
self
,
requirement
:
Requirement
)
->
bool
:
return
requirement
.
index
is
None
or
requirement
.
index
==
self
.
source
[
"name"
]
def
get_finder
(
self
,
requires_python
:
Optional
[
PySpecSet
]
)
->
pip_shims
.
PackageFinder
:
return
get_package_finder
([
self
.
source
],
requires_python
.
as_py_versions
())
@
contextmanager
def
get_finder
(
self
)
->
pip_shims
.
PackageFinder
:
finder
=
get_finder
(
self
.
sources
,
context
.
cache_dir
.
as_posix
())
yield
finder
finder
.
session
.
close
()
def
get_dependencies
(
self
,
candidate
:
Candidate
...
...
@@ -32,6 +32,22 @@ class BaseRepository:
requires_python
:
PySpecSet
,
allow_prereleases
:
bool
=
False
,
)
->
List
[
Candidate
]:
if
self
.
requirement
.
is_named
:
return
self
.
_find_named_matches
(
requirement
,
requires_python
,
allow_prereleases
)
else
:
return
[
Candidate
(
requirement
,
self
)]
def
_find_named_matches
(
self
,
requirement
:
Requirement
,
requires_python
:
PySpecSet
,
allow_prereleases
:
bool
=
False
,
)
->
List
[
Candidate
]:
"""Find candidates of the given NamedRequirement. Let it to be implemented in
subclasses.
"""
raise
NotImplementedError
def
_get_dependencies_from_cache
(
...
...
pdm/models/requirements.py
浏览文件 @
4bbace23
from
pathlib
import
Path
import
re
import
os
from
typing
import
Any
,
Dict
,
Optional
,
Tuple
import
urllib.parse
as
urlparse
...
...
@@ -13,15 +14,10 @@ from packaging.specifiers import SpecifierSet
import
pip_shims
from
pdm.models.markers
import
get_marker
from
pdm.models.candidates
import
(
Candidate
,
RemoteCandidate
,
LocalCandidate
,
VcsCandidate
,
)
from
pdm.models.project_files
import
SetupReader
from
pdm.models.readers
import
SetupReader
from
pdm.types
import
RequirementDict
from
pdm.exceptions
import
RequirementError
from
pdm.utils
import
parse_name_version_from_wheel
,
url_without_fragments
VCS_SCHEMA
=
(
"git"
,
"hg"
,
"svn"
,
"bzr"
)
...
...
@@ -50,7 +46,7 @@ class Requirement:
"""
VCS_REQ
=
re
.
compile
(
rf
"(?P<editable>-e[\t ]+)(?P<vcs>
{
'|'
.
join
(
VCS_SCHEMA
)
}
)\+"
rf
"(?P<editable>-e[\t ]+)
?
(?P<vcs>
{
'|'
.
join
(
VCS_SCHEMA
)
}
)\+"
r
"(?P<url>[^\s;]+)(?P<marker>[\t ]*;[^\n]+)?"
)
_PATH_START
=
r
"(?:\.|/|[a-zA-Z]:[/\\])"
...
...
@@ -140,16 +136,16 @@ class Requirement:
def
as_req_dict
(
self
)
->
Tuple
[
str
,
RequirementDict
]:
r
=
{}
if
self
.
is_vcs
:
r
[
self
.
vcs
]
=
self
.
repo
elif
self
.
url
:
r
[
"url"
]
=
self
.
url
if
self
.
editable
:
r
[
"editable"
]
=
True
if
self
.
extras
:
r
[
"extras"
]
=
sorted
(
self
.
extras
)
if
self
.
path
:
if
self
.
is_vcs
:
r
[
self
.
vcs
]
=
self
.
repo
elif
self
.
path
and
self
.
is_local_dir
:
r
[
"path"
]
=
self
.
str_path
elif
self
.
url
:
r
[
"url"
]
=
self
.
url
if
self
.
marker
:
r
[
"marker"
]
=
str
(
self
.
marker
)
if
self
.
specs
:
...
...
@@ -191,6 +187,17 @@ class Requirement:
ireq
.
req
=
self
return
ireq
@
classmethod
def
from_pkg_requirement
(
cls
,
req
:
PackageRequirement
)
->
"Requirement"
:
klass
=
FileRequirement
if
req
.
url
else
NamedRequirement
return
klass
(
name
=
req
.
name
,
extras
=
req
.
extras
,
url
=
req
.
url
,
specifier
=
req
.
specifier
,
marker
=
req
.
marker
,
)
def
_format_marker
(
self
)
->
str
:
if
self
.
marker
:
return
f
";
{
str
(
self
.
marker
)
}
"
...
...
@@ -207,15 +214,8 @@ class FileRequirement(Requirement):
self
.
_parse_url
()
if
self
.
path
and
not
self
.
path
.
exists
():
raise
RequirementError
(
f
"The local path
{
self
.
path
}
does not exist."
)
if
self
.
is_local
and
not
self
.
name
:
self
.
_parse_name_from_project_files
()
@
property
def
normalized_url
(
self
)
->
Optional
[
str
]:
if
self
.
url
:
return
self
.
url
elif
self
.
path
:
return
f
"file:///
{
self
.
path
.
absolute
().
as_posix
()
}
"
if
not
self
.
name
and
self
.
is_local_dir
:
self
.
_parse_name_from_local
()
@
classmethod
def
from_line
(
cls
,
line
:
str
,
parsed
:
Dict
[
str
,
str
])
->
"FileRequirement"
:
...
...
@@ -228,9 +228,14 @@ class FileRequirement(Requirement):
return
r
def
_parse_url
(
self
)
->
None
:
parsed
=
urlparse
.
urlparse
(
self
.
url
)
if
parsed
.
scheme
==
"file"
and
not
parsed
.
netloc
:
self
.
path
=
Path
(
parsed
.
path
)
if
not
self
.
url
:
if
self
.
path
:
self
.
url
=
f
"file://
{
self
.
path
.
absolute
().
as_posix
()
}
"
else
:
parsed
=
urlparse
.
urlparse
(
self
.
url
)
if
parsed
.
scheme
==
"file"
and
not
parsed
.
netloc
:
self
.
path
=
Path
(
parsed
.
path
)
self
.
_parse_name_from_url
()
@
property
def
is_local
(
self
)
->
bool
:
...
...
@@ -252,10 +257,10 @@ class FileRequirement(Requirement):
editable
=
"-e "
if
self
.
editable
else
""
project_name
=
f
"
{
self
.
project_name
}
"
if
self
.
project_name
else
""
extras
=
f
"[
{
','
.
join
(
sorted
(
self
.
extras
))
}
]"
if
self
.
extras
else
""
if
self
.
path
and
not
for_ireq
and
self
.
editable
:
if
self
.
path
and
not
for_ireq
and
self
.
editable
and
not
project_name
:
location
=
self
.
str_path
else
:
location
=
self
.
normalized_
url
location
=
self
.
url
marker
=
self
.
_format_marker
()
if
for_ireq
and
project_name
:
return
f
"
{
editable
}{
location
}
#egg=
{
project_name
}{
extras
}{
marker
}
"
...
...
@@ -263,15 +268,24 @@ class FileRequirement(Requirement):
delimiter
=
" @ "
if
project_name
else
""
return
f
"
{
editable
}{
project_name
}{
extras
}{
delimiter
}{
location
}{
marker
}
"
def
as_candidate
(
self
)
->
Candidate
:
if
self
.
path
:
return
LocalCandidate
(
req
=
self
)
else
:
return
RemoteCandidate
(
req
=
self
)
def
_parse_name_from_url
(
self
)
->
None
:
parsed
=
urlparse
.
urlparse
(
self
.
url
)
fragments
=
dict
(
urlparse
.
parse_qsl
(
parsed
.
fragment
))
if
"egg"
in
fragments
:
egg_info
=
urlparse
.
unquote
(
fragments
[
"egg"
])
name
,
extras
=
_strip_extras
(
egg_info
)
self
.
name
=
name
self
.
extras
=
extras
if
not
self
.
name
:
filename
=
os
.
path
.
basename
(
url_without_fragments
(
self
.
url
))
if
filename
.
endswith
(
".whl"
):
self
.
name
,
self
.
version
=
parse_name_version_from_wheel
(
filename
)
def
_parse_name_from_
project_files
(
self
)
->
None
:
def
_parse_name_from_
local
(
self
)
->
None
:
result
=
SetupReader
.
read_from_directory
(
self
.
path
.
absolute
().
as_posix
())
self
.
name
=
result
[
"name"
]
if
not
self
.
name
:
raise
RequirementError
(
f
"The local path '
{
self
.
path
}
' is not installable."
)
class
NamedRequirement
(
Requirement
,
PackageRequirement
):
...
...
@@ -312,22 +326,14 @@ class VcsRequirement(FileRequirement):
editable
=
"-e "
if
self
.
editable
else
""
return
f
"
{
editable
}{
self
.
vcs
}
+
{
self
.
url
}{
self
.
_format_marker
()
}
"
def
as_candidate
(
self
)
->
Candidate
:
return
VcsCandidate
(
req
=
self
)
def
_parse_url
(
self
)
->
None
:
if
self
.
name
and
self
.
repo
:
return
if
self
.
url
.
startswith
(
"git@"
):
self
.
url
=
"ssh://"
+
self
.
url
[
4
:].
replace
(
":"
,
"/"
)
parsed
=
urlparse
.
urlparse
(
self
.
url
)
fragments
=
dict
(
urlparse
.
parse_qsl
(
parsed
.
fragment
))
if
"egg"
in
fragments
:
egg_info
=
urlparse
.
unquote
(
fragments
[
"egg"
])
name
,
extras
=
_strip_extras
(
egg_info
)
self
.
name
=
name
self
.
extras
=
extras
self
.
repo
=
urlparse
.
urlunparse
(
parsed
.
_replace
(
fragment
=
""
))
if
not
self
.
name
:
self
.
_parse_name_from_url
()
if
not
self
.
name
:
raise
RequirementError
(
"VCS requirement must provide a 'egg=' fragment."
)
self
.
repo
=
url_without_fragments
(
self
.
url
)
@
staticmethod
def
_build_url_from_req_dict
(
name
:
str
,
url
:
str
,
req_dict
:
RequirementDict
)
->
str
:
...
...
pdm/models/specifiers.py
浏览文件 @
4bbace23
...
...
@@ -287,9 +287,9 @@ class PySpecSet(SpecifierSet):
for
z
in
range
(
prev
[
2
],
current_max
+
1
):
yield
(
*
prev
[:
2
],
z
)
prev
=
(
cur
if
cur
[
1
]
<=
self
.
MAX_PY_VERSIONS
[(
cur
[
0
],)]
else
_bump_version
(
prev
,
0
)
_bump_version
(
prev
,
0
)
if
cur
[
0
]
<
3
and
cur
[
1
]
>
self
.
MAX_PY_VERSIONS
[(
cur
[
0
],)]
else
cur
)
else
:
# X.Y.Z -> X.Y.W
while
prev
<
upper
:
...
...
pdm/resolver/providers.py
浏览文件 @
4bbace23
from
resolvelib.providers
import
AbstractProvider
from
requirementslib
import
Requirement
from
pdm.models.repositories
import
BaseRepository
from
typing
import
List
class
RepositoryProvider
(
AbstractProvider
):
def
__init__
(
self
,
repositor
ies
:
List
[
BaseRepository
]
)
->
None
:
self
.
repositor
ies
=
repositories
def
__init__
(
self
,
repositor
y
:
BaseRepository
)
->
None
:
self
.
repositor
y
=
repository
def
identify
(
self
,
requirement
:
Requirement
)
->
str
:
return
requirement
.
key
pdm/utils.py
浏览文件 @
4bbace23
"""
Compatibility code
"""
import
os
import
atexit
import
shutil
import
tempfile
from
typing
import
List
,
Optional
,
Tuple
from
urllib.parse
import
urlparse
import
urllib.parse
as
parse
from
distlib.wheel
import
Wheel
import
pkg_resources
from
contextlib
import
contextmanager
from
pip_shims.shims
import
InstallCommand
,
PackageFinder
,
get_package_finder
from
pdm.types
import
Source
try
:
from
functools
import
cached_property
except
ImportError
:
class
cached_property
:
def
__init__
(
self
,
func
):
self
.
func
=
func
self
.
attr_name
=
func
.
__name__
self
.
__doc__
=
func
.
__doc__
def
__get__
(
self
,
inst
,
cls
=
None
):
if
inst
is
None
:
return
self
if
self
.
attr_name
not
in
inst
.
__dict__
:
inst
.
__dict__
[
self
.
attr_name
]
=
self
.
func
(
inst
)
return
inst
.
__dict__
[
self
.
attr_name
]
def
prepare_pip_source_args
(
sources
:
List
[
Source
],
pip_args
:
Optional
[
List
[
str
]]
=
None
...
...
@@ -20,7 +44,7 @@ def prepare_pip_source_args(
# Trust the host if it's not verified.
if
not
sources
[
0
].
get
(
"verify_ssl"
,
True
):
pip_args
.
extend
(
[
"--trusted-host"
,
urlparse
(
sources
[
0
][
"url"
]).
hostname
]
[
"--trusted-host"
,
parse
.
urlparse
(
sources
[
0
][
"url"
]).
hostname
]
)
# type: ignore
# Add additional sources as extra indexes.
if
len
(
sources
)
>
1
:
...
...
@@ -29,17 +53,53 @@ def prepare_pip_source_args(
# Trust the host if it's not verified.
if
not
source
.
get
(
"verify_ssl"
,
True
):
pip_args
.
extend
(
[
"--trusted-host"
,
urlparse
(
source
[
"url"
]).
hostname
]
[
"--trusted-host"
,
parse
.
urlparse
(
source
[
"url"
]).
hostname
]
)
# type: ignore
return
pip_args
def
get_finder
(
sources
:
List
[
Source
],
python_versions
:
Optional
[
Tuple
[
str
,
...]]
=
None
)
->
PackageFinder
:
def
get_finder
(
sources
:
List
[
Source
],
cache_dir
:
Optional
[
str
]
=
None
)
->
PackageFinder
:
install_cmd
=
InstallCommand
()
pip_args
=
prepare_pip_source_args
(
sources
)
options
,
_
=
install_cmd
.
parser
.
parse_args
(
pip_args
)
return
get_package_finder
(
install_cmd
=
install_cmd
,
options
=
options
,
python_versions
=
python_versions
)
if
cache_dir
:
options
.
cache_dir
=
cache_dir
return
get_package_finder
(
install_cmd
=
install_cmd
,
options
=
options
)
def
create_tracked_tempdir
(
suffix
:
Optional
[
str
]
=
None
,
prefix
:
Optional
[
str
]
=
""
,
dir
:
Optional
[
str
]
=
None
)
->
str
:
name
=
tempfile
.
mkdtemp
(
suffix
,
prefix
,
dir
)
os
.
makedirs
(
name
,
mode
=
0o777
,
exist_ok
=
True
)
def
clean_up
():
shutil
.
rmtree
(
name
,
ignore_errors
=
True
)
atexit
.
register
(
clean_up
)
return
name
def
parse_name_version_from_wheel
(
filename
:
str
)
->
Tuple
[
str
,
str
]:
w
=
Wheel
(
filename
)
return
w
.
name
,
w
.
version
def
url_without_fragments
(
url
:
str
)
->
str
:
return
parse
.
urlunparse
(
parse
.
urlparse
(
url
).
_replace
(
fragment
=
""
))
@
contextmanager
def
allow_all_markers
():
"""This is a monkey patch function that temporarily disables marker evaluation."""
from
pip._vendor
import
pkg_resources
as
vendor_pkg
def
evaluate_marker
(
text
,
extra
=
None
):
return
True
old_evaluate
=
pkg_resources
.
evaluate_marker
pkg_resources
.
evaluate_marker
=
evaluate_marker
vendor_pkg
.
evaluate_marker
=
evaluate_marker
yield
pkg_resources
.
evaluate_marker
=
old_evaluate
vendor_pkg
=
old_evaluate
tests/fixtures/artifacts/demo-0.0.1-py2.py3-none-any.whl
LFS
浏览文件 @
4bbace23
无法预览此类型文件
tests/fixtures/artifacts/demo-0.0.1.tar.gz
LFS
浏览文件 @
4bbace23
无法预览此类型文件
tests/fixtures/projects/demo/setup.py
浏览文件 @
4bbace23
...
...
@@ -6,6 +6,10 @@ setup(
version
=
"0.0.1"
,
description
=
"test demo"
,
py_modules
=
[
"demo"
],
install_requires
=
[
"idna"
,
"chardet"
],
extras_require
=
{
'tests'
:
[
'pytest'
],
'security'
:
[
'requests'
]},
python_requires
=
">=3.3"
,
install_requires
=
[
"idna"
,
"chardet; os_name=='nt'"
],
extras_require
=
{
"tests"
:
[
"pytest"
],
"security"
:
[
'requests; python_version>="3.6"'
],
},
)
tests/models/test_requirements.py
浏览文件 @
4bbace23
import
pytest
from
pdm.models.requirements
import
Requirement
from
pdm.models.requirements
import
Requirement
,
RequirementError
from
tests
import
FIXTURES
REQUIREMENTS
=
[
...
...
@@ -33,6 +33,11 @@ REQUIREMENTS = [
(
"MyProject"
,
{
"editable"
:
True
,
"git"
:
"http://git.example.com/MyProject"
}),
None
,
),
(
"git+http://git.example.com/MyProject#egg=MyProject"
,
(
"MyProject"
,
{
"git"
:
"http://git.example.com/MyProject"
}),
None
,
),
(
"https://github.com/pypa/pip/archive/1.3.1.zip"
,
(
None
,
{
"url"
:
"https://github.com/pypa/pip/archive/1.3.1.zip"
}),
...
...
@@ -41,19 +46,18 @@ REQUIREMENTS = [
(
(
FIXTURES
/
"projects/demo"
).
as_posix
(),
(
"demo"
,
{
"path"
:
(
FIXTURES
/
"projects/demo"
).
as_posix
()}),
"demo @ file://
/
"
+
(
FIXTURES
/
"projects/demo"
).
as_posix
(),
"demo @ file://"
+
(
FIXTURES
/
"projects/demo"
).
as_posix
(),
),
(
(
FIXTURES
/
"artifacts/demo-0.0.1-py2.py3-none-any.whl"
).
as_posix
(),
(
None
,
"demo"
,
{
"path"
:
(
FIXTURES
/
"artifacts/demo-0.0.1-py2.py3-none-any.whl"
).
as_posix
()
"url"
:
"file://"
+
(
FIXTURES
/
"artifacts/demo-0.0.1-py2.py3-none-any.whl"
).
as_posix
()
},
),
"
file:/
//"
"
demo @ file:
//"
+
(
FIXTURES
/
"artifacts/demo-0.0.1-py2.py3-none-any.whl"
).
as_posix
(),
),
]
...
...
@@ -67,3 +71,20 @@ def test_convert_req_dict_to_req_line(req, req_dict, result):
r
=
Requirement
.
from_req_dict
(
*
req_dict
)
result
=
result
or
req
assert
r
.
as_line
()
==
result
@
pytest
.
mark
.
parametrize
(
"line,expected"
,
[
(
"-e https://github.com/pypa/pip/archive/1.3.1.zip"
,
"Editable requirement is only supported"
,
),
(
"requests; os_name=>'nt'"
,
"Invalid requirement"
),
(
"./nonexist"
,
r
"The local path (.+)? does not exist"
),
(
"./tests"
,
r
"The local path (.+)? is not installable"
),
],
)
def
test_illegal_requirement_line
(
line
,
expected
):
with
pytest
.
raises
(
RequirementError
,
match
=
expected
):
Requirement
.
from_line
(
line
)
tests/models/test_specifiers.py
浏览文件 @
4bbace23
...
...
@@ -4,18 +4,18 @@ from pdm.models.specifiers import PySpecSet
@
pytest
.
mark
.
parametrize
(
'original,normalized'
,
"original,normalized"
,
[
(
'>=3.6'
,
'>=3.6'
),
(
'<3.8'
,
'<3.8'
),
(
'~=2.7.0'
,
'>=2.7,<2.8'
),
(
''
,
''
),
(
'>=3.6,<3.8'
,
'>=3.6,<3.8'
),
(
'>3.6'
,
'>=3.6.1'
),
(
'<=3.7'
,
'<3.7.1'
),
(
'>=3.6,!=3.4.*'
,
'>=3.6'
),
(
'>=3.6,!=3.6.*'
,
'>=3.7'
),
(
'>=3.6,<3.8,!=3.8.*'
,
'>=3.6,<3.8'
),
(
">=3.6"
,
">=3.6"
),
(
"<3.8"
,
"<3.8"
),
(
"~=2.7.0"
,
">=2.7,<2.8"
),
(
""
,
""
),
(
">=3.6,<3.8"
,
">=3.6,<3.8"
),
(
">3.6"
,
">=3.6.1"
),
(
"<=3.7"
,
"<3.7.1"
),
(
">=3.6,!=3.4.*"
,
">=3.6"
),
(
">=3.6,!=3.6.*"
,
">=3.7"
),
(
">=3.6,<3.8,!=3.8.*"
,
">=3.6,<3.8"
),
],
)
def
test_normalize_pyspec
(
original
,
normalized
):
...
...
@@ -24,13 +24,13 @@ def test_normalize_pyspec(original, normalized):
@
pytest
.
mark
.
parametrize
(
'left,right,result'
,
"left,right,result"
,
[
(
'>=3.6'
,
'>=3.0'
,
'>=3.6'
),
(
'>=3.6'
,
'<3.8'
,
'>=3.6,<3.8'
),
(
''
,
'>=3.6'
,
'>=3.6'
),
(
'>=3.6'
,
'<3.2'
,
'impossible'
),
(
'>=2.7,!=3.0.*'
,
'!=3.1.*'
,
'>=2.7,!=3.0.*,!=3.1.*'
),
(
">=3.6"
,
">=3.0"
,
">=3.6"
),
(
">=3.6"
,
"<3.8"
,
">=3.6,<3.8"
),
(
""
,
">=3.6"
,
">=3.6"
),
(
">=3.6"
,
"<3.2"
,
"impossible"
),
(
">=2.7,!=3.0.*"
,
"!=3.1.*"
,
">=2.7,!=3.0.*,!=3.1.*"
),
],
)
def
test_pyspec_and_op
(
left
,
right
,
result
):
...
...
@@ -40,13 +40,14 @@ def test_pyspec_and_op(left, right, result):
@
pytest
.
mark
.
parametrize
(
'left,right,result'
,
"left,right,result"
,
[
(
'>=3.6'
,
'>=3.0'
,
'>=3.0'
),
(
''
,
'>=3.6'
,
''
),
(
'>=3.6'
,
'<3.7'
,
''
),
(
'>=3.6,<3.8'
,
'>=3.4,<3.7'
,
'>=3.4,<3.8'
),
(
'~=2.7'
,
'>=3.6'
,
'>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*'
),
(
">=3.6"
,
">=3.0"
,
">=3.0"
),
(
""
,
">=3.6"
,
""
),
(
">=3.6"
,
"<3.7"
,
""
),
(
">=3.6,<3.8"
,
">=3.4,<3.7"
,
">=3.4,<3.8"
),
(
"~=2.7"
,
">=3.6"
,
">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*"
),
(
"<3.6.5"
,
">=3.7"
,
"!=3.6.5,!=3.6.6,!=3.6.7,!=3.6.8,!=3.6.9,!=3.6.10"
),
],
)
def
test_pyspec_or_op
(
left
,
right
,
result
):
...
...
@@ -56,8 +57,8 @@ def test_pyspec_or_op(left, right, result):
def
test_impossible_pyspec
():
spec
=
PySpecSet
(
'>=3.6,<3.4'
)
a
=
PySpecSet
(
'>=2.7'
)
spec
=
PySpecSet
(
">=3.6,<3.4"
)
a
=
PySpecSet
(
">=2.7"
)
assert
spec
.
is_impossible
assert
(
spec
&
a
).
is_impossible
assert
spec
|
a
==
a
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录