Skip to content

Commit

Permalink
Merge branch 'main' into override-issue-155
Browse files Browse the repository at this point in the history
  • Loading branch information
geoo89 authored Mar 6, 2025
2 parents 04963c2 + bfdc04d commit ead3636
Show file tree
Hide file tree
Showing 16 changed files with 129 additions and 99 deletions.
21 changes: 12 additions & 9 deletions src/rpft/parsers/common/rowparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,15 @@ def get_list_child_model(model):


def is_list_type(model):
# Determine whether model is a list type,
# such as list, List, List[str], ...
# issubclass only works for Python <=3.6
# model.__dict__.get('__origin__') returns different things in different Python
# version.
# This function tries to accommodate both 3.6 and 3.8 (at least)
"""
Determine whether model is a list type, such as list, list[str], List, List[str].
typing.List is deprecated as of Python 3.9
"""
return (
is_basic_list_type(model)
or model is List
or model.__dict__.get("__origin__") in [list, List]
or getattr(model, "__origin__", None) is list
)


Expand Down Expand Up @@ -205,7 +204,7 @@ def assign_value(self, field, key, value, model):
value = list(value)
if isinstance(value[0], str):
assert len(value) == 2
field[key] = {value[0] : value[1]}
field[key] = {value[0]: value[1]}
elif isinstance(value[0], list):
for entry in value:
assert len(entry) == 2
Expand Down Expand Up @@ -327,7 +326,11 @@ def parse_entry(
# The model of field[key] is model, and thus value should also be interpreted
# as being of type model.
if not value_is_parsed:
if is_list_type(model) or is_basic_dict_type(model) or is_parser_model_type(model):
if (
is_list_type(model)
or is_basic_dict_type(model)
or is_parser_model_type(model)
):
# If the expected type of the value is list/object,
# parse the cell content as such.
# Otherwise leave it as a string
Expand Down
14 changes: 6 additions & 8 deletions src/rpft/parsers/creation/contentindexparser.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import importlib
import logging
from collections import OrderedDict
from typing import Dict, List

from rpft.logger.logger import logging_context
from rpft.parsers.common.model_inference import model_from_headers
from rpft.parsers.common.sheetparser import SheetParser
Expand Down Expand Up @@ -58,8 +56,8 @@ def __init__(
self.tag_matcher = tag_matcher
self.template_sheets = {}
self.data_sheets = {}
self.flow_definition_rows: List[ContentIndexRowModel] = []
self.campaign_parsers: Dict[str, tuple[str, CampaignParser]] = {}
self.flow_definition_rows: list[ContentIndexRowModel] = []
self.campaign_parsers: dict[str, tuple[str, CampaignParser]] = {}
self.surveys = {}
self.trigger_parsers = OrderedDict()
self.user_models_module = (
Expand Down Expand Up @@ -133,7 +131,7 @@ def _process_content_index_table(self, sheet: Sheet):
name = campaign_parser.campaign.name

if name in self.campaign_parsers:
LOGGER.warning(
LOGGER.debug(
f"Duplicate campaign definition sheet '{name}'. "
"Overwriting previous definition."
)
Expand All @@ -155,7 +153,7 @@ def _add_template(self, row, update_duplicates=False):
sheet_name = row.sheet_name[0]

if sheet_name in self.template_sheets and update_duplicates:
LOGGER.info(
LOGGER.debug(
f"Duplicate template definition sheet '{sheet_name}'. "
"Overwriting previous definition."
)
Expand Down Expand Up @@ -193,7 +191,7 @@ def _get_sheet_or_die(self, sheet_name):

if len(candidates) > 1:
readers = [c.reader.name for c in candidates]
LOGGER.warning(
LOGGER.debug(
"Duplicate sheets found, "
+ str(
{
Expand Down Expand Up @@ -248,7 +246,7 @@ def _process_data_sheet(self, row):
new_name = row.new_name or sheet_names[0]

if new_name in self.data_sheets:
LOGGER.warning(
LOGGER.debug(
f"Duplicate data sheet {new_name}. Overwriting previous definition."
)

Expand Down
7 changes: 3 additions & 4 deletions src/rpft/parsers/creation/contentindexrowmodel.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from enum import Enum
from typing import List

from rpft.parsers.common.rowparser import ParserModel
from rpft.parsers.creation.models import SurveyConfig
Expand All @@ -24,18 +23,18 @@ class Operation(ParserModel):
class ContentIndexRowModel(ParserModel):
type: str = ""
new_name: str = ""
sheet_name: List[str] = []
sheet_name: list[str] = []
data_sheet: str = ""
data_row_id: str = ""
template_argument_definitions: List[TemplateArgument] = [] # internal name
template_argument_definitions: list[TemplateArgument] = [] # internal name
template_arguments: list = []
options: dict = {}
survey_config: SurveyConfig = SurveyConfig()
operation: Operation = Operation()
data_model: str = ""
group: str = ""
status: str = ""
tags: List[str] = []
tags: list[str] = []

def field_name_to_header_name(field):
if field == "template_argument_definitions":
Expand Down
16 changes: 12 additions & 4 deletions src/rpft/parsers/creation/flowparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,9 +513,17 @@ def _get_row_action(self, row):
scheme=row.urn_scheme or "tel",
)
elif row.type == "remove_from_group":
return RemoveContactGroupAction(
groups=[self._get_or_create_group(row.mainarg_groups[0], row.obj_id)]
)
if not row.mainarg_groups:
LOGGER.warning(f"Removing contact from ALL groups.")
return RemoveContactGroupAction(groups=[], all_groups=True)
elif row.mainarg_groups[0] == "ALL":
return RemoveContactGroupAction(groups=[], all_groups=True)
else:
return RemoveContactGroupAction(
groups=[
self._get_or_create_group(row.mainarg_groups[0], row.obj_id)
]
)
elif row.type == "save_flow_result":
return SetRunResultAction(
row.save_name, row.mainarg_value, category=row.result_category
Expand Down Expand Up @@ -551,7 +559,7 @@ def _get_or_create_group(self, name, uuid=None):
def _get_row_node(self, row):
if (
row.type in ["add_to_group", "remove_from_group", "split_by_group"]
and row.obj_id
and row.obj_id and row.mainarg_groups
):
self.rapidpro_container.record_group_uuid(row.mainarg_groups[0], row.obj_id)

Expand Down
26 changes: 9 additions & 17 deletions src/rpft/parsers/creation/flowrowmodel.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from typing import List

from rpft.parsers.common.rowparser import ParserModel
from rpft.parsers.creation.models import Condition

Expand Down Expand Up @@ -43,7 +41,7 @@ def dict_to_list_of_pairs(headers):
class WhatsAppTemplating(ParserModel):
name: str = ""
uuid: str = ""
variables: List[str] = []
variables: list[str] = []


class Edge(ParserModel):
Expand All @@ -69,15 +67,15 @@ def header_name_to_field_name_with_context(header, row):
class FlowRowModel(ParserModel):
row_id: str = ""
type: str
edges: List[Edge]
loop_variable: List[str] = []
edges: list[Edge]
loop_variable: list[str] = []
include_if: bool = True
mainarg_message_text: str = ""
mainarg_value: str = ""
mainarg_groups: List[str] = []
mainarg_groups: list[str] = []
mainarg_none: str = ""
mainarg_dict: list = [] # encoded as list of pairs
mainarg_destination_row_ids: List[str] = []
mainarg_destination_row_ids: list[str] = []
mainarg_flow_name: str = ""
mainarg_expression: str = ""
mainarg_iterlist: list = []
Expand All @@ -86,28 +84,22 @@ class FlowRowModel(ParserModel):
data_sheet: str = ""
data_row_id: str = ""
template_arguments: list = []
choices: List[str] = []
choices: list[str] = []
save_name: str = ""
result_category: str = ""
image: str = ""
audio: str = ""
video: str = ""
attachments: List[str] = []
attachments: list[str] = []
urn_scheme: str = ""
obj_name: str = ""
obj_id: str = ""
node_name: str = ""
node_uuid: str = ""
no_response: str = ""
ui_type: str = ""
ui_position: List[str] = []

# TODO: Extra validation here, e.g. from must not be empty
# type must come from row_type_to_main_arg.keys() (see below)
# image/audio/video only makes sense if type == send_message
# mainarg_none should be ''
# _ui_position should be '' or a list of two ints
# ...
ui_position: list[str] = []

def field_name_to_header_name(field):
field_map = {
"node_uuid": "_nodeId",
Expand Down
18 changes: 8 additions & 10 deletions src/rpft/parsers/creation/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from typing import List

from rpft.parsers.common.rowparser import ParserModel


Expand Down Expand Up @@ -31,7 +29,7 @@ class ConditionWithMessage(ParserModel):


class ConditionsWithMessage(ParserModel):
conditions: List[ConditionWithMessage] = []
conditions: list[ConditionWithMessage] = []
general_message: str = ""


Expand Down Expand Up @@ -61,7 +59,7 @@ class Message(ParserModel):
image: str = ""
audio: str = ""
video: str = ""
attachments: List[str] = []
attachments: list[str] = []


class TemplateSheet:
Expand All @@ -76,7 +74,7 @@ def __init__(
self,
flow_definitions,
data_sheets,
templates: List[TemplateSheet],
templates: list[TemplateSheet],
surveys,
):
self.flow_definitions = flow_definitions
Expand Down Expand Up @@ -119,7 +117,7 @@ class MCQChoice(ParserModel):


class PostProcessing(ParserModel):
assignments: List[Assignment] = []
assignments: list[Assignment] = []
"""
Assignments to perform via save_value rows.
"""
Expand Down Expand Up @@ -157,7 +155,7 @@ class SurveyQuestionModel(ParserModel):
Type of the question.
"""

messages: List[Message]
messages: list[Message]
"""
Question text.
"""
Expand All @@ -175,7 +173,7 @@ class SurveyQuestionModel(ParserModel):
the question ID as {variable}_complete.
"""

choices: List[MCQChoice] = []
choices: list[MCQChoice] = []
"""
MCQ specific fields.
"""
Expand All @@ -186,7 +184,7 @@ class SurveyQuestionModel(ParserModel):
configuration is used.
"""

relevant: List[Condition] = []
relevant: list[Condition] = []
"""
Conditions required to present the question, otherwise skipped.
"""
Expand Down Expand Up @@ -223,7 +221,7 @@ class SurveyQuestionModel(ParserModel):
that is triggered.
"""

tags: List[str] = []
tags: list[str] = []
"""
Tags allowing to filter questions to appear in a survey.
"""
Expand Down
8 changes: 3 additions & 5 deletions src/rpft/parsers/creation/triggerrowmodel.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
from typing import List

from pydantic.v1 import validator

from rpft.parsers.common.rowparser import ParserModel


class TriggerRowModel(ParserModel):
type: str
keywords: List[str] = ""
keywords: list[str] = ""
flow: str = ""
groups: List[str] = []
exclude_groups: List[str] = []
groups: list[str] = []
exclude_groups: list[str] = []
channel: str = ""
match_type: str = ""

Expand Down
4 changes: 2 additions & 2 deletions src/rpft/parsers/sheets.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json
from abc import ABC
from collections.abc import Mapping
from pathlib import Path
from typing import List, Mapping

import tablib
from googleapiclient.discovery import build
Expand All @@ -28,7 +28,7 @@ def sheets(self) -> Mapping[str, Sheet]:
def get_sheet(self, name) -> Sheet:
return self.sheets.get(name)

def get_sheets_by_name(self, name) -> List[Sheet]:
def get_sheets_by_name(self, name) -> list[Sheet]:
return [sheet] if (sheet := self.get_sheet(name)) else []


Expand Down
6 changes: 3 additions & 3 deletions src/rpft/rapidpro/models/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,17 +373,17 @@ def assign_global_uuids(self, uuid_dict):
group.assign_uuid(uuid_dict)

def main_value(self):
return self.groups[0].name
return self.groups[0].name if self.groups else ""

def render(self):
return NotImplementedError

def get_row_model_fields(self):
# abstract method
obj_id = [group.uuid for group in self.groups][0] if self.groups else ""
return {
"mainarg_groups": [group.name for group in self.groups],
"obj_id": [group.uuid for group in self.groups][0]
or "", # 0th element as obj_id is not yet a list.
"obj_id": obj_id or "", # 0th element as obj_id is not yet a list.
}


Expand Down
Loading

0 comments on commit ead3636

Please sign in to comment.