Merge pull request #119 from ankicommunity/release/v2.3.0

This commit is contained in:
Vikash Kothary 2022-01-16 23:03:12 +00:00 committed by GitHub
commit a8c91b449c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1639 additions and 848 deletions

43
Makefile Normal file → Executable file
View File

@ -1,26 +1,43 @@
#/bin/make
#!/usr/bin/env make
ANKISYNCD_NAME ?= Anki Sync Server
ANKISYNCD_VERSION ?= v2.3.0
ANKISYNCD_DESCRIPTION ?= Self-hosted Anki Sync Server.
ANKI_SERVER_NAME ?= "Anki Sync Server"
ANKI_SERVER_VERSION ?= "v0.1.0"
ANKI_SERVER_DESCRIPTION ?= "Self-hosted Anki Sync Server."
ENV ?= local
-include config/.env.${ENV}
-include config/secrets/.env.*.${ENV}
export
.DEFAULT_GOAL := help
.PHONY: help #: Display list of command and exit.
help:
@awk 'BEGIN {FS = " ?#?: "; print ""${ANKI_SERVER_NAME}" "${ANKI_SERVER_VERSION}"\n"${ANKI_SERVER_DESCRIPTION}"\n\nUsage: make \033[36m<command>\033[0m\n\nCommands:"} /^.PHONY: ?[a-zA-Z_-]/ { printf " \033[36m%-10s\033[0m %s\n", $$2, $$3 }' $(MAKEFILE_LIST)
@${AWK} 'BEGIN {FS = " ?#?: "; print "${ANKISYNCD_NAME} ${ANKISYNCD_VERSION}\n${ANKISYNCD_DESCRIPTION}\n\nUsage: make \033[36m<command>\033[0m\n\nCommands:"} /^.PHONY: ?[a-zA-Z_-]/ { printf " \033[36m%-10s\033[0m %s\n", $$2, $$3 }' $(MAKEFILE_LIST)
.PHONY: docs #: Build and serve documentation.
docs: print-env
@${MKDOCS} ${MKDOCS_OPTION} -f docs/mkdocs.yml
docs:
@${MKDOCS} ${MKDOCS_CMD} -f docs/mkdocs.yml ${MKDOCS_OPTS}
.PHONY: notebooks #: Run jupyter notebooks.
notebooks:
@${JUPYTER} ${JUPYTER_OPTION}
.PHONY: tests #: Run unit tests.
tests:
@${UNITTEST} discover -s tests
.PHONY: run
run:
@${PYTHON} src/ankisyncd/__main__.py
# Run scripts using make
%:
@test -f scripts/${*}.sh
@${SHELL} scripts/${*}.sh
@if [[ -f "scripts/${*}.sh" ]]; then \
${BASH} "scripts/${*}.sh"; fi
.PHONY: init #: Download Python dependencies.
init:
@${POETRY} install
.PHONY: release #: Create new Git release and tags.
release: release-branch release-tags
.PHONY: open
open:
@${OPEN} ${ANKISYNCD_URL}

View File

