Commit dbe13102 by Sébastien Eustace

Merge branch 'master' into develop

# Conflicts:
#	CHANGELOG.md
#	poetry.lock
#	poetry/__version__.py
#	pyproject.toml
#	tests/packages/test_locker.py
parents 3c26c8d0 3dd09c2c
......@@ -24,6 +24,19 @@
- Fixed transitive extra dependencies being removed when updating a specific dependency.
## [0.12.11] - 2019-01-13
### Fixed
- Fixed the way packages information are retrieved for legacy repositories.
- Fixed an error when adding packages with invalid versions.
- Fixed an error when resolving directory dependencies with no sub dependencies.
- Fixed an error when locking packages with no description.
- Fixed path resolution for transitive file dependencies.
- Fixed multiple constraints handling for the root package.
- Fixed exclude functionality on case sensitive systems.
## [0.12.10] - 2018-11-22
### Fixed
......@@ -630,7 +643,8 @@ Initial release
[Unreleased]: https://github.com/sdispater/poetry/compare/0.12.10...master
[Unreleased]: https://github.com/sdispater/poetry/compare/0.12.11...develop
[0.12.11]: https://github.com/sdispater/poetry/releases/tag/0.12.11
[0.12.10]: https://github.com/sdispater/poetry/releases/tag/0.12.10
[0.12.9]: https://github.com/sdispater/poetry/releases/tag/0.12.9
[0.12.8]: https://github.com/sdispater/poetry/releases/tag/0.12.8
......
......@@ -95,7 +95,7 @@ Now, you will need to install the required dependency for Poetry and be sure tha
tests are passing on your machine:
```bash
$ poetry develop
$ poetry install
$ poetry run pytest tests/
```
......
# Poetry: Dependency Management for Python
![Poetry build status](https://travis-ci.org/sdispater/poetry.svg)
Poetry helps you declare, manage and install dependencies of Python projects,
ensuring you have the right stack everywhere.
......@@ -9,6 +7,9 @@ ensuring you have the right stack everywhere.
It supports Python 2.7 and 3.4+.
[![Unix Build Status](https://img.shields.io/travis/sdispater/poetry.svg?label=Unix)](https://travis-ci.org/sdispater/poetry)
[![Windows Build Status](https://img.shields.io/appveyor/ci/sdispater/poetry.svg?label=Windows)](https://ci.appveyor.com/project/sdispater/poetry)
## Installation
Poetry provides a custom installer that will install `poetry` isolated
......
......@@ -61,7 +61,7 @@ Also, instead of modifying the `pyproject.toml` file by hand, you can use the `a
$ poetry add pendulum
```
It will automatically find a suitable version constraint.
It will automatically find a suitable version constraint **and install** the package and subdependencies.
### Version constraints
......
......@@ -148,6 +148,11 @@ poetry completions fish > ~/.config/fish/completions/poetry.fish
# Zsh
poetry completions zsh > ~/.zfunc/_poetry
# Oh-My-Zsh
mkdir $ZSH/plugins/poetry
poetry completions zsh > $ZSH/plugins/poetry/_poetry
```
!!! note
......@@ -159,3 +164,12 @@ For `zsh`, you must then add the following line in your `~/.zshrc` before `compi
```bash
fpath+=~/.zfunc
```
For `oh-my-zsh`, you must then enable poetry in your `~/.zshrc` plugins
```
plugins(
poetry
...
)
```
......@@ -15,7 +15,7 @@ class SearchCommand(Command):
flags = PyPiRepository.SEARCH_FULLTEXT
if self.option("only-name"):
flags = PyPiRepository.SEARCH_FULLTEXT
flags = PyPiRepository.SEARCH_NAME
results = PyPiRepository().search(self.argument("tokens"), flags)
......
......@@ -110,7 +110,7 @@ class PipInstaller(BaseInstaller):
raise
def run(self, *args, **kwargs): # type: (...) -> str
return self._env.run("pip", *args, **kwargs)
return self._env.run("python", "-m", "pip", *args, **kwargs)
def requirement(self, package, formatted=False):
if formatted and not package.source_type:
......
# -*- coding: utf-8 -*-
import os
import re
import shutil
import tempfile
......@@ -12,6 +13,7 @@ from clikit.api.io.flags import VERY_VERBOSE
from poetry.utils._compat import Path
from poetry.utils._compat import basestring
from poetry.utils._compat import glob
from poetry.utils._compat import lru_cache
from poetry.vcs import get_vcs
......@@ -55,8 +57,12 @@ class Builder(object):
explicitely_excluded = set()
for excluded_glob in self._package.exclude:
for excluded in self._path.glob(str(excluded_glob)):
explicitely_excluded.add(excluded.relative_to(self._path).as_posix())
for excluded in glob(
os.path.join(self._path.as_posix(), str(excluded_glob)), recursive=True
):
explicitely_excluded.add(
Path(excluded).relative_to(self._path).as_posix()
)
ignored = vcs_ignored_files | explicitely_excluded
result = set()
......
......@@ -374,8 +374,7 @@ class VersionSolver:
return dependency.name
if not version.is_root():
version = self._provider.complete_package(version)
version = self._provider.complete_package(version)
conflict = False
for incompatibility in self._provider.incompatibilities_for(version):
......
......@@ -11,6 +11,9 @@ class DependencyPackage(object):
def package(self):
return self._package
def clone(self): # type: () -> DependencyPackage
return self.__class__(self._dependency, self._package.clone())
def __getattr__(self, name):
return getattr(self._package, name)
......
......@@ -238,7 +238,7 @@ class Locker(object):
data = {
"name": package.pretty_name,
"version": package.pretty_version,
"description": package.description,
"description": package.description or "",
"category": package.category,
"optional": package.optional,
"python-versions": package.python_versions,
......
......@@ -369,6 +369,9 @@ class Package(object):
for dep in self.requires:
clone.requires.append(dep)
for dep in self.dev_requires:
clone.dev_requires.append(dep)
return clone
def __hash__(self):
......
......@@ -43,3 +43,13 @@ class ProjectPackage(Package):
self._python_marker = parse_marker(
create_nested_marker("python_version", self._python_constraint)
)
def clone(self): # type: () -> ProjectPackage
package = super(ProjectPackage, self).clone()
package.build = self.build
package.packages = self.packages[:]
package.include = self.include[:]
package.exclude = self.exclude[:]
return package
......@@ -349,11 +349,11 @@ class LegacyRepository(PyPiRepository):
default_link = links[0]
for link in links:
if link.is_wheel:
m = wheel_file_re.match(default_link.filename)
m = wheel_file_re.match(link.filename)
python = m.group("pyver")
platform = m.group("plat")
if python == "py2.py3" and platform == "any":
urls["bdist_wheel"] = default_link.url
urls["bdist_wheel"] = link.url
elif link.filename.endswith(".tar.gz"):
urls["sdist"] = link.url
elif (
......
......@@ -34,6 +34,7 @@ from poetry.packages import Package
from poetry.semver import parse_constraint
from poetry.semver import VersionConstraint
from poetry.semver import VersionRange
from poetry.semver.exceptions import ParseVersionError
from poetry.utils._compat import Path
from poetry.utils._compat import to_str
from poetry.utils.helpers import parse_requires
......@@ -116,7 +117,16 @@ class PyPiRepository(Repository):
)
continue
package = Package(name, version)
try:
package = Package(name, version)
except ParseVersionError:
self._log(
'Unable to parse version "{}" for the {} package, skipping'.format(
version, name
),
level="debug",
)
continue
if package.is_prerelease() and not allow_prereleases:
continue
......
class ParseVersionError(ValueError):
pass
......@@ -4,6 +4,7 @@ from typing import List
from typing import Union
from .empty_constraint import EmptyConstraint
from .exceptions import ParseVersionError
from .patterns import COMPLETE_VERSION
from .version_constraint import VersionConstraint
from .version_range import VersionRange
......@@ -197,7 +198,7 @@ class Version(VersionRange):
def parse(cls, text): # type: (str) -> Version
match = COMPLETE_VERSION.match(text)
if match is None:
raise ValueError('Unable to parse "{}".'.format(text))
raise ParseVersionError('Unable to parse "{}".'.format(text))
text = text.rstrip(".")
......
import subprocess
import sys
try:
......@@ -7,6 +6,11 @@ except ImportError:
from functools import lru_cache
try:
from glob2 import glob
except ImportError:
from glob import glob
try:
import urllib.parse as urlparse
except ImportError:
import urlparse
......@@ -43,6 +47,12 @@ else:
from pathlib2 import Path
if not PY36:
from collections import OrderedDict
else:
OrderedDict = dict
def decode(string, encodings=None):
if not PY2 and not isinstance(string, bytes):
return string
......
......@@ -5,6 +5,7 @@ import stat
import tempfile
from contextlib import contextmanager
from typing import List
from typing import Optional
from typing import Union
......@@ -41,7 +42,7 @@ def temporary_directory(*args, **kwargs):
shutil.rmtree(name)
def parse_requires(requires): # type: (str) -> Union[list, None]
def parse_requires(requires): # type: (str) -> List[str]
lines = requires.split("\n")
requires_dist = []
......@@ -79,8 +80,7 @@ def parse_requires(requires): # type: (str) -> Union[list, None]
requires_dist.append(line)
if requires_dist:
return requires_dist
return requires_dist
def get_http_basic_auth(
......
......@@ -41,13 +41,15 @@ typing = { version = "^3.6", python = "~2.7 || ~3.4" }
# Use pathlib2 for Python 2.7 and 3.4
pathlib2 = { version = "^2.3", python = "~2.7 || ~3.4" }
# Use glob2 for Python 2.7 and 3.4
glob2 = { version = "^0.6", python = "~2.7 || ~3.4" }
# Use virtualenv for Python 2.7 since venv does not exist
virtualenv = { version = "^16.0", python = "~2.7" }
# functools32 is needed for Python 2.7
functools32 = { version = "^3.2.3", python = "~2.7" }
[tool.poetry.dev-dependencies]
pytest = "^3.4"
pytest = "^4.1"
pytest-cov = "^2.5"
mkdocs = { version = "^1.0", python = "~2.7.9 || ^3.4" }
pymdown-extensions = "^6.0"
......
......@@ -37,9 +37,10 @@ class MakeReleaseCommand(Command):
for python in self.option("python"):
parts = python.split(":", 1)
if len(parts) == 1:
pythons[parts[0]] = self.PYTHON[parts[0]]
version, python = parts
python = self.PYTHON[parts[0]]
version = parts[0]
else:
version, python = parts
pythons[version] = python
self.check_system(pythons)
......
import pytest
from cleo.testers import CommandTester
from clikit.formatter.ansi_formatter import AnsiFormatter
from tests.helpers import get_package
......@@ -174,7 +176,8 @@ def test_show_basic_with_not_installed_packages_decorated(app, poetry, installed
}
)
tester.execute(decorated=True)
tester.io.set_formatter(AnsiFormatter(forced=True))
tester.execute()
expected = """\
\033[32mcachy \033[0m \033[36m0.1.0\033[0m Cachy package
......@@ -304,7 +307,8 @@ def test_show_latest_decorated(app, poetry, installed, repo):
}
)
tester.execute("--latest", decorated=True)
tester.io.set_formatter(AnsiFormatter(forced=True))
tester.execute("--latest")
expected = """\
\033[32mcachy \033[0m \033[36m0.1.0\033[0m \033[33m0.2.0\033[0m Cachy package
......
[tool.poetry]
name = "project-with-transitive-file-dependencies"
version = "1.2.3"
description = "This is a description"
authors = ["Your Name <you@example.com>"]
license = "MIT"
[tool.poetry.dependencies]
python = "*"
demo = {path = "../../distributions/demo-0.1.0-py2.py3-none-any.whl"}
[tool.poetry.dev-dependencies]
# -*- coding: utf-8 -*-
from distutils.core import setup
packages = ["project_with_extras"]
package_data = {"": ["*"]}
extras_require = {"extras_a": ["pendulum>=1.4.4"], "extras_b": ["cachy>=0.2.0"]}
setup_kwargs = {
"name": "project-with-extras",
"version": "1.2.3",
"description": "This is a description",
"long_description": None,
"author": "Your Name",
"author_email": "you@example.com",
"url": None,
"packages": packages,
"package_data": package_data,
"extras_require": extras_require,
}
setup(**setup_kwargs)
# -*- coding: utf-8 -*-
from setuptools import setup
kwargs = dict(
name="demo",
license="MIT",
version="0.1.2",
description="Demo project.",
author="Sébastien Eustace",
author_email="sebastien@eustace.io",
url="https://github.com/demo/demo",
packages=["demo"],
)
setup(**kwargs)
[[package]]
category = "main"
description = ""
name = "demo"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.1.0"
[package.dependencies]
pendulum = ">=1.4.4"
[package.extras]
bar = ["tomlkit"]
foo = ["cleo"]
[package.source]
reference = ""
type = "file"
url = "tests/fixtures/directory/project_with_transitive_file_dependencies/../../distributions/demo-0.1.0-py2.py3-none-any.whl"
[[package]]
category = "main"
description = ""
name = "pendulum"
optional = false
python-versions = "*"
version = "1.4.4"
[[package]]
category = "main"
description = ""
name = "project-with-transitive-file-dependencies"
optional = false
python-versions = "*"
version = "1.2.3"
[package.dependencies]
demo = "*"
[package.source]
reference = ""
type = "directory"
url = "tests/fixtures/directory/project_with_transitive_file_dependencies"
[metadata]
content-hash = "123456789"
python-versions = "*"
[metadata.hashes]
demo = ["70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a"]
pendulum = []
project-with-transitive-file-dependencies = []
......@@ -710,6 +710,28 @@ def test_run_installs_with_local_poetry_directory_transitive(
assert len(installer.installer.installs) == 2
def test_run_installs_with_local_poetry_file_transitive(
installer, locker, repo, package, tmpdir
):
file_path = Path(
"tests/fixtures/directory/project_with_transitive_file_dependencies/"
)
package.add_dependency(
"project-with-transitive-file-dependencies", {"path": str(file_path)}
)
repo.add_package(get_package("pendulum", "1.4.4"))
repo.add_package(get_package("cachy", "0.2.0"))
installer.run()
expected = fixture("with-file-dependency-transitive")
assert locker.written_data == expected
assert len(installer.installer.installs) == 3
def test_run_installs_with_local_setuptools_directory(
installer, locker, repo, package, tmpdir
):
......
Copyright (c) 2018 Sébastien Eustace
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
[tool.poetry]
name = "my-package"
version = "1.2.3"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
readme = "README.rst"
homepage = "https://poetry.eustace.io/"
repository = "https://github.com/sdispater/poetry"
documentation = "https://poetry.eustace.io/docs"
keywords = ["packaging", "dependency", "poetry"]
classifiers = [
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules"
]
exclude = [
"**/SecondBar.py",
"my_package/FooBar/*",
"my_package/Foo/Bar.py",
"my_package/Foo/lowercasebar.py",
"my_package/bar/foo.py",
"my_package/bar/CapitalFoo.py"
]
# Requirements
[tool.poetry.dependencies]
python = "^3.6"
cleo = "^0.6"
cachy = { version = "^0.2.0", extras = ["msgpack"] }
pendulum = { version = "^1.4", optional = true }
[tool.poetry.dev-dependencies]
pytest = "~3.4"
[tool.poetry.extras]
time = ["pendulum"]
[tool.poetry.scripts]
my-script = "my_package:main"
my-2nd-script = "my_package:main2"
extra-script = {callable = "my_package.extra:main", extras = ["time"]}
Copyright (c) 2018 Sébastien Eustace
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
[tool.poetry]
name = "my-package"
version = "1.2.3"
description = "Some description."
authors = [
"Sébastien Eustace <sebastien@eustace.io>"
]
license = "MIT"
readme = "README.rst"
homepage = "https://poetry.eustace.io/"
repository = "https://github.com/sdispater/poetry"
documentation = "https://poetry.eustace.io/docs"
keywords = ["packaging", "dependency", "poetry"]
classifiers = [
"Topic :: Software Development :: Build Tools",
"Topic :: Software Development :: Libraries :: Python Modules"
]
exclude = [
"my_package/Bar/*/bar/*.py"
]
# Requirements
[tool.poetry.dependencies]
python = "^3.6"
cleo = "^0.6"
cachy = { version = "^0.2.0", extras = ["msgpack"] }
pendulum = { version = "^1.4", optional = true }
[tool.poetry.dev-dependencies]
pytest = "~3.4"
[tool.poetry.extras]
time = ["pendulum"]
[tool.poetry.scripts]
my-script = "my_package:main"
my-2nd-script = "my_package:main2"
extra-script = {callable = "my_package.extra:main", extras = ["time"]}
......@@ -17,3 +17,39 @@ def test_builder_find_excluded_files(mocker):
)
assert builder.find_excluded_files() == {"my_package/sub_pkg1/extra_file.xml"}
def test_builder_find_case_sensitive_excluded_files(mocker):
p = mocker.patch("poetry.vcs.git.Git.get_ignored_files")
p.return_value = []
builder = Builder(
Poetry.create(Path(__file__).parent / "fixtures" / "case_sensitive_exclusions"),
NullEnv(),
NullIO(),
)
assert builder.find_excluded_files() == {
"my_package/FooBar/Bar.py",
"my_package/FooBar/lowercasebar.py",
"my_package/Foo/SecondBar.py",
"my_package/Foo/Bar.py",
"my_package/Foo/lowercasebar.py",
"my_package/bar/foo.py",
"my_package/bar/CapitalFoo.py",
}
def test_builder_find_invalid_case_sensitive_excluded_files(mocker):
p = mocker.patch("poetry.vcs.git.Git.get_ignored_files")
p.return_value = []
builder = Builder(
Poetry.create(
Path(__file__).parent / "fixtures" / "invalid_case_sensitive_exclusions"
),
NullEnv(),
NullIO(),
)
assert {"my_package/Bar/foo/bar/Foo.py"} == builder.find_excluded_files()
import pytest
pytest.register_assert_rewrite("tests.mixology.helpers")
......@@ -107,3 +107,31 @@ cachecontrol = []
lockfile_dep = package.extras["filecache"][0]
assert lockfile_dep.name == "lockfile"
def test_lock_packages_with_null_description(locker, root):
package_a = get_package("A", "1.0.0")
package_a.description = None
locker.set_lock_data(root, [package_a])
with locker.lock.open(encoding="utf-8") as f:
content = f.read()
expected = """[[package]]
category = "main"
description = ""
name = "A"
optional = false
python-versions = "*"
version = "1.0.0"
[metadata]
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"
python-versions = "*"
[metadata.hashes]
A = []
"""
assert expected == content
......@@ -228,6 +228,28 @@ def test_search_for_directory_setup_read_setup_with_extras(provider, mocker):
}
@pytest.mark.skipif(not PY35, reason="AST parsing does not work for Python <3.4")
def test_search_for_directory_setup_read_setup_with_no_dependencies(provider, mocker):
mocker.patch("poetry.utils.env.EnvManager.get", return_value=MockEnv())
dependency = DirectoryDependency(
"demo",
Path(__file__).parent.parent
/ "fixtures"
/ "git"
/ "github.com"
/ "demo"
/ "no-dependencies",
)
package = provider.search_for_directory(dependency)[0]
assert package.name == "demo"
assert package.version.text == "0.1.2"
assert package.requires == []
assert package.extras == {}
def test_search_for_directory_poetry(provider):
dependency = DirectoryDependency(
"demo", Path(__file__).parent.parent / "fixtures" / "project_with_extras"
......
......@@ -17,6 +17,7 @@ from tests.helpers import get_package
from tests.repositories.test_legacy_repository import (
MockRepository as MockLegacyRepository,
)
from tests.repositories.test_pypi_repository import MockRepository as MockPyPIRepository
@pytest.fixture()
......@@ -1533,3 +1534,38 @@ def test_solver_can_solve_with_legacy_repository_using_proper_python_compatible_
check_solver_result(
ops, [{"job": "install", "package": get_package("isort", "4.3.4")}]
)
def test_solver_skips_invalid_versions(package, installed, locked, io):
package.python_versions = "^3.7"
repo = MockPyPIRepository()
pool = Pool([repo])
solver = Solver(package, pool, installed, locked, io)
package.add_dependency("trackpy", "^0.4")
ops = solver.solve()
check_solver_result(
ops, [{"job": "install", "package": get_package("trackpy", "0.4.1")}]
)
def test_multiple_constraints_on_root(package, solver, repo):
package.add_dependency("foo", {"version": "^1.0", "python": "^2.7"})
package.add_dependency("foo", {"version": "^2.0", "python": "^3.7"})
foo15 = get_package("foo", "1.5.0")
foo25 = get_package("foo", "2.5.0")
repo.add_package(foo15)
repo.add_package(foo25)
ops = solver.solve()
check_solver_result(
ops,
[{"job": "install", "package": foo15}, {"job": "install", "package": foo25}],
)
{
"info": {
"author": "Trackpy Contributors",
"author_email": "daniel.b.allan@gmail.com",
"bugtrack_url": null,
"classifiers": [],
"description": "",
"description_content_type": "",
"docs_url": null,
"download_url": "",
"downloads": {
"last_day": -1,
"last_month": -1,
"last_week": -1
},
"home_page": "https://github.com/soft-matter/trackpy",
"keywords": "",
"license": "",
"maintainer": "",
"maintainer_email": "",
"name": "trackpy",
"package_url": "https://pypi.org/project/trackpy/",
"platform": "",
"project_url": "https://pypi.org/project/trackpy/",
"project_urls": {
"Homepage": "https://github.com/soft-matter/trackpy"
},
"release_url": "https://pypi.org/project/trackpy/0.4.1/",
"requires_dist": null,
"requires_python": "",
"summary": "particle-tracking toolkit",
"version": "0.4.1"
},
"last_serial": 3786947,
"releases": {
"0.4.1": [
{
"comment_text": "",
"digests": {
"md5": "4c92e8b74840f57c6047f56a4a4412c4",
"sha256": "f682f75e99f6c29c65e8531899b957c67d9d5a027b28b44258fa2c4a18e851cd"
},
"downloads": -1,
"filename": "trackpy-0.4.1.tar.gz",
"has_sig": false,
"md5_digest": "4c92e8b74840f57c6047f56a4a4412c4",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 121998,
"upload_time": "2018-04-21T09:59:50",
"url": "https://files.pythonhosted.org/packages/62/31/797febf2ea8ea316c345c1d0f10503c3901f3fca5c3ffdc6e92717efdcad/trackpy-0.4.1.tar.gz"
}
],
"unknown": [
{
"comment_text": "",
"digests": {
"md5": "6a879fe7871bd5c62d41b5a2ed84a5cd",
"sha256": "88fedb53b03451a56422d4ecb393ea6bb043e821b3ee1e6518485b303e3bddf5"
},
"downloads": -1,
"filename": "trackpy-unknown.tar.gz",
"has_sig": false,
"md5_digest": "6a879fe7871bd5c62d41b5a2ed84a5cd",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 63292,
"upload_time": "2014-10-13T17:33:16",
"url": "https://files.pythonhosted.org/packages/35/23/3b6422d3c006251e2ad857f5fe520b193d473154f88d1f27de50798f2c6c/trackpy-unknown.tar.gz"
}
]
},
"urls": [
{
"comment_text": "",
"digests": {
"md5": "4c92e8b74840f57c6047f56a4a4412c4",
"sha256": "f682f75e99f6c29c65e8531899b957c67d9d5a027b28b44258fa2c4a18e851cd"
},
"downloads": -1,
"filename": "trackpy-0.4.1.tar.gz",
"has_sig": false,
"md5_digest": "4c92e8b74840f57c6047f56a4a4412c4",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 121998,
"upload_time": "2018-04-21T09:59:50",
"url": "https://files.pythonhosted.org/packages/62/31/797febf2ea8ea316c345c1d0f10503c3901f3fca5c3ffdc6e92717efdcad/trackpy-0.4.1.tar.gz"
}
]
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment