169 lines
4.7 KiB
Python
169 lines
4.7 KiB
Python
|
import hashlib
|
||
|
import json
|
||
|
|
||
|
import pipenv.vendor.tomlkit as tomlkit
|
||
|
|
||
|
from .models import (
|
||
|
DataModel, Hash, Requires, PipfileSection, Pipenv,
|
||
|
PackageCollection, ScriptCollection, SourceCollection,
|
||
|
)
|
||
|
|
||
|
|
||
|
PIPFILE_SECTIONS = {
|
||
|
"source": SourceCollection,
|
||
|
"packages": PackageCollection,
|
||
|
"dev-packages": PackageCollection,
|
||
|
"requires": Requires,
|
||
|
"scripts": ScriptCollection,
|
||
|
"pipfile": PipfileSection,
|
||
|
"pipenv": Pipenv
|
||
|
}
|
||
|
|
||
|
DEFAULT_SOURCE_TOML = """\
|
||
|
[[source]]
|
||
|
name = "pypi"
|
||
|
url = "https://pypi.org/simple"
|
||
|
verify_ssl = true
|
||
|
"""
|
||
|
|
||
|
class Pipfile(DataModel):
|
||
|
"""Representation of a Pipfile.
|
||
|
"""
|
||
|
__SCHEMA__ = {}
|
||
|
|
||
|
@classmethod
|
||
|
def validate(cls, data):
|
||
|
# HACK: DO NOT CALL `super().validate()` here!!
|
||
|
# Cerberus seems to break TOML Kit's inline table preservation if it
|
||
|
# is not at the top-level. Fortunately the spec doesn't have nested
|
||
|
# non-inlined tables, so we're OK as long as validation is only
|
||
|
# performed at section-level. validation is performed.
|
||
|
for key, klass in PIPFILE_SECTIONS.items():
|
||
|
if key not in data:
|
||
|
continue
|
||
|
klass.validate(data[key])
|
||
|
|
||
|
package_categories = set(data.keys()) - set(PIPFILE_SECTIONS.keys())
|
||
|
|
||
|
for category in package_categories:
|
||
|
PackageCollection.validate(data[category])
|
||
|
|
||
|
@classmethod
|
||
|
def load(cls, f, encoding=None):
|
||
|
content = f.read()
|
||
|
if encoding is not None:
|
||
|
content = content.decode(encoding)
|
||
|
data = tomlkit.loads(content)
|
||
|
if "source" not in data:
|
||
|
# HACK: There is no good way to prepend a section to an existing
|
||
|
# TOML document, but there's no good way to copy non-structural
|
||
|
# content from one TOML document to another either. Modify the
|
||
|
# TOML content directly, and load the new in-memory document.
|
||
|
sep = "" if content.startswith("\n") else "\n"
|
||
|
content = DEFAULT_SOURCE_TOML + sep + content
|
||
|
data = tomlkit.loads(content)
|
||
|
return cls(data)
|
||
|
|
||
|
def __getitem__(self, key):
|
||
|
value = self._data[key]
|
||
|
try:
|
||
|
return PIPFILE_SECTIONS[key](value)
|
||
|
except KeyError:
|
||
|
return value
|
||
|
|
||
|
def __setitem__(self, key, value):
|
||
|
if isinstance(value, DataModel):
|
||
|
self._data[key] = value._data
|
||
|
else:
|
||
|
self._data[key] = value
|
||
|
|
||
|
def get_hash(self):
|
||
|
data = {
|
||
|
"_meta": {
|
||
|
"sources": self._data["source"],
|
||
|
"requires": self._data.get("requires", {}),
|
||
|
},
|
||
|
"default": self._data.get("packages", {}),
|
||
|
"develop": self._data.get("dev-packages", {}),
|
||
|
}
|
||
|
for category, values in self._data.items():
|
||
|
if category in PIPFILE_SECTIONS or category in ("default", "develop", "pipenv"):
|
||
|
continue
|
||
|
data[category] = values
|
||
|
content = json.dumps(data, sort_keys=True, separators=(",", ":"))
|
||
|
if isinstance(content, str):
|
||
|
content = content.encode("utf-8")
|
||
|
return Hash.from_hash(hashlib.sha256(content))
|
||
|
|
||
|
def dump(self, f, encoding=None):
|
||
|
content = tomlkit.dumps(self._data)
|
||
|
if encoding is not None:
|
||
|
content = content.encode(encoding)
|
||
|
f.write(content)
|
||
|
|
||
|
@property
|
||
|
def sources(self):
|
||
|
try:
|
||
|
return self["source"]
|
||
|
except KeyError:
|
||
|
raise AttributeError("sources")
|
||
|
|
||
|
@sources.setter
|
||
|
def sources(self, value):
|
||
|
self["source"] = value
|
||
|
|
||
|
@property
|
||
|
def source(self):
|
||
|
try:
|
||
|
return self["source"]
|
||
|
except KeyError:
|
||
|
raise AttributeError("source")
|
||
|
|
||
|
@source.setter
|
||
|
def source(self, value):
|
||
|
self["source"] = value
|
||
|
|
||
|
@property
|
||
|
def packages(self):
|
||
|
try:
|
||
|
return self["packages"]
|
||
|
except KeyError:
|
||
|
raise AttributeError("packages")
|
||
|
|
||
|
@packages.setter
|
||
|
def packages(self, value):
|
||
|
self["packages"] = value
|
||
|
|
||
|
@property
|
||
|
def dev_packages(self):
|
||
|
try:
|
||
|
return self["dev-packages"]
|
||
|
except KeyError:
|
||
|
raise AttributeError("dev-packages")
|
||
|
|
||
|
@dev_packages.setter
|
||
|
def dev_packages(self, value):
|
||
|
self["dev-packages"] = value
|
||
|
|
||
|
@property
|
||
|
def requires(self):
|
||
|
try:
|
||
|
return self["requires"]
|
||
|
except KeyError:
|
||
|
raise AttributeError("requires")
|
||
|
|
||
|
@requires.setter
|
||
|
def requires(self, value):
|
||
|
self["requires"] = value
|
||
|
|
||
|
@property
|
||
|
def scripts(self):
|
||
|
try:
|
||
|
return self["scripts"]
|
||
|
except KeyError:
|
||
|
raise AttributeError("scripts")
|
||
|
|
||
|
@scripts.setter
|
||
|
def scripts(self, value):
|
||
|
self["scripts"] = value
|