• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hivesolutions / appier / #620453825

12 Apr 2024 10:09AM UTC coverage: 60.161%. First build
#620453825

Pull #64

travis-ci

Pull Request #64: Initial approach to regex replace string fix.

31 of 32 new or added lines in 5 files covered. (96.88%)

9686 of 16100 relevant lines covered (60.16%)

1.19 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

28.64
/src/appier/validation.py
1
#!/usr/bin/python
2
# -*- coding: utf-8 -*-
3

4
# Hive Appier Framework
5
# Copyright (c) 2008-2024 Hive Solutions Lda.
6
#
7
# This file is part of Hive Appier Framework.
8
#
9
# Hive Appier Framework is free software: you can redistribute it and/or modify
10
# it under the terms of the Apache License as published by the Apache
11
# Foundation, either version 2.0 of the License, or (at your option) any
12
# later version.
13
#
14
# Hive Appier Framework is distributed in the hope that it will be useful,
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
# Apache License for more details.
18
#
19
# You should have received a copy of the Apache License along with
20
# Hive Appier Framework. If not, see <http://www.apache.org/licenses/>.
21

22
__author__ = "João Magalhães <joamag@hive.pt>"
2✔
23
""" The author(s) of the module """
24

25
__copyright__ = "Copyright (c) 2008-2024 Hive Solutions Lda."
2✔
26
""" The copyright for the module """
27

28
__license__ = "Apache License, Version 2.0"
2✔
29
""" The license for the module """
30

31
import re
2✔
32
import copy
2✔
33
import datetime
2✔
34

35
from . import util
2✔
36
from . import common
2✔
37
from . import legacy
2✔
38
from . import exceptions
2✔
39

40
SIMPLE_REGEX_VALUE = r"^[\:\.\s\w-]+$"
2✔
41
""" The simple regex value used to validate
42
if the provided value is a "simple" one meaning
43
that it may be used safely for URL parts """
44

45
EMAIL_REGEX_VALUE = r"^[\w\d\._%+-]+@[\w\d\.\-]+$"
2✔
46
""" The email regex value used to validate
47
if the provided value is in fact an email """
48

49
URL_REGEX_VALUE = (
2✔
50
    r"^\w+\:\/\/([^@]+\:[^@]+@)?[^\:\/\?#]+(\:\d+)?(\/[^\?#]+)*\/?(\?[^#]*)?(#.*)?$"
51
)
52
""" The URL regex value used to validate
53
if the provided value is in fact an URL/URI """
54

55
SIMPLE_REGEX = re.compile(SIMPLE_REGEX_VALUE, re.ASCII if hasattr(re, "ASCII") else 0)
2✔
56
""" The simple regex used to validate
57
if the provided value is a "simple" one meaning
58
that it may be used safely for URL parts """
59

60
EMAIL_REGEX = re.compile(EMAIL_REGEX_VALUE, re.UNICODE)
2✔
61
""" The email regex used to validate
62
if the provided value is in fact an email """
63

64
URL_REGEX = re.compile(URL_REGEX_VALUE, re.UNICODE)
2✔
65
""" The URL regex used to validate
66
if the provided value is in fact an URL/URI """
67

68

69
def validate(method=None, methods=[], object=None, ctx=None, build=True):
2✔
70
    # retrieves the base request object that is going to be used in
71
    # the construction of the object
72
    request = common.base().get_request()
2✔
73

74
    # uses the provided method to retrieves the complete
75
    # set of methods to be used for validation, this provides
76
    # an extra level of indirection
77
    methods = method() if method else methods
2✔
78
    errors = []
2✔
79

80
    # verifies if the provided object is valid in such case creates
81
    # a copy of it and uses it as the base object for validation
82
    # otherwise used an empty map (form validation)
83
    object = object and copy.copy(object) or {}
2✔
84

85
    # in case the build flag is set must process the received request
86
    # to correctly retrieve populate the object from it
87
    if build:
2✔
88
        # retrieves the current request data and tries to
