# Path: puresnmp/pdu.py
# class PDU(Type):  # type: ignore
# class EndOfMibView(PDU):
# class GetRequest(PDU):
# class GetResponse(PDU):
# class GetNextRequest(GetRequest):
# class SetRequest(PDU):
# class BulkGetRequest(Type):  # type: ignore
# class InformRequest(PDU):
# class Trap(PDU):
#     TYPECLASS = TypeInfo.CONTEXT
#     TAG = 0
# END_OF_MIB_VIEW = EndOfMibView()
#     TAG = 0
#     TAG = 2
#     TAG = 1
#     TAG = 3
#     TYPECLASS = TypeInfo.CONTEXT
#     TAG = 5
#     TAG = 6
#     TAG = 7
#     def decode(cls, data: bytes) -> "PDU":
#     def __init__(
#         self,
#         request_id: int,
#         varbinds: Union[VarBind, List[VarBind]],
#         error_status: int = 0,
#         error_index: int = 0,
#     ) -> None:
#     def __bytes__(self) -> bytes:
#     def __repr__(self) -> str:
#     def __eq__(self, other: Any) -> bool:
#     def pretty(self) -> str:  # pragma: no cover
#     def __init__(self) -> None:
#     def __init__(
#         self, request_id: int, *oids: Union[str, ObjectIdentifier]
#     ) -> None:
#     def decode(cls, data):
#     def __init__(self, request_id, non_repeaters, max_repeaters, *oids):
#     def __bytes__(self) -> bytes:
#     def __repr__(self):
#     def __eq__(self, other):
#     def pretty(self):  # pragma: no cover
#     def __init__(self, *args, **kwargs):
# 
# Path: puresnmp/util.py
# class WalkRow:
#     """
#     A wrapper around an SNMP Walk item.
# 
#     This also keeps track whether this walk result should be considered the
#     last row or not.
#     """
# 
#     value: Any
#     unfinished: bool
# 
# def get_unfinished_walk_oids(grouped_oids):
#     # type: (Dict[ObjectIdentifier, List[VarBind]]) -> List[Tuple[ObjectIdentifier, WalkRow]]
#     """
#     :param grouped_oids: A dictionary containing VarBinds as values. The keys
#         are the base OID of those VarBinds as requested by the user. We need to
#         keep track of the base to be able to tell when a walk over OIDs is
#         finished (that is, when we hit the first OID outside the base).
#     """
# 
#     # grouped_oids contains a list of values for each requested OID. We need to
#     # determine if we need to continue fetching: Inspect the last item of each
#     # list if those OIDs are still children of the requested IDs we need to
#     # continue fetching using *those* IDs (as we're using GetNext behaviour).
#     # If they are *not* children of the requested OIDs, we went too far (in the
#     # case of a bulk operation) and need to remove all outliers.
#     #
#     # The above behaviour is the same for both bulk and simple operations. For
#     # simple operations we simply have a list of 1 element per OID, but the
#     # behaviour is identical
# 
#     # Build a mapping from the originally requested OID to the last fetched OID
#     # from that tree.
#     last_received_oids = {
#         k: WalkRow(v[-1], v[-1].oid in k) for k, v in grouped_oids.items() if v
#     }
# 
#     output = [
#         item
#         for item in sorted(last_received_oids.items())
#         if item[1].unfinished
#     ]
#     return output
# 
# def group_varbinds(varbinds, effective_roots, user_roots=None):
#     # type: (List[VarBind], List[ObjectIdentifier], Optional[List[ObjectIdentifier]]) -> Dict[ObjectIdentifier, List[VarBind]]
#     """
#     Takes a list of varbinds and a list of base OIDs and returns a mapping from
#     those base IDs to lists of varbinds.
# 
#     Varbinds returned from a walk operation which targets multiple OIDs is
#     returned as "interleaved" list. This functions extracts these interleaved
#     items into a more usable dictionary.
# 
#     :param varbinds: A list of VarBind instances.
#     :param effective_roots: The list of OIDs that were requested from the SNMP
#         agent.
#     :param user_roots: The set of VarBind instances that were requested by the
#         user. This is used internally for walk requests. On each request
#         following the first, the requested OIDs will differ from the OIDs
#         requested by the user. This list will keep track of the original OIDs
#         to determine when the walk needs to terminate.
#     """
#     user_roots = user_roots or []
#     n = len(effective_roots)
# 
#     results = {}
#     for i in range(n):
#         results[effective_roots[i]] = varbinds[i::n]
# 
#     if user_roots:
#         new_results = {}
#         for key, value in results.items():
#             containment = [base for base in user_roots if key in base]
#             if len(containment) > 1:
#                 raise RuntimeError(
#                     "Unexpected OID result. A value was "
#                     "contained in more than one base than "
#                     "should be possible!"
#                 )
#             if not containment:
#                 continue
#             new_results[containment[0]] = value
#             results = new_results
# 
#     return results


