Add first version for usage as CLI Tool

NOT FINISHED!
This commit is contained in:
2024-02-25 12:37:47 +00:00
parent bec839e98e
commit 17f3f29c76
7 changed files with 125 additions and 127 deletions
+9
View File
@@ -1,3 +1,4 @@
import dataclasses
from dataclasses import dataclass
@@ -11,6 +12,14 @@ class AcAccount:
class_: str
status: str
def to_dict(self):
d = dataclasses.asdict(self)
d["class"] = d["class_"]
del d["class_"]
return d
def to_json(self):
return json.dumps(self.to_dict())
def account_from_json(a) -> AcAccount:
a["class_"] = a["class"]
+11
View File
@@ -1,3 +1,5 @@
import dataclasses
import json
from dataclasses import dataclass
@@ -47,6 +49,15 @@ class AcProject:
updated_on: int
url_path: str
def to_dict(self):
d = dataclasses.asdict(self)
d["class"] = d["class_"]
del d["class_"]
return d
def to_json(self):
return json.dumps(self.to_dict())
def project_from_json(json_obj: dict) -> AcProject:
json_obj["class_"] = json_obj["class"]
+10
View File
@@ -1,3 +1,5 @@
import dataclasses
import json
from dataclasses import dataclass
from ActiveCollabAPI.AcAccount import AcAccount
@@ -11,3 +13,11 @@ class AcSession:
accounts: [AcAccount]
cur_account: AcAccount
token: AcToken
def to_dict(self):
d = dataclasses.asdict(self)
d["accounts"] = list(map(lambda a: a.to_dict(), self.accounts))
return d
def to_json(self):
return json.dumps(self.to_dict())
+9
View File
@@ -1,3 +1,5 @@
import dataclasses
import json
from dataclasses import dataclass
@@ -7,3 +9,10 @@ class AcUser:
first_name: str
last_name: str
intent: str
def to_dict(self):
d = dataclasses.asdict(self)
return d
def to_json(self):
return json.dumps(self.to_dict())
+5
View File
@@ -53,6 +53,11 @@ class ActiveCollab:
raise Exception('Request token failed! (2)')
return AcToken(res_data["token"])
def get_info(self):
client = ACClient(self.session.cur_account, self.session.token)
res = client.get_info()
return res.json()
def get_projects(self):
client = ACClient(self.session.cur_account, self.session.token)
res = client.get_projects()
+18 -28
View File
@@ -14,39 +14,29 @@
2. Request a token for some Account (user could have multiple accounts) using the Account `url` and the users `intent`
3. Use the token to authenticate for every other API-Call
Example login:
## CLI (Work in progress)
```python
auth = ACAuthenticator()
session = auth.login(username, password)
pprint(session)
user = session['user']
# use first account
account = auth.find_account(account_name)
```
> activecollab [options] [command] [object] [arguments]
Example request token
### Options
```python
token = ACTokenAuthenticator(account, user).issue_token_intent()
pprint(token)
```
- config=<file> - read config from file (json)
Example client request
### Command
```python
ac = ACClient(account, token)
res = ac.get_projects()
pprint(res.json())
```
- list
- get
- create
- update
- delete
-
### Object
## Configuration
- info - get Info like version nummer from server
- project
- task
- subtask
- people
- invoice
config.ini:
```ini
[LOGIN]
username = you@example.com
password = very-secret
account_name = "#123456"
```
+63 -99
View File
@@ -1,115 +1,79 @@
import configparser
import logging
import argparse
import json
from pprint import pprint
from ActiveCollabAPI import AC_LOGIN_BASE_URL
from ActiveCollabAPI.ACAuthenticator import ACAuthenticator
from ActiveCollabAPI.ACClient import ACClient
from ActiveCollabAPI.ACTokenAuthenticator import ACTokenAuthenticator
# Enabling debugging at http.client level (requests->urllib3->http.client)
# you will see the REQUEST, including HEADERS and DATA, and RESPONSE with HEADERS but without DATA.
# the only thing missing will be the response.body which is not logged.
from http.client import HTTPConnection
from ActiveCollabAPI.ActiveCollab import ActiveCollab
HTTPConnection.debuglevel = 1
logging.basicConfig() # you need to initialize logging, otherwise you will not see anything from requests
logging.getLogger().setLevel(logging.DEBUG)
requests_log = logging.getLogger("urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True
def run(args, config):
# run the command
ac = ActiveCollab(config["base_url"])
ac.login_to_first_account(config["username"], config["password"])
output = None
if args.object == "info":
output = run_info(ac, args)
if args.object == "account":
output = run_account(ac, args)
if args.object == "project":
output = run_project(ac, args, output)
return output
def run_account(ac, args):
if args.command == "list":
return ac.session.to_dict()
def run_project(ac, args, output):
if args.command == "list":
output = list(map(lambda p: p.to_dict(), ac.get_projects()))
return output
def run_info(ac, args):
output = ac.get_info()
return output
def serialize_output(output):
# serialize the output
return json.dumps(output)
def load_config(args):
with open(args.config, "r", encoding="utf8") as fh:
config = json.load(fh)
return config
def main():
config = configparser.ConfigParser()
config.read('config.ini')
# parse arguments
parser = argparse.ArgumentParser(
prog='activecollab',
description='This is a CLI Client to Active-Collab Service',
epilog='This is not a offical tool provided by the AC Guys!')
parser.add_argument('-c', '--config', required=True,
help="use the named config file")
parser.add_argument('-v', '--version', action='store_true',
help="show version information for this tool")
username = config['LOGIN']['username']
password = config['LOGIN']['password']
account_name = config['LOGIN']['account_name']
subparsers = parser.add_subparsers(help='sub-command help')
# 1. you need to log in to get a session and a list of accounts
ac = ActiveCollab(AC_LOGIN_BASE_URL)
session = ac.login(username, password)
account = ac.get_account(session) # default returning first account
token = ac.get_token(account, session.user)
# account list
parser_account = subparsers.add_parser('account', help='account help')
parser_account.set_defaults(object='account')
parser_account.add_argument('command', choices=["list"], help='list all accessible accounts')
# 2. you need to get a token for this account
token = ACTokenAuthenticator(account, auth.user).issue_token_intent()
# pprint(token)
# project list get
parser_project = subparsers.add_parser('project', help='project help')
parser_project.set_defaults(object='project')
parser_project.add_argument('command', choices=["list"], help='what to do with the account help')
# 3. now you can query the API
ac = ACClient(account, token)
args = parser.parse_args()
# check version
res = ac.get_info()
ac_version = res.json()
pprint(ac_version)
if ac_version['version'] != '7.4.337':
raise Exception("Supporting only Version 7.4.337!")
# get a list of all projects
# res = ac.get_projects()
# pprint(res.json())
# get task with sub-tasks
# "https://app.activecollab.com/416910/projects/310/tasks/6260"
# project_id = 310
# task_id = 6260
# task_res = ac.get_project_task(project_id, task_id)
# pprint(task_res.json())
# task = task_res.json()
# ac.delete_all_open_subtasks(task)
# create a new subtask for the task without assignment
# subtask = {
# "task_id": task_id,
# "project_id": project_id,
# "assignee_id": "",
# "body": "test qqq"
# }
# res = ac.create_subtask(subtask)
# pprint(res.json())
# # bulk create subtasks
# subtask_texts = []
# with open("sub-tasks.txt", "r", encoding="utf-8") as fh:
# for line in fh:
# subtask_texts.append(line.rstrip('\n'))
# create
# for text in subtask_texts:
# subtask['body'] = text
# ac.create_subtask(subtask)
# delete
# for text in subtask_texts:
# subtask = ac.find_subtask_with_body(task, text)
# if subtask is not None:
# print(subtask)
# ac.delete_subtask(subtask)
# # unassign a single subtask
# subtask_id = 12203
# subtask_res = ac.get_project_task_subtask(project_id, task_id, subtask_id)
# # pprint(subtask_res.json())
# subtask = subtask_res.json()['single']
# res = ac.unassign_sub_task(subtask)
# pprint(res.json())
# delete a subtask
# subtask = {
# "task_id": task_id,
# "project_id": project_id,
# "id": 13764
# }
# res = ac.delete_subtask(subtask)
# pprint(res.json())
res = ac.get_projects()
if res.status_code == 200:
pprint(res.json())
config = load_config(args)
output = run(args, config)
print(serialize_output(output))
if __name__ == "__main__":