Skip to content

Commit

Permalink
Merge branch 'issue_413_plugins_and_mandarin_parser' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
jzohrab committed May 18, 2024
2 parents 7fe027b + 6fa7127 commit c6cd510
Show file tree
Hide file tree
Showing 24 changed files with 521 additions and 92 deletions.
83 changes: 79 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
push:
# A branch github-ci-updates can be created and used for ci
# experiments and tweaks.
branches: [ "develop", "master", "github-ci", "windows" ]
branches: [ "develop", "master", "github-ci", "windows", "pr_413_continued_plugins" ]
pull_request:
branches: [ "develop", "master" ]

Expand Down Expand Up @@ -142,6 +142,71 @@ jobs:
run: inv accept -s -k disabled_data_is_hidden


# Run all plugin tests.
#
# For each plugin:
# - install Lute requirements
# - install plugin reqs
# - run tests.
#
# The Lute requirements are installed first b/c the plugins may come
# with their own conflicting requirements. Doing a full req install
# will (hopefully) uncover conflicts.
plugins:
runs-on: ubuntu-latest
timeout-minutes: 30

strategy:
matrix:
python_version: [ '3.8', '3.9', '3.10', '3.11' ]

steps:

- uses: actions/checkout@v4
with:
submodules: true

- uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python_version }}
cache: 'pip' # caching pip dependencies

# Plugins likely won't need this config file, but just in case ...
- name: Setup config
run: |
mkdir ${{ github.workspace }}/data
echo "ENV: dev" > ${{ github.workspace }}/lute/config/config.yml
echo "DATAPATH: ${{ github.workspace }}/data" >> ${{ github.workspace }}/lute/config/config.yml
echo "DBNAME: test_lute.db" >> ${{ github.workspace }}/lute/config/config.yml
ls ${{ github.workspace }}
cat ${{ github.workspace }}/lute/config/config.yml
- name: test all plugins
run: |
for plugin in $(ls plugins); do
# Lute reqs, such as pytest.
pip install -r requirements.txt
# Lute itself, via toml, so that it can be found
# by each plugin's own "pip install ."
pip install .
pushd plugins/$plugin
pip install .
# Note for future: some plugins may have extra reqs not covered by pip
# (e.g. mecab uses apt-get and exports etc). Idea for future: plugin
# could have a .github folder as well with additional setup scripts.
pytest tests
# pip uninstall $plugin -y
# NOTE: Not bothering to do an uninstall!
# if multiple plugins have different/clashing version requirements,
# perhaps it is best to run into problems in ci.
# This may ultimately come back to haunt me, but it will do for now.
popd
done
code-quality:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -245,14 +310,24 @@ jobs:
# - name: Playwright smoke test
# run: inv playwright || exit /b

# Now having problems with tests not working on windows ...
# getting failure message:
# javascript error: clear_datatable_state is not defined
#
# The above message is called from lute_test_client to clear book
# datatables state. This _used_ to work (e.g. in v3.3.0), and
# I can't track it down at the moment!!!!!!
#
# TODO ci: RESTORE AT LEAST ONE SANITY CHECK TEST ON WINDOWS.
#
# Run specific sanity check.
# Old tests no longer run -- datatables may have timing issues on Windows tests,
# tests were far too flaky.
# inv accept -s -k test_unsupported_language_not_shown || exit /b
# inv accept -s -k import_a_valid_term_file || exit /b
- name: Smoke tests
run: |
inv accept -s -k test_updating_term_status_updates_the_reading_frame || exit /b
# - name: Smoke tests
# run: |
# inv accept -s -k test_updating_term_status_updates_the_reading_frame || exit /b

- name: Remove config to force using prod config
run: del ${{ github.workspace }}\lute\config\config.yml
Expand Down
33 changes: 15 additions & 18 deletions devstart.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,25 @@ def start(port):
"""
Start the dev server with reloads on port.
"""
config_file = AppConfig.default_config_filename()
ac = AppConfig(config_file)

# https://stackoverflow.com/questions/25504149/
# why-does-running-the-flask-dev-server-run-itself-twice
if os.environ.get("WERKZEUG_RUN_MAIN") == "true":
# Reloading.
pass
else:
# First run
msg = f"""
db name: {ac.dbname}
data: {ac.datapath}

Running at:
def dev_print(s):
"Print info on first load only."
if os.environ.get("WERKZEUG_RUN_MAIN") == "true":
# https://stackoverflow.com/questions/25504149/
# why-does-running-the-flask-dev-server-run-itself-twice
# Reloading, do nothing.
return
print(s)

http://localhost:{port}
config_file = AppConfig.default_config_filename()
dev_print("")
app = create_app(config_file, output_func=dev_print)

"""
print(msg)
ac = AppConfig(config_file)
dev_print(f"\ndb name: {ac.dbname}")
dev_print(f"data: {ac.datapath}")
dev_print(f"Running at: http://localhost:{port}\n")

app = create_app(config_file, output_func=print)
app.run(debug=True, port=port)


Expand Down
2 changes: 1 addition & 1 deletion lute/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@
Flit pulls into the pyproject.toml using "dynamic".
"""

__version__ = "3.3.3"
__version__ = "3.4.0dev2"
14 changes: 14 additions & 0 deletions lute/app_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import lute.db.demo
import lute.utils.formutils

from lute.parse.registry import init_parser_plugins, supported_parsers

from lute.models.book import Book
from lute.models.language import Language
from lute.models.setting import BackupSettings, UserSetting
Expand Down Expand Up @@ -339,6 +341,11 @@ def create_app(
- extra_config: dict, e.g. pass { 'TESTING': True } during unit tests.
"""

