Package turbogears :: Module util

Source Code for Module turbogears.util

  1  """The TurboGears utility module.""" 
  2   
  3  _all__ = ['Bunch', 'DictObj', 'DictWrapper', 'Enum', 'setlike', 
  4     'get_package_name', 'get_model', 'load_project_config', 
  5     'ensure_sequence', 'has_arg', 'to_kw', 'from_kw', 'adapt_call', 
  6     'call_on_stack', 'remove_keys', 'arg_index', 
  7     'inject_arg', 'inject_args', 'add_tg_args', 'bind_args', 
  8     'recursive_update', 'combine_contexts', 
  9     'request_available', 'flatten_sequence', 'load_class', 
 10     'parse_http_accept_header', 'simplify_http_accept_header', 
 11     'to_unicode', 'to_utf8', 'quote_cookie', 'unquote_cookie', 
 12     'get_template_encoding_default', 'get_mime_type_for_format', 
 13     'mime_type_has_charset', 'find_precision', 'copy_if_mutable', 
 14     'match_ip', 'deprecated'] 
 15   
 16  import os 
 17  import sys 
 18  import re 
 19  import logging 
 20  import warnings 
 21  import htmlentitydefs 
 22  import socket 
 23  import struct 
 24  from inspect import getargspec, getargvalues 
 25  from itertools import izip, islice, chain 
 26  from operator import isSequenceType 
 27  from Cookie import _quote as quote_cookie, _unquote as unquote_cookie 
 28   
 29  import pkg_resources 
 30   
 31  from cherrypy import request 
 32   
 33  from turbogears.decorator import decorator 
 34  from turbogears import config 
