Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed sync multithreading #107

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading