Skip to content

Commit f48f233

Browse files
authored
Merge pull request #68 from TaskarCenterAtUW/feature-1215
User Story 1215
2 parents 37216d7 + 84607d6 commit f48f233

File tree

8 files changed

+64
-88
lines changed

8 files changed

+64
-88
lines changed

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ pydantic==1.10.4
33
html_testRunner==1.2.1
44
uvicorn==0.20.0
55
python-ms-core==0.0.22
6-
tcat-gtfs-csv-validator~=0.0.40
6+
gtfs-canonical-validator==0.0.5

src/gtfs_flex_validation.py

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@
55
from pathlib import Path
66
from typing import Union, Any
77
from .config import Settings
8-
9-
from tcat_gtfs_csv_validator import gcv_test_release
10-
from tcat_gtfs_csv_validator import exceptions as gcvex
8+
from gtfs_canonical_validator import CanonicalValidator
119

1210
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
1311
# Path used for download file generation.
@@ -17,18 +15,19 @@
1715
logger = logging.getLogger('FLEX_VALIDATION')
1816
logger.setLevel(logging.INFO)
1917

20-
DATA_TYPE = 'gtfs_flex'
21-
SCHEMA_VERSION = 'v2.0'
22-
2318

2419
class GTFSFlexValidation:
25-
def __init__(self, file_path=None, storage_client=None):
20+
def __init__(self, file_path=None, storage_client=None, prefix=None):
2621
self.settings = Settings()
2722
self.container_name = self.settings.storage_container_name
2823
self.storage_client = storage_client
2924
self.file_path = file_path
3025
self.file_relative_path = file_path.split('/')[-1]
3126
self.client = self.storage_client.get_container(container_name=self.container_name)
27+
if prefix:
28+
self.prefix = prefix
29+
else:
30+
self.prefix = self.settings.get_unique_id()
3231

3332
# Facade function to validate the file
3433
# Focuses on the file name with file name validation
@@ -46,13 +45,13 @@ def is_gtfs_flex_valid(self) -> tuple[Union[bool, Any], Union[str, Any]]:
4645
if ext and ext.lower() == '.zip':
4746
downloaded_file_path = self.download_single_file(self.file_path)
4847
logger.info(f' Downloaded file path: {downloaded_file_path}')
49-
try:
50-
gcv_test_release.test_release(DATA_TYPE, SCHEMA_VERSION, downloaded_file_path)
51-
is_valid = True
52-
except Exception as err:
53-
traceback.print_exc()
54-
validation_message = str(err)
55-
logger.error(f' Error While Validating File: {str(err)}')
48+
flex_validator = CanonicalValidator(zip_file=downloaded_file_path)
49+
result = flex_validator.validate()
50+
is_valid = result.status
51+
52+
if result.error is not None:
53+
validation_message = str(result.error)
54+
logger.error(f' Error While Validating File: {str(result.error)}')
5655
GTFSFlexValidation.clean_up(downloaded_file_path)
5756
else:
5857
logger.error(f' Failed to validate because unknown file format')
@@ -67,7 +66,7 @@ def download_single_file(self, file_upload_path=None) -> str:
6766
if not is_exists:
6867
os.makedirs(DOWNLOAD_FILE_PATH)
6968

70-
unique_folder = self.settings.get_unique_id()
69+
unique_folder = self.prefix
7170
dl_folder_path = os.path.join(DOWNLOAD_FILE_PATH, unique_folder)
7271

7372
# Ensure the unique folder path is created
@@ -95,6 +94,5 @@ def clean_up(path):
9594
logger.info(f' Removing File: {path}')
9695
os.remove(path)
9796
else:
98-
folder = os.path.join(DOWNLOAD_FILE_PATH, path)
99-
logger.info(f' Removing Folder: {folder}')
100-
shutil.rmtree(folder, ignore_errors=False)
97+
logger.info(f' Removing Folder: {path}')
98+
shutil.rmtree(path, ignore_errors=False)

