mirror of
https://github.com/krateng/maloja.git
synced 2025-05-31 20:24:21 -04:00
Merge branch 'doreahupgrade'
# Conflicts: # dev/releases/3.2.yml
This commit is contained in:
commit
6b2f1892f8
@ -40,6 +40,7 @@ minor_release_name: "Nicole"
|
|||||||
- "[Bugfix] Fixed Last.fm authentication"
|
- "[Bugfix] Fixed Last.fm authentication"
|
||||||
3.2.3:
|
3.2.3:
|
||||||
notes:
|
notes:
|
||||||
|
- "[Architecture] Upgraded doreah, significant rework of authentication"
|
||||||
- "[Bugfix] Fixed initial permission check"
|
- "[Bugfix] Fixed initial permission check"
|
||||||
- "[Bugfix] Fixed and updated various texts"
|
- "[Bugfix] Fixed and updated various texts"
|
||||||
- "[Bugfix] Fixed moving tracks to different album"
|
- "[Bugfix] Fixed moving tracks to different album"
|
||||||
|
@ -7,7 +7,6 @@ from bottle import response, static_file, FormsDict
|
|||||||
from inspect import signature
|
from inspect import signature
|
||||||
|
|
||||||
from doreah.logging import log
|
from doreah.logging import log
|
||||||
from doreah.auth import authenticated_function
|
|
||||||
|
|
||||||
# nimrodel API
|
# nimrodel API
|
||||||
from nimrodel import EAPI as API
|
from nimrodel import EAPI as API
|
||||||
@ -15,7 +14,7 @@ from nimrodel import Multi
|
|||||||
|
|
||||||
|
|
||||||
from .. import database
|
from .. import database
|
||||||
from ..pkg_global.conf import malojaconfig, data_dir
|
from ..pkg_global.conf import malojaconfig, data_dir, auth
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -575,7 +574,7 @@ def album_info_external(k_filter, k_limit, k_delimit, k_amount):
|
|||||||
|
|
||||||
|
|
||||||
@api.post("newscrobble")
|
@api.post("newscrobble")
|
||||||
@authenticated_function(alternate=api_key_correct,api=True,pass_auth_result_as='auth_result')
|
@auth.authenticated_function(alternate=api_key_correct,api=True,pass_auth_result_as='auth_result')
|
||||||
@catch_exceptions
|
@catch_exceptions
|
||||||
def post_scrobble(
|
def post_scrobble(
|
||||||
artist:Multi=None,
|
artist:Multi=None,
|
||||||
@ -655,7 +654,7 @@ def post_scrobble(
|
|||||||
|
|
||||||
|
|
||||||
@api.post("addpicture")
|
@api.post("addpicture")
|
||||||
@authenticated_function(alternate=api_key_correct,api=True)
|
@auth.authenticated_function(alternate=api_key_correct,api=True)
|
||||||
@catch_exceptions
|
@catch_exceptions
|
||||||
@convert_kwargs
|
@convert_kwargs
|
||||||
def add_picture(k_filter, k_limit, k_delimit, k_amount, k_special):
|
def add_picture(k_filter, k_limit, k_delimit, k_amount, k_special):
|
||||||
@ -678,7 +677,7 @@ def add_picture(k_filter, k_limit, k_delimit, k_amount, k_special):
|
|||||||
|
|
||||||
|
|
||||||
@api.post("importrules")
|
@api.post("importrules")
|
||||||
@authenticated_function(api=True)
|
@auth.authenticated_function(api=True)
|
||||||
@catch_exceptions
|
@catch_exceptions
|
||||||
def import_rulemodule(**keys):
|
def import_rulemodule(**keys):
|
||||||
"""Internal Use Only"""
|
"""Internal Use Only"""
|
||||||
@ -697,7 +696,7 @@ def import_rulemodule(**keys):
|
|||||||
|
|
||||||
|
|
||||||
@api.post("rebuild")
|
@api.post("rebuild")
|
||||||
@authenticated_function(api=True)
|
@auth.authenticated_function(api=True)
|
||||||
@catch_exceptions
|
@catch_exceptions
|
||||||
def rebuild(**keys):
|
def rebuild(**keys):
|
||||||
"""Internal Use Only"""
|
"""Internal Use Only"""
|
||||||
@ -773,7 +772,7 @@ def search(**keys):
|
|||||||
|
|
||||||
|
|
||||||
@api.post("newrule")
|
@api.post("newrule")
|
||||||
@authenticated_function(api=True)
|
@auth.authenticated_function(api=True)
|
||||||
@catch_exceptions
|
@catch_exceptions
|
||||||
def newrule(**keys):
|
def newrule(**keys):
|
||||||
"""Internal Use Only"""
|
"""Internal Use Only"""
|
||||||
@ -784,21 +783,21 @@ def newrule(**keys):
|
|||||||
|
|
||||||
|
|
||||||
@api.post("settings")
|
@api.post("settings")
|
||||||
@authenticated_function(api=True)
|
@auth.authenticated_function(api=True)
|
||||||
@catch_exceptions
|
@catch_exceptions
|
||||||
def set_settings(**keys):
|
def set_settings(**keys):
|
||||||
"""Internal Use Only"""
|
"""Internal Use Only"""
|
||||||
malojaconfig.update(keys)
|
malojaconfig.update(keys)
|
||||||
|
|
||||||
@api.post("apikeys")
|
@api.post("apikeys")
|
||||||
@authenticated_function(api=True)
|
@auth.authenticated_function(api=True)
|
||||||
@catch_exceptions
|
@catch_exceptions
|
||||||
def set_apikeys(**keys):
|
def set_apikeys(**keys):
|
||||||
"""Internal Use Only"""
|
"""Internal Use Only"""
|
||||||
apikeystore.update(keys)
|
apikeystore.update(keys)
|
||||||
|
|
||||||
@api.post("import")
|
@api.post("import")
|
||||||
@authenticated_function(api=True)
|
@auth.authenticated_function(api=True)
|
||||||
@catch_exceptions
|
@catch_exceptions
|
||||||
def import_scrobbles(identifier):
|
def import_scrobbles(identifier):
|
||||||
"""Internal Use Only"""
|
"""Internal Use Only"""
|
||||||
@ -806,7 +805,7 @@ def import_scrobbles(identifier):
|
|||||||
import_scrobbles(identifier)
|
import_scrobbles(identifier)
|
||||||
|
|
||||||
@api.get("backup")
|
@api.get("backup")
|
||||||
@authenticated_function(api=True)
|
@auth.authenticated_function(api=True)
|
||||||
@catch_exceptions
|
@catch_exceptions
|
||||||
def get_backup(**keys):
|
def get_backup(**keys):
|
||||||
"""Internal Use Only"""
|
"""Internal Use Only"""
|
||||||
@ -819,7 +818,7 @@ def get_backup(**keys):
|
|||||||
return static_file(os.path.basename(archivefile),root=tmpfolder)
|
return static_file(os.path.basename(archivefile),root=tmpfolder)
|
||||||
|
|
||||||
@api.get("export")
|
@api.get("export")
|
||||||
@authenticated_function(api=True)
|
@auth.authenticated_function(api=True)
|
||||||
@catch_exceptions
|
@catch_exceptions
|
||||||
def get_export(**keys):
|
def get_export(**keys):
|
||||||
"""Internal Use Only"""
|
"""Internal Use Only"""
|
||||||
@ -833,7 +832,7 @@ def get_export(**keys):
|
|||||||
|
|
||||||
|
|
||||||
@api.post("delete_scrobble")
|
@api.post("delete_scrobble")
|
||||||
@authenticated_function(api=True)
|
@auth.authenticated_function(api=True)
|
||||||
@catch_exceptions
|
@catch_exceptions
|
||||||
def delete_scrobble(timestamp):
|
def delete_scrobble(timestamp):
|
||||||
"""Internal Use Only"""
|
"""Internal Use Only"""
|
||||||
@ -845,7 +844,7 @@ def delete_scrobble(timestamp):
|
|||||||
|
|
||||||
|
|
||||||
@api.post("edit_artist")
|
@api.post("edit_artist")
|
||||||
@authenticated_function(api=True)
|
@auth.authenticated_function(api=True)
|
||||||
@catch_exceptions
|
@catch_exceptions
|
||||||
def edit_artist(id,name):
|
def edit_artist(id,name):
|
||||||
"""Internal Use Only"""
|
"""Internal Use Only"""
|
||||||
@ -855,7 +854,7 @@ def edit_artist(id,name):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@api.post("edit_track")
|
@api.post("edit_track")
|
||||||
@authenticated_function(api=True)
|
@auth.authenticated_function(api=True)
|
||||||
@catch_exceptions
|
@catch_exceptions
|
||||||
def edit_track(id,title):
|
def edit_track(id,title):
|
||||||
"""Internal Use Only"""
|
"""Internal Use Only"""
|
||||||
@ -865,7 +864,7 @@ def edit_track(id,title):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@api.post("edit_album")
|
@api.post("edit_album")
|
||||||
@authenticated_function(api=True)
|
@auth.authenticated_function(api=True)
|
||||||
@catch_exceptions
|
@catch_exceptions
|
||||||
def edit_album(id,albumtitle):
|
def edit_album(id,albumtitle):
|
||||||
"""Internal Use Only"""
|
"""Internal Use Only"""
|
||||||
@ -876,7 +875,7 @@ def edit_album(id,albumtitle):
|
|||||||
|
|
||||||
|
|
||||||
@api.post("merge_tracks")
|
@api.post("merge_tracks")
|
||||||
@authenticated_function(api=True)
|
@auth.authenticated_function(api=True)
|
||||||
@catch_exceptions
|
@catch_exceptions
|
||||||
def merge_tracks(target_id,source_ids):
|
def merge_tracks(target_id,source_ids):
|
||||||
"""Internal Use Only"""
|
"""Internal Use Only"""
|
||||||
@ -887,7 +886,7 @@ def merge_tracks(target_id,source_ids):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@api.post("merge_artists")
|
@api.post("merge_artists")
|
||||||
@authenticated_function(api=True)
|
@auth.authenticated_function(api=True)
|
||||||
@catch_exceptions
|
@catch_exceptions
|
||||||
def merge_artists(target_id,source_ids):
|
def merge_artists(target_id,source_ids):
|
||||||
"""Internal Use Only"""
|
"""Internal Use Only"""
|
||||||
@ -898,7 +897,7 @@ def merge_artists(target_id,source_ids):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@api.post("merge_albums")
|
@api.post("merge_albums")
|
||||||
@authenticated_function(api=True)
|
@auth.authenticated_function(api=True)
|
||||||
@catch_exceptions
|
@catch_exceptions
|
||||||
def merge_artists(target_id,source_ids):
|
def merge_artists(target_id,source_ids):
|
||||||
"""Internal Use Only"""
|
"""Internal Use Only"""
|
||||||
@ -909,7 +908,7 @@ def merge_artists(target_id,source_ids):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@api.post("associate_albums_to_artist")
|
@api.post("associate_albums_to_artist")
|
||||||
@authenticated_function(api=True)
|
@auth.authenticated_function(api=True)
|
||||||
@catch_exceptions
|
@catch_exceptions
|
||||||
def associate_albums_to_artist(target_id,source_ids,remove=False):
|
def associate_albums_to_artist(target_id,source_ids,remove=False):
|
||||||
result = database.associate_albums_to_artist(target_id,source_ids,remove=remove)
|
result = database.associate_albums_to_artist(target_id,source_ids,remove=remove)
|
||||||
@ -921,7 +920,7 @@ def associate_albums_to_artist(target_id,source_ids,remove=False):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@api.post("associate_tracks_to_artist")
|
@api.post("associate_tracks_to_artist")
|
||||||
@authenticated_function(api=True)
|
@auth.authenticated_function(api=True)
|
||||||
@catch_exceptions
|
@catch_exceptions
|
||||||
def associate_tracks_to_artist(target_id,source_ids,remove=False):
|
def associate_tracks_to_artist(target_id,source_ids,remove=False):
|
||||||
result = database.associate_tracks_to_artist(target_id,source_ids,remove=remove)
|
result = database.associate_tracks_to_artist(target_id,source_ids,remove=remove)
|
||||||
@ -933,7 +932,7 @@ def associate_tracks_to_artist(target_id,source_ids,remove=False):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@api.post("associate_tracks_to_album")
|
@api.post("associate_tracks_to_album")
|
||||||
@authenticated_function(api=True)
|
@auth.authenticated_function(api=True)
|
||||||
@catch_exceptions
|
@catch_exceptions
|
||||||
def associate_tracks_to_album(target_id,source_ids):
|
def associate_tracks_to_album(target_id,source_ids):
|
||||||
result = database.associate_tracks_to_album(target_id,source_ids)
|
result = database.associate_tracks_to_album(target_id,source_ids)
|
||||||
@ -945,7 +944,7 @@ def associate_tracks_to_album(target_id,source_ids):
|
|||||||
|
|
||||||
|
|
||||||
@api.post("reparse_scrobble")
|
@api.post("reparse_scrobble")
|
||||||
@authenticated_function(api=True)
|
@auth.authenticated_function(api=True)
|
||||||
@catch_exceptions
|
@catch_exceptions
|
||||||
def reparse_scrobble(timestamp):
|
def reparse_scrobble(timestamp):
|
||||||
"""Internal Use Only"""
|
"""Internal Use Only"""
|
||||||
|
@ -27,7 +27,6 @@ from . import exceptions
|
|||||||
|
|
||||||
# doreah toolkit
|
# doreah toolkit
|
||||||
from doreah.logging import log
|
from doreah.logging import log
|
||||||
from doreah.auth import authenticated_api, authenticated_api_with_alternate
|
|
||||||
import doreah
|
import doreah
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
import cProfile, pstats
|
import cProfile, pstats
|
||||||
|
import time
|
||||||
|
|
||||||
from doreah.logging import log
|
from doreah.logging import log
|
||||||
from doreah.timing import Clock
|
|
||||||
|
|
||||||
from ..pkg_global.conf import data_dir
|
from ..pkg_global.conf import data_dir
|
||||||
|
|
||||||
@ -27,8 +27,7 @@ def profile(func):
|
|||||||
|
|
||||||
def newfunc(*args,**kwargs):
|
def newfunc(*args,**kwargs):
|
||||||
|
|
||||||
clock = Clock()
|
starttime = time.time()
|
||||||
clock.start()
|
|
||||||
|
|
||||||
if FULL_PROFILE:
|
if FULL_PROFILE:
|
||||||
benchmarkfolder = data_dir['logs']("benchmarks")
|
benchmarkfolder = data_dir['logs']("benchmarks")
|
||||||
@ -44,7 +43,7 @@ def profile(func):
|
|||||||
if FULL_PROFILE:
|
if FULL_PROFILE:
|
||||||
localprofiler.disable()
|
localprofiler.disable()
|
||||||
|
|
||||||
seconds = clock.stop()
|
seconds = time.time() - starttime
|
||||||
|
|
||||||
if not SINGLE_CALLS:
|
if not SINGLE_CALLS:
|
||||||
times.setdefault(realfunc,[]).append(seconds)
|
times.setdefault(realfunc,[]).append(seconds)
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
|
import doreah.auth
|
||||||
|
import doreah.logging
|
||||||
from doreah.configuration import Configuration
|
from doreah.configuration import Configuration
|
||||||
from doreah.configuration import types as tp
|
from doreah.configuration import types as tp
|
||||||
|
|
||||||
@ -330,26 +333,15 @@ data_dir = {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
### DOREAH CONFIGURATION
|
### DOREAH OBJECTS
|
||||||
|
|
||||||
from doreah import config
|
auth = doreah.auth.AuthManager(singleuser=True,cookieprefix='maloja',stylesheets=("/maloja.css",),dbfile=data_dir['auth']("auth.sqlite"))
|
||||||
|
|
||||||
config(
|
|
||||||
auth={
|
|
||||||
"multiuser":False,
|
|
||||||
"cookieprefix":"maloja",
|
|
||||||
"stylesheets":["/maloja.css"],
|
|
||||||
"dbfile":data_dir['auth']("auth.ddb")
|
|
||||||
},
|
|
||||||
logging={
|
|
||||||
"logfolder": data_dir['logs']() if malojaconfig["LOGGING"] else None
|
|
||||||
},
|
|
||||||
regular={
|
|
||||||
"offset": malojaconfig["TIMEZONE"]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
#logger = doreah.logging.Logger(logfolder=data_dir['logs']() if malojaconfig["LOGGING"] else None)
|
||||||
|
#log = logger.log
|
||||||
|
|
||||||
|
# this is not how its supposed to be done, but lets ease the transition
|
||||||
|
doreah.logging.defaultlogger.logfolder = data_dir['logs']() if malojaconfig["LOGGING"] else None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,14 +12,13 @@ from jinja2.exceptions import TemplateNotFound
|
|||||||
|
|
||||||
# doreah toolkit
|
# doreah toolkit
|
||||||
from doreah.logging import log
|
from doreah.logging import log
|
||||||
from doreah import auth
|
|
||||||
|
|
||||||
# rest of the project
|
# rest of the project
|
||||||
from . import database
|
from . import database
|
||||||
from .database.jinjaview import JinjaDBConnection
|
from .database.jinjaview import JinjaDBConnection
|
||||||
from .images import image_request
|
from .images import image_request
|
||||||
from .malojauri import uri_to_internal, remove_identical
|
from .malojauri import uri_to_internal, remove_identical
|
||||||
from .pkg_global.conf import malojaconfig, data_dir
|
from .pkg_global.conf import malojaconfig, data_dir, auth
|
||||||
from .pkg_global import conf
|
from .pkg_global import conf
|
||||||
from .jinjaenv.context import jinja_environment
|
from .jinjaenv.context import jinja_environment
|
||||||
from .apis import init_apis, apikeystore
|
from .apis import init_apis, apikeystore
|
||||||
@ -97,7 +96,7 @@ aliases = {
|
|||||||
|
|
||||||
### API
|
### API
|
||||||
|
|
||||||
auth.authapi.mount(server=webserver)
|
conf.auth.authapi.mount(server=webserver)
|
||||||
init_apis(webserver)
|
init_apis(webserver)
|
||||||
|
|
||||||
# redirects for backwards compatibility
|
# redirects for backwards compatibility
|
||||||
@ -197,7 +196,7 @@ def jinja_page(name):
|
|||||||
if name in aliases: redirect(aliases[name])
|
if name in aliases: redirect(aliases[name])
|
||||||
keys = remove_identical(FormsDict.decode(request.query))
|
keys = remove_identical(FormsDict.decode(request.query))
|
||||||
|
|
||||||
adminmode = request.cookies.get("adminmode") == "true" and auth.check(request)
|
adminmode = request.cookies.get("adminmode") == "true" and auth.check_request(request)
|
||||||
|
|
||||||
with JinjaDBConnection() as conn:
|
with JinjaDBConnection() as conn:
|
||||||
|
|
||||||
@ -222,7 +221,7 @@ def jinja_page(name):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
@webserver.route("/<name:re:admin.*>")
|
@webserver.route("/<name:re:admin.*>")
|
||||||
@auth.authenticated
|
@auth.authenticated_function()
|
||||||
def jinja_page_private(name):
|
def jinja_page_private(name):
|
||||||
return jinja_page(name)
|
return jinja_page(name)
|
||||||
|
|
||||||
|
@ -6,9 +6,8 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
import distutils
|
import distutils
|
||||||
from doreah.io import col, ask, prompt
|
from doreah.io import col, ask, prompt
|
||||||
from doreah import auth
|
|
||||||
|
|
||||||
from .pkg_global.conf import data_dir, dir_settings, malojaconfig
|
from .pkg_global.conf import data_dir, dir_settings, malojaconfig, auth
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -67,10 +66,10 @@ def setup():
|
|||||||
|
|
||||||
if forcepassword is not None:
|
if forcepassword is not None:
|
||||||
# user has specified to force the pw, nothing else matters
|
# user has specified to force the pw, nothing else matters
|
||||||
auth.defaultuser.setpw(forcepassword)
|
auth.change_pw(password=forcepassword)
|
||||||
print("Password has been set.")
|
print("Password has been set.")
|
||||||
elif auth.defaultuser.checkpw("admin"):
|
elif auth.still_has_factory_default_user():
|
||||||
# if the actual pw is admin, it means we've never set this up properly (eg first start after update)
|
# this means we've never set this up properly (eg first start after update)
|
||||||
while True:
|
while True:
|
||||||
newpw = prompt("Please set a password for web backend access. Leave this empty to generate a random password.",skip=SKIP,secret=True)
|
newpw = prompt("Please set a password for web backend access. Leave this empty to generate a random password.",skip=SKIP,secret=True)
|
||||||
if newpw is None:
|
if newpw is None:
|
||||||
@ -81,7 +80,7 @@ def setup():
|
|||||||
newpw_repeat = prompt("Please type again to confirm.",skip=SKIP,secret=True)
|
newpw_repeat = prompt("Please type again to confirm.",skip=SKIP,secret=True)
|
||||||
if newpw != newpw_repeat: print("Passwords do not match!")
|
if newpw != newpw_repeat: print("Passwords do not match!")
|
||||||
else: break
|
else: break
|
||||||
auth.defaultuser.setpw(newpw)
|
auth.change_pw(password=newpw)
|
||||||
|
|
||||||
except EOFError:
|
except EOFError:
|
||||||
print("No user input possible. If you are running inside a container, set the environment variable",col['yellow']("MALOJA_SKIP_SETUP=yes"))
|
print("No user input possible. If you are running inside a container, set the environment variable",col['yellow']("MALOJA_SKIP_SETUP=yes"))
|
||||||
|
@ -3,7 +3,7 @@ name = "malojaserver"
|
|||||||
version = "3.2.2"
|
version = "3.2.2"
|
||||||
description = "Self-hosted music scrobble database"
|
description = "Self-hosted music scrobble database"
|
||||||
readme = "./README.md"
|
readme = "./README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.11"
|
||||||
license = { file="./LICENSE" }
|
license = { file="./LICENSE" }
|
||||||
authors = [ { name="Johannes Krattenmacher", email="maloja@dev.krateng.ch" } ]
|
authors = [ { name="Johannes Krattenmacher", email="maloja@dev.krateng.ch" } ]
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ classifiers = [
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bottle>=0.12.16",
|
"bottle>=0.12.16",
|
||||||
"waitress>=2.1.0",
|
"waitress>=2.1.0",
|
||||||
"doreah>=1.9.4, <2",
|
"doreah>=2.0.0, <3",
|
||||||
"nimrodel>=0.8.0",
|
"nimrodel>=0.8.0",
|
||||||
"setproctitle>=1.1.10",
|
"setproctitle>=1.1.10",
|
||||||
#"pyvips>=2.1.16",
|
#"pyvips>=2.1.16",
|
||||||
@ -31,7 +31,9 @@ dependencies = [
|
|||||||
"sqlalchemy>=2.0",
|
"sqlalchemy>=2.0",
|
||||||
"python-datauri>=1.1.0",
|
"python-datauri>=1.1.0",
|
||||||
"requests>=2.27.1",
|
"requests>=2.27.1",
|
||||||
"setuptools>68.0.0"
|
"setuptools>68.0.0",
|
||||||
|
"toml>=0.10.2",
|
||||||
|
"PyYAML>=6.0.1"
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
bottle>=0.12.16
|
bottle>=0.12.16
|
||||||
waitress>=2.1.0
|
waitress>=2.1.0
|
||||||
doreah>=1.9.4, <2
|
doreah>=2.0.0, <3
|
||||||
nimrodel>=0.8.0
|
nimrodel>=0.8.0
|
||||||
setproctitle>=1.1.10
|
setproctitle>=1.1.10
|
||||||
jinja2>=3.0.0
|
jinja2>=3.0.0
|
||||||
@ -10,3 +10,5 @@ sqlalchemy>=2.0
|
|||||||
python-datauri>=1.1.0
|
python-datauri>=1.1.0
|
||||||
requests>=2.27.1
|
requests>=2.27.1
|
||||||
setuptools>68.0.0
|
setuptools>68.0.0
|
||||||
|
toml>=0.10.2
|
||||||
|
PyYAML>=6.0.1
|
Loading…
x
Reference in New Issue
Block a user