Exemple #1
0
def test__hash__fail():
    start = datetime.utcnow() + timedelta(0, 3)  # 3 seconds from now
    start2 = datetime.utcnow() + timedelta(0, 4)
    end2 = start + timedelta(0, 2)
    grow3 = Grow(2, 3, 4, 5, 6, start, end2)
    grow4 = Grow(2, 3, 4, 5, 6, start2, end2)
    assert hash(grow3) != hash(grow4)
Exemple #2
0
def test_to_job_id():
    date_format = "%b %d %Y %H:%M:%S"
    start = datetime.utcnow() + timedelta(0, 3)  # 3 seconds from now
    end2 = start + timedelta(0, 2)
    grow = Grow(2, 3, 4, 5, 6, start, end2)
    assert grow.to_job_id() == "room-2-rack-3-shelf-4-recipe-5-phase-6-start-{}-end-{}".format(
        start.strftime(date_format), end2.strftime(date_format)
    )
Exemple #3
0
def test__eq__():
    start = datetime.utcnow() + timedelta(0, 3)  # 3 seconds from now
    end = start + timedelta(0, 2)  # 5 seconds from now

    grow2 = Grow(1, 2, 3, 4, 5, start, end)
    grow3 = Grow(1, 2, 3, 4, 5, start, end)

    assert grow2 == grow3
Exemple #4
0
def test__eq__fail():
    start = datetime.utcnow() + timedelta(0, 3)  # 3 seconds from now
    start2 = datetime.utcnow() + timedelta(0, 4)
    end = start + timedelta(0, 2)  # 5 seconds from now

    grow3 = Grow(1, 2, 3, 4, 5, start, end)
    grow4 = Grow(1, 2, 3, 4, 5, start2, end)

    assert grow3 != grow4
Exemple #5
0
def write_grow(conn, grow: Grow) -> Grow:
    sql = "INSERT INTO `grows` (recipe_id, start_datetime, estimated_end_datetime, is_finished, all_fields_complete, current_phase, is_new_recipe, tag_set, nutrients, weekly_reps, pruning_date_1, pruning_date_2) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)"
    cursor = conn.cursor()
    cursor.execute(
        sql,
        (
            grow.recipe_id,
            grow.start_datetime,
            grow.estimated_end_datetime,
            grow.is_finished,
            grow.all_fields_complete,
            grow.current_phase,
            grow.is_new_recipe,
            grow.tag_set,
            grow.nutrients,
            # explicitly set this to None if it's not a valid number (0 isn't valid for this field either)
            grow.weekly_reps if grow.weekly_reps else None,
            grow.pruning_date_1,
            grow.pruning_date_2,
        ),
    )
    # return the id since it's created dynamically on insert by AUTO_INCREMENT
    grow_id = cursor.lastrowid
    grow.grow_id = grow_id
    cursor.close()

    return grow
Exemple #6
0
def test_to_json():
    start5 = datetime.utcnow() + timedelta(0, 4)
    start5 -= timedelta(microseconds=start5.microsecond)
    end = start5 + timedelta(0, 2)
    grow_to_json = Grow(1, 2, 3, 4, 5, start5, end)
    assert grow_to_json.to_json() == {
        "room_id": 1,
        "rack_id": 2,
        "shelf_id": 3,
        "recipe_id": 4,
        "recipe_phase_num": 5,
        "start_datetime": start5.replace(
            microsecond=0
        ).isoformat(),
        "end_datetime": end.replace(microsecond=0).isoformat(),
    }
