diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..7f52aed --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[run] +omit = ./testing/* \ No newline at end of file diff --git a/.gitignore b/.gitignore index b0b6f3a..7ebdf52 100644 --- a/.gitignore +++ b/.gitignore @@ -157,4 +157,7 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -.idea/ \ No newline at end of file +.idea/ + +# sequence files +*.sequence \ No newline at end of file diff --git a/README.md b/README.md index 5e259b7..de379c7 100644 --- a/README.md +++ b/README.md @@ -92,9 +92,7 @@ See more in [Full Documentation](https://replaywizard.craftsman.lol/install.html ## Quickstart ```python -from replay_wizard import info - -print(info()) +print('Empty yet') ``` ### More examples in [Full Documentation][documentation_path] @@ -139,12 +137,13 @@ This features will be built during 4 weeks. - python (library was tested with **3.10**, **3.11** versions) - pynput (this package use [LGPL-3.0 license](https://github.com/moses-palmer/pynput/blob/master/COPYING.LGPL) and used in this project as a third party library without modifications) +- pydantic See more in [Full Documentation](https://replaywizard.craftsman.lol/about.html#requirements) ## Development Status -- Package available soon on [PyPi](https://pypi.org/project/replay-wizard/) +- Package available on [PyPi](https://pypi.org/project/replay-wizard/) See more in [Full Documentation](https://replaywizard.craftsman.lol/about.html#development-status) diff --git a/main.py b/main.py new file mode 100644 index 0000000..cb88b5e --- /dev/null +++ b/main.py @@ -0,0 +1,3 @@ +# from replay_wizard import capture +# +# capture('one') diff --git a/pylintrc b/pylintrc index 4a111e5..30e3244 100644 --- a/pylintrc +++ b/pylintrc @@ -1,2 +1,4 @@ [MAIN] ignore-paths=./replay_wizard/package.py +disable= + W0621, # Redefining name outer scope (pytest fixtures) diff --git a/quickstart/main.py b/quickstart/main.py index 8d1bd0e..d4e7db1 100644 --- a/quickstart/main.py +++ b/quickstart/main.py @@ -8,6 +8,6 @@ def main(): ReplayWizard simple usage :return: """ - from replay_wizard import info # pylint: disable=import-outside-toplevel + from replay_wizard.models import Action # pylint: disable=import-outside-toplevel - print(info()) + print(Action) diff --git a/replay_wizard/__init__.py b/replay_wizard/__init__.py index 2eef63a..45df026 100644 --- a/replay_wizard/__init__.py +++ b/replay_wizard/__init__.py @@ -1,4 +1,4 @@ """ -PyGenesis package +ReplayWizard package """ -from .main import info +#from .capturing import capture diff --git a/replay_wizard/capturing.py b/replay_wizard/capturing.py new file mode 100644 index 0000000..f706559 --- /dev/null +++ b/replay_wizard/capturing.py @@ -0,0 +1,41 @@ +# """ +# Main module +# """ +# from pynput import keyboard +# +# +# def on_press(key, f): +# try: +# print('alphanumeric key {0} pressed'.format( +# key.char)) +# f.write(f'{key.char} pressed\n') +# except AttributeError: +# print('special key {0} pressed'.format( +# key)) +# +# +# def on_release(key, f): +# if key == keyboard.Key.esc: +# # Stop listener +# return False +# print('{0} released'.format( +# key)) +# f.write(f'{key.char} released\n') +# +# +# def capture(name): +# file_name = f'{name}.sequence' +# with open(file_name, 'w', encoding='utf-8') as f: +# on_press_handler = lambda key: on_press(key, f) +# on_release_handler = lambda key: on_release(key, f) +# +# with keyboard.Listener( +# on_press=on_press_handler, +# on_release=on_release_handler) as listener: +# listener.join() +# +# # ...or, in a non-blocking fashion: +# # listener = keyboard.Listener( +# # on_press=on_press, +# # on_release=on_release) +# # listener.start() diff --git a/replay_wizard/main.py b/replay_wizard/main.py index deb98c3..e69de29 100644 --- a/replay_wizard/main.py +++ b/replay_wizard/main.py @@ -1,13 +0,0 @@ -""" -Main module -""" - - -def info(): - """ - ReplayWizard info - :return: ReplayWizard usage info - """ - result = ('This is the mock of ReplayWizard package: ' - 'https://github.com/quillcraftsman/replay-wizard') - return result diff --git a/replay_wizard/models/__init__.py b/replay_wizard/models/__init__.py new file mode 100644 index 0000000..b704383 --- /dev/null +++ b/replay_wizard/models/__init__.py @@ -0,0 +1,5 @@ +""" +Models package +""" +from .action import Action, Subtypes +from .sequence import Sequence diff --git a/replay_wizard/models/action.py b/replay_wizard/models/action.py new file mode 100644 index 0000000..5a721f1 --- /dev/null +++ b/replay_wizard/models/action.py @@ -0,0 +1,24 @@ +""" +Action module +""" +from enum import Enum +from pydantic import BaseModel, ConfigDict + + +class Subtypes(str, Enum): + """ + Action subtype + """ + KEYBOARD = 'keyboard' + MOUSE = 'mouse' + + +class Action(BaseModel): + """ + Action model + """ + model_config = ConfigDict(frozen=True) + + subtype: Subtypes + value: str + timedelta: float diff --git a/replay_wizard/models/sequence.py b/replay_wizard/models/sequence.py new file mode 100644 index 0000000..d991a82 --- /dev/null +++ b/replay_wizard/models/sequence.py @@ -0,0 +1,26 @@ +""" +Sequence module +""" +from typing import List +from pydantic import BaseModel +from .action import Action + + +class Sequence(BaseModel): + """ + Action sequence + """ + name: str + actions: List[Action] + + def __len__(self): + return len(self.actions) + + def add(self, new_action: Action): + """ + Add action to sequence + + :param new_action: Action to add + :return: None + """ + self.actions.append(new_action) diff --git a/requirements.txt b/requirements.txt index 670ab3d..e4181b6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ -pynput==1.7.6 \ No newline at end of file +pynput==1.7.6 +pydantic==2.6.3 \ No newline at end of file diff --git a/setup.py b/setup.py index a0393d5..071a8ed 100644 --- a/setup.py +++ b/setup.py @@ -91,6 +91,7 @@ def read(filename): ], install_requires=[ 'pynput==1.7.6', + 'pydantic==2.6.3', ], python_requires=">=3", classifiers=[ diff --git a/testing/test_pygenesis/test_main.py b/testing/test_pygenesis/test_main.py deleted file mode 100644 index 9028bb5..0000000 --- a/testing/test_pygenesis/test_main.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -Test main -""" -from replay_wizard import info - - -def test_info(): - """ - Test info - :return: None - """ - info_expected_text = ('This is the mock of ReplayWizard package: ' - 'https://github.com/quillcraftsman/replay-wizard') - assert info() == info_expected_text diff --git a/testing/test_pygenesis/__init__.py b/testing/test_replay_wizard/__init__.py similarity index 100% rename from testing/test_pygenesis/__init__.py rename to testing/test_replay_wizard/__init__.py diff --git a/testing/test_replay_wizard/test_models/__init__.py b/testing/test_replay_wizard/test_models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/testing/test_replay_wizard/test_models/conftest.py b/testing/test_replay_wizard/test_models/conftest.py new file mode 100644 index 0000000..3b5535c --- /dev/null +++ b/testing/test_replay_wizard/test_models/conftest.py @@ -0,0 +1,29 @@ +""" +Pytest fixtures +""" +from pytest import fixture +from replay_wizard.models import Action, Subtypes + + +@fixture +def put_a_action(): + """ + Simple action fixture + """ + return Action( + subtype=Subtypes.KEYBOARD, + value='a', + timedelta=0.1, + ) + + +@fixture +def put_a_action_dict(): + """ + Action as dict fixture + """ + return { + 'value': 'a', + 'subtype': 'keyboard', + 'timedelta': 0.1, + } diff --git a/testing/test_replay_wizard/test_models/test_actions.py b/testing/test_replay_wizard/test_models/test_actions.py new file mode 100644 index 0000000..862be14 --- /dev/null +++ b/testing/test_replay_wizard/test_models/test_actions.py @@ -0,0 +1,30 @@ +""" +Test Action model module +""" +import pytest +from pydantic import ValidationError +from replay_wizard.models import Subtypes + + +def test_action(put_a_action): + """ + Test simple action + """ + assert put_a_action.value == 'a' + assert put_a_action.subtype == Subtypes.KEYBOARD + assert put_a_action.timedelta == 0.1 + + +def test_action_is_frozen(put_a_action): + """ + Test that action is frozen (immutable) + """ + with pytest.raises(ValidationError): + put_a_action.value = 'b' + + +def test_to_dict(put_a_action, put_a_action_dict): + """ + Test action to dict + """ + assert put_a_action_dict == put_a_action.model_dump() diff --git a/testing/test_replay_wizard/test_models/test_sequence.py b/testing/test_replay_wizard/test_models/test_sequence.py new file mode 100644 index 0000000..6438d14 --- /dev/null +++ b/testing/test_replay_wizard/test_models/test_sequence.py @@ -0,0 +1,65 @@ +""" +Test sequence module +""" +import pytest +from replay_wizard.models import Action, Sequence, Subtypes + + +@pytest.fixture +def empty_sequence(): + """ + Empty sequence fixture + """ + return Sequence( + name='open youtube', + actions=[] + ) + + +def test_full_sequence(put_a_action): + """ + Test full sequence data + """ + put_b = Action( + subtype=Subtypes.KEYBOARD, + value='b', + timedelta=0.1, + ) + Sequence( + name='input a then b', + actions=[put_a_action, put_b] + ) + + +def test_len(empty_sequence): + """ + Test __len__ method + """ + assert len(empty_sequence) == 0 + + +def test_add(empty_sequence, put_a_action): + """ + Test add method + """ + assert len(empty_sequence) == 0 + empty_sequence.add(put_a_action) + assert len(empty_sequence) == 1 + + +def test_to_dict(empty_sequence, put_a_action, put_a_action_dict): + """ + Test sequence to dict + """ + result = { + 'name': 'open youtube', + 'actions': [], + } + assert result == empty_sequence.model_dump() + empty_sequence.add(put_a_action) + + result['actions'] = [ + put_a_action_dict + ] + + assert result == empty_sequence.model_dump()