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

[BUILD] Add pylint actions #2

Merged
merged 13 commits into from
Jan 12, 2024
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
26 changes: 26 additions & 0 deletions .github/workflows/pylint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Pylint

on: [push]

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install requests
pip install flask
pip install fastapi
pip install pylint
- name: Analysing the code with pylint
run: |
pylint $(git ls-files '*.py')
3 changes: 3 additions & 0 deletions oborpc/base/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
OBORPC Bae
"""
11 changes: 8 additions & 3 deletions oborpc/base/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ class OBORMeta(type):
Meta class used
"""
__obor_registry__ = {}
def __new__(mcls, name, bases, namespace, /, **kwargs):
cls = super().__new__(mcls, name, bases, namespace, **kwargs)
def __new__(mcs, name, bases, namespace, /, **kwargs):
cls = super().__new__(mcs, name, bases, namespace, **kwargs)

cls.__oborprocedures__ = {
methodname for methodname, value in namespace.items()
Expand All @@ -19,7 +19,12 @@ def __new__(mcls, name, bases, namespace, /, **kwargs):
return cls


class OBORBase(metaclass=OBORMeta):
class OBORBase(metaclass=OBORMeta): # pylint: disable=too-few-public-methods
"""
Obor Base Class
"""
def __repr__(self) -> str:
return "<OBORBase(metaclass=OBORMeta)>"

def __str__(self) -> str:
return self.__repr__()
6 changes: 5 additions & 1 deletion oborpc/builder/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
"""
OBORPC Builder
"""

from ._base import OBORBuilder
from ._client import ClientBuilder
from ._server import ServerBuilder
from ._fastapi import FastAPIServerBuilder
from ._flask import FlaskServerBuilder
from ._flask import FlaskServerBuilder
14 changes: 13 additions & 1 deletion oborpc/builder/_base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
"""
OBORPC Base Builder
"""
from ..exception import OBORPCBuildException

class OBORBuilder():
"""
OBORPC Builder Class
"""
__registered_base = set()

def __init__(self, host, port=None, timeout=1, retry=0) -> None:
Expand All @@ -20,13 +25,20 @@ def __init__(self, host, port=None, timeout=1, retry=0) -> None:
self.base_url += f":{port}"

def check_has_protocol(self, host: str):
"""
Check whether the given host already defined with protocol or not
"""
if host.startswith("http://"):
return True
if host.startswith("https://"):
return True
return False

def check_registered_base(self, base: str):
"""
Check whether the base RPC class is already built
"""
if base in OBORBuilder.__registered_base:
raise Exception(f"Failed to build client RPC {base} : base class can only built once")
msg = f"Failed to build client RPC {base} : base class can only built once"
raise OBORPCBuildException(msg)
OBORBuilder.__registered_base.add(base)
57 changes: 43 additions & 14 deletions oborpc/builder/_client.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
"""
Client RPC Builder
"""
import inspect
import json
import requests
import logging
import time
import requests
from ._base import OBORBuilder
from ..exception import RPCCallException


class ClientBuilder(OBORBuilder):
def __init__(self, host, port=None, timeout=1, retry=0) -> None:
super().__init__(host, port, timeout, retry)

def create_remote_caller(self, class_name, method_name, url_prefix, timeout = None, retry = None):
"""
Client Builder
"""
def create_remote_caller(
self,
class_name: str,
method_name: str,
url_prefix: str,
timeout: float = None,
retry: int = None
): # pylint: disable=too-many-arguments
"""
create remote caller
"""
def remote_call(*args, **kwargs):
"""
remote call wrapper
"""
start_time = time.time()
try:
t0 = time.time()
data = {
"args": args[1:],
"kwargs": kwargs
Expand All @@ -24,28 +40,41 @@ def remote_call(*args, **kwargs):
url,
headers={"Content-Type": "application/json"},
json=json.dumps(data),
timeout=timeout if timeout != None else self.timeout
timeout=timeout if timeout is not None else self.timeout
)

if not response:
raise Exception(f"rpc call failed method={method_name}")
msg = f"rpc call failed method={method_name}"
raise RPCCallException(msg)

return response.json().get("data")

except Exception as e:
_retry = retry if retry != None else self.retry
_retry = retry if retry is not None else self.retry
if _retry:
return remote_call(*args, **kwargs, retry=_retry-1)
raise Exception(f"rpc call failed method={method_name}")

if isinstance(e, RPCCallException):
raise e
msg = f"rpc call failed method={method_name} : {e}"
raise RPCCallException(msg) from e

finally:
# print("elapsed", time.time() - t0)
pass
elapsed = f"{(time.time() - start_time) * 1000}:.2f"
logging.debug("[RPC-Clientt] remote call take %s ms", elapsed)

return remote_call

def setup_client_rpc(self, instance: object, url_prefix: str = ""):
def build_client_rpc(self, instance: object, url_prefix: str = ""):
"""
Setup client rpc
"""
_class = instance.__class__
iterator_class = _class

self.check_registered_base(_class)

for (name, method) in inspect.getmembers(iterator_class, predicate=inspect.isfunction):
for (name, _) in inspect.getmembers(iterator_class, predicate=inspect.isfunction):
if name not in iterator_class.__oborprocedures__:
continue
setattr(_class, name, self.create_remote_caller(_class.__name__, name, url_prefix))
15 changes: 9 additions & 6 deletions oborpc/builder/_fastapi.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
"""
FastAPI Server Builder
"""
import json
import asyncio
from enum import Enum
from typing import Optional, List, Dict, Union, Type, Any, Sequence, Callable
from fastapi import Request, Response, APIRouter, params
from fastapi.responses import JSONResponse
from fastapi.routing import BaseRoute, APIRoute, ASGIApp, Lifespan, Default, generate_unique_id
from typing import Optional, List, Dict, Union, Type, Any, Sequence, Callable
from ._server import ServerBuilder
from ..base.meta import OBORBase

class FastAPIServerBuilder(ServerBuilder):
def __init__(self, host, port=None, timeout=1, retry=None):
super().__init__(host, port, timeout, retry)

class FastAPIServerBuilder(ServerBuilder):
"""
Dedicated RPC Server Builder for FastAPI
"""
def create_remote_responder(
self,
instance: OBORBase,
router: APIRouter,
class_name: str,
method_name: str,
method: Callable
):
): # pylint: disable=too-many-arguments
@router.post(f"{router.prefix}/{class_name}/{method_name}")
def final_func(request: Request):
request_body = asyncio.run(request.body())
Expand Down Expand Up @@ -49,8 +51,9 @@ def build_router_from_instance(
deprecated: Optional[bool] = None,
include_in_schema: bool = True,
generate_unique_id_function: Callable[[APIRoute], str] = Default(generate_unique_id),
):
): # pylint: disable=too-many-arguments,too-many-locals
"""
build FastAPI API Router from oborpc instance
"""
router = APIRouter(
prefix=prefix,
Expand Down
36 changes: 22 additions & 14 deletions oborpc/builder/_flask.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
"""
Flask Server Builder
"""
import functools
import json
import os
from typing import Callable, Union
from flask import request as flask_request, Blueprint
from ._server import ServerBuilder
from ..base.meta import OBORBase

class FlaskServerBuilder(ServerBuilder):
def __init__(self, host, port=None, timeout=1, retry=None):
super().__init__(host, port, timeout, retry)

"""
Dedicated RPC Server Builder for Flask
"""
def create_remote_responder(
self, instance: OBORBase, router: Blueprint, class_name, method_name, method
):
self,
instance: OBORBase,
router: Blueprint,
class_name: str,
method_name: str,
method: Callable
): # pylint: disable=too-many-arguments
def create_modified_func():
@functools.wraps(method)
def modified_func():
Expand All @@ -31,16 +38,17 @@ def build_blueprint_from_instance(
instance: OBORBase,
blueprint_name: str,
import_name: str,
static_folder: str | os.PathLike | None = None,
static_url_path: str | None = None,
template_folder: str | os.PathLike | None = None,
url_prefix: str | None = None,
subdomain: str | None = None,
url_defaults: dict | None = None,
root_path: str | None = None,
cli_group: str | None = object()
):
static_folder: Union[str, os.PathLike, None] = None,
static_url_path: Union[str, None] = None,
template_folder: Union[str, os.PathLike, None] = None,
url_prefix: Union[str, None] = None,
subdomain: Union[str, None] = None,
url_defaults: Union[dict, None] = None,
root_path: Union[str, None] = None,
cli_group: Union[str, None] = object()
): # pylint: disable=too-many-arguments
"""
build Flask blueprint from oborpc instance
"""
blueprint = Blueprint(
blueprint_name,
Expand Down
25 changes: 19 additions & 6 deletions oborpc/builder/_server.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
"""
Server Builder Base
"""
import inspect
from ._base import OBORBuilder


class ServerBuilder(OBORBuilder):
def __init__(self, host, port=None, timeout=1, retry=0) -> None:
super().__init__(host, port, timeout, retry)

def create_remote_responder(self, instance, router, class_name, method_name, method):
"""
Server Builder
"""
def create_remote_responder(self, instance, router, class_name, method_name, method): # pylint: disable=too-many-arguments
"""
Remote RPC Request Responder
"""
raise NotImplementedError("method should be overridden")

def dispatch_rpc_request(self, instance, method, body):
"""
Dispatch RPC Request
"""
args = body.get("args", [])
kwargs = body.get("kwargs", {})
res = method(instance, *args, **kwargs)
return {"data": res}

def setup_server_rpc(self, instance: object, router):
"""
Setup RPC Server
"""
_class = instance.__class__
iterator_class = instance.__class__.__base__
method_map = {
method_map = { # pylint: disable=unnecessary-comprehension
name: method for (name, method) in inspect.getmembers(
_class, predicate=inspect.isfunction
)
Expand All @@ -29,4 +39,7 @@ def setup_server_rpc(self, instance: object, router):
for (name, method) in inspect.getmembers(iterator_class, predicate=inspect.isfunction):
if name not in iterator_class.__oborprocedures__:
continue
self.create_remote_responder(instance, router, iterator_class.__name__, name, method_map.get(name))
self.create_remote_responder(
instance, router, iterator_class.__name__,
name, method_map.get(name)
)
22 changes: 21 additions & 1 deletion oborpc/decorator.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
"""
OBORPC Decorator
"""
import inspect
from typing import Callable

def procedure(fun):
def procedure(fun: Callable):
"""
Marks the method with this decorator to make it available
for RPC within the class with direct inheritance with `OBORBase`.
To make it works also make sure the method is overridden
in the server class inheritance of the base class

from oborpc.base import meta
from oborpc.decorator import procedure

class Calculator(meta.OborBase):
@procedure
def add(self, a, b):
pass

class CalculatorServer(Calculator):
def tambah(self, a, b):
return a + b
"""
if not inspect.isfunction(fun):
raise TypeError("can only applied for function or method")
fun.__isoborprocedure__ = True
Expand Down
17 changes: 17 additions & 0 deletions oborpc/exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""
OBORPC Exceptions
"""

class OBORPCBuildException(Exception):
"""
Build Related Exceptions
"""
def __init__(self, *args: object) -> None:
super().__init__(*args)

class RPCCallException(Exception):
"""
Any Exception during RPC Call
"""
def __init__(self, *args: object) -> None:
super().__init__(*args)
Loading