dbt-selly/dbt-env/lib/python3.8/site-packages/dbt/graph/selector_spec.py

218 lines
6.5 KiB
Python
Raw Normal View History

2022-03-22 15:13:27 +00:00
import os
import re
from abc import ABCMeta, abstractmethod
from dataclasses import dataclass
from typing import (
Set, Iterator, List, Optional, Dict, Union, Any, Iterable, Tuple
)
from .graph import UniqueId
from .selector_methods import MethodName
from dbt.exceptions import RuntimeException, InvalidSelectorException
RAW_SELECTOR_PATTERN = re.compile(
r'\A'
r'(?P<childrens_parents>(\@))?'
r'(?P<parents>((?P<parents_depth>(\d*))\+))?'
r'((?P<method>([\w.]+)):)?(?P<value>(.*?))'
r'(?P<children>(\+(?P<children_depth>(\d*))))?'
r'\Z'
)
SELECTOR_METHOD_SEPARATOR = '.'
def _probably_path(value: str):
"""Decide if value is probably a path. Windows has two path separators, so
we should check both sep ('\\') and altsep ('/') there.
"""
if os.path.sep in value:
return True
elif os.path.altsep is not None and os.path.altsep in value:
return True
else:
return False
def _match_to_int(match: Dict[str, str], key: str) -> Optional[int]:
raw = match.get(key)
# turn the empty string into None, too.
if not raw:
return None
try:
return int(raw)
except ValueError as exc:
raise RuntimeException(
f'Invalid node spec - could not handle parent depth {raw}'
) from exc
SelectionSpec = Union[
'SelectionCriteria',
'SelectionIntersection',
'SelectionDifference',
'SelectionUnion',
]
@dataclass
class SelectionCriteria:
raw: Any
method: MethodName
method_arguments: List[str]
value: Any
childrens_parents: bool
parents: bool
parents_depth: Optional[int]
children: bool
children_depth: Optional[int]
greedy: bool = False
greedy_warning: bool = False # do not raise warning for yaml selectors
def __post_init__(self):
if self.children and self.childrens_parents:
raise RuntimeException(
f'Invalid node spec {self.raw} - "@" prefix and "+" suffix '
'are incompatible'
)
@classmethod
def default_method(cls, value: str) -> MethodName:
if _probably_path(value):
return MethodName.Path
else:
return MethodName.FQN
@classmethod
def parse_method(
cls, groupdict: Dict[str, Any]
) -> Tuple[MethodName, List[str]]:
raw_method = groupdict.get('method')
if raw_method is None:
return cls.default_method(groupdict['value']), []
method_parts: List[str] = raw_method.split(SELECTOR_METHOD_SEPARATOR)
try:
method_name = MethodName(method_parts[0])
except ValueError as exc:
raise InvalidSelectorException(
f"'{method_parts[0]}' is not a valid method name"
) from exc
method_arguments: List[str] = method_parts[1:]
return method_name, method_arguments
@classmethod
def selection_criteria_from_dict(
cls, raw: Any, dct: Dict[str, Any], greedy: bool = False
) -> 'SelectionCriteria':
if 'value' not in dct:
raise RuntimeException(
f'Invalid node spec "{raw}" - no search value!'
)
method_name, method_arguments = cls.parse_method(dct)
parents_depth = _match_to_int(dct, 'parents_depth')
children_depth = _match_to_int(dct, 'children_depth')
return cls(
raw=raw,
method=method_name,
method_arguments=method_arguments,
value=dct['value'],
childrens_parents=bool(dct.get('childrens_parents')),
parents=bool(dct.get('parents')),
parents_depth=parents_depth,
children=bool(dct.get('children')),
children_depth=children_depth,
greedy=(greedy or bool(dct.get('greedy'))),
)
@classmethod
def dict_from_single_spec(cls, raw: str):
result = RAW_SELECTOR_PATTERN.match(raw)
if result is None:
return {'error': 'Invalid selector spec'}
dct: Dict[str, Any] = result.groupdict()
method_name, method_arguments = cls.parse_method(dct)
meth_name = str(method_name)
if method_arguments:
meth_name = meth_name + '.' + '.'.join(method_arguments)
dct['method'] = meth_name
dct = {k: v for k, v in dct.items() if (v is not None and v != '')}
if 'childrens_parents' in dct:
dct['childrens_parents'] = bool(dct.get('childrens_parents'))
if 'parents' in dct:
dct['parents'] = bool(dct.get('parents'))
if 'children' in dct:
dct['children'] = bool(dct.get('children'))
if 'greedy' in dct:
dct['greedy'] = bool(dct.get('greedy'))
return dct
@classmethod
def from_single_spec(cls, raw: str, greedy: bool = False) -> 'SelectionCriteria':
result = RAW_SELECTOR_PATTERN.match(raw)
if result is None:
# bad spec!
raise RuntimeException(f'Invalid selector spec "{raw}"')
return cls.selection_criteria_from_dict(raw, result.groupdict(), greedy=greedy)
class BaseSelectionGroup(Iterable[SelectionSpec], metaclass=ABCMeta):
def __init__(
self,
components: Iterable[SelectionSpec],
expect_exists: bool = False,
greedy_warning: bool = True,
raw: Any = None,
):
self.components: List[SelectionSpec] = list(components)
self.expect_exists = expect_exists
self.greedy_warning = greedy_warning
self.raw = raw
def __iter__(self) -> Iterator[SelectionSpec]:
for component in self.components:
yield component
@abstractmethod
def combine_selections(
self,
selections: List[Set[UniqueId]],
) -> Set[UniqueId]:
raise NotImplementedError(
'_combine_selections not implemented!'
)
def combined(self, selections: List[Set[UniqueId]]) -> Set[UniqueId]:
if not selections:
return set()
return self.combine_selections(selections)
class SelectionIntersection(BaseSelectionGroup):
def combine_selections(
self,
selections: List[Set[UniqueId]],
) -> Set[UniqueId]:
return set.intersection(*selections)
class SelectionDifference(BaseSelectionGroup):
def combine_selections(
self,
selections: List[Set[UniqueId]],
) -> Set[UniqueId]:
return set.difference(*selections)
class SelectionUnion(BaseSelectionGroup):
def combine_selections(
self,
selections: List[Set[UniqueId]],
) -> Set[UniqueId]:
return set.union(*selections)