Rename some methods to python standards, added some missing type hints

This commit is contained in:
Yonatan Most 2020-04-21 13:22:03 +03:00
Родитель 418bf364a6
Коммит aa5c8152ae
14 изменённых файлов: 253 добавлений и 236 удалений

Просмотреть файл

@ -2,9 +2,11 @@
<dictionary name="pykusto">
<words>
<w>acos</w>
<w>asctime</w>
<w>asin</w>
<w>atan</w>
<w>avgif</w>
<w>bagexpansion</w>
<w>buildschema</w>
<w>countif</w>
<w>countof</w>
@ -19,34 +21,54 @@
<w>endofweek</w>
<w>endofyear</w>
<w>extractjson</w>
<w>fullouter</w>
<w>getmonth</w>
<w>getschema</w>
<w>getyear</w>
<w>hourofday</w>
<w>ifexists</w>
<w>indexof</w>
<w>innerunique</w>
<w>isempty</w>
<w>isfinite</w>
<w>isinf</w>
<w>isnotempty</w>
<w>isnotnull</w>
<w>isutf</w>
<w>itemindex</w>
<w>kusto</w>
<w>leftanti</w>
<w>leftantisemi</w>
<w>leftouter</w>
<w>leftsemi</w>
<w>levelname</w>
<w>loggamma</w>
<w>maxif</w>
<w>minif</w>
<w>monthofyear</w>
<w>ncode</w>
<w>nexec</w>
<w>percentrank</w>
<w>pykusto</w>
<w>rightanti</w>
<w>rightantisemi</w>
<w>rightouter</w>
<w>rightsemi</w>
<w>sqrt</w>
<w>startofday</w>
<w>startofmonth</w>
<w>startofweek</w>
<w>startofyear</w>
<w>stdev</w>
<w>stdevif</w>
<w>stdevp</w>
<w>strcat</w>
<w>strcmp</w>
<w>strlen</w>
<w>strrep</w>
<w>subexpr</w>
<w>substr</w>
<w>sumif</w>
<w>tdigest</w>
<w>timespan</w>
<w>toarray</w>
@ -64,7 +86,10 @@
<w>toreal</w>
<w>totimespan</w>
<w>toupper</w>
<w>treepath</w>
<w>urlquery</w>
<w>varianceif</w>
<w>variancep</w>
<w>weekofyear</w>
<w>welch</w>
</words>

Просмотреть файл

@ -13,8 +13,7 @@ from azure.kusto.data.response import KustoResponseDataSet
from pykusto.expressions import BaseColumn, AnyTypeColumn
from pykusto.item_fetcher import ItemFetcher
from pykusto.kql_converters import KQL
from pykusto.type_utils import INTERNAL_NAME_TO_TYPE, typed_column, DOT_NAME_TO_TYPE
from pykusto.type_utils import INTERNAL_NAME_TO_TYPE, typed_column, DOT_NAME_TO_TYPE, KQL
class KustoResponse:

Просмотреть файл

@ -2,9 +2,8 @@ from datetime import datetime, timedelta
from typing import Any, List, Tuple, Mapping, Optional, Type
from typing import Union
from pykusto.kql_converters import KQL
from pykusto.type_utils import plain_expression, aggregation_expression, PythonTypes, kql_converter, KustoType, \
typed_column
typed_column, KQL
ExpressionType = Union[PythonTypes, 'BaseExpression']
StringType = Union[str, 'StringExpression']
@ -55,7 +54,7 @@ class BaseExpression:
def as_subexpression(self) -> KQL:
return KQL('({})'.format(self.kql))
def gettype(self) -> 'StringExpression':
def get_type(self) -> 'StringExpression':
return StringExpression(KQL('gettype({})'.format(self.kql)))
def __hash__(self) -> 'StringExpression':
@ -234,10 +233,10 @@ class NumberExpression(BaseExpression):
def isfinite(self) -> BooleanExpression:
return BooleanExpression(KQL('isfinite({})'.format(self.kql)))
def isinf(self) -> BooleanExpression:
def is_inf(self) -> BooleanExpression:
return BooleanExpression(KQL('isinf({})'.format(self.kql)))
def isnan(self) -> BooleanExpression:
def is_nan(self) -> BooleanExpression:
return BooleanExpression(KQL('isnan({})'.format(self.kql)))
def log(self) -> 'NumberExpression':
@ -249,7 +248,7 @@ class NumberExpression(BaseExpression):
def log2(self) -> 'NumberExpression':
return NumberExpression(KQL('log2({})'.format(self.kql)))
def loggamma(self) -> 'NumberExpression':
def log_gamma(self) -> 'NumberExpression':
return NumberExpression(KQL('loggamma({})'.format(self.kql)))
def round(self, precision: NumberType = None) -> 'NumberExpression':
@ -351,28 +350,28 @@ class DatetimeExpression(BaseExpression):
def bin_auto(self) -> 'DatetimeExpression':
return DatetimeExpression(KQL('bin_auto({})'.format(self.kql)))
def endofday(self, offset: NumberType = None) -> 'DatetimeExpression':
def end_of_day(self, offset: NumberType = None) -> 'DatetimeExpression':
if offset is None:
res = 'endofday({})'.format(self.kql)
else:
res = 'endofday({}, {})'.format(self.kql, _subexpr_to_kql(offset))
return DatetimeExpression(KQL(res))
def endofmonth(self, offset: NumberType = None) -> 'DatetimeExpression':
def end_of_month(self, offset: NumberType = None) -> 'DatetimeExpression':
if offset is None:
res = 'endofmonth({})'.format(self.kql)
else:
res = 'endofmonth({}, {})'.format(self.kql, _subexpr_to_kql(offset))
return DatetimeExpression(KQL(res))
def endofweek(self, offset: NumberType = None) -> 'DatetimeExpression':
def end_of_week(self, offset: NumberType = None) -> 'DatetimeExpression':
if offset is None:
res = 'endofweek({})'.format(self.kql)
else:
res = 'endofweek({}, {})'.format(self.kql, _subexpr_to_kql(offset))
return DatetimeExpression(KQL(res))
def endofyear(self, offset: NumberType = None) -> 'DatetimeExpression':
def end_of_year(self, offset: NumberType = None) -> 'DatetimeExpression':
if offset is None:
res = 'endofyear({})'.format(self.kql)
else:
@ -382,31 +381,31 @@ class DatetimeExpression(BaseExpression):
def format_datetime(self, format_string: StringType) -> StringExpression:
return StringExpression(KQL('format_datetime({}, {})'.format(self.kql, _subexpr_to_kql(format_string))))
def getmonth(self) -> NumberExpression:
def get_month(self) -> NumberExpression:
return NumberExpression(KQL('getmonth({})'.format(self.kql)))
def getyear(self) -> NumberExpression:
def get_year(self) -> NumberExpression:
return NumberExpression(KQL('getyear({})'.format(self.kql)))
def hourofday(self) -> NumberExpression:
def hour_of_day(self) -> NumberExpression:
return NumberExpression(KQL('hourofday({})'.format(self.kql)))
def startofday(self, offset: NumberType = None) -> 'DatetimeExpression':
def start_of_day(self, offset: NumberType = None) -> 'DatetimeExpression':
return DatetimeExpression(KQL(
('startofday({})' if offset is None else 'startofday({}, {})').format(self.kql, to_kql(offset))
))
def startofmonth(self, offset: NumberType = None) -> 'DatetimeExpression':
def start_of_month(self, offset: NumberType = None) -> 'DatetimeExpression':
return DatetimeExpression(KQL(
('startofmonth({})' if offset is None else 'startofmonth({}, {})').format(self.kql, to_kql(offset))
))
def startofweek(self, offset: NumberType = None) -> 'DatetimeExpression':
def start_of_week(self, offset: NumberType = None) -> 'DatetimeExpression':
return DatetimeExpression(KQL(
('startofweek({})' if offset is None else 'startofweek({}, {})').format(self.kql, to_kql(offset))
))
def startofyear(self, offset: NumberType = None) -> 'DatetimeExpression':
def start_of_year(self, offset: NumberType = None) -> 'DatetimeExpression':
return DatetimeExpression(KQL(
('startofyear({})' if offset is None else 'startofyear({}, {})').format(self.kql, to_kql(offset))
))

