+h2(#working-with-permissions). Working with permissions
+
+In brief, a permission is represented in Arvados as a link object with the following values:
+
+* @link_class@ is @"permission"@.
+* @name@ is one of @"can_read"@, @"can_write"@, @"can_manage"@, or @"can_login"@.
+* @tail_uuid@ identifies the user or role group that receives the permission.
+* @head_uuid@ identifies the Arvados object this permission grants access to.
+
+For details, refer to the "Permissions model documentation":{{ site.baseurl }}/api/permission-model.html. Managing permissions is just a matter of ensuring the desired links exist using the standard @create@, @update@, and @delete@ methods.
+
+h3(#grant-permission). Grant permission to an object
+
+Create a link with values as documented above.
+
+{% codeblock as python %}
+permission = arv_client.links().create(
+ body={
+ 'link': {
+ 'link_class': 'permission',
+ # Adjust name for the level of permission you want to grant
+ 'name': 'can_read',
+ # tail_uuid must identify a user or role group
+ 'tail_uuid': 'zzzzz-tpzed-12345abcde67890',
+ # head_uuid can identify any Arvados object
+ 'head_uuid': 'zzzzz-4zz18-12345abcde67890',
+ },
+ },
+).execute()
+{% endcodeblock %}
+
+h3(#modify-permission). Modify permission on an object
+
+To modify an existing permission—for example, to change its access level—find the existing link object for the permission, then update it with the new values you want. This example shows changing all read-write permissions on a specific collection to read-only. Adjust the filters appropriately to find the permission(s) you want to modify.
+
+{% codeblock as python %}
+import arvados.util
+for permission in arvados.util.keyset_list_all(
+ # Pass the method keyset_list_all will call to retrieve items.
+ # Do not call it yourself.
+ arv_client.links().list,
+ filters=[
+ # You should use this filter for all permission searches,
+ # to exclude other kinds of links.
+ ['link_class', '=', 'permission'],
+ # Add other filters as desired.
+ ['name', '=', 'can_write'],
+ ['head_uuid', '=', 'zzzzz-4zz18-12345abcde67890'],
+ ...,
+ ],
+):
+ arv_client.links().update(
+ uuid=permission['uuid'],
+ body={
+ 'link': {
+ 'name': 'can_read',
+ },
+ },
+ ).execute()
+{% endcodeblock %}
+
+h3(#revoke-permission). Revoke permission from an object
+
+To revoke an existing permission, find the existing link object for the permission, then delete it. This example shows revoking one user's permission to log into any virtual machines. Adjust the filters appropriately to find the permission(s) you want to revoke.
+
+{% codeblock as python %}
+import arvados.util
+for permission in arvados.util.keyset_list_all(
+ # Pass the method keyset_list_all will call to retrieve items.
+ # Do not call it yourself.
+ arv_client.links().list,
+ filters=[
+ # You should use this filter for all permission searches,
+ # to exclude other kinds of links.
+ ['link_class', '=', 'permission'],
+ # Add other filters as desired.
+ ['name', '=', 'can_login'],
+ ['tail_uuid', '=', 'zzzzz-tpzed-12345abcde67890'],
+ ...,
+ ],
+):
+ arv_client.links().delete(
+ uuid=permission['uuid'],
+ ).execute()
+{% endcodeblock %}
+
+h2(#working-with-properties). Working with properties
+
+Container requests, collections, groups, and links can have metadata properties set through their @properties@ field. For details, refer to the "Metadata properties API reference":{{ site.baseurl }}/api/properties.html.
+
+An Arvados cluster can be configured to use a metadata vocabulary. If this is set up, the vocabulary defines standard identifiers and specific properties and their values. These identifiers can also have more human-friendly aliases. The cluster can also be configured to use the vocabulary strictly, so clients may _only_ set properties on objects that are defined in the vocabulary. For more information about configuring a metadata vocabulary, refer to the "Metadata vocabulary administration documentation":{{ site.baseurl }}/admin/metadata-vocabulary.html.
+
+h3(#update-properties). Update the properties of an object
+
+To set an object's properties to a new value, just call the resource's @update@ method with a new @properties@ field in the body. If you want to make changes to the current set of properties, @get@ the object, build a new dictionary based on its @properties@ field, then call the resource's @update@ method with your new dictionary as the @properties@. Below is an example for a container request.
+
+{% codeblock as python %}
+container_request = arv_client.container_requests().get(
+ uuid='zzzzz-xvhdp-12345abcde67890',
+).execute()
+new_properties = dict(container_request['properties'])
+... # Make your desired changes to new_proprties
+container_request = arv_client.container_requests().update(
+ uuid=container_request['uuid'],
+ body={
+ 'container_request': {
+ 'properties': new_properties,
+ },
+ },
+).execute()
+{% endcodeblock %}
+
+h3(#translating-between-vocabulary-identifiers-and-labels). Translate between vocabulary identifiers and labels
+
+Client software might need to present properties to the user in a human-readable form or take input from the user without requiring them to remember identifiers. The "@Vocabulary.convert_to_labels@":{{ site.baseurl }}/sdk/python/arvados/vocabulary.html#arvados.vocabulary.Vocabulary.convert_to_labels and "@Vocabulary.convert_to_identifiers@":{{ site.baseurl }}/sdk/python/arvados/vocabulary.html#arvados.vocabulary.Vocabulary.convert_to_identifiers methods help with these tasks, respectively.
+
+{% codeblock as python %}
+import arvados.vocabulary
+vocabulary = arvados.vocabulary.load_vocabulary(arv_client)
+
+# The argument should be a mapping of vocabulary keys and values using any
+# defined aliases, like this:
+# {'Creature': 'Human', 'Priority': 'Normal'}
+# The return value will be an analogous mapping where all the aliases have
+# been translated to identifiers, like this:
+# {'IDTAGANIMALS': 'IDVALANIMALS2', 'IDTAGIMPORTANCES': 'IDTAGIMPORTANCES1'}
+properties_by_identifier = vocabulary.convert_to_identifiers({...})
+
+# You can use this to set metadata properties on objects that support them.
+project = arv_client.groups().update(
+ uuid='zzzzz-j7d0g-12345abcde67890',
+ body={
+ 'group': {
+ 'properties': properties_by_identifier,
+ },
+ },
+).execute()
+
+# You can report properties to the user by their preferred name.
+print(f"{project['name']} ({project['group_class']} {project['uuid']}) updated with properties:")
+for key, value in vocabulary.convert_to_labels(project['properties']).items():
+ print(f"↳ {key}: {value}")
+{% endcodeblock %}
+
+h3(#querying-the-vocabulary-definition). Query the vocabulary definition
+
+The @arvados.vocabulary@ module provides facilities to interact with the "active metadata vocabulary":{{ site.baseurl }}/admin/metadata-vocabulary.html in the system. The "@Vocabulary@ class":{{ site.baseurl }}/sdk/python/arvados/vocabulary.html#arvados.vocabulary.Vocabulary provides a mapping-like view of a cluster's configured vocabulary.
+
+{% codeblock as python %}
+import arvados.vocabulary
+vocabulary = arvados.vocabulary.load_vocabulary(arv_client)
+
+# You can use the vocabulary object to access specific keys and values by
+# case-insensitive mapping, like this:
+# vocabulary_value = vocabulary[key_alias][value_alias]
+# You can also access the `key_aliases` and `value_aliases` mapping
+# attributes directly to view the entire vocabulary. The example below
+# writes a plaintext table of the vocabulary.
+for vocabulary_key in set(vocabulary.key_aliases.values()):
+ print(
+ vocabulary_key.identifier,
+ vocabulary_key.preferred_label,
+ ', '.join(vocabulary_key.aliases[1:]),
+ sep='\t',
+ )
+ for vocabulary_value in set(vocabulary_key.value_aliases.values()):
+ print(
+ f'↳ {vocabulary_value.identifier}',
+ vocabulary_value.preferred_label,
+ ', '.join(vocabulary_value.aliases[1:]),
+ sep='\t',
+ )
+{% endcodeblock %}
+
+h2(#working-with-collections). Working with collections
+
+The "@arvados.collection.Collection@ class":{{ site.baseurl }}/sdk/python/arvados/collection.html#arvados.collection.Collection provides a high-level interface to read, create, and update collections. It orchestrates multiple requests to API and Keep so you don't have to worry about the low-level details of keeping everything in sync. It uses threads to make multiple requests to Keep in parallel.
+
+This page only shows you how to perform common tasks using the @Collection@ class. To see all the supported constructor arguments and methods, refer to "the @Collection@ class documentation":{{ site.baseurl }}/sdk/python/arvados/collection.html#arvados.collection.Collection.
+
+h3(#load-collection). Load and update an existing collection
+
+Construct the @Collection@ class with the UUID of a collection you want to read. You can pass additional constructor arguments as needed.