diff --git a/.github/workflows/daemonpy_package_checker.yml b/.github/workflows/daemonpy_package_checker.yml index 4b1e06b..28c8811 100755 --- a/.github/workflows/daemonpy_package_checker.yml +++ b/.github/workflows/daemonpy_package_checker.yml @@ -17,11 +17,11 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.10'] + python-version: ['3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v4 - name: set up python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: install dependencies diff --git a/.github/workflows/daemonpy_python3_build.yml b/.github/workflows/daemonpy_python3_build.yml index 6983226..92c9ee0 100755 --- a/.github/workflows/daemonpy_python3_build.yml +++ b/.github/workflows/daemonpy_python3_build.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up python3 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.10' - name: Install dependencies diff --git a/README.md b/README.md index fff91f0..3ae4226 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ other information that should be provided before the modules are installed. - [Usage](#usage) - [Dependencies](#dependencies) - [Package structure](#package-structure) +- [Code coverage](#code-coverage) - [Docs](#docs) - [Copyright and Licence](#copyright-and-licence) @@ -99,7 +100,7 @@ Create short example Module mydaemon.py Copyright - Copyright (C) 2020 - 2024 Vladimir Roncevic + Copyright (C) 2020 - 2025 Vladimir Roncevic mydaemon is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -174,6 +175,17 @@ Package structure 1 directory, 6 files ``` +### Code coverage + +| Name | Stmts | Miss | Cover | +|------|-------|------|-------| +| `daemonpy/__init__.py` | 135 | 79 | 41%| +| `daemonpy/daemon_usage.py` | 45 | 2 | 96%| +| `daemonpy/file_descriptor.py` | 47 | 4 | 91%| +| `daemonpy/file_process_id.py` | 39 | 0 | 100%| +| `daemonpy/unix_operations.py` | 72 | 35 | 51%| +| **Total** | 338 | 120 | 64% | + ### Docs [![Documentation Status](https://readthedocs.org/projects/daemonpy/badge/?version=latest)](https://daemonpy.readthedocs.io/en/latest/?badge=latest) diff --git a/daemonpy/__init__.py b/daemonpy/__init__.py index 38ca626..a0b4097 100644 --- a/daemonpy/__init__.py +++ b/daemonpy/__init__.py @@ -4,7 +4,7 @@ Module __init__.py Copyright - Copyright (C) 2020 - 2024 Vladimir Roncevic + Copyright (C) 2020 - 2025 Vladimir Roncevic daemonpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -37,18 +37,18 @@ from daemonpy.file_process_id import FileProcessId from daemonpy.file_descriptor import FileDescriptor from daemonpy.unix_operations import UnixOperations -except ImportError as ats_error_message: - # Force close python ATS ################################################## - sys.exit(f'\n{__file__}\n{ats_error_message}\n') +except ImportError as ats_error_message: # pragma: no cover + # Force exit python ####################################################### + sys.exit(f'\n{__file__}\n{ats_error_message}\n') # pragma: no cover -__author__ = 'Vladimir Roncevic' -__copyright__ = '(C) 2024, https://vroncevic.github.io/daemonpy' +__author__: str = 'Vladimir Roncevic' +__copyright__: str = '(C) 2025, https://vroncevic.github.io/daemonpy' __credits__: List[str] = ['Vladimir Roncevic', 'Python Software Foundation'] -__license__ = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' -__version__ = '2.0.5' -__maintainer__ = 'Vladimir Roncevic' -__email__ = 'elektron.ronca@gmail.com' -__status__ = 'Updated' +__license__: str = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' +__version__: str = '2.0.6' +__maintainer__: str = 'Vladimir Roncevic' +__email__: str = 'elektron.ronca@gmail.com' +__status__: str = 'Updated' class Daemon(UnixOperations): diff --git a/daemonpy/daemon_usage.py b/daemonpy/daemon_usage.py index 3d6ffa2..a7422f6 100644 --- a/daemonpy/daemon_usage.py +++ b/daemonpy/daemon_usage.py @@ -4,7 +4,7 @@ Module daemon_usage.py Copyright - Copyright (C) 2020 - 2024 Vladimir Roncevic + Copyright (C) 2020 - 2025 Vladimir Roncevic daemonpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -29,18 +29,18 @@ from ats_utilities.console_io.verbose import verbose_message from ats_utilities.exceptions.ats_type_error import ATSTypeError from ats_utilities.exceptions.ats_value_error import ATSValueError -except ImportError as ats_error_message: - # Force close python ATS ################################################## - sys.exit(f'\n{__file__}\n{ats_error_message}\n') +except ImportError as ats_error_message: # pragma: no cover + # Force exit python ####################################################### + sys.exit(f'\n{__file__}\n{ats_error_message}\n') # pragma: no cover -__author__ = 'Vladimir Roncevic' -__copyright__ = '(C) 2024, https://vroncevic.github.io/daemonpy' +__author__: str = 'Vladimir Roncevic' +__copyright__: str = '(C) 2025, https://vroncevic.github.io/daemonpy' __credits__: List[str] = ['Vladimir Roncevic', 'Python Software Foundation'] -__license__ = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' -__version__ = '2.0.5' -__maintainer__ = 'Vladimir Roncevic' -__email__ = 'elektron.ronca@gmail.com' -__status__ = 'Updated' +__license__: str = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' +__version__: str = '2.0.6' +__maintainer__: str = 'Vladimir Roncevic' +__email__: str = 'elektron.ronca@gmail.com' +__status__: str = 'Updated' class DaemonUsage: diff --git a/daemonpy/file_descriptor.py b/daemonpy/file_descriptor.py index f2a1c92..f2e0fd5 100644 --- a/daemonpy/file_descriptor.py +++ b/daemonpy/file_descriptor.py @@ -4,7 +4,7 @@ Module file_descriptor.py Copyright - Copyright (C) 2020 - 2024 Vladimir Roncevic + Copyright (C) 2020 - 2025 Vladimir Roncevic daemonpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -27,18 +27,18 @@ from ats_utilities.checker import ATSChecker from ats_utilities.exceptions.ats_type_error import ATSTypeError from ats_utilities.exceptions.ats_value_error import ATSValueError -except ImportError as ats_error_message: - # Force close python ATS ################################################## - sys.exit(f'\n{__file__}\n{ats_error_message}\n') +except ImportError as ats_error_message: # pragma: no cover + # Force exit python ####################################################### + sys.exit(f'\n{__file__}\n{ats_error_message}\n') # pragma: no cover -__author__ = 'Vladimir Roncevic' -__copyright__ = '(C) 2024, https://vroncevic.github.io/daemonpy' +__author__: str = 'Vladimir Roncevic' +__copyright__: str = '(C) 2025, https://vroncevic.github.io/daemonpy' __credits__: List[str] = ['Vladimir Roncevic', 'Python Software Foundation'] -__license__ = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' -__version__ = '2.0.3' -__maintainer__ = 'Vladimir Roncevic' -__email__ = 'elektron.ronca@gmail.com' -__status__ = 'Updated' +__license__: str = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' +__version__: str = '2.0.3' +__maintainer__: str = 'Vladimir Roncevic' +__email__: str = 'elektron.ronca@gmail.com' +__status__: str = 'Updated' class FileDescriptor: diff --git a/daemonpy/file_process_id.py b/daemonpy/file_process_id.py index d48ab25..c2281b0 100644 --- a/daemonpy/file_process_id.py +++ b/daemonpy/file_process_id.py @@ -4,7 +4,7 @@ Module file_process_id.py Copyright - Copyright (C) 2020 - 2024 Vladimir Roncevic + Copyright (C) 2020 - 2025 Vladimir Roncevic daemonpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -27,18 +27,18 @@ from ats_utilities.checker import ATSChecker from ats_utilities.exceptions.ats_type_error import ATSTypeError from ats_utilities.exceptions.ats_value_error import ATSValueError -except ImportError as ats_error_message: - # Force close python ATS ################################################## - sys.exit(f'\n{__file__}\n{ats_error_message}\n') +except ImportError as ats_error_message: # pragma: no cover + # Force exit python ####################################################### + sys.exit(f'\n{__file__}\n{ats_error_message}\n') # pragma: no cover -__author__ = 'Vladimir Roncevic' -__copyright__ = '(C) 2024, https://vroncevic.github.io/daemonpy' +__author__: str = 'Vladimir Roncevic' +__copyright__: str = '(C) 2025, https://vroncevic.github.io/daemonpy' __credits__: List[str] = ['Vladimir Roncevic', 'Python Software Foundation'] -__license__ = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' -__version__ = '2.0.5' -__maintainer__ = 'Vladimir Roncevic' -__email__ = 'elektron.ronca@gmail.com' -__status__ = 'Updated' +__license__: str = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' +__version__: str = '2.0.6' +__maintainer__: str = 'Vladimir Roncevic' +__email__: str = 'elektron.ronca@gmail.com' +__status__: str = 'Updated' class FileProcessId: diff --git a/daemonpy/unix_operations.py b/daemonpy/unix_operations.py index 5d859bc..ebf5efe 100644 --- a/daemonpy/unix_operations.py +++ b/daemonpy/unix_operations.py @@ -4,7 +4,7 @@ Module unix_operations.py Copyright - Copyright (C) 2020 - 2024 Vladimir Roncevic + Copyright (C) 2020 - 2025 Vladimir Roncevic daemonpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -33,18 +33,18 @@ from ats_utilities.console_io.verbose import verbose_message from ats_utilities.exceptions.ats_type_error import ATSTypeError from ats_utilities.exceptions.ats_value_error import ATSValueError -except ImportError as ats_error_message: - # Force close python ATS ################################################## - sys.exit(f'\n{__file__}\n{ats_error_message}\n') +except ImportError as ats_error_message: # pragma: no cover + # Force exit python ####################################################### + sys.exit(f'\n{__file__}\n{ats_error_message}\n') # pragma: no cover -__author__ = 'Vladimir Roncevic' -__copyright__ = '(C) 2024, https://vroncevic.github.io/daemonpy' +__author__: str = 'Vladimir Roncevic' +__copyright__: str = '(C) 2025, https://vroncevic.github.io/daemonpy' __credits__: List[str] = ['Vladimir Roncevic', 'Python Software Foundation'] -__license__ = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' -__version__ = '2.0.5' -__maintainer__ = 'Vladimir Roncevic' -__email__ = 'elektron.ronca@gmail.com' -__status__ = 'Updated' +__license__: str = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' +__version__: str = '2.0.6' +__maintainer__: str = 'Vladimir Roncevic' +__email__: str = 'elektron.ronca@gmail.com' +__status__: str = 'Updated' class UnixOperations: diff --git a/docs/build/doctrees/daemonpy.daemon_usage.doctree b/docs/build/doctrees/daemonpy.daemon_usage.doctree index 6602be5..5802955 100644 Binary files a/docs/build/doctrees/daemonpy.daemon_usage.doctree and b/docs/build/doctrees/daemonpy.daemon_usage.doctree differ diff --git a/docs/build/doctrees/daemonpy.doctree b/docs/build/doctrees/daemonpy.doctree index 7dcd75c..2bdf426 100644 Binary files a/docs/build/doctrees/daemonpy.doctree and b/docs/build/doctrees/daemonpy.doctree differ diff --git a/docs/build/doctrees/daemonpy.file_descriptor.doctree b/docs/build/doctrees/daemonpy.file_descriptor.doctree index c8ba5ae..b28a3a9 100644 Binary files a/docs/build/doctrees/daemonpy.file_descriptor.doctree and b/docs/build/doctrees/daemonpy.file_descriptor.doctree differ diff --git a/docs/build/doctrees/daemonpy.file_process_id.doctree b/docs/build/doctrees/daemonpy.file_process_id.doctree index 429c72b..515e0a6 100644 Binary files a/docs/build/doctrees/daemonpy.file_process_id.doctree and b/docs/build/doctrees/daemonpy.file_process_id.doctree differ diff --git a/docs/build/doctrees/daemonpy.unix_operations.doctree b/docs/build/doctrees/daemonpy.unix_operations.doctree index 8ff6975..d48a3a4 100644 Binary files a/docs/build/doctrees/daemonpy.unix_operations.doctree and b/docs/build/doctrees/daemonpy.unix_operations.doctree differ diff --git a/docs/build/doctrees/environment.pickle b/docs/build/doctrees/environment.pickle index fddba09..da211af 100644 Binary files a/docs/build/doctrees/environment.pickle and b/docs/build/doctrees/environment.pickle differ diff --git a/docs/build/doctrees/index.doctree b/docs/build/doctrees/index.doctree index 753119d..2d215d3 100644 Binary files a/docs/build/doctrees/index.doctree and b/docs/build/doctrees/index.doctree differ diff --git a/docs/build/doctrees/modules.doctree b/docs/build/doctrees/modules.doctree index a4cf7cd..fcb5efb 100644 Binary files a/docs/build/doctrees/modules.doctree and b/docs/build/doctrees/modules.doctree differ diff --git a/docs/build/html/.buildinfo b/docs/build/html/.buildinfo index b3601a3..1ecc273 100644 --- a/docs/build/html/.buildinfo +++ b/docs/build/html/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: a626c8025b446359291597b4bd00e77f +config: 00aa2f3066fb9b228db180dac36d24bb tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/build/html/_modules/daemonpy.html b/docs/build/html/_modules/daemonpy.html index 35a2f2e..a0b1c2b 100644 --- a/docs/build/html/_modules/daemonpy.html +++ b/docs/build/html/_modules/daemonpy.html @@ -42,7 +42,7 @@

Source code for daemonpy

 Module
     __init__.py
 Copyright
-    Copyright (C) 2020 - 2024 Vladimir Roncevic <elektron.ronca@gmail.com>
+    Copyright (C) 2020 - 2025 Vladimir Roncevic <elektron.ronca@gmail.com>
     daemonpy is free software: you can redistribute it and/or modify it
     under the terms of the GNU General Public License as published by the
     Free Software Foundation, either version 3 of the License, or
@@ -75,18 +75,18 @@ 

Source code for daemonpy

     from daemonpy.file_process_id import FileProcessId
     from daemonpy.file_descriptor import FileDescriptor
     from daemonpy.unix_operations import UnixOperations
-except ImportError as ats_error_message:
-    # Force close python ATS ##################################################
-    sys.exit(f'\n{__file__}\n{ats_error_message}\n')
+except ImportError as ats_error_message:  # pragma: no cover
+    # Force exit python #######################################################
+    sys.exit(f'\n{__file__}\n{ats_error_message}\n')  # pragma: no cover
 
-__author__ = 'Vladimir Roncevic'
-__copyright__ = '(C) 2024, https://vroncevic.github.io/daemonpy'
+__author__: str = 'Vladimir Roncevic'
+__copyright__: str = '(C) 2025, https://vroncevic.github.io/daemonpy'
 __credits__: List[str] = ['Vladimir Roncevic', 'Python Software Foundation']
-__license__ = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE'
-__version__ = '2.0.5'
-__maintainer__ = 'Vladimir Roncevic'
-__email__ = 'elektron.ronca@gmail.com'
-__status__ = 'Updated'
+__license__: str = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE'
+__version__: str = '2.0.6'
+__maintainer__: str = 'Vladimir Roncevic'
+__email__: str = 'elektron.ronca@gmail.com'
+__status__: str = 'Updated'
 
 
 
@@ -372,7 +372,7 @@

Navigation

diff --git a/docs/build/html/_modules/daemonpy/daemon_usage.html b/docs/build/html/_modules/daemonpy/daemon_usage.html index 14967fa..c360fd8 100644 --- a/docs/build/html/_modules/daemonpy/daemon_usage.html +++ b/docs/build/html/_modules/daemonpy/daemon_usage.html @@ -43,7 +43,7 @@

Source code for daemonpy.daemon_usage

 Module
     daemon_usage.py
 Copyright
-    Copyright (C) 2020 - 2024 Vladimir Roncevic <elektron.ronca@gmail.com>
+    Copyright (C) 2020 - 2025 Vladimir Roncevic <elektron.ronca@gmail.com>
     daemonpy is free software: you can redistribute it and/or modify it
     under the terms of the GNU General Public License as published by the
     Free Software Foundation, either version 3 of the License, or
@@ -68,18 +68,18 @@ 

Source code for daemonpy.daemon_usage

     from ats_utilities.console_io.verbose import verbose_message
     from ats_utilities.exceptions.ats_type_error import ATSTypeError
     from ats_utilities.exceptions.ats_value_error import ATSValueError
-except ImportError as ats_error_message:
-    # Force close python ATS ##################################################
-    sys.exit(f'\n{__file__}\n{ats_error_message}\n')
+except ImportError as ats_error_message:  # pragma: no cover
+    # Force exit python #######################################################
+    sys.exit(f'\n{__file__}\n{ats_error_message}\n')  # pragma: no cover
 
-__author__ = 'Vladimir Roncevic'
-__copyright__ = '(C) 2024, https://vroncevic.github.io/daemonpy'
+__author__: str = 'Vladimir Roncevic'
+__copyright__: str = '(C) 2025, https://vroncevic.github.io/daemonpy'
 __credits__: List[str] = ['Vladimir Roncevic', 'Python Software Foundation']
-__license__ = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE'
-__version__ = '2.0.5'
-__maintainer__ = 'Vladimir Roncevic'
-__email__ = 'elektron.ronca@gmail.com'
-__status__ = 'Updated'
+__license__: str = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE'
+__version__: str = '2.0.6'
+__maintainer__: str = 'Vladimir Roncevic'
+__email__: str = 'elektron.ronca@gmail.com'
+__status__: str = 'Updated'
 
 
 
@@ -212,7 +212,7 @@

Navigation

diff --git a/docs/build/html/_modules/daemonpy/file_descriptor.html b/docs/build/html/_modules/daemonpy/file_descriptor.html index dca0795..eda890d 100644 --- a/docs/build/html/_modules/daemonpy/file_descriptor.html +++ b/docs/build/html/_modules/daemonpy/file_descriptor.html @@ -43,7 +43,7 @@

Source code for daemonpy.file_descriptor

 Module
     file_descriptor.py
 Copyright
-    Copyright (C) 2020 - 2024 Vladimir Roncevic <elektron.ronca@gmail.com>
+    Copyright (C) 2020 - 2025 Vladimir Roncevic <elektron.ronca@gmail.com>
     daemonpy is free software: you can redistribute it and/or modify it
     under the terms of the GNU General Public License as published by the
     Free Software Foundation, either version 3 of the License, or
@@ -66,18 +66,18 @@ 

Source code for daemonpy.file_descriptor

     from ats_utilities.checker import ATSChecker
     from ats_utilities.exceptions.ats_type_error import ATSTypeError
     from ats_utilities.exceptions.ats_value_error import ATSValueError
-except ImportError as ats_error_message:
-    # Force close python ATS ##################################################
-    sys.exit(f'\n{__file__}\n{ats_error_message}\n')
+except ImportError as ats_error_message:  # pragma: no cover
+    # Force exit python #######################################################
+    sys.exit(f'\n{__file__}\n{ats_error_message}\n')  # pragma: no cover
 
-__author__ = 'Vladimir Roncevic'
-__copyright__ = '(C) 2024, https://vroncevic.github.io/daemonpy'
+__author__: str = 'Vladimir Roncevic'
+__copyright__: str = '(C) 2025, https://vroncevic.github.io/daemonpy'
 __credits__: List[str] = ['Vladimir Roncevic', 'Python Software Foundation']
-__license__ = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE'
-__version__ = '2.0.3'
-__maintainer__ = 'Vladimir Roncevic'
-__email__ = 'elektron.ronca@gmail.com'
-__status__ = 'Updated'
+__license__: str = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE'
+__version__: str = '2.0.3'
+__maintainer__: str = 'Vladimir Roncevic'
+__email__: str = 'elektron.ronca@gmail.com'
+__status__: str = 'Updated'
 
 
 
@@ -211,7 +211,7 @@

Navigation

diff --git a/docs/build/html/_modules/daemonpy/file_process_id.html b/docs/build/html/_modules/daemonpy/file_process_id.html index c64cbf7..168cb57 100644 --- a/docs/build/html/_modules/daemonpy/file_process_id.html +++ b/docs/build/html/_modules/daemonpy/file_process_id.html @@ -43,7 +43,7 @@

Source code for daemonpy.file_process_id

 Module
     file_process_id.py
 Copyright
-    Copyright (C) 2020 - 2024 Vladimir Roncevic <elektron.ronca@gmail.com>
+    Copyright (C) 2020 - 2025 Vladimir Roncevic <elektron.ronca@gmail.com>
     daemonpy is free software: you can redistribute it and/or modify it
     under the terms of the GNU General Public License as published by the
     Free Software Foundation, either version 3 of the License, or
@@ -66,18 +66,18 @@ 

Source code for daemonpy.file_process_id

     from ats_utilities.checker import ATSChecker
     from ats_utilities.exceptions.ats_type_error import ATSTypeError
     from ats_utilities.exceptions.ats_value_error import ATSValueError
-except ImportError as ats_error_message:
-    # Force close python ATS ##################################################
-    sys.exit(f'\n{__file__}\n{ats_error_message}\n')
+except ImportError as ats_error_message:  # pragma: no cover
+    # Force exit python #######################################################
+    sys.exit(f'\n{__file__}\n{ats_error_message}\n')  # pragma: no cover
 
-__author__ = 'Vladimir Roncevic'
-__copyright__ = '(C) 2024, https://vroncevic.github.io/daemonpy'
+__author__: str = 'Vladimir Roncevic'
+__copyright__: str = '(C) 2025, https://vroncevic.github.io/daemonpy'
 __credits__: List[str] = ['Vladimir Roncevic', 'Python Software Foundation']
-__license__ = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE'
-__version__ = '2.0.5'
-__maintainer__ = 'Vladimir Roncevic'
-__email__ = 'elektron.ronca@gmail.com'
-__status__ = 'Updated'
+__license__: str = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE'
+__version__: str = '2.0.6'
+__maintainer__: str = 'Vladimir Roncevic'
+__email__: str = 'elektron.ronca@gmail.com'
+__status__: str = 'Updated'
 
 
 
@@ -194,7 +194,7 @@

Navigation

diff --git a/docs/build/html/_modules/daemonpy/unix_operations.html b/docs/build/html/_modules/daemonpy/unix_operations.html index 40769eb..fffdf26 100644 --- a/docs/build/html/_modules/daemonpy/unix_operations.html +++ b/docs/build/html/_modules/daemonpy/unix_operations.html @@ -43,7 +43,7 @@

Source code for daemonpy.unix_operations

 Module
     unix_operations.py
 Copyright
-    Copyright (C) 2020 - 2024 Vladimir Roncevic <elektron.ronca@gmail.com>
+    Copyright (C) 2020 - 2025 Vladimir Roncevic <elektron.ronca@gmail.com>
     daemonpy is free software: you can redistribute it and/or modify it
     under the terms of the GNU General Public License as published by the
     Free Software Foundation, either version 3 of the License, or
@@ -72,18 +72,18 @@ 

Source code for daemonpy.unix_operations

     from ats_utilities.console_io.verbose import verbose_message
     from ats_utilities.exceptions.ats_type_error import ATSTypeError
     from ats_utilities.exceptions.ats_value_error import ATSValueError
