things3.things3_api.Things3API.__init__()   A
last analyzed

Complexity

Conditions 4

Size

Total Lines 34
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 28
nop 6
dl 0
loc 34
rs 9.208
c 0
b 0
f 0
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
import sys
9
from os import getcwd
10
import json
11
import socket
12
from flask import Flask
13
from flask import Response
14
from flask import request
15
from werkzeug.serving import make_server
16
from things3.things3 import Things3
17
18
19
class Things3API:
20
    """API Wrapper for the simple read-only API for Things 3."""
21
22
    PATH = getcwd() + "/resources/"
23
    DEFAULT = "kanban.html"
24
    test_mode = "task"
25
    host = "localhost"
26
    port = 15000
27
28
    def on_get(self, url=DEFAULT):
29
        """Handles other GET requests"""
30
        status = 200
31
        filename = self.PATH + url
32
        content_type = "application/json"
33
        if filename.endswith("css"):
34
            content_type = "text/css"
35
        if filename.endswith("html"):
36
            content_type = "text/html"
37
        if filename.endswith("js"):
38
            content_type = "text/javascript"
39
        if filename.endswith("png"):
40
            content_type = "image/png"
41
        if filename.endswith("jpg"):
42
            content_type = "image/jpeg"
43
        if filename.endswith("ico"):
44
            content_type = "image/x-ico"
45
        try:
46
            with open(filename, "rb") as source:
47
                data = source.read()
48
        except FileNotFoundError:
49
            data = "not found"
50
            content_type = "text"
51
            status = 404
52
        return Response(response=data, content_type=content_type, status=status)
53
54
    def mode_selector(self):
55
        """Switch between project and task mode"""
56
        try:
57
            mode = request.args.get("mode")
58
        except RuntimeError:
59
            mode = "task"
60
        if mode == "project" or self.test_mode == "project":
61
            self.things3.mode_project()
62
63
    def config_get(self, key):
64
        """Read key from config"""
65
        data = self.things3.get_config(key)
66
        return Response(response=data)
67
68
    def config_set(self, key):
69
        """Write key to config"""
70
        value = request.get_data().decode("utf-8").strip()
71
        if value:
72
            self.things3.set_config(key, value)
73
        return Response()
74
75
    def seinfeld(self, tag):
76
        """Get tasks logged recently with a specific tag."""
77
        data = self.things3.get_seinfeld(tag)
78
        data = json.dumps(data)
79
        return Response(response=data, content_type="application/json")
80
81
    def tag(self, tag, area=None):
82
        """Get specific tag."""
83
        self.mode_selector()
84
        if area is not None:
85
            data = self.things3.get_tag_today(tag)
86
        else:
87
            data = self.things3.get_tag(tag)
88
        self.things3.mode_task()
89
        data = json.dumps(data)
90
        return Response(response=data, content_type="application/json")
91
92
    def api(self, command):
93
        """Return database as JSON strings."""
94
        if command in self.things3.functions:
95
            func = self.things3.functions[command]
96
            self.mode_selector()
97
            data = func(self.things3)
98
            self.things3.mode_task()
99
            data = json.dumps(data)
100
            return Response(response=data, content_type="application/json")
101
102
        data = json.dumps(self.things3.get_not_implemented())
103
        return Response(response=data, content_type="application/json", status=404)
104
105
    def get_url(self):
106
        """Get the public url for the endpoint"""
107
        fqdn = f"{socket.gethostname()}.local"  # pylint: disable=E1101
108
        return f"http://{fqdn}:{self.port}"
109
110
    def api_filter(self, mode, uuid):
111
        """Filter view by specific modifiers"""
112
        if mode == "area" and uuid != "":
113
            self.things3.filter = f"TASK.area = '{uuid}' AND"
114
            self.things3.filter_area = uuid
115
        if mode == "project" and uuid != "":
116
            self.things3.filter = f"""
117
                (TASK.project = '{uuid}' OR HEADING.project = '{uuid}') AND
118
                """
119
            self.things3.filter_project = uuid
120
        return Response(status=200)
121
122
    def api_filter_reset(self):
123
        """Reset filter modifiers"""
124
        self.things3.filter = ""
125
        self.things3.filter_project = None
126
        self.things3.filter_area = None
127
        return Response(status=200)
128
129
    def __init__(self, database=None, host=None, port=None, expose=None, debug_text=""):
130
        # pylint: disable-msg=too-many-arguments
131
        self.things3 = Things3(database=database, debug_text=debug_text)
132
133
        cfg = self.things3.get_from_config(host, "KANBANVIEW_HOST")
134
        self.host = cfg if cfg else self.host
135
        self.things3.set_config("KANBANVIEW_HOST", self.host)
136
137
        cfg = self.things3.get_from_config(port, "KANBANVIEW_PORT")
138
        self.port = cfg if cfg else self.port
139
        self.things3.set_config("KANBANVIEW_PORT", self.port)
140
141
        cfg = self.things3.get_from_config(expose, "API_EXPOSE")
142
        self.host = "0.0.0.0" if (str(cfg).lower() == "true") else "localhost"
143
        self.things3.set_config("KANBANVIEW_HOST", self.host)
144
        self.things3.set_config("API_EXPOSE", str(cfg).lower() == "true")
145
146
        self.flask = Flask(__name__)
147
        self.flask.add_url_rule("/config/<key>", view_func=self.config_get)
148
        self.flask.add_url_rule(
149
            "/config/<key>", view_func=self.config_set, methods=["PUT"]
150
        )
151
        self.flask.add_url_rule("/api/<command>", view_func=self.api)
152
        self.flask.add_url_rule("/api/<command>", view_func=self.api, methods=["PUT"])
153
        self.flask.add_url_rule("/api/url", view_func=self.get_url)
154
        self.flask.add_url_rule("/api/seinfeld/<tag>", view_func=self.seinfeld)
155
        self.flask.add_url_rule("/api/tag/<tag>", view_func=self.tag)
156
        self.flask.add_url_rule("/api/tag/<tag>/<area>", view_func=self.tag)
157
        self.flask.add_url_rule("/api/filter/<mode>/<uuid>", view_func=self.api_filter)
158
        self.flask.add_url_rule("/api/filter/reset", view_func=self.api_filter_reset)
159
        self.flask.add_url_rule("/<url>", view_func=self.on_get)
160
        self.flask.add_url_rule("/", view_func=self.on_get)
161
        self.flask.app_context().push()
162
        self.flask_context = None
163
164
    def main(self):
165
        """Main function."""
166
        print(f"Serving at http://{self.host}:{self.port} ...")
167
168
        try:
169
            self.flask_context = make_server(
170
                self.host, self.port, self.flask, threaded=True
171
            )
172
            self.flask_context.serve_forever()
173
        except KeyboardInterrupt:
174
            print("Shutting down...")
175
            sys.exit(0)
176
177
178
def main():
179
    """Main entry point for CLI installation"""
180
    Things3API().main()
181
182
183
if __name__ == "__main__":
184
    main()
185