Skip to content

Commit

Permalink
Session lock (#4)
Browse files Browse the repository at this point in the history
* AvlEventHandler async context fix
* Added remove_avl_event_handler method
* Added exclusive async locker for long operations
  • Loading branch information
o-murphy authored Aug 13, 2024
1 parent 89d2d79 commit 2d4d46f
Show file tree
Hide file tree
Showing 13 changed files with 308 additions and 131 deletions.
153 changes: 78 additions & 75 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,17 +146,19 @@ async def upload_driver_image():
### Shortcuts
Shortcuts are available as efficient solutions for some common actions, like .wlp export

```python
from aiowialon import Wialon
from aiowialon.utils.shortcuts import WLP
from aiowialon.shortcuts import WLP

wialon = Wialon(token=TOKEN)


async def dump_unit(item_id):
await wialon.login()
wlp = await WLP.export_item(wialon, item_id)
with open(f"{id}.wlp", 'wb') as fp:
fp.write(wlp)
await wialon.login()
wlp = await WLP.export_item(wialon, item_id)
with open(f"{id}.wlp", 'wb') as fp:
fp.write(wlp)
```

## Wialon Events
Expand Down Expand Up @@ -228,18 +230,21 @@ Use `@wialon.avl_event_handler()` decorator
```python
from aiowialon import AvlEvent


@wialon.avl_event_handler()
async def unit_event(event: AvlEvent):
print("Handler got event:", event)
print("Handler got event:", event)
```

Put the filter function to the decorator to apply filtering of AVL events

```python
from aiowialon import AvlEvent


@wialon.avl_event_handler(lambda event: event.data.i == 734455)
async def unit_734455_event(event: AvlEvent):
print("Handler got event from item 734455:", event)
print("Handler got event from item 734455:", event)
```
> [!NOTE]
> Register handlers in an order in which filters have to be applied. If some handler catched the event, next handler in order will never do.
Expand All @@ -254,14 +259,15 @@ So if u want to handle some specific WialonError, do it in handler's callback sc
```python
from aiowialon import WialonError, WialonAccessDenied


@wialon.avl_event_handler()
async def unit_event(event: AvlEvent):
try:
raise WialonAccessDenied # example of wialon exception raised in callback scope
except WialonError as err:
# do something
pass
try:
raise WialonAccessDenied # example of wialon exception raised in callback scope
except WialonError as err:
# do something
pass

```

### Exceptions Handling (Batch)
Expand Down Expand Up @@ -309,24 +315,61 @@ wialon = Wialon() # set custom requests per second limit
wialon.start_polling(token=TOKEN, logout_finally=False)
```

### Debugging
Enable debug messages for `aiowialon` and `aiohttp`
### Critical requests execution
Some requests to services like `Render`, `Reports`, `Messages` requires blocking other requests to be executed together per single session.
* Use the `@wialon.lock_session` decorator to block async loop till your operation done
* You can apply `@wialon.session_lock` also for handlers
* You can use `@wialon.session_lock` inside the methods when [inheriting Wialon](#extending-aio-wialon)

```python
import logging
from aiowialon import Wialon, WialonError, flags, AvlEvent
logging.basicConfig(level=logging.DEBUG)
import asyncio
from functools import wraps

from aiowialon import Wialon

wialon = Wialon(token=TOKEN)

@wialon.session_lock
async def critical_method(self, params1, params2):
# For example: execute and export report
previous_request_timeout = self.timeout # Store current timeout
try:
self.timeout = 600 # Setup request timeout up to 10 minutes
await self.report_exec_report(**params1)
self.timeout = previous_request_timeout # Return previous timeout
report_result = await self.export_result(**params2)
return report_result
finally:
self.timeout = previous_request_timeout # Return previous timeout
await self.report_cleanup_result()
```

With handlers:

```python
@wialon.avl_event_handler(lambda event: event.data.i == 734455)
@wialon.session_lock
async def unit_event(event: AvlEvent):
print("Handler got event:", event)
# simulating long operation
for i in range(5):
print("Waiting lock release", i)
await asyncio.sleep(1)
```


### Extending AIO Wialon
Inherit from `Wialon` class to add your custom logic and behaviour
You can directly use `Wialon.request` to make requests to special endpoints
* You can directly use `Wialon.request` to make requests to special endpoints
* You can use `@wialon.session_lock` inside the methods when inheriting Wialon


```python
import json
import asyncio
from aiowialon import Wialon


class WialonWithGeocode(Wialon):
class CustomWialon(Wialon):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.__geocode_url = f"{kwargs.get('scheme', 'https')}://geocode-maps.wialon.com/{self.__base_url}/gis_geocode"
Expand All @@ -337,65 +380,25 @@ class WialonWithGeocode(Wialon):
... # other fields
}
return await self.request('geocode_fetch', self.__geocode_url, payload=json.dumps(payload))
```

### Critical requests execution
Some requests to services like `Render`, `Reports`, `Messages` requires blocking other requests to be executed together per single session.
Use asyncio lock globally for `Wialon` instance to execute critical requests.
async def critical_method(self):
@self.session_lock
async def locked_task():
# simulating long operation
for i in range(5):
print("Waiting lock release", i)
await asyncio.sleep(1)
return await locked_task()
```

### Debugging
Enable debug messages for `aiowialon` and `aiohttp`
```python
import asyncio
from functools import wraps

from aiowialon import Wialon


class WialonWithLock(Wialon):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._session_lock = asyncio.Lock()
self._session_lock_event = asyncio.Event()
self._session_lock_event.set()

def session_lock(self):
def decorator(func):
@wraps(func)
async def wrapper(self, *args, **kwargs):
async with self._session_lock:
# Clear the event to indicate that critical_method is running
self._session_lock_event.clear()
try:
result = await func(self, *args, **kwargs)
finally:
# Set the event to signal that critical_method is complete
self._session_lock_event.set()
return result

return wrapper

return decorator

async def call(self, *args, **params):
# Ensure that the method waits until critical_method completes
await self._session_lock_event.wait()
return await super().call(*args, **params)

@session_lock()
async def critical_method(self, params1, params2):
# For example: execute and export report
previous_request_timeout = self.timeout # Store current timeout
try:
self.timeout = 600 # Setup request timeout up to 10 minutes
await self.report_exec_report(**params1)
self.timeout = previous_request_timeout # Return previous timeout
report_result = await self.export_result(**params2)
return report_result
finally:
self.timeout = previous_request_timeout # Return previous timeout
await self.report_cleanup_result()
import logging
from aiowialon import Wialon, WialonError, flags, AvlEvent
logging.basicConfig(level=logging.DEBUG)
```


> [!WARNING]
> ### RISK NOTICE
> THE CODE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
Expand Down
3 changes: 2 additions & 1 deletion aiowialon/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@
"Mike Turchunovich",
"Dmytro Yaroshenko",
]
__version__ = "1.3.1"
__version__ = "1.3.2"

from aiowialon.logger import *
from aiowialon.utils import *
from aiowialon.types import *
from aiowialon.exceptions import *
from aiowialon.api import *
from aiowialon.validators import *
from aiowialon.shortcuts import *
Loading

0 comments on commit 2d4d46f

Please sign in to comment.