From 3e4a499244050359203d6ddea1bfa8e7896a504d Mon Sep 17 00:00:00 2001 From: boulant Date: Wed, 28 Jul 2010 13:07:47 +0000 Subject: [PATCH] Integration of python packaging: initialization --- bin/virtual_salome.py | 15 + configure.ac | 23 +- doc/Makefile.am | 2 +- doc/docutils/Makefile.am | 91 ++++ doc/docutils/conf.py | 200 ++++++++ doc/docutils/docapi.rst | 46 ++ doc/docutils/index.rst | 21 + doc/docutils/salomepypkg.rst | 262 ++++++++++ salome_adm/unix/config_files/check_sphinx.m4 | 35 ++ salome_adm/unix/make_common_starter.am | 10 +- src/KERNEL_PY/Makefile.am | 8 +- src/KERNEL_PY/__init__.py | 219 +++++++++ src/KERNEL_PY/kernel/Makefile.am | 11 + src/KERNEL_PY/kernel/__init__.py | 2 + src/KERNEL_PY/kernel/deprecation.py | 116 +++++ src/KERNEL_PY/kernel/logconfig.py.in | 27 ++ src/KERNEL_PY/kernel/logger.py | 220 +++++++++ src/KERNEL_PY/kernel/studyedit.py | 479 +++++++++++++++++++ src/KERNEL_PY/kernel/termcolor.py | 150 ++++++ 19 files changed, 1929 insertions(+), 8 deletions(-) create mode 100644 doc/docutils/Makefile.am create mode 100644 doc/docutils/conf.py create mode 100644 doc/docutils/docapi.rst create mode 100644 doc/docutils/index.rst create mode 100644 doc/docutils/salomepypkg.rst create mode 100644 salome_adm/unix/config_files/check_sphinx.m4 create mode 100644 src/KERNEL_PY/__init__.py create mode 100644 src/KERNEL_PY/kernel/Makefile.am create mode 100644 src/KERNEL_PY/kernel/__init__.py create mode 100644 src/KERNEL_PY/kernel/deprecation.py create mode 100755 src/KERNEL_PY/kernel/logconfig.py.in create mode 100644 src/KERNEL_PY/kernel/logger.py create mode 100644 src/KERNEL_PY/kernel/studyedit.py create mode 100644 src/KERNEL_PY/kernel/termcolor.py diff --git a/bin/virtual_salome.py b/bin/virtual_salome.py index dfe2d5948..f20685a43 100644 --- a/bin/virtual_salome.py +++ b/bin/virtual_salome.py @@ -205,9 +205,17 @@ def link_module(options): if not os.path.exists(module_lib_py_dir): print "Python directory %s does not exist" % module_lib_py_dir else: + # __GBO__ specific action for the package salome + module_lib_pypkg_dir=os.path.join(module_lib_py_dir,"salome") + lib_pypkg_dir=os.path.join(lib_py_dir,"salome") + mkdir(lib_pypkg_dir) + # __GBO__ mkdir(lib_py_shared_dir) for fn in os.listdir(module_lib_py_dir): if fn == "shared_modules": continue + # __GBO__ + if fn == "salome": continue + # __GBO__ symlink(os.path.join(module_lib_py_dir, fn), os.path.join(lib_py_dir, fn)) pass if os.path.exists(module_lib_py_shared_dir): @@ -215,6 +223,13 @@ def link_module(options): symlink(os.path.join(module_lib_py_shared_dir, fn), os.path.join(lib_py_shared_dir, fn)) pass pass + # __GBO__ + if os.path.exists(module_lib_pypkg_dir): + for fn in os.listdir(module_lib_pypkg_dir): + symlink(os.path.join(module_lib_pypkg_dir, fn), os.path.join(lib_pypkg_dir, fn)) + pass + pass + # __GBO__ else: if verbose: print module_lib_py_shared_dir, " doesn't exist" diff --git a/configure.ac b/configure.ac index 1439a7db4..64578b070 100644 --- a/configure.ac +++ b/configure.ac @@ -134,6 +134,10 @@ AC_ENABLE_DEBUG(no) AC_ENABLE_PRODUCTION(no) AC_ENABLE_MPI_SEQ_CONTAINER(no) +# _GBO_SALOME_PYTHON_PACKAGING_ +PYLOGLEVEL=DEBUG +AC_SUBST(PYLOGLEVEL) + echo echo --------------------------------------------- echo testing libtool @@ -171,8 +175,6 @@ AC_CHECK_LIB(rt,nanosleep) dnl add library libm : AC_CHECK_LIB(m,ceil) -# _CS_gbo We should add all dependent libraries - AC_CXX_USE_STD_IOSTREAM AC_CXX_HAVE_SSTREAM @@ -210,8 +212,9 @@ echo testing swig echo --------------------------------------------- echo -dnl _CS_gbo We should use here a variable given from the CHECK_PYTHON -AM_PATH_PYTHON(2.3) +# _GBO_ This definition is required. Without this definition, the pythondir +# would not be defined. The version doesn't matter. +AM_PATH_PYTHON(2.4) CHECK_SWIG echo @@ -384,6 +387,13 @@ CHECK_HTML_GENERATORS # Additional conditional to avoid compilation of non-portable code AM_CONDITIONAL(WINDOWS, [ test ]) +echo +echo --------------------------------------------- +echo testing sphinx +echo --------------------------------------------- +echo +CHECK_SPHINX + echo echo ============================================================ echo Summary @@ -464,7 +474,7 @@ if test x$libbatch_ok = xno; then echo fi -htmldoc_products="doxygen_ok graphviz_ok rst2html_ok" +htmldoc_products="doxygen_ok graphviz_ok rst2html_ok sphinx_ok" echo --- Html documentation products: only required for doc production summary $htmldoc_products @@ -521,6 +531,7 @@ AC_OUTPUT([ \ doc/salome/gui/Makefile \ doc/salome/gui/doxyfile \ doc/salome/gui/static/header.html \ + doc/docutils/Makefile \ idl/Makefile \ idl/Calcium_Ports.idl \ resources/Makefile \ @@ -549,6 +560,8 @@ AC_OUTPUT([ \ src/GenericObj/Makefile \ src/HDFPersist/Makefile \ src/KERNEL_PY/Makefile \ + src/KERNEL_PY/kernel/Makefile \ + src/KERNEL_PY/kernel/logconfig.py \ src/Launcher/Makefile \ src/LifeCycleCORBA/Makefile \ src/LifeCycleCORBA/Test/Makefile \ diff --git a/doc/Makefile.am b/doc/Makefile.am index fbcc8b5ed..91eeee484 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -27,7 +27,7 @@ # include $(top_srcdir)/salome_adm/unix/make_common_starter.am -SUBDIRS = salome +SUBDIRS = salome docutils docs: usr_docs diff --git a/doc/docutils/Makefile.am b/doc/docutils/Makefile.am new file mode 100644 index 000000000..44819790d --- /dev/null +++ b/doc/docutils/Makefile.am @@ -0,0 +1,91 @@ +# -*- coding: iso-8859-1 -*- +# Copyright (C) 2007-2010 CEA/DEN, EDF R&D, OPEN CASCADE +# +# Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +# CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com + +include $(top_srcdir)/salome_adm/unix/make_common_starter.am + +pydocdir = $(docdir)/docutils/KERNEL + +.PHONY : latex + +if SPHINX_IS_OK + +pydoc_DATA=html/index.html +html/index.html:$(RSTFILES) + make htm + +endif + +EXTRA_DIST= html + +SPHINXOPTS = +SOURCEDIR = $(srcdir) +SPHINXBUILD = sphinx-build +PAPEROPT_a4 = -D latex_paper_size=a4 +ALLSPHINXOPTS = -d doctrees $(PAPEROPT_a4) $(SPHINXOPTS) $(SOURCEDIR) + +SPHINX_PYTHONPATH = $(prefix)/lib/python$(PYTHON_VERSION)/site-packages/salome:$(KERNEL_ROOT_DIR)/bin/salome:$(OMNIORB_ROOT)/lib/python$(PYTHON_VERSION)/site-packages + +SPHINX_LD_LIBRARY_PATH = $(OMNIORB_ROOT)/lib + +htm: + mkdir -p html doctrees + PYTHONPATH=$(SPHINX_PYTHONPATH):${PYTHONPATH}; \ + LD_LIBRARY_PATH=$(SPHINX_LD_LIBRARY_PATH):${LD_LIBRARY_PATH}; \ + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) html + @echo + @echo "Build finished. The HTML pages are in html." + +latex: + mkdir -p latex doctrees + PYTHONPATH=$(SPHINX_PYTHONPATH):${PYTHONPATH}; \ + LD_LIBRARY_PATH=$(SPHINX_LD_LIBRARY_PATH):${LD_LIBRARY_PATH}; \ + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) latex + @echo + @echo "Build finished; the LaTeX files are in latex." + @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ + "run these through (pdf)latex." + +html: + mkdir -p $@ + +RSTFILES= \ + index.rst \ + salomepypkg.rst + +EXTRA_DIST+= $(RSTFILES) + +EXTRA_DIST+= \ + conf.py + +install-data-local: + $(INSTALL) -d $(pydocdir) + if test -d "html"; then b=; else b="$(srcdir)/"; fi; \ + cp -rf $$b"html"/* $(pydocdir) ; \ + if test -f $$b"latex"/kernelpy.pdf; then cp -f $$b"latex"/kernelpy.pdf $(pydocdir) ; fi; + +uninstall-local: + chmod -R +w $(pydocdir) + rm -rf $(pydocdir)/* + +clean-local: + -rm -rf html latex doctrees + if test -d "html"; then rm -rf html ; fi diff --git a/doc/docutils/conf.py b/doc/docutils/conf.py new file mode 100644 index 000000000..a193dd95e --- /dev/null +++ b/doc/docutils/conf.py @@ -0,0 +1,200 @@ +# -*- coding: iso-8859-1 -*- +# +# yacs documentation build configuration file, created by +# sphinx-quickstart on Fri Aug 29 09:57:25 2008. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# The contents of this file are pickled, so don't put values in the namespace +# that aren't pickleable (module imports are okay, they're removed automatically). +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If your extensions are in another directory, add it here. If the directory +# is relative to the documentation root, use os.path.abspath to make it +# absolute, like shown here. +#sys.path.append(os.path.abspath('.')) + +# General configuration +# --------------------- + +# 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'] + +# Uncomment the following line to build the links with Python documentation +# (you might need to set http_proxy environment variable for this to work) +#extensions += ['sphinx.ext.intersphinx'] + +# Intersphinx mapping to add links to modules and objects in the Python +# standard library documentation +intersphinx_mapping = {'http://docs.python.org': None} + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +source_encoding = 'utf-8' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'KERNEL python packages' +copyright = '2010 EDF R&D' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '5.1.4' +# The full version, including alpha/beta/rc tags. +release = '5.1.4' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +language = 'en' + +# 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 documents that shouldn't be included in the build. +#unused_docs = [] + +# List of directories, relative to source directory, that shouldn't be searched +# for source files. +exclude_trees = ['.build','ref','images','CVS','.svn'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + + +# Options for HTML output +# ----------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +html_theme = 'default' +#html_theme = 'nature' +#html_theme = 'agogo' +#html_theme = 'sphinxdoc' +#html_theme = 'omadoc' + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = ['themes'] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = 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'] + +# 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_use_modindex = False + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, the reST sources are included in the HTML build as _sources/. +html_copy_source = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'kernelpydoc' + + +# Options for LaTeX output +# ------------------------ + +# The paper size ('letter' or 'a4'). +latex_paper_size = 'a4' + +# The font size ('10pt', '11pt' or '12pt'). +latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, document class [howto/manual]). +latex_documents = [ + ('index', 'kernelpy.tex', 'Documentation of the KERNEL python packages', 'EDF R\&D', 'manual') +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +latex_logo = '../salome/tui/images/head.png' + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = True + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +latex_use_modindex = False diff --git a/doc/docutils/docapi.rst b/doc/docutils/docapi.rst new file mode 100644 index 000000000..23e9fe095 --- /dev/null +++ b/doc/docutils/docapi.rst @@ -0,0 +1,46 @@ + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + Programming interface (API) of the ``salome.kernel`` package +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +This section describes the python packages and modules of the +``salome.kernel`` python package. The main part is generated from the +code documentation included in source python files. + +:mod:`salome.kernel` -- Package containing the KERNEL python utilities +====================================================================== + +:mod:`deprecation` -- Indication of deprecated modules and functions +-------------------------------------------------------------------- + +.. automodule:: salome.kernel.deprecation + :members: + + +:mod:`termcolor` -- Display colored text in terminal +---------------------------------------------------- + +.. automodule:: salome.kernel.termcolor + :members: + :exclude-members: TEST_termcolor + + +:mod:`logger` -- Logging utility +-------------------------------- + +.. automodule:: salome.kernel.logger + +.. autoclass:: Logger + :members: + :show-inheritance: + +.. autoclass:: ExtLogger + :members: + :show-inheritance: + + +:mod:`studyedit` -- Study editor +-------------------------------- + +.. automodule:: salome.kernel.studyedit + :members: diff --git a/doc/docutils/index.rst b/doc/docutils/index.rst new file mode 100644 index 000000000..1efc96704 --- /dev/null +++ b/doc/docutils/index.rst @@ -0,0 +1,21 @@ + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + Documentation of the KERNEL python packages +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +Main documentation +================== + +.. toctree:: + :maxdepth: 3 + + docapi.rst + + +Additional documentation +======================== + +.. toctree:: + :maxdepth: 3 + + salomepypkg.rst diff --git a/doc/docutils/salomepypkg.rst b/doc/docutils/salomepypkg.rst new file mode 100644 index 000000000..d0d05f168 --- /dev/null +++ b/doc/docutils/salomepypkg.rst @@ -0,0 +1,262 @@ + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +Complement A: Organizing the SALOME python functions in a packaged structure +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +:Contacts: Guillaume Boulant, Christian Caremoli, Renaud Barate + +This notes are the instructions to organise the python files of SALOME +in a packaged python structure. This is the first step of the development +process, whose goal is to validate the principles and show a possible +way. + +Objectives +========== + +The main idea is to import SALOME python functions by doing: + + >>> from salome.kernel. import + +instead of: + + >>> from import + +as it must be done up to now because of the flat organisation of +python files in the installation folders of SALOME modules. + +To reach this target, the files ``.py`` have to be +organised in a packaged python structure where the main package is +named ``salome``, and then sub-packages could be created for each +SALOME module: + +* ``salome.kernel``: for kernel python functions, embedded in the + KERNEL module +* ``salome.gui``: for gui python functions, embedded in the GUI module +* ``salome.geom``: for geom python functions, embedded in the GEOM + module +* and so on ... + +The motivations of this objective are twice: + +* Definitively prevent the risk of naming conflict between python + modules coming from different SALOME modules. Today, the developper + of a module has to take care of the names used in other modules to + choose a name. +* Integrate in SALOME some python modules initialy developed in the + context of domain specific SALOME applications (SALOME-MECA, + SALOME-CFD, OPENTURN, PANTHERE) and whose source files are organized + in a packaged python structure. + +The starting point then is a python library named ``nepal`` that +provides SALOME helper functions classified by modules +(KERNEL,GEOM,...) and organized in a packaged python structure: + +* ``salome.kernel``: helper functions for manipulating the SALOME + study and its components (SComponents and SObject). This provides + also general purpose utilities as a simple logging, a threading + helper. +* ``salome.gui``: helper functions to manipulate the graphical + representation of studies and the general behavior of the graphical + interface. This provides also generic templates for implementing + dialog box with the MVC pattern. +* ``salome.geom``: essentially contains a function called + "visualization of structural elements". This is used by mechanical + ingeneers to create the 3D geometrical object corresponding to the + numerical model of a given structural element. + +The target point is to have the ``salome.kernel`` part in the KERNEL +module, the ``salome.geom`` part in the GEOM module, and so on. And +with **no impact on SALOME scripts** that already exists (import salome, +and all other stuff should be imported and work as before). + + +Problems +======== + +To target this situation, we have to face two problems: + +* A naming conflict with the instruction ``import salome``. The result + is unpredictible because of the existance in the ``sys.path`` of + both a file ``salome.py`` and a package ``salome``. +* The dispatching of ``salome.*`` sub-packages in the different SALOME + modules. + +Naming conflict between ``salome.py`` module and ``salome`` package +------------------------------------------------------------------- + +The problem is solved by installing the ``salome.py`` file under the +name ``__init__.py`` in a folder named ``${salomepythondir}/salome``. + +By this operation, the ``${salomepythondir}/salome`` directory is +transformed in a python package and the instruction ``import salome`` +do the same things as before this modification, without any +modification of the ``sys.path``. + +Dispatching of ``salome.*`` sub-packages in different SALOME modules +-------------------------------------------------------------------- + +When we use a SALOME virtual application, the problem is naturally +solved by the fact that every sub-packages are virtually installed in +the same directory, the directory ``${salomepythondir}/salome`` +containing the file ``__init__.py``. + +Nevertheless, some people doesn't use the virtual application. To get +a robust configuration in any case, one can use the python namespace +pattern. This consists in creating a virtual python package that +aggregates all the sub-packages. + +Technically speaking, this consists in implementing in the file +``${salomepythondir}/salome/__init__.py`` (new version of +``salome.py``) a function that automatically extend the ``__path__`` +variable with sub-packages that can be found in SALOME modules +installation paths. The code looks something like that: + +.. code-block:: python + + import os, sys + + MATCH_ENDING_PATTERN="site-packages/salome" + + def extend_path(pname): + for dir in sys.path: + if not isinstance(dir, basestring) or not os.path.isdir(dir) or not dir.endswith(MATCH_ENDING_PATTERN): + continue + subdir = os.path.join(dir, pname) + # WARN: This may still add duplicate entries to path on + # case-insensitive filesystems + if os.path.isdir(subdir) and subdir not in __path__: + print "INFO - The directory %s is appended to sys.path" % subdir + __path__.append(subdir) + + extend_path(ROOT_PYTHONPACKAGE_NAME) + + +Adaptation of the ``apply_gen`` utility +---------------------------------------- + +Due to the specific above choices, the ``apply_gen`` utility must be +modified so that the sub-folder ``salome`` in ``${salomepythondir}`` +is not generated as a symbolic link any longer but as a real folder +containing symbolic links towards the module specific python +sub-packages (``kernel``, ``geom``, ``smesh``, ...) and to the single +file ``__init__.py`` provided by the KERNEL module. + +This adaptation can be done in the ``virtual_salome.py`` script. + + +What to do with already existing python files? +---------------------------------------------- + +Do nothing at this step, it works fine because the files are installed +in a path included in the ``sys.path``. + +In a future version, it should be nice to reverse all the python files +of the KERNEL library in this packaged structure. But this can't be +done without impact on existing python user scripts. + +Instructions +============ + +Instructions for creating the python package +-------------------------------------------- + +Considering the elements described above, a procedure that works to +get the packaged python structure is: + +* Rename the file ``salome.py`` in ``__init__.py`` (and adapt the + Makefile.am). This is located in the source directory + ``src/KERNEL_PY``. +* Copy the sources files of the kernel part in the source directory + ``src/KERNEL_PY`` starting with a stage named ``kernel`` including + its own packaged structure (only python files and a file + ``__init__.py`` for now) +* Copy the sources files of the geom part in the source directory + ``src/GEOM_PY`` (to be created) of the GEOM module. In this case, we + copy the python files directly in the directory (no stage named + ``geom``, it's not required for source organisation, and will be + created only for installation by makefile). +* Apply the same procedure for every other SALOME modules (it concerns + only SMESH up to now). +* Apply the "namespace pattern" by implementing and invoking the + ``extend_path`` function in the newly created file ``__init__.py`` +* Adapt the ``apply_gen`` utility to take into account the finer + folder hierarchy in ``site-packages``. + +Note that all python files that were existing in the KERNEL module +are leaft untouched but the file ``salome.py``. + +By convention, the source code of the python packages of a SALOME +module is located in the source directory +``src/_PY`` (convention already used in the KERNEL). + +Instructions for the associated documentation +--------------------------------------------- + +One special point for the documentation: + +* The documentation of the python package API is writen in rst + (restructured text) and generated form the source code with sphinx. +* The rst source files are located in the directory + ``/doc/docutils``. +* The html generated files are installed in the directory + ``/share/doc/salome/docutils`` but are not connected to + the in-line documentation of the SALOME associated module (menu help + of the SALOME application). + +Any suggestion on this point would be appreciated. + +TODO (by someone): + +* Move all files ``*.txt`` from the ``/doc`` folder to the + ``/doc/docutils`` folder and analyse what is still to date + and usefull. +* Integrate in this part the reference documentation of the ``salome`` + utility and all documentation associated to the launching process + (in particular virtual application) +* Connect this part of the documentation to the main part (doxygen + part). + + +Synthesis +--------- + +Finaly, here is a synthesis of modifications in source files. + +Files modified: + +* See the CVS patch files KERNEL.patch, GEOM.patch and SMESH.patch + (the only SALOME modules modified today). + +Files to be added: + +* KERNEL: file ``src/KERNEL_PY/__init__.py`` (``salome.py`` renamed) +* KERNEL: directory ``src/KERNEL_PY/kernel`` +* KERNEL: directory ``doc/docutils`` +* KERNEL: file ``salome_adm/unix/config_files/check_sphinx.m4`` +* GEOM : directory ``src/GEOM_PY`` +* GEOM : directory ``doc/docutils`` +* SMESH : directory ``src/SMESH_PY`` +* SMESH : directory ``doc/docutils`` + +Files to be delete: + +* file ``src/KERNEL_PY/salome.py`` + + +Tests and usage +=============== + +The instructions above provides you with a SALOME application whose +modules embed there dedicated python packages. This installation can +can be tested using some test use cases. For example, the +visualisation of structural elements (provided by the package +``salome.geom`` can be tested by: + + >>> from salome.geom.structelem import TEST_StructuralElement + >>> TEST_StructuralElement() + +This can be enter in the GUI python console or in a python interpreter +executed in a SALOME session. + +For more details, read the API documentation in +``/share/doc/salome/docutils``. diff --git a/salome_adm/unix/config_files/check_sphinx.m4 b/salome_adm/unix/config_files/check_sphinx.m4 new file mode 100644 index 000000000..eec149530 --- /dev/null +++ b/salome_adm/unix/config_files/check_sphinx.m4 @@ -0,0 +1,35 @@ +dnl Copyright (C) 2006-2008 CEA/DEN, EDF R&D +dnl +dnl This library is free software; you can redistribute it and/or +dnl modify it under the terms of the GNU Lesser General Public +dnl License as published by the Free Software Foundation; either +dnl version 2.1 of the License. +dnl +dnl This library is distributed in the hope that it will be useful, +dnl but WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +dnl Lesser General Public License for more details. +dnl +dnl You should have received a copy of the GNU Lesser General Public +dnl License along with this library; if not, write to the Free Software +dnl Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +dnl +dnl See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +dnl +AC_DEFUN([CHECK_SPHINX],[ + +AC_CHECKING(for sphinx doc generator) + +sphinx_ok=yes +dnl where is sphinx ? +AC_PATH_PROG(SPHINX,sphinx-build) +if test "x$SPHINX" = "x" +then + AC_MSG_WARN(sphinx not found) + sphinx_ok=no +fi + +AM_CONDITIONAL(SPHINX_IS_OK, [test x"$sphinx_ok" = xyes]) + +])dnl +dnl diff --git a/salome_adm/unix/make_common_starter.am b/salome_adm/unix/make_common_starter.am index 198595381..d1ebd26cd 100644 --- a/salome_adm/unix/make_common_starter.am +++ b/salome_adm/unix/make_common_starter.am @@ -44,9 +44,17 @@ salomeincludedir = $(includedir)/salome libdir = $(prefix)/lib@LIB_LOCATION_SUFFIX@/salome bindir = $(prefix)/bin/salome salomescriptdir = $(bindir) +# _GBO_SALOME_PYTHON_PACKAGING_ +# Maybe we could try to suppress on stage in this folder path, for +# example by installing by default the python files in site-packages +# folder. Then, python packages could be installed in the +# site-packages/salome folder (main package containing the root +# __init__.py file). This could be done by replacing salomepythondir +# and salomepyexecdir by pythondir and pyexecdir respectively (TO BE +# DONE) salomepythondir = $(pythondir)/salome salomepyexecdir = $(pyexecdir)/salome - +salomepypkgdir = $(salomepythondir)/salome # Directory for installing idl files salomeidldir = $(prefix)/idl/salome diff --git a/src/KERNEL_PY/Makefile.am b/src/KERNEL_PY/Makefile.am index 8ec0e4f73..177977feb 100755 --- a/src/KERNEL_PY/Makefile.am +++ b/src/KERNEL_PY/Makefile.am @@ -36,7 +36,6 @@ if CORBA_GEN salomepython_PYTHON += \ Help.py \ PyInterp.py \ - salome.py \ batchmode_salome.py \ salome_test.py \ salome_kernel.py \ @@ -50,4 +49,11 @@ if CORBA_GEN salome_genericobj.py endif +# _GBO_SALOME_PYTHON_PACKAGING_ +# Note that the salome.py should not be installed any more +# Special extention to create a python packaging +SUBDIRS= kernel +salomepypkg_PYTHON= \ + __init__.py + sharedpkgpython_PYTHON = kernel_shared_modules.py diff --git a/src/KERNEL_PY/__init__.py b/src/KERNEL_PY/__init__.py new file mode 100644 index 000000000..850550069 --- /dev/null +++ b/src/KERNEL_PY/__init__.py @@ -0,0 +1,219 @@ +# -*- coding: iso-8859-1 -*- +# Copyright (C) 2007-2010 CEA/DEN, EDF R&D, OPEN CASCADE +# +# Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +# CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +# File : salome.py renamed as __init__.py for python packaging (gboulant) +# Author : Paul RASCLE, EDF +# Module : SALOME +# $Header$ +# +""" +Module salome gives access to Salome ressources. + +variables: + + - salome.orb : CORBA + - salome.naming_service : instance of naming Service class + - methods: + - Resolve(name) : find a CORBA object (ior) by its pathname + - Register(name) : register a CORBA object under a pathname + + - salome.lcc : instance of lifeCycleCORBA class + - methods: + - FindOrLoadComponent(server,name) : + obtain an Engine (CORBA object) + or launch the Engine if not found, + with a Server name and an Engine name + + - salome.sg : salome object to communicate with the graphical user interface (if any) + - methods: + - updateObjBrowser(bool): + - getActiveStudyId(): + - getActiveStudyName(): + + - SelectedCount(): returns number of selected objects + - getSelected(i): returns entry of selected object number i + - getAllSelected(): returns list of entry of selected objects + - AddIObject(Entry): select an existing Interactive object + - RemoveIObject(Entry): remove object from selection + - ClearIObjects(): clear selection + + - Display(*Entry): + - DisplayOnly(Entry): + - Erase(Entry): + - DisplayAll(): + - EraseAll(): + + - IDToObject(Entry): returns CORBA reference from entry + + - salome.myStudyName : active Study Name + - salome.myStudyId : active Study Id + - salome.myStudy : the active Study itself (CORBA ior) + - methods : defined in SALOMEDS.idl + +""" +## @package salome +# Module salome gives access to Salome ressources. +# +# \param salome.orb : CORBA orb object +# \param salome.naming_service : instance of naming Service class (SALOME_NamingServicePy::SALOME_NamingServicePy_i) +# \param salome.lcc : instance of lifeCycleCORBA class (SALOME_LifeCycleCORBA) +# \param salome.sg : Salome object to communicate with the graphical user interface, if running (see interface in salome_iapp::SalomeOutsideGUI) +# \param salome.myStudyName : active Study Name +# \param salome.myStudyId : active Study Id +# \param salome.myStudy : the active Study (interface SALOMEDS::Study) + +# +# ========================================================================== +print "INFO - You just load the python package salome, replacing the python module salome.py" +# +# The function extend_path is used here to aggregate in a single +# virtual python package all the python sub-packages embedded in each +# SALOME modules (python "namespace" pattern). +# +ROOT_PYTHONPACKAGE_NAME="salome" +# +# This root package name is expected to be found as a directory in +# some paths of the sys.path variable, especially the paths +# /lib/pythonX.Y/site-packages/salome where are +# installed the python files. These paths are theorically appended by +# the SALOME main runner and should be in the sys.path at this point +# of the application. The extend_path is looking then for directories +# of the type: +# +# /lib/pythonX.Y/site-packages/salome/ +# +# And append them to the sys.path. These directories are supposed to +# be the pieces to be aggregated as a single virtual python package. +# +import os, sys +MATCH_ENDING_PATTERN="site-packages/salome" +def extend_path(pname): + for dir in sys.path: + if not isinstance(dir, basestring) or not os.path.isdir(dir) or not dir.endswith(MATCH_ENDING_PATTERN): + continue + subdir = os.path.join(dir, pname) + # XXX This may still add duplicate entries to path on + # case-insensitive filesystems + if os.path.isdir(subdir) and subdir not in __path__: + print "INFO - The directory %s is appended to sys.path" % subdir + __path__.append(subdir) + +extend_path(ROOT_PYTHONPACKAGE_NAME) +# ========================================================================== +# + +from salome_kernel import * +from salome_study import * +from salome_iapp import * + +# +# The next block is workaround for the problem of shared symbols loading for the extension modules (e.g. SWIG-generated) +# that causes RTTI unavailable in some cases. To solve this problem, sys.setdlopenflags() function is used. +# Depending on the Python version and platform, the dlopen flags can be defined in the dl, DLFUN or ctypes module. +# +import sys +flags = None +if not flags: + try: + # dl module can be unavailable + import dl + flags = dl.RTLD_NOW | dl.RTLD_GLOBAL + except: + pass + pass +if not flags: + try: + # DLFCN module can be unavailable + import DLFCN + flags = DLFCN.RTLD_NOW | DLFCN.RTLD_GLOBAL + except: + pass + pass +if not flags: + try: + # ctypes module can be unavailable + import ctypes + flags = ctypes.RTLD_GLOBAL + except: + pass + pass + +if flags: + sys.setdlopenflags(flags) + pass + +orb, lcc, naming_service, cm,sg=None,None,None,None,None +myStudyManager, myStudyId, myStudy, myStudyName=None,None,None,None + +salome_initial=1 +def salome_init(theStudyId=0,embedded=0): + """ + Performs only once SALOME general purpose intialisation for scripts. + optional argument : theStudyId + When in embedded interpreter inside IAPP, theStudyId is not used + When used without GUI (external interpreter) + 0 : create a new study (default). + n (>0) : try connection to study with Id = n, or create a new one + if study not found. + If study creation, its Id may be different from theStudyId ! + Provides: + orb reference to CORBA + lcc a LifeCycleCorba instance + naming_service a naming service instance + cm reference to the container manager + sg access to SALOME GUI (when linked with IAPP GUI) + myStudyManager the study manager + myStudyId active study identifier + myStudy active study itself (CORBA reference) + myStudyName active study name + """ + global salome_initial + global orb, lcc, naming_service, cm + global sg + global myStudyManager, myStudyId, myStudy, myStudyName + + try: + if salome_initial: + salome_initial=0 + sg = salome_iapp_init(embedded) + orb, lcc, naming_service, cm = salome_kernel_init() + myStudyManager, myStudyId, myStudy, myStudyName =salome_study_init(theStudyId) + pass + pass + except RuntimeError, inst: + # wait a little to avoid trace mix + import time + time.sleep(0.2) + x = inst + print "salome.salome_init():", x + print """ + ============================================ + May be there is no running SALOME session + salome.salome_init() is intented to be used + within an already running session + ============================================ + """ + raise + +#to expose all objects to pydoc +__all__=dir() diff --git a/src/KERNEL_PY/kernel/Makefile.am b/src/KERNEL_PY/kernel/Makefile.am new file mode 100644 index 000000000..9152084da --- /dev/null +++ b/src/KERNEL_PY/kernel/Makefile.am @@ -0,0 +1,11 @@ +include $(top_srcdir)/salome_adm/unix/make_common_starter.am + +mypkgpythondir =$(salomepypkgdir)/kernel + +mypkgpython_PYTHON = \ + __init__.py \ + deprecation.py \ + logger.py \ + studyedit.py \ + termcolor.py \ + logconfig.py diff --git a/src/KERNEL_PY/kernel/__init__.py b/src/KERNEL_PY/kernel/__init__.py new file mode 100644 index 000000000..7527b3e7b --- /dev/null +++ b/src/KERNEL_PY/kernel/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: iso-8859-1 -*- +__all__ = [ "deprecation", "logger", "termcolor", "studyedit", "logconfig" ] diff --git a/src/KERNEL_PY/kernel/deprecation.py b/src/KERNEL_PY/kernel/deprecation.py new file mode 100644 index 000000000..a99b2713a --- /dev/null +++ b/src/KERNEL_PY/kernel/deprecation.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007-2009 EDF R&D +# +# This file is part of PAL_SRC. +# +# PAL_SRC is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# PAL_SRC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with PAL_SRC; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +""" +This module provides several functions to indicate the deprecation of a +module, a method or a function. This is a temporary module to be used only +during PAL refactoring. +""" + +import sys +import warnings +import inspect +import os + +from salome.kernel import termcolor + +msg_seedoc = "See PAL refactoring documentation for possible replacements." + +def __deprecated_with_msg(func, msg): + + def new_func(*args, **kwargs): + if len(inspect.stack()) > 1: + callingfunc = inspect.stack()[1][3] + else: + callingfunc = "CORBA middleware" + warnings.warn( + ("Call to deprecated function %(funcname)s of module " + + "%(modname)s (called from %(callingfunc)s).\n %(msg)s") % { + 'funcname': func.__name__, + 'modname': func.__module__, + 'callingfunc': callingfunc, + 'msg': msg, + }, + category = DeprecationWarning, + stacklevel = 2 + ) + return func(*args, **kwargs) + return new_func + +def deprecated(msg = msg_seedoc): + """ + This is a decorator which can be used to mark functions + as deprecated. It will result in a warning being emitted + when the function is used. The message in parameter will + be displayed and should indicate how this function can be + replaced. If the terminal can display colors, the warning + messages will appear in blue. + """ + def make_dep(f): + if is_called_by_sphinx(): + return f + else: + g = __deprecated_with_msg(f, msg) + g.__name__ = f.__name__ + g.__doc__ = f.__doc__ + g.__dict__.update(f.__dict__) + return g + return make_dep + +def deprecated_module(msg = msg_seedoc): + """ + This function can be used to mark a module as deprecated. + It must be called explicitly at the beginning of the deprecated + module. It will result in a warning being emitted. The message + in parameter will be displayed and should indicate how this + module can be replaced. If the terminal can display colors, + the warning messages will appear in blue. + """ + if not is_called_by_sphinx(): + warnings.warn( + "Importation of deprecated module %(modname)s.\n %(msg)s" % { + 'modname': inspect.getmodulename(inspect.stack()[1][1]), + 'msg': msg, + }, + category = DeprecationWarning, + stacklevel = 5 + ) + +def is_called_by_sphinx(): + """ + Determine if the calling code is ultimately called by sphinx to generate + documentation. The result can be used to conditionally inhibit the + decorators or some Salome-related imports that fail when called outside + Salome. + """ + calling_file = inspect.stack()[len(inspect.stack())-1][1] + basename = os.path.basename(calling_file) + return (basename == "sphinx-build") + + +def __show_colored_warning(message, category, filename, + lineno, file = sys.stderr): + str = warnings.formatwarning(message, category, filename, lineno) + if category == DeprecationWarning and termcolor.canDisplayColor(file): + file.write(termcolor.makeColoredMessage(str, termcolor.BLUE)) + else: + file.write(str) + +warnings.showwarning = __show_colored_warning diff --git a/src/KERNEL_PY/kernel/logconfig.py.in b/src/KERNEL_PY/kernel/logconfig.py.in new file mode 100755 index 000000000..5f3a9d0a8 --- /dev/null +++ b/src/KERNEL_PY/kernel/logconfig.py.in @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007-2009 EDF R&D +# +# This file is part of PAL_SRC. +# +# PAL_SRC is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# PAL_SRC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with PAL_SRC; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +""" +This module defines a variable to indicate which traces should be logged. +""" + +import logging + +loggingLevel = logging.@PYLOGLEVEL@ diff --git a/src/KERNEL_PY/kernel/logger.py b/src/KERNEL_PY/kernel/logger.py new file mode 100644 index 000000000..d2e42f56d --- /dev/null +++ b/src/KERNEL_PY/kernel/logger.py @@ -0,0 +1,220 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007-2009 EDF R&D +# +# This file is part of PAL_SRC. +# +# PAL_SRC is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# PAL_SRC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with PAL_SRC; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# + +#============================================================================= +# Author : Guillaume Boulant (CSSI) +# Rewritten by Renaud Barate (EDF R&D) +# Project : SALOME +# Copyright : EDF 2001-2009 +# $Header$ +#============================================================================= +""" +This module defines a class which provides logging facility in Salome: +""" + +import sys, os +import logging + +from salome.kernel import termcolor +import salome.kernel.logconfig + +class Logger(logging.Logger): + """ + This class formats and displays log messages in Salome environment. It + inherits :class:`Logger` class defined in :mod:`logging` + module from Python library, so all methods from :class:`logging.Logger` + can be used here. The format of the traces is: + + LEVEL [keyword] : Message + + where `LEVEL` is the level of the message (`DEBUG`, `INFO`, etc.), + `keyword` is the name of the logger, and `Message` is the message to log. + + When creating a new Logger object, the parameter `keyword` defines the + name of the logger, `level` defines the logging level (default is + :const:`logging.WARNING` if PAL module is configured with --disable-debug + option or :const:`logging.DEBUG` otherwise), and `color` defines the color + of the log messages for this logger (log messages will appear in color + only when displayed on color-capable ASCII terminals). See module + :mod:`salome.kernel.termcolor` for the color constants. + + By default, log messages will be displayed only on standard output. They + can also be recorded in a file (see method :meth:`setLogFile`). For now, + the CORBA-based logging facility can not be used through this class. + + A source filename `sourceFileName` can be defined. If this argument is + specified, then the `keyword` is modified to the basename of the `sourceFileName` + + Basic usage:: + + from salome.kernel.logger import Logger + log = Logger("Test") + log.debug("Debug message") + log.info("Information message") + log.warning("Warning message") + log.error("Error message") + log.critical("Fatal error message") + + """ + + def __init__(self, keyword = "KEY", level = salome.kernel.logconfig.loggingLevel, + color = None, sourceFileName=None): + + if sourceFileName is not None: + keyword = os.path.basename(sourceFileName).split('.')[0] + logging.Logger.__init__(self, keyword, level) + self._baseFormatString = "%(levelname)-8s [%(name)s] : %(message)s" + self._baseFormatter = logging.Formatter(self._baseFormatString) + if hasattr(sys.stdout, "flush"): + self._stdoutStream = sys.stdout + else: + self._stdoutStream = _UnFlushableLogStream(sys.stdout) + self._stdoutHandler = logging.StreamHandler(self._stdoutStream) + self._stdoutHandler.setLevel(logging.DEBUG) + self.setColor(color) + self.addHandler(self._stdoutHandler) + self._fileHandler = None + + def showDebug(self): + """ + Log all messages, including DEBUG level messages (equivalent to + ``setLevel(logging.DEBUG)``). + """ + self.setLevel(logging.DEBUG) + + def setLogFile(self, logFilename): + """ + Define a log file to record the log messages (in addition to the + standard output). + """ + self.closeLogFile() + self._fileHandler = logging.FileHandler(logFilename, 'w') + self._fileHandler.setLevel(logging.DEBUG) + self._fileHandler.setFormatter(self._baseFormatter) + self.addHandler(self._fileHandler) + + def setColor(self, color): + """ + Set the color of log messages on color-capable terminals. If `color` + is :const:`None`, the default color will be used. + """ + if color is None or not termcolor.canDisplayColor(self._stdoutStream): + stdoutFormatter = self._baseFormatter + else: + format = ("%s%s%s" % + (termcolor.getControlSequence(color), + self._baseFormatString, + termcolor.getControlSequence(termcolor.DEFAULT))) + stdoutFormatter = logging.Formatter(format) + self._stdoutHandler.setFormatter(stdoutFormatter) + + def closeLogFile(self): + """Close the log file.""" + if self._fileHandler is not None: + self.removeHandler(self._fileHandler) + self._fileHandler.close() + self._fileHandler = None + + def hideDebug(self): + """ + Hide DEBUG level messages (equivalent to ``setLevel(logging.INFO)``). + """ + self.setLevel(logging.INFO) + + def fatal(self, message): + """ + Log a message with CRITICAL level. This method only exists for + backward compatibility and is equivalent to ``critical(message)``. + """ + self.critical(message) + + +class _UnFlushableLogStream: + """ + This utility class allows to log messages to a stream with no `flush` + method. This is useful to send log messages to `PyOut` objects. + """ + + def __init__(self, stream): + self._stream = stream + + def write(self, msg): + self._stream.write(msg) + + def flush(self): + pass + + +class ExtLogger(Logger): + """ + This class extends :class:`Logger` class and adds exception information + when DEBUG messages are recorded. It exists mainly for backward + compatibility, as the same thing can be done by calling + ``Logger.debug(message, exc_info = True)``. + """ + + def debug( self, message ): + """ + Log a DEBUG message with exception information (equivalent to + ``Logger.debug(message, exc_info = True)``). + """ + Logger.debug(self, message, exc_info = True) + + +def TEST_Logger(): + """Test function for logger module""" + log = Logger("TST") + + # Base methods + log.info("Information message") + log.debug("Debug message") + log.fatal("Fatal error message") + + # Message building + data = 12 + log.info("This message displays data = " + str(data)) + + data = {} + data["KERNEL"] = "V1" + data["GEOM"] = "V2" + log.info("This message displays data = " + str(data)) + + # Test with a non-string parameter + log.info(data) + + # Test with a default instance + log = Logger() + log.info("Default logger") + + # Test showDebug method + log.setLogFile("test.log") + log.debug("Debug trace") + log.hideDebug() + log.debug("This trace should NOT be displayed") + log.showDebug() + log.debug("This trace should be displayed") + log.closeLogFile() + log.info("After closing the log file") + + +# Main function only used to test the module +if __name__ == "__main__": + TEST_Logger() diff --git a/src/KERNEL_PY/kernel/studyedit.py b/src/KERNEL_PY/kernel/studyedit.py new file mode 100644 index 000000000..d009e3fbd --- /dev/null +++ b/src/KERNEL_PY/kernel/studyedit.py @@ -0,0 +1,479 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007-2009 EDF R&D +# +# This file is part of PAL_SRC. +# +# PAL_SRC is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# PAL_SRC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with PAL_SRC; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +""" +This module provides a new class :class:`StudyEditor` to complement +:class:`Study` and :class:`StudyBuilder` classes. +""" + +import re + +import salome +from salome.kernel.logger import Logger +from salome.kernel import termcolor +logger = Logger("salome.kernel.studyedit", color = termcolor.PURPLE) + +_editors = {} +_DEFAULT_CONTAINER = "FactoryServer" + +def getActiveStudyId(): + """ + Return the ID of the active study. In GUI mode, this function is equivalent + to ``salome.sg.getActiveStudyId()``. Outside GUI, it returns + ``salome.myStudyId`` variable. + """ + salome.salome_init() + if salome.hasDesktop(): + return salome.sg.getActiveStudyId() + else: + return salome.myStudyId + +def getStudyEditor(studyId = None): + """ + Return a :class:`StudyEditor` instance to edit the study with ID + `studyId`. If `studyId` is :const:`None`, return an editor for the current + study. + """ + if studyId is None: + studyId = getActiveStudyId() + if not _editors.has_key(studyId): + _editors[studyId] = StudyEditor(studyId) + return _editors[studyId] + + +class StudyEditor: + """ + This class provides utility methods to complement :class:`Study` and + :class:`StudyBuilder` classes. Those methods may be moved in those classes + in the future. The parameter `studyId` defines the ID of the study to + edit. If it is :const:`None`, the edited study will be the current study. + The preferred way to get a StudyEditor object is through the method + :meth:`getStudyEditor` which allows to reuse existing instances. + + .. attribute:: studyId + + This instance attribute contains the ID of the edited study. This + attribute should not be modified. + + .. attribute:: study + + This instance attribute contains the underlying :class:`Study` object. + It can be used to access the study but the attribute itself should not + be modified. + + .. attribute:: builder + + This instance attribute contains the underlying :class:`StudyBuilder` + object. It can be used to edit the study but the attribute itself + should not be modified. + + """ + def __init__(self, studyId = None): + salome.salome_init() + if studyId is None: + studyId = getActiveStudyId() + self.studyId = studyId + self.study = salome.myStudyManager.GetStudyByID(studyId) + if self.study is None: + raise Exception("Can't create StudyEditor object: " + "Study %d doesn't exist" % studyId) + self.builder = self.study.NewBuilder() + + def findOrCreateComponent(self, componentName, componentLabel=None, icon=None, + loadEngine = True, engineName = None, + containerName = _DEFAULT_CONTAINER): + """ + Find a component corresponding to the specified name `componentName` in the + study, or create it if none is found. Then eventually load the + corresponding engine and the CORBA objects of this component. + + :type componentName: string + :param componentName: name of the new component if created. The name is + a string used to identify the study component. + Generally, it corresponds to the name of the SALOME + module to be associated to this component. + + :type componentLabel: string + :param componentLabel: label of the new component if created. The label + is the string used in the object browser. + (attribute "AttributeName") + + :type icon: string + :param icon: icon for the new component (attribute "AttributePixMap"). + + :type loadEngine: boolean + :param loadEngine: If :const:`True`, find or load the corresponding + engine and associate it with the component. Default + is True. + + :type engineName: string + :param engineName: name of the engine to be associated to the component. + If :const:`None`, use `componentName` instead because + in most of the SALOME modules, the names are identical. + + :type containerName: string + :param containerName: name of the container in which the engine should be + loaded. + + :return: the SComponent found or created. + + """ + sComponent = self.study.FindComponent(componentName) + if sComponent is None: + sComponent = self.builder.NewComponent(componentName) + # Note that the NewComponent method set the "comment" attribute to the + # value of its argument (componentName here) + self.builder.SetName(sComponent, componentLabel) + if icon is not None: + # _MEM_ : This will be effective if and only if the componentName + # corresponds to the module name (as specified in the SalomeApp.xml) + self.setIcon(sComponent, icon) + + if engineName is None: + engineName = componentName + + engine = salome.lcc.FindOrLoadComponent(containerName,engineName) + self.builder.DefineComponentInstance(sComponent, engine) + + # An internal convention (in this class) is to store the name of the + # associated engine in the parameter attribute of the scomponent (so that + # it could be retrieved in a future usage of this scomponent, for example, + # for the need of the function loadComponentEngine). The comment attribute + # SHOULD NOT be used for this purpose because it's used by the SALOME + # resources manager to identify the SALOME module and then localized + # the resource files + attr = self.builder.FindOrCreateAttribute( sComponent, "AttributeParameter" ) + attr.SetString( "ENGINE_NAME", engineName ) + + if loadEngine: + self.loadComponentEngine(sComponent, containerName) + + return sComponent + + def loadComponentEngine(self, sComponent, + containerName = _DEFAULT_CONTAINER): + """ + Load the engine corresponding to `sComponent` in the container + `containerName`, associate the engine with the component and load the + CORBA objects of this component in the study. + """ + attr = self.builder.FindOrCreateAttribute( sComponent, "AttributeParameter" ) + engineName = attr.GetString( "ENGINE_NAME" ) + engine = salome.lcc.FindOrLoadComponent(containerName, + engineName) + self.builder.LoadWith(sComponent, engine) + + def getOrLoadObject(self, item): + """ + Get the CORBA object associated with the SObject `item`, eventually by + first loading it with the corresponding engine. + """ + object = item.GetObject() + if object is None: # the engine has not been loaded yet + sComponent = item.GetFatherComponent() + self.loadComponentEngine(sComponent) + object = item.GetObject() + return object + + def findOrCreateItem(self, fatherItem, name, fileType = None, + fileName = None, comment = None, icon = None, + IOR = None, typeId = None): + """ + Find an object under `fatherItem` in the study with the given + attributes. Return the first one found if at least one exists, + otherwise create a new one with the given attributes and return it. + + See :meth:`setItem` for the description of the parameters. + """ + sObject = self.findItem(fatherItem, name, fileType, fileName, comment, + icon, IOR, typeId) + if sObject is None: + sObject = self.createItem(fatherItem, name, fileType, fileName, + comment, icon, IOR, typeId) + return sObject + + def findItem(self, fatherItem, name = None, fileType = None, + fileName = None, comment = None, icon = None, IOR = None, + typeId = None): + """ + Find an item with given attributes under `fatherItem` in the study. If + none is found, return :const:`None`. If several items correspond to + the parameters, only the first one is returned. The search is made + only on given parameters (i.e. not :const:`None`). To look explicitly + for an empty attribute, use an empty string in the corresponding + parameter. + + See :meth:`setItem` for the description of the parameters. + """ + foundItem = None; + childIterator = self.study.NewChildIterator(fatherItem) + while childIterator.More() and foundItem is None: + childItem = childIterator.Value() + if childItem and \ + (name is None or childItem.GetName() == name) and \ + (fileType is None or \ + self.getFileType(childItem) == fileType) and \ + (fileName is None or \ + self.getFileName(childItem) == fileName) and \ + (comment is None or childItem.GetComment() == comment) and \ + (icon is None or \ + self.getIcon(childItem) == icon) and \ + (IOR is None or childItem.GetIOR() == IOR and \ + (typeId is None or \ + self.getTypeId(childItem) == typeId)): + foundItem = childItem + childIterator.Next() + return foundItem + + def createItem(self, fatherItem, name, fileType = None, fileName = None, + comment = None, icon = None, IOR = None, typeId = None): + """ + Create a new object named `name` under `fatherItem` in the study, with + the given attributes. If an object named `name` already exists under + the father object, the new object is created with a new name `name_X` + where X is the first available index. + + :type fatherItem: SObject + :param fatherItem: item under which the new item will be added. + + :return: new SObject created in the study + + See :meth:`setItem` for the description of the other parameters. + """ + aSObject = self.builder.NewObject(fatherItem) + + aChildIterator = self.study.NewChildIterator(fatherItem) + aMaxId = -1 + aLength = len(name) + aDelim = "_" + anIdRE = re.compile("^" + aDelim + "[0-9]+") + aNameRE = re.compile("^" + name) + while aChildIterator.More(): + aSObj = aChildIterator.Value() + aChildIterator.Next() + aName = aSObj.GetName() + if re.match(aNameRE,aName): + aTmp = aName[aLength:] + if re.match(anIdRE,aTmp): + import string + anId = string.atol(aTmp[1:]) + if aMaxId < anId: + aMaxId = anId + pass + pass + elif aMaxId < 0: + aMaxId = 0 + pass + pass + pass + + aMaxId = aMaxId + 1 + aName = name + if aMaxId > 0: + aName = aName + aDelim + str(aMaxId) + pass + + self.setItem(aSObject, aName, fileType, fileName, comment, icon, + IOR, typeId) + + return aSObject + + def setItem(self, item, name = None, fileType = None, fileName = None, + comment = None, icon = None, IOR = None, typeId = None): + """ + Modify the attributes of an item in the study. Unspecified attributes + (i.e. those set to :const:`None`) are left unchanged. + + :type item: SObject + :param item: item to modify. + + :type name: string + :param name: item name (attribute 'AttributeName'). + + :type fileType: string + :param fileType: item file type (attribute 'AttributeFileType'). + + :type fileName: string + :param fileName: item file name (attribute + 'AttributeExternalFileDef'). + + :type comment: string + :param comment: item comment (attribute 'AttributeComment'). Note that + this attribute will appear in the 'Value' column in + the object browser. + + :type icon: string + :param icon: item icon name (attribute 'AttributePixMap'). + + :type IOR: string + :param IOR: IOR of a CORBA object associated with the item + (attribute 'AttributeIOR'). + + :type typeId: integer + :param typeId: item type (attribute 'AttributeLocalID'). + """ + logger.debug("setItem (ID=%s): name=%s, fileType=%s, fileName=%s, " + "comment=%s, icon=%s, IOR=%s" % + (item.GetID(), name, fileType, fileName, comment, + icon, IOR)) + # Explicit cast is necessary for unicode to string conversion + if name is not None: + self.builder.SetName(item, str(name)) + if fileType is not None: + self.setFileType(item, fileType) + if fileName is not None: + self.setFileName(item, fileName) + if comment is not None: + self.builder.SetComment(item, str(comment)) + if icon is not None: + self.setIcon(item, icon) + if IOR is not None: + self.builder.SetIOR(item, str(IOR)) + if typeId is not None: + self.setTypeId(item, typeId) + + def removeItem(self, item, withChildren = False ): + """ + Remove the given item from the study (the item still is in + the study after the removal) + @param item: the browser object to be removed + @param withChildren: remove children if True + """ + ok = False + try: + if withChildren: + self.builder.RemoveObjectWithChildren(item) + else: + self.builder.RemoveObject(item) + ok = True + except: + ok = False + + return ok + + def setItemAtTag(self, fatherItem, tag, name = None, fileType = None, + fileName = None, comment = None, icon = None, IOR = None, + typeId = None): + """ + Find an item tagged `tag` under `fatherItem` in the study tree or + create it if there is none, then set its attributes. + + :type fatherItem: SObject + :param fatherItem: item under which the tagged item will be looked for + and eventually created. + + :type tag: integer + :param tag: tag of the item to look for. + + :return: the SObject at `tag` if found or created successfully, or + :const:`None` if an error happened. + + See :meth:`setItem` for the description of the other parameters. + """ + found, sObj = fatherItem.FindSubObject(tag) + if not found: + sObj = self.builder.NewObjectToTag(fatherItem, tag) + self.setItem(sObj, name, fileType, fileName, comment, icon, + IOR, typeId) + return sObj + + def getAttributeValue(self, sObject, attributeName, default = None): + """ + Return the value of the attribute named `attributeName` on the object + `sObject`, or `default` if the attribute doesn't exist. + """ + value = default + found, attr = self.builder.FindAttribute(sObject, attributeName) + if found: + value = attr.Value() + return value + + def setAttributeValue(self, sObject, attributeName, attributeValue): + """ + Set the value of the attribute named `attributeName` on the object + `sObject` to the value `attributeValue`. + """ + attr = self.builder.FindOrCreateAttribute(sObject, attributeName) + attr.SetValue(attributeValue) + + def getTypeId(self, sObject): + """ + Return the value of the attribute "AttributeLocalID" of the object + `sObject`, or :const:`None` if it is not set. + """ + return self.getAttributeValue(sObject, "AttributeLocalID") + + def setTypeId(self, sObject, value): + """ + Set the attribute "AttributeLocalID" of the object `sObject` to the + value `value`. + """ + self.setAttributeValue(sObject, "AttributeLocalID", value) + + def getFileType(self, sObject): + """ + Return the value of the attribute "AttributeFileType" of the object + `sObject`, or an empty string if it is not set. + """ + return self.getAttributeValue(sObject, "AttributeFileType", "") + + def setFileType(self, sObject, value): + """ + Set the attribute "AttributeFileType" of the object `sObject` to the + value `value`. + """ + # Explicit cast is necessary for unicode to string conversion + self.setAttributeValue(sObject, "AttributeFileType", str(value)) + + def getFileName(self, sObject): + """ + Return the value of the attribute "AttributeExternalFileDef" of the + object `sObject`, or an empty string if it is not set. + """ + return self.getAttributeValue(sObject, "AttributeExternalFileDef", "") + + def setFileName(self, sObject, value): + """ + Set the attribute "AttributeExternalFileDef" of the object `sObject` + to the value `value`. + """ + # Explicit cast is necessary for unicode to string conversion + self.setAttributeValue(sObject, "AttributeExternalFileDef", + str(value)) + + def getIcon(self, sObject): + """ + Return the value of the attribute "AttributePixMap" of the object + `sObject`, or an empty string if it is not set. + """ + value = "" + found, attr = self.builder.FindAttribute(sObject, "AttributePixMap") + if found and attr.HasPixMap(): + value = attr.GetPixMap() + return value + + def setIcon(self, sObject, value): + """ + Set the attribute "AttributePixMap" of the object `sObject` to the + value `value`. + """ + attr = self.builder.FindOrCreateAttribute(sObject, "AttributePixMap") + # Explicit cast is necessary for unicode to string conversion + attr.SetPixMap(str(value)) diff --git a/src/KERNEL_PY/kernel/termcolor.py b/src/KERNEL_PY/kernel/termcolor.py new file mode 100644 index 000000000..9d584afc8 --- /dev/null +++ b/src/KERNEL_PY/kernel/termcolor.py @@ -0,0 +1,150 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007-2009 EDF R&D +# +# This file is part of PAL_SRC. +# +# PAL_SRC is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# PAL_SRC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with PAL_SRC; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +# Author : Renaud Barate (EDF R&D) +# Date : August 2009 +# +""" +This module provides utility functions to display colored text in the +terminal. It is based on ISO 6429 standard that defines control codes to +change characters representation in color-capable ASCII terminals. + +In this module, colors are represented as lists of codes, so they can be added +to obtain special effects (e.g. RED + GREEN_BG to display red text on green +background). Several constants are defined for the most usual codes to +facilitate the use of colors, but it is also possible to define colors +directly from the corresponding code from ISO 6429 standard. In fact it is +even necessary for less usual codes that don't have an associated constant +(e.g. PURPLE + ['09'] can be used to display a crossed-out purple text). + +Example:: + + import sys + from pal import termcolor + if termcolor.canDisplayColor(sys.stdout): + print termcolor.makeColoredMessage("Hello world!", termcolor.BLUE) + else: + print "Hello world!" + +""" + +# Constants for color codes +DEFAULT = ['00'] +"""Default color for the terminal""" +BOLD = ['01'] +"""Bold text and brighter colors""" +UNDERLINED = ['04'] +"""Underlined text""" +BLACK_FG = ['30'] +"""Black foreground""" +RED_FG = ['31'] +"""Red foreground""" +GREEN_FG = ['32'] +"""Green foreground""" +YELLOW_FG = ['33'] +"""Yellow foreground""" +BLUE_FG = ['34'] +"""Blue foreground""" +PURPLE_FG = ['35'] +"""Purple foreground""" +CYAN_FG = ['36'] +"""Cyan foreground""" +WHITE_FG = ['37'] +"""White foreground""" +BLACK_BG = ['40'] +"""Black background""" +RED_BG = ['41'] +"""Red background""" +GREEN_BG = ['42'] +"""Green background""" +YELLOW_BG = ['43'] +"""Yellow background""" +BLUE_BG = ['44'] +"""Blue background""" +PURPLE_BG = ['45'] +"""Purple background""" +CYAN_BG = ['46'] +"""Cyan background""" +WHITE_BG = ['47'] +"""White background""" + +# Constants for common colored text +BLACK = BLACK_FG +"""Black text (equivalent to BLACK_FG)""" +RED = BOLD + RED_FG +"""Red text (equivalent to BOLD + RED_FG)""" +GREEN = BOLD + GREEN_FG +"""Green text (equivalent to BOLD + GREEN_FG)""" +YELLOW = BOLD + YELLOW_FG +"""Yellow text (equivalent to BOLD + YELLOW_FG)""" +BLUE = BOLD + BLUE_FG +"""Blue text (equivalent to BOLD + BLUE_FG)""" +PURPLE = BOLD + PURPLE_FG +"""Purple text (equivalent to BOLD + PURPLE_FG)""" +CYAN = BOLD + CYAN_FG +"""Cyan text (equivalent to BOLD + CYAN_FG)""" +WHITE = WHITE_FG +"""White text (equivalent to WHITE_FG)""" + + +def canDisplayColor(stream): + """ + Return True if the stream can display colored text, False otherwise. + """ + return hasattr(stream, "isatty") and stream.isatty() + +def getControlSequence(color): + """ + Return the control sequence for the color in parameter, i.e. the string + telling the terminal to display the following text in the given color. + """ + seq = "\x1b[" + for i in range(len(color)): + seq += color[i] + if i < len(color)-1: + seq += ";" + seq += "m" + return seq + +def makeColoredMessage(message, color): + """ + Return a string that can be used to display the message in parameter with + the given color. + """ + return (getControlSequence(color) + + str(message) + + getControlSequence(DEFAULT)) + +def TEST_termcolor(): + """Test function for termcolor module.""" + import sys + if not canDisplayColor(sys.stdout): + print "Standard output does not support colors." + return + print makeColoredMessage("This message must appear in blue.", BLUE) + print makeColoredMessage("This message must appear in red on green " + + "background.", RED + GREEN_BG) + print makeColoredMessage("This message must appear in magenta and " + + "crossed-out.", PURPLE + ['09']) + + +# Main function only used to test the module +if __name__ == "__main__": + TEST_termcolor() -- 2.39.2