forked from pulp/pulp_python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmodels.py
More file actions
221 lines (193 loc) · 8.98 KB
/
Copy pathmodels.py
File metadata and controls
221 lines (193 loc) · 8.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
import hashlib
import re
import tarfile
from gettext import gettext as _
from pulp_python.common import constants
DEFAULT_CHECKSUM_TYPE = 'sha512'
class Package(object):
"""
This class represents a Python package.
"""
TYPE = constants.PACKAGE_TYPE_ID
# The full list of supported attributes. Attributes beginning with underscore are specific to
# this module and are not found in PKG-INFO.
_ATTRS = ('name', 'version', 'summary', 'home_page', 'author', 'author_email', 'license',
'description', 'platform', '_filename', '_checksum', '_checksum_type')
@classmethod
def from_archive(cls, archive_path):
"""
Instantiate a Package using the metadata found inside the Python package found at
archive_path. This tarball should be the build result of running setup.py sdist on the
package, and should contain a PKG-INFO file. This method will read the PKG-INFO to determine
the package's metadata and unit key.
:param archive_path: A filesystem path to the Python source distribution that this Package
will represent.
:type archive_path: basestring
:return: An instance of Package that represents the package found at
archive_path.
:rtype: pulp.common.models.Package
:raises: ValueError if archive_path does not point to a valid Python tarball
created with setup.py sdist.
:raises: IOError if the archive_path does not exist.
"""
try:
compression_type = cls._compression_type(archive_path)
checksum = cls.checksum(archive_path)
package_archive = tarfile.open(archive_path)
for member in package_archive.getmembers():
if re.match('.*/PKG-INFO$|^PKG-INFO$', member.name):
# We have found the metadata!
metadata_file = member
break
if 'metadata_file' not in locals():
msg = _('The archive at %(path)s does not contain a PKG-INFO file.')
msg = msg % {'path': archive_path}
raise ValueError(msg)
metadata_file = package_archive.extractfile(metadata_file)
metadata = metadata_file.read()
# Build a list of tuples of all the attributes found in the metadata. Ignore attributes
# with a leading underscore, as they are not part of the metadata.
try:
required_attrs = [attr for attr in cls._ATTRS if attr[0] != '_']
attrs = dict()
for attr in required_attrs:
attrs[attr] = re.search('^%s: (?P<field>.*)$' % cls._metadata_label(attr),
metadata, flags=re.MULTILINE).group('field')
except AttributeError:
msg = _('The PKG-INFO file is missing required attributes. Please ensure that the '
'following attributes are all present: %(attrs)s')
msg = msg % {
'attrs': ', '.join([cls._metadata_label(attr) for attr in required_attrs])}
raise ValueError(msg)
# Add the filename, checksum, and checksum_type to the attrs
attrs['_filename'] = '%s-%s.tar%s' % (attrs['name'], attrs['version'], compression_type)
attrs['_checksum'] = checksum
attrs['_checksum_type'] = DEFAULT_CHECKSUM_TYPE
package = cls(**attrs)
return package
finally:
if 'package_archive' in locals():
package_archive.close()
def init_unit(self, conduit):
"""
Use the given conduit's init_unit() method to initialize this Unit and store the underlying
Pulp unit as self._unit.
:param conduit: A conduit with a suitable init_unit() to create a Pulp Unit.
:type conduit: pulp.plugins.conduits.mixins.AddUnitMixin
"""
relative_path = self._filename
unit_key = {'name': self.name, 'version': self.version}
metadata = []
for attr in self._ATTRS:
if attr in unit_key:
continue
metadata.append((attr, getattr(self, attr)))
metadata = dict(metadata)
self._unit = conduit.init_unit(self.TYPE, unit_key, metadata, relative_path)
def save_unit(self, conduit):
"""
Use the given conduit's save_unit() method to save self._unit.
:param conduit: A conduit with a suitable save_unit() to save self._unit.
:type conduit: pulp.plugins.conduits.mixins.AddUnitMixin
"""
conduit.save_unit(self._unit)
@property
def storage_path(self):
"""
Return the storage path for self._unit.
:return: The Unit storage path.
:rtype: basestring
"""
return self._unit.storage_path
@staticmethod
def checksum(path, algorithm=DEFAULT_CHECKSUM_TYPE):
"""
Return the checksum of the given path using the given algorithm.
:param path: A path to a file
:type path: basestring
:param algorithm: The hashlib algorithm you wish to use
:type algorithm: basestring
:return: The file's checksum
:rtype: basestring
"""
chunk_size = 32 * 1024 * 1024
hasher = getattr(hashlib, algorithm)()
with open(path) as file_handle:
bits = file_handle.read(chunk_size)
while bits:
hasher.update(bits)
bits = file_handle.read(chunk_size)
return hasher.hexdigest()
@staticmethod
def _compression_type(path):
"""
Return the type of compression used in the file at path. Can be '', '.bz2', '.gz', or
'.zip'. '' is returned if the file at path matches none of the magic signatures. This
algorithm is based on http://stackoverflow.com/a/13044946.
:param path: The path to the file you wish to test for compression type.
:type path: basestring
:return: File extension used to represent the compression type found at path.
:rtype: basestring
"""
magic_dict = {
"\x1f\x8b\x08": ".gz",
"\x42\x5a\x68": ".bz2",
"\x50\x4b\x03\x04": ".zip"
}
# We need to read the first four bytes of the file to compare
with open(path) as the_file:
header = the_file.read(4)
for magic, filetype in magic_dict.items():
if header.startswith(magic):
return filetype
return ''
@staticmethod
def _metadata_label(attribute):
"""
Return the label in the PKG-INFO file that corresponds to the given attribute.
:param attribute: The attribute on a Package for which you wish to know the PKG-INFO label
:type attribute: basestring
:return: The label in the PKG-INFO file that can be used to get the field
:rtype: basestring
"""
label = attribute[0].upper() + attribute[1:]
return label.replace('_', '-')
def __init__(self, name, version, summary, home_page, author, author_email, license,
description, platform, _filename, _checksum, _checksum_type):
"""
Initialize self with the given parameters as its attributes.
:param name: The name of the package.
:type name: basestring
:param version: The package's version.
:type version: basestring
:param summary: A paragraph summarizing the package.
:type summary: basestring
:param home_page: A URL for the package's website.
:type home_page: basestring
:param author: The author's name.
:type author: basestring
:param author_email: The author's e-mail address.
:type author_email: basestring
:param license: The package's license.
:type license: basestring
:param description: A description of the package.
:type description: basestring
:param platform: A list of platforms that the package is intended to be used on.
:type platform: basestring
:param _filename: The package filename.
:type _filename: basestring
:param _checksum: The checksum of the package.
:type _checksum: basestring
:param _checksum_type: The name of the algorithm used to calculate the checksum.
:type _checksum_type: basestring
"""
for attr in self._ATTRS:
setattr(self, attr, locals()[attr])
self._unit = None
def __repr__(self):
"""
Return a string representation of self.
:return: A string representing self.
:rtype: basestring
"""
return 'Python Package: %(name)s-%(version)s' % {'name': self.name, 'version': self.version}