ํ๊ฒฝ
๊ธฐ์
์ด๊ฒ์ด six
๋ฒ๊ทธ์ธ์ง Pip ๋ฒ๊ทธ์ธ์ง ์ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค. six
์ํ๋ ๊ฒฝ์ฐ ์ค๋กํฉ๋๋ค.
ํ์ ๋ก์ปฌ ๊ฒฝ๋ก๋ฅผ ํ์ฉํ๋ ๊ฒ install_requires
๋ฅผ ํตํด name @ ./some/path
ํ์ง๋ง, URL ํ์ฑ์ ์ ๋ง ๋๋ฉ๋๋ค.
์ด ํจ์์์๋ urlsplit
๋ฅผ ์ฌ์ฉํ์ฌ ๋ค์ด์ค๋ URL์ ๊ฐ๋ณ ๊ตฌ์ฑ ์์๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
๋ค์์ ๋ช ๊ฐ์ง ์ ๋ ฅ์ผ๋ก ํ์๋๋ ๊ฒ์ ๋๋ค.
./foo/bar -> SplitResult(scheme='', netloc='', path='./foo/bar', query='', fragment='')
file:./foo/bar -> SplitResult(scheme='file', netloc='', path='./foo/bar', query='', fragment='')
file://./foo/bar -> SplitResult(scheme='file', netloc='.', path='/foo/bar', query='', fragment='')
๋ง์ง๋ง ๊ฒฐ๊ณผ๋ netloc
๊ฐ ๋น์ด์๋ ๋์ .
์ด๊ณ path
๋ ๋ก์ปฌ์ด ์๋ ์ ๋ ๊ฐ์
๋๋ค. ์ด๊ฒ์ ๋ก์ปฌ์ด ์๋ ๊ฒฝ๋ก์ ๊ด๋ จ๋ ์ค๋ฅ๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค. ๊ด์ฐฎ์ต๋๋ค. ๋ ๋ฒ์งธ ํ์์ ์ฌ์ฉํ์ฌ ์กฐ๊ฑด๋ถ ๋
ผ๋ฆฌ๋ฅผ ์ถฉ์กฑ ํ ์ ์์ต๋๋ค (์ฒซ ๋ฒ์งธ ํ์๋ ์ง์ํด์ผํ์ง๋ง).
๊ทธ๋ฌ๋ ๋ค๋ฅธ ๊ณณ์ ์์ถฉ๋๋ ๋ ผ๋ฆฌ๊ฐ ์์ต๋๋ค.
์ด๊ฒ์ ์ฐ๋ฆฌ๊ฐ ์ด์ ๋ ผ๋ฆฌ๋ฅผ ๋ง์กฑํ๋๋ผ๋ ์คํจํ๋ ๋ ผ๋ฆฌ์ ๋๋ค.
๋ค์์ ๋ฌธ์ ๋ฅผ ๋ณด์ฌ์ฃผ๋ ํ ์คํธ ๊ธฐ๋ฅ์ ๋๋ค.
from six.moves.urllib import parse as urllib_parse
def tryparse(url):
print(url)
parsed = urllib_parse.urlparse(url)
unparsed = urllib_parse.urlunparse(parsed)
parsed_again = urllib_parse.urlparse(unparsed)
print(parsed)
print(unparsed)
print(parsed_again)
./foo/bar
๋ํ ์ถ๋ ฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
>>> tryparse('./foo/bar')
./foo/bar
ParseResult(scheme='', netloc='', path='./foo/bar', params='', query='', fragment='')
./foo/bar
ParseResult(scheme='', netloc='', path='./foo/bar', params='', query='', fragment='')
file:
์คํค๋ง๋ฅผ ์๊ตฌํ๋ ์ฒซ ๋ฒ์งธ ํจ์์ ๋
ผ๋ฆฌ๋ฅผ ๋ง์กฑ ์ํค์ง๋ ์์ง๋ง ๋ชจ๋ ์ข์ต๋๋ค.
file:./foo/bar
๋ํ ์ถ๋ ฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
>>> tryparse('file:./foo/bar')
file:./foo/bar
ParseResult(scheme='file', netloc='', path='./foo/bar', params='', query='', fragment='')
file:///./foo/bar
ParseResult(scheme='file', netloc='', path='/./foo/bar', params='', query='', fragment='')
์ด๋ฐ! ์ฒซ ๋ฒ์งธ ๊ตฌ๋ฌธ ๋ถ์ ํธ์ถ์ ๊ฒฐ๊ณผ๋ฅผ "unparse"ํ ๋ path
๊ฐ ์ ๋ file:///...
๋ฉ๋๋ค.
์ด๊ฒ์ด ๋ ๋ฒ์งธ๋ก ์ธ๊ธ ๋ ๊ฒ์ฌ๊ฐ ์คํจํ๋ ์ด์ ์
๋๋ค. ๊ฒฝ๋ก๊ฐ ๋ก์ปฌ์ด ์๋๋๋ค. ๋๋ ์ด๊ฒ์ด six
์ ๋ฒ๊ทธ๋ผ๊ณ ์๊ฐ ํ์ง๋ง scheme in ['file', '']
ํ๊ณ ์ฌ์ฉ์์๊ฒ ./foo/bar
URI ์์์ ์ฌ์ฉํ๋๋ก ์ง์ํจ์ผ๋ก์จ Pip์์ ์ํ ํ ์ ์์ต๋๋ค.
์ด ๋ ๊ฐ์ง ๋ชจ์ ๋ ๋
ผ๋ฆฌ๋ฅผ ๊ฐ์ํ ๋ distutils
๋๋ setuptools
๊ตฌ์ฑ์์ install_requires
ํค์ ๋ก์ปฌ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ๋ถ๊ฐ๋ฅํฉ๋๋ค.
์์๋๋ ํ๋
name @ ./some/path
(๋๋ ์์งํ ./some/path
)๋ฅผ ์ํํ์ฌ ๋ด ์ฝ๋๋ฒ ์ด์ค์ ๋ก์ปฌ๋ก ๊ณต๊ธ๋๋ ํจํค์ง๋ฅผ ์ง์ ํ ์ ์์ด์ผํฉ๋๋ค.
์ฌํ ๋ฐฉ๋ฒ
#!/usr/bin/env bash
mkdir /tmp/pip-uri-repro && cd /tmp/pip-uri-repro
mkdir -p foo/bar
cat > requirements.txt <<EOF
./foo
EOF
cat > foo/setup.py <<EOF
#!/usr/bin/env python
from setuptools import setup
setup(
name="foo",
version="0.1",
install_requires=[
"bar @ file:./bar"
]
)
EOF
cat > foo/bar/setup.py <<EOF
#!/usr/bin/env python
from setuptools import setup
setup(
name="bar",
version="0.1"
)
EOF
# (OUTPUT 1)
pip install -r requirements.txt
cat > foo/setup.py <<EOF
#!/usr/bin/env python
from setuptools import setup
setup(
name="foo",
version="0.1",
install_requires=[
# we're forced to use an absolute path
# to make the "Invalid URL" error go
# away, which isn't right anyway (the
# error that is raised as a result
# is justified)
"bar @ file://./bar"
]
)
EOF
# (OUTPUT 2)
pip install -r requirements.txt
์ฐ์ถ
์ฒ์ pip install
:
Processing ./foo
ERROR: Complete output from command python setup.py egg_info:
ERROR: error in foo setup command: 'install_requires' must be a string or list of strings containing valid project/version requirement specifiers; Invalid URL given
๋ ๋ฒ์งธ pip install
:
Processing ./foo
ERROR: Exception:
Traceback (most recent call last):
File "/private/tmp/repro-pip-egg/env3/lib/python3.7/site-packages/pip/_internal/cli/base_command.py", line 178, in main
status = self.run(options, args)
File "/private/tmp/repro-pip-egg/env3/lib/python3.7/site-packages/pip/_internal/commands/install.py", line 352, in run
resolver.resolve(requirement_set)
File "/private/tmp/repro-pip-egg/env3/lib/python3.7/site-packages/pip/_internal/resolve.py", line 131, in resolve
self._resolve_one(requirement_set, req)
File "/private/tmp/repro-pip-egg/env3/lib/python3.7/site-packages/pip/_internal/resolve.py", line 294, in _resolve_one
abstract_dist = self._get_abstract_dist_for(req_to_install)
File "/private/tmp/repro-pip-egg/env3/lib/python3.7/site-packages/pip/_internal/resolve.py", line 242, in _get_abstract_dist_for
self.require_hashes
File "/private/tmp/repro-pip-egg/env3/lib/python3.7/site-packages/pip/_internal/operations/prepare.py", line 256, in prepare_linked_requirement
path = url_to_path(req.link.url)
File "/private/tmp/repro-pip-egg/env3/lib/python3.7/site-packages/pip/_internal/download.py", line 521, in url_to_path
% url
ValueError: non-local file URIs are not supported on this platform: 'file://./bar'
ํธ์งํ๋ค:
RFC 3986 ์ด file:
์คํค๋ง์์ ์๋ ๊ฒฝ๋ก URI๊ฐ ํ์ฉ๋์ง ์๋๋ค๊ณ ์ง์ ํ๋ฏ๋ก ๊ธฐ์ ์ ์ผ๋ก six
๋ file:./foo/bar
ํฉ๋๋ค.
๊ทธ๋ฌ๋ ๊ธฐ์ ์ ์ผ๋ก๋ setup.py์์ ๋ค์์ ์ํ ํ ์ ์์ด์ผํฉ๋๋ค.
PKG_DIR = os.path.dirname(os.path.abspath(__file__))
install_requires = [
f"name @ file://{PKG_DIR}/foo/bar"
]
๊ทธ๋ฌ๋ pip๋ /tmp
์์ ํจํค์ง์ "๊นจ๋ํ"๋ณต์ฌ๋ณธ์ ๋ง๋๋ ๊ฒ์ฒ๋ผ ๋ณด์ด๋ฏ๋ก file:///tmp/pip-req-build-9u3z545j/foo/bar
์ ๊ฐ์ ๊ฒ์ ์ป์ต๋๋ค.
ํ ์คํธ ํจ์๋ฅผ ํตํด ์คํํ๋ฉด ๋ ๋ฒ์งธ ํจ์์ ์กฐ๊ฑด์ ์ถฉ์กฑํฉ๋๋ค.
>>> tryparse('file:///tmp/pip-req-build-9u3z545j/foo/bar')
file:///tmp/pip-req-build-9u3z545j/foo/bar
ParseResult(scheme='file', netloc='', path='/tmp/pip-req-build-9u3z545j/foo/bar', params='', query='', fragment='')
file:///tmp/pip-req-build-9u3z545j/foo/bar
ParseResult(scheme='file', netloc='', path='/tmp/pip-req-build-9u3z545j/foo/bar', params='', query='', fragment='')
๊ฑฐ๊ธฐ์ ๋ชจ๋ ๊ฒ์ด ์ข์ต๋๋ค. "unparse"๋ ๋์ผํ ๊ฒฐ๊ณผ๋ฅผ ์ฐ์ถํ๊ณ netloc
์๊ตฌ ์ฌํญ์ ์ฒซ ๋ฒ์งธ ํจ์์ ์กฐ๊ฑด์ ๋ํด ์ถฉ์กฑ๋ฉ๋๋ค.
๊ทธ๋ฌ๋ ๋ ๋ฒ์งธ ํจ์์ ๋
ผ๋ฆฌ๊ฐ ๋ง์กฑ ๋๋๋ผ๋ ์ฌ์ ํ Invalid URL
์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.
pip
(๋๋ distutils ๋๋ setuptools ๋ฑ)์ด ์ถ๋ ฅ์ ์ผํค๊ธฐ ๋๋ฌธ์ ์ ๋ setup.py์์ ๋ค์์ ์ํํ์ต๋๋ค.
import os
PKG_DIR = os.path.dirname(os.path.abspath(__file__))
assert False, os.system(f"find {PKG_DIR}")
์์๋๋ก ๋ชจ๋ ํ์ผ์ด ์๋์ง ํ์ธํ๋ฏ๋ก ํ์ผ์ด ๋๋ฝ๋๊ฑฐ๋ ๋ฌด์ธ๊ฐ๊ฐ ๋ ์ ์์ต๋๋ค. "Invalid URL given"
๊ฐ์๋ ์์ ์ค์ ์ฝ๋๋ฒ ์ด์ค์์ ๋ฌธ์์ด์ด ํ์๋๋ ์ ์ผํ ์์น์
๋๋ค.
์ด ์์ ์์ ๋๋ ๋ฌธ์ ๊ฐ ๋ฌด์์ธ์ง ์ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค.
์ข์, ๋ฌธ์ ๊ฐ ๋ณด์ธ๋ค. setuptools
, pkg-resources
๋ฐ pip
๋ชจ๋ ์ฝ๊ฐ ๋ค๋ฅธ ๋ฒ์ ์ packaging
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํฉ๋๋ค.
pip
์์๋ ์์์ ๋ณด์ฌ ๋๋ฆฐ ๋ฒ์ ์
๋๋ค.
๊ทธ๋ฌ๋ ๋ค๋ฅธ ๋ชจ๋ ๊ฒ์์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค ( "์๋ก์ด"ํญ๋ชฉ์ธ์ง ํ์คํ์ง ์์ง๋ง ๋ค์ ๋
ผ๋ฆฌ๋ ๋งค์ฐ ์ ํ์ ์ด๋ฉฐ RFC 3986์ ๋ฐ๋ผ ์์ ํ ํธํ๋์ง ์์ต๋๋ค. file:///
๊ฐ ํ์ฉ๋์ด์ผํ๋ฏ๋ก ๋น์ด์๋ netloc
) :
if req.url:
parsed_url = urlparse.urlparse(req.url)
if not (parsed_url.scheme and parsed_url.netloc) or (
not parsed_url.scheme and not parsed_url.netloc):
raise InvalidRequirement("Invalid URL given")
๐
์ฆ, ๋ด ํ์ผ ๊ฒฝ๋ก์ file:///foo/bar
์๊ณ file://localhost/foo/bar
์๋๋ฏ๋ก ์คํจํฉ๋๋ค.
์์ ํ ์๋ฃจ์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
import os
from setuptools import setup
PKG_DIR = os.path.dirname(os.path.abspath(__file__))
setup(
install_requires=[
f'foo @ file://localhost{PKG_DIR}/foo/bar'
]
)
์ด๊ฒ์ ๋ชจํธํ๊ณ ์๊ฐ์ ๋ญ๋นํ๋ ์ค๋ฅ์ ํผํฉ ๋ ๋งค์ฐ ๋์ UX์ ๋๋ค.
์ด ์ํฉ์ ์ด๋ป๊ฒ ๊ฐ์ ํ ์ ์์ต๋๊น?
@ Qix- ๋น์ ์ด ์ด๊ฒ์ ๋ฐ๊ฒฌํด์ ๋คํ์ ๋๋ค! ๋๋ ๋ชจ๋ ๋์ผํ ํ์์ ์๋ํ๋ฉด์ ๋ฒฝ์ ๋จธ๋ฆฌ๋ฅผ ์น๊ณ ์์๋ค. ์ด๊ฒ์ https://github.com/pypa/pip/issues/6162์ ๋์ฒด ์ต์ ์ด๋ฉฐ dependency_links์ ์ฌ์ฉ ์ค๋จ์ ๋๋ค.
์ฐ๋ฆฌ๋ ๊ฐ์ธ ์ ์ฅ์๋ฅผ ์ค์ ํ๋ ค๊ณ ํ๋๋ฐ ์์ฒด ๋ด๋ถ ์๋ฒ๊ฐ ์์ต๋๋ค. ์ฐ๋ฆฌ์ ํด๊ฒฐ์ฑ
์ ํจํค์ง๋ฅผ s3์ ๊ฒ์ ํ ๋ค์ ์ฌ์ฉํ๊ธฐ ์ํด ๋ค์ด๋ก๋ํ๊ณ ๋ก์ปฌ ํด๋์ ๋ฃ์ ๋ค์ install_requires
์ถ๊ฐํ๋ ๊ฒ์
๋๋ค.
๋ก์ปฌ ํจํค์ง๋ฅผ ์ค์นํ๋ ์ง๊ด์ ์ธ ๋ฐฉ๋ฒ์ ์ด์ ์ ์ป์ ์์๋ ๋ค๋ฅธ ๋ง์ ์ฌ์ฉ ์ฌ๋ก๊ฐ ์๋ค๊ณ ํ์ ํฉ๋๋ค.
@ryanaklein ์ค์ ๋ก git ํ์ ๋ชจ๋์ ๋ํ ์ฐ๊ตฌ๋์ง ์์ ๋ชจ๋ ๋ถ์ ์ฑ์ ๋ฌด์ํ๊ณ ์๋ํด ๋ณผ ๊ฒ์ ์ ์ํฉ๋๋ค (Git๋ฅผ ์ฌ์ฉํ๋ค๊ณ ๊ฐ์ ). ๊ทธ๊ฒ๋ค์ ๋ธ๋์น๋ก ์๊ฐํ์ง ์๊ณ ํ๊ทธ (๋๋ ๋ฆด๋ฆฌ์ค)๋ก ์๊ฐํ๊ธฐ ์์ํ๋ฉด ์ ๋ง ์ ์๋ํ๊ธฐ ์์ํฉ๋๋ค. ๊ทธ๊ฒ๋ค์ C / C ++ ์ธ๊ณ์์ ๋งค์ฐ ์์ฃผ ์ฌ์ฉ๋๋ฉฐ, ์ฐ๋ฆฌ๋ ๊ทธ๊ฒ๋ค์ ๊ฝค ์ฑ๊ณต์ ์ผ๋ก ์ฌ์ฉํ์ฌ ํ์ด์ฌ ํจํค์ง๋ฅผ ๊ณต๊ธํ์ต๋๋ค (๋ฌผ๋ก ์์ ๋ฒ๊ทธ๋ฅผ ์ ์ธํ๊ณ !).
S3์ ๋คํธ์ํฌ / $$ ๋น์ฉ์ ์ค์ผ ์ ์์ต๋๋ค. :)
์์๋๋ ํ๋
name @ ./some/path
(๋๋ ์์งํ./some/path
)๋ฅผ ์ํํ์ฌ ์ฝ๋๋ฒ ์ด์ค์ ๋ก์ปฌ๋ก ๊ณต๊ธ๋๋ ํจํค์ง๋ฅผ ์ง์ ํ ์ ์์ด์ผํฉ๋๋ค.
์ง์ URL ์ฐธ์กฐ ( name @ ./some/path
)์ ๊ฒฝ์ฐ ์์
์ด ์งํ๋๋ ๋ ๊ณณ์ด ์์ต๋๋ค.
ํ์๋ PEP 508์ ๋ฐ๋ผ ํ์ฉ๋์ง ์์ผ๋ฏ๋ก ๋ชจ๋ ๋๊ตฌ์์ ์๋ํ๋๋ก ์ง์ํ๋ ๊ฒ์ ์ ๋นํํ๊ธฐ๊ฐ ์ด๋ ต์ต๋๋ค.
์ด๊ฒ์ ๋ชจํธํ๊ณ ์๊ฐ์ ๋ญ๋นํ๋ ์ค๋ฅ์ ํผํฉ ๋ ๋งค์ฐ ๋์ UX์ ๋๋ค.
์ด ์ํฉ์ ์ด๋ป๊ฒ ๊ฐ์ ํ ์ ์์ต๋๊น?
์ด ๋ฌธ์ ์ ์ํ๋ ๋ฌด์์ ๋๊น? ์๋ฅผ ๋ค์ด ๋ชจ ๋๋ฆฌ ์ ๋ฆฌํฌ์งํ ๋ฆฌ์ ๋งฅ๋ฝ์์ PyPI์์๋ ๋ก์ปฌ ์ข ์์ฑ์ ํด๊ฒฐํ๊ธฐ์ํ ์๋ฃจ์ ์ด ๊ธด๊ธํ ํ์ํฉ๋๋ค.
npm์์ด ๊ธฐ๋ฅ์ ์ ์ฌํ ๋ฐฉ์์ผ๋ก ๊ตฌํํ์ผ๋ฉฐ dependencies
๋ ๋ก์ปฌ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํ์ฌ package.json
์ง์ ํ ์ ์์ต๋๋ค.
์ด๊ฒ์ด ์์๋๊ธฐ ์ ์ ์ด๋ค ์ผ์ด ์ผ์ด๋์ผํ๋์ง ์์ ๋ณด๋ ค๋ฉด
๊ฐ์ฅ ์ ์ฉํ ๋๊ธ
์ข์, ๋ฌธ์ ๊ฐ ๋ณด์ธ๋ค.
setuptools
,pkg-resources
๋ฐpip
๋ชจ๋ ์ฝ๊ฐ ๋ค๋ฅธ ๋ฒ์ ์packaging
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํฉ๋๋ค.pip
์์๋ ์์์ ๋ณด์ฌ ๋๋ฆฐ ๋ฒ์ ์ ๋๋ค.๊ทธ๋ฌ๋ ๋ค๋ฅธ ๋ชจ๋ ๊ฒ์์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค ( "์๋ก์ด"ํญ๋ชฉ์ธ์ง ํ์คํ์ง ์์ง๋ง ๋ค์ ๋ ผ๋ฆฌ๋ ๋งค์ฐ ์ ํ์ ์ด๋ฉฐ RFC 3986์ ๋ฐ๋ผ ์์ ํ ํธํ๋์ง ์์ต๋๋ค.
file:///
๊ฐ ํ์ฉ๋์ด์ผํ๋ฏ๋ก ๋น์ด์๋netloc
) :๐
์ฆ, ๋ด ํ์ผ ๊ฒฝ๋ก์
file:///foo/bar
์๊ณfile://localhost/foo/bar
์๋๋ฏ๋ก ์คํจํฉ๋๋ค.์์ ํ ์๋ฃจ์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
์ด๊ฒ์ ๋ชจํธํ๊ณ ์๊ฐ์ ๋ญ๋นํ๋ ์ค๋ฅ์ ํผํฉ ๋ ๋งค์ฐ ๋์ UX์ ๋๋ค.
์ด ์ํฉ์ ์ด๋ป๊ฒ ๊ฐ์ ํ ์ ์์ต๋๊น?