prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Consistency on PUT with mandatory schema fields
  While working on #790 I realize that there is something not clear in our specifications.
  
  Currently, if a resource has a mandatory field (eg. groups `members`), then we cannot do a `PUT` with just the `permissions` values. This is because a PUT can lead to a creation, and the `members` fields has to be provided.
  
  On other resources, which have no mandatory field, it is perfectly possible to only provide `permissions`.
  
  But, I believe we should make every resources behave the same way.
  
  For example, when we'll implement the edition of permissions in Kinto-admin, we don't want to have to pass the `data` if it was not changed.
  
  Two solutions:
  - Add a default value (`[]`) for the groups members attribute (_my prefered one, trivial and not absurd_)
  - Allow `data` to be omitted only when the `PUT` replaces an existing object (_more complex to implement, but would work for any resource with mandatory fields_)
  
  Consistency on PUT with mandatory schema fields
  While working on #790 I realize that there is something not clear in our specifications.
  
  Currently, if a resource has a mandatory field (eg. groups `members`), then we cannot do a `PUT` with just the `permissions` values. This is because a PUT can lead to a creation, and the `members` fields has to be provided.
  
  On other resources, which have no mandatory field, it is perfectly possible to only provide `permissions`.
  
  But, I believe we should make every resources behave the same way.
  
  For example, when we'll implement the edition of permissions in Kinto-admin, we don't want to have to pass the `data` if it was not changed.
  
  Two solutions:
  - Add a default value (`[]`) for the groups members attribute (_my prefered one, trivial and not absurd_)
  - Allow `data` to be omitted only when the `PUT` replaces an existing object (_more complex to implement, but would work for any resource with mandatory fields_)
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `kinto/views/groups.py`
  Content:
  ```
  1 import colander
  2 
  3 from kinto.core import resource, utils
  4 from kinto.core.events import ResourceChanged, ACTIONS
  5 from pyramid.events import subscriber
  6 
  7 
  8 def validate_member(node, member):
  9     if member.startswith('/buckets/') or member == 'system.Everyone':
  10         raise colander.Invalid(node, "'{}' is not a valid user ID.".format(member))
  11 
  12 
  13 class GroupSchema(resource.ResourceSchema):
  14     members = colander.SchemaNode(colander.Sequence(),
  15                                   colander.SchemaNode(colander.String(),
  16                                                       validator=validate_member))
  17 
  18 
  19 @resource.register(name='group',
  20                    collection_path='/buckets/{{bucket_id}}/groups',
  21                    record_path='/buckets/{{bucket_id}}/groups/{{id}}')
  22 class Group(resource.ShareableResource):
  23     schema = GroupSchema
  24 
  25     def get_parent_id(self, request):
  26         bucket_id = request.matchdict['bucket_id']
  27         parent_id = utils.instance_uri(request, 'bucket', id=bucket_id)
  28         return parent_id
  29 
  30 
  31 @subscriber(ResourceChanged,
  32             for_resources=('group',),
  33             for_actions=(ACTIONS.DELETE,))
  34 def on_groups_deleted(event):
  35     """Some groups were deleted, remove them from users principals.
  36     """
  37     permission_backend = event.request.registry.permission
  38 
  39     for change in event.impacted_records:
  40         group = change['old']
  41         bucket_id = event.payload['bucket_id']
  42         group_uri = utils.instance_uri(event.request, 'group',
  43                                        bucket_id=bucket_id,
  44                                        id=group['id'])
  45 
  46         permission_backend.remove_principal(group_uri)
  47 
  48 
  49 @subscriber(ResourceChanged,
  50             for_resources=('group',),
  51             for_actions=(ACTIONS.CREATE, ACTIONS.UPDATE))
  52 def on_groups_changed(event):
  53     """Some groups were changed, update users principals.
  54     """
  55     permission_backend = event.request.registry.permission
  56 
  57     for change in event.impacted_records:
  58         if 'old' in change:
  59             existing_record_members = set(change['old'].get('members', []))
  60         else:
  61             existing_record_members = set()
  62 
  63         group = change['new']
  64         group_uri = '/buckets/{bucket_id}/groups/{id}'.format(id=group['id'],
  65                                                               **event.payload)
  66         new_record_members = set(group.get('members', []))
  67         new_members = new_record_members - existing_record_members
  68         removed_members = existing_record_members - new_record_members
  69 
  70         for member in new_members:
  71             # Add the group to the member principal.
  72             permission_backend.add_user_principal(member, group_uri)
  73 
  74         for member in removed_members:
  75             # Remove the group from the member principal.
  76             permission_backend.remove_user_principal(member, group_uri)
  77
  ```
  --- 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/kinto/views/groups.py b/kinto/views/groups.py
  --- a/kinto/views/groups.py
  +++ b/kinto/views/groups.py
  @@ -13,7 +13,8 @@
   class GroupSchema(resource.ResourceSchema):
       members = colander.SchemaNode(colander.Sequence(),
                                     colander.SchemaNode(colander.String(),
  -                                                      validator=validate_member))
  +                                                      validator=validate_member),
  +                                  missing=[])
   
   
   @resource.register(name='group',
  
verification_info: {"golden_diff": "diff --git a/kinto/views/groups.py b/kinto/views/groups.py\n--- a/kinto/views/groups.py\n+++ b/kinto/views/groups.py\n@@ -13,7 +13,8 @@\n class GroupSchema(resource.ResourceSchema):\n     members = colander.SchemaNode(colander.Sequence(),\n                                   colander.SchemaNode(colander.String(),\n-                                                      validator=validate_member))\n+                                                      validator=validate_member),\n+                                  missing=[])\n \n \n @resource.register(name='group',\n", "issue": "Consistency on PUT with mandatory schema fields\nWhile working on #790 I realize that there is something not clear in our specifications.\n\nCurrently, if a resource has a mandatory field (eg. groups `members`), then we cannot do a `PUT` with just the `permissions` values. This is because a PUT can lead to a creation, and the `members` fields has to be provided.\n\nOn other resources, which have no mandatory field, it is perfectly possible to only provide `permissions`.\n\nBut, I believe we should make every resources behave the same way.\n\nFor example, when we'll implement the edition of permissions in Kinto-admin, we don't want to have to pass the `data` if it was not changed.\n\nTwo solutions:\n- Add a default value (`[]`) for the groups members attribute (_my prefered one, trivial and not absurd_)\n- Allow `data` to be omitted only when the `PUT` replaces an existing object (_more complex to implement, but would work for any resource with mandatory fields_)\n\nConsistency on PUT with mandatory schema fields\nWhile working on #790 I realize that there is something not clear in our specifications.\n\nCurrently, if a resource has a mandatory field (eg. groups `members`), then we cannot do a `PUT` with just the `permissions` values. This is because a PUT can lead to a creation, and the `members` fields has to be provided.\n\nOn other resources, which have no mandatory field, it is perfectly possible to only provide `permissions`.\n\nBut, I believe we should make every resources behave the same way.\n\nFor example, when we'll implement the edition of permissions in Kinto-admin, we don't want to have to pass the `data` if it was not changed.\n\nTwo solutions:\n- Add a default value (`[]`) for the groups members attribute (_my prefered one, trivial and not absurd_)\n- Allow `data` to be omitted only when the `PUT` replaces an existing object (_more complex to implement, but would work for any resource with mandatory fields_)\n\n", "before_files": [{"content": "import colander\n\nfrom kinto.core import resource, utils\nfrom kinto.core.events import ResourceChanged, ACTIONS\nfrom pyramid.events import subscriber\n\n\ndef validate_member(node, member):\n    if member.startswith('/buckets/') or member == 'system.Everyone':\n        raise colander.Invalid(node, \"'{}' is not a valid user ID.\".format(member))\n\n\nclass GroupSchema(resource.ResourceSchema):\n    members = colander.SchemaNode(colander.Sequence(),\n                                  colander.SchemaNode(colander.String(),\n                                                      validator=validate_member))\n\n\n@resource.register(name='group',\n                   collection_path='/buckets/{{bucket_id}}/groups',\n                   record_path='/buckets/{{bucket_id}}/groups/{{id}}')\nclass Group(resource.ShareableResource):\n    schema = GroupSchema\n\n    def get_parent_id(self, request):\n        bucket_id = request.matchdict['bucket_id']\n        parent_id = utils.instance_uri(request, 'bucket', id=bucket_id)\n        return parent_id\n\n\n@subscriber(ResourceChanged,\n            for_resources=('group',),\n            for_actions=(ACTIONS.DELETE,))\ndef on_groups_deleted(event):\n    \"\"\"Some groups were deleted, remove them from users principals.\n    \"\"\"\n    permission_backend = event.request.registry.permission\n\n    for change in event.impacted_records:\n        group = change['old']\n        bucket_id = event.payload['bucket_id']\n        group_uri = utils.instance_uri(event.request, 'group',\n                                       bucket_id=bucket_id,\n                                       id=group['id'])\n\n        permission_backend.remove_principal(group_uri)\n\n\n@subscriber(ResourceChanged,\n            for_resources=('group',),\n            for_actions=(ACTIONS.CREATE, ACTIONS.UPDATE))\ndef on_groups_changed(event):\n    \"\"\"Some groups were changed, update users principals.\n    \"\"\"\n    permission_backend = event.request.registry.permission\n\n    for change in event.impacted_records:\n        if 'old' in change:\n            existing_record_members = set(change['old'].get('members', []))\n        else:\n            existing_record_members = set()\n\n        group = change['new']\n        group_uri = '/buckets/{bucket_id}/groups/{id}'.format(id=group['id'],\n                                                              **event.payload)\n        new_record_members = set(group.get('members', []))\n        new_members = new_record_members - existing_record_members\n        removed_members = existing_record_members - new_record_members\n\n        for member in new_members:\n            # Add the group to the member principal.\n            permission_backend.add_user_principal(member, group_uri)\n\n        for member in removed_members:\n            # Remove the group from the member principal.\n            permission_backend.remove_user_principal(member, group_uri)\n", "path": "kinto/views/groups.py"}], "after_files": [{"content": "import colander\n\nfrom kinto.core import resource, utils\nfrom kinto.core.events import ResourceChanged, ACTIONS\nfrom pyramid.events import subscriber\n\n\ndef validate_member(node, member):\n    if member.startswith('/buckets/') or member == 'system.Everyone':\n        raise colander.Invalid(node, \"'{}' is not a valid user ID.\".format(member))\n\n\nclass GroupSchema(resource.ResourceSchema):\n    members = colander.SchemaNode(colander.Sequence(),\n                                  colander.SchemaNode(colander.String(),\n                                                      validator=validate_member),\n                                  missing=[])\n\n\n@resource.register(name='group',\n                   collection_path='/buckets/{{bucket_id}}/groups',\n                   record_path='/buckets/{{bucket_id}}/groups/{{id}}')\nclass Group(resource.ShareableResource):\n    schema = GroupSchema\n\n    def get_parent_id(self, request):\n        bucket_id = request.matchdict['bucket_id']\n        parent_id = utils.instance_uri(request, 'bucket', id=bucket_id)\n        return parent_id\n\n\n@subscriber(ResourceChanged,\n            for_resources=('group',),\n            for_actions=(ACTIONS.DELETE,))\ndef on_groups_deleted(event):\n    \"\"\"Some groups were deleted, remove them from users principals.\n    \"\"\"\n    permission_backend = event.request.registry.permission\n\n    for change in event.impacted_records:\n        group = change['old']\n        bucket_id = event.payload['bucket_id']\n        group_uri = utils.instance_uri(event.request, 'group',\n                                       bucket_id=bucket_id,\n                                       id=group['id'])\n\n        permission_backend.remove_principal(group_uri)\n\n\n@subscriber(ResourceChanged,\n            for_resources=('group',),\n            for_actions=(ACTIONS.CREATE, ACTIONS.UPDATE))\ndef on_groups_changed(event):\n    \"\"\"Some groups were changed, update users principals.\n    \"\"\"\n    permission_backend = event.request.registry.permission\n\n    for change in event.impacted_records:\n        if 'old' in change:\n            existing_record_members = set(change['old'].get('members', []))\n        else:\n            existing_record_members = set()\n\n        group = change['new']\n        group_uri = '/buckets/{bucket_id}/groups/{id}'.format(id=group['id'],\n                                                              **event.payload)\n        new_record_members = set(group.get('members', []))\n        new_members = new_record_members - existing_record_members\n        removed_members = existing_record_members - new_record_members\n\n        for member in new_members:\n            # Add the group to the member principal.\n            permission_backend.add_user_principal(member, group_uri)\n\n        for member in removed_members:\n            # Remove the group from the member principal.\n            permission_backend.remove_user_principal(member, group_uri)\n", "path": "kinto/views/groups.py"}]}