Mercurial > public > mercurial-scm > hg
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() |