pax_global_header00006660000000000000000000000064122700367320014514gustar00rootroot0000000000000052 comment=aef60e0770211cd24cb290bd1de536d9b60776ee Pyro4-4.23/000077500000000000000000000000001227003673200124615ustar00rootroot00000000000000Pyro4-4.23/.gitignore000066400000000000000000000005271227003673200144550ustar00rootroot00000000000000*.py[cod] # C extensions *.so # Packages *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox nosetests.xml # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject # java stuff *.class # logfiles *.log Pyro4-4.23/LICENSE000066400000000000000000000025341227003673200134720ustar00rootroot00000000000000 Pyro - Python Remote Objects Software License, copyright, and disclaimer Pyro is Copyright (c) by Irmen de Jong (irmen@razorvine.net). Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This is the "MIT Software License" which is OSI-certified, and GPL-compatible. See http://www.opensource.org/licenses/mit-license.php Pyro4-4.23/MANIFEST.in000066400000000000000000000005301227003673200142150ustar00rootroot00000000000000include LICENSE include TODO.txt include MANIFEST.in recursive-include tests * recursive-include examples * recursive-include docs * recursive-include contrib * global-exclude */.svn/* global-exclude */.idea/* global-exclude *.class global-exclude *.pyc global-exclude *.pyo global-exclude *.coverage global-exclude nosetests.xml Pyro4-4.23/README.txt000066400000000000000000000010721227003673200141570ustar00rootroot00000000000000PYRO - Python Remote Objects Pyro enables you to build applications in which objects can talk to each other over the network, with minimal programming effort. You can just use normal Python method calls to call objects on other machines. Pyro is written in 100% pure Python and so it runs on many platforms and Python versions, including Python 3.x. This software is copyright (c) by Irmen de Jong (irmen@razorvine.net). This software is released under the MIT software license. This license, including disclaimer, is available in the 'LICENSE' file. Pyro4-4.23/TODO.txt000066400000000000000000000033231227003673200137700ustar00rootroot00000000000000Ideas / TODO The most pressing issues can be found in the issue tracker on Github. * daemon (nameserver) should be able to disconnect clients that haven't been active over the past X seconds * HMAC: Allow per-proxy HMAC key (to be able to connect to different servers with different keys) -- but perhaps remove HMAC completely once the connection validator logic has been reimplemented * More decorators? @synchronized, @oneway, @expose (also on attributes?) * let async/Future use a client-side threadpool instead of spawning endless new threads * automatic callback handling if you pass a callable to the server * Make Pyro support listening to multiple network interfaces at the same time (and returning the correct URI from the daemon. Name server is harder...) * docs: explain callbacks better * docs: how to use the socketserver API to write your own implementation * add multiprocess server (forking?) based on multiprocessing? * simplify the shutdown/close methods so that they only signal a shutdown condition and let the eventloop thread clean up nicely. This to avoid all kinds of exceptions on shutdown (mainly socketserver on ironpython now) * on proxy connect: query the server about the object. Can be a method on the DaemonObject itself. Query for meta info about the object: oneway methods, security settings, exposed attributes (to create properties?), whatever. * object activation / object registration strategies: instance_per_call, instance_per_connection, shared_instance (let the daemon instantiate your object's class instead of user code) * persistent Name Server (store namespace in a database on disk) use sqlite because it needs multithreading/transactions * Pyro-over-SSH (not SSL) using Paramiko Pyro4-4.23/contrib/000077500000000000000000000000001227003673200141215ustar00rootroot00000000000000Pyro4-4.23/contrib/init.d/000077500000000000000000000000001227003673200153065ustar00rootroot00000000000000Pyro4-4.23/contrib/init.d/pyro4-nsd000077500000000000000000000030041227003673200170700ustar00rootroot00000000000000#!/bin/bash # ------------------------------------------------------------------------- # # Copyright (C) <2011> - ppacory@gmail.com # Licensed under the "MIT Software License" for inclusion in Pyro4. # ------------------------------------------------------------------------- LISTEN_ADDRESS=0.0.0.0 LISTEN_PORT=9999 MESSAGEDIR=/var/log/Pyro4 MESSAGELOG=/var/log/Pyro4/NameServer.log PID=/var/run/Pyro4-NameServer.pid # Add Pyro Config # here you can add others ... export PYRO_HMAC_KEY=12345 export PYRO_LOGFILE="$MESSAGELOG" export PYRO_LOGLEVEL=DEBUG # Check the script is being run by root user if [ "$(id -u)" != "0" ]; then echo 1>&2 "ERROR: The $0 script must be run as root" exit 1 fi # Create the PID File touch $PID case "$1" in start) # create the log directory if not exist [ ! -d "$MESSAGEDIR" ] && mkdir -p "$MESSAGEDIR" echo "Starting Pyro4 Name Server" # test if not already running if [ ! -f "/proc/$(cat $PID)/exe" ]; then python -m Pyro4.naming -n "$LISTEN_ADDRESS" -p "$LISTEN_PORT" >/dev/null 2>&1 & echo $!>"$PID" else echo "Pyro4 Name Server already running" fi ;; stop) echo "Stopping Pyro4 Name Server" # test if running if [ -f "/proc/$(cat $PID)/exe" ]; then kill -9 "$(cat $PID)" rm -rf "$PID" else echo "Pyro4 Name Server already stopped" fi ;; restart) $0 start $0 stop ;; *) echo "usage: $0 {start|stop|restart}" esac exit 0 Pyro4-4.23/docs/000077500000000000000000000000001227003673200134115ustar00rootroot00000000000000Pyro4-4.23/docs/Makefile000066400000000000000000000107641227003673200150610ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = ../build/sphinx # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .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/Pyro.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Pyro.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/Pyro" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Pyro" @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' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(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." Pyro4-4.23/docs/make.bat000066400000000000000000000111231227003673200150140ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=../build/sphinx set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source 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 if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 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 if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Pyro.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Pyro.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 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 if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end Pyro4-4.23/docs/source/000077500000000000000000000000001227003673200147115ustar00rootroot00000000000000Pyro4-4.23/docs/source/_static/000077500000000000000000000000001227003673200163375ustar00rootroot00000000000000Pyro4-4.23/docs/source/_static/flammable.png000066400000000000000000000176371227003673200210030ustar00rootroot00000000000000PNG  IHDR=bsRGBgAMA a pHYsodtEXtSoftwarePaint.NET v3.5.9@<IDATx^ M׍dVQ,S/ ()M4zzMPk(O=!I+Z'}GTؽ{wMݨ>O9{@!^8蠃X)<-[9.>_'MZ{a`~5UV£R {*4^lC}e{o ^ׅN:/_VZRKٲeWXRS}obDF Tzt޽\H㏦MeѳAsev}INjg"TzP#`ФI I3T5kYLVjvVȌh!qt<"]KT_,PƱ7oN 4XjUҥE=`ߏ e ~x`JI :kFh#ƥNTC;*T("i0g4k"O=*P_OnrI3SKڵMDZtc ]*U+^ܹsRD4C8qH}DR_Ce[!f,M5Wh:*zjr;VˌߝGWP# @Z2pq^]I|/u2?aDJE]Y=oŅʗ/" |kfчDg;5$]'A3HKAl߾NivIDOIsUNV<.3Y>x57< ogH)ժVuj JU(ɓ'':jü/SׯH2շ&H}=);DҌMYRȢ=>ZXBbs0'WiF=߾K0`*&0ܯ\2!vSx͙-".HfD7t^-i@<E49E^FZ:s m<(q̧DO"E|Aoצ#e Z=QvIKw90v< Z4oQtQ_Mj_t&b֡R^J&{mAгgOqA |1V!F#,xyԫw|A(_~P?_~uO_'媶v{d ]$Ď1~bya Ĩ Y7 >}g pӎa~Kgguڞ:50 "(H\)3o9)@_- Of(quՋOY~>k(ܳvWO'qHuMelS,X_X@ 6Xʈ#tAUe~XrFc"xN+/;l޼y M6)# }Jѕ@ P^6ڦ 6 ?߸I7(DS sʔ)mwݻwIVKWm)SCX 㣽/̜0E<~(!zʎ$ H b 0uOx7A ^Zzwy"TgnnUnA;Ĕve]VbuR%M-/<\=E$QB94եKRmj׮-~SQ2$@@ ȝetGBoMVb.b?b&rI "d\k-_*[ZqKe v,Ⱦ_ ?E)ISuJ@q~_bMxk_ %TΜMiN6f*1cZ"#랔`UNl8o͗3LM#qAj{~>?' u]2BK͛]f- ((ojEϔou} -6tHZ LrV25 &ng7''Ok7֏n8? HZ>t-i+:*q>RQ  ~FN[rZ7u݋{lٲfcw ^Aizv+oqFs,Ϧŋ|hxDjrZ#zƒo75;/Fp} :g6"Zƣ)D_0 ķʚFj+Ngo4sGʮECt*Z鯅f Dώ;@- KGD*5OGax)Sdht;~DlGְaðP\V9=&ͅ#O#T%bjx(xg3%jv<؆ JdiL=an~{ 5,bS&<4aZ5_N#j'"iv Uxr[}w8]4<0aQS|ѕbn!h\TvVgidi "2~`qFg!]JغB/,=zd&Wg0oPj/[,NHi($=HxNIb ] _{%bBbXF x<^1m\|G#Ƅۅ^d^d ޢY3cw5$@fN@571Aͻ^\ {"ΘZ2 )=:oi,C#ÈgZ _yv]ڪlp< iG];< Nmmp7߬H6sES[Sa޿=`Ds$f@cҿcr,es\MqȃӃ^2yq{0rb{&6 %a0^w3:+l$0 lyf*O:N~X !odi94m`&#ꑣ(?F01cU]dC]hQ [{£BXP\zXpaRTЫ꤆Q &']TT@̙3&+x[e~L''i#*s/lrfոQ}E \9Eӿ5jXۢC5eذa@ ]&(ݮ'K Q;fSz9_GQ*!3'dL:WCZw޾(\EH“=Sq$(]ҥ_90f@^t^Km:"`VM.%h8'իW9&lP૯q!'7nX!,% R@x3up:ɒ4y la)a*;eiCʕ-ry0,_SZزjԨѵkW*iղ-y c6xղ$C,P/{@dط=wd)sV=uA{s7w(_/#Fl %PTGՃr, dH!vۍYdH) @ G& 6vy{S-g45@6φqKBF`6pT3BCIi9C|ëTI+3XBFK SXؤCM/;3vBoYneݣ)8ZAޚs( o/r :FduaQ`3W l/&7'')fa2wgkrl <9H3mS _+NWc^sݯ DƘ|d5uj;鿨@1v ,G' 8 d*h=H"m9:&s7s urNE|ճ o|QG[{ݟoDK jJQ!k&@>XzqKv7wF@,img֬ӎͰ ή^I6 ),uk4f">K0S ֔G:3MIhD|*UPĆfOxaPb. b2 *@^]K' Һ9bͲ?,[ّ"MPE βH?:[ Ε&)}^n`^(F2)zx$۫ɺ TlhD{z2%)v0Y $ۃQ5X*ier1}eIcly_|TxC- UΨ1Ja6vu9yjpg6$23#O |"~j/-?/XJjS2sV7T֚mfQu*@kR Σ2@uX Ą_g*~Pld~*ȇ`âM EQ(?jFϬs)$}e(63;9@f@,i& @tl2g=:ޛ3m;w37I~ǩBD-7.:L1SLB*bᮠ5jyzlR>{RqǡV^'}$.W9s`)Şz{*P}*2zWًsE\?d󈇜LBeӉuM;9?{O @L{Ǵr󵖿&9Ov9Mʡ@:\@πяX#$CoPqFhn( fA)}w۸An\[񛪞^ƨGkpT-΍dH_YFT2&90@x "}WցwF,F[/g_Z-?J9yls#{7f/ZYoU8 0hGg7y A_ K `>b]z@}sOßGl.w ^jZ$ƢG:& isi9"SWXϬvdF2BF({ ^{X%1PE>ף+<{+ ׿3R2,E U`qэ>jG[SW~TL9lw uA" d< ড়7NDЈcappLaRjobT8 "ƛu@ G7._xsH`_85:K@s  vbSQ i}lN{ƾnH$m˶~a3”| [ Mjf  ) @3XfLVյg~spͥ*g$5EddVs=a˱˱B!ȌAo|~=E~[r,r,eŁ$If’YaZ˷K9c9c9RLF^g_{=|ҘN0N23[~c9c++Z|>~fϼα?(R᱆u,r,G$I _~&= C,{c:c9[Jd5Q $Bf-AGk`2<c9c3L9So x co|+Z=iZOX"@+A/K - pyC׷k ?c9c DkM) Aw6|oEH*<==`gkݽ}zI.c:c9#ER("*Fjyڦomʦtvm $IǴc9c9RH)rjU!?XuJ:SW5j{SyxZD kFu8c97Xk1d EΟ !@iIT+8҄w|X>`||OBHhz:c97&eh%0!1ӆ }HiCJ!$)bM45di;ܸó%子.wyw{hO@2X $1()_^WA5P|rgn?dQ裵dH)=rɒf$IIwkGL]9SE!AࡵBI,=h˱_Zւ  :P_rm?fׇXIDZ!3Q\X@ Z ZOyKuii|ZǴc97dEڔ5MXco}oO? MS QJjf"o_6#)i˓Gm;C5c97X,#cĀ\d;}|O6h~2a!ɠr+i!`oySԥxD.Mc:cyc ~Bv <9ʏG9Srz´)7iҍaRTz%<)N4A6r BƔwer,o)AIZ[C~8Al%$Y+˻oojw Q~yH>RF߹Heyd'}whw s X $.Y>T{-ޜD,=$+@b+WolۢIͅ$|F~hqHY$!he>_ZWju$I2S96 XpL,&h =h 5XZV{ ~Hix,ydZ>x eK)ںS9c97LA-_^ᚭҺ"|ZA@3e$OitZ GXHRC9=Ij+i o. 0rCvr Xr,oc%B"!v&W8kɄzOqNRd{=CNfHpHV$;~ވg c:cO@u;܀pL0OcJtF(.7"e|z!]task-qj)\jYv3 NS!Z֝ǀu,W\HK 1H;03Z2@ IYУH ,f,^0f;>AH`d@3vyI^9,4Aix Xr,ɋ1)3Hзvy7wx['K=Rv#$¦m#5`XU{AyѤ$1iviR67D%( @X1lc'o{O֐?搷|iɬ/M WB@ďܐ e1!QjE%:5 mBj wz 쥊M" Z_(`9 p|uFȣ&f'w/B({wMF2umYNj%\ ;dZX1G/,il.1U)&nYbϝW4d؉ =}Ƌ/ah!3ήdUXEsX2Sqԝ5/ւp}w}moԾ;XKY ^Jz]29.R8̋>-2 w5.Ҷ!] 2a责f᨟X̠!>/t||^M"Gd^ZZwREG,an>$cA+)|Ujw+z 9C 5ƒ7%/xnnHA)\&ӫf{H;% VEɏ =;չҔˮn{oewwDI3OJd ԁ@Z(%XJǜ]B H?'Ca^D͏5rarrrq7eN-̒g2# *C8W%+eE=TC=v+\)$SJRȍBR6,|ץ2JDQbod=Ur#K<9IVTr9Zȋ}J$jd{ * <5~5(!eqM;GZ&~5,kiKW6ݏ~}i^\=e櫆(6AbV {}>E>K^̻MgMF"T,[e k-B:^+I(b|me~~n'>0\FEJak%}{f +$_tqRlf5G}a%D6/%  OZnb$y3hvE1w Ml"X`sEnPN*/[<О3 OAf׺&_Y'9c='RzS y(>Weayݻ 1F(h*dQR}{h崬\MK-fl  t{ C(ԑҫvi7Z/~8\ .Z;nxGi47mb2OD R3{ _}f<2kܳTb'h:ZO 2Mʥ|+f &4ovHN$NLWS{uRv?#/S =|-0o6KkT0 #TmiJ O>{|)|.r4ИfiQ#3QZa=mYo~ O (h#OZ^ާ:|!Ԓ/ tQ~'J.^R:M.}[J*Mn'Z"T1(.sZ0YFt;{7xnX%ҩYSTA-;+P(ŋ;\il2p3 I/2ԪJ G!绵$iL\pRý~rD9'MmR^`i_}΃TQn& esE5高:&)(A z:fܻ\FAYD۳,s뗮mo\ٷ9[pWlf}Cٗ=hs@nVgF-!Kx Z⩖Z;fTU6rѥ^h'Ei%MSzq0^fv<0VKW ~枧rMkPM3*;DJő@=JcG/ 14e 7V=8ZWA+r&`5k{O [?LU65`QoԴsu&W _(hu\˛NL)aon|*P5*-iu ]HYcy7Ԗ ʾs9 )ܻV)~԰{1uak[{]+$Vi=S7CܚS72M Q6HJXM&ď|Rֲg6 M$a9˳ IJz< R4eL)&*W)8brutܦ=r?z %[gn9*6t/^cT,?hyC OPJ~?~+ %{?k>L޿{^s+qI%6#'{|o|&6z G`4\&IjbYl:_84;?2z*ia x|X 1g&,Wr.176[<+ @j7T*eeYu585t>Ϸ$4{6JJDߧqɝ?|?]._ۥY/9-0; _Ǎ?~gVC0%th8x@-JHNe,53Y wz@)^'w.\pw0"K%_JN2})]W_9*~g?._{_4*6}({'uϕ[[-:>qe-wRݘÐ*+Kb_1L<2.%Jimoc$}&(B:oW,3vÐ]!Ii8GX*%EmDw-NnU#ͲrQBb,h*zK1 ){*If}K((5!r(M2;?̅!͒&3E㶦5b=L'\٦I2:3}MNv":tӤeO@K!F%:eGAXXFSmL5q"40=õ7?lqv9%3u*6vrŜԼRO\L\ڽjN !Uo}\J9mx:4Wy9R*oZvLX E,wT$w˩>K)]i fRg/H@v C!'=I#ڂ?Ksdx3ٚ4(Yߏ{5C{%w?,.{ma)8@{dq~stnm*1Vhh9G45v!w$Iw;ȝ8)zTO k_ N/qe/+ԉ`g-64ۚo&#ѻ;:~/5H&WAVyS`<6}/og 8z~ `O -]S{ ^Ekw/7CtR P)E!s*ͥ%޺q.]#F#r>`4g RiEx27}NCBe5Z{;#6LuV (#mI,ۦ~?s6&%Mf/=)Bpu;mN>})u'uȩp$C"`+a~|ӑGʣXkIgwFeD4Ѓ w0{fS;H^湍07ҢLyV:GC͇O7zYpVaa&2 -xF8^&3/'4 ]5l9>J;&I~_afMG!FWQ.E45+˼Εdյ\U-sug=檔JT23y+0W6/m(B{v_܄~}MH,tV{]z %`eӒKW6_eߗzzïW01yQB>滾CHLoۉRN6iYR^/y fTIvxSc;4KtVtbRcQZf"w IDAT9x'*|0vp D)MOߡ*3Rw;RcE[8QD%&YnifG+0JXWЌSΦXxxˏ5yٛeV+ezyw{ol,p'@ڌWb¹- ԪeG5 9eE*omxh;%E!Hk*݌vk^Odi>0E?s0 Yl'~~,/P8^LFo0_wGy£붳hU*tm19wjgWRÄwyogvNKP콖aZk 8a2;Ԧ#F>s>y R{L2=Ҁ@g#:7s.UX+FZ o |Vswh,EZkoe kr̷4Uʥhi>J4~)\r=VH\0e6;VnO5TZzrT`KbjY=Z2~L?`0(ULK ^[gZ WL/4OZ.o7ZNJ[s4hq1 dd5Yl+d`kRL /=MviBiY:J^!xO:8+02ԄrWZA,Qds6+)1^2cI`2+VŐc"rsLNpy pQ0GH6}r):-{ $aYlʞ?ZEVvtT0R9<)AЬ8[q&̶ߥ3$iJ02 i+9kB  ithX o!QfCrKނ@Hlw ʦ!>V _ {00gIЗ<|yOeG\Q4~p7n9%p߾ٻF?u( mcdm9mjү7>m!BFL<`xe%ۺ{0BPl0ݸI >Փs4K;tjN#Ơ4 O?āEZ%&|2~\x~KBZ|'9_vZEa‘X,r}s&Xo3za'oᅡ|ri8laPXvZ\7&Ľ֐%Wx`{Ή]bD(KW `߫YqmI:HVM@e`-]C0cR9x?X#snlGPĝqLv:Jzvp_$.|iHcMW>MgnAQSOrf!JwO3>K4#r*XfsJ3;~iMx] 2ck/̅6@Nv1c`tڊ d#ðsĮY"Va;) @ U) (T("%z(Ha"9cŚЬA) j|C 8^İӊ (tGRoހQEyb޻H{C"܁L^rߜ$- vۂn۵1r-\F yvKl0BASVt t&_0_R-S]ZR.(w)Şw9XPSAXJ4Jѽ #]Nפ换Zʳ|?r)bai?LT:(MS]>}}DJ>Z9 uUN8͌V~Ẻj1ЦIS=L">8-gfyZߧ' Шѽ.|d:X /}|ZR峛t;@Z0֋ -5TJ"x.heӦ(lo2{w4%G#ˁcmX{1ؤlhh6jU2c N^-/qٜ!%4(*mTA! 1\snq9sS^l嗀˵XYA61G_;dY1YME a,_ZوyGꡯh,/, %‡~UJ4=GQ1WcnJVR)2M:R"lQ <4C-9LAFxj2y~>s*zZB\.?.2Clm_M6\oc0SUcmgH*'te66GY$aA<(^Jv7+⩩E>=%m  )nSeSo/BB>yO`qq ,.4h6׫a0NiuW-)o*v2\-9"L&#UZLD'(Rʯy?'VY^Ydq>bfN^SX5 but{;:>6?>֊<ǹ2kQV-XPǺ0"-~*#rR$ }RZ^L?5=GU5t l )S( Y.u'.`֓6pz7p;Kf0ցi$>8!I)IXAܗγaVr;F64j>K++XduuՓ,/-0??GV&IO [:vvZ\hИ[runib#(ίXmP /zě_ IGϑ&.Bf4eԮ} \buu+,-6i4j˹IQ$I t=Zm,,6x|o`rk0Tnw k_dqgz20h9-KD'^Bk 8^Bhuu{ .pTǹ ~BK];H -6쨏DiB>qjHx X)BX>³|RxD8⩝!$譧hT}N<+;'8ybz^0T Ɍ^~\6k{b%{HE\!,]$ƹ oF+Am_fpsn} HH8籛h5GarZDfJkZ[XiuըScFJkrtZt~"vy3M7@ݹ HLGt30װtdrfbk0̘R҉=I,g'NO5ufL#%7{-N,zr{9Źs8{$KK՜H5EZk|O/ F/"\t͎lCSK| 8֭Yx)i.^aJe?Mv)HOӀ4 ?Al_ mĭF~{_$ω+=}sNsSX1?G<.xۭ+m~%I+$&؟5ڗCT? vN.s)s%TS+,f@T#gQ" vZD~Z޵y=:NTR۱^{J% LXO - kYvwgE@i6faf ^OPwp넔~"X Ȁ}B$vuIԍ ,;EΜ930@9Pk߉XkְM1#qیwM\,Q)/?sN= MWXYYda\Rԅ3FmBhq%M>R*:YR[>G|ofKMvʯZZ~/p)/zR0T3v`RuZ4]tEi4T*t8j12 W|a]֚awgeqiŅ&++K,//K#ͪh$=z'lJݦ& C`Pt;zc(?ڕRDbhffrXZЏSZda>('%R+QcA(R1q|`|{*Ǒ kXZdؖB^{GL%0m* ,;MUaݭõΑXUX^j5@}7mZK*|)irLYgGܪ!fP3{C{gVM͸FfM{2 jscA*L-wtb-xDYy,.4i6XƧue P޽cu^eiefsnĵ䑷^qE\h7E>~ \@AuM2S’±}4YijfZH*=#m&i130T?)6$#IQ5hhd'DC"kB[z8%?dtF^ѨlԩrݑNE*iJQHZ܉:!ҝHXҌrLb,wr^}u-zn?ϱ:\YKQ>ܿ_$qT"3]suRD|V8'ԁPhl&A{؏3ыKLWf|ʹIM+ȓjd<0G'uN;"Bk^Ho ݥ(nv?=#t|l2'GE UR'[rR95k/mZmOqQ+e!go)jB^ri1k1 ` RB%I]9<<355W-͌:c@ >9\Xl:J:s0ֺp.ޠy,7*k*2Vì;^c\%ZPɫ6ON,>2-q:*ZJfmQpcGVIeboŽ`f0WqJ v-J+*%p`4;(dR:y՗M'T_F눯bb?P~ŗyy{@-n;e~48\JR+W%vRpFb5m <|`'!a8RR)QKVsu!yK$yFHO#ЂfiXEK$:k}[ߢ\^;IKG 7=~J_ɗ/שq:%D=^\]f1WTbFJ CƇ*%qUj0a;A3xzfR,g.SQx\' <\C`~d$#pF0:v/$Z(Iv{VMAǴ5i2%M$IILPi#QcytT=LL4sSS$Qq߫<z؃ydm u#M?iZƔ IDATQr3R{.~TKK6sY,[ڕ|?}g?wFɡ{"JKvlgQu>9wڔD%o}`/~VFQR7>{AvM{~XM)/<զsČ($}ּZ!38K͟>e=4P˳QPxټF))5N\LTS&,qr=8jhr}3:J'˵Uj A`f/n|voyޮ?x4LuB$YS)L)!D˜[#ֆa6O-a6R )ۇRLV4S)J![KcCՠmlV)"͋:28ZB NQ r%(\!R|]u5w%ay9gT<,de±.aޮ߳ݻپJ0+O9l^`$' ]歲89ᜍ^eӎw?kSnͽ!G0pgѧe&׌R˲q]8 pAon=-fwa,&7<:sf *Ц6v4UAQ'FQ(5k+^=~ؔ9NImNqSkI*[LQ{j9T$mi¨;N#{-W*7HRUx? +`vYPM|ίvFғГ/z"J{:?|b+%8wn^ZbdqMݤR-8=,8sVh_ijncED. G1M ͥvpcbEaJIRc75L- 9, E@Gm;bFIᑡ+W2xg}L5t$$P^+(OXfuy:c4!!clu|8i6]~ŀ/t9 vꖥƭᅧz۴{=z&k(b<—)V<9Rl]#uk A2NJe:.sxp8* [wLʔ _֌"]0֟&do ;3X節T*vFyr7M\:8@{5 B`%É:F>&oR)Tl=N_;"YT+Y"%kǏ`ϼfJv2bV"HY9<3LS[m:,/-ԥiR(^da翞Uв(9DMg+Z( j/3NR~vQ2X%IžpIt&JfDU } t'50l#)-UV*;Obpw3kƳ %9jaB^{t 0Vja)"ϧ^1_>/ @[9}(ܟ w[N]|#C*n%J‚P9gߏK[Rbew?dlX]^ȅ8\Oɖ}HM̓;f,uVbn~*F /LuemM .>+20UY>HG:2QV(9Ou-.c0̥2r寔Ɔdc`XC-ph۹bDF4Obw8  d5cN&?{}I*uJqք̃VISM$?ާ>goKpyZ(}*V^l0VYISg~TV1SdccC[4hT v8N1_2],8CYEJ*_?5sW~W?dOOzN+YaYYi?Y#3+UT'O:)I5v9 {'N>X(%C=*uAf,1'GQ;'feZ-ǹF&?sK.iB#LdVfoM*K+K\^Yʕ.]ҪF( plG{g^=eMF_"#bfW0tec1\b+<~fǴ9)6Rl*$z iԉHP*rB"8sϱ0V˱rmY&BH;4O`w1ԤCw,:ׇ?;`;%&-< )YaCv_O-UO74S簒D;!ۦ aMuV{БDqLn("릒'LqUˤn [eű__0#{]\`q˥E^K=zFGxb0N.D1upy}$$W%l>TP}RyH%WX5{_͌I;ѐl.RbUή'T:5e34kU00 (.Y)T {)#Du 8}9je$X=PT@c\95ЊXy5s1Pl% .)"svZmtIk[ #?&yJ@>ISqtOY>ws=s9Zt"{O]Ş:shԯ=Bx>5Ǵ/bue˗|i.vJ9ol^:{>'@lץ9X/K_t{=!f Db[6/ {IUQ3g3qc0BO疊DB"SWfT/E|vש\8$^Yk1I6SC!S/Wyv32clxW,*<m pqX&`(EXs? *p{7u7nQ/zL1zy"LilƽK>_0 I%FT\3J()ˣÄ;R=qeؽЍ}TzͧcF)Dnv۸$g",DY PؖSpc?au^fS/5)^IJ8;-ɷ_X^nƺ(uJKZ#J1A՗vZS.Dh>{w.]! `$tWtb'cIv:ÏG;DH`qҒ.tMb`2tY,WEgT ظajX<䡳#RR)̸ qDQO F \"k[Y袄hSȰz %qáb,:JR,&%Md%7`].QTTiɧ; {QT2F&kΌ:$*ppm &=$*R#=8S=; K*LqX1)^F#1o߽DP+40%y럙(e~IDuaZLgc;ShNγm|3%[sGJ1jڃۻ>abHgGCR*';w vGTc!,iOڏ?y4uZjc)58V:?zM~ž(1\ETuN`\ر5NYX.56j.^rHTZu:ZV{mMz6P!0zu^y Wuz;x^Ƒ F EjTid*\BVj8tL0!f`@ A*#%KRЗVt/Qꞹ,pH+ï>CK/]QlԨVJoN+=+phq8\_)-mqp|H\^! 5-L`) n?d7/k9l/:kÄDVH ؓJeJyzPDx.n3l٬<](:08 F GÄ85JRɢg8j&Q!eڭ:fzLxLJB߻%r9^jXbcctŞX2yu1Re\Z\Q֡ffAݠjjթy8*L}V碑gyZđJ}ևM N%H D)HԠS(K UZ./R;;J9?Z i3}qS@>!7w%]TWN>%tqDb[8$6Ol eوu\9TTvJIsH v*cܔpx8M^ˬ,qjYKF'^Z.Dr@Q#Nyoit("j.l^ek*{ _ĒCUY!u|,tE1Ÿx\=XWRp +Nޟn aP9 8U+sV VXޛq[V=#2Qt}觯(ttu{{_3täw?{Ly%ʥR>MJr8LxPq$=zwP祸fi5bzȞֲ(+C* K|k]cXD!5nD߿I*(%8֕F؛&j(Jdzae8-jFbbŅ6f]ZR-džٌơLA[}I슼ڻ?'W``z}Cnoq, [Tc."1 |$p}ZWB Uĩ/$ݸ{u^Vv[kCsLOg² ID2q)V]=*ϲ* 3P}bv7 ~wtCc",,!ZE8W#D2iPu!}S 5kOmm>JL{Tc0\߿o?(}SDQ [aDJ4^oluT2RhR9!oGK,-vX]]byy!OuteHbw?CL:BE_\ 岮>S@;Cz0 [#BJea9b);nX>J_pܘ00׏EXs}AegS zQ>kcRC$>&f"VtyE-&ndNïq!x~)œ'5;)ժ ]%• Л+(&Zx4f4h|gAG`)P l%p4UAYB7Kk]aI9X3qZÌeh {c7YyEVz._Zҥ-k(Y/P((NJ"yRJfcXG-fccTƿ}pkiZH]cYg7F: u|Ȯ(kWp|T5Zry+\RC6k?>f )d*uZE`A Ѵ4U!)SnU6o (ed,-tjt$ݡO?K2ZtZKfԆ4GaE9pXzE{\^++v[: N[,IDAT%1.E3qSo5{xafBYR)It%q,;x@hOWxDr)'+S^LA}6"ߥ$p=' %`E{*-NsDZNo}^LS@Z%y| T]IkeŅVz\%zybF^wV\ 8x#@XqoVzZuz{~G|o֠{ }@خǧ㍻$_MK(9F& B;Qڶ4K njW:xS,ʈY*&)vQl* .ˋ._^ҥ%Vv[je0qܙj&RZ?poRL/ UIQlIgDx9~Umk{?zX7(]ZR)Q)#_J]R Fc^+:j3 }]!4*]ifWo6 B!O0\V괵Ws,R.ʚ~nv)ȩ䟗RǾ~)JXaOdWHYkxBnzJG}qkfbe/'&#1#TfFF/ɜ&L/lGwv?_1Ψꋗv,.riRVi\o//Hp}?kߙ4OcW) |EVzMi[qmCL4%}gS.U˔U䚥T(oyIt²t; 8NqXԫx~b?;FJ]9)a2{a#JG}u9WY^겼Rɡ҄Vg11;8xjM+%A X~_nR%]ͪt5qu"q Eyzd|wn͛?bʗ)¯@(T:&Mg*%)a 9~",р_U-V&fjdi|jp{7 o;Xx׫ԫ%jr;4vЎE=6:TGT>.QGZy'VvcLb31oZ6BȃX{FDKסѬi^Caۢ Ygӄ(j0eef aF0ޱgI>/rlhw,,vjK20)a80zVI 8"xa{西Cy%Z]D)4wX=24cO댷SYyeZNb^bN;,Ly R)!yP*~lԼLa=_|nI3LFF`ϟ}h;Jc?@zkjղܓqQ«7Y:(w( 9k  ,\x PJ䓀3+%Σ+&s,nQi[fl5A +^FO\8?`po[ٺ1{3x|RTk5_zzLӌ[Yt4U* q,i&V)W7Suծ$=v,,je0d2KS'No-xwDT*Q79K>;8Bqctv@ elgZ ,|pe9*z̍)dJr#l9)cGA1jFJUjni6/*!M+~^KT ~RvXfopHÈ<~yA@vJFz"L\r,~G.Gq#JAHkv/Y^Z`iB; M*diOOy`SjL^ժ[dscu]e;l E?.U|$hHrtpg>\TbU2rDqmShQU+ q/drINJ8yF㿡BwbWf-gUl۶P<'J)]έ [;+kh? 袀mmbߡ=-"(̪N NM uS;׈J:mOslF9e&E䊞[q]|Cyn@TZ+QUת4U L^% sx0+K_{FGG\x݃}24;d4K%Mŧ A qO/,~ 8T.8].hTtt;:m5h[2Tbڏ&lx ׂk8QVv{&ۻ]vvg}oe41ȴ0!8q"dduT*%j z|R)DM/?Jy `kT X{)kt^9UML vEWJ;%u}j)LK`a{-HM;B KhiO؟2Yu#%U C?:Q@)GQ:䏃vqk˚w@|Rii'ut4 nj#IB$qZWs# <0 C64*|#4kO;B`ܽlG슈{|N"ݼj -]Iû_~2#ɷ %﮸-|MP$جIa8\h^쐜G^94U\BEZ"'M)Jf\#y#oɛHjN?F4!L'uV ݥDrD-2 CfsH$9)^/$D~L- qx\f4p}Gc&)+L$Dz:5{.r\:f_,֟y|7'=LISI*SD)qRinv1-+-M7Ƨ;fy&bJLei"Fh.FVYM4J(zu'*Wh6tZZ-`VI&ٻ t٥&wr/[l>Skv%ltr$VbPժ 8 F8,!Y4dB)纄i@6hYiEX3s=M+!;HB(ي׏ eflJe >ȮSQ8Opnc<>}r̢PCHwVtڍz, |_5:tZ ђ(CIp⣇z}ȳH\b !(\1qhTT3ѴÚ?fRrBR2@ l @X܁U5>e@X]6֋ ϔ֐wd iMGVlZ9BBn>LT+%-gӚ.09OvL4|IƜf=$aY?9wnWwi4a<G=t+Y(vXdwfFӦiRW h~RZ&g(eqB]ݯVt42!!3+&ʩTJ7tw(Svh6tR)F+G?wgw8_engiyZ&XhjE6YK؅t5Ky-[~!U")Wרs|ՠR-MpcƔ2C),HCpU<3p{ca h`K+ d@|xz3ni7iL#ep~6ggYeY6ʶp3H?M%R l)AIs>$%_^5mAa.NK8ÄW^Oۿڣܹ}ǏCqݐ(nSVh5kt-zI#eNivNiT_}⟿9f/(o fK+ Z>OJTP5JPhGxT+1zM)@g:j7/Ib>+8hTQ@{Kix45yVP62OmnsM|>$k.]mJ.^BJLnL (Ri#~wQhmRrr)V]0J(|œazcz.~j5'Jh4B [*4`fF9ULoA7L&@+ǣ4btpDt-VtZIJJ+RpmO%I0zJfJ?vqXaSWEI*EQHqHFczClHӔ)qliZ-*%ĵ/Y٠X/96c,G45هO prS3q73~O?"s&x(1OSm~[wq M>.1ĥF:}X̀VSpB QE Yfioe*(YceYA@$C}ϨԙRjLoo#T6մf~rvycX@FJEgZ&c?Ӵ40zuʷDd 4nJk+7GלD-4fUeu곭9'Ke'EmnsI",7=#5#8Qh wP-e{=R4.֨6+ iLCi`|&o8h}~Id0ZE!4fa05*mnsY$ :q,R~]D =3Bi4YlNv)-uqs XT1V]On֩_Y ;mngR `<?|&o=[ ů:O8٬mn71b|fq;mngZ* G|Maj:8A45-|r<{8amns;Ӕyu0d(v_Eq0L zBFDOZs4 KenݒA@Vbfۢ٪S&gݮ7wXsNl6cxp6޷J1jY\ln7U+DFyPkns۩65²"L*E!К<|6wXs4!QlVbLC" JLeӛÚv !pDZUJKAWZs-KӔ8a$4]13!es5\RiZ);-IY9= `4YLOV$ O$sjQMV8U6zz YO=]i]G^Zy"RE@fNd-ߨڦׯa>E#,pqIoN8`qJX|nqp3P:-yFv $gfu fm"raںzԵ4-פ/_-l}XlOMhy$‘_`ZVyEmDdQFT!dk!X@Uq6;J2{A8WcqIcz1pC݂t:}X70QI L ËZ8k{3 B5ְm2 lj V'uq&F&y1U##cO866ؚ)o"dWYE"<3ojlUsO0.5jn~Ɋ-&iH|uM_PIصiG 2y" B`h9 ś 6J,dOGACR/{^gD򨧮cFoε|7֫W:׍qaIY9@DYUZSϙĮSO=$8m.XG`QO"ǜa mٶOk1v߀TWznNڠr&2/\EUP TWMWѢѦI 67IyHXCD0w&f5+mV\PӊzYāEoRCzk:F&Qo5aoFkOFhXޠ4+Eo?CU!|kRzz,IŔU( v$2ccay¿8\[%SKvU|UW;4vvt Rj}E%{6"B4,('j!RWvuHXƭcы񏩭k{i}$p@ԨNh]JN] b'G6uDq.|K\ ?9y9s)%HY_{ɚ߸1yXӿվvmM5ê) -^E`5q:|Q yLZj"DX8Z6k%i$iمBfb9ᚦ†R|l<]}ã/'_Aj(!ωRjbs뫚ʇ l{?xp|{h?3 -gb|8EJ|>8^9ZV[..nVねENj!\E, 8jN?IkHH({>=74::6Kjl>xGnfZA ,[GKcrJOwskh!{W*?DG^p2BqD5ٵʻle{]]n/M׈Z&g𴤾su3m5׊]䟜Ҍ-m~ރ|I.q#;zo2}iȂiwwB +ao_ Oh4WyT!BϪתru%uM5M m-]-ߣ:G|  |MY%ccTf؛#.E}=bdb&u-Ki.nށ \ &&JZw9 P`?䐝U|J‘pEFy JEDE2~g|VV֕T V B<tְٱaSf-N ~% pNz߉쒊v߈Q﹕0݌㺯v_RI /k9]PPYݠ[oWG-Vgb^~i_c5ѩbnfT9D@2-PڮJ-y`Tg`Q._zW[X_M_۱ikv o`U0Ϳ?d񼽩{rM}IF@xm/N)~9GwAy!;Ry>r|SRzդ*ITש@DQOH?#Gȹ(­08aJAgD6-3/3YV`=UGu 7c[ٔj< Hlii })S*nw_Kҫ:=H齗{'R `NiLjB{⳶$g`hU8xsɁM6^D$ԛ-^MWǭg(&$d?G0Ţk{4-վڢV o-7?h Tpǣ?XsM+m;X rzy\%hX\E.y jhhe5. kJZVuihjꘖ^4 YwѣjqI1i Y)ٸ6W 1XӨ"抢Yȥu;+TAA7"I'b8Ѝۉ=_{522t ?heidTӀ@,^4Kn)m. H 8#QЮ`(`]!k1ZPRR д%{U3iQeA)B(4/q_فE͛}WT!fbTmKX߉+XfX?!e';>H>dJgsX,6.{z\b6 "}#[S:nͲ'Y5ӔF)ͽ*Pħ0JJ7GZagmg,TjWBSRĶqk?~pX?Qc5qSf'|놦v,ϹTaTy,RK2ɋU_ AK˨*'NAJifKhiY%&A\$l|ֺŬd"8A^^s\]Q?H LsEP5e Gl㒔K͗#:6 .L뷫ޛ$]hftQ -Zv%eiꃷ-^q1*تۉ/р*X !GeӐkGW͈OluQ=Z5\1=,4+0TKv𦤌b$&Ƕ[S8dB#<gkZVBZ9L$& X|zY :n IY@->j XC$} ϸήl R*^_RXlŐwE,XpY& s7# QKb=Ķ:a䤝c$d@q6k(.`⪩0>!p`5N!XX$$XI= {6a`r5 * m=AD`6DzWqs]``\*= FGGC=a$a4aI(9hQƖ*|b Z2ab9_EE妞 |\u~P-Tԑ=BXj5 WUaA;8)-XEǦ_߇ŚXTk!ַUݥnhR?x,i`U ~}tkZz\Eu1\_vScXx528fj:B,tLcY'fAѽ[^/9 l,20BaWUפp~FM:)iY-GiuwrLO.I_|A<=|Qt|?AZ`M3w -6VB[b5ѝa`U.ݧLogNJ봈5W JiX`LvQr{: ) ~JDh(_qtCw9s^遪)C⡪GD%EF'ƥC.L+;,UT"AU9Zٗ^2dOd1>-\QXU_Hg-E]욘q/ߺQJDCEDzFKʿ3h:Ux}Ci%gp-ƙY9%S"'yPlz{XWYx&*ȊS5pyO,*`!urDv:_VߺS\Feb:R56!1tx F,1vgU ,OBޯz`I2 Te|tT!{}0e(s'kF,M3Ȟ e5U#jy;x=CG}V7B:nu C, OTִɩjO4u!HWKEXu9]>*)9j~HBKмXV1i:0 LQ^I ;baнY s ><2UT(m+KXD"|jyx]X39-v>/{]k`Jj.a2X zIi6M]'͠Suh Iq5*?#M-;ϙR'[CMa ~a$sQӐP#}j.ѻmmd2w#Z<[rӱcrjfAe8SD]}/WzoF AX,U ՏGm?r\/ jm 8=p,'f-i(uܟ![0ZO s7W%xܢ%qmmEll! *Sq;RX|BU6Iben ]C{Ep4x$Tl|UzV;6]X[ڢ݆+|B'4 )!@M4>޼~@9**:Y/Ǭ %bgdX|$sh byb-Y-uC5= 0ǬΤKb튻,֭}"׮fܩ$Fk+{_Rײs+;J7M}χuH0~P3q"AhŽf>` YU|\*[|!hԎi!xL,\Az¿~LZH򀊏[@6Xï҈hXc V(p/7:fQK9 AK> f ^T]K++Kc!Q(jmwZ5k|F lzDlbˎ gX?;:D!,0xTw[`P *4Z0ֈZz``%$J;kU[24|7lB.`-5yMؕҲj4%5S&+ePIh@)ʀ 69~ cV|%eT|2>dt4U1/š`l=*%)"MFw I(Sx LPUS=89cQFp͞e02);} 6[-s%E|!MEl,x/{\,PܵE4dU)€zk{{z +$ ;^!e9b!XXOJ'bq?f܆F'$s܋[ ~XTk`jg܍CZ`p nR$봸+- vJrA< nv?4r:GD ttvÑڽID^ IE QmPoM h %Pu9WV#;G LKcir~?-Ps}q` h&(E&qSJ5ED+3cbA )yX]X'~)s6 O ɫdcS'qa) ŝ֝6"rErǿf}`UOImY5-|RgpL+l5ܟXkFn=sˤx^9 S@OYhtdD3d`e0X(@Q+zO^U GE=-&&"%!\*rAH]"!jP^!-%O];$+,n}e"uCdu R.0 E!2'PI9`k랚O+D%x992 ZR[%,pS^}J)$ดsw"-/NO[\PQմ5Ś2Jq֏sH~AA_ت#Jic,uK+c &N)yX<མVJD'wb{ՐGIKtmeC&ᕭmiձ:咪H1gu| ,ѫEJn` eT҅bZPڶ95cjS0#DjԲ<%UR=vINɥE(@^?7( ,Ս;eXgE_@=zQIRQ..V8/BAY]!X~exn៛^brbp10WmlV1k , S ݴuuhxWw-*ͳooEM ;XUCb[x-0Bx%.^~Ez\AęYJkX"W3saZoݠ*n`kpI([g@K\; &3X}ݪ֖ Yȷ 3U(c~Q^XZ ϙr=؟.e~d,z0zq,S.[gK{Z)jwcV,?rI ``y/׈;uY Yd"^oyYqUA?bA Mylb,9HGo'Mwg0[n&iN쌎ԯgzZV-ʰ8.K(=!q`)jxU+@E԰{ BF%-#V\}V^Ru AFIW%ϔkXrA+Y~=dХWތ`b\ጨ"* 'R<}’fc4JB@Lig*G5 Q@*]c.*Ϗȫ;} Ъh(o+<*FFf!7maMnq;j dUD cqXyX2:e5Bq گdn4訢z6I P\r|.,?0R":PG?kAlYR1l7#V݊Z5? \8uF)%-Vyir upyQ[z{6cٝRd%嗀Ѱdx'Dǃ S)ȻDl*<+jtMXwR,%3,Y!FqrZZB ,ĀF! T-xmS[N^"*oV0gUO;}Qk wIY2'o/o:B_.W8X7l- Xk,sy%*3ϙb_QGLTW- e`!@>jEIh m-74;$9rQ~7vWPzhd8ܫ܀T{Ѝx9%5-mܼq}}5|\yh~HYY_(du\oz|{i$K[ӮG2)GUXX8#F޹ eW9cO)*" >1'  HHdV\qfkP]9m"ٮ6XFZ1x閫[D|SP ZgkF-z!3 5 IR:42I`H fP4Q-BXci]m"$xbeK ah9ft괜-GǂlYԽFS`fYwtEƍM09zU/x/ .jjY&>9|tN[?tKN%Tn/2LBsU7$c'dLޢA2꒗J0)X4*HYꬸتp"Zv{q:PiuwVܢ,IЎMI-tH'䞒dHYHfiI'!#ORJ%cc2ZdU'Kzc'[HU}!Xh ZÕ=rVRʞasdliSBڢl5usITQzncJ>r,cZ{mrvI i}P߰u}ŚQ6ñfO sIYzݪsc1]W$̆@+B܋~;{K6_0]vs?94#V݌$Q!, auY6 J:> Ц,SG{7h=t ZU?Xpm!*pOI82 dL-l~#EgXz3 %|τtb:l B6>T cXxшʆNfV$vLuLD' A$a#{UOC+ƇH늊1s+W|:q]uo\ӈ򆵭'fqEu{PF{.޹g[ClɩhۦY-nAx, e c=; 㢌so( {7Sg蚊Y `( &W󎬎-.mt plp pаd'PWTseٮz )ŁXJQ+~5y²7IV^)/bJ38kZ,N-n&~mSYF y=(OG[ǟ6e5t>Ǵ#Pёqu&4?#C&VfJRwOvl__nQrƗ[5ڦvw|Sk]Wh~f X,8wuCG&p%Xaƨ^o4Rb *HYJ+.; o9yYbx/ִbd}_/( \@怋}֯L4G:yCiز'"uYFc +J_`xX?hH Ю{A@=9jŷ ସLxDJ^Oz֣`%c5)ر_I)9qx%J/>Àn2˷9>* gg_)𤊗 QC"5 2 #Ng!-;q>18yeL4@j{P AI|+Q`0 0zcSPix>eXY'qYlfmfj2  ,Y&6SCCDg )3M`(,F- Z ^@6T\ACKh8VU襆vEJݟvڰai3g ǧI *{0Eg-#"fΠ-l2h|2[+h1;ڌ,sZ8.X3f7b =>nB'.] W]u`8.㙠RJ9(l Z'*#Mk9|6`Mw`˴trI'XB? >F0Ppr&|h"@lP`YH93?ABlW*HuLm'$*d;U{ƞ}7Bف>MQ\QӰ@QaJp c9hj8=#SܐyM˕SŨ:86^Ycf֎9c3=V*:6@?ϵ^Eu ސIH)ԜjɟwŚG?ύ'\0>wJPr3:GqCbP+ N 𽬹wM ` KYt=UƂ|d!m-K8JDK9 Q!HiGS?xђgxDҵI*ʵ oSW,p,wdux`ehMYyL{zVº=uw|RQQ;8DB9wZRZ.<$LQn)PB @D\++Lz-E-gw]@ <r L2S v=xmnsxeo^rLm Bsw%C tq%7"Ee'S`kLk!QhX=d/kbwяa_l[d,±FJh Ic,S+sNDtȁ+ܠr]{kh[ )ƚvklRR LϘF] h%7I!<#8 ɖч_Q̎GuKO*/ta Tefґ̜A.}4pg{~`re&`]aQ&[{Id(8^vտf ;bNO왙<=яq$  >c{aNƴr.EI]4WU֨a&;k_N'T#@s9 o ,:{ ezjdfq↎E? ] q0{/JHjڠP7{tEҼb/ _c Yq-h\0*HaV( rCʛ+oEȸ+)i%;?Lje󨧬U_qB{5߉,ADqDx uHh+j"O[%^oPddcd#Zϻ @V@3kLl*:QO};K+mŢdԜwL$\[1iKk7<不F2Ut,Jc$~q gF`$‪/q(Ⱦ\k Xܳ6|N⫵p{^y~!?bD@z ϼ(rqDªzS:ޅvIeE>'s&+i/;2"ˏ>e(|N_e K8 $G9W6[61t޺kDKEw.3MOћZSӄo釓goFnJ[n9?$1I|§vq, r~QhC|z*zZX+auvy0MTi9&.t*Ace3tyLB =k:[#ninlu1,8Cqt0 ~0O axÜYյM?_=QZNWQR\!0*FPX1ǭ eQd9G qXAĎȆVad(PҲ*:+ XdbG5a@`ybHIkHx4r]+yTFw?|k0^Oᘫ7^={su|Wv=%%tj)Y*K7K>:2lƻ[` `$L \vb3N^Ģ9 hƪc2aXf /9Ff$kS!dxb} [S*i"{EZ(CɜnzjauA^TL L1BxkP |t[^Vo$x"t9Ww6oW`1ٙh'Nc^E?1Z1p:gcx>>G!1&O(4;ըWA:`՘@,ӨlWyblD٘v~u79bKY -9{I1Q4?8o] sfS=!AU>Fc7/^#7\7._PsEҹx!izp c7nsn~#ڲ] 9698YA5<̣8(uI`NnqG'@AXQi!ƨ͠b>M!L%ޭؼ4Fbp9(B7 )?v]auۍsh'F&}; 6<2LDɍZTA~X\Z<,mvݘLC8 3<"` HQP+yX*|NHMMw2C42vNd[ת]`yW6` so`AS3Pe@B S񑩽Oj|Bk ÁN~!NGZVn&xAmI35>i5MtO2<2mh$.[Pe|6'8fFF9M:>P~2lt q| ^Fs'p "7Y=pK;RVtLʽ#=/> cIx[V݋?LsЇsX#􁲑CT ›}qΧ^GV?i[N= ꖪ N$tg`+)fk`gT+ä́;,kDy8ª|֧pN3i&4~$YMQ~GTg̪ϢcOpWC˷)ƃ2`4SGN,fWN nԟU5}Ő61~|*|_ {aC.F%tdT2P0v̱>AdSbJ^(eLQvl`U dg9>D_<%d&C~qwIENDB`Pyro4-4.23/docs/source/_static/tf_pyrotaunt.png000066400000000000000000001055761227003673200216210ustar00rootroot00000000000000PNG  IHDRX cHRMz%u0`:o_F pHYs  ~tEXtSoftwarePaint.NET v3.5.87;]IDATx^}T ;  I L}w߽&Xַ>}׵Oٮ; w@x;1;~:⣊w ߁=_~inoeO_fihh똣G;]~믿*֤oOg=y佟7o~pŏ._7aݽ{H{[l鑖moo_VIIi{N:չX=%44իW{oڴ8=#3+.!2$,7 }d`pX9s-Ϝ9s۶mc/]\o9W%ׯ_yf(]Yrr pGOvJ]>:#-xEM֭[wVwqq2d.]<ӯ9 +#Zzoê+WL=zHNIdHVAMNAUFNMEE ?҄{GO51:X##5? уwvJ}}SΝHMUe˜1?y޽{kkk>3667V^.Oݺu#u_~9qd-ɓ9;;?`bbdAPHȔ1# ߕkSTPzM//Wijݸq'z8q 3~ġCfffN>կ_ r}}LUTJJJ<OĘu9}iO?4>ŵ=[ZZ*ںuku!pH;A˨NLVF),---=:zΝ;9F{{{fn4Drw\]dgi:u>ӿ:#G|Tomc3_N^"|ғ>0z@[tC<޽?>}Ago _u׎ރwӧ~:0R/}vAz=>IAbI Oݺ|H`N^СzlatRI_5nkk{ٳg>|X {;v dsSseqQ"lݩ~k.?*$*sBg/o^93#=Nd3FCMeIIA ЏzE=zvXiTߖm/tujVTP8$!1GgϞca}q 'w`f @}}?'%%%^OGow*;?{z>0 ;wq9nJs}>˦T$L*O?DG}2a=EE9}ٿ"bctu)*(>7o޼n}tZpAMH//1GHп/ ߇q`?^?;n`Ύ(+\tO*Y Kk+jj'{^fG$%$b} M'4H޵KW(ԩS> @8a/6}!gm=+3KczIG IzP_cb@@?Tõǭ>7#VሻjBі[GW]S{}Neep)+0?z_~Çzli /022\}JYI񞼜3!4pѿ^A)|C i) L!]aB}L~GOݺϷr5)!OX0>>ݧw[޽ Z>Q7޻cGُfLahaj1YAFOd$.]` (:u;گ x`Ѐ=<ӿBҪUTT/8i6b/ ɵ=#5D#G6aԩN~~~n+ +>U"Y"9i!4/hx>p ~@ >|P^$;$I {ӫ'u!̱޽E!4&&&  'Flu 0 cۇ͂9ғ0;O6M0Q$BZrpC.]G0ۣn ҽ{^]qLW*?z/I_|w:y%^ [srsdfez5=~»[ zZ55 m3ƿ8~KmMHSS}ܕmh鉻 }{BAf#O긛k+*C"3 E[UDVhNN0d^e w,]5y2Q##m;Y鞓ƍQ XgfGC&{&)++]*/J@{nGCր Fn!$b4@ JU6$SSOag a10H79yh8,y<#A o<.\pƗ0.#yF Cō ס_ovH4$%%-oUWW'-m[n4c_afjyv8cݳ{ 949dhdW~A3'ʸY\UTpRo*MAX4("ZSfر\r߆` yXNJoLgqb35#CT6DGf-N|ZzGeDZe @ & |SI~ f^o<ɾ 9rB?뒥alq ; }t+yPxEERlT`MdblH1!T\B]<)++2#?pwgH5_قx9VGԟ6^r{ĉʡaJ*j&\8JʼnJ ZBIH\LavYX4! ]EE3Et?wrqpBu*^zK3gA'_nw6lx_QiIn ]Ν9F8DI[{h1?>jñ-N$먓eCYk +rsASؑ'EzP\d%SFR8eQvzx%dmaN^TUOaA.lgInLQdbͤGϧ)..+ݺm[7BI96_pWں99[J"7UZmL}DR) ZpKji &X^zņ{ܾ.-)qG۷T_/79p˜A#/YnffvjحމP F"-"Vi*bdӭj%׮s6o٪ /v-\LpJ)si~TPP}񒯫gA@>RTT<*HB,4rMdeNvVVdcn,;dgE~'*܃"w22ЃSC9aTVL%TWO͵E%4ZbX]Scn$(bጱxDZ0m MH!AF~.<,;-a}Kš,X@;)1)tYM .#֞`b'~ͣs>X;+)ʒ*rCE|id>z'q颂Ĕd9sܹs ^~'駟?=իW!BrT$h#/w\H$ %ytSMqA !M_Y0H pI #_֖zQ9!Mêh|K-M@'6ќ)hFjΧƜpZ:̞ hэUTQ%#:d H21yn&;ZVv߭|l>>>p[TWS9In9oc@ŜG La78ٜccJ:M Ny43?"+x@!8~Poǧ@Q^QERRL'Qfb %B೨?qUTJSSs ;xD")>&j !E ɶO Hi9{'_؝logizK.uH aX8 ~T!ϝlLQĞh<ptdxviq.JR^hH&x>]{9H:Y] *NEʹ JOTZK(G:ZODVFp](jΠ'O7'9hhZ6w2?,FkN F0 0J))ؙҢ})9&.p"kDP+;rnKm[8 );ʃUEШl֜0}O' }5`ذaSqC2 s63܃@kGV/hx`v;W|&V"sR@AԐ@H9'{ќ1F`Bq.chcw<;KfϞ=->^p38-7#ePKEi@) g|}!.k\ Cz!My)rDZh#h ʦdÓ)a`7[HL! Gb+2 Wj(Єt14&VP&ƺOhed^dwVKSm;̡[#7 @!/\P!>JY ! K_GQOjI_`=t8C`!{J-11k#W ۹g J#F TT?bit#262JsS"TQ@'US|pX0 (;QF;"!yj3L֋H}du3@ePc#G[R:Pjb}T ~_qRϕw/LY.ei0U0M TS=ub>;4ATS{v@O0}Flpz#G !UTUUy<{N¦_E6pԭc}{~reDF03zdoFIq4~D!_ r"~T 6ƤTvh 4H>.sppKNN6]pĊ˓%uKIyVKr`rE"@k6Oh$ ,S_$?\De ZBD>ئyĶ{yzy)Z7HH9{{ٲewЗ(H|1Jg=~A[}]Ϝ -R??(1!~3s=|JYv| Gn\cWX A=aǎж&9ckA pf$E{f̰>\v+XQ _] PG04548yҤFgka)[lV?ɛuH6Px+edgEeul9*:2pIA ,u鈈jjeˀ>]ڛ < #6Nڨ郐? *@yaaa VG\$t@D<,n#P9 '(.: T M%QW.,dgZZ h>BO>ޞӏ=Rr*yg߁w ܾs> ItRk;5Hk螨֒ъyyy2/~Xz\73C_gsr>ٴi3fʁ8)g!!0.~; 8g8G}Sj`zzyddd"-^,B!/ -\m5\ՊpUP7Wiem\#{V ٳgOߒbٽK<e5LH R|Bbʍf LTI|0yy iTqAKx!M&&@;y*2p'rFn٤I\d0h pwYli|؏3? @;]n<[!RMl\hg3]9P̏( \ƵΜ Il |kme&m.EOGG>Ԧ&;Zbx`F'ikx߱YQuzΣY{x4 쏶<.iR@GzvYډM֞OMË)ILU.khutZx}ş;tRqI Pz5L-G'6;ȞФʺ6fd⎊2S)WSU r+W.=OkBGvnowq2P__YZY_]Y0{T߻]uw#XWA H͡`8qQNc-b"\Y F004h/$2 s3s iDM1;2,H5kGL,#C}8j+IT _@_@nY[el675%;:+ki2r\dm_WkF X5 X΀`]ٵ쉗Xm#?h?B'>&3e\3j GV,><nzfF.4n@a{'u`mJ:4k"XKPo &0L3H.sMC])NmȽPv,Do2S$=!\00R 7 :Y!@$tF/AkVvx;Dr?<"|HdsY`̆&1&[yp a^+W0r9Lrpssi*@##ctְAct MWA Zqs,-up̕g4%*8h+XXF˜;p̟rεPMsk#`"rzZ"7"4!G0Qtť DOtut!*(0}~:;;R9"Q@H ZF.Bu[4-6&:o Wzi8͝;vΠ_/3$ФX υ^#0/2R@e׬zA nn3dKGD|Ԅ2lDTl;w!G^R#1W3Ӕ3Umb٪ĉ#{p_ٸ8'D ]+rg 0rx0ŸO`b򫉞93Uh~m?XIp4QhRR_6XYBb0hDǿ:xi|ȃtGu;e%KZtDc46E8H=GCBf:sbuDx#uZ%%#%ꃮ޳;=c'-]F.T#}raG}7d\uBR̵57ܸ~mKfiNZhLo J S 75qf||E4PPWV KJ֖ք`:h?]yuv¢>! qn.?q.?* - Q'p2{S, ZWRR4 1cZS@c[δTj)]FI7a⺆Sy.Q]Fh.3'1Iݎ2 NQt:SSzʧ25485g \{NCw0114MML<=Pc(>y֖;]Ϛ9FPխ"@!j)ёQ@k 6X(Nv.Y@ *A+6@k0(FkEXQC=%y6: P`ΩܜI(gdp\{Yl j_0pS!l̠ܰz8LeKJR pMfpٹ673"jmff߻l  th1B϶ Μ= TC_'aè!k!m0ݸ"LFE\7k" @QObbw-+#=;sf͚eA8X]Koߵ`6:}( 'fΜ$W+*p'6!=P퍨:ٍ&S]pF۪)+/ڸqc?xk,BVCk S0oZmv8(~YC4f깸zWTEOI-#c 绶4&eG闞 E?`:Fw$3PT8/Z1FƐS94펙4q |[> wCA˃@J>x ^U?4'#E*NqҎڈ>6Ӻ0:*2;1>t1gϜ96֖dfu=8 rd0TY/KN@@,̟p+ $;[4ۜA8gϞ>{MM:Q:'++4}O( m85D ٘hѬEhd L&ޕ$y;>M3HIԥnC`r8Xc`y|z\H||00]1CeTp uKȈoyhfnzͥnum?lX2Gpy 7VU@ao{MF614XX_ay+1@ uOOM]S$ vRy4W83ȫMb>SD.HN3pt aFIC]g8(3̙FP>"?u8Jf4=Q kN047$KA~0aVA|`V>X~{fnU*MPuo͌-dgK_Gjժ\kp5 x@T By\ ЂJCh_B@B+~C:W%'gkǍggeio1PLM!ƀ1KΎدχ h탮}{ z74Ah> W3\RaֹS҄b 4Yb#T0 ?&5a2S8/59 c0Go*?'H[<sd鯯Ɏ S'OJ._XeƏQ_BŽz23Ouc 'صkHO-C4 攊`b 9H^Zjs"':_͝=Gy?ο:NQ}B`^Aƻx鎨oPF04$4%0n=Muf| }r-eQsEUqW1&B/ KtآW՘ "seR$?RA@xσ_@8%6 Pk>nwqԷnT7{j}}Uc!\NFJf+7)f^s̶%ʸΉB ^ A@7!/;9ɔS k--ff-,pqs9"6>6+;7[#cvsu;ί6Zg–椙.lf]c(vXò"8ΰ~ƑX.h1Md&CaJR;RXE9T LP0Y˰n a4xHGay)a=-ƈ⃽ͨm,(mT$BB/E[ݙ;sÝ;?(޿]@>쎀IKd5X Źeh<af/;e3=*'V;D(+)S{c81y4;T}-Zd" ' M0\4ԛ~^=ϟQ`Hee$/ƌLtkBw"ɏߣHgc_O2) ..Gs3Sa0`f 4 =7htMǷ di鯏_>lFVfӿ@ >''&%!I0ᨸ&(ǎ}MȰG\Ѹs0a2\;zZyl,_h~?#V?_>ۺCB !JEXFbO+S}n\Å&F3c 1L4=C'6PkINakF<}; JOC:JwRo" Ѭv,[]'k(fH' z!q.VZh!ï.^uvE3fL'$@S[Yu㽫W.w_4u넚IJTw Iv"L<#Rj-/{RVꆅ7j*U6xLɻz@ itڷ_niO0cA0'7; mu)h,h-ɜcۖbb5ݘF _ЁE"/"Qis݈tA6nx"L0c/OgM>L^aNС4jHA[;**(0k9dHacY)[ky7ў{w.Gd…Pi7e9\G~_QKs,vV bB) 61ff/S'Mz)J7߮y΢'i7 &]^Xy סkNDr# NCQHR)tiXJ_[˃f͚A(!ׯ8B!IF螎QFKv7@' +-\k .]RRT揶"{;[{.m-4Q wJ|?x!snG(X2,$ & ] L[b: L-KY:k  ,"j 0@`r0}  MP#fD}5R_)IP`LyThBh}ake%B$t7L+6w ͑6&B`4fÇ\p*B^.9q}"f@2a+7gV|uy=H|>o 'oWۙ9ci+S0t n+L~Kfqt/)nFҥ@7: p:`Eˬ-h*,1H3W5 #|+9@J0@8eX%ja6¿ȓTTHjћkx;@;gϞ}aH>\xMgPW4A%-1Tb|B~-weaԳRirQw&Z~(u~la\>}TՉaH~t*ԇb ޹%3yي[+̐&#;fLe'``fU 4 +F/  A¯ Lp[wGv.aM $D[p9̰ɂ*,\I^d];lF"_vKS}͑҂okm13\lMʈI)6z"W49}ѣ|2NDG> 0B( "y 7H1F @Zȯ8a^Ioe9 %U* m +-ZU0#1#ph\\fS-C)Ph U,zE^8r#R~<@{W.~O4)OK|sg]=x@Y3]S#D;S `t4fmx8xf,(fx}kxpbayVmS~ 㒇-vabsO7ա)F`4 QURmQNzPP)⢢Motoz~vNKK32J A]Kp~fz~H@LG6f9^YrKs㣣?m2Λꗥz?1a>ߕ'M5ڴ-*ЇI^}yR ]ç# 0pB3Lth*gС5{Tᑵ,Ԕj,LO-(8oo_Cog^ZZ\`o\'k"!+6T ,YHCۊGKQ=#HXygDb;}KKv՘-va@CǏuNvEGy{lZ K捣TWyGO_` xéw#/V>`Zbx<蜻-tag:hgF h6Mj(4F-섳yѬ2VZdtisR y(J5b T}x=W !B;;1@C *vX_A[G S]0ɒO^qmͪOjw?nݺqcR&M0qKd JIYC!\K&VRiQad-Uӌb| ]s>t׉V8tA'sj7_Zц8;*N%t}R%4:bkeIAsvL]aKSAoߑD*Yod Oسn] k_yЬ31ɑ T5"ps 6~ Mvj`@긻 h F/]h8P,"Aw֮G5p, 0Ňatϑn@+MG (/+Jjjh?:tϕniaOt9.%w]@LEBg۷Sm~&hH)g6\ z5) E YϟS8O/X6<?6ND16TiIa( 1Tĥ!A)T*f$ᕏlfՀނ#.X͡_WgK?S'ѽKQK{S3FVl?ͭDT &+62::+-^tύaQ/;:fEGbFQ"j)GOoΞ'ñށ':uؙ'+4ִT]kG`|uwC|I:J_޽sˢ1cgPc*< 1\XN(2S8UNF)fG6xG,0YW#'x 4T'Gҭ%s1t~DA/ K/'' _S,"N2y.4ڹ@`@{\C|eaM;)ĂxYjâ#QTtȟ+G>F =P: Ä @P DrB"?h`kiѾ-[>z]GyuN>02tSk[$ eTF D?# ~$I)zseM 0* tn"?N?Cw]z2nxt#}IѨǙG4rÆ gr# ƨPJ,gy]isa=Otl9эCD ҈FrM3c5-@&8 T>@%93Q~a߄8je֦#i%E4g,JMЧ "IG0T4-)GJS_Bw$@2 A-NNF?/[BЩz] ]ϡ[9FӯkE$FR$WhLlO8`ȅn@{\qpG:)4 @VA[[ӈ(eD"N%94ٍ;mYӧ|o 50^?J-{~C9v\ٱ=y 1tߧu  TM>VV^ a!Z,{%58$>b.<]G%x$֖ Ɗetm\sjr<DyӯǸH@00!ȝn,@[PEDQI+W-MKҼ1A=nGܲ*HBΗ#iB/J)>/I+YN7Wv=zԍ,OCD$ GԀJ*,E(U"=29IH/wjܬc½ peG;G4% Kt kȐ {!^z0_C9sRޖ݁dm71@~@ W< ehPJաh2MYq4'ݗFR8yңfw^0NQ B-:` DxYNowI^ٵWsf-}8ƒ1@yjY 6{F2(`ӦR&̭Rh&1u(aգuP#f^[UNg/ @'DŘp@?$7Ҥrs@tyJbr\@. ]h%4Σ<#RRRi9t#t{VJഇQ ̱ޘ~` ҥKQd0`z)pBKo..)Svt~Y=}11:jjDErmr24nwSQabV_tfjFp(k]" ״H:8c}n- &ٔx:BwatK|= MF d $QH'Zo 2@MiFƽ(-OcˈN!%DYSj;aZjm޼=wE&o7,h8DBdeߵa9U:~/?;VYeB=ӵF͝S?5%mu'Oty믝.[Dݙav 94on+# 5%L A.ϥPlq'? "h3E`nb朇'v̫\7n --ؘX-=H#}D곑6•~). SnN.:$9Q4>iٔ`f;LkUdK3R0ZHӣYP#E&؏{S̫h8%ɓQ2֯M7 4`_?*B1h7їS$L"5G{ʏ0Ȕl L(Ɓ tj=0т ?K nbNFT V4Tߔ +JNbe`xS;O?}$'lnjҵo6m_7cet"2y?:-}j 1W"ftaIID F3D4k:^Lm>^RN7/mg<)0*[ iDlhE$ )x% 5 Q;OXYS{;7_Cgǎ#hD}i:KG1`e17#bmJ|hBX@+vaf]l6].hmJKh-Oy` DO[*1c>Рz.c3e!4op;phsvqo@*+#Ax|t2+Ks 4Cc DʐSݞV'&OMqCi2DA|n6;ӏ D%K !]Ooۚrj>5#MF@!j74g >)[>đ :Q $DF}3%s T<,8I} 'B".3ɡPj `$]98h/=mryTFOf̘A&LD9W6"s::4P2 @6BE>GVځ'ggLiI/ț \hr!ȁX%ۙr*P olJ\4@°6ZG$ʈGW'P,:)FED'Ԃ!/T4PdmT<71@2jwW>텥iE B4KX] d,}̸ ^Z`} XEPO֝HO?Kߟ>HG G;xt C]' bh)U?^]4 Eh4,&Pɓ+jwPo<@߫+[m&F[|i;-Cӷymp7aXD9ʈNx^Mv܄Xr1t)W$҆ СCHF$bŠ3:AVH2O|HV-ei÷zfO}pzk_fd="SCheh3G\MM13Х"g[ZH<(3΍DӚ5k(%9u%#mM bjiRibLSn!z-ӈ"< Ξ׾"wV|9vUThɎq#X줛a:FȕQ,nLX))T_WOÛA bfI&fTD=Ct~,'ҧ \;J+|e;ӹ6mf _&Cۊ#deآ{ȋL`قeƐ7*NOMpr8RL}=ZH 8CWiF]ϱ0Dm*px~lC_,ށށ| LB[k#?>C5a:puѳK_ TyBCLؔJy L.EB0Ԕ y5輼a,gfsxrjWkm^~ Ѭ-(Z ՞69٣a9œrDjs"'dqur0'Wn+1W(#C1F*k0H7G]mÂfB̲@ఏiwk9@'xaZltBEi{L2GD wp@~ٷa:gʇ,[ UxO ֠# o.ulo>GV}fy,AGE'F> ,6q2Fਖ9 m y@Ӆͨ- JЄ Q hx>qD>/# ^n+$>w{GRQAC *~!_؀.h ' CTCk |x^aaD[́X[M h!@ agD p0@fdpo6{{ۿ_8sMT4Otd٬G Opȣ]*Qq~07BKl@stJ܁yy #}S(:@xC 2] ; LGvASrkAyww;,^|А Pҽ0Yhu閸s.Ĕi'0J5d|-o4LuZg @XZ<_@ k6u3py g4 tNp{1/ܹ?oG`=9A.)6ȅ96 a:mx5Q6p@bqkوdeua\.vBfpX3i ae|sr.D,rwu!;[=ϦNoa6Ř76)zheBX kL5Cϱc4 lPڧ 'YBHVo@2 )]FCKhk>8Jkr?SUV,@9 ep42 k]]~Y@V\ ZXkZ Y2aT`xP9dT2ӵdx8 =Ǽ&[$>wy0ۦM78&o!#G-fh ֣֒Qjd—yR~(-L? ڤZ(Z ,Uhpܛe127Ə Ξ|U_'O:oۺu`A%4kA܁s sh:f qq c 'CHqvѩG3[5tAQ]AFܠEJI% jU }t4nXWGw{/[zvIU%MCk"ln%M;AMAx H-X l hǛ"5c d8ۓowF0RXQj=OV}a46ؾ:<]E^Р_?{3YF >E#`19 0Q0v)&M̈́~5|ѫ=j Dv[k2GF#҅_m8KV(#}R0|R%#$Z bkZna@]rf?Ab ܚ+tG FpaYPFtbpXZiqw+Hz~8wHCL=G aw'Pݍ!((hQm":U /IUg;ž#S ;;;ڃ_ &т K"BCg;{RudsUUUAw fŚ!e`HSUbI2hٓ0dBT$0(BLsb@xVXsŸ᥇E_O; I i}j`w_ށ{ja󙘠@7R8"ӑ09#!ih"]Sa>oǣ+@`RU#c0x8L/1?L˝r̈́AFG/ax^8!BƯp:"SUD`qe!yh'6*Jʤ -y"ܛ픩DFب*H޵Khxh@ Hܙ#Dk6{f-pu!]O s玼!t&6D LeMa 󍋮tMb_Cri6cfA=<  spp!ޞmgm2?E0a L%{{EB6H( #i:!>cd(]T{|>/+_|80CruF5Q'+h3xƀ}KL/ "LA9Xq$CdB a29" Tzc|5]C[Ca a,Z( Lc󐒦-=JJ_F܁3;w(y6zp*-O*Ufd1F $J4zh̏& z J%Sa n7*L71CKGS KYSe晋6B+@[W wiه/$Wr_V$ ' % >0>M L?⫠HN-19}e{lVZXOӒh( Bb Z@50c&}Hdއ#J5 i@:~G7c9aYi=ƯbTht"MJJDH8V4"9[e6L|6}ʦ(uq0Lbpb7ywLfX/ -2 p845\4A/1cA"2L/ fW&̷2`.c,|4{ 捜 mBE-Q}5Wy⸚Oˊ,,ȡ1T:ԛ7>IF(8V70 DnP7iPx_QTE J?-*\#ɆBz)2xphZE$^Ոͻ7/\蹪i9x;#=GR5 jѱd9`N%TTFv= j.`)ȯ` CkT`bhXhocF<*$)[qL 4I4X"\pMdJMPEnKm[l}<=ȟjסaZC4$K3x4tj`abPmQsW-R Qz.zhzB_nl֜XYxLkixL{h"Q 3&7Zn y:lKxT'LYPSEcid|,5yS=Amy5V ,C)T B[ Ӫ5PhZCzh:|/Ί,Βs)Ybxeoi`[mOa9 #-OZ -nݼ)~{{֯3_:n)%Nã" ¡0wj-3~`OksQQөQ*UcJK+g#e*B4$k4> ?3vCԍHJMœ^y7̝o)m .8QH 4H)UB+qg2H< U  hjhj}U8XS-b8Oj !,[ i $+FmJ$ay(."ꚬAHCJSoہoOm钺g=[:aʠ*: 0и];w<H$x^Q: Ŏ85h*W ө QX%9?BLԬg T(QH\,'rYݼdwk̦Ûilq1UԶYtGd!. (){F (OQT**I(|f/)`rL^O`I@{ I Cx LaS$5IYijoشfM׷_Q;XnE-8ѰLo/j"Aa/(cžG/@ c °Ŝ (g Eq` `@ȋ"Bz$e% i` 'J7# z8A "HH${ܸv]Pz~pnEg<7y2MZO0Q26|) Ry0up.z)l*0(X[H! 0'&G?%&[0 BW>8\tyH2rc<saB1@ Pt%XQy_QZ[&v9_E LiiQuuTTH)ɔAi=RQ?S?xA Ys(ƋU Rp1 Ʌ9'ȥ1/,+c`±s=9,ɕH^n>@DVE w Ž`-1䁿SUmo|i©S.YUK񔅉(xJ)S&U. !l*cċX,y,H6TXgv+t\/OJ4|>d;3I,K6,8삳B<H4̬|&gZl_ԽIH;#]j:d/Y<|Zˈ'eTJhCqäIB85Ba`CG5^9,LOg&.W< l}3`𘅕ג^`&p>cILt ht|yXh(2|V0$ GzuҲ6`0:xCH[rЋ.<hڜlʇSYq;␹N΄P26OF;8pwgs(݌4wA&%vW3P;fMb± BN27T&8v? $1+7qG4H$sg ?@,g4y0>P4  f@}-դ8FkR*p^ @C`$ 5"(3KBD*B a;$@08^$ GPCp.0Wm`M@>|*>[GPY>gu44; ##( O& 1wl&k ` @(5Dܱ! 84{A!S LAh3@* m* 'C.=^\[0 7?} |gKa%J9s߃מ ùQ. s &j cyK#.c禍'OG5y $fnz&"/k 0 wk6m 0 w '8u['„purقr&_ObQvԻ`a$(Qط4_oJBH7`ǹH*a0@q 8ACȑ r%HGH[rC_~x;͠'&P+Hz[H@I0 L&!\ ߀AEP5(t8`JEBC V!C~pUQ9裣3)+y)bw\kv $Mh{#PRSM5T(ù 18S ;k+C>xB9 d1>h8"vy~qo?[QT fJV=ؾO[`8pW@x6 3͚l4x8ZUTZXΈvrQܾn}[}ǝsy%yѧ77?Bune|2 'r(p}0h 1%o,Oh'UЃ j-j䥋q˗/4uĊy3ߝ3i"BS{9gda2X \ / 0@h6P Ch˚ N;Ƀ4ѣ?3'ȑWFZs̋&yƀ s LT pQy2HdWDJJ򢳕mmy;e^իW??ztXS]ݏfΤ-#2C)$e#$@ d ѥH B$@{j>Mm3~kqGk/w7mƉ#FN9F`$u~L! m'8U!C$˗GXOYz[lÆFMILBmxNy&i 0BfkʥK^;Jnj9ɹ(Şbv&$IeaX0i^rbknٳ^D|.:uJzk&¥nAyc{KځIN)&OKL$WC#2=@b'ݬP̑$4(i#M2XOML kVz~.;ɓJKKsrssEEG!Z꤂"'5E{N.a˹󨩬,+.>]QZJ$+}8EC5K3͑1*cM$`t>q~qFuoЈ445HI^`Mb\]^0vL~v(r]^;lA(@1F#@S-3%Sy]@|^p߫ssuu=f;>i ȒIKH=ISx)=sTS^mȤEX 9ku̸&D&zڿݳ[񥜌 oE"Qf;@ȕr doks)7o%ٳg{O011yVphBCEѼMCQ22bࠠ6K80,@oGw#:9|)/ACL>{#&d6ZE0XFYj~ ⧎ɓ&%%&$C}8ڤJ+duh /HG o1=z^MulK+g h kB-p@SyPUnwҥK5N:;;`D/$H2@4@sGgkDy߳E([z簮6L ҂sngnvwW*Luu3}J@,C43g-Ю(#$ 9O绦DSD?YYnKALp7y&o%D!{gJ TH"k@`^ 50_}ou,1S%"ܞyص> r)q_My۶ǎOII }c@4v gGmo۵۷X4w̤¬ yuei%*H~ܒ;w_[SS1"YЖ  p *)~xގ}ve˖η07BhXJ[KIB|\޽_?*l޼y@ff4$zikjڣ1cc?'ށfΟ??rcQCu&r@SMʻ5-7s~u1++*rAE hH]||ϛyUK"z+rrsژI[[[[`)LL3:7^J|wq@Ԕڰadt4F!LQy#r5,Pr,I]fdսPV,Vlbbgg'dܹN.]kkk-[őC4p`>Jpn֬Y~̩PyH|[ZFqtr"3 aw :d&ܬ>cYش>BTT_&y(ځ3OJhY!9B:"I8e0fL ^__{sW|.]л8o $ ma98SYYK# m666M"@^M^)>x\;m֖ N^<򭬬TAU:Zmxm''qMMMv}x"K|;n*̄YnN_= ^@a"!9RRryv{0RPy}m"a?a---nWK݁{u:᏿=wôclZMĀ9` srt  2ǬncU'Ny`ܼqKUINMx5'4w\ t̬LBFruq4q3~.H|2x;v-5AMͬCDKF϶&_ C11/|kYFxovhs1\!Ρa$55c*+/#32LU}-/H|Rx;pF6fF ӪlSX$5554|JJJ`Yd:~5W'`_ is a Python implementation running on the Java virtual machine. - You'll need to use Jython 2.7 or newer (2.5 is no longer supported by Pyro) - The multiplexing server type (select/poll-based server) is not reliable on Jython. You can only use the threadpool server type. - You cannot use the ``other`` parameter to the requestloop of a Threadpool server. The workaround is to spawn a separate thread for each socket that you need to listen to. (The name server does this for the broadcast server, if it detects that it is running on Jython) IronPython ---------- .. sidebar:: IronPython `IronPython `_ is a Python implementation running on the .NET virtual machine. - Pyro requires the :kbd:`zlib` module, which is not included in older IronPython versions. IronPython 2.7 includes it by default. If you need to install it manually, get it `from the developer `_. - IronPython cannot properly serialize exception objects, which could lead to problems when dealing with Pyro's enhanced tracebacks. For now, Pyro contains a workaround for this IronPython `bug `_. Pypy ---- .. sidebar:: Pypy `Pypy `_ is a Python implementation written in Python itself, and it usually is quite a lot faster than the default implementation because it has a :abbr:`JIT (Just in time)`-compiler. I haven't used Pypy much let alone with Pyro, but it seems that at least the recent builds (1.9 and newer) of Pypy work fine with Pyro. Pyro4-4.23/docs/source/api.rst000066400000000000000000000010351227003673200162130ustar00rootroot00000000000000***************** Pyro4 library API ***************** This chapter describes Pyro's library API. All Pyro classes and functions are defined in sub packages such as :mod:`Pyro4.core`, but for ease of use, the most important ones are also placed in the :mod:`Pyro4` package scope. .. toctree:: api/main.rst api/core.rst api/naming.rst api/util.rst api/message.rst api/constants.rst api/config.rst api/errors.rst api/echoserver.rst api/flame.rst api/futures.rst api/socketserver.rst Pyro4-4.23/docs/source/api/000077500000000000000000000000001227003673200154625ustar00rootroot00000000000000Pyro4-4.23/docs/source/api/config.rst000066400000000000000000000011551227003673200174630ustar00rootroot00000000000000``Pyro4.config`` --- Configuration items ======================================== Pyro's configuration is available in the ``Pyro4.config`` object. Detailed information about the API of this object is available in the :doc:`/config` chapter. .. note:: creation of the ``Pyro4.config`` object This object is constructed when you import Pyro4. It is an instance of the :class:`Pyro4.configuration.Configuration` class. The package initializer code creates it and the initial configuration is determined (from defaults and environment variable settings). It is then assigned to ``Pyro4.config``. Pyro4-4.23/docs/source/api/constants.rst000066400000000000000000000013011227003673200202230ustar00rootroot00000000000000:mod:`Pyro4.constants` --- Constant value definitions ===================================================== .. module:: Pyro4.constants .. attribute:: VERSION The library version string (currently "|version|"). .. attribute:: DAEMON_NAME Standard object name for the Daemon itself, preferred over hardcoding it as a string literal. .. attribute:: NAMESERVER_NAME Standard object name for the Name server itself, preferred over hardcoding it as a string literal. .. attribute:: FLAME_NAME Standard object name for the Flame server, preferred over hardcoding it as a string literal. .. attribute:: PROTOCOL_VERSION Pyro's network protocol version number. Pyro4-4.23/docs/source/api/core.rst000066400000000000000000000013261227003673200171460ustar00rootroot00000000000000:mod:`Pyro4.core` --- core Pyro logic ===================================== .. automodule:: Pyro4.core :members: URI, Daemon, DaemonObject, callback, batch, async .. autoclass:: Proxy :members: .. py:attribute:: _pyroTimeout The timeout in seconds for calls on this proxy. Defaults to ``None``. If the timeout expires before the remote method call returns, Pyro will raise a :exc:`Pyro4.errors.TimeoutError`. .. py:attribute:: _pyroOneway A set of attribute names to be called as one-way method calls. This means the client won't wait for a response from the server while it is processing the call. Their return value is always ``None``.Pyro4-4.23/docs/source/api/echoserver.rst000066400000000000000000000003171227003673200203620ustar00rootroot00000000000000:mod:`Pyro4.test.echoserver` --- Built-in echo server for testing purposes ========================================================================== .. automodule:: Pyro4.test.echoserver :members: Pyro4-4.23/docs/source/api/errors.rst000066400000000000000000000007651227003673200175400ustar00rootroot00000000000000:mod:`Pyro4.errors` --- Exception classes ========================================= The exception hierarchy is as follows:: Exception | +-- PyroError | +-- NamingError +-- DaemonError +-- SecurityError +-- CommunicationError | +-- ConnectionClosedError +-- ProtocolError +-- TimeoutError .. automodule:: Pyro4.errors :members: Pyro4-4.23/docs/source/api/flame.rst000066400000000000000000000003031227003673200172740ustar00rootroot00000000000000:mod:`Pyro4.utils.flame` --- Foreign Location Automatic Module Exposer ====================================================================== .. automodule:: Pyro4.utils.flame :members: Pyro4-4.23/docs/source/api/futures.rst000066400000000000000000000002371227003673200177130ustar00rootroot00000000000000:mod:`Pyro4.futures` --- asynchronous calls =========================================== .. automodule:: Pyro4.futures :members: Future, FutureResult Pyro4-4.23/docs/source/api/main.rst000066400000000000000000000025671227003673200171520ustar00rootroot00000000000000:mod:`Pyro4` --- Main API package ================================= .. module:: Pyro4 :mod:`Pyro4` is the main package of Pyro4. It imports most of the other packages that it needs and provides shortcuts to the most frequently used objects and functions from those packages. This means you can mostly just ``import Pyro4`` in your code to start using Pyro. The classes and functions provided are: =================================== ========================== symbol in :mod:`Pyro4` referenced location =================================== ========================== .. py:class:: URI :class:`Pyro4.core.URI` .. py:class:: Proxy :class:`Pyro4.core.Proxy` .. py:class:: Daemon :class:`Pyro4.core.Daemon` .. py:class:: Future :class:`Pyro4.futures.Future` .. py:function:: callback :func:`Pyro4.core.callback` .. py:function:: batch :func:`Pyro4.core.batch` .. py:function:: async :func:`Pyro4.core.async` .. py:function:: locateNS :func:`Pyro4.naming.locateNS` .. py:function:: resolve :func:`Pyro4.naming.resolve` =================================== ========================== .. seealso:: Module :mod:`Pyro4.core` The core Pyro classes and functions. Module :mod:`Pyro4.naming` The Pyro name server logic. Pyro4-4.23/docs/source/api/message.rst000077500000000000000000000004411227003673200176420ustar00rootroot00000000000000:mod:`Pyro4.message` --- Pyro wire protocol message =================================================== .. automodule:: Pyro4.message :members: .. attribute:: MSG_* The various message types .. attribute:: FLAGS_* Various flags that modify the characteristics of the message Pyro4-4.23/docs/source/api/naming.rst000066400000000000000000000002721227003673200174660ustar00rootroot00000000000000:mod:`Pyro4.naming` --- Pyro name server ======================================== .. automodule:: Pyro4.naming :members: .. autoclass:: Pyro4.naming.NameServer :members: Pyro4-4.23/docs/source/api/socketserver.rst000066400000000000000000000043241227003673200207360ustar00rootroot00000000000000Socket server API contract ************************** For now, this is an internal API, used by the Pyro Daemon. The various servers in Pyro4.socketserver implement this. .. py:class:: SocketServer_API **Methods:** .. py:method:: init(daemon, host, port, unixsocket=None) Must bind the server on the given host and port (can be None). daemon is the object that will receive Pyro invocation calls (see below). When host or port is None, the server can select something appropriate itself. If possible, use ``Pyro4.config.COMMTIMEOUT`` on the sockets (see :doc:`config`). Set ``self.sock`` to the daemon server socket. If unixsocket is given the name of a Unix domain socket, that type of socket will be created instead of a regular tcp/ip socket. .. py:method:: loop(loopCondition) Start an endless loop that serves Pyro requests. loopCondition is an optional function that is called every iteration, if it returns False, the loop is terminated and this method returns. .. py:method:: events(eventsockets) Called from external event loops: let the server handle events that occur on one of the sockets of this server. eventsockets is a sequence of all the sockets for which an event occurred. .. py:method:: close() Release the connections and close the server. It can no longer be used after calling this, until you call initServer again. .. py:method:: wakeup() This is called to wake up the :meth:`requestLoop` if it is in a blocking state. **Properties:** .. py:attribute:: sockets must be the list of all sockets used by this server (server socket + all connected client sockets) .. py:attribute:: sock must be the server socket itself. .. py:attribute:: locationStr must be a string of the form ``"serverhostname:serverport"`` can be different from the host:port arguments passed to initServer. because either of those can be None and the server will choose something appropriate. If the socket is a Unix domain socket, it should be of the form ``"./u:socketname"``. Pyro4-4.23/docs/source/api/util.rst000066400000000000000000000007101227003673200171670ustar00rootroot00000000000000:mod:`Pyro4.util` --- Utilities =============================== .. automodule:: Pyro4.util :members: :mod:`Pyro4.socketutil` --- Socket related utilities ==================================================== .. automodule:: Pyro4.socketutil :members: :mod:`Pyro4.threadutil` --- wrapper module for :mod:`threading` =============================================================== .. automodule:: Pyro4.threadutil :members: Pyro4-4.23/docs/source/changelog.rst000066400000000000000000000341221227003673200173740ustar00rootroot00000000000000********** Change Log ********** **Pyro 4.23** - Pyro4.test.echoserver now correctly runs the NS's broadcast server as well - unix domain socket creation no longer fails when bind or connect address is unicode instead of str - docs: added more info on dealing with new serialization configuration in existing code - docs: improved name server documentation on registering objects - docs: various small updates **Pyro 4.22** - support added in daemon to accept multiple serializers in incoming messages - new config item added for that: SERIALIZERS_ACCEPTED (defaults to 'safe' serializers) - wire protocol header changed. Not backwards compatible! New protocol version: 46. - wire protocol: header now contains serializer used for the data payload - wire protocol: header is extensible with optional 'annotations'. One is used for the HMAC digest that used to be in all messages even when the hmac option wasn't enabled. - refactored core.MessageFactory: new submodule Pyro4.message. If you used MessageFactory in your own code you'll need to refactor it to use the new Pyro4.message.Message API instead. - ``disconnects`` example client code updated to reflect this API change - you can now write the protocol in URIs in lowercase if you want ("pyro:...") (will still be converted to uppercase) - fixed poll server loop() not handling self.clients which caused crashes with a custom loopCondition - fixed some unit test hang/timeout/crash issues - improved unit tests for jython, disabled ipv6 tests for jython because of too many issues. - improved unit tests for ironpython. **Pyro 4.21** - fixed denial of service vulnerabilities in socket servers - MSG_PING message type added (internal server ping mechanism) - disconnects example added that uses MSG_PING - more exception types recognised in the serializers (such as GeneratorExit) - fixed async regression when dealing with errors (properly serialize exceptionwrapper) - fixed warehouse and stockmarket tutorials to work with new serializer logic - fixed examples that didn't yet work with new serializer logic - fixed unit tests to use unittest2 on Python 2.6 - no longer supports jython 2.5. You'll have to upgrade to jython 2.7. - got rid of some byte/str handling cruft (because we no longer need to deal with jython 2.5) - implemented autoproxy support for serpent and json serializers. It is not possible to do this for marshal. - fixed serpent serialization problem with backslash escapes in unicode strings (requires serpent >= 1.3) **Pyro 4.20** .. note:: The serializer-change is backwards-incompatible. You may have to change your remote object method contracts to deal with the changes. (or switch back to pickle if you can deal with its inherent security risk) - multiple serializers supported instead of just pickle. (pickle, serpent, json, marshal) pickle is unsafe/unsecure, so a choice of safe/secure serializers is now available - config item SERIALIZER added to select desired serializer, default is 'serpent' - wire protocol version bumped because of this (45) - config item LOGWIRE added to be able to see in the logfile what passes over the wire **Pyro 4.18** - IPV6 support landed in trunk (merged ipv6 branch) - added config item PREFER_IP_VERSION (4,6,0, default=4) - socketutil.getIpVersion added - socketutil.getMyIpAddress removed, use socketutil.getIpAddress("") instead - socketutil.createSocket and createBroadcastSocket got new ipv6 argument to create ipv6 sockets instead of ipv4 - socketutil.bindOnUnusedPort now knows about ipv6 socket type as well - Uri locations using numeric "[...]" ip-address notation are considered to be IPv6 - When Pyro displays a numeric IPv6 address in a Pyro uri, it will also use the "[...]" notation for the address - Added ipv6 related unittests - Added a few best-practices to the manual **Pyro 4.17** - Fixed possible IndentationError problem with sending modules in Flame - Can now deal with exceptions that can't be serialized: they're raised as generic PyroError instead, with appropriate message - added new config item FLAME_ENABLED, to enable/disable the use of Pyro Flame on the server. Default is false (disabled). - Moved futures from core to new futures module. Code using Pyro4.Future will still work. - Added python version info to configuration dump - Made it more clear in the manual that you need to have the same major Python version on both sides **Pyro 4.16** - New implementation for the threadpool server: job queue with self-adjusting number of workers. The workaround that was in place (fixed pool size) has been removed. - minor api doc fix: corrected reference of Pyro4 package members **Pyro 4.15** - Minimum threadpool size increased to 20 (from 4) to give a bit more breathing room while the threadpool scaling still needs to be fixed - Binding a proxy will no longer release an existing connection first, instead it will just do nothing if the proxy has already been bound to its uri - Resolved a race condition related to releasing and binding a proxy, improved unit test - Documentation contains new homepage link - No longer gives a warning about version incompatibility on Jython 2.5 - optimize bytecode flag no longer added in setup script when using jython, this used to crash the setup.py install process on jython - fixed a gc issue due to a circular dependency - IronPython: improved suggesting a free port number in socketutil.findProbablyUnusedPort - IronPython: threadpoolserver no longer attempts to join the worker threads because not all threads seemed to actually exit on IronPython, thereby hanging the process when shutting down a daemon. - Added a paragraph to tips&tricks about MSG_WAITALL - socket.MSG_WAITALL is no longer deleted by importing Pyro on systems that have a broken MSG_WAITALL (Windows). You'll have to check for this yourself now, but I wanted to get rid of this side effect of importing Pyro. **Pyro 4.14** - Fixed source-newline incompatibility with sending module sources with flame, the fixExecSourceNewlines should be used on Python 3.0 and 3.1 as well it seemed. - fix IronPython crash: set socketutil.setNoInherit to a dummy for IronPython because it can't pass the proper arguments to the win32 api call - new config item MAX_MESSAGE_SIZE to optionally set a limit on the size of the messages sent on the wire, default=0 bytes (which means unlimited size). - fixed some unit test problems with pypy and ironpython - fixed some problems with MSG_WAITALL socket option on systems that don't properly support it - temporary workaround for threadpool scaling problem (lock-up): pool is fixed at THREADPOOL_MINTHREADS threads, until the thread pool has been redesigned to get rid of the issues. **Pyro 4.13** - fixed source-newline problem with sending module sources with flame, this could break on Python < 2.7 because exec is very picky about newlines in the source text on older pythons - fixed URI and Proxy equality comparisons and hash(). Note that Proxy equality and hashing is done on the local proxy object and not on the remote Pyro object. - added contrib directory where contributed stuff can be put. For now, there's a Linux init.d script for the name server daemon. - fix setNoInherit on 64-bits Python on Windows (see http://tech.oyster.com/cherrypy-ctypes-and-being-explicit/) - setting natport to 0 now replaces it by the internal port number, to facilitate one-to-one NAT port mapping setups - fixed _pyroId attribute problem when running with Cython **Pyro 4.12** - added a few more code examples and cross-references to the docs to hopefully make it easier to understand what the different ways of connecting your client code and server objects are - proxies no longer connect again if already connected (could happen with threads) - fixed not-equal-comparison for uri and serializer objects (x!=y) **Pyro 4.11** - added host and port parameters to Daemon.serveSimple - added nathost and natport parameters to Daemon to be able to run behind a NAT router/firewall - added nathost and natport options to name server to configure it for use with NAT - added NATHOST and NATPORT config items to configure the external address for use with NAT - added BROADCAST_ADDRS config item. Use this to set the appropriate broadcast addresses (comma separated) The default is '' but you might need to change this on certain platforms (OpenSUSE?) where that doesn't work very well. - changed logger category from Pyro to Pyro4 - connection closed error is no longer logged if it's just a normal terminated proxy connection - fixed a config cleanup error in the test suite that could break it, depending on test execution order **Pyro 4.10** - added Future class that provides async (future) function calls for any callable (not just Pyro proxies) - renamed _AsyncResult to FutureResult - added Flame (foreign location automatic module exposer) in Pyro4.utils.flame, including docs and example - Pyrolite also gained support for Flame (client access) - improved FutureResult.then(), it now accepts additional normal arguments as well instead of only kwargs - renamed Pyro4.config.refresh to Pyro4.config.reset because reset better describes what it is doing - added parameter to config.refresh to make it ignore environment variables - refactored internal threadpool into its own module, added unit tests **Pyro 4.9** - removed AsyncResultTimeout exception - asyncresult.ready is now a property instead of a method - asyncresult.wait() is a new method taking the optional timeout argument to wait for the result to become available. It doesn't raise an exception, instead it returns true or false. - completed the documentation - added gui_eventloop example - added deadlock example - added itunes example - fixed some missing methods in the api reference documentation - serialized data is released a bit faster to improve garbage collection - fixed setting socket options in socketutil.createSocket - socket SO_REUSEADDR option now not set anymore by default; added new config item SOCK_REUSE to be able to set it to True if you want. - threaded server should deal with EINTR and other errors better (retry call) - better closedown of threadpool server - fix for potential autoproxy failure when unregistering pyro objects **Pyro 4.8** - Major additions to the documentation: tutorials, API docs, and much more. - Polished many docstrings in the sources, they're used in the generation of the API docs. - Unix domain socket support. Added :file:`unixdomainsock` example and unit tests. - Added options to the name server and echo server to use Unix domain sockets. - Name server broadcast responder will attempt to guess the caller's correct network interface, and use that to respond with the name server location IP (instead of 0.0.0.0). This should fix some problems that occurred when the nameserver was listening on 0.0.0.0 and the proxy couldn't connect to it after lookup. Added unit test. - API change: async callbacks have been changed into the more general async "call chain", using the ``then()`` method. Added examples and unit tests. - Async calls now copy the proxy internally so they don't serialize after another anymore. - A python 2.6 compatibility issue was fixed in the unit tests. **Pyro 4.7** - AutoProxy feature! This is a very nice one that I've always wanted to realize in Pyro ever since the early days. Now it's here: Pyro will automatically take care of any Pyro objects that you pass around through remote method calls. It will replace them by a proxy automatically, so the receiving side can call methods on it and be sure to talk to the remote object instead of a local copy. No more need to create a proxy object manually. This feature can be switched off using the config item ``AUTOPROXY`` to get the old behavior. Added a new :file:`autoproxy` example and changed several old examples to make use of this feature. - Asynchronous method calls: you can execute a remote method (or a batch of remote method) asynchronously, and retrieve the results sometime in the future. Pyro will take care of collecting the return values in the background. Added :file:`async` example. - One-line-server-setup using ``Pyro4.Daemon.serveSimple``, handy for quickly starting a server with basic settings. - ``nameserver.register()`` behavior change: it will now overwrite an existing registration with the same name unless you provide a ``safe=True`` argument. This means you don't need to ``unregister()`` your server objects anymore all the time when restarting the server. - added ``Pyro4.util.excepthook`` that you can use for ``sys.excepthook`` - Part of the new manual has been written, including a tutorial where two simple applications are built. **Pyro 4.6** - Added batch call feature to greatly speed up many calls on the same proxy. Pyro can do 180,000 calls/sec or more with this. - Fixed handling of connection fail in handshake - A couple of python3 fixes related to the hmac key - More unit test coverage **Pyro 4.5** - Added builtin test echo server, with example and unittest. Try ``python -m Pyro4.test.echoserver -h`` - Made ``Pyro4.config`` into a proper class with error checking. - Some Jython related fixes. - Code cleanups (pep8 is happier now) - Fixed error behaviour, no longer crashes server in some cases - ``HMAC_KEY`` is no longer required, but you'll still get a warning if you don't set it **Pyro 4.4** - removed pickle stream version check (too much overhead for too little benefit). - set no-inherit flag on server socket to prevent problems with child processes blocking the socket. More info: http://www.cherrypy.org/ticket/856 - added HMAC message digests to the protocol, with a user configurable secret shared key in ``HMAC_KEY`` (required). This means you could now safely expose your Pyro interface to the outside world, without risk of getting owned by malicious messages constructed by a hacker. You need to have enough trust in your shared key. note that the data is not encrypted, it is only signed, so you still should not send sensitive data in plain text. Pyro4-4.23/docs/source/clientcode.rst000066400000000000000000000533101227003673200175560ustar00rootroot00000000000000******************************* Clients: Calling remote objects ******************************* This chapter explains how you write code that calls remote objects. Often, a program that calls methods on a Pyro object is called a *client* program. (The program that provides the object and actually runs the methods, is the *server*. Both roles can be mixed in a single program.) Make sure you are familiar with Pyro's :ref:`keyconcepts` before reading on. .. _object-discovery: Object discovery ================ To be able to call methods on a Pyro object, you have to tell Pyro where it can find the actual object. This is done by creating an appropriate URI, which contains amongst others the object name and the location where it can be found. You can create it in a number of ways. * directly use the object name and location. This is the easiest way and you write an URI directly like this: ``PYRO:someobjectid@servername:9999`` It requires that you already know the object id, servername, and port number. You could choose to use fixed object names and fixed port numbers to connect Pyro daemons on. For instance, you could decide that your music server object is always called "musicserver", and is accessible on port 9999 on your server musicbox.my.lan. You could then simply use:: uri_string = "PYRO:musicserver@musicbox.my.lan:9999" # or use Pyro4.URI("...") for an URI object instead of a string Most examples that come with Pyro simply ask the user to type this in on the command line, based on what the server printed. This is not very useful for real programs, but it is a simple way to make it work. You could write the information to a file and read that from a file share (only slightly more useful, but it's just an idea). * use a logical name and look it up in the name server. A more flexible way of locating your objects is using logical names for them and storing those in the Pyro name server. Remember that the name server is like a phone book, you look up a name and it gives you the exact location. To continue on the previous bullet, this means your clients would only have to know the logical name "musicserver". They can then use the name server to obtain the proper URI:: import Pyro4 nameserver = Pyro4.locateNS() uri = nameserver.lookup("musicserver") # ... uri now contains the URI with actual location of the musicserver object You might wonder how Pyro finds the Name server. This is explained in the separate chapter :doc:`nameserver`. * use a logical name and let Pyro look it up in the name server for you. Very similar to the option above, but even more convenient, is using the *meta*-protocol identifier ``PYRONAME`` in your URI string. It lets Pyro know that it should lookup the name following it, in the name server. Pyro should then use the resulting URI from the name server to contact the actual object. So this means you can write:: uri_string = "PYRONAME:musicserver" # or Pyro4.URI("PYRONAME:musicserver") for an URI object You can use this URI everywhere you would normally use a normal uri (using ``PYRO``). Everytime Pyro encounters the ``PYRONAME`` uri it will use the name server automatically to look up the object for you. [#pyroname]_ .. [#pyroname] this is not very efficient if it occurs often. Have a look at the :doc:`tipstricks` chapter for some hints about this. Calling methods =============== Once you have the location of the Pyro object you want to talk to, you create a Proxy for it. Normally you would perhaps create an instance of a class, and invoke methods on that object. But with Pyro, your remote method calls on Pyro objects go trough a proxy. The proxy can be treated as if it was the actual object, so you write normal python code to call the remote methods and deal with the return values, or even exceptions:: # Continuing our imaginary music server example. # Assume that uri contains the uri for the music server object. musicserver = Pyro4.Proxy(uri) try: musicserver.load_playlist("90s rock") musicserver.play() print "Currently playing:", musicserver.current_song() except MediaServerException: print "Couldn't select playlist or start playing" For normal usage, there's not a single line of Pyro specific code once you have a proxy! .. _object-serialization: Serialization ============= Pyro will serialize the objects that you pass to the remote methods, so they can be sent across a network connection. Depending on the serializer that is being used, there will be some limitations on what objects you can use. * serpent: serializes into Python literal expressions. Accepts quite a lot of different types. Many will be serialized as dicts. You might need to explicitly translate literals back to specific types on the receiving end if so desired, because most custom classes aren't dealt with automatically. Requires third party library module, but it will be installed automatically as a dependency of Pyro. This serializer is the default choice. * json: more restricted as serpent, less types supported. Part of the standard library. * marshal: a very limited but fast serializer. Can deal with a small range of builtin types only, no custom classes can be serialized. Part of the standard library. *note: marshal doesn't work correctly in Jython, so you can't use it there* (see `issue 2077 `_) * pickle: the legacy serializer. Fast and supports almost all types. Has security problems though. Part of the standard library. No longer used by default. You select the serializer to be used by setting the ``SERIALIZER`` config item. (See the :doc:`/config` chapter). The valid choices are the names of the serializer from the list mentioned above. .. note:: Since Pyro 4.20 the default serializer is "``serpent``". Before that, it used to be "``pickle``". Serpent is less expressive (not all types can be serialized, some types are serialized in a different form such as strings) but doesn't have pickle's security issues. .. note:: The serializer(s) that a Pyro server/daemon accepts, is controlled by a different config item (``SERIALIZERS_ACCEPTED``). This can be a set of one or more serializers. By default it accepts the set of 'safe' serializers, so not "``pickle``". If the server doesn't accept the serializer that you configured for your client, it will refuse the requests and respond with an exception that tells you about the unsupported serializer choice. If it *does* accept your requests, the server will respond using the same serializer as was used for the request. Upgrading older code that relies on pickle ------------------------------------------ What do you have to do with code that relies on pickle, and worked fine in older Pyro versions, but now crashes? You have three options: #. Redesign remote interfaces #. Configure Pyro to eable the use of pickle again #. Stick to Pyro 4.18 (less preferable) You can redesign the remote interface to only include types that can be serialized (python's built-in types and exception classes, and a few Pyro specific classes such as URIs). That way you benefit from the new security that the alternative serializers provide. If you can't do this, you have to tell Pyro to enable pickle again. This has been made an explicit step because of the security implications of using pickle. Here's how to do this: Client code configuration Tell Pyro to use pickle as serializer for outgoing communication, by setting the ``SERIALIZER`` config item to ``pickle``. For instance, in your code: :code:`Pyro4.config.SERIALIZER = 'pickle'` or set the appropriate environment variable. Server code configuration Tell Pyro to accept pickle as incoming serialization format, by including ``pickle`` in the ``SERIALIZERS_ACCEPTED`` config item list. For instance, in your code: :code:`Pyro4.config.SERIALIZERS_ACCEPTED.add('pickle')`. Or set the appropriate environment variable, for instance: :code:`export PYRO_SERIALIZERS_ACCEPTED=serpent,json,marshal,pickle`. If your server also uses Pyro to call other servers, you may also need to configure it as mentioned above at 'client code'. This is because the incoming and outgoing serializer formats are configured independently. To see how this works in practice you can look at the :file:`stockquotes` example. Proxies, connections, threads and cleaning up ============================================= Here are some rules: * Every single Proxy object will have its own socket connection to the daemon. * You can share Proxy objects among threads, it will re-use the same socket connection. * Usually every connection in the daemon has its own processing thread there, but for more details see the :doc:`servercode` chapter. * The connection will remain active for the lifetime of the proxy object. * At proxy creation, no actual connection is made. The proxy is only actually connected at first use, or when you manually connect it using the ``_pyroReconnect()`` method. * You can free resources by manually closing the proxy connection if you don't need it anymore. This can be done in two ways: 1. calling ``_pyroRelease()`` on the proxy. 2. using the proxy as a context manager in a ``with`` statement. This ensures that when you're done with it, or an error occurs (inside the with-block), the connection is released:: with Pyro4.Proxy(".....") as obj: obj.method() .. note:: You can still use the proxy object when it is disconnected: Pyro will reconnect it as soon as it's needed again. Oneway calls ============ Normal method calls always block until the response is returned. This can be a normal return value, ``None``, or an error in the form of a raised exception. If you know that some methods never return any response or you are simply not interested in it (including exceptions!) you can tell Pyro that certain methods of a proxy object are *one-way* calls:: proxy._pyroOneway.add("someMethod") proxy._pyroOneway.update(["otherMethod", "processStuff"]) the :py:attr:`Pyro4.core.Proxy._pyroOneway` property is a set containing the names of the methods that should be called as one-way (by default it is an empty set). For these methods, Pyro will not wait for a response from the remote object. This means that your client program continues to work, while the remote object is still busy processing the method call. The return value of these calls is always ``None``. You can't tell if the method call was successful, or if the method even exists on the remote object, because errors won't be returned either! See the :file:`oneway` example for more details. .. _batched-calls: Batched calls ============= Doing many small remote method calls in sequence has a fair amount of latency and overhead. Pyro provides a means to gather all these small calls and submit it as a single 'batched call'. When the server processed them all, you get back all results at once. Depending on the size of the arguments, the network speed, and the amount of calls, doing a batched call can be *much* faster than invoking every call by itself. Note that this feature is only available for calls on the same proxy object. How it works: #. You create a batch proxy wrapper object for the proxy object. #. Call all the methods you would normally call on the regular proxy, but use the batch proxy wrapper object instead. #. Call the batch proxy object itself to obtain the generator with the results. You create a batch proxy wrapper using this: ``batch = Pyro4.batch(proxy)`` or this (equivalent): ``batch = proxy._pyroBatch()``. The signature of the batch proxy call is as follows: .. py:method:: batchproxy.__call__([oneway=False, async=False]) Invoke the batch and when done, returns a generator that produces the results of every call, in order. If ``oneway==True``, perform the whole batch as one-way calls, and return ``None`` immediately. If ``async==True``, perform the batch asynchronously, and return an asynchronous call result object immediately. **Simple example**:: batch = Pyro4.batch(proxy) batch.method1() batch.method2() # more calls ... batch.methodN() results = batch() # execute the batch for result in results: print result # process result in order of calls... **Oneway batch**:: results = batch(oneway=True) # results==None **Asynchronous batch** The result value of an asynchronous batch call is a special object. See :ref:`async-calls` for more details about it. This is some simple code doing an asynchronous batch:: results = batch(async=True) # do some stuff... until you're ready and require the results of the async batch: for result in results.value: print result # process the results See the :file:`batchedcalls` example for more details. .. _async-calls: Asynchronous ('future') remote calls & call chains ================================================== You can execute a remote method call and tell Pyro: "hey, I don't need the results right now. Go ahead and compute them, I'll come back later once I need them". The call will be processed in the background and you can collect the results at a later time. If the results are not yet available (because the call is *still* being processed) your code blocks but only at the line you are actually retrieving the results. If they have become available in the meantime, the code doesn't block at all and can process the results immediately. It is possible to define one or more callables (the "call chain") that should be invoked automatically by Pyro as soon as the result value becomes available. You create an async proxy wrapper using this: ``async = Pyro4.async(proxy)`` or this (equivalent): ``async = proxy._pyroAsync()``. Every remote method call you make on the async proxy wrapper, returns a :py:class:`Pyro4.futures.FutureResult` object immediately. This object means 'the result of this will be available at some moment in the future' and has the following interface: .. py:attribute:: value This property contains the result value from the call. If you read this and the value is not yet available, execution is halted until the value becomes available. If it is already available you can read it as usual. .. py:attribute:: ready This property contains the readiness of the result value (``True`` meaning that the value is available). .. py:method:: wait([timeout=None]) Waits for the result value to become available, with optional wait timeout (in seconds). Default is None, meaning infinite timeout. If the timeout expires before the result value is available, the call will return ``False``. If the value has become available, it will return ``True``. .. py:method:: then(callable [, *args, **kwargs]) Add a callable to the call chain, to be invoked when the results become available. The result of the current call will be used as the first argument for the next call. Optional extra arguments can be provided via ``args`` and ``kwargs``. A simple piece of code showing an asynchronous method call:: async = Pyro4.async(proxy) asyncresult = async.remotemethod() print "value available?", asyncresult.ready # ...do some other stuff... print "resultvalue=", asyncresult.value .. note:: :ref:`batched-calls` can also be executed asynchronously. Asynchronous calls are implemented using a background thread that waits for the results. Callables from the call chain are invoked sequentially in this background thread. See the :file:`async` example for more details and example code for call chains. Async calls for normal callables (not only for Pyro proxies) ------------------------------------------------------------ The async proxy wrapper discussed above is only available when you are dealing with Pyro proxies. It provides a convenient syntax to call the methods on the proxy asynchronously. For normal Python code it is sometimes useful to have a similar mechanism as well. Pyro provides this too, see :ref:`future-functions` for more information. Pyro Callbacks ============== Usually there is a nice separation between a server and a client. But with some Pyro programs it is not that simple. It isn't weird for a Pyro object in a server somewhere to invoke a method call on another Pyro object, that could even be running in the client program doing the initial call. In this case the client program is a server itself as well. These kinds of 'reverse' calls are labeled *callbacks*. You have to do a bit of work to make them possible, because normally, a client program is not running the required code to also act as a Pyro server to accept incoming callback calls. In fact, you have to start a Pyro daemon and register the callback Pyro objects in it, just as if you were writing a server program. Keep in mind though that you probably have to run the daemon's request loop in its own background thread. Or make heavy use of oneway method calls. If you don't, your client program won't be able to process the callback requests because it is by itself still waiting for results from the server. **Exceptions in callback objects:** If your callback object raises an exception, Pyro will return that to the server doing the callback. Depending on what that does with it, you might never see the actual exception, let alone the stack trace. This is why Pyro provides a decorator that you can use on the methods in your callback object in the client program: ``@Pyro4.core.callback`` (also available for convenience as ``@Pyro4.callback``). This way, an exception in that method is not only returned to the caller, but also raised again locally in your client program, so you can see it happen including the stack trace:: class Callback(object): @Pyro4.callback def call(self): print("callback received from server!") return 1//0 # crash away See the :file:`callback` example for more details and code. Miscellaneous features ====================== Pyro provides a few miscellaneous features when dealing with remote method calls. They are described in this section. Error handling -------------- You can just do exception handling as you would do when writing normal Python code. However, Pyro provides a few extra features when dealing with errors that occurred in remote objects. This subject is explained in detail its own chapter: :doc:`errors`. See the :file:`exceptions` example for more details. Timeouts -------- Because calls on Pyro objects go over the network, you might encounter network related problems that you don't have when using normal objects. One possible problems is some sort of network hiccup that makes your call unresponsive because the data never arrived at the server or the response never arrived back to the caller. By default, Pyro waits an indefinite amount of time for the call to return. You can choose to configure a *timeout* however. This can be done globally (for all Pyro network related operations) by setting the timeout config item:: Pyro4.config.COMMTIMEOUT = 1.5 # 1.5 seconds You can also do this on a per-proxy basis by setting the timeout property on the proxy:: proxy._pyroTimeout = 1.5 # 1.5 seconds There is also a server setting related to oneway calls, that says if oneway method calls should be executed in a separate thread or not. If this is set to ``False``, they will execute in Pyro4.config.ONEWAY_THREADED = True # this is the default See the :file:`timeout` example for more details. Automatic reconnecting ---------------------- If your client program becomes disconnected to the server (because the server crashed for instance), Pyro will raise a :py:exc:`Pyro4.errors.ConnectionClosedError`. It is possible to catch this and tell Pyro to attempt to reconnect to the server by calling ``_pyroReconnect()`` on the proxy (it takes an optional argument: the number of attempts to reconnect to the daemon. By default this is almost infinite). Once successful, you can resume operations on the proxy:: try: proxy.method() except Pyro4.errors.ConnectionClosedError: # connection lost, try reconnecting obj._pyroReconnect() This will only work if you take a few precautions in the server. Most importantly, if it crashed and comes up again, it needs to publish its Pyro objects with the exact same URI as before (object id, hostname, daemon port number). See the :file:`autoreconnect` example for more details and some suggestions on how to do this. The ``_pyroReconnect()`` method can also be used to force a newly created proxy to connect immediately, rather than on first use. Proxy sharing ------------- Due to internal locking you can freely share proxies among threads. The lock makes sure that only a single thread is actually using the proxy's communication channel at all times. This can be convenient *but* it may not be the best way to approach things. The lock essentially prevents parallelism. If you want calls to go in parallel, give each thread its own proxy. Here are a couple of suggestions on how to make copies of a proxy: #. use the :py:mod:`copy` module, ``proxy2 = copy.copy(proxy)`` #. create a new proxy from the uri of the old one: ``proxy2 = Pyro4.Proxy(proxy._pyroUri)`` #. simply create a proxy in the thread itself (pass the uri to the thread instead of a proxy) See the :file:`proxysharing` example for more details. Pyro4-4.23/docs/source/commandline.rst000066400000000000000000000053521227003673200177360ustar00rootroot00000000000000.. _command-line: ****************** Command line tools ****************** Pyro has several command line tools that you will be using sooner or later. For now, there are no special scripts or executables that call those tools directly. Instead, they're "executable modules" inside Pyro. They're invoked with Python's "-m" command line argument. An idea is to define shell aliases for them, for instance: :kbd:`alias pyrons='python -m Pyro4.naming'` Name server =========== synopsys: :command:`python -m Pyro4.naming [options]` Starts the Pyro Name Server. It can run without any arguments but there are several that you can use, for instance to control the hostname and port that the server is listening on. A short explanation of the available options can be printed with the help option: .. program:: Pyro4.naming .. option:: -h, --help Print a short help message and exit. .. seealso:: :ref:`nameserver-nameserver` for detailed information Name server control =================== synopsys: :command:`python -m Pyro4.nsc [options] command [arguments]` The name server control tool (or 'nsc') is used to talk to a running name server and perform diagnostic or maintenance actions such as querying the registered objects, adding or removing a name registration manually, etc. A short explanation of the available options can be printed with the help option: .. program:: Pyro4.nsc .. option:: -h, --help Print a short help message and exit. .. seealso:: :ref:`nameserver-nsc` for detailed information .. _command-line-echoserver: Test echo server ================ :command:`python -m Pyro4.test.echoserver [options]` This is a simple built-in server that can be used for testing purposes. It launches a Pyro object that has several methods suitable for various tests (see below). Optionally it can also directly launch a name server. This way you can get a simple Pyro server plus name server up with just a few keystrokes. A short explanation of the available options can be printed with the help option: .. program:: Pyro4.test.echoserver .. option:: -h, --help Print a short help message and exit. The echo server object is available by the name ``test.echoserver``. It exposes the following methods: .. method:: echo(argument) Simply returns the given argument object again. .. method:: error() Generates a run time exception. .. method:: shutdown() Terminates the echo server. Configuration check =================== :command:`python -m Pyro4.configuration` This is the equivalent of:: >>> import Pyro4 >>> print Pyro4.config.dump() It prints the Pyro version, the location it is imported from, and a dump of the active configuration items. Pyro4-4.23/docs/source/conf.py000066400000000000000000000167311227003673200162200ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Pyro documentation build configuration file, created by # sphinx-quickstart on Thu Jun 16 22:20:40 2011. # # 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 import Pyro4.constants # 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.insert(0, 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'] # 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' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Pyro' copyright = u'Irmen de Jong' # 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 = Pyro4.constants.VERSION # The full version, including alpha/beta/rc tags. release = Pyro4.constants.VERSION # 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 = [] # 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 # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # 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. See the documentation for # a list of builtin themes. html_theme = 'default' # 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 = { "rightsidebar": True, "bodyfont": "Tahoma,Helvetica,\"Helvetica Neue\",Arial,sans-serif", "linkcolor": "#3070a0", "visitedlinkcolor": "#3070a0", } # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # 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 = "static/pyro.png" # 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 = False # 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 = False # 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 = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'Pyrodoc' # -- 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', 'Pyro.tex', u'Pyro Documentation', u'Irmen de Jong', '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 # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = 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', 'pyro', u'Pyro Documentation', [u'Irmen de Jong'], 1) ] def setup(app): from sphinx.ext.autodoc import cut_lines # skip the copyright line in every module docstring (last line of docstring) app.connect('autodoc-process-docstring', cut_lines(pre=0, post=1, what=['module'])) Pyro4-4.23/docs/source/config.rst000066400000000000000000000156001227003673200167120ustar00rootroot00000000000000**************** Configuring Pyro **************** Pyro can be configured using several *configuration items*. The current configuration is accessible from the ``Pyro4.config`` object, it contains all config items as attributes. You can read them and update them to change Pyro's configuration. (usually you need to do this at the start of your program). For instance, to enable message compression and change the server type, you add something like this to the start of your code:: Pyro4.config.COMPRESSION = True Pyro4.config.SERVERTYPE = "multiplex" You can also set them outside of your program, using environment variables from the shell. To avoid conflicts, the environment variables have a ``PYRO_`` prefix. This means that if you want to change the same two settings as above, but by using environment variables, you would do something like:: $ export PYRO_COMPRESSION=true $ export PYRO_SERVERTYPE=multiplex (or on windows:) C:\> set PYRO_COMPRESSION=true C:\> set PYRO_SERVERTYPE=multiplex Resetting the config to default values -------------------------------------- .. method:: Pyro4.config.reset([useenvironment=True]) Resets the configuration items to their builtin default values. If `useenvironment` is True, it will overwrite builtin config items with any values set by environment variables. If you don't trust your environment, it may be a good idea to reset the config items to just the builtin defaults (ignoring any environment variables) by calling this method with `useenvironment` set to False. Do this before using any other part of the Pyro library. Inspecting current config ------------------------- To inspect the current configuration you have several options: 1. Access individual config items: ``print(Pyro4.config.COMPRESSION)`` 2. Dump the config in a console window: :command:`python -m Pyro4.configuration` This will print something like:: Pyro version: 4.6 Loaded from: E:\Projects\Pyro4\src\Pyro4 Active configuration settings: AUTOPROXY = True COMMTIMEOUT = 0.0 COMPRESSION = False ... 3. Access the config as a dictionary: ``Pyro4.config.asDict()`` 4. Access the config string dump (used in #2): ``Pyro4.config.dump()`` .. _config-items: Overview of Config Items ------------------------ ======================= ======= ============== ======= config item type default meaning ======================= ======= ============== ======= AUTOPROXY bool True Enable to make Pyro automatically replace Pyro objects by proxies in the method arguments and return values of remote method calls. Doesn't work with marshal serializer. COMMTIMEOUT float 0.0 network communication timeout in seconds. 0.0=no timeout (infinite wait) COMPRESSION bool False Enable to make Pyro compress the data that travels over the network DETAILED_TRACEBACK bool False Enable to get detailed exception tracebacks (including the value of local variables per stack frame) DOTTEDNAMES bool False Server side only: Enable to support object traversal using dotted names (a.b.c.d) HMAC_KEY bytes None Shared secret key to sign all communication messages HOST str localhost Hostname where Pyro daemons will bind on MAX_MESSAGE_SIZE int 0 Maximum size in bytes of the messages sent or received on the wire. If a message exceeds this size, a ProtocolError is raised. NS_HOST str *equal to Hostname for the name server HOST* NS_PORT int 9090 TCP port of the name server NS_BCPORT int 9091 UDP port of the broadcast responder from the name server NS_BCHOST str None Hostname for the broadcast responder of the name sever NATHOST str None External hostname in case of NAT NATPORT int None External port in case of NAT BROADCAST_ADDRS str , List of comma separated addresses that Pyro should send broadcasts to (for NS lookup) 0.0.0.0 ONEWAY_THREADED bool True Enable to make oneway calls be processed in their own separate thread POLLTIMEOUT float 2.0 For the multiplexing server only: the timeout of the select or poll calls SERVERTYPE str thread Select the Pyro server type. thread=thread pool based, multiplex=select/poll based SOCK_REUSE bool False Should SO_REUSEADDR be used on sockets that Pyro creates. PREFER_IP_VERSION int 4 The IP address type that is preferred (4=ipv4, 6=ipv6, 0=let OS decide). THREADING2 bool False Use the threading2 module if available instead of Python's standard threading module THREADPOOL_MINTHREADS int 4 For the thread pool server: minimum amount of worker threads to be spawned THREADPOOL_MAXTHREADS int 50 For the thread pool server: maximum amount of worker threads to be spawned THREADPOOL_IDLETIMEOUT float 2.0 For the thread pool server: number of seconds to pass for an idle worker thread to be terminated FLAME_ENABLED bool False Should Pyro Flame be enabled on the server SERIALIZER str serpent The wire protocol serializer to use for clients/proxies (one of: serpent, json, marshal, pickle) SERIALIZERS_ACCEPTED set json,marshal, The wire protocol serializers accepted in the server/daemon. serpent Use comma separated string for initial config, will be a set after initialization. LOGWIRE bool False If wire-level message data should be written to the logfile (you may want to disable COMPRESSION) ======================= ======= ============== ======= There are two special config items that are only available as environment variable settings. This is because they are used at module import time (when the Pyro4 package is being imported). They control Pyro's logging behavior: ======================= ======= ============== ======= environment variable type default meaning ======================= ======= ============== ======= PYRO_LOGLEVEL string *not set* The log level to use for Pyro's logger (DEBUG, WARN, ...) See Python's standard :py:mod:`logging` module for the allowed values. If it is not set, no logging is being configured. PYRO_LOGFILE string pyro.log The name of the log file. Use {stderr} to make the log go to the standard error output. ======================= ======= ============== ======= Pyro4-4.23/docs/source/errors.rst000066400000000000000000000145661227003673200167730ustar00rootroot00000000000000**************************** Errors and remote tracebacks **************************** There is an example that shows various ways to deal with exceptions when writing Pyro code. Have a look at the ``exceptions`` example in the :file:`examples` directory. Pyro errors ----------- Pyro's exception classes can be found in :mod:`Pyro4.errors`. They are used by Pyro if something went wrong inside Pyro itself or related to something Pyro was doing. Remote errors ------------- More interesting are the errors that occur in *your own* objects (the remote Pyro objects). Pyro is doing its best to make the remote objects appear as normal, local, Python objects. That also means that if they raise an error, Pyro will make it appear in the caller, as if the error occurred locally. Say you have a remote object that can divide arbitrary numbers. It will probably raise a ``ZeroDivisionError`` when you supply ``0`` as the divisor. This can be dealt with as follows:: import Pyro4 divider=Pyro4.Proxy( ... ) try: result = divider.div(999,0) except ZeroDivisionError: print "yup, it crashed" Just catch the exception as if you were writing code that deals with normal objects. But, since the error occurred in a *remote* object, and Pyro itself raises it again on the client side, you lose some information: the actual traceback of the error at the time it occurred in the server. Pyro fixes this because it stores the traceback information on a special attribute on the exception object (``_pyroTraceback``). The traceback is stored as a list of strings (each is a line from the traceback text, including newlines). You can use this data on the client to print or process the traceback text from the exception as it occurred in the Pyro object on the server. There is a utility function in :mod:`Pyro4.util` to make it easy to deal with this: :func:`Pyro4.util.getPyroTraceback` You use it like this:: import Pyro4.util try: result = proxy.method() except Exception: print "Pyro traceback:" print "".join(Pyro4.util.getPyroTraceback()) Also, there is another function that you can install in ``sys.excepthook``, if you want Python to automatically print the complete Pyro traceback including the remote traceback, if any: :func:`Pyro4.util.excepthook` A full Pyro exception traceback, including the remote traceback on the server, looks something like this:: Traceback (most recent call last): File "client.py", line 50, in print(test.complexerror()) # due to the excepthook, the exception will show the pyro error File "E:\Projects\Pyro4\src\Pyro4\core.py", line 130, in __call__ return self.__send(self.__name, args, kwargs) File "E:\Projects\Pyro4\src\Pyro4\core.py", line 242, in _pyroInvoke raise data TypeError: unsupported operand type(s) for //: 'str' and 'int' +--- This exception occured remotely (Pyro) - Remote traceback: | Traceback (most recent call last): | File "E:\Projects\Pyro4\src\Pyro4\core.py", line 760, in handleRequest | data=method(*vargs, **kwargs) # this is the actual method call to the Pyro object | File "E:\projects\Pyro4\examples\exceptions\excep.py", line 17, in complexerror | x.crash() | File "E:\projects\Pyro4\examples\exceptions\excep.py", line 22, in crash | s.crash2('going down...') | File "E:\projects\Pyro4\examples\exceptions\excep.py", line 25, in crash2 | x=arg//2 | TypeError: unsupported operand type(s) for //: 'str' and 'int' +--- End of remote traceback As you can see, the first part is only the exception as it occurs locally on the client (raised by Pyro). The indented part marked with 'Remote traceback' is the exception as it occurred in the remote Pyro object. Detailed traceback information ------------------------------ There is another utility that Pyro has to make it easier to debug remote object errors. If you enable the ``DETAILED_TRACEBACK`` config item on the server (see :ref:`config-items`), the remote traceback is extended with details of the values of the local variables in every frame:: +--- This exception occured remotely (Pyro) - Remote traceback: | ---------------------------------------------------- | EXCEPTION : unsupported operand type(s) for //: 'str' and 'int' | Extended stacktrace follows (most recent call last) | ---------------------------------------------------- | File "E:\Projects\Pyro4\src\Pyro4\core.py", line 760, in Daemon.handleRequest | Source code: | data=method(*vargs, **kwargs) # this is the actual method call to the Pyro object | ---------------------------------------------------- | File "E:\projects\Pyro4\examples\exceptions\excep.py", line 17, in TestClass.complexerror | Source code: | x.crash() | Local values: | self = | self._pyroDaemon = | self._pyroId = 'obj_c63d47dd140f44dca8782151643e0c55' | x = | ---------------------------------------------------- | File "E:\projects\Pyro4\examples\exceptions\excep.py", line 22, in Foo.crash | Source code: | self.crash2('going down...') | Local values: | self = | ---------------------------------------------------- | File "E:\projects\Pyro4\examples\exceptions\excep.py", line 25, in Foo.crash2 | Source code: | x=arg//2 | Local values: | arg = 'going down...' | self = | ---------------------------------------------------- | EXCEPTION : unsupported operand type(s) for //: 'str' and 'int' | ---------------------------------------------------- +--- End of remote traceback You can immediately see why the call produced a ``TypeError`` without the need to have a debugger running (the ``arg`` variable is a string and dividing that string by 2 ofcourse is the cause of the error). Ofcourse it is also possible to enable ``DETAILED_TRACEBACK`` on the client, but it is not as useful there (normally it is no problem to run the client code inside a debugger). Pyro4-4.23/docs/source/flame.rst000066400000000000000000000126711227003673200165360ustar00rootroot00000000000000************************************************ Flame: Foreign Location Automatic Module Exposer ************************************************ .. image:: _static/flammable.png :align: left Pyro Flame is an easy way of exposing remote modules and builtins, and even a remote interactive Python console. It is available since Pyro 4.10. With Flame, you don't need to write any server side code anymore, and still be able to call objects, modules and other things on the remote machine. Flame does this by giving a client direct access to any module or builtin that is available on the remote machine. Flame can be found in the :py:mod:`Pyro4.utils.flame` module. .. warning:: Be very sure about what you are doing before enabling Flame. Flame is disabled by default. You need to explicitly set a config item to true, and start a Flame server yourself, to make it available. This is because it allows client programs full access to *everything* on your system. Only use it if you fully trust your environment and the clients that can connect to your machines. (Flame is also symbolic for burning server machines that got totally owned by malicious clients.) Enabling Flame ============== Flame is actually a special Pyro object that is exposed via a normal Pyro daemon. You need to start it explicitly in your daemon. This is done by calling a utility function with your daemon that you want to enable flame on:: import Pyro4.utils.flame Pyro4.utils.flame.start(daemon) Additionally, you have to make some configuration changes: * flame server: set the ``FLAME_ENABLED`` config item to True * flame server: set the ``SERIALIZERS_ACCEPTED`` config item to ``set(["pickle"])`` * flame client: set the ``SERIALIZER`` config item to ``pickle`` You'll have to explicitly enable Flame. When you don't, you'll get an error when trying to start Flame. The config item is False by default to avoid unintentionally running Flame servers. Also, Flame requires the pickle serializer. It doesn't work when using one of the secure serializers, because it needs to be able to transfer custom python objects. Command line server =================== There's a little command line server program that will launch a flame enabled Pyro daemon, to avoid the hassle of having to write a custom server program yourself everywhere you want to provide a Flame server: :command:`python -m Pyro4.utils.flameserver` The command line arguments are similar to the echo server (see :ref:`command-line-echoserver`). Use ``-h`` to make it print a short help text. For the command line server you'll also have to set the ``FLAME_ENABLED`` config item to True, otherwise you'll get an error when trying to start it. Because we're talking about command line clients, the most convenient way to do so is probably by setting the environment variable in your shell: ``PYRO_FLAME_ENABLED=true``. Flame object and examples ========================= A Flame server exposes a ``"Pyro.Flame"`` object (you can hardcode this name or use the constant :py:attr:`Pyro4.constants.FLAME_NAME`). Its interface is described in the API documentation, see :py:class:`Pyro4.utils.flame.Flame`. Connecting to the flame server can be done as usual (by creating a Pyro proxy yourself) or by using the convenience function :py:func:`Pyro4.utils.flame.connect`. A little example follows. You have to have running flame server, and then you can write a client like this:: import Pyro4.utils.flame Pyro4.config.SERIALIZER = "pickle" # flame requires pickle serializer flame = Pyro4.utils.flame.connect("hostname:9999") # or whatever the server runs at socketmodule = flame.module("socket") osmodule = flame.module("os") print "remote host name=", socketmodule.gethostname() print "remote server directory contents=", osmodule.listdir(".") flame.execute("import math") root = flame.evaluate("math.sqrt(500)") print "calculated square root=", root print "remote exceptions also work", flame.evaluate("1//0") # print something on the remote std output flame.builtin("print")("Hello there, remote server stdout!") A remote interactive console can be started like this:: with flame.console() as console: console.interact() # ... you can repeat sessions if you want ... which will print something like:: Python 2.7.2 (default, Jun 12 2011, 20:46:48) [GCC 4.2.1 (Apple Inc. build 5577)] on darwin (Remote console on charon:9999) >>> # type stuff here and it gets executed on the remote machine >>> import socket >>> socket.gethostname() 'charon.local' >>> ^D (Remote session ended) .. note:: The ``getfile`` and ``sendfile`` functions can be used for *very* basic file transfer. The ``getmodule`` and ``sendmodule`` functions can be used to send module source files to other machines so it is possible to execute code that wasn't available before. This is a *very* experimental replacement of the mobile code feature that Pyro 3.x had. It also is a very easy way of totally owning the server because you can make it execute anything you like. Be very careful. .. note:: :doc:`pyrolite` also supports convenient access to a Pyro Flame server. This includes the remote interactive console. See the :file:`flame` example for example code including uploading module source code to the server. Pyro4-4.23/docs/source/index.rst000066400000000000000000000030021227003673200165450ustar00rootroot00000000000000**************************************** Pyro - Python Remote Objects - |version| **************************************** .. image:: _static/pyro-large.png :align: center :alt: PYRO logo What is Pyro? ------------- It is a library that enables you to build applications in which objects can talk to each other over the network, with minimal programming effort. You can just use normal Python method calls to call objects on other machines. Pyro is written in **100% pure Python** and therefore runs on many platforms and Python versions, **including Python 3.x**. Pyro is copyright © Irmen de Jong (irmen@razorvine.net | http://www.razorvine.net). Please read :doc:`license`. Join the `Pyro mailing list `_ for questions and discussion. Pyro can be found on Pypi as `Pyro4 `_. Source on Github: https://github.com/irmen/Pyro4 Contents -------- .. toctree:: :maxdepth: 2 intro.rst install.rst tutorials.rst commandline.rst clientcode.rst servercode.rst nameserver.rst security.rst errors.rst flame.rst tipstricks.rst config.rst upgrading.rst api.rst alternative.rst pyrolite.rst changelog.rst license.rst Indices and tables ================== * :ref:`genindex` * :ref:`search` .. figure:: _static/tf_pyrotaunt.png :target: http://wiki.teamfortress.com/wiki/Pyro :alt: PYYYRRRROOOO :align: center Pyro4-4.23/docs/source/install.rst000066400000000000000000000050431227003673200171130ustar00rootroot00000000000000*************** Installing Pyro *************** This chapter will show how to obtain and install Pyro. Requirements ------------ You need Python 2.6 or newer for Pyro4. Also see :ref:`should-i-choose-pyro4`. Pyro is written in 100% pure Python. It works on any recent operating system. It will default to using the `serpent `_ serializer so you will need to install Serpent as well, unless you configure Pyro to use one of the other serializers. .. note:: When Pyro is configured to use pickle or marshal as its serialization format, it is required to have the same *major* Python versions on your clients and your servers. Otherwise the different parties cannot decipher each others serialized data. This means you cannot let Python 2.x talk to Python 3.x with Pyro. However it should be fine to have Python 2.6.2 talk to Python 2.7.3 for instance. Using one of the implementation independent protocols (serpent or json) will avoid this limitation. Obtaining and installing Pyro ----------------------------- Pyro can be found on the Python package index: http://pypi.python.org/pypi/Pyro4/ (package name ``Pyro4``) You can install it using :command:`pip` or :command:`easy_install`, or download the distribution archive (.tar.gz) from Pypi and run the ``setup.py`` script from that manually. Pyro installs as the ``Pyro4`` package with a couple of sub modules that you usually don't have to access directly. The `serpent `_ serialization library is installed as a dependency. .. note:: Windows users: use one of the suggested tools to install Pyro. If you decide to get the distribution archive (.tar.gz) and use that, one way to extract it is to use the (free) `7-zip `_ archive utility. If you want you can also obtain the source directly from Github: https://github.com/irmen/Pyro4 Stuff you get in the distribution archive ----------------------------------------- If you decide to download the distribution (.tar.gz) you have a bunch of extras over installing Pyro directly. It contains: docs/ the Sphinx/RST sources for this manual examples/ dozens of examples that demonstrate various Pyro features (highly recommended) tests/ all unit tests src/ The library source code (only this part is installed if you install the ``Pyro4`` package) and a couple of other files: a setup script and other miscellaneous files such as the license (see :doc:`license`). Pyro4-4.23/docs/source/intro.rst000066400000000000000000000357421227003673200166110ustar00rootroot00000000000000***************** Intro and Example ***************** .. image:: _static/pyro-large.png :align: center This chapter contains a little overview of Pyro's features and a simple example to show how it looks like. About Pyro: feature overview ============================ Pyro is a library that enables you to build applications in which objects can talk to each other over the network, with minimal programming effort. You can just use normal Python method calls, with almost every possible parameter and return value type, and Pyro takes care of locating the right object on the right computer to execute the method. It is designed to be very easy to use, and to generally stay out of your way. But it also provides a set of powerful features that enables you to build distributed applications rapidly and effortlessly. Pyro is written in **100% pure Python** and therefore runs on many platforms and Python versions, **including Python 3.x**. Here's a quick overview of Pyro's features: - written in 100% Python so extremely portable. - defaults to a safe serializer (`serpent `_) that supports many Python data types. - supports different serializers (serpent, json, marshal, pickle). - support for all Python data types that are pickleable when using the 'pickle' serializer. - runs on normal Python 2.x, Python **3.x**, IronPython, Jython 2.7, Pypy. - works between systems on different architectures and operating systems (64-bit, 32-bit, Intel, PowerPC...) - designed to be very easy to use and get out of your way as much as possible. - name server that keeps track of your object's actual locations so you can move them around transparently. - support for automatic reconnection to servers in case of interruptions. - automatic proxy-ing of Pyro objects which means you can return references to remote objects just as if it were normal objects. - one-way invocations for enhanced performance. - batched invocations for greatly enhanced performance of many calls on the same object. - you can define timeouts on network communications to prevent a call blocking forever if there's something wrong. - asynchronous invocations if you want to get the results 'at some later moment in time'. Pyro will take care of gathering the result values in the background. - remote exceptions will be raised in the caller, as if they were local. You can extract detailed remote traceback information. - stable network communication code that works reliably on many platforms. - possibility to use Pyro's own event loop, or integrate it into your own (or third party) event loop. - many simple examples included to show various features and techniques. - large amount of unit tests and high test coverage. - built upon more than 10 years of existing Pyro history. - can use IPv4, IPv6 and Unix domain sockets .. warning:: When configured to use the :py:mod:`pickle` serializer, your system may be vulnerable because of the sercurity risks of the pickle protocol (possibility of arbitrary code execution). Pyro does have some security measures in place to mitigate this risk somewhat. They are described in the :doc:`security` chapter. It is strongly advised to read it. By default, Pyro is configured to use a different serializer, so you won't have to deal with this unless you change it explicitly. .. note:: Pyro will send the whole object graph you're passing over the wire, even when only a tiny fraction of it is used on the receiving end. Be aware of this: it may be necessary to define special objects for your Pyro interfaces that hold the data you need, rather than passing a huge object structure. Pyro's history ^^^^^^^^^^^^^^ Pyro was started in 1998, more than ten years ago, when remote method invocation technology such as Java's RMI and CORBA were quite popular. I wanted something like that in Python and there was nothing available, so I decided to write my own. Over the years it slowly gained features till it reached version 3.10 or so. At that point it was clear that the code base had become quite ancient and couldn't reliably support any new features, so Pyro4 was born in early 2010, written from scratch. After a couple of versions Pyro4 became stable enough to be considered the new 'main' Pyro version to be preferred over Pyro 3.x (unless you have specific requirements that force you to stick with Pyro3). See :doc:`upgrading` for more information on the different versions and how to upgrade old code to Pyro4. ``Pyro`` is the package name of the older, 3.x version of Pyro. ``Pyro4`` is the package name of the new, current version. Its API and behavior is similar to Pyro 3.x but it is not backwards compatible. So to avoid conflicts, this new version has a different package name. If you're interested in the old version, here is `its homepage `_ and it is also `available on PyPi `_. What can you use it for? ======================== Essentially, Pyro can be used to distribute various kinds of resources or responsibilities: computational (hardware) resources (cpu, storage, printers), informational resources (data, privileged information) and business logic (departments, domains). An example would be a high performance compute cluster with a large storage system attached to it. Usually such a beast is not be accessible directly, rather, smaller systems connect to it and feed it with jobs that need to run on the big cluster. Later, they collect the results. Pyro could be used to expose the available resources on the cluster to other computers. Their client software connects to the cluster and calls the Python program there to perform its heavy duty work, and collect the results (either directly from a method call return value, or perhaps via asynchronous callbacks). Remote controlling resources or other programs is a nice application as well. For instance, you could write a simple remote controller for your media server that is running on a machine somewhere in a closet. A simple remote control client program could be used to instruct the media server to play music, switch playlists, etc. Another example is the use of Pyro to implement a form of `privilege separation `_. There is a small component running with higher privileges, but just able to execute the few tasks (and nothing else) that require those higher privileges. That component could expose one or more Pyro objects that represent the privileged information or logic. Other programs running with normal privileges can talk to those Pyro objects to perform those specific tasks with higher privileges in a controlled manner. On a lower level Pyro is just a form of inter-process communication. So everywhere you would otherwise have used a more primitive form of IPC (such as plain TCP/IP sockets) between Python components, you could consider to use Pyro instead. Have a look at the :file:`examples` directory in the source archive, perhaps one of the many example programs in there gives even more inspiration of possibilities. Simple Example ============== This example will show you in a nutshell what it's like to use Pyro in your programs. A much more extensive introduction is found in the :doc:`tutorials`. We're going to write a simple greeting service that will return a personalized greeting message to its callers. Let's start by just writing it in normal Python first (create two files):: # save this as greeting.py class GreetingMaker(object): def get_fortune(self, name): return "Hello, {0}. Here is your fortune message:\n" \ "Behold the warranty -- the bold print giveth and the fine print taketh away.".format(name) :: # save this as client.py import greeting name=raw_input("What is your name? ") greeting_maker=greeting.GreetingMaker() print greeting_maker.get_fortune(name) If you then run it with :command:`python client.py` a session looks like this:: $ python client.py What is your name? Irmen Hello, Irmen. Here is your fortune message: Behold the warranty -- the bold print giveth and the fine print taketh away. Right that works like a charm but we are now going to use Pyro to make this into a greeting server that you can access easily from anywhere. The :file:`greeting.py` is going to be our server. We'll need to import the Pyro package, start up a Pyro daemon (server) and connect a GreetingMaker object to it:: # saved as greeting.py import Pyro4 class GreetingMaker(object): def get_fortune(self, name): return "Hello, {0}. Here is your fortune message:\n" \ "Behold the warranty -- the bold print giveth and the fine print taketh away.".format(name) greeting_maker=GreetingMaker() daemon=Pyro4.Daemon() # make a Pyro daemon uri=daemon.register(greeting_maker) # register the greeting object as a Pyro object print "Ready. Object uri =", uri # print the uri so we can use it in the client later daemon.requestLoop() # start the event loop of the server to wait for calls And now all that is left is a tiny piece of code that invokes the server from somewhere:: # saved as client.py import Pyro4 uri=raw_input("What is the Pyro uri of the greeting object? ").strip() name=raw_input("What is your name? ").strip() greeting_maker=Pyro4.Proxy(uri) # get a Pyro proxy to the greeting object print greeting_maker.get_fortune(name) # call method normally Open a console window and start the greeting server:: $ python greeting.py Ready. Object uri = PYRO:obj_edb9e53007ce4713b371d0dc6a177955@localhost:51681 (The uri is randomly generated) Open another console window and start the client program:: $ python client.py What is the Pyro uri of the greeting object? <> What is your name? <> Hello, Irmen. Here is your fortune message: Behold the warranty -- the bold print giveth and the fine print taketh away. This covers the most basic use of Pyro! As you can see, all there is to it is starting a daemon, registering one or more objects with it, and getting a proxy to these objects to call methods on as if it was the actual object itself. With a name server ^^^^^^^^^^^^^^^^^^ While the example above works, it could become tiresome to work with object uris like that. There's already a big issue, *how is the client supposed to get the uri, if we're not copy-pasting it?* Thankfully Pyro provides a *name server* that works like an automatic phone book. You can name your objects using logical names and use the name server to search for the corresponding uri. We'll have to modify a few lines in :file:`greeting.py` to make it register the object in the name server:: # saved as greeting.py import Pyro4 class GreetingMaker(object): def get_fortune(self, name): return "Hello, {0}. Here is your fortune message:\n" \ "Tomorrow's lucky number is 12345678.".format(name) greeting_maker=GreetingMaker() daemon=Pyro4.Daemon() # make a Pyro daemon ns=Pyro4.locateNS() # find the name server uri=daemon.register(greeting_maker) # register the greeting object as a Pyro object ns.register("example.greeting", uri) # register the object with a name in the name server print "Ready." daemon.requestLoop() # start the event loop of the server to wait for calls The :file:`client.py` is actually simpler now because we can use the name server to find the object:: # saved as client.py import Pyro4 name=raw_input("What is your name? ").strip() greeting_maker=Pyro4.Proxy("PYRONAME:example.greeting") # use name server object lookup uri shortcut print greeting_maker.get_fortune(name) The program now needs a Pyro name server that is running. You can start one by typing the following command: :command:`python -m Pyro4.naming` in a separate console window (usually there is just *one* name server running in your network). After that, start the server and client as before. There's no need to copy-paste the object uri in the client any longer, it will 'discover' the server automatically, based on the object name (:kbd:`example.greeting`). If you want you can check that this name is indeed known in the name server, by typing the command :command:`python -m Pyro4.nsc list`, which will produce:: $ python -m Pyro4.nsc list --------START LIST Pyro.NameServer --> PYRO:Pyro.NameServer@localhost:9090 example.greeting --> PYRO:obj_663a31d2dde54b00bfe52ec2557d4f4f@localhost:51707 --------END LIST (Once again the uri for our object will be random) This concludes this simple Pyro example. .. note:: In the source archive there is a directory :file:`examples` that contains a truckload of example programs that show the various features of Pyro. If you're interested in them (it is highly recommended to be so!) you will have to download the Pyro distribution archive. Installing Pyro only provides the library modules. For more information, see :doc:`config`. Other means of creating connections ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The example above showed two of the basic ways to set up connections between your client and server code. There are various other options, have a look at the client code details: :ref:`object-discovery` and the server code details: :ref:`publish-objects`. The use of the name server is optional, see :ref:`name-server` for details. Performance =========== Pyro4 is pretty fast at what it does. Here are some measurements done between two processes running on a Core 2 Quad 3Ghz, Windows 7 machine, using the marshal serializer: :benchmark/connections.py: | 2000 connections in 2.165 sec = 924 conn/sec | 2000 new proxy calls in 2.628 sec = 761 calls/sec | 10000 calls in 1.146 sec = 8726 calls/sec :benchmark/client.py: | total time 1.859 seconds | total method calls: 15000 | avg. time per method call: 0.124 msec (8068/sec) (serializer: marshal) :hugetransfer/client.py: | It took 0.49 seconds to transfer 50 mb. | That is 104690 kb/sec. = 102.2 mb/sec. (serializer: marshal) :batchedcalls/client.py: | (using pickle serializer) | Batched remote calls...: | total time taken 0.28 seconds (142300 calls/sec) | batched calls were 14.3 times faster than normal remote calls | Oneway batched remote calls...: | total time taken 0.17 seconds (235200 calls/sec) | oneway batched calls were 23.6 times faster than normal remote calls Other serialization protocols (serpent, json, marshal) will usually be slower than pickle. But because of the security risks of the pickle protocol, a slower but safer protocol is used by default. Pyro4-4.23/docs/source/license.rst000066400000000000000000000030531227003673200170660ustar00rootroot00000000000000******************************* Software License and Disclaimer ******************************* Pyro - Python Remote Objects - version 4.x Pyro is Copyright (c) by Irmen de Jong (irmen@razorvine.net). Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This is the `MIT Software License `_ which is OSI-certified, and GPL-compatible. .. figure:: _static/tf_pyrotaunt.png :target: http://wiki.teamfortress.com/wiki/Pyro :alt: PYYYRRRROOOO :align: center Pyro4-4.23/docs/source/nameserver.rst000066400000000000000000000356421227003673200176240ustar00rootroot00000000000000.. _name-server: *********** Name Server *********** The Pyro Name Server is a tool to help keeping track of your objects in your network. It is also a means to give your Pyro objects logical names instead of the need to always know the exact object name (or id) and its location. Pyro will name its objects like this:: PYRO:obj_dcf713ac20ce4fb2a6e72acaeba57dfd@localhost:51850 PYRO:custom_name@localhost:51851 It's either a generated unique object id on a certain host, or a name you chose yourself. But to connect to these objects you'll always need to know the exact object name or id and the exact hostname and port number of the Pyro daemon where the object is running. This can get tedious, and if you move servers around (or Pyro objects) your client programs can no longer connect to them until you update all URIs. Enter the *name server*. This is a simple phone-book like registry that maps logical object names to their corresponding URIs. No need to remember the exact URI anymore. Instead, you can ask the name server to look it up for you. You only need to give it the logical object name. .. note:: Usually you only need to run *one single instance* of the name server in your network. You can start multiple name servers but they are unconnected; you'll end up with a partitioned name space. **Example scenario:** Assume you've got a document archive server that publishes a Pyro object with several archival related methods in it. This archive server can register this object with the name server, using a logical name such as "Department.ArchiveServer". Any client can now connect to it using only the name "Department.ArchiveServer". They don't need to know the exact Pyro id and don't even need to know the location. This means you can move the archive server to another machine and as long as it updates its record in the name server, all clients won't notice anything and can keep on running without modification. .. _nameserver-nameserver: Starting the Name Server ======================== The easiest way to start a name server is by using the command line tool. synopsys: :command:`python -m Pyro4.naming [options]` Starts the Pyro Name Server. It can run without any arguments but there are several that you can use, for instance to control the hostname and port that the server is listening on. A short explanation of the available options can be printed with the help option. When it starts, it prints a message similar to this:: $ python -m Pyro4.naming -n neptune Broadcast server running on 0.0.0.0:9091 NS running on neptune:9090 (192.168.1.100) URI = PYRO:Pyro.NameServer@neptune:9090 As you can see it prints that it started a broadcast server (and its location), a name server (and its location), and it also printed the URI that clients can use to access it directly. These things will be explained below. There are several command line options: .. program:: Pyro4.naming .. option:: -h, --help Print a short help message and exit. .. option:: -n HOST, --host=HOST Specify hostname or ip address to bind the server on. The default is localhost. If the server binds on localhost, *no broadcast responder* is started. .. option:: -p PORT, --port=PORT Specify port to bind server on (0=random). .. option:: -u UNIXSOCKET, --unixsocket=UNIXSOCKET Specify a Unix domain socket name to bind server on, rather than a normal TCP/IP socket. .. option:: --bchost=BCHOST Specify the hostname or ip address to bind the broadcast responder on. Note: if the hostname where the name server binds on is localhost (or 127.0.x.x), no broadcast responder is started. .. option:: --bcport=BCPORT Specify the port to bind the broadcast responder on (0=random). .. option:: --nathost=NATHOST Specify the external host name to use in case of NAT .. option:: --natport=NATPORT Specify the external port use in case of NAT .. option:: -x, --nobc Don't start a broadcast responder. Clients will not be able to use the UDP-broadcast lookup to discover this name server. (The broadcast responder listens to UDP broadcast packets on the local network subnet, to signal its location to clients that want to talk to the name server) Another way is doing it from within your own code. This is much more complex because you will have to integrate the name server into the rest of your program (perhaps you need to merge event loops). A helper function is available to create it in your program: :py:func:`Pyro4.naming.startNS`. Look at the :file:`eventloop` example to see how you can use this. Configuration items =================== There are a couple of config items related to the nameserver. They are used both by the name server itself (to configure the values it will use to start the server with), and the client code that locates the name server (to give it optional hints where the name server is located). Often these can be overridden with a command line option or with a method parameter in your code. ================== =========== Configuration item description ================== =========== NS_HOST the hostname or ip address of the name server NS_PORT the port number of the name server NS_BCHOST the hostname or ip address of the name server's broadcast responder NS_BCPORT the port number of the name server's broadcast responder NATHOST the external hostname in case of NAT NATPORT the external port in case of NAT ================== =========== .. _nameserver-nsc: Name server control tool ======================== The name server control tool (or 'nsc') is used to talk to a running name server and perform diagnostic or maintenance actions such as querying the registered objects, adding or removing a name registration manually, etc. synopsis: :command:`python -m Pyro4.nsc [options] command [arguments]` .. program:: Pyro4.nsc .. option:: -h, --help Print a short help message and exit. .. option:: -n HOST, --host=HOST Provide the hostname or ip address of the name server. The default is to do a broadcast lookup to search for a name server. .. option:: -p PORT, --port=PORT Provide the port of the name server, or its broadcast port if you're doing a broadcast lookup. .. option:: -u UNIXSOCKET, --unixsocket=UNIXSOCKET Provide the Unix domain socket name of the name server, rather than a normal TCP/IP socket. .. option:: -v, --verbose Print more output that could be useful. The available commands for this tool are: list : list [prefix] List all objects registered in the name server. If you supply a prefix, the list will be filtered to show only the objects whose name starts with the prefix. listmatching : listmatching pattern List only the objects with a name matching the given regular expression pattern. register : register name uri Registers a name to the given Pyro object :abbr:`URI (universal resource identifier)`. remove : remove name Removes the entry with the exact given name from the name server. removematching : removematching pattern Removes all entries matching the given regular expression pattern. ping Does nothing besides checking if the name server is running and reachable. Example:: $ python -m Pyro4.nsc ping Name server ping ok. $ python -m Pyro4.nsc list Pyro --------START LIST - prefix 'Pyro' Pyro.NameServer --> PYRO:Pyro.NameServer@localhost:9090 --------END LIST - prefix 'Pyro' Locating the Name Server and using it in your code ================================================== The name server is a Pyro object itself, and you access it through a normal Pyro proxy. The object exposed is :class:`Pyro4.naming.NameServer`. Getting a proxy for the name server is done using the following function: :func:`Pyro4.naming.locateNS` (also available as :func:`Pyro4.locateNS`). By far the easiest way to locate the Pyro name server is by using the broadcast lookup mechanism. This goes like this: you simply ask Pyro to look up the name server and return a proxy for it. It automatically figures out where in your subnet it is running by doing a broadcast and returning the first Pyro name server that responds. The broadcast is a simple UDP-network broadcast, so this means it usually won't travel outside your network subnet (or through routers) and your firewall needs to allow UDP network traffic. There is a config item ``BROADCAST_ADDRS`` that contains a comma separated list of the broadcast addresses Pyro should use when doing a broadcast lookup. Depending on your network configuration, you may have to change this list to make the lookup work. It could be that you have to add the network broadcast address for the specific network that the name server is located on. .. note:: Broadcast lookup only works if you started a name server that didn't bind on localhost. For instance, the name server started as an example in :ref:`nameserver-nameserver` was told to bind on the host name 'neptune' and it started a broadcast responder as well. If you use the default host (localhost) no broadcast responder can be created. Normally, all name server lookups are done this way. In code, it is simply calling the locator function without any arguments. If you want to circumvent the broadcast lookup (because you know the location of the server already, somehow) you can specify the hostname. .. function:: locateNS([host=None, port=None]) Get a proxy for a name server somewhere in the network. If you're not providing host or port arguments, the configured defaults are used. Unless you specify a host, a broadcast lookup is done to search for a name server. (api reference: :py:func:`Pyro4.naming.locateNS`) :param host: the hostname or ip address where the name server is running. Default is ``None`` which means it uses a network broadcast lookup. If you specify a host, no broadcast lookup is performed. :param port: the port number on which the name server is running. Default is ``None`` which means use the configured default. The exact meaning depends on whether the host parameter is given: * host parameter given: the port now means the actual name server port. * host parameter not given: the port now means the broadcast port. The 'magical' PYRONAME protocol type ==================================== To create a proxy and connect to a Pyro object, Pyro needs an URI so it can find the object. Because it is so convenient, the name server logic has been integrated into Pyro's URI mechanism by means of the special ``PYRONAME`` protocol type (rather than the normal ``PYRO`` protocol type). This protocol type tells Pyro to treat the URI as a logical object name instead, and Pyro will do a name server lookup automatically to get the actual object's URI. The form of a PYRONAME uri is very simple: ``PYRONAME:some_logical_object_name``, where "some_logical_object_name" is the name of a registered Pyro object in the name server. This means that instead of manually resolving objects like this:: nameserver=Pyro4.locateNS() uri=nameserver.lookup("Department.BackupServer") proxy=Pyro4.Proxy(uri) proxy.backup() you can write this instead:: proxy=Pyro4.Proxy("PYRONAME:Department.BackupServer") proxy.backup() An additional benefit of using a PYRONAME uri in a proxy is that the proxy isn't strictly tied to a specific object on a specific location. This is useful in scenarios where the server objects might move to another location, for instance when a disconnect/reconnect occurs. See the :file:`autoreconnect` example for more details about this. .. note:: Pyro has to do a lookup every time it needs to connect one of these PYRONAME uris. If you connect/disconnect many times or with many different objects, consider using PYRO uris (you can type them directly or create them by resolving as explained in the following paragraph) or call :meth:`Pyro4.core.Proxy._pyroBind()` on the proxy to bind it to a fixed PYRO uri instead. Resolving object names ====================== 'Resolving an object name' means to look it up in the name server's registry and getting the actual URI that belongs to it (with the actual object name or id and the location of the daemon in which it is running). This is not normally needed in user code (Pyro takes care of it automatically for you), but it can still be useful in certain situations. So, resolving a logical name can be done in several ways: - let Pyro do it for you, for instance simply pass a ``PYRONAME`` URI to the proxy constructor - use a ``PYRONAME`` URI and resolve it using the ``resolve`` utility function (see below) - obtain a name server proxy and use its ``lookup`` method; ``uri = ns.lookup("objectname")`` You can resolve a ``PYRONAME`` URI explicitly using the following utility function: :func:`Pyro4.naming.resolve` (also available as :func:`Pyro4.resolve`), which goes like this: .. function:: resolve(uri) Finds a name server, and use that to resolve a PYRONAME uri into the direct PYRO uri pointing to the named object. If uri is already a PYRO uri, it is returned unmodified. *Note:* if you need to resolve more than a few names, consider using the name server directly instead of repeatedly calling this function, to avoid the name server lookup overhead from each call. :param uri: PYRONAME uri that you want to resolve :type uri: string or :class:`Pyro4.core.URI` .. _nameserver-registering: Registering object names ======================== 'Registering an object' means that you associate the URI with a logical name, so that clients can refer to your Pyro object by using that name. Your server has to register its Pyro objects with the name server. It first registers an object with the Daemon, gets an URI back, and then registers that URI in the name server using the following method on the name server proxy: .. py:method:: register(name, uri, safe=False) Registers an object (uri) under a logical name in the name server. :param name: logical name that the object will be known as :type name: string :param uri: the URI of the object (you get it from the daemon) :type uri: string or :class:`Pyro4.core.URI` :param safe: normally registering the same name twice silently overwrites the old registration. If you set safe=True, the same name cannot be registered twice. :type safe: bool Other methods ============= The name server has a few other methods that might be useful at times. For instance, you can ask it for a list of all registered objects. Because the name server itself is a regular Pyro object, you can access its methods through a regular Pyro proxy, and refer to the description of the exposed class to see what methods are available: :class:`Pyro4.naming.NameServer`. Pyro4-4.23/docs/source/pyrolite.rst000066400000000000000000000056131227003673200173170ustar00rootroot00000000000000******************************************* Pyrolite - client library for Java and .NET ******************************************* This library allows your Java or .NET program to interface very easily with the Python world. It uses the Pyro protocol to call methods on remote objects. It also supports convenient access to a Pyro Flame server including the remote interactive console. Pyrolite is a tiny library (~60Kb) that implements a part of the client side Pyro library, hence its name 'lite'. Because Pyrolite has no dependencies, it is a much lighter way to use Pyro from Java/.NET than a solution with jython+pyro or IronPython+pyro would provide. So if you don't need Pyro's full feature set, and don't require your Java/.NET code to host Pyro objects itself, Pyrolite may be a good choice to connect java or .NET and python. Pyrolite contains a feature complete implementation of Python's :mod:`pickle` protocol (with fairly intelligent mapping of datatypes between Python and Java/.NET), and a small part of Pyro's client network protocol and proxy logic. It can also use the Serpent serialization format. Get it from here: http://irmen.home.xs4all.nl/pyrolite/ Readme: http://irmen.home.xs4all.nl/pyrolite/README.txt License (same as Pyro): http://irmen.home.xs4all.nl/pyrolite/LICENSE Source is on Github: https://github.com/irmen/Pyrolite Small code example in Java: .. code-block:: java import net.razorvine.pyro.*; NameServerProxy ns = NameServerProxy.locateNS(null); PyroProxy remoteobject = new PyroProxy(ns.lookup("Your.Pyro.Object")); Object result = remoteobject.call("pythonmethod", 42, "hello", new int[]{1,2,3}); String message = (String)result; // cast to the type that 'pythonmethod' returns System.out.println("result message="+message); remoteobject.close(); ns.close(); The same example in C#: .. code-block:: csharp using Razorvine.Pyro; using( NameServerProxy ns = NameServerProxy.locateNS(null) ) { using( PyroProxy something = new PyroProxy(ns.lookup("Your.Pyro.Object")) ) { object result = something.call("pythonmethod", 42, "hello", new int[]{1,2,3}); string message = (string)result; // cast to the type that 'pythonmethod' returns Console.WriteLine("result message="+message); } } You can also use Pyro Flame rather conveniently because of some wrapper classes: .. code-block:: java Config.SERIALIZER = Config.SerializerType.pickle; // flame requires the pickle serializer PyroProxy flame = new PyroProxy(hostname, port, "Pyro.Flame"); FlameModule r_module = (FlameModule) flame.call("module", "socket"); System.out.println("hostname=" + r_module.call("gethostname")); FlameRemoteConsole console = (FlameRemoteConsole) flame.call("console"); console.interact(); console.close(); Pyro4-4.23/docs/source/security.rst000066400000000000000000000123501227003673200173130ustar00rootroot00000000000000.. _security: ******** Security ******** .. warning:: Do not publish any Pyro objects to remote machines unless you've read and understood everything that is discussed in this chapter. This is also true when publishing Pyro objects with different credentials to other processes on the same machine. Why? In short: using Pyro has several security risks. Pyro has a few countermeasures to deal with them. Understanding the risks, the countermeasures, and their limits, is very important to avoid creating systems that are very easy to compromise by malicious entities. Pickle as serialization format (optional) ========================================= When configured to do so, Pyro is able to use the :py:mod:`pickle` module to serialize objects and then sends those pickles over the network. It is well known that using pickle for this purpose is a security risk. The main problem is that allowing a program to unpickle arbitrary data can cause arbitrary code execution and this may wreck or compromise your system. Although this may sound like a showstopper for using Pyro for anything serious, Pyro provides a few facilities to deal with this security risk. The most important one is that by default, a different serializer is used that doesn't have pickle's security problem. Other means to enhance security are discussed below. Network interface binding ========================= By default Pyro binds every server on localhost, to avoid exposing things on a public network or over the internet by mistake. If you want to expose your Pyro objects to anything other than localhost, you have to explicitly tell Pyro the network interface address it should use. This means it is a conscious effort to expose Pyro objects to remote machines. It is possible to tell Pyro the interface address by means of an environment variable or global config item (``HOST``). In some situations, or if you're paranoid, it is advisable to override this setting in your server program by setting the config item from within your own code instead of depending on an externally configured setting. Running Pyro servers with different credentials/user id ======================================================= The following is not a Pyro specific problem, but is important nonetheless: If you want to run your Pyro server as a different user id or with different credentials as regular users, *be very careful* what kind of Pyro objects you expose like this! Treat this situation as if you're exposing your server on the internet (even when it's only running on localhost). Keep in mind that it is still possible that a random user on the same machine connects to the local server. You may need additional security measures to prevent random users from calling your Pyro objects. Protocol encryption =================== Pyro doesn't encrypt the data it sends over the network. This means you must not transfer sensitive data on untrusted networks (especially user data, passwords, and such) because it is possible to eavesdrop. Either encrypt the data yourself before passing it to Pyro, or run Pyro over a secure network (VPN, ssl/ssh tunnel). Dotted names (object traversal) =============================== Using dotted names on Pyro proxies (such as ``proxy.aaa.bbb.ccc()``) is disallowed by default because it is a security vulnerability (for similar reasons as described here http://www.python.org/news/security/PSF-2005-001/ ). You can enable it with the ``DOTTEDNAMES`` config item, but be aware of the implications. The :file:`attributes` example shows one of the exploits you can perform if it is enabled. Environment variables overriding config items ============================================= Almost all config items can be overwritten by an environment variable. If you can't trust the environment in which your script is running, it may be a good idea to reset the config items to their default builtin values, without using any environment variables. See :doc:`config` for the proper way to do this. Preventing arbitrary connections: HMAC signature ================================================ Pyro suggests using a `HMAC signature `_ on every network transfer to prevent malicious requests. The idea is to only have legit clients connect to your Pyro server. Using the HMAC signature ensures that only clients with the correct secret key can create valid requests, and that it is impossible to modify valid requests (even though the network data is not encrypted). You need to create and configure a secure shared key in the ``HMAC_KEY`` config item. The key is a byte string and must be cryptographically secure (there are various methods to create such a key). Your server needs to set this key and every client that wants to connect to it also needs to set it. Pyro will cause a Python-level warning message if you run it without a HMAC key, but it will run just fine. The hashing algorithm that is used in the HMAC is SHA-1 (not MD5). .. warning:: It is hard to keep a shared secret key actually secret! People might read the source code of your clients and extract the key from it. Pyro itself provides no facilities to help you with this, sorry. Pyro4-4.23/docs/source/servercode.rst000066400000000000000000000454011227003673200176100ustar00rootroot00000000000000*************************** Servers: publishing objects *************************** This chapter explains how you write code that publishes objects to be remotely accessible. These objects are then called *Pyro objects* and the program that provides them, is often called a *server* program. (The program that calls the objects is usually called the *client*. Both roles can be mixed in a single program.) Make sure you are familiar with Pyro's :ref:`keyconcepts` before reading on. .. seealso:: :doc:`config` for several config items that you can use to tweak various server side aspects. .. _publish-objects: Pyro Daemon: publishing Pyro objects ==================================== To publish a regular Python object and turn it into a Pyro object, you have to tell Pyro about it. After that, your code has to tell Pyro to start listening for incoming requests and to process them. Both are handled by the *Pyro daemon*. In its most basic form, you create one or more objects that you want to publish as Pyro objects, you create a daemon, register the object(s) with the daemon, and then enter the daemon's request loop:: import Pyro4 class MyPyroThing(object): pass thing=MyPyroThing() daemon=Pyro4.Daemon() uri=daemon.register(thing) print uri daemon.requestLoop() After printing the uri, the server sits waiting for requests. The uri that is being printed looks a bit like this: ``PYRO:obj_dcf713ac20ce4fb2a6e72acaeba57dfd@localhost:51850`` It can be used in a *client* program to create a proxy and access your Pyro object with. .. note:: You can publish any regular Python object as a Pyro object. However since Pyro adds a few Pyro-specific attributes to the object, you can't use: * types that don't allow custom attributes, such as the builtin types (``str`` and ``int`` for instance) * types with ``__slots__`` (a possible way around this is to add Pyro's custom attributes to your ``__slots__``, but that isn't very nice) Oneliner Pyro object publishing ------------------------------- Ok not really a one-liner, but one statement: use ``serveSimple`` to publish a dict of objects and start Pyro's request loop. The code above could also be written as:: import Pyro4 class MyPyroThing(object): pass Pyro4.Daemon.serveSimple( { MyPyroThing(): None }, ns=False, verbose=True) Verbose is set to True because you want it to print out the generated random object uri, otherwise there is no way to connect to your object. You can also choose to provide object names yourself, to use or not use the name server, etc. See :py:func:`Pyro4.core.Daemon.serveSimple`. Note that the amount of options you can provide is quite limited. If you want to control the way the Pyro daemon is constructed, you have to do that by setting the appropriate config options before calling ``serveSimple``. Or you can create a daemon object yourself with the right arguments, and pass that to ``serveSimple`` so that it doesn't create a default daemon itself. Because they are so frequently used, ``serveSimple`` has a ``host`` and ``port`` parameter that you can use to control the host and port of the daemon that it creates (useful if you want to make it run on something else as localhost). Creating a Daemon ----------------- Pyro's daemon is :class:`Pyro4.core.Daemon` and you can also access it by its shortcut ``Pyro4.Daemon``. It has a few optional arguments when you create it: .. function:: Daemon([host=None, port=0, unixsocket=None, nathost=None, natport=None]) Create a new Pyro daemon. :param host: the hostname or IP address to bind the server on. Default is ``None`` which means it uses the configured default (which is localhost). :type host: str or None :param port: port to bind the server on. Defaults to 0, which means to pick a random port. :type port: int :param unixsocket: the name of a Unix domain socket to use instead of a TCP/IP socket. Default is ``None`` (don't use). :type unixsocket: str or None :param nathost: hostname to use in published addresses (useful when running behind a NAT firewall/router). Default is ``None`` which means to just use the normal host. For more details about NAT, see :ref:`nat-router`. :type host: str or None :param natport: port to use in published addresses (useful when running behind a NAT firewall/router). If you use 0 here, Pyro will replace the NAT-port by the internal port number to facilitate one-to-one NAT port mappings. :type port: int Registering objects ------------------- Every object you want to publish as a Pyro object needs to be registered with the daemon. You can let Pyro choose a unique object id for you, or provide a more readable one yourself. .. method:: Daemon.register(obj [, objectId=None]) Registers an object with the daemon to turn it into a Pyro object. :param obj: the object to register :param objectId: optional custom object id (must be unique). Default is to let Pyro create one for you. :type objectId: str or None :returns: an uri for the object :rtype: :class:`Pyro4.core.URI` It is important to do something with the uri that is returned: it is the key to access the Pyro object. You can save it somewhere, or perhaps print it to the screen. The point is, your client programs need it to be able to access your object (they need to create a proxy with it). Maybe the easiest thing is to store it in the Pyro name server. That way it is almost trivial for clients to obtain the proper uri and connect to your object. See :doc:`nameserver` for more information (:ref:`nameserver-registering`), but it boils down to getting a name server proxy and using its ``register`` method:: uri = daemon.register(some_object) ns = Pyro4.locateNS() ns.register("example.objectname", uri) .. note:: If you ever need to create a new uri for an object, you can use :py:meth:`Pyro4.core.Daemon.uriFor`. The reason this method exists on the daemon is because an uri contains location information and the daemon is the one that knows about this. Intermission: Example 1: server and client not using name server ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A little code example that shows the very basics of creating a daemon and publishing a Pyro object with it. Server code:: import Pyro4 class Thing(object): def method(self, arg): return arg*2 # ------ normal code ------ daemon = Pyro4.Daemon() uri = daemon.register(Thing()) print "uri=",uri daemon.requestLoop() # ------ alternatively, using serveSimple ----- Pyro4.Daemon.serveSimple( { Thing(): None }, ns=False, verbose=True) Client code example to connect to this object:: import Pyro4 # use the URI that the server printed: uri = "PYRO:obj_b2459c80671b4d76ac78839ea2b0fb1f@localhost:49383" thing = Pyro4.Proxy(uri) print thing.method(42) # prints 84 With correct additional parameters --described elsewhere in this chapter-- you can control on which port the daemon is listening, on what network interface (ip address/hostname), what the object id is, etc. Intermission: Example 2: server and client, with name server ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A little code example that shows the very basics of creating a daemon and publishing a Pyro object with it, this time using the name server for easier object lookup. Server code:: import Pyro4 class Thing(object): def method(self, arg): return arg*2 # ------ normal code ------ daemon = Pyro4.Daemon() ns = Pyro4.locateNS() uri = daemon.register(Thing()) ns.register("mythingy", uri) daemon.requestLoop() # ------ alternatively, using serveSimple ----- Pyro4.Daemon.serveSimple( { Thing(): "mythingy" }, ns=True, verbose=True) Client code example to connect to this object:: import Pyro4 thing = Pyro4.Proxy("PYRONAME:mythingy") print thing.method(42) # prints 84 Unregistering objects --------------------- When you no longer want to publish an object, you need to unregister it from the daemon: .. method:: Daemon.unregister(objectOrId) :param objectOrId: the object to unregister :type objectOrId: object itself or its id string Running the request loop ------------------------ Once you've registered your Pyro object you'll need to run the daemon's request loop to make Pyro wait for incoming requests. .. method:: Daemon.requestLoop([loopCondition]) :param loopCondition: optional callable returning a boolean, if it returns False the request loop will be aborted and the call returns This is Pyro's event loop and it will take over your program until it returns (it might never.) If this is not what you want, you can control it a tiny bit with the ``loopCondition``, or read the next paragraph. Integrating Pyro in your own event loop --------------------------------------- If you want to use a Pyro daemon in your own program that already has an event loop (aka main loop), you can't simply call ``requestLoop`` because that will block your program. A daemon provides a few tools to let you integrate it into your own event loop: * :py:attr:`Pyro4.core.Daemon.sockets` - list of all socket objects used by the daemon, to inject in your own event loop * :py:meth:`Pyro4.core.Daemon.events` - method to call from your own event loop when Pyro needs to process requests. Argument is a list of sockets that triggered. For more details and example code, see the :file:`eventloop` and :file:`gui_eventloop` examples. They show how to use Pyro including a name server, in your own event loop, and also possible ways to use Pyro from within a GUI program with its own event loop. Cleaning up ----------- To clean up the daemon itself (release its resources) either use the daemon object as a context manager in a ``with`` statement, or manually call :py:meth:`Pyro4.core.Daemon.close`. Autoproxying ============ Pyro will automatically take care of any Pyro objects that you pass around through remote method calls. It will replace them by a proxy automatically, so the receiving side can call methods on it and be sure to talk to the remote object instead of a local copy. There is no need to create a proxy object manually. All you have to do is to register the new object with the appropriate daemon:: def some_pyro_method(self): thing=SomethingNew() self._pyroDaemon.register(thing) return thing # just return it, no need to return a proxy This feature can be enabled or disabled by a config item, see :doc:`config`. (it is on by default). If it is off, a copy of the object itself is returned, and the client won't be able to interact with the actual new Pyro object in the server. There is a :file:`autoproxy` example that shows the use of this feature, and several other examples also make use of it. Note that when using the marshal serializer, this feature doesn't work. You have to use one of the other serializers to use autoproxying. Server types and Object concurrency model ========================================= Pyro supports multiple server types (the way the Daemon listens for requests). Select the desired type by setting the ``SERVERTYPE`` config item. It depends very much on what you are doing in your Pyro objects what server type is most suitable. For instance, if your Pyro object does a lot of I/O, it may benefit from the parallelism provided by the thread pool server. However if it is doing a lot of CPU intensive calculations, the multiplexed server may be more appropriate. If in doubt, go with the default setting. #. threaded server (servertype ``"threaded"``, this is the default) This server uses a thread pool to handle incoming proxy connections. The size of the pool is configurable via various config items. Every proxy on a client that connects to the daemon will be assigned to a thread to handle the remote method calls. This way multiple calls can potentially be processed concurrently. This means your Pyro object must be *thread-safe*! If you access a shared resource from your Pyro object you may need to take thread locking measures such as using Queues. If the thread pool is too small for the number of proxy connections, new proxy connections will be put to wait until another proxy disconnects from the server. #. multiplexed server (servertype ``"multiplex"``) This server uses a select (or poll, if available) based connection multiplexer to process all remote method calls sequentially. No threads are used in this server. It means only one method call is running at a time, so if it takes a while to complete, all other calls are waiting for their turn (even when they are from different proxies). .. note:: If the ``ONEWAY_THREADED`` config item is enabled (it is by default), *oneway* method calls will be executed in a separate worker thread, regardless of the server type you're using. .. note:: It must be pretty obvious but the following is a very important concept so it is repeated once more to be 100% clear: Currently, you register *objects* with Pyro, not *classes*. This means remote method calls to a certain Pyro object always run on the single instance that you registered with Pyro. *When to choose which server type?* With the threadpool server at least you have a chance to achieve concurrency, and you don't have to worry much about blocking I/O in your remote calls. The usual trouble with using threads in Python still applies though: Python threads don't run concurrently unless they release the :abbr:`GIL (Global Interpreter Lock)`. If they don't, you will still hang your server process. For instance if a particular piece of your code doesn't release the :abbr:`GIL (Global Interpreter Lock)` during a longer computation, the other threads will remain asleep waiting to acquire the :abbr:`GIL (Global Interpreter Lock)`. One of these threads may be the Pyro server loop and then your whole Pyro server will become unresponsive. Doing I/O usually means the :abbr:`GIL (Global Interpreter Lock)` is released. Some C extension modules also release it when doing their work. So, depending on your situation, not all hope is lost. With the multiplexed server you don't have threading problems: everything runs in a single main thread. This means your requests are processed sequentially, but it's easier to make the Pyro server unresponsive. Any operation that uses blocking I/O or a long-running computation will block all remote calls until it has completed. Serialization ============= Pyro will serialize the objects that you pass to the remote methods, so they can be sent across a network connection. Depending on the serializer that is being used for your Pyro server, there will be some limitations on what objects you can use, and what serialization format is required of the clients that connect to your server. You specify one or more serializers that are accepted in the daemon/server by setting the ``SERIALIZERS_ACCEPTED`` config item. This is a set of serializer names that are allowed to be used with your server. It defaults to the set of 'safe' serializers. A client that successfully talks to your server will get responses using the same serializer as the one used to send requests to the server. If your server also uses Pyro client code/proxies, you might also need to select the serializer for these by setting the ``SERIALIZER`` config item. See the :doc:`/config` chapter for details about the config items. See :ref:`object-serialization` for more details about serialization, the new config items, and how to deal with existing code that relies on pickle. .. note:: Since Pyro 4.20 the default serializer is "``serpent``". It used to be "``pickle``" in older versions. The default set of accepted serializers in the server is the set of 'safe' serializers, so "``pickle``" is not among the default. Other features ============== Attributes added to Pyro objects -------------------------------- The following attributes will be added your object if you register it as a Pyro object: * ``_pyroId`` - the unique id of this object (a ``str``) * ``_pyroDaemon`` - a reference to the :py:class:`Pyro4.core.Daemon` object that contains this object Even though they start with an underscore (and are private, in a way), you can use them as you so desire. As long as you don't modify them! The daemon reference for instance is useful to register newly created objects with, to avoid the need of storing a global daemon object somewhere. These attributes will be removed again once you unregister the object. Network adapter binding ----------------------- All Pyro daemons bind on localhost by default. This is because of security reasons. This means only processes on the same machine have access to your Pyro objects. If you want to make them available for remote machines, you'll have to tell Pyro on what network interface address it must bind the daemon. .. warning:: Read chapter :doc:`security` before exposing Pyro objects to remote machines! There are a few ways to tell Pyro what network address it needs to use. You can set a global config item ``HOST``, or pass a ``host`` parameter to the constructor of a Daemon, or use a command line argument if you're dealing with the name server. For more details, refer to the chapters in this manual about the relevant Pyro components. Pyro provides a couple of utility functions to help you with finding the appropriate IP address to bind your servers on if you want to make them publicly accessible: * :py:func:`Pyro4.socketutil.getIpAddress` * :py:func:`Pyro4.socketutil.getInterfaceAddress` Daemon Pyro interface --------------------- A rather interesting aspect of Pyro's Daemon is that it (partly) is a Pyro object itself. This means it exposes a couple of remote methods that you can also invoke yourself if you want. The object exposed is :class:`Pyro4.core.DaemonObject` (as you can see it is a bit limited still). You access this object by creating a proxy for the ``"Pyro.Daemon"`` object. That is a reserved object name. You can use it directly but it is preferable to use the constant ``Pyro4.constants.DAEMON_NAME``. An example follows that accesses the daemon object from a running name server:: >>> import Pyro4 >>> daemon=Pyro4.Proxy("PYRO:"+Pyro4.constants.DAEMON_NAME+"@localhost:9090") >>> daemon.ping() >>> daemon.registered() ['Pyro.NameServer', 'Pyro.Daemon'] Pyro4-4.23/docs/source/tipstricks.rst000066400000000000000000000341411227003673200176450ustar00rootroot00000000000000.. _tipstricks: ************* Tips & Tricks ************* Best practices ============== Avoid circular communication topologies. ---------------------------------------- When you can have a circular communication pattern in your system (A-->B-->C-->A) this can cause some problems: * when reusing a proxy it causes a deadlock because the proxy is already being used for an active remote call. See the :file:`deadlock` example. * with the multiplex servertype, the server itself may also block for all other remote calls because the handling of the first is not yet completed. Avoid circularity, or use *oneway* method calls on at least one of the links in the chain. Release your proxies if you can. -------------------------------- A connected proxy that is unused takes up resources on the server. In the case of the threadpool server type, it locks up a single thread. If you have too many connected proxies at the same time, the server may run out of threads and stops responding. (The multiplex server doesn't have this particular issue). It is a good thing to think about when you can release a proxy in your code. Don't worry about reconnecting, that's done automatically once it is used again. You can use explicit ``_pyroRelease`` calls or use the proxy from within a context manager. It's not a good idea to release it after every single remote method call though, because then the cost of reconnecting the socket will cause a serious drop in performance (unless every call is at least a few seconds after the previous one). Avoid large binary blobs over the wire. --------------------------------------- Pyro is not designed to efficiently transfer large amounts of binary data over the network. Try to find another protocol that better suits this requirement. Read :ref:`binarytransfer` for some more details about this. Minimize object graphs that travel over the wire. ------------------------------------------------- Pyro will serialize the whole object graph you're passing, even when only a tiny fraction of it is used on the receiving end. Be aware of this: it may be necessary to define special lightweight objects for your Pyro interfaces that hold the data you need, rather than passing a huge object structure. Logging ======= If you configure it (see :ref:`config-items`) Pyro will write a bit of debug information, errors, and notifications to a log file. It uses Python's standard :py:mod:`logging` module for this. Once enabled, your own program code could use Pyro's logging setup as well. But if you want to configure your own logging, make sure you do that before any Pyro imports. Then Pyro will skip its own autoconfig. Multiple network interfaces =========================== This is a difficult subject but here are a few short notes about it. *At this time, Pyro doesn't support running on multiple network interfaces at the same time*. You can bind a deamon on INADDR_ANY (0.0.0.0) though, including the name server. But weird things happen with the URIs of objects published through these servers, because they will point to 0.0.0.0 and your clients won't be able to connect to the actual objects. The name server however contains a little trick. The broadcast responder can also be bound on 0.0.0.0 and it will in fact try to determine the correct ip address of the interface that a client needs to use to contact the name server on. So while you cannot run Pyro daemons on 0.0.0.0 (to respond to requests from all possible interfaces), sometimes it is possible to run only the name server on 0.0.0.0. The success ratio of all this depends heavily on your network setup. Same major Python version required ================================== When Pyro is configured to use pickle as its serialization format, it is required to have the same *major* Python versions on your clients and your servers. Otherwise the different parties cannot decipher each others serialized data. This means you cannot let Python 2.x talk to Python 3.x with Pyro. However it should be fine to have Python 2.6.2 talk to Python 2.7.3 for instance. Using one of the implementation independent protocols (serpent or json) will avoid this limitation. Wire protocol version ===================== Here is a little tip to find out what wire protocol version a given Pyro server is using. This could be useful if you are getting ``ProtocolError: invalid data or unsupported protocol version`` or something like that. It also works with Pyro 3.x. **Server** This is a way to figure out the protocol version number a given Pyro server is using: by reading the first 6 bytes from the server socket connection. The Pyro daemon will respond with a 4-byte string "``PYRO``" followed by a 2-byte number that is the protocol version used:: $ nc | od -N 6 -t x1c 0000000 50 59 52 4f 00 05 P Y R O \0 005 This one is talking protocol version ``00 05`` (5). This low number means it is a Pyro 3.x server. When you try it on a Pyro 4 server:: $ nc | od -N 6 -t x1c 0000000 50 59 52 4f 00 2c P Y R O \0 , This one is talking protocol version ``00 2c`` (44). For Pyro4 the protocol version started at 40 for the first release and is now at 46 for the current release at the time of writing. **Client** To find out the protocol version that your client code is using, you can use this:: $ python -c "import Pyro4.constants as c; print(c.PROTOCOL_VERSION)" .. _future-functions: Asynchronous ('future') normal function calls ============================================= Pyro provides an async proxy wrapper to call remote methods asynchronously, see :ref:`async-calls`. For normal Python code, Python provides a similar mechanism in the form of the :py:class:`Pyro4.futures.Future` class (also available as ``Pyro4.Future``). With a syntax that is slightly different from normal method calls, it provides the same asynchronous function calls as the async proxy has. Note that Python itself has a similar thing in the standard library since version 3.2, see http://docs.python.org/3/library/concurrent.futures.html#future-objects . However Pyro's Future object is available on older Python versions too, and works slightly differently. It's also a little bit easier to work with. You create a ``Future`` object for a callable that you want to execute in the background, and receive its results somewhere in the future:: def add(x,y): return x+y futurecall = Pyro4.Future(add) result = futurecall(4,5) # do some other stuff... then access the value summation = result.value Actually calling the `Future` object returns control immediately and results in a :py:class:`Pyro4.futures.FutureResult` object. This is the exact same class as with the async proxy. The most important attributes are ``value``, ``ready`` and the ``wait`` method. See :ref:`async-calls` for more details. You can also chain multiple calls, so that the whole call chain is executed sequentially in the background. Rather than doing this on the ``FutureResult`` result object, you should do it directly on the ``Future`` object, with the :py:meth:`Pyro4.futures.Future.then` method. It has the same signature as the ``then`` method from the ``FutureResult`` class:: futurecall = Pyro4.Future(something) futurecall.then(somethingelse, 44) futurecall.then(lastthing, optionalargument="something") See the :file:`futures` example for more details and example code. DNS setup ========= Pyro depends on a working DNS configuration, at least for your local hostname (i.e. 'pinging' your local hostname should work). If your local hostname doesn't resolve to an IP address, you'll have to fix this. This can usually be done by adding an entry to the hosts file. For OpenSUSE, you can also use Yast to fix it (go to Network Settings, enable "Assign hostname to loopback IP"). If Pyro detects a problem with the dns setup it will log a WARNING in the logfile (if logging is enabled), something like: ``weird DNS setup: your-computer-hostname resolves to localhost (127.x.x.x)`` .. _nat-router: Pyro behind a NAT router/firewall ================================= You can run Pyro behind a NAT router/firewall. Assume the external hostname is 'pyro.server.com' and the external port is 5555. Also assume the internal host is 'server1.lan' and the internal port is 9999. You'll need to have a NAT rule that maps pyro.server.com:5555 to server1.lan:9999. You'll need to start your Pyro daemon, where you specify the ``nathost`` and ``natport`` arguments, so that Pyro knows it needs to 'publish' URIs containing that *external* location instead of just using the internal addresses:: # running on server1.lan d = Pyro4.Daemon(port=9999, nathost="pyro.server.com", natport=5555) uri = d.register(Something(), "thing") print uri # "PYRO:thing@pyro.server.com:5555" As you see, the URI now contains the external address. :py:meth:`Pyro4.core.Daemon.uriFor` by default returns URIs with a NAT address in it (if ``nathost`` and ``natport`` were used). You can override this by setting ``nat=False``:: print d.uriFor("thing") # "PYRO:thing@pyro.server.com:5555" print d.uriFor("thing", nat=False) # "PYRO:thing@localhost:36124" uri2 = d.uriFor(uri.object, nat=False) # get non-natted uri The Name server can also be started behind a NAT: it has a couple of command line options that allow you to specify a nathost and natport for it. See :ref:`nameserver-nameserver`. .. note:: The broadcast responder always returns the internal address, never the external NAT address. Also, the name server itself won't translate any URIs that are registered with it. So if you want it to publish URIs with 'external' locations in them, you have to tell the Daemon that registers these URIs to use the correct nathost and natport as well. .. note:: In some situations the NAT simply is configured to pass through any port one-to-one to another host behind the NAT router/firewall. Pyro facilitates this by allowing you to set the natport to 0, in which case Pyro will replace it by the internal port number. .. _binarytransfer: Binary data transfer ==================== Pyro is not meant as a tool to transfer large amounts of binary data (images, sound files, video clips). Its wire protocol is not optimized for these kinds of data. The occasional transmission of such data is fine (:doc:`flame` even provides a convenience method for that, if you like: :meth:`Pyro4.utils.flame.Flame.sendfile`) but usually it is better to use something else to do the actual data transfer (file share+file copy, ftp, scp, rsync). The following table is an indication of the relative speeds when dealing with large amounts of binary data. It lists the results of the :file:`hugetransfer` example, using python 3.3, over a 100 mbit lan connection: ========== ========== ============= ================ serializer str mb/sec bytes mb/sec bytearray mb/sec ========== ========== ============= ================ pickle 30.9 32.8 31.8 marshal 30.0 28.8 32.4 serpent 12.5 9.1 9.1 json 22.5 not supported not supported ========== ========== ============= ================ The json serializer can't deal with actual binary data at all because it can't serialize these types. The serpent serializer is particularly inefficient when dealing with binary data, because by design, it has to encode and decode it as a base-64 string. Marshal and pickle are relatively efficient. But here is a short overview of the ``pickle`` wire protocol overhead for the possible binary types: ``str`` *Python 2.x:* efficient; directly encoded as a byte sequence, because that's what it is. *Python 3.x:* inefficient; encoded in UTF-8 on the wire, because it is a unicode string. ``bytes`` *Python 2.x:* same as ``str`` (available in Python 2.6 and 2.7) *Python 3.x:* efficient; directly encoded as a byte sequence. ``bytearray`` Inefficient; encoded as UTF-8 on the wire (pickle does this in both Python 2.x and 3.x) ``array("B")`` (array of unsigned ints of size 1) *Python 2.x:* very inefficient; every element is encoded as a separate token+value. *Python 3.x:* efficient; uses machine type encoding on the wire (a byte sequence). MSG_WAITALL socket option ========================= Pyro will use the ``MSG_WAITALL`` socket option to receive large messages, if it decides that the feature is available and working correctly. On most systems that define the ``socket.MSG_WAITALL`` symbol, it does, except on Windows: even though the option is there, it doesn't work reliably. If you want to check in your code what Pyro's behavior is, see the ``socketutil.USE_MSG_WAITALL`` attribute (it's a boolean that will be set to False if Pyro decides it can't or should not use MSG_WAITALL). IPV6 support ============ Pyro4 supports IPv6 since version 4.18. You can use IPv6 addresses in the same places where you would normally have used IPv4 addresses. There's one exception: the address notation in a Pyro URI. For a numeric IPv6 address in a Pyro URI, you have to enclose it in brackets. For example: ``PYRO:objectname@[::1]:3456`` points at a Pyro object located on the IPv6 "::1" address (localhost). When Pyro displays a numeric IPv6 location from an URI it will also use the bracket notation. This bracket notation is only used in Pyro URIs, everywhere else you just type the IPv6 address without brackets. To tell Pyro to prefer using IPv6 you can use the ``PREFER_IP_VERSION`` config item. It is set to 4 by default, for backward compatibility reasons. This means that unless you change it to 6 (or 0), Pyro will be using IPv4 addressing. There is a new method to see what IP addressing is used: :py:meth:`Pyro4.socketutil.getIpVersion`, and a few other methods in :py:mod:`Pyro4.socketutil` gained a new optional argument to tell it if it needs to deal with an ipv6 address rather than ipv4, but these are rarely used in client code. Pyro4-4.23/docs/source/tutorials.rst000066400000000000000000001321631227003673200174770ustar00rootroot00000000000000.. include:: ******** Tutorial ******** This tutorial will explain a couple of basic Pyro concepts, a little bit about the name server, and you'll learn to write a simple Pyro application. You'll do this by writing a warehouse system and a stock market simulator, that demonstrate some key Pyro techniques. Warm-up ======= Before proceeding, you should install Pyro if you haven't done so. For instructions about that, see :doc:`install`. In this tutorial, you will use Pyro's default configuration settings, so once Pyro is installed, you're all set! All you need is a text editor and a couple of console windows. During the tutorial, you are supposed to run everything on a single machine. This avoids initial networking complexity. .. note:: For security reasons, Pyro runs stuff on localhost by default. If you want to access things from different machines, you'll have to tell Pyro to do that explicitly. At the end is a small paragraph :ref:`not-localhost` that tells you how you can run the various components on different machines. .. note:: The code of the two tutorial 'projects' is included in the Pyro source archive. Just installing Pyro won't provide this. If you don't want to type all the code, you should extract the Pyro source archive (:file:`Pyro4-X.Y.tar.gz`) somewhere. You will then have an :file:`examples` directory that contains a truckload of examples, including the two tutorial projects we will be creating later in this tutorial, :file:`warehouse` and :file:`stockquotes`. (There is more in there as well: the :file:`tests` directory contains the test suite with all the unittests for Pyro's code base.) Pyro concepts and tools ======================= Pyro enables code to call methods on objects even if that object is running on a remote machine:: +----------+ +----------+ | server A | | server B | | | < network > | | | Python | | Python | | OBJECT ----------foo.invoke()--------> OBJECT | | | | foo | +----------+ +----------+ Pyro is mainly used as a library in your code but it also has several supporting command line tools [#commandline]_. We won't explain every one of them here as you will only need the "name server" for this tutorial. .. [#commandline] Actually there are no scripts or command files included with Pyro right now. The :ref:`command-line` are invoked by starting their package directly using the :kbd:`-m` argument of the Python interpreter. .. _keyconcepts: Key concepts ^^^^^^^^^^^^ Here are a couple of key concepts you encounter when using Pyro: Proxy A proxy is a substitute object for "the real thing". It intercepts the method calls you would normally do on an object as if it was the actual object. Pyro then performs some magic to transfer the call to the computer that contains the *real* object, where the actual method call is done, and the results are returned to the caller. This means the calling code doesn't have to know if it's dealing with a normal or a remote object, because the code is identical. The class implementing Pyro proxies is :class:`Pyro4.core.Proxy` :abbr:`URI (Unique resource identifier)` This is what Pyro uses to identify every object. (similar to what a web page URL is to point to the different documents on the web). Its string form is like this: "PYRO:" + object name + "@" + server name + port number. There are a few other forms it can take as well. You can write the protocol in lowercase too if you want ("pyro:") but it will automatically be converted to uppercase internally. The class implementing Pyro uris is :class:`Pyro4.core.URI` Pyro object This is a normal Python object but it is registered with Pyro so that you can access it remotely. Pyro objects are written just as any other object but the fact that Pyro knows something about them makes them special, in the way that you can call methods on them from other programs. Pyro daemon (server) This is the part of Pyro that listens for remote method calls, dispatches them to the appropriate actual objects, and returns the results to the caller. All Pyro objects are registered in one or more daemons. Pyro name server The name server is a utility that provides a phone book for Pyro applications: you use it to look up a "number" by a "name". The name in Pyro's case is the logical name of a remote object. The number is the exact location where Pyro can contact the object. Usually there is just *one* name server running in your network. Serialization This is the process of transforming objects into streams of bytes that can be transported over the network. The receiver deserializes them back into actual objects. Pyro needs to do this with all the data that is passed as arguments to remote method calls, and their response data. Not all objects can be serialized, so it is possible that passing a certain object to Pyro won't work even though a normal method call would accept it just fine. Starting a name server ^^^^^^^^^^^^^^^^^^^^^^ While the use of the Pyro name server is optional, we will use it in this tutorial. It also shows a few basic Pyro concepts, so let us begin by explaining a little about it. Open a console window and execute the following command to start a name server: :command:`python -Wignore -m Pyro4.naming` The :kbd:`-Wignore` is added to suppress a Pyro warning that we're not interested in right now. The name server will start and it prints something like:: Not starting broadcast server for localhost. NS running on localhost:9090 (127.0.0.1) URI = PYRO:Pyro.NameServer@localhost:9090 .. sidebar:: Localhost By default, Pyro uses *localhost* to run stuff on, so you can't by mistake expose your system to the outside world. You'll need to tell Pyro explicitly to use something else than *localhost*. But it is fine for the tutorial, so we leave it as it is. The name server has started and is listening on *localhost port 9090*. It also printed an :abbr:`URI (unique resource identifier)`. Remember that this is what Pyro uses to identify every object. The name server can be stopped with a :kbd:`control-c`, or on Windows, with :kbd:`ctrl-break`. But let it run in the background for the rest of this tutorial. Interacting with the name server ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ There's another command line tool that let you interact with the name server: "nsc" (name server control tool). You can use it, amongst other things, to see what all known registered objects in the naming server are. Let's do that right now. Type: :command:`python -Wignore -m Pyro4.nsc list` (the :kbd:`-Wignore` again is to suppress a warning) and it will print something like this:: --------START LIST Pyro.NameServer --> PYRO:Pyro.NameServer@localhost:9090 --------END LIST The only object that is currently registered, is the name server itself! (Yes, the name server is a Pyro object itself. Pyro and the "nsc" tool are using Pyro to talk to it). .. note:: As you can see, the name ``Pyro.NameServer`` is registered to point to the URI that we saw earlier. This is mainly for completeness sake, and is not often used, because there are different ways to get to talk to the name server (see below). .. sidebar:: The NameServer object The name server itself is a normal Pyro object which means the 'nsc' tool, and any other code that talks to it, is just using normal Pyro methods. The only "trickery" that makes it a bit different from other Pyro servers is perhaps the broadcast responder, and the two command line tools to interact with it (``Pyro4.naming`` and ``Pyro4.nsc``) This is cool, but there's a little detail left unexplained: *How did the nsc tool know where the name server was?* Pyro has a couple of tactics to locate a name server. The nsc tool uses them too: Pyro uses a network broadcast to see if there's a name server available somewhere (the name server contains a broadcast responder that will respond "Yeah hi I'm here"). So in many cases you won't have to configure anything to be able to discover the name server. If nobody answers though, Pyro tries the configured default or custom location. If still nobody answers it prints a sad message and exits. However if it found the name server, it is then possible to talk to it and get the location of any other registered object. . This means that you won't have to hard code any object locations in your code, and that the code is capable of dynamically discovering everything at runtime. *But enough of that.* We need to start looking at how to actually write some code ourselves that uses Pyro! Building a Warehouse ==================== .. hint:: All code of this part of the tutorial can be found in the :file:`examples/warehouse` directory. You'll build build a simple warehouse that stores items, and that everyone can visit. Visitors can store items and retrieve other items from the warehouse (if they've been stored there). In this tutorial you'll first write a normal Python program that more or less implements the complete warehouse system, but in vanilla Python code. After that you'll add Pyro support to it, to make it a distributed warehouse system, where you can visit the central warehouse from many different computers. phase 1: a simple prototype ^^^^^^^^^^^^^^^^^^^^^^^^^^^ To start with, write the vanilla Python code for the warehouse and its visitors. This prototype is fully working but everything is running in a single process. It contains no Pyro code at all, but shows what the system is going to look like later on. The ``Warehouse`` object simply stores an array of items which we can query, and allows for a person to take an item or to store an item. Here is the code (:file:`warehouse.py`):: from __future__ import print_function class Warehouse(object): def __init__(self): self.contents = ["chair", "bike", "flashlight", "laptop", "couch"] def list_contents(self): return self.contents def take(self, name, item): self.contents.remove(item) print("{0} took the {1}.".format(name, item)) def store(self, name, item): self.contents.append(item) print("{0} stored the {1}.".format(name, item)) Then there is a ``Person`` that can visit the warehouse. The person has a name and deposit and retrieve actions on a particular warehouse. Here is the code (:file:`person.py`):: from __future__ import print_function import sys if sys.version_info < (3, 0): input = raw_input class Person(object): def __init__(self, name): self.name = name def visit(self, warehouse): print("This is {0}.".format(self.name)) self.deposit(warehouse) self.retrieve(warehouse) print("Thank you, come again!") def deposit(self, warehouse): print("The warehouse contains:", warehouse.list_contents()) item = input("Type a thing you want to store (or empty): ").strip() if item: warehouse.store(self.name, item) def retrieve(self, warehouse): print("The warehouse contains:", warehouse.list_contents()) item = input("Type something you want to take (or empty): ").strip() if item: warehouse.take(self.name, item) Finally you need a small script that actually runs the code. It creates the warehouse and two visitors, and makes the visitors perform their actions in the warehouse. Here is the code (:file:`visit.py`):: # This is the code that runs this example. from warehouse import Warehouse from person import Person warehouse = Warehouse() janet = Person("Janet") henry = Person("Henry") janet.visit(warehouse) henry.visit(warehouse) Run this simple program. It will output something like this:: $ python visit.py This is Janet. The warehouse contains: ['chair', 'bike', 'flashlight', 'laptop', 'couch'] Type a thing you want to store (or empty): television # typed in Janet stored the television. The warehouse contains: ['chair', 'bike', 'flashlight', 'laptop', 'couch', 'television'] Type something you want to take (or empty): couch # <-- typed in Janet took the couch. Thank you, come again! This is Henry. The warehouse contains: ['chair', 'bike', 'flashlight', 'laptop', 'television'] Type a thing you want to store (or empty): bricks # <-- typed in Henry stored the bricks. The warehouse contains: ['chair', 'bike', 'flashlight', 'laptop', 'television', 'bricks'] Type something you want to take (or empty): bike # <-- typed in Henry took the bike. Thank you, come again! phase 2: first Pyro version ^^^^^^^^^^^^^^^^^^^^^^^^^^^ That wasn't very exciting but you now have working code for the basics of the warehouse system. Now you'll use Pyro to turn the warehouse into a standalone component, that people from other computers can visit. You'll need to add a couple of lines to the :file:`warehouse.py` file so that it will start a Pyro server for the warehouse object. The easiest way to do this is to create the object that you want to make available as Pyro object, and register it with a 'Pyro daemon' (the server that listens for and processes incoming remote method calls):: warehouse = Warehouse() Pyro4.Daemon.serveSimple( { warehouse: "example.warehouse" }, ns = False) You'll also need to add a little ``main`` function so it will be started correctly, which should make the code now look like this (:file:`warehouse.py`):: from __future__ import print_function import Pyro4 import person class Warehouse(object): def __init__(self): self.contents = ["chair", "bike", "flashlight", "laptop", "couch"] def list_contents(self): return self.contents def take(self, name, item): self.contents.remove(item) print("{0} took the {1}.".format(name, item)) def store(self, name, item): self.contents.append(item) print("{0} stored the {1}.".format(name, item)) def main(): warehouse = Warehouse() Pyro4.Daemon.serveSimple( { warehouse: "example.warehouse" }, ns = False) if __name__=="__main__": main() Start the warehouse in a new console window, it will print something like this:: $ python warehouse.py Object <__main__.Warehouse object at 0x025F4FF0>: uri = PYRO:example.warehouse@localhost:51279 Pyro daemon running. It will become clear what you need to do with this output in a second. You now need to slightly change the :file:`visit.py` script that runs the thing. Instead of creating a warehouse directly and letting the persons visit that, it is going to use Pyro to connect to the stand alone warehouse object that you started above. It needs to know the location of the warehouse object before it can connect to it. This is the **uri** that is printed by the warehouse program above (``PYRO:example.warehouse@localhost:51279``). You'll need to ask the user to enter that uri string into the program, and use Pyro to create a `proxy` to the remote object:: uri = input("Enter the uri of the warehouse: ").strip() warehouse = Pyro4.Proxy(uri) That is all you need to change. Pyro will transparently forward the calls you make on the warehouse object to the remote object, and return the results to your code. So the code will now look like this (:file:`visit.py`):: # This is the code that visits the warehouse. import sys import Pyro4 from person import Person if sys.version_info<(3,0): input = raw_input uri = input("Enter the uri of the warehouse: ").strip() warehouse = Pyro4.Proxy(uri) janet = Person("Janet") henry = Person("Henry") janet.visit(warehouse) henry.visit(warehouse) Notice that the code of ``Warehouse`` and ``Person`` classes didn't change *at all*. Run the program. It will output something like this:: $ python visit.py Enter the uri of the warehouse: PYRO:example.warehouse@localhost:51279 # copied from warehouse output This is Janet. The warehouse contains: ['chair', 'bike', 'flashlight', 'laptop', 'couch'] Type a thing you want to store (or empty): television # typed in The warehouse contains: ['chair', 'bike', 'flashlight', 'laptop', 'couch', 'television'] Type something you want to take (or empty): couch # <-- typed in Thank you, come again! This is Henry. The warehouse contains: ['chair', 'bike', 'flashlight', 'laptop', 'television'] Type a thing you want to store (or empty): bricks # <-- typed in The warehouse contains: ['chair', 'bike', 'flashlight', 'laptop', 'television', 'bricks'] Type something you want to take (or empty): bike # <-- typed in Thank you, come again! And notice that in the other console window, where the warehouse server is running, the following is printed:: Janet stored the television. Janet took the couch. Henry stored the bricks. Henry took the bike. phase 3: final Pyro version ^^^^^^^^^^^^^^^^^^^^^^^^^^^ The code from the previous phase works fine and could be considered to be the final program, but is a bit cumbersome because you need to copy-paste the warehouse URI all the time to be able to use it. You will simplify it a bit in this phase by using the Pyro name server. Also, you will use the Pyro excepthook to print a nicer exception message if anything goes wrong (by taking something from the warehouse that is not present! Try that now with the code from phase 2. You will get a ``ValueError: list.remove(x): x not in list`` but with a not so useful stack trace). .. Note:: Once again you can leave code of the ``Warehouse`` and ``Person`` classes **unchanged**. As you can see, Pyro is not getting in your way at all here. You can often use it with only adding a couple of lines to your existing code. Okay, stop the warehouse program from phase 2 if it is still running, and check if the name server that you started in `Starting a name server`_ is still running in its own console window. In :file:`warehouse.py` locate the statement ``Pyro4.Daemon.serveSimple(...`` and change the ``ns = False`` argument to ``ns = True``. This tells Pyro to use a name server to register the objects in. (The ``Pyro4.Daemon.serveSimple`` is a very easy way to start a Pyro server but it provides very little control. You will learn about another way of starting a server in `Building a Stock market simulator`_). In :file:`visit.py` remove the input statement that asks for the warehouse uri, and change the way the warehouse proxy is created. Because you are now using a name server you can ask Pyro to locate the warehouse object automatically:: warehouse = Pyro4.Proxy("PYRONAME:example.warehouse") Finally, install the ``Pyro4.util.excepthook`` as excepthook. You'll soon see what this does to the exceptions and stack traces your program produces when something goes wrong with a Pyro object. So the code should look something like this (:file:`visit.py`):: # This is the code that visits the warehouse. import sys import Pyro4 import Pyro4.util from person import Person sys.excepthook = Pyro4.util.excepthook warehouse = Pyro4.Proxy("PYRONAME:example.warehouse") janet = Person("Janet") henry = Person("Henry") janet.visit(warehouse) henry.visit(warehouse) Start the warehouse program again in a separate console window. It will print something like this:: $ python warehouse.py Object <__main__.Warehouse object at 0x02496050>: uri = PYRO:obj_426e82eea7534fb5bc78df0b5c0b6a04@localhost:51294 name = example.warehouse Pyro daemon running. As you can see the uri is different this time, it now contains some random id code instead of a name. However it also printed an object name. This is the name that is now used in the name server for your warehouse object. Check this with the 'nsc' tool: :command:`python -m Pyro4.nsc list`, which will print something like:: --------START LIST Pyro.NameServer --> PYRO:Pyro.NameServer@localhost:9090 example.warehouse --> PYRO:obj_426e82eea7534fb5bc78df0b5c0b6a04@localhost:51294 --------END LIST This means you can now refer to that warehouse object using the name ``example.warehouse`` and Pyro will locate the correct object for you automatically. This is what you changed in the :file:`visit.py` code so run that now to see that it indeed works! **Remote exception:** You also installed Pyro's custom excepthook so try that out. Run the :file:`visit.py` script and try to take something from the warehouse that is not present (for instance, batteries):: Type something you want to take (or empty): batteries Traceback (most recent call last): File "visit.py", line 12, in janet.visit(warehouse) File "d:\PROJECTS\Pyro4\examples\warehouse\phase3\person.py", line 14, in visit self.retrieve(warehouse) File "d:\PROJECTS\Pyro4\examples\warehouse\phase3\person.py", line 25, in retrieve warehouse.take(self.name, item) File "d:\PROJECTS\Pyro4\src\Pyro4\core.py", line 161, in __call__ return self.__send(self.__name, args, kwargs) File "d:\PROJECTS\Pyro4\src\Pyro4\core.py", line 314, in _pyroInvoke raise data ValueError: list.remove(x): x not in list +--- This exception occured remotely (Pyro) - Remote traceback: | Traceback (most recent call last): | File "d:\PROJECTS\Pyro4\src\Pyro4\core.py", line 824, in handleRequest | data=method(*vargs, **kwargs) # this is the actual method call to the Pyro object | File "warehouse.py", line 14, in take | self.contents.remove(item) | ValueError: list.remove(x): x not in list +--- End of remote traceback What you can see now is that you not only get the usual exception traceback, *but also the exception that occurred in the remote warehouse object on the server* (the "remote traceback"). This can greatly help locating problems! As you can see it contains the source code lines from the warehouse code that is running in the server, as opposed to the normal local traceback that only shows the remote method call taking place inside Pyro... Building a Stock market simulator ================================= .. hint:: All of the code of this part of the tutorial can be found in the :file:`examples/stockquotes` directory. You'll build a simple stock quote system. The idea is that we have multiple stock markets producing stock symbol quotes. There is an aggregator that combines the quotes from all stock markets. Finally there are multiple viewers that can register themselves by the aggregator and let it know what stock symbols they're interested in. The viewers will then receive near-real-time stock quote updates for the symbols they selected. (Everything is fictional, of course): ============= ====== ========== ====== ======== Stockmarket 1 |rarr| |rarr| Viewer 1 Stockmarket 2 |rarr| Aggregator |rarr| Viewer 2 ... ... Stockmarket N |rarr| |rarr| Viewer N ============= ====== ========== ====== ======== phase 1: simple prototype ^^^^^^^^^^^^^^^^^^^^^^^^^ Again, like the previous application (the warehouse), you first create a working version of the system by only using normal Python code. This simple prototype will be functional but everything will be running in a single process. It contains no Pyro code at all, but shows what the system is going to look like later on. First create a file :file:`stockmarket.py` that will simulate a stock market that is producing stock quotes for registered companies. You should be able to add a 'listener' to it that will be receiving stock quote updates. It should be able to report the stock symbols that are being traded in this market as well. The code is as follows:: # stockmarket.py import random class StockMarket(object): def __init__(self, marketname, symbols): self.name = marketname self.symbolmeans = {} for symbol in symbols: self.symbolmeans[symbol] = random.uniform(20, 200) self.aggregators = [] def generate(self): quotes = {} for symbol, mean in self.symbolmeans.items(): if random.random() < 0.2: quotes[symbol] = round(random.normalvariate(mean, 20), 2) for aggregator in self.aggregators: aggregator.quotes(self.name, quotes) def listener(self,aggregator): self.aggregators.append(aggregator) def symbols(self): return self.symbolmeans.keys() Then we need an :file:`aggregator.py` that combines all stock symbol quotes from all stockmarkets into one 'stream' (this is the object that will be the 'listener' for the stock markets). It should be possible to register one or more 'viewers' with the stock symbols that viewer is interested in, so that every viewer is only receiving the stock symbols it wants. The code is like this:: # aggregator.py class Aggregator(object): def __init__(self): self.viewers = {} self.symbols = [] def add_symbols(self, symbols): self.symbols.extend(symbols) def available_symbols(self): return self.symbols def view(self, viewer, symbols): self.viewers[viewer] = symbols def quotes(self, market, stockquotes): for symbol, value in stockquotes.items(): for viewer, symbols in self.viewers.items(): if symbol in symbols: viewer.quote(market, symbol, value) The :file:`viewer.py` itself is an extremely simple object that just prints out the stock symbol quotes it receives:: # viewer.py from __future__ import print_function class Viewer(object): def quote(self, market, symbol, value): print("{0}.{1}: {2}".format(market, symbol, value)) Finally you need to write a :file:`main.py` that imports the above modules, creates all objects, creates a few companies that are traded on the market, connects them together, and contains a loop that drives the stock market quote generation. Because we are not yet using Pyro here, it just creates two ``Stockmarket`` objects (with a name and the companies being traded). A single ``Aggregator`` object is registered with both markets, to receive all updates. A ``Viewer`` object is created and connected to the ``Aggregator`` with a few companies that the viewer wants to receive quotes from. The code is like this:: # main.py from __future__ import print_function import time from stockmarket import StockMarket from aggregator import Aggregator from viewer import Viewer def main(): nasdaq = StockMarket("NASDAQ", ["AAPL", "CSCO", "MSFT", "GOOG"]) newyork = StockMarket("NYSE", ["IBM", "HPQ", "BP"]) agg = Aggregator() agg.add_symbols(nasdaq.symbols()) agg.add_symbols(newyork.symbols()) print("aggregated symbols:", agg.available_symbols()) nasdaq.listener(agg) newyork.listener(agg) view = Viewer() agg.view(view, ["IBM", "AAPL", "MSFT"]) print("") while True: nasdaq.generate() newyork.generate() time.sleep(0.5) if __name__ == "__main__": main() If you now run :file:`main.py` it will print a stream of stock symbol quote updates that are being generated by the two stock markets (but only the few symbols that the viewer wants to see):: $ python main.py aggregated symbols: ['GOOG', 'AAPL', 'CSCO', 'MSFT', 'HPQ', 'BP', 'IBM'] NYSE.IBM: 74.31 NYSE.IBM: 108.68 NASDAQ.AAPL: 64.17 NYSE.IBM: 83.19 NYSE.IBM: 92.5 NASDAQ.AAPL: 63.09 NASDAQ.MSFT: 161.3 .... phase 2: separation ^^^^^^^^^^^^^^^^^^^ .. note:: For brevity, the code for this phase of the stockquote tutorial is not shown. If you want to see it, have a look at the :file:`stockquotes` example, :file:`phase2`. This phase still contains no Pyro code, but you can already make the three components more autonomous than they were in phase 1. This step is optional, you can skip it and continue with `phase 3: Pyro version`_ below if you want. In this phase, every component of our stock market system now has a main function that starts up the component and connects it to the other component(s). As the stock market is the source of the data, you create a daemon thread in :file:`stockmarket.py` for it so that all markets produces stock quote changes in the background. :file:`main.py` is a lot simpler now: it only starts the various components and then sits to wait for an exit signal. The idea in this phase is that you tweak the existing code a little to make it suitable to be split up in different, autonomous components: - it helps to add a few debug print or log statements so that you can see what is going on in each component - each component will need some form of a 'main' or 'startup' function to create and launch it - the main program just needs to make sure the components are started. phase 3: Pyro version ^^^^^^^^^^^^^^^^^^^^^ Finally you use Pyro to make the various components fully distributed. Pyro is used to make them talk to each other. The actual code for each component class hasn't changed since phase 1, it is just the plumbing that you need to write to glue them together. Pyro is making this a matter of just a few lines of code that is Pyro-specific, the rest of the code is needed anyway to start up and configure the system. To be able to see the final result, the code is listed once more with comments on what changed with respect to the version in phase 1 (phase 2 is optional, it just makes for an easier transition). .. note:: This time we won't be using ``serveSimple`` to publish the objects and start the Daemon. Instead, a daemon is created manually, we register our own objects, and start the request loop ourselves. This needs more code but gives you more control. .. note:: You will see the following lines appear in the code: ``Pyro4.config.SERIALIZER = 'pickle'`` and ``Pyro4.config.SERIALIZERS_ACCEPTED.add('pickle')`` For now, ignore the exact meaning of these particular settings. It is needed to get the stock market tutorial running in the form presented here. Basically it enables Pyro to transfer actual Python objects to remote calls, instead of only simple types such as lists and strings. In other chapters the meaning of this setting will be explained in more detail. main ---- There's no :file:`main.py` anymore. This is because you now start every component by itself, in separate console windows for instance. They run autonomously without the need of a 'main' program to start it all up in one place. stockmarket ----------- The :file:`stockmarket.py` gained a few print statements to see what is going on while it is running. *Important:* there is *a single* change in the code to make it work with Pyro. Because Pyro needs to transfer objects over the network, it requires those objects to be serializable. The ``symbols`` method returned the ``keys()`` of the dictionary of symbols in the stockmarket. While this is a normal list in Python 2, it is a ``dict_keys`` object in Python 3. These cannot be serialized (because it is a special iterator object). The simple solution is to force the method to build a list and return that: ``list(dictionary.keys())``. It also gained a ``run`` method that will be running inside the background thread to generate stock quote updates. The reason this needs to run in a thread is because the ``Stockmarket`` itself is also a Pyro object that must listen to remote method calls (in this case, of the ``Aggregator`` object(s) that want to listen to it). You can also choose to run the Pyro daemon loop in a background thread and generate stock quotes update in the main thread, that doesn't matter, as long as they run independently. Finally it gained a ``main`` function to create a couple of stock markets as we did before. This time however they're registered as Pyro objects with the Pyro daemon. They're also entered in the name server as ``example.stockmarket.`` so the ``Aggregator`` can find them easily. The complete code for :file:`stockmarket.py` is now as follows:: # stockmarket.py from __future__ import print_function import random import threading import time import Pyro4 Pyro4.config.SERIALIZER = 'pickle' Pyro4.config.SERIALIZERS_ACCEPTED.add('pickle') class StockMarket(object): def __init__(self, marketname, symbols): self.name = marketname self.symbolmeans = {} for symbol in symbols: self.symbolmeans[symbol] = random.uniform(20, 200) self.aggregators = [] def generate(self): quotes = {} for symbol, mean in self.symbolmeans.items(): if random.random() < 0.2: quotes[symbol] = round(random.normalvariate(mean, 20), 2) print("new quotes generated for", self.name) for aggregator in self.aggregators: aggregator.quotes(self.name, quotes) def listener(self,aggregator): print("market {0} adding new aggregator".format(self.name)) self.aggregators.append(aggregator) def symbols(self): return list(self.symbolmeans.keys()) def run(self): def generate_symbols(): while True: time.sleep(random.random()) self.generate() thread = threading.Thread(target=generate_symbols) thread.setDaemon(True) thread.start() def main(): nasdaq = StockMarket("NASDAQ", ["AAPL", "CSCO", "MSFT", "GOOG"]) newyork = StockMarket("NYSE", ["IBM", "HPQ", "BP"]) daemon = Pyro4.Daemon() nasdaq_uri = daemon.register(nasdaq) newyork_uri = daemon.register(newyork) ns = Pyro4.locateNS() ns.register("example.stockmarket.nasdaq", nasdaq_uri) ns.register("example.stockmarket.newyork", newyork_uri) nasdaq.run() newyork.run() print("Stockmarkets running.") daemon.requestLoop() if __name__ == "__main__": main() aggregator ---------- The :file:`aggregator.py` also gained a print function to be able to see in its console window what is going on when a new viewer connects. The ``main`` function creates it, and connects it as a Pyro object to the Pyro daemon. It also registers it with the name server as ``example.stockquote.aggregator`` so it can be easily retrieved by any viewer that is interested. *How it connects to the available stock markets:* Remember that the stock market objects registered with the name server using a name of the form ``example.stockmarket.``. It is possible to query the Pyro name server in such a way that it returns a list of all objects matching a name pattern. This is exactly what the aggregator does, it asks for all names starting with ``example.stockmarket.`` and for each of those, creates a Pyro proxy to that stock market. It then registers itself as a listener with that remote stock market object. Finally it starts the daemon loop to wait for incoming calls from any interested viewers. The complete code for :file:`aggregator.py` is now as follows:: # aggregator.py from __future__ import print_function import Pyro4 Pyro4.config.SERIALIZER = 'pickle' Pyro4.config.SERIALIZERS_ACCEPTED.add('pickle') class Aggregator(object): def __init__(self): self.viewers = {} self.symbols = [] def add_symbols(self, symbols): self.symbols.extend(symbols) def available_symbols(self): return self.symbols def view(self, viewer, symbols): print("aggregator gets a new viewer, for symbols:", symbols) self.viewers[viewer] = symbols def quotes(self, market, stockquotes): for symbol, value in stockquotes.items(): for viewer, symbols in self.viewers.items(): if symbol in symbols: viewer.quote(market, symbol, value) def main(): aggregator = Aggregator() daemon = Pyro4.Daemon() agg_uri = daemon.register(aggregator) ns = Pyro4.locateNS() ns.register("example.stockquote.aggregator", agg_uri) for market, market_uri in ns.list(prefix="example.stockmarket.").items(): print("joining market", market) stockmarket = Pyro4.Proxy(market_uri) stockmarket.listener(aggregator) aggregator.add_symbols(stockmarket.symbols()) if not aggregator.available_symbols(): raise ValueError("no symbols found! (have you started the stock market first?)") print("Aggregator running. Symbols:", aggregator.available_symbols()) daemon.requestLoop() if __name__ == "__main__": main() viewer ------ You don't need to change the ``Viewer`` at all, besides the ``main`` function that needs to be added to start it up by itself. It needs to create a viewer object and register it with a Pyro daemon to be able to receive stock quote update calls. You can connect it to a running aggregator simply by asking Pyro to look that up in the name server. That can be done by using the special ``PYRONAME:`` uri format. For the aggregator that would be: ``PYRONAME:example.stockquote.aggregator`` (because ``example.stockquote.aggregator`` is the name the aggregator used to register itself with the name server). It is also nice to ask the user for a list of stock symbols he is interested in so do that and register the viewer with the aggregator, passing the list of entered stock symbols to filter on. Finally start the daemon loop to wait for incoming calls. The code is as follows:: # viewer.py from __future__ import print_function import sys import Pyro4 Pyro4.config.SERIALIZER = 'pickle' Pyro4.config.SERIALIZERS_ACCEPTED.add('pickle') if sys.version_info < (3,0): input = raw_input class Viewer(object): def quote(self, market, symbol, value): print("{0}.{1}: {2}".format(market, symbol, value)) def main(): viewer = Viewer() daemon = Pyro4.Daemon() daemon.register(viewer) aggregator = Pyro4.Proxy("PYRONAME:example.stockquote.aggregator") print("Available stock symbols:", aggregator.available_symbols()) symbols = input("Enter symbols you want to view (comma separated):") symbols = [symbol.strip() for symbol in symbols.split(",")] aggregator.view(viewer, symbols) print("Viewer listening on symbols", symbols) daemon.requestLoop() if __name__ == "__main__": main() running the final program ------------------------- To run the final stock quote system you need to do the following: - Open a new console window to start the name server in. - Set the following environment variable:: set PYRO_SERIALIZERS_ACCEPTED=pickle (windows) export PYRO_SERIALIZERS_ACCEPTED=pickle (unix/mac/linux) Once more, ignore the exact meaning of this particular setting. It is needed to get the stock market tutorial running in the form presented here. In other chapters the meaning of this setting will be explained. - start the Pyro name server (:command:`python -m Pyro4.naming`) in this window. After that, start the following, each in a separate console window (so they run in parallel): - start the stock market - start the aggregator - start one or more of the viewers. The output of the stock market looks like this:: $ python stockmarket.py Stockmarkets running. new quotes generated for NASDAQ new quotes generated for NASDAQ new quotes generated for NYSE new quotes generated for NASDAQ ... The output of the aggregator looks like this:: $ python aggregator.py joining market example.stockmarket.newyork joining market example.stockmarket.nasdaq Aggregator running. Symbols: ['HPQ', 'BP', 'IBM', 'GOOG', 'AAPL', 'CSCO', 'MSFT'] aggregator gets a new viewer, for symbols: ['GOOG', 'CSCO', 'BP'] The output of the viewer looks like this:: $ python viewer.py Available stock symbols: ['HPQ', 'BP', 'IBM', 'GOOG', 'AAPL', 'CSCO', 'MSFT'] Enter symbols you want to view (comma separated):GOOG,CSCO,BP # <---- typed in Viewer listening on symbols ['GOOG', 'CSCO', 'BP'] NYSE.BP: 88.96 NASDAQ.GOOG: -9.61 NYSE.BP: 113.8 NASDAQ.CSCO: 125.11 NYSE.BP: 77.43 NASDAQ.GOOG: 17.64 NASDAQ.CSCO: 157.21 NASDAQ.GOOG: 7.59 ... If you're interested to see what the name server now contains, type :command:`python -m Pyro4.nsc list`:: $ python -m Pyro4.nsc list --------START LIST Pyro.NameServer --> PYRO:Pyro.NameServer@localhost:9090 example.stockmarket.nasdaq --> PYRO:obj_fc742f1656bd4c7e80bee17c33787147@localhost:50510 example.stockmarket.newyork --> PYRO:obj_6bd09853979f4d13a73263e51a9c266b@localhost:50510 example.stockquote.aggregator --> PYRO:obj_2c7a4f5341b1464c8cc6091f3997230f@localhost:50512 --------END LIST .. _not-localhost: Running it on different machines ================================ For security reasons, Pyro runs stuff on localhost by default. If you want to access things from different machines, you'll have to tell Pyro to do that explicitly. This paragraph shows you how very briefly you can do this. For more details, refer to the chapters in this manual about the relevant Pyro components. *Name server* to start the nameserver in such a way that it is accessible from other machines, start it with an appropriate -n argument, like this: :command:`python -m Pyro4.naming -n your_hostname_here` *Warehouse server* You'll have to modify :file:`warehouse.py`. Right before the ``serveSimple`` call you have to tell it to bind the daemon on your hostname instead of localhost. One way to do this is by setting the ``HOST`` config item:: Pyro4.config.HOST = "your_hostname_here" Pyro4.Daemon.serveSimple(...) You can also choose to leave the code alone, and instead set the ``PYRO_HOST`` environment variable before starting the warehouse server. Another option is pass the required host (and perhaps even port) arguments to ``serveSimple``:: Pyro4.Daemon.serveSimple( { warehouse: "example.warehouse" }, host = 'your_hostname_here', ns = True) *Stock market servers* This example already creates a daemon object instead of using the ``serveSimple`` call. You'll have to modify the three source files because they all create a daemon. But you'll only have to add the proper ``host`` argument to the construction of the Daemon, to set it to your machine name instead of the default of localhost. Ofcourse you could also change the ``HOST`` config item (either in the code itself, or by setting the ``PYRO_HOST`` environment variable before launching. Other means of creating connections =================================== In both tutorials above we used the Name Server for easy object lookup. The use of the name server is optional, see :ref:`name-server` for details. There are various other options for connecting your client code to your Pyro objects, have a look at the client code details: :ref:`object-discovery` and the server code details: :ref:`publish-objects`. Ok, what's next? ================ *Congratulations!* You completed the Pyro tutorials in which you built a simple warehouse storage system, and a stock market simulation system consisting of various independent components that talk to each other using Pyro. The Pyro distribution archive contains a truckload of example programs with short descriptions that you could study to see how to use the various features that Pyro has to offer. Or just browse the manual for more detailed information. Please consider joining the Pyro mailing list (see :doc:`front page `). Happy remote object programming! Pyro4-4.23/docs/source/upgrading.rst000066400000000000000000000242001227003673200174210ustar00rootroot00000000000000.. include:: ********************* Upgrading from Pyro 3 ********************* .. _should-i-choose-pyro4: Should I choose Pyro4? ====================== Should you use Pyro 4 or Pyro 3 for your project? This depends on a few things. **Dependencies on older systems** Pyro 4 has more modern system requirements than Pyro 3. It is unsupported on Python versions below 2.6. Pyro 3 runs fine on Python 2.5, and Pyro versions before 3.13 should run on even older Python versions. So if you cannot use Python 2.6 or newer, you should use Pyro 3. Pyro 4 has been written from scratch. While it looks and feels much like Pyro 3 did, its API and implementation are incompatible. If you need to connect to existing systems that use Pyro 3, you can only do that with Pyro 3. Pyro 4 can't talk to them. **Features** Pyro 4 has several features that are not present in Pyro 3, but the reverse is also true. If you require one of the following features for your system, you can only find them -for now- in Pyro 3: - SSL support - connection authentication - mobile code - remote attribute access - Event server Some of them may appear in the future in Pyro 4, but for now they're all exclusive to Pyro 3. **Availability in package forms** Some people can't or won't install software from source and rather use the package manager of their OS. Pyro 4 might not yet be available in your package manager because it is rather new. Pyro 3 is available as a Debian package. So if you are on Debian (or a derivative like Ubuntu or Mint) and you only accept software from the distribution packages, Pyro 3 is your only choice for now. .. note:: The Pyro project itself does not provide any OS-specific packaging. Things like the Debian Pyro package are created and maintained by different people (thanks for that!) **Maturity** Pyro 3 has been around for many years and has a proven track record. It also has a very stable API. It only gets bug fixes and much effort is taken to keep it backward compatible with previous 3.x versions. Pyro 4 on the other hand is still under active development. The important API parts are more or less stabilized but as features are being added or changed, stuff might break. So depending on your requirements of maturity and API stability you might choose one or the other. Differences =========== Here you can find what is different in Pyro4 compared to Pyro3. This should help with converting existing Pyro3 applications to Pyro4. It also serves as a quick overview for developers that are used to Pyro3, to see what the new API and features of Pyro4 are like. General differences from Pyro 3.x ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Pyro4 requires Python 2.6 or newer. Pyro 3.x supports Python 2.5 (Pyro 3.12 and older, even support Python 2.4) - Toplevel package name has been changed from ``Pyro`` into ``Pyro4`` - Mobile code support has been removed. - Remote attribute access (``DynamicProxyWithAttrs``) has been removed (slight chance it appears again in the future in a different form) - Event server has been removed (slight chance it appears again in the future in a different form). - SSL support has been removed. Likely to appear again in a future version. - You don't need to import the various sub packages. Just ``import Pyro4`` and you're done: the main Pyro API elements are available directly in the ``Pyro4`` package. - ``Pyro.core.PyroURI`` has been renamed to ``Pyro4.URI`` - You can choose from two server types to use in your daemon, a threadpool server (that can process requests concurrently, most similar to Pyro3) and a select/poll based server (that processes requests in a single thread by multiplexing all client connections). - Pyro objects in the daemon don't have a forced objectId UUID anymore. They just use the name you give them, or an auto generated one if you don't provide a name yourself. - ``PYROLOC`` protocol has been removed. Just use the ``PYRO`` protocol with the logical name instead of the object id in the URI. - Pyro daemon binds on a random free port by default, instead of on a fixed port. Name server still is on a fixed port by default. - Pyro daemons (including the Name server) bind on localhost by default. This prevents exposure to the outside world in a default install. You have to explicitly tell Pyro4 to bind on another network interface. With Pyro3, it was the other way around, it would bind on all network interfaces by default (potentially exposing unwanted servers). - Logging is done using Python's standard logging module instead of custom logger stuff from ``Pyro.util``. There is some autoconfig voodoo in the package init code that reacts to some environment variable settings. - All classes are new style classes, so you can use super() etc. (this was already the case in the later Pyro 3.x releases but not in earlier ones) - There are no batch files for the utilities (yet), but you can use aliases like this: | ``alias pyro-ns=python -m Pyro4.naming`` | ``alias pyro-nsc=python -m Pyro4.nsc`` - The command line syntax of these tools has changed, just check out the new usage with '-h'. - The name server doesn't have any groups anymore. The namespace is now 'flat'. You don't have to use the ':' in front of names, or separate parts using '.' anymore. You can look up names matching a prefix string or a regular expression now, so with clever names you can still achieve mostly the same as with the old group based namespace. - The name server doesn't have a default group any longer. Name server proxies are regular Pyro proxies and don't perform any voodoo magic on the names anymore. - Config items are in ``Pyro4.config`` (as they were in ``Pyro.config`` in Pyro3) but have changed quite a bit. Most of them have been removed. Some may return in a later version. - Exception traceback processing: ``Pyro4.util.getPyroTraceback`` shouldn't be called with an exception object anymore. Either simply call it without parameters, or supply the usual ex_type, ex_value, ex_tb objects that you can get from sys.exc_info. Or even easier, install Pyro4's custom excepthook. Client code differences ^^^^^^^^^^^^^^^^^^^^^^^ - ``Pyro.core.initClient`` was deprecated for a long time already, and has been removed. - locating the name server: instead of ``locator=Pyro.naming.NameServerLocator(); ns=locator.getNS()`` simply use ``ns=Pyro4.locateNS()`` - Pyro's internal methods and attributes have been renamed and are all prefixed with ``_pyro``. - getting the uri from a proxy: ``proxy.URI`` is now ``proxy._pyroUri`` - the ``Pyro.core.PyroURI`` object has changed quite a bit. It has been renamed to ``Pyro4.URI`` and many of its attributes are different. It knows two types of protocols: ``PYRO`` and ``PYRONAME`` (``PYROLOC`` has gone). The syntax is different too: ``:@`` - looking up stuff in the name server: ``ns.resolve`` is now ``ns.lookup`` - there is only one proxy class: ``Pyro4.Proxy`` - creating proxies: use the Proxy class constructor, pass in an URI or an uri string directly. - rebinding a disconnected proxy: instead of ``obj.adapter.rebindURI`` now use: ``obj._pyroReconnect`` - proxies for 'meta' uris (``PYRONAME`` etc) are not updated anymore with the resolved uri. If you want the old behavior, you have to call ``proxy._pyroBind()`` explicitly to bind the proxy on the resolved PYRO uri. - You can manually resolve a 'meta' uri by using ``Pyro4.resolve``, it will return a new normal PYRO uri. - Oneway methods: ``proxy._setOneway`` got replaced by the ``proxy._pyroOneway`` attribute. That is a set (or sequence) so simply add method names to it that need to be oneway. Server code differences ^^^^^^^^^^^^^^^^^^^^^^^ - ``Pyro.core.initServer`` was deprecated for a long time already, and has been removed. - ``Pyro.core.ObjBase`` is gone, just use any class directly as a Pyro object. Pyro4 injects a few magic attributes in your object. Their names start with ``_pyro`` for easy identification. - ``Pyro.core.SynchronizedObjBase`` is gone as well, you need to create your own thread safety measures if required (locks, Queues, etc) - see above for changes concerning how to locate the name server. - Daemons are created much in the same way as before. But they don't automagically register anything in the name server anymore so you have to do that yourself (``daemon.useNameServer`` is gone) - Daemons now bind on a random free port by default instead of a fixed port. You need to specify a port yourself when creating a daemon if you want a fixed port. - Daemons bind on localhost by default, instead of 0.0.0.0. If you want to expose to the network, you'll have to provide a proper hostname or ip address yourself. There is a utility method in the ``Pyro4.socketutil`` module that can help you with that: ``getIpAddress``. - ``daemon.connect`` has been renamed to ``daemon.register``. It still returns the URI for the object, as usual. - ``daemon.disconnect`` has been renamed to ``daemon.unregister`` - ``daemon.connectPersistent`` is gone, just pass the existing object id as a parameter to the normal register. - getting an object's uri and registering in the name server: use the URI you got from the register call, or use ``daemon.uriFor`` to get a new URI for a given object. Use ``ns.register(objName, uri)`` as usual to register it in the name server. - creating new pyro objects on the server and returning them (factory methods): ``server.getDaemon()`` is replaced by the ``_pyroDaemon`` attribute, and ``obj.getProxy()`` is gone. Typical pattern is now:: uri=self._pyroDaemon.register(object) return Pyro4.Proxy(uri) **However** Pyro4 has an 'AUTOPROXY' feature (on by default) that makes the above unneeded (Pyro4 will wrap pyro objects with a proxy automatically for you). You can turn this feature off to make it look more like Pyro3's behavior. - Unregistering objects from the daemon is done with ``some_pyro_object._pyroDaemon.unregister(some_pyro_object)`` (or use the object's URI). New features ^^^^^^^^^^^^ Pyro4 introduces several new features that weren't available in Pyro3, so there is no migration path needed for those. (Things like batched calls, async calls, new config items etc). Pyro4-4.23/examples/000077500000000000000000000000001227003673200142775ustar00rootroot00000000000000Pyro4-4.23/examples/async/000077500000000000000000000000001227003673200154145ustar00rootroot00000000000000Pyro4-4.23/examples/async/Readme.txt000066400000000000000000000016171227003673200173570ustar00rootroot00000000000000This is an example that shows the asynchronous method call support. The server has a single method that has an artificial delay of three seconds before it returns the result of the computation. The client shows how you might use Pyro's async feature to run the remote method call in the background and deal with the results later (when they are available). client_batch.py shows how to do async batched calls. Notice how this is different from oneway batched calls because we will get results this time (just somewhere in the future). Oneway calls never return a result. client_callchain.py shows the 'call chain' feature, where you can chain one or more asynchronous function calls to be performed as soon as the async call result became available. You can chain normal functions but also more pyro calls ofcourse. The result of the previous call is passed to the next call as argument. Pyro4-4.23/examples/async/client.py000066400000000000000000000045501227003673200172500ustar00rootroot00000000000000from __future__ import print_function import sys import time import Pyro4 if sys.version_info<(3,0): input=raw_input uri=input("enter async server object uri: ").strip() proxy=Pyro4.Proxy(uri) print("* normal call: (notice the delay)") print("result=", proxy.divide(100,5)) print("\n* async call:") async=Pyro4.async(proxy) asyncresult=async.divide(100,5) # returns immediately print("result value available?",asyncresult.ready) # prints False because the server is still 'busy' print("client can do other stuff here.") print("getting result value...(will block until available)") print("resultvalue=",asyncresult.value) # blocks until the result is available print("\n* async call, with normal call inbetween:") async=Pyro4.async(proxy) asyncresult=async.divide(100,5) # returns immediately print("client does normal call: ",proxy.multiply(5,20)) print("client does normal call: ",proxy.multiply(5,30)) print("getting result value of async call...(will block until available)") print("resultvalue=",asyncresult.value) # blocks until the result is available print("\n* async call with exception:") async=Pyro4.async(proxy) asyncresult=async.divide(100,0) # will trigger a zero division error, 100//0 print("getting result value...") try: value=asyncresult.value print("Weird, this shouldn't succeed!?... resultvalue=",value) except ZeroDivisionError: print("got exception (expected):",repr(sys.exc_info()[1])) print("\n* async call with timeout:") async=Pyro4.async(proxy) asyncresult=async.divide(100,5) print("checking if ready within 2 seconds...") ready=asyncresult.wait(2) # wait for ready within 2 seconds but the server takes 3 print("status after waiting=",ready) # should print False print("checking again if ready within 5 seconds...(should be ok now)") ready=asyncresult.wait(timeout=5) # wait 5 seconds now (but server will be done within 1 more second) print("status after waiting=",ready) print("available=",asyncresult.ready) print("resultvalue=",asyncresult.value) print("\n* a few async calls at the same time:") async=Pyro4.async(proxy) results=[ async.divide(100,7), async.divide(100,6), async.divide(100,5), async.divide(100,4), async.divide(100,3), ] print("getting values...") for result in results: print("result=",result.value) print("\ndone.") Pyro4-4.23/examples/async/client_batch.py000066400000000000000000000051351227003673200204110ustar00rootroot00000000000000from __future__ import print_function import sys import time import Pyro4 if sys.version_info<(3,0): input=raw_input def asyncFunction(values): results=[value+1 for value in values] print(">>> async batch function called, returning:",results) return results uri=input("enter async server object uri: ").strip() proxy=Pyro4.Proxy(uri) print("\n* batch async call:") batch=Pyro4.batch(proxy) batch.divide(100,5) batch.divide(99,9) batch.divide(555,2) print("getting results...") asyncresults = batch(async=True) # returns immediately print("result value available?",asyncresults.ready) # prints False because the server is still 'busy' print("client can do other stuff here.") time.sleep(2) print("such as sleeping ;-)") time.sleep(2) print("sleeping some more, batch takes a while") time.sleep(2) print("getting result values...(will block until available)") results=asyncresults.value # blocks until the result is available print("resultvalues=",list(results)) print("\n* batch async call with chained function:") batch=Pyro4.batch(proxy) batch.divide(100,5) batch.divide(99,9) batch.divide(555,2) asyncresults = batch(async=True) # returns immediately asyncresults.then(asyncFunction) \ .then(asyncFunction) \ .then(asyncFunction) print("getting result values...(will block until available)") print("final value=",asyncresults.value) print("\n* batch async call with exception:") batch=Pyro4.batch(proxy) batch.divide(1,1) # first call is ok batch.divide(100,0) # second call will trigger a zero division error, 100//0 asyncresults = batch(async=True) # returns immediately print("getting result values...") try: value=asyncresults.value print("Weird, this shouldn't succeed!?... resultvalues=",list(value)) except ZeroDivisionError: print("got exception (expected):",repr(sys.exc_info()[1])) print("\n* batch async call with timeout:") batch=Pyro4.batch(proxy) batch.divide(100,5) batch.divide(99,9) batch.divide(555,2) asyncresults = batch(async=True) # returns immediately print("checking if ready within 2 seconds...") ready=asyncresults.wait(2) # wait for ready within 2 seconds but the server takes 3 print("status after wait=",ready) # should print False print("checking again if ready within 10 seconds...(should be ok now)") ready=asyncresults.wait(timeout=10) # wait 10 seconds now (but server will be done within ~8 more seconds) print("status after wait=",ready) print("available=",asyncresults.ready) results=asyncresults.value print("resultvalues=",list(results)) print("\ndone.") Pyro4-4.23/examples/async/client_callchain.py000066400000000000000000000057431227003673200212530ustar00rootroot00000000000000from __future__ import print_function import sys import time import Pyro4 if sys.version_info<(3,0): input=raw_input def asyncFunction(value, increase=1): print(">>> async function called with value={0} increase={1}".format(value,increase)) return value+increase def asyncWithMoreArgs(a, b, extra=None): print(">>> async func called with some arguments: a={0}, b={1}, extra={2}".format(a, b, extra)) return a+b uri=input("enter async server object uri: ").strip() proxy=Pyro4.Proxy(uri) print("\n* async call with call chain:") async=Pyro4.async(proxy) asyncresult=async.divide(100,5) # set a chain of callables to be invoked once the value is available asyncresult.then(asyncFunction) \ .then(asyncFunction) \ .then(asyncFunction) print("sleeping 4 seconds") time.sleep(4) # the call chain will be invoked during this sleep period print("back from sleep") # you can still access the final asyncresult.value. It should be 100/5+3=23 print("final value=",asyncresult.value) assert asyncresult.value==23 print("\n* async call with call chain that is set 'too late':") async=Pyro4.async(proxy) asyncresult=async.divide(100,5) # set a chain of callables to be invoked once the value is available # but we set it 'too late' (when the result is already available) time.sleep(4) # result will appear in 3 seconds asyncresult.then(asyncFunction) \ .then(asyncFunction) \ .then(asyncFunction) # let's see what the result value is, should be 100/5+3=23 print("final value=",asyncresult.value) assert asyncresult.value==23 print("\n* async call with call chain, where calls have extra arguments:") async=Pyro4.async(proxy) asyncresult=async.multiply(5,6) # set a chain of callables to be invoked once the value is available # the callable will be invoked like so: function(asyncvalue, normalarg, kwarg=..., kwarg=...) # (the value from the previous call is passed as the first argument to the next call) asyncresult.then(asyncWithMoreArgs, 1) \ .then(asyncWithMoreArgs, 2, extra=42) \ .then(asyncWithMoreArgs, 3, extra="last one") print("sleeping 1 second") time.sleep(1) # the call chain will be invoked during this sleep period print("back from sleep") # you can still access the final asyncresult.value. It should be 5*6+1+2+3=36 print("final value=",asyncresult.value) assert asyncresult.value==36 print("\n* async call with call chain, where calls are new pyro calls:") async=Pyro4.async(proxy) asyncresult=async.divide(100,5) # set a chain of callables to be invoked once the value is available # the callable will be invoked like so: function(asyncvalue, kwarg=..., kwarg=...) asyncresult.then(proxy.add, increase=1) \ .then(proxy.add, increase=2) \ .then(proxy.add, increase=3) print("getting result value (will block until available)") print("final value=",asyncresult.value) assert asyncresult.value==26 # 100/5+1+2+3=26 print("\ndone.") Pyro4-4.23/examples/async/server.py000066400000000000000000000011641227003673200172760ustar00rootroot00000000000000from __future__ import print_function import time import Pyro4 class Thingy(object): def divide(self, a, b): print("dividing {0} by {1} after a slight delay".format(a,b)) time.sleep(3) return a//b def multiply(self, a, b): print("multiply {0} by {1}, no delay".format(a,b)) return a*b def add(self, value, increase): print("adding {1} to {0}, no delay".format(value,increase)) return value+increase d=Pyro4.Daemon() uri=d.register(Thingy(), "example.async") print("server object uri:",uri) print("async server running.") d.requestLoop() Pyro4-4.23/examples/attributes/000077500000000000000000000000001227003673200164655ustar00rootroot00000000000000Pyro4-4.23/examples/attributes/Readme.txt000066400000000000000000000007001227003673200204200ustar00rootroot00000000000000This is an example that shows the DOTTEDNAMES support and implications. You can start the server with or without DOTTEDNAMES enabled. Try both. See what the client does with both settings. The client also tries to perform a security exploit in the server, which will fail if DOTTEDNAMES is not enabled (the default). Lastly, direct attribute access. This feature is not yet available in Pyro so it cannot be demonstrated at this time. Pyro4-4.23/examples/attributes/client.py000066400000000000000000000045721227003673200203250ustar00rootroot00000000000000from __future__ import print_function import sys import Pyro4 if sys.version_info<(3,0): input=raw_input uri=input("enter attribute server object uri: ").strip() p=Pyro4.Proxy(uri) # First do some normal method calls with and without a dotted path notation # to get at the sub-object. If the server is running with DOTTEDNAMES=False, # you will get attribute exceptions when trying to access methods with a # dotted attribute path. If DOTTEDNAMES=True on the server, it will work. # (however it is a security risk because it is possible to exploit object # traversal and obtain internal info of your server or execute arbitrary code). print("DOTTEDNAMES on the server is:",p.dottedNames()) value=p.getSubValue() print("value gotten from p.getSubValue()=",value) try: value=p.sub.getValue() print("value gotten from p.sub.getValue()=",value) except AttributeError: print("AttributeError occurred:",sys.exc_info()[1]) print("setting value to 999") try: p.sub.setValue(999) except AttributeError: print("AttributeError occurred:",sys.exc_info()[1]) value=p.getSubValue() print("value gotten from p.getSubValue()=",value) try: value=p.sub.getValue() print("value gotten from p.sub.getValue()=",value) except AttributeError: print("AttributeError occurred:",sys.exc_info()[1]) # try an object traversal exploit print("attempt to do an object traversal exploit...") oldvalue=p.printSomething() try: # this update() call will work when the server has DOTTEDNAMES set to true...: p.printSomething.im_func.func_globals.update({"something":"J00 HAVE BEEN HAXX0RD"}) except AttributeError: # this exception occurs when the server has DOTTEDNAMES set to false. print("Attributeerror, couldn't update the server's global variable") newvalue=p.printSomething() if newvalue!=oldvalue: print("The server has been exploited, a global variable has been updated with a different value.") print("Old value: {0} new value: {1}".format(oldvalue, newvalue)) # Direct attribute access @todo: not supported yet, will only print a bunch of lines # print("\nDirect attribute access. (not supported yet!)") # print("p.value:",p.value) # print("p._value:",p._value) # print("p.__value:",p.__value) # print("p.sub.value:",p.sub.value) # print("p.sub._value:",p.sub._value) # print("p.sub.__value:",p.sub.__value) Pyro4-4.23/examples/attributes/server.py000066400000000000000000000022131227003673200203430ustar00rootroot00000000000000from __future__ import print_function import sys import Pyro4 if sys.version_info<(3,0): input=raw_input dotted=input("enter value for DOTTEDNAMES config item: ").strip() Pyro4.config.DOTTEDNAMES = dotted in ("1","true","on","yes") something="Something" class SubThingy(object): def __init__(self): self.value=42 self._value=123 self.__value=999 def getValue(self): return self.value def setValue(self,value): self.value=value class Thingy(object): def __init__(self): self.sub=SubThingy() self.value=42 self._value=123 self.__value=999 def getSubValue(self): return self.sub.getValue() def setSubValue(self, value): self.sub.setValue(value) def dottedNames(self): return Pyro4.config.DOTTEDNAMES def printSomething(self): print("something:",something) return something d=Pyro4.Daemon() uri=d.register(Thingy(), "example.attributes") print("server object uri:",uri) print("DOTTEDNAMES=",Pyro4.config.DOTTEDNAMES) print("attributes server running.") d.requestLoop() Pyro4-4.23/examples/autoproxy/000077500000000000000000000000001227003673200163515ustar00rootroot00000000000000Pyro4-4.23/examples/autoproxy/Readme.txt000066400000000000000000000013211227003673200203040ustar00rootroot00000000000000This is an example that shows the autoproxy feature. Pyro will automatically return a Proxy instead of the object itself, if you are passing a Pyro object over a remote call. This means you can easily create new objects in a server and return them from remote calls, without the need to manually wrap them in a proxy. This behavior is enabled by default. It is different from older Pyro releases, so there is a config item AUTOPROXY that you can set to False if you want the old behaviour. You can try it with this example too, set the environment variable PYRO_AUTOPROXY to false and restart the server to see what the effect is. Note that when using the marshal serializer, this feature will not work. Pyro4-4.23/examples/autoproxy/client.py000066400000000000000000000010231227003673200201750ustar00rootroot00000000000000from __future__ import print_function import sys import Pyro4 if sys.version_info<(3,0): input=raw_input uri=input("enter factory server object uri: ").strip() factory=Pyro4.Proxy(uri) # create several things. print("Creating things.") thing1 = factory.createSomething(1) thing2 = factory.createSomething(2) thing3 = factory.createSomething(3) # interact with them on the server. print("Speaking stuff.") thing1.speak("I am the first") thing2.speak("I am second") thing3.speak("I am last then...") Pyro4-4.23/examples/autoproxy/server.py000066400000000000000000000014171227003673200202340ustar00rootroot00000000000000from __future__ import print_function import Pyro4 from thingy import Thingy class Factory(object): def createSomething(self, number): # create a new item thing=Thingy(number) # connect it to the Pyro daemon to make it a Pyro object self._pyroDaemon.register(thing) # Return it. Pyro's autoproxy feature turns it into a proxy automatically. # If that feature is disabled, the object itself (a copy) is returned, # and the client won't be able to interact with the actual Pyro object here. return thing d=Pyro4.Daemon() uri=d.register(Factory(), "example.autoproxy") print("server object uri:",uri) print("autoproxy?",Pyro4.config.AUTOPROXY) print("factory server running.") d.requestLoop() Pyro4-4.23/examples/autoproxy/thingy.py000066400000000000000000000002711227003673200202250ustar00rootroot00000000000000 class Thingy(object): def __init__(self, number): self.number=number def speak(self, message): print("Thingy {0} says: {1}".format(self.number, message)) Pyro4-4.23/examples/autoreconnect/000077500000000000000000000000001227003673200171505ustar00rootroot00000000000000Pyro4-4.23/examples/autoreconnect/Readme.txt000066400000000000000000000035021227003673200211060ustar00rootroot00000000000000This is an example that shows the auto reconnect feature, from a client's perspective. Start the server and the client. You can stop the server while it's running. The client will report that the connection is lost, and that it is trying to rebind. Start the server again. You'll see that the client continues. There are 2 examples: - reconnect using NS (clientNS/serverNS) - reconnect using PYRO (client/server) NOTES: 1- Your server has to be prepared for this feature. It must not rely on any transient internal state to function correctly, because that state is lost when your server is restarted. You could make the state persistent on disk and read it back in at restart. 2- By default Pyro starts its daemons on a random port. If you want to support autoreconnection, you will need to restart your daemon on the port it used before. Easiest is to pick a fixed port. 3- If using the name server or relying on PYRO-uri's: then your server MUST register the objects with their old objectId to the daemon. Otherwise the client will try to access an unknown object Id. 4- If the NS loses its registrations, you're out of luck. Clients that rely on name server based reconnects will fail. 5- The client is reponsible for detecting a network problem itself. It must also explicitly call the reconnect method on the object. 6- Why isn't this automagic? Because you need to have control about it when a network problem occurs. Furthermore, only you can decide if your system needs this feature, and if it can support it (see points above). 7- Read the source files for info on what is going on. 8- Also see the 'disconnects' example for another swing at dealing with client timeouts/disconnects, and how a special proxy class can make it easier to deal with for the clients. Pyro4-4.23/examples/autoreconnect/client.py000066400000000000000000000013361227003673200210030ustar00rootroot00000000000000from __future__ import print_function import time import sys import Pyro4 if sys.version_info<(3,0): input=raw_input print("Autoreconnect using PYRO uri.") # We create a proxy with a PYRO uri. # Reconnect logic depends on the server now. # (it needs to restart the object with the same id) uri=input("Enter the uri that the server printed:").strip() obj=Pyro4.core.Proxy(uri) while True: print("call...") try: obj.method(42) print("Sleeping 1 second") time.sleep(1) except Pyro4.errors.ConnectionClosedError: # or possibly even ProtocolError print("Connection lost. REBINDING...") print("(restart the server now)") obj._pyroReconnect() Pyro4-4.23/examples/autoreconnect/clientNS.py000066400000000000000000000012021227003673200212340ustar00rootroot00000000000000from __future__ import print_function import time import Pyro4 print("Autoreconnect using Name Server.") # We create a proxy with a PYRONAME uri. # That allows Pyro to look up the object again in the NS when # it needs to reconnect later. obj=Pyro4.core.Proxy("PYRONAME:example.autoreconnect") while True: print("call...") try: obj.method(42) print("Sleeping 1 second") time.sleep(1) except Pyro4.errors.ConnectionClosedError: # or possibly even ProtocolError print("Connection lost. REBINDING...") print("(restart the server now)") obj._pyroReconnect() Pyro4-4.23/examples/autoreconnect/server.py000066400000000000000000000020621227003673200210300ustar00rootroot00000000000000from __future__ import print_function import time import Pyro4 print("Autoreconnect using PYRO uri.") class TestClass(object): def method(self,arg): print("Method called with %s" % arg) print("You can now try to stop this server with ctrl-C/ctrl-Break") time.sleep(1) obj=TestClass() # We are responsible to (re)connect objects with the same object Id, # so that the client can reuse its PYRO-uri directly to reconnect. # There are a few options, such as depending on the Name server to # maintain a name registration for our object (see the serverNS for this). # Or we could store our objects in our own persistent database. # But for this example we will just use a pre-generated id (fixed name). # The other thing is that your Daemon must re-bind on the same port. # By default Pyro will select a random port so we specify a fixed port. daemon = Pyro4.core.Daemon(port=7777) uri = daemon.register(obj,objectId="example.autoreconnect_fixed_objectid") print("Server started, uri=%s" % uri) daemon.requestLoop() Pyro4-4.23/examples/autoreconnect/serverNS.py000066400000000000000000000025421227003673200212740ustar00rootroot00000000000000from __future__ import print_function import time import Pyro4 print("Autoreconnect using Name Server.") class TestClass(object): def method(self,arg): print("Method called with %s" % arg) print("You can now try to stop this server with ctrl-C/ctrl-Break") time.sleep(1) obj=TestClass() # if we reconnect the object, it has to have the same objectId as before. # for this example, we rely on the Name Server registration to get our old id back. ns=Pyro4.naming.locateNS() try: existing=ns.lookup("example.autoreconnect") print("Object still exists in Name Server with id: %s" % existing.object) print("Previous daemon socket port: %d" % existing.port) # start the daemon on the previous port daemon = Pyro4.core.Daemon(port=existing.port) # register the object in the daemon with the old objectId daemon.register(obj, objectId=existing.object) except Pyro4.errors.NamingError: # just start a new daemon on a random port daemon = Pyro4.core.Daemon() # register the object in the daemon and let it get a new objectId # also need to register in name server because it's not there yet. uri = daemon.register(obj) ns.register("example.autoreconnect", uri) print("Server started.") daemon.requestLoop() # note: we are not removing the name server registration! Pyro4-4.23/examples/banks/000077500000000000000000000000001227003673200153755ustar00rootroot00000000000000Pyro4-4.23/examples/banks/Readme.txt000066400000000000000000000006621227003673200173370ustar00rootroot00000000000000This is a simple electronic banking example. There are two banks:- Rabobank and ABN (don't ask - I'm from Holland) Their services are started with BankServer.py. The client runs some transactions on both banks (if found), like:- -creating accounts -deleting accounts -deposit money -withdraw money -inquire balance The ABN bank will not allow the client to overdraw and have a negative balance, the Rabobank will. Pyro4-4.23/examples/banks/banks.py000066400000000000000000000042511227003673200170470ustar00rootroot00000000000000 # Unrestricted account. class Account(object): def __init__(self): self._balance = 0.0 def withdraw(self, amount): self._balance -= amount def deposit(self, amount): self._balance += amount def balance(self): return self._balance # Restricted withdrawal account. class RestrictedAccount(Account): def withdraw(self, amount): if amount <= self._balance: self._balance -= amount else: raise ValueError('insufficent balance') # Abstract bank. class Bank(object): def __init__(self): self.accounts = {} def name(self): pass # must override this! def createAccount(self, name): pass # must override this! def deleteAccount(self, name): try: del self.accounts[name] except KeyError: raise KeyError('unknown account') def deposit(self, name, amount): try: return self.accounts[name].deposit(amount) except KeyError: raise KeyError('unknown account') def withdraw(self, name, amount): try: return self.accounts[name].withdraw(amount) except KeyError: raise KeyError('unknown account') def balance(self, name): try: return self.accounts[name].balance() except KeyError: raise KeyError('unknown account') def allAccounts(self): accs = {} for name in self.accounts.keys(): accs[name] = self.accounts[name].balance() return accs # Special bank: Rabobank. It has unrestricted accounts. class Rabobank(Bank): def name(self): return 'Rabobank' def createAccount(self, name): if name in self.accounts: raise ValueError('Account already exists') self.accounts[name] = Account() # Special bank: ABN. It has restricted accounts. class ABN(Bank): def name(self): return 'ABN bank' def createAccount(self, name): if name in self.accounts: raise ValueError('Account already exists') self.accounts[name] = RestrictedAccount() Pyro4-4.23/examples/banks/client.py000066400000000000000000000052421227003673200172300ustar00rootroot00000000000000# # Bank client. # # The client searches the two banks and performs a set of operations. # (the banks are searched simply by listing a namespace prefix path) # from __future__ import print_function import sys import Pyro4 # A bank client. class client(object): def __init__(self,name): self.name=name def doBusiness(self, bank): print("\n*** %s is doing business with %s:" % (self.name, bank.name())) print("Creating account") try: bank.createAccount(self.name) except ValueError: x=sys.exc_info()[1] print("Failed: %s" % x) print("Removing account and trying again") bank.deleteAccount(self.name) bank.createAccount(self.name) print("Deposit money") bank.deposit(self.name, 200.00) print("Deposit money") bank.deposit(self.name, 500.75) print("Balance=%.2f" % bank.balance(self.name)) print("Withdraw money") bank.withdraw(self.name, 400.00) print("Withdraw money (overdraw)") try: bank.withdraw(self.name, 400.00) except ValueError: x=sys.exc_info()[1] print("Failed: %s" % x) print("End balance=%.2f" % bank.balance(self.name)) print("Withdraw money from non-existing account") try: bank.withdraw('GOD',2222.22) print("!!! Succeeded?!? That is an error") except KeyError: x=sys.exc_info()[1] print("Failed as expected: %s" % x) print("Deleting non-existing account") try: bank.deleteAccount('GOD') print("!!! Succeeded?!? That is an error") except KeyError: x=sys.exc_info()[1] print("Failed as expected: %s" % x) ns=Pyro4.naming.locateNS() # list the available banks by looking in the NS for the given prefix path banknames=[name for name in ns.list(prefix="example.banks.")] if not banknames: raise RuntimeError('There are no banks to do business with!') banks=[] # list of banks (proxies) print() for name in banknames: print("Contacting bank: %s" % name) uri=ns.lookup(name) banks.append(Pyro4.core.Proxy(uri)) # Different clients that do business with all banks irmen = client('Irmen') suzy = client('Suzy') for bank in banks: irmen.doBusiness(bank) suzy.doBusiness(bank) # List all accounts print() for bank in banks: print("The accounts in the %s:" % bank.name()) accounts = bank.allAccounts() for name in accounts.keys(): print(" %s : %.2f" % (name,accounts[name])) Pyro4-4.23/examples/banks/server.py000066400000000000000000000007461227003673200172640ustar00rootroot00000000000000# # The banks server # from __future__ import print_function import Pyro4 import banks ns=Pyro4.naming.locateNS() daemon=Pyro4.core.Daemon() uri=daemon.register(banks.Rabobank()) ns.register("example.banks.rabobank",uri) uri=daemon.register(banks.ABN()) ns.register("example.banks.abn",uri) print("available banks:") print(list(ns.list(prefix="example.banks.").keys())) # enter the service loop. print("Banks are ready for customers.") daemon.requestLoop() Pyro4-4.23/examples/batchedcalls/000077500000000000000000000000001227003673200167105ustar00rootroot00000000000000Pyro4-4.23/examples/batchedcalls/Readme.txt000066400000000000000000000007611227003673200206520ustar00rootroot00000000000000This is an example that shows the batched calls feature. The example does a lot of method calls on the same proxy object. It shows the time it takes to do them individually. Afterwards, it does them again but this time using the batched calls feature. It prints the time taken and this should be much faster. It also shows what happens when one of the calls in the batch generates an error. (the batch is aborted and the error is raised locally once the result generator gets to it). Pyro4-4.23/examples/batchedcalls/client.py000066400000000000000000000112021227003673200205340ustar00rootroot00000000000000from __future__ import print_function import sys import time from Pyro4.util import getPyroTraceback import Pyro4 if sys.version_info<(3,0): input=raw_input NUMBER_OF_LOOPS=20000 uri=input("enter server object uri: ").strip() p=Pyro4.Proxy(uri) # First, we do a loop of N normal remote calls on the proxy # We time the loop and validate the computation result print("Normal remote calls...") begin=time.time() total=0 p.printmessage("beginning normal calls") for i in range(NUMBER_OF_LOOPS): total+=p.multiply(7,6) total+=p.add(10,20) p.printmessage("end of normal calls") assert total==(NUMBER_OF_LOOPS*(7*6 + 10+20)) # check duration=time.time()-begin print("that took {0:.2f} seconds ({1:.0f} calls/sec)".format(duration, NUMBER_OF_LOOPS*2.0/duration)) duration_normal=duration # Now we do the same loop of N remote calls but this time we use # the batched calls proxy. It collects all calls and processes them # in a single batch. For many subsequent calls on the same proxy this # is much faster than doing all calls individually. # (but it has a few limitations and requires changes to your code) print("\nBatched remote calls...") begin=time.time() batch=Pyro4.batch(p) # get a batched call proxy for 'p' batch.printmessage("beginning batch #1") for i in range(NUMBER_OF_LOOPS): batch.multiply(7,6) # queue a call, note that it returns 'None' immediately batch.add(10,20) # queue a call, note that it returns 'None' immediately batch.printmessage("end of batch #1") print("processing the results...") total=0 result=batch() # execute the batch of remote calls, it returns a generator that produces all results in sequence for r in result: total+=r duration=time.time()-begin assert total==(NUMBER_OF_LOOPS*(7*6 + 10+20)) # check print("total time taken {0:.2f} seconds ({1:.0f} calls/sec)".format(duration, NUMBER_OF_LOOPS*2.0/duration//100*100)) print("batched calls were {0:.1f} times faster than normal remote calls".format(duration_normal/duration)) # Now we do another loop of batched calls, but this time oneway (no results). print("\nOneway batched remote calls...") begin=time.time() batch=Pyro4.batch(p) # get a batched call proxy for 'p' batch.printmessage("beginning batch #2") for i in range(NUMBER_OF_LOOPS): batch.multiply(7,6) # queue a call, note that it returns 'None' immediately batch.add(10,20) # queue a call, note that it returns 'None' immediately batch.delay(2) # queue a delay of 2 seconds (but we won't notice) batch.printmessage("end of batch #2") print("executing batch, there will be no result values. Check server to see printed messages...") result=batch(oneway=True) # execute the batch of remote calls, oneway, will return None assert result is None duration=time.time()-begin print("total time taken {0:.2f} seconds ({1:.0f} calls/sec)".format(duration, NUMBER_OF_LOOPS*2.0/duration//100*100)) print("oneway batched calls were {0:.1f} times faster than normal remote calls".format(duration_normal/duration)) # Batches can be executed async as well print("\nBatched remote calls, async...") batch=Pyro4.batch(p) # get a batched call proxy for 'p' batch.printmessage("beginning batch #3") batch.multiply(7,6) # queue a call, note that it returns 'None' immediately batch.add(10,20) # queue a call, note that it returns 'None' immediately batch.delay(2) # queue a delay, but this doesn't matter with async batch.printmessage("end of batch #3") print("executing the batch... (should return immediately because async)") asyncresult=batch(async=True) # execute the batch, async (return immediately) print("processing the results...(should wait until async results become available)") results=list(asyncresult.value) print("results=",results) # Show what happens when one of the methods in a batch generates an error. # (the batch is aborted and the error is raised locally again). # Btw, you can re-use a batch proxy once you've called it and processed the results. print("\nBatch with an error. Dividing a number by decreasing divisors...") for d in range(3,-3,-1): # divide by 3,2,1,0,-1,-2,-3... but 0 will be a problem ;-) batch.divide(100,d) print("getting results...") divisor=3 try: for result in batch(): print("100//%d = %d" % (divisor,result)) divisor-=1 # this will raise the proper zerodivision exception once we're about # to process the batch result from the divide by 0 call. except Exception: print("An error occurred during the batch! (expected)") print("".join(getPyroTraceback())) Pyro4-4.23/examples/batchedcalls/server.py000066400000000000000000000012551227003673200205730ustar00rootroot00000000000000from __future__ import print_function import time import Pyro4 from Pyro4.socketutil import getIpAddress class Thingy(object): def multiply(self,a,b): return a*b def add(self,a,b): return a+b def divide(self,a,b): return a//b def error(self): return 1//0 def delay(self, seconds): time.sleep(seconds) return seconds def printmessage(self,message): print(message) return 0 d=Pyro4.Daemon(host=getIpAddress("", workaround127=True), port=0) uri=d.register(Thingy(), "example.batched") print("server object uri:",uri) print("batched calls server running.") d.requestLoop() Pyro4-4.23/examples/benchmark/000077500000000000000000000000001227003673200162315ustar00rootroot00000000000000Pyro4-4.23/examples/benchmark/Readme.txt000066400000000000000000000033311227003673200201670ustar00rootroot00000000000000This test is to find out the average time it takes for a remote PYRO method call. Also it is a kind of stress test because lots of calls are made in a very short time. The oneway method call test is very fast if you run the client and server on different machines. If they're running on the same machine, the speedup is less noticable. There is also the 'connections' benchmark which tests the speed at which Pyro can make new proxy connections. It tests the raw connect speed (by releasing and rebinding existing proxies) and also the speed at which new proxies can be created that perform a single remote method call. Different serializers --------------------- Note that Pyro4's performance is very much affected by two things: 1) the network latency and bandwith 2) the characteristics of your data (small messages or large) 2) the serializer that is used. For instance, here are the numbers of the various serializers on my system (running the benchmark on localhost): serializer | performance (avg. time/call) -----------+------------------------------- pickle | 0.114 msec = 8781 calls/sec marshal | 0.124 msec = 8068 calls/sec json | 0.182 msec = 5508 calls/sec serpent | 0.259 msec = 3856 calls/sec Pickle is very fast (even faster than marshal, which I find surprising) but it has potential security problems. Serpent, the default serializer, is relatively slow, but is has the richest type support of the other serializers that don't have security problems. Don't select a serializer upfront based on the above performance chart. It is just the simple result of this silly benchmark example. Real-world performance may be quite different in your particular situation. Pyro4-4.23/examples/benchmark/bench.py000066400000000000000000000021061227003673200176610ustar00rootroot00000000000000class sub1(object): def meth1(s,arg): return 'This is sub1.meth1' class sub2(sub1): def meth2(s,arg): return 'This is sub2.meth2' class sub3(sub2): def meth3(s,arg): return 'This is sub3.meth3' class sub4(sub3,sub2): def meth4(s,arg): return 'This is sub4.meth4' class bench(sub4): def ping(self): pass def length(self, string): return len(string) def timestwo(self, value): return value*2 def bigreply(self): return 'BIG REPLY'*500 def bigarg(self,arg): return len(arg) def manyargs(self, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15): return a1+a2+a3+a4+a5+a6+a7+a8+a9+a10+a11+a12+a13+a14+a15 def noreply(self, arg): pass def varargs(self, *args): return len(args) def keywords(self, **args): return args def echo(self, *args): return args def oneway(self, *args): # oneway doesn't return anything pass def mapping(self, mapping): return mapping Pyro4-4.23/examples/benchmark/client.py000066400000000000000000000050231227003673200200610ustar00rootroot00000000000000from __future__ import print_function import sys,time import Pyro4 import bench if sys.version_info<(3,0): input=raw_input uri=input("Uri of benchmark server? ").strip() object = Pyro4.core.Proxy(uri) object._pyroOneway.add('oneway') object._pyroBind() def f1(): _=object.length('Irmen de Jong') def f2(): _=object.timestwo(21) def f3(): _=object.bigreply() def f4(): _=object.manyargs(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15) def f5(): _=object.noreply(99993333) def f6(): _=object.varargs('een',2,(3,),[4]) def f7(): _=object.keywords(arg1='zork') def f8(): _=object.echo('een',2,(3,),[4]) def f9(): _=object.meth1('stringetje') def fa(): _=object.meth2('stringetje') def fb(): _=object.meth3('stringetje') def fc(): _=object.meth4('stringetje') def fd(): _=object.bigarg('Argument'*50) def fe(): object.oneway('stringetje',432423434) def ff(): _=object.mapping({"aap":42, "noot": 99, "mies": 987654}) funcs = (f1,f2,f3,f4,f5,f6,f7,f8,f9,fa,fb,fc,fd,fe,ff) print('-------- BENCHMARK REMOTE OBJECT ---------') print('Pay attention to the "fe" test -- this is a Oneway call and should be *fast*') print('(if you are running the server and client on different machines)') begin = time.time() iters = 1000 for f in funcs: sys.stdout.write("%d times %s " % (iters,f.__name__)) voor = time.time() for i in range(iters): f() sys.stdout.write("%.3f\n" % (time.time()-voor)) sys.stdout.flush() duration = time.time()-begin print('total time %.3f seconds' % duration) amount=len(funcs)*iters print('total method calls: %d' % (amount)) avg_pyro_msec = 1000.0*duration/amount print('avg. time per method call: %.3f msec (%d/sec) (serializer: %s)' % (avg_pyro_msec,amount/duration, Pyro4.config.SERIALIZER)) print('-------- BENCHMARK LOCAL OBJECT ---------') object=bench.bench() begin = time.time() iters = 200000 for f in funcs: sys.stdout.write("%d times %s " % (iters,f.__name__)) voor = time.time() for i in range(iters): f() sys.stdout.write("%.3f\n" % (time.time()-voor)) sys.stdout.flush() duration = time.time()-begin print('total time %.3f seconds' % duration) amount=len(funcs)*iters print('total method calls: %d' % (amount)) avg_normal_msec = 1000.0*duration/amount print('avg. time per method call: %.3f msec (%d/sec)' % (avg_normal_msec,amount/duration//1000*1000)) print('Normal method call is %.0f times faster than Pyro method call.'%(avg_pyro_msec/avg_normal_msec)) Pyro4-4.23/examples/benchmark/connections.py000066400000000000000000000024111227003673200211230ustar00rootroot00000000000000from __future__ import print_function import time, sys import Pyro4 if sys.version_info<(3,0): input=raw_input uri=input("Uri of benchmark server? ").strip() print("Timing raw connect speed (no method call)...") p=Pyro4.core.Proxy(uri) p.ping() ITERATIONS=2000 begin=time.time() for loop in range(ITERATIONS): if loop%500==0: print(loop) p._pyroRelease() p._pyroBind() duration=time.time()-begin print("%d connections in %.3f sec = %.0f conn/sec" % (ITERATIONS, duration, ITERATIONS/duration)) del p print("Timing proxy creation+connect+methodcall speed...") ITERATIONS=2000 begin=time.time() for loop in range(ITERATIONS): if loop%500==0: print(loop) with Pyro4.core.Proxy(uri) as p: p.ping() duration=time.time()-begin print("%d new proxy calls in %.3f sec = %.0f calls/sec" % (ITERATIONS, duration, ITERATIONS/duration)) print("Timing proxy methodcall speed...") p=Pyro4.core.Proxy(uri) p.ping() ITERATIONS=10000 begin=time.time() for loop in range(ITERATIONS): if loop%1000==0: print(loop) p.ping() duration=time.time()-begin print("%d calls in %.3f sec = %.0f calls/sec" % (ITERATIONS, duration, ITERATIONS/duration)) print("Serializer used:", Pyro4.config.SERIALIZER) Pyro4-4.23/examples/benchmark/server.py000066400000000000000000000003361227003673200201130ustar00rootroot00000000000000from __future__ import print_function import Pyro4 import bench obj=bench.bench() daemon=Pyro4.Daemon() uri = daemon.register(obj,"example.benchmark") print("Server running, uri = %s" % uri) daemon.requestLoop() Pyro4-4.23/examples/callback/000077500000000000000000000000001227003673200160335ustar00rootroot00000000000000Pyro4-4.23/examples/callback/Readme.txt000066400000000000000000000042661227003673200200010ustar00rootroot00000000000000These examples shows how you can let a server call back to the client. There are 2 examples. 1) first example: server.py + client.py The client creates some worker objects on the server. It provides them with a callback object that lives in the client. When a worker is done with its task, it will invoke a method on the callback object. That means that this time, the client gets a call from the server that notifies it that a worker has completed its job. (Note: the client uses oneway calls to start up the workers, this ensures that they are running in the background) For all of this to work, the client needs to create a daemon as well: it needs to be able to receive (callback) calls after all. So it creates a daemon, a callback receiver, and starts it all up just like a server would do. The client counts the number of 'work completed' callbacks it receives. To remain in the daemon loop, the client provides a special loop condition that is true while the counter is less than the number of workers. Notice that the client sets PYRO_COMMTIMEOUT. That is needed because otherwise it will block in the default requestloop, and it will never evaluate the loopcondition. By setting a timeout we force it to periodically break from the blocking wait and check the loop condition. We could also have used the 'select' servertype instead of setting a PYRO_COMMTIMEOUT, because that one already breaks periodically. (PYRO_POLLTIMEOUT). 2) second example: server2.py + client2.py This example shows how to use the @Pyro4.callback decorator to flag a method to be a callback method. This makes Pyro raise any exceptions that occur in this method also on the side where the method is running. Otherwise it would just silently pass the exception back to the side that was calling the callback method. Also note that this example makes use of Pyro's AutoProxy feature. Sending pyro objects 'over the wire' will automatically convert them into proxies so that the other side will talk to the actual object, instead of a local copy. So the client just sends a callback object to the server, and the server can just return a worker object, as if it was a normal method call. Pyro4-4.23/examples/callback/client.py000066400000000000000000000022631227003673200176660ustar00rootroot00000000000000from __future__ import with_statement import random import Pyro4 # We need to set either a socket communication timeout, # or use the select based server. Otherwise the daemon requestLoop # will block indefinitely and is never able to evaluate the loopCondition. Pyro4.config.COMMTIMEOUT=0.5 NUM_WORKERS=5 class CallbackHandler(object): workdone=0 def done(self, number): print("callback: worker %d reports work is done!" % number) CallbackHandler.workdone+=1 with Pyro4.core.Daemon() as daemon: # register our callback handler callback=CallbackHandler() daemon.register(callback) # contact the server and put it to work print("creating a bunch of workers") with Pyro4.core.Proxy("PYRONAME:example.callback") as server: for _ in range(NUM_WORKERS): worker=server.addworker(callback) # provide our callback handler! worker._pyroOneway.add("work") # to be able to run in the background worker.work(random.randint(1,5)) print("waiting for all work complete...") daemon.requestLoop(loopCondition=lambda: CallbackHandler.workdone ').strip() if line=='/quit': break if line: self.chatbox.publish(self.channel ,self.nick ,line) except EOFError: pass finally: self.chatbox.leave(self.channel, self.nick) self.abort=1 self._pyroDaemon.shutdown() class DaemonThread(threadutil.Thread): def __init__(self, chatter): threadutil.Thread.__init__(self) self.chatter=chatter self.setDaemon(True) def run(self): with Pyro4.core.Daemon() as daemon: daemon.register(self.chatter) daemon.requestLoop(lambda: not self.chatter.abort) chatter=Chatter() daemonthread=DaemonThread(chatter) daemonthread.start() chatter.start() print('Exit.') Pyro4-4.23/examples/chatbox/server.py000066400000000000000000000054111227003673200176100ustar00rootroot00000000000000from __future__ import with_statement import Pyro4 # Chat box administration server. # Handles logins, logouts, channels and nicknames, and the chatting. class ChatBox(object): def __init__(self): self.channels={} # registered channels { channel --> (nick, client callback) list } self.nicks=[] # all registered nicks on this server def getChannels(self): return list(self.channels.keys()) def getNicks(self): return self.nicks def join(self, channel, nick, callback): if not channel or not nick: raise ValueError("invalid channel or nick name") if nick in self.nicks: raise ValueError('this nick is already in use') if channel not in self.channels: print('CREATING NEW CHANNEL %s' % channel) self.channels[channel]=[] self.channels[channel].append((nick, callback)) self.nicks.append(nick) callback._pyroOneway.add('message') # don't wait for results for this method print("%s JOINED %s" % (nick, channel)) self.publish(channel,'SERVER','** '+nick+' joined **') return [nick for (nick,c) in self.channels[channel]] # return all nicks in this channel def leave(self,channel,nick): if not channel in self.channels: print('IGNORED UNKNOWN CHANNEL %s' % channel) return for (n,c) in self.channels[channel]: if n==nick: self.channels[channel].remove((n, c)) break self.publish(channel,'SERVER','** '+nick+' left **') if len(self.channels[channel])<1: del self.channels[channel] print('REMOVED CHANNEL %s' % channel) self.nicks.remove(nick) print("%s LEFT %s" % (nick, channel)) def publish(self, channel, nick, msg): if not channel in self.channels: print('IGNORED UNKNOWN CHANNEL %s' % channel) return for (n,c) in self.channels[channel][:]: # use a copy of the list try: c.message(nick,msg) # oneway call except Pyro4.errors.ConnectionClosedError: # connection dropped, remove the listener if it's still there # check for existence because other thread may have killed it already if (n,c) in self.channels[channel]: self.channels[channel].remove((n, c)) print('Removed dead listener %s %s' % (n, c)) with Pyro4.core.Daemon() as daemon: with Pyro4.naming.locateNS() as ns: uri=daemon.register(ChatBox()) ns.register("example.chatbox.server",uri) # enter the service loop. print('Chatbox open.') daemon.requestLoop() Pyro4-4.23/examples/circular/000077500000000000000000000000001227003673200161035ustar00rootroot00000000000000Pyro4-4.23/examples/circular/Readme.txt000066400000000000000000000011471227003673200200440ustar00rootroot00000000000000Create a chain of objects calling each other: client --> A --> B ^ | | v | `----- C I.e. C calls A again. A detects that the message went full circle and returns the result (a 'trace' of the route) to the client. (the detection checks if the name of the current server is already in the current trace of the route, i.e., if it arrives for a second time on the same server, it concludes that we're done). First start the three servers (servA,B,C) and then run the client. You need to have a nameserver running. Pyro4-4.23/examples/circular/chain.py000066400000000000000000000015251227003673200175420ustar00rootroot00000000000000from __future__ import print_function import Pyro4 # a Chain member. Passes messages to the next link, # until the message went full-circle: then it exits. class Chain(object): def __init__(self, name, next): self.name=name self.nextName=next self.next=None def process(self,message): if self.next is None: self.next=Pyro4.core.Proxy("PYRONAME:example.chain."+self.nextName) if self.name in message: print("Back at %s; we completed the circle!" % self.name) return ["complete at "+self.name] else: print("I'm %s, passing to %s" % (self.name,self.nextName)) message.append(self.name) result=self.next.process(message) result.insert(0,"passed on from "+self.name) return result Pyro4-4.23/examples/circular/client.py000066400000000000000000000002261227003673200177330ustar00rootroot00000000000000from __future__ import print_function import Pyro4 obj=Pyro4.core.Proxy("PYRONAME:example.chain.A") print("Result=%s" % obj.process(["hello"])) Pyro4-4.23/examples/circular/servA.py000066400000000000000000000005441227003673200175400ustar00rootroot00000000000000from __future__ import print_function import Pyro4 import chain this = "A" next = "B" servername="example.chain."+this daemon=Pyro4.core.Daemon() obj=chain.Chain(this,next) uri=daemon.register(obj) ns=Pyro4.naming.locateNS() ns.register(servername,uri) # enter the service loop. print("Server started %s" % this) daemon.requestLoop() Pyro4-4.23/examples/circular/servB.py000066400000000000000000000005441227003673200175410ustar00rootroot00000000000000from __future__ import print_function import Pyro4 import chain this = "B" next = "C" servername="example.chain."+this daemon=Pyro4.core.Daemon() obj=chain.Chain(this,next) uri=daemon.register(obj) ns=Pyro4.naming.locateNS() ns.register(servername,uri) # enter the service loop. print("Server started %s" % this) daemon.requestLoop() Pyro4-4.23/examples/circular/servC.py000066400000000000000000000005441227003673200175420ustar00rootroot00000000000000from __future__ import print_function import Pyro4 import chain this = "C" next = "A" servername="example.chain."+this daemon=Pyro4.core.Daemon() obj=chain.Chain(this,next) uri=daemon.register(obj) ns=Pyro4.naming.locateNS() ns.register(servername,uri) # enter the service loop. print("Server started %s" % this) daemon.requestLoop() Pyro4-4.23/examples/deadlock/000077500000000000000000000000001227003673200160455ustar00rootroot00000000000000Pyro4-4.23/examples/deadlock/Readme.txt000066400000000000000000000011021227003673200177750ustar00rootroot00000000000000This example shows some code that triggers a Pyro conversation deadlock. The client and server engage in a 'conversation' where they will deadlock because a single proxy is used for both the initial server method call, and client callback. The client callback method calls the server again. But it will fail, because the proxy it is using is still engaged in the original method call to the server and is locked (waiting for a response). A simple solution is to never reuse proxies in callbacks, and instead create new ones and use those in the callback functions. Pyro4-4.23/examples/deadlock/bouncer.py000066400000000000000000000017501227003673200200570ustar00rootroot00000000000000from __future__ import print_function, with_statement import Pyro4.threadutil # a message bouncer. Passes messages back to the callback # object, until a certain limit is reached. class Bouncer(object): def __init__(self, name): self.name = name self.count = 0 self.callbackMutex = Pyro4.threadutil.Lock() def register(self, callback): self.callback = callback def process(self, message): print("in process", self.name) if len(message) >= 3: print("Back in", self.name, ", message is large enough... stopping!") return ["complete at " + self.name + ":" + str(self.count)] print("I'm", self.name, ", bouncing back...") message.append(self.name) with self.callbackMutex: result = self.callback.process(message) self.count += 1 result.insert(0, "passed on from " + self.name + ":" + str(self.count)) print("returned from callback") return result Pyro4-4.23/examples/deadlock/client.py000066400000000000000000000026221227003673200176770ustar00rootroot00000000000000from __future__ import print_function import Pyro4 from Pyro4.threadutil import Thread import bouncer abort = False def PyroLoop(daemon): daemon.requestLoop() def main(): global abort daemon = Pyro4.Daemon() server = Pyro4.Proxy("PYRONAME:example.deadlock") bounceObj = bouncer.Bouncer("Client") daemon.register(bounceObj) # callback objece # register callback obj on theserver server.register(bounceObj) # register server as 'callback' on the bounce object in this client # note: we're using the same proxy here as the main program! # This is the main cause of the deadlock, because this proxy will already # be engaged in a call when the callback object here wants to use it as well. # One solution could be to use a new proxy from inside the callback object, like this: # server2 = server.__copy__() # bounceObj.register(server2) bounceObj.register(server) # create a thread that handles callback requests thread = Thread(target=PyroLoop, args=(daemon,)) thread.setDaemon(True) thread.start() print("This bounce example will deadlock!") print("Read the source or Readme.txt for more info why this is the case!") print("Calling server...") result = server.process(["hello"]) print("Result=", result) # <--- you will never see this, it will deadlock in the previous call if __name__ == '__main__': main() Pyro4-4.23/examples/deadlock/server.py000066400000000000000000000005451227003673200177310ustar00rootroot00000000000000from __future__ import print_function import Pyro4 import bouncer daemon = Pyro4.Daemon() uri = daemon.register(bouncer.Bouncer("Server")) Pyro4.locateNS().register("example.deadlock",uri) print("This bounce example will deadlock!") print("Read the source or Readme.txt for more info why this is the case!") print("Bouncer started.") daemon.requestLoop() Pyro4-4.23/examples/disconnects/000077500000000000000000000000001227003673200166135ustar00rootroot00000000000000Pyro4-4.23/examples/disconnects/Readme.txt000066400000000000000000000021451227003673200205530ustar00rootroot00000000000000Example code that shows a possible way to deal with client disconnects in the server. It sets the COMMTIMEOUT config item on the server side. This will make the connections timeout after the given time if no more data is received. That connection will then be terminated. The problem with this is, that a client that is still connected but simply takes too long between remote method calls, will encounter a ConnectionClosedError. But you can use Pyro's auto-reconnect feature to deal with this. The client.py code creates a special Proxy class that you use instead of Pyro's default, which will automatically do this for you on every method call. Alternatively you can do it explicitly in your own code like the 'autoreconnect' client example does. A drawback of the code shown is that it is not very efficient; it now requires two remote messages for every method invocation on the proxy. Note that the custom proxy class shown in the client uses some advanced features of the Pyro API: - overrides internal method that handles method calls - creates and receives custom wire protocol messages Pyro4-4.23/examples/disconnects/client.py000066400000000000000000000051021227003673200204410ustar00rootroot00000000000000from __future__ import print_function import sys import Pyro4 import Pyro4.message import warnings warnings.filterwarnings("ignore") if sys.version_info < (3, 0): input = raw_input print("You can run this client on a different computer so you can disable the network connection (by yanking out the lan cable or whatever).") print("Alternatively, wait for a timeout on the server, which will then close its connection.") uri = input("Uri of server? ").strip() class AutoReconnectingProxy(Pyro4.core.Proxy): """ A Pyro proxy that automatically recovers from a server disconnect. It does this by intercepting every method call and then it first 'pings' the server to see if it still has a working connection. If not, it reconnects the proxy and retries the method call. Drawback is that every method call now uses two remote messages (a ping, and the actual method call). This uses some advanced features of the Pyro API. """ def _pyroInvoke(self, methodname, vargs, kwargs, flags=0): # first test if we have an open connection, if not, we just reconnect if self._pyroConnection: try: print(" ") # send the special 'ping' message to the daemon, to see if this connection is still alive # we expect a 'ping' response (no-op) ping = Pyro4.message.Message(Pyro4.message.MSG_PING, b"", 42, 0, 0) self._pyroConnection.send(ping.to_bytes()) Pyro4.message.Message.recv(self._pyroConnection, [Pyro4.message.MSG_PING]) print(" ") except Pyro4.errors.ConnectionClosedError: # or possibly even ProtocolError print(" ") self._pyroReconnect() print(" ") return super(AutoReconnectingProxy, self)._pyroInvoke(methodname, vargs, kwargs, flags) with AutoReconnectingProxy(uri) as obj: result = obj.echo("12345") print("result =", result) print("\nClient proxy connection is still open. Disable the network now (or wait until the connection timeout on the server expires) and see what the server does.") print("Once you see on the server that it got a timeout or a disconnect, enable the network again.") input("Press enter to continue:") print("\nDoing a new call on the same proxy:") result = obj.echo("12345") print("result =", result) Pyro4-4.23/examples/disconnects/server.py000066400000000000000000000007531227003673200205000ustar00rootroot00000000000000from __future__ import print_function import logging import Pyro4 logging.basicConfig(level=logging.DEBUG) logging.getLogger("Pyro4").setLevel(logging.DEBUG) Pyro4.config.COMMTIMEOUT = 5.0 Pyro4.config.POLLTIMEOUT = 5.0 # only used for multiplexing server class TestDisconnect(object): def echo(self, arg): print("echo: ", arg) return arg d = Pyro4.Daemon() uri = d.register(TestDisconnect(), "disconnect") print("uri =", uri) d.requestLoop() Pyro4-4.23/examples/distributed-computing/000077500000000000000000000000001227003673200206245ustar00rootroot00000000000000Pyro4-4.23/examples/distributed-computing/Readme.txt000066400000000000000000000026231227003673200225650ustar00rootroot00000000000000A simple distributed computing example with "pull" model. There is a single central work dispatcher/gatherer that is contacted by every worker you create. The worker asks the dispatcher for a chunk of work data and returns the results when it is done. The worker in this example finds the prime factorials for the numbers that it gets as 'work' from the dispatcher, and returns the list of factorials as 'result' to the dispatcher. The client program generates a list of random numbers and sends each number as a single work item to the dispatcher. It collects the results and prints them to the screen once everything is complete. *** Starting up *** - We're using a Name Server: * configure it to allow the pickle serializer, for instance by setting the environment variable: PYRO_SERIALIZERS_ACCEPTED=pickle * start the name server. - start the dispatcher (dispatcher.py) - start one or more workers (worker.py). For best results, start one of these on every machine/CPU in your network :-) - finally, give the system a task to solve: start the client.py program. Note: The dispatcher is pretty braindead. It only has a single work and result queue. Running multiple clients will probably break the system. Improvements are left as an exercise. Note: because custom classes are passed over the network (such as WorkItem and queue.Empty) the pickle serializer is used. Pyro4-4.23/examples/distributed-computing/client.py000066400000000000000000000035541227003673200224630ustar00rootroot00000000000000from __future__ import with_statement try: import queue except ImportError: import Queue as queue import random import Pyro4 from workitem import Workitem # we're using custom classes, so need to use pickle Pyro4.config.SERIALIZER='pickle' NUMBER_OF_ITEMS = 40 def main(): print("\nThis program will calculate Prime Factorials of a bunch of random numbers.") print("The more workers you will start (on different cpus/cores/machines),") print("the faster you will get the complete list of results!\n") with Pyro4.core.Proxy("PYRONAME:example.distributed.dispatcher") as dispatcher: placework(dispatcher) numbers=collectresults(dispatcher) printresults(numbers) def placework(dispatcher): print("placing work items into dispatcher queue.") for i in range(NUMBER_OF_ITEMS): number = random.randint(3211, 4999999) * random.randint(3211, 999999) item = Workitem(i+1, number) dispatcher.putWork(item) def collectresults(dispatcher): print("getting results from dispatcher queue.") numbers={} while len(numbers)0: print("there's still stuff in the dispatcher result queue, that is odd...") return numbers def printresults(numbers): print("\nComputed Prime Factorials follow:") for (number, factorials) in numbers.items(): print("%d --> %s" % (number,factorials)) if __name__=="__main__": main() Pyro4-4.23/examples/distributed-computing/dispatcher.py000066400000000000000000000017161227003673200233310ustar00rootroot00000000000000from __future__ import print_function try: import queue except ImportError: import Queue as queue import Pyro4 # we're using custom classes, so need to use pickle Pyro4.config.SERIALIZERS_ACCEPTED.add('pickle') class DispatcherQueue(object): def __init__(self): self.workqueue = queue.Queue() self.resultqueue = queue.Queue() def putWork(self, item): self.workqueue.put(item) def getWork(self, timeout=5): return self.workqueue.get(block=True, timeout=timeout) def putResult(self, item): self.resultqueue.put(item) def getResult(self, timeout=5): return self.resultqueue.get(block=True, timeout=timeout) def workQueueSize(self): return self.workqueue.qsize() def resultQueueSize(self): return self.resultqueue.qsize() ######## main program Pyro4.Daemon.serveSimple({ DispatcherQueue(): "example.distributed.dispatcher" }) Pyro4-4.23/examples/distributed-computing/worker.py000066400000000000000000000027631227003673200225170ustar00rootroot00000000000000from __future__ import print_function import os,socket,sys from math import sqrt try: import queue except ImportError: import Queue as queue import Pyro4 from workitem import Workitem if sys.version_info<(3,0): range=xrange # we're using custom classes, so need to use pickle Pyro4.config.SERIALIZER='pickle' WORKERNAME = "Worker_%d@%s" % (os.getpid(), socket.gethostname()) def factorize(n): """simple algorithm to find the prime factorials of the given number n""" def isPrime(n): return not [x for x in range(2,int(sqrt(n))+1) if n%x == 0] primes = [] candidates = range(2,n+1) candidate = 2 while not primes and candidate in candidates: if n%candidate == 0 and isPrime(candidate): primes = primes + [candidate] + factorize(n//candidate) candidate+=1 return primes def process(item): print("factorizing %s -->" % item.data) sys.stdout.flush() item.result=factorize(int(item.data)) print(item.result) item.processedBy = WORKERNAME def main(): dispatcher = Pyro4.core.Proxy("PYRONAME:example.distributed.dispatcher") print("This is worker %s" % WORKERNAME) print("getting work from dispatcher.") while True: try: item = dispatcher.getWork() except queue.Empty: print("no work available yet.") else: process(item) dispatcher.putResult(item) if __name__=="__main__": main() Pyro4-4.23/examples/distributed-computing/workitem.py000066400000000000000000000004521227003673200230400ustar00rootroot00000000000000class Workitem(object): def __init__(self, itemId, data): print("Created workitem %s" % itemId) self.itemId=itemId self.data=data self.result=None self.processedBy=None def __str__(self): return "" % str(self.itemId) Pyro4-4.23/examples/echoserver/000077500000000000000000000000001227003673200164445ustar00rootroot00000000000000Pyro4-4.23/examples/echoserver/Readme.txt000077500000000000000000000003211227003673200204010ustar00rootroot00000000000000Shows how you might use the built-in test echo server. So, this example only contains some client code. You are supposed to start the echo server with something like: $ python -m Pyro4.test.echoserver Pyro4-4.23/examples/echoserver/client.py000066400000000000000000000014601227003673200202750ustar00rootroot00000000000000from __future__ import print_function import sys import Pyro4 import Pyro4.util print("First start the built-in test echo server with something like:") print("$ python -m Pyro4.test.echoserver") print("Enter the server's uri that was printed:") if sys.version_info<(3,0): uri=raw_input() else: uri=input() uri=uri.strip() echoserver=Pyro4.Proxy(uri) response=echoserver.echo("hello") print("\ngot back from the server: %s" % response) response=echoserver.echo([1,2,3,4]) print("got back from the server: %s" % response) try: echoserver.error() except: print("\ncaught an exception (expected), traceback:") print("".join(Pyro4.util.getPyroTraceback())) print("\nshutting down the test echo server. (restart it if you want to run this again)") echoserver.shutdown() Pyro4-4.23/examples/eventloop/000077500000000000000000000000001227003673200163125ustar00rootroot00000000000000Pyro4-4.23/examples/eventloop/Readme.txt000066400000000000000000000006161227003673200202530ustar00rootroot00000000000000This example shows a possible use of a custom 'event loop'. That means that your own program takes care of the main event loop, and that it needs to detect when 'events' happen on the appropriate Pyro objects. This particular example uses select to wait for the set of objects (sockets, really) and calls the correct event handler. You can add your own application's sockets easily this way. Pyro4-4.23/examples/eventloop/client.py000066400000000000000000000007251227003673200201460ustar00rootroot00000000000000from __future__ import print_function import Pyro4 import sys if sys.version_info<(3,0): input=raw_input with Pyro4.core.Proxy("PYRONAME:example.embedded.server") as proxy: print("5*11=%d" % proxy.multiply(5,11)) print("'x'*10=%s" % proxy.multiply('x',10)) input("press enter to do a loop of some more calls:") for i in range(1,20): print("2*i=%d" % proxy.multiply(2,i)) print("'@'*i=%s" % proxy.multiply('@',i)) Pyro4-4.23/examples/eventloop/server.py000066400000000000000000000054211227003673200201740ustar00rootroot00000000000000from __future__ import print_function import socket import select import sys import Pyro4.core import Pyro4.naming import Pyro4.socketutil if sys.version_info<(3,0): input=raw_input print("Make sure that you don't have a name server running already.") servertype=input("Servertype thread/multiplex (t/m)?") if servertype=='t': Pyro4.config.SERVERTYPE="thread" else: Pyro4.config.SERVERTYPE="multiplex" hostname=socket.gethostname() my_ip = Pyro4.socketutil.getIpAddress(None, workaround127=True) class EmbeddedServer(object): def multiply(self, x, y): return x*y print("initializing services... servertype=%s" % Pyro4.config.SERVERTYPE) # start a name server with broadcast server as well nameserverUri, nameserverDaemon, broadcastServer = Pyro4.naming.startNS(host=my_ip) assert broadcastServer is not None, "expect a broadcast server to be created" print("got a Nameserver, uri=%s" % nameserverUri) print("ns daemon location string=%s" % nameserverDaemon.locationStr) print("ns daemon sockets=%s" % nameserverDaemon.sockets) print("bc server socket=%s (fileno %d)" % (broadcastServer.sock, broadcastServer.fileno())) # create a Pyro daemon pyrodaemon=Pyro4.core.Daemon(host=hostname) print("daemon location string=%s" % pyrodaemon.locationStr) print("daemon sockets=%s" % pyrodaemon.sockets) # register a server object with the daemon serveruri=pyrodaemon.register(EmbeddedServer()) print("server uri=%s" % serveruri) # register it with the embedded nameserver directly nameserverDaemon.nameserver.register("example.embedded.server",serveruri) print("") # below is our custom event loop. while True: print("Waiting for events...") # create sets of the socket objects we will be waiting on # (a set provides fast lookup compared to a list) nameserverSockets = set(nameserverDaemon.sockets) pyroSockets = set(pyrodaemon.sockets) rs=[broadcastServer] # only the broadcast server is directly usable as a select() object rs.extend(nameserverSockets) rs.extend(pyroSockets) rs,_,_ = select.select(rs,[],[],3) eventsForNameserver=[] eventsForDaemon=[] for s in rs: if s is broadcastServer: print("Broadcast server received a request") broadcastServer.processRequest() elif s in nameserverSockets: eventsForNameserver.append(s) elif s in pyroSockets: eventsForDaemon.append(s) if eventsForNameserver: print("Nameserver received a request") nameserverDaemon.events(eventsForNameserver) if eventsForDaemon: print("Daemon received a request") pyrodaemon.events(eventsForDaemon) nameserverDaemon.close() broadcastServer.close() pyrodaemon.close() print("done") Pyro4-4.23/examples/exceptions/000077500000000000000000000000001227003673200164605ustar00rootroot00000000000000Pyro4-4.23/examples/exceptions/Readme.txt000066400000000000000000000014101227003673200204120ustar00rootroot00000000000000This test is to show PYRO's remote exception capabilities. The remote object contains various member functions which raise various kinds of exceptions. The client will print those. Note the special handling of the Pyro exception. It is possible to extract and print the *remote* traceback. You can then see where in the code on the remote side the error occured! By installing Pyro's excepthook (Pyro4.util.excepthook) you can even see the remote traceback when you're not catching any exceptions. Also try to set PYRO_DETAILED_TRACEBACK to True (on the server) to get a very detailed traceback in your client. This can help debugging. Note: you can only use your own exception classes, when you are using the pickle serializer. This is not the default. Pyro4-4.23/examples/exceptions/client.py000066400000000000000000000030001227003673200203010ustar00rootroot00000000000000from __future__ import print_function import sys import Pyro4 test = Pyro4.core.Proxy("PYRONAME:example.exceptions") print(test.div(2.0,9.0)) try: print(2//0) except ZeroDivisionError: print("DIVIDE BY ZERO: %s" % sys.exc_info()[1]) try: print(test.div(2,0)) except ZeroDivisionError: print("DIVIDE BY ZERO: %s" % sys.exc_info()[1]) try: result=test.error() print("%r, %s" % (result,result)) except ValueError: print("VALUERROR: %s" % sys.exc_info()[1]) try: result=test.error2() print("%r, %s" % (result,result)) except ValueError: print("VALUERROR: %s" % sys.exc_info()[1]) try: result=test.othererr() print("%r, %s" % (result,result)) except Exception: print("ANOTHER ERROR: %s" % sys.exc_info()[1]) try: result=test.unserializable() print("%r, %s" % (result,result)) except Exception: print("UNSERIALIZABLE ERROR: %s" % sys.exc_info()[1]) print("\n*** invoking server method that crashes, catching traceback ***") try: print(test.complexerror()) except Exception: print("CAUGHT ERROR >>> %s" % sys.exc_info()[1]) print("Printing Pyro traceback >>>>>>") print("".join(Pyro4.util.getPyroTraceback())) print("<<<<<<< end of Pyro traceback") print("\n*** installing pyro's excepthook") sys.excepthook=Pyro4.util.excepthook print("*** invoking server method that crashes, not catching anything ***") print(test.complexerror()) # due to the excepthook, the exception will show the pyro error Pyro4-4.23/examples/exceptions/excep.py000066400000000000000000000014371227003673200201430ustar00rootroot00000000000000import pickle class UnserializableError(Exception): def __reduce__(self): raise pickle.PicklingError("make this nonpickleable") class TestClass(object): def div(self, arg1, arg2): return arg1/arg2 def error(self): raise ValueError('a valueerror! Great!') def error2(self): return ValueError('a valueerror! Great!') def othererr(self): raise RuntimeError('a runtime error!') def complexerror(self): x=Foo() x.crash() def unserializable(self): raise UnserializableError("this error can't be serialized") class Foo(object): def crash(self): self.crash2('going down...') def crash2(self, arg): # this statement will crash on purpose: x=arg//2 Pyro4-4.23/examples/exceptions/server.py000066400000000000000000000002251227003673200203370ustar00rootroot00000000000000import Pyro4 import excep Pyro4.Daemon.serveSimple( { excep.TestClass(): "example.exceptions" }, ns=True, verbose=True) Pyro4-4.23/examples/flame/000077500000000000000000000000001227003673200153635ustar00rootroot00000000000000Pyro4-4.23/examples/flame/Readme.txt000066400000000000000000000022631227003673200173240ustar00rootroot00000000000000Pyro Flame example. Flame = "foreign location automatic module exposer" Without actually writing any code on the server you can still write clients that access modules and other things on the server. You'll have to start a Pyro Flame server before running the client. Set the correct configuration (see below) and run the following command: python -m Pyro4.utils.flameserver Security (explicitly enable Flame, pickle serializer ---------------------------------------------------- By default, Flame is switched off; the feature cannot be used. This is because it has severe security implications. If you want to use Flame, you have to explicitly enable it in the server's configuration (FLAME_ENABLED config item). Also, because a lot of custom classes are passed over the network, flame requires the pickle serializer (SERIALIZER config item). When launching the server via the above utility command, this is taken care of automatically. If you write your own server and client, remember to configure this correctly yourself. For this example, setting the environment variable: PYRO_FLAME_ENABLED=true before launching the flame server is enough to make it work. Pyro4-4.23/examples/flame/client.py000066400000000000000000000027351227003673200172220ustar00rootroot00000000000000from __future__ import print_function import sys import Pyro4.utils.flame if sys.version_info<(3,0): input=raw_input Pyro4.config.SERIALIZER = "pickle" # flame requires pickle serializer print("Start a Pyro Flame server somewhere.") location = input("what is the location of the flame server, hostname:portnumber? ") print() # connect! flame = Pyro4.utils.flame.connect(location) # basic stuff socketmodule = flame.module("socket") osmodule = flame.module("os") print("remote host name=", socketmodule.gethostname()) print("remote server current directory=", osmodule.getcwd()) flame.execute("import math") root = flame.evaluate("math.sqrt(500)") print("calculated square root=", root) try: print("remote exceptions also work...", flame.evaluate("1//0")) except ZeroDivisionError: print("(caught ZeroDivisionError)") # print something to the remote server output flame.builtin("print")("Hello there, remote server stdout!") # upload a module source and call a function, on the server, in this new module modulesource = open("stuff.py").read() flame.sendmodule("flameexample.stuff", modulesource) result = flame.module("flameexample.stuff").doSomething("hello", 42) print("\nresult from uploaded module:", result) # remote console with flame.console() as console: print("\nStarting a remote console. Enter some commands to execute remotely. End the console as usual.") console.interact() print("Console session ended.") Pyro4-4.23/examples/flame/stuff.py000066400000000000000000000005131227003673200170630ustar00rootroot00000000000000# this module will be sent to the server as 'flameexample.stuff' from __future__ import print_function def doSomething(name, number): print("This text is printed from a module whose code was uploaded by the client:") print(" Hello, my name is {0} and my number is {1}.".format(name,number)) return 999 Pyro4-4.23/examples/futures/000077500000000000000000000000001227003673200157745ustar00rootroot00000000000000Pyro4-4.23/examples/futures/Readme.txt000066400000000000000000000004111227003673200177260ustar00rootroot00000000000000This is an example that shows the asynchronous function call support for normal Python functions. This is just a little extra that Pyro provides, that also works for normal Python code. It looks similar to the async proxy support from the `async` example. Pyro4-4.23/examples/futures/futures.py000066400000000000000000000015361227003673200200500ustar00rootroot00000000000000from __future__ import print_function import Pyro4 def myfunction(a, b, extra=None): print(">>> myfunction called with: a={0}, b={1}, extra={2}".format(a, b, extra)) return a+b print("\n* just a single future call:") future = Pyro4.Future(myfunction) result = future(5,6) # we can do stuff here in the meantime... print("result value=", result.value) assert result.value==11 print("\n* several calls chained:") future = Pyro4.Future(myfunction) future.then(myfunction, 10) future.then(myfunction, 20, extra="something") # the callables will be invoked like so: function(asyncvalue, normalarg, kwarg=..., kwarg=...) # (the value from the previous call is passed as the first argument to the next call) result = future(5, 6) # we can do stuff here in the meantime... print("result value=", result.value) assert result.value==41 Pyro4-4.23/examples/gui_eventloop/000077500000000000000000000000001227003673200171565ustar00rootroot00000000000000Pyro4-4.23/examples/gui_eventloop/Readme.txt000066400000000000000000000022021227003673200211100ustar00rootroot00000000000000This example shows two ways of embedding Pyro's event loop in another application, in this case a GUI application (written using Tkinter). There's one application where a background thread is used for the Pyro daemon. This means you can't directly update the GUI from the Pyro objects (because GUI update calls need to be performed from the GUI mainloop thread). So the threaded gui server submits the gui update calls via a Queue to the actual gui thread. There is a nice thing however, the GUI won't freeze up if a Pyro method call takes a while to execute. The other application doesn't use any threads besides the normal GUI thread. It uses a Tkinter-callback to check Pyro's sockets at a fast interval rate to see if it should dispatch any events to the daemon. Not using threads means you can directly update the GUI from Pyro calls but it also means the GUI will freeze if a Pyro method call takes a while. You also can't use Pyro's requestloop anymore, as it will lock up the GUI while it waits for incoming calls. You'll need to check yourself, using select() on the Pyro socket(s) and dispatching to the daemon manually. Pyro4-4.23/examples/gui_eventloop/client.py000066400000000000000000000011171227003673200210060ustar00rootroot00000000000000from __future__ import print_function import sys import time import Pyro4 print("First make sure one of the gui servers is running.") print("Enter the object uri that was printed:") if sys.version_info<(3,0): uri=raw_input() else: uri=input() uri=uri.strip() guiserver=Pyro4.Proxy(uri) guiserver.message("Hello there!") time.sleep(0.5) guiserver.message("How's it going?") time.sleep(2) for i in range(20): guiserver.message("Counting {0}".format(i)) guiserver.message("now calling the sleep method with 5 seconds") guiserver.sleep(5) print("done!") Pyro4-4.23/examples/gui_eventloop/gui_nothreads.py000066400000000000000000000130031227003673200223600ustar00rootroot00000000000000""" This example shows a Tkinter GUI application that uses event loop callbacks to integrate Pyro's event loop into the Tkinter GUI mainloop. No threads are used. The Pyro event callback is called every so often to check if there are Pyro events to handle, and handles them synchronously. """ from __future__ import with_statement import time import select import Pyro4 try: from tkinter import * import tkinter.simpledialog as simpledialog except ImportError: from Tkinter import * import tkSimpleDialog as simpledialog # Set the Pyro servertype to the multiplexing select-based server that doesn't # use a threadpool to service method calls. This way the method calls are # handled inside the main thread as well. Pyro4.config.SERVERTYPE="multiplex" # The frequency with which the GUI loop calls the Pyro event handler. PYRO_EVENTLOOP_HZ = 50 class PyroGUI(object): """ The Tkinter GUI application that also listens for Pyro calls. """ def __init__(self): self.tk=Tk() self.tk.wm_title("Pyro in a Tkinter GUI eventloop - without threads") self.tk.wm_geometry("500x500") buttonframe=Frame(self.tk) button=Button(buttonframe, text="Messagebox", command=self.button_msgbox_clicked) button.pack(side=LEFT) button=Button(buttonframe, text="Add some text", command=self.button_text_clicked) button.pack(side=LEFT) button=Button(buttonframe, text="Clear all text", command=self.button_clear_clicked) button.pack(side=LEFT) quitbutton=Button(buttonframe, text="Quit", command=self.tk.quit) quitbutton.pack(side=RIGHT) frame=Frame(self.tk, padx=2, pady=2) buttonframe.pack(fill=X) rlabel=Label(frame, text="Pyro server messages:") rlabel.pack(fill=X) self.msg=Message(frame, anchor=NW, width=500, aspect=80, background="white", relief="sunken") self.msg.pack(fill=BOTH, expand=1) frame.pack(fill=BOTH) self.serveroutput=[] def install_pyro_event_callback(self, daemon): """ Add a callback to the tkinter event loop that is invoked every so often. The callback checks the Pyro sockets for activity and dispatches to the daemon's event process method if needed. """ def pyro_event(): while True: # for as long as the pyro socket triggers, dispatch events s,_,_ = select.select(daemon.sockets,[],[],0.01) if s: daemon.events(s) else: # no more events, stop the loop, we'll get called again soon anyway break self.tk.after(1000//PYRO_EVENTLOOP_HZ, pyro_event) self.tk.after(1000//PYRO_EVENTLOOP_HZ, pyro_event) def mainloop(self): self.tk.mainloop() def button_msgbox_clicked(self): # this button event handler is here only to show that gui events are still processed normally number=simpledialog.askinteger("A normal popup","Hi there enter a number",parent=self.tk) def button_clear_clicked(self): self.serveroutput=[] self.msg.config(text="") def button_text_clicked(self): # add some random text to the message list self.add_message("The quick brown fox jumps over the lazy dog!") def add_message(self, message): message="[{0}] {1}".format(time.strftime("%X"), message) self.serveroutput.append(message) self.serveroutput=self.serveroutput[-27:] self.msg.config(text="\n".join(self.serveroutput)) class MessagePrinter(object): """ The Pyro object that interfaces with the GUI application. """ def __init__(self, gui): self.gui=gui def message(self, messagetext): # Add the message to the screen. # Note that you can't do anything that requires gui interaction # (such as popping a dialog box asking for user input), # because the gui (tkinter) is busy processing this pyro call. # It can't do two things at the same time when embedded this way. # If you do something in this method call that takes a long time # to process, the GUI is frozen during that time (because no GUI update # events are handled while this callback is active). self.gui.add_message("from Pyro: "+messagetext) def sleep(self, duration): # Note that you can't perform blocking stuff at all because the method # call is running in the gui mainloop thread and will freeze the GUI. # Try it - you will see the first message but everything locks up until # the sleep returns and the method call ends self.gui.add_message("from Pyro: sleeping {0} seconds...".format(duration)) self.gui.tk.update() time.sleep(duration) self.gui.add_message("from Pyro: woke up!") def main(): gui=PyroGUI() # create a pyro daemon with object daemon=Pyro4.Daemon() obj=MessagePrinter(gui) uri=daemon.register(obj,"pyrogui.message") gui.add_message("Pyro server started. Not using threads.") gui.add_message("Use the command line client to send messages.") urimsg="Pyro object uri = {0}".format(uri) gui.add_message(urimsg) print(urimsg) # add a Pyro event callback to the gui's mainloop gui.install_pyro_event_callback(daemon) # enter the mainloop gui.mainloop() if __name__=="__main__": main() Pyro4-4.23/examples/gui_eventloop/gui_threads.py000066400000000000000000000143371227003673200220360ustar00rootroot00000000000000""" This example shows a Tkinter GUI application that uses a worker thread to run Pyro's event loop. Usually, the GUI toolkit requires that GUI operations are done from within the GUI thread. So, if Pyro interfaces with the GUI, it cannot do that directly because the method calls are done from a different thread. This means we need a layer between them, this example uses a Queue to submit GUI operations to Tkinter's main loop. For this example, the mainloop runs a callback function every so often to check for new work in that Queue and will process it if the Pyro worker thread has put something in it. """ from __future__ import with_statement import time try: import queue except ImportError: import Queue as queue import Pyro4 import Pyro4.threadutil try: from tkinter import * import tkinter.simpledialog as simpledialog except ImportError: from Tkinter import * import tkSimpleDialog as simpledialog # The frequency with which the GUI mainloop checks for work in the Pyro queue. PYRO_QUEUE_HZ = 50 class PyroGUI(object): """ The Tkinter GUI application that also listens for Pyro calls. """ def __init__(self): self.pyro_queue=queue.Queue() self.tk=Tk() self.tk.wm_title("Pyro in a Tkinter GUI eventloop - with threads") self.tk.wm_geometry("500x500") buttonframe=Frame(self.tk) button=Button(buttonframe, text="Messagebox", command=self.button_msgbox_clicked) button.pack(side=LEFT) button=Button(buttonframe, text="Add some text", command=self.button_text_clicked) button.pack(side=LEFT) button=Button(buttonframe, text="Clear all text", command=self.button_clear_clicked) button.pack(side=LEFT) quitbutton=Button(buttonframe, text="Quit", command=self.tk.quit) quitbutton.pack(side=RIGHT) frame=Frame(self.tk, padx=2, pady=2) buttonframe.pack(fill=X) rlabel=Label(frame, text="Pyro server messages:") rlabel.pack(fill=X) self.msg=Message(frame, anchor=NW, width=500, aspect=80, background="white", relief="sunken") self.msg.pack(fill=BOTH, expand=1) frame.pack(fill=BOTH) self.serveroutput=[] def install_pyro_queue_callback(self): """ Add a callback to the tkinter event loop that is invoked every so often. The callback checks the Pyro work queue for work and processes it. """ def check_pyro_queue(): try: while True: # get a work item from the queue (until it is empty) workitem=self.pyro_queue.get_nowait() # execute it in the gui's mainloop thread workitem["callable"](*workitem["vargs"], **workitem["kwargs"]) except queue.Empty: pass self.tk.after(1000//PYRO_QUEUE_HZ, check_pyro_queue) self.tk.after(1000//PYRO_QUEUE_HZ, check_pyro_queue) def mainloop(self): self.tk.mainloop() def button_msgbox_clicked(self): # this button event handler is here only to show that gui events are still processed normally number=simpledialog.askinteger("A normal popup","Hi there enter a number",parent=self.tk) def button_clear_clicked(self): self.serveroutput=[] self.msg.config(text="") def button_text_clicked(self): # add some random text to the message list self.add_message("The quick brown fox jumps over the lazy dog!") def add_message(self, message): message="[{0}] {1}".format(time.strftime("%X"), message) self.serveroutput.append(message) self.serveroutput=self.serveroutput[-27:] self.msg.config(text="\n".join(self.serveroutput)) class MessagePrinter(object): """ The Pyro object that interfaces with the GUI application. It uses a Queue to transfer GUI update calls to Tkinter's mainloop. """ def __init__(self, gui): self.gui=gui def message(self, messagetext): # put a gui-update work item in the queue self.gui.pyro_queue.put( { "callable":self.gui.add_message, "vargs": ("from Pyro: "+messagetext,), "kwargs": {} } ) def sleep(self, duration): # Note that you *can* perform blocking stuff now because the method # call is running in its own thread. It won't freeze the GUI anymore. # However you cannot do anything that requires GUI interaction because # that needs to go through the queue so the mainloop can pick that up. # (opening a dialog from this worker thread will still freeze the GUI) # But a simple sleep() call works fine and the GUI stays responsive. self.gui.pyro_queue.put( { "callable":self.gui.add_message, "vargs": ("from Pyro: sleeping {0} seconds...".format(duration),), "kwargs": {} } ) time.sleep(duration) self.gui.pyro_queue.put( { "callable":self.gui.add_message, "vargs": ("from Pyro: woke up!",), "kwargs": {} } ) class PyroDaemon(Pyro4.threadutil.Thread): def __init__(self, gui): Pyro4.threadutil.Thread.__init__(self) self.gui=gui self.started=Pyro4.threadutil.Event() def run(self): daemon=Pyro4.Daemon() obj=MessagePrinter(self.gui) self.uri=daemon.register(obj,"pyrogui.message2") self.started.set() daemon.requestLoop() def main(): gui=PyroGUI() # create a pyro daemon with object, running in its own worker thread pyro_thread=PyroDaemon(gui) pyro_thread.setDaemon(True) pyro_thread.start() pyro_thread.started.wait() gui.add_message("Pyro server started. Using Pyro worker thread.") gui.add_message("Use the command line client to send messages.") urimsg="Pyro object uri = {0}".format(pyro_thread.uri) gui.add_message(urimsg) print(urimsg) # add a Pyro event callback to the gui's mainloop gui.install_pyro_queue_callback() # enter the mainloop gui.mainloop() if __name__=="__main__": main() Pyro4-4.23/examples/hugetransfer/000077500000000000000000000000001227003673200167745ustar00rootroot00000000000000Pyro4-4.23/examples/hugetransfer/Readme.txt000066400000000000000000000024001227003673200207260ustar00rootroot00000000000000This test transfers huge data structures to see how Pyro handles those. It sets a socket timeout as well to see how Pyro handles that. A couple of problems could be exposed by this test: - Some systems don't really seem to like non blocking sockets and large data transfers. For instance Mac OS X seems eager to cause EAGAIN errors when your data exceeds 'the devils number' number of bytes. Note that this problem only occurs when using specific socket code. Pyro contains a workaround. More info: http://old.nabble.com/The-Devil%27s-Number-td9169165.html http://www.cherrypy.org/ticket/598 - Other systems seemed to have problems receiving large chunks of data. Windows causes memory errors when the receive buffer is too large. Pyro's receive loop works with comfortable smaller data chunks, to avoid these kind of problems. Performance numbers with the various serializers on my local network: serializer | performance (string) | performance (bytes) -----------+--------------------------------------------- pickle | 33260 kb/sec | 33450 kb/sec marshal | 27900 kb/sec | 32300 kb/sec json | 23856 kb/sec | not supported serpent | 13358 kb/sec | 9066 kb/sec Pyro4-4.23/examples/hugetransfer/client.py000066400000000000000000000023101227003673200206200ustar00rootroot00000000000000from __future__ import print_function import sys, time import warnings import Pyro4 warnings.filterwarnings("ignore") #Pyro4.config.COMMTIMEOUT=2 print("Enter the server's uri that was printed:") if sys.version_info<(3,0): uri=raw_input() else: uri=input() uri=uri.strip() datasize = 5*1024*1024 # 5 mb def do_test(data): assert len(data)==datasize totalsize = 0 obj=Pyro4.core.Proxy(uri) obj._pyroBind() begin = time.time() for i in range(10): print("transferring %d bytes" % datasize) size = obj.transfer(data) assert size==datasize totalsize += datasize duration = time.time()-begin totalsize = float(totalsize) print("It took %.2f seconds to transfer %d mb." % (duration, totalsize/1024/1024)) print("That is %.0f kb/sec. = %.1f mb/sec. (serializer: %s)" % (totalsize/1024/duration, totalsize/1024/1024/duration, Pyro4.config.SERIALIZER)) data = 'x'*datasize print("\n\n----test with string data----") do_test(data) print("\n\n----test with byte data----") data = b'x'*datasize do_test(data) data = bytearray(b'x'*datasize) print("\n\n----test with bytearray data----") do_test(data) Pyro4-4.23/examples/hugetransfer/server.py000066400000000000000000000012321227003673200206520ustar00rootroot00000000000000from __future__ import print_function import base64 import Pyro4 import Pyro4.socketutil #Pyro4.config.COMMTIMEOUT=2 class Testclass(object): def transfer(self, data): if Pyro4.config.SERIALIZER=="serpent" and type(data) is dict: # decode serpent base-64 encoded bytes assert data["encoding"]=="base64" data = base64.b64decode(data["data"]) print("received %d bytes" % len(data)) return len(data) Pyro4.Daemon.serveSimple({ Testclass(): "example.hugetransfer" }, host=Pyro4.socketutil.getIpAddress("localhost", workaround127=True), ns=False, verbose=True) Pyro4-4.23/examples/itunes/000077500000000000000000000000001227003673200156065ustar00rootroot00000000000000Pyro4-4.23/examples/itunes/Readme.txt000066400000000000000000000005301227003673200175420ustar00rootroot00000000000000A simple iTunes remote controller tool. (only works on Mac OS X) Run 'itunescontroller' on the mac with the iTunes that you want to control. It only works on mac os because it uses the osascript utility to manipulate iTunes via a few applescript commands. Run the 'remote' on any other host. It does a few remote control operations. Pyro4-4.23/examples/itunes/itunescontroller.py000066400000000000000000000035151227003673200215770ustar00rootroot00000000000000from __future__ import print_function import subprocess import Pyro4 import socket # You can get a lot more info about scripting iTunes here: # http://dougscripts.com/itunes/ class ITunes(object): def __init__(self): # start itunes subprocess.call(["osascript", "-e", "tell application \"iTunes\" to player state"]) def play(self): # continue play subprocess.call(["osascript", "-e", "tell application \"iTunes\" to play"]) def pause(self): # pause play subprocess.call(["osascript", "-e", "tell application \"iTunes\" to pause"]) def stop(self): # stop playing subprocess.call(["osascript", "-e", "tell application \"iTunes\" to stop"]) def next(self): # next song in list subprocess.call(["osascript", "-e", "tell application \"iTunes\" to next track"]) def previous(self): # previous song in list subprocess.call(["osascript", "-e", "tell application \"iTunes\" to previous track"]) def playlist(self, listname): # start playling a defined play list subprocess.call(["osascript", "-e", "tell application \"iTunes\" to play playlist \"{0}\"".format(listname)]) def currentsong(self): # return title and artist of current song return subprocess.check_output(["osascript", "-e", "tell application \"iTunes\"", "-e", "set thisTitle to name of current track", "-e", "set thisArtist to artist of current track", "-e", "set output to thisTitle & \" - \" & thisArtist", "-e", "end tell"]).strip() print("starting...") itunes=ITunes() daemon=Pyro4.Daemon(host=socket.gethostname(), port=39001) uri=daemon.register(itunes,"itunescontroller") print("iTunes controller started, uri =",uri) daemon.requestLoop() Pyro4-4.23/examples/itunes/remote.py000066400000000000000000000010261227003673200174520ustar00rootroot00000000000000from __future__ import print_function import Pyro4 import sys import time if sys.version_info<(3,0): input=raw_input host=input("enter the hostname of the itunescontroller: ") itunes=Pyro4.Proxy("PYRO:itunescontroller@{0}:39001".format(host)) print("setting Playlist 'Music'...") itunes.playlist("Music") itunes.play() print("Current song:", itunes.currentsong()) time.sleep(6) print("next song...") itunes.next() print("Current song:", itunes.currentsong()) time.sleep(6) print("stop.") itunes.stop() Pyro4-4.23/examples/maxsize/000077500000000000000000000000001227003673200157575ustar00rootroot00000000000000Pyro4-4.23/examples/maxsize/Readme.txt000077500000000000000000000011471227003673200177230ustar00rootroot00000000000000Shows how the MAX_MESSAGE_SIZE config item works. The client sends a big message first without a limit, then with a limit set on the message size. The second attempt will fail with a protocol error. The client talks to the echo server so you'll have to start the echo server first in another window: $ python -m Pyro4.test.echoserver You can try to set the PYRO_MAX_MESSAGE_SIZE environment variable to a small value (such as 2000) before starting the echo server, to see how it deals with receiving messages that are too large on the server. (Pyro will log an error and close the connection). Pyro4-4.23/examples/maxsize/client.py000066400000000000000000000017171227003673200176150ustar00rootroot00000000000000from __future__ import print_function import sys import Pyro4 import Pyro4.errors huge_object = [42]*10000 simple_object = {"message": "hello", "irrelevant": huge_object} print("First start the built-in test echo server with something like:") print("$ python -m Pyro4.test.echoserver") print("Enter the server's uri that was printed:") if sys.version_info<(3,0): uri=raw_input() else: uri=input() uri=uri.strip() echoserver=Pyro4.Proxy(uri) Pyro4.config.MAX_MESSAGE_SIZE = 0 print("\nSending big data with no limit on message size...") response = echoserver.echo(simple_object) print("success.") try: Pyro4.config.MAX_MESSAGE_SIZE = 2500 print("\nSending big data with a limit on message size...") response = echoserver.echo(simple_object) print("Hmm, this should have raised an exception") except Pyro4.errors.ProtocolError: ex_t, ex_v, ex_tb = sys.exc_info() print("EXCEPTION (expected):", ex_t, ex_v) Pyro4-4.23/examples/nameserverstress/000077500000000000000000000000001227003673200177125ustar00rootroot00000000000000Pyro4-4.23/examples/nameserverstress/Readme.txt000066400000000000000000000002501227003673200216450ustar00rootroot00000000000000This example contains a stress test for the Naming Server. It creates a bunch of threads that connect to the NS and create/delete registrations randomly, very fast. Pyro4-4.23/examples/nameserverstress/stress.py000066400000000000000000000041441227003673200216120ustar00rootroot00000000000000from __future__ import print_function import sys import random, time import Pyro4 from Pyro4 import threadutil def randomname(): def partname(): return str(random.random())[-2:] parts=["stresstest"] for i in range(random.randint(1,10)): parts.append(partname()) return ".".join(parts) class NamingTrasher(threadutil.Thread): def __init__(self,nsuri,number): threadutil.Thread.__init__(self) self.daemon=True self.number=number self.ns=Pyro4.core.Proxy(nsuri) self.mustStop=False def list(self): items=self.ns.list() def register(self): for i in range(4): try: self.ns.register(randomname(),'PYRO:objname@host:555') except Pyro4.errors.NamingError: pass def remove(self): self.ns.remove(randomname()) def lookup(self): try: uri=self.ns.lookup(randomname()) except Pyro4.errors.NamingError: pass def listprefix(self): entries=self.ns.list(prefix="stresstest.51") def listregex(self): entries=self.ns.list(regex=r"stresstest\.??\.41.*") def run(self): print("Name Server trasher running.") while not self.mustStop: random.choice((self.list, self.register, self.remove, self.lookup, self.listregex, self.listprefix))() sys.stdout.write("%d " % self.number) sys.stdout.flush() time.sleep(0.001) print("Trasher exiting.") def main(): threads=[] ns=Pyro4.naming.locateNS() ns.remove(prefix="stresstest.") for i in range(5): nt=NamingTrasher(ns._pyroUri,i) nt.start() threads.append(nt) try: while True: time.sleep(1) except KeyboardInterrupt: pass print("Break-- waiting for threads to stop.") for nt in threads: nt.mustStop=True nt.join() count=ns.remove(prefix="stresstest.") print("cleaned up %d names." % count) if __name__=='__main__': main() Pyro4-4.23/examples/nonameserver/000077500000000000000000000000001227003673200170035ustar00rootroot00000000000000Pyro4-4.23/examples/nonameserver/Readme.txt000066400000000000000000000004301227003673200207360ustar00rootroot00000000000000This example shows a way to use Pyro without a Name server. Look at the simplicity of the client. The only thing you need to figure out is how to get the correct URI in the client. This example just lets you enter it on the console. You can copy it from the server's output. Pyro4-4.23/examples/nonameserver/client.py000066400000000000000000000005361227003673200206370ustar00rootroot00000000000000# Client that doesn't use the Name Server. Uses URI directly. from __future__ import print_function import sys import Pyro4 if sys.version_info<(3,0): input=raw_input uri = input("Enter the URI of the quote object: ") quotegen=Pyro4.core.Proxy(uri) print("Getting some quotes...") print(quotegen.quote()) print(quotegen.quote()) Pyro4-4.23/examples/nonameserver/server.py000066400000000000000000000015321227003673200206640ustar00rootroot00000000000000# The server that doesn't use the Name Server. from __future__ import print_function import os import Pyro4 class QuoteGen(object): def quote(self): try: quote=os.popen('fortune').read() if len(quote)>0: return quote return "This system cannot provide you a good fortune, install 'fortune'" except: return "This system knows no witty quotes :-(" daemon = Pyro4.core.Daemon() quote1 = QuoteGen() quote2 = QuoteGen() uri1=daemon.register(quote1) # let Pyro create a unique name for this one uri2=daemon.register(quote2, "example.quotegen") # provide a logical name ourselves print("QuoteGen is ready, not using the Name Server.") print("You can use the following two URIs to connect to me:") print(uri1) print(uri2) daemon.requestLoop() Pyro4-4.23/examples/oneway/000077500000000000000000000000001227003673200156015ustar00rootroot00000000000000Pyro4-4.23/examples/oneway/Readme.txt000066400000000000000000000016611227003673200175430ustar00rootroot00000000000000Shows the use of 'oneway' method calls. If you flag a method call 'oneway', Pyro will not wait for a response from the remote object. This means that your client program can continue to work, while the remote object is still busy processing the method call. (Normal remote method calls are synchronous and will always block until the remote object is done with the method call). This example also shows the use of the ONEWAY_THREADED setting in the server. This setting is on by default. It means that oneway method calls are executed in their own separate thread, so the server remains responsive for additional calls from the same client even when the oneway call is still running. If you set this to False, the server will process all calls from the same proxy sequentially (and additional calls will have to wait). Note that a different proxy will still be able to execute calls regardless of the setting of ONEWAY_THREADED. Pyro4-4.23/examples/oneway/client.py000066400000000000000000000016071227003673200174350ustar00rootroot00000000000000from __future__ import print_function import time import Pyro4 serv = Pyro4.core.Proxy("PYRONAME:example.oneway") serv._pyroOneway.add("start") serv._pyroOneway.add("nothing") serv._pyroOneway.add("nonexisting") print("starting server using a oneway call") serv.start(6) print("doing some more oneway calls inbetween") serv.nothing() serv.nothing() serv.nothing() print("calling a non existing method, but since it is flagged oneway, we won't find out") serv.nonexisting() time.sleep(2) print("\nNow contacting the server to see if it's done.") print("we are faster, so you should see a few attempts,") print("until the server is finished.") while True: print("server done?") if serv.ready(): print("yes!") break else: print("no, trying again") time.sleep(1) print("getting the result from the server: %s" % serv.result()) Pyro4-4.23/examples/oneway/server.py000066400000000000000000000016621227003673200174660ustar00rootroot00000000000000from __future__ import print_function import time import Pyro4 # set the oneway behavior to run inside a new thread, otherwise the client stalls. # this is the default, but I've added it here just for clarification. Pyro4.config.ONEWAY_THREADED=True class Server(object): def __init__(self): self.busy=False def start(self, duration): print("start request received. Starting work...") self.busy=True for i in range(duration): time.sleep(1) print(duration-i) print("work is done!") self.busy=False def ready(self): print("ready status requested (%r)" % (not self.busy)) return not self.busy def result(self): return "The result :)" def nothing(self): print("nothing got called, doing nothing") ######## main program Pyro4.Daemon.serveSimple({ Server(): "example.oneway" }) Pyro4-4.23/examples/proxysharing/000077500000000000000000000000001227003673200170345ustar00rootroot00000000000000Pyro4-4.23/examples/proxysharing/Readme.txt000066400000000000000000000006641227003673200210000ustar00rootroot00000000000000This example shows how Pyro deals with sharing proxies in different threads. Due to internal locking you can freely share proxies among threads. The lock makes sure that only a single thread is actually using the proxy's communication channel at all times. This can be convenient BUT it may not be the best way. The lock essentially prevents parallelism. If you want calls to go in parallel, give each thread their own proxy. Pyro4-4.23/examples/proxysharing/client.py000066400000000000000000000061401227003673200206650ustar00rootroot00000000000000from __future__ import print_function import sys import time import Pyro4 from Pyro4 import threadutil if sys.version_info<(3,0): current_thread=threadutil.currentThread else: current_thread=threadutil.current_thread stop=False def myThread(nsproxy, proxy): global stop name=current_thread().getName() try: while not stop: result=nsproxy.list(prefix="example.") result=proxy.method("the quick brown fox jumps over the lazy dog") except Exception: x=sys.exc_info()[1] print("**** Exception in thread %s: {%s} %s" % (name, type(x), x)) nsproxy = Pyro4.naming.locateNS() proxy = Pyro4.core.Proxy("PYRONAME:example.proxysharing") # now create a handful of threads and give each of them the same two proxy objects threads = [] for i in range(5): thread=threadutil.Thread(target=myThread, args=(nsproxy, proxy)) # thread.setDaemon(True) thread.setDaemon(False) threads.append(thread) thread.start() print("Running a bunch of threads for 5 seconds.") print("They're hammering the name server and the test server using the same proxy.") print("You should not see any exceptions.") time.sleep(5) stop=True for thread in threads: thread.join() print("Done.") print("\nNow showing why proxy sharing might not be a good idea for parallelism.") print("Starting 10 threads with the same proxy that all call the work() method.") def myThread2(proxy): global stop while not stop: proxy.work() stop=False proxy.reset_work() threads = [] for i in range(10): thread=threadutil.Thread(target=myThread2, args=[proxy]) thread.setDaemon(False) threads.append(thread) thread.start() print("waiting 5 seconds") start=time.time() time.sleep(5) print("waiting until threads have stopped...") stop=True for thread in threads: thread.join() duration=int(time.time()-start) print("--> time until everything completed: %.2f" % duration) print("--> work done on the server: %d" % proxy.get_work_done()) print("you can see that the 10 threads are waiting for each other to complete,") print("and that not a lot of work has been done on the server.") print("\nDoing the same again but every thread now has its own proxy.") print("Starting 10 threads with different proxies that all call the work() method.") proxy.reset_work() stop=False threads = [] for i in range(10): proxy=Pyro4.core.Proxy(proxy._pyroUri) # create a new proxy thread=threadutil.Thread(target=myThread2, args=[proxy]) thread.setDaemon(False) threads.append(thread) thread.start() print("waiting 5 seconds") start=time.time() time.sleep(5) print("waiting until threads have stopped...") stop=True for thread in threads: thread.join() duration=int(time.time()-start) print("--> time until everything completed: %.2f" % duration) print("--> work done on the server: %d" % proxy.get_work_done()) print("you can see that this time the 10 threads didn't have to wait for each other,") print("and that they got a lot more work done because they really ran in parallel.") Pyro4-4.23/examples/proxysharing/server.py000066400000000000000000000010231227003673200207100ustar00rootroot00000000000000from __future__ import print_function import time import Pyro4 class RemoteObject(object): def __init__(self): self.amount=0 def method(self, arg): return " ~~this is the remote result~~ " def work(self): print("work... %d" %self.amount) time.sleep(0.5) self.amount+=1 def reset_work(self): self.amount=0 def get_work_done(self): return self.amount Pyro4.Daemon.serveSimple({ RemoteObject(): "example.proxysharing" }) Pyro4-4.23/examples/robots/000077500000000000000000000000001227003673200156075ustar00rootroot00000000000000Pyro4-4.23/examples/robots/Readme.txt000066400000000000000000000035531227003673200175530ustar00rootroot00000000000000This is an example that more or less presents an online multiplayer game. The game is a robot destruction derby. It is played on a grid. There are some obstructing walls on the grid that hurt when you collide into them. If you collide into another robot, the other robot takes damage. All robots start with a certain amount of health. If it reaches zero, the robot dies. The last man standing wins! Before starting the gameserver, you need to start a nameserver, if you want to connect remotely to the game server! If you don't have a nameserver running, you can still launch the gameserver but you won't be able to connect to it with the Pyro clients. (make sure you launch the name server with SERIALIZERS_ACCEPTED=pickle) You can click a button to add a couple of robots that are controlled by the server itself. But it is more interesting to actually connect remote robots to the server! Use client.py for that (provide a name and a robot type). The client supports a few robot types that have different behaviors. The robot behavior is controlled by the client! The server only handles game mechanics. In the game server, the Pyro calls are handled by a daemon thread. The GUI updates are done by Tkinter using after() calls. The most interesting parts of this example are perhaps these: - server uses identical code to work with local and remote robots (it did require a few minor tweaks to work around serialization requirements) - Pyro used together with an interactive GUI application (Tkinter) - game state handled by the server, influenced by the clients (robot behavior) - this example makes use of Pyro's AutoProxy feature. Registering observers and getting a robot object back is done via proxies automatically because those are Pyro objects. Because we are using some custom classes, this example requires the pickle serializer. Pyro4-4.23/examples/robots/client.py000066400000000000000000000051131227003673200174370ustar00rootroot00000000000000from __future__ import with_statement import random import sys import remote import Pyro4 # because we're using some custom classes over the wire, we need to use pickle Pyro4.config.SERIALIZERS_ACCEPTED.add('pickle') Pyro4.config.SERIALIZER='pickle' class DrunkenGameObserver(remote.GameObserver): def world_update(self, iteration, world, robotdata): # change directions randomly if random.random()>0.8: if random.random()>=0.5: dx,dy=random.randint(-1,1),0 else: dx,dy=0,random.randint(-1,1) if random.random()>0.7: self.robot.emote("..Hic! *burp*") self.robot.change_direction((dx,dy)) class AngryGameObserver(remote.GameObserver): def __init__(self): super(AngryGameObserver,self).__init__() self.directions=[(1,0), (0,1), (-1,0), (0,-1)] # clockwise motion self.directioncounter=0 def world_update(self, iteration, world, robotdata): # move in a loop yelling angry stuff if iteration % 50 == 0: self.robot.emote("I'll kill you all! GRR") if iteration % 10 ==0: self.directioncounter=(self.directioncounter+1)%4 self.robot.change_direction(self.directions[self.directioncounter]) class ScaredGameObserver(remote.GameObserver): def __init__(self): super(ScaredGameObserver,self).__init__() # run to a corner self.direction=random.choice([(-1,-1),(1,-1),(1,1),(-1,1)]) def start(self): super(ScaredGameObserver,self).start() self.robot.change_direction(self.direction) def world_update(self, iteration, world, robotdata): if iteration%50==0: self.robot.emote("I'm scared!") observers= { "drunk": DrunkenGameObserver, "angry": AngryGameObserver, "scared": ScaredGameObserver, } def main(args): if len(args)!=3: print("usage: client.py ") print(" type is one of: %s" % list(observers.keys())) return name=args[1] observertype=args[2] with Pyro4.Daemon() as daemon: observer=observers[observertype]() daemon.register(observer) gameserver=Pyro4.Proxy("PYRONAME:example.robotserver") robot=gameserver.register(name, observer) robot.emote("Hi there! I'm here to kick your ass") observer.robot=robot print("Pyro server registered on %s" % daemon.locationStr) daemon.requestLoop() if __name__=="__main__": main(sys.argv) Pyro4-4.23/examples/robots/gameserver.py000066400000000000000000000242011227003673200203200ustar00rootroot00000000000000from __future__ import with_statement import random import time import robot import remote try: from tkinter import * except ImportError: from Tkinter import * import Pyro4 from Pyro4 import threadutil # because we're using some custom classes over the wire, we need to use pickle Pyro4.config.SERIALIZERS_ACCEPTED.add('pickle') Pyro4.config.SERIALIZER='pickle' class VisibleRobot(robot.Robot): """represents a robot that is visible on the screen.""" def __init__(self, name, position, direction, grid, color='red'): super(VisibleRobot,self).__init__(name, (grid.width, grid.height), position, direction) self.grid=grid x=self.x*grid.squaresize y=self.y*grid.squaresize self.tkid=grid.create_rectangle(x,y,x+grid.squaresize,y+grid.squaresize,fill=color,outline='black') self.text_tkid=None def popuptext(self, text, sticky=False): if self.text_tkid: self.grid.delete(self.text_tkid) self.text_tkid=self.grid.create_text(self.x*self.grid.squaresize,self.y*self.grid.squaresize,text=text,anchor=CENTER,fill='red') self.text_timer=time.time() if not sticky: self.grid.after(1000, self.__removetext, self.text_tkid) def delete_from_grid(self): self.grid.delete(self.tkid) if self.text_tkid: self.grid.delete(self.text_tkid) def __removetext(self, textid): self.grid.delete(textid) if textid==self.text_tkid: self.text_tkid=None def move(self, world=None): super(VisibleRobot,self).move(world) x=self.x*self.grid.squaresize y=self.y*self.grid.squaresize self.grid.coords(self.tkid, x,y,x+self.grid.squaresize, y+self.grid.squaresize) if self.text_tkid: # also move the popup text self.grid.coords(self.text_tkid, self.x*self.grid.squaresize,self.y*self.grid.squaresize) def died(self, killer, world): self.popuptext("ARGH I died") if killer: killer=killer.serializable() self.observer.death(killer=killer) self.grid.after(800, lambda: self.grid.delete(self.tkid)) def collision(self, other): self.popuptext("Bam!") other.popuptext("ouch") def emote(self,text): self.popuptext(text, False) class RobotGrid(Canvas): def __init__(self, parent, width, height, squaresize=20): self.squaresize=squaresize self.width=width self.height=height pixwidth=width*self.squaresize pixheight=height*self.squaresize Canvas.__init__(self, parent, width=pixwidth, height=pixheight, background='#e0e0e0') self.xview_moveto(0) self.yview_moveto(0) for x in range(width): self.create_line(x*self.squaresize,0,x*self.squaresize,pixheight, fill='#d0d0d0') for y in range(height): self.create_line(0,y*self.squaresize,pixwidth,y*self.squaresize,fill='#d0d0d0') def draw_wall(self, wall, color='navy'): x=wall.x*self.squaresize y=wall.y*self.squaresize self.create_rectangle(x,y,x+self.squaresize,y+self.squaresize,fill=color,outline=color) class GameEngine(object): def __init__(self, gui, world): self.gui=gui self.grid=gui.grid self.world=world self.build_walls() self.gui.buttonhandler=self self.survivor=None self.open_for_signups=True self.iteration=0 def button_clicked(self, button): if button=="add_bot" and self.open_for_signups: for i in range(5): name="local_bot_%d" % self.gui.listbox.size() gameobserver=remote.LocalGameObserver(name) robot=self.signup_robot(name, gameobserver) gameobserver.robot=robot elif button=="start_round": self.open_for_signups=False if self.survivor: self.survivor.delete_from_grid() self.gui.enable_buttons(False) self.start_round() def start_round(self): self.gui.statuslabel.config(text="new round!") print("WORLD:") for line in self.world.dump(): print(line.tostring()) print("NUMBER OF ROBOTS: %d" % len(self.world.robots)) txtid=self.grid.create_text(20,20,text="GO!",font=("Courier",120,"bold"),anchor=NW,fill='purple') self.grid.after(1500,lambda:self.grid.delete(txtid)) self.grid.after(2000,self.update) self.grid.after(2000,self.notify_start) self.iteration=0 def notify_start(self): for robot in self.world.robots: robot.observer.start() def notify_worldupdate(self): self.iteration+=1 for robot in self.world.robots: robotdata=robot.serializable() robot.observer.world_update(self.iteration, self.world, robotdata) def notify_winner(self, winner): winner.observer.victory() def update(self): for robot in self.world.robots: robot.move(self.world) self.notify_worldupdate() self.gui.statuslabel.config(text="survivors: %d" % len(self.world.robots)) if len(self.world.robots)<1: print("[server] No results.") self.round_ends() elif len(self.world.robots)==1: self.survivor=self.world.robots[0] self.world.remove(self.survivor) self.survivor.popuptext("I WIN! HURRAH!", True) print("[server] %s wins!" % self.survivor.name) self.gui.statuslabel.config(text="winner: %s" % self.survivor.name) self.notify_winner(self.survivor) self.round_ends() else: self.gui.tk.after(40, self.update) def round_ends(self): self.gui.listbox.delete(0,END) self.gui.enable_buttons(True) self.open_for_signups=True def build_walls(self): wall_offset=4 wall_size=10 for x in range(wall_size): wall=robot.Wall((x+wall_offset,wall_offset)) self.world.add_wall(wall) self.grid.draw_wall(wall) wall=robot.Wall((x+wall_offset,wall_size+wall_offset+1)) self.world.add_wall(wall) self.grid.draw_wall(wall) wall=robot.Wall((wall_offset,x+wall_offset+1)) self.world.add_wall(wall) self.grid.draw_wall(wall) wall=robot.Wall((wall_size+wall_offset+2,x+wall_offset+1)) self.world.add_wall(wall) self.grid.draw_wall(wall) def signup_robot(self, name, observer=None): if not self.open_for_signups: raise RuntimeError("signups are closed, try again later") for r in self.world.robots: if r.name==name: raise ValueError("that name is already taken") colorint=random.randint(0,0xFFFFFF) color='#%06x' % colorint inversecolor='black' self.gui.listbox.insert(END,name) self.gui.listbox.itemconfig(END,bg=color, fg=inversecolor) while True: x=random.randint(0,self.grid.width-1) y=random.randint(int(self.grid.height*0),self.grid.height-1) if not self.world.collides(x,y): break r=VisibleRobot(name,(x,y),(0,0), self.grid, color=color) self.world.add_robot(r) r.observer=observer observer._pyroOneway.add("world_update") r.popuptext(name) return remote.RemoteBot(r, self) def remove_robot(self, robot): robot.delete_from_grid() self.world.remove(robot) # listnames=list(self.gui.listbox.get(0,END)) # listnames.remove(robot.name) # self.gui.listbox.delete(0,END) # self.gui.listbox.insert(END,*listnames) class GUI(object): def __init__(self, width, height): self.tk=Tk() self.tk.wm_title("bot destruction derby") lframe=Frame(self.tk, borderwidth=3, relief="raised", padx=2, pady=2, background='#808080') self.grid=RobotGrid(lframe, width, height, squaresize=16) rframe=Frame(self.tk, padx=2, pady=2) rlabel=Label(rframe, text="Signups:") rlabel.pack(fill=X) self.listbox=Listbox(rframe, width=15, height=20, font=(None,8)) self.listbox.pack() self.addrobotbutton=Button(rframe, text="Add 5 local bots", command=lambda: self.buttonhandler.button_clicked("add_bot")) self.addrobotbutton.pack() self.startbutton=Button(rframe, text="Start round!", command=lambda: self.buttonhandler.button_clicked("start_round")) self.startbutton.pack() self.statuslabel=Label(rframe, width=20) self.statuslabel.pack(side=BOTTOM) self.grid.pack() lframe.pack(side=LEFT) rframe.pack(side=RIGHT, fill=BOTH) self.buttonhandler=None def enable_buttons(self, enabled=True): if enabled: self.addrobotbutton.config(state=NORMAL) self.startbutton.config(state=NORMAL) else: self.addrobotbutton.config(state=DISABLED) self.startbutton.config(state=DISABLED) class PyroDaemonThread(threadutil.Thread): def __init__(self, engine): threadutil.Thread.__init__(self) self.pyroserver=remote.GameServer(engine) self.pyrodaemon=Pyro4.Daemon() self.ns=Pyro4.locateNS() self.setDaemon(True) def run(self): with self.pyrodaemon: with self.ns: uri=self.pyrodaemon.register(self.pyroserver) self.ns.register("example.robotserver", uri) print("Pyro server registered on %s" % self.pyrodaemon.locationStr) self.pyrodaemon.requestLoop() def main(): width=25 height=25 gui=GUI(width,height) world=robot.World(width, height) engine=GameEngine(gui, world) try: PyroDaemonThread(engine).start() except Pyro4.errors.NamingError: print("Can't find the Pyro Nameserver. Running without remote connections.") gui.tk.mainloop() if __name__=="__main__": main() Pyro4-4.23/examples/robots/remote.py000066400000000000000000000036041227003673200174570ustar00rootroot00000000000000from __future__ import print_function import random import Pyro4 class GameServer(object): def __init__(self, engine): self.engine=engine def register(self, name, observer): robot=self.engine.signup_robot(name, observer) self._pyroDaemon.register(robot) # make the robot a pyro object return robot class RemoteBot(object): def __init__(self, robot, engine): self.robot=robot self.engine=engine def get_data(self): return self.robot.serializable() def change_direction(self, direction): self.robot.dx,self.robot.dy = direction def emote(self, text): self.robot.emote(text) def terminate(self): self.engine.remove_robot(self.robot) class LocalGameObserver(object): def __init__(self, name): self.name=name self.robot=None self._pyroOneway=set() # remote observers have this def world_update(self, iteration, world, robotdata): # change directions randomly if random.random()>0.8: if random.random()>=0.5: dx,dy=random.randint(-1,1),0 else: dx,dy=0,random.randint(-1,1) self.robot.change_direction((dx,dy)) def start(self): self.robot.emote("Here we go!") def victory(self): print("[%s] I WON!!!" % self.name) def death(self, killer): if killer: print("[%s] I DIED (%s did it)" % (self.name, killer.name)) else: print("[%s] I DIED" % self.name) class GameObserver(object): def world_update(self, iteration, world, robotdata): pass def start(self): print("Battle starts!") def victory(self): print("I WON!!!") def death(self, killer): print("I DIED") if killer: print("%s KILLED ME :(" % killer.name) Pyro4-4.23/examples/robots/robot.py000066400000000000000000000072351227003673200173150ustar00rootroot00000000000000from __future__ import print_function import sys import array class Wall(object): """an obstructing static wall""" def __init__(self, position): self.x,self.y = position def serializable(self): return self class Robot(object): """represents a robot moving on a grid.""" def __init__(self, name, grid_dimensions, position, direction=(0,0), strength=5): self.name = name self.x, self.y = position self.dx, self.dy = direction self.gridw, self.gridh = grid_dimensions self.strength = strength def __str__(self): return "ROBOT '%s'; pos(%d,%d); dir(%d,%d); strength %d" %(self.name, self.x, self.y, self.dx, self.dy, self.strength) def serializable(self): if type(self) is Robot: return self else: return Robot(self.name,(self.gridw,self.gridh),(self.x,self.y),(self.dx,self.dy),self.strength) def move(self, world=None): # minmax to avoid moving off the sides x=min(self.gridw-1, max(0,self.x+self.dx)) y=min(self.gridh-1, max(0,self.y+self.dy)) if x==self.x and y==self.y: return if world and self.__process_collision(x,y,world): return self.x,self.y = x,y def __process_collision(self, newx, newy, world): other=world.collides(newx,newy) if not other: return False # we didn't hit anything self.dx,self.dy = 0,0 # come to a standstill when we hit something if isinstance(other,Wall): self.strength-=1 # hit wall, decrease our strength if self.strength<=0: print("[server] %s killed himself!" % self.name) world.remove(self) self.died(None,world) else: other.strength-=1 # hit other robot, decrease other robot's strength self.collision(other) if other.strength<=0: world.remove(other) other.died(self, world) print("[server] %s killed %s!" % (self.name, other.name)) return True def killed(self, victim, world): """you can override this to react on kills""" pass def collision(self, other): """you can override this to react on collisions between bots""" pass def emote(self, text): """you can override this""" print("[server] %s says: '%s'" % (self.name, text)) class World(object): """the world the robots move in (Cartesian grid)""" def __init__(self, width, height): self.width=width self.height=height self.all=[] self.robots=[] def add_wall(self, wall): self.all.append(wall) def add_robot(self, bot): self.all.append(bot) self.robots.append(bot) def collides(self, x, y): for obj in self.all: if obj.x==x and obj.y==y: return obj return None def remove(self, obj): self.all.remove(obj) self.robots.remove(obj) def dump(self): line=' '*self.width if sys.version_info>=(3,0): line=bytes(line, "ASCII") grid=[array.array('b', line) for y in range(self.height)] for obj in self.all: grid[obj.y][obj.x]=ord('R') if isinstance(obj, Robot) else ord('#') return grid def __getstate__(self): all=[o.serializable() for o in self.all] robots=[r.serializable() for r in self.robots] return (self.width,self.height,all,robots) def __setstate__(self, args): self.width,self.height,self.all,self.robots=args Pyro4-4.23/examples/servertypes/000077500000000000000000000000001227003673200166725ustar00rootroot00000000000000Pyro4-4.23/examples/servertypes/Readme.txt000066400000000000000000000007251227003673200206340ustar00rootroot00000000000000Shows the different behaviors of Pyro's server types. First start the server, it will ask what type of server you want to run. The client will print some information about what's happening. Try it with different server types and see how that changes the behavior. You can also try to set ONEWAY_THREADED to False on the server side, to change the behavior of oneway calls. The client will print a message if it detects you have been fiddling with this ;-) Pyro4-4.23/examples/servertypes/client.py000066400000000000000000000063451227003673200205320ustar00rootroot00000000000000from __future__ import with_statement import sys import time import Pyro4 from Pyro4 import threadutil if sys.version_info<(3,0): current_thread=threadutil.currentThread else: current_thread=threadutil.current_thread serv = Pyro4.core.Proxy("PYRONAME:example.servertypes") serv._pyroOneway.add("onewaydelay") print("--------------------------------------------------------------") print(" This part is independent of the type of the server. ") print("--------------------------------------------------------------") print("Calling 5 times oneway method. Should return immediately.") serv.reset() begin=time.time() serv.onewaydelay() serv.onewaydelay() serv.onewaydelay() serv.onewaydelay() serv.onewaydelay() print("Done with the oneway calls.") completed=serv.getcount() print("Number of completed calls in the server: %d" % completed) print("This should be 0, because all 5 calls are still busy in the background.") if completed>0: print(" !!! The oneway calls were not running in the background !!!") print(" ??? Are you sure ONEWAY_THREADED=True on the server ???") print() print("Calling normal delay 5 times. They will all be processed") print("by the same server thread because we're using the same proxy.") r=serv.delay() print(" call processed by: %s" % r) r=serv.delay() print(" call processed by: %s" % r) r=serv.delay() print(" call processed by: %s" % r) r=serv.delay() print(" call processed by: %s" % r) r=serv.delay() print(" call processed by: %s" % r) time.sleep(2) print("Number of completed calls in the server: %d" % serv.getcount()) print("This should be 10, because by now the 5 oneway calls have completed as well.") serv.reset() print("\n--------------------------------------------------------------") print(" This part depends on the type of the server. ") print("--------------------------------------------------------------") print("Creating 5 threads that each call the server at the same time.") serverconfig=serv.getconfig() if serverconfig["SERVERTYPE"]=="thread": print("Servertype is thread. All calls will run in parallel.") print("The time this will take is 1 second (every thread takes 1 second in parallel).") print("You will see that the requests are handled by different server threads.") elif serverconfig["SERVERTYPE"]=="multiplex": print("Servertype is multiplex. The threads will need to get in line.") print("The time this will take is 5 seconds (every thread takes 1 second sequentially).") print("You will see that the requests are handled by a single server thread.") else: print("Unknown servertype") def func(uri): # This will run in a thread. Create a proxy just for this thread: with Pyro4.core.Proxy(uri) as p: processed=p.delay() print(" thread %s called delay, processed by: %s" % (current_thread().getName(), processed)) serv._pyroBind() # simplify the uri threads=[] for i in range(5): t=threadutil.Thread(target=func, args=[serv._pyroUri]) t.setDaemon(True) threads.append(t) t.start() print("Waiting for threads to finish:") for t in threads: t.join() print("Done. Number of completed calls in the server: %d" % serv.getcount()) Pyro4-4.23/examples/servertypes/server.py000066400000000000000000000025041227003673200205530ustar00rootroot00000000000000from __future__ import print_function import time import sys import Pyro4 from Pyro4 import threadutil if sys.version_info<(3,0): input=raw_input current_thread=threadutil.currentThread else: current_thread=threadutil.current_thread class Server(object): def __init__(self): self.callcount=0 def reset(self): self.callcount=0 def getcount(self): return self.callcount # the number of completed calls def getconfig(self): return Pyro4.config.asDict() def delay(self): threadname=current_thread().getName() print("delay called in thread %s" % threadname) time.sleep(1) self.callcount+=1 return threadname def onewaydelay(self): threadname=current_thread().getName() print("onewaydelay called in thread %s" % threadname) time.sleep(1) self.callcount+=1 ######## main program Pyro4.config.SERVERTYPE="undefined" servertype=input("Servertype threaded or multiplex (t/m)?") if servertype=="t": Pyro4.config.SERVERTYPE="thread" else: Pyro4.config.SERVERTYPE="multiplex" daemon=Pyro4.core.Daemon() obj=Server() uri=daemon.register(obj) ns=Pyro4.naming.locateNS() ns.register("example.servertypes", uri) print("Server is ready.") daemon.requestLoop() Pyro4-4.23/examples/shoppingcart/000077500000000000000000000000001227003673200170005ustar00rootroot00000000000000Pyro4-4.23/examples/shoppingcart/Readme.txt000066400000000000000000000013271227003673200207410ustar00rootroot00000000000000A very simple example that shows the creation and manipulation of new objects in the server. It is a shop where the clients need to take a shopping cart (created in the shop server) and put items in it from the shop's inventory. After that they take it to the shop's counter to pay and get a receipt. Due to Pyro's autoproxy feature the shopping carts are automatically returned to the client as a proxy. The Shoppingcart objects remain in the shop server. The client code interacts with them (and with the shop) remotely. The shop returns a receipt (just a text list of purchased goods) at checkout time, and puts back the shopping cart (unregisters and deletes the object) when the client leaves the store. Pyro4-4.23/examples/shoppingcart/clients.py000066400000000000000000000040461227003673200210170ustar00rootroot00000000000000from __future__ import print_function import random import Pyro4 shop = Pyro4.Proxy("PYRONAME:example.shop") print("Simulating some customers.") harrysCart=shop.enter("Harry") sallysCart=shop.enter("Sally") shoplifterCart=shop.enter("shoplifter") # harry buys 4 things and sally 5, shoplifter takes 3 items # note that we put the item directly in the shopping cart. goods=list(shop.goods().keys()) for i in range(4): item=random.choice(goods) print("Harry buys %s" % item) harrysCart.purchase(item) for i in range(5): item=random.choice(goods) print("Sally buys %s" % item) sallysCart.purchase(item) for i in range(3): item=random.choice(goods) print("Shoplifter takes %s" % item) shoplifterCart.purchase(item) print("Customers currently in the shop: %s" % shop.customers()) # Go to the counter to pay and get a receipt. # The shopping cart is still 'inside the shop' (=on the server) # so it knows what is in there for every customer in the store. # Harry pays by just telling his name (and the shop looks up # harry's shoppingcart). # Sally just hands in her shopping cart directly. # The shoplifter tries to leave without paying. try: receipt=shop.payByName("Harry") except: print("ERROR: %s" % ("".join(Pyro4.util.getPyroTraceback()))) print("Harry payed. The cart now contains: %s (should be empty)" % harrysCart.getContents()) print("Harry got this receipt:") print(receipt) receipt=shop.payCart(sallysCart) print("Sally payed. The cart now contains: %s (should be empty)" % sallysCart.getContents()) print("Sally got this receipt:") print(receipt) print("Harry is leaving.") shop.leave("Harry") print("Sally is leaving.") shop.leave("Sally") print("Shoplifter is leaving. (should be impossible i.e. give an error)") try: shop.leave("shoplifter") except: print("".join(Pyro4.util.getPyroTraceback())) print("Harry is attempting to put stuff back in his cart again,") print("which should fail because the cart does no longer exist.") harrysCart.purchase("crap") Pyro4-4.23/examples/shoppingcart/shoppingcart.py000066400000000000000000000006361227003673200220600ustar00rootroot00000000000000from __future__ import print_function class ShoppingCart(object): def __init__(self): self.contents=[] print("(shoppingcart %d taken)" % id(self)) def purchase(self, item): self.contents.append(item) print("(%s put into shoppingcart %d)" % (item, id(self))) def empty(self): self.contents=[] def getContents(self): return self.contents Pyro4-4.23/examples/shoppingcart/shopserver.py000066400000000000000000000044541227003673200215610ustar00rootroot00000000000000from __future__ import print_function import time import Pyro4 from shoppingcart import ShoppingCart class Shop(object): inventory= { "paper" : 1.25, "bread" : 1.50, "meat" : 5.99, "milk" : 0.80, "fruit" : 2.65, "chocolate" : 3.99, "pasta" : 0.50, "sauce" : 1.20, "vegetables": 1.40, "cookies" : 1.99, "pizza" : 3.60, "shampoo" : 2.22, "whiskey" : 24.99 } customersInStore={} def enter(self, name): print("Customer %s enters the store." % name) print("Customer takes a shopping cart.") # create a cart and return it as a pyro object to the client cart=ShoppingCart() self.customersInStore[name]=cart self._pyroDaemon.register(cart) # make cart a pyro object return cart def customers(self): return list(self.customersInStore.keys()) def goods(self): return self.inventory def payByName(self, name): print("Customer %s goes to the counter to pay." % name) cart=self.customersInStore[name] return self.payCart(cart, name) def payCart(self,cart,name=None): receipt=[] if name: receipt.append("Receipt for %s." % name) receipt.append("Receipt Date: "+time.asctime()) total=0.0 for item in cart.getContents(): price=self.inventory[item] total+=price receipt.append("%13s %.2f" % (item,price)) receipt.append("") receipt.append("%13s %.2f" % ("total:",total)) cart.empty() return "\n".join(receipt) def leave(self, name): print("Customer %s leaves." % name) cart=self.customersInStore[name] print(" their shopping cart contains: %s" % cart.getContents()) if cart.getContents(): print(" it is not empty, they are trying to shoplift!") raise Exception("attempt to steal a full cart prevented") # delete the cart and unregister it with pyro del self.customersInStore[name] self._pyroDaemon.unregister(cart) ######## main program Pyro4.Daemon.serveSimple({ Shop(): "example.shop" }) Pyro4-4.23/examples/stdinstdout/000077500000000000000000000000001227003673200166635ustar00rootroot00000000000000Pyro4-4.23/examples/stdinstdout/Readme.txt000066400000000000000000000017741227003673200206320ustar00rootroot00000000000000This example shows a nifty use of a Pyro proxy object. We use it to replace sys.stdin and sys.stdout, so that all input and output is handled by a remote program instead. inputoutput.py is the i/o 'server' that provides the remote input/output program.py is a simple program that asks a few lines of input from the user and prints a few lines of resulting text. If you feed it the URI of the inputoutput server, it will replace the local stdin/stdout with the appropriate Pyro proxies, so that it now does its i/o remotely. There's one special thing going on in the inputoutput server: it needs to wrap the stdin/stdout file objects with a simple proxy object because otherwise Pyro can't inject its custom attributes that it needs to be able to treat the object (file stream) as a Pyro object. The proxy 'knows' that all special Pyro attributes start with _pyro. Also, it needs to intercept the fileno attribute and pretend it doesn't exist, otherwise the thing doesn't seem to work on Python 3.x. Pyro4-4.23/examples/stdinstdout/inputoutput.py000066400000000000000000000031021227003673200216510ustar00rootroot00000000000000# This is the remote input/output server from __future__ import print_function import sys import Pyro4 if sys.version_info<(3,0): input=raw_input class RemoteIOManager(object): """The Pyro object that provides the remote in/out stream proxies""" def __init__(self, stdin_uri, stdout_uri): self.stdin_uri=stdin_uri self.stdout_uri=stdout_uri def getInputOutput(self): return Pyro4.Proxy(self.stdout_uri), Pyro4.Proxy(self.stdin_uri) class SimpleProxy(object): """simple proxy to another object. Needed to be able to use built-in types as a remote Pyro object""" def __init__(self, open_file): #self._obj=open_file object.__setattr__(self, "_obj", open_file) def __getattribute__(self, name): if name=="fileno": # hack to make it work on Python 3.x raise AttributeError(name) elif name.startswith("_pyro"): # little hack to get Pyro's attributes from this object itself return object.__getattribute__(self, name) else: # all other attributes and methods are passed to the proxied _obj return getattr(object.__getattribute__(self, "_obj"), name) d=Pyro4.Daemon() stdin_uri=d.register(SimpleProxy(sys.stdin),"inputoutput.stdin") # remote stdin stdout_uri=d.register(SimpleProxy(sys.stdout),"inputoutput.stdout") # remote stdout uri=d.register(RemoteIOManager(stdin_uri, stdout_uri),"example.inputoutput.manager") print("object uri=",uri) print("server running.") d.requestLoop() Pyro4-4.23/examples/stdinstdout/program.py000066400000000000000000000026611227003673200207110ustar00rootroot00000000000000# this is the program whose input/output you can redirect from __future__ import print_function import sys import Pyro4 if sys.version_info<(3,0): input=raw_input sys.excepthook=Pyro4.util.excepthook def interaction(): print("Hello there.") name=input("What is your name? ").strip() year=input("What year were you born? ").strip() print("Nice meeting you, {0}!".format(name)) print("I heard the wine from {0} was particularly good.".format(year)) def main(): uri=input("uri of the remote i/o server, or enter for local i/o:").strip() print(repr(uri)) if uri: try: remoteIO = Pyro4.Proxy(uri) remote_stdout, remote_stdin = remoteIO.getInputOutput() print("Replacing sys.stdin and sys.stdout. Read and type on the server :-)") orig_stdin=sys.stdin orig_stdout=sys.stdout sys.stdin=remote_stdin # just put a proxy in place of stdin/stdout... sys.stdout=remote_stdout # ... all i/o calls will be passed to the remote object print("---remote interaction starts---") interaction() # call the interaction loop print("---remote interaction ends---") finally: sys.stdout=orig_stdout sys.stdin=orig_stdin else: print("just running locally") interaction() if __name__=="__main__": main() Pyro4-4.23/examples/stockquotes/000077500000000000000000000000001227003673200166635ustar00rootroot00000000000000Pyro4-4.23/examples/stockquotes/Readme.txt000077500000000000000000000057351227003673200206360ustar00rootroot00000000000000This example is the code from the Pyro tutorial where we build a simple stock quote system. The idea is that we have multiple stock markets producing stock symbol quotes. There is an aggregator that combines the quotes from all stock markets. Finally there are multiple viewers that can register themselves by the aggregator and let it know what stock symbols they're interested in. The viewers will then receive near-real-time stock quote updates for the symbols they selected. (Everything is fictional, ofcourse). Stockmarket ->-----\ /----> Viewer Stockmarket ->------> Aggregator ->-----> Viewer Stockmarket ->-----/ \----> Viewer The tutorial consists of 3 phases: phase 1: Simple prototype code where everything is running in a single process. Main.py creates all object, connects them together, and contains a loop that drives the stockmarket quote generation. This code is fully operational but contains no Pyro code at all and shows what the system is going to look like later on. phase 2: Still no Pyro code, but the components are now more autonomous. They each have a main function that starts up the component and connects it to the other component(s). As the Stockmarket is the source of the data, it now contains a thread that produces stock quote changes. Main.py now only starts the various components and then sits to wait for an exit signal. While this phase still doesn't use Pyro at all, the structure of the code and the components are very close to what we want to achieve in the end where everything is fully distributed. phase 3: The components are now fully distributed and we used Pyro to make them talk to each other. There is no main.py anymore because you have to start every component by itself: (in seperate console windows for instance) - start a Pyro name server (python -m Pyro4.naming), make sure that you've set the PYRO_SERIALIZERS_ACCEPTED=pickle environment variable first. - start the stockmarket - start the aggregator - start one or more of the viewers. A lot of subjects are not addressed in this tutorial, such as what to do when one or more of the viewers quits (error handling and unregistration), what to do when a new stockmarket is opening when we have a system running already, what if a viewer is blocking the processing of the stock quote updates, etc. Note that phase 3 of this example makes use of Pyro's AutoProxy feature. Sending pyro objects 'over the wire' will automatically convert them into proxies so that the other side will talk to the actual object, instead of a local copy. Note: ignore the exact meaning of the "PYRO_SERIALIZER=pickle" settings. They are needed to get the stock market tutorial running in the form presented here. Basically it enables Pyro to transfer actual Python objects to remote calls, instead of only simple types such as lists and strings. Pyro4-4.23/examples/stockquotes/phase1/000077500000000000000000000000001227003673200200445ustar00rootroot00000000000000Pyro4-4.23/examples/stockquotes/phase1/aggregator.py000066400000000000000000000011031227003673200225330ustar00rootroot00000000000000class Aggregator(object): def __init__(self): self.viewers = {} self.symbols = [] def add_symbols(self, symbols): self.symbols.extend(symbols) def available_symbols(self): return self.symbols def view(self, viewer, symbols): self.viewers[viewer] = symbols def quotes(self, market, stockquotes): for symbol, value in stockquotes.items(): for viewer, symbols in self.viewers.items(): if symbol in symbols: viewer.quote(market, symbol, value) Pyro4-4.23/examples/stockquotes/phase1/main.py000066400000000000000000000013361227003673200213450ustar00rootroot00000000000000from __future__ import print_function import time from stockmarket import StockMarket from aggregator import Aggregator from viewer import Viewer def main(): nasdaq = StockMarket("NASDAQ", ["AAPL", "CSCO", "MSFT", "GOOG"]) newyork = StockMarket("NYSE", ["IBM", "HPQ", "BP"]) agg = Aggregator() agg.add_symbols(nasdaq.symbols()) agg.add_symbols(newyork.symbols()) print("aggregated symbols:", agg.available_symbols()) nasdaq.listener(agg) newyork.listener(agg) view = Viewer() agg.view(view, ["IBM", "AAPL", "MSFT"]) print("") while True: nasdaq.generate() newyork.generate() time.sleep(0.5) if __name__ == "__main__": main() Pyro4-4.23/examples/stockquotes/phase1/stockmarket.py000066400000000000000000000013461227003673200227510ustar00rootroot00000000000000import random class StockMarket(object): def __init__(self, marketname, symbols): self.name = marketname self.symbolmeans = {} for symbol in symbols: self.symbolmeans[symbol] = random.uniform(20, 200) self.aggregators = [] def generate(self): quotes = {} for symbol, mean in self.symbolmeans.items(): if random.random() < 0.2: quotes[symbol] = round(random.normalvariate(mean, 20), 2) for aggregator in self.aggregators: aggregator.quotes(self.name, quotes) def listener(self,aggregator): self.aggregators.append(aggregator) def symbols(self): return self.symbolmeans.keys() Pyro4-4.23/examples/stockquotes/phase1/viewer.py000066400000000000000000000002521227003673200217160ustar00rootroot00000000000000from __future__ import print_function class Viewer(object): def quote(self, market, symbol, value): print("{0}.{1}: {2}".format(market, symbol, value)) Pyro4-4.23/examples/stockquotes/phase2/000077500000000000000000000000001227003673200200455ustar00rootroot00000000000000Pyro4-4.23/examples/stockquotes/phase2/aggregator.py000066400000000000000000000020321227003673200225360ustar00rootroot00000000000000from __future__ import print_function class Aggregator(object): def __init__(self): self.viewers = {} self.symbols = [] def add_symbols(self, symbols): self.symbols.extend(symbols) def available_symbols(self): return self.symbols def view(self, viewer, symbols): print("aggregator gets a new viewer, for symbols:", symbols) self.viewers[viewer] = symbols def quotes(self, market, stockquotes): for symbol, value in stockquotes.items(): for viewer, symbols in self.viewers.items(): if symbol in symbols: viewer.quote(market, symbol, value) def main(stockmarkets): aggregator = Aggregator() for market in stockmarkets: aggregator.add_symbols(market.symbols()) market.listener(aggregator) if not aggregator.available_symbols(): raise ValueError("no symbols found!") print("aggregated symbols:", aggregator.available_symbols()) return aggregator Pyro4-4.23/examples/stockquotes/phase2/main.py000066400000000000000000000005531227003673200213460ustar00rootroot00000000000000from __future__ import print_function import stockmarket import aggregator import viewer import sys if sys.version_info < (3,0): input = raw_input def main(): markets = stockmarket.main() aggr = aggregator.main(markets) viewer.main(aggr) print("\nPress enter to quit.\n") input() if __name__ == "__main__": main() Pyro4-4.23/examples/stockquotes/phase2/stockmarket.py000066400000000000000000000026141227003673200227510ustar00rootroot00000000000000from __future__ import print_function import random import threading import time class StockMarket(object): def __init__(self, marketname, symbols): self.name = marketname self.symbolmeans = {} for symbol in symbols: self.symbolmeans[symbol] = random.uniform(20, 200) self.aggregators = [] def generate(self): quotes = {} for symbol, mean in self.symbolmeans.items(): if random.random() < 0.2: quotes[symbol] = round(random.normalvariate(mean, 20), 2) print("new quotes generated for", self.name) for aggregator in self.aggregators: aggregator.quotes(self.name, quotes) def listener(self,aggregator): print("market {0} adding new aggregator".format(self.name)) self.aggregators.append(aggregator) def symbols(self): return self.symbolmeans.keys() def run(self): def generate_symbols(): while True: time.sleep(random.random()) self.generate() thread = threading.Thread(target=generate_symbols) thread.setDaemon(True) thread.start() def main(): nasdaq = StockMarket("NASDAQ", ["AAPL", "CSCO", "MSFT", "GOOG"]) newyork = StockMarket("NYSE", ["IBM", "HPQ", "BP"]) nasdaq.run() newyork.run() return [nasdaq, newyork] Pyro4-4.23/examples/stockquotes/phase2/viewer.py000066400000000000000000000004451227003673200217230ustar00rootroot00000000000000from __future__ import print_function class Viewer(object): def quote(self, market, symbol, value): print("{0}.{1}: {2}".format(market, symbol, value)) def main(aggregator): viewer = Viewer() aggregator.view(viewer, ["IBM", "AAPL", "MSFT"]) return viewer Pyro4-4.23/examples/stockquotes/phase3/000077500000000000000000000000001227003673200200465ustar00rootroot00000000000000Pyro4-4.23/examples/stockquotes/phase3/aggregator.py000066400000000000000000000030041227003673200225370ustar00rootroot00000000000000from __future__ import print_function import Pyro4 Pyro4.config.SERIALIZER = 'pickle' Pyro4.config.SERIALIZERS_ACCEPTED.add('pickle') class Aggregator(object): def __init__(self): self.viewers = {} self.symbols = [] def add_symbols(self, symbols): self.symbols.extend(symbols) def available_symbols(self): return self.symbols def view(self, viewer, symbols): print("aggregator gets a new viewer, for symbols:", symbols) self.viewers[viewer] = symbols def quotes(self, market, stockquotes): for symbol, value in stockquotes.items(): for viewer, symbols in self.viewers.items(): if symbol in symbols: viewer.quote(market, symbol, value) def main(): aggregator = Aggregator() daemon = Pyro4.Daemon() agg_uri = daemon.register(aggregator) ns = Pyro4.locateNS() ns.register("example.stockquote.aggregator", agg_uri) for market, market_uri in ns.list(prefix="example.stockmarket.").items(): print("joining market", market) stockmarket = Pyro4.Proxy(market_uri) stockmarket.listener(aggregator) aggregator.add_symbols(stockmarket.symbols()) if not aggregator.available_symbols(): raise ValueError("no symbols found! (have you started the stock market first?)") print("Aggregator running. Symbols:", aggregator.available_symbols()) daemon.requestLoop() if __name__ == "__main__": main() Pyro4-4.23/examples/stockquotes/phase3/stockmarket.py000066400000000000000000000035151227003673200227530ustar00rootroot00000000000000from __future__ import print_function import random import threading import time import Pyro4 Pyro4.config.SERIALIZER = 'pickle' Pyro4.config.SERIALIZERS_ACCEPTED.add('pickle') class StockMarket(object): def __init__(self, marketname, symbols): self.name = marketname self.symbolmeans = {} for symbol in symbols: self.symbolmeans[symbol] = random.uniform(20, 200) self.aggregators = [] def generate(self): quotes = {} for symbol, mean in self.symbolmeans.items(): if random.random() < 0.2: quotes[symbol] = round(random.normalvariate(mean, 20), 2) print("new quotes generated for", self.name) for aggregator in self.aggregators: aggregator.quotes(self.name, quotes) def listener(self,aggregator): print("market {0} adding new aggregator".format(self.name)) self.aggregators.append(aggregator) def symbols(self): return list(self.symbolmeans.keys()) def run(self): def generate_symbols(): while True: time.sleep(random.random()) self.generate() thread = threading.Thread(target=generate_symbols) thread.setDaemon(True) thread.start() def main(): nasdaq = StockMarket("NASDAQ", ["AAPL", "CSCO", "MSFT", "GOOG"]) newyork = StockMarket("NYSE", ["IBM", "HPQ", "BP"]) daemon = Pyro4.Daemon() nasdaq_uri = daemon.register(nasdaq) newyork_uri = daemon.register(newyork) ns = Pyro4.locateNS() ns.register("example.stockmarket.nasdaq", nasdaq_uri) ns.register("example.stockmarket.newyork", newyork_uri) nasdaq.run() newyork.run() print("Stockmarkets running.") daemon.requestLoop() if __name__ == "__main__": main() Pyro4-4.23/examples/stockquotes/phase3/viewer.py000066400000000000000000000015551227003673200217270ustar00rootroot00000000000000from __future__ import print_function import sys import Pyro4 Pyro4.config.SERIALIZER = 'pickle' Pyro4.config.SERIALIZERS_ACCEPTED.add('pickle') if sys.version_info < (3,0): input = raw_input class Viewer(object): def quote(self, market, symbol, value): print("{0}.{1}: {2}".format(market, symbol, value)) def main(): viewer = Viewer() daemon = Pyro4.Daemon() daemon.register(viewer) aggregator = Pyro4.Proxy("PYRONAME:example.stockquote.aggregator") print("Available stock symbols:", aggregator.available_symbols()) symbols = input("Enter symbols you want to view (comma separated):") symbols = [symbol.strip() for symbol in symbols.split(",")] aggregator.view(viewer, symbols) print("Viewer listening on symbols", symbols) daemon.requestLoop() if __name__ == "__main__": main() Pyro4-4.23/examples/timeout/000077500000000000000000000000001227003673200157655ustar00rootroot00000000000000Pyro4-4.23/examples/timeout/Readme.txt000066400000000000000000000005771227003673200177340ustar00rootroot00000000000000This is an example that shows the connection timeout handling (in the client). server.py -- the server you need to run for this example client.py -- client that uses timeout settings The client disables and enables timeouts to show what happens. It shows timeouts during long remote method calls, but also timeouts when trying to connect to a unresponsive server. Pyro4-4.23/examples/timeout/client.py000066400000000000000000000057261227003673200176270ustar00rootroot00000000000000from __future__ import print_function import time, sys import Pyro4 import Pyro4.errors # NOTE: the timer in IronPython seems to be wacky. # So we use wider margins for that, to check if the delays are ok. def approxEqual(x, y): return abs(x-y) < 0.2 # disable timeout globally Pyro4.config.COMMTIMEOUT = 0 obj = Pyro4.core.Proxy("PYRONAME:example.timeout") obj._pyroBind() print("No timeout is configured. Calling delay with 2 seconds.") start = time.time() result = obj.delay(2) assert result == "slept 2 seconds" duration = time.time()-start if sys.platform != "cli": assert approxEqual(duration, 2), "expected 2 seconds duration" else: assert 1.0 < duration < 3.0, "expected about 2 seconds duration" # override timeout for this object obj._pyroTimeout = 1 print("Timeout set to 1 seconds. Calling delay with 2 seconds.") start = time.time() try: result = obj.delay(2) print("!?should have raised TimeoutError!?") except Pyro4.errors.TimeoutError: print("TimeoutError! As expected!") duration = time.time()-start if sys.platform != "cli": assert approxEqual(duration, 1), "expected 1 seconds duration" else: assert 0.9 < duration < 1.9, "expected about 1 second duration" # set timeout globally Pyro4.config.COMMTIMEOUT=1 obj=Pyro4.core.Proxy("PYRONAME:example.timeout") print("COMMTIMEOUT is set globally. Calling delay with 2 seconds.") start=time.time() try: result=obj.delay(2) print("!?should have raised TimeoutError!?") except Pyro4.errors.TimeoutError: print("TimeoutError! As expected!") duration = time.time()-start if sys.platform != "cli": assert approxEqual(duration, 1), "expected 1 seconds duration" else: assert 0.9 < duration < 1.9, "expected about 1 second duration" # override again for this object obj._pyroTimeout = None print("No timeout is configured. Calling delay with 3 seconds.") start = time.time() result = obj.delay(3) assert result == "slept 3 seconds" duration = time.time()-start if sys.platform != "cli": assert approxEqual(duration, 3), "expected 3 seconds duration" else: assert 2.5 < duration < 3.5, "expected about 3 second duration" print("Trying to connect to the frozen daemon.") obj = Pyro4.core.Proxy("PYRONAME:example.timeout.frozendaemon") obj._pyroTimeout = 1 print("Timeout set to 1 seconds. Trying to connect.") start = time.time() try: result = obj.delay(5) print("!?should have raised TimeoutError!?") except Pyro4.errors.TimeoutError: print("TimeoutError! As expected!") duration = time.time()-start if sys.platform != "cli": assert approxEqual(duration,1), "expected 1 seconds duration" else: assert 0.9 < duration < 1.9, "expected about 1 second duration" print("Disabling timeout and trying to connect again. This may take forever now.") print("Feel free to abort with ctrl-c or ctrl-break.") obj._pyroTimeout = None obj.delay(1) Pyro4-4.23/examples/timeout/server.py000066400000000000000000000014241227003673200176460ustar00rootroot00000000000000from __future__ import print_function import time import Pyro4 class TimeoutServer(object): def delay(self, amount): print("sleeping %d" % amount) time.sleep(amount) print("done.") return "slept %d seconds" % amount Pyro4.config.COMMTIMEOUT = 0 # the server won't be using timeouts ns = Pyro4.naming.locateNS() daemon = Pyro4.core.Daemon() daemon2 = Pyro4.core.Daemon() obj = TimeoutServer() obj2 = TimeoutServer() uri = daemon.register(obj) uri2 = daemon2.register(obj2) ns.register("example.timeout", uri) ns.register("example.timeout.frozendaemon", uri2) print("Server ready.") # Note that we're only starting one of the 2 daemons. # daemon2 is not started to simulate connection timeouts. daemon.requestLoop() Pyro4-4.23/examples/unixdomainsock/000077500000000000000000000000001227003673200173325ustar00rootroot00000000000000Pyro4-4.23/examples/unixdomainsock/Readme.txt000066400000000000000000000005751227003673200212770ustar00rootroot00000000000000This is a very simple example that uses a Unix domain socket instead of a normal tcp/ip socket for server communications. The only difference is the parameter passed to the Daemon class. The client code is unaware of any special socket because you just feed it any Pyro URI. This time the URI will encode a Unix domain socket however, instead of a hostname+port number. Pyro4-4.23/examples/unixdomainsock/client.py000066400000000000000000000004171227003673200211640ustar00rootroot00000000000000from __future__ import print_function import sys import Pyro4 if sys.version_info<(3,0): input=raw_input uri=input("enter the server uri: ").strip() with Pyro4.Proxy(uri) as p: response=p.message("Hello there!") print("Response was:",response) Pyro4-4.23/examples/unixdomainsock/server.py000066400000000000000000000006321227003673200212130ustar00rootroot00000000000000from __future__ import print_function import os import Pyro4 class Thingy(object): def message(self, arg): print("Message received:",arg) return "Roger!" if os.path.exists("example_unix.sock"): os.remove("example_unix.sock") d=Pyro4.Daemon(unixsocket="example_unix.sock") uri=d.register(Thingy(), "example.unixsock") print("Server running, uri=",uri) d.requestLoop() Pyro4-4.23/examples/warehouse/000077500000000000000000000000001227003673200163015ustar00rootroot00000000000000Pyro4-4.23/examples/warehouse/Readme.txt000077500000000000000000000026651227003673200202530ustar00rootroot00000000000000This example is the code from the Pyro tutorial where we build a simple warehouse that stores items. The idea is that there is one big warehouse that everyone can store items in, and retrieve other items from (if they're in the warehouse). The tutorial consists of 3 phases: phase 1: Simple prototype code where everything is running in a single process. visit.py creates the warehouse and two visitors. This code is fully operational but contains no Pyro code at all and shows what the system is going to look like later on. phase 2: Pyro is now used to make the warehouse a standalone component. You can still visit it of course. visit.py does need the URI of the warehouse however. (It is printed as soon as the warehouse is started) The code of the Warehouse and the Person classes is unchanged. phase 3: Phase 2 works fine but is a bit cumbersome because you need to copy-paste the warehouse URI to be able to visit it. Phase 3 simplifies things a bit by using the Pyro name server. Also, it uses the Pyro excepthook to print a nicer exception message if anything goes wrong. (Try taking something from the warehouse that is not present!) The code of the Warehouse and the Person classes is still unchanged. Note: to avoid having to deal with serialization issues, this example only passes primitive types (strings in this case) to the remote method calls. Pyro4-4.23/examples/warehouse/phase1/000077500000000000000000000000001227003673200174625ustar00rootroot00000000000000Pyro4-4.23/examples/warehouse/phase1/person.py000066400000000000000000000016121227003673200213420ustar00rootroot00000000000000from __future__ import print_function import sys if sys.version_info < (3, 0): input = raw_input class Person(object): def __init__(self, name): self.name = name def visit(self, warehouse): print("This is {0}.".format(self.name)) self.deposit(warehouse) self.retrieve(warehouse) print("Thank you, come again!") def deposit(self, warehouse): print("The warehouse contains:", warehouse.list_contents()) item = input("Type a thing you want to store (or empty): ").strip() if item: warehouse.store(self.name, item) def retrieve(self, warehouse): print("The warehouse contains:", warehouse.list_contents()) item = input("Type something you want to take (or empty): ").strip() if item: warehouse.take(self.name, item) Pyro4-4.23/examples/warehouse/phase1/visit.py000066400000000000000000000003451227003673200211740ustar00rootroot00000000000000# This is the code that runs this example. from warehouse import Warehouse from person import Person warehouse = Warehouse() janet = Person("Janet") henry = Person("Henry") janet.visit(warehouse) henry.visit(warehouse) Pyro4-4.23/examples/warehouse/phase1/warehouse.py000066400000000000000000000007471227003673200220460ustar00rootroot00000000000000from __future__ import print_function class Warehouse(object): def __init__(self): self.contents = ["chair", "bike", "flashlight", "laptop", "couch"] def list_contents(self): return self.contents def take(self, name, item): self.contents.remove(item) print("{0} took the {1}.".format(name, item)) def store(self, name, item): self.contents.append(item) print("{0} stored the {1}.".format(name, item)) Pyro4-4.23/examples/warehouse/phase2/000077500000000000000000000000001227003673200174635ustar00rootroot00000000000000Pyro4-4.23/examples/warehouse/phase2/person.py000066400000000000000000000016121227003673200213430ustar00rootroot00000000000000from __future__ import print_function import sys if sys.version_info < (3, 0): input = raw_input class Person(object): def __init__(self, name): self.name = name def visit(self, warehouse): print("This is {0}.".format(self.name)) self.deposit(warehouse) self.retrieve(warehouse) print("Thank you, come again!") def deposit(self, warehouse): print("The warehouse contains:", warehouse.list_contents()) item = input("Type a thing you want to store (or empty): ").strip() if item: warehouse.store(self.name, item) def retrieve(self, warehouse): print("The warehouse contains:", warehouse.list_contents()) item = input("Type something you want to take (or empty): ").strip() if item: warehouse.take(self.name, item) Pyro4-4.23/examples/warehouse/phase2/visit.py000066400000000000000000000005241227003673200211740ustar00rootroot00000000000000# This is the code that visits the warehouse. import sys import Pyro4 from person import Person if sys.version_info<(3,0): input = raw_input uri = input("Enter the uri of the warehouse: ").strip() warehouse = Pyro4.Proxy(uri) janet = Person("Janet") henry = Person("Henry") janet.visit(warehouse) henry.visit(warehouse) Pyro4-4.23/examples/warehouse/phase2/warehouse.py000066400000000000000000000013411227003673200220360ustar00rootroot00000000000000from __future__ import print_function import Pyro4 import person class Warehouse(object): def __init__(self): self.contents = ["chair", "bike", "flashlight", "laptop", "couch"] def list_contents(self): return self.contents def take(self, name, item): self.contents.remove(item) print("{0} took the {1}.".format(name, item)) def store(self, name, item): self.contents.append(item) print("{0} stored the {1}.".format(name, item)) def main(): warehouse = Warehouse() Pyro4.Daemon.serveSimple( { warehouse: "example.warehouse" }, ns = False) if __name__=="__main__": main() Pyro4-4.23/examples/warehouse/phase3/000077500000000000000000000000001227003673200174645ustar00rootroot00000000000000Pyro4-4.23/examples/warehouse/phase3/person.py000066400000000000000000000016121227003673200213440ustar00rootroot00000000000000from __future__ import print_function import sys if sys.version_info < (3, 0): input = raw_input class Person(object): def __init__(self, name): self.name = name def visit(self, warehouse): print("This is {0}.".format(self.name)) self.deposit(warehouse) self.retrieve(warehouse) print("Thank you, come again!") def deposit(self, warehouse): print("The warehouse contains:", warehouse.list_contents()) item = input("Type a thing you want to store (or empty): ").strip() if item: warehouse.store(self.name, item) def retrieve(self, warehouse): print("The warehouse contains:", warehouse.list_contents()) item = input("Type something you want to take (or empty): ").strip() if item: warehouse.take(self.name, item) Pyro4-4.23/examples/warehouse/phase3/visit.py000066400000000000000000000004741227003673200212010ustar00rootroot00000000000000# This is the code that visits the warehouse. import sys import Pyro4 import Pyro4.util from person import Person sys.excepthook = Pyro4.util.excepthook warehouse = Pyro4.Proxy("PYRONAME:example.warehouse") janet = Person("Janet") henry = Person("Henry") janet.visit(warehouse) henry.visit(warehouse) Pyro4-4.23/examples/warehouse/phase3/warehouse.py000066400000000000000000000013401227003673200220360ustar00rootroot00000000000000from __future__ import print_function import Pyro4 import person class Warehouse(object): def __init__(self): self.contents = ["chair", "bike", "flashlight", "laptop", "couch"] def list_contents(self): return self.contents def take(self, name, item): self.contents.remove(item) print("{0} took the {1}.".format(name, item)) def store(self, name, item): self.contents.append(item) print("{0} stored the {1}.".format(name, item)) def main(): warehouse = Warehouse() Pyro4.Daemon.serveSimple( { warehouse: "example.warehouse" }, ns = True) if __name__=="__main__": main() Pyro4-4.23/setup.cfg000066400000000000000000000003641227003673200143050ustar00rootroot00000000000000[bdist_rpm] doc_files = LICENSE docs/ [build_sphinx] source-dir = docs/source build-dir = build/sphinx [upload_sphinx] upload-dir = build/sphinx/html [nosetests] verbosity=2 detailed-errors=1 where=tests/PyroTests Pyro4-4.23/setup.py000066400000000000000000000070001227003673200141700ustar00rootroot00000000000000import sys import os try: # try setuptools first, to get access to build_sphinx and test commands from setuptools import setup using_setuptools=True except ImportError: from distutils.core import setup using_setuptools=False if __name__ == '__main__' : sys.path.insert(0, "src") import warnings warnings.simplefilter("default", ImportWarning) # enable ImportWarning import Pyro4.constants print('Pyro version = %s' % Pyro4.constants.VERSION) setupargs={ "name": "Pyro4", "version": Pyro4.constants.VERSION, "license": "MIT", "description": "distributed object middleware for Python (RPC)", "long_description": """Pyro means PYthon Remote Objects. It is a library that enables you to build applications in which objects can talk to eachother over the network, with minimal programming effort. You can just use normal Python method calls, with almost every possible parameter and return value type, and Pyro takes care of locating the right object on the right computer to execute the method. It is designed to be very easy to use, and to generally stay out of your way. But it also provides a set of powerful features that enables you to build distributed applications rapidly and effortlessly. Pyro is written in 100% pure Python and therefore runs on many platforms and Python versions, including Python 2.x, Python 3.x, IronPython, Jython 2.7+ and Pypy. The source code repository is on Github: https://github.com/irmen/Pyro4 """, "author": "Irmen de Jong", "author_email": "irmen@razorvine.net", "keywords": "distributed objects, middleware, network communication, remote method call, IPC", "url": "http://pythonhosted.org/Pyro4/", "package_dir": {'':'src'}, "packages": ['Pyro4', 'Pyro4.socketserver', 'Pyro4.test', 'Pyro4.utils'], "scripts": [], "platforms": "any", "install_requires": ["serpent>=1.3"], "requires": ["serpent"], "classifiers": [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Natural Language :: Dutch", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Topic :: Software Development :: Object Brokering", "Topic :: System :: Distributed Computing", "Topic :: System :: Networking" ] } if os.name != "java": # only add '-O' optimized bytecode compile if not using jython setupargs["options"] = {"install": {"optimize": 1}} if using_setuptools: setupargs["test_suite"]="nose.collector" # use Nose to run unittests setup(**setupargs) if len(sys.argv)>=2 and sys.argv[1].startswith("install"): print("\nOnly the Pyro library has been installed (version %s)." % Pyro4.constants.VERSION) print("If you want to install the tests, the examples, and/or the manual,") print("you have to copy them manually to the desired location.") Pyro4-4.23/src/000077500000000000000000000000001227003673200132505ustar00rootroot00000000000000Pyro4-4.23/src/Pyro4/000077500000000000000000000000001227003673200142655ustar00rootroot00000000000000Pyro4-4.23/src/Pyro4/__init__.py000066400000000000000000000051061227003673200164000ustar00rootroot00000000000000""" Pyro package. Some generic init stuff to set up logging etc. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ import sys if sys.version_info < (2, 6): import warnings warnings.warn("This Pyro version is unsupported on Python versions older than 2.6", ImportWarning) def _configLogging(): """Do some basic config of the logging module at package import time. The configuring is done only if the PYRO_LOGLEVEL env var is set. If you want to use your own logging config, make sure you do that before any Pyro imports. Then Pyro will skip the autoconfig. Set the env var PYRO_LOGFILE to change the name of the autoconfigured log file (default is pyro.log in the current dir). Use '{stderr}' to make the log go to the standard error output.""" import os, logging level = os.environ.get("PYRO_LOGLEVEL") logfilename = os.environ.get("PYRO_LOGFILE", "pyro.log") if logfilename == "{stderr}": logfilename = None if level is not None: levelvalue = getattr(logging, level) if len(logging.root.handlers) == 0: # configure the logging with some sensible defaults. try: import tempfile tempfile = tempfile.TemporaryFile(dir=".") tempfile.close() except OSError: # cannot write in current directory, use the default console logger logging.basicConfig(level=levelvalue) else: # set up a basic logfile in current directory logging.basicConfig( level=levelvalue, filename=logfilename, datefmt="%Y-%m-%d %H:%M:%S", format="[%(asctime)s.%(msecs)03d,%(name)s,%(levelname)s] %(message)s" ) log = logging.getLogger("Pyro4") log.info("Pyro log configured using built-in defaults, level=%s", level) else: # PYRO_LOGLEVEL is not set, disable Pyro logging. No message is printed about this fact. log = logging.getLogger("Pyro4") log.setLevel(9999) _configLogging() del _configLogging # initialize Pyro's configuration from Pyro4.configuration import Configuration config=Configuration() del Configuration # import the required Pyro symbols into this package from Pyro4.core import URI, Proxy, Daemon, callback, batch, async from Pyro4.naming import locateNS, resolve from Pyro4.futures import Future from Pyro4.constants import VERSION as __version__ Pyro4-4.23/src/Pyro4/configuration.py000066400000000000000000000130121227003673200175030ustar00rootroot00000000000000""" Configuration settings. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ # Env vars used at package import time (see __init__.py): # PYRO_LOGLEVEL (enable Pyro log config and set level) # PYRO_LOGFILE (the name of the logfile if you don't like the default) import os import sys import platform class Configuration(object): __slots__=("HOST", "NS_HOST", "NS_PORT", "NS_BCPORT", "NS_BCHOST", "COMPRESSION", "SERVERTYPE", "DOTTEDNAMES", "COMMTIMEOUT", "POLLTIMEOUT", "THREADING2", "ONEWAY_THREADED", "DETAILED_TRACEBACK", "SOCK_REUSE", "PREFER_IP_VERSION", "THREADPOOL_MINTHREADS", "THREADPOOL_MAXTHREADS", "THREADPOOL_IDLETIMEOUT", "HMAC_KEY", "AUTOPROXY", "BROADCAST_ADDRS", "NATHOST", "NATPORT", "MAX_MESSAGE_SIZE", "FLAME_ENABLED", "SERIALIZER", "SERIALIZERS_ACCEPTED", "LOGWIRE" ) def __init__(self): self.reset() def reset(self, useenvironment=True): """ Set default config items. If useenvironment is False, won't read environment variables settings (useful if you can't trust your env). """ self.HOST = "localhost" # don't expose us to the outside world by default self.NS_HOST = self.HOST self.NS_PORT = 9090 # tcp self.NS_BCPORT = 9091 # udp self.NS_BCHOST = None self.NATHOST = None self.NATPORT = 0 self.COMPRESSION = False self.SERVERTYPE = "thread" self.DOTTEDNAMES = False # server-side self.COMMTIMEOUT = 0.0 self.POLLTIMEOUT = 2.0 # seconds self.SOCK_REUSE = False # so_reuseaddr on server sockets? self.THREADING2 = False # use threading2 if available? self.ONEWAY_THREADED = True # oneway calls run in their own thread self.DETAILED_TRACEBACK = False self.THREADPOOL_MINTHREADS = 4 self.THREADPOOL_MAXTHREADS = 50 self.THREADPOOL_IDLETIMEOUT = 2.0 self.HMAC_KEY = None # must be bytes type self.AUTOPROXY = True self.MAX_MESSAGE_SIZE = 0 # 0 = unlimited self.BROADCAST_ADDRS = ", 0.0.0.0" # comma separated list of broadcast addresses self.FLAME_ENABLED = False self.PREFER_IP_VERSION = 4 # 4, 6 or 0 (let OS choose according to RFC 3484) self.SERIALIZER = "serpent" self.SERIALIZERS_ACCEPTED = "serpent,marshal,json" self.LOGWIRE = False # log wire-level messages if useenvironment: # process enviroment variables PREFIX="PYRO_" for symbol in self.__slots__: if PREFIX+symbol in os.environ: value=getattr(self,symbol) envvalue=os.environ[PREFIX+symbol] if value is not None: valuetype=type(value) if valuetype is bool: # booleans are special envvalue=envvalue.lower() if envvalue in ("0", "off", "no", "false"): envvalue=False elif envvalue in ("1", "yes", "on", "true"): envvalue=True else: raise ValueError("invalid boolean value: %s%s=%s" % (PREFIX, symbol, envvalue)) else: envvalue=valuetype(envvalue) # just cast the value to the appropriate type setattr(self, symbol, envvalue) if self.HMAC_KEY and type(self.HMAC_KEY) is not bytes: self.HMAC_KEY = self.HMAC_KEY.encode("utf-8") # convert to bytes self.SERIALIZERS_ACCEPTED = set(self.SERIALIZERS_ACCEPTED.split(',')) def asDict(self): """returns the current config as a regular dictionary""" result={} for item in self.__slots__: result[item]=getattr(self,item) return result def parseAddressesString(self, addresses): """ Parses the addresses string which contains one or more ip addresses separated by a comma. Returns a sequence of these addresses. '' is replaced by the empty string. """ result=[] for addr in addresses.split(','): addr=addr.strip() if addr=="''": addr="" result.append(addr) return result def dump(self): # easy config diagnostics from Pyro4.constants import VERSION import inspect if hasattr(platform, "python_implementation"): implementation = platform.python_implementation() else: implementation = "Jython" if os.name=="java" else "???" config=self.asDict() config["LOGFILE"]=os.environ.get("PYRO_LOGFILE") config["LOGLEVEL"]=os.environ.get("PYRO_LOGLEVEL") result= ["Pyro version: %s" % VERSION, "Loaded from: %s" % os.path.abspath(os.path.split(inspect.getfile(Configuration))[0]), "Python version: %s %s (%s, %s)" % (implementation, platform.python_version(), platform.system(), os.name), "Active configuration settings:"] for n, v in sorted(config.items()): result.append("%s = %s" % (n, v)) return "\n".join(result) if __name__=="__main__": print(Configuration().dump()) Pyro4-4.23/src/Pyro4/constants.py000066400000000000000000000007011227003673200166510ustar00rootroot00000000000000""" Definitions of various hard coded constants. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ # Pyro version VERSION = "4.23" # standard object name for the Daemon object DAEMON_NAME = "Pyro.Daemon" # standard name for the Name server itself NAMESERVER_NAME = "Pyro.NameServer" # standard name for Flame server FLAME_NAME = "Pyro.Flame" # wire protocol version PROTOCOL_VERSION = 46 Pyro4-4.23/src/Pyro4/core.py000066400000000000000000001223221227003673200155710ustar00rootroot00000000000000""" Core logic (uri, daemon, proxy stuff). Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ from __future__ import with_statement import re, sys, time, os import logging, uuid from Pyro4 import constants, threadutil, util, socketutil, errors from Pyro4.socketserver.threadpoolserver import SocketServer_Threadpool from Pyro4.socketserver.multiplexserver import SocketServer_Select, SocketServer_Poll from Pyro4 import futures from Pyro4.message import Message import Pyro4 __all__ = ["URI", "Proxy", "Daemon", "callback", "batch", "async"] if sys.version_info >= (3, 0): basestring = str log = logging.getLogger("Pyro4.core") class URI(object): """ Pyro object URI (universal resource identifier). The uri format is like this: ``PYRO:objectid@location`` where location is one of: - ``hostname:port`` (tcp/ip socket on given port) - ``./u:sockname`` (Unix domain socket on localhost) There is also a 'Magic format' for simple name resolution using Name server: ``PYRONAME:objectname[@location]`` (optional name server location, can also omit location port) You can write the protocol in lowercase if you like (``pyro:...``) but it will automatically be converted to uppercase internally. """ uriRegEx = re.compile(r"(?P[Pp][Yy][Rr][Oo][a-zA-Z]*):(?P\S+?)(@(?P\S+))?$") __slots__ = ("protocol", "object", "sockname", "host", "port", "object") def __init__(self, uri): if isinstance(uri, URI): state=uri.__getstate__() self.__setstate__(state) return if not isinstance(uri, basestring): raise TypeError("uri parameter object is of wrong type") self.sockname=self.host=self.port=None match=self.uriRegEx.match(uri) if not match: raise errors.PyroError("invalid uri") self.protocol=match.group("protocol").upper() self.object=match.group("object") location=match.group("location") if self.protocol=="PYRONAME": self._parseLocation(location, Pyro4.config.NS_PORT) return if self.protocol=="PYRO": if not location: raise errors.PyroError("invalid uri") self._parseLocation(location, None) else: raise errors.PyroError("invalid uri (protocol)") def _parseLocation(self, location, defaultPort): if not location: return if location.startswith("./u:"): self.sockname=location[4:] if (not self.sockname) or ':' in self.sockname: raise errors.PyroError("invalid uri (location)") else: if location.startswith("["): # ipv6 if location.startswith("[["): # possible mistake: double-bracketing raise errors.PyroError("invalid ipv6 address: enclosed in too many brackets") self.host, _, self.port = re.match(r"\[([0-9a-fA-F:%]+)](:(\d+))?", location).groups() else: self.host, _, self.port = location.partition(":") if not self.port: self.port=defaultPort try: self.port=int(self.port) except (ValueError, TypeError): raise errors.PyroError("invalid port in uri, port="+str(self.port)) @staticmethod def isUnixsockLocation(location): """determine if a location string is for a Unix domain socket""" return location.startswith("./u:") @property def location(self): """property containing the location string, for instance ``"servername.you.com:5555"``""" if self.host: if ":" in self.host: # ipv6 return "[%s]:%d" % (self.host, self.port) else: return "%s:%d" % (self.host, self.port) elif self.sockname: return "./u:"+self.sockname else: return None def asString(self): """the string representation of this object""" result=self.protocol+":"+self.object location=self.location if location: result+="@"+location return result def __str__(self): string=self.asString() if sys.version_info<(3, 0) and type(string) is unicode: return string.encode("ascii", "replace") return string def __unicode__(self): return self.asString() def __repr__(self): return "<%s.%s at 0x%x, %s>" % (self.__class__.__module__, self.__class__.__name__, id(self), str(self)) def __eq__(self, other): if not isinstance(other, URI): return False return (self.protocol, self.object, self.sockname, self.host, self.port) \ == (other.protocol, other.object, other.sockname, other.host, other.port) def __ne__(self, other): return not self.__eq__(other) def __hash__(self): return hash((self.protocol, self.object, self.sockname, self.host, self.port)) # note: getstate/setstate are not needed if we use pickle protocol 2, # but this way it helps pickle to make the representation smaller by omitting all attribute names. def __getstate__(self): return self.protocol, self.object, self.sockname, self.host, self.port def __getstate_for_dict__(self): return self.__getstate__() def __setstate__(self, state): self.protocol, self.object, self.sockname, self.host, self.port = state class _RemoteMethod(object): """method call abstraction""" def __init__(self, send, name): self.__send = send self.__name = name def __getattr__(self, name): return _RemoteMethod(self.__send, "%s.%s" % (self.__name, name)) def __call__(self, *args, **kwargs): return self.__send(self.__name, args, kwargs) def _check_hmac(): if Pyro4.config.HMAC_KEY: if sys.version_info>=(3, 0) and type(Pyro4.config.HMAC_KEY) is not bytes: raise errors.PyroError("HMAC_KEY must be bytes type") class Proxy(object): """ Pyro proxy for a remote object. Intercepts method calls and dispatches them to the remote object. .. automethod:: _pyroBind .. automethod:: _pyroRelease .. automethod:: _pyroReconnect .. automethod:: _pyroBatch .. automethod:: _pyroAsync """ __pyroAttributes=frozenset(["__getnewargs__", "__getinitargs__", "_pyroConnection", "_pyroUri", "_pyroOneway", "_pyroTimeout", "_pyroSeq"]) def __init__(self, uri): """ .. autoattribute:: _pyroOneway .. autoattribute:: _pyroTimeout """ _check_hmac() # check if hmac secret key is set if isinstance(uri, basestring): uri=URI(uri) elif not isinstance(uri, URI): raise TypeError("expected Pyro URI") self._pyroUri=uri self._pyroConnection=None self._pyroOneway=set() self._pyroSeq=0 # message sequence number self.__pyroTimeout=Pyro4.config.COMMTIMEOUT self.__pyroLock=threadutil.Lock() self.__pyroConnLock=threadutil.Lock() util.get_serializer(Pyro4.config.SERIALIZER) # assert that the configured serializer is available if os.name=="java" and Pyro4.config.SERIALIZER=="marshal": import warnings warnings.warn("marshal doesn't work correctly with Jython (issue 2077); please choose another serializer", RuntimeWarning) def __del__(self): if hasattr(self, "_pyroConnection"): self._pyroRelease() def __getattr__(self, name): if name in Proxy.__pyroAttributes: # allows it to be safely pickled raise AttributeError(name) return _RemoteMethod(self._pyroInvoke, name) def __repr__(self): connected="connected" if self._pyroConnection else "not connected" return "<%s.%s at 0x%x, %s, for %s>" % (self.__class__.__module__, self.__class__.__name__, id(self), connected, self._pyroUri) def __unicode__(self): return str(self) def __getstate__(self): return self._pyroUri, self._pyroOneway, self.__pyroTimeout # skip the connection def __getstate_for_dict__(self): return self._pyroUri.asString(), tuple(self._pyroOneway), self.__pyroTimeout def __setstate__(self, state): self._pyroUri, self._pyroOneway, self.__pyroTimeout = state self._pyroConnection=None self._pyroSeq=0 self.__pyroLock=threadutil.Lock() self.__pyroConnLock=threadutil.Lock() def __copy__(self): uriCopy=URI(self._pyroUri) return Proxy(uriCopy) def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self._pyroRelease() def __eq__(self, other): if other is self: return True return isinstance(other, Proxy) and other._pyroUri == self._pyroUri and other._pyroOneway == self._pyroOneway def __ne__(self, other): if other and isinstance(other, Proxy): return other._pyroUri != self._pyroUri or other._pyroOneway != self._pyroOneway return True def __hash__(self): return hash(self._pyroUri) ^ hash(frozenset(self._pyroOneway)) def _pyroRelease(self): """release the connection to the pyro daemon""" with self.__pyroConnLock: if self._pyroConnection is not None: self._pyroConnection.close() self._pyroConnection=None log.debug("connection released") def _pyroBind(self): """ Bind this proxy to the exact object from the uri. That means that the proxy's uri will be updated with a direct PYRO uri, if it isn't one yet. If the proxy is already bound, it will not bind again. """ return self.__pyroCreateConnection(True) def __pyroGetTimeout(self): return self.__pyroTimeout def __pyroSetTimeout(self, timeout): self.__pyroTimeout=timeout if self._pyroConnection is not None: self._pyroConnection.timeout=timeout _pyroTimeout=property(__pyroGetTimeout, __pyroSetTimeout) def _pyroInvoke(self, methodname, vargs, kwargs, flags=0): """perform the remote method call communication""" if self._pyroConnection is None: # rebind here, don't do it from inside the invoke because deadlock will occur self.__pyroCreateConnection() serializer = util.get_serializer(Pyro4.config.SERIALIZER) data, compressed = serializer.serializeCall( self._pyroConnection.objectId, methodname, vargs, kwargs, compress=Pyro4.config.COMPRESSION) if compressed: flags |= Pyro4.message.FLAGS_COMPRESSED if methodname in self._pyroOneway: flags |= Pyro4.message.FLAGS_ONEWAY with self.__pyroLock: self._pyroSeq=(self._pyroSeq+1)&0xffff if Pyro4.config.LOGWIRE: log.debug("proxy wiredata sending: msgtype=%d flags=0x%x ser=%d seq=%d data=%r" % (Pyro4.message.MSG_INVOKE, flags, serializer.serializer_id, self._pyroSeq, data)) msg = Message(Pyro4.message.MSG_INVOKE, data, serializer.serializer_id, flags, self._pyroSeq) try: self._pyroConnection.send(msg.to_bytes()) del msg # invite GC to collect the object, don't wait for out-of-scope if flags & Pyro4.message.FLAGS_ONEWAY: return None # oneway call, no response data else: msg = Message.recv(self._pyroConnection, [Pyro4.message.MSG_RESULT]) if Pyro4.config.LOGWIRE: log.debug("proxy wiredata received: msgtype=%d flags=0x%x ser=%d seq=%d data=%r" % (msg.type, msg.flags, msg.serializer_id, msg.seq, msg.data) ) self.__pyroCheckSequence(msg.seq) if msg.serializer_id != serializer.serializer_id: error = "invalid serializer in response: %d" % msg.serializer_id log.error(error) raise errors.ProtocolError(error) data = serializer.deserializeData(msg.data, compressed=msg.flags & Pyro4.message.FLAGS_COMPRESSED) if msg.flags & Pyro4.message.FLAGS_EXCEPTION: if sys.platform=="cli": util.fixIronPythonExceptionForPickle(data, False) raise data else: return data except (errors.CommunicationError, KeyboardInterrupt): # Communication error during read. To avoid corrupt transfers, we close the connection. # Otherwise we might receive the previous reply as a result of a new methodcall! # Special case for keyboardinterrupt: people pressing ^C to abort the client # may be catching the keyboardinterrupt in their code. We should probably be on the # safe side and release the proxy connection in this case too, because they might # be reusing the proxy object after catching the exception... self._pyroRelease() raise def __pyroCheckSequence(self, seq): if seq!=self._pyroSeq: err="invoke: reply sequence out of sync, got %d expected %d" % (seq, self._pyroSeq) log.error(err) raise errors.ProtocolError(err) def __pyroCreateConnection(self, replaceUri=False): """ Connects this proxy to the remote Pyro daemon. Does connection handshake. Returns true if a new connection was made, false if an existing one was already present. """ with self.__pyroConnLock: if self._pyroConnection is not None: return False # already connected from Pyro4.naming import resolve # don't import this globally because of cyclic dependancy uri=resolve(self._pyroUri) # socket connection (normal or Unix domain socket) conn=None log.debug("connecting to %s", uri) connect_location=uri.sockname if uri.sockname else (uri.host, uri.port) with self.__pyroLock: try: if self._pyroConnection is not None: return False # already connected sock=socketutil.createSocket(connect=connect_location, reuseaddr=Pyro4.config.SOCK_REUSE, timeout=self.__pyroTimeout) conn=socketutil.SocketConnection(sock, uri.object) # Do handshake. For now, no need to send anything. (message type CONNECT is not yet used) msg = Message.recv(conn, None) # any trailing data (dataLen>0) is an error message, if any except Exception: x=sys.exc_info()[1] if conn: conn.close() err="cannot connect: %s" % x log.error(err) if isinstance(x, errors.CommunicationError): raise else: ce = errors.CommunicationError(err) ce.__cause__ = x raise ce else: if msg.type==Pyro4.message.MSG_CONNECTFAIL: error="connection rejected" if msg.data: data = msg.data if sys.version_info>=(3, 0): data=str(msg.data, "utf-8") error+=", reason: " + data conn.close() log.error(error) raise errors.CommunicationError(error) elif msg.type==Pyro4.message.MSG_CONNECTOK: self._pyroConnection=conn if replaceUri: self._pyroUri=uri log.debug("connected to %s", self._pyroUri) return True else: conn.close() err="connect: invalid msg type %d received" % msg.type log.error(err) raise errors.ProtocolError(err) def _pyroReconnect(self, tries=100000000): """(re)connect the proxy to the daemon containing the pyro object which the proxy is for""" self._pyroRelease() while tries: try: self.__pyroCreateConnection() return except errors.CommunicationError: tries-=1 if tries: time.sleep(2) msg="failed to reconnect" log.error(msg) raise errors.ConnectionClosedError(msg) def _pyroBatch(self): """returns a helper class that lets you create batched method calls on the proxy""" return _BatchProxyAdapter(self) def _pyroAsync(self): """returns a helper class that lets you do asynchronous method calls on the proxy""" return _AsyncProxyAdapter(self) def _pyroInvokeBatch(self, calls, oneway=False): flags=Pyro4.message.FLAGS_BATCH if oneway: flags|=Pyro4.message.FLAGS_ONEWAY return self._pyroInvoke("", calls, None, flags) class _BatchedRemoteMethod(object): """method call abstraction that is used with batched calls""" def __init__(self, calls, name): self.__calls = calls self.__name = name def __getattr__(self, name): return _BatchedRemoteMethod(self.__calls, "%s.%s" % (self.__name, name)) def __call__(self, *args, **kwargs): self.__calls.append((self.__name, args, kwargs)) class _BatchProxyAdapter(object): """Helper class that lets you batch multiple method calls into one. It is constructed with a reference to the normal proxy that will carry out the batched calls. Call methods on this object thatyou want to batch, and finally call the batch proxy itself. That call will return a generator for the results of every method call in the batch (in sequence).""" def __init__(self, proxy): self.__proxy=proxy self.__calls=[] def __getattr__(self, name): return _BatchedRemoteMethod(self.__calls, name) def __enter__(self): return self def __exit__(self, *args): pass def __copy__(self): return self def __resultsgenerator(self, results): for result in results: if isinstance(result, futures._ExceptionWrapper): result.raiseIt() # re-raise the remote exception locally. else: yield result # it is a regular result object, yield that and continue. def __call__(self, oneway=False, async=False): if oneway and async: raise errors.PyroError("async oneway calls make no sense") if async: return _AsyncRemoteMethod(self, "")() else: results=self.__proxy._pyroInvokeBatch(self.__calls, oneway) self.__calls=[] # clear for re-use if not oneway: return self.__resultsgenerator(results) def _pyroInvoke(self, name, args, kwargs): # ignore all parameters, we just need to execute the batch results=self.__proxy._pyroInvokeBatch(self.__calls) self.__calls=[] # clear for re-use return self.__resultsgenerator(results) class _AsyncProxyAdapter(object): def __init__(self, proxy): self.__proxy=proxy def __getattr__(self, name): return _AsyncRemoteMethod(self.__proxy, name) class _AsyncRemoteMethod(object): """async method call abstraction (call will run in a background thread)""" def __init__(self, proxy, name): self.__proxy = proxy self.__name = name def __getattr__(self, name): return _AsyncRemoteMethod(self.__proxy, "%s.%s" % (self.__name, name)) def __call__(self, *args, **kwargs): result=futures.FutureResult() thread=threadutil.Thread(target=self.__asynccall, args=(result, args, kwargs)) thread.setDaemon(True) thread.start() return result def __asynccall(self, asyncresult, args, kwargs): try: # use a copy of the proxy otherwise calls would be serialized, # and use contextmanager to close the proxy after we're done with self.__proxy.__copy__() as proxy: value = proxy._pyroInvoke(self.__name, args, kwargs) asyncresult.value=value except Exception: # ignore any exceptions here, return them as part of the async result instead asyncresult.value=futures._ExceptionWrapper(sys.exc_info()[1]) def batch(proxy): """convenience method to get a batch proxy adapter""" return proxy._pyroBatch() def async(proxy): """convenience method to get an async proxy adapter""" return proxy._pyroAsync() def pyroObjectToAutoProxy(self): """reduce function that automatically replaces Pyro objects by a Proxy""" if Pyro4.config.AUTOPROXY: daemon = getattr(self, "_pyroDaemon", None) if daemon: # only return a proxy if the object is a registered pyro object return Pyro4.core.Proxy(daemon.uriFor(self)) return self class DaemonObject(object): """The part of the daemon that is exposed as a Pyro object.""" def __init__(self, daemon): self.daemon=daemon def registered(self): """returns a list of all object names registered in this daemon""" return list(self.daemon.objectsById.keys()) def ping(self): """a simple do-nothing method for testing purposes""" pass class Daemon(object): """ Pyro daemon. Contains server side logic and dispatches incoming remote method calls to the appropriate objects. """ def __init__(self, host=None, port=0, unixsocket=None, nathost=None, natport=None): _check_hmac() # check if hmac secret key is set if host is None: host=Pyro4.config.HOST if nathost is None: nathost=Pyro4.config.NATHOST if natport is None: natport=Pyro4.config.NATPORT or None if nathost and unixsocket: raise ValueError("cannot use nathost together with unixsocket") if (nathost is None) ^ (natport is None): raise ValueError("must provide natport with nathost") if Pyro4.config.SERVERTYPE=="thread": self.transportServer=SocketServer_Threadpool() elif Pyro4.config.SERVERTYPE=="multiplex": # choose the 'best' multiplexing implementation if os.name=="java": raise NotImplementedError("select or poll-based server is not supported for jython, use thread server instead") self.transportServer = SocketServer_Poll() if socketutil.hasPoll else SocketServer_Select() else: raise errors.PyroError("invalid server type '%s'" % Pyro4.config.SERVERTYPE) self.transportServer.init(self, host, port, unixsocket) #: The location (str of the form ``host:portnumber``) on which the Daemon is listening self.locationStr=self.transportServer.locationStr log.debug("created daemon on %s", self.locationStr) natport_for_loc = natport if natport==0: # expose internal port number as NAT port as well. (don't use port because it could be 0 and will be chosen by the OS) natport_for_loc = int(self.locationStr.split(":")[1]) #: The NAT-location (str of the form ``nathost:natportnumber``) on which the Daemon is exposed for use with NAT-routing self.natLocationStr = "%s:%d" % (nathost, natport_for_loc) if nathost else None if self.natLocationStr: log.debug("NAT address is %s", self.natLocationStr) pyroObject=DaemonObject(self) pyroObject._pyroId=constants.DAEMON_NAME #: Dictionary from Pyro object id to the actual Pyro object registered by this id self.objectsById={pyroObject._pyroId: pyroObject} self.__mustshutdown=threadutil.Event() self.__loopstopped=threadutil.Event() self.__loopstopped.set() # assert that the configured serializers are available, and remember their ids: self.__serializer_ids = set([util.get_serializer(ser_name).serializer_id for ser_name in Pyro4.config.SERIALIZERS_ACCEPTED]) log.debug("accepted serializers: %s" % Pyro4.config.SERIALIZERS_ACCEPTED) @property def sock(self): return self.transportServer.sock @property def sockets(self): return self.transportServer.sockets @staticmethod def serveSimple(objects, host=None, port=0, daemon=None, ns=True, verbose=True): """ Very basic method to fire up a daemon (or supply one yourself). objects is a dict containing objects to register as keys, and their names (or None) as values. If ns is true they will be registered in the naming server as well, otherwise they just stay local. """ if not daemon: daemon=Daemon(host, port) with daemon: if ns: ns=Pyro4.naming.locateNS() for obj, name in objects.items(): if ns: localname=None # name is used for the name server else: localname=name # no name server, use name in daemon uri=daemon.register(obj, localname) if verbose: print("Object {0}:\n uri = {1}".format(repr(obj), uri)) if name and ns: ns.register(name, uri) if verbose: print(" name = {0}".format(name)) if verbose: print("Pyro daemon running.") daemon.requestLoop() def requestLoop(self, loopCondition=lambda: True): """ Goes in a loop to service incoming requests, until someone breaks this or calls shutdown from another thread. """ self.__mustshutdown.clear() log.info("daemon %s entering requestloop", self.locationStr) try: self.__loopstopped.clear() condition=lambda: not self.__mustshutdown.isSet() and loopCondition() self.transportServer.loop(loopCondition=condition) finally: self.__loopstopped.set() log.debug("daemon exits requestloop") def events(self, eventsockets): """for use in an external event loop: handle any requests that are pending for this daemon""" return self.transportServer.events(eventsockets) def shutdown(self): """Cleanly terminate a daemon that is running in the requestloop. It must be running in a different thread, or this method will deadlock.""" log.debug("daemon shutting down") self.__mustshutdown.set() self.transportServer.wakeup() time.sleep(0.05) self.close() self.__loopstopped.wait() log.info("daemon %s shut down", self.locationStr) def _handshake(self, conn): """Perform connection handshake with new clients""" # For now, client is not sending anything. Just respond with a CONNECT_OK. # We need a minimal amount of data or the socket will remain blocked # on some systems... (messages smaller than 40 bytes) # Return True for successful handshake, False if something was wrong. # We default to the marshal serializer to send message payload of "ok" ser = util.get_serializer("marshal") data = ser.dumps("ok") msg = Message(Pyro4.message.MSG_CONNECTOK, data, ser.serializer_id, 0, 1) conn.send(msg.to_bytes()) return True def handleRequest(self, conn): """ Handle incoming Pyro request. Catches any exception that may occur and wraps it in a reply to the calling side, as to not make this server side loop terminate due to exceptions caused by remote invocations. """ request_flags=0 request_seq=0 request_serializer_id = util.MarshalSerializer.serializer_id wasBatched=False isCallback=False try: msg = Message.recv(conn, [Pyro4.message.MSG_INVOKE, Pyro4.message.MSG_PING]) request_flags = msg.flags request_seq = msg.seq request_serializer_id = msg.serializer_id if Pyro4.config.LOGWIRE: log.debug("daemon wiredata received: msgtype=%d flags=0x%x ser=%d seq=%d data=%r" % (msg.type, msg.flags, msg.serializer_id, msg.seq, msg.data) ) if msg.type == Pyro4.message.MSG_PING: # return same seq, but ignore any data (it's a ping, not an echo). Nothing is deserialized. msg = Message(Pyro4.message.MSG_PING, b"pong", msg.serializer_id, 0, msg.seq) if Pyro4.config.LOGWIRE: log.debug("daemon wiredata sending: msgtype=%d flags=0x%x ser=%d seq=%d data=%r" % (msg.type, msg.flags, msg.serializer_id, msg.seq, msg.data)) conn.send(msg.to_bytes()) return if msg.serializer_id not in self.__serializer_ids: raise errors.ProtocolError("message used serializer that is not accepted: %d" % msg.serializer_id) serializer = util.get_serializer_by_id(msg.serializer_id) objId, method, vargs, kwargs = serializer.deserializeCall(msg.data, compressed=msg.flags & Pyro4.message.FLAGS_COMPRESSED) del msg # invite GC to collect the object, don't wait for out-of-scope obj = self.objectsById.get(objId) if obj is not None: if kwargs and sys.version_info<(2, 6, 5) and os.name!="java": # Python before 2.6.5 doesn't accept unicode keyword arguments kwargs = dict((str(k), kwargs[k]) for k in kwargs) if request_flags & Pyro4.message.FLAGS_BATCH: # batched method calls, loop over them all and collect all results data=[] for method, vargs, kwargs in vargs: method=util.resolveDottedAttribute(obj, method, Pyro4.config.DOTTEDNAMES) try: result=method(*vargs, **kwargs) # this is the actual method call to the Pyro object except Exception: xt, xv = sys.exc_info()[0:2] log.debug("Exception occurred while handling batched request: %s", xv) xv._pyroTraceback=util.formatTraceback(detailed=Pyro4.config.DETAILED_TRACEBACK) if sys.platform=="cli": util.fixIronPythonExceptionForPickle(xv, True) # piggyback attributes data.append(futures._ExceptionWrapper(xv)) break # stop processing the rest of the batch else: data.append(result) wasBatched=True else: # normal single method call method=util.resolveDottedAttribute(obj, method, Pyro4.config.DOTTEDNAMES) if request_flags & Pyro4.message.FLAGS_ONEWAY and Pyro4.config.ONEWAY_THREADED: # oneway call to be run inside its own thread thread=threadutil.Thread(target=method, args=vargs, kwargs=kwargs) thread.setDaemon(True) thread.start() else: isCallback=getattr(method, "_pyroCallback", False) data=method(*vargs, **kwargs) # this is the actual method call to the Pyro object else: log.debug("unknown object requested: %s", objId) raise errors.DaemonError("unknown object") if request_flags & Pyro4.message.FLAGS_ONEWAY: return # oneway call, don't send a response else: data, compressed = serializer.serializeData(data, compress=Pyro4.config.COMPRESSION) response_flags=0 if compressed: response_flags |= Pyro4.message.FLAGS_COMPRESSED if wasBatched: response_flags |= Pyro4.message.FLAGS_BATCH if Pyro4.config.LOGWIRE: log.debug("daemon wiredata sending: msgtype=%d flags=0x%x ser=%d seq=%d data=%r" % (Pyro4.message.MSG_RESULT, response_flags, serializer.serializer_id, request_seq, data)) msg = Message(Pyro4.message.MSG_RESULT, data, serializer.serializer_id, response_flags, request_seq) conn.send(msg.to_bytes()) except Exception: xt, xv = sys.exc_info()[0:2] if xt is not errors.ConnectionClosedError: log.debug("Exception occurred while handling request: %r", xv) if not request_flags & Pyro4.message.FLAGS_ONEWAY: # only return the error to the client if it wasn't a oneway call tblines=util.formatTraceback(detailed=Pyro4.config.DETAILED_TRACEBACK) self._sendExceptionResponse(conn, request_seq, request_serializer_id, xv, tblines) if isCallback or isinstance(xv, (errors.CommunicationError, errors.SecurityError)): raise # re-raise if flagged as callback, communication or security error. def _sendExceptionResponse(self, connection, seq, serializer_id, exc_value, tbinfo): """send an exception back including the local traceback info""" exc_value._pyroTraceback=tbinfo if sys.platform=="cli": util.fixIronPythonExceptionForPickle(exc_value, True) # piggyback attributes serializer = util.get_serializer_by_id(serializer_id) try: data, compressed = serializer.serializeData(exc_value) except: # the exception object couldn't be serialized, use a generic PyroError instead xt, xv, tb = sys.exc_info() msg = "Error serializing exception: %s. Original exception: %s: %s" % (str(xv), type(exc_value), str(exc_value)) exc_value = errors.PyroError(msg) exc_value._pyroTraceback=tbinfo if sys.platform=="cli": util.fixIronPythonExceptionForPickle(exc_value, True) # piggyback attributes data, compressed = serializer.serializeData(exc_value) flags = Pyro4.message.FLAGS_EXCEPTION if compressed: flags |= Pyro4.message.FLAGS_COMPRESSED if Pyro4.config.LOGWIRE: log.debug("daemon wiredata sending (error response): msgtype=%d flags=0x%x ser=%d seq=%d data=%r" % (Pyro4.message.MSG_RESULT, flags, serializer.serializer_id, seq, data)) msg = Message(Pyro4.message.MSG_RESULT, data, serializer.serializer_id, flags, seq) connection.send(msg.to_bytes()) def register(self, obj, objectId=None): """ Register a Pyro object under the given id. Note that this object is now only known inside this daemon, it is not automatically available in a name server. This method returns a URI for the registered object. """ if objectId: if not isinstance(objectId, basestring): raise TypeError("objectId must be a string or None") else: objectId="obj_"+uuid.uuid4().hex # generate a new objectId if hasattr(obj, "_pyroId") and obj._pyroId != "": # check for empty string is needed for Cython raise errors.DaemonError("object already has a Pyro id") if objectId in self.objectsById: raise errors.DaemonError("object already registered with that id") # set some pyro attributes obj._pyroId=objectId obj._pyroDaemon=self if Pyro4.config.AUTOPROXY: # register a custom serializer for the type to automatically return proxies # we need to do this for all known serializers for ser in util._serializers.values(): ser.register_type_replacement(type(obj), pyroObjectToAutoProxy) # register the object in the mapping self.objectsById[obj._pyroId]=obj return self.uriFor(objectId) def unregister(self, objectOrId): """ Remove an object from the known objects inside this daemon. You can unregister an object directly or with its id. """ if objectOrId is None: raise ValueError("object or objectid argument expected") if not isinstance(objectOrId, basestring): objectId=getattr(objectOrId, "_pyroId", None) if objectId is None: raise errors.DaemonError("object isn't registered") else: objectId=objectOrId objectOrId=None if objectId==constants.DAEMON_NAME: return if objectId in self.objectsById: del self.objectsById[objectId] if objectOrId is not None: del objectOrId._pyroId del objectOrId._pyroDaemon # Don't remove the custom type serializer because there may be # other registered objects of the same type still depending on it. def uriFor(self, objectOrId=None, nat=True): """ Get a URI for the given object (or object id) from this daemon. Only a daemon can hand out proper uris because the access location is contained in them. Note that unregistered objects cannot be given an uri, but unregistered object names can (it's just a string we're creating in that case). If nat is set to False, the configured NAT address (if any) is ignored and it will return an URI for the internal address. """ if not isinstance(objectOrId, basestring): objectOrId=getattr(objectOrId, "_pyroId", None) if objectOrId is None: raise errors.DaemonError("object isn't registered") if nat: loc=self.natLocationStr or self.locationStr else: loc=self.locationStr return URI("PYRO:%s@%s" % (objectOrId, loc)) def close(self): """Close down the server and release resources""" log.debug("daemon closing") if self.transportServer: self.transportServer.close() self.transportServer=None def __repr__(self): return "<%s.%s at 0x%x, %s, %d objects>" % (self.__class__.__module__, self.__class__.__name__, id(self), self.locationStr, len(self.objectsById)) def __enter__(self): if not self.transportServer: raise errors.PyroError("cannot reuse this object") return self def __exit__(self, exc_type, exc_value, traceback): self.close() def __getstate__(self): return {} # a little hack to make it possible to serialize Pyro objects, because they can reference a daemon def __getstate_for_dict__(self): return self.__getstate__() # decorators def callback(object): """ decorator to mark a method to be a 'callback'. This will make Pyro raise any errors also on the callback side, and not only on the side that does the callback call. """ object._pyroCallback=True return object try: import serpent def pyro_class_serpent_serializer(obj, serializer, stream, level): # Override the default way that a Pyro URI/proxy/daemon is serialized. # Because it defines a __getstate__ it would otherwise just become a tuple, # and not be deserialized as a class. d = Pyro4.util.SerializerBase.class_to_dict(obj) serializer.ser_builtins_dict(d, stream, level) serpent.register_class(URI, pyro_class_serpent_serializer) serpent.register_class(Proxy, pyro_class_serpent_serializer) serpent.register_class(Daemon, pyro_class_serpent_serializer) serpent.register_class(futures._ExceptionWrapper, pyro_class_serpent_serializer) except ImportError: pass def serialize_core_object_to_dict(obj): return { "__class__": "Pyro4.core." + obj.__class__.__name__, "state": obj.__getstate_for_dict__() } Pyro4.util.SerializerBase.register_class_to_dict(URI, serialize_core_object_to_dict) Pyro4.util.SerializerBase.register_class_to_dict(Proxy, serialize_core_object_to_dict) Pyro4.util.SerializerBase.register_class_to_dict(Daemon, serialize_core_object_to_dict) Pyro4.util.SerializerBase.register_class_to_dict(futures._ExceptionWrapper, futures._ExceptionWrapper.__serialized_dict__) Pyro4-4.23/src/Pyro4/errors.py000066400000000000000000000022101227003673200161460ustar00rootroot00000000000000""" Definition of the various exceptions that are used in Pyro. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ class PyroError(Exception): """Generic base of all Pyro-specific errors.""" pass class CommunicationError(PyroError): """Base class for the errors related to network communication problems.""" pass class ConnectionClosedError(CommunicationError): """The connection was unexpectedly closed.""" pass class TimeoutError(CommunicationError): """ A call could not be completed within the set timeout period, or the network caused a timeout. """ pass class ProtocolError(CommunicationError): """Pyro received a message that didn't match the active Pyro network protocol, or there was a protocol related error.""" pass class NamingError(PyroError): """There was a problem related to the name server or object names.""" pass class DaemonError(PyroError): """The Daemon encountered a problem.""" pass class SecurityError(PyroError): """A security related error occurred.""" pass Pyro4-4.23/src/Pyro4/futures.py000066400000000000000000000132511227003673200163360ustar00rootroot00000000000000""" Support for Futures (asynchronously executed callables). If you're using Python 3.2 or newer, also see http://docs.python.org/3/library/concurrent.futures.html#future-objects Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ from __future__ import with_statement import sys import functools import logging from Pyro4 import threadutil, util __all__=["Future", "FutureResult", "_ExceptionWrapper"] log=logging.getLogger("Pyro4.futures") class Future(object): """ Holds a callable that will be executed asynchronously and provide its result value some time in the future. This is a more general implementation than the AsyncRemoteMethod, which only works with Pyro proxies (and provides a bit different syntax). """ def __init__(self, callable): self.callable = callable self.chain = [] def __call__(self, *args, **kwargs): """ Start the future call with the provided arguments. Control flow returns immediately, with a FutureResult object. """ chain = self.chain del self.chain # make it impossible to add new calls to the chain once we started executing it result=FutureResult() # notice that the call chain doesn't sit on the result object thread=threadutil.Thread(target=self.__asynccall, args=(result, chain, args, kwargs)) thread.setDaemon(True) thread.start() return result def __asynccall(self, asyncresult, chain, args, kwargs): try: value = self.callable(*args, **kwargs) # now walk the callchain, passing on the previous value as first argument for call, args, kwargs in chain: call = functools.partial(call, value) value = call(*args, **kwargs) asyncresult.value = value except Exception: # ignore any exceptions here, return them as part of the async result instead asyncresult.value=_ExceptionWrapper(sys.exc_info()[1]) def then(self, call, *args, **kwargs): """ Add a callable to the call chain, to be invoked when the results become available. The result of the current call will be used as the first argument for the next call. Optional extra arguments can be provided in args and kwargs. """ self.chain.append((call, args, kwargs)) class FutureResult(object): """ The result object for asynchronous calls. """ def __init__(self): self.__ready=threadutil.Event() self.callchain=[] self.valueLock=threadutil.Lock() def wait(self, timeout=None): """ Wait for the result to become available, with optional timeout (in seconds). Returns True if the result is ready, or False if it still isn't ready. """ result=self.__ready.wait(timeout) if result is None: # older pythons return None from wait() return self.__ready.isSet() return result @property def ready(self): """Boolean that contains the readiness of the async result""" return self.__ready.isSet() def get_value(self): self.__ready.wait() if isinstance(self.__value, _ExceptionWrapper): self.__value.raiseIt() else: return self.__value def set_value(self, value): with self.valueLock: self.__value=value # walk the call chain but only as long as the result is not an exception if not isinstance(value, _ExceptionWrapper): for call, args, kwargs in self.callchain: call = functools.partial(call, self.__value) self.__value = call(*args, **kwargs) if isinstance(self.__value, _ExceptionWrapper): break self.callchain=[] self.__ready.set() value=property(get_value, set_value, None, "The result value of the call. Reading it will block if not available yet.") def then(self, call, *args, **kwargs): """ Add a callable to the call chain, to be invoked when the results become available. The result of the current call will be used as the first argument for the next call. Optional extra arguments can be provided in args and kwargs. """ if self.__ready.isSet(): # value is already known, we need to process it immediately (can't use the callchain anymore) call = functools.partial(call, self.__value) self.__value = call(*args, **kwargs) else: # add the call to the callchain, it will be processed later when the result arrives with self.valueLock: self.callchain.append((call, args, kwargs)) return self class _ExceptionWrapper(object): """Class that wraps a remote exception. If this is returned, Pyro will re-throw the exception on the receiving side. Usually this is taken care of by a special response message flag, but in the case of batched calls this flag is useless and another mechanism was needed.""" def __init__(self, exception): self.exception=exception def raiseIt(self): if sys.platform=="cli": util.fixIronPythonExceptionForPickle(self.exception, False) raise self.exception def __serialized_dict__(self): """serialized form as a dictionary""" return { "__class__": "Pyro4.futures._ExceptionWrapper", "exception": util.SerializerBase.class_to_dict(self.exception) } Pyro4-4.23/src/Pyro4/message.py000077500000000000000000000211561227003673200162730ustar00rootroot00000000000000""" The pyro wire protocol message. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ from __future__ import with_statement import hashlib import hmac import struct import logging import sys import Pyro4 from Pyro4 import constants, errors __all__ = ["Message"] log = logging.getLogger("Pyro4.message") MSG_CONNECT = 1 MSG_CONNECTOK = 2 MSG_CONNECTFAIL = 3 MSG_INVOKE = 4 MSG_RESULT = 5 MSG_PING = 6 FLAGS_EXCEPTION = 1<<0 FLAGS_COMPRESSED = 1<<1 FLAGS_ONEWAY = 1<<2 FLAGS_BATCH = 1<<3 SERIALIZER_SERPENT = 1 SERIALIZER_JSON = 2 SERIALIZER_MARSHAL = 3 SERIALIZER_PICKLE = 4 class Message(object): """ Pyro write protocol message. Wire messages contains of a fixed size header, an optional set of annotation chunks, and then the payload data. This class doesn't deal with the payload data: (de)serialization and handling of that data is done elsewhere. Annotation chunks are only parsed, except the 'HMAC' chunk: that is created and validated because it is used as a message digest. The header format is:: 4 id ('PYRO') 2 protocol version 2 message type 2 message flags 2 sequence number 4 data length 2 data serialization format (serializer id) 2 annotations length (total of all chunks, 0 if no annotation chunks present) 2 (reserved) 2 checksum After the header, zero or more annotation chunks may follow, of the format:: 4 id (ASCII) 2 chunk length x annotation chunk databytes After that, the actual payload data bytes follow. The sequencenumber is used to check if response messages correspond to the actual request message. This prevents the situation where Pyro would perhaps return the response data from another remote call (which would not result in an error otherwise!) This could happen for instance if the socket data stream gets out of sync, perhaps due To some form of signal that interrupts I/O. The header checksum is a simple sum of the header fields to make reasonably sure that we are dealing with an actual correct PYRO protocol header and not some random data that happens to start with the 'PYRO' protocol identifier. An 'HMAC' annotation chunk contains the hmac digest of the message data bytes and all of the annotation chunk data bytes (except those of the HMAC chunk itself). """ __slots__ = ["type", "flags", "seq", "data", "data_size", "serializer_id", "annotations", "annotations_size"] header_format = '!4sHHHHiHHHH' header_size = struct.calcsize(header_format) checksum_magic = 0x34E9 def __init__(self, msgType, databytes, serializer_id, flags, seq, annotations=None): self.type = msgType self.flags = flags self.seq = seq self.data = databytes self.data_size = len(self.data) self.serializer_id = serializer_id self.annotations = annotations or {} if Pyro4.config.HMAC_KEY: self.annotations["HMAC"] = self.hmac() self.annotations_size = sum([6+len(v) for v in self.annotations.values()]) if 0 < Pyro4.config.MAX_MESSAGE_SIZE < (self.data_size + self.annotations_size): raise errors.ProtocolError("max message size exceeded (%d where max=%d)" % (self.data_size+self.annotations_size, Pyro4.config.MAX_MESSAGE_SIZE)) def __repr__(self): return "<%s.%s at %x, type=%d flags=%d seq=%d datasize=%d #ann=%d>" % (self.__module__, self.__class__.__name__, id(self), self.type, self.flags, self.seq, self.data_size, len(self.annotations)) def to_bytes(self): """creates a byte stream containing the header followed by annotations (if any) followed by the data""" return self.__header_bytes() + self.__annotations_bytes() + self.data def __header_bytes(self): checksum = (self.type+constants.PROTOCOL_VERSION+self.data_size+self.annotations_size+self.serializer_id+self.flags+self.seq+self.checksum_magic)&0xffff return struct.pack(self.header_format, b"PYRO", constants.PROTOCOL_VERSION, self.type, self.flags, self.seq, self.data_size, self.serializer_id, self.annotations_size, 0, checksum) def __annotations_bytes(self): if self.annotations: a = [] for k, v in self.annotations.items(): if len(k)!=4: raise errors.ProtocolError("annotation key must be of length 4") if sys.version_info>=(3,0): k = k.encode("ASCII") a.append(struct.pack("!4sH", k, len(v))) a.append(v) if sys.platform=="cli": return "".join(a) return b"".join(a) return b"" # Note: this 'chunked' way of sending is not used because it triggers Nagle's algorithm # on some systems (linux). This causes massive delays, unless you change the socket option # TCP_NODELAY to disable the algorithm. What also works, is sending all the message bytes # in one go: connection.send(message.to_bytes()) # def send(self, connection): # """send the message as bytes over the connection""" # connection.send(self.__header_bytes()) # if self.annotations: # connection.send(self.__annotations_bytes()) # connection.send(self.data) @classmethod def from_header(cls, headerData): """Parses a message header. Does not yet process the annotations chunks and message data.""" if not headerData or len(headerData)!=cls.header_size: raise errors.ProtocolError("header data size mismatch") tag, ver, msg_type, flags, seq, data_size, serializer_id, annotations_size, _, checksum = struct.unpack(cls.header_format, headerData) if tag!=b"PYRO" or ver!=constants.PROTOCOL_VERSION: raise errors.ProtocolError("invalid data or unsupported protocol version") if checksum!=(msg_type+ver+data_size+annotations_size+flags+serializer_id+seq+cls.checksum_magic)&0xffff: raise errors.ProtocolError("header checksum mismatch") msg = Message(msg_type, b"", serializer_id, flags, seq) msg.data_size = data_size msg.annotations_size = annotations_size return msg @classmethod def recv(cls, connection, requiredMsgTypes=None): """ Receives a pyro message from a given connection. Accepts the given message types (None=any, or pass a sequence). Also reads annotation chunks and the actual payload data. Validates a HMAC chunk if present. """ msg = cls.from_header(connection.recv(cls.header_size)) if 0 < Pyro4.config.MAX_MESSAGE_SIZE < (msg.data_size+msg.annotations_size): errorMsg = "max message size exceeded (%d where max=%d)" % (msg.data_size+msg.annotations_size, Pyro4.config.MAX_MESSAGE_SIZE) log.error("connection "+str(connection)+": "+errorMsg) connection.close() # close the socket because at this point we can't return the correct sequence number for returning an error message raise errors.ProtocolError(errorMsg) if requiredMsgTypes and msg.type not in requiredMsgTypes: err = "invalid msg type %d received" % msg.type log.error(err) raise errors.ProtocolError(err) if msg.annotations_size: # read annotation chunks annotations_data = connection.recv(msg.annotations_size) msg.annotations = {} i = 0 while i < msg.annotations_size: anno, length = struct.unpack("!4sH", annotations_data[i:i+6]) if sys.version_info>=(3,0): anno = anno.decode("ASCII") msg.annotations[anno] = annotations_data[i+6:i+6+length] i += 6+length # read data msg.data = connection.recv(msg.data_size) if "HMAC" in msg.annotations and Pyro4.config.HMAC_KEY: if msg.annotations["HMAC"] != msg.hmac(): raise errors.SecurityError("message hmac mismatch") elif ("HMAC" in msg.annotations) != bool(Pyro4.config.HMAC_KEY): # Message contains hmac and local HMAC_KEY not set, or vice versa. This is not allowed. err = "hmac key config not symmetric" log.warning(err) raise errors.SecurityError(err) return msg def hmac(self): """returns the hmac of the data and the annotation chunk values (except HMAC chunk itself)""" mac = hmac.new(Pyro4.config.HMAC_KEY, self.data, digestmod=hashlib.sha1) for k, v in self.annotations.items(): if k != "HMAC": mac.update(v) return mac.digest() Pyro4-4.23/src/Pyro4/naming.py000066400000000000000000000370171227003673200161200ustar00rootroot00000000000000""" Name Server and helper functions. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ from __future__ import with_statement import re, logging, socket, sys from Pyro4 import constants, core, socketutil from Pyro4.threadutil import RLock, Thread from Pyro4.errors import PyroError, NamingError import Pyro4 __all__=["locateNS", "resolve", "startNS"] if sys.version_info>=(3, 0): basestring=str log=logging.getLogger("Pyro4.naming") class NameServer(object): """Pyro name server. Provides a simple flat name space to map logical object names to Pyro URIs.""" def __init__(self): self.namespace={} self.lock=RLock() def lookup(self, name): """Lookup the given name, returns an URI if found""" try: return core.URI(self.namespace[name]) except KeyError: raise NamingError("unknown name: "+name) def register(self, name, uri, safe=False): """Register a name with an URI. If safe is true, name cannot be registered twice. The uri can be a string or an URI object.""" if isinstance(uri, core.URI): uri=uri.asString() elif not isinstance(uri, basestring): raise TypeError("only URIs or strings can be registered") else: core.URI(uri) # check if uri is valid if not isinstance(name, basestring): raise TypeError("name must be a str") if safe and name in self.namespace: raise NamingError("name already registered: "+name) with self.lock: self.namespace[name]=uri def remove(self, name=None, prefix=None, regex=None): """Remove a registration. returns the number of items removed.""" if name and name in self.namespace and name!=constants.NAMESERVER_NAME: with self.lock: del self.namespace[name] return 1 if prefix: with self.lock: items=list(self.list(prefix=prefix).keys()) if constants.NAMESERVER_NAME in items: items.remove(constants.NAMESERVER_NAME) for item in items: del self.namespace[item] return len(items) if regex: with self.lock: items=list(self.list(regex=regex).keys()) if constants.NAMESERVER_NAME in items: items.remove(constants.NAMESERVER_NAME) for item in items: del self.namespace[item] return len(items) return 0 def list(self, prefix=None, regex=None): """Retrieve the registered items as a dictionary name-to-URI. The URIs in the resulting dict are strings, not URI objects. You can filter by prefix or by regex.""" with self.lock: if prefix: result={} for name in self.namespace: if name.startswith(prefix): result[name]=self.namespace[name] return result elif regex: result={} try: regex=re.compile(regex+"$") # add end of string marker except re.error: x=sys.exc_info()[1] raise NamingError("invalid regex: "+str(x)) else: for name in self.namespace: if regex.match(name): result[name]=self.namespace[name] return result else: # just return (a copy of) everything return self.namespace.copy() def ping(self): """A simple test method to check if the name server is running correctly.""" pass class NameServerDaemon(core.Daemon): """Daemon that contains the Name Server.""" def __init__(self, host=None, port=None, unixsocket=None, nathost=None, natport=None): if Pyro4.config.DOTTEDNAMES: raise PyroError("Name server won't start with DOTTEDNAMES enabled because of security reasons") if host is None: host=Pyro4.config.HOST if port is None: port=Pyro4.config.NS_PORT if nathost is None: nathost=Pyro4.config.NATHOST if natport is None: natport=Pyro4.config.NATPORT or None super(NameServerDaemon, self).__init__(host, port, unixsocket, nathost=nathost, natport=natport) self.nameserver=NameServer() self.register(self.nameserver, constants.NAMESERVER_NAME) self.nameserver.register(constants.NAMESERVER_NAME, self.uriFor(self.nameserver)) log.info("nameserver daemon created") def close(self): super(NameServerDaemon, self).close() self.nameserver=None def __enter__(self): if not self.nameserver: raise PyroError("cannot reuse this object") return self def __exit__(self, exc_type, exc_value, traceback): self.nameserver=None return super(NameServerDaemon, self).__exit__(exc_type, exc_value, traceback) class BroadcastServer(object): REQUEST_NSURI = "GET_NSURI" if sys.platform=="cli" else b"GET_NSURI" def __init__(self, nsUri, bchost=None, bcport=None): self.nsUri=nsUri if bcport is None: bcport=Pyro4.config.NS_BCPORT if bchost is None: bchost=Pyro4.config.NS_BCHOST if ":" in nsUri.host: # ipv6 bchost = bchost or "::" self.sock=Pyro4.socketutil.createBroadcastSocket((bchost, bcport, 0, 0), reuseaddr=Pyro4.config.SOCK_REUSE, timeout=2.0) else: self.sock=Pyro4.socketutil.createBroadcastSocket((bchost, bcport), reuseaddr=Pyro4.config.SOCK_REUSE, timeout=2.0) self._sockaddr=self.sock.getsockname() bchost=bchost or self._sockaddr[0] bcport=bcport or self._sockaddr[1] if ":" in bchost: # ipv6 self.locationStr="[%s]:%d" % (bchost, bcport) else: self.locationStr="%s:%d" % (bchost, bcport) log.info("ns broadcast server created on %s", self.locationStr) self.running=True def close(self): log.debug("ns broadcast server closing") self.running=False try: self.sock.shutdown(socket.SHUT_RDWR) except (OSError, socket.error): pass self.sock.close() def getPort(self): return self.sock.getsockname()[1] def fileno(self): return self.sock.fileno() def runInThread(self): """Run the broadcast server loop in its own thread. This is mainly for Jython, which has problems with multiplexing it using select() with the Name server itself.""" thread=Thread(target=self.__requestLoop) thread.setDaemon(True) thread.start() log.debug("broadcast server loop running in own thread") def __requestLoop(self): while self.running: self.processRequest() log.debug("broadcast server loop terminating") def processRequest(self): try: data, addr=self.sock.recvfrom(100) if data==self.REQUEST_NSURI: responsedata=core.URI(self.nsUri) if responsedata.host=="0.0.0.0": # replace INADDR_ANY address by the interface IP adress that connects to the requesting client try: interface_ip=socketutil.getInterfaceAddress(addr[0]) responsedata.host=interface_ip except socket.error: pass log.debug("responding to broadcast request from %s: interface %s", addr[0], responsedata.host) responsedata = str(responsedata).encode("iso-8859-1") self.sock.sendto(responsedata, 0, addr) except socket.error: pass def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close() def startNSloop(host=None, port=None, enableBroadcast=True, bchost=None, bcport=None, unixsocket=None, nathost=None, natport=None): """utility function that starts a new Name server and enters its requestloop.""" daemon=NameServerDaemon(host, port, unixsocket, nathost=nathost, natport=natport) nsUri=daemon.uriFor(daemon.nameserver) internalUri=daemon.uriFor(daemon.nameserver, nat=False) bcserver=None if unixsocket: hostip="Unix domain socket" else: hostip=daemon.sock.getsockname()[0] if hostip.startswith("127."): print("Not starting broadcast server for localhost.") log.info("Not starting NS broadcast server because NS is bound to localhost") enableBroadcast=False if enableBroadcast: # Make sure to pass the internal uri to the broadcast responder. # It is almost always useless to let it return the external uri, # because external systems won't be able to talk to this thing anyway. bcserver=BroadcastServer(internalUri, bchost, bcport) print("Broadcast server running on %s" % bcserver.locationStr) bcserver.runInThread() print("NS running on %s (%s)" % (daemon.locationStr, hostip)) if daemon.natLocationStr: print("internal URI = %s" % internalUri) print("external URI = %s" % nsUri) else: print("URI = %s" % nsUri) try: daemon.requestLoop() finally: daemon.close() if bcserver is not None: bcserver.close() print("NS shut down.") def startNS(host=None, port=None, enableBroadcast=True, bchost=None, bcport=None, unixsocket=None, nathost=None, natport=None): """utility fuction to quickly get a Name server daemon to be used in your own event loops. Returns (nameserverUri, nameserverDaemon, broadcastServer).""" daemon=NameServerDaemon(host, port, unixsocket, nathost=nathost, natport=natport) bcserver=None nsUri=daemon.uriFor(daemon.nameserver) if not unixsocket: hostip=daemon.sock.getsockname()[0] if hostip.startswith("127."): # not starting broadcast server for localhost. enableBroadcast=False if enableBroadcast: internalUri=daemon.uriFor(daemon.nameserver, nat=False) bcserver=BroadcastServer(internalUri, bchost, bcport) return nsUri, daemon, bcserver def locateNS(host=None, port=None): """Get a proxy for a name server somewhere in the network.""" if host is None: # first try localhost if we have a good chance of finding it there if Pyro4.config.NS_HOST in ("localhost", "::1") or Pyro4.config.NS_HOST.startswith("127."): host = Pyro4.config.NS_HOST if ":" in host: # ipv6 host="[%s]" % host uristring="PYRO:%s@%s:%d" % (constants.NAMESERVER_NAME, host, port or Pyro4.config.NS_PORT) log.debug("locating the NS: %s", uristring) proxy=core.Proxy(uristring) try: proxy.ping() log.debug("located NS") return proxy except PyroError: pass # broadcast lookup if not port: port=Pyro4.config.NS_BCPORT log.debug("broadcast locate") sock=Pyro4.socketutil.createBroadcastSocket(reuseaddr=Pyro4.config.SOCK_REUSE, timeout=0.7) for _ in range(3): try: for bcaddr in Pyro4.config.parseAddressesString(Pyro4.config.BROADCAST_ADDRS): try: sock.sendto(BroadcastServer.REQUEST_NSURI, 0, (bcaddr, port)) except socket.error: x=sys.exc_info()[1] err=getattr(x, "errno", x.args[0]) if err not in Pyro4.socketutil.ERRNO_EADDRNOTAVAIL: # yeah, windows likes to throw these... if err not in Pyro4.socketutil.ERRNO_EADDRINUSE: # and jython likes to throw thses... raise data, _=sock.recvfrom(100) try: sock.shutdown(socket.SHUT_RDWR) except (OSError, socket.error): pass sock.close() if sys.version_info>=(3, 0): data=data.decode("iso-8859-1") log.debug("located NS: %s", data) return core.Proxy(data) except socket.timeout: continue try: sock.shutdown(socket.SHUT_RDWR) except (OSError, socket.error): pass sock.close() log.debug("broadcast locate failed, try direct connection on NS_HOST") # broadcast failed, try PYRO directly on specific host host=Pyro4.config.NS_HOST port=Pyro4.config.NS_PORT # pyro direct lookup if not port: port=Pyro4.config.NS_PORT if ":" in host: host = "[%s]" % host if core.URI.isUnixsockLocation(host): uristring="PYRO:%s@%s" % (constants.NAMESERVER_NAME, host) else: uristring="PYRO:%s@%s:%d" % (constants.NAMESERVER_NAME, host, port) uri=core.URI(uristring) log.debug("locating the NS: %s", uri) proxy=core.Proxy(uri) try: proxy.ping() log.debug("located NS") return proxy except PyroError as x: e = NamingError("Failed to locate the nameserver") e.__cause__=x raise e def resolve(uri): """Resolve a 'magic' uri (PYRONAME) into the direct PYRO uri.""" if isinstance(uri, basestring): uri=core.URI(uri) elif not isinstance(uri, core.URI): raise TypeError("can only resolve Pyro URIs") if uri.protocol=="PYRO": return uri log.debug("resolving %s", uri) if uri.protocol=="PYRONAME": nameserver=locateNS(uri.host, uri.port) uri=nameserver.lookup(uri.object) nameserver._pyroRelease() return uri else: raise PyroError("invalid uri protocol") def main(args): from optparse import OptionParser parser=OptionParser() parser.add_option("-n", "--host", dest="host", help="hostname to bind server on") parser.add_option("-p", "--port", dest="port", type="int", help="port to bind server on (0=random)") parser.add_option("-u", "--unixsocket", help="Unix domain socket name to bind server on") parser.add_option("", "--bchost", dest="bchost", help="hostname to bind broadcast server on (default is \"\")") parser.add_option("", "--bcport", dest="bcport", type="int", help="port to bind broadcast server on (0=random)") parser.add_option("", "--nathost", dest="nathost", help="external hostname in case of NAT") parser.add_option("", "--natport", dest="natport", type="int", help="external port in case of NAT") parser.add_option("-x", "--nobc", dest="enablebc", action="store_false", default=True, help="don't start a broadcast server") options, args = parser.parse_args(args) startNSloop(options.host, options.port, enableBroadcast=options.enablebc, bchost=options.bchost, bcport=options.bcport, unixsocket=options.unixsocket, nathost=options.nathost, natport=options.natport) if __name__=="__main__": main(sys.argv[1:]) Pyro4-4.23/src/Pyro4/nsc.py000066400000000000000000000070321227003673200154240ustar00rootroot00000000000000""" Name server control tool. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ import sys from Pyro4 import naming, errors if sys.version_info<(3, 0): input=raw_input def handleCommand(nameserver, options, args): def printListResult(resultdict, title=""): print("--------START LIST %s" % title) for name, uri in sorted(resultdict.items()): print("%s --> %s" % (name, uri)) print("--------END LIST %s" % title) def cmd_ping(): nameserver.ping() print("Name server ping ok.") def cmd_listprefix(): if len(args)==1: printListResult(nameserver.list()) else: printListResult(nameserver.list(prefix=args[1]), "- prefix '%s'" % args[1]) def cmd_listregex(): if len(args)!=2: raise SystemExit("requires one argument: pattern") printListResult(nameserver.list(regex=args[1]), "- regex '%s'" % args[1]) def cmd_register(): if len(args)!=3: raise SystemExit("requires two arguments: name uri") nameserver.register(args[1], args[2], safe=True) print("Registered %s" % args[1]) def cmd_remove(): count=nameserver.remove(args[1]) if count>0: print("Removed %s" % args[1]) else: print("Nothing removed") def cmd_removeregex(): if len(args)!=2: raise SystemExit("requires one argument: pattern") sure=input("Potentially removing lots of items from the Name server. Are you sure (y/n)?").strip() if sure in ('y', 'Y'): count=nameserver.remove(regex=args[1]) print("%d items removed." % count) commands={ "ping": cmd_ping, "list": cmd_listprefix, "listmatching": cmd_listregex, "register": cmd_register, "remove": cmd_remove, "removematching": cmd_removeregex } try: commands[args[0]]() except Exception: xt,xv,tb=sys.exc_info() print("Error: %s - %s" % (xt.__name__,xv)) def main(args): from optparse import OptionParser usage = "usage: %prog [options] command [arguments]\nCommand is one of: " \ "register remove removematching list listmatching ping" parser = OptionParser(usage=usage) parser.add_option("-n", "--host", dest="host", help="hostname of the NS") parser.add_option("-p", "--port", dest="port", type="int", help="port of the NS (or bc-port if host isn't specified)") parser.add_option("-u","--unixsocket", help="Unix domain socket name of the NS") parser.add_option("-v", "--verbose", action="store_true", dest="verbose", help="verbose output") options, args = parser.parse_args(args) if not args or args[0] not in ("register", "remove", "removematching", "list", "listmatching", "ping"): parser.error("invalid or missing command") if options.verbose: print("Locating name server...") if options.unixsocket: options.host="./u:"+options.unixsocket try: nameserver=naming.locateNS(options.host, options.port) except errors.PyroError: x=sys.exc_info()[1] print("Failed to locate the name server: %s" % x) return if options.verbose: print("Name server found: %s" % nameserver._pyroUri) handleCommand(nameserver, options, args) if options.verbose: print("Done.") if __name__=="__main__": main(sys.argv[1:]) Pyro4-4.23/src/Pyro4/socketserver/000077500000000000000000000000001227003673200170045ustar00rootroot00000000000000Pyro4-4.23/src/Pyro4/socketserver/__init__.py000066400000000000000000000002051227003673200211120ustar00rootroot00000000000000""" Package for the various server types. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ Pyro4-4.23/src/Pyro4/socketserver/multiplexserver.py000066400000000000000000000222141227003673200226310ustar00rootroot00000000000000""" Socket server based on socket multiplexing. Doesn't use threads. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ import socket, select, sys, logging, os from Pyro4 import socketutil, errors, util import Pyro4 log=logging.getLogger("Pyro4.socketserver.multiplexed") class MultiplexedSocketServerBase(object): """base class for multiplexed transport server for socket connections""" def init(self, daemon, host, port, unixsocket=None): log.info("starting multiplexed socketserver") self.sock=None bind_location=unixsocket if unixsocket else (host, port) self.sock=socketutil.createSocket(bind=bind_location, reuseaddr=Pyro4.config.SOCK_REUSE, timeout=Pyro4.config.COMMTIMEOUT, noinherit=True) self.clients=set() self.daemon=daemon sockaddr=self.sock.getsockname() if sockaddr[0].startswith("127."): if host is None or host.lower()!="localhost" and not host.startswith("127."): log.warning("weird DNS setup: %s resolves to localhost (127.x.x.x)", host) if unixsocket: self.locationStr="./u:"+unixsocket else: host=host or sockaddr[0] port=port or sockaddr[1] if ":" in host: # ipv6 self.locationStr="[%s]:%d" % (host, port) else: self.locationStr="%s:%d" % (host, port) def __repr__(self): return "<%s on %s, %d connections>" % (self.__class__.__name__, self.locationStr, len(self.clients)) def __del__(self): if self.sock is not None: self.sock.close() self.sock=None def events(self, eventsockets): """used for external event loops: handle events that occur on one of the sockets of this server""" for s in eventsockets: if s is self.sock: # server socket, means new connection conn=self._handleConnection(self.sock) if conn: self.clients.add(conn) else: # must be client socket, means remote call active = self.handleRequest(s) if not active: s.close() self.clients.discard(s) def _handleConnection(self, sock): try: if sock is None: return csock, caddr = sock.accept() if Pyro4.config.COMMTIMEOUT: csock.settimeout(Pyro4.config.COMMTIMEOUT) except socket.error: x=sys.exc_info()[1] err=getattr(x, "errno", x.args[0]) if err in socketutil.ERRNO_RETRIES: # just ignore this error for now and continue log.warning("accept() failed errno=%d, shouldn't happen", err) return None if err in socketutil.ERRNO_BADF or err in socketutil.ERRNO_ENOTSOCK: # our server socket got destroyed raise errors.ConnectionClosedError("server socket closed") raise try: conn=socketutil.SocketConnection(csock) if self.daemon._handshake(conn): return conn except: # catch all errors, otherwise the event loop could terminate ex_t, ex_v, ex_tb = sys.exc_info() tb = util.formatTraceback(ex_t, ex_v, ex_tb) log.warning("error during connect/handshake: %s; %s", ex_v, "\n".join(tb)) try: csock.shutdown(socket.SHUT_RDWR) except (OSError, socket.error): pass csock.close() return None def close(self): log.debug("closing socketserver") if self.sock: sockname=None try: sockname=self.sock.getsockname() except socket.error: pass self.sock.close() if type(sockname) is str: # it was a Unix domain socket, remove it from the filesystem if os.path.exists(sockname): os.remove(sockname) self.sock=None for c in self.clients: try: c.close() except Exception: pass self.clients=set() @property def sockets(self): socks=[self.sock] socks.extend(self.clients) return socks def wakeup(self): """bit of a hack to trigger a blocking server to get out of the loop, useful at clean shutdowns""" socketutil.triggerSocket(self.sock) def handleRequest(self, conn): """Handles a single connection request event and returns if the connection is still active""" try: self.daemon.handleRequest(conn) return True except (socket.error, errors.ConnectionClosedError, errors.SecurityError): # client went away or caused a security error. # close the connection silently. return False except: # other error occurred, close the connection, but also log a warning ex_t, ex_v, ex_tb = sys.exc_info() tb = util.formatTraceback(ex_t, ex_v, ex_tb) log.warning("error during handleRequest: %s; %s", ex_v, "\n".join(tb)) return False class SocketServer_Poll(MultiplexedSocketServerBase): """transport server for socket connections, poll loop multiplex version.""" def loop(self, loopCondition=lambda: True): log.debug("enter poll-based requestloop") poll=select.poll() try: fileno2connection={} # map fd to original connection object rlist=list(self.clients)+[self.sock] for r in rlist: poll.register(r.fileno(), select.POLLIN | select.POLLPRI) fileno2connection[r.fileno()]=r while loopCondition(): polls=poll.poll(1000*Pyro4.config.POLLTIMEOUT) for (fd, mask) in polls: conn=fileno2connection[fd] if conn is self.sock: conn=self._handleConnection(self.sock) if conn: poll.register(conn.fileno(), select.POLLIN | select.POLLPRI) fileno2connection[conn.fileno()]=conn self.clients.add(conn) else: active = self.handleRequest(conn) if not active: try: fn=conn.fileno() except socket.error: pass else: conn.close() self.clients.discard(conn) if fn in fileno2connection: poll.unregister(fn) del fileno2connection[fn] except KeyboardInterrupt: log.debug("stopping on break signal") pass finally: if hasattr(poll, "close"): poll.close() log.debug("exit poll-based requestloop") class SocketServer_Select(MultiplexedSocketServerBase): """transport server for socket connections, select loop version.""" def loop(self, loopCondition=lambda: True): log.debug("entering select-based requestloop") while loopCondition(): try: rlist=list(self.clients) rlist.append(self.sock) try: rlist, _, _=select.select(rlist, [], [], Pyro4.config.POLLTIMEOUT) except select.error: if loopCondition(): raise else: # swallow the select error if the loopcondition is no longer true, and exit loop # this can occur if we are shutting down and the socket is no longer valid break if self.sock in rlist: try: rlist.remove(self.sock) except ValueError: pass # this can occur when closing down, even when we just tested for presence in the list conn=self._handleConnection(self.sock) if conn: self.clients.add(conn) for conn in rlist: # no need to remove conn from rlist, because no more processing is done after this if conn in self.clients: active = self.handleRequest(conn) if not active: conn.close() self.clients.discard(conn) except socket.timeout: pass # just continue the loop on a timeout except KeyboardInterrupt: log.debug("stopping on break signal") break log.debug("exit select-based requestloop") Pyro4-4.23/src/Pyro4/socketserver/threadpoolserver.py000066400000000000000000000164331227003673200227550ustar00rootroot00000000000000""" Socket server based on a worker thread pool. Doesn't use select. Uses a single worker thread per client connection. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ from __future__ import with_statement import socket, logging, sys, os import struct from Pyro4 import socketutil, errors import Pyro4.tpjobqueue log=logging.getLogger("Pyro4.socketserver.threadpool") class ClientConnectionJob(object): """ Takes care of a single client connection and all requests that may arrive during its life span. """ def __init__(self, clientSocket, clientAddr, daemon): self.csock = socketutil.SocketConnection(clientSocket) self.caddr = clientAddr self.daemon = daemon def __call__(self): log.debug("job call() %s", self.caddr) # XXX remove this after issue #19 is fixed if self.handleConnection(): try: while True: try: self.daemon.handleRequest(self.csock) except (socket.error, errors.ConnectionClosedError): # client went away. log.debug("disconnected %s", self.caddr) break except errors.SecurityError: log.debug("security error on client %s", self.caddr) break # other errors simply crash the client worker thread (and close its connection) finally: self.csock.close() def handleConnection(self): # connection handshake try: if self.daemon._handshake(self.csock): return True except: ex_t, ex_v, ex_tb = sys.exc_info() tb = Pyro4.util.formatTraceback(ex_t, ex_v, ex_tb) log.warning("error during connect/handshake: %s; %s", ex_v, "\n".join(tb)) self.csock.close() return False def interrupt(self): """attempt to interrupt the worker's request loop""" try: self.csock.sock.shutdown(socket.SHUT_RDWR) self.csock.sock.setblocking(False) except (OSError, socket.error): pass if hasattr(socket, "SO_RCVTIMEO"): # setting a recv timeout seems to break the blocking call to recv() on some systems try: self.csock.sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, struct.pack("ii", 1,1)) except socket.error: pass self.csock.close() class SocketServer_Threadpool(object): """transport server for socket connections, worker thread pool version.""" def init(self, daemon, host, port, unixsocket=None): log.info("starting thread pool socketserver") self.daemon = daemon self.sock=None bind_location=unixsocket if unixsocket else (host,port) self.sock=socketutil.createSocket(bind=bind_location, reuseaddr=Pyro4.config.SOCK_REUSE, timeout=Pyro4.config.COMMTIMEOUT, noinherit=True) self._socketaddr=self.sock.getsockname() if self._socketaddr[0].startswith("127."): if host is None or host.lower()!="localhost" and not host.startswith("127."): log.warning("weird DNS setup: %s resolves to localhost (127.x.x.x)", host) if unixsocket: self.locationStr="./u:"+unixsocket else: host=host or self._socketaddr[0] port=port or self._socketaddr[1] if ":" in host: # ipv6 self.locationStr="[%s]:%d" % (host, port) else: self.locationStr="%s:%d" % (host, port) self.jobqueue = Pyro4.tpjobqueue.ThreadPooledJobQueue() log.info("%d workers started", self.jobqueue.workercount) def __del__(self): if self.sock is not None: self.sock.close() if self.jobqueue is not None: self.jobqueue.close() def __repr__(self): return "<%s on %s, %d workers, %d jobs>" % (self.__class__.__name__, self.locationStr, self.jobqueue.workercount, self.jobqueue.jobcount) def loop(self, loopCondition=lambda: True): log.debug("threadpool server requestloop") while (self.sock is not None) and loopCondition(): try: self.events([self.sock]) except socket.error: x=sys.exc_info()[1] err=getattr(x, "errno", x.args[0]) if not loopCondition(): # swallow the socket error if loop terminates anyway # this can occur if we are asked to shutdown, socket can be invalid then break if err in socketutil.ERRNO_RETRIES: continue else: raise except KeyboardInterrupt: log.debug("stopping on break signal") break log.debug("threadpool server exits requestloop") def events(self, eventsockets): """used for external event loops: handle events that occur on one of the sockets of this server""" # we only react on events on our own server socket. # all other (client) sockets are owned by their individual threads. assert self.sock in eventsockets try: csock, caddr=self.sock.accept() log.debug("connected %s", caddr) if Pyro4.config.COMMTIMEOUT: csock.settimeout(Pyro4.config.COMMTIMEOUT) self.jobqueue.process(ClientConnectionJob(csock, caddr, self.daemon)) except socket.timeout: pass # just continue the loop on a timeout on accept def close(self, joinWorkers=True): log.debug("closing threadpool server") if self.sock: sockname=None try: sockname=self.sock.getsockname() except socket.error: pass try: self.sock.close() if type(sockname) is str: # it was a Unix domain socket, remove it from the filesystem if os.path.exists(sockname): os.remove(sockname) except Exception: pass self.sock=None self.jobqueue.close() for worker in self.jobqueue.busy.copy(): if worker.job is not None: worker.job.interrupt() # try to break all busy workers if joinWorkers: self.jobqueue.drain() @property def sockets(self): # the server socket is all we care about, all client sockets are running in their own threads return [self.sock] def wakeup(self): interruptSocket(self._socketaddr) def interruptSocket(address): """bit of a hack to trigger a blocking server to get out of the loop, useful at clean shutdowns""" try: sock=socketutil.createSocket(connect=address, keepalive=False, timeout=None) socketutil.triggerSocket(sock) try: sock.shutdown(socket.SHUT_RDWR) except (OSError, socket.error): pass sock.close() except socket.error: pass Pyro4-4.23/src/Pyro4/socketutil.py000066400000000000000000000507631227003673200170400ustar00rootroot00000000000000""" Low level socket utilities. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ import socket, os, errno, time, sys from Pyro4.errors import ConnectionClosedError, TimeoutError, CommunicationError import Pyro4 import select if os.name=="java": selectfunction = select.cpython_compatible_select else: selectfunction = select.select if sys.platform=="win32": USE_MSG_WAITALL = False # it doesn't work reliably on Windows even though it's defined else: USE_MSG_WAITALL = hasattr(socket, "MSG_WAITALL") # Note: other interesting errnos are EPERM, ENOBUFS, EMFILE # but it seems to me that all these signify an unrecoverable situation. # So I didn't include them in de list of retryable errors. ERRNO_RETRIES=[errno.EINTR, errno.EAGAIN, errno.EWOULDBLOCK, errno.EINPROGRESS] if hasattr(errno, "WSAEINTR"): ERRNO_RETRIES.append(errno.WSAEINTR) if hasattr(errno, "WSAEWOULDBLOCK"): ERRNO_RETRIES.append(errno.WSAEWOULDBLOCK) if hasattr(errno, "WSAEINPROGRESS"): ERRNO_RETRIES.append(errno.WSAEINPROGRESS) ERRNO_BADF=[errno.EBADF] if hasattr(errno, "WSAEBADF"): ERRNO_BADF.append(errno.WSAEBADF) ERRNO_ENOTSOCK=[errno.ENOTSOCK] if hasattr(errno, "WSAENOTSOCK"): ERRNO_ENOTSOCK.append(errno.WSAENOTSOCK) if not hasattr(socket, "SOL_TCP"): socket.SOL_TCP=socket.IPPROTO_TCP ERRNO_EADDRNOTAVAIL=[errno.EADDRNOTAVAIL] if hasattr(errno, "WSAEADDRNOTAVAIL"): ERRNO_EADDRNOTAVAIL.append(errno.WSAEADDRNOTAVAIL) ERRNO_EADDRINUSE=[errno.EADDRINUSE] if hasattr(errno, "WSAEADDRINUSE"): ERRNO_EADDRINUSE.append(errno.WSAEADDRINUSE) if sys.version_info >= (3, 0): basestring = str def getIpVersion(hostnameOrAddress): """ Determine what the IP version is of the given hostname or ip address (4 or 6). First, it resolves the hostname or address to get an IP address. Then, if the resolved IP contains a ':' it is considered to be an ipv6 address, and if it contains a '.', it is ipv4. """ address = getIpAddress(hostnameOrAddress) if "." in address: return 4 elif ":" in address: return 6 else: raise CommunicationError("Unknown IP address format" + address) def getIpAddress(hostname, workaround127=False, ipVersion=None): """ Returns the IP address for the given host. If you enable the workaround, it will use a little hack if the ip address is found to be the loopback address. The hack tries to discover an externally visible ip address instead (this only works for ipv4 addresses). Set ipVersion=6 to return ipv6 addresses, 4 to return ipv4, 0 to let OS choose the best one or None to use Pyro4.config.PREFER_IP_VERSION. """ def getaddr(ipVersion): if ipVersion == 6: family=socket.AF_INET6 elif ipVersion == 4: family=socket.AF_INET elif ipVersion == 0: family=socket.AF_UNSPEC else: raise ValueError("unknown value for argument ipVersion.") ip=socket.getaddrinfo(hostname or socket.gethostname(), 80, family, socket.SOCK_STREAM, socket.SOL_TCP)[0][4][0] if workaround127 and (ip.startswith("127.") or ip=="0.0.0.0"): ip=getInterfaceAddress("4.2.2.2") return ip try: if hostname and ':' in hostname and ipVersion is None: ipVersion = 0 return getaddr(Pyro4.config.PREFER_IP_VERSION) if ipVersion is None else getaddr(ipVersion) except socket.gaierror: if ipVersion == 6 or (ipVersion is None and Pyro4.config.PREFER_IP_VERSION == 6): # try a (inefficient, but hey) workaround to obtain the ipv6 address: # attempt to connect to one of a few ipv6-servers (google's public dns servers), # and obtain the connected socket's address. (This will only work with an active internet connection) # The Google Public DNS IP addresses are as follows: 8.8.8.8, 8.8.4.4 # The Google Public DNS IPv6 addresses are as follows: 2001:4860:4860::8888, 2001:4860:4860::8844 for address in ["2001:4860:4860::8888", "2001:4860:4860::8844"]: try: return getInterfaceAddress(address) except socket.error: pass raise socket.error("unable to determine IPV6 address") return getaddr(0) def getInterfaceAddress(ip_address): """tries to find the ip address of the interface that connects to the given host's address""" family = socket.AF_INET if getIpVersion(ip_address)==4 else socket.AF_INET6 sock = socket.socket(family, socket.SOCK_DGRAM) try: sock.connect((ip_address, 53)) # 53=dns return sock.getsockname()[0] finally: sock.close() def __nextRetrydelay(delay): # first try a few very short delays, # if that doesn't work, increase by 0.1 sec every time if delay==0.0: return 0.001 if delay==0.001: return 0.01 return delay+0.1 def receiveData(sock, size): """Retrieve a given number of bytes from a socket. It is expected the socket is able to supply that number of bytes. If it isn't, an exception is raised (you will not get a zero length result or a result that is smaller than what you asked for). The partial data that has been received however is stored in the 'partialData' attribute of the exception object.""" try: retrydelay=0.0 msglen=0 chunks=[] EMPTY_BYTES = b"" if sys.platform=="cli": EMPTY_BYTES = "" if USE_MSG_WAITALL: # waitall is very convenient and if a socket error occurs, # we can assume the receive has failed. No need for a loop, # unless it is a retryable error. # Some systems have an erratic MSG_WAITALL and sometimes still return # less bytes than asked. In that case, we drop down into the normal # receive loop to finish the task. while True: try: data=sock.recv(size, socket.MSG_WAITALL) if len(data)==size: return data # less data than asked, drop down into normal receive loop to finish msglen=len(data) chunks=[data] break except socket.timeout: raise TimeoutError("receiving: timeout") except socket.error: x=sys.exc_info()[1] err=getattr(x, "errno", x.args[0]) if err not in ERRNO_RETRIES: raise ConnectionClosedError("receiving: connection lost: "+str(x)) time.sleep(0.00001+retrydelay) # a slight delay to wait before retrying retrydelay=__nextRetrydelay(retrydelay) # old fashioned recv loop, we gather chunks until the message is complete while True: try: while msglen 0: raise JobQueueError("there are still active workers") @property def workercount(self): return len(self.idle) + len(self.busy) @property def workercountSafe(self): with self.lock: return len(self.idle) + len(self.busy) @property def jobcount(self): return self.jobs.qsize() def __repr__(self): return "<%s.%s at 0x%x, %d idle, %d busy, %d jobs>" % \ (self.__class__.__module__, self.__class__.__name__, id(self), len(self.idle), len(self.busy), self.jobcount) def process(self, job): """ Add the job to the general job queue. Job is any callable object. If there's no idle worker available to service it, a new one is spawned as long as the pool size permits it. """ with self.lock: if self.closed: raise JobQueueError("job queue is closed") self.jobs.put(job) if self.jobcount > 0: if not self.idle: self.__spawnIdle() spawnamount = self.jobcount while spawnamount > 1: self.__spawnIdle() spawnamount -= 1 def setIdle(self, worker): with self.lock: self.busy.remove(worker) self.idle.add(worker) def setBusy(self, worker): with self.lock: self.idle.remove(worker) self.busy.add(worker) def halted(self, worker, crashed=False): """Called by a worker when it halts (exits). This removes the worker from the bookkeeping.""" with self.lock: self.__halted(worker) def __halted(self, worker): # Lock-free version that is used internally if worker in self.idle: self.idle.remove(worker) if worker in self.busy: self.busy.remove(worker) log.debug("worker halted: %s", worker.name) def attemptHalt(self, worker): """ Called by a worker to signal it intends to halt. Returns true or false depending on whether the worker was actually allowed to halt. """ with self.lock: if self.workercount > Pyro4.config.THREADPOOL_MINTHREADS: self.__halted(worker) return True return False def getJob(self): """ Called by a worker to obtain a new job from the queue. If there's no job available in the timeout period given by the THREADPOOL_IDLETIMEOUT config item, NoJobAvailableError is raised. """ if self.closed: return None try: return self.jobs.get(timeout=Pyro4.config.THREADPOOL_IDLETIMEOUT) except queue.Empty: raise NoJobAvailableError("queue is empty") def __spawnIdle(self): """ Spawn a new idle worker if there is still room in the pool. (must only be called with self.lock acquired) """ if self.workercount >= Pyro4.config.THREADPOOL_MAXTHREADS: return worker = Worker(self) self.idle.add(worker) log.debug("spawned new idle worker: %s", worker.name) worker.start() Pyro4-4.23/src/Pyro4/util.py000066400000000000000000000553411227003673200156240ustar00rootroot00000000000000""" Miscellaneous utilities. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ import sys, zlib, logging import traceback, linecache try: import copyreg except ImportError: import copy_reg as copyreg import Pyro4 import Pyro4.errors import Pyro4.message log=logging.getLogger("Pyro4.util") def getPyroTraceback(ex_type=None, ex_value=None, ex_tb=None): """Returns a list of strings that form the traceback information of a Pyro exception. Any remote Pyro exception information is included. Traceback information is automatically obtained via ``sys.exc_info()`` if you do not supply the objects yourself.""" def formatRemoteTraceback(remote_tb_lines): result=[" +--- This exception occured remotely (Pyro) - Remote traceback:"] for line in remote_tb_lines: if line.endswith("\n"): line = line[:-1] lines = line.split("\n") for line in lines: result.append("\n | ") result.append(line) result.append("\n +--- End of remote traceback\n") return result try: if ex_type is not None and ex_value is None and ex_tb is None: # possible old (3.x) call syntax where caller is only providing exception object if type(ex_type) is not type: raise TypeError("invalid argument: ex_type should be an exception type, or just supply no arguments at all") if ex_type is None and ex_tb is None: ex_type, ex_value, ex_tb=sys.exc_info() remote_tb=getattr(ex_value, "_pyroTraceback", None) local_tb=formatTraceback(ex_type, ex_value, ex_tb, Pyro4.config.DETAILED_TRACEBACK) if remote_tb: remote_tb=formatRemoteTraceback(remote_tb) return local_tb + remote_tb else: # hmm. no remote tb info, return just the local tb. return local_tb finally: # clean up cycle to traceback, to allow proper GC del ex_type, ex_value, ex_tb def formatTraceback(ex_type=None, ex_value=None, ex_tb=None, detailed=False): """Formats an exception traceback. If you ask for detailed formatting, the result will contain info on the variables in each stack frame. You don't have to provide the exception info objects, if you omit them, this function will obtain them itself using ``sys.exc_info()``.""" if ex_type is not None and ex_value is None and ex_tb is None: # possible old (3.x) call syntax where caller is only providing exception object if type(ex_type) is not type: raise TypeError("invalid argument: ex_type should be an exception type, or just supply no arguments at all") if ex_type is None and ex_tb is None: ex_type, ex_value, ex_tb=sys.exc_info() if detailed and sys.platform!="cli": # detailed tracebacks don't work in ironpython (most of the local vars are omitted) def makeStrValue(value): try: return repr(value) except: try: return str(value) except: return "" try: result=["-"*52+"\n"] result.append(" EXCEPTION %s: %s\n" % (ex_type, ex_value)) result.append(" Extended stacktrace follows (most recent call last)\n") skipLocals=True # don't print the locals of the very first stackframe while ex_tb: frame=ex_tb.tb_frame sourceFileName=frame.f_code.co_filename if "self" in frame.f_locals: location="%s.%s" % (frame.f_locals["self"].__class__.__name__, frame.f_code.co_name) else: location=frame.f_code.co_name result.append("-"*52+"\n") result.append("File \"%s\", line %d, in %s\n" % (sourceFileName, ex_tb.tb_lineno, location)) result.append("Source code:\n") result.append(" "+linecache.getline(sourceFileName, ex_tb.tb_lineno).strip()+"\n") if not skipLocals: names=set() names.update(getattr(frame.f_code, "co_varnames", ())) names.update(getattr(frame.f_code, "co_names", ())) names.update(getattr(frame.f_code, "co_cellvars", ())) names.update(getattr(frame.f_code, "co_freevars", ())) result.append("Local values:\n") for name in sorted(names): if name in frame.f_locals: value=frame.f_locals[name] result.append(" %s = %s\n" % (name, makeStrValue(value))) if name=="self": # print the local variables of the class instance for name, value in vars(value).items(): result.append(" self.%s = %s\n" % (name, makeStrValue(value))) skipLocals=False ex_tb=ex_tb.tb_next result.append("-"*52+"\n") result.append(" EXCEPTION %s: %s\n" % (ex_type, ex_value)) result.append("-"*52+"\n") return result except Exception: return ["-"*52+"\nError building extended traceback!!! :\n", "".join(traceback.format_exception(*sys.exc_info())) + '-'*52 + '\n', "Original Exception follows:\n", "".join(traceback.format_exception(ex_type, ex_value, ex_tb))] else: # default traceback format. return traceback.format_exception(ex_type, ex_value, ex_tb) all_exceptions = {} if sys.version_info < (3, 0): import exceptions for name, t in vars(exceptions).items(): if type(t) is type and issubclass(t, BaseException): all_exceptions[name] = t else: import builtins for name, t in vars(builtins).items(): if type(t) is type and issubclass(t, BaseException): all_exceptions[name] = t for name, t in vars(Pyro4.errors).items(): if type(t) is type and issubclass(t, Pyro4.errors.PyroError): all_exceptions[name] = t class SerializerBase(object): """Base class for (de)serializer implementations (which must be thread safe)""" __custom_class_to_dict_registry = {} def serializeData(self, data, compress=False): """Serialize the given data object, try to compress if told so. Returns a tuple of the serialized data (bytes) and a bool indicating if it is compressed or not.""" data=self.dumps(data) return self.__compressdata(data, compress) def deserializeData(self, data, compressed=False): """Deserializes the given data (bytes). Set compressed to True to decompress the data first.""" if compressed: data=zlib.decompress(data) return self.loads(data) def serializeCall(self, obj, method, vargs, kwargs, compress=False): """Serialize the given method call parameters, try to compress if told so. Returns a tuple of the serialized data and a bool indicating if it is compressed or not.""" data=self.dumpsCall(obj, method, vargs, kwargs) return self.__compressdata(data, compress) def deserializeCall(self, data, compressed=False): """Deserializes the given call data back to (object, method, vargs, kwargs) tuple. Set compressed to True to decompress the data first.""" if compressed: data=zlib.decompress(data) return self.loadsCall(data) def loads(self, data): raise NotImplementedError("implement in subclass") def loadsCall(self, data): raise NotImplementedError("implement in subclass") def dumps(self, data): raise NotImplementedError("implement in subclass") def dumpsCall(self, obj, method, vargs, kwargs): raise NotImplementedError("implement in subclass") def __compressdata(self, data, compress): if not compress or len(data)<200: return data, False # don't waste time compressing small messages compressed=zlib.compress(data) if len(compressed)") if "__" in classname: raise Pyro4.errors.SecurityError("refused to deserialize types with double underscores in their name: "+classname) if classname.startswith("Pyro4.core."): if classname=="Pyro4.core.URI": uri = Pyro4.core.URI.__new__(Pyro4.core.URI) uri.__setstate__(data["state"]) return uri elif classname=="Pyro4.core.Proxy": proxy = Pyro4.core.Proxy.__new__(Pyro4.core.Proxy) state = data["state"] uri = Pyro4.core.URI(state[0]) oneway = set(state[1]) timeout = state[2] proxy.__setstate__((uri, oneway, timeout)) return proxy elif classname=="Pyro4.core.Daemon": return Pyro4.core.Daemon.__new__(Pyro4.core.Daemon) elif classname.startswith("Pyro4.util."): if classname=="Pyro4.util.PickleSerializer": return PickleSerializer() elif classname=="Pyro4.util.MarshalSerializer": return MarshalSerializer() elif classname=="Pyro4.util.JsonSerializer": return JsonSerializer() elif classname=="Pyro4.util.SerpentSerializer": return SerpentSerializer() elif classname.startswith("Pyro4.errors."): errortype = getattr(Pyro4.errors, classname.split('.', 2)[2]) if issubclass(errortype, Pyro4.errors.PyroError): return SerializerBase.make_exception(errortype, data) elif classname == "Pyro4.futures._ExceptionWrapper": ex = SerializerBase.dict_to_class(data["exception"]) return Pyro4.futures._ExceptionWrapper(ex) elif classname.startswith("builtins."): exceptiontype = getattr(builtins, classname.split('.', 1)[1]) if issubclass(exceptiontype, BaseException): return SerializerBase.make_exception(exceptiontype, data) elif classname.startswith("exceptions."): exceptiontype = getattr(exceptions, classname.split('.', 1)[1]) if issubclass(exceptiontype, BaseException): return SerializerBase.make_exception(exceptiontype, data) elif classname in all_exceptions: return SerializerBase.make_exception(all_exceptions[classname], data) # try one of the serializer classes for serializer in _serializers.values(): if classname == serializer.__class__.__name__: return serializer raise Pyro4.errors.ProtocolError("unsupported serialized class: "+classname) @staticmethod def make_exception(exceptiontype, data): ex = exceptiontype(*data["args"]) if "attributes" in data: # restore custom attributes on the exception object for attr, value in data["attributes"].items(): setattr(ex, attr, value) return ex def recreate_classes(self, literal): t = type(literal) if t is set: return set([self.recreate_classes(x) for x in literal]) if t is list: return [self.recreate_classes(x) for x in literal] if t is tuple: return tuple(self.recreate_classes(x) for x in literal) if t is dict: if "__class__" in literal: return self.dict_to_class(literal) result = {} for key, value in literal.items(): result[key] = self.recreate_classes(value) return result return literal def __eq__(self, other): """this equality method is only to support the unit tests of this class""" return isinstance(other, SerializerBase) and vars(self)==vars(other) def __ne__(self, other): return not self.__eq__(other) __hash__=object.__hash__ class PickleSerializer(SerializerBase): """ A (de)serializer that wraps the Pickle serialization protocol. It can optionally compress the serialized data, and is thread safe. """ serializer_id = Pyro4.message.SERIALIZER_PICKLE def dumpsCall(self, obj, method, vargs, kwargs): return pickle.dumps((obj, method, vargs, kwargs), pickle.HIGHEST_PROTOCOL) def dumps(self, data): return pickle.dumps(data, pickle.HIGHEST_PROTOCOL) def loadsCall(self, data): return pickle.loads(data) def loads(self, data): return pickle.loads(data) @classmethod def register_type_replacement(cls, object_type, replacement_function): def copyreg_function(obj): return replacement_function(obj).__reduce__() try: copyreg.pickle(object_type, copyreg_function) except TypeError: pass class MarshalSerializer(SerializerBase): """(de)serializer that wraps the marshal serialization protocol.""" serializer_id = Pyro4.message.SERIALIZER_MARSHAL def dumpsCall(self, obj, method, vargs, kwargs): return marshal.dumps((obj, method, vargs, kwargs)) def dumps(self, data): try: return marshal.dumps(data) except (ValueError, TypeError): return marshal.dumps(self.class_to_dict(data)) def loadsCall(self, data): obj, method, vargs, kwargs = marshal.loads(data) vargs = self.recreate_classes(vargs) kwargs = self.recreate_classes(kwargs) return obj, method, vargs, kwargs def loads(self, data): return self.recreate_classes(marshal.loads(data)) @classmethod def register_type_replacement(cls, object_type, replacement_function): pass # marshal serializer doesn't support per-type hooks class SerpentSerializer(SerializerBase): """(de)serializer that wraps the serpent serialization protocol.""" serializer_id = Pyro4.message.SERIALIZER_SERPENT def dumpsCall(self, obj, method, vargs, kwargs): return serpent.dumps((obj, method, vargs, kwargs)) def dumps(self, data): return serpent.dumps(data) def loadsCall(self, data): obj, method, vargs, kwargs = serpent.loads(data) vargs = self.recreate_classes(vargs) kwargs = self.recreate_classes(kwargs) return obj, method, vargs, kwargs def loads(self, data): return self.recreate_classes(serpent.loads(data)) @classmethod def register_type_replacement(cls, object_type, replacement_function): def custom_serializer(object, serpent_serializer, outputstream, indentlevel): replaced = replacement_function(object) if replaced is object: serpent_serializer.ser_default_class(replaced, outputstream, indentlevel) else: serpent_serializer._serialize(replaced, outputstream, indentlevel) serpent.register_class(object_type, custom_serializer) class JsonSerializer(SerializerBase): """(de)serializer that wraps the json serialization protocol.""" serializer_id = Pyro4.message.SERIALIZER_JSON __type_replacements = {} def dumpsCall(self, obj, method, vargs, kwargs): data = {"object": obj, "method": method, "params": vargs, "kwargs": kwargs} data = json.dumps(data, ensure_ascii=False, default=self.default) return data.encode("utf-8") def dumps(self, data): data = json.dumps(data, ensure_ascii=False, default=self.default) return data.encode("utf-8") def loadsCall(self, data): data=data.decode("utf-8") data = json.loads(data) vargs = self.recreate_classes(data["params"]) kwargs = self.recreate_classes(data["kwargs"]) return data["object"], data["method"], vargs, kwargs def loads(self, data): data=data.decode("utf-8") return self.recreate_classes(json.loads(data)) def default(self, obj): replacer = self.__type_replacements.get(type(obj), None) if replacer: obj = replacer(obj) return self.class_to_dict(obj) @classmethod def register_type_replacement(cls, object_type, replacement_function): cls.__type_replacements[object_type] = replacement_function """The various serializers that are supported""" _serializers = {} _serializers_by_id = {} def get_serializer(name): try: return _serializers[name] except KeyError: raise Pyro4.errors.ProtocolError("serializer '%s' is unknown or not available" % name) def get_serializer_by_id(sid): try: return _serializers_by_id[sid] except KeyError: raise Pyro4.errors.ProtocolError("no serializer available for id %d" % sid) # determine the serializers that are supported try: import cPickle as pickle except ImportError: import pickle assert pickle.HIGHEST_PROTOCOL>=2, "pickle needs to support protocol 2 or higher" _ser = PickleSerializer() _serializers["pickle"] = _ser _serializers_by_id[_ser.serializer_id] = _ser import marshal _ser = MarshalSerializer() _serializers["marshal"] = _ser _serializers_by_id[_ser.serializer_id] = _ser try: import json _ser = JsonSerializer() _serializers["json"] = _ser _serializers_by_id[_ser.serializer_id] = _ser except ImportError: pass try: import serpent if '-' in serpent.__version__: ver = serpent.__version__.split('-', 1)[0] else: ver = serpent.__version__ ver = tuple(map(int, ver.split("."))) if ver<(1,3): raise RuntimeError("requires serpent 1.3 or better") _ser = SerpentSerializer() _serializers["serpent"] = _ser _serializers_by_id[_ser.serializer_id] = _ser except ImportError: #warnings.warn("serpent serializer not available", RuntimeWarning) pass del _ser def resolveDottedAttribute(obj, attr, allowDotted): """ Resolves a dotted attribute name to an object. Raises an AttributeError if any attribute in the chain starts with a '``_``'. If the optional allowDotted argument is false, dots are not supported and this function operates similar to ``getattr(obj, attr)``. """ if allowDotted: attrs = attr.split('.') for i in attrs: if i.startswith('_'): raise AttributeError('attempt to access private attribute "%s"' % i) else: obj = getattr(obj, i) return obj else: return getattr(obj, attr) def excepthook(ex_type, ex_value, ex_tb): """An exception hook you can use for ``sys.excepthook``, to automatically print remote Pyro tracebacks""" traceback = "".join(getPyroTraceback(ex_type, ex_value, ex_tb)) sys.stderr.write(traceback) def fixIronPythonExceptionForPickle(exceptionObject, addAttributes): """ Function to hack around a bug in IronPython where it doesn't pickle exception attributes. We piggyback them into the exception's args. Bug report is at http://ironpython.codeplex.com/workitem/30805 """ if hasattr(exceptionObject, "args"): if addAttributes: # piggyback the attributes on the exception args instead. ironpythonArgs = vars(exceptionObject) ironpythonArgs["__ironpythonargs__"] = True exceptionObject.args += (ironpythonArgs,) else: # check if there is a piggybacked object in the args # if there is, extract the exception attributes from it. if len(exceptionObject.args) > 0: piggyback = exceptionObject.args[-1] if type(piggyback) is dict and piggyback.get("__ironpythonargs__"): del piggyback["__ironpythonargs__"] exceptionObject.args = exceptionObject.args[:-1] exceptionObject.__dict__.update(piggyback) Pyro4-4.23/src/Pyro4/utils/000077500000000000000000000000001227003673200154255ustar00rootroot00000000000000Pyro4-4.23/src/Pyro4/utils/__init__.py000066400000000000000000000000401227003673200175300ustar00rootroot00000000000000# just to make this a package. Pyro4-4.23/src/Pyro4/utils/flame.py000066400000000000000000000260031227003673200170640ustar00rootroot00000000000000""" Pyro FLAME: Foreign Location Automatic Module Exposer. Easy but potentially very dangerous way of exposing remote modules and builtins. Flame requires the pickle serializer to be used. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ from __future__ import with_statement import sys import types import code import Pyro4.core import Pyro4.util import Pyro4.constants import Pyro4.errors try: import importlib except ImportError: importlib = None try: import builtins except ImportError: import __builtin__ as builtins try: from cStringIO import StringIO except ImportError: from io import StringIO __all__ = ["connect", "start", "createModule", "Flame"] # Exec is a statement in Py2, a function in Py3 # Workaround as written by Ned Batchelder on his blog. if sys.version_info > (3, 0): def exec_function(source, filename, global_map): source=fixExecSourceNewlines(source) exec(compile(source, filename, "exec"), global_map) else: # OK, this is pretty gross. In Py2, exec was a statement, but that will # be a syntax error if we try to put it in a Py3 file, even if it isn't # executed. So hide it inside an evaluated string literal instead. eval(compile("""\ def exec_function(source, filename, global_map): source=fixExecSourceNewlines(source) exec compile(source, filename, "exec") in global_map """, "", "exec" )) def fixExecSourceNewlines(source): if sys.version_info < (2,7) or sys.version_info[:2] in ((3,0), (3,1)): # for python versions prior to 2.7 (and 3.0/3.1), compile is kinda picky. # it needs unix type newlines and a trailing newline to work correctly. source = source.replace("\r\n", "\n") source = source.rstrip() + "\n" # remove trailing whitespace that might cause IndentationErrors source = source.rstrip() return source class FlameModule(object): """Proxy to a remote module.""" def __init__(self, flameserver, module): # store a proxy to the flameserver regardless of autoproxy setting self.flameserver = Pyro4.core.Proxy(flameserver._pyroDaemon.uriFor(flameserver)) self.module = module def __getattr__(self, item): if item in ("__getnewargs__", "__getinitargs__"): raise AttributeError(item) return Pyro4.core._RemoteMethod(self.__invoke, "%s.%s" % (self.module, item)) def __getstate__(self): return self.__dict__ def __setstate__(self, args): self.__dict__ = args def __invoke(self, module, args, kwargs): return self.flameserver._invokeModule(module, args, kwargs) def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.flameserver._pyroRelease() def __repr__(self): return "<%s.%s at 0x%x, module '%s' at %s>" % (self.__class__.__module__, self.__class__.__name__, id(self), self.module, self.flameserver._pyroUri.location) class FlameBuiltin(object): """Proxy to a remote builtin function.""" def __init__(self, flameserver, builtin): # store a proxy to the flameserver regardless of autoproxy setting self.flameserver = Pyro4.core.Proxy(flameserver._pyroDaemon.uriFor(flameserver)) self.builtin = builtin def __call__(self, *args, **kwargs): return self.flameserver._invokeBuiltin(self.builtin, args, kwargs) def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.flameserver._pyroRelease() def __repr__(self): return "<%s.%s at 0x%x, builtin '%s' at %s>" % (self.__class__.__module__, self.__class__.__name__, id(self), self.builtin, self.flameserver._pyroUri.location) class RemoteInteractiveConsole(object): """Proxy to a remote interactive console.""" class LineSendingConsole(code.InteractiveConsole): """makes sure the lines are sent to the remote console""" def __init__(self, remoteconsole): code.InteractiveConsole.__init__(self, filename="") self.remoteconsole = remoteconsole def push(self, line): output, more = self.remoteconsole.push_and_get_output(line) if output: sys.stdout.write(output) return more def __init__(self, remoteconsoleuri): # store a proxy to the console regardless of autoproxy setting self.remoteconsole = Pyro4.core.Proxy(remoteconsoleuri) def interact(self): console = self.LineSendingConsole(self.remoteconsole) console.interact(banner=self.remoteconsole.get_banner()) print("(Remote session ended)") def close(self): self.remoteconsole.terminate() self.remoteconsole._pyroRelease() def __repr__(self): return "<%s.%s at 0x%x, for %s>" % (self.__class__.__module__, self.__class__.__name__, id(self), self.remoteconsole._pyroUri.location) def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close() class InteractiveConsole(code.InteractiveConsole): """Interactive console wrapper that saves output written to stdout so it can be returned as value""" def push_and_get_output(self, line): output, more = "", False stdout_save = sys.stdout try: sys.stdout = StringIO() more = self.push(line) output = sys.stdout.getvalue() sys.stdout.close() finally: sys.stdout = stdout_save return output, more def get_banner(self): return self.banner # custom banner string, set by Pyro daemon def write(self, data): sys.stdout.write(data) # stdout instead of stderr def terminate(self): self._pyroDaemon.unregister(self) self.resetbuffer() class Flame(object): """ The actual FLAME server logic. Usually created by using :py:meth:`Pyro4.core.Daemon.startFlame`. Be *very* cautious before starting this: it allows the clients full access to everything on your system. """ def __init__(self): if "pickle" not in Pyro4.config.SERIALIZERS_ACCEPTED: raise RuntimeError("flame requires the pickle serializer to be enabled") def module(self, name): """import a module on the server given by the module name and returns a proxy to it""" if importlib: importlib.import_module(name) else: __import__(name) return FlameModule(self, name) def builtin(self, name): """returns a proxy to the given builtin on the server""" return FlameBuiltin(self, name) def execute(self, code): """execute a piece of code""" exec_function(code, "", globals()) def evaluate(self, expression): """evaluate an expression and return its result""" return eval(expression) def sendmodule(self, modulename, modulesource): """ Send the source of a module to the server and make the server load it. Note that you still have to actually ``import`` it on the server to access it. Sending a module again will replace the previous one with the new. """ createModule(modulename, modulesource) def getmodule(self, modulename): """obtain the source code from a module on the server""" import inspect module = __import__(modulename, globals={}, locals={}) return inspect.getsource(module) def sendfile(self, filename, filedata): """store a new file on the server""" import os, stat with open(filename, "wb") as targetfile: os.chmod(filename, stat.S_IRUSR | stat.S_IWUSR) # readable/writable by owner only targetfile.write(filedata) def getfile(self, filename): """read any accessible file from the server""" with open(filename, "rb") as file: return file.read() def console(self): """get a proxy for a remote interactive console session""" console = InteractiveConsole(filename="") uri = self._pyroDaemon.register(console) console.banner = "Python %s on %s\n(Remote console on %s)" % (sys.version, sys.platform, uri.location) return RemoteInteractiveConsole(uri) def _invokeBuiltin(self, builtin, args, kwargs): return getattr(builtins, builtin)(*args, **kwargs) def _invokeModule(self, dottedname, args, kwargs): # dottedname is something like "os.path.walk" so strip off the module name modulename, dottedname = dottedname.split('.', 1) module = sys.modules[modulename] # we override the DOTTEDNAMES setting here because this safeguard makes no sense # with the Flame server (if enabled it already allows full access to anything): method = Pyro4.util.resolveDottedAttribute(module, dottedname, True) return method(*args, **kwargs) def createModule(name, source, filename="", namespace=None): """ Utility function to create a new module with the given name (dotted notation allowed), directly from the source string. Adds it to sys.modules, and returns the new module object. If you provide a namespace dict (such as ``globals()``), it will import the module into that namespace too. """ path = "" components = name.split('.') module = types.ModuleType("pyro-flame-module-context") for component in components: # build the module hierarchy. path += '.' + component real_path = path[1:] if real_path in sys.modules: # use already loaded modules instead of overwriting them module = sys.modules[real_path] else: setattr(module, component, types.ModuleType(real_path)) module = getattr(module, component) sys.modules[real_path] = module exec_function(source, filename, module.__dict__) if namespace is not None: namespace[components[0]] = __import__(name) return module def start(daemon): """ Create and register a Flame server in the given daemon. Be *very* cautious before starting this: it allows the clients full access to everything on your system. """ if Pyro4.config.FLAME_ENABLED: return daemon.register(Flame(), Pyro4.constants.FLAME_NAME) else: raise Pyro4.errors.SecurityError("Flame is disabled in the server configuration") def connect(location): """ Connect to a Flame server on the given location, for instance localhost:9999 or ./u:unixsock This is just a convenience function to creates an appropriate Pyro proxy. """ proxy = Pyro4.core.Proxy("PYRO:%s@%s" % (Pyro4.constants.FLAME_NAME, location)) proxy._pyroBind() return proxy Pyro4-4.23/src/Pyro4/utils/flameserver.py000066400000000000000000000040421227003673200203120ustar00rootroot00000000000000""" Pyro FLAME: Foreign Location Automatic Module Exposer. Easy but potentially very dangerous way of exposing remote modules and builtins. This is the commandline server. You can start this module as a script from the command line, to easily get a flame server running: :command:`python -m Pyro4.utils.flameserver` Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ import sys import Pyro4.utils.flame import Pyro4.core def main(args, returnWithoutLooping=False): from optparse import OptionParser parser = OptionParser() parser.add_option("-H", "--host", default="localhost", help="hostname to bind server on (default=localhost)") parser.add_option("-p", "--port", type="int", default=0, help="port to bind server on") parser.add_option("-u", "--unixsocket", help="Unix domain socket name to bind server on") parser.add_option("-q", "--quiet", action="store_true", default=False, help="don't output anything") parser.add_option("-k", "--key", help="the HMAC key to use") options, args = parser.parse_args(args) if not options.quiet: print("Starting Pyro Flame server.") hmac = (options.key or "").encode("utf-8") if not hmac: print("Warning: HMAC key not set. Anyone can connect to this server!") Pyro4.config.HMAC_KEY = hmac or Pyro4.config.HMAC_KEY if not options.quiet and Pyro4.config.HMAC_KEY: print("HMAC_KEY set to: %s" % Pyro4.config.HMAC_KEY) Pyro4.config.SERIALIZERS_ACCEPTED = set(["pickle"]) # flame requires pickle serializer daemon = Pyro4.core.Daemon(host=options.host, port=options.port, unixsocket=options.unixsocket) uri = Pyro4.utils.flame.start(daemon) if not options.quiet: print("server uri: %s" % uri) print("server is running.") if returnWithoutLooping: return daemon, uri # for unit testing else: daemon.requestLoop() daemon.close() return 0 if __name__ == "__main__": sys.exit(main(sys.argv[1:])) Pyro4-4.23/tests/000077500000000000000000000000001227003673200136235ustar00rootroot00000000000000Pyro4-4.23/tests/PyroTests/000077500000000000000000000000001227003673200155775ustar00rootroot00000000000000Pyro4-4.23/tests/PyroTests/__init__.py000066400000000000000000000000201227003673200177000ustar00rootroot00000000000000# just a packagePyro4-4.23/tests/PyroTests/test_core.py000066400000000000000000000670371227003673200201550ustar00rootroot00000000000000""" Tests for the core logic. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ from __future__ import with_statement import copy import logging import os, sys, time import warnings import Pyro4.configuration import Pyro4.core import Pyro4.errors import Pyro4.constants import Pyro4.futures from testsupport import * if sys.version_info>=(3,0): import imp reload=imp.reload class Thing(object): def __init__(self, arg): self.arg=arg def __eq__(self,other): return self.arg==other.arg __hash__=object.__hash__ class CoreTestsWithoutHmac(unittest.TestCase): def setUp(self): warnings.simplefilter("ignore") Pyro4.config.reset() def testProxy(self): Pyro4.config.HMAC_KEY=None # check that proxy without hmac is possible _=Pyro4.Proxy("PYRO:object@host:9999") def testDaemon(self): Pyro4.config.HMAC_KEY=None # check that daemon without hmac is possible d=Pyro4.Daemon() d.shutdown() class CoreTests(unittest.TestCase): def setUp(self): Pyro4.config.HMAC_KEY = b"testsuite" def tearDown(self): Pyro4.config.HMAC_KEY = None def testConfig(self): self.assertTrue(type(Pyro4.config.COMPRESSION) is bool) self.assertTrue(type(Pyro4.config.NS_PORT) is int) config=Pyro4.config.asDict() self.assertTrue(type(config) is dict) self.assertTrue("COMPRESSION" in config) self.assertEqual(Pyro4.config.COMPRESSION, config["COMPRESSION"]) def testConfigValid(self): try: Pyro4.config.XYZ_FOOBAR=True # don't want to allow weird config names self.fail("expected exception for weird config item") except AttributeError: pass def testConfigParseBool(self): config=Pyro4.configuration.Configuration() self.assertTrue(type(config.COMPRESSION) is bool) os.environ["PYRO_COMPRESSION"]="yes" config.reset() self.assertTrue(config.COMPRESSION) os.environ["PYRO_COMPRESSION"]="off" config.reset() self.assertFalse(config.COMPRESSION) os.environ["PYRO_COMPRESSION"]="foobar" self.assertRaises(ValueError, config.reset) del os.environ["PYRO_COMPRESSION"] config.reset() def testConfigDump(self): config=Pyro4.configuration.Configuration() dump=config.dump() self.assertTrue("version:" in dump) self.assertTrue("LOGLEVEL" in dump) def testLogInit(self): _=logging.getLogger("Pyro4") os.environ["PYRO_LOGLEVEL"]="DEBUG" os.environ["PYRO_LOGFILE"]="{stderr}" reload(Pyro4) _=logging.getLogger("Pyro4") del os.environ["PYRO_LOGLEVEL"] del os.environ["PYRO_LOGFILE"] reload(Pyro4) def testUriStrAndRepr(self): uri="PYRONAME:some_obj_name" p=Pyro4.core.URI(uri) self.assertEqual(uri,str(p)) uri="PYRONAME:some_obj_name@host.com" p=Pyro4.core.URI(uri) self.assertEqual(uri+":"+str(Pyro4.config.NS_PORT),str(p)) # a PYRONAME uri with a hostname gets a port too if omitted uri="PYRONAME:some_obj_name@host.com:8888" p=Pyro4.core.URI(uri) self.assertEqual(uri,str(p)) expected="" % id(p) self.assertEqual(expected, repr(p)) uri="PYRO:12345@host.com:9999" p=Pyro4.core.URI(uri) self.assertEqual(uri,str(p)) self.assertEqual(uri,p.asString()) uri="PYRO:12345@./u:sockname" p=Pyro4.core.URI(uri) self.assertEqual(uri,str(p)) uri="PYRO:12345@./u:sockname" unicodeuri=unicode(uri) p=Pyro4.core.URI(unicodeuri) self.assertEqual(uri,str(p)) self.assertEqual(unicodeuri,unicode(p)) self.assertTrue(type(p.sockname) is unicode) def testUriParsingPyro(self): p=Pyro4.core.URI("PYRONAME:some_obj_name") self.assertEqual("PYRONAME",p.protocol) self.assertEqual("some_obj_name",p.object) self.assertEqual(None,p.host) self.assertEqual(None,p.sockname) self.assertEqual(None,p.port) p=Pyro4.core.URI("PYRONAME:some_obj_name@host.com:9999") self.assertEqual("PYRONAME",p.protocol) self.assertEqual("some_obj_name",p.object) self.assertEqual("host.com",p.host) self.assertEqual(9999,p.port) p=Pyro4.core.URI("PYRO:12345@host.com:4444") self.assertEqual("PYRO",p.protocol) self.assertEqual("12345",p.object) self.assertEqual("host.com",p.host) self.assertEqual(None,p.sockname) self.assertEqual(4444,p.port) p=Pyro4.core.URI("PYRO:12345@./u:sockname") self.assertEqual("12345",p.object) self.assertEqual("sockname",p.sockname) p=Pyro4.core.URI("PYRO:12345@./u:/tmp/sockname") self.assertEqual("12345",p.object) self.assertEqual("/tmp/sockname",p.sockname) p=Pyro4.core.URI("PYRO:12345@./u:../sockname") self.assertEqual("12345",p.object) self.assertEqual("../sockname",p.sockname) p=Pyro4.core.URI("pyro:12345@host.com:4444") self.assertEqual("PYRO",p.protocol) self.assertEqual("12345",p.object) self.assertEqual("host.com",p.host) self.assertEqual(None,p.sockname) self.assertEqual(4444,p.port) def testUriParsingPyroname(self): p=Pyro4.core.URI("PYRONAME:objectname") self.assertEqual("PYRONAME",p.protocol) self.assertEqual("objectname",p.object) self.assertEqual(None,p.host) self.assertEqual(None,p.port) p=Pyro4.core.URI("PYRONAME:objectname@nameserverhost") self.assertEqual("PYRONAME",p.protocol) self.assertEqual("objectname",p.object) self.assertEqual("nameserverhost",p.host) self.assertEqual(Pyro4.config.NS_PORT,p.port) # Pyroname uri with host gets a port too if not specified p=Pyro4.core.URI("PYRONAME:objectname@nameserverhost:4444") self.assertEqual("PYRONAME",p.protocol) self.assertEqual("objectname",p.object) self.assertEqual("nameserverhost",p.host) self.assertEqual(4444,p.port) p=Pyro4.core.URI("PyroName:some_obj_name@host.com:9999") self.assertEqual("PYRONAME",p.protocol) p=Pyro4.core.URI("pyroname:some_obj_name@host.com:9999") self.assertEqual("PYRONAME",p.protocol) def testInvalidUris(self): self.assertRaises(TypeError, Pyro4.core.URI, None) self.assertRaises(TypeError, Pyro4.core.URI, 99999) self.assertRaises(Pyro4.errors.PyroError, Pyro4.core.URI, "") self.assertRaises(Pyro4.errors.PyroError, Pyro4.core.URI, "a") self.assertRaises(Pyro4.errors.PyroError, Pyro4.core.URI, "PYR") self.assertRaises(Pyro4.errors.PyroError, Pyro4.core.URI, "PYRO") self.assertRaises(Pyro4.errors.PyroError, Pyro4.core.URI, "PYRO:") self.assertRaises(Pyro4.errors.PyroError, Pyro4.core.URI, "PYRO::") self.assertRaises(Pyro4.errors.PyroError, Pyro4.core.URI, "PYRO:a") self.assertRaises(Pyro4.errors.PyroError, Pyro4.core.URI, "PYRO:x@") self.assertRaises(Pyro4.errors.PyroError, Pyro4.core.URI, "PYRO:x@hostname") self.assertRaises(Pyro4.errors.PyroError, Pyro4.core.URI, "PYRO:@hostname:portstr") self.assertRaises(Pyro4.errors.PyroError, Pyro4.core.URI, "PYRO:@hostname:7766") self.assertRaises(Pyro4.errors.PyroError, Pyro4.core.URI, "PYRO:objid@hostname:7766:bogus") self.assertRaises(Pyro4.errors.PyroError, Pyro4.core.URI, "PYROLOC:objname") self.assertRaises(Pyro4.errors.PyroError, Pyro4.core.URI, "PYROLOC:objname@host") self.assertRaises(Pyro4.errors.PyroError, Pyro4.core.URI, "PYROLOC:objectname@hostname:4444") self.assertRaises(Pyro4.errors.PyroError, Pyro4.core.URI, "PYRONAME:") self.assertRaises(Pyro4.errors.PyroError, Pyro4.core.URI, "PYRONAME:objname@nameserver:bogus") self.assertRaises(Pyro4.errors.PyroError, Pyro4.core.URI, "PYRONAME:objname@nameserver:7766:bogus") self.assertRaises(Pyro4.errors.PyroError, Pyro4.core.URI, "FOOBAR:") self.assertRaises(Pyro4.errors.PyroError, Pyro4.core.URI, "FOOBAR:objid@hostname:7766") self.assertRaises(Pyro4.errors.PyroError, Pyro4.core.URI, "PYRO:12345@./u:sockname:9999") def testUriUnicode(self): p=Pyro4.core.URI(unicode("PYRO:12345@host.com:4444")) self.assertEqual("PYRO",p.protocol) self.assertEqual("12345",p.object) self.assertEqual("host.com",p.host) self.assertTrue(type(p.protocol) is unicode) self.assertTrue(type(p.object) is unicode) self.assertTrue(type(p.host) is unicode) self.assertEqual(None,p.sockname) self.assertEqual(4444,p.port) uri="PYRO:12345@hostname:9999" p=Pyro4.core.URI(uri) pu=Pyro4.core.URI(unicode(uri)) self.assertEqual("PYRO",pu.protocol) self.assertEqual("hostname",pu.host) self.assertEqual(p,pu) self.assertEqual(str(p), str(pu)) unicodeuri="PYRO:weirdchars"+unichr(0x20ac)+"@host"+unichr(0x20AC)+".com:4444" pu=Pyro4.core.URI(unicodeuri) self.assertEqual("PYRO",pu.protocol) self.assertEqual("host"+unichr(0x20AC)+".com",pu.host) self.assertEqual("weirdchars"+unichr(0x20AC),pu.object) if sys.version_info<=(3,0): self.assertEqual("PYRO:weirdchars?@host?.com:4444", pu.__str__()) expected="" % id(pu) self.assertEqual(expected, repr(pu)) else: self.assertEqual("PYRO:weirdchars"+unichr(0x20ac)+"@host"+unichr(0x20ac)+".com:4444", pu.__str__()) expected=("") % id(pu) self.assertEqual(expected, repr(pu)) self.assertEqual("PYRO:weirdchars"+unichr(0x20ac)+"@host"+unichr(0x20ac)+".com:4444", pu.asString()) self.assertEqual("PYRO:weirdchars"+unichr(0x20ac)+"@host"+unichr(0x20ac)+".com:4444", unicode(pu)) def testUriCopy(self): p1=Pyro4.core.URI("PYRO:12345@hostname:9999") p2=Pyro4.core.URI(p1) p3=copy.copy(p1) self.assertEqual(p1.protocol, p2.protocol) self.assertEqual(p1.host, p2.host) self.assertEqual(p1.port, p2.port) self.assertEqual(p1.object, p2.object) self.assertEqual(p1,p2) self.assertEqual(p1.protocol, p3.protocol) self.assertEqual(p1.host, p3.host) self.assertEqual(p1.port, p3.port) self.assertEqual(p1.object, p3.object) self.assertEqual(p1,p3) def testUriEqual(self): p1=Pyro4.core.URI("PYRO:12345@host.com:9999") p2=Pyro4.core.URI("PYRO:12345@host.com:9999") p3=Pyro4.core.URI("PYRO:99999@host.com:4444") self.assertEqual(p1,p2) self.assertNotEqual(p1,p3) self.assertNotEqual(p2,p3) self.assertTrue(p1==p2) self.assertFalse(p1==p3) self.assertFalse(p2==p3) self.assertFalse(p1!=p2) self.assertTrue(p1!=p3) self.assertTrue(p2!=p3) self.assertTrue(hash(p1)==hash(p2)) self.assertTrue(hash(p1)!=hash(p3)) p2.port=4444 p2.object="99999" self.assertNotEqual(p1,p2) self.assertEqual(p2,p3) self.assertFalse(p1==p2) self.assertTrue(p2==p3) self.assertTrue(p1!=p2) self.assertFalse(p2!=p3) self.assertTrue(hash(p1)!=hash(p2)) self.assertTrue(hash(p2)==hash(p3)) self.assertFalse(p1==42) self.assertTrue(p1!=42) def testLocation(self): self.assertTrue(Pyro4.core.URI.isUnixsockLocation("./u:name")) self.assertFalse(Pyro4.core.URI.isUnixsockLocation("./p:name")) self.assertFalse(Pyro4.core.URI.isUnixsockLocation("./x:name")) self.assertFalse(Pyro4.core.URI.isUnixsockLocation("foobar")) def testProxyOffline(self): # only offline stuff here. # online stuff needs a running daemon, so we do that in another test, to keep this one simple self.assertRaises(TypeError, Pyro4.core.Proxy, 999) # wrong arg p1=Pyro4.core.Proxy("PYRO:9999@localhost:15555") p2=Pyro4.core.Proxy(Pyro4.core.URI("PYRO:9999@localhost:15555")) self.assertEqual(p1._pyroUri, p2._pyroUri) self.assertTrue(p1._pyroConnection is None) p1._pyroRelease() p1._pyroRelease() # try copying a not-connected proxy p3=copy.copy(p1) self.assertTrue(p3._pyroConnection is None) self.assertTrue(p1._pyroConnection is None) self.assertEqual(p3._pyroUri, p1._pyroUri) self.assertFalse(p3._pyroUri is p1._pyroUri) def testProxyRepr(self): p=Pyro4.core.Proxy("PYRO:9999@localhost:15555") address=id(p) expected="" % address self.assertEqual(expected, repr(p)) self.assertEqual(unicode(expected), unicode(p)) def testProxySettings(self): p1=Pyro4.core.Proxy("PYRO:9999@localhost:15555") p2=Pyro4.core.Proxy("PYRO:9999@localhost:15555") p1._pyroOneway.add("method") self.assertTrue("method" in p1._pyroOneway, "p1 should have oneway method") self.assertFalse("method" in p2._pyroOneway, "p2 should not have the same oneway method") self.assertFalse(p1._pyroOneway is p2._pyroOneway, "p1 and p2 should have different oneway tables") def testProxyWithStmt(self): class ConnectionMock(object): closeCalled=False def close(self): self.closeCalled=True connMock=ConnectionMock() # first without a 'with' statement p=Pyro4.core.Proxy("PYRO:9999@localhost:15555") p._pyroConnection=connMock self.assertFalse(connMock.closeCalled) p._pyroRelease() self.assertTrue(p._pyroConnection is None) self.assertTrue(connMock.closeCalled) connMock=ConnectionMock() with Pyro4.core.Proxy("PYRO:9999@localhost:15555") as p: p._pyroConnection=connMock self.assertTrue(p._pyroConnection is None) self.assertTrue(connMock.closeCalled) connMock=ConnectionMock() try: with Pyro4.core.Proxy("PYRO:9999@localhost:15555") as p: p._pyroConnection=connMock print(1//0) # cause an error self.fail("expected error") except ZeroDivisionError: pass self.assertTrue(p._pyroConnection is None) self.assertTrue(connMock.closeCalled) p=Pyro4.core.Proxy("PYRO:9999@localhost:15555") with p: self.assertTrue(p._pyroUri is not None) with p: self.assertTrue(p._pyroUri is not None) def testNoConnect(self): wrongUri=Pyro4.core.URI("PYRO:foobar@localhost:59999") with Pyro4.core.Proxy(wrongUri) as p: try: p.ping() self.fail("CommunicationError expected") except Pyro4.errors.CommunicationError: pass def testTimeoutGetSet(self): class ConnectionMock(object): def __init__(self): self.timeout=Pyro4.config.COMMTIMEOUT def close(self): pass Pyro4.config.COMMTIMEOUT=None p=Pyro4.core.Proxy("PYRO:obj@host:555") self.assertEqual(None, p._pyroTimeout) p._pyroTimeout=5 self.assertEqual(5, p._pyroTimeout) p=Pyro4.core.Proxy("PYRO:obj@host:555") p._pyroConnection=ConnectionMock() self.assertEqual(None, p._pyroTimeout) p._pyroTimeout=5 self.assertEqual(5, p._pyroTimeout) self.assertEqual(5, p._pyroConnection.timeout) Pyro4.config.COMMTIMEOUT=2 p=Pyro4.core.Proxy("PYRO:obj@host:555") p._pyroConnection=ConnectionMock() self.assertEqual(2, p._pyroTimeout) self.assertEqual(2, p._pyroConnection.timeout) p._pyroTimeout=None self.assertEqual(None, p._pyroTimeout) self.assertEqual(None, p._pyroConnection.timeout) Pyro4.config.COMMTIMEOUT=None def testDecorators(self): # just test the decorator itself, testing the callback # exception handling is kinda hard in unit tests. Maybe later. class Test(object): @Pyro4.callback def method(self): pass def method2(self): pass t=Test() self.assertEqual(True, getattr(t.method,"_pyroCallback")) self.assertEqual(False, getattr(t.method2,"_pyroCallback", False)) def testProxyEquality(self): p1=Pyro4.core.Proxy("PYRO:thing@localhost:15555") p2=Pyro4.core.Proxy("PYRO:thing@localhost:15555") p3=Pyro4.core.Proxy("PYRO:other@machine:16666") self.assertTrue(p1==p2) self.assertFalse(p1!=p2) self.assertFalse(p1==p3) self.assertTrue(p1!=p3) self.assertTrue(hash(p1)==hash(p2)) self.assertFalse(hash(p1)==hash(p3)) p1._pyroOneway.add("onewaymethod") self.assertFalse(p1==p2) self.assertFalse(hash(p1)==hash(p2)) self.assertFalse(p1==42) self.assertTrue(p1!=42) class RemoteMethodTests(unittest.TestCase): class BatchProxyMock(object): def __copy__(self): return self def __enter__(self): return self def __exit__(self, *args): pass def _pyroBatch(self): return Pyro4.core._BatchProxyAdapter(self) def _pyroInvokeBatch(self, calls, oneway=False): self.result=[] for methodname, args, kwargs in calls: if methodname=="error": self.result.append(Pyro4.futures._ExceptionWrapper(ValueError("some exception"))) break # stop processing the rest, this is what Pyro should do in case of an error in a batch elif methodname=="pause": time.sleep(args[0]) self.result.append("INVOKED %s args=%s kwargs=%s" % (methodname,args,kwargs)) if oneway: return else: return self.result class AsyncProxyMock(object): def __copy__(self): return self def __enter__(self): return self def __exit__(self, *args): pass def _pyroAsync(self): return Pyro4.core._AsyncProxyAdapter(self) def _pyroInvoke(self, methodname, vargs, kwargs, flags=0): if methodname=="pause_and_divide": time.sleep(vargs[0]) return vargs[1]//vargs[2] else: raise NotImplementedError(methodname) def setUp(self): Pyro4.config.HMAC_KEY = b"testsuite" def tearDown(self): Pyro4.config.HMAC_KEY = None def testRemoteMethod(self): class ProxyMock(object): def invoke(self, name, args, kwargs): return "INVOKED name=%s args=%s kwargs=%s" % (name,args,kwargs) def __getattr__(self, name): return Pyro4.core._RemoteMethod(self.invoke, name) o=ProxyMock() self.assertEqual("INVOKED name=foo args=(1,) kwargs={}", o.foo(1)) #normal self.assertEqual("INVOKED name=foo.bar args=(1,) kwargs={}", o.foo.bar(1)) #dotted self.assertEqual("INVOKED name=foo.bar args=(1, 'hello') kwargs={'a': True}", o.foo.bar(1,"hello",a=True)) p=Pyro4.core.Proxy("PYRO:obj@host:666") a=p.someattribute self.assertTrue(isinstance(a, Pyro4.core._RemoteMethod), "attribute access should just be a RemoteMethod") a2=a.nestedattribute self.assertTrue(isinstance(a2, Pyro4.core._RemoteMethod), "nested attribute should just be another RemoteMethod") def testBatchMethod(self): proxy=self.BatchProxyMock() batch=Pyro4.batch(proxy) self.assertEqual(None, batch.foo(42)) self.assertEqual(None, batch.bar("abc")) self.assertEqual(None, batch.baz(42,"abc",arg=999)) self.assertEqual(None, batch.error()) # generate an exception self.assertEqual(None, batch.foo(42)) # this call should not be performed after the error results=batch() result=next(results) self.assertEqual("INVOKED foo args=(42,) kwargs={}",result) result=next(results) self.assertEqual("INVOKED bar args=('abc',) kwargs={}",result) result=next(results) self.assertEqual("INVOKED baz args=(42, 'abc') kwargs={'arg': 999}",result) self.assertRaises(ValueError, next, results) # the call to error() should generate an exception self.assertRaises(StopIteration, next, results) # and now there should not be any more results self.assertEqual(4, len(proxy.result)) # should have done 4 calls, not 5 def testBatchMethodOneway(self): proxy=self.BatchProxyMock() batch=Pyro4.batch(proxy) self.assertEqual(None, batch.foo(42)) self.assertEqual(None, batch.bar("abc")) self.assertEqual(None, batch.baz(42,"abc",arg=999)) self.assertEqual(None, batch.error()) # generate an exception self.assertEqual(None, batch.foo(42)) # this call should not be performed after the error results=batch(oneway=True) self.assertEqual(None, results) # oneway always returns None self.assertEqual(4, len(proxy.result)) # should have done 4 calls, not 5 self.assertRaises(Pyro4.errors.PyroError, batch, oneway=True, async=True) # oneway+async=booboo def testBatchMethodAsync(self): proxy=self.BatchProxyMock() batch=Pyro4.batch(proxy) self.assertEqual(None, batch.foo(42)) self.assertEqual(None, batch.bar("abc")) self.assertEqual(None, batch.pause(0.5)) # pause shouldn't matter with async self.assertEqual(None, batch.baz(42,"abc",arg=999)) begin=time.time() asyncresult=batch(async=True) duration=time.time()-begin self.assertTrue(duration<0.1, "batch oneway with pause should still return almost immediately") results=asyncresult.value self.assertEqual(4, len(proxy.result)) # should have done 4 calls result=next(results) self.assertEqual("INVOKED foo args=(42,) kwargs={}",result) result=next(results) self.assertEqual("INVOKED bar args=('abc',) kwargs={}",result) result=next(results) self.assertEqual("INVOKED pause args=(0.5,) kwargs={}",result) result=next(results) self.assertEqual("INVOKED baz args=(42, 'abc') kwargs={'arg': 999}",result) self.assertRaises(StopIteration, next, results) # and now there should not be any more results def testBatchMethodReuse(self): proxy=self.BatchProxyMock() batch=Pyro4.batch(proxy) batch.foo(1) batch.foo(2) results=batch() self.assertEqual(['INVOKED foo args=(1,) kwargs={}', 'INVOKED foo args=(2,) kwargs={}'], list(results)) # re-use the batch proxy: batch.foo(3) batch.foo(4) results=batch() self.assertEqual(['INVOKED foo args=(3,) kwargs={}', 'INVOKED foo args=(4,) kwargs={}'], list(results)) results=batch() self.assertEqual(0, len(list(results))) def testAsyncMethod(self): proxy=self.AsyncProxyMock() async=Pyro4.async(proxy) begin=time.time() result=async.pause_and_divide(0.2,10,2) # returns immediately duration=time.time()-begin self.assertTrue(duration<0.1) self.assertFalse(result.ready) _=result.value self.assertTrue(result.ready) def testAsyncCallbackMethod(self): class AsyncFunctionHolder(object): asyncFunctionCount=0 def asyncFunction(self, value, amount=1): self.asyncFunctionCount+=1 return value+amount proxy=self.AsyncProxyMock() async=Pyro4.async(proxy) result=async.pause_and_divide(0.2,10,2) # returns immediately holder=AsyncFunctionHolder() result.then(holder.asyncFunction, amount=2) \ .then(holder.asyncFunction, amount=4) \ .then(holder.asyncFunction) value=result.value self.assertEqual(10//2+2+4+1,value) self.assertEqual(3,holder.asyncFunctionCount) def testCrashingAsyncCallbackMethod(self): def normalAsyncFunction(value, x): return value+x def crashingAsyncFunction(value): return 1//0 # crash proxy=self.AsyncProxyMock() async=Pyro4.async(proxy) result=async.pause_and_divide(0.2,10,2) # returns immediately result.then(crashingAsyncFunction).then(normalAsyncFunction,2) try: value=result.value self.fail("expected exception") except ZeroDivisionError: pass # ok def testAsyncMethodTimeout(self): proxy=self.AsyncProxyMock() async=Pyro4.async(proxy) result=async.pause_and_divide(1,10,2) # returns immediately self.assertFalse(result.ready) self.assertFalse(result.wait(0.5)) # won't be ready after 0.5 sec self.assertTrue(result.wait(1)) # will be ready within 1 seconds more self.assertTrue(result.ready) self.assertEqual(5,result.value) class TestSimpleServe(unittest.TestCase): class DaemonMock(object): def __init__(self): self.objects={} def register(self, object, name): self.objects[object]=name def __enter__(self): pass def __exit__(self, *args): pass def requestLoop(self, *args): pass def testSimpleServe(self): d=TestSimpleServe.DaemonMock() o1=Thing(1) o2=Thing(2) objects={ o1: "test.o1", o2: None } Pyro4.core.Daemon.serveSimple(objects,daemon=d, ns=False, verbose=False) self.assertEqual( {o1: "test.o1", o2: None}, d.objects) def futurestestfunc(a, b, extra=None): if extra is None: return a+b else: return a+b+extra def crashingfuturestestfunc(a): return 1//0 # crash class TestFutures(unittest.TestCase): def testSimpleFuture(self): f=Pyro4.Future(futurestestfunc) r=f(4,5) self.assertTrue(isinstance(r, Pyro4.futures.FutureResult)) value=r.value self.assertEqual(9, value) def testFutureChain(self): f=Pyro4.Future(futurestestfunc) f.then(futurestestfunc, 6) f.then(futurestestfunc, 7, extra=10) r=f(4,5) value=r.value self.assertEqual(4+5+6+7+10,value) def testCrashingChain(self): f=Pyro4.Future(futurestestfunc) f.then(futurestestfunc, 6) f.then(crashingfuturestestfunc) f.then(futurestestfunc, 8) r=f(4,5) try: value=r.value self.fail("expected exception") except ZeroDivisionError: pass #ok if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] unittest.main() Pyro4-4.23/tests/PyroTests/test_daemon.py000066400000000000000000000405421227003673200204600ustar00rootroot00000000000000""" Tests for the daemon. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ from __future__ import with_statement import os, time, socket import Pyro4.core import Pyro4.constants import Pyro4.socketutil import Pyro4.message from Pyro4.errors import DaemonError,PyroError from testsupport import * class MyObj(object): def __init__(self, arg): self.arg=arg def __eq__(self,other): return self.arg==other.arg __hash__=object.__hash__ class DaemonTests(unittest.TestCase): # We create a daemon, but notice that we are not actually running the requestloop. # 'on-line' tests are all taking place in another test, to keep this one simple. def setUp(self): Pyro4.config.POLLTIMEOUT=0.1 Pyro4.config.HMAC_KEY = b"testsuite" def tearDown(self): Pyro4.config.HMAC_KEY = None def testSerializerConfig(self): self.assertIsInstance(Pyro4.config.SERIALIZERS_ACCEPTED, set) self.assertIsInstance(Pyro4.config.SERIALIZER, basestring) self.assertGreater(len(Pyro4.config.SERIALIZERS_ACCEPTED), 1) def testSerializerAccepted(self): class ConnectionMock(object): def __init__(self, msg): self.data = msg.to_bytes() def recv(self, datasize): chunk = self.data[:datasize] self.data = self.data[datasize:] return chunk def send(self, data): pass self.assertTrue("marshal" in Pyro4.config.SERIALIZERS_ACCEPTED) self.assertFalse("pickle" in Pyro4.config.SERIALIZERS_ACCEPTED) with Pyro4.core.Daemon(port=0) as d: msg = Pyro4.message.Message(Pyro4.message.MSG_INVOKE, b"", Pyro4.message.SERIALIZER_MARSHAL, 0, 0) cm = ConnectionMock(msg) d.handleRequest(cm) # marshal serializer should be accepted msg = Pyro4.message.Message(Pyro4.message.MSG_INVOKE, b"", Pyro4.message.SERIALIZER_PICKLE, 0, 0) cm = ConnectionMock(msg) try: d.handleRequest(cm) self.fail("should crash") except Pyro4.errors.ProtocolError as x: self.assertTrue("serializer that is not accepted" in str(x)) pass def testDaemon(self): with Pyro4.core.Daemon(port=0) as d: hostname, port = d.locationStr.split(":") port = int(port) self.assertTrue(Pyro4.constants.DAEMON_NAME in d.objectsById) self.assertEqual("PYRO:"+Pyro4.constants.DAEMON_NAME+"@"+d.locationStr, str(d.uriFor(Pyro4.constants.DAEMON_NAME))) # check the string representations expected=("") % (id(d), d.locationStr) self.assertEqual(expected,str(d)) self.assertEqual(expected,unicode(d)) self.assertEqual(expected,repr(d)) sockname=d.sock.getsockname() self.assertEqual(port, sockname[1]) daemonobj=d.objectsById[Pyro4.constants.DAEMON_NAME] daemonobj.ping() daemonobj.registered() try: daemonobj.shutdown() self.fail("should not succeed to call unexposed method") except AttributeError: pass def testDaemonUnixSocket(self): if hasattr(socket,"AF_UNIX"): SOCKNAME="test_unixsocket" with Pyro4.core.Daemon(unixsocket=SOCKNAME) as d: locationstr="./u:"+SOCKNAME self.assertEqual(locationstr, d.locationStr) self.assertEqual("PYRO:"+Pyro4.constants.DAEMON_NAME+"@"+locationstr, str(d.uriFor(Pyro4.constants.DAEMON_NAME))) # check the string representations expected=("") % (id(d), locationstr) self.assertEqual(expected,str(d)) self.assertEqual(SOCKNAME,d.sock.getsockname()) self.assertEqual(socket.AF_UNIX,d.sock.family) def testServertypeThread(self): old_servertype=Pyro4.config.SERVERTYPE Pyro4.config.SERVERTYPE="thread" with Pyro4.core.Daemon(port=0) as d: sock=d.sock self.assertTrue(sock in d.sockets, "daemon's socketlist should contain the server socket") self.assertTrue(len(d.sockets)==1, "daemon without connections should have just 1 socket") Pyro4.config.SERVERTYPE=old_servertype def testServertypeMultiplex(self): old_servertype=Pyro4.config.SERVERTYPE Pyro4.config.SERVERTYPE="multiplex" # this type is not supported in Jython if os.name=="java": self.assertRaises(NotImplementedError, Pyro4.core.Daemon, port=0) else: with Pyro4.core.Daemon(port=0) as d: sock=d.sock self.assertTrue(sock in d.sockets, "daemon's socketlist should contain the server socket") self.assertTrue(len(d.sockets)==1, "daemon without connections should have just 1 socket") Pyro4.config.SERVERTYPE=old_servertype def testServertypeFoobar(self): old_servertype=Pyro4.config.SERVERTYPE Pyro4.config.SERVERTYPE="foobar" self.assertRaises(PyroError, Pyro4.core.Daemon) Pyro4.config.SERVERTYPE=old_servertype def testRegisterTwice(self): with Pyro4.core.Daemon(port=0) as d: o1=MyObj("object1") d.register(o1) self.assertRaises(DaemonError, d.register, o1, None) d.unregister(o1) d.register(o1) self.assertTrue(hasattr(o1, "_pyroId")) d.unregister(o1) self.assertFalse(hasattr(o1, "_pyroId")) o1._pyroId="FOOBAR" self.assertRaises(DaemonError, d.register, o1, None) o1._pyroId="" d.register(o1) # with empty-string _pyroId register should worlk def testRegisterEtc(self): d=Pyro4.core.Daemon(port=0) try: self.assertEqual(1, len(d.objectsById)) o1=MyObj("object1") o2=MyObj("object2") d.register(o1) self.assertRaises(DaemonError, d.register, o2, Pyro4.constants.DAEMON_NAME) # cannot use daemon name d.register(o2, "obj2a") self.assertEqual(3, len(d.objectsById)) self.assertEqual(o1, d.objectsById[o1._pyroId]) self.assertEqual(o2, d.objectsById["obj2a"]) self.assertEqual("obj2a", o2._pyroId) self.assertEqual(d, o2._pyroDaemon) # test unregister d.unregister("unexisting_thingie") self.assertRaises(ValueError, d.unregister, None) d.unregister("obj2a") d.unregister(o1._pyroId) self.assertEqual(1, len(d.objectsById)) self.assertTrue(o1._pyroId not in d.objectsById) self.assertTrue(o2._pyroId not in d.objectsById) # test unregister objects del o2._pyroId d.register(o2) objectid = o2._pyroId self.assertTrue(objectid in d.objectsById) self.assertEqual(2, len(d.objectsById)) d.unregister(o2) # no more _pyro attributs must remain after unregistering for attr in vars(o2): self.assertFalse(attr.startswith("_pyro")) self.assertEqual(1, len(d.objectsById)) self.assertFalse(objectid in d.objectsById) self.assertRaises(DaemonError, d.unregister, [1,2,3]) # test unregister daemon name d.unregister(Pyro4.constants.DAEMON_NAME) self.assertTrue(Pyro4.constants.DAEMON_NAME in d.objectsById) # weird args w=MyObj("weird") self.assertRaises(AttributeError, d.register, None) self.assertRaises(AttributeError, d.register, 4444) self.assertRaises(TypeError, d.register, w, 666) # uri return value from register uri=d.register(MyObj("xyz")) self.assertTrue(isinstance(uri, Pyro4.core.URI)) uri=d.register(MyObj("xyz"), "test.register") self.assertTrue("test.register", uri.object) finally: d.close() def testRegisterUnicode(self): with Pyro4.core.Daemon(port=0) as d: myobj1=MyObj("hello1") myobj2=MyObj("hello2") myobj3=MyObj("hello3") uri1=d.register(myobj1, "str_name") uri2=d.register(myobj2, unicode("unicode_name")) uri3=d.register(myobj3, "unicode_"+unichr(0x20ac)) self.assertEqual(4, len(d.objectsById)) uri=d.uriFor(myobj1) self.assertEqual(uri1,uri) _=Pyro4.core.Proxy(uri) uri=d.uriFor(myobj2) self.assertEqual(uri2,uri) _=Pyro4.core.Proxy(uri) uri=d.uriFor(myobj3) self.assertEqual(uri3,uri) _=Pyro4.core.Proxy(uri) uri=d.uriFor("str_name") self.assertEqual(uri1,uri) _=Pyro4.core.Proxy(uri) uri=d.uriFor(unicode("unicode_name")) self.assertEqual(uri2,uri) _=Pyro4.core.Proxy(uri) uri=d.uriFor("unicode_"+unichr(0x20ac)) self.assertEqual(uri3,uri) _=Pyro4.core.Proxy(uri) def testDaemonObject(self): with Pyro4.core.Daemon(port=0) as d: daemon=Pyro4.core.DaemonObject(d) obj1=MyObj("object1") obj2=MyObj("object2") obj3=MyObj("object2") d.register(obj1,"obj1") d.register(obj2,"obj2") d.register(obj3) daemon.ping() registered=daemon.registered() self.assertTrue(type(registered) is list) self.assertEqual(4, len(registered)) self.assertTrue("obj1" in registered) self.assertTrue("obj2" in registered) self.assertTrue(obj3._pyroId in registered) try: daemon.shutdown() self.fail("should not succeed to call unexposed method") except AttributeError: pass def testUriFor(self): d=Pyro4.core.Daemon(port=0) try: o1=MyObj("object1") o2=MyObj("object2") self.assertRaises(DaemonError, d.uriFor, o1) self.assertRaises(DaemonError, d.uriFor, o2) d.register(o1,None) d.register(o2,"object_two") o3=MyObj("object3") self.assertRaises(DaemonError, d.uriFor, o3) #can't get an uri for an unregistered object (note: unregistered name is allright) u1=d.uriFor(o1) u2=d.uriFor(o2._pyroId) u3=d.uriFor("unexisting_thingie") # unregistered name is no problem, it's just an uri we're requesting u4=d.uriFor(o2) self.assertEqual(Pyro4.core.URI, type(u1)) self.assertEqual("PYRO",u1.protocol) self.assertEqual("PYRO",u2.protocol) self.assertEqual("PYRO",u3.protocol) self.assertEqual("PYRO",u4.protocol) self.assertEqual("object_two",u4.object) self.assertEqual(Pyro4.core.URI("PYRO:unexisting_thingie@"+d.locationStr), u3) finally: d.close() def testDaemonWithStmt(self): d=Pyro4.core.Daemon() self.assertTrue(d.transportServer is not None) d.close() # closes the transportserver and sets it to None self.assertTrue(d.transportServer is None) with Pyro4.core.Daemon() as d: self.assertTrue(d.transportServer is not None) pass self.assertTrue(d.transportServer is None) try: with Pyro4.core.Daemon() as d: print(1//0) # cause an error self.fail("expected error") except ZeroDivisionError: pass self.assertTrue(d.transportServer is None) d=Pyro4.core.Daemon() with d: pass try: with d: pass self.fail("expected error") except PyroError: # you cannot re-use a daemon object in multiple with statements pass d.close() def testRequestloopCondition(self): with Pyro4.core.Daemon(port=0) as d: condition=lambda:False start=time.time() d.requestLoop(loopCondition=condition) #this should return almost immediately duration=time.time()-start self.assertAlmostEqual(0.0, duration, places=1) def testHandshake(self): class ConnectionMock(object): def __init__(self): self.received = b"" def send(self, data): self.received += data def recv(self, datasize): chunk = self.received[:datasize] self.received = self.received[datasize:] return chunk conn = ConnectionMock() with Pyro4.core.Daemon(port=0) as d: success = d._handshake(conn) self.assertTrue(success) msg = Pyro4.message.Message.recv(conn) self.assertEqual(Pyro4.message.MSG_CONNECTOK, msg.type) self.assertEqual(1, msg.seq) def testNAT(self): with Pyro4.core.Daemon() as d: self.assertTrue(d.natLocationStr is None) with Pyro4.core.Daemon(nathost="nathosttest", natport=12345) as d: self.assertEqual("nathosttest:12345", d.natLocationStr) self.assertNotEqual(d.locationStr, d.natLocationStr) uri=d.register(MyObj(1)) self.assertEqual("nathosttest:12345", uri.location) uri=d.uriFor("object") self.assertEqual("nathosttest:12345", uri.location) uri=d.uriFor("object", nat=False) self.assertNotEqual("nathosttest:12345", uri.location) try: d=Pyro4.core.Daemon(nathost="bla") self.fail("expected error") except ValueError: pass try: d=Pyro4.core.Daemon(natport=5555) self.fail("expected error") except ValueError: pass try: d=Pyro4.core.Daemon(nathost="bla", natport=5555, unixsocket="testsock") self.fail("expected error") except ValueError: pass def testNATzeroPort(self): servertype = Pyro4.config.SERVERTYPE try: Pyro4.config.SERVERTYPE="multiplex" with Pyro4.core.Daemon(nathost="nathosttest", natport=99999) as d: host, port = d.locationStr.split(":") self.assertNotEqual(99999, port) self.assertEqual("nathosttest:99999", d.natLocationStr) with Pyro4.core.Daemon(nathost="nathosttest", natport=0) as d: host, port = d.locationStr.split(":") self.assertEqual("nathosttest:%s" % port, d.natLocationStr) Pyro4.config.SERVERTYPE="thread" with Pyro4.core.Daemon(nathost="nathosttest", natport=99999) as d: host, port = d.locationStr.split(":") self.assertNotEqual(99999, port) self.assertEqual("nathosttest:99999", d.natLocationStr) with Pyro4.core.Daemon(nathost="nathosttest", natport=0) as d: host, port = d.locationStr.split(":") self.assertEqual("nathosttest:%s" % port, d.natLocationStr) finally: Pyro4.config.SERVERTYPE=servertype def testNATconfig(self): try: Pyro4.config.NATHOST=None Pyro4.config.NATPORT=0 with Pyro4.core.Daemon() as d: self.assertTrue(d.natLocationStr is None) Pyro4.config.NATHOST="nathosttest" Pyro4.config.NATPORT=12345 with Pyro4.core.Daemon() as d: self.assertEqual("nathosttest:12345", d.natLocationStr) finally: Pyro4.config.NATHOST=None Pyro4.config.NATPORT=0 if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] unittest.main() Pyro4-4.23/tests/PyroTests/test_echoserver.py000066400000000000000000000037261227003673200213650ustar00rootroot00000000000000""" Tests for the built-in test echo server. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ import time import Pyro4.test.echoserver as echoserver import Pyro4 from threading import Thread,Event from testsupport import * class EchoServerThread(Thread): def __init__(self): super(EchoServerThread,self).__init__() self.setDaemon(True) self.started=Event() self.terminated=Event() def run(self): self.echodaemon,self.echoserver,self.uri=echoserver.main(["-q"], returnWithoutLooping=True) self.started.set() self.echodaemon.requestLoop(loopCondition=lambda:not self.echoserver.must_shutdown) self.terminated.set() class TestEchoserver(unittest.TestCase): def setUp(self): Pyro4.config.HMAC_KEY = b"testsuite" self.echoserverthread=EchoServerThread() self.echoserverthread.start() self.echoserverthread.started.wait() self.uri=self.echoserverthread.uri def tearDown(self): self.echoserverthread.echodaemon.shutdown() time.sleep(0.01) self.echoserverthread.terminated.wait() Pyro4.config.HMAC_KEY=None def testEcho(self): echo=Pyro4.Proxy(self.uri) try: self.assertEqual("hello", echo.echo("hello")) self.assertEqual(None, echo.echo(None)) self.assertEqual([1,2,3], echo.echo([1,2,3])) finally: echo.shutdown() def testError(self): try: echo=Pyro4.Proxy(self.uri) try: echo.error() self.fail("expected exception") except: tb="".join(Pyro4.util.getPyroTraceback()) self.assertTrue("Remote traceback" in tb) self.assertTrue("ZeroDivisionError" in tb) finally: echo.shutdown() if __name__ == "__main__": unittest.main() Pyro4-4.23/tests/PyroTests/test_flame.py000066400000000000000000000063361227003673200203040ustar00rootroot00000000000000""" Tests for Pyro Flame. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ from __future__ import with_statement import Pyro4.utils.flame import Pyro4.utils.flameserver import Pyro4.errors from testsupport import * class FlameDisabledTests(unittest.TestCase): def testFlameDisabled(self): with Pyro4.core.Daemon() as d: self.assertRaises(Pyro4.errors.SecurityError, Pyro4.utils.flame.start, d) # default should be disabled def testRequirePickle(self): with Pyro4.core.Daemon() as d: Pyro4.config.FLAME_ENABLED=True Pyro4.config.SERIALIZERS_ACCEPTED.discard("pickle") self.assertRaises(RuntimeError, Pyro4.utils.flame.start, d) # require pickle Pyro4.config.SERIALIZERS_ACCEPTED.add("pickle") Pyro4.utils.flame.start(d) Pyro4.config.SERIALIZERS_ACCEPTED.discard("pickle") class FlameTests(unittest.TestCase): def setUp(self): Pyro4.config.HMAC_KEY=b"testsuite" Pyro4.config.FLAME_ENABLED=True Pyro4.config.SERIALIZERS_ACCEPTED.add("pickle") def tearDown(self): Pyro4.config.HMAC_KEY=None Pyro4.config.FLAME_ENABLED=False Pyro4.config.SERIALIZERS_ACCEPTED.discard("pickle") def testCreateModule(self): module=Pyro4.utils.flame.createModule("testmodule", "def x(y): return y*y") self.assertEqual(9, module.x(3)) module=Pyro4.utils.flame.createModule("testmodule2.submodule.subsub", "def x(y): return y*y") self.assertEqual(9, module.x(3)) import testmodule2.submodule.subsub self.assertEqual(9, testmodule2.submodule.subsub.x(3)) def testCreateModuleNamespace(self): namespace={} Pyro4.utils.flame.createModule("testmodule2.submodule.subsub", "def x(y): return y*y", namespace=namespace) self.assertEqual(9, namespace["testmodule2"].submodule.subsub.x(3)) def testExecFunction(self): namespace={} Pyro4.utils.flame.exec_function("foobar=5+6", "", namespace) self.assertEqual(11, namespace["foobar"]) def testExecFunctionNewlines(self): namespace={} Pyro4.utils.flame.exec_function("if True:\r\n foobar=5+6\r\n ", "", namespace) self.assertEqual(11, namespace["foobar"]) def testFlameModule(self): with Pyro4.core.Daemon() as d: Pyro4.utils.flame.start(d) flameserver=d.objectsById[Pyro4.constants.FLAME_NAME] with Pyro4.utils.flame.FlameModule(flameserver, "sys") as m: self.assertTrue("module 'sys' at" in str(m)) self.assertTrue(isinstance(m.exc_info , Pyro4.core._RemoteMethod)) def testFlameBuiltin(self): with Pyro4.core.Daemon() as d: Pyro4.utils.flame.start(d) flameserver=d.objectsById[Pyro4.constants.FLAME_NAME] with Pyro4.utils.flame.FlameBuiltin(flameserver, "max") as builtin: self.assertTrue(hasattr(builtin, "__call__")) self.assertTrue("builtin 'max' at" in str(builtin)) if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] unittest.main() Pyro4-4.23/tests/PyroTests/test_ironpython.py000066400000000000000000000042531227003673200214250ustar00rootroot00000000000000""" Tests for some Ironpython peculiarities. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ import sys import pickle from testsupport import unittest if sys.platform=="cli": class IronPythonWeirdnessTests(unittest.TestCase): def testExceptionWithAttrsPickle(self): # ironpython doesn't pickle exception attributes # see bug report http://ironpython.codeplex.com/workitem/30805 ex=ValueError("some exception") ex.custom_attribute=42 ex2=pickle.loads(pickle.dumps(ex)) self.assertTrue(hasattr(ex,"custom_attribute")) self.assertFalse(hasattr(ex2,"custom_attribute")) # custom attribute will be gone after pickling self.assertNotEqual(ex2,ex) # the object won't be equal def testExceptionReduce(self): # ironpython doesn't pickle exception attributes # see bug report http://ironpython.codeplex.com/workitem/30805 # it could be caused by a malfunctioning __reduce__ ex=ValueError("some exception") ex.custom_attribute=42 r=ex.__reduce__() # the reduce result should be: # (ValueError, ("some exception",), {"custom_attribute": 42}) # but in Ironpython the custom attributes are not returned. self.assertNotEqual( (ValueError, ("some exception",), {"custom_attribute": 42}), r) self.assertEqual( (ValueError, ("some exception",)), r) def testTbFrame(self): # there's some stuff missing on traceback frames # this prevents a detailed stack trace to be printed by # the functions in util.py, for instance. def crash(): a=1 b=0 return a//b try: crash() except: ex_t, ex_v, ex_tb=sys.exc_info() while ex_tb.tb_next: ex_tb=ex_tb.tb_next self.assertEqual(None, ex_tb.tb_frame.f_back) # should not be none... :( if __name__ == "__main__": unittest.main() Pyro4-4.23/tests/PyroTests/test_message.py000077500000000000000000000200031227003673200206320ustar00rootroot00000000000000""" Tests for pyro write protocol message. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ import hashlib import hmac from testsupport import unittest import Pyro4.message from Pyro4.message import Message import Pyro4.constants import Pyro4.util import Pyro4.errors def pyrohmac(data, annotations={}): mac = hmac.new(Pyro4.config.HMAC_KEY, data, digestmod=hashlib.sha1) for k, v in annotations.items(): if k != "HMAC": mac.update(v) return mac.digest() class ConnectionMock(object): def __init__(self, data=b""): self.received = data def send(self, data): self.received += data def recv(self, datasize): chunk = self.received[:datasize] self.received = self.received[datasize:] return chunk class MessageTestsHmac(unittest.TestCase): def setUp(self): Pyro4.config.HMAC_KEY = b"testsuite" self.ser = Pyro4.util.get_serializer(Pyro4.config.SERIALIZER) def tearDown(self): Pyro4.config.HMAC_KEY = None def testMessage(self): Message(99, b"", self.ser.serializer_id, 0, 0) # doesn't check msg type here self.assertRaises(Pyro4.errors.ProtocolError, Message.from_header, "FOOBAR") msg = Message(Pyro4.message.MSG_CONNECT, b"hello", self.ser.serializer_id, 0, 0) self.assertEqual(Pyro4.message.MSG_CONNECT, msg.type) self.assertEqual(5, msg.data_size) self.assertEqual(b"hello", msg.data) self.assertEqual(4+2+20, msg.annotations_size) mac = pyrohmac(b"hello", msg.annotations) self.assertDictEqual({"HMAC": mac}, msg.annotations) hdr = msg.to_bytes()[:24] msg = Message.from_header(hdr) self.assertEqual(Pyro4.message.MSG_CONNECT, msg.type) self.assertEqual(4+2+20, msg.annotations_size) self.assertEqual(5, msg.data_size) hdr = Message(Pyro4.message.MSG_RESULT, b"", self.ser.serializer_id, 0, 0).to_bytes()[:24] msg = Message.from_header(hdr) self.assertEqual(Pyro4.message.MSG_RESULT, msg.type) self.assertEqual(4+2+20, msg.annotations_size) self.assertEqual(0, msg.data_size) hdr = Message(Pyro4.message.MSG_RESULT, b"hello", 12345, 60006, 30003).to_bytes()[:24] msg = Message.from_header(hdr) self.assertEqual(Pyro4.message.MSG_RESULT, msg.type) self.assertEqual(60006, msg.flags) self.assertEqual(5, msg.data_size) self.assertEqual(12345, msg.serializer_id) self.assertEqual(30003, msg.seq) msg = Message(255, b"", self.ser.serializer_id, 0, 255).to_bytes() self.assertEqual(50, len(msg)) msg = Message(1, b"", self.ser.serializer_id, 0, 255).to_bytes() self.assertEqual(50, len(msg)) msg = Message(1, b"", self.ser.serializer_id, flags=253, seq=254).to_bytes() self.assertEqual(50, len(msg)) # compression is a job of the code supplying the data, so the messagefactory should leave it untouched data = b"x"*1000 msg = Message(Pyro4.message.MSG_INVOKE, data, self.ser.serializer_id, 0, 0).to_bytes() msg2 = Message(Pyro4.message.MSG_INVOKE, data, self.ser.serializer_id, Pyro4.message.FLAGS_COMPRESSED, 0).to_bytes() self.assertEqual(len(msg), len(msg2)) def testMessageHeaderDatasize(self): msg = Message(Pyro4.message.MSG_RESULT, b"hello", 12345, 60006, 30003) msg.data_size = 0x12345678 # hack it to a large value to see if it comes back ok hdr = msg.to_bytes()[:24] msg = Message.from_header(hdr) self.assertEqual(Pyro4.message.MSG_RESULT, msg.type) self.assertEqual(60006, msg.flags) self.assertEqual(0x12345678, msg.data_size) self.assertEqual(12345, msg.serializer_id) self.assertEqual(30003, msg.seq) def testAnnotations(self): annotations = { "TEST": b"abcde" } msg = Message(Pyro4.message.MSG_CONNECT, b"hello", self.ser.serializer_id, 0, 0, annotations) data = msg.to_bytes() annotations_size = 4+2+20 + 4+2+5 self.assertEqual(msg.header_size + 5 + annotations_size, len(data)) self.assertEqual(annotations_size, msg.annotations_size) self.assertEqual(2, len(msg.annotations)) self.assertEqual(b"abcde", msg.annotations["TEST"]) mac = pyrohmac(b"hello", annotations) self.assertEqual(mac, msg.annotations["HMAC"]) def testAnnotationsIdLength4(self): try: msg = Message(Pyro4.message.MSG_CONNECT, b"hello", self.ser.serializer_id, 0, 0, { "TOOLONG": b"abcde" }) data = msg.to_bytes() self.fail("should fail, too long") except Pyro4.errors.ProtocolError: pass try: msg = Message(Pyro4.message.MSG_CONNECT, b"hello", self.ser.serializer_id, 0, 0, { "QQ": b"abcde" }) data = msg.to_bytes() self.fail("should fail, too short") except Pyro4.errors.ProtocolError: pass def testRecvAnnotations(self): annotations = { "TEST": b"abcde" } msg = Message(Pyro4.message.MSG_CONNECT, b"hello", self.ser.serializer_id, 0, 0, annotations) c = ConnectionMock() c.send(msg.to_bytes()) msg = Message.recv(c) self.assertEqual(0, len(c.received)) self.assertEqual(5, msg.data_size) self.assertEqual(b"hello", msg.data) self.assertEqual(b"abcde", msg.annotations["TEST"]) self.assertTrue("HMAC" in msg.annotations) def testProtocolVersion(self): version = Pyro4.constants.PROTOCOL_VERSION Pyro4.constants.PROTOCOL_VERSION = 0 # fake invalid protocol version number msg = Message(Pyro4.message.MSG_RESULT, b"", self.ser.serializer_id, 0, 1).to_bytes() Pyro4.constants.PROTOCOL_VERSION = version self.assertRaises(Pyro4.errors.ProtocolError, Message.from_header, msg) def testHmac(self): try: hk = Pyro4.config.HMAC_KEY Pyro4.config.HMAC_KEY = b"test key" data = Message(Pyro4.message.MSG_RESULT, b"test", 42, 0, 1).to_bytes() c = ConnectionMock(data) finally: Pyro4.config.HMAC_KEY = hk # test checking of different hmacs try: Message.recv(c) self.fail("crash expected") except Pyro4.errors.SecurityError as x: self.assertTrue("hmac" in str(x)) c = ConnectionMock(data) # test that it works again when resetting the key try: hk = Pyro4.config.HMAC_KEY Pyro4.config.HMAC_KEY = b"test key" Message.recv(c) finally: Pyro4.config.HMAC_KEY = hk c = ConnectionMock(data) # test that it doesn't work when no key is set try: hk = Pyro4.config.HMAC_KEY Pyro4.config.HMAC_KEY = b"" Message.recv(c) self.fail("crash expected") except Pyro4.errors.SecurityError as x: self.assertTrue("hmac key config" in str(x)) finally: Pyro4.config.HMAC_KEY = hk def testChecksum(self): msg = Message(Pyro4.message.MSG_RESULT, b"test", 42, 0, 1) c = ConnectionMock() c.send(msg.to_bytes()) # corrupt the checksum bytes data = c.received data = data[:msg.header_size-2] + b'\x00\x00' + data[msg.header_size:] c = ConnectionMock(data) try: Message.recv(c) self.fail("crash expected") except Pyro4.errors.ProtocolError as x: self.assertTrue("checksum" in str(x)) class MessageTestsNoHmac(unittest.TestCase): def testRecvNoAnnotations(self): msg = Message(Pyro4.message.MSG_CONNECT, b"hello", 42, 0, 0) c = ConnectionMock() c.send(msg.to_bytes()) msg = Message.recv(c) self.assertEqual(0, len(c.received)) self.assertEqual(5, msg.data_size) self.assertEqual(b"hello", msg.data) self.assertEqual(0, msg.annotations_size) self.assertEqual(0, len(msg.annotations)) if __name__ == "__main__": unittest.main() Pyro4-4.23/tests/PyroTests/test_naming.py000066400000000000000000000222271227003673200204660ustar00rootroot00000000000000""" Tests for the name server (online/running). Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ from __future__ import with_statement import time import Pyro4.core import Pyro4.naming import Pyro4.socketutil import Pyro4.constants from Pyro4.errors import NamingError from Pyro4 import threadutil from testsupport import * class NSLoopThread(threadutil.Thread): def __init__(self, nameserver): super(NSLoopThread,self).__init__() self.setDaemon(True) self.nameserver=nameserver self.running=threadutil.Event() self.running.clear() def run(self): self.running.set() self.nameserver.requestLoop() class BCSetupTests(unittest.TestCase): def setUp(self): Pyro4.config.HMAC_KEY=b"testsuite" def tearDown(self): Pyro4.config.HMAC_KEY=None def testBCstart(self): myIpAddress=Pyro4.socketutil.getIpAddress("", workaround127=True) nsUri, nameserver, bcserver = Pyro4.naming.startNS(host=myIpAddress, port=0, bcport=0, enableBroadcast=False) self.assertTrue(bcserver is None) nameserver.close() nsUri, nameserver, bcserver = Pyro4.naming.startNS(host=myIpAddress, port=0, bcport=0, enableBroadcast=True) self.assertTrue(bcserver is not None, "expected a BC server to be running. Check DNS setup (hostname must not resolve to loopback address") self.assertTrue(bcserver.fileno() > 1) self.assertTrue(bcserver.sock is not None) nameserver.close() bcserver.close() class NameServerTests(unittest.TestCase): def setUp(self): Pyro4.config.POLLTIMEOUT=0.1 Pyro4.config.HMAC_KEY=b"testsuite" myIpAddress=Pyro4.socketutil.getIpAddress("", workaround127=True) self.nsUri, self.nameserver, self.bcserver = Pyro4.naming.startNS(host=myIpAddress, port=0, bcport=0) self.assertTrue(self.bcserver is not None,"expected a BC server to be running") self.bcserver.runInThread() self.daemonthread=NSLoopThread(self.nameserver) self.daemonthread.start() self.daemonthread.running.wait() time.sleep(0.05) self.old_bcPort=Pyro4.config.NS_BCPORT self.old_nsPort=Pyro4.config.NS_PORT self.old_nsHost=Pyro4.config.NS_HOST Pyro4.config.NS_PORT=self.nsUri.port Pyro4.config.NS_HOST=myIpAddress Pyro4.config.NS_BCPORT=self.bcserver.getPort() def tearDown(self): time.sleep(0.01) self.nameserver.shutdown() self.bcserver.close() # self.daemonthread.join() Pyro4.config.HMAC_KEY=None Pyro4.config.NS_HOST=self.old_nsHost Pyro4.config.NS_PORT=self.old_nsPort Pyro4.config.NS_BCPORT=self.old_bcPort def testLookupAndRegister(self): ns=Pyro4.naming.locateNS() # broadcast lookup self.assertTrue(isinstance(ns, Pyro4.core.Proxy)) ns._pyroRelease() ns=Pyro4.naming.locateNS(self.nsUri.host) # normal lookup self.assertTrue(isinstance(ns, Pyro4.core.Proxy)) uri=ns._pyroUri self.assertEqual("PYRO",uri.protocol) self.assertEqual(self.nsUri.host,uri.host) self.assertEqual(Pyro4.config.NS_PORT,uri.port) ns._pyroRelease() ns=Pyro4.naming.locateNS(self.nsUri.host,Pyro4.config.NS_PORT) uri=ns._pyroUri self.assertEqual("PYRO",uri.protocol) self.assertEqual(self.nsUri.host,uri.host) self.assertEqual(Pyro4.config.NS_PORT,uri.port) # check that we cannot register a stupid type self.assertRaises(TypeError, ns.register, "unittest.object1", 5555) # we can register str or URI, lookup always returns URI ns.register("unittest.object2", "PYRO:55555@host.com:4444") self.assertEqual(Pyro4.core.URI("PYRO:55555@host.com:4444"), ns.lookup("unittest.object2")) ns.register("unittest.object3", Pyro4.core.URI("PYRO:66666@host.com:4444")) self.assertEqual(Pyro4.core.URI("PYRO:66666@host.com:4444"), ns.lookup("unittest.object3")) ns._pyroRelease() def testDaemonPyroObj(self): uri=self.nsUri uri.object=Pyro4.constants.DAEMON_NAME with Pyro4.core.Proxy(uri) as daemonobj: daemonobj.ping() daemonobj.registered() try: daemonobj.shutdown() self.fail("should not succeed to call unexposed method on daemon") except AttributeError: pass def testMulti(self): uristr=str(self.nsUri) p=Pyro4.core.Proxy(uristr) p._pyroBind() p._pyroRelease() uri=Pyro4.naming.resolve(uristr) p=Pyro4.core.Proxy(uri) p._pyroBind() p._pyroRelease() uri=Pyro4.naming.resolve(uristr) p=Pyro4.core.Proxy(uri) p._pyroBind() p._pyroRelease() uri=Pyro4.naming.resolve(uristr) p=Pyro4.core.Proxy(uri) p._pyroBind() p._pyroRelease() uri=Pyro4.naming.resolve(uristr) p=Pyro4.core.Proxy(uri) p._pyroBind() p._pyroRelease() uri=Pyro4.naming.resolve(uristr) p=Pyro4.core.Proxy(uri) p._pyroBind() p._pyroRelease() daemonUri="PYRO:"+Pyro4.constants.DAEMON_NAME+"@"+uri.location _=Pyro4.naming.resolve(daemonUri) _=Pyro4.naming.resolve(daemonUri) _=Pyro4.naming.resolve(daemonUri) _=Pyro4.naming.resolve(daemonUri) _=Pyro4.naming.resolve(daemonUri) _=Pyro4.naming.resolve(daemonUri) uri=Pyro4.naming.resolve(daemonUri) pyronameUri="PYRONAME:"+Pyro4.constants.NAMESERVER_NAME+"@"+uri.location _=Pyro4.naming.resolve(pyronameUri) _=Pyro4.naming.resolve(pyronameUri) _=Pyro4.naming.resolve(pyronameUri) _=Pyro4.naming.resolve(pyronameUri) _=Pyro4.naming.resolve(pyronameUri) _=Pyro4.naming.resolve(pyronameUri) def testResolve(self): resolved1=Pyro4.naming.resolve(Pyro4.core.URI("PYRO:12345@host.com:4444")) resolved2=Pyro4.naming.resolve("PYRO:12345@host.com:4444") self.assertTrue(type(resolved1) is Pyro4.core.URI) self.assertEqual(resolved1, resolved2) self.assertEqual("PYRO:12345@host.com:4444", str(resolved1)) ns=Pyro4.naming.locateNS(self.nsUri.host, self.nsUri.port) host="["+self.nsUri.host+"]" if ":" in self.nsUri.host else self.nsUri.host uri=Pyro4.naming.resolve("PYRONAME:"+Pyro4.constants.NAMESERVER_NAME+"@"+host+":"+str(self.nsUri.port)) self.assertEqual("PYRO",uri.protocol) self.assertEqual(self.nsUri.host,uri.host) self.assertEqual(Pyro4.constants.NAMESERVER_NAME,uri.object) self.assertEqual(uri, ns._pyroUri) ns._pyroRelease() # broadcast lookup self.assertRaises(NamingError, Pyro4.naming.resolve, "PYRONAME:unknown_object") uri=Pyro4.naming.resolve("PYRONAME:"+Pyro4.constants.NAMESERVER_NAME) self.assertEqual(Pyro4.core.URI,type(uri)) self.assertEqual("PYRO",uri.protocol) # test some errors self.assertRaises(NamingError, Pyro4.naming.resolve, "PYRONAME:unknown_object@"+host) self.assertRaises(TypeError, Pyro4.naming.resolve, 999) #wrong arg type def testRefuseDottedNames(self): with Pyro4.naming.locateNS(self.nsUri.host, self.nsUri.port) as ns: # the name server should never have dotted names enabled self.assertRaises(AttributeError, ns.namespace.keys) self.assertTrue(ns._pyroConnection is not None) self.assertTrue(ns._pyroConnection is None) class NameServerTests0000(unittest.TestCase): def setUp(self): Pyro4.config.POLLTIMEOUT=0.1 Pyro4.config.HMAC_KEY=b"testsuite" self.nsUri, self.nameserver, self.bcserver = Pyro4.naming.startNS(host="", port=0, bcport=0) self.assertEqual("0.0.0.0", self.nsUri.host, "for hostname \"\" the resulting ip must be 0.0.0.0") self.assertTrue(self.bcserver is not None,"expected a BC server to be running") self.bcserver.runInThread() self.old_bcPort=Pyro4.config.NS_BCPORT self.old_nsPort=Pyro4.config.NS_PORT self.old_nsHost=Pyro4.config.NS_HOST Pyro4.config.NS_PORT=self.nsUri.port Pyro4.config.NS_HOST=self.nsUri.host Pyro4.config.NS_BCPORT=self.bcserver.getPort() def tearDown(self): time.sleep(0.01) self.nameserver.shutdown() self.bcserver.close() Pyro4.config.NS_HOST=self.old_nsHost Pyro4.config.NS_PORT=self.old_nsPort Pyro4.config.NS_BCPORT=self.old_bcPort Pyro4.config.HMAC_KEY=None def testBCLookup0000(self): ns=Pyro4.naming.locateNS() # broadcast lookup self.assertTrue(isinstance(ns, Pyro4.core.Proxy)) self.assertNotEqual("0.0.0.0", ns._pyroUri.host, "returned location must not be 0.0.0.0 when running on 0.0.0.0") ns._pyroRelease() if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] unittest.main() Pyro4-4.23/tests/PyroTests/test_naming2.py000066400000000000000000000256501227003673200205530ustar00rootroot00000000000000""" Tests for the name server (offline/basic logic). Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ from __future__ import with_statement import sys, select, os import Pyro4.core import Pyro4.naming import Pyro4.nsc import Pyro4.constants import Pyro4.socketutil from Pyro4.errors import NamingError,PyroError from testsupport import * class OfflineNameServerTests(unittest.TestCase): def setUp(self): Pyro4.config.HMAC_KEY=b"testsuite" def tearDown(self): Pyro4.config.HMAC_KEY=None def testRegister(self): ns=Pyro4.naming.NameServer() ns.ping() ns.register("test.object1","PYRO:000000@host.com:4444") ns.register("test.object2","PYRO:222222@host.com:4444") ns.register("test.object3","PYRO:333333@host.com:4444") self.assertEqual("PYRO:000000@host.com:4444",str(ns.lookup("test.object1"))) ns.register("test.object1","PYRO:111111@host.com:4444") # registering again should be ok by default self.assertEqual("PYRO:111111@host.com:4444",str(ns.lookup("test.object1")), "should be new uri") ns.register("test.sub.objectA",Pyro4.core.URI("PYRO:AAAAAA@host.com:4444")) ns.register("test.sub.objectB",Pyro4.core.URI("PYRO:BBBBBB@host.com:4444")) # if safe=True, a registration of an existing name shoould give a NamingError self.assertRaises(NamingError, ns.register, "test.object1", "PYRO:X@Y:5555", safe=True) self.assertRaises(TypeError, ns.register, None, None) self.assertRaises(TypeError, ns.register, 4444, 4444) self.assertRaises(TypeError, ns.register, "test.wrongtype", 4444) self.assertRaises(TypeError, ns.register, 4444, "PYRO:X@Y:5555") self.assertRaises(NamingError, ns.lookup, "unknown_object") uri=ns.lookup("test.object3") self.assertEqual(Pyro4.core.URI("PYRO:333333@host.com:4444"), uri) # lookup always returns URI ns.remove("unknown_object") ns.remove("test.object1") ns.remove("test.object2") ns.remove("test.object3") all=ns.list() self.assertEqual(2, len(all)) # 2 leftover objects self.assertRaises(PyroError, ns.register, "test.nonurivalue", "THISVALUEISNOTANURI") def testRemove(self): ns=Pyro4.naming.NameServer() ns.register(Pyro4.constants.NAMESERVER_NAME, "PYRO:nameserver@host:555") for i in range(20): ns.register("test.%d" % i, "PYRO:obj@host:555") self.assertEqual(21, len(ns.list())) self.assertEqual(0, ns.remove("wrong")) self.assertEqual(0, ns.remove(prefix="wrong")) self.assertEqual(0, ns.remove(regex="wrong.*")) self.assertEqual(1, ns.remove("test.0")) self.assertEqual(20, len(ns.list())) self.assertEqual(11, ns.remove(prefix="test.1")) # 1, 10-19 self.assertEqual(8, ns.remove(regex=r"test\..")) # 2-9 self.assertEqual(1, len(ns.list())) def testRemoveProtected(self): ns=Pyro4.naming.NameServer() ns.register(Pyro4.constants.NAMESERVER_NAME, "PYRO:nameserver@host:555") self.assertEqual(0, ns.remove(Pyro4.constants.NAMESERVER_NAME)) self.assertEqual(0, ns.remove(prefix="Pyro")) self.assertEqual(0, ns.remove(regex="Pyro.*")) self.assertTrue(Pyro4.constants.NAMESERVER_NAME in ns.list()) def testUnicodeNames(self): ns=Pyro4.naming.NameServer() uri=Pyro4.core.URI("PYRO:unicode"+unichr(0x20ac)+"@host:5555") ns.register("unicodename"+unichr(0x20ac), uri) x=ns.lookup("unicodename"+unichr(0x20ac)) self.assertEqual(uri, x) def testList(self): ns=Pyro4.naming.NameServer() ns.register("test.objects.1","PYRONAME:something1") ns.register("test.objects.2","PYRONAME:something2") ns.register("test.objects.3","PYRONAME:something3") ns.register("test.other.a","PYRONAME:somethingA") ns.register("test.other.b","PYRONAME:somethingB") ns.register("test.other.c","PYRONAME:somethingC") ns.register("entirely.else","PYRONAME:meh") objects=ns.list() self.assertEqual(7,len(objects)) objects=ns.list(prefix="nothing") self.assertEqual(0,len(objects)) objects=ns.list(prefix="test.") self.assertEqual(6,len(objects)) objects=ns.list(regex=r".+other..") self.assertEqual(3,len(objects)) self.assertTrue("test.other.a" in objects) self.assertEqual("PYRONAME:somethingA", objects["test.other.a"]) objects=ns.list(regex=r"\d\d\d\d\d\d\d\d\d\d") self.assertEqual(0,len(objects)) self.assertRaises(NamingError, ns.list, regex="((((((broken") def testRefuseDotted(self): try: Pyro4.config.DOTTEDNAMES=True _=Pyro4.naming.NameServerDaemon(port=0) self.fail("should refuse to create name server") except PyroError: pass finally: Pyro4.config.DOTTEDNAMES=False def testNameserverWithStmt(self): ns=Pyro4.naming.NameServerDaemon(port=0) self.assertFalse(ns.nameserver is None) ns.close() self.assertTrue(ns.nameserver is None) with Pyro4.naming.NameServerDaemon(port=0) as ns: self.assertFalse(ns.nameserver is None) pass self.assertTrue(ns.nameserver is None) try: with Pyro4.naming.NameServerDaemon(port=0) as ns: self.assertFalse(ns.nameserver is None) print(1//0) # cause an error self.fail("expected error") except ZeroDivisionError: pass self.assertTrue(ns.nameserver is None) ns=Pyro4.naming.NameServerDaemon(port=0) with ns: pass try: with ns: pass self.fail("expected error") except PyroError: # you cannot re-use a name server object in multiple with statements pass ns.close() def testStartNSfunc(self): myIpAddress=Pyro4.socketutil.getIpAddress("", workaround127=True) uri1,ns1,bc1=Pyro4.naming.startNS(host=myIpAddress, port=0, bcport=0, enableBroadcast=False) uri2,ns2,bc2=Pyro4.naming.startNS(host=myIpAddress, port=0, bcport=0, enableBroadcast=True) self.assertTrue(isinstance(uri1, Pyro4.core.URI)) self.assertTrue(isinstance(ns1, Pyro4.naming.NameServerDaemon)) self.assertTrue(bc1 is None) self.assertTrue(isinstance(bc2, Pyro4.naming.BroadcastServer)) sock=bc2.sock self.assertTrue(hasattr(sock,"fileno")) _=bc2.processRequest ns1.close() ns2.close() bc2.close() def testOwnloopBasics(self): myIpAddress=Pyro4.socketutil.getIpAddress("",workaround127=True) uri1,ns1,bc1=Pyro4.naming.startNS(host=myIpAddress, port=0, bcport=0, enableBroadcast=True) self.assertTrue(bc1.fileno() > 0) if Pyro4.socketutil.hasPoll: p=select.poll() if os.name=="java": # jython requires nonblocking sockets for poll ns1.sock.setblocking(False) bc1.sock.setblocking(False) for s in ns1.sockets: p.register(s, select.POLLIN) p.register(bc1.fileno(), select.POLLIN) p.poll(100) if hasattr(p,"close"): p.close() else: rs=[bc1] rs.extend(ns1.sockets) _,_,_=select.select(rs,[],[],0.1) ns1.close() bc1.close() def testNSmain(self): oldstdout=sys.stdout oldstderr=sys.stderr try: sys.stdout=StringIO() sys.stderr=StringIO() self.assertRaises(SystemExit, Pyro4.naming.main, ["--invalidarg"]) self.assertTrue("no such option" in sys.stderr.getvalue()) sys.stderr.truncate(0) sys.stdout.truncate(0) self.assertRaises(SystemExit, Pyro4.naming.main, ["-h"]) self.assertTrue("show this help message" in sys.stdout.getvalue()) finally: sys.stdout=oldstdout sys.stderr=oldstderr def testNSCmain(self): oldstdout=sys.stdout oldstderr=sys.stderr try: sys.stdout=StringIO() sys.stderr=StringIO() self.assertRaises(SystemExit, Pyro4.nsc.main, ["--invalidarg"]) self.assertTrue("no such option" in sys.stderr.getvalue()) sys.stderr.truncate(0) sys.stdout.truncate(0) self.assertRaises(SystemExit, Pyro4.nsc.main, ["-h"]) self.assertTrue("show this help message" in sys.stdout.getvalue()) finally: sys.stdout=oldstdout sys.stderr=oldstderr def testNSCfunctions(self): oldstdout=sys.stdout oldstderr=sys.stderr try: sys.stdout=StringIO() sys.stderr=StringIO() ns=Pyro4.naming.NameServer() Pyro4.nsc.handleCommand(ns, None, ["foo"]) self.assertTrue(sys.stdout.getvalue().startswith("Error: KeyError ")) Pyro4.nsc.handleCommand(ns, None, ["ping"]) self.assertTrue(sys.stdout.getvalue().endswith("ping ok.\n")) Pyro4.nsc.handleCommand(ns, None, ["list"]) self.assertTrue(sys.stdout.getvalue().endswith("END LIST \n")) Pyro4.nsc.handleCommand(ns, None, ["listmatching", "name.$"]) self.assertTrue(sys.stdout.getvalue().endswith("END LIST - regex 'name.$'\n")) self.assertFalse("name1" in sys.stdout.getvalue()) Pyro4.nsc.handleCommand(ns, None, ["register", "name1", "PYRO:obj1@hostname:9999"]) self.assertTrue(sys.stdout.getvalue().endswith("Registered name1\n")) Pyro4.nsc.handleCommand(ns, None, ["remove", "name2"]) self.assertTrue(sys.stdout.getvalue().endswith("Nothing removed\n")) Pyro4.nsc.handleCommand(ns, None, ["listmatching", "name.$"]) self.assertTrue("name1 --> PYRO:obj1@hostname:9999" in sys.stdout.getvalue()) #Pyro4.nsc.handleCommand(ns, None, ["removematching","name?"]) finally: sys.stdout=oldstdout sys.stderr=oldstderr def testNAT(self): uri,ns,bc=Pyro4.naming.startNS(host="", port=0, enableBroadcast=True, nathost="nathosttest", natport=12345) self.assertEqual("nathosttest:12345", uri.location) self.assertEqual("nathosttest:12345", ns.uriFor("thing").location) self.assertNotEqual("nathosttest:12345", bc.nsUri.location, "broadcast location must not be the NAT location") ns.close() bc.close() if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] unittest.main() Pyro4-4.23/tests/PyroTests/test_package.py000066400000000000000000000016771227003673200206160ustar00rootroot00000000000000""" Tests for the package structure and import names. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ import Pyro4 import Pyro4.constants import Pyro4.core import Pyro4.errors import Pyro4.naming import Pyro4.nsc import Pyro4.socketutil import Pyro4.threadutil import Pyro4.util from testsupport import unittest class TestPackage(unittest.TestCase): def testPyro4(self): self.assertEqual(Pyro4.core.Daemon, Pyro4.Daemon) self.assertEqual(Pyro4.core.Proxy, Pyro4.Proxy) self.assertEqual(Pyro4.core.URI, Pyro4.URI) self.assertEqual(Pyro4.core.callback, Pyro4.callback) self.assertEqual(Pyro4.core.async, Pyro4.async) self.assertEqual(Pyro4.core.batch, Pyro4.batch) self.assertEqual(Pyro4.naming.locateNS, Pyro4.locateNS) self.assertEqual(Pyro4.naming.resolve, Pyro4.resolve) if __name__ == "__main__": unittest.main() Pyro4-4.23/tests/PyroTests/test_serialize.py000066400000000000000000000410761227003673200212070ustar00rootroot00000000000000""" Tests for the data serializer. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ from __future__ import with_statement import sys import os import copy import pprint import Pyro4.util import Pyro4.errors import Pyro4.core import Pyro4.futures import Pyro4.message from testsupport import * class SerializeTests_pickle(unittest.TestCase): SERIALIZER="pickle" def setUp(self): self.previous_serializer=Pyro4.config.SERIALIZER Pyro4.config.SERIALIZER=self.SERIALIZER Pyro4.config.HMAC_KEY=b"testsuite" self.ser=Pyro4.util.get_serializer(Pyro4.config.SERIALIZER) def tearDown(self): Pyro4.config.HMAC_KEY=None Pyro4.config.SERIALIZER=self.previous_serializer def testSerItself(self): s=Pyro4.util.get_serializer(Pyro4.config.SERIALIZER) p,_=self.ser.serializeData(s) s2=self.ser.deserializeData(p) self.assertEqual(s,s2) self.assertTrue(s==s2) self.assertFalse(s!=s2) def testSerUnicode(self): data = unicode("x") ser,_ = self.ser.serializeData(data) expected_type = str if sys.platform=="cli" else bytes # ironpython serializes into str, not bytes... :( self.assertTrue(type(ser) is expected_type) ser,_ = self.ser.serializeCall(data, unicode("method"), [], {}) self.assertTrue(type(ser) is expected_type) def testSerCompression(self): d1,c1=self.ser.serializeData("small data", compress=True) d2,c2=self.ser.serializeData("small data", compress=False) self.assertFalse(c1) self.assertEqual(d1,d2) bigdata="x"*1000 d1,c1=self.ser.serializeData(bigdata, compress=False) d2,c2=self.ser.serializeData(bigdata, compress=True) self.assertFalse(c1) self.assertTrue(c2) self.assertTrue(len(d2) < len(d1)) self.assertEqual(bigdata, self.ser.deserializeData(d1, compressed=False)) self.assertEqual(bigdata, self.ser.deserializeData(d2, compressed=True)) def testSerErrors(self): e1=Pyro4.errors.NamingError(unicode("x")) e1._pyroTraceback = ["this is the remote traceback"] orig_e = copy.copy(e1) e2=Pyro4.errors.PyroError(unicode("x")) e3=Pyro4.errors.ProtocolError(unicode("x")) if sys.platform=="cli": Pyro4.util.fixIronPythonExceptionForPickle(e1, True) p,_=self.ser.serializeData(e1) e=self.ser.deserializeData(p) if sys.platform=="cli": Pyro4.util.fixIronPythonExceptionForPickle(e, False) self.assertTrue(isinstance(e, Pyro4.errors.NamingError)) self.assertEqual(repr(orig_e), repr(e)) self.assertEqual(["this is the remote traceback"], e._pyroTraceback, "remote traceback info should be present") p,_=self.ser.serializeData(e2) e=self.ser.deserializeData(p) self.assertTrue(isinstance(e, Pyro4.errors.PyroError)) self.assertEqual(repr(e2), repr(e)) p,_=self.ser.serializeData(e3) e=self.ser.deserializeData(p) self.assertTrue(isinstance(e, Pyro4.errors.ProtocolError)) self.assertEqual(repr(e3), repr(e)) def testSerializeExceptionWithAttr(self): ex=ZeroDivisionError("test error") ex._pyroTraceback=["test traceback payload"] Pyro4.util.fixIronPythonExceptionForPickle(ex,True) # hack for ironpython data,compressed=self.ser.serializeData(ex) ex2=self.ser.deserializeData(data,compressed) Pyro4.util.fixIronPythonExceptionForPickle(ex2,False) # hack for ironpython self.assertEqual(ZeroDivisionError, type(ex2)) self.assertTrue(hasattr(ex2, "_pyroTraceback")) self.assertEqual(["test traceback payload"], ex2._pyroTraceback) def testSerCoreOffline(self): uri=Pyro4.core.URI("PYRO:9999@host.com:4444") p,_=self.ser.serializeData(uri) uri2=self.ser.deserializeData(p) self.assertEqual(uri, uri2) self.assertEqual("PYRO",uri2.protocol) self.assertEqual("9999",uri2.object) self.assertEqual("host.com:4444",uri2.location) self.assertEqual(4444, uri2.port) self.assertEqual(None, uri2.sockname) uri=Pyro4.core.URI("PYRO:12345@./u:/tmp/socketname") p,_=self.ser.serializeData(uri) uri2=self.ser.deserializeData(p) self.assertEqual(uri, uri2) self.assertEqual("PYRO",uri2.protocol) self.assertEqual("12345",uri2.object) self.assertEqual("./u:/tmp/socketname",uri2.location) self.assertEqual(None, uri2.port) self.assertEqual("/tmp/socketname", uri2.sockname) proxy=Pyro4.core.Proxy("PYRO:9999@host.com:4444") proxy._pyroTimeout=42 self.assertTrue(proxy._pyroConnection is None) p,_=self.ser.serializeData(proxy) proxy2=self.ser.deserializeData(p) self.assertTrue(proxy._pyroConnection is None) self.assertTrue(proxy2._pyroConnection is None) self.assertEqual(proxy2._pyroUri, proxy._pyroUri) self.assertEqual(42, proxy2._pyroTimeout) def testNested(self): if self.SERIALIZER=="marshal": self.skipTest("marshal can't serialize custom objects") uri1=Pyro4.core.URI("PYRO:1111@host.com:111") uri2=Pyro4.core.URI("PYRO:2222@host.com:222") _=self.ser.serializeData(uri1) data=[uri1, uri2] p,_=self.ser.serializeData(data) [u1, u2]=self.ser.deserializeData(p) self.assertEqual(uri1, u1) self.assertEqual(uri2, u2) def testSerDaemonHack(self): # This tests the hack that a Daemon should be serializable, # but only to support serializing Pyro objects. # The serialized form of a Daemon should be empty (and thus, useless) with Pyro4.core.Daemon(port=0) as daemon: d,_=self.ser.serializeData(daemon) d2=self.ser.deserializeData(d) self.assertTrue(len(d2.__dict__)==0, "deserialized daemon should be empty") try: Pyro4.config.AUTOPROXY=False obj=pprint.PrettyPrinter(stream="dummy", width=42) obj.name="hello" daemon.register(obj) o,_=self.ser.serializeData(obj) if self.SERIALIZER=="pickle": # only pickle can deserialize the PrettyPrinter class without the need of explicit deserialization function o2=self.ser.deserializeData(o) self.assertEqual("hello", o2.name) self.assertEqual(42, o2._width) finally: Pyro4.config.AUTOPROXY=True def testPyroClasses(self): uri = Pyro4.core.URI("PYRO:object@host:4444") s, c = self.ser.serializeData(uri) x = self.ser.deserializeData(s, c) self.assertEqual(uri, x) uri=Pyro4.core.URI("PYRO:12345@./u:/tmp/socketname") s, c = self.ser.serializeData(uri) x = self.ser.deserializeData(s, c) self.assertEqual(uri, x) proxy=Pyro4.core.Proxy(uri) s, c = self.ser.serializeData(proxy) x = self.ser.deserializeData(s, c) self.assertEqual(proxy._pyroUri, x._pyroUri) self.assertTrue(x._) daemon=Pyro4.core.Daemon() s, c = self.ser.serializeData(daemon) x = self.ser.deserializeData(s, c) self.assertTrue(isinstance(x, Pyro4.core.Daemon)) wrapper = Pyro4.futures._ExceptionWrapper(ZeroDivisionError("divided by zero")) s, c = self.ser.serializeData(wrapper) x = self.ser.deserializeData(s, c) self.assertIsInstance(x, Pyro4.futures._ExceptionWrapper) self.assertEqual("divided by zero", str(x.exception)) def testAutoProxy(self): if self.SERIALIZER=="marshal": self.skipTest("marshal can't serialize custom objects") self.ser.register_type_replacement(MyThing2, Pyro4.core.pyroObjectToAutoProxy) t1 = MyThing2("1") t2 = MyThing2("2") with Pyro4.core.Daemon() as d: d.register(t1, "thingy1") d.register(t2, "thingy2") data = [t1, ["apple", t2] ] s, c = self.ser.serializeData(data) data = self.ser.deserializeData(s, c) self.assertEqual("apple", data[1][0]) p1 = data[0] p2 = data[1][1] self.assertIsInstance(p1, Pyro4.core.Proxy) self.assertIsInstance(p2, Pyro4.core.Proxy) self.assertEqual("thingy1", p1._pyroUri.object) self.assertEqual("thingy2", p2._pyroUri.object) def testCustomClassFail(self): if self.SERIALIZER=="pickle": self.skipTest("pickle simply serializes custom classes") o = pprint.PrettyPrinter(stream="dummy", width=42) s, c = self.ser.serializeData(o) try: x = self.ser.deserializeData(s, c) self.fail("error expected, shouldn't deserialize unknown class") except Pyro4.errors.ProtocolError: pass def testData(self): data = [42, "hello"] ser, compressed = self.ser.serializeData(data) expected_type = str if sys.platform=="cli" else bytes # ironpython serializes into str, not bytes... :( self.assertTrue(type(ser) is expected_type) self.assertFalse(compressed) data2 = self.ser.deserializeData(ser, compressed=False) self.assertEqual(data, data2) def testCallPlain(self): ser, compressed = self.ser.serializeCall("object", "method", "vargs", "kwargs") self.assertFalse(compressed) obj, method, vargs, kwargs = self.ser.deserializeCall(ser, compressed=False) self.assertEqual("object", obj) self.assertEqual("method", method) self.assertEqual("vargs", vargs) self.assertEqual("kwargs", kwargs) def testCallPyroObjAsArg(self): if self.SERIALIZER=="marshal": self.skipTest("marshal can't serialize custom objects") uri = Pyro4.core.URI("PYRO:555@localhost:80") ser, compressed = self.ser.serializeCall("object", "method", [uri], {"thing": uri}) self.assertFalse(compressed) obj, method, vargs, kwargs = self.ser.deserializeCall(ser, compressed=False) self.assertEqual("object", obj) self.assertEqual("method", method) self.assertEqual([uri], vargs) self.assertEqual({"thing": uri}, kwargs) def testCallCustomObjAsArg(self): if self.SERIALIZER=="marshal": self.skipTest("marshal can't serialize custom objects") e = ZeroDivisionError("hello") ser, compressed = self.ser.serializeCall("object", "method", [e], {"thing": e}) self.assertFalse(compressed) obj, method, vargs, kwargs = self.ser.deserializeCall(ser, compressed=False) self.assertEqual("object", obj) self.assertEqual("method", method) self.assertTrue(isinstance(vargs, list)) self.assertTrue(isinstance(vargs[0], ZeroDivisionError)) self.assertEqual("hello", str(vargs[0])) self.assertTrue(isinstance(kwargs["thing"], ZeroDivisionError)) self.assertEqual("hello", str(kwargs["thing"])) def testSerializeException(self): e = ZeroDivisionError() d, c = self.ser.serializeData(e) e2 = self.ser.deserializeData(d, c) self.assertTrue(isinstance(e2, ZeroDivisionError)) self.assertEqual("", str(e2)) e = ZeroDivisionError("hello") d, c = self.ser.serializeData(e) e2 = self.ser.deserializeData(d, c) self.assertTrue(isinstance(e2, ZeroDivisionError)) self.assertEqual("hello", str(e2)) e = ZeroDivisionError("hello", 42) d, c = self.ser.serializeData(e) e2 = self.ser.deserializeData(d, c) self.assertTrue(isinstance(e2, ZeroDivisionError)) self.assertTrue(str(e2) in ("('hello', 42)", "(u'hello', 42)")) e.custom_attribute = 999 if sys.platform=="cli": Pyro4.util.fixIronPythonExceptionForPickle(e, True) ser, compressed = self.ser.serializeData(e) e2 = self.ser.deserializeData(ser, compressed) if sys.platform=="cli": Pyro4.util.fixIronPythonExceptionForPickle(e2, False) self.assertTrue(isinstance(e2, ZeroDivisionError)) self.assertTrue(str(e2) in ("('hello', 42)", "(u'hello', 42)")) self.assertEqual(999, e2.custom_attribute) def testSerializeSpecialException(self): self.assertTrue("GeneratorExit" in Pyro4.util.all_exceptions) e = GeneratorExit() d, c = self.ser.serializeData(e) e2 = self.ser.deserializeData(d, c) self.assertTrue(isinstance(e2, GeneratorExit)) def testRecreateClasses(self): self.assertEqual([1,2,3], self.ser.recreate_classes([1,2,3])) d = {"__class__": "invalid" } try: self.ser.recreate_classes(d) self.fail("error expected") except Pyro4.errors.ProtocolError: pass # ok d = {"__class__": "Pyro4.core.URI", "state": ['PYRO', '555', None, 'localhost', 80] } uri = self.ser.recreate_classes(d) self.assertEqual(Pyro4.core.URI("PYRO:555@localhost:80"), uri) number, uri = self.ser.recreate_classes([1,{"uri": d}]) self.assertEqual(1, number) self.assertEqual(Pyro4.core.URI("PYRO:555@localhost:80"), uri["uri"]) class SerializeTests_serpent(SerializeTests_pickle): SERIALIZER="serpent" class SerializeTests_json(SerializeTests_pickle): SERIALIZER="json" if os.name!="java": # The marshal serializer is not working correctly under jython, # see http://bugs.jython.org/issue2077 # So we only include this when not running jython class SerializeTests_marshal(SerializeTests_pickle): SERIALIZER="marshal" class GenericTests(unittest.TestCase): def testSerializersAvailable(self): Pyro4.util.get_serializer("pickle") Pyro4.util.get_serializer("marshal") try: import json Pyro4.util.get_serializer("json") except ImportError: pass try: import serpent Pyro4.util.get_serializer("serpent") except ImportError: pass def testSerializersAvailableById(self): Pyro4.util.get_serializer_by_id(Pyro4.message.SERIALIZER_PICKLE) Pyro4.util.get_serializer_by_id(Pyro4.message.SERIALIZER_MARSHAL) self.assertRaises(Pyro4.errors.ProtocolError, lambda: Pyro4.util.get_serializer_by_id(9999999)) def testDictClassFail(self): o = pprint.PrettyPrinter(stream="dummy", width=42) d = Pyro4.util.SerializerBase.class_to_dict(o) self.assertEqual(42, d["_width"]) self.assertEqual("pprint.PrettyPrinter", d["__class__"]) try: x = Pyro4.util.SerializerBase.dict_to_class(d) self.fail("error expected") except Pyro4.errors.ProtocolError: pass def testDictException(self): x = ZeroDivisionError("hello", 42) expected = { "__class__": None, "args": ("hello", 42), "attributes": {} } if sys.version_info < (3, 0): expected["__class__"] = "exceptions.ZeroDivisionError" else: expected["__class__"] = "builtins.ZeroDivisionError" d = Pyro4.util.SerializerBase.class_to_dict(x) self.assertEqual(expected, d) x.custom_attribute = 999 expected["attributes"] = {"custom_attribute": 999} d = Pyro4.util.SerializerBase.class_to_dict(x) self.assertEqual(expected, d) def testDictClassOk(self): uri = Pyro4.core.URI("PYRO:object@host:4444") d = Pyro4.util.SerializerBase.class_to_dict(uri) self.assertEqual("Pyro4.core.URI", d["__class__"]) self.assertTrue("state" in d) x = Pyro4.util.SerializerBase.dict_to_class(d) self.assertEqual(uri, x) self.assertEqual(4444, x.port) uri = Pyro4.core.URI("PYRO:12345@./u:/tmp/socketname") d = Pyro4.util.SerializerBase.class_to_dict(uri) self.assertEqual("Pyro4.core.URI", d["__class__"]) self.assertTrue("state" in d) x = Pyro4.util.SerializerBase.dict_to_class(d) self.assertEqual(uri, x) self.assertEqual("/tmp/socketname", x.sockname) if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] unittest.main() Pyro4-4.23/tests/PyroTests/test_server.py000066400000000000000000000747221227003673200205320ustar00rootroot00000000000000""" Tests for a running Pyro server, without timeouts. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ from __future__ import with_statement import Pyro4.core import Pyro4.errors import Pyro4.util import Pyro4.message import time, os, sys, platform from Pyro4 import threadutil from testsupport import * class MyThing(object): def __init__(self): self.dictionary={"number":42} def getDict(self): return self.dictionary def multiply(self,x,y): return x*y def divide(self,x,y): return x//y def ping(self): pass def echo(self, obj): return obj def delay(self, delay): time.sleep(delay) return "slept %d seconds" % delay def delayAndId(self, delay, id): time.sleep(delay) return "slept for "+str(id) def testargs(self,x,*args,**kwargs): if kwargs: key=list(kwargs.keys())[0] return [x, list(args), kwargs] # don't return tuples, this enables us to test json serialization as well. def nonserializableException(self): raise NonserializableError(("xantippe", lambda x: 0)) class DaemonLoopThread(threadutil.Thread): def __init__(self, pyrodaemon): super(DaemonLoopThread,self).__init__() self.setDaemon(True) self.pyrodaemon=pyrodaemon self.running=threadutil.Event() self.running.clear() def run(self): self.running.set() try: self.pyrodaemon.requestLoop() except: print("Swallow exception from terminated daemon") class DaemonWithSabotagedHandshake(Pyro4.core.Daemon): def _handshake(self, conn): # a bit of a hack, overriding this internal method to return a CONNECTFAIL... serializer = Pyro4.util.get_serializer("marshal") data, _ = serializer.serializeData("rigged connection failure", compress=False) msg = Pyro4.message.Message(Pyro4.message.MSG_CONNECTFAIL, data, serializer.serializer_id, 0, 1) conn.send(msg.to_bytes()) return False class ServerTestsBrokenHandshake(unittest.TestCase): def setUp(self): Pyro4.config.HMAC_KEY=b"testsuite" Pyro4.config.SERIALIZERS_ACCEPTED.add("pickle") self.daemon=DaemonWithSabotagedHandshake(port=0) obj=MyThing() uri=self.daemon.register(obj, "something") self.objectUri=uri self.daemonthread=DaemonLoopThread(self.daemon) self.daemonthread.start() self.daemonthread.running.wait() time.sleep(0.05) def tearDown(self): time.sleep(0.05) self.daemon.shutdown() self.daemonthread.join() Pyro4.config.HMAC_KEY=None Pyro4.config.SERIALIZERS_ACCEPTED.discard("pickle") def testDaemonConnectFail(self): # check what happens when the daemon responds with a failed connection msg with Pyro4.Proxy(self.objectUri) as p: try: p.ping() self.fail("expected CommunicationError") except Pyro4.errors.CommunicationError: xv=sys.exc_info()[1] message=str(xv) self.assertTrue("reason:" in message) self.assertTrue("rigged connection failure" in message) class ServerTestsOnce(unittest.TestCase): """tests that are fine to run with just a single server type""" def setUp(self): Pyro4.config.HMAC_KEY=b"testsuite" Pyro4.config.SERIALIZERS_ACCEPTED.add("pickle") self.daemon=Pyro4.core.Daemon(port=0) obj=MyThing() uri=self.daemon.register(obj, "something") self.objectUri=uri self.daemonthread=DaemonLoopThread(self.daemon) self.daemonthread.start() self.daemonthread.running.wait() time.sleep(0.05) def tearDown(self): time.sleep(0.05) if self.daemon is not None: self.daemon.shutdown() self.daemonthread.join() Pyro4.config.HMAC_KEY=None Pyro4.config.SERIALIZERS_ACCEPTED.discard("pickle") def testPingMessage(self): with Pyro4.core.Proxy(self.objectUri) as p: p._pyroBind() conn = p._pyroConnection msg = Pyro4.message.Message(Pyro4.message.MSG_PING, b"something", 42, 0, 999) conn.send(msg.to_bytes()) msg = Pyro4.message.Message.recv(conn, [Pyro4.message.MSG_PING]) self.assertEqual(Pyro4.message.MSG_PING, msg.type) self.assertEqual(999, msg.seq) self.assertEqual(b"pong", msg.data) def testNoDottedNames(self): Pyro4.config.DOTTEDNAMES=False with Pyro4.core.Proxy(self.objectUri) as p: self.assertEqual(55,p.multiply(5,11)) x=p.getDict() self.assertEqual({"number":42}, x) try: p.dictionary.update({"more":666}) # should fail with DOTTEDNAMES=False (the default) self.fail("expected AttributeError") except AttributeError: pass x=p.getDict() self.assertEqual({"number":42}, x) def testSomeArgumentTypes(self): with Pyro4.core.Proxy(self.objectUri) as p: self.assertEqual([1,[],{}], p.testargs(1)) self.assertEqual([1,[2,3],{'a':4}], p.testargs(1,2,3,a=4)) self.assertEqual([1,[],{'a':2}], p.testargs(1, **{'a':2})) @unittest.skipUnless(sys.version_info>=(2,6,5), "unicode kwargs needs 2.6.5 or newer") def testUnicodeKwargs(self): with Pyro4.core.Proxy(self.objectUri) as p: self.assertEqual([1,[],{unichr(65):2}], p.testargs(1, **{unichr(65):2})) result=p.testargs(unichr(0x20ac), **{unichr(0x20ac):2}) self.assertEqual(result[0], unichr(0x20ac)) key=list(result[2].keys())[0] self.assertTrue(type(key) is unicode) self.assertEqual(key, unichr(0x20ac)) def testDottedNames(self): try: Pyro4.config.DOTTEDNAMES=True with Pyro4.core.Proxy(self.objectUri) as p: self.assertEqual(55,p.multiply(5,11)) x=p.getDict() self.assertEqual({"number":42}, x) p.dictionary.update({"more":666}) # updating it remotely should work with DOTTEDNAMES=True x=p.getDict() self.assertEqual({"number":42, "more":666}, x) # eek, it got updated! finally: Pyro4.config.DOTTEDNAMES=False def testNormalProxy(self): with Pyro4.core.Proxy(self.objectUri) as p: self.assertEqual(42,p.multiply(7,6)) def testExceptions(self): with Pyro4.core.Proxy(self.objectUri) as p: try: p.divide(1,0) self.fail("should crash") except ZeroDivisionError: pass try: p.multiply("a", "b") self.fail("should crash") except TypeError: pass def testNonserializableException_other(self): with Pyro4.core.Proxy(self.objectUri) as p: try: p.nonserializableException() self.fail("should crash") except Exception: xt, xv, tb = sys.exc_info() self.assertTrue(issubclass(xt, Pyro4.errors.PyroError)) tblines = "\n".join(Pyro4.util.getPyroTraceback()) self.assertTrue("unsupported serialized class" in tblines) def testNonserializableException_pickle(self): with Pyro4.core.Proxy(self.objectUri) as p: Pyro4.config.SERIALIZER = "pickle" try: p.nonserializableException() self.fail("should crash") except Exception: xt, xv, tb = sys.exc_info() self.assertTrue(issubclass(xt, Pyro4.errors.PyroError)) tblines = "\n".join(Pyro4.util.getPyroTraceback()) self.assertTrue("PyroError: Error serializing exception" in tblines) s1 = "Original exception: :" s2 = "Original exception: :" self.assertTrue(s1 in tblines or s2 in tblines) self.assertTrue("raise NonserializableError((\"xantippe" in tblines) finally: Pyro4.config.SERIALIZER="serpent" def testBatchProxy(self): with Pyro4.core.Proxy(self.objectUri) as p: batch=Pyro4.batch(p) self.assertEqual(None,batch.multiply(7,6)) self.assertEqual(None,batch.divide(999,3)) self.assertEqual(None,batch.ping()) self.assertEqual(None,batch.divide(999,0)) # force an exception here self.assertEqual(None,batch.multiply(3,4)) # this call should not be performed after the error results=batch() self.assertEqual(42,next(results)) self.assertEqual(333,next(results)) self.assertEqual(None,next(results)) self.assertRaises(ZeroDivisionError, next, results) # 999//0 should raise this error self.assertRaises(StopIteration, next, results) # no more results should be available after the error def testAsyncProxy(self): with Pyro4.core.Proxy(self.objectUri) as p: async=Pyro4.async(p) begin=time.time() result=async.delayAndId(1,42) duration=time.time()-begin self.assertTrue(duration<0.1) self.assertFalse(result.ready) self.assertFalse(result.wait(0.5)) # not available within 0.5 sec self.assertEqual("slept for 42",result.value) self.assertTrue(result.ready) self.assertTrue(result.wait()) def testAsyncProxyCallchain(self): class FuncHolder(object): count=AtomicCounter() def function(self, value, increase=1): self.count.incr() return value+increase with Pyro4.core.Proxy(self.objectUri) as p: async=Pyro4.async(p) holder=FuncHolder() begin=time.time() result=async.multiply(2,3) result.then(holder.function, increase=10) \ .then(holder.function, increase=5) \ .then(holder.function) duration=time.time()-begin self.assertTrue(duration<0.1) value=result.value self.assertTrue(result.ready) self.assertEqual(22,value) self.assertEqual(3,holder.count.value()) def testBatchOneway(self): with Pyro4.core.Proxy(self.objectUri) as p: batch=Pyro4.batch(p) self.assertEqual(None,batch.multiply(7,6)) self.assertEqual(None,batch.delay(1)) # a delay shouldn't matter with oneway self.assertEqual(None,batch.multiply(3,4)) begin=time.time() results=batch(oneway=True) duration=time.time()-begin self.assertTrue(duration<0.1,"oneway batch with delay should return almost immediately") self.assertEqual(None,results) def testBatchAsync(self): with Pyro4.core.Proxy(self.objectUri) as p: batch=Pyro4.batch(p) self.assertEqual(None,batch.multiply(7,6)) self.assertEqual(None,batch.delay(1)) # a delay shouldn't matter with async self.assertEqual(None,batch.multiply(3,4)) begin=time.time() asyncresult=batch(async=True) duration=time.time()-begin self.assertTrue(duration<0.1,"async batch with delay should return almost immediately") results=asyncresult.value self.assertEqual(42,next(results)) self.assertEqual("slept 1 seconds",next(results)) self.assertEqual(12,next(results)) self.assertRaises(StopIteration, next, results) # no more results should be available def testBatchAsyncCallchain(self): class FuncHolder(object): count=AtomicCounter() def function(self, values): result=[value+1 for value in values] self.count.incr() return result with Pyro4.core.Proxy(self.objectUri) as p: batch=Pyro4.batch(p) self.assertEqual(None,batch.multiply(7,6)) self.assertEqual(None,batch.multiply(3,4)) result=batch(async=True) holder=FuncHolder() result.then(holder.function).then(holder.function) value=result.value self.assertTrue(result.ready) self.assertEqual([44,14],value) self.assertEqual(2,holder.count.value()) def testPyroTracebackNormal(self): with Pyro4.core.Proxy(self.objectUri) as p: try: p.divide(999,0) # force error here self.fail("expected error") except ZeroDivisionError: # going to check if the magic pyro traceback attribute is available for batch methods too tb="".join(Pyro4.util.getPyroTraceback()) self.assertTrue("Remote traceback:" in tb) # validate if remote tb is present self.assertTrue("ZeroDivisionError" in tb) # the error self.assertTrue("return x//y" in tb) # the statement def testPyroTracebackBatch(self): with Pyro4.core.Proxy(self.objectUri) as p: batch=Pyro4.batch(p) self.assertEqual(None,batch.divide(999,0)) # force an exception here results=batch() try: next(results) self.fail("expected error") except ZeroDivisionError: # going to check if the magic pyro traceback attribute is available for batch methods too tb="".join(Pyro4.util.getPyroTraceback()) self.assertTrue("Remote traceback:" in tb) # validate if remote tb is present self.assertTrue("ZeroDivisionError" in tb) # the error self.assertTrue("return x//y" in tb) # the statement self.assertRaises(StopIteration, next, results) # no more results should be available after the error def testAutoProxy(self): obj=MyThing2() Pyro4.config.SERIALIZER="pickle" try: with Pyro4.core.Proxy(self.objectUri) as p: Pyro4.config.AUTOPROXY=False # make sure autoproxy is disabled result=p.echo(obj) self.assertTrue(isinstance(result,MyThing2)) self.daemon.register(obj) result=p.echo(obj) self.assertTrue(isinstance(result,MyThing2), "with autoproxy off the object should be an instance of the class") self.daemon.unregister(obj) result=p.echo(obj) self.assertTrue(isinstance(result,MyThing2), "serialized object must still be normal object") Pyro4.config.AUTOPROXY=True # make sure autoproxying is enabled result=p.echo(obj) self.assertTrue(isinstance(result,MyThing2), "non-pyro object must be returned as normal class") self.daemon.register(obj) result=p.echo(obj) self.assertTrue(isinstance(result, Pyro4.core.Proxy),"serialized pyro object must be a proxy") self.daemon.unregister(obj) result=p.echo(obj) self.assertTrue(isinstance(result,MyThing2), "unregistered pyro object must be normal class again") # note: the custom serializer may still be active but it should be smart enough to see # that the object is no longer a pyro object, and therefore, no proxy should be created. finally: Pyro4.config.AUTOPROXY=True Pyro4.config.SERIALIZER="serpent" def testConnectOnce(self): with Pyro4.core.Proxy(self.objectUri) as proxy: self.assertTrue(proxy._pyroBind(), "first bind should always connect") self.assertFalse(proxy._pyroBind(), "second bind should not connect again") def testConnectingThreads(self): class ConnectingThread(threadutil.Thread): new_connections=AtomicCounter() def __init__(self, proxy, event): threadutil.Thread.__init__(self) self.proxy=proxy self.event=event self.setDaemon(True) self.new_connections.reset() def run(self): self.event.wait() if self.proxy._pyroBind(): ConnectingThread.new_connections.incr() # 1 more new connection done with Pyro4.core.Proxy(self.objectUri) as proxy: event = threadutil.Event() threads = [ConnectingThread(proxy, event) for _ in range(20)] for t in threads: t.start() event.set() for t in threads: t.join() self.assertEqual(1, ConnectingThread.new_connections.value()) # proxy shared among threads must still have only 1 connect done def testMaxMsgSize(self): with Pyro4.core.Proxy(self.objectUri) as p: bigobject = [42] * 1000 result = p.echo(bigobject) self.assertEqual(result, bigobject) Pyro4.config.MAX_MESSAGE_SIZE = 999 try: result = p.echo(bigobject) self.fail("should fail with ProtocolError msg too large") except Pyro4.errors.ProtocolError: pass Pyro4.config.MAX_MESSAGE_SIZE = 0 def testCleanup(self): p1 = Pyro4.core.Proxy(self.objectUri) p2 = Pyro4.core.Proxy(self.objectUri) p3 = Pyro4.core.Proxy(self.objectUri) p1.echo(42) p2.echo(42) p3.echo(42) # we have several active connections still up, see if we can cleanly shutdown the daemon # (it should interrupt the worker's socket connections) time.sleep(0.1) self.daemon.shutdown() self.daemon=None p1._pyroRelease() p2._pyroRelease() p3._pyroRelease() class ServerTestsThreadNoTimeout(unittest.TestCase): SERVERTYPE="thread" COMMTIMEOUT=None def setUp(self): Pyro4.config.POLLTIMEOUT=0.1 Pyro4.config.SERVERTYPE=self.SERVERTYPE Pyro4.config.COMMTIMEOUT=self.COMMTIMEOUT Pyro4.config.THREADPOOL_MINTHREADS=10 Pyro4.config.THREADPOOL_MAXTHREADS=20 Pyro4.config.HMAC_KEY=b"testsuite" Pyro4.config.SERIALIZERS_ACCEPTED.add("pickle") self.daemon=Pyro4.core.Daemon(port=0) obj=MyThing() uri=self.daemon.register(obj, "something") self.objectUri=uri self.daemonthread=DaemonLoopThread(self.daemon) self.daemonthread.start() self.daemonthread.running.wait() time.sleep(0.05) def tearDown(self): time.sleep(0.05) self.daemon.shutdown() self.daemonthread.join() Pyro4.config.SERVERTYPE="thread" Pyro4.config.COMMTIMEOUT=None Pyro4.config.HMAC_KEY=None Pyro4.config.SERIALIZERS_ACCEPTED.discard("pickle") def testConnectionStuff(self): p1=Pyro4.core.Proxy(self.objectUri) p2=Pyro4.core.Proxy(self.objectUri) self.assertTrue(p1._pyroConnection is None) self.assertTrue(p2._pyroConnection is None) p1.ping() p2.ping() _=p1.multiply(11,5) _=p2.multiply(11,5) self.assertTrue(p1._pyroConnection is not None) self.assertTrue(p2._pyroConnection is not None) p1._pyroRelease() p1._pyroRelease() p2._pyroRelease() p2._pyroRelease() self.assertTrue(p1._pyroConnection is None) self.assertTrue(p2._pyroConnection is None) p1._pyroBind() _=p1.multiply(11,5) _=p2.multiply(11,5) self.assertTrue(p1._pyroConnection is not None) self.assertTrue(p2._pyroConnection is not None) self.assertEqual("PYRO",p1._pyroUri.protocol) self.assertEqual("PYRO",p2._pyroUri.protocol) p1._pyroRelease() p2._pyroRelease() def testReconnectAndCompression(self): # try reconnects with Pyro4.core.Proxy(self.objectUri) as p: self.assertTrue(p._pyroConnection is None) p._pyroReconnect(tries=100) self.assertTrue(p._pyroConnection is not None) self.assertTrue(p._pyroConnection is None) # test compression: try: with Pyro4.core.Proxy(self.objectUri) as p: Pyro4.config.COMPRESSION=True self.assertEqual(55, p.multiply(5,11)) self.assertEqual("*"*1000, p.multiply("*"*500,2)) finally: Pyro4.config.COMPRESSION=False def testOneway(self): with Pyro4.core.Proxy(self.objectUri) as p: self.assertEqual(55, p.multiply(5,11)) p._pyroOneway.add("multiply") self.assertEqual(None, p.multiply(5,11)) self.assertEqual(None, p.multiply(5,11)) self.assertEqual(None, p.multiply(5,11)) p._pyroOneway.remove("multiply") self.assertEqual(55, p.multiply(5,11)) self.assertEqual(55, p.multiply(5,11)) self.assertEqual(55, p.multiply(5,11)) # check nonexisting method behavoir self.assertRaises(AttributeError, p.nonexisting) p._pyroOneway.add("nonexisting") # now it shouldn't fail because of oneway semantics p.nonexisting() # also test on class: class ProxyWithOneway(Pyro4.core.Proxy): def __init__(self, arg): super(ProxyWithOneway,self).__init__(arg) self._pyroOneway=["multiply"] # set is faster but don't care for this test with ProxyWithOneway(self.objectUri) as p: self.assertEqual(None, p.multiply(5,11)) p._pyroOneway=[] # empty set is better but don't care in this test self.assertEqual(55, p.multiply(5,11)) def testOnewayDelayed(self): try: with Pyro4.core.Proxy(self.objectUri) as p: p.ping() Pyro4.config.ONEWAY_THREADED=True # the default p._pyroOneway.add("delay") now=time.time() p.delay(1) # oneway so we should continue right away self.assertTrue(time.time()-now < 0.2, "delay should be running as oneway") now=time.time() self.assertEqual(55,p.multiply(5,11), "expected a normal result from a non-oneway call") self.assertTrue(time.time()-now < 0.2, "delay should be running in its own thread") # make oneway calls run in the server thread # we can change the config here and the server will pick it up on the fly Pyro4.config.ONEWAY_THREADED=False now=time.time() p.delay(1) # oneway so we should continue right away self.assertTrue(time.time()-now < 0.2, "delay should be running as oneway") now=time.time() self.assertEqual(55,p.multiply(5,11), "expected a normal result from a non-oneway call") self.assertFalse(time.time()-now < 0.2, "delay should be running in the server thread") finally: Pyro4.config.ONEWAY_THREADED=True # back to normal def testSerializeConnected(self): # online serialization tests ser=Pyro4.util.get_serializer(Pyro4.config.SERIALIZER) proxy=Pyro4.core.Proxy(self.objectUri) proxy._pyroBind() self.assertFalse(proxy._pyroConnection is None) p,_=ser.serializeData(proxy) proxy2=ser.deserializeData(p) self.assertTrue(proxy2._pyroConnection is None) self.assertFalse(proxy._pyroConnection is None) self.assertEqual(proxy2._pyroUri, proxy._pyroUri) proxy2._pyroBind() self.assertFalse(proxy2._pyroConnection is None) self.assertFalse(proxy2._pyroConnection is proxy._pyroConnection) proxy._pyroRelease() proxy2._pyroRelease() self.assertTrue(proxy._pyroConnection is None) self.assertTrue(proxy2._pyroConnection is None) proxy.ping() proxy2.ping() # try copying a connected proxy import copy proxy3=copy.copy(proxy) self.assertTrue(proxy3._pyroConnection is None) self.assertFalse(proxy._pyroConnection is None) self.assertEqual(proxy3._pyroUri, proxy._pyroUri) self.assertFalse(proxy3._pyroUri is proxy._pyroUri) proxy._pyroRelease() proxy2._pyroRelease() proxy3._pyroRelease() def testException(self): with Pyro4.core.Proxy(self.objectUri) as p: try: p.divide(1,0) except: et,ev,tb=sys.exc_info() self.assertEqual(ZeroDivisionError, et) pyrotb="".join(Pyro4.util.getPyroTraceback(et,ev,tb)) self.assertTrue("Remote traceback" in pyrotb) self.assertTrue("ZeroDivisionError" in pyrotb) del tb def testTimeoutCall(self): Pyro4.config.COMMTIMEOUT=None with Pyro4.core.Proxy(self.objectUri) as p: p.ping() start=time.time() p.delay(0.5) duration=time.time()-start self.assertTrue(0.44) myip=SU.getIpAddress("", workaround127=True) self.assertTrue(len(myip)>4) self.assertFalse(myip.startswith("127.")) self.assertEqual("127.0.0.1", SU.getIpAddress("127.0.0.1", workaround127=False)) self.assertNotEqual("127.0.0.1", SU.getIpAddress("127.0.0.1", workaround127=True)) @unittest.skipUnless(has_ipv6, "ipv6 testcase") def testGetIP6(self): self.assertTrue(":" in SU.getIpAddress("::1", ipVersion=6)) # self.assertTrue(":" in SU.getIpAddress("", ipVersion=6)) self.assertTrue(":" in SU.getIpAddress("localhost", ipVersion=6)) def testGetIpVersion4(self): version = Pyro4.config.PREFER_IP_VERSION try: Pyro4.config.PREFER_IP_VERSION=4 self.assertEqual(4, SU.getIpVersion("127.0.0.1")) self.assertEqual(4, SU.getIpVersion("localhost")) Pyro4.config.PREFER_IP_VERSION=0 self.assertEqual(4, SU.getIpVersion("127.0.0.1")) finally: Pyro4.config.PREFER_IP_VERSION = version @unittest.skipUnless(has_ipv6, "ipv6 testcase") def testGetIpVersion6(self): version = Pyro4.config.PREFER_IP_VERSION try: Pyro4.config.PREFER_IP_VERSION=6 self.assertEqual(6, SU.getIpVersion("127.0.0.1")) self.assertEqual(6, SU.getIpVersion("::1")) self.assertEqual(6, SU.getIpVersion("localhost")) Pyro4.config.PREFER_IP_VERSION=4 self.assertEqual(4, SU.getIpVersion("127.0.0.1")) self.assertEqual(6, SU.getIpVersion("::1")) Pyro4.config.PREFER_IP_VERSION=0 self.assertEqual(4, SU.getIpVersion("127.0.0.1")) self.assertEqual(6, SU.getIpVersion("::1")) finally: Pyro4.config.PREFER_IP_VERSION = version def testGetInterfaceAddress(self): self.assertTrue(SU.getInterfaceAddress("localhost").startswith("127.")) if has_ipv6: self.assertTrue(":" in SU.getInterfaceAddress("::1")) def testUnusedPort(self): port1=SU.findProbablyUnusedPort() port2=SU.findProbablyUnusedPort() self.assertTrue(port1>0) self.assertNotEqual(port1,port2) port1=SU.findProbablyUnusedPort(socktype=socket.SOCK_DGRAM) port2=SU.findProbablyUnusedPort(socktype=socket.SOCK_DGRAM) self.assertTrue(port1>0) self.assertNotEqual(port1,port2) @unittest.skipUnless(has_ipv6, "ipv6 testcase") def testUnusedPort6(self): port1=SU.findProbablyUnusedPort(family=socket.AF_INET6) port2=SU.findProbablyUnusedPort(family=socket.AF_INET6) self.assertTrue(port1>0) self.assertNotEqual(port1,port2) port1=SU.findProbablyUnusedPort(family=socket.AF_INET6, socktype=socket.SOCK_DGRAM) port2=SU.findProbablyUnusedPort(family=socket.AF_INET6, socktype=socket.SOCK_DGRAM) self.assertTrue(port1>0) self.assertNotEqual(port1,port2) def testBindUnusedPort(self): sock1=socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock2=socket.socket(socket.AF_INET, socket.SOCK_STREAM) port1=SU.bindOnUnusedPort(sock1) port2=SU.bindOnUnusedPort(sock2) self.assertTrue(port1>0) self.assertNotEqual(port1,port2) sockname=sock1.getsockname() self.assertEqual(("127.0.0.1",port1), sockname) sock1.close() sock2.close() @unittest.skipUnless(has_ipv6, "ipv6 testcase") def testBindUnusedPort6(self): sock1=socket.socket(socket.AF_INET6, socket.SOCK_STREAM) sock2=socket.socket(socket.AF_INET6, socket.SOCK_STREAM) port1=SU.bindOnUnusedPort(sock1) port2=SU.bindOnUnusedPort(sock2) self.assertTrue(port1>0) self.assertNotEqual(port1,port2) host,port,_,_=sock1.getsockname() self.assertTrue(":" in host) self.assertEqual(port1, port) sock1.close() sock2.close() def testCreateUnboundSockets(self): s=SU.createSocket() self.assertEqual(socket.AF_INET, s.family) bs=SU.createBroadcastSocket() self.assertEqual(socket.AF_INET, bs.family) try: host,port=s.getsockname() # can either fail with socket.error or return (host,0) self.assertEqual(0,port) except socket.error: pass try: host,port=bs.getsockname() # can either fail with socket.error or return (host,0) self.assertEqual(0,port) except socket.error: pass s.close() bs.close() @unittest.skipUnless(has_ipv6, "ipv6 testcase") def testCreateUnboundSockets6(self): s=SU.createSocket(ipv6=True) self.assertEqual(socket.AF_INET6, s.family) bs=SU.createBroadcastSocket(ipv6=True) self.assertEqual(socket.AF_INET6, bs.family) try: host,port,_,_=s.getsockname() # can either fail with socket.error or return (host,0) self.assertEqual(0,port) except socket.error: pass try: host,port,_,_=bs.getsockname() # can either fail with socket.error or return (host,0) self.assertEqual(0,port) except socket.error: pass s.close() bs.close() def testCreateBoundSockets(self): s=SU.createSocket(bind=('127.0.0.1',0)) self.assertEqual(socket.AF_INET, s.family) bs=SU.createBroadcastSocket(bind=('127.0.0.1',0)) self.assertEqual('127.0.0.1',s.getsockname()[0]) self.assertEqual('127.0.0.1',bs.getsockname()[0]) s.close() bs.close() self.assertRaises(ValueError, SU.createSocket, bind=('localhost',12345), connect=('localhost',1234)) @unittest.skipUnless(has_ipv6, "ipv6 testcase") def testCreateBoundSockets6(self): s=SU.createSocket(bind=('::1',0)) self.assertEqual(socket.AF_INET6, s.family) bs=SU.createBroadcastSocket(bind=('::1',0)) self.assertTrue(':' in s.getsockname()[0]) self.assertTrue(':' in bs.getsockname()[0]) s.close() bs.close() self.assertRaises(ValueError, SU.createSocket, bind=('::1',12345), connect=('::1',1234)) @unittest.skipUnless(hasattr(socket, "AF_UNIX"), "unix domain sockets required") def testCreateBoundUnixSockets(self): SOCKNAME="test_unixsocket" if os.path.exists(SOCKNAME): os.remove(SOCKNAME) s=SU.createSocket(bind=SOCKNAME) self.assertEqual(socket.AF_UNIX, s.family) self.assertEqual(SOCKNAME,s.getsockname()) s.close() if os.path.exists(SOCKNAME): os.remove(SOCKNAME) # unicode arg SOCKNAME = unicode(SOCKNAME) s=SU.createSocket(bind=SOCKNAME) self.assertEqual(socket.AF_UNIX, s.family) self.assertEqual(SOCKNAME,s.getsockname()) s.close() if os.path.exists(SOCKNAME): os.remove(SOCKNAME) self.assertRaises(ValueError, SU.createSocket, bind=SOCKNAME, connect=SOCKNAME) def testSend(self): ss=SU.createSocket(bind=("localhost",0)) port=ss.getsockname()[1] cs=SU.createSocket(connect=("localhost",port)) SU.sendData(cs,tobytes("foobar!")*10) cs.shutdown(socket.SHUT_WR) a=ss.accept() data=SU.receiveData(a[0], 5) self.assertEqual(tobytes("fooba"),data) data=SU.receiveData(a[0], 5) self.assertEqual(tobytes("r!foo"),data) a[0].close() ss.close() cs.close() @unittest.skipUnless(hasattr(socket, "AF_UNIX"), "unix domain sockets required") def testSendUnix(self): SOCKNAME="test_unixsocket" ss=SU.createSocket(bind=SOCKNAME) cs=SU.createSocket(connect=SOCKNAME) SU.sendData(cs,tobytes("foobar!")*10) cs.shutdown(socket.SHUT_WR) a=ss.accept() data=SU.receiveData(a[0], 5) self.assertEqual(tobytes("fooba"),data) data=SU.receiveData(a[0], 5) self.assertEqual(tobytes("r!foo"),data) a[0].close() ss.close() cs.close() if os.path.exists(SOCKNAME): os.remove(SOCKNAME) def testBroadcast(self): ss=SU.createBroadcastSocket((None, 0)) port=ss.getsockname()[1] cs=SU.createBroadcastSocket() for bcaddr in Pyro4.config.parseAddressesString(Pyro4.config.BROADCAST_ADDRS): try: cs.sendto(tobytes("monkey"),0,(bcaddr,port)) except socket.error: x=sys.exc_info()[1] err=getattr(x, "errno", x.args[0]) if err not in Pyro4.socketutil.ERRNO_EADDRNOTAVAIL: # yeah, windows likes to throw these... if err not in Pyro4.socketutil.ERRNO_EADDRINUSE: # and jython likes to throw thses... raise data,_=ss.recvfrom(500) self.assertEqual(tobytes("monkey"),data) cs.close() ss.close() def testMsgWaitallProblems(self): ss=SU.createSocket(bind=("localhost",0), timeout=2) port=ss.getsockname()[1] cs=SU.createSocket(connect=("localhost",port), timeout=2) a=ss.accept() # test some sizes that might be problematic with MSG_WAITALL for size in [1000,10000,32000,32768,32780,41950,41952,42000,65000,65535,65600,80000]: SU.sendData(cs,tobytes("x")*size) data=SU.receiveData(a[0],size) SU.sendData(a[0], data) data=SU.receiveData(cs,size) self.assertEqual(size, len(data)) a[0].close() ss.close() cs.close() def testMsgWaitallProblems2(self): class ReceiveThread(threadutil.Thread): def __init__(self, sock, sizes): super(ReceiveThread,self).__init__() self.sock=sock self.sizes=sizes def run(self): cs,_ = self.sock.accept() for size in self.sizes: data=SU.receiveData(cs,size) SU.sendData(cs, data) cs.close() ss=SU.createSocket(bind=("localhost",0)) SIZES=[1000,10000,32000,32768,32780,41950,41952,42000,65000,65535,65600,80000,999999] serverthread=ReceiveThread(ss, SIZES) serverthread.setDaemon(True) serverthread.start() port=ss.getsockname()[1] cs=SU.createSocket(connect=("localhost",port), timeout=2) # test some sizes that might be problematic with MSG_WAITALL for size in SIZES: SU.sendData(cs,tobytes("x")*size) data=SU.receiveData(cs,size) self.assertEqual(size, len(data)) serverthread.join() ss.close() cs.close() class ServerCallback(object): def _handshake(self, connection): if not isinstance(connection, SU.SocketConnection): raise TypeError("handshake expected SocketConnection parameter") serializer = Pyro4.util.get_serializer("marshal") data, _ = serializer.serializeData("ok", compress=False) msg = Pyro4.message.Message(Pyro4.message.MSG_CONNECTOK, data, serializer.serializer_id, 0, 1) connection.send(msg.to_bytes()) return True def handleRequest(self, connection): if not isinstance(connection, SU.SocketConnection): raise TypeError("handleRequest expected SocketConnection parameter") msg = Pyro4.message.Message.recv(connection, [Pyro4.message.MSG_PING]) if msg.type == Pyro4.message.MSG_PING: msg = Pyro4.message.Message(Pyro4.message.MSG_PING, b"", msg.serializer_id, 0, msg.seq) connection.send(msg.to_bytes()) else: print("unhandled message type", msg.type) connection.close() class ServerCallback_BrokenHandshake(ServerCallback): def _handshake(self, connection): raise ZeroDivisionError("handshake crashed (on purpose)") class TestDaemon(Daemon): def __init__(self): pass # avoid all regular daemon initialization class TestSocketServer(unittest.TestCase): def testServer_thread(self): daemon=ServerCallback() port=SU.findProbablyUnusedPort() serv=SocketServer_Threadpool() serv.init(daemon,"localhost",port) self.assertEqual("localhost:"+str(port), serv.locationStr) self.assertTrue(serv.sock is not None) conn=SU.SocketConnection(serv.sock, "ID12345") self.assertEqual("ID12345",conn.objectId) self.assertTrue(conn.sock is not None) conn.close() conn.close() self.assertFalse(conn.sock is None, "connections keep their socket object even if it's closed") serv.close() serv.close() self.assertTrue(serv.sock is None) def testServer_select(self): daemon=ServerCallback() port=SU.findProbablyUnusedPort() serv=SocketServer_Select() serv.init(daemon,"localhost",port) self.assertEqual("localhost:"+str(port), serv.locationStr) self.assertTrue(serv.sock is not None) conn=SU.SocketConnection(serv.sock, "ID12345") self.assertEqual("ID12345",conn.objectId) self.assertTrue(conn.sock is not None) conn.close() conn.close() self.assertFalse(conn.sock is None, "connections keep their socket object even if it's closed") serv.close() serv.close() self.assertTrue(serv.sock is None) def testServer_poll(self): daemon=ServerCallback() port=SU.findProbablyUnusedPort() serv=SocketServer_Poll() serv.init(daemon,"localhost",port) self.assertEqual("localhost:"+str(port), serv.locationStr) self.assertTrue(serv.sock is not None) conn=SU.SocketConnection(serv.sock, "ID12345") self.assertEqual("ID12345",conn.objectId) self.assertTrue(conn.sock is not None) conn.close() conn.close() self.assertFalse(conn.sock is None, "connections keep their socket object even if it's closed") serv.close() serv.close() self.assertTrue(serv.sock is None) @unittest.skipUnless(SU.hasSelect, "requires select()") @unittest.skipUnless(os.name!="java", "select-server not yet supported in jython") class TestServerDOS_select(unittest.TestCase): def setUp(self): self.orig_poll_timeout = Pyro4.config.POLLTIMEOUT self.orig_comm_timeout = Pyro4.config.COMMTIMEOUT Pyro4.config.POLLTIMEOUT = 0.5 Pyro4.config.COMMTIMEOUT = 0.5 self.socket_server = SocketServer_Select def tearDown(self): Pyro4.config.POLLTIMEOUT = self.orig_poll_timeout Pyro4.config.COMMTIMEOUT = self.orig_comm_timeout class ServerThread(threadutil.Thread): def __init__(self, server, daemon): threadutil.Thread.__init__(self) self.serv = server() self.serv.init(daemon(), "localhost", 0) self.locationStr = self.serv.locationStr self.stop_loop = threadutil.Event() def run(self): self.serv.loop(loopCondition=lambda: not self.stop_loop.is_set()) self.serv.close() def testConnectCrash(self): serv_thread = TestServerDOS_select.ServerThread(self.socket_server, ServerCallback_BrokenHandshake) serv_thread.start() time.sleep(0.2) self.assertTrue(serv_thread.is_alive(), "server thread failed to start") try: host, port = serv_thread.locationStr.split(':') port = int(port) try: # first connection attempt (will fail because server daemon _handshake crashes) csock = SU.createSocket(connect=(host, port)) conn = SU.SocketConnection(csock, "uri") Pyro4.message.Message.recv(conn, [Pyro4.message.MSG_CONNECTOK]) except errors.ConnectionClosedError: pass conn.close() try: # second connection attempt, should still work (i.e. server should still be running) csock = SU.createSocket(connect=(host, port)) conn = SU.SocketConnection(csock, "uri") Pyro4.message.Message.recv(conn, [Pyro4.message.MSG_CONNECTOK]) except errors.ConnectionClosedError: pass finally: conn.close() serv_thread.stop_loop.set() serv_thread.join() def testInvalidMessageCrash(self): serv_thread = TestServerDOS_select.ServerThread(self.socket_server, TestDaemon) serv_thread.start() time.sleep(0.2) self.assertTrue(serv_thread.is_alive(), "server thread failed to start") def connect(host, port): # connect to the server csock = SU.createSocket(connect=(host, port)) conn = SU.SocketConnection(csock, "uri") # get the handshake/connect response Pyro4.message.Message.recv(conn, [Pyro4.message.MSG_CONNECTOK]) return conn try: host, port = serv_thread.locationStr.split(':') port = int(port) conn = connect(host, port) # invoke something, but screw up the message (in this case, mess with the protocol version) orig_protocol_version = Pyro4.constants.PROTOCOL_VERSION Pyro4.constants.PROTOCOL_VERSION = 9999 msgbytes = Pyro4.message.Message(Pyro4.message.MSG_PING, b"something", 42, 0, 0).to_bytes() Pyro4.constants.PROTOCOL_VERSION = orig_protocol_version conn.send(msgbytes) # this should cause an error in the server because of invalid msg try: msg = Pyro4.message.Message.recv(conn, [Pyro4.message.MSG_RESULT]) data = msg.data if sys.version_info >= (2, 7): data = msg.data.decode("ascii", errors="ignore") # convert raw message to string to check some stuff self.assertTrue("Traceback" in data) self.assertTrue("ProtocolError" in data) self.assertTrue("version" in data) except errors.ConnectionClosedError: # invalid message can have caused the connection to be closed, this is fine pass # invoke something again, this should still work (server must still be running) conn.close() conn = connect(host, port) msg = Pyro4.message.Message(Pyro4.message.MSG_PING, b"something", 42, 0, 999) conn.send(msg.to_bytes()) msg = Pyro4.message.Message.recv(conn, [Pyro4.message.MSG_PING]) self.assertEqual(Pyro4.message.MSG_PING, msg.type) self.assertEqual(999, msg.seq) self.assertEqual(b"pong", msg.data) finally: conn.close() serv_thread.stop_loop.set() serv_thread.join() @unittest.skipUnless(SU.hasPoll, "requires poll()") @unittest.skipUnless(os.name!="java", "poll-server not yet supported in jython") class TestServerDOS_poll(TestServerDOS_select): def setUp(self): super(TestServerDOS_poll, self).setUp() self.socket_server = SocketServer_Poll class TestServerDOS_threading(TestServerDOS_select): def setUp(self): super(TestServerDOS_threading, self).setUp() self.socket_server = SocketServer_Threadpool self.orig_maxthreads = Pyro4.config.THREADPOOL_MAXTHREADS Pyro4.config.THREADPOOL_MAXTHREADS = 1 def tearDown(self): Pyro4.config.THREADPOOL_MAXTHREADS = self.orig_maxthreads if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] unittest.main() Pyro4-4.23/tests/PyroTests/test_tpjobqueue.py000066400000000000000000000113671227003673200214030ustar00rootroot00000000000000""" Tests for the thread pooled job queue. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ from __future__ import with_statement, print_function import time import random from Pyro4.tpjobqueue import ThreadPooledJobQueue, JobQueueError import Pyro4.threadutil from testsupport import unittest MIN_POOL_SIZE = 5 MAX_POOL_SIZE = 10 IDLE_TIMEOUT = 0.5 JOB_TIME = 0.2 class Job(object): def __init__(self, name="unnamed"): self.name=name def __call__(self): # print("Job() '%s'" % self.name) time.sleep(JOB_TIME - random.random()/10.0) # print("Job() '%s' done" % self.name) class TPJobQueueTests(unittest.TestCase): def setUp(self): Pyro4.config.THREADPOOL_MINTHREADS = MIN_POOL_SIZE Pyro4.config.THREADPOOL_MAXTHREADS = MAX_POOL_SIZE Pyro4.config.THREADPOOL_IDLETIMEOUT = IDLE_TIMEOUT def tearDown(self): Pyro4.config.reset() def testJQcreate(self): with ThreadPooledJobQueue() as jq: _=repr(jq) self.assertEqual(MIN_POOL_SIZE, jq.workercountSafe) jq.drain() def testJQsingle(self): with ThreadPooledJobQueue() as jq: job = Job() jq.process(job) self.assertEqual(MIN_POOL_SIZE, jq.workercountSafe) time.sleep(0.02) # let it pick up the job self.assertEqual(1, len(jq.busy)) worker = list(jq.busy)[0] self.assertEqual(job, worker.job, "busy worker should be running our job") jq.drain() def testJQgrow(self): with ThreadPooledJobQueue() as jq: for i in range(MIN_POOL_SIZE): jq.process(Job(str(i))) self.assertTrue(jq.workercountSafe >= MIN_POOL_SIZE) self.assertTrue(jq.workercountSafe <= MAX_POOL_SIZE) jq.process(Job(str(i+1))) self.assertTrue(jq.workercountSafe >= MIN_POOL_SIZE) self.assertTrue(jq.workercountSafe <= MAX_POOL_SIZE) jq.drain() def testJQmanyjobs(self): class Job2(object): def __init__(self, name="unnamed"): self.name=name def __call__(self): time.sleep(0.01) with ThreadPooledJobQueue() as jq: for i in range(1+MAX_POOL_SIZE*100): jq.process(Job2(str(i))) time.sleep(2) self.assertEqual(0, jq.jobcount, "queue must be finished in under two seconds") jq.drain() def testJQshrink(self): with ThreadPooledJobQueue() as jq: self.assertEqual(MIN_POOL_SIZE, jq.workercountSafe) jq.process(Job("i1")) jq.process(Job("i2")) jq.process(Job("i3")) jq.process(Job("i4")) jq.process(Job("i5")) self.assertTrue(jq.workercountSafe >= MIN_POOL_SIZE) self.assertTrue(jq.workercountSafe <= MAX_POOL_SIZE) time.sleep(JOB_TIME + 1.1*IDLE_TIMEOUT) # wait till the workers are done jq.process(Job("i6")) self.assertEqual(MIN_POOL_SIZE, jq.workercountSafe) # one of the now idle workers should have picked this up time.sleep(JOB_TIME + 1.1*IDLE_TIMEOUT) # wait till the workers are done for i in range(2*MAX_POOL_SIZE): jq.process(Job(str(i+1))) self.assertEqual(MAX_POOL_SIZE, jq.workercountSafe) time.sleep(JOB_TIME*2 + 1.1*IDLE_TIMEOUT) # wait till the workers are done self.assertEqual(MIN_POOL_SIZE, jq.workercountSafe) # should have shrunk back to the minimal pool size jq.drain() def testJQclose(self): # test that after closing a job queue, no more new jobs are taken from the queue, and some other stuff with ThreadPooledJobQueue() as jq: for i in range(2*MAX_POOL_SIZE): jq.process(Job(str(i+1))) self.assertTrue(jq.jobcount > 1) self.assertTrue(jq.workercount > 1) self.assertRaises(JobQueueError, jq.drain) # can't drain if not yet closed self.assertRaises(JobQueueError, jq.process, Job(1)) # must not allow new jobs after closing self.assertTrue(jq.jobcount > 1) self.assertTrue(jq.workercount > 1) time.sleep(JOB_TIME*1.1) jobs_left = jq.jobcount time.sleep(JOB_TIME*1.1) # wait till jobs finish and a new one *might* be taken off the queue self.assertEqual(jobs_left, jq.jobcount, "may not process new jobs after close") self.assertEqual(0, jq.workercount, "all workers must be stopped by now") jq.drain() if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] unittest.main() Pyro4-4.23/tests/PyroTests/test_util.py000066400000000000000000000226501227003673200201720ustar00rootroot00000000000000""" Tests for the utility functions. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ import sys, imp, os, platform import Pyro4.util from testsupport import * if not hasattr(imp,"reload"): imp.reload=reload # python 2.5 doesn't have imp.reload def crash(arg=100): pre1="black" pre2=999 def nest(p1,p2): s="white"+pre1 x=pre2 y=arg//2 p3=p1//p2 return p3 a=10 b=0 s="hello" c=nest(a,b) return c class TestUtils(unittest.TestCase): def testFormatTracebackNormal(self): try: crash() self.fail("must crash with ZeroDivisionError") except ZeroDivisionError: tb="".join(Pyro4.util.formatTraceback(detailed=False)) self.assertTrue("p3=p1//p2" in tb) self.assertTrue("ZeroDivisionError" in tb) self.assertFalse(" a = 10" in tb) self.assertFalse(" s = 'whiteblack'" in tb) self.assertFalse(" pre2 = 999" in tb) self.assertFalse(" x = 999" in tb) def testFormatTracebackDetail(self): try: crash() self.fail("must crash with ZeroDivisionError") except ZeroDivisionError: tb="".join(Pyro4.util.formatTraceback(detailed=True)) self.assertTrue("p3=p1//p2" in tb) self.assertTrue("ZeroDivisionError" in tb) if sys.platform!="cli": self.assertTrue(" a = 10" in tb) self.assertTrue(" s = 'whiteblack'" in tb) self.assertTrue(" pre2 = 999" in tb) self.assertTrue(" x = 999" in tb) def testPyroTraceback(self): try: crash() self.fail("must crash with ZeroDivisionError") except ZeroDivisionError: pyro_tb=Pyro4.util.formatTraceback(detailed=True) if sys.platform!="cli": self.assertTrue(" Extended stacktrace follows (most recent call last)\n" in pyro_tb) try: crash("stringvalue") self.fail("must crash with TypeError") except TypeError: x=sys.exc_info()[1] x._pyroTraceback=pyro_tb # set the remote traceback info pyrotb="".join(Pyro4.util.getPyroTraceback()) self.assertTrue("Remote traceback" in pyrotb) self.assertTrue("crash(\"stringvalue\")" in pyrotb) self.assertTrue("TypeError:" in pyrotb) self.assertTrue("ZeroDivisionError" in pyrotb) del x._pyroTraceback pyrotb="".join(Pyro4.util.getPyroTraceback()) self.assertFalse("Remote traceback" in pyrotb) self.assertFalse("ZeroDivisionError" in pyrotb) self.assertTrue("crash(\"stringvalue\")" in pyrotb) self.assertTrue("TypeError:" in pyrotb) def testPyroTracebackArgs(self): try: crash() self.fail("must crash with ZeroDivisionError") except ZeroDivisionError: ex_type, ex_value, ex_tb = sys.exc_info() x=ex_value tb1=Pyro4.util.getPyroTraceback() tb2=Pyro4.util.getPyroTraceback(ex_type, ex_value, ex_tb) self.assertEqual(tb1, tb2) tb1=Pyro4.util.formatTraceback() tb2=Pyro4.util.formatTraceback(ex_type, ex_value, ex_tb) self.assertEqual(tb1, tb2) tb2=Pyro4.util.formatTraceback(detailed=True) if sys.platform!="cli": self.assertNotEqual(tb1, tb2) # old call syntax, should get an error now: self.assertRaises(TypeError, Pyro4.util.getPyroTraceback, x) self.assertRaises(TypeError, Pyro4.util.formatTraceback, x) def testExcepthook(self): # simply test the excepthook by calling it the way Python would try: crash() self.fail("must crash with ZeroDivisionError") except ZeroDivisionError: pyro_tb=Pyro4.util.formatTraceback() try: crash("stringvalue") self.fail("must crash with TypeError") except TypeError: ex_type,ex_value,ex_tb=sys.exc_info() ex_value._pyroTraceback=pyro_tb # set the remote traceback info oldstderr=sys.stderr try: sys.stderr=StringIO() Pyro4.util.excepthook(ex_type, ex_value, ex_tb) output=sys.stderr.getvalue() self.assertTrue("Remote traceback" in output) self.assertTrue("crash(\"stringvalue\")" in output) self.assertTrue("TypeError:" in output) self.assertTrue("ZeroDivisionError" in output) finally: sys.stderr=oldstderr def clearEnv(self): if "PYRO_HOST" in os.environ: del os.environ["PYRO_HOST"] if "PYRO_NS_PORT" in os.environ: del os.environ["PYRO_NS_PORT"] if "PYRO_COMPRESSION" in os.environ: del os.environ["PYRO_COMPRESSION"] Pyro4.config.reset() def testConfig(self): self.clearEnv() try: self.assertEqual(9090, Pyro4.config.NS_PORT) self.assertEqual("localhost", Pyro4.config.HOST) self.assertEqual(False, Pyro4.config.COMPRESSION) os.environ["NS_PORT"]="4444" Pyro4.config.reset() self.assertEqual(9090, Pyro4.config.NS_PORT) os.environ["PYRO_NS_PORT"]="4444" os.environ["PYRO_HOST"]="something.com" os.environ["PYRO_COMPRESSION"]="OFF" Pyro4.config.reset() self.assertEqual(4444, Pyro4.config.NS_PORT) self.assertEqual("something.com", Pyro4.config.HOST) self.assertEqual(False, Pyro4.config.COMPRESSION) finally: self.clearEnv() self.assertEqual(9090, Pyro4.config.NS_PORT) self.assertEqual("localhost", Pyro4.config.HOST) self.assertEqual(False, Pyro4.config.COMPRESSION) def testConfigReset(self): try: Pyro4.config.reset() self.assertEqual("localhost", Pyro4.config.HOST) Pyro4.config.HOST="foobar" self.assertEqual("foobar", Pyro4.config.HOST) Pyro4.config.reset() self.assertEqual("localhost", Pyro4.config.HOST) os.environ["PYRO_HOST"]="foobar" Pyro4.config.reset() self.assertEqual("foobar", Pyro4.config.HOST) del os.environ["PYRO_HOST"] Pyro4.config.reset() self.assertEqual("localhost", Pyro4.config.HOST) finally: self.clearEnv() def testResolveAttr(self): class Test(object): def __init__(self,value): self.value=value def __str__(self): return "<%s>" % self.value obj=Test("obj") obj.a=Test("a") obj.a.b=Test("b") obj.a.b.c=Test("c") obj.a._p=Test("p1") obj.a._p.q=Test("q1") obj.a.__p=Test("p2") obj.a.__p.q=Test("q2") #check the method with dotted disabled self.assertEqual("",str(Pyro4.util.resolveDottedAttribute(obj,"a",False))) self.assertRaises(AttributeError, Pyro4.util.resolveDottedAttribute, obj, "a.b",False) self.assertRaises(AttributeError, Pyro4.util.resolveDottedAttribute, obj, "a.b.c",False) self.assertRaises(AttributeError, Pyro4.util.resolveDottedAttribute, obj, "a.b.c.d",False) self.assertRaises(AttributeError, Pyro4.util.resolveDottedAttribute, obj, "a._p",False) self.assertRaises(AttributeError, Pyro4.util.resolveDottedAttribute, obj, "a._p.q",False) self.assertRaises(AttributeError, Pyro4.util.resolveDottedAttribute, obj, "a.__p.q",False) #now with dotted enabled self.assertEqual("",str(Pyro4.util.resolveDottedAttribute(obj,"a",True))) self.assertEqual("",str(Pyro4.util.resolveDottedAttribute(obj,"a.b",True))) self.assertEqual("",str(Pyro4.util.resolveDottedAttribute(obj,"a.b.c",True))) self.assertRaises(AttributeError,Pyro4.util.resolveDottedAttribute, obj,"a.b.c.d",True) # doesn't exist self.assertRaises(AttributeError,Pyro4.util.resolveDottedAttribute, obj,"a._p",True) #private self.assertRaises(AttributeError,Pyro4.util.resolveDottedAttribute, obj,"a._p.q",True) #private self.assertRaises(AttributeError,Pyro4.util.resolveDottedAttribute, obj,"a.__p.q",True) #private @unittest.skipUnless(sys.version_info>=(2,6,5), "unicode kwargs needs 2.6.5 or newer") def testUnicodeKwargs(self): # test the way the interpreter deals with unicode function kwargs # those are supported by Python after 2.6.5 def function(*args, **kwargs): return args, kwargs processed_args=function(*(1,2,3), **{ unichr(65): 42 }) self.assertEqual( ((1,2,3), { unichr(65): 42}), processed_args) processed_args=function(*(1,2,3), **{ unichr(0x20ac): 42 }) key = list(processed_args[1].keys())[0] self.assertTrue(type(key) is unicode) self.assertEqual(key, unichr(0x20ac)) self.assertEqual( ((1,2,3), { unichr(0x20ac): 42}), processed_args) if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] unittest.main() Pyro4-4.23/tests/PyroTests/testsupport.py000066400000000000000000000031541227003673200205700ustar00rootroot00000000000000""" Support code for the test suite. There's some Python 2.x <-> 3.x compatibility code here. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ from __future__ import with_statement import sys import threading import pickle __all__=["tobytes", "unicode", "unichr", "basestring", "StringIO", "next", "AtomicCounter", "NonserializableError", "MyThing2", "unittest" ] if sys.version_info<(3,0): from StringIO import StringIO def tobytes(string, encoding=None): return string unicode=unicode unichr=unichr basestring=basestring else: from io import StringIO def tobytes(string, encoding="iso-8859-1"): return bytes(string,encoding) unicode=str unichr=chr basestring=str if sys.version_info<(2,6): def next(iterable): return iterable.next() else: next=next if (sys.version_info >= (2, 7) and sys.version_info < (3, 0)) or \ (sys.version_info >= (3, 1)): import unittest else: import unittest2 as unittest class AtomicCounter(object): def __init__(self): self.lock = threading.Lock() self.count = 0 def reset(self): self.count = 0 def incr(self): with self.lock: self.count += 1 def value(self): with self.lock: return self.count class NonserializableError(Exception): def __reduce__(self): raise pickle.PicklingError("to make this error non-serializable") class MyThing2(object): def __init__(self, name="?"): self.name = name Pyro4-4.23/tests/run_suite.py000066400000000000000000000007561227003673200162220ustar00rootroot00000000000000""" Run the complete test suite. This requires nose and coverage to be installed. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ import sys import os import nose dirname = os.path.dirname(__file__) if dirname: print("chdir to "+dirname) os.chdir(dirname) sys.path.insert(0,"../src") # add Pyro source directory nose.main(argv=["noserunner", "--cover-erase","--with-coverage","--cover-package=Pyro4", "--with-xunit"]) Pyro4-4.23/tests/run_suite_simple.py000066400000000000000000000024221227003673200175630ustar00rootroot00000000000000""" Run the complete test suite. Doesn't require nose and coverage, but is more braindead and gives less output. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ import unittest import sys, os if len(sys.argv)==2 and sys.argv[1]=="--tox": # running from Tox, don't screw with paths otherwise it screws up the virtualenv pass else: # running from normal shell invocation dirname = os.path.dirname(__file__) if dirname: print("chdir to "+dirname) os.chdir(dirname) sys.path.insert(0,"../src") # add Pyro source directory sys.path.insert(1,"PyroTests") if __name__=="__main__": # add test modules here modules = [module[:-3] for module in sorted(os.listdir("PyroTests")) if module.endswith(".py") and not module.startswith("__")] print("gathering testcases from %s" % modules) suite=unittest.TestSuite() for module in modules: m=__import__("PyroTests."+module) m=getattr(m,module) testcases = unittest.defaultTestLoader.loadTestsFromModule(m) suite.addTest(testcases) print("\nRUNNING UNIT TESTS...") result=unittest.TextTestRunner(verbosity=1).run(suite) if not result.wasSuccessful(): sys.exit(10) Pyro4-4.23/tests/run_syntaxcheck.py000066400000000000000000000015601227003673200174070ustar00rootroot00000000000000""" Run some syntax checks. Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ import os import sys sys.path.insert(0,"../src") sys.path.insert(1,"PyroTests") def Pyflakes(path, modules): try: from pyflakes.scripts.pyflakes import checkPath except ImportError: print("PYFLAKES not installed. Skipping.") return warnings=0 for m in modules: warnings+=checkPath(os.path.join(path,m)) print("%d warnings occurred in pyflakes check" % warnings) def main(args): pyropath="../src/Pyro4" pyromodules=[module for module in os.listdir(pyropath) if module.endswith(".py")] checkers=args or ["flakes"] if "flakes" in checkers: print("-"*20+"PYFLAKES") Pyflakes(pyropath, pyromodules) if __name__=="__main__": main(sys.argv[1:]) Pyro4-4.23/tox.ini000066400000000000000000000003721227003673200137760ustar00rootroot00000000000000[tox] envlist=py26,py27,py32,py33,pypy,jython [testenv] deps=serpent>=1.3 changedir=tests commands=python -E run_suite_simple.py --tox [testenv:jython] commands=jython run_suite_simple.py --tox [testenv:py26] deps= unittest2 serpent>=1.3