plankapy
1import requests 2import json 3 4API_URL = "http://localhost:3000" 5API_USER = "demo- demo.demo" 6API_PASS = "demo" 7OFFSET = 65535 8 9class Planka: 10 """API wrapper class for Planka 11 - url: URL of Planka instance 12 - username: Username of Planka user 13 - password: Password of Planka user 14 """ 15 def __init__(self, url:str, username:str, password:str, templates="config/templates.json"): 16 self.url = url 17 self.username = username 18 self.password = password 19 self.auth = None 20 with open(templates) as f: 21 self.templates = json.load(f) 22 self.authenticate() 23 24 def __repr__(self): 25 return f"<{type(self).__name__}:\n\tBase URL: {self.url}\n\tLogin User: {self.username}\n\tLogin Pass: {self.password}\n\tAPI Token: {self.auth}\n>" 26 27 def deauthenticate(self) -> bool: 28 """Deletes the auth token from the Planka API 29 - **return:** True if successful, False if not 30 """ 31 try: 32 self.request("DELETE", "/api/access-tokens/me") 33 self.auth = None 34 return True 35 except: 36 raise InvalidToken(f"No active access token assigned to this instance\n{self.__repr__()}") 37 38 def validate(self) -> bool: 39 """Validates the Planka API connection 40 - **return:** True if successful, False if not 41 """ 42 try: 43 self.request("GET", "/*") 44 return True 45 except: 46 raise InvalidToken(f"Invalid API credentials\n{self.__repr__()}") 47 48 def authenticate(self) -> bool: 49 """Gets an auth token from the Planka API 50 - **return:** True if successful, False if not 51 """ 52 try: 53 request = requests.post(f"{self.url}/api/access-tokens", data={'emailOrUsername': self.username, 'password': self.password}) 54 self.auth = request.json()['item'] 55 if not self.auth: 56 raise InvalidToken(f"Invalid API credentials\n{self.__repr__()}") 57 return True 58 except: 59 raise InvalidToken(f"Invalid API credentials\n{self.__repr__()}") 60 61 def request(self, method:str, endpoint:str, data:dict=None) -> dict: 62 """Makes a request to the Planka API 63 - method: HTTP method 64 - endpoint: API endpoint 65 - data: Data to send with request (default: None) 66 - **return:** JSON response from Planka API 67 """ 68 if not self.auth: 69 self.authenticate() 70 headers = \ 71 { 72 "Content-Type": "application/json", 73 "Authorization": f"Bearer {self.auth}" 74 } 75 url = f"{self.url}{endpoint}" 76 response = requests.request(method, url, headers=headers, json=data) 77 78 if response.status_code == 401: 79 raise InvalidToken("Invalid API credentials") 80 81 if response.status_code not in [200, 201]: 82 raise InvalidToken(f"Failed to {method} {url} with status code {response.status_code}") 83 84 try: 85 return response.json() 86 except: 87 raise InvalidToken(f"Failed to parse response from {url}") 88 89 def get_template(self, template:str) -> dict: 90 """Returns a template from the templates.json file 91 - template: Name of template to return 92 - **return:** Template dictionary 93 """ 94 try: 95 return self.templates[template] 96 except: 97 raise InvalidToken(f"Template not found: {template}") 98 99class Controller(): 100 def __init__(self, instance:Planka) -> None: 101 """Controller class for Planka API 102 - instance: Planka API instance 103 """ 104 self.instance = instance 105 self.template:dict = None 106 self.data:dict = None 107 self.response:dict = None 108 109 def __str__(self) -> str: 110 """Returns a string representation of the controller object 111 - **return:** String representation of controller object 112 """ 113 return f"{type(self).__name__}:\n{json.dumps(self.data, sort_keys=True, indent=4)}" 114 115 def __repr__(self) -> str: 116 """Returns a string representation of the controller object 117 - **return:** String representation of controller object 118 """ 119 return f"<{type(self).__name__}({self.__class__.__bases__[0].__name__})>{self.__str__()}" 120 121 def build(self, **kwargs) -> dict: 122 """Builds the controller data 123 - **return:** Controller data dictionary 124 """ 125 if not kwargs: 126 return kwargs 127 valid_keys = self.template.keys() 128 data = {key: value for key, value in kwargs.items() if key in valid_keys} 129 self.data = data 130 return self.data 131 132 def create(self, route:str, data:dict=None) -> dict: 133 """Creates a new controller object (POST) 134 - route: Route for controller object POST request 135 - **return:** POST response dictionary 136 """ 137 if not data: 138 data = self.data 139 if not data: 140 raise InvalidToken(f"Please Build a {type(self).__name__} before creating") 141 self.response = self.instance.request("POST", route, data) 142 return self.response 143 144 def get(self, route:str) -> dict: 145 """Gets a controller object (GET) 146 - route: Route for controller object GET request 147 - **return:** GET response dictionary 148 """ 149 return self.instance.request("GET", route) 150 151 def update(self, route:str, data:dict=None) -> dict: 152 """Updates a controller object (PATCH) 153 - route: Route for controller object PATCH request 154 - oid: ID of controller object 155 - **return:** PATCH response dictionary 156 """ 157 if not data: 158 data = self.data 159 if not self.data: 160 raise InvalidToken(f"Please Build a {type(self).__name__} before updating") 161 self.response = self.instance.request("PATCH", route, data=data) 162 return self.response 163 164 def delete(self, route:str) -> dict: 165 """Deletes a controller object (DELETE) 166 - route: Route for controller object DELETE request 167 - oid: ID of controller object 168 - **return:** DELETE response dictionary 169 """ 170 return self.instance.request("DELETE", route) 171 172 def last_response(self) -> dict: 173 """Returns the last response from the controller object 174 - **return:** Last response dictionary 175 """ 176 return self.response 177 178class Project(Controller): 179 def __init__(self, instance:Planka, **kwargs) -> None: 180 self.instance = instance 181 self.template = instance.get_template("project") 182 self.data = self.build(**kwargs) 183 184 def get(self, name:str=None, oid:str=None) -> dict: 185 """Gets a project by name 186 - oid: ID of project to get (optional) 187 - name: Name of project if None returns all projects 188 - **return:** GET response dictionary 189 """ 190 if oid: 191 return super().get(f"/api/projects/{oid}") 192 prjs = super().get("/api/projects") 193 if not name: 194 return prjs 195 prj_names = [prj["name"] for prj in prjs["items"]] 196 if name not in prj_names: 197 raise InvalidToken(f"Project {name} not found") 198 prj_id = [prj for prj in prjs["items"] if prj["name"] == name][0]["id"] 199 return super().get(f"/api/projects/{prj_id}") 200 201 def get_project_names(self) -> list: 202 """Gets a list of project names 203 - **return:** List of project names 204 """ 205 return [prj["name"] for prj in self.get()['items']] 206 207 def create(self) -> dict: 208 """Creates a new project 209 - **return:** POST response dictionary 210 """ 211 if not self.data: 212 raise InvalidToken(f"Please Build a {type(self).__name__} before creating") 213 if self.data["name"] in [prj["name"] for prj in self.get()['items']]: 214 raise InvalidToken(f"Project {self.data['name']} already exists") 215 return super().create("/api/projects") 216 217 def update(self, name:str) -> dict: 218 """Updates a project 219 - name: Name of project to update 220 - **return:** PATCH response dictionary 221 """ 222 prj_id = prj_id = self.get(name)['item']['id'] 223 return super().update(f"/api/projects/{prj_id}") 224 225 def delete(self, name:str) -> dict: 226 """Deletes a project 227 - name: Name of project to delete 228 - **return:** DELETE response dictionary 229 """ 230 prj_id = self.get(name)['item']['id'] 231 return super().delete(f"/api/projects/{prj_id}") 232 233class Board(Controller): 234 def __init__(self, instance:Planka, **kwargs) -> None: 235 self.instance = instance 236 self.template = instance.get_template("board") 237 self.data = self.build(**kwargs) 238 239 def get(self, project_name:str=None, board_name:str=None, oid:str=None) -> dict: 240 """Gets a board by name 241 - oid: ID of board to get (optonal) 242 - name: Name of board if None returns all boards 243 - project_name: Name of project to get boards from 244 - **return:** GET response dictionary 245 """ 246 if oid: 247 return super().get(f"/api/boards/{oid}") 248 if not (project_name): 249 raise InvalidToken("Please provide a project name") 250 prj_con = Project(self.instance) 251 prj = prj_con.get(project_name) 252 boards = prj["included"]["boards"] 253 if not board_name: 254 return boards 255 board_names = [board["name"] for board in boards] 256 if board_name not in board_names: 257 raise InvalidToken(f"Board `{board_name}` not found") 258 board_id = [board for board in boards if board["name"] == board_name][0]["id"] 259 return super().get(f"/api/boards/{board_id}") 260 261 def create(self, project_name:str) -> dict: 262 """Creates a new board 263 - prj_name: Name of project to create board in 264 - **return:** POST response dictionary 265 """ 266 if not self.data: 267 raise InvalidToken(f"Please Build a {type(self).__name__} before creating") 268 prj_con = Project(self.instance) 269 prj_id = prj_con.get(project_name)['item']['id'] 270 return super().create(f"/api/projects/{prj_id}/boards") 271 272 def update(self, project_name:str=None, board_name:str=None, data:dict=None, oid:str=None) -> dict: 273 """Updates a board 274 - oid: ID of board to update (optional) 275 - project_name: Name of project to update board in 276 - board_name: Name of board to update 277 - **return:** PATCH response dictionary 278 """ 279 if not data: 280 data = self.data 281 if not data: 282 raise InvalidToken(f"Please Build a {type(self).__name__} before updating") 283 if oid: 284 return super().update(f"/api/boards/{oid}", data=data) 285 if not (project_name and board_name): 286 raise InvalidToken("Please provide project and board names") 287 board_id = self.get(project_name, board_name)['item']['id'] 288 return super().update(f"/api/boards/{board_id}", data=self.data) 289 290 def delete(self, project_name:str=None, board_name:str=None, oid:str=None): 291 """Deletes a board 292 - oid: ID of board to delete (optional) 293 - project_name: Name of project to delete board in 294 - board_name: Name of board to delete 295 - **return:** DELETE response dictionary 296 """ 297 if oid: 298 return super().delete(f"/api/boards/{oid}") 299 if not project_name: 300 raise InvalidToken("Please provide a project name") 301 if not board_name: 302 raise InvalidToken("Please provide a board name") 303 board_id = self.get(project_name, board_name)['item']['id'] 304 return super().delete(f"/api/boards/{board_id}") 305 306class List(Controller): 307 def __init__(self, instance:Planka, **kwargs) -> None: 308 self.instance = instance 309 self.template = instance.get_template("list") 310 self.data = self.build(**kwargs) 311 312 def get(self, project_name:str=None, board_name:str=None, list_name:str=None): 313 """Gets a list by name 314 NOTE: No GET route for list by ID 315 - project_name: Name of project to get list from 316 - board_name: Name of board to get list from 317 - list_name: Name of list to get 318 - **return:** GET response dictionary 319 """ 320 if not (project_name and board_name): 321 raise InvalidToken("Please provide project and board names") 322 board_con = Board(self.instance) 323 board = board_con.get(project_name, board_name) 324 lists = board["included"]["lists"] 325 list_names = [lst["name"] for lst in lists] 326 if not list_name: 327 return lists 328 if list_name not in list_names: 329 raise InvalidToken(f"List `{list_name}` not found") 330 return [lst for lst in lists if lst["name"] == list_name][0] 331 332 def create(self, project_name:str=None, board_name:str=None, data:dict=None): 333 """Creates a new list 334 - project_name: Name of project to create list in 335 - board_name: Name of board to create list in 336 - **return:** POST response dictionary 337 """ 338 if not data: 339 data = self.data 340 if not data: 341 raise InvalidToken(f"Please Build a {type(self).__name__} before creating") 342 if not (project_name and board_name): 343 raise InvalidToken("Please provide project and board name") 344 board_con = Board(self.instance) 345 board_id = board_con.get(project_name, board_name)['item']['id'] 346 return super().create(f"/api/boards/{board_id}/lists") 347 348 def update(self, project_name:str=None, board_name:str=None, list_name:str=None, data:dict=None, oid:str=None): 349 """Updates a list 350 - oid: ID of list to update (optional) 351 - project_name: Name of project to update list in 352 - board_name: Name of board to update list in 353 - list_name: Name of list to update 354 - **return:** PATCH response dictionary 355 """ 356 if not data: 357 data = self.data 358 if not data: 359 raise InvalidToken(f"Please Build a {type(self).__name__} before updating") 360 if oid: 361 return super().update(f"/api/lists/{oid}", data=data) 362 if not (project_name and board_name and list_name): 363 raise InvalidToken("Please provide project, board, and list names") 364 lst = self.get(project_name, board_name, list_name) 365 return super().update(f"/api/lists/{lst['id']}", data=data) 366 367 def delete(self, project_name:str=None, board_name:str=None, list_name:str=None, oid:str=None): 368 """Deletes a list 369 - oid: ID of list to delete (optional) 370 - project_name: Name of project to delete list in 371 - board_name: Name of board to delete list in 372 - list_name: Name of list to delete 373 - **return:** DELETE response dictionary 374 """ 375 if oid: 376 return super().delete(f"/api/lists/{oid}") 377 if not (project_name and board_name and list_name): 378 raise InvalidToken("Please provide a project, board, and list names") 379 lst = self.get(project_name, board_name, list_name) 380 return super().delete(f"/api/lists/{lst['id']}") 381 382class Card(Controller): 383 def __init__(self, instance:Planka, **kwargs) -> None: 384 self.instance = instance 385 self.template = instance.get_template("card") 386 self.data = self.build(**kwargs) 387 388 def get(self, project_name:str=None, board_name:str=None, list_name:str=None, card_name:str=None, oid:str=None): 389 """Gets a card by name 390 - oid: ID of card to get (optional) 391 - project_name: Name of project to get card from 392 - board_name: Name of board to get card from 393 - list_name: Name of list to get card from 394 - card_name: Name of card to get 395 - **return:** GET response dictionary 396 """ 397 if oid != None: 398 return super().get(f"/api/cards/{oid}") 399 if not (project_name and board_name and list_name): 400 raise InvalidToken("Please provide project, board, and list names") 401 board_con = Board(self.instance) 402 board = board_con.get(project_name, board_name) 403 lst_id = [ls for ls in board["included"]["lists"] if ls["name"] == list_name][0]["id"] 404 cards = [card for card in board["included"]["cards"] if card["listId"] == lst_id] 405 card_names = [card["name"] for card in cards] 406 if not card_name: 407 return [self.get(oid=card["id"]) for card in cards] 408 if card_name not in card_names: 409 raise InvalidToken(f"Card `{card_name}` not found") 410 card_id = [card for card in cards if card["name"] == card_name][0]['id'] 411 return super().get(f"/api/cards/{card_id}") 412 413 def create(self, project_name:str=None, board_name:str=None, list_name:str=None, data:dict=None): 414 """Creates a new card 415 - project_name: Name of project to create card in 416 - board_name: Name of board to create card in 417 - list_name: Name of list to create card in 418 - **return:** POST response dictionary 419 """ 420 if not data: 421 data = self.data 422 if not data: 423 raise InvalidToken(f"Please Build a {type(self).__name__} before creating") 424 if not (project_name and board_name and list_name): 425 raise InvalidToken("Please provide a project, board and list names") 426 board_con = Board(self.instance) 427 board = board_con.get(project_name, board_name) 428 lst_id = [ls for ls in board["included"]["lists"] if ls["name"] == list_name][0]["id"] 429 return super().create(f"/api/lists/{lst_id}/cards") 430 431 def delete(self, project_name:str=None, board_name:str=None, list_name:str=None, card_name:str=None, oid:str=None): 432 """Deletes a card 433 - oid: ID of card to delete (optional) 434 - project_name: Name of project to delete card in 435 - board_name: Name of board to delete card in 436 - list_name: Name of list to delete card in 437 - card_name: Name of card to delete 438 - **return:** DELETE response dictionary 439 """ 440 if oid != None: 441 return super().delete(f"/api/cards/{oid}") 442 if not (project_name and board_name and list_name and card_name): 443 raise InvalidToken("Please provide a project, board, list, and card name") 444 card = self.get(project_name, board_name, list_name, card_name) 445 return super().delete(f"/api/cards/{card['id']}") 446 447 def update(self, project_name:str=None, board_name:str=None, list_name:str=None, card_name:str=None, data:dict=None, oid:str=None): 448 """Updates a card 449 - oid: ID of card to update (optional) 450 - project_name: Name of project to update card in 451 - board_name: Name of board to update card in 452 - list_name: Name of list to update card in 453 - card_name: Name of card to update 454 - **return:** PATCH response dictionary 455 """ 456 if not data: 457 data = self.data 458 if not data: 459 raise InvalidToken(f"Please Build a {type(self).__name__} before updating") 460 if oid: 461 return super().update(f"/api/cards/{oid}", data=data) 462 if not (project_name and board_name and list_name and card_name): 463 raise InvalidToken("Please provide a project, board, list, and card name") 464 card = self.get(project_name, board_name, list_name, card_name) 465 return super().update(f"/api/cards/{card['id']}", data=data) 466 467 def get_labels(self, project_name:str=None, board_name:str=None, list_name:str=None, card_name:str=None, oid:str=None): 468 """Gets labels for a card 469 - oid: ID of card to get labels from (optional) 470 - project_name: Name of project to get card from 471 - board_name: Name of board to get card from 472 - list_name: Name of list to get card from 473 - card_name: Name of card to get 474 - **return:** GET response dictionary 475 """ 476 if oid: 477 return self.get(oid=oid)['included']['cardLabels'] 478 if not (project_name and board_name and list_name and card_name): 479 raise InvalidToken("Please provide project, board, list, and card names") 480 card_id = self.get(project_name, board_name, list_name, card_name)['item']['id'] 481 return self.get(oid=card_id)['included']['cardLabels'] 482 483class Label(Controller): 484 def __init__(self, instance:Planka, **kwargs) -> None: 485 self.instance = instance 486 self.template = instance.get_template("label") 487 self.options = instance.get_template("colors") 488 self.data = self.build(**kwargs) 489 490 def colors(self) -> list: 491 return self.options 492 493 def get(self, project_name:str=None, board_name:str=None, label_name:str=None) -> dict: 494 """Gets a label by name 495 - project_name: Name of project to get label from 496 - board_name: Name of board to get label from 497 - label_name: Name of label to get 498 - **return:** GET response dictionary 499 """ 500 if not (project_name and board_name): 501 raise InvalidToken("Please provide project and board names") 502 board_con = Board(self.instance) 503 board = board_con.get(project_name, board_name) 504 labels = board["included"]["labels"] 505 label_names = [label["name"] for label in labels] 506 if not label_name: 507 return labels 508 if label_name not in label_names: 509 raise InvalidToken(f"Label `{label_name}` not found") 510 return [label for label in labels if label["name"] == label_name][0] 511 512 def create(self, project_name:str=None, board_name:str=None, data:dict=None): 513 """Creates a new label 514 - project_name: Name of project to create label in 515 - board_name: Name of board to create label in 516 - **return:** POST response dictionary 517 """ 518 if not data: 519 data = self.data 520 if not data: 521 raise InvalidToken(f"Please Build a {type(self).__name__} before creating") 522 if not (project_name and board_name): 523 raise InvalidToken("Please provide project and board names") 524 board_con = Board(self.instance) 525 board = board_con.get(project_name, board_name)['item'] 526 return super().create(f"/api/boards/{board['id']}/labels") 527 528 def delete(self, project_name:str=None, board_name:str=None, label_name:str=None, oid:str=None): 529 """Deletes a label 530 - oid: ID of label to delete (optional) 531 - project_name: Name of project to delete label from 532 - board_name: Name of board to delete label from 533 - label_name: Name of label to delete 534 - **return:** DELETE response dictionary 535 """ 536 if oid: 537 return super().delete(f"/api/labels/{oid}") 538 if not (project_name and board_name and label_name): 539 raise InvalidToken("Please provide project, board, and label names") 540 label = self.get(project_name, board_name, label_name) 541 return super().delete(f"/api/labels/{label['id']}") 542 543 def add(self, project_name:str=None, board_name:str=None, list_name:str=None ,card_name:str=None, label_name:str=None, card_id:str=None, label_id:str=None): 544 """Adds a label to a card 545 - project_name: Name of project to add label to card in 546 - board_name: Name of board to add label to card in 547 - label_name: Name of label to add to card 548 - card_name: Name of card to add label to 549 - list_name: Name of list to add label to card in 550 - **return:** POST response dictionary 551 """ 552 if label_id and card_id: 553 return super().create(f"/api/cards/{card_id}/labels", data={"labelId":label_id}) 554 if not (project_name and board_name and label_name): 555 raise InvalidToken("Please provide a project, board, label name") 556 if card_id: 557 label = self.get(project_name, board_name, label_name) 558 return super().create(f"/api/cards/{card_id}/labels", data={"labelId":label['item']['id']}) 559 if not (card_name and list_name): 560 raise InvalidToken("Please provide a card and list name") 561 card_con = Card(self.instance) 562 card = card_con.get(project_name, board_name, list_name, card_name) 563 label = self.get(project_name, board_name, label_name) 564 return super().create(f"/api/cards/{card['item']['id']}/labels", {"labelId":label['item']['id']}) 565 566 def remove(self, project_name:str=None, board_name:str=None, list_name:str=None ,card_name:str=None, label_name:str=None, card_id:str=None, label_id:str=None): 567 """Removes a label from a card 568 - project_name: Name of project to remove label from card in 569 - board_name: Name of board to remove label from card in 570 - label_name: Name of label to remove from card 571 - card_name: Name of card to remove label from 572 - list_name: Name of list to remove label from card in 573 - **return:** DELETE response dictionary 574 """ 575 if label_id and card_id: 576 return super().delete(f"/api/cards/{card_id}/labels/{label_id}") 577 if not (project_name and board_name and label_name): 578 raise InvalidToken("Please provide a project, board, label name") 579 if card_id: 580 label_id = [label['id'] for label in Card(self.instance).get_labels(oid=card_id) if label['name'] == label_name][0] 581 return super().delete(f"/api/cards/{card_id}/labels/{label['item']['id']}") 582 if not (card_name and list_name): 583 raise InvalidToken("Please provide a card and list name") 584 card_con = Card(self.instance) 585 card = card_con.get(project_name, board_name, list_name, card_name) 586 label = self.get(project_name, board_name, label_name) 587 return super().delete(f"/api/cards/{card['item']['id']}/labels/{label['item']['id']}") 588 589class Task(Controller): 590 def __init__(self, instance:Planka, **kwargs) -> None: 591 self.instance = instance 592 self.template = instance.get_template("task") 593 self.data = self.build(**kwargs) 594 595 def get(self, project_name:str=None, board_name:str=None, list_name:str=None, card_name:str=None, task_name:str=None) -> dict: 596 """Gets a task by name 597 NOTE: No GET route for tasks by OID 598 - project_name: Name of project to get task from 599 - board_name: Name of board to get task from 600 - list_name: Name of list to get task from 601 - card_name: Name of card to get task from 602 - task_name: Name of task to get 603 - **return:** GET response dictionary 604 """ 605 if not (project_name and board_name and list_name and card_name): 606 raise InvalidToken("Please provide project, board, list, and card names") 607 board_con = Board(self.instance) 608 board = board_con.get(project_name, board_name) 609 list_id = [ls for ls in board["included"]["lists"] if ls["name"] == list_name][0]["id"] 610 cards = [card for card in board["included"]["cards"] if card["name"] == card_name and card["listId"] == list_id] 611 card_id = [card for card in cards if card["name"] == card_name][0]["id"] 612 tasks = [task for task in board["included"]["tasks"] if task["cardId"] == card_id] 613 task_names = [task["name"] for task in tasks] 614 if not task_name: 615 return tasks 616 if task_name not in task_names: 617 raise InvalidToken(f"Task `{task_name}` not found") 618 return [task for task in tasks if task["name"] == task_name][0] 619 620 def create(self, project_name:str=None, board_name:str=None, list_name:str=None, card_name:str=None, data:dict=None, card_id:str=None): 621 """Creates a new task 622 - card_id: ID of card to create task in (optional) 623 - project_name: Name of project to create task in 624 - board_name: Name of board to create task in 625 - list_name: Name of list to create task in 626 - card_name: Name of card to create task in 627 - **return:** POST response dictionary 628 """ 629 if not data: 630 data = self.data 631 if not data: 632 raise InvalidToken(f"Please Build a {type(self).__name__} before creating") 633 if card_id: 634 return super().create(f"/api/cards/{card_id}/tasks") 635 if not (project_name and board_name and list_name and card_name): 636 raise InvalidToken("Please provide project, board, list, and card names") 637 board_con = Board(self.instance) 638 board = board_con.get(project_name, board_name) 639 list_id = [ls for ls in board["included"]["lists"] if ls["name"] == list_name][0]["id"] 640 cards = [card for card in board["included"]["cards"] if card["name"] == card_name and card["listId"] == list_id] 641 card_id = [card for card in cards if card["name"] == card_name][0]["id"] 642 return super().create(f"/api/cards/{card_id}/tasks") 643 644 def update(self, project_name:str=None, board_name:str=None, list_name:str=None, card_name:str=None, task_name:str=None, data:dict=None, oid:str=None): 645 """Updates a task 646 - oid: Object ID of task to update (optional) 647 - project_name: Name of project to update task in 648 - board_name: Name of board to update task in 649 - list_name: Name of list to update task in 650 - card_name: Name of card to update task in 651 - task_name: Name of task to update 652 - **return:** PATCH response dictionary 653 """ 654 if not data: 655 data = self.data 656 if not data: 657 raise InvalidToken(f"Please Build a {type(self).__name__} before updating") 658 if oid: 659 return super().update(f"/api/tasks/{oid}") 660 if not (project_name and board_name and list_name and card_name and task_name): 661 raise InvalidToken("Please provide project, board, list, card, and task names") 662 task = self.get(project_name, board_name, list_name, card_name, task_name) 663 return super().update(f"/api/tasks/{task['id']}") 664 665 def delete(self, project_name:str=None, board_name:str=None, list_name:str=None, card_name:str=None, task_name:str=None, oid:str=None): 666 """Deletes a task 667 - oid: ID of task to delete (Use this if you already have the ID) 668 - project_name: Name of project to delete task from 669 - board_name: Name of board to delete task from 670 - list_name: Name of list to delete task from 671 - card_name: Name of card to delete task from 672 - task_name: Name of task to delete 673 - **return:** DELETE response dictionary 674 """ 675 if oid: 676 return super().delete(f"/api/tasks/{id}") 677 if not (project_name and board_name and list_name and card_name and task_name): 678 raise InvalidToken("Please provide project, board, list, card, and task names") 679 task = self.get(project_name, board_name, list_name, card_name, task_name) 680 return super().delete(f"/api/tasks/{task['id']}") 681 682class Attachment(Controller): 683 def __init__(self, instance:Planka, **kwargs) -> None: 684 self.instance = instance 685 self.template = instance.get_template("attachment") 686 self.data = self.build(**kwargs) 687 688class Stopwatch(Controller): 689 def __init__(self, instance:Planka, **kwargs) -> None: 690 self.instance = instance 691 self.template = instance.get_template("stopwatch") 692 self.data = self.build(**kwargs) 693 694class Background(Controller): 695 def __init__(self, instance:Planka, **kwargs) -> None: 696 self.instance = instance 697 self.template = instance.get_template("background") 698 self.options = instance.get_template("gradients") 699 self.data = self.build(**kwargs) 700 701 def gradients(self) -> dict: 702 """Gets all gradients 703 - **return:** GET response dictionary 704 """ 705 return self.options 706 707 def apply(self, prj_name:str): 708 """Applies a gradient to a project 709 - project: Name of project to apply gradient to 710 - **return:** PATCH response dictionary 711 """ 712 project = Project(self.instance) 713 prj_id = project.get(prj_name)["item"]["id"] 714 if "type" not in self.data.keys(): 715 raise InvalidToken("Please specify a background type: `gradient` | `image`") 716 if self.data["type"] == "gradient" and self.data["name"] not in self.options: 717 raise InvalidToken(f"Gradient {self.data['name']} not found: please choose from\n{self.options}") 718 return super().update(f"/api/projects/{prj_id}", data={"background": self.data}) 719 720 def clear(self, prj_name:str): 721 """Clears a gradient from a project 722 - project: Name of project to clear gradient from 723 - **return:** PATCH response dictionary 724 """ 725 project = Project(self.instance) 726 prj_id = project.get(prj_name)["item"]["id"] 727 return super().update(f"/api/projects/{prj_id}", data={"background": None}) 728 729class Comment(Controller): 730 def __init__(self, instance:Planka, **kwargs) -> None: 731 self.instance = instance 732 self.template = instance.get_template("comment-action") 733 self.data = self.build(**kwargs) 734 735class User(Controller): 736 def __init__(self, instance:Planka, **kwargs) -> None: 737 """Creates a user 738 - username: Username of user to create 739 - name: Display name of user to create 740 - password: Password of user to create 741 - email: Email of user to create 742 - subscribe: Subscibe user to own cards (default: False) 743 - organization: Organization of user to create (default: None) 744 - admin: Admin state of user to create (default: False) 745 """ 746 self.instance = instance 747 self.template = instance.get_template("user") 748 self.data = self.build(**kwargs) 749 750 def get(self, username:str=None): 751 """Gets a user 752 - username: Username of user to get (all if not provided) 753 - **return:** GET response dictionary 754 """ 755 if not username: 756 return super().get("/api/users")["items"] 757 users = super().get("/api/users")["items"] 758 names = [user["username"] for user in users] 759 if username not in names: 760 raise InvalidToken(f"User {username} not found") 761 return users[names.index(username)] 762 763 def create(self, data:dict=None): 764 """Creates a user 765 - data: Data dictionary to create user with (optional) 766 - **return:** POST response dictionary 767 """ 768 if not data: 769 data = self.data 770 if not data: 771 raise InvalidToken("Please either build a user or provide a data dictionary") 772 if self.data["username"] in [user["username"] for user in self.get()]: 773 raise InvalidToken(f"User {self.data['username']} already exists") 774 return super().create("/api/users", data=self.data) 775 776 def delete(self, username:str, oid:str=None): 777 """Deletes a user 778 - username: Username of user to delete 779 - oid: ID of user to delete (Use this if you already have the ID) 780 - **return:** DELETE response dictionary 781 """ 782 if oid: 783 return super().delete(f"/api/users/{oid}") 784 if username not in [user["username"] for user in self.get()]: 785 raise InvalidToken(f"User {username} not found") 786 return super().delete(f"/api/users/{self.get(username)['id']}") 787 788 def update(self, username:str, oid:str=None, data:dict=None): 789 """Updates a user 790 - username: Username of user to update 791 - oid: ID of user to update (Use this if you already have the ID) 792 - data: Data dictionary to update user with (optional) 793 - **return:** PATCH response dictionary 794 """ 795 user = self.get(username) 796 if not data: 797 data = self.data 798 if not data: 799 raise InvalidToken("Please either build a user or provide a data dictionary") 800 return super().update(f"/api/users/{user['id']}", data=data) 801 802class InvalidToken(Exception): 803 """General Error for invalid API inputs 804 """ 805 pass
10class Planka: 11 """API wrapper class for Planka 12 - url: URL of Planka instance 13 - username: Username of Planka user 14 - password: Password of Planka user 15 """ 16 def __init__(self, url:str, username:str, password:str, templates="config/templates.json"): 17 self.url = url 18 self.username = username 19 self.password = password 20 self.auth = None 21 with open(templates) as f: 22 self.templates = json.load(f) 23 self.authenticate() 24 25 def __repr__(self): 26 return f"<{type(self).__name__}:\n\tBase URL: {self.url}\n\tLogin User: {self.username}\n\tLogin Pass: {self.password}\n\tAPI Token: {self.auth}\n>" 27 28 def deauthenticate(self) -> bool: 29 """Deletes the auth token from the Planka API 30 - **return:** True if successful, False if not 31 """ 32 try: 33 self.request("DELETE", "/api/access-tokens/me") 34 self.auth = None 35 return True 36 except: 37 raise InvalidToken(f"No active access token assigned to this instance\n{self.__repr__()}") 38 39 def validate(self) -> bool: 40 """Validates the Planka API connection 41 - **return:** True if successful, False if not 42 """ 43 try: 44 self.request("GET", "/*") 45 return True 46 except: 47 raise InvalidToken(f"Invalid API credentials\n{self.__repr__()}") 48 49 def authenticate(self) -> bool: 50 """Gets an auth token from the Planka API 51 - **return:** True if successful, False if not 52 """ 53 try: 54 request = requests.post(f"{self.url}/api/access-tokens", data={'emailOrUsername': self.username, 'password': self.password}) 55 self.auth = request.json()['item'] 56 if not self.auth: 57 raise InvalidToken(f"Invalid API credentials\n{self.__repr__()}") 58 return True 59 except: 60 raise InvalidToken(f"Invalid API credentials\n{self.__repr__()}") 61 62 def request(self, method:str, endpoint:str, data:dict=None) -> dict: 63 """Makes a request to the Planka API 64 - method: HTTP method 65 - endpoint: API endpoint 66 - data: Data to send with request (default: None) 67 - **return:** JSON response from Planka API 68 """ 69 if not self.auth: 70 self.authenticate() 71 headers = \ 72 { 73 "Content-Type": "application/json", 74 "Authorization": f"Bearer {self.auth}" 75 } 76 url = f"{self.url}{endpoint}" 77 response = requests.request(method, url, headers=headers, json=data) 78 79 if response.status_code == 401: 80 raise InvalidToken("Invalid API credentials") 81 82 if response.status_code not in [200, 201]: 83 raise InvalidToken(f"Failed to {method} {url} with status code {response.status_code}") 84 85 try: 86 return response.json() 87 except: 88 raise InvalidToken(f"Failed to parse response from {url}") 89 90 def get_template(self, template:str) -> dict: 91 """Returns a template from the templates.json file 92 - template: Name of template to return 93 - **return:** Template dictionary 94 """ 95 try: 96 return self.templates[template] 97 except: 98 raise InvalidToken(f"Template not found: {template}")
API wrapper class for Planka
- url: URL of Planka instance
- username: Username of Planka user
- password: Password of Planka user
28 def deauthenticate(self) -> bool: 29 """Deletes the auth token from the Planka API 30 - **return:** True if successful, False if not 31 """ 32 try: 33 self.request("DELETE", "/api/access-tokens/me") 34 self.auth = None 35 return True 36 except: 37 raise InvalidToken(f"No active access token assigned to this instance\n{self.__repr__()}")
Deletes the auth token from the Planka API
- return: True if successful, False if not
39 def validate(self) -> bool: 40 """Validates the Planka API connection 41 - **return:** True if successful, False if not 42 """ 43 try: 44 self.request("GET", "/*") 45 return True 46 except: 47 raise InvalidToken(f"Invalid API credentials\n{self.__repr__()}")
Validates the Planka API connection
- return: True if successful, False if not
49 def authenticate(self) -> bool: 50 """Gets an auth token from the Planka API 51 - **return:** True if successful, False if not 52 """ 53 try: 54 request = requests.post(f"{self.url}/api/access-tokens", data={'emailOrUsername': self.username, 'password': self.password}) 55 self.auth = request.json()['item'] 56 if not self.auth: 57 raise InvalidToken(f"Invalid API credentials\n{self.__repr__()}") 58 return True 59 except: 60 raise InvalidToken(f"Invalid API credentials\n{self.__repr__()}")
Gets an auth token from the Planka API
- return: True if successful, False if not
62 def request(self, method:str, endpoint:str, data:dict=None) -> dict: 63 """Makes a request to the Planka API 64 - method: HTTP method 65 - endpoint: API endpoint 66 - data: Data to send with request (default: None) 67 - **return:** JSON response from Planka API 68 """ 69 if not self.auth: 70 self.authenticate() 71 headers = \ 72 { 73 "Content-Type": "application/json", 74 "Authorization": f"Bearer {self.auth}" 75 } 76 url = f"{self.url}{endpoint}" 77 response = requests.request(method, url, headers=headers, json=data) 78 79 if response.status_code == 401: 80 raise InvalidToken("Invalid API credentials") 81 82 if response.status_code not in [200, 201]: 83 raise InvalidToken(f"Failed to {method} {url} with status code {response.status_code}") 84 85 try: 86 return response.json() 87 except: 88 raise InvalidToken(f"Failed to parse response from {url}")
Makes a request to the Planka API
- method: HTTP method
- endpoint: API endpoint
- data: Data to send with request (default: None)
- return: JSON response from Planka API
90 def get_template(self, template:str) -> dict: 91 """Returns a template from the templates.json file 92 - template: Name of template to return 93 - **return:** Template dictionary 94 """ 95 try: 96 return self.templates[template] 97 except: 98 raise InvalidToken(f"Template not found: {template}")
Returns a template from the templates.json file
- template: Name of template to return
- return: Template dictionary
100class Controller(): 101 def __init__(self, instance:Planka) -> None: 102 """Controller class for Planka API 103 - instance: Planka API instance 104 """ 105 self.instance = instance 106 self.template:dict = None 107 self.data:dict = None 108 self.response:dict = None 109 110 def __str__(self) -> str: 111 """Returns a string representation of the controller object 112 - **return:** String representation of controller object 113 """ 114 return f"{type(self).__name__}:\n{json.dumps(self.data, sort_keys=True, indent=4)}" 115 116 def __repr__(self) -> str: 117 """Returns a string representation of the controller object 118 - **return:** String representation of controller object 119 """ 120 return f"<{type(self).__name__}({self.__class__.__bases__[0].__name__})>{self.__str__()}" 121 122 def build(self, **kwargs) -> dict: 123 """Builds the controller data 124 - **return:** Controller data dictionary 125 """ 126 if not kwargs: 127 return kwargs 128 valid_keys = self.template.keys() 129 data = {key: value for key, value in kwargs.items() if key in valid_keys} 130 self.data = data 131 return self.data 132 133 def create(self, route:str, data:dict=None) -> dict: 134 """Creates a new controller object (POST) 135 - route: Route for controller object POST request 136 - **return:** POST response dictionary 137 """ 138 if not data: 139 data = self.data 140 if not data: 141 raise InvalidToken(f"Please Build a {type(self).__name__} before creating") 142 self.response = self.instance.request("POST", route, data) 143 return self.response 144 145 def get(self, route:str) -> dict: 146 """Gets a controller object (GET) 147 - route: Route for controller object GET request 148 - **return:** GET response dictionary 149 """ 150 return self.instance.request("GET", route) 151 152 def update(self, route:str, data:dict=None) -> dict: 153 """Updates a controller object (PATCH) 154 - route: Route for controller object PATCH request 155 - oid: ID of controller object 156 - **return:** PATCH response dictionary 157 """ 158 if not data: 159 data = self.data 160 if not self.data: 161 raise InvalidToken(f"Please Build a {type(self).__name__} before updating") 162 self.response = self.instance.request("PATCH", route, data=data) 163 return self.response 164 165 def delete(self, route:str) -> dict: 166 """Deletes a controller object (DELETE) 167 - route: Route for controller object DELETE request 168 - oid: ID of controller object 169 - **return:** DELETE response dictionary 170 """ 171 return self.instance.request("DELETE", route) 172 173 def last_response(self) -> dict: 174 """Returns the last response from the controller object 175 - **return:** Last response dictionary 176 """ 177 return self.response
101 def __init__(self, instance:Planka) -> None: 102 """Controller class for Planka API 103 - instance: Planka API instance 104 """ 105 self.instance = instance 106 self.template:dict = None 107 self.data:dict = None 108 self.response:dict = None
Controller class for Planka API
- instance: Planka API instance
122 def build(self, **kwargs) -> dict: 123 """Builds the controller data 124 - **return:** Controller data dictionary 125 """ 126 if not kwargs: 127 return kwargs 128 valid_keys = self.template.keys() 129 data = {key: value for key, value in kwargs.items() if key in valid_keys} 130 self.data = data 131 return self.data
Builds the controller data
- return: Controller data dictionary
133 def create(self, route:str, data:dict=None) -> dict: 134 """Creates a new controller object (POST) 135 - route: Route for controller object POST request 136 - **return:** POST response dictionary 137 """ 138 if not data: 139 data = self.data 140 if not data: 141 raise InvalidToken(f"Please Build a {type(self).__name__} before creating") 142 self.response = self.instance.request("POST", route, data) 143 return self.response
Creates a new controller object (POST)
- route: Route for controller object POST request
- return: POST response dictionary
145 def get(self, route:str) -> dict: 146 """Gets a controller object (GET) 147 - route: Route for controller object GET request 148 - **return:** GET response dictionary 149 """ 150 return self.instance.request("GET", route)
Gets a controller object (GET)
- route: Route for controller object GET request
- return: GET response dictionary
152 def update(self, route:str, data:dict=None) -> dict: 153 """Updates a controller object (PATCH) 154 - route: Route for controller object PATCH request 155 - oid: ID of controller object 156 - **return:** PATCH response dictionary 157 """ 158 if not data: 159 data = self.data 160 if not self.data: 161 raise InvalidToken(f"Please Build a {type(self).__name__} before updating") 162 self.response = self.instance.request("PATCH", route, data=data) 163 return self.response
Updates a controller object (PATCH)
- route: Route for controller object PATCH request
- oid: ID of controller object
- return: PATCH response dictionary
165 def delete(self, route:str) -> dict: 166 """Deletes a controller object (DELETE) 167 - route: Route for controller object DELETE request 168 - oid: ID of controller object 169 - **return:** DELETE response dictionary 170 """ 171 return self.instance.request("DELETE", route)
Deletes a controller object (DELETE)
- route: Route for controller object DELETE request
- oid: ID of controller object
- return: DELETE response dictionary
179class Project(Controller): 180 def __init__(self, instance:Planka, **kwargs) -> None: 181 self.instance = instance 182 self.template = instance.get_template("project") 183 self.data = self.build(**kwargs) 184 185 def get(self, name:str=None, oid:str=None) -> dict: 186 """Gets a project by name 187 - oid: ID of project to get (optional) 188 - name: Name of project if None returns all projects 189 - **return:** GET response dictionary 190 """ 191 if oid: 192 return super().get(f"/api/projects/{oid}") 193 prjs = super().get("/api/projects") 194 if not name: 195 return prjs 196 prj_names = [prj["name"] for prj in prjs["items"]] 197 if name not in prj_names: 198 raise InvalidToken(f"Project {name} not found") 199 prj_id = [prj for prj in prjs["items"] if prj["name"] == name][0]["id"] 200 return super().get(f"/api/projects/{prj_id}") 201 202 def get_project_names(self) -> list: 203 """Gets a list of project names 204 - **return:** List of project names 205 """ 206 return [prj["name"] for prj in self.get()['items']] 207 208 def create(self) -> dict: 209 """Creates a new project 210 - **return:** POST response dictionary 211 """ 212 if not self.data: 213 raise InvalidToken(f"Please Build a {type(self).__name__} before creating") 214 if self.data["name"] in [prj["name"] for prj in self.get()['items']]: 215 raise InvalidToken(f"Project {self.data['name']} already exists") 216 return super().create("/api/projects") 217 218 def update(self, name:str) -> dict: 219 """Updates a project 220 - name: Name of project to update 221 - **return:** PATCH response dictionary 222 """ 223 prj_id = prj_id = self.get(name)['item']['id'] 224 return super().update(f"/api/projects/{prj_id}") 225 226 def delete(self, name:str) -> dict: 227 """Deletes a project 228 - name: Name of project to delete 229 - **return:** DELETE response dictionary 230 """ 231 prj_id = self.get(name)['item']['id'] 232 return super().delete(f"/api/projects/{prj_id}")
180 def __init__(self, instance:Planka, **kwargs) -> None: 181 self.instance = instance 182 self.template = instance.get_template("project") 183 self.data = self.build(**kwargs)
Controller class for Planka API
- instance: Planka API instance
185 def get(self, name:str=None, oid:str=None) -> dict: 186 """Gets a project by name 187 - oid: ID of project to get (optional) 188 - name: Name of project if None returns all projects 189 - **return:** GET response dictionary 190 """ 191 if oid: 192 return super().get(f"/api/projects/{oid}") 193 prjs = super().get("/api/projects") 194 if not name: 195 return prjs 196 prj_names = [prj["name"] for prj in prjs["items"]] 197 if name not in prj_names: 198 raise InvalidToken(f"Project {name} not found") 199 prj_id = [prj for prj in prjs["items"] if prj["name"] == name][0]["id"] 200 return super().get(f"/api/projects/{prj_id}")
Gets a project by name
- oid: ID of project to get (optional)
- name: Name of project if None returns all projects
- return: GET response dictionary
202 def get_project_names(self) -> list: 203 """Gets a list of project names 204 - **return:** List of project names 205 """ 206 return [prj["name"] for prj in self.get()['items']]
Gets a list of project names
- return: List of project names
208 def create(self) -> dict: 209 """Creates a new project 210 - **return:** POST response dictionary 211 """ 212 if not self.data: 213 raise InvalidToken(f"Please Build a {type(self).__name__} before creating") 214 if self.data["name"] in [prj["name"] for prj in self.get()['items']]: 215 raise InvalidToken(f"Project {self.data['name']} already exists") 216 return super().create("/api/projects")
Creates a new project
- return: POST response dictionary
218 def update(self, name:str) -> dict: 219 """Updates a project 220 - name: Name of project to update 221 - **return:** PATCH response dictionary 222 """ 223 prj_id = prj_id = self.get(name)['item']['id'] 224 return super().update(f"/api/projects/{prj_id}")
Updates a project
- name: Name of project to update
- return: PATCH response dictionary
226 def delete(self, name:str) -> dict: 227 """Deletes a project 228 - name: Name of project to delete 229 - **return:** DELETE response dictionary 230 """ 231 prj_id = self.get(name)['item']['id'] 232 return super().delete(f"/api/projects/{prj_id}")
Deletes a project
- name: Name of project to delete
- return: DELETE response dictionary
Inherited Members
234class Board(Controller): 235 def __init__(self, instance:Planka, **kwargs) -> None: 236 self.instance = instance 237 self.template = instance.get_template("board") 238 self.data = self.build(**kwargs) 239 240 def get(self, project_name:str=None, board_name:str=None, oid:str=None) -> dict: 241 """Gets a board by name 242 - oid: ID of board to get (optonal) 243 - name: Name of board if None returns all boards 244 - project_name: Name of project to get boards from 245 - **return:** GET response dictionary 246 """ 247 if oid: 248 return super().get(f"/api/boards/{oid}") 249 if not (project_name): 250 raise InvalidToken("Please provide a project name") 251 prj_con = Project(self.instance) 252 prj = prj_con.get(project_name) 253 boards = prj["included"]["boards"] 254 if not board_name: 255 return boards 256 board_names = [board["name"] for board in boards] 257 if board_name not in board_names: 258 raise InvalidToken(f"Board `{board_name}` not found") 259 board_id = [board for board in boards if board["name"] == board_name][0]["id"] 260 return super().get(f"/api/boards/{board_id}") 261 262 def create(self, project_name:str) -> dict: 263 """Creates a new board 264 - prj_name: Name of project to create board in 265 - **return:** POST response dictionary 266 """ 267 if not self.data: 268 raise InvalidToken(f"Please Build a {type(self).__name__} before creating") 269 prj_con = Project(self.instance) 270 prj_id = prj_con.get(project_name)['item']['id'] 271 return super().create(f"/api/projects/{prj_id}/boards") 272 273 def update(self, project_name:str=None, board_name:str=None, data:dict=None, oid:str=None) -> dict: 274 """Updates a board 275 - oid: ID of board to update (optional) 276 - project_name: Name of project to update board in 277 - board_name: Name of board to update 278 - **return:** PATCH response dictionary 279 """ 280 if not data: 281 data = self.data 282 if not data: 283 raise InvalidToken(f"Please Build a {type(self).__name__} before updating") 284 if oid: 285 return super().update(f"/api/boards/{oid}", data=data) 286 if not (project_name and board_name): 287 raise InvalidToken("Please provide project and board names") 288 board_id = self.get(project_name, board_name)['item']['id'] 289 return super().update(f"/api/boards/{board_id}", data=self.data) 290 291 def delete(self, project_name:str=None, board_name:str=None, oid:str=None): 292 """Deletes a board 293 - oid: ID of board to delete (optional) 294 - project_name: Name of project to delete board in 295 - board_name: Name of board to delete 296 - **return:** DELETE response dictionary 297 """ 298 if oid: 299 return super().delete(f"/api/boards/{oid}") 300 if not project_name: 301 raise InvalidToken("Please provide a project name") 302 if not board_name: 303 raise InvalidToken("Please provide a board name") 304 board_id = self.get(project_name, board_name)['item']['id'] 305 return super().delete(f"/api/boards/{board_id}")
235 def __init__(self, instance:Planka, **kwargs) -> None: 236 self.instance = instance 237 self.template = instance.get_template("board") 238 self.data = self.build(**kwargs)
Controller class for Planka API
- instance: Planka API instance
240 def get(self, project_name:str=None, board_name:str=None, oid:str=None) -> dict: 241 """Gets a board by name 242 - oid: ID of board to get (optonal) 243 - name: Name of board if None returns all boards 244 - project_name: Name of project to get boards from 245 - **return:** GET response dictionary 246 """ 247 if oid: 248 return super().get(f"/api/boards/{oid}") 249 if not (project_name): 250 raise InvalidToken("Please provide a project name") 251 prj_con = Project(self.instance) 252 prj = prj_con.get(project_name) 253 boards = prj["included"]["boards"] 254 if not board_name: 255 return boards 256 board_names = [board["name"] for board in boards] 257 if board_name not in board_names: 258 raise InvalidToken(f"Board `{board_name}` not found") 259 board_id = [board for board in boards if board["name"] == board_name][0]["id"] 260 return super().get(f"/api/boards/{board_id}")
Gets a board by name
- oid: ID of board to get (optonal)
- name: Name of board if None returns all boards
- project_name: Name of project to get boards from
- return: GET response dictionary
262 def create(self, project_name:str) -> dict: 263 """Creates a new board 264 - prj_name: Name of project to create board in 265 - **return:** POST response dictionary 266 """ 267 if not self.data: 268 raise InvalidToken(f"Please Build a {type(self).__name__} before creating") 269 prj_con = Project(self.instance) 270 prj_id = prj_con.get(project_name)['item']['id'] 271 return super().create(f"/api/projects/{prj_id}/boards")
Creates a new board
- prj_name: Name of project to create board in
- return: POST response dictionary
273 def update(self, project_name:str=None, board_name:str=None, data:dict=None, oid:str=None) -> dict: 274 """Updates a board 275 - oid: ID of board to update (optional) 276 - project_name: Name of project to update board in 277 - board_name: Name of board to update 278 - **return:** PATCH response dictionary 279 """ 280 if not data: 281 data = self.data 282 if not data: 283 raise InvalidToken(f"Please Build a {type(self).__name__} before updating") 284 if oid: 285 return super().update(f"/api/boards/{oid}", data=data) 286 if not (project_name and board_name): 287 raise InvalidToken("Please provide project and board names") 288 board_id = self.get(project_name, board_name)['item']['id'] 289 return super().update(f"/api/boards/{board_id}", data=self.data)
Updates a board
- oid: ID of board to update (optional)
- project_name: Name of project to update board in
- board_name: Name of board to update
- return: PATCH response dictionary
291 def delete(self, project_name:str=None, board_name:str=None, oid:str=None): 292 """Deletes a board 293 - oid: ID of board to delete (optional) 294 - project_name: Name of project to delete board in 295 - board_name: Name of board to delete 296 - **return:** DELETE response dictionary 297 """ 298 if oid: 299 return super().delete(f"/api/boards/{oid}") 300 if not project_name: 301 raise InvalidToken("Please provide a project name") 302 if not board_name: 303 raise InvalidToken("Please provide a board name") 304 board_id = self.get(project_name, board_name)['item']['id'] 305 return super().delete(f"/api/boards/{board_id}")
Deletes a board
- oid: ID of board to delete (optional)
- project_name: Name of project to delete board in
- board_name: Name of board to delete
- return: DELETE response dictionary
Inherited Members
307class List(Controller): 308 def __init__(self, instance:Planka, **kwargs) -> None: 309 self.instance = instance 310 self.template = instance.get_template("list") 311 self.data = self.build(**kwargs) 312 313 def get(self, project_name:str=None, board_name:str=None, list_name:str=None): 314 """Gets a list by name 315 NOTE: No GET route for list by ID 316 - project_name: Name of project to get list from 317 - board_name: Name of board to get list from 318 - list_name: Name of list to get 319 - **return:** GET response dictionary 320 """ 321 if not (project_name and board_name): 322 raise InvalidToken("Please provide project and board names") 323 board_con = Board(self.instance) 324 board = board_con.get(project_name, board_name) 325 lists = board["included"]["lists"] 326 list_names = [lst["name"] for lst in lists] 327 if not list_name: 328 return lists 329 if list_name not in list_names: 330 raise InvalidToken(f"List `{list_name}` not found") 331 return [lst for lst in lists if lst["name"] == list_name][0] 332 333 def create(self, project_name:str=None, board_name:str=None, data:dict=None): 334 """Creates a new list 335 - project_name: Name of project to create list in 336 - board_name: Name of board to create list in 337 - **return:** POST response dictionary 338 """ 339 if not data: 340 data = self.data 341 if not data: 342 raise InvalidToken(f"Please Build a {type(self).__name__} before creating") 343 if not (project_name and board_name): 344 raise InvalidToken("Please provide project and board name") 345 board_con = Board(self.instance) 346 board_id = board_con.get(project_name, board_name)['item']['id'] 347 return super().create(f"/api/boards/{board_id}/lists") 348 349 def update(self, project_name:str=None, board_name:str=None, list_name:str=None, data:dict=None, oid:str=None): 350 """Updates a list 351 - oid: ID of list to update (optional) 352 - project_name: Name of project to update list in 353 - board_name: Name of board to update list in 354 - list_name: Name of list to update 355 - **return:** PATCH response dictionary 356 """ 357 if not data: 358 data = self.data 359 if not data: 360 raise InvalidToken(f"Please Build a {type(self).__name__} before updating") 361 if oid: 362 return super().update(f"/api/lists/{oid}", data=data) 363 if not (project_name and board_name and list_name): 364 raise InvalidToken("Please provide project, board, and list names") 365 lst = self.get(project_name, board_name, list_name) 366 return super().update(f"/api/lists/{lst['id']}", data=data) 367 368 def delete(self, project_name:str=None, board_name:str=None, list_name:str=None, oid:str=None): 369 """Deletes a list 370 - oid: ID of list to delete (optional) 371 - project_name: Name of project to delete list in 372 - board_name: Name of board to delete list in 373 - list_name: Name of list to delete 374 - **return:** DELETE response dictionary 375 """ 376 if oid: 377 return super().delete(f"/api/lists/{oid}") 378 if not (project_name and board_name and list_name): 379 raise InvalidToken("Please provide a project, board, and list names") 380 lst = self.get(project_name, board_name, list_name) 381 return super().delete(f"/api/lists/{lst['id']}")
308 def __init__(self, instance:Planka, **kwargs) -> None: 309 self.instance = instance 310 self.template = instance.get_template("list") 311 self.data = self.build(**kwargs)
Controller class for Planka API
- instance: Planka API instance
313 def get(self, project_name:str=None, board_name:str=None, list_name:str=None): 314 """Gets a list by name 315 NOTE: No GET route for list by ID 316 - project_name: Name of project to get list from 317 - board_name: Name of board to get list from 318 - list_name: Name of list to get 319 - **return:** GET response dictionary 320 """ 321 if not (project_name and board_name): 322 raise InvalidToken("Please provide project and board names") 323 board_con = Board(self.instance) 324 board = board_con.get(project_name, board_name) 325 lists = board["included"]["lists"] 326 list_names = [lst["name"] for lst in lists] 327 if not list_name: 328 return lists 329 if list_name not in list_names: 330 raise InvalidToken(f"List `{list_name}` not found") 331 return [lst for lst in lists if lst["name"] == list_name][0]
Gets a list by name NOTE: No GET route for list by ID
- project_name: Name of project to get list from
- board_name: Name of board to get list from
- list_name: Name of list to get
- return: GET response dictionary
333 def create(self, project_name:str=None, board_name:str=None, data:dict=None): 334 """Creates a new list 335 - project_name: Name of project to create list in 336 - board_name: Name of board to create list in 337 - **return:** POST response dictionary 338 """ 339 if not data: 340 data = self.data 341 if not data: 342 raise InvalidToken(f"Please Build a {type(self).__name__} before creating") 343 if not (project_name and board_name): 344 raise InvalidToken("Please provide project and board name") 345 board_con = Board(self.instance) 346 board_id = board_con.get(project_name, board_name)['item']['id'] 347 return super().create(f"/api/boards/{board_id}/lists")
Creates a new list
- project_name: Name of project to create list in
- board_name: Name of board to create list in
- return: POST response dictionary
349 def update(self, project_name:str=None, board_name:str=None, list_name:str=None, data:dict=None, oid:str=None): 350 """Updates a list 351 - oid: ID of list to update (optional) 352 - project_name: Name of project to update list in 353 - board_name: Name of board to update list in 354 - list_name: Name of list to update 355 - **return:** PATCH response dictionary 356 """ 357 if not data: 358 data = self.data 359 if not data: 360 raise InvalidToken(f"Please Build a {type(self).__name__} before updating") 361 if oid: 362 return super().update(f"/api/lists/{oid}", data=data) 363 if not (project_name and board_name and list_name): 364 raise InvalidToken("Please provide project, board, and list names") 365 lst = self.get(project_name, board_name, list_name) 366 return super().update(f"/api/lists/{lst['id']}", data=data)
Updates a list
- oid: ID of list to update (optional)
- project_name: Name of project to update list in
- board_name: Name of board to update list in
- list_name: Name of list to update
- return: PATCH response dictionary
368 def delete(self, project_name:str=None, board_name:str=None, list_name:str=None, oid:str=None): 369 """Deletes a list 370 - oid: ID of list to delete (optional) 371 - project_name: Name of project to delete list in 372 - board_name: Name of board to delete list in 373 - list_name: Name of list to delete 374 - **return:** DELETE response dictionary 375 """ 376 if oid: 377 return super().delete(f"/api/lists/{oid}") 378 if not (project_name and board_name and list_name): 379 raise InvalidToken("Please provide a project, board, and list names") 380 lst = self.get(project_name, board_name, list_name) 381 return super().delete(f"/api/lists/{lst['id']}")
Deletes a list
- oid: ID of list to delete (optional)
- project_name: Name of project to delete list in
- board_name: Name of board to delete list in
- list_name: Name of list to delete
- return: DELETE response dictionary
Inherited Members
383class Card(Controller): 384 def __init__(self, instance:Planka, **kwargs) -> None: 385 self.instance = instance 386 self.template = instance.get_template("card") 387 self.data = self.build(**kwargs) 388 389 def get(self, project_name:str=None, board_name:str=None, list_name:str=None, card_name:str=None, oid:str=None): 390 """Gets a card by name 391 - oid: ID of card to get (optional) 392 - project_name: Name of project to get card from 393 - board_name: Name of board to get card from 394 - list_name: Name of list to get card from 395 - card_name: Name of card to get 396 - **return:** GET response dictionary 397 """ 398 if oid != None: 399 return super().get(f"/api/cards/{oid}") 400 if not (project_name and board_name and list_name): 401 raise InvalidToken("Please provide project, board, and list names") 402 board_con = Board(self.instance) 403 board = board_con.get(project_name, board_name) 404 lst_id = [ls for ls in board["included"]["lists"] if ls["name"] == list_name][0]["id"] 405 cards = [card for card in board["included"]["cards"] if card["listId"] == lst_id] 406 card_names = [card["name"] for card in cards] 407 if not card_name: 408 return [self.get(oid=card["id"]) for card in cards] 409 if card_name not in card_names: 410 raise InvalidToken(f"Card `{card_name}` not found") 411 card_id = [card for card in cards if card["name"] == card_name][0]['id'] 412 return super().get(f"/api/cards/{card_id}") 413 414 def create(self, project_name:str=None, board_name:str=None, list_name:str=None, data:dict=None): 415 """Creates a new card 416 - project_name: Name of project to create card in 417 - board_name: Name of board to create card in 418 - list_name: Name of list to create card in 419 - **return:** POST response dictionary 420 """ 421 if not data: 422 data = self.data 423 if not data: 424 raise InvalidToken(f"Please Build a {type(self).__name__} before creating") 425 if not (project_name and board_name and list_name): 426 raise InvalidToken("Please provide a project, board and list names") 427 board_con = Board(self.instance) 428 board = board_con.get(project_name, board_name) 429 lst_id = [ls for ls in board["included"]["lists"] if ls["name"] == list_name][0]["id"] 430 return super().create(f"/api/lists/{lst_id}/cards") 431 432 def delete(self, project_name:str=None, board_name:str=None, list_name:str=None, card_name:str=None, oid:str=None): 433 """Deletes a card 434 - oid: ID of card to delete (optional) 435 - project_name: Name of project to delete card in 436 - board_name: Name of board to delete card in 437 - list_name: Name of list to delete card in 438 - card_name: Name of card to delete 439 - **return:** DELETE response dictionary 440 """ 441 if oid != None: 442 return super().delete(f"/api/cards/{oid}") 443 if not (project_name and board_name and list_name and card_name): 444 raise InvalidToken("Please provide a project, board, list, and card name") 445 card = self.get(project_name, board_name, list_name, card_name) 446 return super().delete(f"/api/cards/{card['id']}") 447 448 def update(self, project_name:str=None, board_name:str=None, list_name:str=None, card_name:str=None, data:dict=None, oid:str=None): 449 """Updates a card 450 - oid: ID of card to update (optional) 451 - project_name: Name of project to update card in 452 - board_name: Name of board to update card in 453 - list_name: Name of list to update card in 454 - card_name: Name of card to update 455 - **return:** PATCH response dictionary 456 """ 457 if not data: 458 data = self.data 459 if not data: 460 raise InvalidToken(f"Please Build a {type(self).__name__} before updating") 461 if oid: 462 return super().update(f"/api/cards/{oid}", data=data) 463 if not (project_name and board_name and list_name and card_name): 464 raise InvalidToken("Please provide a project, board, list, and card name") 465 card = self.get(project_name, board_name, list_name, card_name) 466 return super().update(f"/api/cards/{card['id']}", data=data) 467 468 def get_labels(self, project_name:str=None, board_name:str=None, list_name:str=None, card_name:str=None, oid:str=None): 469 """Gets labels for a card 470 - oid: ID of card to get labels from (optional) 471 - project_name: Name of project to get card from 472 - board_name: Name of board to get card from 473 - list_name: Name of list to get card from 474 - card_name: Name of card to get 475 - **return:** GET response dictionary 476 """ 477 if oid: 478 return self.get(oid=oid)['included']['cardLabels'] 479 if not (project_name and board_name and list_name and card_name): 480 raise InvalidToken("Please provide project, board, list, and card names") 481 card_id = self.get(project_name, board_name, list_name, card_name)['item']['id'] 482 return self.get(oid=card_id)['included']['cardLabels']
384 def __init__(self, instance:Planka, **kwargs) -> None: 385 self.instance = instance 386 self.template = instance.get_template("card") 387 self.data = self.build(**kwargs)
Controller class for Planka API
- instance: Planka API instance
389 def get(self, project_name:str=None, board_name:str=None, list_name:str=None, card_name:str=None, oid:str=None): 390 """Gets a card by name 391 - oid: ID of card to get (optional) 392 - project_name: Name of project to get card from 393 - board_name: Name of board to get card from 394 - list_name: Name of list to get card from 395 - card_name: Name of card to get 396 - **return:** GET response dictionary 397 """ 398 if oid != None: 399 return super().get(f"/api/cards/{oid}") 400 if not (project_name and board_name and list_name): 401 raise InvalidToken("Please provide project, board, and list names") 402 board_con = Board(self.instance) 403 board = board_con.get(project_name, board_name) 404 lst_id = [ls for ls in board["included"]["lists"] if ls["name"] == list_name][0]["id"] 405 cards = [card for card in board["included"]["cards"] if card["listId"] == lst_id] 406 card_names = [card["name"] for card in cards] 407 if not card_name: 408 return [self.get(oid=card["id"]) for card in cards] 409 if card_name not in card_names: 410 raise InvalidToken(f"Card `{card_name}` not found") 411 card_id = [card for card in cards if card["name"] == card_name][0]['id'] 412 return super().get(f"/api/cards/{card_id}")
Gets a card by name
- oid: ID of card to get (optional)
- project_name: Name of project to get card from
- board_name: Name of board to get card from
- list_name: Name of list to get card from
- card_name: Name of card to get
- return: GET response dictionary
414 def create(self, project_name:str=None, board_name:str=None, list_name:str=None, data:dict=None): 415 """Creates a new card 416 - project_name: Name of project to create card in 417 - board_name: Name of board to create card in 418 - list_name: Name of list to create card in 419 - **return:** POST response dictionary 420 """ 421 if not data: 422 data = self.data 423 if not data: 424 raise InvalidToken(f"Please Build a {type(self).__name__} before creating") 425 if not (project_name and board_name and list_name): 426 raise InvalidToken("Please provide a project, board and list names") 427 board_con = Board(self.instance) 428 board = board_con.get(project_name, board_name) 429 lst_id = [ls for ls in board["included"]["lists"] if ls["name"] == list_name][0]["id"] 430 return super().create(f"/api/lists/{lst_id}/cards")
Creates a new card
- project_name: Name of project to create card in
- board_name: Name of board to create card in
- list_name: Name of list to create card in
- return: POST response dictionary
432 def delete(self, project_name:str=None, board_name:str=None, list_name:str=None, card_name:str=None, oid:str=None): 433 """Deletes a card 434 - oid: ID of card to delete (optional) 435 - project_name: Name of project to delete card in 436 - board_name: Name of board to delete card in 437 - list_name: Name of list to delete card in 438 - card_name: Name of card to delete 439 - **return:** DELETE response dictionary 440 """ 441 if oid != None: 442 return super().delete(f"/api/cards/{oid}") 443 if not (project_name and board_name and list_name and card_name): 444 raise InvalidToken("Please provide a project, board, list, and card name") 445 card = self.get(project_name, board_name, list_name, card_name) 446 return super().delete(f"/api/cards/{card['id']}")
Deletes a card
- oid: ID of card to delete (optional)
- project_name: Name of project to delete card in
- board_name: Name of board to delete card in
- list_name: Name of list to delete card in
- card_name: Name of card to delete
- return: DELETE response dictionary
448 def update(self, project_name:str=None, board_name:str=None, list_name:str=None, card_name:str=None, data:dict=None, oid:str=None): 449 """Updates a card 450 - oid: ID of card to update (optional) 451 - project_name: Name of project to update card in 452 - board_name: Name of board to update card in 453 - list_name: Name of list to update card in 454 - card_name: Name of card to update 455 - **return:** PATCH response dictionary 456 """ 457 if not data: 458 data = self.data 459 if not data: 460 raise InvalidToken(f"Please Build a {type(self).__name__} before updating") 461 if oid: 462 return super().update(f"/api/cards/{oid}", data=data) 463 if not (project_name and board_name and list_name and card_name): 464 raise InvalidToken("Please provide a project, board, list, and card name") 465 card = self.get(project_name, board_name, list_name, card_name) 466 return super().update(f"/api/cards/{card['id']}", data=data)
Updates a card
- oid: ID of card to update (optional)
- project_name: Name of project to update card in
- board_name: Name of board to update card in
- list_name: Name of list to update card in
- card_name: Name of card to update
- return: PATCH response dictionary
468 def get_labels(self, project_name:str=None, board_name:str=None, list_name:str=None, card_name:str=None, oid:str=None): 469 """Gets labels for a card 470 - oid: ID of card to get labels from (optional) 471 - project_name: Name of project to get card from 472 - board_name: Name of board to get card from 473 - list_name: Name of list to get card from 474 - card_name: Name of card to get 475 - **return:** GET response dictionary 476 """ 477 if oid: 478 return self.get(oid=oid)['included']['cardLabels'] 479 if not (project_name and board_name and list_name and card_name): 480 raise InvalidToken("Please provide project, board, list, and card names") 481 card_id = self.get(project_name, board_name, list_name, card_name)['item']['id'] 482 return self.get(oid=card_id)['included']['cardLabels']
Gets labels for a card
- oid: ID of card to get labels from (optional)
- project_name: Name of project to get card from
- board_name: Name of board to get card from
- list_name: Name of list to get card from
- card_name: Name of card to get
- return: GET response dictionary
Inherited Members
484class Label(Controller): 485 def __init__(self, instance:Planka, **kwargs) -> None: 486 self.instance = instance 487 self.template = instance.get_template("label") 488 self.options = instance.get_template("colors") 489 self.data = self.build(**kwargs) 490 491 def colors(self) -> list: 492 return self.options 493 494 def get(self, project_name:str=None, board_name:str=None, label_name:str=None) -> dict: 495 """Gets a label by name 496 - project_name: Name of project to get label from 497 - board_name: Name of board to get label from 498 - label_name: Name of label to get 499 - **return:** GET response dictionary 500 """ 501 if not (project_name and board_name): 502 raise InvalidToken("Please provide project and board names") 503 board_con = Board(self.instance) 504 board = board_con.get(project_name, board_name) 505 labels = board["included"]["labels"] 506 label_names = [label["name"] for label in labels] 507 if not label_name: 508 return labels 509 if label_name not in label_names: 510 raise InvalidToken(f"Label `{label_name}` not found") 511 return [label for label in labels if label["name"] == label_name][0] 512 513 def create(self, project_name:str=None, board_name:str=None, data:dict=None): 514 """Creates a new label 515 - project_name: Name of project to create label in 516 - board_name: Name of board to create label in 517 - **return:** POST response dictionary 518 """ 519 if not data: 520 data = self.data 521 if not data: 522 raise InvalidToken(f"Please Build a {type(self).__name__} before creating") 523 if not (project_name and board_name): 524 raise InvalidToken("Please provide project and board names") 525 board_con = Board(self.instance) 526 board = board_con.get(project_name, board_name)['item'] 527 return super().create(f"/api/boards/{board['id']}/labels") 528 529 def delete(self, project_name:str=None, board_name:str=None, label_name:str=None, oid:str=None): 530 """Deletes a label 531 - oid: ID of label to delete (optional) 532 - project_name: Name of project to delete label from 533 - board_name: Name of board to delete label from 534 - label_name: Name of label to delete 535 - **return:** DELETE response dictionary 536 """ 537 if oid: 538 return super().delete(f"/api/labels/{oid}") 539 if not (project_name and board_name and label_name): 540 raise InvalidToken("Please provide project, board, and label names") 541 label = self.get(project_name, board_name, label_name) 542 return super().delete(f"/api/labels/{label['id']}") 543 544 def add(self, project_name:str=None, board_name:str=None, list_name:str=None ,card_name:str=None, label_name:str=None, card_id:str=None, label_id:str=None): 545 """Adds a label to a card 546 - project_name: Name of project to add label to card in 547 - board_name: Name of board to add label to card in 548 - label_name: Name of label to add to card 549 - card_name: Name of card to add label to 550 - list_name: Name of list to add label to card in 551 - **return:** POST response dictionary 552 """ 553 if label_id and card_id: 554 return super().create(f"/api/cards/{card_id}/labels", data={"labelId":label_id}) 555 if not (project_name and board_name and label_name): 556 raise InvalidToken("Please provide a project, board, label name") 557 if card_id: 558 label = self.get(project_name, board_name, label_name) 559 return super().create(f"/api/cards/{card_id}/labels", data={"labelId":label['item']['id']}) 560 if not (card_name and list_name): 561 raise InvalidToken("Please provide a card and list name") 562 card_con = Card(self.instance) 563 card = card_con.get(project_name, board_name, list_name, card_name) 564 label = self.get(project_name, board_name, label_name) 565 return super().create(f"/api/cards/{card['item']['id']}/labels", {"labelId":label['item']['id']}) 566 567 def remove(self, project_name:str=None, board_name:str=None, list_name:str=None ,card_name:str=None, label_name:str=None, card_id:str=None, label_id:str=None): 568 """Removes a label from a card 569 - project_name: Name of project to remove label from card in 570 - board_name: Name of board to remove label from card in 571 - label_name: Name of label to remove from card 572 - card_name: Name of card to remove label from 573 - list_name: Name of list to remove label from card in 574 - **return:** DELETE response dictionary 575 """ 576 if label_id and card_id: 577 return super().delete(f"/api/cards/{card_id}/labels/{label_id}") 578 if not (project_name and board_name and label_name): 579 raise InvalidToken("Please provide a project, board, label name") 580 if card_id: 581 label_id = [label['id'] for label in Card(self.instance).get_labels(oid=card_id) if label['name'] == label_name][0] 582 return super().delete(f"/api/cards/{card_id}/labels/{label['item']['id']}") 583 if not (card_name and list_name): 584 raise InvalidToken("Please provide a card and list name") 585 card_con = Card(self.instance) 586 card = card_con.get(project_name, board_name, list_name, card_name) 587 label = self.get(project_name, board_name, label_name) 588 return super().delete(f"/api/cards/{card['item']['id']}/labels/{label['item']['id']}")
485 def __init__(self, instance:Planka, **kwargs) -> None: 486 self.instance = instance 487 self.template = instance.get_template("label") 488 self.options = instance.get_template("colors") 489 self.data = self.build(**kwargs)
Controller class for Planka API
- instance: Planka API instance
494 def get(self, project_name:str=None, board_name:str=None, label_name:str=None) -> dict: 495 """Gets a label by name 496 - project_name: Name of project to get label from 497 - board_name: Name of board to get label from 498 - label_name: Name of label to get 499 - **return:** GET response dictionary 500 """ 501 if not (project_name and board_name): 502 raise InvalidToken("Please provide project and board names") 503 board_con = Board(self.instance) 504 board = board_con.get(project_name, board_name) 505 labels = board["included"]["labels"] 506 label_names = [label["name"] for label in labels] 507 if not label_name: 508 return labels 509 if label_name not in label_names: 510 raise InvalidToken(f"Label `{label_name}` not found") 511 return [label for label in labels if label["name"] == label_name][0]
Gets a label by name
- project_name: Name of project to get label from
- board_name: Name of board to get label from
- label_name: Name of label to get
- return: GET response dictionary
513 def create(self, project_name:str=None, board_name:str=None, data:dict=None): 514 """Creates a new label 515 - project_name: Name of project to create label in 516 - board_name: Name of board to create label in 517 - **return:** POST response dictionary 518 """ 519 if not data: 520 data = self.data 521 if not data: 522 raise InvalidToken(f"Please Build a {type(self).__name__} before creating") 523 if not (project_name and board_name): 524 raise InvalidToken("Please provide project and board names") 525 board_con = Board(self.instance) 526 board = board_con.get(project_name, board_name)['item'] 527 return super().create(f"/api/boards/{board['id']}/labels")
Creates a new label
- project_name: Name of project to create label in
- board_name: Name of board to create label in
- return: POST response dictionary
529 def delete(self, project_name:str=None, board_name:str=None, label_name:str=None, oid:str=None): 530 """Deletes a label 531 - oid: ID of label to delete (optional) 532 - project_name: Name of project to delete label from 533 - board_name: Name of board to delete label from 534 - label_name: Name of label to delete 535 - **return:** DELETE response dictionary 536 """ 537 if oid: 538 return super().delete(f"/api/labels/{oid}") 539 if not (project_name and board_name and label_name): 540 raise InvalidToken("Please provide project, board, and label names") 541 label = self.get(project_name, board_name, label_name) 542 return super().delete(f"/api/labels/{label['id']}")
Deletes a label
- oid: ID of label to delete (optional)
- project_name: Name of project to delete label from
- board_name: Name of board to delete label from
- label_name: Name of label to delete
- return: DELETE response dictionary
544 def add(self, project_name:str=None, board_name:str=None, list_name:str=None ,card_name:str=None, label_name:str=None, card_id:str=None, label_id:str=None): 545 """Adds a label to a card 546 - project_name: Name of project to add label to card in 547 - board_name: Name of board to add label to card in 548 - label_name: Name of label to add to card 549 - card_name: Name of card to add label to 550 - list_name: Name of list to add label to card in 551 - **return:** POST response dictionary 552 """ 553 if label_id and card_id: 554 return super().create(f"/api/cards/{card_id}/labels", data={"labelId":label_id}) 555 if not (project_name and board_name and label_name): 556 raise InvalidToken("Please provide a project, board, label name") 557 if card_id: 558 label = self.get(project_name, board_name, label_name) 559 return super().create(f"/api/cards/{card_id}/labels", data={"labelId":label['item']['id']}) 560 if not (card_name and list_name): 561 raise InvalidToken("Please provide a card and list name") 562 card_con = Card(self.instance) 563 card = card_con.get(project_name, board_name, list_name, card_name) 564 label = self.get(project_name, board_name, label_name) 565 return super().create(f"/api/cards/{card['item']['id']}/labels", {"labelId":label['item']['id']})
Adds a label to a card
- project_name: Name of project to add label to card in
- board_name: Name of board to add label to card in
- label_name: Name of label to add to card
- card_name: Name of card to add label to
- list_name: Name of list to add label to card in
- return: POST response dictionary
567 def remove(self, project_name:str=None, board_name:str=None, list_name:str=None ,card_name:str=None, label_name:str=None, card_id:str=None, label_id:str=None): 568 """Removes a label from a card 569 - project_name: Name of project to remove label from card in 570 - board_name: Name of board to remove label from card in 571 - label_name: Name of label to remove from card 572 - card_name: Name of card to remove label from 573 - list_name: Name of list to remove label from card in 574 - **return:** DELETE response dictionary 575 """ 576 if label_id and card_id: 577 return super().delete(f"/api/cards/{card_id}/labels/{label_id}") 578 if not (project_name and board_name and label_name): 579 raise InvalidToken("Please provide a project, board, label name") 580 if card_id: 581 label_id = [label['id'] for label in Card(self.instance).get_labels(oid=card_id) if label['name'] == label_name][0] 582 return super().delete(f"/api/cards/{card_id}/labels/{label['item']['id']}") 583 if not (card_name and list_name): 584 raise InvalidToken("Please provide a card and list name") 585 card_con = Card(self.instance) 586 card = card_con.get(project_name, board_name, list_name, card_name) 587 label = self.get(project_name, board_name, label_name) 588 return super().delete(f"/api/cards/{card['item']['id']}/labels/{label['item']['id']}")
Removes a label from a card
- project_name: Name of project to remove label from card in
- board_name: Name of board to remove label from card in
- label_name: Name of label to remove from card
- card_name: Name of card to remove label from
- list_name: Name of list to remove label from card in
- return: DELETE response dictionary
Inherited Members
590class Task(Controller): 591 def __init__(self, instance:Planka, **kwargs) -> None: 592 self.instance = instance 593 self.template = instance.get_template("task") 594 self.data = self.build(**kwargs) 595 596 def get(self, project_name:str=None, board_name:str=None, list_name:str=None, card_name:str=None, task_name:str=None) -> dict: 597 """Gets a task by name 598 NOTE: No GET route for tasks by OID 599 - project_name: Name of project to get task from 600 - board_name: Name of board to get task from 601 - list_name: Name of list to get task from 602 - card_name: Name of card to get task from 603 - task_name: Name of task to get 604 - **return:** GET response dictionary 605 """ 606 if not (project_name and board_name and list_name and card_name): 607 raise InvalidToken("Please provide project, board, list, and card names") 608 board_con = Board(self.instance) 609 board = board_con.get(project_name, board_name) 610 list_id = [ls for ls in board["included"]["lists"] if ls["name"] == list_name][0]["id"] 611 cards = [card for card in board["included"]["cards"] if card["name"] == card_name and card["listId"] == list_id] 612 card_id = [card for card in cards if card["name"] == card_name][0]["id"] 613 tasks = [task for task in board["included"]["tasks"] if task["cardId"] == card_id] 614 task_names = [task["name"] for task in tasks] 615 if not task_name: 616 return tasks 617 if task_name not in task_names: 618 raise InvalidToken(f"Task `{task_name}` not found") 619 return [task for task in tasks if task["name"] == task_name][0] 620 621 def create(self, project_name:str=None, board_name:str=None, list_name:str=None, card_name:str=None, data:dict=None, card_id:str=None): 622 """Creates a new task 623 - card_id: ID of card to create task in (optional) 624 - project_name: Name of project to create task in 625 - board_name: Name of board to create task in 626 - list_name: Name of list to create task in 627 - card_name: Name of card to create task in 628 - **return:** POST response dictionary 629 """ 630 if not data: 631 data = self.data 632 if not data: 633 raise InvalidToken(f"Please Build a {type(self).__name__} before creating") 634 if card_id: 635 return super().create(f"/api/cards/{card_id}/tasks") 636 if not (project_name and board_name and list_name and card_name): 637 raise InvalidToken("Please provide project, board, list, and card names") 638 board_con = Board(self.instance) 639 board = board_con.get(project_name, board_name) 640 list_id = [ls for ls in board["included"]["lists"] if ls["name"] == list_name][0]["id"] 641 cards = [card for card in board["included"]["cards"] if card["name"] == card_name and card["listId"] == list_id] 642 card_id = [card for card in cards if card["name"] == card_name][0]["id"] 643 return super().create(f"/api/cards/{card_id}/tasks") 644 645 def update(self, project_name:str=None, board_name:str=None, list_name:str=None, card_name:str=None, task_name:str=None, data:dict=None, oid:str=None): 646 """Updates a task 647 - oid: Object ID of task to update (optional) 648 - project_name: Name of project to update task in 649 - board_name: Name of board to update task in 650 - list_name: Name of list to update task in 651 - card_name: Name of card to update task in 652 - task_name: Name of task to update 653 - **return:** PATCH response dictionary 654 """ 655 if not data: 656 data = self.data 657 if not data: 658 raise InvalidToken(f"Please Build a {type(self).__name__} before updating") 659 if oid: 660 return super().update(f"/api/tasks/{oid}") 661 if not (project_name and board_name and list_name and card_name and task_name): 662 raise InvalidToken("Please provide project, board, list, card, and task names") 663 task = self.get(project_name, board_name, list_name, card_name, task_name) 664 return super().update(f"/api/tasks/{task['id']}") 665 666 def delete(self, project_name:str=None, board_name:str=None, list_name:str=None, card_name:str=None, task_name:str=None, oid:str=None): 667 """Deletes a task 668 - oid: ID of task to delete (Use this if you already have the ID) 669 - project_name: Name of project to delete task from 670 - board_name: Name of board to delete task from 671 - list_name: Name of list to delete task from 672 - card_name: Name of card to delete task from 673 - task_name: Name of task to delete 674 - **return:** DELETE response dictionary 675 """ 676 if oid: 677 return super().delete(f"/api/tasks/{id}") 678 if not (project_name and board_name and list_name and card_name and task_name): 679 raise InvalidToken("Please provide project, board, list, card, and task names") 680 task = self.get(project_name, board_name, list_name, card_name, task_name) 681 return super().delete(f"/api/tasks/{task['id']}")
591 def __init__(self, instance:Planka, **kwargs) -> None: 592 self.instance = instance 593 self.template = instance.get_template("task") 594 self.data = self.build(**kwargs)
Controller class for Planka API
- instance: Planka API instance
596 def get(self, project_name:str=None, board_name:str=None, list_name:str=None, card_name:str=None, task_name:str=None) -> dict: 597 """Gets a task by name 598 NOTE: No GET route for tasks by OID 599 - project_name: Name of project to get task from 600 - board_name: Name of board to get task from 601 - list_name: Name of list to get task from 602 - card_name: Name of card to get task from 603 - task_name: Name of task to get 604 - **return:** GET response dictionary 605 """ 606 if not (project_name and board_name and list_name and card_name): 607 raise InvalidToken("Please provide project, board, list, and card names") 608 board_con = Board(self.instance) 609 board = board_con.get(project_name, board_name) 610 list_id = [ls for ls in board["included"]["lists"] if ls["name"] == list_name][0]["id"] 611 cards = [card for card in board["included"]["cards"] if card["name"] == card_name and card["listId"] == list_id] 612 card_id = [card for card in cards if card["name"] == card_name][0]["id"] 613 tasks = [task for task in board["included"]["tasks"] if task["cardId"] == card_id] 614 task_names = [task["name"] for task in tasks] 615 if not task_name: 616 return tasks 617 if task_name not in task_names: 618 raise InvalidToken(f"Task `{task_name}` not found") 619 return [task for task in tasks if task["name"] == task_name][0]
Gets a task by name NOTE: No GET route for tasks by OID
- project_name: Name of project to get task from
- board_name: Name of board to get task from
- list_name: Name of list to get task from
- card_name: Name of card to get task from
- task_name: Name of task to get
- return: GET response dictionary
621 def create(self, project_name:str=None, board_name:str=None, list_name:str=None, card_name:str=None, data:dict=None, card_id:str=None): 622 """Creates a new task 623 - card_id: ID of card to create task in (optional) 624 - project_name: Name of project to create task in 625 - board_name: Name of board to create task in 626 - list_name: Name of list to create task in 627 - card_name: Name of card to create task in 628 - **return:** POST response dictionary 629 """ 630 if not data: 631 data = self.data 632 if not data: 633 raise InvalidToken(f"Please Build a {type(self).__name__} before creating") 634 if card_id: 635 return super().create(f"/api/cards/{card_id}/tasks") 636 if not (project_name and board_name and list_name and card_name): 637 raise InvalidToken("Please provide project, board, list, and card names") 638 board_con = Board(self.instance) 639 board = board_con.get(project_name, board_name) 640 list_id = [ls for ls in board["included"]["lists"] if ls["name"] == list_name][0]["id"] 641 cards = [card for card in board["included"]["cards"] if card["name"] == card_name and card["listId"] == list_id] 642 card_id = [card for card in cards if card["name"] == card_name][0]["id"] 643 return super().create(f"/api/cards/{card_id}/tasks")
Creates a new task
- card_id: ID of card to create task in (optional)
- project_name: Name of project to create task in
- board_name: Name of board to create task in
- list_name: Name of list to create task in
- card_name: Name of card to create task in
- return: POST response dictionary
645 def update(self, project_name:str=None, board_name:str=None, list_name:str=None, card_name:str=None, task_name:str=None, data:dict=None, oid:str=None): 646 """Updates a task 647 - oid: Object ID of task to update (optional) 648 - project_name: Name of project to update task in 649 - board_name: Name of board to update task in 650 - list_name: Name of list to update task in 651 - card_name: Name of card to update task in 652 - task_name: Name of task to update 653 - **return:** PATCH response dictionary 654 """ 655 if not data: 656 data = self.data 657 if not data: 658 raise InvalidToken(f"Please Build a {type(self).__name__} before updating") 659 if oid: 660 return super().update(f"/api/tasks/{oid}") 661 if not (project_name and board_name and list_name and card_name and task_name): 662 raise InvalidToken("Please provide project, board, list, card, and task names") 663 task = self.get(project_name, board_name, list_name, card_name, task_name) 664 return super().update(f"/api/tasks/{task['id']}")
Updates a task
- oid: Object ID of task to update (optional)
- project_name: Name of project to update task in
- board_name: Name of board to update task in
- list_name: Name of list to update task in
- card_name: Name of card to update task in
- task_name: Name of task to update
- return: PATCH response dictionary
666 def delete(self, project_name:str=None, board_name:str=None, list_name:str=None, card_name:str=None, task_name:str=None, oid:str=None): 667 """Deletes a task 668 - oid: ID of task to delete (Use this if you already have the ID) 669 - project_name: Name of project to delete task from 670 - board_name: Name of board to delete task from 671 - list_name: Name of list to delete task from 672 - card_name: Name of card to delete task from 673 - task_name: Name of task to delete 674 - **return:** DELETE response dictionary 675 """ 676 if oid: 677 return super().delete(f"/api/tasks/{id}") 678 if not (project_name and board_name and list_name and card_name and task_name): 679 raise InvalidToken("Please provide project, board, list, card, and task names") 680 task = self.get(project_name, board_name, list_name, card_name, task_name) 681 return super().delete(f"/api/tasks/{task['id']}")
Deletes a task
- oid: ID of task to delete (Use this if you already have the ID)
- project_name: Name of project to delete task from
- board_name: Name of board to delete task from
- list_name: Name of list to delete task from
- card_name: Name of card to delete task from
- task_name: Name of task to delete
- return: DELETE response dictionary
Inherited Members
683class Attachment(Controller): 684 def __init__(self, instance:Planka, **kwargs) -> None: 685 self.instance = instance 686 self.template = instance.get_template("attachment") 687 self.data = self.build(**kwargs)
684 def __init__(self, instance:Planka, **kwargs) -> None: 685 self.instance = instance 686 self.template = instance.get_template("attachment") 687 self.data = self.build(**kwargs)
Controller class for Planka API
- instance: Planka API instance
Inherited Members
689class Stopwatch(Controller): 690 def __init__(self, instance:Planka, **kwargs) -> None: 691 self.instance = instance 692 self.template = instance.get_template("stopwatch") 693 self.data = self.build(**kwargs)
690 def __init__(self, instance:Planka, **kwargs) -> None: 691 self.instance = instance 692 self.template = instance.get_template("stopwatch") 693 self.data = self.build(**kwargs)
Controller class for Planka API
- instance: Planka API instance
Inherited Members
695class Background(Controller): 696 def __init__(self, instance:Planka, **kwargs) -> None: 697 self.instance = instance 698 self.template = instance.get_template("background") 699 self.options = instance.get_template("gradients") 700 self.data = self.build(**kwargs) 701 702 def gradients(self) -> dict: 703 """Gets all gradients 704 - **return:** GET response dictionary 705 """ 706 return self.options 707 708 def apply(self, prj_name:str): 709 """Applies a gradient to a project 710 - project: Name of project to apply gradient to 711 - **return:** PATCH response dictionary 712 """ 713 project = Project(self.instance) 714 prj_id = project.get(prj_name)["item"]["id"] 715 if "type" not in self.data.keys(): 716 raise InvalidToken("Please specify a background type: `gradient` | `image`") 717 if self.data["type"] == "gradient" and self.data["name"] not in self.options: 718 raise InvalidToken(f"Gradient {self.data['name']} not found: please choose from\n{self.options}") 719 return super().update(f"/api/projects/{prj_id}", data={"background": self.data}) 720 721 def clear(self, prj_name:str): 722 """Clears a gradient from a project 723 - project: Name of project to clear gradient from 724 - **return:** PATCH response dictionary 725 """ 726 project = Project(self.instance) 727 prj_id = project.get(prj_name)["item"]["id"] 728 return super().update(f"/api/projects/{prj_id}", data={"background": None})
696 def __init__(self, instance:Planka, **kwargs) -> None: 697 self.instance = instance 698 self.template = instance.get_template("background") 699 self.options = instance.get_template("gradients") 700 self.data = self.build(**kwargs)
Controller class for Planka API
- instance: Planka API instance
702 def gradients(self) -> dict: 703 """Gets all gradients 704 - **return:** GET response dictionary 705 """ 706 return self.options
Gets all gradients
- return: GET response dictionary
708 def apply(self, prj_name:str): 709 """Applies a gradient to a project 710 - project: Name of project to apply gradient to 711 - **return:** PATCH response dictionary 712 """ 713 project = Project(self.instance) 714 prj_id = project.get(prj_name)["item"]["id"] 715 if "type" not in self.data.keys(): 716 raise InvalidToken("Please specify a background type: `gradient` | `image`") 717 if self.data["type"] == "gradient" and self.data["name"] not in self.options: 718 raise InvalidToken(f"Gradient {self.data['name']} not found: please choose from\n{self.options}") 719 return super().update(f"/api/projects/{prj_id}", data={"background": self.data})
Applies a gradient to a project
- project: Name of project to apply gradient to
- return: PATCH response dictionary
721 def clear(self, prj_name:str): 722 """Clears a gradient from a project 723 - project: Name of project to clear gradient from 724 - **return:** PATCH response dictionary 725 """ 726 project = Project(self.instance) 727 prj_id = project.get(prj_name)["item"]["id"] 728 return super().update(f"/api/projects/{prj_id}", data={"background": None})
Clears a gradient from a project
- project: Name of project to clear gradient from
- return: PATCH response dictionary
Inherited Members
730class Comment(Controller): 731 def __init__(self, instance:Planka, **kwargs) -> None: 732 self.instance = instance 733 self.template = instance.get_template("comment-action") 734 self.data = self.build(**kwargs)
731 def __init__(self, instance:Planka, **kwargs) -> None: 732 self.instance = instance 733 self.template = instance.get_template("comment-action") 734 self.data = self.build(**kwargs)
Controller class for Planka API
- instance: Planka API instance
Inherited Members
736class User(Controller): 737 def __init__(self, instance:Planka, **kwargs) -> None: 738 """Creates a user 739 - username: Username of user to create 740 - name: Display name of user to create 741 - password: Password of user to create 742 - email: Email of user to create 743 - subscribe: Subscibe user to own cards (default: False) 744 - organization: Organization of user to create (default: None) 745 - admin: Admin state of user to create (default: False) 746 """ 747 self.instance = instance 748 self.template = instance.get_template("user") 749 self.data = self.build(**kwargs) 750 751 def get(self, username:str=None): 752 """Gets a user 753 - username: Username of user to get (all if not provided) 754 - **return:** GET response dictionary 755 """ 756 if not username: 757 return super().get("/api/users")["items"] 758 users = super().get("/api/users")["items"] 759 names = [user["username"] for user in users] 760 if username not in names: 761 raise InvalidToken(f"User {username} not found") 762 return users[names.index(username)] 763 764 def create(self, data:dict=None): 765 """Creates a user 766 - data: Data dictionary to create user with (optional) 767 - **return:** POST response dictionary 768 """ 769 if not data: 770 data = self.data 771 if not data: 772 raise InvalidToken("Please either build a user or provide a data dictionary") 773 if self.data["username"] in [user["username"] for user in self.get()]: 774 raise InvalidToken(f"User {self.data['username']} already exists") 775 return super().create("/api/users", data=self.data) 776 777 def delete(self, username:str, oid:str=None): 778 """Deletes a user 779 - username: Username of user to delete 780 - oid: ID of user to delete (Use this if you already have the ID) 781 - **return:** DELETE response dictionary 782 """ 783 if oid: 784 return super().delete(f"/api/users/{oid}") 785 if username not in [user["username"] for user in self.get()]: 786 raise InvalidToken(f"User {username} not found") 787 return super().delete(f"/api/users/{self.get(username)['id']}") 788 789 def update(self, username:str, oid:str=None, data:dict=None): 790 """Updates a user 791 - username: Username of user to update 792 - oid: ID of user to update (Use this if you already have the ID) 793 - data: Data dictionary to update user with (optional) 794 - **return:** PATCH response dictionary 795 """ 796 user = self.get(username) 797 if not data: 798 data = self.data 799 if not data: 800 raise InvalidToken("Please either build a user or provide a data dictionary") 801 return super().update(f"/api/users/{user['id']}", data=data)
737 def __init__(self, instance:Planka, **kwargs) -> None: 738 """Creates a user 739 - username: Username of user to create 740 - name: Display name of user to create 741 - password: Password of user to create 742 - email: Email of user to create 743 - subscribe: Subscibe user to own cards (default: False) 744 - organization: Organization of user to create (default: None) 745 - admin: Admin state of user to create (default: False) 746 """ 747 self.instance = instance 748 self.template = instance.get_template("user") 749 self.data = self.build(**kwargs)
Creates a user
- username: Username of user to create
- name: Display name of user to create
- password: Password of user to create
- email: Email of user to create
- subscribe: Subscibe user to own cards (default: False)
- organization: Organization of user to create (default: None)
- admin: Admin state of user to create (default: False)
751 def get(self, username:str=None): 752 """Gets a user 753 - username: Username of user to get (all if not provided) 754 - **return:** GET response dictionary 755 """ 756 if not username: 757 return super().get("/api/users")["items"] 758 users = super().get("/api/users")["items"] 759 names = [user["username"] for user in users] 760 if username not in names: 761 raise InvalidToken(f"User {username} not found") 762 return users[names.index(username)]
Gets a user
- username: Username of user to get (all if not provided)
- return: GET response dictionary
764 def create(self, data:dict=None): 765 """Creates a user 766 - data: Data dictionary to create user with (optional) 767 - **return:** POST response dictionary 768 """ 769 if not data: 770 data = self.data 771 if not data: 772 raise InvalidToken("Please either build a user or provide a data dictionary") 773 if self.data["username"] in [user["username"] for user in self.get()]: 774 raise InvalidToken(f"User {self.data['username']} already exists") 775 return super().create("/api/users", data=self.data)
Creates a user
- data: Data dictionary to create user with (optional)
- return: POST response dictionary
777 def delete(self, username:str, oid:str=None): 778 """Deletes a user 779 - username: Username of user to delete 780 - oid: ID of user to delete (Use this if you already have the ID) 781 - **return:** DELETE response dictionary 782 """ 783 if oid: 784 return super().delete(f"/api/users/{oid}") 785 if username not in [user["username"] for user in self.get()]: 786 raise InvalidToken(f"User {username} not found") 787 return super().delete(f"/api/users/{self.get(username)['id']}")
Deletes a user
- username: Username of user to delete
- oid: ID of user to delete (Use this if you already have the ID)
- return: DELETE response dictionary
789 def update(self, username:str, oid:str=None, data:dict=None): 790 """Updates a user 791 - username: Username of user to update 792 - oid: ID of user to update (Use this if you already have the ID) 793 - data: Data dictionary to update user with (optional) 794 - **return:** PATCH response dictionary 795 """ 796 user = self.get(username) 797 if not data: 798 data = self.data 799 if not data: 800 raise InvalidToken("Please either build a user or provide a data dictionary") 801 return super().update(f"/api/users/{user['id']}", data=data)
Updates a user
- username: Username of user to update
- oid: ID of user to update (Use this if you already have the ID)
- data: Data dictionary to update user with (optional)
- return: PATCH response dictionary
Inherited Members
General Error for invalid API inputs
Inherited Members
- builtins.Exception
- Exception
- builtins.BaseException
- with_traceback
- add_note