Exemple #7
0
def test_create_grow_from_json_string_format():
    start = datetime.utcnow() + timedelta(0, 3)  # 3 seconds from now
    start -= timedelta(microseconds=start.microsecond)
    end = start + timedelta(0, 2)  # 5 seconds from now

    grow = Grow.from_json(
        {
            "room_id": 1,
            "rack_id": 2,
            "shelf_id": 3,
            "recipe_id": 4,
            "recipe_phase_num": 5,
            "start_datetime": start.strftime("%Y-%m-%d %H:%M:%S"),
            "end_datetime": end.strftime("%Y-%m-%d %H:%M:%S"),
        }
    )

    assert grow.room_id == 1
    assert grow.rack_id == 2
    assert grow.shelf_id == 3
    assert grow.recipe_id == 4
    assert grow.recipe_phase_num == 5
    # needs to round because calendar.timegm is converting to seconds losing microsecond precision
    assert grow.start_datetime == start
    assert grow.end_datetime == end
    def return_all_entities_listener(message) -> None:
        print("Received message in entities_listener:", message)
        assert "rooms" in message
        assert "racks" in message
        assert "shelves" in message
        assert "grows" in message
        assert "recipes" in message
        assert "recipe_phases" in message

        found_rooms = [Room.from_json(r) for r in message["rooms"]]
        found_racks = [Rack.from_json(r) for r in message["racks"]]
        found_shelves = [Shelf.from_json(s) for s in message["shelves"]]
        found_grows = [Grow.from_json(g) for g in message["grows"]]
        found_recipes = [Recipe.from_json(r) for r in message["recipes"]]
        found_recipe_phases = [
            RecipePhase.from_json(rp) for rp in message["recipe_phases"]
        ]

        assert collections.Counter(found_rooms) == collections.Counter(rooms)
        assert collections.Counter(found_racks) == collections.Counter(racks)
        assert collections.Counter(found_shelves) == collections.Counter(
            shelves)
        assert collections.Counter(found_grows) == collections.Counter(grows)
        assert collections.Counter(found_recipes) == collections.Counter(
            recipes)
        assert collections.Counter(found_recipe_phases) == collections.Counter(
            recipe_phases)

        flag.append(True)
    def return_all_entities_listener(message) -> None:
        message_dict = json.loads(message)
        print("Received message in entities_listener:", message_dict)
        assert "rooms" in message_dict
        assert "racks" in message_dict
        assert "shelves" in message_dict
        assert "grows" in message_dict
        assert "grow_phases" in message_dict
        assert "recipes" in message_dict
        assert "recipe_phases" in message_dict
        found_rooms = [Room.from_json(r) for r in message_dict["rooms"]]
        found_racks = [Rack.from_json(r) for r in message_dict["racks"]]
        found_shelves = [Shelf.from_json(s) for s in message_dict["shelves"]]
        found_grows = [Grow.from_json(g) for g in message_dict["grows"]]
        found_grow_phases = [
            GrowPhase.from_json(g) for g in message_dict["grow_phases"]
        ]
        found_recipes = [Recipe.from_json(r) for r in message_dict["recipes"]]
        found_recipe_phases = [
            RecipePhase.from_json(rp) for rp in message_dict["recipe_phases"]
        ]

        assert collections.Counter(found_rooms) == collections.Counter(rooms)
        assert collections.Counter(found_racks) == collections.Counter(racks)
        assert collections.Counter(found_shelves) == collections.Counter(
            shelves
        )
        assert len(found_grows) > 0
        x = len(found_grows)
        assert (
            found_grows[x - 1].start_datetime.strftime("%Y-%m-%d %H:%M:%S")
            == start
        )
        assert (
            found_grows[x - 1].estimated_end_datetime.strftime(
                "%Y-%m-%d %H:%M:%S"
            )
            == end
        )
        i = len(found_grow_phases)
        assert (
            found_grow_phases[i - 1].phase_start_datetime.strftime(
                "%Y-%m-%d %H:%M:%S"
            )
            == start
        )
        assert (
            found_grow_phases[i - 1].phase_end_datetime.strftime(
                "%Y-%m-%d %H:%M:%S"
            )
            == end
        )
        j = len(found_recipes)
        assert found_recipe_phases[j - 1].power_level == p_level
        assert found_recipe_phases[j - 1].red_level == r_level
        assert found_recipe_phases[j - 1].blue_level == b_level

        flag.append(True)
        grow.append(found_grows[0].grow_id)
Exemple #10
0
def schedule_grow_for_shelf(grow: Grow, power_level: int, red_level: int,
                            blue_level: int) -> None:
    config = AppConfig(
    )  # no arguments needed because it's a singleton instance
    shelf_grow_dict = {
        "grow": grow.to_json(),
        "power_level": power_level,
        "red_level": red_level,
        "blue_level": blue_level,
    }
    config.sio.emit("set_lights_for_grow", shelf_grow_dict)
    print("Event emitted from socketio obj")
