diff --git a/.gitignore b/.gitignore index e648e8f..adb8fb5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ *.pyc *.pyo +__pycache__ +.pytest_cache +*.egg-info prototypes dist diff --git a/app.json b/app.json new file mode 100644 index 0000000..fba6cdd --- /dev/null +++ b/app.json @@ -0,0 +1,23 @@ +{ + "app1": { + "route": { + "operator": "and", + "rules": [ + { + "key": "url.host", + "operator": "regex", + "value": ".*" + }, + { + "key": "url.path", + "operator": "regex", + "value": "\/" + } + ] + }, + "function": { + "operator": "const", + "value": "OK" + } + } +} diff --git a/pyproject.toml b/pyproject.toml index 1c1162e..c130d3c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,15 +1,17 @@ [project] name = "router" -version ="0.1.0" +version ="0.2" authors = [ { name="RemiZOffAlex", email="remizoffalex@gmail.com" }, ] description = "Маршрутизатор" requires-python = ">=3.10" classifiers = [ + "Programming Language :: Python", + "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12" ] -keywords = ["documentation"] +keywords = ["broker", "router"] dependencies = [] diff --git a/router/broker.py b/router/broker.py deleted file mode 100644 index ad6917f..0000000 --- a/router/broker.py +++ /dev/null @@ -1,57 +0,0 @@ -__author__ = 'RemiZOffAlex' -__email__ = 'remizoffalex@mail.ru' - - -import logging - -from collections.abc import Callable - -from .route import Route - -log = logging.getLogger(__name__) - - -class Broker: - def __init__(self): - self.routes = [] - - def __call__(self, query): - """Вызов метода - """ - for route in self.routes: - log.debug(route) - output = route.comparator(query) - if output: - return (route.endpoint, output) - - def __getitem__(self, key): - method = self.methods[key] - return method.function - - def __iter__(self): - return iter(self.methods) - - def __len__(self): - return len(self.methods) - - def __setitem__(self, key, function, pre=None): - method = Method(function=function, pre=pre) - self.methods[key] = method - - def __delitem__(self, key): - del self.methods[key] - - def __repr__(self): - return repr(self.methods) - - def route(self, comparator: Callable, *args): - """Декоратор - """ - def wrap(function): - _route = Route( - comparator=comparator, - endpoint=function - ) - self.routes.append(_route) - return function - return wrap diff --git a/router/compiler.py b/router/compiler.py deleted file mode 100644 index b0b957f..0000000 --- a/router/compiler.py +++ /dev/null @@ -1,37 +0,0 @@ -__author__ = 'RemiZOffAlex' -__email__ = 'remizoffalex@mail.ru' - -import logging - -from .dsl.common import And, Equal, Or, Regex - -log = logging.getLogger(__name__) - - -class Compiler: - def __call__(self, rule): - if isinstance(rule, list): - val = [] - for item in rule: - val.append(self(item)) - return val - elif isinstance(rule, dict): - assert 'operator' in rule, \ - 'Not operator in rule: {rule}'.format(rule=rule) - if rule['operator'] == 'and': - val = self(rule['rules']) - result = And(val) - return result - elif rule['operator'] == 'or': - val = self(rule['rules']) - return Or(val) - elif rule['operator'] == 'equal': - return Equal( - key=rule['key'], - value=rule['value'] - ) - elif rule['operator'] == 'regex': - return Regex( - key=rule['key'], - value=rule['value'] - ) diff --git a/router/syntetic/url.py b/router/syntetic/url.py deleted file mode 100644 index 4e5b32a..0000000 --- a/router/syntetic/url.py +++ /dev/null @@ -1,95 +0,0 @@ -__author__ = 'RemiZOffAlex' -__email__ = 'remizoffalex@mail.ru' - -import re - - -_part_re = re.compile( - r""" - (?: - (?P/) # a slash - | - (?P[^[a-zA-Z_][a-zA-Z0-9_]*) # converter name - (?:\((?P.*?)\))? # converter arguments - : # variable delimiter - )? - (?P[a-zA-Z_][a-zA-Z0-9_]*) # variable name - > - ) - ) - """, - re.VERBOSE, -) - - -def rule_parse(rule: str) -> list: - """Парсим правило - """ - result = [] - pos = 0 - - while pos < len(rule): - match = _part_re.match(rule, pos) - print('match:', match) - pos = match.end() - data = match.groupdict() - print('data:', data) - result.append(data) - return result - -def rule_compile(rule: list): - """Компилируем регулярку из правила - """ - result = [] - params = {} - for item in rule: - if item['slash']: - result.append(item['slash']) - elif item['static']: - result.append(item['static']) - elif item['converter']: - params[item['variable']] = None - if item['converter']=='int': - result.append('(?P<{}>\d+)'.format(item['variable'])) - print('item:', item) - regexp = ''.join(result) - print('regexp:', regexp) - return (re.compile(regexp), params) - -def rule_extract(regexp, request): - pass - -def rule_comparator(rule, request): - try: - rule_proto = rule_parse(rule) - print('rule_proto:', rule_proto) - rule_regex, params = rule_compile(rule_proto) - print('rule:', rule_regex) - result = rule_regex.match(url) - for key in params: - params[key] = result.group(key) - print(key, ':', params[key]) - return params - except Exception as error: - return None - -def main(): - print('_part_re.groups:', _part_re.groups) - print('_part_re.groups:', _part_re.groups) - rule = '/v.' - urls = [ - '/v3451.5432', - '/v3sdfsdf451.5432' - ] - - for url in urls: - params = rule_comparator(rule, url) - if params: - print('route {} is valid:'.format(url), params) - else: - print('route {} invalid'.format(url)) diff --git a/router/__init__.py b/src/router/__init__.py similarity index 100% rename from router/__init__.py rename to src/router/__init__.py diff --git a/src/router/broker.py b/src/router/broker.py new file mode 100644 index 0000000..3e418a5 --- /dev/null +++ b/src/router/broker.py @@ -0,0 +1,67 @@ +__author__ = 'RemiZOffAlex' +__email__ = 'remizoffalex@mail.ru' + + +import logging + +from collections.abc import Callable + +from .route import Route + +log = logging.getLogger(__name__) + + +class Broker: + def __init__(self, debug: bool = False): + self.routes = {} + self.debug = debug + + def __call__(self, query): + """Вызов метода + """ + for key in self.routes: + _route = self.routes[key] + if self.debug: + log.debug('{key}: {route}'.format( + key=key, + route=_route + )) + print(key) + print(_route) + print(_route.comparator) + print(query) + output = _route.comparator(query) + if output: + return (_route.endpoint, output) + + def __getitem__(self, key): + method = self.routes[key] + return method.function + + def __iter__(self): + return iter(self.routes) + + def __len__(self): + return len(self.routes) + + def __setitem__(self, key, function, pre=None): + method = Method(function=function, pre=pre) + self.routes[key] = method + + def __delitem__(self, key): + del self.routes[key] + + def __repr__(self): + return repr(self.routes) + + def route(self, name: str, comparator: Callable, *args): + """Декоратор + """ + def wrap(function): + _route = Route( + comparator=comparator, + endpoint=function + ) + self.routes[name] = _route + return function + return wrap diff --git a/router/dsl/__init__.py b/src/router/dsl/__init__.py similarity index 100% rename from router/dsl/__init__.py rename to src/router/dsl/__init__.py diff --git a/src/router/dsl/common.py b/src/router/dsl/common.py new file mode 100644 index 0000000..0e39955 --- /dev/null +++ b/src/router/dsl/common.py @@ -0,0 +1,68 @@ +__author__ = 'RemiZOffAlex' +__email__ = 'remizoffalex@mail.ru' + +import re +import logging + +log = logging.getLogger(__name__) + + +class Node: + __params__ = {} + + def __init__(self, **kwargs): + log.debug('{}.__init__'.format(self.__class__.__name__)) + for item in self.__params__: + setattr(self, item, kwargs[item]) + + def __call__(self, state): + print(self.__class__.__name__) + raise ValueError('Метод не реализован') + + def __repr__(self): + log.debug('{}.__repr__'.format(self.__class__.__name__)) + result = self.__class__.__name__ + '(' + ')' + params = [] + for item in self.__params__: + param = getattr(self, item) + params.append('{} = {}'.format(item, getattr(self, item))) + result = self.__class__.__name__ + '(' + ', '.join(params) + ')' + + log.debug('__repr__') + return result + + +class Regex(Node): + __params__ = { + 'key': '', + 'value': '' + } + + def __init__(self, key, value): + self.key = key + self.value = re.compile(value) + + def __json__(self): + return { + 'operator': 'regex', + 'key': self.key, + 'value': self.value.pattern + } + + def __call__(self, query): + print('Regex.__call__') + if isinstance(query, dict): + result = self.value.match(query[self.key]) + else: + if '.' in self.key: + keys = self.key.split('.') + value = query + for key in keys: + value = getattr(value, key) + else: + value = getattr(query, self.key) + print(value) + print(type(value).__name__) + result = self.value.match(value) + print(result) + return result diff --git a/router/dsl/common.py b/src/router/dsl/logic.py similarity index 71% rename from router/dsl/common.py rename to src/router/dsl/logic.py index 29e2d8c..43cc242 100644 --- a/router/dsl/common.py +++ b/src/router/dsl/logic.py @@ -4,12 +4,15 @@ __email__ = 'remizoffalex@mail.ru' import re import logging +from .common import Node + log = logging.getLogger(__name__) -class And: - def __init__(self, rules): - self.rules = rules +class And(Node): + __params__ = { + 'rules': '' + } def __json__(self): result = [] @@ -26,6 +29,10 @@ class And: class Equal: + __params__ = { + 'key': '', + 'value': '' + } def __init__(self, key, value): self.key = key self.value = value @@ -43,6 +50,9 @@ class Equal: class Or: + __params__ = { + 'rules': '' + } def __init__(self, rules): self.rules = rules @@ -58,21 +68,3 @@ class Or: def __call__(self, query): print('Or.__call__') return any(map(lambda x: x(query), self.rules)) - - -class Regex: - def __init__(self, key, value): - self.key = key - self.value = re.compile(value) - - def __json__(self): - return { - 'operator': 'regex', - 'key': self.key, - 'value': self.value.pattern - } - - def __call__(self, query): - print('Regex.__call__') - result = self.value.match(query[self.key]) - return result diff --git a/router/endpoint.py b/src/router/endpoint.py similarity index 100% rename from router/endpoint.py rename to src/router/endpoint.py diff --git a/router/route.py b/src/router/route.py similarity index 66% rename from router/route.py rename to src/router/route.py index f8f63e5..3a3ec93 100644 --- a/router/route.py +++ b/src/router/route.py @@ -11,3 +11,9 @@ class Route: print('Route.__init__') self.comparator = comparator self.endpoint = endpoint + + def __repr__(self): + return 'Route(comparator={comparator}, endpoint={endpoint})'.format( + comparator=self.comparator, + endpoint=self.endpoint + )