Skip to main content

Bazis Permit

PyPI version Python Versions License

An extension package for Bazis that provides a flexible and powerful permission management system with support for roles, permission groups, and granular access control at object and field levels.

Quick Start

# Install the package
uv add bazis-permit

# Create a model with permissions
from bazis.contrib.permit.models_abstract import PermitModelMixin, PermitSelectorMixin
from bazis.core.models_abstract import DtMixin, UuidMixin, JsonApiMixin
from django.db import models

class Organization(PermitSelectorMixin, DtMixin, UuidMixin, JsonApiMixin):
    """Organization - selector source"""
    name = models.CharField(max_length=255)
    
    @classmethod
    def get_selector_for_user(cls, user):
        return user.organization

class Document(PermitModelMixin, DtMixin, UuidMixin, JsonApiMixin):
    """Document with access control"""
    title = models.CharField(max_length=255)
    org_owner = models.ForeignKey(Organization, on_delete=models.CASCADE)

# Create a route with permission checking
from bazis.contrib.permit.routes_abstract import PermitRouteBase
from django.apps import apps

class DocumentRouteSet(PermitRouteBase):
    model = apps.get_model('myapp.Document')

Table of Contents

Description

Bazis Permit is an extension package for the Bazis framework that provides a comprehensive permission management system. The package includes:

  • Role-based model — users have roles, roles contain permission groups
  • Granular control — permissions at object and field levels
  • Selectors — flexible system for linking permissions with objects
  • Automatic checking — built-in permission verification in routes
  • Dynamic schemas — JSON:API schemas adapt to user permissions
  • Caching — efficient permission caching for improved performance
  • Field filtering — visibility restrictions for fields based on permissions

This package requires installation of bazis and bazis-users packages.

Requirements

  • Python: 3.12+
  • bazis: latest version
  • bazis-users: latest version
  • PostgreSQL: 12+
  • Redis: For caching

Installation

uv add bazis-permit

Using pip

pip install bazis-permit

Core Concepts

Permission System Levels

The permission system has the following hierarchy:

User
  └─ role_current (Current role)
       └─ Role
            └─ groups_permission (Permission groups)
                 └─ GroupPermission
                      └─ permissions
                           └─ Permission

1. Permission

The simplest structure, represented as a string in a special format.

Model: bazis.contrib.permit.models.Permission

Examples:

entity.document.item.view.org_owner
entity.document.item.change.author
entity.document.field.view.all.description.enable

2. GroupPermission

A set of permissions grouped by some criterion.

Model: bazis.contrib.permit.models.GroupPermission

Example:

from bazis.contrib.permit.models import GroupPermission, Permission

# Create a permission group for document management
group = GroupPermission.objects.create(
    slug='document_manager',
    name='Document Manager'
)

# Add permissions to the group
group.permissions.add(
    Permission.objects.get_or_create(slug='entity.document.item.add.all')[0],
    Permission.objects.get_or_create(slug='entity.document.item.view.all')[0],
    Permission.objects.get_or_create(slug='entity.document.item.change.author')[0],
)

3. Role

A role includes multiple permission groups.

Model: bazis.contrib.permit.models.Role

Example:

from bazis.contrib.permit.models import Role

# Create a role
role = Role.objects.create(
    slug='manager',
    name='Manager',
    for_anonymous=False  # Not available for anonymous users
)

# Add permission groups
role.groups_permission.add(
    document_group,
    report_group
)

4. User

A user can have multiple roles, but only one role is active.

Model: Must inherit from bazis.contrib.permit.models_abstract.UserPermitMixin

Example:

from bazis.contrib.permit.models_abstract import UserPermitMixin
from bazis.contrib.users.models_abstract import UserAbstract
from bazis.core.models_abstract import DtMixin, UuidMixin, JsonApiMixin

class User(UserPermitMixin, UserAbstract, DtMixin, UuidMixin, JsonApiMixin):
    class Meta:
        verbose_name = 'User'
        verbose_name_plural = 'Users'

# Assign a role to the user
user.roles.add(manager_role)
user.role_current = manager_role
user.save()

Permission Format

General permission format:

LABEL_APP.LABEL_MODEL.PERM_LEVEL.PERM_OPERATION.SELECTOR[.ADDITIONAL]+

Components:

LABEL_APP.LABEL_MODEL

Full model label including the app label.

Examples:

  • entity.document
  • organization.organization
  • facility.facility

PERM_LEVEL

Permission level:

  • item — object level
  • field — object field level (bounded context)

PERM_OPERATION

Operation to which the permission applies.

Basic operations (CrudAccessAction):

  • add — create
  • view — read/view
  • change — update
  • delete — delete
  • check — check (used for validation after create/update)

Custom operation example:

from bazis.core.schemas import AccessAction

class CustomAccessAction(AccessAction):
    APPROVE = 'approve'  # Approve
    REJECT = 'reject'    # Reject

SELECTOR

Name of a relation of the model that links the permission with an object: a foreign key, a many-to-many field or a reverse relation (its related_name) to a selector source (see Selectors), or a path of relations to one (parent__author).

Special selectors:

  • all — permission applies to all objects
  • self — the object is the selector source itself (the user)
  • author — permission applies to objects where the user is the author
  • org_owner — permission applies to objects of the user's organization
  • participants — permission applies to objects where the user is one of the participants (a many-to-many field)

ADDITIONAL

Additional parameters to refine the permission.

Examples:

  1. Field conditions:
entity.document.item.view.author=__selector__&is_active=true

View documents where author = current user AND is_active = true

  1. Field-level restrictions:
entity.document.field.view.all.description.enable

Enable visibility of the description field for all

entity.document.field.view.author=__selector__&is_active=true.__all__.disable

Disable all fields for author's documents with is_active=true

  1. Value filters:
entity.document.field.add.all.name.filter:^[A-Z]+$

The name field must match the regex when creating

entity.parent.field.change.all.child_entities.filter:child_is_active=true

Only items with child_is_active=true can be added to child_entities

Three tools restrict the objects an item references through its relationships:

  • The visibility of the related objects (Bazis 2.7, always on): a created or changed item can reference only the objects the user can view (view permission), and a reverse relation can link and unlink only the objects the user can change (change permission). Other references fail with 403 and the error code ERR_RELATION_ACCESS. Only newly referenced objects are checked, so an unchanged relation to an object that became invisible does not block a change. included shows only the objects the user can view. The core checks this with restrict_queryset of the default route of the related model, PermitRouteBase for a permission-protected model; a route turns it off with relation_targets_check = False. A route without a user (not a UserRouteBase) is checked for the authenticated user of the request if it is known (UserMixin.CTX_USER_REQUEST), otherwise as for an anonymous user. BAZIS_PERMIT_RELATIONS_VIEW_CHECK and PermitRouteBase.relations_view_check are deprecated and will be removed in 3.0: the setting has no effect, relations_view_check = False works as relation_targets_check = False.
  • check permissions are verified on the saved item, whose selectors are filled at that point (also by triggers): entity.extended_entity.item.check.author_parent allows creating and changing extended entities only for the author of the parent entity.
  • filter: field permissions restrict the objects a relation can reference (see above).
  • Filter, sorting and search (Bazis 2.9, BAZIS_FILTERS_STRICT): the filter, sort and search of a request reach only the fields the field permissions show the user (PermitRouteBase.query_fields), on the route and through a relation of another route into the objects of the model; the related objects are restricted by restrict_queryset. A field a view field permission hides from the user in every object of the list is 400 ERR_FILTER (as an unknown field), and a hidden search field is left out of the search. The scope is per user, not per object: a field shown in only some objects (a field permission with a selector such as author), or in an object that no field permission matches (such an object has every field), can be filtered, sorted and searched in all the objects the user sees. Hide such a field from the route (fields) if its values must not be found by trial. The conditions of the permissions themselves (selectors, filter: restrictions) are not restricted.

Permission Cache

The permissions of a role are cached for BAZIS_PERMISSION_CACHE_EXPIRE seconds (7 by default). Any change of roles, permission groups, permissions or their relations invalidates the cache of all roles.