-except ImportError as ats_error_message:
-    # Force close python ATS ##################################################
-    sys.exit(f'\n{__file__}\n{ats_error_message}\n')
+except ImportError as ats_error_message:  # pragma: no cover
+    # Force exit python #######################################################
+    sys.exit(f'\n{__file__}\n{ats_error_message}\n')  # pragma: no cover
 
-__author__ = 'Vladimir Roncevic'
-__copyright__ = '(C) 2024, https://vroncevic.github.io/daemonpy'
+__author__: str = 'Vladimir Roncevic'
+__copyright__: str = '(C) 2025, https://vroncevic.github.io/daemonpy'
 __credits__: List[str] = ['Vladimir Roncevic', 'Python Software Foundation']
-__license__ = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE'
-__version__ = '2.0.5'
-__maintainer__ = 'Vladimir Roncevic'
-__email__ = 'elektron.ronca@gmail.com'
-__status__ = 'Updated'
+__license__: str = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE'
+__version__: str = '2.0.6'
+__maintainer__: str = 'Vladimir Roncevic'
+__email__: str = 'elektron.ronca@gmail.com'
+__status__: str = 'Updated'
 
 
 
@@ -282,7 +282,7 @@

Navigation

diff --git a/docs/build/html/_modules/index.html b/docs/build/html/_modules/index.html index 8db2e37..675503d 100644 --- a/docs/build/html/_modules/index.html +++ b/docs/build/html/_modules/index.html @@ -76,7 +76,7 @@

Navigation

diff --git a/docs/build/html/_sources/index.rst.txt b/docs/build/html/_sources/index.rst.txt index 87c9bb2..87a25b6 100644 --- a/docs/build/html/_sources/index.rst.txt +++ b/docs/build/html/_sources/index.rst.txt @@ -87,7 +87,7 @@ Create short example Module mydaemon.py Copyright - Copyright (C) 2020 - 2024 Vladimir Roncevic + Copyright (C) 2020 - 2025 Vladimir Roncevic mydaemon is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -175,7 +175,7 @@ Copyright and licence .. |license: apache 2.0| image:: https://img.shields.io/badge/license-apache%202.0-blue.svg :target: https://opensource.org/licenses/apache-2.0 -Copyright (C) 2020 - 2024 by `vroncevic.github.io/daemonpy `_ +Copyright (C) 2020 - 2025 by `vroncevic.github.io/daemonpy `_ **daemonpy** is free software; you can redistribute it and/or modify it under the same terms as Python itself, either Python version 3.x or, diff --git a/docs/build/html/daemonpy.daemon_usage.html b/docs/build/html/daemonpy.daemon_usage.html index 9da643b..5419421 100644 --- a/docs/build/html/daemonpy.daemon_usage.html +++ b/docs/build/html/daemonpy.daemon_usage.html @@ -50,7 +50,7 @@

Navigation

Module

daemon_usage.py

-
Copyright

Copyright (C) 2020 - 2024 Vladimir Roncevic <elektron.ronca@gmail.com> +

Copyright

Copyright (C) 2020 - 2025 Vladimir Roncevic <elektron.ronca@gmail.com> daemonpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -215,7 +215,7 @@

Navigation

diff --git a/docs/build/html/daemonpy.file_descriptor.html b/docs/build/html/daemonpy.file_descriptor.html index 0105057..a92a1ec 100644 --- a/docs/build/html/daemonpy.file_descriptor.html +++ b/docs/build/html/daemonpy.file_descriptor.html @@ -50,7 +50,7 @@

Navigation

Module

file_descriptor.py

-
Copyright

Copyright (C) 2020 - 2024 Vladimir Roncevic <elektron.ronca@gmail.com> +

Copyright

Copyright (C) 2020 - 2025 Vladimir Roncevic <elektron.ronca@gmail.com> daemonpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -202,7 +202,7 @@

Navigation

diff --git a/docs/build/html/daemonpy.file_process_id.html b/docs/build/html/daemonpy.file_process_id.html index 1cd92e6..d49b740 100644 --- a/docs/build/html/daemonpy.file_process_id.html +++ b/docs/build/html/daemonpy.file_process_id.html @@ -50,7 +50,7 @@

Navigation

Module

file_process_id.py

-
Copyright

Copyright (C) 2020 - 2024 Vladimir Roncevic <elektron.ronca@gmail.com> +

Copyright

Copyright (C) 2020 - 2025 Vladimir Roncevic <elektron.ronca@gmail.com> daemonpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -181,7 +181,7 @@

Navigation

diff --git a/docs/build/html/daemonpy.html b/docs/build/html/daemonpy.html index 38851a3..b3f5c82 100644 --- a/docs/build/html/daemonpy.html +++ b/docs/build/html/daemonpy.html @@ -101,7 +101,7 @@

Submodules
Module

__init__.py

-
Copyright

Copyright (C) 2020 - 2024 Vladimir Roncevic <elektron.ronca@gmail.com> +

Copyright

Copyright (C) 2020 - 2025 Vladimir Roncevic <elektron.ronca@gmail.com> daemonpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -358,7 +358,7 @@

Navigation

diff --git a/docs/build/html/daemonpy.unix_operations.html b/docs/build/html/daemonpy.unix_operations.html index 80c0183..379e0df 100644 --- a/docs/build/html/daemonpy.unix_operations.html +++ b/docs/build/html/daemonpy.unix_operations.html @@ -46,7 +46,7 @@

Navigation

Module

unix_operations.py

-
Copyright

Copyright (C) 2020 - 2024 Vladimir Roncevic <elektron.ronca@gmail.com> +

Copyright

Copyright (C) 2020 - 2025 Vladimir Roncevic <elektron.ronca@gmail.com> daemonpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -261,7 +261,7 @@

Navigation

diff --git a/docs/build/html/genindex.html b/docs/build/html/genindex.html index 6307440..f868e1f 100644 --- a/docs/build/html/genindex.html +++ b/docs/build/html/genindex.html @@ -267,7 +267,7 @@

Navigation

diff --git a/docs/build/html/index.html b/docs/build/html/index.html index 98290a6..3e172b0 100644 --- a/docs/build/html/index.html +++ b/docs/build/html/index.html @@ -108,7 +108,7 @@

Usage Module mydaemon.py Copyright - Copyright (C) 2020 - 2024 Vladimir Roncevic <elektron.ronca@gmail.com> + Copyright (C) 2020 - 2025 Vladimir Roncevic <elektron.ronca@gmail.com> mydaemon is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -187,7 +187,7 @@

Package structure

Copyright and licence

license: gpl v3 license: apache 2.0

-

Copyright (C) 2020 - 2024 by vroncevic.github.io/daemonpy

+

Copyright (C) 2020 - 2025 by vroncevic.github.io/daemonpy

daemonpy is free software; you can redistribute it and/or modify it under the same terms as Python itself, either Python version 3.x or, at your option, any later version of Python 3 you may have available.

@@ -267,7 +267,7 @@

Navigation

diff --git a/docs/build/html/modules.html b/docs/build/html/modules.html index e02ce1f..5109992 100644 --- a/docs/build/html/modules.html +++ b/docs/build/html/modules.html @@ -145,7 +145,7 @@

Navigation

diff --git a/docs/build/html/objects.inv b/docs/build/html/objects.inv index 1bd2218..558f9ad 100644 Binary files a/docs/build/html/objects.inv and b/docs/build/html/objects.inv differ diff --git a/docs/build/html/py-modindex.html b/docs/build/html/py-modindex.html index 832fe15..1af7723 100644 --- a/docs/build/html/py-modindex.html +++ b/docs/build/html/py-modindex.html @@ -111,7 +111,7 @@

Navigation

diff --git a/docs/build/html/search.html b/docs/build/html/search.html index 6ef4c41..a767b00 100644 --- a/docs/build/html/search.html +++ b/docs/build/html/search.html @@ -95,7 +95,7 @@

Navigation