@ -26,9 +26,11 @@ It supports Python 3 and Anki 2.1.
- [Installing](#installing)
- [Installing (Docker)](#installing-docker)
- [Setting up Anki](#setting-up-anki)
- [Anki 2.1](#anki-21)
- [Anki 2.0](#anki-20)
- [AnkiDroid](#ankidroid)
- [Anki 2.1](#anki-21)
- [Anki 2.0](#anki-20)
- [AnkiDroid](#ankidroid)
- [Development](#development)
- [Testing](#testing)
- [ENVVAR configuration overrides](#envvar-configuration-overrides)
- [Support for other database backends](#support-for-other-database-backends)
</details>
@ -59,34 +61,46 @@ Installing
requests to ankisyncd.
An example configuration with ankisyncd running on the same machine as Nginx
and listening on port `27702` may look like:
and listening on port `27702` may look like ([entire config template click me](https://github.com/ankicommunity/anki-sync-server/blob/develop/docs/nginx.conf)):
```
```nginx
server {
listen 27701;
server_name default;
location / {
proxy_http_version 1.0;
proxy_pass http://localhost:27702/;
}
listen 27701;
server_name default;
location / {
proxy_http_version 1.0;
proxy_pass http://127.0.0.1:27702/;
}
}
```
5. Run ankisyncd:
$ python -m ankisyncd
```
$ python -m ankisyncd
```
---
Installing (Docker)
-------------------
Follow [these instructions](https://github.com/kuklinistvan/docker-anki-sync-server#usage).
Follow [these instructions](https://github.com/ankicommunity/anki-devops-services#about-this-docker-image).
Setting up Anki
---------------
### Install addon from ankiweb (support 2.1)
1.on add-on window,click `Get Add-ons` and fill in the textbox with the code `358444159`
2.there,you get add-on `custom sync server redirector`,choose it.Then click `config` below right
3.apply your server ip address
if this step is taken,the following instructions regarding addon setting 2.1( including 2.1.28 and above) can be skipped.
### Anki 2.1.28 and above
Create a new directory in [the add-ons folder][addons21] (name it something
@ -94,7 +108,7 @@ like ankisyncd), create a file named `__init__.py` containing the code below
and put it in the `ankisyncd` directory.
import os
addr = "http://127.0.0.1:27701/" # put your server address here
os.environ["SYNC_ENDPOINT"] = addr + "sync/"
os.environ["SYNC_ENDPOINT_MEDIA"] = addr + "msync/"
@ -106,7 +120,7 @@ like ankisyncd), create a file named `__init__.py` containing the code below
and put it in the `ankisyncd` directory.
import anki.sync, anki.hooks, aqt
addr = "http://127.0.0.1:27701/" # put your server address here
anki.sync.SYNC_BASE = "%s" + addr
def resetHostNum():
@ -119,7 +133,7 @@ Create a file (name it something like ankisyncd.py) containing the code below
and put it in `~/Anki/addons`.
import anki.sync
addr = "http://127.0.0.1:27701/" # put your server address here
anki.sync.SYNC_BASE = addr
anki.sync.SYNC_MEDIA_BASE = addr + "msync/"
@ -142,6 +156,35 @@ Even though the AnkiDroid interface will request an email address, this is not
required; it will simply be the username you configured with `ankisyncctl.py
adduser`.
Development
-----------
### Testing
0. Prerequites
This project uses [GNU Make](https://www.gnu.org/software/make/) to simplify the development commands. It also uses [Poetry](https://python-poetry.org/) to manage the Python dependencies. Ensure they are installed.
1. Create a config for your local environment.
```bash
$ cp config/.env.example config/.env.local
```
See [ENVVAR configuration overrides](#envvar-configuration-overrides) for more information.
2. Download Python dependencies.
```bash
$ make init
```
3. Run unit tests.
```bash
$ make tests
```
ENVVAR configuration overrides
------------------------------

View File

@ -1,8 +1,5 @@
# .env.example (anki-sync-server)
## Make
MKDOCS=mkdocs
JUPYTER=jupyter
# file: .env.example (ankisyncd)
# cp config/.env.example config/.env.local
## Ankisyncd
ANKISYNCD_HOST=0.0.0.0
@ -12,17 +9,31 @@ ANKISYNCD_BASE_URL=/sync/
ANKISYNCD_BASE_MEDIA_URL=/msync/
ANKISYNCD_AUTH_DB_PATH=./auth.db
ANKISYNCD_SESSION_DB_PATH=./session.db
ANKISYNCD_FULL_SYNC_MANAGER
ANKISYNCD_SESSION_MANAGER
ANKISYNCD_USER_MANAGER
ANKISYNCD_COLLECTION_WRAPPER
### ANKISYNCD_FULL_SYNC_MANAGER
### ANKISYNCD_SESSION_MANAGER
### ANKISYNCD_USER_MANAGER
### ANKISYNCD_COLLECTION_WRAPPER
ANKISYNCD_URL=http://${ANKISYNCD_HOST}:${ANKISYNCD_PORT}
## Mkdocs
MKDOCS_OPTION=serve
MKDOCS_HOST=localhost
MKDOCS_PORT=5000
MKDOCS_OPTS=-a ${MKDOCS_HOST}:${MKDOCS_PORT}
MKDOCS_CMD=serve
## Jupyter
JUPYTER_OPTION=lab
### JUPYTER_CMD
### JUPYTER_NOTEBOOK_DIR
## Path
PATH:=.venv/bin/path:${PATH}
## Make
AWK=awk
BASH=bash
POETRY=poetry
PYTHON=python
MKDOCS=mkdocs
JUPYTER=jupyter
UNITTEST=python -m unittest
## Shell
SHELL=${BASH}
PATH:=.venv/bin:${PATH}

View File

@ -6,4 +6,6 @@ site_author: Anki Community
site_url: https://ankicommunity.github.io/anki-sync-server
repo_url: https://github.com/ankicommunity/anki-sync-server
docs_dir: src
site_dir: build
site_dir: build
plugins:
- mkdocs-jupyter

View File

@ -0,0 +1,29 @@
# file: nginx.example.conf
# description: Example nginx.conf to set up a reverse proxy.
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
client_max_body_size 2048m;
server {
listen 27701;
# server_name should be modified (LAN eg: 192.168.1.43 )
server_name default;
location / {
proxy_http_version 1.0;
proxy_pass http://127.0.0.1:27702/;
}
}
}

View File

@ -149,13 +149,6 @@
"source": [
"col.close()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {

1876
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -3,15 +3,17 @@ name = "anki-sync-server"
version = "2.3.0"
description = "Self-hosted Anki Sync Server."
authors = ["Vikash Kothary <kothary.vikash@gmail.com>"]
packages = [
{ include = "ankisyncd", from = "src" }
]
[tool.poetry.dependencies]
python = "^3.8"
anki = "^2.1.36"
anki = "2.1.43"
beautifulsoup4 = "^4.9.1"
requests = "^2.24.0"
markdown = "^3.2.2"
send2trash = "^1.5.0"
pyaudio = "^0.2.11"
decorator = "^4.4.2"
psutil = "^5.7.2"
distro = "^1.5.0"
@ -21,6 +23,8 @@ webob = "^1.8.6"
mkdocs = "^1.1.2"
jupyter = "^1.0.0"
jupyterlab = "^2.2.2"
webtest = "^2.0.35"
mkdocs-jupyter = "^0.19.0"
[build-system]
requires = ["poetry>=0.12"]

7
scripts/jupyter.sh Normal file
View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
# file: jupyter.sh
# description: Run Jupyter Notebooks.
[[ ! -z "${JUPYTER_CMD}" ]] || JUPYTER_CMD=lab
[[ ! -z "${JUPYTER_NOTEBOOK_DIR}" ]] || JUPYTER_NOTEBOOK_DIR=docs/src/notebooks
${JUPYTER} ${JUPYTER_CMD} --notebook-dir=${JUPYTER_NOTEBOOK_DIR}

View File

@ -1,13 +0,0 @@
#!/bin/bash
# file: lock.sh
# description: Lock dependencies and export requirements.
echo "THE FILE WAS GENERATED BY POETRY, DO NOT EDIT!\n\n" > src/requirements.txt
echo "THE FILE WAS GENERATED BY POETRY, DO NOT EDIT!\n\n" > src/requirements-dev.txt
poetry lock
poetry export --without-hashes -f requirements.txt >> src/requirements.txt
poetry export --dev --without-hashes -f requirements.txt >> src/requirements-dev.txt
echo "-e src/." >> src/requirements-dev.txt

15
scripts/poetry-export.sh Normal file
View File

@ -0,0 +1,15 @@
#!/usr/bin/env bash
# file: poetry-export.sh
# description: Lock dependencies and export them as backward-compatible requirements.txt files.
echo '[INFO] Updating poetry.lock file.'
poetry lock
echo '[INFO] Generating requirements.txt.'
echo -e "# THE FILE WAS GENERATED BY POETRY, DO NOT EDIT!\n\n" > src/requirements.txt
poetry export --without-hashes -f requirements.txt >> src/requirements.txt
echo '[INFO] Generating requirements-dev.txt.'
echo -e "# THE FILE WAS GENERATED BY POETRY, DO NOT EDIT!\n\n" > src/requirements-dev.txt
poetry export --dev --without-hashes -f requirements.txt >> src/requirements-dev.txt
echo "-e src/." >> src/requirements-dev.txt

View File

@ -1,5 +0,0 @@
#!/bin/bash
# file: print-env.sh
# description: Print env variable.
echo "${ENV}"

5
scripts/printenv.sh Normal file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
# file: printenv.sh
# description: Print all environment variables. Used for debugging.
printenv | sort

39
scripts/release-branch.sh Normal file
View File

@ -0,0 +1,39 @@
#!/usr/bin/env bash
# file: release-branch.sh
# description: Prepare a release branch from develop to master.
## Build release context as environment variables.
GIT_BRANCH=$(git symbolic-ref --short HEAD)
## TODO: get package version from pyproject.toml
CURRENT_VERSION=v2.2.0
## TODO: get new package version e.g. minor, major, bugfix
NEW_VERSION=v2.3.0
## TODO: ensure you're on the develop branch else fail
if [[ "${GIT_BRANCH}" != "develop" ]]; then
echo 'Please switch to to the develop branch to create a release branch.'
exit 0
fi
## Create release branch
git checkout -b "release/${NEW_VERSION}" develop
## TODO: bump package version in pyproject.toml
## TODO: commit changes to pyproject.toml
## TODO: generate new CHANGELOG entry from commits
## TODO: commit changes to CHANGELOG
## Return to develop
git checkout develop
if [[ -z "${CI}" ]]; then
echo "Please confirm the release branch is correct."
read -p "Press enter to continue"
echo
fi
## Push branch and tags
git push origin "release/${NEW_VERSION}"
## TODO: create PR for review

32
scripts/release-tags.sh Normal file
View File

@ -0,0 +1,32 @@
#!/usr/bin/env bash
# file: release-tags.sh
# description: Automatic consistent release tags following the SemVer convension.
# set -x
# trap read debug
## Build release context as environment variables.
GIT_BRANCH=$(git symbolic-ref --short HEAD)
## TODO: ensure this is the main branch
if [[ "${GIT_BRANCH}" != "main" ]]; then
echo 'Please switch to the main branch to create release tags.'
exit 0
fi
## TODO: get package version from pyproject.toml
CURRENT_VERSION=2.3.0
## Create GitHub Release
git tag -a ${CURRENT_VERSION} -m "v${CURRENT_VERSION}"
if [[ -z "${CI}" ]]; then
echo "Please confirm the release tag is correct."
read -p "Press enter to continue"
echo
fi
git push --tags
## TODO: publish to PyPI.

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
import os
import sys
import getpass

View File

@ -10,6 +10,7 @@ import random
import requests
import json
import os
from typing import List,Tuple
from anki.db import DB, DBError
from anki.utils import ids2str, intTime, platDesc, checksum, devMode
@ -72,9 +73,40 @@ class Syncer(object):
if 'crt' in rchg:
self.col.crt = rchg['crt']
self.prepareToChunk()
# this fn was cloned from anki module(version 2.1.36)
def basicCheck(self) -> bool:
"Basic integrity check for syncing. True if ok."
# cards without notes
if self.col.db.scalar(
"""
select 1 from cards where nid not in (select id from notes) limit 1"""
):
return False
# notes without cards or models
if self.col.db.scalar(
"""
select 1 from notes where id not in (select distinct nid from cards)
or mid not in %s limit 1"""
% ids2str(self.col.models.ids())
):
return False
# invalid ords
for m in self.col.models.all():
# ignore clozes
if m["type"] != MODEL_STD:
continue
if self.col.db.scalar(
"""
select 1 from cards where ord not in %s and nid in (
select id from notes where mid = ?) limit 1"""
% ids2str([t["ord"] for t in m["tmpls"]]),
m["id"],
):
return False
return True
def sanityCheck(self, full):
if not self.col.basicCheck():
if not self.basicCheck():
return "failed basic check"
for t in "cards", "notes", "revlog", "graves":
if self.col.db.scalar(
@ -83,7 +115,7 @@ class Syncer(object):
for g in self.col.decks.all():
if g['usn'] == -1:
return "deck had usn = -1"
for t, usn in self.col.tags.allItems():
for t, usn in self.allItems():
if usn == -1:
return "tag had usn = -1"
found = False
@ -276,10 +308,12 @@ from notes where %s""" % lim, self.maxUsn)
# Tags
##########################################################################
def allItems(self) -> List[Tuple[str, int]]:
tags=self.col.db.execute("select tag, usn from tags")
return [(tag, int(usn)) for tag,usn in tags]
def getTags(self):
tags = []
for t, usn in self.col.tags.allItems():
for t, usn in self.allItems():
if usn == -1:
self.col.tags.tags[t] = self.maxUsn
tags.append(t)
@ -331,7 +365,9 @@ from notes where %s""" % lim, self.maxUsn)
return self.col.conf
def mergeConf(self, conf):
self.col.backend.set_all_config(json.dumps(conf).encode())
for key, value in conf.items():
self.col.set_config(key, value)
# self.col.backend.set_all_config(json.dumps(conf).encode())
# Wrapper for requests that tracks upload/download progress
##########################################################################
@ -568,7 +604,7 @@ class FullSyncer(HttpSyncer):
# make sure it's ok before we try to upload
if self.col.db.scalar("pragma integrity_check") != "ok":
return False
if not self.col.basicCheck():
if not self.basicCheck():
return False
# apply some adjustments, then upload
self.col.beforeUpload()

View File

@ -121,6 +121,10 @@ class SyncCollectionHandler(Syncer):
self.minUsn = minUsn
self.lnewer = not lnewer
lgraves = self.removed()
# convert grave:None to {'cards': [], 'notes': [], 'decks': []}
# because req.POST['data'] returned value of grave is None
if graves==None:
graves={'cards': [], 'notes': [], 'decks': []}
self.remove(graves)
return lgraves
@ -178,7 +182,7 @@ class SyncCollectionHandler(Syncer):
]
def getTags(self):
return [t for t, usn in self.col.tags.allItems()
return [t for t, usn in self.allItems()
if usn >= self.minUsn]
class SyncMediaHandler:

View File

@ -1,91 +1,113 @@
THE FILE WAS GENERATED BY POETRY, DO NOT EDIT!
# THE FILE WAS GENERATED BY POETRY, DO NOT EDIT!
anki==2.1.37; python_version >= "3.8"
appnope==0.1.2; platform_system == "Darwin" and python_version >= "3.7" and sys_platform == "darwin"
argon2-cffi==20.1.0; python_version >= "3.5"
async-generator==1.10; python_version >= "3.6"
attrs==20.3.0; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5"
backcall==0.2.0; python_version >= "3.7"
beautifulsoup4==4.9.3
bleach==3.2.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
certifi==2020.12.5; python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.8"
cffi==1.14.4; implementation_name === "pypy" and python_version >= "3.5"
chardet==4.0.0; python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.8"
click==7.1.2; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5"
colorama==0.4.4; python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.7" and python_full_version >= "3.5.0"
anki==2.1.43; python_version >= "3.8"
appnope==0.1.2; platform_system == "Darwin" and python_version >= "3.8" and sys_platform == "darwin"
argon2-cffi-bindings==21.2.0; python_version >= "3.6"
argon2-cffi==21.3.0; python_version >= "3.6"
asttokens==2.0.5; python_version >= "3.8"
attrs==21.4.0; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.7"
backcall==0.2.0; python_version >= "3.8"
beautifulsoup4==4.10.0; python_full_version > "3.0.0"
black==21.12b0; python_full_version >= "3.6.2" and python_version >= "3.8"
bleach==4.1.0; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.7"
certifi==2021.10.8; python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.8"
cffi==1.15.0; implementation_name == "pypy" and python_version >= "3.7" and python_full_version >= "3.6.1"
charset-normalizer==2.0.10; python_full_version >= "3.6.0" and python_version >= "3.8"
click==8.0.3; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.8"
colorama==0.4.4; python_version >= "3.8" and python_full_version < "3.0.0" and platform_system == "Windows" and sys_platform == "win32" or platform_system == "Windows" and python_version >= "3.8" and python_full_version >= "3.5.0" and sys_platform == "win32"
debugpy==1.5.1; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7"
decorator==4.4.2; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.2.0")
defusedxml==0.6.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
distro==1.5.0
entrypoints==0.3; python_version >= "3.6"
future==0.18.2; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.5"
idna==2.10; python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.8"
ipykernel==5.4.2; python_version >= "3.6"
ipython-genutils==0.2.0; python_version >= "3.7"
ipython==7.19.0; python_version >= "3.7"
ipywidgets==7.5.1
jedi==0.17.2; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7"
jinja2==2.11.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
joblib==1.0.0; python_version >= "3.6"
json5==0.9.5; python_version >= "3.5"
jsonschema==3.2.0; python_version >= "3.5"
jupyter-client==6.1.7; python_version >= "3.6"
jupyter-console==6.2.0; python_version >= "3.6"
jupyter-core==4.7.0; python_version >= "3.6"
defusedxml==0.7.1; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.7"
distro==1.6.0
entrypoints==0.3; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.7"
executing==0.8.2; python_version >= "3.8"
ghp-import==2.0.2; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.6"
idna==3.3; python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.8"
importlib-metadata==4.10.0; python_version < "3.10" and python_version >= "3.7" and python_full_version >= "3.7.1"
importlib-resources==5.4.0; python_version < "3.9" and python_version >= "3.7" and python_full_version >= "3.7.1"
ipykernel==6.7.0; python_version >= "3.7"
ipython-genutils==0.2.0; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.7"
ipython==8.0.0; python_version >= "3.8"
ipywidgets==7.6.5
jedi==0.18.1; python_version >= "3.8"
jinja2==3.0.3; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.7"
json5==0.9.6; python_version >= "3.5"
jsonschema==4.4.0; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.7"
jupyter-client==7.1.1; python_full_version >= "3.7.1" and python_version >= "3.7" and python_version < "4"
jupyter-console==6.4.0; python_version >= "3.6"
jupyter-core==4.9.1; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.7"
jupyter==1.0.0
jupyterlab-pygments==0.1.2; python_version >= "3.6"
jupyterlab-pygments==0.1.2; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.7"
jupyterlab-server==1.2.0; python_version >= "3.5"
jupyterlab==2.2.9; python_version >= "3.5"
livereload==2.6.3; python_version >= "3.5"
lunr==0.5.8; python_version >= "3.5"
markdown==3.3.3; python_version >= "3.6"
markupsafe==1.1.1; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5"
mistune==0.8.4; python_version >= "3.6"
mkdocs==1.1.2; python_version >= "3.5"
nbclient==0.5.1; python_version >= "3.6"
nbconvert==6.0.7; python_version >= "3.6"
nbformat==5.0.8; python_version >= "3.6"
nest-asyncio==1.4.3; python_version >= "3.6"
nltk==3.5; python_version >= "3.5"
notebook==6.1.5; python_version >= "3.5"
orjson==3.4.6; python_version >= "3.8"
packaging==20.8; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
pandocfilters==1.4.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
parso==0.7.1; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7"
pexpect==4.8.0; sys_platform != "win32" and python_version >= "3.7"
pickleshare==0.7.5; python_version >= "3.7"
prometheus-client==0.9.0; python_version >= "3.5"
prompt-toolkit==3.0.8; python_full_version >= "3.6.1" and python_version >= "3.7"
protobuf==3.14.0; python_version >= "3.8"
psutil==5.8.0; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0")
ptyprocess==0.6.0; os_name != "nt" and python_version >= "3.7" and sys_platform != "win32"
py==1.10.0; python_version >= "3.5" and python_full_version < "3.0.0" and implementation_name === "pypy" or implementation_name === "pypy" and python_version >= "3.5" and python_full_version >= "3.4.0"
pyaudio==0.2.11
pycparser==2.20; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5"
pygments==2.7.3; python_version >= "3.7"
pyparsing==2.4.7; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
pyrsistent==0.17.3; python_version >= "3.5"
pysocks==1.7.1; python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.8"
python-dateutil==2.8.1; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.5"
pywin32==300; sys_platform == "win32" and python_version >= "3.6"
pywinpty==0.5.7; os_name == "nt" and python_version >= "3.6"
pyyaml==5.3.1; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5"
pyzmq==20.0.0; python_version >= "3.6"
qtconsole==5.0.1; python_version >= "3.6"
qtpy==1.9.0; python_version >= "3.6"
regex==2020.11.13; python_version >= "3.5"
requests==2.25.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0")
send2trash==1.5.0
six==1.15.0; python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.8"
soupsieve==2.1; python_version >= "3.8"
terminado==0.9.1; python_version >= "3.6"
testpath==0.4.4; python_version >= "3.6"
tornado==6.1; python_version >= "3.6"
tqdm==4.54.1; python_version >= "3.5" and python_full_version < "3.0.0" or python_version >= "3.5" and python_full_version >= "3.4.0"
traitlets==5.0.5; python_version >= "3.7"
urllib3==1.26.2; python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4" and python_version >= "3.8"
wcwidth==0.2.5; python_full_version >= "3.6.1" and python_version >= "3.6"
webencodings==0.5.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
webob==1.8.6; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0")
widgetsnbextension==3.5.1
jupyterlab-widgets==1.0.2; python_version >= "3.6"
jupyterlab==2.3.2; python_version >= "3.5"
jupytext==1.13.6; python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.7.1"
markdown-it-py==1.1.0; python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.7.1"
markdown==3.3.6; python_version >= "3.6"
markupsafe==2.0.1; python_version >= "3.6"
matplotlib-inline==0.1.3; python_version >= "3.8"
mdit-py-plugins==0.3.0; python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.7.1"
mergedeep==1.3.4; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.6"
mistune==0.8.4; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.7"
mkdocs-jupyter==0.19.0; python_full_version >= "3.7.1" and python_version < "4"
mkdocs-material-extensions==1.0.3; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.6"
mkdocs-material==8.1.7; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.6"
mkdocs==1.2.3; python_version >= "3.6"
mypy-extensions==0.4.3; python_full_version >= "3.6.2" and python_version >= "3.8"
nbclient==0.5.10; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.7"
nbconvert==6.4.0; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.7"
nbformat==5.1.3; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.7"
nest-asyncio==1.5.4; python_full_version >= "3.7.1" and python_version >= "3.7" and python_version < "4"
notebook==6.4.7; python_version >= "3.6"
orjson==3.6.5; platform_machine == "x86_64" and python_version >= "3.8"
packaging==21.3; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.7"
pandocfilters==1.5.0; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.7"
parso==0.8.3; python_version >= "3.8"
pathspec==0.9.0; python_full_version >= "3.6.2" and python_version >= "3.8"
pexpect==4.8.0; sys_platform != "win32" and python_version >= "3.8"
pickleshare==0.7.5; python_version >= "3.8"
platformdirs==2.4.1; python_full_version >= "3.6.2" and python_version >= "3.8"
prometheus-client==0.12.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
prompt-toolkit==3.0.24; python_full_version >= "3.6.2" and python_version >= "3.8"
protobuf==3.19.3; python_version >= "3.8"
psutil==5.9.0; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0")
ptyprocess==0.7.0; os_name != "nt" and python_version >= "3.8" and sys_platform != "win32"
pure-eval==0.2.1; python_version >= "3.8"
py==1.11.0; implementation_name == "pypy" and python_version >= "3.7" and python_full_version >= "3.6.1"
pycparser==2.21; implementation_name == "pypy" and python_version >= "3.7" and python_full_version >= "3.6.1"
pygments==2.11.2; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.8"
pymdown-extensions==9.1; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.6"
pyparsing==3.0.6; python_version >= "3.6"
pyrsistent==0.18.1; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.7"
pysocks==1.7.1; python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.8"
python-dateutil==2.8.2; python_full_version >= "3.6.1" and python_version >= "3.7"
pywin32==303; sys_platform == "win32" and platform_python_implementation != "PyPy" and python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.7"
pywinpty==1.1.6; os_name == "nt" and python_version >= "3.6"
pyyaml-env-tag==0.1; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.6"
pyyaml==6.0; python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.7.1"
pyzmq==22.3.0; python_full_version >= "3.6.1" and python_version >= "3.7"
qtconsole==5.2.2; python_version >= "3.6"
qtpy==2.0.0; python_version >= "3.6"
requests==2.27.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0")
send2trash==1.8.0
six==1.16.0; python_full_version >= "3.7.1" and python_version >= "3.7" and python_version < "4" and (python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.8")
soupsieve==2.3.1; python_full_version > "3.0.0" and python_version >= "3.8"
stack-data==0.1.4; python_version >= "3.8"
terminado==0.12.1; python_version >= "3.6"
testpath==0.5.0; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.7"
toml==0.10.2; python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.7.1"
tomli==1.2.3; python_full_version >= "3.6.2" and python_version >= "3.8"
tornado==6.1; python_full_version >= "3.6.1" and python_version >= "3.7"
traitlets==5.1.1; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.8"
typing-extensions==4.0.1
urllib3==1.26.8; python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version < "4" and python_version >= "3.8"
waitress==2.0.0; python_full_version >= "3.6.0"
watchdog==2.1.6; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.6"
wcwidth==0.2.5; python_full_version >= "3.6.2" and python_version >= "3.8"
webencodings==0.5.1; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.7"
webob==1.8.7; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0")
webtest==2.0.35
widgetsnbextension==3.5.2
zipp==3.7.0; python_version < "3.9" and python_version >= "3.7" and python_full_version >= "3.7.1"
-e src/.

View File

@ -1,22 +1,22 @@
THE FILE WAS GENERATED BY POETRY, DO NOT EDIT!
# THE FILE WAS GENERATED BY POETRY, DO NOT EDIT!
anki==2.1.37; python_version >= "3.8"
beautifulsoup4==4.9.3
certifi==2020.12.5; python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.8"
chardet==4.0.0; python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.8"
anki==2.1.43; python_version >= "3.8"
beautifulsoup4==4.10.0; python_full_version > "3.0.0"
certifi==2021.10.8; python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.8"
charset-normalizer==2.0.10; python_full_version >= "3.6.0" and python_version >= "3.8"
decorator==4.4.2; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.2.0")
distro==1.5.0
idna==2.10; python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.8"
markdown==3.3.3; python_version >= "3.6"
orjson==3.4.6; python_version >= "3.8"
protobuf==3.14.0; python_version >= "3.8"
psutil==5.8.0; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0")
pyaudio==0.2.11
pysocks==1.7.1; python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.8"
requests==2.25.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0")
send2trash==1.5.0
six==1.15.0; python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.8"
soupsieve==2.1; python_version >= "3.8"
urllib3==1.26.2; python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4" and python_version >= "3.8"
webob==1.8.6; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0")
distro==1.6.0
idna==3.3; python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.8"
importlib-metadata==4.10.0; python_version < "3.10" and python_version >= "3.7"
markdown==3.3.6; python_version >= "3.6"
orjson==3.6.5; platform_machine == "x86_64" and python_version >= "3.8"
protobuf==3.19.3; python_version >= "3.8"
psutil==5.9.0; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0")
pysocks==1.7.1; python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.8"
requests==2.27.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0")
send2trash==1.8.0
soupsieve==2.3.1; python_full_version > "3.0.0" and python_version >= "3.8"
urllib3==1.26.8; python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version < "4" and python_version >= "3.8"
webob==1.8.7; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0")
zipp==3.7.0; python_version < "3.10" and python_version >= "3.7"