An anonymous user matches no selector: a selector permission (e.g. view.author) of the role for anonymous users allows nothing.

Selectors

Selectors allow linking permissions with specific objects through field values.

Creating a Selector Source

from bazis.contrib.permit.models_abstract import PermitSelectorMixin
from bazis.core.models_abstract import DtMixin, UuidMixin, JsonApiMixin
from django.db import models

class Organization(PermitSelectorMixin, DtMixin, UuidMixin, JsonApiMixin):
    """Organization - selector source"""
    name = models.CharField(max_length=255)
    
    @classmethod
    def get_selector_for_user(cls, user):
        """
        Links user with organization.
        Assumes User has an organization field.
        """
        return user.organization
    
    class Meta:
        verbose_name = 'Organization'
        verbose_name_plural = 'Organizations'

Using Selector in a Model

from bazis.contrib.permit.models_abstract import PermitModelMixin

class Document(PermitModelMixin, DtMixin, UuidMixin, JsonApiMixin):
    """Document with access control"""
    title = models.CharField(max_length=255)
    content = models.TextField()
    org_owner = models.ForeignKey(
        Organization,
        verbose_name='Owner Organization',
        on_delete=models.CASCADE
    )
    
    class Meta:
        verbose_name = 'Document'
        verbose_name_plural = 'Documents'

Creating a Permission with Selector

entity.document.item.view.org_owner

Meaning: User can view documents where org_owner equals user.organization

Many-to-many and Reverse Relations

A selector can also be a many-to-many field of the model or a reverse relation of a selector source, named by its related_name. The object matches when the selector value (the user, his organization...) is one of its related objects:

class Team(PermitModelMixin, DtMixin, UuidMixin, JsonApiMixin):
    name = models.CharField(max_length=255)

class Meeting(PermitModelMixin, AuthorMixin, DtMixin, UuidMixin, JsonApiMixin):
    title = models.CharField(max_length=255)
    participants = models.ManyToManyField(User, related_name='meetings', blank=True)
    team = models.ForeignKey(Team, null=True, on_delete=models.SET_NULL, related_name='meetings')

class User(UserPermitMixin, PermitSelectorMixin, UuidMixin, UserAbstract, JsonApiMixin):
    team = models.ForeignKey(Team, null=True, on_delete=models.SET_NULL, related_name='members')
    teams_watched = models.ManyToManyField(Team, related_name='watchers', blank=True)
entity.meeting.item.view.participants         # many-to-many: the user is a participant
entity.team.item.view.members                 # reverse foreign key: the team of the user
entity.team.item.view.watchers                # reverse many-to-many: the teams he watches
entity.meeting.item.view.team__members        # a path: the meetings of the user's team
entity.meeting.item.change.participants
entity.meeting.item.check.participants        # a change must keep the user a participant
entity.meeting.field.view.participants.description.disable

Such a selector is an EXISTS subquery (<selector>__pk=<value>): an object matches once, whatever the number of its related objects, also combined with other permissions (view.author and view.participants). It works for every operation, the field permissions and the custom operations (the transits of bazis-statusy: app.model.item.transit.participants.<status>.<transit>). The subquery uses the unique index of the automatic through table (object_id, user_id) and the index of a foreign key; a custom through model needs a unique constraint or an index on its two foreign keys. autogen_selectors_fields is not needed for these selectors.

Usage

Creating Models

Selector Source Model

from bazis.contrib.permit.models_abstract import PermitSelectorMixin
from bazis.core.models_abstract import DtMixin, UuidMixin, JsonApiMixin
from django.db import models

class Organization(PermitSelectorMixin, DtMixin, UuidMixin, JsonApiMixin):
    name = models.CharField('Name', max_length=255)
    
    @classmethod
    def get_selector_for_user(cls, user):
        return user.organization
    
    class Meta:
        verbose_name = 'Organization'
        verbose_name_plural = 'Organizations'

Model with Access Control

from bazis.contrib.permit.models_abstract import PermitModelMixin
from bazis.contrib.author.models_abstract import AuthorMixin
from bazis.core.models_abstract import DtMixin, UuidMixin, JsonApiMixin
from django.db import models