src/gtfx_flex_validator.py

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
1+
import os
2+
import gc
13
import logging
2-
import uuid
4+
import threading
35
import urllib.parse
4-
from python_ms_core import Core
5-
from python_ms_core.core.queue.models.queue_message import QueueMessage
6+
from pathlib import Path
67
from .config import Settings
7-
from .gtfs_flex_validation import GTFSFlexValidation
8-
from .serializer.gtfx_flex_serializer import GTFSFlexUpload
8+
from python_ms_core import Core
99
from .models.file_upload_msg import FileUploadMsg
10-
import threading
11-
import time
10+
from .gtfs_flex_validation import GTFSFlexValidation
11+
from python_ms_core.core.queue.models.queue_message import QueueMessage
1212

1313
logging.basicConfig()
1414
logger = logging.getLogger('FLEX_VALIDATOR')
1515
logger.setLevel(logging.INFO)
1616

17+
DOWNLOAD_FILE_PATH = f'{Path.cwd()}/downloads'
1718

1819
class GTFSFlexValidator:
1920
_settings = Settings()
@@ -22,8 +23,8 @@ def __init__(self):
2223
self.core = Core()
2324
self.settings = Settings()
2425
self._subscription_name = self.settings.request_subscription
25-
self.request_topic = self.core.get_topic(topic_name=self.settings.request_topic_name,max_concurrent_messages=self.settings.max_concurrent_messages)
26-
# self.response_topic = self.core.get_topic(topic_name=self.settings.response_topic_name)
26+
self.request_topic = self.core.get_topic(topic_name=self.settings.request_topic_name,
27+
max_concurrent_messages=self.settings.max_concurrent_messages)
2728
self.logger = self.core.get_logger()
2829
self.storage_client = self.core.get_storage_client()
2930
self.listening_thread = threading.Thread(target=self.subscribe)
@@ -41,37 +42,45 @@ def process(message) -> None:
4142
logger.info(' No Message')
4243

4344
self.request_topic.subscribe(subscription=self._subscription_name, callback=process)
44-
45-
def process_message(self, upload_msg: FileUploadMsg) -> None:
4645

46+
def process_message(self, upload_msg: FileUploadMsg) -> None:
4747
try:
4848
file_upload_path = urllib.parse.unquote(upload_msg.data.file_upload_path)
49-
logger.info(f' Received message for Project Group: {upload_msg.data.tdei_project_group_id}')
49+
logger.info(f' Message ID: {upload_msg.messageId}')
5050
logger.info(file_upload_path)
5151
if file_upload_path:
5252
# Do the validation in the other class
53-
validator = GTFSFlexValidation(file_path=file_upload_path, storage_client=self.storage_client)
53+
validator = GTFSFlexValidation(file_path=file_upload_path, storage_client=self.storage_client, prefix=upload_msg.messageId)
5454
validation = validator.validate()
55-
self.send_status(valid=validation[0], upload_message=upload_msg,
56-
validation_message=validation[1])
55+
self.send_status(
56+
valid=validation[0],
57+
upload_message=upload_msg,
58+
validation_message=validation[1]
59+
)
5760
else:
5861
logger.info(' No file Path found in message!')
5962
except Exception as e:
6063
logger.error(f' Error processing message: {e}')
6164
self.send_status(valid=False, upload_message=upload_msg, validation_message=str(e))
65+
finally:
66+
folder_to_delete = os.path.join(DOWNLOAD_FILE_PATH, upload_msg.messageId)
67+
GTFSFlexValidation.clean_up(folder_to_delete)
68+
gc.collect()
69+
6270

