memegen.routes.image.get_with_text()   F
last analyzed

Complexity

Conditions 17

Size

Total Lines 57
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 34
CRAP Score 17.0067

Importance

Changes 0
Metric Value
cc 17
eloc 45
nop 8
dl 0
loc 57
rs 1.8
c 0
b 0
f 0
ccs 34
cts 35
cp 0.9714
crap 17.0067

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like memegen.routes.image.get_with_text() 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.

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1 1
from flask import Blueprint, current_app, request, redirect
0 ignored issues
show
introduced by
Unable to import 'flask'
Loading history...
2
from webargs import fields, flaskparser
0 ignored issues
show
introduced by
Unable to import 'webargs'
Loading history...
3 1
import log
4 1
5
from .. import domain
6 1
7
from ._cache import Cache
8 1
from ._utils import route, track, display
9 1
10
11
blueprint = Blueprint('image', __name__)
12 1
cache_filtered = Cache()
13 1
cache_unfiltered = Cache(filtered=False)
14 1
15 1
PLACEHOLDER = "https://raw.githubusercontent.com/jacebrowning/memegen/master/memegen/static/images/missing.png"
16
OPTIONS = {
17 1
    'alt': fields.Str(missing=None),
18
    'font': fields.Str(missing=None),
19
    'preview': fields.Bool(missing=False),
20
    'share': fields.Bool(missing=False),
21
    'width': fields.Int(missing=None),
22
    'height': fields.Int(missing=None),
23
    'watermark': fields.Str(missing=None),
24
}
25
26
27
@blueprint.route("/latest.jpg")
28 1
@blueprint.route("/latest<int:index>.jpg")
29 1
@flaskparser.use_kwargs({'filtered': fields.Bool(missing=True)})
30 1
def get_latest(index=1, filtered=True):
31 1
    cache = cache_filtered if filtered else cache_unfiltered
32 1
    kwargs = cache.get(index - 1)
33 1
34
    if kwargs:
35 1
        kwargs['preview'] = True
36 1
    else:
37
        kwargs['key'] = 'custom'
38 1
        kwargs['path'] = "your_meme/goes_here"
39 1
        kwargs['alt'] = PLACEHOLDER
40 1
41
    return redirect(route('.get', _external=True, **kwargs))
42 1
43
44
@blueprint.route("/<key>.jpg")
45 1
@flaskparser.use_kwargs(OPTIONS)
46 1
def get_without_text(key, **options):
47
    options.pop('preview')
48 1
    options.pop('share')
49 1
50
    template = current_app.template_service.find(key)
51 1
    text = domain.Text(template.default_path)
52 1
53
    return redirect(route('.get', key=key, path=text.path, **options))
54 1
55
56
@blueprint.route("/<key>.jpeg")
57 1
def get_without_text_jpeg(key):
58
    return redirect(route('.get_without_text', key=key))
59 1
60
61
@blueprint.route("/<key>/<path:path>.jpg", endpoint='get')
62 1
@flaskparser.use_kwargs(OPTIONS)
63 1
def get_with_text(key, path, alt, font, watermark, preview, share, **size):
64
    assert len(size) == 2
65 1
    options = dict(key=key, path=path,
66
                   alt=alt, font=font, watermark=watermark, **size)
67 1
    if preview:
68 1
        options['preview'] = True
69 1
    if share:
70
        options['share'] = True
71
72 1
    text = domain.Text(path)
73 1
    fontfile = current_app.font_service.find(font)
74
75 1
    template = current_app.template_service.find(key, allow_missing=True)
76 1
    if template.key != key:
77 1
        options['key'] = template.key
78 1
        return redirect(route('.get', **options))
79
80 1
    if path != text.path:
81 1
        options['path'] = text.path
82 1
        return redirect(route('.get', **options))
83
84 1
    if alt and "://memegen.link/" in alt:
85 1
        options.pop('alt')
86 1
        options['key'] = alt.split('/')[3]
87
        return redirect(route('.get', **options))
88 1
89 1
    if alt and "://" in alt and key != 'custom':
