Skip to content

Commit

Permalink
Fixed sync multithreading (#107)
Browse files Browse the repository at this point in the history
* Fixed sync multithreading

* Changed tests workflow to GH Actions services

* Fixed import

* Fixed bulk test

* Fixed test resources

* Fixed tests

* Added xfail

* Added xfail
  • Loading branch information
danyi1212 authored Dec 23, 2024
1 parent 65de845 commit 8fcda46
Show file tree
Hide file tree
Showing 14 changed files with 614 additions and 632 deletions.
17 changes: 8 additions & 9 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ jobs:
pytest:
runs-on: ubuntu-latest
name: pytest
services:
pdp:
image: permitio/pdp-v2:latest
ports:
- 7766:7000
env:
PDP_API_KEY: ${{ secrets.PROJECT_API_KEY }}
PDP_DEBUG: true
steps:
- name: Checkout code
uses: actions/checkout@v4
Expand All @@ -26,9 +34,6 @@ jobs:
with:
python-version: '3.11.8'

- name: Install Docker
uses: docker-practice/actions-setup-docker@master

- name: Creation env ${{ env.ENV_NAME }} under 'Permit.io Tests' workspace
run: |
response=$(curl -X POST \
Expand Down Expand Up @@ -56,12 +61,6 @@ jobs:
echo "New env api key: $ENV_API_KEY"
- name: local PDP runnning
env:
PDP_API_KEY: ${{ env.ENV_API_KEY }}
PERMIT_API_KEY: ${{ env.ENV_API_KEY }}
run: docker run -d -p 7766:7000 --env PDP_API_KEY=${{ env.ENV_API_KEY }} --env PDP_DEBUG=true permitio/pdp-v2:latest

- name: Test with pytest
env:
PDP_URL: http://localhost:7766
Expand Down
6 changes: 6 additions & 0 deletions permit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@
UserInput,
)
from .exceptions import (
PermitAlreadyExistsError,
PermitApiDetailedError,
PermitApiError,
PermitConnectionError,
PermitContextChangeError,
PermitContextError,
PermitError,
PermitException,
PermitNotFoundError,
PermitValidationError,
)
from .permit import Permit
from .utils.context import Context
38 changes: 22 additions & 16 deletions permit/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import aiohttp
from loguru import logger
from pydantic.v1 import ValidationError
from typing_extensions import deprecated

from permit import ErrorDetails, HTTPValidationError
Expand Down Expand Up @@ -126,8 +127,8 @@ class PermitValidationError(PermitApiError):
Validation error response from the Permit API.
"""

def __init__(self, response: aiohttp.ClientResponse, body: dict):
self._content = HTTPValidationError.parse_obj(body)
def __init__(self, response: aiohttp.ClientResponse, content: HTTPValidationError, body: dict):
self._content = content
super().__init__(response, body)

def _get_message(self) -> str:
Expand All @@ -148,8 +149,8 @@ class PermitApiDetailedError(PermitApiError):
Detailed error response from the Permit API.
"""

def __init__(self, response: aiohttp.ClientResponse, body: dict):
self._content = ErrorDetails.parse_obj(body)
def __init__(self, response: aiohttp.ClientResponse, content: ErrorDetails, body: dict):
self._content = content
super().__init__(response, body)

def _get_message(self) -> str:
Expand Down Expand Up @@ -211,21 +212,26 @@ async def handle_api_error(response: aiohttp.ClientResponse):
text = await response.text()
raise PermitApiError(response, {"details": text}) from e

try:
if response.status == 422:
raise PermitValidationError(response, json)
elif response.status == 409:
raise PermitAlreadyExistsError(response, json)
elif response.status == 404:
raise PermitNotFoundError(response, json)
if response.status == 422:
try:
validation_content = HTTPValidationError.parse_obj(json)
except ValidationError as e:
raise PermitApiError(response, json) from e
else:
raise PermitApiDetailedError(response, json)
except PermitApiError as e:
raise e
except Exception as e:
logger.exception(f"Failed to create specific error class for status {response.status}: {e}")
raise PermitValidationError(response, validation_content, json)

try:
content = ErrorDetails.parse_obj(json)
except ValidationError as e:
raise PermitApiError(response, json) from e

if response.status == 409:
raise PermitAlreadyExistsError(response, content, json)
elif response.status == 404:
raise PermitNotFoundError(response, content, json)
else:
raise PermitApiDetailedError(response, content, json)


def handle_client_error(func):
@functools.wraps(func)
Expand Down
15 changes: 14 additions & 1 deletion permit/utils/sync.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import asyncio
import threading
from asyncio import iscoroutinefunction
from functools import wraps
from typing import Any, Awaitable, Callable, Coroutine, TypeVar
Expand All @@ -9,10 +10,22 @@
T = TypeVar("T")


def run_coroutine_sync(coroutine: Coroutine[Any, Any, T]) -> T:
try:
loop = asyncio.get_running_loop()
except RuntimeError:
return asyncio.run(coroutine)

if threading.current_thread() is threading.main_thread():
return loop.run_until_complete(coroutine)
else:
return asyncio.run_coroutine_threadsafe(coroutine, loop).result()


def async_to_sync(func: Callable[P, Coroutine[Any, Any, T]]) -> Callable[P, T]:
@wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
return asyncio.run(func(*args, **kwargs))
return run_coroutine_sync(func(*args, **kwargs))

return wrapper

Expand Down
152 changes: 74 additions & 78 deletions tests/endpoints/test_bulk_operations.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import pytest
import uuid

from loguru import logger
from tests.utils import handle_api_error

from permit import Permit, RoleCreate, TenantCreate, UserCreate
from permit.api.models import (
ResourceCreate,
ResourceInstanceCreate,
RoleAssignmentCreate,
)
from permit.exceptions import PermitApiError, PermitConnectionError
from permit.exceptions import PermitAlreadyExistsError

# Schema ----------------------------------------------------------------
EDITOR = "editor"
Expand Down Expand Up @@ -45,31 +45,30 @@
},
)


