comparison mercurial/hgweb/webcommands.py @ 36880:67fb0dca29bc

hgweb: always return iterable from @webcommand functions (API) We had to hack up this function to support our transition to the new response API. Now that we're done with the transition (!!), we can return to returning an iterator of content chunks from these functions. It is tempting to return a normal object and not a generator. However, as the keyword extension demonstrates, extensions may wish to wrap commands and have a try..finally block around execution. Since there is a generator producing content and that generator could be executing code, the try..finally needs to live for as long as the generator is running. That means we have to return a generator so wrappers can consume the generator inside a try..finally. .. api:: hgweb @webcommand functions must use the new response object passed in via ``web.res`` to initiate sending of a response. The hgweb WSGI application will no longer start sending the response automatically. Differential Revision: https://phab.mercurial-scm.org/D2796
author Gregory Szorc <gregory.szorc@gmail.com>
date Sat, 10 Mar 2018 20:35:35 -0800
parents 97f44b0720e2
children 66f62d120ba2
comparison
equal deleted inserted replaced
36879:9675147aec06 36880:67fb0dca29bc
55 When called, functions receive as arguments a ``requestcontext``, 55 When called, functions receive as arguments a ``requestcontext``,
56 ``wsgirequest``, and a templater instance for generatoring output. 56 ``wsgirequest``, and a templater instance for generatoring output.
57 The functions should populate the ``rctx.res`` object with details 57 The functions should populate the ``rctx.res`` object with details
58 about the HTTP response. 58 about the HTTP response.
59 59
60 The function can return the ``requestcontext.res`` instance to signal 60 The function returns a generator to be consumed by the WSGI application.
61 that it wants to use this object to generate the response. If an iterable 61 For most commands, this should be the result from
62 is returned, the ``wsgirequest`` instance will be used and the returned 62 ``web.res.sendresponse()``.
63 content will constitute the response body. ``True`` can be returned to
64 indicate that the function already sent output and the caller doesn't
65 need to do anything more to send the response.
66 63
67 Usage: 64 Usage:
68 65
69 @webcommand('mycommand') 66 @webcommand('mycommand')
70 def mycommand(web, req, tmpl): 67 def mycommand(web, req, tmpl):
133 web.res.headers['Content-Type'] = mt 130 web.res.headers['Content-Type'] = mt
134 filename = (path.rpartition('/')[-1] 131 filename = (path.rpartition('/')[-1]
135 .replace('\\', '\\\\').replace('"', '\\"')) 132 .replace('\\', '\\\\').replace('"', '\\"'))
136 web.res.headers['Content-Disposition'] = 'inline; filename="%s"' % filename 133 web.res.headers['Content-Disposition'] = 'inline; filename="%s"' % filename
137 web.res.setbodybytes(text) 134 web.res.setbodybytes(text)
138 return web.res 135 return web.res.sendresponse()
139 136
140 def _filerevision(web, req, tmpl, fctx): 137 def _filerevision(web, req, tmpl, fctx):
141 f = fctx.path() 138 f = fctx.path()
142 text = fctx.data() 139 text = fctx.data()
143 parity = paritygen(web.stripecount) 140 parity = paritygen(web.stripecount)
163 rename=webutil.renamelink(fctx), 160 rename=webutil.renamelink(fctx),
164 permissions=fctx.manifest().flags(f), 161 permissions=fctx.manifest().flags(f),
165 ishead=int(ishead), 162 ishead=int(ishead),
166 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx)))) 163 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx))))
167 164
168 return web.res 165 return web.res.sendresponse()
169 166
170 @webcommand('file') 167 @webcommand('file')
171 def file(web, req, tmpl): 168 def file(web, req, tmpl):
172 """ 169 """
173 /file/{revision}[/{path}] 170 /file/{revision}[/{path}]
353 lessvars=lessvars, 350 lessvars=lessvars,
354 modedesc=searchfunc[1], 351 modedesc=searchfunc[1],
355 showforcekw=showforcekw, 352 showforcekw=showforcekw,
356 showunforcekw=showunforcekw)) 353 showunforcekw=showunforcekw))
357 354
358 return web.res 355 return web.res.sendresponse()
359 356
360 @webcommand('changelog') 357 @webcommand('changelog')
361 def changelog(web, req, tmpl, shortlog=False): 358 def changelog(web, req, tmpl, shortlog=False):
362 """ 359 """
363 /changelog[/{revision}] 360 /changelog[/{revision}]
453 revcount=revcount, 450 revcount=revcount,
454 morevars=morevars, 451 morevars=morevars,
455 lessvars=lessvars, 452 lessvars=lessvars,
456 query=query)) 453 query=query))
457 454
458 return web.res 455 return web.res.sendresponse()
459 456
460 @webcommand('shortlog') 457 @webcommand('shortlog')
461 def shortlog(web, req, tmpl): 458 def shortlog(web, req, tmpl):
462 """ 459 """
463 /shortlog 460 /shortlog
488 templates related to diffs may all be used to produce the output. 485 templates related to diffs may all be used to produce the output.
489 """ 486 """
490 ctx = webutil.changectx(web.repo, req) 487 ctx = webutil.changectx(web.repo, req)
491 web.res.setbodygen(tmpl('changeset', 488 web.res.setbodygen(tmpl('changeset',
492 **webutil.changesetentry(web, req, tmpl, ctx))) 489 **webutil.changesetentry(web, req, tmpl, ctx)))
493 return web.res 490 return web.res.sendresponse()
494 491
495 rev = webcommand('rev')(changeset) 492 rev = webcommand('rev')(changeset)
496 493
497 def decodepath(path): 494 def decodepath(path):
498 """Hook for mapping a path in the repository to a path in the 495 """Hook for mapping a path in the repository to a path in the
600 fentries=filelist, 597 fentries=filelist,
601 dentries=dirlist, 598 dentries=dirlist,
602 archives=web.archivelist(hex(node)), 599 archives=web.archivelist(hex(node)),
603 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx)))) 600 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx))))
604 601
605 return web.res 602 return web.res.sendresponse()
606 603
607 @webcommand('tags') 604 @webcommand('tags')
608 def tags(web, req, tmpl): 605 def tags(web, req, tmpl):
609 """ 606 """
610 /tags 607 /tags
636 node=hex(web.repo.changelog.tip()), 633 node=hex(web.repo.changelog.tip()),
637 entries=lambda **x: entries(False, False, **x), 634 entries=lambda **x: entries(False, False, **x),
638 entriesnotip=lambda **x: entries(True, False, **x), 635 entriesnotip=lambda **x: entries(True, False, **x),
639 latestentry=lambda **x: entries(True, True, **x))) 636 latestentry=lambda **x: entries(True, True, **x)))
640 637
641 return web.res 638 return web.res.sendresponse()
642 639
643 @webcommand('bookmarks') 640 @webcommand('bookmarks')
644 def bookmarks(web, req, tmpl): 641 def bookmarks(web, req, tmpl):
645 """ 642 """
646 /bookmarks 643 /bookmarks
677 node=hex(web.repo.changelog.tip()), 674 node=hex(web.repo.changelog.tip()),
678 lastchange=[{'date': web.repo[latestrev].date()}], 675 lastchange=[{'date': web.repo[latestrev].date()}],
679 entries=lambda **x: entries(latestonly=False, **x), 676 entries=lambda **x: entries(latestonly=False, **x),
680 latestentry=lambda **x: entries(latestonly=True, **x))) 677 latestentry=lambda **x: entries(latestonly=True, **x)))
681 678
682 return web.res 679 return web.res.sendresponse()
683 680
684 @webcommand('branches') 681 @webcommand('branches')
685 def branches(web, req, tmpl): 682 def branches(web, req, tmpl):
686 """ 683 """
687 /branches 684 /branches
702 'branches', 699 'branches',
703 node=hex(web.repo.changelog.tip()), 700 node=hex(web.repo.changelog.tip()),
704 entries=entries, 701 entries=entries,
705 latestentry=latestentry)) 702 latestentry=latestentry))
706 703
707 return web.res 704 return web.res.sendresponse()
708 705
709 @webcommand('summary') 706 @webcommand('summary')
710 def summary(web, req, tmpl): 707 def summary(web, req, tmpl):
711 """ 708 """
712 /summary 709 /summary
787 node=tip.hex(), 784 node=tip.hex(),
788 symrev='tip', 785 symrev='tip',
789 archives=web.archivelist('tip'), 786 archives=web.archivelist('tip'),
790 labels=web.configlist('web', 'labels'))) 787 labels=web.configlist('web', 'labels')))
791 788
792 return web.res 789 return web.res.sendresponse()
793 790
794 @webcommand('filediff') 791 @webcommand('filediff')
795 def filediff(web, req, tmpl): 792 def filediff(web, req, tmpl):
796 """ 793 """
797 /diff/{revision}/{path} 794 /diff/{revision}/{path}
836 symrev=webutil.symrevorshortnode(req, ctx), 833 symrev=webutil.symrevorshortnode(req, ctx),
837 rename=rename, 834 rename=rename,
838 diff=diffs, 835 diff=diffs,
839 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx)))) 836 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx))))
840 837
841 return web.res 838 return web.res.sendresponse()
842 839
843 diff = webcommand('diff')(filediff) 840 diff = webcommand('diff')(filediff)
844 841
845 @webcommand('comparison') 842 @webcommand('comparison')
846 def comparison(web, req, tmpl): 843 def comparison(web, req, tmpl):
915 rightrev=rightrev, 912 rightrev=rightrev,
916 rightnode=hex(rightnode), 913 rightnode=hex(rightnode),
917 comparison=comparison, 914 comparison=comparison,
918 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx)))) 915 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx))))
919 916
920 return web.res 917 return web.res.sendresponse()
921 918
922 @webcommand('annotate') 919 @webcommand('annotate')
923 def annotate(web, req, tmpl): 920 def annotate(web, req, tmpl):
924 """ 921 """
925 /annotate/{revision}/{path} 922 /annotate/{revision}/{path}
1009 permissions=fctx.manifest().flags(f), 1006 permissions=fctx.manifest().flags(f),
1010 ishead=int(ishead), 1007 ishead=int(ishead),
1011 diffopts=diffopts, 1008 diffopts=diffopts,
1012 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx)))) 1009 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx))))
1013 1010
1014 return web.res 1011 return web.res.sendresponse()
1015 1012
1016 @webcommand('filelog') 1013 @webcommand('filelog')
1017 def filelog(web, req, tmpl): 1014 def filelog(web, req, tmpl):
1018 """ 1015 """
1019 /filelog/{revision}/{path} 1016 /filelog/{revision}/{path}
1149 revcount=revcount, 1146 revcount=revcount,
1150 morevars=morevars, 1147 morevars=morevars,
1151 lessvars=lessvars, 1148 lessvars=lessvars,
1152 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx)))) 1149 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx))))
1153 1150
1154 return web.res 1151 return web.res.sendresponse()
1155 1152
1156 @webcommand('archive') 1153 @webcommand('archive')
1157 def archive(web, req, tmpl): 1154 def archive(web, req, tmpl):
1158 """ 1155 """
1159 /archive/{revision}.{format}[/{path}] 1156 /archive/{revision}.{format}[/{path}]
1223 1220
1224 archival.archive(web.repo, bodyfh, cnode, artype, prefix=name, 1221 archival.archive(web.repo, bodyfh, cnode, artype, prefix=name,
1225 matchfn=match, 1222 matchfn=match,
1226 subrepos=web.configbool("web", "archivesubrepos")) 1223 subrepos=web.configbool("web", "archivesubrepos"))
1227 1224
1228 return True 1225 return []
1229 1226
1230 @webcommand('static') 1227 @webcommand('static')
1231 def static(web, req, tmpl): 1228 def static(web, req, tmpl):
1232 fname = req.req.qsparams['file'] 1229 fname = req.req.qsparams['file']
1233 # a repo owner may set web.static in .hg/hgrc to get any file 1230 # a repo owner may set web.static in .hg/hgrc to get any file
1238 if isinstance(tp, str): 1235 if isinstance(tp, str):
1239 tp = [tp] 1236 tp = [tp]
1240 static = [os.path.join(p, 'static') for p in tp] 1237 static = [os.path.join(p, 'static') for p in tp]
1241 1238
1242 staticfile(static, fname, web.res) 1239 staticfile(static, fname, web.res)
1243 return web.res 1240 return web.res.sendresponse()
1244 1241
1245 @webcommand('graph') 1242 @webcommand('graph')
1246 def graph(web, req, tmpl): 1243 def graph(web, req, tmpl):
1247 """ 1244 """
1248 /graph[/{revision}] 1245 /graph[/{revision}]
1399 jsdata=lambda **x: jsdata(), 1396 jsdata=lambda **x: jsdata(),
1400 nodes=lambda **x: nodes(), 1397 nodes=lambda **x: nodes(),
1401 node=ctx.hex(), 1398 node=ctx.hex(),
1402 changenav=changenav)) 1399 changenav=changenav))
1403 1400
1404 return web.res 1401 return web.res.sendresponse()
1405 1402
1406 def _getdoc(e): 1403 def _getdoc(e):
1407 doc = e[0].__doc__ 1404 doc = e[0].__doc__
1408 if doc: 1405 if doc:
1409 doc = _(doc).partition('\n')[0] 1406 doc = _(doc).partition('\n')[0]
1461 'helptopics', 1458 'helptopics',
1462 topics=topics, 1459 topics=topics,
1463 earlycommands=earlycommands, 1460 earlycommands=earlycommands,
1464 othercommands=othercommands, 1461 othercommands=othercommands,
1465 title='Index')) 1462 title='Index'))
1466 return web.res 1463 return web.res.sendresponse()
1467 1464
1468 # Render an index of sub-topics. 1465 # Render an index of sub-topics.
1469 if topicname in helpmod.subtopics: 1466 if topicname in helpmod.subtopics:
1470 topics = [] 1467 topics = []
1471 for entries, summary, _doc in helpmod.subtopics[topicname]: 1468 for entries, summary, _doc in helpmod.subtopics[topicname]:
1478 web.res.setbodygen(tmpl( 1475 web.res.setbodygen(tmpl(
1479 'helptopics', 1476 'helptopics',
1480 topics=topics, 1477 topics=topics,
1481 title=topicname, 1478 title=topicname,
1482 subindex=True)) 1479 subindex=True))
1483 return web.res 1480 return web.res.sendresponse()
1484 1481
1485 u = webutil.wsgiui.load() 1482 u = webutil.wsgiui.load()
1486 u.verbose = True 1483 u.verbose = True
1487 1484
1488 # Render a page from a sub-topic. 1485 # Render a page from a sub-topic.
1504 web.res.setbodygen(tmpl( 1501 web.res.setbodygen(tmpl(
1505 'help', 1502 'help',
1506 topic=topicname, 1503 topic=topicname,
1507 doc=doc)) 1504 doc=doc))
1508 1505
1509 return web.res 1506 return web.res.sendresponse()
1510 1507
1511 # tell hggettext to extract docstrings from these functions: 1508 # tell hggettext to extract docstrings from these functions:
1512 i18nfunctions = commands.values() 1509 i18nfunctions = commands.values()