6371
def send_status(self, valid: bool, upload_message: FileUploadMsg, validation_message: str = '') -> None:
6472
response_message = {
65-
"file_upload_path": upload_message.data.file_upload_path,
66-
"user_id": upload_message.data.user_id ,
67-
"tdei_project_group_id": upload_message.data.tdei_project_group_id,
68-
"success": valid,
69-
"message": validation_message
70-
}
71-
logger.info(f' Publishing new message with ID: {upload_message.messageId} with status: {valid} and Message: {validation_message}')
73+
'file_upload_path': upload_message.data.file_upload_path,
74+
'user_id': upload_message.data.user_id,
75+
'tdei_project_group_id': upload_message.data.tdei_project_group_id,
76+
'success': valid,
77+
'message': validation_message
78+
}
79+
logger.info(
80+
f' Publishing new message with ID: {upload_message.messageId} with status: {valid} and Message: {validation_message}')
7281
data = QueueMessage.data_from({
7382
'messageId': upload_message.messageId,
74-
'message': 'Validation complete',
83+
'message': 'Validation complete',
7584
'messageType': upload_message.messageType,
7685
'data': response_message
7786
})
@@ -85,4 +94,4 @@ def send_response(self, data: QueueMessage) -> None:
8594
logger.error(f'Error sending response: {e}')
8695

8796
def stop_listening(self):
88-
self.listening_thread.join(timeout=0)
97+
self.listening_thread.join(timeout=0)

tests/unit_tests/test_file_upload_msg.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ def setUp(self):
1919
self.upload = FileUploadMsg.from_dict(data=data)
2020

2121
def test_message_type(self):
22-
self.assertEqual(self.upload.messageType, "workflow_identifier")
22+
self.assertEqual(self.upload.messageType, 'workflow_identifier')
2323

2424
def test_message_id(self):
25-
self.upload.messageId = "abc"
26-
self.assertEqual(self.upload.messageId, "abc")
25+
self.upload.messageId = 'abc'
26+
self.assertEqual(self.upload.messageId, 'abc')
2727

