Timeback OneRoster v1.2 client for rostering and gradebook APIs
Project description
timeback-oneroster
Python client for the OneRoster v1.2 API.
Installation
# pip
pip install timeback-oneroster
# uv (add to a project)
uv add timeback-oneroster
# uv (install into current environment)
uv pip install timeback-oneroster
Quick Start
from timeback_oneroster import OneRosterClient
async def main():
client = OneRosterClient(
env="staging", # or "production"
client_id="your-client-id",
client_secret="your-client-secret",
)
# List all schools
schools = await client.schools.list()
for school in schools:
print(school.name)
# Get a specific user
user = await client.users.get("user-sourced-id")
print(f"{user.given_name} {user.family_name}")
await client.close()
Client Structure
client = OneRosterClient(options)
# Rostering
client.users # All users
client.students # Students (filtered users)
client.teachers # Teachers (filtered users)
client.classes # Classes
client.schools # Schools
# client.courses # Coming soon
# client.enrollments # Coming soon
# client.terms # Coming soon
Resource Operations
Each resource supports:
# List all items
users = await client.users.list()
# List with type-safe filtering (recommended)
active_teachers = await client.users.list(
where={"status": "active", "role": "teacher"}
)
# With operators
teachers_or_aides = await client.users.list(
where={"role": {"in_": ["teacher", "aide"]}}
)
# Not equal
non_deleted = await client.users.list(
where={"status": {"ne": "deleted"}}
)
# Sorting
sorted_users = await client.users.list(
where={"status": "active"},
sort="familyName",
order_by="asc",
)
# Legacy filter string (still supported)
active_users = await client.users.list(filter="status='active'")
# Get by sourcedId
user = await client.users.get("user-id")
# Create (where supported)
await client.classes.create({
"title": "Math 101",
"course": {"sourcedId": "course-id"},
"school": {"sourcedId": "school-id"},
})
# Update (where supported)
await client.classes.update("class-id", {"title": "Math 102"})
# Delete (where supported)
await client.classes.delete("class-id")
Nested Resources
# Schools
classes = await client.schools("school-id").classes()
students = await client.schools("school-id").students()
teachers = await client.schools("school-id").teachers()
courses = await client.schools("school-id").courses()
# Classes
students = await client.classes("class-id").students()
teachers = await client.classes("class-id").teachers()
enrollments = await client.classes("class-id").enrollments()
# Enroll a student
await client.classes("class-id").enroll("student-id", role="student")
# Users
classes = await client.users("user-id").classes()
demographics = await client.users("user-id").demographics()
# Students / Teachers
classes = await client.students("student-id").classes()
classes = await client.teachers("teacher-id").classes()
Filtering
The client supports type-safe filtering with the where parameter:
# Simple equality
users = await client.users.list(where={"status": "active"})
# Multiple conditions (AND)
users = await client.users.list(
where={"status": "active", "role": "teacher"}
)
# Operators
users = await client.users.list(where={"score": {"gte": 90}}) # >=
users = await client.users.list(where={"score": {"gt": 90}}) # >
users = await client.users.list(where={"score": {"lte": 90}}) # <=
users = await client.users.list(where={"score": {"lt": 90}}) # <
users = await client.users.list(where={"status": {"ne": "deleted"}}) # !=
users = await client.users.list(where={"email": {"contains": "@school.edu"}}) # substring
# Match any of multiple values (OR)
users = await client.users.list(
where={"role": {"in_": ["teacher", "aide"]}}
)
# Exclude multiple values
users = await client.users.list(
where={"status": {"not_in": ["deleted", "inactive"]}}
)
# Explicit OR across fields
users = await client.users.list(
where={"OR": [{"role": "teacher"}, {"status": "active"}]}
)
Pagination
For large datasets, use streaming:
# Collect all users
all_users = await client.users.stream().to_list()
# With limits
first_100 = await client.users.stream(max_items=100).to_list()
# With filtering
active_users = await client.users.stream(
where={"status": "active"}
).to_list()
# Get first item only
first_user = await client.users.stream().first()
Configuration
OneRosterClient(
# Environment-based (recommended)
env="production", # or "staging"
client_id="...",
client_secret="...",
# Or explicit URLs
base_url="https://api.example.com",
auth_url="https://auth.example.com/oauth2/token",
client_id="...",
client_secret="...",
# Optional
timeout=30.0, # Request timeout in seconds
)
Environment Variables
If credentials are not provided explicitly, the client reads from:
ONEROSTER_CLIENT_IDONEROSTER_CLIENT_SECRETONEROSTER_BASE_URL(optional)ONEROSTER_TOKEN_URL(optional)
Error Handling
from timeback_oneroster import OneRosterError, NotFoundError, AuthenticationError
try:
user = await client.users.get("invalid-id")
except NotFoundError as e:
print(f"User not found: {e.sourced_id}")
except AuthenticationError:
print("Invalid credentials")
except OneRosterError as e:
print(f"API error: {e}")
Async Context Manager
async with OneRosterClient(client_id="...", client_secret="...") as client:
schools = await client.schools.list()
# Client is automatically closed
FastAPI Integration
from fastapi import FastAPI, Depends
from timeback_oneroster import OneRosterClient
app = FastAPI()
async def get_oneroster():
client = OneRosterClient(
env="production",
client_id="...",
client_secret="...",
)
try:
yield client
finally:
await client.close()
@app.get("/schools")
async def list_schools(client: OneRosterClient = Depends(get_oneroster)):
return await client.schools.list()
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file timeback_oneroster-0.1.1.tar.gz.
File metadata
- Download URL: timeback_oneroster-0.1.1.tar.gz
- Upload date:
- Size: 37.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fd6f9f059bbfb23fde2af56190fb8a45990210d834c60afdd4b5ffee67727885
|
|
| MD5 |
09dd561e212217e9926dd12c334809b6
|
|
| BLAKE2b-256 |
454952fa60e328d731db83d7b36288c4f16fb8c0cd08445d4795b9ebbf2765b8
|
File details
Details for the file timeback_oneroster-0.1.1-py3-none-any.whl.
File metadata
- Download URL: timeback_oneroster-0.1.1-py3-none-any.whl
- Upload date:
- Size: 53.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1a29de7a290d9e3ce163659713727b3e2db2995e468ef03ee0e08b5ee6ba15a7
|
|
| MD5 |
41d2a62a1d0cc540c188bd559722c9fe
|
|
| BLAKE2b-256 |
eb2051809f0f0690c8ca87a1436aae16cf5ca549e1732c7dcae824012d378427
|