From 36cb9f4eac6072919c5c36453329b6ac8e0acbff Mon Sep 17 00:00:00 2001
From: Madhan Mohan Reddy Peram <madhan.mohan.reddy.peram@ni.com>
Date: Thu, 7 Nov 2024 08:38:56 +0530
Subject: [PATCH 1/5] First Pass

---
 nisystemlink/clients/result/__init__.py       |   3 +
 nisystemlink/clients/result/_result_client.py | 180 +++++++++
 .../clients/result/models/__init__.py         |  11 +
 .../models/_create_results_partial_success.py |  20 +
 .../models/_delete_results_partial_success.py |  17 +
 .../clients/result/models/_paged_results.py   |  14 +
 .../result/models/_query_results_request.py   | 141 +++++++
 nisystemlink/clients/result/models/_result.py |  89 +++++
 .../integration/result/test_result_client.py  | 368 ++++++++++++++++++
 9 files changed, 843 insertions(+)
 create mode 100644 nisystemlink/clients/result/__init__.py
 create mode 100644 nisystemlink/clients/result/_result_client.py
 create mode 100644 nisystemlink/clients/result/models/__init__.py
 create mode 100644 nisystemlink/clients/result/models/_create_results_partial_success.py
 create mode 100644 nisystemlink/clients/result/models/_delete_results_partial_success.py
 create mode 100644 nisystemlink/clients/result/models/_paged_results.py
 create mode 100644 nisystemlink/clients/result/models/_query_results_request.py
 create mode 100644 nisystemlink/clients/result/models/_result.py
 create mode 100644 tests/integration/result/test_result_client.py

