From 5793e57aa9d25edee118785f19a66b5dd4ea8642 Mon Sep 17 00:00:00 2001 From: W1CDN Date: Sun, 16 Apr 2023 16:59:09 -0500 Subject: [PATCH 01/14] Sort /packets descending by created. --- api_app.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/api_app.py b/api_app.py index 338662b..b4f3da8 100644 --- a/api_app.py +++ b/api_app.py @@ -5,7 +5,7 @@ import configparser import csv import ast import glob -import json +import json, operator import sqlite3 api_app = Flask(__name__) api = Api(api_app) @@ -51,7 +51,6 @@ def dict_factory(cursor, row): def get_db_connection(): conn = sqlite3.connect('database.db') - #conn.row_factory = sqlite3.Row conn.row_factory = dict_factory return conn @@ -71,6 +70,9 @@ class Packets(Resource): #data = read_logs(log_folder) conn = get_db_connection() data = select_all_frames(conn) + # Sort by created date, descending (https://stackoverflow.com/a/45266808) + data.sort(key=operator.itemgetter('created'), reverse=True) + #data.sort(key=created, reverse=True) return {'data': data}, 200 # return data and 200 OK code # Read config From 1cffde2903e565ea866ec50ecebae01c8bace5c9 Mon Sep 17 00:00:00 2001 From: W1CDN Date: Sun, 16 Apr 2023 18:50:39 -0500 Subject: [PATCH 02/14] At /packets, return 10 records by default and add n=x to return x packets. --- README.md | 5 +++-- api_app.py | 10 +++++++--- kiss_and_db.py | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7405ca0..b2d8076 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,9 @@ You can use screen to detach the session. 3. Access the API from whatever other system you want. ## Endpoints: --`/packets` - gives the most recent packets, with the fields from the Dire Wolf -User Guide. +-`/packets` - gives the most recent packets, sorted descending by time received. + - argument `n` will return a specific number of packets, default 10. E.g., + `https://digi.w1cdn.net/aprs_api/packets?n=1` returns one packet. Example of an object packet sent by W1CDN-1 and digipeated by K0UND-2: ``` diff --git a/api_app.py b/api_app.py index b4f3da8..6ceebf1 100644 --- a/api_app.py +++ b/api_app.py @@ -1,4 +1,4 @@ -from flask import Flask +from flask import Flask, request from flask_restful import Resource, Api, reqparse from datetime import date, timedelta import configparser @@ -67,9 +67,13 @@ def select_all_frames(conn): class Packets(Resource): def get(self): - #data = read_logs(log_folder) + try: + n = int(request.args.get('n')) + except: + n = 10 + print(n) conn = get_db_connection() - data = select_all_frames(conn) + data = select_all_frames(conn)[-n:] # Sort by created date, descending (https://stackoverflow.com/a/45266808) data.sort(key=operator.itemgetter('created'), reverse=True) #data.sort(key=created, reverse=True) diff --git a/kiss_and_db.py b/kiss_and_db.py index a8c7072..2b6ec2f 100644 --- a/kiss_and_db.py +++ b/kiss_and_db.py @@ -82,7 +82,7 @@ def main(): path=["WIDE1-1"], info=b">Hello World!", ) - ki.write(frame) + #ki.write(frame) # Watch for new packets to come in while True: From cd5d24b6418cbf8ab7bc4cd0c3a962db75fbd792 Mon Sep 17 00:00:00 2001 From: W1CDN Date: Sun, 16 Apr 2023 19:29:09 -0500 Subject: [PATCH 03/14] Add comments. --- api_app.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api_app.py b/api_app.py index 6ceebf1..00d0a9e 100644 --- a/api_app.py +++ b/api_app.py @@ -67,12 +67,14 @@ def select_all_frames(conn): class Packets(Resource): def get(self): + # Handle arguments that may or may not exist try: n = int(request.args.get('n')) except: n = 10 - print(n) + conn = get_db_connection() + # Limit to number of records requested data = select_all_frames(conn)[-n:] # Sort by created date, descending (https://stackoverflow.com/a/45266808) data.sort(key=operator.itemgetter('created'), reverse=True) From 6957219468c233382c7b7d2a7d60ad7cd0009228 Mon Sep 17 00:00:00 2001 From: W1CDN Date: Sun, 16 Apr 2023 21:04:26 -0500 Subject: [PATCH 04/14] Add basic API call to select by "from" station. --- api_app.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/api_app.py b/api_app.py index 00d0a9e..427680e 100644 --- a/api_app.py +++ b/api_app.py @@ -65,6 +65,17 @@ def select_all_frames(conn): rows = cur.fetchall() return rows +def select_frames(conn, n, from_): + cur = conn.cursor() + # Workaround to deal with missing value in WHERE + from_ = "IS NOT NULL" if from_ == None else "='"+from_+"'" + #sql = "SELECT * FROM frames LIMIT "+n + sql = 'SELECT * FROM frames WHERE "from" {from_} LIMIT {n}'.format(from_=from_, n=n) + print(sql) + cur.execute(sql) + rows = cur.fetchall() + return rows + class Packets(Resource): def get(self): # Handle arguments that may or may not exist @@ -72,10 +83,11 @@ class Packets(Resource): n = int(request.args.get('n')) except: n = 10 + from_ = None if request.args.get('from') == None else request.args.get('from') conn = get_db_connection() # Limit to number of records requested - data = select_all_frames(conn)[-n:] + data = select_frames(conn, n = n, from_ = from_) # Sort by created date, descending (https://stackoverflow.com/a/45266808) data.sort(key=operator.itemgetter('created'), reverse=True) #data.sort(key=created, reverse=True) From 2121119365f080de2733483cca746b17d275b372 Mon Sep 17 00:00:00 2001 From: W1CDN Date: Sun, 23 Apr 2023 21:13:06 -0500 Subject: [PATCH 05/14] Update docs. --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b2d8076..95c2f4e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ direwolf logs into a REST API in JSON format. 1. Set up database file with `python init_db.py`. 2. Run `app.py` with either a Python call or a real WSGI server. You can use screen to detach the session. - - Default URL is http://127.0.0.1:5000 + - Default URL is http://127.0.0.1:5001 - Example `waitress` and `screen` scripts are included, see - `api_waitress.py` and - `start-aprs_api.sh` @@ -20,6 +20,9 @@ You can use screen to detach the session. -`/packets` - gives the most recent packets, sorted descending by time received. - argument `n` will return a specific number of packets, default 10. E.g., `https://digi.w1cdn.net/aprs_api/packets?n=1` returns one packet. + - argument `from` will return packets from the named station-SSID (no wildcards). + E.g., `https://digi.w1cdn.net/aprs_api/packets?n=1&from=W1CDN-1` returns + one packet from W1CDN-1. Example of an object packet sent by W1CDN-1 and digipeated by K0UND-2: ``` From ab850a76a3dc00886bf2b2d885f41a2363f02a03 Mon Sep 17 00:00:00 2001 From: W1CDN Date: Sun, 23 Apr 2023 21:13:27 -0500 Subject: [PATCH 06/14] Don't get hung up on parsing errors. --- kiss_and_db.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kiss_and_db.py b/kiss_and_db.py index 2b6ec2f..a38dfd3 100644 --- a/kiss_and_db.py +++ b/kiss_and_db.py @@ -88,7 +88,10 @@ def main(): while True: conn = get_db_connection() for frame in ki.read(min_frames=1): - a = aprslib.parse(str(frame)) + try: + a = aprslib.parse(str(frame)) + except: + a = dict() a['station_call'] = config['Settings']['station_call'] a['station_lat'] = config['Settings']['station_lat'] a['station_lon'] = config['Settings']['station_lon'] From f396fe87af0288e6e57810065945dfb6cec7e45c Mon Sep 17 00:00:00 2001 From: W1CDN Date: Tue, 25 Apr 2023 14:19:29 -0500 Subject: [PATCH 07/14] Order by created date in db call. --- api_app.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/api_app.py b/api_app.py index 427680e..4f64710 100644 --- a/api_app.py +++ b/api_app.py @@ -70,7 +70,7 @@ def select_frames(conn, n, from_): # Workaround to deal with missing value in WHERE from_ = "IS NOT NULL" if from_ == None else "='"+from_+"'" #sql = "SELECT * FROM frames LIMIT "+n - sql = 'SELECT * FROM frames WHERE "from" {from_} LIMIT {n}'.format(from_=from_, n=n) + sql = 'SELECT * FROM frames WHERE "from" {from_} ORDER BY created DESC LIMIT {n}'.format(from_=from_, n=n) print(sql) cur.execute(sql) rows = cur.fetchall() @@ -89,15 +89,12 @@ class Packets(Resource): # Limit to number of records requested data = select_frames(conn, n = n, from_ = from_) # Sort by created date, descending (https://stackoverflow.com/a/45266808) - data.sort(key=operator.itemgetter('created'), reverse=True) - #data.sort(key=created, reverse=True) + #data.sort(key=operator.itemgetter('created'), reverse=True) return {'data': data}, 200 # return data and 200 OK code # Read config config = read_config() log_folder = config['Settings']['log_folder'] -# Load logs first (just to check for errors before page loads) -#data = read_logs(log_folder) # Start subprocess to watch KISS connection import subprocess From cc89ab1a4ccd4d363f9c7d47d54f10b29a818193 Mon Sep 17 00:00:00 2001 From: W1CDN Date: Thu, 27 Apr 2023 19:19:12 -0500 Subject: [PATCH 08/14] Don't mess with frame if it can't be parsed. --- kiss_and_db.py | 55 +++++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/kiss_and_db.py b/kiss_and_db.py index a38dfd3..e94c058 100644 --- a/kiss_and_db.py +++ b/kiss_and_db.py @@ -90,35 +90,36 @@ def main(): for frame in ki.read(min_frames=1): try: a = aprslib.parse(str(frame)) - except: - a = dict() - a['station_call'] = config['Settings']['station_call'] - a['station_lat'] = config['Settings']['station_lat'] - a['station_lon'] = config['Settings']['station_lon'] - print(a) - # Make this a string and deal with it later (probably a mistake) - a['path'] = str(a['path']) - # Store true/false as 1/0 - if 'alive' in a: - if a['alive'] == True: - a['alive'] = 1 - else: - a['alive'] = 0 - # Build an INSERT statement based on the fields we have from the frame - attrib_names = ', '.join('"%s"' % w for w in a.keys()) - attrib_values = ", ".join("?" * len(a.keys())) - sql = "INSERT INTO frames ("+attrib_names+") VALUES ("+attrib_values+")" - try: - # Insert data - conn.execute(sql, list(a.values())) - conn.commit() + a['station_call'] = config['Settings']['station_call'] + a['station_lat'] = config['Settings']['station_lat'] + a['station_lon'] = config['Settings']['station_lon'] + print(a) + # Make this a string and deal with it later (probably a mistake) + a['path'] = str(a['path']) + # Store true/false as 1/0 + if 'alive' in a: + if a['alive'] == True: + a['alive'] = 1 + else: + a['alive'] = 0 + # Build an INSERT statement based on the fields we have from the frame + attrib_names = ', '.join('"%s"' % w for w in a.keys()) + attrib_values = ", ".join("?" * len(a.keys())) + sql = "INSERT INTO frames ("+attrib_names+") VALUES ("+attrib_values+")" + try: + # Insert data + conn.execute(sql, list(a.values())) + conn.commit() - # TODO remove packets that are older ('created') than a limit set in config.ini - # "5 minutes" also works - conn.execute("DELETE FROM frames WHERE created < DATETIME('now', '"+config['Settings']['keep_time']+"')") - conn.commit() + # TODO remove packets that are older ('created') than a limit set in config.ini + # "5 minutes" also works + conn.execute("DELETE FROM frames WHERE created < DATETIME('now', '"+config['Settings']['keep_time']+"')") + conn.commit() + except: + print("Error with SQLite!") except: - print("Error with SQLite!") + print("Frame could not be parsed.") + conn.close() From efe61ae4c5c3a26cd311a6e717bab44556c75b45 Mon Sep 17 00:00:00 2001 From: W1CDN Date: Thu, 27 Apr 2023 19:29:22 -0500 Subject: [PATCH 09/14] Add msgNo field. --- kiss_and_db.py | 1 + schema.sql | 1 + 2 files changed, 2 insertions(+) diff --git a/kiss_and_db.py b/kiss_and_db.py index e94c058..24e4209 100644 --- a/kiss_and_db.py +++ b/kiss_and_db.py @@ -22,6 +22,7 @@ db_fields = ("id", "mbits", "messagecapable", "message_text", +"msgNo", "mtype", "object_format", "object_name", diff --git a/schema.sql b/schema.sql index 91e5b43..b329b89 100644 --- a/schema.sql +++ b/schema.sql @@ -17,6 +17,7 @@ CREATE TABLE frames ( mbits INT, messagecapable INT, message_text TEXT, + msgNo INT, mtype TEXT, object_format TEXT, object_name TEXT, From a99de3a8597829b1be635c93a858b2bd3225e55b Mon Sep 17 00:00:00 2001 From: W1CDN Date: Sat, 13 May 2023 11:09:41 -0500 Subject: [PATCH 10/14] Allow query on any field in the packets table. --- api_app.py | 73 +++++++++++++++++++++++++++++++++++++++++++----- requirements.txt | 2 ++ 2 files changed, 68 insertions(+), 7 deletions(-) diff --git a/api_app.py b/api_app.py index 4f64710..c854482 100644 --- a/api_app.py +++ b/api_app.py @@ -10,6 +10,55 @@ import sqlite3 api_app = Flask(__name__) api = Api(api_app) +# TODO this is duplicated from kiss_and_db.py, can I avoid that? +db_fields = ("id", +"addresse", +"alive", +"altitude", +"comment", +"course", +"created", +"format", +"frame", +"from", +"gpsfixstatus", +"latitude", +"longitude", +"mbits", +"messagecapable", +"message_text", +"msgNo", +"mtype", +"object_format", +"object_name", +"path", +"phg", +"phg_dir", +"phg_gain", +"phg_height", +"phg_power", +"phg_range", +"posambiguity", +"raw", +"raw_timestamp", +"speed", +"station_call", +"station_lat", +"station_lon", +"status", +"subpacket", +"symbol", +"symbol_table", +"telemetry", +"timestamp", +"to", +"tEQNS", +"tPARM", +"tUNIT", +"via", +"weather", +"wx_raw_timestamp") + def read_config(): config = configparser.ConfigParser() config.read('config.ini') @@ -65,12 +114,22 @@ def select_all_frames(conn): rows = cur.fetchall() return rows -def select_frames(conn, n, from_): +def select_frames(conn, n, from_, url_params): + + # Should pass this a dict of fields and values (request.args) + # TODO clean data before sending to DB + # Filter out any keys that don't match db fields + # From https://stackoverflow.com/a/20256491 + dictfilt = lambda x, y: dict([ (i,x[i]) for i in x if i in set(y) ]) + field_where = dictfilt(url_params, db_fields) + # Then loop through fields to create query parts + # From https://stackoverflow.com/a/73512269/2152245 + field_where_str = ' AND '.join([f'"{k}" = \'{v}\'' for k,v in field_where.items()]) + cur = conn.cursor() # Workaround to deal with missing value in WHERE - from_ = "IS NOT NULL" if from_ == None else "='"+from_+"'" - #sql = "SELECT * FROM frames LIMIT "+n - sql = 'SELECT * FROM frames WHERE "from" {from_} ORDER BY created DESC LIMIT {n}'.format(from_=from_, n=n) + field_where_query = "" if field_where_str == "" else "WHERE "+field_where_str + sql = 'SELECT * FROM frames {field_where_query} ORDER BY created DESC LIMIT {n}'.format(field_where_query=field_where_query, n=n) print(sql) cur.execute(sql) rows = cur.fetchall() @@ -87,7 +146,7 @@ class Packets(Resource): conn = get_db_connection() # Limit to number of records requested - data = select_frames(conn, n = n, from_ = from_) + data = select_frames(conn, n = n, from_ = from_, url_params = request.args.to_dict()) # Sort by created date, descending (https://stackoverflow.com/a/45266808) #data.sort(key=operator.itemgetter('created'), reverse=True) return {'data': data}, 200 # return data and 200 OK code @@ -97,8 +156,8 @@ config = read_config() log_folder = config['Settings']['log_folder'] # Start subprocess to watch KISS connection -import subprocess -subprocess.Popen(["python3","kiss_and_db.py"]) +#import subprocess +#subprocess.Popen(["python3","kiss_and_db.py"]) api.add_resource(Packets, '/packets') # and '/locations' is our entry point for Locations diff --git a/requirements.txt b/requirements.txt index 85407aa..13524b3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,5 @@ flask flask_restful aprs aprslib +sqlite3 +json From 863efdd84c5e28af23d1e215337215165ab67d05 Mon Sep 17 00:00:00 2001 From: W1CDN Date: Sat, 13 May 2023 11:14:21 -0500 Subject: [PATCH 11/14] Use LIKE instead of =. --- api_app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api_app.py b/api_app.py index c854482..6ed1429 100644 --- a/api_app.py +++ b/api_app.py @@ -124,7 +124,7 @@ def select_frames(conn, n, from_, url_params): field_where = dictfilt(url_params, db_fields) # Then loop through fields to create query parts # From https://stackoverflow.com/a/73512269/2152245 - field_where_str = ' AND '.join([f'"{k}" = \'{v}\'' for k,v in field_where.items()]) + field_where_str = ' AND '.join([f'"{k}" LIKE \'{v}\'' for k,v in field_where.items()]) cur = conn.cursor() # Workaround to deal with missing value in WHERE From cb9af0f5b833d79a00aa4707355b3a92ff574a57 Mon Sep 17 00:00:00 2001 From: W1CDN Date: Sat, 13 May 2023 17:26:38 -0500 Subject: [PATCH 12/14] Move tuple of frame table fields to a separate files in case we need it more places. --- api_app.py | 50 ++------------------------------------------------ constants.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ kiss_and_db.py | 48 ------------------------------------------------ 3 files changed, 50 insertions(+), 96 deletions(-) create mode 100644 constants.py diff --git a/api_app.py b/api_app.py index 6ed1429..18054e3 100644 --- a/api_app.py +++ b/api_app.py @@ -11,53 +11,7 @@ api_app = Flask(__name__) api = Api(api_app) # TODO this is duplicated from kiss_and_db.py, can I avoid that? -db_fields = ("id", -"addresse", -"alive", -"altitude", -"comment", -"course", -"created", -"format", -"frame", -"from", -"gpsfixstatus", -"latitude", -"longitude", -"mbits", -"messagecapable", -"message_text", -"msgNo", -"mtype", -"object_format", -"object_name", -"path", -"phg", -"phg_dir", -"phg_gain", -"phg_height", -"phg_power", -"phg_range", -"posambiguity", -"raw", -"raw_timestamp", -"speed", -"station_call", -"station_lat", -"station_lon", -"status", -"subpacket", -"symbol", -"symbol_table", -"telemetry", -"timestamp", -"to", -"tEQNS", -"tPARM", -"tUNIT", -"via", -"weather", -"wx_raw_timestamp") +import constants def read_config(): config = configparser.ConfigParser() @@ -121,7 +75,7 @@ def select_frames(conn, n, from_, url_params): # Filter out any keys that don't match db fields # From https://stackoverflow.com/a/20256491 dictfilt = lambda x, y: dict([ (i,x[i]) for i in x if i in set(y) ]) - field_where = dictfilt(url_params, db_fields) + field_where = dictfilt(url_params, constants.db_frames_fields) # Then loop through fields to create query parts # From https://stackoverflow.com/a/73512269/2152245 field_where_str = ' AND '.join([f'"{k}" LIKE \'{v}\'' for k,v in field_where.items()]) diff --git a/constants.py b/constants.py new file mode 100644 index 0000000..3bd8420 --- /dev/null +++ b/constants.py @@ -0,0 +1,48 @@ +# Tuple of frames table fields +db_frames_fields = ("id", +"addresse", +"alive", +"altitude", +"comment", +"course", +"created", +"format", +"frame", +"from", +"gpsfixstatus", +"latitude", +"longitude", +"mbits", +"messagecapable", +"message_text", +"msgNo", +"mtype", +"object_format", +"object_name", +"path", +"phg", +"phg_dir", +"phg_gain", +"phg_height", +"phg_power", +"phg_range", +"posambiguity", +"raw", +"raw_timestamp", +"speed", +"station_call", +"station_lat", +"station_lon", +"status", +"subpacket", +"symbol", +"symbol_table", +"telemetry", +"timestamp", +"to", +"tEQNS", +"tPARM", +"tUNIT", +"via", +"weather", +"wx_raw_timestamp") diff --git a/kiss_and_db.py b/kiss_and_db.py index 24e4209..5e23ef9 100644 --- a/kiss_and_db.py +++ b/kiss_and_db.py @@ -6,54 +6,6 @@ import json import aprslib import configparser -db_fields = ("id", -"addresse", -"alive", -"altitude", -"comment", -"course", -"created", -"format", -"frame", -"from", -"gpsfixstatus", -"latitude", -"longitude", -"mbits", -"messagecapable", -"message_text", -"msgNo", -"mtype", -"object_format", -"object_name", -"path", -"phg", -"phg_dir", -"phg_gain", -"phg_height", -"phg_power", -"phg_range", -"posambiguity", -"raw", -"raw_timestamp", -"speed", -"station_call", -"station_lat", -"station_lon", -"status", -"subpacket", -"symbol", -"symbol_table", -"telemetry", -"timestamp", -"to", -"tEQNS", -"tPARM", -"tUNIT", -"via", -"weather", -"wx_raw_timestamp") - def read_config(): config = configparser.ConfigParser() config.read('config.ini') From e19a8c777ccfd721fd3bd0954e2b216549f8805a Mon Sep 17 00:00:00 2001 From: W1CDN Date: Sat, 13 May 2023 20:59:17 -0500 Subject: [PATCH 13/14] Fix bad comment. --- api_app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api_app.py b/api_app.py index 18054e3..a1c2dc2 100644 --- a/api_app.py +++ b/api_app.py @@ -110,8 +110,8 @@ config = read_config() log_folder = config['Settings']['log_folder'] # Start subprocess to watch KISS connection -#import subprocess -#subprocess.Popen(["python3","kiss_and_db.py"]) +import subprocess +subprocess.Popen(["python3","kiss_and_db.py"]) api.add_resource(Packets, '/packets') # and '/locations' is our entry point for Locations From 494f53bd62d39c2a420e554a106a9c40a189b7ac Mon Sep 17 00:00:00 2001 From: W1CDN Date: Sat, 13 May 2023 20:59:38 -0500 Subject: [PATCH 14/14] Add created_unix field to frames table. --- constants.py | 1 + kiss_and_db.py | 2 ++ schema.sql | 1 + 3 files changed, 4 insertions(+) diff --git a/constants.py b/constants.py index 3bd8420..8689d11 100644 --- a/constants.py +++ b/constants.py @@ -6,6 +6,7 @@ db_frames_fields = ("id", "comment", "course", "created", +"created_unix", "format", "frame", "from", diff --git a/kiss_and_db.py b/kiss_and_db.py index 5e23ef9..bfb5d9b 100644 --- a/kiss_and_db.py +++ b/kiss_and_db.py @@ -5,6 +5,7 @@ import aprs import json import aprslib import configparser +import time def read_config(): config = configparser.ConfigParser() @@ -46,6 +47,7 @@ def main(): a['station_call'] = config['Settings']['station_call'] a['station_lat'] = config['Settings']['station_lat'] a['station_lon'] = config['Settings']['station_lon'] + a['created_unix'] = int(time.time()) print(a) # Make this a string and deal with it later (probably a mistake) a['path'] = str(a['path']) diff --git a/schema.sql b/schema.sql index b329b89..4a64402 100644 --- a/schema.sql +++ b/schema.sql @@ -8,6 +8,7 @@ CREATE TABLE frames ( comment TEXT, course REAL, created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + created_unix INT, format TEXT, frame TEXT, "from" TEXT,