class ParentEntity(PermitModelMixin, AuthorMixin, DtMixin, UuidMixin, JsonApiMixin):
    """Parent entity with permissions"""
    # Specify fields for automatic selector generation
    autogen_selectors_fields = ['author']
    
    name = models.CharField('Name', max_length=255)
    description = models.TextField('Description', blank=True)
    is_active = models.BooleanField('Active', default=True)
    price = models.DecimalField('Price', max_digits=10, decimal_places=2)
    
    class Meta:
        verbose_name = 'Parent Entity'
        verbose_name_plural = 'Parent Entities'

Selector Auto-generation:

autogen_selectors_fields — list of fields for which selector fields with GIN indexes will be automatically created. If None — auto-generation is disabled. Many-to-many and reverse relation selectors do not need them (see Many-to-many and Reverse Relations).

Creating Permissions

from bazis.contrib.permit.models import Permission, GroupPermission

# Permissions for ParentEntity
parent_entity_perms = [
    'entity.parent_entity.item.add.all',           # Everyone can create
    'entity.parent_entity.item.view.author',       # Author can view
    'entity.parent_entity.item.change.author',     # Author can edit
]

# Create permission group
group = GroupPermission.objects.create(
    slug='parent_entity_group',
    name='Parent Entity Permissions'
)

for perm_slug in parent_entity_perms:
    perm, created = Permission.objects.get_or_create(slug=perm_slug)
    group.permissions.add(perm)

Creating Roles

from bazis.contrib.permit.models import Role

# Create manager role
manager_role = Role.objects.create(
    slug='manager',
    name='Manager',
    for_anonymous=False
)

# Add permission groups to role
manager_role.groups_permission.add(
    parent_entity_group,
    child_entity_group,
    document_group
)

# Assign role to user
user.roles.add(manager_role)
user.role_current = manager_role
user.save()

Creating Routes

from bazis.contrib.permit.routes_abstract import PermitRouteBase
from bazis.contrib.author.routes_abstract import AuthorRouteBase
from bazis.core.schemas import SchemaFields
from django.apps import apps

class ParentEntityRouteSet(PermitRouteBase, AuthorRouteBase):
    """Route with automatic permission checking"""
    model = apps.get_model('entity.ParentEntity')
    
    fields = {
        None: SchemaFields(
            include={
                'extended_entity': None,
                'dependent_entities': None,
            },
        ),
    }

What PermitRouteBase provides:

  1. Automatic permission checking for every CRUD action
  2. Dynamic schemas — fields are automatically hidden/shown based on permissions
  3. QuerySet filtering — user sees only objects they have access to
  4. Meta-fields:
    • for_change — list of IDs available for editing
    • for_delete — list of IDs available for deletion
    • for_create — create availability
    • crud_actions — list of available actions for the object

Access Checking

Automatic Checking

In routes inheriting from PermitRouteBase, access checking happens automatically when accessing self.schemas.

Manual Checking in Custom Actions

from bazis.core.schemas import CrudAccessAction
from django.apps import apps

class DocumentRouteSet(PermitRouteBase):
    model = apps.get_model('myapp.Document')
    
    @http_post('/{item_id}/sign/')
    def action_sign(self, item_id: str):
        """Sign document"""
        document = self.set_item(item_id)
        
        # Check: can user view the document
        self.check_access(CrudAccessAction.VIEW, document)
        
        # Check: can user create signature
        self.check_access(
            CrudAccessAction.ADD,
            apps.get_model('document.Signature')
        )
        
        # Signing logic
        signature = Signature.objects.create(
            document=document,
            user=self.inject.user
        )
        
        return {'status': 'signed', 'signature_id': str(signature.id)}

Examples

Example 1: Basic Permission Setup

from bazis.contrib.permit.models import Permission, GroupPermission, Role

# Create permissions
permissions = {
    'document_add': 'myapp.document.item.add.all',
    'document_view_own': 'myapp.document.item.view.author',
    'document_view_org': 'myapp.document.item.view.org_owner',
    'document_change_own': 'myapp.document.item.change.author',
    'document_delete_own': 'myapp.document.item.delete.author',
}

