From bbfb4f92af498ada25ab9279c37629c1b19695d3 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 20 Feb 2024 10:40:23 +0300 Subject: [PATCH] fmt + StateFileFilter --- pddnsc/base.py | 14 ++++++------- pddnsc/cli.py | 39 +++++++++++++------------------------ pddnsc/filters/__init__.py | 2 +- pddnsc/filters/files.py | 40 ++++++++++++++++++++++++++++++++++---- pddnsc/loaders.py | 25 ++++++++++++++++++++++++ pddnsc/outputs/__init__.py | 4 ++-- pddnsc/outputs/console.py | 1 + pddnsc/outputs/files.py | 10 +++++++--- pddnsc/plugins.py | 35 ++++++++++++++++----------------- pddnsc/sources/__init__.py | 2 +- pddnsc/sources/fake.py | 21 +++++++++----------- pddnsc/sources/ipfy.py | 1 + settings/config.toml | 13 +++++++++---- 13 files changed, 128 insertions(+), 79 deletions(-) create mode 100644 pddnsc/loaders.py diff --git a/pddnsc/base.py b/pddnsc/base.py index f84c448..43fe738 100644 --- a/pddnsc/base.py +++ b/pddnsc/base.py @@ -2,6 +2,7 @@ import httpx import asyncio from abc import ABC, abstractmethod + class BaseSourceProvider(ABC): _childs = {} registred = {} @@ -46,12 +47,10 @@ class BaseSourceProvider(ABC): cls.registred[name] = provider(name, config, ipv4t, ipv6t) @abstractmethod - async def fetch_v4(self) -> str: - ... + async def fetch_v4(self) -> str: ... @abstractmethod - async def fetch_v6(self) -> str: - ... + async def fetch_v6(self) -> str: ... class BaseOutputProvider(ABC): @@ -94,8 +93,8 @@ class BaseOutputProvider(ABC): return await self.set_addrs_imp(source_provider, addr_v4, addr_v6) @abstractmethod - async def set_addrs_imp(self, source_provider, addr_v4, addr_v6): - ... + async def set_addrs_imp(self, source_provider, addr_v4, addr_v6): ... + class BaseFilterProvider(ABC): _childs = {} @@ -137,5 +136,4 @@ class BaseFilterProvider(ABC): return await self.check_imp(source_provider, addr_v4, addr_v6) @abstractmethod - async def check_imp(self, source_provider, addr_v4, addr_v6): - ... \ No newline at end of file + async def check_imp(self, source_provider, addr_v4, addr_v6): ... diff --git a/pddnsc/cli.py b/pddnsc/cli.py index 4e9ba29..483943e 100644 --- a/pddnsc/cli.py +++ b/pddnsc/cli.py @@ -3,9 +3,8 @@ import asyncio from abc import ABC, abstractmethod import toml from .base import BaseFilterProvider, BaseSourceProvider, BaseOutputProvider -from . import sources -from . import outputs -from . import filters +from .plugins import use_plugins + async def source_task(): providers = BaseSourceProvider.registred.values() @@ -30,12 +29,6 @@ async def source_task(): return result -async def output_task(providers, result): - providers = BaseOutputProvider.registred.values() - await asyncio.gather( - *(asyncio.create_task(p.set_addrs(*result), name=p.name) for p in providers) - ) - async def filter_task(ip_result): providers = BaseFilterProvider.registred.values() result = True @@ -55,29 +48,23 @@ async def filter_task(ip_result): await gather except asyncio.CancelledError: pass - + if not result: print("failed filter:", failed) return result - + + +async def output_task(result): + providers = BaseOutputProvider.registred.values() + await asyncio.gather( + *(asyncio.create_task(p.set_addrs(*result), name=p.name) for p in providers) + ) + async def app(ipv4t, ipv6t): config = toml.load("settings/config.toml") - for source_name in config["sources"]: - BaseSourceProvider.register_provider( - source_name, config["sources"][source_name], ipv4t, ipv6t - ) - - for filter_name in config["filters"]: - BaseFilterProvider.register_provider( - filter_name, config["filters"][filter_name], ipv4t, ipv6t - ) - - for output_name in config["outputs"]: - BaseOutputProvider.register_provider( - output_name, config["outputs"][output_name], ipv4t, ipv6t - ) + use_plugins(config, ipv4t, ipv6t) debug = config.get("debug", False) if debug: @@ -100,13 +87,13 @@ async def app(ipv4t, ipv6t): print( f"output providers: {[*BaseOutputProvider.registred]}, {[*map(str, BaseOutputProvider.registred.values())]}" ) - #print(config) result = await source_task() if not await filter_task(result): print("stop by filters") return await output_task(result) + print("done") async def main(): diff --git a/pddnsc/filters/__init__.py b/pddnsc/filters/__init__.py index 1587c10..6be9257 100644 --- a/pddnsc/filters/__init__.py +++ b/pddnsc/filters/__init__.py @@ -1,3 +1,3 @@ -from pddnsc.plugins import load_plugins +from pddnsc.loaders import load_plugins load_plugins(__file__) diff --git a/pddnsc/filters/files.py b/pddnsc/filters/files.py index 3680978..3330e2c 100644 --- a/pddnsc/filters/files.py +++ b/pddnsc/filters/files.py @@ -6,9 +6,10 @@ from os.path import isfile from pddnsc.base import BaseFilterProvider + class StateHashFilter(BaseFilterProvider): async def check_imp(self, source_provider, addr_v4, addr_v6): - if not isfile(self.config['filepath']): + if not isfile(self.config["filepath"]): return True new_state = { @@ -16,8 +17,39 @@ class StateHashFilter(BaseFilterProvider): "ipv6": addr_v6 or "", } new_state_str = json.dumps(new_state) - new_sha = hashlib.sha256(new_state_str.encode(encoding='utf-8')) - async with aiofiles.open(self.config['filepath'], mode='r', encoding='utf-8') as f: + new_sha = hashlib.sha256(new_state_str.encode(encoding="utf-8")) + async with aiofiles.open( + self.config["filepath"], mode="r", encoding="utf-8" + ) as f: old_state_hash = await f.read() - + return old_state_hash != new_sha.hexdigest() + + +class StateFileFilter(BaseFilterProvider): + async def check_imp(self, source_provider, addr_v4, addr_v6): + if not isfile(self.config["filepath"]): + return True + + new_state = { + "ipv4": addr_v4 or "", + "ipv6": addr_v6 or "", + } + + async with aiofiles.open( + self.config["filepath"], mode="r", encoding="utf-8" + ) as f: + old_state = json.loads(await f.read()) + + result = True + + if "check_ipv4" not in self.config and "check_ipv4" not in self.config: + return new_state != old_state + + if self.config.get("check_ipv4", False): + result = result and new_state["ipv4"] != old_state["ipv4"] + + if self.config.get("check_ipv6", False): + result = result and new_state["ipv6"] != old_state["ipv6"] + + return result diff --git a/pddnsc/loaders.py b/pddnsc/loaders.py new file mode 100644 index 0000000..00ee9f5 --- /dev/null +++ b/pddnsc/loaders.py @@ -0,0 +1,25 @@ +import os +import traceback +from importlib import util + + +def load_module(path): + name = os.path.split(path)[-1] + spec = util.spec_from_file_location(name, path) + module = util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def load_plugins(init_filepath): + dirpath = os.path.dirname(os.path.abspath(init_filepath)) + for fname in os.listdir(dirpath): + if ( + not fname.startswith(".") + and not fname.startswith("__") + and fname.endswith(".py") + ): + try: + load_module(os.path.join(dirpath, fname)) + except Exception: + traceback.print_exc() diff --git a/pddnsc/outputs/__init__.py b/pddnsc/outputs/__init__.py index c97dfea..6be9257 100644 --- a/pddnsc/outputs/__init__.py +++ b/pddnsc/outputs/__init__.py @@ -1,3 +1,3 @@ -from pddnsc.plugins import load_plugins +from pddnsc.loaders import load_plugins -load_plugins(__file__) \ No newline at end of file +load_plugins(__file__) diff --git a/pddnsc/outputs/console.py b/pddnsc/outputs/console.py index 36a2062..93495e9 100644 --- a/pddnsc/outputs/console.py +++ b/pddnsc/outputs/console.py @@ -2,6 +2,7 @@ import asyncio from pddnsc.base import BaseOutputProvider + class JustPrint(BaseOutputProvider): async def set_addrs_imp(self, source_provider, addr_v4, addr_v6): print(f">> {self.name}") diff --git a/pddnsc/outputs/files.py b/pddnsc/outputs/files.py index 13a18df..b0ab7c2 100644 --- a/pddnsc/outputs/files.py +++ b/pddnsc/outputs/files.py @@ -13,7 +13,9 @@ class StateFile(BaseOutputProvider): "ipv6": addr_v6 or "", } state_str = json.dumps(state) - async with aiofiles.open(self.config['filepath'], mode='w', encoding='utf-8') as f: + async with aiofiles.open( + self.config["filepath"], mode="w", encoding="utf-8" + ) as f: await f.write(state_str) @@ -24,6 +26,8 @@ class StateHashFile(BaseOutputProvider): "ipv6": addr_v6 or "", } state_str = json.dumps(state) - sha = hashlib.sha256(state_str.encode(encoding='utf-8')) - async with aiofiles.open(self.config['filepath'], mode='w', encoding='utf-8') as f: + sha = hashlib.sha256(state_str.encode(encoding="utf-8")) + async with aiofiles.open( + self.config["filepath"], mode="w", encoding="utf-8" + ) as f: await f.write(sha.hexdigest()) diff --git a/pddnsc/plugins.py b/pddnsc/plugins.py index be09711..8547462 100644 --- a/pddnsc/plugins.py +++ b/pddnsc/plugins.py @@ -1,22 +1,21 @@ -import os -import traceback -from importlib import util +from .base import BaseSourceProvider, BaseFilterProvider, BaseOutputProvider +from . import sources +from . import outputs +from . import filters -def load_module(path): - name = os.path.split(path)[-1] - spec = util.spec_from_file_location(name, path) - module = util.module_from_spec(spec) - spec.loader.exec_module(module) - return module +def use_plugins(config, ipv4t, ipv6t): + for source_name in config["sources"]: + BaseSourceProvider.register_provider( + source_name, config["sources"][source_name], ipv4t, ipv6t + ) + for filter_name in config["filters"]: + BaseFilterProvider.register_provider( + filter_name, config["filters"][filter_name], ipv4t, ipv6t + ) -def load_plugins(init_filepath): - dirpath = os.path.dirname(os.path.abspath(init_filepath)) - for fname in os.listdir(dirpath): - if not fname.startswith('.') and \ - not fname.startswith('__') and fname.endswith('.py'): - try: - load_module(os.path.join(dirpath, fname)) - except Exception: - traceback.print_exc() + for output_name in config["outputs"]: + BaseOutputProvider.register_provider( + output_name, config["outputs"][output_name], ipv4t, ipv6t + ) diff --git a/pddnsc/sources/__init__.py b/pddnsc/sources/__init__.py index 1587c10..6be9257 100644 --- a/pddnsc/sources/__init__.py +++ b/pddnsc/sources/__init__.py @@ -1,3 +1,3 @@ -from pddnsc.plugins import load_plugins +from pddnsc.loaders import load_plugins load_plugins(__file__) diff --git a/pddnsc/sources/fake.py b/pddnsc/sources/fake.py index ac0254e..cbf1284 100644 --- a/pddnsc/sources/fake.py +++ b/pddnsc/sources/fake.py @@ -3,29 +3,26 @@ import asyncio from pddnsc.base import BaseSourceProvider + class DummySource(BaseSourceProvider): async def fetch_v4(self) -> str: - async with httpx.AsyncClient(transport=self.ipv4t) as client: - result = await asyncio.sleep(10, result=None) - result = await asyncio.sleep(10, result=None) + result = await asyncio.sleep(self.config.get("delay", 1), result=None) return result async def fetch_v6(self) -> str: - async with httpx.AsyncClient(transport=self.ipv6t) as client: - result = await asyncio.sleep(10, result=None) - result = await asyncio.sleep(10, result=None) + result = await asyncio.sleep(self.config.get("delay", 1), result=None) return result class FakeSource(BaseSourceProvider): async def fetch_v4(self) -> str: - async with httpx.AsyncClient(transport=self.ipv4t) as client: - result = await asyncio.sleep( - 3.3, result=self.config.get("ipv4", "127.0.0.1") - ) + result = await asyncio.sleep( + self.config.get("delay", 1), result=self.config.get("ipv4", "127.0.0.1") + ) return result async def fetch_v6(self) -> str: - async with httpx.AsyncClient(transport=self.ipv6t) as client: - result = await asyncio.sleep(4.4, result=self.config.get("ipv6", "::1")) + result = await asyncio.sleep( + self.config.get("delay", 1), result=self.config.get("ipv6", "::1") + ) return result diff --git a/pddnsc/sources/ipfy.py b/pddnsc/sources/ipfy.py index 5ab3095..8a06d49 100644 --- a/pddnsc/sources/ipfy.py +++ b/pddnsc/sources/ipfy.py @@ -2,6 +2,7 @@ import httpx from pddnsc.base import BaseSourceProvider + class IPIFYSource(BaseSourceProvider): async def fetch_v4(self) -> str: async with httpx.AsyncClient(transport=self.ipv4t) as client: diff --git a/settings/config.toml b/settings/config.toml index 4d3fd42..e50a56a 100644 --- a/settings/config.toml +++ b/settings/config.toml @@ -1,21 +1,26 @@ debug = true [sources] - [sources.test1-src] + [sources.ipfy] provider = "IPIFYSource" - [sources.test2-src] + [sources.fake] provider = "FakeSource" + delay = 10 ipv6 = "fe80::1" [filters] + [filters.state-file] + provider = "StateFileFilter" + filepath = "state/state.json" + check_ipv4 = true [filters.state-hash] provider = "StateHashFilter" filepath = "state/hash.txt" - + [outputs] [outputs.print] provider = "JustPrint" - [outputs.file] + [outputs.state-file] provider = "StateFile" filepath = "state/state.json" [outputs.hash-file]