diff --git a/nisystemlink/clients/result/__init__.py b/nisystemlink/clients/result/__init__.py
new file mode 100644
index 00000000..45fc19fb
--- /dev/null
+++ b/nisystemlink/clients/result/__init__.py
@@ -0,0 +1,3 @@
+from ._result_client import ResultClient
+
+# flake8: noqa
diff --git a/nisystemlink/clients/result/_result_client.py b/nisystemlink/clients/result/_result_client.py
new file mode 100644
index 00000000..5b6ee2c8
--- /dev/null
+++ b/nisystemlink/clients/result/_result_client.py
@@ -0,0 +1,180 @@
+"""Implementations of the Result Client."""
+
+from typing import List, Optional
+
+from nisystemlink.clients import core
+from nisystemlink.clients.core._uplink._base_client import BaseClient
+from nisystemlink.clients.core._uplink._methods import delete, get, post
+from nisystemlink.clients.result.models import Result
+from uplink import Field, Query, retry, returns
+
+from . import models
+
+
+@retry(when=retry.when.status(429), stop=retry.stop.after_attempt(5))
+class ResultClient(BaseClient):
+
+    def __init__(self, configuration: Optional[core.HttpConfiguration] = None):
+        """Initialize an instance.
+
+        Args:
+            configuration: Defines the web server to connect to and information about
+                how to connect. If not provided, the
+                :class:`HttpConfigurationManager <nisystemlink.clients.core.HttpConfigurationManager>`
+                is used to obtain the configuration.
+
+        Raises:
+            ApiException: if unable to communicate with the Result Service.
+        """
+        if configuration is None:
+            configuration = core.HttpConfigurationManager.get_configuration()
+        super().__init__(configuration, base_path="/nitestmonitor/v2/")
+
+    @post("results", args=[Field("results")])
+    def create_results(
+        self, results: List[Result]
+    ) -> models.CreateResultsPartialSuccess:
+        """Creates one or more results and returns errors for failed creations.
+
+        Args:
+            results: A list of results to attempt to create.
+
+        Returns: A list of created results, results that failed to create, and errors for
+            failures.
+
+        Raises:
+            ApiException: if unable to communicate with the ``/nitestmonitor`` service of provided invalid
+                arguments.
+        """
+        ...
+
+    @get(
+        "results",
+        args=[Query("continuationToken"), Query("take"), Query("returnCount")],
+    )
+    def get_results(
+        self,
+        continuation_token: Optional[str] = None,
+        take: Optional[int] = None,
+        return_count: Optional[bool] = None,
+    ) -> models.PagedResults:
+        """Reads a list of results.
+
+        Args:
+            continuation_token: The token used to paginate results.
+            take: The number of results to get in this request.
+            return_count: Whether or not to return the total number of results available.
+
+        Returns:
+            A list of results.
+
+        Raises:
+            ApiException: if unable to communicate with the ``/nitestmonitor`` Service
+                or provided an invalid argument.
+        """
+        ...
+
+    @get("results/{id}")
+    def get_result(self, id: str) -> models.Result:
+        """Retrieves a single result by id.
+
+        Args:
+            id (str): Unique ID of a result.
+
+        Returns:
+            The single result matching `id`
+
+        Raises:
+            ApiException: if unable to communicate with the ``/nitestmonitor`` Service
+                or provided an invalid argument.
+        """
+        ...
+
+    @post("query-results")
+    def query_results(self, query: models.QueryResultsRequest) -> models.PagedResults:
+        """Queries for results that match the filter.
+
+        Args:
+            query : The query contains a DynamicLINQ query string in addition to other details
+                about how to filter and return the list of results.
+
+        Returns:
+            A paged list of results with a continuation token to get the next page.
+
+        Raises:
+            ApiException: if unable to communicate with the ``/nitestmonitor`` Service or provided invalid
+                arguments.
+        """
+        ...
+
+    @returns.json  # type: ignore
+    @post("query-result-values")
+    def query_result_values(self, query: models.QueryResultValuesRequest) -> List[str]:
+        """Queries for results that match the query and returns a list of the requested field.
+
+        Args:
+            query : The query for the fields you want.
+
+        Returns:
+            A list of the values of the field you requested.
+
+        Raises:
+            ApiException: if unable to communicate with the ``/nitestmonitor`` Service or provided
+            invalid arguments.
+        """
+        ...
+
+    @post("update-results", args=[Field("results"), Field("replace")])
+    def update_results(
+        self, results: List[Result], replace: bool = False
+    ) -> models.CreateResultsPartialSuccess:
+        """Updates a list of results with optional field replacement.
+
+        Args:
+            `results`: A list of results to update. Results are matched for update by id.
+            `replace`: Replace the existing fields instead of merging them. Defaults to `False`.
+                If this is `True`, then `keywords` and `properties` for the result will be
+                    replaced by what is in the `results` provided in this request.
+                If this is `False`, then the `keywords` and `properties` in this request will
+                    merge with what is already present in the server resource.
+
+        Returns: A list of updates results, results that failed to update, and errors for
+            failures.
+
+        Raises:
+            ApiException: if unable to communicate with the ``/nitestmonitor`` Service
+                or provided an invalid argument.
+        """
+        ...
+
+    @delete("results/{id}")
+    def delete_result(self, id: str) -> None:
+        """Deletes a single result by id.
+
+        Args:
+            id (str): Unique ID of a result.
+
+        Raises:
+            ApiException: if unable to communicate with the ``/nitestmonitor`` Service
+                or provided an invalid argument.
+        """
+        ...
+
+    @post("delete-results", args=[Field("ids")])
+    def delete_results(
+        self, ids: List[str]
+    ) -> Optional[models.DeleteResultsPartialSuccess]:
+        """Deletes multiple results.
+
+        Args:
+            ids (List[str]): List of unique IDs of results.
+
+        Returns:
+            A partial success if any results failed to delete, or None if all
+            results were deleted successfully.
+
+        Raises:
+            ApiException: if unable to communicate with the ``/nitestmonitor`` Service
+                or provided an invalid argument.
+        """
+        ...
diff --git a/nisystemlink/clients/result/models/__init__.py b/nisystemlink/clients/result/models/__init__.py
new file mode 100644
index 00000000..14328bbf
--- /dev/null
+++ b/nisystemlink/clients/result/models/__init__.py
@@ -0,0 +1,11 @@
+from ._result import Result, StatusObject, StatusType
+from ._create_results_partial_success import CreateResultsPartialSuccess
+from ._delete_results_partial_success import DeleteResultsPartialSuccess
+from ._paged_results import PagedResults
+from ._query_results_request import (
+    QueryResultsRequest,
+    ResultField,
+    QueryResultValuesRequest,
+)
+
+# flake8: noqa
diff --git a/nisystemlink/clients/result/models/_create_results_partial_success.py b/nisystemlink/clients/result/models/_create_results_partial_success.py
new file mode 100644
index 00000000..b9511dc6
--- /dev/null
+++ b/nisystemlink/clients/result/models/_create_results_partial_success.py
@@ -0,0 +1,20 @@
+from typing import List, Optional
+
+from nisystemlink.clients.core import ApiError
+from nisystemlink.clients.core._uplink._json_model import JsonModel
+from nisystemlink.clients.result.models import Result
+
+
+class CreateResultsPartialSuccess(JsonModel):
+    results: List[Result]
+    """The list of results that were successfully created."""
+
+    failed: Optional[List[Result]] = None
+    """The list of results that were not created.
+    If this is `None`, then all results were successfully created.
+    """
+
+    error: Optional[ApiError] = None
+    """Error messages for results that were not created.
+    If this is `None`, then all results were successfully created.
+    """
diff --git a/nisystemlink/clients/result/models/_delete_results_partial_success.py b/nisystemlink/clients/result/models/_delete_results_partial_success.py
new file mode 100644
index 00000000..2583c08a
--- /dev/null
+++ b/nisystemlink/clients/result/models/_delete_results_partial_success.py
@@ -0,0 +1,17 @@
+from typing import List, Optional
+
+from nisystemlink.clients.core import ApiError
+from nisystemlink.clients.core._uplink._json_model import JsonModel
+
+
+class DeleteResultsPartialSuccess(JsonModel):
+    """The result of deleting multiple results when one or more results could not be deleted."""
+
+    ids: List[str]
+    """The IDs of the results that were successfully deleted."""
+
+    failed: Optional[List[str]]
+    """The IDs of the results that could not be deleted."""
+
+    error: Optional[ApiError]
+    """The error that occurred when deleting the results."""
diff --git a/nisystemlink/clients/result/models/_paged_results.py b/nisystemlink/clients/result/models/_paged_results.py
new file mode 100644
index 00000000..d5e09a40
--- /dev/null
+++ b/nisystemlink/clients/result/models/_paged_results.py
@@ -0,0 +1,14 @@
+from typing import List, Optional
+
+from nisystemlink.clients.core._uplink._with_paging import WithPaging
+from nisystemlink.clients.result.models import Result
+
+
+class PagedResults(WithPaging):
+    """The response for a Results query containing matched results."""
+
+    results: List[Result]
+    """A list of all the results in this page."""
+
+    total_count: Optional[int]
+    """The total number of results that match the query."""
diff --git a/nisystemlink/clients/result/models/_query_results_request.py b/nisystemlink/clients/result/models/_query_results_request.py
new file mode 100644
index 00000000..0303c575
--- /dev/null
+++ b/nisystemlink/clients/result/models/_query_results_request.py
@@ -0,0 +1,141 @@
+from enum import Enum
+from typing import List, Optional
+
+from nisystemlink.clients.core._uplink._json_model import JsonModel
+from pydantic import Field
+
+
+class ResultField(str, Enum):
+    """The valid ways to order a result query."""
+
+    ID = "ID"
+    STARTED_AT = "STARTED_AT"
+    UPDATED_AT = "UPDATED_AT"
+    PROGRAM_NAME = "PROGRAM_NAME"
+    SYSTEM_ID = "SYSTEM_ID"
+    HOST_NAME = "HOST_NAME"
+    OPERATOR = "OPERATOR"
+    SERIAL_NUMBER = "SERIAL_NUMBER"
+    PART_NUMBER = "PART_NUMBER"
+    PROPERTIES = "PROPERTIES"
+    TOTAL_TIME_IN_SECONDS = "TOTAL_TIME_IN_SECONDS"
+
+
+class ComparisonType(str, Enum):
+    """The valid ways to order a result query."""
+
+    DEFAULT = "DEFAULT"
+    NUMERIC = "NUMERIC"
+    LEXICOGRAPHIC = "LEXICOGRAPHIC"
+
+
+class QueryResultsBase(JsonModel):
+    filter: Optional[str] = None
+    """
+    The result query filter in Dynamic Linq format.
+    Allowed properties in the filter are:
+    - `id`: String for the global identifier of the result
+    - `status`: String for the status of the result
+    - `systemId`: String for the system identifier of the result
+    - `hostName`: String for the host name of the result
+    - `operator`: String for the operator of the result
+    - `serialNumber`: String for the serial number of the result
+    - `totalTimeInSeconds`: Float for the total time in seconds of the result
+    - `partNumber`: String representing the part number of the result
+    - `programName`: String of the program name
+    - `startedAt`: ISO-8601 formatted UTC timestamp indicating when the result was started.
+    - `updatedAt`: ISO-8601 formatted UTC timestamp indicating when the result was last updated.
+    - `keywords`: A list of keyword strings
+    - `properties`: A dictionary of additional string to string properties
+    - `fileIds`: A list of string ids for files stored in the file service (`/nifile`)
+    - `dataTableIds`: A list of string ids for data tables stored in the data frame service (`/nidataframe`)
+    - `workspaceId`: String for the workspace identifier of the result
+    See [Dynamic Linq](https://github.com/ni/systemlink-OpenAPI-documents/wiki/Dynamic-Linq-Query-Language)
+    documentation for more details.
+    `"@0"`, `"@1"` etc. can be used in conjunction with the `substitutions` parameter to keep this
+    query string more simple and reusable.
+    """
+
+    substitutions: Optional[List[str]] = None
+    """String substitutions into the `filter`.
+    Makes substitutions in the query filter expression. Substitutions for the query expression are
+    indicated by non-negative integers that are prefixed with the "at" symbol. Each substitution in
+    the given expression will be replaced by the element at the corresponding index (zero-based) in
+    this list. For example, "@0" in the filter expression will be replaced with the element at the
+    zeroth index of the substitutions list.
+    """
+
+
+class QueryProductsBase(JsonModel):
+    product_filter: Optional[str] = None
+    """
+    The product query filter in Dynamic Linq format.
+    Allowed properties in the filter are:
+    - `id`: String for the global identifier of the product
+    - `partNumber`: String representing the part number of the product
+    - `name`: String of the product name
+    - `family`: String for the product family
+    - `updatedAt`: ISO-8601 formatted UTC timestamp indicating when the product was last updated.
+    - `keywords`: A list of keyword strings
+    - `properties`: A dictionary of additional string to string properties
+    - `fileIds`: A list of string ids for files stored in the file service (`/nifile`)
+    See [Dynamic Linq](https://github.com/ni/systemlink-OpenAPI-documents/wiki/Dynamic-Linq-Query-Language)
+    documentation for more details.
+    `"@0"`, `"@1"` etc. can be used in conjunction with the `substitutions` parameter to keep this
+    query string more simple and reusable.
+    """
+
+    product_substitutions: Optional[List[str]] = None
+    """String substitutions into the `filter`.
+    Makes substitutions in the query filter expression. Substitutions for the query expression are
+    indicated by non-negative integers that are prefixed with the "at" symbol. Each substitution in
+    the given expression will be replaced by the element at the corresponding index (zero-based) in
+    this list. For example, "@0" in the filter expression will be replaced with the element at the
+    zeroth index of the substitutions list.
+    """
+
+
+class QueryResultsRequest(QueryResultsBase, QueryProductsBase):
+
+    order_by: Optional[ResultField] = Field(None, alias="orderBy")
+    """Specifies the fields to use to sort the results.
+    By default, results are sorted by `id`
+    """
+    order_by_key: Optional[str] = Field(None, alias="orderByKey")
+    """Specifies the property to use to sort the results when ordering by PROPERTIES.
+    Results that do not contain the orderByKey will be considered the smallest value.
+    """
+    order_by_comparison_type: Optional[ComparisonType] = Field(
+        None, alias="orderByComparisonType"
+    )
+    """An enumeration of comparison types that can be used for ordered queries.
+    For non-DEFAULT comparisons, values that cannot be converted will be considered the smallest value.
+    """
+    descending: Optional[bool] = None
+    """Specifies whether to return the results in descending order.
+    By default, this value is `false` and results are sorted in ascending order.
+    """
+    take: Optional[int] = None
+    """Maximum number of results to return in the current API response.
+    Uses the default if the specified value is negative. The default value is `1000` results.
+    """
+    continuation_token: Optional[str] = None
+    """Allows users to continue the query at the next result that matches the given criteria.
+    To retrieve the next page of results, pass the continuation token from the previous
+    page in the next request. The service responds with the next page of data and provides a new
+    continuation token. To paginate results, continue sending requests with the newest continuation
+    token provided in each response.
+    """
+    return_count: Optional[bool] = None
+    """If true, the response will include a count of all results matching the filter.
+    By default, this value is `False` and count is not returned. Note that returning the count may
+    incur performance penalties as the service may have to do a complete walk of the database to
+    compute count. """
+
+
+class QueryResultValuesRequest(QueryResultsBase):
+    field: Optional[ResultField] = None
+    """The result field to return for this query."""
+
+    starts_with: Optional[str] = None
+    """Only return string parameters prefixed by this value (case sensitive)."""
diff --git a/nisystemlink/clients/result/models/_result.py b/nisystemlink/clients/result/models/_result.py
new file mode 100644
index 00000000..3835c904
--- /dev/null
+++ b/nisystemlink/clients/result/models/_result.py
@@ -0,0 +1,89 @@
+from datetime import datetime
+from enum import Enum
+from typing import Dict, List, Optional
+
+from nisystemlink.clients.core._uplink._json_model import JsonModel
+
+
+class StatusType(str, Enum):
+    """The types of statuses that a result can have."""
+
+    LOOPING = "LOOPING"
+    SKIPPED = "SKIPPED"
+    CUSTOM = "CUSTOM"
+    DONE = "DONE"
+    PASSED = "PASSED"
+    FAILED = "FAILED"
+    RUNNING = "RUNNING"
+    WAITING = "WAITING"
+    TERMINATED = "TERMINATED"
+    ERRORED = "ERRORED"
+    TIMED_OUT = "TIMED_OUT"
+
+
+class StatusObject(JsonModel):
+    """Contains information about a status object."""
+
+    status_type: StatusType
+    """The type of status."""
+
+    status_name: Optional[str]
+    """The name of the status."""
+
+
+class Result(JsonModel):
+    """Contains information about a result."""
+
+    status: StatusObject
+    """The status of the result."""
+
+    started_at: Optional[datetime]
+    """The time that the result started."""
+
+    updated_at: Optional[datetime]
+    """The last time that this result was updated."""
+
+    program_name: str
+    """The name of the program that generated this result."""
+
+    id: Optional[str]
+    """The globally unique id of the result."""
+
+    system_id: Optional[str]
+    """The id of the system that generated this result."""
+
+    host_name: Optional[str]
+    """The name of the host that generated this result."""
+
+    part_number: Optional[str]
+    """The part number is the unique identifier of a product within a single org."""
+
+    serial_number: Optional[str]
+    """The serial number of the system that generated this result."""
+
+    total_time_in_seconds: Optional[float]
+    """The total time that the result took to run in seconds."""
+
+    keywords: Optional[List[str]]
+    """A list of keywords that categorize this result."""
+
+    properties: Optional[Dict[str, str]]
+    """A list of custom properties for this result."""
+
+    operator: Optional[str]
+    """The operator that ran the result."""
+
+    file_ids: Optional[List[str]]
+    """A list of file ids that are attached to this result."""
+
+    data_table_ids: Optional[List[str]]
+    """A list of data table ids that are attached to this result."""
+
+    status_type_summary: Optional[Dict[str, int]]
+    """A summary of the status types in the result."""
+
+    workspace: Optional[str]
+    """The id of the workspace that this product belongs to."""
+
+    is_finalized: Optional[bool]
+    """Whether the result is finalized."""
diff --git a/tests/integration/result/test_result_client.py b/tests/integration/result/test_result_client.py
new file mode 100644
index 00000000..1db5c667
--- /dev/null
+++ b/tests/integration/result/test_result_client.py
@@ -0,0 +1,368 @@
+import uuid
+from typing import List
+
+import pytest
+from nisystemlink.clients.core._http_configuration import HttpConfiguration
+from nisystemlink.clients.result._result_client import ResultClient
+from nisystemlink.clients.result.models import (
+    CreateResultsPartialSuccess,
+    Result,
+    StatusObject,
+    StatusType,
+)
+from nisystemlink.clients.result.models._paged_results import PagedResults
+from nisystemlink.clients.result.models._query_results_request import (
+    QueryResultsRequest,
+    QueryResultValuesRequest,
+    ResultField,
+)
+
+
+@pytest.fixture(scope="class")
+def client(enterprise_config: HttpConfiguration) -> ResultClient:
+    """Fixture to create a ResultClient instance."""
+    return ResultClient(enterprise_config)
+
+
+@pytest.fixture
+def unique_identifier() -> str:
+    """Unique result id for this test."""
+    result_id = uuid.uuid1().hex
+    return result_id
+
+
+@pytest.fixture
+def create_results(client: ResultClient):
+    """Fixture to return a factory that creates results."""
+    responses: List[CreateResultsPartialSuccess] = []
+
+    def _create_results(results: List[Result]) -> CreateResultsPartialSuccess:
+        response = client.create_results(results)
+        responses.append(response)
+        return response
+
+    yield _create_results
+
+    created_results: List[Result] = []
+    for response in responses:
+        if response.results:
+            created_results = created_results + response.results
+    client.delete_results(ids=[str(result.id) for result in created_results])
+
+
+@pytest.mark.integration
+@pytest.mark.enterprise
+class TestResultClient:
+
+    def test__create_single_result__one_result_created_with_right_field_values(
+        self, client: ResultClient, create_results, unique_identifier
+    ):
+        part_number = unique_identifier
+        keywords = ["testing"]
+        properties = {"test_property": "yes"}
+        program_name = "Test Program"
+        status = StatusObject(status_type=StatusType.PASSED, status_name="Passed")
+        host_name = "Test Host"
+        system_id = "Test System"
+        serial_number = "Test Serial Number"
+        result = Result(
+            part_number=part_number,
+            keywords=keywords,
+            properties=properties,
+            program_name=program_name,
+            status=status,
+            host_name=host_name,
+            system_id=system_id,
+            serial_number=serial_number,
+        )
+
+        response: CreateResultsPartialSuccess = create_results([result])
+
+        assert response is not None
+        assert len(response.results) == 1
+        created_result = response.results[0]
+        assert created_result.part_number == part_number
+        assert created_result.keywords == keywords
+        assert created_result.properties == properties
+        assert created_result.program_name == program_name
+        assert created_result.status == status
+        assert created_result.host_name == host_name
+        assert created_result.system_id == system_id
+        assert created_result.serial_number == serial_number
+
+    def test__create_multiple_results__multiple_creates_succeed(
+        self, client: ResultClient, create_results
+    ):
+        program_name = "Test Program"
+        status = StatusObject(status_type=StatusType.PASSED, status_name="Passed")
+        results = [
+            Result(
+                part_number=uuid.uuid1().hex, program_name=program_name, status=status
+            ),
+            Result(
+                part_number=uuid.uuid1().hex, program_name=program_name, status=status
+            ),
+        ]
+
+        response: CreateResultsPartialSuccess = create_results(results)
+
+        assert response is not None
+        assert len(response.results) == 2
+
+    def test__create_single_result_and_get_results__at_least_one_result_exists(
+        self, client: ResultClient, create_results, unique_identifier
+    ):
+        program_name = "Test Program"
+        status = StatusObject(status_type=StatusType.PASSED, status_name="Passed")
+        results = [
+            Result(
+                part_number=unique_identifier, program_name=program_name, status=status
+            )
+        ]
+        create_results(results)
+
+        get_response = client.get_results()
+
+        assert get_response is not None
+        assert len(get_response.results) >= 1
+
+    def test__create_multiple_results_and_get_results_with_take__only_take_returned(
+        self, client: ResultClient, create_results, unique_identifier
+    ):
+        program_name = "Test Program"
+        status = StatusObject(status_type=StatusType.PASSED, status_name="Passed")
+        results = [
+            Result(
+                part_number=unique_identifier, program_name=program_name, status=status
+            ),
+            Result(
+                part_number=unique_identifier, program_name=program_name, status=status
+            ),
+        ]
+        create_results(results)
+
+        get_response = client.get_results(take=1)
+
+        assert get_response is not None
+        assert len(get_response.results) == 1
+
+    def test__create_multiple_results_and_get_results_with_count_at_least_one_count(
+        self, client: ResultClient, create_results, unique_identifier
+    ):
+        program_name = "Test Program"
+        status = StatusObject(status_type=StatusType.PASSED, status_name="Passed")
+        results = [
+            Result(
+                part_number=unique_identifier, program_name=program_name, status=status
+            ),
+            Result(
+                part_number=unique_identifier, program_name=program_name, status=status
+            ),
+        ]
+        create_results(results)
+
+        get_response: PagedResults = client.get_results(return_count=True)
+
+        assert get_response is not None
+        assert get_response.total_count is not None and get_response.total_count >= 2
+
+    def test__get_result_by_id__result_matches_expected(
+        self, client: ResultClient, create_results, unique_identifier
+    ):
+        part_number = unique_identifier
+        program_name = "Test Program"
+        status = StatusObject(status_type=StatusType.PASSED, status_name="Passed")
+        results = [
+            Result(part_number=part_number, program_name=program_name, status=status)
+        ]
+
+        create_response: CreateResultsPartialSuccess = create_results(results)
+
+        assert create_response is not None
+        id = str(create_response.results[0].id)
+        result = client.get_result(id)
+        assert result is not None
+        assert result.part_number == part_number
+        assert result.program_name == program_name
+        assert result.status == status
+
+    def test__query_result_by_part_number__matches_expected(
+        self, client: ResultClient, create_results, unique_identifier
+    ):
+        part_number = unique_identifier
+        program_name = "Test Program"
+        status = StatusObject(status_type=StatusType.PASSED, status_name="Passed")
+        results = [
+            Result(part_number=part_number, program_name=program_name, status=status)
+        ]
+
+        create_response: CreateResultsPartialSuccess = create_results(results)
+
+        assert create_response is not None
+        query_request = QueryResultsRequest(
+            filter=f'partNumber="{part_number}"', return_count=True
+        )
+        query_response: PagedResults = client.query_results(query_request)
+        assert query_response.total_count == 1
+        assert query_response.results[0].part_number == part_number
+
+    def test__query_result_values_for_name__name_matches(
+        self, client: ResultClient, create_results, unique_identifier
+    ):
+        part_number = unique_identifier
+        program_name = "Test Program"
+        status = StatusObject(status_type=StatusType.PASSED, status_name="Passed")
+
+        create_response: CreateResultsPartialSuccess = create_results(
+            [Result(part_number=part_number, program_name=program_name, status=status)]
+        )
+        assert create_response is not None
+        query_request = QueryResultValuesRequest(
+            filter=f'partNumber="{part_number}"', field=ResultField.PROGRAM_NAME
+        )
+        query_response: List[str] = client.query_result_values(query_request)
+
+        assert query_response is not None
+        assert len(query_response) == 1
+        assert query_response[0] == program_name
+
+    def test__update_keywords_with_replace__keywords_replaced(
+        self, client: ResultClient, create_results, unique_identifier
+    ):
+        original_keyword = "originalKeyword"
+        updated_keyword = "updatedKeyword"
+        program_name = "Test Program"
+        status = StatusObject(status_type=StatusType.PASSED, status_name="Passed")
+        create_response: CreateResultsPartialSuccess = create_results(
+            [
+                Result(
+                    part_number=unique_identifier,
+                    keywords=[original_keyword],
+                    program_name=program_name,
+                    status=status,
+                )
+            ]
+        )
+        assert create_response is not None
+        assert len(create_response.results) == 1
+
+        updated_result = create_response.results[0]
+        updated_result.keywords = [updated_keyword]
+        update_response = client.update_results([updated_result], replace=True)
+
+        assert update_response is not None
+        assert len(update_response.results) == 1
+        assert (
+            update_response.results[0].keywords is not None
+            and updated_keyword in update_response.results[0].keywords
+        )
+        assert original_keyword not in update_response.results[0].keywords
+
+    def test__update_keywords_no_replace__keywords_appended(
+        self, client: ResultClient, create_results, unique_identifier
+    ):
+        original_keyword = "originalKeyword"
+        additional_keyword = "additionalKeyword"
+        program_name = "Test Program"
+        status = StatusObject(status_type=StatusType.PASSED, status_name="Passed")
+        create_response: CreateResultsPartialSuccess = create_results(
+            [
+                Result(
+                    part_number=unique_identifier,
+                    keywords=[original_keyword],
+                    program_name=program_name,
+                    status=status,
+                )
+            ]
+        )
+        assert create_response is not None
+        assert len(create_response.results) == 1
+
+        updated_result = create_response.results[0]
+        updated_result.keywords = [additional_keyword]
+        update_response = client.update_results([updated_result], replace=False)
+
+        assert update_response is not None
+        assert len(update_response.results) == 1
+        assert (
+            update_response.results[0].keywords is not None
+            and original_keyword in update_response.results[0].keywords
+        )
+        assert (
+            update_response.results[0].keywords is not None
+            and additional_keyword in update_response.results[0].keywords
+        )
+
+    def test__update_properties_with_replace__properties_replaced(
+        self, client: ResultClient, create_results, unique_identifier
+    ):
+        new_key = "newKey"
+        original_properties = {"originalKey": "originalValue"}
+        program_name = "Test Program"
+        status = StatusObject(status_type=StatusType.PASSED, status_name="Passed")
+        new_properties = {new_key: "newValue"}
+        create_response: CreateResultsPartialSuccess = create_results(
+            [
+                Result(
+                    part_number=unique_identifier,
+                    properties=original_properties,
+                    program_name=program_name,
+                    status=status,
+                )
+            ]
+        )
+        assert create_response is not None
+        assert len(create_response.results) == 1
+
+        updated_result = create_response.results[0]
+        updated_result.properties = new_properties
+        update_response = client.update_results([updated_result], replace=True)
+
+        assert update_response is not None
+        assert len(update_response.results) == 1
+        assert (
+            update_response.results[0].properties is not None
+            and len(update_response.results[0].properties) == 1
+        )
+        assert new_key in update_response.results[0].properties.keys()
+        assert update_response.results[0].properties[new_key] == new_properties[new_key]
+
+    def test__update_properties_append__properties_appended(
+        self, client: ResultClient, create_results, unique_identifier
+    ):
+        original_key = "originalKey"
+        new_key = "newKey"
+        original_properties = {original_key: "originalValue"}
+        program_name = "Test Program"
+        status = StatusObject(status_type=StatusType.PASSED, status_name="Passed")
+        new_properties = {new_key: "newValue"}
+        create_response: CreateResultsPartialSuccess = create_results(
+            [
+                Result(
+                    part_number=unique_identifier,
+                    properties=original_properties,
+                    program_name=program_name,
+                    status=status,
+                )
+            ]
+        )
+        assert create_response is not None
+        assert len(create_response.results) == 1
+
+        updated_result = create_response.results[0]
+        updated_result.properties = new_properties
+        update_response = client.update_results([updated_result], replace=False)
+
+        assert update_response is not None
+        assert len(update_response.results) == 1
+        updated_result = update_response.results[0]
+        assert (
+            updated_result.properties is not None
+            and len(updated_result.properties) == 2
+        )
+        assert original_key in updated_result.properties.keys()
+        assert new_key in updated_result.properties.keys()
+        assert (
+            updated_result.properties[original_key] == original_properties[original_key]
+        )
+        assert updated_result.properties[new_key] == new_properties[new_key]

