diff --git a/pythoncz/models/github.py b/pythoncz/models/github.py index 3abdeb1d..8df07a5c 100644 --- a/pythoncz/models/github.py +++ b/pythoncz/models/github.py @@ -42,9 +42,12 @@ def _create_api_session(): '(+https://python.cz)').format(now=datetime.now()) session = requests.Session() + token = app.config['GITHUB_TOKEN'] + if not token: + raise Exception('GITHUB_TOKEN not configured') session.headers.update({ 'User-Agent': user_agent, - 'Authorization': 'token {}'.format(app.config['GITHUB_TOKEN']), + 'Authorization': 'token {}'.format(token), }) return session diff --git a/pythoncz/models/meetups.py b/pythoncz/models/meetups.py index c2f17ee8..5ece5cc3 100644 --- a/pythoncz/models/meetups.py +++ b/pythoncz/models/meetups.py @@ -1,5 +1,6 @@ -import requests +from functools import lru_cache from lxml import html +import requests from slugify import slugify @@ -10,6 +11,7 @@ 'Seznam_m%C4%9Bst_v_%C4%8Cesku_podle_po%C4%8Dtu_obyvatel') +@lru_cache() def get_meetups(lang='cs'): return sort_by_city_size(scrape_meetups(lang)) @@ -36,6 +38,15 @@ def scrape_meetups(lang='cs'): continue +@lru_cache() +def scrape_cities(): + res = requests.get(WIKI_URL) + res.raise_for_status() + root = html.fromstring(res.text) + rows = root.cssselect('.wikitable tbody tr') + return [row.cssselect('td')[1].text_content().strip() for row in rows[1:]] + + def sort_by_city_size(meetups): """ Sorts given iterable of meetups by the size of the city. While pyvo.cz @@ -47,23 +58,13 @@ def sort_by_city_size(meetups): that the visitor of the page is close to them, thus they deserve to be higher in the list. """ - res = requests.get(WIKI_URL) - res.raise_for_status() - - root = html.fromstring(res.content.decode('utf-8')) - rows = root.cssselect('.wikitable tbody tr') - rows.pop(0) - - city_slugs = [ - slugify(row.cssselect('td')[0].text_content().strip()) + '-pyvo' - for row in rows - ] - - # fix discrepancies - city_slugs[city_slugs.index('hradec-kralove-pyvo')] = 'hradec-pyvo' + city_slugs = [slugify(city) + '-pyvo' for city in scrape_cities()] + # convert list [city1, city2, ...] into dict {city1: 0, city2: 1, ...} + city_slugs = {city: n for n, city in enumerate(city_slugs)} + city_slugs['hradec-pyvo'] = city_slugs['hradec-kralove-pyvo'] def key_func(meetup): slug = meetup['url'].rstrip('/').split('/')[-1] - return city_slugs.index(slug) + return city_slugs[slug] return sorted(meetups, key=key_func) diff --git a/pythoncz/static/main.css b/pythoncz/static/main.css index bbbd6579..995e0ea0 100644 --- a/pythoncz/static/main.css +++ b/pythoncz/static/main.css @@ -647,7 +647,7 @@ i.icon-english::before { padding-left: 3em; margin: 0.5em 0; background: url("./images/pyvo.png"); - background-size: contain; + background-size: 2em; background-repeat: no-repeat; } diff --git a/pythoncz/views.py b/pythoncz/views.py index 9890328b..7db94c7d 100644 --- a/pythoncz/views.py +++ b/pythoncz/views.py @@ -1,3 +1,4 @@ +import logging import os import subprocess import itertools @@ -14,6 +15,8 @@ INDEX_EVENTS_LIMIT = 3 +logger = logging.getLogger(__name__) + # Templating @@ -118,15 +121,17 @@ def events_ical(): @app.route('/zapojse/') def get_involved_cs(): disabled = os.getenv('DISABLE_GITHUB_ISSUES_FETCH', False) - try: - if disabled: - raise Exception('DISABLE_GITHUB_ISSUES_FETCH is set') - issues = github.get_issues(app.config['GITHUB_ORGANIZATIONS']) - return render_template('get_involved_cs.html', issues=issues) - except Exception as e: - template = render_template('get_involved_cs.html', issues=[], error=e) - code = 200 if disabled else 500 - return make_response(template, code) + issues = [] + error = None + if not disabled: + try: + issues = github.get_issues(app.config['GITHUB_ORGANIZATIONS']) + except Exception as e: + logger.exception('Failed to fetch GitHub issues') + error = e + template = render_template('get_involved_cs.html', issues=issues, error=error) + code = 200 if not error else 500 + return make_response(template, code) # Redirects of legacy stuff diff --git a/pythoncz_tests/models/github_test.py b/pythoncz_tests/models/github_test.py index bc8796e4..9d39f761 100644 --- a/pythoncz_tests/models/github_test.py +++ b/pythoncz_tests/models/github_test.py @@ -6,6 +6,7 @@ import responses from werkzeug.contrib.cache import NullCache +from pythoncz import app from pythoncz.models import github as github_module from pythoncz_tests.models import github_test_fixtures as fixtures @@ -21,7 +22,19 @@ def add_github_graphql(self, *args, **kwargs): @pytest.fixture() -def requests_mock(): +def patched_token(): + """ + Code expects the token to be configured. + Also this makes sure that we do not accidentally access the non-mock API. + """ + orig_value = app.config['GITHUB_TOKEN'] + app.config['GITHUB_TOKEN'] = 'test' + yield + app.config['GITHUB_TOKEN'] = orig_value + + +@pytest.fixture() +def requests_mock(patched_token): """Provides mechanism for faking HTTP responses""" with RequestsMock() as mock: yield mock @@ -78,7 +91,7 @@ def test_get_issues_api_error(github, requests_mock): assert excinfo.value.response.status_code == 500 -def test_create_api_session(github): +def test_create_api_session(patched_token, github): """ The '_create_api_session' helper should add User-Agent and Authorization HTTP headers to the HTTP session