USER_A = UserCreate(
key="auth0|asaf",
key=str(uuid.uuid4()),
email="asaf@permit.io",
first_name="Asaf",
last_name="Cohen",
attributes={"age": 35},
)
USER_B = UserCreate(
key="auth0|john",
key=str(uuid.uuid4()),
email="john@permit.io",
first_name="John",
last_name="Doe",
attributes={"age": 27},
)
USER_C = UserCreate(
key="auth0|jane",
key=str(uuid.uuid4()),
email="jane@permit.io",
first_name="Jane",
last_name="Doe",
attributes={"age": 25},
)

TENANT_1 = TenantCreate(key="ten1", name="Tenant 1")
TENANT_2 = TenantCreate(key="ten2", name="Tenant 2")
TENANT_1 = TenantCreate(key=str(uuid.uuid4()), name="Tenant 1")
TENANT_2 = TenantCreate(key=str(uuid.uuid4()), name="Tenant 2")

ADMIN = RoleCreate(
key="admin",
Expand All @@ -88,19 +87,19 @@

ACC1 = ResourceInstanceCreate(
resource=ACCOUNT.key,
key="acc1",
key=str(uuid.uuid4()),
tenant=TENANT_1.key,
)

ACC2 = ResourceInstanceCreate(
resource=ACCOUNT.key,
key="acc2",
key=str(uuid.uuid4()),
tenant=TENANT_1.key,
)

ACC3 = ResourceInstanceCreate(
resource=ACCOUNT.key,
key="acc3",
key=str(uuid.uuid4()),
tenant=TENANT_2.key,
)

Expand Down Expand Up @@ -137,101 +136,98 @@


async def test_bulk_operations(permit: Permit):
## create resource and global role ------------------------------------
try:
## create resource and global role ------------------------------------
resource = await permit.api.resources.create(ACCOUNT)
assert resource is not None
assert resource.key == ACCOUNT.key
except PermitAlreadyExistsError:
logger.info("Account resource already exists...")

try:
role = await permit.api.roles.create(ADMIN)
assert role is not None
assert role.key == ADMIN.key
except PermitAlreadyExistsError:
logger.info("Admin role already exists...")

## bulk create tenants ------------------------------------

# initial number of tenants
tenants = await permit.api.tenants.list()
len_tenants_original = len(tenants)
## bulk create tenants ------------------------------------

# create tenants in bulk
await permit.api.tenants.bulk_create(CREATED_TENANTS)
# initial number of tenants
tenants = await permit.api.tenants.list()
len_tenants_original = len(tenants)

# check increased number of tenants
tenants = await permit.api.tenants.list()
assert len(tenants) == len_tenants_original + len(CREATED_TENANTS)
# create tenants in bulk
await permit.api.tenants.bulk_create(CREATED_TENANTS)

## bulk create users ------------------------------------
# check increased number of tenants
tenants = await permit.api.tenants.list()
assert len(tenants) == len_tenants_original + len(CREATED_TENANTS)

# initial number of users
users = (await permit.api.users.list()).data
len_users_original = len(users)
## bulk create users ------------------------------------

# create users in bulk
await permit.api.users.bulk_create(CREATED_USERS)
# initial number of users
users = (await permit.api.users.list()).data
len_users_original = len(users)

# check increased number of users
users = (await permit.api.users.list()).data
assert len(users) == len_users_original + len(CREATED_USERS)
# create users in bulk
await permit.api.users.bulk_create(CREATED_USERS)

## bulk create resource instances ------------------------------------
# initial number of users
instances = await permit.api.resource_instances.list()
len_instances_original = len(instances)
# check increased number of users
users = (await permit.api.users.list()).data
assert len(users) == len_users_original + len(CREATED_USERS)

# create instances in bulk (keep one to create implicitly by role assignment)
await permit.api.resource_instances.bulk_replace(CREATED_RESOURCE_INSTANCES[:-1])
## bulk create resource instances ------------------------------------
# initial number of users
instances = await permit.api.resource_instances.list()
len_instances_original = len(instances)

# check increased number of instances
instances = await permit.api.resource_instances.list()
assert len(instances) == len_instances_original + len(CREATED_RESOURCE_INSTANCES) - 1
# create instances in bulk (keep one to create implicitly by role assignment)
await permit.api.resource_instances.bulk_replace(CREATED_RESOURCE_INSTANCES[:-1])

## bulk create role assignments ------------------------------------
# check increased number of instances
instances = await permit.api.resource_instances.list()
assert len(instances) == len_instances_original + len(CREATED_RESOURCE_INSTANCES) - 1

# initial number of role assignments
assignments = await permit.api.role_assignments.list()
len_assignments_original = len(assignments)
## bulk create role assignments ------------------------------------

# create role assignments in bulk
await permit.api.role_assignments.bulk_assign(CREATED_ASSIGNMENTS)
# initial number of role assignments
assignments = await permit.api.role_assignments.list()
len_assignments_original = len(assignments)

# check increased number of role assignments
assignments = await permit.api.role_assignments.list()
assert len(assignments) == len_assignments_original + len(CREATED_ASSIGNMENTS)
# create role assignments in bulk
await permit.api.role_assignments.bulk_assign(CREATED_ASSIGNMENTS)

# check that instance created implicitly
instances = await permit.api.resource_instances.list()
assert len(instances) == len_instances_original + len(CREATED_RESOURCE_INSTANCES)
# check increased number of role assignments
assignments = await permit.api.role_assignments.list()
assert len(assignments) == len_assignments_original + len(CREATED_ASSIGNMENTS)

## bulk delete resource instances -----------------------------------
await permit.api.resource_instances.bulk_delete(
[f"{inst.resource}:{inst.key}" for inst in CREATED_RESOURCE_INSTANCES]
)
# check that instance created implicitly
instances = await permit.api.resource_instances.list()
assert len(instances) == len_instances_original + len(CREATED_RESOURCE_INSTANCES)

instances = await permit.api.resource_instances.list()
assert len(instances) == len_instances_original
## bulk delete resource instances -----------------------------------
await permit.api.resource_instances.bulk_delete(
[f"{inst.resource}:{inst.key}" for inst in CREATED_RESOURCE_INSTANCES]
)

assignments = await permit.api.role_assignments.list()
assert len(assignments) == len_assignments_original + 1 # (tenant role)
instances = await permit.api.resource_instances.list()
assert len(instances) == len_instances_original

## bulk delete users -----------------------------------
await permit.api.users.bulk_delete([user.key for user in CREATED_USERS])
assignments = await permit.api.role_assignments.list()
assert len(assignments) == len_assignments_original + 1 # (tenant role)

users = (await permit.api.users.list()).data
assert len(users) == len_users_original
## bulk delete users -----------------------------------
await permit.api.users.bulk_delete([user.key for user in CREATED_USERS])

assignments = await permit.api.role_assignments.list()
assert len(assignments) == len_assignments_original
users = (await permit.api.users.list()).data
assert len(users) == len_users_original

## bulk delete tenants -----------------------------------
await permit.api.tenants.bulk_delete([tenant.key for tenant in CREATED_TENANTS])
assignments = await permit.api.role_assignments.list()
assert len(assignments) == len_assignments_original + 1 # (tenant role)

tenants = await permit.api.tenants.list()
assert len(tenants) == len_tenants_original
## bulk delete tenants -----------------------------------
await permit.api.tenants.bulk_delete([tenant.key for tenant in CREATED_TENANTS])

except PermitApiError as error:
handle_api_error(error, "Got API Error")
except PermitConnectionError:
raise
except Exception as error: # noqa: BLE001
logger.error(f"Got error: {error}")
pytest.fail(f"Got error: {error}")
tenants = await permit.api.tenants.list()
assert len(tenants) == len_tenants_original
Loading

0 comments on commit 8fcda46

Please sign in to comment.