From dbb905575a3a91ed61310f97730d03b4003bec5b Mon Sep 17 00:00:00 2001
From: Madhan Mohan Reddy Peram <madhan.mohan.reddy.peram@ni.com>
Date: Thu, 7 Nov 2024 09:32:15 +0530
Subject: [PATCH 2/5] Added the examples

---
 docs/api_reference.rst        |  1 +
 docs/api_reference/result.rst | 23 +++++++++
 docs/getting_started.rst      | 27 +++++++++++
 examples/result/results.py    | 87 +++++++++++++++++++++++++++++++++++
 4 files changed, 138 insertions(+)
 create mode 100644 docs/api_reference/result.rst
 create mode 100644 examples/result/results.py

diff --git a/docs/api_reference.rst b/docs/api_reference.rst
index b9181186..47e11445 100644
--- a/docs/api_reference.rst
+++ b/docs/api_reference.rst
@@ -10,6 +10,7 @@ API Reference
    api_reference/core
    api_reference/tag
    api_reference/testmonitor
+   api_reference/result
    api_reference/dataframe
    api_reference/spec
    api_reference/file
diff --git a/docs/api_reference/result.rst b/docs/api_reference/result.rst
new file mode 100644
index 00000000..5893f7b0
--- /dev/null
+++ b/docs/api_reference/result.rst
@@ -0,0 +1,23 @@
+.. _api_tag_page:
+
+nisystemlink.clients.result
+======================
+
+.. autoclass:: nisystemlink.clients.result.ResultClient
+   :exclude-members: __init__
+
+   .. automethod:: __init__
+   .. automethod:: create_results
+   .. automethod:: get_results
+   .. automethod:: query_results
+   .. automethod:: query_result_values
+   .. automethod:: update_results
+   .. automethod:: delete_result
+   .. automethod:: delete_results
+
+.. automodule:: nisystemlink.clients.result.models
+   :members:
+   :imported-members:
+
+.. automodule:: nisystemlink.clients.result.utilities
+   :members:
\ No newline at end of file
diff --git a/docs/getting_started.rst b/docs/getting_started.rst
index 0efe9358..06a72061 100644
--- a/docs/getting_started.rst
+++ b/docs/getting_started.rst
@@ -182,5 +182,32 @@ Examples
 Get the metadata of a File using its Id and download it.
 
 .. literalinclude:: ../examples/file/download_file.py
