Changeset 64

Show
Ignore:
Timestamp:
07/26/07 22:26:55 (1 year ago)
Author:
edsuom
Message:

Unit tested and debugged resources.projects.APIDocResource; put ProxyResource? into resources.external from which it was inadvertently omitted

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • projects/Twisted-Goodies/trunk/twisted_goodies/simpleserver/http/resources/external.py

    r61 r64  
    3333 
    3434 
    35 class TracResource(WSGIResource): 
     35class TracResource(wsgi.WSGIResource): 
    3636    """ 
    3737    I provide a Trac site for a specified path via a WSGI gateway. 
     
    5050    def __init__(self, path, env={}): 
    5151        self.path, self.env = path, env 
    52         WSGIResource.__init__(self, self.tracApplication) 
     52        wsgi.WSGIResource.__init__(self, self.tracApplication) 
    5353 
    5454    def tracApplication(self, environ, start_response): 
     
    6262 
    6363 
    64 class NevowResource(WSGIResource): 
     64class NevowResource(wsgi.WSGIResource): 
    6565    """ 
    6666    I provide a Nevow site for a specified path via a WSGI gateway. 
     
    7272    def __init__(self, path): 
    7373        self.path = path 
    74         WSGIResource.__init__(self, self.nevowApplication) 
     74        wsgi.WSGIResource.__init__(self, self.nevowApplication) 
    7575 
    7676    def nevowApplication(self, environ, start_response): 
     
    8181        # environ modifications? 
    8282        return nevow.wsgi.WSGIRequest(environ, start_response) 
     83 
     84 
     85class ProxyResource(object): 
     86    """ 
     87    I provide proxied resources from another HTTP server. 
     88 
     89    When the first instance for a particular upstream server is constructed, 
     90    the client protocol for proxy connections to its I{upstreamHost} and 
     91    I{upstreamPort} is created and cached, with an I{SSL} option for securing 
     92    those connections. 
     93    """ 
     94    implements(iweb.IResource) 
     95 
     96    upstreamSpecs = {} 
     97 
     98    def __init__(self, upstreamHost, upstreamPort, SSL=False): 
     99        self.cacheKey = (upstreamHost, upstreamPort, SSL) 
     100        if self.cacheKey not in self.upstreamSpecs: 
     101            thisProtocol = protocol.ClientCreator( 
     102                reactor, chttp.HTTPClientProtocol) 
     103            specs = {'protocol': thisProtocol, 
     104                     'host': upstreamHost, 
     105                     'port': upstreamPort} 
     106            if SSL: 
     107                specs['context'] = ssl.ClientContextFactory() 
     108            self.upstreamSpecs[self.cacheKey] = specs 
     109 
     110    def renderHTTP(self, request): 
     111        """ 
     112        Returns a deferred to the HTTP rendered by forwarding a client version 
     113        of the supplied I{request} to another HTTP server. Any I{:<port} 
     114        substring appearing in the response from that server is removed to 
     115        ensure that further requests are proxied through me. 
     116        """ 
     117        def gotResponse(response): 
     118            location = response.headers.getHeader('location') 
     119            if location is not None: 
     120                newLocation = location.replace(":%d" % specs['port'], "") 
     121                response.headers.setHeader('location', newLocation) 
     122            return response 
     123 
     124        def connected(protocolObject): 
     125            self.uri = request.uri 
     126            newRequest = chttp.ClientRequest( 
     127                request.method, request.uri, request.headers, request.stream) 
     128            return protocolObject.submitRequest( 
     129                newRequest).addCallbacks(gotResponse) 
     130 
     131        specs = self.upstreamSpecs[self.cacheKey] 
     132        if 'context' in specs: 
     133            d = specs['protocol'].connectSSL( 
     134                specs['host'], specs['port'], specs['context']) 
     135        else: 
     136            d = specs['protocol'].connectTCP(specs['host'], specs['port']) 
     137        d.addCallback(connected) 
     138        return d 
     139 
     140    def locateChild(self, request, segments): 
     141        return self, () 
  • projects/Twisted-Goodies/trunk/twisted_goodies/simpleserver/http/resources/projects.py

    r61 r64  
    2323""" 
    2424 
    25 import tempfile, shutil, os.path 
     25import tempfile, shutil, os.path, re 
    2626from twisted.internet import defer, utils 
    2727from twisted.python import procutils 
    2828from twisted.web2.resource import Resource, RedirectResource 
    2929 
     30from basic import StanResource 
     31 
    3032 
    3133class APIDocResource(Resource): 
    3234    """ 
     35    I provide Web access to generated API documentation for projects hosted 
     36    within a particular I{vhostPath}. 
    3337    """ 
     38    re_rev = re.compile(r"Revision:\s+(\d+)") 
     39     
    3440    def __init__(self, vhostPath, projectURLProto, projectRepoProto): 
    35         self.vhostPath = vhostPath 
     41        self.vhostPath = os.path.realpath(vhostPath) 
    3642        for name in ('projectURLProto', 'projectRepoProto'): 
    3743            string = locals()[name] 
     
    4450        if not result: 
    4551            raise ImportError("Can't locate %s executable" % name) 
     52        return result 
     53 
     54    def _packageName(self, projectName): 
     55        return projectName.lower().replace("-", "_") 
     56 
     57    def _repo(self, projectName): 
     58        repo = self.projectRepoProto % projectName 
     59        return os.path.join(repo, self._packageName(projectName)) 
    4660 
    4761    def locateChild(self, request, segments): 
    4862        """ 
     63        Returns a (deferred) instance of L{RedirectResource} that points to a 
     64        particular HTML file containing generated API documentation. If the 
     65        documentation does not yet exist or is out of date, it will be 
     66        (re)generated before the deferred fires. 
    4967        """ 
    5068        def ready(null): 
     
    5472         
    5573        projectName = segments[0] 
    56         packageName = projectName.lower().replace("-", "_") 
    5774        if len(segments) > 1: 
    5875            apiFile = segments[1] 
    5976        else: 
    60             apiFile = "%s.html" % packageName 
     77            apiFile = "%s.html" % self._packageName(projectName) 
    6178        d = defer.maybeDeferred( 
    62             self.ensureDocsPresent, projectName, packageName, apiFile) 
     79            self.ensureDocsPresent, projectName, apiFile) 
    6380        d.addCallback(ready) 
    6481        return d 
    6582 
    66     def ensureDocsPresent(self, projectName, packageName, apiFile): 
     83    def ensureDocsPresent(self, projectName, apiFile): 
    6784        """ 
     85        If the specified I{apiFile} for the project I{projectName} does not 
     86        exist or is out of date, corrects the situation and returns a deferred 
     87        that fires when done.  Otherwise, returns nothing. 
    6888        """ 
    69         filePath = os.path.join(self.vhostPath, 'api', projectName, apiFile) 
    70         if False and not os.path.exists(filePath): 
    71             tmpDir = tempfile.mkdtemp() 
    72             d = self.svn(tmpDir, projectName) 
    73             d.addCallback(lambda _: self.pydoctor(tmpDir, projectName)) 
    74             d.addCallback(lambda _: shutil.rmtree(tmpDir)) 
     89        def compareRevs(currentRev): 
     90            if currentRev != docRev: 
     91                # Tell the world that the docs are at the new rev... 
     92                fh = open(revFile, 'w') 
     93                fh.write(currentRev) 
     94                fh.close() 
     95                # ...then make good on that (just once) 
     96                tmpDir = tempfile.mkdtemp() 
     97                d = self.svnExport(tmpDir, projectName) 
     98                d.addCallback(lambda _: self.pydoctor(tmpDir, projectName)) 
     99                d.addCallback(lambda _: shutil.rmtree(tmpDir)) 
     100                d.addCallback(lambda _: writeNewRev(currentRev)) 
     101                return d 
     102         
     103        revFile = os.path.join(self.vhostPath, 'api', projectName, 'REV') 
     104        if os.path.exists(revFile): 
     105            # The rev for which the current docs were generated 
     106            fh = open(revFile) 
     107            docRev = fh.readline().strip() 
     108            fh.close() 
     109            # Get and compare to the current rev 
     110            d = self.svnRev(projectName) 
     111            d.addCallback(compareRevs) 
    75112            return d 
    76113 
    77     def svn(self, tmpDir, projectName): 
     114    def svnRev(self, projectName): 
    78115        """ 
     116        Returns a deferred that fires with the current revision of the 
     117        I{projectName} project in the SVN repo, i.e., the revision of the 
     118        newest file found from a recursive info command. The revision number is 
     119        provided as a string. 
    79120        """ 
    80         repo = self.projectRepoProto % projectName 
    81         args = [ 
    82             "export", "-q", "--force", 
    83             "%s/%s/trunk" % (repo, projectName)] 
    84         return utils.getProcessValue( 
    85             self._executable('svn'), args=args, path=tmpDir) 
     121        def gotInfo(info): 
     122            maxRev = 0 
     123            for line in [x.strip() for x in info.split("\n")]: 
     124                match = self.re_rev.match(line) 
     125                if match: 
     126                    thisRev = int(match.group(1)) 
     127                    maxRev = max([maxRev, thisRev]) 
     128            return str(maxRev) 
     129         
     130        args = ["info", "-R", self._repo(projectName)] 
     131        d = utils.getProcessOutput(self._executable('svn'), args=args) 
     132        d.addCallback(gotInfo) 
     133        return d 
    86134 
    87     def pydoctor(self, tmpDir, vhostPath, projectName): 
     135    def svnExport(self, tmpDir, projectName): 
    88136        """ 
     137        Exports to the specified I{tmpDir} a copy of the SVN repo for the 
     138        I{projectName} project. Returns a deferred that fires when the export 
     139        is done. 
     140        """ 
     141        repo = self._repo(projectName) 
     142        packageDir = os.path.realpath( 
     143            os.path.join(tmpDir, self._packageName(projectName))) 
     144        args = ["export", "-q", "--force", repo, packageDir] 
     145        return utils.getProcessValue(self._executable('svn'), args=args) 
     146 
     147    def pydoctor(self, tmpDir, projectName): 
     148        """ 
     149        Generates API documentation for the package corresponding to the 
     150        specified I{projectName}, which must be present in a specified 
     151        I{tmpDir}. The resulting HTML documents will appear in a subdirectory 
     152        C{api/<projectName>} of my vhost path after the returned deferred fires. 
    89153        """ 
    90154        url = self.projectURLProto % projectName 
    91155        args = [ 
    92             "--add-package=%s" % packageName
     156            "--make-html"
    93157            "--project-name=%s" % projectName, 
    94             "--make-html"
     158            "--add-package=%s" % self._packageName(projectName)
    95159            "--html-output=%s/api/%s" % (self.vhostPath, projectName), 
    96             "--project-url=%s%s" % (url, projectName)
     160            "--project-url=%s" % url
    97161        return utils.getProcessValue( 
    98162            self._executable('pydoctor'), args=args, path=tmpDir) 
     
    101165class DownloadResource(StanResource): 
    102166    """ 
     167    I provide a page of HTML that shows the different ways to download projects 
     168    hosted within a particular I{vhostPath}. 
    103169    """ 
    104170    addSlash = True 
     
    121187    def locateChild(self, request, segments): 
    122188        self.projectName = segments[0] 
    123         self.title = "How to Download %s" % projectName 
     189        self.title = "How to Download %s" % self.projectName 
    124190        if len(segments) == 1: 
    125191            return self, () 
  • projects/Twisted-Goodies/trunk/twisted_goodies/simpleserver/http/resources/test/test_projects.py

    r63 r64  
    2323""" 
    2424 
    25 import tempfile 
     25import os, os.path, shutil, tempfile 
    2626from twisted.trial.unittest import TestCase 
    2727 
     
    2929 
    3030 
    31 class Test_APIDocResource(TestCase): 
     31class BaseTC(TestCase): 
     32    def _get_vhostPath(self): 
     33        if not hasattr(self, '_vhostPath'): 
     34            self._vhostPath = "test.com" 
     35            self.dirs = [] 
     36            self.mkdir() 
     37        return self._vhostPath 
     38    vhostPath = property(_get_vhostPath) 
     39     
     40    def mkdir(self, *pathParts): 
     41        dirPath = os.path.join(self.vhostPath, *pathParts) 
     42        if not os.path.exists(dirPath): 
     43            os.makedirs(dirPath) 
     44            self.dirs.append(dirPath) 
     45        return dirPath 
     46 
     47    def tearDown(self): 
     48        dirs = getattr(self, 'dirs', []) 
     49        while dirs: 
     50            dirPath = dirs.pop() 
     51            if os.path.exists(dirPath): 
     52                shutil.rmtree(dirPath) 
     53             
     54 
     55class Test_APIDocResource(BaseTC): 
    3256    def setUp(self): 
    33         # TODO 
    34         #self.vhostPath = ... 
    3557        self.projectURLProto = "/trac/%s/wiki" 
    3658        self.projectRepoProto = "svn://tellectual.com/foss/projects/%s/trunk" 
     
    3860            self.vhostPath, self.projectURLProto, self.projectRepoProto) 
    3961 
     62    def test_svnRev(self): 
     63        def gotRev(rev): 
     64            self.failUnless(rev.isdigit()) 
     65            self.failUnless(int(rev) >= 63) 
     66         
     67        return self.rsrc.svnRev("Twisted-Goodies").addCallback(gotRev) 
     68 
     69    def test_svnExport(self): 
     70        def done(errCode): 
     71            self.failUnlessEqual(errCode, 0) 
     72            self.failUnless('twisted_goodies' in os.listdir(tmpDir)) 
     73         
     74        tmpDir = self.mkdir("tmp") 
     75        return self.rsrc.svnExport(tmpDir, "Twisted-Goodies").addCallback(done) 
     76 
     77    def test_pydoctor(self): 
     78        def done(errCode): 
     79            # pydoctor complains a lot 
     80            # self.failUnlessEqual(errCode, 0) 
     81            apiSubDir = os.path.join(apiDir, "Twisted-Goodies") 
     82            self.failUnless('twisted_goodies.html' in os.listdir(apiSubDir)) 
     83         
     84        apiDir = self.mkdir("api") 
     85        tmpDir = self.mkdir("tmp") 
     86        d = self.rsrc.svnExport(tmpDir, "Twisted-Goodies") 
     87        d.addCallback(lambda _: self.rsrc.pydoctor(tmpDir, "Twisted-Goodies")) 
     88        d.addCallback(done) 
     89        return d 
     90