remove kontor-cli

This commit is contained in:
Thomas Peetz
2025-04-26 22:17:09 +02:00
parent 3b4e5634b1
commit 0a4f6bb0f9
30 changed files with 0 additions and 887 deletions
-108
View File
@@ -1,108 +0,0 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
coverage-report/
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# PyCharm
.idea/
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
-105
View File
@@ -1,105 +0,0 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
coverage-report/
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
-5
View File
@@ -1,5 +0,0 @@
# Kontor CLI Change History
## 0.0.1
Initial release.
-10
View File
@@ -1,10 +0,0 @@
FROM python:3.9-alpine
LABEL MAINTAINER="Thomas Peetz <thomas.peetz@thpeetz.de>"
ENV PS1="\[\e[0;33m\]|> kontor <| \[\e[1;35m\]\W\[\e[0m\] \[\e[0m\]# "
WORKDIR /src
COPY . /src
RUN pip install --no-cache-dir -r requirements.txt \
&& python setup.py install
WORKDIR /
ENTRYPOINT ["kontor"]
-1
View File
@@ -1 +0,0 @@
-5
View File
@@ -1,5 +0,0 @@
recursive-include *.py
include setup.cfg
include README.md CHANGELOG.md LICENSE.md
include *.txt
recursive-include kontor/templates *
-31
View File
@@ -1,31 +0,0 @@
.PHONY: clean virtualenv test docker dist dist-upload
clean:
find . -name '*.py[co]' -delete
virtualenv:
virtualenv --prompt '|> kontor <| ' env
env/bin/pip install -r requirements-dev.txt
env/bin/python setup.py develop
@echo
@echo "VirtualENV Setup Complete. Now run: source env/bin/activate"
@echo
test:
python -m pytest \
-v \
--cov=kontor \
--cov-report=term \
--cov-report=html:coverage-report \
tests/
docker: clean
docker build -t kontor:latest .
dist: clean
rm -rf dist/*
python setup.py sdist
python setup.py bdist_wheel
dist-upload:
twine upload dist/*
-69
View File
@@ -1,69 +0,0 @@
# Kontor CLI
## Installation
```
$ pip install -r requirements.txt
$ python setup.py install
```
## Development
This project includes a number of helpers in the `Makefile` to streamline common development tasks.
### Environment Setup
The following demonstrates setting up and working with a development environment:
```
### create a virtualenv for development
$ make virtualenv
$ source env/bin/activate
### run kontor cli application
$ kontor --help
### run pytest / coverage
$ make test
```
### Releasing to PyPi
Before releasing to PyPi, you must configure your login credentials:
**~/.pypirc**:
```
[pypi]
username = YOUR_USERNAME
password = YOUR_PASSWORD
```
Then use the included helper function via the `Makefile`:
```
$ make dist
$ make dist-upload
```
## Deployments
### Docker
Included is a basic `Dockerfile` for building and distributing `Kontor CLI`,
and can be built with the included `make` helper:
```
$ make docker
$ docker run -it kontor --help
```
@@ -1,46 +0,0 @@
### Kontor CLI Configuration Settings
---
kontor:
### Toggle application level debug (does not toggle framework debugging)
# debug: false
### Where external (third-party) plugins are loaded from
# plugin_dir: /var/lib/kontor/plugins/
### Where all plugin configurations are loaded from
# plugin_config_dir: /etc/kontor/plugins.d/
### Where external templates are loaded from
# template_dir: /var/lib/kontor/templates/
### The log handler label
# log_handler: colorlog
### The output handler label
# output_handler: jinja2
### sample foo option
# foo: bar
log.colorlog:
### Where the log file lives (no log file by default)
# file: null
### The level for which to log. One of: info, warning, error, fatal, debug
# level: info
### Whether or not to log to console
# to_console: true
### Whether or not to rotate the log file when it reaches `max_bytes`
# rotate: false
### Max size in bytes that a log file can grow until it is rotated.
# max_bytes: 512000
### The maximum number of log files to maintain when rotating
# max_files: 4
View File
@@ -1,7 +0,0 @@
from enum import Enum
class ArgumentData(Enum):
EXPORT_TYPE = 'export_type'
DB_FILE = 'db_file'
DATA_TYPE = 'data_type'
DELETE_FIRST = 'delete_first'
@@ -1,34 +0,0 @@
from cement import Controller
from cement.utils.version import get_version_banner
from ..core.version import get_version
VERSION_BANNER = """
Kontor CLI %s
%s
""" % (get_version(), get_version_banner())
class CliBase(Controller):
class Meta:
label = 'clibase'
# text displayed at the top of --help output
description = 'Kontor CLI'
# text displayed at the bottom of --help output
epilog = 'Usage: kontor command1 --foo bar'
# controller level arguments. ex: 'kontor --version'
arguments = [
### add a version banner
( [ '-v', '--version' ],
{ 'action' : 'version',
'version' : VERSION_BANNER } ),
]
def _default(self):
"""Default action if no sub-command is passed."""
self.app.args.print_help()
@@ -1,107 +0,0 @@
import mariadb
from cement import Controller, ex
from kontor.controllers import ArgumentData
class Database(Controller):
class Meta:
label = 'database'
stacked_type = 'nested'
stacked_on = 'clibase'
arguments = [
(['-m', '--dry-run'],
{'action': 'store_true',
'dest': 'dry_run'}),
]
@ex(
help='export database to given file',
arguments=[
(['-f', '--file'],
{'help': 'file to store database content',
'action': 'store',
'dest': 'db_file'})
],
)
def export(self):
data = {
ArgumentData.DB_FILE: 'data.json',
ArgumentData.EXPORT_TYPE: 'JSON',
}
if self.app.pargs.db_file is not None:
data[ArgumentData.DB_FILE] = self.app.pargs.db_file
db = self.app.kontor_db
self.app.log.info(f"export DB to {data[ArgumentData.DB_FILE]} as {data[ArgumentData.EXPORT_TYPE]}")
results = db.export_db(data[ArgumentData.EXPORT_TYPE], data[ArgumentData.DB_FILE])
for key, value in results.items():
self.app.log.info(f"{key}: {value}")
@ex(
label='import',
help='import data from file into database',
arguments=[
(['-f', '--file'],
{'help': 'file to read data',
'action': 'store',
'dest': 'db_file'}),
(['-d', '--delete-first'],
{'help': 'delete existing entries before import',
'action': 'store_true',
'dest': 'delete_first'})
],
)
def import_cmd(self):
data = {
ArgumentData.DB_FILE: 'data.json',
ArgumentData.DATA_TYPE: 'JSON',
ArgumentData.DELETE_FIRST: False,
}
if self.app.pargs.db_file is not None:
data[ArgumentData.DB_FILE] = self.app.pargs.db_file
if self.app.pargs.delete_first is not None:
data[ArgumentData.DELETE_FIRST] = self.app.pargs.delete_first
db = self.app.kontor_db
if data[ArgumentData.DELETE_FIRST]:
db.delete_entries()
db.import_db(data[ArgumentData.DB_FILE])
@ex(
help='check the db schema against MetaDataTable and MetaDataColumn'
)
def check(self):
mariadb_conn = mariadb.connect(
host=self.app.config['mariadb']['host'],
port=int(self.app.config['mariadb']['port']),
user=self.app.config['mariadb']['user'],
password=self.app.config['mariadb']['password'],
database=self.app.config['mariadb']['database']
)
table_list = []
cursor = mariadb_conn.cursor()
cursor.execute("SHOW TABLES")
for (tablename,) in cursor.fetchall():
table_list.append(tablename)
db = self.app.kontor_db
table_names = db.get_table_names()
for table in table_list:
if table not in table_names:
self.app.log.info(f"{table} is not stored in MetaDataTable")
continue
meta_data = db.get_columns(table)
field_info = self.get_table_field_info(cursor, table)
for column in field_info:
if column not in meta_data:
self.app.log.info(f"column {column} of table {table} is not in MetaDataColumn")
mariadb_conn.close()
def get_table_field_info(self, cursor, table) -> dict:
info = {}
cursor.execute(f"SELECT * FROM {table} LIMIT 1")
field_info = mariadb.fieldinfo()
for column in cursor.description:
column_name = column[0]
column_type = field_info.type(column)
column_flags = field_info.flag(column)
info[column_name] = {"type": column_type, "flags": column_flags}
return info
@@ -1,81 +0,0 @@
from cement import Controller, ex
class Media(Controller):
class Meta:
label = 'media'
stacked_type = 'nested'
stacked_on = 'clibase'
@ex(
label='update',
help='update title for mediafiles',
)
def update_title(self):
db = self.app.kontor_db
updates = db.update_titles()
self.app.log.info(f"{len(updates)} entries updated")
@ex(
label='download',
help='download all marked videos',
arguments=[
(['-d', '--dir'],
{'help': 'directory to store videos',
'action': 'store',
'dest': 'media_dir'})
],
)
def download(self):
data = {
'media_dir': '/data/media',
}
if self.app.pargs.media_dir is not None:
data['media_dir'] = self.app.pargs.media_dir
db = self.app.kontor_db
downloads = db.get_download_list()
self.app.log.info(f"found {len(downloads)} links for download")
for entry_id in downloads:
result = db.download_file(entry_id, download_dir=data['media_dir'])
if result is not None:
self.app.log.info(f"file {result} successfully downloaded")
@ex(
help='add url to database',
arguments=[
(['-u', '--url'],
{'help': 'link to downloadable video',
'action': 'store',
'dest': 'link'})
],
)
def add(self):
data = {
'link_url': None
}
if self.app.pargs.link is not None:
data['link_url'] = self.app.pargs.link
self.app.log.info(f"add url {data['link_url']} to database")
db = self.app.kontor_db
result = db.add_link(self.app.pargs.link)
self.app.log.info(result)
else:
self.app.log.info("no url was given.")
@ex(
help='check files if existing',
arguments=[
(['-d', '--dir'],
{'help': 'directory to store videos',
'action': 'store',
'dest': 'media_dir'})
],
)
def check(self):
data = {
'media_dir': '/data/media',
}
if self.app.pargs.media_dir is not None:
data['media_dir'] = self.app.pargs.media_dir
db = self.app.kontor_db
db.check_files()
-4
View File
@@ -1,4 +0,0 @@
class KontorError(Exception):
"""Generic errors."""
pass
-7
View File
@@ -1,7 +0,0 @@
from cement.utils.version import get_version as cement_get_version
VERSION = (0, 0, 1, 'alpha', 0)
def get_version(version=VERSION):
return cement_get_version(version)
-120
View File
@@ -1,120 +0,0 @@
from cement import App, TestApp, init_defaults
from cement.core.exc import CaughtSignal
from kontor_schema import Base, KontorDB
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from .core.exc import KontorError
from .controllers.base import CliBase
from .controllers.database import Database
from .controllers.media import Media
# configuration defaults
CONFIG = init_defaults('kontor', 'mariadb', 'media')
CONFIG['mariadb']['user'] = 'kontor'
CONFIG['mariadb']['password'] = 'kontor'
CONFIG['mariadb']['host'] = '127.0.0.1'
CONFIG['mariadb']['port'] = '3316'
CONFIG['mariadb']['database'] = 'kontor'
CONFIG['media']['yt-dlp'] = '/home/tpeetz/bin/yt-dlp'
CONFIG['media']['dir'] = '/data/media'
def extend_sqlalchemy(app):
app.log.debug('extending kontor application with sqlalchemy')
connect_string = ('mariadb+mariadbconnector://{}:{}@{}:{}/{}'.format(
app.config.get('mariadb', 'user'),
app.config.get('mariadb', 'password'),
app.config.get('mariadb', 'host'),
app.config.get('mariadb', 'port'),
app.config.get('mariadb', 'database')
))
# engine = create_engine(connect_string, echo=True)
engine = create_engine(connect_string)
Base.metadata.create_all(bind=engine, checkfirst=True)
__session__ = sessionmaker(bind=engine)
app.extend('engine', engine)
kontor_db = KontorDB(engine, app.log)
app.extend('kontor_db', kontor_db)
class Kontor(App):
"""Kontor CLI primary application."""
class Meta:
label = 'kontor'
# configuration defaults
config_defaults = CONFIG
# call sys.exit() on close
exit_on_close = True
# load additional framework extensions
extensions = [
'yaml',
'colorlog',
'jinja2',
]
# configuration handler
config_handler = 'yaml'
# configuration file suffix
config_file_suffix = '.yml'
# set the log handler
log_handler = 'colorlog'
# set the output handler
output_handler = 'jinja2'
hooks = [
('post_setup', extend_sqlalchemy),
]
# register handlers
handlers = [
CliBase,
Database,
Media,
]
class KontorTest(TestApp,Kontor):
"""A sub-class of Kontor that is better suited for testing."""
class Meta:
label = 'kontor'
def main():
with Kontor() as app:
try:
app.run()
except AssertionError as e:
print('AssertionError > %s' % e.args[0])
app.exit_code = 1
if app.debug is True:
import traceback
traceback.print_exc()
except KontorError as e:
print('KontorError > %s' % e.args[0])
app.exit_code = 1
if app.debug is True:
import traceback
traceback.print_exc()
except CaughtSignal as e:
# Default Cement signals are SIGINT and SIGTERM, exit 0 (non-error)
print('\n%s' % e)
app.exit_code = 0
if __name__ == '__main__':
main()
-8
View File
@@ -1,8 +0,0 @@
-r requirements.txt
pytest
pytest-cov
coverage
twine>=1.11.0
setuptools>=38.6.0
wheel>=0.31.0
-9
View File
@@ -1,9 +0,0 @@
-e ../kontor-schema
cement==3.0.12
cement[jinja2]
cement[yaml]
cement[colorlog]
mariadb
sqlalchemy
pathlib
View File
-28
View File
@@ -1,28 +0,0 @@
from setuptools import setup, find_packages
from kontor.core.version import get_version
VERSION = get_version()
f = open('README.md', 'r')
LONG_DESCRIPTION = f.read()
f.close()
setup(
name='kontor',
version=VERSION,
description='Kontor CLI',
long_description=LONG_DESCRIPTION,
long_description_content_type='text/markdown',
author='Thomas Peetz',
author_email='thomas.peetz@thpeetz.de',
url='https://gitlab.com/tpeetz/kontor/',
license='unlicensed',
packages=find_packages(exclude=['ez_setup', 'tests*']),
package_data={'kontor': ['templates/*']},
include_package_data=True,
entry_points="""
[console_scripts]
kontor = kontor.main:main
""",
)
-16
View File
@@ -1,16 +0,0 @@
"""
PyTest Fixtures.
"""
import pytest
from cement import fs
@pytest.fixture(scope="function")
def tmp(request):
"""
Create a `tmp` object that geneates a unique temporary directory, and file
for each test function that requires it.
"""
t = fs.Tmp()
yield t
t.remove()
-35
View File
@@ -1,35 +0,0 @@
from kontor.main import KontorTest
def test_kontor():
# test kontor without any subcommands or arguments
with KontorTest() as app:
app.run()
assert app.exit_code == 0
def test_kontor_debug():
# test that debug mode is functional
argv = ['--debug']
with KontorTest(argv=argv) as app:
app.run()
assert app.debug is True
def test_command1():
# test command1 without arguments
argv = ['command1']
with KontorTest(argv=argv) as app:
app.run()
data,output = app.last_rendered
assert data['foo'] == 'bar'
assert output.find('Foo => bar')
# test command1 with arguments
argv = ['command1', '--foo', 'not-bar']
with KontorTest(argv=argv) as app:
app.run()
data,output = app.last_rendered
assert data['foo'] == 'not-bar'
assert output.find('Foo => not-bar')
-16
View File
@@ -1,16 +0,0 @@
"""
PyTest Fixtures.
"""
import pytest
from cement import fs
@pytest.fixture(scope="function")
def tmp(request):
"""
Create a `tmp` object that geneates a unique temporary directory, and file
for each test function that requires it.
"""
t = fs.Tmp()
yield t
t.remove()
-35
View File
@@ -1,35 +0,0 @@
from kontor.main import KontorTest
def test_kontor():
# test kontor without any subcommands or arguments
with KontorTest() as app:
app.run()
assert app.exit_code == 0
def test_kontor_debug():
# test that debug mode is functional
argv = ['--debug']
with KontorTest(argv=argv) as app:
app.run()
assert app.debug is True
def test_command1():
# test command1 without arguments
argv = ['command1']
with KontorTest(argv=argv) as app:
app.run()
data,output = app.last_rendered
assert data['foo'] == 'bar'
assert output.find('Foo => bar')
# test command1 with arguments
argv = ['command1', '--foo', 'not-bar']
with KontorTest(argv=argv) as app:
app.run()
data,output = app.last_rendered
assert data['foo'] == 'not-bar'
assert output.find('Foo => not-bar')