diff --git a/docs/build/html/searchindex.js b/docs/build/html/searchindex.js index 7d5ad7c..71cc674 100644 --- a/docs/build/html/searchindex.js +++ b/docs/build/html/searchindex.js @@ -1 +1 @@ -Search.setIndex({"docnames": ["daemonpy", "daemonpy.daemon_usage", "daemonpy.file_descriptor", "daemonpy.file_process_id", "daemonpy.unix_operations", "index", "modules"], "filenames": ["daemonpy.rst", "daemonpy.daemon_usage.rst", "daemonpy.file_descriptor.rst", "daemonpy.file_process_id.rst", "daemonpy.unix_operations.rst", "index.rst", "modules.rst"], "titles": ["daemonpy package", "daemonpy.daemon_usage module", "daemonpy.file_descriptor module", "daemonpy.file_process_id module", "daemonpy.unix_operations module", "Creating Daemon process", "daemonpy"], "terms": {"daemon_usag": [0, 5, 6], "daemonusag": [0, 1, 6], "daemon_oper": [0, 1], "_p_verbos": [0, 1, 2, 3, 4, 6], "check": [0, 1], "usage_statu": [0, 1], "file_descriptor": [0, 5, 6], "filedescriptor": [0, 2, 6], "format": [0, 2], "stderr": [0, 2], "stdin": [0, 2], "stdout": [0, 2], "file_process_id": [0, 5, 6], "fileprocessid": [0, 3, 6], "_mode": [0, 3], "unix_oper": [0, 5, 6], "unixoper": [0, 4, 6], "_no_process": [0, 4], "_os_target": [0, 4], "_sleep": [0, 4], "first_fork": [0, 4], "second_fork": [0, 4], "unix_kil": [0, 4], "unix_statu": [0, 4], "__init__": [0, 1, 2, 3, 4, 5], "py": [0, 1, 2, 3, 4, 5], "copyright": [0, 1, 2, 3, 4], "c": [0, 1, 2, 3, 4, 5], "2020": [0, 1, 2, 3, 4, 5], "2024": [0, 1, 2, 3, 4, 5], "vladimir": [0, 1, 2, 3, 4, 5], "roncev": [0, 1, 2, 3, 4, 5], "elektron": [0, 1, 2, 3, 4, 5], "ronca": [0, 1, 2, 3, 4, 5], "gmail": [0, 1, 2, 3, 4, 5], "com": [0, 1, 2, 3, 4, 5], "i": [0, 1, 2, 3, 4, 5], "free": [0, 1, 2, 3, 4, 5], "softwar": [0, 1, 2, 3, 4, 5], "you": [0, 1, 2, 3, 4, 5], "can": [0, 1, 2, 3, 4, 5], "redistribut": [0, 1, 2, 3, 4, 5], "modifi": [0, 1, 2, 3, 4, 5], "under": [0, 1, 2, 3, 4, 5], "term": [0, 1, 2, 3, 4, 5], "gnu": [0, 1, 2, 3, 4, 5], "gener": [0, 1, 2, 3, 4, 5], "public": [0, 1, 2, 3, 4, 5], "licens": [0, 1, 2, 3, 4, 5], "publish": [0, 1, 2, 3, 4, 5], "foundat": [0, 1, 2, 3, 4, 5], "either": [0, 1, 2, 3, 4, 5], "version": [0, 1, 2, 3, 4, 5], "3": [0, 1, 2, 3, 4, 5], "your": [0, 1, 2, 3, 4, 5], "option": [0, 1, 2, 3, 4, 5], "ani": [0, 1, 2, 3, 4, 5], "later": [0, 1, 2, 3, 4, 5], "distribut": [0, 1, 2, 3, 4, 5], "hope": [0, 1, 2, 3, 4, 5], "us": [0, 1, 2, 3, 4, 5], "without": [0, 1, 2, 3, 4, 5], "warranti": [0, 1, 2, 3, 4, 5], "even": [0, 1, 2, 3, 4, 5], "impli": [0, 1, 2, 3, 4, 5], "merchant": [0, 1, 2, 3, 4, 5], "fit": [0, 1, 2, 3, 4, 5], "FOR": [0, 1, 2, 3, 4, 5], "A": [0, 1, 2, 3, 4, 5], "particular": [0, 1, 2, 3, 4, 5], "purpos": [0, 1, 2, 3, 4, 5], "see": [0, 1, 2, 3, 4, 5], "more": [0, 1, 2, 3, 4, 5], "detail": [0, 1, 2, 3, 4, 5], "should": [0, 1, 2, 3, 4, 5], "have": [0, 1, 2, 3, 4, 5], "receiv": [0, 1, 2, 3, 4, 5], "copi": [0, 1, 2, 3, 4, 5], "along": [0, 1, 2, 3, 4, 5], "thi": [0, 1, 2, 3, 4, 5], "program": [0, 1, 2, 3, 4, 5], "If": [0, 1, 2, 3, 4, 5], "http": [0, 1, 2, 3, 4, 5], "www": [0, 1, 2, 3, 4, 5], "org": [0, 1, 2, 3, 4, 5], "info": [0, 1, 2, 3, 4, 5], "defin": [0, 1, 2, 3, 4, 5], "class": [0, 1, 2, 3, 4, 5], "daemon": [0, 1, 6], "attribut": [0, 1, 2, 3, 4, 5], "": [0, 1, 2, 3, 4, 5], "method": [0, 1, 2, 3, 4, 5], "creat": [0, 1, 2, 3, 4], "base": [0, 1, 2, 3, 4, 5], "backend": 0, "api": [0, 1, 2, 3, 4], "pid": [0, 3, 4, 5], "str": [0, 1, 2, 3, 4], "verbos": [0, 1, 4], "bool": [0, 1, 4], "fals": [0, 1, 4], "sourc": [0, 1, 2, 3, 4], "It": [0, 1, 2, 3, 4, 5], "_pkg_verbos": [0, 1, 2, 3, 4], "consol": [0, 1, 2, 3, 4], "text": [0, 1, 2, 3, 4], "indic": [0, 1, 2, 3, 4], "process": [0, 1, 2, 3, 4], "phase": [0, 1, 2, 3, 4], "_daemon_usag": 0, "usag": [0, 1, 6], "_pid": [0, 3], "file": [0, 2, 3, 4, 5], "path": [0, 2, 3, 4], "initi": [0, 1, 2, 3, 4], "constructor": [0, 1, 2, 3, 4], "start": [0, 1, 4, 6], "stop": [0, 1, 6], "restart": [0, 1, 6], "exit_handl": [0, 6], "At": 0, "exit": [0, 4, 5], "delet": 0, "run": [0, 5, 6], "abstract": 0, "none": [0, 1, 3, 4, 5], "paramet": [0, 1, 4], "enabl": [0, 1, 4], "disabl": [0, 1, 4], "except": [0, 1, 4, 5], "remov": 0, "return": [0, 1, 4], "true": [0, 4, 5], "success": [0, 4], "oper": [0, 1, 4, 5], "type": [0, 1, 2, 4, 5], "overrid": 0, "when": 0, "subclass": 0, "self": [0, 5], "call": 0, "after": 0, "ha": 0, "been": 0, "atstypeerror": [0, 1, 4], "atsvalueerror": [0, 1, 4], "an": [1, 2, 3, 4, 5], "object": [1, 2, 3, 4], "list": [1, 3, 4], "support": [1, 2, 3, 4, 5], "_usage_statu": 1, "statu": [1, 4], "properti": [1, 4], "set": [1, 4, 5], "get": [1, 4], "int": [1, 2, 4], "descriptor": 2, "context": [2, 3], "manag": [2, 3], "desc_path": 2, "desc_typ": 2, "standard": 2, "input": 2, "stream": 2, "id": [2, 3, 4], "data": 2, "output": 2, "error": 2, "messag": [2, 4], "desciptor": 2, "mode": [2, 3], "_desc_path": 2, "devic": 2, "_desc_typ": 2, "_desc_fil": 2, "__enter__": [2, 3], "open": [2, 3, 4], "__exit__": [2, 3], "close": [2, 3, 5], "dict": 2, "0": [2, 4], "r": [2, 3, 5], "1": [2, 4, 5], "2": 2, "pid_path": [3, 4], "pid_mod": 3, "_pid_path": 3, "_pid_mod": 3, "w": 3, "unix": 4, "like": 4, "o": 4, "system": 4, "No": 4, "_unix_statu": 4, "make": 4, "sure": 4, "group": 4, "leader": 4, "won": 4, "t": 4, "mere": 4, "termin": 4, "kill": 4, "linux": 4, "linux2": 4, "float": 4, "code": [4, 5], "fork": 4, "fail": 4, "daemonpi": 5, "develop": 5, "python": 5, "100": 5, "The": 5, "readm": 5, "introduc": 5, "modul": [5, 6], "provid": 5, "instruct": 5, "how": 5, "machin": 5, "mai": 5, "other": 5, "inform": 5, "befor": 5, "ar": 5, "submodul": [5, 6], "navig": 5, "releas": 5, "page": 5, "download": 5, "extract": 5, "archiv": 5, "To": 5, "tar": 5, "xvzf": 5, "x": 5, "y": 5, "z": 5, "gz": 5, "cd": 5, "python3": 5, "pip3": 5, "requir": 5, "txt": 5, "m": 5, "build": 5, "dist": 5, "py3": 5, "whl": 5, "Or": 5, "follow": 5, "pyton3": 5, "setup": 5, "install_lib": 5, "install_egg_info": 5, "docker": 5, "imag": 5, "contain": 5, "pip": 5, "short": 5, "exampl": 5, "usr": 5, "bin": 5, "env": 5, "mydaemon": 5, "import": 5, "sy": 5, "from": 5, "time": 5, "sleep": 5, "try": 5, "importerror": 5, "ats_error_messag": 5, "forc": 5, "ATS": 5, "f": 5, "n": 5, "__file__": 5, "def": 5, "while": 5, "__name__": 5, "__main__": 5, "tmp": 5, "argv": 5, "next": 5, "librari": 5, "ats": 5, "util": 5, "app": 5, "tool": 5, "script": 5, "oop": 5, "directori": 5, "6": 5, "vroncev": 5, "github": 5, "io": 5, "same": 5, "itself": 5, "avail": 5, "let": 5, "help": 5, "psf": 5, "index": 5, "search": 5, "packag": 6, "content": 6}, "objects": {"": [[0, 0, 0, "-", "daemonpy"]], "daemonpy": [[0, 1, 1, "", "Daemon"], [1, 0, 0, "-", "daemon_usage"], [2, 0, 0, "-", "file_descriptor"], [3, 0, 0, "-", "file_process_id"], [4, 0, 0, "-", "unix_operations"]], "daemonpy.Daemon": [[0, 2, 1, "", "_P_VERBOSE"], [0, 3, 1, "", "daemonize"], [0, 3, 1, "", "exit_handler"], [0, 3, 1, "", "restart"], [0, 3, 1, "", "run"], [0, 3, 1, "", "start"], [0, 3, 1, "", "stop"], [0, 3, 1, "", "usage"]], "daemonpy.daemon_usage": [[1, 1, 1, "", "DaemonUsage"]], "daemonpy.daemon_usage.DaemonUsage": [[1, 2, 1, "", "DAEMON_OPERATIONS"], [1, 2, 1, "", "_P_VERBOSE"], [1, 3, 1, "", "check"], [1, 4, 1, "", "usage_status"]], "daemonpy.file_descriptor": [[2, 1, 1, "", "FileDescriptor"]], "daemonpy.file_descriptor.FileDescriptor": [[2, 2, 1, "", "FORMAT"], [2, 2, 1, "", "STDERR"], [2, 2, 1, "", "STDIN"], [2, 2, 1, "", "STDOUT"], [2, 2, 1, "", "_P_VERBOSE"]], "daemonpy.file_process_id": [[3, 1, 1, "", "FileProcessId"]], "daemonpy.file_process_id.FileProcessId": [[3, 2, 1, "", "_MODE"], [3, 2, 1, "", "_P_VERBOSE"]], "daemonpy.unix_operations": [[4, 1, 1, "", "UnixOperations"]], "daemonpy.unix_operations.UnixOperations": [[4, 2, 1, "", "_NO_PROCESS"], [4, 2, 1, "", "_OS_TARGET"], [4, 2, 1, "", "_P_VERBOSE"], [4, 2, 1, "", "_SLEEP"], [4, 3, 1, "", "first_fork"], [4, 3, 1, "", "second_fork"], [4, 3, 1, "", "unix_kill"], [4, 4, 1, "", "unix_status"]]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:attribute", "3": "py:method", "4": "py:property"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "attribute", "Python attribute"], "3": ["py", "method", "Python method"], "4": ["py", "property", "Python property"]}, "titleterms": {"daemonpi": [0, 1, 2, 3, 4, 6], "packag": [0, 5], "submodul": 0, "modul": [0, 1, 2, 3, 4], "content": [0, 5], "daemon_usag": 1, "file_descriptor": 2, "file_process_id": 3, "unix_oper": 4, "creat": 5, "daemon": 5, "process": 5, "instal": 5, "usag": 5, "depend": 5, "structur": 5, "copyright": 5, "licenc": 5, "indic": 5, "tabl": 5}, "envversion": {"sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.viewcode": 1, "sphinx": 60}, "alltitles": {"daemonpy package": [[0, "daemonpy-package"]], "Submodules": [[0, "submodules"]], "Module contents": [[0, "module-daemonpy"]], "daemonpy.daemon_usage module": [[1, "module-daemonpy.daemon_usage"]], "daemonpy.file_descriptor module": [[2, "module-daemonpy.file_descriptor"]], "daemonpy.file_process_id module": [[3, "module-daemonpy.file_process_id"]], "daemonpy.unix_operations module": [[4, "module-daemonpy.unix_operations"]], "Creating Daemon process": [[5, "creating-daemon-process"]], "Contents:": [[5, null]], "Installation": [[5, "installation"]], "Usage": [[5, "usage"]], "Dependencies": [[5, "dependencies"]], "Package structure": [[5, "package-structure"]], "Copyright and licence": [[5, "copyright-and-licence"]], "Indices and tables": [[5, "indices-and-tables"]], "daemonpy": [[6, "daemonpy"]]}, "indexentries": {"daemon (class in daemonpy)": [[0, "daemonpy.Daemon"]], "_p_verbose (daemonpy.daemon attribute)": [[0, "daemonpy.Daemon._P_VERBOSE"]], "daemonize() (daemonpy.daemon method)": [[0, "daemonpy.Daemon.daemonize"]], "daemonpy": [[0, "module-daemonpy"]], "exit_handler() (daemonpy.daemon method)": [[0, "daemonpy.Daemon.exit_handler"]], "module": [[0, "module-daemonpy"], [1, "module-daemonpy.daemon_usage"], [2, "module-daemonpy.file_descriptor"], [3, "module-daemonpy.file_process_id"], [4, "module-daemonpy.unix_operations"]], "restart() (daemonpy.daemon method)": [[0, "daemonpy.Daemon.restart"]], "run() (daemonpy.daemon method)": [[0, "daemonpy.Daemon.run"]], "start() (daemonpy.daemon method)": [[0, "daemonpy.Daemon.start"]], "stop() (daemonpy.daemon method)": [[0, "daemonpy.Daemon.stop"]], "usage() (daemonpy.daemon method)": [[0, "daemonpy.Daemon.usage"]], "daemon_operations (daemonpy.daemon_usage.daemonusage attribute)": [[1, "daemonpy.daemon_usage.DaemonUsage.DAEMON_OPERATIONS"]], "daemonusage (class in daemonpy.daemon_usage)": [[1, "daemonpy.daemon_usage.DaemonUsage"]], "_p_verbose (daemonpy.daemon_usage.daemonusage attribute)": [[1, "daemonpy.daemon_usage.DaemonUsage._P_VERBOSE"]], "check() (daemonpy.daemon_usage.daemonusage method)": [[1, "daemonpy.daemon_usage.DaemonUsage.check"]], "daemonpy.daemon_usage": [[1, "module-daemonpy.daemon_usage"]], "usage_status (daemonpy.daemon_usage.daemonusage property)": [[1, "daemonpy.daemon_usage.DaemonUsage.usage_status"]], "format (daemonpy.file_descriptor.filedescriptor attribute)": [[2, "daemonpy.file_descriptor.FileDescriptor.FORMAT"]], "filedescriptor (class in daemonpy.file_descriptor)": [[2, "daemonpy.file_descriptor.FileDescriptor"]], "stderr (daemonpy.file_descriptor.filedescriptor attribute)": [[2, "daemonpy.file_descriptor.FileDescriptor.STDERR"]], "stdin (daemonpy.file_descriptor.filedescriptor attribute)": [[2, "daemonpy.file_descriptor.FileDescriptor.STDIN"]], "stdout (daemonpy.file_descriptor.filedescriptor attribute)": [[2, "daemonpy.file_descriptor.FileDescriptor.STDOUT"]], "_p_verbose (daemonpy.file_descriptor.filedescriptor attribute)": [[2, "daemonpy.file_descriptor.FileDescriptor._P_VERBOSE"]], "daemonpy.file_descriptor": [[2, "module-daemonpy.file_descriptor"]], "fileprocessid (class in daemonpy.file_process_id)": [[3, "daemonpy.file_process_id.FileProcessId"]], "_mode (daemonpy.file_process_id.fileprocessid attribute)": [[3, "daemonpy.file_process_id.FileProcessId._MODE"]], "_p_verbose (daemonpy.file_process_id.fileprocessid attribute)": [[3, "daemonpy.file_process_id.FileProcessId._P_VERBOSE"]], "daemonpy.file_process_id": [[3, "module-daemonpy.file_process_id"]], "unixoperations (class in daemonpy.unix_operations)": [[4, "daemonpy.unix_operations.UnixOperations"]], "_no_process (daemonpy.unix_operations.unixoperations attribute)": [[4, "daemonpy.unix_operations.UnixOperations._NO_PROCESS"]], "_os_target (daemonpy.unix_operations.unixoperations attribute)": [[4, "daemonpy.unix_operations.UnixOperations._OS_TARGET"]], "_p_verbose (daemonpy.unix_operations.unixoperations attribute)": [[4, "daemonpy.unix_operations.UnixOperations._P_VERBOSE"]], "_sleep (daemonpy.unix_operations.unixoperations attribute)": [[4, "daemonpy.unix_operations.UnixOperations._SLEEP"]], "daemonpy.unix_operations": [[4, "module-daemonpy.unix_operations"]], "first_fork() (daemonpy.unix_operations.unixoperations method)": [[4, "daemonpy.unix_operations.UnixOperations.first_fork"]], "second_fork() (daemonpy.unix_operations.unixoperations method)": [[4, "daemonpy.unix_operations.UnixOperations.second_fork"]], "unix_kill() (daemonpy.unix_operations.unixoperations method)": [[4, "daemonpy.unix_operations.UnixOperations.unix_kill"]], "unix_status (daemonpy.unix_operations.unixoperations property)": [[4, "daemonpy.unix_operations.UnixOperations.unix_status"]]}}) \ No newline at end of file +Search.setIndex({"docnames": ["daemonpy", "daemonpy.daemon_usage", "daemonpy.file_descriptor", "daemonpy.file_process_id", "daemonpy.unix_operations", "index", "modules"], "filenames": ["daemonpy.rst", "daemonpy.daemon_usage.rst", "daemonpy.file_descriptor.rst", "daemonpy.file_process_id.rst", "daemonpy.unix_operations.rst", "index.rst", "modules.rst"], "titles": ["daemonpy package", "daemonpy.daemon_usage module", "daemonpy.file_descriptor module", "daemonpy.file_process_id module", "daemonpy.unix_operations module", "Creating Daemon process", "daemonpy"], "terms": {"daemon_usag": [0, 5, 6], "daemonusag": [0, 1, 6], "daemon_oper": [0, 1], "_p_verbos": [0, 1, 2, 3, 4, 6], "check": [0, 1], "usage_statu": [0, 1], "file_descriptor": [0, 5, 6], "filedescriptor": [0, 2, 6], "format": [0, 2], "stderr": [0, 2], "stdin": [0, 2], "stdout": [0, 2], "file_process_id": [0, 5, 6], "fileprocessid": [0, 3, 6], "_mode": [0, 3], "unix_oper": [0, 5, 6], "unixoper": [0, 4, 6], "_no_process": [0, 4], "_os_target": [0, 4], "_sleep": [0, 4], "first_fork": [0, 4], "second_fork": [0, 4], "unix_kil": [0, 4], "unix_statu": [0, 4], "__init__": [0, 1, 2, 3, 4, 5], "py": [0, 1, 2, 3, 4, 5], "copyright": [0, 1, 2, 3, 4], "c": [0, 1, 2, 3, 4, 5], "2020": [0, 1, 2, 3, 4, 5], "2025": [0, 1, 2, 3, 4, 5], "vladimir": [0, 1, 2, 3, 4, 5], "roncev": [0, 1, 2, 3, 4, 5], "elektron": [0, 1, 2, 3, 4, 5], "ronca": [0, 1, 2, 3, 4, 5], "gmail": [0, 1, 2, 3, 4, 5], "com": [0, 1, 2, 3, 4, 5], "i": [0, 1, 2, 3, 4, 5], "free": [0, 1, 2, 3, 4, 5], "softwar": [0, 1, 2, 3, 4, 5], "you": [0, 1, 2, 3, 4, 5], "can": [0, 1, 2, 3, 4, 5], "redistribut": [0, 1, 2, 3, 4, 5], "modifi": [0, 1, 2, 3, 4, 5], "under": [0, 1, 2, 3, 4, 5], "term": [0, 1, 2, 3, 4, 5], "gnu": [0, 1, 2, 3, 4, 5], "gener": [0, 1, 2, 3, 4, 5], "public": [0, 1, 2, 3, 4, 5], "licens": [0, 1, 2, 3, 4, 5], "publish": [0, 1, 2, 3, 4, 5], "foundat": [0, 1, 2, 3, 4, 5], "either": [0, 1, 2, 3, 4, 5], "version": [0, 1, 2, 3, 4, 5], "3": [0, 1, 2, 3, 4, 5], "your": [0, 1, 2, 3, 4, 5], "option": [0, 1, 2, 3, 4, 5], "ani": [0, 1, 2, 3, 4, 5], "later": [0, 1, 2, 3, 4, 5], "distribut": [0, 1, 2, 3, 4, 5], "hope": [0, 1, 2, 3, 4, 5], "us": [0, 1, 2, 3, 4, 5], "without": [0, 1, 2, 3, 4, 5], "warranti": [0, 1, 2, 3, 4, 5], "even": [0, 1, 2, 3, 4, 5], "impli": [0, 1, 2, 3, 4, 5], "merchant": [0, 1, 2, 3, 4, 5], "fit": [0, 1, 2, 3, 4, 5], "FOR": [0, 1, 2, 3, 4, 5], "A": [0, 1, 2, 3, 4, 5], "particular": [0, 1, 2, 3, 4, 5], "purpos": [0, 1, 2, 3, 4, 5], "see": [0, 1, 2, 3, 4, 5], "more": [0, 1, 2, 3, 4, 5], "detail": [0, 1, 2, 3, 4, 5], "should": [0, 1, 2, 3, 4, 5], "have": [0, 1, 2, 3, 4, 5], "receiv": [0, 1, 2, 3, 4, 5], "copi": [0, 1, 2, 3, 4, 5], "along": [0, 1, 2, 3, 4, 5], "thi": [0, 1, 2, 3, 4, 5], "program": [0, 1, 2, 3, 4, 5], "If": [0, 1, 2, 3, 4, 5], "http": [0, 1, 2, 3, 4, 5], "www": [0, 1, 2, 3, 4, 5], "org": [0, 1, 2, 3, 4, 5], "info": [0, 1, 2, 3, 4, 5], "defin": [0, 1, 2, 3, 4, 5], "class": [0, 1, 2, 3, 4, 5], "daemon": [0, 1, 6], "attribut": [0, 1, 2, 3, 4, 5], "": [0, 1, 2, 3, 4, 5], "method": [0, 1, 2, 3, 4, 5], "creat": [0, 1, 2, 3, 4], "base": [0, 1, 2, 3, 4, 5], "backend": 0, "api": [0, 1, 2, 3, 4], "pid": [0, 3, 4, 5], "str": [0, 1, 2, 3, 4], "verbos": [0, 1, 4], "bool": [0, 1, 4], "fals": [0, 1, 4], "sourc": [0, 1, 2, 3, 4], "It": [0, 1, 2, 3, 4, 5], "_pkg_verbos": [0, 1, 2, 3, 4], "consol": [0, 1, 2, 3, 4], "text": [0, 1, 2, 3, 4], "indic": [0, 1, 2, 3, 4], "process": [0, 1, 2, 3, 4], "phase": [0, 1, 2, 3, 4], "_daemon_usag": 0, "usag": [0, 1, 6], "_pid": [0, 3], "file": [0, 2, 3, 4, 5], "path": [0, 2, 3, 4], "initi": [0, 1, 2, 3, 4], "constructor": [0, 1, 2, 3, 4], "start": [0, 1, 4, 6], "stop": [0, 1, 6], "restart": [0, 1, 6], "exit_handl": [0, 6], "At": 0, "exit": [0, 4, 5], "delet": 0, "run": [0, 5, 6], "abstract": 0, "none": [0, 1, 3, 4, 5], "paramet": [0, 1, 4], "enabl": [0, 1, 4], "disabl": [0, 1, 4], "except": [0, 1, 4, 5], "remov": 0, "return": [0, 1, 4], "true": [0, 4, 5], "success": [0, 4], "oper": [0, 1, 4, 5], "type": [0, 1, 2, 4, 5], "overrid": 0, "when": 0, "subclass": 0, "self": [0, 5], "call": 0, "after": 0, "ha": 0, "been": 0, "atstypeerror": [0, 1, 4], "atsvalueerror": [0, 1, 4], "an": [1, 2, 3, 4, 5], "object": [1, 2, 3, 4], "list": [1, 3, 4], "support": [1, 2, 3, 4, 5], "_usage_statu": 1, "statu": [1, 4], "properti": [1, 4], "set": [1, 4, 5], "get": [1, 4], "int": [1, 2, 4], "descriptor": 2, "context": [2, 3], "manag": [2, 3], "desc_path": 2, "desc_typ": 2, "standard": 2, "input": 2, "stream": 2, "id": [2, 3, 4], "data": 2, "output": 2, "error": 2, "messag": [2, 4], "desciptor": 2, "mode": [2, 3], "_desc_path": 2, "devic": 2, "_desc_typ": 2, "_desc_fil": 2, "__enter__": [2, 3], "open": [2, 3, 4], "__exit__": [2, 3], "close": [2, 3, 5], "dict": 2, "0": [2, 4], "r": [2, 3, 5], "1": [2, 4, 5], "2": 2, "pid_path": [3, 4], "pid_mod": 3, "_pid_path": 3, "_pid_mod": 3, "w": 3, "unix": 4, "like": 4, "o": 4, "system": 4, "No": 4, "_unix_statu": 4, "make": 4, "sure": 4, "group": 4, "leader": 4, "won": 4, "t": 4, "mere": 4, "termin": 4, "kill": 4, "linux": 4, "linux2": 4, "float": 4, "code": [4, 5], "fork": 4, "fail": 4, "daemonpi": 5, "develop": 5, "python": 5, "100": 5, "The": 5, "readm": 5, "introduc": 5, "modul": [5, 6], "provid": 5, "instruct": 5, "how": 5, "machin": 5, "mai": 5, "other": 5, "inform": 5, "befor": 5, "ar": 5, "submodul": [5, 6], "navig": 5, "releas": 5, "page": 5, "download": 5, "extract": 5, "archiv": 5, "To": 5, "tar": 5, "xvzf": 5, "x": 5, "y": 5, "z": 5, "gz": 5, "cd": 5, "python3": 5, "pip3": 5, "requir": 5, "txt": 5, "m": 5, "build": 5, "dist": 5, "py3": 5, "whl": 5, "Or": 5, "follow": 5, "pyton3": 5, "setup": 5, "install_lib": 5, "install_egg_info": 5, "docker": 5, "imag": 5, "contain": 5, "pip": 5, "short": 5, "exampl": 5, "usr": 5, "bin": 5, "env": 5, "mydaemon": 5, "import": 5, "sy": 5, "from": 5, "time": 5, "sleep": 5, "try": 5, "importerror": 5, "ats_error_messag": 5, "forc": 5, "ATS": 5, "f": 5, "n": 5, "__file__": 5, "def": 5, "while": 5, "__name__": 5, "__main__": 5, "tmp": 5, "argv": 5, "next": 5, "librari": 5, "ats": 5, "util": 5, "app": 5, "tool": 5, "script": 5, "oop": 5, "directori": 5, "6": 5, "vroncev": 5, "github": 5, "io": 5, "same": 5, "itself": 5, "avail": 5, "let": 5, "help": 5, "psf": 5, "index": 5, "search": 5, "packag": 6, "content": 6}, "objects": {"": [[0, 0, 0, "-", "daemonpy"]], "daemonpy": [[0, 1, 1, "", "Daemon"], [1, 0, 0, "-", "daemon_usage"], [2, 0, 0, "-", "file_descriptor"], [3, 0, 0, "-", "file_process_id"], [4, 0, 0, "-", "unix_operations"]], "daemonpy.Daemon": [[0, 2, 1, "", "_P_VERBOSE"], [0, 3, 1, "", "daemonize"], [0, 3, 1, "", "exit_handler"], [0, 3, 1, "", "restart"], [0, 3, 1, "", "run"], [0, 3, 1, "", "start"], [0, 3, 1, "", "stop"], [0, 3, 1, "", "usage"]], "daemonpy.daemon_usage": [[1, 1, 1, "", "DaemonUsage"]], "daemonpy.daemon_usage.DaemonUsage": [[1, 2, 1, "", "DAEMON_OPERATIONS"], [1, 2, 1, "", "_P_VERBOSE"], [1, 3, 1, "", "check"], [1, 4, 1, "", "usage_status"]], "daemonpy.file_descriptor": [[2, 1, 1, "", "FileDescriptor"]], "daemonpy.file_descriptor.FileDescriptor": [[2, 2, 1, "", "FORMAT"], [2, 2, 1, "", "STDERR"], [2, 2, 1, "", "STDIN"], [2, 2, 1, "", "STDOUT"], [2, 2, 1, "", "_P_VERBOSE"]], "daemonpy.file_process_id": [[3, 1, 1, "", "FileProcessId"]], "daemonpy.file_process_id.FileProcessId": [[3, 2, 1, "", "_MODE"], [3, 2, 1, "", "_P_VERBOSE"]], "daemonpy.unix_operations": [[4, 1, 1, "", "UnixOperations"]], "daemonpy.unix_operations.UnixOperations": [[4, 2, 1, "", "_NO_PROCESS"], [4, 2, 1, "", "_OS_TARGET"], [4, 2, 1, "", "_P_VERBOSE"], [4, 2, 1, "", "_SLEEP"], [4, 3, 1, "", "first_fork"], [4, 3, 1, "", "second_fork"], [4, 3, 1, "", "unix_kill"], [4, 4, 1, "", "unix_status"]]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:attribute", "3": "py:method", "4": "py:property"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "attribute", "Python attribute"], "3": ["py", "method", "Python method"], "4": ["py", "property", "Python property"]}, "titleterms": {"daemonpi": [0, 1, 2, 3, 4, 6], "packag": [0, 5], "submodul": 0, "modul": [0, 1, 2, 3, 4], "content": [0, 5], "daemon_usag": 1, "file_descriptor": 2, "file_process_id": 3, "unix_oper": 4, "creat": 5, "daemon": 5, "process": 5, "instal": 5, "usag": 5, "depend": 5, "structur": 5, "copyright": 5, "licenc": 5, "indic": 5, "tabl": 5}, "envversion": {"sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.viewcode": 1, "sphinx": 60}, "alltitles": {"daemonpy package": [[0, "daemonpy-package"]], "Submodules": [[0, "submodules"]], "Module contents": [[0, "module-daemonpy"]], "daemonpy.daemon_usage module": [[1, "module-daemonpy.daemon_usage"]], "daemonpy.file_descriptor module": [[2, "module-daemonpy.file_descriptor"]], "daemonpy.file_process_id module": [[3, "module-daemonpy.file_process_id"]], "daemonpy.unix_operations module": [[4, "module-daemonpy.unix_operations"]], "Creating Daemon process": [[5, "creating-daemon-process"]], "Contents:": [[5, null]], "Installation": [[5, "installation"]], "Usage": [[5, "usage"]], "Dependencies": [[5, "dependencies"]], "Package structure": [[5, "package-structure"]], "Copyright and licence": [[5, "copyright-and-licence"]], "Indices and tables": [[5, "indices-and-tables"]], "daemonpy": [[6, "daemonpy"]]}, "indexentries": {"daemon (class in daemonpy)": [[0, "daemonpy.Daemon"]], "_p_verbose (daemonpy.daemon attribute)": [[0, "daemonpy.Daemon._P_VERBOSE"]], "daemonize() (daemonpy.daemon method)": [[0, "daemonpy.Daemon.daemonize"]], "daemonpy": [[0, "module-daemonpy"]], "exit_handler() (daemonpy.daemon method)": [[0, "daemonpy.Daemon.exit_handler"]], "module": [[0, "module-daemonpy"], [1, "module-daemonpy.daemon_usage"], [2, "module-daemonpy.file_descriptor"], [3, "module-daemonpy.file_process_id"], [4, "module-daemonpy.unix_operations"]], "restart() (daemonpy.daemon method)": [[0, "daemonpy.Daemon.restart"]], "run() (daemonpy.daemon method)": [[0, "daemonpy.Daemon.run"]], "start() (daemonpy.daemon method)": [[0, "daemonpy.Daemon.start"]], "stop() (daemonpy.daemon method)": [[0, "daemonpy.Daemon.stop"]], "usage() (daemonpy.daemon method)": [[0, "daemonpy.Daemon.usage"]], "daemon_operations (daemonpy.daemon_usage.daemonusage attribute)": [[1, "daemonpy.daemon_usage.DaemonUsage.DAEMON_OPERATIONS"]], "daemonusage (class in daemonpy.daemon_usage)": [[1, "daemonpy.daemon_usage.DaemonUsage"]], "_p_verbose (daemonpy.daemon_usage.daemonusage attribute)": [[1, "daemonpy.daemon_usage.DaemonUsage._P_VERBOSE"]], "check() (daemonpy.daemon_usage.daemonusage method)": [[1, "daemonpy.daemon_usage.DaemonUsage.check"]], "daemonpy.daemon_usage": [[1, "module-daemonpy.daemon_usage"]], "usage_status (daemonpy.daemon_usage.daemonusage property)": [[1, "daemonpy.daemon_usage.DaemonUsage.usage_status"]], "format (daemonpy.file_descriptor.filedescriptor attribute)": [[2, "daemonpy.file_descriptor.FileDescriptor.FORMAT"]], "filedescriptor (class in daemonpy.file_descriptor)": [[2, "daemonpy.file_descriptor.FileDescriptor"]], "stderr (daemonpy.file_descriptor.filedescriptor attribute)": [[2, "daemonpy.file_descriptor.FileDescriptor.STDERR"]], "stdin (daemonpy.file_descriptor.filedescriptor attribute)": [[2, "daemonpy.file_descriptor.FileDescriptor.STDIN"]], "stdout (daemonpy.file_descriptor.filedescriptor attribute)": [[2, "daemonpy.file_descriptor.FileDescriptor.STDOUT"]], "_p_verbose (daemonpy.file_descriptor.filedescriptor attribute)": [[2, "daemonpy.file_descriptor.FileDescriptor._P_VERBOSE"]], "daemonpy.file_descriptor": [[2, "module-daemonpy.file_descriptor"]], "fileprocessid (class in daemonpy.file_process_id)": [[3, "daemonpy.file_process_id.FileProcessId"]], "_mode (daemonpy.file_process_id.fileprocessid attribute)": [[3, "daemonpy.file_process_id.FileProcessId._MODE"]], "_p_verbose (daemonpy.file_process_id.fileprocessid attribute)": [[3, "daemonpy.file_process_id.FileProcessId._P_VERBOSE"]], "daemonpy.file_process_id": [[3, "module-daemonpy.file_process_id"]], "unixoperations (class in daemonpy.unix_operations)": [[4, "daemonpy.unix_operations.UnixOperations"]], "_no_process (daemonpy.unix_operations.unixoperations attribute)": [[4, "daemonpy.unix_operations.UnixOperations._NO_PROCESS"]], "_os_target (daemonpy.unix_operations.unixoperations attribute)": [[4, "daemonpy.unix_operations.UnixOperations._OS_TARGET"]], "_p_verbose (daemonpy.unix_operations.unixoperations attribute)": [[4, "daemonpy.unix_operations.UnixOperations._P_VERBOSE"]], "_sleep (daemonpy.unix_operations.unixoperations attribute)": [[4, "daemonpy.unix_operations.UnixOperations._SLEEP"]], "daemonpy.unix_operations": [[4, "module-daemonpy.unix_operations"]], "first_fork() (daemonpy.unix_operations.unixoperations method)": [[4, "daemonpy.unix_operations.UnixOperations.first_fork"]], "second_fork() (daemonpy.unix_operations.unixoperations method)": [[4, "daemonpy.unix_operations.UnixOperations.second_fork"]], "unix_kill() (daemonpy.unix_operations.unixoperations method)": [[4, "daemonpy.unix_operations.UnixOperations.unix_kill"]], "unix_status (daemonpy.unix_operations.unixoperations property)": [[4, "daemonpy.unix_operations.UnixOperations.unix_status"]]}}) \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index 9211348..c433925 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -4,7 +4,7 @@ Module conf.py Copyright - Copyright (C) 2020 - 2024 Vladimir Roncevic + Copyright (C) 2020 - 2025 Vladimir Roncevic daemonpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -27,9 +27,9 @@ sys.path.insert(0, os.path.abspath('../../')) project: str = 'daemonpy' -project_copyright: str = '2024, Vladimir Roncevic ' +project_copyright: str = '2025, Vladimir Roncevic ' author: str = 'Vladimir Roncevic ' -version: str = '2.0.5' +version: str = '2.0.6' release: str = 'https://github.com/vroncevic/daemonpy/releases' extensions: List[str] = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] templates_path: List[str] = ['_templates'] diff --git a/docs/source/index.rst b/docs/source/index.rst index 87c9bb2..87a25b6 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -87,7 +87,7 @@ Create short example Module mydaemon.py Copyright - Copyright (C) 2020 - 2024 Vladimir Roncevic + Copyright (C) 2020 - 2025 Vladimir Roncevic mydaemon is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -175,7 +175,7 @@ Copyright and licence .. |license: apache 2.0| image:: https://img.shields.io/badge/license-apache%202.0-blue.svg :target: https://opensource.org/licenses/apache-2.0 -Copyright (C) 2020 - 2024 by `vroncevic.github.io/daemonpy `_ +Copyright (C) 2020 - 2025 by `vroncevic.github.io/daemonpy `_ **daemonpy** is free software; you can redistribute it and/or modify it under the same terms as Python itself, either Python version 3.x or, diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index d98a042..0000000 --- a/setup.cfg +++ /dev/null @@ -1,46 +0,0 @@ -# -# Module -# setup.cfg -# Copyright -# Copyright (C) 2020 - 2024 Vladimir Roncevic -# daemonpy is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the -# Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# daemonpy is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License for more details. -# You should have received a copy of the GNU General Public License along -# with this program. If not, see . -# Info -# Defines setup for daemonpy package. -# - -[metadata] -name = daemonpy -version = 2.0.5 -author = Vladimir Roncevic -author_email = elektron.ronca@gmail.com -description = Creating Daemon process -license=GPL 2024 Free software to use and distributed it. -long_description = file: README.md -long_description_content_type = text/markdown -keywords=Unix, Linux, Daemon, Process -platforms = any -url = https://vroncevic.github.io/daemonpy -classifiers = - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - License :: OSI Approved :: GNU Lesser General Public License v2 (LGPLv2) - License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+) - License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3) - License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+) - License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) - Operating System :: OS Independent - -[options] -packages = find: -python_requires = >=3.10 -install_requires= - ats-utilities diff --git a/setup.py b/setup.py index 8e98029..22a02fa 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ Module setup.py Copyright - Copyright (C) 2020 - 2024 Vladimir Roncevic + Copyright (C) 2020 - 2025 Vladimir Roncevic daemonpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -25,44 +25,37 @@ from os.path import abspath, dirname, join from setuptools import setup -__author__ = 'Vladimir Roncevic' -__copyright__ = '(C) 2024, https://vroncevic.github.io/daemonpy' +__author__: str = 'Vladimir Roncevic' +__copyright__: str = '(C) 2025, https://vroncevic.github.io/daemonpy' __credits__: List[str] = ['Vladimir Roncevic', 'Python Software Foundation'] -__license__ = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' -__version__ = '2.0.5' -__maintainer__ = 'Vladimir Roncevic' -__email__ = 'elektron.ronca@gmail.com' -__status__ = 'Updated' +__license__: str = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' +__version__: str = '2.0.6' +__maintainer__: str = 'Vladimir Roncevic' +__email__: str = 'elektron.ronca@gmail.com' +__status__: str = 'Updated' THIS_DIR: str = abspath(dirname(__file__)) long_description: Optional[str] = None with open(join(THIS_DIR, 'README.md'), encoding='utf-8') as readme: long_description = readme.read() PROGRAMMING_LANG: str = 'Programming Language :: Python ::' -VERSIONS: List[str] = ['3.10', '3.11'] +VERSIONS: List[str] = ['3.10', '3.11', '3.12'] SUPPORTED_PY_VERSIONS: List[str] = [ f'{PROGRAMMING_LANG} {VERSION}' for VERSION in VERSIONS ] LICENSE_PREFIX: str = 'License :: OSI Approved ::' LICENSES: List[str] = [ - 'GNU Lesser General Public License v2 (LGPLv2)', - 'GNU Lesser General Public License v2 or later (LGPLv2+)', - 'GNU Lesser General Public License v3 (LGPLv3)', - 'GNU Lesser General Public License v3 or later (LGPLv3+)', - 'GNU Library or Lesser General Public License (LGPL)' + 'GNU Lesser General Public License v3 or later (LGPLv3+)' ] -APPROVED_LICENSES: List[str] = [ - f'{LICENSE_PREFIX} {LICENSE}' for LICENSE in LICENSES -] -PYP_CLASSIFIERS: List[str] = SUPPORTED_PY_VERSIONS + APPROVED_LICENSES +PYP_CLASSIFIERS: List[str] = SUPPORTED_PY_VERSIONS setup( name='daemonpy', - version='2.0.5', + version='2.0.6', description='Creating Daemon process', author='Vladimir Roncevic', author_email='elektron.ronca@gmail.com', url='https://vroncevic.github.io/daemonpy/', - license='GPL 2024 Free software to use and distributed it.', + license='GPL-3.0-or-later', long_description=long_description, long_description_content_type='text/markdown', keywords='Unix, Linux, Daemon, Process', diff --git a/tests/.coverage b/tests/.coverage index 444d571..fbd5c76 100644 Binary files a/tests/.coverage and b/tests/.coverage differ diff --git a/tests/ats_coverage.py b/tests/ats_coverage.py new file mode 100644 index 0000000..1db4607 --- /dev/null +++ b/tests/ats_coverage.py @@ -0,0 +1,215 @@ +# -*- coding: UTF-8 -*- + +''' +Module + ats_coverage.py +Copyright + Copyright (C) 2025 Vladimir Roncevic + ats_coverage is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + ats_coverage is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + You should have received a copy of the GNU General Public License along + with this program. If not, see . +Info + Defines attribute(s) and method(s) for coverage support. +''' + +import sys +from typing import Any, Dict, List, Optional +from os.path import exists, basename +from json import load +from unittest import TestLoader, TestSuite, TextTestRunner +from argparse import Namespace + +try: + from pathlib import Path + from ats_utilities.checker import ATSChecker + from ats_utilities.console_io.success import success_message + from ats_utilities.console_io.error import error_message + from ats_utilities.option import ATSOptionParser + from ats_utilities.exceptions.ats_type_error import ATSTypeError + from ats_utilities.exceptions.ats_file_error import ATSFileError + from coverage import Coverage +except ImportError as ats_error_message: # pragma: no cover + # Force exit python ####################################################### + sys.exit(f'\n{__file__}\n{ats_error_message}\n') # pragma: no cover + +__author__: str = 'Vladimir Roncevic' +__copyright__: str = '(C) 2025, https://vroncevic.github.io/ats_coverage' +__credits__: List[str] = ['Vladimir Roncevic', 'Python Software Foundation'] +__license__: str = 'https://github.com/vroncevic/ats_coverage/blob/dev/LICENSE' +__version__: str = '1.0.0' +__maintainer__: str = 'Vladimir Roncevic' +__email__: str = 'elektron.ronca@gmail.com' +__status__: str = 'Updated' + + +def run_coverage(pro_name: str) -> str: + ''' + Runs coverage for project and generates report. + + :param pro_name: Project name + :type pro_name: + :exceptions: ATSTypeError | ATSFileError + ''' + checker: ATSChecker = ATSChecker() + error_msg: Optional[str] = None + error_id: Optional[int] = None + error_msg, error_id = checker.check_params([('str:pro_name', pro_name)]) + if error_id == checker.TYPE_ERROR: + raise ATSTypeError(error_msg) + if not exists(f'../{pro_name}'): + raise ATSFileError(f'missing ../{pro_name}') + cov = Coverage(source=[f'../{pro_name}']) + cov.start() + tests: TestSuite = TestLoader().discover('.', pattern='*_test.py') + test_runner = TextTestRunner(verbosity=2) + test_runner.run(tests) + cov.stop() + cov.save() + report_file_name: str = f'{pro_name}_coverage.json' + cov.json_report(outfile=report_file_name) + success_message([f'\nats_coverage: generated coverage {report_file_name}']) + return report_file_name + + +def load_report(report_file_path: str) -> Dict[str, Any]: + ''' + Loads report from report file. + + :param report_file_path: Report file path + :type report_file_path: + :exceptions: ATSTypeError | ATSFileError + ''' + checker: ATSChecker = ATSChecker() + error_msg: Optional[str] = None + error_id: Optional[int] = None + error_msg, error_id = checker.check_params([( + 'str:report_file_path', report_file_path + )]) + if error_id == checker.TYPE_ERROR: + raise ATSTypeError(error_msg) + if not exists(report_file_path): + raise ATSFileError(f'{report_file_path} does not exist.') + data: Dict[str, Any] = {} + with open(report_file_path, 'r', encoding='utf-8') as loaded_file: + data = load(loaded_file) + return data + + +def find_root_package(module_path: str) -> Optional[Path]: + ''' + Finds root package for project structure. + + :param module_path: Absolute path + :type module_path: + :exceptions: ATSTypeError + ''' + checker: ATSChecker = ATSChecker() + error_msg: Optional[str] = None + error_id: Optional[int] = None + error_msg, error_id = checker.check_params([( + 'str:module_path', module_path + )]) + if error_id == checker.TYPE_ERROR: + raise ATSTypeError(error_msg) + root: Optional[Path] = None + path: Path = Path(module_path).resolve() + while path.parent != path: + if (path / '__init__.py').exists(): + root = path + path = path.parent + return root + + +def update_readme(coverage: Dict[str, Any]) -> None: + ''' + Updates README.md file with code coverage report table. + + :param coverage: Coverage data report + :type coverage: + :exceptions: ATSTypeError + ''' + checker: ATSChecker = ATSChecker() + error_msg: Optional[str] = None + error_id: Optional[int] = None + error_msg, error_id = checker.check_params([('dict:coverage', coverage)]) + if error_id == checker.TYPE_ERROR: + raise ATSTypeError(error_msg) + readme_path: str = '../README.md' + start_marker: str = '### Code coverage' + end_marker: str = '### Docs' + lines: List[str] = [] + with open(readme_path, 'r', encoding='utf-8') as current_file: + lines = current_file.readlines() + new_lines: List[str] = [] + inside_block: bool = False + stmts: str = 'num_statements' + miss: str = 'missing_lines' + cover: str = 'percent_covered_display' + for line in lines: + if start_marker in line: + inside_block = True + new_lines.append(line) + new_lines.append('\n') + new_lines.append('| Name | Stmts | Miss | Cover |\n') + new_lines.append('|------|-------|------|-------|\n') + file_names: List[str] = coverage['files'] + for name in file_names: + root_package: Optional[Path] = find_root_package(name) + module: str = '' + if name.startswith(str(root_package)): + result: str = name[len(str(root_package)):] + result = result.lstrip('/') + module = f'{basename(str(root_package))}/{result}' + file_summary: Dict[str, Any] = coverage['files'][name] + statements: str = file_summary['summary'][stmts] + missing: str = file_summary['summary'][miss] + covered: str = file_summary['summary'][cover] + new_lines.append( + f'| `{module}` | {statements} | {missing} | {covered}%|\n' + ) + total: str = '| **Total** |' + total_statements: str = coverage['totals'][stmts] + total_missing: str = coverage['totals'][miss] + total_covered: str = coverage['totals'][cover] + total += f' {total_statements} |' + total += f' {total_missing} |' + total += f' {total_covered}% |\n' + new_lines.append(total) + continue + elif end_marker in line: + inside_block = False + new_lines.append('\n') + new_lines.append(line) + continue + if not inside_block: + new_lines.append(line) + with open(readme_path, 'w', encoding='utf-8') as update_file: + update_file.writelines(new_lines) + + +if __name__ == "__main__": + cli: ATSOptionParser = ATSOptionParser( + 'ats_coverage 2025', '1.0.0', 'GPLv3', False + ) + cli.add_operation( + '-n', '--name', dest='name', + help='generate coverage report for project (provide name)' + ) + args: Namespace = cli.parse_args(sys.argv) + if not bool(getattr(args, "name")): + error_message(['ats_coverage: missing name argument']) + sys.exit(127) + try: + pro_report_file: str = f'{getattr(args, "name")}_coverage.json' + report_data: Dict[str, Any] = load_report(pro_report_file) + update_readme(report_data) + except (ATSTypeError, ATSFileError) as e: + error_message([f'ats_coverage: {e}']) + sys.exit(128) diff --git a/tests/daemon_test.py b/tests/daemon_test.py index 85768d6..5b1e32e 100644 --- a/tests/daemon_test.py +++ b/tests/daemon_test.py @@ -4,7 +4,7 @@ Module daemon_test.py Copyright - Copyright (C) 2020 - 2024 Vladimir Roncevic + Copyright (C) 2020 - 2025 Vladimir Roncevic daemonpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -35,14 +35,14 @@ # Force close python test ################################################# sys.exit(f'\n{__file__}\n{test_error_message}\n') -__author__ = 'Vladimir Roncevic' -__copyright__ = '(C) 2024, https://vroncevic.github.io/daemonpy' +__author__: str = 'Vladimir Roncevic' +__copyright__: str = '(C) 2025, https://vroncevic.github.io/daemonpy' __credits__: List[str] = ['Vladimir Roncevic', 'Python Software Foundation'] -__license__ = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' -__version__ = '2.0.5' -__maintainer__ = 'Vladimir Roncevic' -__email__ = 'elektron.ronca@gmail.com' -__status__ = 'Updated' +__license__: str = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' +__version__: str = '2.0.6' +__maintainer__: str = 'Vladimir Roncevic' +__email__: str = 'elektron.ronca@gmail.com' +__status__: str = 'Updated' class MyDaemon(Daemon): diff --git a/tests/daemon_usage_test.py b/tests/daemon_usage_test.py index ac256ab..55f7b94 100644 --- a/tests/daemon_usage_test.py +++ b/tests/daemon_usage_test.py @@ -4,7 +4,7 @@ Module daemon_usage_test.py Copyright - Copyright (C) 2020 - 2024 Vladimir Roncevic + Copyright (C) 2020 - 2025 Vladimir Roncevic daemonpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -34,14 +34,14 @@ # Force close python test ################################################# sys.exit(f'\n{__file__}\n{test_error_message}\n') -__author__ = 'Vladimir Roncevic' -__copyright__ = '(C) 2024, https://vroncevic.github.io/daemonpy' +__author__: str = 'Vladimir Roncevic' +__copyright__: str = '(C) 2025, https://vroncevic.github.io/daemonpy' __credits__: List[str] = ['Vladimir Roncevic', 'Python Software Foundation'] -__license__ = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' -__version__ = '2.0.5' -__maintainer__ = 'Vladimir Roncevic' -__email__ = 'elektron.ronca@gmail.com' -__status__ = 'Updated' +__license__: str = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' +__version__: str = '2.0.6' +__maintainer__: str = 'Vladimir Roncevic' +__email__: str = 'elektron.ronca@gmail.com' +__status__: str = 'Updated' class DaemonUsageTestCase(unittest.TestCase): diff --git a/tests/daemonpy_coverage.json b/tests/daemonpy_coverage.json new file mode 100644 index 0000000..3d69ce4 --- /dev/null +++ b/tests/daemonpy_coverage.json @@ -0,0 +1 @@ +{"meta": {"format": 3, "version": "7.6.10", "timestamp": "2025-11-03T06:53:11.200323", "branch_coverage": false, "show_contexts": false}, "files": {"/data/dev/python/daemonpy/github/daemonpy/daemonpy/__init__.py": {"executed_lines": [3, 23, 24, 25, 26, 27, 28, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 44, 45, 46, 47, 48, 49, 50, 51, 54, 55, 75, 77, 87, 88, 89, 90, 91, 92, 94, 96, 97, 98, 99, 100, 101, 103, 113, 114, 115, 116, 119, 120, 121, 122, 138, 176, 203, 225, 246, 266, 267], "summary": {"covered_lines": 56, "num_statements": 135, "percent_covered": 41.48148148148148, "percent_covered_display": "41", "missing_lines": 79, "excluded_lines": 2}, "missing_lines": [93, 95, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 135, 136, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 186, 187, 188, 189, 190, 191, 192, 193, 198, 199, 200, 201, 213, 214, 215, 216, 217, 218, 219, 220, 222, 223, 235, 236, 237, 238, 239, 241, 243, 244, 254, 255, 256, 258, 259, 262, 264], "excluded_lines": [40, 42], "functions": {"Daemon.__init__": {"executed_lines": [87, 88, 89, 90, 91, 92, 94, 96, 97, 98, 99, 100, 101], "summary": {"covered_lines": 13, "num_statements": 15, "percent_covered": 86.66666666666667, "percent_covered_display": "87", "missing_lines": 2, "excluded_lines": 0}, "missing_lines": [93, 95], "excluded_lines": []}, "Daemon.usage": {"executed_lines": [113, 114, 115, 116, 119, 120, 121, 122], "summary": {"covered_lines": 8, "num_statements": 21, "percent_covered": 38.095238095238095, "percent_covered_display": "38", "missing_lines": 13, "excluded_lines": 0}, "missing_lines": [123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 135, 136], "excluded_lines": []}, "Daemon.daemonize": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 27, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 27, "excluded_lines": 0}, "missing_lines": [146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174], "excluded_lines": []}, "Daemon.start": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0}, "missing_lines": [186, 187, 188, 189, 190, 191, 192, 193, 198, 199, 200, 201], "excluded_lines": []}, "Daemon.stop": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 10, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 10, "excluded_lines": 0}, "missing_lines": [213, 214, 215, 216, 217, 218, 219, 220, 222, 223], "excluded_lines": []}, "Daemon.restart": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 8, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 8, "excluded_lines": 0}, "missing_lines": [235, 236, 237, 238, 239, 241, 243, 244], "excluded_lines": []}, "Daemon.exit_handler": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 7, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 7, "excluded_lines": 0}, "missing_lines": [254, 255, 256, 258, 259, 262, 264], "excluded_lines": []}, "Daemon.run": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "": {"executed_lines": [3, 23, 24, 25, 26, 27, 28, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 44, 45, 46, 47, 48, 49, 50, 51, 54, 55, 75, 77, 103, 138, 176, 203, 225, 246, 266, 267], "summary": {"covered_lines": 35, "num_statements": 35, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 2}, "missing_lines": [], "excluded_lines": [40, 42]}}, "classes": {"Daemon": {"executed_lines": [87, 88, 89, 90, 91, 92, 94, 96, 97, 98, 99, 100, 101, 113, 114, 115, 116, 119, 120, 121, 122], "summary": {"covered_lines": 21, "num_statements": 100, "percent_covered": 21.0, "percent_covered_display": "21", "missing_lines": 79, "excluded_lines": 0}, "missing_lines": [93, 95, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 135, 136, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 186, 187, 188, 189, 190, 191, 192, 193, 198, 199, 200, 201, 213, 214, 215, 216, 217, 218, 219, 220, 222, 223, 235, 236, 237, 238, 239, 241, 243, 244, 254, 255, 256, 258, 259, 262, 264], "excluded_lines": []}, "": {"executed_lines": [3, 23, 24, 25, 26, 27, 28, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 44, 45, 46, 47, 48, 49, 50, 51, 54, 55, 75, 77, 103, 138, 176, 203, 225, 246, 266, 267], "summary": {"covered_lines": 35, "num_statements": 35, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 2}, "missing_lines": [], "excluded_lines": [40, 42]}}}, "/data/dev/python/daemonpy/github/daemonpy/daemonpy/daemon_usage.py": {"executed_lines": [3, 23, 24, 26, 27, 28, 29, 30, 31, 36, 37, 38, 39, 40, 41, 42, 43, 46, 47, 63, 64, 66, 74, 75, 77, 78, 86, 88, 89, 97, 99, 109, 110, 111, 112, 115, 116, 117, 118, 119, 120, 121, 122, 123, 129], "summary": {"covered_lines": 43, "num_statements": 45, "percent_covered": 95.55555555555556, "percent_covered_display": "96", "missing_lines": 2, "excluded_lines": 2}, "missing_lines": [124, 125], "excluded_lines": [32, 34], "functions": {"DaemonUsage.__init__": {"executed_lines": [74, 75], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "DaemonUsage.usage_status": {"executed_lines": [97], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "DaemonUsage.check": {"executed_lines": [109, 110, 111, 112, 115, 116, 117, 118, 119, 120, 121, 122, 123, 129], "summary": {"covered_lines": 14, "num_statements": 16, "percent_covered": 87.5, "percent_covered_display": "88", "missing_lines": 2, "excluded_lines": 0}, "missing_lines": [124, 125], "excluded_lines": []}, "": {"executed_lines": [3, 23, 24, 26, 27, 28, 29, 30, 31, 36, 37, 38, 39, 40, 41, 42, 43, 46, 47, 63, 64, 66, 77, 78, 88, 89, 99], "summary": {"covered_lines": 25, "num_statements": 25, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 2}, "missing_lines": [], "excluded_lines": [32, 34]}}, "classes": {"DaemonUsage": {"executed_lines": [74, 75, 86, 97, 109, 110, 111, 112, 115, 116, 117, 118, 119, 120, 121, 122, 123, 129], "summary": {"covered_lines": 18, "num_statements": 20, "percent_covered": 90.0, "percent_covered_display": "90", "missing_lines": 2, "excluded_lines": 0}, "missing_lines": [124, 125], "excluded_lines": []}, "": {"executed_lines": [3, 23, 24, 26, 27, 28, 29, 30, 31, 36, 37, 38, 39, 40, 41, 42, 43, 46, 47, 63, 64, 66, 77, 78, 88, 89, 99], "summary": {"covered_lines": 25, "num_statements": 25, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 2}, "missing_lines": [], "excluded_lines": [32, 34]}}}, "/data/dev/python/daemonpy/github/daemonpy/daemonpy/file_descriptor.py": {"executed_lines": [3, 23, 24, 26, 27, 28, 29, 34, 35, 36, 37, 38, 39, 40, 41, 44, 45, 66, 67, 68, 69, 70, 74, 84, 85, 86, 87, 90, 91, 92, 93, 94, 95, 96, 97, 98, 100, 108, 109, 110, 123, 125, 131, 132, 133], "summary": {"covered_lines": 43, "num_statements": 47, "percent_covered": 91.48936170212765, "percent_covered_display": "91", "missing_lines": 4, "excluded_lines": 2}, "missing_lines": [114, 115, 116, 117], "excluded_lines": [30, 32], "functions": {"FileDescriptor.__init__": {"executed_lines": [84, 85, 86, 87, 90, 91, 92, 93, 94, 95, 96, 97, 98], "summary": {"covered_lines": 13, "num_statements": 13, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "FileDescriptor.__enter__": {"executed_lines": [108, 109, 110, 123], "summary": {"covered_lines": 4, "num_statements": 8, "percent_covered": 50.0, "percent_covered_display": "50", "missing_lines": 4, "excluded_lines": 0}, "missing_lines": [114, 115, 116, 117], "excluded_lines": []}, "FileDescriptor.__exit__": {"executed_lines": [131, 132, 133], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "": {"executed_lines": [3, 23, 24, 26, 27, 28, 29, 34, 35, 36, 37, 38, 39, 40, 41, 44, 45, 66, 67, 68, 69, 70, 74, 100, 125], "summary": {"covered_lines": 23, "num_statements": 23, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 2}, "missing_lines": [], "excluded_lines": [30, 32]}}, "classes": {"FileDescriptor": {"executed_lines": [84, 85, 86, 87, 90, 91, 92, 93, 94, 95, 96, 97, 98, 108, 109, 110, 123, 131, 132, 133], "summary": {"covered_lines": 20, "num_statements": 24, "percent_covered": 83.33333333333333, "percent_covered_display": "83", "missing_lines": 4, "excluded_lines": 0}, "missing_lines": [114, 115, 116, 117], "excluded_lines": []}, "": {"executed_lines": [3, 23, 24, 26, 27, 28, 29, 34, 35, 36, 37, 38, 39, 40, 41, 44, 45, 66, 67, 68, 69, 70, 74, 100, 125], "summary": {"covered_lines": 23, "num_statements": 23, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 2}, "missing_lines": [], "excluded_lines": [30, 32]}}}, "/data/dev/python/daemonpy/github/daemonpy/daemonpy/file_process_id.py": {"executed_lines": [3, 23, 24, 26, 27, 28, 29, 34, 35, 36, 37, 38, 39, 40, 41, 44, 45, 63, 64, 66, 78, 79, 80, 81, 84, 85, 86, 87, 88, 89, 90, 91, 92, 94, 102, 103, 106, 108, 114, 115, 116], "summary": {"covered_lines": 39, "num_statements": 39, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 2}, "missing_lines": [], "excluded_lines": [30, 32], "functions": {"FileProcessId.__init__": {"executed_lines": [78, 79, 80, 81, 84, 85, 86, 87, 88, 89, 90, 91, 92], "summary": {"covered_lines": 13, "num_statements": 13, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "FileProcessId.__enter__": {"executed_lines": [102, 103, 106], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "FileProcessId.__exit__": {"executed_lines": [114, 115, 116], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "": {"executed_lines": [3, 23, 24, 26, 27, 28, 29, 34, 35, 36, 37, 38, 39, 40, 41, 44, 45, 63, 64, 66, 94, 108], "summary": {"covered_lines": 20, "num_statements": 20, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 2}, "missing_lines": [], "excluded_lines": [30, 32]}}, "classes": {"FileProcessId": {"executed_lines": [78, 79, 80, 81, 84, 85, 86, 87, 88, 89, 90, 91, 92, 102, 103, 106, 114, 115, 116], "summary": {"covered_lines": 19, "num_statements": 19, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "": {"executed_lines": [3, 23, 24, 26, 27, 28, 29, 34, 35, 36, 37, 38, 39, 40, 41, 44, 45, 63, 64, 66, 94, 108], "summary": {"covered_lines": 20, "num_statements": 20, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 2}, "missing_lines": [], "excluded_lines": [30, 32]}}}, "/data/dev/python/daemonpy/github/daemonpy/daemonpy/unix_operations.py": {"executed_lines": [3, 23, 24, 25, 26, 27, 28, 30, 31, 32, 33, 34, 35, 40, 41, 42, 43, 44, 45, 46, 47, 50, 51, 70, 71, 72, 73, 75, 83, 86, 90, 91, 99, 101, 102, 110, 112, 126, 140], "summary": {"covered_lines": 37, "num_statements": 72, "percent_covered": 51.388888888888886, "percent_covered_display": "51", "missing_lines": 35, "excluded_lines": 2}, "missing_lines": [121, 122, 123, 124, 135, 136, 137, 138, 156, 157, 158, 159, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 174, 175, 176, 177, 178, 179, 180, 181, 182, 191, 192, 194, 195], "excluded_lines": [36, 38], "functions": {"UnixOperations.__init__": {"executed_lines": [83, 86], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "UnixOperations.unix_status": {"executed_lines": [110], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "UnixOperations.first_fork": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 4, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 4, "excluded_lines": 0}, "missing_lines": [121, 122, 123, 124], "excluded_lines": []}, "UnixOperations.second_fork": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 4, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 4, "excluded_lines": 0}, "missing_lines": [135, 136, 137, 138], "excluded_lines": []}, "UnixOperations.unix_kill": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 27, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 27, "excluded_lines": 0}, "missing_lines": [156, 157, 158, 159, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 174, 175, 176, 177, 178, 179, 180, 181, 182, 191, 192, 194, 195], "excluded_lines": []}, "": {"executed_lines": [3, 23, 24, 25, 26, 27, 28, 30, 31, 32, 33, 34, 35, 40, 41, 42, 43, 44, 45, 46, 47, 50, 51, 70, 71, 72, 73, 75, 90, 91, 101, 102, 112, 126, 140], "summary": {"covered_lines": 33, "num_statements": 33, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 2}, "missing_lines": [], "excluded_lines": [36, 38]}}, "classes": {"UnixOperations": {"executed_lines": [83, 86, 99, 110], "summary": {"covered_lines": 4, "num_statements": 39, "percent_covered": 10.256410256410257, "percent_covered_display": "10", "missing_lines": 35, "excluded_lines": 0}, "missing_lines": [121, 122, 123, 124, 135, 136, 137, 138, 156, 157, 158, 159, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 174, 175, 176, 177, 178, 179, 180, 181, 182, 191, 192, 194, 195], "excluded_lines": []}, "": {"executed_lines": [3, 23, 24, 25, 26, 27, 28, 30, 31, 32, 33, 34, 35, 40, 41, 42, 43, 44, 45, 46, 47, 50, 51, 70, 71, 72, 73, 75, 90, 91, 101, 102, 112, 126, 140], "summary": {"covered_lines": 33, "num_statements": 33, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 2}, "missing_lines": [], "excluded_lines": [36, 38]}}}}, "totals": {"covered_lines": 218, "num_statements": 338, "percent_covered": 64.49704142011835, "percent_covered_display": "64", "missing_lines": 120, "excluded_lines": 10}} \ No newline at end of file diff --git a/tests/daemonpy_coverage.xml b/tests/daemonpy_coverage.xml new file mode 100644 index 0000000..fb5f545 --- /dev/null +++ b/tests/daemonpy_coverage.xml @@ -0,0 +1,377 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/file_descriptor_test.py b/tests/file_descriptor_test.py index cfce9c9..6aa4d2f 100644 --- a/tests/file_descriptor_test.py +++ b/tests/file_descriptor_test.py @@ -34,14 +34,14 @@ # Force close python test ################################################# sys.exit(f'\n{__file__}\n{test_error_message}\n') -__author__ = 'Vladimir Roncevic' -__copyright__ = '(C) 2024, https://vroncevic.github.io/daemonpy' +__author__: str = 'Vladimir Roncevic' +__copyright__: str = '(C) 2025, https://vroncevic.github.io/daemonpy' __credits__: List[str] = ['Vladimir Roncevic', 'Python Software Foundation'] -__license__ = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' -__version__ = '2.0.5' -__maintainer__ = 'Vladimir Roncevic' -__email__ = 'elektron.ronca@gmail.com' -__status__ = 'Updated' +__license__: str = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' +__version__: str = '2.0.6' +__maintainer__: str = 'Vladimir Roncevic' +__email__: str = 'elektron.ronca@gmail.com' +__status__: str = 'Updated' class FileDescriptorTestCase(unittest.TestCase): diff --git a/tests/file_process_id_test.py b/tests/file_process_id_test.py index 2048343..f9e60cb 100644 --- a/tests/file_process_id_test.py +++ b/tests/file_process_id_test.py @@ -4,7 +4,7 @@ Module file_process_id_test.py Copyright - Copyright (C) 2020 - 2024 Vladimir Roncevic + Copyright (C) 2020 - 2025 Vladimir Roncevic daemonpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -34,14 +34,14 @@ # Force close python test ################################################# sys.exit(f'\n{__file__}\n{test_error_message}\n') -__author__ = 'Vladimir Roncevic' -__copyright__ = '(C) 2024, https://vroncevic.github.io/daemonpy' +__author__: str = 'Vladimir Roncevic' +__copyright__: str = '(C) 2025, https://vroncevic.github.io/daemonpy' __credits__: List[str] = ['Vladimir Roncevic', 'Python Software Foundation'] -__license__ = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' -__version__ = '2.0.5' -__maintainer__ = 'Vladimir Roncevic' -__email__ = 'elektron.ronca@gmail.com' -__status__ = 'Updated' +__license__: str = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' +__version__: str = '2.0.6' +__maintainer__: str = 'Vladimir Roncevic' +__email__: str = 'elektron.ronca@gmail.com' +__status__: str = 'Updated' class FileProcessIdTestCase(unittest.TestCase): diff --git a/tests/htmlcov/class_index.html b/tests/htmlcov/class_index.html new file mode 100644 index 0000000..5468652 --- /dev/null +++ b/tests/htmlcov/class_index.html @@ -0,0 +1,187 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 64% +

