From ee9256077e78361ce6dccfa9b630ec95d0b7e153 Mon Sep 17 00:00:00 2001 From: Joe Stanley Date: Sun, 13 Dec 2020 14:07:52 -0800 Subject: [PATCH 01/17] initial commit of sphinx builder work --- .github/workflows/sphinx-build.yml | 53 +++++++++++++++++ source/_templates/base.rst | 5 ++ source/_templates/class.rst | 65 ++++++++++++++++++++ source/_templates/module.rst | 20 +++++++ source/channels.rst | 11 ++++ source/comtrade.rst | 16 +++++ source/conf.py | 96 ++++++++++++++++++++++++++++++ source/datreaders.rst | 18 ++++++ source/index.rst | 15 +++++ source/sphinxrequires.txt | 5 ++ 10 files changed, 304 insertions(+) create mode 100644 .github/workflows/sphinx-build.yml create mode 100644 source/_templates/base.rst create mode 100644 source/_templates/class.rst create mode 100644 source/_templates/module.rst create mode 100644 source/channels.rst create mode 100644 source/comtrade.rst create mode 100644 source/conf.py create mode 100644 source/datreaders.rst create mode 100644 source/index.rst create mode 100644 source/sphinxrequires.txt diff --git a/.github/workflows/sphinx-build.yml b/.github/workflows/sphinx-build.yml new file mode 100644 index 0000000..cd08d18 --- /dev/null +++ b/.github/workflows/sphinx-build.yml @@ -0,0 +1,53 @@ +# Syntax reference for this file: +# https://help.github.com/en/articles/workflow-syntax-for-github-actions + +name: Sphinx Documentation Builder +on: [push, pull_request] + +# https://gist.github.com/c-bata/ed5e7b7f8015502ee5092a3e77937c99 +jobs: + build-and-delpoy: + name: Build + runs-on: ubuntu-latest + steps: + # https://github.com/marketplace/actions/checkout + - uses: actions/checkout@v2 + # https://github.com/marketplace/actions/setup-python + # ^-- This gives info on matrix testing. + - name: Install Python + uses: actions/setup-python@v1 + with: + python-version: 3.8 + # I don't know where the "run" thing is documented. + - name: Install dependencies + run: | + #pip install -r requirements.txt + pip install -r source/sphinxrequires.txt + - name: Build Sphinx docs + if: success() + run: | + # Only uses the latest version of 'comtrade' available from PyPI + pip install comtrade + sphinx-build -M html source docs + + # https://github.com/marketplace/actions/github-pages + #- if: success() + # uses: crazy-max/ghaction-github-pages@master + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # with: + # target_branch: gh-pages + # build_dir: _build/html/ + + # https://github.com/peaceiris/actions-gh-pages + - name: Deploy + if: success() + uses: peaceiris/actions-gh-pages@v3 + with: + publish_branch: gh-pages + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: docs/html/ + + +# This action probably does everything for you: +# https://github.com/marketplace/actions/sphinx-build diff --git a/source/_templates/base.rst b/source/_templates/base.rst new file mode 100644 index 0000000..278387b --- /dev/null +++ b/source/_templates/base.rst @@ -0,0 +1,5 @@ +{{ fullname | escape | underline}} + + + +.. auto{{ objtype }}:: {{ objname }} \ No newline at end of file diff --git a/source/_templates/class.rst b/source/_templates/class.rst new file mode 100644 index 0000000..85105fa --- /dev/null +++ b/source/_templates/class.rst @@ -0,0 +1,65 @@ +{% if referencefile %} +.. include:: {{ referencefile }} +{% endif %} + +{{ objname }} +{{ underline }} + +.. currentmodule:: {{ module }} + +.. autoclass:: {{ objname }} + :show-inheritance: + + {% if '__init__' in methods %} + {% set caught_result = methods.remove('__init__') %} + {% endif %} + + {% block attributes_summary %} + {% if attributes %} + + .. rubric:: Attributes Summary + + .. autosummary:: + {% for item in attributes %} + ~{{ name }}.{{ item }} + {%- endfor %} + + {% endif %} + {% endblock %} + + {% block methods_summary %} + {% if methods %} + + .. rubric:: Methods Summary + + .. autosummary:: + {% for item in methods %} + ~{{ name }}.{{ item }} + {%- endfor %} + + {% endif %} + {% endblock %} + + {% block attributes_documentation %} + {% if attributes %} + + .. rubric:: Attributes Documentation + + {% for item in attributes %} + .. autoattribute:: {{ item }} + {%- endfor %} + + {% endif %} + {% endblock %} + + {% block methods_documentation %} + {% if methods %} + + .. rubric:: Methods Documentation + + {% for item in methods %} + .. automethod:: {{ item }} + {%- endfor %} + + {% endif %} + {% endblock %} diff --git a/source/_templates/module.rst b/source/_templates/module.rst new file mode 100644 index 0000000..d952d0b --- /dev/null +++ b/source/_templates/module.rst @@ -0,0 +1,20 @@ +{{ fullname | escape | underline }} + +.. rubric:: Description + +.. automodule:: {{ fullname }} + +.. currentmodule:: {{ fullname }} + + + +{% if functions %} +.. rubric:: Functions + +.. autosummary:: + :toctree: . + {% for function in functions %} + {{ function }} + {% endfor %} + +{% endif %} \ No newline at end of file diff --git a/source/channels.rst b/source/channels.rst new file mode 100644 index 0000000..771d0f9 --- /dev/null +++ b/source/channels.rst @@ -0,0 +1,11 @@ +Channels API Reference +====================== + +.. autoclass:: comtrade.Channel + :members: + +.. autoclass:: comtrade.StatusChannel + :members: + +.. autoclass:: comtrade.AnalogChannel + :members: diff --git a/source/comtrade.rst b/source/comtrade.rst new file mode 100644 index 0000000..6dbbd8d --- /dev/null +++ b/source/comtrade.rst @@ -0,0 +1,16 @@ +API Reference +============= + + +.. autoclass:: comtrade.Cfg + :members: + +.. autoclass:: comtrade.Comtrade + :members: + + +Additional Package Functions +---------------------------- + +.. autofunction:: comtrade.fill_with_zeros_to_the_right + diff --git a/source/conf.py b/source/conf.py new file mode 100644 index 0000000..9c38265 --- /dev/null +++ b/source/conf.py @@ -0,0 +1,96 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import re +# import sys + + +# Verify Import +try: + import comtrade +except: + print("Couldn't import `comtrade` module!") + sys.exit(9) + + +# -- Project information ----------------------------------------------------- + +project = 'python-comtrade' +copyright = '2018, David Rodrigues Parrini' +author = 'David Rodrigues Parrini' + + +# -- 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', + 'sphinx.ext.napoleon', + 'sphinx.ext.autosummary', + 'numpydoc', + 'sphinx_sitemap', +] +autosummary_generate = True +numpydoc_show_class_members = True + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'classic' +html_title = 'python-comtrade`' +# html_theme_options = { + # 'rightsidebar': 'false', + # 'stickysidebar': 'false', + # 'collapsiblesidebar': 'false', + # 'externalrefs': 'false', + # 'footerbgcolor': '#08385D', + # 'footertextcolor': '#ffffff', + # 'sidebarbgcolor': '#08385D', + # # 'sidebartextcolor': , + # 'relbarbgcolor': '#08385D', + # 'relbartextcolor': '#ffffff', + # # 'relbarlinkcolor': '#3432D8', + # 'bgcolor': '#ffffff', + # # 'textcolor': , + # 'linkcolor': '#3432D8', + # # 'visitedlinkcolor': , + # 'headbgcolor': '#C1C1C1', + # 'headtextcolor': '#08385D', + # 'headlinkcolor': '#3432D8', + # # 'codebgcolor': , + # # 'codetextcolor': , + # # 'bodyfont': , + # # 'headfont': , +# } + +# 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'] + + + + +# END \ No newline at end of file diff --git a/source/datreaders.rst b/source/datreaders.rst new file mode 100644 index 0000000..30d72bc --- /dev/null +++ b/source/datreaders.rst @@ -0,0 +1,18 @@ +DatReaders API Reference +======================== + +.. autoclass:: comtrade.DatReader + :members: + +.. autoclass:: comtrade.AsciiDatReader + :members: + +.. autoclass:: comtrade.BinaryDatReader + :members: + +.. autoclass:: comtrade.Binary32DatReader + :members: + +.. autoclass:: comtrade.Float32DatReader + :members: + diff --git a/source/index.rst b/source/index.rst new file mode 100644 index 0000000..2ca6990 --- /dev/null +++ b/source/index.rst @@ -0,0 +1,15 @@ +.. pycev documentation master file + + +python-comtrade: COMTRADE Reader for Python 3 +============================================= + +.. mdinclude:: ../README.md + + +Source Repository and Package Release (PyPI): +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- GitHub: https://github.com/dparrini/python-comtrade +- PyPI: https://pypi.org/project/comtrade/ + diff --git a/source/sphinxrequires.txt b/source/sphinxrequires.txt new file mode 100644 index 0000000..c324da0 --- /dev/null +++ b/source/sphinxrequires.txt @@ -0,0 +1,5 @@ +wheel +sphinx +pyserial +numpydoc +sphinx-sitemap \ No newline at end of file From 64b809eb3691138d11b813cb2e77d656b491f361 Mon Sep 17 00:00:00 2001 From: Joe Stanley Date: Sun, 13 Dec 2020 14:09:05 -0800 Subject: [PATCH 02/17] renamed folder to docsource --- .github/workflows/sphinx-build.yml | 2 +- {source => docsource}/_templates/base.rst | 0 {source => docsource}/_templates/class.rst | 0 {source => docsource}/_templates/module.rst | 0 {source => docsource}/channels.rst | 0 {source => docsource}/comtrade.rst | 0 {source => docsource}/conf.py | 0 {source => docsource}/datreaders.rst | 0 {source => docsource}/index.rst | 0 {source => docsource}/sphinxrequires.txt | 0 10 files changed, 1 insertion(+), 1 deletion(-) rename {source => docsource}/_templates/base.rst (100%) rename {source => docsource}/_templates/class.rst (100%) rename {source => docsource}/_templates/module.rst (100%) rename {source => docsource}/channels.rst (100%) rename {source => docsource}/comtrade.rst (100%) rename {source => docsource}/conf.py (100%) rename {source => docsource}/datreaders.rst (100%) rename {source => docsource}/index.rst (100%) rename {source => docsource}/sphinxrequires.txt (100%) diff --git a/.github/workflows/sphinx-build.yml b/.github/workflows/sphinx-build.yml index cd08d18..2addce9 100644 --- a/.github/workflows/sphinx-build.yml +++ b/.github/workflows/sphinx-build.yml @@ -28,7 +28,7 @@ jobs: run: | # Only uses the latest version of 'comtrade' available from PyPI pip install comtrade - sphinx-build -M html source docs + sphinx-build -M html docsource docs # https://github.com/marketplace/actions/github-pages #- if: success() diff --git a/source/_templates/base.rst b/docsource/_templates/base.rst similarity index 100% rename from source/_templates/base.rst rename to docsource/_templates/base.rst diff --git a/source/_templates/class.rst b/docsource/_templates/class.rst similarity index 100% rename from source/_templates/class.rst rename to docsource/_templates/class.rst diff --git a/source/_templates/module.rst b/docsource/_templates/module.rst similarity index 100% rename from source/_templates/module.rst rename to docsource/_templates/module.rst diff --git a/source/channels.rst b/docsource/channels.rst similarity index 100% rename from source/channels.rst rename to docsource/channels.rst diff --git a/source/comtrade.rst b/docsource/comtrade.rst similarity index 100% rename from source/comtrade.rst rename to docsource/comtrade.rst diff --git a/source/conf.py b/docsource/conf.py similarity index 100% rename from source/conf.py rename to docsource/conf.py diff --git a/source/datreaders.rst b/docsource/datreaders.rst similarity index 100% rename from source/datreaders.rst rename to docsource/datreaders.rst diff --git a/source/index.rst b/docsource/index.rst similarity index 100% rename from source/index.rst rename to docsource/index.rst diff --git a/source/sphinxrequires.txt b/docsource/sphinxrequires.txt similarity index 100% rename from source/sphinxrequires.txt rename to docsource/sphinxrequires.txt From 34fcf9fa456d391d6e8ba4065ababe10067b8db0 Mon Sep 17 00:00:00 2001 From: Joe Stanley Date: Sun, 13 Dec 2020 14:11:50 -0800 Subject: [PATCH 03/17] attempted fix to github action --- .github/workflows/sphinx-build.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/sphinx-build.yml b/.github/workflows/sphinx-build.yml index 2addce9..fb935e8 100644 --- a/.github/workflows/sphinx-build.yml +++ b/.github/workflows/sphinx-build.yml @@ -21,12 +21,11 @@ jobs: # I don't know where the "run" thing is documented. - name: Install dependencies run: | - #pip install -r requirements.txt pip install -r source/sphinxrequires.txt + # Only uses the latest version of 'comtrade' available from PyPI - name: Build Sphinx docs if: success() run: | - # Only uses the latest version of 'comtrade' available from PyPI pip install comtrade sphinx-build -M html docsource docs From 9178442bc454651f78868462eb521e464407ecb1 Mon Sep 17 00:00:00 2001 From: Joe Stanley Date: Sun, 13 Dec 2020 14:12:44 -0800 Subject: [PATCH 04/17] attempted fix to github action --- .github/workflows/sphinx-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sphinx-build.yml b/.github/workflows/sphinx-build.yml index fb935e8..f0de21b 100644 --- a/.github/workflows/sphinx-build.yml +++ b/.github/workflows/sphinx-build.yml @@ -21,7 +21,7 @@ jobs: # I don't know where the "run" thing is documented. - name: Install dependencies run: | - pip install -r source/sphinxrequires.txt + pip install -r docsource/sphinxrequires.txt # Only uses the latest version of 'comtrade' available from PyPI - name: Build Sphinx docs if: success() From 0982ff6a6cd596fe64c25443cd46f4383b9a82bb Mon Sep 17 00:00:00 2001 From: Joe Stanley Date: Sun, 13 Dec 2020 14:27:14 -0800 Subject: [PATCH 05/17] updated to fix md reader, added gitignore to avoid committing locally-built docs --- .gitignore | 2 ++ docsource/conf.py | 1 + docsource/index.rst | 5 +---- docsource/sphinxrequires.txt | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9be1311 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Sphinx documentation +docs/ diff --git a/docsource/conf.py b/docsource/conf.py index 9c38265..f161ad0 100644 --- a/docsource/conf.py +++ b/docsource/conf.py @@ -40,6 +40,7 @@ 'sphinx.ext.autosummary', 'numpydoc', 'sphinx_sitemap', + 'm2r2', ] autosummary_generate = True numpydoc_show_class_members = True diff --git a/docsource/index.rst b/docsource/index.rst index 2ca6990..0754970 100644 --- a/docsource/index.rst +++ b/docsource/index.rst @@ -1,14 +1,11 @@ .. pycev documentation master file -python-comtrade: COMTRADE Reader for Python 3 -============================================= - .. mdinclude:: ../README.md Source Repository and Package Release (PyPI): -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +============================================= - GitHub: https://github.com/dparrini/python-comtrade - PyPI: https://pypi.org/project/comtrade/ diff --git a/docsource/sphinxrequires.txt b/docsource/sphinxrequires.txt index c324da0..09f1f66 100644 --- a/docsource/sphinxrequires.txt +++ b/docsource/sphinxrequires.txt @@ -1,5 +1,5 @@ wheel sphinx -pyserial +m2r2 numpydoc sphinx-sitemap \ No newline at end of file From 7c4396deedcb96c55a6cf1bd41e37e52bf0ddccd Mon Sep 17 00:00:00 2001 From: Joe Stanley Date: Sun, 13 Dec 2020 14:30:14 -0800 Subject: [PATCH 06/17] created links to API references --- docsource/index.rst | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docsource/index.rst b/docsource/index.rst index 0754970..47f1f46 100644 --- a/docsource/index.rst +++ b/docsource/index.rst @@ -1,4 +1,15 @@ -.. pycev documentation master file +.. python-comtrade documentation master file + +API Documentation +================= + +.. toctree:: + :maxdepth: 1 + + comtrade + channels + datareaders + .. mdinclude:: ../README.md From c4c8123efdbb52ca7ebaadf59a0e5d2aac2e0724 Mon Sep 17 00:00:00 2001 From: Joe Stanley Date: Sun, 13 Dec 2020 14:32:29 -0800 Subject: [PATCH 07/17] fixed link to datreaders.rst --- docsource/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docsource/index.rst b/docsource/index.rst index 47f1f46..0145574 100644 --- a/docsource/index.rst +++ b/docsource/index.rst @@ -8,7 +8,7 @@ API Documentation comtrade channels - datareaders + datreaders From 5fbf0e69fae0e19f5b658b104279e033911edcaf Mon Sep 17 00:00:00 2001 From: Joe Stanley Date: Sun, 13 Dec 2020 14:35:56 -0800 Subject: [PATCH 08/17] cleaned up documentation to simplify API references --- docsource/channels.rst | 11 ----------- docsource/datreaders.rst | 18 ------------------ docsource/index.rst | 3 +-- docsource/support.rst | 39 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 31 deletions(-) delete mode 100644 docsource/channels.rst delete mode 100644 docsource/datreaders.rst create mode 100644 docsource/support.rst diff --git a/docsource/channels.rst b/docsource/channels.rst deleted file mode 100644 index 771d0f9..0000000 --- a/docsource/channels.rst +++ /dev/null @@ -1,11 +0,0 @@ -Channels API Reference -====================== - -.. autoclass:: comtrade.Channel - :members: - -.. autoclass:: comtrade.StatusChannel - :members: - -.. autoclass:: comtrade.AnalogChannel - :members: diff --git a/docsource/datreaders.rst b/docsource/datreaders.rst deleted file mode 100644 index 30d72bc..0000000 --- a/docsource/datreaders.rst +++ /dev/null @@ -1,18 +0,0 @@ -DatReaders API Reference -======================== - -.. autoclass:: comtrade.DatReader - :members: - -.. autoclass:: comtrade.AsciiDatReader - :members: - -.. autoclass:: comtrade.BinaryDatReader - :members: - -.. autoclass:: comtrade.Binary32DatReader - :members: - -.. autoclass:: comtrade.Float32DatReader - :members: - diff --git a/docsource/index.rst b/docsource/index.rst index 0145574..5e7a499 100644 --- a/docsource/index.rst +++ b/docsource/index.rst @@ -7,8 +7,7 @@ API Documentation :maxdepth: 1 comtrade - channels - datreaders + support diff --git a/docsource/support.rst b/docsource/support.rst new file mode 100644 index 0000000..d05c907 --- /dev/null +++ b/docsource/support.rst @@ -0,0 +1,39 @@ +Supporting Module Classes +========================= + +The following classes are used to support the primary classes in parsing +and describing COMTRADE files. + + + +Channels API Reference +====================== + +.. autoclass:: comtrade.Channel + :members: + +.. autoclass:: comtrade.StatusChannel + :members: + +.. autoclass:: comtrade.AnalogChannel + :members: + + +DatReaders API Reference +======================== + +.. autoclass:: comtrade.DatReader + :members: + +.. autoclass:: comtrade.AsciiDatReader + :members: + +.. autoclass:: comtrade.BinaryDatReader + :members: + +.. autoclass:: comtrade.Binary32DatReader + :members: + +.. autoclass:: comtrade.Float32DatReader + :members: + From 6ff47d8ee63bc29cde3f103e07557e1647bb6c49 Mon Sep 17 00:00:00 2001 From: Joe Stanley Date: Sun, 13 Dec 2020 14:38:06 -0800 Subject: [PATCH 09/17] removed unnecessary heading --- docsource/support.rst | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docsource/support.rst b/docsource/support.rst index d05c907..e3608c8 100644 --- a/docsource/support.rst +++ b/docsource/support.rst @@ -1,11 +1,3 @@ -Supporting Module Classes -========================= - -The following classes are used to support the primary classes in parsing -and describing COMTRADE files. - - - Channels API Reference ====================== From d28ae6db7ef4cd06a34dc22c2c131cbbe01dc81a Mon Sep 17 00:00:00 2001 From: Joe Stanley Date: Sun, 13 Dec 2020 14:42:02 -0800 Subject: [PATCH 10/17] refactored and cleaned api reference titles --- docsource/comtrade.rst | 11 +++++++---- docsource/index.rst | 4 ++-- docsource/support.rst | 8 ++++---- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/docsource/comtrade.rst b/docsource/comtrade.rst index 6dbbd8d..b7d1ffd 100644 --- a/docsource/comtrade.rst +++ b/docsource/comtrade.rst @@ -1,16 +1,19 @@ -API Reference -============= - +Cfg Class Reference +=================== .. autoclass:: comtrade.Cfg :members: + +Comtrade Class Reference +======================== + .. autoclass:: comtrade.Comtrade :members: Additional Package Functions ----------------------------- +============================ .. autofunction:: comtrade.fill_with_zeros_to_the_right diff --git a/docsource/index.rst b/docsource/index.rst index 5e7a499..1526cea 100644 --- a/docsource/index.rst +++ b/docsource/index.rst @@ -1,7 +1,7 @@ .. python-comtrade documentation master file -API Documentation -================= +Module Documentation +==================== .. toctree:: :maxdepth: 1 diff --git a/docsource/support.rst b/docsource/support.rst index e3608c8..2631484 100644 --- a/docsource/support.rst +++ b/docsource/support.rst @@ -1,5 +1,5 @@ -Channels API Reference -====================== +Channel Object References +========================= .. autoclass:: comtrade.Channel :members: @@ -11,8 +11,8 @@ Channels API Reference :members: -DatReaders API Reference -======================== +DatReader Object Reference +========================== .. autoclass:: comtrade.DatReader :members: From efbe4d2736abff33f90745f2379ac739032b3a2a Mon Sep 17 00:00:00 2001 From: Joe Stanley Date: Sun, 13 Dec 2020 14:49:14 -0800 Subject: [PATCH 11/17] fixed stray backtick --- docsource/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docsource/conf.py b/docsource/conf.py index f161ad0..d555f30 100644 --- a/docsource/conf.py +++ b/docsource/conf.py @@ -60,7 +60,7 @@ # a list of builtin themes. # html_theme = 'classic' -html_title = 'python-comtrade`' +html_title = 'python-comtrade' # html_theme_options = { # 'rightsidebar': 'false', # 'stickysidebar': 'false', From b70fb781971724c965b329ead8123a93608b18d5 Mon Sep 17 00:00:00 2001 From: engineerjoe440 Date: Thu, 22 Jul 2021 20:54:59 -0700 Subject: [PATCH 12/17] added initial unittest workflow --- .github/workflows/unittest.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/unittest.yml diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml new file mode 100644 index 0000000..1f856b8 --- /dev/null +++ b/.github/workflows/unittest.yml @@ -0,0 +1,27 @@ +name: Unit Test + +on: [push, pull_request] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.6, 3.7, 3.8] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + if [ -f test/testrequires.txt ]; then pip install -r test/testrequires.txt; fi + python3 -c "import comtrade; print('comtrade.__file__')" + - name: Test with Python unittest + run: | + python3 -m unittest tests/tests.py From 2c76019ed8dc1765ee183ac979a3a3f55ab72ac5 Mon Sep 17 00:00:00 2001 From: Joe Stanley <33275230+engineerjoe440@users.noreply.github.com> Date: Wed, 11 Jan 2023 20:59:51 -0800 Subject: [PATCH 13/17] use newer versions --- .github/workflows/unittest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml index 1f856b8..102c842 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unittest.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.6, 3.7, 3.8] + python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v2 From 6ea4edd9704f60676856eb6f1c399f436cc9cf13 Mon Sep 17 00:00:00 2001 From: Joe Stanley Date: Fri, 13 Jan 2023 21:58:43 -0800 Subject: [PATCH 14/17] cleanup of primary module --- .gitignore | 160 +++++++++++++++++++++++++++++++++++++++++- LICENSE.md => LICENSE | 0 README.md | 91 ++++++++++++++---------- comtrade.py | 149 +++++++++++++++++++++++---------------- docsource/conf.py | 20 +++--- pyproject.toml | 26 +++++++ setup.py | 3 + 7 files changed, 339 insertions(+), 110 deletions(-) rename LICENSE.md => LICENSE (100%) create mode 100644 pyproject.toml create mode 100644 setup.py diff --git a/.gitignore b/.gitignore index 9be1311..6769e21 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,160 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + # Sphinx documentation -docs/ +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ \ No newline at end of file diff --git a/LICENSE.md b/LICENSE similarity index 100% rename from LICENSE.md rename to LICENSE diff --git a/README.md b/README.md index 039779a..593591b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,12 @@ # Python Comtrade -__Python Comtrade__ is a module for Python 3 designed to read _Common Format for Transient Data Exchange_ (COMTRADE) files. These consists of oscillography data recorded during power system outages, control systems tests, validation and tests of field equipment, protective relaying logs, etc. The COMTRADE format is defined by IEEE Standards, summarized in the table below. Some equipment vendors put additional information in proprietary versions of it. This module aims IEEE definitions but may support those proprietary versions. - +A Python 3 module designed to read _Common Format for Transient Data Exchange_ +(COMTRADE) files. These consists of oscillography data recorded during power +system outages, control systems tests, validation and tests of field equipment, +protective relaying logs, etc. The COMTRADE format is defined by IEEE Standards, +summarized in the table below. Some equipment vendors put additional information +in proprietary versions of it. This module aims IEEE definitions but may support +those proprietary versions. | Standard | Revision | |:---------------------------------------|:--------:| @@ -9,27 +14,26 @@ __Python Comtrade__ is a module for Python 3 designed to read _Common Format for | IEEE C37.111(TM)-1999 | 1999 | | IEEE C37.111(TM)-2013 / IEC 60255-24 | 2013 | - ## Installation -``` -pip install comtrade +```shell +pip install python-comtrade ``` Or just copy `comtrade.py` from this repository. - ## How to Use -The examples below shows how to open both CFG and DAT files or the new CFF file to plot (using `pyplot`) analog channel oscillography. - - +The examples below shows how to open both CFG and DAT files or the new CFF file +to plot (using `pyplot`) analog channel oscillography. ### CFG and DAT files (all revisions) -Comtrade files separated in CFG and DAT formats can also be read with `Comtrade.load`. A `CFG` file path must be passed as an argument and, optionaly, a `DAT` file path too (if the file name is not equal of the CFG file). +Comtrade files separated in CFG and DAT formats can also be read with +`Comtrade.load`. A `CFG` file path must be passed as an argument and, optionaly, +a `DAT` file path too (if the file name is not equal of the CFG file). -```py +```python import matplotlib.pyplot as plt from comtrade import Comtrade @@ -44,13 +48,13 @@ plt.legend([rec.analog_channel_ids[0], rec.analog_channel_ids[1]]) plt.show() ``` -It will read the contents of additional header (`*.hdr`) and information (`*.inf`) files. -Their contents are available through `Comtrade.hdr` and `Comtrade.inf` properties. - +It will read the contents of additional header (`*.hdr`) and information +(`*.inf`) files. Their contents are available through `Comtrade.hdr` and +`Comtrade.inf` properties. ### CFF files (2013 revision) -```py +```python import matplotlib.pyplot as plt from comtrade import Comtrade @@ -65,20 +69,29 @@ plt.legend([rec.analog_channel_ids[0], rec.analog_channel_ids[1]]) plt.show() ``` -A `Comtrade` class must be instantiated and the method `load` called with the `CFF` file path. - -`Comtrade.analog` and `Comtrade.status` lists stores analog and status channel sample lists respectively. These can be accessed through zero-based indexes, i.e., `Comtrade.analog[0]`. The list `Comtrade.time` stores each sample time in seconds. +A `Comtrade` class must be instantiated and the method `load` called with the +`CFF` file path. -More information can be accessed through `Comtrade.cfg` object, which stores data such as detailed channel information. +`Comtrade.analog` and `Comtrade.status` lists stores analog and status channel +sample lists respectively. These can be accessed through zero-based indexes, +i.e., `Comtrade.analog[0]`. The list `Comtrade.time` stores each sample time in +seconds. -Data of additional sections, such as HDR and INF, can be accessed through `hdr` and `inf` properties, respectively. +More information can be accessed through `Comtrade.cfg` object, which stores +data such as detailed channel information. +Data of additional sections, such as HDR and INF, can be accessed through `hdr` +and `inf` properties, respectively. ## Features -This module implements some of the functionality described in each of the Standard revisions. The tables below lists some features and file formats and which revision supports it. It also shows whether this module support the feature or the format. +This module implements some of the functionality described in each of the +Standard revisions. The tables below lists some features and file formats and +which revision supports it. It also shows whether this module support the +feature or the format. -Feel free to pull requests implementing one of these unsupported features or fixing bugs. +Feel free to pull requests implementing one of these unsupported features or +fixing bugs. | Formats | 1991 | 1999 | 2013 | Module Support | |:------------------------------------------------------|:----:|:-----:|:----:|:---------------:| @@ -93,7 +106,6 @@ Feel free to pull requests implementing one of these unsupported features or fix | Float32 data file format | | | x | x | | Schema for phasor data | | | x | no | - | Features | 1991 | 1999 | 2013 | Module Support | |:------------------------------------------------------|:----:|:-----:|:----:|:---------------:| | COMTRADE standard revision | | x | x | x | @@ -106,7 +118,6 @@ Feel free to pull requests implementing one of these unsupported features or fix | Multiple sample rates | x | x | x | Partial | | Nanoseconds scale | | | x | x | - ### Unsupported features * Nanoseconds time base within Python's `datetime` objects (such as `start_timestamp` and `trigger_timestamp` properties). It warns the user but doesn't use it, truncating the numbers. @@ -114,13 +125,12 @@ Feel free to pull requests implementing one of these unsupported features or fix * Null fields in ASCII data (blank columns). * Missing data fields in binary data (`0xFFFF...`) are treated as any other value. - ### Additional settings -#### Numpy arrays as data structures +#### NumPy arrays as data structures -The use of `numpy.array` as a data structure to hold time, analog and status data can be enforced -in `Comtrade` object constructor: +The use of `numpy.array` as a data structure to hold time, analog and status +data can be enforced in `Comtrade` object constructor: ```python obj = Comtrade(use_numpy_arrays=True) @@ -128,40 +138,43 @@ obj = Comtrade(use_numpy_arrays=True) It may improve performance for computations after loading data. - #### File encodings -Specify the `encoding` as a keyword argument on all load methods as you'd specify for common file loading: +Specify the `encoding` as a keyword argument on all load methods as you'd +specify for common file loading: ```python rec = Comtrade() rec.load("sample_files/sample_ascii.cff", encoding="iso-8859-1") ``` - ## Documentation https://github.com/dparrini/python-comtrade ## Support -Feel free to report any bugs you find. You are welcome to fork and submit pull requests. +Feel free to report any bugs you find. You are welcome to fork and submit pull +requests. ## Development -To run tests, use Python's `unittest`. From a clone of the GitHub repository, run the command: +To run tests, use Python's `unittest`. From a clone of the GitHub repository, +run the command: -#### On Windows -``` +### On Windows + +```shell python -m unittest tests\tests.py ``` -#### On Linux: -``` +### On Linux + +```shell python3 -m unittest ./tests/tests.py ``` ## License -The module is available at [GitHub](https://github.com/dparrini/python-comtrade) under the MIT license. - +The module is available at [GitHub](https://github.com/dparrini/python-comtrade) +under the MIT license. diff --git a/comtrade.py b/comtrade.py index ace6134..6e86a37 100644 --- a/comtrade.py +++ b/comtrade.py @@ -39,6 +39,7 @@ except ModuleNotFoundError: HAS_NUMPY = False +__version__ = "0.0.11" # COMTRADE standard revisions REV_1991 = "1991" @@ -184,15 +185,18 @@ def _read_timestamp(timestamp_line: str, rev_year: str, ignore_warnings: bool = def _file_is_utf8(file_path): if os.path.exists(file_path): - with open(file_path, "r") as file: - return _stream_is_utf8(file) + try: + with open(file_path, "r", encoding="utf-8") as file: + return _stream_is_utf8(file) + except UnicodeDecodeError: + return False return False def _stream_is_utf8(stream): try: - contents = stream.readlines() - except UnicodeDecodeError as exception: + _ = stream.readlines() + except UnicodeDecodeError: return True return False @@ -208,10 +212,11 @@ def __init__(self, **kwargs): Cfg object constructor. Keyword arguments: - ignore_warnings -- whether warnings are displayed in stdout + ignore_warnings -- whether warnings are displayed in stdout (default: False) """ self.filename = "" + self.filepath = None # implicit data self._time_base = self.TIME_BASE_MICROSEC @@ -249,7 +254,7 @@ def __init__(self, **kwargs): def station_name(self) -> str: """Return the recording device's station name.""" return self._station_name - + @property def rec_dev_id(self) -> str: """Return the recording device id.""" @@ -274,17 +279,17 @@ def analog_channels(self) -> list: def status_channels(self) -> list: """Return the status channels list with complete channel description.""" return self._status_channels - + @property def analog_count(self) -> int: """Return the number of analog channels.""" return self._analog_count - + @property def status_count(self) -> int: """Return the number of status channels.""" return self._status_count - + @property def time_base(self) -> float: """Return the time base.""" @@ -294,12 +299,12 @@ def time_base(self) -> float: def frequency(self) -> float: """Return the measured line frequency in Hertz.""" return self._frequency - + @property def ft(self) -> str: """Return the expected DAT file format.""" return self._ft - + @property def timemult(self) -> float: """Return the DAT time multiplier (Default = 1).""" @@ -310,34 +315,34 @@ def timestamp_critical(self) -> bool: """Returns whether the DAT file must contain non-zero timestamp values.""" return self._timestamp_critical - + @property def start_timestamp(self) -> dt.datetime: """Return the recording start time stamp as a datetime object.""" return self._start_timestamp - + @property def trigger_timestamp(self) -> dt.datetime: """Return the trigger time stamp as a datetime object.""" return self._trigger_timestamp - + @property def nrates(self) -> int: """Return the number of different sample rates within the DAT file.""" return self._nrates - + @property def sample_rates(self) -> list: """ Return a list with pairs describing the number of samples for a given sample rate. - """ + """ return self._sample_rates # Deprecated properties - Changed "Digital" for "Status" @property def digital_channels(self) -> list: - """Returns the status channels bidimensional values list.""" + """Returns the status channels bi-dimensional values list.""" if not self.ignore_warnings: warnings.warn(FutureWarning("digital_channels is deprecated, " "use status_channels instead.")) @@ -364,8 +369,11 @@ def load(self, filepath, **user_kwargs): with open(self.filepath, "r", **kwargs) as cfg: self._read_io(cfg) else: - raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), - self.filepath) + raise FileNotFoundError( + errno.ENOENT, + os.strerror(errno.ENOENT), + self.filepath + ) def read(self, cfg_lines): """Read CFG-format data of a FileIO or StringIO object.""" @@ -460,7 +468,7 @@ def _read_io(self, cfg): self._timestamp_critical = False line_count = line_count + 1 - for inrate in range(self._nrates): + for _ in range(self._nrates): line = cfg.readline() # each sample rate samp, endsamp = _read_sep_values(line) @@ -520,7 +528,7 @@ def _read_io(self, cfg): def _get_time_base(self, using_nanoseconds: bool): """ - Return the time base, which is based on the fractionary part of the + Return the time base, which is based on the fractionary part of the seconds in a timestamp (00.XXXXX). """ if using_nanoseconds: @@ -538,7 +546,7 @@ class Comtrade: EXT_HDR = "hdr" # format specific ASCII_SEPARATOR = "," - + def __init__(self, **kwargs): """ Comtrade object constructor. @@ -557,6 +565,7 @@ def __init__(self, **kwargs): self._status_channel_ids = [] self._status_phases = [] self._timestamp_critical = False + self._total_samples = 0 # Data types if "use_numpy_arrays" in kwargs: @@ -602,7 +611,7 @@ def cfg(self) -> Cfg: def hdr(self): """Return the HDR file contents.""" return self._hdr - + @property def inf(self): """Return the INF file contents.""" @@ -612,17 +621,17 @@ def inf(self): def analog_channel_ids(self) -> list: """Returns the analog channels name list.""" return self._analog_channel_ids - + @property def analog_phases(self) -> list: """Returns the analog phase name list.""" return self._analog_phases - + @property def status_channel_ids(self) -> list: """Returns the status channels name list.""" return self._status_channel_ids - + @property def status_phases(self) -> list: """Returns the status phase name list.""" @@ -635,12 +644,12 @@ def time(self) -> list: @property def analog(self) -> list: - """Return the analog channel values bidimensional list.""" + """Return the analog channel values bi-dimensional list.""" return self._analog_values - + @property def status(self) -> list: - """Return the status channel values bidimensional list.""" + """Return the status channel values bi-dimensional list.""" return self._status_values @property @@ -702,21 +711,32 @@ def ft(self) -> str: def digital_channel_ids(self) -> list: """Returns the status channels name list.""" if not self.ignore_warnings: - warnings.warn(FutureWarning("digital_channel_ids is deprecated, use status_channel_ids instead.")) + warnings.warn( + FutureWarning( + "digital_channel_ids is deprecated, use status_channel_ids " + "instead." + ) + ) return self._status_channel_ids @property def digital(self) -> list: - """Returns the status channels bidimensional values list.""" + """Returns the status channels bi-dimensional values list.""" if not self.ignore_warnings: - warnings.warn(FutureWarning("digital is deprecated, use status instead.")) + warnings.warn( + FutureWarning("digital is deprecated, use status instead.") + ) return self._status_values @property def digital_count(self) -> int: """Returns the number of status channels.""" if not self.ignore_warnings: - warnings.warn(FutureWarning("digital_count is deprecated, use status_count instead.")) + warnings.warn( + FutureWarning( + "digital_count is deprecated, use status_count instead." + ) + ) return self._cfg.status_count def _get_dat_reader(self): @@ -734,7 +754,9 @@ def _get_dat_reader(self): dat = Float32DatReader(**dat_kwargs) else: dat = None - raise Exception("Not supported data file format: {}".format(self.ft)) + raise Exception( + f"Not supported data file format: {self.ft}" + ) return dat def read(self, cfg_lines, dat_lines_or_bytes) -> None: @@ -745,7 +767,7 @@ def read(self, cfg_lines, dat_lines_or_bytes) -> None: # channel ids self._cfg_extract_channels_ids(self._cfg) - + # channel phases self._cfg_extract_phases(self._cfg) @@ -758,7 +780,7 @@ def read(self, cfg_lines, dat_lines_or_bytes) -> None: def _cfg_extract_channels_ids(self, cfg) -> None: self._analog_channel_ids = [channel.name for channel in cfg.analog_channels] self._status_channel_ids = [channel.name for channel in cfg.status_channels] - + def _cfg_extract_phases(self, cfg) -> None: self._analog_phases = [channel.ph for channel in cfg.analog_channels] self._status_phases = [channel.ph for channel in cfg.status_channels] @@ -775,7 +797,7 @@ def load(self, cfg_file, dat_file = None, **kwargs) -> None: object. dat_file, inf_file, and hdr_file are optional (Default: None). cfg_file is the cfg file path, including its extension. - dat_file is optional, and may be set if the DAT file name differs from + dat_file is optional, and may be set if the DAT file name differs from the CFG file name. Keyword arguments: @@ -820,14 +842,16 @@ def load(self, cfg_file, dat_file = None, **kwargs) -> None: # check if the CFF file exists self._load_cff(cfg_file) else: - raise Exception(r"Expected CFG file path, got intead \"{}\".".format(cfg_file)) + raise FileNotFoundError( + r"Expected CFG file path, got instead \"{}\".".format(cfg_file) + ) def _load_cfg_dat(self, cfg_filepath, dat_filepath, **kwargs): self._cfg.load(cfg_filepath, **kwargs) # channel ids self._cfg_extract_channels_ids(self._cfg) - + # channel phases self._cfg_extract_phases(self._cfg) @@ -866,13 +890,13 @@ def _load_cff(self, cff_file_path: str, **kwargs): hdr_lines = [] inf_lines = [] # file type: CFG, HDR, INF, DAT - ftype = None + file_type = None # file format: ASCII, BINARY, BINARY32, FLOAT32 - fformat = None + file_format = None if "encoding" not in kwargs and _file_is_utf8(cff_file_path): kwargs["encoding"] = "utf-8" # Number of bytes for binary/float dat - fbytes = 0 + file_bytes = 0 with open(cff_file_path, "r", **kwargs) as file: header_re = re.compile(CFF_HEADER_REXP) last_match = None @@ -884,39 +908,42 @@ def _load_cff(self, cff_file_path: str, **kwargs): if mobj is not None: last_match = mobj groups = last_match.groups() - ftype = groups[0] + file_type = groups[0] if len(groups) > 1: - fformat = last_match.groups()[1] + file_format = last_match.groups()[1] fbytes_obj = last_match.groups()[2] - fbytes = int(fbytes_obj) if fbytes_obj is not None else 0 + if fbytes_obj is not None: + file_bytes = int(fbytes_obj) + else: + file_bytes = 0 - elif last_match is not None and ftype == "CFG": + elif last_match is not None and file_type == "CFG": cfg_lines.append(line.strip()) - elif last_match is not None and ftype == "DAT": - if fformat == TYPE_ASCII: + elif last_match is not None and file_type == "DAT": + if file_format == TYPE_ASCII: dat_lines.append(line.strip()) else: break - elif last_match is not None and ftype == "HDR": + elif last_match is not None and file_type == "HDR": hdr_lines.append(line.strip()) - elif last_match is not None and ftype == "INF": + elif last_match is not None and file_type == "INF": inf_lines.append(line.strip()) line = file.readline() - if fformat == TYPE_ASCII: + if file_format == TYPE_ASCII: # process ASCII CFF data self.read("\n".join(cfg_lines), "\n".join(dat_lines)) else: # read dat bytes total_bytes = os.path.getsize(cff_file_path) - cff_bytes_read = total_bytes - fbytes + cff_bytes_read = total_bytes - file_bytes with open(cff_file_path, "rb") as file: file.read(cff_bytes_read) - dat_bytes = file.read(fbytes) + dat_bytes = file.read(file_bytes) self.read("\n".join(cfg_lines), dat_bytes) # stores additional data @@ -935,9 +962,14 @@ def cfg_summary(self): interval_line = "From {} to {} with time mult. = {}" format_line = "{} format" - lines = [header_line.format(self.analog_count, self.status_count, - self.channels_count), - "Line frequency: {} Hz".format(self.frequency)] + lines = [ + header_line.format( + self.analog_count, + self.status_count, + self.channels_count + ), + f"Line frequency: {self.frequency} Hz" + ] for i in range(self._cfg.nrates): rate, points = self._cfg.sample_rates[i] lines.append(sample_line.format(rate, points)) @@ -975,6 +1007,7 @@ def __init__(self, n: int, name='', ph='', ccbm='', y=0): def __str__(self): fields = [str(self.n), self.name, self.ph, self.ccbm, str(self.y)] + return ','.join(fields) class AnalogChannel(Channel): @@ -1183,8 +1216,7 @@ def __init__(self, **kwargs): self.STRUCT_FORMAT_STATUS_ONLY = "II {dcount:d}H" def get_reader_format(self, analog_channels, status_bytes): - # Number of status fields of 2 bytes based on the total number of - # bytes. + # Number of status fields of 2 bytes based on the total number of bytes. dcount = math.floor(status_bytes / 2) # Check the file configuration @@ -1219,7 +1251,6 @@ def parse(self, contents): row_reader = struct.Struct(self.get_reader_format(achannels, dbytes)) # Row reading function. - next_row = None if isinstance(contents, io.TextIOBase) or \ isinstance(contents, io.BufferedIOBase): # Read all buffer contents diff --git a/docsource/conf.py b/docsource/conf.py index d555f30..54a43a9 100644 --- a/docsource/conf.py +++ b/docsource/conf.py @@ -18,7 +18,7 @@ # Verify Import try: import comtrade -except: +except ImportError: print("Couldn't import `comtrade` module!") sys.exit(9) @@ -35,12 +35,13 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = [ 'sphinx.ext.autodoc', - 'sphinx.ext.napoleon', - 'sphinx.ext.autosummary', - 'numpydoc', - 'sphinx_sitemap', - 'm2r2', +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.napoleon', + 'sphinx.ext.autosummary', + 'numpydoc', + 'sphinx_sitemap', + 'm2r2', ] autosummary_generate = True numpydoc_show_class_members = True @@ -91,7 +92,4 @@ # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['static'] - - - -# END \ No newline at end of file +# END diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..fed2a92 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,26 @@ +[build-system] +requires = ["flit_core >=3.2,<4"] +build-backend = "flit_core.buildapi" + +[project] +name = "python-comtrade" +authors = [ + {name = "David Parrini"}, + {name = "Joe Stanley", email = "engineerjoe440@yahoo.com"} +] +maintainers = [ + {name = "Joe Stanley", email = "engineerjoe440@yahoo.com"} +] +description = "A Python 3 module designed to read Common Format for Transient Data Exchange (COMTRADE) files." +readme = "README.md" +license = {file = "LICENSE"} +classifiers = [ + "License :: OSI Approved :: MIT License" +] +dynamic = ["version"] + +[project.urls] +Home = "https://github.com/engineerjoe440/python-comtrade" + +[tool.flit.module] +name = "comtrade" diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..fc1f76c --- /dev/null +++ b/setup.py @@ -0,0 +1,3 @@ +from setuptools import setup + +setup() \ No newline at end of file From d1ffb433079ddc9ca847fc0f4d9cd0f49850b577 Mon Sep 17 00:00:00 2001 From: Joe Stanley Date: Fri, 13 Jan 2023 22:06:30 -0800 Subject: [PATCH 15/17] cleanup of primary module --- docsource/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docsource/conf.py b/docsource/conf.py index 54a43a9..81e3e81 100644 --- a/docsource/conf.py +++ b/docsource/conf.py @@ -12,7 +12,7 @@ # # import os # import re -# import sys +import sys # Verify Import From 6539aa8d82df9a72794030d7e8e17d6c6296b0ac Mon Sep 17 00:00:00 2001 From: Joe Stanley <33275230+engineerjoe440@users.noreply.github.com> Date: Mon, 16 Jan 2023 10:52:45 -0800 Subject: [PATCH 16/17] revert pyproject change --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fed2a92..cddea90 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ authors = [ {name = "Joe Stanley", email = "engineerjoe440@yahoo.com"} ] maintainers = [ - {name = "Joe Stanley", email = "engineerjoe440@yahoo.com"} + {name = "David Parrini"}, ] description = "A Python 3 module designed to read Common Format for Transient Data Exchange (COMTRADE) files." readme = "README.md" From 03cf762a7b11c0be4d8d89be60378aefe5f1b9e7 Mon Sep 17 00:00:00 2001 From: Joe Stanley Date: Wed, 25 Jan 2023 11:34:26 -0800 Subject: [PATCH 17/17] added support to handle CFG header of unexpected length (use warning) --- comtrade.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/comtrade.py b/comtrade.py index 6e86a37..9bf7ce1 100644 --- a/comtrade.py +++ b/comtrade.py @@ -394,7 +394,8 @@ def _read_io(self, cfg): line = cfg.readline() # station, device, and comtrade standard revision information packed = _read_sep_values(line) - if 3 == len(packed): + pack_len = len(packed) + if 3 == pack_len: # only 1999 revision and above has the standard revision year self._station_name, self._rec_dev_id, self._rev_year = packed self._rev_year = self._rev_year.strip() @@ -403,9 +404,21 @@ def _read_io(self, cfg): if not self.ignore_warnings: msg = WARNING_UNKNOWN_REVISION.format(self._rev_year) warnings.warn(Warning(msg)) - else: + elif pack_len == 2: self._station_name, self._rec_dev_id = packed self._rev_year = REV_1991 + else: + warnings.warn( + ( + f"COMTRADE CFG file presents a heading with an {pack_len} " + "fields, more than the expected 3. This may be indication " + "of an invalid COMTRADE file format." + ), + UserWarning + ) + self._station_name = packed[0] + self._rev_year = packed[-1] + self._rec_dev_id = ",".join(packed[1:-1]) line_count = line_count + 1 # Second line