From 386c88e372ade86b1a7a2401c9829151c8c274e9 Mon Sep 17 00:00:00 2001 From: Vitor Santos Costa Date: Sun, 14 May 2017 11:27:44 +0100 Subject: [PATCH] update to latest ipykernel --- include/dswiatoms.h | 983 ------------------ packages/python/swig/MANIFEST.in | 32 +- packages/python/swig/YAP4PY.md | 93 ++ packages/python/swig/setup.py.in | 162 +++ packages/python/yap_kernel/CONTRIBUTING.md | 3 + packages/python/yap_kernel/COPYING.md | 59 ++ packages/python/yap_kernel/MANIFEST.in | 25 +- packages/python/yap_kernel/README.md | 39 + packages/python/yap_kernel/appveyor.yml | 35 + .../yap_kernel/data_kernelspec/kernel.json | 11 + .../yap_kernel/data_kernelspec/logo-32x32.png | Bin 0 -> 1084 bytes .../yap_kernel/data_kernelspec/logo-64x64.png | Bin 0 -> 2180 bytes ...rnel-4.7.0.dev0.macosx-10.12-x86_64.tar.gz | Bin 0 -> 187935 bytes .../dist/yap_kernel-4.7.0.dev0.tar.gz | Bin 0 -> 93844 bytes packages/python/yap_kernel/docs/changelog.rst | 194 ++++ packages/python/yap_kernel/docs/conf.py | 303 ++++++ packages/python/yap_kernel/docs/index.rst | 23 + packages/python/yap_kernel/docs/make.bat | 263 +++++ .../examples/embedding/inprocess_qtconsole.py | 46 + .../examples/embedding/inprocess_terminal.py | 31 + .../examples/embedding/internal_ipkernel.py | 55 + .../examples/embedding/ipkernel_qtapp.py | 75 ++ .../examples/embedding/ipkernel_wxapp.py | 119 +++ packages/python/yap_kernel/readthedocs.yml | 3 + packages/python/yap_kernel/setup.cfg | 13 + packages/python/yap_kernel/setup.py | 20 +- packages/python/yap_kernel/setup.py.in | 20 +- .../yap_kernel/yap_kernel.egg-info/PKG-INFO | 20 + .../yap_kernel.egg-info/SOURCES.txt | 81 ++ .../yap_kernel.egg-info/dependency_links.txt | 1 + .../yap_kernel.egg-info/requires.txt | 12 + .../yap_kernel.egg-info/top_level.txt | 2 + .../yap_kernel/yap_kernel/#__main__.py# | 5 + .../yap_kernel/yap_kernel/#kernelapp.py# | 492 +++++++++ .../python/yap_kernel/yap_kernel/__init__.py | 3 + .../python/yap_kernel/yap_kernel/__main__.py | 5 + .../python/yap_kernel/yap_kernel/_version.py | 5 + .../python/yap_kernel/yap_kernel/codeutil.py | 38 + .../yap_kernel/yap_kernel/comm/__init__.py | 2 + .../python/yap_kernel/yap_kernel/comm/comm.py | 164 +++ .../yap_kernel/yap_kernel/comm/manager.py | 130 +++ .../python/yap_kernel/yap_kernel/connect.py | 183 ++++ .../python/yap_kernel/yap_kernel/datapub.py | 62 ++ .../yap_kernel/yap_kernel/displayhook.py | 80 ++ .../python/yap_kernel/yap_kernel/embed.py | 57 + .../yap_kernel/yap_kernel/eventloops.py | 309 ++++++ .../yap_kernel/yap_kernel/gui/__init__.py | 15 + .../yap_kernel/yap_kernel/gui/gtk3embed.py | 88 ++ .../yap_kernel/yap_kernel/gui/gtkembed.py | 86 ++ .../python/yap_kernel/yap_kernel/heartbeat.py | 68 ++ .../yap_kernel/inprocess/__init__.py | 8 + .../yap_kernel/inprocess/blocking.py | 93 ++ .../yap_kernel/inprocess/channels.py | 97 ++ .../yap_kernel/yap_kernel/inprocess/client.py | 180 ++++ .../yap_kernel/inprocess/constants.py | 8 + .../yap_kernel/inprocess/ipkernel.py | 315 ++++++ .../yap_kernel/inprocess/manager.py | 81 ++ .../yap_kernel/yap_kernel/inprocess/socket.py | 64 ++ .../yap_kernel/inprocess/tests/__init__.py | 0 .../yap_kernel/inprocess/tests/test_kernel.py | 76 ++ .../inprocess/tests/test_kernelmanager.py | 115 ++ .../yap_kernel/yap_kernel/interactiveshell.py | 251 +++++ .../python/yap_kernel/yap_kernel/iostream.py | 383 +++++++ .../python/yap_kernel/yap_kernel/jsonutil.py | 173 +++ .../python/yap_kernel/yap_kernel/kernelapp.py | 491 +++++++++ .../yap_kernel/yap_kernel/kernelbase.py | 756 ++++++++++++++ .../yap_kernel/yap_kernel/kernelspec.py | 188 ++++ packages/python/yap_kernel/yap_kernel/log.py | 23 + .../yap_kernel/yap_kernel/parentpoller.py | 117 +++ .../yap_kernel/yap_kernel/pickleutil.py | 455 ++++++++ .../yap_kernel/yap_kernel/pylab/__init__.py | 0 .../yap_kernel/pylab/backend_inline.py | 163 +++ .../yap_kernel/yap_kernel/pylab/config.py | 110 ++ .../yap_kernel/resources/logo-32x32.png | Bin 0 -> 1084 bytes .../yap_kernel/resources/logo-64x64.png | Bin 0 -> 2180 bytes .../python/yap_kernel/yap_kernel/serialize.py | 186 ++++ .../yap_kernel/yap_kernel/tests/__init__.py | 49 + .../yap_kernel/tests/test_connect.py | 63 ++ .../yap_kernel/tests/test_embed_kernel.py | 163 +++ .../yap_kernel/yap_kernel/tests/test_io.py | 42 + .../yap_kernel/tests/test_jsonutil.py | 113 ++ .../yap_kernel/tests/test_kernel.py | 283 +++++ .../yap_kernel/tests/test_kernelspec.py | 146 +++ .../yap_kernel/tests/test_message_spec.py | 539 ++++++++++ .../yap_kernel/tests/test_pickleutil.py | 68 ++ .../yap_kernel/tests/test_serialize.py | 210 ++++ .../yap_kernel/tests/test_start_kernel.py | 48 + .../yap_kernel/tests/test_zmq_shell.py | 208 ++++ .../yap_kernel/yap_kernel/tests/utils.py | 166 +++ .../python/yap_kernel/yap_kernel/yapkernel.py | 381 +++++++ .../python/yap_kernel/yap_kernel/zmqshell.py | 601 +++++++++++ .../python/yap_kernel/yap_kernel_launcher.py | 16 + 92 files changed, 10935 insertions(+), 1009 deletions(-) delete mode 100644 include/dswiatoms.h create mode 100644 packages/python/swig/YAP4PY.md create mode 100644 packages/python/swig/setup.py.in create mode 100644 packages/python/yap_kernel/CONTRIBUTING.md create mode 100644 packages/python/yap_kernel/COPYING.md create mode 100644 packages/python/yap_kernel/README.md create mode 100644 packages/python/yap_kernel/appveyor.yml create mode 100644 packages/python/yap_kernel/data_kernelspec/kernel.json create mode 100644 packages/python/yap_kernel/data_kernelspec/logo-32x32.png create mode 100644 packages/python/yap_kernel/data_kernelspec/logo-64x64.png create mode 100644 packages/python/yap_kernel/dist/yap_kernel-4.7.0.dev0.macosx-10.12-x86_64.tar.gz create mode 100644 packages/python/yap_kernel/dist/yap_kernel-4.7.0.dev0.tar.gz create mode 100644 packages/python/yap_kernel/docs/changelog.rst create mode 100644 packages/python/yap_kernel/docs/conf.py create mode 100644 packages/python/yap_kernel/docs/index.rst create mode 100644 packages/python/yap_kernel/docs/make.bat create mode 100644 packages/python/yap_kernel/examples/embedding/inprocess_qtconsole.py create mode 100644 packages/python/yap_kernel/examples/embedding/inprocess_terminal.py create mode 100644 packages/python/yap_kernel/examples/embedding/internal_ipkernel.py create mode 100644 packages/python/yap_kernel/examples/embedding/ipkernel_qtapp.py create mode 100644 packages/python/yap_kernel/examples/embedding/ipkernel_wxapp.py create mode 100644 packages/python/yap_kernel/readthedocs.yml create mode 100644 packages/python/yap_kernel/setup.cfg create mode 100644 packages/python/yap_kernel/yap_kernel.egg-info/PKG-INFO create mode 100644 packages/python/yap_kernel/yap_kernel.egg-info/SOURCES.txt create mode 100644 packages/python/yap_kernel/yap_kernel.egg-info/dependency_links.txt create mode 100644 packages/python/yap_kernel/yap_kernel.egg-info/requires.txt create mode 100644 packages/python/yap_kernel/yap_kernel.egg-info/top_level.txt create mode 100644 packages/python/yap_kernel/yap_kernel/#__main__.py# create mode 100644 packages/python/yap_kernel/yap_kernel/#kernelapp.py# create mode 100644 packages/python/yap_kernel/yap_kernel/__init__.py create mode 100644 packages/python/yap_kernel/yap_kernel/__main__.py create mode 100644 packages/python/yap_kernel/yap_kernel/_version.py create mode 100644 packages/python/yap_kernel/yap_kernel/codeutil.py create mode 100644 packages/python/yap_kernel/yap_kernel/comm/__init__.py create mode 100644 packages/python/yap_kernel/yap_kernel/comm/comm.py create mode 100644 packages/python/yap_kernel/yap_kernel/comm/manager.py create mode 100644 packages/python/yap_kernel/yap_kernel/connect.py create mode 100644 packages/python/yap_kernel/yap_kernel/datapub.py create mode 100644 packages/python/yap_kernel/yap_kernel/displayhook.py create mode 100644 packages/python/yap_kernel/yap_kernel/embed.py create mode 100644 packages/python/yap_kernel/yap_kernel/eventloops.py create mode 100644 packages/python/yap_kernel/yap_kernel/gui/__init__.py create mode 100644 packages/python/yap_kernel/yap_kernel/gui/gtk3embed.py create mode 100644 packages/python/yap_kernel/yap_kernel/gui/gtkembed.py create mode 100644 packages/python/yap_kernel/yap_kernel/heartbeat.py create mode 100644 packages/python/yap_kernel/yap_kernel/inprocess/__init__.py create mode 100644 packages/python/yap_kernel/yap_kernel/inprocess/blocking.py create mode 100644 packages/python/yap_kernel/yap_kernel/inprocess/channels.py create mode 100644 packages/python/yap_kernel/yap_kernel/inprocess/client.py create mode 100644 packages/python/yap_kernel/yap_kernel/inprocess/constants.py create mode 100644 packages/python/yap_kernel/yap_kernel/inprocess/ipkernel.py create mode 100644 packages/python/yap_kernel/yap_kernel/inprocess/manager.py create mode 100644 packages/python/yap_kernel/yap_kernel/inprocess/socket.py create mode 100644 packages/python/yap_kernel/yap_kernel/inprocess/tests/__init__.py create mode 100644 packages/python/yap_kernel/yap_kernel/inprocess/tests/test_kernel.py create mode 100644 packages/python/yap_kernel/yap_kernel/inprocess/tests/test_kernelmanager.py create mode 100644 packages/python/yap_kernel/yap_kernel/interactiveshell.py create mode 100644 packages/python/yap_kernel/yap_kernel/iostream.py create mode 100644 packages/python/yap_kernel/yap_kernel/jsonutil.py create mode 100644 packages/python/yap_kernel/yap_kernel/kernelapp.py create mode 100644 packages/python/yap_kernel/yap_kernel/kernelbase.py create mode 100644 packages/python/yap_kernel/yap_kernel/kernelspec.py create mode 100644 packages/python/yap_kernel/yap_kernel/log.py create mode 100644 packages/python/yap_kernel/yap_kernel/parentpoller.py create mode 100644 packages/python/yap_kernel/yap_kernel/pickleutil.py create mode 100644 packages/python/yap_kernel/yap_kernel/pylab/__init__.py create mode 100644 packages/python/yap_kernel/yap_kernel/pylab/backend_inline.py create mode 100644 packages/python/yap_kernel/yap_kernel/pylab/config.py create mode 100644 packages/python/yap_kernel/yap_kernel/resources/logo-32x32.png create mode 100644 packages/python/yap_kernel/yap_kernel/resources/logo-64x64.png create mode 100644 packages/python/yap_kernel/yap_kernel/serialize.py create mode 100644 packages/python/yap_kernel/yap_kernel/tests/__init__.py create mode 100644 packages/python/yap_kernel/yap_kernel/tests/test_connect.py create mode 100644 packages/python/yap_kernel/yap_kernel/tests/test_embed_kernel.py create mode 100644 packages/python/yap_kernel/yap_kernel/tests/test_io.py create mode 100644 packages/python/yap_kernel/yap_kernel/tests/test_jsonutil.py create mode 100644 packages/python/yap_kernel/yap_kernel/tests/test_kernel.py create mode 100644 packages/python/yap_kernel/yap_kernel/tests/test_kernelspec.py create mode 100644 packages/python/yap_kernel/yap_kernel/tests/test_message_spec.py create mode 100644 packages/python/yap_kernel/yap_kernel/tests/test_pickleutil.py create mode 100644 packages/python/yap_kernel/yap_kernel/tests/test_serialize.py create mode 100644 packages/python/yap_kernel/yap_kernel/tests/test_start_kernel.py create mode 100644 packages/python/yap_kernel/yap_kernel/tests/test_zmq_shell.py create mode 100644 packages/python/yap_kernel/yap_kernel/tests/utils.py create mode 100644 packages/python/yap_kernel/yap_kernel/yapkernel.py create mode 100644 packages/python/yap_kernel/yap_kernel/zmqshell.py create mode 100644 packages/python/yap_kernel/yap_kernel_launcher.py diff --git a/include/dswiatoms.h b/include/dswiatoms.h deleted file mode 100644 index 25e81daa8..000000000 --- a/include/dswiatoms.h +++ /dev/null @@ -1,983 +0,0 @@ - - /* This file, dswiatoms.h, was generated automatically - by calling "yap -L misc/buildswiatoms" - and is based on SWIATOMS, copied from the SWI-Prolog distribution - please do not update */ - -#define ATOM_abort ((atom_t)(0*2+1)) -#define ATOM_aborted ((atom_t)(1*2+1)) -#define ATOM_abs ((atom_t)(2*2+1)) -#define ATOM_access ((atom_t)(3*2+1)) -#define ATOM_access_level ((atom_t)(4*2+1)) -#define ATOM_acos ((atom_t)(5*2+1)) -#define ATOM_acosh ((atom_t)(6*2+1)) -#define ATOM_acyclic_term ((atom_t)(7*2+1)) -#define ATOM_add_import ((atom_t)(8*2+1)) -#define ATOM_address ((atom_t)(9*2+1)) -#define ATOM_agc ((atom_t)(10*2+1)) -#define ATOM_agc_gained ((atom_t)(11*2+1)) -#define ATOM_agc_margin ((atom_t)(12*2+1)) -#define ATOM_agc_time ((atom_t)(13*2+1)) -#define ATOM_alias ((atom_t)(14*2+1)) -#define ATOM_allow_variable_name_as_functor ((atom_t)(15*2+1)) -#define ATOM_alnum ((atom_t)(16*2+1)) -#define ATOM_alpha ((atom_t)(17*2+1)) -#define ATOM_alternative ((atom_t)(18*2+1)) -#define ATOM_and ((atom_t)(19*2+1)) -#define ATOM_anonvar ((atom_t)(20*2+1)) -#define ATOM_append ((atom_t)(21*2+1)) -#define ATOM_ar_equals ((atom_t)(22*2+1)) -#define ATOM_ar_not_equal ((atom_t)(23*2+1)) -#define ATOM_argument ((atom_t)(24*2+1)) -#define ATOM_argumentlimit ((atom_t)(25*2+1)) -#define ATOM_arity ((atom_t)(26*2+1)) -#define ATOM_as ((atom_t)(27*2+1)) -#define ATOM_ascii ((atom_t)(28*2+1)) -#define ATOM_asin ((atom_t)(29*2+1)) -#define ATOM_asinh ((atom_t)(30*2+1)) -#define ATOM_assert ((atom_t)(31*2+1)) -#define ATOM_asserta ((atom_t)(32*2+1)) -#define ATOM_at ((atom_t)(33*2+1)) -#define ATOM_at_equals ((atom_t)(34*2+1)) -#define ATOM_at_exit ((atom_t)(35*2+1)) -#define ATOM_at_larger ((atom_t)(36*2+1)) -#define ATOM_at_larger_eq ((atom_t)(37*2+1)) -#define ATOM_at_not_equals ((atom_t)(38*2+1)) -#define ATOM_at_smaller ((atom_t)(39*2+1)) -#define ATOM_at_smaller_eq ((atom_t)(40*2+1)) -#define ATOM_atan ((atom_t)(41*2+1)) -#define ATOM_atan2 ((atom_t)(42*2+1)) -#define ATOM_atanh ((atom_t)(43*2+1)) -#define ATOM_atom ((atom_t)(44*2+1)) -#define ATOM_atom_garbage_collection ((atom_t)(45*2+1)) -#define ATOM_atomic ((atom_t)(46*2+1)) -#define ATOM_atoms ((atom_t)(47*2+1)) -#define ATOM_att ((atom_t)(48*2+1)) -#define ATOM_attributes ((atom_t)(49*2+1)) -#define ATOM_attvar ((atom_t)(50*2+1)) -#define ATOM_autoload ((atom_t)(51*2+1)) -#define ATOM_back_quotes ((atom_t)(52*2+1)) -#define ATOM_backslash ((atom_t)(53*2+1)) -#define ATOM_backtrace ((atom_t)(54*2+1)) -#define ATOM_backquoted_string ((atom_t)(55*2+1)) -#define ATOM_bar ((atom_t)(56*2+1)) -#define ATOM_base ((atom_t)(57*2+1)) -#define ATOM_begin ((atom_t)(58*2+1)) -#define ATOM_binary ((atom_t)(59*2+1)) -#define ATOM_binary_stream ((atom_t)(60*2+1)) -#define ATOM_bind ((atom_t)(61*2+1)) -#define ATOM_bitor ((atom_t)(62*2+1)) -#define ATOM_blobs ((atom_t)(63*2+1)) -#define ATOM_bof ((atom_t)(64*2+1)) -#define ATOM_bom ((atom_t)(65*2+1)) -#define ATOM_bool ((atom_t)(66*2+1)) -#define ATOM_boolean ((atom_t)(67*2+1)) -#define ATOM_brace_term_position ((atom_t)(68*2+1)) -#define ATOM_brace_terms ((atom_t)(69*2+1)) -#define ATOM_break ((atom_t)(70*2+1)) -#define ATOM_break_level ((atom_t)(71*2+1)) -#define ATOM_btree ((atom_t)(72*2+1)) -#define ATOM_buffer ((atom_t)(73*2+1)) -#define ATOM_buffer_size ((atom_t)(74*2+1)) -#define ATOM_built_in_procedure ((atom_t)(75*2+1)) -#define ATOM_busy ((atom_t)(76*2+1)) -#define ATOM_byte ((atom_t)(77*2+1)) -#define ATOM_c_stack ((atom_t)(78*2+1)) -#define ATOM_call ((atom_t)(79*2+1)) -#define ATOM_callable ((atom_t)(80*2+1)) -#define ATOM_canceled ((atom_t)(81*2+1)) -#define ATOM_case_sensitive_file_names ((atom_t)(82*2+1)) -#define ATOM_catch ((atom_t)(83*2+1)) -#define ATOM_category ((atom_t)(84*2+1)) -#define ATOM_ceil ((atom_t)(85*2+1)) -#define ATOM_ceiling ((atom_t)(86*2+1)) -#define ATOM_char_type ((atom_t)(87*2+1)) -#define ATOM_character ((atom_t)(88*2+1)) -#define ATOM_character_code ((atom_t)(89*2+1)) -#define ATOM_character_escapes ((atom_t)(90*2+1)) -#define ATOM_chars ((atom_t)(91*2+1)) -#define ATOM_chdir ((atom_t)(92*2+1)) -#define ATOM_chmod ((atom_t)(93*2+1)) -#define ATOM_choice ((atom_t)(94*2+1)) -#define ATOM_class ((atom_t)(95*2+1)) -#define ATOM_clause ((atom_t)(96*2+1)) -#define ATOM_clause_reference ((atom_t)(97*2+1)) -#define ATOM_clauses ((atom_t)(98*2+1)) -#define ATOM_close ((atom_t)(99*2+1)) -#define ATOM_close_on_abort ((atom_t)(100*2+1)) -#define ATOM_close_on_exec ((atom_t)(101*2+1)) -#define ATOM_close_option ((atom_t)(102*2+1)) -#define ATOM_cm ((atom_t)(103*2+1)) -#define ATOM_cntrl ((atom_t)(104*2+1)) -#define ATOM_co ((atom_t)(105*2+1)) -#define ATOM_codes ((atom_t)(106*2+1)) -#define ATOM_collected ((atom_t)(107*2+1)) -#define ATOM_collections ((atom_t)(108*2+1)) -#define ATOM_colon ((atom_t)(109*2+1)) -#define ATOM_colon_eq ((atom_t)(110*2+1)) -#define ATOM_comma ((atom_t)(111*2+1)) -#define ATOM_comments ((atom_t)(112*2+1)) -#define ATOM_compound ((atom_t)(113*2+1)) -#define ATOM_context ((atom_t)(114*2+1)) -#define ATOM_context_module ((atom_t)(115*2+1)) -#define ATOM_continue ((atom_t)(116*2+1)) -#define ATOM_copysign ((atom_t)(117*2+1)) -#define ATOM_core ((atom_t)(118*2+1)) -#define ATOM_core_left ((atom_t)(119*2+1)) -#define ATOM_cos ((atom_t)(120*2+1)) -#define ATOM_cosh ((atom_t)(121*2+1)) -#define ATOM_cputime ((atom_t)(122*2+1)) -#define ATOM_create ((atom_t)(123*2+1)) -#define ATOM_csym ((atom_t)(124*2+1)) -#define ATOM_csymf ((atom_t)(125*2+1)) -#define ATOM_cumulative ((atom_t)(126*2+1)) -#define ATOM_curl ((atom_t)(127*2+1)) -#define ATOM_current ((atom_t)(128*2+1)) -#define ATOM_current_input ((atom_t)(129*2+1)) -#define ATOM_current_locale ((atom_t)(130*2+1)) -#define ATOM_current_output ((atom_t)(131*2+1)) -#define ATOM_cut ((atom_t)(132*2+1)) -#define ATOM_cut_call ((atom_t)(133*2+1)) -#define ATOM_cut_exit ((atom_t)(134*2+1)) -#define ATOM_cut_parent ((atom_t)(135*2+1)) -#define ATOM_cutted ((atom_t)(136*2+1)) -#define ATOM_cycles ((atom_t)(137*2+1)) -#define ATOM_cyclic_term ((atom_t)(138*2+1)) -#define ATOM_dand ((atom_t)(139*2+1)) -#define ATOM_date ((atom_t)(140*2+1)) -#define ATOM_db_reference ((atom_t)(141*2+1)) -#define ATOM_dc_call_prolog ((atom_t)(142*2+1)) -#define ATOM_dcall ((atom_t)(143*2+1)) -#define ATOM_dcall_cleanup ((atom_t)(144*2+1)) -#define ATOM_dcatch ((atom_t)(145*2+1)) -#define ATOM_dcut ((atom_t)(146*2+1)) -#define ATOM_dde_error ((atom_t)(147*2+1)) -#define ATOM_dde_handle ((atom_t)(148*2+1)) -#define ATOM_deadline ((atom_t)(149*2+1)) -#define ATOM_debug ((atom_t)(150*2+1)) -#define ATOM_debug_on_error ((atom_t)(151*2+1)) -#define ATOM_debug_topic ((atom_t)(152*2+1)) -#define ATOM_debugger_print_options ((atom_t)(153*2+1)) -#define ATOM_debugger_show_context ((atom_t)(154*2+1)) -#define ATOM_debugging ((atom_t)(155*2+1)) -#define ATOM_dec10 ((atom_t)(156*2+1)) -#define ATOM_decimal_point ((atom_t)(157*2+1)) -#define ATOM_default ((atom_t)(158*2+1)) -#define ATOM_defined ((atom_t)(159*2+1)) -#define ATOM_delete ((atom_t)(160*2+1)) -#define ATOM_depth_limit_exceeded ((atom_t)(161*2+1)) -#define ATOM_destroy ((atom_t)(162*2+1)) -#define ATOM_detached ((atom_t)(163*2+1)) -#define ATOM_detect ((atom_t)(164*2+1)) -#define ATOM_development ((atom_t)(165*2+1)) -#define ATOM_dexit ((atom_t)(166*2+1)) -#define ATOM_dforeign_registered ((atom_t)(167*2+1)) -#define ATOM_dgarbage_collect ((atom_t)(168*2+1)) -#define ATOM_digit ((atom_t)(169*2+1)) -#define ATOM_directory ((atom_t)(170*2+1)) -#define ATOM_discontiguous ((atom_t)(171*2+1)) -#define ATOM_div ((atom_t)(172*2+1)) -#define ATOM_divide ((atom_t)(173*2+1)) -#define ATOM_dload ((atom_t)(174*2+1)) -#define ATOM_dmessage_queue ((atom_t)(175*2+1)) -#define ATOM_dmutex ((atom_t)(176*2+1)) -#define ATOM_domain_error ((atom_t)(177*2+1)) -#define ATOM_dos ((atom_t)(178*2+1)) -#define ATOM_dot ((atom_t)(179*2+1)) -#define ATOM_dot_lists ((atom_t)(180*2+1)) -#define ATOM_dots ((atom_t)(181*2+1)) -#define ATOM_double_quotes ((atom_t)(182*2+1)) -#define ATOM_doublestar ((atom_t)(183*2+1)) -#define ATOM_dparse_quasi_quotations ((atom_t)(184*2+1)) -#define ATOM_dprof_node ((atom_t)(185*2+1)) -#define ATOM_dquasi_quotation ((atom_t)(186*2+1)) -#define ATOM_dquery_loop ((atom_t)(187*2+1)) -#define ATOM_drecover_and_rethrow ((atom_t)(188*2+1)) -#define ATOM_dstream ((atom_t)(189*2+1)) -#define ATOM_dthread_init ((atom_t)(190*2+1)) -#define ATOM_dthrow ((atom_t)(191*2+1)) -#define ATOM_dtime ((atom_t)(192*2+1)) -#define ATOM_dtoplevel ((atom_t)(193*2+1)) -#define ATOM_duplicate_key ((atom_t)(194*2+1)) -#define ATOM_dvard ((atom_t)(195*2+1)) -#define ATOM_dvariable_names ((atom_t)(196*2+1)) -#define ATOM_dwakeup ((atom_t)(197*2+1)) -#define ATOM_dynamic ((atom_t)(198*2+1)) -#define ATOM_e ((atom_t)(199*2+1)) -#define ATOM_encoding ((atom_t)(200*2+1)) -#define ATOM_end ((atom_t)(201*2+1)) -#define ATOM_end_of_file ((atom_t)(202*2+1)) -#define ATOM_end_of_line ((atom_t)(203*2+1)) -#define ATOM_end_of_stream ((atom_t)(204*2+1)) -#define ATOM_environment ((atom_t)(205*2+1)) -#define ATOM_eof ((atom_t)(206*2+1)) -#define ATOM_eof_action ((atom_t)(207*2+1)) -#define ATOM_eof_code ((atom_t)(208*2+1)) -#define ATOM_epsilon ((atom_t)(209*2+1)) -#define ATOM_equal ((atom_t)(210*2+1)) -#define ATOM_equals ((atom_t)(211*2+1)) -#define ATOM_erase ((atom_t)(212*2+1)) -#define ATOM_erased ((atom_t)(213*2+1)) -#define ATOM_erf ((atom_t)(214*2+1)) -#define ATOM_erfc ((atom_t)(215*2+1)) -#define ATOM_error ((atom_t)(216*2+1)) -#define ATOM_eval ((atom_t)(217*2+1)) -#define ATOM_evaluable ((atom_t)(218*2+1)) -#define ATOM_evaluation_error ((atom_t)(219*2+1)) -#define ATOM_exception ((atom_t)(220*2+1)) -#define ATOM_exclusive ((atom_t)(221*2+1)) -#define ATOM_execute ((atom_t)(222*2+1)) -#define ATOM_exist ((atom_t)(223*2+1)) -#define ATOM_existence_error ((atom_t)(224*2+1)) -#define ATOM_exit ((atom_t)(225*2+1)) -#define ATOM_exited ((atom_t)(226*2+1)) -#define ATOM_exp ((atom_t)(227*2+1)) -#define ATOM_export ((atom_t)(228*2+1)) -#define ATOM_exported ((atom_t)(229*2+1)) -#define ATOM_exports ((atom_t)(230*2+1)) -#define ATOM_expression ((atom_t)(231*2+1)) -#define ATOM_external_exception ((atom_t)(232*2+1)) -#define ATOM_externals ((atom_t)(233*2+1)) -#define ATOM_fact ((atom_t)(234*2+1)) -#define ATOM_factor ((atom_t)(235*2+1)) -#define ATOM_fail ((atom_t)(236*2+1)) -#define ATOM_failure_error ((atom_t)(237*2+1)) -#define ATOM_false ((atom_t)(238*2+1)) -#define ATOM_feature ((atom_t)(239*2+1)) -#define ATOM_file ((atom_t)(240*2+1)) -#define ATOM_file_name ((atom_t)(241*2+1)) -#define ATOM_file_name_variables ((atom_t)(242*2+1)) -#define ATOM_file_no ((atom_t)(243*2+1)) -#define ATOM_flag ((atom_t)(244*2+1)) -#define ATOM_flag_value ((atom_t)(245*2+1)) -#define ATOM_float ((atom_t)(246*2+1)) -#define ATOM_float_format ((atom_t)(247*2+1)) -#define ATOM_float_fractional_part ((atom_t)(248*2+1)) -#define ATOM_float_integer_part ((atom_t)(249*2+1)) -#define ATOM_float_overflow ((atom_t)(250*2+1)) -#define ATOM_float_underflow ((atom_t)(251*2+1)) -#define ATOM_floor ((atom_t)(252*2+1)) -#define ATOM_force ((atom_t)(253*2+1)) -#define ATOM_foreign ((atom_t)(254*2+1)) -#define ATOM_foreign_function ((atom_t)(255*2+1)) -#define ATOM_foreign_return_value ((atom_t)(256*2+1)) -#define ATOM_fork ((atom_t)(257*2+1)) -#define ATOM_frame ((atom_t)(258*2+1)) -#define ATOM_frame_attribute ((atom_t)(259*2+1)) -#define ATOM_frame_finished ((atom_t)(260*2+1)) -#define ATOM_frame_reference ((atom_t)(261*2+1)) -#define ATOM_free_of_attvar ((atom_t)(262*2+1)) -#define ATOM_freeze ((atom_t)(263*2+1)) -#define ATOM_full ((atom_t)(264*2+1)) -#define ATOM_fullstop ((atom_t)(265*2+1)) -#define ATOM_functor_name ((atom_t)(266*2+1)) -#define ATOM_functors ((atom_t)(267*2+1)) -#define ATOM_fx ((atom_t)(268*2+1)) -#define ATOM_fy ((atom_t)(269*2+1)) -#define ATOM_garbage_collected ((atom_t)(270*2+1)) -#define ATOM_garbage_collection ((atom_t)(271*2+1)) -#define ATOM_gc ((atom_t)(272*2+1)) -#define ATOM_gcd ((atom_t)(273*2+1)) -#define ATOM_gctime ((atom_t)(274*2+1)) -#define ATOM_gdiv ((atom_t)(275*2+1)) -#define ATOM_getcwd ((atom_t)(276*2+1)) -#define ATOM_global ((atom_t)(277*2+1)) -#define ATOM_global_shifts ((atom_t)(278*2+1)) -#define ATOM_global_stack ((atom_t)(279*2+1)) -#define ATOM_globallimit ((atom_t)(280*2+1)) -#define ATOM_globalused ((atom_t)(281*2+1)) -#define ATOM_goal ((atom_t)(282*2+1)) -#define ATOM_goal_expansion ((atom_t)(283*2+1)) -#define ATOM_grammar ((atom_t)(284*2+1)) -#define ATOM_graph ((atom_t)(285*2+1)) -#define ATOM_ground ((atom_t)(286*2+1)) -#define ATOM_grouping ((atom_t)(287*2+1)) -#define ATOM_gvar ((atom_t)(288*2+1)) -#define ATOM_halt ((atom_t)(289*2+1)) -#define ATOM_has_alternatives ((atom_t)(290*2+1)) -#define ATOM_hash ((atom_t)(291*2+1)) -#define ATOM_hashed ((atom_t)(292*2+1)) -#define ATOM_hat ((atom_t)(293*2+1)) -#define ATOM_heap_gc ((atom_t)(294*2+1)) -#define ATOM_heapused ((atom_t)(295*2+1)) -#define ATOM_help ((atom_t)(296*2+1)) -#define ATOM_hidden ((atom_t)(297*2+1)) -#define ATOM_hide_childs ((atom_t)(298*2+1)) -#define ATOM_history_depth ((atom_t)(299*2+1)) -#define ATOM_ifthen ((atom_t)(300*2+1)) -#define ATOM_ignore ((atom_t)(301*2+1)) -#define ATOM_ignore_ops ((atom_t)(302*2+1)) -#define ATOM_import_into ((atom_t)(303*2+1)) -#define ATOM_import_type ((atom_t)(304*2+1)) -#define ATOM_imported ((atom_t)(305*2+1)) -#define ATOM_imported_procedure ((atom_t)(306*2+1)) -#define ATOM_index ((atom_t)(307*2+1)) -#define ATOM_indexed ((atom_t)(308*2+1)) -#define ATOM_inf ((atom_t)(309*2+1)) -#define ATOM_inferences ((atom_t)(310*2+1)) -#define ATOM_infinite ((atom_t)(311*2+1)) -#define ATOM_informational ((atom_t)(312*2+1)) -#define ATOM_init_file ((atom_t)(313*2+1)) -#define ATOM_initialization ((atom_t)(314*2+1)) -#define ATOM_input ((atom_t)(315*2+1)) -#define ATOM_inserted_char ((atom_t)(316*2+1)) -#define ATOM_instantiation_error ((atom_t)(317*2+1)) -#define ATOM_int ((atom_t)(318*2+1)) -#define ATOM_int64_t ((atom_t)(319*2+1)) -#define ATOM_int_overflow ((atom_t)(320*2+1)) -#define ATOM_integer ((atom_t)(321*2+1)) -#define ATOM_integer_expression ((atom_t)(322*2+1)) -#define ATOM_interrupt ((atom_t)(323*2+1)) -#define ATOM_io_error ((atom_t)(324*2+1)) -#define ATOM_io_mode ((atom_t)(325*2+1)) -#define ATOM_ioctl ((atom_t)(326*2+1)) -#define ATOM_is ((atom_t)(327*2+1)) -#define ATOM_iso ((atom_t)(328*2+1)) -#define ATOM_iso_latin_1 ((atom_t)(329*2+1)) -#define ATOM_isovar ((atom_t)(330*2+1)) -#define ATOM_join ((atom_t)(331*2+1)) -#define ATOM_jump ((atom_t)(332*2+1)) -#define ATOM_kernel ((atom_t)(333*2+1)) -#define ATOM_key ((atom_t)(334*2+1)) -#define ATOM_key_value_position ((atom_t)(335*2+1)) -#define ATOM_larger ((atom_t)(336*2+1)) -#define ATOM_larger_equal ((atom_t)(337*2+1)) -#define ATOM_level ((atom_t)(338*2+1)) -#define ATOM_lgamma ((atom_t)(339*2+1)) -#define ATOM_li ((atom_t)(340*2+1)) -#define ATOM_library ((atom_t)(341*2+1)) -#define ATOM_limit ((atom_t)(342*2+1)) -#define ATOM_line ((atom_t)(343*2+1)) -#define ATOM_line_count ((atom_t)(344*2+1)) -#define ATOM_line_position ((atom_t)(345*2+1)) -#define ATOM_list ((atom_t)(346*2+1)) -#define ATOM_list_position ((atom_t)(347*2+1)) -#define ATOM_listing ((atom_t)(348*2+1)) -#define ATOM_local ((atom_t)(349*2+1)) -#define ATOM_local_shifts ((atom_t)(350*2+1)) -#define ATOM_local_stack ((atom_t)(351*2+1)) -#define ATOM_locale ((atom_t)(352*2+1)) -#define ATOM_locale_property ((atom_t)(353*2+1)) -#define ATOM_locallimit ((atom_t)(354*2+1)) -#define ATOM_localused ((atom_t)(355*2+1)) -#define ATOM_lock ((atom_t)(356*2+1)) -#define ATOM_locked ((atom_t)(357*2+1)) -#define ATOM_log ((atom_t)(358*2+1)) -#define ATOM_log10 ((atom_t)(359*2+1)) -#define ATOM_long ((atom_t)(360*2+1)) -#define ATOM_loose ((atom_t)(361*2+1)) -#define ATOM_low ((atom_t)(362*2+1)) -#define ATOM_lower ((atom_t)(363*2+1)) -#define ATOM_lsb ((atom_t)(364*2+1)) -#define ATOM_lshift ((atom_t)(365*2+1)) -#define ATOM_main ((atom_t)(366*2+1)) -#define ATOM_map ((atom_t)(367*2+1)) -#define ATOM_map_position ((atom_t)(368*2+1)) -#define ATOM_map_punify ((atom_t)(369*2+1)) -#define ATOM_map_select ((atom_t)(370*2+1)) -#define ATOM_mark ((atom_t)(371*2+1)) -#define ATOM_matches ((atom_t)(372*2+1)) -#define ATOM_max ((atom_t)(373*2+1)) -#define ATOM_max_arity ((atom_t)(374*2+1)) -#define ATOM_max_dde_handles ((atom_t)(375*2+1)) -#define ATOM_max_depth ((atom_t)(376*2+1)) -#define ATOM_max_files ((atom_t)(377*2+1)) -#define ATOM_max_frame_size ((atom_t)(378*2+1)) -#define ATOM_max_length ((atom_t)(379*2+1)) -#define ATOM_max_path_length ((atom_t)(380*2+1)) -#define ATOM_max_size ((atom_t)(381*2+1)) -#define ATOM_max_variable_length ((atom_t)(382*2+1)) -#define ATOM_memory ((atom_t)(383*2+1)) -#define ATOM_message ((atom_t)(384*2+1)) -#define ATOM_message_lines ((atom_t)(385*2+1)) -#define ATOM_message_queue ((atom_t)(386*2+1)) -#define ATOM_message_queue_property ((atom_t)(387*2+1)) -#define ATOM_meta_argument ((atom_t)(388*2+1)) -#define ATOM_meta_argument_specifier ((atom_t)(389*2+1)) -#define ATOM_meta_atom ((atom_t)(390*2+1)) -#define ATOM_meta_predicate ((atom_t)(391*2+1)) -#define ATOM_min ((atom_t)(392*2+1)) -#define ATOM_min_free ((atom_t)(393*2+1)) -#define ATOM_minus ((atom_t)(394*2+1)) -#define ATOM_mismatched_char ((atom_t)(395*2+1)) -#define ATOM_mod ((atom_t)(396*2+1)) -#define ATOM_mode ((atom_t)(397*2+1)) -#define ATOM_modify ((atom_t)(398*2+1)) -#define ATOM_module ((atom_t)(399*2+1)) -#define ATOM_module_class ((atom_t)(400*2+1)) -#define ATOM_module_property ((atom_t)(401*2+1)) -#define ATOM_module_transparent ((atom_t)(402*2+1)) -#define ATOM_modules ((atom_t)(403*2+1)) -#define ATOM_msb ((atom_t)(404*2+1)) -#define ATOM_multifile ((atom_t)(405*2+1)) -#define ATOM_mutex ((atom_t)(406*2+1)) -#define ATOM_mutex_option ((atom_t)(407*2+1)) -#define ATOM_mutex_property ((atom_t)(408*2+1)) -#define ATOM_natural ((atom_t)(409*2+1)) -#define ATOM_newline ((atom_t)(410*2+1)) -#define ATOM_next_argument ((atom_t)(411*2+1)) -#define ATOM_nil ((atom_t)(412*2+1)) -#define ATOM_nl ((atom_t)(413*2+1)) -#define ATOM_nlink ((atom_t)(414*2+1)) -#define ATOM_no_memory ((atom_t)(415*2+1)) -#define ATOM_nodebug ((atom_t)(416*2+1)) -#define ATOM_non_empty_list ((atom_t)(417*2+1)) -#define ATOM_non_terminal ((atom_t)(418*2+1)) -#define ATOM_none ((atom_t)(419*2+1)) -#define ATOM_nonvar ((atom_t)(420*2+1)) -#define ATOM_noprofile ((atom_t)(421*2+1)) -#define ATOM_normal ((atom_t)(422*2+1)) -#define ATOM_not ((atom_t)(423*2+1)) -#define ATOM_not_equals ((atom_t)(424*2+1)) -#define ATOM_not_implemented ((atom_t)(425*2+1)) -#define ATOM_not_less_than_one ((atom_t)(426*2+1)) -#define ATOM_not_less_than_zero ((atom_t)(427*2+1)) -#define ATOM_not_provable ((atom_t)(428*2+1)) -#define ATOM_not_strict_equal ((atom_t)(429*2+1)) -#define ATOM_not_unique ((atom_t)(430*2+1)) -#define ATOM_number ((atom_t)(431*2+1)) -#define ATOM_number_of_clauses ((atom_t)(432*2+1)) -#define ATOM_number_of_rules ((atom_t)(433*2+1)) -#define ATOM_numbervar_option ((atom_t)(434*2+1)) -#define ATOM_numbervars ((atom_t)(435*2+1)) -#define ATOM_occurs_check ((atom_t)(436*2+1)) -#define ATOM_octet ((atom_t)(437*2+1)) -#define ATOM_off ((atom_t)(438*2+1)) -#define ATOM_on ((atom_t)(439*2+1)) -#define ATOM_open ((atom_t)(440*2+1)) -#define ATOM_operator ((atom_t)(441*2+1)) -#define ATOM_operator_priority ((atom_t)(442*2+1)) -#define ATOM_operator_specifier ((atom_t)(443*2+1)) -#define ATOM_optimise ((atom_t)(444*2+1)) -#define ATOM_or ((atom_t)(445*2+1)) -#define ATOM_order ((atom_t)(446*2+1)) -#define ATOM_output ((atom_t)(447*2+1)) -#define ATOM_owner ((atom_t)(448*2+1)) -#define ATOM_pair ((atom_t)(449*2+1)) -#define ATOM_paren ((atom_t)(450*2+1)) -#define ATOM_parent ((atom_t)(451*2+1)) -#define ATOM_parent_goal ((atom_t)(452*2+1)) -#define ATOM_partial ((atom_t)(453*2+1)) -#define ATOM_past ((atom_t)(454*2+1)) -#define ATOM_past_end_of_stream ((atom_t)(455*2+1)) -#define ATOM_pattern ((atom_t)(456*2+1)) -#define ATOM_pc ((atom_t)(457*2+1)) -#define ATOM_peek ((atom_t)(458*2+1)) -#define ATOM_period ((atom_t)(459*2+1)) -#define ATOM_permission_error ((atom_t)(460*2+1)) -#define ATOM_pi ((atom_t)(461*2+1)) -#define ATOM_pipe ((atom_t)(462*2+1)) -#define ATOM_plain ((atom_t)(463*2+1)) -#define ATOM_plus ((atom_t)(464*2+1)) -#define ATOM_popcount ((atom_t)(465*2+1)) -#define ATOM_portray ((atom_t)(466*2+1)) -#define ATOM_portray_goal ((atom_t)(467*2+1)) -#define ATOM_position ((atom_t)(468*2+1)) -#define ATOM_posix ((atom_t)(469*2+1)) -#define ATOM_powm ((atom_t)(470*2+1)) -#define ATOM_predicate_indicator ((atom_t)(471*2+1)) -#define ATOM_predicates ((atom_t)(472*2+1)) -#define ATOM_print ((atom_t)(473*2+1)) -#define ATOM_print_message ((atom_t)(474*2+1)) -#define ATOM_priority ((atom_t)(475*2+1)) -#define ATOM_private_procedure ((atom_t)(476*2+1)) -#define ATOM_procedure ((atom_t)(477*2+1)) -#define ATOM_process_comment ((atom_t)(478*2+1)) -#define ATOM_process_cputime ((atom_t)(479*2+1)) -#define ATOM_profile_mode ((atom_t)(480*2+1)) -#define ATOM_profile_no_cpu_time ((atom_t)(481*2+1)) -#define ATOM_profile_node ((atom_t)(482*2+1)) -#define ATOM_program ((atom_t)(483*2+1)) -#define ATOM_program_counter ((atom_t)(484*2+1)) -#define ATOM_prolog ((atom_t)(485*2+1)) -#define ATOM_prolog_atom_start ((atom_t)(486*2+1)) -#define ATOM_prolog_flag ((atom_t)(487*2+1)) -#define ATOM_prolog_flag_access ((atom_t)(488*2+1)) -#define ATOM_prolog_flag_option ((atom_t)(489*2+1)) -#define ATOM_prolog_flag_type ((atom_t)(490*2+1)) -#define ATOM_prolog_identifier_continue ((atom_t)(491*2+1)) -#define ATOM_prolog_symbol ((atom_t)(492*2+1)) -#define ATOM_prolog_var_start ((atom_t)(493*2+1)) -#define ATOM_prompt ((atom_t)(494*2+1)) -#define ATOM_property ((atom_t)(495*2+1)) -#define ATOM_protocol ((atom_t)(496*2+1)) -#define ATOM_prove ((atom_t)(497*2+1)) -#define ATOM_public ((atom_t)(498*2+1)) -#define ATOM_punct ((atom_t)(499*2+1)) -#define ATOM_quasi_quotation ((atom_t)(500*2+1)) -#define ATOM_quasi_quotation_position ((atom_t)(501*2+1)) -#define ATOM_quasi_quotation_syntax ((atom_t)(502*2+1)) -#define ATOM_quasi_quotations ((atom_t)(503*2+1)) -#define ATOM_query ((atom_t)(504*2+1)) -#define ATOM_question_mark ((atom_t)(505*2+1)) -#define ATOM_queue_option ((atom_t)(506*2+1)) -#define ATOM_quiet ((atom_t)(507*2+1)) -#define ATOM_quote ((atom_t)(508*2+1)) -#define ATOM_quoted ((atom_t)(509*2+1)) -#define ATOM_radix ((atom_t)(510*2+1)) -#define ATOM_random ((atom_t)(511*2+1)) -#define ATOM_random_float ((atom_t)(512*2+1)) -#define ATOM_random_option ((atom_t)(513*2+1)) -#define ATOM_rational ((atom_t)(514*2+1)) -#define ATOM_rationalize ((atom_t)(515*2+1)) -#define ATOM_rdiv ((atom_t)(516*2+1)) -#define ATOM_read ((atom_t)(517*2+1)) -#define ATOM_read_only ((atom_t)(518*2+1)) -#define ATOM_read_option ((atom_t)(519*2+1)) -#define ATOM_read_write ((atom_t)(520*2+1)) -#define ATOM_readline ((atom_t)(521*2+1)) -#define ATOM_real_time ((atom_t)(522*2+1)) -#define ATOM_receiver ((atom_t)(523*2+1)) -#define ATOM_record ((atom_t)(524*2+1)) -#define ATOM_record_position ((atom_t)(525*2+1)) -#define ATOM_redefine ((atom_t)(526*2+1)) -#define ATOM_redo ((atom_t)(527*2+1)) -#define ATOM_redo_in_skip ((atom_t)(528*2+1)) -#define ATOM_references ((atom_t)(529*2+1)) -#define ATOM_rem ((atom_t)(530*2+1)) -#define ATOM_rename ((atom_t)(531*2+1)) -#define ATOM_repeat ((atom_t)(532*2+1)) -#define ATOM_report_error ((atom_t)(533*2+1)) -#define ATOM_reposition ((atom_t)(534*2+1)) -#define ATOM_representation_error ((atom_t)(535*2+1)) -#define ATOM_representation_errors ((atom_t)(536*2+1)) -#define ATOM_reset ((atom_t)(537*2+1)) -#define ATOM_resource_error ((atom_t)(538*2+1)) -#define ATOM_resource_handle ((atom_t)(539*2+1)) -#define ATOM_retry ((atom_t)(540*2+1)) -#define ATOM_round ((atom_t)(541*2+1)) -#define ATOM_rshift ((atom_t)(542*2+1)) -#define ATOM_running ((atom_t)(543*2+1)) -#define ATOM_runtime ((atom_t)(544*2+1)) -#define ATOM_save_class ((atom_t)(545*2+1)) -#define ATOM_save_option ((atom_t)(546*2+1)) -#define ATOM_scripting ((atom_t)(547*2+1)) -#define ATOM_see ((atom_t)(548*2+1)) -#define ATOM_seed ((atom_t)(549*2+1)) -#define ATOM_seek_method ((atom_t)(550*2+1)) -#define ATOM_select ((atom_t)(551*2+1)) -#define ATOM_semicolon ((atom_t)(552*2+1)) -#define ATOM_separated ((atom_t)(553*2+1)) -#define ATOM_set ((atom_t)(554*2+1)) -#define ATOM_set_end_of_stream ((atom_t)(555*2+1)) -#define ATOM_setup_call_catcher_cleanup ((atom_t)(556*2+1)) -#define ATOM_shared ((atom_t)(557*2+1)) -#define ATOM_shared_object ((atom_t)(558*2+1)) -#define ATOM_shared_object_handle ((atom_t)(559*2+1)) -#define ATOM_shell ((atom_t)(560*2+1)) -#define ATOM_shift_time ((atom_t)(561*2+1)) -#define ATOM_sign ((atom_t)(562*2+1)) -#define ATOM_signal ((atom_t)(563*2+1)) -#define ATOM_signal_handler ((atom_t)(564*2+1)) -#define ATOM_silent ((atom_t)(565*2+1)) -#define ATOM_sin ((atom_t)(566*2+1)) -#define ATOM_singletons ((atom_t)(567*2+1)) -#define ATOM_sinh ((atom_t)(568*2+1)) -#define ATOM_size ((atom_t)(569*2+1)) -#define ATOM_size_t ((atom_t)(570*2+1)) -#define ATOM_skip ((atom_t)(571*2+1)) -#define ATOM_skipped ((atom_t)(572*2+1)) -#define ATOM_smaller ((atom_t)(573*2+1)) -#define ATOM_smaller_equal ((atom_t)(574*2+1)) -#define ATOM_softcut ((atom_t)(575*2+1)) -#define ATOM_source_sink ((atom_t)(576*2+1)) -#define ATOM_space ((atom_t)(577*2+1)) -#define ATOM_spacing ((atom_t)(578*2+1)) -#define ATOM_spare ((atom_t)(579*2+1)) -#define ATOM_spy ((atom_t)(580*2+1)) -#define ATOM_sqrt ((atom_t)(581*2+1)) -#define ATOM_stack ((atom_t)(582*2+1)) -#define ATOM_stack_parameter ((atom_t)(583*2+1)) -#define ATOM_stack_shifts ((atom_t)(584*2+1)) -#define ATOM_stacks ((atom_t)(585*2+1)) -#define ATOM_stand_alone ((atom_t)(586*2+1)) -#define ATOM_standard ((atom_t)(587*2+1)) -#define ATOM_star ((atom_t)(588*2+1)) -#define ATOM_start ((atom_t)(589*2+1)) -#define ATOM_stat ((atom_t)(590*2+1)) -#define ATOM_state ((atom_t)(591*2+1)) -#define ATOM_static_procedure ((atom_t)(592*2+1)) -#define ATOM_statistics ((atom_t)(593*2+1)) -#define ATOM_status ((atom_t)(594*2+1)) -#define ATOM_stderr ((atom_t)(595*2+1)) -#define ATOM_stream ((atom_t)(596*2+1)) -#define ATOM_stream_option ((atom_t)(597*2+1)) -#define ATOM_stream_or_alias ((atom_t)(598*2+1)) -#define ATOM_stream_pair ((atom_t)(599*2+1)) -#define ATOM_stream_position ((atom_t)(600*2+1)) -#define ATOM_stream_property ((atom_t)(601*2+1)) -#define ATOM_stream_type_check ((atom_t)(602*2+1)) -#define ATOM_strict_equal ((atom_t)(603*2+1)) -#define ATOM_string ((atom_t)(604*2+1)) -#define ATOM_string_position ((atom_t)(605*2+1)) -#define ATOM_strong ((atom_t)(606*2+1)) -#define ATOM_subterm_positions ((atom_t)(607*2+1)) -#define ATOM_suffix ((atom_t)(608*2+1)) -#define ATOM_symbol_char ((atom_t)(609*2+1)) -#define ATOM_syntax_error ((atom_t)(610*2+1)) -#define ATOM_syntax_errors ((atom_t)(611*2+1)) -#define ATOM_system ((atom_t)(612*2+1)) -#define ATOM_SYSTEM_ERROR_INTERNAL ((atom_t)(613*2+1)) -#define ATOM_system_init_file ((atom_t)(614*2+1)) -#define ATOM_system_thread_id ((atom_t)(615*2+1)) -#define ATOM_system_time ((atom_t)(616*2+1)) -#define ATOM_tan ((atom_t)(617*2+1)) -#define ATOM_tanh ((atom_t)(618*2+1)) -#define ATOM_temporary_files ((atom_t)(619*2+1)) -#define ATOM_term ((atom_t)(620*2+1)) -#define ATOM_term_expansion ((atom_t)(621*2+1)) -#define ATOM_term_position ((atom_t)(622*2+1)) -#define ATOM_terminal ((atom_t)(623*2+1)) -#define ATOM_terminal_capability ((atom_t)(624*2+1)) -#define ATOM_test ((atom_t)(625*2+1)) -#define ATOM_text ((atom_t)(626*2+1)) -#define ATOM_text_stream ((atom_t)(627*2+1)) -#define ATOM_thousands_sep ((atom_t)(628*2+1)) -#define ATOM_thread ((atom_t)(629*2+1)) -#define ATOM_thread_cputime ((atom_t)(630*2+1)) -#define ATOM_thread_get_message_option ((atom_t)(631*2+1)) -#define ATOM_thread_initialization ((atom_t)(632*2+1)) -#define ATOM_thread_local ((atom_t)(633*2+1)) -#define ATOM_thread_local_procedure ((atom_t)(634*2+1)) -#define ATOM_thread_option ((atom_t)(635*2+1)) -#define ATOM_thread_property ((atom_t)(636*2+1)) -#define ATOM_threads ((atom_t)(637*2+1)) -#define ATOM_threads_created ((atom_t)(638*2+1)) -#define ATOM_throw ((atom_t)(639*2+1)) -#define ATOM_tilde ((atom_t)(640*2+1)) -#define ATOM_time ((atom_t)(641*2+1)) -#define ATOM_time_stamp ((atom_t)(642*2+1)) -#define ATOM_timeout ((atom_t)(643*2+1)) -#define ATOM_timeout_error ((atom_t)(644*2+1)) -#define ATOM_timezone ((atom_t)(645*2+1)) -#define ATOM_to_lower ((atom_t)(646*2+1)) -#define ATOM_to_upper ((atom_t)(647*2+1)) -#define ATOM_top ((atom_t)(648*2+1)) -#define ATOM_top_level ((atom_t)(649*2+1)) -#define ATOM_toplevel ((atom_t)(650*2+1)) -#define ATOM_trace ((atom_t)(651*2+1)) -#define ATOM_trace_any ((atom_t)(652*2+1)) -#define ATOM_trace_call ((atom_t)(653*2+1)) -#define ATOM_trace_exit ((atom_t)(654*2+1)) -#define ATOM_trace_fail ((atom_t)(655*2+1)) -#define ATOM_trace_gc ((atom_t)(656*2+1)) -#define ATOM_trace_redo ((atom_t)(657*2+1)) -#define ATOM_traceinterc ((atom_t)(658*2+1)) -#define ATOM_tracing ((atom_t)(659*2+1)) -#define ATOM_trail ((atom_t)(660*2+1)) -#define ATOM_trail_shifts ((atom_t)(661*2+1)) -#define ATOM_traillimit ((atom_t)(662*2+1)) -#define ATOM_trailused ((atom_t)(663*2+1)) -#define ATOM_transparent ((atom_t)(664*2+1)) -#define ATOM_transposed_char ((atom_t)(665*2+1)) -#define ATOM_transposed_word ((atom_t)(666*2+1)) -#define ATOM_true ((atom_t)(667*2+1)) -#define ATOM_truncate ((atom_t)(668*2+1)) -#define ATOM_tty ((atom_t)(669*2+1)) -#define ATOM_tty_control ((atom_t)(670*2+1)) -#define ATOM_type ((atom_t)(671*2+1)) -#define ATOM_type_error ((atom_t)(672*2+1)) -#define ATOM_undefined ((atom_t)(673*2+1)) -#define ATOM_undefined_global_variable ((atom_t)(674*2+1)) -#define ATOM_undefinterc ((atom_t)(675*2+1)) -#define ATOM_unicode_be ((atom_t)(676*2+1)) -#define ATOM_unicode_le ((atom_t)(677*2+1)) -#define ATOM_unify ((atom_t)(678*2+1)) -#define ATOM_unify_determined ((atom_t)(679*2+1)) -#define ATOM_uninstantiation_error ((atom_t)(680*2+1)) -#define ATOM_unique ((atom_t)(681*2+1)) -#define ATOM_univ ((atom_t)(682*2+1)) -#define ATOM_unknown ((atom_t)(683*2+1)) -#define ATOM_unlimited ((atom_t)(684*2+1)) -#define ATOM_unload ((atom_t)(685*2+1)) -#define ATOM_unlock ((atom_t)(686*2+1)) -#define ATOM_unlocked ((atom_t)(687*2+1)) -#define ATOM_update ((atom_t)(688*2+1)) -#define ATOM_upper ((atom_t)(689*2+1)) -#define ATOM_user ((atom_t)(690*2+1)) -#define ATOM_user_error ((atom_t)(691*2+1)) -#define ATOM_user_flags ((atom_t)(692*2+1)) -#define ATOM_user_input ((atom_t)(693*2+1)) -#define ATOM_user_output ((atom_t)(694*2+1)) -#define ATOM_utc ((atom_t)(695*2+1)) -#define ATOM_utf8 ((atom_t)(696*2+1)) -#define ATOM_v ((atom_t)(697*2+1)) -#define ATOM_var ((atom_t)(698*2+1)) -#define ATOM_variable ((atom_t)(699*2+1)) -#define ATOM_variable_names ((atom_t)(700*2+1)) -#define ATOM_variables ((atom_t)(701*2+1)) -#define ATOM_very_deep ((atom_t)(702*2+1)) -#define ATOM_vmi ((atom_t)(703*2+1)) -#define ATOM_volatile ((atom_t)(704*2+1)) -#define ATOM_wait ((atom_t)(705*2+1)) -#define ATOM_wakeup ((atom_t)(706*2+1)) -#define ATOM_walltime ((atom_t)(707*2+1)) -#define ATOM_warning ((atom_t)(708*2+1)) -#define ATOM_wchar_t ((atom_t)(709*2+1)) -#define ATOM_weak ((atom_t)(710*2+1)) -#define ATOM_when_condition ((atom_t)(711*2+1)) -#define ATOM_white ((atom_t)(712*2+1)) -#define ATOM_write ((atom_t)(713*2+1)) -#define ATOM_write_attributes ((atom_t)(714*2+1)) -#define ATOM_write_option ((atom_t)(715*2+1)) -#define ATOM_xdigit ((atom_t)(716*2+1)) -#define ATOM_xf ((atom_t)(717*2+1)) -#define ATOM_xfx ((atom_t)(718*2+1)) -#define ATOM_xfy ((atom_t)(719*2+1)) -#define ATOM_xml ((atom_t)(720*2+1)) -#define ATOM_xor ((atom_t)(721*2+1)) -#define ATOM_xpceref ((atom_t)(722*2+1)) -#define ATOM_yf ((atom_t)(723*2+1)) -#define ATOM_yfx ((atom_t)(724*2+1)) -#define ATOM_zero_divisor ((atom_t)(725*2+1)) -#define FUNCTOR_abs1 ((functor_t)(0*4+2)) -#define FUNCTOR_access1 ((functor_t)(1*4+2)) -#define FUNCTOR_acos1 ((functor_t)(2*4+2)) -#define FUNCTOR_acosh1 ((functor_t)(3*4+2)) -#define FUNCTOR_alias1 ((functor_t)(4*4+2)) -#define FUNCTOR_and2 ((functor_t)(5*4+2)) -#define FUNCTOR_ar_equals2 ((functor_t)(6*4+2)) -#define FUNCTOR_ar_not_equal2 ((functor_t)(7*4+2)) -#define FUNCTOR_asin1 ((functor_t)(8*4+2)) -#define FUNCTOR_asinh1 ((functor_t)(9*4+2)) -#define FUNCTOR_assert1 ((functor_t)(10*4+2)) -#define FUNCTOR_asserta1 ((functor_t)(11*4+2)) -#define FUNCTOR_atan1 ((functor_t)(12*4+2)) -#define FUNCTOR_atan2 ((functor_t)(13*4+2)) -#define FUNCTOR_atanh1 ((functor_t)(14*4+2)) -#define FUNCTOR_atan22 ((functor_t)(15*4+2)) -#define FUNCTOR_atom1 ((functor_t)(16*4+2)) -#define FUNCTOR_att3 ((functor_t)(17*4+2)) -#define FUNCTOR_backslash1 ((functor_t)(18*4+2)) -#define FUNCTOR_bar2 ((functor_t)(19*4+2)) -#define FUNCTOR_bitor2 ((functor_t)(20*4+2)) -#define FUNCTOR_bom1 ((functor_t)(21*4+2)) -#define FUNCTOR_brace_term_position3 ((functor_t)(22*4+2)) -#define FUNCTOR_break1 ((functor_t)(23*4+2)) -#define FUNCTOR_break2 ((functor_t)(24*4+2)) -#define FUNCTOR_break3 ((functor_t)(25*4+2)) -#define FUNCTOR_buffer1 ((functor_t)(26*4+2)) -#define FUNCTOR_buffer_size1 ((functor_t)(27*4+2)) -#define FUNCTOR_busy2 ((functor_t)(28*4+2)) -#define FUNCTOR_call1 ((functor_t)(29*4+2)) -#define FUNCTOR_catch3 ((functor_t)(30*4+2)) -#define FUNCTOR_ceil1 ((functor_t)(31*4+2)) -#define FUNCTOR_ceiling1 ((functor_t)(32*4+2)) -#define FUNCTOR_chars1 ((functor_t)(33*4+2)) -#define FUNCTOR_chars2 ((functor_t)(34*4+2)) -#define FUNCTOR_class1 ((functor_t)(35*4+2)) -#define FUNCTOR_clause1 ((functor_t)(36*4+2)) -#define FUNCTOR_close_on_abort1 ((functor_t)(37*4+2)) -#define FUNCTOR_close_on_exec1 ((functor_t)(38*4+2)) -#define FUNCTOR_codes1 ((functor_t)(39*4+2)) -#define FUNCTOR_codes2 ((functor_t)(40*4+2)) -#define FUNCTOR_colon2 ((functor_t)(41*4+2)) -#define FUNCTOR_comma2 ((functor_t)(42*4+2)) -#define FUNCTOR_context2 ((functor_t)(43*4+2)) -#define FUNCTOR_copysign2 ((functor_t)(44*4+2)) -#define FUNCTOR_cos1 ((functor_t)(45*4+2)) -#define FUNCTOR_cosh1 ((functor_t)(46*4+2)) -#define FUNCTOR_cputime0 ((functor_t)(47*4+2)) -#define FUNCTOR_curl1 ((functor_t)(48*4+2)) -#define FUNCTOR_cut_call1 ((functor_t)(49*4+2)) -#define FUNCTOR_cut_exit1 ((functor_t)(50*4+2)) -#define FUNCTOR_dand2 ((functor_t)(51*4+2)) -#define FUNCTOR_date3 ((functor_t)(52*4+2)) -#define FUNCTOR_date9 ((functor_t)(53*4+2)) -#define FUNCTOR_dc_call_prolog0 ((functor_t)(54*4+2)) -#define FUNCTOR_dcall1 ((functor_t)(55*4+2)) -#define FUNCTOR_dcut1 ((functor_t)(56*4+2)) -#define FUNCTOR_dde_error2 ((functor_t)(57*4+2)) -#define FUNCTOR_debugging1 ((functor_t)(58*4+2)) -#define FUNCTOR_decimal_point1 ((functor_t)(59*4+2)) -#define FUNCTOR_detached1 ((functor_t)(60*4+2)) -#define FUNCTOR_dexit2 ((functor_t)(61*4+2)) -#define FUNCTOR_dforeign_registered2 ((functor_t)(62*4+2)) -#define FUNCTOR_dgarbage_collect1 ((functor_t)(63*4+2)) -#define FUNCTOR_div2 ((functor_t)(64*4+2)) -#define FUNCTOR_gdiv2 ((functor_t)(65*4+2)) -#define FUNCTOR_divide2 ((functor_t)(66*4+2)) -#define FUNCTOR_dmessage_queue1 ((functor_t)(67*4+2)) -#define FUNCTOR_dmutex1 ((functor_t)(68*4+2)) -#define FUNCTOR_domain_error2 ((functor_t)(69*4+2)) -#define FUNCTOR_dot2 ((functor_t)(70*4+2)) -#define FUNCTOR_doublestar2 ((functor_t)(71*4+2)) -#define FUNCTOR_dparse_quasi_quotations2 ((functor_t)(72*4+2)) -#define FUNCTOR_dprof_node1 ((functor_t)(73*4+2)) -#define FUNCTOR_dquasi_quotation3 ((functor_t)(74*4+2)) -#define FUNCTOR_drecover_and_rethrow2 ((functor_t)(75*4+2)) -#define FUNCTOR_dstream1 ((functor_t)(76*4+2)) -#define FUNCTOR_dthread_init0 ((functor_t)(77*4+2)) -#define FUNCTOR_dthrow1 ((functor_t)(78*4+2)) -#define FUNCTOR_dtime2 ((functor_t)(79*4+2)) -#define FUNCTOR_duplicate_key1 ((functor_t)(80*4+2)) -#define FUNCTOR_dvard1 ((functor_t)(81*4+2)) -#define FUNCTOR_dwakeup1 ((functor_t)(82*4+2)) -#define FUNCTOR_e0 ((functor_t)(83*4+2)) -#define FUNCTOR_encoding1 ((functor_t)(84*4+2)) -#define FUNCTOR_end_of_stream1 ((functor_t)(85*4+2)) -#define FUNCTOR_eof_action1 ((functor_t)(86*4+2)) -#define FUNCTOR_epsilon0 ((functor_t)(87*4+2)) -#define FUNCTOR_equals2 ((functor_t)(88*4+2)) -#define FUNCTOR_erased1 ((functor_t)(89*4+2)) -#define FUNCTOR_erf1 ((functor_t)(90*4+2)) -#define FUNCTOR_erfc1 ((functor_t)(91*4+2)) -#define FUNCTOR_error2 ((functor_t)(92*4+2)) -#define FUNCTOR_eval1 ((functor_t)(93*4+2)) -#define FUNCTOR_evaluation_error1 ((functor_t)(94*4+2)) -#define FUNCTOR_exception1 ((functor_t)(95*4+2)) -#define FUNCTOR_exception3 ((functor_t)(96*4+2)) -#define FUNCTOR_existence_error2 ((functor_t)(97*4+2)) -#define FUNCTOR_existence_error3 ((functor_t)(98*4+2)) -#define FUNCTOR_exited1 ((functor_t)(99*4+2)) -#define FUNCTOR_exp1 ((functor_t)(100*4+2)) -#define FUNCTOR_exports1 ((functor_t)(101*4+2)) -#define FUNCTOR_external_exception1 ((functor_t)(102*4+2)) -#define FUNCTOR_fail0 ((functor_t)(103*4+2)) -#define FUNCTOR_failure_error1 ((functor_t)(104*4+2)) -#define FUNCTOR_file1 ((functor_t)(105*4+2)) -#define FUNCTOR_file4 ((functor_t)(106*4+2)) -#define FUNCTOR_file_name1 ((functor_t)(107*4+2)) -#define FUNCTOR_file_no1 ((functor_t)(108*4+2)) -#define FUNCTOR_float1 ((functor_t)(109*4+2)) -#define FUNCTOR_float_fractional_part1 ((functor_t)(110*4+2)) -#define FUNCTOR_float_integer_part1 ((functor_t)(111*4+2)) -#define FUNCTOR_floor1 ((functor_t)(112*4+2)) -#define FUNCTOR_foreign_function1 ((functor_t)(113*4+2)) -#define FUNCTOR_frame3 ((functor_t)(114*4+2)) -#define FUNCTOR_frame_finished1 ((functor_t)(115*4+2)) -#define FUNCTOR_gcd2 ((functor_t)(116*4+2)) -#define FUNCTOR_goal_expansion2 ((functor_t)(117*4+2)) -#define FUNCTOR_ground1 ((functor_t)(118*4+2)) -#define FUNCTOR_grouping1 ((functor_t)(119*4+2)) -#define FUNCTOR_hat2 ((functor_t)(120*4+2)) -#define FUNCTOR_ifthen2 ((functor_t)(121*4+2)) -#define FUNCTOR_import_into1 ((functor_t)(122*4+2)) -#define FUNCTOR_input0 ((functor_t)(123*4+2)) -#define FUNCTOR_input4 ((functor_t)(124*4+2)) -#define FUNCTOR_integer1 ((functor_t)(125*4+2)) -#define FUNCTOR_interrupt1 ((functor_t)(126*4+2)) -#define FUNCTOR_io_error2 ((functor_t)(127*4+2)) -#define FUNCTOR_is2 ((functor_t)(128*4+2)) -#define FUNCTOR_isovar1 ((functor_t)(129*4+2)) -#define FUNCTOR_key_value_position7 ((functor_t)(130*4+2)) -#define FUNCTOR_larger2 ((functor_t)(131*4+2)) -#define FUNCTOR_larger_equal2 ((functor_t)(132*4+2)) -#define FUNCTOR_lgamma1 ((functor_t)(133*4+2)) -#define FUNCTOR_line_count1 ((functor_t)(134*4+2)) -#define FUNCTOR_list_position4 ((functor_t)(135*4+2)) -#define FUNCTOR_listing1 ((functor_t)(136*4+2)) -#define FUNCTOR_locale1 ((functor_t)(137*4+2)) -#define FUNCTOR_locked2 ((functor_t)(138*4+2)) -#define FUNCTOR_log1 ((functor_t)(139*4+2)) -#define FUNCTOR_log101 ((functor_t)(140*4+2)) -#define FUNCTOR_lsb1 ((functor_t)(141*4+2)) -#define FUNCTOR_lshift2 ((functor_t)(142*4+2)) -#define FUNCTOR_map_position5 ((functor_t)(143*4+2)) -#define FUNCTOR_max2 ((functor_t)(144*4+2)) -#define FUNCTOR_max_size1 ((functor_t)(145*4+2)) -#define FUNCTOR_message_lines1 ((functor_t)(146*4+2)) -#define FUNCTOR_min2 ((functor_t)(147*4+2)) -#define FUNCTOR_minus1 ((functor_t)(148*4+2)) -#define FUNCTOR_minus2 ((functor_t)(149*4+2)) -#define FUNCTOR_mod2 ((functor_t)(150*4+2)) -#define FUNCTOR_mode1 ((functor_t)(151*4+2)) -#define FUNCTOR_msb1 ((functor_t)(152*4+2)) -#define FUNCTOR_newline1 ((functor_t)(153*4+2)) -#define FUNCTOR_nlink1 ((functor_t)(154*4+2)) -#define FUNCTOR_nonvar1 ((functor_t)(155*4+2)) -#define FUNCTOR_not_implemented2 ((functor_t)(156*4+2)) -#define FUNCTOR_not_provable1 ((functor_t)(157*4+2)) -#define FUNCTOR_not_strict_equal2 ((functor_t)(158*4+2)) -#define FUNCTOR_occurs_check2 ((functor_t)(159*4+2)) -#define FUNCTOR_or1 ((functor_t)(160*4+2)) -#define FUNCTOR_output0 ((functor_t)(161*4+2)) -#define FUNCTOR_permission_error3 ((functor_t)(162*4+2)) -#define FUNCTOR_pi0 ((functor_t)(163*4+2)) -#define FUNCTOR_pipe1 ((functor_t)(164*4+2)) -#define FUNCTOR_plus1 ((functor_t)(165*4+2)) -#define FUNCTOR_plus2 ((functor_t)(166*4+2)) -#define FUNCTOR_popcount1 ((functor_t)(167*4+2)) -#define FUNCTOR_portray1 ((functor_t)(168*4+2)) -#define FUNCTOR_position1 ((functor_t)(169*4+2)) -#define FUNCTOR_powm3 ((functor_t)(170*4+2)) -#define FUNCTOR_print1 ((functor_t)(171*4+2)) -#define FUNCTOR_print_message2 ((functor_t)(172*4+2)) -#define FUNCTOR_priority1 ((functor_t)(173*4+2)) -#define FUNCTOR_procedure2 ((functor_t)(174*4+2)) -#define FUNCTOR_prove1 ((functor_t)(175*4+2)) -#define FUNCTOR_prove2 ((functor_t)(176*4+2)) -#define FUNCTOR_punct2 ((functor_t)(177*4+2)) -#define FUNCTOR_quasi_quotation4 ((functor_t)(178*4+2)) -#define FUNCTOR_quasi_quotation_position5 ((functor_t)(179*4+2)) -#define FUNCTOR_random1 ((functor_t)(180*4+2)) -#define FUNCTOR_random_float0 ((functor_t)(181*4+2)) -#define FUNCTOR_rational1 ((functor_t)(182*4+2)) -#define FUNCTOR_rationalize1 ((functor_t)(183*4+2)) -#define FUNCTOR_rdiv2 ((functor_t)(184*4+2)) -#define FUNCTOR_redo1 ((functor_t)(185*4+2)) -#define FUNCTOR_rem2 ((functor_t)(186*4+2)) -#define FUNCTOR_repeat1 ((functor_t)(187*4+2)) -#define FUNCTOR_reposition1 ((functor_t)(188*4+2)) -#define FUNCTOR_representation_error1 ((functor_t)(189*4+2)) -#define FUNCTOR_representation_errors1 ((functor_t)(190*4+2)) -#define FUNCTOR_resource_error1 ((functor_t)(191*4+2)) -#define FUNCTOR_retry1 ((functor_t)(192*4+2)) -#define FUNCTOR_round1 ((functor_t)(193*4+2)) -#define FUNCTOR_rshift2 ((functor_t)(194*4+2)) -#define FUNCTOR_semicolon2 ((functor_t)(195*4+2)) -#define FUNCTOR_setup_call_catcher_cleanup4 ((functor_t)(196*4+2)) -#define FUNCTOR_shared_object2 ((functor_t)(197*4+2)) -#define FUNCTOR_shell2 ((functor_t)(198*4+2)) -#define FUNCTOR_sign1 ((functor_t)(199*4+2)) -#define FUNCTOR_signal1 ((functor_t)(200*4+2)) -#define FUNCTOR_signal2 ((functor_t)(201*4+2)) -#define FUNCTOR_sin1 ((functor_t)(202*4+2)) -#define FUNCTOR_singletons1 ((functor_t)(203*4+2)) -#define FUNCTOR_sinh1 ((functor_t)(204*4+2)) -#define FUNCTOR_size1 ((functor_t)(205*4+2)) -#define FUNCTOR_smaller2 ((functor_t)(206*4+2)) -#define FUNCTOR_smaller_equal2 ((functor_t)(207*4+2)) -#define FUNCTOR_softcut2 ((functor_t)(208*4+2)) -#define FUNCTOR_spy1 ((functor_t)(209*4+2)) -#define FUNCTOR_sqrt1 ((functor_t)(210*4+2)) -#define FUNCTOR_star2 ((functor_t)(211*4+2)) -#define FUNCTOR_status1 ((functor_t)(212*4+2)) -#define FUNCTOR_stream4 ((functor_t)(213*4+2)) -#define FUNCTOR_stream_position4 ((functor_t)(214*4+2)) -#define FUNCTOR_strict_equal2 ((functor_t)(215*4+2)) -#define FUNCTOR_string1 ((functor_t)(216*4+2)) -#define FUNCTOR_string2 ((functor_t)(217*4+2)) -#define FUNCTOR_string_position2 ((functor_t)(218*4+2)) -#define FUNCTOR_syntax_error1 ((functor_t)(219*4+2)) -#define FUNCTOR_syntax_error3 ((functor_t)(220*4+2)) -#define FUNCTOR_tan1 ((functor_t)(221*4+2)) -#define FUNCTOR_tanh1 ((functor_t)(222*4+2)) -#define FUNCTOR_term_expansion2 ((functor_t)(223*4+2)) -#define FUNCTOR_term_position5 ((functor_t)(224*4+2)) -#define FUNCTOR_thousands_sep1 ((functor_t)(225*4+2)) -#define FUNCTOR_timeout1 ((functor_t)(226*4+2)) -#define FUNCTOR_timeout_error2 ((functor_t)(227*4+2)) -#define FUNCTOR_trace1 ((functor_t)(228*4+2)) -#define FUNCTOR_traceinterc3 ((functor_t)(229*4+2)) -#define FUNCTOR_tracing1 ((functor_t)(230*4+2)) -#define FUNCTOR_true0 ((functor_t)(231*4+2)) -#define FUNCTOR_truncate1 ((functor_t)(232*4+2)) -#define FUNCTOR_tty1 ((functor_t)(233*4+2)) -#define FUNCTOR_type1 ((functor_t)(234*4+2)) -#define FUNCTOR_type_error2 ((functor_t)(235*4+2)) -#define FUNCTOR_undefinterc4 ((functor_t)(236*4+2)) -#define FUNCTOR_unify_determined2 ((functor_t)(237*4+2)) -#define FUNCTOR_uninstantiation_error1 ((functor_t)(238*4+2)) -#define FUNCTOR_var1 ((functor_t)(239*4+2)) -#define FUNCTOR_wakeup3 ((functor_t)(240*4+2)) -#define FUNCTOR_warning3 ((functor_t)(241*4+2)) -#define FUNCTOR_xor2 ((functor_t)(242*4+2)) -#define FUNCTOR_xpceref1 ((functor_t)(243*4+2)) -#define FUNCTOR_xpceref2 ((functor_t)(244*4+2)) - - -#define N_SWI_ATOMS 726 -#define N_SWI_FUNCTORS 245 -#define N_SWI_HASH_BITS 11 -#define N_SWI_HASH 2048 diff --git a/packages/python/swig/MANIFEST.in b/packages/python/swig/MANIFEST.in index c53dc317e..ce535d5dd 100644 --- a/packages/python/swig/MANIFEST.in +++ b/packages/python/swig/MANIFEST.in @@ -1,8 +1,24 @@ -recursive-include yap4py *.dylib -recursive-include yap4py *.dll -recursive-include yap4py *.so -recursive-include yap4py/prolog *.yap -recursive-include yap4py *.yss -recursive-include yap4py/prolog *.pl -recursive-include yap4py/prolog *.r -recursive-include yap4py *.md +include COPYING.md +include CONTRIBUTING.md +include README.md + +# Documentation +graft docs +exclude docs/\#* + +# Examples +graft examples + +# docs subdirs we want to skip +prune docs/build +prune docs/gh-pages +prune docs/dist + +# Patterns to exclude from any directory +global-exclude *~ +global-exclude *.pyc +global-exclude *.pyo +global-exclude .git +global-exclude .ipynb_checkpoints + +prune data_kernelspec diff --git a/packages/python/swig/YAP4PY.md b/packages/python/swig/YAP4PY.md new file mode 100644 index 000000000..d609f6392 --- /dev/null +++ b/packages/python/swig/YAP4PY.md @@ -0,0 +1,93 @@ + +
+![The YAP Logo](docs/icons/yap_128x128x32.png) +
+ +NOTE: this version of YAP is still experimental, documentation may be out of date. + +## Introduction + +This document provides User information on version 6.3.4 of +YAP (Yet Another Prolog). The YAP Prolog System is a +high-performance Prolog compiler developed at Universidade do +Porto. YAP supports stream Input/Output, sockets, modules, + exceptions, Prolog debugger, C-interface, dynamic code, internal + database, DCGs, saved states, co-routining, arrays, threads. + +We explicitly allow both commercial and non-commercial use of YAP. + + +YAP is based on the David H. D. Warren's WAM (Warren Abstract Machine), +with several optimizations for better performance. YAP follows the +Edinburgh tradition, and was originally designed to be largely +compatible with DEC-10 Prolog, Quintus Prolog, and especially with +C-Prolog. More recently, we have worked on being compatible with SICStus Prolog and with SWI-Prolog. + +YAP implements most of the ISO-Prolog standard. We are striving at +full compatibility, and the manual describes what is still +missing. +The document is intended neither as an introduction to Prolog nor to the +implementation aspects of the compiler. A good introduction to +programming in Prolog is the book @cite TheArtOfProlog , by +L. Sterling and E. Shapiro, published by "The MIT Press, Cambridge +MA". Other references should include the classical @cite ProgrammingInProlog , by W.F. Clocksin and C.S. Mellish, published by +Springer-Verlag. + +YAP 6.3.4 has been built with the gcc and clang compilers on Linux and OSX machines. We expect to recover support for WIN32 machines and +Android next. + +We are happy to include in YAP several excellent packages developed +under separate licenses. Our thanks to the authors for their kind +authorization to include these packages. + +The overall copyright and permission notice for YAP4.3 can be found in +the Artistic file in this directory. YAP follows the Perl Artistic +license, and it is thus non-copylefted freeware. Some components of YAP have been obtained from SWI Prolog and ciao, and have +different licenses. + +If you have a question about this software, desire to add code, found a +bug, want to request a feature, or wonder how to get further assistance, +please send e-mail to . To +subscribe to the mailing list, visit the page +. + +On-line documentation is available for [YAP](http://www.dcc.fp.pt/~vsc/yap/) + + + +The packages are, in alphabetical order: + ++ The CHR package developed by Tom Schrijvers, +Christian Holzbaur, and Jan Wielemaker. + ++ The CLP(BN) package and Horus toolkit developed by Tiago Gomes, and Vítor Santos Costa. + ++ The CLP(R) package developed by Leslie De Koninck, Bart Demoen, Tom +Schrijvers, and Jan Wielemaker, based on the CLP(Q,R) implementation +by Christian Holzbaur. + ++ The CPLint package developed by Fabrizio Riguzzi's research +laboratory at the [University of Ferrara](http://www.ing.unife.it/Docenti/FabrizioRiguzzi/) + ++ The CUDA interface package developed by Carlos Martínez, Jorge +Buenabad, Inês Dutra and Vítor Santos Costa. + ++ The [GECODE](http://www.gecode.org) interface package developed by Denys Duchier and Vítor Santos Costa. + ++ The [JPL](http://www.swi-prolog.org/packages/jpl/) (Java-Prolog Library) package developed by . + + The minisat SAT solver interface developed by Michael Codish, + Vitaly Lagoon, and Peter J. Stuckey. + ++ The MYDDAS relational data-base interface developed at the + Universidade do Porto by Tiago Soares, Michel Ferreira, and Ricardo Rocha. + ++ The [PRISM](http://rjida.meijo-u.ac.jp/prism/) logic-based +programming system for statistical modeling developed at the Sato +Research Laboratory, TITECH, Japan. + ++ The ProbLog 1 system developed by the [ProbLog](https://dtai.cs.kuleuven.be/problog) team in the +DTAI group of KULeuven. + ++ The [R](http://stoics.org.uk/~nicos/sware/packs/real/) interface package developed by Nicos Angelopoulos, +Vítor Santos Costa, João Azevedo, Jan Wielemaker, and Rui Camacho. diff --git a/packages/python/swig/setup.py.in b/packages/python/swig/setup.py.in new file mode 100644 index 000000000..c027ae89a --- /dev/null +++ b/packages/python/swig/setup.py.in @@ -0,0 +1,162 @@ +#!/usr/bin/env python +# coding: utf-8 + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. +from __future__ import print_function + +from setuptools import setup +from setuptools.extension import Extension +from codecs import open +from os import path, makedirs, walk +from shutil import copytree, rmtree, copy2, move +from glob import glob +from pathlib import Path +import platform +import os.path + +# the name of the package +name = 'yap_kernel' + +#----------------------------------------------------------------------------- +# Minimal Python version sanity check +#----------------------------------------------------------------------------- + +import sys + +v = sys.version_info +if v[:2] < (2,7) or (v[0] >= 3 and v[:2] < (3,3)): + error = "ERROR: %s requires Python version 2.7 or 3.3 or above." % name + print(error, file=sys.stderr) + sys.exit(1) + +PY3 = (sys.version_info[0] >= 3) + +#----------------------------------------------------------------------------- +# get on with it +#----------------------------------------------------------------------------- + +from glob import glob +import os +import shutil + +from distutils.core import setup + +pjoin = os.path.join +here = os.path.abspath(os.path.dirname(__file__)) +pkg_root = pjoin(here, name) + +my_extra_link_args = [] +if platform.system() == 'Darwin': + my_extra_link_args = ['-Wl,-rpath','-Wl,${_ABS_PYTHON_MODULE_PATH}'] + so = 'dylib' +#or dll in glob('yap/dlls/*'): +# move( dll ,'lib' ) + + +cplus=['${RELATIVE_SOURCE}CXX/yapi.cpp'] + +py2yap=['${RELATIVE_SOURCE}packages/python/python.c', + '${RELATIVE_SOURCE}packages/python/pl2py.c', + '${RELATIVE_SOURCE}packages/python/pybips.c', + '${RELATIVE_SOURCE}packages/python/py2pl.c', + '${RELATIVE_SOURCE}packages/python/pl2pl.c', + '${RELATIVE_SOURCE}packages/python/pypreds.c' +] + +native_sources = ['yapPYTHON_wrap.cxx']+py2yap+cplus +here = path.abspath(path.dirname(__file__)) + +# Get the long description from the README file + +extensions=[Extension('_yap', native_sources, + define_macros = [('MAJOR_VERSION', '1'), + ('MINOR_VERSION', '0'), + ('_YAP_NOT_INSTALLED_', '1'), + ('YAP_PYTHON', '1')], + runtime_library_dirs=['yap4py','${libdir}','${bindir}'], + swig_opts=['-modern', '-c++', '-py3','-I${RELATIVE_SOURCE}/CXX'], + library_dirs=['../../..','../../../CXX','../../packages/python',"${dlls}","${bindir}", '.'], + extra_link_args=my_extra_link_args, + extra_compile_args=['-g3','-O0'], + libraries=['Yap','${GMP_LIBRARIES}'], + include_dirs=['../../..', + '${GMP_INCLUDE_DIRS}', + '${RELATIVE_SOURCE}H', + '${RELATIVE_SOURCE}H/generated', + '${RELATIVE_SOURCE}OPTYap', + '${RELATIVE_SOURCE}os', + '${RELATIVE_SOURCE}include', + '${RELATIVE_SOURCE}CXX', '.'] +)] + +packages = ['yap4py'] + +pls = [] +for (r,d,fs) in walk('dylib'): + for f in fs: + pls += [os.path.join(r, f)] +for (r,d,fs) in walk('yss'): + for f in fs: + pls += [os.path.join(r, f)] +for (r,d,fs) in walk('pl'): + for f in fs: + pls += [os.path.join(r, f)] +for (r,d,fs) in walk('yap'): + for f in fs: + pls += [os.path.join(r, f)] + +for d, _, _ in os.walk(pjoin(here, name)): + if os.path.exists(pjoin(d, '__init__.py')): + packages.append(d[len(here)+1:].replace(os.path.sep, '.')) + +package_data = { + 'yap4pyl': pls, +} + +version_ns = {'__version__'='6.3','minor-version'='6','minor-version'='3','patch'='5'} + + +setup_args = dict( + name = name, + version = version_ns['__version__'], + scripts = glob(pjoin('scripts', '*')), + packages = packages, + py_modules = ['yap'], + package_data = package_data, + description = "YAP in Python", + author = 'YAP Development Team', + author_email = 'ipython-dev@scipy.org', + url = 'http://ipython.org', + license = 'BSD', + extensions = ['extensions'], + platforms = "Linux, Mac OS X, Windows", + keywords = ['Interactive', 'Interpreter', 'Shell', 'Web'], + classifiers = [ + 'Intended Audience :: Developers', + 'Intended Audience :: System Administrators', + 'Intended Audience :: Science/Research', + 'License :: OSI Approved :: BSD License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + ], +) + +if 'develop' in sys.argv or any(a.startswith('bdist') for a in sys.argv): + import setuptools + +setuptools_args = {} +install_requires = setuptools_args['install_requires'] = [ +] + +extras_require = setuptools_args['extras_require'] = { + 'test:python_version=="2.7"': ['mock'], + 'test': ['nose_warnings_filters', 'nose-timer'], +} + +if 'setuptools' in sys.modules: + setup_args.update(setuptools_args) + +if __name__ == '__main__': + setup(**setup_args) diff --git a/packages/python/yap_kernel/CONTRIBUTING.md b/packages/python/yap_kernel/CONTRIBUTING.md new file mode 100644 index 000000000..6b73dbf31 --- /dev/null +++ b/packages/python/yap_kernel/CONTRIBUTING.md @@ -0,0 +1,3 @@ +# Contributing + +We follow the [IPython Contributing Guide](https://github.com/ipython/ipython/blob/master/CONTRIBUTING.md). diff --git a/packages/python/yap_kernel/COPYING.md b/packages/python/yap_kernel/COPYING.md new file mode 100644 index 000000000..93f45a894 --- /dev/null +++ b/packages/python/yap_kernel/COPYING.md @@ -0,0 +1,59 @@ +# Licensing terms + +This project is licensed under the terms of the Modified BSD License +(also known as New or Revised or 3-Clause BSD), as follows: + +- Copyright (c) 2015, IPython Development Team + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of the IPython Development Team nor the names of its +contributors may be used to endorse or promote products derived from this +software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +## About the IPython Development Team + +The IPython Development Team is the set of all contributors to the IPython project. +This includes all of the IPython subprojects. + +The core team that coordinates development on GitHub can be found here: +https://github.com/ipython/. + +## Our Copyright Policy + +IPython uses a shared copyright model. Each contributor maintains copyright +over their contributions to IPython. But, it is important to note that these +contributions are typically only changes to the repositories. Thus, the IPython +source code, in its entirety is not the copyright of any single person or +institution. Instead, it is the collective copyright of the entire IPython +Development Team. If individual contributors want to maintain a record of what +changes/contributions they have specific copyright on, they should indicate +their copyright in the commit message of the change, when they commit the +change to one of the IPython repositories. + +With this in mind, the following banner should be used in any source code file +to indicate the copyright and license terms: + + # Copyright (c) IPython Development Team. + # Distributed under the terms of the Modified BSD License. diff --git a/packages/python/yap_kernel/MANIFEST.in b/packages/python/yap_kernel/MANIFEST.in index 8d3871272..ce535d5dd 100644 --- a/packages/python/yap_kernel/MANIFEST.in +++ b/packages/python/yap_kernel/MANIFEST.in @@ -1 +1,24 @@ -recursive-include yap_kernel/resources *.* +include COPYING.md +include CONTRIBUTING.md +include README.md + +# Documentation +graft docs +exclude docs/\#* + +# Examples +graft examples + +# docs subdirs we want to skip +prune docs/build +prune docs/gh-pages +prune docs/dist + +# Patterns to exclude from any directory +global-exclude *~ +global-exclude *.pyc +global-exclude *.pyo +global-exclude .git +global-exclude .ipynb_checkpoints + +prune data_kernelspec diff --git a/packages/python/yap_kernel/README.md b/packages/python/yap_kernel/README.md new file mode 100644 index 000000000..9744da286 --- /dev/null +++ b/packages/python/yap_kernel/README.md @@ -0,0 +1,39 @@ +# IPython Kernel for Jupyter + +This package provides the IPython kernel for Jupyter. + +## Installation from source + +1. `git clone` +2. `cd ipykernel` +3. `pip install -e .` + +After that, all normal `ipython` commands will use this newly-installed version of the kernel. + +## Running tests + +Ensure you have `nosetests` and the `nose-warnings-filters` plugin installed with + +```bash +pip install nose nose-warnings-filters +``` + +and then from the root directory + +```bash +nosetests ipykernel +``` + +## Running tests with coverage + +Follow the instructions from `Running tests`. Ensure you have the `coverage` module installed with + +```bash +pip install coverage +``` + +and then from the root directory + +```bash +nosetests --with-coverage --cover-package ipykernel ipykernel +``` diff --git a/packages/python/yap_kernel/appveyor.yml b/packages/python/yap_kernel/appveyor.yml new file mode 100644 index 000000000..939ea8f80 --- /dev/null +++ b/packages/python/yap_kernel/appveyor.yml @@ -0,0 +1,35 @@ +build: false +shallow_clone: false +skip_branch_with_pr: true +clone_depth: 1 + +environment: + + matrix: + - python: "C:/Python27-x64" + - python: "C:/Python27" + - python: "C:/Python36-x64" + - python: "C:/Python36" + +cache: + - C:\Users\appveyor\AppData\Local\pip\Cache + +init: + - cmd: set PATH=%python%;%python%\scripts;%PATH% +install: + - cmd: | + pip install --upgrade pip wheel + pip --version + - cmd: | + pip install --pre -e . coverage nose_warnings_filters + pip install ipykernel[test] nose-timer + - cmd: | + pip install matplotlib numpy + pip freeze + - cmd: python -c "import ipykernel.kernelspec; ipykernel.kernelspec.install(user=True)" +test_script: + - cmd: nosetests --with-coverage --with-timer --cover-package=ipykernel ipykernel + +on_success: + - cmd: pip install codecov + - cmd: codecov diff --git a/packages/python/yap_kernel/data_kernelspec/kernel.json b/packages/python/yap_kernel/data_kernelspec/kernel.json new file mode 100644 index 000000000..4da9e0b0f --- /dev/null +++ b/packages/python/yap_kernel/data_kernelspec/kernel.json @@ -0,0 +1,11 @@ +{ + "argv": [ + "python", + "-m", + "ipykernel_launcher", + "-f", + "{connection_file}" + ], + "display_name": "Python 3", + "language": "python" +} \ No newline at end of file diff --git a/packages/python/yap_kernel/data_kernelspec/logo-32x32.png b/packages/python/yap_kernel/data_kernelspec/logo-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..be81330765764699553aa4fbaf0e9fc27c20c6d2 GIT binary patch literal 1084 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ℜ+(yTv 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 literal 0 HcmV?d00001 diff --git a/packages/python/yap_kernel/dist/yap_kernel-4.7.0.dev0.tar.gz b/packages/python/yap_kernel/dist/yap_kernel-4.7.0.dev0.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..204422c71da91c2014540a5acab92531ea4c1899 GIT binary patch literal 93844 zcmV($K;yq3iwFqS>k?T4|72-%bT4^fa9?X>a&Bd8Ei^7SE-)@+Wp*$wbYXG;?7i!H z+sKtDn!khZ%RAK2@sRY8(A~}`QBPz^wzXT9)RNrp-j0U?NsvMd1lRy5iIepFKM!(V z>^#X?w<;7skdiFh>7Iy5TO?4|Rjby$R!zNe=h9CiKWwkNFWhCf?_Vu1{qD1Vmf`ba zV}t)b=fBIV>-?8Lzgt;bSzh0GzVTue-mk2$uRZ^r+W6g9_)OBwOO*QERoYuv4vGy1 zw@eZzqW}IzKGVwe-#I!wJK2Bv?ri_?wL9v6hV_5GzCL&TS6-~s`me69y;xhu^?weC z@H@5qxc*=K^F-~$QI-VVNftyy$9eCoK^%thwaPAh^bnq9qfc z`+xcQ>f`?ZI{W`1==o8K`xu_oWeUEARKV}6N!0ffq6<_~#RK~B zHtq+50N%em-IdmSr{RTZtS+PYI#OP$4*hEtC+ft%3a};ky4Kzay-Dh0t!4|$Fx^Qv z9j6WSW;{)T;YFqzy{1}SUfF0VO^bH@D?g0KBR|U2neUApXFCiPl}=USr+#wf_g%+1 z@%uq4R12zk5zGMk3nG=qlceXWJZu2QnD}Z8 zGY_&1<^T}70y@|S7HULx;H>`T@csDY===TMyd;cOrNsP=w1KG{1xRYxak|LyU?J~Rfc zpKKqV?eCq!4m#X9c()6Ly``W5b$E294))*fpFy#+qZV~4)ttPVI(ntv?w#zsfuGwi z_Yd~Zex!lC+CMwQzFt9R+v<4x6f|368xm$CrCB$nBR0dk*ykv)tW3+1ok8>Ez#b0CF(C zgO)ly-rL#7FMB`i!DO~ier!pbr+eSMgF^7q+1-A-{Tk-hxCJDzCOhv=_TJ)<0S>3{ zUY?%qpS?TVQ?HMXb`j9d>E6lr`#XE5e^LiWrv$Bcr+Y26yM4Az9YYfUDtHawU%ort zC!p;gp6#8SygNSIKRRr}`o0IKoH#q%P<5A}dvr)s1ppnL{D_Sr3<&rw_5RHsJU>Br z5~Q{f1g8M8oinSL1N{L2Û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 + +- Fix eventloop-integration bug preventing Qt windows/widgets from displaying with ipykernel 4.6.0 and IPython ≥ 5.2. +- Avoid deprecation warnings about naive datetimes when working with jupyter_client ≥ 5.0. + + +4.6.0 +***** + +`4.6.0 on GitHub `__ + +- Add to API `DisplayPublisher.publish` two new fully backward-compatible + keyword-args: + - `update: bool` + - `transient: dict` +- Support new `transient` key in `display_data` messages spec for `publish`. + For a display data message, `transient` contains data that shouldn't be + persisted to files or documents. Add a `display_id` to this `transient` + dict by `display(obj, display_id=...)` +- Add `ipykernel_launcher` module which removes the current working directory + from `sys.path` before launching the kernel. This helps to reduce the cases + where the kernel won't start because there's a `random.py` (or similar) + module in the current working directory. +- Add busy/idle messages on IOPub during processing of aborted requests +- Add active event loop setting to GUI, which enables the correct response + to IPython's `is_event_loop_running_xxx` +- Include IPython kernelspec in wheels to reduce reliance on "native kernel + spec" in jupyter_client +- Modify `OutStream` to inherit from `TextIOBase` instead of object to improve + API support and error reporting +- Fix IPython kernel death messages at start, such as "Kernel Restarting..." + and "Kernel appears to have died", when parent-poller handles PID 1 +- Various bugfixes + + +4.5 +--- + +4.5.2 +***** + +`4.5.2 on GitHub `__ + +- Fix bug when instantiating Comms outside of the IPython kernel (introduced in 4.5.1). + + +4.5.1 +***** + +`4.5.1 on GitHub `__ + +- Add missing ``stream`` parameter to overridden :func:`getpass` +- Remove locks from iopub thread, which could cause deadlocks during debugging +- Fix regression where KeyboardInterrupt was treated as an aborted request, rather than an error +- Allow instantiating Comms outside of the IPython kernel + +4.5.0 +***** + +`4.5 on GitHub `__ + +- Use figure.dpi instead of savefig.dpi to set DPI for inline figures +- Support ipympl matplotlib backend (requires IPython update as well to fully work) +- Various bugfixes, including fixes for output coming from threads, + and :func:`input` when called with non-string prompts, which stdlib allows. + + +4.4 +--- + +4.4.1 +***** + +`4.4.1 on GitHub `__ + +- Fix circular import of matplotlib on Python 2 caused by the inline backend changes in 4.4.0. + + +4.4.0 +***** + +`4.4.0 on GitHub `__ + +- Use `MPLBACKEND`_ environment variable to tell matplotlib >= 1.5 use use the inline backend by default. + This is only done if MPLBACKEND is not already set and no backend has been explicitly loaded, + so setting ``MPLBACKEND=Qt4Agg`` or calling ``%matplotlib notebook`` or ``matplotlib.use('Agg')`` + will take precedence. +- Fixes for logging problems caused by 4.3, + where logging could go to the terminal instead of the notebook. +- Add ``--sys-prefix`` and ``--profile`` arguments to :command:`ipython kernel install` +- Allow Comm (Widget) messages to be sent from background threads. +- Select inline matplotlib backend by default if ``%matplotlib`` magic or + ``matplotlib.use()`` are not called explicitly (for matplotlib >= 1.5). +- Fix some longstanding minor deviations from the message protocol + (missing status: ok in a few replies, connect_reply format). +- Remove calls to NoOpContext from IPython, deprecated in 5.0. + +.. _MPLBACKEND: http://matplotlib.org/devel/coding_guide.html?highlight=mplbackend#developing-a-new-backend + + +4.3 +--- + +4.3.2 +***** + +- Use a nonempty dummy session key for inprocess kernels to avoid security + warnings. + +4.3.1 +***** + +- Fix Windows Python 3.5 incompatibility caused by faulthandler patch in 4.3 + +4.3.0 +***** + +`4.3.0 on GitHub `__ + +- Publish all IO in a thread, via :class:`IOPubThread`. + This solves the problem of requiring :meth:`sys.stdout.flush` to be called in the notebook to produce output promptly during long-running cells. +- Remove refrences to outdated IPython guiref in kernel banner. +- Patch faulthandler to use ``sys.__stderr__`` instead of forwarded ``sys.stderr``, + which has no fileno when forwarded. +- Deprecate some vestiges of the Big Split: + - :func:`ipykernel.find_connection_file` is deprecated. Use :func:`jupyter_client.find_connection_file` instead. + - Various pieces of code specific to IPython parallel are deprecated in ipykernel + and moved to ipyparallel. + + +4.2 +--- + +4.2.2 +***** + +`4.2.2 on GitHub `__ + +- Don't show interactive debugging info when kernel crashes +- Fix handling of numerical types in json_clean +- Testing fixes for output capturing + +4.2.1 +***** + +`4.2.1 on GitHub `__ + +- Fix default display name back to "Python X" instead of "pythonX" + +4.2.0 +***** + +`4.2 on GitHub `_ + +- Support sending a full message in initial opening of comms (metadata, buffers were not previously allowed) +- When using ``ipython kernel install --name`` to install the IPython kernelspec, default display-name to the same value as ``--name``. + +4.1 +--- + +4.1.1 +***** + +`4.1.1 on GitHub `_ + +- Fix missing ``ipykernel.__version__`` on Python 2. +- Fix missing ``target_name`` when opening comms from the frontend. + +4.1.0 +***** + +`4.1 on GitHub `_ + + +- add ``ipython kernel install`` entrypoint for installing the IPython + kernelspec +- provisional implementation of ``comm_info`` request/reply for msgspec + v5.1 + +4.0 +--- + +`4.0 on GitHub `_ + +4.0 is the first release of ipykernel as a standalone package. diff --git a/packages/python/yap_kernel/docs/conf.py b/packages/python/yap_kernel/docs/conf.py new file mode 100644 index 000000000..70259a23b --- /dev/null +++ b/packages/python/yap_kernel/docs/conf.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# IPython Kernel documentation build configuration file, created by +# sphinx-quickstart on Mon Oct 5 11:32:44 2015. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +import shlex + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'IPython Kernel' +copyright = '2015, IPython Development Team' +author = 'IPython Development Team' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# + +version_ns = {} +here = os.path.dirname(__file__) +version_py = os.path.join(here, os.pardir, 'yap_kernel', '_version.py') +with open(version_py) as f: + exec(compile(f.read(), version_py, 'exec'), version_ns) + +# The short X.Y version. +version = '%i.%i' % version_ns['version_info'][:2] +# The full version, including alpha/beta/rc tags. +release = version_ns['__version__'] + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +default_role = 'literal' + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# html_theme = 'alabaster' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'yap_kerneldoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'yap_kernel.tex', 'IPython Kernel Documentation', + 'IPython Development Team', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'yap_kernel', 'IPython Kernel Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'yap_kernel', 'IPython Kernel Documentation', + author, 'yap_kernel', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = { + 'https://docs.python.org/': None, + 'ipython': ('https://ipython.readthedocs.io/en/latest', None), + 'jupyter': ('https://jupyter.readthedocs.io/en/latest', None), +} diff --git a/packages/python/yap_kernel/docs/index.rst b/packages/python/yap_kernel/docs/index.rst new file mode 100644 index 000000000..770904d7e --- /dev/null +++ b/packages/python/yap_kernel/docs/index.rst @@ -0,0 +1,23 @@ +.. _index: + +IPython Kernel Docs +=================== + +This contains minimal version-sensitive documentation for the IPython kernel package. +Most IPython kernel documentation is in the `IPython documentation `_. + +Contents: + +.. toctree:: + :maxdepth: 2 + + changelog.rst + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/packages/python/yap_kernel/docs/make.bat b/packages/python/yap_kernel/docs/make.bat new file mode 100644 index 000000000..0951cdd22 --- /dev/null +++ b/packages/python/yap_kernel/docs/make.bat @@ -0,0 +1,263 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 2> nul +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\IPythonKernel.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\IPythonKernel.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/packages/python/yap_kernel/examples/embedding/inprocess_qtconsole.py b/packages/python/yap_kernel/examples/embedding/inprocess_qtconsole.py new file mode 100644 index 000000000..3fc662944 --- /dev/null +++ b/packages/python/yap_kernel/examples/embedding/inprocess_qtconsole.py @@ -0,0 +1,46 @@ +from __future__ import print_function +import os + +from IPython.qt.console.rich_ipython_widget import RichIPythonWidget +from IPython.qt.inprocess import QtInProcessKernelManager +from IPython.lib import guisupport + + +def print_process_id(): + print('Process ID is:', os.getpid()) + + +def main(): + # Print the ID of the main process + print_process_id() + + app = guisupport.get_app_qt4() + + # Create an in-process kernel + # >>> print_process_id() + # will print the same process ID as the main process + kernel_manager = QtInProcessKernelManager() + kernel_manager.start_kernel() + kernel = kernel_manager.kernel + kernel.gui = 'qt4' + kernel.shell.push({'foo': 43, 'print_process_id': print_process_id}) + + kernel_client = kernel_manager.client() + kernel_client.start_channels() + + def stop(): + kernel_client.stop_channels() + kernel_manager.shutdown_kernel() + app.exit() + + control = RichIPythonWidget() + control.kernel_manager = kernel_manager + control.kernel_client = kernel_client + control.exit_requested.connect(stop) + control.show() + + guisupport.start_event_loop_qt4(app) + + +if __name__ == '__main__': + main() diff --git a/packages/python/yap_kernel/examples/embedding/inprocess_terminal.py b/packages/python/yap_kernel/examples/embedding/inprocess_terminal.py new file mode 100644 index 000000000..a295c0a2f --- /dev/null +++ b/packages/python/yap_kernel/examples/embedding/inprocess_terminal.py @@ -0,0 +1,31 @@ +from __future__ import print_function +import os + +from IPython.kernel.inprocess import InProcessKernelManager +from IPython.terminal.console.interactiveshell import ZMQTerminalInteractiveShell + + +def print_process_id(): + print('Process ID is:', os.getpid()) + + +def main(): + print_process_id() + + # Create an in-process kernel + # >>> print_process_id() + # will print the same process ID as the main process + kernel_manager = InProcessKernelManager() + kernel_manager.start_kernel() + kernel = kernel_manager.kernel + kernel.gui = 'qt4' + kernel.shell.push({'foo': 43, 'print_process_id': print_process_id}) + client = kernel_manager.client() + client.start_channels() + + shell = ZMQTerminalInteractiveShell(manager=kernel_manager, client=client) + shell.mainloop() + + +if __name__ == '__main__': + main() diff --git a/packages/python/yap_kernel/examples/embedding/internal_ipkernel.py b/packages/python/yap_kernel/examples/embedding/internal_ipkernel.py new file mode 100644 index 000000000..0c65cbff8 --- /dev/null +++ b/packages/python/yap_kernel/examples/embedding/internal_ipkernel.py @@ -0,0 +1,55 @@ +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import sys + +from IPython.lib.kernel import connect_qtconsole +from IPython.kernel.zmq.kernelapp import YAP_KernelApp + +#----------------------------------------------------------------------------- +# Functions and classes +#----------------------------------------------------------------------------- +def mpl_kernel(gui): + """Launch and return an IPython kernel with matplotlib support for the desired gui + """ + kernel = YAP_KernelApp.instance() + kernel.initialize(['python', '--matplotlib=%s' % gui, + #'--log-level=10' + ]) + return kernel + + +class InternalYAPKernel(object): + + def init_yapkernel(self, backend): + # Start IPython kernel with GUI event loop and mpl support + self.yapkernel = mpl_kernel(backend) + # To create and track active qt consoles + self.consoles = [] + + # This application will also act on the shell user namespace + self.namespace = self.yapkernel.shell.user_ns + + # Example: a variable that will be seen by the user in the shell, and + # that the GUI modifies (the 'Counter++' button increments it): + self.namespace['app_counter'] = 0 + #self.namespace['yapkernel'] = self.yapkernel # dbg + + def print_namespace(self, evt=None): + print("\n***Variables in User namespace***") + for k, v in self.namespace.items(): + if not k.startswith('_'): + print('%s -> %r' % (k, v)) + sys.stdout.flush() + + def new_qt_console(self, evt=None): + """start a new qtconsole connected to our kernel""" + return connect_qtconsole(self.yapkernel.abs_connection_file, profile=self.yapkernel.profile) + + def count(self, evt=None): + self.namespace['app_counter'] += 1 + + def cleanup_consoles(self, evt=None): + for c in self.consoles: + c.kill() diff --git a/packages/python/yap_kernel/examples/embedding/ipkernel_qtapp.py b/packages/python/yap_kernel/examples/embedding/ipkernel_qtapp.py new file mode 100644 index 000000000..7a3f2f8cb --- /dev/null +++ b/packages/python/yap_kernel/examples/embedding/ipkernel_qtapp.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +"""Example integrating an IPython kernel into a GUI App. + +This trivial GUI application internally starts an IPython kernel, to which Qt +consoles can be connected either by the user at the command line or started +from the GUI itself, via a button. The GUI can also manipulate one variable in +the kernel's namespace, and print the namespace to the console. + +Play with it by running the script and then opening one or more consoles, and +pushing the 'Counter++' and 'Namespace' buttons. + +Upon exit, it should automatically close all consoles opened from the GUI. + +Consoles attached separately from a terminal will not be terminated, though +they will notice that their kernel died. +""" +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +from PyQt4 import Qt + +from internal_yapkernel import InternalYAPKernel + +#----------------------------------------------------------------------------- +# Functions and classes +#----------------------------------------------------------------------------- +class SimpleWindow(Qt.QWidget, InternalYAPKernel): + + def __init__(self, app): + Qt.QWidget.__init__(self) + self.app = app + self.add_widgets() + self.init_yapkernel('qt') + + def add_widgets(self): + self.setGeometry(300, 300, 400, 70) + self.setWindowTitle('IPython in your app') + + # Add simple buttons: + console = Qt.QPushButton('Qt Console', self) + console.setGeometry(10, 10, 100, 35) + self.connect(console, Qt.SIGNAL('clicked()'), self.new_qt_console) + + namespace = Qt.QPushButton('Namespace', self) + namespace.setGeometry(120, 10, 100, 35) + self.connect(namespace, Qt.SIGNAL('clicked()'), self.print_namespace) + + count = Qt.QPushButton('Count++', self) + count.setGeometry(230, 10, 80, 35) + self.connect(count, Qt.SIGNAL('clicked()'), self.count) + + # Quit and cleanup + quit = Qt.QPushButton('Quit', self) + quit.setGeometry(320, 10, 60, 35) + self.connect(quit, Qt.SIGNAL('clicked()'), Qt.qApp, Qt.SLOT('quit()')) + + self.app.connect(self.app, Qt.SIGNAL("lastWindowClosed()"), + self.app, Qt.SLOT("quit()")) + + self.app.aboutToQuit.connect(self.cleanup_consoles) + +#----------------------------------------------------------------------------- +# Main script +#----------------------------------------------------------------------------- + +if __name__ == "__main__": + app = Qt.QApplication([]) + # Create our window + win = SimpleWindow(app) + win.show() + + # Very important, IPython-specific step: this gets GUI event loop + # integration going, and it replaces calling app.exec_() + win.yapkernel.start() diff --git a/packages/python/yap_kernel/examples/embedding/ipkernel_wxapp.py b/packages/python/yap_kernel/examples/embedding/ipkernel_wxapp.py new file mode 100644 index 000000000..2ac7c3a47 --- /dev/null +++ b/packages/python/yap_kernel/examples/embedding/ipkernel_wxapp.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python +"""Example integrating an IPython kernel into a GUI App. + +This trivial GUI application internally starts an IPython kernel, to which Qt +consoles can be connected either by the user at the command line or started +from the GUI itself, via a button. The GUI can also manipulate one variable in +the kernel's namespace, and print the namespace to the console. + +Play with it by running the script and then opening one or more consoles, and +pushing the 'Counter++' and 'Namespace' buttons. + +Upon exit, it should automatically close all consoles opened from the GUI. + +Consoles attached separately from a terminal will not be terminated, though +they will notice that their kernel died. + +Ref: Modified from wxPython source code wxPython/samples/simple/simple.py +""" +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- +import sys + +import wx + +from internal_yapkernel import InternalYAPKernel + +#----------------------------------------------------------------------------- +# Functions and classes +#----------------------------------------------------------------------------- + +class MyFrame(wx.Frame, InternalYAPKernel): + """ + This is MyFrame. It just shows a few controls on a wxPanel, + and has a simple menu. + """ + + def __init__(self, parent, title): + wx.Frame.__init__(self, parent, -1, title, + pos=(150, 150), size=(350, 285)) + + # Create the menubar + menuBar = wx.MenuBar() + + # and a menu + menu = wx.Menu() + + # add an item to the menu, using \tKeyName automatically + # creates an accelerator, the third param is some help text + # that will show up in the statusbar + menu.Append(wx.ID_EXIT, "E&xit\tAlt-X", "Exit this simple sample") + + # bind the menu event to an event handler + self.Bind(wx.EVT_MENU, self.OnTimeToClose, id=wx.ID_EXIT) + + # and put the menu on the menubar + menuBar.Append(menu, "&File") + self.SetMenuBar(menuBar) + + self.CreateStatusBar() + + # Now create the Panel to put the other controls on. + panel = wx.Panel(self) + + # and a few controls + text = wx.StaticText(panel, -1, "Hello World!") + text.SetFont(wx.Font(14, wx.SWISS, wx.NORMAL, wx.BOLD)) + text.SetSize(text.GetBestSize()) + qtconsole_btn = wx.Button(panel, -1, "Qt Console") + ns_btn = wx.Button(panel, -1, "Namespace") + count_btn = wx.Button(panel, -1, "Count++") + close_btn = wx.Button(panel, -1, "Quit") + + # bind the button events to handlers + self.Bind(wx.EVT_BUTTON, self.new_qt_console, qtconsole_btn) + self.Bind(wx.EVT_BUTTON, self.print_namespace, ns_btn) + self.Bind(wx.EVT_BUTTON, self.count, count_btn) + self.Bind(wx.EVT_BUTTON, self.OnTimeToClose, close_btn) + + # Use a sizer to layout the controls, stacked vertically and with + # a 10 pixel border around each + sizer = wx.BoxSizer(wx.VERTICAL) + for ctrl in [text, qtconsole_btn, ns_btn, count_btn, close_btn]: + sizer.Add(ctrl, 0, wx.ALL, 10) + panel.SetSizer(sizer) + panel.Layout() + + # Start the IPython kernel with gui support + self.init_yapkernel('wx') + + def OnTimeToClose(self, evt): + """Event handler for the button click.""" + print("See ya later!") + sys.stdout.flush() + self.cleanup_consoles(evt) + self.Close() + # Not sure why, but our IPython kernel seems to prevent normal WX + # shutdown, so an explicit exit() call is needed. + sys.exit() + + +class MyApp(wx.App): + def OnInit(self): + frame = MyFrame(None, "Simple wxPython App") + self.SetTopWindow(frame) + frame.Show(True) + self.yapkernel = frame.yapkernel + return True + +#----------------------------------------------------------------------------- +# Main script +#----------------------------------------------------------------------------- + +if __name__ == '__main__': + app = MyApp(redirect=False, clearSigInt=False) + + # Very important, IPython-specific step: this gets GUI event loop + # integration going, and it replaces calling app.MainLoop() + app.yapkernel.start() diff --git a/packages/python/yap_kernel/readthedocs.yml b/packages/python/yap_kernel/readthedocs.yml new file mode 100644 index 000000000..f8b3b417d --- /dev/null +++ b/packages/python/yap_kernel/readthedocs.yml @@ -0,0 +1,3 @@ +python: + version: 3.5 + pip_install: true diff --git a/packages/python/yap_kernel/setup.cfg b/packages/python/yap_kernel/setup.cfg new file mode 100644 index 000000000..60af4adf1 --- /dev/null +++ b/packages/python/yap_kernel/setup.cfg @@ -0,0 +1,13 @@ +[bdist_wheel] +universal=0 + +[nosetests] +warningfilters= default |.* |DeprecationWarning |ipykernel.* + default |.* |DeprecationWarning |IPython.* + ignore |.*assert.* |DeprecationWarning |.* + ignore |.*observe.* |DeprecationWarning |IPython.* + ignore |.*default.* |DeprecationWarning |IPython.* + ignore |.*default.* |DeprecationWarning |jupyter_client.* + ignore |.*Metada.* |DeprecationWarning |IPython.* + + diff --git a/packages/python/yap_kernel/setup.py b/packages/python/yap_kernel/setup.py index 3ae6ea1d3..7497062da 100644 --- a/packages/python/yap_kernel/setup.py +++ b/packages/python/yap_kernel/setup.py @@ -43,7 +43,7 @@ for d, _, _ in os.walk(pjoin(here, name)): packages.append(d[len(here)+1:].replace(os.path.sep, '.')) package_data = { - 'ipykernel': ['resources/*.*'], + 'yap_kernel': ['resources/*.*'], } version_ns = {} @@ -56,9 +56,10 @@ setup_args = dict( version = version_ns['__version__'], scripts = glob(pjoin('scripts', '*')), packages = packages, + py_modules = ['yapkernel_launcher'], package_data = package_data, - description = "IPython Kernel for Jupyter", - author = 'IPython Development Team', + description = "YAP Kernel for Jupyter", + author = 'YAP Development Team', author_email = 'ipython-dev@scipy.org', url = 'http://ipython.org', license = 'BSD', @@ -79,12 +80,13 @@ if 'develop' in sys.argv or any(a.startswith('bdist') for a in sys.argv): import setuptools setuptools_args = {} -# install_requires = setuptools_args['install_requires'] = [ -# 'ipython>=4.0.0', -# 'traitlets>=4.1.0', -# 'jupyter_client', -# 'tornado>=4.0', -# ] +install_requires = setuptools_args['install_requires'] = [ + 'ipython>=4.0.0', + 'traitlets>=4.1.0', + 'jupyter_client', + 'tornado>=4.0', + 'yap4py' +] if any(a.startswith(('bdist', 'build', 'install')) for a in sys.argv): from ipykernel.kernelspec import write_kernel_spec, make_ipkernel_cmd, KERNEL_NAME diff --git a/packages/python/yap_kernel/setup.py.in b/packages/python/yap_kernel/setup.py.in index 928c797be..4e8d5c9ad 100644 --- a/packages/python/yap_kernel/setup.py.in +++ b/packages/python/yap_kernel/setup.py.in @@ -34,7 +34,7 @@ import shutil from distutils.core import setup pjoin = os.path.join -here = os.path.abspath(os.path.dirname(__file__)) +here = os.path.relpath(os.path.dirname(__file__)) pkg_root = pjoin(here, name) packages = [] @@ -56,12 +56,11 @@ setup_args = dict( version = version_ns['__version__'], scripts = glob(pjoin('scripts', '*')), packages = packages, - package_dir = {'':'${CMAKE_CURRENT_SOURCE_DIR}'}, - py_modules = ['ipykernel_launcher'], + py_modules = ['yap_kernel_launcher'], package_data = package_data, - description = "IPython Kernel for Jupyter", - author = 'IPython Development Team', - author_email = 'ipython-dev@scipy.org', + description = "YAP Kernel for Jupyter", + author = 'IPython Development Team and Vitor Santos Costa', + author_email = 'vsc@dcc.fc.up.ot', url = 'http://ipython.org', license = 'BSD', platforms = "Linux, Mac OS X, Windows", @@ -86,12 +85,13 @@ install_requires = setuptools_args['install_requires'] = [ 'traitlets>=4.1.0', 'jupyter_client', 'tornado>=4.0', + 'yap4py' ] if any(a.startswith(('bdist', 'build', 'install')) for a in sys.argv): - from ipykernel.kernelspec import write_kernel_spec, make_ipkernel_cmd, KERNEL_NAME + from yap_kernel.kernelspec import write_kernel_spec, make_yap_kernel_cmd, KERNEL_NAME - argv = make_ipkernel_cmd(executable='python') + argv = make_yap_kernel_cmd(executable='python') dest = os.path.join(here, 'data_kernelspec') if os.path.exists(dest): shutil.rmtree(dest) @@ -101,6 +101,10 @@ if any(a.startswith(('bdist', 'build', 'install')) for a in sys.argv): (pjoin('share', 'jupyter', 'kernels', KERNEL_NAME), glob(pjoin(dest, '*'))), ] +setuptools_args['zip_safe']=False +setuptools_args['eager_resources'] = ['yap_kernel'] +setuptools_args['include_package_data']=True + extras_require = setuptools_args['extras_require'] = { 'test:python_version=="2.7"': ['mock'], 'test': ['nose_warnings_filters', 'nose-timer'], diff --git a/packages/python/yap_kernel/yap_kernel.egg-info/PKG-INFO b/packages/python/yap_kernel/yap_kernel.egg-info/PKG-INFO new file mode 100644 index 000000000..20b88b0d9 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel.egg-info/PKG-INFO @@ -0,0 +1,20 @@ +Metadata-Version: 1.1 +Name: yap-kernel +Version: 4.7.0.dev0 +Summary: YAP Kernel for Jupyter +Home-page: http://ipython.org +Author: YAP Development Team +Author-email: ipython-dev@scipy.org +License: BSD +Description: UNKNOWN +Keywords: Interactive,Interpreter,Shell,Web +Platform: Linux +Platform: Mac OS X +Platform: Windows +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: System Administrators +Classifier: Intended Audience :: Science/Research +Classifier: License :: OSI Approved :: BSD License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 diff --git a/packages/python/yap_kernel/yap_kernel.egg-info/SOURCES.txt b/packages/python/yap_kernel/yap_kernel.egg-info/SOURCES.txt new file mode 100644 index 000000000..c78e840ed --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel.egg-info/SOURCES.txt @@ -0,0 +1,81 @@ +CONTRIBUTING.md +COPYING.md +MANIFEST.in +README.md +setup.cfg +setup.py +/Users/vsc/github/yap-6.3/yap_kernel/data_kernelspec/kernel.json +/Users/vsc/github/yap-6.3/yap_kernel/data_kernelspec/logo-32x32.png +/Users/vsc/github/yap-6.3/yap_kernel/data_kernelspec/logo-64x64.png +docs/Makefile +docs/changelog.rst +docs/conf.py +docs/index.rst +docs/make.bat +examples/embedding/inprocess_qtconsole.py +examples/embedding/inprocess_terminal.py +examples/embedding/internal_ipkernel.py +examples/embedding/ipkernel_qtapp.py +examples/embedding/ipkernel_wxapp.py +yap_kernel/__init__.py +yap_kernel/__main__.py +yap_kernel/_version.py +yap_kernel/codeutil.py +yap_kernel/connect.py +yap_kernel/datapub.py +yap_kernel/displayhook.py +yap_kernel/embed.py +yap_kernel/eventloops.py +yap_kernel/heartbeat.py +yap_kernel/interactiveshell.py +yap_kernel/iostream.py +yap_kernel/jsonutil.py +yap_kernel/kernelapp.py +yap_kernel/kernelbase.py +yap_kernel/kernelspec.py +yap_kernel/log.py +yap_kernel/parentpoller.py +yap_kernel/pickleutil.py +yap_kernel/serialize.py +yap_kernel/yapkernel.py +yap_kernel/zmqshell.py +yap_kernel.egg-info/PKG-INFO +yap_kernel.egg-info/SOURCES.txt +yap_kernel.egg-info/dependency_links.txt +yap_kernel.egg-info/requires.txt +yap_kernel.egg-info/top_level.txt +yap_kernel/comm/__init__.py +yap_kernel/comm/comm.py +yap_kernel/comm/manager.py +yap_kernel/gui/__init__.py +yap_kernel/gui/gtk3embed.py +yap_kernel/gui/gtkembed.py +yap_kernel/inprocess/__init__.py +yap_kernel/inprocess/blocking.py +yap_kernel/inprocess/channels.py +yap_kernel/inprocess/client.py +yap_kernel/inprocess/constants.py +yap_kernel/inprocess/ipkernel.py +yap_kernel/inprocess/manager.py +yap_kernel/inprocess/socket.py +yap_kernel/inprocess/tests/__init__.py +yap_kernel/inprocess/tests/test_kernel.py +yap_kernel/inprocess/tests/test_kernelmanager.py +yap_kernel/pylab/__init__.py +yap_kernel/pylab/backend_inline.py +yap_kernel/pylab/config.py +yap_kernel/resources/logo-32x32.png +yap_kernel/resources/logo-64x64.png +yap_kernel/tests/__init__.py +yap_kernel/tests/test_connect.py +yap_kernel/tests/test_embed_kernel.py +yap_kernel/tests/test_io.py +yap_kernel/tests/test_jsonutil.py +yap_kernel/tests/test_kernel.py +yap_kernel/tests/test_kernelspec.py +yap_kernel/tests/test_message_spec.py +yap_kernel/tests/test_pickleutil.py +yap_kernel/tests/test_serialize.py +yap_kernel/tests/test_start_kernel.py +yap_kernel/tests/test_zmq_shell.py +yap_kernel/tests/utils.py \ No newline at end of file diff --git a/packages/python/yap_kernel/yap_kernel.egg-info/dependency_links.txt b/packages/python/yap_kernel/yap_kernel.egg-info/dependency_links.txt new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/packages/python/yap_kernel/yap_kernel.egg-info/requires.txt b/packages/python/yap_kernel/yap_kernel.egg-info/requires.txt new file mode 100644 index 000000000..fb4f4c2f2 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel.egg-info/requires.txt @@ -0,0 +1,12 @@ +ipython>=4.0.0 +traitlets>=4.1.0 +jupyter_client +tornado>=4.0 +yap4py + +[test] +nose_warnings_filters +nose-timer + +[test:python_version=="2.7"] +mock diff --git a/packages/python/yap_kernel/yap_kernel.egg-info/top_level.txt b/packages/python/yap_kernel/yap_kernel.egg-info/top_level.txt new file mode 100644 index 000000000..d69977fb4 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel.egg-info/top_level.txt @@ -0,0 +1,2 @@ +yap_kernel +yapkernel_launcher diff --git a/packages/python/yap_kernel/yap_kernel/#__main__.py# b/packages/python/yap_kernel/yap_kernel/#__main__.py# new file mode 100644 index 000000000..9e3c5ae71 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/#__main__.py# @@ -0,0 +1,5 @@ +if __name__ == '__main__': + from ipykernel import kernelapp as app + app.launch_new_instance() + + diff --git a/packages/python/yap_kernel/yap_kernel/#kernelapp.py# b/packages/python/yap_kernel/yap_kernel/#kernelapp.py# new file mode 100644 index 000000000..00eeffb38 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/#kernelapp.py# @@ -0,0 +1,492 @@ +"""An Application for launching a kernel""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from __future__ import print_function + +import atexit +import os +import sys +import signal +import traceback +import logging + +from tornado import ioloop +import zmq +from zmq.eventloop import ioloop as zmq_ioloop +from zmq.eventloop.zmqstream import ZMQStream + +from IPython.core.application import ( + BaseIPythonApplication, base_flags, base_aliases, catch_config_error +) +from IPython.core.profiledir import ProfileDir +from IPython.core.shellapp import ( + InteractiveShellApp, shell_flags, shell_aliases +) +from IPython.utils import io +from ipython_genutils.path import filefind, ensure_dir_exists +from traitlets import ( + Any, Instance, Dict, Unicode, Integer, Bool, DottedObjectName, Type, default +) +from ipython_genutils.importstring import import_item +from jupyter_core.paths import jupyter_runtime_dir +from jupyter_client import write_connection_file +from jupyter_client.connect import ConnectionFileMixin + +# local imports +from .iostream import IOPubThread +from .heartbeat import Heartbeat +from .yapkernel import YAPKernel +from .parentpoller import ParentPollerUnix, ParentPollerWindows +from jupyter_client.session import ( + Session, session_flags, session_aliases, +) +from .zmqshell import ZMQInteractiveShell + +#----------------------------------------------------------------------------- +# Flags and Aliases +#----------------------------------------------------------------------------- + +kernel_aliases = dict(base_aliases) +kernel_aliases.update({ + 'ip' : 'YAP_KernelApp.ip', + 'hb' : 'YAP_KernelApp.hb_port', + 'shell' : 'YAP_KernelApp.shell_port', + 'iopub' : 'YAP_KernelApp.iopub_port', + 'stdin' : 'YAP_KernelApp.stdin_port', + 'control' : 'YAP_KernelApp.control_port', + 'f' : 'YAP_KernelApp.connection_file', + 'transport': 'YAP_KernelApp.transport', +}) + +kernel_flags = dict(base_flags) +kernel_flags.update({ + 'no-stdout' : ( + {'YAP_KernelApp' : {'no_stdout' : True}}, + "redirect stdout to the null device"), + 'no-stderr' : ( + {'YAP_KernelApp' : {'no_stderr' : True}}, + "redirect stderr to the null device"), + 'pylab' : ( + {'YAP_KernelApp' : {'pylab' : 'auto'}}, + """Pre-load matplotlib and numpy for interactive use with + the default matplotlib backend."""), +}) + +# inherit flags&aliases for any IPython shell apps +kernel_aliases.update(shell_aliases) +kernel_flags.update(shell_flags) + +# inherit flags&aliases for Sessions +kernel_aliases.update(session_aliases) +kernel_flags.update(session_flags) + +_ctrl_c_message = """\ +NOTE: When using the `ipython kernel` entry point, Ctrl-C will not work. + +To exit, you will have to explicitly quit this process, by either sending +"quit" from a client, or using Ctrl-\\ in UNIX-like environments. + +To read more about this, see https://github.com/ipython/ipython/issues/2049 + +""" + +#----------------------------------------------------------------------------- +# Application class for starting an IPython Kernel +#----------------------------------------------------------------------------- + +class YAP_KernelApp(BaseIPythonApplication, InteractiveShellApp, + ConnectionFileMixin): + name='YAP Kernel' + aliases = Dict(kernel_aliases) + flags = Dict(kernel_flags) + classes = [YAPKernel, ZMQInteractiveShell, ProfileDir, Session] + # the kernel class, as an importstring + kernel_class = Type('yap_kernel.kernelbase.YAP_Kernel', + klass='yap_kernel.kernelbase.YAP_Kernel', + help="""The Kernel subclass to be used. + + This should allow easy re-use of the YAP_KernelApp entry point + to configure and launch kernels other than IPython's own. + """).tag(config=True) + kernel = Any() + poller = Any() # don't restrict this even though current pollers are all Threads + heartbeat = Instance(Heartbeat, allow_none=True) + ports = Dict() + + + subcommands = { + 'install': ( + 'yap_kernel.kernelspec.InstallYAPKernelSpecApp', + 'Install the YAP kernel' + ), + } + + # connection info: + connection_dir = Unicode() + + @default('connection_dir') + def _default_connection_dir(self): + return jupyter_runtime_dir() + + @property + def abs_connection_file(self): + if os.path.basename(self.connection_file) == self.connection_file: + return os.path.join(self.connection_dir, self.connection_file) + else: + return self.connection_file + + # streams, etc. + no_stdout = Bool(False, help="redirect stdout to the null device").tag(config=True) + no_stderr = Bool(False, help="redirect stderr to the null device").tag(config=True) + outstream_class = DottedObjectName('yap_kernel.iostream.OutStream', + help="The importstring for the OutStream factory").tag(config=True) + displayhook_class = DottedObjectName('yap_kernel.displayhook.ZMQDisplayHook', + help="The importstring for the DisplayHook factory").tag(config=True) + + # polling + parent_handle = Integer(int(os.environ.get('JPY_PARENT_PID') or 0), + help="""kill this process if its parent dies. On Windows, the argument + specifies the HANDLE of the parent process, otherwise it is simply boolean. + """).tag(config=True) + interrupt = Integer(int(os.environ.get('JPY_INTERRUPT_EVENT') or 0), + help="""ONLY USED ON WINDOWS + Interrupt this process when the parent is signaled. + """).tag(config=True) + + def init_crash_handler(self): + sys.excepthook = self.excepthook + + def excepthook(self, etype, evalue, tb): + # write uncaught traceback to 'real' stderr, not zmq-forwarder + traceback.print_exception(etype, evalue, tb, file=sys.__stderr__) + + def init_poller(self): + if sys.platform == 'win32': + if self.interrupt or self.parent_handle: + self.poller = ParentPollerWindows(self.interrupt, self.parent_handle) + elif self.parent_handle and self.parent_handle != 1: + # PID 1 (init) is special and will never go away, + # only be reassigned. + # Parent polling doesn't work if ppid == 1 to start with. + self.poller = ParentPollerUnix() + + def _bind_socket(self, s, port): + iface = '%s://%s' % (self.transport, self.ip) + if self.transport == 'tcp': + if port <= 0: + port = s.bind_to_random_port(iface) + else: + s.bind("tcp://%s:%i" % (self.ip, port)) + elif self.transport == 'ipc': + if port <= 0: + port = 1 + path = "%s-%i" % (self.ip, port) + while os.path.exists(path): + port = port + 1 + path = "%s-%i" % (self.ip, port) + else: + path = "%s-%i" % (self.ip, port) + s.bind("ipc://%s" % path) + return port + + def write_connection_file(self): + """write connection info to JSON file""" + cf = self.abs_connection_file + self.log.debug("Writing connection file: %s", cf) + write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport, + shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port, + iopub_port=self.iopub_port, control_port=self.control_port) + + def cleanup_connection_file(self): + cf = self.abs_connection_file + self.log.debug("Cleaning up connection file: %s", cf) + try: + os.remove(cf) + except (IOError, OSError): + pass + + self.cleanup_ipc_files() + + def init_connection_file(self): + if not self.connection_file: + self.connection_file = "kernel-%s.json"%os.getpid() + try: + self.connection_file = filefind(self.connection_file, ['.', self.connection_dir]) + except IOError: + self.log.debug("Connection file not found: %s", self.connection_file) + # This means I own it, and I'll create it in this directory: + ensure_dir_exists(os.path.dirname(self.abs_connection_file), 0o700) + # Also, I will clean it up: + atexit.register(self.cleanup_connection_file) + return + try: + self.load_connection_file() + except Exception: + self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True) + self.exit(1) + + def init_sockets(self): + # Create a context, a session, and the kernel sockets. + self.log.info("Starting the kernel at pid: %i", os.getpid()) + context = zmq.Context.instance() + # Uncomment this to try closing the context. + # atexit.register(context.term) + + self.shell_socket = context.socket(zmq.ROUTER) + self.shell_socket.linger = 1000 + self.shell_port = self._bind_socket(self.shell_socket, self.shell_port) + self.log.debug("shell ROUTER Channel on port: %i" % self.shell_port) + + self.stdin_socket = context.socket(zmq.ROUTER) + self.stdin_socket.linger = 1000 + self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port) + self.log.debug("stdin ROUTER Channel on port: %i" % self.stdin_port) + + self.control_socket = context.socket(zmq.ROUTER) + self.control_socket.linger = 1000 + self.control_port = self._bind_socket(self.control_socket, self.control_port) + self.log.debug("control ROUTER Channel on port: %i" % self.control_port) + + self.init_iopub(context) + + def init_iopub(self, context): + self.iopub_socket = context.socket(zmq.PUB) + self.iopub_socket.linger = 1000 + self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port) + self.log.debug("iopub PUB Channel on port: %i" % self.iopub_port) + self.configure_tornado_logger() + self.iopub_thread = IOPubThread(self.iopub_socket, pipe=True) + self.iopub_thread.start() + # backward-compat: wrap iopub socket API in background thread + self.iopub_socket = self.iopub_thread.background_socket + + def init_heartbeat(self): + """start the heart beating""" + # heartbeat doesn't share context, because it mustn't be blocked + # by the GIL, which is accessed by libzmq when freeing zero-copy messages + hb_ctx = zmq.Context() + self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port)) + self.hb_port = self.heartbeat.port + self.log.debug("Heartbeat REP Channel on port: %i" % self.hb_port) + self.heartbeat.start() + + def log_connection_info(self): + """display connection info, and store ports""" + basename = os.path.basename(self.connection_file) + if basename == self.connection_file or \ + os.path.dirname(self.connection_file) == self.connection_dir: + # use shortname + tail = basename + else: + tail = self.connection_file + lines = [ + "To connect another client to this kernel, use:", + " --existing %s" % tail, + ] + # log connection info + # info-level, so often not shown. + # frontends should use the %connect_info magic + # to see the connection info + for line in lines: + self.log.info(line) + # also raw print to the terminal if no parent_handle (`ipython kernel`) + # unless log-level is CRITICAL (--quiet) + if not self.parent_handle and self.log_level < logging.CRITICAL: + io.rprint(_ctrl_c_message) + for line in lines: + io.rprint(line) + + self.ports = dict(shell=self.shell_port, iopub=self.iopub_port, + stdin=self.stdin_port, hb=self.hb_port, + control=self.control_port) + + def init_blackhole(self): + """redirects stdout/stderr to devnull if necessary""" + if self.no_stdout or self.no_stderr: + blackhole = open(os.devnull, 'w') + if self.no_stdout: + sys.stdout = sys.__stdout__ = blackhole + if self.no_stderr: + sys.stderr = sys.__stderr__ = blackhole + + def init_io(self): + """Redirect input streams and set a display hook.""" + if self.outstream_class: + outstream_factory = import_item(str(self.outstream_class)) + sys.stdout = outstream_factory(self.session, self.iopub_thread, u'stdout') + sys.stderr = outstream_factory(self.session, self.iopub_thread, u'stderr') + if self.displayhook_class: + displayhook_factory = import_item(str(self.displayhook_class)) + self.displayhook = displayhook_factory(self.session, self.iopub_socket) + sys.displayhook = self.displayhook + + self.patch_io() + + def patch_io(self): + """Patch important libraries that can't handle sys.stdout forwarding""" + try: + import faulthandler + except ImportError: + pass + else: + # Warning: this is a monkeypatch of `faulthandler.enable`, watch for possible + # updates to the upstream API and update accordingly (up-to-date as of Python 3.5): + # https://docs.python.org/3/library/faulthandler.html#faulthandler.enable + + # change default file to __stderr__ from forwarded stderr + faulthandler_enable = faulthandler.enable + def enable(file=sys.__stderr__, all_threads=True, **kwargs): + return faulthandler_enable(file=file, all_threads=all_threads, **kwargs) + + faulthandler.enable = enable + + if hasattr(faulthandler, 'register'): + faulthandler_register = faulthandler.register + def register(signum, file=sys.__stderr__, all_threads=True, chain=False, **kwargs): + return faulthandler_register(signum, file=file, all_threads=all_threads, + chain=chain, **kwargs) + faulthandler.register = register + + def init_signal(self): + signal.signal(signal.SIGINT, signal.SIG_IGN) + + def init_kernel(self): + """Create the Kernel object itself""" + shell_stream = ZMQStream(self.shell_socket) + control_stream = ZMQStream(self.control_socket) + + kernel_factory = self.kernel_class.instance + + kernel = kernel_factory(parent=self, session=self.session, + shell_streams=[shell_stream, control_stream], + iopub_thread=self.iopub_thread, + iopub_socket=self.iopub_socket, + stdin_socket=self.stdin_socket, + log=self.log, + profile_dir=self.profile_dir, + user_ns=self.user_ns, + ) + kernel.record_ports({ + name + '_port': port for name, port in self.ports.items() + }) + self.kernel = kernel + + # Allow the displayhook to get the execution count + self.displayhook.get_execution_count = lambda: kernel.execution_count + + def init_gui_pylab(self): + """Enable GUI event loop integration, taking pylab into account.""" + + # Register inline backend as default + # this is higher priority than matplotlibrc, + # but lower priority than anything else (mpl.use() for instance). + # This only affects matplotlib >= 1.5 + if not os.environ.get('MPLBACKEND'): + os.environ['MPLBACKEND'] = 'module://yap_kernel.pylab.backend_inline' + + # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab` + # to ensure that any exception is printed straight to stderr. + # Normally _showtraceback associates the reply with an execution, + # which means frontends will never draw it, as this exception + # is not associated with any execute request. + + shell = self.shell + _showtraceback = shell._showtraceback + try: + # replace error-sending traceback with stderr + def print_tb(etype, evalue, stb): + print ("GUI event loop or pylab initialization failed", + file=sys.stderr) + print (shell.InteractiveTB.stb2text(stb), file=sys.stderr) + shell._showtraceback = print_tb + InteractiveShellApp.init_gui_pylab(self) + finally: + shell._showtraceback = _showtraceback + + def init_shell(self): + self.shell = getattr(self.kernel, 'shell', None) + if self.shell: + self.shell.configurables.append(self) + + def init_extensions(self): + super(YAP_KernelApp, self).init_extensions() + # BEGIN HARDCODED WIDGETS HACK + # Ensure ipywidgets extension is loaded if available + extension_man = self.shell.extension_manager + if 'ipywidgets' not in extension_man.loaded: + try: + extension_man.load_extension('ipywidgets') + except ImportError as e: + self.log.debug('ipywidgets package not installed. Widgets will not be available.') + # END HARDCODED WIDGETS HACK + + def configure_tornado_logger(self): + """ Configure the tornado logging.Logger. + + Must set up the tornado logger or else tornado will call + basicConfig for the root logger which makes the root logger + go to the real sys.stderr instead of the capture streams. + This function mimics the setup of logging.basicConfig. + """ + logger = logging.getLogger('tornado') + handler = logging.StreamHandler() + formatter = logging.Formatter(logging.BASIC_FORMAT) + handler.setFormatter(formatter) + logger.addHandler(handler) + + @catch_config_error + def initialize(self, argv=None): + super(YAP_KernelApp, self).initialize(argv) + if self.subapp is not None: + return + # register zmq IOLoop with tornado + zmq_ioloop.install() + self.init_blackhole() + self.init_connection_file() + self.init_poller() + self.init_sockets() + self.init_heartbeat() + # writing/displaying connection info must be *after* init_sockets/heartbeat + self.write_connection_file() + # Log connection info after writing connection file, so that the connection + # file is definitely available at the time someone reads the log. + self.log_connection_info() + self.init_io() + self.init_signal() + self.init_kernel() + # shell init steps + self.init_path() + self.init_shell() + if self.shell: + self.init_gui_pylab() + self.init_extensions() + self.init_code() + # flush stdout/stderr, so that anything written to these streams during + # initialization do not get associated with the first execution request + sys.stdout.flush() + sys.stderr.flush() + + def start(self): + if self.subapp is not None: + return self.subapp.start() + if self.poller is not None: + self.poller.start() + self.kernel.start() + try: + ioloop.IOLoop.instance().start() + except KeyboardInterrupt: + pass + +launch_new_instance = YAP_KernelApp.launch_instance + +def main(): + """Run an YAPKernel as an application""" + app = YAP_KernelApp.instance() + app.initialize() + app.start() + + +if __name__ == '__main__': + main() diff --git a/packages/python/yap_kernel/yap_kernel/__init__.py b/packages/python/yap_kernel/yap_kernel/__init__.py new file mode 100644 index 000000000..668e1c61a --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/__init__.py @@ -0,0 +1,3 @@ +from ._version import version_info, __version__, kernel_protocol_version_info, kernel_protocol_version +from .connect import * + diff --git a/packages/python/yap_kernel/yap_kernel/__main__.py b/packages/python/yap_kernel/yap_kernel/__main__.py new file mode 100644 index 000000000..3d2e13411 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/__main__.py @@ -0,0 +1,5 @@ +if __name__ == '__main__': + from yap_kernel import kernelapp as app + app.launch_new_instance() + + diff --git a/packages/python/yap_kernel/yap_kernel/_version.py b/packages/python/yap_kernel/yap_kernel/_version.py new file mode 100644 index 000000000..01f234f77 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/_version.py @@ -0,0 +1,5 @@ +version_info = (6, 3, 5) +__version__ = '.'.join(map(str, version_info)) + +kernel_protocol_version_info = (5, 1) +kernel_protocol_version = '%s.%s' % kernel_protocol_version_info diff --git a/packages/python/yap_kernel/yap_kernel/codeutil.py b/packages/python/yap_kernel/yap_kernel/codeutil.py new file mode 100644 index 000000000..804166144 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/codeutil.py @@ -0,0 +1,38 @@ +# encoding: utf-8 + +"""Utilities to enable code objects to be pickled. + +Any process that import this module will be able to pickle code objects. This +includes the func_code attribute of any function. Once unpickled, new +functions can be built using new.function(code, globals()). Eventually +we need to automate all of this so that functions themselves can be pickled. + +Reference: A. Tremols, P Cogolo, "Python Cookbook," p 302-305 +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import warnings +warnings.warn("yap_kernel.codeutil is deprecated since IPykernel 4.3.1. It has moved to ipyparallel.serialize", DeprecationWarning) + +import sys +import types +try: + import copyreg # Py 3 +except ImportError: + import copy_reg as copyreg # Py 2 + +def code_ctor(*args): + return types.CodeType(*args) + +def reduce_code(co): + args = [co.co_argcount, co.co_nlocals, co.co_stacksize, + co.co_flags, co.co_code, co.co_consts, co.co_names, + co.co_varnames, co.co_filename, co.co_name, co.co_firstlineno, + co.co_lnotab, co.co_freevars, co.co_cellvars] + if sys.version_info[0] >= 3: + args.insert(1, co.co_kwonlyargcount) + return code_ctor, tuple(args) + +copyreg.pickle(types.CodeType, reduce_code) diff --git a/packages/python/yap_kernel/yap_kernel/comm/__init__.py b/packages/python/yap_kernel/yap_kernel/comm/__init__.py new file mode 100644 index 000000000..1faa164c0 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/comm/__init__.py @@ -0,0 +1,2 @@ +from .manager import * +from .comm import * diff --git a/packages/python/yap_kernel/yap_kernel/comm/comm.py b/packages/python/yap_kernel/yap_kernel/comm/comm.py new file mode 100644 index 000000000..5743425a5 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/comm/comm.py @@ -0,0 +1,164 @@ +"""Base class for a Comm""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import uuid + +from traitlets.config import LoggingConfigurable +from yap_kernel.kernelbase import Kernel + +from yap_kernel.jsonutil import json_clean +from traitlets import Instance, Unicode, Bytes, Bool, Dict, Any, default + + +class Comm(LoggingConfigurable): + """Class for communicating between a Frontend and a Kernel""" + kernel = Instance('yap_kernel.kernelbase.Kernel', allow_none=True) + + @default('kernel') + def _default_kernel(self): + if Kernel.initialized(): + return Kernel.instance() + + comm_id = Unicode() + + @default('comm_id') + def _default_comm_id(self): + return uuid.uuid4().hex + + primary = Bool(True, help="Am I the primary or secondary Comm?") + + target_name = Unicode('comm') + target_module = Unicode(None, allow_none=True, help="""requirejs module from + which to load comm target.""") + + topic = Bytes() + + @default('topic') + def _default_topic(self): + return ('comm-%s' % self.comm_id).encode('ascii') + + _open_data = Dict(help="data dict, if any, to be included in comm_open") + _close_data = Dict(help="data dict, if any, to be included in comm_close") + + _msg_callback = Any() + _close_callback = Any() + + _closed = Bool(True) + + def __init__(self, target_name='', data=None, metadata=None, buffers=None, **kwargs): + if target_name: + kwargs['target_name'] = target_name + super(Comm, self).__init__(**kwargs) + if self.kernel: + if self.primary: + # I am primary, open my peer. + self.open(data=data, metadata=metadata, buffers=buffers) + else: + self._closed = False + + def _publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys): + """Helper for sending a comm message on IOPub""" + data = {} if data is None else data + metadata = {} if metadata is None else metadata + content = json_clean(dict(data=data, comm_id=self.comm_id, **keys)) + self.kernel.session.send(self.kernel.iopub_socket, msg_type, + content, + metadata=json_clean(metadata), + parent=self.kernel._parent_header, + ident=self.topic, + buffers=buffers, + ) + + def __del__(self): + """trigger close on gc""" + self.close() + + # publishing messages + + def open(self, data=None, metadata=None, buffers=None): + """Open the frontend-side version of this comm""" + if data is None: + data = self._open_data + comm_manager = getattr(self.kernel, 'comm_manager', None) + if comm_manager is None: + raise RuntimeError("Comms cannot be opened without a kernel " + "and a comm_manager attached to that kernel.") + + comm_manager.register_comm(self) + try: + self._publish_msg('comm_open', + data=data, metadata=metadata, buffers=buffers, + target_name=self.target_name, + target_module=self.target_module, + ) + self._closed = False + except: + comm_manager.unregister_comm(self) + raise + + def close(self, data=None, metadata=None, buffers=None): + """Close the frontend-side version of this comm""" + if self._closed: + # only close once + return + self._closed = True + # nothing to send if we have no kernel + # can be None during interpreter cleanup + if not self.kernel: + return + if data is None: + data = self._close_data + self._publish_msg('comm_close', + data=data, metadata=metadata, buffers=buffers, + ) + self.kernel.comm_manager.unregister_comm(self) + + def send(self, data=None, metadata=None, buffers=None): + """Send a message to the frontend-side version of this comm""" + self._publish_msg('comm_msg', + data=data, metadata=metadata, buffers=buffers, + ) + + # registering callbacks + + def on_close(self, callback): + """Register a callback for comm_close + + Will be called with the `data` of the close message. + + Call `on_close(None)` to disable an existing callback. + """ + self._close_callback = callback + + def on_msg(self, callback): + """Register a callback for comm_msg + + Will be called with the `data` of any comm_msg messages. + + Call `on_msg(None)` to disable an existing callback. + """ + self._msg_callback = callback + + # handling of incoming messages + + def handle_close(self, msg): + """Handle a comm_close message""" + self.log.debug("handle_close[%s](%s)", self.comm_id, msg) + if self._close_callback: + self._close_callback(msg) + + def handle_msg(self, msg): + """Handle a comm_msg message""" + self.log.debug("handle_msg[%s](%s)", self.comm_id, msg) + if self._msg_callback: + shell = self.kernel.shell + if shell: + shell.events.trigger('pre_execute') + self._msg_callback(msg) + if shell: + shell.events.trigger('post_execute') + + +__all__ = ['Comm'] diff --git a/packages/python/yap_kernel/yap_kernel/comm/manager.py b/packages/python/yap_kernel/yap_kernel/comm/manager.py new file mode 100644 index 000000000..b3a091c7b --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/comm/manager.py @@ -0,0 +1,130 @@ +"""Base class to manage comms""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import sys +import logging + +from traitlets.config import LoggingConfigurable + +from ipython_genutils.importstring import import_item +from ipython_genutils.py3compat import string_types +from traitlets import Instance, Unicode, Dict, Any, default + +from .comm import Comm + + +class CommManager(LoggingConfigurable): + """Manager for Comms in the Kernel""" + + kernel = Instance('yap_kernel.kernelbase.Kernel') + comms = Dict() + targets = Dict() + + # Public APIs + + def register_target(self, target_name, f): + """Register a callable f for a given target name + + f will be called with two arguments when a comm_open message is received with `target`: + + - the Comm instance + - the `comm_open` message itself. + + f can be a Python callable or an import string for one. + """ + if isinstance(f, string_types): + f = import_item(f) + + self.targets[target_name] = f + + def unregister_target(self, target_name, f): + """Unregister a callable registered with register_target""" + return self.targets.pop(target_name) + + def register_comm(self, comm): + """Register a new comm""" + comm_id = comm.comm_id + comm.kernel = self.kernel + self.comms[comm_id] = comm + return comm_id + + def unregister_comm(self, comm): + """Unregister a comm, and close its counterpart""" + # unlike get_comm, this should raise a KeyError + comm = self.comms.pop(comm.comm_id) + + def get_comm(self, comm_id): + """Get a comm with a particular id + + Returns the comm if found, otherwise None. + + This will not raise an error, + it will log messages if the comm cannot be found. + """ + try: + return self.comms[comm_id] + except KeyError: + self.log.warn("No such comm: %s", comm_id) + if self.log.isEnabledFor(logging.DEBUG): + # don't create the list of keys if debug messages aren't enabled + self.log.debug("Current comms: %s", list(self.comms.keys())) + + # Message handlers + def comm_open(self, stream, ident, msg): + """Handler for comm_open messages""" + content = msg['content'] + comm_id = content['comm_id'] + target_name = content['target_name'] + f = self.targets.get(target_name, None) + comm = Comm(comm_id=comm_id, + primary=False, + target_name=target_name, + ) + self.register_comm(comm) + if f is None: + self.log.error("No such comm target registered: %s", target_name) + else: + try: + f(comm, msg) + return + except Exception: + self.log.error("Exception opening comm with target: %s", target_name, exc_info=True) + + # Failure. + try: + comm.close() + except: + self.log.error("""Could not close comm during `comm_open` failure + clean-up. The comm may not have been opened yet.""", exc_info=True) + + def comm_msg(self, stream, ident, msg): + """Handler for comm_msg messages""" + content = msg['content'] + comm_id = content['comm_id'] + comm = self.get_comm(comm_id) + if comm is None: + return + + try: + comm.handle_msg(msg) + except Exception: + self.log.error('Exception in comm_msg for %s', comm_id, exc_info=True) + + def comm_close(self, stream, ident, msg): + """Handler for comm_close messages""" + content = msg['content'] + comm_id = content['comm_id'] + comm = self.get_comm(comm_id) + if comm is None: + return + + del self.comms[comm_id] + + try: + comm.handle_close(msg) + except Exception: + self.log.error('Exception in comm_close for %s', comm_id, exc_info=True) + +__all__ = ['CommManager'] diff --git a/packages/python/yap_kernel/yap_kernel/connect.py b/packages/python/yap_kernel/yap_kernel/connect.py new file mode 100644 index 000000000..299fec1b7 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/connect.py @@ -0,0 +1,183 @@ +"""Connection file-related utilities for the kernel +""" +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from __future__ import absolute_import + +import json +import sys +from subprocess import Popen, PIPE +import warnings + +from IPython.core.profiledir import ProfileDir +from IPython.paths import get_ipython_dir +from ipython_genutils.path import filefind +from ipython_genutils.py3compat import str_to_bytes + +import jupyter_client +from jupyter_client import write_connection_file + + + +def get_connection_file(app=None): + """Return the path to the connection file of an app + + Parameters + ---------- + app : YAPKernelApp instance [optional] + If unspecified, the currently running app will be used + """ + if app is None: + from yap_kernel.kernelapp import YAPKernelApp + if not YAPKernelApp.initialized(): + raise RuntimeError("app not specified, and not in a running Kernel") + + app = YAPKernelApp.instance() + return filefind(app.connection_file, ['.', app.connection_dir]) + + +def find_connection_file(filename='kernel-*.json', profile=None): + """DEPRECATED: find a connection file, and return its absolute path. + + THIS FUNCION IS DEPRECATED. Use juptyer_client.find_connection_file instead. + + Parameters + ---------- + filename : str + The connection file or fileglob to search for. + profile : str [optional] + The name of the profile to use when searching for the connection file, + if different from the current IPython session or 'default'. + + Returns + ------- + str : The absolute path of the connection file. + """ + + import warnings + warnings.warn("""yap_kernel.find_connection_file is deprecated, use jupyter_client.find_connection_file""", + DeprecationWarning, stacklevel=2) + from IPython.core.application import BaseIPythonApplication as IPApp + try: + # quick check for absolute path, before going through logic + return filefind(filename) + except IOError: + pass + + if profile is None: + # profile unspecified, check if running from an IPython app + if IPApp.initialized(): + app = IPApp.instance() + profile_dir = app.profile_dir + else: + # not running in IPython, use default profile + profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default') + else: + # find profiledir by profile name: + profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile) + security_dir = profile_dir.security_dir + + return jupyter_client.find_connection_file(filename, path=['.', security_dir]) + + +def _find_connection_file(connection_file, profile=None): + """Return the absolute path for a connection file + + - If nothing specified, return current Kernel's connection file + - If profile specified, show deprecation warning about finding connection files in profiles + - Otherwise, call jupyter_client.find_connection_file + """ + if connection_file is None: + # get connection file from current kernel + return get_connection_file() + else: + # connection file specified, allow shortnames: + if profile is not None: + warnings.warn( + "Finding connection file by profile is deprecated.", + DeprecationWarning, stacklevel=3, + ) + return find_connection_file(connection_file, profile=profile) + else: + return jupyter_client.find_connection_file(connection_file) + + +def get_connection_info(connection_file=None, unpack=False, profile=None): + """Return the connection information for the current Kernel. + + Parameters + ---------- + connection_file : str [optional] + The connection file to be used. Can be given by absolute path, or + IPython will search in the security directory of a given profile. + If run from IPython, + + If unspecified, the connection file for the currently running + IPython Kernel will be used, which is only allowed from inside a kernel. + unpack : bool [default: False] + if True, return the unpacked dict, otherwise just the string contents + of the file. + profile : DEPRECATED + + Returns + ------- + The connection dictionary of the current kernel, as string or dict, + depending on `unpack`. + """ + cf = _find_connection_file(connection_file, profile) + + with open(cf) as f: + info = f.read() + + if unpack: + info = json.loads(info) + # ensure key is bytes: + info['key'] = str_to_bytes(info.get('key', '')) + return info + + +def connect_qtconsole(connection_file=None, argv=None, profile=None): + """Connect a qtconsole to the current kernel. + + This is useful for connecting a second qtconsole to a kernel, or to a + local notebook. + + Parameters + ---------- + connection_file : str [optional] + The connection file to be used. Can be given by absolute path, or + IPython will search in the security directory of a given profile. + If run from IPython, + + If unspecified, the connection file for the currently running + IPython Kernel will be used, which is only allowed from inside a kernel. + argv : list [optional] + Any extra args to be passed to the console. + profile : DEPRECATED + + Returns + ------- + :class:`subprocess.Popen` instance running the qtconsole frontend + """ + argv = [] if argv is None else argv + + cf = _find_connection_file(connection_file, profile) + + cmd = ';'.join([ + "from IPython.qt.console import qtconsoleapp", + "qtconsoleapp.main()" + ]) + + return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv, + stdout=PIPE, stderr=PIPE, close_fds=(sys.platform != 'win32'), + ) + + +__all__ = [ + 'write_connection_file', + 'get_connection_file', + 'find_connection_file', + 'get_connection_info', + 'connect_qtconsole', +] diff --git a/packages/python/yap_kernel/yap_kernel/datapub.py b/packages/python/yap_kernel/yap_kernel/datapub.py new file mode 100644 index 000000000..c8a05f707 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/datapub.py @@ -0,0 +1,62 @@ +"""Publishing native (typically pickled) objects. +""" + +import warnings +warnings.warn("yap_kernel.datapub is deprecated. It has moved to ipyparallel.datapub", DeprecationWarning) + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from traitlets.config import Configurable +from traitlets import Instance, Dict, CBytes, Any +from yap_kernel.jsonutil import json_clean +from yap_kernel.serialize import serialize_object +from jupyter_client.session import Session, extract_header + + +class ZMQDataPublisher(Configurable): + + topic = topic = CBytes(b'datapub') + session = Instance(Session, allow_none=True) + pub_socket = Any(allow_none=True) + parent_header = Dict({}) + + def set_parent(self, parent): + """Set the parent for outbound messages.""" + self.parent_header = extract_header(parent) + + def publish_data(self, data): + """publish a data_message on the IOPub channel + + Parameters + ---------- + + data : dict + The data to be published. Think of it as a namespace. + """ + session = self.session + buffers = serialize_object(data, + buffer_threshold=session.buffer_threshold, + item_threshold=session.item_threshold, + ) + content = json_clean(dict(keys=list(data.keys()))) + session.send(self.pub_socket, 'data_message', content=content, + parent=self.parent_header, + buffers=buffers, + ident=self.topic, + ) + + +def publish_data(data): + """publish a data_message on the IOPub channel + + Parameters + ---------- + + data : dict + The data to be published. Think of it as a namespace. + """ + warnings.warn("yap_kernel.datapub is deprecated. It has moved to ipyparallel.datapub", DeprecationWarning) + + from yap_kernel.zmqshell import ZMQInteractiveShell + ZMQInteractiveShell.instance().data_pub.publish_data(data) diff --git a/packages/python/yap_kernel/yap_kernel/displayhook.py b/packages/python/yap_kernel/yap_kernel/displayhook.py new file mode 100644 index 000000000..141222c3c --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/displayhook.py @@ -0,0 +1,80 @@ +"""Replacements for sys.displayhook that publish over ZMQ.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import sys + +from IPython.core.displayhook import DisplayHook +from yap_kernel.jsonutil import encode_images +from ipython_genutils.py3compat import builtin_mod +from traitlets import Instance, Dict, Any +from jupyter_client.session import extract_header, Session + + +class ZMQDisplayHook(object): + """A simple displayhook that publishes the object's repr over a ZeroMQ + socket.""" + topic = b'execute_result' + + def __init__(self, session, pub_socket): + self.session = session + self.pub_socket = pub_socket + self.parent_header = {} + + def get_execution_count(self): + """This method is replaced in kernelapp""" + return 0 + + def __call__(self, obj): + if obj is None: + return + + builtin_mod._ = obj + sys.stdout.flush() + sys.stderr.flush() + contents = {u'execution_count': self.get_execution_count(), + u'data': {'text/plain': repr(obj)}, + u'metadata': {}} + self.session.send(self.pub_socket, u'execute_result', contents, + parent=self.parent_header, ident=self.topic) + + def set_parent(self, parent): + self.parent_header = extract_header(parent) + + +class ZMQShellDisplayHook(DisplayHook): + """A displayhook subclass that publishes data using ZeroMQ. This is intended + to work with an InteractiveShell instance. It sends a dict of different + representations of the object.""" + topic=None + + session = Instance(Session, allow_none=True) + pub_socket = Any(allow_none=True) + parent_header = Dict({}) + + def set_parent(self, parent): + """Set the parent for outbound messages.""" + self.parent_header = extract_header(parent) + + def start_displayhook(self): + self.msg = self.session.msg(u'execute_result', { + 'data': {}, + 'metadata': {}, + }, parent=self.parent_header) + + def write_output_prompt(self): + """Write the output prompt.""" + self.msg['content']['execution_count'] = self.prompt_count + + def write_format_data(self, format_dict, md_dict=None): + self.msg['content']['data'] = encode_images(format_dict) + self.msg['content']['metadata'] = md_dict + + def finish_displayhook(self): + """Finish up all displayhook activities.""" + sys.stdout.flush() + sys.stderr.flush() + if self.msg['content']['data']: + self.session.send(self.pub_socket, self.msg, ident=self.topic) + self.msg = None diff --git a/packages/python/yap_kernel/yap_kernel/embed.py b/packages/python/yap_kernel/yap_kernel/embed.py new file mode 100644 index 000000000..6cae598c2 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/embed.py @@ -0,0 +1,57 @@ +"""Simple function for embedding an IPython kernel +""" +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import sys + +from IPython.utils.frame import extract_module_locals + +from .kernelapp import YAPKernelApp + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- + +def embed_kernel(module=None, local_ns=None, **kwargs): + """Embed and start an IPython kernel in a given scope. + + Parameters + ---------- + module : ModuleType, optional + The module to load into IPython globals (default: caller) + local_ns : dict, optional + The namespace to load into IPython user namespace (default: caller) + + kwargs : various, optional + Further keyword args are relayed to the YAPKernelApp constructor, + allowing configuration of the Kernel. Will only have an effect + on the first embed_kernel call for a given process. + + """ + # get the app if it exists, or set it up if it doesn't + if YAPKernelApp.initialized(): + app = YAPKernelApp.instance() + else: + app = YAPKernelApp.instance(**kwargs) + app.initialize([]) + # Undo unnecessary sys module mangling from init_sys_modules. + # This would not be necessary if we could prevent it + # in the first place by using a different InteractiveShell + # subclass, as in the regular embed case. + main = app.kernel.shell._orig_sys_modules_main_mod + if main is not None: + sys.modules[app.kernel.shell._orig_sys_modules_main_name] = main + + # load the calling scope if not given + (caller_module, caller_locals) = extract_module_locals(1) + if module is None: + module = caller_module + if local_ns is None: + local_ns = caller_locals + + app.kernel.user_module = module + app.kernel.user_ns = local_ns + app.shell.set_completer_frame() + app.start() diff --git a/packages/python/yap_kernel/yap_kernel/eventloops.py b/packages/python/yap_kernel/yap_kernel/eventloops.py new file mode 100644 index 000000000..0befc687a --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/eventloops.py @@ -0,0 +1,309 @@ +# encoding: utf-8 +"""Event loop integration for the ZeroMQ-based kernels.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import os +import sys +import platform + +import zmq + +from distutils.version import LooseVersion as V +from traitlets.config.application import Application +from IPython.utils import io + +def _use_appnope(): + """Should we use appnope for dealing with OS X app nap? + + Checks if we are on OS X 10.9 or greater. + """ + return sys.platform == 'darwin' and V(platform.mac_ver()[0]) >= V('10.9') + +def _notify_stream_qt(kernel, stream): + + from IPython.external.qt_for_kernel import QtCore + + if _use_appnope() and kernel._darwin_app_nap: + from appnope import nope_scope as context + else: + from contextlib import contextmanager + @contextmanager + def context(): + yield + + def process_stream_events(): + while stream.getsockopt(zmq.EVENTS) & zmq.POLLIN: + with context(): + kernel.do_one_iteration() + + fd = stream.getsockopt(zmq.FD) + notifier = QtCore.QSocketNotifier(fd, QtCore.QSocketNotifier.Read, kernel.app) + notifier.activated.connect(process_stream_events) + +# mapping of keys to loop functions +loop_map = { + 'inline': None, + 'nbagg': None, + 'notebook': None, + 'ipympl': None, + None : None, +} + +def register_integration(*toolkitnames): + """Decorator to register an event loop to integrate with the IPython kernel + + The decorator takes names to register the event loop as for the %gui magic. + You can provide alternative names for the same toolkit. + + The decorated function should take a single argument, the IPython kernel + instance, arrange for the event loop to call ``kernel.do_one_iteration()`` + at least every ``kernel._poll_interval`` seconds, and start the event loop. + + :mod:`yap_kernel.eventloops` provides and registers such functions + for a few common event loops. + """ + def decorator(func): + for name in toolkitnames: + loop_map[name] = func + return func + + return decorator + + +def _loop_qt(app): + """Inner-loop for running the Qt eventloop + + Pulled from guisupport.start_event_loop in IPython < 5.2, + since IPython 5.2 only checks `get_ipython().active_eventloop` is defined, + rather than if the eventloop is actually running. + """ + app._in_event_loop = True + app.exec_() + app._in_event_loop = False + + +@register_integration('qt', 'qt4') +def loop_qt4(kernel): + """Start a kernel with PyQt4 event loop integration.""" + + from IPython.lib.guisupport import get_app_qt4 + + kernel.app = get_app_qt4([" "]) + kernel.app.setQuitOnLastWindowClosed(False) + + for s in kernel.shell_streams: + _notify_stream_qt(kernel, s) + + _loop_qt(kernel.app) + + +@register_integration('qt5') +def loop_qt5(kernel): + """Start a kernel with PyQt5 event loop integration.""" + os.environ['QT_API'] = 'pyqt5' + return loop_qt4(kernel) + + +def _loop_wx(app): + """Inner-loop for running the Wx eventloop + + Pulled from guisupport.start_event_loop in IPython < 5.2, + since IPython 5.2 only checks `get_ipython().active_eventloop` is defined, + rather than if the eventloop is actually running. + """ + app._in_event_loop = True + app.MainLoop() + app._in_event_loop = False + + +@register_integration('wx') +def loop_wx(kernel): + """Start a kernel with wx event loop support.""" + + import wx + + if _use_appnope() and kernel._darwin_app_nap: + # we don't hook up App Nap contexts for Wx, + # just disable it outright. + from appnope import nope + nope() + + doi = kernel.do_one_iteration + # Wx uses milliseconds + poll_interval = int(1000*kernel._poll_interval) + + # We have to put the wx.Timer in a wx.Frame for it to fire properly. + # We make the Frame hidden when we create it in the main app below. + class TimerFrame(wx.Frame): + def __init__(self, func): + wx.Frame.__init__(self, None, -1) + self.timer = wx.Timer(self) + # Units for the timer are in milliseconds + self.timer.Start(poll_interval) + self.Bind(wx.EVT_TIMER, self.on_timer) + self.func = func + + def on_timer(self, event): + self.func() + + # We need a custom wx.App to create our Frame subclass that has the + # wx.Timer to drive the ZMQ event loop. + class IPWxApp(wx.App): + def OnInit(self): + self.frame = TimerFrame(doi) + self.frame.Show(False) + return True + + # The redirect=False here makes sure that wx doesn't replace + # sys.stdout/stderr with its own classes. + kernel.app = IPWxApp(redirect=False) + + # The import of wx on Linux sets the handler for signal.SIGINT + # to 0. This is a bug in wx or gtk. We fix by just setting it + # back to the Python default. + import signal + if not callable(signal.getsignal(signal.SIGINT)): + signal.signal(signal.SIGINT, signal.default_int_handler) + + _loop_wx(kernel.app) + + +@register_integration('tk') +def loop_tk(kernel): + """Start a kernel with the Tk event loop.""" + + try: + from tkinter import Tk # Py 3 + except ImportError: + from Tkinter import Tk # Py 2 + doi = kernel.do_one_iteration + # Tk uses milliseconds + poll_interval = int(1000*kernel._poll_interval) + # For Tkinter, we create a Tk object and call its withdraw method. + class Timer(object): + def __init__(self, func): + self.app = Tk() + self.app.withdraw() + self.func = func + + def on_timer(self): + self.func() + self.app.after(poll_interval, self.on_timer) + + def start(self): + self.on_timer() # Call it once to get things going. + self.app.mainloop() + + kernel.timer = Timer(doi) + kernel.timer.start() + + +@register_integration('gtk') +def loop_gtk(kernel): + """Start the kernel, coordinating with the GTK event loop""" + from .gui.gtkembed import GTKEmbed + + gtk_kernel = GTKEmbed(kernel) + gtk_kernel.start() + + +@register_integration('gtk3') +def loop_gtk3(kernel): + """Start the kernel, coordinating with the GTK event loop""" + from .gui.gtk3embed import GTKEmbed + + gtk_kernel = GTKEmbed(kernel) + gtk_kernel.start() + + +@register_integration('osx') +def loop_cocoa(kernel): + """Start the kernel, coordinating with the Cocoa CFRunLoop event loop + via the matplotlib MacOSX backend. + """ + import matplotlib + if matplotlib.__version__ < '1.1.0': + kernel.log.warn( + "MacOSX backend in matplotlib %s doesn't have a Timer, " + "falling back on Tk for CFRunLoop integration. Note that " + "even this won't work if Tk is linked against X11 instead of " + "Cocoa (e.g. EPD). To use the MacOSX backend in the kernel, " + "you must use matplotlib >= 1.1.0, or a native libtk." + ) + return loop_tk(kernel) + + from matplotlib.backends.backend_macosx import TimerMac, show + + # scale interval for sec->ms + poll_interval = int(1000*kernel._poll_interval) + + real_excepthook = sys.excepthook + def handle_int(etype, value, tb): + """don't let KeyboardInterrupts look like crashes""" + if etype is KeyboardInterrupt: + io.raw_print("KeyboardInterrupt caught in CFRunLoop") + else: + real_excepthook(etype, value, tb) + + # add doi() as a Timer to the CFRunLoop + def doi(): + # restore excepthook during IPython code + sys.excepthook = real_excepthook + kernel.do_one_iteration() + # and back: + sys.excepthook = handle_int + + t = TimerMac(poll_interval) + t.add_callback(doi) + t.start() + + # but still need a Poller for when there are no active windows, + # during which time mainloop() returns immediately + poller = zmq.Poller() + if kernel.control_stream: + poller.register(kernel.control_stream.socket, zmq.POLLIN) + for stream in kernel.shell_streams: + poller.register(stream.socket, zmq.POLLIN) + + while True: + try: + # double nested try/except, to properly catch KeyboardInterrupt + # due to pyzmq Issue #130 + try: + # don't let interrupts during mainloop invoke crash_handler: + sys.excepthook = handle_int + show.mainloop() + sys.excepthook = real_excepthook + # use poller if mainloop returned (no windows) + # scale by extra factor of 10, since it's a real poll + poller.poll(10*poll_interval) + kernel.do_one_iteration() + except: + raise + except KeyboardInterrupt: + # Ctrl-C shouldn't crash the kernel + io.raw_print("KeyboardInterrupt caught in kernel") + finally: + # ensure excepthook is restored + sys.excepthook = real_excepthook + + + +def enable_gui(gui, kernel=None): + """Enable integration with a given GUI""" + if gui not in loop_map: + e = "Invalid GUI request %r, valid ones are:%s" % (gui, loop_map.keys()) + raise ValueError(e) + if kernel is None: + if Application.initialized(): + kernel = getattr(Application.instance(), 'kernel', None) + if kernel is None: + raise RuntimeError("You didn't specify a kernel," + " and no IPython Application with a kernel appears to be running." + ) + loop = loop_map[gui] + if loop and kernel.eventloop is not None and kernel.eventloop is not loop: + raise RuntimeError("Cannot activate multiple GUI eventloops") + kernel.eventloop = loop diff --git a/packages/python/yap_kernel/yap_kernel/gui/__init__.py b/packages/python/yap_kernel/yap_kernel/gui/__init__.py new file mode 100644 index 000000000..1351f3c27 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/gui/__init__.py @@ -0,0 +1,15 @@ +"""GUI support for the IPython ZeroMQ kernel. + +This package contains the various toolkit-dependent utilities we use to enable +coordination between the IPython kernel and the event loops of the various GUI +toolkits. +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2010-2011 The IPython Development Team. +# +# Distributed under the terms of the BSD License. +# +# The full license is in the file COPYING.txt, distributed as part of this +# software. +#----------------------------------------------------------------------------- diff --git a/packages/python/yap_kernel/yap_kernel/gui/gtk3embed.py b/packages/python/yap_kernel/yap_kernel/gui/gtk3embed.py new file mode 100644 index 000000000..5cea1adb6 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/gui/gtk3embed.py @@ -0,0 +1,88 @@ +"""GUI support for the IPython ZeroMQ kernel - GTK toolkit support. +""" +#----------------------------------------------------------------------------- +# Copyright (C) 2010-2011 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING.txt, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- +# stdlib +import sys + +# Third-party +import gi +gi.require_version ('Gdk', '3.0') +gi.require_version ('Gtk', '3.0') +from gi.repository import GObject, Gtk + +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- + +class GTKEmbed(object): + """A class to embed a kernel into the GTK main event loop. + """ + def __init__(self, kernel): + self.kernel = kernel + # These two will later store the real gtk functions when we hijack them + self.gtk_main = None + self.gtk_main_quit = None + + def start(self): + """Starts the GTK main event loop and sets our kernel startup routine. + """ + # Register our function to initiate the kernel and start gtk + GObject.idle_add(self._wire_kernel) + Gtk.main() + + def _wire_kernel(self): + """Initializes the kernel inside GTK. + + This is meant to run only once at startup, so it does its job and + returns False to ensure it doesn't get run again by GTK. + """ + self.gtk_main, self.gtk_main_quit = self._hijack_gtk() + GObject.timeout_add(int(1000*self.kernel._poll_interval), + self.iterate_kernel) + return False + + def iterate_kernel(self): + """Run one iteration of the kernel and return True. + + GTK timer functions must return True to be called again, so we make the + call to :meth:`do_one_iteration` and then return True for GTK. + """ + self.kernel.do_one_iteration() + return True + + def stop(self): + # FIXME: this one isn't getting called because we have no reliable + # kernel shutdown. We need to fix that: once the kernel has a + # shutdown mechanism, it can call this. + self.gtk_main_quit() + sys.exit() + + def _hijack_gtk(self): + """Hijack a few key functions in GTK for IPython integration. + + Modifies pyGTK's main and main_quit with a dummy so user code does not + block IPython. This allows us to use %run to run arbitrary pygtk + scripts from a long-lived IPython session, and when they attempt to + start or stop + + Returns + ------- + The original functions that have been hijacked: + - Gtk.main + - Gtk.main_quit + """ + def dummy(*args, **kw): + pass + # save and trap main and main_quit from gtk + orig_main, Gtk.main = Gtk.main, dummy + orig_main_quit, Gtk.main_quit = Gtk.main_quit, dummy + return orig_main, orig_main_quit diff --git a/packages/python/yap_kernel/yap_kernel/gui/gtkembed.py b/packages/python/yap_kernel/yap_kernel/gui/gtkembed.py new file mode 100644 index 000000000..d9dc7e6f4 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/gui/gtkembed.py @@ -0,0 +1,86 @@ +"""GUI support for the IPython ZeroMQ kernel - GTK toolkit support. +""" +#----------------------------------------------------------------------------- +# Copyright (C) 2010-2011 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING.txt, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- +# stdlib +import sys + +# Third-party +import gobject +import gtk + +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- + +class GTKEmbed(object): + """A class to embed a kernel into the GTK main event loop. + """ + def __init__(self, kernel): + self.kernel = kernel + # These two will later store the real gtk functions when we hijack them + self.gtk_main = None + self.gtk_main_quit = None + + def start(self): + """Starts the GTK main event loop and sets our kernel startup routine. + """ + # Register our function to initiate the kernel and start gtk + gobject.idle_add(self._wire_kernel) + gtk.main() + + def _wire_kernel(self): + """Initializes the kernel inside GTK. + + This is meant to run only once at startup, so it does its job and + returns False to ensure it doesn't get run again by GTK. + """ + self.gtk_main, self.gtk_main_quit = self._hijack_gtk() + gobject.timeout_add(int(1000*self.kernel._poll_interval), + self.iterate_kernel) + return False + + def iterate_kernel(self): + """Run one iteration of the kernel and return True. + + GTK timer functions must return True to be called again, so we make the + call to :meth:`do_one_iteration` and then return True for GTK. + """ + self.kernel.do_one_iteration() + return True + + def stop(self): + # FIXME: this one isn't getting called because we have no reliable + # kernel shutdown. We need to fix that: once the kernel has a + # shutdown mechanism, it can call this. + self.gtk_main_quit() + sys.exit() + + def _hijack_gtk(self): + """Hijack a few key functions in GTK for IPython integration. + + Modifies pyGTK's main and main_quit with a dummy so user code does not + block IPython. This allows us to use %run to run arbitrary pygtk + scripts from a long-lived IPython session, and when they attempt to + start or stop + + Returns + ------- + The original functions that have been hijacked: + - gtk.main + - gtk.main_quit + """ + def dummy(*args, **kw): + pass + # save and trap main and main_quit from gtk + orig_main, gtk.main = gtk.main, dummy + orig_main_quit, gtk.main_quit = gtk.main_quit, dummy + return orig_main, orig_main_quit diff --git a/packages/python/yap_kernel/yap_kernel/heartbeat.py b/packages/python/yap_kernel/yap_kernel/heartbeat.py new file mode 100644 index 000000000..cb03a4627 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/heartbeat.py @@ -0,0 +1,68 @@ +"""The client and server for a basic ping-pong style heartbeat. +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2011 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import errno +import os +import socket +from threading import Thread + +import zmq + +from jupyter_client.localinterfaces import localhost + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- + + +class Heartbeat(Thread): + "A simple ping-pong style heartbeat that runs in a thread." + + def __init__(self, context, addr=None): + if addr is None: + addr = ('tcp', localhost(), 0) + Thread.__init__(self) + self.context = context + self.transport, self.ip, self.port = addr + if self.port == 0: + if addr[0] == 'tcp': + s = socket.socket() + # '*' means all interfaces to 0MQ, which is '' to socket.socket + s.bind(('' if self.ip == '*' else self.ip, 0)) + self.port = s.getsockname()[1] + s.close() + elif addr[0] == 'ipc': + self.port = 1 + while os.path.exists("%s-%s" % (self.ip, self.port)): + self.port = self.port + 1 + else: + raise ValueError("Unrecognized zmq transport: %s" % addr[0]) + self.addr = (self.ip, self.port) + self.daemon = True + + def run(self): + self.socket = self.context.socket(zmq.ROUTER) + self.socket.linger = 1000 + c = ':' if self.transport == 'tcp' else '-' + self.socket.bind('%s://%s' % (self.transport, self.ip) + c + str(self.port)) + while True: + try: + zmq.device(zmq.QUEUE, self.socket, self.socket) + except zmq.ZMQError as e: + if e.errno == errno.EINTR: + continue + else: + raise + else: + break diff --git a/packages/python/yap_kernel/yap_kernel/inprocess/__init__.py b/packages/python/yap_kernel/yap_kernel/inprocess/__init__.py new file mode 100644 index 000000000..8da75615e --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/inprocess/__init__.py @@ -0,0 +1,8 @@ +from .channels import ( + InProcessChannel, + InProcessHBChannel, +) + +from .client import InProcessKernelClient +from .manager import InProcessKernelManager +from .blocking import BlockingInProcessKernelClient diff --git a/packages/python/yap_kernel/yap_kernel/inprocess/blocking.py b/packages/python/yap_kernel/yap_kernel/inprocess/blocking.py new file mode 100644 index 000000000..7cc3e100f --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/inprocess/blocking.py @@ -0,0 +1,93 @@ +""" Implements a fully blocking kernel client. + +Useful for test suites and blocking terminal interfaces. +""" +#----------------------------------------------------------------------------- +# Copyright (C) 2012 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING.txt, distributed as part of this software. +#----------------------------------------------------------------------------- + +try: + from queue import Queue, Empty # Py 3 +except ImportError: + from Queue import Queue, Empty # Py 2 + +# IPython imports +from IPython.utils.io import raw_print +from traitlets import Type + +# Local imports +from .channels import ( + InProcessChannel, +) +from .client import InProcessKernelClient + +class BlockingInProcessChannel(InProcessChannel): + + def __init__(self, *args, **kwds): + super(BlockingInProcessChannel, self).__init__(*args, **kwds) + self._in_queue = Queue() + + def call_handlers(self, msg): + self._in_queue.put(msg) + + def get_msg(self, block=True, timeout=None): + """ Gets a message if there is one that is ready. """ + if timeout is None: + # Queue.get(timeout=None) has stupid uninteruptible + # behavior, so wait for a week instead + timeout = 604800 + return self._in_queue.get(block, timeout) + + def get_msgs(self): + """ Get all messages that are currently ready. """ + msgs = [] + while True: + try: + msgs.append(self.get_msg(block=False)) + except Empty: + break + return msgs + + def msg_ready(self): + """ Is there a message that has been received? """ + return not self._in_queue.empty() + + +class BlockingInProcessStdInChannel(BlockingInProcessChannel): + def call_handlers(self, msg): + """ Overridden for the in-process channel. + + This methods simply calls raw_input directly. + """ + msg_type = msg['header']['msg_type'] + if msg_type == 'input_request': + _raw_input = self.client.kernel._sys_raw_input + prompt = msg['content']['prompt'] + raw_print(prompt, end='') + self.client.input(_raw_input()) + +class BlockingInProcessKernelClient(InProcessKernelClient): + + # The classes to use for the various channels. + shell_channel_class = Type(BlockingInProcessChannel) + iopub_channel_class = Type(BlockingInProcessChannel) + stdin_channel_class = Type(BlockingInProcessStdInChannel) + + def wait_for_ready(self): + # Wait for kernel info reply on shell channel + while True: + msg = self.shell_channel.get_msg(block=True) + if msg['msg_type'] == 'kernel_info_reply': + self._handle_kernel_info_reply(msg) + break + + # Flush IOPub channel + while True: + try: + msg = self.iopub_channel.get_msg(block=True, timeout=0.2) + print(msg['msg_type']) + except Empty: + break diff --git a/packages/python/yap_kernel/yap_kernel/inprocess/channels.py b/packages/python/yap_kernel/yap_kernel/inprocess/channels.py new file mode 100644 index 000000000..0b78d99b2 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/inprocess/channels.py @@ -0,0 +1,97 @@ +"""A kernel client for in-process kernels.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from jupyter_client.channelsabc import HBChannelABC + +from .socket import DummySocket + +#----------------------------------------------------------------------------- +# Channel classes +#----------------------------------------------------------------------------- + +class InProcessChannel(object): + """Base class for in-process channels.""" + proxy_methods = [] + + def __init__(self, client=None): + super(InProcessChannel, self).__init__() + self.client = client + self._is_alive = False + + def is_alive(self): + return self._is_alive + + def start(self): + self._is_alive = True + + def stop(self): + self._is_alive = False + + def call_handlers(self, msg): + """ This method is called in the main thread when a message arrives. + + Subclasses should override this method to handle incoming messages. + """ + raise NotImplementedError('call_handlers must be defined in a subclass.') + + def flush(self, timeout=1.0): + pass + + + def call_handlers_later(self, *args, **kwds): + """ Call the message handlers later. + + The default implementation just calls the handlers immediately, but this + method exists so that GUI toolkits can defer calling the handlers until + after the event loop has run, as expected by GUI frontends. + """ + self.call_handlers(*args, **kwds) + + def process_events(self): + """ Process any pending GUI events. + + This method will be never be called from a frontend without an event + loop (e.g., a terminal frontend). + """ + raise NotImplementedError + + + +class InProcessHBChannel(object): + """A dummy heartbeat channel interface for in-process kernels. + + Normally we use the heartbeat to check that the kernel process is alive. + When the kernel is in-process, that doesn't make sense, but clients still + expect this interface. + """ + + time_to_dead = 3.0 + + def __init__(self, client=None): + super(InProcessHBChannel, self).__init__() + self.client = client + self._is_alive = False + self._pause = True + + def is_alive(self): + return self._is_alive + + def start(self): + self._is_alive = True + + def stop(self): + self._is_alive = False + + def pause(self): + self._pause = True + + def unpause(self): + self._pause = False + + def is_beating(self): + return not self._pause + + +HBChannelABC.register(InProcessHBChannel) diff --git a/packages/python/yap_kernel/yap_kernel/inprocess/client.py b/packages/python/yap_kernel/yap_kernel/inprocess/client.py new file mode 100644 index 000000000..9f6707f07 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/inprocess/client.py @@ -0,0 +1,180 @@ +"""A client for in-process kernels.""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2012 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +# IPython imports +from yap_kernel.inprocess.socket import DummySocket +from traitlets import Type, Instance, default +from jupyter_client.clientabc import KernelClientABC +from jupyter_client.client import KernelClient + +# Local imports +from .channels import ( + InProcessChannel, + InProcessHBChannel, +) + +#----------------------------------------------------------------------------- +# Main kernel Client class +#----------------------------------------------------------------------------- + +class InProcessKernelClient(KernelClient): + """A client for an in-process kernel. + + This class implements the interface of + `jupyter_client.clientabc.KernelClientABC` and allows + (asynchronous) frontends to be used seamlessly with an in-process kernel. + + See `jupyter_client.client.KernelClient` for docstrings. + """ + + # The classes to use for the various channels. + shell_channel_class = Type(InProcessChannel) + iopub_channel_class = Type(InProcessChannel) + stdin_channel_class = Type(InProcessChannel) + hb_channel_class = Type(InProcessHBChannel) + + kernel = Instance('yap_kernel.inprocess.yapkernel.InProcessKernel', + allow_none=True) + + #-------------------------------------------------------------------------- + # Channel management methods + #-------------------------------------------------------------------------- + + @default('blocking_class') + def _default_blocking_class(self): + from .blocking import BlockingInProcessKernelClient + return BlockingInProcessKernelClient + + def get_connection_info(self): + d = super(InProcessKernelClient, self).get_connection_info() + d['kernel'] = self.kernel + return d + + def start_channels(self, *args, **kwargs): + super(InProcessKernelClient, self).start_channels() + self.kernel.frontends.append(self) + + @property + def shell_channel(self): + if self._shell_channel is None: + self._shell_channel = self.shell_channel_class(self) + return self._shell_channel + + @property + def iopub_channel(self): + if self._iopub_channel is None: + self._iopub_channel = self.iopub_channel_class(self) + return self._iopub_channel + + @property + def stdin_channel(self): + if self._stdin_channel is None: + self._stdin_channel = self.stdin_channel_class(self) + return self._stdin_channel + + @property + def hb_channel(self): + if self._hb_channel is None: + self._hb_channel = self.hb_channel_class(self) + return self._hb_channel + + # Methods for sending specific messages + # ------------------------------------- + + def execute(self, code, silent=False, store_history=True, + user_expressions={}, allow_stdin=None): + if allow_stdin is None: + allow_stdin = self.allow_stdin + content = dict(code=code, silent=silent, store_history=store_history, + user_expressions=user_expressions, + allow_stdin=allow_stdin) + msg = self.session.msg('execute_request', content) + self._dispatch_to_kernel(msg) + return msg['header']['msg_id'] + + def complete(self, code, cursor_pos=None): + if cursor_pos is None: + cursor_pos = len(code) + content = dict(code=code, cursor_pos=cursor_pos) + msg = self.session.msg('complete_request', content) + self._dispatch_to_kernel(msg) + return msg['header']['msg_id'] + + def inspect(self, code, cursor_pos=None, detail_level=0): + if cursor_pos is None: + cursor_pos = len(code) + content = dict(code=code, cursor_pos=cursor_pos, + detail_level=detail_level, + ) + msg = self.session.msg('inspect_request', content) + self._dispatch_to_kernel(msg) + return msg['header']['msg_id'] + + def history(self, raw=True, output=False, hist_access_type='range', **kwds): + content = dict(raw=raw, output=output, + hist_access_type=hist_access_type, **kwds) + msg = self.session.msg('history_request', content) + self._dispatch_to_kernel(msg) + return msg['header']['msg_id'] + + def shutdown(self, restart=False): + # FIXME: What to do here? + raise NotImplementedError('Cannot shutdown in-process kernel') + + def kernel_info(self): + """Request kernel info.""" + msg = self.session.msg('kernel_info_request') + self._dispatch_to_kernel(msg) + return msg['header']['msg_id'] + + def comm_info(self, target_name=None): + """Request a dictionary of valid comms and their targets.""" + if target_name is None: + content = {} + else: + content = dict(target_name=target_name) + msg = self.session.msg('comm_info_request', content) + self._dispatch_to_kernel(msg) + return msg['header']['msg_id'] + + def input(self, string): + if self.kernel is None: + raise RuntimeError('Cannot send input reply. No kernel exists.') + self.kernel.raw_input_str = string + + def is_complete(self, code): + msg = self.session.msg('is_complete_request', {'code': code}) + self._dispatch_to_kernel(msg) + return msg['header']['msg_id'] + + def _dispatch_to_kernel(self, msg): + """ Send a message to the kernel and handle a reply. + """ + kernel = self.kernel + if kernel is None: + raise RuntimeError('Cannot send request. No kernel exists.') + + stream = DummySocket() + self.session.send(stream, msg) + msg_parts = stream.recv_multipart() + kernel.dispatch_shell(stream, msg_parts) + + idents, reply_msg = self.session.recv(stream, copy=False) + self.shell_channel.call_handlers_later(reply_msg) + + +#----------------------------------------------------------------------------- +# ABC Registration +#----------------------------------------------------------------------------- + +KernelClientABC.register(InProcessKernelClient) diff --git a/packages/python/yap_kernel/yap_kernel/inprocess/constants.py b/packages/python/yap_kernel/yap_kernel/inprocess/constants.py new file mode 100644 index 000000000..fe07a3406 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/inprocess/constants.py @@ -0,0 +1,8 @@ +"""Shared constants. +""" + +# Because inprocess communication is not networked, we can use a common Session +# key everywhere. This is not just the empty bytestring to avoid tripping +# certain security checks in the rest of Jupyter that assumes that empty keys +# are insecure. +INPROCESS_KEY = b'inprocess' diff --git a/packages/python/yap_kernel/yap_kernel/inprocess/ipkernel.py b/packages/python/yap_kernel/yap_kernel/inprocess/ipkernel.py new file mode 100644 index 000000000..7ea18407d --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/inprocess/ipkernel.py @@ -0,0 +1,315 @@ +"""An in-process kernel""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from contextlib import contextmanager +import logging +import sys + +from IPython.core.interactiveshell import InteractiveShellABC +from yap_kernel.jsonutil import json_clean +from traitlets import Any, Enum, Instance, List, Type, default +from yap_kernel.yapkernel import YAPKernel +from yap_kernel.zmqshell import ZMQInteractiveShell + +from .constants import INPROCESS_KEY +from .socket import DummySocket +from ..iostream import OutStream, BackgroundSocket, IOPubThread + +#----------------------------------------------------------------------------- +# Main kernel class +#----------------------------------------------------------------------------- + +class InProcessKernel(YAPKernel): + + #------------------------------------------------------------------------- + # InProcessKernel interface + #------------------------------------------------------------------------- + + # The frontends connected to this kernel. + frontends = List( + Instance('yap_kernel.inprocess.client.InProcessKernelClient', + allow_none=True) + ) + + # The GUI environment that the kernel is running under. This need not be + # specified for the normal operation for the kernel, but is required for + # IPython's GUI support (including pylab). The default is 'inline' because + # it is safe under all GUI toolkits. + gui = Enum(('tk', 'gtk', 'wx', 'qt', 'qt4', 'inline'), + default_value='inline') + + raw_input_str = Any() + stdout = Any() + stderr = Any() + + #------------------------------------------------------------------------- + # Kernel interface + #------------------------------------------------------------------------- + + shell_class = Type(allow_none=True) + shell_streams = List() + control_stream = Any() + _underlying_iopub_socket = Instance(DummySocket, ()) + iopub_thread = Instance(IOPubThread) + + @default('iopub_thread') + def _default_iopub_thread(self): + thread = IOPubThread(self._underlying_iopub_socket) + thread.start() + return thread + + iopub_socket = Instance(BackgroundSocket) + + @default('iopub_socket') + def _default_iopub_socket(self): + return self.iopub_thread.background_socket + + stdin_socket = Instance(DummySocket, ()) + + def __init__(self, **traits): + super(InProcessKernel, self).__init__(**traits) + + self._underlying_iopub_socket.observe(self._io_dispatch, names=['message_sent']) + pjoin = os.path.join + here = os.path.abspath(os.path.dirname(__file__)) + yap_lib_path = pjoin(here, "../yap4py/prolog" ) + yap_dll_path = pjoin(here, "../yap4py" ) + args = yap.YAPEngineArgs() + args.setYapLibDir(yap_dll_path) + args.setYapShareDir(yap_lib_path) + #args.setYapPrologBootFile(os.path.join(yap_lib_path."startup.yss")) + self.yapeng = yap.YAPEngine( args ) + self.q = None + self.yapeng.goal( use_module( library('yapi') ) ) + self.shell.run_cell = self.run_cell + self.shell.kernel = self + + def execute_request(self, stream, ident, parent): + """ Override for temporary IO redirection. """ + with self._redirected_io(): + super(InProcessKernel, self).execute_request(stream, ident, parent) + + def start(self): + """ Override registration of dispatchers for streams. """ + self.shell.exit_now = False + + def _abort_queue(self, stream): + """ The in-process kernel doesn't abort requests. """ + pass + + def _input_request(self, prompt, ident, parent, password=False): + # Flush output before making the request. + self.raw_input_str = None + sys.stderr.flush() + sys.stdout.flush() + + # Send the input request. + content = json_clean(dict(prompt=prompt, password=password)) + msg = self.session.msg(u'input_request', content, parent) + for frontend in self.frontends: + if frontend.session.session == parent['header']['session']: + frontend.stdin_channel.call_handlers(msg) + break + else: + logging.error('No frontend found for raw_input request') + return str() + + # Await a response. + while self.raw_input_str is None: + frontend.stdin_channel.process_events() + return self.raw_input_str + + #------------------------------------------------------------------------- + # Protected interface + #------------------------------------------------------------------------- + + @contextmanager + def _redirected_io(self): + """ Temporarily redirect IO to the kernel. + """ + sys_stdout, sys_stderr = sys.stdout, sys.stderr + sys.stdout, sys.stderr = self.stdout, self.stderr + yield + sys.stdout, sys.stderr = sys_stdout, sys_stderr + + #------ Trait change handlers -------------------------------------------- + + def _io_dispatch(self, change): + """ Called when a message is sent to the IO socket. + """ + ident, msg = self.session.recv(self.iopub_socket, copy=False) + for frontend in self.frontends: + frontend.iopub_channel.call_handlers(msg) + + #------ Trait initializers ----------------------------------------------- + + @default('log') + def _default_log(self): + return logging.getLogger(__name__) + + @default('session') + def _default_session(self): + from jupyter_client.session import Session + return Session(parent=self, key=INPROCESS_KEY) + + @default('shell_class') + def _default_shell_class(self): + return InProcessInteractiveShell + + @default('stdout') + def _default_stdout(self): + return OutStream(self.session, self.iopub_thread, u'stdout') + + @default('stderr') + def _default_stderr(self): + return OutStream(self.session, self.iopub_thread, u'stderr') + +#----------------------------------------------------------------------------- +# Interactive shell subclass +#----------------------------------------------------------------------------- + +class InProcessInteractiveShell(ZMQInteractiveShell): + + kernel = Instance('yap_kernel.inprocess.yapkernel.InProcessKernel', + allow_none=True) + + #------------------------------------------------------------------------- + # InteractiveShell interface + #------------------------------------------------------------------------- + + def enable_gui(self, gui=None): + """Enable GUI integration for the kernel.""" + from yap_kernel.eventloops import enable_gui + if not gui: + gui = self.kernel.gui + enable_gui(gui, kernel=self.kernel) + self.active_eventloop = gui + + + def enable_matplotlib(self, gui=None): + """Enable matplotlib integration for the kernel.""" + if not gui: + gui = self.kernel.gui + return super(InProcessInteractiveShell, self).enable_matplotlib(gui) + + def enable_pylab(self, gui=None, import_all=True, welcome_message=False): + """Activate pylab support at runtime.""" + if not gui: + gui = self.kernel.gui + return super(InProcessInteractiveShell, self).enable_pylab(gui, import_all, + welcome_message) + + + def closeq(self): + if self.q: + self.q.close() + self.q = None + + def run_cell(self, s, store_history=True, silent=False, shell_futures=True): + + """Run a complete IPython cell. + + Parameters + ---------- + raw_cell : str + The code (including IPython code such as %magic functions) to run. + store_history : bool + If True, the raw and translated cell will be stored in IPython's + history. For user code calling back into IPython's machinery, this + should be set to False. + silent : bool + If True, avoid side-effects, such as implicit displayhooks and + and logging. silent=True forces store_history=False. + shell_futures : bool + If True, the code will share future statements with the interactive + shell. It will both be affected by previous __future__ imports, and + any __future__ imports in the code will affect the shell. If False, + __future__ imports are not shared in either direction. + + Returns + ------- + result : :class:`ExecutionResult` + """ + + def numbervars(self, l): + return self.yapeng.fun(bindvars(l)) + + result = ExecutionResult() + + if (not s) or s.isspace(): + self.shell.last_execution_succeeded = True + return result + + if store_history: + result.execution_count = self.shell.execution_count + + def error_before_exec(value): + result.error_before_exec = value + self.shell.last_execution_succeeded = False + return result + + + if not self.q: + try: + self.q = self.yapeng.query(s) + except SyntaxError: + return error_before_exec( sys.exc_info()[1]) + + cell = s # cell has to exist so it can be stored/logged + + # Store raw and processed history + # if not silent: + # self.shell..logger.log(cell, s) + + has_raised = False + try: + #f = io.StringIO() + # with redirect_stdout(f): + run = self.q.next() + # print('{0}'.format(f.getvalue())) + # Execute the user code + if run: + myvs = self.numbervars(self.q.namedVars()) + if myvs: + for eq in myvs: + name = eq[0] + binding = eq[1] + if name != binding: + print(name + " = " + str(binding)) + else: + print("yes") + if self.q.deterministic(): + self.closeq() + else: + print("No (more) answers") + self.closeq() + except: + result.error_in_exec = sys.exc_info()[1] + # self.showtraceback() + has_raised = True + self.closeq() + + + self.shell.last_execution_succeeded = not has_raised + result.result = self.shell.last_execution_succeeded + print( self.q ) + # Reset this so later displayed values do not modify the + # ExecutionResult + # self.displayhook.exec_result = None + + #self.events.trigger('post_execute') + #if not silent: + # self.events.trigger('post_run_cell') + + if store_history: + # Write output to the database. Does nothing unless + # history output logging is enabled. + # self.history_manager.store_output(self.execution_count) + # Each cell is a *single* input, regardless of how many lines it has + self.shell.execution_count += 1 + + return result + +InteractiveShellABC.register(InProcessInteractiveShell) diff --git a/packages/python/yap_kernel/yap_kernel/inprocess/manager.py b/packages/python/yap_kernel/yap_kernel/inprocess/manager.py new file mode 100644 index 000000000..baca6150b --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/inprocess/manager.py @@ -0,0 +1,81 @@ +"""A kernel manager for in-process kernels.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from traitlets import Instance, DottedObjectName, default +from jupyter_client.managerabc import KernelManagerABC +from jupyter_client.manager import KernelManager +from jupyter_client.session import Session + +from .constants import INPROCESS_KEY + + +class InProcessKernelManager(KernelManager): + """A manager for an in-process kernel. + + This class implements the interface of + `jupyter_client.kernelmanagerabc.KernelManagerABC` and allows + (asynchronous) frontends to be used seamlessly with an in-process kernel. + + See `jupyter_client.kernelmanager.KernelManager` for docstrings. + """ + + # The kernel process with which the KernelManager is communicating. + kernel = Instance('yap_kernel.inprocess.yapkernel.InProcessKernel', + allow_none=True) + # the client class for KM.client() shortcut + client_class = DottedObjectName('yap_kernel.inprocess.BlockingInProcessKernelClient') + + @default('blocking_class') + def _default_blocking_class(self): + from .blocking import BlockingInProcessKernelClient + return BlockingInProcessKernelClient + + @default('session') + def _default_session(self): + # don't sign in-process messages + return Session(key=INPROCESS_KEY, parent=self) + + #-------------------------------------------------------------------------- + # Kernel management methods + #-------------------------------------------------------------------------- + + def start_kernel(self, **kwds): + from yap_kernel.inprocess.yapkernel import InProcessKernel + self.kernel = InProcessKernel(parent=self, session=self.session) + + def shutdown_kernel(self): + self.kernel.iopub_thread.stop() + self._kill_kernel() + + def restart_kernel(self, now=False, **kwds): + self.shutdown_kernel() + self.start_kernel(**kwds) + + @property + def has_kernel(self): + return self.kernel is not None + + def _kill_kernel(self): + self.kernel = None + + def interrupt_kernel(self): + raise NotImplementedError("Cannot interrupt in-process kernel.") + + def signal_kernel(self, signum): + raise NotImplementedError("Cannot signal in-process kernel.") + + def is_alive(self): + return self.kernel is not None + + def client(self, **kwargs): + kwargs['kernel'] = self.kernel + return super(InProcessKernelManager, self).client(**kwargs) + + +#----------------------------------------------------------------------------- +# ABC Registration +#----------------------------------------------------------------------------- + +KernelManagerABC.register(InProcessKernelManager) diff --git a/packages/python/yap_kernel/yap_kernel/inprocess/socket.py b/packages/python/yap_kernel/yap_kernel/inprocess/socket.py new file mode 100644 index 000000000..f7d78317d --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/inprocess/socket.py @@ -0,0 +1,64 @@ +""" Defines a dummy socket implementing (part of) the zmq.Socket interface. """ + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import abc +import warnings +try: + from queue import Queue # Py 3 +except ImportError: + from Queue import Queue # Py 2 + +import zmq + +from traitlets import HasTraits, Instance, Int +from ipython_genutils.py3compat import with_metaclass + +#----------------------------------------------------------------------------- +# Generic socket interface +#----------------------------------------------------------------------------- + +class SocketABC(with_metaclass(abc.ABCMeta, object)): + + @abc.abstractmethod + def recv_multipart(self, flags=0, copy=True, track=False): + raise NotImplementedError + + @abc.abstractmethod + def send_multipart(self, msg_parts, flags=0, copy=True, track=False): + raise NotImplementedError + + @classmethod + def register(cls, other_cls): + if other_cls is not DummySocket: + warnings.warn("SocketABC is deprecated since yap_kernel version 4.5.0.", + DeprecationWarning, stacklevel=2) + abc.ABCMeta.register(cls, other_cls) + +#----------------------------------------------------------------------------- +# Dummy socket class +#----------------------------------------------------------------------------- + +class DummySocket(HasTraits): + """ A dummy socket implementing (part of) the zmq.Socket interface. """ + + queue = Instance(Queue, ()) + message_sent = Int(0) # Should be an Event + context = Instance(zmq.Context) + def _context_default(self): + return zmq.Context.instance() + + #------------------------------------------------------------------------- + # Socket interface + #------------------------------------------------------------------------- + + def recv_multipart(self, flags=0, copy=True, track=False): + return self.queue.get_nowait() + + def send_multipart(self, msg_parts, flags=0, copy=True, track=False): + msg_parts = list(map(zmq.Message, msg_parts)) + self.queue.put_nowait(msg_parts) + self.message_sent += 1 + +SocketABC.register(DummySocket) diff --git a/packages/python/yap_kernel/yap_kernel/inprocess/tests/__init__.py b/packages/python/yap_kernel/yap_kernel/inprocess/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/packages/python/yap_kernel/yap_kernel/inprocess/tests/test_kernel.py b/packages/python/yap_kernel/yap_kernel/inprocess/tests/test_kernel.py new file mode 100644 index 000000000..0231c8688 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/inprocess/tests/test_kernel.py @@ -0,0 +1,76 @@ +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from __future__ import print_function + +import sys +import unittest + +from ipykernel.inprocess.blocking import BlockingInProcessKernelClient +from ipykernel.inprocess.manager import InProcessKernelManager +from ipykernel.inprocess.ipkernel import InProcessKernel +from ipykernel.tests.utils import assemble_output +from IPython.testing.decorators import skipif_not_matplotlib +from IPython.utils.io import capture_output +from ipython_genutils import py3compat + +if py3compat.PY3: + from io import StringIO +else: + from StringIO import StringIO + + +class InProcessKernelTestCase(unittest.TestCase): + + def setUp(self): + self.km = InProcessKernelManager() + self.km.start_kernel() + self.kc = self.km.client() + self.kc.start_channels() + self.kc.wait_for_ready() + + @skipif_not_matplotlib + def test_pylab(self): + """Does %pylab work in the in-process kernel?""" + kc = self.kc + kc.execute('%pylab') + out, err = assemble_output(kc.iopub_channel) + self.assertIn('matplotlib', out) + + def test_raw_input(self): + """ Does the in-process kernel handle raw_input correctly? + """ + io = StringIO('foobar\n') + sys_stdin = sys.stdin + sys.stdin = io + try: + if py3compat.PY3: + self.kc.execute('x = input()') + else: + self.kc.execute('x = raw_input()') + finally: + sys.stdin = sys_stdin + self.assertEqual(self.km.kernel.shell.user_ns.get('x'), 'foobar') + + def test_stdout(self): + """ Does the in-process kernel correctly capture IO? + """ + kernel = InProcessKernel() + + with capture_output() as io: + kernel.shell.run_cell('print("foo")') + self.assertEqual(io.stdout, 'foo\n') + + kc = BlockingInProcessKernelClient(kernel=kernel, session=kernel.session) + kernel.frontends.append(kc) + kc.execute('print("bar")') + out, err = assemble_output(kc.iopub_channel) + self.assertEqual(out, 'bar\n') + + def test_getpass_stream(self): + "Tests that kernel getpass accept the stream parameter" + kernel = InProcessKernel() + kernel._allow_stdin = True + kernel._input_request = lambda *args, **kwargs : None + + kernel.getpass(stream='non empty') diff --git a/packages/python/yap_kernel/yap_kernel/inprocess/tests/test_kernelmanager.py b/packages/python/yap_kernel/yap_kernel/inprocess/tests/test_kernelmanager.py new file mode 100644 index 000000000..f3e44364b --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/inprocess/tests/test_kernelmanager.py @@ -0,0 +1,115 @@ +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from __future__ import print_function + +import unittest + +from ipykernel.inprocess.blocking import BlockingInProcessKernelClient +from ipykernel.inprocess.manager import InProcessKernelManager + +#----------------------------------------------------------------------------- +# Test case +#----------------------------------------------------------------------------- + +class InProcessKernelManagerTestCase(unittest.TestCase): + + def setUp(self): + self.km = InProcessKernelManager() + + def tearDown(self): + if self.km.has_kernel: + self.km.shutdown_kernel() + + def test_interface(self): + """ Does the in-process kernel manager implement the basic KM interface? + """ + km = self.km + self.assert_(not km.has_kernel) + + km.start_kernel() + self.assert_(km.has_kernel) + self.assert_(km.kernel is not None) + + kc = km.client() + self.assert_(not kc.channels_running) + + kc.start_channels() + self.assert_(kc.channels_running) + + old_kernel = km.kernel + km.restart_kernel() + self.assertIsNotNone(km.kernel) + self.assertNotEquals(km.kernel, old_kernel) + + km.shutdown_kernel() + self.assert_(not km.has_kernel) + + self.assertRaises(NotImplementedError, km.interrupt_kernel) + self.assertRaises(NotImplementedError, km.signal_kernel, 9) + + kc.stop_channels() + self.assert_(not kc.channels_running) + + def test_execute(self): + """ Does executing code in an in-process kernel work? + """ + km = self.km + km.start_kernel() + kc = km.client() + kc.start_channels() + kc.wait_for_ready() + kc.execute('foo = 1') + self.assertEquals(km.kernel.shell.user_ns['foo'], 1) + + def test_complete(self): + """ Does requesting completion from an in-process kernel work? + """ + km = self.km + km.start_kernel() + kc = km.client() + kc.start_channels() + kc.wait_for_ready() + km.kernel.shell.push({'my_bar': 0, 'my_baz': 1}) + kc.complete('my_ba', 5) + msg = kc.get_shell_msg() + self.assertEqual(msg['header']['msg_type'], 'complete_reply') + self.assertEqual(sorted(msg['content']['matches']), + ['my_bar', 'my_baz']) + + def test_inspect(self): + """ Does requesting object information from an in-process kernel work? + """ + km = self.km + km.start_kernel() + kc = km.client() + kc.start_channels() + kc.wait_for_ready() + km.kernel.shell.user_ns['foo'] = 1 + kc.inspect('foo') + msg = kc.get_shell_msg() + self.assertEqual(msg['header']['msg_type'], 'inspect_reply') + content = msg['content'] + assert content['found'] + text = content['data']['text/plain'] + self.assertIn('int', text) + + def test_history(self): + """ Does requesting history from an in-process kernel work? + """ + km = self.km + km.start_kernel() + kc = km.client() + kc.start_channels() + kc.wait_for_ready() + kc.execute('1') + kc.history(hist_access_type='tail', n=1) + msg = kc.shell_channel.get_msgs()[-1] + self.assertEquals(msg['header']['msg_type'], 'history_reply') + history = msg['content']['history'] + self.assertEquals(len(history), 1) + self.assertEquals(history[0][2], '1') + + +if __name__ == '__main__': + unittest.main() diff --git a/packages/python/yap_kernel/yap_kernel/interactiveshell.py b/packages/python/yap_kernel/yap_kernel/interactiveshell.py new file mode 100644 index 000000000..1899e8232 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/interactiveshell.py @@ -0,0 +1,251 @@ +# -*- coding: utf-8 -*- +"""YAP Stuff for Main IPython class.""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2001 Janko Hauser +# Copyright (C) 2001-2007 Fernando Perez. +# Copyright (C) 2008-2011 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +from __future__ import absolute_import, print_function + +import __future__ +import abc +import ast +import atexit +import functools +import os +import re +import runpy +import signal + +import sys +import tempfile +import traceback +import types +import subprocess +import warnings +import yap4py.yapi +import yap +from io import open as io_open + +from pickleshare import PickleShareDB + +from traitlets.config.configurable import SingletonConfigurable +from IPython.core import oinspect +from IPython.core import magic +from IPython.core import page +from IPython.core import prefilter +from IPython.core import shadowns +from IPython.core import ultratb +from IPython.core import interactiveshell +from IPython.core.alias import Alias, AliasManager +from IPython.core.autocall import ExitAutocall +from IPython.core.builtin_trap import BuiltinTrap +from IPython.core.events import EventManager, available_events +from IPython.core.compilerop import CachingCompiler, check_linecache_ipython +from IPython.core.debugger import Pdb +from IPython.core.display_trap import DisplayTrap +from IPython.core.displayhook import DisplayHook +from IPython.core.displaypub import DisplayPublisher +from IPython.core.error import InputRejected, UsageError +from IPython.core.extensions import ExtensionManager +from IPython.core.formatters import DisplayFormatter +from IPython.core.history import HistoryManager +from IPython.core.inputsplitter import ESC_MAGIC, ESC_MAGIC2 +from IPython.core.logger import Logger +from IPython.core.macro import Macro +from IPython.core.payload import PayloadManager +from IPython.core.prefilter import PrefilterManager +from IPython.core.profiledir import ProfileDir +from IPython.core.usage import default_banner +from IPython.core.interactiveshell import InteractiveShellABC, InteractiveShell, ExecutionResult +from IPython.testing.skipdoctest import skip_doctest +from IPython.utils import PyColorize +from IPython.utils import io +from IPython.utils import py3compat +from IPython.utils import openpy +from IPython.utils.decorators import undoc +from IPython.utils.io import ask_yes_no +from IPython.utils.ipstruct import Struct +from IPython.paths import get_ipython_dir +from IPython.utils.path import get_home_dir, get_py_filename, ensure_dir_exists +from IPython.utils.process import system, getoutput +from IPython.utils.py3compat import (builtin_mod, unicode_type, string_types, + with_metaclass, iteritems) +from IPython.utils.strdispatch import StrDispatch +from IPython.utils.syspathcontext import prepended_to_syspath +from IPython.utils.text import format_screen, LSString, SList, DollarFormatter +from IPython.utils.tempdir import TemporaryDirectory +from traitlets import ( + Integer, Bool, CaselessStrEnum, Enum, List, Dict, Unicode, Instance, Type, + observe, default, +) +from warnings import warn +from logging import error +from collections import namedtuple + +use_module = namedtuple('use_module', 'file') +bindvars = namedtuple('bindvars', 'list') +library = namedtuple('library', 'list') +v = namedtuple('_', 'slot') +load_fieos = namedtuple('load_files', 'file ofile args') + + +class YAPInteraction: + """An enhanced, interactive shell for YAP.""" + + def __init__(self, shell, **kwargs): + try: + if self.yapeng: + return + except Exception: + pass + pjoin = os.path.join + here = os.path.abspath(os.path.dirname(__file__)) + yap_lib_path = pjoin(here, "../yap4py/prolog") + yap_dll_path = pjoin(here, "../yap4py") + self.args = yap.YAPEngineArgs() + self.args.setYapLibDir(yap_dll_path) + self.args.setYapShareDir(yap_lib_path) + # args.setYapPrologBootFile(os.path.join(yap_lib_path."startup.yss")) + self.yapeng = yap.YAPEngine(self.args) + self.q = None + self.yapeng.goal(use_module(library('yapi'))) + self.shell = shell + self.run = False + + def eng(self): + return self.yapeng + + def closeq(self): + if self.q: + self.q.close() + self.q = None + + def numbervars(self, l): + return self.yapeng.fun(bindvars(l)) + + def run_cell(self, s, store_history=True, silent=False, + shell_futures=True): + """Run a complete IPython cell. + + Parameters + ---------- + raw_cell : str + The code (including IPython code such as + %magic functions) to run. + store_history : bool + If True, the raw and translated cell will be stored in IPython's + history. For user code calling back into + IPython's machinery, this + should be set to False. + silent : bool + If True, avoid side-effects, such as implicit displayhooks and + and logging. silent=True forces store_history=False. + shell_futures : bool + If True, the code will share future statements with the interactive + shell. It will both be affected by previous __future__ imports, and + any __future__ imports in the code will affect the shell. If False, + __future__ imports are not shared in either direction. + + Returns + ------- + result : :class:`ExecutionResult` + """ + + result = ExecutionResult() + + if store_history: + result.execution_count = self.shell.execution_count + + def error_before_exec(value): + result.error_before_exec = value + self.shell.last_execution_succeeded = False + return result + + # inspect for ?? in the text + st = s.strip('\n\j\r\t ') + if (st): + (p0, pm, pf) = st.rpartition('??') + if pm == '??': + if pf.isdigit(p): + maxits = int(pf)*2 + s = p0 + elif pf.isspace(p): + maxits = 1 + s = p0 + else: + s = st + maxits = 2 + else: + # business as usual + s = st + maxits = 2 + elif st == '': + # next. please + maxis = 2 + self.qclose() + + if not self.q: + try: + if s: + self.q = self.yapeng.query(s) + else: + return + except SyntaxError: + return error_before_exec(sys.exc_info()[1]) + + cell = s # cell has to exist so it can be stored/logged + # Store raw and processed history + # if not silent: + # self.shell..logger.log(cell, s) + has_raised = False + self.run = True + try: + while self.run and maxits != 0: + # f = io.StringIO() + # with redirect_stdout(f): + self.run = self.q.next() + # print('{0}'.format(f.getvalue())) + # Execute the user code + if self.run: + myvs = self.numbervars(self.q.namedVars()) + if myvs: + for eq in myvs: + name = eq[0] + binding = eq[1] + if name != binding: + print(name + " = " + str(binding)) + else: + print("yes") + if self.q.deterministic(): + self.closeq() + self.run = False + self.q = None + else: + maxits -= 2 + else: + print("No (more) answers") + self.closeq() + self.run = False + except Exception: + result.error_in_exec = sys.exc_info()[1] + # self.showtraceback() + has_raised = True + self.closeq() + + self.shell.last_execution_succeeded = not has_raised + result.result = self.shell.last_execution_succeeded + # Reset this so later displayed values do not modify the + # ExecutionResult + # self.displayhook.exec_result = None + + self.events.trigger('post_execute') + if not silent: + self.events.trigger('post_self.run_cell') + + return result diff --git a/packages/python/yap_kernel/yap_kernel/iostream.py b/packages/python/yap_kernel/yap_kernel/iostream.py new file mode 100644 index 000000000..4e99e9676 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/iostream.py @@ -0,0 +1,383 @@ +# coding: utf-8 +"""Wrappers for forwarding stdout/stderr over zmq""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from __future__ import print_function +import atexit +from binascii import b2a_hex +import os +import sys +import threading +import warnings +from io import StringIO, UnsupportedOperation, TextIOBase + +import zmq +from zmq.eventloop.ioloop import IOLoop +from zmq.eventloop.zmqstream import ZMQStream + +from jupyter_client.session import extract_header + +from ipython_genutils import py3compat +from ipython_genutils.py3compat import unicode_type + +#----------------------------------------------------------------------------- +# Globals +#----------------------------------------------------------------------------- + +MASTER = 0 +CHILD = 1 + +#----------------------------------------------------------------------------- +# IO classes +#----------------------------------------------------------------------------- + +class IOPubThread(object): + """An object for sending IOPub messages in a background thread + + Prevents a blocking main thread from delaying output from threads. + + IOPubThread(pub_socket).background_socket is a Socket-API-providing object + whose IO is always run in a thread. + """ + + def __init__(self, socket, pipe=False): + """Create IOPub thread + + Parameters + ---------- + + socket: zmq.PUB Socket + the socket on which messages will be sent. + pipe: bool + Whether this process should listen for IOPub messages + piped from subprocesses. + """ + self.socket = socket + self.background_socket = BackgroundSocket(self) + self._master_pid = os.getpid() + self._pipe_flag = pipe + self.io_loop = IOLoop() + if pipe: + self._setup_pipe_in() + self._local = threading.local() + self._events = {} + self._setup_event_pipe() + self.thread = threading.Thread(target=self._thread_main) + self.thread.daemon = True + + def _thread_main(self): + """The inner loop that's actually run in a thread""" + self.io_loop.start() + self.io_loop.close(all_fds=True) + + def _setup_event_pipe(self): + """Create the PULL socket listening for events that should fire in this thread.""" + ctx = self.socket.context + pipe_in = ctx.socket(zmq.PULL) + pipe_in.linger = 0 + + _uuid = b2a_hex(os.urandom(16)).decode('ascii') + iface = self._event_interface = 'inproc://%s' % _uuid + pipe_in.bind(iface) + self._event_puller = ZMQStream(pipe_in, self.io_loop) + self._event_puller.on_recv(self._handle_event) + + @property + def _event_pipe(self): + """thread-local event pipe for signaling events that should be processed in the thread""" + try: + event_pipe = self._local.event_pipe + except AttributeError: + # new thread, new event pipe + ctx = self.socket.context + event_pipe = ctx.socket(zmq.PUSH) + event_pipe.linger = 0 + event_pipe.connect(self._event_interface) + self._local.event_pipe = event_pipe + return event_pipe + + def _handle_event(self, msg): + """Handle an event on the event pipe""" + event_id = msg[0] + event_f = self._events.pop(event_id) + event_f() + + def _setup_pipe_in(self): + """setup listening pipe for IOPub from forked subprocesses""" + ctx = self.socket.context + + # use UUID to authenticate pipe messages + self._pipe_uuid = os.urandom(16) + + pipe_in = ctx.socket(zmq.PULL) + pipe_in.linger = 0 + + try: + self._pipe_port = pipe_in.bind_to_random_port("tcp://127.0.0.1") + except zmq.ZMQError as e: + warnings.warn("Couldn't bind IOPub Pipe to 127.0.0.1: %s" % e + + "\nsubprocess output will be unavailable." + ) + self._pipe_flag = False + pipe_in.close() + return + self._pipe_in = ZMQStream(pipe_in, self.io_loop) + self._pipe_in.on_recv(self._handle_pipe_msg) + + def _handle_pipe_msg(self, msg): + """handle a pipe message from a subprocess""" + if not self._pipe_flag or not self._is_master_process(): + return + if msg[0] != self._pipe_uuid: + print("Bad pipe message: %s", msg, file=sys.__stderr__) + return + self.send_multipart(msg[1:]) + + def _setup_pipe_out(self): + # must be new context after fork + ctx = zmq.Context() + pipe_out = ctx.socket(zmq.PUSH) + pipe_out.linger = 3000 # 3s timeout for pipe_out sends before discarding the message + pipe_out.connect("tcp://127.0.0.1:%i" % self._pipe_port) + return ctx, pipe_out + + def _is_master_process(self): + return os.getpid() == self._master_pid + + def _check_mp_mode(self): + """check for forks, and switch to zmq pipeline if necessary""" + if not self._pipe_flag or self._is_master_process(): + return MASTER + else: + return CHILD + + def start(self): + """Start the IOPub thread""" + self.thread.start() + # make sure we don't prevent process exit + # I'm not sure why setting daemon=True above isn't enough, but it doesn't appear to be. + atexit.register(self.stop) + + def stop(self): + """Stop the IOPub thread""" + if not self.thread.is_alive(): + return + self.io_loop.add_callback(self.io_loop.stop) + self.thread.join() + if hasattr(self._local, 'event_pipe'): + self._local.event_pipe.close() + + def close(self): + self.socket.close() + self.socket = None + + @property + def closed(self): + return self.socket is None + + def schedule(self, f): + """Schedule a function to be called in our IO thread. + + If the thread is not running, call immediately. + """ + if self.thread.is_alive(): + event_id = os.urandom(16) + while event_id in self._events: + event_id = os.urandom(16) + self._events[event_id] = f + self._event_pipe.send(event_id) + else: + f() + + def send_multipart(self, *args, **kwargs): + """send_multipart schedules actual zmq send in my thread. + + If my thread isn't running (e.g. forked process), send immediately. + """ + self.schedule(lambda : self._really_send(*args, **kwargs)) + + def _really_send(self, msg, *args, **kwargs): + """The callback that actually sends messages""" + mp_mode = self._check_mp_mode() + + if mp_mode != CHILD: + # we are master, do a regular send + self.socket.send_multipart(msg, *args, **kwargs) + else: + # we are a child, pipe to master + # new context/socket for every pipe-out + # since forks don't teardown politely, use ctx.term to ensure send has completed + ctx, pipe_out = self._setup_pipe_out() + pipe_out.send_multipart([self._pipe_uuid] + msg, *args, **kwargs) + pipe_out.close() + ctx.term() + + +class BackgroundSocket(object): + """Wrapper around IOPub thread that provides zmq send[_multipart]""" + io_thread = None + + def __init__(self, io_thread): + self.io_thread = io_thread + + def __getattr__(self, attr): + """Wrap socket attr access for backward-compatibility""" + if attr.startswith('__') and attr.endswith('__'): + # don't wrap magic methods + super(BackgroundSocket, self).__getattr__(attr) + if hasattr(self.io_thread.socket, attr): + warnings.warn("Accessing zmq Socket attribute %s on BackgroundSocket" % attr, + DeprecationWarning, stacklevel=2) + return getattr(self.io_thread.socket, attr) + super(BackgroundSocket, self).__getattr__(attr) + + def __setattr__(self, attr, value): + if attr == 'io_thread' or (attr.startswith('__' and attr.endswith('__'))): + super(BackgroundSocket, self).__setattr__(attr, value) + else: + warnings.warn("Setting zmq Socket attribute %s on BackgroundSocket" % attr, + DeprecationWarning, stacklevel=2) + setattr(self.io_thread.socket, attr, value) + + def send(self, msg, *args, **kwargs): + return self.send_multipart([msg], *args, **kwargs) + + def send_multipart(self, *args, **kwargs): + """Schedule send in IO thread""" + return self.io_thread.send_multipart(*args, **kwargs) + + +class OutStream(TextIOBase): + """A file like object that publishes the stream to a 0MQ PUB socket. + + Output is handed off to an IO Thread + """ + + # The time interval between automatic flushes, in seconds. + flush_interval = 0.2 + topic = None + encoding = 'UTF-8' + + def __init__(self, session, pub_thread, name, pipe=None): + if pipe is not None: + warnings.warn("pipe argument to OutStream is deprecated and ignored", + DeprecationWarning) + # This is necessary for compatibility with Python built-in streams + self.session = session + if not isinstance(pub_thread, IOPubThread): + # Backward-compat: given socket, not thread. Wrap in a thread. + warnings.warn("OutStream should be created with IOPubThread, not %r" % pub_thread, + DeprecationWarning, stacklevel=2) + pub_thread = IOPubThread(pub_thread) + pub_thread.start() + self.pub_thread = pub_thread + self.name = name + self.topic = b'stream.' + py3compat.cast_bytes(name) + self.parent_header = {} + self._master_pid = os.getpid() + self._flush_pending = False + self._io_loop = pub_thread.io_loop + self._new_buffer() + + def _is_master_process(self): + return os.getpid() == self._master_pid + + def set_parent(self, parent): + self.parent_header = extract_header(parent) + + def close(self): + self.pub_thread = None + + @property + def closed(self): + return self.pub_thread is None + + def _schedule_flush(self): + """schedule a flush in the IO thread + + call this on write, to indicate that flush should be called soon. + """ + if self._flush_pending: + return + self._flush_pending = True + + # add_timeout has to be handed to the io thread via event pipe + def _schedule_in_thread(): + self._io_loop.call_later(self.flush_interval, self._flush) + self.pub_thread.schedule(_schedule_in_thread) + + def flush(self): + """trigger actual zmq send + + send will happen in the background thread + """ + if self.pub_thread.thread.is_alive(): + # wait for flush to actually get through: + self.pub_thread.schedule(self._flush) + evt = threading.Event() + self.pub_thread.schedule(evt.set) + evt.wait() + else: + self._flush() + + def _flush(self): + """This is where the actual send happens. + + _flush should generally be called in the IO thread, + unless the thread has been destroyed (e.g. forked subprocess). + """ + self._flush_pending = False + data = self._flush_buffer() + if data: + # FIXME: this disables Session's fork-safe check, + # since pub_thread is itself fork-safe. + # There should be a better way to do this. + self.session.pid = os.getpid() + content = {u'name':self.name, u'text':data} + self.session.send(self.pub_thread, u'stream', content=content, + parent=self.parent_header, ident=self.topic) + + def write(self, string): + if self.pub_thread is None: + raise ValueError('I/O operation on closed file') + else: + # Make sure that we're handling unicode + if not isinstance(string, unicode_type): + string = string.decode(self.encoding, 'replace') + + is_child = (not self._is_master_process()) + # only touch the buffer in the IO thread to avoid races + self.pub_thread.schedule(lambda : self._buffer.write(string)) + if is_child: + # newlines imply flush in subprocesses + # mp.Pool cannot be trusted to flush promptly (or ever), + # and this helps. + if '\n' in string: + self.flush() + else: + self._schedule_flush() + + def writelines(self, sequence): + if self.pub_thread is None: + raise ValueError('I/O operation on closed file') + else: + for string in sequence: + self.write(string) + + def _flush_buffer(self): + """clear the current buffer and return the current buffer data. + + This should only be called in the IO thread. + """ + data = u'' + if self._buffer is not None: + buf = self._buffer + self._new_buffer() + data = buf.getvalue() + buf.close() + return data + + def _new_buffer(self): + self._buffer = StringIO() diff --git a/packages/python/yap_kernel/yap_kernel/jsonutil.py b/packages/python/yap_kernel/yap_kernel/jsonutil.py new file mode 100644 index 000000000..3121e53cc --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/jsonutil.py @@ -0,0 +1,173 @@ +"""Utilities to manipulate JSON objects.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import math +import re +import types +from datetime import datetime +import numbers + +try: + # base64.encodestring is deprecated in Python 3.x + from base64 import encodebytes +except ImportError: + # Python 2.x + from base64 import encodestring as encodebytes + +from ipython_genutils import py3compat +from ipython_genutils.py3compat import unicode_type, iteritems +from ipython_genutils.encoding import DEFAULT_ENCODING +next_attr_name = '__next__' if py3compat.PY3 else 'next' + +#----------------------------------------------------------------------------- +# Globals and constants +#----------------------------------------------------------------------------- + +# timestamp formats +ISO8601 = "%Y-%m-%dT%H:%M:%S.%f" +ISO8601_PAT=re.compile(r"^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(\.\d{1,6})?Z?([\+\-]\d{2}:?\d{2})?$") + +# holy crap, strptime is not threadsafe. +# Calling it once at import seems to help. +datetime.strptime("1", "%d") + +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- + + +# constants for identifying png/jpeg data +PNG = b'\x89PNG\r\n\x1a\n' +# front of PNG base64-encoded +PNG64 = b'iVBORw0KG' +JPEG = b'\xff\xd8' +# front of JPEG base64-encoded +JPEG64 = b'/9' +# front of PDF base64-encoded +PDF64 = b'JVBER' + +def encode_images(format_dict): + """b64-encodes images in a displaypub format dict + + Perhaps this should be handled in json_clean itself? + + Parameters + ---------- + + format_dict : dict + A dictionary of display data keyed by mime-type + + Returns + ------- + + format_dict : dict + A copy of the same dictionary, + but binary image data ('image/png', 'image/jpeg' or 'application/pdf') + is base64-encoded. + + """ + encoded = format_dict.copy() + + pngdata = format_dict.get('image/png') + if isinstance(pngdata, bytes): + # make sure we don't double-encode + if not pngdata.startswith(PNG64): + pngdata = encodebytes(pngdata) + encoded['image/png'] = pngdata.decode('ascii') + + jpegdata = format_dict.get('image/jpeg') + if isinstance(jpegdata, bytes): + # make sure we don't double-encode + if not jpegdata.startswith(JPEG64): + jpegdata = encodebytes(jpegdata) + encoded['image/jpeg'] = jpegdata.decode('ascii') + + pdfdata = format_dict.get('application/pdf') + if isinstance(pdfdata, bytes): + # make sure we don't double-encode + if not pdfdata.startswith(PDF64): + pdfdata = encodebytes(pdfdata) + encoded['application/pdf'] = pdfdata.decode('ascii') + + return encoded + + +def json_clean(obj): + """Clean an object to ensure it's safe to encode in JSON. + + Atomic, immutable objects are returned unmodified. Sets and tuples are + converted to lists, lists are copied and dicts are also copied. + + Note: dicts whose keys could cause collisions upon encoding (such as a dict + with both the number 1 and the string '1' as keys) will cause a ValueError + to be raised. + + Parameters + ---------- + obj : any python object + + Returns + ------- + out : object + + A version of the input which will not cause an encoding error when + encoded as JSON. Note that this function does not *encode* its inputs, + it simply sanitizes it so that there will be no encoding errors later. + + """ + # types that are 'atomic' and ok in json as-is. + atomic_ok = (unicode_type, type(None)) + + # containers that we need to convert into lists + container_to_list = (tuple, set, types.GeneratorType) + + # Since bools are a subtype of Integrals, which are a subtype of Reals, + # we have to check them in that order. + + if isinstance(obj, bool): + return obj + + if isinstance(obj, numbers.Integral): + # cast int to int, in case subclasses override __str__ (e.g. boost enum, #4598) + return int(obj) + + if isinstance(obj, numbers.Real): + # cast out-of-range floats to their reprs + if math.isnan(obj) or math.isinf(obj): + return repr(obj) + return float(obj) + + if isinstance(obj, atomic_ok): + return obj + + if isinstance(obj, bytes): + return obj.decode(DEFAULT_ENCODING, 'replace') + + if isinstance(obj, container_to_list) or ( + hasattr(obj, '__iter__') and hasattr(obj, next_attr_name)): + obj = list(obj) + + if isinstance(obj, list): + return [json_clean(x) for x in obj] + + if isinstance(obj, dict): + # First, validate that the dict won't lose data in conversion due to + # key collisions after stringification. This can happen with keys like + # True and 'true' or 1 and '1', which collide in JSON. + nkeys = len(obj) + nkeys_collapsed = len(set(map(unicode_type, obj))) + if nkeys != nkeys_collapsed: + raise ValueError('dict cannot be safely converted to JSON: ' + 'key collision would lead to dropped values') + # If all OK, proceed by making the new dict that will be json-safe + out = {} + for k,v in iteritems(obj): + out[unicode_type(k)] = json_clean(v) + return out + if isinstance(obj, datetime): + return obj.strftime(ISO8601) + + # we don't understand it, it's probably an unserializable object + raise ValueError("Can't clean for JSON: %r" % obj) diff --git a/packages/python/yap_kernel/yap_kernel/kernelapp.py b/packages/python/yap_kernel/yap_kernel/kernelapp.py new file mode 100644 index 000000000..efa83b7dd --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/kernelapp.py @@ -0,0 +1,491 @@ +"""An Application for launching a kernel""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from __future__ import print_function + +import atexit +import os +import sys +import signal +import traceback +import logging + +from tornado import ioloop +import zmq +from zmq.eventloop import ioloop as zmq_ioloop +from zmq.eventloop.zmqstream import ZMQStream + +from IPython.core.application import ( + BaseIPythonApplication, base_flags, base_aliases, catch_config_error +) +from IPython.core.profiledir import ProfileDir +from IPython.core.shellapp import ( + InteractiveShellApp, shell_flags, shell_aliases +) +from IPython.utils import io +from ipython_genutils.path import filefind, ensure_dir_exists +from traitlets import ( + Any, Instance, Dict, Unicode, Integer, Bool, DottedObjectName, Type, default +) +from ipython_genutils.importstring import import_item +from jupyter_core.paths import jupyter_runtime_dir +from jupyter_client import write_connection_file +from jupyter_client.connect import ConnectionFileMixin + +# local imports +from .iostream import IOPubThread +from .heartbeat import Heartbeat +from .yapkernel import YAPKernel +from .parentpoller import ParentPollerUnix, ParentPollerWindows +from jupyter_client.session import ( + Session, session_flags, session_aliases, +) +from .zmqshell import ZMQInteractiveShell + +#----------------------------------------------------------------------------- +# Flags and Aliases +#----------------------------------------------------------------------------- + +kernel_aliases = dict(base_aliases) +kernel_aliases.update({ + 'ip' : 'YAPKernelApp.ip', + 'hb' : 'YAPKernelApp.hb_port', + 'shell' : 'YAPKernelApp.shell_port', + 'iopub' : 'YAPKernelApp.iopub_port', + 'stdin' : 'YAPKernelApp.stdin_port', + 'control' : 'YAPKernelApp.control_port', + 'f' : 'YAPKernelApp.connection_file', + 'transport': 'YAPKernelApp.transport', +}) + +kernel_flags = dict(base_flags) +kernel_flags.update({ + 'no-stdout' : ( + {'YAPKernelApp' : {'no_stdout' : True}}, + "redirect stdout to the null device"), + 'no-stderr' : ( + {'YAPKernelApp' : {'no_stderr' : True}}, + "redirect stderr to the null device"), + 'pylab' : ( + {'YAPKernelApp' : {'pylab' : 'auto'}}, + """Pre-load matplotlib and numpy for interactive use with + the default matplotlib backend."""), +}) + +# inherit flags&aliases for any IPython shell apps +kernel_aliases.update(shell_aliases) +kernel_flags.update(shell_flags) + +# inherit flags&aliases for Sessions +kernel_aliases.update(session_aliases) +kernel_flags.update(session_flags) + +_ctrl_c_message = """\ +NOTE: When using the `ipython kernel` entry point, Ctrl-C will not work. + +To exit, you will have to explicitly quit this process, by either sending +"quit" from a client, or using Ctrl-\\ in UNIX-like environments. + +To read more about this, see https://github.com/ipython/ipython/issues/2049 + +""" + +#----------------------------------------------------------------------------- +# Application class for starting an IPython Kernel +#----------------------------------------------------------------------------- + +class YAPKernelApp(BaseIPythonApplication, InteractiveShellApp, + ConnectionFileMixin): + name='YAP Kernel' + aliases = Dict(kernel_aliases) + flags = Dict(kernel_flags) + classes = [YAPKernel, ZMQInteractiveShell, ProfileDir, Session] + # the kernel class, as an importstring + kernel_class = Type('yap_kernel.yapkernel.YAPKernel', + klass='yap_kernel.yapkernel.YAPKernel', + help="""The Kernel subclass to be used. + + This should allow easy re-use of the YAPKernelApp entry point + to configure and launch kernels other than IPython's own. + """).tag(config=True) + kernel = Any() + poller = Any() # don't restrict this even though current pollers are all Threads + heartbeat = Instance(Heartbeat, allow_none=True) + ports = Dict() + + subcommands = { + 'install': ( + 'yap_kernel.kernelspec.InstallYAPKernelSpecApp', + 'Install the YAP kernel' + ), + } + + # connection info: + connection_dir = Unicode() + + @default('connection_dir') + def _default_connection_dir(self): + return jupyter_runtime_dir() + + @property + def abs_connection_file(self): + if os.path.basename(self.connection_file) == self.connection_file: + return os.path.join(self.connection_dir, self.connection_file) + else: + return self.connection_file + + # streams, etc. + no_stdout = Bool(False, help="redirect stdout to the null device").tag(config=True) + no_stderr = Bool(False, help="redirect stderr to the null device").tag(config=True) + outstream_class = DottedObjectName('yap_kernel.iostream.OutStream', + help="The importstring for the OutStream factory").tag(config=True) + displayhook_class = DottedObjectName('yap_kernel.displayhook.ZMQDisplayHook', + help="The importstring for the DisplayHook factory").tag(config=True) + + # polling + parent_handle = Integer(int(os.environ.get('JPY_PARENT_PID') or 0), + help="""kill this process if its parent dies. On Windows, the argument + specifies the HANDLE of the parent process, otherwise it is simply boolean. + """).tag(config=True) + interrupt = Integer(int(os.environ.get('JPY_INTERRUPT_EVENT') or 0), + help="""ONLY USED ON WINDOWS + Interrupt this process when the parent is signaled. + """).tag(config=True) + + def init_crash_handler(self): + sys.excepthook = self.excepthook + + def excepthook(self, etype, evalue, tb): + # write uncaught traceback to 'real' stderr, not zmq-forwarder + traceback.print_exception(etype, evalue, tb, file=sys.__stderr__) + + def init_poller(self): + if sys.platform == 'win32': + if self.interrupt or self.parent_handle: + self.poller = ParentPollerWindows(self.interrupt, self.parent_handle) + elif self.parent_handle and self.parent_handle != 1: + # PID 1 (init) is special and will never go away, + # only be reassigned. + # Parent polling doesn't work if ppid == 1 to start with. + self.poller = ParentPollerUnix() + + def _bind_socket(self, s, port): + iface = '%s://%s' % (self.transport, self.ip) + if self.transport == 'tcp': + if port <= 0: + port = s.bind_to_random_port(iface) + else: + s.bind("tcp://%s:%i" % (self.ip, port)) + elif self.transport == 'ipc': + if port <= 0: + port = 1 + path = "%s-%i" % (self.ip, port) + while os.path.exists(path): + port = port + 1 + path = "%s-%i" % (self.ip, port) + else: + path = "%s-%i" % (self.ip, port) + s.bind("ipc://%s" % path) + return port + + def write_connection_file(self): + """write connection info to JSON file""" + cf = self.abs_connection_file + self.log.debug("Writing connection file: %s", cf) + write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport, + shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port, + iopub_port=self.iopub_port, control_port=self.control_port) + + def cleanup_connection_file(self): + cf = self.abs_connection_file + self.log.debug("Cleaning up connection file: %s", cf) + try: + os.remove(cf) + except (IOError, OSError): + pass + + self.cleanup_ipc_files() + + def init_connection_file(self): + if not self.connection_file: + self.connection_file = "kernel-%s.json"%os.getpid() + try: + self.connection_file = filefind(self.connection_file, ['.', self.connection_dir]) + except IOError: + self.log.debug("Connection file not found: %s", self.connection_file) + # This means I own it, and I'll create it in this directory: + ensure_dir_exists(os.path.dirname(self.abs_connection_file), 0o700) + # Also, I will clean it up: + atexit.register(self.cleanup_connection_file) + return + try: + self.load_connection_file() + except Exception: + self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True) + self.exit(1) + + def init_sockets(self): + # Create a context, a session, and the kernel sockets. + self.log.info("Starting the kernel at pid: %i", os.getpid()) + context = zmq.Context.instance() + # Uncomment this to try closing the context. + # atexit.register(context.term) + + self.shell_socket = context.socket(zmq.ROUTER) + self.shell_socket.linger = 1000 + self.shell_port = self._bind_socket(self.shell_socket, self.shell_port) + self.log.debug("shell ROUTER Channel on port: %i" % self.shell_port) + + self.stdin_socket = context.socket(zmq.ROUTER) + self.stdin_socket.linger = 1000 + self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port) + self.log.debug("stdin ROUTER Channel on port: %i" % self.stdin_port) + + self.control_socket = context.socket(zmq.ROUTER) + self.control_socket.linger = 1000 + self.control_port = self._bind_socket(self.control_socket, self.control_port) + self.log.debug("control ROUTER Channel on port: %i" % self.control_port) + + self.init_iopub(context) + + def init_iopub(self, context): + self.iopub_socket = context.socket(zmq.PUB) + self.iopub_socket.linger = 1000 + self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port) + self.log.debug("iopub PUB Channel on port: %i" % self.iopub_port) + self.configure_tornado_logger() + self.iopub_thread = IOPubThread(self.iopub_socket, pipe=True) + self.iopub_thread.start() + # backward-compat: wrap iopub socket API in background thread + self.iopub_socket = self.iopub_thread.background_socket + + def init_heartbeat(self): + """start the heart beating""" + # heartbeat doesn't share context, because it mustn't be blocked + # by the GIL, which is accessed by libzmq when freeing zero-copy messages + hb_ctx = zmq.Context() + self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port)) + self.hb_port = self.heartbeat.port + self.log.debug("Heartbeat REP Channel on port: %i" % self.hb_port) + self.heartbeat.start() + + def log_connection_info(self): + """display connection info, and store ports""" + basename = os.path.basename(self.connection_file) + if basename == self.connection_file or \ + os.path.dirname(self.connection_file) == self.connection_dir: + # use shortname + tail = basename + else: + tail = self.connection_file + lines = [ + "To connect another client to this kernel, use:", + " --existing %s" % tail, + ] + # log connection info + # info-level, so often not shown. + # frontends should use the %connect_info magic + # to see the connection info + for line in lines: + self.log.info(line) + # also raw print to the terminal if no parent_handle (`ipython kernel`) + # unless log-level is CRITICAL (--quiet) + if not self.parent_handle and self.log_level < logging.CRITICAL: + io.rprint(_ctrl_c_message) + for line in lines: + io.rprint(line) + + self.ports = dict(shell=self.shell_port, iopub=self.iopub_port, + stdin=self.stdin_port, hb=self.hb_port, + control=self.control_port) + + def init_blackhole(self): + """redirects stdout/stderr to devnull if necessary""" + if self.no_stdout or self.no_stderr: + blackhole = open(os.devnull, 'w') + if self.no_stdout: + sys.stdout = sys.__stdout__ = blackhole + if self.no_stderr: + sys.stderr = sys.__stderr__ = blackhole + + def init_io(self): + """Redirect input streams and set a display hook.""" + if self.outstream_class: + outstream_factory = import_item(str(self.outstream_class)) + sys.stdout = outstream_factory(self.session, self.iopub_thread, u'stdout') + sys.stderr = outstream_factory(self.session, self.iopub_thread, u'stderr') + if self.displayhook_class: + displayhook_factory = import_item(str(self.displayhook_class)) + self.displayhook = displayhook_factory(self.session, self.iopub_socket) + sys.displayhook = self.displayhook + + self.patch_io() + + def patch_io(self): + """Patch important libraries that can't handle sys.stdout forwarding""" + try: + import faulthandler + except ImportError: + pass + else: + # Warning: this is a monkeypatch of `faulthandler.enable`, watch for possible + # updates to the upstream API and update accordingly (up-to-date as of Python 3.5): + # https://docs.python.org/3/library/faulthandler.html#faulthandler.enable + + # change default file to __stderr__ from forwarded stderr + faulthandler_enable = faulthandler.enable + def enable(file=sys.__stderr__, all_threads=True, **kwargs): + return faulthandler_enable(file=file, all_threads=all_threads, **kwargs) + + faulthandler.enable = enable + + if hasattr(faulthandler, 'register'): + faulthandler_register = faulthandler.register + def register(signum, file=sys.__stderr__, all_threads=True, chain=False, **kwargs): + return faulthandler_register(signum, file=file, all_threads=all_threads, + chain=chain, **kwargs) + faulthandler.register = register + + def init_signal(self): + signal.signal(signal.SIGINT, signal.SIG_IGN) + + def init_kernel(self): + """Create the Kernel object itself""" + shell_stream = ZMQStream(self.shell_socket) + control_stream = ZMQStream(self.control_socket) + + kernel_factory = self.kernel_class.instance + + kernel = kernel_factory(parent=self, session=self.session, + shell_streams=[shell_stream, control_stream], + iopub_thread=self.iopub_thread, + iopub_socket=self.iopub_socket, + stdin_socket=self.stdin_socket, + log=self.log, + profile_dir=self.profile_dir, + user_ns=self.user_ns, + ) + kernel.record_ports({ + name + '_port': port for name, port in self.ports.items() + }) + self.kernel = kernel + + # Allow the displayhook to get the execution count + self.displayhook.get_execution_count = lambda: kernel.execution_count + + def init_gui_pylab(self): + """Enable GUI event loop integration, taking pylab into account.""" + + # Register inline backend as default + # this is higher priority than matplotlibrc, + # but lower priority than anything else (mpl.use() for instance). + # This only affects matplotlib >= 1.5 + if not os.environ.get('MPLBACKEND'): + os.environ['MPLBACKEND'] = 'module://yap_kernel.pylab.backend_inline' + + # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab` + # to ensure that any exception is printed straight to stderr. + # Normally _showtraceback associates the reply with an execution, + # which means frontends will never draw it, as this exception + # is not associated with any execute request. + + shell = self.shell + _showtraceback = shell._showtraceback + try: + # replace error-sending traceback with stderr + def print_tb(etype, evalue, stb): + print ("GUI event loop or pylab initialization failed", + file=sys.stderr) + print (shell.InteractiveTB.stb2text(stb), file=sys.stderr) + shell._showtraceback = print_tb + InteractiveShellApp.init_gui_pylab(self) + finally: + shell._showtraceback = _showtraceback + + def init_shell(self): + self.shell = getattr(self.kernel, 'shell', None) + if self.shell: + self.shell.configurables.append(self) + + def init_extensions(self): + super(YAPKernelApp, self).init_extensions() + # BEGIN HARDCODED WIDGETS HACK + # Ensure ipywidgets extension is loaded if available + extension_man = self.shell.extension_manager + if 'ipywidgets' not in extension_man.loaded: + try: + extension_man.load_extension('ipywidgets') + except ImportError as e: + self.log.debug('ipywidgets package not installed. Widgets will not be available.') + # END HARDCODED WIDGETS HACK + + def configure_tornado_logger(self): + """ Configure the tornado logging.Logger. + + Must set up the tornado logger or else tornado will call + basicConfig for the root logger which makes the root logger + go to the real sys.stderr instead of the capture streams. + This function mimics the setup of logging.basicConfig. + """ + logger = logging.getLogger('tornado') + handler = logging.StreamHandler() + formatter = logging.Formatter(logging.BASIC_FORMAT) + handler.setFormatter(formatter) + logger.addHandler(handler) + + @catch_config_error + def initialize(self, argv=None): + super(YAPKernelApp, self).initialize(argv) + if self.subapp is not None: + return + # register zmq IOLoop with tornado + zmq_ioloop.install() + self.init_blackhole() + self.init_connection_file() + self.init_poller() + self.init_sockets() + self.init_heartbeat() + # writing/displaying connection info must be *after* init_sockets/heartbeat + self.write_connection_file() + # Log connection info after writing connection file, so that the connection + # file is definitely available at the time someone reads the log. + self.log_connection_info() + self.init_io() + self.init_signal() + self.init_kernel() + # shell init steps + self.init_path() + self.init_shell() + if self.shell: + self.init_gui_pylab() + self.init_extensions() + self.init_code() + # flush stdout/stderr, so that anything written to these streams during + # initialization do not get associated with the first execution request + sys.stdout.flush() + sys.stderr.flush() + + def start(self): + if self.subapp is not None: + return self.subapp.start() + if self.poller is not None: + self.poller.start() + self.kernel.start() + try: + ioloop.IOLoop.instance().start() + except KeyboardInterrupt: + pass + +launch_new_instance = YAPKernelApp.launch_instance + +def main(): + """Run an YAPKernel as an application""" + app = YAPKernelApp.instance() + app.initialize() + app.start() + + +if __name__ == '__main__': + main() diff --git a/packages/python/yap_kernel/yap_kernel/kernelbase.py b/packages/python/yap_kernel/yap_kernel/kernelbase.py new file mode 100644 index 000000000..bdf606441 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/kernelbase.py @@ -0,0 +1,756 @@ +"""Base class for a kernel that talks to frontends over 0MQ.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from __future__ import print_function + +import sys +import time +import logging +import uuid + +from datetime import datetime +try: + # jupyter_client >= 5, use tz-aware now + from jupyter_client.session import utcnow as now +except ImportError: + # jupyter_client < 5, use local now() + now = datetime.now + +from signal import signal, default_int_handler, SIGINT + +import zmq +from tornado import ioloop +from zmq.eventloop.zmqstream import ZMQStream + +from traitlets.config.configurable import SingletonConfigurable +from IPython.core.error import StdinNotImplementedError +from ipython_genutils import py3compat +from ipython_genutils.py3compat import unicode_type, string_types +from yap_kernel.jsonutil import json_clean +from traitlets import ( + Any, Instance, Float, Dict, List, Set, Integer, Unicode, Bool, observe, default +) + +from jupyter_client.session import Session + +from ._version import kernel_protocol_version + +class Kernel(SingletonConfigurable): + + #--------------------------------------------------------------------------- + # Kernel interface + #--------------------------------------------------------------------------- + + # attribute to override with a GUI + eventloop = Any(None) + + @observe('eventloop') + def _update_eventloop(self, change): + """schedule call to eventloop from IOLoop""" + loop = ioloop.IOLoop.instance() + loop.add_callback(self.enter_eventloop) + + session = Instance(Session, allow_none=True) + profile_dir = Instance('IPython.core.profiledir.ProfileDir', allow_none=True) + shell_streams = List() + control_stream = Instance(ZMQStream, allow_none=True) + iopub_socket = Any() + iopub_thread = Any() + stdin_socket = Any() + log = Instance(logging.Logger, allow_none=True) + + # identities: + int_id = Integer(-1) + ident = Unicode() + + @default('ident') + def _default_ident(self): + return unicode_type(uuid.uuid4()) + + # This should be overridden by wrapper kernels that implement any real + # language. + language_info = {} + + # any links that should go in the help menu + help_links = List() + + # Private interface + + _darwin_app_nap = Bool(True, + help="""Whether to use appnope for compatiblity with OS X App Nap. + + Only affects OS X >= 10.9. + """ + ).tag(config=True) + + # track associations with current request + _allow_stdin = Bool(False) + _parent_header = Dict() + _parent_ident = Any(b'') + # Time to sleep after flushing the stdout/err buffers in each execute + # cycle. While this introduces a hard limit on the minimal latency of the + # execute cycle, it helps prevent output synchronization problems for + # clients. + # Units are in seconds. The minimum zmq latency on local host is probably + # ~150 microseconds, set this to 500us for now. We may need to increase it + # a little if it's not enough after more interactive testing. + _execute_sleep = Float(0.0005).tag(config=True) + + # Frequency of the kernel's event loop. + # Units are in seconds, kernel subclasses for GUI toolkits may need to + # adapt to milliseconds. + _poll_interval = Float(0.05).tag(config=True) + + # If the shutdown was requested over the network, we leave here the + # necessary reply message so it can be sent by our registered atexit + # handler. This ensures that the reply is only sent to clients truly at + # the end of our shutdown process (which happens after the underlying + # IPython shell's own shutdown). + _shutdown_message = None + + # This is a dict of port number that the kernel is listening on. It is set + # by record_ports and used by connect_request. + _recorded_ports = Dict() + + # set of aborted msg_ids + aborted = Set() + + # Track execution count here. For IPython, we override this to use the + # execution count we store in the shell. + execution_count = 0 + + msg_types = [ + 'execute_request', 'complete_request', + 'inspect_request', 'history_request', + 'comm_info_request', 'kernel_info_request', + 'connect_request', 'shutdown_request', + 'is_complete_request', + # deprecated: + 'apply_request', + ] + # add deprecated ipyparallel control messages + control_msg_types = msg_types + ['clear_request', 'abort_request'] + + def __init__(self, **kwargs): + super(Kernel, self).__init__(**kwargs) + + # Build dict of handlers for message types + self.shell_handlers = {} + for msg_type in self.msg_types: + self.shell_handlers[msg_type] = getattr(self, msg_type) + + self.control_handlers = {} + for msg_type in self.control_msg_types: + self.control_handlers[msg_type] = getattr(self, msg_type) + + + def dispatch_control(self, msg): + """dispatch control requests""" + idents,msg = self.session.feed_identities(msg, copy=False) + try: + msg = self.session.deserialize(msg, content=True, copy=False) + except: + self.log.error("Invalid Control Message", exc_info=True) + return + + self.log.debug("Control received: %s", msg) + + # Set the parent message for side effects. + self.set_parent(idents, msg) + self._publish_status(u'busy') + + header = msg['header'] + msg_type = header['msg_type'] + + handler = self.control_handlers.get(msg_type, None) + if handler is None: + self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type) + else: + try: + handler(self.control_stream, idents, msg) + except Exception: + self.log.error("Exception in control handler:", exc_info=True) + + sys.stdout.flush() + sys.stderr.flush() + self._publish_status(u'idle') + + def should_handle(self, stream, msg, idents): + """Check whether a shell-channel message should be handled + + Allows subclasses to prevent handling of certain messages (e.g. aborted requests). + """ + msg_id = msg['header']['msg_id'] + if msg_id in self.aborted: + msg_type = msg['header']['msg_type'] + # is it safe to assume a msg_id will not be resubmitted? + self.aborted.remove(msg_id) + reply_type = msg_type.split('_')[0] + '_reply' + status = {'status' : 'aborted'} + md = {'engine' : self.ident} + md.update(status) + self.session.send(stream, reply_type, metadata=md, + content=status, parent=msg, ident=idents) + return False + return True + + def dispatch_shell(self, stream, msg): + """dispatch shell requests""" + # flush control requests first + if self.control_stream: + self.control_stream.flush() + + idents,msg = self.session.feed_identities(msg, copy=False) + try: + msg = self.session.deserialize(msg, content=True, copy=False) + except: + self.log.error("Invalid Message", exc_info=True) + return + + # Set the parent message for side effects. + self.set_parent(idents, msg) + self._publish_status(u'busy') + + header = msg['header'] + msg_id = header['msg_id'] + msg_type = msg['header']['msg_type'] + + # Print some info about this message and leave a '--->' marker, so it's + # easier to trace visually the message chain when debugging. Each + # handler prints its message at the end. + self.log.debug('\n*** MESSAGE TYPE:%s***', msg_type) + self.log.debug(' Content: %s\n --->\n ', msg['content']) + + if not self.should_handle(stream, msg, idents): + return + + handler = self.shell_handlers.get(msg_type, None) + if handler is None: + self.log.warn("Unknown message type: %r", msg_type) + else: + self.log.debug("%s: %s", msg_type, msg) + self.pre_handler_hook() + try: + handler(stream, idents, msg) + except Exception: + self.log.error("Exception in message handler:", exc_info=True) + finally: + self.post_handler_hook() + + sys.stdout.flush() + sys.stderr.flush() + self._publish_status(u'idle') + + def pre_handler_hook(self): + """Hook to execute before calling message handler""" + # ensure default_int_handler during handler call + self.saved_sigint_handler = signal(SIGINT, default_int_handler) + + def post_handler_hook(self): + """Hook to execute after calling message handler""" + signal(SIGINT, self.saved_sigint_handler) + + def enter_eventloop(self): + """enter eventloop""" + self.log.info("entering eventloop %s", self.eventloop) + for stream in self.shell_streams: + # flush any pending replies, + # which may be skipped by entering the eventloop + stream.flush(zmq.POLLOUT) + # restore default_int_handler + signal(SIGINT, default_int_handler) + while self.eventloop is not None: + try: + self.eventloop(self) + except KeyboardInterrupt: + # Ctrl-C shouldn't crash the kernel + self.log.error("KeyboardInterrupt caught in kernel") + continue + else: + # eventloop exited cleanly, this means we should stop (right?) + self.eventloop = None + break + self.log.info("exiting eventloop") + + def start(self): + """register dispatchers for streams""" + if self.control_stream: + self.control_stream.on_recv(self.dispatch_control, copy=False) + + def make_dispatcher(stream): + def dispatcher(msg): + return self.dispatch_shell(stream, msg) + return dispatcher + + for s in self.shell_streams: + s.on_recv(make_dispatcher(s), copy=False) + + # publish idle status + self._publish_status('starting') + + def do_one_iteration(self): + """step eventloop just once""" + if self.control_stream: + self.control_stream.flush() + for stream in self.shell_streams: + # handle at most one request per iteration + stream.flush(zmq.POLLIN, 1) + stream.flush(zmq.POLLOUT) + + def record_ports(self, ports): + """Record the ports that this kernel is using. + + The creator of the Kernel instance must call this methods if they + want the :meth:`connect_request` method to return the port numbers. + """ + self._recorded_ports = ports + + #--------------------------------------------------------------------------- + # Kernel request handlers + #--------------------------------------------------------------------------- + + def _publish_execute_input(self, code, parent, execution_count): + """Publish the code request on the iopub stream.""" + + self.session.send(self.iopub_socket, u'execute_input', + {u'code':code, u'execution_count': execution_count}, + parent=parent, ident=self._topic('execute_input') + ) + + def _publish_status(self, status, parent=None): + """send status (busy/idle) on IOPub""" + self.session.send(self.iopub_socket, + u'status', + {u'execution_state': status}, + parent=parent or self._parent_header, + ident=self._topic('status'), + ) + + def set_parent(self, ident, parent): + """Set the current parent_header + + Side effects (IOPub messages) and replies are associated with + the request that caused them via the parent_header. + + The parent identity is used to route input_request messages + on the stdin channel. + """ + self._parent_ident = ident + self._parent_header = parent + + def send_response(self, stream, msg_or_type, content=None, ident=None, + buffers=None, track=False, header=None, metadata=None): + """Send a response to the message we're currently processing. + + This accepts all the parameters of :meth:`jupyter_client.session.Session.send` + except ``parent``. + + This relies on :meth:`set_parent` having been called for the current + message. + """ + return self.session.send(stream, msg_or_type, content, self._parent_header, + ident, buffers, track, header, metadata) + + def init_metadata(self, parent): + """Initialize metadata. + + Run at the beginning of execution requests. + """ + # FIXME: `started` is part of ipyparallel + # Remove for yap_kernel 5.0 + return { + 'started': now(), + } + + def finish_metadata(self, parent, metadata, reply_content): + """Finish populating metadata. + + Run after completing an execution request. + """ + return metadata + + def execute_request(self, stream, ident, parent): + """handle an execute_request""" + + try: + content = parent[u'content'] + code = py3compat.cast_unicode_py2(content[u'code']) + silent = content[u'silent'] + store_history = content.get(u'store_history', not silent) + user_expressions = content.get('user_expressions', {}) + allow_stdin = content.get('allow_stdin', False) + except: + self.log.error("Got bad msg: ") + self.log.error("%s", parent) + return + + stop_on_error = content.get('stop_on_error', True) + + metadata = self.init_metadata(parent) + + # Re-broadcast our input for the benefit of listening clients, and + # start computing output + if not silent: + self.execution_count += 1 + self._publish_execute_input(code, parent, self.execution_count) + + reply_content = self.do_execute(code, silent, store_history, + user_expressions, allow_stdin) + + # Flush output before sending the reply. + sys.stdout.flush() + sys.stderr.flush() + # FIXME: on rare occasions, the flush doesn't seem to make it to the + # clients... This seems to mitigate the problem, but we definitely need + # to better understand what's going on. + if self._execute_sleep: + time.sleep(self._execute_sleep) + + # Send the reply. + reply_content = json_clean(reply_content) + metadata = self.finish_metadata(parent, metadata, reply_content) + + reply_msg = self.session.send(stream, u'execute_reply', + reply_content, parent, metadata=metadata, + ident=ident) + + self.log.debug("%s", reply_msg) + + if not silent and reply_msg['content']['status'] == u'error' and stop_on_error: + self._abort_queues() + + def do_execute(self, code, silent, store_history=True, + user_expressions=None, allow_stdin=False): + """Execute user code. Must be overridden by subclasses. + """ + raise NotImplementedError + + def complete_request(self, stream, ident, parent): + content = parent['content'] + code = content['code'] + cursor_pos = content['cursor_pos'] + + matches = self.do_complete(code, cursor_pos) + matches = json_clean(matches) + completion_msg = self.session.send(stream, 'complete_reply', + matches, parent, ident) + self.log.debug("%s", completion_msg) + + def do_complete(self, code, cursor_pos): + """Override in subclasses to find completions. + """ + return {'matches' : [], + 'cursor_end' : cursor_pos, + 'cursor_start' : cursor_pos, + 'metadata' : {}, + 'status' : 'ok'} + + def inspect_request(self, stream, ident, parent): + content = parent['content'] + + reply_content = self.do_inspect(content['code'], content['cursor_pos'], + content.get('detail_level', 0)) + # Before we send this object over, we scrub it for JSON usage + reply_content = json_clean(reply_content) + msg = self.session.send(stream, 'inspect_reply', + reply_content, parent, ident) + self.log.debug("%s", msg) + + def do_inspect(self, code, cursor_pos, detail_level=0): + """Override in subclasses to allow introspection. + """ + return {'status': 'ok', 'data': {}, 'metadata': {}, 'found': False} + + def history_request(self, stream, ident, parent): + content = parent['content'] + + reply_content = self.do_history(**content) + + reply_content = json_clean(reply_content) + msg = self.session.send(stream, 'history_reply', + reply_content, parent, ident) + self.log.debug("%s", msg) + + def do_history(self, hist_access_type, output, raw, session=None, start=None, + stop=None, n=None, pattern=None, unique=False): + """Override in subclasses to access history. + """ + return {'status': 'ok', 'history': []} + + def connect_request(self, stream, ident, parent): + if self._recorded_ports is not None: + content = self._recorded_ports.copy() + else: + content = {} + content['status'] = 'ok' + msg = self.session.send(stream, 'connect_reply', + content, parent, ident) + self.log.debug("%s", msg) + + @property + def kernel_info(self): + return { + 'protocol_version': kernel_protocol_version, + 'implementation': self.implementation, + 'implementation_version': self.implementation_version, + 'language_info': self.language_info, + 'banner': self.banner, + 'help_links': self.help_links, + } + + def kernel_info_request(self, stream, ident, parent): + content = {'status': 'ok'} + content.update(self.kernel_info) + msg = self.session.send(stream, 'kernel_info_reply', + content, parent, ident) + self.log.debug("%s", msg) + + def comm_info_request(self, stream, ident, parent): + content = parent['content'] + target_name = content.get('target_name', None) + + # Should this be moved to yapkernel? + if hasattr(self, 'comm_manager'): + comms = { + k: dict(target_name=v.target_name) + for (k, v) in self.comm_manager.comms.items() + if v.target_name == target_name or target_name is None + } + else: + comms = {} + reply_content = dict(comms=comms, status='ok') + msg = self.session.send(stream, 'comm_info_reply', + reply_content, parent, ident) + self.log.debug("%s", msg) + + def shutdown_request(self, stream, ident, parent): + content = self.do_shutdown(parent['content']['restart']) + self.session.send(stream, u'shutdown_reply', content, parent, ident=ident) + # same content, but different msg_id for broadcasting on IOPub + self._shutdown_message = self.session.msg(u'shutdown_reply', + content, parent + ) + + self._at_shutdown() + # call sys.exit after a short delay + loop = ioloop.IOLoop.instance() + loop.add_timeout(time.time()+0.1, loop.stop) + + def do_shutdown(self, restart): + """Override in subclasses to do things when the frontend shuts down the + kernel. + """ + return {'status': 'ok', 'restart': restart} + + def is_complete_request(self, stream, ident, parent): + content = parent['content'] + code = content['code'] + + reply_content = self.do_is_complete(code) + reply_content = json_clean(reply_content) + reply_msg = self.session.send(stream, 'is_complete_reply', + reply_content, parent, ident) + self.log.debug("%s", reply_msg) + + def do_is_complete(self, code): + """Override in subclasses to find completions. + """ + return {'status' : 'unknown', + } + + #--------------------------------------------------------------------------- + # Engine methods (DEPRECATED) + #--------------------------------------------------------------------------- + + def apply_request(self, stream, ident, parent): + self.log.warn("""apply_request is deprecated in kernel_base, moving to ipyparallel.""") + try: + content = parent[u'content'] + bufs = parent[u'buffers'] + msg_id = parent['header']['msg_id'] + except: + self.log.error("Got bad msg: %s", parent, exc_info=True) + return + + md = self.init_metadata(parent) + + reply_content, result_buf = self.do_apply(content, bufs, msg_id, md) + + # flush i/o + sys.stdout.flush() + sys.stderr.flush() + + md = self.finish_metadata(parent, md, reply_content) + + self.session.send(stream, u'apply_reply', reply_content, + parent=parent, ident=ident,buffers=result_buf, metadata=md) + + def do_apply(self, content, bufs, msg_id, reply_metadata): + """DEPRECATED""" + raise NotImplementedError + + #--------------------------------------------------------------------------- + # Control messages (DEPRECATED) + #--------------------------------------------------------------------------- + + def abort_request(self, stream, ident, parent): + """abort a specific msg by id""" + self.log.warn("abort_request is deprecated in kernel_base. It os only part of IPython parallel") + msg_ids = parent['content'].get('msg_ids', None) + if isinstance(msg_ids, string_types): + msg_ids = [msg_ids] + if not msg_ids: + self._abort_queues() + for mid in msg_ids: + self.aborted.add(str(mid)) + + content = dict(status='ok') + reply_msg = self.session.send(stream, 'abort_reply', content=content, + parent=parent, ident=ident) + self.log.debug("%s", reply_msg) + + def clear_request(self, stream, idents, parent): + """Clear our namespace.""" + self.log.warn("clear_request is deprecated in kernel_base. It os only part of IPython parallel") + content = self.do_clear() + self.session.send(stream, 'clear_reply', ident=idents, parent=parent, + content = content) + + def do_clear(self): + """DEPRECATED""" + raise NotImplementedError + + #--------------------------------------------------------------------------- + # Protected interface + #--------------------------------------------------------------------------- + + def _topic(self, topic): + """prefixed topic for IOPub messages""" + base = "kernel.%s" % self.ident + + return py3compat.cast_bytes("%s.%s" % (base, topic)) + + def _abort_queues(self): + for stream in self.shell_streams: + if stream: + self._abort_queue(stream) + + def _abort_queue(self, stream): + poller = zmq.Poller() + poller.register(stream.socket, zmq.POLLIN) + while True: + idents,msg = self.session.recv(stream, zmq.NOBLOCK, content=True) + if msg is None: + return + + self.log.info("Aborting:") + self.log.info("%s", msg) + msg_type = msg['header']['msg_type'] + reply_type = msg_type.split('_')[0] + '_reply' + + status = {'status' : 'aborted'} + md = {'engine' : self.ident} + md.update(status) + self._publish_status('busy', parent=msg) + reply_msg = self.session.send(stream, reply_type, metadata=md, + content=status, parent=msg, ident=idents) + self._publish_status('idle', parent=msg) + self.log.debug("%s", reply_msg) + # We need to wait a bit for requests to come in. This can probably + # be set shorter for true asynchronous clients. + poller.poll(50) + + def _no_raw_input(self): + """Raise StdinNotImplentedError if active frontend doesn't support + stdin.""" + raise StdinNotImplementedError("raw_input was called, but this " + "frontend does not support stdin.") + + def getpass(self, prompt='', stream=None): + """Forward getpass to frontends + + Raises + ------ + StdinNotImplentedError if active frontend doesn't support stdin. + """ + if not self._allow_stdin: + raise StdinNotImplementedError( + "getpass was called, but this frontend does not support input requests." + ) + if stream is not None: + import warnings + warnings.warn("The `stream` parameter of `getpass.getpass` will have no effect when using yap_kernel", + UserWarning, stacklevel=2) + return self._input_request(prompt, + self._parent_ident, + self._parent_header, + password=True, + ) + + def raw_input(self, prompt=''): + """Forward raw_input to frontends + + Raises + ------ + StdinNotImplentedError if active frontend doesn't support stdin. + """ + if not self._allow_stdin: + raise StdinNotImplementedError( + "raw_input was called, but this frontend does not support input requests." + ) + return self._input_request(str(prompt), + self._parent_ident, + self._parent_header, + password=False, + ) + + def _input_request(self, prompt, ident, parent, password=False): + # Flush output before making the request. + sys.stderr.flush() + sys.stdout.flush() + # flush the stdin socket, to purge stale replies + while True: + try: + self.stdin_socket.recv_multipart(zmq.NOBLOCK) + except zmq.ZMQError as e: + if e.errno == zmq.EAGAIN: + break + else: + raise + + # Send the input request. + content = json_clean(dict(prompt=prompt, password=password)) + self.session.send(self.stdin_socket, u'input_request', content, parent, + ident=ident) + + # Await a response. + while True: + try: + ident, reply = self.session.recv(self.stdin_socket, 0) + except Exception: + self.log.warn("Invalid Message:", exc_info=True) + except KeyboardInterrupt: + # re-raise KeyboardInterrupt, to truncate traceback + raise KeyboardInterrupt + else: + break + try: + value = py3compat.unicode_to_str(reply['content']['value']) + except: + self.log.error("Bad input_reply: %s", parent) + value = '' + if value == '\x04': + # EOF + raise EOFError + return value + + def _at_shutdown(self): + """Actions taken at shutdown by the kernel, called by python's atexit. + """ + # io.rprint("Kernel at_shutdown") # dbg + if self._shutdown_message is not None: + self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown')) + self.log.debug("%s", self._shutdown_message) + [ s.flush(zmq.POLLOUT) for s in self.shell_streams ] diff --git a/packages/python/yap_kernel/yap_kernel/kernelspec.py b/packages/python/yap_kernel/yap_kernel/kernelspec.py new file mode 100644 index 000000000..a3c19a3d1 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/kernelspec.py @@ -0,0 +1,188 @@ +"""The IPython kernel spec for Jupyter""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from __future__ import print_function + +import errno +import json +import os +import shutil +import sys +import tempfile + +from jupyter_client.kernelspec import KernelSpecManager + +pjoin = os.path.join + +KERNEL_NAME = 'YAPKernel' + +# path to kernelspec resources +RESOURCES = pjoin(os.path.dirname(__file__), 'resources') + + +def make_yap_kernel_cmd(mod='yap_kernel', executable=None, extra_arguments=None, **kw): + """Build Popen command list for launching an IPython kernel. + + Parameters + ---------- + mod : str, optional (default 'yap_kernel') + A string of an IPython module whose __main__ starts an IPython kernel + + executable : str, optional (default sys.executable) + The Python executable to use for the kernel process. + + extra_arguments : list, optional + A list of extra arguments to pass when executing the launch code. + + Returns + ------- + + A Popen command list + """ + if executable is None: + executable = sys.executable + extra_arguments = extra_arguments or [] + arguments = [executable, '-m', mod, '-f', '{connection_file}'] + arguments.extend(extra_arguments) + + return arguments + + +def get_kernel_dict(extra_arguments=None): + """Construct dict for kernel.json""" + return { + 'argv': make_yap_kernel_cmd(extra_arguments=extra_arguments), + 'display_name': 'YAP 6a', + 'language': 'prolog', + } + + +def write_kernel_spec(path=None, overrides=None, extra_arguments=None): + """Write a kernel spec directory to `path` + + If `path` is not specified, a temporary directory is created. + If `overrides` is given, the kernelspec JSON is updated before writing. + + The path to the kernelspec is always returned. + """ + if path is None: + path = os.path.join(tempfile.mkdtemp(suffix='_kernels'), KERNEL_NAME) + + # stage resources + shutil.copytree(RESOURCES, path) + # write kernel.json + kernel_dict = get_kernel_dict(extra_arguments) + + if overrides: + kernel_dict.update(overrides) + with open(pjoin(path, 'kernel.json'), 'w') as f: + json.dump(kernel_dict, f, indent=1) + + return path + + +def install(kernel_spec_manager=None, user=False, kernel_name=KERNEL_NAME, display_name=None, + prefix=None, profile=None): + """Install the IPython kernelspec for Jupyter + + Parameters + ---------- + + kernel_spec_manager: KernelSpecManager [optional] + A KernelSpecManager to use for installation. + If none provided, a default instance will be created. + user: bool [default: False] + Whether to do a user-only install, or system-wide. + kernel_name: str, optional + Specify a name for the kernelspec. + This is needed for having multiple IPython kernels for different environments. + display_name: str, optional + Specify the display name for the kernelspec + profile: str, optional + Specify a custom profile to be loaded by the kernel. + prefix: str, optional + Specify an install prefix for the kernelspec. + This is needed to install into a non-default location, such as a conda/virtual-env. + + Returns + ------- + + The path where the kernelspec was installed. + """ + if kernel_spec_manager is None: + kernel_spec_manager = KernelSpecManager() + + if (kernel_name != KERNEL_NAME) and (display_name is None): + # kernel_name is specified and display_name is not + # default display_name to kernel_name + display_name = kernel_name + overrides = {} + if display_name: + overrides["display_name"] = display_name + if profile: + extra_arguments = ["--profile", profile] + if not display_name: + # add the profile to the default display name + overrides["display_name"] = 'Python %i [profile=%s]' % (sys.version_info[0], profile) + else: + extra_arguments = None + path = write_kernel_spec(overrides=overrides, extra_arguments=extra_arguments) + dest = kernel_spec_manager.install_kernel_spec( + path, kernel_name=kernel_name, user=user, prefix=prefix) + # cleanup afterward + shutil.rmtree(path) + return dest + +# Entrypoint + +from traitlets.config import Application + + +class InstallYAPKernelSpecApp(Application): + """Dummy app wrapping argparse""" + name = 'ipython-kernel-install' + + def initialize(self, argv=None): + if argv is None: + argv = sys.argv[1:] + self.argv = argv + + def start(self): + import argparse + parser = argparse.ArgumentParser(prog=self.name, + description="Install the IPython kernel spec.") + parser.add_argument('--user', action='store_true', + help="Install for the current user instead of system-wide") + parser.add_argument('--name', type=str, default=KERNEL_NAME, + help="Specify a name for the kernelspec." + " This is needed to have multiple IPython kernels at the same time.") + parser.add_argument('--display-name', type=str, + help="Specify the display name for the kernelspec." + " This is helpful when you have multiple IPython kernels.") + parser.add_argument('--profile', type=str, + help="Specify an IPython profile to load. " + "This can be used to create custom versions of the kernel.") + parser.add_argument('--prefix', type=str, + help="Specify an install prefix for the kernelspec." + " This is needed to install into a non-default location, such as a conda/virtual-env.") + parser.add_argument('--sys-prefix', action='store_const', const=sys.prefix, dest='prefix', + help="Install to Python's sys.prefix." + " Shorthand for --prefix='%s'. For use in conda/virtual-envs." % sys.prefix) + opts = parser.parse_args(self.argv) + try: + dest = install(user=opts.user, kernel_name=opts.name, profile=opts.profile, + prefix=opts.prefix, display_name=opts.display_name) + except OSError as e: + if e.errno == errno.EACCES: + print(e, file=sys.stderr) + if opts.user: + print("Perhaps you want `sudo` or `--user`?", file=sys.stderr) + self.exit(1) + raise + print("Installed kernelspec %s in %s" % (opts.name, dest)) + + +if __name__ == '__main__': + InstallYAPKernelSpecApp.launch_instance() diff --git a/packages/python/yap_kernel/yap_kernel/log.py b/packages/python/yap_kernel/yap_kernel/log.py new file mode 100644 index 000000000..25c3630e4 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/log.py @@ -0,0 +1,23 @@ +from logging import INFO, DEBUG, WARN, ERROR, FATAL + +from zmq.log.handlers import PUBHandler + +import warnings +warnings.warn("yap_kernel.log is deprecated. It has moved to ipyparallel.engine.log", DeprecationWarning) + +class EnginePUBHandler(PUBHandler): + """A simple PUBHandler subclass that sets root_topic""" + engine=None + + def __init__(self, engine, *args, **kwargs): + PUBHandler.__init__(self,*args, **kwargs) + self.engine = engine + + @property + def root_topic(self): + """this is a property, in case the handler is created + before the engine gets registered with an id""" + if isinstance(getattr(self.engine, 'id', None), int): + return "engine.%i"%self.engine.id + else: + return "engine" diff --git a/packages/python/yap_kernel/yap_kernel/parentpoller.py b/packages/python/yap_kernel/yap_kernel/parentpoller.py new file mode 100644 index 000000000..446f656df --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/parentpoller.py @@ -0,0 +1,117 @@ +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +try: + import ctypes +except: + ctypes = None +import os +import platform +import signal +import time +try: + from _thread import interrupt_main # Py 3 +except ImportError: + from thread import interrupt_main # Py 2 +from threading import Thread + +from traitlets.log import get_logger + +import warnings + +class ParentPollerUnix(Thread): + """ A Unix-specific daemon thread that terminates the program immediately + when the parent process no longer exists. + """ + + def __init__(self): + super(ParentPollerUnix, self).__init__() + self.daemon = True + + def run(self): + # We cannot use os.waitpid because it works only for child processes. + from errno import EINTR + while True: + try: + if os.getppid() == 1: + get_logger().warning("Parent appears to have exited, shutting down.") + os._exit(1) + time.sleep(1.0) + except OSError as e: + if e.errno == EINTR: + continue + raise + + +class ParentPollerWindows(Thread): + """ A Windows-specific daemon thread that listens for a special event that + signals an interrupt and, optionally, terminates the program immediately + when the parent process no longer exists. + """ + + def __init__(self, interrupt_handle=None, parent_handle=None): + """ Create the poller. At least one of the optional parameters must be + provided. + + Parameters + ---------- + interrupt_handle : HANDLE (int), optional + If provided, the program will generate a Ctrl+C event when this + handle is signaled. + + parent_handle : HANDLE (int), optional + If provided, the program will terminate immediately when this + handle is signaled. + """ + assert(interrupt_handle or parent_handle) + super(ParentPollerWindows, self).__init__() + if ctypes is None: + raise ImportError("ParentPollerWindows requires ctypes") + self.daemon = True + self.interrupt_handle = interrupt_handle + self.parent_handle = parent_handle + + def run(self): + """ Run the poll loop. This method never returns. + """ + try: + from _winapi import WAIT_OBJECT_0, INFINITE + except ImportError: + from _subprocess import WAIT_OBJECT_0, INFINITE + + # Build the list of handle to listen on. + handles = [] + if self.interrupt_handle: + handles.append(self.interrupt_handle) + if self.parent_handle: + handles.append(self.parent_handle) + arch = platform.architecture()[0] + c_int = ctypes.c_int64 if arch.startswith('64') else ctypes.c_int + + # Listen forever. + while True: + result = ctypes.windll.kernel32.WaitForMultipleObjects( + len(handles), # nCount + (c_int * len(handles))(*handles), # lpHandles + False, # bWaitAll + INFINITE) # dwMilliseconds + + if WAIT_OBJECT_0 <= result < len(handles): + handle = handles[result - WAIT_OBJECT_0] + + if handle == self.interrupt_handle: + # check if signal handler is callable + # to avoid 'int not callable' error (Python issue #23395) + if callable(signal.getsignal(signal.SIGINT)): + interrupt_main() + + elif handle == self.parent_handle: + get_logger().warning("Parent appears to have exited, shutting down.") + os._exit(1) + elif result < 0: + # wait failed, just give up and stop polling. + warnings.warn("""Parent poll failed. If the frontend dies, + the kernel may be left running. Please let us know + about your system (bitness, Python, etc.) at + ipython-dev@scipy.org""") + return diff --git a/packages/python/yap_kernel/yap_kernel/pickleutil.py b/packages/python/yap_kernel/yap_kernel/pickleutil.py new file mode 100644 index 000000000..b24aa70af --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/pickleutil.py @@ -0,0 +1,455 @@ +# encoding: utf-8 +"""Pickle related utilities. Perhaps this should be called 'can'.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import warnings +warnings.warn("yap_kernel.pickleutil is deprecated. It has moved to ipyparallel.", DeprecationWarning) + +import copy +import sys +from types import FunctionType + +try: + import cPickle as pickle +except ImportError: + import pickle + +from ipython_genutils import py3compat +from ipython_genutils.importstring import import_item +from ipython_genutils.py3compat import string_types, iteritems, buffer_to_bytes, buffer_to_bytes_py2 + +# This registers a hook when it's imported +try: + # available since ipyparallel 5.1.1 + from ipyparallel.serialize import codeutil +except ImportError: + # Deprecated since yap_kernel 4.3.1 + from yap_kernel import codeutil + +from traitlets.log import get_logger + +if py3compat.PY3: + buffer = memoryview + class_type = type +else: + from types import ClassType + class_type = (type, ClassType) + +try: + PICKLE_PROTOCOL = pickle.DEFAULT_PROTOCOL +except AttributeError: + PICKLE_PROTOCOL = pickle.HIGHEST_PROTOCOL + +def _get_cell_type(a=None): + """the type of a closure cell doesn't seem to be importable, + so just create one + """ + def inner(): + return a + return type(py3compat.get_closure(inner)[0]) + +cell_type = _get_cell_type() + +#------------------------------------------------------------------------------- +# Functions +#------------------------------------------------------------------------------- + + +def interactive(f): + """decorator for making functions appear as interactively defined. + This results in the function being linked to the user_ns as globals() + instead of the module globals(). + """ + + # build new FunctionType, so it can have the right globals + # interactive functions never have closures, that's kind of the point + if isinstance(f, FunctionType): + mainmod = __import__('__main__') + f = FunctionType(f.__code__, mainmod.__dict__, + f.__name__, f.__defaults__, + ) + # associate with __main__ for uncanning + f.__module__ = '__main__' + return f + + +def use_dill(): + """use dill to expand serialization support + + adds support for object methods and closures to serialization. + """ + # import dill causes most of the magic + import dill + + # dill doesn't work with cPickle, + # tell the two relevant modules to use plain pickle + + global pickle + pickle = dill + + try: + from yap_kernel import serialize + except ImportError: + pass + else: + serialize.pickle = dill + + # disable special function handling, let dill take care of it + can_map.pop(FunctionType, None) + +def use_cloudpickle(): + """use cloudpickle to expand serialization support + + adds support for object methods and closures to serialization. + """ + import cloudpickle + + global pickle + pickle = cloudpickle + + try: + from yap_kernel import serialize + except ImportError: + pass + else: + serialize.pickle = cloudpickle + + # disable special function handling, let cloudpickle take care of it + can_map.pop(FunctionType, None) + + +#------------------------------------------------------------------------------- +# Classes +#------------------------------------------------------------------------------- + + +class CannedObject(object): + def __init__(self, obj, keys=[], hook=None): + """can an object for safe pickling + + Parameters + ========== + + obj: + The object to be canned + keys: list (optional) + list of attribute names that will be explicitly canned / uncanned + hook: callable (optional) + An optional extra callable, + which can do additional processing of the uncanned object. + + large data may be offloaded into the buffers list, + used for zero-copy transfers. + """ + self.keys = keys + self.obj = copy.copy(obj) + self.hook = can(hook) + for key in keys: + setattr(self.obj, key, can(getattr(obj, key))) + + self.buffers = [] + + def get_object(self, g=None): + if g is None: + g = {} + obj = self.obj + for key in self.keys: + setattr(obj, key, uncan(getattr(obj, key), g)) + + if self.hook: + self.hook = uncan(self.hook, g) + self.hook(obj, g) + return self.obj + + +class Reference(CannedObject): + """object for wrapping a remote reference by name.""" + def __init__(self, name): + if not isinstance(name, string_types): + raise TypeError("illegal name: %r"%name) + self.name = name + self.buffers = [] + + def __repr__(self): + return ""%self.name + + def get_object(self, g=None): + if g is None: + g = {} + + return eval(self.name, g) + + +class CannedCell(CannedObject): + """Can a closure cell""" + def __init__(self, cell): + self.cell_contents = can(cell.cell_contents) + + def get_object(self, g=None): + cell_contents = uncan(self.cell_contents, g) + def inner(): + return cell_contents + return py3compat.get_closure(inner)[0] + + +class CannedFunction(CannedObject): + + def __init__(self, f): + self._check_type(f) + self.code = f.__code__ + if f.__defaults__: + self.defaults = [ can(fd) for fd in f.__defaults__ ] + else: + self.defaults = None + + closure = py3compat.get_closure(f) + if closure: + self.closure = tuple( can(cell) for cell in closure ) + else: + self.closure = None + + self.module = f.__module__ or '__main__' + self.__name__ = f.__name__ + self.buffers = [] + + def _check_type(self, obj): + assert isinstance(obj, FunctionType), "Not a function type" + + def get_object(self, g=None): + # try to load function back into its module: + if not self.module.startswith('__'): + __import__(self.module) + g = sys.modules[self.module].__dict__ + + if g is None: + g = {} + if self.defaults: + defaults = tuple(uncan(cfd, g) for cfd in self.defaults) + else: + defaults = None + if self.closure: + closure = tuple(uncan(cell, g) for cell in self.closure) + else: + closure = None + newFunc = FunctionType(self.code, g, self.__name__, defaults, closure) + return newFunc + +class CannedClass(CannedObject): + + def __init__(self, cls): + self._check_type(cls) + self.name = cls.__name__ + self.old_style = not isinstance(cls, type) + self._canned_dict = {} + for k,v in cls.__dict__.items(): + if k not in ('__weakref__', '__dict__'): + self._canned_dict[k] = can(v) + if self.old_style: + mro = [] + else: + mro = cls.mro() + + self.parents = [ can(c) for c in mro[1:] ] + self.buffers = [] + + def _check_type(self, obj): + assert isinstance(obj, class_type), "Not a class type" + + def get_object(self, g=None): + parents = tuple(uncan(p, g) for p in self.parents) + return type(self.name, parents, uncan_dict(self._canned_dict, g=g)) + +class CannedArray(CannedObject): + def __init__(self, obj): + from numpy import ascontiguousarray + self.shape = obj.shape + self.dtype = obj.dtype.descr if obj.dtype.fields else obj.dtype.str + self.pickled = False + if sum(obj.shape) == 0: + self.pickled = True + elif obj.dtype == 'O': + # can't handle object dtype with buffer approach + self.pickled = True + elif obj.dtype.fields and any(dt == 'O' for dt,sz in obj.dtype.fields.values()): + self.pickled = True + if self.pickled: + # just pickle it + self.buffers = [pickle.dumps(obj, PICKLE_PROTOCOL)] + else: + # ensure contiguous + obj = ascontiguousarray(obj, dtype=None) + self.buffers = [buffer(obj)] + + def get_object(self, g=None): + from numpy import frombuffer + data = self.buffers[0] + if self.pickled: + # we just pickled it + return pickle.loads(buffer_to_bytes_py2(data)) + else: + if not py3compat.PY3 and isinstance(data, memoryview): + # frombuffer doesn't accept memoryviews on Python 2, + # so cast to old-style buffer + data = buffer(data.tobytes()) + return frombuffer(data, dtype=self.dtype).reshape(self.shape) + + +class CannedBytes(CannedObject): + wrap = staticmethod(buffer_to_bytes) + + def __init__(self, obj): + self.buffers = [obj] + + def get_object(self, g=None): + data = self.buffers[0] + return self.wrap(data) + +class CannedBuffer(CannedBytes): + wrap = buffer + +class CannedMemoryView(CannedBytes): + wrap = memoryview + +#------------------------------------------------------------------------------- +# Functions +#------------------------------------------------------------------------------- + +def _import_mapping(mapping, original=None): + """import any string-keys in a type mapping + + """ + log = get_logger() + log.debug("Importing canning map") + for key,value in list(mapping.items()): + if isinstance(key, string_types): + try: + cls = import_item(key) + except Exception: + if original and key not in original: + # only message on user-added classes + log.error("canning class not importable: %r", key, exc_info=True) + mapping.pop(key) + else: + mapping[cls] = mapping.pop(key) + +def istype(obj, check): + """like isinstance(obj, check), but strict + + This won't catch subclasses. + """ + if isinstance(check, tuple): + for cls in check: + if type(obj) is cls: + return True + return False + else: + return type(obj) is check + +def can(obj): + """prepare an object for pickling""" + + import_needed = False + + for cls,canner in iteritems(can_map): + if isinstance(cls, string_types): + import_needed = True + break + elif istype(obj, cls): + return canner(obj) + + if import_needed: + # perform can_map imports, then try again + # this will usually only happen once + _import_mapping(can_map, _original_can_map) + return can(obj) + + return obj + +def can_class(obj): + if isinstance(obj, class_type) and obj.__module__ == '__main__': + return CannedClass(obj) + else: + return obj + +def can_dict(obj): + """can the *values* of a dict""" + if istype(obj, dict): + newobj = {} + for k, v in iteritems(obj): + newobj[k] = can(v) + return newobj + else: + return obj + +sequence_types = (list, tuple, set) + +def can_sequence(obj): + """can the elements of a sequence""" + if istype(obj, sequence_types): + t = type(obj) + return t([can(i) for i in obj]) + else: + return obj + +def uncan(obj, g=None): + """invert canning""" + + import_needed = False + for cls,uncanner in iteritems(uncan_map): + if isinstance(cls, string_types): + import_needed = True + break + elif isinstance(obj, cls): + return uncanner(obj, g) + + if import_needed: + # perform uncan_map imports, then try again + # this will usually only happen once + _import_mapping(uncan_map, _original_uncan_map) + return uncan(obj, g) + + return obj + +def uncan_dict(obj, g=None): + if istype(obj, dict): + newobj = {} + for k, v in iteritems(obj): + newobj[k] = uncan(v,g) + return newobj + else: + return obj + +def uncan_sequence(obj, g=None): + if istype(obj, sequence_types): + t = type(obj) + return t([uncan(i,g) for i in obj]) + else: + return obj + +#------------------------------------------------------------------------------- +# API dictionaries +#------------------------------------------------------------------------------- + +# These dicts can be extended for custom serialization of new objects + +can_map = { + 'numpy.ndarray' : CannedArray, + FunctionType : CannedFunction, + bytes : CannedBytes, + memoryview : CannedMemoryView, + cell_type : CannedCell, + class_type : can_class, +} +if buffer is not memoryview: + can_map[buffer] = CannedBuffer + +uncan_map = { + CannedObject : lambda obj, g: obj.get_object(g), + dict : uncan_dict, +} + +# for use in _import_mapping: +_original_can_map = can_map.copy() +_original_uncan_map = uncan_map.copy() diff --git a/packages/python/yap_kernel/yap_kernel/pylab/__init__.py b/packages/python/yap_kernel/yap_kernel/pylab/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/packages/python/yap_kernel/yap_kernel/pylab/backend_inline.py b/packages/python/yap_kernel/yap_kernel/pylab/backend_inline.py new file mode 100644 index 000000000..63b96935c --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/pylab/backend_inline.py @@ -0,0 +1,163 @@ +"""A matplotlib backend for publishing figures via display_data""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from __future__ import print_function + +import matplotlib +from matplotlib.backends.backend_agg import new_figure_manager, FigureCanvasAgg # analysis: ignore +from matplotlib._pylab_helpers import Gcf + +from IPython.core.getipython import get_ipython +from IPython.core.display import display + +from .config import InlineBackend + + +def show(close=None, block=None): + """Show all figures as SVG/PNG payloads sent to the IPython clients. + + Parameters + ---------- + close : bool, optional + If true, a ``plt.close('all')`` call is automatically issued after + sending all the figures. If this is set, the figures will entirely + removed from the internal list of figures. + block : Not used. + The `block` parameter is a Matplotlib experimental parameter. + We accept it in the function signature for compatibility with other + backends. + """ + if close is None: + close = InlineBackend.instance().close_figures + try: + for figure_manager in Gcf.get_all_fig_managers(): + display(figure_manager.canvas.figure) + finally: + show._to_draw = [] + # only call close('all') if any to close + # close triggers gc.collect, which can be slow + if close and Gcf.get_all_fig_managers(): + matplotlib.pyplot.close('all') + + +# This flag will be reset by draw_if_interactive when called +show._draw_called = False +# list of figures to draw when flush_figures is called +show._to_draw = [] + + +def draw_if_interactive(): + """ + Is called after every pylab drawing command + """ + # signal that the current active figure should be sent at the end of + # execution. Also sets the _draw_called flag, signaling that there will be + # something to send. At the end of the code execution, a separate call to + # flush_figures() will act upon these values + manager = Gcf.get_active() + if manager is None: + return + fig = manager.canvas.figure + + # Hack: matplotlib FigureManager objects in interacive backends (at least + # in some of them) monkeypatch the figure object and add a .show() method + # to it. This applies the same monkeypatch in order to support user code + # that might expect `.show()` to be part of the official API of figure + # objects. + # For further reference: + # https://github.com/ipython/ipython/issues/1612 + # https://github.com/matplotlib/matplotlib/issues/835 + + if not hasattr(fig, 'show'): + # Queue up `fig` for display + fig.show = lambda *a: display(fig) + + # If matplotlib was manually set to non-interactive mode, this function + # should be a no-op (otherwise we'll generate duplicate plots, since a user + # who set ioff() manually expects to make separate draw/show calls). + if not matplotlib.is_interactive(): + return + + # ensure current figure will be drawn, and each subsequent call + # of draw_if_interactive() moves the active figure to ensure it is + # drawn last + try: + show._to_draw.remove(fig) + except ValueError: + # ensure it only appears in the draw list once + pass + # Queue up the figure for drawing in next show() call + show._to_draw.append(fig) + show._draw_called = True + + +def flush_figures(): + """Send all figures that changed + + This is meant to be called automatically and will call show() if, during + prior code execution, there had been any calls to draw_if_interactive. + + This function is meant to be used as a post_execute callback in IPython, + so user-caused errors are handled with showtraceback() instead of being + allowed to raise. If this function is not called from within IPython, + then these exceptions will raise. + """ + if not show._draw_called: + return + + if InlineBackend.instance().close_figures: + # ignore the tracking, just draw and close all figures + try: + return show(True) + except Exception as e: + # safely show traceback if in IPython, else raise + ip = get_ipython() + if ip is None: + raise e + else: + ip.showtraceback() + return + try: + # exclude any figures that were closed: + active = set([fm.canvas.figure for fm in Gcf.get_all_fig_managers()]) + for fig in [ fig for fig in show._to_draw if fig in active ]: + try: + display(fig) + except Exception as e: + # safely show traceback if in IPython, else raise + ip = get_ipython() + if ip is None: + raise e + else: + ip.showtraceback() + return + finally: + # clear flags for next round + show._to_draw = [] + show._draw_called = False + + +# Changes to matplotlib in version 1.2 requires a mpl backend to supply a default +# figurecanvas. This is set here to a Agg canvas +# See https://github.com/matplotlib/matplotlib/pull/1125 +FigureCanvas = FigureCanvasAgg + +def _enable_matplotlib_integration(): + """Enable extra IPython matplotlib integration when we are loaded as the matplotlib backend.""" + from matplotlib import get_backend + ip = get_ipython() + backend = get_backend() + if ip and backend == 'module://%s' % __name__: + from IPython.core.pylabtools import configure_inline_support + try: + configure_inline_support(ip, backend) + except ImportError: + # bugs may cause a circular import on Python 2 + def configure_once(*args): + configure_inline_support(ip, backend) + ip.events.unregister('post_run_cell', configure_once) + ip.events.register('post_run_cell', configure_once) + +_enable_matplotlib_integration() diff --git a/packages/python/yap_kernel/yap_kernel/pylab/config.py b/packages/python/yap_kernel/yap_kernel/pylab/config.py new file mode 100644 index 000000000..249389fab --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/pylab/config.py @@ -0,0 +1,110 @@ +"""Configurable for configuring the IPython inline backend + +This module does not import anything from matplotlib. +""" +#----------------------------------------------------------------------------- +# Copyright (C) 2011 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +from traitlets.config.configurable import SingletonConfigurable +from traitlets import ( + Dict, Instance, Set, Bool, TraitError, Unicode +) + +#----------------------------------------------------------------------------- +# Configurable for inline backend options +#----------------------------------------------------------------------------- + +def pil_available(): + """Test if PIL/Pillow is available""" + out = False + try: + from PIL import Image + out = True + except: + pass + return out + +# inherit from InlineBackendConfig for deprecation purposes +class InlineBackendConfig(SingletonConfigurable): + pass + +class InlineBackend(InlineBackendConfig): + """An object to store configuration of the inline backend.""" + + # The typical default figure size is too large for inline use, + # so we shrink the figure size to 6x4, and tweak fonts to + # make that fit. + rc = Dict({'figure.figsize': (6.0,4.0), + # play nicely with white background in the Qt and notebook frontend + 'figure.facecolor': (1,1,1,0), + 'figure.edgecolor': (1,1,1,0), + # 12pt labels get cutoff on 6x4 logplots, so use 10pt. + 'font.size': 10, + # 72 dpi matches SVG/qtconsole + # this only affects PNG export, as SVG has no dpi setting + 'figure.dpi': 72, + # 10pt still needs a little more room on the xlabel: + 'figure.subplot.bottom' : .125 + }, + help="""Subset of matplotlib rcParams that should be different for the + inline backend.""" + ).tag(config=True) + + figure_formats = Set({'png'}, + help="""A set of figure formats to enable: 'png', + 'retina', 'jpeg', 'svg', 'pdf'.""").tag(config=True) + + def _update_figure_formatters(self): + if self.shell is not None: + from IPython.core.pylabtools import select_figure_formats + select_figure_formats(self.shell, self.figure_formats, **self.print_figure_kwargs) + + def _figure_formats_changed(self, name, old, new): + if 'jpg' in new or 'jpeg' in new: + if not pil_available(): + raise TraitError("Requires PIL/Pillow for JPG figures") + self._update_figure_formatters() + + figure_format = Unicode(help="""The figure format to enable (deprecated + use `figure_formats` instead)""").tag(config=True) + + def _figure_format_changed(self, name, old, new): + if new: + self.figure_formats = {new} + + print_figure_kwargs = Dict({'bbox_inches' : 'tight'}, + help="""Extra kwargs to be passed to fig.canvas.print_figure. + + Logical examples include: bbox_inches, quality (for jpeg figures), etc. + """ + ).tag(config=True) + _print_figure_kwargs_changed = _update_figure_formatters + + close_figures = Bool(True, + help="""Close all figures at the end of each cell. + + When True, ensures that each cell starts with no active figures, but it + also means that one must keep track of references in order to edit or + redraw figures in subsequent cells. This mode is ideal for the notebook, + where residual plots from other cells might be surprising. + + When False, one must call figure() to create new figures. This means + that gcf() and getfigs() can reference figures created in other cells, + and the active figure can continue to be edited with pylab/pyplot + methods that reference the current active figure. This mode facilitates + iterative editing of figures, and behaves most consistently with + other matplotlib backends, but figure barriers between cells must + be explicit. + """).tag(config=True) + + shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', + allow_none=True) + diff --git a/packages/python/yap_kernel/yap_kernel/resources/logo-32x32.png b/packages/python/yap_kernel/yap_kernel/resources/logo-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..be81330765764699553aa4fbaf0e9fc27c20c6d2 GIT binary patch literal 1084 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 threshold: + # buffer larger than threshold, prevent pickling + obj.buffers[i] = None + buffers.append(buf) + # buffer too small for separate send, coerce to bytes + # because pickling buffer objects just results in broken pointers + elif isinstance(buf, memoryview): + obj.buffers[i] = buf.tobytes() + elif isinstance(buf, buffer): + obj.buffers[i] = bytes(buf) + return buffers + +def _restore_buffers(obj, buffers): + """restore buffers extracted by """ + if isinstance(obj, CannedObject) and obj.buffers: + for i,buf in enumerate(obj.buffers): + if buf is None: + obj.buffers[i] = buffers.pop(0) + +def serialize_object(obj, buffer_threshold=MAX_BYTES, item_threshold=MAX_ITEMS): + """Serialize an object into a list of sendable buffers. + + Parameters + ---------- + + obj : object + The object to be serialized + buffer_threshold : int + The threshold (in bytes) for pulling out data buffers + to avoid pickling them. + item_threshold : int + The maximum number of items over which canning will iterate. + Containers (lists, dicts) larger than this will be pickled without + introspection. + + Returns + ------- + [bufs] : list of buffers representing the serialized object. + """ + buffers = [] + if istype(obj, sequence_types) and len(obj) < item_threshold: + cobj = can_sequence(obj) + for c in cobj: + buffers.extend(_extract_buffers(c, buffer_threshold)) + elif istype(obj, dict) and len(obj) < item_threshold: + cobj = {} + for k in sorted(obj): + c = can(obj[k]) + buffers.extend(_extract_buffers(c, buffer_threshold)) + cobj[k] = c + else: + cobj = can(obj) + buffers.extend(_extract_buffers(cobj, buffer_threshold)) + + buffers.insert(0, pickle.dumps(cobj, PICKLE_PROTOCOL)) + return buffers + +def deserialize_object(buffers, g=None): + """reconstruct an object serialized by serialize_object from data buffers. + + Parameters + ---------- + + bufs : list of buffers/bytes + + g : globals to be used when uncanning + + Returns + ------- + + (newobj, bufs) : unpacked object, and the list of remaining unused buffers. + """ + bufs = list(buffers) + pobj = buffer_to_bytes_py2(bufs.pop(0)) + canned = pickle.loads(pobj) + if istype(canned, sequence_types) and len(canned) < MAX_ITEMS: + for c in canned: + _restore_buffers(c, bufs) + newobj = uncan_sequence(canned, g) + elif istype(canned, dict) and len(canned) < MAX_ITEMS: + newobj = {} + for k in sorted(canned): + c = canned[k] + _restore_buffers(c, bufs) + newobj[k] = uncan(c, g) + else: + _restore_buffers(canned, bufs) + newobj = uncan(canned, g) + + return newobj, bufs + +def pack_apply_message(f, args, kwargs, buffer_threshold=MAX_BYTES, item_threshold=MAX_ITEMS): + """pack up a function, args, and kwargs to be sent over the wire + + Each element of args/kwargs will be canned for special treatment, + but inspection will not go any deeper than that. + + Any object whose data is larger than `threshold` will not have their data copied + (only numpy arrays and bytes/buffers support zero-copy) + + Message will be a list of bytes/buffers of the format: + + [ cf, pinfo, , ] + + With length at least two + len(args) + len(kwargs) + """ + + arg_bufs = list(chain.from_iterable( + serialize_object(arg, buffer_threshold, item_threshold) for arg in args)) + + kw_keys = sorted(kwargs.keys()) + kwarg_bufs = list(chain.from_iterable( + serialize_object(kwargs[key], buffer_threshold, item_threshold) for key in kw_keys)) + + info = dict(nargs=len(args), narg_bufs=len(arg_bufs), kw_keys=kw_keys) + + msg = [pickle.dumps(can(f), PICKLE_PROTOCOL)] + msg.append(pickle.dumps(info, PICKLE_PROTOCOL)) + msg.extend(arg_bufs) + msg.extend(kwarg_bufs) + + return msg + +def unpack_apply_message(bufs, g=None, copy=True): + """unpack f,args,kwargs from buffers packed by pack_apply_message() + Returns: original f,args,kwargs""" + bufs = list(bufs) # allow us to pop + assert len(bufs) >= 2, "not enough buffers!" + pf = buffer_to_bytes_py2(bufs.pop(0)) + f = uncan(pickle.loads(pf), g) + pinfo = buffer_to_bytes_py2(bufs.pop(0)) + info = pickle.loads(pinfo) + arg_bufs, kwarg_bufs = bufs[:info['narg_bufs']], bufs[info['narg_bufs']:] + + args = [] + for i in range(info['nargs']): + arg, arg_bufs = deserialize_object(arg_bufs, g) + args.append(arg) + args = tuple(args) + assert not arg_bufs, "Shouldn't be any arg bufs left over" + + kwargs = {} + for key in info['kw_keys']: + kwarg, kwarg_bufs = deserialize_object(kwarg_bufs, g) + kwargs[key] = kwarg + assert not kwarg_bufs, "Shouldn't be any kwarg bufs left over" + + return f,args,kwargs diff --git a/packages/python/yap_kernel/yap_kernel/tests/__init__.py b/packages/python/yap_kernel/yap_kernel/tests/__init__.py new file mode 100644 index 000000000..7419b68d3 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/tests/__init__.py @@ -0,0 +1,49 @@ +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import os +import shutil +import sys +import tempfile + +try: + from unittest.mock import patch +except ImportError: + from mock import patch + +from jupyter_core import paths as jpaths +from IPython import paths as ipaths +from yap_kernel.kernelspec import install + +pjoin = os.path.join + +tmp = None +patchers = [] + +def setup(): + """setup temporary env for tests""" + global tmp + tmp = tempfile.mkdtemp() + patchers[:] = [ + patch.dict(os.environ, { + 'HOME': tmp, + # Let tests work with --user install when HOME is changed: + 'PYTHONPATH': os.pathsep.join(sys.path), + }), + ] + for p in patchers: + p.start() + + # install IPython in the temp home: + install(user=True) + + +def teardown(): + for p in patchers: + p.stop() + + try: + shutil.rmtree(tmp) + except (OSError, IOError): + # no such file + pass diff --git a/packages/python/yap_kernel/yap_kernel/tests/test_connect.py b/packages/python/yap_kernel/yap_kernel/tests/test_connect.py new file mode 100644 index 000000000..cb9485c54 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/tests/test_connect.py @@ -0,0 +1,63 @@ +"""Tests for kernel connection utilities""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import json +import os + +import nose.tools as nt + +from traitlets.config import Config +from ipython_genutils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory +from ipython_genutils.py3compat import str_to_bytes +from yap_kernel import connect +from yap_kernel.kernelapp import YAP_KernelApp + + +sample_info = dict(ip='1.2.3.4', transport='ipc', + shell_port=1, hb_port=2, iopub_port=3, stdin_port=4, control_port=5, + key=b'abc123', signature_scheme='hmac-md5', + ) + + +class DummyKernelApp(YAP_KernelApp): + def initialize(self, argv=[]): + self.init_profile_dir() + self.init_connection_file() + + +def test_get_connection_file(): + cfg = Config() + with TemporaryWorkingDirectory() as d: + cfg.ProfileDir.location = d + cf = 'kernel.json' + app = DummyKernelApp(config=cfg, connection_file=cf) + app.initialize() + + profile_cf = os.path.join(app.connection_dir, cf) + nt.assert_equal(profile_cf, app.abs_connection_file) + with open(profile_cf, 'w') as f: + f.write("{}") + nt.assert_true(os.path.exists(profile_cf)) + nt.assert_equal(connect.get_connection_file(app), profile_cf) + + app.connection_file = cf + nt.assert_equal(connect.get_connection_file(app), profile_cf) + + +def test_get_connection_info(): + with TemporaryDirectory() as d: + cf = os.path.join(d, 'kernel.json') + connect.write_connection_file(cf, **sample_info) + json_info = connect.get_connection_info(cf) + info = connect.get_connection_info(cf, unpack=True) + + nt.assert_equal(type(json_info), type("")) + sub_info = {k:v for k,v in info.items() if k in sample_info} + nt.assert_equal(sub_info, sample_info) + + info2 = json.loads(json_info) + info2['key'] = str_to_bytes(info2['key']) + sub_info2 = {k:v for k,v in info.items() if k in sample_info} + nt.assert_equal(sub_info2, sample_info) diff --git a/packages/python/yap_kernel/yap_kernel/tests/test_embed_kernel.py b/packages/python/yap_kernel/yap_kernel/tests/test_embed_kernel.py new file mode 100644 index 000000000..03de53df7 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/tests/test_embed_kernel.py @@ -0,0 +1,163 @@ +"""test IPython.embed_kernel()""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import os +import shutil +import sys +import tempfile +import time + +from contextlib import contextmanager +from subprocess import Popen, PIPE + +import nose.tools as nt + +from jupyter_client import BlockingKernelClient +from jupyter_core import paths +from IPython.paths import get_ipython_dir +from ipython_genutils import py3compat +from ipython_genutils.py3compat import unicode_type + + +SETUP_TIMEOUT = 60 +TIMEOUT = 15 + + +@contextmanager +def setup_kernel(cmd): + """start an embedded kernel in a subprocess, and wait for it to be ready + + Returns + ------- + kernel_manager: connected KernelManager instance + """ + kernel = Popen([sys.executable, '-c', cmd], stdout=PIPE, stderr=PIPE) + connection_file = os.path.join( + paths.jupyter_runtime_dir(), + 'kernel-%i.json' % kernel.pid, + ) + # wait for connection file to exist, timeout after 5s + tic = time.time() + while not os.path.exists(connection_file) \ + and kernel.poll() is None \ + and time.time() < tic + SETUP_TIMEOUT: + time.sleep(0.1) + + if kernel.poll() is not None: + o,e = kernel.communicate() + e = py3compat.cast_unicode(e) + raise IOError("Kernel failed to start:\n%s" % e) + + if not os.path.exists(connection_file): + if kernel.poll() is None: + kernel.terminate() + raise IOError("Connection file %r never arrived" % connection_file) + + client = BlockingKernelClient(connection_file=connection_file) + client.load_connection_file() + client.start_channels() + client.wait_for_ready() + + try: + yield client + finally: + client.stop_channels() + kernel.terminate() + +def test_embed_kernel_basic(): + """IPython.embed_kernel() is basically functional""" + cmd = '\n'.join([ + 'from IPython import embed_kernel', + 'def go():', + ' a=5', + ' b="hi there"', + ' embed_kernel()', + 'go()', + '', + ]) + + with setup_kernel(cmd) as client: + # oinfo a (int) + msg_id = client.inspect('a') + msg = client.get_shell_msg(block=True, timeout=TIMEOUT) + content = msg['content'] + nt.assert_true(content['found']) + + msg_id = client.execute("c=a*2") + msg = client.get_shell_msg(block=True, timeout=TIMEOUT) + content = msg['content'] + nt.assert_equal(content['status'], u'ok') + + # oinfo c (should be 10) + msg_id = client.inspect('c') + msg = client.get_shell_msg(block=True, timeout=TIMEOUT) + content = msg['content'] + nt.assert_true(content['found']) + text = content['data']['text/plain'] + nt.assert_in('10', text) + +def test_embed_kernel_namespace(): + """IPython.embed_kernel() inherits calling namespace""" + cmd = '\n'.join([ + 'from IPython import embed_kernel', + 'def go():', + ' a=5', + ' b="hi there"', + ' embed_kernel()', + 'go()', + '', + ]) + + with setup_kernel(cmd) as client: + # oinfo a (int) + msg_id = client.inspect('a') + msg = client.get_shell_msg(block=True, timeout=TIMEOUT) + content = msg['content'] + nt.assert_true(content['found']) + text = content['data']['text/plain'] + nt.assert_in(u'5', text) + + # oinfo b (str) + msg_id = client.inspect('b') + msg = client.get_shell_msg(block=True, timeout=TIMEOUT) + content = msg['content'] + nt.assert_true(content['found']) + text = content['data']['text/plain'] + nt.assert_in(u'hi there', text) + + # oinfo c (undefined) + msg_id = client.inspect('c') + msg = client.get_shell_msg(block=True, timeout=TIMEOUT) + content = msg['content'] + nt.assert_false(content['found']) + +def test_embed_kernel_reentrant(): + """IPython.embed_kernel() can be called multiple times""" + cmd = '\n'.join([ + 'from IPython import embed_kernel', + 'count = 0', + 'def go():', + ' global count', + ' embed_kernel()', + ' count = count + 1', + '', + 'while True:' + ' go()', + '', + ]) + + with setup_kernel(cmd) as client: + for i in range(5): + msg_id = client.inspect('count') + msg = client.get_shell_msg(block=True, timeout=TIMEOUT) + content = msg['content'] + nt.assert_true(content['found']) + text = content['data']['text/plain'] + nt.assert_in(unicode_type(i), text) + + # exit from embed_kernel + client.execute("get_ipython().exit_now = True") + msg = client.get_shell_msg(block=True, timeout=TIMEOUT) + time.sleep(0.2) diff --git a/packages/python/yap_kernel/yap_kernel/tests/test_io.py b/packages/python/yap_kernel/yap_kernel/tests/test_io.py new file mode 100644 index 000000000..b41e47326 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/tests/test_io.py @@ -0,0 +1,42 @@ +"""Test IO capturing functionality""" + +import io + +import zmq + +from jupyter_client.session import Session +from yap_kernel.iostream import IOPubThread, OutStream + +import nose.tools as nt + +def test_io_api(): + """Test that wrapped stdout has the same API as a normal TextIO object""" + session = Session() + ctx = zmq.Context() + pub = ctx.socket(zmq.PUB) + thread = IOPubThread(pub) + thread.start() + + stream = OutStream(session, thread, 'stdout') + + # cleanup unused zmq objects before we start testing + thread.stop() + thread.close() + ctx.term() + + assert stream.errors is None + assert not stream.isatty() + with nt.assert_raises(io.UnsupportedOperation): + stream.detach() + with nt.assert_raises(io.UnsupportedOperation): + next(stream) + with nt.assert_raises(io.UnsupportedOperation): + stream.read() + with nt.assert_raises(io.UnsupportedOperation): + stream.readline() + with nt.assert_raises(io.UnsupportedOperation): + stream.seek() + with nt.assert_raises(io.UnsupportedOperation): + stream.tell() + + diff --git a/packages/python/yap_kernel/yap_kernel/tests/test_jsonutil.py b/packages/python/yap_kernel/yap_kernel/tests/test_jsonutil.py new file mode 100644 index 000000000..794ff6c96 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/tests/test_jsonutil.py @@ -0,0 +1,113 @@ +# coding: utf-8 +"""Test suite for our JSON utilities.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import json +import sys + +if sys.version_info < (3,): + from base64 import decodestring as decodebytes +else: + from base64 import decodebytes + +from datetime import datetime +import numbers + +import nose.tools as nt + +from .. import jsonutil +from ..jsonutil import json_clean, encode_images +from ipython_genutils.py3compat import unicode_to_str, str_to_bytes, iteritems + +class MyInt(object): + def __int__(self): + return 389 +numbers.Integral.register(MyInt) + +class MyFloat(object): + def __float__(self): + return 3.14 +numbers.Real.register(MyFloat) + + +def test(): + # list of input/expected output. Use None for the expected output if it + # can be the same as the input. + pairs = [(1, None), # start with scalars + (1.0, None), + ('a', None), + (True, None), + (False, None), + (None, None), + # Containers + ([1, 2], None), + ((1, 2), [1, 2]), + (set([1, 2]), [1, 2]), + (dict(x=1), None), + ({'x': 1, 'y':[1,2,3], '1':'int'}, None), + # More exotic objects + ((x for x in range(3)), [0, 1, 2]), + (iter([1, 2]), [1, 2]), + (datetime(1991, 7, 3, 12, 00), "1991-07-03T12:00:00.000000"), + (MyFloat(), 3.14), + (MyInt(), 389) + ] + + for val, jval in pairs: + if jval is None: + jval = val + out = json_clean(val) + # validate our cleanup + nt.assert_equal(out, jval) + # and ensure that what we return, indeed encodes cleanly + json.loads(json.dumps(out)) + + +def test_encode_images(): + # invalid data, but the header and footer are from real files + pngdata = b'\x89PNG\r\n\x1a\nblahblahnotactuallyvalidIEND\xaeB`\x82' + jpegdata = b'\xff\xd8\xff\xe0\x00\x10JFIFblahblahjpeg(\xa0\x0f\xff\xd9' + pdfdata = b'%PDF-1.\ntrailer<>]>>>>>>' + + fmt = { + 'image/png' : pngdata, + 'image/jpeg' : jpegdata, + 'application/pdf' : pdfdata + } + encoded = encode_images(fmt) + for key, value in iteritems(fmt): + # encoded has unicode, want bytes + decoded = decodebytes(encoded[key].encode('ascii')) + nt.assert_equal(decoded, value) + encoded2 = encode_images(encoded) + nt.assert_equal(encoded, encoded2) + + b64_str = {} + for key, encoded in iteritems(encoded): + b64_str[key] = unicode_to_str(encoded) + encoded3 = encode_images(b64_str) + nt.assert_equal(encoded3, b64_str) + for key, value in iteritems(fmt): + # encoded3 has str, want bytes + decoded = decodebytes(str_to_bytes(encoded3[key])) + nt.assert_equal(decoded, value) + +def test_lambda(): + with nt.assert_raises(ValueError): + json_clean(lambda : 1) + + +def test_exception(): + bad_dicts = [{1:'number', '1':'string'}, + {True:'bool', 'True':'string'}, + ] + for d in bad_dicts: + nt.assert_raises(ValueError, json_clean, d) + + +def test_unicode_dict(): + data = {u'üniço∂e': u'üniço∂e'} + clean = jsonutil.json_clean(data) + nt.assert_equal(data, clean) diff --git a/packages/python/yap_kernel/yap_kernel/tests/test_kernel.py b/packages/python/yap_kernel/yap_kernel/tests/test_kernel.py new file mode 100644 index 000000000..0c4a2b87b --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/tests/test_kernel.py @@ -0,0 +1,283 @@ +# coding: utf-8 +"""test the IPython Kernel""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import io +import os.path +import sys +import time + +import nose.tools as nt + +from IPython.testing import decorators as dec, tools as tt +from ipython_genutils import py3compat +from IPython.paths import locate_profile +from ipython_genutils.tempdir import TemporaryDirectory + +from .utils import ( + new_kernel, kernel, TIMEOUT, assemble_output, execute, + flush_channels, wait_for_idle) + + +def _check_master(kc, expected=True, stream="stdout"): + execute(kc=kc, code="import sys") + flush_channels(kc) + msg_id, content = execute(kc=kc, code="print (sys.%s._is_master_process())" % stream) + stdout, stderr = assemble_output(kc.iopub_channel) + nt.assert_equal(stdout.strip(), repr(expected)) + + +def _check_status(content): + """If status=error, show the traceback""" + if content['status'] == 'error': + nt.assert_true(False, ''.join(['\n'] + content['traceback'])) + + +# printing tests + +def test_simple_print(): + """simple print statement in kernel""" + with kernel() as kc: + iopub = kc.iopub_channel + msg_id, content = execute(kc=kc, code="print ('hi')") + stdout, stderr = assemble_output(iopub) + nt.assert_equal(stdout, 'hi\n') + nt.assert_equal(stderr, '') + _check_master(kc, expected=True) + + +def test_sys_path(): + """test that sys.path doesn't get messed up by default""" + with kernel() as kc: + msg_id, content = execute(kc=kc, code="import sys; print (repr(sys.path[0]))") + stdout, stderr = assemble_output(kc.iopub_channel) + nt.assert_equal(stdout, "''\n") + +def test_sys_path_profile_dir(): + """test that sys.path doesn't get messed up when `--profile-dir` is specified""" + + with new_kernel(['--profile-dir', locate_profile('default')]) as kc: + msg_id, content = execute(kc=kc, code="import sys; print (repr(sys.path[0]))") + stdout, stderr = assemble_output(kc.iopub_channel) + nt.assert_equal(stdout, "''\n") + +@dec.skipif(sys.platform == 'win32', "subprocess prints fail on Windows") +def test_subprocess_print(): + """printing from forked mp.Process""" + with new_kernel() as kc: + iopub = kc.iopub_channel + + _check_master(kc, expected=True) + flush_channels(kc) + np = 5 + code = '\n'.join([ + "from __future__ import print_function", + "import time", + "import multiprocessing as mp", + "pool = [mp.Process(target=print, args=('hello', i,)) for i in range(%i)]" % np, + "for p in pool: p.start()", + "for p in pool: p.join()", + "time.sleep(0.5)," + ]) + + msg_id, content = execute(kc=kc, code=code) + stdout, stderr = assemble_output(iopub) + nt.assert_equal(stdout.count("hello"), np, stdout) + for n in range(np): + nt.assert_equal(stdout.count(str(n)), 1, stdout) + nt.assert_equal(stderr, '') + _check_master(kc, expected=True) + _check_master(kc, expected=True, stream="stderr") + + +def test_subprocess_noprint(): + """mp.Process without print doesn't trigger iostream mp_mode""" + with kernel() as kc: + iopub = kc.iopub_channel + + np = 5 + code = '\n'.join([ + "import multiprocessing as mp", + "pool = [mp.Process(target=range, args=(i,)) for i in range(%i)]" % np, + "for p in pool: p.start()", + "for p in pool: p.join()" + ]) + + msg_id, content = execute(kc=kc, code=code) + stdout, stderr = assemble_output(iopub) + nt.assert_equal(stdout, '') + nt.assert_equal(stderr, '') + + _check_master(kc, expected=True) + _check_master(kc, expected=True, stream="stderr") + + +@dec.skipif(sys.platform == 'win32', "subprocess prints fail on Windows") +def test_subprocess_error(): + """error in mp.Process doesn't crash""" + with new_kernel() as kc: + iopub = kc.iopub_channel + + code = '\n'.join([ + "import multiprocessing as mp", + "p = mp.Process(target=int, args=('hi',))", + "p.start()", + "p.join()", + ]) + + msg_id, content = execute(kc=kc, code=code) + stdout, stderr = assemble_output(iopub) + nt.assert_equal(stdout, '') + nt.assert_true("ValueError" in stderr, stderr) + + _check_master(kc, expected=True) + _check_master(kc, expected=True, stream="stderr") + +# raw_input tests + +def test_raw_input(): + """test [raw_]input""" + with kernel() as kc: + iopub = kc.iopub_channel + + input_f = "input" if py3compat.PY3 else "raw_input" + theprompt = "prompt> " + code = 'print({input_f}("{theprompt}"))'.format(**locals()) + msg_id = kc.execute(code, allow_stdin=True) + msg = kc.get_stdin_msg(block=True, timeout=TIMEOUT) + nt.assert_equal(msg['header']['msg_type'], u'input_request') + content = msg['content'] + nt.assert_equal(content['prompt'], theprompt) + text = "some text" + kc.input(text) + reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) + nt.assert_equal(reply['content']['status'], 'ok') + stdout, stderr = assemble_output(iopub) + nt.assert_equal(stdout, text + "\n") + + +@dec.skipif(py3compat.PY3) +def test_eval_input(): + """test input() on Python 2""" + with kernel() as kc: + iopub = kc.iopub_channel + + input_f = "input" if py3compat.PY3 else "raw_input" + theprompt = "prompt> " + code = 'print(input("{theprompt}"))'.format(**locals()) + msg_id = kc.execute(code, allow_stdin=True) + msg = kc.get_stdin_msg(block=True, timeout=TIMEOUT) + nt.assert_equal(msg['header']['msg_type'], u'input_request') + content = msg['content'] + nt.assert_equal(content['prompt'], theprompt) + kc.input("1+1") + reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) + nt.assert_equal(reply['content']['status'], 'ok') + stdout, stderr = assemble_output(iopub) + nt.assert_equal(stdout, "2\n") + + +def test_save_history(): + # Saving history from the kernel with %hist -f was failing because of + # unicode problems on Python 2. + with kernel() as kc, TemporaryDirectory() as td: + file = os.path.join(td, 'hist.out') + execute(u'a=1', kc=kc) + wait_for_idle(kc) + execute(u'b=u"abcþ"', kc=kc) + wait_for_idle(kc) + _, reply = execute("%hist -f " + file, kc=kc) + nt.assert_equal(reply['status'], 'ok') + with io.open(file, encoding='utf-8') as f: + content = f.read() + nt.assert_in(u'a=1', content) + nt.assert_in(u'b=u"abcþ"', content) + + +@dec.skip_without('faulthandler') +def test_smoke_faulthandler(): + with kernel() as kc: + # Note: faulthandler.register is not available on windows. + code = u'\n'.join([ + 'import sys', + 'import faulthandler', + 'import signal', + 'faulthandler.enable()', + 'if not sys.platform.startswith("win32"):', + ' faulthandler.register(signal.SIGTERM)']) + _, reply = execute(code, kc=kc) + nt.assert_equal(reply['status'], 'ok', reply.get('traceback', '')) + + +def test_help_output(): + """ipython kernel --help-all works""" + tt.help_all_output_test('kernel') + + +def test_is_complete(): + with kernel() as kc: + # There are more test cases for this in core - here we just check + # that the kernel exposes the interface correctly. + kc.is_complete('2+2') + reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) + assert reply['content']['status'] == 'complete' + + # SyntaxError should mean it's complete + kc.is_complete('raise = 2') + reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) + assert reply['content']['status'] == 'invalid' + + kc.is_complete('a = [1,\n2,') + reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) + assert reply['content']['status'] == 'incomplete' + assert reply['content']['indent'] == '' + + +def test_complete(): + with kernel() as kc: + execute(u'a = 1', kc=kc) + wait_for_idle(kc) + cell = 'import IPython\nb = a.' + kc.complete(cell) + reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) + c = reply['content'] + nt.assert_equal(c['status'], 'ok') + nt.assert_equal(c['cursor_start'], cell.find('a.')) + nt.assert_equal(c['cursor_end'], cell.find('a.') + 2) + matches = c['matches'] + nt.assert_greater(len(matches), 0) + for match in matches: + nt.assert_equal(match[:2], 'a.') + + +@dec.skip_without('matplotlib') +def test_matplotlib_inline_on_import(): + with kernel() as kc: + cell = '\n'.join([ + 'import matplotlib, matplotlib.pyplot as plt', + 'backend = matplotlib.get_backend()' + ]) + _, reply = execute(cell, + user_expressions={'backend': 'backend'}, + kc=kc) + _check_status(reply) + backend_bundle = reply['user_expressions']['backend'] + _check_status(backend_bundle) + nt.assert_in('backend_inline', backend_bundle['data']['text/plain']) + + +def test_shutdown(): + """Kernel exits after polite shutdown_request""" + with new_kernel() as kc: + km = kc.parent + execute(u'a = 1', kc=kc) + wait_for_idle(kc) + kc.shutdown() + for i in range(100): # 10s timeout + if km.is_alive(): + time.sleep(.1) + else: + break + nt.assert_false(km.is_alive()) diff --git a/packages/python/yap_kernel/yap_kernel/tests/test_kernelspec.py b/packages/python/yap_kernel/yap_kernel/tests/test_kernelspec.py new file mode 100644 index 000000000..4aa72865d --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/tests/test_kernelspec.py @@ -0,0 +1,146 @@ +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import json +import io +import os +import shutil +import sys +import tempfile + +try: + from unittest import mock +except ImportError: + import mock # py2 + +from jupyter_core.paths import jupyter_data_dir + +from yap_kernel.kernelspec import ( + make_yapkernel_cmd, + get_kernel_dict, + write_kernel_spec, + install, + InstallYAPKernelSpecApp, + KERNEL_NAME, + RESOURCES, +) + +import nose.tools as nt + +pjoin = os.path.join + + +def test_make_yapkernel_cmd(): + cmd = make_yapkernel_cmd() + nt.assert_equal(cmd, [ + sys.executable, + '-m', + 'yap_kernel_launcher', + '-f', + '{connection_file}' + ]) + + +def assert_kernel_dict(d): + nt.assert_equal(d['argv'], make_yapkernel_cmd()) + nt.assert_equal(d['display_name'], 'Python %i' % sys.version_info[0]) + nt.assert_equal(d['language'], 'python') + + +def test_get_kernel_dict(): + d = get_kernel_dict() + assert_kernel_dict(d) + + +def assert_kernel_dict_with_profile(d): + nt.assert_equal(d['argv'], make_yapkernel_cmd( + extra_arguments=["--profile", "test"])) + nt.assert_equal(d['display_name'], 'Python %i' % sys.version_info[0]) + nt.assert_equal(d['language'], 'python') + + +def test_get_kernel_dict_with_profile(): + d = get_kernel_dict(["--profile", "test"]) + assert_kernel_dict_with_profile(d) + + +def assert_is_spec(path): + for fname in os.listdir(RESOURCES): + dst = pjoin(path, fname) + assert os.path.exists(dst) + kernel_json = pjoin(path, 'kernel.json') + assert os.path.exists(kernel_json) + with io.open(kernel_json, encoding='utf8') as f: + json.load(f) + + +def test_write_kernel_spec(): + path = write_kernel_spec() + assert_is_spec(path) + shutil.rmtree(path) + + +def test_write_kernel_spec_path(): + path = os.path.join(tempfile.mkdtemp(), KERNEL_NAME) + path2 = write_kernel_spec(path) + nt.assert_equal(path, path2) + assert_is_spec(path) + shutil.rmtree(path) + + +def test_install_kernelspec(): + + path = tempfile.mkdtemp() + try: + test = InstallYAPKernelSpecApp.launch_instance(argv=['--prefix', path]) + assert_is_spec(os.path.join( + path, 'share', 'jupyter', 'kernels', KERNEL_NAME)) + finally: + shutil.rmtree(path) + + +def test_install_user(): + tmp = tempfile.mkdtemp() + + with mock.patch.dict(os.environ, {'HOME': tmp}): + install(user=True) + data_dir = jupyter_data_dir() + + assert_is_spec(os.path.join(data_dir, 'kernels', KERNEL_NAME)) + + +def test_install(): + system_jupyter_dir = tempfile.mkdtemp() + + with mock.patch('jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH', + [system_jupyter_dir]): + install() + + assert_is_spec(os.path.join(system_jupyter_dir, 'kernels', KERNEL_NAME)) + + +def test_install_profile(): + system_jupyter_dir = tempfile.mkdtemp() + + with mock.patch('jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH', + [system_jupyter_dir]): + install(profile="Test") + + spec = os.path.join(system_jupyter_dir, 'kernels', KERNEL_NAME, "kernel.json") + with open(spec) as f: + spec = json.load(f) + nt.assert_true(spec["display_name"].endswith(" [profile=Test]")) + nt.assert_equal(spec["argv"][-2:], ["--profile", "Test"]) + + +def test_install_display_name_overrides_profile(): + system_jupyter_dir = tempfile.mkdtemp() + + with mock.patch('jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH', + [system_jupyter_dir]): + install(display_name="Display", profile="Test") + + spec = os.path.join(system_jupyter_dir, 'kernels', KERNEL_NAME, "kernel.json") + with open(spec) as f: + spec = json.load(f) + nt.assert_equal(spec["display_name"], "Display") diff --git a/packages/python/yap_kernel/yap_kernel/tests/test_message_spec.py b/packages/python/yap_kernel/yap_kernel/tests/test_message_spec.py new file mode 100644 index 000000000..4991ec288 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/tests/test_message_spec.py @@ -0,0 +1,539 @@ +"""Test suite for our zeromq-based message specification.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import re +import sys +from distutils.version import LooseVersion as V +try: + from queue import Empty # Py 3 +except ImportError: + from Queue import Empty # Py 2 + +import nose.tools as nt +from nose.plugins.skip import SkipTest + +from traitlets import ( + HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum +) +from ipython_genutils.py3compat import string_types, iteritems + +from .utils import TIMEOUT, start_global_kernel, flush_channels, execute + +#----------------------------------------------------------------------------- +# Globals +#----------------------------------------------------------------------------- +KC = None + +def setup(): + global KC + KC = start_global_kernel() + +#----------------------------------------------------------------------------- +# Message Spec References +#----------------------------------------------------------------------------- + +class Reference(HasTraits): + + """ + Base class for message spec specification testing. + + This class is the core of the message specification test. The + idea is that child classes implement trait attributes for each + message keys, so that message keys can be tested against these + traits using :meth:`check` method. + + """ + + def check(self, d): + """validate a dict against our traits""" + for key in self.trait_names(): + nt.assert_in(key, d) + # FIXME: always allow None, probably not a good idea + if d[key] is None: + continue + try: + setattr(self, key, d[key]) + except TraitError as e: + assert False, str(e) + + +class Version(Unicode): + def __init__(self, *args, **kwargs): + self.min = kwargs.pop('min', None) + self.max = kwargs.pop('max', None) + kwargs['default_value'] = self.min + super(Version, self).__init__(*args, **kwargs) + + def validate(self, obj, value): + if self.min and V(value) < V(self.min): + raise TraitError("bad version: %s < %s" % (value, self.min)) + if self.max and (V(value) > V(self.max)): + raise TraitError("bad version: %s > %s" % (value, self.max)) + + +class RMessage(Reference): + msg_id = Unicode() + msg_type = Unicode() + header = Dict() + parent_header = Dict() + content = Dict() + + def check(self, d): + super(RMessage, self).check(d) + RHeader().check(self.header) + if self.parent_header: + RHeader().check(self.parent_header) + +class RHeader(Reference): + msg_id = Unicode() + msg_type = Unicode() + session = Unicode() + username = Unicode() + version = Version(min='5.0') + +mime_pat = re.compile(r'^[\w\-\+\.]+/[\w\-\+\.]+$') + +class MimeBundle(Reference): + metadata = Dict() + data = Dict() + def _data_changed(self, name, old, new): + for k,v in iteritems(new): + assert mime_pat.match(k) + nt.assert_is_instance(v, string_types) + + +# shell replies +class Reply(Reference): + status = Enum((u'ok', u'error'), default_value=u'ok') + + +class ExecuteReply(Reply): + execution_count = Integer() + + def check(self, d): + Reference.check(self, d) + if d['status'] == 'ok': + ExecuteReplyOkay().check(d) + elif d['status'] == 'error': + ExecuteReplyError().check(d) + + +class ExecuteReplyOkay(Reply): + status = Enum(('ok',)) + user_expressions = Dict() + + +class ExecuteReplyError(Reply): + ename = Unicode() + evalue = Unicode() + traceback = List(Unicode()) + + +class InspectReply(Reply, MimeBundle): + found = Bool() + + +class ArgSpec(Reference): + args = List(Unicode()) + varargs = Unicode() + varkw = Unicode() + defaults = List() + + +class Status(Reference): + execution_state = Enum((u'busy', u'idle', u'starting'), default_value=u'busy') + + +class CompleteReply(Reply): + matches = List(Unicode()) + cursor_start = Integer() + cursor_end = Integer() + status = Unicode() + + +class LanguageInfo(Reference): + name = Unicode('python') + version = Unicode(sys.version.split()[0]) + + +class KernelInfoReply(Reply): + protocol_version = Version(min='5.0') + implementation = Unicode('ipython') + implementation_version = Version(min='2.1') + language_info = Dict() + banner = Unicode() + + def check(self, d): + Reference.check(self, d) + LanguageInfo().check(d['language_info']) + + +class ConnectReply(Reference): + shell_port = Integer() + control_port = Integer() + stdin_port = Integer() + iopub_port = Integer() + hb_port = Integer() + + +class CommInfoReply(Reply): + comms = Dict() + + +class IsCompleteReply(Reference): + status = Enum((u'complete', u'incomplete', u'invalid', u'unknown'), default_value=u'complete') + + def check(self, d): + Reference.check(self, d) + if d['status'] == 'incomplete': + IsCompleteReplyIncomplete().check(d) + + +class IsCompleteReplyIncomplete(Reference): + indent = Unicode() + + +# IOPub messages + +class ExecuteInput(Reference): + code = Unicode() + execution_count = Integer() + + +class Error(ExecuteReplyError): + """Errors are the same as ExecuteReply, but without status""" + status = None # no status field + + +class Stream(Reference): + name = Enum((u'stdout', u'stderr'), default_value=u'stdout') + text = Unicode() + + +class DisplayData(MimeBundle): + pass + + +class ExecuteResult(MimeBundle): + execution_count = Integer() + + +class HistoryReply(Reply): + history = List(List()) + + +references = { + 'execute_reply' : ExecuteReply(), + 'inspect_reply' : InspectReply(), + 'status' : Status(), + 'complete_reply' : CompleteReply(), + 'kernel_info_reply': KernelInfoReply(), + 'connect_reply': ConnectReply(), + 'comm_info_reply': CommInfoReply(), + 'is_complete_reply': IsCompleteReply(), + 'execute_input' : ExecuteInput(), + 'execute_result' : ExecuteResult(), + 'history_reply' : HistoryReply(), + 'error' : Error(), + 'stream' : Stream(), + 'display_data' : DisplayData(), + 'header' : RHeader(), +} +""" +Specifications of `content` part of the reply messages. +""" + + +def validate_message(msg, msg_type=None, parent=None): + """validate a message + + This is a generator, and must be iterated through to actually + trigger each test. + + If msg_type and/or parent are given, the msg_type and/or parent msg_id + are compared with the given values. + """ + RMessage().check(msg) + if msg_type: + nt.assert_equal(msg['msg_type'], msg_type) + if parent: + nt.assert_equal(msg['parent_header']['msg_id'], parent) + content = msg['content'] + ref = references[msg['msg_type']] + ref.check(content) + + +#----------------------------------------------------------------------------- +# Tests +#----------------------------------------------------------------------------- + +# Shell channel + +def test_execute(): + flush_channels() + + msg_id = KC.execute(code='x=1') + reply = KC.get_shell_msg(timeout=TIMEOUT) + validate_message(reply, 'execute_reply', msg_id) + + +def test_execute_silent(): + flush_channels() + msg_id, reply = execute(code='x=1', silent=True) + + # flush status=idle + status = KC.iopub_channel.get_msg(timeout=TIMEOUT) + validate_message(status, 'status', msg_id) + nt.assert_equal(status['content']['execution_state'], 'idle') + + nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1) + count = reply['execution_count'] + + msg_id, reply = execute(code='x=2', silent=True) + + # flush status=idle + status = KC.iopub_channel.get_msg(timeout=TIMEOUT) + validate_message(status, 'status', msg_id) + nt.assert_equal(status['content']['execution_state'], 'idle') + + nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1) + count_2 = reply['execution_count'] + nt.assert_equal(count_2, count) + + +def test_execute_error(): + flush_channels() + + msg_id, reply = execute(code='1/0') + nt.assert_equal(reply['status'], 'error') + nt.assert_equal(reply['ename'], 'ZeroDivisionError') + + error = KC.iopub_channel.get_msg(timeout=TIMEOUT) + validate_message(error, 'error', msg_id) + + +def test_execute_inc(): + """execute request should increment execution_count""" + flush_channels() + + msg_id, reply = execute(code='x=1') + count = reply['execution_count'] + + flush_channels() + + msg_id, reply = execute(code='x=2') + count_2 = reply['execution_count'] + nt.assert_equal(count_2, count+1) + +def test_execute_stop_on_error(): + """execute request should not abort execution queue with stop_on_error False""" + flush_channels() + + fail = '\n'.join([ + # sleep to ensure subsequent message is waiting in the queue to be aborted + 'import time', + 'time.sleep(0.5)', + 'raise ValueError', + ]) + KC.execute(code=fail) + msg_id = KC.execute(code='print("Hello")') + KC.get_shell_msg(timeout=TIMEOUT) + reply = KC.get_shell_msg(timeout=TIMEOUT) + nt.assert_equal(reply['content']['status'], 'aborted') + + flush_channels() + + KC.execute(code=fail, stop_on_error=False) + msg_id = KC.execute(code='print("Hello")') + KC.get_shell_msg(timeout=TIMEOUT) + reply = KC.get_shell_msg(timeout=TIMEOUT) + nt.assert_equal(reply['content']['status'], 'ok') + + +def test_user_expressions(): + flush_channels() + + msg_id, reply = execute(code='x=1', user_expressions=dict(foo='x+1')) + user_expressions = reply['user_expressions'] + nt.assert_equal(user_expressions, {u'foo': { + u'status': u'ok', + u'data': {u'text/plain': u'2'}, + u'metadata': {}, + }}) + + +def test_user_expressions_fail(): + flush_channels() + + msg_id, reply = execute(code='x=0', user_expressions=dict(foo='nosuchname')) + user_expressions = reply['user_expressions'] + foo = user_expressions['foo'] + nt.assert_equal(foo['status'], 'error') + nt.assert_equal(foo['ename'], 'NameError') + + +def test_oinfo(): + flush_channels() + + msg_id = KC.inspect('a') + reply = KC.get_shell_msg(timeout=TIMEOUT) + validate_message(reply, 'inspect_reply', msg_id) + + +def test_oinfo_found(): + flush_channels() + + msg_id, reply = execute(code='a=5') + + msg_id = KC.inspect('a') + reply = KC.get_shell_msg(timeout=TIMEOUT) + validate_message(reply, 'inspect_reply', msg_id) + content = reply['content'] + assert content['found'] + text = content['data']['text/plain'] + nt.assert_in('Type:', text) + nt.assert_in('Docstring:', text) + + +def test_oinfo_detail(): + flush_channels() + + msg_id, reply = execute(code='ip=get_ipython()') + + msg_id = KC.inspect('ip.object_inspect', cursor_pos=10, detail_level=1) + reply = KC.get_shell_msg(timeout=TIMEOUT) + validate_message(reply, 'inspect_reply', msg_id) + content = reply['content'] + assert content['found'] + text = content['data']['text/plain'] + nt.assert_in('Signature:', text) + nt.assert_in('Source:', text) + + +def test_oinfo_not_found(): + flush_channels() + + msg_id = KC.inspect('dne') + reply = KC.get_shell_msg(timeout=TIMEOUT) + validate_message(reply, 'inspect_reply', msg_id) + content = reply['content'] + nt.assert_false(content['found']) + + +def test_complete(): + flush_channels() + + msg_id, reply = execute(code="alpha = albert = 5") + + msg_id = KC.complete('al', 2) + reply = KC.get_shell_msg(timeout=TIMEOUT) + validate_message(reply, 'complete_reply', msg_id) + matches = reply['content']['matches'] + for name in ('alpha', 'albert'): + nt.assert_in(name, matches) + + +def test_kernel_info_request(): + flush_channels() + + msg_id = KC.kernel_info() + reply = KC.get_shell_msg(timeout=TIMEOUT) + validate_message(reply, 'kernel_info_reply', msg_id) + + +def test_connect_request(): + flush_channels() + msg = KC.session.msg('connect_request') + KC.shell_channel.send(msg) + return msg['header']['msg_id'] + + msg_id = KC.kernel_info() + reply = KC.get_shell_msg(timeout=TIMEOUT) + validate_message(reply, 'connect_reply', msg_id) + + +def test_comm_info_request(): + flush_channels() + if not hasattr(KC, 'comm_info'): + raise SkipTest() + msg_id = KC.comm_info() + reply = KC.get_shell_msg(timeout=TIMEOUT) + validate_message(reply, 'comm_info_reply', msg_id) + + +def test_single_payload(): + flush_channels() + msg_id, reply = execute(code="for i in range(3):\n"+ + " x=range?\n") + payload = reply['payload'] + next_input_pls = [pl for pl in payload if pl["source"] == "set_next_input"] + nt.assert_equal(len(next_input_pls), 1) + +def test_is_complete(): + flush_channels() + + msg_id = KC.is_complete("a = 1") + reply = KC.get_shell_msg(timeout=TIMEOUT) + validate_message(reply, 'is_complete_reply', msg_id) + +def test_history_range(): + flush_channels() + + msg_id_exec = KC.execute(code='x=1', store_history = True) + reply_exec = KC.get_shell_msg(timeout=TIMEOUT) + + msg_id = KC.history(hist_access_type = 'range', raw = True, output = True, start = 1, stop = 2, session = 0) + reply = KC.get_shell_msg(timeout=TIMEOUT) + validate_message(reply, 'history_reply', msg_id) + content = reply['content'] + nt.assert_equal(len(content['history']), 1) + +def test_history_tail(): + flush_channels() + + msg_id_exec = KC.execute(code='x=1', store_history = True) + reply_exec = KC.get_shell_msg(timeout=TIMEOUT) + + msg_id = KC.history(hist_access_type = 'tail', raw = True, output = True, n = 1, session = 0) + reply = KC.get_shell_msg(timeout=TIMEOUT) + validate_message(reply, 'history_reply', msg_id) + content = reply['content'] + nt.assert_equal(len(content['history']), 1) + +def test_history_search(): + flush_channels() + + msg_id_exec = KC.execute(code='x=1', store_history = True) + reply_exec = KC.get_shell_msg(timeout=TIMEOUT) + + msg_id = KC.history(hist_access_type = 'search', raw = True, output = True, n = 1, pattern = '*', session = 0) + reply = KC.get_shell_msg(timeout=TIMEOUT) + validate_message(reply, 'history_reply', msg_id) + content = reply['content'] + nt.assert_equal(len(content['history']), 1) + +# IOPub channel + + +def test_stream(): + flush_channels() + + msg_id, reply = execute("print('hi')") + + stdout = KC.iopub_channel.get_msg(timeout=TIMEOUT) + validate_message(stdout, 'stream', msg_id) + content = stdout['content'] + nt.assert_equal(content['text'], u'hi\n') + + +def test_display_data(): + flush_channels() + + msg_id, reply = execute("from IPython.core.display import display; display(1)") + + display = KC.iopub_channel.get_msg(timeout=TIMEOUT) + validate_message(display, 'display_data', parent=msg_id) + data = display['content']['data'] + nt.assert_equal(data['text/plain'], u'1') diff --git a/packages/python/yap_kernel/yap_kernel/tests/test_pickleutil.py b/packages/python/yap_kernel/yap_kernel/tests/test_pickleutil.py new file mode 100644 index 000000000..6e968430b --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/tests/test_pickleutil.py @@ -0,0 +1,68 @@ + +import os +import pickle + +import nose.tools as nt + +from yap_kernel.pickleutil import can, uncan, codeutil + +def interactive(f): + f.__module__ = '__main__' + return f + +def dumps(obj): + return pickle.dumps(can(obj)) + +def loads(obj): + return uncan(pickle.loads(obj)) + +def test_no_closure(): + @interactive + def foo(): + a = 5 + return a + + pfoo = dumps(foo) + bar = loads(pfoo) + nt.assert_equal(foo(), bar()) + +def test_generator_closure(): + # this only creates a closure on Python 3 + @interactive + def foo(): + i = 'i' + r = [ i for j in (1,2) ] + return r + + pfoo = dumps(foo) + bar = loads(pfoo) + nt.assert_equal(foo(), bar()) + +def test_nested_closure(): + @interactive + def foo(): + i = 'i' + def g(): + return i + return g() + + pfoo = dumps(foo) + bar = loads(pfoo) + nt.assert_equal(foo(), bar()) + +def test_closure(): + i = 'i' + @interactive + def foo(): + return i + + pfoo = dumps(foo) + bar = loads(pfoo) + nt.assert_equal(foo(), bar()) + +def test_uncan_bytes_buffer(): + data = b'data' + canned = can(data) + canned.buffers = [memoryview(buf) for buf in canned.buffers] + out = uncan(canned) + nt.assert_equal(out, data) diff --git a/packages/python/yap_kernel/yap_kernel/tests/test_serialize.py b/packages/python/yap_kernel/yap_kernel/tests/test_serialize.py new file mode 100644 index 000000000..770a37d6f --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/tests/test_serialize.py @@ -0,0 +1,210 @@ +"""test serialization tools""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import pickle +from collections import namedtuple + +import nose.tools as nt + +from yap_kernel.serialize import serialize_object, deserialize_object +from IPython.testing import decorators as dec +from yap_kernel.pickleutil import CannedArray, CannedClass, interactive +from ipython_genutils.py3compat import iteritems + +#------------------------------------------------------------------------------- +# Globals and Utilities +#------------------------------------------------------------------------------- + +def roundtrip(obj): + """roundtrip an object through serialization""" + bufs = serialize_object(obj) + obj2, remainder = deserialize_object(bufs) + nt.assert_equals(remainder, []) + return obj2 + + +SHAPES = ((100,), (1024,10), (10,8,6,5), (), (0,)) +DTYPES = ('uint8', 'float64', 'int32', [('g', 'float32')], '|S10') + +#------------------------------------------------------------------------------- +# Tests +#------------------------------------------------------------------------------- + +def new_array(shape, dtype): + import numpy + return numpy.random.random(shape).astype(dtype) + +def test_roundtrip_simple(): + for obj in [ + 'hello', + dict(a='b', b=10), + [1,2,'hi'], + (b'123', 'hello'), + ]: + obj2 = roundtrip(obj) + nt.assert_equal(obj, obj2) + +def test_roundtrip_nested(): + for obj in [ + dict(a=range(5), b={1:b'hello'}), + [range(5),[range(3),(1,[b'whoda'])]], + ]: + obj2 = roundtrip(obj) + nt.assert_equal(obj, obj2) + +def test_roundtrip_buffered(): + for obj in [ + dict(a=b"x"*1025), + b"hello"*500, + [b"hello"*501, 1,2,3] + ]: + bufs = serialize_object(obj) + nt.assert_equal(len(bufs), 2) + obj2, remainder = deserialize_object(bufs) + nt.assert_equal(remainder, []) + nt.assert_equal(obj, obj2) + +def test_roundtrip_memoryview(): + b = b'asdf' * 1025 + view = memoryview(b) + bufs = serialize_object(view) + nt.assert_equal(len(bufs), 2) + v2, remainder = deserialize_object(bufs) + nt.assert_equal(remainder, []) + nt.assert_equal(v2.tobytes(), b) + +@dec.skip_without('numpy') +def test_numpy(): + import numpy + from numpy.testing.utils import assert_array_equal + for shape in SHAPES: + for dtype in DTYPES: + A = new_array(shape, dtype=dtype) + bufs = serialize_object(A) + bufs = [memoryview(b) for b in bufs] + B, r = deserialize_object(bufs) + nt.assert_equal(r, []) + nt.assert_equal(A.shape, B.shape) + nt.assert_equal(A.dtype, B.dtype) + assert_array_equal(A,B) + +@dec.skip_without('numpy') +def test_recarray(): + import numpy + from numpy.testing.utils import assert_array_equal + for shape in SHAPES: + for dtype in [ + [('f', float), ('s', '|S10')], + [('n', int), ('s', '|S1'), ('u', 'uint32')], + ]: + A = new_array(shape, dtype=dtype) + + bufs = serialize_object(A) + B, r = deserialize_object(bufs) + nt.assert_equal(r, []) + nt.assert_equal(A.shape, B.shape) + nt.assert_equal(A.dtype, B.dtype) + assert_array_equal(A,B) + +@dec.skip_without('numpy') +def test_numpy_in_seq(): + import numpy + from numpy.testing.utils import assert_array_equal + for shape in SHAPES: + for dtype in DTYPES: + A = new_array(shape, dtype=dtype) + bufs = serialize_object((A,1,2,b'hello')) + canned = pickle.loads(bufs[0]) + nt.assert_is_instance(canned[0], CannedArray) + tup, r = deserialize_object(bufs) + B = tup[0] + nt.assert_equal(r, []) + nt.assert_equal(A.shape, B.shape) + nt.assert_equal(A.dtype, B.dtype) + assert_array_equal(A,B) + +@dec.skip_without('numpy') +def test_numpy_in_dict(): + import numpy + from numpy.testing.utils import assert_array_equal + for shape in SHAPES: + for dtype in DTYPES: + A = new_array(shape, dtype=dtype) + bufs = serialize_object(dict(a=A,b=1,c=range(20))) + canned = pickle.loads(bufs[0]) + nt.assert_is_instance(canned['a'], CannedArray) + d, r = deserialize_object(bufs) + B = d['a'] + nt.assert_equal(r, []) + nt.assert_equal(A.shape, B.shape) + nt.assert_equal(A.dtype, B.dtype) + assert_array_equal(A,B) + +def test_class(): + @interactive + class C(object): + a=5 + bufs = serialize_object(dict(C=C)) + canned = pickle.loads(bufs[0]) + nt.assert_is_instance(canned['C'], CannedClass) + d, r = deserialize_object(bufs) + C2 = d['C'] + nt.assert_equal(C2.a, C.a) + +def test_class_oldstyle(): + @interactive + class C: + a=5 + + bufs = serialize_object(dict(C=C)) + canned = pickle.loads(bufs[0]) + nt.assert_is_instance(canned['C'], CannedClass) + d, r = deserialize_object(bufs) + C2 = d['C'] + nt.assert_equal(C2.a, C.a) + +def test_tuple(): + tup = (lambda x:x, 1) + bufs = serialize_object(tup) + canned = pickle.loads(bufs[0]) + nt.assert_is_instance(canned, tuple) + t2, r = deserialize_object(bufs) + nt.assert_equal(t2[0](t2[1]), tup[0](tup[1])) + +point = namedtuple('point', 'x y') + +def test_namedtuple(): + p = point(1,2) + bufs = serialize_object(p) + canned = pickle.loads(bufs[0]) + nt.assert_is_instance(canned, point) + p2, r = deserialize_object(bufs, globals()) + nt.assert_equal(p2.x, p.x) + nt.assert_equal(p2.y, p.y) + +def test_list(): + lis = [lambda x:x, 1] + bufs = serialize_object(lis) + canned = pickle.loads(bufs[0]) + nt.assert_is_instance(canned, list) + l2, r = deserialize_object(bufs) + nt.assert_equal(l2[0](l2[1]), lis[0](lis[1])) + +def test_class_inheritance(): + @interactive + class C(object): + a=5 + + @interactive + class D(C): + b=10 + + bufs = serialize_object(dict(D=D)) + canned = pickle.loads(bufs[0]) + nt.assert_is_instance(canned['D'], CannedClass) + d, r = deserialize_object(bufs) + D2 = d['D'] + nt.assert_equal(D2.a, D.a) + nt.assert_equal(D2.b, D.b) diff --git a/packages/python/yap_kernel/yap_kernel/tests/test_start_kernel.py b/packages/python/yap_kernel/yap_kernel/tests/test_start_kernel.py new file mode 100644 index 000000000..f655a23b3 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/tests/test_start_kernel.py @@ -0,0 +1,48 @@ +import nose.tools as nt + +from .test_embed_kernel import setup_kernel + +TIMEOUT = 15 + +def test_ipython_start_kernel_userns(): + cmd = ('from IPython import start_kernel\n' + 'ns = {"tre": 123}\n' + 'start_kernel(user_ns=ns)') + + with setup_kernel(cmd) as client: + msg_id = client.inspect('tre') + msg = client.get_shell_msg(block=True, timeout=TIMEOUT) + content = msg['content'] + assert content['found'] + text = content['data']['text/plain'] + nt.assert_in(u'123', text) + + # user_module should be an instance of DummyMod + msg_id = client.execute("usermod = get_ipython().user_module") + msg = client.get_shell_msg(block=True, timeout=TIMEOUT) + content = msg['content'] + nt.assert_equal(content['status'], u'ok') + msg_id = client.inspect('usermod') + msg = client.get_shell_msg(block=True, timeout=TIMEOUT) + content = msg['content'] + assert content['found'] + text = content['data']['text/plain'] + nt.assert_in(u'DummyMod', text) + +def test_ipython_start_kernel_no_userns(): + # Issue #4188 - user_ns should be passed to shell as None, not {} + cmd = ('from IPython import start_kernel\n' + 'start_kernel()') + + with setup_kernel(cmd) as client: + # user_module should not be an instance of DummyMod + msg_id = client.execute("usermod = get_ipython().user_module") + msg = client.get_shell_msg(block=True, timeout=TIMEOUT) + content = msg['content'] + nt.assert_equal(content['status'], u'ok') + msg_id = client.inspect('usermod') + msg = client.get_shell_msg(block=True, timeout=TIMEOUT) + content = msg['content'] + assert content['found'] + text = content['data']['text/plain'] + nt.assert_not_in(u'DummyMod', text) diff --git a/packages/python/yap_kernel/yap_kernel/tests/test_zmq_shell.py b/packages/python/yap_kernel/yap_kernel/tests/test_zmq_shell.py new file mode 100644 index 000000000..b0db1b27c --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/tests/test_zmq_shell.py @@ -0,0 +1,208 @@ +# -*- coding: utf-8 -*- +""" Tests for zmq shell / display publisher. """ + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import os +try: + from queue import Queue +except ImportError: + # py2 + from Queue import Queue +from threading import Thread +import unittest + +from traitlets import Int +import zmq + +from yap_kernel.zmqshell import ZMQDisplayPublisher +from jupyter_client.session import Session + + +class NoReturnDisplayHook(object): + """ + A dummy DisplayHook which allows us to monitor + the number of times an object is called, but which + does *not* return a message when it is called. + """ + call_count = 0 + + def __call__(self, obj): + self.call_count += 1 + + +class ReturnDisplayHook(NoReturnDisplayHook): + """ + A dummy DisplayHook with the same counting ability + as its base class, but which also returns the same + message when it is called. + """ + def __call__(self, obj): + super(ReturnDisplayHook, self).__call__(obj) + return obj + + +class CounterSession(Session): + """ + This is a simple subclass to allow us to count + the calls made to the session object by the display + publisher. + """ + send_count = Int(0) + + def send(self, *args, **kwargs): + """ + A trivial override to just augment the existing call + with an increment to the send counter. + """ + self.send_count += 1 + super(CounterSession, self).send(*args, **kwargs) + + +class ZMQDisplayPublisherTests(unittest.TestCase): + """ + Tests the ZMQDisplayPublisher in zmqshell.py + """ + + def setUp(self): + self.context = zmq.Context() + self.socket = self.context.socket(zmq.PUB) + self.session = CounterSession() + + self.disp_pub = ZMQDisplayPublisher( + session = self.session, + pub_socket = self.socket + ) + + def tearDown(self): + """ + We need to close the socket in order to proceed with the + tests. + TODO - There is still an open file handler to '/dev/null', + presumably created by zmq. + """ + self.disp_pub.clear_output() + self.socket.close() + self.context.term() + + def test_display_publisher_creation(self): + """ + Since there's no explicit constructor, here we confirm + that keyword args get assigned correctly, and override + the defaults. + """ + self.assertEqual(self.disp_pub.session, self.session) + self.assertEqual(self.disp_pub.pub_socket, self.socket) + + def test_thread_local_hooks(self): + """ + Confirms that the thread_local attribute is correctly + initialised with an empty list for the display hooks + """ + self.assertEqual(self.disp_pub._hooks, []) + def hook(msg): + return msg + self.disp_pub.register_hook(hook) + self.assertEqual(self.disp_pub._hooks, [hook]) + + q = Queue() + def set_thread_hooks(): + q.put(self.disp_pub._hooks) + t = Thread(target=set_thread_hooks) + t.start() + thread_hooks = q.get(timeout=10) + self.assertEqual(thread_hooks, []) + + def test_publish(self): + """ + Publish should prepare the message and eventually call + `send` by default. + """ + data = dict(a = 1) + + self.assertEqual(self.session.send_count, 0) + self.disp_pub.publish(data) + self.assertEqual(self.session.send_count, 1) + + def test_display_hook_halts_send(self): + """ + If a hook is installed, and on calling the object + it does *not* return a message, then we assume that + the message has been consumed, and should not be + processed (`sent`) in the normal manner. + """ + data = dict(a = 1) + hook = NoReturnDisplayHook() + + self.disp_pub.register_hook(hook) + self.assertEqual(hook.call_count, 0) + self.assertEqual(self.session.send_count, 0) + + self.disp_pub.publish(data) + + self.assertEqual(hook.call_count, 1) + self.assertEqual(self.session.send_count, 0) + + def test_display_hook_return_calls_send(self): + """ + If a hook is installed and on calling the object + it returns a new message, then we assume that this + is just a message transformation, and the message + should be sent in the usual manner. + """ + data = dict(a=1) + hook = ReturnDisplayHook() + + self.disp_pub.register_hook(hook) + self.assertEqual(hook.call_count, 0) + self.assertEqual(self.session.send_count, 0) + + self.disp_pub.publish(data) + + self.assertEqual(hook.call_count, 1) + self.assertEqual(self.session.send_count, 1) + + def test_unregister_hook(self): + """ + Once a hook is unregistered, it should not be called + during `publish`. + """ + data = dict(a = 1) + hook = NoReturnDisplayHook() + + self.disp_pub.register_hook(hook) + self.assertEqual(hook.call_count, 0) + self.assertEqual(self.session.send_count, 0) + + self.disp_pub.publish(data) + + self.assertEqual(hook.call_count, 1) + self.assertEqual(self.session.send_count, 0) + + # + # After unregistering the `NoReturn` hook, any calls + # to publish should *not* got through the DisplayHook, + # but should instead hit the usual `session.send` call + # at the end. + # + # As a result, the hook call count should *not* increase, + # but the session send count *should* increase. + # + first = self.disp_pub.unregister_hook(hook) + self.disp_pub.publish(data) + + self.assertTrue(first) + self.assertEqual(hook.call_count, 1) + self.assertEqual(self.session.send_count, 1) + + # + # If a hook is not installed, `unregister_hook` + # should return false. + # + second = self.disp_pub.unregister_hook(hook) + self.assertFalse(second) + + +if __name__ == '__main__': + unittest.main() diff --git a/packages/python/yap_kernel/yap_kernel/tests/utils.py b/packages/python/yap_kernel/yap_kernel/tests/utils.py new file mode 100644 index 000000000..434ccec03 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/tests/utils.py @@ -0,0 +1,166 @@ +"""utilities for testing IPython kernels""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import atexit +import os + +from contextlib import contextmanager +from subprocess import PIPE, STDOUT +try: + from queue import Empty # Py 3 +except ImportError: + from Queue import Empty # Py 2 + +import nose +import nose.tools as nt + +from jupyter_client import manager + +#------------------------------------------------------------------------------- +# Globals +#------------------------------------------------------------------------------- + +STARTUP_TIMEOUT = 60 +TIMEOUT = 15 + +KM = None +KC = None + +#------------------------------------------------------------------------------- +# code +#------------------------------------------------------------------------------- +def start_new_kernel(**kwargs): + """start a new kernel, and return its Manager and Client + + Integrates with our output capturing for tests. + """ + try: + stdout = nose.iptest_stdstreams_fileno() + except AttributeError: + stdout = open(os.devnull) + kwargs.update(dict(stdout=stdout, stderr=STDOUT)) + return manager.start_new_kernel(startup_timeout=STARTUP_TIMEOUT, **kwargs) + +def flush_channels(kc=None): + """flush any messages waiting on the queue""" + from .test_message_spec import validate_message + + if kc is None: + kc = KC + for channel in (kc.shell_channel, kc.iopub_channel): + while True: + try: + msg = channel.get_msg(block=True, timeout=0.1) + except Empty: + break + else: + validate_message(msg) + + +def execute(code='', kc=None, **kwargs): + """wrapper for doing common steps for validating an execution request""" + from .test_message_spec import validate_message + if kc is None: + kc = KC + msg_id = kc.execute(code=code, **kwargs) + reply = kc.get_shell_msg(timeout=TIMEOUT) + validate_message(reply, 'execute_reply', msg_id) + busy = kc.get_iopub_msg(timeout=TIMEOUT) + validate_message(busy, 'status', msg_id) + nt.assert_equal(busy['content']['execution_state'], 'busy') + + if not kwargs.get('silent'): + execute_input = kc.get_iopub_msg(timeout=TIMEOUT) + validate_message(execute_input, 'execute_input', msg_id) + nt.assert_equal(execute_input['content']['code'], code) + + return msg_id, reply['content'] + +def start_global_kernel(): + """start the global kernel (if it isn't running) and return its client""" + global KM, KC + if KM is None: + KM, KC = start_new_kernel() + atexit.register(stop_global_kernel) + else: + flush_channels(KC) + return KC + +@contextmanager +def kernel(): + """Context manager for the global kernel instance + + Should be used for most kernel tests + + Returns + ------- + kernel_client: connected KernelClient instance + """ + yield start_global_kernel() + +def uses_kernel(test_f): + """Decorator for tests that use the global kernel""" + def wrapped_test(): + with kernel() as kc: + test_f(kc) + wrapped_test.__doc__ = test_f.__doc__ + wrapped_test.__name__ = test_f.__name__ + return wrapped_test + +def stop_global_kernel(): + """Stop the global shared kernel instance, if it exists""" + global KM, KC + KC.stop_channels() + KC = None + if KM is None: + return + KM.shutdown_kernel(now=True) + KM = None + +def new_kernel(argv=None): + """Context manager for a new kernel in a subprocess + + Should only be used for tests where the kernel must not be re-used. + + Returns + ------- + kernel_client: connected KernelClient instance + """ + stdout = getattr(nose, 'iptest_stdstreams_fileno', open(os.devnull)) + kwargs = dict(stdout=stdout, stderr=STDOUT) + if argv is not None: + kwargs['extra_arguments'] = argv + return manager.run_kernel(**kwargs) + +def assemble_output(iopub): + """assemble stdout/err from an execution""" + stdout = '' + stderr = '' + while True: + msg = iopub.get_msg(block=True, timeout=1) + msg_type = msg['msg_type'] + content = msg['content'] + if msg_type == 'status' and content['execution_state'] == 'idle': + # idle message signals end of output + break + elif msg['msg_type'] == 'stream': + if content['name'] == 'stdout': + stdout += content['text'] + elif content['name'] == 'stderr': + stderr += content['text'] + else: + raise KeyError("bad stream: %r" % content['name']) + else: + # other output, ignored + pass + return stdout, stderr + +def wait_for_idle(kc): + while True: + msg = kc.iopub_channel.get_msg(block=True, timeout=1) + msg_type = msg['msg_type'] + content = msg['content'] + if msg_type == 'status' and content['execution_state'] == 'idle': + break diff --git a/packages/python/yap_kernel/yap_kernel/yapkernel.py b/packages/python/yap_kernel/yap_kernel/yapkernel.py new file mode 100644 index 000000000..e1d677c03 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/yapkernel.py @@ -0,0 +1,381 @@ +"""The IPython kernel implementation""" + +import getpass +import sys +import traceback + +from IPython.core import release +from ipython_genutils.py3compat import builtin_mod, PY3, unicode_type, safe_unicode +from IPython.utils.tokenutil import token_at_cursor, line_at_cursor +from traitlets import Instance, Type, Any, List + +from .comm import CommManager +from .kernelbase import Kernel as KernelBase +from .zmqshell import ZMQInteractiveShell +from .interactiveshell import YAPInteraction + +class YAPKernel(KernelBase): + shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', + allow_none=True) + shell_class = Type(ZMQInteractiveShell) + user_module = Any() + def _user_module_changed(self, name, old, new): + if self.shell is not None: + self.shell.user_module = new + + user_ns = Instance(dict, args=None, allow_none=True) + def _user_ns_changed(self, name, old, new): + if self.shell is not None: + self.shell.user_ns = new + self.shell.init_user_ns() + + # A reference to the Python builtin 'raw_input' function. + # (i.e., __builtin__.raw_input for Python 2.7, builtins.input for Python 3) + _sys_raw_input = Any() + _sys_eval_input = Any() + + def __init__(self, **kwargs): + super(YAPKernel, self).__init__(**kwargs) + + # Initialize the InteractiveShell subclass + self.shell = self.shell_class.instance(parent=self, + profile_dir = self.profile_dir, + user_module = self.user_module, + user_ns = self.user_ns, + kernel = self, + ) + self.shell.displayhook.session = self.session + self.shell.displayhook.pub_socket = self.iopub_socket + self.shell.displayhook.topic = self._topic('execute_result') + self.shell.display_pub.session = self.session + self.shell.display_pub.pub_socket = self.iopub_socket + self.comm_manager = CommManager(parent=self, kernel=self) + + self.shell.configurables.append(self.comm_manager) + comm_msg_types = [ 'comm_open', 'comm_msg', 'comm_close' ] + for msg_type in comm_msg_types: + self.shell_handlers[msg_type] = getattr(self.comm_manager, msg_type) + + self.engine = YAPInteraction(self) + self.shell.run_cell = self.engine.run_cell + + help_links = List([ + { + 'text': "Python", + 'url': "http://docs.python.org/%i.%i" % sys.version_info[:2], + }, + { + 'text': "IPython", + 'url': "http://ipython.org/documentation.html", + }, + { + 'text': "NumPy", + 'url': "http://docs.scipy.org/doc/numpy/reference/", + }, + { + 'text': "SciPy", + 'url': "http://docs.scipy.org/doc/scipy/reference/", + }, + { + 'text': "Matplotlib", + 'url': "http://matplotlib.org/contents.html", + }, + { + 'text': "SymPy", + 'url': "http://docs.sympy.org/latest/index.html", + }, + { + 'text': "pandas", + 'url': "http://pandas.pydata.org/pandas-docs/stable/", + }, + ]).tag(config=True) + + # Kernel info fields + implementation = 'yap' + implementation_version = "6.3" + language_info = { + 'name': 'YAP Kernel', + 'version': '6.3', + 'mimetype': 'text/x-prolog', + 'codemirror_mode': { + 'name': 'prolog', + 'version': sys.version_info[0] + }, + 'pygments_lexer': 'prolog', + 'nbconvert_exporter': 'prolog', + 'file_extension': '.yap' + } + + @property + def banner(self): + return self.shell.banner + + def start(self): + self.shell.exit_now = False + super(YAPKernel, self).start() + + def set_parent(self, ident, parent): + """Overridden from parent to tell the display hook and output streams + about the parent message. + """ + super(YAPKernel, self).set_parent(ident, parent) + self.shell.set_parent(parent) + + def init_metadata(self, parent): + """Initialize metadata. + + Run at the beginning of each execution request. + """ + md = super(YAPKernel, self).init_metadata(parent) + # FIXME: remove deprecated ipyparallel-specific code + # This is required for ipyparallel < 5.0 + md.update({ + 'dependencies_met' : True, + 'engine' : self.ident, + }) + return md + + def finish_metadata(self, parent, metadata, reply_content): + """Finish populating metadata. + + Run after completing an execution request. + """ + # FIXME: remove deprecated ipyparallel-specific code + # This is required by ipyparallel < 5.0 + metadata['status'] = reply_content['status'] + if reply_content['status'] == 'error' and reply_content['ename'] == 'UnmetDependency': + metadata['dependencies_met'] = False + + return metadata + + def _forward_input(self, allow_stdin=False): + """Forward raw_input and getpass to the current frontend. + + via input_request + """ + self._allow_stdin = allow_stdin + + if PY3: + self._sys_raw_input = builtin_mod.input + builtin_mod.input = self.raw_input + else: + self._sys_raw_input = builtin_mod.raw_input + self._sys_eval_input = builtin_mod.input + builtin_mod.raw_input = self.raw_input + builtin_mod.input = lambda prompt='': eval(self.raw_input(prompt)) + self._save_getpass = getpass.getpass + getpass.getpass = self.getpass + + def _restore_input(self): + """Restore raw_input, getpass""" + if PY3: + builtin_mod.input = self._sys_raw_input + else: + builtin_mod.raw_input = self._sys_raw_input + builtin_mod.input = self._sys_eval_input + + getpass.getpass = self._save_getpass + + @property + def execution_count(self): + return self.shell.execution_count + + @execution_count.setter + def execution_count(self, value): + # Ignore the incrememnting done by KernelBase, in favour of our shell's + # execution counter. + pass + + def do_execute(self, code, silent, store_history=True, + user_expressions=None, allow_stdin=False): + shell = self.shell # we'll need this a lot here + + self._forward_input(allow_stdin) + + reply_content = {} + try: + res = self.shell.run_cell(code, store_history=store_history, silent=silent) + finally: + self._restore_input() + + if res.error_before_exec is not None: + err = res.error_before_exec + else: + err = res.error_in_exec + + if res.success: + reply_content[u'status'] = u'ok' + else: + reply_content[u'status'] = u'error' + + reply_content.update({ + u'traceback': shell._last_traceback or [], + u'ename': unicode_type(type(err).__name__), + u'evalue': safe_unicode(err), + }) + + # FIXME: deprecated piece for ipyparallel (remove in 5.0): + e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, + method='execute') + reply_content['engine_info'] = e_info + + + # Return the execution counter so clients can display prompts + reply_content['execution_count'] = shell.execution_count - 1 + + if 'traceback' in reply_content: + self.log.info("Exception in execute request:\n%s", '\n'.join(reply_content['traceback'])) + + + # At this point, we can tell whether the main code execution succeeded + # or not. If it did, we proceed to evaluate user_expressions + if reply_content['status'] == 'ok': + reply_content[u'user_expressions'] = \ + shell.user_expressions(user_expressions or {}) + else: + # If there was an error, don't even try to compute expressions + reply_content[u'user_expressions'] = {} + + # Payloads should be retrieved regardless of outcome, so we can both + # recover partial output (that could have been generated early in a + # block, before an error) and always clear the payload system. + reply_content[u'payload'] = shell.payload_manager.read_payload() + # Be aggressive about clearing the payload because we don't want + # it to sit in memory until the next execute_request comes in. + shell.payload_manager.clear_payload() + + return reply_content + + def do_complete(self, code, cursor_pos): + # FIXME: IPython completers currently assume single line, + # but completion messages give multi-line context + # For now, extract line from cell, based on cursor_pos: + if cursor_pos is None: + cursor_pos = len(code) + line, offset = line_at_cursor(code, cursor_pos) + line_cursor = cursor_pos - offset + + txt, matches = self.shell.complete('', line, line_cursor) + return {'matches' : matches, + 'cursor_end' : cursor_pos, + 'cursor_start' : cursor_pos - len(txt), + 'metadata' : {}, + 'status' : 'ok'} + + def do_inspect(self, code, cursor_pos, detail_level=0): + name = token_at_cursor(code, cursor_pos) + info = self.shell.object_inspect(name) + + reply_content = {'status' : 'ok'} + reply_content['data'] = data = {} + reply_content['metadata'] = {} + reply_content['found'] = info['found'] + if info['found']: + info_text = self.shell.object_inspect_text( + name, + detail_level=detail_level, + ) + data['text/plain'] = info_text + + return reply_content + + def do_history(self, hist_access_type, output, raw, session=0, start=0, + stop=None, n=None, pattern=None, unique=False): + if hist_access_type == 'tail': + hist = self.shell.history_manager.get_tail(n, raw=raw, output=output, + include_latest=True) + + elif hist_access_type == 'range': + hist = self.shell.history_manager.get_range(session, start, stop, + raw=raw, output=output) + + elif hist_access_type == 'search': + hist = self.shell.history_manager.search( + pattern, raw=raw, output=output, n=n, unique=unique) + else: + hist = [] + + return { + 'status': 'ok', + 'history' : list(hist), + } + + def do_shutdown(self, restart): + self.shell.exit_now = True + return dict(status='ok', restart=restart) + + def do_is_complete(self, code): + status, indent_spaces = self.shell.input_transformer_manager.check_complete(code) + r = {'status': status} + if status == 'incomplete': + r['indent'] = ' ' * indent_spaces + return r + + def do_apply(self, content, bufs, msg_id, reply_metadata): + from .serialize import serialize_object, unpack_apply_message + shell = self.shell + try: + working = shell.user_ns + + prefix = "_"+str(msg_id).replace("-","")+"_" + + f,args,kwargs = unpack_apply_message(bufs, working, copy=False) + + fname = getattr(f, '__name__', 'f') + + fname = prefix+"f" + argname = prefix+"args" + kwargname = prefix+"kwargs" + resultname = prefix+"result" + + ns = { fname : f, argname : args, kwargname : kwargs , resultname : None } + # print ns + working.update(ns) + code = "%s = %s(*%s,**%s)" % (resultname, fname, argname, kwargname) + try: + exec(code, shell.user_global_ns, shell.user_ns) + result = working.get(resultname) + finally: + for key in ns: + working.pop(key) + + result_buf = serialize_object(result, + buffer_threshold=self.session.buffer_threshold, + item_threshold=self.session.item_threshold, + ) + + except BaseException as e: + # invoke IPython traceback formatting + shell.showtraceback() + reply_content = { + u'traceback': shell._last_traceback or [], + u'ename': unicode_type(type(e).__name__), + u'evalue': safe_unicode(e), + } + # FIXME: deprecated piece for ipyparallel (remove in 5.0): + e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply') + reply_content['engine_info'] = e_info + + self.send_response(self.iopub_socket, u'error', reply_content, + ident=self._topic('error')) + self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback'])) + result_buf = [] + reply_content['status'] = 'error' + else: + reply_content = {'status' : 'ok'} + + return reply_content, result_buf + + def do_clear(self): + self.shell.reset(False) + return dict(status='ok') + + +# This exists only for backwards compatibility - use YAPKernel instead + +class Kernel(YAPKernel): + def __init__(self, *args, **kwargs): + import warnings + warnings.warn('Kernel is a deprecated alias of yap_kernel.yapkernel.YAPKernel', + DeprecationWarning) + super(Kernel, self).__init__(*args, **kwargs) diff --git a/packages/python/yap_kernel/yap_kernel/zmqshell.py b/packages/python/yap_kernel/yap_kernel/zmqshell.py new file mode 100644 index 000000000..d4af7afe1 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel/zmqshell.py @@ -0,0 +1,601 @@ +# -*- coding: utf-8 -*- +"""A ZMQ-based subclass of InteractiveShell. + +This code is meant to ease the refactoring of the base InteractiveShell into +something with a cleaner architecture for 2-process use, without actually +breaking InteractiveShell itself. So we're doing something a bit ugly, where +we subclass and override what we want to fix. Once this is working well, we +can go back to the base class and refactor the code for a cleaner inheritance +implementation that doesn't rely on so much monkeypatching. + +But this lets us maintain a fully working IPython as we develop the new +machinery. This should thus be thought of as scaffolding. +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from __future__ import print_function + +import os +import sys +import time +import warnings +from threading import local + +from tornado import ioloop + +from IPython.core.interactiveshell import ( + InteractiveShell, InteractiveShellABC +) +from IPython.core import page +from IPython.core.autocall import ZMQExitAutocall +from IPython.core.displaypub import DisplayPublisher +from IPython.core.error import UsageError +from IPython.core.magics import MacroToEdit, CodeMagics +from IPython.core.magic import magics_class, line_magic, Magics +from IPython.core import payloadpage +from IPython.core.usage import default_banner +from IPython.display import display, Javascript +from yap_kernel import ( + get_connection_file, get_connection_info, connect_qtconsole +) +from IPython.utils import openpy +from yap_kernel.jsonutil import json_clean, encode_images +from IPython.utils.process import arg_split +from ipython_genutils import py3compat +from ipython_genutils.py3compat import unicode_type +from traitlets import ( + Instance, Type, Dict, CBool, CBytes, Any, default, observe +) +from yap_kernel.displayhook import ZMQShellDisplayHook + +from jupyter_core.paths import jupyter_runtime_dir +from jupyter_client.session import extract_header, Session + +#----------------------------------------------------------------------------- +# Functions and classes +#----------------------------------------------------------------------------- + +class ZMQDisplayPublisher(DisplayPublisher): + """A display publisher that publishes data using a ZeroMQ PUB socket.""" + + session = Instance(Session, allow_none=True) + pub_socket = Any(allow_none=True) + parent_header = Dict({}) + topic = CBytes(b'display_data') + + # thread_local: + # An attribute used to ensure the correct output message + # is processed. See yap_kernel Issue 113 for a discussion. + _thread_local = Any() + + def set_parent(self, parent): + """Set the parent for outbound messages.""" + self.parent_header = extract_header(parent) + + def _flush_streams(self): + """flush IO Streams prior to display""" + sys.stdout.flush() + sys.stderr.flush() + + @default('_thread_local') + def _default_thread_local(self): + """Initialize our thread local storage""" + return local() + + @property + def _hooks(self): + if not hasattr(self._thread_local, 'hooks'): + # create new list for a new thread + self._thread_local.hooks = [] + return self._thread_local.hooks + + def publish(self, data, metadata=None, source=None, transient=None, + update=False, + ): + """Publish a display-data message + + Parameters + ---------- + data: dict + A mime-bundle dict, keyed by mime-type. + metadata: dict, optional + Metadata associated with the data. + transient: dict, optional, keyword-only + Transient data that may only be relevant during a live display, + such as display_id. + Transient data should not be persisted to documents. + update: bool, optional, keyword-only + If True, send an update_display_data message instead of display_data. + """ + self._flush_streams() + if metadata is None: + metadata = {} + if transient is None: + transient = {} + self._validate_data(data, metadata) + content = {} + content['data'] = encode_images(data) + content['metadata'] = metadata + content['transient'] = transient + + msg_type = 'update_display_data' if update else 'display_data' + + # Use 2-stage process to send a message, + # in order to put it through the transform + # hooks before potentially sending. + msg = self.session.msg( + msg_type, json_clean(content), + parent=self.parent_header + ) + + # Each transform either returns a new + # message or None. If None is returned, + # the message has been 'used' and we return. + for hook in self._hooks: + msg = hook(msg) + if msg is None: + return + + self.session.send( + self.pub_socket, msg, ident=self.topic, + ) + + def clear_output(self, wait=False): + """Clear output associated with the current execution (cell). + + Parameters + ---------- + wait: bool (default: False) + If True, the output will not be cleared immediately, + instead waiting for the next display before clearing. + This reduces bounce during repeated clear & display loops. + + """ + content = dict(wait=wait) + self._flush_streams() + self.session.send( + self.pub_socket, u'clear_output', content, + parent=self.parent_header, ident=self.topic, + ) + + def register_hook(self, hook): + """ + Registers a hook with the thread-local storage. + + Parameters + ---------- + hook : Any callable object + + Returns + ------- + Either a publishable message, or `None`. + + The DisplayHook objects must return a message from + the __call__ method if they still require the + `session.send` method to be called after tranformation. + Returning `None` will halt that execution path, and + session.send will not be called. + """ + self._hooks.append(hook) + + def unregister_hook(self, hook): + """ + Un-registers a hook with the thread-local storage. + + Parameters + ---------- + hook: Any callable object which has previously been + registered as a hook. + + Returns + ------- + bool - `True` if the hook was removed, `False` if it wasn't + found. + """ + try: + self._hooks.remove(hook) + return True + except ValueError: + return False + + +@magics_class +class KernelMagics(Magics): + #------------------------------------------------------------------------ + # Magic overrides + #------------------------------------------------------------------------ + # Once the base class stops inheriting from magic, this code needs to be + # moved into a separate machinery as well. For now, at least isolate here + # the magics which this class needs to implement differently from the base + # class, or that are unique to it. + + _find_edit_target = CodeMagics._find_edit_target + + @line_magic + def edit(self, parameter_s='', last_call=['','']): + """Bring up an editor and execute the resulting code. + + Usage: + %edit [options] [args] + + %edit runs an external text editor. You will need to set the command for + this editor via the ``TerminalInteractiveShell.editor`` option in your + configuration file before it will work. + + This command allows you to conveniently edit multi-line code right in + your IPython session. + + If called without arguments, %edit opens up an empty editor with a + temporary file and will execute the contents of this file when you + close it (don't forget to save it!). + + Options: + + -n + Open the editor at a specified line number. By default, the IPython + editor hook uses the unix syntax 'editor +N filename', but you can + configure this by providing your own modified hook if your favorite + editor supports line-number specifications with a different syntax. + + -p + Call the editor with the same data as the previous time it was used, + regardless of how long ago (in your current session) it was. + + -r + Use 'raw' input. This option only applies to input taken from the + user's history. By default, the 'processed' history is used, so that + magics are loaded in their transformed version to valid Python. If + this option is given, the raw input as typed as the command line is + used instead. When you exit the editor, it will be executed by + IPython's own processor. + + Arguments: + + If arguments are given, the following possibilites exist: + + - The arguments are numbers or pairs of colon-separated numbers (like + 1 4:8 9). These are interpreted as lines of previous input to be + loaded into the editor. The syntax is the same of the %macro command. + + - If the argument doesn't start with a number, it is evaluated as a + variable and its contents loaded into the editor. You can thus edit + any string which contains python code (including the result of + previous edits). + + - If the argument is the name of an object (other than a string), + IPython will try to locate the file where it was defined and open the + editor at the point where it is defined. You can use ``%edit function`` + to load an editor exactly at the point where 'function' is defined, + edit it and have the file be executed automatically. + + If the object is a macro (see %macro for details), this opens up your + specified editor with a temporary file containing the macro's data. + Upon exit, the macro is reloaded with the contents of the file. + + Note: opening at an exact line is only supported under Unix, and some + editors (like kedit and gedit up to Gnome 2.8) do not understand the + '+NUMBER' parameter necessary for this feature. Good editors like + (X)Emacs, vi, jed, pico and joe all do. + + - If the argument is not found as a variable, IPython will look for a + file with that name (adding .py if necessary) and load it into the + editor. It will execute its contents with execfile() when you exit, + loading any code in the file into your interactive namespace. + + Unlike in the terminal, this is designed to use a GUI editor, and we do + not know when it has closed. So the file you edit will not be + automatically executed or printed. + + Note that %edit is also available through the alias %ed. + """ + + opts,args = self.parse_options(parameter_s, 'prn:') + + try: + filename, lineno, _ = CodeMagics._find_edit_target(self.shell, args, opts, last_call) + except MacroToEdit: + # TODO: Implement macro editing over 2 processes. + print("Macro editing not yet implemented in 2-process model.") + return + + # Make sure we send to the client an absolute path, in case the working + # directory of client and kernel don't match + filename = os.path.abspath(filename) + + payload = { + 'source' : 'edit_magic', + 'filename' : filename, + 'line_number' : lineno + } + self.shell.payload_manager.write_payload(payload) + + # A few magics that are adapted to the specifics of using pexpect and a + # remote terminal + + @line_magic + def clear(self, arg_s): + """Clear the terminal.""" + if os.name == 'posix': + self.shell.system("clear") + else: + self.shell.system("cls") + + if os.name == 'nt': + # This is the usual name in windows + cls = line_magic('cls')(clear) + + # Terminal pagers won't work over pexpect, but we do have our own pager + + @line_magic + def less(self, arg_s): + """Show a file through the pager. + + Files ending in .py are syntax-highlighted.""" + if not arg_s: + raise UsageError('Missing filename.') + + if arg_s.endswith('.py'): + cont = self.shell.pycolorize(openpy.read_py_file(arg_s, skip_encoding_cookie=False)) + else: + cont = open(arg_s).read() + page.page(cont) + + more = line_magic('more')(less) + + # Man calls a pager, so we also need to redefine it + if os.name == 'posix': + @line_magic + def man(self, arg_s): + """Find the man page for the given command and display in pager.""" + page.page(self.shell.getoutput('man %s | col -b' % arg_s, + split=False)) + + @line_magic + def connect_info(self, arg_s): + """Print information for connecting other clients to this kernel + + It will print the contents of this session's connection file, as well as + shortcuts for local clients. + + In the simplest case, when called from the most recently launched kernel, + secondary clients can be connected, simply with: + + $> jupyter --existing + + """ + + try: + connection_file = get_connection_file() + info = get_connection_info(unpack=False) + except Exception as e: + warnings.warn("Could not get connection info: %r" % e) + return + + # if it's in the default dir, truncate to basename + if jupyter_runtime_dir() == os.path.dirname(connection_file): + connection_file = os.path.basename(connection_file) + + + print (info + '\n') + print ("Paste the above JSON into a file, and connect with:\n" + " $> jupyter --existing \n" + "or, if you are local, you can connect with just:\n" + " $> jupyter --existing {0}\n" + "or even just:\n" + " $> jupyter --existing\n" + "if this is the most recent Jupyter kernel you have started.".format( + connection_file + ) + ) + + @line_magic + def qtconsole(self, arg_s): + """Open a qtconsole connected to this kernel. + + Useful for connecting a qtconsole to running notebooks, for better + debugging. + """ + + # %qtconsole should imply bind_kernel for engines: + # FIXME: move to ipyparallel Kernel subclass + if 'ipyparallel' in sys.modules: + from ipyparallel import bind_kernel + bind_kernel() + + try: + connect_qtconsole(argv=arg_split(arg_s, os.name=='posix')) + except Exception as e: + warnings.warn("Could not start qtconsole: %r" % e) + return + + @line_magic + def autosave(self, arg_s): + """Set the autosave interval in the notebook (in seconds). + + The default value is 120, or two minutes. + ``%autosave 0`` will disable autosave. + + This magic only has an effect when called from the notebook interface. + It has no effect when called in a startup file. + """ + + try: + interval = int(arg_s) + except ValueError: + raise UsageError("%%autosave requires an integer, got %r" % arg_s) + + # javascript wants milliseconds + milliseconds = 1000 * interval + display(Javascript("IPython.notebook.set_autosave_interval(%i)" % milliseconds), + include=['application/javascript'] + ) + if interval: + print("Autosaving every %i seconds" % interval) + else: + print("Autosave disabled") + + +class ZMQInteractiveShell(InteractiveShell): + """A subclass of InteractiveShell for ZMQ.""" + + displayhook_class = Type(ZMQShellDisplayHook) + display_pub_class = Type(ZMQDisplayPublisher) + data_pub_class = Type('yap_kernel.datapub.ZMQDataPublisher') + kernel = Any() + parent_header = Any() + + @default('banner1') + def _default_banner1(self): + return default_banner + + # Override the traitlet in the parent class, because there's no point using + # readline for the kernel. Can be removed when the readline code is moved + # to the terminal frontend. + colors_force = CBool(True) + readline_use = CBool(False) + # autoindent has no meaning in a zmqshell, and attempting to enable it + # will print a warning in the absence of readline. + autoindent = CBool(False) + + exiter = Instance(ZMQExitAutocall) + + @default('exiter') + def _default_exiter(self): + return ZMQExitAutocall(self) + + @observe('exit_now') + def _update_exit_now(self, change): + """stop eventloop when exit_now fires""" + if change['new']: + loop = ioloop.IOLoop.instance() + loop.add_timeout(time.time() + 0.1, loop.stop) + + keepkernel_on_exit = None + + # Over ZeroMQ, GUI control isn't done with PyOS_InputHook as there is no + # interactive input being read; we provide event loop support in yapkernel + def enable_gui(self, gui): + from .eventloops import enable_gui as real_enable_gui + try: + real_enable_gui(gui) + self.active_eventloop = gui + except ValueError as e: + raise UsageError("%s" % e) + + def init_environment(self): + """Configure the user's environment.""" + env = os.environ + # These two ensure 'ls' produces nice coloring on BSD-derived systems + env['TERM'] = 'xterm-color' + env['CLICOLOR'] = '1' + # Since normal pagers don't work at all (over pexpect we don't have + # single-key control of the subprocess), try to disable paging in + # subprocesses as much as possible. + env['PAGER'] = 'cat' + env['GIT_PAGER'] = 'cat' + + def init_hooks(self): + super(ZMQInteractiveShell, self).init_hooks() + self.set_hook('show_in_pager', page.as_hook(payloadpage.page), 99) + + def init_data_pub(self): + """Delay datapub init until request, for deprecation warnings""" + pass + + @property + def data_pub(self): + if not hasattr(self, '_data_pub'): + warnings.warn("InteractiveShell.data_pub is deprecated outside IPython parallel.", + DeprecationWarning, stacklevel=2) + + self._data_pub = self.data_pub_class(parent=self) + self._data_pub.session = self.display_pub.session + self._data_pub.pub_socket = self.display_pub.pub_socket + return self._data_pub + + @data_pub.setter + def data_pub(self, pub): + self._data_pub = pub + + def ask_exit(self): + """Engage the exit actions.""" + self.exit_now = (not self.keepkernel_on_exit) + payload = dict( + source='ask_exit', + keepkernel=self.keepkernel_on_exit, + ) + self.payload_manager.write_payload(payload) + + def run_cell(self, *args, **kwargs): + self._last_traceback = None + return super(ZMQInteractiveShell, self).run_cell(*args, **kwargs) + + def _showtraceback(self, etype, evalue, stb): + # try to preserve ordering of tracebacks and print statements + sys.stdout.flush() + sys.stderr.flush() + + exc_content = { + u'traceback' : stb, + u'ename' : unicode_type(etype.__name__), + u'evalue' : py3compat.safe_unicode(evalue), + } + + dh = self.displayhook + # Send exception info over pub socket for other clients than the caller + # to pick up + topic = None + if dh.topic: + topic = dh.topic.replace(b'execute_result', b'error') + + exc_msg = dh.session.send(dh.pub_socket, u'error', json_clean(exc_content), + dh.parent_header, ident=topic) + + # FIXME - Once we rely on Python 3, the traceback is stored on the + # exception object, so we shouldn't need to store it here. + self._last_traceback = stb + + def set_next_input(self, text, replace=False): + """Send the specified text to the frontend to be presented at the next + input cell.""" + payload = dict( + source='set_next_input', + text=text, + replace=replace, + ) + self.payload_manager.write_payload(payload) + + def set_parent(self, parent): + """Set the parent header for associating output with its triggering input""" + self.parent_header = parent + self.displayhook.set_parent(parent) + self.display_pub.set_parent(parent) + if hasattr(self, '_data_pub'): + self.data_pub.set_parent(parent) + try: + sys.stdout.set_parent(parent) + except AttributeError: + pass + try: + sys.stderr.set_parent(parent) + except AttributeError: + pass + + def get_parent(self): + return self.parent_header + + def init_magics(self): + super(ZMQInteractiveShell, self).init_magics() + self.register_magics(KernelMagics) + self.magics_manager.register_alias('ed', 'edit') + + def init_virtualenv(self): + # Overridden not to do virtualenv detection, because it's probably + # not appropriate in a kernel. To use a kernel in a virtualenv, install + # it inside the virtualenv. + # https://ipython.readthedocs.io/en/latest/install/kernel_install.html + pass + +InteractiveShellABC.register(ZMQInteractiveShell) diff --git a/packages/python/yap_kernel/yap_kernel_launcher.py b/packages/python/yap_kernel/yap_kernel_launcher.py new file mode 100644 index 000000000..bcbd94404 --- /dev/null +++ b/packages/python/yap_kernel/yap_kernel_launcher.py @@ -0,0 +1,16 @@ +"""Entry point for launching an IPython kernel. + +This is separate from the yap_kernel package so we can avoid doing imports until +after removing the cwd from sys.path. +""" + +import sys + +if __name__ == '__main__': + # Remove the CWD from sys.path while we load stuff. + # This is added back by InteractiveShellApp.init_path() + if sys.path[0] == '': + del sys.path[0] + + from yap_kernel import kernelapp as app + app.launch_new_instance()