77from .config import Settings
88from gtfs_canonical_validator import CanonicalValidator
99
10+ # in an effort to be more permissive of small errors, accept these which could conceivably be calculated/fixed/interpreted by common applications
11+ CHANGE_ERROR_TO_WARNING = [ "block_trips_with_overlapping_stop_times" , "trip_distance_exceeds_shape_distance" , "decreasing_or_equal_stop_time_distance" , "decreasing_shape_distance" ,
12+ "empty_file" , "equal_shape_distance_diff_coordinates" , "fare_transfer_rule_duration_limit_type_without_duration_limit" ,
13+ "fare_transfer_rule_duration_limit_without_type" , "fare_transfer_rule_invalid_transfer_count" , "fare_transfer_rule_missing_transfer_count" ,
14+ "fare_transfer_rule_with_forbidden_transfer_count" , "forbidden_shape_dist_traveled" , "invalid_currency" , "invalid_currency_amount" ,
15+ "invalid_url" , "location_with_unexpected_stop_time" , "missing_trip_edge" , "new_line_in_value" , "point_near_origin" , "point_near_pole" ,
16+ "route_both_short_and_long_name_missing" , "route_networks_specified_in_more_than_one_file" , "start_and_end_range_equal" , "start_and_end_range_out_of_order" ,
17+ "station_with_parent_station" , "stop_time_timepoint_without_times" , "stop_time_with_arrival_before_previous_departure_time" ,
18+ "stop_time_with_only_arrival_or_departure_time" , "stop_without_location" , "timeframe_only_start_or_end_time_specified" , "timeframe_overlap" ,
19+ "timeframe_start_or_end_time_greater_than_twenty_four_hours" , "u_r_i_syntax_error" ]
20+
21+ FLEX_FATAL_ERROR_CODES = [ "missing_required_element" , "unsupported_feature_type" , "unsupported_geo_json_type" , "unsupported_geometry_type" ,
22+ "invalid_geometry" , "forbidden_prior_day_booking_field_value" , "forbidden_prior_notice_start_day" , "forbidden_prior_notice_start_time" ,
23+ "forbidden_real_time_booking_field_value" , "forbidden_same_day_booking_field_value" , "invalid_prior_notice_duration_min" ,
24+ "missing_prior_day_booking_field_value" , "missing_prior_notice_duration_min" , "missing_prior_notice_start_time" ,
25+ "prior_notice_last_day_after_start_day" ]
26+
27+ FLEX_FIELDS = {
28+ 'stop_times.txt' : [ "start_pickup_dropoff_window" , "end_pickup_dropoff_window" , "pickup_booking_rule_id" , "drop_off_booking_rule_id" ,
29+ "mean_duration_factor" , "mean_duration_offset" , "safe_duration_factor" , "safe_duration_offset" ]
30+ }
31+
32+ FLEX_FILES = ["locations.geojson" , "booking_rules.txt" , "location_groups.txt" , "location_group_stops.txt" ]
33+
1034ROOT_DIR = os .path .dirname (os .path .abspath (__file__ ))
1135# Path used for download file generation.
1236DOWNLOAD_FILE_PATH = f'{ Path .cwd ()} /downloads'
@@ -47,30 +71,69 @@ def is_gtfs_flex_valid(self) -> tuple[Union[bool, Any], Union[str, Any]]:
4771 logger .info (f' Downloaded file path: { downloaded_file_path } ' )
4872 flex_validator = CanonicalValidator (zip_file = downloaded_file_path )
4973 result = flex_validator .validate ()
74+
5075 is_valid = result .status
76+ if isinstance (result .error , list ) and result .error is not None :
77+ for error in result .error [:]:
78+ # change some smaller errors to warnings instead to relax the strict validation MD gives us
79+ if error ['code' ] in CHANGE_ERROR_TO_WARNING :
80+ if (result .info is None ): result .info = []
81+
82+ result .info .append (error )
83+ result .error .remove (error )
84+ continue
85+
86+ # these are error codes from MD that relate to pathways that are fatal
87+ if error ['code' ] in FLEX_FATAL_ERROR_CODES :
88+ is_valid = False
89+ continue
90+
91+ # some of the notices relate to pathways, but there's no way to tell except with this logic:
92+ for notice in error ['sampleNotices' ]:
93+ # one of the fields in a given file is a pathway-spec field--if it's flagged, fail
94+ if "fieldName" in notice and "filename" in notice :
95+ if notice ['filename' ] in FLEX_FIELDS and \
96+ notice ['fieldName' ] in FLEX_FIELDS [notice ['filename' ]]:
97+ is_valid = False
98+ continue
99+
100+ # one of the pathways spec'd files has an error--if so, fail
101+ if "filename" in notice :
102+ if (notice ['filename' ] in FLEX_FILES ):
103+ is_valid = False
104+ continue
105+
106+ # similar to the above, but the field for the filename is parent/child
107+ if "childFilename" in notice :
108+ if (notice ['childFilename' ] in FLEX_FILES ):
109+ is_valid = False
110+ continue
111+
112+ # if all errors have been downgraded to warnings, mark us as a success
113+ if len (result .error ) == 0 :
114+ is_valid = True
115+
116+ if result .error is not None :
117+ validation_message = str (result .error )
118+ logger .error (f' Error While Validating File: { str (result .error )} ' )
119+
120+
51121
52- if result .error is not None :
53- validation_message = str (result .error )
54- logger .error (f' Error While Validating File: { str (result .error )} ' )
55122 GTFSFlexValidation .clean_up (downloaded_file_path )
56123 else :
57124 logger .error (f' Failed to validate because unknown file format' )
58125
59126 return is_valid , validation_message
60127
61128 # Downloads the file to local folder of the server
62- # file_upload_path is the fullUrl of where the
129+ # file_upload_path is the fullUrl of where the
63130 # file is uploaded.
64131 def download_single_file (self , file_upload_path = None ) -> str :
65- is_exists = os .path .exists (DOWNLOAD_FILE_PATH )
66- if not is_exists :
67- os .makedirs (DOWNLOAD_FILE_PATH )
68-
69- unique_folder = self .prefix
132+ unique_folder = self .settings .get_unique_id ()
70133 dl_folder_path = os .path .join (DOWNLOAD_FILE_PATH , unique_folder )
71134
72- # Ensure the unique folder path is created
73- os .makedirs (dl_folder_path , exist_ok = True )
135+ if not os . path . exists ( dl_folder_path ):
136+ os .makedirs (dl_folder_path )
74137
75138 file = self .storage_client .get_file_from_url (self .container_name , file_upload_path )
76139 try :
@@ -82,11 +145,9 @@ def download_single_file(self, file_upload_path=None) -> str:
82145 return f'{ dl_folder_path } /{ file_path } '
83146 else :
84147 logger .info (' File not found!' )
85- raise Exception ('File not found!' )
86148 except Exception as e :
87149 traceback .print_exc ()
88150 logger .error (e )
89- raise e
90151
91152 @staticmethod
92153 def clean_up (path ):
0 commit comments