2929from urllib import request as http
3030
3131
32- DEFAULT_AUTH_HEADER = ' Authorization'
32+ DEFAULT_AUTH_HEADER = " Authorization"
3333ASYNC_FUNCNAME_MARKER = "_async"
3434
3535
@@ -39,43 +39,51 @@ class ClientError(Exception):
3939
4040class Client :
4141 """
42- Kanboard API client
42+ Kanboard API client for interacting with the Kanboard JSON-RPC API.
4343
44- Example:
44+ This client provides both synchronous and asynchronous access to all Kanboard API methods.
45+ Methods are dynamically resolved based on the Kanboard API method names, using snake_case for Python calls.
4546
47+ Example usage:
4648 from kanboard import Client
4749
4850 kb = Client(url="http://localhost/jsonrpc.php",
4951 username="jsonrpc",
5052 password="your_api_token")
5153
5254 project_id = kb.create_project(name="My project")
53-
5455 """
5556
56- def __init__ (self ,
57- url : str ,
58- username : str ,
59- password : str ,
60- auth_header : str = DEFAULT_AUTH_HEADER ,
61- cafile : Optional [str ] = None ,
62- insecure : bool = False ,
63- ignore_hostname_verification : bool = False ,
64- user_agent : str = 'Kanboard Python API Client' ,
65- loop : Optional [asyncio .AbstractEventLoop ] = None ):
57+ def __init__ (
58+ self ,
59+ url : str ,
60+ username : str ,
61+ password : str ,
62+ auth_header : str = DEFAULT_AUTH_HEADER ,
63+ cafile : Optional [str ] = None ,
64+ insecure : bool = False ,
65+ ignore_hostname_verification : bool = False ,
66+ user_agent : str = "Kanboard Python API Client" ,
67+ loop : Optional [asyncio .AbstractEventLoop ] = None ,
68+ ):
6669 """
67- Constructor
70+ Initialize a new Kanboard API client instance.
6871
6972 Args:
70- url: API url endpoint
71- username: API username or real username
72- password: API token or user password
73- auth_header: API HTTP header
74- cafile: Path to a custom CA certificate
75- insecure: Ignore SSL certificate errors and ignore hostname mismatches
76- ignore_hostname_verification: Ignore SSL certificate hostname verification
77- user_agent: Use a personalized user agent
78- loop: An asyncio event loop. Default: asyncio.get_event_loop()
73+ url (str): The Kanboard JSON-RPC API endpoint URL (e.g.,
74+ 'http://localhost/jsonrpc.php').
75+ username (str): Kanboard API username or real username.
76+ password (str): Kanboard API token or user password.
77+ auth_header (str, optional): HTTP header for authentication. Defaults to 'Authorization'.
78+ cafile (Optional[str], optional): Path to a custom CA certificate file. Defaults to None.
79+ insecure (bool, optional): If True, ignore SSL certificate errors and hostname mismatches.
80+ Defaults to False.
81+ ignore_hostname_verification (bool, optional): If True, skip SSL certificate hostname verification.
82+ Defaults to False.
83+ user_agent (str, optional): Custom User-Agent string for HTTP requests. Defaults to
84+ 'Kanboard Python API Client'.
85+ loop (Optional[asyncio.AbstractEventLoop], optional): Asyncio event loop to use. If None, uses the
86+ current event loop or creates a new one.
7987 """
8088 self ._url = url
8189 self ._username = username
@@ -94,16 +102,21 @@ def __init__(self,
94102
95103 def __getattr__ (self , name : str ):
96104 if self .is_async_method_name (name ):
105+
97106 async def function (* args , ** kwargs ):
98107 return await self ._event_loop .run_in_executor (
99108 None ,
100109 functools .partial (
101- self .execute ,
102- method = self ._to_camel_case (self .get_funcname_from_async_name (name )), ** kwargs ))
110+ self .execute , method = self ._to_camel_case (self .get_funcname_from_async_name (name )), ** kwargs
111+ ),
112+ )
113+
103114 return function
104115 else :
116+
105117 def function (* args , ** kwargs ):
106118 return self .execute (method = self ._to_camel_case (name ), ** kwargs )
119+
107120 return function
108121
109122 @staticmethod
@@ -112,31 +125,29 @@ def is_async_method_name(funcname: str) -> bool:
112125
113126 @staticmethod
114127 def get_funcname_from_async_name (funcname : str ) -> str :
115- return funcname [:len (funcname ) - len (ASYNC_FUNCNAME_MARKER )]
128+ return funcname [: len (funcname ) - len (ASYNC_FUNCNAME_MARKER )]
116129
117130 @staticmethod
118131 def _to_camel_case (snake_str : str ) -> str :
119- components = snake_str .split ('_' )
120- return components [0 ] + '' .join (x .title () for x in components [1 :])
132+ components = snake_str .split ("_" )
133+ return components [0 ] + "" .join (x .title () for x in components [1 :])
121134
122135 @staticmethod
123136 def _parse_response (response : bytes ):
124137 try :
125- body = json .loads (response .decode (errors = ' ignore' ))
138+ body = json .loads (response .decode (errors = " ignore" ))
126139
127- if ' error' in body :
128- message = body .get (' error' ).get (' message' )
140+ if " error" in body :
141+ message = body .get (" error" ).get (" message" )
129142 raise ClientError (message )
130143
131- return body .get (' result' )
144+ return body .get (" result" )
132145 except ValueError :
133146 return None
134147
135148 def _do_request (self , headers : Dict [str , str ], body : Dict ):
136149 try :
137- request = http .Request (self ._url ,
138- headers = headers ,
139- data = json .dumps (body ).encode ())
150+ request = http .Request (self ._url , headers = headers , data = json .dumps (body ).encode ())
140151
141152 ssl_context = ssl .create_default_context (cafile = self ._cafile )
142153 if self ._insecure :
@@ -153,31 +164,26 @@ def _do_request(self, headers: Dict[str, str], body: Dict):
153164
154165 def execute (self , method : str , ** kwargs ):
155166 """
156- Call remote API procedure
167+ Call a remote Kanboard API procedure.
157168
158169 Args:
159- method: Procedure name
160- kwargs: Procedure named arguments
170+ method (str): The Kanboard API method name in camelCase (e.g., 'createProject').
171+ ** kwargs: Named arguments to pass to the API method.
161172
162173 Returns:
163- Procedure result
174+ The result returned by the Kanboard API for the requested method.
164175
165176 Raises:
166- urllib.error.HTTPError: Any HTTP error (Python 3)
177+ ClientError: If the API returns an error or if a network/HTTP error occurs.
167178 """
168- payload = {
169- 'id' : 1 ,
170- 'jsonrpc' : '2.0' ,
171- 'method' : method ,
172- 'params' : kwargs
173- }
179+ payload = {"id" : 1 , "jsonrpc" : "2.0" , "method" : method , "params" : kwargs }
174180
175- credentials = base64 .b64encode (' {}:{}' .format (self ._username , self ._password ).encode ())
176- auth_header_prefix = ' Basic ' if self ._auth_header == DEFAULT_AUTH_HEADER else ''
181+ credentials = base64 .b64encode (" {}:{}" .format (self ._username , self ._password ).encode ())
182+ auth_header_prefix = " Basic " if self ._auth_header == DEFAULT_AUTH_HEADER else ""
177183 headers = {
178184 self ._auth_header : auth_header_prefix + credentials .decode (),
179- ' Content-Type' : ' application/json' ,
180- ' User-Agent' : self ._user_agent ,
185+ " Content-Type" : " application/json" ,
186+ " User-Agent" : self ._user_agent ,
181187 }
182188
183189 return self ._do_request (headers , payload )
0 commit comments