Skip to main content
Azure · lesson 11 of 19
Azure intermediate Lesson 11 of 19

Azure Cosmos DB: Items and Partition Keys

Create a Cosmos DB container with a partition key, upsert items into it, and read one back with the Python SDK against the official vNext emulator — then see why reading with the wrong partition key 404s instead of finding the item a different way.

By SkillByExample · Published

azure cosmos-db nosql

A Cosmos DB container doesn’t have a schema, but it does have a partition key: a required property every item must carry, which Cosmos DB uses to decide where that item physically lives. This lesson runs against Microsoft’s own vNext emulator in Docker — a real local Cosmos DB, not a third-party stand-in — so everything here except RU pricing behaves the way the production service does.

Upserting Items and Reading One Back

from azure.cosmos import CosmosClient, PartitionKey

ENDPOINT = "http://localhost:8081"
KEY = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="

client = CosmosClient(ENDPOINT, credential=KEY)
db = client.create_database_if_not_exists("storedb")
container = db.create_container_if_not_exists(
    id="orders", partition_key=PartitionKey(path="/customer")
)

for order_id, customer, total in [("o0", "alice", 30), ("o1", "bob", 12), ("o2", "alice", 55)]:
    container.upsert_item({"id": order_id, "customer": customer, "total": total})
    ru = container.client_connection.last_response_headers.get("x-ms-request-charge")
    print(f"upsert {order_id} RU: {ru}")

item = container.read_item(item="o0", partition_key="alice")
print("read o0 total:", item["total"])
print("read o0 has etag:", "_etag" in item)
$ docker run -d --name az-cosmos-demo -e PROTOCOL=http mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:vnext-EN20261019
30c0908bbbd64f58327f47a1743d9516006ff6c850b44e557fbf03dd03fb0be6
$ until docker logs az-cosmos-demo 2>&1 | grep -q 'System is now fully ready to accept requests'; do sleep 0.5; done
$ docker run --rm --network container:az-cosmos-demo -v "$(pwd)":/app -w /app python:3.14-slim bash -c "pip install -q azure-cosmos==4.17.1 2>&1 | tail -1 && python cosmos_items.py"
[notice] To update, run: pip install --upgrade pip
upsert o0 RU: 1
upsert o1 RU: 1
upsert o2 RU: 1
read o0 total: 30
read o0 has etag: True

The emulator reports RU: 1 for every write here, regardless of item size — that’s the one place it diverges from the real service, which prices a request by its actual size and complexity. read_item takes both the id and the partition_key because together they’re the full address of one item; a container with a million items and the right partition key still costs roughly the same as one with ten.

The Wrong Partition Key Looks Exactly Like “Not Found”

from azure.cosmos import CosmosClient, exceptions

ENDPOINT = "http://localhost:8081"
KEY = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="

client = CosmosClient(ENDPOINT, credential=KEY)
container = client.get_database_client("storedb").get_container_client("orders")

try:
    container.read_item(item="o0", partition_key="bob")
except exceptions.CosmosResourceNotFoundError as e:
    print("read with wrong partition key ->", type(e).__name__, e.status_code)

item = container.read_item(item="o0", partition_key="alice")
print("read with correct partition key -> total:", item["total"])
$ docker run --rm --network container:az-cosmos-demo -v "$(pwd)":/app -w /app python:3.14-slim bash -c "pip install -q azure-cosmos==4.17.1 2>&1 | tail -1 && python cosmos_wrong_pk.py"
[notice] To update, run: pip install --upgrade pip
read with wrong partition key -> CosmosResourceNotFoundError 404
read with correct partition key -> total: 30

o0 really does exist — just under partition key "alice", not "bob". Cosmos DB’s point-read API doesn’t search other partitions to tell you that; it treats “right id, wrong partition key” as indistinguishable from “no such item”, because the partition key is part of the lookup address, not a filter applied after finding the item. Code that doesn’t already know an item’s partition key has to query for it instead of guessing at read_item.

Practice

1. Upsert a fourth order and overwrite one of the existing orders' total with upsert_item, then confirm the overwrite took effect.
from azure.cosmos import CosmosClient

ENDPOINT = "http://localhost:8081"
KEY = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="

client = CosmosClient(ENDPOINT, credential=KEY)
container = client.get_database_client("storedb").get_container_client("orders")

container.upsert_item({"id": "o3", "customer": "carol", "total": 99})
container.upsert_item({"id": "o0", "customer": "alice", "total": 45})

