summaryrefslogtreecommitdiff
path: root/cad/src/scratch/api_enforcement.py
blob: 29f7eb32c0dea0c23c51b5a0f5ee347d762b1d81 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# Copyright 2006-2007 Nanorex, Inc.  See LICENSE file for details.
"""
api_enforcement.py - utilities for API enforcement (experimental).

Provides def or class privateMethod, etc.

@author: Will
@version: $Id$
@copyright: 2006-2007 Nanorex, Inc.  See LICENSE file for details.

History:

Experimental code by Will. May have worked, but never extensively used.

Bruce 071107 split it from debug.py into this scratch file.
"""

# REVIEW: might require some imports from debug.py

import sys, os, time, types, traceback
from utilities.constants import noop
import foundation.env as env
from utilities import debug_flags

# ==

API_ENFORCEMENT = False   # for performance, commit this only as False

class APIViolation(Exception):
    pass

# We compare class names to find out whether calls to private methods
# are originating from within the same class (or one of its friends). This
# could give false negatives, if two classes defined in two different places
# have the same name. A work-around would be to use classes as members of
# the "friends" tuple instead of strings. But then we need to do extra
# imports, and that seems to be not only inefficient, but to sometimes
# cause exceptions to be raised.

def _getClassName(frame):
    """
    Given a frame (as returned by sys._getframe(int)), dig into
    the list of local variables' names in this stack frame. If the
    frame represents a method call in an instance of a class, then the
    first local variable name will be "self" or whatever was used instead
    of "self". Use that to index the local variables for the frame and
    get the instance that owns that method. Return the class name of
    the instance.
    See http://docs.python.org/ref/types.html for more details.
    """
    varnames = frame.f_code.co_varnames
    selfname = varnames[0]
    methodOwner = frame.f_locals[selfname]
    return methodOwner.__class__.__name__

def _privateMethod(friends=()):
    """
    Verify that the call made to this method came either from within its
    own class, or from a friendly class which has been granted access. This
    is done by digging around in the Python call stack. The "friends" argument
    should be a tuple of strings, the names of the classes that are considered
    friendly. If no list of friends is given, then any calls from any other
    classes will be flagged as API violations.

    CAVEAT: Detection of API violations is done by comparing only the name of
    the class. (This is due to some messiness I encountered while trying to
    use the actual class object itself, apparently a complication of importing.)
    This means that some violations may not be detected, if we're ever careless
    enough to give two classes the same name.

    ADDITIONAL CAVEAT: Calls from global functions will usually be flagged as API
    violations, and should always be flagged. But this approach will not catch
    all such cases. If the first argument to the function happens to be an
    instance whose class name is the same as the class wherein the private
    method is defined, it won't be caught.
    """
    f1 = sys._getframe(1)
    f2 = sys._getframe(2)
    called = _getClassName(f1)
    caller = _getClassName(f2)
    if caller == called or caller in friends:
        # These kinds of calls are okay.
        return
    # Uh oh, an API violation. Print information that will
    # make it easy to track it down.
    import inspect
    f1 = inspect.getframeinfo(f1)
    f2 = inspect.getframeinfo(f2)
    lineno, meth = f1[1], f1[2]
    lineno2, meth2 = f2[1], f2[2]
    print
    print (called + "." + meth +
           " (line " + repr(lineno) + ")" +
           " is a private method called by")
    print (caller + "." + meth2 +
           " (line " + repr(lineno2) + ")" +
           " in file " + f2[0])
    raise APIViolation

if API_ENFORCEMENT:
    privateMethod = _privateMethod
else:
    # If we're not checking API violations, be as low-impact as possible.
    # In this case 'friends' is ignored.
    def privateMethod(friends = None):
        return

# end