Remove datafreeze dependency

in-house-export-serialization
Kevin Chung 2020-04-27 01:02:52 -04:00
parent c9d31b67aa
commit 593fed300b
5 changed files with 68 additions and 53 deletions

View File

@ -5,12 +5,9 @@ import re
import tempfile import tempfile
import zipfile import zipfile
import datafreeze
import dataset import dataset
import six import six
from alembic.util import CommandError from alembic.util import CommandError
from datafreeze.format import SERIALIZERS
from datafreeze.format.fjson import JSONEncoder, JSONSerializer
from flask import current_app as app from flask import current_app as app
from flask_migrate import upgrade from flask_migrate import upgrade
from sqlalchemy.exc import OperationalError, ProgrammingError from sqlalchemy.exc import OperationalError, ProgrammingError
@ -20,6 +17,7 @@ from CTFd import __version__ as CTFD_VERSION
from CTFd.cache import cache from CTFd.cache import cache
from CTFd.models import db, get_class_by_tablename from CTFd.models import db, get_class_by_tablename
from CTFd.utils import get_app_config, set_config from CTFd.utils import get_app_config, set_config
from CTFd.utils.exports.freeze import freeze_export
from CTFd.utils.migrations import ( from CTFd.utils.migrations import (
create_database, create_database,
drop_database, drop_database,
@ -29,52 +27,6 @@ from CTFd.utils.migrations import (
from CTFd.utils.uploads import get_uploader from CTFd.utils.uploads import get_uploader
class CTFdSerializer(JSONSerializer):
"""
Slightly modified datafreeze serializer so that we can properly
export the CTFd database into a zip file.
"""
def close(self):
for path, result in self.buckets.items():
result = self.wrap(result)
if self.fileobj is None:
fh = open(path, "wb")
else:
fh = self.fileobj
# Certain databases (MariaDB) store JSON as LONGTEXT.
# Before emitting a file we should standardize to valid JSON (i.e. a dict)
# See Issue #973
for i, r in enumerate(result["results"]):
data = r.get("requirements")
if data:
try:
if isinstance(data, six.string_types):
result["results"][i]["requirements"] = json.loads(data)
except ValueError:
pass
data = json.dumps(
result, cls=JSONEncoder, indent=self.export.get_int("indent")
)
callback = self.export.get("callback")
if callback:
data = "%s && %s(%s);" % (callback, callback, data)
if six.PY3:
fh.write(bytes(data, encoding="utf-8"))
else:
fh.write(data)
if self.fileobj is None:
fh.close()
SERIALIZERS["ctfd"] = CTFdSerializer # Load the custom serializer
def export_ctf(): def export_ctf():
# TODO: For some unknown reason dataset is only able to see alembic_version during tests. # TODO: For some unknown reason dataset is only able to see alembic_version during tests.
# Even using a real sqlite database. This makes this test impossible to pass in sqlite. # Even using a real sqlite database. This makes this test impossible to pass in sqlite.
@ -89,7 +41,7 @@ def export_ctf():
for table in tables: for table in tables:
result = db[table].all() result = db[table].all()
result_file = six.BytesIO() result_file = six.BytesIO()
datafreeze.freeze(result, format="ctfd", fileobj=result_file) freeze_export(result, fileobj=result_file)
result_file.seek(0) result_file.seek(0)
backup_zip.writestr("db/{}.json".format(table), result_file.read()) backup_zip.writestr("db/{}.json".format(table), result_file.read())

View File

@ -0,0 +1,11 @@
import json
from datetime import datetime, date
from decimal import Decimal
class JSONEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, (datetime, date)):
return obj.isoformat()
if isinstance(obj, Decimal):
return str(obj)

View File

@ -0,0 +1,11 @@
from CTFd.utils.exports.serializers import JSONSerializer
from sqlalchemy.exc import ProgrammingError, OperationalError
def freeze_export(result, fileobj):
try:
query = result
serializer = JSONSerializer(query, fileobj)
serializer.serialize()
except (OperationalError, ProgrammingError) as e:
raise OperationalError("Invalid query: %s" % e)

View File

@ -0,0 +1,44 @@
import json
import six
from collections import OrderedDict
from CTFd.utils.exports.encoders import JSONEncoder
class JSONSerializer(object):
def __init__(self, query, fileobj):
self.query = query
self.fileobj = fileobj
self.buckets = []
def serialize(self):
for row in self.query:
self.write(None, row)
self.close()
def write(self, path, result):
self.buckets.append([result])
def wrap(self, result):
result = OrderedDict([("count", len(result)), ("results", result)])
result["meta"] = {}
return result
def close(self):
for result in self.buckets:
result = self.wrap(result)
# Certain databases (MariaDB) store JSON as LONGTEXT.
# Before emitting a file we should standardize to valid JSON (i.e. a dict)
# See Issue #973
for i, r in enumerate(result["results"]):
data = r.get("requirements")
if data:
try:
if isinstance(data, six.string_types):
result["results"][i]["requirements"] = json.loads(data)
except ValueError:
pass
data = json.dumps(result, cls=JSONEncoder, indent=2)
self.fileobj.write(data.encode("utf-8"))

View File

@ -13,13 +13,10 @@ itsdangerous==1.1.0
requests>=2.20.0 requests>=2.20.0
PyMySQL==0.9.3 PyMySQL==0.9.3
gunicorn==19.9.0 gunicorn==19.9.0
banal==0.4.2
normality==2.0.0
dataset==1.1.2 dataset==1.1.2
mistune==0.8.4 mistune==0.8.4
netaddr==0.7.19 netaddr==0.7.19
redis==3.3.11 redis==3.3.11
datafreeze==0.1.0
gevent==1.4.0 gevent==1.4.0
python-dotenv==0.10.3 python-dotenv==0.10.3
flask-restx==0.1.1 flask-restx==0.1.1