Compare commits

..

10 Commits

Author SHA1 Message Date
VikashKothary
35cf66d403 Update requirements{-dev}.txt 2023-07-14 16:11:06 +00:00
LuckyTurtleDev
86ba071b6c
add Dockerimage (#152)
* add Dockerfile

* Create docker.yml

* Update docker.yml

* Update docker.yml

* Update docker.yml

* Update docker.yml

* Update docker.yml

* Update docker.yml

* Update docker.yml

* hardcode python version

* install rustup

* Revert "install rustup"

This reverts commit 5f701ee084713bb633c7e840523574d96fa9191e.

* Update docker.yml

* Update docker.yml
2023-07-14 17:10:16 +01:00
VikashKothary
7532c960f2 Update requirements{-dev}.txt 2023-03-26 10:03:19 +00:00
Vikash Kothary
a9a2bb5646
docs: Add acknowledgements section
Resolves https://github.com/ankicommunity/anki-sync-server/issues/161
2023-03-26 11:02:32 +01:00
Vikash Kothary
f2c4316e29 fix: Check if path is None before checking path 2022-10-19 09:59:40 +01:00
VikashKothary
bc6f8e8f5c Update requirements{-dev}.txt 2022-10-19 08:54:28 +00:00
Vikash Kothary
4041d28b03 fix: Update cli to load config from config file and env variables 2022-10-19 09:51:35 +01:00
Vikash Kothary
4c4ce64025 refactor: Seperate users into multiple files 2022-10-14 23:51:43 +01:00
Vikash Kothary
82a3e729f1 refactor: Separate sessions into multiple files 2022-10-14 23:42:34 +01:00
Vikash Kothary
97a740417b refactor: Move exceptions into a separate file 2022-10-14 23:36:13 +01:00
16 changed files with 2732 additions and 2352 deletions

44
.github/workflows/docker.yml vendored Normal file
View File

@ -0,0 +1,44 @@
on:
push:
pull_request:
release:
types: [published]
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout repository and submodules
uses: actions/checkout@v3
with:
submodules: recursive
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
if: ${{ inputs.platforms != 'linux/amd64' }}
uses: docker/setup-qemu-action@v2
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ format('ghcr.io/{0}', github.repository) }}
tags: |
type=semver,pattern=v{{major}}
type=semver,pattern=v{{major}}.{{minor}}
type=ref,event=branch
type=ref,event=tag
- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}}
platforms: linux/amd64

19
Dockerfile Normal file
View File

@ -0,0 +1,19 @@
FROM python:3.10-slim
COPY src /src
RUN cd /src \
&& pip install -r requirements.txt \
&& pip install -e .
COPY src/ankisyncd /ankisyncd
COPY src/ankisyncd_cli /ankisyncd_cli
COPY src/ankisyncd.conf /ankisyncd.conf
RUN sed -i -e '/data_root =/ s/= .*/= \/data\/collections/' /ankisyncd.conf \
&& sed -i -e '/auth_db_path =/ s/= .*/= \/data\/auth\.db/' /ankisyncd.conf \
&& sed -i -e '/session_db_path =/ s/= .*/= \/data\/session.db/' /ankisyncd.conf \
&& cat /ankisyncd.conf
#see https://github.com/ankicommunity/anki-sync-server/issues/139
ENV PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python
CMD ["python", "-m", "ankisyncd"]

View File