Exemple #11
0
def read_incomplete_grows(conn) -> List[Grow]:
    sql = "SELECT grow_id, recipe_id, start_datetime, estimated_end_datetime, is_finished, all_fields_complete, olcc_number, current_phase, is_new_recipe, tag_set, nutrients, weekly_reps, pruning_date_1, pruning_date_2, harvest_weight, trim_weight, dry_weight, notes FROM grows WHERE is_finished = true AND all_fields_complete = false"

    with conn.cursor() as cursor:
        cursor.execute(sql)
        all_grows = cursor.fetchall()
        print("all_grows", all_grows)
        found_grows: List[Grow] = [
            Grow(
                grow_id,
                rid,
                sd,
                ed,
                fin,
                comp,
                olcc,
                cp,
                inr,
                ts,
                nts,
                wr,
                pd1,
                pd2,
                hw,
                tw,
                dw,
                notes,
            ) for (
                grow_id,
                rid,
                sd,
                ed,
                fin,
                comp,
                olcc,
                cp,
                inr,
                ts,
                nts,
                wr,
                pd1,
                pd2,
                hw,
                tw,
                dw,
                notes,
            ) in all_grows
        ]

        cursor.close()
        return found_grows
Exemple #12
0
def read_grow(conn, grow_id: int) -> Optional[Grow]:
    sql = "SELECT grow_id, recipe_id, start_datetime, estimated_end_datetime, is_finished, all_fields_complete, olcc_number, current_phase, is_new_recipe, tag_set, nutrients, weekly_reps, pruning_date_1, pruning_date_2, harvest_weight, trim_weight, dry_weight, notes FROM grows WHERE grow_id = %s"

    with conn.cursor() as cursor:
        cursor.execute(sql, (grow_id))
        found_grow = cursor.fetchone()
        grow: Optional[Grow] = None
        if found_grow is not None:
            (
                grow_id,
                recipe_id,
                start_datetime,
                estimated_end_datetime,
                is_finished,
                all_fields_complete,
                olcc_number,
                current_phase,
                is_new_recipe,
                tag_set,
                nutrients,
                weekly_reps,
                pruning_date_1,
                pruning_date_2,
                harvest_weight,
                trim_weight,
                dry_weight,
                notes,
            ) = found_grow
            grow = Grow(
                grow_id,
                recipe_id,
                start_datetime,
                estimated_end_datetime,
                is_finished,
                all_fields_complete,
                olcc_number,
                current_phase,
                is_new_recipe,
                tag_set,
                nutrients,
                weekly_reps,
                pruning_date_1,
                pruning_date_2,
                harvest_weight,
                trim_weight,
                dry_weight,
                notes,
            )

        cursor.close()
        return grow
Exemple #13
0
def test_create_grow():
    start = datetime.utcnow() + timedelta(0, 3)  # 3 seconds from now
    start -= timedelta(microseconds=start.microsecond)
    end = start + timedelta(0, 2)  # 5 seconds from now

    grow = Grow(1, 2, 3, 4, 5, start, end)

    assert grow.room_id == 1
    assert grow.rack_id == 2
    assert grow.shelf_id == 3
    assert grow.recipe_id == 4
    assert grow.recipe_phase_num == 5
    assert grow.start_datetime == start
    assert grow.end_datetime == end
def _test_send_shelf_grow(sio, room_id, rack_id, shelf_id, recipe_phases):
    assert len(recipe_phases) == 1  # only support 1 phase for now

    print("Sending shelf grow")
    start = datetime.utcnow() + timedelta(0, 3)  # 3 seconds from now
    end = start + timedelta(0, 2)  # 5 seconds from now

    start_time = start.strftime("%Y-%m-%d %H:%M:%S")
    end_time = end.strftime("%Y-%m-%d %H:%M:%S")

    shelf_grows = []
    for rp in recipe_phases:

        shelf_grow = Grow.from_json({
            "room_id": room_id,
            "rack_id": rack_id,
            "shelf_id": shelf_id,
            "recipe_id": rp.recipe_id,
            "recipe_phase_num": rp.recipe_phase_num,
            "start_datetime": start_time,
            "end_datetime": end_time,
        })
        shelf_grows.append(shelf_grow)

    flag = []

    @sio.on("start_grows_for_shelves_succeeded")
    def start_grows_for_shelves_succeeded(message) -> None:
        assert "succeeded" in message
        assert message["succeeded"]
        flag.append(True)

    @sio.on("set_lights_for_grow")
    def set_lights_for_grow(message) -> None:
        assert "grow" in message
        assert "power_level" in message
        assert "red_level" in message
        assert "blue_level" in message
        flag.append(True)

    sio.emit("start_grows_for_shelves",
             {"grows": [s.to_json() for s in shelf_grows]})
    wait_for_event(sio, flag, 1, 5, "test_start_grows_for_shelves")

    for i in range(len(shelf_grows)):
        wait_for_event(sio, flag, i + 2, 5, "test_set_lights_for_grow_job")

    print("test send shelf grow passed")
    return shelf_grow