+ +
+ +
+ + +
+
+

+ Files + Functions + Classes +

+

+ coverage.py v7.6.10, + created at 2025-11-03 06:53 +0100 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Fileclassstatementsmissingexcludedcoverage
/data/dev/python/daemonpy/github/daemonpy/daemonpy/__init__.pyDaemon10079021%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/__init__.py(no class)3502100%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/daemon_usage.pyDaemonUsage202090%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/daemon_usage.py(no class)2502100%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/file_descriptor.pyFileDescriptor244083%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/file_descriptor.py(no class)2302100%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/file_process_id.pyFileProcessId1900100%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/file_process_id.py(no class)2002100%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/unix_operations.pyUnixOperations3935010%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/unix_operations.py(no class)3302100%
Total 3381201064%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/tests/htmlcov/coverage_html_cb_6fb7b396.js b/tests/htmlcov/coverage_html_cb_6fb7b396.js new file mode 100644 index 0000000..1face13 --- /dev/null +++ b/tests/htmlcov/coverage_html_cb_6fb7b396.js @@ -0,0 +1,733 @@ +// Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +// For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +// Coverage.py HTML report browser code. +/*jslint browser: true, sloppy: true, vars: true, plusplus: true, maxerr: 50, indent: 4 */ +/*global coverage: true, document, window, $ */ + +coverage = {}; + +// General helpers +function debounce(callback, wait) { + let timeoutId = null; + return function(...args) { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => { + callback.apply(this, args); + }, wait); + }; +}; + +function checkVisible(element) { + const rect = element.getBoundingClientRect(); + const viewBottom = Math.max(document.documentElement.clientHeight, window.innerHeight); + const viewTop = 30; + return !(rect.bottom < viewTop || rect.top >= viewBottom); +} + +function on_click(sel, fn) { + const elt = document.querySelector(sel); + if (elt) { + elt.addEventListener("click", fn); + } +} + +// Helpers for table sorting +function getCellValue(row, column = 0) { + const cell = row.cells[column] // nosemgrep: eslint.detect-object-injection + if (cell.childElementCount == 1) { + var child = cell.firstElementChild; + if (child.tagName === "A") { + child = child.firstElementChild; + } + if (child instanceof HTMLDataElement && child.value) { + return child.value; + } + } + return cell.innerText || cell.textContent; +} + +function rowComparator(rowA, rowB, column = 0) { + let valueA = getCellValue(rowA, column); + let valueB = getCellValue(rowB, column); + if (!isNaN(valueA) && !isNaN(valueB)) { + return valueA - valueB; + } + return valueA.localeCompare(valueB, undefined, {numeric: true}); +} + +function sortColumn(th) { + // Get the current sorting direction of the selected header, + // clear state on other headers and then set the new sorting direction. + const currentSortOrder = th.getAttribute("aria-sort"); + [...th.parentElement.cells].forEach(header => header.setAttribute("aria-sort", "none")); + var direction; + if (currentSortOrder === "none") { + direction = th.dataset.defaultSortOrder || "ascending"; + } + else if (currentSortOrder === "ascending") { + direction = "descending"; + } + else { + direction = "ascending"; + } + th.setAttribute("aria-sort", direction); + + const column = [...th.parentElement.cells].indexOf(th) + + // Sort all rows and afterwards append them in order to move them in the DOM. + Array.from(th.closest("table").querySelectorAll("tbody tr")) + .sort((rowA, rowB) => rowComparator(rowA, rowB, column) * (direction === "ascending" ? 1 : -1)) + .forEach(tr => tr.parentElement.appendChild(tr)); + + // Save the sort order for next time. + if (th.id !== "region") { + let th_id = "file"; // Sort by file if we don't have a column id + let current_direction = direction; + const stored_list = localStorage.getItem(coverage.INDEX_SORT_STORAGE); + if (stored_list) { + ({th_id, direction} = JSON.parse(stored_list)) + } + localStorage.setItem(coverage.INDEX_SORT_STORAGE, JSON.stringify({ + "th_id": th.id, + "direction": current_direction + })); + if (th.id !== th_id || document.getElementById("region")) { + // Sort column has changed, unset sorting by function or class. + localStorage.setItem(coverage.SORTED_BY_REGION, JSON.stringify({ + "by_region": false, + "region_direction": current_direction + })); + } + } + else { + // Sort column has changed to by function or class, remember that. + localStorage.setItem(coverage.SORTED_BY_REGION, JSON.stringify({ + "by_region": true, + "region_direction": direction + })); + } +} + +// Find all the elements with data-shortcut attribute, and use them to assign a shortcut key. +coverage.assign_shortkeys = function () { + document.querySelectorAll("[data-shortcut]").forEach(element => { + document.addEventListener("keypress", event => { + if (event.target.tagName.toLowerCase() === "input") { + return; // ignore keypress from search filter + } + if (event.key === element.dataset.shortcut) { + element.click(); + } + }); + }); +}; + +// Create the events for the filter box. +coverage.wire_up_filter = function () { + // Populate the filter and hide100 inputs if there are saved values for them. + const saved_filter_value = localStorage.getItem(coverage.FILTER_STORAGE); + if (saved_filter_value) { + document.getElementById("filter").value = saved_filter_value; + } + const saved_hide100_value = localStorage.getItem(coverage.HIDE100_STORAGE); + if (saved_hide100_value) { + document.getElementById("hide100").checked = JSON.parse(saved_hide100_value); + } + + // Cache elements. + const table = document.querySelector("table.index"); + const table_body_rows = table.querySelectorAll("tbody tr"); + const no_rows = document.getElementById("no_rows"); + + // Observe filter keyevents. + const filter_handler = (event => { + // Keep running total of each metric, first index contains number of shown rows + const totals = new Array(table.rows[0].cells.length).fill(0); + // Accumulate the percentage as fraction + totals[totals.length - 1] = { "numer": 0, "denom": 0 }; // nosemgrep: eslint.detect-object-injection + + var text = document.getElementById("filter").value; + // Store filter value + localStorage.setItem(coverage.FILTER_STORAGE, text); + const casefold = (text === text.toLowerCase()); + const hide100 = document.getElementById("hide100").checked; + // Store hide value. + localStorage.setItem(coverage.HIDE100_STORAGE, JSON.stringify(hide100)); + + // Hide / show elements. + table_body_rows.forEach(row => { + var show = false; + // Check the text filter. + for (let column = 0; column < totals.length; column++) { + cell = row.cells[column]; + if (cell.classList.contains("name")) { + var celltext = cell.textContent; + if (casefold) { + celltext = celltext.toLowerCase(); + } + if (celltext.includes(text)) { + show = true; + } + } + } + + // Check the "hide covered" filter. + if (show && hide100) { + const [numer, denom] = row.cells[row.cells.length - 1].dataset.ratio.split(" "); + show = (numer !== denom); + } + + if (!show) { + // hide + row.classList.add("hidden"); + return; + } + + // show + row.classList.remove("hidden"); + totals[0]++; + + for (let column = 0; column < totals.length; column++) { + // Accumulate dynamic totals + cell = row.cells[column] // nosemgrep: eslint.detect-object-injection + if (cell.classList.contains("name")) { + continue; + } + if (column === totals.length - 1) { + // Last column contains percentage + const [numer, denom] = cell.dataset.ratio.split(" "); + totals[column]["numer"] += parseInt(numer, 10); // nosemgrep: eslint.detect-object-injection + totals[column]["denom"] += parseInt(denom, 10); // nosemgrep: eslint.detect-object-injection + } + else { + totals[column] += parseInt(cell.textContent, 10); // nosemgrep: eslint.detect-object-injection + } + } + }); + + // Show placeholder if no rows will be displayed. + if (!totals[0]) { + // Show placeholder, hide table. + no_rows.style.display = "block"; + table.style.display = "none"; + return; + } + + // Hide placeholder, show table. + no_rows.style.display = null; + table.style.display = null; + + const footer = table.tFoot.rows[0]; + // Calculate new dynamic sum values based on visible rows. + for (let column = 0; column < totals.length; column++) { + // Get footer cell element. + const cell = footer.cells[column]; // nosemgrep: eslint.detect-object-injection + if (cell.classList.contains("name")) { + continue; + } + + // Set value into dynamic footer cell element. + if (column === totals.length - 1) { + // Percentage column uses the numerator and denominator, + // and adapts to the number of decimal places. + const match = /\.([0-9]+)/.exec(cell.textContent); + const places = match ? match[1].length : 0; + const { numer, denom } = totals[column]; // nosemgrep: eslint.detect-object-injection + cell.dataset.ratio = `${numer} ${denom}`; + // Check denom to prevent NaN if filtered files contain no statements + cell.textContent = denom + ? `${(numer * 100 / denom).toFixed(places)}%` + : `${(100).toFixed(places)}%`; + } + else { + cell.textContent = totals[column]; // nosemgrep: eslint.detect-object-injection + } + } + }); + + document.getElementById("filter").addEventListener("input", debounce(filter_handler)); + document.getElementById("hide100").addEventListener("input", debounce(filter_handler)); + + // Trigger change event on setup, to force filter on page refresh + // (filter value may still be present). + document.getElementById("filter").dispatchEvent(new Event("input")); + document.getElementById("hide100").dispatchEvent(new Event("input")); +}; +coverage.FILTER_STORAGE = "COVERAGE_FILTER_VALUE"; +coverage.HIDE100_STORAGE = "COVERAGE_HIDE100_VALUE"; + +// Set up the click-to-sort columns. +coverage.wire_up_sorting = function () { + document.querySelectorAll("[data-sortable] th[aria-sort]").forEach( + th => th.addEventListener("click", e => sortColumn(e.target)) + ); + + // Look for a localStorage item containing previous sort settings: + let th_id = "file", direction = "ascending"; + const stored_list = localStorage.getItem(coverage.INDEX_SORT_STORAGE); + if (stored_list) { + ({th_id, direction} = JSON.parse(stored_list)); + } + let by_region = false, region_direction = "ascending"; + const sorted_by_region = localStorage.getItem(coverage.SORTED_BY_REGION); + if (sorted_by_region) { + ({ + by_region, + region_direction + } = JSON.parse(sorted_by_region)); + } + + const region_id = "region"; + if (by_region && document.getElementById(region_id)) { + direction = region_direction; + } + // If we are in a page that has a column with id of "region", sort on + // it if the last sort was by function or class. + let th; + if (document.getElementById(region_id)) { + th = document.getElementById(by_region ? region_id : th_id); + } + else { + th = document.getElementById(th_id); + } + th.setAttribute("aria-sort", direction === "ascending" ? "descending" : "ascending"); + th.click() +}; + +coverage.INDEX_SORT_STORAGE = "COVERAGE_INDEX_SORT_2"; +coverage.SORTED_BY_REGION = "COVERAGE_SORT_REGION"; + +// Loaded on index.html +coverage.index_ready = function () { + coverage.assign_shortkeys(); + coverage.wire_up_filter(); + coverage.wire_up_sorting(); + + on_click(".button_prev_file", coverage.to_prev_file); + on_click(".button_next_file", coverage.to_next_file); + + on_click(".button_show_hide_help", coverage.show_hide_help); +}; + +// -- pyfile stuff -- + +coverage.LINE_FILTERS_STORAGE = "COVERAGE_LINE_FILTERS"; + +coverage.pyfile_ready = function () { + // If we're directed to a particular line number, highlight the line. + var frag = location.hash; + if (frag.length > 2 && frag[1] === "t") { + document.querySelector(frag).closest(".n").classList.add("highlight"); + coverage.set_sel(parseInt(frag.substr(2), 10)); + } + else { + coverage.set_sel(0); + } + + on_click(".button_toggle_run", coverage.toggle_lines); + on_click(".button_toggle_mis", coverage.toggle_lines); + on_click(".button_toggle_exc", coverage.toggle_lines); + on_click(".button_toggle_par", coverage.toggle_lines); + + on_click(".button_next_chunk", coverage.to_next_chunk_nicely); + on_click(".button_prev_chunk", coverage.to_prev_chunk_nicely); + on_click(".button_top_of_page", coverage.to_top); + on_click(".button_first_chunk", coverage.to_first_chunk); + + on_click(".button_prev_file", coverage.to_prev_file); + on_click(".button_next_file", coverage.to_next_file); + on_click(".button_to_index", coverage.to_index); + + on_click(".button_show_hide_help", coverage.show_hide_help); + + coverage.filters = undefined; + try { + coverage.filters = localStorage.getItem(coverage.LINE_FILTERS_STORAGE); + } catch(err) {} + + if (coverage.filters) { + coverage.filters = JSON.parse(coverage.filters); + } + else { + coverage.filters = {run: false, exc: true, mis: true, par: true}; + } + + for (cls in coverage.filters) { + coverage.set_line_visibilty(cls, coverage.filters[cls]); // nosemgrep: eslint.detect-object-injection + } + + coverage.assign_shortkeys(); + coverage.init_scroll_markers(); + coverage.wire_up_sticky_header(); + + document.querySelectorAll("[id^=ctxs]").forEach( + cbox => cbox.addEventListener("click", coverage.expand_contexts) + ); + + // Rebuild scroll markers when the window height changes. + window.addEventListener("resize", coverage.build_scroll_markers); +}; + +coverage.toggle_lines = function (event) { + const btn = event.target.closest("button"); + const category = btn.value + const show = !btn.classList.contains("show_" + category); + coverage.set_line_visibilty(category, show); + coverage.build_scroll_markers(); + coverage.filters[category] = show; + try { + localStorage.setItem(coverage.LINE_FILTERS_STORAGE, JSON.stringify(coverage.filters)); + } catch(err) {} +}; + +coverage.set_line_visibilty = function (category, should_show) { + const cls = "show_" + category; + const btn = document.querySelector(".button_toggle_" + category); + if (btn) { + if (should_show) { + document.querySelectorAll("#source ." + category).forEach(e => e.classList.add(cls)); + btn.classList.add(cls); + } + else { + document.querySelectorAll("#source ." + category).forEach(e => e.classList.remove(cls)); + btn.classList.remove(cls); + } + } +}; + +// Return the nth line div. +coverage.line_elt = function (n) { + return document.getElementById("t" + n)?.closest("p"); +}; + +// Set the selection. b and e are line numbers. +coverage.set_sel = function (b, e) { + // The first line selected. + coverage.sel_begin = b; + // The next line not selected. + coverage.sel_end = (e === undefined) ? b+1 : e; +}; + +coverage.to_top = function () { + coverage.set_sel(0, 1); + coverage.scroll_window(0); +}; + +coverage.to_first_chunk = function () { + coverage.set_sel(0, 1); + coverage.to_next_chunk(); +}; + +coverage.to_prev_file = function () { + window.location = document.getElementById("prevFileLink").href; +} + +coverage.to_next_file = function () { + window.location = document.getElementById("nextFileLink").href; +} + +coverage.to_index = function () { + location.href = document.getElementById("indexLink").href; +} + +coverage.show_hide_help = function () { + const helpCheck = document.getElementById("help_panel_state") + helpCheck.checked = !helpCheck.checked; +} + +// Return a string indicating what kind of chunk this line belongs to, +// or null if not a chunk. +coverage.chunk_indicator = function (line_elt) { + const classes = line_elt?.className; + if (!classes) { + return null; + } + const match = classes.match(/\bshow_\w+\b/); + if (!match) { + return null; + } + return match[0]; +}; + +coverage.to_next_chunk = function () { + const c = coverage; + + // Find the start of the next colored chunk. + var probe = c.sel_end; + var chunk_indicator, probe_line; + while (true) { + probe_line = c.line_elt(probe); + if (!probe_line) { + return; + } + chunk_indicator = c.chunk_indicator(probe_line); + if (chunk_indicator) { + break; + } + probe++; + } + + // There's a next chunk, `probe` points to it. + var begin = probe; + + // Find the end of this chunk. + var next_indicator = chunk_indicator; + while (next_indicator === chunk_indicator) { + probe++; + probe_line = c.line_elt(probe); + next_indicator = c.chunk_indicator(probe_line); + } + c.set_sel(begin, probe); + c.show_selection(); +}; + +coverage.to_prev_chunk = function () { + const c = coverage; + + // Find the end of the prev colored chunk. + var probe = c.sel_begin-1; + var probe_line = c.line_elt(probe); + if (!probe_line) { + return; + } + var chunk_indicator = c.chunk_indicator(probe_line); + while (probe > 1 && !chunk_indicator) { + probe--; + probe_line = c.line_elt(probe); + if (!probe_line) { + return; + } + chunk_indicator = c.chunk_indicator(probe_line); + } + + // There's a prev chunk, `probe` points to its last line. + var end = probe+1; + + // Find the beginning of this chunk. + var prev_indicator = chunk_indicator; + while (prev_indicator === chunk_indicator) { + probe--; + if (probe <= 0) { + return; + } + probe_line = c.line_elt(probe); + prev_indicator = c.chunk_indicator(probe_line); + } + c.set_sel(probe+1, end); + c.show_selection(); +}; + +// Returns 0, 1, or 2: how many of the two ends of the selection are on +// the screen right now? +coverage.selection_ends_on_screen = function () { + if (coverage.sel_begin === 0) { + return 0; + } + + const begin = coverage.line_elt(coverage.sel_begin); + const end = coverage.line_elt(coverage.sel_end-1); + + return ( + (checkVisible(begin) ? 1 : 0) + + (checkVisible(end) ? 1 : 0) + ); +}; + +coverage.to_next_chunk_nicely = function () { + if (coverage.selection_ends_on_screen() === 0) { + // The selection is entirely off the screen: + // Set the top line on the screen as selection. + + // This will select the top-left of the viewport + // As this is most likely the span with the line number we take the parent + const line = document.elementFromPoint(0, 0).parentElement; + if (line.parentElement !== document.getElementById("source")) { + // The element is not a source line but the header or similar + coverage.select_line_or_chunk(1); + } + else { + // We extract the line number from the id + coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10)); + } + } + coverage.to_next_chunk(); +}; + +coverage.to_prev_chunk_nicely = function () { + if (coverage.selection_ends_on_screen() === 0) { + // The selection is entirely off the screen: + // Set the lowest line on the screen as selection. + + // This will select the bottom-left of the viewport + // As this is most likely the span with the line number we take the parent + const line = document.elementFromPoint(document.documentElement.clientHeight-1, 0).parentElement; + if (line.parentElement !== document.getElementById("source")) { + // The element is not a source line but the header or similar + coverage.select_line_or_chunk(coverage.lines_len); + } + else { + // We extract the line number from the id + coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10)); + } + } + coverage.to_prev_chunk(); +}; + +// Select line number lineno, or if it is in a colored chunk, select the +// entire chunk +coverage.select_line_or_chunk = function (lineno) { + var c = coverage; + var probe_line = c.line_elt(lineno); + if (!probe_line) { + return; + } + var the_indicator = c.chunk_indicator(probe_line); + if (the_indicator) { + // The line is in a highlighted chunk. + // Search backward for the first line. + var probe = lineno; + var indicator = the_indicator; + while (probe > 0 && indicator === the_indicator) { + probe--; + probe_line = c.line_elt(probe); + if (!probe_line) { + break; + } + indicator = c.chunk_indicator(probe_line); + } + var begin = probe + 1; + + // Search forward for the last line. + probe = lineno; + indicator = the_indicator; + while (indicator === the_indicator) { + probe++; + probe_line = c.line_elt(probe); + indicator = c.chunk_indicator(probe_line); + } + + coverage.set_sel(begin, probe); + } + else { + coverage.set_sel(lineno); + } +}; + +coverage.show_selection = function () { + // Highlight the lines in the chunk + document.querySelectorAll("#source .highlight").forEach(e => e.classList.remove("highlight")); + for (let probe = coverage.sel_begin; probe < coverage.sel_end; probe++) { + coverage.line_elt(probe).querySelector(".n").classList.add("highlight"); + } + + coverage.scroll_to_selection(); +}; + +coverage.scroll_to_selection = function () { + // Scroll the page if the chunk isn't fully visible. + if (coverage.selection_ends_on_screen() < 2) { + const element = coverage.line_elt(coverage.sel_begin); + coverage.scroll_window(element.offsetTop - 60); + } +}; + +coverage.scroll_window = function (to_pos) { + window.scroll({top: to_pos, behavior: "smooth"}); +}; + +coverage.init_scroll_markers = function () { + // Init some variables + coverage.lines_len = document.querySelectorAll("#source > p").length; + + // Build html + coverage.build_scroll_markers(); +}; + +coverage.build_scroll_markers = function () { + const temp_scroll_marker = document.getElementById("scroll_marker") + if (temp_scroll_marker) temp_scroll_marker.remove(); + // Don't build markers if the window has no scroll bar. + if (document.body.scrollHeight <= window.innerHeight) { + return; + } + + const marker_scale = window.innerHeight / document.body.scrollHeight; + const line_height = Math.min(Math.max(3, window.innerHeight / coverage.lines_len), 10); + + let previous_line = -99, last_mark, last_top; + + const scroll_marker = document.createElement("div"); + scroll_marker.id = "scroll_marker"; + document.getElementById("source").querySelectorAll( + "p.show_run, p.show_mis, p.show_exc, p.show_exc, p.show_par" + ).forEach(element => { + const line_top = Math.floor(element.offsetTop * marker_scale); + const line_number = parseInt(element.querySelector(".n a").id.substr(1)); + + if (line_number === previous_line + 1) { + // If this solid missed block just make previous mark higher. + last_mark.style.height = `${line_top + line_height - last_top}px`; + } + else { + // Add colored line in scroll_marker block. + last_mark = document.createElement("div"); + last_mark.id = `m${line_number}`; + last_mark.classList.add("marker"); + last_mark.style.height = `${line_height}px`; + last_mark.style.top = `${line_top}px`; + scroll_marker.append(last_mark); + last_top = line_top; + } + + previous_line = line_number; + }); + + // Append last to prevent layout calculation + document.body.append(scroll_marker); +}; + +coverage.wire_up_sticky_header = function () { + const header = document.querySelector("header"); + const header_bottom = ( + header.querySelector(".content h2").getBoundingClientRect().top - + header.getBoundingClientRect().top + ); + + function updateHeader() { + if (window.scrollY > header_bottom) { + header.classList.add("sticky"); + } + else { + header.classList.remove("sticky"); + } + } + + window.addEventListener("scroll", updateHeader); + updateHeader(); +}; + +coverage.expand_contexts = function (e) { + var ctxs = e.target.parentNode.querySelector(".ctxs"); + + if (!ctxs.classList.contains("expanded")) { + var ctxs_text = ctxs.textContent; + var width = Number(ctxs_text[0]); + ctxs.textContent = ""; + for (var i = 1; i < ctxs_text.length; i += width) { + key = ctxs_text.substring(i, i + width).trim(); + ctxs.appendChild(document.createTextNode(contexts[key])); + ctxs.appendChild(document.createElement("br")); + } + ctxs.classList.add("expanded"); + } +}; + +document.addEventListener("DOMContentLoaded", () => { + if (document.body.classList.contains("indexfile")) { + coverage.index_ready(); + } + else { + coverage.pyfile_ready(); + } +}); diff --git a/tests/htmlcov/favicon_32_cb_58284776.png b/tests/htmlcov/favicon_32_cb_58284776.png new file mode 100644 index 0000000..8649f04 Binary files /dev/null and b/tests/htmlcov/favicon_32_cb_58284776.png differ diff --git a/tests/htmlcov/function_index.html b/tests/htmlcov/function_index.html new file mode 100644 index 0000000..d78899d --- /dev/null +++ b/tests/htmlcov/function_index.html @@ -0,0 +1,339 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 64% +

