From f6c187613f9fcbff88dce1908bda8c58e6152685 Mon Sep 17 00:00:00 2001 From: Kerry McAdams Date: Wed, 23 Aug 2023 13:37:59 -0400 Subject: [PATCH 1/5] script adjustments and sphinx autoapi setup --- .gitignore | 4 +- README.md | 29 +++- gen.py | 26 ++- main.py | 18 +- package/doc/Makefile | 30 ++++ package/doc/make.bat | 47 ++++++ .../doc/source/_autoapi_templates/index.rst | 15 ++ package/doc/source/conf.py | 156 ++++++++++++++++++ package/doc/source/index.rst | 14 ++ package/pyproject.toml | 4 +- 10 files changed, 332 insertions(+), 11 deletions(-) create mode 100644 package/doc/Makefile create mode 100644 package/doc/make.bat create mode 100644 package/doc/source/_autoapi_templates/index.rst create mode 100644 package/doc/source/conf.py create mode 100644 package/doc/source/index.rst diff --git a/.gitignore b/.gitignore index e949724d3..a09be1dd8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ -out/ +package/src +package/doc/source/_build +.venv *__pycache__/ diff --git a/README.md b/README.md index c378927bf..52b1918f3 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,34 @@ autocomplete. # Documentation -TODO +1. Run main.py to generate the stubs from Mechanical 232. + + python main.py + + Note: There may be an Unhandled Exception when the stubs are done running. + If the message, "Done creating all Mechanical stubs" appears, proceed + to the next step. + +2. Next, create a virtual environment and activate it: + + python -m venv .venv + + Windows: + .venv\Scripts\activate.bat + + Linux: + source .venv/bin/activate + +3. Navigate to the package directory and install mechanical-stubs + + cd package && pip install -e . + +4. Navigate to the doc directory and make the Sphinx documentation + + cd doc && make html + + Note: Warning messages can be ignored for now. + # Quickstart diff --git a/gen.py b/gen.py index e9b5c9174..c7bc6dbf6 100644 --- a/gen.py +++ b/gen.py @@ -83,7 +83,7 @@ def write_docstring(buffer: typing.TextIO, doc_member: typing.Optional[DocMember return indent = " " * indent_level buffer.write(f'{indent}"""\n') - buffer.write(f"{indent}{summary}\n") + buffer.write(f"{indent}{summary}\n") #include utf-8 encoding buffer.write(f'{indent}"""\n') @@ -140,6 +140,13 @@ class Property: value: typing.Optional[typing.Any] # may be used if static +def fix_str(prop_str): + backtick = prop_str.index('`') + open_bracket = prop_str.index('[') + new_str = prop_str[0:backtick]+prop_str[open_bracket:] + return new_str.replace('+','.') + + def get_properties(class_type: typing.Any, doc: typing.Dict[str, DocMember], type_filter: typing.Callable=None) -> typing.List[Property]: # TODO - base class properties are not handled here. They might be published if they are ansys types # or not if they are system types (e.g. the IList methods implemented by an Ansys type that derives from System.Collections.Generic.IList) @@ -147,6 +154,10 @@ def get_properties(class_type: typing.Any, doc: typing.Dict[str, DocMember], typ output = [] for prop in props: prop_type = f'"{prop.PropertyType.ToString()}"' + + if "`" in prop_type: + prop_type = fix_str(prop_type) + prop_name = prop.Name declaring_type_name = prop.DeclaringType.ToString() method_doc_key = f"P:{declaring_type_name}.{prop_name}" @@ -186,9 +197,14 @@ def write_property(buffer: typing.TextIO, prop: Property, indent_level: int=1) - buffer.write(f"{indent}@property\n") buffer.write(f"{indent}def {prop.name}(cls) -> typing.Optional[{prop.type}]:\n") indent = " " * (1 + indent_level) + write_docstring(buffer, prop.doc, indent_level + 1) + if prop.value: - buffer.write(f"{indent}return {prop.value}\n") + if (type(prop.value) != type(1)) and ("`" in f"{prop.value}"): + prop.value = fix_str(f"{prop.value}") + + buffer.write(f"{indent}return {prop.value}\n") else: buffer.write(f"{indent}return None\n") else: @@ -196,7 +212,7 @@ def write_property(buffer: typing.TextIO, prop: Property, indent_level: int=1) - # setter only, can't use @property, use python builtin-property feature buffer.write(f"{indent}def {prop.name}(self, newvalue: typing.Optional[{prop.type}]) -> None:\n") indent = " " * (1 + indent_level) - write_docstring(buffer, prop.doc, indent_level + 1) + write_docstring(buffer, prop.doc, indent_level + 1) buffer.write(f"{indent}return None\n") buffer.write("\n") indent = " " * (indent_level) @@ -206,7 +222,7 @@ def write_property(buffer: typing.TextIO, prop: Property, indent_level: int=1) - buffer.write(f"{indent}@property\n") buffer.write(f"{indent}def {prop.name}(self) -> typing.Optional[{prop.type}]:\n") indent = " " * (1 + indent_level) - write_docstring(buffer, prop.doc, indent_level + 1) + write_docstring(buffer, prop.doc, indent_level + 1) buffer.write(f"{indent}return None\n") buffer.write("\n") @@ -274,7 +290,7 @@ def write_module(namespace: str, mod_types: typing.List, doc: typing.Dict[str, D class_types = [mod_type for mod_type in mod_types if mod_type.IsClass or mod_type.IsInterface] enum_types = [mod_type for mod_type in mod_types if mod_type.IsEnum] logging.info(f"Writing to {str(outdir.resolve())}") - with open(outdir / "__init__.py", "w") as f: + with open(outdir / "__init__.py", "w", encoding='utf-8') as f: #TODO - jinja if len(enum_types) > 0: f.write("from enum import Enum\n") diff --git a/main.py b/main.py index c0847471b..fce86d7fd 100644 --- a/main.py +++ b/main.py @@ -9,7 +9,7 @@ import gen def resolve(): - install_dir = os.environ["AWP_ROOTDV_DEV"] + install_dir = os.environ["AWP_ROOT232"] # Change this back to AWP_ROOTDV_DEV platform_string = "winx64" if os.name == "nt" else "linx64" sys.path.append(os.path.join(install_dir, "aisol", "bin", platform_string)) version = int(install_dir[-3:]) @@ -24,7 +24,7 @@ def resolve(): resolve() -outdir = pathlib.Path(__file__).parent / "out" +outdir = pathlib.Path(__file__).parent / "package" / "src" logging.getLogger().setLevel(logging.INFO) ASSEMBLIES = [ @@ -46,9 +46,21 @@ def is_type_published(mod_type: "System.RuntimeType"): return "Ansys.Utilities.Sdk.PublishedAttribute" in map(str, attrs) def make(): - outdir.mkdir(exist_ok=True) + outdir.mkdir(parents=True, exist_ok=True) for assembly in ASSEMBLIES: gen.make(outdir, assembly, type_filter=is_type_published) + + with open(os.path.join(outdir, "Ansys","__init__.py"), 'w') as f: + f.write('''try: + import importlib.metadata as importlib_metadata +except ModuleNotFoundError: # pragma: no cover + import importlib_metadata # type: ignore +__version__ = importlib_metadata.version("ansys-mechanical-stubs") +"""Mechanical Scripting version""" +''') + + print("Done creating all Mechanical stubs.") + def minify(): pass diff --git a/package/doc/Makefile b/package/doc/Makefile new file mode 100644 index 000000000..1e465723b --- /dev/null +++ b/package/doc/Makefile @@ -0,0 +1,30 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS = -j auto +SPHINXBUILD = sphinx-build +SOURCEDIR = source +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +clean: + rm -rf $(BUILDDIR)/* + rm -rf source/examples/gallery_examples + find . -type d -name "_autosummary" -exec rm -rf {} + + +pdf: + @$(SPHINXBUILD) -M latex "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + cd $(BUILDDIR)/latex && latexmk -r latexmkrc -pdf *.tex -interaction=nonstopmode || true + (test -f $(BUILDDIR)/latex/*.pdf && echo pdf exists) || exit 1 \ No newline at end of file diff --git a/package/doc/make.bat b/package/doc/make.bat new file mode 100644 index 000000000..ffaade837 --- /dev/null +++ b/package/doc/make.bat @@ -0,0 +1,47 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=_build + +if "%1" == "" goto help +if "%1" == "clean" goto clean +if "%1" == "pdf" goto pdf + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:clean +rmdir /s /q %BUILDDIR% > /NUL 2>&1 +for /d /r %SOURCEDIR% %%d in (_autosummary) do @if exist "%%d" rmdir /s /q "%%d" +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:pdf +%SPHINXBUILD% -M lulatex %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +call %BUILDDIR%/latex/make.bat +goto end + +:end +popd \ No newline at end of file diff --git a/package/doc/source/_autoapi_templates/index.rst b/package/doc/source/_autoapi_templates/index.rst new file mode 100644 index 000000000..e72844f90 --- /dev/null +++ b/package/doc/source/_autoapi_templates/index.rst @@ -0,0 +1,15 @@ +API reference +============= + +This section provides descriptions of Ansys-Mechanical-Stubs subpackages, submodules, classes, +methods, and attributes. Use the search feature or click links to view API documentation. + +.. toctree:: + :titlesonly: + :maxdepth: 2 + + {% for page in pages %} + {% if (page.top_level_object or page.name.split('.')) and page.display %} + {{ page.include_path }} + {% endif %} + {% endfor %} \ No newline at end of file diff --git a/package/doc/source/conf.py b/package/doc/source/conf.py new file mode 100644 index 000000000..40cd4a359 --- /dev/null +++ b/package/doc/source/conf.py @@ -0,0 +1,156 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +from datetime import datetime +import os + +from ansys_sphinx_theme import ( + ansys_favicon, + ansys_logo_white, + ansys_logo_white_cropped, + get_version_match, + latex, + pyansys_logo_black, + watermark, +) +from sphinx.builders.latex import LaTeXBuilder + +from Ansys import __version__ + +LaTeXBuilder.supported_image_types = ["image/png", "image/pdf", "image/svg+xml"] + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +# Project information +project = "ansys-mechanical-stubs" +copyright = f"(c) {datetime.now().year} ANSYS, Inc. All rights reserved" +author = "ANSYS, Inc." +release = version = __version__ +cname = os.getenv("DOCUMENTATION_CNAME", default="scripting.mechanical.docs.pyansys.com") +switcher_version = get_version_match(__version__) + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + 'sphinx.ext.intersphinx', + 'sphinx.ext.autodoc', + 'sphinx.ext.viewcode', + 'numpydoc', + 'sphinx.ext.autosummary', + 'sphinx.ext.inheritance_diagram', + "sphinx_jinja", + "autoapi.extension", +] +exclude_patterns = [] + +# Configuration for Sphinx autoapi +autoapi_dirs = ["../../src/Ansys"] +autoapi_type = "python" +autoapi_options = [ + "members", + "undoc-members", + "show-inheritance", + "show-module-summary", + "special-members", +] +autoapi_template_dir = "_autoapi_templates" +suppress_warnings = ["autoapi.python_import_resolution"] +exclude_patterns.append("_autoapi_templates/index.rst") +autoapi_python_use_implicit_namespaces = True + +# Intersphinx mapping +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), + "numpy": ("https://numpy.org/devdocs", None), + "matplotlib": ("https://matplotlib.org/stable", None), + "imageio": ("https://imageio.readthedocs.io/en/stable", None), + "pandas": ("https://pandas.pydata.org/pandas-docs/stable", None), + "pytest": ("https://docs.pytest.org/en/stable", None), + } + +# Numpydoc config +numpydoc_use_plots = True +numpydoc_show_class_members = False # we take care of autosummary on our own +numpydoc_xref_param_type = True +numpydoc_validate = True +numpydoc_validation_checks = { + # general + "GL06", # Found unknown section + "GL07", # Sections are in the wrong order. + # "GL08", # The object does not have a docstring + "GL09", # Deprecation warning should precede extended summary + "GL10", # reST directives {directives} must be followed by two colons + # Summary + "SS01", # No summary found + "SS02", # Summary does not start with a capital letter + "SS03", # Summary does not end with a period + "SS04", # Summary contains heading whitespaces + "SS05", # Summary must start with infinitive verb, not third person + # Parameters + "PR10", # Parameter "{param_name}" requires a space before the colon ' + # separating the parameter name and type", +} + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +# Favicon +html_favicon = ansys_favicon + +# The suffix(es) of source filenames. +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = "sphinx" + +# The language for content autogenerated by Sphinx_PyAEDT. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = "en" + +# Select desired logo, theme, and declare the html title +html_logo = pyansys_logo_black +html_theme = "ansys_sphinx_theme" +html_short_title = html_title = "Mechanical Stubs" + +# specify the location of your github repo +html_context = { + "github_user": "ansys", + "github_repo": "mechanical-stubs", + "github_version": "main", + "doc_path": "doc/source", +} +html_theme_options = { + "switcher": { + "json_url": f"https://{cname}/versions.json", + "version_match": switcher_version, + }, + "check_switcher": False, + "github_url": "https://github.com/ansys/mechanical-stubs", + "show_prev_next": False, + "show_breadcrumbs": True, + "collapse_navigation": True, + "use_edit_page_button": True, + "additional_breadcrumbs": [ + ("PyAnsys", "https://docs.pyansys.com/"), + ], + "icon_links": [ + { + "name": "Support", + "url": "https://github.com/ansys/mechanical-stubs/discussions", + "icon": "fa fa-comment fa-fw", + }, + ], +} + + diff --git a/package/doc/source/index.rst b/package/doc/source/index.rst new file mode 100644 index 000000000..42f1945a4 --- /dev/null +++ b/package/doc/source/index.rst @@ -0,0 +1,14 @@ +.. Mechanical documentation master file, created by + sphinx-quickstart on Fri Aug 18 17:09:15 2023. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Mechanical API Documentation +====================================== + +This documentation lists the APIs used in Ansys Mechanical 2023R2. + +.. toctree:: + :maxdepth: 2 + + autoapi/index diff --git a/package/pyproject.toml b/package/pyproject.toml index b19fa9a0c..37e189ff2 100644 --- a/package/pyproject.toml +++ b/package/pyproject.toml @@ -30,10 +30,12 @@ dependencies = [ "numpydoc==1.5.0", "sphinx-copybutton==0.5.1", "build>= 0.10.0", + "sphinx.autoapi", + "sphinx.jinja", ] [tool.flit.module] -name = "ansys.mechanical.stubs" +name = "Ansys" [project.urls] Source = "https://github.com/pyansys/mechanical-stubs/" From 1338c62e340a5d9b977f09e61c46aa9b92b482d7 Mon Sep 17 00:00:00 2001 From: Kerry McAdams Date: Wed, 23 Aug 2023 13:53:04 -0400 Subject: [PATCH 2/5] added pre-commit checks --- .pre-commit-config.yaml | 27 ++++++ gen.py | 185 ++++++++++++++++++++++++++++------------ main.py | 37 +++++--- 3 files changed, 183 insertions(+), 66 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..3a5656706 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,27 @@ +repos: + +- repo: https://github.com/psf/black + rev: 23.7.0 + hooks: + - id: black + +- repo: https://github.com/adamchainz/blacken-docs + rev: 1.16.0 + hooks: + - id: blacken-docs + additional_dependencies: [black==23.7.0] + +- repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + +# - repo: https://github.com/PyCQA/flake8 +# rev: 6.1.0 +# hooks: +# - id: flake8 + +- repo: https://github.com/codespell-project/codespell + rev: v2.2.5 + hooks: + - id: codespell \ No newline at end of file diff --git a/gen.py b/gen.py index c7bc6dbf6..5fe8f7040 100644 --- a/gen.py +++ b/gen.py @@ -1,21 +1,24 @@ """Module containing routine to generate python stubs for an assembly.""" -from dataclasses import dataclass -import clr -import System import json import logging import os import pathlib import typing import xml.etree.ElementTree as ET +from dataclasses import dataclass + +import clr +import System + def is_namespace(something): - """ Returns True if object is Namespace: Module """ + """Returns True if object is Namespace: Module""" if isinstance(something, type(System)): return True -def iter_module(module, type_filter: typing.Callable=None): + +def iter_module(module, type_filter: typing.Callable = None): """ Recursively iterate through all namespaces in assembly """ @@ -31,8 +34,9 @@ def iter_module(module, type_filter: typing.Callable=None): namespaces[namespace].append(mod_type) return namespaces -def crawl_loaded_references(assembly, type_filter: typing.Callable=None) -> dict: - """ Crawl Loaded assemblies to get Namespaces. """ + +def crawl_loaded_references(assembly, type_filter: typing.Callable = None) -> dict: + """Crawl Loaded assemblies to get Namespaces.""" return iter_module(assembly, type_filter) @@ -74,22 +78,27 @@ def example(self) -> ET.Element: return self._element.find("example") -def write_docstring(buffer: typing.TextIO, doc_member: typing.Optional[DocMember], indent_level=1) -> None: +def write_docstring( + buffer: typing.TextIO, doc_member: typing.Optional[DocMember], indent_level=1 +) -> None: """Write docstring of class or enum with the given indentation level, if available.""" - if doc_member == None: + if doc_member is None: return summary = doc_member.summary - if summary == None: + if summary is None: return indent = " " * indent_level buffer.write(f'{indent}"""\n') - buffer.write(f"{indent}{summary}\n") #include utf-8 encoding + buffer.write(f"{indent}{summary}\n") buffer.write(f'{indent}"""\n') ENUM_VALUE_REPLACEMENTS = {"None": "None_", "True": "True_"} -def write_enum_field(buffer: typing.TextIO, field: typing.Any, indent_level: int=1) -> None: + +def write_enum_field( + buffer: typing.TextIO, field: typing.Any, indent_level: int = 1 +) -> None: name = field.Name logging.debug(f" writing enum value {name}") int_value = field.GetRawConstantValue() @@ -98,9 +107,19 @@ def write_enum_field(buffer: typing.TextIO, field: typing.Any, indent_level: int buffer.write(f"{indent}{str_value} = {int_value}\n") -def write_enum(buffer: typing.TextIO, enum_type: typing.Any, namespace: str, doc: typing.Dict[str, DocMember], type_filter: typing.Callable=None) -> None: +def write_enum( + buffer: typing.TextIO, + enum_type: typing.Any, + namespace: str, + doc: typing.Dict[str, DocMember], + type_filter: typing.Callable = None, +) -> None: logging.debug(f" writing enum {enum_type.Name}") - fields = [field for field in enum_type.GetFields() if field.IsLiteral and (type_filter is None or type_filter(field))] + fields = [ + field + for field in enum_type.GetFields() + if field.IsLiteral and (type_filter is None or type_filter(field)) + ] buffer.write(f"class {enum_type.Name}(Enum):\n") enum_doc = doc.get(f"T:{namespace}.{enum_type.Name}", None) write_docstring(buffer, enum_doc, 1) @@ -137,37 +156,55 @@ class Property: setter: bool doc: DocMember static: bool - value: typing.Optional[typing.Any] # may be used if static + value: typing.Optional[typing.Any] # may be used if static def fix_str(prop_str): - backtick = prop_str.index('`') - open_bracket = prop_str.index('[') - new_str = prop_str[0:backtick]+prop_str[open_bracket:] - return new_str.replace('+','.') - + backtick = prop_str.index("`") + open_bracket = prop_str.index("[") + new_str = prop_str[0:backtick] + prop_str[open_bracket:] + return new_str.replace("+", ".") -def get_properties(class_type: typing.Any, doc: typing.Dict[str, DocMember], type_filter: typing.Callable=None) -> typing.List[Property]: + +def get_properties( + class_type: typing.Any, + doc: typing.Dict[str, DocMember], + type_filter: typing.Callable = None, +) -> typing.List[Property]: # TODO - base class properties are not handled here. They might be published if they are ansys types # or not if they are system types (e.g. the IList methods implemented by an Ansys type that derives from System.Collections.Generic.IList) - props = [prop for prop in class_type.GetProperties() if (type_filter is None or type_filter(prop))] + props = [ + prop + for prop in class_type.GetProperties() + if (type_filter is None or type_filter(prop)) + ] output = [] for prop in props: prop_type = f'"{prop.PropertyType.ToString()}"' - + if "`" in prop_type: prop_type = fix_str(prop_type) - + prop_name = prop.Name declaring_type_name = prop.DeclaringType.ToString() method_doc_key = f"P:{declaring_type_name}.{prop_name}" prop_doc = doc.get(method_doc_key, None) - property = Property(getter=False, setter=False, type=prop_type, name=prop_name, doc=prop_doc, static=False, value=None) + property = Property( + getter=False, + setter=False, + type=prop_type, + name=prop_name, + doc=prop_doc, + static=False, + value=None, + ) get_method = prop.GetMethod if get_method: if class_type.IsInterface or get_method.IsPublic: property.getter = True - if get_method.IsStatic: # I don't know how to get the static modifier from the property with reflection + if ( + get_method.IsStatic + ): # I don't know how to get the static modifier from the property with reflection property.static = True if get_method.IsPublic and get_method.IsStatic: property.value = prop.GetValue(None, None) @@ -178,7 +215,7 @@ def get_properties(class_type: typing.Any, doc: typing.Dict[str, DocMember], typ # ----- to test the setter only properties, there wasn't another example handy so I hacked this.. # also had to hack to add published to the interface in C# (filed bug about this) - #if prop.Name == "ObjectId" and class_type.Name == "IDataModelObject": + # if prop.Name == "ObjectId" and class_type.Name == "IDataModelObject": # property.getter=False # property.setter=True # ----- @@ -187,32 +224,38 @@ def get_properties(class_type: typing.Any, doc: typing.Dict[str, DocMember], typ return output -def write_property(buffer: typing.TextIO, prop: Property, indent_level: int=1) -> None: +def write_property( + buffer: typing.TextIO, prop: Property, indent_level: int = 1 +) -> None: logging.debug(f" writing property {prop.name}") indent = " " * indent_level if prop.static: # this only works for autocomplete for python 3.9+ - assert prop.getter and not prop.setter, "Don't deal with public static getter+setter" + assert ( + prop.getter and not prop.setter + ), "Don't deal with public static getter+setter" buffer.write(f"{indent}@classmethod\n") buffer.write(f"{indent}@property\n") buffer.write(f"{indent}def {prop.name}(cls) -> typing.Optional[{prop.type}]:\n") indent = " " * (1 + indent_level) - + write_docstring(buffer, prop.doc, indent_level + 1) - + if prop.value: - if (type(prop.value) != type(1)) and ("`" in f"{prop.value}"): + if (type(prop.value) is not type(1)) and ("`" in f"{prop.value}"): prop.value = fix_str(f"{prop.value}") - buffer.write(f"{indent}return {prop.value}\n") + buffer.write(f"{indent}return {prop.value}\n") else: buffer.write(f"{indent}return None\n") else: if prop.setter and not prop.getter: # setter only, can't use @property, use python builtin-property feature - buffer.write(f"{indent}def {prop.name}(self, newvalue: typing.Optional[{prop.type}]) -> None:\n") + buffer.write( + f"{indent}def {prop.name}(self, newvalue: typing.Optional[{prop.type}]) -> None:\n" + ) indent = " " * (1 + indent_level) - write_docstring(buffer, prop.doc, indent_level + 1) + write_docstring(buffer, prop.doc, indent_level + 1) buffer.write(f"{indent}return None\n") buffer.write("\n") indent = " " * (indent_level) @@ -220,14 +263,16 @@ def write_property(buffer: typing.TextIO, prop: Property, indent_level: int=1) - else: assert prop.getter buffer.write(f"{indent}@property\n") - buffer.write(f"{indent}def {prop.name}(self) -> typing.Optional[{prop.type}]:\n") + buffer.write( + f"{indent}def {prop.name}(self) -> typing.Optional[{prop.type}]:\n" + ) indent = " " * (1 + indent_level) - write_docstring(buffer, prop.doc, indent_level + 1) + write_docstring(buffer, prop.doc, indent_level + 1) buffer.write(f"{indent}return None\n") buffer.write("\n") -def write_method(buffer: typing.TextIO, method: Method, indent_level: int=1) -> None: +def write_method(buffer: typing.TextIO, method: Method, indent_level: int = 1) -> None: logging.debug(f" writing method {method.name}") indent = " " * indent_level if method.static: @@ -244,26 +289,49 @@ def write_method(buffer: typing.TextIO, method: Method, indent_level: int=1) -> buffer.write("\n") -def get_methods(class_type: typing.Any, doc: typing.Dict[str, DocMember], type_filter: typing.Callable=None) -> typing.List[Method]: +def get_methods( + class_type: typing.Any, + doc: typing.Dict[str, DocMember], + type_filter: typing.Callable = None, +) -> typing.List[Method]: # TODO - base class properties are not handled here. They might be published if they are ansys types # or not if they are system types (e.g. the IList methods implemented by an Ansys type that derives from System.Collections.Generic.IList) - methods = [prop for prop in class_type.GetMethods() if (type_filter is None or type_filter(prop))] + methods = [ + prop + for prop in class_type.GetMethods() + if (type_filter is None or type_filter(prop)) + ] output = [] for method in methods: method_return_type = f'"{method.ReturnType.ToString()}"' method_name = method.Name params = method.GetParameters() - args = [Param(type=param.ParameterType.ToString(), name=param.Name) for param in params] + args = [ + Param(type=param.ParameterType.ToString(), name=param.Name) + for param in params + ] full_method_name = method_name + f"({','.join([arg.type for arg in args])})" declaring_type_name = method.DeclaringType.ToString() method_doc_key = f"M:{declaring_type_name}.{full_method_name}" method_doc = doc.get(method_doc_key, None) - method = Method(name=method_name, doc=method_doc, return_type=method_return_type, static=method.IsStatic, args=args) + method = Method( + name=method_name, + doc=method_doc, + return_type=method_return_type, + static=method.IsStatic, + args=args, + ) output.append(method) return output -def write_class(buffer: typing.TextIO, class_type: typing.Any, namespace: str, doc: typing.Dict[str, DocMember], type_filter: typing.Callable=None) -> None: +def write_class( + buffer: typing.TextIO, + class_type: typing.Any, + namespace: str, + doc: typing.Dict[str, DocMember], + type_filter: typing.Callable = None, +) -> None: logging.debug(f" writing class {class_type.Name}") buffer.write(f"class {class_type.Name}(object):\n") class_doc = doc.get(f"T:{namespace}.{class_type.Name}", None) @@ -281,17 +349,25 @@ def write_class(buffer: typing.TextIO, class_type: typing.Any, namespace: str, buffer.write("\n") -def write_module(namespace: str, mod_types: typing.List, doc: typing.Dict[str, DocMember], outdir: str, type_filter: typing.Callable=None) -> None: +def write_module( + namespace: str, + mod_types: typing.List, + doc: typing.Dict[str, DocMember], + outdir: str, + type_filter: typing.Callable = None, +) -> None: outdir = pathlib.Path(outdir) for token in namespace.split("."): outdir = outdir / token logging.info(f"Writing to {str(outdir.resolve())}") outdir.mkdir(exist_ok=True, parents=True) - class_types = [mod_type for mod_type in mod_types if mod_type.IsClass or mod_type.IsInterface] + class_types = [ + mod_type for mod_type in mod_types if mod_type.IsClass or mod_type.IsInterface + ] enum_types = [mod_type for mod_type in mod_types if mod_type.IsEnum] logging.info(f"Writing to {str(outdir.resolve())}") - with open(outdir / "__init__.py", "w", encoding='utf-8') as f: - #TODO - jinja + with open(outdir / "__init__.py", "w", encoding="utf-8") as f: + # TODO - jinja if len(enum_types) > 0: f.write("from enum import Enum\n") f.write("import typing\n\n") @@ -309,7 +385,7 @@ def load_doc(xml_path: str) -> ET: root = tree.getroot() members = root.find("members") doc_members = [DocMember(member) for member in members] - output = { doc_member.name: doc_member for doc_member in doc_members} + output = {doc_member.name: doc_member for doc_member in doc_members} return output @@ -328,18 +404,22 @@ def get_doc(assembly: "System.Reflection.RuntimeAssembly"): return None -def get_namespaces(assembly: "System.Reflection.RuntimeAssembly", type_filter: typing.Callable=None) -> typing.Dict: +def get_namespaces( + assembly: "System.Reflection.RuntimeAssembly", type_filter: typing.Callable = None +) -> typing.Dict: """Get all the namespaces and filtered types in the assembly given by assembly_name.""" - logging.info(f" Getting types from the {os.path.basename(assembly.CodeBase)} assembly") + logging.info( + f" Getting types from the {os.path.basename(assembly.CodeBase)} assembly" + ) namespaces = crawl_loaded_references(assembly, type_filter) return namespaces -def make(outdir: str, assembly_name: str, type_filter: typing.Callable=None) -> None: +def make(outdir: str, assembly_name: str, type_filter: typing.Callable = None) -> None: """Generate python stubs for an assembly.""" logging.info(f"Loading assembly {assembly_name}") assembly = clr.AddReference(assembly_name) - if type_filter != None: + if type_filter is not None: logging.info(f" Using a type_filter: {str(type_filter)}") namespaces = get_namespaces(assembly, type_filter) dump_types(namespaces) @@ -350,4 +430,3 @@ def make(outdir: str, assembly_name: str, type_filter: typing.Callable=None) -> logging.info(f" {len(namespaces.items())} namespaces") write_module(namespace, mod_types, doc, outdir, type_filter) logging.info(f"Done processing {namespace}") - diff --git a/main.py b/main.py index fce86d7fd..db2cc7322 100644 --- a/main.py +++ b/main.py @@ -1,20 +1,24 @@ -import clr -import System # isort: skip import logging import os import pathlib import shutil import sys +import clr + import gen +import System # isort: skip + + def resolve(): - install_dir = os.environ["AWP_ROOT232"] # Change this back to AWP_ROOTDV_DEV + install_dir = os.environ["AWP_ROOT232"] # Change this back to AWP_ROOTDV_DEV platform_string = "winx64" if os.name == "nt" else "linx64" sys.path.append(os.path.join(install_dir, "aisol", "bin", platform_string)) version = int(install_dir[-3:]) clr.AddReference("Ansys.Mechanical.Embedding") import Ansys + assembly_resolver = Ansys.Mechanical.Embedding.AssemblyResolver if version == 231: resolve_handler = assembly_resolver.WindowsResolveEventHandler @@ -22,20 +26,23 @@ def resolve(): resolve_handler = assembly_resolver.MechanicalResolveEventHandler System.AppDomain.CurrentDomain.AssemblyResolve += resolve_handler + resolve() -outdir = pathlib.Path(__file__).parent / "package" / "src" +outdir = pathlib.Path(__file__).parent / "package" / "src" logging.getLogger().setLevel(logging.INFO) ASSEMBLIES = [ "Ansys.Mechanical.DataModel", "Ansys.Mechanical.Interfaces", - "Ansys.ACT.WB1" - ] + "Ansys.ACT.WB1", +] + def clean(): shutil.rmtree(outdir, ignore_errors=True) + def is_type_published(mod_type: "System.RuntimeType"): # TODO - should this filter just get applied by the sphinx system as opposed to the stub generator? # that way all the System stuff we depend on could also get generated (like in the iron-python-stubs @@ -45,26 +52,30 @@ def is_type_published(mod_type: "System.RuntimeType"): return False return "Ansys.Utilities.Sdk.PublishedAttribute" in map(str, attrs) + def make(): - outdir.mkdir(parents=True, exist_ok=True) + outdir.mkdir(parents=True, exist_ok=True) for assembly in ASSEMBLIES: gen.make(outdir, assembly, type_filter=is_type_published) - - with open(os.path.join(outdir, "Ansys","__init__.py"), 'w') as f: - f.write('''try: + + with open(os.path.join(outdir, "Ansys", "__init__.py"), "w") as f: + f.write( + '''try: import importlib.metadata as importlib_metadata except ModuleNotFoundError: # pragma: no cover import importlib_metadata # type: ignore __version__ = importlib_metadata.version("ansys-mechanical-stubs") """Mechanical Scripting version""" -''') - +''' + ) + print("Done creating all Mechanical stubs.") def minify(): pass + # TODO - argparse MAKE = True MINIFY = False @@ -100,4 +111,4 @@ def write_docs(commands, tiny_pages_path): fid.write(" :toctree: _autosummary/\n\n") for ans_name in commands: cmd_name = ans_name - fid.write(f" {cmd_name}\n") \ No newline at end of file + fid.write(f" {cmd_name}\n") From 06a7de9212be6e82e2d0eb8ef7438fbb0f4bd437 Mon Sep 17 00:00:00 2001 From: Kerry McAdams Date: Wed, 23 Aug 2023 14:49:28 -0400 Subject: [PATCH 3/5] updated readme and added versions to dependencies --- README.md | 13 +++++++++---- package/pyproject.toml | 4 ++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 52b1918f3..ef86863c5 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,12 @@ autocomplete. # Documentation -1. Run main.py to generate the stubs from Mechanical 232. +1. Install Mechanical 2023 R2 onto your computer. + + Ensure the environment variable, AWP_ROOT232, is set to the location of + Mechanical 2023 R2 (C:\Program Files\Ansys Inc\v232). + +2. Run main.py to generate the stubs from Mechanical 232. python main.py @@ -26,7 +31,7 @@ autocomplete. If the message, "Done creating all Mechanical stubs" appears, proceed to the next step. -2. Next, create a virtual environment and activate it: +3. Next, create a virtual environment and activate it: python -m venv .venv @@ -36,11 +41,11 @@ autocomplete. Linux: source .venv/bin/activate -3. Navigate to the package directory and install mechanical-stubs +4. Navigate to the package directory and install mechanical-stubs cd package && pip install -e . -4. Navigate to the doc directory and make the Sphinx documentation +5. Navigate to the doc directory and make the Sphinx documentation cd doc && make html diff --git a/package/pyproject.toml b/package/pyproject.toml index 37e189ff2..1c7c0d376 100644 --- a/package/pyproject.toml +++ b/package/pyproject.toml @@ -30,8 +30,8 @@ dependencies = [ "numpydoc==1.5.0", "sphinx-copybutton==0.5.1", "build>= 0.10.0", - "sphinx.autoapi", - "sphinx.jinja", + "sphinx-autoapi==2.1.1", + "sphinx-jinja==2.0.2", ] [tool.flit.module] From 517ddebd2b179950f9f76a14c0e7871ffd112d23 Mon Sep 17 00:00:00 2001 From: Kerry McAdams Date: Thu, 24 Aug 2023 15:07:15 -0400 Subject: [PATCH 4/5] small adjustments --- .pre-commit-config.yaml | 19 ++++++++++---- README.md | 20 +++++++------- main.py | 28 +++++++++++++++++--- package/doc/Makefile | 1 - package/doc/source/conf.py | 51 +++++++++++++++++------------------- package/doc/source/index.rst | 4 +-- package/pyproject.toml | 2 +- 7 files changed, 75 insertions(+), 50 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3a5656706..7379b472d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,9 +1,9 @@ repos: -- repo: https://github.com/psf/black - rev: 23.7.0 - hooks: - - id: black +# - repo: https://github.com/psf/black +# rev: 23.7.0 +# hooks: +# - id: black - repo: https://github.com/adamchainz/blacken-docs rev: 1.16.0 @@ -15,6 +15,7 @@ repos: rev: 5.12.0 hooks: - id: isort + args: ["--profile", "black"] # - repo: https://github.com/PyCQA/flake8 # rev: 6.1.0 @@ -24,4 +25,12 @@ repos: - repo: https://github.com/codespell-project/codespell rev: v2.2.5 hooks: - - id: codespell \ No newline at end of file + - id: codespell + +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-merge-conflict + - id: debug-statements + - id: check-yaml + - id: trailing-whitespace \ No newline at end of file diff --git a/README.md b/README.md index ef86863c5..7a542b17a 100644 --- a/README.md +++ b/README.md @@ -18,38 +18,38 @@ autocomplete. # Documentation -1. Install Mechanical 2023 R2 onto your computer. +1. Install Mechanical 2023 R2 onto your computer. Ensure the environment variable, AWP_ROOT232, is set to the location of Mechanical 2023 R2 (C:\Program Files\Ansys Inc\v232). 2. Run main.py to generate the stubs from Mechanical 232. - python main.py + ```python main.py``` Note: There may be an Unhandled Exception when the stubs are done running. - If the message, "Done creating all Mechanical stubs" appears, proceed + If the message, "Done creating all Mechanical stubs" appears, proceed to the next step. 3. Next, create a virtual environment and activate it: - python -m venv .venv - + ```python -m venv .venv``` + Windows: - .venv\Scripts\activate.bat + ```.venv\Scripts\activate.bat``` Linux: - source .venv/bin/activate + ```source .venv/bin/activate``` 4. Navigate to the package directory and install mechanical-stubs - cd package && pip install -e . + ```cd package && pip install -e .``` 5. Navigate to the doc directory and make the Sphinx documentation - cd doc && make html + ```cd doc && make html``` - Note: Warning messages can be ignored for now. + Note: Warning messages can be ignored for now. # Quickstart diff --git a/main.py b/main.py index db2cc7322..8625ec39d 100644 --- a/main.py +++ b/main.py @@ -11,11 +11,17 @@ import System # isort: skip -def resolve(): +def get_version(): install_dir = os.environ["AWP_ROOT232"] # Change this back to AWP_ROOTDV_DEV + version = int(install_dir[-3:]) + + return install_dir, version + + +def resolve(): + install_dir, version = get_version() platform_string = "winx64" if os.name == "nt" else "linx64" sys.path.append(os.path.join(install_dir, "aisol", "bin", platform_string)) - version = int(install_dir[-3:]) clr.AddReference("Ansys.Mechanical.Embedding") import Ansys @@ -54,17 +60,31 @@ def is_type_published(mod_type: "System.RuntimeType"): def make(): + install_dir, version = get_version() # 232 + version = str(version) + version = version[:2] + "." + version[2:] + major_minor = version.split(".") + major = major_minor[0] + minor = major_minor[1] + outdir.mkdir(parents=True, exist_ok=True) + for assembly in ASSEMBLIES: gen.make(outdir, assembly, type_filter=is_type_published) with open(os.path.join(outdir, "Ansys", "__init__.py"), "w") as f: f.write( - '''try: + f'''try: import importlib.metadata as importlib_metadata except ModuleNotFoundError: # pragma: no cover import importlib_metadata # type: ignore -__version__ = importlib_metadata.version("ansys-mechanical-stubs") +patch = importlib_metadata.version("ansys-mechanical-stubs") + +# major, minor, patch +version_info = {major}, {minor}, patch + +# Format version +__version__ = ".".join(map(str, version_info)) """Mechanical Scripting version""" ''' ) diff --git a/package/doc/Makefile b/package/doc/Makefile index 1e465723b..5282ea88f 100644 --- a/package/doc/Makefile +++ b/package/doc/Makefile @@ -21,7 +21,6 @@ help: clean: rm -rf $(BUILDDIR)/* - rm -rf source/examples/gallery_examples find . -type d -name "_autosummary" -exec rm -rf {} + pdf: diff --git a/package/doc/source/conf.py b/package/doc/source/conf.py index 40cd4a359..16dfc4a76 100644 --- a/package/doc/source/conf.py +++ b/package/doc/source/conf.py @@ -3,9 +3,10 @@ # For the full list of built-in configuration values, see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html -from datetime import datetime import os +from datetime import datetime +from Ansys import __version__ from ansys_sphinx_theme import ( ansys_favicon, ansys_logo_white, @@ -17,31 +18,31 @@ ) from sphinx.builders.latex import LaTeXBuilder -from Ansys import __version__ - LaTeXBuilder.supported_image_types = ["image/png", "image/pdf", "image/svg+xml"] # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information # Project information -project = "ansys-mechanical-stubs" +project = "ansys-mechanical-scripting" copyright = f"(c) {datetime.now().year} ANSYS, Inc. All rights reserved" author = "ANSYS, Inc." -release = version = __version__ -cname = os.getenv("DOCUMENTATION_CNAME", default="scripting.mechanical.docs.pyansys.com") -switcher_version = get_version_match(__version__) +release = version = __version__ +cname = os.getenv( + "DOCUMENTATION_CNAME", default="scripting.mechanical.docs.pyansys.com" +) +switcher_version = get_version_match(__version__) # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration extensions = [ - 'sphinx.ext.intersphinx', - 'sphinx.ext.autodoc', - 'sphinx.ext.viewcode', - 'numpydoc', - 'sphinx.ext.autosummary', - 'sphinx.ext.inheritance_diagram', + "sphinx.ext.intersphinx", + "sphinx.ext.autodoc", + "sphinx.ext.viewcode", + "numpydoc", + "sphinx.ext.autosummary", + "sphinx.ext.inheritance_diagram", "sphinx_jinja", "autoapi.extension", ] @@ -58,7 +59,7 @@ "special-members", ] autoapi_template_dir = "_autoapi_templates" -suppress_warnings = ["autoapi.python_import_resolution"] +suppress_warnings = ["autoapi.python_import_resolution", "epub.duplicated_toc_entry"] exclude_patterns.append("_autoapi_templates/index.rst") autoapi_python_use_implicit_namespaces = True @@ -70,7 +71,7 @@ "imageio": ("https://imageio.readthedocs.io/en/stable", None), "pandas": ("https://pandas.pydata.org/pandas-docs/stable", None), "pytest": ("https://docs.pytest.org/en/stable", None), - } +} # Numpydoc config numpydoc_use_plots = True @@ -103,10 +104,10 @@ html_favicon = ansys_favicon # The suffix(es) of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" @@ -121,7 +122,7 @@ # Select desired logo, theme, and declare the html title html_logo = pyansys_logo_black html_theme = "ansys_sphinx_theme" -html_short_title = html_title = "Mechanical Stubs" +html_short_title = html_title = "Ansys Mechanical Scripting" # specify the location of your github repo html_context = { @@ -143,14 +144,10 @@ "use_edit_page_button": True, "additional_breadcrumbs": [ ("PyAnsys", "https://docs.pyansys.com/"), - ], - "icon_links": [ - { - "name": "Support", - "url": "https://github.com/ansys/mechanical-stubs/discussions", - "icon": "fa fa-comment fa-fw", - }, + ("PyMechanical", "https://mechanical.docs.pyansys.com/"), + ( + "Mechanical Scripting", + "https://mechanical.docs.pyansys.com/version/stable/user_guide_scripting/index.html", + ), ], } - - diff --git a/package/doc/source/index.rst b/package/doc/source/index.rst index 42f1945a4..bbd9c6c4f 100644 --- a/package/doc/source/index.rst +++ b/package/doc/source/index.rst @@ -4,9 +4,9 @@ contain the root `toctree` directive. Mechanical API Documentation -====================================== +============================ -This documentation lists the APIs used in Ansys Mechanical 2023R2. +This documentation lists the APIs used in Ansys Mechanical 2023R2. .. toctree:: :maxdepth: 2 diff --git a/package/pyproject.toml b/package/pyproject.toml index 1c7c0d376..5e413f23b 100644 --- a/package/pyproject.toml +++ b/package/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "flit_core.buildapi" [project] # Check https://flit.readthedocs.io/en/latest/pyproject_toml.html for all available sections name = "ansys-mechanical-stubs" -version = "0.1.dev0" +version = "0" description = "PyMechanical scripting API stubs." readme = "README.rst" requires-python = ">=3.9" From 997775076b10ae76bfff326e2f60827eadf65c60 Mon Sep 17 00:00:00 2001 From: Kerry McAdams Date: Fri, 25 Aug 2023 16:08:02 -0400 Subject: [PATCH 5/5] reverting suppress warning options --- package/doc/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/doc/source/conf.py b/package/doc/source/conf.py index 16dfc4a76..c6e6574be 100644 --- a/package/doc/source/conf.py +++ b/package/doc/source/conf.py @@ -59,7 +59,7 @@ "special-members", ] autoapi_template_dir = "_autoapi_templates" -suppress_warnings = ["autoapi.python_import_resolution", "epub.duplicated_toc_entry"] +suppress_warnings = ["autoapi.python_import_resolution"] exclude_patterns.append("_autoapi_templates/index.rst") autoapi_python_use_implicit_namespaces = True