+   :language: python
+   :linenos:
+
+
+Result API
+-------
+
+Overview
+~~~~~~~~
+
+The :class:`.ResultClient` class is the primary entry point of the Result API.
+
+When constructing a :class:`.ResultClient`, you can pass an
+:class:`.HttpConfiguration` (like one retrieved from the
+:class:`.HttpConfigurationManager`), or let :class:`.ResultClient` use the
+default connection. The default connection depends on your environment.
+
+With a :class:`.ResultClient` object, you can:
+
+* Create, update, query, and delete results
+
+Examples
+~~~~~~~~
+
+Create, query, update, and delete some results
+
+.. literalinclude:: ../examples/result/results.py
    :language: python
    :linenos:
\ No newline at end of file
diff --git a/examples/result/results.py b/examples/result/results.py
new file mode 100644
index 00000000..944c5369
--- /dev/null
+++ b/examples/result/results.py
@@ -0,0 +1,87 @@
+from nisystemlink.clients.core import HttpConfiguration
+from nisystemlink.clients.result import ResultClient
+from nisystemlink.clients.result.models import (
+    QueryResultsRequest,
+    QueryResultValuesRequest,
+    Result,
+    ResultField,
+    StatusObject,
+    StatusType,
+)
+
+program_name = "Example Name"
+host_name = "Example Host"
+
+
+def create_some_results():
+    """Create two example results on your server."""
+    new_results = [
+        Result(
+            part_number="Example 123 AA",
+            program_name=program_name,
+            host_name=host_name,
+            status=StatusObject(status_type=StatusType.PASSED, status_name="Passed"),
+            keywords=["original keyword"],
+            properties={"original property key": "yes"},
+        ),
+        Result(
+            part_number="Example 123 AA1",
+            program_name=program_name,
+            host_name=host_name,
+            status=StatusObject(status_type=StatusType.FAILED, status_name="Failed"),
+            keywords=["original keyword"],
+            properties={"original property key": "original"},
+        ),
+    ]
+    create_response = client.create_results(new_results)
+    return create_response
+
+
+# Setup the server configuration to point to your instance of SystemLink Enterprise
+server_configuration = HttpConfiguration(
+    server_uri="https://dev-api.lifecyclesolutions.ni.com",
+    api_key="30IUOX8btHdgziA8hgya502zVH8wp2tWGEDH-yMaF6",
+)
+client = ResultClient(configuration=server_configuration)
+
+# Get all the results using the continuation token in batches of 100 at a time.
+response = client.get_results(take=100, return_count=True)
+all_results = response.results
+while response.continuation_token:
+    response = client.get_results(
+        take=100, continuation_token=response.continuation_token, return_count=True
+    )
+    all_results.extend(response.results)
+
+create_response = create_some_results()
+
+# use get for first result created
+created_result = client.get_result(create_response.results[0].id)
+
+# Query results without continuation
+query_request = QueryResultsRequest(
+    filter=f'programName="{program_name}" && hostName="{host_name}"',
+    return_count=True,
+    order_by=ResultField.HOST_NAME,
+)
+response = client.query_results(query_request)
+
+# Update the first result that you just created and replace the keywords
+updated_result = create_response.results[0]
+updated_result.keywords = ["new keyword"]
+updated_result.properties = {"new property key": "new value"}
+update_response = client.update_results([create_response.results[0]], replace=True)
+
+# Query for just the ids of results that match the family
+values_query = QueryResultValuesRequest(
+    filter=f'programName="{program_name}"', field=ResultField.ID
+)
+values_response = client.query_result_values(query=values_query)
+
+# delete each created result individually by id
+for result in create_response.results:
+    client.delete_result(result.id)
+
+# Create some more and delete them with a single call to delete.
+create_response = create_some_results()
+client.delete_results([result.id for result in create_response.results])