Просмотреть файл

@ -6,9 +6,8 @@ from pykusto.expressions import AnyTypeColumn, NumberType, NumberExpression, Tim
ExpressionType, AggregationExpression, StringType, StringExpression, BooleanExpression, \
NumberAggregationExpression, MappingAggregationExpression, ArrayAggregationExpression, to_kql, DynamicExpression, \
ArrayExpression, ColumnToType, BaseColumn, AnyExpression, AnyAggregationExpression, MappingExpression
from pykusto.kql_converters import KQL
from pykusto.logger import logger
from pykusto.type_utils import plain_expression, get_base_types, KustoType
from pykusto.type_utils import plain_expression, get_base_types, KustoType, KQL
class Functions:
@ -180,20 +179,20 @@ class Functions:
# def degrees(self): return
@staticmethod
def endofday(expr: DatetimeExpression, offset: NumberType = None) -> DatetimeExpression:
return expr.endofday(offset)
def end_of_day(expr: DatetimeExpression, offset: NumberType = None) -> DatetimeExpression:
return expr.end_of_day(offset)
@staticmethod
def endofmonth(expr: DatetimeType, offset: NumberType = None) -> DatetimeExpression:
return expr.endofmonth(offset)
def end_of_month(expr: DatetimeType, offset: NumberType = None) -> DatetimeExpression:
return expr.end_of_month(offset)
@staticmethod
def endofweek(expr: DatetimeType, offset: NumberType = None) -> DatetimeExpression:
return expr.endofweek(offset)
def end_of_week(expr: DatetimeType, offset: NumberType = None) -> DatetimeExpression:
return expr.end_of_week(offset)
@staticmethod
def endofyear(expr: DatetimeType, offset: NumberType = None) -> DatetimeExpression:
return expr.endofyear(offset)
def end_of_year(expr: DatetimeType, offset: NumberType = None) -> DatetimeExpression:
return expr.end_of_year(offset)
# def estimate_data_size(self): return
@ -239,16 +238,16 @@ class Functions:
# def gamma(self): return
@staticmethod
def getmonth(expr: DatetimeType) -> NumberExpression:
return expr.getmonth()
def get_month(expr: DatetimeType) -> NumberExpression:
return expr.get_month()
@staticmethod
def gettype(expr: ExpressionType) -> StringExpression:
return expr.gettype()
def get_type(expr: ExpressionType) -> StringExpression:
return expr.get_type()
@staticmethod
def getyear(expr: DatetimeType) -> NumberExpression:
return expr.getyear()
def get_year(expr: DatetimeType) -> NumberExpression:
return expr.get_year()
@staticmethod
def hash(expr: ExpressionType) -> StringExpression:
@ -259,8 +258,8 @@ class Functions:
return expr.hash_sha256()
@staticmethod
def hourofday(expr: DatetimeType) -> NumberExpression:
return expr.hourofday()
def hour_of_day(expr: DatetimeType) -> NumberExpression:
return expr.hour_of_day()
@staticmethod
def iff(predicate: BooleanType, if_true: ExpressionType, if_false: ExpressionType) -> BaseExpression:
@ -300,35 +299,35 @@ class Functions:
# def isascii(self): return
@staticmethod
def isempty(expr: ExpressionType) -> BooleanExpression:
def is_empty(expr: ExpressionType) -> BooleanExpression:
return expr.is_empty()
@staticmethod
def isfinite(expr: NumberType) -> BooleanExpression:
def is_finite(expr: NumberType) -> BooleanExpression:
return expr.isfinite()
@staticmethod
def isinf(expr: NumberType) -> BooleanExpression:
return expr.isinf()
def is_inf(expr: NumberType) -> BooleanExpression:
return expr.is_inf()
@staticmethod
def isnan(expr: NumberExpression) -> BooleanExpression:
return expr.isnan()
def is_nan(expr: NumberExpression) -> BooleanExpression:
return expr.is_nan()
@staticmethod
def isnotempty(expr: ExpressionType) -> BooleanExpression:
def is_not_empty(expr: ExpressionType) -> BooleanExpression:
return expr.is_not_empty()
@staticmethod
def isnotnull(expr: ExpressionType) -> BooleanExpression:
def is_not_null(expr: ExpressionType) -> BooleanExpression:
return expr.is_not_null()
@staticmethod
def isnull(expr: ExpressionType) -> BooleanExpression:
def is_null(expr: ExpressionType) -> BooleanExpression:
return expr.is_null()
@staticmethod
def isutf8(expr: StringType) -> BooleanExpression:
def is_utf8(expr: StringType) -> BooleanExpression:
return expr.is_utf8()
@staticmethod
@ -344,8 +343,8 @@ class Functions:
return expr.log2()
@staticmethod
def loggamma(expr: NumberType) -> NumberExpression:
return expr.loggamma()
def log_gamma(expr: NumberType) -> NumberExpression:
return expr.log_gamma()
@staticmethod
def make_datetime(year: NumberType,
@ -376,7 +375,7 @@ class Functions:
# def min_of(self): return
@staticmethod
def monthofyear() -> NumberExpression: raise NotImplemented # TODO
def month_of_year() -> NumberExpression: raise NotImplemented # TODO
@staticmethod
def new_guid() -> AnyExpression: raise NotImplemented # TODO
@ -576,20 +575,20 @@ class Functions:
return NumberExpression(KQL('sqrt({})'.format(to_kql(expr))))
@staticmethod
def startofday(expr: DatetimeType, offset: NumberType = None) -> DatetimeExpression:
return expr.startofday(offset)
def start_of_day(expr: DatetimeType, offset: NumberType = None) -> DatetimeExpression:
return expr.start_of_day(offset)
@staticmethod
def startofmonth(expr: DatetimeType, offset: NumberType = None) -> DatetimeExpression:
return expr.startofmonth(offset)
def start_of_month(expr: DatetimeType, offset: NumberType = None) -> DatetimeExpression:
return expr.start_of_month(offset)
@staticmethod
def startofweek(expr: DatetimeType, offset: NumberType = None) -> DatetimeExpression:
return expr.startofweek(offset)
def start_of_week(expr: DatetimeType, offset: NumberType = None) -> DatetimeExpression:
return expr.start_of_week(offset)
@staticmethod
def startofyear(expr: DatetimeType, offset: NumberType = None) -> DatetimeExpression:
return expr.startofyear(offset)
def start_of_year(expr: DatetimeType, offset: NumberType = None) -> DatetimeExpression:
return expr.start_of_year(offset)
@staticmethod
def strcat(*strings: StringType) -> StringExpression:
@ -654,60 +653,60 @@ class Functions:
# def tdigest_merge(self): return
@staticmethod
def tobool(expr: ExpressionType) -> BooleanExpression:
def to_bool(expr: ExpressionType) -> BooleanExpression:
return BooleanExpression(KQL('tobool({})'.format(to_kql(expr))))
@staticmethod
def toboolean(expr: ExpressionType) -> BooleanExpression:
def to_boolean(expr: ExpressionType) -> BooleanExpression:
return BooleanExpression(KQL('toboolean({})'.format(to_kql(expr))))
@staticmethod
def todatetime(expr: StringType) -> DatetimeExpression:
def to_datetime(expr: StringType) -> DatetimeExpression:
return DatetimeExpression(KQL('todatetime({})'.format(to_kql(expr))))
@staticmethod
def todecimal(expr: NumberType) -> NumberExpression:
def to_decimal(expr: NumberType) -> NumberExpression:
return NumberExpression(KQL("todecimal({})".format(to_kql(expr))))
@staticmethod
def todouble(expr: NumberType) -> NumberExpression:
def to_double(expr: NumberType) -> NumberExpression:
return NumberExpression(KQL("todouble({})".format(to_kql(expr))))
@staticmethod
def todynamic() -> DynamicExpression: raise NotImplemented # TODO
def to_dynamic() -> DynamicExpression: raise NotImplemented # TODO
@staticmethod
def toguid() -> AnyExpression: raise NotImplemented # TODO
def to_guid() -> AnyExpression: raise NotImplemented # TODO
@staticmethod
def tohex(expr1: NumberType, expr2: NumberType = None) -> StringExpression:
def to_hex(expr1: NumberType, expr2: NumberType = None) -> StringExpression:
return StringExpression(KQL(('tohex({})' if expr2 is None else 'tohex({}, {})').format(to_kql(expr1), to_kql(expr2))))
@staticmethod
def toint(expr: NumberType) -> NumberExpression:
def to_int(expr: NumberType) -> NumberExpression:
return NumberExpression(KQL("toint({})".format(to_kql(expr))))
@staticmethod
def tolong(expr: NumberType) -> NumberExpression:
def to_long(expr: NumberType) -> NumberExpression:
return NumberExpression(KQL("tolong({})".format(to_kql(expr))))
@staticmethod
def tolower(expr: StringType) -> StringExpression:
def to_lower(expr: StringType) -> StringExpression:
return expr.lower()
@staticmethod
def toreal(expr: NumberType) -> NumberExpression:
def to_real(expr: NumberType) -> NumberExpression:
return NumberExpression(KQL("toreal({})".format(to_kql(expr))))
@staticmethod
def tostring(expr: ExpressionType):
def to_string(expr: ExpressionType):
return expr.to_string()
@staticmethod
def totimespan() -> TimespanExpression: raise NotImplemented # TODO
def to_timespan() -> TimespanExpression: raise NotImplemented # TODO
@staticmethod
def toupper(expr: StringType) -> StringExpression:
def to_upper(expr: StringType) -> StringExpression:
return expr.upper()
# def to_utf8(self): return
@ -733,7 +732,7 @@ class Functions:
def url_encode() -> StringExpression: raise NotImplemented # TODO
@staticmethod
def weekofyear() -> NumberExpression: raise NotImplemented # TODO
def week_of_year() -> NumberExpression: raise NotImplemented # TODO
# def welch_test(self): return
@ -764,7 +763,7 @@ class Functions:
return NumberAggregationExpression(KQL('avg({})'.format(to_kql(expr))))
@staticmethod
def avgif(expr: ExpressionType, predicate: BooleanType) -> NumberAggregationExpression:
def avg_if(expr: ExpressionType, predicate: BooleanType) -> NumberAggregationExpression:
return NumberAggregationExpression(KQL('avgif({}, {})'.format(to_kql(expr), to_kql(predicate))))
# def buildschema(self):
@ -776,7 +775,7 @@ class Functions:
return NumberAggregationExpression(KQL(res))
@staticmethod
def countif(predicate: BooleanType) -> NumberAggregationExpression:
def count_if(predicate: BooleanType) -> NumberAggregationExpression:
return NumberAggregationExpression(KQL('countif({})'.format(to_kql(predicate))))
@staticmethod
@ -786,7 +785,7 @@ class Functions:
))
@staticmethod
def dcountif(expr: ExpressionType, predicate: BooleanType, accuracy: NumberType = 0) -> NumberAggregationExpression:
def dcount_if(expr: ExpressionType, predicate: BooleanType, accuracy: NumberType = 0) -> NumberAggregationExpression:
return NumberAggregationExpression(KQL('dcountif({}, {}, {})'.format(
to_kql(expr), to_kql(predicate), to_kql(accuracy)
)))
@ -860,7 +859,7 @@ class Functions:
return AnyAggregationExpression(KQL('sum({})'.format(to_kql(expr))))
@staticmethod
def sumif(expr: ExpressionType, predicate: BooleanType) -> AggregationExpression:
def sum_if(expr: ExpressionType, predicate: BooleanType) -> AggregationExpression:
return AnyAggregationExpression(KQL('sumif({}, {})'.format(to_kql(expr), to_kql(predicate))))
# def tdigest(self):
@ -875,7 +874,7 @@ class Functions:
return AnyAggregationExpression(KQL('variance({})'.format(to_kql(expr))))
@staticmethod
def varianceif(expr: ExpressionType, predicate: BooleanType) -> AggregationExpression:
def variance_if(expr: ExpressionType, predicate: BooleanType) -> AggregationExpression:
return AnyAggregationExpression(KQL('varianceif({}, {})'.format(to_kql(expr), to_kql(predicate))))
@staticmethod

