Revision a464ce71

b/lib/ht.py
292 292
            compat.all(val_type(v) for v in container.values()))
293 293

  
294 294
  return desc(TAnd(TDict, fn))
295

  
296

  
297
def _TStrictDictCheck(require_all, exclusive, items, val):
298
  """Helper function for L{TStrictDict}.
299

  
300
  """
301
  notfound_fn = lambda _: not exclusive
302

  
303
  if require_all and not frozenset(val.keys()).issuperset(items.keys()):
304
    # Requires items not found in value
305
    return False
306

  
307
  return compat.all(items.get(key, notfound_fn)(value)
308
                    for (key, value) in val.items())
309

  
310

  
311
def TStrictDict(require_all, exclusive, items):
312
  """Strict dictionary check with specific keys.
313

  
314
  @type require_all: boolean
315
  @param require_all: Whether all keys in L{items} are required
316
  @type exclusive: boolean
317
  @param exclusive: Whether only keys listed in L{items} should be accepted
318
  @type items: dictionary
319
  @param items: Mapping from key (string) to verification function
320

  
321
  """
322
  descparts = ["Dictionary containing"]
323

  
324
  if exclusive:
325
    descparts.append(" none but the")
326

  
327
  if require_all:
328
    descparts.append(" required")
329

  
330
  if len(items) == 1:
331
    descparts.append(" key ")
332
  else:
333
    descparts.append(" keys ")
334

  
335
  descparts.append(utils.CommaJoin("\"%s\" (value %s)" % (key, value)
336
                                   for (key, value) in items.items()))
337

  
338
  desc = WithDesc("".join(descparts))
339

  
340
  return desc(TAnd(TDict,
341
                   compat.partial(_TStrictDictCheck, require_all, exclusive,
342
                                  items)))
b/test/ganeti.ht_unittest.py
187 187
    self.assertFalse(fn({"x": None}))
188 188
    self.assertFalse(fn({"": 8234}))
189 189

  
190
  def testStrictDictRequireAllExclusive(self):
191
    fn = ht.TStrictDict(True, True, { "a": ht.TInt, })
192
    self.assertFalse(fn(1))
193
    self.assertFalse(fn(None))
194
    self.assertFalse(fn({}))
195
    self.assertFalse(fn({"a": "Hello", }))
196
    self.assertFalse(fn({"unknown": 999,}))
197
    self.assertFalse(fn({"unknown": None,}))
198

  
199
    self.assertTrue(fn({"a": 123, }))
200
    self.assertTrue(fn({"a": -5, }))
201

  
202
    fn = ht.TStrictDict(True, True, { "a": ht.TInt, "x": ht.TString, })
203
    self.assertFalse(fn({}))
204
    self.assertFalse(fn({"a": -5, }))
205
    self.assertTrue(fn({"a": 123, "x": "", }))
206
    self.assertFalse(fn({"a": 123, "x": None, }))
207

  
208
  def testStrictDictExclusive(self):
209
    fn = ht.TStrictDict(False, True, { "a": ht.TInt, "b": ht.TList, })
210
    self.assertTrue(fn({}))
211
    self.assertTrue(fn({"a": 123, }))
212
    self.assertTrue(fn({"b": range(4), }))
213
    self.assertFalse(fn({"b": 123, }))
214

  
215
    self.assertFalse(fn({"foo": {}, }))
216
    self.assertFalse(fn({"bar": object(), }))
217

  
218
  def testStrictDictRequireAll(self):
219
    fn = ht.TStrictDict(True, False, { "a": ht.TInt, "m": ht.TInt, })
220
    self.assertTrue(fn({"a": 1, "m": 2, "bar": object(), }))
221
    self.assertFalse(fn({}))
222
    self.assertFalse(fn({"a": 1, "bar": object(), }))
223
    self.assertFalse(fn({"a": 1, "m": [], "bar": object(), }))
224

  
225
  def testStrictDict(self):
226
    fn = ht.TStrictDict(False, False, { "a": ht.TInt, })
227
    self.assertTrue(fn({}))
228
    self.assertFalse(fn({"a": ""}))
229
    self.assertTrue(fn({"a": 11}))
230
    self.assertTrue(fn({"other": 11}))
231
    self.assertTrue(fn({"other": object()}))
232

  
190 233

  
191 234
if __name__ == "__main__":
192 235
  testutils.GanetiTestProgram()

Also available in: Unified diff