From abc8cf09aad89e3e90ca0d3f40aa2fd7d5a6e0c4 Mon Sep 17 00:00:00 2001 From: hpk Date: Sat, 16 Aug 2008 17:26:59 +0200 Subject: [PATCH] [svn r57321] merging the event branch: * moving in test, misc, code, io directories and py/__init__.py * py/bin/_find.py does not print to stderr anymore * a few fixes to conftest files in other dirs some more fixes and adjustments pending --HG-- branch : trunk --- py/__init__.py | 31 +- py/bin/_findpy.py | 2 +- py/c-extension/__init__.py | 1 + py/code/__init__.py | 1 + py/code/code.py | 102 + py/code/excinfo.py | 325 ++ py/code/frame.py | 55 + py/code/safe_repr.py | 63 + py/code/source.py | 287 ++ py/code/testing/__init__.py | 1 + py/code/testing/test_code.py | 85 + py/code/testing/test_excinfo.py | 584 +++ py/code/testing/test_frame.py | 15 + py/code/testing/test_safe_repr.py | 34 + py/code/testing/test_source.py | 316 ++ py/code/traceback2.py | 200 + py/compat/conftest.py | 2 +- py/doc/TODO.txt | 577 +++ py/doc/__init__.py | 1 + py/doc/apigen.txt | 284 ++ py/doc/apigen_refactorings.txt | 35 + py/doc/bin.txt | 67 + py/doc/code.txt | 141 + py/doc/coding-style.txt | 73 + py/doc/confrest.py | 170 + py/doc/conftest.py | 344 ++ py/doc/contact.txt | 81 + py/doc/download.txt | 113 + py/doc/example/genhtml.py | 13 + py/doc/example/genhtmlcss.py | 23 + py/doc/example/genxml.py | 17 + py/doc/example/pytest/failure_demo.py | 120 + py/doc/example/pytest/test_failures.py | 11 + .../example/pytest/test_setup_flow_example.py | 42 + py/doc/execnet.txt | 224 + py/doc/future.txt | 137 + py/doc/future/code_template.txt | 640 +++ py/doc/future/planning.txt | 64 + py/doc/future/planning2.txt | 39 + py/doc/future/pylib_pypy.txt | 37 + py/doc/future/rsession_todo.txt | 15 + py/doc/greenlet.txt | 315 ++ py/doc/impl-test.txt | 294 ++ py/doc/index.txt | 62 + py/doc/io.txt | 45 + py/doc/links.txt | 33 + py/doc/log.txt | 208 + py/doc/misc.txt | 220 + py/doc/path.txt | 258 ++ py/doc/release-0.9.0.txt | 31 + py/doc/style.css | 1078 +++++ py/doc/talk/execnet-overview.txt | 64 + py/doc/talk/make.py | 11 + py/doc/talk/makeref.py | 54 + py/doc/talk/notes.txt | 16 + py/doc/talk/pytest-overview.txt | 200 + py/doc/talk/ui/default/blank.gif | Bin 0 -> 49 bytes py/doc/talk/ui/default/framing.css | 25 + py/doc/talk/ui/default/iepngfix.htc | 42 + py/doc/talk/ui/default/opera.css | 8 + py/doc/talk/ui/default/outline.css | 16 + py/doc/talk/ui/default/pretty.css | 121 + py/doc/talk/ui/default/print.css | 24 + py/doc/talk/ui/default/py-web.png | Bin 0 -> 2163 bytes py/doc/talk/ui/default/s5-core.css | 11 + py/doc/talk/ui/default/slides.css | 13 + py/doc/talk/ui/default/slides.js | 558 +++ py/doc/talk/ui/merlinux-klein.png | Bin 0 -> 2340 bytes py/doc/talk/ui/py-web.png | Bin 0 -> 2163 bytes py/doc/talk/ui/py.css | 74 + py/doc/test.txt | 622 +++ py/doc/test_conftest.py | 148 + py/doc/why_py.txt | 185 + py/doc/xml.txt | 172 + py/green/conftest.py | 4 +- py/io/__init__.py | 1 + py/io/dupfile.py | 22 + py/io/fdcapture.py | 59 + py/io/forkedfunc.py | 108 + py/io/stdcapture.py | 148 + py/io/terminalwriter.py | 128 + py/io/testing/__init__.py | 1 + py/io/testing/test_dupfile.py | 20 + py/io/testing/test_fdcapture.py | 59 + py/io/testing/test_forkedfunc.py | 138 + py/io/testing/test_stdcapture.py | 150 + py/io/testing/test_terminalwriter.py | 108 + py/misc/__init__.py | 1 + py/misc/_dist.py | 203 + py/misc/buildcmodule.py | 84 + py/misc/cache.py | 157 + py/misc/cmdline/__init__.py | 1 + py/misc/cmdline/countloc.py | 84 + py/misc/conftest-socketgatewayrun.py | 63 + py/misc/difftime.py | 32 + py/misc/dynpkg.py | 84 + py/misc/error.py | 79 + py/misc/findmissingdocstrings.py | 76 + py/misc/killproc.py | 10 + py/misc/rest.py | 73 + py/misc/std.py | 19 + py/misc/svnlook.py | 34 + py/misc/terminal_helper.py | 132 + py/misc/testing/__init__.py | 1 + py/misc/testing/data/svnlookrepo.dump | 160 + py/misc/testing/test_api.py | 44 + py/misc/testing/test_cache.py | 68 + py/misc/testing/test_error.py | 20 + py/misc/testing/test_initpkg.py | 267 ++ py/misc/testing/test_oskill.py | 19 + py/misc/testing/test_std.py | 13 + py/misc/testing/test_svnlook.py | 60 + py/misc/testing/test_terminal.py | 25 + py/misc/testing/test_update_website.py | 75 + py/test/__init__.py | 1 + py/test/cmdline.py | 24 + py/test/collect.py | 413 ++ py/test/compat.py | 58 + py/test/config.py | 256 ++ py/test/conftesthandle.py | 75 + py/test/custompdb.py | 71 + py/test/defaultconftest.py | 113 + py/test/dsession/__init__.py | 1 + py/test/dsession/dsession.py | 230 + py/test/dsession/hostmanage.py | 208 + py/test/dsession/masterslave.py | 116 + py/test/dsession/mypickle.py | 173 + py/test/dsession/testing/__init__.py | 1 + py/test/dsession/testing/basetest.py | 32 + py/test/dsession/testing/test_dsession.py | 361 ++ .../testing/test_functional_dsession.py | 152 + py/test/dsession/testing/test_hostmanage.py | 321 ++ py/test/dsession/testing/test_masterslave.py | 90 + py/test/dsession/testing/test_mypickle.py | 214 + py/test/event.py | 149 + py/test/looponfail/__init__.py | 1 + py/test/looponfail/remote.py | 160 + py/test/looponfail/testing/__init__.py | 1 + py/test/looponfail/testing/test_remote.py | 114 + py/test/looponfail/testing/test_util.py | 90 + py/test/looponfail/util.py | 70 + py/test/outcome.py | 123 + py/test/pycollect.py | 388 ++ py/test/report/__init__.py | 1 + py/test/report/base.py | 90 + py/test/report/collectonly.py | 43 + py/test/report/rest.py | 287 ++ py/test/report/terminal.py | 199 + py/test/report/testing/__init__.py | 1 + py/test/report/testing/test_basereporter.py | 118 + py/test/report/testing/test_collectonly.py | 53 + py/test/report/testing/test_rest.py | 356 ++ py/test/report/testing/test_terminal.py | 164 + py/test/report/testing/test_web.py | 90 + py/test/report/testing/test_webjs.py | 143 + py/test/report/web.py | 473 ++ py/test/report/webdata/__init__.py | 0 py/test/report/webdata/index.html | 126 + py/test/report/webdata/json.py | 310 ++ py/test/report/webdata/source.js | 3862 +++++++++++++++++ py/test/report/webjs.py | 355 ++ py/test/runner.py | 146 + py/test/session.py | 151 + py/test/testing/__init__.py | 1 + py/test/testing/acceptance_test.py | 425 ++ .../testing/import_test/package/__init__.py | 1 + .../package/absolute_import_shared_lib.py | 1 + .../package/module_that_imports_shared_lib.py | 1 + .../testing/import_test/package/shared_lib.py | 3 + .../import_test/package/test_import.py | 52 + py/test/testing/setupdata.py | 241 + py/test/testing/suptest.py | 230 + py/test/testing/test_collect.py | 619 +++ py/test/testing/test_compat.py | 53 + py/test/testing/test_config.py | 493 +++ py/test/testing/test_conftesthandle.py | 89 + py/test/testing/test_doctest.py | 34 + py/test/testing/test_event.py | 58 + py/test/testing/test_outcome.py | 61 + py/test/testing/test_repevent.py | 30 + py/test/testing/test_runner_functional.py | 262 ++ py/test/testing/test_session.py | 274 ++ py/test/testing/test_setup_nested.py | 143 + py/test/web/__init__.py | 0 py/test/web/exception.py | 6 + py/test/web/post_multipart.py | 58 + py/test/web/webcheck.py | 41 + 187 files changed, 27242 insertions(+), 18 deletions(-) create mode 100644 py/c-extension/__init__.py create mode 100644 py/code/__init__.py create mode 100644 py/code/code.py create mode 100644 py/code/excinfo.py create mode 100644 py/code/frame.py create mode 100644 py/code/safe_repr.py create mode 100644 py/code/source.py create mode 100644 py/code/testing/__init__.py create mode 100644 py/code/testing/test_code.py create mode 100644 py/code/testing/test_excinfo.py create mode 100644 py/code/testing/test_frame.py create mode 100644 py/code/testing/test_safe_repr.py create mode 100644 py/code/testing/test_source.py create mode 100644 py/code/traceback2.py create mode 100644 py/doc/TODO.txt create mode 100644 py/doc/__init__.py create mode 100644 py/doc/apigen.txt create mode 100644 py/doc/apigen_refactorings.txt create mode 100644 py/doc/bin.txt create mode 100644 py/doc/code.txt create mode 100644 py/doc/coding-style.txt create mode 100644 py/doc/confrest.py create mode 100644 py/doc/conftest.py create mode 100644 py/doc/contact.txt create mode 100644 py/doc/download.txt create mode 100644 py/doc/example/genhtml.py create mode 100644 py/doc/example/genhtmlcss.py create mode 100644 py/doc/example/genxml.py create mode 100644 py/doc/example/pytest/failure_demo.py create mode 100644 py/doc/example/pytest/test_failures.py create mode 100644 py/doc/example/pytest/test_setup_flow_example.py create mode 100644 py/doc/execnet.txt create mode 100644 py/doc/future.txt create mode 100644 py/doc/future/code_template.txt create mode 100644 py/doc/future/planning.txt create mode 100644 py/doc/future/planning2.txt create mode 100644 py/doc/future/pylib_pypy.txt create mode 100644 py/doc/future/rsession_todo.txt create mode 100644 py/doc/greenlet.txt create mode 100644 py/doc/impl-test.txt create mode 100644 py/doc/index.txt create mode 100644 py/doc/io.txt create mode 100644 py/doc/links.txt create mode 100644 py/doc/log.txt create mode 100644 py/doc/misc.txt create mode 100644 py/doc/path.txt create mode 100644 py/doc/release-0.9.0.txt create mode 100644 py/doc/style.css create mode 100644 py/doc/talk/execnet-overview.txt create mode 100755 py/doc/talk/make.py create mode 100644 py/doc/talk/makeref.py create mode 100644 py/doc/talk/notes.txt create mode 100644 py/doc/talk/pytest-overview.txt create mode 100644 py/doc/talk/ui/default/blank.gif create mode 100644 py/doc/talk/ui/default/framing.css create mode 100644 py/doc/talk/ui/default/iepngfix.htc create mode 100644 py/doc/talk/ui/default/opera.css create mode 100644 py/doc/talk/ui/default/outline.css create mode 100644 py/doc/talk/ui/default/pretty.css create mode 100644 py/doc/talk/ui/default/print.css create mode 100644 py/doc/talk/ui/default/py-web.png create mode 100644 py/doc/talk/ui/default/s5-core.css create mode 100644 py/doc/talk/ui/default/slides.css create mode 100644 py/doc/talk/ui/default/slides.js create mode 100644 py/doc/talk/ui/merlinux-klein.png create mode 100644 py/doc/talk/ui/py-web.png create mode 100644 py/doc/talk/ui/py.css create mode 100644 py/doc/test.txt create mode 100644 py/doc/test_conftest.py create mode 100644 py/doc/why_py.txt create mode 100644 py/doc/xml.txt create mode 100644 py/io/__init__.py create mode 100644 py/io/dupfile.py create mode 100644 py/io/fdcapture.py create mode 100644 py/io/forkedfunc.py create mode 100644 py/io/stdcapture.py create mode 100644 py/io/terminalwriter.py create mode 100644 py/io/testing/__init__.py create mode 100644 py/io/testing/test_dupfile.py create mode 100644 py/io/testing/test_fdcapture.py create mode 100644 py/io/testing/test_forkedfunc.py create mode 100644 py/io/testing/test_stdcapture.py create mode 100644 py/io/testing/test_terminalwriter.py create mode 100644 py/misc/__init__.py create mode 100644 py/misc/_dist.py create mode 100644 py/misc/buildcmodule.py create mode 100644 py/misc/cache.py create mode 100644 py/misc/cmdline/__init__.py create mode 100755 py/misc/cmdline/countloc.py create mode 100644 py/misc/conftest-socketgatewayrun.py create mode 100644 py/misc/difftime.py create mode 100644 py/misc/dynpkg.py create mode 100644 py/misc/error.py create mode 100755 py/misc/findmissingdocstrings.py create mode 100644 py/misc/killproc.py create mode 100644 py/misc/rest.py create mode 100644 py/misc/std.py create mode 100644 py/misc/svnlook.py create mode 100644 py/misc/terminal_helper.py create mode 100644 py/misc/testing/__init__.py create mode 100644 py/misc/testing/data/svnlookrepo.dump create mode 100644 py/misc/testing/test_api.py create mode 100644 py/misc/testing/test_cache.py create mode 100644 py/misc/testing/test_error.py create mode 100644 py/misc/testing/test_initpkg.py create mode 100644 py/misc/testing/test_oskill.py create mode 100644 py/misc/testing/test_std.py create mode 100644 py/misc/testing/test_svnlook.py create mode 100644 py/misc/testing/test_terminal.py create mode 100644 py/misc/testing/test_update_website.py create mode 100644 py/test/__init__.py create mode 100644 py/test/cmdline.py create mode 100644 py/test/collect.py create mode 100644 py/test/compat.py create mode 100644 py/test/config.py create mode 100644 py/test/conftesthandle.py create mode 100644 py/test/custompdb.py create mode 100644 py/test/defaultconftest.py create mode 100644 py/test/dsession/__init__.py create mode 100644 py/test/dsession/dsession.py create mode 100644 py/test/dsession/hostmanage.py create mode 100644 py/test/dsession/masterslave.py create mode 100644 py/test/dsession/mypickle.py create mode 100644 py/test/dsession/testing/__init__.py create mode 100644 py/test/dsession/testing/basetest.py create mode 100644 py/test/dsession/testing/test_dsession.py create mode 100644 py/test/dsession/testing/test_functional_dsession.py create mode 100644 py/test/dsession/testing/test_hostmanage.py create mode 100644 py/test/dsession/testing/test_masterslave.py create mode 100644 py/test/dsession/testing/test_mypickle.py create mode 100644 py/test/event.py create mode 100644 py/test/looponfail/__init__.py create mode 100644 py/test/looponfail/remote.py create mode 100644 py/test/looponfail/testing/__init__.py create mode 100644 py/test/looponfail/testing/test_remote.py create mode 100644 py/test/looponfail/testing/test_util.py create mode 100644 py/test/looponfail/util.py create mode 100644 py/test/outcome.py create mode 100644 py/test/pycollect.py create mode 100644 py/test/report/__init__.py create mode 100644 py/test/report/base.py create mode 100644 py/test/report/collectonly.py create mode 100644 py/test/report/rest.py create mode 100644 py/test/report/terminal.py create mode 100644 py/test/report/testing/__init__.py create mode 100644 py/test/report/testing/test_basereporter.py create mode 100644 py/test/report/testing/test_collectonly.py create mode 100644 py/test/report/testing/test_rest.py create mode 100644 py/test/report/testing/test_terminal.py create mode 100644 py/test/report/testing/test_web.py create mode 100644 py/test/report/testing/test_webjs.py create mode 100644 py/test/report/web.py create mode 100644 py/test/report/webdata/__init__.py create mode 100644 py/test/report/webdata/index.html create mode 100644 py/test/report/webdata/json.py create mode 100644 py/test/report/webdata/source.js create mode 100644 py/test/report/webjs.py create mode 100644 py/test/runner.py create mode 100644 py/test/session.py create mode 100644 py/test/testing/__init__.py create mode 100644 py/test/testing/acceptance_test.py create mode 100644 py/test/testing/import_test/package/__init__.py create mode 100644 py/test/testing/import_test/package/absolute_import_shared_lib.py create mode 100644 py/test/testing/import_test/package/module_that_imports_shared_lib.py create mode 100644 py/test/testing/import_test/package/shared_lib.py create mode 100644 py/test/testing/import_test/package/test_import.py create mode 100644 py/test/testing/setupdata.py create mode 100644 py/test/testing/suptest.py create mode 100644 py/test/testing/test_collect.py create mode 100644 py/test/testing/test_compat.py create mode 100644 py/test/testing/test_config.py create mode 100644 py/test/testing/test_conftesthandle.py create mode 100644 py/test/testing/test_doctest.py create mode 100644 py/test/testing/test_event.py create mode 100644 py/test/testing/test_outcome.py create mode 100644 py/test/testing/test_repevent.py create mode 100644 py/test/testing/test_runner_functional.py create mode 100644 py/test/testing/test_session.py create mode 100644 py/test/testing/test_setup_nested.py create mode 100644 py/test/web/__init__.py create mode 100644 py/test/web/exception.py create mode 100644 py/test/web/post_multipart.py create mode 100644 py/test/web/webcheck.py diff --git a/py/__init__.py b/py/__init__.py index 58b26d509..752768d0b 100644 --- a/py/__init__.py +++ b/py/__init__.py @@ -11,8 +11,8 @@ version = "1.0-pre-alpha" initpkg(__name__, description = "pylib and py.test: agile development and test support library", - revision = int('$LastChangedRevision: 56284 $'.split(':')[1][:-1]), - lastchangedate = '$LastChangedDate: 2008-07-04 08:51:51 +0200 (Fri, 04 Jul 2008) $', + revision = int('$LastChangedRevision: 57321 $'.split(':')[1][:-1]), + lastchangedate = '$LastChangedDate: 2008-08-16 17:26:59 +0200 (Sat, 16 Aug 2008) $', version = version, url = "http://codespeak.net/py", download_url = "XXX", # "http://codespeak.net/download/py/py-%s.tar.gz" %(version,), @@ -26,11 +26,11 @@ initpkg(__name__, exportdefs = { # helpers for use from test functions or collectors 'test.__doc__' : ('./test/__init__.py', '__doc__'), - 'test.raises' : ('./test/raises.py', 'raises'), - 'test.deprecated_call' : ('./test/deprecate.py', 'deprecated_call'), - 'test.skip' : ('./test/item.py', 'skip'), - 'test.fail' : ('./test/item.py', 'fail'), - 'test.exit' : ('./test/session.py', 'exit'), + 'test.raises' : ('./test/outcome.py', 'raises'), + 'test.deprecated_call' : ('./test/outcome.py', 'deprecated_call'), + 'test.skip' : ('./test/outcome.py', 'skip'), + 'test.fail' : ('./test/outcome.py', 'fail'), + 'test.exit' : ('./test/outcome.py', 'exit'), 'test.pdb' : ('./test/custompdb.py', 'set_trace'), # configuration/initialization related test api @@ -41,13 +41,13 @@ initpkg(__name__, # for customization of collecting/running tests 'test.collect.Collector' : ('./test/collect.py', 'Collector'), 'test.collect.Directory' : ('./test/collect.py', 'Directory'), - 'test.collect.Module' : ('./test/collect.py', 'Module'), - 'test.collect.DoctestFile' : ('./test/collect.py', 'DoctestFile'), - 'test.collect.Class' : ('./test/collect.py', 'Class'), - 'test.collect.Instance' : ('./test/collect.py', 'Instance'), - 'test.collect.Generator' : ('./test/collect.py', 'Generator'), - 'test.collect.Item' : ('./test/item.py', 'Item'), - 'test.collect.Function' : ('./test/item.py', 'Function'), + 'test.collect.Module' : ('./test/pycollect.py', 'Module'), + 'test.collect.DoctestFile' : ('./test/pycollect.py', 'DoctestFile'), + 'test.collect.Class' : ('./test/pycollect.py', 'Class'), + 'test.collect.Instance' : ('./test/pycollect.py', 'Instance'), + 'test.collect.Generator' : ('./test/pycollect.py', 'Generator'), + 'test.collect.Item' : ('./test/collect.py', 'Item'), + 'test.collect.Function' : ('./test/pycollect.py', 'Function'), # thread related API (still in early design phase) '_thread.WorkerPool' : ('./thread/pool.py', 'WorkerPool'), @@ -93,6 +93,7 @@ initpkg(__name__, 'builtin.sorted' : ('./builtin/sorted.py', 'sorted'), 'builtin.BaseException' : ('./builtin/exception.py', 'BaseException'), 'builtin.GeneratorExit' : ('./builtin/exception.py', 'GeneratorExit'), + 'builtin.sysex' : ('./builtin/exception.py', 'sysex'), 'builtin.set' : ('./builtin/set.py', 'set'), 'builtin.frozenset' : ('./builtin/set.py', 'frozenset'), @@ -111,6 +112,8 @@ initpkg(__name__, 'io.FDCapture' : ('./io/fdcapture.py', 'FDCapture'), 'io.StdCapture' : ('./io/stdcapture.py', 'StdCapture'), 'io.StdCaptureFD' : ('./io/stdcapture.py', 'StdCaptureFD'), + 'io.TerminalWriter' : ('./io/terminalwriter.py', 'TerminalWriter'), + 'io.ForkedFunc' : ('./io/forkedfunc.py', 'ForkedFunc'), # error module, defining all errno's as Classes 'error' : ('./misc/error.py', 'error'), diff --git a/py/bin/_findpy.py b/py/bin/_findpy.py index c1e208fae..f8fc2a818 100755 --- a/py/bin/_findpy.py +++ b/py/bin/_findpy.py @@ -19,7 +19,7 @@ def searchpy(current): # if p == current: # return True if current != sys.path[0]: # if we are already first, then ok - print >>sys.stderr, "inserting into sys.path:", current + #print >>sys.stderr, "inserting into sys.path:", current sys.path.insert(0, current) return True current = opd(current) diff --git a/py/c-extension/__init__.py b/py/c-extension/__init__.py new file mode 100644 index 000000000..792d60054 --- /dev/null +++ b/py/c-extension/__init__.py @@ -0,0 +1 @@ +# diff --git a/py/code/__init__.py b/py/code/__init__.py new file mode 100644 index 000000000..f15acf851 --- /dev/null +++ b/py/code/__init__.py @@ -0,0 +1 @@ +""" python inspection/code generation API """ diff --git a/py/code/code.py b/py/code/code.py new file mode 100644 index 000000000..422425295 --- /dev/null +++ b/py/code/code.py @@ -0,0 +1,102 @@ +import py + +class Code(object): + """ wrapper around Python code objects """ + def __init__(self, rawcode): + rawcode = getattr(rawcode, 'im_func', rawcode) + rawcode = getattr(rawcode, 'func_code', rawcode) + self.raw = rawcode + try: + self.filename = rawcode.co_filename + self.firstlineno = rawcode.co_firstlineno - 1 + self.name = rawcode.co_name + except AttributeError: + raise TypeError("not a code object: %r" %(rawcode,)) + + def __eq__(self, other): + return self.raw == other.raw + + def __ne__(self, other): + return not self == other + + def new(self, rec=False, **kwargs): + """ return new code object with modified attributes. + if rec-cursive is true then dive into code + objects contained in co_consts. + """ + names = [x for x in dir(self.raw) if x[:3] == 'co_'] + for name in kwargs: + if name not in names: + raise TypeError("unknown code attribute: %r" %(name, )) + if rec: + newconstlist = [] + co = self.raw + cotype = type(co) + for c in co.co_consts: + if isinstance(c, cotype): + c = self.__class__(c).new(rec=True, **kwargs) + newconstlist.append(c) + return self.new(rec=False, co_consts=tuple(newconstlist), **kwargs) + for name in names: + if name not in kwargs: + kwargs[name] = getattr(self.raw, name) + return py.std.new.code( + kwargs['co_argcount'], + kwargs['co_nlocals'], + kwargs['co_stacksize'], + kwargs['co_flags'], + kwargs['co_code'], + kwargs['co_consts'], + kwargs['co_names'], + kwargs['co_varnames'], + kwargs['co_filename'], + kwargs['co_name'], + kwargs['co_firstlineno'], + kwargs['co_lnotab'], + kwargs['co_freevars'], + kwargs['co_cellvars'], + ) + + def path(self): + """ return a py.path.local object pointing to the source code """ + fn = self.raw.co_filename + try: + return fn.__path__ + except AttributeError: + p = py.path.local(self.raw.co_filename) + if not p.check(file=1): + # XXX maybe try harder like the weird logic + # in the standard lib [linecache.updatecache] does? + p = self.raw.co_filename + return p + + path = property(path, None, None, "path of this code object") + + def fullsource(self): + """ return a py.code.Source object for the full source file of the code + """ + fn = self.raw.co_filename + try: + return fn.__source__ + except AttributeError: + path = self.path + if not isinstance(path, py.path.local): + return None + return py.code.Source(self.path.read(mode="rU")) + fullsource = property(fullsource, None, None, + "full source containing this code object") + + def source(self): + """ return a py.code.Source object for the code object's source only + """ + # return source only for that part of code + import inspect + return py.code.Source(inspect.getsource(self.raw)) + + def getargs(self): + """ return a tuple with the argument names for the code object + """ + # handfull shortcut for getting args + raw = self.raw + return raw.co_varnames[:raw.co_argcount] + diff --git a/py/code/excinfo.py b/py/code/excinfo.py new file mode 100644 index 000000000..ce3c712ef --- /dev/null +++ b/py/code/excinfo.py @@ -0,0 +1,325 @@ +""" + +Exception Info representation + formatting + +""" +import py +from py.__.code import safe_repr +from sys import exc_info + +class ExceptionInfo(object): + """ wraps sys.exc_info() objects and offers + help for navigating the traceback. + """ + _striptext = '' + def __init__(self, tup=None, exprinfo=None): + # NB. all attributes are private! Subclasses or other + # ExceptionInfo-like classes may have different attributes. + if tup is None: + tup = exc_info() + if exprinfo is None and isinstance(tup[1], py.magic.AssertionError): + exprinfo = tup[1].msg + if exprinfo and exprinfo.startswith('assert '): + self._striptext = 'AssertionError: ' + self._excinfo = tup + self.type, self.value, tb = self._excinfo + self.typename = self.type.__name__ + self.traceback = py.code.Traceback(tb) + + def __repr__(self): + return "" % (self.typename, len(self.traceback)) + + def exconly(self, tryshort=False): + """ return the exception as a string + + when 'tryshort' resolves to True, and the exception is a + py.magic.AssertionError, only the actual exception part of + the exception representation is returned (so 'AssertionError: ' is + removed from the beginning) + """ + lines = py.std.traceback.format_exception_only(self.type, self.value) + text = ''.join(lines) + if text.endswith('\n'): + text = text[:-1] + if tryshort: + if text.startswith(self._striptext): + text = text[len(self._striptext):] + return text + + def errisinstance(self, exc): + """ return True if the exception is an instance of exc """ + return isinstance(self.value, exc) + + def _getreprcrash(self): + exconly = self.exconly(tryshort=True) + entry = self.traceback.getcrashentry() + path, lineno = entry.path, entry.lineno + reprcrash = ReprFileLocation(path, lineno+1, exconly) + return reprcrash + + def getrepr(self, showlocals=False, style="long", tbfilter=True, funcargs=False): + """ return str()able representation of this exception info. + showlocals: show locals per traceback entry + style: long|short|no traceback style + tbfilter: hide entries (where __tracebackhide__ is true) + """ + fmt = FormattedExcinfo(showlocals=showlocals, style=style, + tbfilter=tbfilter, funcargs=funcargs) + return fmt.repr_excinfo(self) + + def __str__(self): + entry = self.traceback[-1] + loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly()) + return str(loc) + +class FormattedExcinfo(object): + """ presenting information about failing Functions and Generators. """ + # for traceback entries + flow_marker = ">" + fail_marker = "E" + + def __init__(self, showlocals=False, style="long", tbfilter=True, funcargs=False): + self.showlocals = showlocals + self.style = style + self.tbfilter = tbfilter + self.funcargs = funcargs + + def _getindent(self, source): + # figure out indent for given source + try: + s = str(source.getstatement(len(source)-1)) + except KeyboardInterrupt: + raise + except: + s = str(source[-1]) + return 4 + (len(s) - len(s.lstrip())) + + def _getentrysource(self, entry): + source = entry.getsource() + if source is None: + source = py.code.Source("???") + return source.deindent() + + def _saferepr(self, obj): + return safe_repr._repr(obj) + + def repr_args(self, entry): + if self.funcargs: + args = [] + for argname, argvalue in entry.frame.getargs(): + args.append((argname, self._saferepr(argvalue))) + return ReprFuncArgs(args) + + def get_source(self, source, line_index=-1, excinfo=None): + """ return formatted and marked up source lines. """ + lines = [] + if source is None: + source = py.code.Source("???") + line_index = 0 + if line_index < 0: + line_index += len(source) + for i in range(len(source)): + if i == line_index: + prefix = self.flow_marker + " " + else: + prefix = " " + line = prefix + source[i] + lines.append(line) + if excinfo is not None: + indent = self._getindent(source) + lines.extend(self.get_exconly(excinfo, indent=indent, markall=True)) + return lines + + def get_exconly(self, excinfo, indent=4, markall=False): + lines = [] + indent = " " * indent + # get the real exception information out + exlines = excinfo.exconly(tryshort=True).split('\n') + failindent = self.fail_marker + indent[1:] + for line in exlines: + lines.append(failindent + line) + if not markall: + failindent = indent + return lines + + def repr_locals(self, locals): + if self.showlocals: + lines = [] + items = locals.items() + items.sort() + for name, value in items: + if name == '__builtins__': + lines.append("__builtins__ = ") + else: + # This formatting could all be handled by the _repr() function, which is + # only repr.Repr in disguise, so is very configurable. + str_repr = safe_repr._repr(value) + #if len(str_repr) < 70 or not isinstance(value, + # (list, tuple, dict)): + lines.append("%-10s = %s" %(name, str_repr)) + #else: + # self._line("%-10s =\\" % (name,)) + # # XXX + # py.std.pprint.pprint(value, stream=self.excinfowriter) + return ReprLocals(lines) + + def repr_traceback_entry(self, entry, excinfo=None): + # excinfo is not None if this is the last tb entry + source = self._getentrysource(entry) + line_index = entry.lineno - entry.getfirstlinesource() + + lines = [] + if self.style == "long": + reprargs = self.repr_args(entry) + lines.extend(self.get_source(source, line_index, excinfo)) + message = excinfo and excinfo.typename or "" + filelocrepr = ReprFileLocation(entry.path, entry.lineno+1, message) + localsrepr = self.repr_locals(entry.locals) + return ReprEntry(lines, reprargs, localsrepr, filelocrepr) + else: + if self.style == "short": + line = source[line_index].lstrip() + lines.append(' File "%s", line %d, in %s' % ( + entry.path.basename, entry.lineno+1, entry.name)) + lines.append(" " + line) + if excinfo: + lines.extend(self.get_exconly(excinfo, indent=4)) + return ReprEntry(lines, None, None, None) + + def repr_traceback(self, excinfo): + traceback = excinfo.traceback + if self.tbfilter: + traceback = traceback.filter() + recursionindex = traceback.recursionindex() + last = traceback[-1] + entries = [] + extraline = None + for index, entry in py.builtin.enumerate(traceback): + einfo = (last == entry) and excinfo or None + reprentry = self.repr_traceback_entry(entry, einfo) + entries.append(reprentry) + if index == recursionindex: + extraline = "!!! Recursion detected (same locals & position)" + break + return ReprTraceback(entries, extraline, style=self.style) + + def repr_excinfo(self, excinfo): + reprtraceback = self.repr_traceback(excinfo) + reprcrash = excinfo._getreprcrash() + return ReprExceptionInfo(reprtraceback, reprcrash) + +class Repr: + def __str__(self): + tw = py.io.TerminalWriter(stringio=True) + self.toterminal(tw) + return tw.stringio.getvalue().strip() + + def __repr__(self): + return "<%s instance at %0x>" %(self.__class__, id(self)) + +class ReprExceptionInfo(Repr): + def __init__(self, reprtraceback, reprcrash): + self.reprtraceback = reprtraceback + self.reprcrash = reprcrash + self.sections = [] + + def addsection(self, name, content, sep="-"): + self.sections.append((name, content, sep)) + + def toterminal(self, tw): + self.reprtraceback.toterminal(tw) + for name, content, sep in self.sections: + tw.sep(sep, name) + tw.line(content) + +class ReprTraceback(Repr): + entrysep = "_ " + + def __init__(self, reprentries, extraline, style): + self.reprentries = reprentries + self.extraline = extraline + self.style = style + + def toterminal(self, tw): + sepok = False + for entry in self.reprentries: + if sepok and self.style == "long": + tw.sep(self.entrysep) + tw.line("") + sepok = True + entry.toterminal(tw) + if self.extraline: + tw.line(self.extraline) + +class ReprEntry(Repr): + localssep = "_ " + + def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr): + self.lines = lines + self.reprfuncargs = reprfuncargs + self.reprlocals = reprlocals + self.reprfileloc = filelocrepr + + def toterminal(self, tw): + if self.reprfuncargs: + self.reprfuncargs.toterminal(tw) + for line in self.lines: + red = line.startswith("E ") + tw.line(tw.markup(bold=True, red=red, text=line)) + if self.reprlocals: + #tw.sep(self.localssep, "Locals") + tw.line("") + self.reprlocals.toterminal(tw) + if self.reprfileloc: + tw.line("") + self.reprfileloc.toterminal(tw) + + def __str__(self): + return "%s\n%s\n%s" % ("\n".join(self.lines), + self.reprlocals, + self.reprfileloc) + +class ReprFileLocation(Repr): + def __init__(self, path, lineno, message): + self.path = str(path) + self.lineno = lineno + self.message = message + + def toterminal(self, tw): + # filename and lineno output for each entry, + # using an output format that most editors unterstand + msg = self.message + i = msg.find("\n") + if i != -1: + msg = msg[:i] + tw.line("%s:%s: %s" %(self.path, self.lineno, msg)) + +class ReprLocals(Repr): + def __init__(self, lines): + self.lines = lines + + def toterminal(self, tw): + for line in self.lines: + tw.line(line) + +class ReprFuncArgs(Repr): + def __init__(self, args): + self.args = args + + def toterminal(self, tw): + if self.args: + linesofar = "" + for name, value in self.args: + ns = "%s = %s" %(name, value) + if len(ns) + len(linesofar) + 2 > tw.fullwidth: + if linesofar: + tw.line(linesofar) + linesofar = ns + else: + if linesofar: + linesofar += ", " + ns + else: + linesofar = ns + if linesofar: + tw.line(linesofar) + tw.line("") diff --git a/py/code/frame.py b/py/code/frame.py new file mode 100644 index 000000000..8b67ba27d --- /dev/null +++ b/py/code/frame.py @@ -0,0 +1,55 @@ +import py +import py.__.code.safe_repr + +class Frame(object): + """Wrapper around a Python frame holding f_locals and f_globals + in which expressions can be evaluated.""" + + def __init__(self, frame): + self.code = py.code.Code(frame.f_code) + self.lineno = frame.f_lineno - 1 + self.f_globals = frame.f_globals + self.f_locals = frame.f_locals + self.raw = frame + + def statement(self): + return self.code.fullsource.getstatement(self.lineno) + statement = property(statement, None, None, + "statement this frame is at") + + def eval(self, code, **vars): + """ evaluate 'code' in the frame + + 'vars' are optional additional local variables + + returns the result of the evaluation + """ + f_locals = self.f_locals.copy() + f_locals.update(vars) + return eval(code, self.f_globals, f_locals) + + def exec_(self, code, **vars): + """ exec 'code' in the frame + + 'vars' are optiona; additional local variables + """ + f_locals = self.f_locals.copy() + f_locals.update(vars) + exec code in self.f_globals, f_locals + + def repr(self, object): + """ return a 'safe' (non-recursive, one-line) string repr for 'object' + """ + return py.__.code.safe_repr._repr(object) + + def is_true(self, object): + return object + + def getargs(self): + """ return a list of tuples (name, value) for all arguments + """ + retval = [] + for arg in self.code.getargs(): + retval.append((arg, self.f_locals[arg])) + return retval + diff --git a/py/code/safe_repr.py b/py/code/safe_repr.py new file mode 100644 index 000000000..86a03caa2 --- /dev/null +++ b/py/code/safe_repr.py @@ -0,0 +1,63 @@ +"""Defines a safe repr function. This will always return a string of "reasonable" length +no matter what the object does in it's own repr function. Let's examine what can go wrong +in an arbitrary repr function. +The default repr will return something like (on Win32 anyway): +. Well behaved user-defined repr() methods will do similar. +The usual expectation is that repr will return a single line string. + +1. However, the repr method can raise an exception of an arbitrary type. + +Also, the return value may not be as expected: + 2. The return value may not be a string! + 3. The return value may not be a single line string, it may contain line breaks. + 4. The method may enter a loop and never return. + 5. The return value may be enormous, eg range(100000) + + The standard library has a nice implementation in the repr module that will do the job, + but the exception + handling is silent, so the the output contains no clue that repr() call raised an + exception. I would like to be told if repr raises an exception, it's a serious error, so + a sublass of repr overrides the method that does repr for class instances.""" + + +import repr +import __builtin__ + + +class SafeRepr(repr.Repr): + def __init__(self, *args, **kwargs): + repr.Repr.__init__(self, *args, **kwargs) + # Do we need a commandline switch for this? + self.maxstring = 240 # 3 * 80 chars + self.maxother = 160 # 2 * 80 chars + def repr_instance(self, x, level): + try: + # Try the vanilla repr and make sure that the result is a string + s = str(__builtin__.repr(x)) + except (KeyboardInterrupt, MemoryError, SystemExit): + raise + except Exception ,e: + try: + exc_name = e.__class__.__name__ + except: + exc_name = 'unknown' + try: + exc_info = str(e) + except: + exc_info = 'unknown' + return '<[%s("%s") raised in repr()] %s object at 0x%x>' % \ + (exc_name, exc_info, x.__class__.__name__, id(x)) + except: + try: + name = x.__class__.__name__ + except: + name = 'unknown' + return '<[unknown exception raised in repr()] %s object at 0x%x>' % \ + (name, id(x)) + if len(s) > self.maxstring: + i = max(0, (self.maxstring-3)//2) + j = max(0, self.maxstring-3-i) + s = s[:i] + '...' + s[len(s)-j:] + return s + +_repr = SafeRepr().repr diff --git a/py/code/source.py b/py/code/source.py new file mode 100644 index 000000000..6fdd08533 --- /dev/null +++ b/py/code/source.py @@ -0,0 +1,287 @@ +from __future__ import generators +import sys +import inspect, tokenize +import py +cpy_compile = compile + +# DON'T IMPORT PY HERE + +class Source(object): + """ a mutable object holding a source code fragment, + possibly deindenting it. + """ + def __init__(self, *parts, **kwargs): + self.lines = lines = [] + de = kwargs.get('deindent', True) + rstrip = kwargs.get('rstrip', True) + for part in parts: + if not part: + partlines = [] + if isinstance(part, Source): + partlines = part.lines + elif isinstance(part, (unicode, str)): + partlines = part.split('\n') + if rstrip: + while partlines: + if partlines[-1].strip(): + break + partlines.pop() + else: + partlines = getsource(part, deindent=de).lines + if de: + partlines = deindent(partlines) + lines.extend(partlines) + + def __eq__(self, other): + try: + return self.lines == other.lines + except AttributeError: + if isinstance(other, str): + return str(self) == other + return False + + def __getitem__(self, key): + if isinstance(key, int): + return self.lines[key] + else: + if key.step not in (None, 1): + raise IndexError("cannot slice a Source with a step") + return self.__getslice__(key.start, key.stop) + + def __len__(self): + return len(self.lines) + + def __getslice__(self, start, end): + newsource = Source() + newsource.lines = self.lines[start:end] + return newsource + + def strip(self): + """ return new source object with trailing + and leading blank lines removed. + """ + start, end = 0, len(self) + while start < end and not self.lines[start].strip(): + start += 1 + while end > start and not self.lines[end-1].strip(): + end -= 1 + source = Source() + source.lines[:] = self.lines[start:end] + return source + + def putaround(self, before='', after='', indent=' ' * 4): + """ return a copy of the source object with + 'before' and 'after' wrapped around it. + """ + before = Source(before) + after = Source(after) + newsource = Source() + lines = [ (indent + line) for line in self.lines] + newsource.lines = before.lines + lines + after.lines + return newsource + + def indent(self, indent=' ' * 4): + """ return a copy of the source object with + all lines indented by the given indent-string. + """ + newsource = Source() + newsource.lines = [(indent+line) for line in self.lines] + return newsource + + def getstatement(self, lineno): + """ return Source statement which contains the + given linenumber (counted from 0). + """ + start, end = self.getstatementrange(lineno) + return self[start:end] + + def getstatementrange(self, lineno): + """ return (start, end) tuple which spans the minimal + statement region which containing the given lineno. + """ + # XXX there must be a better than these heuristic ways ... + # XXX there may even be better heuristics :-) + if not (0 <= lineno < len(self)): + raise IndexError("lineno out of range") + + # 1. find the start of the statement + from codeop import compile_command + for start in range(lineno, -1, -1): + trylines = self.lines[start:lineno+1] + # quick hack to indent the source and get it as a string in one go + trylines.insert(0, 'def xxx():') + trysource = '\n '.join(trylines) + # ^ space here + try: + compile_command(trysource) + except (SyntaxError, OverflowError, ValueError): + pass + else: + break # got a valid or incomplete statement + + # 2. find the end of the statement + for end in range(lineno+1, len(self)+1): + trysource = self[start:end] + if trysource.isparseable(): + break + + return start, end + + def getblockend(self, lineno): + # XXX + lines = [x + '\n' for x in self.lines[lineno:]] + blocklines = inspect.getblock(lines) + #print blocklines + return lineno + len(blocklines) - 1 + + def deindent(self, offset=None): + """ return a new source object deindented by offset. + If offset is None then guess an indentation offset from + the first non-blank line. Subsequent lines which have a + lower indentation offset will be copied verbatim as + they are assumed to be part of multilines. + """ + # XXX maybe use the tokenizer to properly handle multiline + # strings etc.pp? + newsource = Source() + newsource.lines[:] = deindent(self.lines, offset) + return newsource + + def isparseable(self, deindent=True): + """ return True if source is parseable, heuristically + deindenting it by default. + """ + import parser + if deindent: + source = str(self.deindent()) + else: + source = str(self) + try: + parser.suite(source+'\n') + except (parser.ParserError, SyntaxError): + return False + else: + return True + + def __str__(self): + return "\n".join(self.lines) + + def compile(self, filename=None, mode='exec', flag=generators.compiler_flag, + dont_inherit=0, _genframe=None): + """ return compiled code object. if filename is None + invent an artificial filename which displays + the source/line position of the caller frame. + """ + if not filename or py.path.local(filename).check(file=0): + if _genframe is None: + _genframe = sys._getframe(1) # the caller + fn,lineno = _genframe.f_code.co_filename, _genframe.f_lineno + if not filename: + filename = '' % (fn, lineno) + else: + filename = '' % (filename, fn, lineno) + source = "\n".join(self.lines) + '\n' + try: + co = cpy_compile(source, filename, mode, flag) + except SyntaxError, ex: + # re-represent syntax errors from parsing python strings + msglines = self.lines[:ex.lineno] + if ex.offset: + msglines.append(" "*ex.offset + '^') + msglines.append("syntax error probably generated here: %s" % filename) + newex = SyntaxError('\n'.join(msglines)) + newex.offset = ex.offset + newex.lineno = ex.lineno + newex.text = ex.text + raise newex + else: + co_filename = MyStr(filename) + co_filename.__source__ = self + return py.code.Code(co).new(rec=1, co_filename=co_filename) + #return newcode_withfilename(co, co_filename) + +# +# public API shortcut functions +# + +def compile_(source, filename=None, mode='exec', flags= + generators.compiler_flag, dont_inherit=0): + """ compile the given source to a raw code object, + which points back to the source code through + "co_filename.__source__". All code objects + contained in the code object will recursively + also have this special subclass-of-string + filename. + """ + _genframe = sys._getframe(1) # the caller + s = Source(source) + co = s.compile(filename, mode, flags, _genframe=_genframe) + return co + + +# +# helper functions +# +class MyStr(str): + """ custom string which allows to add attributes. """ + +def getsource(obj, **kwargs): + if hasattr(obj, 'func_code'): + obj = obj.func_code + elif hasattr(obj, 'f_code'): + obj = obj.f_code + try: + fullsource = obj.co_filename.__source__ + except AttributeError: + try: + strsrc = inspect.getsource(obj) + except IndentationError: + strsrc = "\"Buggy python version consider upgrading, cannot get source\"" + assert isinstance(strsrc, str) + return Source(strsrc, **kwargs) + else: + lineno = obj.co_firstlineno - 1 + end = fullsource.getblockend(lineno) + return fullsource[lineno:end+1] + + +def deindent(lines, offset=None): + if offset is None: + for line in lines: + line = line.expandtabs() + s = line.lstrip() + if s: + offset = len(line)-len(s) + break + else: + offset = 0 + if offset == 0: + return list(lines) + newlines = [] + def readline_generator(lines): + for line in lines: + yield line + '\n' + while True: + yield '' + + readline = readline_generator(lines).next + + try: + for _, _, (sline, _), (eline, _), _ in tokenize.generate_tokens(readline): + if sline > len(lines): + break # End of input reached + if sline > len(newlines): + line = lines[sline - 1].expandtabs() + if line.lstrip() and line[:offset].isspace(): + line = line[offset:] # Deindent + newlines.append(line) + + for i in range(sline, eline): + # Don't deindent continuing lines of + # multiline tokens (i.e. multiline strings) + newlines.append(lines[i]) + except (IndentationError, tokenize.TokenError): + pass + # Add any lines we didn't see. E.g. if an exception was raised. + newlines.extend(lines[len(newlines):]) + return newlines diff --git a/py/code/testing/__init__.py b/py/code/testing/__init__.py new file mode 100644 index 000000000..792d60054 --- /dev/null +++ b/py/code/testing/__init__.py @@ -0,0 +1 @@ +# diff --git a/py/code/testing/test_code.py b/py/code/testing/test_code.py new file mode 100644 index 000000000..e8cb9f8c5 --- /dev/null +++ b/py/code/testing/test_code.py @@ -0,0 +1,85 @@ +from __future__ import generators +import py +import new + +def test_newcode(): + source = "i = 3" + co = compile(source, '', 'exec') + code = py.code.Code(co) + newco = code.new() + assert co == newco + +def test_ne(): + code1 = py.code.Code(compile('foo = "bar"', '', 'exec')) + assert code1 == code1 + code2 = py.code.Code(compile('foo = "baz"', '', 'exec')) + assert code2 != code1 + +def test_newcode_unknown_args(): + code = py.code.Code(compile("", '', 'exec')) + py.test.raises(TypeError, 'code.new(filename="hello")') + +def test_newcode_withfilename(): + source = py.code.Source(""" + def f(): + def g(): + pass + """) + co = compile(str(source)+'\n', 'nada', 'exec') + obj = 'hello' + newco = py.code.Code(co).new(rec=True, co_filename=obj) + def walkcode(co): + for x in co.co_consts: + if isinstance(x, type(co)): + for y in walkcode(x): + yield y + yield co + + names = [] + for code in walkcode(newco): + assert newco.co_filename == obj + assert newco.co_filename is obj + names.append(code.co_name) + assert 'f' in names + assert 'g' in names + +def test_newcode_with_filename(): + source = "i = 3" + co = compile(source, '', 'exec') + code = py.code.Code(co) + class MyStr(str): + pass + filename = MyStr("hello") + filename.__source__ = py.code.Source(source) + newco = code.new(rec=True, co_filename=filename) + assert newco.co_filename is filename + s = py.code.Source(newco) + assert str(s) == source + + +def test_new_code_object_carries_filename_through(): + class mystr(str): + pass + filename = mystr("dummy") + co = compile("hello\n", filename, 'exec') + assert not isinstance(co.co_filename, mystr) + c2 = new.code(co.co_argcount, co.co_nlocals, co.co_stacksize, + co.co_flags, co.co_code, co.co_consts, + co.co_names, co.co_varnames, + filename, + co.co_name, co.co_firstlineno, co.co_lnotab, + co.co_freevars, co.co_cellvars) + assert c2.co_filename is filename + +def test_code_gives_back_name_for_not_existing_file(): + name = 'abc-123' + co_code = compile("pass\n", name, 'exec') + assert co_code.co_filename == name + code = py.code.Code(co_code) + assert str(code.path) == name + assert code.fullsource is None + +def test_code_with_class(): + class A: + pass + py.test.raises(TypeError, "py.code.Code(A)") diff --git a/py/code/testing/test_excinfo.py b/py/code/testing/test_excinfo.py new file mode 100644 index 000000000..73cef7394 --- /dev/null +++ b/py/code/testing/test_excinfo.py @@ -0,0 +1,584 @@ +import py +from py.__.code.excinfo import FormattedExcinfo, ReprExceptionInfo + +class TWMock: + def __init__(self): + self.lines = [] + def sep(self, sep, line=None): + self.lines.append((sep, line)) + def line(self, line): + self.lines.append(line) + def markup(self, text, **kw): + return text + + fullwidth = 80 + +def test_excinfo_simple(): + try: + raise ValueError + except ValueError: + info = py.code.ExceptionInfo() + assert info.type == ValueError + +def test_excinfo_getstatement(): + def g(): + raise ValueError + def f(): + g() + try: + f() + except ValueError: + excinfo = py.code.ExceptionInfo() + linenumbers = [f.func_code.co_firstlineno-1+3, + f.func_code.co_firstlineno-1+1, + g.func_code.co_firstlineno-1+1,] + l = list(excinfo.traceback) + foundlinenumbers = [x.lineno for x in l] + print l[0].frame.statement + assert foundlinenumbers == linenumbers + #for x in info: + # print "%s:%d %s" %(x.path.relto(root), x.lineno, x.statement) + #xxx + +# testchain for getentries test below +def f(): + # + raise ValueError + # +def g(): + # + __tracebackhide__ = True + f() + # +def h(): + # + g() + # + +class TestTraceback_f_g_h: + def setup_method(self, method): + try: + h() + except ValueError: + self.excinfo = py.code.ExceptionInfo() + + def test_traceback_entries(self): + tb = self.excinfo.traceback + entries = list(tb) + assert len(tb) == 4 # maybe fragile test + assert len(entries) == 4 # maybe fragile test + names = ['f', 'g', 'h'] + for entry in entries: + try: + names.remove(entry.frame.code.name) + except ValueError: + pass + assert not names + + def test_traceback_entry_getsource(self): + tb = self.excinfo.traceback + s = str(tb[-1].getsource() ) + assert s.startswith("def f():") + assert s.endswith("raise ValueError") + + def test_traceback_entry_getsource_in_construct(self): + source = py.code.Source("""\ + def xyz(): + try: + raise ValueError + except somenoname: + pass + xyz() + """) + try: + exec source.compile() + except NameError: + tb = py.code.ExceptionInfo().traceback + print tb[-1].getsource() + s = str(tb[-1].getsource()) + assert s.startswith("def xyz():\n try:") + assert s.endswith("except somenoname:") + + def test_traceback_cut(self): + co = py.code.Code(f) + path, firstlineno = co.path, co.firstlineno + traceback = self.excinfo.traceback + newtraceback = traceback.cut(path=path, firstlineno=firstlineno) + assert len(newtraceback) == 1 + newtraceback = traceback.cut(path=path, lineno=firstlineno+2) + assert len(newtraceback) == 1 + + def test_traceback_filter(self): + traceback = self.excinfo.traceback + ntraceback = traceback.filter() + assert len(ntraceback) == len(traceback) - 1 + + def test_traceback_recursion_index(self): + def f(n): + if n < 10: + n += 1 + f(n) + excinfo = py.test.raises(RuntimeError, f, 8) + traceback = excinfo.traceback + recindex = traceback.recursionindex() + assert recindex == 3 + + def test_traceback_no_recursion_index(self): + def do_stuff(): + raise RuntimeError + def reraise_me(): + import sys + exc, val, tb = sys.exc_info() + raise exc, val, tb + def f(n): + try: + do_stuff() + except: + reraise_me() + excinfo = py.test.raises(RuntimeError, f, 8) + traceback = excinfo.traceback + recindex = traceback.recursionindex() + assert recindex is None + + def test_traceback_getcrashentry(self): + def i(): + __tracebackhide__ = True + raise ValueError + def h(): + i() + def g(): + __tracebackhide__ = True + h() + def f(): + g() + + excinfo = py.test.raises(ValueError, f) + tb = excinfo.traceback + entry = tb.getcrashentry() + co = py.code.Code(h) + assert entry.frame.code.path == co.path + assert entry.lineno == co.firstlineno + 1 + assert entry.frame.code.name == 'h' + + def test_traceback_getcrashentry_empty(self): + def g(): + __tracebackhide__ = True + raise ValueError + def f(): + __tracebackhide__ = True + g() + + excinfo = py.test.raises(ValueError, f) + tb = excinfo.traceback + entry = tb.getcrashentry() + co = py.code.Code(g) + assert entry.frame.code.path == co.path + assert entry.lineno == co.firstlineno + 2 + assert entry.frame.code.name == 'g' + +def hello(x): + x + 5 + +def test_tbentry_reinterpret(): + try: + hello("hello") + except TypeError: + excinfo = py.code.ExceptionInfo() + tbentry = excinfo.traceback[-1] + msg = tbentry.reinterpret() + assert msg.startswith("TypeError: ('hello' + 5)") + +def test_excinfo_exconly(): + excinfo = py.test.raises(ValueError, h) + assert excinfo.exconly().startswith('ValueError') + +def test_excinfo_repr(): + excinfo = py.test.raises(ValueError, h) + s = repr(excinfo) + assert s == "" + +def test_excinfo_str(): + excinfo = py.test.raises(ValueError, h) + s = str(excinfo) + print s + assert s.startswith(__file__[:-1]) # pyc file + assert s.endswith("ValueError") + assert len(s.split(":")) == 3 + +def test_excinfo_errisinstance(): + excinfo = py.test.raises(ValueError, h) + assert excinfo.errisinstance(ValueError) + +def test_excinfo_no_sourcecode(): + try: + exec "raise ValueError()" + except ValueError: + excinfo = py.code.ExceptionInfo() + s = str(excinfo.traceback[-1]) + assert s == " File '':1 in \n ???\n" + +def test_entrysource_Queue_example(): + import Queue + try: + Queue.Queue().get(timeout=0.001) + except Queue.Empty: + excinfo = py.code.ExceptionInfo() + entry = excinfo.traceback[-1] + source = entry.getsource() + assert source is not None + s = str(source).strip() + assert s.startswith("def get") + +def test_codepath_Queue_example(): + py.test.skip("try harder to get at the paths of code objects.") + import Queue + try: + Queue.Queue().get(timeout=0.001) + except Queue.Empty: + excinfo = py.code.ExceptionInfo() + entry = excinfo.traceback[-1] + path = entry.path + assert isinstance(path, py.path.local) + assert path.basename == "Queue.py" + assert path.check() + +class TestFormattedExcinfo: + def setup_method(self, method): + self.tmpdir = py.test.ensuretemp("%s_%s" %( + self.__class__.__name__, method.__name__)) + + def importasmod(self, source): + source = py.code.Source(source) + modpath = self.tmpdir.join("mod.py") + self.tmpdir.ensure("__init__.py") + modpath.write(source) + return modpath.pyimport() + + def excinfo_from_exec(self, source): + source = py.code.Source(source).strip() + try: + exec source.compile() + except KeyboardInterrupt: + raise + except: + return py.code.ExceptionInfo() + assert 0, "did not raise" + + def test_repr_source(self): + pr = FormattedExcinfo() + source = py.code.Source(""" + def f(x): + pass + """).strip() + pr.flow_marker = "|" + lines = pr.get_source(source, 0) + assert len(lines) == 2 + assert lines[0] == "| def f(x):" + assert lines[1] == " pass" + + def test_repr_source_excinfo(self): + """ check if indentation is right """ + pr = FormattedExcinfo() + excinfo = self.excinfo_from_exec(""" + def f(): + assert 0 + f() + """) + pr = FormattedExcinfo() + source = pr._getentrysource(excinfo.traceback[-1]) + lines = pr.get_source(source, 1, excinfo) + print lines + assert lines == [ + ' def f():', + '> assert 0', + 'E assert 0' + ] + + + def test_repr_source_not_existing(self): + pr = FormattedExcinfo() + co = compile("raise ValueError()", "", "exec") + try: + exec co + except ValueError: + excinfo = py.code.ExceptionInfo() + repr = pr.repr_excinfo(excinfo) + assert repr.reprtraceback.reprentries[1].lines[0] == "> ???" + + def test_repr_local(self): + p = FormattedExcinfo(showlocals=True) + loc = {'y': 5, 'z': 7, 'x': 3, '__builtins__': __builtins__} + reprlocals = p.repr_locals(loc) + assert reprlocals.lines + print reprlocals.lines + assert reprlocals.lines[0] == '__builtins__ = ' + assert reprlocals.lines[1] == 'x = 3' + assert reprlocals.lines[2] == 'y = 5' + assert reprlocals.lines[3] == 'z = 7' + + def test_repr_tracebackentry_lines(self): + mod = self.importasmod(""" + def func1(): + raise ValueError("hello\\nworld") + """) + excinfo = py.test.raises(ValueError, mod.func1) + excinfo.traceback = excinfo.traceback.filter() + p = FormattedExcinfo() + reprtb = p.repr_traceback_entry(excinfo.traceback[-1]) + print reprtb + + # test as intermittent entry + lines = reprtb.lines + assert lines[0] == ' def func1():' + assert lines[1] == '> raise ValueError("hello\\nworld")' + + # test as last entry + p = FormattedExcinfo(showlocals=True) + repr_entry = p.repr_traceback_entry(excinfo.traceback[-1], excinfo) + lines = repr_entry.lines + assert lines[0] == ' def func1():' + assert lines[1] == '> raise ValueError("hello\\nworld")' + assert lines[2] == 'E ValueError: hello' + assert lines[3] == 'E world' + assert not lines[4:] + + loc = repr_entry.reprlocals is not None + loc = repr_entry.reprfileloc + assert loc.path == mod.__file__ + assert loc.lineno == 3 + #assert loc.message == "ValueError: hello" + + def test_repr_tracebackentry_lines(self): + mod = self.importasmod(""" + def func1(m, x, y, z): + raise ValueError("hello\\nworld") + """) + excinfo = py.test.raises(ValueError, mod.func1, "m"*90, 5, 13, "z"*120) + excinfo.traceback = excinfo.traceback.filter() + entry = excinfo.traceback[-1] + p = FormattedExcinfo(funcargs=True) + reprfuncargs = p.repr_args(entry) + assert reprfuncargs.args[0] == ('m', repr("m"*90)) + assert reprfuncargs.args[1] == ('x', '5') + assert reprfuncargs.args[2] == ('y', '13') + assert reprfuncargs.args[3] == ('z', repr("z" * 120)) + + p = FormattedExcinfo(funcargs=True) + repr_entry = p.repr_traceback_entry(entry) + assert repr_entry.reprfuncargs.args == reprfuncargs.args + tw = TWMock() + repr_entry.toterminal(tw) + assert tw.lines[0] == "m = " + repr('m' * 90) + assert tw.lines[1] == "x = 5, y = 13" + assert tw.lines[2] == "z = " + repr('z' * 120) + + def test_repr_tracebackentry_short(self): + mod = self.importasmod(""" + def func1(): + raise ValueError("hello") + def entry(): + func1() + """) + excinfo = py.test.raises(ValueError, mod.entry) + p = FormattedExcinfo(style="short") + reprtb = p.repr_traceback_entry(excinfo.traceback[-2]) + lines = reprtb.lines + basename = py.path.local(mod.__file__).basename + assert lines[0] == ' File "%s", line 5, in entry' % basename + assert lines[1] == ' func1()' + + # test last entry + p = FormattedExcinfo(style="short") + reprtb = p.repr_traceback_entry(excinfo.traceback[-1], excinfo) + lines = reprtb.lines + assert lines[0] == ' File "%s", line 3, in func1' % basename + assert lines[1] == ' raise ValueError("hello")' + assert lines[2] == 'E ValueError: hello' + + def test_repr_tracebackentry_no(self): + mod = self.importasmod(""" + def func1(): + raise ValueError("hello") + def entry(): + func1() + """) + excinfo = py.test.raises(ValueError, mod.entry) + p = FormattedExcinfo(style="no") + p.repr_traceback_entry(excinfo.traceback[-2]) + + p = FormattedExcinfo(style="no") + reprentry = p.repr_traceback_entry(excinfo.traceback[-1], excinfo) + lines = reprentry.lines + assert lines[0] == 'E ValueError: hello' + assert not lines[1:] + + def test_repr_traceback_tbfilter(self): + mod = self.importasmod(""" + def f(x): + raise ValueError(x) + def entry(): + f(0) + """) + excinfo = py.test.raises(ValueError, mod.entry) + p = FormattedExcinfo(tbfilter=True) + reprtb = p.repr_traceback(excinfo) + assert len(reprtb.reprentries) == 2 + p = FormattedExcinfo(tbfilter=False) + reprtb = p.repr_traceback(excinfo) + assert len(reprtb.reprentries) == 3 + + def test_repr_traceback_and_excinfo(self): + mod = self.importasmod(""" + def f(x): + raise ValueError(x) + def entry(): + f(0) + """) + excinfo = py.test.raises(ValueError, mod.entry) + + for style in ("long", "short"): + p = FormattedExcinfo(style=style) + reprtb = p.repr_traceback(excinfo) + assert len(reprtb.reprentries) == 2 + assert reprtb.style == style + assert not reprtb.extraline + repr = p.repr_excinfo(excinfo) + assert repr.reprtraceback + assert len(repr.reprtraceback.reprentries) == len(reprtb.reprentries) + assert repr.reprcrash.path.endswith("mod.py") + assert repr.reprcrash.message == "ValueError: 0" + + def test_repr_excinfo_addouterr(self): + mod = self.importasmod(""" + def entry(): + raise ValueError() + """) + excinfo = py.test.raises(ValueError, mod.entry) + repr = excinfo.getrepr() + repr.addsection("title", "content") + twmock = TWMock() + repr.toterminal(twmock) + assert twmock.lines[-1] == "content" + assert twmock.lines[-2] == ("-", "title") + + def test_repr_excinfo_reprcrash(self): + mod = self.importasmod(""" + def entry(): + raise ValueError() + """) + excinfo = py.test.raises(ValueError, mod.entry) + repr = excinfo.getrepr() + assert repr.reprcrash.path.endswith("mod.py") + assert repr.reprcrash.lineno == 3 + assert repr.reprcrash.message == "ValueError" + assert str(repr.reprcrash).endswith("mod.py:3: ValueError") + + def test_repr_traceback_recursion(self): + mod = self.importasmod(""" + def rec2(x): + return rec1(x+1) + def rec1(x): + return rec2(x-1) + def entry(): + rec1(42) + """) + excinfo = py.test.raises(RuntimeError, mod.entry) + + for style in ("short", "long", "no"): + p = FormattedExcinfo(style="short") + reprtb = p.repr_traceback(excinfo) + assert reprtb.extraline == "!!! Recursion detected (same locals & position)" + assert str(reprtb) + + def test_tb_entry_AssertionError(self): + # probably this test is a bit redundant + # as py/magic/testing/test_assertion.py + # already tests correctness of + # assertion-reinterpretation logic + mod = self.importasmod(""" + def somefunc(): + x = 1 + assert x == 2 + """) + py.magic.invoke(assertion=True) + try: + excinfo = py.test.raises(AssertionError, mod.somefunc) + finally: + py.magic.revoke(assertion=True) + + p = FormattedExcinfo() + reprentry = p.repr_traceback_entry(excinfo.traceback[-1], excinfo) + lines = reprentry.lines + assert lines[-1] == "E assert 1 == 2" + + def test_reprexcinfo_getrepr(self): + mod = self.importasmod(""" + def f(x): + raise ValueError(x) + def entry(): + f(0) + """) + excinfo = py.test.raises(ValueError, mod.entry) + + for style in ("short", "long", "no"): + for showlocals in (True, False): + repr = excinfo.getrepr(style=style, showlocals=showlocals) + assert isinstance(repr, ReprExceptionInfo) + assert repr.reprtraceback.style == style + + def test_toterminal_long(self): + mod = self.importasmod(""" + def g(x): + raise ValueError(x) + def f(): + g(3) + """) + excinfo = py.test.raises(ValueError, mod.f) + excinfo.traceback = excinfo.traceback.filter() + repr = excinfo.getrepr() + tw = TWMock() + repr.toterminal(tw) + assert tw.lines[0] == "" + tw.lines.pop(0) + assert tw.lines[0] == " def f():" + assert tw.lines[1] == "> g(3)" + assert tw.lines[2] == "" + assert tw.lines[3].endswith("mod.py:5: ") + assert tw.lines[4] == ("_ ", None) + assert tw.lines[5] == "" + assert tw.lines[6] == " def g(x):" + assert tw.lines[7] == "> raise ValueError(x)" + assert tw.lines[8] == "E ValueError: 3" + assert tw.lines[9] == "" + assert tw.lines[10].endswith("mod.py:3: ValueError") + + + def test_format_excinfo(self): + mod = self.importasmod(""" + def g(x): + raise ValueError(x) + def f(): + g(3) + """) + excinfo = py.test.raises(ValueError, mod.f) + def format_and_str(kw): + tw = py.io.TerminalWriter(stringio=True) + repr = excinfo.getrepr(**kw) + repr.toterminal(tw) + assert tw.stringio.getvalue() + + for combo in self.allcombos(): + yield format_and_str, combo + + def allcombos(self): + for style in ("long", "short", "no"): + for showlocals in (True, False): + for tbfilter in (True, False): + for funcargs in (True, False): + kw = {'style': style, + 'showlocals': showlocals, + 'funcargs': funcargs, + 'tbfilter': tbfilter + } + yield kw + diff --git a/py/code/testing/test_frame.py b/py/code/testing/test_frame.py new file mode 100644 index 000000000..edd49cdd0 --- /dev/null +++ b/py/code/testing/test_frame.py @@ -0,0 +1,15 @@ +import sys +import py + +def test_frame_getsourcelineno_myself(): + def func(): + return sys._getframe(0) + f = func() + f = py.code.Frame(f) + source, lineno = f.code.fullsource, f.lineno + assert source[lineno].startswith(" return sys._getframe(0)") + +def test_code_from_func(): + co = py.code.Code(test_frame_getsourcelineno_myself) + assert co.firstlineno + assert co.path diff --git a/py/code/testing/test_safe_repr.py b/py/code/testing/test_safe_repr.py new file mode 100644 index 000000000..31d06d804 --- /dev/null +++ b/py/code/testing/test_safe_repr.py @@ -0,0 +1,34 @@ + +import py +from py.__.code import safe_repr + +def test_simple_repr(): + assert safe_repr._repr(1) == '1' + assert safe_repr._repr(None) == 'None' + +class BrokenRepr: + def __init__(self, ex): + self.ex = ex + foo = 0 + def __repr__(self): + raise self.ex + +def test_exception(): + assert 'Exception' in safe_repr._repr(BrokenRepr(Exception("broken"))) + +class BrokenReprException(Exception): + __str__ = None + __repr__ = None + +def test_broken_exception(): + assert 'Exception' in safe_repr._repr(BrokenRepr(BrokenReprException("really broken"))) + +def test_string_exception(): + assert 'unknown' in safe_repr._repr(BrokenRepr("string")) + +def test_big_repr(): + assert len(safe_repr._repr(range(1000))) <= \ + len('[' + safe_repr.SafeRepr().maxlist * "1000" + ']') + + + diff --git a/py/code/testing/test_source.py b/py/code/testing/test_source.py new file mode 100644 index 000000000..b19f46d23 --- /dev/null +++ b/py/code/testing/test_source.py @@ -0,0 +1,316 @@ +from py.code import Source +import py +import sys + +def test_source_str_function(): + x = Source("3") + assert str(x) == "3" + + x = Source(" 3") + assert str(x) == "3" + + x = Source(""" + 3 + """, rstrip=False) + assert str(x) == "\n3\n " + + x = Source(""" + 3 + """, rstrip=True) + assert str(x) == "\n3" + +def test_unicode(): + x = Source(unicode("4")) + assert str(x) == "4" + + +def test_source_from_function(): + source = py.code.Source(test_source_str_function) + assert str(source).startswith('def test_source_str_function():') + +def test_source_from_inner_function(): + def f(): + pass + source = py.code.Source(f, deindent=False) + assert str(source).startswith(' def f():') + source = py.code.Source(f) + assert str(source).startswith('def f():') + +def test_source_putaround_simple(): + source = Source("raise ValueError") + source = source.putaround( + "try:", """\ + except ValueError: + x = 42 + else: + x = 23""") + assert str(source)=="""\ +try: + raise ValueError +except ValueError: + x = 42 +else: + x = 23""" + +def test_source_putaround(): + source = Source() + source = source.putaround(""" + if 1: + x=1 + """) + assert str(source).strip() == "if 1:\n x=1" + +def test_source_strips(): + source = Source("") + assert source == Source() + assert str(source) == '' + assert source.strip() == source + +def test_source_strip_multiline(): + source = Source() + source.lines = ["", " hello", " "] + source2 = source.strip() + assert source2.lines == [" hello"] + +def test_syntaxerror_rerepresentation(): + ex = py.test.raises(SyntaxError, py.code.compile, 'x x') + assert ex.value.lineno == 1 + assert ex.value.offset == 3 + assert ex.value.text.strip(), 'x x' + +def test_isparseable(): + assert Source("hello").isparseable() + assert Source("if 1:\n pass").isparseable() + assert Source(" \nif 1:\n pass").isparseable() + assert not Source("if 1:\n").isparseable() + assert not Source(" \nif 1:\npass").isparseable() + +class TestAccesses: + source = Source("""\ + def f(x): + pass + def g(x): + pass + """) + def test_getrange(self): + x = self.source[0:2] + assert x.isparseable() + assert len(x.lines) == 2 + assert str(x) == "def f(x):\n pass" + + def test_getline(self): + x = self.source[0] + assert x == "def f(x):" + + def test_len(self): + assert len(self.source) == 4 + + def test_iter(self): + l = [x for x in self.source] + assert len(l) == 4 + +class TestSourceParsingAndCompiling: + source = Source("""\ + def f(x): + assert (x == + 3 + + 4) + """).strip() + + def test_compile(self): + co = py.code.compile("x=3") + exec co + assert x == 3 + + def test_compile_unicode(self): + co = py.code.compile(unicode('u"\xc3\xa5"', 'utf8'), mode='eval') + val = eval(co) + assert isinstance(val, unicode) + + def test_compile_and_getsource_simple(self): + co = py.code.compile("x=3") + exec co + source = py.code.Source(co) + assert str(source) == "x=3" + + def test_getstatement(self): + #print str(self.source) + ass = str(self.source[1:]) + for i in range(1, 4): + #print "trying start in line %r" % self.source[i] + s = self.source.getstatement(i) + #x = s.deindent() + assert str(s) == ass + + def test_getstatementrange_within_constructs(self): + source = Source("""\ + try: + try: + raise ValueError + except SomeThing: + pass + finally: + 42 + """) + assert len(source) == 7 + assert source.getstatementrange(0) == (0, 7) + assert source.getstatementrange(1) == (1, 5) + assert source.getstatementrange(2) == (2, 3) + assert source.getstatementrange(3) == (1, 5) + assert source.getstatementrange(4) == (4, 5) + assert source.getstatementrange(5) == (0, 7) + assert source.getstatementrange(6) == (6, 7) + + def test_getstatementrange_bug(self): + source = Source("""\ + try: + x = ( + y + + z) + except: + pass + """) + assert len(source) == 6 + assert source.getstatementrange(2) == (1, 4) + + def test_getstatementrange_bug2(self): + py.test.skip("fix me (issue19)") + source = Source("""\ + assert ( + 33 + == + [ + X(3, + b=1, c=2 + ), + ] + ) + """) + assert len(source) == 9 + assert source.getstatementrange(5) == (0, 9) + + def test_compile_and_getsource(self): + co = self.source.compile() + exec co + f(7) + excinfo = py.test.raises(AssertionError, "f(6)") + frame = excinfo.traceback[-1].frame + stmt = frame.code.fullsource.getstatement(frame.lineno) + #print "block", str(block) + assert str(stmt).strip().startswith('assert') + + def test_compilefuncs_and_path_sanity(self): + def check(comp, name): + co = comp(self.source, name) + if not name: + expected = "" %(mypath, mylineno+2+1) + else: + expected = "" % (name, mypath, mylineno+2+1) + fn = co.co_filename + assert fn == expected + + mycode = py.code.Code(self.test_compilefuncs_and_path_sanity) + mylineno = mycode.firstlineno + mypath = mycode.path + + for comp in py.code.compile, py.code.Source.compile: + for name in '', None, 'my': + yield check, comp, name + + def test_offsetless_synerr(self): + py.test.raises(SyntaxError, py.code.compile, "lambda a,a: 0", mode='eval') + +def test_getstartingblock_singleline(): + class A: + def __init__(self, *args): + frame = sys._getframe(1) + self.source = py.code.Frame(frame).statement + + x = A('x', 'y') + + l = [i for i in x.source.lines if i.strip()] + assert len(l) == 1 + +def test_getstartingblock_multiline(): + class A: + def __init__(self, *args): + frame = sys._getframe(1) + self.source = py.code.Frame(frame).statement + + x = A('x', + 'y' \ + , + 'z') + + l = [i for i in x.source.lines if i.strip()] + assert len(l) == 4 + +def test_getline_finally(): + #py.test.skip("inner statements cannot be located yet.") + def c(): pass + excinfo = py.test.raises(TypeError, """ + teardown = None + try: + c(1) + finally: + if teardown: + teardown() + """) + source = excinfo.traceback[-1].statement + assert str(source).strip() == 'c(1)' + +def test_getfuncsource_dynamic(): + source = """ + def f(): + raise ValueError + + def g(): pass + """ + co = py.code.compile(source) + exec co + assert str(py.code.Source(f)).strip() == 'def f():\n raise ValueError' + assert str(py.code.Source(g)).strip() == 'def g(): pass' + + +def test_getfuncsource_with_multine_string(): + def f(): + c = '''while True: + pass +''' + assert str(py.code.Source(f)).strip() == "def f():\n c = '''while True:\n pass\n'''" + + +def test_deindent(): + from py.__.code.source import deindent as deindent + assert deindent(['\tfoo', '\tbar', ]) == ['foo', 'bar'] + + def f(): + c = '''while True: + pass +''' + import inspect + lines = deindent(inspect.getsource(f).splitlines()) + assert lines == ["def f():", " c = '''while True:", " pass", "'''"] + + source = """ + def f(): + def g(): + pass + """ + lines = deindent(source.splitlines()) + assert lines == ['', 'def f():', ' def g():', ' pass', ' '] + +def test_source_of_class_at_eof_without_newline(): + py.test.skip("CPython's inspect.getsource is buggy") + # this test fails because the implicit inspect.getsource(A) below + # does not return the "x = 1" last line. + tmpdir = py.test.ensuretemp("source_write_read") + source = py.code.Source(''' + class A(object): + def method(self): + x = 1 + ''') + path = tmpdir.join("a.py") + path.write(source) + s2 = py.code.Source(tmpdir.join("a.py").pyimport().A) + assert str(source).strip() == str(s2).strip() diff --git a/py/code/traceback2.py b/py/code/traceback2.py new file mode 100644 index 000000000..bf901162f --- /dev/null +++ b/py/code/traceback2.py @@ -0,0 +1,200 @@ +from __future__ import generators +import py +import sys + +class TracebackEntry(object): + """ a single entry in a traceback """ + + exprinfo = None + + def __init__(self, rawentry): + self._rawentry = rawentry + self.frame = py.code.Frame(rawentry.tb_frame) + # Ugh. 2.4 and 2.5 differs here when encountering + # multi-line statements. Not sure about the solution, but + # should be portable + self.lineno = rawentry.tb_lineno - 1 + self.relline = self.lineno - self.frame.code.firstlineno + + def __repr__(self): + return "" %(self.frame.code.path, self.lineno+1) + + def statement(self): + """ return a py.code.Source object for the current statement """ + source = self.frame.code.fullsource + return source.getstatement(self.lineno) + statement = property(statement, None, None, + "statement of this traceback entry.") + + def path(self): + return self.frame.code.path + path = property(path, None, None, "path to the full source code") + + def getlocals(self): + return self.frame.f_locals + locals = property(getlocals, None, None, "locals of underlaying frame") + + def reinterpret(self): + """Reinterpret the failing statement and returns a detailed information + about what operations are performed.""" + if self.exprinfo is None: + from py.__.magic import exprinfo + source = str(self.statement).strip() + x = exprinfo.interpret(source, self.frame, should_fail=True) + if not isinstance(x, str): + raise TypeError, "interpret returned non-string %r" % (x,) + self.exprinfo = x + return self.exprinfo + + def getfirstlinesource(self): + return self.frame.code.firstlineno + + def getsource(self): + """ return failing source code. """ + source = self.frame.code.fullsource + if source is None: + try: + sourcelines, lineno = py.std.inspect.findsource(self.frame.code.raw) + except IOError: + return None + source = py.code.Source() + source.lines = map(str.rstrip, sourcelines) + start = self.getfirstlinesource() + end = self.lineno + try: + _, end = source.getstatementrange(end) + except IndexError: + end = self.lineno + 1 + # heuristic to stop displaying source on e.g. + # if something: # assume this causes a NameError + # # _this_ lines and the one + # below we don't want from entry.getsource() + for i in range(self.lineno, end): + if source[i].rstrip().endswith(':'): + end = i + 1 + break + return source[start:end] + source = property(getsource) + + def ishidden(self): + """ return True if the current frame has a var __tracebackhide__ + resolving to True + + mostly for internal use + """ + try: + return self.frame.eval("__tracebackhide__") + except (SystemExit, KeyboardInterrupt): + raise + except: + return False + + def __str__(self): + try: + fn = str(self.path) + except py.error.Error: + fn = '???' + name = self.frame.code.name + try: + line = str(self.statement).lstrip() + except KeyboardInterrupt: + raise + except: + line = "???" + return " File %r:%d in %s\n %s\n" %(fn, self.lineno+1, name, line) + + def name(self): + return self.frame.code.raw.co_name + name = property(name, None, None, "co_name of underlaying code") + +class Traceback(list): + """ Traceback objects encapsulate and offer higher level + access to Traceback entries. + """ + Entry = TracebackEntry + def __init__(self, tb): + """ initialize from given python traceback object. """ + if hasattr(tb, 'tb_next'): + def f(cur): + while cur is not None: + yield self.Entry(cur) + cur = cur.tb_next + list.__init__(self, f(tb)) + else: + list.__init__(self, tb) + + def cut(self, path=None, lineno=None, firstlineno=None): + """ return a Traceback instance wrapping part of this Traceback + + by provding any combination of path, lineno and firstlineno, the + first frame to start the to-be-returned traceback is determined + + this allows cutting the first part of a Traceback instance e.g. + for formatting reasons (removing some uninteresting bits that deal + with handling of the exception/traceback) + """ + for x in self: + if ((path is None or x.frame.code.path == path) and + (lineno is None or x.lineno == lineno) and + (firstlineno is None or x.frame.code.firstlineno == firstlineno)): + return Traceback(x._rawentry) + return self + + def __getitem__(self, key): + val = super(Traceback, self).__getitem__(key) + if isinstance(key, type(slice(0))): + val = self.__class__(val) + return val + + def filter(self, fn=lambda x: not x.ishidden()): + """ return a Traceback instance with certain items removed + + fn is a function that gets a single argument, a TracebackItem + instance, and should return True when the item should be added + to the Traceback, False when not + + by default this removes all the TracebackItems which are hidden + (see ishidden() above) + """ + return Traceback(filter(fn, self)) + + def getcrashentry(self): + """ return last non-hidden traceback entry that lead + to the exception of a traceback. + """ + tb = self.filter() + if not tb: + tb = self + return tb[-1] + + def recursionindex(self): + """ return the index of the frame/TracebackItem where recursion + originates if appropriate, None if no recursion occurred + """ + cache = {} + for i, entry in py.builtin.enumerate(self): + key = entry.frame.code.path, entry.lineno + #print "checking for recursion at", key + l = cache.setdefault(key, []) + if l: + f = entry.frame + loc = f.f_locals + for otherloc in l: + if f.is_true(f.eval(co_equal, + __recursioncache_locals_1=loc, + __recursioncache_locals_2=otherloc)): + return i + l.append(entry.frame.f_locals) + return None + +# def __str__(self): +# for x in self +# l = [] +## for func, entry in self._tblist: +# l.append(entry.display()) +# return "".join(l) + + +co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2', + '?', 'eval') + diff --git a/py/compat/conftest.py b/py/compat/conftest.py index aa79e2b0f..fccc62bce 100644 --- a/py/compat/conftest.py +++ b/py/compat/conftest.py @@ -1,5 +1,5 @@ import py class Directory(py.test.collect.Directory): - def run(self): + def listdir(self): py.test.skip("compat tests currently need to be run manually") diff --git a/py/doc/TODO.txt b/py/doc/TODO.txt new file mode 100644 index 000000000..4873776d4 --- /dev/null +++ b/py/doc/TODO.txt @@ -0,0 +1,577 @@ +Things to do for 1.0.0 +========================= + +py.test +-------------- + +- BUG: write test/fix --showlocals (not showing anything) + +- review and refactor architecture of py.test with particular + respect to: + - allow custom reporting + - writing (stacked) extensions / plugins (compared to Nose) + - event naming and processing + - porting existing extensions (htmlconftest / buildbot / PyPy's conftest's ...) + - fast and stable distributed testing + - reliable cross-platform testing + +- fix reporting/usage degradation after reporter-merge merge: + - collapse skips with same reason and lineno into one line + +- fix and investigate win32 failures + +- (needs review) adjust py.test documentation to reflect new + collector/session architecture + +- document py.test's conftest.py approach + +- review and optimize skip-handling (it can be quite slow in + certain situations because e.g. setup/teardown is fully performed + although we have "skip by keyword" and could detect this early) + +py.execnet +-------------- + +- cross-python version (2.2/2.3-2.5/6) and cross-platform testing of + setup/teardown semantics + +- optimize general setup and rsync timing? + +py.apigen +---------------- + +- refactor to produce intermediate data/files capturing + info of test runs +- refactor html renderer to work on intermediate + data/files rather than on the live data + +- check out CodeInvestigator + http://codeinvestigator.googlepages.com/main + + +ld (review and shift to above) +================================= + +refactorings +------------------ + +- session / collection unification (particularly tryiter and buildname2items) + +- reporting unification, i.e. use dist-testing Reporter class + also for "normal" session, consider introduction of tkinter + session (M978) + +- refine doctests usage (particularly skips of doctests if + some imports/conditions are not satisfied) + +- generalization of "host specifications" for execnet and + py.test --dist usages in particular (see also revision 37500 which + contained a draft for that). The goal is to have cross-platform + testing and dist-testing and other usages of py.execnet all + use a common syntax for specifiying connection methods and + be able to instantiate gateways/connections through it. + +- unification of "gateway"/host setup and teardown, including + rsyncing, i.e. cross-platform and dist-testing. + +- py.apigen tool -> separate runtime-data collection and + web page generation. (see M750), provide "py.apigen" tool + for generating API documentation + +- py.log: unify API, possibly deprecate duplicate ones, + base things on a Config object (hte latter almost a feature though) + (M988) + +- consider setup/teardown for generative tests (M826) + +- fix teardown problems regarding when teardown is done (should be done + after test run, not before the next one) + +features +-------------- + +- have a py.test scan/run database for results and test names + etc. (to allow quicker selection of tests and post-run + information on failures etc.) (M760) + +- consider features of py.apigen (recheck closed "M1016") + +- integrate rlcompleter2 (make it remotely workable) + and maybe integrate with "pdb" / pdbplus (M975) + +- integrate native collecting of unittest.py tests from py.test + (along the PyPy lib-python tests) (M987) + +- provide an automated conversion script helper for converting + unittest.py based tests to py.test ones. (M987) + +- references from ReST docs to modules, functions and classes + of apigen generated html docs (M960) + +- review svn-testing (and escape characters), consider + svn-bindings (M634) + + +packaging +------------------------------------- + +* debian and TAR/zip packages for py lib, + particularly look into C module issues (greenlet most importantly) + +* do something about c-extensions both on unix-ish + versus win32 systems + +* ensure compatibility with Python 2.3 - 2.5, + see what is missing for 2.2 + +* optional: support setuptools (eggs?) installs, and instally + from pypi (and register pylib there) + +* (DONE/c-modules don't) see if things work on Win32 (partially done) + +* (partly DONE) refine and implement releasescheme/download + + +APIGEN / source viewer +------------------------------------- + +* (DONE, XXX functions/methods?) integrate rest directive into + py/documentation/conftest.py + with help code from py.__.rest.directive.... + make sure that the txt files in py/documentation/ use it + +testing +----------- + +* these should all work on 1.0 and on the py lib and pypy: + - running "py.test -s" + - running "py.test --pdb" + - running "py.test --looponfailing" + - running "py.test" distributed on some hosts + + (guido tested all on win32, everything works except --dist (requires + os.fork to work)) + +code quality +----------------- + +* no function implementation longer than 30 lines + +* no lines longer than 80 characters + +* review the pylib issue tracker + (cfbolz: done: what has a 1.0.0 tag (or lower) should be looked at again) + +py.test +------- + +* (postponed, likely) py.test fails to parse strangely formatted code after assertion failure + +Missing docstrings +------------------ + +:: + + code.Traceback.recursionindex misses a docstring + code.Traceback.filter misses a docstring + code.Traceback.cut misses a docstring + code.Traceback.__getitem__ misses a docstring + code.Traceback.Entry misses a docstring + code.Traceback.Entry.ishidden misses a docstring + code.Traceback.Entry.getfirstlinesource misses a docstring + code.Traceback.Entry.__str__ misses a docstring + code.Traceback.Entry.__repr__ misses a docstring + code.Traceback.Entry.__init__ misses a docstring + code.Source.getblockend misses a docstring + code.Source.__str__ misses a docstring + code.Source.__len__ misses a docstring + code.Source.__init__ misses a docstring + code.Source.__getslice__ misses a docstring + code.Source.__getitem__ misses a docstring + code.Source.__eq__ misses a docstring + code.Frame.repr misses a docstring + code.Frame.is_true misses a docstring + code.Frame.getargs misses a docstring + code.Frame.exec_ misses a docstring + code.Frame.eval misses a docstring + code.Frame.__init__ misses a docstring + code.ExceptionInfo.exconly misses a docstring + code.ExceptionInfo.errisinstance misses a docstring + code.ExceptionInfo.__str__ misses a docstring + code.ExceptionInfo.__init__ misses a docstring + code.Code misses a docstring + code.Code.source misses a docstring + code.Code.getargs misses a docstring + code.Code.__init__ misses a docstring + code.Code.__eq__ misses a docstring + execnet.SshGateway misses a docstring + execnet.SshGateway.join misses a docstring + execnet.SshGateway.exit misses a docstring + execnet.SshGateway.__repr__ misses a docstring + execnet.SshGateway.__init__ misses a docstring + execnet.SshGateway.ThreadOut.write misses a docstring + execnet.SshGateway.ThreadOut.setwritefunc misses a docstring + execnet.SshGateway.ThreadOut.setdefaultwriter misses a docstring + execnet.SshGateway.ThreadOut.resetdefault misses a docstring + execnet.SshGateway.ThreadOut.isatty misses a docstring + execnet.SshGateway.ThreadOut.flush misses a docstring + execnet.SshGateway.ThreadOut.delwritefunc misses a docstring + execnet.SshGateway.ThreadOut.deinstall misses a docstring + execnet.SocketGateway misses a docstring + execnet.SocketGateway.join misses a docstring + execnet.SocketGateway.exit misses a docstring + execnet.SocketGateway.__repr__ misses a docstring + execnet.SocketGateway.__init__ misses a docstring + execnet.PopenGateway misses a docstring + execnet.PopenGateway.remote_bootstrap_gateway misses a docstring + execnet.PopenGateway.join misses a docstring + execnet.PopenGateway.exit misses a docstring + execnet.PopenGateway.__repr__ misses a docstring + execnet.PopenGateway.__init__ misses a docstring + initpkg misses a docstring + log.setconsumer misses a docstring + log.get misses a docstring + log.Syslog misses a docstring + log.STDOUT misses a docstring + log.STDERR misses a docstring + log.Producer.set_consumer misses a docstring + log.Producer.get_consumer misses a docstring + log.Producer.__repr__ misses a docstring + log.Producer.__init__ misses a docstring + log.Producer.__getattr__ misses a docstring + log.Producer.__call__ misses a docstring + log.Producer.Message misses a docstring + log.Producer.Message.prefix misses a docstring + log.Producer.Message.content misses a docstring + log.Producer.Message.__str__ misses a docstring + log.Producer.Message.__init__ misses a docstring + log.Path misses a docstring + log.Path.__init__ misses a docstring + log.Path.__call__ misses a docstring + magic.View.__viewkey__ misses a docstring + magic.View.__repr__ misses a docstring + magic.View.__new__ misses a docstring + magic.View.__matchkey__ misses a docstring + magic.View.__getattr__ misses a docstring + magic.AssertionError misses a docstring + path.svnwc.visit misses a docstring + path.svnwc.mkdir misses a docstring + path.svnwc.dump misses a docstring + path.svnwc.check misses a docstring + path.svnwc.add misses a docstring + path.svnwc.__str__ misses a docstring + path.svnwc.__repr__ misses a docstring + path.svnwc.__new__ misses a docstring + path.svnwc.__iter__ misses a docstring + path.svnwc.__hash__ misses a docstring + path.svnwc.__eq__ misses a docstring + path.svnwc.__div__ misses a docstring + path.svnwc.__contains__ misses a docstring + path.svnwc.__cmp__ misses a docstring + path.svnwc.__add__ misses a docstring + path.svnwc.Checkers misses a docstring + path.svnurl.visit misses a docstring + path.svnurl.check misses a docstring + path.svnurl.__repr__ misses a docstring + path.svnurl.__new__ misses a docstring + path.svnurl.__ne__ misses a docstring + path.svnurl.__iter__ misses a docstring + path.svnurl.__hash__ misses a docstring + path.svnurl.__div__ misses a docstring + path.svnurl.__contains__ misses a docstring + path.svnurl.__cmp__ misses a docstring + path.svnurl.__add__ misses a docstring + path.svnurl.Checkers misses a docstring + path.local.visit misses a docstring + path.local.sysexec has an 'XXX' in its docstring + path.local.check misses a docstring + path.local.__repr__ misses a docstring + path.local.__iter__ misses a docstring + path.local.__hash__ misses a docstring + path.local.__eq__ misses a docstring + path.local.__div__ misses a docstring + path.local.__contains__ misses a docstring + path.local.__cmp__ misses a docstring + path.local.__add__ misses a docstring + path.local.Checkers misses a docstring + test.rest.RestReporter misses a docstring + test.rest.RestReporter.summary misses a docstring + test.rest.RestReporter.skips misses a docstring + test.rest.RestReporter.repr_traceback misses a docstring + test.rest.RestReporter.repr_source misses a docstring + test.rest.RestReporter.repr_signal misses a docstring + test.rest.RestReporter.repr_failure misses a docstring + test.rest.RestReporter.report_unknown misses a docstring + test.rest.RestReporter.report_TestStarted misses a docstring + test.rest.RestReporter.report_TestFinished misses a docstring + test.rest.RestReporter.report_SkippedTryiter misses a docstring + test.rest.RestReporter.report_SendItem misses a docstring + test.rest.RestReporter.report_RsyncFinished misses a docstring + test.rest.RestReporter.report_ReceivedItemOutcome misses a docstring + test.rest.RestReporter.report_Nodes misses a docstring + test.rest.RestReporter.report_ItemStart misses a docstring + test.rest.RestReporter.report_ImmediateFailure misses a docstring + test.rest.RestReporter.report_HostReady misses a docstring + test.rest.RestReporter.report_HostRSyncing misses a docstring + test.rest.RestReporter.report_FailedTryiter misses a docstring + test.rest.RestReporter.report misses a docstring + test.rest.RestReporter.print_summary misses a docstring + test.rest.RestReporter.prepare_source misses a docstring + test.rest.RestReporter.hangs misses a docstring + test.rest.RestReporter.get_rootpath misses a docstring + test.rest.RestReporter.get_path_from_item misses a docstring + test.rest.RestReporter.get_linkwriter misses a docstring + test.rest.RestReporter.get_item_name misses a docstring + test.rest.RestReporter.get_host misses a docstring + test.rest.RestReporter.failures misses a docstring + test.rest.RestReporter.add_rest misses a docstring + test.rest.RestReporter.__init__ misses a docstring + test.rest.RelLinkWriter misses a docstring + test.rest.RelLinkWriter.get_link misses a docstring + test.rest.NoLinkWriter misses a docstring + test.rest.NoLinkWriter.get_link misses a docstring + test.rest.LinkWriter misses a docstring + test.rest.LinkWriter.get_link misses a docstring + test.rest.LinkWriter.__init__ misses a docstring + test.exit misses a docstring + test.deprecated_call misses a docstring + test.collect.Module misses a docstring + test.collect.Module.tryiter has an 'XXX' in its docstring + test.collect.Module.teardown misses a docstring + test.collect.Module.startcapture misses a docstring + test.collect.Module.skipbykeyword misses a docstring + test.collect.Module.setup misses a docstring + test.collect.Module.run misses a docstring + test.collect.Module.makeitem misses a docstring + test.collect.Module.listnames misses a docstring + test.collect.Module.join misses a docstring + test.collect.Module.haskeyword misses a docstring + test.collect.Module.getsortvalue misses a docstring + test.collect.Module.getpathlineno misses a docstring + test.collect.Module.getouterr misses a docstring + test.collect.Module.getitembynames misses a docstring + test.collect.Module.funcnamefilter misses a docstring + test.collect.Module.finishcapture misses a docstring + test.collect.Module.classnamefilter misses a docstring + test.collect.Module.buildname2items misses a docstring + test.collect.Module.__repr__ misses a docstring + test.collect.Module.__ne__ misses a docstring + test.collect.Module.__init__ misses a docstring + test.collect.Module.__hash__ misses a docstring + test.collect.Module.__eq__ misses a docstring + test.collect.Module.__cmp__ misses a docstring + test.collect.Module.Skipped misses a docstring + test.collect.Module.Passed misses a docstring + test.collect.Module.Outcome misses a docstring + test.collect.Module.Failed misses a docstring + test.collect.Module.ExceptionFailure misses a docstring + test.collect.Instance misses a docstring + test.collect.Instance.tryiter has an 'XXX' in its docstring + test.collect.Instance.teardown misses a docstring + test.collect.Instance.startcapture misses a docstring + test.collect.Instance.skipbykeyword misses a docstring + test.collect.Instance.setup misses a docstring + test.collect.Instance.run misses a docstring + test.collect.Instance.makeitem misses a docstring + test.collect.Instance.listnames misses a docstring + test.collect.Instance.join misses a docstring + test.collect.Instance.haskeyword misses a docstring + test.collect.Instance.getsortvalue misses a docstring + test.collect.Instance.getpathlineno misses a docstring + test.collect.Instance.getouterr misses a docstring + test.collect.Instance.getitembynames misses a docstring + test.collect.Instance.funcnamefilter misses a docstring + test.collect.Instance.finishcapture misses a docstring + test.collect.Instance.classnamefilter misses a docstring + test.collect.Instance.buildname2items misses a docstring + test.collect.Instance.__repr__ misses a docstring + test.collect.Instance.__ne__ misses a docstring + test.collect.Instance.__init__ misses a docstring + test.collect.Instance.__hash__ misses a docstring + test.collect.Instance.__eq__ misses a docstring + test.collect.Instance.__cmp__ misses a docstring + test.collect.Generator misses a docstring + test.collect.Generator.tryiter has an 'XXX' in its docstring + test.collect.Generator.teardown misses a docstring + test.collect.Generator.startcapture misses a docstring + test.collect.Generator.skipbykeyword misses a docstring + test.collect.Generator.setup misses a docstring + test.collect.Generator.run misses a docstring + test.collect.Generator.listnames misses a docstring + test.collect.Generator.join misses a docstring + test.collect.Generator.haskeyword misses a docstring + test.collect.Generator.getsortvalue misses a docstring + test.collect.Generator.getpathlineno misses a docstring + test.collect.Generator.getouterr misses a docstring + test.collect.Generator.getitembynames misses a docstring + test.collect.Generator.getcallargs misses a docstring + test.collect.Generator.finishcapture misses a docstring + test.collect.Generator.buildname2items misses a docstring + test.collect.Generator.__repr__ misses a docstring + test.collect.Generator.__ne__ misses a docstring + test.collect.Generator.__init__ misses a docstring + test.collect.Generator.__hash__ misses a docstring + test.collect.Generator.__eq__ misses a docstring + test.collect.Generator.__cmp__ misses a docstring + test.collect.DoctestFile misses a docstring + test.collect.DoctestFile.tryiter has an 'XXX' in its docstring + test.collect.DoctestFile.teardown misses a docstring + test.collect.DoctestFile.startcapture misses a docstring + test.collect.DoctestFile.skipbykeyword misses a docstring + test.collect.DoctestFile.setup misses a docstring + test.collect.DoctestFile.run misses a docstring + test.collect.DoctestFile.makeitem misses a docstring + test.collect.DoctestFile.listnames misses a docstring + test.collect.DoctestFile.join misses a docstring + test.collect.DoctestFile.haskeyword misses a docstring + test.collect.DoctestFile.getsortvalue misses a docstring + test.collect.DoctestFile.getpathlineno misses a docstring + test.collect.DoctestFile.getouterr misses a docstring + test.collect.DoctestFile.getitembynames misses a docstring + test.collect.DoctestFile.funcnamefilter misses a docstring + test.collect.DoctestFile.finishcapture misses a docstring + test.collect.DoctestFile.classnamefilter misses a docstring + test.collect.DoctestFile.buildname2items misses a docstring + test.collect.DoctestFile.__repr__ misses a docstring + test.collect.DoctestFile.__ne__ misses a docstring + test.collect.DoctestFile.__init__ misses a docstring + test.collect.DoctestFile.__hash__ misses a docstring + test.collect.DoctestFile.__eq__ misses a docstring + test.collect.DoctestFile.__cmp__ misses a docstring + test.collect.Directory misses a docstring + test.collect.Directory.tryiter has an 'XXX' in its docstring + test.collect.Directory.teardown misses a docstring + test.collect.Directory.startcapture misses a docstring + test.collect.Directory.skipbykeyword misses a docstring + test.collect.Directory.setup misses a docstring + test.collect.Directory.run misses a docstring + test.collect.Directory.recfilter misses a docstring + test.collect.Directory.makeitem misses a docstring + test.collect.Directory.listnames misses a docstring + test.collect.Directory.join misses a docstring + test.collect.Directory.haskeyword misses a docstring + test.collect.Directory.getsortvalue misses a docstring + test.collect.Directory.getpathlineno misses a docstring + test.collect.Directory.getouterr misses a docstring + test.collect.Directory.getitembynames misses a docstring + test.collect.Directory.finishcapture misses a docstring + test.collect.Directory.filefilter misses a docstring + test.collect.Directory.buildname2items misses a docstring + test.collect.Directory.__repr__ misses a docstring + test.collect.Directory.__ne__ misses a docstring + test.collect.Directory.__init__ misses a docstring + test.collect.Directory.__hash__ misses a docstring + test.collect.Directory.__eq__ misses a docstring + test.collect.Directory.__cmp__ misses a docstring + test.collect.Collector misses a docstring + test.collect.Collector.tryiter has an 'XXX' in its docstring + test.collect.Collector.teardown misses a docstring + test.collect.Collector.startcapture misses a docstring + test.collect.Collector.skipbykeyword misses a docstring + test.collect.Collector.setup misses a docstring + test.collect.Collector.run misses a docstring + test.collect.Collector.listnames misses a docstring + test.collect.Collector.join misses a docstring + test.collect.Collector.haskeyword misses a docstring + test.collect.Collector.getsortvalue misses a docstring + test.collect.Collector.getpathlineno misses a docstring + test.collect.Collector.getouterr misses a docstring + test.collect.Collector.getitembynames misses a docstring + test.collect.Collector.finishcapture misses a docstring + test.collect.Collector.buildname2items misses a docstring + test.collect.Collector.__repr__ misses a docstring + test.collect.Collector.__ne__ misses a docstring + test.collect.Collector.__init__ misses a docstring + test.collect.Collector.__hash__ misses a docstring + test.collect.Collector.__eq__ misses a docstring + test.collect.Collector.__cmp__ misses a docstring + test.collect.Class misses a docstring + test.collect.Class.tryiter has an 'XXX' in its docstring + test.collect.Class.teardown misses a docstring + test.collect.Class.startcapture misses a docstring + test.collect.Class.skipbykeyword misses a docstring + test.collect.Class.setup misses a docstring + test.collect.Class.run misses a docstring + test.collect.Class.makeitem misses a docstring + test.collect.Class.listnames misses a docstring + test.collect.Class.join misses a docstring + test.collect.Class.haskeyword misses a docstring + test.collect.Class.getsortvalue misses a docstring + test.collect.Class.getpathlineno misses a docstring + test.collect.Class.getouterr misses a docstring + test.collect.Class.getitembynames misses a docstring + test.collect.Class.funcnamefilter misses a docstring + test.collect.Class.finishcapture misses a docstring + test.collect.Class.classnamefilter misses a docstring + test.collect.Class.buildname2items misses a docstring + test.collect.Class.__repr__ misses a docstring + test.collect.Class.__ne__ misses a docstring + test.collect.Class.__init__ misses a docstring + test.collect.Class.__hash__ misses a docstring + test.collect.Class.__eq__ misses a docstring + test.collect.Class.__cmp__ misses a docstring + test.cmdline.main misses a docstring + test.Item misses a docstring + test.Item.tryiter has an 'XXX' in its docstring + test.Item.teardown misses a docstring + test.Item.startcapture misses a docstring + test.Item.skipbykeyword misses a docstring + test.Item.setup misses a docstring + test.Item.run misses a docstring + test.Item.listnames misses a docstring + test.Item.join misses a docstring + test.Item.haskeyword misses a docstring + test.Item.getsortvalue misses a docstring + test.Item.getpathlineno misses a docstring + test.Item.getouterr misses a docstring + test.Item.getitembynames misses a docstring + test.Item.finishcapture misses a docstring + test.Item.buildname2items misses a docstring + test.Item.__repr__ misses a docstring + test.Item.__ne__ misses a docstring + test.Item.__init__ misses a docstring + test.Item.__hash__ misses a docstring + test.Item.__eq__ misses a docstring + test.Item.__cmp__ misses a docstring + test.Function.tryiter has an 'XXX' in its docstring + test.Function.teardown misses a docstring + test.Function.startcapture misses a docstring + test.Function.skipbykeyword misses a docstring + test.Function.setup misses a docstring + test.Function.run misses a docstring + test.Function.listnames misses a docstring + test.Function.join misses a docstring + test.Function.haskeyword misses a docstring + test.Function.getsortvalue misses a docstring + test.Function.getpathlineno misses a docstring + test.Function.getouterr misses a docstring + test.Function.getitembynames misses a docstring + test.Function.finishcapture misses a docstring + test.Function.buildname2items misses a docstring + test.Function.__repr__ misses a docstring + test.Function.__ne__ misses a docstring + test.Function.__init__ misses a docstring + test.Function.__hash__ misses a docstring + test.Function.__eq__ misses a docstring + test.Function.__cmp__ misses a docstring + test.Config.__init__ misses a docstring + xml.raw.__init__ misses a docstring + xml.html misses a docstring + xml.html.__tagclass__ misses a docstring + xml.html.__tagclass__.unicode misses a docstring + xml.html.__tagclass__.__unicode__ misses a docstring + xml.html.__tagclass__.__repr__ misses a docstring + xml.html.__tagclass__.__init__ misses a docstring + xml.html.__tagclass__.Attr misses a docstring + xml.html.__tagclass__.Attr.__init__ misses a docstring + xml.html.__metaclass__ misses a docstring + xml.html.__metaclass__.__getattr__ misses a docstring + xml.html.Style misses a docstring + xml.html.Style.__init__ misses a docstring + xml.escape misses a docstring + xml.Tag misses a docstring + xml.Tag.unicode misses a docstring + xml.Tag.__unicode__ misses a docstring + xml.Tag.__repr__ misses a docstring + xml.Tag.__init__ misses a docstring + xml.Namespace misses a docstring diff --git a/py/doc/__init__.py b/py/doc/__init__.py new file mode 100644 index 000000000..792d60054 --- /dev/null +++ b/py/doc/__init__.py @@ -0,0 +1 @@ +# diff --git a/py/doc/apigen.txt b/py/doc/apigen.txt new file mode 100644 index 000000000..806e66cc4 --- /dev/null +++ b/py/doc/apigen.txt @@ -0,0 +1,284 @@ +=========================================== +apigen - API documentation generation tool +=========================================== + +What is it? +=========== + +Apigen is a tool for automatically generating API reference documentation for +Python projects. It works by examining code at runtime rather than at compile +time. This way it is capable of displaying information about the code base +after initialization. A drawback is that you cannot easily document source code +that automatically starts server processes or has some other irreversible +effects upon getting imported. + +The apigen functionality is normally triggered from :api:`py.test`, and while +running the tests it gathers information such as code paths, arguments and +return values of callables, and exceptions that can be raised while the code +runs (XXX not yet!) to include in the documentation. It's also possible to +run the tracer (which collects the data) in other code if your project +does not use :api:`py.test` but still wants to collect the runtime information +and build the docs. + +Apigen is written for the :api:`py` lib, but can be used to build documentation +for any project: there are hooks in py.test to, by providing a simple script, +build api documentation for the tested project when running py.test. Of course +this does imply :api:`py.test` is actually used: if little or no tests are +actually ran, the additional information (code paths, arguments and return +values and exceptions) can not be gathered and thus there will be less of an +advantage of apigen compared to other solutions. + +Features +======== + +Some features were mentioned above already, but here's a complete list of all +the niceties apigen has to offer: + + * source documents + + Apigen not only builds the API documentation, but also a tree of + syntax-colored source files, with links from the API docs to the source + files. + + * abundance of information + + compared to other documentation generation tools, apigen produces an + abundant amount of information: it provides syntax-colored code snippets, + code path traces, etc. + + * linking + + besides links to the source files, apigen provides links all across the + documentation: callable arguments and return values link to their + definition (if part of the documented code), class definition to their + base classes (again, if they're part of the documented code), and + everywhere are links to the source files (including in traces) + + * (hopefully) improves testing + + because the documentation is built partially from test results, developers + may (especially if they're using the documentation themselves) be more + aware of untested parts of the code, or parts can use more tests or need + attention + +Using apigen +============ + +To trigger apigen, all you need to do is run the :source:`py/bin/py.test` tool +with an --apigen argument, as such:: + + $ py.test --apigen= + +where is a path to a script containing some special hooks to build +the documents (see below). The script to build the documents for the :api:`py` +lib can be found in :source:`py/apigen/apigen.py`, so building those documents +can be done by cd'ing to the 'py' directory, and executing:: + + $ py.test --apigen=apigen/apigen.py + +The documents will by default be built in the *parent directory* of the +*package dir* (in this case the 'py' directory). Be careful that you don't +overwrite anything! + +Other projects +============== + +To use apigen from another project, there are three things that you need to do: + +Use :api:`py.test` for unit tests +--------------------------------- + +This is a good idea anyway... ;) The more tests, the more tracing information +and such can be built, so it makes sense to have good test coverage when using +this tool. + +Provide :api:`py.test` hooks +---------------------------- + +To hook into the unit testing framework, you will need to write a script with +two functions. The first should be called 'get_documentable_items', gets a +package dir (the root of the project) as argument, and should return a tuple +with the package name as first element, and a dict as second. The dict should +contain, for all the to-be-documented items, a dotted name as key and a +reference to the item as value. + +The second function should be called 'build', and gets also the package dir as +argument, but also a reference to a DocStorageAcessor, which contains +information gathered by the tracer, and a reference to a +:api:`py.io.StdCaptureFD` instance that is used to capture stdout and stderr, +and allows writing to them, when the docs are built. + +This 'build' function is responsible for actually building the documentation, +and, depending on your needs, can be used to control each aspect of it. In most +situations you will just copy the code from :source:`py/apigen/apigen.py`'s +build() function, but if you want you can choose to build entirely different +output formats by directly accessing the DocStorageAccessor class. + +Provide layout +-------------- + +For the :api:`py` lib tests, the 'LayoutPage' class found in +:source:`py/apigen/layout.py` is used, which produces HTML specific for that +particular library (with a menubar, etc.). To customize this, you will need to +provide a similar class, most probably using the Page base class from +:source:`py/doc/confrest.py`. Note that this step depends on how heavy the +customization in the previous step is done: if you decide to directly use the +DocStorageAccessor rather than let the code in :source:`py/apigen/htmlgen.py` +build HTML for you, this can be skipped. + +Using apigen from code +====================== + +If you want to avoid using :api:`py.test`, or have an other idea of how to best +collect information while running code, the apigen functionality can be +directly accessed. The most important classes are the Tracer class found in +:source:`py/apigen/tracer/tracer.py`, which holds the information gathered +during the tests, and the DocStorage and DocStorageAccessor classes from +:source:`py/apigen/tracer/docstorage.py`, which (respectively) store the data, +and make it accessible. + +Gathering information +--------------------- + +To gather information about documentation, you will first need to tell the tool +what objects it should investigate. Only information for registered objects +will be stored. An example:: + + >>> import py + >>> from py.__.apigen.tracer.docstorage import DocStorage, DocStorageAccessor + >>> from py.__.apigen.tracer.tracer import Tracer + >>> toregister = {'py.path.local': py.path.local, + ... 'py.path.svnwc': py.path.svnwc} + >>> ds = DocStorage().from_dict(toregister) + >>> t = Tracer(ds) + >>> t.start_tracing() + >>> p = py.path.local('.') + >>> p.check(dir=True) + True + >>> t.end_tracing() + +Now the 'ds' variable should contain all kinds of information about both the +:api:`py.path.local` and the :api:`py.path.svnwc` classes, and things like call +stacks, possible argument types, etc. as additional information about +:api:`py.path.local.check()` (since it was called from the traced code). + +Using the information +--------------------- + +To use the information, we need to get a DocStorageAccessor instance to +provide access to the data stored in the DocStorage object:: + + >>> dsa = DocStorageAccessor(ds) + +Currently there is no API reference available for this object, so you'll have +to read the source (:source:`py/apigen/tracer/docstorage.py`) to see what +functionality it offers. + +Comparison with other documentation generation tools +==================================================== + +Apigen is of course not the only documentation generation tool available for +Python. Although we knew in advance that our tool had certain features the +others do not offer, we decided to investigate a bit so that we could do a +proper comparison. + +Tools examined +-------------- + +After some 'googling around', it turned out that the amount of documentation +generation tools available was surprisingly low. There were only 5 packages +I could find, of which 1 (called 'HappyDoc') seems dead (last release 2001), +one (called 'Pudge') not yet born (perhaps DOA even? most of the links on the +website are dead), and one (called 'Endo') specific to the Enthought suite. +The remaining two were Epydoc, which is widely used [1]_, and PyDoctor, which is +used only by (and written for) the Twisted project, but can be used seperately. + +Epydoc +~~~~~~ + +http://epydoc.sourceforge.net/ + +Epydoc is the best known, and most widely used, documentation generation tool +for Python. It builds a documentation tree by inspecting imported modules and +using Python's introspection features. This way it can display information like +containment, inheritance, and docstrings. + +The tool is relatively sophisticated, with support for generating HTML and PDF, +choosing different styles (CSS), generating graphs using Graphviz, etc. Also +it allows using markup (which can be ReST, JavaDoc, or their own 'epytext' +format) inside docstrings for displaying rich text in the result. + +Quick overview: + + * builds docs from object tree + * displays relatively little information, just inheritance trees, API and + docstrings + * supports some markup (ReST, 'epytext', JavaDoc) in docstrings + +PyDoctor +~~~~~~~~ + +http://codespeak.net/~mwh/pydoctor/ + +This tool is written by Michael Hudson for the Twisted project. The major +difference between this and Epydoc is that it browses the AST (Abstract Syntax +Tree) instead of using 'live' objects, which means that code that uses special +import mechanisms, or depends on other code that is not available can still be +inspected. On the other hand, code that, for example, puts bound methods into a +module namespace is not documented. + +The tool is relatively simple and doesn't support the more advanced features +that Epydoc offers. It was written for Twisted and there are no current plans to +promote its use for unrelated projects. + +Quick overview: + + * inspects AST rather than object tree + * again not a lot of information, the usual API docstrings, class inheritance + and module structure, but that's it + * rather heavy dependencies (depends on Twisted/Nevow (trunk version)) + * written for Twisted, but quite nice output with other applications + +Quick overview lists of the other tools +--------------------------------------- + +HappyDoc +~~~~~~~~ + +http://happydoc.sourceforge.net/ + + * dead + * inspects AST + * quite flexible, different output formats (HTML, XML, SGML, PDF) + * pluggable docstring parsers + +Pudge +~~~~~ + +http://pudge.lesscode.org/ + + * immature, dead? + * builds docs from live object tree (I think?) + * supports ReST + * uses Kid templates + +Endo +~~~~ + +https://svn.enthought.com/enthought/wiki/EndoHowTo + + * inspects object tree (I think?) + * 'traits' aware (see https://svn.enthought.com/enthought/wiki/Traits) + * customizable HTML output with custom templating engine + * little documentation, seems like it's written for Enthought's own use + mostly + * heavy dependencies + +.. [1] Epydoc doesn't seem to be developed anymore, either, but it's so + widely used it can not be ignored... + +Questions, remarks, etc. +======================== + +For more information, questions, remarks, etc. see http://codespeak.net/py. +This website also contains links to mailing list and IRC channel. diff --git a/py/doc/apigen_refactorings.txt b/py/doc/apigen_refactorings.txt new file mode 100644 index 000000000..dbe438303 --- /dev/null +++ b/py/doc/apigen_refactorings.txt @@ -0,0 +1,35 @@ + +Proposed apigen refactorings +============================= + +First of all we would like to have some kind of a persistent storage +for apigen, so we could use it for different purposes (hint! hint! pdb) +than only web pages. This will resolve the issue of having separated +apigen "data" generation and web page generation. + +Apigen is very usefull feature, but we don't use it in general. Which +is bad. One of the reasons is above and the other one is that API of +apigen is not that well defined which makes it harder to use. So what +I think would be: + +* **py.apigen** tool, which will take tests and initpkg (or whatever + means of collecting data) and will try to store it somewhere + (not sure, plain text log as a first step?). Than next step + would be to have tools for generating webpages out of it + (py.webapi or so) and other tools which will integrate it to pdb, + emacs (pick your random IDE here) or whatever. + +* Another option is to have py.test generate those data and have another + tools using it. + +* Data storage. Text with a log comes in mind, but it's not very handy. + Using any sort of SQL doesn't really counts, cause it makes pylib + less standalone, especially that I wouldn't like to have write all + those SQL myself, but rather use some kind of sql object relational + mapper. Another format might be some kind of structured text + (xml anyone?) or pickled stuff. Pickle has problems on his own, + so I don't have any best solution at hand. + +* Accessing. These are all strings and simple types built on top of it. + Probably would be good not to store all data in memory, because it might + be huge in case we would like to have all past informations there. diff --git a/py/doc/bin.txt b/py/doc/bin.txt new file mode 100644 index 000000000..62a781591 --- /dev/null +++ b/py/doc/bin.txt @@ -0,0 +1,67 @@ +====================== +``py/bin/`` scripts +====================== + +The py-lib contains some scripts, most of which are +small ones (apart from ``py.test``) that help during +the python development process. If working +from a svn-checkout of py lib you may add ``py/bin`` +to your shell ``PATH`` which should make the scripts +available on your command prompt. + +``py.test`` +=========== + +The ``py.test`` executable is the main entry point into the py-lib testing tool, +see the `py.test documentation`_. + +.. _`py.test documentation`: test.html + +``py.cleanup`` +============== + +Usage: ``py.cleanup [PATH]`` + +Delete pyc file recursively, starting from ``PATH`` (which defaults to the +current working directory). Don't follow links and don't recurse into +directories with a ".". + + +``py.countloc`` +=============== + +Usage: ``py.countloc [PATHS]`` + +Count (non-empty) lines of python code and number of python files recursively +starting from a ``PATHS`` given on the command line (starting from the current +working directory). Distinguish between test files and normal ones and report +them separately. + +``py.lookup`` +============= + +Usage: ``py.lookup SEARCH_STRING [options]`` + +Looks recursively at Python files for a ``SEARCH_STRING``, starting from the +present working directory. Prints the line, with the filename and line-number +prepended. + +``py.rest`` +=========== + +Usage: ``py.rest [PATHS] [options]`` + +Loot recursively for .txt files starting from ``PATHS`` and convert them to +html using docutils (or to pdf files, if the --pdf option is used). + +``py.rest`` has some extra features over rst2html (which is shipped with +docutils). Most of these are still experimental, the one which is most likely +not going to change is the `graphviz`_ directive. With that you can embed .dot +files into your document and have them be converted to png (when outputting +html) and to eps (when outputting pdf). Otherwise the directive works mostly +like the image directive:: + + .. graphviz:: example.dot + :scale: 90 + +.. _`graphviz`: http://www.graphviz.org diff --git a/py/doc/code.txt b/py/doc/code.txt new file mode 100644 index 000000000..f7f16a546 --- /dev/null +++ b/py/doc/code.txt @@ -0,0 +1,141 @@ +============== +:api:`py.code` +============== + +The :api:`py.code` part of the 'py lib' contains some functionality to help +dealing with Python code objects. Even though working with Python's internal +code objects (as found on frames and callables) can be very powerful, it's +usually also quite cumbersome, because the API provided by core Python is +relatively low level and not very accessible. + +The :api:`py.code` library tries to simplify accessing the code objects as well +as creating them. There is a small set of interfaces a user needs to deal with, +all nicely bundled together, and with a rich set of 'Pythonic' functionality. + +source: :source:`py/code/` + +Contents of the library +======================= + +Every object in the :api:`py.code` library wraps a code Python object related +to code objects, source code, frames and tracebacks: the :api:`py.code.Code` +class wraps code objects, :api:`py.code.Source` source snippets, +:api:`py.code.Traceback` exception tracebacks, :api:`py.code.Frame` frame +objects (as found in e.g. tracebacks) and :api:`py.code.ExceptionInfo` the +tuple provided by sys.exc_info() (containing exception and traceback +information when an exception occurs). Also in the library is a helper function +:api:`py.code.compile()` that provides the same functionality as Python's +built-in 'compile()' function, but returns a wrapped code object. + +The wrappers +============ + +:api:`py.code.Code` +------------------- + +Code objects are instantiated with a code object or a callable as argument, +and provide functionality to compare themselves with other Code objects, get to +the source file or its contents, create new Code objects from scratch, etc. + +A quick example:: + + >>> import py + >>> c = py.code.Code(py.path.local.read) + >>> c.path.basename + 'common.py' + >>> isinstance(c.source(), py.code.Source) + True + >>> str(c.source()).split('\n')[0] + "def read(self, mode='rb'):" + +source: :source:`py/code/code.py` + +:api:`py.code.Source` +--------------------- + +Source objects wrap snippets of Python source code, providing a simple yet +powerful interface to read, deindent, slice, compare, compile and manipulate +them, things that are not so easy in core Python. + +Example:: + + >>> s = py.code.Source("""\ + ... def foo(): + ... print "foo" + ... """) + >>> str(s).startswith('def') # automatic de-indentation! + True + >>> s.isparseable() + True + >>> sub = s.getstatement(1) # get the statement starting at line 1 + >>> str(sub).strip() # XXX why is the strip() required?!? + 'print "foo"' + +source: :source:`py/code/source.py` + +:api:`py.code.Traceback` +------------------------ + +Tracebacks are usually not very easy to examine, you need to access certain +somewhat hidden attributes of the traceback's items (resulting in expressions +such as 'fname = tb.tb_next.tb_frame.f_code.co_filename'). The Traceback +interface (and its TracebackItem children) tries to improve this. + +Example:: + + >>> import sys + >>> try: + ... py.path.local(100) # illegal argument + ... except: + ... exc, e, tb = sys.exc_info() + >>> t = py.code.Traceback(tb) + >>> first = t[1] # get the second entry (first is in this doc) + >>> first.path.basename # second is in py/path/local.py + 'local.py' + >>> isinstance(first.statement, py.code.Source) + True + >>> str(first.statement).strip().startswith('raise ValueError') + True + +source: :source:`py/code/traceback2.py` + +:api:`py.code.Frame` +-------------------- + +Frame wrappers are used in :api:`py.code.Traceback` items, and will usually not +directly be instantiated. They provide some nice methods to evaluate code +'inside' the frame (using the frame's local variables), get to the underlying +code (frames have a code attribute that points to a :api:`py.code.Code` object) +and examine the arguments. + +Example (using the 'first' TracebackItem instance created above):: + + >>> frame = first.frame + >>> isinstance(frame.code, py.code.Code) + True + >>> isinstance(frame.eval('self'), py.__.path.local.local.LocalPath) + True + >>> [namevalue[0] for namevalue in frame.getargs()] + ['cls', 'path'] + +:api:`py.code.ExceptionInfo` +---------------------------- + +A wrapper around the tuple returned by sys.exc_info() (will call sys.exc_info() +itself if the tuple is not provided as an argument), provides some handy +attributes to easily access the traceback and exception string. + +Example:: + + >>> import sys + >>> try: + ... foobar() + ... except: + ... excinfo = py.code.ExceptionInfo() + >>> excinfo.typename + 'NameError' + >>> isinstance(excinfo.traceback, py.code.Traceback) + True + >>> excinfo.exconly() + "NameError: name 'foobar' is not defined" + diff --git a/py/doc/coding-style.txt b/py/doc/coding-style.txt new file mode 100644 index 000000000..688168909 --- /dev/null +++ b/py/doc/coding-style.txt @@ -0,0 +1,73 @@ +===================================================== +Coding Style for the Py lib and friendly applications +===================================================== + +.. contents:: +.. sectnum:: + +Honour PEP 8: Style Guide for Python Code +----------------------------------------- + +First of all, if you haven't already read it, read the `PEP 8 +Style Guide for Python Code`_ which, if in doubt, serves as +the default coding-style for the py lib. + +Documentation and Testing +------------------------- + +- generally we want to drive and interweave coding of + documentation, tests and real code as much as possible. + Without good documentation others may never know about + your latest and greatest feature. + +naming +------ + +- directories, modules and namespaces are always **lowercase** + +- classes and especially Exceptions are most often **CamelCase** + +- types, i.e. very widely usable classes like the ``py.path`` + family are all lower case. + +- never use plural names in directory and file names + +- functions/methods are lowercase and ``_`` - separated if + you really need to separate at all + +- it's appreciated if you manage to name files in a directory + so that tab-completion on the shell level is as easy as possible. + + +committing +---------- + +- adding features requires adding appropriate tests. + +- bug fixes should be encoded in a test before being fixed. + +- write telling log messages because several people + will read your diffs, and we plan to have a search facility + over the py lib's subversion repository. + +- if you add ``.txt`` or ``.py`` files to the repository then + please make sure you have ``svn:eol-style`` set to native. + which allows checkin/checkout in native line-ending format. + +Miscellaneous +------------- + +- Tests are the insurance that your code will be maintained + further and survives major releases. + +- Try to put the tests close to the tested code, don't + overload directories with names. + +- If you think of exporting new py lib APIs, discuss it first on the + `py-dev mailing list`_ and possibly write a chapter in our + `future_` book. Communication is considered a key here to make + sure that the py lib develops in a consistent way. + +.. _`PEP 8 Style Guide for Python Code`: http://www.python.org/peps/pep-0008.html +.. _`py-dev mailing list`: http://codespeak.net/mailman/listinfo/py-dev +.. _`future`: future.html diff --git a/py/doc/confrest.py b/py/doc/confrest.py new file mode 100644 index 000000000..8132e7db8 --- /dev/null +++ b/py/doc/confrest.py @@ -0,0 +1,170 @@ +import py +from py.__.misc.rest import convert_rest_html, strip_html_header +from py.__.misc.difftime import worded_time +from py.__.doc.conftest import get_apigenpath, get_docpath +from py.__.apigen.linker import relpath + +html = py.xml.html + +class Page(object): + doctype = ('\n') + + def __init__(self, project, title, targetpath, stylesheeturl=None, + type="text/html", encoding="ISO-8859-1"): + self.project = project + self.title = project.prefix_title + title + self.targetpath = targetpath + self.stylesheeturl = stylesheeturl + self.type = type + self.encoding = encoding + + self.body = html.body() + self.head = html.head() + self._root = html.html(self.head, self.body) + self.fill() + + def a_docref(self, name, relhtmlpath): + docpath = self.project.get_docpath() + return html.a(name, class_="menu", + href=relpath(self.targetpath.strpath, + docpath.join(relhtmlpath).strpath)) + + def a_apigenref(self, name, relhtmlpath): + apipath = get_apigenpath() + return html.a(name, class_="menu", + href=relpath(self.targetpath.strpath, + apipath.join(relhtmlpath).strpath)) + + def fill_menubar(self): + items = [ + self.a_docref("index", "index.html"), + self.a_apigenref("api", "api/index.html"), + self.a_apigenref("source", "source/index.html"), + self.a_docref("contact", "contact.html"), + self.a_docref("download", "download.html"), + ] + items2 = [items.pop(0)] + sep = " " + for item in items: + items2.append(sep) + items2.append(item) + self.menubar = html.div(id="menubar", *items2) + + def fill(self): + content_type = "%s;charset=%s" %(self.type, self.encoding) + self.head.append(html.title(self.title)) + self.head.append(html.meta(name="Content-Type", content=content_type)) + if self.stylesheeturl: + self.head.append( + html.link(href=self.stylesheeturl, + media="screen", rel="stylesheet", + type="text/css")) + self.fill_menubar() + + self.metaspace = html.div( + html.div(self.title, class_="project_title"), + self.menubar, + id='metaspace') + + self.body.append(self.project.logo) + self.body.append(self.metaspace) + self.contentspace = html.div(id="contentspace") + self.body.append(self.contentspace) + + def unicode(self, doctype=True): + page = self._root.unicode() + if doctype: + return self.doctype + page + else: + return page + +class PyPage(Page): + def get_menubar(self): + menubar = super(PyPage, self).get_menubar() + # base layout + menubar.append( + html.a("issue", href="https://codespeak.net/issue/py-dev/", + class_="menu"), + ) + return menubar + + +def getrealname(username): + try: + import uconf + except ImportError: + return username + try: + user = uconf.system.User(username) + except KeyboardInterrupt: + raise + try: + return user.realname or username + except KeyError: + return username + + +class Project: + mydir = py.magic.autopath().dirpath() + # string for url, path for local file + stylesheet = mydir.join('style.css') + title = "py lib" + prefix_title = "" # we have a logo already containing "py lib" + encoding = 'latin1' + logo = html.div( + html.a( + html.img(alt="py lib", id='pyimg', height=114, width=154, + src="http://codespeak.net/img/pylib.png"), + href="http://codespeak.net")) + Page = PyPage + + + def get_content(self, txtpath, encoding): + return unicode(txtpath.read(), encoding) + + def get_docpath(self): + return get_docpath() + + def get_htmloutputpath(self, txtpath): + docpath = self.get_docpath() + reloutputpath = txtpath.new(ext='.html').relto(self.mydir) + return docpath.join(reloutputpath) + + def process(self, txtpath): + encoding = self.encoding + content = self.get_content(txtpath, encoding) + docpath = self.get_docpath() + outputpath = self.get_htmloutputpath(txtpath) + + stylesheet = self.stylesheet + if isinstance(self.stylesheet, py.path.local): + if not docpath.join(stylesheet.basename).check(): + docpath.ensure(dir=True) + stylesheet.copy(docpath) + stylesheet = relpath(outputpath.strpath, + docpath.join(stylesheet.basename).strpath) + + content = convert_rest_html(content, txtpath, + stylesheet=stylesheet, encoding=encoding) + content = strip_html_header(content, encoding=encoding) + + page = self.Page(self, "[%s] " % txtpath.purebasename, + outputpath, stylesheeturl=stylesheet) + + try: + svninfo = txtpath.info() + modified = " modified %s by %s" % (worded_time(svninfo.mtime), + getrealname(svninfo.last_author)) + except (KeyboardInterrupt, SystemExit): + raise + except: + modified = " " + + page.contentspace.append( + html.div(html.div(modified, style="float: right; font-style: italic;"), + id = 'docinfoline')) + + page.contentspace.append(py.xml.raw(content)) + outputpath.ensure().write(page.unicode().encode(encoding)) + diff --git a/py/doc/conftest.py b/py/doc/conftest.py new file mode 100644 index 000000000..07803898a --- /dev/null +++ b/py/doc/conftest.py @@ -0,0 +1,344 @@ +from __future__ import generators +import py +from py.__.misc import rest +from py.__.apigen.linker import relpath +import os + +pypkgdir = py.path.local(py.__file__).dirpath() + +mypath = py.magic.autopath().dirpath() + +TIMEOUT_URLOPEN = 5.0 + +Option = py.test.config.Option +option = py.test.config.addoptions("documentation check options", + Option('-R', '--checkremote', + action="store_true", dest="checkremote", default=False, + help="urlopen() remote links found in ReST text files.", + ), + Option('', '--forcegen', + action="store_true", dest="forcegen", default=False, + help="force generation of html files even if they appear up-to-date" + ), +) + +def get_apigenpath(): + from py.__.conftest import option + path = os.environ.get('APIGENPATH') + if path is None: + path = option.apigenpath + return pypkgdir.join(path, abs=True) + +def get_docpath(): + from py.__.conftest import option + path = os.environ.get('DOCPATH') + if path is None: + path = option.docpath + return pypkgdir.join(path, abs=True) + +def get_apigen_relpath(): + return relpath(get_docpath().strpath + '/', + get_apigenpath().strpath + '/') + +def deindent(s, sep='\n'): + leastspaces = -1 + lines = s.split(sep) + for line in lines: + if not line.strip(): + continue + spaces = len(line) - len(line.lstrip()) + if leastspaces == -1 or spaces < leastspaces: + leastspaces = spaces + if leastspaces == -1: + return s + for i, line in py.builtin.enumerate(lines): + if not line.strip(): + lines[i] = '' + else: + lines[i] = line[leastspaces:] + return sep.join(lines) + +_initialized = False +def checkdocutils(): + global _initialized + try: + import docutils + except ImportError: + py.test.skip("docutils not importable") + if not _initialized: + from py.__.rest import directive + directive.register_linkrole('api', resolve_linkrole) + directive.register_linkrole('source', resolve_linkrole) + _initialized = True + +def restcheck(path): + localpath = path + if hasattr(path, 'localpath'): + localpath = path.localpath + checkdocutils() + import docutils.utils + + try: + cur = localpath + for x in cur.parts(reverse=True): + confrest = x.dirpath('confrest.py') + if confrest.check(file=1): + confrest = confrest.pyimport() + project = confrest.Project() + _checkskip(path, project.get_htmloutputpath(path)) + project.process(path) + break + else: + # defer to default processor + _checkskip(path) + rest.process(path) + except KeyboardInterrupt: + raise + except docutils.utils.SystemMessage: + # we assume docutils printed info on stdout + py.test.fail("docutils processing failed, see captured stderr") + +def _checkskip(lpath, htmlpath=None): + if not option.forcegen: + lpath = py.path.local(lpath) + if htmlpath is not None: + htmlpath = py.path.local(htmlpath) + if lpath.ext == '.txt': + htmlpath = htmlpath or lpath.new(ext='.html') + if htmlpath.check(file=1) and htmlpath.mtime() >= lpath.mtime(): + py.test.skip("html file is up to date, use --forcegen to regenerate") + #return [] # no need to rebuild + +class ReSTSyntaxTest(py.test.collect.Item): + def execute(self): + mypath = self.fspath + restcheck(py.path.svnwc(mypath)) + +class DoctestText(py.test.collect.Item): + def execute(self): + s = self._normalize_linesep() + l = [] + prefix = '.. >>> ' + mod = py.std.types.ModuleType(self.fspath.purebasename) + skipchunk = False + for line in deindent(s).split('\n'): + stripped = line.strip() + if skipchunk and line.startswith(skipchunk): + print "skipping", line + continue + skipchunk = False + if stripped.startswith(prefix): + try: + exec py.code.Source(stripped[len(prefix):]).compile() in \ + mod.__dict__ + except ValueError, e: + if e.args and e.args[0] == "skipchunk": + skipchunk = " " * (len(line) - len(line.lstrip())) + else: + raise + else: + l.append(line) + docstring = "\n".join(l) + mod.__doc__ = docstring + failed, tot = py.compat.doctest.testmod(mod, verbose=1) + if failed: + py.test.fail("doctest %s: %s failed out of %s" %( + self.fspath, failed, tot)) + + def _normalize_linesep(self): + # XXX quite nasty... but it works (fixes win32 issues) + s = self.fspath.read() + linesep = '\n' + if '\r' in s: + if '\n' not in s: + linesep = '\r' + else: + linesep = '\r\n' + s = s.replace(linesep, '\n') + return s + +class LinkCheckerMaker(py.test.collect.Collector): + def listdir(self): + l = [] + for call, tryfn, path, lineno in genlinkchecks(self.fspath): + l.append("%s:%d" %(tryfn, lineno)) + return l + + def join(self, name): + i = name.rfind(':') + assert i != -1 + jname, jlineno = name[:i], int(name[i+1:]) + for call, tryfn, path, lineno in genlinkchecks(self.fspath): + if tryfn == jname and lineno == jlineno: + return CheckLink(name, parent=self, args=(tryfn, path, lineno), callobj=call) + +class CheckLink(py.test.collect.Function): + def repr_metainfo(self): + return self.ReprMetaInfo(fspath=self.fspath, lineno=self._args[2], + modpath="checklink: %s" % (self._args[0],)) + def setup(self): + pass + def teardown(self): + pass + +class ReSTChecker(py.test.collect.Module): + DoctestText = DoctestText + ReSTSyntaxTest = ReSTSyntaxTest + + def __repr__(self): + return py.test.collect.Collector.__repr__(self) + + def setup(self): + pass + def teardown(self): + pass + def listdir(self): + return [self.fspath.basename, 'checklinks', 'doctest'] + def join(self, name): + if name == self.fspath.basename: + return self.ReSTSyntaxTest(name, parent=self) + elif name == 'checklinks': + return LinkCheckerMaker(name, self) + elif name == 'doctest': + return self.DoctestText(name, self) + +# generating functions + args as single tests +def genlinkchecks(path): + for lineno, line in py.builtin.enumerate(path.readlines()): + line = line.strip() + if line.startswith('.. _'): + if line.startswith('.. _`'): + delim = '`:' + else: + delim = ':' + l = line.split(delim, 1) + if len(l) != 2: + continue + tryfn = l[1].strip() + if tryfn.startswith('http:') or tryfn.startswith('https'): + if option.checkremote: + yield urlcheck, tryfn, path, lineno + elif tryfn.startswith('webcal:'): + continue + else: + i = tryfn.find('#') + if i != -1: + checkfn = tryfn[:i] + else: + checkfn = tryfn + if checkfn.strip() and (1 or checkfn.endswith('.html')): + yield localrefcheck, tryfn, path, lineno + +def urlcheck(tryfn, path, lineno): + old = py.std.socket.getdefaulttimeout() + py.std.socket.setdefaulttimeout(TIMEOUT_URLOPEN) + try: + try: + print "trying remote", tryfn + py.std.urllib2.urlopen(tryfn) + finally: + py.std.socket.setdefaulttimeout(old) + except (py.std.urllib2.URLError, py.std.urllib2.HTTPError), e: + if e.code in (401, 403): # authorization required, forbidden + py.test.skip("%s: %s" %(tryfn, str(e))) + else: + py.test.fail("remote reference error %r in %s:%d\n%s" %( + tryfn, path.basename, lineno+1, e)) + +def localrefcheck(tryfn, path, lineno): + # assume it should be a file + i = tryfn.find('#') + if tryfn.startswith('javascript:'): + return # don't check JS refs + if i != -1: + anchor = tryfn[i+1:] + tryfn = tryfn[:i] + else: + anchor = '' + fn = path.dirpath(tryfn) + ishtml = fn.ext == '.html' + fn = ishtml and fn.new(ext='.txt') or fn + print "filename is", fn + if not fn.check(): # not ishtml or not fn.check(): + if not py.path.local(tryfn).check(): # the html could be there + py.test.fail("reference error %r in %s:%d" %( + tryfn, path.basename, lineno+1)) + if anchor: + source = unicode(fn.read(), 'latin1') + source = source.lower().replace('-', ' ') # aehem + + anchor = anchor.replace('-', ' ') + match2 = ".. _`%s`:" % anchor + match3 = ".. _%s:" % anchor + candidates = (anchor, match2, match3) + print "candidates", repr(candidates) + for line in source.split('\n'): + line = line.strip() + if line in candidates: + break + else: + py.test.fail("anchor reference error %s#%s in %s:%d" %( + tryfn, anchor, path.basename, lineno+1)) + + +# ___________________________________________________________ +# +# hooking into py.test Directory collector's chain ... + +class DocDirectory(py.test.collect.Directory): + ReSTChecker = ReSTChecker + + def listdir(self): + results = super(DocDirectory, self).listdir() + for x in self.fspath.listdir('*.txt', sort=True): + results.append(x.basename) + return results + + def join(self, name): + if not name.endswith('.txt'): + return super(DocDirectory, self).join(name) + p = self.fspath.join(name) + if p.check(file=1): + return self.ReSTChecker(p, parent=self) +Directory = DocDirectory + +def resolve_linkrole(name, text, check=True): + apigen_relpath = get_apigen_relpath() + if name == 'api': + if text == 'py': + return ('py', apigen_relpath + 'api/index.html') + else: + assert text.startswith('py.'), ( + 'api link "%s" does not point to the py package') % (text,) + dotted_name = text + if dotted_name.find('(') > -1: + dotted_name = dotted_name[:text.find('(')] + # remove pkg root + path = dotted_name.split('.')[1:] + dotted_name = '.'.join(path) + obj = py + if check: + for chunk in path: + try: + obj = getattr(obj, chunk) + except AttributeError: + raise AssertionError( + 'problem with linkrole :api:`%s`: can not resolve ' + 'dotted name %s' % (text, dotted_name,)) + return (text, apigen_relpath + 'api/%s.html' % (dotted_name,)) + elif name == 'source': + assert text.startswith('py/'), ('source link "%s" does not point ' + 'to the py package') % (text,) + relpath = '/'.join(text.split('/')[1:]) + if check: + pkgroot = py.__pkg__.getpath() + abspath = pkgroot.join(relpath) + assert pkgroot.join(relpath).check(), ( + 'problem with linkrole :source:`%s`: ' + 'path %s does not exist' % (text, relpath)) + if relpath.endswith('/') or not relpath: + relpath += 'index.html' + else: + relpath += '.html' + return (text, apigen_relpath + 'source/%s' % (relpath,)) + diff --git a/py/doc/contact.txt b/py/doc/contact.txt new file mode 100644 index 000000000..811ff95e7 --- /dev/null +++ b/py/doc/contact.txt @@ -0,0 +1,81 @@ +py lib contact and communication +=================================== + +.. contents:: +.. sectnum:: + +IRC Channel #pylib on irc.freenode.net +-------------------------------------------- + +The #pylib channel on freenode displays all commits to the py lib +and you are welcome to lurk or to ask questions there! + +`py-dev`_ developers mailing list +----------------------------------- + +If you see bugs and/or can provide patches, please +subscribe to the `py-dev developers list`_. +As of Febrary 2007 it has medium to low traffic. + + +`py-svn`_ commit mailing list +----------------------------------- + +If you'd like to see ongoing development commits, +please subscribe to: + + `py-svn general commit mailing list`_ + +This list (as of February 2007) has medium to high traffic. + + +`development bug/feature tracker`_ +--------------------------------------------- + +This (somewhat old) roundup instance still serves +to file bugs and track issues. However, we also +keep a list of "TODOs" in various directories. + + +Coding and communication +------------------------ + +We are practicing what could be called documentation, +vision, discussion and automated-test driven development. +In the `future`_ book we try to layout visions and ideas for +the near coding feature to give a means for preliminary +feedback before code hits the ground. + +With our `coding style`_ we are mostly following +cpython guidance with some additional restrictions +some of which projects like twisted_ or zope3_ have +adopted in similar ways. + +.. _`zope3`: http://zope3.zwiki.org/ +.. _twisted: http://www.twistedmatrix.org +.. _future: future.html + +.. _`get an account`: + + +get an account on codespeak +--------------------------- + +codespeak_ is employing a liberal committing scheme. If you know +someone who is active on codespeak already or you are otherwise known in +the community then you will most probably just get access. But even if +you are new to the python developer community you may still get one if +you want to improve things and can be expected to honour the +style of coding and communication. + +.. _`coding style`: coding-style.html +.. _us: http://codespeak.net/mailman/listinfo/py-dev +.. _codespeak: http://codespeak.net/ +.. _`py-dev`: +.. _`development mailing list`: +.. _`py-dev developers list`: http://codespeak.net/mailman/listinfo/py-dev +.. _`subversion commit mailing list`: +.. _`py-svn`: +.. _`py-svn general commit mailing list`: http://codespeak.net/mailman/listinfo/py-svn +.. _`development bug/feature tracker`: https://codespeak.net/issue/py-dev/ + diff --git a/py/doc/download.txt b/py/doc/download.txt new file mode 100644 index 000000000..346eefd31 --- /dev/null +++ b/py/doc/download.txt @@ -0,0 +1,113 @@ +Download and Installation of the py lib +=============================================== + +.. contents:: +.. sectnum:: + +Downloading a tar/zip file and installing it +=================================================== + +The latest public release: + + `download py-0.9.0.tar.gz`_ + `download py-0.9.0.zip`_ + +.. _`download py-0.9.0.tar.gz`: http://codespeak.net/download/py/py-0.9.0.tar.gz +.. _`download py-0.9.0.zip`: http://codespeak.net/download/py/py-0.9.0.zip + +The py lib can be `globally installed via setup.py`_ +or `used locally`_. + +WARNING: win32 there is no pre-packaged c-extension +module (greenlet) yet and thus greenlets will not work +out of the box. + +Getting (and updating) via subversion +-------------------------------------------- + +Use Subversion to checkout the latest 0.9.x stable release: + + svn co http://codespeak.net/svn/py/release/0.9.x py-0.9.x + +to obtain the complete code and documentation source. + +If you experience problems with the subversion checkout e.g. +because you have a http-proxy in between that doesn't proxy +DAV requests you can try to use "codespeak.net:8080" instead +of just "codespeak.net". Alternatively, you may tweak +your local subversion installation. + +If you want to follow stable snapshots +then you may use the equivalent of this invocation: + + svn co http://codespeak.net/svn/py/dist py-dist + + +.. _`globally installed via setup.py`: + +Installation via setup.py +------------------------------ + +Go to your unpacked/checked out directory +and issue: + + python setup.py install + + +.. _`used locally`: + +Local Installation/Usage +------------------------------ + +You need to put the checkout-directory into your ``PYTHONPATH`` +and you want to have the ``py-dist/py/bin/py.test`` script in +your (unixish) system path, which lets you execute test files +and directories. + +There is a convenient way for Bash/Shell based systems +to setup the ``PYTHONPATH`` as well as the shell ``PATH``, insert:: + + eval `python ~/path/to/py-dist/py/env.py` + +into your ``.bash_profile``. Of course, you need to +specify your own checkout-directory. + + +.. _`svn-external scenario`: + +The py lib as an svn external +------------------------------------------------------- + +Add the py lib as an external to your project `DIRECTORY` +which contains your svn-controlled root package:: + + svn propedit 'svn:externals' DIRECTORY + +which will open an editor where you can add +the following line: + + py http://codespeak.net/svn/py/dist + +This will make your projcet automatically use the +most recent stable snapshot of the py lib. + +Alternatively you may use this url for +integrating the development version: + + http://codespeak.net/svn/py/trunk + +or the next one for following the e.g. the 0.9 release branch + + http://codespeak.net/svn/py/release/0.9.x + + +py subversion directory structure +================================= + +The directory release layout of the repository is +going to follow this scheme:: + + http://codespeak.net/ + svn/py/dist # latest stable (may or may not be a release) + svn/py/release # release tags and branches + svn/py/trunk # head development / merge point diff --git a/py/doc/example/genhtml.py b/py/doc/example/genhtml.py new file mode 100644 index 000000000..38bdef7f7 --- /dev/null +++ b/py/doc/example/genhtml.py @@ -0,0 +1,13 @@ +from py.xml import html + +paras = "First Para", "Second para" + +doc = html.html( + html.head( + html.meta(name="Content-Type", value="text/html; charset=latin1")), + html.body( + [html.p(p) for p in paras])) + +print unicode(doc).encode('latin1') + + diff --git a/py/doc/example/genhtmlcss.py b/py/doc/example/genhtmlcss.py new file mode 100644 index 000000000..d5c8158ae --- /dev/null +++ b/py/doc/example/genhtmlcss.py @@ -0,0 +1,23 @@ +import py +html = py.xml.html + +class my(html): + "a custom style" + class body(html.body): + style = html.Style(font_size = "120%") + + class h2(html.h2): + style = html.Style(background = "grey") + + class p(html.p): + style = html.Style(font_weight="bold") + +doc = my.html( + my.head(), + my.body( + my.h2("hello world"), + my.p("bold as bold can") + ) +) + +print doc.unicode(indent=2) diff --git a/py/doc/example/genxml.py b/py/doc/example/genxml.py new file mode 100644 index 000000000..fca24d4e0 --- /dev/null +++ b/py/doc/example/genxml.py @@ -0,0 +1,17 @@ + +import py +class ns(py.xml.Namespace): + pass + +doc = ns.books( + ns.book( + ns.author("May Day"), + ns.title("python for java programmers"),), + ns.book( + ns.author("why", class_="somecssclass"), + ns.title("Java for Python programmers"),), + publisher="N.N", + ) +print doc.unicode(indent=2).encode('utf8') + + diff --git a/py/doc/example/pytest/failure_demo.py b/py/doc/example/pytest/failure_demo.py new file mode 100644 index 000000000..0481c93dd --- /dev/null +++ b/py/doc/example/pytest/failure_demo.py @@ -0,0 +1,120 @@ +from py.test import raises +import py + +def otherfunc(a,b): + assert a==b + +def somefunc(x,y): + otherfunc(x,y) + +def otherfunc_multi(a,b): + assert (a == + b) + +class TestFailing(object): + def test_simple(self): + def f(): + return 42 + def g(): + return 43 + + assert f() == g() + + def test_simple_multiline(self): + otherfunc_multi( + 42, + 6*9) + + def test_not(self): + def f(): + return 42 + assert not f() + + def test_complex_error(self): + def f(): + return 44 + def g(): + return 43 + somefunc(f(), g()) + + def test_z1_unpack_error(self): + l = [] + a,b = l + + def test_z2_type_error(self): + l = 3 + a,b = l + + def test_startswith(self): + s = "123" + g = "456" + assert s.startswith(g) + + def test_startswith_nested(self): + def f(): + return "123" + def g(): + return "456" + assert f().startswith(g()) + + def test_global_func(self): + assert isinstance(globf(42), float) + + def test_instance(self): + self.x = 6*7 + assert self.x != 42 + + def test_compare(self): + assert globf(10) < 5 + + def test_try_finally(self): + x = 1 + try: + assert x == 0 + finally: + x = 0 + + def test_raises(self): + s = 'qwe' + raises(TypeError, "int(s)") + + def test_raises_doesnt(self): + raises(IOError, "int('3')") + + def test_raise(self): + raise ValueError("demo error") + + def test_tupleerror(self): + a,b = [1] + + def test_reinterpret_fails_with_print_for_the_fun_of_it(self): + l = [1,2,3] + print "l is", l + a,b = l.pop() + + def test_some_error(self): + if namenotexi: + pass + + def test_generator(self): + yield None + + def func1(self): + assert 41 == 42 + + def test_generator2(self): + yield self.func1 + +# thanks to Matthew Scott for this test +def test_dynamic_compile_shows_nicely(): + src = 'def foo():\n assert 1 == 0\n' + name = 'abc-123' + module = py.std.imp.new_module(name) + code = py.code.compile(src, name, 'exec') + exec code in module.__dict__ + py.std.sys.modules[name] = module + module.foo() + + +def globf(x): + return x+1 diff --git a/py/doc/example/pytest/test_failures.py b/py/doc/example/pytest/test_failures.py new file mode 100644 index 000000000..f26957c20 --- /dev/null +++ b/py/doc/example/pytest/test_failures.py @@ -0,0 +1,11 @@ + +import py +failure_demo = py.magic.autopath().dirpath('failure_demo.py') +from py.__.doc.test_conftest import countoutcomes + +def test_failure_demo_fails_properly(): + config = py.test.config._reparse([failure_demo]) + session = config.initsession() + failed, passed, skipped = countoutcomes(session) + assert failed == 21 + assert passed == 0 diff --git a/py/doc/example/pytest/test_setup_flow_example.py b/py/doc/example/pytest/test_setup_flow_example.py new file mode 100644 index 000000000..0c763a1a8 --- /dev/null +++ b/py/doc/example/pytest/test_setup_flow_example.py @@ -0,0 +1,42 @@ +def setup_module(module): + module.TestStateFullThing.classcount = 0 + +class TestStateFullThing: + def setup_class(cls): + cls.classcount += 1 + + def teardown_class(cls): + cls.classcount -= 1 + + def setup_method(self, method): + self.id = eval(method.func_name[5:]) + + def test_42(self): + assert self.classcount == 1 + assert self.id == 42 + + def test_23(self): + assert self.classcount == 1 + assert self.id == 23 + +def teardown_module(module): + assert module.TestStateFullThing.classcount == 0 + +""" For this example the control flow happens as follows:: + import test_setup_flow_example + setup_module(test_setup_flow_example) + setup_class(TestStateFullThing) + instance = TestStateFullThing() + setup_method(instance, instance.test_42) + instance.test_42() + setup_method(instance, instance.test_23) + instance.test_23() + teardown_class(TestStateFullThing) + teardown_module(test_setup_flow_example) + +Note that ``setup_class(TestStateFullThing)`` is called and not +``TestStateFullThing.setup_class()`` which would require you +to insert ``setup_class = classmethod(setup_class)`` to make +your setup function callable. +""" + diff --git a/py/doc/execnet.txt b/py/doc/execnet.txt new file mode 100644 index 000000000..626f98b5b --- /dev/null +++ b/py/doc/execnet.txt @@ -0,0 +1,224 @@ +The py.execnet library +====================== + +.. contents:: +.. sectnum:: + +A new view on distributed execution +----------------------------------- + +``py.execnet`` supports ad-hoc distribution of parts of +a program across process and network barriers. *Ad-hoc* +means that the client side may completely control + +* which parts of a program execute remotely and + +* which data protocols are used between them + +without requiring any prior manual installation +of user program code on the remote side. In fact, +not even a prior installation of any server code +is required, provided there is a way to get +an input/output connection to a python interpreter +(for example via "ssh" and a "python" executable). + +By comparison, traditional Remote Method Based (RMI) +require prior installation and manual rather +heavy processes of setup, distribution and +communication between program parts. + + +What about Security? Are you completely nuts? +--------------------------------------------- + +We'll talk about that later :-) + +Basic Features +============== + +With ''py.execnet'' you get the means + +- to navigate through the network with Process, Thread, SSH + and Socket- gateways that allow you ... + +- to distribute your program across a network and define + communication protocols from the client side, making + server maintenance superflous. In fact, there is no such + thing as a server. It's just another computer ... if it + doesn't run in a kernel-level jail [#]_ in which case + even that is virtualized. + + +Available Gateways/Connection methods +----------------------------------------- + +You may use one of the following connection methods: + +* :api:`py.execnet.PopenGateway` a subprocess on the local + machine. Useful for jailing certain parts of a program + or for making use of multiple processors. + +* :api:`py.execnet.SshGateway` a way to connect to + a remote ssh server and distribute execution to it. + +* :api:`py.execnet.SocketGateway` a way to connect to + a remote Socket based server. *Note* that this method + requires a manually started + :source:py/execnet/script/socketserver.py + script. You can run this "server script" without + having the py lib installed on that remote system. + +Remote execution approach +------------------------------------- + +All gateways offer one main high level function: + + def remote_exec(source): + """return channel object for communicating with the asynchronously + executing 'source' code which will have a corresponding 'channel' + object in its executing namespace.""" + +With `remote_exec` you send source code to the other +side and get both a local and a remote Channel_ object, +which you can use to have the local and remote site +communicate data in a structured way. Here is +an example: + + >>> import py + >>> gw = py.execnet.PopenGateway() + >>> channel = gw.remote_exec(""" + ... import os + ... channel.send(os.getpid()) + ... """) + >>> remote_pid = channel.receive() + >>> remote_pid != py.std.os.getpid() + True + +`remote_exec` implements the idea to ``determine +protocol and remote code from the client/local side``. +This makes distributing a program run in an ad-hoc +manner (using e.g. :api:`py.execnet.SshGateway`) very easy. + +You should not need to maintain software on the other sides +you are running your code at, other than the Python +executable itself. + +.. _`Channel`: +.. _`channel-api`: +.. _`exchange data`: + +The **Channel** interface for exchanging data across gateways +------------------------------------------------------------- + +While executing custom strings on "the other side" is simple enough +it is often tricky to deal with. Therefore we want a way +to send data items to and fro between the distributedly running +program. The idea is to inject a Channel object for each +execution of source code. This Channel object allows two +program parts to send data to each other. +Here is the current interface:: + + # + # API for sending and receiving anonymous values + # + channel.send(item): + sends the given item to the other side of the channel, + possibly blocking if the sender queue is full. + Note that items need to be marshallable (all basic + python types are): + + channel.receive(): + receives an item that was sent from the other side, + possibly blocking if there is none. + Note that exceptions from the other side will be + reraised as gateway.RemoteError exceptions containing + a textual representation of the remote traceback. + + channel.waitclose(timeout=None): + wait until this channel is closed. Note that a closed + channel may still hold items that will be received or + send. Note that exceptions from the other side will be + reraised as gateway.RemoteError exceptions containing + a textual representation of the remote traceback. + + channel.close(): + close this channel on both the local and the remote side. + A remote side blocking on receive() on this channel + will get woken up and see an EOFError exception. + + +The complete Fileserver example +........................................ + +problem: retrieving contents of remote files:: + + import py + contentserverbootstrap = py.code.Source( + """ + for fn in channel: + f = open(fn, 'rb') + try: + channel.send(f.read()) + finally: + f.close() + """) + # open a gateway to a fresh child process + contentgateway = py.execnet.SshGateway('codespeak.net') + channel = contentgateway.remote_exec(contentserverbootstrap) + + for fn in somefilelist: + channel.send(fn) + content = channel.receive() + # process content + + # later you can exit / close down the gateway + contentgateway.exit() + + +A more complicated "nested" Gateway Example +........................................... + +The following example opens a PopenGateway, i.e. a python +child process, starts a socket server within that process and +then opens a SocketGateway to the freshly started +socketserver. Thus it forms a "triangle":: + + + CLIENT < ... > PopenGateway() + < . + . . + . . + . . + > SocketGateway() + +The below "socketserver" mentioned script is a small script that +basically listens and accepts socket connections, receives one +liners and executes them. + +Here are 20 lines of code making the above triangle happen:: + + import py + port = 7770 + socketserverbootstrap = py.code.Source( + mypath.dirpath().dirpath('bin', 'socketserver.py').read(), + """ + import socket + sock = bind_and_listen(("localhost", %r)) + channel.send("ok") + startserver(sock) + """ % port) + # open a gateway to a fresh child process + proxygw = py.execnet.PopenGateway() + + # execute asynchronously the above socketserverbootstrap on the other + channel = proxygw.remote_exec(socketserverbootstrap) + + # the other side should start the socket server now + assert channel.receive() == "ok" + gw = py.execnet.SocketGateway('localhost', cls.port) + print "initialized socket gateway to port", cls.port + +.. [#] There is an interesting emerging `Jail`_ linux technology + as well as a host of others, of course. + +.. _`Jail`: http://books.rsbac.org/unstable/x2223.html diff --git a/py/doc/future.txt b/py/doc/future.txt new file mode 100644 index 000000000..0993406e0 --- /dev/null +++ b/py/doc/future.txt @@ -0,0 +1,137 @@ +======================================================= +Visions and ideas for further development of the py lib +======================================================= + +.. contents:: +.. sectnum:: + +This document tries to describe directions and guiding ideas +for the near-future development of the py lib. *Note that all +statements within this document - even if they sound factual - +mostly just express thoughts and ideas. They not always refer to +real code so read with some caution.* + + +Distribute tests ad-hoc across multiple platforms +====================================================== + +After some more refactoring and unification of +the current testing and distribution support code +we'd like to be able to run tests on multiple +platforms simultanously and allow for interaction +and introspection into the (remote) failures. + + +Make APIGEN useful for more projects +================================================ + +The new APIGEN tool offers rich information +derived from running tests against an application: +argument types and callsites, i.e. it shows +the places where a particular API is used. +In its first incarnation, there are still +some specialties that likely prevent it +from documenting APIs for other projects. +We'd like to evolve to a `py.apigen` tool +that can make use of information provided +by a py.test run. + +Consider APIGEN and pdb integration +=================================== + +The information provided by APIGEN can be used in many +different ways. An example of this could be to write +an extension to pdb which makes it available. +Imagine you could issue a pdb command +"info " and get information +regarding incoming, and outgoing types, possible +exceptions, field types and call sites. + +Distribute channels/programs across networks +================================================ + +Apart from stabilizing setup/teardown procedures +for `py.execnet`_, we'd like to generalize its +implementation to allow connecting two programs +across multiple hosts, i.e. we'd like to arbitrarily +send "channels" across the network. Likely this +will be done by using the "pipe" model, i.e. +that each channel is actually a pair of endpoints, +both of which can be independently transported +across the network. The programs who "own" +these endpoints remain connected. + +.. _`py.execnet`: execnet.html + +Benchmarking and persistent storage +========================================= + +For storing test results, but also benchmarking +and other information, we need a solid way +to store all kinds of information from test runs. +We'd like to generate statistics or html-overview +out of it, but also use such information to determine when +a certain test broke, or when its performance +decreased considerably. + +.. _`restructured text`: http://docutils.sourceforge.net/docs/user/rst/quickref.html +.. _`python standard library`: http://www.python.org/doc/2.3.4/lib/lib.html +.. _`xpython EuroPython 2004 talk`: http://codespeak.net/svn/user/hpk/talks/xpython-talk.txt +.. _`under the xpy tree`: http://codespeak.net/svn/user/hpk/xpy/xml.py +.. _`future book`: future.html +.. _`PEP-324 subprocess module`: http://www.python.org/peps/pep-0324.html +.. _`subprocess implementation`: http://www.lysator.liu.se/~astrand/popen5/ +.. _`py.test`: test.html + + +.. _`general-path`: +.. _`a more general view on path objects`: + +Refactor path implementations to use a Filesystem Abstraction +============================================================= + +It seems like a good idea to refactor all python implementations to +use an internal Filesystem abstraction. The current code base +would be transformed to have Filesystem implementations for e.g. +local, subversion and subversion "working copy" filesystems. Today +the according code is scattered through path-handling code. + +On a related note, Armin Rigo has hacked `pylufs`_ and more recently has +written `pyfuse`_ which allow to +implement kernel-level linux filesystems with pure python. Now +the idea is that the mentioned filesystem implementations would +be directly usable for such linux-filesystem glue code. + +In other words, implementing a `memoryfs`_ or a `dictfs`_ would +give you two things for free: a filesystem mountable at kernel level +as well as a uniform "path" object allowing you to access your +filesystem in convenient ways. (At some point it might +even become interesting to think about interfacing to +`reiserfs v4 features`_ at the Filesystem level but that +is a can of subsequent worms). + +.. _`memoryfs`: http://codespeak.net/svn/user/arigo/hack/pyfuse/memoryfs.py +.. _`dictfs`: http://codespeak.net/pipermail/py-dev/2005-January/000191.html +.. _`pylufs`: http://codespeak.net/svn/user/arigo/hack/pylufs/ +.. _`pyfuse`: http://codespeak.net/svn/user/arigo/hack/pyfuse/ +.. _`reiserfs v4 features`: http://www.namesys.com/v4/v4.html + + +Integrate interactive completion +================================== + +It'd be nice to integrate the bash-like +rlcompleter2_ python command line completer +into the py lib, and making it work remotely +and with pdb. + +.. _rlcompleter2: http://codespeak.net/rlcompleter2/ + +Consider more features +================================== + +There are many more features and useful classes +that might be nice to integrate. For example, we might put +Armin's `lazy list`_ implementation into the py lib. + +.. _`lazy list`: http://codespeak.net/svn/user/arigo/hack/misc/collect.py diff --git a/py/doc/future/code_template.txt b/py/doc/future/code_template.txt new file mode 100644 index 000000000..86b1b1d93 --- /dev/null +++ b/py/doc/future/code_template.txt @@ -0,0 +1,640 @@ +=============================================================== +py.code_template: Lightweight and flexible code template system +=============================================================== + +.. contents:: +.. sectnum:: + +Motivation +========== + +There are as many python templating systems as there are web frameworks +(a lot). This is partly because it is so darned easy to write a templating +system in Python. What are the distinguishing characteristics of the +py.code_template templating system? + + * Optimized for generating code (Python, C, bash scripts, etc.), + not XML or HTML + + * Designed for use by Python programmers, not by web artists + + + Aesthetic sensibilities are different + + + The templates should be an organic part of a module -- just more code + + + Templates do not need to be incredibly full-featured, because + programmers are perfectly capable of escaping to Python for + advanced features. + + - No requirement to support inheritance + - No requirement to support exec + + * Designed so that templates can be coded in the most natural way + for the task at hand + + + Generation of code and scripts often does not follow MVC paradigm! + + + Small template fragments are typically coded *inside* Python modules + + + Sometimes it is natural to put strings inside code; sometimes it is + natural to put code inside strings. Both should be supported as + reasonably and naturally as possible. + +Imaginary-world examples +======================== + +These would be real-world examples, but, not only is this module not yet +implemented, as of now, PyPy is not incredibly useful to the average +programmer... + +translator/c/genc.py +-------------------- + +The original function:: + + def gen_readable_parts_of_main_c_file(f, database, preimplementationlines=[]): + # + # All declarations + # + structdeflist = database.getstructdeflist() + print >> f + print >> f, '/***********************************************************/' + print >> f, '/*** Structure definitions ***/' + print >> f + for node in structdeflist: + print >> f, 'struct %s;' % node.name + print >> f + for node in structdeflist: + for line in node.definition(): + print >> f, line + print >> f + print >> f, '/***********************************************************/' + print >> f, '/*** Forward declarations ***/' + print >> f + for node in database.globalcontainers(): + for line in node.forward_declaration(): + print >> f, line + + # + # Implementation of functions and global structures and arrays + # + print >> f + print >> f, '/***********************************************************/' + print >> f, '/*** Implementations ***/' + print >> f + for line in preimplementationlines: + print >> f, line + print >> f, '#include "src/g_include.h"' + print >> f + blank = True + for node in database.globalcontainers(): + if blank: + print >> f + blank = False + for line in node.implementation(): + print >> f, line + blank = True + +This could be refactored heavily. An initial starting point +would look something like this, although later, the template +instance could be passed in and reused directly, rather than +passing the file handle around:: + + def gen_readable_parts_of_main_c_file(f, database, preimplementationlines=[]): + def container_implementation(): + # Helper function designed to introduce blank lines + # between container implementations + + blank = True + for node in database.globalcontainers(): + if blank: + yield '' + blank = False + for line in node.implementation(): + yield line + blank = True + + t = code_template.Template() + # + # All declarations + # + structdeflist = database.getstructdeflist() + t.write(dedent=8, text=''' + + /***********************************************************/ + /*** Structure definitions ***/ + + {for node in structdeflist} + struct {node.name}; + {endfor} + + {for node in structdeflist} + {for line in node.definition} + {line} + {endfor} + {endfor} + + /***********************************************************/ + /*** Forward declarations ***/ + + {for node in database.globalcontainers()} + {for line in node.forward_declaration()} + {line} + {endfor} + {endfor} + + {** + ** Implementation of functions and global structures and arrays + **} + + /***********************************************************/ + /*** Implementations ***/ + + {for line in preimplementationlines} + {line} + {endfor} + + #include "src/g_include.h" + + {for line in container_implementation()} + {line} + {endfor} + """) + t.output(f) + +translator/c/genc.py gen_makefile +--------------------------------- + +The original code:: + + MAKEFILE = ''' + CC = gcc + + $(TARGET): $(OBJECTS) + \t$(CC) $(LDFLAGS) -o $@ $(OBJECTS) $(LIBDIRS) $(LIBS) + + %.o: %.c + \t$(CC) $(CFLAGS) -o $@ -c $< $(INCLUDEDIRS) + + clean: + \trm -f $(OBJECTS) + ''' + + def gen_makefile(self, targetdir): + def write_list(lst, prefix): + for i, fn in enumerate(lst): + print >> f, prefix, fn, + if i < len(lst)-1: + print >> f, '\\' + else: + print >> f + prefix = ' ' * len(prefix) + + compiler = self.getccompiler(extra_includes=['.']) + cfiles = [] + ofiles = [] + for fn in compiler.cfilenames: + fn = py.path.local(fn).basename + assert fn.endswith('.c') + cfiles.append(fn) + ofiles.append(fn[:-2] + '.o') + + f = targetdir.join('Makefile').open('w') + print >> f, '# automatically generated Makefile' + print >> f + print >> f, 'TARGET =', py.path.local(compiler.outputfilename).basename + print >> f + write_list(cfiles, 'SOURCES =') + print >> f + write_list(ofiles, 'OBJECTS =') + print >> f + args = ['-l'+libname for libname in compiler.libraries] + print >> f, 'LIBS =', ' '.join(args) + args = ['-L'+path for path in compiler.library_dirs] + print >> f, 'LIBDIRS =', ' '.join(args) + args = ['-I'+path for path in compiler.include_dirs] + write_list(args, 'INCLUDEDIRS =') + print >> f + print >> f, 'CFLAGS =', ' '.join(compiler.compile_extra) + print >> f, 'LDFLAGS =', ' '.join(compiler.link_extra) + print >> f, MAKEFILE.strip() + f.close() + + +Could look something like this:: + + MAKEFILE = ''' + # automatically generated Makefile + + TARGET = {py.path.local(compiler.outputfilename).basename} + + {for line in write_list(cfiles, 'SOURCES =')} + {line} + {endfor} + + {for line in write_list(ofiles, 'OBJECTS =')} + {line} + {endfor} + + LIBS ={for libname in compiler.libraries} -l{libname}{endfor} + LIBDIRS ={for path in compiler.library_dirs} -L{path}{endfor} + INCLUDEDIRS ={for path in compiler.include_dirs} -I{path}{endfor} + + CFLAGS ={for extra in compiler.compile_extra} {extra}{endfor} + LDFLAGS ={for extra in compiler.link_extra} {extra}{endfor} + + CC = gcc + + $(TARGET): $(OBJECTS) + \t$(CC) $(LDFLAGS) -o $@ $(OBJECTS) $(LIBDIRS) $(LIBS) + + %.o: %.c + \t$(CC) $(CFLAGS) -o $@ -c $< $(INCLUDEDIRS) + + clean: + \trm -f $(OBJECTS) + ''' + + def gen_makefile(self, targetdir): + def write_list(lst, prefix): + for i, fn in enumerate(lst): + yield '%s %s %s' % (prefix, fn, i < len(list)-1 and '\\' or '') + prefix = ' ' * len(prefix) + + compiler = self.getccompiler(extra_includes=['.']) + cfiles = [] + ofiles = [] + for fn in compiler.cfilenames: + fn = py.path.local(fn).basename + assert fn.endswith('.c') + cfiles.append(fn) + ofiles.append(fn[:-2] + '.o') + + code_template.Template(MAKEFILE).output(targetdir.join('Makefile')) + + +translator/llvm/module/excsupport.py +------------------------------------ + +The original string:: + + invokeunwind_code = ''' + ccc %(returntype)s%%__entrypoint__%(entrypointname)s { + %%result = invoke %(cconv)s %(returntype)s%%%(entrypointname)s to label %%no_exception except label %%exception + + no_exception: + store %%RPYTHON_EXCEPTION_VTABLE* null, %%RPYTHON_EXCEPTION_VTABLE** %%last_exception_type + ret %(returntype)s %%result + + exception: + ret %(noresult)s + } + + ccc int %%__entrypoint__raised_LLVMException() { + %%tmp = load %%RPYTHON_EXCEPTION_VTABLE** %%last_exception_type + %%result = cast %%RPYTHON_EXCEPTION_VTABLE* %%tmp to int + ret int %%result + } + + internal fastcc void %%unwind() { + unwind + } + ''' + +Could look something like this if it was used in conjunction with a template:: + + invokeunwind_code = ''' + ccc {returntype}%__entrypoint__{entrypointname} { + %result = invoke {cconv} {returntype}%{entrypointname} to label %no_exception except label %exception + + no_exception: + store %RPYTHON_EXCEPTION_VTABLE* null, %RPYTHON_EXCEPTION_VTABLE** %last_exception_type + ret {returntype} %result + + exception: + ret {noresult} + } + + ccc int %__entrypoint__raised_LLVMException() { + %tmp = load %RPYTHON_EXCEPTION_VTABLE** %last_exception_type + %result = cast %RPYTHON_EXCEPTION_VTABLE* %tmp to int + ret int %result + } + + internal fastcc void %unwind() { + unwind + } + ''' + + +Template syntax +=============== + +Design decision +--------------- + +As all programmers must know by now, all the special symbols on the keyboard +are quite heavily overloaded. Often, template systems work around this fact +by having special notation like `<*` ... `*>` or {% ... %}. Some template systems +even have multiple special notations -- one for comments, one for statements, +one for expressions, etc. + +I find these hard to type and ugly. Other markups are either too lightweight, +or use characters which occur so frequently in the target languages that it +becomes hard to distinguish marked-up content from content which should be +rendered as-is. + +The compromise taken by *code_template* is to use braces (**{}**) for markup. + +This immediately raises the question: what about when the marked-up language +is C or C++? The answer is that if the leading brace is immediately followed +by whitespace, it is normal text; if not it is the start of markup. + +To support normal text which has a leading brace immediately followed by +an identifier, if the first whitespace character after the brace is a space +character (e.g. not a newline or tab), it will be removed from the output. + +Examples:: + + { This is normal text and the space between { and This will be removed} + {'this must be a valid Python expression' + ' because it is treated as markup'} + { + This is normal text, but nothing is altered (the newline is kept intact) + } + + {{1:'Any valid Python expression is allowed as markup'}[1].ljust(30)} + +.. _`Code element`: + +Elements +-------- + +Templates consist of normal text and code elements. +(Comments are considered to be code elements.) + +All code elements start with a `left brace`_ which is not followed by +whitespace. + +Keyword element +~~~~~~~~~~~~~~~ + +A keyword element is a `code element`_ which starts with a keyword_. + +For example, *{if foo}* is a keyword element, but *{foo}* is a `substituted expression`_. + +Keyword +~~~~~~~ + +A keyword is a word used in `conditional text`_ or in `repeated text`_, e.g. +one of *if*, *elif*, *else*, *endif*, *for*, or *endfor*. + +Keywords are designed to match their Python equivalents. However, since +templates cannot use spacing to indicate expression nesting, the additional +keywords *endif* and *endfor* are required. + +Left brace +~~~~~~~~~~ + +All elements other than normal text start with a left brace -- the symbol '{', +sometimes known as a 'curly bracket'. A left brace is itself considered +to be normal text if it is followed by whitespace. If the whitespace starts +with a space character, that space character will be stripped from the output. +If the whitespace starts with a tab or linefeed character, the whitespace will +be left in the output. + +Normal Text +~~~~~~~~~~~ + +Normal text remains unsubstituted. Transition from text to the other elements +is effected by use of a `left brace`_ which is not followed by whitespace. + +Comment +~~~~~~~ + +A comment starts with a left brace followed by an asterisk ('{`*`'), and +ends with an asterisk followed by a right brace ('`*`}'):: + + This is a template -- this text will be copied to the output. + {* This is a comment and this text will not be copied to the output *} + + {* + Comments can span lines, + but cannot be nested + *} + +Substituted expression +~~~~~~~~~~~~~~~~~~~~~~ + +Any python expression may be used:: + + Dear {record.name}, + we are sorry to inform you that you did not win {record.contest}. + +The expression must be surrounded by braces, and there must not be any +whitespace between the leftmost brace and the start of the expression. + +The expression will automatically be converted to a string with str(). + +Conditional text +~~~~~~~~~~~~~~~~ + +The following template has text which is included conditionally:: + + This text will always be included in the output + {if foo} + This text will be included if foo is true + {elif bar} + This text will be included if foo is not true but bar is true + {else} + This text will be included if neither foo nor bar is true + {endif} + +The {elif} and {else} elements are optional. + +Repeated text +~~~~~~~~~~~~~ + +The following template shows how to pull multiple items out of a list:: + + {for student, score in sorted(scorelist)} + {student.ljust(20)} {score} + {endfor} + +Whitespace removal or modification +---------------------------------- + +In general, whitespace in `Normal Text`_ is transferred unchanged to the +output. There are three exceptions to this rule: + +Line separators +~~~~~~~~~~~~~~~ + +Each newline is converted to the final output using os.linesep. + +Beginning or end of string +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +py.code_template is designed to allow easy use of templates inside of python +modules. The canonical way to write a template is inside a triple-quoted +string, e.g.:: + + my_template = ''' + This is my template. It can have any text at all in it except + another triple-single-quote. + ''' + +To support this usage, if the first character is a newline, it will be +removed, and if the last line consists solely of whitespace with no +trailing newline, it will also be removed. + +A comment or single keyword element on a line +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Whenever a `keyword element`_ or comment_ is on a line +*by itself*, that line will not be copied to the output. + +This happens when: + - There is nothing on the line before the keyword element + or comment except whitespace (spaces and/or tabs). + + - There is nothing on the line after the keyword element + or comment except a newline. + +Note that even a multi-line comment or keyword element can +have the preceding whitespace and subsequent newline stripped +by this rule. + +The primary purpose of this rule is to allow the Python +programmer to use indentation, **even inside a template**:: + + This is a template + + {if mylist} + List items: + {for item in mylist} + - {item} + {endfor} + {endif} + +Template usage +============== + +Templates are used by importing the Template class from py.code_template, +constructing a template, and then sending data with the write() method. + +In general, there are four methods for getting the formatted data back out +of the template object: + + - read() reads all the data currently in the object + + - output(fobj) outputs the data to a file + + fobj can either be an open file object, or a string. If it is + a string, the file will be opened, written, and closed. + + - open(fobj) (or calling the object constructor with a file object) + + If the open() method is used, or if a file object is passed to + the constructor, each write() will automatically flush the data + out to the file. If the fobj is a string, it is considered to + be *owned*, otherwise it is considered to be *borrowed*. *Owned* + file objects are closed when the class is deleted. + + - write() can be explicitly called with a file object, in which case + it will invoke output() on that object after it generates the data. + +Template instantiation and methods +================================== + +template = code_template.Template(outf=None, cache=None) + +If outf is given, it will be passed to the open() method + +cache may be given as a mapping. If not given, the template will use +the shared default cache. This is not thread safe. + +template.open +------------- + +template.open(outf, borrowed = None) + +The open method closes the internal file object if it was already open, +and then re-opens it on the given file. It is an error to call open() +if there is data in the object left over from previous writes. (Call +output() instead.) + +borrowed defaults to 0 if outf is a string, and 1 if it is a file object. + +borrowed can also be set explicitly if required. + +template.close +-------------- + +close() disassociates the file from the template, and closes the file if +it was not borrowed. close() is automatically called by the destructor. + +template.write +-------------- + +template.write(text='', outf=None, dedent=0, localvars=None, globalvars=None, +framelevel=1) + +The write method has the following parameters: + + - text is the template itself + + - if outf is not None, the output method will be invoked on the object + after the current template is processed. If no outf is given, data + will be accumulated internal to the instance until a write() with outf + is processed, or read() or output() is called, whichever comes first, if + there is no file object. If there is a file object, data will be flushed + to the file after every write. + + - dedent, if given is applied to each line in the template, to "de-indent" + + - localvars and globalvars default to the dictionaries of the caller. A copy + of localvars is made so that the __TrueSpace__ identifier can be added. + + - cache may be given as a mapping. If not given, the template will use + the shared default cache. This is not thread safe. + + - framelevel is used to determine which stackframe to access for globals + and locals if localvars and/or globalvars are not specified. The default + is to use the caller's frame. + +The write method supports the print >> file protocol by deleting the softspace +attribute on every invocation. This allows code like:: + + t = code_template.Template() + print >> t, "Hello, world" + + +template.read +-------------- + +This method reads and flushes all accumulated data in the object. Note that +if a file has been associated with the object, there will never be any data +to read. + +template.output +--------------- + +This method takes one parameter, outf. template.output() first +invokes template.read() to read and flush all accumulated data, +and then outputs the data to the file specified by outf. + +If outf has a write() method, that will be invoked with the +data. If outf has no write() method, it will be treated as +a filename, and that file will be replaced. + +Caching and thread safety +========================= + +The compiled version of every template is cached internal to the +code_template module (unless a separate cache object is specified). + +This allows efficient template reuse, but is not currently thread-safe. +Alternatively, each invocation of a template object can specify a +cache object. This is thread-safe, but not very efficient. A shared +model could be implemented later. + diff --git a/py/doc/future/planning.txt b/py/doc/future/planning.txt new file mode 100644 index 000000000..991e0187d --- /dev/null +++ b/py/doc/future/planning.txt @@ -0,0 +1,64 @@ +Release +======= + +currently working configurations +-------------------------------- + +2.3 - 2.4.2 work + +with setuptools: 2.3 - 2.4.2 as 'develop' + +regular installation: works mostly, strange test-failures + +to be tested: 2.2, windows + +absolutely necessary steps: +---------------------------- + + * documentation + + * improving getting started, describe install methods + * describe the rest stuff? + * py.log + * py.path is mostly undocumented, API documentation + + * basic windows testing, maybe disabling execnet?, what about the scripts in windows? + + * are all c extensions compiled when installing globally? + + * refactoring py.log + + * write/read methods on py.path should be renamed/deprecated: setcontent, getcontent instead? + + * what about _subprocess.c? + + * warning for docutils + + * don't expose _extpy + + * py/bin should be nicefied, get optparse interface + + * _findpy.py + * py.cleanup: + * py.lookup: add -i option + * pytest.cmd + * rst2pdf.py: merge with py.rest, add warnings when missing tex + * _makepyrelease.py: move somewhere + * py.countloc + * py.test + * py.rest + * win32 + + * skip tests if dependencies are not installed + +nice to have +------------ + + * sets.py, subprocess.py in compat + * fix -k option to py.test + * add --report=(text|terminal|session|rest|tkinter|rest) to py.test + * put Armin's collect class into py.__builtin__ (not done) + * try get rid of Collect.tryiter() in favour of (not done) + using Armin's collect class + + diff --git a/py/doc/future/planning2.txt b/py/doc/future/planning2.txt new file mode 100644 index 000000000..8f554a742 --- /dev/null +++ b/py/doc/future/planning2.txt @@ -0,0 +1,39 @@ +Missing features/bugs in pylib: +==================================== + +* new skip method, so web interface would show skips which + are broken (say py.test.skip("stuff", reason=py.test.BORKEN)), + proposed by me and xoraxax + +* integration of session classes - needed for developement + +* more robust failure recovery from execnet - not sure how to perform + it, but select() approach sounds like a plan (instead of threads) + (unsure what than) + +* provide a bit more patchy approach to green stuff, ie you import it and + all (known) operations on sockets are performed via the green interface, + this should allow using arbitrary applications (well, not using C-level + I/O) to mix with green threads. + +* integrate green execnet a bit more (?) + +* once session integration is done, it would be cool to have nightly + testing done in a systematic manner (instead of bunch of hacks, which + is how it looks like right now), so for example codespeak would be able + to store information (ie via svn) and when one woke up he can type py.test + show and see the information of all nightly test runs which he likes. + +* py.test.pdb - there is my hack for a while now, which integrates + rlcompleter2 with pdb. First of all it requires some strange changes + to rlcompleter itself, which has no tests. Long-term plan would be + to have pyrepl+rlcompleter2+pdb fixes integrated into pylib and + have it tested. This requires work though. + +* add a link to pylib in pypy/lib? Since pylib features mostly work on top + of pypy-c, it would be nice to have it (as we do have it in svn anyway) + +* fix generative tests. + - they should be distributed atomically (for various reasons) + - fix setup/teardown logic (ie setup_generator/teardown_generator) + - XXX there was sth else diff --git a/py/doc/future/pylib_pypy.txt b/py/doc/future/pylib_pypy.txt new file mode 100644 index 000000000..33bd135a2 --- /dev/null +++ b/py/doc/future/pylib_pypy.txt @@ -0,0 +1,37 @@ +Here I'm trying to list all problems regarding pypy-c <-> pylib interaction +=========================================================================== + +* in test/terminal/terminal.py lines around 141:: + rev = py.__package__.getrev() + self.out.line("using py lib: %s " % ( + py.path.local(py.__file__).dirpath(), rev)) + +* py.code issues:: + def __init__(self, rawcode): + rawcode = getattr(rawcode, 'im_func', rawcode) + rawcode = getattr(rawcode, 'func_code', rawcode) + self.raw = rawcode + self.filename = rawcode.co_filename + AttributeError: 'internal-code' object has no attribute 'co_filename' + +* types.BuiltinFunctionType == types.MethodType which confuses apigen + +* compiler module problems - some bogus IndentationError + communicates by inspect.getsource() + +* execnet just hangs + +* lack of tmpfile + +* assertion error magic is not working + +* sha counting hangs (misc/testing/test\_initpkg) + +* extpy does not work, because it does not support loops in modules + (while pypy __builtins__ module has a loop), funny :-) + +* py.compat.subprocess hangs for obscure reasons + (possibly the same stuff as execnet - some threading issues and + select.select) + + Armin says: "haha, select.select probably does not release the GIL" \ No newline at end of file diff --git a/py/doc/future/rsession_todo.txt b/py/doc/future/rsession_todo.txt new file mode 100644 index 000000000..6e8b99020 --- /dev/null +++ b/py/doc/future/rsession_todo.txt @@ -0,0 +1,15 @@ +Various tasks which needs to be done at some point +================================================== + +* Write down pinging interface, so we'll know if hosts are responding or + are mostly down (detecting hanging nodes) + +* Write down support for rsync progress + +* Discovery of nodes which are available for accepting distributed testing + +* Test the tests rescheduling, so high-latency nodes would not take part + in that. + +* make sure that C-c semantics are ok (nodes are killed properly). + There was an attempt to do so, but it's not tested and not always work. \ No newline at end of file diff --git a/py/doc/greenlet.txt b/py/doc/greenlet.txt new file mode 100644 index 000000000..47ba76bdf --- /dev/null +++ b/py/doc/greenlet.txt @@ -0,0 +1,315 @@ +===================================================== +py.magic.greenlet: Lightweight concurrent programming +===================================================== + +.. contents:: +.. sectnum:: + +Motivation +========== + +The "greenlet" package is a spin-off of `Stackless`_, a version of CPython +that supports micro-threads called "tasklets". Tasklets run +pseudo-concurrently (typically in a single or a few OS-level threads) and +are synchronized with data exchanges on "channels". + +A "greenlet", on the other hand, is a still more primitive notion of +micro-thread with no implicit scheduling; coroutines, in other words. +This is useful when you want to +control exactly when your code runs. You can build custom scheduled +micro-threads on top of greenlet; however, it seems that greenlets are +useful on their own as a way to make advanced control flow structures. +For example, we can recreate generators; the difference with Python's own +generators is that our generators can call nested functions and the nested +functions can yield values too. (Additionally, you don't need a "yield" +keyword. See the example in :source:`py/c-extension/greenlet/test_generator.py`). + +Greenlets are provided as a C extension module for the regular unmodified +interpreter. + +.. _`Stackless`: http://www.stackless.com + +Example +------- + +Let's consider a system controlled by a terminal-like console, where the user +types commands. Assume that the input comes character by character. In such +a system, there will typically be a loop like the following one:: + + def process_commands(*args): + while True: + line = '' + while not line.endswith('\n'): + line += read_next_char() + if line == 'quit\n': + print "are you sure?" + if read_next_char() != 'y': + continue # ignore the command + process_command(line) + +Now assume that you want to plug this program into a GUI. Most GUI toolkits +are event-based. They will invoke a call-back for each character the user +presses. [Replace "GUI" with "XML expat parser" if that rings more bells to +you ``:-)``] In this setting, it is difficult to implement the +read_next_char() function needed by the code above. We have two incompatible +functions:: + + def event_keydown(key): + ?? + + def read_next_char(): + ?? should wait for the next event_keydown() call + +You might consider doing that with threads. Greenlets are an alternate +solution that don't have the related locking and shutdown problems. You +start the process_commands() function in its own, separate greenlet, and +then you exchange the keypresses with it as follows:: + + def event_keydown(key): + # jump into g_processor, sending it the key + g_processor.switch(key) + + def read_next_char(): + # g_self is g_processor in this simple example + g_self = greenlet.getcurrent() + # jump to the parent (main) greenlet, waiting for the next key + next_char = g_self.parent.switch() + return next_char + + g_processor = greenlet(process_commands) + g_processor.switch(*args) # input arguments to process_commands() + + gui.mainloop() + +In this example, the execution flow is: when read_next_char() is called, it +is part of the g_processor greenlet, so when it switches to its parent +greenlet, it resumes execution in the top-level main loop (the GUI). When +the GUI calls event_keydown(), it switches to g_processor, which means that +the execution jumps back wherever it was suspended in that greenlet -- in +this case, to the switch() instruction in read_next_char() -- and the ``key`` +argument in event_keydown() is passed as the return value of the switch() in +read_next_char(). + +Note that read_next_char() will be suspended and resumed with its call stack +preserved, so that it will itself return to different positions in +process_commands() depending on where it was originally called from. This +allows the logic of the program to be kept in a nice control-flow way; we +don't have to completely rewrite process_commands() to turn it into a state +machine. + + +Usage +===== + +Introduction +------------ + +A "greenlet" is a small independent pseudo-thread. Think about it as a +small stack of frames; the outermost (bottom) frame is the initial +function you called, and the innermost frame is the one in which the +greenlet is currently paused. You work with greenlets by creating a +number of such stacks and jumping execution between them. Jumps are never +implicit: a greenlet must choose to jump to another greenlet, which will +cause the former to suspend and the latter to resume where it was +suspended. Jumping between greenlets is called "switching". + +When you create a greenlet, it gets an initially empty stack; when you +first switch to it, it starts the run a specified function, which may call +other functions, switch out of the greenlet, etc. When eventually the +outermost function finishes its execution, the greenlet's stack becomes +empty again and the greenlet is "dead". Greenlets can also die of an +uncaught exception. + +For example:: + + from py.magic import greenlet + + def test1(): + print 12 + gr2.switch() + print 34 + + def test2(): + print 56 + gr1.switch() + print 78 + + gr1 = greenlet(test1) + gr2 = greenlet(test2) + gr1.switch() + +The last line jumps to test1, which prints 12, jumps to test2, prints 56, +jumps back into test1, prints 34; and then test1 finishes and gr1 dies. +At this point, the execution comes back to the original ``gr1.switch()`` +call. Note that 78 is never printed. + +Parents +------- + +Let's see where execution goes when a greenlet dies. Every greenlet has a +"parent" greenlet. The parent greenlet is initially the one in which the +greenlet was created (this can be changed at any time). The parent is +where execution continues when a greenlet dies. This way, greenlets are +organized in a tree. Top-level code that doesn't run in a user-created +greenlet runs in the implicit "main" greenlet, which is the root of the +tree. + +In the above example, both gr1 and gr2 have the main greenlet as a parent. +Whenever one of them dies, the execution comes back to "main". + +Uncaught exceptions are propagated into the parent, too. For example, if +the above test2() contained a typo, it would generate a NameError that +would kill gr2, and the exception would go back directly into "main". +The traceback would show test2, but not test1. Remember, switches are not +calls, but transfer of execution between parallel "stack containers", and +the "parent" defines which stack logically comes "below" the current one. + +Instantiation +------------- + +``py.magic.greenlet`` is the greenlet type, which supports the following +operations: + +``greenlet(run=None, parent=None)`` + Create a new greenlet object (without running it). ``run`` is the + callable to invoke, and ``parent`` is the parent greenlet, which + defaults to the current greenlet. + +``greenlet.getcurrent()`` + Returns the current greenlet (i.e. the one which called this + function). + +``greenlet.GreenletExit`` + This special exception does not propagate to the parent greenlet; it + can be used to kill a single greenlet. + +The ``greenlet`` type can be subclassed, too. A greenlet runs by calling +its ``run`` attribute, which is normally set when the greenlet is +created; but for subclasses it also makes sense to define a ``run`` method +instead of giving a ``run`` argument to the constructor. + +Switching +--------- + +Switches between greenlets occur when the method switch() of a greenlet is +called, in which case execution jumps to the greenlet whose switch() is +called, or when a greenlet dies, in which case execution jumps to the +parent greenlet. During a switch, an object or an exception is "sent" to +the target greenlet; this can be used as a convenient way to pass +information between greenlets. For example:: + + def test1(x, y): + z = gr2.switch(x+y) + print z + + def test2(u): + print u + gr1.switch(42) + + gr1 = greenlet(test1) + gr2 = greenlet(test2) + gr1.switch("hello", " world") + +This prints "hello world" and 42, with the same order of execution as the +previous example. Note that the arguments of test1() and test2() are not +provided when the greenlet is created, but only the first time someone +switches to it. + +Here are the precise rules for sending objects around: + +``g.switch(obj=None or *args)`` + Switches execution to the greenlet ``g``, sending it the given + ``obj``. As a special case, if ``g`` did not start yet, then it will + start to run now; in this case, any number of arguments can be + provided, and ``g.run(*args)`` is called. + +Dying greenlet + If a greenlet's ``run()`` finishes, its return value is the object + sent to its parent. If ``run()`` terminates with an exception, the + exception is propagated to its parent (unless it is a + ``greenlet.GreenletExit`` exception, in which case the exception + object is caught and *returned* to the parent). + +Apart from the cases described above, the target greenlet normally +receives the object as the return value of the call to ``switch()`` in +which it was previously suspended. Indeed, although a call to +``switch()`` does not return immediately, it will still return at some +point in the future, when some other greenlet switches back. When this +occurs, then execution resumes just after the ``switch()`` where it was +suspended, and the ``switch()`` itself appears to return the object that +was just sent. This means that ``x = g.switch(y)`` will send the object +``y`` to ``g``, and will later put the (unrelated) object that some +(unrelated) greenlet passes back to us into ``x``. + +Note that any attempt to switch to a dead greenlet actually goes to the +dead greenlet's parent, or its parent's parent, and so on. (The final +parent is the "main" greenlet, which is never dead.) + +Methods and attributes of greenlets +----------------------------------- + +``g.switch(obj=None or *args)`` + Switches execution to the greenlet ``g``. See above. + +``g.run`` + The callable that ``g`` will run when it starts. After ``g`` started, + this attribute no longer exists. + +``g.parent`` + The parent greenlet. This is writeable, but it is not allowed to + create cycles of parents. + +``g.gr_frame`` + The current top frame, or None. + +``g.dead`` + True if ``g`` is dead (i.e. it finished its execution). + +``bool(g)`` + True if ``g`` is active, False if it is dead or not yet started. + +``g.throw([typ, [val, [tb]]])`` + Switches execution to the greenlet ``g``, but immediately raises the + given exception in ``g``. If no argument is provided, the exception + defaults to ``greenlet.GreenletExit``. The normal exception + propagation rules apply, as described above. Note that calling this + method is almost equivalent to the following:: + + def raiser(): + raise typ, val, tb + g_raiser = greenlet(raiser, parent=g) + g_raiser.switch() + + except that this trick does not work for the + ``greenlet.GreenletExit`` exception, which would not propagate + from ``g_raiser`` to ``g``. + +Greenlets and Python threads +---------------------------- + +Greenlets can be combined with Python threads; in this case, each thread +contains an independent "main" greenlet with a tree of sub-greenlets. It +is not possible to mix or switch between greenlets belonging to different +threads. + +Garbage-collecting live greenlets +--------------------------------- + +If all the references to a greenlet object go away (including the +references from the parent attribute of other greenlets), then there is no +way to ever switch back to this greenlet. In this case, a GreenletExit +exception is generated into the greenlet. This is the only case where a +greenlet receives the execution asynchronously. This gives +``try:finally:`` blocks a chance to clean up resources held by the +greenlet. This feature also enables a programming style in which +greenlets are infinite loops waiting for data and processing it. Such +loops are automatically interrupted when the last reference to the +greenlet goes away. + +The greenlet is expected to either die or be resurrected by having a new +reference to it stored somewhere; just catching and ignoring the +GreenletExit is likely to lead to an infinite loop. + +Greenlets do not participate in garbage collection; cycles involving data +that is present in a greenlet's frames will not be detected. Storing +references to other greenlets cyclically may lead to leaks. diff --git a/py/doc/impl-test.txt b/py/doc/impl-test.txt new file mode 100644 index 000000000..c570280e8 --- /dev/null +++ b/py/doc/impl-test.txt @@ -0,0 +1,294 @@ +=============================================== +Implementation and Customization of ``py.test`` +=============================================== + +.. contents:: +.. sectnum:: + + + +.. _`basicpicture`: + + +Collecting and running tests / implementation remarks +====================================================== + +In order to customize ``py.test`` it's good to understand +its basic architure (WARNING: these are not guaranteed +yet to stay the way they are now!):: + + ___________________ + | | + | Collector | + |___________________| + / \ + | Item.run() + | ^ + receive test Items / + | /execute test Item + | / + ___________________/ + | | + | Session | + |___________________| + + ............................. + . conftest.py configuration . + . cmdline options . + ............................. + + +The *Session* basically receives test *Items* from a *Collector*, +and executes them via the ``Item.run()`` method. It monitors +the outcome of the test and reports about failures and successes. + +.. _`collection process`: + +Collectors and the test collection process +------------------------------------------ + +The collecting process is iterative, i.e. the session +traverses and generates a *collector tree*. Here is an example of such +a tree, generated with the command ``py.test --collectonly py/xmlobj``:: + + + + + + + + + + + + + + + + + +By default all directories not starting with a dot are traversed, +looking for ``test_*.py`` and ``*_test.py`` files. Those files +are imported under their `package name`_. + +.. _`collector API`: + +test items are collectors as well +--------------------------------- + +To make the reporting life simple for the session object +items offer a ``run()`` method as well. In fact the session +distinguishes "collectors" from "items" solely by interpreting +their return value. If it is a list, then we recurse into +it, otherwise we consider the "test" as passed. + +.. _`package name`: + +constructing the package name for modules +----------------------------------------- + +Test modules are imported under their fully qualified +name. Given a module ``path`` the fully qualified package +name is constructed as follows: + +* determine the last "upward" directory from ``path`` that + contains an ``__init__.py`` file. Going upwards + means repeatedly calling the ``dirpath()`` method + on a path object (which returns the parent directory + as a path object). + +* insert this base directory into the sys.path list + as its first element + +* import the root package + +* determine the fully qualified name for the module located + at ``path`` ... + + * if the imported root package has a __package__ object + then call ``__package__.getimportname(path)`` + + * otherwise use the relative path of the module path to + the base dir and turn slashes into dots and strike + the trailing ``.py``. + +The Module collector will eventually trigger +``__import__(mod_fqdnname, ...)`` to finally get to +the live module object. + +Side note: this whole logic is performed by local path +object's ``pyimport()`` method. + +Module Collector +----------------- + +The default Module collector looks for test functions +and test classes and methods. Test functions and methods +are prefixed ``test`` by default. Test classes must +start with a capitalized ``Test`` prefix. + + +Customizing the testing process +=============================== + +writing conftest.py files +----------------------------------- + +You may put conftest.py files containing project-specific +configuration in your project's root directory, it's usually +best to put it just into the same directory level as your +topmost ``__init__.py``. In fact, ``py.test`` performs +an "upwards" search starting from the directory that you specify +to be tested and will lookup configuration values right-to-left. +You may have options that reside e.g. in your home directory +but note that project specific settings will be considered +first. There is a flag that helps you debugging your +conftest.py configurations:: + + py.test --traceconfig + +adding custom options ++++++++++++++++++++++++ + +To register a project-specific command line option +you may have the following code within a ``conftest.py`` file:: + + import py + Option = py.test.config.Option + option = py.test.config.addoptions("pypy options", + Option('-V', '--view', action="store_true", dest="view", default=False, + help="view translation tests' flow graphs with Pygame"), + ) + +and you can then access ``option.view`` like this:: + + if option.view: + print "view this!" + +The option will be available if you type ``py.test -h`` +Note that you may only register upper case short +options. ``py.test`` reserves all lower +case short options for its own cross-project usage. + +customizing the collecting and running process +----------------------------------------------- + +To introduce different test items you can create +one or more ``conftest.py`` files in your project. +When the collection process traverses directories +and modules the default collectors will produce +custom Collectors and Items if they are found +in a local ``conftest.py`` file. + +example: perform additional ReST checks ++++++++++++++++++++++++++++++++++++++++ + +With your custom collectors or items you can completely +derive from the standard way of collecting and running +tests in a localized manner. Let's look at an example. +If you invoke ``py.test --collectonly py/documentation`` +then you get:: + + + + + + + + + + + + + + + + + ... + +In ``py/documentation/conftest.py`` you find the following +customization:: + + class DocDirectory(py.test.collect.Directory): + + def run(self): + results = super(DocDirectory, self).run() + for x in self.fspath.listdir('*.txt', sort=True): + results.append(x.basename) + return results + + def join(self, name): + if not name.endswith('.txt'): + return super(DocDirectory, self).join(name) + p = self.fspath.join(name) + if p.check(file=1): + return ReSTChecker(p, parent=self) + + Directory = DocDirectory + +The existence of the 'Directory' name in the +``pypy/documentation/conftest.py`` module makes the collection +process defer to our custom "DocDirectory" collector. We extend +the set of collected test items by ``ReSTChecker`` instances +which themselves create ``ReSTSyntaxTest`` and ``LinkCheckerMaker`` +items. All of this instances (need to) follow the `collector API`_. + +Customizing the reporting of Test Failures +-------------------------------------------- + +XXX implement Item.repr_run and Item.repr_path for your test items + +Writing new assertion methods +------------------------------------- + +XXX __tracebackhide__, and use "print" + + +Customizing the collection process in a module +---------------------------------------------- + + REPEATED WARNING: details of the collection and running process are + still subject to refactorings and thus details will change. + If you are customizing py.test at "Item" level then you + definitely want to be subscribed to the `py-dev mailing list`_ + to follow ongoing development. + +If you have a module where you want to take responsibility for +collecting your own test Items and possibly even for executing +a test then you can provide `generative tests`_ that yield +callables and possibly arguments as a tuple. This should +serve some immediate purposes like paramtrized tests. + +.. _`generative tests`: test.html#generative-tests + +The other extension possibility goes deeper into the machinery +and allows you to specify a custom test ``Item`` class which +is responsible for setting up and executing an underlying +test. [XXX not working: You can integrate your custom ``py.test.collect.Item`` subclass +by binding an ``Item`` name to a test class.] Or you can +extend the collection process for a whole directory tree +by putting Items in a ``conftest.py`` configuration file. +The collection process constantly looks at according names +in the *chain of conftest.py* modules to determine collectors +and items at ``Directory``, ``Module``, ``Class``, ``Function`` +or ``Generator`` level. Note that, right now, except for ``Function`` +items all classes are pure collectors, i.e. will return a list +of names (possibly empty). + +XXX implement doctests as alternatives to ``Function`` items. + +Customizing execution of Functions +---------------------------------- + +- Function test items allow total control of executing their + contained test method. ``function.run()`` will get called by the + session in order to actually run a test. The method is responsible + for performing proper setup/teardown ("Test Fixtures") for a + Function test. + +- ``Function.execute(target, *args)`` methods are invoked by + the default ``Function.run()`` to actually execute a python + function with the given (usually empty set of) arguments. + + +.. _`py-dev mailing list`: http://codespeak.net/mailman/listinfo/py-dev diff --git a/py/doc/index.txt b/py/doc/index.txt new file mode 100644 index 000000000..fad599e33 --- /dev/null +++ b/py/doc/index.txt @@ -0,0 +1,62 @@ +py lib documentation +================================================= + + The py lib aims at supporting a decent development process + addressing deployment, versioning, testing and documentation + perspectives. + +`Download and Installation`_ + +`0.9.0 release announcement`_ + +Main tools and API +---------------------- + +`py.test`_ introduces to the **py.test** testing utility. + +`py.execnet`_ distributes programs across the net. + +`py.magic.greenlet`_: micro-threads (lightweight in-process concurrent programming) + +`py.path`_: local and subversion Path and Filesystem access + +`py lib scripts`_ describe the scripts contained in the ``py/bin`` directory. + +`apigen`_: a new way to generate rich Python API documentation + +support functionality +--------------------------------- + +`py.code`_: High-level access/manipulation of Python code and traceback objects. + +`py.xml`_ for generating in-memory xml/html object trees + +`py.io`_: Helper Classes for Capturing of Input/Output + +`py.log`_: an alpha document about the ad-hoc logging facilities + +`miscellaneous features`_ describes some small but nice py lib features. + +Background and Motivation information +------------------------------------------- + +`future`_ handles development visions and plans for the near future. + +`why what how py?`_, describing motivation and background of the py lib. + +.. _`download and installation`: download.html +.. _`py-dev at codespeak net`: http://codespeak.net/mailman/listinfo/py-dev +.. _`py.execnet`: execnet.html +.. _`py.magic.greenlet`: greenlet.html +.. _`apigen`: apigen.html +.. _`py.log`: log.html +.. _`py.io`: io.html +.. _`py.path`: path.html +.. _`py.code`: code.html +.. _`py.test`: test.html +.. _`py lib scripts`: bin.html +.. _`py.xml`: xml.html +.. _`Why What how py?`: why_py.html +.. _`future`: future.html +.. _`miscellaneous features`: misc.html +.. _`0.9.0 release announcement`: release-0.9.0.html diff --git a/py/doc/io.txt b/py/doc/io.txt new file mode 100644 index 000000000..ec738b92d --- /dev/null +++ b/py/doc/io.txt @@ -0,0 +1,45 @@ +======= +py.io +======= + +.. contents:: +.. sectnum:: + +The 'py' lib provides helper classes for capturing IO during +execution of a program. + +IO Capturing examples +=============================================== + +:api:`py.io.StdCapture` +--------------------------- + +Basic Example: + + >>> import py + >>> capture = py.io.StdCapture() + >>> print "hello" + >>> out,err = capture.reset() + >>> out.strip() == "hello" + True + +For calling functions you may use a shortcut: + + >>> import py + >>> def f(): print "hello" + >>> res, out, err = py.io.StdCapture.call(f) + >>> out.strip() == "hello" + True + +:api:`py.io.StdCaptureFD` +--------------------------- + +If you also want to capture writes to the stdout/stderr +filedescriptors you may invoke: + + >>> import py, sys + >>> capture = py.io.StdCaptureFD() + >>> sys.stderr.write("world") + >>> out,err = capture.reset() + >>> err + 'world' diff --git a/py/doc/links.txt b/py/doc/links.txt new file mode 100644 index 000000000..45a41227d --- /dev/null +++ b/py/doc/links.txt @@ -0,0 +1,33 @@ + +===== +Links +===== + +Some links to ongoing discussions and comments about pylib and technics/concepts pylib uses. + +* `Discussion `_ + about site-packages. That's why pylib autopath and py.__.misc.dynpkg are a good idea ;-) + + +* `Pyinotify `_ uses code from pypy autopath functions. + +* `Testing (WSGI) Applications with Paste `_ and py.test. "This has been written with py.test in mind." Paste uses py.test. + + +* `Agile Testing `_ by Grig Gheorghiu + + * `Slides from 'py library overview' presentation at SoCal Piggies meeting + `_ + + * `Python unit testing part 3: the py.test tool and library + `_ + + * `greenlets and py.xml + `_ + + * `Keyword-based logging with the py library + `_ + + + + diff --git a/py/doc/log.txt b/py/doc/log.txt new file mode 100644 index 000000000..a1cbc3f71 --- /dev/null +++ b/py/doc/log.txt @@ -0,0 +1,208 @@ +.. role:: code(strong) +.. role:: file(literal) + +======================================== +:code:`py.log` documentation and musings +======================================== + +.. contents:: +.. sectnum:: + +Foreword +======== + +This document is an attempt to briefly state the actual specification of the +:code:`py.log` module. It was written by Francois Pinard and also contains +some ideas for enhancing the py.log facilities. + +NOTE that `py.log` is subject to refactorings, it may change with +the next release. + +This document is meant to trigger or facilitate discussions. It shamelessly +steals from the `Agile Testing`__ comments, and from other sources as well, +without really trying to sort them out. + +__ http://agiletesting.blogspot.com/2005/06/keyword-based-logging-with-py-library.html + + +Logging organisation +==================== + +The :code:`py.log` module aims a niche comparable to the one of the +`logging module`__ found within the standard Python distributions, yet +with much simpler paradigms for configuration and usage. + +__ http://www.python.org/doc/2.4.2/lib/module-logging.html + +Holger Krekel, the main :code:`py` library developer, introduced +the idea of keyword-based logging and the idea of logging *producers* and +*consumers*. A log producer is an object used by the application code +to send messages to various log consumers. When you create a log +producer, you define a set of keywords that are then used to both route +the logging messages to consumers, and to prefix those messages. + +In fact, each log producer has a few keywords associated with it for +identification purposes. These keywords form a tuple of strings, and +may be used to later retrieve a particular log producer. + +A log producer may (or may not) be associated with a log consumer, meant +to handle log messages in particular ways. The log consumers can be +``STDOUT``, ``STDERR``, log files, syslog, the Windows Event Log, user +defined functions, etc. (Yet, logging to syslog or to the Windows Event +Log is only future plans for now). A log producer has never more than +one consumer at a given time, but it is possible to dynamically switch +a producer to use another consumer. On the other hand, a single log +consumer may be associated with many producers. + +Note that creating and associating a producer and a consumer is done +automatically when not otherwise overriden, so using :code:`py` logging +is quite comfortable even in the smallest programs. More typically, +the application programmer will likely design a hierarchy of producers, +and will select keywords appropriately for marking the hierarchy tree. +If a node of the hierarchical tree of producers has to be divided in +sub-trees, all producers in the sub-trees share, as a common prefix, the +keywords of the node being divided. In other words, we go further down +in the hierarchy of producers merely by adding keywords. + +Using the :code:`py.log` library +================================ + +To use the :code:`py.log` library, the user must import it into a Python +application, create at least one log producer and one log consumer, have +producers and consumers associated, and finally call the log producers +as needed, giving them log messages. + +Importing +--------- + +Once the :code:`py` library is installed on your system, a mere:: + + import py + +holds enough magic for lazily importing the various facilities of the +:code:`py` library when they are first needed. This is really how +:code:`py.log` is made available to the application. For example, after +the above ``import py``, one may directly write ``py.log.Producer(...)`` +and everything should work fine, the user does not have to worry about +specifically importing more modules. + +Creating a producer +------------------- + +There are three ways for creating a log producer instance: + + + As soon as ``py.log`` is first evaluated within an application + program, a default log producer is created, and made available under + the name ``py.log.default``. The keyword ``default`` is associated + with that producer. + + + The ``py.log.Producer()`` constructor may be explicitly called + for creating a new instance of a log producer. That constructor + accepts, as an argument, the keywords that should be associated with + that producer. Keywords may be given either as a tuple of keyword + strings, or as a single space-separated string of keywords. + + + Whenever an attribute is *taken* out of a log producer instance, + for the first time that attribute is taken, a new log producer is + created. The keywords associated with that new producer are those + of the initial producer instance, to which is appended the name of + the attribute being taken. + +The last point is especially useful, as it allows using log producers +without further declarations, merely creating them *on-the-fly*. + +Creating a consumer +------------------- + +There are many ways for creating or denoting a log consumer: + + + A default consumer exists within the ``py.log`` facilities, which + has the effect of writing log messages on the Python standard output + stream. That consumer is associated at the very top of the producer + hierarchy, and as such, is called whenever no other consumer is + found. + + + The notation ``py.log.STDOUT`` accesses a log consumer which writes + log messages on the Python standard output stream. + + + The notation ``py.log.STDERR`` accesses a log consumer which writes + log messages on the Python standard error stream. + + + The ``py.log.File()`` constructor accepts, as argument, either a file + already opened in write mode or any similar file-like object, and + creates a log consumer able to write log messages onto that file. + + + The ``py.log.Path()`` constructor accepts a file name for its first + argument, and creates a log consumer able to write log messages into + that file. The constructor call accepts a few keyword parameters: + + + ``append``, which is ``False`` by default, may be used for + opening the file in append mode instead of write mode. + + + ``delayed_create``, which is ``False`` by default, maybe be used + for opening the file at the latest possible time. Consequently, + the file will not be created if it did not exist, and no actual + log message gets written to it. + + + ``buffering``, which is 1 by default, is used when opening the + file. Buffering can be turned off by specifying a 0 value. The + buffer size may also be selected through this argument. + + + Any user defined function may be used for a log consumer. Such a + function should accept a single argument, which is the message to + write, and do whatever is deemed appropriate by the programmer. + When the need arises, this may be an especially useful and flexible + feature. + + + The special value ``None`` means no consumer at all. This acts just + like if there was a consumer which would silently discard all log + messages sent to it. + +Associating producers and consumers +----------------------------------- + +Each log producer may have at most one log consumer associated with +it. A log producer gets associated with a log consumer through a +``py.log.set_consumer()`` call. That function accepts two arguments, +the first identifying a producer (a tuple of keyword strings or a single +space-separated string of keywords), the second specifying the precise +consumer to use for that producer. Until this function is called for a +producer, that producer does not have any explicit consumer associated +with it. + +Now, the hierarchy of log producers establishes which consumer gets used +whenever a producer has no explicit consumer. When a log producer +has no consumer explicitly associated with it, it dynamically and +recursively inherits the consumer of its parent node, that is, that node +being a bit closer to the root of the hierarchy. In other words, the +rightmost keywords of that producer are dropped until another producer +is found which has an explicit consumer. A nice side-effect is that, +by explicitly associating a consumer with a producer, all consumer-less +producers which appear under that producer, in the hierarchy tree, +automatically *inherits* that consumer. + +Writing log messages +-------------------- + +All log producer instances are also functions, and this is by calling +them that log messages are generated. Each call to a producer object +produces the text for one log entry, which in turn, is sent to the log +consumer for that producer. + +The log entry displays, after a prefix identifying the log producer +being used, all arguments given in the call, converted to strings and +space-separated. (This is meant by design to be fairly similar to what +the ``print`` statement does in Python). The prefix itself is made up +of a colon-separated list of keywords associated with the producer, the +whole being set within square brackets. + +Note that the consumer is responsible for adding the newline at the end +of the log entry. That final newline is not part of the text for the +log entry. + +Other details +------------- + ++ Should speak about pickle-ability of :code:`py.log`. + ++ What is :code:`log.get` (in :file:`logger.py`)? diff --git a/py/doc/misc.txt b/py/doc/misc.txt new file mode 100644 index 000000000..befa5493e --- /dev/null +++ b/py/doc/misc.txt @@ -0,0 +1,220 @@ +==================================== +Miscellaneous features of the py lib +==================================== + +.. contents:: +.. sectnum:: + +Mapping the standard python library into py +=========================================== + + Warning: This feature is very young and thus experimental. + Be prepared to adapt your code later if you use it. + +After you have worked with the py lib a bit, you might enjoy +the lazy importing, i.e. you only have to do ``import py`` and +work your way to your desired object. Using the full path +also ensures that there remains a focus on getting short paths +to objects. + +The :api:`py.std` hook +---------------------- + +Of course, no matter what, everybody will continue to use the +python standard library because it is a very usable code base. +However, to properly support lazyness the py lib offers a way +to get to many standard modules without requiring "import" +statements. For example, to get to the print-exception +functionality of the standard library you can write:: + + py.std.traceback.print_exc() + +without having to do anything else than the usual ``import py`` +at the beginning. Note that not having imports for the +`python standard library` obviously gets rid of the *unused +import* problem. Modules only get imported when you actually +need them. + +Moreover, this approach resolves some of the issues stated in +`the relative/absolute import PEP-328`_, as with the above +approach you never have ambiguity problems. The above +traceback-usage is an absolute path that will not be +accidentally get confused with local names. (Well, never put +a file ``py.py`` in an importable path, btw, mind you :-) + +Automagically accessing sub packages doesn't work (yet?) +-------------------------------------------------------- + +If you use the :api:`py.std` hook you currently cannot magically +import nested packages which otherwise need explicit imports of +their sub-packages. For example, the suversion bindings +require you to do something like:: + + import svn.client + +If you just do the naive thing with the py lib, i.e. write +``py.std.svn.client`` it will not work unless you previously +imported it already. The py lib currently doesn't try to +magically make this work. The :api:`py.std` hook really is +intended for Python standard modules which very seldomly (if +at all) provide such nested packages. + +**Note that you may never rely** on module identity, i.e. +that ``X is py.std.X`` for any ``X``. This is to allow +us later to lazyly import nested packages. Yes, lazyness +is hard to resist :-) + +Note: you get an AttributeError, not an ImportError +--------------------------------------------------- + +If you say ``py.std.XYZ`` and importing ``XYZ`` produces an +``ImportError`` , it will actually show up as an +``AttributeError``. It is deemed more important to adhere to +the standard ``__getattr__`` protocol than to let the +``ImportError`` pass through. For example, you might want to +do:: + + getattr(py.std.cStringIO, 'StringIO', py.std.StringIO.StringIO) + +and you would expect that it works. It does work although it will +take away some lazyness because ``py.std.StringIO.StringIO`` will +be imported in any case. + +.. _`the relative/absolute import PEP-328`: http://www.python.org/peps/pep-0328.html + +Support for interaction with system utilities/binaries +====================================================== + +sources: + + * :source:`py/process/` + * :source:`py/path/local/` + +Currently, the py lib offers two ways to interact with +system executables. :api:`py.process.cmdexec()` invokes +the shell in order to execute a string. The other +one, :api:`py.path.local`'s 'sysexec()' method lets you +directly execute a binary. + +Both approaches will raise an exception in case of a return- +code other than 0 and otherwise return the stdout-output +of the child process. + +The shell based approach +------------------------ + +You can execute a command via your system shell +by doing something like:: + + out = py.process.cmdexec('ls -v') + +However, the ``cmdexec`` approach has a few shortcomings: + +- it relies on the underlying system shell +- it neccessitates shell-escaping for expressing arguments +- it does not easily allow to "fix" the binary you want to run. +- it only allows to execute executables from the local + filesystem + +.. _sysexec: + +local paths have ``sysexec`` +---------------------------- + +The py lib currently offers a stripped down functionality of what +the new `PEP-324 subprocess module`_ offers. The main functionality +of synchronously executing a system executable has a straightforward API:: + + binsvn.sysexec('ls', 'http://codespeak.net/svn') + +where ``binsvn`` is a path that points to the ``svn`` commandline +binary. Note that this function would not offer any shell-escaping +so you really have to pass in separated arguments. This idea +fits nicely into `a more general view on path objects`_. + +For a first go, we are just reusing the existing `subprocess +implementation`_ but don't expose any of its API apart +from the above ``sysexec()`` method. + +Note, however, that currently the support for the ``sysexec`` interface on +win32 is not thoroughly tested. If you run into problems with it, we are +interested to hear about them. If you are running a Python older than 2.4 you +will have to install the `pywin32 package`_. + + +.. _`future book`: future.html +.. _`PEP-324 subprocess module`: http://www.python.org/peps/pep-0324.html +.. _`subprocess implementation`: http://www.lysator.liu.se/~astrand/popen5/ +.. _`a more general view on path objects`: future.html#general-path +.. _`pywin32 package`: http://pywin32.sourceforge.net/ + +finding an executable local path +-------------------------------- + +Finding an executable is quite different on multiple platforms. +Currently, the ``PATH`` environment variable based search on +unix platforms is supported:: + + py.path.local.sysfind('svn') + +which returns the first path whose ``basename`` matches ``svn``. +In principle, `sysfind` deploys platform specific algorithms +to perform the search. On Windows, for example, it may look +at the registry (XXX). + +To make the story complete, we allow to pass in a second ``checker`` +argument that is called for each found executable. For example, if +you have multiple binaries available you may want to select the +right version:: + + def mysvn(p): + """ check that the given svn binary has version 1.1. """ + line = p.execute('--version'').readlines()[0] + if line.find('version 1.1'): + return p + binsvn = py.path.local.sysfind('svn', checker=mysvn) + + + +Cross-Python Version compatibility helpers +============================================= + +sources: + + * :source:`py/compat/` + * :source:`py/builtin/` + +The py-lib contains some helpers that make writing scripts that work on various +Python versions easier. + +:api:`py.compat` +---------------- + +:api:`py.compat` provides fixed versions (currently from Python 2.4.4) of +various newer modules to be able to use them in various Python versions. +Currently these are: + + * doctest + * optparse + * subprocess + * textwrap + +They are used by replacing the normal ``import ...`` byr +``from py.compat import ...``. + +:api:`py.builtin` +----------------- + +:api:`py.builtin` provides various builtins that were added in later Python +versions. If the used Python version used does not provide these builtins, they +are pure-Python reimplementations. These currently are: + + * enumerate + * reversed + * sorted + * BaseException + * set and frozenset (using either the builtin, if available, or the sets + module) + +:api:`py.builtin.BaseException` is just ``Exception`` before Python 2.5. + diff --git a/py/doc/path.txt b/py/doc/path.txt new file mode 100644 index 000000000..aae977c9c --- /dev/null +++ b/py/doc/path.txt @@ -0,0 +1,258 @@ +======= +py.path +======= + +.. contents:: +.. sectnum:: + +The 'py' lib provides a uniform high-level api to deal with filesystems +and filesystem-like interfaces: :api:`py.path`. It aims to offer a central +object to fs-like object trees (reading from and writing to files, adding +files/directories, examining the types and structure, etc.), and out-of-the-box +provides a number of implementations of this API. + +Path implementations provided by :api:`py.path` +=============================================== + +:api:`py.path.local` +-------------------- + +The first and most obvious of the implementations is a wrapper around a local +filesystem. It's just a bit nicer in usage than the regular Python APIs, and +of course all the functionality is bundled together rather than spread over a +number of modules. + +Example usage, here we use the :api:`py.test.ensuretemp()` function to create +a :api:`py.path.local` object for us (which wraps a directory):: + + >>> import py + >>> temppath = py.test.ensuretemp('py.path_documentation') + >>> foopath = temppath.join('foo') # get child 'foo' (lazily) + >>> foopath.check() # check if child 'foo' exists + False + >>> foopath.write('bar') # write some data to it + >>> foopath.check() + True + >>> foopath.read() + 'bar' + >>> foofile = foopath.open() # return a 'real' file object + >>> foofile.read(1) + 'b' + +:api:`py.path.svnurl` and :api:`py.path.svnwc` +---------------------------------------------- + +Two other :api:`py.path` implementations that the py lib provides wrap the +popular `Subversion`_ revision control system: the first (called 'svnurl') +by interfacing with a remote server, the second by wrapping a local checkout. +Both allow you to access relatively advanced features such as metadata and +versioning, and both in a way more user-friendly manner than existing other +solutions. + +Some example usage of :api:`py.path.svnurl`:: + + .. >>> import py + .. >>> if not py.test.config.option.checkremote: raise ValueError('skipchunk') + >>> url = py.path.svnurl('http://codespeak.net/svn/py') + >>> info = url.info() + >>> info.kind + 'dir' + >>> firstentry = url.log()[-1] + >>> import time + >>> time.strftime('%Y-%m-%d', time.gmtime(firstentry.date)) + '2004-10-02' + +Example usage of :api:`py.path.svnwc`:: + + .. >>> if not py.test.config.option.checkremote: raise ValueError('skipchunk') + >>> temp = py.test.ensuretemp('py.path_documentation') + >>> wc = py.path.svnwc(temp.join('svnwc')) + >>> wc.checkout('http://codespeak.net/svn/py/dist/py/path/local') + >>> wc.join('local.py').check() + True + +.. _`Subversion`: http://subversion.tigris.org/ + +Common vs. specific API +======================= + +All Path objects support a common set of operations, suitable +for many use cases and allowing to transparently switch the +path object within an application (e.g. from "local" to "svnwc"). +The common set includes functions such as `path.read()` to read all data +from a file, `path.write()` to write data, `path.listdir()` to get a list +of directory entries, `path.check()` to check if a node exists +and is of a particular type, `path.join()` to get +to a (grand)child, `path.visit()` to recursively walk through a node's +children, etc. Only things that are not common on 'normal' filesystems (yet), +such as handling metadata (e.g. the Subversion "properties") require +using specific APIs. + +Examples +--------------------------------- + +A quick 'cookbook' of small examples that will be useful 'in real life', +which also presents parts of the 'common' API, and shows some non-common +methods: + +Searching `.txt` files ++++++++++++++++++++++++++++++++++++++ + +Search for a particular string inside all files with a .txt extension in a +specific directory. + +:: + + >>> dirpath = temppath.ensure('testdir', dir=True) + >>> dirpath.join('textfile1.txt').write('foo bar baz') + >>> dirpath.join('textfile2.txt').write('frob bar spam eggs') + >>> subdir = dirpath.ensure('subdir', dir=True) + >>> subdir.join('textfile1.txt').write('foo baz') + >>> subdir.join('textfile2.txt').write('spam eggs spam foo bar spam') + >>> results = [] + >>> for fpath in dirpath.visit('*.txt'): + ... if 'bar' in fpath.read(): + ... results.append(fpath.basename) + >>> results.sort() + >>> results + ['textfile1.txt', 'textfile2.txt', 'textfile2.txt'] + +Working with Paths +++++++++++++++++++++ + +This example shows the :api:`py.path` features to deal with +filesystem paths Note that the filesystem is never touched, +all operations are performed on a string level (so the paths +don't have to exist, either):: + + >>> p1 = py.path.local('/foo/bar') + >>> p2 = p1.join('baz/qux') + >>> p2 == py.path.local('/foo/bar/baz/qux') + True + >>> sep = py.path.local.sep + >>> p2.relto(p1).replace(sep, '/') # os-specific path sep in the string + 'baz/qux' + >>> p3 = p1 / 'baz/qux' # the / operator allows joining, too + >>> p2 == p3 + True + >>> p4 = p1 + ".py" + >>> p4.basename == "bar.py" + True + >>> p4.ext == ".py" + True + >>> p4.purebasename == "bar" + True + +This should be possible on every implementation of :api:`py.path`, so +regardless of whether the implementation wraps a UNIX filesystem, a Windows +one, or a database or object tree, these functions should be available (each +with their own notion of path seperators and dealing with conversions, etc.). + +Checking path types ++++++++++++++++++++++ + +Now we will show a bit about the powerful 'check()' method on paths, which +allows you to check whether a file exists, what type it is, etc.:: + + >>> file1 = temppath.join('file1') + >>> file1.check() # does it exist? + False + >>> file1 = file1.ensure(file=True) # 'touch' the file + >>> file1.check() + True + >>> file1.check(dir=True) # is it a dir? + False + >>> file1.check(file=True) # or a file? + True + >>> file1.check(ext='.txt') # check the extension + False + >>> textfile = temppath.ensure('text.txt', file=True) + >>> textfile.check(ext='.txt') + True + >>> file1.check(basename='file1') # we can use all the path's properties here + True + +Setting svn-properties ++++++++++++++++++++++++++++++++++++++++ + +As an example of 'uncommon' methods, we'll show how to read and write +properties in an :api:`py.path.svnwc` instance:: + + .. >>> if not py.test.config.option.checkremote: raise ValueError('skipchunk') + >>> wc.propget('foo') + '' + >>> wc.propset('foo', 'bar') + >>> wc.propget('foo') + 'bar' + >>> len(wc.status().prop_modified) # our own props + 1 + >>> msg = wc.revert() # roll back our changes + >>> len(wc.status().prop_modified) + 0 + +SVN authentication +++++++++++++++++++++++ + +Some uncommon functionality can also be provided as extensions, such as SVN +authentication:: + + .. >>> if not py.test.config.option.checkremote: raise ValueError('skipchunk') + >>> auth = py.path.SvnAuth('anonymous', 'user', cache_auth=False, + ... interactive=False) + >>> wc.auth = auth + >>> wc.update() # this should work + >>> path = wc.ensure('thisshouldnotexist.txt') + >>> try: + ... path.commit('testing') + ... except py.process.cmdexec.Error, e: + ... pass + >>> 'authorization failed' in str(e) + True + +Known problems / limitations +=================================== + +* The SVN path objects require the "svn" command line, + there is currently no support for python bindings. + Parsing the svn output can lead to problems, particularly + regarding if you have a non-english "locales" setting. + +* While the path objects basically work on windows, + there is no attention yet on making unicode paths + work or deal with the famous "8.3" filename issues. + +Future plans +============ + +The Subversion path implementations are based +on the `svn` command line, not on the bindings. +It makes sense now to directly use the bindings. + +Moreover, it would be good, also considering +`py.execnet`_ distribution of programs, to +be able to manipulate Windows Paths on Linux +and vice versa. So we'd like to consider +refactoring the path implementations +to provide this choice (and getting rid +of platform-dependencies as much as possible). + +There is some experimental small approach +(:source:`py/path/gateway/`) aiming at having +a convenient Remote Path implementation +and some considerations about future +works in the according :source:`py/path/gateway/TODO.txt` + +There are various hacks out there to have +Memory-Filesystems and even path objects +being directly mountable under Linux (via `fuse`). +However, the Path object implementations +do not internally have a clean abstraction +of going to the filesystem - so with some +refactoring it should become easier to +have very custom Path objects, still offering +the quite full interface without requiring +to know about all details of the full path +implementation. + +.. _`py.execnet`: execnet.html + diff --git a/py/doc/release-0.9.0.txt b/py/doc/release-0.9.0.txt new file mode 100644 index 000000000..8498cc4db --- /dev/null +++ b/py/doc/release-0.9.0.txt @@ -0,0 +1,31 @@ +py lib 0.9.0: py.test, distributed execution, greenlets and more +====================================================================== + +Welcome to the 0.9.0 py lib release - a library aiming to +support agile and test-driven python development on various levels. + +Main API/Tool Features: + +* py.test: cross-project testing tool with many advanced features +* py.execnet: ad-hoc code distribution to SSH, Socket and local sub processes +* py.magic.greenlet: micro-threads on standard CPython ("stackless-light") +* py.path: path abstractions over local and subversion files +* rich documentation of py's exported API +* tested against Linux, OSX and partly against Win32, python 2.3-2.5 + +All these features and their API have extensive documentation, +generated with the new "apigen", which we intend to make accessible +for other python projects as well. + +Download/Install: http://codespeak.net/py/0.9.0/download.html +Documentation/API: http://codespeak.net/py/0.9.0/index.html + +Work on the py lib has been partially funded by the +European Union IST programme and by http://merlinux.de +within the PyPy project. + +best, have fun and let us know what you think! + +holger krekel, Maciej Fijalkowski, +Guido Wesdorp, Carl Friedrich Bolz + diff --git a/py/doc/style.css b/py/doc/style.css new file mode 100644 index 000000000..3653dd1c1 --- /dev/null +++ b/py/doc/style.css @@ -0,0 +1,1078 @@ +body,body.editor,body.body { + font: 110% "Times New Roman", Arial, Verdana, Helvetica, serif; + background: White; + color: Black; +} + +a, a.reference { + text-decoration: none; +} +a[href]:hover { text-decoration: underline; } + +img { + border: none; + vertical-align: middle; +} + +p, div.text { + text-align: left; + line-height: 1.5em; + margin: 0.5em 0em 0em 0em; +} + + + +p a:active { + color: Red; + background-color: transparent; +} + +p img { + border: 0; + margin: 0; +} + +img.inlinephoto { + padding: 0; + padding-right: 1em; + padding-top: 0.7em; + float: left; +} + +hr { + clear: both; + height: 1px; + color: #8CACBB; + background-color: transparent; +} + + +ul { + line-height: 1.5em; + /*list-style-image: url("bullet.gif"); */ + margin-left: 1.5em; + padding:0; +} + +ol { + line-height: 1.5em; + margin-left: 1.5em; + padding:0; +} + +ul a, ol a { + text-decoration: underline; +} + +dl { +} + +dt { + font-weight: bold; +} + +dd { + line-height: 1.5em; + margin-bottom: 1em; +} + +blockquote { + font-family: Times, "Times New Roman", serif; + font-style: italic; + font-size: 120%; +} + +code { + color: Black; + /*background-color: #dee7ec;*/ + background-color: #cccccc; +} + +pre { + padding: 1em; + border: 1px solid #8cacbb; + color: Black; + background-color: #dee7ec; + background-color: #cccccc; + overflow: auto; +} + + +.netscape4 { + display: none; +} + +/* main page styles */ + +/*a[href]:hover { color: black; text-decoration: underline; } +a[href]:link { color: black; text-decoration: underline; } +a[href] { color: black; text-decoration: underline; } +*/ + +span.menu_selected { + color: black; + font: 140% Verdana, Helvetica, Arial, sans-serif; + text-decoration: none; + padding-right: 0.3em; + background-color: #cccccc; +} + + +a.menu { + /*color: #3ba6ec; */ + font: 140% Verdana, Helvetica, Arial, sans-serif; + text-decoration: none; + padding-right: 0.3em; +} + +a.menu[href]:visited, a.menu[href]:link{ + /*color: #3ba6ec; */ + font: 140% Verdana, Helvetica, Arial, sans-serif; + text-decoration: none; +} + +a.menu[href]:hover { + /*color: black;*/ +} + +div.project_title{ + /*border-spacing: 20px;*/ + font: 160% Verdana, Helvetica, Arial, sans-serif; + color: #3ba6ec; + vertical-align: middle; + padding-bottom: 0.3em; +} + +a.wikicurrent { + font: 100% Verdana, Helvetica, Arial, sans-serif; + color: #3ba6ec; + vertical-align: middle; +} + + +table.body { + border: 0; + /*padding: 0; + border-spacing: 0px; + border-collapse: separate; + */ +} + +td.page-header-left { + padding: 5px; + /*border-bottom: 1px solid #444444;*/ +} + +td.page-header-top { + padding: 0; + + /*border-bottom: 1px solid #444444;*/ +} + +td.sidebar { + padding: 1 0 0 1; +} + +td.sidebar p.classblock { + padding: 0 5 0 5; + margin: 1 1 1 1; + border: 1px solid #444444; + background-color: #eeeeee; +} + +td.sidebar p.userblock { + padding: 0 5 0 5; + margin: 1 1 1 1; + border: 1px solid #444444; + background-color: #eeeeff; +} + +td.content { + padding: 1 5 1 5; + vertical-align: top; + width: 100%; +} + +p.ok-message { + background-color: #22bb22; + padding: 5 5 5 5; + color: white; + font-weight: bold; +} +p.error-message { + background-color: #bb2222; + padding: 5 5 5 5; + color: white; + font-weight: bold; +} + +p:first-child { + margin: 0 ; + padding: 0; +} + +/* style for forms */ +table.form { + padding: 2; + border-spacing: 0px; + border-collapse: separate; +} + +table.form th { + color: #333388; + text-align: right; + vertical-align: top; + font-weight: normal; +} +table.form th.header { + font-weight: bold; + background-color: #eeeeff; + text-align: left; +} + +table.form th.required { + font-weight: bold; +} + +table.form td { + color: #333333; + empty-cells: show; + vertical-align: top; +} + +table.form td.optional { + font-weight: bold; + font-style: italic; +} + +table.form td.html { + color: #777777; +} + +/* style for lists */ +table.list { + border-spacing: 0px; + border-collapse: separate; + vertical-align: top; + padding-top: 0; + width: 100%; +} + +table.list th { + padding: 0 4 0 4; + color: #404070; + background-color: #eeeeff; + border-right: 1px solid #404070; + border-top: 1px solid #404070; + border-bottom: 1px solid #404070; + vertical-align: top; + empty-cells: show; +} +table.list th a[href]:hover { color: #404070 } +table.list th a[href]:link { color: #404070 } +table.list th a[href] { color: #404070 } +table.list th.group { + background-color: #f4f4ff; + text-align: center; + font-size: 120%; +} + +table.list td { + padding: 0 4 0 4; + border: 0 2 0 2; + border-right: 1px solid #404070; + color: #404070; + background-color: white; + vertical-align: top; + empty-cells: show; +} + +table.list tr.normal td { + background-color: white; + white-space: nowrap; +} + +table.list tr.alt td { + background-color: #efefef; + white-space: nowrap; +} + +table.list td:first-child { + border-left: 1px solid #404070; + border-right: 1px solid #404070; +} + +table.list th:first-child { + border-left: 1px solid #404070; + border-right: 1px solid #404070; +} + +table.list tr.navigation th { + text-align: right; +} +table.list tr.navigation th:first-child { + border-right: none; + text-align: left; +} + + +/* style for message displays */ +table.messages { + border-spacing: 0px; + border-collapse: separate; + width: 100%; +} + +table.messages th.header{ + padding-top: 10px; + border-bottom: 1px solid gray; + font-weight: bold; + background-color: white; + color: #707040; +} + +table.messages th { + font-weight: bold; + color: black; + text-align: left; + border-bottom: 1px solid #afafaf; +} + +table.messages td { + font-family: monospace; + background-color: #efefef; + border-bottom: 1px solid #afafaf; + color: black; + empty-cells: show; + border-right: 1px solid #afafaf; + vertical-align: top; + padding: 2 5 2 5; +} + +table.messages td:first-child { + border-left: 1px solid #afafaf; + border-right: 1px solid #afafaf; +} + +/* style for file displays */ +table.files { + border-spacing: 0px; + border-collapse: separate; + width: 100%; +} + +table.files th.header{ + padding-top: 10px; + border-bottom: 1px solid gray; + font-weight: bold; + background-color: white; + color: #707040; +} + +table.files th { + border-bottom: 1px solid #afafaf; + font-weight: bold; + text-align: left; +} + +table.files td { + font-family: monospace; + empty-cells: show; +} + +/* style for history displays */ +table.history { + border-spacing: 0px; + border-collapse: separate; + width: 100%; +} + +table.history th.header{ + padding-top: 10px; + border-bottom: 1px solid gray; + font-weight: bold; + background-color: white; + color: #707040; + font-size: 100%; +} + +table.history th { + border-bottom: 1px solid #afafaf; + font-weight: bold; + text-align: left; + font-size: 90%; +} + +table.history td { + font-size: 90%; + vertical-align: top; + empty-cells: show; +} + + +/* style for class list */ +table.classlist { + border-spacing: 0px; + border-collapse: separate; + width: 100%; +} + +table.classlist th.header{ + padding-top: 10px; + border-bottom: 1px solid gray; + font-weight: bold; + background-color: white; + color: #707040; +} + +table.classlist th { + font-weight: bold; + text-align: left; +} + + +/* style for class help display */ +table.classhelp { + border-spacing: 0px; + border-collapse: separate; + width: 100%; +} + +table.classhelp th { + font-weight: bold; + text-align: left; + color: #707040; +} + +table.classhelp td { + padding: 2 2 2 2; + border: 1px solid black; + text-align: left; + vertical-align: top; + empty-cells: show; +} + + +/* style for "other" displays */ +table.otherinfo { + border-spacing: 0px; + border-collapse: separate; + width: 100%; +} + +table.otherinfo th.header{ + padding-top: 10px; + border-bottom: 1px solid gray; + font-weight: bold; + background-color: white; + color: #707040; +} + +table.otherinfo th { + border-bottom: 1px solid #afafaf; + font-weight: bold; + text-align: left; +} + +input { + border: 1px solid #8cacbb; + color: Black; + background-color: white; + vertical-align: middle; + margin-bottom: 1px; /* IE bug fix */ + padding: 0.1em; +} + +select { + border: 1px solid #8cacbb; + color: Black; + background-color: white; + vertical-align: middle; + margin-bottom: 1px; /* IE bug fix */ + padding: 0.1em; +} + + +a.nonexistent { + color: #FF2222; +} +a.nonexistent:visited { + color: #FF2222; +} +a.external { + color: #AA6600; +} + +/* +dl,ul,ol { + margin-top: 1pt; +} +tt,pre { + font-family: Lucida Console,Courier New,Courier,monotype; + font-size: 12pt; +} +pre.code { + margin-top: 8pt; + margin-bottom: 8pt; + background-color: #FFFFEE; + white-space:pre; + border-style:solid; + border-width:1pt; + border-color:#999999; + color:#111111; + padding:5px; + width:100%; +} +*/ +div.diffold { + background-color: #FFFF80; + border-style:none; + border-width:thin; + width:100%; +} +div.diffnew { + background-color: #80FF80; + border-style:none; + border-width:thin; + width:100%; +} +div.message { + margin-top: 6pt; + background-color: #E8FFE8; + border-style:solid; + border-width:1pt; + border-color:#999999; + color:#440000; + padding:5px; + width:100%; +} +strong.highlight { + background-color: #FFBBBB; +/* as usual, NetScape fucks up with innocent CSS + border-color: #FFAAAA; + border-style: solid; + border-width: 1pt; +*/ +} + +table.navibar { + background-color: #C8C8C8; + border-spacing: 3px; +} +td.navibar { + background-color: #E8E8E8; + vertical-align: top; + text-align: right; + padding: 0px; +} + +div.pagename { + font-size: 140%; + color: blue; + text-align: center; + font-weight: bold; + background-color: white; + padding: 0 ; +} + +a.wikiaction, input.wikiaction { + color: black; + text-decoration: None; + text-align: center; + color: black; + /*border: 1px solid #3ba6ec; */ + margin: 4px; + padding: 5; + padding-bottom: 0; + white-space: nowrap; +} + +a.wikiaction[href]:hover { + color: black; + text-decoration: none; + /*background-color: #dddddd; */ +} + +span.wikiuserpref { + padding-top: 1em; + font-size: 120%; +} + +div.wikitrail { + vertical-align: bottom; + /*font-size: -1;*/ + padding-top: 1em; + display: none; +} + +div.wikiaction { + vertical-align: middle; + /*border-bottom: 1px solid #8cacbb;*/ + padding-bottom:1em; + text-align: left; + width: 100%; +} + +div.wikieditmenu { + text-align: right; +} + +form.wikiedit { + border: 1px solid #8cacbb; + background-color: #f0f0f0; + background-color: #fabf00; + padding: 1em; + padding-right: 0em; +} + +div.legenditem { + padding-top: 0.5em; + padding-left: 0.3em; +} + +span.wikitoken { + background-color: #eeeeee; +} + + +div#contentspace h1:first-child, div.heading:first-child { + padding-top: 0; + margin-top: 0; +} +div#contentspace h2:first-child { + padding-top: 0; + margin-top: 0; +} + +/* heading and paragraph text */ + +div.heading, h1 { + font-family: Verdana, Helvetica, Arial, sans-serif; + background-color: #58b3ef; + background-color: #FFFFFF; + /*color: #4893cf;*/ + color: black; + padding-top: 1.0em; + padding-bottom:0.2em; + text-align: left; + margin-top: 0em; + /*margin-bottom:8pt;*/ + font-weight: bold; + font-size: 115%; + border-bottom: 1px solid #8CACBB; +} + + +h1, h2, h3, h4, h5, h6 { + color: Black; + clear: left; + font: 100% Verdana, Helvetica, Arial, sans-serif; + margin: 0; + padding-left: 0em; + padding-top: 1em; + padding-bottom: 0.2em; + /*border-bottom: 1px solid #8CACBB;*/ +} +/* h1,h2 { padding-top: 0; }*/ + + +h1 { font-size: 145%; } +h2 { font-size: 135%; } +h3 { font-size: 125%; } +h4 { font-size: 120%; } +h5 { font-size: 110%; } +h6 { font-size: 80%; } + +h1 a { text-decoration: None;} + +div.exception { + background-color: #bb2222; + padding: 5 5 5 5; + color: white; + font-weight: bold; +} +pre.exception { + font-size: 110%; + padding: 1em; + border: 1px solid #8cacbb; + color: Black; + background-color: #dee7ec; + background-color: #cccccc; +} + +/* defines for navgiation bar (documentation) */ + + +div.direntry { + padding-top: 0.3em; + padding-bottom: 0.3em; + margin-right: 1em; + font-weight: bold; + background-color: #dee7ec; + font-size: 110%; +} + +div.fileentry { + font-family: Verdana, Helvetica, Arial, sans-serif; + padding-bottom: 0.3em; + white-space: nowrap; + line-height: 150%; +} + +a.fileentry { + white-space: nowrap; +} + + +span.left { + text-align: left; +} +span.right { + text-align: right; +} + +div.navbar { + /*margin: 0;*/ + font-size: 80% /*smaller*/; + font-weight: bold; + text-align: left; + /* position: fixed; */ + top: 100pt; + left: 0pt; /* auto; */ + width: 120pt; + /* right: auto; + right: 0pt; 2em; */ +} + + +div.history a { + /* font-size: 70%; */ +} + +div.wikiactiontitle { + font-weight: bold; +} + +/* REST defines */ + +div.document { + margin: 0; +} + +h1.title { + margin: 0; + margin-bottom: 0.5em; +} + +td.toplist { + vertical-align: top; +} + +img#pyimg { + position: absolute; + top: 4px; + left: 4px; +} + +div#navspace { + position: absolute; + top: 100px; + left: 11px; + font-size: 100%; + width: 150px; + overflow: hidden; /* scroll; */ +} + +div#metaspace { + position: absolute; + top: 10px; + left: 170px; +} + +div#errorline { + position: relative; + top: 5px; + float: right; +} + +div#contentspace { + position: absolute; + /* font: 120% "Times New Roman", serif;*/ + font: 110% Verdana, Helvetica, Arial, sans-serif; + top: 100px; + left: 170px; + margin-right: 5px; +} + +div#menubar { +/* width: 400px; */ + float: left; +} + +/* for the documentation page */ +div#docinfoline { + position: relative; + top: 5px; + left: 0px; + + /*background-color: #dee7ec; */ + padding: 5pt; + padding-bottom: 1em; + color: black; + /*border-width: 1pt; + border-style: solid;*/ + +} + +div#docnavlist { + /*background-color: #dee7ec; */ + padding: 5pt; + padding-bottom: 2em; + color: black; + border-width: 1pt; + /*border-style: solid;*/ +} + + +/* text markup */ + +div.listtitle { + color: Black; + clear: left; + font: 120% Verdana, Helvetica, Arial, sans-serif; + margin: 0; + padding-left: 0em; + padding-top: 0em; + padding-bottom: 0.2em; + margin-right: 0.5em; + border-bottom: 1px solid #8CACBB; +} + +div.actionbox h3 { + padding-top: 0; + padding-right: 0.5em; + padding-left: 0.5em; + background-color: #fabf00; + text-align: center; + border: 1px solid black; /* 8cacbb; */ +} + +div.actionbox a { + display: block; + padding-bottom: 0.5em; + padding-top: 0.5em; + margin-left: 0.5em; +} + +div.actionbox a.history { + display: block; + padding-bottom: 0.5em; + padding-top: 0.5em; + margin-left: 0.5em; + font-size: 90%; +} + +div.actionbox { + margin-bottom: 2em; + padding-bottom: 1em; + overflow: hidden; /* scroll; */ +} + +/* taken from docutils (oh dear, a bit senseless) */ +ol.simple, ul.simple { + margin-bottom: 1em } + +ol.arabic { + list-style: decimal } + +ol.loweralpha { + list-style: lower-alpha } + +ol.upperalpha { + list-style: upper-alpha } + +ol.lowerroman { + list-style: lower-roman } + +ol.upperroman { + list-style: upper-roman } + + +/* +:Author: David Goodger +:Contact: goodger@users.sourceforge.net +:date: $Date: 2003/01/22 22:26:48 $ +:version: $Revision: 1.29 $ +:copyright: This stylesheet has been placed in the public domain. + +Default cascading style sheet for the HTML output of Docutils. +*/ +/* +.first { + margin-top: 0 } + +.last { + margin-bottom: 0 } + +a.toc-backref { + text-decoration: none ; + color: black } + +dd { + margin-bottom: 0.5em } + +div.abstract { + margin: 2em 5em } + +div.abstract p.topic-title { + font-weight: bold ; + text-align: center } + +div.attention, div.caution, div.danger, div.error, div.hint, +div.important, div.note, div.tip, div.warning { + margin: 2em ; + border: medium outset ; + padding: 1em } + +div.attention p.admonition-title, div.caution p.admonition-title, +div.danger p.admonition-title, div.error p.admonition-title, +div.warning p.admonition-title { + color: red ; + font-weight: bold ; + font-family: sans-serif } + +div.hint p.admonition-title, div.important p.admonition-title, +div.note p.admonition-title, div.tip p.admonition-title { + font-weight: bold ; + font-family: sans-serif } + +div.dedication { + margin: 2em 5em ; + text-align: center ; + font-style: italic } + +div.dedication p.topic-title { + font-weight: bold ; + font-style: normal } + +div.figure { + margin-left: 2em } + +div.footer, div.header { + font-size: smaller } + +div.system-messages { + margin: 5em } + +div.system-messages h1 { + color: red } + +div.system-message { + border: medium outset ; + padding: 1em } + +div.system-message p.system-message-title { + color: red ; + font-weight: bold } + +div.topic { + margin: 2em } + +h1.title { + text-align: center } + +h2.subtitle { + text-align: center } + +hr { + width: 75% } + +p.caption { + font-style: italic } + +p.credits { + font-style: italic ; + font-size: smaller } + +p.label { + white-space: nowrap } + +p.topic-title { + font-weight: bold } + +pre.address { + margin-bottom: 0 ; + margin-top: 0 ; + font-family: serif ; + font-size: 100% } + +pre.line-block { + font-family: serif ; + font-size: 100% } + +pre.literal-block, pre.doctest-block { + margin-left: 2em ; + margin-right: 2em ; + background-color: #eeeeee } + +span.classifier { + font-family: sans-serif ; + font-style: oblique } + +span.classifier-delimiter { + font-family: sans-serif ; + font-weight: bold } + +span.interpreted { + font-family: sans-serif } + +span.option { + white-space: nowrap } + +span.option-argument { + font-style: italic } + +span.pre { + white-space: pre } + +span.problematic { + color: red } + +table { + margin-top: 0.5em ; + margin-bottom: 0.5em } + +table.citation { + border-left: solid thin gray ; + padding-left: 0.5ex } + +table.docinfo { + margin: 2em 4em } + +table.footnote { + border-left: solid thin black ; + padding-left: 0.5ex } + +td, th { + padding-left: 0.5em ; + padding-right: 0.5em ; + vertical-align: top } + +th.docinfo-name, th.field-name { + font-weight: bold ; + text-align: left ; + white-space: nowrap } + +h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { + font-size: 100% } + +tt { + background-color: #eeeeee } + +ul.auto-toc { + list-style-type: none } +*/ + +div.section { + margin-top: 1.0em ; +} diff --git a/py/doc/talk/execnet-overview.txt b/py/doc/talk/execnet-overview.txt new file mode 100644 index 000000000..393296e54 --- /dev/null +++ b/py/doc/talk/execnet-overview.txt @@ -0,0 +1,64 @@ +.. include:: + +================================================= +py.execnet - simple ad-hoc networking +================================================= + +:Authors: Holger Krekel, merlinux GmbH +:Date: 13th March 2006 + +remote method invocation is cumbersome +======================================== + +- CORBA/RMI/SOAP model is cumbersome +- "infection" with object references throughout your program +- need to define interfaces, generate stubs/skeletons +- need to start server processes ahead of time +- complicates programming + +what you want of ad-hoc networks +==================================== + +- ad hoc **local protocols** +- avoid defining and maintaining global interfaces +- deploy protocols purely from the client side +- zero installation required on server side + +py.execnet model of ad-hoc networks +==================================== + +- *Gateways* can be setup via e.g. SSH logins or via Popen +- *Gateway.remote_exec* allows execution of arbitrary code +- means of communication between the two sides: *Channels* + (with send & receive methods) +- example requirements: ssh login + python installed + +py.execnet.SshGateway example +==================================== + +interactive:: + + gw = py.execnet.SshGateway('codespeak.net') + + channel = gw.remote_exec(""" + for filename in channel: + try: + content = open(filename).read() + except (OSError, IOError): + content = None + channel.send(content) + """) + +next steps / references +==================================== + +- ad-hoc p2p networks +- chaining channels / passing channels around +- ensure it also works nicely on win32 +- btw, py.execnet is part of the py lib + + http://codespeak.net/py/ + +.. |bullet| unicode:: U+02022 +.. footer:: Holger Krekel (merlinux) |bullet| 13th March 2006 + diff --git a/py/doc/talk/make.py b/py/doc/talk/make.py new file mode 100755 index 000000000..4fb2f59e4 --- /dev/null +++ b/py/doc/talk/make.py @@ -0,0 +1,11 @@ +#!/usr/bin/python + +import py + +for x in py.path.local(): + if x.ext == '.txt': + cmd = ("python /home/hpk/projects/docutils/tools/rst2s5.py " + "%s %s" %(x, x.new(ext='.html'))) + print "execing", cmd + py.std.os.system(cmd) + diff --git a/py/doc/talk/makeref.py b/py/doc/talk/makeref.py new file mode 100644 index 000000000..6fdb44d76 --- /dev/null +++ b/py/doc/talk/makeref.py @@ -0,0 +1,54 @@ + +import py +py.magic.autopath() +import py +pydir = py.path.local(py.__file__).dirpath() +distdir = pydir.dirpath() +dist_url = 'http://codespeak.net/svn/py/dist/' +#issue_url = 'http://codespeak.net/issue/py-dev/' + +docdir = pydir.join('documentation') +reffile = docdir / 'talk' / '_ref.txt' + +linkrex = py.std.re.compile('`(\S+)`_') + +name2target = {} +def addlink(linkname, linktarget): + assert linkname and linkname != '/' + if linktarget in name2target: + if linkname in name2target[linktarget]: + return + name2target.setdefault(linktarget, []).append(linkname) + +for textfile in docdir.visit(lambda x: x.ext == '.txt', + lambda x: x.check(dotfile=0)): + for linkname in linkrex.findall(textfile.read()): + if '/' in linkname: + for startloc in ('', 'py'): + cand = distdir.join(startloc, linkname) + if cand.check(): + rel = cand.relto(distdir) + # we are in py/doc/x.txt + count = rel.count("/") + 1 + target = '../' * count + rel + addlink(linkname, target) + break + else: + print "WARNING %s: link %r may be bogus" %(textfile, linkname) + elif linkname.startswith('issue'): + addlink(linkname, issue_url+linkname) + +items = name2target.items() +items.sort() + +lines = [] +for linktarget, linknamelist in items: + linknamelist.sort() + for linkname in linknamelist[:-1]: + lines.append(".. _`%s`:" % linkname) + lines.append(".. _`%s`: %s" %(linknamelist[-1], linktarget)) + +reffile.write("\n".join(lines)) +print "wrote %d references to %r" %(len(lines), reffile) +#print "last ten lines" +#for x in lines[-10:]: print x diff --git a/py/doc/talk/notes.txt b/py/doc/talk/notes.txt new file mode 100644 index 000000000..ac419d3e9 --- /dev/null +++ b/py/doc/talk/notes.txt @@ -0,0 +1,16 @@ + +* Persistant storage layer for storing py.test output, sharing such stuff + and presenting (Presenting mostly means combining tones of hacks here + and there). We need to store test results, revisions and additional + metadata like apigen output + +* Having some kind of pdbplus, which will combine rlcompleter, apigen + information and other various fixes. + +* Improve distributed testing by: + + - sharing even more code with normal testing + - using greenexecnet wherever possible (falling back to normal + execnet) + - make test redistribution somehow (in a clean way!) + - C-c support diff --git a/py/doc/talk/pytest-overview.txt b/py/doc/talk/pytest-overview.txt new file mode 100644 index 000000000..fbb0f634c --- /dev/null +++ b/py/doc/talk/pytest-overview.txt @@ -0,0 +1,200 @@ +.. include:: + +================================================= +py.test - flexible and powerful automated testing +================================================= + +:Authors: Holger Krekel, merlinux GmbH +:Date: 13th March 2006 + +Intro: Benefits of Automated Testing +====================================== + +- prove that code changes actually fix a certain issue +- minimizing Time to Feedback for developers +- reducing overall Time to Market +- document usage of plugins +- tests as a means of communication +- easing entry for newcomers + +py.test Purposes & Goals +=============================== + +- automated cross-project open source testing tool +- flexible per-project customization +- reusing test methods/reporting across projects +- various iterative test collection methods +- support for distributed testing +- py lib is a development support library + +What is Python? +=============================== + +- easy-to-learn flexible OO high level language +- glue-language for connecting C++, Java and scripting +- used e.g. by Google for deployment/testing/implementation +- used by BIND (mainstream DNS internet server) for testing +- Jython provides Python for JVM +- IronPython provides Python for .NET +- CPython is mainstream C-based platform +- PyPy - Python in Python implementation + +Main drivers of py.test development +====================================== + +- PyPy project testing needs (part of EU project) +- needs by individual (freely contributing) projects +- at least 20 projects using py.test and py lib +- higher level innovation plans by merlinux & experts +- commercial needs +- almost three years of (non-fulltime) development + +Authors & copyrights +============================================== + +- initial: Holger Krekel, Armin Rigo +- major contributors: Jan Balster, Brian Dorsey, Grig + Gheorghiu +- many others with small patches +- MIT license + +who is merlinux? +=============================== + +- small company founded in 2004 by Holger Krekel and Laura + Creighton + +- purpose: research and development / open source technologies + +- 7 employees (no win32 experts!), 6 freelancers + +- three larger projects: + + - PyPy - next generation Python implementation + - mailwitness - digital invoicing/signatures + - provider of development servers + +- technologies: virtualization, deployment and testing + +Main Features of py.test +=============================== + +- simplest possible ``assert`` approach +- clean setup/teardown semantics +- stdout/stderr capturing per test +- per-project/directory cmdline options (many predefined) +- test selection support +- customizable auto-collection of tests +- `more features`_ ... + +.. _`more features`: ../test.html#features + +Main User-Level entry points +=============================== + +- ``py.test.raises(Exc, func, *args, **kwargs)`` +- ``py.test.fail(msg)`` -> fail a test +- ``py.test.skip(msg)`` -> skip a test +- ``py.test.ensuretemp(prefix)`` -> per-test session temporary directory +- ``conftest.py`` can modify almost arbitrary testing aspects + (but it's a bit involved) + +some py lib components +=============================== + +- ``py.execnet`` provides ad-hoc means to distribute programs +- ``py.path`` objects abstract local and svn files +- ``py.log`` offers (preliminary) logging support +- ``py.xml.html`` for programmatic html generation +- lazy import ...``import py`` is enough + +py.test Implementation +=============================== + +- `basic picture`_ +- Session objects (Terminal and Tcl-GUI) +- reporting hooks are on session objects +- Collector hierarchy yield iteratively tests +- uses py lib extensively (py.path/py.execnet) +- "conftest.py" per-directory configuration mechanism + +.. _`basic picture`: ../test.html + +Session objects +=============================== + +- responsible for driving the testing process +- make use of iterative Collector hierarchies +- responsible for reporting (XXX) +- can be split to a Frontend and BackendSession + for distributed testing (GUI frontend uses it) + +Collector objects +=============================== + +- Collectors / Test Items form a tree +- the tree is build iteratively (driven from Sessions) +- collector tree can be viewed with ``--collectonly`` +- ``run()`` returns list of (test) names or runs the test +- ``join(name)`` returns a sub collector/item +- various helper methods to e.g. determine file/location + +Extensions: ReST documentation checking +========================================= + +- `py/documentation/conftest.py`_ provides test + items for checking documentation and link integrity + +- uses its own collector/testitem hierarchy + +- invokes ``docutils`` processing, reports errors + +.. _`py/documentation/conftest.py`: ../conftest.py + +Extensions: Distributed Testing +============================================== + +- using py.execnet to dispatch on different python versions +- using py.execnet to dispatch tests on other hosts/platforms +- currently: Popen, SSH and Socket gateways +- missing support pushing tests to "the other side" +- missing for deployment on multiple machines +- but it's already possible ... + +Example using pywinauto from linux +============================================== + +- start socketserver.py on windows +- connect a SocketGateway e.g. from linux +- send tests, execute and report tracebacks through the + gateway +- remotely use pywinauto to automate testing of GUI work flow +- interactive example ... + +Status of py lib +=============================== + +- mostly developed on linux/OSX +- basically all tests pass on win32 as well +- but missing some win32 convenience +- some support for generation of html/ReST/PDFs reports +- py.execnet works rather reliably (pending deeper win32 testing) +- flexible configuration but sometimes non-obvious/documented + (requires understanding of internals) + +Next Steps py lib / py.test +=============================== + +- refined py.execnet distribution of programs +- more configurable and customizable reporting +- implement support for testing distribution +- explore refined win32 support +- automated collection of unittest.py based tests +- make spawning processes/gateways more robust +- doctest support +- unify logging approaches (py.log.*) +- ... + + +.. |bullet| unicode:: U+02022 +.. footer:: Holger Krekel (merlinux) |bullet| |bullet| 13th March 2006 diff --git a/py/doc/talk/ui/default/blank.gif b/py/doc/talk/ui/default/blank.gif new file mode 100644 index 0000000000000000000000000000000000000000..75b945d2553848b8b6f41fe5e24599c0687b8472 GIT binary patch literal 49 zcmZ?wbhEHbWMp7unE0RJ|Ns9C3=9Vj8~~DvKUo+V7?>DzfNY>Fh|Ltj$Y2csQN9XW literal 0 HcmV?d00001 diff --git a/py/doc/talk/ui/default/framing.css b/py/doc/talk/ui/default/framing.css new file mode 100644 index 000000000..c4727f303 --- /dev/null +++ b/py/doc/talk/ui/default/framing.css @@ -0,0 +1,25 @@ +/* This file has been placed in the public domain. */ +/* The following styles size, place, and layer the slide components. + Edit these if you want to change the overall slide layout. + The commented lines can be uncommented (and modified, if necessary) + to help you with the rearrangement process. */ + +/* target = 1024x768 */ + +div#header, div#footer, .slide {width: 100%; top: 0; left: 0;} +div#header {position: fixed; top: 0; height: 3em; z-index: 1;} +div#footer {top: auto; bottom: 0; height: 2.5em; z-index: 5;} +.slide {top: 0; width: 92%; padding: 2.5em 4% 4%; z-index: 2;} +div#controls {left: 50%; bottom: 0; width: 50%; z-index: 100;} +div#controls form {position: absolute; bottom: 0; right: 0; width: 100%; + margin: 0;} +#currentSlide {position: absolute; width: 10%; left: 45%; bottom: 1em; + z-index: 10;} +html>body #currentSlide {position: fixed;} + +/* +div#header {background: #FCC;} +div#footer {background: #CCF;} +div#controls {background: #BBD;} +div#currentSlide {background: #FFC;} +*/ diff --git a/py/doc/talk/ui/default/iepngfix.htc b/py/doc/talk/ui/default/iepngfix.htc new file mode 100644 index 000000000..9f3d628b5 --- /dev/null +++ b/py/doc/talk/ui/default/iepngfix.htc @@ -0,0 +1,42 @@ + + + + + \ No newline at end of file diff --git a/py/doc/talk/ui/default/opera.css b/py/doc/talk/ui/default/opera.css new file mode 100644 index 000000000..c9d1148be --- /dev/null +++ b/py/doc/talk/ui/default/opera.css @@ -0,0 +1,8 @@ +/* This file has been placed in the public domain. */ +/* DO NOT CHANGE THESE unless you really want to break Opera Show */ +.slide { + visibility: visible !important; + position: static !important; + page-break-before: always; +} +#slide0 {page-break-before: avoid;} diff --git a/py/doc/talk/ui/default/outline.css b/py/doc/talk/ui/default/outline.css new file mode 100644 index 000000000..fa767e227 --- /dev/null +++ b/py/doc/talk/ui/default/outline.css @@ -0,0 +1,16 @@ +/* This file has been placed in the public domain. */ +/* Don't change this unless you want the layout stuff to show up in the + outline view! */ + +.layout div, #footer *, #controlForm * {display: none;} +#footer, #controls, #controlForm, #navLinks, #toggle { + display: block; visibility: visible; margin: 0; padding: 0;} +#toggle {float: right; padding: 0.5em;} +html>body #toggle {position: fixed; top: 0; right: 0;} + +/* making the outline look pretty-ish */ + +#slide0 h1, #slide0 h2, #slide0 h3, #slide0 h4 {border: none; margin: 0;} +#toggle {border: 1px solid; border-width: 0 0 1px 1px; background: #FFF;} + +.outline {display: inline ! important;} diff --git a/py/doc/talk/ui/default/pretty.css b/py/doc/talk/ui/default/pretty.css new file mode 100644 index 000000000..99d0a3823 --- /dev/null +++ b/py/doc/talk/ui/default/pretty.css @@ -0,0 +1,121 @@ +/* This file has been placed in the public domain. */ +/* Following are the presentation styles -- edit away! */ + +html, body {margin: 0; padding: 0;} +body {background: #fff color: #222; font-size: 2em;} +/* Replace the background style above with the style below (and again for + div#header) for a graphic: */ +/* background: white url(bodybg.gif) -16px 0 no-repeat; */ +:link, :visited {text-decoration: none; color: #00C;} +#controls :active {color: #88A !important;} +#controls :focus {outline: 1px dotted #227;} +h1, h2, h3, h4 {font-size: 100%; margin: 0; padding: 0; font-weight: inherit;} + +blockquote {padding: 0 2em 0.5em; margin: 0 1.5em 0.5em;} +blockquote p {margin: 0;} + +kbd {font-weight: bold; font-size: 1em;} +sup {font-size: smaller; line-height: 1px;} + +.slide pre {padding: 0; margin-left: 0; margin-right: 0; font-size: 90%;} +.slide ul ul li {list-style: square; } +.slide img.leader {display: block; margin: 0 auto;} +.slide tt {font-size: 90%;} + +div#header, div#footer {background: #005; color: #AAB; font-family: sans-serif;} +/* background: #005 url(bodybg.gif) -16px 0 no-repeat; */ +div#footer {font-size: 0.5em; font-weight: bold; padding: 1em 0;} +#footer h1 {display: block; padding: 0 1em;} +#footer h2 {display: block; padding: 0.8em 1em 0;} + +.slide {font-size: 1.5em;} +.slide li {font-size: 1.0em; padding-bottom: 0.2em;} +.slide h1 {position: absolute; top: 0.45em; z-index: 1; + margin: 0; padding-left: 0.7em; white-space: nowrap; + font: bold 110% sans-serif; color: #DDE; background: #005;} +.slide h2 {font: bold 120%/1em sans-serif; padding-top: 0.5em;} +.slide h3 {font: bold 100% sans-serif; padding-top: 0.5em;} +h1 abbr {font-variant: small-caps;} + +div#controls {position: absolute; left: 50%; bottom: 0; + width: 50%; text-align: right; font: bold 0.9em sans-serif;} +html>body div#controls {position: fixed; padding: 0 0 1em 0; top: auto;} +div#controls form {position: absolute; bottom: 0; right: 0; width: 100%; + margin: 0; padding: 0;} +#controls #navLinks a {padding: 0; margin: 0 0.5em; + background: #005; border: none; color: #779; cursor: pointer;} +#controls #navList {height: 1em;} +#controls #navList #jumplist {position: absolute; bottom: 0; right: 0; + background: #DDD; color: #227;} + +#currentSlide {text-align: center; font-size: 0.5em; color: #449; + font-family: sans-serif; font-weight: bold;} + +#slide0 {padding-top: 1.5em} +#slide0 h1 {position: static; margin: 1em 0 0; padding: 0; color: #000; + font: bold 2em sans-serif; white-space: normal; background: transparent;} +#slide0 h2 {font: bold italic 1em sans-serif; margin: 0.25em;} +#slide0 h3 {margin-top: 1.5em; font-size: 1.5em;} +#slide0 h4 {margin-top: 0; font-size: 1em;} + +ul.urls {list-style: none; display: inline; margin: 0;} +.urls li {display: inline; margin: 0;} +.external {border-bottom: 1px dotted gray;} +html>body .external {border-bottom: none;} +.external:after {content: " \274F"; font-size: smaller; color: #77B;} + +.incremental, .incremental *, .incremental *:after {visibility: visible; + color: white; border: 0;} +img.incremental {visibility: hidden;} +.slide .current {color: green;} + +.slide-display {display: inline ! important;} + +.huge {font-family: sans-serif; font-weight: bold; font-size: 150%;} +.big {font-family: sans-serif; font-weight: bold; font-size: 120%;} +.small {font-size: 75%;} +.tiny {font-size: 50%;} +.huge tt, .big tt, .small tt, .tiny tt {font-size: 115%;} +.huge pre, .big pre, .small pre, .tiny pre {font-size: 115%;} + +.maroon {color: maroon;} +.red {color: red;} +.magenta {color: magenta;} +.fuchsia {color: fuchsia;} +.pink {color: #FAA;} +.orange {color: orange;} +.yellow {color: yellow;} +.lime {color: lime;} +.green {color: green;} +.olive {color: olive;} +.teal {color: teal;} +.cyan {color: cyan;} +.aqua {color: aqua;} +.blue {color: blue;} +.navy {color: navy;} +.purple {color: purple;} +.black {color: black;} +.gray {color: gray;} +.silver {color: silver;} +.white {color: white;} + +.left {text-align: left ! important;} +.center {text-align: center ! important;} +.right {text-align: right ! important;} + +.animation {position: relative; margin: 1em 0; padding: 0;} +.animation img {position: absolute;} + +/* Docutils-specific overrides */ + +.slide table.docinfo {margin: 1em 0 0.5em 2em;} + +pre.literal-block, pre.doctest-block {background-color: white;} + +tt.docutils {background-color: white;} + +/* diagnostics */ +/* +li:after {content: " [" attr(class) "]"; color: #F88;} +div:before {content: "[" attr(class) "]"; color: #F88;} +*/ diff --git a/py/doc/talk/ui/default/print.css b/py/doc/talk/ui/default/print.css new file mode 100644 index 000000000..9d057cc8c --- /dev/null +++ b/py/doc/talk/ui/default/print.css @@ -0,0 +1,24 @@ +/* This file has been placed in the public domain. */ +/* The following rule is necessary to have all slides appear in print! + DO NOT REMOVE IT! */ +.slide, ul {page-break-inside: avoid; visibility: visible !important;} +h1 {page-break-after: avoid;} + +body {font-size: 12pt; background: white;} +* {color: black;} + +#slide0 h1 {font-size: 200%; border: none; margin: 0.5em 0 0.25em;} +#slide0 h3 {margin: 0; padding: 0;} +#slide0 h4 {margin: 0 0 0.5em; padding: 0;} +#slide0 {margin-bottom: 3em;} + +#header {display: none;} +#footer h1 {margin: 0; border-bottom: 1px solid; color: gray; + font-style: italic;} +#footer h2, #controls {display: none;} + +.print {display: inline ! important;} + +/* The following rule keeps the layout stuff out of print. + Remove at your own risk! */ +.layout, .layout * {display: none !important;} diff --git a/py/doc/talk/ui/default/py-web.png b/py/doc/talk/ui/default/py-web.png new file mode 100644 index 0000000000000000000000000000000000000000..61c748359c12577a1b57a939b8f9e877faa82d1e GIT binary patch literal 2163 zcmV-(2#oiMP)Z6SN!2|v3sV>IGcGYOVIUwZy@RHMO+(O{5$tWfda!RTD6?l_2J zi~JD9u^j~d_`0W775Ay;v%LXBbviPYy7hhA%9SKZpX$%mU-LY|5*=4i9mzhlRhOem z6~h16b~4RHi3r$q>NtyEIKskfboFMPX&f+*v$|B4<*Zq`uIo62&~e=#S7wB{PndeM zj+Bo|&2IK?svOR79Cz(rrI9$cTAD}E_i0GA0dv>L{l(d(HS3$+surkdG}j0%a0$yi zdY@Ie*Af*0TO(s=mfh;T%PRUZEm460YZVFO%QWb!sW$J@9u;62ES7nGrl2~mvZ_t0 zwb2=-Oc2d)3M!3N+d0xE6#^{QtdZQT<8w9Cww|;}MX)YdEV)^l3c8yitx~N~r?Vgs@ACtufi;K1vPQ+gQ%c7 zcqOxVD9uBdvfDNlXO_@*)D_fZez!JN;-W%2v%JzYtLjqyb=IAd3>6yccu!0S zaoH@5RcwQ*M-^Qe9w*?c6GF}`b+ak`V%-Q{atb|9js;Yq$yQRf?esKO_4cL`=hEO| zqHqgbeN?p_O$9A^T9-;Jhl3ZxP$X8V88U50-E7KMHK59}-v)A}RHfR$m{r%RMpW6q zXf46wsq)XBDIdTudKyv9oVBF3Lb{i>VR_rpG#hTs(}*gw*N$jb#|zmdX+Bld;|q)F zW>nYCR5Ytx8Va-OTh)+iU`?kIDz@jOyHr;1Y{aE>Gpa0@PK6KxRmvSZkZG@JHo{7} zA(h`xWK=6|cTddP!>XoKV=EC7OF{+Ne9WKPXsl{VbuC$UiB#ml$_gsmpf&X>wrWHW zo~&s$lKojzDqBXyRR$0qDTkKrWt(QZgbJ!C?-g19?qO9!s@dF5gnGr-cCF+)Sp^lw zIATu8l(jvu#VQKjxi*wy*Ds~Qx3{-%Z+}ekG%ak`K&MZFjZjH$h;> zR)6X&er>rFAHMd3zF?J6+D=qa$xly3s_xt;P)Ra{^}vtKA*`HgIrc@X_!Y7qN}5$7 z6@K;o!0J2nJl%z7E>^%Lr2;5PRzX?`hDS@ab`gT-B2`B35aSV{2JOslcTGWYYy4 zDb0^&6q$eevb?DfywXY5Qy((cvx-nH$3SGmqSD>I?LDdJ_(qz|)mF{)XV-PC0;<{A zhtTD@i#9h5W_KqJ`|+=0|||bJ+rX-_K;EqvTpVc z5va@Eu$r^}ja8bPRzkJ3V_gDiOj`A>8A;dvdWLDMJ z9gq)guVf4@?wxYSsf2>}@Loy-YwngSt;*2scc~TAP3G`ob-zc)p+Okoa$sZXvA%k~1FW2D{pV)x$La_ov?Bta zUY7EqTcCn47Ix8=6EoEv7z)wbrR{A_c=>&%8oCv#Z27dydY3GUonjSXG)kWcS7)0(n@A1sqpGs^u~aFJR!>Y4b;99e8b!FW!h9UnrtIM;y%>fxg@#Y} zI3mX%)phTqsR$SBCRQtxG@i|o!B?P4=~*B9g}5(5#gB8KI#3;`4paxKuSYeIDI0t) z)z}YgL2=?dd?wWZs4PJV>YRKkRS?*HL5|^lE|qP`Zd|-CN!9t1R0ppPULCwTcy;jV z;MKvauSRw7>i^>vY#fjvL-b0i582}+fY1?sc&hG5siSvMssGHH%8}hI@KoBr?%y9O z>m94K{f6kniCoXwf3{Lrq7lN1<^TWy2XskIMF-OY1Pm1blXD+t0000QbVXQnQ*UN; pcVTj606}DLVr3vnZDD6+Qe|Oec?uoLvvB|b002ovPDHLkV1j>l9BTjo literal 0 HcmV?d00001 diff --git a/py/doc/talk/ui/default/s5-core.css b/py/doc/talk/ui/default/s5-core.css new file mode 100644 index 000000000..6965f5e8f --- /dev/null +++ b/py/doc/talk/ui/default/s5-core.css @@ -0,0 +1,11 @@ +/* This file has been placed in the public domain. */ +/* Do not edit or override these styles! + The system will likely break if you do. */ + +div#header, div#footer, div#controls, .slide {position: absolute;} +html>body div#header, html>body div#footer, + html>body div#controls, html>body .slide {position: fixed;} +.handout {display: none;} +.layout {display: block;} +.slide, .hideme, .incremental {visibility: hidden;} +#slide0 {visibility: visible;} diff --git a/py/doc/talk/ui/default/slides.css b/py/doc/talk/ui/default/slides.css new file mode 100644 index 000000000..a27b34bb4 --- /dev/null +++ b/py/doc/talk/ui/default/slides.css @@ -0,0 +1,13 @@ +/* This file has been placed in the public domain. */ + +/* required to make the slide show run at all */ +@import url(s5-core.css); + +/* sets basic placement and size of slide components */ +@import url(framing.css); + +/* styles that make the slides look good */ +@import url(pretty.css); + +/* pypy override */ +@import url(../py.css); diff --git a/py/doc/talk/ui/default/slides.js b/py/doc/talk/ui/default/slides.js new file mode 100644 index 000000000..81e04e5d4 --- /dev/null +++ b/py/doc/talk/ui/default/slides.js @@ -0,0 +1,558 @@ +// S5 v1.1 slides.js -- released into the Public Domain +// Modified for Docutils (http://docutils.sf.net) by David Goodger +// +// Please see http://www.meyerweb.com/eric/tools/s5/credits.html for +// information about all the wonderful and talented contributors to this code! + +var undef; +var slideCSS = ''; +var snum = 0; +var smax = 1; +var slideIDs = new Array(); +var incpos = 0; +var number = undef; +var s5mode = true; +var defaultView = 'slideshow'; +var controlVis = 'visible'; + +var isIE = navigator.appName == 'Microsoft Internet Explorer' ? 1 : 0; +var isOp = navigator.userAgent.indexOf('Opera') > -1 ? 1 : 0; +var isGe = navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('Safari') < 1 ? 1 : 0; + +function hasClass(object, className) { + if (!object.className) return false; + return (object.className.search('(^|\\s)' + className + '(\\s|$)') != -1); +} + +function hasValue(object, value) { + if (!object) return false; + return (object.search('(^|\\s)' + value + '(\\s|$)') != -1); +} + +function removeClass(object,className) { + if (!object) return; + object.className = object.className.replace(new RegExp('(^|\\s)'+className+'(\\s|$)'), RegExp.$1+RegExp.$2); +} + +function addClass(object,className) { + if (!object || hasClass(object, className)) return; + if (object.className) { + object.className += ' '+className; + } else { + object.className = className; + } +} + +function GetElementsWithClassName(elementName,className) { + var allElements = document.getElementsByTagName(elementName); + var elemColl = new Array(); + for (var i = 0; i< allElements.length; i++) { + if (hasClass(allElements[i], className)) { + elemColl[elemColl.length] = allElements[i]; + } + } + return elemColl; +} + +function isParentOrSelf(element, id) { + if (element == null || element.nodeName=='BODY') return false; + else if (element.id == id) return true; + else return isParentOrSelf(element.parentNode, id); +} + +function nodeValue(node) { + var result = ""; + if (node.nodeType == 1) { + var children = node.childNodes; + for (var i = 0; i < children.length; ++i) { + result += nodeValue(children[i]); + } + } + else if (node.nodeType == 3) { + result = node.nodeValue; + } + return(result); +} + +function slideLabel() { + var slideColl = GetElementsWithClassName('*','slide'); + var list = document.getElementById('jumplist'); + smax = slideColl.length; + for (var n = 0; n < smax; n++) { + var obj = slideColl[n]; + + var did = 'slide' + n.toString(); + if (obj.getAttribute('id')) { + slideIDs[n] = obj.getAttribute('id'); + } + else { + obj.setAttribute('id',did); + slideIDs[n] = did; + } + if (isOp) continue; + + var otext = ''; + var menu = obj.firstChild; + if (!menu) continue; // to cope with empty slides + while (menu && menu.nodeType == 3) { + menu = menu.nextSibling; + } + if (!menu) continue; // to cope with slides with only text nodes + + var menunodes = menu.childNodes; + for (var o = 0; o < menunodes.length; o++) { + otext += nodeValue(menunodes[o]); + } + list.options[list.length] = new Option(n + ' : ' + otext, n); + } +} + +function currentSlide() { + var cs; + var footer_nodes; + var vis = 'visible'; + if (document.getElementById) { + cs = document.getElementById('currentSlide'); + footer_nodes = document.getElementById('footer').childNodes; + } else { + cs = document.currentSlide; + footer = document.footer.childNodes; + } + cs.innerHTML = '' + snum + '<\/span> ' + + '\/<\/span> ' + + '' + (smax-1) + '<\/span>'; + if (snum == 0) { + vis = 'hidden'; + } + cs.style.visibility = vis; + for (var i = 0; i < footer_nodes.length; i++) { + if (footer_nodes[i].nodeType == 1) { + footer_nodes[i].style.visibility = vis; + } + } +} + +function go(step) { + if (document.getElementById('slideProj').disabled || step == 0) return; + var jl = document.getElementById('jumplist'); + var cid = slideIDs[snum]; + var ce = document.getElementById(cid); + if (incrementals[snum].length > 0) { + for (var i = 0; i < incrementals[snum].length; i++) { + removeClass(incrementals[snum][i], 'current'); + removeClass(incrementals[snum][i], 'incremental'); + } + } + if (step != 'j') { + snum += step; + lmax = smax - 1; + if (snum > lmax) snum = lmax; + if (snum < 0) snum = 0; + } else + snum = parseInt(jl.value); + var nid = slideIDs[snum]; + var ne = document.getElementById(nid); + if (!ne) { + ne = document.getElementById(slideIDs[0]); + snum = 0; + } + if (step < 0) {incpos = incrementals[snum].length} else {incpos = 0;} + if (incrementals[snum].length > 0 && incpos == 0) { + for (var i = 0; i < incrementals[snum].length; i++) { + if (hasClass(incrementals[snum][i], 'current')) + incpos = i + 1; + else + addClass(incrementals[snum][i], 'incremental'); + } + } + if (incrementals[snum].length > 0 && incpos > 0) + addClass(incrementals[snum][incpos - 1], 'current'); + ce.style.visibility = 'hidden'; + ne.style.visibility = 'visible'; + jl.selectedIndex = snum; + currentSlide(); + number = 0; +} + +function goTo(target) { + if (target >= smax || target == snum) return; + go(target - snum); +} + +function subgo(step) { + if (step > 0) { + removeClass(incrementals[snum][incpos - 1],'current'); + removeClass(incrementals[snum][incpos], 'incremental'); + addClass(incrementals[snum][incpos],'current'); + incpos++; + } else { + incpos--; + removeClass(incrementals[snum][incpos],'current'); + addClass(incrementals[snum][incpos], 'incremental'); + addClass(incrementals[snum][incpos - 1],'current'); + } +} + +function toggle() { + var slideColl = GetElementsWithClassName('*','slide'); + var slides = document.getElementById('slideProj'); + var outline = document.getElementById('outlineStyle'); + if (!slides.disabled) { + slides.disabled = true; + outline.disabled = false; + s5mode = false; + fontSize('1em'); + for (var n = 0; n < smax; n++) { + var slide = slideColl[n]; + slide.style.visibility = 'visible'; + } + } else { + slides.disabled = false; + outline.disabled = true; + s5mode = true; + fontScale(); + for (var n = 0; n < smax; n++) { + var slide = slideColl[n]; + slide.style.visibility = 'hidden'; + } + slideColl[snum].style.visibility = 'visible'; + } +} + +function showHide(action) { + var obj = GetElementsWithClassName('*','hideme')[0]; + switch (action) { + case 's': obj.style.visibility = 'visible'; break; + case 'h': obj.style.visibility = 'hidden'; break; + case 'k': + if (obj.style.visibility != 'visible') { + obj.style.visibility = 'visible'; + } else { + obj.style.visibility = 'hidden'; + } + break; + } +} + +// 'keys' code adapted from MozPoint (http://mozpoint.mozdev.org/) +function keys(key) { + if (!key) { + key = event; + key.which = key.keyCode; + } + if (key.which == 84) { + toggle(); + return; + } + if (s5mode) { + switch (key.which) { + case 10: // return + case 13: // enter + if (window.event && isParentOrSelf(window.event.srcElement, 'controls')) return; + if (key.target && isParentOrSelf(key.target, 'controls')) return; + if(number != undef) { + goTo(number); + break; + } + case 32: // spacebar + case 34: // page down + case 39: // rightkey + case 40: // downkey + if(number != undef) { + go(number); + } else if (!incrementals[snum] || incpos >= incrementals[snum].length) { + go(1); + } else { + subgo(1); + } + break; + case 33: // page up + case 37: // leftkey + case 38: // upkey + if(number != undef) { + go(-1 * number); + } else if (!incrementals[snum] || incpos <= 0) { + go(-1); + } else { + subgo(-1); + } + break; + case 36: // home + goTo(0); + break; + case 35: // end + goTo(smax-1); + break; + case 67: // c + showHide('k'); + break; + } + if (key.which < 48 || key.which > 57) { + number = undef; + } else { + if (window.event && isParentOrSelf(window.event.srcElement, 'controls')) return; + if (key.target && isParentOrSelf(key.target, 'controls')) return; + number = (((number != undef) ? number : 0) * 10) + (key.which - 48); + } + } + return false; +} + +function clicker(e) { + number = undef; + var target; + if (window.event) { + target = window.event.srcElement; + e = window.event; + } else target = e.target; + if (target.href != null || hasValue(target.rel, 'external') || isParentOrSelf(target, 'controls') || isParentOrSelf(target,'embed') || isParentOrSelf(target, 'object')) return true; + if (!e.which || e.which == 1) { + if (!incrementals[snum] || incpos >= incrementals[snum].length) { + go(1); + } else { + subgo(1); + } + } +} + +function findSlide(hash) { + var target = document.getElementById(hash); + if (target) { + for (var i = 0; i < slideIDs.length; i++) { + if (target.id == slideIDs[i]) return i; + } + } + return null; +} + +function slideJump() { + if (window.location.hash == null || window.location.hash == '') { + currentSlide(); + return; + } + if (window.location.hash == null) return; + var dest = null; + dest = findSlide(window.location.hash.slice(1)); + if (dest == null) { + dest = 0; + } + go(dest - snum); +} + +function fixLinks() { + var thisUri = window.location.href; + thisUri = thisUri.slice(0, thisUri.length - window.location.hash.length); + var aelements = document.getElementsByTagName('A'); + for (var i = 0; i < aelements.length; i++) { + var a = aelements[i].href; + var slideID = a.match('\#.+'); + if ((slideID) && (slideID[0].slice(0,1) == '#')) { + var dest = findSlide(slideID[0].slice(1)); + if (dest != null) { + if (aelements[i].addEventListener) { + aelements[i].addEventListener("click", new Function("e", + "if (document.getElementById('slideProj').disabled) return;" + + "go("+dest+" - snum); " + + "if (e.preventDefault) e.preventDefault();"), true); + } else if (aelements[i].attachEvent) { + aelements[i].attachEvent("onclick", new Function("", + "if (document.getElementById('slideProj').disabled) return;" + + "go("+dest+" - snum); " + + "event.returnValue = false;")); + } + } + } + } +} + +function externalLinks() { + if (!document.getElementsByTagName) return; + var anchors = document.getElementsByTagName('a'); + for (var i=0; i' + + '