diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index c6cdaf0..a3bbe27 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,10 +1,9 @@ --- name: Bug report about: Create a report to help us improve -title: '' +title: "" labels: bug -assignees: '' - +assignees: "" --- **Describe the bug** @@ -12,6 +11,7 @@ A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' @@ -24,14 +24,16 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - - OS: [e.g. iOS] - - OS Version: [e.g. 22] - - Programm version or release: [e.g. v1.0] + +- OS: [e.g. iOS] +- OS Version: [e.g. 22] +- Programm version or release: [e.g. v1.0] **Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS Version: [e.g. 22] - - Programm version or release: [e.g. v1.0] + +- Device: [e.g. iPhone6] +- OS Version: [e.g. 22] +- Programm version or release: [e.g. v1.0] **Additional context** Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 2dca65b..bc89a10 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,3 +1,2 @@ blank_issues_enabled: true contact_links: - diff --git a/.github/ISSUE_TEMPLATE/docs-request.md b/.github/ISSUE_TEMPLATE/docs-request.md index 9db2d28..06acdbf 100644 --- a/.github/ISSUE_TEMPLATE/docs-request.md +++ b/.github/ISSUE_TEMPLATE/docs-request.md @@ -1,10 +1,9 @@ --- name: Docs request about: Suggest an idea for this project -title: '' +title: "" labels: documentation -assignees: '' - +assignees: "" --- **Is your feature request related to a problem? Please describe.** diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 11fc491..d883b8f 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,10 +1,9 @@ --- name: Feature request about: Suggest an idea for this project -title: '' +title: "" labels: enhancement -assignees: '' - +assignees: "" --- **Is your feature request related to a problem? Please describe.** diff --git a/.github/ISSUE_TEMPLATE/task--todo-.md b/.github/ISSUE_TEMPLATE/task--todo-.md index 9bbdce6..710b23a 100644 --- a/.github/ISSUE_TEMPLATE/task--todo-.md +++ b/.github/ISSUE_TEMPLATE/task--todo-.md @@ -3,14 +3,13 @@ name: Task (TODO) about: Elaborate a task that has to be done title: "TODO: Title" labels: TODO -assignees: '' - +assignees: "" --- - Describe the task in general. Say what it is about. What it aims for. ## Thoughts + State and explain your thoughts. ## Additional stuff diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 35264bc..569ed4a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,24 +1,23 @@ _optional description text_ - ## Changes -- _add Changes_ -**Fixes _add Links_** +- _add Changes_ +**Fixes _add Links_** ## Screenshots -(prefer animated gif) +(prefer animated gif) ## Checklist -- [ ] tested locally -- [ ] added new dependencies -- [ ] updated the docs -- [ ] added a test +- [ ] tested locally +- [ ] added new dependencies +- [ ] updated the docs +- [ ] added a test + +## Notes -Notes ------ _Add notes here._ _For example sth that has to be watched out for_ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b9d0c68..01793f5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: 2c9f875913ee60ca25ce70243dc24d5b6415598c # frozen: v4.6.0 + rev: 2c9f875913ee60ca25ce70243dc24d5b6415598c # frozen: v4.6.0 hooks: # check file system problems - id: check-case-conflict @@ -30,13 +30,13 @@ repos: - id: check-merge-conflict - repo: https://github.com/abravalheri/validate-pyproject - rev: bea368871c59605bf2471441d0c6214bd3b80c44 # frozen: v0.18 + rev: bea368871c59605bf2471441d0c6214bd3b80c44 # frozen: v0.18 hooks: - id: validate-pyproject files: pyproject.toml$ - repo: https://github.com/editorconfig-checker/editorconfig-checker.python - rev: 2b74735540f79457a50369e5c17a2c35d52c3298 # frozen: 2.7.3 + rev: 2b74735540f79457a50369e5c17a2c35d52c3298 # frozen: 2.7.3 hooks: - id: editorconfig-checker alias: ec @@ -44,7 +44,7 @@ repos: - "--disable-indent-size" - repo: https://github.com/google/yapf - rev: 3e6cd99b6652cd00867b86e5e82a1dbccc5c0f72 # frozen: v0.40.2 + rev: 3e6cd99b6652cd00867b86e5e82a1dbccc5c0f72 # frozen: v0.40.2 hooks: - id: yapf exclude: /ui_[^/]+\.py$|_res.py$ @@ -52,19 +52,19 @@ repos: - "toml" - repo: https://github.com/pycqa/isort - rev: c235f5e450b4b84e58d114ed4c589cbf454175a3 # frozen: 5.13.2 + rev: c235f5e450b4b84e58d114ed4c589cbf454175a3 # frozen: 5.13.2 hooks: - id: isort name: isort (python) exclude: /ui_[^/]+\.py$|_res.py$ - repo: https://github.com/pre-commit/mirrors-prettier - rev: "f12edd9c7be1c20cfa42420fd0e6df71e42b51ea" # frozen: v4.0.0-alpha.8 + rev: "f12edd9c7be1c20cfa42420fd0e6df71e42b51ea" # frozen: v4.0.0-alpha.8 hooks: - id: prettier - repo: https://github.com/rstcheck/rstcheck - rev: "445861c31d8134562e723d77115721038de2e687" # frozen: v6.2.0 + rev: "445861c31d8134562e723d77115721038de2e687" # frozen: v6.2.0 hooks: - id: rstcheck additional_dependencies: [sphinx] @@ -82,7 +82,7 @@ repos: # - id: mypy - repo: https://github.com/PyCQA/bandit - rev: "22b4226078b041a16bf05163347a66ab4dbcf3a5" # frozen: 1.7.8 + rev: "22b4226078b041a16bf05163347a66ab4dbcf3a5" # frozen: 1.7.8 hooks: - id: bandit files: "python/ectec-gui/src/" diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 09827eb..c59995c 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -14,22 +14,22 @@ appearance, race, religion, or sexual identity and orientation. Examples of behavior that contributes to creating a positive environment include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting ## Our Responsibilities diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6e95480..5dcb5d9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,23 +3,25 @@ ## 1. Always create a Pull Request with a reasonable description ## 2. Document your code in detail - - Wiki entries are welcome! Please keep the wiki well structured. - - Each function, class, method and file needs a docstring - - The numpy docsting format is preferred *(in python code)* - - Use English *or occasionally German* + +- Wiki entries are welcome! Please keep the wiki well structured. +- Each function, class, method and file needs a docstring +- The numpy docsting format is preferred _(in python code)_ +- Use English _or occasionally German_ ## 3. Use the functions provided by git and Github ## 4. Python Coding - - Use setter and getters through *property* decorators if needed. - +- Use setter and getters through _property_ decorators if needed. + + +## 5. Create Tests whenever possible - ## 5. Create Tests whenever possible - - Use python *unittest* library for python code. +- Use python _unittest_ library for python code. +--- -*** -*By contributing you agree that your contribution will be licensed under the same license as this repository and you affirm the [Developer Certificate of Origin][dco].* +_By contributing you agree that your contribution will be licensed under the same license as this repository and you affirm the [Developer Certificate of Origin][dco]._ [dco]: https://developercertificate.org/ diff --git a/README.md b/README.md index e002531..55b034f 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ in `python/ectec-gui/README.md`. ### Install as a python package First you will need to install a recent version of -[Python](https://www.python.org/) *(>=3.7)* including the pip package installer. +[Python](https://www.python.org/) _(>=3.7)_ including the pip package installer. Then head to the latest release of this repository. Download either the wheel distribution named `ectec-.whl` or the sdist distribution named `ectec-.tar.gz`. @@ -53,7 +53,6 @@ This installation method doesn't install the application system-wide but instead supplies a portable version of the app that is contained in one folder. - ## User Guide The chattool consists of two programs (graphical user interfaces). diff --git a/plan/ROADMAP.md b/plan/ROADMAP.md index 8d34c14..c805c8e 100644 --- a/plan/ROADMAP.md +++ b/plan/ROADMAP.md @@ -1,68 +1,67 @@ # Version 1.0.0 -- [x] Client User Role (UR) -- [x] Connect to server with role and name -- [x] UR: Mantain list of users -- [x] UR: Send raw text messages to a one or multiple persons -- [x] UR: Receive raw text messages -- [x] UR: Chat view +- [x] Client User Role (UR) +- [x] Connect to server with role and name +- [x] UR: Mantain list of users +- [x] UR: Send raw text messages to a one or multiple persons +- [x] UR: Receive raw text messages +- [x] UR: Chat view
-- [x] Server (S) -- [x] S: Let users connect -- [x] S: List connected users -- [x] S: Forward messages -- [x] S: Tell users to update users list +- [x] Server (S) +- [x] S: Let users connect +- [x] S: List connected users +- [x] S: Forward messages +- [x] S: Tell users to update users list
-- [x] *About* +- [x] _About_
-- [x] S: Unbound (free) port detection -- [x] S: Kicking clients -- [x] S: Blocking any new clients option +- [x] S: Unbound (free) port detection +- [x] S: Kicking clients +- [x] S: Blocking any new clients option # Version 1.1.0 -- [ ] Reporting assistant +- [ ] Reporting assistant # Version 1.2.0 -- [ ] UR: Filter messages by receiver -- [ ] UR: Display time a message was received -- [ ] Detection of servers in the network -- [ ] Prevent client from memory overflow by a too long history. -- [ ] S: Displaying server logs in the GUI +- [ ] UR: Filter messages by receiver +- [ ] UR: Display time a message was received +- [ ] Detection of servers in the network +- [ ] Prevent client from memory overflow by a too long history. +- [ ] S: Displaying server logs in the GUI # Version 1.3.0 -- [ ] Display of rich text messages -- [ ] Formatting of messages (bold, italic, encrypted/plain) -- [ ] Markdown editor, html editor, preview/editor +- [ ] Display of rich text messages +- [ ] Formatting of messages (bold, italic, encrypted/plain) +- [ ] Markdown editor, html editor, preview/editor # Version 1.4.0 -- [ ] Client Person in the Middle Role (MR) +- [ ] Client Person in the Middle Role (MR) # Version 1.5.0 -- [ ] UR: Encryption using RSA -- [ ] UR: Encryption using AES -- [ ] UR: Hashing -- [ ] UR: Signing - +- [ ] UR: Encryption using RSA +- [ ] UR: Encryption using AES +- [ ] UR: Hashing +- [ ] UR: Signing # Planned -- [ ] Displaying client logs in the GUI +- [ ] Displaying client logs in the GUI -- [ ] Context help (F1 key) -- [ ] Help document for students -- [ ] Tasks and technical information in the Help document -- [ ] Developer docs connected to the GUI and context help +- [ ] Context help (F1 key) +- [ ] Help document for students +- [ ] Tasks and technical information in the Help document +- [ ] Developer docs connected to the GUI and context help -- [ ] Information for getting in touch -- [ ] Settings for default values +- [ ] Information for getting in touch +- [ ] Settings for default values diff --git a/plan/basic-protocol.txt b/plan/basic-protocol.txt index 74938c1..e028e43 100644 --- a/plan/basic-protocol.txt +++ b/plan/basic-protocol.txt @@ -239,5 +239,3 @@ state #END:close state :end {pass} - - diff --git a/python/ectec-gui/README.md b/python/ectec-gui/README.md index 73ee5b5..3974da5 100644 --- a/python/ectec-gui/README.md +++ b/python/ectec-gui/README.md @@ -5,19 +5,19 @@ of the Ectec chattool. The GUI uses the ectec package which is contained in the packaged python distribution as a back-end. -- [Ectec GUI](#ectec-gui) - - [Building from Source](#building-from-source) - - [Building the python package](#building-the-python-package) - - [Building the PyInstaller distributable](#building-the-pyinstaller-distributable) - - [Development](#development) - - [Testing](#testing) - - [QT Resource System](#qt-resource-system) - - [QT Designer and `.ui` files](#qt-designer-and-ui-files) - - [QT Internationalization](#qt-internationalization) +- [Ectec GUI](#ectec-gui) + - [Building from Source](#building-from-source) + - [Building the python package](#building-the-python-package) + - [Building the PyInstaller distributable](#building-the-pyinstaller-distributable) + - [Development](#development) + - [Testing](#testing) + - [QT Resource System](#qt-resource-system) + - [QT Designer and `.ui` files](#qt-designer-and-ui-files) + - [QT Internationalization](#qt-internationalization) ## Building from Source -You will need a complete *Python 3* installation for the following steps. +You will need a complete _Python 3_ installation for the following steps. And you will need to know how to call the commands `python` or `pip` from the command line. Often these commands have a slightly different name: `python3` and `pip3`. @@ -26,54 +26,51 @@ the command line. Often these commands have a slightly different name: 1. Install the `build` and the `wheel` python package. - ```bash - > pip install -U build wheel - ``` + ```bash + > pip install -U build wheel + ``` 2. Enter the directory of this file (python/ectecgui/). 3. Build the python package. - ```bash - > python -m build -s -w - ``` + ```bash + > python -m build -s -w + ``` The wheel and the source distributable python package can now be found in the dist folder. - - ### Building the PyInstaller distributable 1. Create a virtual python environment in the folder `env`: - ```bash - > python -m venv env - ``` + ```bash + > python -m venv env + ``` 2. Enter/source the virtual environment. In bash type: - ```bash - > source env/bin/activate - ``` + ```bash + > source env/bin/activate + ``` 3. Enter the directory `python/ectecgui` of this repository. 4. Install `ectec` and `ectecgui` in the virtual environment. - ```bash - > python install . - ``` + ```bash + > python install . + ``` 5. Run pyinstaller. - ```bash - > python -m PyInstaller pyinstall.spec - ``` + ```bash + > python -m PyInstaller pyinstall.spec + ``` 6. You will find the distributable build (the `Ectec` folder) in `./dist/`. - ## Development ### Testing @@ -106,42 +103,44 @@ To use the resources specified inside a qt resource file named `assets.qrc` in t 1. Configure the resource file in `dodo.py`: - ```python - RESOURCES = {..., - "path/to/assets.qrc" : "parent/directory/of/python/module/" - } + ```python + RESOURCES = {..., + "path/to/assets.qrc" : "parent/directory/of/python/module/" + } + + ``` 2. Run the build command that was introduced previously with `PyQt5` installed. - ```bash - > doit - -- qrc:res/breeze.qrc - -- rcc:res/breeze.qrc - . rcc:path/to/assets.qrc - -- uic:res/clientConnect.ui - -- uic:res/clientUser.ui - -- uic:res/server.ui - -- uic:res/about.ui - -- pro - -- lupdate - -- lrelease - -- rcc:res/ectec.qrc - ``` + ```bash + > doit + -- qrc:res/breeze.qrc + -- rcc:res/breeze.qrc + . rcc:path/to/assets.qrc + -- uic:res/clientConnect.ui + -- uic:res/clientUser.ui + -- uic:res/server.ui + -- uic:res/about.ui + -- pro + -- lupdate + -- lrelease + -- rcc:res/ectec.qrc + ``` 3. A python module named `assets_res.py` was created in the directory specified. Import the python module in the python code you want to use the resources in. - ```python - import assets_res - ``` + ```python + import assets_res + ``` 4. You can access the resources when using the qt framework by starting the path with `:`. If you defined an image file with the path `images/icon.png` in the resource file you will be able to access it e.g. using `QtCore.QFile` like so: - ```python - file = QtCore.QFile(':/images/icon.png') - ``` + ```python + file = QtCore.QFile(':/images/icon.png') + ``` ### QT Designer and `.ui` files @@ -152,59 +151,59 @@ To use a UI from an `.ui`-File follow these steps: 1. Configure the form file in `dodo.py`. - ```python - FORMS = {..., - "path/to/form.ui" : "path/to/python/module.py"} - ``` + ```python + FORMS = {..., + "path/to/form.ui" : "path/to/python/module.py"} + ``` 2. Run the build command that was introduced previously with `PyQt5` installed. - ```bash - > doit - -- qrc:res/breeze.qrc - -- rcc:res/breeze.qrc - -- uic:res/clientConnect.ui - -- uic:res/clientUser.ui - -- uic:res/server.ui - -- uic:res/about.ui - . uic:path/to/form.ui - -- pro - -- lupdate - -- lrelease - -- rcc:res/ectec.qrc - ``` + ```bash + > doit + -- qrc:res/breeze.qrc + -- rcc:res/breeze.qrc + -- uic:res/clientConnect.ui + -- uic:res/clientUser.ui + -- uic:res/server.ui + -- uic:res/about.ui + . uic:path/to/form.ui + -- pro + -- lupdate + -- lrelease + -- rcc:res/ectec.qrc + ``` 3. Create import statements of as the following in the `__init__` of the module with the python _ui_ object class. This allows the UI to import the resource file's python module from within the same module. - Example: + Example: - ```python - import ..ectec_res as ectec_res - ``` + ```python + import ..ectec_res as ectec_res + ``` 4. Import the UI in the module you want to implement the `QDialog`. - Example: + Example: - ```python - from .maindialog import Ui_Main - ``` + ```python + from .maindialog import Ui_Main + ``` 5. Load the UI inside the dialog's init method. - Example: + Example: - ```python - self.ui = Ui_Main() - self.ui.setupUi(self) - ``` + ```python + self.ui = Ui_Main() + self.ui.setupUi(self) + ``` ### QT Internationalization The QT framework provides a bunch of tools for internationalization that allow the GUI to be available in multiple languages. The `lupdate` or `pylupdate5` command collects the strings to be translated into a `.ts` -translation file. This file can be opened with *QT Linguist* to translate the strings into a target language and store +translation file. This file can be opened with _QT Linguist_ to translate the strings into a target language and store the translation in the translation file as well. It is importent to wrap every UI string with a call to the `QtApplication`'s translate function so that it is @@ -216,6 +215,7 @@ _tr = QtApplication.translate ``` The strings that should be translated should then be wrapped by `_tr`: + ```python dialog.setTitle(_tr("The context of the string", "The title in developer English")) @@ -244,7 +244,7 @@ described [above](#qt-designer-and-ui-files). The translation files wished have TRANSLATIONS = ["res/ectecgui.en.ts", "res/ectecgui.de.ts"] ``` -The dot between `ectecgui` and the *language code* must be in place. +The dot between `ectecgui` and the _language code_ must be in place. The `.ts` files can be created or updated by running the previously introduced build command: diff --git a/python/ectec-gui/src/ectec/__init__.py b/python/ectec-gui/src/ectec/__init__.py index 5db5dff..e0619e3 100644 --- a/python/ectec-gui/src/ectec/__init__.py +++ b/python/ectec-gui/src/ectec/__init__.py @@ -40,11 +40,11 @@ # ---- Logging -logger = logs.getLogger(__name__) # Parent logger for the module +logger = logs.getLogger(__name__) # Parent logger for the module logger.setLevel(logs.DEBUG) # Disable logging output sot that users of this lib can log as desired. -nullhandler = logs.NullHandler() # Bin for logs +nullhandler = logs.NullHandler() # Bin for logs logger.addHandler(nullhandler) # ---- Exceptions Client Side diff --git a/python/ectec-gui/src/ectec/client.py b/python/ectec-gui/src/ectec/client.py index eb61ba9..faf7cfe 100644 --- a/python/ectec-gui/src/ectec/client.py +++ b/python/ectec-gui/src/ectec/client.py @@ -482,7 +482,7 @@ def filter_recipient(self, *recipients: str): for recipient in recipients: if recipient in pkg.recipient: yield pkg - break # breaks inner loop + break # breaks inner loop # ---- Client - General @@ -513,13 +513,13 @@ class Client: This class provides methods useful for all Ectec clients. """ - TIMEOUT = 2 #: s timeout for awaited commands + TIMEOUT = 2 #: s timeout for awaited commands - TRANSMISSION_TIMEOUT = 0.200 #: seconds of timeout between parts - COMMAND_TIMEOUT = 0.300 #: s timeout for a command to end - SOCKET_BUFSIZE = 8192 #: bytes to read from socket at once - COMMAND_SEPERATOR = b'\n' #: seperates commands of the ectec protocol - COMMAND_LENGTH = 4096 #: bytes - the maximum length of a command + TRANSMISSION_TIMEOUT = 0.200 #: seconds of timeout between parts + COMMAND_TIMEOUT = 0.300 #: s timeout for a command to end + SOCKET_BUFSIZE = 8192 #: bytes to read from socket at once + COMMAND_SEPERATOR = b'\n' #: seperates commands of the ectec protocol + COMMAND_LENGTH = 4096 #: bytes - the maximum length of a command def __init__(self): self.socket: socket.SocketType = None @@ -563,7 +563,7 @@ def recv_bytes(self, length, start_timeout=None, timeout=None) -> bytes: self.socket.settimeout(start_timeout) try: - msg = self.socket.recv(bufsize) # msg was empty + msg = self.socket.recv(bufsize) # msg was empty except socket.timeout as error: raise CommandTimeout( "The receiving of the data timed out.") from error @@ -572,7 +572,7 @@ def recv_bytes(self, length, start_timeout=None, timeout=None) -> bytes: # connection was closed raise ConnectionClosed("The connection was closed.") - if len(msg) >= length: # separator found + if len(msg) >= length: # separator found data = msg[:length] self._buffer = msg[length:] @@ -589,7 +589,7 @@ def recv_bytes(self, length, start_timeout=None, timeout=None) -> bytes: data_length = len(msg) needed = length - data_length - while True: # ends by an error or a return + while True: # ends by an error or a return try: part = self.socket.recv(bufsize) except socket.timeout as error: @@ -671,7 +671,7 @@ class variable `COMMAND_SEPERATOR`. Commands also must not exceed a self.socket.settimeout(start_timeout) try: - msg = self.socket.recv(bufsize) # msg was empty + msg = self.socket.recv(bufsize) # msg was empty except socket.timeout as error: raise CommandTimeout( "The receiving of the command timed out.") from error @@ -683,9 +683,9 @@ class variable `COMMAND_SEPERATOR`. Commands also must not exceed a # check buffer or first data received i = msg.find(seperator) - if i >= 0: # separator found + if i >= 0: # separator found command = msg[:i] - self._buffer = msg[i + len(seperator):] # seperator is removed + self._buffer = msg[i + len(seperator):] # seperator is removed # for expected behavoir check length of command cmd_length = len(command) @@ -706,7 +706,7 @@ class variable `COMMAND_SEPERATOR`. Commands also must not exceed a timeout = timeout * 0.000000001 if timeout is not None else None length = len(msg) - while True: # ends by an error or a return + while True: # ends by an error or a return try: part = self.socket.recv(bufsize) except socket.timeout as error: @@ -718,8 +718,8 @@ class variable `COMMAND_SEPERATOR`. Commands also must not exceed a time_elapsed = time.perf_counter_ns() - start_time - i = part.find(seperator) # end of command? - if i >= 0: # separator found + i = part.find(seperator) # end of command? + if i >= 0: # separator found command = msg + part[:i] # seperator is removed self._buffer = part[i + len(seperator):] @@ -1080,7 +1080,7 @@ def run(self): # Handle UPDATE USERS command res = self.client.parse_update(cmd) - if res is not False: # in case of empty list + if res is not False: # in case of empty list # update users self.client._update_users(res) continue @@ -1266,7 +1266,7 @@ def __exit__(self, exc_type, exc_value, traceback): self.client.disconnect() # wether to raise the Exception or suppress - return False # do not suppress + return False # do not suppress def connect(self, server: str, port: int): """ @@ -1377,7 +1377,7 @@ def disconnect(self): """ if self._thread and self._thread.is_alive(): self._thread.end.set() - self._thread.join() # raises an exception if thread isn't alive + self._thread.join() # raises an exception if thread isn't alive if self.socket: self.socket.close() diff --git a/python/ectec-gui/src/ectec/logs.py b/python/ectec-gui/src/ectec/logs.py index fcd8333..6da9889 100644 --- a/python/ectec-gui/src/ectec/logs.py +++ b/python/ectec-gui/src/ectec/logs.py @@ -59,12 +59,12 @@ def indent(text, by, space=" ", prefix="", suffix=""): The indented text. """ - t = "" # Stores the indented lines and will be returned + t = "" # Stores the indented lines and will be returned # Iterate of the lines of the text for l in text.splitlines(True): # The indented line is added to the result - t += prefix + by*space + suffix + l + t += prefix + by * space + suffix + l return t @@ -87,5 +87,5 @@ def formatException(self, exc_info): """ Format an exception so that it prints on a single line. """ - result = super().formatException(exc_info) # Super formats for us. - return indent(result, 2, prefix="| >>>") # Indent output + result = super().formatException(exc_info) # Super formats for us. + return indent(result, 2, prefix="| >>>") # Indent output diff --git a/python/ectec-gui/src/ectec/server.py b/python/ectec-gui/src/ectec/server.py index 9170b31..68ee05c 100644 --- a/python/ectec-gui/src/ectec/server.py +++ b/python/ectec-gui/src/ectec/server.py @@ -81,6 +81,7 @@ class ConnectionAdapter(logging.LoggerAdapter): More context. The default is {}. """ + def __init__(self, logger, remote, extra=None): """ Adapter to add connection context information. @@ -264,13 +265,13 @@ class ClientHandler(socketserver.BaseRequestHandler): """ # ---- Process (server) wide constants - TIMEOUT = 0.5 #: s timeout for awaited commands + TIMEOUT = 0.5 #: s timeout for awaited commands - TRANSMISSION_TIMEOUT = 0.200 #: seconds of timeout between parts - COMMAND_TIMEOUT = 0.300 #: s timeout for a command to end - SOCKET_BUFSIZE = 8192 #: bytes to read from socket at once - COMMAND_SEPERATOR = b'\n' #: seperates commands of the ectec protocol - COMMAND_LENGTH = 4096 #: bytes - the maximum length of a command + TRANSMISSION_TIMEOUT = 0.200 #: seconds of timeout between parts + COMMAND_TIMEOUT = 0.300 #: s timeout for a command to end + SOCKET_BUFSIZE = 8192 #: bytes to read from socket at once + COMMAND_SEPERATOR = b'\n' #: seperates commands of the ectec protocol + COMMAND_LENGTH = 4096 #: bytes - the maximum length of a command #: Users with the listed roles are shared with all clients PUBLIC_ROLES = [Role.USER] @@ -310,7 +311,7 @@ def setup(self): self.client_data: ClientData = None #: stores received bytes that belong to the next command - self.buffer: bytes = bytes(0) # bytes object with zero bytes + self.buffer: bytes = bytes(0) # bytes object with zero bytes #: lock used when accessing socket for sending self.sending_lock = threading.Lock() @@ -428,9 +429,9 @@ def run(self): if not compatible: # Version is incompatible - self.send_info(False) # Tell the client + self.send_info(False) # Tell the client self.log.debug('Incompatible version. Refused') - return # The connection socket is closed automatically + return # The connection socket is closed automatically self.log.debug("Version check passed. Sending answer.") @@ -453,14 +454,14 @@ def run(self): raise RequestRefusedError( f"'{role_str}' is not a valid role") from None - if role_str not in self.clients: # Defined as class variable + if role_str not in self.clients: # Defined as class variable raise RequestRefusedError(f"'{role_str}' is not a valid role") # Check if name is already used with self.Locks.clients: for dummy, client_list in self.clients.items(): - for client in client_list: # client : ClientData - if client.name.lower() == name.lower(): # Design choice + for client in client_list: # client : ClientData + if client.name.lower() == name.lower(): # Design choice # Name in use raise RequestRefusedError( f"'{name}' collides with an existing clients name") @@ -532,7 +533,7 @@ def handle_user(self): while True: try: package = self.recv_pkg() - except (OSError, ConnectionClosed) as error: # Connection closed + except (OSError, ConnectionClosed) as error: # Connection closed return except CommandTimeout as error: # wait for next command @@ -596,7 +597,7 @@ def recv_bytes(self, length, start_timeout=None, timeout=None) -> bytes: self.request.settimeout(start_timeout) try: - msg = self.request.recv(bufsize) # msg was empty + msg = self.request.recv(bufsize) # msg was empty except socket.timeout as error: raise CommandTimeout( "The receiving of the data timed out.") from error @@ -606,7 +607,7 @@ def recv_bytes(self, length, start_timeout=None, timeout=None) -> bytes: raise ConnectionClosed( "The connection was closed by the client.") - if len(msg) >= length: # separator found + if len(msg) >= length: # separator found data = msg[:length] self.buffer = msg[length:] @@ -623,7 +624,7 @@ def recv_bytes(self, length, start_timeout=None, timeout=None) -> bytes: data_length = len(msg) needed = length - data_length - while True: # ends by an error or a return + while True: # ends by an error or a return try: part = self.request.recv(bufsize) except socket.timeout as error: @@ -704,7 +705,7 @@ class variable `COMMAND_SEPERATOR`. Commands also must not exceed a self.request.settimeout(start_timeout) try: - msg = self.request.recv(bufsize) # msg was empty + msg = self.request.recv(bufsize) # msg was empty except socket.timeout as error: raise CommandTimeout( "The receiving of the command timed out.") from error @@ -717,9 +718,9 @@ class variable `COMMAND_SEPERATOR`. Commands also must not exceed a # check buffer or first data received i = msg.find(seperator) - if i >= 0: # separator found + if i >= 0: # separator found command = msg[:i] - self.buffer = msg[i + len(seperator):] # seperator is removed + self.buffer = msg[i + len(seperator):] # seperator is removed # for expected behavoir check length of command cmd_length = len(command) @@ -740,7 +741,7 @@ class variable `COMMAND_SEPERATOR`. Commands also must not exceed a timeout = timeout * 0.000000001 if timeout is not None else None length = len(msg) - while True: # ends by an error or a return + while True: # ends by an error or a return try: part = self.request.recv(bufsize) except socket.timeout as error: @@ -753,10 +754,11 @@ class variable `COMMAND_SEPERATOR`. Commands also must not exceed a time_elapsed = time.perf_counter_ns() - start_time - i = part.find(seperator) # end of command? - if i >= 0: # separator found + i = part.find(seperator) # end of command? + if i >= 0: # separator found command = msg + part[:i] - self.buffer = part[i + len(seperator):] # seperator is removed + self.buffer = part[i + + len(seperator):] # seperator is removed # for expected behavoir check length of command cmd_length = len(command) @@ -816,7 +818,7 @@ def recv_info(self): cmd = raw_cmd.decode(encoding='utf-8', errors='backslashreplace') # match regular expression - match = self.regex_info.fullmatch(cmd) # should match the whole text + match = self.regex_info.fullmatch(cmd) # should match the whole text if not match: raise CommandError("Received data doesn't match INFO command.") @@ -1217,7 +1219,7 @@ def shutdown_request(self, request): # the socket and waits for GC to perform the actual close. request.shutdown(socket.SHUT_RDWR) except OSError: - pass # some platforms may raise ENOTCONN here + pass # some platforms may raise ENOTCONN here self.close_request(request) def process_request_thread(self, request, client_address): @@ -1246,12 +1248,12 @@ def process_request(self, request, client_address): t.start() # Extra part - t.name = str(client_address[0]) # name thread after ip - t.request_socket = request # add attribute + t.name = str(client_address[0]) # name thread after ip + t.request_socket = request # add attribute def server_close(self): """Cleanup the server.""" - super().server_close() # should call socketserver.TCPServer's method + super().server_close() # should call socketserver.TCPServer's method # Changed from socketserver.ThreadingMixIn if self.block_on_close: @@ -1428,20 +1430,21 @@ def running(self) -> bool: class ServerRunningContextManager: """The ContextManager for a running server.""" + def __init__(self, server): """Init the ContextManager""" self.server = server def __enter__(self): """Enter the context. Does nothing.""" - return self # not necessary + return self # not necessary def __exit__(self, exc_type, exc_value, traceback): """Exit the context and stop the server.""" self.server.stop() # wether to raise the Exception or suppress - return False # do not suppress + return False # do not suppress def start(self, port: int, address: str = ""): """ diff --git a/python/ectec-gui/src/ectec/version.py b/python/ectec-gui/src/ectec/version.py index dc9370a..c464b6b 100644 --- a/python/ectec-gui/src/ectec/version.py +++ b/python/ectec-gui/src/ectec/version.py @@ -35,6 +35,7 @@ class VersionException(Exception): Superclass of all Exceptions related to versions """ + class Version: """ The superclass of all Versions. @@ -54,6 +55,7 @@ class SubVersionException(SemanticVersionException): Raise if there are too many subversions in a semantic version. """ + @total_ordering class SemanticVersion(Version): """ diff --git a/python/ectec-gui/src/ectecgui/__init__.py b/python/ectec-gui/src/ectecgui/__init__.py index c33e769..a9157ce 100644 --- a/python/ectec-gui/src/ectecgui/__init__.py +++ b/python/ectec-gui/src/ectecgui/__init__.py @@ -45,7 +45,7 @@ # ---- Logging --------------------------------------------------------------- -logger = logging.getLogger(__name__) # Parent logger for the module +logger = logging.getLogger(__name__) # Parent logger for the module logger.setLevel(logs.DEBUG) # Disable default logging behaviour by creating bin for log events diff --git a/python/ectec-gui/src/ectecgui/client/chatview.py b/python/ectec-gui/src/ectecgui/client/chatview.py index dbb2584..d9af22b 100644 --- a/python/ectec-gui/src/ectecgui/client/chatview.py +++ b/python/ectec-gui/src/ectecgui/client/chatview.py @@ -49,6 +49,7 @@ class ModelPackageStorage(PackageStorage): model : EctecPackageModel The connected model. """ + def __init__(self, model: 'EctecPackageModel'): """ Init a PackageStorage that is connected to a Model. @@ -131,6 +132,7 @@ class EctecPackageModel(QAbstractListModel): The ModelPackageStorage used by this Model, by default None """ + def __init__(self, parent=None, storage: ModelPackageStorage = None) -> None: @@ -198,6 +200,7 @@ class ChatViewDelegate(QStyledItemDelegate): The color role for the bubble's border. """ + def __init__(self, local_name: str, parent: Optional[QObject] = None): """ Init the delegate. @@ -480,12 +483,13 @@ def helpEvent(self, event: QHelpEvent, view: QAbstractItemView, tooltipRect = receiver_rect else: # no tooltip available - QToolTip.hideText() # hiding the tooltip isn't done automatically - event.ignore() # necessary after hiding the tooltip + QToolTip.hideText( + ) # hiding the tooltip isn't done automatically + event.ignore() # necessary after hiding the tooltip return False # pos to global - tooltipPos = event.globalPos() # cursor position on screen\ + tooltipPos = event.globalPos() # cursor position on screen\ # Show tooltip QToolTip.showText(tooltipPos, tooltipText, view, tooltipRect) @@ -536,7 +540,7 @@ def calculate( -self.margin - self.border_width, -self.margin - self.border_width) indent = int(bubble_frame.width() * (1 - self.rel_size)) - if local: # message by local client + if local: # message by local client bubble_frame.adjust(indent, 0, 0, 0) else: bubble_frame.adjust(0, 0, -indent, 0) @@ -557,7 +561,7 @@ def calculate( receiver_text) font.setItalic(False) - if local: # message by local client + if local: # message by local client title_rect = QRect(0, 0, inner_frame.width(), receiver_rect.height()) title_rect.translate(inner_frame.topLeft()) @@ -669,6 +673,7 @@ class ChatView(QListView): it should be able to cope with data of type `Package`, by default ChatViewDelegate. """ + def __init__(self, parent=None, local_name='', delegate=None): """ Init. @@ -705,7 +710,8 @@ def setLocalName(self, local_name: str): """Set `local_name` in qt fashion.""" self.local_name = str(local_name) if isinstance(self.itemDelegate(), ChatViewDelegate): - delegate = cast(ChatViewDelegate, self.itemDelegate()) # type hint + delegate = cast(ChatViewDelegate, + self.itemDelegate()) # type hint delegate.local_name = local_name def localName(self) -> str: diff --git a/python/ectec-gui/src/ectecgui/client/qobjects.py b/python/ectec-gui/src/ectecgui/client/qobjects.py index 0b780b3..d275c5f 100644 --- a/python/ectec-gui/src/ectecgui/client/qobjects.py +++ b/python/ectec-gui/src/ectecgui/client/qobjects.py @@ -36,11 +36,10 @@ logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) - # ---- Validators ------------------------------------------------------------ # host name re as specified in https://www.rfc-editor.org/rfc/rfc952 as hname -name = r"(?!-)[a-zA-Z0-9-]+(? None: """ Converts the given key press event into a line edit action. @@ -56,7 +57,7 @@ def keyPressEvent(self, event: QKeyEvent) -> None: The key event to process """ - if not self.hasSelectedText(): # selections are not supported + if not self.hasSelectedText(): # selections are not supported # switch key type key = event.key() if key == Qt.Key.Key_Backspace: @@ -64,9 +65,9 @@ def keyPressEvent(self, event: QKeyEvent) -> None: pos = self.cursorPosition() text = self.text() - delete = None # will hold the index span to delete + delete = None # will hold the index span to delete - if pos > 1: # check whether 0 < pos -1 + if pos > 1: # check whether 0 < pos -1 # else it will be a normal delete if pos > 2 and text[pos - 1] == ' ': # double backspace @@ -75,7 +76,7 @@ def keyPressEvent(self, event: QKeyEvent) -> None: # backspace and del delete = (pos - 1, pos + 1) - if delete: # whether a double deletion should take place + if delete: # whether a double deletion should take place text = text[:delete[0]] + text[delete[1]:] pos = delete[0] @@ -85,15 +86,15 @@ def keyPressEvent(self, event: QKeyEvent) -> None: logger.debug( 'UserListLineEdit: Backspace deletes {}'.format( delete)) - return # no more processing of this event + return # no more processing of this event elif key == Qt.Key.Key_Delete: # check whether seperator gets deleted pos = self.cursorPosition() text = self.text() - delete = None # will hold the index span to delete + delete = None # will hold the index span to delete - if pos < len(text): # check that pos isn't at the end + if pos < len(text): # check that pos isn't at the end # else it will be a normal delete if 1 < pos and text[pos] == ' ': # del and backspace @@ -102,7 +103,7 @@ def keyPressEvent(self, event: QKeyEvent) -> None: # double del delete = (pos, pos + 2) - if delete: # whether a double deletion should take place + if delete: # whether a double deletion should take place text = text[:delete[0]] + text[delete[1]:] pos = delete[0] @@ -112,7 +113,7 @@ def keyPressEvent(self, event: QKeyEvent) -> None: logger.debug( 'UserListLineEdit: Delete deletes {}'.format( delete)) - return # no more processing of this event + return # no more processing of this event return super().keyPressEvent(event) @@ -131,6 +132,7 @@ class UsernameValidator(QValidator): max_length : int, optional The maximum length for the user name, by default None """ + def __init__(self, parent=None, max_length=None) -> None: """ Init validator. @@ -189,6 +191,7 @@ def validate(self, input: str, class UserListValidator(QValidator): + def __init__(self, parent=None, max_user_length: int = None, @@ -252,12 +255,12 @@ def validate(self, input: str, # check and fix regular list by iterating over the letters and # inserting or removing letters as needed. - fixed = '' # the fixed input string - new_pos = pos # the new cursor position + fixed = '' # the fixed input string + new_pos = pos # the new cursor position - pre: str = None # previous char - lname = 0 # the length of the currently processed word - lusers = 0 # the number of already processed users + pre: str = None # previous char + lname = 0 # the length of the currently processed word + lusers = 0 # the number of already processed users for i, c in enumerate(input): @@ -265,7 +268,7 @@ def validate(self, input: str, if c.isspace(): # space at the beginning or after another space will be removed if not pre or pre.isspace(): - if i < pos: new_pos -= 1 # adjust cursor pos + if i < pos: new_pos -= 1 # adjust cursor pos continue if pre != ';': @@ -274,12 +277,12 @@ def validate(self, input: str, lusers += 1 if self.max_users: if lusers >= self.max_users: - break # max length of input reached + break # max length of input reached # insert semicolon and space pre = ' ' fixed += '; ' - if i < pos: new_pos += 1 # adjust cursor pos + if i < pos: new_pos += 1 # adjust cursor pos else: # regular space after semicolon - no change pre = c @@ -290,14 +293,14 @@ def validate(self, input: str, # semicolon at start or after space or another semicolon # is removed. if not pre or pre.isspace() or pre == ';': - if i < pos: new_pos -= 1 # adjust cursor pos + if i < pos: new_pos -= 1 # adjust cursor pos else: # semicolon seperates the users -> new word lname = 0 lusers += 1 if self.max_users: if lusers >= self.max_users: - break # max length of input reached + break # max length of input reached # add semicolon and space pre = ' ' @@ -306,7 +309,7 @@ def validate(self, input: str, elif re.fullmatch(r'\w', c): # word - new char - if self.max_user_length: # check length of the user name + if self.max_user_length: # check length of the user name lname += 1 if lname > self.max_user_length: # no more chars for this user name @@ -320,7 +323,7 @@ def validate(self, input: str, else: # no valid char -> remove - if i < pos: new_pos -= 1 # adjust cursor pos + if i < pos: new_pos -= 1 # adjust cursor pos # complete check if self.regex.fullmatch(fixed): diff --git a/python/ectec-gui/src/ectecgui/client/userclient/window.py b/python/ectec-gui/src/ectecgui/client/userclient/window.py index e4c795a..865912a 100644 --- a/python/ectec-gui/src/ectecgui/client/userclient/window.py +++ b/python/ectec-gui/src/ectecgui/client/userclient/window.py @@ -212,7 +212,7 @@ def __init__(self, client: QUserClient, parent=None) -> None: # init comboboxes self.ui.comboBoxFrom.setInsertPolicy(QComboBox.InsertPolicy.NoInsert) self.ui.comboBoxTo.setInsertPolicy(QComboBox.InsertPolicy.NoInsert) - self.slotUsersUpdated() # add items + self.slotUsersUpdated() # add items # Override deletion of the user list seperator in `comboBoxTo` self.ui.comboBoxTo.setLineEdit(UserListLineEdit()) @@ -249,7 +249,6 @@ def __init__(self, client: QUserClient, parent=None) -> None: self.ui.action_about.triggered.connect(self.slotAbout) self.ui.action_logs.triggered.connect(open_logs) - @pyqtSlot() def slotSend(self): """Send the package the user put into GUI.""" @@ -260,12 +259,12 @@ def slotSend(self): receiver = raw_receiver.split('; ') if not receiver: logger.debug('Send: No receiver specified.') - return # receiver needed + return # receiver needed if not receiver[-1]: receiver = receiver[:-1] if not receiver: logger.debug('Send: No receiver specified.') - return # receiver needed + return # receiver needed # content content = self.ui.textEditContent.toPlainText() @@ -343,9 +342,9 @@ def slotDisconnect(self): """ logger.debug("Disconnecting.") - self.close() # Does the disconnecting in `closeEvent` + self.close() # Does the disconnecting in `closeEvent` - self.ended.emit() # tells other's to return to previous window. + self.ended.emit() # tells other's to return to previous window. @pyqtSlot() def slotConnectionClosed(self): diff --git a/python/ectec-gui/src/ectecgui/client/wConnect.py b/python/ectec-gui/src/ectecgui/client/wConnect.py index 798be33..9ea407f 100644 --- a/python/ectec-gui/src/ectecgui/client/wConnect.py +++ b/python/ectec-gui/src/ectecgui/client/wConnect.py @@ -46,13 +46,11 @@ #: The function that provides internationalization by translation. _tr = QApplication.translate - # ---- Logging --------------------------------------------------------------- logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) - # ---- Add widgets to the main window form ----------------------------------- @@ -295,7 +293,7 @@ def slotConnect(self): # start connecting self.ui.labelStatus.setText(_tr('dConnect', 'Connecting...')) QApplication.processEvents( - QEventLoop.ExcludeUserInputEvents) # update label + QEventLoop.ExcludeUserInputEvents) # update label self.ct = self.ConnectThread(self.client, address, port) self.ct.succeeded.connect(self.slotConnected) @@ -336,7 +334,7 @@ def slotConnectFailed(self, exception): This shows a message box and inits the *Connect* phase. """ - self.ct = None # free thread + self.ct = None # free thread logger.warning("Connecting failed: " + str(exception)) @@ -364,7 +362,7 @@ def slotConnected(self): This opens the client window matching the user role """ - self.ct = None # free thread + self.ct = None # free thread role = ectec.Role(self.ui.comboBoxRole.currentText()) diff --git a/python/ectec-gui/src/ectecgui/ectecQt/__init__.py b/python/ectec-gui/src/ectecgui/ectecQt/__init__.py index 2bd16f1..f3a150f 100644 --- a/python/ectec-gui/src/ectecgui/ectecQt/__init__.py +++ b/python/ectec-gui/src/ectecgui/ectecQt/__init__.py @@ -33,6 +33,7 @@ class ThreadQ(QtCore.QThread): + def __init__(self, parent=None, target: Callable = None): super().__init__(parent=parent) self._target = target @@ -47,6 +48,7 @@ def is_alive(self): def pyqtClassSignal(*types, name): + class ClassSignal(QtCore.QObject): signal = QtCore.pyqtSignal(*types, name=name) @@ -67,6 +69,7 @@ def disconnect(self, slot=None): def signal(*args, name=None): + class Signal(QtCore.QObject): signal = pyqtSignal(*args, name=name) if name else pyqtSignal(*args) @@ -85,6 +88,7 @@ def disconnect(self, slot=None): # descriptor class class SignalInstancer: + def __init__(self, name=None) -> None: self.name = name if name else None diff --git a/python/ectec-gui/src/ectecgui/logs.py b/python/ectec-gui/src/ectecgui/logs.py index c88b03d..6a19ab2 100644 --- a/python/ectec-gui/src/ectecgui/logs.py +++ b/python/ectec-gui/src/ectecgui/logs.py @@ -88,7 +88,7 @@ def indent(text, by, space=" ", prefix="", suffix=""): The indented text. """ - t = "" # Stores the indented lines and will be returned + t = "" # Stores the indented lines and will be returned # Iterate of the lines of the text for line in text.splitlines(True): @@ -116,6 +116,7 @@ class EctecGuiFormatter(logging.Formatter): The format variable style used, by default '{' """ + def __init__(self, program=None, fmt=None, datefmt=None, style='{'): """ Init the Formatter. @@ -140,18 +141,19 @@ def formatException(self, exc_info): """ Format an exception so that it prints on a single line. """ - result = super().formatException(exc_info) # Super formats for us. - return indent(result, 2, prefix="| >>>") # Indent output + result = super().formatException(exc_info) # Super formats for us. + return indent(result, 2, prefix="| >>>") # Indent output def formatStack(self, stack_info): """ Format an exception so that it prints on a single line. """ - result = super().formatStack(stack_info) # Super formats for us. - return indent(result, 2, prefix="| >>>") # Indent output + result = super().formatStack(stack_info) # Super formats for us. + return indent(result, 2, prefix="| >>>") # Indent output class SessionRotatingFileHandler(handlers.BaseRotatingHandler): + def __init__(self, filename, sessionCount: int, @@ -225,7 +227,7 @@ def rotate(self, source: str, dest: str): # Do not rename the file but instead copy it and clear it afterwards shutil.copyfile(source, dest) - open(source, 'w').close() # clear file + open(source, 'w').close() # clear file def shouldRollover(self, record: logging.LogRecord) -> bool: """ @@ -244,13 +246,14 @@ def shouldRollover(self, record: logging.LogRecord) -> bool: bool False. """ - return False # Only rollover when session changes + return False # Only rollover when session changes # ---- QtMessageHandling ----------------------------------------------------- class QtMessageHander: + def __init__(self, logger: logging.Logger): self.logger = logger diff --git a/python/ectec-gui/src/ectecgui/reverse_traceback.py b/python/ectec-gui/src/ectecgui/reverse_traceback.py index 2da624d..2e5ae57 100644 --- a/python/ectec-gui/src/ectecgui/reverse_traceback.py +++ b/python/ectec-gui/src/ectecgui/reverse_traceback.py @@ -82,6 +82,7 @@ class TracebackInfo: Exception: An error occurred. """ + def __init__(self, groupdict: Dict[str, str]): """ Init the TracebackInfo diff --git a/python/ectec-gui/src/ectecgui/setup.py b/python/ectec-gui/src/ectecgui/setup.py index e960e3c..a9968b0 100644 --- a/python/ectec-gui/src/ectecgui/setup.py +++ b/python/ectec-gui/src/ectecgui/setup.py @@ -80,12 +80,12 @@ def setup_logging(args: Namespace, program: str, default_log_filename: str): if args.log_file: if args.log_file_path is None: log_dir = Path(user_log_dir(APPNAME, APPAUTHOR)) - log_dir.mkdir(parents=True, exist_ok=True) # ensure dir exists + log_dir.mkdir(parents=True, exist_ok=True) # ensure dir exists log_file = log_dir / default_log_filename else: log_file = args.log_file_path - max_size = 300 * 1024 * 1024 # 300 MiB + max_size = 300 * 1024 * 1024 # 300 MiB file_handler = logs.SessionRotatingFileHandler(log_file, sessionCount=5) file_handler.setFormatter(logs.EctecGuiFormatter(program)) diff --git a/python/ectec-gui/tests/__init__.py b/python/ectec-gui/tests/__init__.py index 18cf72f..18cade1 100644 --- a/python/ectec-gui/tests/__init__.py +++ b/python/ectec-gui/tests/__init__.py @@ -46,7 +46,7 @@ def _import_ectec(*submodules): ectec = __import__('ectec') for sbm in submodules: - __import__('ectec.'+sbm) + __import__('ectec.' + sbm) except: # PATH = sys.path path = osp.abspath(osp.join(osp.dirname(__file__), '../src/')) @@ -57,7 +57,7 @@ def _import_ectec(*submodules): ectec = __import__('ectec') for sbm in submodules: - __import__('ectec.'+sbm) + __import__('ectec.' + sbm) return ectec @@ -162,6 +162,7 @@ def addSkip(self, test, reason): class EctecTestRunner(unittest.TextTestRunner): + def run(self, test): "Run the given test case or test suite." result = self._makeResult() @@ -179,9 +180,10 @@ def run(self, test): # noisy. The -Wd and -Wa flags can be used to bypass this # only when self.warnings is None. if self.warnings in ['default', 'always']: - warnings.filterwarnings('module', - category=DeprecationWarning, - message=r'Please use assert\w+ instead.') + warnings.filterwarnings( + 'module', + category=DeprecationWarning, + message=r'Please use assert\w+ instead.') startTime = time.perf_counter() startTestRun = getattr(result, 'startTestRun', None) if startTestRun is not None: @@ -205,8 +207,7 @@ def run(self, test): expectedFails = unexpectedSuccesses = skipped = 0 try: results = map(len, (result.expectedFailures, - result.unexpectedSuccesses, - result.skipped)) + result.unexpectedSuccesses, result.skipped)) except AttributeError: pass else: @@ -237,7 +238,7 @@ def run(self, test): if unexpectedSuccesses: infos.append("unexpected successes=%d" % unexpectedSuccesses) if infos: - self.stream.writeln(" (%s)" % (", ".join(infos),)) + self.stream.writeln(" (%s)" % (", ".join(infos), )) else: self.stream.write("\n") return result diff --git a/python/ectec-gui/tests/__main__.py b/python/ectec-gui/tests/__main__.py index e11c41d..503d1c5 100644 --- a/python/ectec-gui/tests/__main__.py +++ b/python/ectec-gui/tests/__main__.py @@ -24,14 +24,15 @@ """ import os.path as osp -import unittest import sys +import unittest from . import EctecTestResult, EctecTestRunner if __name__ == '__main__': loader = unittest.TestLoader() - runner = EctecTestRunner(verbosity=3, buffer=True, + runner = EctecTestRunner(verbosity=3, + buffer=True, resultclass=EctecTestResult) suite = suite = unittest.TestSuite([]) diff --git a/python/ectec-gui/tests/client.py b/python/ectec-gui/tests/client.py index ef9d2ff..7910bdd 100644 --- a/python/ectec-gui/tests/client.py +++ b/python/ectec-gui/tests/client.py @@ -46,14 +46,14 @@ def test_init(self): with self.subTest('No time, one recipient'): p = client.Package('testsender', 'testrecipient', 'testtype') self.assertEqual(p.sender, 'testsender') - self.assertEqual(p.recipient, ('testrecipient',)) + self.assertEqual(p.recipient, ('testrecipient', )) self.assertEqual(p.content, b'') self.assertEqual(p.type, 'testtype') self.assertEqual(p.time, None) with self.subTest('No time, more recipients'): - p = client.Package( - 'testsender', ['testrecipient', 'reci'], 'testtype') + p = client.Package('testsender', ['testrecipient', 'reci'], + 'testtype') self.assertEqual(p.sender, 'testsender') self.assertEqual(p.recipient, ('testrecipient', 'reci')) self.assertEqual(p.content, b'') @@ -65,7 +65,7 @@ def test_init(self): p = client.Package('testsender', 'testrecipient', 'testtype', time.timestamp()) self.assertEqual(p.sender, 'testsender') - self.assertEqual(p.recipient, ('testrecipient',)) + self.assertEqual(p.recipient, ('testrecipient', )) self.assertEqual(p.content, b'') self.assertEqual(p.type, 'testtype') self.assertEqual(p.time, time) @@ -118,8 +118,8 @@ def test_hash(self): with self.subTest('Different recipients'): p1 = client.Package('testsender', 'testrecipient', 'testtype') - p2 = client.Package( - 'testsender', ['testrecipient', 'testman'], 'testtype') + p2 = client.Package('testsender', ['testrecipient', 'testman'], + 'testtype') self.assertNotEqual(p1, p2) self.assertNotEqual(hash(p1), hash(p2)) @@ -225,8 +225,9 @@ def test_remove(self): self.assertEqual(ps.all(), [p2, p3, p4, p6]) with self.subTest('Remove with function filter.'): + def filter_function(pkg): - return bool(pkg.time) # removed if there is a time + return bool(pkg.time) # removed if there is a time ps = client.PackageStorage() ps.add(as_list=packages) @@ -237,8 +238,9 @@ def filter_function(pkg): self.assertEqual(ps.all(), [p1, p4, p5]) with self.subTest('remove one and remove with function filter.'): + def filter_function(pkg): - return bool(pkg.time) # removed if there is a time + return bool(pkg.time) # removed if there is a time ps = client.PackageStorage() ps.add(as_list=packages) @@ -276,8 +278,8 @@ def test_filter(self): p4 = client.Package('testsender', 'testrecipient', 'sometype') p5 = client.Package('testman', 'testrecipient', 'testtype') p6 = client.Package('testman', 'testrecipient', 'testtype', 2) - p7 = client.Package( - 'testman', ['testrecipient', 'someman'], 'testtype', 2) + p7 = client.Package('testman', ['testrecipient', 'someman'], + 'testtype', 2) packages = [p1, p2, p3, p4, p5, p6, p7] @@ -297,7 +299,7 @@ def test_filter(self): ps.add(as_list=packages) self.assertEqual(ps.all(), packages) - filtered = list(ps.filter(recipient=('testrecipient',))) + filtered = list(ps.filter(recipient=('testrecipient', ))) self.assertEqual(filtered, [p1, p2, p3, p4, p5, p6]) @@ -386,8 +388,7 @@ def test_filter_recipient(self): p4 = client.Package('testsender', 'name2', 'sometype') p5 = client.Package('testman', 'name2', 'testtype') p6 = client.Package('testman', 'name3', 'testtype', 2) - p7 = client.Package( - 'testman', ['name2', 'someman'], 'testtype', 2) + p7 = client.Package('testman', ['name2', 'someman'], 'testtype', 2) packages = [p1, p2, p3, p4, p5, p6, p7] @@ -464,17 +465,16 @@ def setUp(self): def test_recv_bytes(self): """Test the receiving of x random bytes.""" - numbers = [1, 2, 3, 4, 5, 6, 10, 11, 14, 4096, - 6500, 30000, 500 * 1000] + numbers = [1, 2, 3, 4, 5, 6, 10, 11, 14, 4096, 6500, 30000, 500 * 1000] # no buffer required - self.client.socket.settimeout(0) # wrong timout - should be handled + self.client.socket.settimeout(0) # wrong timout - should be handled for length in numbers: with self.subTest("Same length", length=length): data = secrets.token_bytes(length) - thread = FunctionThread( - target=self.client.recv_bytes, args=[length]) + thread = FunctionThread(target=self.client.recv_bytes, + args=[length]) thread.start() t1 = time.perf_counter() @@ -495,8 +495,8 @@ def test_recv_bytes(self): with self.subTest("Buffer needed", length=length): data = secrets.token_bytes(length) - thread = FunctionThread( - target=self.client.recv_bytes, args=[length]) + thread = FunctionThread(target=self.client.recv_bytes, + args=[length]) thread.start() self.server_socket.sendall(data) @@ -519,7 +519,7 @@ def test_recv_bytes(self): t1 = time.perf_counter() self.client.recv_bytes(100, start_timeout=timeout) t2 = time.perf_counter() - if t2-t1 > timeout+0.100: + if t2 - t1 > timeout + 0.100: self.fail(f"Timout too late by: {t2-t1-timeout}") # test end timeout @@ -529,31 +529,30 @@ def test_recv_bytes(self): with self.subTest("Part timout", timeout=timeout): def run(self, timeout, length): - times = timeout * 2 // (self.client.COMMAND_TIMEOUT*0.5) + times = timeout * 2 // (self.client.COMMAND_TIMEOUT * 0.5) for i in range(int(times)): - data = secrets.token_bytes(length//10) + data = secrets.token_bytes(length // 10) self.server_socket.sendall(data) - time.sleep(self.client.COMMAND_TIMEOUT*0.5) + time.sleep(self.client.COMMAND_TIMEOUT * 0.5) thread = FunctionThread(target=run, args=[self, timeout, length]) thread.start() with self.assertRaises(client.CommandTimeout): t1 = time.perf_counter() - self.client.recv_bytes(length, timeout=timeout) # in seconds + self.client.recv_bytes(length, timeout=timeout) # in seconds t2 = time.perf_counter() thread.join() - if t2-t1 > timeout+self.client.COMMAND_TIMEOUT: + if t2 - t1 > timeout + self.client.COMMAND_TIMEOUT: self.fail(f"Timout too late by: {t2-t1-timeout}") def test_recv_command(self): """Test the receiving of a random command.""" - numbers = [1, 2, 3, 4, 5, 6, 10, 11, 14, 4096, - 6500, 30000, 500 * 1000] + numbers = [1, 2, 3, 4, 5, 6, 10, 11, 14, 4096, 6500, 30000, 500 * 1000] # no buffer required - self.client.socket.settimeout(0) # wrong timout - should be handled + self.client.socket.settimeout(0) # wrong timout - should be handled for length in numbers: with self.subTest("Same length", length=length): data = secrets.token_bytes(length).replace( @@ -561,11 +560,11 @@ def test_recv_command(self): thread = FunctionThread( target=self.client.recv_command, - args=[length*2 + len(self.client.COMMAND_SEPERATOR)]) + args=[length * 2 + len(self.client.COMMAND_SEPERATOR)]) thread.start() - self.server_socket.sendall( - data + self.client.COMMAND_SEPERATOR) + self.server_socket.sendall(data + + self.client.COMMAND_SEPERATOR) thread.join() @@ -582,12 +581,13 @@ def test_recv_command(self): data = secrets.token_bytes(length).replace( self.client.COMMAND_SEPERATOR, b'a') - thread = FunctionThread( - target=self.client.recv_command, args=[length*2]) + thread = FunctionThread(target=self.client.recv_command, + args=[length * 2]) thread.start() - self.server_socket.sendall( - data[:-4] + self.client.COMMAND_SEPERATOR + data[-4:]) + self.server_socket.sendall(data[:-4] + + self.client.COMMAND_SEPERATOR + + data[-4:]) thread.join() @@ -607,7 +607,7 @@ def test_recv_command(self): t1 = time.perf_counter() self.client.recv_command(100, start_timeout=timeout) t2 = time.perf_counter() - if t2-t1 > timeout+0.100: + if t2 - t1 > timeout + 0.100: self.fail(f"Timout too late by: {t2-t1-timeout}") # test end timeout @@ -617,24 +617,24 @@ def test_recv_command(self): with self.subTest("Part timout", timeout=timeout): def run(self, timeout, length): - times = timeout * 2 // (self.client.COMMAND_TIMEOUT*0.5) + times = timeout * 2 // (self.client.COMMAND_TIMEOUT * 0.5) for i in range(int(times)): - data = secrets.token_bytes(length//10).replace( + data = secrets.token_bytes(length // 10).replace( self.client.COMMAND_SEPERATOR, b'a') self.server_socket.sendall(data) - time.sleep(self.client.COMMAND_TIMEOUT*0.5) + time.sleep(self.client.COMMAND_TIMEOUT * 0.5) thread = FunctionThread(target=run, args=[self, timeout, length]) thread.start() with self.assertRaises(client.CommandTimeout): t1 = time.perf_counter() - self.client.recv_command( - length*2, timeout=timeout) # in seconds + self.client.recv_command(length * 2, + timeout=timeout) # in seconds t2 = time.perf_counter() thread.join() - if t2-t1 > timeout+self.client.COMMAND_TIMEOUT: + if t2 - t1 > timeout + self.client.COMMAND_TIMEOUT: self.fail(f"Timout too late by: {t2-t1-timeout}") def test_parse_info(self): @@ -818,30 +818,32 @@ def test_send_command(self): ans = self.server_socket.recv(4096) - self.assertEqual(ans, b'HELLO test g3' + - self.client.COMMAND_SEPERATOR) + self.assertEqual(ans, + b'HELLO test g3' + self.client.COMMAND_SEPERATOR) with self.subTest("With data"): self.client.send_command('HELLO test g3', b'gjkl') ans = self.server_socket.recv(4096) - self.assertEqual(ans, b'HELLO test g3' + - self.client.COMMAND_SEPERATOR + b'gjkl') + self.assertEqual( + ans, + b'HELLO test g3' + self.client.COMMAND_SEPERATOR + b'gjkl') def test_send_info(self): self.client.send_info('testversion') ans = self.server_socket.recv(4096) - self.assertEqual(ans, b'INFO testversion' + - self.client.COMMAND_SEPERATOR) + self.assertEqual(ans, + b'INFO testversion' + self.client.COMMAND_SEPERATOR) def test_send_register(self): self.client.send_register('testname', 'testrole') ans = self.server_socket.recv(4096) - self.assertEqual(ans, b'REGISTER testname AS testrole' + - self.client.COMMAND_SEPERATOR) + self.assertEqual( + ans, + b'REGISTER testname AS testrole' + self.client.COMMAND_SEPERATOR) def test_send_package(self): sep = self.client.COMMAND_SEPERATOR @@ -929,23 +931,23 @@ def test_client_closes(self): @unittest.skip("Not implemented yet.") def test_bad_command(self): - pass # TODO test_bad_command + pass # TODO test_bad_command @unittest.skip("Not implemented yet.") def test_package(self): - pass # TODO test_package + pass # TODO test_package @unittest.skip("Not implemented yet.") def test_update(self): - pass # TODO test_update + pass # TODO test_update @unittest.skip("Not implemented yet.") def test_error(self): - pass # TODO test_error + pass # TODO test_error @unittest.skip("Not implemented yet.") def test_all_commands(self): - pass # TODO test_all_commands + pass # TODO test_all_commands class UserClientTestCase(unittest.TestCase): diff --git a/python/ectec-gui/tests/sc_tests.py b/python/ectec-gui/tests/sc_tests.py index 7b791b1..db73eff 100644 --- a/python/ectec-gui/tests/sc_tests.py +++ b/python/ectec-gui/tests/sc_tests.py @@ -53,7 +53,7 @@ def check_logs(self): for record in self.handler.records: if record.exc_info: raise record.exc_info[1] - if record.levelno > - logging.WARNING: + if record.levelno > -logging.WARNING: raise Exception(record.msg) # @unittest.skip("") @@ -101,8 +101,8 @@ def test_one_client(self): self.assertEqual(server.users[0][1], ectec.Role.USER) # send package - package = ectec.client.Package( - 'testsender', 'testrecipient', 'text/plain') + package = ectec.client.Package('testsender', 'testrecipient', + 'text/plain') client.send(package) @@ -233,8 +233,8 @@ def test_two_clients(self): self.assertEqual(server.users[0][1], ectec.Role.USER) # send package - package = ectec.client.Package( - 'client1', 'client2', 'text/plain') + package = ectec.client.Package('client1', 'client2', + 'text/plain') client1.send(package) time.sleep(0.05) @@ -262,7 +262,7 @@ def test_many_clients(self): clients = [] with server.start(0): - try: # for disconnecting already connected clients + try: # for disconnecting already connected clients # ---- connect clients for i in range(N): @@ -273,7 +273,8 @@ def test_many_clients(self): # check user list time.sleep(0.01) - self.assertEqual(len(client.users), i+1, f"Iteration {i}") + self.assertEqual(len(client.users), i + 1, + f"Iteration {i}") # check user list for server self.assertEqual(len(server.users), N) @@ -284,8 +285,8 @@ def test_many_clients(self): self.assertEqual(len(client.users), N, client.username) # ---- send package - package = ectec.client.Package( - 'client1', 'client2', 'text/plain') + package = ectec.client.Package('client1', 'client2', + 'text/plain') clients[0].send(package) # ---- receive package @@ -305,13 +306,14 @@ def test_many_clients(self): def send_pkg(i): send_event.wait() - package = ectec.client.Package( - f'userclient_{i}', f'userclient_{i+1}', 'text/plain') + package = ectec.client.Package(f'userclient_{i}', + f'userclient_{i+1}', + 'text/plain') clients[i].send(package) threads = [] for i in range(len(clients)): - thread = threading.Thread(target=send_pkg, args=(i,)) + thread = threading.Thread(target=send_pkg, args=(i, )) thread.start() threads.append(thread) @@ -333,11 +335,12 @@ def send_pkg(i): for i in range(len(clients)): clients[i].disconnect() - if i < len(clients)-1: + if i < len(clients) - 1: # check user list time.sleep(0.01) - clients[i+1]._update() - self.assertEqual(len(clients[i+1].users), N-i-1, i) + clients[i + 1]._update() + self.assertEqual(len(clients[i + 1].users), N - i - 1, + i) finally: diff --git a/python/ectec-gui/tests/server.py b/python/ectec-gui/tests/server.py index 7c19a9d..dc3788f 100644 --- a/python/ectec-gui/tests/server.py +++ b/python/ectec-gui/tests/server.py @@ -38,7 +38,6 @@ ectecserver = ectec.server ectecversion = ectec.version - # ---- TestCases CLIENTS = copy.deepcopy(ectecserver.ClientHandler.clients) @@ -64,8 +63,8 @@ def setUp(self): # connect client socket self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.connect_thread = FunctionThread( - target=self.server_socket.accept, daemon=True) + self.connect_thread = FunctionThread(target=self.server_socket.accept, + daemon=True) self.connect_thread.start() self.client_socket.connect(("127.0.0.1", port)) @@ -87,9 +86,7 @@ def __init__(self, request, address, server): TestHandler.clients = copy.deepcopy(CLIENTS) - self.handler = TestHandler(self.handler_socket, - self.address, - server) + self.handler = TestHandler(self.handler_socket, self.address, server) self.handler.log = mock.MagicMock(spec=logging.LoggerAdapter) self.handler.setup() @@ -106,17 +103,16 @@ def test_check_version(self): def test_recv_bytes(self): """Test the `receiving of x random bytes.""" - numbers = [1, 2, 3, 4, 5, 6, 10, 11, 14, 4096, - 6500, 30000, 500 * 1000] + numbers = [1, 2, 3, 4, 5, 6, 10, 11, 14, 4096, 6500, 30000, 500 * 1000] # no buffer required - self.handler_socket.settimeout(0) # wrong timout - should be handled + self.handler_socket.settimeout(0) # wrong timout - should be handled for length in numbers: with self.subTest("Same length", length=length): data = secrets.token_bytes(length) - thread = FunctionThread( - target=self.handler.recv_bytes, args=[length]) + thread = FunctionThread(target=self.handler.recv_bytes, + args=[length]) thread.start() t1 = time.perf_counter() @@ -137,8 +133,8 @@ def test_recv_bytes(self): with self.subTest("Buffer needed", length=length): data = secrets.token_bytes(length) - thread = FunctionThread( - target=self.handler.recv_bytes, args=[length]) + thread = FunctionThread(target=self.handler.recv_bytes, + args=[length]) thread.start() self.client_socket.sendall(data) @@ -161,7 +157,7 @@ def test_recv_bytes(self): t1 = time.perf_counter() self.handler.recv_bytes(100, start_timeout=timeout) t2 = time.perf_counter() - if t2-t1 > timeout+0.200: + if t2 - t1 > timeout + 0.200: self.fail(f"Timout too late by: {t2-t1-timeout}") # test end timeout @@ -171,31 +167,31 @@ def test_recv_bytes(self): with self.subTest("Part timout", timeout=timeout): def run(self, timeout, length): - times = timeout * 2 // (self.handler.COMMAND_TIMEOUT*0.5) + times = timeout * 2 // (self.handler.COMMAND_TIMEOUT * 0.5) for i in range(int(times)): - data = secrets.token_bytes(length//10) + data = secrets.token_bytes(length // 10) self.client_socket.sendall(data) - time.sleep(self.handler.COMMAND_TIMEOUT*0.5) + time.sleep(self.handler.COMMAND_TIMEOUT * 0.5) thread = FunctionThread(target=run, args=[self, timeout, length]) thread.start() with self.assertRaises(ectecserver.CommandTimeout): t1 = time.perf_counter() - self.handler.recv_bytes(length, timeout=timeout) # in seconds + self.handler.recv_bytes(length, + timeout=timeout) # in seconds t2 = time.perf_counter() thread.join() - if t2-t1 > timeout+self.handler.COMMAND_TIMEOUT+0.2: + if t2 - t1 > timeout + self.handler.COMMAND_TIMEOUT + 0.2: self.fail(f"Timout too late by: {t2-t1-timeout}") def test_recv_command(self): """Test the receiving of a random command.""" - numbers = [1, 2, 3, 4, 5, 6, 10, 11, 14, 4096, - 6500, 30000, 500 * 1000] + numbers = [1, 2, 3, 4, 5, 6, 10, 11, 14, 4096, 6500, 30000, 500 * 1000] # no buffer required - self.handler_socket.settimeout(0) # wrong timout - should be handled + self.handler_socket.settimeout(0) # wrong timout - should be handled for length in numbers: with self.subTest("Same length", length=length): data = secrets.token_bytes(length).replace( @@ -203,11 +199,11 @@ def test_recv_command(self): thread = FunctionThread( target=self.handler.recv_command, - args=[length*2 + len(self.handler.COMMAND_SEPERATOR)]) + args=[length * 2 + len(self.handler.COMMAND_SEPERATOR)]) thread.start() - self.client_socket.sendall( - data + self.handler.COMMAND_SEPERATOR) + self.client_socket.sendall(data + + self.handler.COMMAND_SEPERATOR) thread.join() @@ -224,12 +220,13 @@ def test_recv_command(self): data = secrets.token_bytes(length).replace( self.handler.COMMAND_SEPERATOR, b'a') - thread = FunctionThread( - target=self.handler.recv_command, args=[length*2]) + thread = FunctionThread(target=self.handler.recv_command, + args=[length * 2]) thread.start() - self.client_socket.sendall( - data[:-4] + self.handler.COMMAND_SEPERATOR + data[-4:]) + self.client_socket.sendall(data[:-4] + + self.handler.COMMAND_SEPERATOR + + data[-4:]) thread.join() @@ -249,7 +246,7 @@ def test_recv_command(self): t1 = time.perf_counter() self.handler.recv_command(100, start_timeout=timeout) t2 = time.perf_counter() - if t2-t1 > timeout+0.200: + if t2 - t1 > timeout + 0.200: self.fail(f"Timout too late by: {t2-t1-timeout}") # test end timeout @@ -259,30 +256,29 @@ def test_recv_command(self): with self.subTest("Part timout", timeout=timeout): def run(self, timeout, length): - times = timeout * 2 // (self.handler.COMMAND_TIMEOUT*0.5) + times = timeout * 2 // (self.handler.COMMAND_TIMEOUT * 0.5) for i in range(int(times)): - data = secrets.token_bytes(length//10).replace( + data = secrets.token_bytes(length // 10).replace( self.handler.COMMAND_SEPERATOR, b'a') self.client_socket.sendall(data) - time.sleep(self.handler.COMMAND_TIMEOUT*0.5) + time.sleep(self.handler.COMMAND_TIMEOUT * 0.5) thread = FunctionThread(target=run, args=[self, timeout, length]) thread.start() with self.assertRaises(ectecserver.CommandTimeout): t1 = time.perf_counter() - self.handler.recv_command( - length*2, timeout=timeout) # in seconds + self.handler.recv_command(length * 2, + timeout=timeout) # in seconds t2 = time.perf_counter() thread.join() - if t2-t1 > timeout+self.handler.COMMAND_TIMEOUT+0.200: + if t2 - t1 > timeout + self.handler.COMMAND_TIMEOUT + 0.200: self.fail(f"Timout too late by: {t2-t1-timeout}") def test_recv_info(self): """Test receiving of the INFO command.""" - versions = ['1.1.1', '1.1.1-label', - '7.2.6+meta', '3.5.2-labe+meta'] + versions = ['1.1.1', '1.1.1-label', '7.2.6+meta', '3.5.2-labe+meta'] version = '' with self.subTest(version=version): @@ -323,8 +319,7 @@ def test_recv_register(self): for role in roles: for name in names: with self.subTest(name=name, role=role.value): - command = 'REGISTER {} AS {}'.format(name, - role.value) + command = 'REGISTER {} AS {}'.format(name, role.value) command = command.encode(encoding='utf-8') command += self.handler.COMMAND_SEPERATOR @@ -337,12 +332,12 @@ def test_recv_register(self): def test_recv_pkg(self): """Test the receiving of a package using the PACKAGE command.""" - numbers = [1, 2, 3, 4, 5, 6, 10, 11, 14, 4096, - 6500, 30000] + numbers = [1, 2, 3, 4, 5, 6, 10, 11, 14, 4096, 6500, 30000] names = ['plain', '_underscore_', 'long_name', 'Numbers1234'] - types = ['plain', 'meme/type', '_undescore_', - 'long-type', 'numbered133type'] + types = [ + 'plain', 'meme/type', '_undescore_', 'long-type', 'numbered133type' + ] self.client_socket.settimeout(1) @@ -357,9 +352,7 @@ def test_recv_pkg(self): template = 'PACKAGE {} FROM {} TO {} WITH {}' - command = template.format(typ, - sender, - recipient, + command = template.format(typ, sender, recipient, str(length)) command = command.encode(encoding='utf-8') @@ -369,8 +362,7 @@ def test_recv_pkg(self): package = (sender, recipient, typ, content) - thread = FunctionThread( - target=self.handler.recv_pkg) + thread = FunctionThread(target=self.handler.recv_pkg) thread.start() self.client_socket.sendall(command) @@ -390,9 +382,7 @@ def test_recv_pkg(self): template = 'PACKAGE {} FROM {} TO {} WITH {}' - command = template.format(typ, - sender, - recipient, + command = template.format(typ, sender, recipient, str(length)) command = command.encode(encoding='utf-8') @@ -402,8 +392,7 @@ def test_recv_pkg(self): package = (sender, recipient, typ, content) - thread = FunctionThread( - target=self.handler.recv_pkg) + thread = FunctionThread(target=self.handler.recv_pkg) thread.start() self.client_socket.sendall(command) @@ -416,17 +405,16 @@ def test_recv_pkg(self): def test_send_pkg(self): """Test the sending of a package.""" - numbers = [1, 2, 3, 4, 5, 6, 10, 11, 14, 4096, - 6500, 30000, 500 * 1000] + numbers = [1, 2, 3, 4, 5, 6, 10, 11, 14, 4096, 6500, 30000, 500 * 1000] for length in numbers: with self.subTest(length=length): content = secrets.token_bytes(length) - package = ectecserver.Package( - 'plain', 'plain', 'some_type', content) + package = ectecserver.Package('plain', 'plain', 'some_type', + content) - thread = FunctionThread( - target=self.handler.send_pkg, args=[package]) + thread = FunctionThread(target=self.handler.send_pkg, + args=[package]) thread.start() command = 'PACKAGE some_type FROM plain TO plain WITH ' + \ @@ -437,7 +425,7 @@ def test_send_pkg(self): i = 0 result = b'' - while i < length*0.5 and len(result) < len(expected): + while i < length * 0.5 and len(result) < len(expected): result += self.client_socket.recv(8192) i += 1 @@ -459,9 +447,11 @@ def test_send_update(self): # users with self.subTest("3 registered users"): - cl = [ectecserver.ClientData('one', 'user', None, None), - ectecserver.ClientData('two', 'user', None, None), - ectecserver.ClientData('three', 'user', None, None)] + cl = [ + ectecserver.ClientData('one', 'user', None, None), + ectecserver.ClientData('two', 'user', None, None), + ectecserver.ClientData('three', 'user', None, None) + ] self.handler.clients[ectec.Role.USER.value] = cl self.handler.send_update() @@ -472,7 +462,7 @@ def test_send_update(self): result = self.client_socket.recv(4096) - self.assertEqual(command+data, result) + self.assertEqual(command + data, result) def test_send_error(self): """Test the `send_error` method.""" @@ -547,7 +537,7 @@ class ClientHandlerAdvancedTestCase(unittest.TestCase): def test_handling_user(self): """Tests the handling of one user client.""" sep = self.handler.COMMAND_SEPERATOR - self.client_socket.settimeout(1) # test shouldn't hang on fail + self.client_socket.settimeout(1) # test shouldn't hang on fail thread = FunctionThread(target=self.handler.handle) thread.start() @@ -584,8 +574,9 @@ def test_handling_user(self): client_list = self.handler.get_client_list() address = ectec.Address._make(self.client_socket.getsockname()) - expected_list = [ectecserver.ClientData( - name, role, address, self.handler)] + expected_list = [ + ectecserver.ClientData(name, role, address, self.handler) + ] self.assertEqual(client_list, expected_list) diff --git a/python/examples/example1/server.py b/python/examples/example1/server.py index 4547814..e9834b3 100644 --- a/python/examples/example1/server.py +++ b/python/examples/example1/server.py @@ -5,10 +5,9 @@ @author: Simon """ +import _thread import socket import time -import _thread - HOST = '127.0.0.1' PORT = 65432 @@ -22,6 +21,7 @@ def on_new_client(clientsocket, addr): data = conn.recv(1024) print(str(data)) + while True: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.bind((HOST, PORT)) diff --git a/python/examples/example2/Messenger-Server.py b/python/examples/example2/Messenger-Server.py index e8e1a14..34d5b74 100755 --- a/python/examples/example2/Messenger-Server.py +++ b/python/examples/example2/Messenger-Server.py @@ -6,10 +6,11 @@ @author: Yannick Funke """ -import messengerstuff import socketserver import threading +import messengerstuff + port = 40000 try: diff --git a/python/examples/example2/Messenger.py b/python/examples/example2/Messenger.py index 0d61115..d0d768a 100755 --- a/python/examples/example2/Messenger.py +++ b/python/examples/example2/Messenger.py @@ -6,10 +6,12 @@ @author: Yannick Funke """ -import messengerstuff +import time import tkinter as tk import tkinter.messagebox as tkm -import time + +import messengerstuff + dsp = messengerstuff.dsp ip = '127.0.0.1' @@ -17,92 +19,92 @@ class Messenger: - + def __init__(self, id, port): self.window = tk.Tk() - + self.text = tk.Text(self.window, state='disabled') self.text.pack() - + self.wr = tk.Frame(self.window) self.b = tk.Button(self.wr, text='Send', command=self.send) self.e = tk.Entry(self.wr) self.e.pack(side='left') self.b.pack() self.wr.pack() - + self.c = dsp.DSPSocket() with self.c: self.c.connect((id, port)) self.load() self.mil = 2000 - + self.window.after(self.mil, self.update) self.window.mainloop() - - + def update(self): p = self.c.request(dsp.Requests.NormalSync, self.last) d = p.data d.sort() if d: - self.last = d[len(d)-1] - + self.last = d[len(d) - 1] + self.text.config(state='normal') for e in d: self.text.insert('end', str(e) + '\n\n') self.text.config(state='disabled') - + self.window.after(self.mil, self.update) print('Updated') - + self.text.see('end') - + def send(self): mes = self.e.get() self.c.request(dsp.Requests.Log, mes) self.e.delete(0, 'end') print('Send') - + def load(self): # ask name - name = type('Name', (), {'name':'Unknown'}) + name = type('Name', (), {'name': 'Unknown'}) + def ok(name): t = e.get() if t: name.name = t w.quit() w.destroy() - + w = tk.Toplevel(self.window) w.title('Enter Username') w.transient(self.window) - + e = tk.Entry(w) e.pack() b = tk.Button(w, text='OK', command=lambda: ok(name)) b.pack() - + w.mainloop() - + # login self.c.request(dsp.Requests.Login, name.name) print('Loged in') - + # get Chat and show p = self.c.request(dsp.Requests.CompleteSync, None) chat = p.data.events chat.sort() - self.last = chat[len(chat)-1] - + self.last = chat[len(chat) - 1] + self.text.config(state='normal') for e in chat: self.text.insert('end', str(e) + '\n\n') self.text.config(state='disabled') print('Loaded Chat') - + self.text.see('end') - - + + if __name__ == '__main__': m = Messenger(ip, port) diff --git a/python/examples/example2/bot.py b/python/examples/example2/bot.py index 1b4e6bc..a8a9192 100755 --- a/python/examples/example2/bot.py +++ b/python/examples/example2/bot.py @@ -6,20 +6,23 @@ """ import messengerstuff + dsp = messengerstuff.dsp -import time import calendar import datetime +import time + def loadChat(s): c = s.request(dsp.Requests.CompleteSync, None) return c.data + def analize_Write_Time(s): c = loadChat(s) c.events.sort() user = [] - + for e in c.events: if type(e) == messengerstuff.Post: # is post # find user in list @@ -31,30 +34,30 @@ def analize_Write_Time(s): else: # add user user.append((e.user, [])) - i = len(user)-1 - + i = len(user) - 1 + # add post to users list user[i][1].append(e) - + # get time spans of each user analie = [] for l in user: u = l[0] ps = l[1] times = [] - + for i, p in enumerate(ps): - if i +1 < len(ps): + if i + 1 < len(ps): t = p.time - t2 = ps[i+1].time - + t2 = ps[i + 1].time + t_z = calendar.timegm(t) t2_z = calendar.timegm(t2) - + diff = t2_z - t_z - + times.append(diff) - + le = len(times) s = sum(times) if le == 0: @@ -63,11 +66,12 @@ def analize_Write_Time(s): analie.append((u, average)) return analie + def analize_Posts(s): c = loadChat(s) c.events.sort() user = [] - + for e in c.events: if type(e) == messengerstuff.Post: # is post # find user in list @@ -79,24 +83,26 @@ def analize_Posts(s): else: # add user user.append([e.user, 1]) - i = len(user)-1 - + i = len(user) - 1 + # add post to users list user[i][1] += 1 return user + def analize_User(s): c = loadChat(s) user = set() - + for e in c.events: user.add(e.user) - + return user + def time_to_str(t): return time.strftime("%a, %d %b %Y %H:%M:%S", t) - + ip = '192.168.178.32' port = 50001 @@ -110,10 +116,6 @@ def time_to_str(t): for u in us: print(u) -print() +print() for u in l: print(u[0], u[1]) - - - - diff --git a/python/examples/example2/dsp.py b/python/examples/example2/dsp.py index 3f8b8cd..882bc2d 100755 --- a/python/examples/example2/dsp.py +++ b/python/examples/example2/dsp.py @@ -5,11 +5,10 @@ @author: Yannick Funke """ -import socketserver -import socket -import pickle import enum - +import pickle +import socket +import socketserver class Requests(enum.Enum): @@ -21,56 +20,60 @@ class Requests(enum.Enum): Long = 6 LongEnd = 7 Disconnect = 8 - + class Answers(enum.Enum): Error = 1 Successful = 2 Data = 3 - - + + class Package: - + def __init__(self, requesttype, data): self.request = requesttype self.data = data - + def to_bytes(self): return pickle.dumps(self) - + + def load(bytes): """load Package/... from bytes""" return pickle.loads(bytes) - class DSPError(Exception): pass + class UnkownDSPRequest(DSPError): pass + class DSPLongError(DSPError): pass + class DSPLoginError(DSPError): pass + class ClientConnection(socketserver.BaseRequestHandler): """Should be subclassed database , ... ; so variables for the hole server, as classvariable""" - + def __init__(self, request, client_address, server): print('Init') self.user = None self.longtype = None self.longparts = None super().__init__(request, client_address, server) - + def handle(self): # Connection accepted # waiting for requests - + ip = self.client_address print('Connected to [{}]'.format(ip)) while True: @@ -80,7 +83,7 @@ def handle(self): else: r = pickle.loads(br) print(r.request, self.user) - + # Progress Request if self.longtype != None: # Long if r.request == Requests.Long: @@ -92,15 +95,17 @@ def handle(self): data = b'' for p in self.longparts: data += p - + r = Package(self.longtype, load(data)) - + self.longttype = None self.longparts = None else: - self.sendError(DSPLongError("Can't request {} while Long".format(r.request))) + self.sendError( + DSPLongError("Can't request {} while Long".format( + r.request))) continue - + if r.request == Requests.Login: an = self.login(r.data) if issubclass(type(an), DSPError): @@ -113,7 +118,7 @@ def handle(self): if issubclass(type(an), DSPError): self.sendError(an) else: - self.sendData(an) + self.sendData(an) elif r.request == Requests.NormalSync: an = self.getData(r.data, self.user) if issubclass(type(an), DSPError): @@ -135,86 +140,84 @@ def handle(self): break else: an = self.handle_other_request(r, self.user) - if issubclass(type(an), DSPError) : + if issubclass(type(an), DSPError): self.sendError(an) elif an == True: self.sendOK() else: self.sendData(an) - + def sendp(self, p): b = p.to_bytes() self.request.sendall(b) self.request.sendall(b'end') - - + def sendError(self, error): p = Package(Answers.Error, error) #self.request.sendall(p.to_bytes()) self.sendp(p) - + def sendOK(self): p = Package(Answers.Successful, None) self.sendp(p) - + def sendData(self, data): p = Package(Answers.Data, data) self.sendp(p) - + def getallData(self, user): """Should be overwritten - + return data or return DSPError if unsuccesful""" pass - - + def getData(self, key, user): """Should be overwritten - + return data or return DSPError if unsuccesful""" pass - + def newData(self, user, data): """Should be overwritten - + return True if successful return DSPError if unsuccessful""" pass - + def login(self, requestdata): """Should be overwritten - + returns LoginError if login is unsuccessful returns login key if login is succesful""" return True - + def disconnect(self, user): """Should be overwritten""" pass - + def handle_other_request(self, package, user): """May be overwritten - + return True if successful return DSPError if unsuccessful return data if data is required""" return UnkownDSPRequest(str(package.request)) - - -class DSPSocket: - + + +class DSPSocket: + def __init__(self): self.c = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - + def connect(self, address): self.c.connect(address) - + def sendRequest(self, package): b = package.to_bytes() self.c.sendall(b) - + fullan = b'' an = b'' while True: @@ -225,14 +228,14 @@ def sendRequest(self, package): fullan += an break return load(fullan) - + def request(self, type, data=None): p = Package(type, data) return self.sendRequest(p) - + def __enter__(self): pass - + def __exit__(self, *args): try: self.request(Requests.Disconnect, None) @@ -240,5 +243,3 @@ def __exit__(self, *args): pass finally: self.c.close() - - diff --git a/python/examples/example2/messengerstuff.py b/python/examples/example2/messengerstuff.py index 19b0329..c51d4e5 100755 --- a/python/examples/example2/messengerstuff.py +++ b/python/examples/example2/messengerstuff.py @@ -4,99 +4,103 @@ @author: Yannick """ -import time import calendar -import dsp import threading +import time + +import dsp # Messenger-Dienst Zubehör + class Event: - + def __init__(self, time): self.time = time - + def __gt__(self, other): return self.time > other.time - + def __lt__(self, other): return other > self - + def __eq__(self, other): return self.time == other.time - + def str_time(self): epoch = calendar.timegm(self.time) local = time.localtime(epoch) string = time.strftime("%a, %d %b %Y %H:%M:%S", local) return string - + def __str__(self): return "" + class Post(Event): - + def __init__(self, user, time, text): super().__init__(time) self.user = user self.text = text - + def __str__(self): time = self.str_time() user = str(self.user) headline = time + ' ' + user return headline + '\n' + self.text - + def __eq__(self, other): return super().__eq__(other) and self.user == other.user \ and self.text == other.text - - - + + class ConnectEvent(Event): - + def __init__(self, time, user, connect): super().__init__(time) self.user = user self.con = connect - + def __str__(self): if self.con: return self.str_time() + ' ' + str(self.user) + ' went online.' else: - return self.str_time() + ' ' + str(self.user) + ' went offline.' - + return self.str_time() + ' ' + str( + self.user) + ' went offline.' + def __repr__(self): return str(self) - + def __eq__(self, other): - return super().__eq__(other) and self.user == other.user and self.con == other.con - + return super().__eq__( + other) and self.user == other.user and self.con == other.con + class User: - + def __init__(self, name): self.name = name - + def __str__(self): return self.name - + def __hash__(self): return hash(self.name) - + def __eq__(self, other): return self.name == other.name - + class Chat: - + def __init__(self): self.events = [] self.user = [] - + def post(self, post): self.events.append(post) - + def connect(self, user, dis, time): if dis: del self.user[self.user.index(user)] @@ -104,41 +108,37 @@ def connect(self, user, dis, time): else: self.user.append(user) self.events.append(ConnectEvent(time, user, True)) - + def getSince(self, event): self.events.sort() i = self.events.index(event) - return self.events[i+1:] - + return self.events[i + 1:] + class ChatClient(dsp.ClientConnection): chat = Chat() lock = threading.Lock() - + def login(self, data): u = User(data) with self.lock: self.chat.connect(u, False, time.gmtime()) return u - + def getallData(self, user): return self.chat - + def getData(self, time, user): with self.lock: d = self.chat.getSince(time) return d - + def newData(self, user, data): p = Post(user, time.gmtime(), data) with self.lock: self.chat.post(p) return True - + def disconnect(self, user): with self.lock: self.chat.connect(user, True, time.gmtime()) - - - - diff --git a/python/examples/example3/project.pro b/python/examples/example3/project.pro index 4cce40b..abb2bc6 100644 --- a/python/examples/example3/project.pro +++ b/python/examples/example3/project.pro @@ -2,4 +2,4 @@ SOURCES = programm.py RESOURCES = res.qrc FORMS = hauptdialog.ui TRANSLATIONS = pyqt-example_en.ts \ - pyqt-example_de.ts \ No newline at end of file + pyqt-example_de.ts diff --git a/python/examples/example3/res.py b/python/examples/example3/res.py index 34dc5ca..a9cb21e 100644 --- a/python/examples/example3/res.py +++ b/python/examples/example3/res.py @@ -2660,10 +2660,15 @@ rcc_version = 2 qt_resource_struct = qt_resource_struct_v2 + def qInitResources(): - QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, + qt_resource_name, qt_resource_data) + def qCleanupResources(): - QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, + qt_resource_name, qt_resource_data) + qInitResources() diff --git a/python/examples/example4/chattool.py b/python/examples/example4/chattool.py index 9993df1..f19a1cb 100644 --- a/python/examples/example4/chattool.py +++ b/python/examples/example4/chattool.py @@ -7,16 +7,15 @@ # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. - from PyQt5 import QtCore, QtGui, QtWidgets, uic class MainWindow(QWidgets.QDialog): + def __init__(self, parent=None): super().__init__(parent) self.ui = uic.loadUi("chattool.ui", self) - def changeEvent(self, event): super().changeEvent(event) if isinstance(event, QtCore.QEvent):