89
        # "load" it as JSON data, in case it fails gracefully
90
        # handles the failure setting the value as an empty map
91
        data_j = util.request_json()
×
92

93
        # uses all the values referencing data in the request to try
94
        # to populate the object this way it may be constructed using
95
        # any of theses strategies (easier for the developer)
96
        for name, value in data_j.items():
×
97
            object[name] = value
×
98
        for name, value in request.files_s.items():
×
99
            object[name] = value
×
100
        for name, value in request.post_s.items():
×
101
            object[name] = value
×
102
        for name, value in request.params_s.items():
×
103
            object[name] = value
×
104

105
    # iterates over the complete set of methods registered for validation
106
    # and runs them expecting exceptions to be raised from them, each adding
107
    # new errors to the current errors stack
108
    for method in methods:
2✔
109
        try:
2✔
110
            method(object, ctx=ctx)
2✔
111
        except exceptions.ValidationMultipleError as error:
2✔
112
            errors.extend(error.errors)
×
113
        except exceptions.ValidationInternalError as error:
2✔
114
            errors.append((error.name, error.message))
2✔
115

116
    # creates the map that will be used to store the association between the
117
    # field name of the object and the validation errors associated with it
118
    errors_map = dict()
2✔
119
    for name, message in errors:
2✔
120
        if not name in errors_map:
2✔
121
            errors_map[name] = []
2✔
122
        _errors = errors_map[name]
2✔
123
        _errors.append(message)
2✔
124

125
    # returns both the newly created errors map that associates each of the
126
    # model name with the sequence of errors and the validated object (state)
127
    return errors_map, object
2✔
128

129

130
def validate_b(method=None, methods=[], object=None, ctx=None, build=True):
2✔
131
    errors_map, object = validate(
×
132
        method=method, methods=methods, object=object, ctx=ctx, build=build
133
    )
134
    result = False if errors_map else True
×
135
    return result
×
136

137

138
def validate_e(method=None, methods=[], object=None, ctx=None, build=True):
2✔
139
    errors_map, object = validate(
×
140
        method=method, methods=methods, object=object, ctx=ctx, build=build
141
    )
142
    if not errors_map:
×
143
        return
×
144
    raise exceptions.ValidationError(errors_map, object)
×
145

146

147
def safe(comparison):
2✔
148
    try:
×
NEW
149
        return comparison()
×
150
    except TypeError:
×
151
        return False
×
152

153

154
def custom(name, callable, message="value is not valid", locale=True):
2✔
155
    def validation(object, ctx):
×
156
        value = object.get(name, None)
×
157
        if value == None:
×
158
            return True
×
159
        if callable(value):
×
160
            return True
×
161
        message_l = _to_locale(message) if locale else message
×
162
        raise exceptions.ValidationInternalError(name, message_l)
×
163

164
    return validation
×
165

166

167
def eq(name, value_c, message="must be equal to %s", locale=True):
2✔
168
    def validation(object, ctx):
×
169
        value = object.get(name, None)
×
170
        if value == None:
×
171
            return True
×
172
        if value == value_c:
×
173
            return True
×
174
        message_l = _to_locale(message) if locale else message
×
175
        raise exceptions.ValidationInternalError(name, message_l % str(value_c))
×
176

177
    return validation
×
178

179

180
def gt(name, value_c, message="must be greater than %s", locale=True):
2✔
181
    def validation(object, ctx):
×
182
        value = object.get(name, None)
×
183
        if value == None:
×
184
            return True
×
185
        if safe(lambda: value > value_c):
×
186
            return True
×
187
        message_l = _to_locale(message) if locale else message
×
188
        raise exceptions.ValidationInternalError(name, message_l % str(value_c))
×
189

190
    return validation
×
191

192

193
def gte(name, value_c, message="must be greater than or equal to %s", locale=True):
2✔
194
    def validation(object, ctx):
×
195
        value = object.get(name, None)
×
196
        if value == None:
×
197
            return True