def null_print(s): # pylint: disable=unused-argument
pass

outfunc = output_func or null_print

if app_config_path is None:
if os.path.exists("config.yml"):
app_config_path = "config.yml"
Expand All @@ -351,6 +358,13 @@ def create_app(

if extra_config is None:
extra_config = {}
outfunc("Initializing app.")
app = _create_app(app_config, extra_config)

outfunc("Initializing parsers from plugins ...")
init_parser_plugins()
outfunc("Enabled parsers:")
for _, v in supported_parsers():
outfunc(f" * {v}")

return app
2 changes: 1 addition & 1 deletion lute/db/language_defs
2 changes: 1 addition & 1 deletion lute/db/schema/baseline.sql
Original file line number Diff line number Diff line change
Expand Up @@ -388,4 +388,4 @@ BEGIN
WHERE WoID = NEW.WoID;
END
;
COMMIT;
COMMIT;
2 changes: 1 addition & 1 deletion lute/dev_api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def dummy_language_dict(langname, term):
@bp.route("/disable_parser/<string:parsername>/<string:renameto>", methods=["GET"])
def disable_parser(parsername, renameto):
"Hack: rename a parser in the registry so that languages can't find it."
p = lute.parse.registry.parsers
p = lute.parse.registry.__LUTE_PARSERS__
if parsername in p:
p[renameto] = p.pop(parsername)
langs = db.session.query(Language).all()
Expand Down
19 changes: 9 additions & 10 deletions lute/language/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,25 @@
from lute.db import db


def get_defs():
"Return language definitions."
def get_supported_defs():
"Return supported language definitions."
ret = []
def_glob = os.path.join(_language_defs_path(), "**", "definition.yaml")
for f in glob(def_glob):
entry = {}
d = {}
lang = None
with open(f, "r", encoding="utf-8") as df:
d = yaml.safe_load(df)
lang = Language.from_dict(d)
entry["language"] = lang
entry["books"] = _get_books(f, lang.name)
ret.append(entry)
lang = Language.from_dict(d)
if lang.is_supported:
entry = {"language": lang, "books": _get_books(f, lang.name)}
ret.append(entry)
ret.sort(key=lambda x: x["language"].name)
return ret


def predefined_languages():
"Languages defined in yaml files."
return [d["language"] for d in get_defs()]
return [d["language"] for d in get_supported_defs()]


def _get_books(lang_definition_filename, lang_name):
Expand All @@ -52,7 +51,7 @@ def _get_books(lang_definition_filename, lang_name):

def get_language_def(lang_name):
"Get a lang def and its stories."
defs = get_defs()
defs = get_supported_defs()
ret = [d for d in defs if d["language"].name == lang_name]
if len(ret) == 0:
raise RuntimeError(f"Missing language def name {lang_name}")
Expand Down
58 changes: 29 additions & 29 deletions lute/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import argparse
import shutil
import logging
import textwrap
from waitress import serve
from lute.app_factory import create_app
from lute.config.app_config import AppConfig
Expand All @@ -26,7 +27,7 @@ def _print(s):
"""
if isinstance(s, str):
s = s.split("\n")
msg = "\n".join([" " + lin.strip() for lin in s])
msg = "\n".join(f" {lin}" for lin in s)
print(msg, flush=True)


Expand All @@ -41,61 +42,60 @@ def _create_prod_config_if_needed():
_print(["", "Using new production config.", ""])


def _create_app(config_file_path=None):
def _get_config_file_path(config_file_path=None):
"""
Configure and init the app.
Get final config file to use.
Uses config file if set (throws if doesn't exist);
otherwise, uses the prod config, creating a prod config
if necessary.
"""
_print(["", "Starting Lute:"])

use_config = config_file_path
if config_file_path is not None:
_print([f"Using specified config: {config_file_path}"])
_print(f"Using specified config: {config_file_path}")
elif os.path.exists("config.yml"):
_print(["Using config.yml found in root"])
config_file_path = "config.yml"
_print("Using config.yml found in root")
use_config = "config.yml"
else:
_print(["Using default config"])
_print("Using default config")
_create_prod_config_if_needed()
config_file_path = AppConfig.default_config_filename()
use_config = AppConfig.default_config_filename()

ac = AppConfig(use_config)
_print(f" data path: {ac.datapath}")
_print(f" database: {ac.dbfilename}")
if ac.is_docker:
_print(" (Note these are container paths, not host paths.)")
_print("")

app_config = AppConfig(config_file_path)
return use_config

_print(["", "Initializing app."])

def _start(args):
"Configure and start the app."
_print("\nStarting Lute.\n")

config_file_path = _get_config_file_path(args.config)
app = create_app(config_file_path, output_func=_print)
_print(f"data path: {app_config.datapath}")
_print(f"database: {app_config.dbfilename}")
if app_config.is_docker:
_print("(Note these are container paths, not host paths.)")

close_msg = """
When you're finished reading, stop this process
with Ctrl-C or your system equivalent.
"""
if app_config.is_docker:
if app.env_config.is_docker:
close_msg = """
When you're finished reading, stop this container
with Ctrl-C, docker compose stop, or docker stop <containerid>
as appropriate.
"""
_print(close_msg)

return app
_print(textwrap.dedent(close_msg))


def _start(args):
"Configure and start the app."
app = _create_app(args.config)

_print(
f"""
Lute is running. Open a web browser, and go to:
msg = f"""Lute is running. Open a web browser, and go to:
http://localhost:{args.port}
"""
)
_print(textwrap.dedent(msg))

serve(app, host="0.0.0.0", port=args.port)


Expand Down
Loading

0 comments on commit c6cd510

Please sign in to comment.