From c31dfc5365d70be83ebbec56ddc587aa6aa8c4b6 Mon Sep 17 00:00:00 2001
From: Madhan Mohan Reddy Peram <madhan.mohan.reddy.peram@ni.com>
Date: Thu, 7 Nov 2024 09:33:22 +0530
Subject: [PATCH 3/5] Removed the unnecessary changes

---
 examples/result/results.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/examples/result/results.py b/examples/result/results.py
index 944c5369..400cb4c9 100644
--- a/examples/result/results.py
+++ b/examples/result/results.py
@@ -39,8 +39,8 @@ def create_some_results():
 
 # Setup the server configuration to point to your instance of SystemLink Enterprise
 server_configuration = HttpConfiguration(
-    server_uri="https://dev-api.lifecyclesolutions.ni.com",
-    api_key="30IUOX8btHdgziA8hgya502zVH8wp2tWGEDH-yMaF6",
+    server_uri="https://yourserver.yourcompany.com",
+    api_key="YourAPIKeyGeneratedFromSystemLink",
 )
 client = ResultClient(configuration=server_configuration)
 

From 5c6139f1ac86a0ba1aee3051f920c97e58d1ceac Mon Sep 17 00:00:00 2001
From: Madhan Mohan Reddy Peram <madhan.mohan.reddy.peram@ni.com>
Date: Tue, 12 Nov 2024 12:15:11 +0530
Subject: [PATCH 4/5] Fix: PR Comments