+ +
+ +
+ + +
+
+

+ Files + Functions + Classes +

+

+ coverage.py v7.6.10, + created at 2025-11-03 06:53 +0100 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Filefunctionstatementsmissingexcludedcoverage
/data/dev/python/daemonpy/github/daemonpy/daemonpy/__init__.pyDaemon.__init__152087%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/__init__.pyDaemon.usage2113038%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/__init__.pyDaemon.daemonize272700%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/__init__.pyDaemon.start121200%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/__init__.pyDaemon.stop101000%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/__init__.pyDaemon.restart8800%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/__init__.pyDaemon.exit_handler7700%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/__init__.pyDaemon.run000100%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/__init__.py(no function)3502100%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/daemon_usage.pyDaemonUsage.__init__200100%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/daemon_usage.pyDaemonUsage.usage_status100100%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/daemon_usage.pyDaemonUsage.usage_status100100%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/daemon_usage.pyDaemonUsage.check162088%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/daemon_usage.py(no function)2502100%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/file_descriptor.pyFileDescriptor.__init__1300100%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/file_descriptor.pyFileDescriptor.__enter__84050%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/file_descriptor.pyFileDescriptor.__exit__300100%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/file_descriptor.py(no function)2302100%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/file_process_id.pyFileProcessId.__init__1300100%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/file_process_id.pyFileProcessId.__enter__300100%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/file_process_id.pyFileProcessId.__exit__300100%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/file_process_id.py(no function)2002100%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/unix_operations.pyUnixOperations.__init__200100%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/unix_operations.pyUnixOperations.unix_status100100%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/unix_operations.pyUnixOperations.unix_status100100%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/unix_operations.pyUnixOperations.first_fork4400%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/unix_operations.pyUnixOperations.second_fork4400%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/unix_operations.pyUnixOperations.unix_kill272700%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/unix_operations.py(no function)3302100%
Total 3381201064%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/tests/htmlcov/index.html b/tests/htmlcov/index.html new file mode 100644 index 0000000..22fb766 --- /dev/null +++ b/tests/htmlcov/index.html @@ -0,0 +1,139 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 64% +

