pluginbase-1.0.0/0000775000175000017500000000000013425371076015017 5ustar steinersteiner00000000000000pluginbase-1.0.0/LICENSE0000664000175000017500000000272513301414030016007 0ustar steinersteiner00000000000000Copyright (c) 2014 by Armin Ronacher. Some rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pluginbase-1.0.0/MANIFEST.in0000664000175000017500000000032413301414030016531 0ustar steinersteiner00000000000000include Makefile LICENSE recursive-include tests * recursive-include docs * recursive-exclude docs *.pyc recursive-exclude docs *.pyo recursive-exclude tests *.pyc recursive-exclude tests *.pyo prune docs/_build pluginbase-1.0.0/Makefile0000664000175000017500000000032313301414030016432 0ustar steinersteiner00000000000000test: @cd tests; PYTHONPATH=.. py.test --tb=native upload-docs: $(MAKE) -C docs dirhtml rsync -a docs/_build/dirhtml/* flow.srv.pocoo.org:/srv/websites/pluginbase.pocoo.org/static/ .PHONY: test upload-docs pluginbase-1.0.0/PKG-INFO0000664000175000017500000000357113425371076016122 0ustar steinersteiner00000000000000Metadata-Version: 2.1 Name: pluginbase Version: 1.0.0 Summary: PluginBase is a module for Python that enables the development of flexible plugin systems in Python. Home-page: http://github.com/mitsuhiko/pluginbase Author: Armin Ronacher Author-email: armin.ronacher@active-4.com Maintainer: Spencer McIntyre Maintainer-email: zeroSteiner@gmail.com License: UNKNOWN Description: # PluginBase PluginBase is a module for Python that enables the development of flexible plugin systems in Python. Step 1: ```python from pluginbase import PluginBase plugin_base = PluginBase(package='yourapplication.plugins') ``` Step 2: ```python plugin_source = plugin_base.make_plugin_source( searchpath=['./path/to/plugins', './path/to/more/plugins']) ``` Step 3: ```python with plugin_source: from yourapplication.plugins import my_plugin my_plugin.do_something_cool() ``` Or alternatively: ```python my_plugin = plugin_source.load_plugin('my_plugin') my_plugin.do_something_cool() ``` Platform: UNKNOWN Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Environment :: Plugins Classifier: Intended Audience :: Developers Description-Content-Type: text/markdown pluginbase-1.0.0/README.md0000664000175000017500000000113413301420652016261 0ustar steinersteiner00000000000000# PluginBase PluginBase is a module for Python that enables the development of flexible plugin systems in Python. Step 1: ```python from pluginbase import PluginBase plugin_base = PluginBase(package='yourapplication.plugins') ``` Step 2: ```python plugin_source = plugin_base.make_plugin_source( searchpath=['./path/to/plugins', './path/to/more/plugins']) ``` Step 3: ```python with plugin_source: from yourapplication.plugins import my_plugin my_plugin.do_something_cool() ``` Or alternatively: ```python my_plugin = plugin_source.load_plugin('my_plugin') my_plugin.do_something_cool() ``` pluginbase-1.0.0/docs/0000775000175000017500000000000013425371076015747 5ustar steinersteiner00000000000000pluginbase-1.0.0/docs/Makefile0000664000175000017500000001073113301414030017366 0ustar steinersteiner00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Classy.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Classy.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Classy" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Classy" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." latexpdf: latex $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." pluginbase-1.0.0/docs/_static/0000775000175000017500000000000013425371076017375 5ustar steinersteiner00000000000000pluginbase-1.0.0/docs/_static/itsdangerous.png0000664000175000017500000004503313301414030022574 0ustar steinersteiner00000000000000PNG  IHDRȀsBIT|d pHYs  mtEXtSoftwarewww.inkscape.org< IDATxu]Շw)+Zݽ8 -^ܡ@A(E''$d}eΜ3g$}##Y{o/f>sw' IO]ֻ  uͬed:62AALO/kffO~ol ifY.kAD`Xk30p?;AAt&=OlN'$ٶv!fG4 N2[90*=3Y 6K@VW9lVtkoP3AAIxX }_6vkml]V?f>ѝ p;a(  X̶kwX   Jj`{`< f@.1k~l AA pGYQDc@/4pG3۬v ;a wA l_`2QU`07& w۠B} ~YC{M0%rM&GǺO\wkz[f#9CWAA*:6`G`f6.֠afY9IBб3 κ6;e/@ HlF3"3or`I ppmaY%vrF/.U|W ߞh =_}00#zxc`O܎*=`5`S3;Ef=}sߟ^A Gi s# 91>~^>XhfYMQdڛދx =p3AA}镟oR0+o‚yq`U`gZ3+wA f-zRXh'ŀ ?dQllr`g`Z`xt5f6h4f6zyË́G_{]!hNN^_gj<"haOz3ݟlSȿ Atb^8f9-Y}_a;%nkY-f6+p$Du̾B-JU I~#〟Ț+& ||G `u{kmn93ݿgirt~|zKz ۙ X]-gi  Ϲ+e 'rf.cV3YAꎶc~F`g>9 ,?p[fעlxzg>Á*f;:k|\^rlPdqMSE+'"nrc)tπ.瀵gTxl7`Ywc9;61bfrE{(UJNFo4df?"ߖa-HNnVKZ<,/ְcf EuG18T:㑸յnlÀݽh:lcLZDB/P`̬w_ m/~;|7iy#&}Ebk9Moyۢ33W/{krۜs !0=ieXw\uKsn-ah}t,l ~Dsݽ*맙Nk1mro}wK ~_<zςjJ|~`;d^)Q#QD6,NEoτ n8܅2W2anMw餿ޅ鼭:4]e4 SZ秴%L?5u>uthc"7:als,0cSv^4hzea[G9.F?ˠ_e(r̍vs HL/ lTks{2B*Ǜu>3}ÁπʹҹY7 Ŷo o_^5;K^V-e߁ݝr7t,RKZHA=#_YQ'IW3زum2כ[ۙBjo2;8Ӈ+F9s7r |v.rCqdUX~v@p2evvEPY[jT8!}l?e7 =by[$p^>O˃ N*Z Y}}X.qާz BeY^p"~O@c3#1"ꅜ7Do dJ%= X*ep Ա?Eqw8" EQl>Q+~,H~/?-梭ɞU<0A?_Pm7oj锻?!9weĭ&ZEtQ7Je73;̖@2uQմE֭OqJ)vjW{ۻ !̡WƫIڲot`g@ ߠSJ[!0+rF7dPn?&xv]˯zy+HTQPtuusu?ϣYޢZ5 (AB:o=V'z}VtSN/07"4f|}_+ߺ'Ne!ݠAp? OzCsY>s@2/795+X /Sw WzǹKr7wt5~B7lzf6gQ#i=*]6"x)rZ@cZY\ſyR UN$^sVtDCBzf̦ANVCuHlQB;aM 3ukS)npe:X6@H2-.5q=D^W27 9:hpCҿ7~{蚉x;ȴkho_9Ί\])7S!!fQn(|;jvgЭ,3A۪3ںSu_nTȉ8-v LOdxfg 'J|4UVFE Jk%O3+V f$3EfW^4f0,Kټef+✔7LEUIڹ5moǚaTGvg~+v+ BJuH+/̷NB LӔdu*7ɭ\.)aOdǺ'R W$[Ud}dIᆉwz3?T*լtME-UE;̍ -殕6Ywf_>|9?sWH3:/A3{&OȞxsPiZ CJl ")??VH~r (>xݟ*@r̺67%;w3h"YnTE%a"Ӻ/rYӺ4u^ۺJVc" 0)3{1lgff][ch>OS 0+Vz ;E;&ٱf6{61lX笧ױRut$x!Kى̋ЛfG$v3Pm聿AVOfbme*tmBwTz`lCcӟ{'Tl"oM+Xw {za+7mCͪ"=B߲&Gr*qR,Vq.!9iol3zS˧ɾ/YXkO9}#M~$Za*U(ROKyV,Flgb| do''Q~=wo}h@HyA !JHTz`Pf6& ELt#zwF[fWZ#UpFOC]@H]vH_DR%loMۙ!uH?\@pwORS:@wχ~d@>Kaf7ERZA+*"Nܘ^ ;Z`#]wS. +\jh2HCgBVt/`rgQqovJ49n \B cfk{"'jC.:<3wP f` 7 AnBI/=UJ?DzaKPv7Us5 [mjtZ-9+){Gٗ6Y T#CD_aYd{Q-U\"Vj g.՚bV쩐]=@ȺwL5/Cȕ"y. Ѓ=Tfd}cdK?cӸkw"/ ?K~̳iy}ʒy1(OX%2-NGŞopVH&-J{ezt/(F\i&ż'Ծ&R|U/p>F>4u;3;e"mmVRׄx\~;wj1Kw= }0VX,lf4Ӷ?sNC07AB2,2.Jgfk1bmbo3=ݽ /D3^ |; F plUT>6r(v&mYcк3*T:zZ޺:bf+LVB7uwgf7:,H$ T(FZW؞>k!Ш6qNWX+n 3;O07}P%v f0z.<6zxqH@2 Ѻ]$?CXn')LgC'qf6Sg|yS|zY?@e(OB1k#oݟ?¢49,PMvZ~(wg>CUs ',s!5WjZv&^ j!B5ßZ`p;3r|D>儉t3[Jm(Q\SB=( t (9h8bl7&?Bo0Uu؏oʂPoL2ڦb,~Nx6eVe+^$@&!UloZI$lM[ za( =V49+pT]-jt@ȟrU +{1rجfV&%bj$ٜ+\^1:[#i%1 w~ONn 2<4IJ ZV}̷rGڟ*iMr-s *֘ϛW gW%i+GPYYdm<17y2 5J3 1}pP,A1+.m?[-Y'aaSj rŔ~ɪX i n4=^BY̿:(UzM6Tï4R4r0Wr4҃vzV-Pby]ASr|OPjhX/CkN"aKfv}gڹn3DJy!\mhZ1+i%/;Sy|2Ϣ%1R5əxC䕑\)#8_f5XUN &;{S}M`?8=PDjx4zC>%ڜ:7=ElkX n IDAT'm? KIak]xk}n.3g_ʹ18_aE W[}=6ͭaGo$dۚ6FÚhc43,SU)h3Ԩ͵iWGlsbm4LOzTΪ(qQoϠiJ ͬ{/Psl):XuLV}90SuEJ}Qgi@?[Pp$6Bq;=wޕۗ+u:o&g,zPUDn _ۼ cz̮mאIc?=5Y%@ouǸ:Z3]3?Cg9Z'AT9Bz:4C`WM-p+ :k6YȷUw6YPG<8rKR:W9,n_=gG8#3:/|cPhꏩ}h|5 QbCbiwFtp 0ĕpT;==cOd\ju$FgQZ K=ኺ;f Ikh80}F ˧a.Ăl+Z_UaT;Tt~[f*!sz>zKy2k E 0#Rw I\Lc{ ڧhqN^X~1޹MW(vОJ2 2@5GF/Y);[VwiO>`ǡku8ƹx+lW|3#Uvz͞+7~k9r.:c4]w}R=Ѝ{rwk_Aڝ M ܄?ߡ7\QId=~ -3 e]ݳ-ms9{2-p :דg>?^aZ$l@B{^6gd;vrg&7"zᕍW~ȶhwALW%Ahv^oinCl,R #ճ(z~lYB$̌szBuާE? sǵ v&fկg k8y#ޠaf\YTFV `W"$3LHMy+K|Twȿe$Ɔ#kYfCi=p_=;Ȋ~ +9.DH$<ǻ62- Lz)AF 3;)Tyj 42͏?HU: Dӄ9v:GM&G!X+dʺ8puPGSF#Aݍn!(ȓx*Z~HH F0D(vҔr3 @pP#uQ7y. In/ ?+VtZPz+PmիO5l> B (S r_XHx 7if[5t hDpT9a4$VF"a< !08Enk٦ ݁  h&*aȒ0WPcA$,Gi*umAA0 W>'#T^dIF ̀-]$J 7D…f^#! N /"Ȓʁp-"(!Ҫ(eqWulFCAt%@_DhTiTtv47d=Dª(OB}X$@YW!x)(V /"awpYޘ3tcl>3{]ܥ0 w:@΅,$,|@WY6.ᆠꆌ¾L2ٔfgTj2fs$$ l _Dj EfE*`+ow.Y "aB0 }O$\4̦ wYLjGטr-h^&cPTH$n!kKאHb"|hqI_kc3A-} IJ  `$HShUZ|nTIvK ad`4Y5`^{fe -r:Y fDF CQ"Hu$2%!2~fV҉ܝѧ_vK7f60w?ke ;p=0=/\$>A~ %}JcfoTINi(f@5N :aӿtu_IV nh&{ "'c\u!fvbvAG, nTrg}K,wt:E=Pe7QR+Q4VE#Nݓ'EBDwYnz#KyH4,<-͏$ffGhw4{I螘 fvdf=3;u&3]B3{n7l*Q+fv]iff62C1[3if7قzI3CZ7܊fvofa*3;̞F03fl `}:3̆ٹ_Z6/qi1uY~t6lgo32l[31mffkf]jfTӠ[IG{V40]c3۷͞ 3kf1ׁ1f6>6ͬ%t1wPP?4BNK ³IezSE| oC{"YGb"g0]*(`lfE֝ۯv_04m̲z%Z r}2YV`\CPJ , OFVp"'GQ´;R;䖝e,l"5w<~H*0eZo2gg?#~ӹ?U㛖?+-y:qpm Hm2' Gƍi]ێO.@3}H;,D"a( 7qF}>`%Dd])Z8ЮM_ =M_X 7`ZV؏-27GU҃j2>3iri=͑2ӼJŴ:X?^t3!HTH{ }CH(\]-zn?sn} L7Tm|7g_UZ˝P`- AE B"뮎yYf4y=+~h }=tʲx7]An~~])\mnt_sG-7 i̍u yZg$02힝CznheCJkhZ)p`}BI}XGNGEg3_nkfYow/vs \Ee3i|?k*mGPE/H+uH6̱mhq}Gl-} Eȉx9h2-|R=N@VS,R65IFKo٩627>&_FrPm}ȴXX̞+!?N0E ,efo&(dxqwO ~N'W5tb nffu'mg'O@!)E+$G{Rod;k1ʰ7FcT A} I$DBu2p",ތ^Go'6cmf6sjw4W>*yPߌ砇VMYtwFS̎ȵjf_13lNڝ ;w? - NgCI dKk'L[ >,SV ܽQ-RLVlb2_[Xݯw. 2y/M޳DHa ȋJe3F8}ӐOԒ;VU wF!^hB +lavCn G"!K p ;gH3>2=ef/Y}S̬̀E;`p}S0Gfhy*v6EhO=YhW(AFY0݋v R,ҹcRd90w{f}SEͫ3-os[PHac6+;?Ǻ.hXd E@WsE5B͎zKJ 6Z swC@+G񳶳iL#,N"67-$N-LXZ!WHǼ0dLA(aهiv-9g#A-=ӧWZ wS%F͂5ݱhXw2 wlk$OA# ЛȁgM#0# :+N*13~%!-8#cgt %BlݍnBM\)}֟|F/{$+!("ovf炠@>=8<* BzˠbpY̶Ao(lŌH8-DBt)7R83{=̞F ˺S] #PGlAx8p *$%} .BRWCaqSS'GOBt )t(mN[60ċT L:7=]| C_=}Mviz ӈ!:'}J8st6%?YYB M7S~E}l ݐOBT J/g!]PnQzQRfwZCw,d= pFPR[l]lf 9 yNkq-fD굽 I u< Q3QճP(ЃHVB> Y$NJfI^6-$& U&\mAON.Aowb.["h`WvgpqǙRI$l"! !kG(Lw<|a;AA7 B'"N!Ks(Tq(rJ| _*1[H2_ifGC-Uvo^7虦) pWFAMɸ9(' x0j3L7!.7" `f6 UI M},}U;At+"0ߣo{ȁyȯkp 7\U)-F!ƛٔ(Hw̾l7Y6[*4\ALĄpѐÖ(({O1R8E7"6N`v (>C5(qS ~f&M I t1f6Jt p*+xlYL_Y6r f"df # 3.rhgs Q2 4rf6J}fQ{ACXw \ Cy $VN5Ɂ8̊JE2Nb$0Q%`$EC* qA0&6@(9"Waq G7,( pnf롰ϷK#c3!m:f$d I 4 ~'r<Op,Yq0Y ~Ű:po I 4)1(EhT^hu$"By."441-/G8EQ] &-‚d =C;ρ CP QmgIl<|0w=?D96ȉŀCALI15Q(|+$ CQ cbˎ. IDATT2Rpiw4NH/ͅJGoF!EQ~|2;m #f,n,OQhW(( hazTqw Eɕ6 A&1MHiKsf0_M IX D`<0-0m  6m}H+ hN‚D$À3[| ^z y:f, If6u>+l]T B ` BafW#rB|ؚ"?Ƹ? q\0Qp~}eig$Dsj {IH#U݊,-9XYEf6uqgEKM?hxw= l}jHdi8 3~ ̉ʊw<'.XFǡ0S, 讄 0;'`ifPP;P(4(Rgȧ`lb#]7(ZfvT$qU_\8{rAjǁPDOTˀl 'E  t1f OY'p%pWvE.F_Er1?#*OT4jA4aAzV+ŁMofBJ[ĵji;_aQeY6s`9`+$  4f6'mzYoyn{ p7kx 3^r}lc }38MAI&zkq(sM~mx L8)auv`w G3[ xzX8]sX'8:M0< hϣ"Q%$mT[Qρ P6ȵQeAЍ,P)SlJ?K~@ΕAA7CO`G٪`JD/3g40 U|4 9F At3!(AQ3 ǡaatZn%bw,JUNͭńEAФ@%EV eM\?1_"B?|`Z$ f[!(y]AAՄ@JJ< e^<Ux| fkvA7 :>AI 3(3~ `fS] "!( w19%|gfmAi(Ym[ b|ERAwvl;Tmr.w 'BP6f,ugzuȪ-rTx8A5>A%, @E eFdK{] v‚TD {{ super() }} {% if pagename == 'index' %}
{% endif %} {% endblock %} {% block footer %} {% if pagename == 'index' %}
{% endif %} {% endblock %} {# do not display relbars #} {% block relbar1 %}{% endblock %} {% block relbar2 %} {% if theme_github_fork %} Fork me on GitHub {% endif %} {% endblock %} {% block sidebar1 %}{% endblock %} {% block sidebar2 %}{% endblock %} pluginbase-1.0.0/docs/_themes/flask_small/static/0000775000175000017500000000000013425371076023152 5ustar steinersteiner00000000000000pluginbase-1.0.0/docs/_themes/flask_small/static/flasky.css_t0000664000175000017500000001076713301414030025470 0ustar steinersteiner00000000000000/* * flasky.css_t * ~~~~~~~~~~~~ * * Sphinx stylesheet -- flasky theme based on nature theme. * * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @import url("basic.css"); @import url(http://fonts.googleapis.com/css?family=Karla:400); {% set font_family = "'Karla', sans-serif" %} /* -- page layout ----------------------------------------------------------- */ body { font-family: {{ font_family }}; font-weight: normal; font-size: 17px; color: #000; background: white; margin: 0; padding: 0; } div.documentwrapper { float: left; width: 100%; } div.bodywrapper { margin: 40px auto 0 auto; width: 700px; } hr { border: 1px solid #B1B4B6; } div.body { background-color: #ffffff; color: #3E4349; padding: 0 30px 30px 30px; } img.floatingflask { padding: 0 0 10px 10px; float: right; } div.footer { text-align: right; color: #888; padding: 10px; font-size: 14px; width: 650px; margin: 0 auto 40px auto; } div.footer a { color: #888; text-decoration: underline; } div.related { line-height: 32px; color: #888; } div.related ul { padding: 0 0 0 10px; } div.related a { color: #444; } /* -- body styles ----------------------------------------------------------- */ a { color: #215974; text-decoration: underline; } a:hover { color: #888; text-decoration: underline; } div.body { padding-bottom: 40px; /* saved for footer */ } div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { font-family: {{ font_family }}; font-weight: normal; margin: 30px 0px 10px 0px; padding: 0; color: black; } div.body h1 { font-size: 240%; } div.body h2 { font-size: 180%; } div.body h3 { font-size: 150%; } div.body h4 { font-size: 130%; } div.body h5 { font-size: 100%; } div.body h6 { font-size: 100%; } a.headerlink { color: white; padding: 0 4px; text-decoration: none; } a.headerlink:hover { color: #444; background: #eaeaea; } div.body p, div.body dd, div.body li, div.body blockquote { line-height: 1.3em; } div.admonition { background: #fafafa; margin: 20px -30px; padding: 10px 30px; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; } div.admonition p.admonition-title { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; font-size: 24px; margin: 0 0 10px 0; padding: 0; line-height: 1; } div.admonition p.last { margin-bottom: 0; } div.highlight{ background-color: white; } dt:target, .highlight { background: #FAF3E8; } div.note { background-color: #eee; border: 1px solid #ccc; } div.seealso { background-color: #ffc; border: 1px solid #ff6; } div.topic { background-color: #eee; } div.warning { background-color: #ffe4e4; border: 1px solid #f66; } p.admonition-title { display: inline; } p.admonition-title:after { content: ":"; } pre, tt { font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; font-size: 0.85em; } img.screenshot { } tt.descname, tt.descclassname { font-size: 0.95em; } tt.descname { padding-right: 0.08em; } img.screenshot { -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils { border: 1px solid #888; -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils td, table.docutils th { border: 1px solid #888; padding: 0.25em 0.7em; } table.field-list, table.footnote { border: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } table.footnote { margin: 15px 0; width: 100%; border: 1px solid #eee; } table.field-list th { padding: 0 0.8em 0 0; } table.field-list td { padding: 0; } table.footnote td { padding: 0.5em; } dl { margin: 0; padding: 0; } dl dd { margin-left: 30px; } pre { padding: 0; margin: 15px -30px; padding: 8px; line-height: 1.3em; padding: 7px 30px; background: #eee; border-radius: 2px; -moz-border-radius: 2px; -webkit-border-radius: 2px; } dl pre { margin-left: -60px; padding-left: 60px; } tt { background-color: #ecf0f3; color: #222; /* padding: 1px 2px; */ } tt.xref, a tt { background-color: #FBFBFB; } a:hover tt { background: #EEE; } pluginbase-1.0.0/docs/_themes/flask_small/theme.conf0000664000175000017500000000026513301414030023614 0ustar steinersteiner00000000000000[theme] inherit = basic stylesheet = flasky.css nosidebar = true pygments_style = flask_theme_support.FlaskyStyle [options] index_logo = '' index_logo_height = 160px github_fork = pluginbase-1.0.0/docs/_themes/flask_theme_support.py0000664000175000017500000001141313301414030024000 0ustar steinersteiner00000000000000# flasky extensions. flasky pygments style based on tango style from pygments.style import Style from pygments.token import Keyword, Name, Comment, String, Error, \ Number, Operator, Generic, Whitespace, Punctuation, Other, Literal class FlaskyStyle(Style): background_color = "#f8f8f8" default_style = "" styles = { # No corresponding class for the following: #Text: "", # class: '' Whitespace: "underline #f8f8f8", # class: 'w' Error: "#a40000 border:#ef2929", # class: 'err' Other: "#000000", # class 'x' Comment: "italic #8f5902", # class: 'c' Comment.Preproc: "noitalic", # class: 'cp' Keyword: "bold #004461", # class: 'k' Keyword.Constant: "bold #004461", # class: 'kc' Keyword.Declaration: "bold #004461", # class: 'kd' Keyword.Namespace: "bold #004461", # class: 'kn' Keyword.Pseudo: "bold #004461", # class: 'kp' Keyword.Reserved: "bold #004461", # class: 'kr' Keyword.Type: "bold #004461", # class: 'kt' Operator: "#582800", # class: 'o' Operator.Word: "bold #004461", # class: 'ow' - like keywords Punctuation: "bold #000000", # class: 'p' # because special names such as Name.Class, Name.Function, etc. # are not recognized as such later in the parsing, we choose them # to look the same as ordinary variables. Name: "#000000", # class: 'n' Name.Attribute: "#c4a000", # class: 'na' - to be revised Name.Builtin: "#004461", # class: 'nb' Name.Builtin.Pseudo: "#3465a4", # class: 'bp' Name.Class: "#000000", # class: 'nc' - to be revised Name.Constant: "#000000", # class: 'no' - to be revised Name.Decorator: "#888", # class: 'nd' - to be revised Name.Entity: "#ce5c00", # class: 'ni' Name.Exception: "bold #cc0000", # class: 'ne' Name.Function: "#000000", # class: 'nf' Name.Property: "#000000", # class: 'py' Name.Label: "#f57900", # class: 'nl' Name.Namespace: "#000000", # class: 'nn' - to be revised Name.Other: "#000000", # class: 'nx' Name.Tag: "bold #004461", # class: 'nt' - like a keyword Name.Variable: "#000000", # class: 'nv' - to be revised Name.Variable.Class: "#000000", # class: 'vc' - to be revised Name.Variable.Global: "#000000", # class: 'vg' - to be revised Name.Variable.Instance: "#000000", # class: 'vi' - to be revised Number: "#990000", # class: 'm' Literal: "#000000", # class: 'l' Literal.Date: "#000000", # class: 'ld' String: "#4e9a06", # class: 's' String.Backtick: "#4e9a06", # class: 'sb' String.Char: "#4e9a06", # class: 'sc' String.Doc: "italic #8f5902", # class: 'sd' - like a comment String.Double: "#4e9a06", # class: 's2' String.Escape: "#4e9a06", # class: 'se' String.Heredoc: "#4e9a06", # class: 'sh' String.Interpol: "#4e9a06", # class: 'si' String.Other: "#4e9a06", # class: 'sx' String.Regex: "#4e9a06", # class: 'sr' String.Single: "#4e9a06", # class: 's1' String.Symbol: "#4e9a06", # class: 'ss' Generic: "#000000", # class: 'g' Generic.Deleted: "#a40000", # class: 'gd' Generic.Emph: "italic #000000", # class: 'ge' Generic.Error: "#ef2929", # class: 'gr' Generic.Heading: "bold #000080", # class: 'gh' Generic.Inserted: "#00A000", # class: 'gi' Generic.Output: "#888", # class: 'go' Generic.Prompt: "#745334", # class: 'gp' Generic.Strong: "bold #000000", # class: 'gs' Generic.Subheading: "bold #800080", # class: 'gu' Generic.Traceback: "bold #a40000", # class: 'gt' } pluginbase-1.0.0/docs/conf.py0000664000175000017500000001553213301414030017231 0ustar steinersteiner00000000000000# -*- coding: utf-8 -*- # # pluginbase documentation build configuration file, created by # sphinx-quickstart on Mon Apr 26 19:53:01 2010. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.append(os.path.abspath('_themes')) sys.path.append(os.path.abspath('..')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'pluginbase' copyright = u'2014, Armin Ronacher' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '1.0' # The full version, including alpha/beta/rc tags. release = '1.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'flask_small' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. html_theme_options = { 'github_fork': 'mitsuhiko/pluginbase' } # Add any paths that contain custom themes here, relative to this directory. html_theme_path = ['_themes'] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". html_title = 'pluginbase' # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'pluginbasedoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'pluginbase.tex', u'pluginbase documentation', u'Armin Ronacher', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'pluginbase', u'pluginbase documentation', [u'Armin Ronacher'], 1) ] intersphinx_mapping = { 'http://docs.python.org/dev': None } pluginbase-1.0.0/docs/index.rst0000664000175000017500000001367013301414030017574 0ustar steinersteiner00000000000000PluginBase ========== .. module:: pluginbase Ever tried creating a plugin system for a Python application and you discovered fighting against the import system? Me too. This is where PluginBase comes in. PluginBase is a module for Python which extends the import system for the most common form of plugin usage which is providing a consistent import experience for plugins from a variety of sources. Essentially it allows you to build very flexible plugin based applications which pull in plugins from bundled sources as well as application specific ones without bypassing the Python import system. How does it work? It's super simple: Step 1: Create a "plugin base" object. It defines a pseudo package under which all your plugins will reside. For instance it could be ``yourapplication.plugins``:: from pluginbase import PluginBase plugin_base = PluginBase(package='yourapplication.plugins') Step 2: Now that you have a plugin base, you can define a plugin source which is the list of all sources which provide plugins:: plugin_source = plugin_base.make_plugin_source( searchpath=['./path/to/plugins', './path/to/more/plugins']) Step 3: To import a plugin all you need to do is to use the regular import system. The only change is that you need to import the plugin source through the ``with`` statement:: with plugin_source: from yourapplication.plugins import my_plugin my_plugin.do_something_cool() Alternatively you can also import plugins programmatically instead of using the import statement:: my_plugin = plugin_source.load_plugin('my_plugin') For a more complex example see the one from the git repo: `pluginbase-example `__. Installation ------------ You can get the library directly from PyPI:: pip install pluginbase FAQ --- Q: Why is there a plugin base and a plugin source class? This decision was taken so that multiple applications can co-exist together. For instance imagine you have an application that implements a wiki but you want multiple instances of that wiki to exist in the same Python interpreter. The plugin sources split out the load paths of the different applications. Each instance of the wiki would have its own plugin source and they can work independently of each other. Q: Do plugins pollute ``sys.modules``? While a plugin source is alive the plugins do indeed reside in ``sys.modules``. This decision was made consciously so that as little as possible of the Python library ecosystem breaks. However when the plugin source gets garbage collected all loaded plugins will also get garbage collected. Q: How does PluginBase deal with different versions of the same plugin? Each plugin source works independently of each other. The way this works is by internally translating the module name. By default that module name is a random number but it can also be forced to a hash of a specific value to make it stable across restarts which allows pickle and similar libraries to work. This internal module renaming means that ``yourapplication.module.foo`` will internally be called ``pluginbase._internalspace._sp7...be4`` for instance. The same plugin loaded from another plugin source will have a different internal name. Q: What happens if a plugin wants to import other modules? All fine. Plugins can import from itself as well as other plugins that can be located. Q: Does PluginBase support pickle? Yes, pickle works fine for plugins but it does require defining a stable identifier when creating a plugin source. This could for instance be a file system path:: plugin_source = base.make_plugin_source( searchpath=[app.plugin_path], identifier=app.config_filename) Q: What happens if I import from the plugin module without the plugin source activated through the ``with`` statement? The import will fail with a descriptive error message explaining that a plugin source needs to be activated. Q: Can I automatically discover all modules that are available? Yes you can. Just use the :meth:`PluginSource.list_plugins` method which returns a list of all plugins that a source can import. Q: Why would I use this over setuptools based plugins? PluginBase and setuptools based plugins solve very different problems and are incompatible on an architectural point of view. PluginBase does not solve plugin distribution through PyPI but allows plugins to be virtualized from each other. Setuptools on the other hand is based on PyPI based distribution but piggybacks on top of the regular import system. There are advantages and disadvantages to both of them. Setuptools based plugins are very useful to extend libraries from other libraries. For instance the Jinja2 template engine hooks into the Babel library for internationalization through setuptools. On the other hand applications distributed to users can benefit from a PluginBase based system which allows them to take control over how plugins are distributed and full separation from each other. API --- High Level `````````` .. autoclass:: PluginBase :members: .. autoclass:: PluginSource :members: .. autofunction:: get_plugin_source .. autofunction:: get_searchpath Import Hook Control ``````````````````` .. autofunction:: pluginbase.import_hook.enable .. autofunction:: pluginbase.import_hook.disable .. data:: pluginbase.import_hook.enabled Indicates if the import hook is currently active or not. Internals ````````` .. autodata:: pluginbase._internalspace This module is where pluginbase keeps track of all loaded plugins. Generally one can completely ignore the existence of it, but in some situations it might be useful to discover currently loaded modules through this when debugging. pluginbase-1.0.0/docs/make.bat0000664000175000017500000001001213301414030017323 0ustar steinersteiner00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Classy.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Classy.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end pluginbase-1.0.0/pluginbase.egg-info/0000775000175000017500000000000013425371076020642 5ustar steinersteiner00000000000000pluginbase-1.0.0/pluginbase.egg-info/PKG-INFO0000664000175000017500000000357113425371076021745 0ustar steinersteiner00000000000000Metadata-Version: 2.1 Name: pluginbase Version: 1.0.0 Summary: PluginBase is a module for Python that enables the development of flexible plugin systems in Python. Home-page: http://github.com/mitsuhiko/pluginbase Author: Armin Ronacher Author-email: armin.ronacher@active-4.com Maintainer: Spencer McIntyre Maintainer-email: zeroSteiner@gmail.com License: UNKNOWN Description: # PluginBase PluginBase is a module for Python that enables the development of flexible plugin systems in Python. Step 1: ```python from pluginbase import PluginBase plugin_base = PluginBase(package='yourapplication.plugins') ``` Step 2: ```python plugin_source = plugin_base.make_plugin_source( searchpath=['./path/to/plugins', './path/to/more/plugins']) ``` Step 3: ```python with plugin_source: from yourapplication.plugins import my_plugin my_plugin.do_something_cool() ``` Or alternatively: ```python my_plugin = plugin_source.load_plugin('my_plugin') my_plugin.do_something_cool() ``` Platform: UNKNOWN Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Environment :: Plugins Classifier: Intended Audience :: Developers Description-Content-Type: text/markdown pluginbase-1.0.0/pluginbase.egg-info/SOURCES.txt0000664000175000017500000000144413425371076022531 0ustar steinersteiner00000000000000LICENSE MANIFEST.in Makefile README.md pluginbase.py setup.py docs/Makefile docs/conf.py docs/index.rst docs/make.bat docs/_static/itsdangerous.png docs/_themes/.gitignore docs/_themes/LICENSE docs/_themes/README docs/_themes/flask_theme_support.py docs/_themes/flask_small/layout.html docs/_themes/flask_small/theme.conf docs/_themes/flask_small/static/flasky.css_t pluginbase.egg-info/PKG-INFO pluginbase.egg-info/SOURCES.txt pluginbase.egg-info/dependency_links.txt pluginbase.egg-info/not-zip-safe pluginbase.egg-info/top_level.txt tests/conftest.py tests/dummy.py tests/shutdown.py tests/test_advanced.py tests/test_basics.py tests/test_shutdown.py tests/plugins/advanced.py tests/plugins/hello.py tests/plugins/hello2.py tests/plugins/withresources/__init__.py tests/plugins/withresources/hello.txtpluginbase-1.0.0/pluginbase.egg-info/dependency_links.txt0000664000175000017500000000000113425371076024710 0ustar steinersteiner00000000000000 pluginbase-1.0.0/pluginbase.egg-info/not-zip-safe0000664000175000017500000000000113301414535023057 0ustar steinersteiner00000000000000 pluginbase-1.0.0/pluginbase.egg-info/top_level.txt0000664000175000017500000000001313425371076023366 0ustar steinersteiner00000000000000pluginbase pluginbase-1.0.0/pluginbase.py0000664000175000017500000003763513425370157017537 0ustar steinersteiner00000000000000# -*- coding: utf-8 -*- """ pluginbase ~~~~~~~~~~ Pluginbase is a module for Python that provides a system for building plugin based applications. :copyright: (c) Copyright 2014 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ import os import sys import uuid import errno import pkgutil import hashlib import threading from types import ModuleType from weakref import ref as weakref PY2 = sys.version_info[0] == 2 if PY2: text_type = unicode string_types = (unicode, str) from cStringIO import StringIO as NativeBytesIO else: text_type = str string_types = (str,) from io import BytesIO as NativeBytesIO __version__ = '1.0.0' _local = threading.local() _internalspace = ModuleType(__name__ + '._internalspace') _internalspace.__path__ = [] sys.modules[_internalspace.__name__] = _internalspace def get_plugin_source(module=None, stacklevel=None): """Returns the :class:`PluginSource` for the current module or the given module. The module can be provided by name (in which case an import will be attempted) or as a module object. If no plugin source can be discovered, the return value from this method is `None`. This function can be very useful if additional data has been attached to the plugin source. For instance this could allow plugins to get access to a back reference to the application that created them. :param module: optionally the module to locate the plugin source of. :param stacklevel: defines how many levels up the module should search for before it discovers the plugin frame. The default is 0. This can be useful for writing wrappers around this function. """ if module is None: frm = sys._getframe((stacklevel or 0) + 1) name = frm.f_globals['__name__'] glob = frm.f_globals elif isinstance(module, string_types): frm = sys._getframe(1) name = module glob = __import__(module, frm.f_globals, frm.f_locals, ['__dict__']).__dict__ else: name = module.__name__ glob = module.__dict__ return _discover_space(name, glob) def get_searchpath(path, depth=float('inf'), followlinks=False): """This utility function returns a list directories suitable for use as the *searchpath* argument to :class:`PluginSource`. This will recursively add directories up to the specified depth. :param str path: The directory on the file system to start the search path at. It will be included in the result. :param int depth: The number of directories to recurse into while building the search path. By default the function will iterate into all child directories. :param bool followlinks: Whether or not to recurse into directories which are symbolic links. :return: A list of directories, including *path* and child directories. :rtype: list """ # os.walk implements a depth-first approach which results in unnecessarily # slow execution when *path* is a large tree and *depth* is a small number paths = [path] for dir_entry in os.listdir(path): sub_path = os.path.join(path, dir_entry) if not os.path.isdir(sub_path): continue if not followlinks and os.path.islink(sub_path): continue if depth: paths.extend(get_searchpath(sub_path, depth - 1, followlinks)) return paths def _discover_space(name, globals): try: return _local.space_stack[-1] except (AttributeError, IndexError): pass if '__pluginbase_state__' in globals: return globals['__pluginbase_state__'].source mod_name = globals.get('__name__') if mod_name is not None and \ mod_name.startswith(_internalspace.__name__ + '.'): end = mod_name.find('.', len(_internalspace.__name__) + 1) space = sys.modules.get(mod_name[:end]) if space is not None: return space.__pluginbase_state__.source def _shutdown_module(mod): members = list(mod.__dict__.items()) for key, value in members: if key[:1] != '_': setattr(mod, key, None) for key, value in members: setattr(mod, key, None) def _to_bytes(s): if isinstance(s, text_type): return s.encode('utf-8') return s class _IntentionallyEmptyModule(ModuleType): def __getattr__(self, name): try: return ModuleType.__getattr__(self, name) except AttributeError: if name[:2] == '__': raise raise RuntimeError( 'Attempted to import from a plugin base module (%s) without ' 'having a plugin source activated. To solve this error ' 'you have to move the import into a "with" block of the ' 'associated plugin source.' % self.__name__) class _PluginSourceModule(ModuleType): def __init__(self, source): modname = '%s.%s' % (_internalspace.__name__, source.spaceid) ModuleType.__init__(self, modname) self.__pluginbase_state__ = PluginBaseState(source) @property def __path__(self): try: ps = self.__pluginbase_state__.source except AttributeError: return [] return ps.searchpath + ps.base.searchpath def _setup_base_package(module_name): try: mod = __import__(module_name, None, None, ['__name__']) except ImportError: mod = None if '.' in module_name: parent_mod = __import__(module_name.rsplit('.', 1)[0], None, None, ['__name__']) else: parent_mod = None if mod is None: mod = _IntentionallyEmptyModule(module_name) if parent_mod is not None: setattr(parent_mod, module_name.rsplit('.', 1)[-1], mod) sys.modules[module_name] = mod class PluginBase(object): """The plugin base acts as a control object around a dummy Python package that acts as a container for plugins. Usually each application creates exactly one base object for all plugins. :param package: the name of the package that acts as the plugin base. Usually this module does not exist. Unless you know what you are doing you should not create this module on the file system. :param searchpath: optionally a shared search path for modules that will be used by all plugin sources registered. """ def __init__(self, package, searchpath=None): #: the name of the dummy package. self.package = package if searchpath is None: searchpath = [] #: the default search path shared by all plugins as list. self.searchpath = searchpath _setup_base_package(package) def make_plugin_source(self, *args, **kwargs): """Creates a plugin source for this plugin base and returns it. All parameters are forwarded to :class:`PluginSource`. """ return PluginSource(self, *args, **kwargs) class PluginSource(object): """The plugin source is what ultimately decides where plugins are loaded from. Plugin bases can have multiple plugin sources which act as isolation layer. While this is not a security system it generally is not possible for plugins from different sources to accidentally cross talk. Once a plugin source has been created it can be used in a ``with`` statement to change the behavior of the ``import`` statement in the block to define which source to load the plugins from:: plugin_source = plugin_base.make_plugin_source( searchpath=['./path/to/plugins', './path/to/more/plugins']) with plugin_source: from myapplication.plugins import my_plugin :param base: the base this plugin source belongs to. :param identifier: optionally a stable identifier. If it's not defined a random identifier is picked. It's useful to set this to a stable value to have consistent tracebacks between restarts and to support pickle. :param searchpath: a list of paths where plugins are looked for. :param persist: optionally this can be set to `True` and the plugins will not be cleaned up when the plugin source gets garbage collected. """ # Set these here to false by default so that a completely failing # constructor does not fuck up the destructor. persist = False mod = None def __init__(self, base, identifier=None, searchpath=None, persist=False): #: indicates if this plugin source persists or not. self.persist = persist if identifier is None: identifier = str(uuid.uuid4()) #: the identifier for this source. self.identifier = identifier #: A reference to the plugin base that created this source. self.base = base #: a list of paths where plugins are searched in. self.searchpath = searchpath #: The internal module name of the plugin source as it appears #: in the :mod:`pluginsource._internalspace`. self.spaceid = '_sp' + hashlib.md5( _to_bytes(self.base.package) + b'|' + _to_bytes(identifier), ).hexdigest() #: a reference to the module on the internal #: :mod:`pluginsource._internalspace`. self.mod = _PluginSourceModule(self) if hasattr(_internalspace, self.spaceid): raise RuntimeError('This plugin source already exists.') sys.modules[self.mod.__name__] = self.mod setattr(_internalspace, self.spaceid, self.mod) def __del__(self): if not self.persist: self.cleanup() def list_plugins(self): """Returns a sorted list of all plugins that are available in this plugin source. This can be useful to automatically discover plugins that are available and is usually used together with :meth:`load_plugin`. """ rv = [] for _, modname, ispkg in pkgutil.iter_modules(self.mod.__path__): rv.append(modname) return sorted(rv) def load_plugin(self, name): """This automatically loads a plugin by the given name from the current source and returns the module. This is a convenient alternative to the import statement and saves you from invoking ``__import__`` or a similar function yourself. :param name: the name of the plugin to load. """ if '.' in name: raise ImportError('Plugin names cannot contain dots.') with self: return __import__(self.base.package + '.' + name, globals(), {}, ['__name__']) def open_resource(self, plugin, filename): """This function locates a resource inside the plugin and returns a byte stream to the contents of it. If the resource cannot be loaded an :exc:`IOError` will be raised. Only plugins that are real Python packages can contain resources. Plain old Python modules do not allow this for obvious reasons. .. versionadded:: 0.3 :param plugin: the name of the plugin to open the resource of. :param filename: the name of the file within the plugin to open. """ mod = self.load_plugin(plugin) fn = getattr(mod, '__file__', None) if fn is not None: if fn.endswith(('.pyc', '.pyo')): fn = fn[:-1] if os.path.isfile(fn): return open(os.path.join(os.path.dirname(fn), filename), 'rb') buf = pkgutil.get_data(self.mod.__name__ + '.' + plugin, filename) if buf is None: raise IOError(errno.ENOENT, 'Could not find resource') return NativeBytesIO(buf) def cleanup(self): """Cleans up all loaded plugins manually. This is necessary to call only if :attr:`persist` is enabled. Otherwise this happens automatically when the source gets garbage collected. """ self.__cleanup() def __cleanup(self, _sys=sys, _shutdown_module=_shutdown_module): # The default parameters are necessary because this can be fired # from the destructor and so late when the interpreter shuts down # that these functions and modules might be gone. if self.mod is None or self.mod.__name__ is None: return modname = self.mod.__name__ self.mod.__pluginbase_state__ = None self.mod = None try: delattr(_internalspace, self.spaceid) except AttributeError: pass prefix = modname + '.' # avoid the bug described in issue #6 if modname in _sys.modules: del _sys.modules[modname] for key, value in list(_sys.modules.items()): if not key.startswith(prefix): continue mod = _sys.modules.pop(key, None) if mod is None: continue _shutdown_module(mod) def __assert_not_cleaned_up(self): if self.mod is None: raise RuntimeError('The plugin source was already cleaned up.') def __enter__(self): self.__assert_not_cleaned_up() _local.__dict__.setdefault('space_stack', []).append(self) return self def __exit__(self, exc_type, exc_value, tb): try: _local.space_stack.pop() except (AttributeError, IndexError): pass def _rewrite_module_path(self, modname): self.__assert_not_cleaned_up() if modname == self.base.package: return self.mod.__name__ elif modname.startswith(self.base.package + '.'): pieces = modname.split('.') return self.mod.__name__ + '.' + '.'.join( pieces[self.base.package.count('.') + 1:]) class PluginBaseState(object): __slots__ = ('_source',) def __init__(self, source): if source.persist: self._source = lambda: source else: self._source = weakref(source) @property def source(self): rv = self._source() if rv is None: raise AttributeError('Plugin source went away') return rv class _ImportHook(ModuleType): def __init__(self, name, system_import): ModuleType.__init__(self, name) self._system_import = system_import self.enabled = True def enable(self): """Enables the import hook which drives the plugin base system. This is the default. """ self.enabled = True def disable(self): """Disables the import hook and restores the default import system behavior. This effectively breaks pluginbase but can be useful for testing purposes. """ self.enabled = False def plugin_import(self, name, globals=None, locals=None, fromlist=None, level=None): if level is None: # set the level to the default value specific to this python version level = -1 if PY2 else 0 import_name = name if self.enabled: ref_globals = globals if ref_globals is None: ref_globals = sys._getframe(1).f_globals space = _discover_space(name, ref_globals) if space is not None: actual_name = space._rewrite_module_path(name) if actual_name is not None: import_name = actual_name return self._system_import(import_name, globals, locals, fromlist, level) try: import __builtin__ as builtins except ImportError: import builtins import_hook = _ImportHook(__name__ + '.import_hook', builtins.__import__) builtins.__import__ = import_hook.plugin_import sys.modules[import_hook.__name__] = import_hook del builtins pluginbase-1.0.0/setup.cfg0000664000175000017500000000004613425371076016640 0ustar steinersteiner00000000000000[egg_info] tag_build = tag_date = 0 pluginbase-1.0.0/setup.py0000664000175000017500000000260513425370667016541 0ustar steinersteiner00000000000000import os import sys base_directory = os.path.dirname(__file__) from setuptools import setup DESCRIPTION = """\ PluginBase is a module for Python that enables the development of flexible \ plugin systems in Python.\ """ with open(os.path.join(base_directory, 'README.md'), 'r') as file_h: long_description = file_h.read() setup( name='pluginbase', author='Armin Ronacher', author_email='armin.ronacher@active-4.com', maintainer='Spencer McIntyre', maintainer_email='zeroSteiner@gmail.com', version='1.0.0', description=DESCRIPTION, long_description=long_description, long_description_content_type='text/markdown', url='http://github.com/mitsuhiko/pluginbase', py_modules=['pluginbase'], zip_safe=False, classifiers=[ 'License :: OSI Approved :: BSD License', 'Programming Language :: Python', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: Implementation :: PyPy', 'Environment :: Plugins', 'Intended Audience :: Developers', ] ) pluginbase-1.0.0/tests/0000775000175000017500000000000013425371076016161 5ustar steinersteiner00000000000000pluginbase-1.0.0/tests/conftest.py0000664000175000017500000000115513301414030020337 0ustar steinersteiner00000000000000import gc import pytest from pluginbase import PluginBase @pytest.fixture(scope='function') def base(): return PluginBase(package='dummy.plugins') @pytest.fixture(scope='function') def dummy_internal_name(): return 'pluginbase._internalspace._sp7bb7d8da1d24ae5a5205609c951b8be4' @pytest.fixture(scope='function') def source(base): return base.make_plugin_source(searchpath=['./plugins'], identifier='demo') @pytest.yield_fixture(scope='function', autouse=True) def run_garbage_collection(): gc.collect() try: yield finally: gc.collect() pluginbase-1.0.0/tests/dummy.py0000664000175000017500000000007713301414030017647 0ustar steinersteiner00000000000000# make sure there is a module we can base our modules against. pluginbase-1.0.0/tests/plugins/0000775000175000017500000000000013425371076017642 5ustar steinersteiner00000000000000pluginbase-1.0.0/tests/plugins/advanced.py0000664000175000017500000000030013301414030021727 0ustar steinersteiner00000000000000from pluginbase import get_plugin_source def get_app(): rv = get_plugin_source(stacklevel=1) if rv is not None: return rv.app def get_app_name(): return get_app().name pluginbase-1.0.0/tests/plugins/hello.py0000664000175000017500000000032013301414030021267 0ustar steinersteiner00000000000000def import_self(): from dummy.plugins import hello return hello def get_plugin_source(): from pluginbase import get_plugin_source return get_plugin_source() def demo_func(): return 42 pluginbase-1.0.0/tests/plugins/hello2.py0000664000175000017500000000003613301414030021355 0ustar steinersteiner00000000000000def awesome_stuff(): pass pluginbase-1.0.0/tests/plugins/withresources/0000775000175000017500000000000013425371076022550 5ustar steinersteiner00000000000000pluginbase-1.0.0/tests/plugins/withresources/__init__.py0000664000175000017500000000002413301414030024632 0ustar steinersteiner00000000000000def foo(): pass pluginbase-1.0.0/tests/plugins/withresources/hello.txt0000664000175000017500000000002113301414030024362 0ustar steinersteiner00000000000000I am a textfile. pluginbase-1.0.0/tests/shutdown.py0000664000175000017500000000042313301414030020362 0ustar steinersteiner00000000000000from pluginbase import PluginBase base = PluginBase(package='dummy.modules') plugin_source = base.make_plugin_source( searchpath=['./plugins']) # This dangles around. This will be collected when the interpreter # shuts down. hello = plugin_source.load_plugin('hello') pluginbase-1.0.0/tests/test_advanced.py0000664000175000017500000000103113301414030021307 0ustar steinersteiner00000000000000import pytest def test_custom_state(base): class App(object): name = 'foobar' source = base.make_plugin_source(searchpath=['./plugins']) source.app = App() plg = source.load_plugin('advanced') assert plg.get_app_name() == 'foobar' def test_plugin_resources(source): with source.open_resource('withresources', 'hello.txt') as f: contents = f.read() assert contents == b'I am a textfile.\n' with pytest.raises(IOError): source.open_resource('withresources', 'missingfile.txt') pluginbase-1.0.0/tests/test_basics.py0000664000175000017500000000551113301414030021015 0ustar steinersteiner00000000000000import gc import sys from pluginbase import get_plugin_source def test_basic_plugin(source, dummy_internal_name): # When the source is active the import gives us a module with source: from dummy.plugins import hello # Which because of a stable identifier has a predictable name. assert hello.__name__ == dummy_internal_name + '.hello' # And can continue to import from itself. assert hello.import_self() is hello # On the other hand without a source will fall flat on the floor. try: from dummy.plugins import hello except RuntimeError: pass else: assert False, 'Expected a runtime error but managed to ' \ 'import hello (%s)' % hello def test_fetching_plugin_source(source): # Finding the plugin source outside of a plugin and without a with # block of a plugin source returns None. assert get_plugin_source() is None # Inside a source block we can find the source through mere calling. with source: assert get_plugin_source() is source # A module can always find its own source as well (the hello module # calls get_plugin_source() itself). with source: from dummy.plugins import hello assert hello.get_plugin_source() is source # Last but not least the plugin source can be found by module names # (in a plugin source block by the import name and in any case by # the internal name) with source: assert get_plugin_source('dummy.plugins.hello') is source assert get_plugin_source(hello.__name__) is source # As well as by module object. assert get_plugin_source(hello) is source def test_cleanup(base): new_source = base.make_plugin_source(searchpath=['./plugins']) mod_name = new_source.mod.__name__ assert sys.modules.get(mod_name) is new_source.mod with new_source: from dummy.plugins import hello new_source = None gc.collect() assert sys.modules.get(mod_name) is None assert hello.import_self is None def test_persist(base): new_source = base.make_plugin_source(searchpath=['./plugins'], persist=True) mod_name = new_source.mod.__name__ assert sys.modules.get(mod_name) is new_source.mod with new_source: from dummy.plugins import hello new_source = None assert sys.modules.get(mod_name) is not None assert hello.import_self is not None sys.modules[mod_name].__pluginbase_state__.source.cleanup() assert sys.modules.get(mod_name) is None assert hello.import_self is None def test_list_plugins(source): plugins = source.list_plugins() hello_plugins = [x for x in plugins if x.startswith('hello')] assert hello_plugins == ['hello', 'hello2'] def test_load_plugin(source): hello = source.load_plugin('hello') assert hello.demo_func() == 42 pluginbase-1.0.0/tests/test_shutdown.py0000664000175000017500000000063513301414030021426 0ustar steinersteiner00000000000000import os import sys import subprocess def test_clean_shutdown(): env = dict(os.environ) env['PYTHONPATH'] = '..:.' c = subprocess.Popen([sys.executable, '-c', 'import shutdown'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) stdout, stderr = c.communicate() assert stdout == b'' assert stderr == b''