Compare commits

...

2 Commits

Author SHA1 Message Date
affa15e191 Get JSON query string from config 2022-03-14 06:58:45 +01:00
6686e91236 Add example json file for query string 2022-03-14 00:59:40 +01:00
8 changed files with 126 additions and 78 deletions

5
.gitignore vendored
View File

@ -235,4 +235,7 @@ fabric.properties
.idea/deployment.xml .idea/deployment.xml
.idea/misc.xml .idea/misc.xml
.idea/remote-mappings.xml .idea/remote-mappings.xml
.idea/mvw-dl.iml .idea/*.iml
# ---> Project-specific
data

8
.idea/.gitignore generated vendored
View File

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -1,26 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="1">
<item index="0" class="java.lang.String" itemvalue="google" />
</list>
</value>
</option>
</inspection_tool>
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredIdentifiers">
<list>
<option value="str.decode" />
</list>
</option>
</inspection_tool>
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>

View File

@ -7,6 +7,7 @@ state_file_retention = 50
state_file_name_prefix = state- state_file_name_prefix = state-
state_file_name_suffix = .log state_file_name_suffix = .log
mvw_endpoint = http://localhost:8000/api/query mvw_endpoint = http://localhost:8000/api/query
title_dedup_winner = first
[maus] [maus]
min_duration = 1200 min_duration = 1200
@ -15,8 +16,10 @@ query = @maus-query.json
# query = {"queries":[{"fields":["topic"],"query":"die sendung mit der maus"},{"fields":["channel"],"query":"ARD"}],"sortBy":"timestamp","sortOrder":"desc","future":false,"offset":0,"size":50} # query = {"queries":[{"fields":["topic"],"query":"die sendung mit der maus"},{"fields":["channel"],"query":"ARD"}],"sortBy":"timestamp","sortOrder":"desc","future":false,"offset":0,"size":50}
# state_file_name = maus # state_file_name = maus
# tmp_base_dir = %(tmp_base_dir)s/maus # tmp_base_dir = %(tmp_base_dir)s/maus
dl_dir = ~/maus
[test] [test]
min_duration = 100 min_duration = 100
max_duration = 200 max_duration = 200
query = {"queries":[{"fields":["topic"],"query":"die sendung mit der maus"},{"fields":["channel"],"query":"ARD"}],"sortBy":"timestamp","sortOrder":"desc","future":false,"offset":0,"size":50} query = {"queries":[{"fields":["topic"],"query":"die sendung mit der maus"},{"fields":["channel"],"query":"ARD"}],"sortBy":"timestamp","sortOrder":"desc","future":false,"offset":0,"size":50}
dl_dir = test

128
main.py
View File

@ -1,8 +1,20 @@
import configparser import configparser
import json
import logging import logging
import os import os
import sys import sys
import requests
from rich.logging import RichHandler from rich.logging import RichHandler
from rich.traceback import install
from rich.console import Console
from rich.table import Table
import typing as t
console = Console()
# We use Python 3.5+ type hints; we're working with JSON objects; we're following a 2016 suggestion from
# Python's "typing" GitHub issue tracker on how to create a "JSONType" hint since such a thing does not yet
# officially exist: https://github.com/python/typing/issues/182#issuecomment-186684288
JSONType = t.Union[str, int, float, bool, None, t.Dict[str, t.Any], t.List[t.Any]]
# Exit codes # Exit codes
@ -13,9 +25,27 @@ from rich.logging import RichHandler
class CONST(object): class CONST(object):
__slots__ = () __slots__ = ()
LOG_FORMAT = "%(message)s" LOG_FORMAT = "%(message)s"
CFG_THIS_FILE_DIRNAME = os.path.dirname(__file__)
CFG_DEFAULT_FILENAME = "config.ini" CFG_DEFAULT_FILENAME = "config.ini"
CFG_DEFAULT_ABS_PATH = os.path.join(os.getcwd(), CFG_DEFAULT_FILENAME) CFG_DEFAULT_ABS_PATH = os.path.join(CFG_THIS_FILE_DIRNAME, CFG_DEFAULT_FILENAME)
CFG_MANDATORY = "query" CFG_KNOWN_DEFAULTS = [
{"key": "self_name", "value": "mvw-dl"},
{"key": "tmp_base_dir", "value": os.path.join(CFG_THIS_FILE_DIRNAME, "data/tmp/%(self_name)s")},
{"key": "state_base_dir", "value": os.path.join(CFG_THIS_FILE_DIRNAME, "data/var/lib/%(self_name)s")},
{"key": "state_files_dir", "value": "%(state_base_dir)s/state"},
{"key": "state_file_retention", "value": "50"},
{"key": "state_file_name_prefix", "value": "state-"},
{"key": "state_file_name_suffix", "value": ".log"},
{"key": "mvw_endpoint", "value": "http://localhost:8000/api/query"},
{"key": "title_dedup_winner", "value": "first"}
]
CFG_KNOWN_SECTION = [
{"key": "min_duration", "is_mandatory": False},
{"key": "max_duration", "is_mandatory": False},
{"key": "query", "is_mandatory": True},
{"key": "dl_dir", "is_mandatory": True}
]
CFG_MANDATORY = [section_cfg["key"] for section_cfg in CFG_KNOWN_SECTION if section_cfg["is_mandatory"]]
CONST = CONST() CONST = CONST()
@ -30,6 +60,7 @@ logging.basicConfig(
) )
log = logging.getLogger("rich") log = logging.getLogger("rich")
log.setLevel(logging.DEBUG) log.setLevel(logging.DEBUG)
install(show_locals=True)
class ConfigParser(configparser.ConfigParser): class ConfigParser(configparser.ConfigParser):
@ -49,16 +80,7 @@ class ConfigParser(configparser.ConfigParser):
ini_defaults = [] ini_defaults = []
internal_defaults = { internal_defaults = {default["key"]: default["value"] for default in CONST.CFG_KNOWN_DEFAULTS}
"self_name": "mvw-dl",
"tmp_base_dir": "/tmp/%(self_name)s",
"state_base_dir": "/var/lib/%(self_name)s",
"state_files_dir": "%(state_base_dir)s/state",
"state_file_retention": "50",
"state_file_name_prefix": "state-",
"state_file_name_suffix": ".log",
"mvw_endpoint": "http://localhost:8000/api/query"
}
config = ConfigParser(defaults=internal_defaults) config = ConfigParser(defaults=internal_defaults)
config.read(CONST.CFG_DEFAULT_FILENAME) config.read(CONST.CFG_DEFAULT_FILENAME)
@ -89,7 +111,7 @@ def validate_default_section(config_obj: configparser.ConfigParser()) -> None:
def config_has_valid_section(config_obj: configparser.ConfigParser()) -> bool: def config_has_valid_section(config_obj: configparser.ConfigParser()) -> bool:
has_valid_section = False has_valid_section = False
for config_obj_section in config_obj.sections(): for config_obj_section in config_obj.sections():
if CONST.CFG_MANDATORY in config_obj.options(config_obj_section): if set(CONST.CFG_MANDATORY).issubset(config_obj.options(config_obj_section)):
has_valid_section = True has_valid_section = True
break break
return has_valid_section return has_valid_section
@ -104,46 +126,68 @@ def is_same_as_default(config_kv_pair: dict) -> bool:
def validate_config_sections(config_obj: configparser.ConfigParser()) -> None: def validate_config_sections(config_obj: configparser.ConfigParser()) -> None:
for section in config_obj.sections(): for this_section in config_obj.sections():
log.debug(print_section_header(section)) log.debug(print_section_header(this_section))
if CONST.CFG_MANDATORY not in config_obj.options(section, no_defaults=True): if not set(CONST.CFG_MANDATORY).issubset(config_obj.options(this_section, no_defaults=True)):
log.warning(f"Config section '[{section}]' does not have mandatory option '{CONST.CFG_MANDATORY}' set, " log.warning(f"Config section '[{this_section}]' does not have all mandatory options "
f"skipping section ...") f"{CONST.CFG_MANDATORY} set, skipping section ...")
config_obj.remove_section(section) config_obj.remove_section(this_section)
else: else:
for key in config_obj.options(section, no_defaults=True): for key in config_obj.options(this_section, no_defaults=True):
kv_prefix = "~" kv_prefix = "~"
if is_default(key): if is_default(key):
kv_prefix = "+" kv_prefix = "+"
if is_same_as_default({key: config_obj[section][key]}): if is_same_as_default({key: config_obj[this_section][key]}):
kv_prefix = "=" kv_prefix = "="
log.debug(f"{kv_prefix} {key} = {config_obj[section][key]}") log.debug(f"{kv_prefix} {key} = {config_obj[this_section][key]}")
def query_string_from_file(filename: str) -> str:
with open(filename, "r") as jsonfile:
query_string = jsonfile.read()
return query_string
def get_query_payload(section_name: str, config_obj: configparser.ConfigParser()) -> JSONType:
log.debug(f"Generating HTTP POST JSON payload ...")
query = config_obj.get(section_name, "query")
if query[0] == "@":
query = query.split("@", 1)[1]
query = query_string_from_file(query)
return json.loads(query)
def get_json_response(section_name: str, config_obj: configparser.ConfigParser(), payload: JSONType) -> JSONType:
log.debug(f"Downloading JSON list of Mediathek files that match search criteria")
url = config_obj.get(section_name, "mvw_endpoint")
req_header = {"Content-Type": "text/plain", "asdasd": "aaaaaaaaaa"}
s = requests.Session()
req = requests.Request("POST", url, data=json.dumps(payload), headers=req_header)
prepped = req.prepare()
newline = "\n"
#req.method
#req.url
#for header, value in list(req.headers.items()):
# headers_table.add_row(header, value)
quit()
with s.send(prepped) as s:
pass
# log.debug(s.content)
if __name__ == '__main__':
validate_default_section(config) validate_default_section(config)
if config_has_valid_section(config): if config_has_valid_section(config):
validate_config_sections(config) validate_config_sections(config)
else: else:
log.error(f"No valid config section found. A valid config section has at least the '{CONST.CFG_MANDATORY}' " log.error(f"No valid config section found. A valid config section has at least the mandatory options "
f"option set. Exiting 2 ...") f"{CONST.CFG_MANDATORY} set. Exiting 2 ...")
sys.exit(2) sys.exit(2)
quit() log.debug(f"Iterating over config sections ...")
for section in config.sections():
log.debug(f"Processing section '[{section}]' ...")
query_payload = get_query_payload(section, config)
json_response = get_json_response(section, config, query_payload)
# This is a sample Python script.
# Press Umschalt+F10 to execute it or replace it with your code.
# Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings.
def print_hi(name):
# Use a breakpoint in the code line below to debug your script.
print(f'Hi, {name}') # Press Strg+F8 to toggle the breakpoint.
# Press the green button in the gutter to run the script.
if __name__ == '__main__':
print_hi('PyCharm')
# See PyCharm help at https://www.jetbrains.com/help/pycharm/

21
maus-query.json Normal file
View File

@ -0,0 +1,21 @@
{
"queries": [
{
"fields": [
"topic"
],
"query": "die sendung mit der maus"
},
{
"fields": [
"channel"
],
"query": "ARD"
}
],
"sortBy": "timestamp",
"sortOrder": "desc",
"future": false,
"offset": 0,
"size": 20
}

View File

@ -1 +1,2 @@
rich rich
requests

View File

@ -4,9 +4,19 @@
# #
# pip-compile # pip-compile
# #
certifi==2021.10.8
# via requests
charset-normalizer==2.0.12
# via requests
commonmark==0.9.1 commonmark==0.9.1
# via rich # via rich
idna==3.3
# via requests
pygments==2.11.2 pygments==2.11.2
# via rich # via rich
requests==2.27.1
# via -r requirements.in
rich==12.0.0 rich==12.0.0
# via -r requirements.in # via -r requirements.in
urllib3==1.26.8
# via requests