×
198
        if safe(lambda: value >= value_c):
×
199
            return True
×
200
        message_l = _to_locale(message) if locale else message
×
201
        raise exceptions.ValidationInternalError(name, message_l % str(value_c))
×
202

203
    return validation
×
204

205

206
def lt(name, value_c, message="must be less than %s", locale=True):
2✔
207
    def validation(object, ctx):
×
208
        value = object.get(name, None)
×
209
        if value == None:
×
210
            return True
×
211
        if safe(lambda: value < value_c):
×
212
            return True
×
213
        message_l = _to_locale(message) if locale else message
×
214
        raise exceptions.ValidationInternalError(name, message_l % str(value_c))
×
215

216
    return validation
×
217

218

219
def lte(name, value_c, message="must be less than or equal to %s", locale=True):
2✔
220
    def validation(object, ctx):
2✔
221
        value = object.get(name, None)
2✔
222
        if value == None:
2✔
223
            return True
2✔
224
        if safe(lambda: value <= value_c):
2✔
225
            return True
2✔
226
        message_l = _to_locale(message) if locale else message
227
        raise exceptions.ValidationInternalError(name, message_l % str(value_c))
2✔
228

229
    return validation
230

2✔
231

2✔
232
def not_null(name, message="value is not set", locale=True):
2✔
233
    def validation(object, ctx):
2✔
234
        value = object.get(name, None)
2✔
235
        if not value == None:
2✔
236
            return True
2✔
237
        message_l = _to_locale(message) if locale else message
×
238
        raise exceptions.ValidationInternalError(name, message_l)
×
239

240
    return validation
2✔
241

242

243
def not_empty(name, message="value is empty", locale=True):
2✔
244
    def validation(object, ctx):
×
245
        value = object.get(name, None)
×
246
        if value == None:
×
247
            return True
×
248
        if len(value):
×
249
            return True
×
250
        message_l = _to_locale(message) if locale else message
×
251
        raise exceptions.ValidationInternalError(name, message_l)
×
252

253
    return validation
×
254

255

256
def not_false(name, message="value is false", locale=True):
2✔
257
    def validation(object, ctx):
×
258
        value = object.get(name, None)
×
259
        if value == None:
×
260
            return True
×
261
        if not value == False:
×
262
            return True
×
263
        message_l = _to_locale(message) if locale else message
×
264
        raise exceptions.ValidationInternalError(name, message_l)
×
265

266
    return validation
×
267

268

269
def is_in(name, values, message="value is not in set", locale=True):
2✔
270
    def validation(object, ctx):
×
271
        value = object.get(name, None)
×
272
        if value == None:
×
273
            return True
×
274
        if value in values:
×
275
            return True
×
276
        message_l = _to_locale(message) if locale else message
×
277
        raise exceptions.ValidationInternalError(name, message_l)
×
278

×
279
    return validation
×
280

281

×
282
def is_upper(name, message="value contains lower cased characters", locale=True):
283
    def validation(object, ctx):
284
        value = object.get(name, None)
2✔
285
        if value == None:
×
286
            return True
×
287
        if value == "":
×
288
            return True
×
289
        if value == value.upper():
×
290
            return True
×
291
        message_l = _to_locale(message) if locale else message
×
292
        raise exceptions.ValidationInternalError(name, message_l)
×
293

×
294
    return validation
×
295

296

×
297
def is_lower(name, message="value contains upper cased characters", locale=True):
298
    def validation(object, ctx):
299
        value = object.get(name, None)
2✔
300
        if value == None:
×
301
            return True
×
302
        if value == "":
×
303
            return True
×
304
        if value == value.lower():
×
305
            return True
×
306
        message_l = _to_locale(message) if locale else message
×
307
        raise exceptions.ValidationInternalError(name, message_l)
×
308

×
309
    return validation
×
310

311

×
312
def is_simple(name, message="value contains invalid characters", locale=True):
313
    def validation(object, ctx):
314
        value = object.get(name, None)
2✔
315
        if value == None:
