• 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 eq(name, value_c, message="must be equal to %s", locale=True):
2✔
155
    def validation(object, ctx):
×
156
        value = object.get(name, None)
×
157
        if value == None:
×
158
            return True
×
159
        if value == value_c:
×
160
            return True
×
161
        message_l = _to_locale(message) if locale else message
×
162
        raise exceptions.ValidationInternalError(name, message_l % str(value_c))
×
163

164
    return validation
×
165

166

167
def gt(name, value_c, message="must be greater than %s", locale=True):
2✔
168
    def validation(object, ctx):
×
169
        value = object.get(name, None)
×
170
        if value == None:
×
171
            return True
×
172
        if safe(lambda: 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 gte(name, value_c, message="must be greater than or equal to %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 lt(name, value_c, message="must be less than %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 lte(name, value_c, message="must be less than or equal to %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 not_null(name, message="value is not set", locale=True):
2✔
220
    def validation(object, ctx):
2✔
221
        value = object.get(name, None)
2✔
222
        if not value == None:
2✔
223
            return True
2✔
224
        message_l = _to_locale(message) if locale else message
2✔
225
        raise exceptions.ValidationInternalError(name, message_l)
2✔
226

227
    return validation
2✔
228

229

230
def not_empty(name, message="value is empty", locale=True):
2✔
231
    def validation(object, ctx):
2✔
232
        value = object.get(name, None)
2✔
233
        if value == None:
2✔
234
            return True
2✔
235
        if len(value):
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_false(name, message="value is false", locale=True):
2✔
244
    def validation(object, ctx):
×
245
        value = object.get(name, None)
×
246
        if value == None:
×
247
            return True
×
248
        if not value == False:
×
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 is_in(name, values, message="value is not in set", locale=True):
2✔
257
    def validation(object, ctx):
×
258
        value = object.get(name, None)
×
259
        if value == None:
×
260
            return True
×
261
        if value in values:
×
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_upper(name, message="value contains lower cased characters", locale=True):
2✔
270
    def validation(object, ctx):
×
271
        value = object.get(name, None)
×
272
        if value == None:
×
273
            return True
×
274
        if value == "":
×
275
            return True
×
276
        if value == value.upper():
×
277
            return True
×
278
        message_l = _to_locale(message) if locale else message
×
279
        raise exceptions.ValidationInternalError(name, message_l)
×
280

281
    return validation
×
282

283

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

296
    return validation
×
297

298

299
def is_simple(name, message="value contains invalid characters", locale=True):
2✔
300
    def validation(object, ctx):
×
301
        value = object.get(name, None)
×
302
        if value == None:
×
303
            return True
×
304
        if value == "":
×
305
            return True
×
306
        if SIMPLE_REGEX.match(value):
×
307
            return True
×
308
        message_l = _to_locale(message) if locale else message
×
309
        raise exceptions.ValidationInternalError(name, message_l)
×
310

311
    return validation
×
312

313

314
def is_email(name, message="value is not a valid email", locale=True):
2✔
315
    def validation(object, ctx):
2✔
316
        value = object.get(name, None)
2✔
317
        if value == None:
2✔
318
            return True
×
319
        if value == "":
2✔
320
            return True
×
321
        if EMAIL_REGEX.match(value):
2✔
322
            return True
2✔
323
        message_l = _to_locale(message) if locale else message
2✔
324
        raise exceptions.ValidationInternalError(name, message_l)
2✔
325

326
    return validation
2✔
327

328

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

341
    return validation
×
342

343

344
def is_regex(name, regex, message="value has incorrect format", locale=True):
2✔
345
    def validation(object, ctx):
×
346
        value = object.get(name, None)
×
347
        if value == None:
×
348
            return True
×
349
        if value == "":
×
350
            return True
×
351
        match = re.match(regex, value)
×
352
        if match:
×
353
            return True
×
354
        message_l = _to_locale(message) if locale else message
×
355
        raise exceptions.ValidationInternalError(name, message_l)
×
356

357
    return validation
×
358

359

360
def field_eq(name, field, message="must be equal to %s", locale=True):
2✔
361
    def validation(object, ctx):
×
362
        name_v = object.get(name, None)
×
363
        field_v = object.get(field, None)
×
364
        if name_v == None:
×
365
            return True
×
366
        if field_v == None:
×
367
            return True
×
368
        if name_v == field_v:
×
369
            return True
×
370
        message_l = _to_locale(message) if locale else message
×
371
        raise exceptions.ValidationInternalError(name, message_l % field)
×
372

373
    return validation
×
374

375

376
def field_gt(name, field, message="must be greater than %s", locale=True):
2✔
377
    def validation(object, ctx):
×
378
        name_v = object.get(name, None)
×
379
        field_v = object.get(field, None)
×
380
        if name_v == None:
×
381
            return True
×
382
        if field_v == None:
×
383
            return True
×
384
        if safe(lambda: name_v > field_v):
×
385
            return True
×
386
        message_l = _to_locale(message) if locale else message
×
387
        raise exceptions.ValidationInternalError(name, message_l % field)
×
388

389
    return validation
×
390

391

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

405
    return validation
×
406

407

408
def field_lt(name, field, message="must be less than %s", locale=True):
2✔
409
    def validation(object, ctx):
×
410
        name_v = object.get(name, None)
×
411
        field_v = object.get(field, None)
×
412
        if name_v == None:
×
413
            return True
×
414
        if field_v == None:
×
415
            return True
×
416
        if safe(lambda: name_v < field_v):
×
417
            return True
×
418
        message_l = _to_locale(message) if locale else message
×
419
        raise exceptions.ValidationInternalError(name, message_l % field)
×
420

421
    return validation
×
422

423

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

437
    return validation
×
438

439

440
def string_gt(name, size, message="must be larger than %d characters", locale=True):
2✔
441
    def validation(object, ctx):
×
442
        value = object.get(name, None)
×
443
        if value == None:
×
444
            return True
×
445
        if value == "":
×
446
            return True
×
447
        if len(value) > size:
×
448
            return True
×
449
        message_l = _to_locale(message) if locale else message
×
450
        raise exceptions.ValidationInternalError(name, message_l % size)
×
451

452
    return validation
×
453

454

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

467
    return validation
2✔
468

469

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

482
    return validation
×
483

484

485
def equals(first_name, second_name, message="value is not equal to %s", locale=True):
2✔
486
    def validation(object, ctx):
×
487
        first_value = object.get(first_name, None)
×
488
        second_value = object.get(second_name, None)
×
489
        if first_value == None:
×
490
            return True
×
491
        if second_value == None:
×
492
            return True
×
493
        if first_value == second_value:
×
494
            return True
×
495
        message_l = _to_locale(message) if locale else message
×
496
        raise exceptions.ValidationInternalError(first_name, message_l % second_name)
×
497

498
    return validation
×
499

500

501
def not_past(name, message="date is in the past", locale=True):
2✔
502
    def validation(object, ctx):
×
503
        value = object.get(name, None)
×
504
        if value == None:
×
505
            return True
×
506
        if value >= datetime.datetime.utcnow():
×
507
            return True
×
508
        message_l = _to_locale(message) if locale else message
×
509
        raise exceptions.ValidationInternalError(name, message_l)
×
510

511
    return validation
×
512

513

514
def not_duplicate(name, collection, message="value is duplicate", locale=True):
2✔
515
    def validation(object, ctx):
2✔
516
        _id = object.get("_id", None)
2✔
517
        value = object.get(name, None)
2✔
518
        if value == None:
2✔
519
            return True
2✔
520
        if value == "":
2✔
521
            return True
×
522
        adapter = common.base().get_adapter()
2✔
523
        _collection = adapter.collection(collection)
2✔
524
        item = _collection.find_one({name: value})
2✔
525
        if not item:
2✔
526
            return True
2✔
527
        if str(item["_id"]) == str(_id):
2✔
528
            return True
2✔
529
        message_l = _to_locale(message) if locale else message
2✔
530
        raise exceptions.ValidationInternalError(name, message_l)
2✔
531

532
    return validation
2✔
533

534

535
def all_different(name, name_ref=None, message="has duplicates", locale=True):
2✔
536
    def validation(object, ctx):
×
537
        # uses the currently provided context to retrieve
538
        # the definition of the name to be validation and
539
        # in it's a valid relation type tries to retrieve
540
        # the underlying referenced name otherwise default
541
        # to the provided one or the id name
542
        cls = ctx.__class__
×
543
        definition = cls.definition_n(name)
×
544
        type = definition.get("type", legacy.UNICODE)
×
545
        _name_ref = name_ref or (hasattr(type, "_name") and type._name or "id")
×
546

547
        # tries to retrieve both the value for the identifier
548
        # in the current object and the values of the sequence
549
        # that is going to be used for all different matching in
550
        # case any of them does not exist returns valid
551
        value = object.get(name, None)
×
552
        if value == None:
×
553
            return True
×
554
        if len(value) == 0:
×
555
            return True
×
556

557
        # verifies if the sequence is in fact a proxy object and
558
        # contains the ids attribute in case that's the case the
559
        # ids attributes is retrieved as the sequence instead
560
        if hasattr(value, "ids"):
×
561
            values = value.ids
×
562

563
        # otherwise this is a normal sequence and the it must be
564
        # iterates to check if the reference name should be retrieve
565
        # or if the concrete values should be used instead
566
        else:
567
            values = [
×
568
                getattr(_value, _name_ref) if hasattr(_value, _name_ref) else _value
569
                for _value in value
570
            ]
571

572
        # creates a set structure from the the sequence of values
573
        # and in case the size of the sequence and the set are the
574
        # same the sequence is considered to not contain duplicates
575
        values_set = set(values)
×
576
        if len(value) == len(values_set):
×
577
            return True
×
578
        message_l = _to_locale(message) if locale else message
×
579
        raise exceptions.ValidationInternalError(name, message_l)
×
580

581
    return validation
×
582

583

584
def no_self(name, name_ref=None, message="contains self", locale=True):
2✔
585
    def validation(object, ctx):
×
586
        # uses the currently provided context to retrieve
587
        # the definition of the name to be validation and
588
        # in it's a valid relation type tries to retrieve
589
        # the underlying referenced name otherwise default
590
        # to the provided one or the id name
591
        cls = ctx.__class__
×
592
        definition = cls.definition_n(name)
×
593
        type = definition.get("type", legacy.UNICODE)
×
594
        _name_ref = name_ref or (hasattr(type, "_name") and type._name or "id")
×
595

596
        # tries to retrieve both the value for the identifier
597
        # in the current object and the values of the sequence
598
        # that is going to be used for existence matching in
599
        # case any of them does not exist returns valid
600
        _id = object.get(_name_ref, None)
×
601
        value = object.get(name, None)
×
602
        if _id == None:
×
603
            return True
×
604
        if value == None:
×
605
            return True
×
606

607
        # verifies if the sequence is in fact a proxy object and
608
        # contains the ids attribute in case that's the case the
609
        # ids attributes is retrieved as the sequence instead
610
        if hasattr(value, "ids"):
×
611
            values = value.ids
×
612

613
        # otherwise this is a normal sequence and the it must be
614
        # iterates to check if the reference name should be retrieve
615
        # or if the concrete values should be used instead
616
        else:
617
            values = [
×
618
                getattr(_value, _name_ref) if hasattr(_value, _name_ref) else _value
619
                for _value in value
620
            ]
621

622
        # verifies if the current identifier value exists in the
623
        # sequence and if that's the case raises the validation
624
        # exception indicating the validation problem
625
        exists = _id in values
×
626
        if not exists:
×
627
            return True
×
628
        message_l = _to_locale(message) if locale else message
×
629
        raise exceptions.ValidationInternalError(name, message_l)
×
630

631
    return validation
×
632

633

634
def _to_locale(value):
2✔
635
    return common.base().to_locale(value)
2✔
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