def _set_stop_area_locality(connection): """ Add locality info based on stops contained within the stop areas. """ # Find stop areas with associated locality codes with connection.begin(): query_stop_areas = connection.execute( db.select([ models.StopArea.code.label("code"), models.StopPoint.locality_ref.label("ref"), db.func.count(models.StopPoint.locality_ref).label("count") ]).select_from( models.StopArea.__table__.join( models.StopPoint, models.StopArea.code == models.StopPoint.stop_area_ref)).group_by( models.StopArea.code, models.StopPoint.locality_ref)) stop_areas = query_stop_areas.fetchall() # Find locality for each stop area that contain the most stops areas, ambiguous = _find_stop_area_mode(stop_areas, "locality_ref") # if still ambiguous, measure distance between stop area and each # locality and add to above if ambiguous: add_areas = _find_locality_distance(connection, ambiguous.keys()) areas.extend(add_areas) utils.logger.info("Adding locality codes to stop areas") for a in areas: connection.execute( db.update(models.StopArea).values({ "locality_ref": a["locality_ref"] }).where(models.StopArea.code == a["code"]))
def call(cls, limit): """ Request a call, checking whether it was within the daily limit. :param limit: The limit on number of calls each day starting at 00:00 UTC. Ignored if is None or negative. """ tz = db.bindparam("utc", "UTC") one = db.literal_column("1") today = db.func.date(db.func.timezone(tz, db.func.now())) date_last_called = db.func.date(db.func.timezone(tz, cls.last_called)) statement = ( db.update(cls) .values( last_called=db.func.now(), call_count=db.case( (date_last_called < today, one), else_=cls.call_count + one, ), ) .returning(cls.call_count) ) count = db.session.execute(statement).scalar() if limit is None or limit < 0: utils.logger.debug(f"Request limit {limit!r} ignored") return True elif count <= limit: utils.logger.debug(f"Request was allowed: {count} <= {limit}") return True else: utils.logger.warning(f"Request limit exceeded: {count} > {limit}") return False
def _set_tram_admin_area(connection): """ Set admin area ref for tram stops and areas to be the same as their localities. """ tram_area = "147" with connection.begin(): # Update stop points admin_area_ref = (db.select([models.Locality.admin_area_ref]).where( models.Locality.code == models.StopPoint.locality_ref).as_scalar()) utils.logger.info("Updating tram stops with admin area ref") connection.execute( db.update(models.StopPoint).values({ models.StopPoint.admin_area_ref: admin_area_ref }).where(models.StopPoint.admin_area_ref == tram_area)) # Find stop areas with associated admin area codes stop_areas = connection.execute( db.select([ models.StopArea.code.label("code"), models.StopPoint.admin_area_ref.label("ref"), db.func.count(models.StopPoint.admin_area_ref).label("count") ]).select_from( models.StopArea.__table__.join( models.StopPoint, models.StopArea.code == models.StopPoint.stop_area_ref)). where(models.StopArea.admin_area_ref == tram_area).group_by( models.StopArea.code, models.StopPoint.admin_area_ref)) areas, ambiguous = _find_stop_area_mode(stop_areas.fetchall(), "admin_area_ref") utils.logger.info("Adding locality codes to stop areas") for a in areas: connection.execute( db.update(models.StopArea).values({ "admin_area_ref": a["admin_area_ref"] }).where(models.StopArea.code == a["code"])) for area, areas in ambiguous.items(): utils.logger.warning(f"Area {area}: ambiguous admin areas {areas}")
def _replace_row(connection, model, element): """ Replaces values for rows in tables matching attributes from this element. :param connection: Connection for population. :param model: The database model class. :param element: A ``replace`` XML element. :returns: Number of rows replaced. """ name = model.__name__ if not element.keys(): raise ValueError("Each <replace> element requires at least one XML " "attribute to filter rows.") matching = connection.execute( db.select([model.__table__]).where(_match_attr(model, element.attrib))) matching_entries = matching.fetchall() if not matching_entries: logger.warning(f"{name}: No rows matching {element.attrib} found.") return 0 updated_values = {} for value in element: column = value.tag old_value = value.get("old") new_value = value.text existing = set(getattr(r, column) for r in matching_entries) # Checks if new values already exist if existing == {new_value}: logger.warning(f"{name}: {column} {new_value!r} for " f"{element.attrib} already matches.") continue # Gives a warning if set value does not match the existing # value, suggesting it may have been changed in the dataset if old_value and not all(e == old_value for e in existing): if len(existing) > 1: values = f"values {sorted(existing)}" else: values = f"value {next(iter(existing))!r}" logger.warning(f"{name}: {column} {old_value!r} for " f"{element.attrib} does not match existing " f"{values}.") updated_values[column] = new_value if updated_values: # Update matched entries update_matching = connection.execute( db.update(model).values(updated_values).where( _match_attr(model, element.attrib))) return update_matching.rowcount else: return 0
def test_request_next_day(create_db): # Move the log to the previous day to test the count resetting statement = db.update(models.RequestLog).values( last_called=(models.RequestLog.last_called - db.cast("1 day", db.Interval)), call_count=50, ) db.session.execute(statement) log = models.RequestLog.query.one() assert log.call_count == 50 assert models.RequestLog.call(5) assert log.call_count == 1