diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..94e8d21 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,67 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '17 4 * * 6' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: [ 'python' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..04e35f9 --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,32 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries + +name: Upload Python Package + +on: + release: + types: [created] + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml new file mode 100644 index 0000000..0d22b6c --- /dev/null +++ b/.github/workflows/python-tests.yml @@ -0,0 +1,36 @@ +name: Run python tests + +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 + pip install pycodestyle isort pylint yapf + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Check pycodestyle + run: | + pycodestyle --ignore E501,E402 --exclude=.git,dev3 openvpn_status_parser tests + - name: Run pylint + run: | + pylint openvpn_status_parser tests + - name: Run tests + run: | + python3 setup.py test + - name: Check formatting + run: | + isort openvpn_status_parser tests; yapf --recursive -i . + git diff --exit-code # This fails if isort&yapf combo made any changes diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..f9eecf7 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,5 @@ +[format] +max-line-length=125 + +[messages control] +disable=line-too-long,missing-docstring,no-self-use,fixme,bad-indentation,bad-continuation,invalid-name,too-many-locals,duplicate-code,too-many-branches,wrong-import-order diff --git a/.style.yapf b/.style.yapf new file mode 100644 index 0000000..93d64ae --- /dev/null +++ b/.style.yapf @@ -0,0 +1,10 @@ +[style] +based_on_style = pep8 +allow_split_before_dict_value = false +blank_line_before_nested_class_or_def = false +coalesce_brackets = true +column_limit = 125 +dedent_closing_brackets = true +each_dict_entry_on_separate_line = true +join_multiple_lines = true +spaces_around_power_operator = true diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..cd5c224 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,22 @@ +MIT License: + +Copyright (C) 2012-2016, Olli Jarva + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.rst b/README.rst index 6737e1c..70f69cb 100644 --- a/README.rst +++ b/README.rst @@ -34,26 +34,7 @@ Or using Python: pprint.pprint(parser.routing_table) pprint.pprint(parser.details) +License +------- -MIT License: - -Copyright (C) 2012-2016, Olli Jarva \ - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +MIT License; see LICENSE.txt for full details. \ No newline at end of file diff --git a/openvpn_status_parser/__init__.py b/openvpn_status_parser/__init__.py index 17e0030..c56d94b 100644 --- a/openvpn_status_parser/__init__.py +++ b/openvpn_status_parser/__init__.py @@ -29,10 +29,11 @@ import datetime import logging import sys + from . import exceptions -class OpenVPNStatusParser(object): +class OpenVPNStatusParser: """ Usage: @@ -40,7 +41,6 @@ class OpenVPNStatusParser(object): parser = OpenVPNStatusParser(filename) pprint.pprint(parser.connected_clients) """ - def __init__(self, filename): self.filename = filename self._connected_clients = None @@ -59,34 +59,33 @@ def __init__(self, filename): def _process_title(self, row): try: self._details["title"] = row[1] - except IndexError: + except IndexError as err: logging.error("TITLE row is invalid: %s", row) - raise exceptions.MalformedFileException("TITLE row is invalid") + raise exceptions.MalformedFileException("TITLE row is invalid") from err def _process_time(self, row): try: self._details["timestamp"] = datetime.datetime.fromtimestamp(int(row[2])) - except (IndexError, ValueError): + except (IndexError, ValueError) as err: logging.error("TIME row is invalid: %s", row) - raise exceptions.MalformedFileException("TIME row is invalid") + raise exceptions.MalformedFileException("TIME row is invalid") from err def _process_header(self, row): try: self.topics_for[row[1]] = row[2:] - except IndexError: + except IndexError as err: logging.error("HEADER row is invalid: %s", row) - raise exceptions.MalformedFileException("HEADER row is invalid") + raise exceptions.MalformedFileException("HEADER row is invalid") from err def _process_client_list(self, row): try: self._connected_clients[row[1]] = dict(zip(self.topics_for["CLIENT_LIST"], row[1:])) - self._connected_clients[row[1]]["connected_since"] = ( - datetime.datetime.fromtimestamp(int(row[-1]))) - except IndexError: + self._connected_clients[row[1]]["connected_since"] = (datetime.datetime.fromtimestamp(int(row[-1]))) + except IndexError as err: logging.error("CLIENT_LIST row is invalid: %s", row) - raise exceptions.MalformedFileException("CLIENT_LIST row is invalid") - except KeyError: - raise exceptions.MalformedFileException("Topics for CLIENT_LIST are missing") + raise exceptions.MalformedFileException("CLIENT_LIST row is invalid") from err + except KeyError as err: + raise exceptions.MalformedFileException("Topics for CLIENT_LIST are missing") from err def _process_routing_table(self, row): if len(row[1:]) != len(self.topics_for.get("ROUTING_TABLE", [])): @@ -94,20 +93,20 @@ def _process_routing_table(self, row): try: self._routing_table[row[2]] = dict(zip(self.topics_for["ROUTING_TABLE"], row[1:])) self._routing_table[row[2]]["last_ref"] = datetime.datetime.fromtimestamp(int(row[-1])) - except IndexError: + except IndexError as err: logging.error("ROUTING_TABLE row is invalid: %s", row) - raise exceptions.MalformedFileException("ROUTING_TABLE row is invalid") - except ValueError: - raise exceptions.MalformedFileException("Invalid timestamp") - except KeyError: - raise exceptions.MalformedFileException("Topics for ROUTING_TABLE are missing") + raise exceptions.MalformedFileException("ROUTING_TABLE row is invalid") from err + except ValueError as err: + raise exceptions.MalformedFileException("Invalid timestamp") from err + except KeyError as err: + raise exceptions.MalformedFileException("Topics for ROUTING_TABLE are missing") from err def _process_global_stats(self, row): try: self._details[row[1]] = row[2] - except IndexError: + except IndexError as err: logging.error("GLOBAL_STATS row is invalid: %s", row) - raise exceptions.MalformedFileException("GLOBAL_STATS row is invalid") + raise exceptions.MalformedFileException("GLOBAL_STATS row is invalid") from err def _parse_file(self): self._details = {} @@ -122,9 +121,8 @@ def _parse_file(self): elif row_title == "END": return True else: - logging.warning("Line was not parsed. Keyword %s not recognized. %s", - row_title, row) - raise exceptions.MalformedFileException("Unhandled keyword %s", row_title) + logging.warning("Line was not parsed. Keyword %s not recognized. %s", row_title, row) + raise exceptions.MalformedFileException(f"Unhandled keyword {row_title}") logging.error("File was incomplete. END line was missing.") raise exceptions.MalformedFileException("END line was missing.") diff --git a/openvpn_status_parser/openvpn-status-parser b/openvpn_status_parser/openvpn-status-parser index 964c226..697ac0d 100644 --- a/openvpn_status_parser/openvpn-status-parser +++ b/openvpn_status_parser/openvpn-status-parser @@ -1,5 +1,4 @@ #!/usr/bin/env python - """OpenVPN status parser Usage: @@ -12,6 +11,7 @@ import pprint import sys import docopt + import openvpn_status_parser diff --git a/setup.py b/setup.py index 85ec264..7849cd9 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,8 @@ -from setuptools import setup, find_packages from codecs import open from os import path +from setuptools import find_packages, setup + here = path.abspath(path.dirname(__file__)) with open(path.join(here, 'README.rst'), encoding='utf-8') as f: @@ -15,21 +16,19 @@ url='https://github.com/ojarva/openvpn-status-parser', author='Olli Jarva', author_email='olli@jarva.fi', - license='BSD', - + license='MIT', classifiers=[ 'Development Status :: 4 - Beta', - 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', - - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: Implementation :: PyPy', ], keywords='openvpn', @@ -37,7 +36,6 @@ install_requires=["docopt>=0.6.2"], test_suite="tests", scripts=["openvpn_status_parser/openvpn-status-parser"], - extras_require={ 'dev': ['twine', 'wheel'], }, diff --git a/tests/__init__.py b/tests/__init__.py index 25fe5e4..7e52626 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,6 +1,6 @@ +import datetime import glob import unittest -import datetime import openvpn_status_parser import openvpn_status_parser.exceptions @@ -11,17 +11,19 @@ def _test_file(self, filename): parsed = openvpn_status_parser.OpenVPNStatusParser(filename) def _call_details(): - parsed.details + parsed.details # pylint: disable=pointless-statement self.assertRaises(openvpn_status_parser.exceptions.MalformedFileException, _call_details) def ch(filename): - return lambda self: self._test_file(filename) + return lambda self: self._test_file(filename) # pylint: disable=protected-access + -for filename in glob.glob("tests/testfiles/broken/*.status"): - print("Adding %s" % filename) - setattr(TestBroken, "test_%s" % filename.replace(".", "_"), ch(filename)) +for fn in glob.glob("tests/testfiles/broken/*.status"): + print(f"Adding {fn}") + test_func_name = fn.replace(".", "_") + setattr(TestBroken, f"test_{test_func_name}", ch(fn)) if __name__ == '__main__': unittest.main()