Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/tsp_report' into tsp_report
Browse files Browse the repository at this point in the history
  • Loading branch information
pchtsp committed Jul 18, 2024
2 parents ffcef7c + e4bdf9e commit 63363e3
Show file tree
Hide file tree
Showing 15 changed files with 478 additions and 93 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test_cornflow_server.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:
steps:
- uses: actions/checkout@v1
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Copy DAG files
Expand Down
3 changes: 3 additions & 0 deletions cornflow-server/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ RUN pip install "cornflow==${CORNFLOW_VERSION}"
# create folder for logs
RUN mkdir -p /usr/src/app/log

# create folder for object storage
RUN mkdir -p /usr/src/app/static

# create folder for custom ssh keys
RUN mkdir /usr/src/app/.ssh

Expand Down
3 changes: 2 additions & 1 deletion cornflow-server/MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ include MANIFEST.in
include README.rst
include setup.py
include cornflow/migrations/*
include cornflow/migrations/versions/*
include cornflow/migrations/versions/*
include cornflow/static/*
3 changes: 3 additions & 0 deletions cornflow-server/cornflow/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ def create_app(env_name="development", dataconn=None):
:return: the application that is going to be running :class:`Flask`
:rtype: :class:`Flask`
"""
if os.getenv("FLASK_ENV", None) is not None:
env_name = os.getenv("FLASK_ENV")

dictConfig(log_config(app_config[env_name].LOG_LEVEL))

app = Flask(__name__)
Expand Down
8 changes: 7 additions & 1 deletion cornflow-server/cornflow/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class DefaultConfig(object):
FILE_BACKEND = os.getenv("FILE_BACKEND", "local")
UPLOAD_FOLDER = os.getenv(
"UPLOAD_FOLDER",
os.path.abspath(os.path.join(os.path.dirname(__file__), "../static")),
os.path.abspath(os.path.join(os.path.dirname(__file__), "./static")),
)
ALLOWED_EXTENSIONS = os.getenv("ALLOWED_EXTENSIONS", ["pdf", "html"])

Expand Down Expand Up @@ -95,6 +95,7 @@ class Development(DefaultConfig):
""" """

ENV = "development"
UPLOAD_FOLDER = os.getenv("UPLOAD_FOLDER", "/usr/src/app/static")


class Testing(DefaultConfig):
Expand All @@ -114,6 +115,10 @@ class Testing(DefaultConfig):
AIRFLOW_PWD = os.getenv("AIRFLOW_PWD", "admin")
OPEN_DEPLOYMENT = 1
LOG_LEVEL = int(os.getenv("LOG_LEVEL", 10))
UPLOAD_FOLDER = os.getenv(
"UPLOAD_FOLDER",
os.path.abspath(os.path.join(os.path.dirname(__file__), "./static")),
)


class Production(DefaultConfig):
Expand All @@ -126,6 +131,7 @@ class Production(DefaultConfig):
# needs to be on to avoid getting only 500 codes:
# and https://medium.com/@johanesriandy/flask-error-handler-not-working-on-production-mode-3adca4c7385c
PROPAGATE_EXCEPTIONS = True
UPLOAD_FOLDER = os.getenv("UPLOAD_FOLDER", "/usr/src/app/static")


app_config = {"development": Development, "testing": Testing, "production": Production}
40 changes: 35 additions & 5 deletions cornflow-server/cornflow/endpoints/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ def post(self, **kwargs):
the reference_id for the newly created report if successful) and a integer with the HTTP status code
:rtype: Tuple(dict, integer)
"""
execution = ExecutionModel.get_one_object(id=kwargs["execution_id"])

execution = ExecutionModel.get_one_object(idx=kwargs["execution_id"])

if execution is None:
raise ObjectDoesNotExist("The execution does not exist")
Expand Down Expand Up @@ -122,7 +123,7 @@ def post(self, **kwargs):
except Exception as error:
report.delete()
current_app.logger.error(error)
raise FileError
raise FileError(error=str(error))


class ReportDetailsEndpointBase(BaseMetaResource):
Expand Down Expand Up @@ -153,15 +154,18 @@ def get(self, idx):
:rtype: Tuple(dict, integer)
"""
current_app.logger.info(f"User {self.get_user()} gets details of report {idx}")
report = self.get_detail(user_id=self.get_user_id(), idx=idx)
report = self.get_detail(user=self.get_user(), idx=idx)
if report is None:
raise ObjectDoesNotExist

directory, file = report.file_url.split(report.name)
file = f"{report.name}{file}"
directory = directory[:-1]

return send_from_directory(directory, file)
response = send_from_directory(directory, file)
response.headers["File-Description"] = report.description
response.headers["File-Name"] = report.name
return response

@doc(description="Edit a report", tags=["Reports"], inherit=False)
@authenticate(auth_class=Auth())
Expand All @@ -176,7 +180,33 @@ def put(self, idx, **data):
:rtype: Tuple(dict, integer)
"""
current_app.logger.info(f"User {self.get_user()} edits report {idx}")
return self.put_detail(data, user=self.get_user(), idx=idx)

report = self.get_detail(user=self.get_user(), idx=idx)

try:
if report.name != data["name"]:
directory, file = report.file_url.split(report.name)

new_location = (
f"{os.path.join(directory, secure_filename(data['name']))}{file}"
)
old_location = report.file_url

current_app.logger.debug(f"Old location: {old_location}")
current_app.logger.debug(f"New location: {new_location}")

os.rename(old_location, new_location)
data["file_url"] = new_location

except Exception as error:
current_app.logger.error(error)
return {"error": "Error moving file"}, 400

report.update(data)

report.save()

return {"message": "Updated correctly"}, 200

@doc(description="Delete a report", tags=["Reports"], inherit=False)
@authenticate(auth_class=Auth())
Expand Down
4 changes: 4 additions & 0 deletions cornflow-server/cornflow/models/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ def user_id(self):
"""
return db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)

@declared_attr
def user(self):
return db.relationship("UserModel")

def __init__(self, data: dict):
super().__init__()
self.user_id = data.get("user_id")
Expand Down
5 changes: 4 additions & 1 deletion cornflow-server/cornflow/shared/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ class InvalidUsage(Exception):
def __init__(self, error=None, status_code=None, payload=None, log_txt=None):
Exception.__init__(self, error)
if error is not None:
self.error = error
if isinstance(error, Exception):
self.error = str(error)
else:
self.error = error
if status_code is not None:
self.status_code = status_code
self.payload = payload
Expand Down
23 changes: 22 additions & 1 deletion cornflow-server/cornflow/tests/unit/test_reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ def test_new_report_no_execution(self):
),
)

self.assertEqual(400, response.status_code)
self.assertEqual(404, response.status_code)
self.assertTrue("error" in response.json)

def test_get_no_reports(self):
Expand Down Expand Up @@ -187,6 +187,27 @@ def test_get_one_report(self):
self.assertEqual(200, response.status_code)
self.assertEqual(content, file)

def test_modify_report(self):
item = self.test_new_report_html()

payload = {"name": "new_name", "description": "some_description"}

response = self.client.put(
f"{self.url}{item['id']}/",
headers=self.get_header_with_auth(self.token),
json=payload,
)

self.assertEqual(response.status_code, 200)

response = self.client.get(
f"{self.url}{item['id']}/", headers=self.get_header_with_auth(self.token)
)

self.assertEqual(200, response.status_code)
self.assertEqual("new_name", dict(response.headers)["File-Name"])
self.assertEqual("some_description", dict(response.headers)["File-Description"])

def test_delete_report(self):
item = self.test_new_report_html()
response = self.client.delete(
Expand Down
Empty file removed cornflow-server/static/__init__.py
Empty file.
18 changes: 14 additions & 4 deletions libs/client/cornflow_client/cornflow_client.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from .raw_cornflow_client import RawCornFlow, CornFlowApiError

# TODO: review the standard calls for the reports.
# TODO: modify the headers on the calls that require a file.
# TODO: have the download report method to receive the path to save it on the local machine.


Expand All @@ -22,7 +21,11 @@ def __init__(self, url, token=None):
)
self.create_report = self.expect_status(self.raw.create_report, 201)
self.get_reports = self.expect_status(self.raw.get_reports, 200)
self.get_one_report = self.expect_status(self.raw.get_one_report, 200)
self.get_one_report = self.expect_status(
self.raw.get_one_report, 200, json=False
)
self.put_one_report = self.expect_status(self.raw.put_one_report, 200)
self.delete_one_report = self.expect_status(self.raw.delete_one_report, 200)

self.create_instance_data_check = self.expect_status(
self.raw.create_instance_data_check, 201
Expand Down Expand Up @@ -92,10 +95,13 @@ def token(self, token):
self.raw.token = token

@staticmethod
def expect_status(func, expected_status=None):
def expect_status(func, expected_status=None, json=True):
"""
Gets the response of the call
and raise an exception if the status of the response is not the expected
The response of the call is the json in the body for those calls that are application/json
For the calls that are form/data the response of the call is the content and the headers
"""

def decorator(*args, **kwargs):
Expand All @@ -104,7 +110,11 @@ def decorator(*args, **kwargs):
raise CornFlowApiError(
f"Expected a code {expected_status}, got a {response.status_code} error instead: {response.text}"
)
return response.json()

if json:
return response.json()
else:
return response.content, response.headers

return decorator

Expand Down
Loading

0 comments on commit 63363e3

Please sign in to comment.