Skip to main content

py-ucan

pypi

This is a Python library to help the web applications make use of UCANs in their authorization flows. To learn more about UCANs and how you might use them in your application, visit ucan website or read the spec.

Contents

Installation ⇧

pip install -U py-ucan

Usage ⇧

Ucan objects ⇧

All the objects are based on Pydantic v2 models. Ideally you would only need to use model_validate, model_validate_json and model_dump from the pydantic model API, but please go through their docs for advanced usage.

NOTE: Ojects can be instantiated with field names in camel case, but to access those fields, you need to use their snake case names.

ucan.ResourcePointer

from ucan import ResourcePointer

# -- from encoded value
resource = ResourcePointer.decode("fileverse://solo.fileverse.io")

# -- from kwargs: snake case
resource = ResourcePointer(scheme="fileverse", hier_part="//solo.fileverse.io")

# -- from kwargs: camel case
resource = ResourcePointer(scheme="fileverse", hierPart="//solo.fileverse.io")

# -- from dict: snake case
resource = ResourcePointer.model_validate({"scheme": "fileverse", "hier_part": "//solo.fileverse.io"})

# -- from dict: camel case
resource = ResourcePointer.model_validate({"scheme": "fileverse", "hierPart": "//solo.fileverse.io"})

# -- from json: snake case
resource = ResourcePointer.model_validate_json('{"scheme": "fileverse", "hier_part": "//solo.fileverse.io"}')

# -- from json: camel case
resource = ResourcePointer.model_validate_json('{"scheme": "fileverse", "hierPart": "//solo.fileverse.io"}')

# access values
print(resource.scheme, resource.hier_part)
# output: fileverse //solo.fileverse.io

# dump to python dict
# all the objects above will dump to a dict with camel case fields
print(resource.model_dump()) 
# output: {'scheme': 'fileverse', 'hierPart': '//solo.fileverse.io'}

# encode as string
print(resource.encode()) 
# or 
print(str(resource)) 
# output: fileverse://solo.fileverse.io

ucan.Ability

from ucan import Ability

# -- from encoded value
ability = Ability.decode("crud/edit")

# -- from kwargs
ability = Ability(namespace="crud", segments=["edit"])

# -- from dict
ability = Ability.model_validate({"namespace": "crud", "segments": ["edit"]})

# -- from json
ability = Ability.model_validate_json('{"namespace": "crud", "segments": ["edit"]}')

# access values
print(ability.namespace, ability.segments)
# output: crud ['edit']

# dump to python dict
# all the objects above will dump to a dict
print(ability.model_dump()) 
# output: {'namespace': 'crud', 'segments': ['edit']}

# encode as string
print(ability.encode()) 
# or 
print(str(ability)) 
# output: crud/edit

ucan.Capability

NOTE: since with is a reserved keyword in python, the with field in Capability is accessible via with_ attribute, but the Capability model supports loading values having with field name using a python dict or a JSON string (see examples below).

import json

from ucan import Ability, Capability, ResourcePointer


# --- load `with` value

# loaded from kwargs
resource = ResourcePointer(scheme="https", hier_part="//app.example.com") 
# or loaded from string
resource = ResourcePointer.decode("https:////app.example.com")
# or an encoded string
resource = "https://app.example.com"  # encoded value
# or a dict with snake case field names
resource = {"scheme": "https", "hier_part": "//app.example.com"}
# or a dict with camel case field names
resource = {"scheme": "https", "hierPart": "//app.example.com"}


# --- load `can` value

# loaded from kwargs
ability = Ability(namespace="crud", segments=["view"]) 
# or loaded from string
ability = Ability.decode("crud/view")
# or an encoded string
ability = "crud/view"  # encoded value
# or a dict with snake case field names
ability = {"namespace": "crud", "segments": ["view"]}


# --- load `capability` value
# note: all the above variations of `resource` and `ability` are supported.

# -- from kwargs
capability = Capability(with_=resource, can=ability)
# -- or by spreading the dict as kwargs
capability = Capability(**{"with": resource, "can": ability})

# -- from dict
capability = Capability.model_validate({"with": resource, "can": ability})
# or
capability = Capability.model_validate({"with_": resource, "can": ability})

# -- from json
capability = Capability.model_validate_json(
    json.dumps({"with": resource, "can": ability})
)
# or
capability = Capability.model_validate_json(
    json.dumps({"with_": resource, "can": ability})
)

