diff --git a/protos/sift/test_reports/v1/test_reports.proto b/protos/sift/test_reports/v1/test_reports.proto new file mode 100644 index 000000000..610fd51de --- /dev/null +++ b/protos/sift/test_reports/v1/test_reports.proto @@ -0,0 +1,629 @@ +syntax = "proto3"; + +package sift_internal.test_reports.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/field_mask.proto"; +import "google/protobuf/timestamp.proto"; +import "protoc-gen-openapiv2/options/annotations.proto"; +import "sift/metadata/v1/metadata.proto"; +import "sift/unit/v2/unit.proto"; + + +option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { + info: {title: "Test Results Service"} +}; + +message TestReport { + // Unique identifier for the run + string test_report_id = 1 [(google.api.field_behavior) = REQUIRED]; + + // The status of the test run + TestStatus status = 2 [(google.api.field_behavior) = REQUIRED]; + + // The name of the test run + string name = 3 [(google.api.field_behavior) = REQUIRED]; + + // The name of the test system + string test_system_name = 4 [(google.api.field_behavior) = REQUIRED]; + + // The test case that was run + string test_case = 5 [(google.api.field_behavior) = REQUIRED]; + + // The start time of the test run + google.protobuf.Timestamp start_time = 6 [(google.api.field_behavior) = REQUIRED]; + + // The end time of the test run + google.protobuf.Timestamp end_time = 7 [(google.api.field_behavior) = REQUIRED]; + + // The metadata values associated with this test run + repeated sift.metadata.v1.MetadataValue metadata = 8 [(google.api.field_behavior) = OPTIONAL]; + + // The serial number for the DUT + string serial_number = 9 [(google.api.field_behavior) = OPTIONAL]; + + // The part number for the DUT + string part_number = 10 [(google.api.field_behavior) = OPTIONAL]; + + // Unique identifier for user owner + string system_operator = 11 [(google.api.field_behavior) = OPTIONAL]; + + // The date and time the test run was archived (internal) + google.protobuf.Timestamp archived_date = 12 [(google.api.field_behavior) = OPTIONAL]; + + // Whether the test run is archived (externally exposed) + bool is_archived = 13 [(google.api.field_behavior) = OPTIONAL]; +} + +enum TestStatus { + TEST_STATUS_UNSPECIFIED = 0; + TEST_STATUS_DRAFT = 1; // Barebones test report created, waiting for file processing + TEST_STATUS_PASSED = 2; + TEST_STATUS_FAILED = 3; + TEST_STATUS_ABORTED = 4; + TEST_STATUS_ERROR = 5; + TEST_STATUS_IN_PROGRESS = 6; + TEST_STATUS_SKIPPED = 7; +} + +enum TestStepType { + TEST_STEP_TYPE_UNSPECIFIED = 0; + TEST_STEP_TYPE_SEQUENCE = 1; // ResultSet/MainSequence - top-level test sequence + TEST_STEP_TYPE_GROUP = 2; // TestGroup - logical grouping of test steps + TEST_STEP_TYPE_ACTION = 3; // SessionAction - individual executable action + TEST_STEP_TYPE_FLOW_CONTROL = 4; // Flow control elements (While, If, etc.) +} + +message TestStep { + // unique identifier for the step + string test_step_id = 1 [(google.api.field_behavior) = REQUIRED]; + + // pointer to overall test run + string test_report_id = 2 [(google.api.field_behavior) = REQUIRED]; + + // pointer to parent step, if any + string parent_step_id = 3 [(google.api.field_behavior) = OPTIONAL]; + + // Name of the test step for display + string name = 4 [(google.api.field_behavior) = REQUIRED]; + + // Description of the test step from test controller + string description = 5 [(google.api.field_behavior) = OPTIONAL]; + + // Semantic type of the test step + TestStepType step_type = 6 [(google.api.field_behavior) = REQUIRED]; + + // Order among siblings (1, 2, 3...) + int32 step_number = 7 [(google.api.field_behavior) = REQUIRED]; + + // Hierarchical path (e.g., "1", "1.1", "1.2.3") + string step_path = 8 [(google.api.field_behavior) = REQUIRED]; + + // Status of the test step + TestStatus status = 9 [(google.api.field_behavior) = REQUIRED]; + + // Start time of the test step + google.protobuf.Timestamp start_time = 10 [(google.api.field_behavior) = REQUIRED]; + + // End time of the test step + google.protobuf.Timestamp end_time = 11 [(google.api.field_behavior) = REQUIRED]; + + // Error information of the test step + ErrorInfo error_info = 12 [(google.api.field_behavior) = OPTIONAL]; + + //repeated Measurement measurements = XX; TOOD: to be added in ENG-5623 +} + +message ErrorInfo { + int32 error_code = 1 [(google.api.field_behavior) = REQUIRED]; + string error_message = 2 [(google.api.field_behavior) = REQUIRED]; +} + +enum TestMeasurementType { + TEST_MEASUREMENT_TYPE_UNSPECIFIED = 0; + TEST_MEASUREMENT_TYPE_DOUBLE = 1; + TEST_MEASUREMENT_TYPE_STRING = 3; + TEST_MEASUREMENT_TYPE_BOOLEAN = 4; + TEST_MEASUREMENT_TYPE_LIMIT = 5; +} + +message TestMeasurement { + string measurement_id = 1 [(google.api.field_behavior) = REQUIRED]; + TestMeasurementType measurement_type = 2 [(google.api.field_behavior) = REQUIRED]; + string name = 3 [(google.api.field_behavior) = REQUIRED]; + string test_step_id = 4 [(google.api.field_behavior) = REQUIRED]; + string test_report_id = 5 [(google.api.field_behavior) = REQUIRED]; + + oneof value { + double numeric_value = 6; + string string_value = 7; + bool boolean_value = 8; + } + sift.unit.v2.Unit unit = 9 [(google.api.field_behavior) = OPTIONAL]; + oneof bounds { + NumericBounds numeric_bounds = 10; + StringBounds string_bounds = 11; + } + bool passed = 12 [(google.api.field_behavior) = REQUIRED]; + google.protobuf.Timestamp timestamp = 13 [(google.api.field_behavior) = REQUIRED]; +} + +message NumericBounds { + optional double min = 1 [(google.api.field_behavior) = OPTIONAL]; + optional double max = 2 [(google.api.field_behavior) = OPTIONAL]; +} + +message StringBounds { + string expected_value = 1 [(google.api.field_behavior) = REQUIRED]; +} + +service TestReportService { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_tag) = {description: "Service to manage test reports"}; + + // Imports a test report from an already-uploaded file + rpc ImportTestReport(ImportTestReportRequest) returns (ImportTestReportResponse) { + option (google.api.http) = { + post: "/api/v1/test-reports" + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "ImportTestReport" + description: "Imports a test report from an already-uploaded file." + }; + } + + // Creates a test report + rpc CreateTestReport(CreateTestReportRequest) returns (CreateTestReportResponse) { + option (google.api.http) = { + post: "/api/v1/test-reports:create" + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "CreateTestReport" + description: "Creates a test report" + }; + } + + // Gets a single test report + rpc GetTestReport(GetTestReportRequest) returns (GetTestReportResponse) { + option (google.api.http) = {get: "/api/v1/test-reports/{test_report_id}"}; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "GetTestReport" + description: "Gets a single test report" + }; + } + + // Lists test reports with optional filtering + rpc ListTestReports(ListTestReportsRequest) returns (ListTestReportsResponse) { + option (google.api.http) = {get: "/api/v1/test-reports"}; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "ListTestReports" + description: "Lists test reports with optional filtering" + }; + } + + // Updates a test report + rpc UpdateTestReport(UpdateTestReportRequest) returns (UpdateTestReportResponse) { + option (google.api.http) = { + patch: "/api/v1/test-reports" + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "UpdateTestReport" + description: "Updates a test report" + }; + } + + // Deletes a test report + rpc DeleteTestReport(DeleteTestReportRequest) returns (DeleteTestReportResponse) { + option (google.api.http) = {delete: "/api/v1/test-reports/{test_report_id}"}; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "DeleteTestReport" + description: "Deletes a test report" + }; + } + + // Creates a test step + rpc CreateTestStep(CreateTestStepRequest) returns (CreateTestStepResponse) { + option (google.api.http) = { + post: "/api/v1/test-steps" + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "CreateTestStep" + description: "Creates a test step" + }; + } + + // Lists test steps with optional filtering + rpc ListTestSteps(ListTestStepsRequest) returns (ListTestStepsResponse) { + option (google.api.http) = {get: "/api/v1/test-steps"}; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "ListTestSteps" + description: "Lists test steps with optional filtering" + }; + } + + // Updates a test step + rpc UpdateTestStep(UpdateTestStepRequest) returns (UpdateTestStepResponse) { + option (google.api.http) = { + patch: "/api/v1/test-steps" + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "UpdateTestStep" + description: "Updates a test step" + }; + } + + // Deletes a test step + rpc DeleteTestStep(DeleteTestStepRequest) returns (DeleteTestStepResponse) { + option (google.api.http) = {delete: "/api/v1/test-steps/{test_step_id}"}; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "DeleteTestStep" + description: "Deletes a test step" + }; + } + + // Creates a test measurement + rpc CreateTestMeasurement(CreateTestMeasurementRequest) returns (CreateTestMeasurementResponse) { + option (google.api.http) = { + post: "/api/v1/test-measurements" + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "CreateTestMeasurement" + description: "Creates a test measurement" + }; + } + + // Creates multiple test measurements in a single request + rpc CreateTestMeasurements(CreateTestMeasurementsRequest) returns (CreateTestMeasurementsResponse) { + option (google.api.http) = { + post: "/api/v1/test-measurements:batch" + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "CreateTestMeasurements" + description: "Creates multiple test measurements in a single request" + }; + } + + // Lists test measurements with optional filtering + rpc ListTestMeasurements(ListTestMeasurementsRequest) returns (ListTestMeasurementsResponse) { + option (google.api.http) = {get: "/api/v1/test-measurements"}; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "ListTestMeasurements" + description: "Lists test measurements with optional filtering" + }; + } + + // Updates a test measurement + rpc UpdateTestMeasurement(UpdateTestMeasurementRequest) returns (UpdateTestMeasurementResponse) { + option (google.api.http) = { + patch: "/api/v1/test-measurements" + body: "*" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "UpdateTestMeasurement" + description: "Updates a test measurement" + }; + } + + // Deletes a test measurement + rpc DeleteTestMeasurement(DeleteTestMeasurementRequest) returns (DeleteTestMeasurementResponse) { + option (google.api.http) = {delete: "/api/v1/test-measurements/{measurement_id}"}; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "DeleteTestMeasurement" + description: "Deletes a test measurement" + }; + } +} + +// Request message for ImportTestReport +message ImportTestReportRequest { + // The remote file ID containing the XML test data + string remote_file_id = 1 [(google.api.field_behavior) = REQUIRED]; +} + +// Response message for ImportTestReport +message ImportTestReportResponse { + // The imported test report + TestReport test_report = 1; +} + +// Request message for CreateTestReport +message CreateTestReportRequest { + // The status of the test run + TestStatus status = 1 [(google.api.field_behavior) = REQUIRED]; + + // The name of the test run + string name = 2 [(google.api.field_behavior) = REQUIRED]; + + // The name of the test system + string test_system_name = 3 [(google.api.field_behavior) = REQUIRED]; + + // The test case that was run + string test_case = 4 [(google.api.field_behavior) = REQUIRED]; + + // The start time of the test run + google.protobuf.Timestamp start_time = 5 [(google.api.field_behavior) = REQUIRED]; + + // The end time of the test run + google.protobuf.Timestamp end_time = 6 [(google.api.field_behavior) = REQUIRED]; + + // The metadata values associated with this test run + repeated sift.metadata.v1.MetadataValue metadata = 7 [(google.api.field_behavior) = OPTIONAL]; + + // The serial number for the DUT + string serial_number = 8 [(google.api.field_behavior) = OPTIONAL]; + + // The part number for the DUT + string part_number = 9 [(google.api.field_behavior) = OPTIONAL]; + + // Unique identifier for user owner + string system_operator = 10 [(google.api.field_behavior) = OPTIONAL]; +} + +// Response message for CreateTestReport +message CreateTestReportResponse { + // The created test report + TestReport test_report = 1; +} + +// Request message for GetTestReport +message GetTestReportRequest { + // The ID of the test report to get + string test_report_id = 1 [(google.api.field_behavior) = REQUIRED]; +} + +// Response message for GetTestReport +message GetTestReportResponse { + // The test report + TestReport test_report = 1; +} + +// Request message for ListTestReports +message ListTestReportsRequest { + // The maximum number of test reports to return. + // The service may return fewer than this value. + // If unspecified, at most 50 test reports will be returned. + // The maximum value is 1000; values above 1000 will be coerced to 1000. + uint32 page_size = 1 [(google.api.field_behavior) = OPTIONAL]; + + // A page token, received from a previous `ListTestReports` call. + // Provide this to retrieve the subsequent page. + // When paginating, all other parameters provided to `ListTestReports` must match + // the call that provided the page token. + string page_token = 2 [(google.api.field_behavior) = OPTIONAL]; + + // A [Common Expression Language (CEL)](https://github.com/google/cel-spec) filter string. + // Available fields to filter by are `test_report_id`, `status`, `name`, `test_system_name`, + // `test_case`, `start_time`, `end_time`, `serial_number`, + // `part_number`, `system_operator`, `archived_date`, and `metadata`. + // Metadata can be used in filters by using `metadata.{metadata_key_name}` as the field name. + // For further information about how to use CELs, please refer to [this guide](https://github.com/google/cel-spec/blob/master/doc/langdef.md#standard-definitions). + // For more information about the fields used for filtering, please refer to [this definition](/docs/api/grpc/protocol-buffers/test-results#testreport). Optional. + string filter = 3 [(google.api.field_behavior) = OPTIONAL]; + + // How to order the retrieved test reports. Formatted as a comma-separated string i.e. "FIELD_NAME[ desc],...". + // Available fields to order_by are `test_report_id`, `name`, `test_system_name`, `test_case`, `start_time`, `end_time`, + // `created_date`, and `modified_date`. + // If left empty, items are ordered by `start_time` in descending order (newest-first). + // For more information about the format of this field, read [this](https://google.aip.dev/132#ordering) + // Example: "start_time desc,name" + string order_by = 4 [(google.api.field_behavior) = OPTIONAL]; +} + +// Response message for ListTestReports +message ListTestReportsResponse { + // The list of test reports + repeated TestReport test_reports = 1; + + // The next page token for pagination + string next_page_token = 2; +} + +// Request message for UpdateTestReport +message UpdateTestReportRequest { + // The test report to update + TestReport test_report = 1 [(google.api.field_behavior) = REQUIRED]; + + // The field mask specifying which fields to update. The fields available to be updated are + // `status`, `name`, `test_system_name`, `test_case`, `start_time`, `end_time`, `serial_number`, + // `part_number`, `system_operator`, and `is_archived`. + google.protobuf.FieldMask update_mask = 2 [(google.api.field_behavior) = OPTIONAL]; +} + +// Response message for UpdateTestReport +message UpdateTestReportResponse { + // The updated test report + TestReport test_report = 1; +} + +// Request message for DeleteTestReport +message DeleteTestReportRequest { + // The ID of the test report to delete + string test_report_id = 1 [(google.api.field_behavior) = REQUIRED]; +} + +// Response message for DeleteTestReport +message DeleteTestReportResponse { + // Empty response indicating successful deletion +} + +// Request message for CreateTestStep +message CreateTestStepRequest { + // The test step to create + TestStep test_step = 1 [(google.api.field_behavior) = REQUIRED]; +} + +// Response message for CreateTestStep +message CreateTestStepResponse { + // The created test step + TestStep test_step = 1; +} + +// Request message for ListTestSteps +message ListTestStepsRequest { + // The maximum number of test steps to return. + // The service may return fewer than this value. + // If unspecified, at most 50 test steps will be returned. + // The maximum value is 1000; values above 1000 will be coerced to 1000. + uint32 page_size = 1 [(google.api.field_behavior) = OPTIONAL]; + + // A page token, received from a previous `ListTestSteps` call. + // Provide this to retrieve the subsequent page. + // When paginating, all other parameters provided to `ListTestSteps` must match + // the call that provided the page token. + string page_token = 2 [(google.api.field_behavior) = OPTIONAL]; + + // A [Common Expression Language (CEL)](https://github.com/google/cel-spec) filter string. + // Available fields to filter by are `test_step_id`, `test_report_id`, `parent_step_id`, `name`, + // `description`, `step_type`, `step_number`, `step_path`, `status`, `start_time`, `end_time`, + // `error_code`, `error_message`, `created_date`, and `modified_date`. + // For further information about how to use CELs, please refer to [this guide](https://github.com/google/cel-spec/blob/master/doc/langdef.md#standard-definitions). + // For more information about the fields used for filtering, please refer to [this definition](/docs/api/grpc/protocol-buffers/test-results#teststep). Optional. + string filter = 3 [(google.api.field_behavior) = OPTIONAL]; + + // How to order the retrieved test steps. Formatted as a comma-separated string i.e. "FIELD_NAME[ desc],...". + // Available fields to order_by are `test_step_id`, `name`, `step_type`, `step_number`, `step_path`, `status`, + // `start_time`, `end_time`, `created_date`, and `modified_date`. + // If left empty, items are ordered by `step_path` in ascending order. + // For more information about the format of this field, read [this](https://google.aip.dev/132#ordering) + // Example: "step_path asc,start_time desc" + string order_by = 4 [(google.api.field_behavior) = OPTIONAL]; +} + +// Response message for ListTestSteps +message ListTestStepsResponse { + // The list of test steps + repeated TestStep test_steps = 1; + + // The next page token for pagination + string next_page_token = 2; +} + +// Request message for UpdateTestStep +message UpdateTestStepRequest { + // The test step to update + TestStep test_step = 1 [(google.api.field_behavior) = REQUIRED]; + + // The field mask specifying which fields to update. The fields available to be updated are + // `name`, `description`, `step_type`, `step_number`, `step_path`, `test_case`, `status`, + // `start_time`, `end_time`, and `error_info`. + google.protobuf.FieldMask update_mask = 2 [(google.api.field_behavior) = OPTIONAL]; +} + +// Response message for UpdateTestStep +message UpdateTestStepResponse { + // The updated test step + TestStep test_step = 1; +} + +// Request message for DeleteTestStep +message DeleteTestStepRequest { + // The ID of the test step to delete + string test_step_id = 1 [(google.api.field_behavior) = REQUIRED]; +} + +// Response message for DeleteTestStep +message DeleteTestStepResponse { + // Empty response indicating successful deletion +} + +// Request message for CreateTestMeasurement +message CreateTestMeasurementRequest { + // The test measurement to create + TestMeasurement test_measurement = 1 [(google.api.field_behavior) = REQUIRED]; +} + +// Response message for CreateTestMeasurement +message CreateTestMeasurementResponse { + // The created test measurement + TestMeasurement test_measurement = 1; +} + +// Request message for CreateTestMeasurements +message CreateTestMeasurementsRequest { + // The test measurements to create + repeated TestMeasurement test_measurements = 1 [(google.api.field_behavior) = REQUIRED]; +} + +// Response message for CreateTestMeasurements +message CreateTestMeasurementsResponse { + // The number of test measurements successfully created + int32 measurements_created_count = 1 [(google.api.field_behavior) = REQUIRED]; + + // The IDs of the created test measurements + repeated string measurement_ids = 2 [(google.api.field_behavior) = REQUIRED]; +} + +// Request message for ListTestMeasurements +message ListTestMeasurementsRequest { + // The maximum number of test measurements to return. + // The service may return fewer than this value. + // If unspecified, at most 50 test measurements will be returned. + // The maximum value is 1000; values above 1000 will be coerced to 1000. + uint32 page_size = 1 [(google.api.field_behavior) = OPTIONAL]; + + // A page token, received from a previous `ListTestMeasurements` call. + // Provide this to retrieve the subsequent page. + // When paginating, all other parameters provided to `ListTestMeasurements` must match + // the call that provided the page token. + string page_token = 2 [(google.api.field_behavior) = OPTIONAL]; + + // A [Common Expression Language (CEL)](https://github.com/google/cel-spec) filter string. + // Available fields to filter by are `measurement_id`, `measurement_type`, `name`, `test_step_id`, + // `test_report_id`, `numeric_value`, `string_value`, `boolean_value`, `passed`, `timestamp`, + // `created_date`, and `modified_date`. + // For further information about how to use CELs, please refer to [this guide](https://github.com/google/cel-spec/blob/master/doc/langdef.md#standard-definitions). + // For more information about the fields used for filtering, please refer to [this definition](/docs/api/grpc/protocol-buffers/test-results#testmeasurement). Optional. + string filter = 3 [(google.api.field_behavior) = OPTIONAL]; + + // How to order the retrieved test measurements. Formatted as a comma-separated string i.e. "FIELD_NAME[ desc],...". + // Available fields to order_by are `measurement_id`, `name`, `measurement_type`, `test_step_id`, `test_report_id`, + // `passed`, `timestamp`, `created_date`, and `modified_date`. + // If left empty, items are ordered by `timestamp` in ascending order. + // For more information about the format of this field, read [this](https://google.aip.dev/132#ordering) + // Example: "timestamp asc,name" + string order_by = 4 [(google.api.field_behavior) = OPTIONAL]; +} + +// Response message for ListTestMeasurements +message ListTestMeasurementsResponse { + // The list of test measurements + repeated TestMeasurement test_measurements = 1; + + // The next page token for pagination + string next_page_token = 2; +} + +// Request message for UpdateTestMeasurement +message UpdateTestMeasurementRequest { + // The test measurement to update + TestMeasurement test_measurement = 1 [(google.api.field_behavior) = REQUIRED]; + + // The field mask specifying which fields to update. The fields available to be updated are + // `name`, `measurement_type`, `numeric_value`, `string_value`, `boolean_value`, `unit`, `numeric_bounds`, + // `string_bounds`, `passed`, and `timestamp`. + google.protobuf.FieldMask update_mask = 2 [(google.api.field_behavior) = OPTIONAL]; +} + +// Response message for UpdateTestMeasurement +message UpdateTestMeasurementResponse { + // The updated test measurement + TestMeasurement test_measurement = 1; +} + +// Request message for DeleteTestMeasurement +message DeleteTestMeasurementRequest { + // The ID of the test measurement to delete + string measurement_id = 1 [(google.api.field_behavior) = REQUIRED]; +} + +// Response message for DeleteTestMeasurement +message DeleteTestMeasurementResponse { + // Empty response indicating successful deletion +} diff --git a/python/lib/sift/test_reports/__init__.py b/python/lib/sift/test_reports/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python/lib/sift/test_reports/v1/__init__.py b/python/lib/sift/test_reports/v1/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python/lib/sift/test_reports/v1/test_reports_pb2.py b/python/lib/sift/test_reports/v1/test_reports_pb2.py new file mode 100644 index 000000000..6d8eb5554 --- /dev/null +++ b/python/lib/sift/test_reports/v1/test_reports_pb2.py @@ -0,0 +1,296 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: sift/test_reports/v1/test_reports.proto +# Protobuf Python Version: 5.26.1 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.api import annotations_pb2 as google_dot_api_dot_annotations__pb2 +from google.api import field_behavior_pb2 as google_dot_api_dot_field__behavior__pb2 +from google.protobuf import field_mask_pb2 as google_dot_protobuf_dot_field__mask__pb2 +from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 +from protoc_gen_openapiv2.options import annotations_pb2 as protoc__gen__openapiv2_dot_options_dot_annotations__pb2 +from sift.metadata.v1 import metadata_pb2 as sift_dot_metadata_dot_v1_dot_metadata__pb2 +from sift.unit.v2 import unit_pb2 as sift_dot_unit_dot_v2_dot_unit__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\'sift/test_reports/v1/test_reports.proto\x12\x1dsift_internal.test_reports.v1\x1a\x1cgoogle/api/annotations.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a google/protobuf/field_mask.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a.protoc-gen-openapiv2/options/annotations.proto\x1a\x1fsift/metadata/v1/metadata.proto\x1a\x17sift/unit/v2/unit.proto\"\x91\x05\n\nTestReport\x12)\n\x0etest_report_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\x0ctestReportId\x12\x46\n\x06status\x18\x02 \x01(\x0e\x32).sift_internal.test_reports.v1.TestStatusB\x03\xe0\x41\x02R\x06status\x12\x17\n\x04name\x18\x03 \x01(\tB\x03\xe0\x41\x02R\x04name\x12-\n\x10test_system_name\x18\x04 \x01(\tB\x03\xe0\x41\x02R\x0etestSystemName\x12 \n\ttest_case\x18\x05 \x01(\tB\x03\xe0\x41\x02R\x08testCase\x12>\n\nstart_time\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\tstartTime\x12:\n\x08\x65nd_time\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\x07\x65ndTime\x12@\n\x08metadata\x18\x08 \x03(\x0b\x32\x1f.sift.metadata.v1.MetadataValueB\x03\xe0\x41\x01R\x08metadata\x12(\n\rserial_number\x18\t \x01(\tB\x03\xe0\x41\x01R\x0cserialNumber\x12$\n\x0bpart_number\x18\n \x01(\tB\x03\xe0\x41\x01R\npartNumber\x12,\n\x0fsystem_operator\x18\x0b \x01(\tB\x03\xe0\x41\x01R\x0esystemOperator\x12\x44\n\rarchived_date\x18\x0c \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x01R\x0c\x61rchivedDate\x12$\n\x0bis_archived\x18\r \x01(\x08\x42\x03\xe0\x41\x01R\nisArchived\"\xf0\x04\n\x08TestStep\x12%\n\x0ctest_step_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\ntestStepId\x12)\n\x0etest_report_id\x18\x02 \x01(\tB\x03\xe0\x41\x02R\x0ctestReportId\x12)\n\x0eparent_step_id\x18\x03 \x01(\tB\x03\xe0\x41\x01R\x0cparentStepId\x12\x17\n\x04name\x18\x04 \x01(\tB\x03\xe0\x41\x02R\x04name\x12%\n\x0b\x64\x65scription\x18\x05 \x01(\tB\x03\xe0\x41\x01R\x0b\x64\x65scription\x12M\n\tstep_type\x18\x06 \x01(\x0e\x32+.sift_internal.test_reports.v1.TestStepTypeB\x03\xe0\x41\x02R\x08stepType\x12$\n\x0bstep_number\x18\x07 \x01(\x05\x42\x03\xe0\x41\x02R\nstepNumber\x12 \n\tstep_path\x18\x08 \x01(\tB\x03\xe0\x41\x02R\x08stepPath\x12\x46\n\x06status\x18\t \x01(\x0e\x32).sift_internal.test_reports.v1.TestStatusB\x03\xe0\x41\x02R\x06status\x12>\n\nstart_time\x18\n \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\tstartTime\x12:\n\x08\x65nd_time\x18\x0b \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\x07\x65ndTime\x12L\n\nerror_info\x18\x0c \x01(\x0b\x32(.sift_internal.test_reports.v1.ErrorInfoB\x03\xe0\x41\x01R\terrorInfo\"Y\n\tErrorInfo\x12\"\n\nerror_code\x18\x01 \x01(\x05\x42\x03\xe0\x41\x02R\terrorCode\x12(\n\rerror_message\x18\x02 \x01(\tB\x03\xe0\x41\x02R\x0c\x65rrorMessage\"\xc6\x05\n\x0fTestMeasurement\x12*\n\x0emeasurement_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\rmeasurementId\x12\x62\n\x10measurement_type\x18\x02 \x01(\x0e\x32\x32.sift_internal.test_reports.v1.TestMeasurementTypeB\x03\xe0\x41\x02R\x0fmeasurementType\x12\x17\n\x04name\x18\x03 \x01(\tB\x03\xe0\x41\x02R\x04name\x12%\n\x0ctest_step_id\x18\x04 \x01(\tB\x03\xe0\x41\x02R\ntestStepId\x12)\n\x0etest_report_id\x18\x05 \x01(\tB\x03\xe0\x41\x02R\x0ctestReportId\x12%\n\rnumeric_value\x18\x06 \x01(\x01H\x00R\x0cnumericValue\x12#\n\x0cstring_value\x18\x07 \x01(\tH\x00R\x0bstringValue\x12%\n\rboolean_value\x18\x08 \x01(\x08H\x00R\x0c\x62ooleanValue\x12+\n\x04unit\x18\t \x01(\x0b\x32\x12.sift.unit.v2.UnitB\x03\xe0\x41\x01R\x04unit\x12U\n\x0enumeric_bounds\x18\n \x01(\x0b\x32,.sift_internal.test_reports.v1.NumericBoundsH\x01R\rnumericBounds\x12R\n\rstring_bounds\x18\x0b \x01(\x0b\x32+.sift_internal.test_reports.v1.StringBoundsH\x01R\x0cstringBounds\x12\x1b\n\x06passed\x18\x0c \x01(\x08\x42\x03\xe0\x41\x02R\x06passed\x12=\n\ttimestamp\x18\r \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\ttimestampB\x07\n\x05valueB\x08\n\x06\x62ounds\"W\n\rNumericBounds\x12\x1a\n\x03min\x18\x01 \x01(\x01\x42\x03\xe0\x41\x01H\x00R\x03min\x88\x01\x01\x12\x1a\n\x03max\x18\x02 \x01(\x01\x42\x03\xe0\x41\x01H\x01R\x03max\x88\x01\x01\x42\x06\n\x04_minB\x06\n\x04_max\":\n\x0cStringBounds\x12*\n\x0e\x65xpected_value\x18\x01 \x01(\tB\x03\xe0\x41\x02R\rexpectedValue\"D\n\x17ImportTestReportRequest\x12)\n\x0eremote_file_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\x0cremoteFileId\"f\n\x18ImportTestReportResponse\x12J\n\x0btest_report\x18\x01 \x01(\x0b\x32).sift_internal.test_reports.v1.TestReportR\ntestReport\"\x87\x04\n\x17\x43reateTestReportRequest\x12\x46\n\x06status\x18\x01 \x01(\x0e\x32).sift_internal.test_reports.v1.TestStatusB\x03\xe0\x41\x02R\x06status\x12\x17\n\x04name\x18\x02 \x01(\tB\x03\xe0\x41\x02R\x04name\x12-\n\x10test_system_name\x18\x03 \x01(\tB\x03\xe0\x41\x02R\x0etestSystemName\x12 \n\ttest_case\x18\x04 \x01(\tB\x03\xe0\x41\x02R\x08testCase\x12>\n\nstart_time\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\tstartTime\x12:\n\x08\x65nd_time\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x02R\x07\x65ndTime\x12@\n\x08metadata\x18\x07 \x03(\x0b\x32\x1f.sift.metadata.v1.MetadataValueB\x03\xe0\x41\x01R\x08metadata\x12(\n\rserial_number\x18\x08 \x01(\tB\x03\xe0\x41\x01R\x0cserialNumber\x12$\n\x0bpart_number\x18\t \x01(\tB\x03\xe0\x41\x01R\npartNumber\x12,\n\x0fsystem_operator\x18\n \x01(\tB\x03\xe0\x41\x01R\x0esystemOperator\"f\n\x18\x43reateTestReportResponse\x12J\n\x0btest_report\x18\x01 \x01(\x0b\x32).sift_internal.test_reports.v1.TestReportR\ntestReport\"A\n\x14GetTestReportRequest\x12)\n\x0etest_report_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\x0ctestReportId\"c\n\x15GetTestReportResponse\x12J\n\x0btest_report\x18\x01 \x01(\x0b\x32).sift_internal.test_reports.v1.TestReportR\ntestReport\"\x9b\x01\n\x16ListTestReportsRequest\x12 \n\tpage_size\x18\x01 \x01(\rB\x03\xe0\x41\x01R\x08pageSize\x12\"\n\npage_token\x18\x02 \x01(\tB\x03\xe0\x41\x01R\tpageToken\x12\x1b\n\x06\x66ilter\x18\x03 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\x12\x1e\n\x08order_by\x18\x04 \x01(\tB\x03\xe0\x41\x01R\x07orderBy\"\x8f\x01\n\x17ListTestReportsResponse\x12L\n\x0ctest_reports\x18\x01 \x03(\x0b\x32).sift_internal.test_reports.v1.TestReportR\x0btestReports\x12&\n\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"\xac\x01\n\x17UpdateTestReportRequest\x12O\n\x0btest_report\x18\x01 \x01(\x0b\x32).sift_internal.test_reports.v1.TestReportB\x03\xe0\x41\x02R\ntestReport\x12@\n\x0bupdate_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMaskB\x03\xe0\x41\x01R\nupdateMask\"f\n\x18UpdateTestReportResponse\x12J\n\x0btest_report\x18\x01 \x01(\x0b\x32).sift_internal.test_reports.v1.TestReportR\ntestReport\"D\n\x17\x44\x65leteTestReportRequest\x12)\n\x0etest_report_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\x0ctestReportId\"\x1a\n\x18\x44\x65leteTestReportResponse\"b\n\x15\x43reateTestStepRequest\x12I\n\ttest_step\x18\x01 \x01(\x0b\x32\'.sift_internal.test_reports.v1.TestStepB\x03\xe0\x41\x02R\x08testStep\"^\n\x16\x43reateTestStepResponse\x12\x44\n\ttest_step\x18\x01 \x01(\x0b\x32\'.sift_internal.test_reports.v1.TestStepR\x08testStep\"\x99\x01\n\x14ListTestStepsRequest\x12 \n\tpage_size\x18\x01 \x01(\rB\x03\xe0\x41\x01R\x08pageSize\x12\"\n\npage_token\x18\x02 \x01(\tB\x03\xe0\x41\x01R\tpageToken\x12\x1b\n\x06\x66ilter\x18\x03 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\x12\x1e\n\x08order_by\x18\x04 \x01(\tB\x03\xe0\x41\x01R\x07orderBy\"\x87\x01\n\x15ListTestStepsResponse\x12\x46\n\ntest_steps\x18\x01 \x03(\x0b\x32\'.sift_internal.test_reports.v1.TestStepR\ttestSteps\x12&\n\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"\xa4\x01\n\x15UpdateTestStepRequest\x12I\n\ttest_step\x18\x01 \x01(\x0b\x32\'.sift_internal.test_reports.v1.TestStepB\x03\xe0\x41\x02R\x08testStep\x12@\n\x0bupdate_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMaskB\x03\xe0\x41\x01R\nupdateMask\"^\n\x16UpdateTestStepResponse\x12\x44\n\ttest_step\x18\x01 \x01(\x0b\x32\'.sift_internal.test_reports.v1.TestStepR\x08testStep\">\n\x15\x44\x65leteTestStepRequest\x12%\n\x0ctest_step_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\ntestStepId\"\x18\n\x16\x44\x65leteTestStepResponse\"~\n\x1c\x43reateTestMeasurementRequest\x12^\n\x10test_measurement\x18\x01 \x01(\x0b\x32..sift_internal.test_reports.v1.TestMeasurementB\x03\xe0\x41\x02R\x0ftestMeasurement\"z\n\x1d\x43reateTestMeasurementResponse\x12Y\n\x10test_measurement\x18\x01 \x01(\x0b\x32..sift_internal.test_reports.v1.TestMeasurementR\x0ftestMeasurement\"\x81\x01\n\x1d\x43reateTestMeasurementsRequest\x12`\n\x11test_measurements\x18\x01 \x03(\x0b\x32..sift_internal.test_reports.v1.TestMeasurementB\x03\xe0\x41\x02R\x10testMeasurements\"\x91\x01\n\x1e\x43reateTestMeasurementsResponse\x12\x41\n\x1ameasurements_created_count\x18\x01 \x01(\x05\x42\x03\xe0\x41\x02R\x18measurementsCreatedCount\x12,\n\x0fmeasurement_ids\x18\x02 \x03(\tB\x03\xe0\x41\x02R\x0emeasurementIds\"\xa0\x01\n\x1bListTestMeasurementsRequest\x12 \n\tpage_size\x18\x01 \x01(\rB\x03\xe0\x41\x01R\x08pageSize\x12\"\n\npage_token\x18\x02 \x01(\tB\x03\xe0\x41\x01R\tpageToken\x12\x1b\n\x06\x66ilter\x18\x03 \x01(\tB\x03\xe0\x41\x01R\x06\x66ilter\x12\x1e\n\x08order_by\x18\x04 \x01(\tB\x03\xe0\x41\x01R\x07orderBy\"\xa3\x01\n\x1cListTestMeasurementsResponse\x12[\n\x11test_measurements\x18\x01 \x03(\x0b\x32..sift_internal.test_reports.v1.TestMeasurementR\x10testMeasurements\x12&\n\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"\xc0\x01\n\x1cUpdateTestMeasurementRequest\x12^\n\x10test_measurement\x18\x01 \x01(\x0b\x32..sift_internal.test_reports.v1.TestMeasurementB\x03\xe0\x41\x02R\x0ftestMeasurement\x12@\n\x0bupdate_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMaskB\x03\xe0\x41\x01R\nupdateMask\"z\n\x1dUpdateTestMeasurementResponse\x12Y\n\x10test_measurement\x18\x01 \x01(\x0b\x32..sift_internal.test_reports.v1.TestMeasurementR\x0ftestMeasurement\"J\n\x1c\x44\x65leteTestMeasurementRequest\x12*\n\x0emeasurement_id\x18\x01 \x01(\tB\x03\xe0\x41\x02R\rmeasurementId\"\x1f\n\x1d\x44\x65leteTestMeasurementResponse*\xd6\x01\n\nTestStatus\x12\x1b\n\x17TEST_STATUS_UNSPECIFIED\x10\x00\x12\x15\n\x11TEST_STATUS_DRAFT\x10\x01\x12\x16\n\x12TEST_STATUS_PASSED\x10\x02\x12\x16\n\x12TEST_STATUS_FAILED\x10\x03\x12\x17\n\x13TEST_STATUS_ABORTED\x10\x04\x12\x15\n\x11TEST_STATUS_ERROR\x10\x05\x12\x1b\n\x17TEST_STATUS_IN_PROGRESS\x10\x06\x12\x17\n\x13TEST_STATUS_SKIPPED\x10\x07*\xa1\x01\n\x0cTestStepType\x12\x1e\n\x1aTEST_STEP_TYPE_UNSPECIFIED\x10\x00\x12\x1b\n\x17TEST_STEP_TYPE_SEQUENCE\x10\x01\x12\x18\n\x14TEST_STEP_TYPE_GROUP\x10\x02\x12\x19\n\x15TEST_STEP_TYPE_ACTION\x10\x03\x12\x1f\n\x1bTEST_STEP_TYPE_FLOW_CONTROL\x10\x04*\xc4\x01\n\x13TestMeasurementType\x12%\n!TEST_MEASUREMENT_TYPE_UNSPECIFIED\x10\x00\x12 \n\x1cTEST_MEASUREMENT_TYPE_DOUBLE\x10\x01\x12 \n\x1cTEST_MEASUREMENT_TYPE_STRING\x10\x03\x12!\n\x1dTEST_MEASUREMENT_TYPE_BOOLEAN\x10\x04\x12\x1f\n\x1bTEST_MEASUREMENT_TYPE_LIMIT\x10\x05\x32\x9f\x1b\n\x11TestReportService\x12\xef\x01\n\x10ImportTestReport\x12\x36.sift_internal.test_reports.v1.ImportTestReportRequest\x1a\x37.sift_internal.test_reports.v1.ImportTestReportResponse\"j\x92\x41H\x12\x10ImportTestReport\x1a\x34Imports a test report from an already-uploaded file.\x82\xd3\xe4\x93\x02\x19\"\x14/api/v1/test-reports:\x01*\x12\xd7\x01\n\x10\x43reateTestReport\x12\x36.sift_internal.test_reports.v1.CreateTestReportRequest\x1a\x37.sift_internal.test_reports.v1.CreateTestReportResponse\"R\x92\x41)\x12\x10\x43reateTestReport\x1a\x15\x43reates a test report\x82\xd3\xe4\x93\x02 \"\x1b/api/v1/test-reports:create:\x01*\x12\xd6\x01\n\rGetTestReport\x12\x33.sift_internal.test_reports.v1.GetTestReportRequest\x1a\x34.sift_internal.test_reports.v1.GetTestReportResponse\"Z\x92\x41*\x12\rGetTestReport\x1a\x19Gets a single test report\x82\xd3\xe4\x93\x02\'\x12%/api/v1/test-reports/{test_report_id}\x12\xde\x01\n\x0fListTestReports\x12\x35.sift_internal.test_reports.v1.ListTestReportsRequest\x1a\x36.sift_internal.test_reports.v1.ListTestReportsResponse\"\\\x92\x41=\x12\x0fListTestReports\x1a*Lists test reports with optional filtering\x82\xd3\xe4\x93\x02\x16\x12\x14/api/v1/test-reports\x12\xd0\x01\n\x10UpdateTestReport\x12\x36.sift_internal.test_reports.v1.UpdateTestReportRequest\x1a\x37.sift_internal.test_reports.v1.UpdateTestReportResponse\"K\x92\x41)\x12\x10UpdateTestReport\x1a\x15Updates a test report\x82\xd3\xe4\x93\x02\x19\x32\x14/api/v1/test-reports:\x01*\x12\xde\x01\n\x10\x44\x65leteTestReport\x12\x36.sift_internal.test_reports.v1.DeleteTestReportRequest\x1a\x37.sift_internal.test_reports.v1.DeleteTestReportResponse\"Y\x92\x41)\x12\x10\x44\x65leteTestReport\x1a\x15\x44\x65letes a test report\x82\xd3\xe4\x93\x02\'*%/api/v1/test-reports/{test_report_id}\x12\xc4\x01\n\x0e\x43reateTestStep\x12\x34.sift_internal.test_reports.v1.CreateTestStepRequest\x1a\x35.sift_internal.test_reports.v1.CreateTestStepResponse\"E\x92\x41%\x12\x0e\x43reateTestStep\x1a\x13\x43reates a test step\x82\xd3\xe4\x93\x02\x17\"\x12/api/v1/test-steps:\x01*\x12\xd2\x01\n\rListTestSteps\x12\x33.sift_internal.test_reports.v1.ListTestStepsRequest\x1a\x34.sift_internal.test_reports.v1.ListTestStepsResponse\"V\x92\x41\x39\x12\rListTestSteps\x1a(Lists test steps with optional filtering\x82\xd3\xe4\x93\x02\x14\x12\x12/api/v1/test-steps\x12\xc4\x01\n\x0eUpdateTestStep\x12\x34.sift_internal.test_reports.v1.UpdateTestStepRequest\x1a\x35.sift_internal.test_reports.v1.UpdateTestStepResponse\"E\x92\x41%\x12\x0eUpdateTestStep\x1a\x13Updates a test step\x82\xd3\xe4\x93\x02\x17\x32\x12/api/v1/test-steps:\x01*\x12\xd0\x01\n\x0e\x44\x65leteTestStep\x12\x34.sift_internal.test_reports.v1.DeleteTestStepRequest\x1a\x35.sift_internal.test_reports.v1.DeleteTestStepResponse\"Q\x92\x41%\x12\x0e\x44\x65leteTestStep\x1a\x13\x44\x65letes a test step\x82\xd3\xe4\x93\x02#*!/api/v1/test-steps/{test_step_id}\x12\xee\x01\n\x15\x43reateTestMeasurement\x12;.sift_internal.test_reports.v1.CreateTestMeasurementRequest\x1a<.sift_internal.test_reports.v1.CreateTestMeasurementResponse\"Z\x92\x41\x33\x12\x15\x43reateTestMeasurement\x1a\x1a\x43reates a test measurement\x82\xd3\xe4\x93\x02\x1e\"\x19/api/v1/test-measurements:\x01*\x12\x94\x02\n\x16\x43reateTestMeasurements\x12<.sift_internal.test_reports.v1.CreateTestMeasurementsRequest\x1a=.sift_internal.test_reports.v1.CreateTestMeasurementsResponse\"}\x92\x41P\x12\x16\x43reateTestMeasurements\x1a\x36\x43reates multiple test measurements in a single request\x82\xd3\xe4\x93\x02$\"\x1f/api/v1/test-measurements:batch:\x01*\x12\xfc\x01\n\x14ListTestMeasurements\x12:.sift_internal.test_reports.v1.ListTestMeasurementsRequest\x1a;.sift_internal.test_reports.v1.ListTestMeasurementsResponse\"k\x92\x41G\x12\x14ListTestMeasurements\x1a/Lists test measurements with optional filtering\x82\xd3\xe4\x93\x02\x1b\x12\x19/api/v1/test-measurements\x12\xee\x01\n\x15UpdateTestMeasurement\x12;.sift_internal.test_reports.v1.UpdateTestMeasurementRequest\x1a<.sift_internal.test_reports.v1.UpdateTestMeasurementResponse\"Z\x92\x41\x33\x12\x15UpdateTestMeasurement\x1a\x1aUpdates a test measurement\x82\xd3\xe4\x93\x02\x1e\x32\x19/api/v1/test-measurements:\x01*\x12\xfc\x01\n\x15\x44\x65leteTestMeasurement\x12;.sift_internal.test_reports.v1.DeleteTestMeasurementRequest\x1a<.sift_internal.test_reports.v1.DeleteTestMeasurementResponse\"h\x92\x41\x33\x12\x15\x44\x65leteTestMeasurement\x1a\x1a\x44\x65letes a test measurement\x82\xd3\xe4\x93\x02,**/api/v1/test-measurements/{measurement_id}\x1a#\x92\x41 \x12\x1eService to manage test reportsB\xde\x01\n!com.sift_internal.test_reports.v1B\x10TestReportsProtoP\x01\xa2\x02\x03STX\xaa\x02\x1bSiftInternal.TestReports.V1\xca\x02\x1bSiftInternal\\TestReports\\V1\xe2\x02\'SiftInternal\\TestReports\\V1\\GPBMetadata\xea\x02\x1dSiftInternal::TestReports::V1\x92\x41\x18\x12\x16\n\x14Test Results Serviceb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'sift.test_reports.v1.test_reports_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n!com.sift_internal.test_reports.v1B\020TestReportsProtoP\001\242\002\003STX\252\002\033SiftInternal.TestReports.V1\312\002\033SiftInternal\\TestReports\\V1\342\002\'SiftInternal\\TestReports\\V1\\GPBMetadata\352\002\035SiftInternal::TestReports::V1\222A\030\022\026\n\024Test Results Service' + _globals['_TESTREPORT'].fields_by_name['test_report_id']._loaded_options = None + _globals['_TESTREPORT'].fields_by_name['test_report_id']._serialized_options = b'\340A\002' + _globals['_TESTREPORT'].fields_by_name['status']._loaded_options = None + _globals['_TESTREPORT'].fields_by_name['status']._serialized_options = b'\340A\002' + _globals['_TESTREPORT'].fields_by_name['name']._loaded_options = None + _globals['_TESTREPORT'].fields_by_name['name']._serialized_options = b'\340A\002' + _globals['_TESTREPORT'].fields_by_name['test_system_name']._loaded_options = None + _globals['_TESTREPORT'].fields_by_name['test_system_name']._serialized_options = b'\340A\002' + _globals['_TESTREPORT'].fields_by_name['test_case']._loaded_options = None + _globals['_TESTREPORT'].fields_by_name['test_case']._serialized_options = b'\340A\002' + _globals['_TESTREPORT'].fields_by_name['start_time']._loaded_options = None + _globals['_TESTREPORT'].fields_by_name['start_time']._serialized_options = b'\340A\002' + _globals['_TESTREPORT'].fields_by_name['end_time']._loaded_options = None + _globals['_TESTREPORT'].fields_by_name['end_time']._serialized_options = b'\340A\002' + _globals['_TESTREPORT'].fields_by_name['metadata']._loaded_options = None + _globals['_TESTREPORT'].fields_by_name['metadata']._serialized_options = b'\340A\001' + _globals['_TESTREPORT'].fields_by_name['serial_number']._loaded_options = None + _globals['_TESTREPORT'].fields_by_name['serial_number']._serialized_options = b'\340A\001' + _globals['_TESTREPORT'].fields_by_name['part_number']._loaded_options = None + _globals['_TESTREPORT'].fields_by_name['part_number']._serialized_options = b'\340A\001' + _globals['_TESTREPORT'].fields_by_name['system_operator']._loaded_options = None + _globals['_TESTREPORT'].fields_by_name['system_operator']._serialized_options = b'\340A\001' + _globals['_TESTREPORT'].fields_by_name['archived_date']._loaded_options = None + _globals['_TESTREPORT'].fields_by_name['archived_date']._serialized_options = b'\340A\001' + _globals['_TESTREPORT'].fields_by_name['is_archived']._loaded_options = None + _globals['_TESTREPORT'].fields_by_name['is_archived']._serialized_options = b'\340A\001' + _globals['_TESTSTEP'].fields_by_name['test_step_id']._loaded_options = None + _globals['_TESTSTEP'].fields_by_name['test_step_id']._serialized_options = b'\340A\002' + _globals['_TESTSTEP'].fields_by_name['test_report_id']._loaded_options = None + _globals['_TESTSTEP'].fields_by_name['test_report_id']._serialized_options = b'\340A\002' + _globals['_TESTSTEP'].fields_by_name['parent_step_id']._loaded_options = None + _globals['_TESTSTEP'].fields_by_name['parent_step_id']._serialized_options = b'\340A\001' + _globals['_TESTSTEP'].fields_by_name['name']._loaded_options = None + _globals['_TESTSTEP'].fields_by_name['name']._serialized_options = b'\340A\002' + _globals['_TESTSTEP'].fields_by_name['description']._loaded_options = None + _globals['_TESTSTEP'].fields_by_name['description']._serialized_options = b'\340A\001' + _globals['_TESTSTEP'].fields_by_name['step_type']._loaded_options = None + _globals['_TESTSTEP'].fields_by_name['step_type']._serialized_options = b'\340A\002' + _globals['_TESTSTEP'].fields_by_name['step_number']._loaded_options = None + _globals['_TESTSTEP'].fields_by_name['step_number']._serialized_options = b'\340A\002' + _globals['_TESTSTEP'].fields_by_name['step_path']._loaded_options = None + _globals['_TESTSTEP'].fields_by_name['step_path']._serialized_options = b'\340A\002' + _globals['_TESTSTEP'].fields_by_name['status']._loaded_options = None + _globals['_TESTSTEP'].fields_by_name['status']._serialized_options = b'\340A\002' + _globals['_TESTSTEP'].fields_by_name['start_time']._loaded_options = None + _globals['_TESTSTEP'].fields_by_name['start_time']._serialized_options = b'\340A\002' + _globals['_TESTSTEP'].fields_by_name['end_time']._loaded_options = None + _globals['_TESTSTEP'].fields_by_name['end_time']._serialized_options = b'\340A\002' + _globals['_TESTSTEP'].fields_by_name['error_info']._loaded_options = None + _globals['_TESTSTEP'].fields_by_name['error_info']._serialized_options = b'\340A\001' + _globals['_ERRORINFO'].fields_by_name['error_code']._loaded_options = None + _globals['_ERRORINFO'].fields_by_name['error_code']._serialized_options = b'\340A\002' + _globals['_ERRORINFO'].fields_by_name['error_message']._loaded_options = None + _globals['_ERRORINFO'].fields_by_name['error_message']._serialized_options = b'\340A\002' + _globals['_TESTMEASUREMENT'].fields_by_name['measurement_id']._loaded_options = None + _globals['_TESTMEASUREMENT'].fields_by_name['measurement_id']._serialized_options = b'\340A\002' + _globals['_TESTMEASUREMENT'].fields_by_name['measurement_type']._loaded_options = None + _globals['_TESTMEASUREMENT'].fields_by_name['measurement_type']._serialized_options = b'\340A\002' + _globals['_TESTMEASUREMENT'].fields_by_name['name']._loaded_options = None + _globals['_TESTMEASUREMENT'].fields_by_name['name']._serialized_options = b'\340A\002' + _globals['_TESTMEASUREMENT'].fields_by_name['test_step_id']._loaded_options = None + _globals['_TESTMEASUREMENT'].fields_by_name['test_step_id']._serialized_options = b'\340A\002' + _globals['_TESTMEASUREMENT'].fields_by_name['test_report_id']._loaded_options = None + _globals['_TESTMEASUREMENT'].fields_by_name['test_report_id']._serialized_options = b'\340A\002' + _globals['_TESTMEASUREMENT'].fields_by_name['unit']._loaded_options = None + _globals['_TESTMEASUREMENT'].fields_by_name['unit']._serialized_options = b'\340A\001' + _globals['_TESTMEASUREMENT'].fields_by_name['passed']._loaded_options = None + _globals['_TESTMEASUREMENT'].fields_by_name['passed']._serialized_options = b'\340A\002' + _globals['_TESTMEASUREMENT'].fields_by_name['timestamp']._loaded_options = None + _globals['_TESTMEASUREMENT'].fields_by_name['timestamp']._serialized_options = b'\340A\002' + _globals['_NUMERICBOUNDS'].fields_by_name['min']._loaded_options = None + _globals['_NUMERICBOUNDS'].fields_by_name['min']._serialized_options = b'\340A\001' + _globals['_NUMERICBOUNDS'].fields_by_name['max']._loaded_options = None + _globals['_NUMERICBOUNDS'].fields_by_name['max']._serialized_options = b'\340A\001' + _globals['_STRINGBOUNDS'].fields_by_name['expected_value']._loaded_options = None + _globals['_STRINGBOUNDS'].fields_by_name['expected_value']._serialized_options = b'\340A\002' + _globals['_IMPORTTESTREPORTREQUEST'].fields_by_name['remote_file_id']._loaded_options = None + _globals['_IMPORTTESTREPORTREQUEST'].fields_by_name['remote_file_id']._serialized_options = b'\340A\002' + _globals['_CREATETESTREPORTREQUEST'].fields_by_name['status']._loaded_options = None + _globals['_CREATETESTREPORTREQUEST'].fields_by_name['status']._serialized_options = b'\340A\002' + _globals['_CREATETESTREPORTREQUEST'].fields_by_name['name']._loaded_options = None + _globals['_CREATETESTREPORTREQUEST'].fields_by_name['name']._serialized_options = b'\340A\002' + _globals['_CREATETESTREPORTREQUEST'].fields_by_name['test_system_name']._loaded_options = None + _globals['_CREATETESTREPORTREQUEST'].fields_by_name['test_system_name']._serialized_options = b'\340A\002' + _globals['_CREATETESTREPORTREQUEST'].fields_by_name['test_case']._loaded_options = None + _globals['_CREATETESTREPORTREQUEST'].fields_by_name['test_case']._serialized_options = b'\340A\002' + _globals['_CREATETESTREPORTREQUEST'].fields_by_name['start_time']._loaded_options = None + _globals['_CREATETESTREPORTREQUEST'].fields_by_name['start_time']._serialized_options = b'\340A\002' + _globals['_CREATETESTREPORTREQUEST'].fields_by_name['end_time']._loaded_options = None + _globals['_CREATETESTREPORTREQUEST'].fields_by_name['end_time']._serialized_options = b'\340A\002' + _globals['_CREATETESTREPORTREQUEST'].fields_by_name['metadata']._loaded_options = None + _globals['_CREATETESTREPORTREQUEST'].fields_by_name['metadata']._serialized_options = b'\340A\001' + _globals['_CREATETESTREPORTREQUEST'].fields_by_name['serial_number']._loaded_options = None + _globals['_CREATETESTREPORTREQUEST'].fields_by_name['serial_number']._serialized_options = b'\340A\001' + _globals['_CREATETESTREPORTREQUEST'].fields_by_name['part_number']._loaded_options = None + _globals['_CREATETESTREPORTREQUEST'].fields_by_name['part_number']._serialized_options = b'\340A\001' + _globals['_CREATETESTREPORTREQUEST'].fields_by_name['system_operator']._loaded_options = None + _globals['_CREATETESTREPORTREQUEST'].fields_by_name['system_operator']._serialized_options = b'\340A\001' + _globals['_GETTESTREPORTREQUEST'].fields_by_name['test_report_id']._loaded_options = None + _globals['_GETTESTREPORTREQUEST'].fields_by_name['test_report_id']._serialized_options = b'\340A\002' + _globals['_LISTTESTREPORTSREQUEST'].fields_by_name['page_size']._loaded_options = None + _globals['_LISTTESTREPORTSREQUEST'].fields_by_name['page_size']._serialized_options = b'\340A\001' + _globals['_LISTTESTREPORTSREQUEST'].fields_by_name['page_token']._loaded_options = None + _globals['_LISTTESTREPORTSREQUEST'].fields_by_name['page_token']._serialized_options = b'\340A\001' + _globals['_LISTTESTREPORTSREQUEST'].fields_by_name['filter']._loaded_options = None + _globals['_LISTTESTREPORTSREQUEST'].fields_by_name['filter']._serialized_options = b'\340A\001' + _globals['_LISTTESTREPORTSREQUEST'].fields_by_name['order_by']._loaded_options = None + _globals['_LISTTESTREPORTSREQUEST'].fields_by_name['order_by']._serialized_options = b'\340A\001' + _globals['_UPDATETESTREPORTREQUEST'].fields_by_name['test_report']._loaded_options = None + _globals['_UPDATETESTREPORTREQUEST'].fields_by_name['test_report']._serialized_options = b'\340A\002' + _globals['_UPDATETESTREPORTREQUEST'].fields_by_name['update_mask']._loaded_options = None + _globals['_UPDATETESTREPORTREQUEST'].fields_by_name['update_mask']._serialized_options = b'\340A\001' + _globals['_DELETETESTREPORTREQUEST'].fields_by_name['test_report_id']._loaded_options = None + _globals['_DELETETESTREPORTREQUEST'].fields_by_name['test_report_id']._serialized_options = b'\340A\002' + _globals['_CREATETESTSTEPREQUEST'].fields_by_name['test_step']._loaded_options = None + _globals['_CREATETESTSTEPREQUEST'].fields_by_name['test_step']._serialized_options = b'\340A\002' + _globals['_LISTTESTSTEPSREQUEST'].fields_by_name['page_size']._loaded_options = None + _globals['_LISTTESTSTEPSREQUEST'].fields_by_name['page_size']._serialized_options = b'\340A\001' + _globals['_LISTTESTSTEPSREQUEST'].fields_by_name['page_token']._loaded_options = None + _globals['_LISTTESTSTEPSREQUEST'].fields_by_name['page_token']._serialized_options = b'\340A\001' + _globals['_LISTTESTSTEPSREQUEST'].fields_by_name['filter']._loaded_options = None + _globals['_LISTTESTSTEPSREQUEST'].fields_by_name['filter']._serialized_options = b'\340A\001' + _globals['_LISTTESTSTEPSREQUEST'].fields_by_name['order_by']._loaded_options = None + _globals['_LISTTESTSTEPSREQUEST'].fields_by_name['order_by']._serialized_options = b'\340A\001' + _globals['_UPDATETESTSTEPREQUEST'].fields_by_name['test_step']._loaded_options = None + _globals['_UPDATETESTSTEPREQUEST'].fields_by_name['test_step']._serialized_options = b'\340A\002' + _globals['_UPDATETESTSTEPREQUEST'].fields_by_name['update_mask']._loaded_options = None + _globals['_UPDATETESTSTEPREQUEST'].fields_by_name['update_mask']._serialized_options = b'\340A\001' + _globals['_DELETETESTSTEPREQUEST'].fields_by_name['test_step_id']._loaded_options = None + _globals['_DELETETESTSTEPREQUEST'].fields_by_name['test_step_id']._serialized_options = b'\340A\002' + _globals['_CREATETESTMEASUREMENTREQUEST'].fields_by_name['test_measurement']._loaded_options = None + _globals['_CREATETESTMEASUREMENTREQUEST'].fields_by_name['test_measurement']._serialized_options = b'\340A\002' + _globals['_CREATETESTMEASUREMENTSREQUEST'].fields_by_name['test_measurements']._loaded_options = None + _globals['_CREATETESTMEASUREMENTSREQUEST'].fields_by_name['test_measurements']._serialized_options = b'\340A\002' + _globals['_CREATETESTMEASUREMENTSRESPONSE'].fields_by_name['measurements_created_count']._loaded_options = None + _globals['_CREATETESTMEASUREMENTSRESPONSE'].fields_by_name['measurements_created_count']._serialized_options = b'\340A\002' + _globals['_CREATETESTMEASUREMENTSRESPONSE'].fields_by_name['measurement_ids']._loaded_options = None + _globals['_CREATETESTMEASUREMENTSRESPONSE'].fields_by_name['measurement_ids']._serialized_options = b'\340A\002' + _globals['_LISTTESTMEASUREMENTSREQUEST'].fields_by_name['page_size']._loaded_options = None + _globals['_LISTTESTMEASUREMENTSREQUEST'].fields_by_name['page_size']._serialized_options = b'\340A\001' + _globals['_LISTTESTMEASUREMENTSREQUEST'].fields_by_name['page_token']._loaded_options = None + _globals['_LISTTESTMEASUREMENTSREQUEST'].fields_by_name['page_token']._serialized_options = b'\340A\001' + _globals['_LISTTESTMEASUREMENTSREQUEST'].fields_by_name['filter']._loaded_options = None + _globals['_LISTTESTMEASUREMENTSREQUEST'].fields_by_name['filter']._serialized_options = b'\340A\001' + _globals['_LISTTESTMEASUREMENTSREQUEST'].fields_by_name['order_by']._loaded_options = None + _globals['_LISTTESTMEASUREMENTSREQUEST'].fields_by_name['order_by']._serialized_options = b'\340A\001' + _globals['_UPDATETESTMEASUREMENTREQUEST'].fields_by_name['test_measurement']._loaded_options = None + _globals['_UPDATETESTMEASUREMENTREQUEST'].fields_by_name['test_measurement']._serialized_options = b'\340A\002' + _globals['_UPDATETESTMEASUREMENTREQUEST'].fields_by_name['update_mask']._loaded_options = None + _globals['_UPDATETESTMEASUREMENTREQUEST'].fields_by_name['update_mask']._serialized_options = b'\340A\001' + _globals['_DELETETESTMEASUREMENTREQUEST'].fields_by_name['measurement_id']._loaded_options = None + _globals['_DELETETESTMEASUREMENTREQUEST'].fields_by_name['measurement_id']._serialized_options = b'\340A\002' + _globals['_TESTREPORTSERVICE']._loaded_options = None + _globals['_TESTREPORTSERVICE']._serialized_options = b'\222A \022\036Service to manage test reports' + _globals['_TESTREPORTSERVICE'].methods_by_name['ImportTestReport']._loaded_options = None + _globals['_TESTREPORTSERVICE'].methods_by_name['ImportTestReport']._serialized_options = b'\222AH\022\020ImportTestReport\0324Imports a test report from an already-uploaded file.\202\323\344\223\002\031\"\024/api/v1/test-reports:\001*' + _globals['_TESTREPORTSERVICE'].methods_by_name['CreateTestReport']._loaded_options = None + _globals['_TESTREPORTSERVICE'].methods_by_name['CreateTestReport']._serialized_options = b'\222A)\022\020CreateTestReport\032\025Creates a test report\202\323\344\223\002 \"\033/api/v1/test-reports:create:\001*' + _globals['_TESTREPORTSERVICE'].methods_by_name['GetTestReport']._loaded_options = None + _globals['_TESTREPORTSERVICE'].methods_by_name['GetTestReport']._serialized_options = b'\222A*\022\rGetTestReport\032\031Gets a single test report\202\323\344\223\002\'\022%/api/v1/test-reports/{test_report_id}' + _globals['_TESTREPORTSERVICE'].methods_by_name['ListTestReports']._loaded_options = None + _globals['_TESTREPORTSERVICE'].methods_by_name['ListTestReports']._serialized_options = b'\222A=\022\017ListTestReports\032*Lists test reports with optional filtering\202\323\344\223\002\026\022\024/api/v1/test-reports' + _globals['_TESTREPORTSERVICE'].methods_by_name['UpdateTestReport']._loaded_options = None + _globals['_TESTREPORTSERVICE'].methods_by_name['UpdateTestReport']._serialized_options = b'\222A)\022\020UpdateTestReport\032\025Updates a test report\202\323\344\223\002\0312\024/api/v1/test-reports:\001*' + _globals['_TESTREPORTSERVICE'].methods_by_name['DeleteTestReport']._loaded_options = None + _globals['_TESTREPORTSERVICE'].methods_by_name['DeleteTestReport']._serialized_options = b'\222A)\022\020DeleteTestReport\032\025Deletes a test report\202\323\344\223\002\'*%/api/v1/test-reports/{test_report_id}' + _globals['_TESTREPORTSERVICE'].methods_by_name['CreateTestStep']._loaded_options = None + _globals['_TESTREPORTSERVICE'].methods_by_name['CreateTestStep']._serialized_options = b'\222A%\022\016CreateTestStep\032\023Creates a test step\202\323\344\223\002\027\"\022/api/v1/test-steps:\001*' + _globals['_TESTREPORTSERVICE'].methods_by_name['ListTestSteps']._loaded_options = None + _globals['_TESTREPORTSERVICE'].methods_by_name['ListTestSteps']._serialized_options = b'\222A9\022\rListTestSteps\032(Lists test steps with optional filtering\202\323\344\223\002\024\022\022/api/v1/test-steps' + _globals['_TESTREPORTSERVICE'].methods_by_name['UpdateTestStep']._loaded_options = None + _globals['_TESTREPORTSERVICE'].methods_by_name['UpdateTestStep']._serialized_options = b'\222A%\022\016UpdateTestStep\032\023Updates a test step\202\323\344\223\002\0272\022/api/v1/test-steps:\001*' + _globals['_TESTREPORTSERVICE'].methods_by_name['DeleteTestStep']._loaded_options = None + _globals['_TESTREPORTSERVICE'].methods_by_name['DeleteTestStep']._serialized_options = b'\222A%\022\016DeleteTestStep\032\023Deletes a test step\202\323\344\223\002#*!/api/v1/test-steps/{test_step_id}' + _globals['_TESTREPORTSERVICE'].methods_by_name['CreateTestMeasurement']._loaded_options = None + _globals['_TESTREPORTSERVICE'].methods_by_name['CreateTestMeasurement']._serialized_options = b'\222A3\022\025CreateTestMeasurement\032\032Creates a test measurement\202\323\344\223\002\036\"\031/api/v1/test-measurements:\001*' + _globals['_TESTREPORTSERVICE'].methods_by_name['CreateTestMeasurements']._loaded_options = None + _globals['_TESTREPORTSERVICE'].methods_by_name['CreateTestMeasurements']._serialized_options = b'\222AP\022\026CreateTestMeasurements\0326Creates multiple test measurements in a single request\202\323\344\223\002$\"\037/api/v1/test-measurements:batch:\001*' + _globals['_TESTREPORTSERVICE'].methods_by_name['ListTestMeasurements']._loaded_options = None + _globals['_TESTREPORTSERVICE'].methods_by_name['ListTestMeasurements']._serialized_options = b'\222AG\022\024ListTestMeasurements\032/Lists test measurements with optional filtering\202\323\344\223\002\033\022\031/api/v1/test-measurements' + _globals['_TESTREPORTSERVICE'].methods_by_name['UpdateTestMeasurement']._loaded_options = None + _globals['_TESTREPORTSERVICE'].methods_by_name['UpdateTestMeasurement']._serialized_options = b'\222A3\022\025UpdateTestMeasurement\032\032Updates a test measurement\202\323\344\223\002\0362\031/api/v1/test-measurements:\001*' + _globals['_TESTREPORTSERVICE'].methods_by_name['DeleteTestMeasurement']._loaded_options = None + _globals['_TESTREPORTSERVICE'].methods_by_name['DeleteTestMeasurement']._serialized_options = b'\222A3\022\025DeleteTestMeasurement\032\032Deletes a test measurement\202\323\344\223\002,**/api/v1/test-measurements/{measurement_id}' + _globals['_TESTSTATUS']._serialized_start=6332 + _globals['_TESTSTATUS']._serialized_end=6546 + _globals['_TESTSTEPTYPE']._serialized_start=6549 + _globals['_TESTSTEPTYPE']._serialized_end=6710 + _globals['_TESTMEASUREMENTTYPE']._serialized_start=6713 + _globals['_TESTMEASUREMENTTYPE']._serialized_end=6909 + _globals['_TESTREPORT']._serialized_start=311 + _globals['_TESTREPORT']._serialized_end=968 + _globals['_TESTSTEP']._serialized_start=971 + _globals['_TESTSTEP']._serialized_end=1595 + _globals['_ERRORINFO']._serialized_start=1597 + _globals['_ERRORINFO']._serialized_end=1686 + _globals['_TESTMEASUREMENT']._serialized_start=1689 + _globals['_TESTMEASUREMENT']._serialized_end=2399 + _globals['_NUMERICBOUNDS']._serialized_start=2401 + _globals['_NUMERICBOUNDS']._serialized_end=2488 + _globals['_STRINGBOUNDS']._serialized_start=2490 + _globals['_STRINGBOUNDS']._serialized_end=2548 + _globals['_IMPORTTESTREPORTREQUEST']._serialized_start=2550 + _globals['_IMPORTTESTREPORTREQUEST']._serialized_end=2618 + _globals['_IMPORTTESTREPORTRESPONSE']._serialized_start=2620 + _globals['_IMPORTTESTREPORTRESPONSE']._serialized_end=2722 + _globals['_CREATETESTREPORTREQUEST']._serialized_start=2725 + _globals['_CREATETESTREPORTREQUEST']._serialized_end=3244 + _globals['_CREATETESTREPORTRESPONSE']._serialized_start=3246 + _globals['_CREATETESTREPORTRESPONSE']._serialized_end=3348 + _globals['_GETTESTREPORTREQUEST']._serialized_start=3350 + _globals['_GETTESTREPORTREQUEST']._serialized_end=3415 + _globals['_GETTESTREPORTRESPONSE']._serialized_start=3417 + _globals['_GETTESTREPORTRESPONSE']._serialized_end=3516 + _globals['_LISTTESTREPORTSREQUEST']._serialized_start=3519 + _globals['_LISTTESTREPORTSREQUEST']._serialized_end=3674 + _globals['_LISTTESTREPORTSRESPONSE']._serialized_start=3677 + _globals['_LISTTESTREPORTSRESPONSE']._serialized_end=3820 + _globals['_UPDATETESTREPORTREQUEST']._serialized_start=3823 + _globals['_UPDATETESTREPORTREQUEST']._serialized_end=3995 + _globals['_UPDATETESTREPORTRESPONSE']._serialized_start=3997 + _globals['_UPDATETESTREPORTRESPONSE']._serialized_end=4099 + _globals['_DELETETESTREPORTREQUEST']._serialized_start=4101 + _globals['_DELETETESTREPORTREQUEST']._serialized_end=4169 + _globals['_DELETETESTREPORTRESPONSE']._serialized_start=4171 + _globals['_DELETETESTREPORTRESPONSE']._serialized_end=4197 + _globals['_CREATETESTSTEPREQUEST']._serialized_start=4199 + _globals['_CREATETESTSTEPREQUEST']._serialized_end=4297 + _globals['_CREATETESTSTEPRESPONSE']._serialized_start=4299 + _globals['_CREATETESTSTEPRESPONSE']._serialized_end=4393 + _globals['_LISTTESTSTEPSREQUEST']._serialized_start=4396 + _globals['_LISTTESTSTEPSREQUEST']._serialized_end=4549 + _globals['_LISTTESTSTEPSRESPONSE']._serialized_start=4552 + _globals['_LISTTESTSTEPSRESPONSE']._serialized_end=4687 + _globals['_UPDATETESTSTEPREQUEST']._serialized_start=4690 + _globals['_UPDATETESTSTEPREQUEST']._serialized_end=4854 + _globals['_UPDATETESTSTEPRESPONSE']._serialized_start=4856 + _globals['_UPDATETESTSTEPRESPONSE']._serialized_end=4950 + _globals['_DELETETESTSTEPREQUEST']._serialized_start=4952 + _globals['_DELETETESTSTEPREQUEST']._serialized_end=5014 + _globals['_DELETETESTSTEPRESPONSE']._serialized_start=5016 + _globals['_DELETETESTSTEPRESPONSE']._serialized_end=5040 + _globals['_CREATETESTMEASUREMENTREQUEST']._serialized_start=5042 + _globals['_CREATETESTMEASUREMENTREQUEST']._serialized_end=5168 + _globals['_CREATETESTMEASUREMENTRESPONSE']._serialized_start=5170 + _globals['_CREATETESTMEASUREMENTRESPONSE']._serialized_end=5292 + _globals['_CREATETESTMEASUREMENTSREQUEST']._serialized_start=5295 + _globals['_CREATETESTMEASUREMENTSREQUEST']._serialized_end=5424 + _globals['_CREATETESTMEASUREMENTSRESPONSE']._serialized_start=5427 + _globals['_CREATETESTMEASUREMENTSRESPONSE']._serialized_end=5572 + _globals['_LISTTESTMEASUREMENTSREQUEST']._serialized_start=5575 + _globals['_LISTTESTMEASUREMENTSREQUEST']._serialized_end=5735 + _globals['_LISTTESTMEASUREMENTSRESPONSE']._serialized_start=5738 + _globals['_LISTTESTMEASUREMENTSRESPONSE']._serialized_end=5901 + _globals['_UPDATETESTMEASUREMENTREQUEST']._serialized_start=5904 + _globals['_UPDATETESTMEASUREMENTREQUEST']._serialized_end=6096 + _globals['_UPDATETESTMEASUREMENTRESPONSE']._serialized_start=6098 + _globals['_UPDATETESTMEASUREMENTRESPONSE']._serialized_end=6220 + _globals['_DELETETESTMEASUREMENTREQUEST']._serialized_start=6222 + _globals['_DELETETESTMEASUREMENTREQUEST']._serialized_end=6296 + _globals['_DELETETESTMEASUREMENTRESPONSE']._serialized_start=6298 + _globals['_DELETETESTMEASUREMENTRESPONSE']._serialized_end=6329 + _globals['_TESTREPORTSERVICE']._serialized_start=6912 + _globals['_TESTREPORTSERVICE']._serialized_end=10399 +# @@protoc_insertion_point(module_scope) diff --git a/python/lib/sift/test_reports/v1/test_reports_pb2.pyi b/python/lib/sift/test_reports/v1/test_reports_pb2.pyi new file mode 100644 index 000000000..8c3279b11 --- /dev/null +++ b/python/lib/sift/test_reports/v1/test_reports_pb2.pyi @@ -0,0 +1,1119 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" + +import builtins +import collections.abc +import google.protobuf.descriptor +import google.protobuf.field_mask_pb2 +import google.protobuf.internal.containers +import google.protobuf.internal.enum_type_wrapper +import google.protobuf.message +import google.protobuf.timestamp_pb2 +import sift.metadata.v1.metadata_pb2 +import sift.unit.v2.unit_pb2 +import sys +import typing + +if sys.version_info >= (3, 10): + import typing as typing_extensions +else: + import typing_extensions + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +class _TestStatus: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + +class _TestStatusEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_TestStatus.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + TEST_STATUS_UNSPECIFIED: _TestStatus.ValueType # 0 + TEST_STATUS_DRAFT: _TestStatus.ValueType # 1 + """Barebones test report created, waiting for file processing""" + TEST_STATUS_PASSED: _TestStatus.ValueType # 2 + TEST_STATUS_FAILED: _TestStatus.ValueType # 3 + TEST_STATUS_ABORTED: _TestStatus.ValueType # 4 + TEST_STATUS_ERROR: _TestStatus.ValueType # 5 + TEST_STATUS_IN_PROGRESS: _TestStatus.ValueType # 6 + TEST_STATUS_SKIPPED: _TestStatus.ValueType # 7 + +class TestStatus(_TestStatus, metaclass=_TestStatusEnumTypeWrapper): ... + +TEST_STATUS_UNSPECIFIED: TestStatus.ValueType # 0 +TEST_STATUS_DRAFT: TestStatus.ValueType # 1 +"""Barebones test report created, waiting for file processing""" +TEST_STATUS_PASSED: TestStatus.ValueType # 2 +TEST_STATUS_FAILED: TestStatus.ValueType # 3 +TEST_STATUS_ABORTED: TestStatus.ValueType # 4 +TEST_STATUS_ERROR: TestStatus.ValueType # 5 +TEST_STATUS_IN_PROGRESS: TestStatus.ValueType # 6 +TEST_STATUS_SKIPPED: TestStatus.ValueType # 7 +global___TestStatus = TestStatus + +class _TestStepType: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + +class _TestStepTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_TestStepType.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + TEST_STEP_TYPE_UNSPECIFIED: _TestStepType.ValueType # 0 + TEST_STEP_TYPE_SEQUENCE: _TestStepType.ValueType # 1 + """ResultSet/MainSequence - top-level test sequence""" + TEST_STEP_TYPE_GROUP: _TestStepType.ValueType # 2 + """TestGroup - logical grouping of test steps""" + TEST_STEP_TYPE_ACTION: _TestStepType.ValueType # 3 + """SessionAction - individual executable action""" + TEST_STEP_TYPE_FLOW_CONTROL: _TestStepType.ValueType # 4 + """Flow control elements (While, If, etc.)""" + +class TestStepType(_TestStepType, metaclass=_TestStepTypeEnumTypeWrapper): ... + +TEST_STEP_TYPE_UNSPECIFIED: TestStepType.ValueType # 0 +TEST_STEP_TYPE_SEQUENCE: TestStepType.ValueType # 1 +"""ResultSet/MainSequence - top-level test sequence""" +TEST_STEP_TYPE_GROUP: TestStepType.ValueType # 2 +"""TestGroup - logical grouping of test steps""" +TEST_STEP_TYPE_ACTION: TestStepType.ValueType # 3 +"""SessionAction - individual executable action""" +TEST_STEP_TYPE_FLOW_CONTROL: TestStepType.ValueType # 4 +"""Flow control elements (While, If, etc.)""" +global___TestStepType = TestStepType + +class _TestMeasurementType: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + +class _TestMeasurementTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_TestMeasurementType.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + TEST_MEASUREMENT_TYPE_UNSPECIFIED: _TestMeasurementType.ValueType # 0 + TEST_MEASUREMENT_TYPE_DOUBLE: _TestMeasurementType.ValueType # 1 + TEST_MEASUREMENT_TYPE_STRING: _TestMeasurementType.ValueType # 3 + TEST_MEASUREMENT_TYPE_BOOLEAN: _TestMeasurementType.ValueType # 4 + TEST_MEASUREMENT_TYPE_LIMIT: _TestMeasurementType.ValueType # 5 + +class TestMeasurementType(_TestMeasurementType, metaclass=_TestMeasurementTypeEnumTypeWrapper): ... + +TEST_MEASUREMENT_TYPE_UNSPECIFIED: TestMeasurementType.ValueType # 0 +TEST_MEASUREMENT_TYPE_DOUBLE: TestMeasurementType.ValueType # 1 +TEST_MEASUREMENT_TYPE_STRING: TestMeasurementType.ValueType # 3 +TEST_MEASUREMENT_TYPE_BOOLEAN: TestMeasurementType.ValueType # 4 +TEST_MEASUREMENT_TYPE_LIMIT: TestMeasurementType.ValueType # 5 +global___TestMeasurementType = TestMeasurementType + +@typing.final +class TestReport(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TEST_REPORT_ID_FIELD_NUMBER: builtins.int + STATUS_FIELD_NUMBER: builtins.int + NAME_FIELD_NUMBER: builtins.int + TEST_SYSTEM_NAME_FIELD_NUMBER: builtins.int + TEST_CASE_FIELD_NUMBER: builtins.int + START_TIME_FIELD_NUMBER: builtins.int + END_TIME_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + SERIAL_NUMBER_FIELD_NUMBER: builtins.int + PART_NUMBER_FIELD_NUMBER: builtins.int + SYSTEM_OPERATOR_FIELD_NUMBER: builtins.int + ARCHIVED_DATE_FIELD_NUMBER: builtins.int + IS_ARCHIVED_FIELD_NUMBER: builtins.int + test_report_id: builtins.str + """Unique identifier for the run""" + status: global___TestStatus.ValueType + """The status of the test run""" + name: builtins.str + """The name of the test run""" + test_system_name: builtins.str + """The name of the test system""" + test_case: builtins.str + """The test case that was run""" + serial_number: builtins.str + """The serial number for the DUT""" + part_number: builtins.str + """The part number for the DUT""" + system_operator: builtins.str + """Unique identifier for user owner""" + is_archived: builtins.bool + """Whether the test run is archived (externally exposed)""" + @property + def start_time(self) -> google.protobuf.timestamp_pb2.Timestamp: + """The start time of the test run""" + + @property + def end_time(self) -> google.protobuf.timestamp_pb2.Timestamp: + """The end time of the test run""" + + @property + def metadata(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[sift.metadata.v1.metadata_pb2.MetadataValue]: + """The metadata values associated with this test run""" + + @property + def archived_date(self) -> google.protobuf.timestamp_pb2.Timestamp: + """The date and time the test run was archived (internal)""" + + def __init__( + self, + *, + test_report_id: builtins.str = ..., + status: global___TestStatus.ValueType = ..., + name: builtins.str = ..., + test_system_name: builtins.str = ..., + test_case: builtins.str = ..., + start_time: google.protobuf.timestamp_pb2.Timestamp | None = ..., + end_time: google.protobuf.timestamp_pb2.Timestamp | None = ..., + metadata: collections.abc.Iterable[sift.metadata.v1.metadata_pb2.MetadataValue] | None = ..., + serial_number: builtins.str = ..., + part_number: builtins.str = ..., + system_operator: builtins.str = ..., + archived_date: google.protobuf.timestamp_pb2.Timestamp | None = ..., + is_archived: builtins.bool = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["archived_date", b"archived_date", "end_time", b"end_time", "start_time", b"start_time"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["archived_date", b"archived_date", "end_time", b"end_time", "is_archived", b"is_archived", "metadata", b"metadata", "name", b"name", "part_number", b"part_number", "serial_number", b"serial_number", "start_time", b"start_time", "status", b"status", "system_operator", b"system_operator", "test_case", b"test_case", "test_report_id", b"test_report_id", "test_system_name", b"test_system_name"]) -> None: ... + +global___TestReport = TestReport + +@typing.final +class TestStep(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TEST_STEP_ID_FIELD_NUMBER: builtins.int + TEST_REPORT_ID_FIELD_NUMBER: builtins.int + PARENT_STEP_ID_FIELD_NUMBER: builtins.int + NAME_FIELD_NUMBER: builtins.int + DESCRIPTION_FIELD_NUMBER: builtins.int + STEP_TYPE_FIELD_NUMBER: builtins.int + STEP_NUMBER_FIELD_NUMBER: builtins.int + STEP_PATH_FIELD_NUMBER: builtins.int + STATUS_FIELD_NUMBER: builtins.int + START_TIME_FIELD_NUMBER: builtins.int + END_TIME_FIELD_NUMBER: builtins.int + ERROR_INFO_FIELD_NUMBER: builtins.int + test_step_id: builtins.str + """unique identifier for the step""" + test_report_id: builtins.str + """pointer to overall test run""" + parent_step_id: builtins.str + """pointer to parent step, if any""" + name: builtins.str + """Name of the test step for display""" + description: builtins.str + """Description of the test step from test controller""" + step_type: global___TestStepType.ValueType + """Semantic type of the test step""" + step_number: builtins.int + """Order among siblings (1, 2, 3...)""" + step_path: builtins.str + """Hierarchical path (e.g., "1", "1.1", "1.2.3")""" + status: global___TestStatus.ValueType + """Status of the test step""" + @property + def start_time(self) -> google.protobuf.timestamp_pb2.Timestamp: + """Start time of the test step""" + + @property + def end_time(self) -> google.protobuf.timestamp_pb2.Timestamp: + """End time of the test step""" + + @property + def error_info(self) -> global___ErrorInfo: + """Error information of the test step""" + + def __init__( + self, + *, + test_step_id: builtins.str = ..., + test_report_id: builtins.str = ..., + parent_step_id: builtins.str = ..., + name: builtins.str = ..., + description: builtins.str = ..., + step_type: global___TestStepType.ValueType = ..., + step_number: builtins.int = ..., + step_path: builtins.str = ..., + status: global___TestStatus.ValueType = ..., + start_time: google.protobuf.timestamp_pb2.Timestamp | None = ..., + end_time: google.protobuf.timestamp_pb2.Timestamp | None = ..., + error_info: global___ErrorInfo | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["end_time", b"end_time", "error_info", b"error_info", "start_time", b"start_time"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["description", b"description", "end_time", b"end_time", "error_info", b"error_info", "name", b"name", "parent_step_id", b"parent_step_id", "start_time", b"start_time", "status", b"status", "step_number", b"step_number", "step_path", b"step_path", "step_type", b"step_type", "test_report_id", b"test_report_id", "test_step_id", b"test_step_id"]) -> None: ... + +global___TestStep = TestStep + +@typing.final +class ErrorInfo(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ERROR_CODE_FIELD_NUMBER: builtins.int + ERROR_MESSAGE_FIELD_NUMBER: builtins.int + error_code: builtins.int + error_message: builtins.str + def __init__( + self, + *, + error_code: builtins.int = ..., + error_message: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["error_code", b"error_code", "error_message", b"error_message"]) -> None: ... + +global___ErrorInfo = ErrorInfo + +@typing.final +class TestMeasurement(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + MEASUREMENT_ID_FIELD_NUMBER: builtins.int + MEASUREMENT_TYPE_FIELD_NUMBER: builtins.int + NAME_FIELD_NUMBER: builtins.int + TEST_STEP_ID_FIELD_NUMBER: builtins.int + TEST_REPORT_ID_FIELD_NUMBER: builtins.int + NUMERIC_VALUE_FIELD_NUMBER: builtins.int + STRING_VALUE_FIELD_NUMBER: builtins.int + BOOLEAN_VALUE_FIELD_NUMBER: builtins.int + UNIT_FIELD_NUMBER: builtins.int + NUMERIC_BOUNDS_FIELD_NUMBER: builtins.int + STRING_BOUNDS_FIELD_NUMBER: builtins.int + PASSED_FIELD_NUMBER: builtins.int + TIMESTAMP_FIELD_NUMBER: builtins.int + measurement_id: builtins.str + measurement_type: global___TestMeasurementType.ValueType + name: builtins.str + test_step_id: builtins.str + test_report_id: builtins.str + numeric_value: builtins.float + string_value: builtins.str + boolean_value: builtins.bool + passed: builtins.bool + @property + def unit(self) -> sift.unit.v2.unit_pb2.Unit: ... + @property + def numeric_bounds(self) -> global___NumericBounds: ... + @property + def string_bounds(self) -> global___StringBounds: ... + @property + def timestamp(self) -> google.protobuf.timestamp_pb2.Timestamp: ... + def __init__( + self, + *, + measurement_id: builtins.str = ..., + measurement_type: global___TestMeasurementType.ValueType = ..., + name: builtins.str = ..., + test_step_id: builtins.str = ..., + test_report_id: builtins.str = ..., + numeric_value: builtins.float = ..., + string_value: builtins.str = ..., + boolean_value: builtins.bool = ..., + unit: sift.unit.v2.unit_pb2.Unit | None = ..., + numeric_bounds: global___NumericBounds | None = ..., + string_bounds: global___StringBounds | None = ..., + passed: builtins.bool = ..., + timestamp: google.protobuf.timestamp_pb2.Timestamp | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["boolean_value", b"boolean_value", "bounds", b"bounds", "numeric_bounds", b"numeric_bounds", "numeric_value", b"numeric_value", "string_bounds", b"string_bounds", "string_value", b"string_value", "timestamp", b"timestamp", "unit", b"unit", "value", b"value"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["boolean_value", b"boolean_value", "bounds", b"bounds", "measurement_id", b"measurement_id", "measurement_type", b"measurement_type", "name", b"name", "numeric_bounds", b"numeric_bounds", "numeric_value", b"numeric_value", "passed", b"passed", "string_bounds", b"string_bounds", "string_value", b"string_value", "test_report_id", b"test_report_id", "test_step_id", b"test_step_id", "timestamp", b"timestamp", "unit", b"unit", "value", b"value"]) -> None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["bounds", b"bounds"]) -> typing.Literal["numeric_bounds", "string_bounds"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["value", b"value"]) -> typing.Literal["numeric_value", "string_value", "boolean_value"] | None: ... + +global___TestMeasurement = TestMeasurement + +@typing.final +class NumericBounds(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + MIN_FIELD_NUMBER: builtins.int + MAX_FIELD_NUMBER: builtins.int + min: builtins.float + max: builtins.float + def __init__( + self, + *, + min: builtins.float | None = ..., + max: builtins.float | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["_max", b"_max", "_min", b"_min", "max", b"max", "min", b"min"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["_max", b"_max", "_min", b"_min", "max", b"max", "min", b"min"]) -> None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_max", b"_max"]) -> typing.Literal["max"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_min", b"_min"]) -> typing.Literal["min"] | None: ... + +global___NumericBounds = NumericBounds + +@typing.final +class StringBounds(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + EXPECTED_VALUE_FIELD_NUMBER: builtins.int + expected_value: builtins.str + def __init__( + self, + *, + expected_value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["expected_value", b"expected_value"]) -> None: ... + +global___StringBounds = StringBounds + +@typing.final +class ImportTestReportRequest(google.protobuf.message.Message): + """Request message for ImportTestReport""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + REMOTE_FILE_ID_FIELD_NUMBER: builtins.int + remote_file_id: builtins.str + """The remote file ID containing the XML test data""" + def __init__( + self, + *, + remote_file_id: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["remote_file_id", b"remote_file_id"]) -> None: ... + +global___ImportTestReportRequest = ImportTestReportRequest + +@typing.final +class ImportTestReportResponse(google.protobuf.message.Message): + """Response message for ImportTestReport""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TEST_REPORT_FIELD_NUMBER: builtins.int + @property + def test_report(self) -> global___TestReport: + """The imported test report""" + + def __init__( + self, + *, + test_report: global___TestReport | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["test_report", b"test_report"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["test_report", b"test_report"]) -> None: ... + +global___ImportTestReportResponse = ImportTestReportResponse + +@typing.final +class CreateTestReportRequest(google.protobuf.message.Message): + """Request message for CreateTestReport""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + STATUS_FIELD_NUMBER: builtins.int + NAME_FIELD_NUMBER: builtins.int + TEST_SYSTEM_NAME_FIELD_NUMBER: builtins.int + TEST_CASE_FIELD_NUMBER: builtins.int + START_TIME_FIELD_NUMBER: builtins.int + END_TIME_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + SERIAL_NUMBER_FIELD_NUMBER: builtins.int + PART_NUMBER_FIELD_NUMBER: builtins.int + SYSTEM_OPERATOR_FIELD_NUMBER: builtins.int + status: global___TestStatus.ValueType + """The status of the test run""" + name: builtins.str + """The name of the test run""" + test_system_name: builtins.str + """The name of the test system""" + test_case: builtins.str + """The test case that was run""" + serial_number: builtins.str + """The serial number for the DUT""" + part_number: builtins.str + """The part number for the DUT""" + system_operator: builtins.str + """Unique identifier for user owner""" + @property + def start_time(self) -> google.protobuf.timestamp_pb2.Timestamp: + """The start time of the test run""" + + @property + def end_time(self) -> google.protobuf.timestamp_pb2.Timestamp: + """The end time of the test run""" + + @property + def metadata(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[sift.metadata.v1.metadata_pb2.MetadataValue]: + """The metadata values associated with this test run""" + + def __init__( + self, + *, + status: global___TestStatus.ValueType = ..., + name: builtins.str = ..., + test_system_name: builtins.str = ..., + test_case: builtins.str = ..., + start_time: google.protobuf.timestamp_pb2.Timestamp | None = ..., + end_time: google.protobuf.timestamp_pb2.Timestamp | None = ..., + metadata: collections.abc.Iterable[sift.metadata.v1.metadata_pb2.MetadataValue] | None = ..., + serial_number: builtins.str = ..., + part_number: builtins.str = ..., + system_operator: builtins.str = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["end_time", b"end_time", "start_time", b"start_time"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["end_time", b"end_time", "metadata", b"metadata", "name", b"name", "part_number", b"part_number", "serial_number", b"serial_number", "start_time", b"start_time", "status", b"status", "system_operator", b"system_operator", "test_case", b"test_case", "test_system_name", b"test_system_name"]) -> None: ... + +global___CreateTestReportRequest = CreateTestReportRequest + +@typing.final +class CreateTestReportResponse(google.protobuf.message.Message): + """Response message for CreateTestReport""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TEST_REPORT_FIELD_NUMBER: builtins.int + @property + def test_report(self) -> global___TestReport: + """The created test report""" + + def __init__( + self, + *, + test_report: global___TestReport | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["test_report", b"test_report"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["test_report", b"test_report"]) -> None: ... + +global___CreateTestReportResponse = CreateTestReportResponse + +@typing.final +class GetTestReportRequest(google.protobuf.message.Message): + """Request message for GetTestReport""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TEST_REPORT_ID_FIELD_NUMBER: builtins.int + test_report_id: builtins.str + """The ID of the test report to get""" + def __init__( + self, + *, + test_report_id: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["test_report_id", b"test_report_id"]) -> None: ... + +global___GetTestReportRequest = GetTestReportRequest + +@typing.final +class GetTestReportResponse(google.protobuf.message.Message): + """Response message for GetTestReport""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TEST_REPORT_FIELD_NUMBER: builtins.int + @property + def test_report(self) -> global___TestReport: + """The test report""" + + def __init__( + self, + *, + test_report: global___TestReport | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["test_report", b"test_report"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["test_report", b"test_report"]) -> None: ... + +global___GetTestReportResponse = GetTestReportResponse + +@typing.final +class ListTestReportsRequest(google.protobuf.message.Message): + """Request message for ListTestReports""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + PAGE_SIZE_FIELD_NUMBER: builtins.int + PAGE_TOKEN_FIELD_NUMBER: builtins.int + FILTER_FIELD_NUMBER: builtins.int + ORDER_BY_FIELD_NUMBER: builtins.int + page_size: builtins.int + """The maximum number of test reports to return. + The service may return fewer than this value. + If unspecified, at most 50 test reports will be returned. + The maximum value is 1000; values above 1000 will be coerced to 1000. + """ + page_token: builtins.str + """A page token, received from a previous `ListTestReports` call. + Provide this to retrieve the subsequent page. + When paginating, all other parameters provided to `ListTestReports` must match + the call that provided the page token. + """ + filter: builtins.str + """A [Common Expression Language (CEL)](https://github.com/google/cel-spec) filter string. + Available fields to filter by are `test_report_id`, `status`, `name`, `test_system_name`, + `test_case`, `start_time`, `end_time`, `serial_number`, + `part_number`, `system_operator`, `archived_date`, and `metadata`. + Metadata can be used in filters by using `metadata.{metadata_key_name}` as the field name. + For further information about how to use CELs, please refer to [this guide](https://github.com/google/cel-spec/blob/master/doc/langdef.md#standard-definitions). + For more information about the fields used for filtering, please refer to [this definition](/docs/api/grpc/protocol-buffers/test-results#testreport). Optional. + """ + order_by: builtins.str + """How to order the retrieved test reports. Formatted as a comma-separated string i.e. "FIELD_NAME[ desc],...". + Available fields to order_by are `test_report_id`, `name`, `test_system_name`, `test_case`, `start_time`, `end_time`, + `created_date`, and `modified_date`. + If left empty, items are ordered by `start_time` in descending order (newest-first). + For more information about the format of this field, read [this](https://google.aip.dev/132#ordering) + Example: "start_time desc,name" + """ + def __init__( + self, + *, + page_size: builtins.int = ..., + page_token: builtins.str = ..., + filter: builtins.str = ..., + order_by: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["filter", b"filter", "order_by", b"order_by", "page_size", b"page_size", "page_token", b"page_token"]) -> None: ... + +global___ListTestReportsRequest = ListTestReportsRequest + +@typing.final +class ListTestReportsResponse(google.protobuf.message.Message): + """Response message for ListTestReports""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TEST_REPORTS_FIELD_NUMBER: builtins.int + NEXT_PAGE_TOKEN_FIELD_NUMBER: builtins.int + next_page_token: builtins.str + """The next page token for pagination""" + @property + def test_reports(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___TestReport]: + """The list of test reports""" + + def __init__( + self, + *, + test_reports: collections.abc.Iterable[global___TestReport] | None = ..., + next_page_token: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["next_page_token", b"next_page_token", "test_reports", b"test_reports"]) -> None: ... + +global___ListTestReportsResponse = ListTestReportsResponse + +@typing.final +class UpdateTestReportRequest(google.protobuf.message.Message): + """Request message for UpdateTestReport""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TEST_REPORT_FIELD_NUMBER: builtins.int + UPDATE_MASK_FIELD_NUMBER: builtins.int + @property + def test_report(self) -> global___TestReport: + """The test report to update""" + + @property + def update_mask(self) -> google.protobuf.field_mask_pb2.FieldMask: + """The field mask specifying which fields to update. The fields available to be updated are + `status`, `name`, `test_system_name`, `test_case`, `start_time`, `end_time`, `serial_number`, + `part_number`, `system_operator`, and `is_archived`. + """ + + def __init__( + self, + *, + test_report: global___TestReport | None = ..., + update_mask: google.protobuf.field_mask_pb2.FieldMask | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["test_report", b"test_report", "update_mask", b"update_mask"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["test_report", b"test_report", "update_mask", b"update_mask"]) -> None: ... + +global___UpdateTestReportRequest = UpdateTestReportRequest + +@typing.final +class UpdateTestReportResponse(google.protobuf.message.Message): + """Response message for UpdateTestReport""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TEST_REPORT_FIELD_NUMBER: builtins.int + @property + def test_report(self) -> global___TestReport: + """The updated test report""" + + def __init__( + self, + *, + test_report: global___TestReport | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["test_report", b"test_report"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["test_report", b"test_report"]) -> None: ... + +global___UpdateTestReportResponse = UpdateTestReportResponse + +@typing.final +class DeleteTestReportRequest(google.protobuf.message.Message): + """Request message for DeleteTestReport""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TEST_REPORT_ID_FIELD_NUMBER: builtins.int + test_report_id: builtins.str + """The ID of the test report to delete""" + def __init__( + self, + *, + test_report_id: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["test_report_id", b"test_report_id"]) -> None: ... + +global___DeleteTestReportRequest = DeleteTestReportRequest + +@typing.final +class DeleteTestReportResponse(google.protobuf.message.Message): + """Response message for DeleteTestReport + Empty response indicating successful deletion + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + def __init__( + self, + ) -> None: ... + +global___DeleteTestReportResponse = DeleteTestReportResponse + +@typing.final +class CreateTestStepRequest(google.protobuf.message.Message): + """Request message for CreateTestStep""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TEST_STEP_FIELD_NUMBER: builtins.int + @property + def test_step(self) -> global___TestStep: + """The test step to create""" + + def __init__( + self, + *, + test_step: global___TestStep | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["test_step", b"test_step"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["test_step", b"test_step"]) -> None: ... + +global___CreateTestStepRequest = CreateTestStepRequest + +@typing.final +class CreateTestStepResponse(google.protobuf.message.Message): + """Response message for CreateTestStep""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TEST_STEP_FIELD_NUMBER: builtins.int + @property + def test_step(self) -> global___TestStep: + """The created test step""" + + def __init__( + self, + *, + test_step: global___TestStep | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["test_step", b"test_step"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["test_step", b"test_step"]) -> None: ... + +global___CreateTestStepResponse = CreateTestStepResponse + +@typing.final +class ListTestStepsRequest(google.protobuf.message.Message): + """Request message for ListTestSteps""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + PAGE_SIZE_FIELD_NUMBER: builtins.int + PAGE_TOKEN_FIELD_NUMBER: builtins.int + FILTER_FIELD_NUMBER: builtins.int + ORDER_BY_FIELD_NUMBER: builtins.int + page_size: builtins.int + """The maximum number of test steps to return. + The service may return fewer than this value. + If unspecified, at most 50 test steps will be returned. + The maximum value is 1000; values above 1000 will be coerced to 1000. + """ + page_token: builtins.str + """A page token, received from a previous `ListTestSteps` call. + Provide this to retrieve the subsequent page. + When paginating, all other parameters provided to `ListTestSteps` must match + the call that provided the page token. + """ + filter: builtins.str + """A [Common Expression Language (CEL)](https://github.com/google/cel-spec) filter string. + Available fields to filter by are `test_step_id`, `test_report_id`, `parent_step_id`, `name`, + `description`, `step_type`, `step_number`, `step_path`, `status`, `start_time`, `end_time`, + `error_code`, `error_message`, `created_date`, and `modified_date`. + For further information about how to use CELs, please refer to [this guide](https://github.com/google/cel-spec/blob/master/doc/langdef.md#standard-definitions). + For more information about the fields used for filtering, please refer to [this definition](/docs/api/grpc/protocol-buffers/test-results#teststep). Optional. + """ + order_by: builtins.str + """How to order the retrieved test steps. Formatted as a comma-separated string i.e. "FIELD_NAME[ desc],...". + Available fields to order_by are `test_step_id`, `name`, `step_type`, `step_number`, `step_path`, `status`, + `start_time`, `end_time`, `created_date`, and `modified_date`. + If left empty, items are ordered by `step_path` in ascending order. + For more information about the format of this field, read [this](https://google.aip.dev/132#ordering) + Example: "step_path asc,start_time desc" + """ + def __init__( + self, + *, + page_size: builtins.int = ..., + page_token: builtins.str = ..., + filter: builtins.str = ..., + order_by: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["filter", b"filter", "order_by", b"order_by", "page_size", b"page_size", "page_token", b"page_token"]) -> None: ... + +global___ListTestStepsRequest = ListTestStepsRequest + +@typing.final +class ListTestStepsResponse(google.protobuf.message.Message): + """Response message for ListTestSteps""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TEST_STEPS_FIELD_NUMBER: builtins.int + NEXT_PAGE_TOKEN_FIELD_NUMBER: builtins.int + next_page_token: builtins.str + """The next page token for pagination""" + @property + def test_steps(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___TestStep]: + """The list of test steps""" + + def __init__( + self, + *, + test_steps: collections.abc.Iterable[global___TestStep] | None = ..., + next_page_token: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["next_page_token", b"next_page_token", "test_steps", b"test_steps"]) -> None: ... + +global___ListTestStepsResponse = ListTestStepsResponse + +@typing.final +class UpdateTestStepRequest(google.protobuf.message.Message): + """Request message for UpdateTestStep""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TEST_STEP_FIELD_NUMBER: builtins.int + UPDATE_MASK_FIELD_NUMBER: builtins.int + @property + def test_step(self) -> global___TestStep: + """The test step to update""" + + @property + def update_mask(self) -> google.protobuf.field_mask_pb2.FieldMask: + """The field mask specifying which fields to update. The fields available to be updated are + `name`, `description`, `step_type`, `step_number`, `step_path`, `test_case`, `status`, + `start_time`, `end_time`, and `error_info`. + """ + + def __init__( + self, + *, + test_step: global___TestStep | None = ..., + update_mask: google.protobuf.field_mask_pb2.FieldMask | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["test_step", b"test_step", "update_mask", b"update_mask"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["test_step", b"test_step", "update_mask", b"update_mask"]) -> None: ... + +global___UpdateTestStepRequest = UpdateTestStepRequest + +@typing.final +class UpdateTestStepResponse(google.protobuf.message.Message): + """Response message for UpdateTestStep""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TEST_STEP_FIELD_NUMBER: builtins.int + @property + def test_step(self) -> global___TestStep: + """The updated test step""" + + def __init__( + self, + *, + test_step: global___TestStep | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["test_step", b"test_step"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["test_step", b"test_step"]) -> None: ... + +global___UpdateTestStepResponse = UpdateTestStepResponse + +@typing.final +class DeleteTestStepRequest(google.protobuf.message.Message): + """Request message for DeleteTestStep""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TEST_STEP_ID_FIELD_NUMBER: builtins.int + test_step_id: builtins.str + """The ID of the test step to delete""" + def __init__( + self, + *, + test_step_id: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["test_step_id", b"test_step_id"]) -> None: ... + +global___DeleteTestStepRequest = DeleteTestStepRequest + +@typing.final +class DeleteTestStepResponse(google.protobuf.message.Message): + """Response message for DeleteTestStep + Empty response indicating successful deletion + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + def __init__( + self, + ) -> None: ... + +global___DeleteTestStepResponse = DeleteTestStepResponse + +@typing.final +class CreateTestMeasurementRequest(google.protobuf.message.Message): + """Request message for CreateTestMeasurement""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TEST_MEASUREMENT_FIELD_NUMBER: builtins.int + @property + def test_measurement(self) -> global___TestMeasurement: + """The test measurement to create""" + + def __init__( + self, + *, + test_measurement: global___TestMeasurement | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["test_measurement", b"test_measurement"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["test_measurement", b"test_measurement"]) -> None: ... + +global___CreateTestMeasurementRequest = CreateTestMeasurementRequest + +@typing.final +class CreateTestMeasurementResponse(google.protobuf.message.Message): + """Response message for CreateTestMeasurement""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TEST_MEASUREMENT_FIELD_NUMBER: builtins.int + @property + def test_measurement(self) -> global___TestMeasurement: + """The created test measurement""" + + def __init__( + self, + *, + test_measurement: global___TestMeasurement | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["test_measurement", b"test_measurement"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["test_measurement", b"test_measurement"]) -> None: ... + +global___CreateTestMeasurementResponse = CreateTestMeasurementResponse + +@typing.final +class CreateTestMeasurementsRequest(google.protobuf.message.Message): + """Request message for CreateTestMeasurements""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TEST_MEASUREMENTS_FIELD_NUMBER: builtins.int + @property + def test_measurements(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___TestMeasurement]: + """The test measurements to create""" + + def __init__( + self, + *, + test_measurements: collections.abc.Iterable[global___TestMeasurement] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["test_measurements", b"test_measurements"]) -> None: ... + +global___CreateTestMeasurementsRequest = CreateTestMeasurementsRequest + +@typing.final +class CreateTestMeasurementsResponse(google.protobuf.message.Message): + """Response message for CreateTestMeasurements""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + MEASUREMENTS_CREATED_COUNT_FIELD_NUMBER: builtins.int + MEASUREMENT_IDS_FIELD_NUMBER: builtins.int + measurements_created_count: builtins.int + """The number of test measurements successfully created""" + @property + def measurement_ids(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: + """The IDs of the created test measurements""" + + def __init__( + self, + *, + measurements_created_count: builtins.int = ..., + measurement_ids: collections.abc.Iterable[builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["measurement_ids", b"measurement_ids", "measurements_created_count", b"measurements_created_count"]) -> None: ... + +global___CreateTestMeasurementsResponse = CreateTestMeasurementsResponse + +@typing.final +class ListTestMeasurementsRequest(google.protobuf.message.Message): + """Request message for ListTestMeasurements""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + PAGE_SIZE_FIELD_NUMBER: builtins.int + PAGE_TOKEN_FIELD_NUMBER: builtins.int + FILTER_FIELD_NUMBER: builtins.int + ORDER_BY_FIELD_NUMBER: builtins.int + page_size: builtins.int + """The maximum number of test measurements to return. + The service may return fewer than this value. + If unspecified, at most 50 test measurements will be returned. + The maximum value is 1000; values above 1000 will be coerced to 1000. + """ + page_token: builtins.str + """A page token, received from a previous `ListTestMeasurements` call. + Provide this to retrieve the subsequent page. + When paginating, all other parameters provided to `ListTestMeasurements` must match + the call that provided the page token. + """ + filter: builtins.str + """A [Common Expression Language (CEL)](https://github.com/google/cel-spec) filter string. + Available fields to filter by are `measurement_id`, `measurement_type`, `name`, `test_step_id`, + `test_report_id`, `numeric_value`, `string_value`, `boolean_value`, `passed`, `timestamp`, + `created_date`, and `modified_date`. + For further information about how to use CELs, please refer to [this guide](https://github.com/google/cel-spec/blob/master/doc/langdef.md#standard-definitions). + For more information about the fields used for filtering, please refer to [this definition](/docs/api/grpc/protocol-buffers/test-results#testmeasurement). Optional. + """ + order_by: builtins.str + """How to order the retrieved test measurements. Formatted as a comma-separated string i.e. "FIELD_NAME[ desc],...". + Available fields to order_by are `measurement_id`, `name`, `measurement_type`, `test_step_id`, `test_report_id`, + `passed`, `timestamp`, `created_date`, and `modified_date`. + If left empty, items are ordered by `timestamp` in ascending order. + For more information about the format of this field, read [this](https://google.aip.dev/132#ordering) + Example: "timestamp asc,name" + """ + def __init__( + self, + *, + page_size: builtins.int = ..., + page_token: builtins.str = ..., + filter: builtins.str = ..., + order_by: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["filter", b"filter", "order_by", b"order_by", "page_size", b"page_size", "page_token", b"page_token"]) -> None: ... + +global___ListTestMeasurementsRequest = ListTestMeasurementsRequest + +@typing.final +class ListTestMeasurementsResponse(google.protobuf.message.Message): + """Response message for ListTestMeasurements""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TEST_MEASUREMENTS_FIELD_NUMBER: builtins.int + NEXT_PAGE_TOKEN_FIELD_NUMBER: builtins.int + next_page_token: builtins.str + """The next page token for pagination""" + @property + def test_measurements(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___TestMeasurement]: + """The list of test measurements""" + + def __init__( + self, + *, + test_measurements: collections.abc.Iterable[global___TestMeasurement] | None = ..., + next_page_token: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["next_page_token", b"next_page_token", "test_measurements", b"test_measurements"]) -> None: ... + +global___ListTestMeasurementsResponse = ListTestMeasurementsResponse + +@typing.final +class UpdateTestMeasurementRequest(google.protobuf.message.Message): + """Request message for UpdateTestMeasurement""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TEST_MEASUREMENT_FIELD_NUMBER: builtins.int + UPDATE_MASK_FIELD_NUMBER: builtins.int + @property + def test_measurement(self) -> global___TestMeasurement: + """The test measurement to update""" + + @property + def update_mask(self) -> google.protobuf.field_mask_pb2.FieldMask: + """The field mask specifying which fields to update. The fields available to be updated are + `name`, `measurement_type`, `numeric_value`, `string_value`, `boolean_value`, `unit`, `numeric_bounds`, + `string_bounds`, `passed`, and `timestamp`. + """ + + def __init__( + self, + *, + test_measurement: global___TestMeasurement | None = ..., + update_mask: google.protobuf.field_mask_pb2.FieldMask | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["test_measurement", b"test_measurement", "update_mask", b"update_mask"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["test_measurement", b"test_measurement", "update_mask", b"update_mask"]) -> None: ... + +global___UpdateTestMeasurementRequest = UpdateTestMeasurementRequest + +@typing.final +class UpdateTestMeasurementResponse(google.protobuf.message.Message): + """Response message for UpdateTestMeasurement""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TEST_MEASUREMENT_FIELD_NUMBER: builtins.int + @property + def test_measurement(self) -> global___TestMeasurement: + """The updated test measurement""" + + def __init__( + self, + *, + test_measurement: global___TestMeasurement | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["test_measurement", b"test_measurement"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["test_measurement", b"test_measurement"]) -> None: ... + +global___UpdateTestMeasurementResponse = UpdateTestMeasurementResponse + +@typing.final +class DeleteTestMeasurementRequest(google.protobuf.message.Message): + """Request message for DeleteTestMeasurement""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + MEASUREMENT_ID_FIELD_NUMBER: builtins.int + measurement_id: builtins.str + """The ID of the test measurement to delete""" + def __init__( + self, + *, + measurement_id: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["measurement_id", b"measurement_id"]) -> None: ... + +global___DeleteTestMeasurementRequest = DeleteTestMeasurementRequest + +@typing.final +class DeleteTestMeasurementResponse(google.protobuf.message.Message): + """Response message for DeleteTestMeasurement + Empty response indicating successful deletion + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + def __init__( + self, + ) -> None: ... + +global___DeleteTestMeasurementResponse = DeleteTestMeasurementResponse diff --git a/python/lib/sift/test_reports/v1/test_reports_pb2_grpc.py b/python/lib/sift/test_reports/v1/test_reports_pb2_grpc.py new file mode 100644 index 000000000..fab4528d6 --- /dev/null +++ b/python/lib/sift/test_reports/v1/test_reports_pb2_grpc.py @@ -0,0 +1,543 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from sift.test_reports.v1 import test_reports_pb2 as sift_dot_test__reports_dot_v1_dot_test__reports__pb2 + + +class TestReportServiceStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.ImportTestReport = channel.unary_unary( + '/sift_internal.test_reports.v1.TestReportService/ImportTestReport', + request_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.ImportTestReportRequest.SerializeToString, + response_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.ImportTestReportResponse.FromString, + ) + self.CreateTestReport = channel.unary_unary( + '/sift_internal.test_reports.v1.TestReportService/CreateTestReport', + request_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.CreateTestReportRequest.SerializeToString, + response_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.CreateTestReportResponse.FromString, + ) + self.GetTestReport = channel.unary_unary( + '/sift_internal.test_reports.v1.TestReportService/GetTestReport', + request_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.GetTestReportRequest.SerializeToString, + response_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.GetTestReportResponse.FromString, + ) + self.ListTestReports = channel.unary_unary( + '/sift_internal.test_reports.v1.TestReportService/ListTestReports', + request_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.ListTestReportsRequest.SerializeToString, + response_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.ListTestReportsResponse.FromString, + ) + self.UpdateTestReport = channel.unary_unary( + '/sift_internal.test_reports.v1.TestReportService/UpdateTestReport', + request_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.UpdateTestReportRequest.SerializeToString, + response_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.UpdateTestReportResponse.FromString, + ) + self.DeleteTestReport = channel.unary_unary( + '/sift_internal.test_reports.v1.TestReportService/DeleteTestReport', + request_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.DeleteTestReportRequest.SerializeToString, + response_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.DeleteTestReportResponse.FromString, + ) + self.CreateTestStep = channel.unary_unary( + '/sift_internal.test_reports.v1.TestReportService/CreateTestStep', + request_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.CreateTestStepRequest.SerializeToString, + response_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.CreateTestStepResponse.FromString, + ) + self.ListTestSteps = channel.unary_unary( + '/sift_internal.test_reports.v1.TestReportService/ListTestSteps', + request_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.ListTestStepsRequest.SerializeToString, + response_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.ListTestStepsResponse.FromString, + ) + self.UpdateTestStep = channel.unary_unary( + '/sift_internal.test_reports.v1.TestReportService/UpdateTestStep', + request_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.UpdateTestStepRequest.SerializeToString, + response_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.UpdateTestStepResponse.FromString, + ) + self.DeleteTestStep = channel.unary_unary( + '/sift_internal.test_reports.v1.TestReportService/DeleteTestStep', + request_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.DeleteTestStepRequest.SerializeToString, + response_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.DeleteTestStepResponse.FromString, + ) + self.CreateTestMeasurement = channel.unary_unary( + '/sift_internal.test_reports.v1.TestReportService/CreateTestMeasurement', + request_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.CreateTestMeasurementRequest.SerializeToString, + response_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.CreateTestMeasurementResponse.FromString, + ) + self.CreateTestMeasurements = channel.unary_unary( + '/sift_internal.test_reports.v1.TestReportService/CreateTestMeasurements', + request_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.CreateTestMeasurementsRequest.SerializeToString, + response_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.CreateTestMeasurementsResponse.FromString, + ) + self.ListTestMeasurements = channel.unary_unary( + '/sift_internal.test_reports.v1.TestReportService/ListTestMeasurements', + request_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.ListTestMeasurementsRequest.SerializeToString, + response_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.ListTestMeasurementsResponse.FromString, + ) + self.UpdateTestMeasurement = channel.unary_unary( + '/sift_internal.test_reports.v1.TestReportService/UpdateTestMeasurement', + request_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.UpdateTestMeasurementRequest.SerializeToString, + response_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.UpdateTestMeasurementResponse.FromString, + ) + self.DeleteTestMeasurement = channel.unary_unary( + '/sift_internal.test_reports.v1.TestReportService/DeleteTestMeasurement', + request_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.DeleteTestMeasurementRequest.SerializeToString, + response_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.DeleteTestMeasurementResponse.FromString, + ) + + +class TestReportServiceServicer(object): + """Missing associated documentation comment in .proto file.""" + + def ImportTestReport(self, request, context): + """Imports a test report from an already-uploaded file + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def CreateTestReport(self, request, context): + """Creates a test report + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetTestReport(self, request, context): + """Gets a single test report + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListTestReports(self, request, context): + """Lists test reports with optional filtering + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def UpdateTestReport(self, request, context): + """Updates a test report + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeleteTestReport(self, request, context): + """Deletes a test report + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def CreateTestStep(self, request, context): + """Creates a test step + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListTestSteps(self, request, context): + """Lists test steps with optional filtering + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def UpdateTestStep(self, request, context): + """Updates a test step + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeleteTestStep(self, request, context): + """Deletes a test step + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def CreateTestMeasurement(self, request, context): + """Creates a test measurement + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def CreateTestMeasurements(self, request, context): + """Creates multiple test measurements in a single request + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListTestMeasurements(self, request, context): + """Lists test measurements with optional filtering + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def UpdateTestMeasurement(self, request, context): + """Updates a test measurement + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeleteTestMeasurement(self, request, context): + """Deletes a test measurement + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_TestReportServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'ImportTestReport': grpc.unary_unary_rpc_method_handler( + servicer.ImportTestReport, + request_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.ImportTestReportRequest.FromString, + response_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.ImportTestReportResponse.SerializeToString, + ), + 'CreateTestReport': grpc.unary_unary_rpc_method_handler( + servicer.CreateTestReport, + request_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.CreateTestReportRequest.FromString, + response_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.CreateTestReportResponse.SerializeToString, + ), + 'GetTestReport': grpc.unary_unary_rpc_method_handler( + servicer.GetTestReport, + request_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.GetTestReportRequest.FromString, + response_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.GetTestReportResponse.SerializeToString, + ), + 'ListTestReports': grpc.unary_unary_rpc_method_handler( + servicer.ListTestReports, + request_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.ListTestReportsRequest.FromString, + response_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.ListTestReportsResponse.SerializeToString, + ), + 'UpdateTestReport': grpc.unary_unary_rpc_method_handler( + servicer.UpdateTestReport, + request_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.UpdateTestReportRequest.FromString, + response_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.UpdateTestReportResponse.SerializeToString, + ), + 'DeleteTestReport': grpc.unary_unary_rpc_method_handler( + servicer.DeleteTestReport, + request_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.DeleteTestReportRequest.FromString, + response_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.DeleteTestReportResponse.SerializeToString, + ), + 'CreateTestStep': grpc.unary_unary_rpc_method_handler( + servicer.CreateTestStep, + request_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.CreateTestStepRequest.FromString, + response_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.CreateTestStepResponse.SerializeToString, + ), + 'ListTestSteps': grpc.unary_unary_rpc_method_handler( + servicer.ListTestSteps, + request_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.ListTestStepsRequest.FromString, + response_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.ListTestStepsResponse.SerializeToString, + ), + 'UpdateTestStep': grpc.unary_unary_rpc_method_handler( + servicer.UpdateTestStep, + request_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.UpdateTestStepRequest.FromString, + response_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.UpdateTestStepResponse.SerializeToString, + ), + 'DeleteTestStep': grpc.unary_unary_rpc_method_handler( + servicer.DeleteTestStep, + request_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.DeleteTestStepRequest.FromString, + response_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.DeleteTestStepResponse.SerializeToString, + ), + 'CreateTestMeasurement': grpc.unary_unary_rpc_method_handler( + servicer.CreateTestMeasurement, + request_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.CreateTestMeasurementRequest.FromString, + response_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.CreateTestMeasurementResponse.SerializeToString, + ), + 'CreateTestMeasurements': grpc.unary_unary_rpc_method_handler( + servicer.CreateTestMeasurements, + request_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.CreateTestMeasurementsRequest.FromString, + response_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.CreateTestMeasurementsResponse.SerializeToString, + ), + 'ListTestMeasurements': grpc.unary_unary_rpc_method_handler( + servicer.ListTestMeasurements, + request_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.ListTestMeasurementsRequest.FromString, + response_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.ListTestMeasurementsResponse.SerializeToString, + ), + 'UpdateTestMeasurement': grpc.unary_unary_rpc_method_handler( + servicer.UpdateTestMeasurement, + request_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.UpdateTestMeasurementRequest.FromString, + response_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.UpdateTestMeasurementResponse.SerializeToString, + ), + 'DeleteTestMeasurement': grpc.unary_unary_rpc_method_handler( + servicer.DeleteTestMeasurement, + request_deserializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.DeleteTestMeasurementRequest.FromString, + response_serializer=sift_dot_test__reports_dot_v1_dot_test__reports__pb2.DeleteTestMeasurementResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'sift_internal.test_reports.v1.TestReportService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class TestReportService(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def ImportTestReport(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/sift_internal.test_reports.v1.TestReportService/ImportTestReport', + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.ImportTestReportRequest.SerializeToString, + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.ImportTestReportResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def CreateTestReport(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/sift_internal.test_reports.v1.TestReportService/CreateTestReport', + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.CreateTestReportRequest.SerializeToString, + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.CreateTestReportResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetTestReport(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/sift_internal.test_reports.v1.TestReportService/GetTestReport', + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.GetTestReportRequest.SerializeToString, + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.GetTestReportResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def ListTestReports(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/sift_internal.test_reports.v1.TestReportService/ListTestReports', + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.ListTestReportsRequest.SerializeToString, + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.ListTestReportsResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def UpdateTestReport(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/sift_internal.test_reports.v1.TestReportService/UpdateTestReport', + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.UpdateTestReportRequest.SerializeToString, + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.UpdateTestReportResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def DeleteTestReport(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/sift_internal.test_reports.v1.TestReportService/DeleteTestReport', + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.DeleteTestReportRequest.SerializeToString, + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.DeleteTestReportResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def CreateTestStep(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/sift_internal.test_reports.v1.TestReportService/CreateTestStep', + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.CreateTestStepRequest.SerializeToString, + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.CreateTestStepResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def ListTestSteps(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/sift_internal.test_reports.v1.TestReportService/ListTestSteps', + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.ListTestStepsRequest.SerializeToString, + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.ListTestStepsResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def UpdateTestStep(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/sift_internal.test_reports.v1.TestReportService/UpdateTestStep', + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.UpdateTestStepRequest.SerializeToString, + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.UpdateTestStepResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def DeleteTestStep(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/sift_internal.test_reports.v1.TestReportService/DeleteTestStep', + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.DeleteTestStepRequest.SerializeToString, + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.DeleteTestStepResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def CreateTestMeasurement(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/sift_internal.test_reports.v1.TestReportService/CreateTestMeasurement', + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.CreateTestMeasurementRequest.SerializeToString, + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.CreateTestMeasurementResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def CreateTestMeasurements(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/sift_internal.test_reports.v1.TestReportService/CreateTestMeasurements', + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.CreateTestMeasurementsRequest.SerializeToString, + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.CreateTestMeasurementsResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def ListTestMeasurements(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/sift_internal.test_reports.v1.TestReportService/ListTestMeasurements', + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.ListTestMeasurementsRequest.SerializeToString, + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.ListTestMeasurementsResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def UpdateTestMeasurement(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/sift_internal.test_reports.v1.TestReportService/UpdateTestMeasurement', + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.UpdateTestMeasurementRequest.SerializeToString, + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.UpdateTestMeasurementResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def DeleteTestMeasurement(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/sift_internal.test_reports.v1.TestReportService/DeleteTestMeasurement', + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.DeleteTestMeasurementRequest.SerializeToString, + sift_dot_test__reports_dot_v1_dot_test__reports__pb2.DeleteTestMeasurementResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/python/lib/sift/test_reports/v1/test_reports_pb2_grpc.pyi b/python/lib/sift/test_reports/v1/test_reports_pb2_grpc.pyi new file mode 100644 index 000000000..174fc9323 --- /dev/null +++ b/python/lib/sift/test_reports/v1/test_reports_pb2_grpc.pyi @@ -0,0 +1,324 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" + +import abc +import collections.abc +import grpc +import grpc.aio +import sift.test_reports.v1.test_reports_pb2 +import typing + +_T = typing.TypeVar("_T") + +class _MaybeAsyncIterator(collections.abc.AsyncIterator[_T], collections.abc.Iterator[_T], metaclass=abc.ABCMeta): ... + +class _ServicerContext(grpc.ServicerContext, grpc.aio.ServicerContext): # type: ignore[misc, type-arg] + ... + +class TestReportServiceStub: + def __init__(self, channel: typing.Union[grpc.Channel, grpc.aio.Channel]) -> None: ... + ImportTestReport: grpc.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.ImportTestReportRequest, + sift.test_reports.v1.test_reports_pb2.ImportTestReportResponse, + ] + """Imports a test report from an already-uploaded file""" + + CreateTestReport: grpc.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.CreateTestReportRequest, + sift.test_reports.v1.test_reports_pb2.CreateTestReportResponse, + ] + """Creates a test report""" + + GetTestReport: grpc.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.GetTestReportRequest, + sift.test_reports.v1.test_reports_pb2.GetTestReportResponse, + ] + """Gets a single test report""" + + ListTestReports: grpc.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.ListTestReportsRequest, + sift.test_reports.v1.test_reports_pb2.ListTestReportsResponse, + ] + """Lists test reports with optional filtering""" + + UpdateTestReport: grpc.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.UpdateTestReportRequest, + sift.test_reports.v1.test_reports_pb2.UpdateTestReportResponse, + ] + """Updates a test report""" + + DeleteTestReport: grpc.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.DeleteTestReportRequest, + sift.test_reports.v1.test_reports_pb2.DeleteTestReportResponse, + ] + """Deletes a test report""" + + CreateTestStep: grpc.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.CreateTestStepRequest, + sift.test_reports.v1.test_reports_pb2.CreateTestStepResponse, + ] + """Creates a test step""" + + ListTestSteps: grpc.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.ListTestStepsRequest, + sift.test_reports.v1.test_reports_pb2.ListTestStepsResponse, + ] + """Lists test steps with optional filtering""" + + UpdateTestStep: grpc.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.UpdateTestStepRequest, + sift.test_reports.v1.test_reports_pb2.UpdateTestStepResponse, + ] + """Updates a test step""" + + DeleteTestStep: grpc.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.DeleteTestStepRequest, + sift.test_reports.v1.test_reports_pb2.DeleteTestStepResponse, + ] + """Deletes a test step""" + + CreateTestMeasurement: grpc.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.CreateTestMeasurementRequest, + sift.test_reports.v1.test_reports_pb2.CreateTestMeasurementResponse, + ] + """Creates a test measurement""" + + CreateTestMeasurements: grpc.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.CreateTestMeasurementsRequest, + sift.test_reports.v1.test_reports_pb2.CreateTestMeasurementsResponse, + ] + """Creates multiple test measurements in a single request""" + + ListTestMeasurements: grpc.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.ListTestMeasurementsRequest, + sift.test_reports.v1.test_reports_pb2.ListTestMeasurementsResponse, + ] + """Lists test measurements with optional filtering""" + + UpdateTestMeasurement: grpc.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.UpdateTestMeasurementRequest, + sift.test_reports.v1.test_reports_pb2.UpdateTestMeasurementResponse, + ] + """Updates a test measurement""" + + DeleteTestMeasurement: grpc.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.DeleteTestMeasurementRequest, + sift.test_reports.v1.test_reports_pb2.DeleteTestMeasurementResponse, + ] + """Deletes a test measurement""" + +class TestReportServiceAsyncStub: + ImportTestReport: grpc.aio.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.ImportTestReportRequest, + sift.test_reports.v1.test_reports_pb2.ImportTestReportResponse, + ] + """Imports a test report from an already-uploaded file""" + + CreateTestReport: grpc.aio.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.CreateTestReportRequest, + sift.test_reports.v1.test_reports_pb2.CreateTestReportResponse, + ] + """Creates a test report""" + + GetTestReport: grpc.aio.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.GetTestReportRequest, + sift.test_reports.v1.test_reports_pb2.GetTestReportResponse, + ] + """Gets a single test report""" + + ListTestReports: grpc.aio.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.ListTestReportsRequest, + sift.test_reports.v1.test_reports_pb2.ListTestReportsResponse, + ] + """Lists test reports with optional filtering""" + + UpdateTestReport: grpc.aio.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.UpdateTestReportRequest, + sift.test_reports.v1.test_reports_pb2.UpdateTestReportResponse, + ] + """Updates a test report""" + + DeleteTestReport: grpc.aio.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.DeleteTestReportRequest, + sift.test_reports.v1.test_reports_pb2.DeleteTestReportResponse, + ] + """Deletes a test report""" + + CreateTestStep: grpc.aio.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.CreateTestStepRequest, + sift.test_reports.v1.test_reports_pb2.CreateTestStepResponse, + ] + """Creates a test step""" + + ListTestSteps: grpc.aio.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.ListTestStepsRequest, + sift.test_reports.v1.test_reports_pb2.ListTestStepsResponse, + ] + """Lists test steps with optional filtering""" + + UpdateTestStep: grpc.aio.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.UpdateTestStepRequest, + sift.test_reports.v1.test_reports_pb2.UpdateTestStepResponse, + ] + """Updates a test step""" + + DeleteTestStep: grpc.aio.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.DeleteTestStepRequest, + sift.test_reports.v1.test_reports_pb2.DeleteTestStepResponse, + ] + """Deletes a test step""" + + CreateTestMeasurement: grpc.aio.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.CreateTestMeasurementRequest, + sift.test_reports.v1.test_reports_pb2.CreateTestMeasurementResponse, + ] + """Creates a test measurement""" + + CreateTestMeasurements: grpc.aio.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.CreateTestMeasurementsRequest, + sift.test_reports.v1.test_reports_pb2.CreateTestMeasurementsResponse, + ] + """Creates multiple test measurements in a single request""" + + ListTestMeasurements: grpc.aio.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.ListTestMeasurementsRequest, + sift.test_reports.v1.test_reports_pb2.ListTestMeasurementsResponse, + ] + """Lists test measurements with optional filtering""" + + UpdateTestMeasurement: grpc.aio.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.UpdateTestMeasurementRequest, + sift.test_reports.v1.test_reports_pb2.UpdateTestMeasurementResponse, + ] + """Updates a test measurement""" + + DeleteTestMeasurement: grpc.aio.UnaryUnaryMultiCallable[ + sift.test_reports.v1.test_reports_pb2.DeleteTestMeasurementRequest, + sift.test_reports.v1.test_reports_pb2.DeleteTestMeasurementResponse, + ] + """Deletes a test measurement""" + +class TestReportServiceServicer(metaclass=abc.ABCMeta): + @abc.abstractmethod + def ImportTestReport( + self, + request: sift.test_reports.v1.test_reports_pb2.ImportTestReportRequest, + context: _ServicerContext, + ) -> typing.Union[sift.test_reports.v1.test_reports_pb2.ImportTestReportResponse, collections.abc.Awaitable[sift.test_reports.v1.test_reports_pb2.ImportTestReportResponse]]: + """Imports a test report from an already-uploaded file""" + + @abc.abstractmethod + def CreateTestReport( + self, + request: sift.test_reports.v1.test_reports_pb2.CreateTestReportRequest, + context: _ServicerContext, + ) -> typing.Union[sift.test_reports.v1.test_reports_pb2.CreateTestReportResponse, collections.abc.Awaitable[sift.test_reports.v1.test_reports_pb2.CreateTestReportResponse]]: + """Creates a test report""" + + @abc.abstractmethod + def GetTestReport( + self, + request: sift.test_reports.v1.test_reports_pb2.GetTestReportRequest, + context: _ServicerContext, + ) -> typing.Union[sift.test_reports.v1.test_reports_pb2.GetTestReportResponse, collections.abc.Awaitable[sift.test_reports.v1.test_reports_pb2.GetTestReportResponse]]: + """Gets a single test report""" + + @abc.abstractmethod + def ListTestReports( + self, + request: sift.test_reports.v1.test_reports_pb2.ListTestReportsRequest, + context: _ServicerContext, + ) -> typing.Union[sift.test_reports.v1.test_reports_pb2.ListTestReportsResponse, collections.abc.Awaitable[sift.test_reports.v1.test_reports_pb2.ListTestReportsResponse]]: + """Lists test reports with optional filtering""" + + @abc.abstractmethod + def UpdateTestReport( + self, + request: sift.test_reports.v1.test_reports_pb2.UpdateTestReportRequest, + context: _ServicerContext, + ) -> typing.Union[sift.test_reports.v1.test_reports_pb2.UpdateTestReportResponse, collections.abc.Awaitable[sift.test_reports.v1.test_reports_pb2.UpdateTestReportResponse]]: + """Updates a test report""" + + @abc.abstractmethod + def DeleteTestReport( + self, + request: sift.test_reports.v1.test_reports_pb2.DeleteTestReportRequest, + context: _ServicerContext, + ) -> typing.Union[sift.test_reports.v1.test_reports_pb2.DeleteTestReportResponse, collections.abc.Awaitable[sift.test_reports.v1.test_reports_pb2.DeleteTestReportResponse]]: + """Deletes a test report""" + + @abc.abstractmethod + def CreateTestStep( + self, + request: sift.test_reports.v1.test_reports_pb2.CreateTestStepRequest, + context: _ServicerContext, + ) -> typing.Union[sift.test_reports.v1.test_reports_pb2.CreateTestStepResponse, collections.abc.Awaitable[sift.test_reports.v1.test_reports_pb2.CreateTestStepResponse]]: + """Creates a test step""" + + @abc.abstractmethod + def ListTestSteps( + self, + request: sift.test_reports.v1.test_reports_pb2.ListTestStepsRequest, + context: _ServicerContext, + ) -> typing.Union[sift.test_reports.v1.test_reports_pb2.ListTestStepsResponse, collections.abc.Awaitable[sift.test_reports.v1.test_reports_pb2.ListTestStepsResponse]]: + """Lists test steps with optional filtering""" + + @abc.abstractmethod + def UpdateTestStep( + self, + request: sift.test_reports.v1.test_reports_pb2.UpdateTestStepRequest, + context: _ServicerContext, + ) -> typing.Union[sift.test_reports.v1.test_reports_pb2.UpdateTestStepResponse, collections.abc.Awaitable[sift.test_reports.v1.test_reports_pb2.UpdateTestStepResponse]]: + """Updates a test step""" + + @abc.abstractmethod + def DeleteTestStep( + self, + request: sift.test_reports.v1.test_reports_pb2.DeleteTestStepRequest, + context: _ServicerContext, + ) -> typing.Union[sift.test_reports.v1.test_reports_pb2.DeleteTestStepResponse, collections.abc.Awaitable[sift.test_reports.v1.test_reports_pb2.DeleteTestStepResponse]]: + """Deletes a test step""" + + @abc.abstractmethod + def CreateTestMeasurement( + self, + request: sift.test_reports.v1.test_reports_pb2.CreateTestMeasurementRequest, + context: _ServicerContext, + ) -> typing.Union[sift.test_reports.v1.test_reports_pb2.CreateTestMeasurementResponse, collections.abc.Awaitable[sift.test_reports.v1.test_reports_pb2.CreateTestMeasurementResponse]]: + """Creates a test measurement""" + + @abc.abstractmethod + def CreateTestMeasurements( + self, + request: sift.test_reports.v1.test_reports_pb2.CreateTestMeasurementsRequest, + context: _ServicerContext, + ) -> typing.Union[sift.test_reports.v1.test_reports_pb2.CreateTestMeasurementsResponse, collections.abc.Awaitable[sift.test_reports.v1.test_reports_pb2.CreateTestMeasurementsResponse]]: + """Creates multiple test measurements in a single request""" + + @abc.abstractmethod + def ListTestMeasurements( + self, + request: sift.test_reports.v1.test_reports_pb2.ListTestMeasurementsRequest, + context: _ServicerContext, + ) -> typing.Union[sift.test_reports.v1.test_reports_pb2.ListTestMeasurementsResponse, collections.abc.Awaitable[sift.test_reports.v1.test_reports_pb2.ListTestMeasurementsResponse]]: + """Lists test measurements with optional filtering""" + + @abc.abstractmethod + def UpdateTestMeasurement( + self, + request: sift.test_reports.v1.test_reports_pb2.UpdateTestMeasurementRequest, + context: _ServicerContext, + ) -> typing.Union[sift.test_reports.v1.test_reports_pb2.UpdateTestMeasurementResponse, collections.abc.Awaitable[sift.test_reports.v1.test_reports_pb2.UpdateTestMeasurementResponse]]: + """Updates a test measurement""" + + @abc.abstractmethod + def DeleteTestMeasurement( + self, + request: sift.test_reports.v1.test_reports_pb2.DeleteTestMeasurementRequest, + context: _ServicerContext, + ) -> typing.Union[sift.test_reports.v1.test_reports_pb2.DeleteTestMeasurementResponse, collections.abc.Awaitable[sift.test_reports.v1.test_reports_pb2.DeleteTestMeasurementResponse]]: + """Deletes a test measurement""" + +def add_TestReportServiceServicer_to_server(servicer: TestReportServiceServicer, server: typing.Union[grpc.Server, grpc.aio.Server]) -> None: ... diff --git a/python/lib/sift_client/_internal/low_level_wrappers/__init__.py b/python/lib/sift_client/_internal/low_level_wrappers/__init__.py index 6bdef7f17..69722cc41 100644 --- a/python/lib/sift_client/_internal/low_level_wrappers/__init__.py +++ b/python/lib/sift_client/_internal/low_level_wrappers/__init__.py @@ -7,6 +7,8 @@ from sift_client._internal.low_level_wrappers.ping import PingLowLevelClient from sift_client._internal.low_level_wrappers.rules import RulesLowLevelClient from sift_client._internal.low_level_wrappers.runs import RunsLowLevelClient +from sift_client._internal.low_level_wrappers.test_results import TestResultsLowLevelClient +from sift_client._internal.low_level_wrappers.upload import UploadLowLevelClient __all__ = [ "AssetsLowLevelClient", @@ -16,4 +18,6 @@ "PingLowLevelClient", "RulesLowLevelClient", "RunsLowLevelClient", + "TestResultsLowLevelClient", + "UploadLowLevelClient", ] diff --git a/python/lib/sift_client/_internal/low_level_wrappers/test_results.py b/python/lib/sift_client/_internal/low_level_wrappers/test_results.py new file mode 100644 index 000000000..d46bd16d1 --- /dev/null +++ b/python/lib/sift_client/_internal/low_level_wrappers/test_results.py @@ -0,0 +1,507 @@ +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING, Any, cast + +from sift.test_reports.v1.test_reports_pb2 import ( + CreateTestMeasurementRequest, + CreateTestMeasurementResponse, + CreateTestMeasurementsRequest, + CreateTestMeasurementsResponse, + CreateTestReportRequest, + CreateTestReportResponse, + CreateTestStepRequest, + CreateTestStepResponse, + DeleteTestMeasurementRequest, + DeleteTestReportRequest, + DeleteTestStepRequest, + GetTestReportRequest, + GetTestReportResponse, + ImportTestReportRequest, + ImportTestReportResponse, + ListTestMeasurementsRequest, + ListTestMeasurementsResponse, + ListTestReportsRequest, + ListTestReportsResponse, + ListTestStepsRequest, + ListTestStepsResponse, + UpdateTestMeasurementRequest, + UpdateTestMeasurementResponse, + UpdateTestReportRequest, + UpdateTestReportResponse, + UpdateTestStepRequest, + UpdateTestStepResponse, +) +from sift.test_reports.v1.test_reports_pb2_grpc import TestReportServiceStub + +from sift_client._internal.low_level_wrappers.base import LowLevelClientBase +from sift_client.sift_types.test_report import ( + TestMeasurement, + TestMeasurementUpdate, + TestReport, + TestReportUpdate, + TestStatus, + TestStep, + TestStepUpdate, +) +from sift_client.transport import WithGrpcClient +from sift_client.util.metadata import metadata_dict_to_proto + +if TYPE_CHECKING: + from datetime import datetime + + from sift_client.transport.grpc_transport import GrpcClient + +# Configure logging +logger = logging.getLogger(__name__) + + +class TestResultsLowLevelClient(LowLevelClientBase, WithGrpcClient): + """Low-level client for the TestResultsAPI. + + This class provides a thin wrapper around the autogenerated bindings for the TestResultsAPI. + """ + + def __init__(self, grpc_client: GrpcClient): + """Initialize the TestResultsLowLevelClient. + + Args: + grpc_client: The gRPC client to use for making API calls. + """ + super().__init__(grpc_client) + + async def import_test_report(self, remote_file_id: str) -> TestReport: + """Import a test report from an already-uploaded file. + + Args: + remote_file_id: The remote file ID containing the XML test data. + + Returns: + The imported TestReport. + + Raises: + ValueError: If remote_file_id is not provided. + """ + if not remote_file_id: + raise ValueError("remote_file_id must be provided") + + request = ImportTestReportRequest(remote_file_id=remote_file_id) + response = await self._grpc_client.get_stub(TestReportServiceStub).ImportTestReport(request) + grpc_test_report = cast("ImportTestReportResponse", response).test_report + return TestReport._from_proto(grpc_test_report) + + async def create_test_report( + self, + *, + status: TestStatus | int, + name: str, + test_system_name: str, + test_case: str, + start_time: datetime, + end_time: datetime, + metadata: dict[str, str | float | bool] | None = None, + serial_number: str | None = None, + part_number: str | None = None, + system_operator: str | None = None, + ) -> TestReport: + """Create a new test report. + + Args: + status: The status of the test run (TestStatus enum). + name: The name of the test run. + test_system_name: The name of the test system. + test_case: The test case that was run. + start_time: The start time of the test run. + end_time: The end time of the test run. + metadata: The metadata values associated with this test run. + serial_number: The serial number for the DUT. + part_number: The part number for the DUT. + system_operator: Unique identifier for user owner. + + Returns: + The created TestReport. + """ + request_kwargs: dict[str, Any] = { + "status": status.value if isinstance(status, TestStatus) else status, + "name": name, + "test_system_name": test_system_name, + "test_case": test_case, + } + + # Handle timestamps + from google.protobuf.timestamp_pb2 import Timestamp + + start_ts = Timestamp() + start_ts.FromDatetime(start_time) + request_kwargs["start_time"] = start_ts + + end_ts = Timestamp() + end_ts.FromDatetime(end_time) + request_kwargs["end_time"] = end_ts + + if metadata is not None: + request_kwargs["metadata"] = metadata_dict_to_proto(metadata) + + if serial_number is not None: + request_kwargs["serial_number"] = serial_number + + if part_number is not None: + request_kwargs["part_number"] = part_number + + if system_operator is not None: + request_kwargs["system_operator"] = system_operator + + request = CreateTestReportRequest(**request_kwargs) + response = await self._grpc_client.get_stub(TestReportServiceStub).CreateTestReport(request) + grpc_test_report = cast("CreateTestReportResponse", response).test_report + return TestReport._from_proto(grpc_test_report) + + async def get_test_report(self, test_report_id: str) -> TestReport: + """Get a test report by test_report_id. + + Args: + test_report_id: The test report ID to get. + + Returns: + The TestReport. + + Raises: + ValueError: If test_report_id is not provided. + """ + if not test_report_id: + raise ValueError("test_report_id must be provided") + + request = GetTestReportRequest(test_report_id=test_report_id) + response = await self._grpc_client.get_stub(TestReportServiceStub).GetTestReport(request) + grpc_test_report = cast("GetTestReportResponse", response).test_report + return TestReport._from_proto(grpc_test_report) + + async def list_test_reports( + self, + *, + page_size: int | None = None, + page_token: str | None = None, + query_filter: str | None = None, + order_by: str | None = None, + ) -> tuple[list[TestReport], str]: + """List test reports with optional filtering and pagination. + + Args: + page_size: The maximum number of test reports to return. + page_token: A page token for pagination. + query_filter: A CEL filter string. + order_by: How to order the retrieved test reports. + + Returns: + A tuple of (test_reports, next_page_token). + """ + request_kwargs: dict[str, Any] = {} + if page_size is not None: + request_kwargs["page_size"] = page_size + if page_token is not None: + request_kwargs["page_token"] = page_token + if query_filter is not None: + request_kwargs["filter"] = query_filter + if order_by is not None: + request_kwargs["order_by"] = order_by + + request = ListTestReportsRequest(**request_kwargs) + response = await self._grpc_client.get_stub(TestReportServiceStub).ListTestReports(request) + response = cast("ListTestReportsResponse", response) + + test_reports = [TestReport._from_proto(tr) for tr in response.test_reports] + return test_reports, response.next_page_token + + async def list_all_test_reports( + self, + *, + query_filter: str | None = None, + order_by: str | None = None, + max_results: int | None = None, + ) -> list[TestReport]: + """List all test reports with optional filtering. + + Args: + query_filter: A CEL filter string. + order_by: How to order the retrieved test reports. + max_results: Maximum number of results to return. + + Returns: + A list of all matching test reports. + """ + return await self._handle_pagination( + self.list_test_reports, + kwargs={"query_filter": query_filter}, + order_by=order_by, + max_results=max_results, + ) + + async def update_test_report(self, update: TestReportUpdate) -> TestReport: + """Update an existing test report. + + Args: + update: The updates to apply. + + Returns: + The updated TestReport. + """ + test_report_proto, field_mask = update.to_proto_with_mask() + request = UpdateTestReportRequest(test_report=test_report_proto, update_mask=field_mask) + response = await self._grpc_client.get_stub(TestReportServiceStub).UpdateTestReport(request) + grpc_test_report = cast("UpdateTestReportResponse", response).test_report + return TestReport._from_proto(grpc_test_report) + + async def delete_test_report(self, test_report_id: str) -> None: + """Delete a test report. + + Args: + test_report_id: The ID of the test report to delete. + + Raises: + ValueError: If test_report_id is not provided. + """ + if not test_report_id: + raise ValueError("test_report_id must be provided") + + request = DeleteTestReportRequest(test_report_id=test_report_id) + await self._grpc_client.get_stub(TestReportServiceStub).DeleteTestReport(request) + + # Test Steps + + async def create_test_step(self, test_step: TestStep) -> TestStep: + """Create a new test step. + + Args: + test_step: The test step to create. + + Returns: + The created TestStep. + """ + request = CreateTestStepRequest(test_step=test_step._to_proto()) + response = await self._grpc_client.get_stub(TestReportServiceStub).CreateTestStep(request) + grpc_test_step = cast("CreateTestStepResponse", response).test_step + return TestStep._from_proto(grpc_test_step) + + async def list_test_steps( + self, + *, + page_size: int | None = None, + page_token: str | None = None, + query_filter: str | None = None, + order_by: str | None = None, + ) -> tuple[list[TestStep], str]: + """List test steps with optional filtering and pagination. + + Args: + page_size: The maximum number of test steps to return. + page_token: A page token for pagination. + query_filter: A CEL filter string. + order_by: How to order the retrieved test steps. + + Returns: + A tuple of (test_steps, next_page_token). + """ + request_kwargs: dict[str, Any] = {} + if page_size is not None: + request_kwargs["page_size"] = page_size + if page_token is not None: + request_kwargs["page_token"] = page_token + if query_filter is not None: + request_kwargs["filter"] = query_filter + if order_by is not None: + request_kwargs["order_by"] = order_by + + request = ListTestStepsRequest(**request_kwargs) + response = await self._grpc_client.get_stub(TestReportServiceStub).ListTestSteps(request) + response = cast("ListTestStepsResponse", response) + + test_steps = [TestStep._from_proto(ts) for ts in response.test_steps] + return test_steps, response.next_page_token + + async def list_all_test_steps( + self, + *, + query_filter: str | None = None, + order_by: str | None = None, + max_results: int | None = None, + ) -> list[TestStep]: + """List all test steps with optional filtering. + + Args: + query_filter: A CEL filter string. + order_by: How to order the retrieved test steps. + max_results: Maximum number of results to return. + + Returns: + A list of all matching test steps. + """ + return await self._handle_pagination( + self.list_test_steps, + kwargs={"query_filter": query_filter}, + order_by=order_by, + max_results=max_results, + ) + + async def update_test_step(self, update: TestStepUpdate) -> TestStep: + """Update an existing test step. + + Args: + update: The updates to apply. + + Returns: + The updated TestStep. + """ + test_step_proto, field_mask = update.to_proto_with_mask() + + request = UpdateTestStepRequest(test_step=test_step_proto, update_mask=field_mask) + response = await self._grpc_client.get_stub(TestReportServiceStub).UpdateTestStep(request) + grpc_test_step = cast("UpdateTestStepResponse", response).test_step + return TestStep._from_proto(grpc_test_step) + + async def delete_test_step(self, test_step_id: str) -> None: + """Delete a test step. + + Args: + test_step_id: The ID of the test step to delete. + + Raises: + ValueError: If test_step_id is not provided. + """ + if not test_step_id: + raise ValueError("test_step_id must be provided") + + request = DeleteTestStepRequest(test_step_id=test_step_id) + await self._grpc_client.get_stub(TestReportServiceStub).DeleteTestStep(request) + + # Test Measurements + + async def create_test_measurement(self, test_measurement: TestMeasurement) -> TestMeasurement: + """Create a new test measurement. + + Args: + test_measurement: The test measurement to create. + + Returns: + The created TestMeasurement. + """ + request = CreateTestMeasurementRequest(test_measurement=test_measurement._to_proto()) + response = await self._grpc_client.get_stub(TestReportServiceStub).CreateTestMeasurement( + request + ) + grpc_test_measurement = cast("CreateTestMeasurementResponse", response).test_measurement + return TestMeasurement._from_proto(grpc_test_measurement) + + async def create_test_measurements( + self, test_measurements: list[TestMeasurement] + ) -> tuple[int, list[str]]: + """Create multiple test measurements in a single request. + + Args: + test_measurements: The test measurements to create. + + Returns: + A tuple of (measurements_created_count, measurement_ids). + """ + measurement_protos = [tm._to_proto() for tm in test_measurements] + request = CreateTestMeasurementsRequest(test_measurements=measurement_protos) + response = await self._grpc_client.get_stub(TestReportServiceStub).CreateTestMeasurements( + request + ) + response = cast("CreateTestMeasurementsResponse", response) + return response.measurements_created_count, list(response.measurement_ids) + + async def list_test_measurements( + self, + *, + page_size: int | None = None, + page_token: str | None = None, + query_filter: str | None = None, + order_by: str | None = None, + ) -> tuple[list[TestMeasurement], str]: + """List test measurements with optional filtering and pagination. + + Args: + page_size: The maximum number of test measurements to return. + page_token: A page token for pagination. + query_filter: A CEL filter string. + order_by: How to order the retrieved test measurements. + + Returns: + A tuple of (test_measurements, next_page_token). + """ + request_kwargs: dict[str, Any] = {} + if page_size is not None: + request_kwargs["page_size"] = page_size + if page_token is not None: + request_kwargs["page_token"] = page_token + if query_filter is not None: + request_kwargs["filter"] = query_filter + if order_by is not None: + request_kwargs["order_by"] = order_by + + request = ListTestMeasurementsRequest(**request_kwargs) + response = await self._grpc_client.get_stub(TestReportServiceStub).ListTestMeasurements( + request + ) + response = cast("ListTestMeasurementsResponse", response) + + test_measurements = [TestMeasurement._from_proto(tm) for tm in response.test_measurements] + return test_measurements, response.next_page_token + + async def list_all_test_measurements( + self, + *, + query_filter: str | None = None, + order_by: str | None = None, + max_results: int | None = None, + ) -> list[TestMeasurement]: + """List all test measurements with optional filtering. + + Args: + query_filter: A CEL filter string. + order_by: How to order the retrieved test measurements. + max_results: Maximum number of results to return. + + Returns: + A list of all matching test measurements. + """ + return await self._handle_pagination( + self.list_test_measurements, + kwargs={"query_filter": query_filter}, + order_by=order_by, + max_results=max_results, + ) + + async def update_test_measurement(self, update: TestMeasurementUpdate) -> TestMeasurement: + """Update an existing test measurement. + + Args: + update: The updates to apply. + + Returns: + The updated TestMeasurement. + """ + test_measurement_proto, field_mask = update.to_proto_with_mask() + request = UpdateTestMeasurementRequest( + test_measurement=test_measurement_proto, update_mask=field_mask + ) + response = await self._grpc_client.get_stub(TestReportServiceStub).UpdateTestMeasurement( + request + ) + grpc_test_measurement = cast("UpdateTestMeasurementResponse", response).test_measurement + return TestMeasurement._from_proto(grpc_test_measurement) + + async def delete_test_measurement(self, measurement_id: str) -> None: + """Delete a test measurement. + + Args: + measurement_id: The ID of the test measurement to delete. + + Raises: + ValueError: If measurement_id is not provided. + """ + if not measurement_id: + raise ValueError("measurement_id must be provided") + + request = DeleteTestMeasurementRequest(measurement_id=measurement_id) + await self._grpc_client.get_stub(TestReportServiceStub).DeleteTestMeasurement(request) diff --git a/python/lib/sift_client/_internal/low_level_wrappers/upload.py b/python/lib/sift_client/_internal/low_level_wrappers/upload.py new file mode 100644 index 000000000..ba7e595f3 --- /dev/null +++ b/python/lib/sift_client/_internal/low_level_wrappers/upload.py @@ -0,0 +1,189 @@ +from __future__ import annotations + +import asyncio +import logging +from pathlib import Path +from typing import TYPE_CHECKING, Any + +from requests_toolbelt import MultipartEncoder + +from sift_client._internal.low_level_wrappers.base import LowLevelClientBase +from sift_client.transport import WithRestClient + +if TYPE_CHECKING: + from sift_client.transport.rest_transport import RestClient + +# Configure logging +logger = logging.getLogger(__name__) + + +class UploadLowLevelClient(LowLevelClientBase, WithRestClient): + """Low-level client for file upload operations. + + This class provides a thin wrapper for uploading file attachments via REST API. + + Example: + ```python + from sift_client.client import SiftClient + from sift_client._internal.low_level_wrappers.upload import UploadLowLevelClient + + # Initialize the REST client + sift_client = SiftClient(rest_url="https://your-api.siftstack.com", grpc_url="https://your-grpc-api.siftstack.com", api_key="your-api-key") + + # Create the upload client + upload_client = UploadLowLevelClient(sift_client.rest_client) + + # Upload a file + remote_file_id = await upload_client.upload_attachment( + path="path/to/file.mp4", + entity_id="run_12345", + entity_type="runs", + description="Video of test run", + ) + ``` + """ + + UPLOAD_PATH = "/api/v0/remote-files/upload" + UPLOAD_BULK_PATH = "/api/v0/remote-files/upload:bulk" + + def __init__(self, rest_client: RestClient): + """Initialize the UploadLowLevelClient. + + Args: + rest_client: The REST client to use for making API calls. + """ + super().__init__(rest_client) + + async def upload_attachment( + self, + path: str | Path, + entity_id: str, + entity_type: str, + metadata: dict[str, Any] | None = None, + description: str | None = None, + organization_id: str | None = None, + ) -> str: + """Upload a file attachment to an entity. + + Args: + path: Path to the file to upload. + entity_id: The ID of the entity to attach the file to. + entity_type: The type of entity (e.g., "runs", "annotations", "annotation_logs"). + metadata: Optional metadata for the file (e.g., video/image metadata). + description: Optional description of the file. + organization_id: Optional organization ID. + + Returns: + The remote file ID of the uploaded file. + + Raises: + ValueError: If the path doesn't point to a regular file or MIME type cannot be determined. + Exception: If the upload fails. + """ + posix_path = Path(path) if isinstance(path, str) else path + + if not posix_path.is_file(): + raise ValueError(f"Provided path, '{path}', does not point to a regular file.") + + file_name, mimetype, content_encoding = self._mime_and_content_type_from_path(posix_path) + + if not mimetype: + raise ValueError(f"The MIME-type of '{posix_path}' could not be computed.") + + # Run the synchronous file upload in a thread pool to avoid blocking the event loop + loop = asyncio.get_event_loop() + return await loop.run_in_executor( + None, + self._upload_file_sync, + posix_path, + file_name, + mimetype, + content_encoding, + entity_id, + entity_type, + metadata, + description, + organization_id, + ) + + def _upload_file_sync( + self, + path: Path, + file_name: str, + mimetype: str, + content_encoding: str | None, + entity_id: str, + entity_type: str, + metadata: dict[str, Any] | None, + description: str | None, + organization_id: str | None, + ) -> str: + """Synchronous helper to upload the file. + + This is called from a thread pool to avoid blocking the async event loop. + """ + with open(path, "rb") as file: + form_fields: dict[str, Any] = { + "entityId": entity_id, + "entityType": entity_type, + } + + if content_encoding: + form_fields["file"] = ( + file_name, + file, + mimetype, + { + "Content-Encoding": content_encoding, + }, + ) + else: + form_fields["file"] = (file_name, file, mimetype) + + if metadata: + import json + + form_fields["metadata"] = json.dumps( + metadata, default=lambda x: x.as_json() if hasattr(x, "as_json") else x + ) + + if organization_id: + form_fields["organizationId"] = organization_id + + if description: + form_fields["description"] = description + + form_data = MultipartEncoder(fields=form_fields) + + # Use the RestClient to make the POST request + response = self._rest_client.post( + endpoint=self.UPLOAD_PATH, + data=form_data, # type: ignore + headers={ + "Content-Type": form_data.content_type, + }, + ) + + if response.status_code != 200: + raise Exception( + f"Request failed with status code {response.status_code} ({response.reason})." + ) + + response_data = response.json() + return response_data.get("remoteFile", {}).get("remoteFileId") + + @staticmethod + def _mime_and_content_type_from_path(path: Path) -> tuple[str, str | None, str | None]: + """Determine the MIME type and content encoding from a file path. + + Args: + path: The file path to analyze. + + Returns: + A tuple of (file_name, mime_type, content_encoding). + """ + import mimetypes + + file_name = path.name + mime, encoding = mimetypes.guess_type(path) + return file_name, mime, encoding diff --git a/python/lib/sift_client/_tests/integrated/test_files/demo_test_report.xml b/python/lib/sift_client/_tests/integrated/test_files/demo_test_report.xml new file mode 100644 index 000000000..1b19dee80 --- /dev/null +++ b/python/lib/sift_client/_tests/integrated/test_files/demo_test_report.xml @@ -0,0 +1,4313 @@ + + + + + + + + + + SequenceCall + Main + + + + + + + + + + + SequenceCall + Setup + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + Done + + + + + + + AdditionalResults + Main + + + + + + + + + + + + + DAQ1.100Hz Engine.uut_2_p + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + Done + + + + + + + AdditionalResults + Main + + + + + + + + + + + SequenceCall + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + + + + + + + + NI_Flow_While + Main + + + + + + + + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + + {False} + + + + + + + NI_Flow_If + Main + + + + + + + + + + + + + (If) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + NI_Wait + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + NI_Wait + Main + + + + + + + + + + + SequenceCall + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + + + + + + + + NI_Flow_While + Main + + + + + + + + + + + NumericLimitTest + Main + + + + + + + + + + + + + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + + + + + + + + + NI_Flow_While + Main + + + + + + + + + + + NumericLimitTest + Main + + + + + + + + + + + + + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + NI_Wait + Main + + + + + + + + + + + SequenceCall + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + =20 && Locals.TimestampDelta<=60 && Locals.i<=60]]> + + + + + + + NI_Flow_While + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + + =20 && Locals.TimestampDelta<=60 && Locals.i<=60]]> + + + + + + + NI_Flow_While + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + + =20 && Locals.TimestampDelta<=60 && Locals.i<=60]]> + + + + + + + NI_Flow_While + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + + =20 && Locals.TimestampDelta<=60 && Locals.i<=60]]> + + + + + + + NI_Flow_While + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + + =20 && Locals.TimestampDelta<=60 && Locals.i<=60]]> + + + + + + + NI_Flow_While + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + + =20 && Locals.TimestampDelta<=60 && Locals.i<=60]]> + + + + + + + NI_Flow_While + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + + =20 && Locals.TimestampDelta<=60 && Locals.i<=60]]> + + + + + + + NI_Flow_While + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + + =20 && Locals.TimestampDelta<=60 && Locals.i<=60]]> + + + + + + + NI_Flow_While + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + + =20 && Locals.TimestampDelta<=60 && Locals.i<=60]]> + + + + + + + NI_Flow_While + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + + =20 && Locals.TimestampDelta<=60 && Locals.i<=60]]> + + + + + + + NI_Flow_While + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + + =20 && Locals.TimestampDelta<=60 && Locals.i<=60]]> + + + + + + + NI_Flow_While + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + + =20 && Locals.TimestampDelta<=60 && Locals.i<=60]]> + + + + + + + NI_Flow_While + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + + =20 && Locals.TimestampDelta<=60 && Locals.i<=60]]> + + + + + + + NI_Flow_While + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + + =20 && Locals.TimestampDelta<=60 && Locals.i<=60]]> + + + + + + + NI_Flow_While + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + + =20 && Locals.TimestampDelta<=60 && Locals.i<=60]]> + + + + + + + NI_Flow_While + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + + =20 && Locals.TimestampDelta<=60 && Locals.i<=60]]> + + + + + + + NI_Flow_While + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + + =20 && Locals.TimestampDelta<=60 && Locals.i<=60]]> + + + + + + + NI_Flow_While + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + + =20 && Locals.TimestampDelta<=60 && Locals.i<=60]]> + + + + + + + NI_Flow_While + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + + =20 && Locals.TimestampDelta<=60 && Locals.i<=60]]> + + + + + + + NI_Flow_While + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + NI_Wait + Main + + + + + + + + + + + PassFailTest + Main + + + + + + + + + + + + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + SequenceCall + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + + + + + + + + NI_Flow_While + Main + + + + + + + + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + + + + + + + + + NI_Flow_While + Main + + + + + + + + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + + {False} + + + + + + + NI_Flow_If + Main + + + + + + + + + + + + + (If) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + NI_Wait + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + NI_Wait + Main + + + + + + + + + + + Label + Main + + + + + + + + + + + SequenceCall + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + + + + + + + + NI_Flow_While + Main + + + + + + + + + + + NumericLimitTest + Main + + + + + + + + + + + + + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + + + + + + + + + NI_Flow_While + Main + + + + + + + + + + + NumericLimitTest + Main + + + + + + + + + + + + + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + + SequenceCall + Main + + + + + + + + + + + + + + + + + + + + + + + + + + + AdditionalResults + Main + + + + + + + + + + + + SequenceCall + Main + + + + + + + + + + + + + + + + + + + + + + + + + + + AdditionalResults + Main + + + + + + + + + + + + SequenceCall + Main + + + + + + + + + + + SequenceCall + Main + + + + + + + + + + + + + + + + + + SequenceCall + Main + + + + + + + + + + + + + + NumericLimitTest + Main + + + + + + + + + + + + + + + + + + + + + + + + NumericLimitTest + Main + + + + + + + + + + + NumericLimitTest + Main + + + + + + + + + + + + + + + + + + + + + + + + NumericLimitTest + Main + + + + + + + + + + + + + + + + + + + + + + + + + SequenceCall + Main + + + + + + + + + + + + + + NumericLimitTest + Main + + + + + + + + + + + + + + + + + + + + + + + + NumericLimitTest + Main + + + + + + + + + + + NumericLimitTest + Main + + + + + + + + + + + + + + + + + + + + + + + + NumericLimitTest + Main + + + + + + + + + + + + + + + + + + + + + + + + + SequenceCall + Main + + + + + + + + + + + + + + NumericLimitTest + Main + + + + + + + + + + + + + + + + + + + + + + + + NumericLimitTest + Main + + + + + + + + + + + NumericLimitTest + Main + + + + + + + + + + + + + + + + + + + + + + + + NumericLimitTest + Main + + + + + + + + + + + + + + + + + + + + + + + + + SequenceCall + Main + + + + + + + + + + + + + + + + + + SequenceCall + Main + + + + + + + + + + + + + + Action + Main + + + + + + + + + + + + NI_Wait + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + NI_Wait + Main + + + + + + + + + + + + + + + + + + + + + + + + + + + AdditionalResults + Main + + + + + + + + + + + + SequenceCall + Main + + + + + + + + + + + + + + Action + Main + + + + + + + + + + + + NI_Wait + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + NI_Wait + Main + + + + + + + + + + + + + + + + + + + + + + + + + + + AdditionalResults + Main + + + + + + + + + + + + SequenceCall + Main + + + + + + + + + + + + + + Action + Main + + + + + + + + + + + + NI_Wait + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + NI_Wait + Main + + + + + + + + + + + + + + + + + + + + + + + + + + + AdditionalResults + Main + + + + + + + + + + + + SequenceCall + Main + + + + + + + + + + + + + + + + + + + + + + + + + + + AdditionalResults + Main + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AdditionalResults + Main + + + + + + + + + + + + + {False} + + + + + + + NI_Flow_If + Main + + + + + + + + + + + + + (If) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + + {False} + + + + + + + NI_Flow_If + Main + + + + + + + + + + + + + (If) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + SequenceCall + Main + + + + + + + + + + + SequenceCall + Main + + + + + + + + + + + SequenceCall + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + SequenceCall + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + + =20 && Locals.TimestampDelta<=60 && Locals.i<=60]]> + + + + + + + NI_Flow_While + Main + + + + + + + + + + + + + (While) + + + + + + + NI_Flow_End + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + SequenceCall + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + SequenceCall + Main + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + Action + Main + + + + + + + + + + + + + example-test-system + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + xml + + + + + %$.13g + + + + + True + + + + + + + + + + + + + + + + + + + + + + + + + + SequenceFileDirectory + + + + + + + + + + Report + + + + + + + + + + + + + + + + + + + + + + + + DLL + + + + + + + + + + #FFFFFF + + + + + #32FFFF + + + + + #0000E0 + + + + + #8000CC + + + + + #000000 + + + + + #00C4C4 + + + + + #FF8000 + + + + + #ADD8E6 + + + + + #FF32CC + + + + + #000080 + + + + + #FF0000 + + + + + #FF0000 + + + + + #008000 + + + + + #B98028 + + + + + #008000 + + + + + #FFCC33 + + + + + #0000FF + + + + + #FF0000 + + + + + #FF0000 + + + + + #00FF00 + + + + + #FFFF00 + + + + + #00FFCC + + + + + #FFCC33 + + + + + #FF2020 + + + + + #D0D0D0 + + + + + #00C4C4 + + + + + + + TimeFirst + + + + + + + + + + + + + + + + + BatchReport + + + + + + + + tr5_horizontal.xsl + + + + + C:\Program Files\National Instruments\TestStand 2023\Components\Models\TestStandModels\ATML\StyleSheets\tr5_horizontal.xsl + + + + + + horizontal.xsl + + + report.xsl + + + expand.xsl + + + + + + + + + + + + + TR_horizontal.xsl + + + trml.xsl + + + + + + + + + + + + + + + + + + + + + + + + + + + ATML5 + + + + + "$(ClientFileDir)\\$(ClientFileName)_Report[$(FileTime)][$(FileDate)]$(Unique).$(FileExtension)" + + + + + "$(ClientFileDir)\\$(ClientFileName)_Report[$(FileTime)][$(FileDate)]$(Unique).$(FileExtension)" + + + + + "$(ClientFileDir)\\$(ClientFileName)_BatchReport[$(FileTime)][$(FileDate)]$(Unique).$(FileExtension)" + + + + + "$(ClientFileDir)\\$(ClientFileName)_BatchReport[$(FileTime)][$(FileDate)]$(Unique).$(FileExtension)" + + + + + + + + + + + + + + + + + + + + + + tr5_horizontal.xsl + + + tr5_report.xsl + + + tr5_expand.xsl + + + + + + + + + + + + + + + + + + + + + + + + tr6_horizontal.xsl + + + tr6_report.xsl + + + tr6_expand.xsl + + + + + + + + + + + + + + LPTC-02-WIN + + + + + UNKNOWN + + + + + + + + + + + 3898 + + + + + + + + NONE + + + + + + + + diff --git a/python/lib/sift_client/_tests/integrated/test_results.py b/python/lib/sift_client/_tests/integrated/test_results.py new file mode 100644 index 000000000..85d5d70dd --- /dev/null +++ b/python/lib/sift_client/_tests/integrated/test_results.py @@ -0,0 +1,255 @@ +import asyncio +import os +from datetime import datetime, timedelta, timezone +from pathlib import Path + +import grpc +from grpc import aio as aiogrpc + +from sift_client.client import SiftClient, SiftConnectionConfig +from sift_client.sift_types import TestMeasurementType, TestStatus, TestStepType +from sift_client.sift_types.test_report import ( + NumericBounds, + TestMeasurement, + TestReportUpdate, + TestStep, +) + + +async def main(): + grpc_url = os.getenv("SIFT_GRPC_URI", "localhost:50051") + rest_url = os.getenv("SIFT_REST_URI", "localhost:8080") + api_key = os.getenv("SIFT_API_KEY", "") + client = SiftClient( + connection_config=SiftConnectionConfig( + grpc_url=grpc_url, + api_key=api_key, + rest_url=rest_url, + ) + ) + + # Create a test report + simulated_time = datetime.now(timezone.utc) + print(TestStatus) + test_report = client.test_results.create_report( + status=TestStatus.PASSED, + name="Test Report with Steps and Measurements", + test_system_name="Test System", + test_case="Test Case", + start_time=simulated_time, + end_time=simulated_time, + ) + print(f"Created test report: {test_report.id_}") + + # Create multiple test steps + step1 = client.test_results.create_step( + TestStep( + test_report_id=test_report.id_, + name="Step 1: Initialization", + description="Initialize the test environment", + step_type=TestStepType.ACTION, + step_path="1", + status=TestStatus.PASSED, + start_time=simulated_time, + end_time=simulated_time + timedelta(seconds=10), + ), + ) + simulated_time = simulated_time + timedelta(seconds=10.1) + print(f"Created step 1: {step1.id_}") + + step1_1 = client.test_results.create_step( + TestStep( + test_report_id=test_report.id_, + parent_step_id=step1.id_, + name="Step 1.1: Substep 1", + description="Substep 1 of Step 1", + step_type=TestStepType.ACTION, + step_path="1.1", + status=TestStatus.PASSED, + start_time=simulated_time, + end_time=simulated_time + timedelta(seconds=10), + ), + ) + print(f"Created step 1.1: {step1_1.id_}") + simulated_time = simulated_time + timedelta(seconds=10.1) + + step2 = client.test_results.create_step( + TestStep( + test_report_id=test_report.id_, + name="Step 2: Data Collection", + description="Collect sensor data", + step_type=TestStepType.ACTION, + step_path="2", + status=TestStatus.PASSED, + start_time=simulated_time, + end_time=simulated_time + timedelta(seconds=10), + ) + ) + print(f"Created step 2: {step2.id_}") + simulated_time = simulated_time + timedelta(seconds=10.1) + step3 = client.test_results.create_step( + TestStep( + test_report_id=test_report.id_, + name="Step 3: Validation", + description="Validate collected data", + step_type=TestStepType.ACTION, + step_path="3", + status=TestStatus.PASSED, + start_time=simulated_time, + end_time=simulated_time + timedelta(seconds=10), + ), + ) + print(f"Created step 3: {step3.id_}") + simulated_time = simulated_time + timedelta(seconds=10.1) + # Create measurements for each step + measurement1 = client.test_results.create_measurement( + TestMeasurement( + test_report_id=test_report.id_, + test_step_id=step1.id_, + name="Temperature Reading", + measurement_type=TestMeasurementType.DOUBLE, + numeric_value=25.5, + numeric_bounds=NumericBounds( + min=24, + max=26, + ), + unit="Celsius", + passed=True, + timestamp=simulated_time, + ), + update_step=True, + ) + print(f"Created measurement 1: {measurement1.id_}") + + measurement2 = client.test_results.create_measurement( + TestMeasurement( + test_report_id=test_report.id_, + test_step_id=step2.id_, + name="FW Version", + measurement_type=TestMeasurementType.STRING, + string_value="1.10.3", + passed=True, + timestamp=step2.start_time, + ), + update_step=True, + ) + print(f"Created measurement 2: {measurement2.id_}") + + measurement3 = client.test_results.create_measurement( + TestMeasurement( + test_report_id=test_report.id_, + test_step_id=step3.id_, + name="Status Check", + measurement_type=TestMeasurementType.BOOLEAN, + boolean_value=True, + passed=True, + timestamp=step3.start_time, + ), + update_step=True, + ) + print(f"Created measurement 3: {measurement3.id_}") + + measurement4 = client.test_results.create_measurement( + TestMeasurement( + test_report_id=test_report.id_, + test_step_id=step1_1.id_, + name="Substep 1.1: Substep 1.1.1", + measurement_type=TestMeasurementType.BOOLEAN, + boolean_value=True, + passed=True, + timestamp=step1_1.start_time, + ) + ) + print(f"Created measurement 4: {measurement4}") + + measurement2 = client.test_results.update_measurement( + measurement2, + update={ + "passed": False, + "string_expected_value": "1.10.4", + }, + update_step=True, + ) + print(f"Updated measurement 2: {measurement2}") + assert measurement2.passed == False + assert measurement2.string_expected_value == "1.10.4" + + measurement4 = client.test_results.update_measurement( + measurement4, + update={ + "passed": False, + "numeric_bounds": NumericBounds( + min=10, + max=20, + ), + }, + update_step=True, + ) + print(f"Updated measurement 4: {measurement4}") + assert measurement4.passed == False + assert measurement4.numeric_bounds == NumericBounds( + min=10, + max=20, + ) + + # Verify update_step propogated the status. + updated_step = client.test_results.get_step(test_step_id=measurement4.test_step_id) + assert updated_step.status == TestStatus.FAILED + + # Update the report with metadata + new_end_time = measurement4.timestamp + timedelta(seconds=10) + updated_report = client.test_results.update_report( + test_report=test_report, + update=TestReportUpdate( + metadata={ + "test_environment": "production", + "temperature": 22.5, + "humidity": 45.0, + "automated": True, + }, + status=TestStatus.FAILED, + end_time=new_end_time, + ), + ) + print(f"Updated report with metadata: {updated_report.metadata}") + assert updated_report.metadata == { + "test_environment": "production", + "temperature": 22.5, + "humidity": 45.0, + "automated": True, + } + assert updated_report.status == TestStatus.FAILED + assert updated_report.end_time == new_end_time + + # Archive the report + archived_report = client.test_results.archive_report(test_report=test_report) + assert archived_report.is_archived + + client.test_results.delete_report(test_report=test_report) + try: + deleted_report = client.test_results.get_report(test_report_id=test_report.id_) + assert deleted_report is None # Shouldn't reach here so error if we get something. + except aiogrpc.AioRpcError as e: + print(f"Report deleted: {e}") + assert e.code() == grpc.StatusCode.NOT_FOUND + + # Import a test report from a file + create_time = datetime.now(timezone.utc) + current_dir = Path(__file__).parent + test_file = Path(current_dir, "test_files", "demo_test_report.xml") + test_report = client.test_results.import_test_report(test_file=test_file) + print(f"Imported test report: {test_report.id_}") + + # Excercise find_report, custom_filter, and filtering by commonon-proto fields such as created_date + found_report = client.test_results.find_report(custom_filter=f"test_report_id == '{test_report.id_}' && created_date >= timestamp('{create_time}')") + assert found_report is not None + assert found_report.id_ == test_report.id_ + + client.test_results.delete_report(test_report=found_report) + print(f"Found and deleted imported report: {found_report.id_}") + + print("Test completed successfully") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/python/lib/sift_client/client.py b/python/lib/sift_client/client.py index 427e4def5..106325077 100644 --- a/python/lib/sift_client/client.py +++ b/python/lib/sift_client/client.py @@ -15,6 +15,8 @@ RulesAPIAsync, RunsAPI, RunsAPIAsync, + TestResultsAPI, + TestResultsAPIAsync, ) from sift_client.transport import ( GrpcClient, @@ -86,6 +88,9 @@ class SiftClient( runs: RunsAPI """Instance of the Runs API for making synchronous requests.""" + test_results: TestResultsAPI + """Instance of the Test Results API for making synchronous requests.""" + async_: AsyncAPIs """Accessor for the asynchronous APIs. All asynchronous APIs are available as attributes on this accessor.""" @@ -130,7 +135,7 @@ def __init__( self.ingestion = IngestionAPIAsync(self) self.rules = RulesAPI(self) self.runs = RunsAPI(self) - + self.test_results = TestResultsAPI(self) # Accessor for the asynchronous APIs self.async_ = AsyncAPIs( ping=PingAPIAsync(self), @@ -140,6 +145,7 @@ def __init__( ingestion=IngestionAPIAsync(self), rules=RulesAPIAsync(self), runs=RunsAPIAsync(self), + test_results=TestResultsAPIAsync(self), ) @property diff --git a/python/lib/sift_client/resources/__init__.py b/python/lib/sift_client/resources/__init__.py index 5997acb02..a9498a330 100644 --- a/python/lib/sift_client/resources/__init__.py +++ b/python/lib/sift_client/resources/__init__.py @@ -5,6 +5,9 @@ from sift_client.resources.ping import PingAPIAsync from sift_client.resources.rules import RulesAPIAsync from sift_client.resources.runs import RunsAPIAsync +from sift_client.resources.test_results import TestResultsAPIAsync + +# ruff: noqa All imports needs to be imported before sync_stubs to avoid circular import from sift_client.resources.sync_stubs import ( AssetsAPI, CalculatedChannelsAPI, @@ -12,6 +15,7 @@ PingAPI, RulesAPI, RunsAPI, + TestResultsAPI, ) __all__ = [ @@ -28,4 +32,6 @@ "RulesAPIAsync", "RunsAPI", "RunsAPIAsync", + "TestResultsAPI", + "TestResultsAPIAsync", ] diff --git a/python/lib/sift_client/resources/sync_stubs/__init__.py b/python/lib/sift_client/resources/sync_stubs/__init__.py index 7246389a4..440a9de21 100644 --- a/python/lib/sift_client/resources/sync_stubs/__init__.py +++ b/python/lib/sift_client/resources/sync_stubs/__init__.py @@ -10,6 +10,7 @@ PingAPIAsync, RulesAPIAsync, RunsAPIAsync, + TestResultsAPIAsync, ) PingAPI = generate_sync_api(PingAPIAsync, "PingAPI") @@ -18,5 +19,6 @@ ChannelsAPI = generate_sync_api(ChannelsAPIAsync, "ChannelsAPI") RulesAPI = generate_sync_api(RulesAPIAsync, "RulesAPI") RunsAPI = generate_sync_api(RunsAPIAsync, "RunsAPI") +TestResultsAPI = generate_sync_api(TestResultsAPIAsync, "TestResultsAPI") -__all__ = ["AssetsAPI", "CalculatedChannelsAPI", "PingAPI", "RunsAPI"] +__all__ = ["AssetsAPI", "CalculatedChannelsAPI", "PingAPI", "RunsAPI", "TestResultsAPI"] diff --git a/python/lib/sift_client/resources/sync_stubs/__init__.pyi b/python/lib/sift_client/resources/sync_stubs/__init__.pyi index 0c52d3b15..46b0fc8d1 100644 --- a/python/lib/sift_client/resources/sync_stubs/__init__.pyi +++ b/python/lib/sift_client/resources/sync_stubs/__init__.pyi @@ -4,6 +4,7 @@ from __future__ import annotations import re from datetime import datetime +from pathlib import Path from typing import Any import pandas as pd @@ -15,18 +16,28 @@ from sift_client.sift_types.calculated_channel import CalculatedChannel, Calcula from sift_client.sift_types.channel import Channel, ChannelReference from sift_client.sift_types.rule import Rule, RuleAction, RuleUpdate from sift_client.sift_types.run import Run, RunUpdate +from sift_client.sift_types.test_report import ( + TestMeasurement, + TestMeasurementType, + TestMeasurementUpdate, + TestReport, + TestReportUpdate, + TestStatus, + TestStep, + TestStepType, + TestStepUpdate, +) class AssetsAPI: """Sync counterpart to `AssetsAPIAsync`. High-level API for interacting with assets. - This class provides a Pythonic, notebook-friendly interface for interacting with the AssetsAPI. - It handles automatic handling of gRPC services, seamless type conversion, and clear error handling. - - All methods in this class use the Asset class from the low-level wrapper, which is a user-friendly - representation of an asset using standard Python data structures and types. + This class provides a Pythonic, notebook-friendly interface for interacting with the AssetsAPI. + It handles automatic handling of gRPC services, seamless type conversion, and clear error handling. + All methods in this class use the Asset class from the low-level wrapper, which is a user-friendly + representation of an asset using standard Python data structures and types. """ def __init__(self, sift_client: SiftClient): @@ -139,12 +150,11 @@ class CalculatedChannelsAPI: High-level API for interacting with calculated channels. - This class provides a Pythonic, notebook-friendly interface for interacting with the CalculatedChannelsAPI. - It handles automatic handling of gRPC services, seamless type conversion, and clear error handling. - - All methods in this class use the CalculatedChannel class from the low-level wrapper, which is a user-friendly - representation of a calculated channel using standard Python data structures and types. + This class provides a Pythonic, notebook-friendly interface for interacting with the CalculatedChannelsAPI. + It handles automatic handling of gRPC services, seamless type conversion, and clear error handling. + All methods in this class use the CalculatedChannel class from the low-level wrapper, which is a user-friendly + representation of a calculated channel using standard Python data structures and types. """ def __init__(self, sift_client: SiftClient): @@ -351,12 +361,11 @@ class ChannelsAPI: High-level API for interacting with channels. - This class provides a Pythonic, notebook-friendly interface for interacting with the ChannelsAPI. - It handles automatic handling of gRPC services, seamless type conversion, and clear error handling. - - All methods in this class use the Channel class from the low-level wrapper, which is a user-friendly - representation of a channel using standard Python data structures and types. + This class provides a Pythonic, notebook-friendly interface for interacting with the ChannelsAPI. + It handles automatic handling of gRPC services, seamless type conversion, and clear error handling. + All methods in this class use the Channel class from the low-level wrapper, which is a user-friendly + representation of a channel using standard Python data structures and types. """ def __init__(self, sift_client: SiftClient): @@ -496,12 +505,11 @@ class RulesAPI: High-level API for interacting with rules. - This class provides a Pythonic, notebook-friendly interface for interacting with the RulesAPI. - It handles automatic handling of gRPC services, seamless type conversion, and clear error handling. - - All methods in this class use the Rule class from the low-level wrapper, which is a user-friendly - representation of a rule using standard Python data structures and types. + This class provides a Pythonic, notebook-friendly interface for interacting with the RulesAPI. + It handles automatic handling of gRPC services, seamless type conversion, and clear error handling. + All methods in this class use the Rule class from the low-level wrapper, which is a user-friendly + representation of a rule using standard Python data structures and types. """ def __init__(self, sift_client: SiftClient): @@ -656,12 +664,11 @@ class RunsAPI: High-level API for interacting with runs. - This class provides a Pythonic, notebook-friendly interface for interacting with the RunsAPI. - It handles automatic handling of gRPC services, seamless type conversion, and clear error handling. - - All methods in this class use the Run class from the low-level wrapper, which is a user-friendly - representation of a run using standard Python data structures and types. + This class provides a Pythonic, notebook-friendly interface for interacting with the RunsAPI. + It handles automatic handling of gRPC services, seamless type conversion, and clear error handling. + All methods in this class use the Run class from the low-level wrapper, which is a user-friendly + representation of a run using standard Python data structures and types. """ def __init__(self, sift_client: SiftClient): @@ -811,3 +818,311 @@ class RunsAPI: The updated Run. """ ... + +class TestResultsAPI: + """Sync counterpart to `TestResultsAPIAsync`. + + High-level API for interacting with test reports, steps, and measurements. + """ + + def __init__(self, sift_client: SiftClient): + """Initialize the TestResultsAPI. + + Args: + sift_client: The Sift client to use. + """ + ... + + def _run(self, coro): ... + def archive_report(self, *, test_report: str | TestReport) -> TestReport: + """Archive a test report. + + Args: + test_report: The TestReport or test report ID to archive. + """ + ... + + def create_measurement( + self, test_measurement: TestMeasurement, update_step: bool = False + ) -> TestMeasurement: + """Create a new test measurement. + + Args: + test_measurement: The test measurement to create. + update_step: Whether to update the step to failed if the measurement is being created is failed. + + Returns: + The created TestMeasurement. + """ + ... + + def create_measurements( + self, test_measurements: list[TestMeasurement] + ) -> tuple[int, list[str]]: + """Create multiple test measurements in a single request. + + Args: + test_measurements: The test measurements to create. + + Returns: + A tuple of (measurements_created_count, measurement_ids). + """ + ... + + def create_report( + self, + status: TestStatus, + name: str, + test_system_name: str, + test_case: str, + start_time: datetime, + end_time: datetime, + metadata: dict[str, str | float | bool] | None = None, + serial_number: str | None = None, + part_number: str | None = None, + system_operator: str | None = None, + ) -> TestReport: + """Create a new test report. + + Args: + status: The status of the test run (TestStatus enum). + name: The name of the test run. + test_system_name: The name of the test system. + test_case: The test case that was run. + start_time: The start time of the test run. + end_time: The end time of the test run. + metadata: The metadata values associated with this test run. + serial_number: The serial number for the DUT. + part_number: The part number for the DUT. + system_operator: Unique identifier for user owner. + + Returns: + The created TestReport. + """ + ... + + def create_step(self, test_step: TestStep) -> TestStep: + """Create a new test step. + + Args: + test_step: The test step to create. + + Returns: + The created TestStep. + """ + ... + + def delete_measurement(self, *, test_measurement: str | TestMeasurement) -> None: + """Delete a test measurement. + + Args: + test_measurement: The TestMeasurement or measurement ID to delete. + """ + ... + + def delete_report(self, *, test_report: str | TestReport) -> None: + """Delete a test report. + + Args: + test_report: The TestReport or test report ID to delete. + """ + ... + + def delete_step(self, *, test_step: str | TestStep) -> None: + """Delete a test step. + + Args: + test_step: The TestStep or test step ID to delete. + """ + ... + + def find_report(self, **kwargs) -> TestReport | None: + """Find a single test report matching the given query. Takes the same arguments as `list_`. If more than one test report is found, + raises an error. + + Args: + **kwargs: Keyword arguments to pass to `list_reports`. + + Returns: + The TestReport found or None. + """ + ... + + def get_report(self, *, test_report_id: str) -> TestReport: + """Get a TestReport. + + Args: + test_report_id: The ID of the test report. + + Returns: + The TestReport. + """ + ... + + def get_step(self, test_step_id: str) -> TestStep: + """Get a TestStep. + + Args: + test_step_id: The ID of the test step. + """ + ... + + def import_test_report(self, test_file: str | Path) -> TestReport: + """Import a test report from an already-uploaded file. + + Args: + test_file: The path to the test report file to import. + + Returns: + The imported TestReport. + """ + ... + + def list_measurements( + self, + *, + measurement_id: str | None = None, + test_step_id: str | None = None, + test_report_id: str | None = None, + name: str | None = None, + name_contains: str | None = None, + measurement_type: TestMeasurementType | None = None, + passed: bool | None = None, + order_by: str | None = None, + limit: int | None = None, + ) -> list[TestMeasurement]: + """List test measurements with optional filtering. + + Args: + measurement_id: Measurement ID to filter by. + test_step_id: Test step ID to filter by. + test_report_id: Test report ID to filter by. + name: Exact name of the test measurement. + name_contains: Partial name of the test measurement. + measurement_type: Measurement type to filter by (TestMeasurementType enum). + passed: Whether the measurement passed. + order_by: How to order the retrieved test measurements. + limit: How many test measurements to retrieve. If None, retrieves all matches. + + Returns: + A list of TestMeasurements that matches the filter. + """ + ... + + def list_reports( + self, + *, + name: str | None = None, + name_contains: str | None = None, + name_regex: str | re.Pattern | None = None, + test_report_id: str | None = None, + status: TestStatus | None = None, + test_system_name: str | None = None, + test_case: str | None = None, + serial_number: str | None = None, + part_number: str | None = None, + system_operator: str | None = None, + created_by_user_id: str | None = None, + is_archived: bool | None = None, + custom_filter: str | None = None, + order_by: str | None = None, + limit: int | None = None, + ) -> list[TestReport]: + """List test reports with optional filtering. + + Args: + name: Exact name of the test report. + name_contains: Partial name of the test report. + name_regex: Regular expression string to filter test reports by name. + test_report_id: Test report ID to filter by. + status: Status to filter by (TestStatus enum). + test_system_name: Test system name to filter by. + test_case: Test case to filter by. + serial_number: Serial number to filter by. + part_number: Part number to filter by. + system_operator: System operator to filter by. + created_by_user_id: User ID who created the test report. + is_archived: Whether to include only archived or non-archived reports. + custom_filter: Custom filter to apply to the test reports. + order_by: How to order the retrieved test reports. If used, this will override the other filters. + limit: How many test reports to retrieve. If None, retrieves all matches. + + Returns: + A list of TestReports that matches the filter. + """ + ... + + def list_steps( + self, + *, + test_step_id: str | None = None, + test_report_id: str | None = None, + parent_step_id: str | None = None, + name: str | None = None, + name_contains: str | None = None, + status: TestStatus | None = None, + step_type: TestStepType | None = None, + order_by: str | None = None, + limit: int | None = None, + ) -> list[TestStep]: + """List test steps with optional filtering. + + Args: + test_step_id: Test step ID to filter by. + test_report_id: Test report ID to filter by. + parent_step_id: Parent step ID to filter by. + name: Exact name of the test step. + name_contains: Partial name of the test step. + status: Status to filter by (TestStatus enum). + step_type: Step type to filter by (TestStepType enum). + order_by: How to order the retrieved test steps. + limit: How many test steps to retrieve. If None, retrieves all matches. + + Returns: + A list of TestSteps that matches the filter. + """ + ... + + def update_measurement( + self, + test_measurement: TestMeasurement, + update: TestMeasurementUpdate | dict, + update_step: bool = False, + ) -> TestMeasurement: + """Update a TestMeasurement. + + Args: + test_measurement: The TestMeasurement or measurement ID to update. + update: Updates to apply to the TestMeasurement. + update_step: Whether to update the step to failed if the measurement is being updated to failed. + + Returns: + The updated TestMeasurement. + """ + ... + + def update_report( + self, test_report: str | TestReport, update: TestReportUpdate | dict + ) -> TestReport: + """Update a TestReport. + + Args: + test_report: The TestReport or test report ID to update. + update: Updates to apply to the TestReport. + + Returns: + The updated TestReport. + """ + ... + + def update_step(self, test_step: str | TestStep, update: TestStepUpdate | dict) -> TestStep: + """Update a TestStep. + + Args: + test_step: The TestStep or test step ID to update. + update: Updates to apply to the TestStep. + + Returns: + The updated TestStep. + """ + ... diff --git a/python/lib/sift_client/resources/test_results.py b/python/lib/sift_client/resources/test_results.py new file mode 100644 index 000000000..92b45dd53 --- /dev/null +++ b/python/lib/sift_client/resources/test_results.py @@ -0,0 +1,527 @@ +from __future__ import annotations + +import re +import uuid +from datetime import datetime +from typing import TYPE_CHECKING + +from sift_client._internal.low_level_wrappers.test_results import TestResultsLowLevelClient +from sift_client._internal.low_level_wrappers.upload import UploadLowLevelClient +from sift_client.resources._base import ResourceBase +from sift_client.sift_types.test_report import ( + TestMeasurement, + TestMeasurementType, + TestMeasurementUpdate, + TestReport, + TestReportUpdate, + TestStatus, + TestStep, + TestStepType, + TestStepUpdate, +) +from sift_client.util.cel_utils import contains, equals, match + +if TYPE_CHECKING: + from datetime import datetime + from pathlib import Path + + from sift_client.client import SiftClient + + +class TestResultsAPIAsync(ResourceBase): + """High-level API for interacting with test reports, steps, and measurements.""" + + def __init__(self, sift_client: SiftClient): + """Initialize the TestResultsAPI. + + Args: + sift_client: The Sift client to use. + """ + super().__init__(sift_client) + self._low_level_client = TestResultsLowLevelClient(grpc_client=self.client.grpc_client) + self._upload_client = UploadLowLevelClient(rest_client=self.client.rest_client) + + async def import_test_report(self, test_file: str | Path) -> TestReport: + """Import a test report from an already-uploaded file. + + Args: + test_file: The path to the test report file to import. + + Returns: + The imported TestReport. + """ + # Generate a temporary UUID for the test report. The report service will override this with the created report ID. + temp_uuid = str(uuid.uuid4()) + remote_file_id = await self._upload_client.upload_attachment( + path=test_file, + entity_id=temp_uuid, + entity_type="test_reports", + ) + test_report = await self._low_level_client.import_test_report(remote_file_id=remote_file_id) + return self._apply_client_to_instance(test_report) + + async def create_report( + self, + status: TestStatus, + name: str, + test_system_name: str, + test_case: str, + start_time: datetime, + end_time: datetime, + metadata: dict[str, str | float | bool] | None = None, + serial_number: str | None = None, + part_number: str | None = None, + system_operator: str | None = None, + ) -> TestReport: + """Create a new test report. + + Args: + status: The status of the test run (TestStatus enum). + name: The name of the test run. + test_system_name: The name of the test system. + test_case: The test case that was run. + start_time: The start time of the test run. + end_time: The end time of the test run. + metadata: The metadata values associated with this test run. + serial_number: The serial number for the DUT. + part_number: The part number for the DUT. + system_operator: Unique identifier for user owner. + + Returns: + The created TestReport. + """ + test_report = await self._low_level_client.create_test_report( + status=status, + name=name, + test_system_name=test_system_name, + test_case=test_case, + start_time=start_time, + end_time=end_time, + metadata=metadata, + serial_number=serial_number, + part_number=part_number, + system_operator=system_operator, + ) + return self._apply_client_to_instance(test_report) + + async def get_report(self, *, test_report_id: str) -> TestReport: + """Get a TestReport. + + Args: + test_report_id: The ID of the test report. + + Returns: + The TestReport. + """ + test_report = await self._low_level_client.get_test_report(test_report_id=test_report_id) + return self._apply_client_to_instance(test_report) + + async def list_reports( + self, + *, + name: str | None = None, + name_contains: str | None = None, + name_regex: str | re.Pattern | None = None, + test_report_id: str | None = None, + status: TestStatus | None = None, + test_system_name: str | None = None, + test_case: str | None = None, + serial_number: str | None = None, + part_number: str | None = None, + system_operator: str | None = None, + created_by_user_id: str | None = None, + is_archived: bool | None = None, + custom_filter: str | None = None, + order_by: str | None = None, + limit: int | None = None, + ) -> list[TestReport]: + """List test reports with optional filtering. + + Args: + name: Exact name of the test report. + name_contains: Partial name of the test report. + name_regex: Regular expression string to filter test reports by name. + test_report_id: Test report ID to filter by. + status: Status to filter by (TestStatus enum). + test_system_name: Test system name to filter by. + test_case: Test case to filter by. + serial_number: Serial number to filter by. + part_number: Part number to filter by. + system_operator: System operator to filter by. + created_by_user_id: User ID who created the test report. + is_archived: Whether to include only archived or non-archived reports. + custom_filter: Custom filter to apply to the test reports. + order_by: How to order the retrieved test reports. If used, this will override the other filters. + limit: How many test reports to retrieve. If None, retrieves all matches. + + Returns: + A list of TestReports that matches the filter. + """ + # Build CEL filter + filter_parts = [] + + if name: + filter_parts.append(equals("name", name)) + elif name_contains: + filter_parts.append(contains("name", name_contains)) + elif name_regex: + if isinstance(name_regex, re.Pattern): + name_regex = name_regex.pattern + filter_parts.append(match("name", name_regex)) # type: ignore + + if test_report_id: + filter_parts.append(equals("test_report_id", test_report_id)) + + if status is not None: + filter_parts.append(equals("status", status)) + + if test_system_name: + filter_parts.append(equals("test_system_name", test_system_name)) + + if test_case: + filter_parts.append(equals("test_case", test_case)) + + if serial_number: + filter_parts.append(equals("serial_number", serial_number)) + + if part_number: + filter_parts.append(equals("part_number", part_number)) + + if system_operator: + filter_parts.append(equals("system_operator", system_operator)) + + if created_by_user_id: + filter_parts.append(equals("created_by_user_id", created_by_user_id)) + + if is_archived is not None: + filter_parts.append(equals("is_archived", is_archived)) + + query_filter = " && ".join(filter_parts) if filter_parts else None + if custom_filter: + if filter_parts: + raise ValueError("Custom filter cannot be used with other filters") + query_filter = custom_filter + + test_reports = await self._low_level_client.list_all_test_reports( + query_filter=query_filter, + order_by=order_by, + max_results=limit, + ) + return self._apply_client_to_instances(test_reports) + + async def find_report(self, **kwargs) -> TestReport | None: + """Find a single test report matching the given query. Takes the same arguments as `list_`. If more than one test report is found, + raises an error. + + Args: + **kwargs: Keyword arguments to pass to `list_reports`. + + Returns: + The TestReport found or None. + """ + test_reports = await self.list_reports(**kwargs) + if len(test_reports) > 1: + for report in test_reports: + print(report) + raise ValueError("Multiple test reports found for query") + elif len(test_reports) == 1: + return test_reports[0] + return None + + async def update_report( + self, test_report: str | TestReport, update: TestReportUpdate | dict + ) -> TestReport: + """Update a TestReport. + + Args: + test_report: The TestReport or test report ID to update. + update: Updates to apply to the TestReport. + + Returns: + The updated TestReport. + """ + if isinstance(test_report, str): + test_report = await self.get_report(test_report_id=test_report) + + if isinstance(update, dict): + update = TestReportUpdate.model_validate(update) + + update.resource_id = test_report.id_ + updated_test_report = await self._low_level_client.update_test_report(update) + return self._apply_client_to_instance(updated_test_report) + + async def archive_report(self, *, test_report: str | TestReport) -> TestReport: + """Archive a test report. + + Args: + test_report: The TestReport or test report ID to archive. + """ + return await self.update_report(test_report=test_report, update={"is_archived": True}) + + async def delete_report(self, *, test_report: str | TestReport) -> None: + """Delete a test report. + + Args: + test_report: The TestReport or test report ID to delete. + """ + test_report_id = test_report.id_ if isinstance(test_report, TestReport) else test_report + if not isinstance(test_report_id, str): + raise TypeError(f"test_report_id must be a string not {type(test_report_id)}") + await self._low_level_client.delete_test_report(test_report_id=test_report_id) + + async def create_step(self, test_step: TestStep) -> TestStep: + """Create a new test step. + + Args: + test_step: The test step to create. + + Returns: + The created TestStep. + """ + test_step = await self._low_level_client.create_test_step(test_step) + return self._apply_client_to_instance(test_step) + + async def list_steps( + self, + *, + test_step_id: str | None = None, + test_report_id: str | None = None, + parent_step_id: str | None = None, + name: str | None = None, + name_contains: str | None = None, + status: TestStatus | None = None, + step_type: TestStepType | None = None, + order_by: str | None = None, + limit: int | None = None, + ) -> list[TestStep]: + """List test steps with optional filtering. + + Args: + test_step_id: Test step ID to filter by. + test_report_id: Test report ID to filter by. + parent_step_id: Parent step ID to filter by. + name: Exact name of the test step. + name_contains: Partial name of the test step. + status: Status to filter by (TestStatus enum). + step_type: Step type to filter by (TestStepType enum). + order_by: How to order the retrieved test steps. + limit: How many test steps to retrieve. If None, retrieves all matches. + + Returns: + A list of TestSteps that matches the filter. + """ + # Build CEL filter + filter_parts = [] + + if test_step_id: + filter_parts.append(equals("test_step_id", test_step_id)) + + if test_report_id: + filter_parts.append(equals("test_report_id", test_report_id)) + + if parent_step_id: + filter_parts.append(equals("parent_step_id", parent_step_id)) + + if name: + filter_parts.append(equals("name", name)) + elif name_contains: + filter_parts.append(contains("name", name_contains)) + + if status is not None: + filter_parts.append(equals("status", status)) + + if step_type is not None: + filter_parts.append(equals("step_type", step_type)) + + query_filter = " && ".join(filter_parts) if filter_parts else None + + test_steps = await self._low_level_client.list_all_test_steps( + query_filter=query_filter, + order_by=order_by, + max_results=limit, + ) + return self._apply_client_to_instances(test_steps) + + async def get_step(self, test_step_id: str) -> TestStep: + """Get a TestStep. + + Args: + test_step_id: The ID of the test step. + """ + test_steps = await self._low_level_client.list_all_test_steps( + query_filter=equals("test_step_id", test_step_id), max_results=1 + ) + if not test_steps: + raise ValueError(f"TestStep with ID {test_step_id} not found") + test_step = test_steps[0] + return self._apply_client_to_instance(test_step) + + async def update_step( + self, test_step: str | TestStep, update: TestStepUpdate | dict + ) -> TestStep: + """Update a TestStep. + + Args: + test_step: The TestStep or test step ID to update. + update: Updates to apply to the TestStep. + + Returns: + The updated TestStep. + """ + test_step_id = test_step.id_ if isinstance(test_step, TestStep) else test_step + + if isinstance(update, dict): + update = TestStepUpdate.model_validate(update) + + update.resource_id = test_step_id + updated_test_step = await self._low_level_client.update_test_step(update) + return self._apply_client_to_instance(updated_test_step) + + async def delete_step(self, *, test_step: str | TestStep) -> None: + """Delete a test step. + + Args: + test_step: The TestStep or test step ID to delete. + """ + test_step_id = test_step.id_ if isinstance(test_step, TestStep) else test_step + if not isinstance(test_step_id, str): + raise TypeError(f"test_step_id must be a string not {type(test_step_id)}") + await self._low_level_client.delete_test_step(test_step_id=test_step_id) + + async def create_measurement( + self, test_measurement: TestMeasurement, update_step: bool = False + ) -> TestMeasurement: + """Create a new test measurement. + + Args: + test_measurement: The test measurement to create. + update_step: Whether to update the step to failed if the measurement is being created is failed. + + Returns: + The created TestMeasurement. + """ + test_measurement = await self._low_level_client.create_test_measurement(test_measurement) + measurement = self._apply_client_to_instance(test_measurement) + if update_step: + step = await self.get_step(test_step_id=test_measurement.test_step_id) + if step.status == TestStatus.PASSED and not measurement.passed: + await self.update_step(test_step=step, update={"status": TestStatus.FAILED}) + return measurement + + async def create_measurements( + self, test_measurements: list[TestMeasurement] + ) -> tuple[int, list[str]]: + """Create multiple test measurements in a single request. + + Args: + test_measurements: The test measurements to create. + + Returns: + A tuple of (measurements_created_count, measurement_ids). + """ + return await self._low_level_client.create_test_measurements(test_measurements) + + async def list_measurements( + self, + *, + measurement_id: str | None = None, + test_step_id: str | None = None, + test_report_id: str | None = None, + name: str | None = None, + name_contains: str | None = None, + measurement_type: TestMeasurementType | None = None, + passed: bool | None = None, + order_by: str | None = None, + limit: int | None = None, + ) -> list[TestMeasurement]: + """List test measurements with optional filtering. + + Args: + measurement_id: Measurement ID to filter by. + test_step_id: Test step ID to filter by. + test_report_id: Test report ID to filter by. + name: Exact name of the test measurement. + name_contains: Partial name of the test measurement. + measurement_type: Measurement type to filter by (TestMeasurementType enum). + passed: Whether the measurement passed. + order_by: How to order the retrieved test measurements. + limit: How many test measurements to retrieve. If None, retrieves all matches. + + Returns: + A list of TestMeasurements that matches the filter. + """ + # Build CEL filter + filter_parts = [] + + if measurement_id: + filter_parts.append(equals("measurement_id", measurement_id)) + + if test_step_id: + filter_parts.append(equals("test_step_id", test_step_id)) + + if test_report_id: + filter_parts.append(equals("test_report_id", test_report_id)) + + if name: + filter_parts.append(equals("name", name)) + elif name_contains: + filter_parts.append(contains("name", name_contains)) + + if measurement_type is not None: + filter_parts.append(equals("measurement_type", measurement_type)) + + if passed is not None: + filter_parts.append(equals("passed", passed)) + + query_filter = " && ".join(filter_parts) if filter_parts else None + + test_measurements = await self._low_level_client.list_all_test_measurements( + query_filter=query_filter, + order_by=order_by, + max_results=limit, + ) + return self._apply_client_to_instances(test_measurements) + + async def update_measurement( + self, + test_measurement: TestMeasurement, + update: TestMeasurementUpdate | dict, + update_step: bool = False, + ) -> TestMeasurement: + """Update a TestMeasurement. + + Args: + test_measurement: The TestMeasurement or measurement ID to update. + update: Updates to apply to the TestMeasurement. + update_step: Whether to update the step to failed if the measurement is being updated to failed. + + Returns: + The updated TestMeasurement. + """ + if isinstance(update, dict): + update = TestMeasurementUpdate.model_validate(update) + + update.resource_id = test_measurement.id_ + updated_test_measurement = await self._low_level_client.update_test_measurement(update) + updated_test_measurement = self._apply_client_to_instance(updated_test_measurement) + # If measurement is being updated to failed, see if step is passed and update it to failed if so + if update_step and update.passed is not None and not update.passed: + step = await self.get_step(test_step_id=updated_test_measurement.test_step_id) + if step.status == TestStatus.PASSED: + await self.update_step(test_step=step, update={"status": TestStatus.FAILED}) + return updated_test_measurement + + async def delete_measurement(self, *, test_measurement: str | TestMeasurement) -> None: + """Delete a test measurement. + + Args: + test_measurement: The TestMeasurement or measurement ID to delete. + """ + measurement_id = ( + test_measurement.id_ + if isinstance(test_measurement, TestMeasurement) + else test_measurement + ) + if not isinstance(measurement_id, str): + raise TypeError(f"measurement_id must be a string not {type(measurement_id)}") + await self._low_level_client.delete_test_measurement(measurement_id=measurement_id) diff --git a/python/lib/sift_client/sift_types/__init__.py b/python/lib/sift_client/sift_types/__init__.py index 6a389fa51..428fe5916 100644 --- a/python/lib/sift_client/sift_types/__init__.py +++ b/python/lib/sift_client/sift_types/__init__.py @@ -19,6 +19,13 @@ RuleVersion, ) from sift_client.sift_types.run import Run, RunUpdate +from sift_client.sift_types.test_report import ( + TestMeasurementType, + TestReport, + TestReportUpdate, + TestStatus, + TestStepType, +) __all__ = [ "Asset", @@ -38,4 +45,9 @@ "RuleVersion", "Run", "RunUpdate", + "TestMeasurementType", + "TestReport", + "TestReportUpdate", + "TestStatus", + "TestStepType", ] diff --git a/python/lib/sift_client/sift_types/_base.py b/python/lib/sift_client/sift_types/_base.py index 9254992cf..4c6c79b8e 100644 --- a/python/lib/sift_client/sift_types/_base.py +++ b/python/lib/sift_client/sift_types/_base.py @@ -1,6 +1,7 @@ from __future__ import annotations from abc import ABC, abstractmethod +from enum import Enum from typing import TYPE_CHECKING, Any, Callable, ClassVar, Generic, TypeVar from google.protobuf import field_mask_pb2, message @@ -167,6 +168,8 @@ def _build_proto_and_paths( paths.append(path) else: try: + if isinstance(value, Enum): + value = value.value setattr(proto_msg, field_name, value) paths.append(path) except TypeError as e: diff --git a/python/lib/sift_client/sift_types/test_report.py b/python/lib/sift_client/sift_types/test_report.py new file mode 100644 index 000000000..f8789cc9e --- /dev/null +++ b/python/lib/sift_client/sift_types/test_report.py @@ -0,0 +1,413 @@ +from __future__ import annotations + +from datetime import datetime, timezone +from enum import Enum +from typing import TYPE_CHECKING, ClassVar + +from pydantic import ConfigDict +from sift.test_reports.v1.test_reports_pb2 import ( + ErrorInfo as ErrorInfoProto, +) +from sift.test_reports.v1.test_reports_pb2 import ( + NumericBounds as NumericBoundsProto, +) +from sift.test_reports.v1.test_reports_pb2 import ( + StringBounds as StringBoundsProto, +) +from sift.test_reports.v1.test_reports_pb2 import ( + TestMeasurement as TestMeasurementProto, +) +from sift.test_reports.v1.test_reports_pb2 import ( + TestReport as TestReportProto, +) +from sift.test_reports.v1.test_reports_pb2 import ( + TestStep as TestStepProto, +) + +from sift_client.sift_types._base import BaseType, MappingHelper, ModelUpdate +from sift_client.util.metadata import metadata_dict_to_proto, metadata_proto_to_dict + +if TYPE_CHECKING: + from sift_client.client import SiftClient + + +class TestStatus(Enum): + """TestStatus enum.""" + + UNSPECIFIED = 0 + DRAFT = 1 + PASSED = 2 + FAILED = 3 + ABORTED = 4 + ERROR = 5 + IN_PROGRESS = 6 + SKIPPED = 7 + + +class TestStepType(Enum): + """TestStepType enum.""" + + UNSPECIFIED = 0 + SEQUENCE = 1 + GROUP = 2 + ACTION = 3 + FLOW_CONTROL = 4 + + +class TestMeasurementType(Enum): + """TestMeasurementType enum.""" + + UNSPECIFIED = 0 + DOUBLE = 1 + STRING = 3 + BOOLEAN = 4 + LIMIT = 5 + + +class TestReportUpdate(ModelUpdate[TestReportProto]): + """Update model for TestReport.""" + + model_config = ConfigDict(arbitrary_types_allowed=True) + + status: TestStatus | None = None + name: str | None = None + test_system_name: str | None = None + test_case: str | None = None + start_time: datetime | None = None + end_time: datetime | None = None + metadata: dict[str, str | float | bool] | None = None + serial_number: str | None = None + part_number: str | None = None + system_operator: str | None = None + is_archived: bool | None = None + + _to_proto_helpers: ClassVar = { + "metadata": MappingHelper( + proto_attr_path="metadata", update_field="metadata", converter=metadata_dict_to_proto + ), + } + + def _get_proto_class(self) -> type[TestReportProto]: + return TestReportProto + + def _add_resource_id_to_proto(self, proto_msg: TestReportProto): + if self._resource_id is None: + raise ValueError("Resource ID must be set before adding to proto") + proto_msg.test_report_id = self._resource_id + + +class ErrorInfo(BaseType[ErrorInfoProto, "ErrorInfo"]): + """ErrorInfo model representing error information in a test step.""" + + error_code: int + error_message: str + + @classmethod + def _from_proto(cls, proto: ErrorInfoProto, sift_client: SiftClient | None = None) -> ErrorInfo: + return cls( + id_=None, + error_code=proto.error_code, + error_message=proto.error_message, + _client=sift_client, + ) + + def _to_proto(self) -> ErrorInfoProto: + """Convert to protobuf message.""" + return ErrorInfoProto( + error_code=self.error_code, + error_message=self.error_message, + ) + + +class TestStepUpdate(ModelUpdate[TestStepProto]): + """Update model for TestStep.""" + + model_config = ConfigDict(arbitrary_types_allowed=True) + + name: str | None = None + description: str | None = None + step_type: TestStepType | None = None + step_number: int | None = None + step_path: str | None = None + status: TestStatus | None = None + start_time: datetime | None = None + end_time: datetime | None = None + error_info: ErrorInfo | None = None + + def _get_proto_class(self) -> type[TestStepProto]: + return TestStepProto + + def _add_resource_id_to_proto(self, proto_msg: TestStepProto): + if self._resource_id is None: + raise ValueError("Resource ID must be set before adding to proto") + proto_msg.test_step_id = self._resource_id + + +class TestStep(BaseType[TestStepProto, "TestStep"]): + """TestStep model representing a step in a test.""" + + model_config = ConfigDict(arbitrary_types_allowed=True) + + test_report_id: str + parent_step_id: str | None = None + name: str + description: str | None = None + step_type: TestStepType + step_path: str + status: TestStatus + start_time: datetime + end_time: datetime + error_info: ErrorInfo | None = None + + @classmethod + def _from_proto(cls, proto: TestStepProto, sift_client: SiftClient | None = None) -> TestStep: + return cls( + id_=proto.test_step_id, + test_report_id=proto.test_report_id, + parent_step_id=proto.parent_step_id if proto.parent_step_id else None, + name=proto.name, + description=proto.description if proto.description else None, + step_type=TestStepType(proto.step_type), + step_path=proto.step_path, + status=TestStatus(proto.status), + start_time=proto.start_time.ToDatetime(tzinfo=timezone.utc), + end_time=proto.end_time.ToDatetime(tzinfo=timezone.utc), + error_info=ErrorInfo._from_proto(proto.error_info, sift_client) + if proto.HasField("error_info") + else None, + _client=sift_client, + ) + + def _to_proto(self) -> TestStepProto: + """Convert to protobuf message.""" + proto = TestStepProto( + test_step_id=self.id_ or "", + test_report_id=self.test_report_id, + name=self.name, + step_type=self.step_type.value, # type: ignore + step_number=int(self.step_path.split(".")[0]), + step_path=self.step_path, + status=self.status.value, # type: ignore + ) + + proto.start_time.FromDatetime(self.start_time) + proto.end_time.FromDatetime(self.end_time) + + if self.parent_step_id: + proto.parent_step_id = self.parent_step_id + + if self.description: + proto.description = self.description + + if self.error_info: + proto.error_info.CopyFrom(self.error_info._to_proto()) + + return proto + + +class NumericBounds(BaseType[NumericBoundsProto, "NumericBounds"]): + """NumericBounds model representing numeric bounds for test measurements.""" + + min: float | None = None + max: float | None = None + + @classmethod + def _from_proto( + cls, proto: NumericBoundsProto, sift_client: SiftClient | None = None + ) -> NumericBounds: + return cls( + min=proto.min if proto.HasField("min") else None, + max=proto.max if proto.HasField("max") else None, + _client=sift_client, + ) + + def _to_proto(self) -> NumericBoundsProto: + """Convert to protobuf message.""" + return NumericBoundsProto(min=self.min, max=self.max) + + +class TestMeasurementUpdate(ModelUpdate[TestMeasurementProto]): + """Update model for TestMeasurement.""" + + model_config = ConfigDict(arbitrary_types_allowed=True) + + name: str | None = None + measurement_type: int | None = None + numeric_value: float | None = None + string_value: str | None = None + boolean_value: bool | None = None + unit: str | None = None + numeric_bounds: NumericBounds | None = None + string_expected_value: str | None = None + passed: bool | None = None + timestamp: datetime | None = None + + _to_proto_helpers: ClassVar = { + "string_expected_value": MappingHelper( + proto_attr_path="string_bounds.expected_value", update_field="string_bounds" + ), + } + + def _get_proto_class(self) -> type[TestMeasurementProto]: + return TestMeasurementProto + + def _add_resource_id_to_proto(self, proto_msg: TestMeasurementProto): + if self._resource_id is None: + raise ValueError("Resource ID must be set before adding to proto") + proto_msg.measurement_id = self._resource_id + + +class TestMeasurement(BaseType[TestMeasurementProto, "TestMeasurement"]): + """TestMeasurement model representing a measurement in a test.""" + + model_config = ConfigDict(arbitrary_types_allowed=True) + + measurement_type: TestMeasurementType + name: str + test_step_id: str + test_report_id: str + numeric_value: float | None = None + string_value: str | None = None + boolean_value: bool | None = None + unit: str | None = None + numeric_bounds: NumericBounds | None = None + string_expected_value: str | None = None + passed: bool + timestamp: datetime + + @classmethod + def _from_proto( + cls, proto: TestMeasurementProto, sift_client: SiftClient | None = None + ) -> TestMeasurement: + numeric_value = None + string_value = None + boolean_value = None + + if proto.HasField("numeric_value"): + numeric_value = proto.numeric_value + elif proto.HasField("string_value"): + string_value = proto.string_value + elif proto.HasField("boolean_value"): + boolean_value = proto.boolean_value + + return cls( + id_=proto.measurement_id, + measurement_type=TestMeasurementType(proto.measurement_type), + name=proto.name, + test_step_id=proto.test_step_id, + test_report_id=proto.test_report_id, + numeric_value=numeric_value, + string_value=string_value, + boolean_value=boolean_value, + unit=proto.unit.abbreviated_name if proto.HasField("unit") else None, + numeric_bounds=NumericBounds._from_proto(proto.numeric_bounds, sift_client) + if proto.HasField("numeric_bounds") + else None, + string_expected_value=proto.string_bounds.expected_value + if proto.HasField("string_bounds") + else None, + passed=proto.passed, + timestamp=proto.timestamp.ToDatetime(tzinfo=timezone.utc), + _client=sift_client, + ) + + def _to_proto(self) -> TestMeasurementProto: + """Convert to protobuf message.""" + proto = TestMeasurementProto( + measurement_id=self.id_ or "", + measurement_type=self.measurement_type.value, # type: ignore + name=self.name, + test_step_id=self.test_step_id, + test_report_id=self.test_report_id, + passed=self.passed, + ) + + proto.timestamp.FromDatetime(self.timestamp) + + if self.numeric_value is not None: + proto.numeric_value = self.numeric_value + elif self.string_value is not None: + proto.string_value = self.string_value + elif self.boolean_value is not None: + proto.boolean_value = self.boolean_value + + if self.numeric_bounds: + proto.numeric_bounds.CopyFrom(self.numeric_bounds._to_proto()) + + if self.string_expected_value: + proto.string_bounds.CopyFrom( + StringBoundsProto(expected_value=self.string_expected_value) + ) + + return proto + + +class TestReport(BaseType[TestReportProto, "TestReport"]): + """TestReport model representing a test report.""" + + model_config = ConfigDict(arbitrary_types_allowed=True) + + status: TestStatus + name: str + test_system_name: str + test_case: str + start_time: datetime + end_time: datetime + metadata: dict[str, str | float | bool] + serial_number: str | None = None + part_number: str | None = None + system_operator: str | None = None + archived_date: datetime | None = None + is_archived: bool + + @classmethod + def _from_proto( + cls, proto: TestReportProto, sift_client: SiftClient | None = None + ) -> TestReport: + return cls( + id_=proto.test_report_id, + status=TestStatus(proto.status), + name=proto.name, + test_system_name=proto.test_system_name, + test_case=proto.test_case, + start_time=proto.start_time.ToDatetime(tzinfo=timezone.utc), + end_time=proto.end_time.ToDatetime(tzinfo=timezone.utc), + metadata=metadata_proto_to_dict(proto.metadata), # type: ignore + serial_number=proto.serial_number if proto.serial_number else None, + part_number=proto.part_number if proto.part_number else None, + system_operator=proto.system_operator if proto.system_operator else None, + archived_date=proto.archived_date.ToDatetime(tzinfo=timezone.utc) + if proto.HasField("archived_date") + else None, + is_archived=proto.is_archived, + _client=sift_client, + ) + + def _to_proto(self) -> TestReportProto: + """Convert to protobuf message.""" + proto = TestReportProto( + test_report_id=self.id_ or "", + status=self.status.value, # type: ignore + name=self.name, + test_system_name=self.test_system_name, + test_case=self.test_case, + metadata=metadata_dict_to_proto(self.metadata), + is_archived=self.is_archived, + ) + + proto.start_time.FromDatetime(self.start_time) + proto.end_time.FromDatetime(self.end_time) + + if self.serial_number: + proto.serial_number = self.serial_number + + if self.part_number: + proto.part_number = self.part_number + + if self.system_operator: + proto.system_operator = self.system_operator + + if self.archived_date: + proto.archived_date.FromDatetime(self.archived_date) + + return proto diff --git a/python/lib/sift_client/transport/rest_transport.py b/python/lib/sift_client/transport/rest_transport.py index 071515c94..7e30093a6 100644 --- a/python/lib/sift_client/transport/rest_transport.py +++ b/python/lib/sift_client/transport/rest_transport.py @@ -40,6 +40,9 @@ def __init__( cert_via_openssl: Whether to use OpenSSL for SSL/TLS. retry: The retry configuration for requests. """ + if not base_url.startswith("http"): + # urljoin (used when executing requests) requires URL starting with http or https + base_url = f"https://{base_url}" if use_ssl else f"http://{base_url}" self.base_url = base_url self.api_key = api_key self.use_ssl = use_ssl @@ -116,6 +119,8 @@ def _execute( **kwargs, ) -> requests.Response: full_url = urljoin(self.base_url, endpoint) + print(f"base_url: {self.base_url}") + print(f"Executing {method} request to {full_url}") return self._client._session.request(method, full_url, headers=headers, data=data, **kwargs) def get(self, endpoint: str, headers: dict | None = None, **kwargs) -> requests.Response: diff --git a/python/lib/sift_client/util/util.py b/python/lib/sift_client/util/util.py index 4202ee715..58319e33d 100644 --- a/python/lib/sift_client/util/util.py +++ b/python/lib/sift_client/util/util.py @@ -11,6 +11,7 @@ PingAPIAsync, RulesAPIAsync, RunsAPIAsync, + TestResultsAPIAsync, ) @@ -37,3 +38,6 @@ class AsyncAPIs(NamedTuple): rules: RulesAPIAsync """Instance of the Rules API for making asynchronous requests.""" + + test_results: TestResultsAPIAsync + """Instance of the Test Results API for making asynchronous requests."""