# Create group for regular users
user_group = GroupPermission.objects.create(
    slug='document_user',
    name='Document User'
)

for perm_slug in ['document_add', 'document_view_own', 'document_change_own']:
    perm, _ = Permission.objects.get_or_create(slug=permissions[perm_slug])
    user_group.permissions.add(perm)

# Create group for administrators
admin_group = GroupPermission.objects.create(
    slug='document_admin',
    name='Document Administrator'
)

for perm_slug in permissions.values():
    perm, _ = Permission.objects.get_or_create(slug=perm_slug)
    admin_group.permissions.add(perm)

# Create roles
user_role = Role.objects.create(slug='user', name='User')
user_role.groups_permission.add(user_group)

admin_role = Role.objects.create(slug='admin', name='Administrator')
admin_role.groups_permission.add(admin_group)

Example 2: Complex Permissions with Conditions

# Permissions with field conditions
complex_perms = [
    # Can only create with name "Test name"
    'entity.parent_entity.item.check.author=__selector__&name=Test name',
    
    # Can view own objects
    'entity.parent_entity.item.view.author=__selector__',
    
    # Can edit only inactive own objects
    'entity.parent_entity.item.change.author=__selector__&is_active=false',
    
    # Description field visible only for active own objects
    'entity.parent_entity.field.view.author=__selector__&is_active=true.description.enable',
    
    # All other fields hidden for active own objects
    'entity.parent_entity.field.view.author=__selector__&is_active=true.__all__.disable',
    
    # Description field hidden for inactive own objects
    'entity.parent_entity.field.view.author=__selector__&is_active=false.description.disable',
]

group = GroupPermission.objects.create(
    slug='complex_permissions',
    name='Complex Permissions'
)

for perm_slug in complex_perms:
    perm, _ = Permission.objects.get_or_create(slug=perm_slug)
    group.permissions.add(perm)

Example 3: Field Filters

# Field value filters
field_filter_perms = [
    # child_name field must match regex when creating
    'entity.child_entity.field.add.all.child_name.filter:^[A-Z]+$',
    
    # Only active items can be added to child_entities
    'entity.parent_entity.field.add.all.child_entities.filter:child_is_active=true',
    
    # child_name must match regex when changing
    'entity.child_entity.field.change.all.child_name.filter:^[A-Z]+$',
    
    # Only active items can be added to child_entities when changing
    'entity.parent_entity.field.change.all.child_entities.filter:child_is_active=true',
    
    # Only active parents can be set in parent_entity
    'entity.dependent_entity.field.add.all.parent_entity.filter:is_active=true',
    'entity.dependent_entity.field.change.all.parent_entity.filter:is_active=true',
]

group = GroupPermission.objects.create(
    slug='field_filters',
    name='Field Filters'
)

for perm_slug in field_filter_perms:
    perm, _ = Permission.objects.get_or_create(slug=perm_slug)
    group.permissions.add(perm)

Example 4: Access Checking in Tests

import pytest
from bazis.contrib.permit.models import Permission, GroupPermission, Role
from bazis.contrib.users import get_user_model

User = get_user_model()

@pytest.mark.django_db
def test_permissions(sample_app):
    # Create users
    user_1 = User.objects.create_user('user1', email='user1@example.com', password='pass')
    user_2 = User.objects.create_user('user2', email='user2@example.com', password='pass')
    
    # Create permission group
    group = GroupPermission.objects.create(slug='test_group', name='Test Group')
    group.permissions.add(
        Permission.objects.get_or_create(slug='entity.document.item.add.all')[0],
        Permission.objects.get_or_create(slug='entity.document.item.view.author')[0],
        Permission.objects.get_or_create(slug='entity.document.item.change.author')[0],
    )
    
    # Create role and assign to user
    role = Role.objects.create(slug='user_role', name='User Role')
    role.groups_permission.add(group)
    user_1.roles.add(role)
    user_1.role_current = role
    user_1.save()
    
    # user_1 can create documents
    response = get_api_client(sample_app, user_1.jwt_build()).post(
        '/api/v1/documents/document/',
        json_data={
            'data': {
                'type': 'myapp.document',
                'bs:action': 'add',
                'attributes': {'title': 'New Document'}
            }
        }
    )
    assert response.status_code == 201
    doc_id = response.json()['data']['id']
    
    # user_1 can view their own document
    response = get_api_client(sample_app, user_1.jwt_build()).get(
        f'/api/v1/documents/document/{doc_id}/'
    )
    assert response.status_code == 200
    
    # user_2 cannot view another user's document
    response = get_api_client(sample_app, user_2.jwt_build()).get(
        f'/api/v1/documents/document/{doc_id}/'
    )
    assert response.status_code == 403

