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

Complexity

Conditions 17

Size

Total Lines 57
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
eloc 45
nop 8
dl 0
loc 57
rs 1.8
c 0
b 0
f 0

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
from flask import Blueprint, current_app, request, redirect
2
from webargs import fields, flaskparser
3
import log
4
5
from .. import domain
6
7
from ._cache import Cache
8
from ._utils import route, track, display
9
10
11
blueprint = Blueprint('image', __name__)
12
cache_filtered = Cache()
13
cache_unfiltered = Cache(filtered=False)
14
15
PLACEHOLDER = "https://raw.githubusercontent.com/jacebrowning/memegen-flask/main/memegen/static/images/missing.png"
16
OPTIONS = {
17
    '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
@blueprint.route("/latest<int:index>.jpg")
29
@flaskparser.use_kwargs({'filtered': fields.Bool(missing=True)})
30
def get_latest(index=1, filtered=True):
31
    cache = cache_filtered if filtered else cache_unfiltered
32
    kwargs = cache.get(index - 1)
33
34
    if kwargs:
35
        kwargs['preview'] = True
36
    else:
37
        kwargs['key'] = 'custom'
38
        kwargs['path'] = "your_meme/goes_here"
39
        kwargs['alt'] = PLACEHOLDER
40
41
    return redirect(route('.get', _external=True, **kwargs))
42
43
44
@blueprint.route("/<key>.jpg")
45
@flaskparser.use_kwargs(OPTIONS)
46
def get_without_text(key, **options):
47
    options.pop('preview')
48
    options.pop('share')
49
50
    template = current_app.template_service.find(key)
51
    text = domain.Text(template.default_path)
52
53
    return redirect(route('.get', key=key, path=text.path, **options))
54
55
56
@blueprint.route("/<key>.jpeg")
57
def get_without_text_jpeg(key):
58
    return redirect(route('.get_without_text', key=key))
59
60
61
@blueprint.route("/<key>/<path:path>.jpg", endpoint='get')
62
@flaskparser.use_kwargs(OPTIONS)
63
def get_with_text(key, path, alt, font, watermark, preview, share, **size):
64
    assert len(size) == 2
65
    options = dict(key=key, path=path,
66
                   alt=alt, font=font, watermark=watermark, **size)
67
    if preview:
68
        options['preview'] = True
69
    if share:
70
        options['share'] = True
71
72
    text = domain.Text(path)
73
    fontfile = current_app.font_service.find(font)
74
75
    template = current_app.template_service.find(key, allow_missing=True)
76
    if template.key != key:
77
        options['key'] = template.key
78
        return redirect(route('.get', **options))
79
80
    if path != text.path:
81
        options['path'] = text.path
82
        return redirect(route('.get', **options))
83
84
    if alt and "://memegen.link/" in alt:
85
        options.pop('alt')
86
        options['key'] = alt.split('/')[3]
87
        return redirect(route('.get', **options))
88
89
    if alt and "://" in alt and key != 'custom':
90
        options['key'] = 'custom'
91
        return redirect(route('.get', **options))
92
93
    if alt and template.path == template.get_path(alt, download=False):
94
        options.pop('alt')
95
        return redirect(route('.get', **options))
96
97
    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
    if not valid:
103
        options.pop('watermark')
104
        return redirect(route('.get', **options))
105
106
    image = current_app.image_service.create(
107
        template, text,
108
        style=PLACEHOLDER if alt == 'none' else alt,
109
        font=fontfile, size=size, watermark=final_watermark,
110
    )
111
112
    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
        track(image.text)
116
117
    return display(image.text, image.path, share=share)
118
119
120
@blueprint.route("/<key>/<path:path>.jpeg")
121
def get_with_text_jpeg(key, path):
122
    return redirect(route('.get', key=key, path=path))
123
124
125
@blueprint.route("/_<code>.jpg")
126
@flaskparser.use_kwargs(OPTIONS)
127
def get_encoded(code, alt, font, watermark, preview, share, **size):
128
    assert len(size) == 2
129
    options = dict(code=code, font=font, watermark=watermark, **size)
130
    if share:
131
        options['share'] = True
132
133
    if alt or preview:
134
        return redirect(route('.get_encoded', **options))
135
136
    key, path = current_app.link_service.decode(code)
137
    template = current_app.template_service.find(key)
138
    text = domain.Text(path)
139
    fontfile = current_app.font_service.find(font)
140
141
    if font and not fontfile:
142
        options.pop('font')
143
        return redirect(route('.get_encoded', **options))
144
145
    watermark, valid = _get_watermark(request, text, watermark, share)
146
    if not valid:
147
        options.pop('watermark')
148
        return redirect(route('.get_encoded', **options))
149
150
    image = current_app.image_service.create(
151
        template, text, font=fontfile, size=size, watermark=watermark,
152
    )
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("Watermark disabled (no text)")
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