cascade-core 0.1.12__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 vadzimshpak
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,58 @@
1
+ Metadata-Version: 2.4
2
+ Name: cascade-core
3
+ Version: 0.1.12
4
+ Summary: Cascade Core
5
+ License: MIT License
6
+
7
+ Copyright (c) 2024 vadzimshpak
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+
27
+ Requires-Python: >=3.11
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Requires-Dist: mss
31
+ Requires-Dist: python-dotenv
32
+ Requires-Dist: requests
33
+ Provides-Extra: test
34
+ Requires-Dist: pytest; extra == "test"
35
+ Dynamic: license-file
36
+
37
+ # Cascade Core
38
+
39
+ <p align="center">
40
+ <img src="./media/logo.png" width="250">
41
+ </p>
42
+
43
+ Arch:
44
+ - Command - smallest part
45
+ - Pipeline - mix of commands
46
+ - Program - mix of commands and pipelines
47
+ - [Next level...]
48
+ - [Next level...]
49
+
50
+ Philosophy:
51
+
52
+ - Each well-tested and documented command is part of the sucessfull pipeline.
53
+ - Each well-tested and documented pipeline is part of the sucessfull program.
54
+ - Each well-tested and documented program is part of the sucessfull cascade.
55
+ - Each well-tested and documented cascade is part of the sucessfull business.
56
+
57
+ Add submodule to project:
58
+ `git submodule add https://github.com/vadzimshpak/cascade-core cascade`
@@ -0,0 +1,22 @@
1
+ # Cascade Core
2
+
3
+ <p align="center">
4
+ <img src="./media/logo.png" width="250">
5
+ </p>
6
+
7
+ Arch:
8
+ - Command - smallest part
9
+ - Pipeline - mix of commands
10
+ - Program - mix of commands and pipelines
11
+ - [Next level...]
12
+ - [Next level...]
13
+
14
+ Philosophy:
15
+
16
+ - Each well-tested and documented command is part of the sucessfull pipeline.
17
+ - Each well-tested and documented pipeline is part of the sucessfull program.
18
+ - Each well-tested and documented program is part of the sucessfull cascade.
19
+ - Each well-tested and documented cascade is part of the sucessfull business.
20
+
21
+ Add submodule to project:
22
+ `git submodule add https://github.com/vadzimshpak/cascade-core cascade`
@@ -0,0 +1,6 @@
1
+ from .command import Command
2
+ from .pipeline import Pipeline
3
+ from .program import Program
4
+ from .logger import get_logger
5
+ from .resource import Resource
6
+ from .stack import Stack
@@ -0,0 +1,69 @@
1
+ from __future__ import annotations
2
+ import time
3
+ import random
4
+ from typing import TYPE_CHECKING, Self
5
+
6
+ from .logger import get_logger
7
+ from .stack import Stack
8
+
9
+
10
+ if TYPE_CHECKING:
11
+ from .operator import Operator
12
+
13
+
14
+ logger = get_logger(__name__)
15
+ random.seed(time.time())
16
+
17
+ class Command:
18
+ def __init__(self):
19
+ self._store_var = None
20
+ self._stack: Stack = None
21
+ self._skip_on_raise = False
22
+ self._jump_on_raise = None
23
+ self._jump_on_success = None
24
+ self._vars = []
25
+
26
+ def execute(self, stack: Stack):
27
+ logger.debug(f"Execute subject: {type(self).__name__}")
28
+
29
+ self._stack = stack
30
+ result = self.body()
31
+
32
+ if type(result) is str:
33
+ logger.debug(f"Got {result if len(result) < 100 else result[:100] + '...'} from {type(self).__name__}")
34
+ else:
35
+ logger.debug(f"Got {result} from {type(self).__name__}")
36
+
37
+ if self._store_var:
38
+ stack.update(self._store_var, result)
39
+
40
+ return result
41
+
42
+ def body(self):
43
+ raise Exception("This subject doesn't have body!")
44
+
45
+ def get_param_value(self, number: int):
46
+ return self._stack.top_value(self._vars[number])
47
+
48
+ def __rshift__(self, command: Operator) -> Self | object | Exception:
49
+ from .operator import Execute, Store, RaiseSkip, JumpOnRaise, JumpOnSuccess
50
+
51
+ if type(command) == Execute:
52
+ try:
53
+ return self.execute(command.stack)
54
+ except Exception as e:
55
+ return e
56
+
57
+ elif type(command) == Store:
58
+ self._store_var = command.var_name
59
+
60
+ elif type(command) == RaiseSkip:
61
+ self._skip_on_raise = True
62
+
63
+ elif type(command) == JumpOnRaise:
64
+ self._jump_on_raise = command.command_index
65
+
66
+ elif type(command) == JumpOnSuccess:
67
+ self._jump_on_success = command.command_index
68
+
69
+ return self
@@ -0,0 +1 @@
1
+ from .service import *
@@ -0,0 +1,108 @@
1
+ import time
2
+ import random
3
+ import logging
4
+
5
+ from cascade.command import Command
6
+ from cascade.logger import get_logger
7
+
8
+
9
+ logger = get_logger(__name__)
10
+ random.seed(time.time())
11
+
12
+
13
+ class SleepCommand(Command):
14
+ """
15
+ Sleep n seconds
16
+ """
17
+
18
+ def __init__(self, secs: int):
19
+ super().__init__()
20
+ self.secs = secs
21
+
22
+ def body(self):
23
+ time.sleep(self.secs)
24
+
25
+ class SleepRandomRangeCommand(Command):
26
+ """
27
+ Sleep a random number of seconds within a range
28
+ """
29
+
30
+ def __init__(self, secs_from: int, secs_to: int):
31
+ super().__init__()
32
+ self.secs_from = secs_from
33
+ self.secs_to = secs_to
34
+
35
+ def body(self):
36
+ time.sleep(random.randrange(self.secs_from, self.secs_to))
37
+
38
+ class LogCommand(Command):
39
+ """
40
+ Log a message at a specified level
41
+ """
42
+
43
+ def __init__(self, level: int, message: str):
44
+ super().__init__()
45
+ self.level = level
46
+ self.message = message
47
+
48
+ def body(self):
49
+ logger.log(self.level, self.message)
50
+
51
+ class LogExceptionCommand(Command):
52
+ """
53
+ Log an exception at the ERROR level
54
+ """
55
+
56
+ def __init__(self, exception: object):
57
+ super().__init__()
58
+ self.exception = exception
59
+
60
+ def body(self):
61
+ logger.log(logging.ERROR, self.exception)
62
+
63
+ class DynamicLogCommand(Command):
64
+ """
65
+ Log a dynamic message at a specified level
66
+ """
67
+
68
+ def __init__(self, level: int):
69
+ super().__init__()
70
+ self.level = level
71
+
72
+ def body(self):
73
+ message = self.get_param_value(0)
74
+ logger.log(self.level, message)
75
+
76
+ class SetStackCommand(Command):
77
+ """
78
+ Set the stack to a specified value, need to use with Store(<var>) operator!
79
+ """
80
+
81
+ def __init__(self, value):
82
+ super().__init__()
83
+ self.value = value
84
+
85
+ def body(self):
86
+ return self.value
87
+
88
+ class DebugStackCommand(Command):
89
+ """
90
+ Debug the current stack
91
+ """
92
+
93
+ def __init__(self):
94
+ super().__init__()
95
+
96
+ def body(self):
97
+ logger.debug("Stack: " + repr(self._stack))
98
+
99
+ class RaiseCommand(Command):
100
+ """
101
+ Raise an exception
102
+ """
103
+
104
+ def __init__(self):
105
+ super().__init__()
106
+
107
+ def body(self):
108
+ raise Exception("Test raise")
@@ -0,0 +1,83 @@
1
+ import logging
2
+ import sys
3
+ import os
4
+ import requests
5
+ import mss
6
+ import mss.tools
7
+ import io
8
+
9
+
10
+ class ConsoleHandler(logging.StreamHandler):
11
+ def __init__(self, log_level: str, stream):
12
+ super().__init__(stream)
13
+
14
+ formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
15
+ self.setFormatter(formatter)
16
+ self.setLevel(log_level)
17
+
18
+ class TGHandler(logging.Handler):
19
+ def __init__(self, token: str, chats: str, level: str, image_on_error: bool):
20
+ super().__init__()
21
+ self.chats = chats.split(',')
22
+ self.image_on_error = image_on_error
23
+ self.url = f"https://api.telegram.org/bot{token}/sendMessage"
24
+ self.image_url = f"https://api.telegram.org/bot{token}/sendPhoto"
25
+
26
+ formatter = logging.Formatter("%(levelname)s - %(message)s")
27
+ self.setFormatter(formatter)
28
+ self.setLevel(level)
29
+
30
+ def emit(self, record):
31
+ log_entry = self.format(record)
32
+
33
+ for chat in self.chats:
34
+ payload = {
35
+ "chat_id": chat,
36
+ "text": log_entry
37
+ }
38
+
39
+ requests.post(self.url, json=payload)
40
+
41
+ if not self.image_on_error:
42
+ return
43
+
44
+ with mss.mss() as sct:
45
+ monitor = sct.monitors[1]
46
+ sct_img = sct.grab(monitor)
47
+ png_bytes = mss.tools.to_png(sct_img.rgb, sct_img.size)
48
+
49
+ image_file = io.BytesIO(png_bytes)
50
+ image_file.name = 'screenshot.png'
51
+
52
+ for chat in self.chats:
53
+ payload = {'chat_id': chat}
54
+ files = {'photo': image_file}
55
+
56
+ requests.post(self.image_url, data=payload, files=files)
57
+
58
+ class CustomFileHandler(logging.FileHandler):
59
+ def __init__(self, filename, mode = "a", encoding = None, delay = False, errors = None):
60
+ super().__init__(filename, mode, encoding, delay, errors)
61
+
62
+ formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
63
+ self.setFormatter(formatter)
64
+ self.setLevel(logging.DEBUG)
65
+
66
+
67
+ def get_logger(name: str):
68
+ log_level = os.getenv("LOG_LEVEL", "DEBUG")
69
+ tg_token = os.getenv("TG_BOT_TOKEN", None)
70
+ tg_chats = os.getenv("TG_BOT_CHATS", None)
71
+ tg_log_level = os.getenv("TG_LOG_LEVEL", "INFO")
72
+ tg_image_on_error = os.getenv("TG_IMAGE_ON_ERROR", 0) == "1"
73
+
74
+ logger = logging.getLogger(f"genshin_game.[{name}]")
75
+ logger.setLevel(logging.DEBUG)
76
+
77
+ logger.addHandler(ConsoleHandler(log_level, sys.stdout))
78
+ logger.addHandler(CustomFileHandler("log.txt"))
79
+
80
+ if tg_token and tg_chats:
81
+ logger.addHandler(TGHandler(tg_token, tg_chats, tg_log_level, tg_image_on_error))
82
+
83
+ return logger
@@ -0,0 +1,38 @@
1
+ from .stack import Stack
2
+
3
+ class Operator:
4
+ pass
5
+
6
+ class Execute(Operator):
7
+ def __init__(self, stack: Stack):
8
+ self.stack = stack
9
+
10
+ class Store(Operator):
11
+ def __init__(self, var_name):
12
+ self.var_name = var_name
13
+
14
+ class Param(Operator):
15
+ def __init__(self, value, accumulated=None):
16
+ self.values = accumulated if accumulated is not None else []
17
+ self.values.append(value)
18
+
19
+ def __rshift__(self, subject):
20
+ if isinstance(subject, Param):
21
+ return Param(subject.values[0], accumulated=self.values)
22
+
23
+ subject._vars = self.values
24
+ return subject
25
+
26
+ class RaiseSkip(Operator):
27
+ def __init__(self):
28
+ pass
29
+
30
+ class JumpOnRaise(Operator):
31
+ def __init__(self, command_index: int):
32
+ super().__init__()
33
+ self.command_index = command_index
34
+
35
+ class JumpOnSuccess(Operator):
36
+ def __init__(self, command_index: int):
37
+ super().__init__()
38
+ self.command_index = command_index
@@ -0,0 +1,49 @@
1
+ from .command import Command
2
+ from .logger import get_logger
3
+
4
+ logger = get_logger(__name__)
5
+
6
+ class Pipeline(Command):
7
+ def __init__(self):
8
+ super().__init__()
9
+ self.pipeline = []
10
+
11
+ def _process_chain(self, chain: list):
12
+ from .operator import Execute
13
+ self._stack.push({} if not self._stack.top() else self._stack.top().copy())
14
+ self._stack.update("__debug_name", type(self).__name__)
15
+
16
+ i = 0
17
+ while i < len(chain):
18
+ subject = chain[i]
19
+ i += 1
20
+
21
+ if type(subject) == list:
22
+ self._process_chain(subject)
23
+ continue
24
+
25
+ result = subject >> Execute(self._stack)
26
+
27
+ if isinstance(result, Exception):
28
+ if subject._jump_on_raise is not None:
29
+ i = subject._jump_on_raise
30
+ logger.debug(f"{type(self).__name__} jump to {i} subject")
31
+ continue
32
+
33
+ if subject._skip_on_raise:
34
+ logger.debug(f"{type(self).__name__} skip pipeline")
35
+ break
36
+ else:
37
+ logger.debug(f"{type(self).__name__} raise")
38
+ raise result
39
+
40
+ if subject._jump_on_success is not None:
41
+ i = subject._jump_on_success
42
+ logger.debug(f"{type(self).__name__} jump to {i} subject")
43
+ continue
44
+
45
+ result = self._stack.pop()
46
+ return result.get("result")
47
+
48
+ def body(self):
49
+ return self._process_chain(self.pipeline)
@@ -0,0 +1,6 @@
1
+ from .pipeline import Pipeline
2
+
3
+
4
+ class Program(Pipeline):
5
+ def __init__(self):
6
+ super().__init__()
@@ -0,0 +1,9 @@
1
+ import base64
2
+
3
+ class Resource:
4
+ def __init__(self, path: str):
5
+ self.path = path
6
+
7
+ def openAsBase64(self):
8
+ with open(self.path, "rb") as image_file:
9
+ return base64.b64encode(image_file.read()).decode('utf-8')
@@ -0,0 +1,26 @@
1
+ class Stack:
2
+ def __init__(self):
3
+ self.stack = [{}]
4
+
5
+ def push(self, data):
6
+ self.stack.append(data)
7
+
8
+ def pop(self):
9
+ return self.stack.pop()
10
+
11
+ def top(self):
12
+ if len(self.stack) == 0:
13
+ return None
14
+ return self.stack[-1]
15
+
16
+ def update(self, key, value):
17
+ self.stack[-1].update({key: value})
18
+
19
+ def raw(self):
20
+ return self.stack
21
+
22
+ def top_value(self, var_name):
23
+ return self.top().get(var_name)
24
+
25
+ def __repr__(self):
26
+ return repr(self.stack)
@@ -0,0 +1,58 @@
1
+ Metadata-Version: 2.4
2
+ Name: cascade-core
3
+ Version: 0.1.12
4
+ Summary: Cascade Core
5
+ License: MIT License
6
+
7
+ Copyright (c) 2024 vadzimshpak
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+
27
+ Requires-Python: >=3.11
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Requires-Dist: mss
31
+ Requires-Dist: python-dotenv
32
+ Requires-Dist: requests
33
+ Provides-Extra: test
34
+ Requires-Dist: pytest; extra == "test"
35
+ Dynamic: license-file
36
+
37
+ # Cascade Core
38
+
39
+ <p align="center">
40
+ <img src="./media/logo.png" width="250">
41
+ </p>
42
+
43
+ Arch:
44
+ - Command - smallest part
45
+ - Pipeline - mix of commands
46
+ - Program - mix of commands and pipelines
47
+ - [Next level...]
48
+ - [Next level...]
49
+
50
+ Philosophy:
51
+
52
+ - Each well-tested and documented command is part of the sucessfull pipeline.
53
+ - Each well-tested and documented pipeline is part of the sucessfull program.
54
+ - Each well-tested and documented program is part of the sucessfull cascade.
55
+ - Each well-tested and documented cascade is part of the sucessfull business.
56
+
57
+ Add submodule to project:
58
+ `git submodule add https://github.com/vadzimshpak/cascade-core cascade`
@@ -0,0 +1,18 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ cascade/__init__.py
5
+ cascade/command.py
6
+ cascade/logger.py
7
+ cascade/operator.py
8
+ cascade/pipeline.py
9
+ cascade/program.py
10
+ cascade/resource.py
11
+ cascade/stack.py
12
+ cascade/commands/__init__.py
13
+ cascade/commands/service.py
14
+ cascade_core.egg-info/PKG-INFO
15
+ cascade_core.egg-info/SOURCES.txt
16
+ cascade_core.egg-info/dependency_links.txt
17
+ cascade_core.egg-info/requires.txt
18
+ cascade_core.egg-info/top_level.txt
@@ -0,0 +1,6 @@
1
+ mss
2
+ python-dotenv
3
+ requests
4
+
5
+ [test]
6
+ pytest
@@ -0,0 +1,24 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "cascade-core"
7
+ version = "0.1.12"
8
+ description = "Cascade Core"
9
+ readme = "README.md"
10
+ license = { file = "LICENSE" }
11
+ requires-python = ">=3.11"
12
+ dependencies = [
13
+ "mss",
14
+ "python-dotenv",
15
+ "requests",
16
+ ]
17
+
18
+ [project.optional-dependencies]
19
+ test = [
20
+ "pytest",
21
+ ]
22
+
23
+ [tool.setuptools.packages.find]
24
+ include = ["cascade*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+