dump_fixtures()   C
last analyzed

Complexity

Conditions 10

Size

Total Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
dl 0
loc 38
rs 5.9999
c 1
b 0
f 0

How to fix   Complexity   

Complexity

Complex classes like dump_fixtures() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# -*- coding: utf-8 -*-
2
3
"""
4
flask_jsondash.model_factories
5
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6
7
Data generation utilities for all charts and dashboards.
8
9
:copyright: (c) 2016 by Chris Tabor.
10
:license: MIT, see LICENSE for more details.
11
"""
12
13
import os
14
import json
15
from datetime import datetime as dt
16
from random import choice, randrange
17
from uuid import uuid1
18
19
import click
20
from werkzeug.datastructures import ImmutableMultiDict
21
22
from flask_jsondash import db, settings
23
24
adapter = db.get_db_handler()
25
26
27
def get_random_group():
28
    """Return a random namespaced group of charts.
29
30
    Returns:
31
        str: A random chart name.
32
33
    Examples:
34
        >>> get_random_group()
35
        >>> {'charts': [...], 'dependencies', 'js_url': '...', ...}
36
    """
37
    return choice(list(settings.CHARTS_CONFIG.values()))
38
39
40
def get_random_chart(group):
41
    """Get a random chart from a specific chart sub-group.
42
43
    Args:
44
        group (dict): A group from the global chart settings config.
45
    """
46
    return choice(list(group['charts']))
47
48
49
def load_fixtures(path):
50
    """Generate all dashboards using any given schemas.
51
52
    Note: this does NOT account for nested folders,
53
    only all json in the given directory.
54
55
    Args:
56
        name (path): The relative folder path for all json configs
57
    """
58
    click.echo('Loading fixtures for path ' + path)
59
    files = [f for f in os.listdir(path) if f.endswith('.json')]
60
    for file in files:
61
        file = '{}/{}/{}'.format(os.getcwd(), path, file)
62
        click.echo('Loading ' + file)
63
        with open(file, 'r') as fixture:
64
            data = json.loads(fixture.read())
65
            adapter.create(data=data)
66
67
68
def dump_fixtures(path, delete_after=False):
69
    """Generate fixture data (json) from existing records in db.
70
71
    Args:
72
        name (path): The folder path to save configs in (must exist first!)
73
        delete_after (bool, optional): Delete records after dumping fixtures.
74
    """
75
    click.echo('Saving db as fixtures to: ' + path)
76
    # If an error occured, don't delete any records
77
    errors = []
78
    # Allow absolute OR relative paths.
79
    cwd = '' if path.startswith('/') else os.getcwd()
80
    dashboards = list(adapter.read())
81
    if not dashboards:
82
        click.echo('Nothing to dump.')
83
        return
84
    for dashboard in dashboards:
85
        # Mongo id is not serializeable
86
        if '_id' in dashboard:
87
            dashboard.pop('_id')
88
        # Update date records
89
        dashboard.update(date=str(dashboard['date']))
90
        name = '{}-{}'.format(dashboard['name'], dashboard['id'])
91
        name = name.replace(' ', '-').lower()
92
        fullpath = '{}/{}/{}.json'.format(cwd, path, name)
93
        click.echo('Saving fixture: ' + fullpath)
94
        try:
95
            with open(fullpath, 'w') as fixture:
96
                fixture.write(json.dumps(dashboard, indent=4))
97
        except Exception as e:
98
            click.echo(e)
99
            errors.append(fullpath)
100
            continue
101
    if delete_after and not errors:
102
        adapter.delete_all()
103
    if errors:
104
        click.echo('The following records could not be dumped: {}'.format(
105
            errors
106
        ))
107
108
109
def make_fake_dashboard(name='Random chart', max_charts=10):
110
    """Generate fake dashboard data with a specific number of random charts.
111
112
    Args:
113
        name (str): The name of the new dashboard (default: {'Random chart'})
114
        max_charts (int): Max number of charts to make (default: {10})
115
116
    Returns:
117
        dict: The chart configuration.
118
    """
119
    charts = ImmutableMultiDict([
120
        make_fake_chart_data() for _ in range(max_charts)]
121
    )
122
    return dict(
123
        name=name,
124
        created_by='global',
125
        date=dt.now(),
126
        category=choice(list(settings.CHARTS_CONFIG.keys())),
127
        modules=db.format_charts(charts),
128
        id=str(uuid1()),
129
        layout='freeform',
130
    )
131
132
133
def make_fake_chart_data(**kwargs):
134
    """Return chart data in required format.
135
136
    Args:
137
        name (None, optional): The name of the chart.
138
        height (None, optional): The height of the chart.
139
        width (None, optional): The width of the chart.
140
        data_source (None, optional): The data source (url) for the chart.
141
142
    Returns:
143
        tuple: A 2-tuple of type (string_label, jsonstring) for the chart.
144
145
    Examples:
146
        >>> make_fake_chart_date(width=10, height=10, name='foo')
147
        >>> ('foo', '{"name": "foo", "height": 10, "width": 10}')
148
    """
149
    _uuid = str(uuid1())
150
    # All of these chart types have example endpoints to use locally.
151
    chart = choice(['bar', 'line', 'step', 'area'])
152
    will_use_inputs = randrange(1, 100) > 50
153
    url = 'http://127.0.0.1:5004/bar'
154
    config = dict(
155
        name=kwargs.get('name'),
156
        width=kwargs.get('width', randrange(200, 2000)),
157
        height=kwargs.get('height', randrange(200, 2000)),
158
        type=chart,
159
        family='C3',
160
        dataSource=kwargs.get('data_source', url),
161
        guid=_uuid,
162
    )
163
    if will_use_inputs:
164
        config.update(
165
            inputs=dict(
166
                btn_classes=['btn', 'btn-info', 'btn-sm'],
167
                submit_text='Submit',
168
                help_text='Change a value',
169
                options=[
170
                    dict(type='number', name='range', label='Number'),
171
                ]
172
            )
173
        )
174
    return (
175
        'module_{}'.format(_uuid),
176
        json.dumps(config)
177
    )
178
179
180
@click.command()
181
@click.option('--records',
182
              default=10,
183
              help='Number of records to insert fake dashboard data into DB.')
184
@click.option('--max-charts',
185
              default=5,
186
              help='Number of charts per dashboard to create.')
187
@click.option('--fixtures',
188
              default=None,
189
              help='A path to load existing configurations from.')
190
@click.option('--dump',
191
              default=None,
192
              help='A path to dump db records, as json dashboard configs.')
193
@click.option('--delete',
194
              is_flag=True,
195
              default=False,
196
              help='Determine if the database records should be deleted')
197
def insert_dashboards(records, max_charts, fixtures, dump, delete):
198
    """Insert a number of dashboard records into the database."""
199
    if fixtures is not None:
200
        return load_fixtures(fixtures)
201
    if dump is not None:
202
        return dump_fixtures(dump, delete_after=delete)
203
    if dump is None and delete:
204
        click.echo('Deleting all records!')
205
        adapter.delete_all()
206
        return
207
    for i in range(records):
208
        data = make_fake_dashboard(
209
            name='Test chart #{}'.format(i),
210
            max_charts=max_charts)
211
        adapter.create(data=data)
212
213
214
def delete_all():
215
    """Delete all dashboards."""
216
    adapter.delete_all()
217
218
219
if __name__ == '__main__':
220
    insert_dashboards()
221