+ +
+ +
+ + +
+
+

+ Files + Functions + Classes +

+

+ coverage.py v7.6.10, + created at 2025-11-03 06:53 +0100 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Filestatementsmissingexcludedcoverage
/data/dev/python/daemonpy/github/daemonpy/daemonpy/__init__.py13579241%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/daemon_usage.py452296%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/file_descriptor.py474291%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/file_process_id.py3902100%
/data/dev/python/daemonpy/github/daemonpy/daemonpy/unix_operations.py7235251%
Total3381201064%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/tests/htmlcov/keybd_closed_cb_ce680311.png b/tests/htmlcov/keybd_closed_cb_ce680311.png new file mode 100644 index 0000000..ba119c4 Binary files /dev/null and b/tests/htmlcov/keybd_closed_cb_ce680311.png differ diff --git a/tests/htmlcov/status.json b/tests/htmlcov/status.json new file mode 100644 index 0000000..0d275cd --- /dev/null +++ b/tests/htmlcov/status.json @@ -0,0 +1 @@ +{"note":"This file is an internal implementation detail to speed up HTML report generation. Its format can change at any time. You might be looking for the JSON report: https://coverage.rtfd.io/cmd.html#cmd-json","format":5,"version":"7.6.10","globals":"14fe0927a8c6bc54b53d318a71119b3c","files":{"z_74f19c91e79fbf87___init___py":{"hash":"60a8ebf9f20530f56979a67d2c966f5f","index":{"url":"z_74f19c91e79fbf87___init___py.html","file":"/data/dev/python/daemonpy/github/daemonpy/daemonpy/__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":135,"n_excluded":2,"n_missing":79,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_74f19c91e79fbf87_daemon_usage_py":{"hash":"ac528f98b21a5c7e62b29a6c55963350","index":{"url":"z_74f19c91e79fbf87_daemon_usage_py.html","file":"/data/dev/python/daemonpy/github/daemonpy/daemonpy/daemon_usage.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":45,"n_excluded":2,"n_missing":2,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_74f19c91e79fbf87_file_descriptor_py":{"hash":"dec2c01302c3f5529eaf886f675a6fd3","index":{"url":"z_74f19c91e79fbf87_file_descriptor_py.html","file":"/data/dev/python/daemonpy/github/daemonpy/daemonpy/file_descriptor.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":47,"n_excluded":2,"n_missing":4,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_74f19c91e79fbf87_file_process_id_py":{"hash":"48ca43a5ff0012f4e21169fc95b4d998","index":{"url":"z_74f19c91e79fbf87_file_process_id_py.html","file":"/data/dev/python/daemonpy/github/daemonpy/daemonpy/file_process_id.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":39,"n_excluded":2,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_74f19c91e79fbf87_unix_operations_py":{"hash":"09e46f30b95313f32919327b922695b8","index":{"url":"z_74f19c91e79fbf87_unix_operations_py.html","file":"/data/dev/python/daemonpy/github/daemonpy/daemonpy/unix_operations.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":72,"n_excluded":2,"n_missing":35,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}}}} \ No newline at end of file diff --git a/tests/htmlcov/style_cb_8e611ae1.css b/tests/htmlcov/style_cb_8e611ae1.css new file mode 100644 index 0000000..3cdaf05 --- /dev/null +++ b/tests/htmlcov/style_cb_8e611ae1.css @@ -0,0 +1,337 @@ +@charset "UTF-8"; +/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */ +/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */ +/* Don't edit this .css file. Edit the .scss file instead! */ +html, body, h1, h2, h3, p, table, td, th { margin: 0; padding: 0; border: 0; font-weight: inherit; font-style: inherit; font-size: 100%; font-family: inherit; vertical-align: baseline; } + +body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; font-size: 1em; background: #fff; color: #000; } + +@media (prefers-color-scheme: dark) { body { background: #1e1e1e; } } + +@media (prefers-color-scheme: dark) { body { color: #eee; } } + +html > body { font-size: 16px; } + +a:active, a:focus { outline: 2px dashed #007acc; } + +p { font-size: .875em; line-height: 1.4em; } + +table { border-collapse: collapse; } + +td { vertical-align: top; } + +table tr.hidden { display: none !important; } + +p#no_rows { display: none; font-size: 1.15em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; } + +a.nav { text-decoration: none; color: inherit; } + +a.nav:hover { text-decoration: underline; color: inherit; } + +.hidden { display: none; } + +header { background: #f8f8f8; width: 100%; z-index: 2; border-bottom: 1px solid #ccc; } + +@media (prefers-color-scheme: dark) { header { background: black; } } + +@media (prefers-color-scheme: dark) { header { border-color: #333; } } + +header .content { padding: 1rem 3.5rem; } + +header h2 { margin-top: .5em; font-size: 1em; } + +header h2 a.button { font-family: inherit; font-size: inherit; border: 1px solid; border-radius: .2em; background: #eee; color: inherit; text-decoration: none; padding: .1em .5em; margin: 1px calc(.1em + 1px); cursor: pointer; border-color: #ccc; } + +@media (prefers-color-scheme: dark) { header h2 a.button { background: #333; } } + +@media (prefers-color-scheme: dark) { header h2 a.button { border-color: #444; } } + +header h2 a.button.current { border: 2px solid; background: #fff; border-color: #999; cursor: default; } + +@media (prefers-color-scheme: dark) { header h2 a.button.current { background: #1e1e1e; } } + +@media (prefers-color-scheme: dark) { header h2 a.button.current { border-color: #777; } } + +header p.text { margin: .5em 0 -.5em; color: #666; font-style: italic; } + +@media (prefers-color-scheme: dark) { header p.text { color: #aaa; } } + +header.sticky { position: fixed; left: 0; right: 0; height: 2.5em; } + +header.sticky .text { display: none; } + +header.sticky h1, header.sticky h2 { font-size: 1em; margin-top: 0; display: inline-block; } + +header.sticky .content { padding: 0.5rem 3.5rem; } + +header.sticky .content p { font-size: 1em; } + +header.sticky ~ #source { padding-top: 6.5em; } + +main { position: relative; z-index: 1; } + +footer { margin: 1rem 3.5rem; } + +footer .content { padding: 0; color: #666; font-style: italic; } + +@media (prefers-color-scheme: dark) { footer .content { color: #aaa; } } + +#index { margin: 1rem 0 0 3.5rem; } + +h1 { font-size: 1.25em; display: inline-block; } + +#filter_container { float: right; margin: 0 2em 0 0; line-height: 1.66em; } + +#filter_container #filter { width: 10em; padding: 0.2em 0.5em; border: 2px solid #ccc; background: #fff; color: #000; } + +@media (prefers-color-scheme: dark) { #filter_container #filter { border-color: #444; } } + +@media (prefers-color-scheme: dark) { #filter_container #filter { background: #1e1e1e; } } + +@media (prefers-color-scheme: dark) { #filter_container #filter { color: #eee; } } + +#filter_container #filter:focus { border-color: #007acc; } + +#filter_container :disabled ~ label { color: #ccc; } + +@media (prefers-color-scheme: dark) { #filter_container :disabled ~ label { color: #444; } } + +#filter_container label { font-size: .875em; color: #666; } + +@media (prefers-color-scheme: dark) { #filter_container label { color: #aaa; } } + +header button { font-family: inherit; font-size: inherit; border: 1px solid; border-radius: .2em; background: #eee; color: inherit; text-decoration: none; padding: .1em .5em; margin: 1px calc(.1em + 1px); cursor: pointer; border-color: #ccc; } + +@media (prefers-color-scheme: dark) { header button { background: #333; } } + +@media (prefers-color-scheme: dark) { header button { border-color: #444; } } + +header button:active, header button:focus { outline: 2px dashed #007acc; } + +header button.run { background: #eeffee; } + +@media (prefers-color-scheme: dark) { header button.run { background: #373d29; } } + +header button.run.show_run { background: #dfd; border: 2px solid #00dd00; margin: 0 .1em; } + +@media (prefers-color-scheme: dark) { header button.run.show_run { background: #373d29; } } + +header button.mis { background: #ffeeee; } + +@media (prefers-color-scheme: dark) { header button.mis { background: #4b1818; } } + +header button.mis.show_mis { background: #fdd; border: 2px solid #ff0000; margin: 0 .1em; } + +@media (prefers-color-scheme: dark) { header button.mis.show_mis { background: #4b1818; } } + +header button.exc { background: #f7f7f7; } + +@media (prefers-color-scheme: dark) { header button.exc { background: #333; } } + +header button.exc.show_exc { background: #eee; border: 2px solid #808080; margin: 0 .1em; } + +@media (prefers-color-scheme: dark) { header button.exc.show_exc { background: #333; } } + +header button.par { background: #ffffd5; } + +@media (prefers-color-scheme: dark) { header button.par { background: #650; } } + +header button.par.show_par { background: #ffa; border: 2px solid #bbbb00; margin: 0 .1em; } + +@media (prefers-color-scheme: dark) { header button.par.show_par { background: #650; } } + +#help_panel, #source p .annotate.long { display: none; position: absolute; z-index: 999; background: #ffffcc; border: 1px solid #888; border-radius: .2em; color: #333; padding: .25em .5em; } + +#source p .annotate.long { white-space: normal; float: right; top: 1.75em; right: 1em; height: auto; } + +#help_panel_wrapper { float: right; position: relative; } + +#keyboard_icon { margin: 5px; } + +#help_panel_state { display: none; } + +#help_panel { top: 25px; right: 0; padding: .75em; border: 1px solid #883; color: #333; } + +#help_panel .keyhelp p { margin-top: .75em; } + +#help_panel .legend { font-style: italic; margin-bottom: 1em; } + +.indexfile #help_panel { width: 25em; } + +.pyfile #help_panel { width: 18em; } + +#help_panel_state:checked ~ #help_panel { display: block; } + +kbd { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em .35em; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-weight: bold; background: #eee; border-radius: 3px; } + +#source { padding: 1em 0 1em 3.5rem; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; } + +#source p { position: relative; white-space: pre; } + +#source p * { box-sizing: border-box; } + +#source p .n { float: left; text-align: right; width: 3.5rem; box-sizing: border-box; margin-left: -3.5rem; padding-right: 1em; color: #999; user-select: none; } + +@media (prefers-color-scheme: dark) { #source p .n { color: #777; } } + +#source p .n.highlight { background: #ffdd00; } + +#source p .n a { scroll-margin-top: 6em; text-decoration: none; color: #999; } + +@media (prefers-color-scheme: dark) { #source p .n a { color: #777; } } + +#source p .n a:hover { text-decoration: underline; color: #999; } + +@media (prefers-color-scheme: dark) { #source p .n a:hover { color: #777; } } + +#source p .t { display: inline-block; width: 100%; box-sizing: border-box; margin-left: -.5em; padding-left: 0.3em; border-left: 0.2em solid #fff; } + +@media (prefers-color-scheme: dark) { #source p .t { border-color: #1e1e1e; } } + +#source p .t:hover { background: #f2f2f2; } + +@media (prefers-color-scheme: dark) { #source p .t:hover { background: #282828; } } + +#source p .t:hover ~ .r .annotate.long { display: block; } + +#source p .t .com { color: #008000; font-style: italic; line-height: 1px; } + +@media (prefers-color-scheme: dark) { #source p .t .com { color: #6a9955; } } + +#source p .t .key { font-weight: bold; line-height: 1px; } + +#source p .t .str { color: #0451a5; } + +@media (prefers-color-scheme: dark) { #source p .t .str { color: #9cdcfe; } } + +#source p.mis .t { border-left: 0.2em solid #ff0000; } + +#source p.mis.show_mis .t { background: #fdd; } + +@media (prefers-color-scheme: dark) { #source p.mis.show_mis .t { background: #4b1818; } } + +#source p.mis.show_mis .t:hover { background: #f2d2d2; } + +@media (prefers-color-scheme: dark) { #source p.mis.show_mis .t:hover { background: #532323; } } + +#source p.run .t { border-left: 0.2em solid #00dd00; } + +#source p.run.show_run .t { background: #dfd; } + +@media (prefers-color-scheme: dark) { #source p.run.show_run .t { background: #373d29; } } + +#source p.run.show_run .t:hover { background: #d2f2d2; } + +@media (prefers-color-scheme: dark) { #source p.run.show_run .t:hover { background: #404633; } } + +#source p.exc .t { border-left: 0.2em solid #808080; } + +#source p.exc.show_exc .t { background: #eee; } + +@media (prefers-color-scheme: dark) { #source p.exc.show_exc .t { background: #333; } } + +#source p.exc.show_exc .t:hover { background: #e2e2e2; } + +@media (prefers-color-scheme: dark) { #source p.exc.show_exc .t:hover { background: #3c3c3c; } } + +#source p.par .t { border-left: 0.2em solid #bbbb00; } + +#source p.par.show_par .t { background: #ffa; } + +@media (prefers-color-scheme: dark) { #source p.par.show_par .t { background: #650; } } + +#source p.par.show_par .t:hover { background: #f2f2a2; } + +@media (prefers-color-scheme: dark) { #source p.par.show_par .t:hover { background: #6d5d0c; } } + +#source p .r { position: absolute; top: 0; right: 2.5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; } + +#source p .annotate { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; color: #666; padding-right: .5em; } + +@media (prefers-color-scheme: dark) { #source p .annotate { color: #ddd; } } + +#source p .annotate.short:hover ~ .long { display: block; } + +#source p .annotate.long { width: 30em; right: 2.5em; } + +#source p input { display: none; } + +#source p input ~ .r label.ctx { cursor: pointer; border-radius: .25em; } + +#source p input ~ .r label.ctx::before { content: "▶ "; } + +#source p input ~ .r label.ctx:hover { background: #e8f4ff; color: #666; } + +@media (prefers-color-scheme: dark) { #source p input ~ .r label.ctx:hover { background: #0f3a42; } } + +@media (prefers-color-scheme: dark) { #source p input ~ .r label.ctx:hover { color: #aaa; } } + +#source p input:checked ~ .r label.ctx { background: #d0e8ff; color: #666; border-radius: .75em .75em 0 0; padding: 0 .5em; margin: -.25em 0; } + +@media (prefers-color-scheme: dark) { #source p input:checked ~ .r label.ctx { background: #056; } } + +@media (prefers-color-scheme: dark) { #source p input:checked ~ .r label.ctx { color: #aaa; } } + +#source p input:checked ~ .r label.ctx::before { content: "▼ "; } + +#source p input:checked ~ .ctxs { padding: .25em .5em; overflow-y: scroll; max-height: 10.5em; } + +#source p label.ctx { color: #999; display: inline-block; padding: 0 .5em; font-size: .8333em; } + +@media (prefers-color-scheme: dark) { #source p label.ctx { color: #777; } } + +#source p .ctxs { display: block; max-height: 0; overflow-y: hidden; transition: all .2s; padding: 0 .5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; white-space: nowrap; background: #d0e8ff; border-radius: .25em; margin-right: 1.75em; text-align: right; } + +@media (prefers-color-scheme: dark) { #source p .ctxs { background: #056; } } + +#index { font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 0.875em; } + +#index table.index { margin-left: -.5em; } + +#index td, #index th { text-align: right; padding: .25em .5em; border-bottom: 1px solid #eee; } + +@media (prefers-color-scheme: dark) { #index td, #index th { border-color: #333; } } + +#index td.name, #index th.name { text-align: left; width: auto; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; min-width: 15em; } + +#index th { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; font-style: italic; color: #333; cursor: pointer; } + +@media (prefers-color-scheme: dark) { #index th { color: #ddd; } } + +#index th:hover { background: #eee; } + +@media (prefers-color-scheme: dark) { #index th:hover { background: #333; } } + +#index th .arrows { color: #666; font-size: 85%; font-family: sans-serif; font-style: normal; pointer-events: none; } + +#index th[aria-sort="ascending"], #index th[aria-sort="descending"] { white-space: nowrap; background: #eee; padding-left: .5em; } + +@media (prefers-color-scheme: dark) { #index th[aria-sort="ascending"], #index th[aria-sort="descending"] { background: #333; } } + +#index th[aria-sort="ascending"] .arrows::after { content: " ▲"; } + +#index th[aria-sort="descending"] .arrows::after { content: " ▼"; } + +#index td.name { font-size: 1.15em; } + +#index td.name a { text-decoration: none; color: inherit; } + +#index td.name .no-noun { font-style: italic; } + +#index tr.total td, #index tr.total_dynamic td { font-weight: bold; border-top: 1px solid #ccc; border-bottom: none; } + +#index tr.region:hover { background: #eee; } + +@media (prefers-color-scheme: dark) { #index tr.region:hover { background: #333; } } + +#index tr.region:hover td.name { text-decoration: underline; color: inherit; } + +#scroll_marker { position: fixed; z-index: 3; right: 0; top: 0; width: 16px; height: 100%; background: #fff; border-left: 1px solid #eee; will-change: transform; } + +@media (prefers-color-scheme: dark) { #scroll_marker { background: #1e1e1e; } } + +@media (prefers-color-scheme: dark) { #scroll_marker { border-color: #333; } } + +#scroll_marker .marker { background: #ccc; position: absolute; min-height: 3px; width: 100%; } + +@media (prefers-color-scheme: dark) { #scroll_marker .marker { background: #444; } } diff --git a/tests/htmlcov/z_74f19c91e79fbf87___init___py.html b/tests/htmlcov/z_74f19c91e79fbf87___init___py.html new file mode 100644 index 0000000..680704b --- /dev/null +++ b/tests/htmlcov/z_74f19c91e79fbf87___init___py.html @@ -0,0 +1,372 @@ + + + + + Coverage for /data/dev/python/daemonpy/github/daemonpy/daemonpy/__init__.py: 41% + + + + + +
+
+

+ Coverage for /data/dev/python/daemonpy/github/daemonpy/daemonpy/__init__.py: + 41% +

+ +

+ 135 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.6.10, + created at 2025-11-03 06:53 +0100 +

+ +
+
+
+

1# -*- coding: UTF-8 -*- 

+

2 

+

3''' 

+

4Module 

+

5 __init__.py 

+

6Copyright 

+

7 Copyright (C) 2020 - 2025 Vladimir Roncevic <elektron.ronca@gmail.com> 

+

8 daemonpy is free software: you can redistribute it and/or modify it 

+

9 under the terms of the GNU General Public License as published by the 

+

10 Free Software Foundation, either version 3 of the License, or 

+

11 (at your option) any later version. 

+

12 daemonpy is distributed in the hope that it will be useful, but 

+

13 WITHOUT ANY WARRANTY; without even the implied warranty of 

+

14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 

+

15 See the GNU General Public License for more details. 

+

16 You should have received a copy of the GNU General Public License along 

+

17 with this program. If not, see <http://www.gnu.org/licenses/>. 

+

18Info 

+

19 Defines class Daemon with attribute(s) and method(s). 

+

20 Creates a base class with backend API. 

+

21''' 

+

22 

+

23import sys 

+

24from typing import List, Optional 

+

25from atexit import register 

+

26from os.path import exists 

+

27from os import chdir, setsid, umask, dup2, getpid, remove 

+

28from abc import abstractmethod 

+

29 

+

30try: 

+

31 from ats_utilities.checker import ATSChecker 

+

32 from ats_utilities.console_io.error import error_message 

+

33 from ats_utilities.console_io.verbose import verbose_message 

+

34 from ats_utilities.exceptions.ats_type_error import ATSTypeError 

+

35 from ats_utilities.exceptions.ats_value_error import ATSValueError 

+

36 from daemonpy.daemon_usage import DaemonUsage 

+

37 from daemonpy.file_process_id import FileProcessId 

+

38 from daemonpy.file_descriptor import FileDescriptor 

+

39 from daemonpy.unix_operations import UnixOperations 

+

40except ImportError as ats_error_message: # pragma: no cover 

+

41 # Force exit python ####################################################### 

+

42 sys.exit(f'\n{__file__}\n{ats_error_message}\n') # pragma: no cover 

+

43 

+

44__author__: str = 'Vladimir Roncevic' 

+

45__copyright__: str = '(C) 2025, https://vroncevic.github.io/daemonpy' 

+

46__credits__: List[str] = ['Vladimir Roncevic', 'Python Software Foundation'] 

+

47__license__: str = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' 

+

48__version__: str = '2.0.6' 

+

49__maintainer__: str = 'Vladimir Roncevic' 

+

50__email__: str = 'elektron.ronca@gmail.com' 

+

51__status__: str = 'Updated' 

+

52 

+

53 

+

54class Daemon(UnixOperations): 

+

55 ''' 

+

56 Defines class Daemon with attribute(s) and method(s). 

+

57 Creates a base class with backend API. 

+

58 

+

59 It defines: 

+

60 

+

61 :attributes: 

+

62 | _PKG_VERBOSE - Console text indicator for process-phase. 

+

63 | _daemon_usage - Daemon usage. 

+

64 | _pid - PID file path. 

+

65 :methods: 

+

66 | __init__ - Initials Daemon constructor. 

+

67 | daemonize - Creates daemon process. 

+

68 | start - Starts daemon process. 

+

69 | stop - Stops daemon process. 

+

70 | restart - Restarts daemon process. 

+

71 | exit_handler - At exit delete PID file. 

+

72 | run - Runs daemon process (abstract method). 

+

73 ''' 

+

74 

+

75 _P_VERBOSE: str = 'DAEMONPY' 

+

76 

+

77 def __init__(self, pid: str, verbose: bool = False) -> None: 

+

78 ''' 

+

79 Initials Daemon constructor. 

+

80 

+

81 :param pid: PID file path 

+

82 :type pid: <str> 

+

83 :param verbose: Enable/Disable verbose option 

+

84 :type verbose: <bool> 

+

85 :exceptions: ATSTypeError | ATSValueError 

+

86 ''' 

+

87 super().__init__() 

+

88 error_msg: Optional[str] = None 

+

89 error_id: Optional[int] = None 

+

90 checker: ATSChecker = ATSChecker() 

+

91 error_msg, error_id = checker.check_params([('str:pid', pid)]) 

+

92 if error_id == checker.TYPE_ERROR: 

+

93 raise ATSTypeError(error_msg) 

+

94 if not bool(pid): 

+

95 raise ATSValueError('missing PID file') 

+

96 verbose_message(verbose, [f'{self._P_VERBOSE} init daemon']) 

+

97 self._daemon_usage: Optional[DaemonUsage] = None 

+

98 self._pid: Optional[str] = None 

+

99 if self.unix_status: 

+

100 self._daemon_usage = DaemonUsage() 

+

101 self._pid = pid 

+

102 

+

103 def usage(self, operation: str, verbose: bool = False) -> None: 

+

104 ''' 

+

105 Creates daemon process. 

+

106 

+

107 :param operation: Daemon operation 

+

108 :type operation: <str> 

+

109 :param verbose: Enable/Disable verbose option 

+

110 :type verbose: <bool> 

+

111 :exceptions: ATSTypeError | ATSValueError 

+

112 ''' 

+

113 error_msg: Optional[str] = None 

+

114 error_id: Optional[int] = None 

+

115 checker: ATSChecker = ATSChecker() 

+

116 error_msg, error_id = checker.check_params([ 

+

117 ('str:operation', operation) 

+

118 ]) 

+

119 if error_id == checker.TYPE_ERROR: 

+

120 raise ATSTypeError(error_msg) 

+

121 if not bool(operation): 

+

122 raise ATSValueError('missing daemon operation') 

+

123 verbose_message(verbose, [f'{self._P_VERBOSE} daemon', operation]) 

+

124 if self.unix_status and bool(self._daemon_usage): 

+

125 self._daemon_usage.check(operation, verbose) 

+

126 if self._daemon_usage.usage_status == 127: 

+

127 sys.exit(127) 

+

128 elif self._daemon_usage.usage_status == 0: 

+

129 self.start(verbose) 

+

130 elif self._daemon_usage.usage_status == 1: 

+

131 self.stop(verbose) 

+

132 elif self._daemon_usage.usage_status == 2: 

+

133 self.restart(verbose) 

+

134 else: 

+

135 error_message([f'{self._P_VERBOSE} wrong option code']) 

+

136 sys.exit(128) 

+

137 

+

138 def daemonize(self, verbose: bool = False) -> None: 

+

139 ''' 

+

140 Creates daemon process. 

+

141 

+

142 :param verbose: Enable/Disable verbose option 

+

143 :type verbose: <bool> 

+

144 :exceptions: None 

+

145 ''' 

+

146 null: str = '/dev/null' 

+

147 verbose_message(verbose, [f'{self._P_VERBOSE} create daemon']) 

+

148 if self.unix_status: 

+

149 try: 

+

150 self.first_fork() 

+

151 chdir('/') 

+

152 setsid() 

+

153 umask(0) 

+

154 self.second_fork() 

+

155 except OSError as os_error: 

+

156 error_message([ 

+

157 f'fork #1 failed: {os_error.errno} {os_error.strerror}\n' 

+

158 ]) 

+

159 sys.exit(1) 

+

160 sys.stdout.flush() 

+

161 sys.stderr.flush() 

+

162 with FileDescriptor(null, FileDescriptor.STDIN) as in_file: 

+

163 if bool(in_file): 

+

164 dup2(in_file.fileno(), sys.stdin.fileno()) 

+

165 with FileDescriptor(null, FileDescriptor.STDOUT) as out_file: 

+

166 if bool(out_file): 

+

167 dup2(out_file.fileno(), sys.stdout.fileno()) 

+

168 with FileDescriptor(null, FileDescriptor.STDERR) as err_file: 

+

169 if bool(err_file): 

+

170 dup2(err_file.fileno(), sys.stderr.fileno()) 

+

171 register(self.exit_handler) 

+

172 with FileProcessId(self._pid, 'w+') as pid: 

+

173 if bool(pid): 

+

174 pid.write(f'{str(getpid())}\n') 

+

175 

+

176 def start(self, verbose: bool = False) -> bool: 

+

177 ''' 

+

178 Start daemon process. 

+

179 

+

180 :param verbose: Enable/Disable verbose option 

+

181 :type verbose: <bool> 

+

182 :return: True (success operation) | False 

+

183 :rtype: <bool> 

+

184 :exceptions: None 

+

185 ''' 

+

186 status: bool = False 

+

187 verbose_message(verbose, [f'{self._P_VERBOSE} start daemon']) 

+

188 if self.unix_status: 

+

189 with FileProcessId(self._pid, 'w+') as pid: 

+

190 if bool(pid): 

+

191 pid_content: str = pid.read().strip() 

+

192 if bool(pid_content): 

+

193 error_message([ 

+

194 f'{self._P_VERBOSE} file', self._pid, 

+

195 'already exists, daemon already running?' 

+

196 ]) 

+

197 else: 

+

198 self.daemonize(verbose) 

+

199 self.run() 

+

200 status = True 

+

201 return status 

+

202 

+

203 def stop(self, verbose: bool = False) -> bool: 

+

204 ''' 

+

205 Stop daemon process. 

+

206 

+

207 :param verbose: Enable/Disable verbose option. 

+

208 :type verbose: <bool> 

+

209 :return: True (success operation) | False 

+

210 :rtype: <bool> 

+

211 :exceptions: None 

+

212 ''' 

+

213 status: bool = False 

+

214 verbose_message(verbose, [f'{self._P_VERBOSE} stop daemon']) 

+

215 if self.unix_status: 

+

216 with FileProcessId(self._pid, 'r') as pid: 

+

217 if bool(pid) and bool(self._pid): 

+

218 pid_content: str = pid.read().strip() 

+

219 if not bool(pid_content): 

+

220 error_message([f'{self._P_VERBOSE} daemon running?']) 

+

221 else: 

+

222 status = self.unix_kill(int(pid_content), self._pid) 

+

223 return status 

+

224 

+

225 def restart(self, verbose: bool = False) -> bool: 

+

226 ''' 

+

227 Restart daemon process. 

+

228 

+

229 :param verbose: Enable/Disable verbose option 

+

230 :type verbose: <bool> 

+

231 :return: True (success operation) | False 

+

232 :rtype: <bool> 

+

233 :exceptions: None 

+

234 ''' 

+

235 status: bool = False 

+

236 verbose_message(verbose, [f'{self._P_VERBOSE} restart daemon']) 

+

237 if self.unix_status: 

+

238 if all([self.stop(verbose), self.start(verbose)]): 

+

239 status = True 

+

240 else: 

+

241 error_message([f'{self._P_VERBOSE} faled to restart daemon']) 

+

242 else: 

+

243 error_message([f'{self._P_VERBOSE} daemon is active?']) 

+

244 return status 

+

245 

+

246 def exit_handler(self, verbose: bool = False) -> None: 

+

247 ''' 

+

248 Remove PID file at exit. 

+

249 

+

250 :param verbose: Enable/Disable verbose option 

+

251 :type verbose: <bool> 

+

252 :exceptions: None 

+

253 ''' 

+

254 if self.unix_status: 

+

255 if not bool(self._pid): 

+

256 error_message([f'{self._P_VERBOSE} check PID', self._pid]) 

+

257 else: 

+

258 if exists(self._pid): 

+

259 verbose_message( 

+

260 verbose, [f'{self._P_VERBOSE} removing PID', self._pid] 

+

261 ) 

+

262 remove(self._pid) 

+

263 else: 

+

264 error_message([f'{self._P_VERBOSE} check PID', self._pid]) 

+

265 

+

266 @abstractmethod 

+

267 def run(self) -> None: 

+

268 ''' 

+

269 Run daemon process. 

+

270 Override this method when subclass self. 

+

271 It will be called after the process has been 

+

272 daemonized by start() or restart(). 

+

273 

+

274 :exceptions: None 

+

275 ''' 

+
+ + + diff --git a/tests/htmlcov/z_74f19c91e79fbf87_daemon_usage_py.html b/tests/htmlcov/z_74f19c91e79fbf87_daemon_usage_py.html new file mode 100644 index 0000000..47b5d49 --- /dev/null +++ b/tests/htmlcov/z_74f19c91e79fbf87_daemon_usage_py.html @@ -0,0 +1,228 @@ + + + + + Coverage for /data/dev/python/daemonpy/github/daemonpy/daemonpy/daemon_usage.py: 96% + + + + + +
+
+

+ Coverage for /data/dev/python/daemonpy/github/daemonpy/daemonpy/daemon_usage.py: + 96% +

+ +

+ 45 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.6.10, + created at 2025-11-03 06:53 +0100 +

+ +
+
+
+

1# -*- coding: UTF-8 -*- 

+

2 

+

3''' 

+

4Module 

+

5 daemon_usage.py 

+

6Copyright 

+

7 Copyright (C) 2020 - 2025 Vladimir Roncevic <elektron.ronca@gmail.com> 

+

8 daemonpy is free software: you can redistribute it and/or modify it 

+

9 under the terms of the GNU General Public License as published by the 

+

10 Free Software Foundation, either version 3 of the License, or 

+

11 (at your option) any later version. 

+

12 daemonpy is distributed in the hope that it will be useful, but 

+

13 WITHOUT ANY WARRANTY; without even the implied warranty of 

+

14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 

+

15 See the GNU General Public License for more details. 

+

16 You should have received a copy of the GNU General Public License along 

+

17 with this program. If not, see <http://www.gnu.org/licenses/>. 

+

18Info 

+

19 Defines class DaemonUsage with attribute(s) and method(s). 

+

20 Creates an API for daemon usage. 

+

21''' 

+

22 

+

23import sys 

+

24from typing import List, Optional 

+

25 

+

26try: 

+

27 from ats_utilities.checker import ATSChecker 

+

28 from ats_utilities.console_io.error import error_message 

+

29 from ats_utilities.console_io.verbose import verbose_message 

+

30 from ats_utilities.exceptions.ats_type_error import ATSTypeError 

+

31 from ats_utilities.exceptions.ats_value_error import ATSValueError 

+

32except ImportError as ats_error_message: # pragma: no cover 

+

33 # Force exit python ####################################################### 

+

34 sys.exit(f'\n{__file__}\n{ats_error_message}\n') # pragma: no cover 

+

35 

+

36__author__: str = 'Vladimir Roncevic' 

+

37__copyright__: str = '(C) 2025, https://vroncevic.github.io/daemonpy' 

+

38__credits__: List[str] = ['Vladimir Roncevic', 'Python Software Foundation'] 

+

39__license__: str = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' 

+

40__version__: str = '2.0.6' 

+

41__maintainer__: str = 'Vladimir Roncevic' 

+

42__email__: str = 'elektron.ronca@gmail.com' 

+

43__status__: str = 'Updated' 

+

44 

+

45 

+

46class DaemonUsage: 

+

47 ''' 

+

48 Defines class DaemonUsage with attribute(s) and method(s). 

+

49 Creates an API for daemon usage. 

+

50 

+

51 It defines: 

+

52 

+

53 :attributes: 

+

54 | _PKG_VERBOSE - Console text indicator for process-phase. 

+

55 | DAEMON_OPERATIONS - List of supported operations. 

+

56 | _usage_status - Daemon usage status. 

+

57 :methods: 

+

58 | __init__ - Initials DaemonUsage constructor. 

+

59 | usage_status - Property methods for set/get operations. 

+

60 | check - Checks usage for daemon process. 

+

61 ''' 

+

62 

+

63 _P_VERBOSE: str = 'DAEMONPY::DAEMON_USAGE' 

+

64 DAEMON_OPERATIONS: List[str] = ['start', 'stop', 'restart'] 

+

65 

+

66 def __init__(self, verbose: bool = False) -> None: 

+

67 ''' 

+

68 Initials DaemonUsage constructor. 

+

69 

+

70 :param verbose: Enable/Disable verbose option 

+

71 :type verbose: <bool> 

+

72 :exceptions: None 

+

73 ''' 

+

74 verbose_message(verbose, [f'{self._P_VERBOSE} init usage']) 

+

75 self._usage_status: int = 0 

+

76 

+

77 @property 

+

78 def usage_status(self) -> int: 

+

79 ''' 

+

80 Property method for getting daemon usage status. 

+

81 

+

82 :return: Daemon usage status 

+

83 :rtype: <int> 

+

84 :exceptions: None 

+

85 ''' 

+

86 return self._usage_status 

+

87 

+

88 @usage_status.setter 

+

89 def usage_status(self, usage_status: int) -> None: 

+

90 ''' 

+

91 Property method for setting daemon usage status. 

+

92 

+

93 :param usage_status: Daemon usage status 

+

94 :type usage_status: <int> 

+

95 :exceptions: None 

+

96 ''' 

+

97 self._usage_status = usage_status 

+

98 

+

99 def check(self, daemon_operation: str, verbose: bool = False) -> None: 

+

100 ''' 

+

101 Checks usage of Daemon process. 

+

102 

+

103 :param daemon_operation: Daemon operation 

+

104 :type daemon_operation: <str> 

+

105 :param verbose: Enable/Disable verbose option 

+

106 :type verbose: <bool> 

+

107 :exceptions: ATSTypeError | ATSValueError 

+

108 ''' 

+

109 error_msg: Optional[str] = None 

+

110 error_id: Optional[int] = None 

+

111 checker: ATSChecker = ATSChecker() 

+

112 error_msg, error_id = checker.check_params([ 

+

113 ('str:daemon_operation', daemon_operation) 

+

114 ]) 

+

115 if error_id == checker.TYPE_ERROR: 

+

116 raise ATSTypeError(error_msg) 

+

117 if not bool(daemon_operation): 

+

118 raise ATSValueError('missing daemon operation') 

+

119 verbose_message(verbose, [f'{self._P_VERBOSE} checking usage']) 

+

120 for index, option in enumerate(self.DAEMON_OPERATIONS): 

+

121 if option == daemon_operation: 

+

122 self._usage_status = index 

+

123 if daemon_operation not in self.DAEMON_OPERATIONS: 

+

124 self._usage_status = 127 

+

125 error_message([ 

+

126 f'{self._P_VERBOSE} usage: {0}', 

+

127 '|'.join(self.DAEMON_OPERATIONS) 

+

128 ]) 

+

129 verbose_message( 

+

130 verbose, [f'{self._P_VERBOSE} usage status', self._usage_status] 

+

131 ) 

+
+ + + diff --git a/tests/htmlcov/z_74f19c91e79fbf87_file_descriptor_py.html b/tests/htmlcov/z_74f19c91e79fbf87_file_descriptor_py.html new file mode 100644 index 0000000..e99e184 --- /dev/null +++ b/tests/htmlcov/z_74f19c91e79fbf87_file_descriptor_py.html @@ -0,0 +1,230 @@ + + + + + Coverage for /data/dev/python/daemonpy/github/daemonpy/daemonpy/file_descriptor.py: 91% + + + + + +
+
+

+ Coverage for /data/dev/python/daemonpy/github/daemonpy/daemonpy/file_descriptor.py: + 91% +

+ +

+ 47 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.6.10, + created at 2025-11-03 06:53 +0100 +

+ +
+
+
+

1# -*- coding: UTF-8 -*- 

+

2 

+

3''' 

+

4Module 

+

5 file_descriptor.py 

+

6Copyright 

+

7 Copyright (C) 2020 - 2025 Vladimir Roncevic <elektron.ronca@gmail.com> 

+

8 daemonpy is free software: you can redistribute it and/or modify it 

+

9 under the terms of the GNU General Public License as published by the 

+

10 Free Software Foundation, either version 3 of the License, or 

+

11 (at your option) any later version. 

+

12 daemonpy is distributed in the hope that it will be useful, but 

+

13 WITHOUT ANY WARRANTY; without even the implied warranty of 

+

14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 

+

15 See the GNU General Public License for more details. 

+

16 You should have received a copy of the GNU General Public License along 

+

17 with this program. If not, see <http://www.gnu.org/licenses/>. 

+

18Info 

+

19 Defines class FileDescriptor with attribute(s) and method(s). 

+

20 Creates an API for the file descriptor context management. 

+

21''' 

+

22 

+

23import sys 

+

24from typing import Any, List, Dict, IO, Optional 

+

25 

+

26try: 

+

27 from ats_utilities.checker import ATSChecker 

+

28 from ats_utilities.exceptions.ats_type_error import ATSTypeError 

+

29 from ats_utilities.exceptions.ats_value_error import ATSValueError 

+

30except ImportError as ats_error_message: # pragma: no cover 

+

31 # Force exit python ####################################################### 

+

32 sys.exit(f'\n{__file__}\n{ats_error_message}\n') # pragma: no cover 

+

33 

+

34__author__: str = 'Vladimir Roncevic' 

+

35__copyright__: str = '(C) 2025, https://vroncevic.github.io/daemonpy' 

+

36__credits__: List[str] = ['Vladimir Roncevic', 'Python Software Foundation'] 

+

37__license__: str = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' 

+

38__version__: str = '2.0.3' 

+

39__maintainer__: str = 'Vladimir Roncevic' 

+

40__email__: str = 'elektron.ronca@gmail.com' 

+

41__status__: str = 'Updated' 

+

42 

+

43 

+

44class FileDescriptor: 

+

45 ''' 

+

46 Defines class FileDescriptor with attribute(s) and method(s). 

+

47 Creates an API for the file descriptor context management. 

+

48 

+

49 It defines: 

+

50 

+

51 :attributes: 

+

52 | _PKG_VERBOSE - Console text indicator for process-phase. 

+

53 | STDIN - Standard input is a stream id for input data. 

+

54 | STDOUT - Standard output is a stream id for output data. 

+

55 | STDERR - Standard error is a stream id for error messages. 

+

56 | FORMAT - Supported desciptor types with modes. 

+

57 | _desc_path - File descriptor device path. 

+

58 | _desc_type - File descriptor device type. 

+

59 | _desc_file - File descriptor device object. 

+

60 :methods: 

+

61 | __init__ - Initials FileDescriptor constructor. 

+

62 | __enter__ - Opens descriptor file. 

+

63 | __exit__ - Closes descriptor file. 

+

64 ''' 

+

65 

+

66 _P_VERBOSE: str = 'DAEMONPY::FILE_DESCRIPTOR' 

+

67 STDIN: int = 0 

+

68 STDOUT: int = 1 

+

69 STDERR: int = 2 

+

70 FORMAT: Dict[int, Any] = { 

+

71 STDIN: 'r', STDOUT: 'a+', STDERR: ['a+', 0] 

+

72 } 

+

73 

+

74 def __init__(self, desc_path: str, desc_type: Any) -> None: 

+

75 ''' 

+

76 Initials FileDescriptor constructor. 

+

77 

+

78 :param desc_path: file descriptor device path 

+

79 :type desc_path: <str> 

+

80 :param desc_type: file descriptor device type 

+

81 :type desc_type: <int> 

+

82 :exceptions: ATSTypeError | ATSValueError 

+

83 ''' 

+

84 error_msg: Optional[str] = None 

+

85 error_id: Optional[int] = None 

+

86 checker: ATSChecker = ATSChecker() 

+

87 error_msg, error_id = checker.check_params([ 

+

88 ('str:desc_path', desc_path) 

+

89 ]) 

+

90 if error_id == checker.TYPE_ERROR: 

+

91 raise ATSTypeError(error_msg) 

+

92 if not bool(desc_path): 

+

93 raise ATSValueError('missing device path file') 

+

94 if any([not bool(desc_type), desc_type not in self.FORMAT.values()]): 

+

95 raise ATSValueError('check device file format') 

+

96 self._desc_path: Optional[str] = desc_path 

+

97 self._desc_type: str | List[str | int] = desc_type 

+

98 self._desc_file: Optional[IO[Any]] = None 

+

99 

+

100 def __enter__(self) -> IO[Any] | None: 

+

101 ''' 

+

102 Opens descriptor file. 

+

103 

+

104 :return: File IO stream | None 

+

105 :rtype: <IO[Any]> | <NoneType> 

+

106 :exceptions: None 

+

107 ''' 

+

108 if bool(self._desc_path) and bool(self._desc_type): 

+

109 if isinstance(self._desc_type, str): 

+

110 self._desc_file = open( 

+

111 self._desc_path, self._desc_type, encoding='utf-8' 

+

112 ) 

+

113 else: 

+

114 if isinstance(self._desc_type[0], str): 

+

115 if isinstance(self._desc_type[1], int): 

+

116 if self._desc_type[1] == 0: 

+

117 self._desc_file = open( 

+

118 self._desc_path, 

+

119 self._desc_type[0], 

+

120 self._desc_type[1], 

+

121 encoding='utf-8' 

+

122 ) 

+

123 return self._desc_file 

+

124 

+

125 def __exit__(self, *args: Any) -> None: 

+

126 ''' 

+

127 Closes descriptor file. 

+

128 

+

129 :exceptions: None 

+

130 ''' 

+

131 if bool(self._desc_file): 

+

132 if not self._desc_file.closed: 

+

133 self._desc_file.close() 

+
+ + + diff --git a/tests/htmlcov/z_74f19c91e79fbf87_file_process_id_py.html b/tests/htmlcov/z_74f19c91e79fbf87_file_process_id_py.html new file mode 100644 index 0000000..e73496c --- /dev/null +++ b/tests/htmlcov/z_74f19c91e79fbf87_file_process_id_py.html @@ -0,0 +1,213 @@ + + + + + Coverage for /data/dev/python/daemonpy/github/daemonpy/daemonpy/file_process_id.py: 100% + + + + + +
+
+

+ Coverage for /data/dev/python/daemonpy/github/daemonpy/daemonpy/file_process_id.py: + 100% +

+ +

+ 39 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.6.10, + created at 2025-11-03 06:53 +0100 +

+ +
+
+
+

1# -*- coding: UTF-8 -*- 

+

2 

+

3''' 

+

4Module 

+

5 file_process_id.py 

+

6Copyright 

+

7 Copyright (C) 2020 - 2025 Vladimir Roncevic <elektron.ronca@gmail.com> 

+

8 daemonpy is free software: you can redistribute it and/or modify it 

+

9 under the terms of the GNU General Public License as published by the 

+

10 Free Software Foundation, either version 3 of the License, or 

+

11 (at your option) any later version. 

+

12 daemonpy is distributed in the hope that it will be useful, but 

+

13 WITHOUT ANY WARRANTY; without even the implied warranty of 

+

14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 

+

15 See the GNU General Public License for more details. 

+

16 You should have received a copy of the GNU General Public License along 

+

17 with this program. If not, see <http://www.gnu.org/licenses/>. 

+

18Info 

+

19 Defines class FileProcessId with attribute(s) and method(s). 

+

20 Creates an API for the file process id context management. 

+

21''' 

+

22 

+

23import sys 

+

24from typing import Any, List, IO, Optional 

+

25 

+

26try: 

+

27 from ats_utilities.checker import ATSChecker 

+

28 from ats_utilities.exceptions.ats_type_error import ATSTypeError 

+

29 from ats_utilities.exceptions.ats_value_error import ATSValueError 

+

30except ImportError as ats_error_message: # pragma: no cover 

+

31 # Force exit python ####################################################### 

+

32 sys.exit(f'\n{__file__}\n{ats_error_message}\n') # pragma: no cover 

+

33 

+

34__author__: str = 'Vladimir Roncevic' 

+

35__copyright__: str = '(C) 2025, https://vroncevic.github.io/daemonpy' 

+

36__credits__: List[str] = ['Vladimir Roncevic', 'Python Software Foundation'] 

+

37__license__: str = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' 

+

38__version__: str = '2.0.6' 

+

39__maintainer__: str = 'Vladimir Roncevic' 

+

40__email__: str = 'elektron.ronca@gmail.com' 

+

41__status__: str = 'Updated' 

+

42 

+

43 

+

44class FileProcessId: 

+

45 ''' 

+

46 Defines class FileProcessId with attribute(s) and method(s). 

+

47 Creates an API for the file process id context management. 

+

48 

+

49 It defines: 

+

50 

+

51 :attributes: 

+

52 | _PKG_VERBOSE - Console text indicator for process-phase. 

+

53 | _MODE - Supported modes for process id file. 

+

54 | _pid_path - PID file path. 

+

55 | _pid_mode - PID file mode. 

+

56 | _pid - PID file object. 

+

57 :methods: 

+

58 | __init__ - Initials FileProcessId constructor. 

+

59 | __enter__ - Opens PID file. 

+

60 | __exit__ - Closes PID file. 

+

61 ''' 

+

62 

+

63 _P_VERBOSE: str = 'DAEMONPY::FILE_PROCESS_ID' 

+

64 _MODE: List[str] = ['w+', 'r'] 

+

65 

+

66 def __init__( 

+

67 self, pid_path: Optional[str], pid_mode: Optional[str] 

+

68 ) -> None: 

+

69 ''' 

+

70 Initials FileProcessId constructor. 

+

71 

+

72 :param pid_path: file process id path | None 

+

73 :type pid_path: <Optional[str]> 

+

74 :param pid_mode: file process id mode | None 

+

75 :type pid_mode: <Optional[str]> 

+

76 :exceptions: ATSTypeError | ATSValueError 

+

77 ''' 

+

78 error_msg: Optional[str] = None 

+

79 error_id: Optional[int] = None 

+

80 checker: ATSChecker = ATSChecker() 

+

81 error_msg, error_id = checker.check_params([ 

+

82 ('str:pid_path', pid_path), ('str:pid_mode', pid_mode) 

+

83 ]) 

+

84 if error_id == checker.TYPE_ERROR: 

+

85 raise ATSTypeError(error_msg) 

+

86 if not bool(pid_path): 

+

87 raise ATSValueError('missing PID path file') 

+

88 if any([not bool(pid_mode), pid_mode not in self._MODE]): 

+

89 raise ATSValueError('check PID mode file') 

+

90 self._pid_path: Optional[str] = pid_path 

+

91 self._pid_mode: Optional[str] = pid_mode 

+

92 self._pid: Optional[IO[Any]] = None 

+

93 

+

94 def __enter__(self) -> Optional[IO[Any]]: 

+

95 ''' 

+

96 Opens PID file. 

+

97 

+

98 :return: File IO stream | None 

+

99 :rtype: <Optional[IO[Any]]> 

+

100 :exceptions: None 

+

101 ''' 

+

102 if bool(self._pid_path) and bool(self._pid_mode): 

+

103 self._pid = open( 

+

104 self._pid_path, self._pid_mode, encoding='utf-8' 

+

105 ) 

+

106 return self._pid 

+

107 

+

108 def __exit__(self, *args: Any) -> None: 

+

109 ''' 

+

110 Closes PID file. 

+

111 

+

112 :exceptions: None 

+

113 ''' 

+

114 if bool(self._pid): 

+

115 if not self._pid.closed: 

+

116 self._pid.close() 

+
+ + + diff --git a/tests/htmlcov/z_74f19c91e79fbf87_unix_operations_py.html b/tests/htmlcov/z_74f19c91e79fbf87_unix_operations_py.html new file mode 100644 index 0000000..daddcbe --- /dev/null +++ b/tests/htmlcov/z_74f19c91e79fbf87_unix_operations_py.html @@ -0,0 +1,292 @@ + + + + + Coverage for /data/dev/python/daemonpy/github/daemonpy/daemonpy/unix_operations.py: 51% + + + + + +
+
+

+ Coverage for /data/dev/python/daemonpy/github/daemonpy/daemonpy/unix_operations.py: + 51% +

+ +

+ 72 statements   + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.6.10, + created at 2025-11-03 06:53 +0100 +

+ +
+
+
+

1# -*- coding: UTF-8 -*- 

+

2 

+

3''' 

+

4Module 

+

5 unix_operations.py 

+

6Copyright 

+

7 Copyright (C) 2020 - 2025 Vladimir Roncevic <elektron.ronca@gmail.com> 

+

8 daemonpy is free software: you can redistribute it and/or modify it 

+

9 under the terms of the GNU General Public License as published by the 

+

10 Free Software Foundation, either version 3 of the License, or 

+

11 (at your option) any later version. 

+

12 daemonpy is distributed in the hope that it will be useful, but 

+

13 WITHOUT ANY WARRANTY; without even the implied warranty of 

+

14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 

+

15 See the GNU General Public License for more details. 

+

16 You should have received a copy of the GNU General Public License along 

+

17 with this program. If not, see <http://www.gnu.org/licenses/>. 

+

18Info 

+

19 Defines class UnixOperations with attribute(s) and method(s). 

+

20 Creates an API for operating Unix Like OS processes. 

+

21''' 

+

22 

+

23import sys 

+

24from typing import List, Optional 

+

25from os import fork, kill, remove 

+

26from os.path import exists 

+

27from signal import SIGTERM 

+

28from time import sleep 

+

29 

+

30try: 

+

31 from ats_utilities.checker import ATSChecker 

+

32 from ats_utilities.console_io.error import error_message 

+

33 from ats_utilities.console_io.verbose import verbose_message 

+

34 from ats_utilities.exceptions.ats_type_error import ATSTypeError 

+

35 from ats_utilities.exceptions.ats_value_error import ATSValueError 

+

36except ImportError as ats_error_message: # pragma: no cover 

+

37 # Force exit python ####################################################### 

+

38 sys.exit(f'\n{__file__}\n{ats_error_message}\n') # pragma: no cover 

+

39 

+

40__author__: str = 'Vladimir Roncevic' 

+

41__copyright__: str = '(C) 2025, https://vroncevic.github.io/daemonpy' 

+

42__credits__: List[str] = ['Vladimir Roncevic', 'Python Software Foundation'] 

+

43__license__: str = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' 

+

44__version__: str = '2.0.6' 

+

45__maintainer__: str = 'Vladimir Roncevic' 

+

46__email__: str = 'elektron.ronca@gmail.com' 

+

47__status__: str = 'Updated' 

+

48 

+

49 

+

50class UnixOperations: 

+

51 ''' 

+

52 Defines class UnixOperations with attribute(s) and method(s). 

+

53 Creates an API for operating Unix Like OS processes. 

+

54 

+

55 It defines: 

+

56 

+

57 :attributes: 

+

58 | _PKG_VERBOSE - Console text indicator for process-phase. 

+

59 | _OS_TARGET - List of supported operating systems. 

+

60 | _NO_PROCESS - No such process message. 

+

61 | _unix_status - Unix status (True for unix like OS). 

+

62 :methods: 

+

63 | __init__ - Initials UnixOperations constructor. 

+

64 | unix_status - Property methods for set/get operations. 

+

65 | first_fork - Makes sure that process is not group leader. 

+

66 | second_fork - Won't be started merely by opening a terminal. 

+

67 | unix_kill - Kills unix like OS process. 

+

68 ''' 

+

69 

+

70 _P_VERBOSE: str = 'DAEMONPY::UNIX_OPERATIONS' 

+

71 _OS_TARGET: List[str] = ['linux', 'linux2'] 

+

72 _NO_PROCESS: str = 'No such process' 

+

73 _SLEEP: float = 0.1 

+

74 

+

75 def __init__(self, verbose: bool = False) -> None: 

+

76 ''' 

+

77 Initials UnixOperations constructor. 

+

78 

+

79 :param verbose: Enable/Disable verbose option 

+

80 :type verbose: <bool> 

+

81 :exceptions: None 

+

82 ''' 

+

83 verbose_message( 

+

84 verbose, [f'{self._P_VERBOSE} init daemon operations'] 

+

85 ) 

+

86 self._unix_status: bool = any( 

+

87 sys.platform == os_target for os_target in self._OS_TARGET 

+

88 ) 

+

89 

+

90 @property 

+

91 def unix_status(self) -> bool: 

+

92 ''' 

+

93 Property method for getting unix like OS status. 

+

94 

+

95 :return: Unix like OS status 

+

96 :rtype: <bool> 

+

97 :exceptions: None 

+

98 ''' 

+

99 return self._unix_status 

+

100 

+

101 @unix_status.setter 

+

102 def unix_status(self, unix_status: bool) -> None: 

+

103 ''' 

+

104 Property method for setting unix like OS status. 

+

105 

+

106 :param unix_status: Unix like OS status 

+

107 :type unix_status: <bool> 

+

108 :exceptions: None 

+

109 ''' 

+

110 self._unix_status = unix_status 

+

111 

+

112 def first_fork(self, verbose: bool = False) -> None: 

+

113 ''' 

+

114 Makes sure that process is not group leader. 

+

115 

+

116 :param verbose: Enable/Disable verbose option 

+

117 :type verbose: <bool> 

+

118 :exit code: 0 (success fork) | 1 (failed) 

+

119 :exceptions: None 

+

120 ''' 

+

121 if self._unix_status: 

+

122 if fork() > 0: 

+

123 verbose_message(verbose, [f'{self._P_VERBOSE} first fork']) 

+

124 sys.exit(0) 

+

125 

+

126 def second_fork(self, verbose: bool = False) -> None: 

+

127 ''' 

+

128 Won't be started merely by opening a terminal. 

+

129 

+

130 :param verbose: Enable/Disable verbose option 

+

131 :type verbose: <bool> 

+

132 :exit code: 0 (success fork) | 1 (failed) 

+

133 :exceptions: None 

+

134 ''' 

+

135 if self._unix_status: 

+

136 if fork() > 0: 

+

137 verbose_message(verbose, [f'{self._P_VERBOSE} second fork']) 

+

138 sys.exit(0) 

+

139 

+

140 def unix_kill( 

+

141 self, pid: int, pid_path: str, verbose: bool = False 

+

142 ) -> bool: 

+

143 ''' 

+

144 Kills Unix Like OS process. 

+

145 

+

146 :param pid: Process ID 

+

147 :type pid: <int> 

+

148 :param pid_path: PID file path 

+

149 :type pid_path: <str> 

+

150 :param verbose: Enable/Disable verbose option 

+

151 :type verbose: <bool> 

+

152 :return: True (success operation) | False 

+

153 :rtype: <bool> 

+

154 :exceptions: ATSTypeError | ATSValueError 

+

155 ''' 

+

156 error_msg: Optional[str] = None 

+

157 error_id: Optional[int] = None 

+

158 checker: ATSChecker = ATSChecker() 

+

159 error_msg, error_id = checker.check_params([ 

+

160 ('int:pid', pid), ('str:pid_path', pid_path) 

+

161 ]) 

+

162 if error_id == checker.TYPE_ERROR: 

+

163 raise ATSTypeError(error_msg) 

+

164 if not bool(pid): 

+

165 raise ATSValueError('missing PID') 

+

166 if not bool(pid_path): 

+

167 raise ATSValueError('missing PID path') 

+

168 status: bool = False 

+

169 if self._unix_status: 

+

170 try: 

+

171 verbose_message( 

+

172 verbose, [f'{self._P_VERBOSE} kill process {pid}'] 

+

173 ) 

+

174 while True: 

+

175 kill(pid, SIGTERM) 

+

176 sleep(self._SLEEP) 

+

177 status = True 

+

178 except OSError as os_error: 

+

179 os_error = str(os_error) 

+

180 if os_error.find(self._NO_PROCESS) > 0: 

+

181 if exists(pid_path): 

+

182 verbose_message( 

+

183 verbose, 

+

184 [ 

+

185 f'{self._P_VERBOSE}', 

+

186 f'{self._NO_PROCESS}', 

+

187 f'with PID: {pid},', 

+

188 f'removing pid file {pid_path}' 

+

189 ] 

+

190 ) 

+

191 remove(pid_path) 

+

192 status = True 

+

193 else: 

+

194 error_message([f'{self._P_VERBOSE} {os_error}']) 

+

195 return status 

+
+ + + diff --git a/tests/run_coverage.sh b/tests/run_coverage.sh index 5525008..1bcb7ee 100755 --- a/tests/run_coverage.sh +++ b/tests/run_coverage.sh @@ -7,5 +7,11 @@ # @author Vladimir Roncevic # +rm -rf htmlcov daemonpy_coverage.xml daemonpy_coverage.json .coverage python3 -m coverage run -m --source=../daemonpy unittest discover -s ./ -p '*_test.py' -vvv -python3 -m coverage html +python3 -m coverage html -d htmlcov +python3 -m coverage xml -o daemonpy_coverage.xml +python3 -m coverage json -o daemonpy_coverage.json +python3 -m coverage report --format=markdown -m +python3 ats_coverage.py -n daemonpy +echo "Done" diff --git a/tests/unix_operations_test.py b/tests/unix_operations_test.py index c9b75c3..b5558f0 100644 --- a/tests/unix_operations_test.py +++ b/tests/unix_operations_test.py @@ -4,7 +4,7 @@ Module unix_operations_test.py Copyright - Copyright (C) 2020 - 2024 Vladimir Roncevic + Copyright (C) 2020 - 2025 Vladimir Roncevic codecipher is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -32,14 +32,14 @@ # Force close python test ################################################# sys.exit(f'\n{__file__}\n{test_error_message}\n') -__author__ = 'Vladimir Roncevic' -__copyright__ = '(C) 2024, https://vroncevic.github.io/daemonpy' +__author__: str = 'Vladimir Roncevic' +__copyright__: str = '(C) 2025, https://vroncevic.github.io/daemonpy' __credits__: List[str] = ['Vladimir Roncevic', 'Python Software Foundation'] -__license__ = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' -__version__ = '2.0.5' -__maintainer__ = 'Vladimir Roncevic' -__email__ = 'elektron.ronca@gmail.com' -__status__ = 'Updated' +__license__: str = 'https://github.com/vroncevic/daemonpy/blob/dev/LICENSE' +__version__: str = '2.0.6' +__maintainer__: str = 'Vladimir Roncevic' +__email__: str = 'elektron.ronca@gmail.com' +__status__: str = 'Updated' class UnixOperationsTestCase(unittest.TestCase):