---
 examples/result/results.py                     | 10 +++++-----
 nisystemlink/clients/result/_result_client.py  |  8 ++++----
 tests/integration/result/test_result_client.py |  8 ++++----
 3 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/examples/result/results.py b/examples/result/results.py
index 400cb4c9..60514f14 100644
--- a/examples/result/results.py
+++ b/examples/result/results.py
@@ -44,17 +44,17 @@ def create_some_results():
 )
 client = ResultClient(configuration=server_configuration)
 
+create_response = create_some_results()
+
 # Get all the results using the continuation token in batches of 100 at a time.
-response = client.get_results(take=100, return_count=True)
+response = client.get_results_paged(take=100, return_count=True)
 all_results = response.results
 while response.continuation_token:
-    response = client.get_results(
+    response = client.get_results_paged(
         take=100, continuation_token=response.continuation_token, return_count=True
     )
     all_results.extend(response.results)
 
-create_response = create_some_results()
-
 # use get for first result created
 created_result = client.get_result(create_response.results[0].id)
 
@@ -64,7 +64,7 @@ def create_some_results():
     return_count=True,
     order_by=ResultField.HOST_NAME,
 )
-response = client.query_results(query_request)
+response = client.query_results_paged(query_request)
 
 # Update the first result that you just created and replace the keywords
 updated_result = create_response.results[0]
