Compare commits
10 Commits
e94bb778c0
...
35cf66d403
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35cf66d403 | ||
|
|
86ba071b6c | ||
|
|
7532c960f2 | ||
|
|
a9a2bb5646 | ||
|
|
f2c4316e29 | ||
|
|
bc6f8e8f5c | ||
|
|
4041d28b03 | ||
|
|
4c4ce64025 | ||
|
|
82a3e729f1 | ||
|
|
97a740417b |
44
.github/workflows/docker.yml
vendored
Normal file
44
.github/workflows/docker.yml
vendored
Normal 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
19
Dockerfile
Normal 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"]
|
||||
17
README.md
17
README.md
@ -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
4526
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
||||
1
src/ankisyncd/exceptions.py
Normal file
1
src/ankisyncd/exceptions.py
Normal file
@ -0,0 +1 @@
|
||||
from webob.exc import HTTPBadRequest as BadRequestException
|
||||
@ -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()
|
||||
|
||||
40
src/ankisyncd/sessions/__init__.py
Normal file
40
src/ankisyncd/sessions/__init__.py
Normal 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()
|
||||
19
src/ankisyncd/sessions/simple_manager.py
Normal file
19
src/ankisyncd/sessions/simple_manager.py
Normal 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]
|
||||
@ -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()
|
||||
38
src/ankisyncd/users/__init__.py
Normal file
38
src/ankisyncd/users/__init__.py
Normal 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()
|
||||
38
src/ankisyncd/users/simple_manager.py
Normal file
38
src/ankisyncd/users/simple_manager.py
Normal 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)
|
||||
@ -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()
|
||||
@ -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():
|
||||
|
||||
@ -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/.
|
||||
|
||||
@ -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"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user