Passed
Push — master ( 072294...914db1 )
by Alexander
01:30
created

things3.things3_api   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 161
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 122
dl 0
loc 161
rs 10
c 0
b 0
f 0
wmc 30

9 Methods

Rating   Name   Duplication   Size   Complexity  
A Things3API.tag() 0 10 2
A Things3API.get_url() 0 3 1
A Things3API.mode_selector() 0 8 4
A Things3API.__init__() 0 20 3
C Things3API.on_get() 0 27 9
A Things3API.api() 0 14 2
A Things3API.api_filter() 0 9 5
A Things3API.main() 0 11 2
A Things3API.api_filter_reset() 0 4 1

1 Function

Rating   Name   Duplication   Size   Complexity  
A main() 0 3 1
1
#!/usr/bin/env python3
2
# -*- coding: utf-8 -*-
3
4
"""Simple read-only Things 3 Web Serivce."""
5
6
from __future__ import print_function
7
8
__author__ = "Alexander Willner"
9
__copyright__ = "Copyright 2020 Alexander Willner"
10
__credits__ = ["Alexander Willner"]
11
__license__ = "Apache License 2.0"
12
__version__ = "2.5.3"
13
__maintainer__ = "Alexander Willner"
14
__email__ = "[email protected]"
15
__status__ = "Development"
16
17
import sys
18
from os import getcwd
19
import json
20
import socket
21
from flask import Flask
22
from flask import Response
23
from flask import request
24
from werkzeug.serving import make_server
25
from things3.things3 import Things3
26
27
28
class Things3API():
29
    """API Wrapper for the simple read-only API for Things 3."""
30
31
    PATH = getcwd() + '/resources/'
32
    DEFAULT = 'kanban.html'
33
    things3 = None
34
    test_mode = "task"
35
    host = 'localhost'
36
    port = 15000
37
38
    def on_get(self, url=DEFAULT):
39
        """Handles other GET requests"""
40
        status = 200
41
        filename = self.PATH + url
42
        content_type = 'application/json'
43
        if filename.endswith('css'):
44
            content_type = 'text/css'
45
        if filename.endswith('html'):
46
            content_type = 'text/html'
47
        if filename.endswith('js'):
48
            content_type = 'text/javascript'
49
        if filename.endswith('png'):
50
            content_type = 'image/png'
51
        if filename.endswith('jpg'):
52
            content_type = 'image/jpeg'
53
        if filename.endswith('ico'):
54
            content_type = 'image/x-ico'
55
        try:
56
            with open(filename, 'rb') as source:
57
                data = source.read()
58
        except FileNotFoundError:
59
            data = 'not found'
60
            content_type = 'text'
61
            status = 404
62
        return Response(response=data,
63
                        content_type=content_type,
64
                        status=status)
65
66
    def mode_selector(self):
67
        """Switch between project and task mode"""
68
        try:
69
            mode = request.args.get('mode')
70
        except RuntimeError:
71
            mode = 'task'
72
        if mode == "project" or self.test_mode == "project":
73
            self.things3.mode_project()
74
75
    def tag(self, tag, area=None):
76
        """Get specific tag."""
77
        self.mode_selector()
78
        if area is not None:
79
            data = self.things3.get_tag_today(tag)
80
        else:
81
            data = self.things3.get_tag(tag)
82
        self.things3.mode_task()
83
        data = json.dumps(data)
84
        return Response(response=data, content_type='application/json')
85
86
    def api(self, command):
87
        """Return database as JSON strings."""
88
        if command in self.things3.functions:
89
            func = self.things3.functions[command]
90
            self.mode_selector()
91
            data = func(self.things3)
92
            self.things3.mode_task()
93
            data = json.dumps(data)
94
            return Response(response=data, content_type='application/json')
95
96
        data = json.dumps(self.things3.get_not_implemented())
97
        return Response(response=data,
98
                        content_type='application/json',
99
                        status=404)
100
101
    def get_url(self):
102
        """Get the public url for the endpoint"""
103
        return f"http://{socket.gethostname()}:{self.port}"
104
105
    def api_filter(self, mode, uuid):
106
        """Filter view by specific modifiers"""
107
        if mode == "area" and uuid != "":
108
            self.things3.filter = f"TASK.area = '{uuid}' AND"
109
        if mode == "project" and uuid != "":
110
            self.things3.filter = f"""
111
                (TASK.project = '{uuid}' OR HEADING.project = '{uuid}') AND
112
                """
113
        return Response(status=200)
114
115
    def api_filter_reset(self):
116
        """Reset filter modifiers"""
117
        self.things3.filter = ""
118
        return Response(status=200)
119
120
    def __init__(self, database=None, host=None, port=None):
121
        cfg = Things3.get_from_config(Things3.config, host, 'KANBANVIEW_HOST')
122
        self.host = cfg if cfg else self.host
123
        cfg = Things3.get_from_config(Things3.config, port, 'KANBANVIEW_PORT')
124
        self.port = cfg if cfg else self.port
125
126
        self.things3 = Things3(database=database)
127
        self.flask = Flask(__name__)
128
        self.flask.add_url_rule('/api/<command>', view_func=self.api)
129
        self.flask.add_url_rule('/api/url', view_func=self.get_url)
130
        self.flask.add_url_rule('/api/tag/<tag>', view_func=self.tag)
131
        self.flask.add_url_rule('/api/tag/<tag>/<area>', view_func=self.tag)
132
        self.flask.add_url_rule(
133
            '/api/filter/<mode>/<uuid>', view_func=self.api_filter)
134
        self.flask.add_url_rule('/api/filter/reset',
135
                                view_func=self.api_filter_reset)
136
        self.flask.add_url_rule('/<url>', view_func=self.on_get)
137
        self.flask.add_url_rule('/', view_func=self.on_get)
138
        self.flask.app_context().push()
139
        self.flask_context = None
140
141
    def main(self):
142
        """"Main function."""
143
        print(f"Serving at http://{self.host}:{self.port} ...")
144
145
        try:
146
            self.flask_context = make_server(
147
                self.host, self.port, self.flask, threaded=True)
148
            self.flask_context.serve_forever()
149
        except KeyboardInterrupt:
150
            print("Shutting down...")
151
            sys.exit(0)
152
153
154
def main():
155
    """Main entry point for CLI installation"""
156
    Things3API().main()
157
158
159
if __name__ == "__main__":
160
    main()
161