2✔
316
            return True
2✔
317
        if value == "":
2✔
318
            return True
×
319
        if SIMPLE_REGEX.match(value):
2✔
320
            return True
×
321
        message_l = _to_locale(message) if locale else message
2✔
322
        raise exceptions.ValidationInternalError(name, message_l)
2✔
323

2✔
324
    return validation
2✔
325

326

2✔
327
def is_email(name, message="value is not a valid email", locale=True):
328
    def validation(object, ctx):
329
        value = object.get(name, None)
2✔
330
        if value == None:
×
331
            return True
×
332
        if value == "":
×
333
            return True
×
334
        if EMAIL_REGEX.match(value):
×
335
            return True
×
336
        message_l = _to_locale(message) if locale else message
×
337
        raise exceptions.ValidationInternalError(name, message_l)
×
338

×
339
    return validation
×
340

341

×
342
def is_url(name, message="value is not a valid URL", locale=True):
343
    def validation(object, ctx):
344
        value = object.get(name, None)
2✔
345
        if value == None:
×
346
            return True
×
347
        if value == "":
×
348
            return True
×
349
        if URL_REGEX.match(value):
×
350
            return True
×
351
        message_l = _to_locale(message) if locale else message
×
352
        raise exceptions.ValidationInternalError(name, message_l)
×
353

×
354
    return validation
×
355

×
356

357
def is_regex(name, regex, message="value has incorrect format", locale=True):
×
358
    def validation(object, ctx):
359
        value = object.get(name, None)
360
        if value == None:
2✔
361
            return True
×
362
        if value == "":
×
363
            return True
×
364
        match = re.match(regex, value)
×
365
        if match:
×
366
            return True
×
367
        message_l = _to_locale(message) if locale else message
×
368
        raise exceptions.ValidationInternalError(name, message_l)
×
369

×
370
    return validation
×
371

×
372

373
def field_eq(name, field, message="must be equal to %s", locale=True):
×
374
    def validation(object, ctx):
375
        name_v = object.get(name, None)
376
        field_v = object.get(field, None)
2✔
377
        if name_v == None:
×
378
            return True
×
379
        if field_v == None:
×
380
            return True
×
381
        if name_v == field_v:
×
382
            return True
×
383
        message_l = _to_locale(message) if locale else message
×
384
        raise exceptions.ValidationInternalError(name, message_l % field)
×
385

×
386
    return validation
×
387

×
388

389
def field_gt(name, field, message="must be greater than %s", locale=True):
×
390
    def validation(object, ctx):
391
        name_v = object.get(name, None)
392
        field_v = object.get(field, None)
2✔
393
        if name_v == None:
×
394
            return True
×
395
        if field_v == None:
×
396
            return True
×
397
        if safe(lambda: name_v > field_v):
×
398
            return True
×
399
        message_l = _to_locale(message) if locale else message
×
400
        raise exceptions.ValidationInternalError(name, message_l % field)
×
401

×
402
    return validation
×
403

×
404

405
def field_gte(name, field, message="must be greater or equal than %s", locale=True):
×
406
    def validation(object, ctx):
407
        name_v = object.get(name, None)
408
        field_v = object.get(field, None)
2✔
409
        if name_v == None:
×
410
            return True
×
411
        if field_v == None:
×
412
            return True
×
413
        if safe(lambda: name_v >= field_v):
×
414
            return True
×
415
        message_l = _to_locale(message) if locale else message
×
416
        raise exceptions.ValidationInternalError(name, message_l % field)
×
417

×
418
    return validation
×
419

×
420

421
def field_lt(name, field, message="must be less than %s", locale=True):
×
422
    def validation(object, ctx):
423
        name_v = object.get(name, None)
424
        field_v = object.get(field, None)
2✔
425
        if name_v == None:
×
426
            return True
×
427
        if field_v == None:
×
428
            return True
×
429
        if safe(lambda: name_v < field_v):
×
430
            return True
×
431
        message_l = _to_locale(message) if locale else message