Просмотреть файл

@ -84,7 +84,7 @@ class ItemFetcher(metaclass=ABCMeta):
self.__items[name] = item
return item
def _get_item(self, name: str, fallback: Callable) -> Any:
def _get_item(self, name: str, fallback: Callable[[], Any]) -> Any:
if self.__items is not None:
resolved_item = self.__items.get(name)
if resolved_item is not None:

Просмотреть файл

@ -1,90 +0,0 @@
import json
from datetime import datetime, timedelta
from itertools import chain
from numbers import Number
from typing import NewType, Mapping, Union, List, Tuple
from pykusto.type_utils import kql_converter, KustoType
KQL = NewType('KQL', str)
@kql_converter(KustoType.DATETIME)
def datetime_to_kql(dt: datetime) -> KQL:
return KQL(dt.strftime('datetime(%Y-%m-%d %H:%M:%S.%f)'))
@kql_converter(KustoType.TIMESPAN)
def timedelta_to_kql(td: timedelta) -> KQL:
hours, remainder = divmod(td.seconds, 3600)
minutes, seconds = divmod(remainder, 60)
return KQL('time({days}.{hours}:{minutes}:{seconds}.{microseconds})'.format(
days=td.days,
hours=hours,
minutes=minutes,
seconds=seconds,
microseconds=td.microseconds,
))
@kql_converter(KustoType.ARRAY, KustoType.MAPPING)
def dynamic_to_kql(d: Union[Mapping, List, Tuple]) -> KQL:
try:
query = list(json.dumps(d))
except TypeError:
# Using exceptions as part of normal flow is not best practice, however in this case we have a good reason.
# The given object might contain a non-primitive object somewhere inside it, and the only way to find it is to go through the entire hierarchy, which is exactly
# what's being done in the process of json conversion.
# Also keep in mind that exception handling in Python has no performance overhead (unlike e.g. Java).
return build_dynamic(d)
# Convert square brackets to round brackets (Issue #11)
counter = 0
prev = ""
for i, c in enumerate(query):
if counter == 0:
if c == "[":
query[i] = "("
elif c == "]":
query[i] = ")"
elif c in ['"', '\''] and prev != "\\":
counter += 1
elif counter > 0:
if c in ['"', '\''] and prev != "\\":
counter -= 1
prev = query[i]
assert counter == 0
return KQL("".join(query))
def build_dynamic(d: Union[Mapping, List, Tuple]) -> KQL:
from pykusto.expressions import BaseExpression
if isinstance(d, BaseExpression):
return d.kql
if isinstance(d, Mapping):
return KQL('pack({})'.format(', '.join(map(build_dynamic, chain(*d.items())))))
if isinstance(d, (List, Tuple)):
return KQL('pack_array({})'.format(', '.join(map(build_dynamic, d))))
from pykusto.expressions import to_kql
return to_kql(d)
@kql_converter(KustoType.BOOL)
def bool_to_kql(b: bool) -> KQL:
return KQL('true') if b else KQL('false')
@kql_converter(KustoType.STRING)
def str_to_kql(s: str) -> KQL:
return KQL('"{}"'.format(s))
@kql_converter(KustoType.INT, KustoType.LONG, KustoType.REAL)
def number_to_kql(n: Number) -> KQL:
return KQL(str(n))
# noinspection PyUnusedLocal
@kql_converter(KustoType.NULL)
def none_to_kql(n: type(None)) -> KQL:
return KQL("")