# access values
# note: `with` field cannot be accessed with the name `with`, use `with_` instead.
# note: print function outputs the str representation of the models
# and since `ResourcePointer` and `Ability` models implement their 
# respective `__str__` callbacks, the output is simply their encoded values.
print(capability.with_, capability.can)
# output: https://app.example.com crud/view

# dump to python dict
# all the objects above will dump to a dict
print(capability.model_dump()) 
"""
output:
{
    'with': {'scheme': 'https', 'hierPart': '//app.example.com'},
    'can': {'namespace': 'crud', 'segments': ['view']}
}
"""

# encode all capability parts
print(capability.encode()) 
# output: {'with': 'https://app.example.com', 'can': 'crud/view'}

Parsing UCAN Tokens ⇧

To parse a token without validating its signature or expiry, you need to use the parse function or Ucan.decode model function.

import ucan

# receive the token from user request.
encoded_token = "eyJhbG..."  # request.headers.get("Authorization") or similar

# using `ucan.parse`

try:
    # here `parsed_token` is an instance of `ucan.Ucan`.
    parsed_token = ucan.parse(encoded_token)

except ValueError as e:
    # Invalid token
    pass

# or, using `ucan.Ucan.decode` model function

try:
    # here `parsed_token` is an instance of `ucan.Ucan`.
    parsed_token = ucan.Ucan.decode(encoded_token)

except ValueError as e:
    # Invalid token
    pass

Validating UCAN Tokens ⇧

To validate a token, you need to use the validate function.

import ucan

# receive the token from user request.
encoded_token = "eyJhbG..."  # request.headers.get("Authorization") or similar

# parse and validate the token
try:
    # here `parsed_token` is an instance of `ucan.Ucan`.
    parsed_token = await ucan.validate(encoded_token)

except Exception as e:
    # Invalid token
    pass

Verifying UCAN Invocations ⇧

Using a UCAN to authorize an action is called "invocation".

To verify invocations, you need to use the verify function.

import ucan

# receive the token from user request.
encoded_token = "eyJhbG..."  # request.headers.get("Authorization") or similar

# generate service keypair
service_key = ucan.EdKeypair.generate()
service_did = service_key.did()  # will return "did:key:zabcde..."

# known resource and user to validate against
doc_id = "some-id"
user_did = "did:key:z6Mk..."

result = await ucan.verify(
    encoded_token,
    # to make sure we're the intended recipient of this UCAN
    audience=service_did,
    # capabilities required for this invocation & which owner we expect for each capability
    required_capabilities=[
        ucan.RequiredCapability(
            capability=ucan.Capability(
                with_=ucan.ResourcePointer(
                    scheme="fileverse", hier_part="//portal.fileverse.io"
                ),
                can=ucan.Ability(namespace=doc_id, segments=["EDIT", "VIEW"]),
            ),
            # check against a known owner of the `doc_id` resource
            root_issuer=user_did,
        ),
    ],
)

# result will be one of the following:
# error: ucan.VerifyResultError(ok=False, errors=[Exception("...")]
# success: ucan.VerifyResultOk(ok=True, value=[ucan.Verification(..)])

if isinstance(result, ucan.VerifyResultOk):
    # The UCAN authorized the user
    pass

else:
    # Unauthorized
    pass

Release files for py-ucan 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for py-ucan 1.0.0
File Size Uploaded
py_ucan-1.0.0.tar.gz 24.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for py-ucan 1.0.0
File Interpreter ABI Platform
py_ucan-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 48.2 kB

Release files / py_ucan-1.0.0.tar.gz

Download URL py_ucan-1.0.0.tar.gz
Size 24.0 kB
Tags Source
SHA-256 checksum
How to use checksums
25bde973fa5b98ecaf890dc730f2f161f6430706d73ad55d27b4973cc11ddc01
BLAKE2b-256 checksum
How to use checksums
3dde967fdfa887dc28302fae74eab772e9642fc3cbf31a03d0c9dd7bd1098173
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.0 CPython/3.12.5

Release files / py_ucan-1.0.0-py3-none-any.whl

Download URL py_ucan-1.0.0-py3-none-any.whl
Size 24.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c8bf7b9e7a21f8e6ea755216b8fd73dfcfd368b810f258f28c890caf215c5584
BLAKE2b-256 checksum
How to use checksums
4717f474b22ceb5f2bd6352b2f9898c05bbcc4f8b19cf9ab1261fa0442e04e6d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/5.1.0 CPython/3.12.5

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page