async def _push_to_dynatrace(context: MetricsContext, project_id: str,
                             lines_batch: List[IngestLine]):
    ingest_input = "\n".join([line.to_string() for line in lines_batch])
    if context.print_metric_ingest_input:
        context.log("Ingest input is: ")
        context.log(ingest_input)
    dt_url = f"{context.dynatrace_url.rstrip('/')}/api/v2/metrics/ingest"
    ingest_response = await context.dt_session.post(
        url=dt_url,
        headers={
            "Authorization": f"Api-Token {context.dynatrace_api_key}",
            "Content-Type": "text/plain; charset=utf-8"
        },
        data=ingest_input,
        verify_ssl=context.require_valid_certificate)

    if ingest_response.status == 401:
        context.dynatrace_connectivity = DynatraceConnectivity.ExpiredToken
        raise Exception("Expired token")
    elif ingest_response.status == 403:
        context.dynatrace_connectivity = DynatraceConnectivity.WrongToken
        raise Exception(
            "Wrong token - missing 'Ingest metrics using API V2' permission")
    elif ingest_response.status == 404 or ingest_response.status == 405:
        context.dynatrace_connectivity = DynatraceConnectivity.WrongURL
        raise Exception(f"Wrong URL {dt_url}")

    ingest_response_json = await ingest_response.json()
    context.dynatrace_request_count[ingest_response.status] \
        = context.dynatrace_request_count.get(ingest_response.status, 0) + 1
    context.dynatrace_ingest_lines_ok_count[project_id] \
        = context.dynatrace_ingest_lines_ok_count.get(project_id, 0) + ingest_response_json.get("linesOk", 0)
    context.dynatrace_ingest_lines_invalid_count[project_id] \
        = context.dynatrace_ingest_lines_invalid_count.get(project_id, 0) + ingest_response_json.get("linesInvalid", 0)
    context.log(project_id, f"Ingest response: {ingest_response_json}")
    await log_invalid_lines(context, ingest_response_json, lines_batch)
async def push_ingest_lines(context: MetricsContext, project_id: str,
                            fetch_metric_results: List[IngestLine]):
    if context.dynatrace_connectivity != DynatraceConnectivity.Ok:
        context.log(project_id,
                    f"Skipping push due to detected connectivity error")
        return

    if not fetch_metric_results:
        context.log(project_id, "Skipping push due to no data to push")

    lines_sent = 0
    maximum_lines_threshold = context.maximum_metric_data_points_per_minute
    start_time = time.time()
    try:
        lines_batch = []
        for result in fetch_metric_results:
            lines_batch.append(result)
            lines_sent += 1
            if len(lines_batch) >= context.metric_ingest_batch_size:
                await _push_to_dynatrace(context, project_id, lines_batch)
                lines_batch = []
            if lines_sent >= maximum_lines_threshold:
                await _push_to_dynatrace(context, project_id, lines_batch)
                lines_dropped_count = len(
                    fetch_metric_results) - maximum_lines_threshold
                context.dynatrace_ingest_lines_dropped_count[project_id] = \
                    context.dynatrace_ingest_lines_dropped_count.get(project_id, 0) + lines_dropped_count
                context.log(
                    project_id,
                    f"Number of metric lines exceeded maximum {maximum_lines_threshold}, dropped {lines_dropped_count} lines"
                )
                return
        if lines_batch:
            await _push_to_dynatrace(context, project_id, lines_batch)
    except Exception as e:
        if isinstance(e, InvalidURL):
            context.dynatrace_connectivity = DynatraceConnectivity.WrongURL
        context.log(
            project_id,
            f"Failed to push ingest lines to Dynatrace due to {type(e).__name__} {e}"
        )
    finally:
        push_data_time = time.time() - start_time
        context.push_to_dynatrace_execution_time[project_id] = push_data_time
        context.log(
            project_id,
            f"Finished uploading metric ingest lines to Dynatrace in {push_data_time} s"
        )