def test_quick_fixes(self): err = PromptingError([ PromptingError.WrongColumnType(["A"], "text", frozenset({"number"})), PromptingError.WrongColumnType(["B", "C"], "datetime", frozenset({"number"})), ]) quick_fixes_result = err.as_quick_fixes() self.assertEqual( quick_fixes_result, [ QuickFix( I18nMessage.TODO_i18n("Convert Text to Numbers"), QuickFixAction.PrependStep("converttexttonumber", {"colnames": ["A"]}), ), QuickFix( I18nMessage.TODO_i18n("Convert Dates & Times to Numbers"), QuickFixAction.PrependStep("converttexttonumber", {"colnames": ["B", "C"]}), ), ], ) error_result = err.as_error_str() self.assertEqual( error_result, ("The column “A” must be converted from Text to Numbers.\n\n" "The columns “B” and “C” must be converted from Dates & Times to Numbers." ), )
def test_quick_fixes(self): err = PromptingError([ PromptingError.WrongColumnType(["A"], "text", frozenset({"number"})), PromptingError.WrongColumnType(["B", "C"], "text", frozenset({"number"})), ]) result = err.as_render_errors() self.assertEqual( result, [ RenderError( I18nMessage( "py.renderer.execute.types.PromptingError.WrongColumnType.general.message.before_convert_buttons", { "columns": 1, "0": "A", "found_type": "text", }, None, ), [ QuickFix( I18nMessage( "py.renderer.execute.types.PromptingError.WrongColumnType.general.quick_fix", {"wanted_type": "number"}, None, ), QuickFixAction.PrependStep("converttexttonumber", {"colnames": ["A"]}), ) ], ), RenderError( I18nMessage( "py.renderer.execute.types.PromptingError.WrongColumnType.general.message.before_convert_buttons", { "columns": 2, "0": "B", "1": "C", "found_type": "text", }, None, ), [ QuickFix( I18nMessage( "py.renderer.execute.types.PromptingError.WrongColumnType.general.quick_fix", {"wanted_type": "number"}, None, ), QuickFixAction.PrependStep( "converttexttonumber", {"colnames": ["B", "C"]}), ) ], ), ], )
def test_quick_fixes_multiple_conversions(self): # For example, "linechart" X axis may be temporal or number err = PromptingError([ PromptingError.WrongColumnType( ["A"], "text", frozenset({"number", "date", "timestamp"})) ]) result = err.as_render_errors() self.assertEqual( result, [ RenderError( I18nMessage( "py.renderer.execute.types.PromptingError.WrongColumnType.general.message.before_convert_buttons", { "columns": 1, "0": "A", "found_type": "text", }, None, ), [ QuickFix( I18nMessage( "py.renderer.execute.types.PromptingError.WrongColumnType.general.quick_fix", {"wanted_type": "date"}, None, ), QuickFixAction.PrependStep("converttexttodate", {"colnames": ["A"]}), ), QuickFix( I18nMessage( "py.renderer.execute.types.PromptingError.WrongColumnType.general.quick_fix", {"wanted_type": "number"}, None, ), QuickFixAction.PrependStep("converttexttonumber", {"colnames": ["A"]}), ), QuickFix( I18nMessage( "py.renderer.execute.types.PromptingError.WrongColumnType.general.quick_fix", {"wanted_type": "timestamp"}, None, ), QuickFixAction.PrependStep("convert-date", {"colnames": ["A"]}), ), ], ), ], )
def test_quick_fixes_convert_to_text(self): err = PromptingError([ PromptingError.WrongColumnType(["A", "B"], None, frozenset({"text"})) ]) result = err.as_render_errors() self.assertEqual( result, [ RenderError( I18nMessage( "py.renderer.execute.types.PromptingError.WrongColumnType.as_error_message.shouldBeText", { "columns": 2, "0": "A", "1": "B" }, None, ), [ QuickFix( I18nMessage( "py.renderer.execute.types.PromptingError.WrongColumnType.as_quick_fixes.shouldBeText", {}, None, ), QuickFixAction.PrependStep( "converttotext", {"colnames": ["A", "B"]}), ) ], ) ], )
def test_cache_render_result(self): with arrow_table_context(make_column("A", [1])) as (table_path, table): result = LoadedRenderResult( path=table_path, table=table, columns=[Column("A", ColumnType.Number(format="{:,}"))], errors=[ RenderError( I18nMessage("e1", {"text": "hi"}, None), [ QuickFix( I18nMessage("q1", {"var": 2}, None), QuickFixAction.PrependStep("filter", {"a": "x"}), ) ], ), RenderError(I18nMessage("e2", {}, None), []), ], json={"foo": "bar"}, ) cache_render_result(self.workflow, self.step, 1, result) cached = self.step.cached_render_result self.assertEqual(cached.step_id, self.step.id) self.assertEqual(cached.delta_id, 1) self.assertEqual( crr_parquet_key(cached), f"wf-{self.workflow.id}/wfm-{self.step.id}/delta-1.dat", ) # Reading completely freshly from the DB should give the same thing db_step = Step.objects.get(id=self.step.id) from_db = db_step.cached_render_result self.assertEqual(from_db, cached) with open_cached_render_result(from_db) as result2: assert_arrow_table_equals( result2.table, make_table(make_column("A", [1], format="{:,}")) ) self.assertEqual( result2.columns, [Column("A", ColumnType.Number(format="{:,}"))] )
def as_render_errors(self) -> List[RenderError]: """Build RenderError(s) that describe this error. Render errors will include a QuickFix that would resolve this error.""" if self.should_be_text: message = I18nMessage.trans( "py.renderer.execute.types.PromptingError.WrongColumnType.as_quick_fixes.shouldBeText", default="Convert to Text.", ) else: # i18n: The parameters {found_type} and {best_wanted_type} will have values among "text", "number", "datetime"; however, including an (possibly empty) "other" case is mandatory. message = I18nMessage.trans( "py.renderer.execute.types.PromptingError.WrongColumnType.as_quick_fixes.general", default= "Convert { found_type, select, text {Text} number {Numbers} datetime {Dates & Times} other {}} to {best_wanted_type, select, text {Text} number {Numbers} datetime {Dates & Times} other{}}.", args={ "found_type": self.found_type, "best_wanted_type": self.best_wanted_type_id, }, ) params = {"colnames": self.column_names} if "text" in self.wanted_types: module_id = "converttotext" elif "number" in self.wanted_types: module_id = "converttexttonumber" elif "datetime" in self.wanted_types: module_id = "convert-date" else: raise RuntimeError( f"Unhandled wanted_types: {self.wanted_types}") return [ RenderError( self._as_i18n_message(), [ QuickFix(message, QuickFixAction.PrependStep(module_id, params)) ], ) ]
def test_quick_fixes_convert_to_text(self): err = PromptingError([ PromptingError.WrongColumnType(["A", "B"], None, frozenset({"text"})) ]) quick_fixes_result = err.as_quick_fixes() self.assertEqual( quick_fixes_result, [ QuickFix( I18nMessage.TODO_i18n("Convert to Text"), QuickFixAction.PrependStep("converttotext", {"colnames": ["A", "B"]}), ) ], ) error_result = err.as_error_str() self.assertEqual(error_result, "The columns “A” and “B” must be converted to Text.")
def as_quick_fix(self): """Build a QuickFix that would resolve this error.""" if self.should_be_text: prompt = f"Convert to {self.best_wanted_type_name}" else: prompt = ( f"Convert {self.found_type_name} to {self.best_wanted_type_name}" ) params = {"colnames": self.column_names} if "text" in self.wanted_types: module_id = "converttotext" elif "number" in self.wanted_types: module_id = "converttexttonumber" elif "datetime" in self.wanted_types: module_id = "convert-date" else: raise RuntimeError(f"Unhandled wanted_types: {self.wanted_types}") return QuickFix( I18nMessage.TODO_i18n(prompt), QuickFixAction.PrependStep(module_id, params), )
def test_cache_render_result(self): result = RenderResult( arrow_table({"A": [1]}), [ RenderError( I18nMessage("e1", [1, "x"]), [ QuickFix( I18nMessage("q1", []), QuickFixAction.PrependStep("filter", {"a": "x"}), ) ], ), RenderError(I18nMessage("e2", []), []), ], {"foo": "bar"}, ) cache_render_result(self.workflow, self.wf_module, self.delta.id, result) cached = self.wf_module.cached_render_result self.assertEqual(cached.wf_module_id, self.wf_module.id) self.assertEqual(cached.delta_id, self.delta.id) self.assertEqual( crr_parquet_key(cached), f"wf-{self.workflow.id}/wfm-{self.wf_module.id}/delta-{self.delta.id}.dat", ) # Reading completely freshly from the DB should give the same thing db_wf_module = WfModule.objects.get(id=self.wf_module.id) from_db = db_wf_module.cached_render_result self.assertEqual(from_db, cached) with open_cached_render_result(from_db) as result2: assert_render_result_equals(result2, result)
def _dict_to_quick_fix(value: Dict[str, Any]) -> QuickFix: return QuickFix( _dict_to_i18n_message(value["buttonText"]), _dict_to_quick_fix_action(value["action"]), )
def as_render_error(self) -> RenderError: """Build RenderError(s) that describe this error. Render errors should include at least one QuickFix to resolve the error. Errors the user can see: (wanted_types = {date, timestamp}) "A", "B" and 2 others are Text. [Convert to Date] [Convert to Timestamp] (wanted_types = {number}) "A" and "B" are Date. Select Number. (wanted_types = {text} - special case because all types convert to text) "A" is not Text. [Convert to Text] """ if self.should_be_text: icu_args = { "columns": len(self.column_names), **{ str(i): name for i, name in enumerate(self.column_names) }, } # i18n: The parameter {columns} will contain the total number of columns that need to be converted; you will also receive the column names as {0}, {1}, {2}, etc. message = trans( "py.renderer.execute.types.PromptingError.WrongColumnType.as_error_message.shouldBeText", default="{ columns, plural, offset:2" " =1 {“{0}” is not Text.}" " =2 {“{0}” and “{1}” are not Text.}" " =3 {“{0}”, “{1}” and “{2}” are not Text.}" " other {“{0}”, “{1}” and # others are not Text.}}", arguments=icu_args, ) return RenderError( message, quick_fixes=[ QuickFix( trans( "py.renderer.execute.types.PromptingError.WrongColumnType.as_quick_fixes.shouldBeText", default="Convert to Text", ), QuickFixAction.PrependStep( "converttotext", dict(colnames=self.column_names)), ) ], ) else: icu_args = { "columns": len(self.column_names), "found_type": self.found_type, **{ str(i): name for i, name in enumerate(self.column_names) }, } quick_fixes = [ QuickFix( # i18n: The parameter {wanted_type} will have values among "text", "number", "date", "timestamp" or "other". ("other" may translate to "".) trans( "py.renderer.execute.types.PromptingError.WrongColumnType.general.quick_fix", default= "Convert to {wanted_type, select, text {Text} number {Number} date {Date} timestamp {Timestamp} other {}}", arguments=dict(wanted_type=wanted_type), ), QuickFixAction.PrependStep( _QUICK_FIX_CONVERSIONS[(self.found_type, wanted_type)], dict(colnames=self.column_names), ), ) for wanted_type in sorted( self.wanted_types) # sort for determinism if (self.found_type, wanted_type) in _QUICK_FIX_CONVERSIONS ] if quick_fixes: # i18n: The parameter {columns} will contain the total number of columns that need to be converted; you will also receive the column names: {0}, {1}, {2}, etc. The parameter {found_type} will be "date", "text", "number", "timestamp" or "other". ("other" may translate to "".) message = trans( "py.renderer.execute.types.PromptingError.WrongColumnType.general.message.before_convert_buttons", default="{columns, plural, offset:2" " =1 {“{0}” is {found_type, select, text {Text} number {Number} timestamp {Timestamp} date {Date} other {}}.}" " =2 {“{0}” and “{1}” are {found_type, select, text {Text} number {Number} date {Date} timestamp {Timestamp} other {}}.}" " =3 {“{0}”, “{1}” and “{2}” are {found_type, select, text {Text} number {Number} date {Date} timestamp {Timestamp} other {}}.}" " other {“{0}”, “{1}” and # others are {found_type, select, text {Text} number {Number} date {Date} timestamp {Timestamp} other {}}.}}", arguments=icu_args, ) else: # i18n: The parameter {columns} will contain the total number of columns that need to be converted; you will also receive the column names: {0}, {1}, {2}, etc. The parameters {found_type} and {best_wanted_type} will be "date", "text", "number", "timestamp" or "other". ("other" may translate to "".) message = trans( "py.renderer.execute.types.PromptingError.WrongColumnType.general.message.without_convert_buttons", default="{columns, plural, offset:2" " =1 {“{0}” is {found_type, select, text {Text} number {Number} timestamp {Timestamp} date {Date} other {}}. Select {best_wanted_type, select, text {Text} number {Number} date {Date} timestamp {Timestamp} other {}}.}" " =2 {“{0}” and “{1}” are {found_type, select, text {Text} number {Number} date {Date} timestamp {Timestamp} other {}}. Select {best_wanted_type, select, text {Text} number {Number} date {Date} timestamp {Timestamp} other {}}.}" " =3 {“{0}”, “{1}” and “{2}” are {found_type, select, text {Text} number {Number} date {Date} timestamp {Timestamp} other {}}. Select {best_wanted_type, select, text {Text} number {Number} date {Date} timestamp {Timestamp} other {}}.}" " other {“{0}”, “{1}” and # others are {found_type, select, text {Text} number {Number} date {Date} timestamp {Timestamp} other {}}. Select {best_wanted_type, select, text {Text} number {Number} date {Date} timestamp {Timestamp} other {}}.}}", arguments=dict(best_wanted_type=self.best_wanted_type, **icu_args), ) return RenderError(message, quick_fixes=quick_fixes)