Skip to content

Commit

Permalink
🔖 release: v0.2.9 (#36)
Browse files Browse the repository at this point in the history
## Bug fixes

* Fix error when using login() method in async function [#34]
* Fix and improve error message in fs.decode (async) [#35]
  • Loading branch information
Daxexs authored Jan 26, 2025
2 parents 0812045 + 90e1fd5 commit 2a6a795
Show file tree
Hide file tree
Showing 11 changed files with 159 additions and 56 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Flet-Easy changelog

## v0.2.9 (25/01/25)

### Bug fixes

* Fix error when using login() method in async function ([#34](https://github.com/Daxexs/flet-easy/issues/34))
* Fix and improve error message in fs.decode (async) ([#35](https://github.com/Daxexs/flet-easy/issues/35))

## v0.2.8 (21/12/24)

* Support for `Flet>=0.25.*`.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "flet-easy"
version = "0.2.8"
version = "0.2.9"
description = "⚡Flet-Easy is a user-friendly add-on package for Flet, offering a cleaner code structure with numerous customizable features like JWT, routers, decorators, middleware and more."
authors = [{ name = "Daxexs", email = "[email protected]" }]

Expand Down
6 changes: 4 additions & 2 deletions src/flet_easy/cli/main.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import argparse
import contextlib

from flet_easy.exceptions import FletEasyError

try:
from cookiecutter.main import cookiecutter
from rich_argparse import RichHelpFormatter
except ImportError:
raise Exception('To use the cli (fs) Install: "pip install flet-easy[all] --upgrade"')
raise FletEasyError('To use the cli (fs) Install: "pip install flet-easy[all] --upgrade"')

VERSION = "0.2.8"
VERSION = "0.2.9"

RichHelpFormatter.styles["argparse.text"] = "italic"
RichHelpFormatter.styles["argparse.help"] = "light_sky_blue3"
Expand Down
84 changes: 62 additions & 22 deletions src/flet_easy/datasy.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@

from flet import Page

from flet_easy.exceptions import LoginError
from flet_easy.extra import Msg, Redirect
from flet_easy.extrasJwt import (
SecretKey,
_decode_payload_async,
_decode_payload,
encode_verified,
)
from flet_easy.inheritance import Keyboardsy, Resizesy, SessionStorageEdit, Viewsy
Expand Down Expand Up @@ -56,7 +57,6 @@ def __init__(
auto_logout: bool,
page_on_keyboard: Keyboardsy,
page_on_resize: Resizesy,
login_async: bool = False,
go: Callable[[str], None] = None,
) -> None:
self.__page: Page = page
Expand All @@ -77,7 +77,6 @@ def __init__(
self.__sleep: int = 1
self._key_login: str = None
self._login_done: bool = False
self._login_async: bool = login_async

@property
def page(self):
Expand Down Expand Up @@ -242,9 +241,8 @@ async def __logaut_init(self, topic, msg: Msg):
elif msg.method == "updateLoginSessions":
self._login_done = msg.value
self._create_task_login_update(
decode=await _decode_payload_async(
page=self.page,
key_login=self.key_login,
decode=_decode_payload(
jwt=await self.page.client_storage.get_async(self.key_login),
secret_key=(
self.secret_key.secret
if self.secret_key.secret is not None
Expand Down Expand Up @@ -275,24 +273,14 @@ def _create_tasks(self, time_expiry: timedelta, key: str, sleep: int) -> None:
sleep_time=sleep,
).start()

def login(
def __login(
self,
key: str,
value: Dict[str, Any] | Any,
next_route: str,
time_expiry: timedelta = None,
sleep: int = 1,
):
"""Registering in the client's storage the key and value in all browser sessions.
### Parameters to use:
* `key` : It is the identifier to store the value in the client storage.
* `value` : Recommend to use a dict if you use JWT.
* `next_route` : Redirect to next route after creating login.
* `time_expiry` : Time to expire the session, use the `timedelta` class to configure. (Optional)
* `sleep` : Time to do login checks, default is 1s. (Optional)
"""
) -> str | None:
if time_expiry:
assert isinstance(
value, Dict
Expand All @@ -308,18 +296,70 @@ def login(
value = encode_verified(self.secret_key, value, time_expiry)
self._login_done = True

if self.__auto_logout:
self._create_tasks(time_expiry, key, sleep)

self.page.run_task(self.page.client_storage.set_async, key, value).result()
if self.__auto_logout:
self._create_tasks(time_expiry, key, sleep)

if self.page.web:
self.page.pubsub.send_others_on_topic(
self.page.client_ip + self.page.client_user_agent,
Msg("login", key, {"value": value, "next_route": next_route}),
)

return value

def login(
self,
key: str,
value: Dict[str, Any] | Any,
next_route: str,
time_expiry: timedelta = None,
sleep: int = 1,
):
"""Registering in the client's storage the key and value in all browser sessions.
### Parameters to use:
* `key` : It is the identifier to store the value in the client storage.
* `value` : Recommend to use a dict if you use JWT.
* `next_route` : Redirect to next route after creating login.
* `time_expiry` : Time to expire the session, use the `timedelta` class to configure. (Optional)
* `sleep` : Time to do login checks, default is 1s. (Optional)
"""
value = self.__login(key, value, next_route, time_expiry, sleep)

try:
self.page.client_storage.set(key, value)
except TimeoutError:
raise LoginError(
"The operation has timed out. Please use 'login_async()' instead of 'login()'."
)

self.__go(next_route)

async def login_async(
self,
key: str,
value: Dict[str, Any] | Any,
next_route: str,
time_expiry: timedelta = None,
sleep: int = 1,
):
"""Registering in the client's storage the key and value in all browser sessions.
* This method is asynchronous.
### Parameters to use:
* `key` : It is the identifier to store the value in the client storage.
* `value` : Recommend to use a dict if you use JWT.
* `next_route` : Redirect to next route after creating login.
* `time_expiry` : Time to expire the session, use the `timedelta` class to configure. (Optional)
* `sleep` : Time to do login checks, default is 1s. (Optional)
"""

value = self.__login(key, value, next_route, time_expiry, sleep)
await self.page.client_storage.set_async(key, value)
self.page.run_thread(self.__go, next_route)

""" Page go """

def go(self, route: str):
Expand Down
46 changes: 46 additions & 0 deletions src/flet_easy/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
class FletEasyError(Exception):
"""FletEasy error captured"""

pass


class LoginError(FletEasyError):
"""Login error captured"""

pass


class LoginRequiredError(FletEasyError):
"""Login required - used in route FletEasyX"""

pass


class RouteError(FletEasyError):
"""Route error"""

pass


class AlgorithmJwtError(FletEasyError):
"""Algorithm error"""

pass


class LogoutError(FletEasyError):
"""Logout error"""

pass


class MidlewareError(FletEasyError):
"""Middleware error"""

pass


class AddPagesError(FletEasyError):
"""Add pages error in route"""

pass
10 changes: 4 additions & 6 deletions src/flet_easy/extrasJwt.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from datetime import datetime, timezone
from typing import Any, Dict

from flet import Page
from flet_easy.exceptions import AlgorithmJwtError

with contextlib.suppress(ImportError):
from jwt import decode, encode
Expand Down Expand Up @@ -74,19 +74,17 @@ def encode_verified(secret_key: SecretKey, value: str, time_expiration) -> str |
time_expiry=time_expiration,
)
else:
Exception("Algorithm not implemented in encode_verified method.")
raise AlgorithmJwtError("Algorithm not implemented in encode_verified method.")


async def _decode_payload_async(
page: Page, key_login: str, secret_key: str, algorithms: str
) -> Dict[str, Any]:
def _decode_payload(jwt: str, secret_key: str, algorithms: str) -> Dict[str, Any]:
"""Decodes the payload stored in the client storage."""
assert (
secret_key is not None
), "The secret_key algorithm is not supported, only (RS256, HS256) is accepted."

return decode(
jwt=await page.client_storage.get_async(key_login),
jwt=jwt,
key=secret_key,
algorithms=[algorithms],
)
10 changes: 7 additions & 3 deletions src/flet_easy/fletEasy.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from flet_easy.exceptions import AddPagesError, FletEasyError

try:
from flet import AppView, Page, WebRenderer, app
except ImportError:
raise Exception('Install "flet" the latest version available -> pip install flet --upgrade.')
raise FletEasyError(
'Install "flet" the latest version available -> pip install flet[all] --upgrade.'
)

from collections import deque
from functools import wraps
Expand Down Expand Up @@ -229,7 +233,7 @@ def main(page: Page):
export_asgi_app=export_asgi_app,
)
except RuntimeError:
raise Exception(
raise FletEasyError(
"Ifs you are using fastapi from flet, set the 'fastapi = True' parameter of the run() method."
)

Expand Down Expand Up @@ -287,7 +291,7 @@ def add_pages(self, group_pages: List[AddPagesy]):
else:
self.__pages.extend(page._add_pages())
except Exception as e:
raise e
raise AddPagesError("Add pages error in route: ", e)

@classmethod
def page(
Expand Down
34 changes: 20 additions & 14 deletions src/flet_easy/jwt.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
from rsa import newkeys

from flet_easy.datasy import Datasy, evaluate_secret_key
from flet_easy.exceptions import LoginError, LogoutError
from flet_easy.extra import Msg
from flet_easy.extrasJwt import _decode_payload_async
from flet_easy.extrasJwt import _decode_payload


class EasyKey:
Expand Down Expand Up @@ -46,23 +47,22 @@ def secret_key(self) -> str:
return token_bytes(64).hex().encode("utf-8")


async def _handle_decode_errors(data: Datasy, key_login: str) -> Union[Dict[str, Any], bool]:
def _handle_decode_errors(jwt: str, data: Datasy, key_login: str) -> Union[Dict[str, Any], bool]:
"""decodes the jwt and updates the browser sessions."""
try:
data._key_login = key_login
evaluate_secret_key(data)

if not await data.page.client_storage.contains_key_async(key_login):
if jwt is None:
return False

if data.auto_logout and not data._login_done:
data.page.pubsub.send_others_on_topic(
data.page.client_ip, Msg("updateLogin", value=data._login_done)
)

decode = await _decode_payload_async(
page=data.page,
key_login=key_login,
decode = _decode_payload(
jwt=jwt,
secret_key=(
data.secret_key.secret
if data.secret_key.secret is not None
Expand All @@ -85,14 +85,13 @@ async def _handle_decode_errors(data: Datasy, key_login: str) -> Union[Dict[str,
return False
except DecodeError as e:
data.logout(key_login)()
Exception(
raise LogoutError(
"Decoding error, possibly there is a double use of the 'client_storage' 'key', Secret key invalid! or ",
e,
)
return False
except Exception as e:
data.logout(key_login)()
raise Exception("Login error:", e)
raise LogoutError("Login error:", e)


def decode(key_login: str, data: Datasy) -> Dict[str, Any] | bool:
Expand All @@ -102,10 +101,13 @@ def decode(key_login: str, data: Datasy) -> Dict[str, Any] | bool:
* `key_login` : key used to store data in the client, also used in the `login` method of `Datasy`.
* `data` : Instance object of the `Datasy` class.
"""
assert not data._login_async, "Use the 'decode_async' method instead of 'decode'."
assert data.secret_key is not None, "set the 'secret_key' in the class parameter (FletEasy)."

return data.page.run_task(_handle_decode_errors, data, key_login).result()
try:
return _handle_decode_errors(
jwt=data.page.client_storage.get(key_login), data=data, key_login=key_login
)
except TimeoutError as e:
raise LoginError("Use the 'decode_async' method instead of 'decode'. | More details:", e)


async def decode_async(key_login: str, data: Datasy) -> Dict[str, Any] | bool:
Expand All @@ -115,7 +117,11 @@ async def decode_async(key_login: str, data: Datasy) -> Dict[str, Any] | bool:
* `key_login` : key used to store data in the client, also used in the `login` method of `Datasy`.
* `data` : Instance object of the `Datasy` class.
"""
assert data._login_async, "Use the 'decode' method instead of 'decode_async'."
assert data.secret_key is not None, "set the 'secret_key' in the class parameter (FletEasy)."

return await _handle_decode_errors(data, key_login)
try:
return _handle_decode_errors(
jwt=await data.page.client_storage.get_async(key_login), data=data, key_login=key_login
)
except TimeoutError as e:
raise LoginError("Use the 'decode' method instead of 'decode_async'. | More details:", e)
Loading

0 comments on commit 2a6a795

Please sign in to comment.