Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

from __future__ import absolute_import, division, print_function 

 

import datetime 

import hashlib 

import json 

import re 

 

import semantic_version 

 

from appr.exception import (InvalidRelease, PackageAlreadyExists, 

PackageReleaseNotFound, raise_package_not_found) 

from appr.models.blob_base import BlobBase 

from appr.semver import last_version, select_version 

 

SCHEMA_VERSION = "v0" 

 

 

def get_media_type(mediatype): 

if mediatype: 

match = re.match(r"application/vnd\.appr\.package-manifest\.(.+?)\.(.+).json", mediatype) 

if match: 

mediatype = match.group(1) 

return mediatype 

 

 

def content_media_type(media_type): 

return "application/vnd.appr.package.%s.%s.tar+gzip" % (media_type, SCHEMA_VERSION) 

 

 

def manifest_media_type(media_type): 

return "application/vnd.appr.package-manifest.%s.%s.json" % (get_media_type(media_type), 

SCHEMA_VERSION) 

 

 

def digest_manifest(manifest): 

return hashlib.sha256(json.dumps(manifest, sort_keys=True)).hexdigest() 

 

 

class PackageBase(object): 

def __init__(self, package_name, release=None, media_type=None, blob=None, metadata=None): 

self.package = package_name 

self.media_type = get_media_type(media_type) 

self.namespace, self.name = package_name.split("/") 

self.release = release 

self._data = None 

self.created_at = None 

self.packager = None 

self._blob = None 

self._blob_size = 0 

self._digest = None 

self._blob = None 

self.metadata = metadata 

self.blob = blob 

 

@property 

def blob(self): 

return self._blob 

 

@blob.setter 

def blob(self, value): 

if value is not None: 

if not isinstance(value, BlobBase): 

raise ValueError("blob must be a BlobBase instance") 

self._blob = value 

 

def channels(self, channel_class, iscurrent=True): 

""" Returns all available channels for a package """ 

channels = channel_class.all(self.package) 

result = [] 

# yapf:disable 

for channel in channels: 

if ((iscurrent and channel.current == self.release) or 

(not iscurrent and self.release in channel.releases())): 

result.append(channel.name) 

# yapf:enable 

return result 

 

@property 

def digest(self): 

if not self._digest and self.blob: 

self._digest = self.blob.digest 

return self._digest 

 

@property 

def blob_size(self): 

if not self._blob_size and self.blob: 

self._blob_size = self.blob.size 

return self._blob_size 

 

@property 

def content_media_type(self): 

return content_media_type(self.media_type) 

 

@property 

def manifest_media_type(self): 

return manifest_media_type(self.media_type) 

 

def content_descriptor(self): 

return { 

"mediaType": self.content_media_type, 

"size": self.blob_size, 

"digest": self.digest, 

"urls": [] 

} 

 

@classmethod 

def view_manifests(cls, package_name, release, manifest_only=False, media_type=None): 

res = [] 

for mtype in cls.manifests(package_name, release): 

if media_type is not None and media_type != mtype: 

continue 

package = cls.get(package_name, release, mtype) 

if manifest_only: 

res.append(package.manifest()) 

else: 

res.append(package.data) 

return res 

 

def manifest(self): 

manifest = {"mediaType": self.manifest_media_type, "content": self.content_descriptor()} 

return manifest 

 

@classmethod 

def view_releases(cls, package, media_type=None): 

return [ 

item 

for release in cls.all_releases(package, media_type=media_type) 

for item in cls.view_manifests(package, release, False, media_type=media_type) 

] 

 

@property 

def data(self): 

if self._data is None: 

self._data = {'created_at': datetime.datetime.utcnow().isoformat()} 

d = { 

"package": self.package, 

"release": self.release, 

"metadata": self.metadata, 

"mediaType": self.manifest_media_type, 

"content": self.content_descriptor() 

} 

self._data.update(d) 

return self._data 

 

@data.setter 

def data(self, data): 

self._data = data 

self.created_at = data['created_at'] 

self.metadata = data.get('metadata', None) 

self.release = data['release'] 

self._digest = data['content']['digest'] 

self._blob_size = data['content']['size'] 

self.media_type = get_media_type(data['mediaType']) 

 

@classmethod 

def check_release(cls, release): 

try: 

semantic_version.Version(release) 

except ValueError as e: 

raise InvalidRelease(e.message, {"version": release}) 

return None 

 

@classmethod 

def get(cls, package, release, media_type): 

""" 

package: string following "namespace/package_name" format 

release: release query. If None return latest release 

 

returns: (package blob(targz) encoded in base64, release) 

""" 

p = cls(package, release) 

p.pull(release, media_type) 

return p 

 

@classmethod 

def get_release(cls, package, release_query, stable=False): 

releases = cls.all_releases(package) 

if not releases: 

raise_package_not_found(package, release=release_query) 

if release_query is None or release_query == 'default': 

return last_version(releases, stable) 

else: 

try: 

return select_version(releases, str(release_query), stable) 

except ValueError as e: 

raise InvalidRelease(e.message, {"release": release_query}) 

 

def pull(self, release_query=None, media_type=None): 

media_type = get_media_type(media_type) 

if media_type is None: 

media_type = self.media_type 

if release_query is None: 

release_query = self.release 

package = self.package 

release = self.get_release(package, release_query) 

if release is None: 

raise PackageReleaseNotFound("No release match '%s' for package '%s'" % (release_query, 

package), 

{"package": package, 

"release_query": release_query}) 

 

self.data = self._fetch(package, str(release), media_type) 

return self 

 

def save(self, force=False, **kwargs): 

self.check_release(self.release) 

if self.isdeleted_release(self.package, self.release) and not force: 

raise PackageAlreadyExists("Package release %s existed" % self.package, 

{"package": self.package, 

"release": self.release}) 

self.blob.save(self.content_media_type) 

self._save(force, **kwargs) 

 

def releases(self): 

return self.all_releases(self.package) 

 

@classmethod 

def delete(cls, package, release, media_type): 

cls._delete(package, release, media_type) 

 

def _save(self, force=False, **kwargs): 

raise NotImplementedError 

 

@classmethod 

def all(cls, namespace=None, **kwargs): 

raise NotImplementedError 

 

@classmethod 

def _fetch(cls, package, release, media_type): 

raise NotImplementedError 

 

@classmethod 

def _delete(cls, package, release, media_type): 

raise NotImplementedError 

 

@classmethod 

def all_releases(cls, package, media_type=None): 

raise NotImplementedError 

 

@classmethod 

def search(cls, query, **kwargs): 

raise NotImplementedError 

 

@classmethod 

def isdeleted_release(cls, package, release): 

raise NotImplementedError 

 

@classmethod 

def reindex(cls): 

raise NotImplementedError 

 

@classmethod 

def dump_all(cls, blob_cls): 

""" produce a dict with all packages """ 

raise NotImplementedError 

 

@classmethod 

def manifests(cls, package, release): 

raise NotImplementedError