Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions superset/charts/commands/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from flask_babel import lazy_gettext as _
from flask_babel import _
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bycatch, lazy text cannot be properly formatted by Marshmallow.

from marshmallow.validate import ValidationError

from superset.commands.exceptions import (
Expand All @@ -28,13 +28,41 @@
)


class TimeRangeUnclearError(ValidationError):
"""
Time range is in valid error.
"""

def __init__(self, human_readable: str) -> None:
super().__init__(
_(
"Time string is unclear."
" Please specify [%(human_readable)s ago]"
" or [%(human_readable)s later].",
human_readable=human_readable,
),
field_name="time_range",
)


class TimeRangeParseFailError(ValidationError):
def __init__(self, human_readable: str) -> None:
super().__init__(
_(
"Cannot parse time string [%(human_readable)s]",
human_readable=human_readable,
),
field_name="time_range",
)


class DatabaseNotFoundValidationError(ValidationError):
"""
Marshmallow validation error for database does not exist
"""

def __init__(self) -> None:
super().__init__(_("Database does not exist"), field_names=["database"])
super().__init__(_("Database does not exist"), field_name="database")
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bycatch: should use field_name instead of field_names.



class DashboardsNotFoundValidationError(ValidationError):
Expand All @@ -43,7 +71,7 @@ class DashboardsNotFoundValidationError(ValidationError):
"""

def __init__(self) -> None:
super().__init__(_("Dashboards do not exist"), field_names=["dashboards"])
super().__init__(_("Dashboards do not exist"), field_name="dashboards")


class DatasourceTypeUpdateRequiredValidationError(ValidationError):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,14 @@ def upgrade():
)

try:
slices = session.query(Slice).filter(where_clause).all()
slices = session.query(Slice).filter(where_clause)
total = slices.count()
sep = " : "
pattern = DateRangeMigration.x_dateunit
for idx, slc in enumerate(slices):
print(f"Upgrading ({idx + 1}/{len(slices)}): {slc.slice_name}#{slc.id}")
idx = 0
for slc in slices.yield_per(100):
idx += 1
print(f"Upgrading ({idx}/{total}): {slc.slice_name}#{slc.id}")
params = json.loads(slc.params)
time_range = params["time_range"]
if sep in time_range:
Expand All @@ -93,7 +96,6 @@ def upgrade():
if re.match(pattern, end):
end = f"{end.strip()} later"
params["time_range"] = f"{start}{sep}{end}"

slc.params = json.dumps(params, sort_keys=True, indent=4)
session.commit()
except OperationalError:
Expand Down
29 changes: 10 additions & 19 deletions superset/utils/date_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@
Suppress,
)

from .core import memoized
from superset.charts.commands.exceptions import (
TimeRangeParseFailError,
TimeRangeUnclearError,
)
from superset.utils.core import memoized

ParserElement.enablePackrat()

Expand Down Expand Up @@ -73,15 +77,7 @@ def parse_human_datetime(human_readable: str) -> datetime:
"""
x_periods = r"^\s*([0-9]+)\s+(second|minute|hour|day|week|month|quarter|year)s?\s*$"
if re.search(x_periods, human_readable, re.IGNORECASE):
raise ValueError(
_(
"Date string is unclear."
" Please specify [%(human_readable)s ago]"
" or [%(human_readable)s later]",
human_readable=human_readable,
)
)

raise TimeRangeUnclearError(human_readable)
try:
dttm = parse(human_readable)
except (ValueError, OverflowError) as ex:
Expand All @@ -90,12 +86,7 @@ def parse_human_datetime(human_readable: str) -> datetime:
# 0 == not parsed at all
if parsed_flags == 0:
logger.exception(ex)
raise ValueError(
_(
"Couldn't parse date string [%(human_readable)s]",
human_readable=human_readable,
)
)
raise TimeRangeParseFailError(human_readable)
# when time is not extracted, we 'reset to midnight'
if parsed_flags & 2 == 0:
parsed_dttm = parsed_dttm.replace(hour=0, minute=0, second=0)
Expand Down Expand Up @@ -492,9 +483,9 @@ def datetime_eval(datetime_expression: Optional[str] = None) -> Optional[datetim

class DateRangeMigration: # pylint: disable=too-few-public-methods
x_dateunit_in_since = (
r'"time_range":\s"\s*[0-9]+\s(day|week|month|quarter|year)s?\s*\s:\s'
r'"time_range":\s*"\s*[0-9]+\s+(day|week|month|quarter|year)s?\s*\s:\s'
)
x_dateunit_in_until = (
r'"time_range":\s".*\s:\s\s*[0-9]+\s(day|week|month|quarter|year)s?\s*"'
r'"time_range":\s*".*\s:\s*[0-9]+\s+(day|week|month|quarter|year)s?\s*"'
)
x_dateunit = r"\s*[0-9]+\s(day|week|month|quarter|year)s?\s*"
x_dateunit = r"^\s*[0-9]+\s+(day|week|month|quarter|year)s?\s*$"
16 changes: 13 additions & 3 deletions tests/utils/date_parser_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
from datetime import datetime, timedelta
from unittest.mock import patch

from superset.charts.commands.exceptions import (
TimeRangeParseFailError,
TimeRangeUnclearError,
)
from superset.utils.date_parser import (
DateRangeMigration,
datetime_eval,
Expand Down Expand Up @@ -265,13 +269,13 @@ def test_parse_past_timedelta(self, mock_datetime):
self.assertEqual(parse_past_timedelta("1 month"), timedelta(31))

def test_parse_human_datetime(self):
with self.assertRaises(ValueError):
with self.assertRaises(TimeRangeUnclearError):
parse_human_datetime(" 2 days ")

with self.assertRaises(ValueError):
with self.assertRaises(TimeRangeUnclearError):
parse_human_datetime("2 day")

with self.assertRaises(ValueError):
with self.assertRaises(TimeRangeParseFailError):
parse_human_datetime("xxxxxxx")

def test_DateRangeMigration(self):
Expand All @@ -291,3 +295,9 @@ def test_DateRangeMigration(self):

field = " 8 days "
self.assertRegex(field, DateRangeMigration.x_dateunit)

field = "last week"
self.assertNotRegex(field, DateRangeMigration.x_dateunit)
Comment thread
zhaoyongjie marked this conversation as resolved.

field = "10 years ago"
self.assertNotRegex(field, DateRangeMigration.x_dateunit)