Exemple #15
0
def read_shelf_current_grows(conn, shelf_id) -> List[Grow]:
    sql = "SELECT room_id, rack_id, shelf_id, recipe_id, recipe_phase_num, start_datetime, end_datetime FROM grows WHERE shelf_id=%s AND end_datetime > %s"

    utc_now = datetime.utcnow()
    with conn.cursor() as cursor:
        cursor.execute(sql, (shelf_id, utc_now))
        all_grows = cursor.fetchall()
        print("all_grows", all_grows)
        found_shelf_grows: List[Grow] = [
            Grow(room_id, rack_id, sid, rid, rpn, sd, ed)
            for (room_id, rack_id, sid, rid, rpn, sd, ed) in all_grows
        ]

        cursor.close()
        return found_shelf_grows
    def start_grows_for_shelves(message) -> None:
        print("message:", message)
        logging.debug("message sent to post_room_schedule:", message)
        if "grows" not in message:
            send_message_to_namespace_if_specified(
                socketio,
                message,
                "start_grow_for_shelf_succeeded",
                {
                    "succeeded": False,
                    "reason": "Grow not included"
                },
            )

        grows: List[Grow] = [Grow.from_json(g) for g in message["grows"]]

        for grow in grows:
            (
                power_level,
                red_level,
                blue_level,
            ) = app_config.db.read_lights_from_recipe_phase(
                grow.recipe_id, grow.recipe_phase_num)

            app_config.scheduler.add_job(
                schedule_grow_for_shelf,
                "interval",
                start_date=grow.start_datetime,
                end_date=grow.end_datetime,
                args=[grow, power_level, red_level, blue_level],
                id=grow.to_job_id(),
                minutes=5,
            )

        # TODO (lww515): What to do after harvest is finished?

        # write grows to db
        app_config.db.write_grows(grows)

        logging.debug("start_grow_for_shelf succeeded!")
        send_message_to_namespace_if_specified(
            socketio, message, "start_grows_for_shelves_succeeded",
            {"succeeded": True})
        print("Grow started successfully, event emitted")
Exemple #17
0
def test__str__():
    start6 = datetime.utcnow() + timedelta(0, 4)
    end = start6 + timedelta(0, 2)
    grow = Grow(1, 2, 3, 4, 5, start6, end)
    grow2 = Grow(1, 2, 3, 4, 5, start6, end)
    assert str(grow2) == str(grow)
    def start_grows_for_shelves(message) -> None:
        db_conn = None
        try:
            validation_succeeded, failure_reason = validate_start_grows_for_shelves(
                app_config, message)
            print(
                "Start_grows_for_shelves validation:",
                validation_succeeded,
                failure_reason,
            )
            if not validation_succeeded:
                send_message_to_namespace_if_specified(
                    socketio,
                    message,
                    "start_grows_for_shelves_succeeded",
                    {
                        "succeeded": False,
                        "reason": failure_reason
                    },
                )
                print("Failed validation!")
                return

            db_conn = app_config.db._new_transaction()
            is_new_recipe: bool = bool(message["is_new_recipe"])
            recipe_id: Optional[int] = None
            end_date_str: Optional[str] = None
            end_date: Optional[datetime] = None
            if is_new_recipe:
                # create the recipe and the recipe phases before creating the grow
                recipe_name = (message["recipe_name"]
                               if "recipe_name" in message else None)
                recipe_no_id: Recipe = Recipe(None, recipe_name)
                recipe: Recipe = app_config.db.write_recipe(
                    db_conn, recipe_no_id)
                recipe_id = recipe.recipe_id

                light_configurations: List[Any] = message["grow_phases"]
                end_date_str = message["end_date"]
                end_date = iso8601_string_to_datetime(end_date_str)

                recipe_phases: List[
                    RecipePhase] = create_recipe_phases_from_light_configurations(
                        light_configurations, recipe_id, end_date)
                print("recipe_phases:", recipe_phases)
                # write the recipe phases to database
                app_config.db.write_recipe_phases(db_conn, recipe_phases)

            else:
                recipe_id = message["template_recipe_id"]

            # create the grow first so we can read the grow_id
            grow_start_date: datetime = iso8601_string_to_datetime(
                message["grow_phases"][0]["start_date"])
            grow_estimated_end_date: datetime = iso8601_string_to_datetime(
                message["end_date"])

            current_phase: int = 0

            # tag set, nutrients, weekly reps, and pruning dates are optional fields
            tag_set: Optional[str] = message.get("tag_set")
            nutrients: Optional[str] = message.get("nutrients")

            weekly_reps: Optional[int] = message.get("weekly_reps")
            pruning_date_1: Optional[datetime] = iso8601_string_to_datetime(
                message["pruning_date_1"]) if message.get(
                    "pruning_date_1") else None
            pruning_date_2: Optional[datetime] = iso8601_string_to_datetime(
                message["pruning_date_2"]) if message.get(
                    "pruning_date_2") else None

            grow_without_id: Grow = Grow(
                None,
                recipe_id,
                grow_start_date,
                grow_estimated_end_date,
                False,
                False,
                None,
                current_phase,
                is_new_recipe,
                tag_set,
                nutrients,
                weekly_reps,
                pruning_date_1,
                pruning_date_2,
                None,
                None,
                None,
                None,
            )

            grow: Grow = app_config.db.write_grow(db_conn, grow_without_id)

            light_configurations = message["grow_phases"]
            end_date_str = message["end_date"]
            end_date = iso8601_string_to_datetime(end_date_str)
            grow_phases: List[
                GrowPhase] = create_grow_phases_from_light_configurations(
                    light_configurations, grow.grow_id, recipe_id, end_date)

            shelf_grows: List[ShelfGrow] = []
            for shelf_data in message["shelves"]:
                shelf_data["grow_id"] = grow.grow_id
                shelf_grow = ShelfGrow.from_json(shelf_data)
                shelf_grows.append(shelf_grow)

            # only schedule 1st grow phase, but write all grow phases to DB
            first_grow_phase: GrowPhase = grow_phases[0]
            (
                power_level,
                red_level,
                blue_level,
            ) = app_config.db.read_lights_from_recipe_phase(
                db_conn,
                first_grow_phase.recipe_id,
                first_grow_phase.recipe_phase_num,
            )

            # write grow phases and shelf grows to db
            app_config.db.write_grow_phases(db_conn, grow_phases)
            app_config.db.write_shelf_grows(db_conn, shelf_grows)

            # enqueue the job to the job scheduler
            client_schedule_job(
                shelf_grows,
                first_grow_phase,
                power_level,
                red_level,
                blue_level,
            )

            print("start_grow_for_shelf succeeded!")

            # commit the transaction
            db_conn.commit()

            send_message_to_namespace_if_specified(
                socketio,
                message,
                "start_grows_for_shelves_succeeded",
                {
                    "succeeded": True,
                    "grow": grow.to_json()
                },
            )
            print("Grow started successfully, event emitted")
        except Exception as e:
            exception_str: str = str(e)
            print(
                "Error with starting grow:",
                message,
                exception_str,
                traceback.format_exc(),
            )
            # rollback connection if it exists and report error back to client
            if db_conn:
                db_conn.rollback()

            send_message_to_namespace_if_specified(
                socketio,
                message,
                "start_grows_for_shelves_succeeded",
                {
                    "succeeded":
                    False,
                    "reason":
                    "Unexpected error. Please document the conditions that lead to this error. {}"
                    .format(exception_str),
                },
            )
        finally:
            if db_conn:
                # close the db connection if it's defined
                db_conn.close()
    def harvest_grow(message) -> None:
        db_conn = None
        try:
            if "grow" not in message:
                send_message_to_namespace_if_specified(
                    socketio,
                    message,
                    "harvest_grow_response",
                    {
                        "succeeded": False,
                        "reason": "Grow not included"
                    },
                )
                return

            grow_json = message["grow"]
            grow_id: int = int(grow_json["grow_id"])
            grow: Optional[Grow] = app_config.db.read_grow(grow_id)
            if not grow:
                send_message_to_namespace_if_specified(
                    socketio,
                    message,
                    "harvest_grow_response",
                    {
                        "succeeded": False,
                        "reason": "Grow not found"
                    },
                )
                return

            found_grow_json = grow.to_json()
            # read all data sent in by user, and mark it on grow
            for key in grow_json:
                found_grow_json[key] = grow_json[key]

            updated_grow: Grow = Grow.from_json(found_grow_json)

            # harvest the grow by marking it as complete
            harvest_datetime: datetime = datetime.utcnow()
            updated_grow.estimated_end_datetime = harvest_datetime
            updated_grow.is_finished = True

            # end grow
            db_conn = app_config.db._new_transaction()
            app_config.db.update_grow_harvest_data(db_conn, updated_grow)

            # read current grow phase
            current_phase: int = updated_grow.current_phase
            current_grow_phase: Optional[
                GrowPhase] = app_config.db.read_grow_phase(
                    db_conn, updated_grow.grow_id, current_phase)
            if not current_grow_phase:
                send_message_to_namespace_if_specified(
                    socketio,
                    message,
                    "harvest_grow_response",
                    {
                        "succeeded":
                        False,
                        "reason":
                        "Current grow phase {} not found".format(
                            current_phase),
                    },
                )
                return

            # remove ongoing job so that it stops running
            client_remove_job(current_grow_phase)

            # update current recipe phase to have proper end date
            print("Updating grow_phase:", current_grow_phase)
            app_config.db.end_grow_phase(db_conn, current_grow_phase,
                                         harvest_datetime)

            # commit the transaction
            db_conn.commit()
            send_message_to_namespace_if_specified(socketio, message,
                                                   "harvest_grow_response",
                                                   {"succeeded": True})
        except Exception as e:
            exception_str: str = str(e)
            print(
                "Error with harvesting grow:",
                message,
                exception_str,
                traceback.format_exc(),
            )
            # rollback connection if it exists and report error back to client
            if db_conn:
                db_conn.rollback()

            send_message_to_namespace_if_specified(
                socketio,
                message,
                "harvest_grow_response",
                {
                    "succeeded":
                    False,
                    "reason":
                    "Unexpected error. Please document the conditions that lead to this error. {}"
                    .format(exception_str),
                },
            )
        finally:
            if db_conn:
                # close the db connection if it's defined
                db_conn.close()
    def update_incomplete_grow(message) -> None:
        db_conn = None
        try:
            if "grow" not in message:
                send_message_to_namespace_if_specified(
                    socketio,
                    message,
                    "update_incomplete_grow_response",
                    {
                        "succeeded": False,
                        "reason": "Grow not included"
                    },
                )
                return

            grow_json = message["grow"]
            grow_id: int = int(grow_json["grow_id"])
            grow: Optional[Grow] = app_config.db.read_grow(grow_id)
            if not grow:
                send_message_to_namespace_if_specified(
                    socketio,
                    message,
                    "update_incomplete_grow_response",
                    {
                        "succeeded": False,
                        "reason": "Grow not found"
                    },
                )
                return

            found_grow_json = grow.to_json()
            # read all data sent in by user, and mark it on grow
            for key in grow_json:
                found_grow_json[key] = grow_json[key]

            updated_grow: Grow = Grow.from_json(found_grow_json)

            # harvest the grow by marking it as complete
            harvest_datetime: datetime = datetime.utcnow()
            updated_grow.estimated_end_datetime = harvest_datetime
            updated_grow.is_finished = True
            print(
                "grow_json to harvest in update_incomplete_grow:",
                grow_json,
                updated_grow,
                flush=True,
            )

            # end grow
            db_conn = app_config.db._new_transaction()
            app_config.db.update_grow_harvest_data(db_conn, updated_grow)

            # commit changes
            db_conn.commit()
            send_message_to_namespace_if_specified(
                socketio,
                message,
                "update_incomplete_grow_response",
                {"succeeded": True},
            )
        except Exception as e:
            exception_str: str = str(e)
            print(
                "Error with updating incomplete grow:",
                message,
                exception_str,
                traceback.format_exc(),
            )
            # rollback connection if it exists and report error back to client
            if db_conn:
                db_conn.rollback()

            send_message_to_namespace_if_specified(
                socketio,
                message,
                "update_incomplete_grow_response",
                {
                    "succeeded":
                    False,
                    "reason":
                    "Unexpected error. Please document the conditions that lead to this error. {}"
                    .format(exception_str),
                },
            )
        finally:
            if db_conn:
                # close the db connection if it's defined
                db_conn.close()