2828
def test_file_upload_path(self):
2929
self.assertEqual(self.upload.data.file_upload_path,
69.3 KB
Binary file not shown.
Binary file not shown.
6.31 KB
Binary file not shown.

tests/unit_tests/test_gtfs_flex_validation.py

Lines changed: 11 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,10 @@
99
DOWNLOAD_FILE_PATH = f'{Path.cwd()}/downloads'
1010
SAVED_FILE_PATH = f'{Path.cwd()}/tests/unit_tests/test_files'
1111

12-
SUCCESS_FILE_NAME = 'success_1_all_attrs.zip'
13-
MAC_SUCCESS_FILE_NAME = 'success_2_mac_issue.zip'
12+
SUCCESS_FILE_NAME = 'browncounty-mn-us--flex-v2.zip'
13+
MAC_SUCCESS_FILE_NAME = 'otterexpress-mn-us--flex-v2.zip'
1414
FAILURE_FILE_NAME = 'fail_schema_1.zip'
1515

16-
DATA_TYPE = 'gtfs_flex'
17-
SCHEMA_VERSION = 'v2.0'
18-
1916

2017
class TestSuccessWithMacOSFile(unittest.TestCase):
2118
@patch.object(GTFSFlexValidation, 'download_single_file')
@@ -35,10 +32,11 @@ def setUp(self, mock_download_single_file):
3532
self.validator.file_relative_path = MAC_SUCCESS_FILE_NAME
3633
self.validator.container_name = None
3734
self.validator.settings = Settings()
35+
self.validator.prefix = self.validator.settings.get_unique_id()
3836
mock_download_single_file.return_value = file_path
3937

4038
def tearDown(self):
41-
pass
39+
GTFSFlexValidation.clean_up(os.path.join(DOWNLOAD_FILE_PATH, self.validator.prefix))
4240

4341
def test_validate_with_valid_file(self):
4442
# Arrange
@@ -70,15 +68,17 @@ def setUp(self, mock_download_single_file):
7068
os.makedirs(dl_folder_path, exist_ok=True) # Ensure this directory is created in the test
7169

7270
with patch.object(GTFSFlexValidation, '__init__', return_value=None):
73-
self.validator = GTFSFlexValidation(file_path=file_path, storage_client=MagicMock())
71+
self.validator = GTFSFlexValidation(file_path=file_path, storage_client=MagicMock(),
72+
prefix=Settings().get_unique_id())
7473
self.validator.file_path = file_path
7574
self.validator.file_relative_path = SUCCESS_FILE_NAME
7675
self.validator.container_name = None
7776
self.validator.settings = Settings()
77+
self.validator.prefix = self.validator.settings.get_unique_id()
7878
mock_download_single_file.return_value = os.path.join(dl_folder_path, SUCCESS_FILE_NAME)
7979

8080
def tearDown(self):
81-
pass
81+
GTFSFlexValidation.clean_up(os.path.join(DOWNLOAD_FILE_PATH, self.validator.prefix))
8282

8383
def test_validate_with_valid_file(self):
8484
# Arrange
@@ -128,33 +128,6 @@ def test_download_single_file(self):
128128
content = f.read()
129129
self.assertEqual(content, b'file_content')
130130

131-
def test_download_multiple_file_with_same_name(self):
132-
# Arrange
133-
file_upload_path = DOWNLOAD_FILE_PATH
134-
self.validator.storage_client = MagicMock()
135-
self.validator.storage_client.get_file_from_url = MagicMock()
136-
file = MagicMock()
137-
file.file_path = 'text_file.txt'
138-
file.get_stream = MagicMock(return_value=b'file_content')
139-
self.validator.storage_client.get_file_from_url.return_value = file
140-
141-
# Act
142-
first_downloaded_file_path = self.validator.download_single_file(file_upload_path=file_upload_path)
143-
second_downloaded_file_path = self.validator.download_single_file(file_upload_path=file_upload_path)
144-
145-
# Assert
146-
self.assertNotEqual(first_downloaded_file_path, second_downloaded_file_path,
147-
"The downloaded file paths should be different for files with the same name.")
148-
149-
# Check if the get_file_from_url was called for both download attempts
150-
self.assertEqual(self.validator.storage_client.get_file_from_url.call_count, 2,
151-
"get_file_from_url should be called twice for two downloads.")
152-
file.get_stream.assert_called()
153-
154-
# Additional assertions to verify that the paths indeed point to different locations
155-
self.assertTrue(first_downloaded_file_path.startswith(DOWNLOAD_FILE_PATH))
156-
self.assertTrue(second_downloaded_file_path.startswith(DOWNLOAD_FILE_PATH))
157-
158131
def test_clean_up_file(self):
159132
# Arrange
160133
file_upload_path = DOWNLOAD_FILE_PATH
@@ -203,10 +176,11 @@ def setUp(self, mock_download_single_file):
203176
self.validator.file_relative_path = FAILURE_FILE_NAME
204177
self.validator.container_name = None
205178
self.validator.settings = MagicMock()
179+
self.validator.prefix = Settings().get_unique_id()
206180
mock_download_single_file.return_value = file_path
207181

208182
def tearDown(self):
209-
pass
183+
GTFSFlexValidation.clean_up(os.path.join(DOWNLOAD_FILE_PATH, self.validator.prefix))
210184

211185
def test_validate_with_invalid_file(self):
212186
# Arrange
@@ -259,12 +233,7 @@ def test_download_single_file_exception(self):
259233
file.get_stream = MagicMock(side_effect=FileNotFoundError("Mocked FileNotFoundError"))
260234
self.validator.storage_client.get_file_from_url.return_value = file
261235

262-
# Create the mock folder that would be used
263-
unique_id = "mocked-uuid"
264-
self.validator.settings.get_unique_id = MagicMock()
265-
self.validator.settings.get_unique_id.return_value = unique_id
266-
267-
dl_folder_path = os.path.join(DOWNLOAD_FILE_PATH, unique_id)
236+
dl_folder_path = os.path.join(DOWNLOAD_FILE_PATH, self.validator.prefix)
268237
os.makedirs(dl_folder_path, exist_ok=True)
269238

270239
# Act & Assert

0 commit comments

Comments
 (0)