1"""
2
3uritemplate.template
4====================
5
6This module contains the essential inner workings of uritemplate.
7
8What treasures await you:
9
10- URITemplate class
11
12You see a treasure chest of knowledge in front of you.
13What do you do?
14>
15
16"""
17
18import re
19import typing as t
20
21from uritemplate import orderedset
22from uritemplate import variable
23
24template_re = re.compile("{([^}]+)}")
25
26
27def _merge(
28 var_dict: t.Optional[variable.VariableValueDict],
29 overrides: variable.VariableValueDict,
30) -> variable.VariableValueDict:
31 if var_dict:
32 opts = var_dict.copy()
33 opts.update(overrides)
34 return opts
35 return overrides
36
37
38class URITemplate:
39 """This parses the template and will be used to expand it.
40
41 This is the most important object as the center of the API.
42
43 Example::
44
45 from uritemplate import URITemplate
46 import requests
47
48
49 t = URITemplate(
50 'https://api.github.com/users/sigmavirus24/gists{/gist_id}'
51 )
52 uri = t.expand(gist_id=123456)
53 resp = requests.get(uri)
54 for gist in resp.json():
55 print(gist['html_url'])
56
57 Please note::
58
59 str(t)
60 # 'https://api.github.com/users/sigmavirus24/gists{/gistid}'
61 repr(t) # is equivalent to
62 # URITemplate(str(t))
63 # Where str(t) is interpreted as the URI string.
64
65 Also, ``URITemplates`` are hashable so they can be used as keys in
66 dictionaries.
67
68 """
69
70 def __init__(self, uri: str):
71 #: The original URI to be parsed.
72 self.uri: str = uri
73 #: A list of the variables in the URI. They are stored as
74 #: :class:`~uritemplate.variable.URIVariable`\ s
75 self.variables: t.List[variable.URIVariable] = [
76 variable.URIVariable(m.groups()[0])
77 for m in template_re.finditer(self.uri)
78 ]
79 #: A set of variable names in the URI.
80 self.variable_names = orderedset.OrderedSet()
81 for var in self.variables:
82 for name in var.variable_names:
83 self.variable_names.add(name)
84
85 def __repr__(self) -> str:
86 return 'URITemplate("%s")' % self
87
88 def __str__(self) -> str:
89 return self.uri
90
91 def __eq__(self, other: object) -> bool:
92 if not isinstance(other, URITemplate):
93 return NotImplemented
94 return self.uri == other.uri
95
96 def __hash__(self) -> int:
97 return hash(self.uri)
98
99 def _expand(
100 self, var_dict: variable.VariableValueDict, replace: bool
101 ) -> str:
102 if not self.variables:
103 return self.uri
104
105 expansion = var_dict
106 expanded: t.Dict[str, str] = {}
107 for v in self.variables:
108 expanded.update(v.expand(expansion))
109
110 def replace_all(match: "re.Match[str]") -> str:
111 return expanded.get(match.groups()[0], "")
112
113 def replace_partial(match: "re.Match[str]") -> str:
114 match_group = match.groups()[0]
115 var = "{%s}" % match_group
116 return expanded.get(match_group) or var
117
118 replace_func = replace_partial if replace else replace_all
119
120 return template_re.sub(replace_func, self.uri)
121
122 def expand(
123 self,
124 var_dict: t.Optional[variable.VariableValueDict] = None,
125 **kwargs: variable.VariableValue,
126 ) -> str:
127 """Expand the template with the given parameters.
128
129 :param dict var_dict: Optional dictionary with variables and values
130 :param kwargs: Alternative way to pass arguments
131 :returns: str
132
133 Example::
134
135 t = URITemplate('https://api.github.com{/end}')
136 t.expand({'end': 'users'})
137 t.expand(end='gists')
138
139 .. note:: Passing values by both parts, may override values in
140 ``var_dict``. For example::
141
142 expand('https://{var}', {'var': 'val1'}, var='val2')
143
144 ``val2`` will be used instead of ``val1``.
145
146 """
147 return self._expand(_merge(var_dict, kwargs), False)
148
149 def partial(
150 self,
151 var_dict: t.Optional[variable.VariableValueDict] = None,
152 **kwargs: variable.VariableValue,
153 ) -> "URITemplate":
154 """Partially expand the template with the given parameters.
155
156 If all of the parameters for the template are not given, return a
157 partially expanded template.
158
159 :param dict var_dict: Optional dictionary with variables and values
160 :param kwargs: Alternative way to pass arguments
161 :returns: :class:`URITemplate`
162
163 Example::
164
165 t = URITemplate('https://api.github.com{/end}')
166 t.partial() # => URITemplate('https://api.github.com{/end}')
167
168 """
169 return URITemplate(self._expand(_merge(var_dict, kwargs), True))