diff options
author | Marcello Stanisci <stanisci.m@gmail.com> | 2017-12-05 20:50:11 +0100 |
---|---|---|
committer | Marcello Stanisci <stanisci.m@gmail.com> | 2017-12-05 20:50:11 +0100 |
commit | c7a1a78c049eb224ff303c56784b77c0b8c04b6b (patch) | |
tree | 832468c9cfa0899bfc29abeed84ffe983639460c | |
parent | f51e10e56ef39ff79878f56bf6d77c633f052afb (diff) | |
download | blog-c7a1a78c049eb224ff303c56784b77c0b8c04b6b.tar.gz blog-c7a1a78c049eb224ff303c56784b77c0b8c04b6b.tar.bz2 blog-c7a1a78c049eb224ff303c56784b77c0b8c04b6b.zip |
mostly linted
-rw-r--r-- | talerblog/blog/blog.py | 158 | ||||
-rw-r--r-- | talerblog/blog/content.py | 36 | ||||
-rw-r--r-- | talerblog/helpers.py | 59 | ||||
-rw-r--r-- | talerblog/talerconfig.py | 277 | ||||
-rw-r--r-- | talerblog/tests.py | 26 |
5 files changed, 288 insertions, 268 deletions
diff --git a/talerblog/blog/blog.py b/talerblog/blog/blog.py index c42e9d4..101a968 100644 --- a/talerblog/blog/blog.py +++ b/talerblog/blog/blog.py @@ -20,37 +20,32 @@ Implement URL handlers and payment logic for the blog merchant. """ -import flask -from urllib.parse import urljoin, urlencode, quote, parse_qsl -import requests +from urllib.parse import urljoin, quote, parse_qsl import logging import os import base64 -import random -import time -import json -import datetime -from pprint import pprint +import requests +import flask from talerblog.talerconfig import TalerConfig -from talerblog.helpers import (make_url, - expect_parameter, join_urlparts, get_query_string, - backend_error) -from talerblog.blog.content import (articles, -get_article_file, get_image_file) +from talerblog.helpers import (make_url, \ + expect_parameter, join_urlparts, \ + get_query_string, backend_error) +from talerblog.blog.content import (ARTICLES, \ + get_article_file, get_image_file) -logger = logging.getLogger(__name__) +LOGGER = logging.getLogger(__name__) -base_dir = os.path.dirname(os.path.abspath(__file__)) +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -app = flask.Flask(__name__, template_folder=base_dir) +app = flask.Flask(__name__, template_folder=BASE_DIR) app.debug = True app.secret_key = base64.b64encode(os.urandom(64)).decode('utf-8') -tc = TalerConfig.from_env() +TC = TalerConfig.from_env() -BACKEND_URL = tc["frontends"]["backend"].value_string(required=True) -CURRENCY = tc["taler"]["currency"].value_string(required=True) -INSTANCE = tc["blog"]["instance"].value_string(required=True) +BACKEND_URL = TC["frontends"]["backend"].value_string(required=True) +CURRENCY = TC["taler"]["currency"].value_string(required=True) +INSTANCE = TC["blog"]["instance"].value_string(required=True) ARTICLE_AMOUNT = dict(value=1, fraction=0, currency=CURRENCY) app.config.from_object(__name__) @@ -69,7 +64,7 @@ def utility_processor(): def index(): return flask.render_template("templates/index.html", merchant_currency=CURRENCY, - articles=articles.values()) + articles=ARTICLES.values()) @app.route("/javascript") @@ -85,17 +80,17 @@ def refund(): article_name = flask.request.form.get("article_name") if not article_name: return flask.jsonify(dict(error="No article_name found in form")), 400 - logger.info("Looking for %s to refund" % article_name) + LOGGER.info("Looking for %s to refund" % article_name) order_id = payed_articles.get(article_name) if not order_id: return flask.jsonify(dict(error="Aborting refund: article not payed")), 401 - r = requests.post(urljoin(BACKEND_URL, "refund"), - json=dict(order_id=order_id, - refund=dict(value=1, fraction=0, currency=CURRENCY), - reason="Demo reimbursement", - instance=INSTANCE)) - if 200 != r.status_code: - return backend_error(r) + resp = requests.post(urljoin(BACKEND_URL, "refund"), + json=dict(order_id=order_id, + refund=dict(value=1, fraction=0, currency=CURRENCY), + reason="Demo reimbursement", + instance=INSTANCE)) + if resp.status_code != 200: + return backend_error(resp) payed_articles[article_name] = "__refunded" response = flask.make_response() response.headers["X-Taler-Refund-Url"] = make_url("/refund", ("order_id", order_id)) @@ -104,13 +99,13 @@ def refund(): else: order_id = expect_parameter("order_id", False) if not order_id: - logger.error("Missing parameter 'order_id'") + LOGGER.error("Missing parameter 'order_id'") return flask.jsonify(dict(error="Missing parameter 'order_id'")), 400 - r = requests.get(urljoin(BACKEND_URL, "refund"), params=dict(order_id=order_id, - instance=INSTANCE)) - if 200 != r.status_code: - return backend_error(r) - return flask.jsonify(r.json()), r.status_code + resp = requests.get(urljoin(BACKEND_URL, "refund"), + params=dict(order_id=order_id, instance=INSTANCE)) + if resp.status_code != 200: + return backend_error(resp) + return flask.jsonify(resp.json()), resp.status_code @app.route("/generate-contract", methods=["GET"]) @@ -140,10 +135,11 @@ def generate_contract(): ), extra=dict(article_name=article_name), ) - r = requests.post(urljoin(BACKEND_URL, "proposal"), json=dict(order=order)) - if r.status_code != 200: - return backend_error(r) - proposal_resp = r.json() + resp = requests.post(urljoin(BACKEND_URL, "proposal"), + json=dict(order=order)) + if resp.status_code != 200: + return backend_error(resp) + proposal_resp = resp.json() return flask.jsonify(**proposal_resp) @@ -155,34 +151,35 @@ def cc_payment(name): @app.route("/essay/<name>") @app.route("/essay/<name>/data/<data>") -def article(name, data=None): - logger.info("processing %s" % name) +def article(namex, data=None): + LOGGER.info("processing %s" % namex) payed_articles = flask.session.get("payed_articles", {}) - if payed_articles.get(name, "") == "__refunded": - return flask.render_template("templates/article_refunded.html", article_name=name) + if payed_articles.get(namex, "") == "__refunded": + return flask.render_template("templates/article_refunded.html", article_name=namex) - if name in payed_articles: - article = articles[name] - if article is None: + if namex in payed_articles: + articlex = ARTICLES[namex] + if articlex is None: flask.abort(500) if data is not None: - if data in article.extra_files: + if data in articlex.extra_files: return flask.send_file(get_image_file(data)) - else: - return "permission denied", 400 + return "permission denied", 400 return flask.render_template("templates/article_frame.html", - article_file=get_article_file(article), - article_name=name) + article_file=get_article_file(articlex), + article_name=namex) - contract_url = make_url("/generate-contract", ("article_name",name)) - response = flask.make_response(flask.render_template("templates/fallback.html"), 402) + contract_url = make_url("/generate-contract", + ("article_name", namex)) + response = flask.make_response( + flask.render_template("templates/fallback.html"), 402) response.headers["X-Taler-Contract-Url"] = contract_url response.headers["X-Taler-Contract-Query"] = "fulfillment_url" # Useless (?) header, as X-Taler-Contract-Url takes always (?) precedence # over X-Offer-Url. This one might only be useful if the contract retrieval # goes wrong. - response.headers["X-Taler-Offer-Url"] = make_url("/essay/" + quote(name)) + response.headers["X-Taler-Offer-Url"] = make_url("/essay/" + quote(namex)) return response @@ -190,36 +187,41 @@ def article(name, data=None): def pay(): deposit_permission = flask.request.get_json() if deposit_permission is None: - e = flask.jsonify(error="no json in body"), - return e, 400 - r = requests.post(urljoin(BACKEND_URL, "pay"), json=deposit_permission) - if 200 != r.status_code: - return backend_error(r) - proposal_data = r.json()["contract_terms"] + return flask.jsonify(error="no json in body"), 400 + resp = requests.post(urljoin(BACKEND_URL, "pay"), + json=deposit_permission) + if resp.status_code != 200: + return backend_error(resp) + proposal_data = resp.json()["contract_terms"] article_name = proposal_data["extra"]["article_name"] payed_articles = flask.session["payed_articles"] = flask.session.get("payed_articles", {}) - if len(r.json()["refund_permissions"]) != 0: + + try: + resp.json()["refund_permissions"].pop() # we had some refunds on the article purchase already! - logger.info("Article %s was refunded, before /pay" % article_name) + LOGGER.info("Article %s was refunded, before /pay" % article_name) payed_articles[article_name] = "__refunded" - return flask.jsonify(r.json()), 200 + return flask.jsonify(resp.json()), 200 + except IndexError: + pass + if not deposit_permission["order_id"]: - logger.error("order_id missing from deposit_permission!") + LOGGER.error("order_id missing from deposit_permission!") return flask.jsonify(dict(error="internal error: ask for refund!")), 500 if article_name not in payed_articles: - logger.info("Article %s goes in state" % article_name) + LOGGER.info("Article %s goes in state" % article_name) payed_articles[article_name] = deposit_permission["order_id"] - return flask.jsonify(r.json()), 200 + return flask.jsonify(resp.json()), 200 @app.route("/history") def history(): qs = get_query_string().decode("utf-8") url = urljoin(BACKEND_URL, "history") - r = requests.get(url, params=dict(parse_qsl(qs))) - if 200 != r.status_code: - return backend_error(r) - return flask.jsonify(r.json()), r.status_code + resp = requests.get(url, params=dict(parse_qsl(qs))) + if resp.status_code != 200: + return backend_error(resp) + return flask.jsonify(resp.json()), resp.status_code @app.route("/backoffice") @@ -232,17 +234,17 @@ def track(): def track_transfer(): qs = get_query_string().decode("utf-8") url = urljoin(BACKEND_URL, "track/transfer") - r = requests.get(url, params=dict(parse_qsl(qs))) - if 200 != r.status_code: - return backend_error(r) - return flask.jsonify(r.json()), r.status_code + resp = requests.get(url, params=dict(parse_qsl(qs))) + if resp.status_code != 200: + return backend_error(resp) + return flask.jsonify(resp.json()), resp.status_code @app.route("/track/order") def track_order(): qs = get_query_string().decode("utf-8") url = urljoin(BACKEND_URL, "track/transaction") - r = requests.get(url, params=dict(parse_qsl(qs))) - if 200 != r.status_code: - return backend_error(r) - return flask.jsonify(r.json()), r.status_code + resp = requests.get(url, params=dict(parse_qsl(qs))) + if resp.status_code != 200: + return backend_error(resp) + return flask.jsonify(resp.json()), resp.status_code diff --git a/talerblog/blog/content.py b/talerblog/blog/content.py index 42ecf18..3202a50 100644 --- a/talerblog/blog/content.py +++ b/talerblog/blog/content.py @@ -18,33 +18,32 @@ Define content and associated metadata that is served on the blog. """ -from collections import OrderedDict -from bs4 import BeautifulSoup -from pkg_resources import resource_stream, resource_filename -from collections import namedtuple +from collections import OrderedDict, namedtuple import logging import os import re +from bs4 import BeautifulSoup +from pkg_resources import resource_stream, resource_filename -logger = logging.getLogger(__name__) +LOGGER = logging.getLogger(__name__) Article = namedtuple("Article", "slug title teaser main_file extra_files") -articles = OrderedDict() +ARTICLES = OrderedDict() -def add_article(slug, title, teaser, main_file, extra_files=[]): - articles[slug] = Article(slug, title, teaser, main_file, extra_files) +def add_article(slug, title, teaser, main_file, extra_files): + ARTICLES[slug] = Article(slug, title, teaser, main_file, extra_files) def get_image_file(image): - f = resource_filename("talerblog", os.path.join("blog/data/", image)) - return os.path.abspath(f) + filex = resource_filename("talerblog", os.path.join("blog/data/", image)) + return os.path.abspath(filex) def get_article_file(article): - f = resource_filename("talerblog", article.main_file) - return os.path.basename(f) + filex = resource_filename("talerblog", article.main_file) + return os.path.basename(filex) def add_from_html(resource_name, teaser_paragraph=0, title=None): @@ -56,29 +55,30 @@ def add_from_html(resource_name, teaser_paragraph=0, title=None): if title is None: title_el = soup.find("h1", attrs={"class":["chapter", "unnumbered"]}) if title_el is None: - logger.warn("Can't extract title from '%s'", resource_name) + LOGGER.warning("Can't extract title from '%s'", resource_name) title = resource_name else: title = title_el.get_text().strip() slug = title.replace(" ", "_") paragraphs = soup.find_all("p") - + teaser = soup.find("p", attrs={"id":["teaser"]}) if teaser is None: teaser = str(paragraphs[teaser_paragraph]) - p = re.compile("^/essay/[^/]+/data/[^/]+$") + re_proc = re.compile("^/essay/[^/]+/data/[^/]+$") imgs = soup.find_all("img") extra_files = [] for img in imgs: # We require that any image whose access is regulated is src'd # as "<slug>/data/img.png". We also need to check if the <slug> # component actually matches the article's slug - if p.match(img['src']): + if re_proc.match(img['src']): if img['src'].split(os.sep)[2] == slug: - logger.info("extra file for %s is %s" % (slug, os.path.basename(img['src']))) + LOGGER.info("extra file for %s is %s" % (slug, os.path.basename(img['src']))) extra_files.append(os.path.basename(img['src'])) else: - logger.warning("Image src and slug don't match: '%s' != '%s'" % (img['src'].split(os.sep)[2], slug)) + LOGGER.warning("Image src and slug don't match: '%s' != '%s'" \ + % (img['src'].split(os.sep)[2], slug)) add_article(slug, title, teaser, resource_name, extra_files) diff --git a/talerblog/helpers.py b/talerblog/helpers.py index 257192d..614e463 100644 --- a/talerblog/helpers.py +++ b/talerblog/helpers.py @@ -15,22 +15,18 @@ # @author Florian Dold # @author Marcello Stanisci -from flask import request, jsonify, make_response, current_app, render_template -import flask from urllib.parse import urljoin, urlencode import logging -import requests -import re -import datetime import json +import flask from .talerconfig import TalerConfig -logger = logging.getLogger(__name__) +LOGGER = logging.getLogger(__name__) -tc = TalerConfig.from_env() -BACKEND_URL = tc["frontends"]["backend"].value_string(required=True) -NDIGITS = tc["frontends"]["NDIGITS"].value_int() -CURRENCY = tc["taler"]["CURRENCY"].value_string() +TC = TalerConfig.from_env() +BACKEND_URL = TC["frontends"]["backend"].value_string(required=True) +NDIGITS = TC["frontends"]["NDIGITS"].value_int() +CURRENCY = TC["taler"]["CURRENCY"].value_string() FRACTION_BASE = 1e8 @@ -40,29 +36,30 @@ if not NDIGITS: class MissingParameterException(Exception): def __init__(self, param): self.param = param + super().__init__() def amount_to_float(amount): return amount['value'] + (float(amount['fraction']) / float(FRACTION_BASE)) -def amount_from_float(x): - value = int(x) - fraction = int((x - value) * FRACTION_BASE) +def amount_from_float(floatx): + value = int(floatx) + fraction = int((floatx - value) * FRACTION_BASE) return dict(currency=CURRENCY, value=value, fraction=fraction) def join_urlparts(*parts): - s = "" - i = 0 - while i < len(parts): - n = parts[i] - i += 1 - if s.endswith("/"): - n = n.lstrip("/") - elif s and not n.startswith("/"): - n = "/" + n - s += n - return s + ret = "" + part = 0 + while part < len(parts): + buf = parts[part] + part += 1 + if ret.endswith("/"): + buf = buf.lstrip("/") + elif ret and not buf.startswith("/"): + buf = "/" + buf + ret += buf + return ret def make_url(page, *query_params): @@ -72,10 +69,10 @@ def make_url(page, *query_params): """ query = urlencode(query_params) if page.startswith("/"): - root = request.url_root + root = flask.request.url_root page = page.lstrip("/") else: - root = request.base_url + root = flask.request.base_url url = urljoin(root, "%s?%s" % (page, query)) # urlencode is overly eager with quoting, the wallet right now # needs some characters unquoted. @@ -83,22 +80,22 @@ def make_url(page, *query_params): def expect_parameter(name, alt=None): - value = request.args.get(name, None) + value = flask.request.args.get(name, None) if value is None and alt is None: - logger.error("Missing parameter '%s' from '%s'." % (name, request.args)) + LOGGER.error("Missing parameter '%s' from '%s'." % (name, flask.request.args)) raise MissingParameterException(name) return value if value else alt def get_query_string(): - return request.query_string + return flask.request.query_string def backend_error(requests_response): - logger.error("Backend error: status code: " + LOGGER.error("Backend error: status code: " + str(requests_response.status_code)) try: return flask.jsonify(requests_response.json()), requests_response.status_code except json.decoder.JSONDecodeError: - logger.error("Backend error (NO JSON returned): status code: " + LOGGER.error("Backend error (NO JSON returned): status code: " + str(requests_response.status_code)) return flask.jsonify(dict(error="Backend died, no JSON got from it")), 502 diff --git a/talerblog/talerconfig.py b/talerblog/talerconfig.py index ba4dfbb..a7ca065 100644 --- a/talerblog/talerconfig.py +++ b/talerblog/talerconfig.py @@ -18,23 +18,22 @@ Parse GNUnet-style configurations in pure Python """ -# FIXME: make sure that autovivification of config entries -# does not leave garbage behind (use weakrefs!) - import logging import collections import os import weakref +import sys +import re -logger = logging.getLogger(__name__) +LOGGER = logging.getLogger(__name__) __all__ = ["TalerConfig"] -taler_datadir = None +TALER_DATADIR = None try: # not clear if this is a good idea ... - from talerpaths import taler_datadir as t - taler_datadir = t + from talerpaths import TALER_DATADIR as t + TALER_DATADIR = t except ImportError: pass @@ -45,7 +44,7 @@ class ExpansionSyntaxError(Exception): pass -def expand(s, getter): +def expand(var, getter): """ Do shell-style parameter expansion. Supported syntax: @@ -56,18 +55,18 @@ def expand(s, getter): pos = 0 result = "" while pos != -1: - start = s.find("$", pos) + start = var.find("$", pos) if start == -1: break - if s[start:].startswith("${"): + if var[start:].startswith("${"): balance = 1 end = start + 2 - while balance > 0 and end < len(s): - balance += {"{": 1, "}": -1}.get(s[end], 0) + while balance > 0 and end < len(var): + balance += {"{": 1, "}": -1}.get(var[end], 0) end += 1 if balance != 0: raise ExpansionSyntaxError("unbalanced parentheses") - piece = s[start+2:end-1] + piece = var[start+2:end-1] if piece.find(":-") > 0: varname, alt = piece.split(":-", 1) replace = getter(varname) @@ -77,20 +76,20 @@ def expand(s, getter): varname = piece replace = getter(varname) if replace is None: - replace = s[start:end] + replace = var[start:end] else: end = start + 2 - while end < len(s) and s[start+1:end+1].isalnum(): + while end < len(var) and var[start+1:end+1].isalnum(): end += 1 - varname = s[start+1:end] + varname = var[start+1:end] replace = getter(varname) if replace is None: - replace = s[start:end] + replace = var[start:end] result = result + replace pos = end - return result + s[pos:] + return result + var[pos:] class OptionDict(collections.defaultdict): @@ -99,79 +98,81 @@ class OptionDict(collections.defaultdict): self.section_name = section_name super().__init__() def __missing__(self, key): - e = Entry(self.config(), self.section_name, key) - self[key] = e - return e - def __getitem__(self, slice): - return super().__getitem__(slice.lower()) - def __setitem__(self, slice, value): - super().__setitem__(slice.lower(), value) + entry = Entry(self.config(), self.section_name, key) + self[key] = entry + return entry + def __getitem__(self, chunk): + return super().__getitem__(chunk.lower()) + def __setitem__(self, chunk, value): + super().__setitem__(chunk.lower(), value) class SectionDict(collections.defaultdict): - def __init__(self): - super().__init__() def __missing__(self, key): - v = OptionDict(self, key) - self[key] = v - return v - def __getitem__(self, slice): - return super().__getitem__(slice.lower()) - def __setitem__(self, slice, value): - super().__setitem__(slice.lower(), value) + value = OptionDict(self, key) + self[key] = value + return value + def __getitem__(self, chunk): + return super().__getitem__(chunk.lower()) + def __setitem__(self, chunk, value): + super().__setitem__(chunk.lower(), value) class Entry: - def __init__(self, config, section, option, value=None, filename=None, lineno=None): - self.value = value - self.filename = filename - self.lineno = lineno + def __init__(self, config, section, option, **kwargs): + self.value = kwargs.get("value") + self.filename = kwargs.get("filename") + self.lineno = kwargs.get("lineno") self.section = section self.option = option self.config = weakref.ref(config) def __repr__(self): - return "<Entry section=%s, option=%s, value=%s>" % (self.section, self.option, repr(self.value),) + return "<Entry section=%s, option=%s, value=%s>" \ + % (self.section, self.option, repr(self.value),) def __str__(self): return self.value - def value_string(self, default=None, warn=False, required=False): + def value_string(self, default=None, required=False, warn=False): if required and self.value is None: - raise ConfigurationError("Missing required option '%s' in section '%s'" % (self.option.upper(), self.section.upper())) + raise ConfigurationError("Missing required option '%s' in section '%s'" \ + % (self.option.upper(), self.section.upper())) if self.value is None: if warn: if default is not None: - logger.warn("Configuration is missing option '%s' in section '%s', falling back to '%s'", - self.option, self.section, default) + LOGGER.warning("Configuration is missing option '%s' in section '%s',\ + falling back to '%s'", self.option, self.section, default) else: - logger.warn("Configuration is missing option '%s' in section '%s'", self.option.upper(), self.section.upper()) + LOGGER.warning("Configuration ** is missing option '%s' in section '%s'", + self.option.upper(), self.section.upper()) return default return self.value - def value_int(self, default=None, warn=False, required=False): - v = self.value_string(default, warn, required) - if v is None: + def value_int(self, default=None, required=False, warn=False): + value = self.value_string(default, warn, required) + if value is None: return None try: - return int(v) + return int(value) except ValueError: - raise ConfigurationError("Expected number for option '%s' in section '%s'" % (self.option.upper(), self.section.upper())) + raise ConfigurationError("Expected number for option '%s' in section '%s'" \ + % (self.option.upper(), self.section.upper())) def _getsubst(self, key): - x = self.config()["paths"][key].value - if x is not None: - return x - x = os.environ.get(key) - if x is not None: - return x + value = self.config()["paths"][key].value + if value is not None: + return value + value = os.environ.get(key) + if value is not None: + return value return None - def value_filename(self, default=None, warn=False, required=False): - v = self.value_string(default, warn, required) - if v is None: + def value_filename(self, default=None, required=False, warn=False): + value = self.value_string(default, required, warn) + if value is None: return None - return expand(v, lambda x: self._getsubst(x)) + return expand(value, self._getsubst) def location(self): if self.filename is None or self.lineno is None: @@ -190,6 +191,8 @@ class TalerConfig: """ self.sections = SectionDict() + # defaults != config file: the first is the 'base' + # whereas the second overrides things from the first. @staticmethod def from_file(filename=None, load_defaults=True): cfg = TalerConfig() @@ -204,14 +207,17 @@ class TalerConfig: cfg.load_file(filename) return cfg - def value_string(self, section, option, default=None, required=None, warn=False): - return self.sections[section][option].value_string(default, required, warn) + def value_string(self, section, option, **kwargs): + return self.sections[section][option].value_string( + kwargs.get("default"), kwargs.get("required"), kwargs.get("warn")) - def value_filename(self, section, option, default=None, required=None, warn=False): - return self.sections[section][option].value_filename(default, required, warn) + def value_filename(self, section, option, **kwargs): + return self.sections[section][option].value_filename( + kwargs.get("default"), kwargs.get("required"), kwargs.get("warn")) - def value_int(self, section, option, default=None, required=None, warn=False): - return self.sections[section][option].value_int(default, required, warn) + def value_int(self, section, option, **kwargs): + return self.sections[section][option].value_int( + kwargs.get("default"), kwargs.get("required"), kwargs.get("warn")) def load_defaults(self): base_dir = os.environ.get("TALER_BASE_CONFIG") @@ -220,12 +226,15 @@ class TalerConfig: return prefix = os.environ.get("TALER_PREFIX") if prefix: + tmp = os.path.split(os.path.normpath(prefix)) + if re.match("lib", tmp[1]): + prefix = tmp[0] self.load_dir(os.path.join(prefix, "share/taler/config.d")) return - if taler_datadir: - self.load_dir(os.path.join(taler_datadir, "share/taler/config.d")) + if TALER_DATADIR: + self.load_dir(os.path.join(TALER_DATADIR, "share/taler/config.d")) return - logger.warn("no base directory found") + LOGGER.warning("no base directory found") @staticmethod def from_env(*args, **kwargs): @@ -240,7 +249,7 @@ class TalerConfig: try: files = os.listdir(dirname) except FileNotFoundError: - logger.warn("can't read config directory '%s'", dirname) + LOGGER.warning("can't read config directory '%s'", dirname) return for file in files: if not file.endswith(".conf"): @@ -249,73 +258,85 @@ class TalerConfig: def load_file(self, filename): sections = self.sections - with open(filename, "r") as file: - lineno = 0 - current_section = None - for line in file: - lineno += 1 - line = line.strip() - if len(line) == 0: - # empty line - continue - if line.startswith("#"): - # comment - continue - if line.startswith("["): - if not line.endswith("]"): - logger.error("invalid section header in line %s: %s", lineno, repr(line)) - section_name = line.strip("[]").strip().strip('"') - current_section = section_name - continue - if current_section is None: - logger.error("option outside of section in line %s: %s", lineno, repr(line)) - continue - kv = line.split("=", 1) - if len(kv) != 2: - logger.error("invalid option in line %s: %s", lineno, repr(line)) - key = kv[0].strip() - value = kv[1].strip() - if value.startswith('"'): - value = value[1:] - if not value.endswith('"'): - logger.error("mismatched quotes in line %s: %s", lineno, repr(line)) - else: - value = value[:-1] - e = Entry(self.sections, current_section, key, value, filename, lineno) - sections[current_section][key] = e + try: + with open(filename, "r") as file: + lineno = 0 + current_section = None + for line in file: + lineno += 1 + line = line.strip() + if line == "": + # empty line + continue + if line.startswith("#"): + # comment + continue + if line.startswith("["): + if not line.endswith("]"): + LOGGER.error("invalid section header in line %s: %s", + lineno, repr(line)) + section_name = line.strip("[]").strip().strip('"') + current_section = section_name + continue + if current_section is None: + LOGGER.error("option outside of section in line %s: %s", lineno, repr(line)) + continue + pair = line.split("=", 1) + if len(pair) != 2: + LOGGER.error("invalid option in line %s: %s", lineno, repr(line)) + key = pair[0].strip() + value = pair[1].strip() + if value.startswith('"'): + value = value[1:] + if not value.endswith('"'): + LOGGER.error("mismatched quotes in line %s: %s", lineno, repr(line)) + else: + value = value[:-1] + entry = Entry(self.sections, current_section, key, + value=value, filename=filename, lineno=lineno) + sections[current_section][key] = entry + except FileNotFoundError: + LOGGER.error("Configuration file (%s) not found", filename) + sys.exit(3) def dump(self): - for section_name, section in self.sections.items(): - print("[%s]" % (section.section_name,)) - for option_name, e in section.items(): - print("%s = %s # %s" % (e.option, e.value, e.location())) - - def __getitem__(self, slice): - if isinstance(slice, str): - return self.sections[slice] + for kv_section in self.sections.items(): + print("[%s]" % (kv_section[1].section_name,)) + for kv_option in kv_section[1].items(): + print("%s = %s # %s" % \ + (kv_option[1].option, + kv_option[1].value, + kv_option[1].location())) + + def __getitem__(self, chunk): + if isinstance(chunk, str): + return self.sections[chunk] raise TypeError("index must be string") if __name__ == "__main__": - import sys import argparse - parser = argparse.ArgumentParser() - parser.add_argument("--section", "-s", dest="section", default=None, metavar="SECTION") - parser.add_argument("--option", "-o", dest="option", default=None, metavar="OPTION") - parser.add_argument("--config", "-c", dest="config", default=None, metavar="FILE") - parser.add_argument("--filename", "-f", dest="expand_filename", default=False, action='store_true') - args = parser.parse_args() - - tc = TalerConfig.from_file(args.config) - - if args.section is not None and args.option is not None: - if args.expand_filename: - x = tc.value_filename(args.section, args.option) + PARSER = argparse.ArgumentParser() + PARSER.add_argument("--section", "-s", dest="section", + default=None, metavar="SECTION") + PARSER.add_argument("--option", "-o", dest="option", + default=None, metavar="OPTION") + PARSER.add_argument("--config", "-c", dest="config", + default=None, metavar="FILE") + PARSER.add_argument("--filename", "-f", dest="expand_filename", + default=False, action='store_true') + ARGS = PARSER.parse_args() + + TC = TalerConfig.from_file(ARGS.config) + + if ARGS.section is not None and ARGS.option is not None: + if ARGS.expand_filename: + X = TC.value_filename(ARGS.section, ARGS.option) else: - x = tc.value_string(args.section, args.option) - if x is not None: - print(x) + X = TC.value_string(ARGS.section, ARGS.option) + if X is not None: + print(X) else: - tc.dump() + TC.dump() diff --git a/talerblog/tests.py b/talerblog/tests.py index 36d27c5..dc720cf 100644 --- a/talerblog/tests.py +++ b/talerblog/tests.py @@ -5,8 +5,8 @@ from mock import patch, MagicMock from talerblog.blog import blog from talerblog.talerconfig import TalerConfig -tc = TalerConfig.from_env() -CURRENCY = tc["taler"]["currency"].value_string(required=True) +TC = TalerConfig.from_env() +CURRENCY = TC["taler"]["currency"].value_string(required=True) class BlogTestCase(unittest.TestCase): def setUp(self): @@ -14,12 +14,12 @@ class BlogTestCase(unittest.TestCase): self.app = blog.app.test_client() @patch("requests.post") - def test_proposal_creation(self, mocked_post): + def test_proposal_creation(self, mocked_post): ret_post = MagicMock() ret_post.status_code = 200 ret_post.json.return_value = {} mocked_post.return_value = ret_post - self.app.get("/generate-contract?nonce=55&article_name=Check_Me") + self.app.get("/generate-contract?nonce=55&article_name=Check_Me") mocked_post.assert_called_with( "http://backend.test.taler.net/proposal", json={ @@ -35,21 +35,21 @@ class BlogTestCase(unittest.TestCase): "fraction": 0, "currency": CURRENCY}, "products": [{ - "description": "Essay: Check Me", - "quantity": 1, - "product_id": 0, - "price": { - "value": 1, - "fraction": 0, - "currency": CURRENCY}}], + "description": "Essay: Check Me", + "quantity": 1, + "product_id": 0, + "price": { + "value": 1, + "fraction": 0, + "currency": CURRENCY}}], "fulfillment_url": "http://localhost/essay/Check_Me", "pay_url": "http://localhost/pay", "merchant": { - "instance": tc["blog"]["instance"].value_string(required=True), + "instance": TC["blog"]["instance"].value_string(required=True), "address": "nowhere", "name": "Kudos Inc.", "jurisdiction": "none"}, "extra": {"article_name": "Check_Me"}}}) -if "__main__" == __name__: +if __name__ == "__main__": unittest.main() |