×
432
        raise exceptions.ValidationInternalError(name, message_l % field)
×
433

×
434
    return validation
×
435

×
436

437
def field_lte(name, field, message="must be less or equal than %s", locale=True):
×
438
    def validation(object, ctx):
439
        name_v = object.get(name, None)
440
        field_v = object.get(field, None)
2✔
441
        if name_v == None:
×
442
            return True
×
443
        if field_v == None:
×
444
            return True
×
445
        if safe(lambda: name_v <= field_v):
×
446
            return True
×
447
        message_l = _to_locale(message) if locale else message
×
448
        raise exceptions.ValidationInternalError(name, message_l % field)
×
449

×
450
    return validation
×
451

452

×
453
def string_gt(name, size, message="must be larger than %d characters", locale=True):
454
    def validation(object, ctx):
455
        value = object.get(name, None)
2✔
456
        if value == None:
2✔
457
            return True
2✔
458
        if value == "":
2✔
459
            return True
×
460
        if len(value) > size:
2✔
461
            return True
×
462
        message_l = _to_locale(message) if locale else message
2✔
463
        raise exceptions.ValidationInternalError(name, message_l % size)
2✔
464

2✔
465
    return validation
2✔
466

467

2✔
468
def string_lt(name, size, message="must be smaller than %d characters", locale=True):
469
    def validation(object, ctx):
470
        value = object.get(name, None)
2✔
471
        if value == None:
×
472
            return True
×
473
        if value == "":
×
474
            return True
×
475
        if len(value) < size:
×
476
            return True
×
477
        message_l = _to_locale(message) if locale else message
×
478
        raise exceptions.ValidationInternalError(name, message_l % size)
×
479

×
480
    return validation
×
481

482

×
483
def string_eq(name, size, message="must be exactly %d characters", locale=True):
484
    def validation(object, ctx):
485
        value = object.get(name, None)
2✔
486
        if value == None:
×
487
            return True
×
488
        if value == "":
×
489
            return True
×
490
        if len(value) == size:
×
491
            return True
×
492
        message_l = _to_locale(message) if locale else message
×
493
        raise exceptions.ValidationInternalError(name, message_l % size)
×
494

×
495
    return validation
×
496

×
497

498
def equals(first_name, second_name, message="value is not equal to %s", locale=True):
×
499
    def validation(object, ctx):
500
        first_value = object.get(first_name, None)
501
        second_value = object.get(second_name, None)
2✔
502
        if first_value == None:
×
503
            return True
×
504
        if second_value == None:
×
505
            return True
×
506
        if first_value == second_value:
×
507
            return True
×
508
        message_l = _to_locale(message) if locale else message
×
509
        raise exceptions.ValidationInternalError(first_name, message_l % second_name)
×
510

511
    return validation
×
512

513

514
def not_past(name, message="date is in the past", locale=True):
2✔
515
    def validation(object, ctx):
2✔
516
        value = object.get(name, None)
2✔
517
        if value == None:
2✔
518
            return True
2✔
519
        if value >= datetime.datetime.utcnow():
2✔
520
            return True
2✔
521
        message_l = _to_locale(message) if locale else message
×
522
        raise exceptions.ValidationInternalError(name, message_l)
2✔
523

2✔
524
    return validation
2✔
525

2✔
526

2✔
527
def not_duplicate(name, collection, message="value is duplicate", locale=True):
2✔
528
    def validation(object, ctx):
2✔
529
        _id = object.get("_id", None)
2✔
530
        value = object.get(name, None)
2✔
531
        if value == None:
532
            return True
2✔
533
        if value == "":
534
            return True
535
        adapter = common.base().get_adapter()
2✔
536
        _collection = adapter.collection(collection)
×
537
        item = _collection.find_one({name: value})
538
        if not item:
539
            return True
540
        if str(item["_id"]) == str(_id):
541
            return True
542
        message_l = _to_locale(message) if locale else message