Просмотреть файл

@ -10,9 +10,8 @@ from pykusto.expressions import BooleanType, ExpressionType, AggregationExpressi
StringType, AssignmentBase, AssignmentFromAggregationToColumn, AssignmentToSingleColumn, AnyTypeColumn, \
BaseExpression, \
AssignmentFromColumnToColumn, AnyExpression, to_kql, ColumnToType
from pykusto.kql_converters import KQL
from pykusto.logger import logger
from pykusto.type_utils import KustoType
from pykusto.type_utils import KustoType, KQL
from pykusto.udf import stringify_python_func

Просмотреть файл

@ -1,9 +1,13 @@
import json
from datetime import datetime, timedelta
from enum import Enum
from typing import Union, Mapping, Type, Dict, Callable, Tuple, List, Any, Set
from itertools import chain
from numbers import Number
from typing import Union, Mapping, Type, Dict, Callable, Tuple, List, Any, Set, NewType
# TODO: Unhandled data types: guid, decimal
PythonTypes = Union[str, int, float, bool, datetime, Mapping, List, Tuple, timedelta]
KQL = NewType('KQL', str)
class KustoType(Enum):
@ -76,7 +80,7 @@ class TypeRegistrar:
can then be used to retrieve the python type or function corresponding to a given Kusto type.
"""
name: str
registry: Dict[KustoType, Callable]
registry: Dict[KustoType, Callable[[Any], KQL]]
def __init__(self, name: str) -> None:
"""
@ -88,8 +92,8 @@ class TypeRegistrar:
def __repr__(self) -> str:
return self.name
def __call__(self, *types: KustoType) -> Callable:
def inner(wrapped) -> Callable:
def __call__(self, *types: KustoType) -> Callable[[Callable[[Any], KQL]], Callable[[Any], KQL]]:
def inner(wrapped: Callable[[Any], KQL]) -> Callable[[Any], KQL]:
for t in types:
previous = self.registry.setdefault(t, wrapped)
if previous is not wrapped:
@ -99,7 +103,7 @@ class TypeRegistrar:
return inner
def for_obj(self, obj: PythonTypes) -> Callable:
def for_obj(self, obj: PythonTypes) -> KQL:
"""
Given an object of Kusto type, retrieve the python type or function associated with the object's type, and call
it with the given object as a parameter
@ -112,7 +116,7 @@ class TypeRegistrar:
return registered_callable(obj)
raise ValueError("{}: no registered callable for object {} of type {}".format(self, obj, type(obj).__name__))
def for_type(self, t: Type[PythonTypes]) -> Callable:
def for_type(self, t: Type[PythonTypes]) -> Callable[[Any], KQL]:
"""
Given a Kusto type, retrieve the associated python type or function
@ -129,3 +133,84 @@ kql_converter = TypeRegistrar("KQL Converter")
typed_column = TypeRegistrar("Column")
plain_expression = TypeRegistrar("Plain expression")
aggregation_expression = TypeRegistrar("Aggregation expression")
@kql_converter(KustoType.DATETIME)
def datetime_to_kql(dt: datetime) -> KQL:
return KQL(dt.strftime('datetime(%Y-%m-%d %H:%M:%S.%f)'))
@kql_converter(KustoType.TIMESPAN)
def timedelta_to_kql(td: timedelta) -> KQL:
hours, remainder = divmod(td.seconds, 3600)
minutes, seconds = divmod(remainder, 60)
return KQL('time({days}.{hours}:{minutes}:{seconds}.{microseconds})'.format(
days=td.days,
hours=hours,
minutes=minutes,
seconds=seconds,
microseconds=td.microseconds,
))
@kql_converter(KustoType.ARRAY, KustoType.MAPPING)
def dynamic_to_kql(d: Union[Mapping, List, Tuple]) -> KQL:
try:
query = list(json.dumps(d))
except TypeError:
# Using exceptions as part of normal flow is not best practice, however in this case we have a good reason.
# The given object might contain a non-primitive object somewhere inside it, and the only way to find it is to go through the entire hierarchy, which is exactly
# what's being done in the process of json conversion.
# Also keep in mind that exception handling in Python has no performance overhead (unlike e.g. Java).
return build_dynamic(d)
# Convert square brackets to round brackets (Issue #11)
counter = 0
prev = ""
for i, c in enumerate(query):
if counter == 0:
if c == "[":
query[i] = "("
elif c == "]":
query[i] = ")"
elif c in ['"', '\''] and prev != "\\":
counter += 1
elif counter > 0:
if c in ['"', '\''] and prev != "\\":
counter -= 1
prev = query[i]
assert counter == 0
return KQL("".join(query))
def build_dynamic(d: Union[Mapping, List, Tuple]) -> KQL:
from pykusto.expressions import BaseExpression
if isinstance(d, BaseExpression):
return d.kql
if isinstance(d, Mapping):
return KQL('pack({})'.format(', '.join(map(build_dynamic, chain(*d.items())))))
if isinstance(d, (List, Tuple)):
return KQL('pack_array({})'.format(', '.join(map(build_dynamic, d))))
from pykusto.expressions import to_kql
return to_kql(d)
@kql_converter(KustoType.BOOL)
def bool_to_kql(b: bool) -> KQL:
return KQL('true') if b else KQL('false')
@kql_converter(KustoType.STRING)
def str_to_kql(s: str) -> KQL:
return KQL('"{}"'.format(s))
@kql_converter(KustoType.INT, KustoType.LONG, KustoType.REAL)
def number_to_kql(n: Number) -> KQL:
return KQL(str(n))
# noinspection PyUnusedLocal
@kql_converter(KustoType.NULL)
def none_to_kql(n: None) -> KQL:
return KQL("")

