From 0ce139886f6a62eeb84d5d04ca1376c749fb14b3 Mon Sep 17 00:00:00 2001 From: Ugur Yilmaz Date: Mon, 8 Jun 2020 15:37:20 +0000 Subject: [PATCH 01/11] added pytango as dependency to readthedocs --- .readthedocs.yml | 23 +++++++++++++++++++++++ docs/src/conf.py | 4 ++-- docs/src/requirements.txt | 1 + 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 .readthedocs.yml create mode 100644 docs/src/requirements.txt diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 0000000..2c02a57 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,23 @@ +# .readthedocs.yml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/src/conf.py + +# Build documentation with MkDocs +#mkdocs: +# configuration: mkdocs.yml + +# Optionally build your docs in additional formats such as PDF and ePub +# formats: all + +# Optionally set the version of Python and requirements required to build your docs +python: + version: 3.7 + install: + - requirements: docs/src/requirements.txt diff --git a/docs/src/conf.py b/docs/src/conf.py index a0212d6..2357187 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -13,9 +13,9 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # -#import tango +import tango #import skabase -autodoc_mock_imports = ['PyTango', 'tango', 'tango.server','run', 'DeviceMeta', 'command', +autodoc_mock_imports = ['PyTango','run', 'DeviceMeta', 'command', 'future', 'future.utils', 'logging', 'logging.handlers', 'ska', 'ska.base', 'SKAMaster', 'SKASubarray','numpy' ] diff --git a/docs/src/requirements.txt b/docs/src/requirements.txt new file mode 100644 index 0000000..5a2a997 --- /dev/null +++ b/docs/src/requirements.txt @@ -0,0 +1 @@ +PyTango==9.3.1 -- GitLab From 9a00f4c0db9c8b0c91c05a93b38d8c6813df1b06 Mon Sep 17 00:00:00 2001 From: Ugur Yilmaz Date: Mon, 8 Jun 2020 15:43:11 +0000 Subject: [PATCH 02/11] added boost-python and numpy as dependency --- docs/src/requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/requirements.txt b/docs/src/requirements.txt index 5a2a997..0297a66 100644 --- a/docs/src/requirements.txt +++ b/docs/src/requirements.txt @@ -1 +1,3 @@ +boost-python +numpy PyTango==9.3.1 -- GitLab From dff14a7f694cb86a1313734c2987bcc5e7bfc3b4 Mon Sep 17 00:00:00 2001 From: Ugur Yilmaz Date: Mon, 8 Jun 2020 15:47:14 +0000 Subject: [PATCH 03/11] fixed the versioning --- docs/src/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/requirements.txt b/docs/src/requirements.txt index 0297a66..82f1e95 100644 --- a/docs/src/requirements.txt +++ b/docs/src/requirements.txt @@ -1,3 +1,3 @@ -boost-python -numpy +boost-python==1.65.1 +numpy==1.17.2 PyTango==9.3.1 -- GitLab From 1b8a417478096f0b9f0bae2f1bf5c2e54861d732 Mon Sep 17 00:00:00 2001 From: Ugur Yilmaz Date: Mon, 8 Jun 2020 15:55:50 +0000 Subject: [PATCH 04/11] trying mocking tango --- .readthedocs.yml | 23 ----------------------- docs/src/conf.py | 10 +++++++++- docs/src/requirements.txt | 3 --- 3 files changed, 9 insertions(+), 27 deletions(-) delete mode 100644 .readthedocs.yml delete mode 100644 docs/src/requirements.txt diff --git a/.readthedocs.yml b/.readthedocs.yml deleted file mode 100644 index 2c02a57..0000000 --- a/.readthedocs.yml +++ /dev/null @@ -1,23 +0,0 @@ -# .readthedocs.yml -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details - -# Required -version: 2 - -# Build documentation in the docs/ directory with Sphinx -sphinx: - configuration: docs/src/conf.py - -# Build documentation with MkDocs -#mkdocs: -# configuration: mkdocs.yml - -# Optionally build your docs in additional formats such as PDF and ePub -# formats: all - -# Optionally set the version of Python and requirements required to build your docs -python: - version: 3.7 - install: - - requirements: docs/src/requirements.txt diff --git a/docs/src/conf.py b/docs/src/conf.py index 2357187..3fc57cb 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -13,7 +13,15 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # -import tango +# Import tango +try: + import tango +except ImportError: + from mock_tango_extension import tango +from tango import Release +print("Building documentation for PyTango {0}".format(Release.version_long)) +print("Using PyTango from: {0}".format(os.path.dirname(tango.__file__))) + #import skabase autodoc_mock_imports = ['PyTango','run', 'DeviceMeta', 'command', 'future', 'future.utils', 'logging', 'logging.handlers', 'ska', diff --git a/docs/src/requirements.txt b/docs/src/requirements.txt deleted file mode 100644 index 82f1e95..0000000 --- a/docs/src/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -boost-python==1.65.1 -numpy==1.17.2 -PyTango==9.3.1 -- GitLab From febcdaf023c5ce46c4aa948691b0696f109c873e Mon Sep 17 00:00:00 2001 From: Ugur Yilmaz Date: Mon, 8 Jun 2020 15:59:22 +0000 Subject: [PATCH 05/11] copied mock_tango_extension file from pytango repo --- docs/src/mock_tango_extension.py | 133 +++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 docs/src/mock_tango_extension.py diff --git a/docs/src/mock_tango_extension.py b/docs/src/mock_tango_extension.py new file mode 100644 index 0000000..6197d2b --- /dev/null +++ b/docs/src/mock_tango_extension.py @@ -0,0 +1,133 @@ +"""Mock the tango._tango extension module. + +This is useful to build the documentation without building the extension. +However this is a bit tricky since the python side relies on what the +extension exposes. Here is the list of the mocking aspects that require +special attention: + + - __doc__ should not contain the mock documentation + - __mro__ is required for autodoc + - __name__ attribute is required + - Device_6Impl class should not be accessible + - the __base__ attribute for Device_[X]Impl is required + - it shoud be possible to set __init__, __getattr__ and __setattr__ methods + - tango.base_types.__document_enum needs to be patched before it is called + - tango.base_types.__document_method needs to be patched before it is called + - the mocks should not have any public methods such as assert_[...] + - _tango.constants.TgLibVers is required (e.g. '9.2.2') + - _tango._get_tango_lib_release function is required (e.g. lambda: 922) + - tango._tango AND tango.constants modules have to be patched + - autodoc requires a proper inheritance for the device impl classes + +Patching tango._tango using sys.modules does not seem to work for python +version older than 3.5 (failed with 2.7 and 3.4) +""" + +# Imports +import sys +from unittest.mock import MagicMock + +__all__ = ('tango',) + + +# Constants +TANGO_VERSION = '9.2.2' +TANGO_VERSION_INT = int(TANGO_VERSION[::2]) + + +# Extension mock class +class ExtensionMock(MagicMock): + + # Remove the mock documentation + __doc__ = None + + # The method resolution order is required for autodoc + __mro__ = object, + + @property + def __name__(self): + # __name__ is used for some objects + if self._mock_name is None: + return '' + return self._mock_name.split('.')[-1] + + def __getattr__(self, name): + # Limit device class discovery + if name == 'Device_6Impl': + raise AttributeError + # Regular mock behavior + return MagicMock.__getattr__(self, name) + + def __setattr__(self, name, value): + # Ignore unsupported magic methods + if name in ["__init__", "__getattr__", "__setattr__", + "__str__", "__repr__"]: + return + # Hook as soon as possible and patch the documentation methods + if name == 'side_effect' and self.__name__ == 'AccessControlType': + import tango.utils + import tango.base_types + import tango.device_server + import tango.connection + tango.utils.__dict__['document_enum'] = document_enum + tango.utils.__dict__['document_method'] = document_method + tango.base_types.__dict__['__document_enum'] = document_enum + tango.device_server.__dict__['__document_method'] = document_method + tango.connection.__dict__['__document_method'] = document_method + tango.connection.__dict__['__document_static_method'] = document_method + MagicMock.__setattr__(self, name, value) + + +# Remove all public methods +for name in dir(ExtensionMock): + if not name.startswith('_') and \ + callable(getattr(ExtensionMock, name)): + setattr(ExtensionMock, name, None) + + +# Patched version of document_enum +def document_enum(klass, enum_name, desc, append=True): + getattr(klass, enum_name).__doc__ = desc + + +# Patched version of document_enum +def document_method(klass, name, doc, add=True): + method = lambda self: None + method.__doc__ = doc + method.__name__ = name + setattr(klass, name, method) + + +# Use empty classes for device impl inheritance scheme +def set_device_implementations(module): + attrs = {'__module__': module.__name__} + module.DeviceImpl = type('DeviceImpl', (object,), attrs) + module.Device_2Impl = type('Device_2Impl', (module.DeviceImpl,), attrs) + module.Device_3Impl = type('Device_3Impl', (module.Device_2Impl,), attrs) + module.Device_4Impl = type('Device_4Impl', (module.Device_3Impl,), attrs) + module.Device_5Impl = type('Device_5Impl', (module.Device_4Impl,), attrs) + + +# Use empty classes for device proxy inheritance scheme +def set_device_proxy_implementations(module): + attrs = {'__module__': module.__name__} + module.Connection = type('Connection', (object,), attrs) + module.DeviceProxy = type('DeviceProxy', (module.Connection,), attrs) + + +# Patch the extension module +_tango = ExtensionMock(name='_tango') +_tango.constants.TgLibVers = TANGO_VERSION +_tango._get_tango_lib_release.return_value = TANGO_VERSION_INT +set_device_implementations(_tango) +set_device_proxy_implementations(_tango) + + +# Patch modules +sys.modules['tango._tango'] = _tango +sys.modules['tango.constants'] = _tango.constants +print('Mocking tango._tango extension module') + + +# Try to import +import tango -- GitLab From 2016dd2ef154475f1a1c254f0036c913710f0de0 Mon Sep 17 00:00:00 2001 From: Ugur Yilmaz Date: Mon, 8 Jun 2020 16:01:54 +0000 Subject: [PATCH 06/11] added missing path --- docs/src/conf.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/src/conf.py b/docs/src/conf.py index 3fc57cb..c3661fd 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -13,6 +13,11 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # +import os +import sys +sys.path.append(os.path.abspath('../../csp-lmc-common/')) +sys.path.append(os.path.abspath('../../csp-lmc-mid/')) +sys.path.append(os.path.abspath('./')) # Import tango try: import tango @@ -29,11 +34,6 @@ autodoc_mock_imports = ['PyTango','run', 'DeviceMeta', 'command', ] autodoc_member_order = 'bysource' -import os -import sys -sys.path.append(os.path.abspath('../../csp-lmc-common/')) -sys.path.append(os.path.abspath('../../csp-lmc-mid/')) - import sphinx_rtd_theme def setup(app): -- GitLab From 1bcaf81d2d876b577a52483668130638abbcc293 Mon Sep 17 00:00:00 2001 From: Ugur Yilmaz Date: Mon, 8 Jun 2020 16:06:12 +0000 Subject: [PATCH 07/11] commented out tango import from mocking --- docs/src/mock_tango_extension.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/mock_tango_extension.py b/docs/src/mock_tango_extension.py index 6197d2b..1044ec0 100644 --- a/docs/src/mock_tango_extension.py +++ b/docs/src/mock_tango_extension.py @@ -130,4 +130,4 @@ print('Mocking tango._tango extension module') # Try to import -import tango +# import tango -- GitLab From af4db00a211184f2050362ed8914ca60e5b88cf0 Mon Sep 17 00:00:00 2001 From: Ugur Yilmaz Date: Mon, 8 Jun 2020 16:53:25 +0000 Subject: [PATCH 08/11] update sphinx version --- .readthedocs.yml | 23 ++++++ docs/src/conf.py | 22 ++--- docs/src/mock_tango_extension.py | 133 ------------------------------- docs/src/requirements.txt | 1 + 4 files changed, 31 insertions(+), 148 deletions(-) create mode 100644 .readthedocs.yml delete mode 100644 docs/src/mock_tango_extension.py create mode 100644 docs/src/requirements.txt diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 0000000..2c02a57 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,23 @@ +# .readthedocs.yml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/src/conf.py + +# Build documentation with MkDocs +#mkdocs: +# configuration: mkdocs.yml + +# Optionally build your docs in additional formats such as PDF and ePub +# formats: all + +# Optionally set the version of Python and requirements required to build your docs +python: + version: 3.7 + install: + - requirements: docs/src/requirements.txt diff --git a/docs/src/conf.py b/docs/src/conf.py index c3661fd..a0212d6 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -13,27 +13,19 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # -import os -import sys -sys.path.append(os.path.abspath('../../csp-lmc-common/')) -sys.path.append(os.path.abspath('../../csp-lmc-mid/')) -sys.path.append(os.path.abspath('./')) -# Import tango -try: - import tango -except ImportError: - from mock_tango_extension import tango -from tango import Release -print("Building documentation for PyTango {0}".format(Release.version_long)) -print("Using PyTango from: {0}".format(os.path.dirname(tango.__file__))) - +#import tango #import skabase -autodoc_mock_imports = ['PyTango','run', 'DeviceMeta', 'command', +autodoc_mock_imports = ['PyTango', 'tango', 'tango.server','run', 'DeviceMeta', 'command', 'future', 'future.utils', 'logging', 'logging.handlers', 'ska', 'ska.base', 'SKAMaster', 'SKASubarray','numpy' ] autodoc_member_order = 'bysource' +import os +import sys +sys.path.append(os.path.abspath('../../csp-lmc-common/')) +sys.path.append(os.path.abspath('../../csp-lmc-mid/')) + import sphinx_rtd_theme def setup(app): diff --git a/docs/src/mock_tango_extension.py b/docs/src/mock_tango_extension.py deleted file mode 100644 index 1044ec0..0000000 --- a/docs/src/mock_tango_extension.py +++ /dev/null @@ -1,133 +0,0 @@ -"""Mock the tango._tango extension module. - -This is useful to build the documentation without building the extension. -However this is a bit tricky since the python side relies on what the -extension exposes. Here is the list of the mocking aspects that require -special attention: - - - __doc__ should not contain the mock documentation - - __mro__ is required for autodoc - - __name__ attribute is required - - Device_6Impl class should not be accessible - - the __base__ attribute for Device_[X]Impl is required - - it shoud be possible to set __init__, __getattr__ and __setattr__ methods - - tango.base_types.__document_enum needs to be patched before it is called - - tango.base_types.__document_method needs to be patched before it is called - - the mocks should not have any public methods such as assert_[...] - - _tango.constants.TgLibVers is required (e.g. '9.2.2') - - _tango._get_tango_lib_release function is required (e.g. lambda: 922) - - tango._tango AND tango.constants modules have to be patched - - autodoc requires a proper inheritance for the device impl classes - -Patching tango._tango using sys.modules does not seem to work for python -version older than 3.5 (failed with 2.7 and 3.4) -""" - -# Imports -import sys -from unittest.mock import MagicMock - -__all__ = ('tango',) - - -# Constants -TANGO_VERSION = '9.2.2' -TANGO_VERSION_INT = int(TANGO_VERSION[::2]) - - -# Extension mock class -class ExtensionMock(MagicMock): - - # Remove the mock documentation - __doc__ = None - - # The method resolution order is required for autodoc - __mro__ = object, - - @property - def __name__(self): - # __name__ is used for some objects - if self._mock_name is None: - return '' - return self._mock_name.split('.')[-1] - - def __getattr__(self, name): - # Limit device class discovery - if name == 'Device_6Impl': - raise AttributeError - # Regular mock behavior - return MagicMock.__getattr__(self, name) - - def __setattr__(self, name, value): - # Ignore unsupported magic methods - if name in ["__init__", "__getattr__", "__setattr__", - "__str__", "__repr__"]: - return - # Hook as soon as possible and patch the documentation methods - if name == 'side_effect' and self.__name__ == 'AccessControlType': - import tango.utils - import tango.base_types - import tango.device_server - import tango.connection - tango.utils.__dict__['document_enum'] = document_enum - tango.utils.__dict__['document_method'] = document_method - tango.base_types.__dict__['__document_enum'] = document_enum - tango.device_server.__dict__['__document_method'] = document_method - tango.connection.__dict__['__document_method'] = document_method - tango.connection.__dict__['__document_static_method'] = document_method - MagicMock.__setattr__(self, name, value) - - -# Remove all public methods -for name in dir(ExtensionMock): - if not name.startswith('_') and \ - callable(getattr(ExtensionMock, name)): - setattr(ExtensionMock, name, None) - - -# Patched version of document_enum -def document_enum(klass, enum_name, desc, append=True): - getattr(klass, enum_name).__doc__ = desc - - -# Patched version of document_enum -def document_method(klass, name, doc, add=True): - method = lambda self: None - method.__doc__ = doc - method.__name__ = name - setattr(klass, name, method) - - -# Use empty classes for device impl inheritance scheme -def set_device_implementations(module): - attrs = {'__module__': module.__name__} - module.DeviceImpl = type('DeviceImpl', (object,), attrs) - module.Device_2Impl = type('Device_2Impl', (module.DeviceImpl,), attrs) - module.Device_3Impl = type('Device_3Impl', (module.Device_2Impl,), attrs) - module.Device_4Impl = type('Device_4Impl', (module.Device_3Impl,), attrs) - module.Device_5Impl = type('Device_5Impl', (module.Device_4Impl,), attrs) - - -# Use empty classes for device proxy inheritance scheme -def set_device_proxy_implementations(module): - attrs = {'__module__': module.__name__} - module.Connection = type('Connection', (object,), attrs) - module.DeviceProxy = type('DeviceProxy', (module.Connection,), attrs) - - -# Patch the extension module -_tango = ExtensionMock(name='_tango') -_tango.constants.TgLibVers = TANGO_VERSION -_tango._get_tango_lib_release.return_value = TANGO_VERSION_INT -set_device_implementations(_tango) -set_device_proxy_implementations(_tango) - - -# Patch modules -sys.modules['tango._tango'] = _tango -sys.modules['tango.constants'] = _tango.constants -print('Mocking tango._tango extension module') - - -# Try to import -# import tango diff --git a/docs/src/requirements.txt b/docs/src/requirements.txt new file mode 100644 index 0000000..a32835c --- /dev/null +++ b/docs/src/requirements.txt @@ -0,0 +1 @@ +sphinx==3.1.0 \ No newline at end of file -- GitLab From a3f6fb01a66c11989195fee9da595061afc2fcd0 Mon Sep 17 00:00:00 2001 From: Ugur Yilmaz Date: Tue, 9 Jun 2020 11:49:06 +0000 Subject: [PATCH 09/11] directly ported pytango configuration for testing --- .readthedocs.yml | 6 +- docs/src/conf.py | 272 ++++++++++++++++++++++++++++++- docs/src/environment.yml | 10 ++ docs/src/mock_tango_extension.py | 133 +++++++++++++++ 4 files changed, 415 insertions(+), 6 deletions(-) create mode 100644 docs/src/environment.yml create mode 100644 docs/src/mock_tango_extension.py diff --git a/.readthedocs.yml b/.readthedocs.yml index 2c02a57..6580136 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -17,7 +17,5 @@ sphinx: # formats: all # Optionally set the version of Python and requirements required to build your docs -python: - version: 3.7 - install: - - requirements: docs/src/requirements.txt +conda: + file: docs/src/environment.yml diff --git a/docs/src/conf.py b/docs/src/conf.py index a0212d6..ee13ee6 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -12,10 +12,17 @@ # 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. # +# Import tango +try: + import tango +except ImportError: + from mock_tango_extension import tango +from tango import Release +print("Building documentation for PyTango {0}".format(Release.version_long)) +print("Using PyTango from: {0}".format(os.path.dirname(tango.__file__))) -#import tango #import skabase -autodoc_mock_imports = ['PyTango', 'tango', 'tango.server','run', 'DeviceMeta', 'command', +autodoc_mock_imports = ['PyTango','run', 'DeviceMeta', 'command', 'future', 'future.utils', 'logging', 'logging.handlers', 'ska', 'ska.base', 'SKAMaster', 'SKASubarray','numpy' ] @@ -216,3 +223,264 @@ intersphinx_mapping = {'https://docs.python.org/': None} # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True + +def copy_spaces(origin): + r = '' + for x in range(len(origin)): + if origin[x] in (' ', '\t'): + r += origin[x] + else: + return r + return r + +def type_to_link(tipus): + if tipus[:9] == 'sequence<' and tipus[-1:] == '>': + return 'sequence<' + type_to_link(tipus[9:-1]) + '>' + #elif tipus in dir(PyTango): + else: + return ':class:`' + tipus + "`" + #else: + # return tipus + +def type_to_pytango_link(tipus): + if tipus[:9] == 'sequence<' and tipus[-1:] == '>': + return 'sequence<' + type_to_link(tipus[9:-1]) + '>' + elif tipus in dir(tango): + return ':class:`' + tipus + "`" + else: + return tipus + +def possible_type_to_link(text): + if len(text) and text[0] == '(' and text[-1] == ')': + return '(' + type_to_link(text[1:-1]) +')' + return text + +def parse_typed_line(line): + spacesSplit = line.strip().split(' ') + first = spacesSplit[0].strip() + return possible_type_to_link(first) + ' ' + ' '.join(spacesSplit[1:]) + +def parse_parameters(line): + spaces = copy_spaces(line) + miniLine = line.strip() + + if miniLine[:2] != '- ': + return line + + spl = miniLine[2:].split(':', 1) + + assert(len(spl) == 2) + + return spaces + ':' + spl[0].strip() + ': ' + parse_typed_line(spl[1]) + + +def parse_bullet_with_type(line): + spaces = copy_spaces(line) + miniLine = line.strip() + + if miniLine[:2] not in ['- ', '* ']: + return line + + spl = miniLine.split(':', 1) + + if len(spl) != 2: + return line + + return spaces + spl[0] + ': ' + parse_typed_line(spl[1]) + + +def parse_throws(line): + words = re.split('(\W+)', line) + assert(line == ''.join(words)) + return ''.join(map(type_to_pytango_link, words)) + + +# http://codedump.tumblr.com/post/94712647/handling-python-docstring-indentation +def docstring_to_lines(docstring): + if not docstring: + return [] + lines = docstring.expandtabs().splitlines() + + # Determine minimum indentation (first line doesn't count): + indent = sys.maxint + for line in lines[1:]: + stripped = line.lstrip() + if stripped: + indent = min(indent, len(line) - len(stripped)) + + # Remove indentation (first line is special): + trimmed = [lines[0].strip()] + if indent < sys.maxint: + for line in lines[1:]: + trimmed.append(line[indent:].rstrip()) + + # Strip off trailing and leading blank lines: + while trimmed and not trimmed[-1]: + trimmed.pop() + while trimmed and not trimmed[0]: + trimmed.pop(0) + return trimmed + +def search_ONLY_signature(name, text): + lines = docstring_to_lines(text) + + # There should be ONE signature and must be the FIRST text + # Signature is the ONLY starting at position 0 + + signatureLine = None + + for ln in range(len(lines)): + line = lines[ln] + + if len(line.strip()) and line[0] != ' ': + parentesis = line.split('(', 1) + fname = parentesis[0].strip() + if len(parentesis)==2 and fname == name.rsplit('.',1)[1]: + if signatureLine is not None: # More than one signature! + return None + signatureLine = ln + else: + return None # There's a text as FIRST text that's NOT the signature! + + if signatureLine is None: + return None + + return lines[signatureLine] + +def split_signature(text): + if text is None: + return None + + # split "fname(params)", "returntype" + ops = text.split('->') + if len(ops) != 2: + return None + + # get rid of "fname" + params = ops[0].strip() + ret_type = ops[1].strip() + p = params.find('(') + if p < 0: + return None + params = params[p:] + return params, ret_type + + + +_with_only_one_signature_methods = {} + +def __reformat_lines(app, what, name, obj, options, lines): + global _with_only_one_signature_methods + if what != 'method': + for ln in range(len(lines)): + lines[ln] = parse_bullet_with_type(lines[ln]) + return + + toinsert = [] + parsingParameters = False + parsingThrows = False + + toinsert.append((0, "")) + + for ln in range(len(lines)): + line = lines[ln] + + if len(line) and line[0] != ' ': + if name in _with_only_one_signature_methods: + # This method has one and only one signature. So it will + # be displayed by sphinx, there's no need for us to fake + # it here... + lines[ln] = "" + else: + parentesis = line.split('(', 1) + fname = parentesis[0].strip() + if len(parentesis)==2 and fname == name.rsplit('.',1)[1]: + sg = split_signature(line) + if sg is not None: + # Main lines are like small titles (**bold**): + lines[ln] = '**' + fname +'** *' + sg[0] + '* **->** ' + type_to_link(sg[1]) + # Add an ENTER after the title, to make a different + # paragraph. So if I have 2 signatures, there's no problem + # with it... + toinsert.append((ln+1, "")) + + ## Main lines are like small titles (**bold**): + #lines[ln]='**' + line.strip() + '**' + ## Add an ENTER after the title, to make a different + ## paragraph. So if I have 2 signatures, there's no problem + ## with it... + #toinsert.append((ln+1, "")) + + + # Mark the "New in this version" lines... + if line.strip()[:14] == "New in PyTango": + lines[ln] = copy_spaces(lines[ln]) + "*" + line.strip() + "*" + parsingParameters = False + parsingThrows = False + + # Look for special control_words + # To replace the actual syntax: "Return : something" + # with the one understood by reStructuredText ":Return: something" + spl = line.strip().split(':', 1) + control_word = spl[0].strip() + + if ((len(spl) != 2) + or (control_word not in ["Parameters", "Return", "Throws", "Example", "See Also" ]) ): + if parsingParameters: + lines[ln] = parse_parameters(line) + elif parsingThrows: + lines[ln] = parse_throws(line) + continue + + parsingParameters = False + parsingThrows = False + spaces = copy_spaces(line) + + # The Example control word is even more special. I will put + # the contents from the following line into a code tag (::) + if control_word == 'Example': + lines[ln] = spaces + ":" + control_word + ": " + spl[1] + toinsert.append((ln+1, "")) + toinsert.append((ln+1, spaces + ' ::')) + toinsert.append((ln+1, "")) + elif control_word == 'Parameters': + lines[ln] = spaces + ":Parameters:" + parse_parameters(spl[1]) + parsingParameters = True + elif control_word == 'Return': + lines[ln] = spaces + ":Return: " + parse_typed_line(spl[1]) + elif control_word == "Throws": + lines[ln] = spaces + ":Throws:" + parse_throws(spl[1]) + parsingThrows = True + else: + lines[ln] = spaces + ":" + control_word + ": " + spl[1] + + for x in range(len(toinsert)-1, -1, -1): + pos, txt = toinsert[x] + lines.insert(pos, txt) + + +def __process_signature(app, what, name, obj, options, signature, return_annotation): + global _with_only_one_signature_methods + if what != 'method': + return + sg = split_signature(search_ONLY_signature(name, obj.__doc__)) + if sg is not None: + _with_only_one_signature_methods[name] = True + return sg + return (signature, return_annotation) + +def setup(app): + # sphinx will call these methods when he finds an object to document. + # I want to edit the docstring to adapt its format to something more + # beautiful. + # I also want to edit the signature because boost methods have no + # signature. I will read the signature from the docstring. + # The order sphinx will call it is __process_signature, __reformat_lines. + # And it is important because I keep some information between the two + # processes + # Problem is __process_signature works great with python methods... + # but is not even called for methods defined by boost. So, as it is, + # is useless now. + + #app.connect('autodoc-process-signature', __process_signature) + app.connect('autodoc-process-docstring', __reformat_lines) diff --git a/docs/src/environment.yml b/docs/src/environment.yml new file mode 100644 index 0000000..4598327 --- /dev/null +++ b/docs/src/environment.yml @@ -0,0 +1,10 @@ +name: py3 +dependencies: +- python=3 +- numpy +- gevent +- graphviz +- mock +- pillow +- sphinx +- sphinx_rtd_theme diff --git a/docs/src/mock_tango_extension.py b/docs/src/mock_tango_extension.py new file mode 100644 index 0000000..6197d2b --- /dev/null +++ b/docs/src/mock_tango_extension.py @@ -0,0 +1,133 @@ +"""Mock the tango._tango extension module. + +This is useful to build the documentation without building the extension. +However this is a bit tricky since the python side relies on what the +extension exposes. Here is the list of the mocking aspects that require +special attention: + + - __doc__ should not contain the mock documentation + - __mro__ is required for autodoc + - __name__ attribute is required + - Device_6Impl class should not be accessible + - the __base__ attribute for Device_[X]Impl is required + - it shoud be possible to set __init__, __getattr__ and __setattr__ methods + - tango.base_types.__document_enum needs to be patched before it is called + - tango.base_types.__document_method needs to be patched before it is called + - the mocks should not have any public methods such as assert_[...] + - _tango.constants.TgLibVers is required (e.g. '9.2.2') + - _tango._get_tango_lib_release function is required (e.g. lambda: 922) + - tango._tango AND tango.constants modules have to be patched + - autodoc requires a proper inheritance for the device impl classes + +Patching tango._tango using sys.modules does not seem to work for python +version older than 3.5 (failed with 2.7 and 3.4) +""" + +# Imports +import sys +from unittest.mock import MagicMock + +__all__ = ('tango',) + + +# Constants +TANGO_VERSION = '9.2.2' +TANGO_VERSION_INT = int(TANGO_VERSION[::2]) + + +# Extension mock class +class ExtensionMock(MagicMock): + + # Remove the mock documentation + __doc__ = None + + # The method resolution order is required for autodoc + __mro__ = object, + + @property + def __name__(self): + # __name__ is used for some objects + if self._mock_name is None: + return '' + return self._mock_name.split('.')[-1] + + def __getattr__(self, name): + # Limit device class discovery + if name == 'Device_6Impl': + raise AttributeError + # Regular mock behavior + return MagicMock.__getattr__(self, name) + + def __setattr__(self, name, value): + # Ignore unsupported magic methods + if name in ["__init__", "__getattr__", "__setattr__", + "__str__", "__repr__"]: + return + # Hook as soon as possible and patch the documentation methods + if name == 'side_effect' and self.__name__ == 'AccessControlType': + import tango.utils + import tango.base_types + import tango.device_server + import tango.connection + tango.utils.__dict__['document_enum'] = document_enum + tango.utils.__dict__['document_method'] = document_method + tango.base_types.__dict__['__document_enum'] = document_enum + tango.device_server.__dict__['__document_method'] = document_method + tango.connection.__dict__['__document_method'] = document_method + tango.connection.__dict__['__document_static_method'] = document_method + MagicMock.__setattr__(self, name, value) + + +# Remove all public methods +for name in dir(ExtensionMock): + if not name.startswith('_') and \ + callable(getattr(ExtensionMock, name)): + setattr(ExtensionMock, name, None) + + +# Patched version of document_enum +def document_enum(klass, enum_name, desc, append=True): + getattr(klass, enum_name).__doc__ = desc + + +# Patched version of document_enum +def document_method(klass, name, doc, add=True): + method = lambda self: None + method.__doc__ = doc + method.__name__ = name + setattr(klass, name, method) + + +# Use empty classes for device impl inheritance scheme +def set_device_implementations(module): + attrs = {'__module__': module.__name__} + module.DeviceImpl = type('DeviceImpl', (object,), attrs) + module.Device_2Impl = type('Device_2Impl', (module.DeviceImpl,), attrs) + module.Device_3Impl = type('Device_3Impl', (module.Device_2Impl,), attrs) + module.Device_4Impl = type('Device_4Impl', (module.Device_3Impl,), attrs) + module.Device_5Impl = type('Device_5Impl', (module.Device_4Impl,), attrs) + + +# Use empty classes for device proxy inheritance scheme +def set_device_proxy_implementations(module): + attrs = {'__module__': module.__name__} + module.Connection = type('Connection', (object,), attrs) + module.DeviceProxy = type('DeviceProxy', (module.Connection,), attrs) + + +# Patch the extension module +_tango = ExtensionMock(name='_tango') +_tango.constants.TgLibVers = TANGO_VERSION +_tango._get_tango_lib_release.return_value = TANGO_VERSION_INT +set_device_implementations(_tango) +set_device_proxy_implementations(_tango) + + +# Patch modules +sys.modules['tango._tango'] = _tango +sys.modules['tango.constants'] = _tango.constants +print('Mocking tango._tango extension module') + + +# Try to import +import tango -- GitLab From 0283c1078505f6cae94ad30e20d558e960ba0a90 Mon Sep 17 00:00:00 2001 From: Ugur Yilmaz Date: Tue, 9 Jun 2020 11:58:49 +0000 Subject: [PATCH 10/11] updated the readthedocs.yml file to reflect the pytango --- .readthedocs.yml | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 6580136..9e43bc0 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,21 +1,2 @@ -# .readthedocs.yml -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details - -# Required -version: 2 - -# Build documentation in the docs/ directory with Sphinx -sphinx: - configuration: docs/src/conf.py - -# Build documentation with MkDocs -#mkdocs: -# configuration: mkdocs.yml - -# Optionally build your docs in additional formats such as PDF and ePub -# formats: all - -# Optionally set the version of Python and requirements required to build your docs conda: file: docs/src/environment.yml -- GitLab From 576d64faff8f761430a57888d628ea593f0015d3 Mon Sep 17 00:00:00 2001 From: Ugur Yilmaz Date: Tue, 9 Jun 2020 12:10:30 +0000 Subject: [PATCH 11/11] fix path of mock_tango_extension.py --- docs/src/conf.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/src/conf.py b/docs/src/conf.py index ee13ee6..5584c81 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -12,6 +12,14 @@ # 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. # + + +import os +import sys +sys.path.append(os.path.abspath('../../csp-lmc-common/')) +sys.path.append(os.path.abspath('../../csp-lmc-mid/')) + +sys.path.append(os.path.abspath('./')) # Import tango try: import tango @@ -28,11 +36,6 @@ autodoc_mock_imports = ['PyTango','run', 'DeviceMeta', 'command', ] autodoc_member_order = 'bysource' -import os -import sys -sys.path.append(os.path.abspath('../../csp-lmc-common/')) -sys.path.append(os.path.abspath('../../csp-lmc-mid/')) - import sphinx_rtd_theme def setup(app): -- GitLab