90 1
        options['key'] = 'custom'
91
        return redirect(route('.get', **options))
92 1
93 1
    if alt and template.path == template.get_path(alt, download=False):
94 1
        options.pop('alt')
95 1
        return redirect(route('.get', **options))
96
97 1
    if font and not fontfile:
98
        options.pop('font')
99
        return redirect(route('.get', **options))
100
101
    final_watermark, valid = _get_watermark(request, text, watermark, share)
102 1
    if not valid:
103 1
        options.pop('watermark')
104 1
        return redirect(route('.get', **options))
105 1
106
    image = current_app.image_service.create(
107 1
        template, text,
108
        style=PLACEHOLDER if alt == 'none' else alt,
109
        font=fontfile, size=size, watermark=final_watermark,
110 1
    )
111
112 1
    if not preview:
113
        cache_filtered.add(key=key, path=path, alt=alt, font=font)
114
        cache_unfiltered.add(key=key, path=path, alt=alt, font=font)
115 1
        track(image.text)
116
117
    return display(image.text, image.path, share=share)
118 1
119 1
120 1
@blueprint.route("/<key>/<path:path>.jpeg")
121 1
def get_with_text_jpeg(key, path):
122
    return redirect(route('.get', key=key, path=path))
123 1
124
125 1
@blueprint.route("/_<code>.jpg")
126
@flaskparser.use_kwargs(OPTIONS)
127
def get_encoded(code, alt, font, watermark, preview, share, **size):
128 1
    assert len(size) == 2
129 1
    options = dict(code=code, font=font, watermark=watermark, **size)
130 1
    if share:
131 1
        options['share'] = True
132
133 1
    if alt or preview:
134 1
        return redirect(route('.get_encoded', **options))
135 1
136 1
    key, path = current_app.link_service.decode(code)
137 1
    template = current_app.template_service.find(key)
138 1
    text = domain.Text(path)
139
    fontfile = current_app.font_service.find(font)
140
141
    if font and not fontfile:
142 1
        options.pop('font')
143 1
        return redirect(route('.get_encoded', **options))
144 1
145
    watermark, valid = _get_watermark(request, text, watermark, share)
146 1
    if not valid:
147 1
        options.pop('watermark')
148 1
        return redirect(route('.get_encoded', **options))
149
150 1
    image = current_app.image_service.create(
151 1
        template, text, font=fontfile, size=size, watermark=watermark,
152 1
    )
153
154
    track(image.text)
155
156
    return display(image.text, image.path, share=share)
157
158
159
def _get_watermark(_request, text: domain.Text, watermark: str, share: bool):
160
    referrer = _request.environ.get('HTTP_REFERER', "").lower()
161
    agent = _request.environ.get('HTTP_USER_AGENT', "").lower()
162
    log.debug("Referrer=%r Agent=%r", referrer, agent)
163
164
    if not text:
165
        log.debug(f"Watermark disabled (no text)")
0 ignored issues
show
introduced by
Using an f-string that does not have any interpolated variables
Loading history...
166
        if watermark:
167
            return None, False
168
        else:
169
            return None, True
170
171
    if watermark == 'none':
172
        if share:
173
            log.debug("Watermark disabled (share=true)")
174
            return None, True
175
        for option in current_app.config['WATERMARK_OPTIONS']:
176
            for identity in (referrer, agent):
177
                if option and identity and option in identity:
178
                    log.debug(f"Watermark disabled ({option} in {identity})")
179
                    return None, True
180
181
        log.warning("Request does not support unmarked images")
182
        return None, False
183
184
    if watermark and watermark not in current_app.config['WATERMARK_OPTIONS']:
185
        log.warning("Unsupported custom watermark: %r", watermark)
186
        return watermark, False
187
188
    if watermark:
189
        log.debug("Using custom watermark: %r", watermark)
190
        return watermark, True
191
192
    default = current_app.config['WATERMARK_OPTIONS'][0]
193
    log.debug("Using default watermark: %r", default)
194
    return default, True
195