from x690.types import Null, ObjectIdentifier
from puresnmp.pdu import VarBind
from puresnmp.util import WalkRow, get_unfinished_walk_oids, group_varbinds
            VarBind(OID("3.3.2"), Null()),
            VarBind(OID("3.3.3"), Null()),
        ],
    }

    assert result == expected


def test_get_unfinished_walk_oids():
    """
    Using get_unfinished_walk_oids should tell us which OIDs are not yet
    completed in the existing OID groups (by looking at the OIDs)
    """
    oid_groups = {
        OID("1.1"): [
            VarBind(OID("1.1.1"), Null()),
            VarBind(OID("1.1.2"), Null()),
        ],
        OID("2.2"): [
            VarBind(OID("2.2.1"), Null()),
            VarBind(OID("2.2.2"), Null()),
        ],
        OID("3.3"): [
            VarBind(OID("3.3.1"), Null()),
            VarBind(OID("3.4.2"), Null()),
        ],
    }
    result = get_unfinished_walk_oids(oid_groups)
    expected = [
        (OID("1.1"), WalkRow(VarBind(OID("1.1.2"), Null()), unfinished=True)),

# Path: puresnmp/pdu.py
# class PDU(Type):  # type: ignore
# class EndOfMibView(PDU):
# class GetRequest(PDU):
# class GetResponse(PDU):
# class GetNextRequest(GetRequest):
# class SetRequest(PDU):
# class BulkGetRequest(Type):  # type: ignore
# class InformRequest(PDU):
# class Trap(PDU):
#     TYPECLASS = TypeInfo.CONTEXT
#     TAG = 0
# END_OF_MIB_VIEW = EndOfMibView()
#     TAG = 0
#     TAG = 2
#     TAG = 1
#     TAG = 3
#     TYPECLASS = TypeInfo.CONTEXT
#     TAG = 5
#     TAG = 6
#     TAG = 7
#     def decode(cls, data: bytes) -> "PDU":
#     def __init__(
#         self,
#         request_id: int,
#         varbinds: Union[VarBind, List[VarBind]],
#         error_status: int = 0,
#         error_index: int = 0,
#     ) -> None:
#     def __bytes__(self) -> bytes:
#     def __repr__(self) -> str:
#     def __eq__(self, other: Any) -> bool:
#     def pretty(self) -> str:  # pragma: no cover
#     def __init__(self) -> None:
#     def __init__(
#         self, request_id: int, *oids: Union[str, ObjectIdentifier]
#     ) -> None:
#     def decode(cls, data):
#     def __init__(self, request_id, non_repeaters, max_repeaters, *oids):
#     def __bytes__(self) -> bytes:
#     def __repr__(self):
#     def __eq__(self, other):
#     def pretty(self):  # pragma: no cover
#     def __init__(self, *args, **kwargs):
# 
# Path: puresnmp/util.py
# class WalkRow:
#     """
#     A wrapper around an SNMP Walk item.
# 
#     This also keeps track whether this walk result should be considered the
#     last row or not.
#     """
# 
#     value: Any
#     unfinished: bool
# 
# def get_unfinished_walk_oids(grouped_oids):
#     # type: (Dict[ObjectIdentifier, List[VarBind]]) -> List[Tuple[ObjectIdentifier, WalkRow]]
#     """
#     :param grouped_oids: A dictionary containing VarBinds as values. The keys
#         are the base OID of those VarBinds as requested by the user. We need to
#         keep track of the base to be able to tell when a walk over OIDs is
#         finished (that is, when we hit the first OID outside the base).
#     """
# 
#     # grouped_oids contains a list of values for each requested OID. We need to
#     # determine if we need to continue fetching: Inspect the last item of each
#     # list if those OIDs are still children of the requested IDs we need to
#     # continue fetching using *those* IDs (as we're using GetNext behaviour).
#     # If they are *not* children of the requested OIDs, we went too far (in the
#     # case of a bulk operation) and need to remove all outliers.
#     #
#     # The above behaviour is the same for both bulk and simple operations. For
#     # simple operations we simply have a list of 1 element per OID, but the
#     # behaviour is identical
# 
#     # Build a mapping from the originally requested OID to the last fetched OID
#     # from that tree.
#     last_received_oids = {
#         k: WalkRow(v[-1], v[-1].oid in k) for k, v in grouped_oids.items() if v
#     }
# 
#     output = [
#         item
#         for item in sorted(last_received_oids.items())
#         if item[1].unfinished
#     ]
#     return output
# 
# def group_varbinds(varbinds, effective_roots, user_roots=None):
#     # type: (List[VarBind], List[ObjectIdentifier], Optional[List[ObjectIdentifier]]) -> Dict[ObjectIdentifier, List[VarBind]]
#     """
#     Takes a list of varbinds and a list of base OIDs and returns a mapping from
#     those base IDs to lists of varbinds.
# 
#     Varbinds returned from a walk operation which targets multiple OIDs is
#     returned as "interleaved" list. This functions extracts these interleaved
#     items into a more usable dictionary.
# 
#     :param varbinds: A list of VarBind instances.
#     :param effective_roots: The list of OIDs that were requested from the SNMP
#         agent.
#     :param user_roots: The set of VarBind instances that were requested by the
#         user. This is used internally for walk requests. On each request
#         following the first, the requested OIDs will differ from the OIDs
#         requested by the user. This list will keep track of the original OIDs
#         to determine when the walk needs to terminate.
#     """
#     user_roots = user_roots or []
#     n = len(effective_roots)
# 
#     results = {}
#     for i in range(n):
#         results[effective_roots[i]] = varbinds[i::n]
# 
#     if user_roots:
#         new_results = {}
#         for key, value in results.items():
#             containment = [base for base in user_roots if key in base]
#             if len(containment) > 1:
#                 raise RuntimeError(
#                     "Unexpected OID result. A value was "
#                     "contained in more than one base than "
#                     "should be possible!"
#                 )
#             if not containment:
#                 continue
#             new_results[containment[0]] = value
#             results = new_results
# 
#     return results


# Path: tests/test_util.py
from x690.types import Null, ObjectIdentifier
from puresnmp.pdu import VarBind
from puresnmp.util import WalkRow, get_unfinished_walk_oids, group_varbinds

            VarBind(OID("3.3.2"), Null()),
            VarBind(OID("3.3.3"), Null()),
        ],
    }

    assert result == expected


def test_get_unfinished_walk_oids():
    """
    Using get_unfinished_walk_oids should tell us which OIDs are not yet
    completed in the existing OID groups (by looking at the OIDs)
    """
    oid_groups = {
        OID("1.1"): [
            VarBind(OID("1.1.1"), Null()),
            VarBind(OID("1.1.2"), Null()),
        ],
        OID("2.2"): [
            VarBind(OID("2.2.1"), Null()),
            VarBind(OID("2.2.2"), Null()),
        ],
        OID("3.3"): [
            VarBind(OID("3.3.1"), Null()),
            VarBind(OID("3.4.2"), Null()),
        ],
    }
    result = get_unfinished_walk_oids(oid_groups)
    expected = [
        (OID("1.1"), WalkRow(VarBind(OID("1.1.2"), Null()), unfinished=True)),

        (OID("2.2"), WalkRow(VarBind(OID("2.2.2"), Null()), unfinished=True)),