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

SlowAPI / fast-json-pointer / 3638191208

pending completion
3638191208

push

github

Tristan Sweeney
Set targets for all badge links

28 of 28 relevant lines covered (100.0%)

1.0 hits per line

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

100.0
/src/fast_json_pointer/rfc6901_parser.py
1
'''Implements low-level json pointer parsing. See `RFC 6901 Section 4
1✔
2
<https://www.rfc-editor.org/rfc/rfc6901#section-4>`_ for the specification that this
3
parser adheres to.
4
'''
5

6
import re
1✔
7
from typing import *
1✔
8

9
from .exceptions import ParseException
1✔
10

11
RE_INVALID_ESCAPE = re.compile("(~[^01]|~$)")
1✔
12

13

14
def validate(pointer: str) -> None:
1✔
15
    '''Validate that a string is a well formed json pointer.
16
    
17
    :raises: :exc:`.ParseException`: If json pointer is invalid.
18

19
    >>> validate('') # empty string is fine, means "whole json object"
20
    >>> validate('foo') # parts must lead with '/'
21
    Traceback (most recent call last):
22
    fast_json_pointer.exceptions.ParseException: ...
23
    >>> validate('/foo~') # ~ is the escape char, can't be solo
24
    Traceback (most recent call last):
25
    fast_json_pointer.exceptions.ParseException: ...
26
    >>> validate('/~2/foo') # only ~0, ~1 are valid escapes
27
    Traceback (most recent call last):
28
    fast_json_pointer.exceptions.ParseException: ...
29
    '''
30
    
31
    if len(pointer) > 0 and not pointer.startswith("/"):
1✔
32
        raise ParseException("JSON pointers must be empty or start with '/'")
1✔
33

34
    if match := RE_INVALID_ESCAPE.search(pointer):
1✔
35
        raise ParseException("Found invalid escape {}".format(match.group()))
1✔
36

37

38
def parse(pointer: str) -> list[str]:
1✔
39
    r'''Parse a json pointer into a list of unescaped path parts.
40

41
    :raises: :exc:`.ParseException`: If json pointer is invalid.
42

43
    >>> parse('') # empty string is "the whole json object"
44
    []
45
    >>> parse('/') # keys can be zero-length strings
46
    ['']
47
    >>> parse('/ //  ') # which can look funky
48
    [' ', '', '  ']
49
    >>> parse('/foo/m~0n/a~1b') # ~1 escapes /, ~0 escapes ~
50
    ['foo', 'm~n', 'a/b']
51
    >>> parse('/c%d/e^f') # funky symbols are fine too!
52
    ['c%d', 'e^f']
53
    >>> parse(r'/i\\j/g|h/k\l') # r-string avoids escaping backslashes
54
    ['i\\\\j', 'g|h', 'k\\l']
55
    '''
56
    validate(pointer)
1✔
57

58
    parts = pointer.split("/")
1✔
59
    # discard "empty" str, as "/foo/bar".split() becomes ["", "foo", "bar"]
60
    parts.pop(0) 
1✔
61
    return [unescape(p) for p in parts]
1✔
62

63

64
def unparse(parts: Iterable[str]) -> str:
1✔
65
    r'''Combine an iterable of unescaped path parts into a json pointer.
66
    
67
    >>> unparse([])
68
    ''
69
    >>> unparse([''])
70
    '/'
71
    >>> unparse([' ', '', '  '])
72
    '/ //  '
73
    >>> unparse(['foo', 'm~n', 'a/b'])
74
    '/foo/m~0n/a~1b'
75
    >>> unparse(['c%d', 'e^f'])
76
    '/c%d/e^f'
77
    >>> unparse([r'i\\j', 'g|h', r'k\l'])
78
    '/i\\\\j/g|h/k\\l'
79
    '''
80
    return "".join('/' + escape(part) for part in parts)
1✔
81

82

83
def escape(part: str) -> str:
1✔
84
    '''Escape a path part.
85
    
86
    >>> escape("foo")
87
    'foo'
88
    >>> escape("m~/0")
89
    'm~0~10'
90
    '''
91
    # Escape `~` first! https://www.rfc-editor.org/rfc/rfc6901#section-4
92
    return part.replace("~", "~0").replace("/", "~1")
1✔
93

94

95
def unescape(part: str) -> str:
1✔
96
    '''Unescape a path part.
97
    
98
    >>> unescape("foo")
99
    'foo'
100
    >>> unescape("m~0~10")
101
    'm~/0'
102
    '''
103
    # Unscape `~` last! https://www.rfc-editor.org/rfc/rfc6901#section-4
104
    return part.replace("~1", "/").replace("~0", "~")
1✔
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