×
543
        raise exceptions.ValidationInternalError(name, message_l)
×
544

×
545
    return validation
×
546

547

548
def all_different(name, name_ref=None, message="has duplicates", locale=True):
549
    def validation(object, ctx):
550
        # uses the currently provided context to retrieve
551
        # the definition of the name to be validation and
×
552
        # in it's a valid relation type tries to retrieve
×
553
        # the underlying referenced name otherwise default
×
554
        # to the provided one or the id name
×
555
        cls = ctx.__class__
×
556
        definition = cls.definition_n(name)
557
        type = definition.get("type", legacy.UNICODE)
558
        _name_ref = name_ref or (hasattr(type, "_name") and type._name or "id")
559

560
        # tries to retrieve both the value for the identifier
×
561
        # in the current object and the values of the sequence
×
562
        # that is going to be used for all different matching in
563
        # case any of them does not exist returns valid
564
        value = object.get(name, None)
565
        if value == None:
566
            return True
567
        if len(value) == 0:
×
568
            return True
569

570
        # verifies if the sequence is in fact a proxy object and
571
        # contains the ids attribute in case that's the case the
572
        # ids attributes is retrieved as the sequence instead
573
        if hasattr(value, "ids"):
574
            values = value.ids
575

×
576
        # otherwise this is a normal sequence and the it must be
×
577
        # iterates to check if the reference name should be retrieve
×
578
        # or if the concrete values should be used instead
×
579
        else:
×
580
            values = [
581
                getattr(_value, _name_ref) if hasattr(_value, _name_ref) else _value
×
582
                for _value in value
583
            ]
584

2✔
585
        # creates a set structure from the the sequence of values
×
586
        # and in case the size of the sequence and the set are the
587
        # same the sequence is considered to not contain duplicates
588
        values_set = set(values)
589
        if len(value) == len(values_set):
590
            return True
591
        message_l = _to_locale(message) if locale else message
×
592
        raise exceptions.ValidationInternalError(name, message_l)
×
593

×
594
    return validation
×
595

596

597
def no_self(name, name_ref=None, message="contains self", locale=True):
598
    def validation(object, ctx):
599
        # uses the currently provided context to retrieve
600
        # the definition of the name to be validation and
×
601
        # in it's a valid relation type tries to retrieve
×
602
        # the underlying referenced name otherwise default
×
603
        # to the provided one or the id name
×
604
        cls = ctx.__class__
×
605
        definition = cls.definition_n(name)
×
606
        type = definition.get("type", legacy.UNICODE)
607
        _name_ref = name_ref or (hasattr(type, "_name") and type._name or "id")
608

609
        # tries to retrieve both the value for the identifier
610
        # in the current object and the values of the sequence
×
611
        # that is going to be used for existence matching in
×
612
        # case any of them does not exist returns valid
613
        _id = object.get(_name_ref, None)
614
        value = object.get(name, None)
615
        if _id == None:
616
            return True
617
        if value == None:
×
618
            return True
619

620
        # verifies if the sequence is in fact a proxy object and
621
        # contains the ids attribute in case that's the case the
622
        # ids attributes is retrieved as the sequence instead
623
        if hasattr(value, "ids"):
624
            values = value.ids
625

×
626
        # otherwise this is a normal sequence and the it must be
×
627
        # iterates to check if the reference name should be retrieve
×
628
        # or if the concrete values should be used instead
×
629
        else:
×
630
            values = [
631
                getattr(_value, _name_ref) if hasattr(_value, _name_ref) else _value
×
632
                for _value in value
633
            ]
634

2✔
635
        # verifies if the current identifier value exists in the
2✔
636
        # sequence and if that's the case raises the validation
637
        # exception indicating the validation problem
638
        exists = _id in values
639
        if not exists:
640
            return True
641
        message_l = _to_locale(message) if locale else message
642
        raise exceptions.ValidationInternalError(name, message_l)
643

644
    return validation
645

646

647
def _to_locale(value):
648
    return common.base().to_locale(value)
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc