1import itertools 2import json 3import pkgutil 4import re 5 6from jsonschema.compat import MutableMapping, str_types, urlsplit 7 8 9class URIDict(MutableMapping): 10 """ 11 Dictionary which uses normalized URIs as keys. 12 """ 13 14 def normalize(self, uri): 15 return urlsplit(uri).geturl() 16 17 def __init__(self, *args, **kwargs): 18 self.store = dict() 19 self.store.update(*args, **kwargs) 20 21 def __getitem__(self, uri): 22 return self.store[self.normalize(uri)] 23 24 def __setitem__(self, uri, value): 25 self.store[self.normalize(uri)] = value 26 27 def __delitem__(self, uri): 28 del self.store[self.normalize(uri)] 29 30 def __iter__(self): 31 return iter(self.store) 32 33 def __len__(self): 34 return len(self.store) 35 36 def __repr__(self): 37 return repr(self.store) 38 39 40class Unset(object): 41 """ 42 An as-of-yet unset attribute or unprovided default parameter. 43 """ 44 45 def __repr__(self): 46 return "<unset>" 47 48 49def load_schema(name): 50 """ 51 Load a schema from ./schemas/``name``.json and return it. 52 """ 53 54 data = pkgutil.get_data("jsonschema", "schemas/{0}.json".format(name)) 55 return json.loads(data.decode("utf-8")) 56 57 58def indent(string, times=1): 59 """ 60 A dumb version of `textwrap.indent` from Python 3.3. 61 """ 62 63 return "\n".join(" " * (4 * times) + line for line in string.splitlines()) 64 65 66def format_as_index(indices): 67 """ 68 Construct a single string containing indexing operations for the indices. 69 70 For example, [1, 2, "foo"] -> [1][2]["foo"] 71 72 Arguments: 73 74 indices (sequence): 75 76 The indices to format. 77 """ 78 79 if not indices: 80 return "" 81 return "[%s]" % "][".join(repr(index) for index in indices) 82 83 84def find_additional_properties(instance, schema): 85 """ 86 Return the set of additional properties for the given ``instance``. 87 88 Weeds out properties that should have been validated by ``properties`` and 89 / or ``patternProperties``. 90 91 Assumes ``instance`` is dict-like already. 92 """ 93 94 properties = schema.get("properties", {}) 95 patterns = "|".join(schema.get("patternProperties", {})) 96 for property in instance: 97 if property not in properties: 98 if patterns and re.search(patterns, property): 99 continue 100 yield property 101 102 103def extras_msg(extras): 104 """ 105 Create an error message for extra items or properties. 106 """ 107 108 if len(extras) == 1: 109 verb = "was" 110 else: 111 verb = "were" 112 return ", ".join(repr(extra) for extra in extras), verb 113 114 115def types_msg(instance, types): 116 """ 117 Create an error message for a failure to match the given types. 118 119 If the ``instance`` is an object and contains a ``name`` property, it will 120 be considered to be a description of that object and used as its type. 121 122 Otherwise the message is simply the reprs of the given ``types``. 123 """ 124 125 reprs = [] 126 for type in types: 127 try: 128 reprs.append(repr(type["name"])) 129 except Exception: 130 reprs.append(repr(type)) 131 return "%r is not of type %s" % (instance, ", ".join(reprs)) 132 133 134def flatten(suitable_for_isinstance): 135 """ 136 isinstance() can accept a bunch of really annoying different types: 137 * a single type 138 * a tuple of types 139 * an arbitrary nested tree of tuples 140 141 Return a flattened tuple of the given argument. 142 """ 143 144 types = set() 145 146 if not isinstance(suitable_for_isinstance, tuple): 147 suitable_for_isinstance = (suitable_for_isinstance,) 148 for thing in suitable_for_isinstance: 149 if isinstance(thing, tuple): 150 types.update(flatten(thing)) 151 else: 152 types.add(thing) 153 return tuple(types) 154 155 156def ensure_list(thing): 157 """ 158 Wrap ``thing`` in a list if it's a single str. 159 160 Otherwise, return it unchanged. 161 """ 162 163 if isinstance(thing, str_types): 164 return [thing] 165 return thing 166 167 168def equal(one, two): 169 """ 170 Check if two things are equal, but evade booleans and ints being equal. 171 """ 172 return unbool(one) == unbool(two) 173 174 175def unbool(element, true=object(), false=object()): 176 """ 177 A hack to make True and 1 and False and 0 unique for ``uniq``. 178 """ 179 180 if element is True: 181 return true 182 elif element is False: 183 return false 184 return element 185 186 187def uniq(container): 188 """ 189 Check if all of a container's elements are unique. 190 191 Successively tries first to rely that the elements are hashable, then 192 falls back on them being sortable, and finally falls back on brute 193 force. 194 """ 195 196 try: 197 return len(set(unbool(i) for i in container)) == len(container) 198 except TypeError: 199 try: 200 sort = sorted(unbool(i) for i in container) 201 sliced = itertools.islice(sort, 1, None) 202 for i, j in zip(sort, sliced): 203 if i == j: 204 return False 205 except (NotImplementedError, TypeError): 206 seen = [] 207 for e in container: 208 e = unbool(e) 209 if e in seen: 210 return False 211 seen.append(e) 212 return True 213