prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Simple condition (without and/or) does not work in event rule
  ### Deployment Type
  
  Self-hosted
  
  ### NetBox Version
  
  v3.7.0
  
  ### Python Version
  
  3.11
  
  ### Steps to Reproduce
  
  1. Create webhook: Name = Test, URL = http://127.0.0.1:9000 (doesn't matter in this case, it won't be triggered but is required to configure event rule)
  2. Go to **Event rules - Add**:
  - Name = Test
  - Content types = Circuit
  - select Updates
  - set Conditions:
  ```
  {
      "attr": "status.value",
      "value": "active"
  }
  ```
  
  - Action type = Webhook
  - Webhook = Test
  - **Create**
  
  
  ### Expected Behavior
  
  Event rule is created
  
  ### Observed Behavior
  
  Error is shown about the condition:
  
  **Ruleset must have exactly one logical operator (found 2)** 
  
  The examples in https://docs.netbox.dev/en/stable/reference/conditions/ look the same: simple JSON object with attributes `attr` and `value`.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `netbox/extras/conditions.py`
  Content:
  ```
  1 import functools
  2 import re
  3 from django.utils.translation import gettext as _
  4 
  5 __all__ = (
  6     'Condition',
  7     'ConditionSet',
  8 )
  9 
  10 
  11 AND = 'and'
  12 OR = 'or'
  13 
  14 
  15 def is_ruleset(data):
  16     """
  17     Determine whether the given dictionary looks like a rule set.
  18     """
  19     return type(data) is dict and len(data) == 1 and list(data.keys())[0] in (AND, OR)
  20 
  21 
  22 class Condition:
  23     """
  24     An individual conditional rule that evaluates a single attribute and its value.
  25 
  26     :param attr: The name of the attribute being evaluated
  27     :param value: The value being compared
  28     :param op: The logical operation to use when evaluating the value (default: 'eq')
  29     """
  30     EQ = 'eq'
  31     GT = 'gt'
  32     GTE = 'gte'
  33     LT = 'lt'
  34     LTE = 'lte'
  35     IN = 'in'
  36     CONTAINS = 'contains'
  37     REGEX = 'regex'
  38 
  39     OPERATORS = (
  40         EQ, GT, GTE, LT, LTE, IN, CONTAINS, REGEX
  41     )
  42 
  43     TYPES = {
  44         str: (EQ, CONTAINS, REGEX),
  45         bool: (EQ, CONTAINS),
  46         int: (EQ, GT, GTE, LT, LTE, CONTAINS),
  47         float: (EQ, GT, GTE, LT, LTE, CONTAINS),
  48         list: (EQ, IN, CONTAINS),
  49         type(None): (EQ,)
  50     }
  51 
  52     def __init__(self, attr, value, op=EQ, negate=False):
  53         if op not in self.OPERATORS:
  54             raise ValueError(_("Unknown operator: {op}. Must be one of: {operators}").format(
  55                 op=op, operators=', '.join(self.OPERATORS)
  56             ))
  57         if type(value) not in self.TYPES:
  58             raise ValueError(_("Unsupported value type: {value}").format(value=type(value)))
  59         if op not in self.TYPES[type(value)]:
  60             raise ValueError(_("Invalid type for {op} operation: {value}").format(op=op, value=type(value)))
  61 
  62         self.attr = attr
  63         self.value = value
  64         self.eval_func = getattr(self, f'eval_{op}')
  65         self.negate = negate
  66 
  67     def eval(self, data):
  68         """
  69         Evaluate the provided data to determine whether it matches the condition.
  70         """
  71         def _get(obj, key):
  72             if isinstance(obj, list):
  73                 return [dict.get(i, key) for i in obj]
  74 
  75             return dict.get(obj, key)
  76 
  77         try:
  78             value = functools.reduce(_get, self.attr.split('.'), data)
  79         except TypeError:
  80             # Invalid key path
  81             value = None
  82         result = self.eval_func(value)
  83 
  84         if self.negate:
  85             return not result
  86         return result
  87 
  88     # Equivalency
  89 
  90     def eval_eq(self, value):
  91         return value == self.value
  92 
  93     def eval_neq(self, value):
  94         return value != self.value
  95 
  96     # Numeric comparisons
  97 
  98     def eval_gt(self, value):
  99         return value > self.value
  100 
  101     def eval_gte(self, value):
  102         return value >= self.value
  103 
  104     def eval_lt(self, value):
  105         return value < self.value
  106 
  107     def eval_lte(self, value):
  108         return value <= self.value
  109 
  110     # Membership
  111 
  112     def eval_in(self, value):
  113         return value in self.value
  114 
  115     def eval_contains(self, value):
  116         return self.value in value
  117 
  118     # Regular expressions
  119 
  120     def eval_regex(self, value):
  121         return re.match(self.value, value) is not None
  122 
  123 
  124 class ConditionSet:
  125     """
  126     A set of one or more Condition to be evaluated per the prescribed logic (AND or OR). Example:
  127 
  128     {"and": [
  129         {"attr": "foo", "op": "eq", "value": 1},
  130         {"attr": "bar", "op": "eq", "value": 2, "negate": true}
  131     ]}
  132 
  133     :param ruleset: A dictionary mapping a logical operator to a list of conditional rules
  134     """
  135     def __init__(self, ruleset):
  136         if type(ruleset) is not dict:
  137             raise ValueError(_("Ruleset must be a dictionary, not {ruleset}.").format(ruleset=type(ruleset)))
  138         if len(ruleset) != 1:
  139             raise ValueError(_("Ruleset must have exactly one logical operator (found {ruleset})").format(
  140                 ruleset=len(ruleset)))
  141 
  142         # Determine the logic type
  143         logic = list(ruleset.keys())[0]
  144         if type(logic) is not str or logic.lower() not in (AND, OR):
  145             raise ValueError(_("Invalid logic type: {logic} (must be '{op_and}' or '{op_or}')").format(
  146                 logic=logic, op_and=AND, op_or=OR
  147             ))
  148         self.logic = logic.lower()
  149 
  150         # Compile the set of Conditions
  151         self.conditions = [
  152             ConditionSet(rule) if is_ruleset(rule) else Condition(**rule)
  153             for rule in ruleset[self.logic]
  154         ]
  155 
  156     def eval(self, data):
  157         """
  158         Evaluate the provided data to determine whether it matches this set of conditions.
  159         """
  160         func = any if self.logic == 'or' else all
  161         return func(d.eval(data) for d in self.conditions)
  162
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/netbox/extras/conditions.py b/netbox/extras/conditions.py
  --- a/netbox/extras/conditions.py
  +++ b/netbox/extras/conditions.py
  @@ -135,23 +135,23 @@
       def __init__(self, ruleset):
           if type(ruleset) is not dict:
               raise ValueError(_("Ruleset must be a dictionary, not {ruleset}.").format(ruleset=type(ruleset)))
  -        if len(ruleset) != 1:
  -            raise ValueError(_("Ruleset must have exactly one logical operator (found {ruleset})").format(
  -                ruleset=len(ruleset)))
  -
  -        # Determine the logic type
  -        logic = list(ruleset.keys())[0]
  -        if type(logic) is not str or logic.lower() not in (AND, OR):
  -            raise ValueError(_("Invalid logic type: {logic} (must be '{op_and}' or '{op_or}')").format(
  -                logic=logic, op_and=AND, op_or=OR
  -            ))
  -        self.logic = logic.lower()
   
  -        # Compile the set of Conditions
  -        self.conditions = [
  -            ConditionSet(rule) if is_ruleset(rule) else Condition(**rule)
  -            for rule in ruleset[self.logic]
  -        ]
  +        if len(ruleset) == 1:
  +            self.logic = (list(ruleset.keys())[0]).lower()
  +            if self.logic not in (AND, OR):
  +                raise ValueError(_("Invalid logic type: must be 'AND' or 'OR'. Please check documentation."))
  +
  +            # Compile the set of Conditions
  +            self.conditions = [
  +                ConditionSet(rule) if is_ruleset(rule) else Condition(**rule)
  +                for rule in ruleset[self.logic]
  +            ]
  +        else:
  +            try:
  +                self.logic = None
  +                self.conditions = [Condition(**ruleset)]
  +            except TypeError:
  +                raise ValueError(_("Incorrect key(s) informed. Please check documentation."))
   
       def eval(self, data):
           """
  
verification_info: {"golden_diff": "diff --git a/netbox/extras/conditions.py b/netbox/extras/conditions.py\n--- a/netbox/extras/conditions.py\n+++ b/netbox/extras/conditions.py\n@@ -135,23 +135,23 @@\n     def __init__(self, ruleset):\n         if type(ruleset) is not dict:\n             raise ValueError(_(\"Ruleset must be a dictionary, not {ruleset}.\").format(ruleset=type(ruleset)))\n-        if len(ruleset) != 1:\n-            raise ValueError(_(\"Ruleset must have exactly one logical operator (found {ruleset})\").format(\n-                ruleset=len(ruleset)))\n-\n-        # Determine the logic type\n-        logic = list(ruleset.keys())[0]\n-        if type(logic) is not str or logic.lower() not in (AND, OR):\n-            raise ValueError(_(\"Invalid logic type: {logic} (must be '{op_and}' or '{op_or}')\").format(\n-                logic=logic, op_and=AND, op_or=OR\n-            ))\n-        self.logic = logic.lower()\n \n-        # Compile the set of Conditions\n-        self.conditions = [\n-            ConditionSet(rule) if is_ruleset(rule) else Condition(**rule)\n-            for rule in ruleset[self.logic]\n-        ]\n+        if len(ruleset) == 1:\n+            self.logic = (list(ruleset.keys())[0]).lower()\n+            if self.logic not in (AND, OR):\n+                raise ValueError(_(\"Invalid logic type: must be 'AND' or 'OR'. Please check documentation.\"))\n+\n+            # Compile the set of Conditions\n+            self.conditions = [\n+                ConditionSet(rule) if is_ruleset(rule) else Condition(**rule)\n+                for rule in ruleset[self.logic]\n+            ]\n+        else:\n+            try:\n+                self.logic = None\n+                self.conditions = [Condition(**ruleset)]\n+            except TypeError:\n+                raise ValueError(_(\"Incorrect key(s) informed. Please check documentation.\"))\n \n     def eval(self, data):\n         \"\"\"\n", "issue": "Simple condition (without and/or) does not work in event rule\n### Deployment Type\n\nSelf-hosted\n\n### NetBox Version\n\nv3.7.0\n\n### Python Version\n\n3.11\n\n### Steps to Reproduce\n\n1. Create webhook: Name = Test, URL = http://127.0.0.1:9000 (doesn't matter in this case, it won't be triggered but is required to configure event rule)\r\n2. Go to **Event rules - Add**:\r\n- Name = Test\r\n- Content types = Circuit\r\n- select Updates\r\n- set Conditions:\r\n```\r\n{\r\n    \"attr\": \"status.value\",\r\n    \"value\": \"active\"\r\n}\r\n```\r\n\r\n- Action type = Webhook\r\n- Webhook = Test\r\n- **Create**\r\n\n\n### Expected Behavior\n\nEvent rule is created\n\n### Observed Behavior\n\nError is shown about the condition:\r\n\r\n**Ruleset must have exactly one logical operator (found 2)** \r\n\r\nThe examples in https://docs.netbox.dev/en/stable/reference/conditions/ look the same: simple JSON object with attributes `attr` and `value`.\n", "before_files": [{"content": "import functools\nimport re\nfrom django.utils.translation import gettext as _\n\n__all__ = (\n    'Condition',\n    'ConditionSet',\n)\n\n\nAND = 'and'\nOR = 'or'\n\n\ndef is_ruleset(data):\n    \"\"\"\n    Determine whether the given dictionary looks like a rule set.\n    \"\"\"\n    return type(data) is dict and len(data) == 1 and list(data.keys())[0] in (AND, OR)\n\n\nclass Condition:\n    \"\"\"\n    An individual conditional rule that evaluates a single attribute and its value.\n\n    :param attr: The name of the attribute being evaluated\n    :param value: The value being compared\n    :param op: The logical operation to use when evaluating the value (default: 'eq')\n    \"\"\"\n    EQ = 'eq'\n    GT = 'gt'\n    GTE = 'gte'\n    LT = 'lt'\n    LTE = 'lte'\n    IN = 'in'\n    CONTAINS = 'contains'\n    REGEX = 'regex'\n\n    OPERATORS = (\n        EQ, GT, GTE, LT, LTE, IN, CONTAINS, REGEX\n    )\n\n    TYPES = {\n        str: (EQ, CONTAINS, REGEX),\n        bool: (EQ, CONTAINS),\n        int: (EQ, GT, GTE, LT, LTE, CONTAINS),\n        float: (EQ, GT, GTE, LT, LTE, CONTAINS),\n        list: (EQ, IN, CONTAINS),\n        type(None): (EQ,)\n    }\n\n    def __init__(self, attr, value, op=EQ, negate=False):\n        if op not in self.OPERATORS:\n            raise ValueError(_(\"Unknown operator: {op}. Must be one of: {operators}\").format(\n                op=op, operators=', '.join(self.OPERATORS)\n            ))\n        if type(value) not in self.TYPES:\n            raise ValueError(_(\"Unsupported value type: {value}\").format(value=type(value)))\n        if op not in self.TYPES[type(value)]:\n            raise ValueError(_(\"Invalid type for {op} operation: {value}\").format(op=op, value=type(value)))\n\n        self.attr = attr\n        self.value = value\n        self.eval_func = getattr(self, f'eval_{op}')\n        self.negate = negate\n\n    def eval(self, data):\n        \"\"\"\n        Evaluate the provided data to determine whether it matches the condition.\n        \"\"\"\n        def _get(obj, key):\n            if isinstance(obj, list):\n                return [dict.get(i, key) for i in obj]\n\n            return dict.get(obj, key)\n\n        try:\n            value = functools.reduce(_get, self.attr.split('.'), data)\n        except TypeError:\n            # Invalid key path\n            value = None\n        result = self.eval_func(value)\n\n        if self.negate:\n            return not result\n        return result\n\n    # Equivalency\n\n    def eval_eq(self, value):\n        return value == self.value\n\n    def eval_neq(self, value):\n        return value != self.value\n\n    # Numeric comparisons\n\n    def eval_gt(self, value):\n        return value > self.value\n\n    def eval_gte(self, value):\n        return value >= self.value\n\n    def eval_lt(self, value):\n        return value < self.value\n\n    def eval_lte(self, value):\n        return value <= self.value\n\n    # Membership\n\n    def eval_in(self, value):\n        return value in self.value\n\n    def eval_contains(self, value):\n        return self.value in value\n\n    # Regular expressions\n\n    def eval_regex(self, value):\n        return re.match(self.value, value) is not None\n\n\nclass ConditionSet:\n    \"\"\"\n    A set of one or more Condition to be evaluated per the prescribed logic (AND or OR). Example:\n\n    {\"and\": [\n        {\"attr\": \"foo\", \"op\": \"eq\", \"value\": 1},\n        {\"attr\": \"bar\", \"op\": \"eq\", \"value\": 2, \"negate\": true}\n    ]}\n\n    :param ruleset: A dictionary mapping a logical operator to a list of conditional rules\n    \"\"\"\n    def __init__(self, ruleset):\n        if type(ruleset) is not dict:\n            raise ValueError(_(\"Ruleset must be a dictionary, not {ruleset}.\").format(ruleset=type(ruleset)))\n        if len(ruleset) != 1:\n            raise ValueError(_(\"Ruleset must have exactly one logical operator (found {ruleset})\").format(\n                ruleset=len(ruleset)))\n\n        # Determine the logic type\n        logic = list(ruleset.keys())[0]\n        if type(logic) is not str or logic.lower() not in (AND, OR):\n            raise ValueError(_(\"Invalid logic type: {logic} (must be '{op_and}' or '{op_or}')\").format(\n                logic=logic, op_and=AND, op_or=OR\n            ))\n        self.logic = logic.lower()\n\n        # Compile the set of Conditions\n        self.conditions = [\n            ConditionSet(rule) if is_ruleset(rule) else Condition(**rule)\n            for rule in ruleset[self.logic]\n        ]\n\n    def eval(self, data):\n        \"\"\"\n        Evaluate the provided data to determine whether it matches this set of conditions.\n        \"\"\"\n        func = any if self.logic == 'or' else all\n        return func(d.eval(data) for d in self.conditions)\n", "path": "netbox/extras/conditions.py"}], "after_files": [{"content": "import functools\nimport re\nfrom django.utils.translation import gettext as _\n\n__all__ = (\n    'Condition',\n    'ConditionSet',\n)\n\n\nAND = 'and'\nOR = 'or'\n\n\ndef is_ruleset(data):\n    \"\"\"\n    Determine whether the given dictionary looks like a rule set.\n    \"\"\"\n    return type(data) is dict and len(data) == 1 and list(data.keys())[0] in (AND, OR)\n\n\nclass Condition:\n    \"\"\"\n    An individual conditional rule that evaluates a single attribute and its value.\n\n    :param attr: The name of the attribute being evaluated\n    :param value: The value being compared\n    :param op: The logical operation to use when evaluating the value (default: 'eq')\n    \"\"\"\n    EQ = 'eq'\n    GT = 'gt'\n    GTE = 'gte'\n    LT = 'lt'\n    LTE = 'lte'\n    IN = 'in'\n    CONTAINS = 'contains'\n    REGEX = 'regex'\n\n    OPERATORS = (\n        EQ, GT, GTE, LT, LTE, IN, CONTAINS, REGEX\n    )\n\n    TYPES = {\n        str: (EQ, CONTAINS, REGEX),\n        bool: (EQ, CONTAINS),\n        int: (EQ, GT, GTE, LT, LTE, CONTAINS),\n        float: (EQ, GT, GTE, LT, LTE, CONTAINS),\n        list: (EQ, IN, CONTAINS),\n        type(None): (EQ,)\n    }\n\n    def __init__(self, attr, value, op=EQ, negate=False):\n        if op not in self.OPERATORS:\n            raise ValueError(_(\"Unknown operator: {op}. Must be one of: {operators}\").format(\n                op=op, operators=', '.join(self.OPERATORS)\n            ))\n        if type(value) not in self.TYPES:\n            raise ValueError(_(\"Unsupported value type: {value}\").format(value=type(value)))\n        if op not in self.TYPES[type(value)]:\n            raise ValueError(_(\"Invalid type for {op} operation: {value}\").format(op=op, value=type(value)))\n\n        self.attr = attr\n        self.value = value\n        self.eval_func = getattr(self, f'eval_{op}')\n        self.negate = negate\n\n    def eval(self, data):\n        \"\"\"\n        Evaluate the provided data to determine whether it matches the condition.\n        \"\"\"\n        def _get(obj, key):\n            if isinstance(obj, list):\n                return [dict.get(i, key) for i in obj]\n\n            return dict.get(obj, key)\n\n        try:\n            value = functools.reduce(_get, self.attr.split('.'), data)\n        except TypeError:\n            # Invalid key path\n            value = None\n        result = self.eval_func(value)\n\n        if self.negate:\n            return not result\n        return result\n\n    # Equivalency\n\n    def eval_eq(self, value):\n        return value == self.value\n\n    def eval_neq(self, value):\n        return value != self.value\n\n    # Numeric comparisons\n\n    def eval_gt(self, value):\n        return value > self.value\n\n    def eval_gte(self, value):\n        return value >= self.value\n\n    def eval_lt(self, value):\n        return value < self.value\n\n    def eval_lte(self, value):\n        return value <= self.value\n\n    # Membership\n\n    def eval_in(self, value):\n        return value in self.value\n\n    def eval_contains(self, value):\n        return self.value in value\n\n    # Regular expressions\n\n    def eval_regex(self, value):\n        return re.match(self.value, value) is not None\n\n\nclass ConditionSet:\n    \"\"\"\n    A set of one or more Condition to be evaluated per the prescribed logic (AND or OR). Example:\n\n    {\"and\": [\n        {\"attr\": \"foo\", \"op\": \"eq\", \"value\": 1},\n        {\"attr\": \"bar\", \"op\": \"eq\", \"value\": 2, \"negate\": true}\n    ]}\n\n    :param ruleset: A dictionary mapping a logical operator to a list of conditional rules\n    \"\"\"\n    def __init__(self, ruleset):\n        if type(ruleset) is not dict:\n            raise ValueError(_(\"Ruleset must be a dictionary, not {ruleset}.\").format(ruleset=type(ruleset)))\n\n        if len(ruleset) == 1:\n            self.logic = (list(ruleset.keys())[0]).lower()\n            if self.logic not in (AND, OR):\n                raise ValueError(_(\"Invalid logic type: must be 'AND' or 'OR'. Please check documentation.\"))\n\n            # Compile the set of Conditions\n            self.conditions = [\n                ConditionSet(rule) if is_ruleset(rule) else Condition(**rule)\n                for rule in ruleset[self.logic]\n            ]\n        else:\n            try:\n                self.logic = None\n                self.conditions = [Condition(**ruleset)]\n            except TypeError:\n                raise ValueError(_(\"Incorrect key(s) informed. Please check documentation.\"))\n\n    def eval(self, data):\n        \"\"\"\n        Evaluate the provided data to determine whether it matches this set of conditions.\n        \"\"\"\n        func = any if self.logic == 'or' else all\n        return func(d.eval(data) for d in self.conditions)\n", "path": "netbox/extras/conditions.py"}]}