Example 5: Client Usage

class PermitClient {
  constructor(apiUrl, token) {
    this.apiUrl = apiUrl;
    this.token = token;
  }

  async getSchemaForAction(resource, action, itemId = null) {
    let url = `${this.apiUrl}/${resource}/`;
    
    if (itemId) {
      url += `${itemId}/schema_${action}/`;
    } else {
      url += `schema_${action}/`;
    }
    
    const response = await fetch(url, {
      headers: {
        'Authorization': `Bearer ${this.token}`
      }
    });
    
    if (response.ok) {
      return await response.json();
    }
    
    throw new Error('Cannot get schema');
  }

  async canPerformAction(resource, action, itemId = null) {
    try {
      await this.getSchemaForAction(resource, action, itemId);
      return true;
    } catch {
      return false;
    }
  }

  async getAvailableActions(resource, itemId) {
    const response = await fetch(
      `${this.apiUrl}/${resource}/${itemId}/`,
      {
        headers: {
          'Authorization': `Bearer ${this.token}`
        }
      }
    );
    
    if (response.ok) {
      const data = await response.json();
      return data.meta.crud_actions || [];
    }
    
    return [];
  }
}

// Usage
const client = new PermitClient('http://api.example.com/api/v1', jwtToken);

// Check if action is possible
const canEdit = await client.canPerformAction('documents/document', 'update', docId);

if (canEdit) {
  console.log('User can edit this document');
}

// Get available actions
const actions = await client.getAvailableActions('documents/document', docId);
console.log('Available actions:', actions);
// ['view', 'change', 'delete']

License

Apache License 2.0

See LICENSE file for details.

Support

If you have questions or issues:


Made with ❤️ by the Bazis team

Metadata

Release files for bazis-permit 2.7.1

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

Source distribution (sdist)

Source distribution for bazis-permit 2.7.1
File Size Uploaded
bazis_permit-2.7.1.tar.gz 177.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for bazis-permit 2.7.1
File Interpreter ABI Platform
bazis_permit-2.7.1-py3-none-any.whl Python 3 none any Details

Total release size: 236.4 kB

Release files / bazis_permit-2.7.1.tar.gz

Download URL bazis_permit-2.7.1.tar.gz
Size 177.9 kB
Tags Source
SHA-256 checksum
How to use checksums
84383051c26144daf8d74dd83d438362dd69371915218871d4f360739c043e06
BLAKE2b-256 checksum
How to use checksums
561417edd234254d19365dea829793a921e14cccee5ee06f2c196cfa2da9b24b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.24 {"installer":{"name":"uv","version":"0.12.24","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}

Release files / bazis_permit-2.7.1-py3-none-any.whl

Download URL bazis_permit-2.7.1-py3-none-any.whl
Size 58.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2d0342df0f1be47947919cfca4e7e0c758030143931f86f587599a96a6f85081
BLAKE2b-256 checksum
How to use checksums
9033932899999e0ba77b4c8d735ba02506cda5ade4807d98333bc004e63aa868
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.24 {"installer":{"name":"uv","version":"0.12.24","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}

Release history Release notifications | RSS feed

2.9.1

2 release files

2.9.0

2 release files

2.8.0

2 release files

This release

2.7.1 This release

2 release files

2.7.0

2 release files

2.6.0

2 release files

2.5.0

2 release files

2.4.1

2 release files

2.4.0

2 release files

2.3.1

2 release files

2.3.0

2 release files

2.2.3

2 release files

2.2.2

2 release files

2.2.1

2 release files

2.2.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