print("o3 total:", container.read_item(item="o3", partition_key="carol")["total"])
print("o0 total after overwrite:", container.read_item(item="o0", partition_key="alice")["total"])
$ docker run --rm --network container:az-cosmos-demo -v "$(pwd)":/app -w /app python:3.14-slim bash -c "pip install -q azure-cosmos==4.17.1 2>&1 | tail -1 && python cosmos_overwrite.py"
[notice] To update, run: pip install --upgrade pip
o3 total: 99
o0 total after overwrite: 45

upsert_item with an id that already exists in that partition replaces the whole item — o0’s total goes from 30 to 45, nothing merges field by field.

2. Call create_item with an id that already exists and see what exception that raises instead.
from azure.cosmos import CosmosClient, exceptions

ENDPOINT = "http://localhost:8081"
KEY = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="

client = CosmosClient(ENDPOINT, credential=KEY)
container = client.get_database_client("storedb").get_container_client("orders")

try:
    container.create_item({"id": "o0", "customer": "alice", "total": 1})
except exceptions.CosmosResourceExistsError as e:
    print("create_item on existing id ->", type(e).__name__, e.status_code)
$ docker run --rm --network container:az-cosmos-demo -v "$(pwd)":/app -w /app python:3.14-slim bash -c "pip install -q azure-cosmos==4.17.1 2>&1 | tail -1 && python cosmos_create_conflict.py"
[notice] To update, run: pip install --upgrade pip
create_item on existing id -> CosmosResourceExistsError 409

create_item is upsert_item’s stricter sibling: same partition key rules, but a duplicate id raises CosmosResourceExistsError (409) instead of silently overwriting.

3. Create a second container with a different partition key path and confirm the two containers' items are independent.
from azure.cosmos import CosmosClient, PartitionKey

ENDPOINT = "http://localhost:8081"
KEY = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="

client = CosmosClient(ENDPOINT, credential=KEY)
db = client.get_database_client("storedb")
products = db.create_container_if_not_exists(
    id="products", partition_key=PartitionKey(path="/category")
)
products.upsert_item({"id": "p0", "category": "books", "name": "Cosmos DB Guide"})

orders = db.get_container_client("orders")
print("products container item count:", len(list(products.read_all_items())))
has_o0 = any(i["id"] == "o0" for i in orders.read_all_items())
print("orders container still has o0:", has_o0)
$ docker run --rm --network container:az-cosmos-demo -v "$(pwd)":/app -w /app python:3.14-slim bash -c "pip install -q azure-cosmos==4.17.1 2>&1 | tail -1 && python cosmos_second_container.py"
[notice] To update, run: pip install --upgrade pip
products container item count: 1
orders container still has o0: True

Containers in the same database don’t share items or even partition-key schemes — products uses /category, orders uses /customer, and neither container’s items are visible through the other’s client.

Cleanup

$ docker rm -f az-cosmos-demo
az-cosmos-demo

Next: parameterised and aggregate queries against this same container.

Frequently Asked Questions

What is a Cosmos DB partition key, and why does read_item need it?
The partition key is a required property on every item that Cosmos DB uses to decide which physical partition stores it. A point read (read_item) is only cheap and fast because the SDK sends the partition key value along with the id, so the request can go straight to the right partition instead of scanning every partition in the container — which is exactly what a wrong partition key breaks.
What happens if I call read_item with the right id but the wrong partition key?
A CosmosResourceNotFoundError with status_code 404 — the same error as if the item didn't exist at all. Cosmos DB doesn't look the id up across partitions to tell you 'found it, but in a different partition'; the partition key is part of the address, and a wrong address looks identical to 'not found' from read_item's point of view.
Can I run Cosmos DB locally without an Azure subscription?
Yes, with Microsoft's own vNext emulator, distributed as a Docker image (mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator). It accepts the real SDK and a well-known local key, and the item CRUD this lesson covers — upsert, read, create, patch — behaves the way the real service does. It diverges in other ways this track's later lessons hit directly: RU costs are flat regardless of request size, there's only ever one physical partition (so cross-partition query behavior can't be observed), and the change feed's partition_key scoping is silently ignored rather than enforced.
Does upsert_item overwrite an existing item or only insert new ones?
Both: it inserts the item if its id (within that partition key value) doesn't exist yet, and replaces it entirely if it does. There's no separate 'insert that fails on conflict' call in this lesson's examples — create_item is that call, and raises CosmosResourceExistsError (409) on a duplicate id, where upsert_item would have just overwritten it.