diff --git a/nisystemlink/clients/result/_result_client.py b/nisystemlink/clients/result/_result_client.py
index 5b6ee2c8..de699407 100644
--- a/nisystemlink/clients/result/_result_client.py
+++ b/nisystemlink/clients/result/_result_client.py
@@ -52,7 +52,7 @@ def create_results(
         "results",
         args=[Query("continuationToken"), Query("take"), Query("returnCount")],
     )
-    def get_results(
+    def get_results_paged(
         self,
         continuation_token: Optional[str] = None,
         take: Optional[int] = None,
@@ -91,7 +91,7 @@ def get_result(self, id: str) -> models.Result:
         ...
 
     @post("query-results")
-    def query_results(self, query: models.QueryResultsRequest) -> models.PagedResults:
+    def query_results_paged(self, query: models.QueryResultsRequest) -> models.PagedResults:
         """Queries for results that match the filter.
 
         Args:
@@ -113,10 +113,10 @@ def query_result_values(self, query: models.QueryResultValuesRequest) -> List[st
         """Queries for results that match the query and returns a list of the requested field.
 
         Args:
-            query : The query for the fields you want.
+            query : The query for the fields.
 
         Returns:
-            A list of the values of the field you requested.
+            A list of the values of the queried field.
 
         Raises:
             ApiException: if unable to communicate with the ``/nitestmonitor`` Service or provided
diff --git a/tests/integration/result/test_result_client.py b/tests/integration/result/test_result_client.py
index 1db5c667..9adc9a59 100644
--- a/tests/integration/result/test_result_client.py
+++ b/tests/integration/result/test_result_client.py
@@ -121,7 +121,7 @@ def test__create_single_result_and_get_results__at_least_one_result_exists(
         ]
         create_results(results)
 
-        get_response = client.get_results()
+        get_response = client.get_results_paged()
 
         assert get_response is not None
         assert len(get_response.results) >= 1
@@ -141,7 +141,7 @@ def test__create_multiple_results_and_get_results_with_take__only_take_returned(
         ]
         create_results(results)
 
-        get_response = client.get_results(take=1)
+        get_response = client.get_results_paged(take=1)
 
         assert get_response is not None
         assert len(get_response.results) == 1
@@ -161,7 +161,7 @@ def test__create_multiple_results_and_get_results_with_count_at_least_one_count(
         ]
         create_results(results)
 
-        get_response: PagedResults = client.get_results(return_count=True)
+        get_response: PagedResults = client.get_results_paged(return_count=True)
 
         assert get_response is not None
         assert get_response.total_count is not None and get_response.total_count >= 2
@@ -202,7 +202,7 @@ def test__query_result_by_part_number__matches_expected(
         query_request = QueryResultsRequest(
             filter=f'partNumber="{part_number}"', return_count=True
         )
-        query_response: PagedResults = client.query_results(query_request)
+        query_response: PagedResults = client.query_results_paged(query_request)
         assert query_response.total_count == 1
         assert query_response.results[0].part_number == part_number
 

From a87f71dfee740353b91822ee95dbef8e5a938c52 Mon Sep 17 00:00:00 2001
From: Madhan Mohan Reddy Peram <madhan.mohan.reddy.peram@ni.com>
Date: Tue, 12 Nov 2024 12:30:13 +0530
Subject: [PATCH 5/5] Fix: Linting

---
 nisystemlink/clients/result/_result_client.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/nisystemlink/clients/result/_result_client.py b/nisystemlink/clients/result/_result_client.py
index de699407..8b8bc857 100644
--- a/nisystemlink/clients/result/_result_client.py
+++ b/nisystemlink/clients/result/_result_client.py
@@ -91,7 +91,9 @@ def get_result(self, id: str) -> models.Result:
         ...
 
     @post("query-results")
-    def query_results_paged(self, query: models.QueryResultsRequest) -> models.PagedResults:
+    def query_results_paged(
+        self, query: models.QueryResultsRequest
+    ) -> models.PagedResults:
         """Queries for results that match the filter.
 
         Args: