Skip to content

Commit 68320ad

Browse files
AlejandraGalanmarioncottardggsdcalejandrarr1601
authored
Release/v1.3.6 (#751)
* Add get role for other roles (#720) * add get role for other roles * Simplify test * add service user * Feature/kpis dag (#715) * KPIs DAG * Added migration * Small changes * Small fix * Fix airflow interactions * Small change for databricks compatibility * Small fix in relaunch execution endpoint * Make schema_kpis not mandatory * KPIs checks * Unit tests * Try fix cornflow-client tests by uninstalling cc before reinstalling it * Try fix cornflow-server tests * Small change * Accept old url as well * Fix number of permissions * Bump version cornflow client * Bump version cornflow server * Added kpis_to_dict method * Bump version 1.3.4rc2 * fix * bump server * Save kpis jsonschema in DB, new flag for data-checks executions * Missing change * Added migration, small change * Requirement version for testing * Fix unit tests * Small fix * Fixed requirements.txt * Fix requirements * bump client * bump cornflow * Distinction warning/error in solution checks * Fixed rostering schema, fixed bug when solution_checks_schema is empty * Try fix * bump cornflow client * bump cornflow client * bump cornflow * bump cornflow * Fixed query * Return kpis schemas * bump cornflow client * bump cornflow * Small fix * Adapted to review * Feature/execution files (#722) * Initial version * Updated requirements * Print traceback * Small fix * Small fix * Truncate sheet names * Small fix * Allow stringIO * Small change * Small fix * First unit tests, small changes * Small changes * More changes, unit tests * Dags requirements * Pandas version * Fixed server test * Fixed solve_model_dag solve() * Try fix licenses test * Fixed import * Small change in operations order * bump cornflow client * bump cornflow * Added unit test * Removed ranges in requirements * Fixed bug with update_dag_registry --------- Co-authored-by: Alejandra Galán <alejandra.galan@baobabsoluciones.es> --------- Co-authored-by: Alejandra Galán <alejandra.galan@baobabsoluciones.es> * Feature/remove pandas dependency (#725) * Build excel with openpyxl * Offer both frontend styles * Updated openpyxl, added pandas to requirements-dev * Update cornflow requirements * Small fix * Dump values to json * Jsonify only non-primitive types * Restored requirements.txt * Try improve coverage * More coverage * Small simplification * Added dummy role (#726) Co-authored-by: Alejandra Galán <99278460+AlejandraGalan@users.noreply.github.com> * Feature/update develop (#747) * Bump cornflow-client version on cornflow-server requirements * Bump version * Bump version * Bump version * Bump cornflow-client version on cornflow-server * Bump version * Bump version cornflow client * Bump version cornflow server * Add get role for other roles (#720) (#721) * add get role for other roles * fix * Simplify test * add service user * bump cornflow client * bump cornflow * Release/v1.3.5 (#727) * Add get role for other roles (#720) * add get role for other roles * fix * Simplify test * add service user * Feature/kpis dag (#715) * KPIs DAG * Added migration * Small changes * Small fix * Fix airflow interactions * Small change for databricks compatibility * Small fix in relaunch execution endpoint * Make schema_kpis not mandatory * KPIs checks * Unit tests * Try fix cornflow-client tests by uninstalling cc before reinstalling it * Try fix cornflow-server tests * Small change * Accept old url as well * Fix number of permissions * Bump version cornflow client * Bump version cornflow server * Added kpis_to_dict method * Bump version 1.3.4rc2 * fix * bump server * Save kpis jsonschema in DB, new flag for data-checks executions * Missing change * Added migration, small change * Requirement version for testing * Fix unit tests * Small fix * Fixed requirements.txt * Fix requirements * bump client * bump cornflow * Distinction warning/error in solution checks * Fixed rostering schema, fixed bug when solution_checks_schema is empty * Try fix * bump cornflow client * bump cornflow client * bump cornflow * bump cornflow * Fixed query * Return kpis schemas * bump cornflow client * bump cornflow * Small fix * Adapted to review * Feature/execution files (#722) * Initial version * Updated requirements * Print traceback * Small fix * Small fix * Truncate sheet names * Small fix * Allow stringIO * Small change * Small fix * First unit tests, small changes * Small changes * More changes, unit tests * Dags requirements * Pandas version * Fixed server test * Fixed solve_model_dag solve() * Try fix licenses test * Fixed import * Small change in operations order * bump cornflow client * bump cornflow * Added unit test * Removed ranges in requirements * Fixed bug with update_dag_registry --------- Co-authored-by: Alejandra Galán <alejandra.galan@baobabsoluciones.es> --------- Co-authored-by: Alejandra Galán <alejandra.galan@baobabsoluciones.es> * Feature/remove pandas dependency (#725) * Build excel with openpyxl * Offer both frontend styles * Updated openpyxl, added pandas to requirements-dev * Update cornflow requirements * Small fix * Dump values to json * Jsonify only non-primitive types * Restored requirements.txt * Try improve coverage * More coverage * Small simplification * Added dummy role (#726) Co-authored-by: Alejandra Galán <99278460+AlejandraGalan@users.noreply.github.com> --------- Co-authored-by: marioncottard <86973971+marioncottard@users.noreply.github.com> * bump client * bump cornflow --------- Co-authored-by: Guillermo González-Santander de la Cruz <guillermo.gonzalez@baobabsoluciones.es> Co-authored-by: marioncottard <86973971+marioncottard@users.noreply.github.com> * update requirements.txt (#750) * Avoid loading data column in execution list endpoint (#749) * Avoid loading data column in execution list endpoint The GET /executions list query was loading the full data JSON column from the database on every row, even though it was never included in the list response. This caused unnecessary memory and I/O overhead proportional to the size of each execution's solution payload. Changes: - Defer loading of data and checks columns in ExecutionModel.get_all_objects - Remove indicators field from the list response schema - Update list test fixtures to reflect the new response shape - Add tests that verify data is not loaded in the list query * Clean up execution list data loading tests Consolidate the two test classes into a single TestExecutionListDataLoading class and remove process-internal references from docstrings and assertion messages. * bump cornflow client * bump cornflow * delete --------- Co-authored-by: marioncottard <86973971+marioncottard@users.noreply.github.com> Co-authored-by: Guillermo González-Santander de la Cruz <guillermo.gonzalez@baobabsoluciones.es> Co-authored-by: Alejandra Reliegos <alejandra.reliegos@baobabsoluciones.es>
1 parent d8c5765 commit 68320ad

14 files changed

Lines changed: 184 additions & 28 deletions

File tree

.claude/settings.json

Lines changed: 0 additions & 11 deletions
This file was deleted.

cornflow-dags/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
cornflow-client==1.3.5
1+
cornflow-client==1.3.6rc1
22
orloge==0.17.2
33
PuLP==2.9.0
44
pandas==2.2.0

cornflow-server/Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# VERSION 1.3.5
1+
# VERSION 1.3.6rc1
22
# AUTHOR: sistemas@baobabsoluciones.es
33

44
FROM python:3.12-slim
@@ -9,7 +9,7 @@ ENV DEBIAN_FRONTEND=noninteractive
99
ENV TERM=linux
1010

1111
# CORNFLOW vars
12-
ARG CORNFLOW_VERSION=1.3.5
12+
ARG CORNFLOW_VERSION=1.3.6rc1
1313

1414
# install linux pkg
1515
RUN apt update -y && apt-get install -y --no-install-recommends \

cornflow-server/airflow_config/Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# AIRFLOW VERSION 2.9.1
22
# AUTHOR: cornflow@baobabsoluciones.es
33
# DESCRIPTION: Airflow 2.9.1 image personalized for use with Cornflow (from baobabsoluciones/pysolver image)
4-
# baobab code version is 1.3.5
4+
# baobab code version is 1.3.6rc1
55

66
FROM baobabsoluciones/pysolver:1.1
77
LABEL maintainer="cornflow@baobabsoluciones"
@@ -20,7 +20,7 @@ ENV AIRFLOW_HOME=${AIRFLOW_USER_HOME}
2020
# install Airflow and extras: celery,postgres and redis
2121
RUN pip install "apache-airflow[amazon,celery,google,postgres,redis,sendgrid]==${AIRFLOW_VERSION}" --constraint "${CONSTRAINT_URL}"
2222
# We add these overruns due to security reasons as suggested here: https://airflow.apache.org/docs/apache-airflow/stable/installation/installing-from-pypi.html#upgrading-and-installing-dependencies-including-providers
23-
RUN pip install "apache-airflow[amazon,celery,google,postgres,redis,sendgrid]==${AIRFLOW_VERSION}" "cryptography==42.0.5" "requests==2.31.0" "Werkzeug==2.3.8"
23+
RUN pip install "apache-airflow[amazon,celery,google,postgres,redis,sendgrid]==${AIRFLOW_VERSION}" "cryptography==48.0.1" "requests==2.31.0" "Werkzeug==2.3.8"
2424

2525
# copy init script and config to container
2626
COPY scripts ${AIRFLOW_HOME}/scripts

cornflow-server/cornflow/models/execution.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import hashlib
88
from sqlalchemy.dialects.postgresql import JSON, TEXT
99
from sqlalchemy import desc, or_
10+
from sqlalchemy.orm import defer
1011
from sqlalchemy.sql.expression import false
1112

1213
# Imports from internal modules
@@ -184,7 +185,7 @@ def get_all_objects(
184185
:return: The objects
185186
:rtype: list(:class:`BaseDataModel`)
186187
"""
187-
query = cls.query.filter(cls.deleted_at == None)
188+
query = cls.query.options(defer(cls.data), defer(cls.checks)).filter(cls.deleted_at == None)
188189
user_access = int(current_app.config["USER_ACCESS_ALL_OBJECTS"])
189190
if (
190191
user is not None

cornflow-server/cornflow/schemas/execution.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,11 @@ class ExecutionDetailsWithIndicatorsAndLogResponse(
150150
):
151151
log = fields.Nested(BasicLogSchema, attribute="log_json")
152152

153+
class Meta:
154+
# Exclude 'indicators' from the list endpoint schema: data is deferred in
155+
# get_all_objects, so accessing obj.data here would trigger N+1 lazy loads.
156+
exclude = ("indicators",)
157+
153158

154159
class ExecutionStatusEndpointResponse(Schema):
155160
id = fields.Str()

cornflow-server/cornflow/shared/const.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
DATABRICKS_BACKEND = 2
88

99

10-
CORNFLOW_VERSION = "1.3.5"
10+
CORNFLOW_VERSION = "1.3.6rc1"
1111
INTERNAL_TOKEN_ISSUER = "cornflow"
1212

1313
# endpoints responses for health check

cornflow-server/cornflow/tests/base_test_execution.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ def load_file_fk(_file):
104104
"log",
105105
"instance_id",
106106
"name",
107-
"indicators",
108107
"username",
109108
"first_name",
110109
"last_name",

cornflow-server/cornflow/tests/unit/test_executions.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from unittest.mock import patch
1313

1414
from flask import current_app
15+
from sqlalchemy import event, inspect as sa_inspect
1516
from cornflow.app import create_app
1617
from cornflow.tests import base_test_execution
1718

@@ -30,9 +31,11 @@
3031
VIEWER_ROLE,
3132
)
3233
from cornflow.tests.const import (
34+
DAG_URL,
3335
EXECUTION_FILES_CLEANUP_URL,
3436
EXECUTION_FILES_URL,
3537
EXECUTION_PATH,
38+
EXECUTION_SOLUTION_PATH,
3639
EXECUTION_URL_NORUN,
3740
INSTANCE_PATH,
3841
INSTANCE_URL,
@@ -492,3 +495,162 @@ def test_cleanup_is_service_only(self):
492495
self.assertEqual("0 files were deleted.", response.json["message"])
493496

494497
# endregion
498+
499+
500+
class TestExecutionListDataLoading(CustomTestCase):
501+
"""
502+
Tests that the execution list endpoint does not load heavy columns from the
503+
database and returns only the expected basic fields.
504+
"""
505+
506+
def setUp(self):
507+
super().setUp()
508+
# Load instance fixture and create a parent instance
509+
with open(INSTANCE_PATH) as f:
510+
instance_payload = json.load(f)
511+
self.instance_id = self.create_new_row(
512+
INSTANCE_URL, InstanceModel, instance_payload
513+
)
514+
515+
# Load execution fixture and create the execution (without triggering a run)
516+
with open(EXECUTION_PATH) as f:
517+
execution_payload = json.load(f)
518+
execution_payload["instance_id"] = self.instance_id
519+
self.execution_id = self.create_new_row(
520+
EXECUTION_URL_NORUN, ExecutionModel, execution_payload
521+
)
522+
523+
# Use a service user to push solution data into the execution via the DAG
524+
# endpoint so that the `data` column is non-NULL in the database.
525+
service_token = self.create_service_user()
526+
with open(EXECUTION_SOLUTION_PATH) as f:
527+
solution_data = json.load(f)
528+
self.update_row(
529+
url=DAG_URL + self.execution_id + "/",
530+
change={"data": solution_data},
531+
payload_to_check={},
532+
check_payload=False,
533+
token=service_token,
534+
)
535+
536+
def _capture_queries_for_get_all_objects(self):
537+
captured_queries = []
538+
539+
def _listener(conn, cursor, statement, parameters, context, executemany):
540+
captured_queries.append(statement)
541+
542+
engine = db.engine
543+
event.listen(engine, "before_cursor_execute", _listener)
544+
try:
545+
executions = ExecutionModel.get_all_objects(user=self.user)
546+
finally:
547+
event.remove(engine, "before_cursor_execute", _listener)
548+
549+
return executions, captured_queries
550+
551+
def test_data_is_deferred_in_list_via_sqlalchemy_inspect(self):
552+
executions = ExecutionModel.get_all_objects(user=self.user)
553+
554+
self.assertGreater(
555+
len(executions),
556+
0,
557+
"Expected at least one execution to be returned by get_all_objects",
558+
)
559+
560+
for execution in executions:
561+
state = sa_inspect(execution)
562+
self.assertIn(
563+
"data",
564+
state.unloaded,
565+
"'data' should be deferred (not loaded eagerly) in the list query.",
566+
)
567+
568+
def test_data_is_not_in_select_via_sql_interception(self):
569+
executions, captured_queries = self._capture_queries_for_get_all_objects()
570+
571+
self.assertGreater(
572+
len(executions),
573+
0,
574+
"Expected at least one execution to be returned by get_all_objects",
575+
)
576+
577+
self.assertTrue(
578+
len(captured_queries) > 0,
579+
"No SQL queries were captured; the event listener may not have fired.",
580+
)
581+
582+
data_in_query = any(
583+
'"data"' in q or " data," in q.lower() or " data " in q.lower()
584+
for q in captured_queries
585+
)
586+
self.assertFalse(
587+
data_in_query,
588+
"The SELECT generated by get_all_objects must not include the 'data' column. "
589+
"Captured queries: " + str(captured_queries),
590+
)
591+
592+
def test_list_endpoint_does_not_return_indicators(self):
593+
from cornflow.tests.const import EXECUTION_URL
594+
595+
response = self.client.get(
596+
EXECUTION_URL,
597+
follow_redirects=True,
598+
headers=self.get_header_with_auth(self.token),
599+
)
600+
601+
self.assertEqual(
602+
200,
603+
response.status_code,
604+
f"GET /execution/ returned unexpected status {response.status_code}",
605+
)
606+
607+
items = response.json
608+
self.assertIsInstance(items, list)
609+
self.assertGreater(len(items), 0, "Expected at least one execution in the list")
610+
611+
for item in items:
612+
self.assertNotIn(
613+
"indicators",
614+
item,
615+
"'indicators' must not appear in execution list items.",
616+
)
617+
618+
def test_list_endpoint_returns_basic_fields(self):
619+
from cornflow.tests.const import EXECUTION_URL
620+
621+
response = self.client.get(
622+
EXECUTION_URL,
623+
follow_redirects=True,
624+
headers=self.get_header_with_auth(self.token),
625+
)
626+
627+
self.assertEqual(200, response.status_code)
628+
629+
items = response.json
630+
self.assertIsInstance(items, list)
631+
self.assertGreater(len(items), 0, "Expected at least one execution in the list")
632+
633+
required_fields = [
634+
"id",
635+
"name",
636+
"description",
637+
"created_at",
638+
"updated_at",
639+
"user_id",
640+
"username",
641+
"data_hash",
642+
"state",
643+
"message",
644+
"config",
645+
"instance_id",
646+
"schema",
647+
"log",
648+
]
649+
650+
for item in items:
651+
for field in required_fields:
652+
self.assertIn(
653+
field,
654+
item,
655+
f"Required field '{field}' is missing from the execution list response.",
656+
)

cornflow-server/requirements.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ alembic==1.9.2
22
apispec==6.3.0
33
cachetools==5.3.3
44
click==8.1.7
5-
cornflow-client==1.3.5
6-
cryptography==46.0.5
5+
cornflow-client==1.3.6rc1
6+
cryptography==48.0.1
77
disposable-email-domains==0.0.162
88
Flask==2.3.2
99
flask-apispec==0.11.4
@@ -23,7 +23,7 @@ ldap3==2.9.1
2323
marshmallow==3.26.2
2424
PuLP==2.9.0
2525
psycopg2==2.9.9
26-
PyJWT==2.12.0
26+
PyJWT==2.13.0
2727
pytups==0.86.2
2828
requests==2.32.4
2929
SQLAlchemy==1.3.21

0 commit comments

Comments
 (0)