Просмотреть файл

@ -126,7 +126,7 @@ class MockKustoClient(KustoClient):
databases_response: KustoResponseDataSet
getschema_response: KustoResponseDataSet
main_response: KustoResponseDataSet
upon_execute: Callable
upon_execute: Callable[[RecordedQuery], None]
record_metadata: bool
def __init__(
@ -137,7 +137,7 @@ class MockKustoClient(KustoClient):
databases_response: KustoResponseDataSet = mock_databases_response([]),
getschema_response: KustoResponseDataSet = mock_getschema_response([]),
main_response: KustoResponseDataSet = mock_response(tuple()),
upon_execute: Callable = None,
upon_execute: Callable[[RecordedQuery], None] = None,
record_metadata: bool = False
):
self.recorded_queries = []

Просмотреть файл

@ -33,7 +33,8 @@ class TestClientFetch(TestBase):
mock_response_future = Future()
mock_response_future.executed = False
def upon_execute(query):
# noinspection PyUnusedLocal
def upon_execute(query): # Parameter required since function is passed as Callable[[RecordedQuery], None]
mock_response_future.result()
mock_response_future.executed = True
@ -86,7 +87,7 @@ class TestClientFetch(TestBase):
query_thread.join()
# Make sure the fetch query was indeed called
assert mock_response_future.executed
# Before the fix the order of returned query was reveresed
# Before the fix the order of returned query was reversed
self.assertEqual(
[
RecordedQuery('test_db', '.show table test_table | project AttributeName, AttributeType | limit 10000'),

Просмотреть файл

@ -193,7 +193,7 @@ class TestExpressions(TestBase):
Query().extend(foo=t.timespanField + timedelta(hours=1)).render(),
)
def test_substract_timespan_from_timespan(self):
def test_subtract_timespan_from_timespan(self):
self.assertEqual(
' | extend foo = timespanField - time(0.1:0:0.0)',
Query().extend(foo=t.timespanField - timedelta(hours=1)).render(),

Просмотреть файл

@ -59,41 +59,41 @@ class TestFunction(TestBase):
def test_endofday(self):
self.assertEqual(
" | where (endofday(dateField)) > datetime(2019-07-23 00:00:00.000000)",
Query().where(f.endofday(t.dateField) > datetime(2019, 7, 23)).render()
Query().where(f.end_of_day(t.dateField) > datetime(2019, 7, 23)).render()
)
self.assertEqual(
" | where (endofday(dateField, 2)) > datetime(2019-07-23 00:00:00.000000)",
Query().where(f.endofday(t.dateField, 2) > datetime(2019, 7, 23)).render()
Query().where(f.end_of_day(t.dateField, 2) > datetime(2019, 7, 23)).render()
)
def test_endofmonth(self):
self.assertEqual(
" | where (endofmonth(dateField)) > datetime(2019-07-23 00:00:00.000000)",
Query().where(f.endofmonth(t.dateField) > datetime(2019, 7, 23)).render()
Query().where(f.end_of_month(t.dateField) > datetime(2019, 7, 23)).render()
)
self.assertEqual(
" | where (endofmonth(dateField, 2)) > datetime(2019-07-23 00:00:00.000000)",
Query().where(f.endofmonth(t.dateField, 2) > datetime(2019, 7, 23)).render()
Query().where(f.end_of_month(t.dateField, 2) > datetime(2019, 7, 23)).render()
)
def test_endofweek(self):
self.assertEqual(
" | where (endofweek(dateField)) > datetime(2019-07-23 00:00:00.000000)",
Query().where(f.endofweek(t.dateField) > datetime(2019, 7, 23)).render()
Query().where(f.end_of_week(t.dateField) > datetime(2019, 7, 23)).render()
)
self.assertEqual(
" | where (endofweek(dateField, 2)) > datetime(2019-07-23 00:00:00.000000)",
Query().where(f.endofweek(t.dateField, 2) > datetime(2019, 7, 23)).render()
Query().where(f.end_of_week(t.dateField, 2) > datetime(2019, 7, 23)).render()
)
def test_endofyear(self):
self.assertEqual(
" | where (endofyear(dateField)) > datetime(2019-07-23 00:00:00.000000)",
Query().where(f.endofyear(t.dateField) > datetime(2019, 7, 23)).render()
Query().where(f.end_of_year(t.dateField) > datetime(2019, 7, 23)).render()
)
self.assertEqual(
" | where (endofyear(dateField, 2)) > datetime(2019-07-23 00:00:00.000000)",
Query().where(f.endofyear(t.dateField, 2) > datetime(2019, 7, 23)).render()
Query().where(f.end_of_year(t.dateField, 2) > datetime(2019, 7, 23)).render()
)
def test_exp(self):
@ -144,25 +144,25 @@ class TestFunction(TestBase):
def test_getmonth(self):
self.assertEqual(
" | where (getmonth(dateField)) > 3",
Query().where(f.getmonth(t.dateField) > 3).render()
Query().where(f.get_month(t.dateField) > 3).render()
)
def test_gettype(self):
self.assertEqual(
' | where (gettype(dateField)) == "datetime"',
Query().where(f.gettype(t.dateField) == 'datetime').render()
Query().where(f.get_type(t.dateField) == 'datetime').render()
)
def test_getyear(self):
self.assertEqual(
" | where (getyear(dateField)) > 2019",
Query().where(f.getyear(t.dateField) > 2019).render()
Query().where(f.get_year(t.dateField) > 2019).render()
)
def test_hourofday(self):
self.assertEqual(
" | where (hourofday(dateField)) == 3",
Query().where(f.hourofday(t.dateField) == 3).render()
Query().where(f.hour_of_day(t.dateField) == 3).render()
)
def test_hash(self):
@ -180,49 +180,49 @@ class TestFunction(TestBase):
def test_isempty(self):
self.assertEqual(
" | where isempty(stringField)",
Query().where(f.isempty(t.stringField)).render()
Query().where(f.is_empty(t.stringField)).render()
)
def test_isfinite(self):
self.assertEqual(
" | where isfinite(numField)",
Query().where(f.isfinite(t.numField)).render()
Query().where(f.is_finite(t.numField)).render()
)
def test_isinf(self):
self.assertEqual(
" | where isinf(numField)",
Query().where(f.isinf(t.numField)).render()
Query().where(f.is_inf(t.numField)).render()
)
def test_isnan(self):
self.assertEqual(
" | where isnan(numField)",
Query().where(f.isnan(t.numField)).render()
Query().where(f.is_nan(t.numField)).render()
)
def test_isnotempty(self):
self.assertEqual(
" | where isnotempty(stringField)",
Query().where(f.isnotempty(t.stringField)).render()
Query().where(f.is_not_empty(t.stringField)).render()
)
def test_isnotnull(self):
self.assertEqual(
" | where isnotnull(stringField)",
Query().where(f.isnotnull(t.stringField)).render()
Query().where(f.is_not_null(t.stringField)).render()
)
def test_isnull(self):
self.assertEqual(
" | where isnull(stringField)",
Query().where(f.isnull(t.stringField)).render()
Query().where(f.is_null(t.stringField)).render()
)
def test_isutf8(self):
self.assertEqual(
" | where isutf8(stringField)",
Query().where(f.isutf8(t.stringField)).render()
Query().where(f.is_utf8(t.stringField)).render()
)
def test_log(self):
@ -246,7 +246,7 @@ class TestFunction(TestBase):
def test_loggamma(self):
self.assertEqual(
" | where (loggamma(numField)) < 3",
Query().where(f.loggamma(t.numField) < 3).render()
Query().where(f.log_gamma(t.numField) < 3).render()
)
def test_make_datetime(self):
@ -300,7 +300,7 @@ class TestFunction(TestBase):
def test_parse_json_number_expression(self):
self.assertEqual(
' | where (todouble(parse_json(stringField).bar)) > 4',
Query().where(f.todouble(f.parse_json(t.stringField).bar) > 4).render()
Query().where(f.to_double(f.parse_json(t.stringField).bar) > 4).render()
)
def test_parse_json_array(self):
@ -341,25 +341,25 @@ class TestFunction(TestBase):
def test_startofday(self):
self.assertEqual(
" | where (startofday(dateField)) > datetime(2019-07-23 00:00:00.000000)",
Query().where(f.startofday(t.dateField) > datetime(2019, 7, 23)).render()
Query().where(f.start_of_day(t.dateField) > datetime(2019, 7, 23)).render()
)
def test_startofmonth(self):
self.assertEqual(
" | where (startofmonth(dateField)) > datetime(2019-07-01 00:00:00.000000)",
Query().where(f.startofmonth(t.dateField) > datetime(2019, 7, 1)).render()
Query().where(f.start_of_month(t.dateField) > datetime(2019, 7, 1)).render()
)
def test_startofweek(self):
self.assertEqual(
" | where (startofweek(dateField)) > datetime(2019-07-08 00:00:00.000000)",
Query().where(f.startofweek(t.dateField) > datetime(2019, 7, 8)).render()
Query().where(f.start_of_week(t.dateField) > datetime(2019, 7, 8)).render()
)
def test_startofyear(self):
self.assertEqual(
" | where (startofyear(dateField)) > datetime(2019-01-01 00:00:00.000000)",
Query().where(f.startofyear(t.dateField) > datetime(2019, 1, 1)).render()
Query().where(f.start_of_year(t.dateField) > datetime(2019, 1, 1)).render()
)
def test_strcat(self):
@ -418,8 +418,8 @@ class TestFunction(TestBase):
def test_strrep(self):
self.assertEqual(
' | where (strrep(stringField, numField)) == "ABCABC"',
Query().where(f.strrep(t.stringField, t.numField) == 'ABCABC').render()
' | where (strrep(stringField, numField)) == "ABC"',
Query().where(f.strrep(t.stringField, t.numField) == 'ABC').render()
)
self.assertEqual(
' | where (strrep(stringField, numField, ",")) == "ABC,ABC"',
@ -432,8 +432,8 @@ class TestFunction(TestBase):
def test_substring(self):
self.assertEqual(
' | where (substring(stringField, numField)) == "ABCABC"',
Query().where(f.substring(t.stringField, t.numField) == 'ABCABC').render()
' | where (substring(stringField, numField)) == "ABC"',
Query().where(f.substring(t.stringField, t.numField) == 'ABC').render()
)
self.assertEqual(
' | where (substring(stringField, numField, 4)) == "ABC,ABC"',
@ -457,71 +457,71 @@ class TestFunction(TestBase):
def test_tobool(self):
self.assertEqual(
" | where tobool(stringField)",
Query().where(f.tobool(t.stringField)).render()
Query().where(f.to_bool(t.stringField)).render()
)
def test_toboolean(self):
self.assertEqual(
" | where toboolean(stringField)",
Query().where(f.toboolean(t.stringField)).render()
Query().where(f.to_boolean(t.stringField)).render()
)
def test_todouble(self):
self.assertEqual(
" | where (todouble(stringField)) > 0.2",
Query().where(f.todouble(t.stringField) > 0.2).render()
Query().where(f.to_double(t.stringField) > 0.2).render()
)
def test_todecimal(self):
self.assertEqual(
" | where (todecimal(stringField)) > 0.2",
Query().where(f.todecimal(t.stringField) > 0.2).render()
Query().where(f.to_decimal(t.stringField) > 0.2).render()
)
def test_toint(self):
self.assertEqual(
" | where (toint(stringField)) > 1",
Query().where(f.toint(t.stringField) > 1).render()
Query().where(f.to_int(t.stringField) > 1).render()
)
def test_tolong(self):
self.assertEqual(
" | where (tolong(stringField)) > 2222222222",
Query().where(f.tolong(t.stringField) > 2222222222).render()
Query().where(f.to_long(t.stringField) > 2222222222).render()
)
def test_todatetime(self):
self.assertEqual(
" | extend foo = todatetime(stringField)",
Query().extend(foo=f.todatetime(t.stringField)).render()
Query().extend(foo=f.to_datetime(t.stringField)).render()
)
def test_tolower(self):
self.assertEqual(
' | where (tolower(stringField)) == "foo"',
Query().where(f.tolower(t.stringField) == "foo").render()
Query().where(f.to_lower(t.stringField) == "foo").render()
)
def test_toreal(self):
self.assertEqual(
" | where (toreal(stringField)) > 0.2",
Query().where(f.toreal(t.stringField) > 0.2).render()
Query().where(f.to_real(t.stringField) > 0.2).render()
)
def test_toupper(self):
self.assertEqual(
' | where (toupper(stringField)) == "FOO"',
Query().where(f.toupper(t.stringField) == "FOO").render()
Query().where(f.to_upper(t.stringField) == "FOO").render()
)
def test_tohex(self):
self.assertEqual(
' | where (tohex(256)) == "100"',
Query().where(f.tohex(256) == "100").render()
Query().where(f.to_hex(256) == "100").render()
)
# ------------------------------------------------------
# Aggregative Functions
# Aggregation Functions
# ------------------------------------------------------
def test_any(self):
@ -573,7 +573,7 @@ class TestFunction(TestBase):
def test_avgif(self):
self.assertEqual(
" | summarize avgif(numField, boolField)",
Query().summarize(f.avgif(t.numField, t.boolField)).render()
Query().summarize(f.avg_if(t.numField, t.boolField)).render()
)
def test_bin(self):
@ -631,7 +631,7 @@ class TestFunction(TestBase):
def test_countif(self):
self.assertEqual(
" | summarize countif(numField == 1)",
Query().summarize(f.countif(t.numField == 1)).render()
Query().summarize(f.count_if(t.numField == 1)).render()
)
def test_dcount(self):
@ -648,7 +648,7 @@ class TestFunction(TestBase):
def test_dcountif(self):
self.assertEqual(
" | summarize dcountif(stringField, boolField, 0)",
Query().summarize(f.dcountif(t.stringField, t.boolField)).render()
Query().summarize(f.dcount_if(t.stringField, t.boolField)).render()
)
def test_make_bag(self):
@ -744,7 +744,7 @@ class TestFunction(TestBase):
def test_sumif(self):
self.assertEqual(
" | summarize sumif(numField, boolField)",
Query().summarize(f.sumif(t.numField, t.boolField)).render()
Query().summarize(f.sum_if(t.numField, t.boolField)).render()
)
def test_variance(self):
@ -756,7 +756,7 @@ class TestFunction(TestBase):
def test_varianceif(self):
self.assertEqual(
" | summarize varianceif(numField, boolField)",
Query().summarize(f.varianceif(t.numField, t.boolField)).render()
Query().summarize(f.variance_if(t.numField, t.boolField)).render()
)
def test_variancep(self):

Просмотреть файл

@ -198,7 +198,7 @@ class TestQuery(TestBase):
def test_summarize_by_expression(self):
self.assertEqual(
"test_table | summarize count(stringField) by tostring(mapField)",
Query(t).summarize(f.count(t.stringField)).by(f.tostring(t.mapField)).render(),
Query(t).summarize(f.count(t.stringField)).by(f.to_string(t.mapField)).render(),
)
def test_mv_expand(self):

Просмотреть файл

@ -1,5 +1,5 @@
from pykusto.expressions import to_kql
from pykusto.type_utils import TypeRegistrar, KustoType
from pykusto.type_utils import TypeRegistrar, KustoType, KQL
from test.test_base import TestBase
@ -69,11 +69,11 @@ class TestUtils(TestBase):
test_annotation = TypeRegistrar("Test annotation")
@test_annotation(KustoType.STRING)
def str_annotated_1(s: str) -> str:
return "response to " + s
def str_annotated_1(s: str) -> KQL:
return KQL("response to " + s)
def str_annotated_2(s: str) -> str:
return "response to " + s
def str_annotated_2(s: str) -> KQL:
return KQL("response to " + s)
self.assertRaises(
TypeError("Test annotation: type already registered: string"),