Test Failed
Pull Request — master (#2)
by Heiko 'riot'
06:45
created

isomer.ui.store.inventory   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 186
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 91
dl 0
loc 186
rs 10
c 0
b 0
f 0
wmc 15

5 Functions

Rating   Name   Duplication   Size   Complexity  
A get_inventory() 0 17 1
A populate_store() 0 19 1
A log() 0 4 1
C inspect_wheel() 0 77 10
A get_store() 0 14 2
1
# !/usr/bin/env python
2
# -*- coding: UTF-8 -*-
3
4
# Isomer - The distributed application framework
5
# ==============================================
6
# Copyright (C) 2011-2020 Heiko 'riot' Weinen <[email protected]> and others.
7
#
8
# This program is free software: you can redistribute it and/or modify
9
# it under the terms of the GNU Affero General Public License as published by
10
# the Free Software Foundation, either version 3 of the License, or
11
# (at your option) any later version.
12
#
13
# This program is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
# GNU Affero General Public License for more details.
17
#
18
# You should have received a copy of the GNU Affero General Public License
19
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
21
"""
22
23
Module: Store
24
=============
25
26
Software inventory functionality
27
28
"""
29
30
import os
31
import base64
32
from docutils import core
33
from typing import Tuple
34
35
import requests
36
import zipfile
37
38
from urllib.parse import urljoin
39
40
from isomer.misc.std import std_datetime
41
from isomer.logger import isolog, debug
42
from isomer.ui.store import DEFAULT_STORE_URL
43
44
_STORE = {}
45
46
47
def log(*args, **kwargs):
48
    """Log as store emitter"""
49
    kwargs.update({"emitter": "STORE", "frame_ref": 2})
50
    isolog(*args, **kwargs)
51
52
53
def get_store(source=DEFAULT_STORE_URL,
54
              auth: Tuple[str, str] = None):
55
    """Return current store
56
57
    This function acts as cache for
58
    :func: `~isomer.store.inventory.populate_store`
59
60
    :param: source: Optionally, specify alternative store url
61
    :param: auth: Optional tuple containing username and password"""
62
63
    if _STORE == {}:
64
        return populate_store(source, auth)
65
    else:
66
        return _STORE
67
68
69
def inspect_wheel(wheel: str):
70
    """Inspeggtor reporting for duty!
71
72
    This thing opens up Python Eggs to analyze their content in an isomeric way.
73
    Some data pulled out is specific to isomer modules but could be used for general
74
    structures. Specifically, a `docs/README.rst` and a `docs/preview.png` is looked
75
    for.
76
77
    :param wheel: Filename of the python egg to inspect
78
    :return: Dictionary with
79
        `info` - content of docs/README.rst if found
80
        `preview` - content of docs/preview.png if found
81
        `date` - publishing date according to EGG-INFO/PKG-INFO timestamp
82
        `requires` - any required external python packages
83
    :rtype: dict
84
    """
85
    archive = zipfile.ZipFile(wheel)
86
87
    meta_name = os.path.basename(wheel).split("-py")[0] + ".dist-info/METADATA"
88
89
    try:
90
        info = archive.read("docs/README.rst").decode('utf-8')
91
92
        info = core.publish_parts(info, writer_name="html")["html_body"]
93
    except KeyError:
94
        info = "No information provided"
95
96
    try:
97
        preview = base64.b64encode(archive.read("docs/preview.png")).decode('utf-8')
98
    except KeyError:
99
        preview = ""
100
101
    pkg_info_info = archive.getinfo(meta_name)
102
103
    date = std_datetime(pkg_info_info.date_time)
104
105
    requires = []
106
107
    homepage = ""
108
    author = ""
109
    contact = ""
110
    package_license = ""
111
112
    try:
113
        lines = str(archive.read(meta_name), encoding="ascii").split("\n")
114
115
        for line in lines:
116
            if line.startswith("Home-page:"):
117
                homepage = line.split("Home-page: ")[1]
118
            if line.startswith("Author:"):
119
                author = line.split("Author: ")[1]
120
            if line.startswith("Author-email:"):
121
                contact = line.split("Author-email: ")[1]
122
            if line.startswith("License:"):
123
                package_license = line.split("License: ")[1]
124
            if line.startswith("Requires-Dist:"):
125
                req = line.split("Requires-Dist: ")[1]
126
                req = req.replace(" (", "").replace(")", "")
127
                requires.append(req)
128
    except KeyError:
129
        log("No metadata found")
130
131
    result = {
132
        'info': info,
133
        'preview': preview,
134
        'date': date,
135
        'requires': requires,
136
        'homepage': homepage,
137
        'author': author,
138
        'contact': contact,
139
        'license': package_license,
140
        'downloads': "-",
141
        'stars': "-"
142
    }
143
    log(result, pretty=True)
144
145
    return result
146
147
148
def populate_store(source=DEFAULT_STORE_URL,
149
                   auth: Tuple[str, str] = None):
150
    """Grab data from the isomer software store"""
151
152
    global _STORE
153
154
    log("Getting store information for", source, lvl=debug)
155
156
    url = urljoin(source, "/store")
157
    data = requests.get(url, auth=auth)
158
    index = data.json()
159
160
    _STORE = {
161
        'source': source,
162
        'auth': auth,
163
        'packages': index
164
    }
165
166
    return _STORE
167
168
169
def get_inventory(ctx):
170
    """Check local instance and its environments for installed modules"""
171
172
    instance_configuration = ctx.obj['instance_configuration']
173
    environment = instance_configuration['environment']
174
    environment_modules = instance_configuration['environments'][environment]['modules']
175
176
    instance_modules = instance_configuration['modules']
177
178
    log("Instance:", instance_modules, pretty=True)
179
180
    result = {
181
        'instance': instance_modules,
182
        'current': environment_modules
183
    }
184
185
    return result
186