Repository: getmoto/moto (python)

Issue:
Feature Request: Support apigateway patchOperations for 'path': '/apiStages'
When calling `client.update_usage_plan(usagePlanId=usage_plan_id, patchOperations=[{
        'op': 'add',
        'path': '/apiStages',
        'value': f'{api_gw_id}:{api_gw_stage}',}])`
The api-gw should be attached to the usage plan but this seemed to not be the case.
I ran this code with real boto3 client and it worked. Any plan to support `patchOperations` for `/apiStages` path?

And thanks BTW for the great project

Pull request description:
ApiGateway: update_usage_plan() now supports adding apiStages
Fixes #7360

Patch:
diff --git a/docs/docs/services/apigateway.rst b/docs/docs/services/apigateway.rst
index 988edd6ab..4d3797589 100644
--- a/docs/docs/services/apigateway.rst
+++ b/docs/docs/services/apigateway.rst
@@ -151,5 +151,13 @@ apigateway
 - [X] update_stage
 - [ ] update_usage
 - [X] update_usage_plan
+  
+        The following PatchOperations are currently supported:
+        add    : Everything except /apiStages/{apidId:stageName}/throttle/ and children
+        replace: Everything except /apiStages/{apidId:stageName}/throttle/ and children
+        remove : Nothing yet
+        copy   : Nothing yet
+        
+
 - [ ] update_vpc_link
 
diff --git a/moto/apigateway/models.py b/moto/apigateway/models.py
index d207bbd83..14cf41b39 100644
--- a/moto/apigateway/models.py
+++ b/moto/apigateway/models.py
@@ -874,21 +874,29 @@ class UsagePlan(BaseModel):
         for op in patch_operations:
             path = op["path"]
             value = op["value"]
+            if op["op"] == "add":
+                if path == "/apiStages":
+                    self.api_stages.append(
+                        {"apiId": value.split(":")[0], "stage": value.split(":")[1]}
+                    )
             if op["op"] == "replace":
                 if "/name" in path:
                     self.name = value
-                if "/productCode" in path:
-                    self.product_code = value
                 if "/description" in path:
                     self.description = value
+            if op["op"] in ["add", "replace"]:
+                if "/productCode" in path:
+                    self.product_code = value
                 if "/quota/limit" in path:
                     self.quota["limit"] = value
                 if "/quota/period" in path:
                     self.quota["period"] = value
+                if path == "/quota/offset":
+                    self.quota["offset"] = value
                 if "/throttle/rateLimit" in path:
-                    self.throttle["rateLimit"] = value
+                    self.throttle["rateLimit"] = int(value)
                 if "/throttle/burstLimit" in path:
-                    self.throttle["burstLimit"] = value
+                    self.throttle["burstLimit"] = int(value)
 
 
 class RequestValidator(BaseModel):
@@ -2103,6 +2111,13 @@ class APIGatewayBackend(BaseBackend):
         return self.usage_plans[usage_plan_id]
 
     def update_usage_plan(self, usage_plan_id: str, patch_operations: Any) -> UsagePlan:
+        """
+        The following PatchOperations are currently supported:
+        add    : Everything except /apiStages/{apidId:stageName}/throttle/ and children
+        replace: Everything except /apiStages/{apidId:stageName}/throttle/ and children
+        remove : Nothing yet
+        copy   : Nothing yet
+        """
         if usage_plan_id not in self.usage_plans:
             raise UsagePlanNotFoundException()
         self.usage_plans[usage_plan_id].apply_patch_operations(patch_operations)
diff --git a/requirements.txt b/requirements.txt
index f5a476248..f3f09255e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,2 @@
-#  Please add requirements to setup.py
+#  Please add requirements to setup.cfg
 -e .[all]


Test patch:
diff --git a/tests/test_apigateway/test_apigateway.py b/tests/test_apigateway/test_apigateway.py
index 88bfc2a2b..1abc572c8 100644
--- a/tests/test_apigateway/test_apigateway.py
+++ b/tests/test_apigateway/test_apigateway.py
@@ -1701,13 +1701,28 @@ def test_update_usage_plan():
     payload = {
         "name": "TEST-PLAN-2",
         "description": "Description",
-        "quota": {"limit": 10, "period": "DAY", "offset": 0},
-        "throttle": {"rateLimit": 2, "burstLimit": 1},
-        "apiStages": [{"apiId": "foo", "stage": "bar"}],
         "tags": {"tag_key": "tag_value"},
     }
     response = client.create_usage_plan(**payload)
     usage_plan_id = response["id"]
+
+    add = client.update_usage_plan(
+        usagePlanId=usage_plan_id,
+        patchOperations=[
+            {"op": "add", "path": "/apiStages", "value": "foo:bar"},
+            {"op": "add", "path": "/productCode", "value": "mypc"},
+            {"op": "add", "path": "/quota/limit", "value": "1"},
+            {"op": "add", "path": "/quota/offset", "value": "2"},
+            {"op": "add", "path": "/quota/period", "value": "DAY"},
+            {"op": "add", "path": "/throttle/burstLimit", "value": "11"},
+            {"op": "add", "path": "/throttle/rateLimit", "value": "12"},
+        ],
+    )
+    assert add["apiStages"] == [{"apiId": "foo", "stage": "bar"}]
+    assert add["productCode"] == "mypc"
+    assert add["quota"] == {"limit": 1, "offset": 2, "period": "DAY"}
+    assert add["throttle"] == {"burstLimit": 11, "rateLimit": 12}
+
     response = client.update_usage_plan(
         usagePlanId=usage_plan_id,
         patchOperations=[