35 36 37 -def deprecated(message=None):
38 """Decorator which can be used to mark functions as deprecated. 39 40 It will result in a warning being emitted when the function is used. 41 42 Inspired by http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/391367 43 44 """ 45 def decorate(func): 46 if not decorate.message: 47 decorate.message = ("Call to deprecated function %s." 48 % func.__name__) 49 def new_func(*args, **kwargs): 50 if not decorate.warned: 51 warnings.warn(decorate.message, category=DeprecationWarning, 52 stacklevel=2) 53 decorate.warned = True 54 return func(*args, **kwargs)
55 new_func.__name__ = func.__name__ 56 new_func.__doc__ = func.__doc__ 57 new_func.__dict__.update(func.__dict__) 58 return new_func 59 decorate.message = message 60 decorate.warned = False 61 return decorate 62
63 64 -def missing_dependency_error(name=None):
65 msg = """\ 66 Before you can run this command, you need to install all the project's 67 dependencies by running "python setup.py develop" in the project directory, or 68 you can install the application with "python setup.py install", or build an egg 69 with "python setup.py bdist_egg" and install it with "easy_install dist/<egg>". 70 71 If you are stuck, visit http://docs.turbogears.org/GettingHelp for support.""" 72 if name: 73 msg = ("This project requires the %s package but it could not be " 74 "found.\n\n" % name) + msg 75 return msg
76
77 78 -class Bunch(dict):
79 """Simple but handy collector of a bunch of named stuff.""" 80
81 - def __repr__(self):
82 keys = self.keys() 83 keys.sort() 84 args = ', '.join(['%s=%r' % (key, self[key]) for key in keys]) 85 return '%s(%s)' % (self.__class__.__name__, args)
86
87 - def __getattr__(self, name):
88 try: 89 return self[name] 90 except KeyError: 91 raise AttributeError(name)
92 93 __setattr__ = dict.__setitem__ 94
95 - def __delattr__(self, name):
96 try: 97 del self[name] 98 except KeyError: 99 raise AttributeError(name)
100
101 102 -class DictObj(Bunch):
103 104 @deprecated("Use Bunch instead of DictObj and DictWrapper.")
105 - def __init__(self, *args, **kw):
106 super(DictObj, self).__init__(*args, **kw)
107 108 DictWrapper = DictObj
109 110 111 -def Enum(*names):
112 """True immutable symbolic enumeration with qualified value access. 113 114 Written by Zoran Isailovski: 115 http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/413486 116 117 """ 118 119 # Uncomment the following check if you don't like empty enums. 120 # if not names: 121 # raise ValueError("Empty enums are not supported") 122 123 class EnumClass(object): 124 __slots__ = names 125 def __iter__(self): 126 return iter(constants)
127 def __len__(self): 128 return len(constants) 129 def __getitem__(self, i): 130 return constants[i] 131 def __repr__(self): 132 return 'Enum' + str(names) 133 def __str__(self): 134 return 'enum ' + str(constants) 135 136 enumType = EnumClass() 137 138 class EnumValue(object): 139 __slots__ = ('__value') 140 def __init__(self, value): 141 self.__value = value 142 Value = property(lambda self: self.__value) 143 EnumType = property(lambda self: enumType) 144 def __hash__(self): 145 return hash(self.__value) 146 def __cmp__(self, other): 147 # C fans might want to remove the following check 148 # to make all enums comparable by ordinal value 149 if not (isinstance(other, EnumValue) 150 and self.EnumType is other.EnumType): 151 raise TypeError("Only values from the same enum are comparable") 152 return cmp(self.__value, other.__value) 153 def __invert__(self): 154 return constants[maximum - self.__value] 155 def __nonzero__(self): 156 return bool(self.__value) 157 def __repr__(self): 158 return str(names[self.__value]) 159 160 maximum = len(names) - 1 161 constants = [None] * len(names) 162 for i, each in enumerate(names): 163 val = EnumValue(i) 164 setattr(EnumClass, each, val) 165 constants[i] = val 166 constants = tuple(constants) 167 return enumType 168
169 170 -class setlike(list):
171 """Set preserving item order.""" 172
173 - def add(self, item):
174 if item not in self: 175 self.append(item)
176
177 - def add_all(self, iterable):
178 for item in iterable: 179 self.add(item)
180
181 182 -def get_project_meta(name):
183 """Get egg-info file with that name in the current project.""" 184 for dirname in os.listdir('./'): 185 if dirname.lower().endswith('egg-info'): 186 fname = os.path.join(dirname, name) 187 return fname
188
189 190 -def get_project_config():
191 """Try to select appropriate project configuration file.""" 192 return os.path.exists('setup.py') and 'dev.cfg' or 'prod.cfg'
193
194 195 -def load_project_config(configfile=None):
196 """Try to update the project settings from the config file specified. 197 198 If configfile is C{None}, uses L{get_project_config} to locate one. 199 200 """ 201 if configfile is None: 202 configfile = get_project_config() 203 if not os.path.isfile(configfile): 204 print 'Config file %s not found or is not a file.' % ( 205 os.path.abspath(configfile),) 206 sys.exit() 207 package = get_package_name() 208 config.update_config(configfile=configfile, modulename=package + '.config')
209
210 211 -def get_package_name():
212 """Try to find out the package name of the current directory.""" 213 package = config.get('package') 214 if package: 215 return package 216 if hasattr(sys, 'argv') and "--egg" in sys.argv: 217 projectname = sys.argv[sys.argv.index("--egg")+1] 218 egg = pkg_resources.get_distribution(projectname) 219 top_level = egg._get_metadata("top_level.txt") 220 else: 221 fname = get_project_meta('top_level.txt') 222 top_level = fname and open(fname) or [] 223 for package in top_level: 224 package = package.rstrip() 225 if package and package != 'locales': 226 return package
227
228 229 -def get_project_name():
230 pkg_info = get_project_meta('PKG-INFO') 231 if pkg_info: 232 name = list(open(pkg_info))[1][6:-1] 233 return name.strip()
234
235 236 -def get_model():
237 package_name = get_package_name() 238 if not package_name: 239 return None 240 package = __import__(package_name, {}, {}, ["model"]) 241 if hasattr(package, "model"): 242 return package.model
243
244 245 -def ensure_sequence(obj):
246 """Construct a sequence from object.""" 247 if obj is None: 248 return [] 249 elif isSequenceType(obj): 250 return obj 251 else: 252 return [obj]
253
254 255 -def to_kw(func, args, kw, start=0):
256 """Convert all applicable arguments to keyword arguments.""" 257 argnames, defaults = getargspec(func)[::3] 258 defaults = ensure_sequence(defaults) 259 kv_pairs = izip( 260 islice(argnames, start, len(argnames) - len(defaults)), args) 261 for k, v in kv_pairs: 262 kw[k] = v 263 return args[len(argnames)-len(defaults)-start:], kw
264
265 266 -def from_kw(func, args, kw, start=0):
267 """Extract named positional arguments from keyword arguments.""" 268 argnames, defaults = getargspec(func)[::3] 269 defaults = ensure_sequence(defaults) 270 newargs = [kw.pop(name) for name in islice(argnames, start, 271 len(argnames) - len(defaults)) if name in kw] 272 newargs.extend(args) 273 return newargs, kw
274
275 276 -def adapt_call(func, args, kw, start=0):
277 """Remove unsupported func arguments from given args list and kw dict. 278 279 @param func: the callable to inspect for supported arguments 280 @type func: callable 281 282 @param args: the names of the positional arguments intended to be passed 283 to func 284 @type args: list 285 286 @param kw: the keyword arguments intended to be passed to func 287 @type kw: dict 288 289 @keyparam start: the number of items from the start of the argument list of 290 func to disregard. Set start=1 to use adapt_call on a bound method to 291 disregard the implicit self argument. 292 293 @type start: int 294 295 Returns args list and kw dict from which arguments unsupported by func 296 have been removed. The passed in kw dict is also stripped as a side-effect. 297 The returned objects can then be used to call the target function. 298 299 Example: 300 301 def myfunc(arg1, arg2, kwarg1='foo'): 302 pass 303 304 args, kw = adapt_call(myfunc, ['args1, 'bogus1'], 305 {'kwargs1': 'bar', 'bogus2': 'spamm'}) 306 # --> ['args1'], {'kwargs1': 'bar'} 307 myfunc(*args, **kw) 308 309 """ 310 argnames, varargs, kwargs = getargspec(func)[:3] 311 del argnames[:start] 312 if kwargs in (None, "_decorator__kwargs"): 313 remove_keys(kw, [key for key in kw if key not in argnames]) 314 if varargs in (None, "_decorator__varargs"): 315 args = args[:len(argnames)] 316 for n, key in enumerate(argnames): 317 if key in kw: 318 args = args[:n] 319 break 320 return args, kw
321
322 323 -def call_on_stack(func_name, kw, start=0):
324 """Check if a call to function matching pattern is on stack.""" 325 try: 326 frame = sys._getframe(start+1) 327 except ValueError: 328 return False 329 while frame.f_back: 330 frame = frame.f_back 331 if frame.f_code.co_name == func_name: 332 args = getargvalues(frame)[3] 333 for key in kw.iterkeys(): 334 try: 335 if kw[key] != args[key]: 336 break 337 except (KeyError, TypeError): 338 break 339 else: 340 return True 341 return False
342
343 344 -def remove_keys(dict_, seq):
345 """Gracefully remove keys from dict.""" 346 for key in seq: 347 dict_.pop(key, None) 348 return dict_
349
350 351 -def has_arg(func, argname):
352 """Check whether function has argument.""" 353 return argname in getargspec(func)[0]
354
355 356 -def arg_index(func, argname):
357 """Find index of argument as declared for given function.""" 358 argnames = getargspec(func)[0] 359 if has_arg(func, argname): 360 return argnames.index(argname) 361 else: 362 return None
363
364 365 -def inject_arg(func, argname, argval, args, kw, start=0):
366 """Insert argument into call.""" 367 argnames, defaults = getargspec(func)[::3] 368 defaults = ensure_sequence(defaults) 369 pos = arg_index(func, argname) 370 if pos is None or pos > len(argnames) - len(defaults) - 1: 371 kw[argname] = argval 372 else: 373 pos -= start 374 args = tuple(chain(islice(args, pos), (argval,), 375 islice(args, pos, None))) 376 return args, kw
377
378 379 -def inject_args(func, injections, args, kw, start=0):
380 """Insert arguments into call.""" 381 for argname, argval in injections.iteritems(): 382 args, kw = inject_arg(func, argname, argval, args, kw, start) 383 return args, kw
384
385 386 -def inject_call(func, injections, *args, **kw):
387 """Insert arguments and call.""" 388 args, kw = inject_args(func, injections, args, kw) 389 return func(*args, **kw)
390
391 392 -def add_tg_args(func, args):
393 """Add hint for special arguments that shall not be removed.""" 394 try: 395 tg_args = func._tg_args 396 except AttributeError: 397 tg_args = set() 398 tg_args.update(args) 399 func._tg_args = tg_args
400
401 402 -def bind_args(**add):
403 """Call with arguments set to a predefined value.""" 404 def entagle(func): 405 return lambda func, *args, **kw: inject_call(func, add, *args, **kw)
406 407 def make_decorator(func): 408 argnames, varargs, kwargs, defaults = getargspec(func) 409 defaults = list(ensure_sequence(defaults)) 410 defaults = [d for d in defaults if 411 argnames[-len(defaults) + defaults.index(d)] not in add] 412 argnames = [arg for arg in argnames if arg not in add] 413 return decorator(entagle, (argnames, varargs, kwargs, defaults))(func) 414 415 return make_decorator 416
417 418 -def recursive_update(to_dict, from_dict):
419 """Recursively update all dicts in to_dict with values from from_dict.""" 420 # probably slow as hell :( should be optimized somehow... 421 for k, v in from_dict.iteritems(): 422 if isinstance(v, dict) and isinstance(to_dict[k], dict): 423 recursive_update(to_dict[k], v) 424 else: 425 to_dict[k] = v 426 return to_dict
427
428 429 -def combine_contexts(frames=None, depth=None):
430 """Combine contexts (globals, locals) of frames.""" 431 locals_ = {} 432 globals_ = {} 433 if frames is None: 434 frames = [] 435 if depth is not None: 436 frames.extend([sys._getframe(d+1) for d in depth]) 437 for frame in frames: 438 locals_.update(frame.f_locals) 439 globals_.update(frame.f_globals) 440 return locals_, globals_
441
442 443 -def request_available():
444 """Check if cherrypy.request is available.""" 445 stage = getattr(request, 'stage', None) 446 return stage is not None
447
448 449 -def flatten_sequence(seq):
450 """Flatten sequence.""" 451 for item in seq: 452 if isSequenceType(item) and not isinstance(item, basestring): 453 for item in flatten_sequence(item): 454 yield item 455 else: 456 yield item
457
458 459 -def load_class(dottedpath):
460 """Load a class from a module in dotted-path notation. 461 462 E.g.: load_class("package.module.class"). 463 464 Based on recipe 16.3 from Python Cookbook, 2ed., by Alex Martelli, 465 Anna Martelli Ravenscroft, and David Ascher (O'Reilly Media, 2005) 466 467 """ 468 assert dottedpath is not None, "dottedpath must not be None" 469 splitted_path = dottedpath.split('.') 470 modulename = '.'.join(splitted_path[:-1]) 471 classname = splitted_path[-1] 472 try: 473 try: 474 module = __import__(modulename, globals(), locals(), [classname]) 475 except ValueError: # Py < 2.5 476 if not modulename: 477 module = __import__(__name__.split('.', 1)[0], 478 globals(), locals(), [classname]) 479 except ImportError: 480 # properly log the exception information and return None 481 # to tell caller we did not succeed 482 logging.exception('tg.utils: Could not import %s' 483 ' because an exception occurred', dottedpath) 484 return None 485 try: 486 return getattr(module, classname) 487 except AttributeError: 488 logging.exception('tg.utils: Could not import %s' 489 ' because the class was not found', dottedpath) 490 return None
491
492 493 -def parse_http_accept_header(accept):
494 """Parse an HTTP Accept header (RFC 2616) into a sorted list. 495 496 The quality factors in the header determine the sort order. 497 The values can include possible media-range parameters. 498 This function can also be used for the Accept-Charset, 499 Accept-Encoding and Accept-Language headers. 500 501 """ 502 if accept is None: 503 return [] 504 items = [] 505 for item in accept.split(','): 506 params = item.split(';') 507 for i, param in enumerate(params[1:]): 508 param = param.split('=', 1) 509 if param[0].strip() == 'q': 510 try: 511 q = float(param[1]) 512 if not 0 < q <= 1: 513 raise ValueError 514 except (IndexError, ValueError): 515 q = 0 516 else: 517 item = ';'.join(params[:i+1]) 518 break 519 else: 520 q = 1 521 if q: 522 item = item.strip() 523 if item: 524 items.append((item, q)) 525 items.sort(key=lambda item: -item[1]) 526 return [item[0] for item in items]
527
528 529 -def simplify_http_accept_header(accept):
530 """Parse an HTTP Accept header (RFC 2616) into a preferred value. 531 532 The quality factors in the header determine the preference. 533 Possible media-range parameters are allowed, but will be ignored. 534 This function can also be used for the Accept-Charset, 535 Accept-Encoding and Accept-Language headers. 536 537 This is similar to parse_http_accept_header(accept)[0], but faster. 538 539 """ 540 if accept is None: 541 return None 542 best_item = accept 543 best_q = 0 544 for item in accept.split(','): 545 params = item.split(';') 546 item = params.pop(0) 547 for param in params: 548 param = param.split('=', 1) 549 if param[0].strip() == 'q': 550 try: 551 q = float(param[1]) 552 if not 0 < q <= 1: 553 raise ValueError 554 except (IndexError, ValueError): 555 q = 0 556 break 557 else: 558 q = 1 559 if q > best_q: 560 item = item.strip() 561 if item: 562 best_item = item 563 if q == 1: 564 break 565 best_q = q 566 return best_item
567
568 569 -def to_unicode(value):
570 """Convert encoded string to unicode string. 571 572 Uses get_template_encoding_default() to guess source string encoding. 573 Handles turbogears.i18n.lazystring correctly. 574 575 """ 576 if isinstance(value, str): 577 # try to make sure we won't get UnicodeDecodeError from the template 578 # by converting all encoded strings to Unicode strings 579 try: 580 value = unicode(value) 581 except UnicodeDecodeError: 582 try: 583 value = unicode(value, get_template_encoding_default()) 584 except UnicodeDecodeError: 585 # fail early 586 raise ValueError("Non-unicode string: %r" % value) 587 return value
588
589 590 -def to_utf8(value):
591 """Convert a unicode string to utf-8 encoded plain string. 592 593 Handles turbogears.i18n.lazystring correctly. 594 595 Does nothing to already encoded string. 596 597 """ 598 if isinstance(value, str): 599 pass 600 elif hasattr(value, '__unicode__'): 601 value = unicode(value) 602 if isinstance(value, unicode): 603 value = value.encode('utf-8') 604 return value
605
606 607 -def get_template_encoding_default(engine_name=None):
608 """Return default encoding for template files (Kid, Genshi, etc.).""" 609 if engine_name is None: 610 engine_name = config.get('tg.defaultview', 'genshi') 611 return config.get('%s.encoding' % engine_name, 612 config.get('%s.default_encoding' % engine_name, 'utf-8'))
613 614 615 _format_mime_types = dict( 616 plain='text/plain', text='text/plain', 617 html='text/html', xhtml = 'text/html', # see note below 618 xml='text/xml', json='application/json')
619 620 -def get_mime_type_for_format(format):
621 """Return default MIME media type for a template format. 622 623 Note: By default we are serving xhtml as "text/html" instead of the more 624 correct "application/xhtml+xml", since many browsers, particularly MSIE, 625 do not support this. We are assuming that xhtml means XHTML 1.0 here, 626 where this approach is possible. It would be possible to use some kind 627 of content negotiation to deliver a customized content type, but we avoid 628 this because it causes more harm (e.g. with proxies involved) than good. 629 630 If you want to serve the proper content type (e.g. for XHTML 1.1), 631 set tg.format_mime_types= {'xhtml': 'application/xhtml+xml'}. 632 You can also set a particular content type per controller using the 633 content_type parameter of the expose decorator. 634 635 For detailed information about this issues, see here: 636 http://www.smackthemouse.com/xhtmlxml, http://schneegans.de/web/xhtml/. 637 638 """ 639 mime_type = config.get('tg.format_mime_types', {}).get(format) 640 if not mime_type: 641 mime_type = _format_mime_types.get(format, 'text/html') 642 return mime_type
643
644 645 -def mime_type_has_charset(mime_type):
646 """Return whether the MIME media type supports a charset parameter. 647 648 Note: According to RFC4627, we do not output a charset parameter 649 for "application/json" (this type always uses a UTF encoding). 650 651 """ 652 if not mime_type: 653 return False 654 if mime_type.startswith('text/'): 655 return True 656 if mime_type.startswith('application/'): 657 if mime_type.endswith('/xml') or mime_type.endswith('+xml'): 658 return True 659 if mime_type.endswith('/javascript'): 660 return True 661 return False
662
663 664 -def find_precision(value):
665 """Find precision of some arbitrary value. 666 667 The main intention for this function is to use it together with 668 turbogears.i18n.format.format_decimal() where one has to inform 669 the precision wanted. So, use it like this: 670 671 format_decimal(some_number, find_precision(some_number)) 672 673 """ 674 decimals = '' 675 try: 676 decimals = str(value).split('.', 1)[1] 677 except IndexError: 678 pass 679 return len(decimals)
680
681 682 -def copy_if_mutable(value, feedback=False):
683 """Make a copy of the value if it is mutable. 684 685 Returns the value. If feedback is set to true, also returns 686 whether value was mutable as the second element of a tuple. 687 688 """ 689 if isinstance(value, dict): 690 mutable = True 691 value = value.copy() 692 elif isinstance(value, list): 693 mutable = True 694 value = value[:] 695 else: 696 mutable = False 697 if feedback: 698 return value, mutable 699 else: 700 return value
701
702 703 -def fixentities(htmltext):
704 """Replace HTML character entities with numerical references. 705 706 Note: This won't handle CDATA sections properly. 707 708 """ 709 def repl(matchobj): 710 entity = htmlentitydefs.entitydefs.get(matchobj.group(1).lower()) 711 if not entity: 712 return matchobj.group(0) 713 elif len(entity) == 1: 714 if entity in '&<>\'"': 715 return matchobj.group(0) 716 return '&#%d;' % ord(entity) 717 else: 718 return entity
719 return re.sub('&(\w+);?', repl, htmltext) 720 721 722 if hasattr(socket, 'inet_pton') and hasattr(socket, 'AF_INET6'):
723 724 - def inet6_aton(addr):
725 """Convert IP6 standard hex notation to IP6 address.""" 726 return socket.inet_pton(socket.AF_INET6, addr)
727 728 else: # Windows etc. 729 730 import string 731 _inet6_chars = string.hexdigits + ':.'
732 733 - def inet6_aton(addr):
734 """Convert IPv6 standard hex notation to IPv6 address. 735 736 Inspired by http://twistedmatrix.com/trac/. 737 738 """ 739 faulty = addr.lstrip(_inet6_chars) 740 if faulty: 741 raise ValueError("Illegal character '%c' in IPv6 address" % faulty[0]) 742 parts = addr.split(':') 743 elided = parts.count('') 744 extenso = '.' in parts[-1] and 7 or 8 745 if len(parts) > extenso or elided > 3: 746 raise ValueError("Syntactically invalid IPv6 address") 747 if elided == 3: 748 return '\x00' * 16 749 if elided: 750 zeros = ['0'] * (extenso - len(parts) + elided) 751 if addr.startswith('::'): 752 parts[:2] = zeros 753 elif addr.endswith('::'): 754 parts[-2:] = zeros 755 else: 756 idx = parts.index('') 757 parts[idx:idx+1] = zeros 758 if len(parts) != extenso: 759 raise ValueError("Syntactically invalid IPv6 address") 760 if extenso == 7: 761 ipv4 = parts.pop() 762 if ipv4.count('.') != 3: 763 raise ValueError("Syntactically invalid IPv6 address") 764 parts = [int(x, 16) for x in parts] 765 return struct.pack('!6H', *parts) + socket.inet_aton(ipv4) 766 else: 767 parts = [int(x, 16) for x in parts] 768 return struct.pack('!8H', *parts)
769
770 771 -def inet_aton(addr):
772 """Convert IPv4 or IPv6 notation to IPv6 address.""" 773 if ':' in addr: 774 return inet6_aton(addr) 775 else: 776 return struct.pack('!QL', 0, 0xffff) + socket.inet_aton(addr)
777
778 779 -def _inet_prefix(addr, masked):
780 """Remove the number of masked bits from the IPV6 address.""" 781 hi, lo = struct.unpack("!QQ", addr) 782 return (hi << 64 | lo) >> masked
783
784 785 -def match_ip(cidr, ip):
786 """Check whether IP address matches CIDR IP address block.""" 787 if '/' in cidr: 788 cidr, prefix = cidr.split('/', 1) 789 masked = (':' in cidr and 128 or 32) - int(prefix) 790 else: 791 masked = None 792 cidr = inet_aton(cidr) 793 ip = inet_aton(ip) 794 if masked: 795 cidr = _inet_prefix(cidr, masked) 796 ip = _inet_prefix(ip, masked) 797 return ip == cidr
798