@ -31,15 +31,10 @@ Linux and macOS) which can sync to a web version (AnkiWeb) and mobile
versions for Android and iOS.
This is a personal Anki server, which you can sync against instead of
AnkiWeb. It was originally developed by [David Snopek](https://github.com/dsnopek)
to support the flashcard functionality on Bibliobird, a web application for
language learning.
This version is a fork of [jdoe0/ankisyncd](https://github.com/jdoe0/ankisyncd).
It supports Python 3 and Anki 2.1.
AnkiWeb.
[Anki]: https://apps.ankiweb.net/
[dsnopek's Anki Sync Server]: https://github.com/dsnopek/anki-sync-server
<details open><summary>Contents</summary>
@ -254,3 +249,11 @@ sqlite3 is used by default for user data, authentication and session persistence
persistence requirements (the media DB and files are being worked on). All that is
required is to extend one of the existing manager classes and then reference those
classes in the config file. See ankisyncd.conf for example config.
## Acknowledgment
- This server was originally developed by [David Snopek](https://github.com/dsnopek)
to support the flashcard functionality on Bibliobird, a web application for
language learning.
- It was then forked by `jdoe0` to add supports Python 3 and Anki 2.1.
- It was then forked by [tsudoko](https://github.com/tsudoko) which was the base for this repo.

4526
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -28,7 +28,7 @@ def load_from_env(conf):
def load_from_file(path=None):
# backwards compat
if len(path) > 1:
if path is not None and len(path) > 1:
path = path[1]
else:
path = None

View File

@ -0,0 +1 @@
from webob.exc import HTTPBadRequest as BadRequestException

View File

@ -1,10 +1,11 @@
import shutil
from sqlite3 import dbapi2 as sqlite
from webob.exc import HTTPBadRequest
from anki.db import DB
from anki.collection import Collection
from ankisyncd.exceptions import BadRequestException
class FullSyncManager:
def test_db(self, db: DB):
@ -12,7 +13,7 @@ class FullSyncManager:
:param anki.db.DB db: the database uploaded from the client.
"""
if db.scalar("pragma integrity_check") != "ok":
raise HTTPBadRequest(
raise BadRequestException(
"Integrity check failed for uploaded collection database file."
)
@ -34,7 +35,9 @@ class FullSyncManager:
with DB(temp_db_path) as test_db:
self.test_db(test_db)
except sqlite.Error as e:
raise HTTPBadRequest("Uploaded collection database file is " "corrupt.")
raise BadRequestException(
"Uploaded collection database file is " "corrupt."
)
# Overwrite existing db.
col.close()

View File

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
import importlib
import inspect
from ankisyncd import logging
from ankisyncd.sessions.simple_manager import SimpleSessionManager
from ankisyncd.sessions.sqlite_manager import SqliteSessionManager
logger = logging.get_logger(__name__)
def get_session_manager(config):
if "session_db_path" in config and config["session_db_path"]:
logger.info(
"Found session_db_path in config, using SqliteSessionManager for auth"
)
return SqliteSessionManager(config["session_db_path"])
elif "session_manager" in config and config["session_manager"]: # load from config
logger.info(
"Found session_manager in config, using {} for persisting sessions".format(
config["session_manager"]
)
)
module_name, class_name = config["session_manager"].rsplit(".", 1)
module = importlib.import_module(module_name.strip())
class_ = getattr(module, class_name.strip())
if not SimpleSessionManager in inspect.getmro(class_):
raise TypeError(
""""session_manager" found in the conf file but it doesn''t
inherit from SimpleSessionManager"""
)
return class_(config)
else:
logger.warning(
"Neither session_db_path nor session_manager set, "
"ankisyncd will lose sessions on application restart"
)
return SimpleSessionManager()

View File

@ -0,0 +1,19 @@
class SimpleSessionManager:
"""A simple session manager that keeps the sessions in memory."""
def __init__(self):
self.sessions = {}
def load(self, hkey, session_factory=None):
return self.sessions.get(hkey)
def load_from_skey(self, skey, session_factory=None):
for i in self.sessions:
if self.sessions[i].skey == skey:
return self.sessions[i]
def save(self, hkey, session):
self.sessions[hkey] = session
def delete(self, hkey):
del self.sessions[hkey]

View File

@ -1,30 +1,6 @@
# -*- coding: utf-8 -*-
import os
import logging
from sqlite3 import dbapi2 as sqlite
logger = logging.getLogger("ankisyncd.sessions")
class SimpleSessionManager:
"""A simple session manager that keeps the sessions in memory."""
def __init__(self):
self.sessions = {}
def load(self, hkey, session_factory=None):
return self.sessions.get(hkey)
def load_from_skey(self, skey, session_factory=None):
for i in self.sessions:
if self.sessions[i].skey == skey:
return self.sessions[i]
def save(self, hkey, session):
self.sessions[hkey] = session
def delete(self, hkey):
del self.sessions[hkey]
from ankisyncd.sessions.simple_manager import SimpleSessionManager
class SqliteSessionManager(SimpleSessionManager):
@ -128,36 +104,3 @@ class SqliteSessionManager(SimpleSessionManager):
cursor.execute(self.fs("DELETE FROM session WHERE hkey=?"), (hkey,))
conn.commit()
def get_session_manager(config):
if "session_db_path" in config and config["session_db_path"]:
logger.info(
"Found session_db_path in config, using SqliteSessionManager for auth"
)
return SqliteSessionManager(config["session_db_path"])
elif "session_manager" in config and config["session_manager"]: # load from config
logger.info(
"Found session_manager in config, using {} for persisting sessions".format(
config["session_manager"]
)
)
import importlib
import inspect
module_name, class_name = config["session_manager"].rsplit(".", 1)
module = importlib.import_module(module_name.strip())
class_ = getattr(module, class_name.strip())
if not SimpleSessionManager in inspect.getmro(class_):
raise TypeError(
""""session_manager" found in the conf file but it doesn''t
inherit from SimpleSessionManager"""
)
return class_(config)
else:
logger.warning(
"Neither session_db_path nor session_manager set, "
"ankisyncd will lose sessions on application restart"
)
return SimpleSessionManager()

View File

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
import importlib
import inspect
from ankisyncd import logging
from ankisyncd.users.simple_manager import SimpleUserManager
from ankisyncd.users.sqlite_manager import SqliteUserManager
logger = logging.get_logger(__name__)
def get_user_manager(config):
if "auth_db_path" in config and config["auth_db_path"]:
logger.info("Found auth_db_path in config, using SqliteUserManager for auth")
return SqliteUserManager(config["auth_db_path"], config["data_root"])
elif "user_manager" in config and config["user_manager"]: # load from config
logger.info(
"Found user_manager in config, using {} for auth".format(
config["user_manager"]
)
)
module_name, class_name = config["user_manager"].rsplit(".", 1)
module = importlib.import_module(module_name.strip())
class_ = getattr(module, class_name.strip())
if not SimpleUserManager in inspect.getmro(class_):
raise TypeError(
""""user_manager" found in the conf file but it doesn''t
inherit from SimpleUserManager"""
)
return class_(config)
else:
logger.warning(
"neither auth_db_path nor user_manager set, ankisyncd will accept any password"
)
return SimpleUserManager()

View File

@ -0,0 +1,38 @@
import os
from ankisyncd import logging
logger = logging.get_logger(__name__)
class SimpleUserManager:
"""A simple user manager that always allows any user."""
def __init__(self, collection_path=""):
self.collection_path = collection_path
def authenticate(self, username, password):
"""
Returns True if this username is allowed to connect with this password.
False otherwise. Override this to change how users are authenticated.
"""
return True
def userdir(self, username):
"""
Returns the directory name for the given user. By default, this is just
the username. Override this to adjust the mapping between users and
their directory.
"""
return username
def _create_user_dir(self, username):
user_dir_path = os.path.join(self.collection_path, username)
if not os.path.isdir(user_dir_path):
logger.info(
"Creating collection directory for user '{}' at {}".format(
username, user_dir_path
)
)
os.makedirs(user_dir_path)

View File

@ -1,45 +1,11 @@
# -*- coding: utf-8 -*-
import binascii
import hashlib
import logging
import os
import sqlite3 as sqlite
from ankisyncd import logging
from ankisyncd.users.simple_manager import SimpleUserManager
logger = logging.getLogger("ankisyncd.users")
class SimpleUserManager:
"""A simple user manager that always allows any user."""
def __init__(self, collection_path=""):
self.collection_path = collection_path
def authenticate(self, username, password):
"""
Returns True if this username is allowed to connect with this password.
False otherwise. Override this to change how users are authenticated.
"""
return True
def userdir(self, username):
"""
Returns the directory name for the given user. By default, this is just
the username. Override this to adjust the mapping between users and
their directory.
"""
return username
def _create_user_dir(self, username):
user_dir_path = os.path.join(self.collection_path, username)
if not os.path.isdir(user_dir_path):
logger.info(
"Creating collection directory for user '{}' at {}".format(
username, user_dir_path
)
)
os.makedirs(user_dir_path)
logger = logging.get_logger(__name__)
class SqliteUserManager(SimpleUserManager):
@ -208,33 +174,3 @@ class SqliteUserManager(SimpleUserManager):
)
conn.commit()
conn.close()
def get_user_manager(config):
if "auth_db_path" in config and config["auth_db_path"]:
logger.info("Found auth_db_path in config, using SqliteUserManager for auth")
return SqliteUserManager(config["auth_db_path"], config["data_root"])
elif "user_manager" in config and config["user_manager"]: # load from config
logger.info(
"Found user_manager in config, using {} for auth".format(
config["user_manager"]
)
)
import importlib
import inspect
module_name, class_name = config["user_manager"].rsplit(".", 1)
module = importlib.import_module(module_name.strip())
class_ = getattr(module, class_name.strip())
if not SimpleUserManager in inspect.getmro(class_):
raise TypeError(
""""user_manager" found in the conf file but it doesn''t
inherit from SimpleUserManager"""
)
return class_(config)
else:
logger.warning(
"neither auth_db_path nor user_manager set, ankisyncd will accept any password"
)
return SimpleUserManager()

View File

@ -3,11 +3,12 @@
import sys
import getpass
from ankisyncd import config
from ankisyncd import config as config_provider
from ankisyncd.users import get_user_manager
config = config.load()
config = config_provider.load_from_file()
config_provider.load_from_env(config)
def usage():

View File

@ -2,124 +2,139 @@
anki==2.1.49 ; python_version >= "3.8" and python_version < "4.0"
anyio==3.6.1 ; python_version >= "3.8" and python_version < "4.0"
appnope==0.1.3 ; python_version >= "3.8" and python_version < "4.0" and platform_system == "Darwin" or python_version >= "3.8" and python_version < "4.0" and sys_platform == "darwin"
anyio==3.7.1 ; python_version >= "3.8" and python_version < "4.0"
appnope==0.1.3 ; python_version >= "3.8" and python_version < "4.0" and (platform_system == "Darwin" or sys_platform == "darwin")
argon2-cffi-bindings==21.2.0 ; python_version >= "3.8" and python_version < "4.0"
argon2-cffi==21.3.0 ; python_version >= "3.8" and python_version < "4.0"
asttokens==2.0.8 ; python_version >= "3.8" and python_version < "4.0"
attrs==22.1.0 ; python_version >= "3.8" and python_version < "4.0"
arrow==1.2.3 ; python_version >= "3.8" and python_version < "4.0"
asttokens==2.2.1 ; python_version >= "3.8" and python_version < "4.0"
attrs==23.1.0 ; python_version >= "3.8" and python_version < "4.0"
backcall==0.2.0 ; python_version >= "3.8" and python_version < "4.0"
beautifulsoup4==4.11.1 ; python_version >= "3.8" and python_version < "4.0"
black==22.10.0 ; python_version >= "3.8" and python_version < "4.0"
bleach==5.0.1 ; python_version >= "3.8" and python_version < "4.0"
certifi==2022.9.24 ; python_version >= "3.8" and python_version < "4"
beautifulsoup4==4.12.2 ; python_version >= "3.8" and python_version < "4.0"
black==22.12.0 ; python_version >= "3.8" and python_version < "4.0"
bleach==6.0.0 ; python_version >= "3.8" and python_version < "4.0"
certifi==2023.5.7 ; python_version >= "3.8" and python_version < "4.0"
cffi==1.15.1 ; python_version >= "3.8" and python_version < "4.0"
charset-normalizer==2.1.1 ; python_version >= "3.8" and python_version < "4"
click==8.1.3 ; python_version >= "3.8" and python_version < "4.0"
colorama==0.4.5 ; python_version >= "3.8" and python_version < "4.0" and platform_system == "Windows" or python_version >= "3.8" and python_version < "4.0" and sys_platform == "win32"
debugpy==1.6.3 ; python_version >= "3.8" and python_version < "4.0"
charset-normalizer==3.2.0 ; python_version >= "3.8" and python_version < "4.0"
click==8.1.5 ; python_version >= "3.8" and python_version < "4.0"
colorama==0.4.6 ; python_version >= "3.8" and python_version < "4.0" and (platform_system == "Windows" or sys_platform == "win32")
comm==0.1.3 ; python_version >= "3.8" and python_version < "4.0"
debugpy==1.6.7 ; python_version >= "3.8" and python_version < "4.0"
decorator==4.4.2 ; python_version >= "3.8" and python_version < "4.0"
defusedxml==0.7.1 ; python_version >= "3.8" and python_version < "4.0"
distro==1.8.0 ; python_version >= "3.8" and python_version < "4.0"
entrypoints==0.4 ; python_version >= "3.8" and python_version < "4.0"
executing==1.1.1 ; python_version >= "3.8" and python_version < "4.0"
fastjsonschema==2.16.2 ; python_version >= "3.8" and python_version < "4.0"
exceptiongroup==1.1.2 ; python_version >= "3.8" and python_version < "3.11"
executing==1.2.0 ; python_version >= "3.8" and python_version < "4.0"
fastjsonschema==2.17.1 ; python_version >= "3.8" and python_version < "4.0"
fqdn==1.5.1 ; python_version >= "3.8" and python_version < "4"
ghp-import==2.1.0 ; python_version >= "3.8" and python_version < "4.0"
idna==3.4 ; python_version >= "3.8" and python_version < "4"
importlib-metadata==5.0.0 ; python_version >= "3.8" and python_version < "3.10"
importlib-resources==5.10.0 ; python_version >= "3.8" and python_version < "3.9"
ipykernel==6.16.0 ; python_version >= "3.8" and python_version < "4.0"
idna==3.4 ; python_version >= "3.8" and python_version < "4.0"
importlib-metadata==6.8.0 ; python_version >= "3.8" and python_version < "3.10"
importlib-resources==6.0.0 ; python_version >= "3.8" and python_version < "3.9"
ipykernel==6.24.0 ; python_version >= "3.8" and python_version < "4.0"
ipython-genutils==0.2.0 ; python_version >= "3.8" and python_version < "4.0"
ipython==8.5.0 ; python_version >= "3.8" and python_version < "4.0"
ipywidgets==8.0.2 ; python_version >= "3.8" and python_version < "4.0"
jedi==0.18.1 ; python_version >= "3.8" and python_version < "4.0"
ipython==8.12.2 ; python_version >= "3.8" and python_version < "4.0"
ipywidgets==8.0.7 ; python_version >= "3.8" and python_version < "4.0"
isoduration==20.11.0 ; python_version >= "3.8" and python_version < "4.0"
jedi==0.18.2 ; python_version >= "3.8" and python_version < "4.0"
jinja2==3.1.2 ; python_version >= "3.8" and python_version < "4.0"
json5==0.9.10 ; python_version >= "3.8" and python_version < "4.0"
jsonschema==4.16.0 ; python_version >= "3.8" and python_version < "4.0"
jupyter-client==7.4.2 ; python_version >= "3.8" and python_version < "4.0"
jupyter-console==6.4.4 ; python_version >= "3.8" and python_version < "4.0"
jupyter-core==4.11.1 ; python_version >= "3.8" and python_version < "4.0"
jupyter-server==1.21.0 ; python_version >= "3.8" and python_version < "4.0"
json5==0.9.14 ; python_version >= "3.8" and python_version < "4.0"
jsonpointer==2.4 ; python_version >= "3.8" and python_version < "4.0"
jsonschema-specifications==2023.6.1 ; python_version >= "3.8" and python_version < "4.0"
jsonschema==4.18.3 ; python_version >= "3.8" and python_version < "4.0"
jsonschema[format-nongpl]==4.18.3 ; python_version >= "3.8" and python_version < "4.0"
jupyter-client==8.3.0 ; python_version >= "3.8" and python_version < "4.0"
jupyter-console==6.6.3 ; python_version >= "3.8" and python_version < "4.0"
jupyter-core==5.3.1 ; python_version >= "3.8" and python_version < "4.0"
jupyter-events==0.6.3 ; python_version >= "3.8" and python_version < "4.0"
jupyter-server-terminals==0.4.4 ; python_version >= "3.8" and python_version < "4.0"
jupyter-server==2.7.0 ; python_version >= "3.8" and python_version < "4.0"
jupyter==1.0.0 ; python_version >= "3.8" and python_version < "4.0"
jupyterlab-pygments==0.2.2 ; python_version >= "3.8" and python_version < "4.0"
jupyterlab-server==1.2.0 ; python_version >= "3.8" and python_version < "4.0"
jupyterlab-widgets==3.0.3 ; python_version >= "3.8" and python_version < "4.0"
jupyterlab-widgets==3.0.8 ; python_version >= "3.8" and python_version < "4.0"
jupyterlab==2.3.2 ; python_version >= "3.8" and python_version < "4.0"
jupytext==1.14.1 ; python_version >= "3.8" and python_version < "4.0"
lxml==4.9.1 ; python_version >= "3.8" and python_version < "4.0"
markdown-it-py==2.1.0 ; python_version >= "3.8" and python_version < "4.0"
jupytext==1.14.7 ; python_version >= "3.8" and python_version < "4.0"
lxml==4.9.3 ; python_version >= "3.8" and python_version < "4.0"
markdown-it-py==3.0.0 ; python_version >= "3.8" and python_version < "4.0"
markdown==3.3.7 ; python_version >= "3.8" and python_version < "4.0"
markupsafe==2.1.1 ; python_version >= "3.8" and python_version < "4.0"
markupsafe==2.1.3 ; python_version >= "3.8" and python_version < "4.0"
matplotlib-inline==0.1.6 ; python_version >= "3.8" and python_version < "4.0"
mdit-py-plugins==0.3.1 ; python_version >= "3.8" and python_version < "4.0"
mdit-py-plugins==0.4.0 ; python_version >= "3.8" and python_version < "4.0"
mdurl==0.1.2 ; python_version >= "3.8" and python_version < "4.0"
mergedeep==1.3.4 ; python_version >= "3.8" and python_version < "4.0"
mistune==0.8.4 ; python_version >= "3.8" and python_version < "4.0"
mkdocs-jupyter==0.19.0 ; python_version >= "3.8" and python_version < "4"
mkdocs-material-extensions==1.0.3 ; python_version >= "3.8" and python_version < "4"
mkdocs-material==8.5.6 ; python_version >= "3.8" and python_version < "4"
mkdocs==1.4.0 ; python_version >= "3.8" and python_version < "4.0"
mypy-extensions==0.4.3 ; python_version >= "3.8" and python_version < "4.0"
nbclassic==0.4.5 ; python_version >= "3.8" and python_version < "4.0"
nbclient==0.7.0 ; python_version >= "3.8" and python_version < "4.0"
mkdocs-material-extensions==1.1.1 ; python_version >= "3.8" and python_version < "4"
mkdocs-material==8.5.11 ; python_version >= "3.8" and python_version < "4"
mkdocs==1.4.3 ; python_version >= "3.8" and python_version < "4.0"
mypy-extensions==1.0.0 ; python_version >= "3.8" and python_version < "4.0"
nbclassic==1.0.0 ; python_version >= "3.8" and python_version < "4.0"
nbclient==0.8.0 ; python_version >= "3.8" and python_version < "4.0"
nbconvert==6.5.4 ; python_version >= "3.8" and python_version < "4.0"
nbformat==5.7.0 ; python_version >= "3.8" and python_version < "4.0"
nbformat==5.9.1 ; python_version >= "3.8" and python_version < "4.0"
nest-asyncio==1.5.6 ; python_version >= "3.8" and python_version < "4.0"
notebook-shim==0.1.0 ; python_version >= "3.8" and python_version < "4.0"
notebook==6.5.1 ; python_version >= "3.8" and python_version < "4.0"
orjson==3.8.0 ; python_version >= "3.8" and python_version < "4.0" and platform_machine == "x86_64"
packaging==21.3 ; python_version >= "3.8" and python_version < "4.0"
notebook-shim==0.2.3 ; python_version >= "3.8" and python_version < "4.0"
notebook==6.5.4 ; python_version >= "3.8" and python_version < "4.0"
orjson==3.9.2 ; python_version >= "3.8" and python_version < "4.0" and platform_machine == "x86_64"
overrides==7.3.1 ; python_version >= "3.8" and python_version < "4.0"
packaging==23.1 ; python_version >= "3.8" and python_version < "4.0"
pandocfilters==1.5.0 ; python_version >= "3.8" and python_version < "4.0"
parso==0.8.3 ; python_version >= "3.8" and python_version < "4.0"
pathspec==0.10.1 ; python_version >= "3.8" and python_version < "4.0"
pathspec==0.11.1 ; python_version >= "3.8" and python_version < "4.0"
pexpect==4.8.0 ; python_version >= "3.8" and python_version < "4.0" and sys_platform != "win32"
pickleshare==0.7.5 ; python_version >= "3.8" and python_version < "4.0"
pkgutil-resolve-name==1.3.10 ; python_version >= "3.8" and python_version < "3.9"
platformdirs==2.5.2 ; python_version >= "3.8" and python_version < "4.0"
prometheus-client==0.15.0 ; python_version >= "3.8" and python_version < "4.0"
prompt-toolkit==3.0.31 ; python_version >= "3.8" and python_version < "4.0"
platformdirs==3.8.1 ; python_version >= "3.8" and python_version < "4.0"
prometheus-client==0.17.1 ; python_version >= "3.8" and python_version < "4.0"
prompt-toolkit==3.0.39 ; python_version >= "3.8" and python_version < "4.0"
protobuf==3.20.2 ; python_version >= "3.8" and python_version < "4.0"
psutil==5.9.2 ; python_version >= "3.8" and python_version < "4.0"
ptyprocess==0.7.0 ; python_version >= "3.8" and python_version < "4.0" and os_name != "nt" or python_version >= "3.8" and python_version < "4.0" and sys_platform != "win32"
psutil==5.9.5 ; python_version >= "3.8" and python_version < "4.0"
ptyprocess==0.7.0 ; python_version >= "3.8" and python_version < "4.0" and (os_name != "nt" or sys_platform != "win32")
pure-eval==0.2.2 ; python_version >= "3.8" and python_version < "4.0"
py==1.11.0 ; python_version >= "3.8" and python_version < "4.0" and implementation_name == "pypy"
pycparser==2.21 ; python_version >= "3.8" and python_version < "4.0"
pygments==2.13.0 ; python_version >= "3.8" and python_version < "4.0"
pymdown-extensions==9.6 ; python_version >= "3.8" and python_version < "4"
pyparsing==3.0.9 ; python_version >= "3.8" and python_version < "4.0"
pyrsistent==0.18.1 ; python_version >= "3.8" and python_version < "4.0"
pysocks==1.7.1 ; python_version >= "3.8" and python_version < "4"
pygments==2.15.1 ; python_version >= "3.8" and python_version < "4.0"
pymdown-extensions==10.1 ; python_version >= "3.8" and python_version < "4"
pysocks==1.7.1 ; python_version >= "3.8" and python_version < "4.0"
python-dateutil==2.8.2 ; python_version >= "3.8" and python_version < "4.0"
pywin32==304 ; sys_platform == "win32" and platform_python_implementation != "PyPy" and python_version >= "3.8" and python_version < "4.0"
pywinpty==2.0.8 ; python_version >= "3.8" and python_version < "4.0" and os_name == "nt"
python-json-logger==2.0.7 ; python_version >= "3.8" and python_version < "4.0"
pywin32==306 ; sys_platform == "win32" and platform_python_implementation != "PyPy" and python_version >= "3.8" and python_version < "4.0"
pywinpty==2.0.10 ; python_version >= "3.8" and python_version < "4.0" and os_name == "nt"
pyyaml-env-tag==0.1 ; python_version >= "3.8" and python_version < "4.0"
pyyaml==6.0 ; python_version >= "3.8" and python_version < "4.0"
pyzmq==24.0.1 ; python_version >= "3.8" and python_version < "4.0"
qtconsole==5.3.2 ; python_version >= "3.8" and python_version < "4.0"
qtpy==2.2.1 ; python_version >= "3.8" and python_version < "4.0"
requests==2.28.1 ; python_version >= "3.8" and python_version < "4"
requests[socks]==2.28.1 ; python_version >= "3.8" and python_version < "4"
send2trash==1.8.0 ; python_version >= "3.8" and python_version < "4.0"
pyzmq==25.1.0 ; python_version >= "3.8" and python_version < "4.0"
qtconsole==5.4.3 ; python_version >= "3.8" and python_version < "4.0"
qtpy==2.3.1 ; python_version >= "3.8" and python_version < "4.0"
referencing==0.29.1 ; python_version >= "3.8" and python_version < "4.0"
requests==2.31.0 ; python_version >= "3.8" and python_version < "4.0"
requests[socks]==2.31.0 ; python_version >= "3.8" and python_version < "4.0"
rfc3339-validator==0.1.4 ; python_version >= "3.8" and python_version < "4.0"
rfc3986-validator==0.1.1 ; python_version >= "3.8" and python_version < "4.0"
rpds-py==0.8.10 ; python_version >= "3.8" and python_version < "4.0"
send2trash==1.8.2 ; python_version >= "3.8" and python_version < "4.0"
six==1.16.0 ; python_version >= "3.8" and python_version < "4.0"
sniffio==1.3.0 ; python_version >= "3.8" and python_version < "4.0"
soupsieve==2.3.2.post1 ; python_version >= "3.8" and python_version < "4.0"
stack-data==0.5.1 ; python_version >= "3.8" and python_version < "4.0"
soupsieve==2.4.1 ; python_version >= "3.8" and python_version < "4.0"
stack-data==0.6.2 ; python_version >= "3.8" and python_version < "4.0"
stringcase==1.2.0 ; python_version >= "3.8" and python_version < "4.0"
terminado==0.16.0 ; python_version >= "3.8" and python_version < "4.0"
tinycss2==1.1.1 ; python_version >= "3.8" and python_version < "4.0"
terminado==0.17.1 ; python_version >= "3.8" and python_version < "4.0"
tinycss2==1.2.1 ; python_version >= "3.8" and python_version < "4.0"
toml==0.10.2 ; python_version >= "3.8" and python_version < "4.0"
tomli==2.0.1 ; python_version >= "3.8" and python_full_version < "3.11.0a7"
tornado==6.2 ; python_version >= "3.8" and python_version < "4.0"
traitlets==5.4.0 ; python_version >= "3.8" and python_version < "4.0"
typing-extensions==4.4.0 ; python_version >= "3.8" and python_version < "3.10"
urllib3==1.26.12 ; python_version >= "3.8" and python_version < "4"
tornado==6.3.2 ; python_version >= "3.8" and python_version < "4.0"
traitlets==5.9.0 ; python_version >= "3.8" and python_version < "4.0"
typing-extensions==4.7.1 ; python_version >= "3.8" and python_version < "3.10"
uri-template==1.3.0 ; python_version >= "3.8" and python_version < "4.0"
urllib3==2.0.3 ; python_version >= "3.8" and python_version < "4.0"
waitress==2.1.2 ; python_version >= "3.8" and python_version < "4.0"
watchdog==2.1.9 ; python_version >= "3.8" and python_version < "4.0"
wcwidth==0.2.5 ; python_version >= "3.8" and python_version < "4.0"
watchdog==3.0.0 ; python_version >= "3.8" and python_version < "4.0"
wcwidth==0.2.6 ; python_version >= "3.8" and python_version < "4.0"
webcolors==1.13 ; python_version >= "3.8" and python_version < "4.0"
webencodings==0.5.1 ; python_version >= "3.8" and python_version < "4.0"
webob==1.8.7 ; python_version >= "3.8" and python_version < "4.0"
websocket-client==1.4.1 ; python_version >= "3.8" and python_version < "4.0"
websocket-client==1.6.1 ; python_version >= "3.8" and python_version < "4.0"
webtest==2.0.35 ; python_version >= "3.8" and python_version < "4.0"
widgetsnbextension==4.0.3 ; python_version >= "3.8" and python_version < "4.0"
zipp==3.9.0 ; python_version >= "3.8" and python_version < "3.10"
widgetsnbextension==4.0.8 ; python_version >= "3.8" and python_version < "4.0"
zipp==3.16.1 ; python_version >= "3.8" and python_version < "3.10"
-e src/.

View File

@ -2,23 +2,23 @@
anki==2.1.49 ; python_version >= "3.8" and python_version < "4.0"
beautifulsoup4==4.11.1 ; python_version >= "3.8" and python_version < "4.0"
certifi==2022.9.24 ; python_version >= "3.8" and python_version < "4"
charset-normalizer==2.1.1 ; python_version >= "3.8" and python_version < "4"
beautifulsoup4==4.12.2 ; python_version >= "3.8" and python_version < "4.0"
certifi==2023.5.7 ; python_version >= "3.8" and python_version < "4.0"
charset-normalizer==3.2.0 ; python_version >= "3.8" and python_version < "4.0"
decorator==4.4.2 ; python_version >= "3.8" and python_version < "4.0"
distro==1.8.0 ; python_version >= "3.8" and python_version < "4.0"
idna==3.4 ; python_version >= "3.8" and python_version < "4"
importlib-metadata==5.0.0 ; python_version >= "3.8" and python_version < "3.10"
idna==3.4 ; python_version >= "3.8" and python_version < "4.0"
importlib-metadata==6.8.0 ; python_version >= "3.8" and python_version < "3.10"
markdown==3.3.7 ; python_version >= "3.8" and python_version < "4.0"
orjson==3.8.0 ; python_version >= "3.8" and python_version < "4.0" and platform_machine == "x86_64"
orjson==3.9.2 ; python_version >= "3.8" and python_version < "4.0" and platform_machine == "x86_64"
protobuf==3.20.2 ; python_version >= "3.8" and python_version < "4.0"
psutil==5.9.2 ; python_version >= "3.8" and python_version < "4.0"
pysocks==1.7.1 ; python_version >= "3.8" and python_version < "4"
requests==2.28.1 ; python_version >= "3.8" and python_version < "4"
requests[socks]==2.28.1 ; python_version >= "3.8" and python_version < "4"
send2trash==1.8.0 ; python_version >= "3.8" and python_version < "4.0"
soupsieve==2.3.2.post1 ; python_version >= "3.8" and python_version < "4.0"
psutil==5.9.5 ; python_version >= "3.8" and python_version < "4.0"
pysocks==1.7.1 ; python_version >= "3.8" and python_version < "4.0"
requests==2.31.0 ; python_version >= "3.8" and python_version < "4.0"
requests[socks]==2.31.0 ; python_version >= "3.8" and python_version < "4.0"
send2trash==1.8.2 ; python_version >= "3.8" and python_version < "4.0"
soupsieve==2.4.1 ; python_version >= "3.8" and python_version < "4.0"
stringcase==1.2.0 ; python_version >= "3.8" and python_version < "4.0"
urllib3==1.26.12 ; python_version >= "3.8" and python_version < "4"
urllib3==2.0.3 ; python_version >= "3.8" and python_version < "4.0"
webob==1.8.7 ; python_version >= "3.8" and python_version < "4.0"
zipp==3.9.0 ; python_version >= "3.8" and python_version < "3.10"
zipp==3.16.1 ; python_version >= "3.8" and python_version < "3.10"