Skip to content

Commit

Permalink
Merge pull request #61 from cul-it/develop
Browse files Browse the repository at this point in the history
Pre-release merge for v0.8.1
  • Loading branch information
erickpeirson authored Jul 3, 2018
2 parents 00a2007 + 654490c commit b046ad7
Show file tree
Hide file tree
Showing 25 changed files with 374 additions and 235 deletions.
2 changes: 1 addition & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ omit =
app.py
setup.py
docs/*
test*
*test*
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ python:
script:
- pip install -U pip pipenv
- pipenv install
- pipenv install ./
- ./tests/lintstats.sh
- ./tests/docker_tests.sh
- docker build ./ -t arxiv/base
- pipenv run nose2 --with-coverage
- pipenv run nose2 -s arxiv --with-coverage
after_success:
- coveralls
- if [ "$TRAVIS_BRANCH" == "master" ] && [ -z ${TRAVIS_TAG} ]; then
Expand Down
5 changes: 0 additions & 5 deletions Pipfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
[[source]]

url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"


[packages]

flask = "*"
"nose2" = "*"
jsonschema = "*"
Expand All @@ -17,6 +14,4 @@ uwsgi = "*"
pydocstyle = "*"
"boto3" = "*"


[dev-packages]

214 changes: 107 additions & 107 deletions Pipfile.lock

Large diffs are not rendered by default.

14 changes: 0 additions & 14 deletions arxiv/__init__.py

This file was deleted.

14 changes: 13 additions & 1 deletion arxiv/base/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,18 @@ def create_web_app() -> Flask:

from typing import Optional
from flask import Blueprint, Flask
from werkzeug.exceptions import NotFound

from arxiv.base.context_processors import config_url_builder
from arxiv.base import exceptions
from arxiv.base import exceptions, urls, config
from arxiv.base.converter import ArXivConverter


def placeholder(*args, **kwargs):
"""Placeholder route for external endpoints."""
raise NotFound("This endpoint is not provided by this service.")


class Base(object):
"""Attaches a base UI blueprint and context processors to an app."""

Expand Down Expand Up @@ -63,3 +69,9 @@ def init_app(self, app: Flask) -> None:
# Register base exception handlers.
for error, handler in exceptions.get_handlers():
app.errorhandler(error)(handler)

# Register URLs for other services. This allows us to use flask's
# ``url_for()`` function to generate URLs for services that are
# deployed at the same hostname.
for name, pattern in config.ARXIV_URLS:
app.add_url_rule(pattern, name, placeholder)
67 changes: 39 additions & 28 deletions arxiv/base/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,43 @@

SERVER_NAME = None

ARXIV_TWITTER_URL = os.environ.get('ARXIV_TWITTER_URL',
'https://twitter.com/arxiv')
ARXIV_SEARCH_BOX_URL = os.environ.get('SEARCH_BOX_URL', '/search')
ARXIV_SEARCH_ADVANCED_URL = os.environ.get('ARXIV_SEARCH_ADVANCED_URL',
'/search/advanced')
ARXIV_ACCOUNT_URL = os.environ.get('ACCOUNT_URL', '/user')
ARXIV_LOGIN_URL = os.environ.get('LOGIN_URL', '/user/login')
ARXIV_LOGOUT_URL = os.environ.get('LOGOUT_URL', '/user/logout')
ARXIV_HOME_URL = os.environ.get('ARXIV_HOME_URL', 'https://arxiv.org')
ARXIV_HELP_URL = os.environ.get('ARXIV_HELP_URL', '/help')
ARXIV_CONTACT_URL = os.environ.get('ARXIV_CONTACT_URL', '/help/contact')
ARXIV_BLOG_URL = os.environ.get('ARXIV_BLOG_URL',
"https://blogs.cornell.edu/arxiv/")
ARXIV_WIKI_URL = os.environ.get(
'ARXIV_WIKI_URL',
"https://confluence.cornell.edu/display/arxivpub/arXiv+Public+Wiki"
)
ARXIV_ACCESSIBILITY_URL = os.environ.get(
'ARXIV_ACCESSIBILITY_URL',
"mailto:[email protected]"
)
ARXIV_LIBRARY_URL = os.environ.get('ARXIV_LIBRARY_URL',
'https://library.cornell.edu')
ARXIV_ACKNOWLEDGEMENT_URL = os.environ.get(
'ARXIV_ACKNOWLEDGEMENT_URL',
"https://confluence.cornell.edu/x/ALlRF"
)
EXTERNAL_URLS = [
("twitter", os.environ.get("ARXIV_TWITTER_URL",
"https://twitter.com/arxiv")),
("blog", os.environ.get("ARXIV_BLOG_URL",
"https://blogs.cornell.edu/arxiv/")),
("wiki", os.environ.get("ARXIV_WIKI_URL",
"https://confluence.cornell.edu/display/arxivpub/"
"arXiv+Public+Wiki")),
("accessibility", os.environ.get("ARXIV_ACCESSIBILITY_URL",
"mailto:[email protected]")),
("library", os.environ.get("ARXIV_LIBRARY_URL",
"https://library.cornell.edu")),
("acknowledgment", os.environ.get(
"ARXIV_ACKNOWLEDGEMENT_URL",
"https://confluence.cornell.edu/x/ALlRF"
)),
]
"""External URLs, configurable via environment variables."""

ARXIV_BUSINESS_TZ = os.environ.get('ARXIV_BUSINESS_TZ', 'US/Eastern')
ARXIV_URLS = [
("help", "/help"),
("contact", "/help/contact"),
("search_box", "/search"),
("search_advanced", "/search/advanced"),
("account", "/user"),
("login", "/user/login"),
("logout", "/user/logout"),
("home", "/"),
("pdf", "/pdf/<arxiv:paper_id>"),
]
"""
URLs for other services, for use with :func:`flask.url_for`.
This only works for services at the same hostname, since Flask uses the
hostname on the request to generate the full URL. For addresses at a different
hostname, use :func:`arxiv.base.urls.config_url`, which relies on
``EXTERNAL_URLS`` in this configuration file.
"""

ARXIV_BUSINESS_TZ = os.environ.get("ARXIV_BUSINESS_TZ", "US/Eastern")
18 changes: 2 additions & 16 deletions arxiv/base/context_processors.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,9 @@
"""Context processors for the base application."""

from typing import Dict, Callable
from flask import current_app
from arxiv.base import config
from arxiv.base.exceptions import ConfigurationError
from arxiv.base.urls import config_url


def config_url_builder() -> Dict[str, Callable]:
"""Inject a configurable URL factory."""
def config_url(target: str) -> str:
"""Generate a URL from this app's configuration."""
target = target.upper()
# Look for the URL on the config of the current app (this will *not* be
# base); fall back to the base config if not found.
try:
url: str = current_app.config.get(f'ARXIV_{target}_URL')
if url is None:
url = getattr(config, f'ARXIV_{target}_URL')
except AttributeError as e:
raise ConfigurationError(f'URL for {target} not set') from e
return url
"""Inject :func:`.config_url` into the template rendering context."""
return dict(config_url=config_url)
4 changes: 2 additions & 2 deletions arxiv/base/templates/base/footer.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div class="columns" role="navigation" aria-label="Secondary">
<div class="column">
<ul class="nav-spaced">
<li><a href="{{ config_url('contact') }}">Contact</a></li>
<li><a href="{{ url_for('contact') }}">Contact</a></li>
<li><a href="{{ config_url('twitter') }}">Find us on Twitter</a></li>
</ul>
</div>
Expand All @@ -14,7 +14,7 @@
<div class="column">
<ul class="nav-spaced">
<li><a href="{{ config_url('accessibility') }}">Web Accessibility Help</a></li>
<li><a href="{{ config_url('help') }}">Help with using arXiv</a></li>
<li><a href="{{ url_for('help') }}">Help with using arXiv</a></li>
</ul>
</div>
</div>
6 changes: 3 additions & 3 deletions arxiv/base/templates/base/header.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
<div class="level-left">
<a class="level-item" href="{{ config_url('library') }}"><img src="{{ url_for('base.static', filename='images/CUL-reduced-white-SMALL.svg') }}" alt="Cornell University Library" width="300" aria-label="logo" /></a>
</div>
<div class="level-right"><p class="sponsors level-item is-marginless"><a href="{{ config_url('acknowledgement') }}">We gratefully acknowledge support from<br /> the Simons Foundation and member institutions</a></p></div>
<div class="level-right"><p class="sponsors level-item is-marginless"><a href="{{ config_url('acknowledgment') }}">We gratefully acknowledge support from<br /> the Simons Foundation and member institutions</a></p></div>
</div>
<!-- contains arXiv identity and search bar -->
<div class="identity level is-marginless">
<div class="level-left">
<h1 class="level-item"><a href="{{ config_url('home') }}" aria-label="arxiv-logo">arXiv.org</a></h1>
<h1 class="level-item"><a href="{{ url_for('home') }}" aria-label="arxiv-logo">arXiv.org</a></h1>
</div>
{{ macros.compactsearch(config_url, 'level-right') }}
</div> <!-- closes identity -->

<!-- # TODO: reintroduce this once we have access to the user's session.
<div class="user-tools box is-pulled-right" role="navigation" aria-label="User menu"><a href="{{ config_url('login') }}">Login</a> | <a href="{{ config_url('account') }}">My Account</a> | <a href="{{ config_url('logout') }}">Logout</a></div>
<div class="user-tools box is-pulled-right" role="navigation" aria-label="User menu"><a href="{{ url_for('login') }}">Login</a> | <a href="{{ url_for('account') }}">My Account</a> | <a href="{{ url_for('logout') }}">Logout</a></div>
-->
4 changes: 2 additions & 2 deletions arxiv/base/templates/base/macros.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
within level wrapper, allowed values are level-left or level-right.
#}
<div class="search-block {{ alignstyle }}">
<form class="level-item" method="GET" action="{{ config_url('search_box') }}">
<form class="level-item" method="GET" action="{{ url_for('search_box') }}">
<div class="field has-addons">
<div class="control">
<input class="input is-small" type="text" name="query" placeholder="Search..." aria-label="Search term or terms" />
<p class="help"><a href="{{ config_url('help') }}">Help</a> | <a href="{{ config_url('search_advanced') }}">Advanced Search</a></p>
<p class="help"><a href="{{ url_for('help') }}">Help</a> | <a href="{{ url_for('search_advanced') }}">Advanced Search</a></p>
</div>
<div class="control">
<div class="select is-small">
Expand Down
37 changes: 1 addition & 36 deletions arxiv/base/tests/test_context_processors.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
"""Tests for :mod:`arxiv.base.context_processors`."""

from unittest import TestCase, mock
from unittest import TestCase
from flask import Flask

from arxiv.base.context_processors import config_url_builder
from arxiv.base.exceptions import ConfigurationError


class TestConfigURLBuilderContextProcessor(TestCase):
Expand All @@ -18,37 +17,3 @@ def test_config_url_builder_returns_a_function(self):
"Should contain `config_url` key")
self.assertTrue(hasattr(extra_context['config_url'], '__call__'),
"Value for ``config_url`` should be callable.")

def test_config_url_key_exists(self):
"""config_url() is called for an URL that is configured on the app."""
app = Flask('foo')
app.config['ARXIV_FOO_URL'] = 'https://foo.arxiv.org'
with app.app_context():
extra_context = config_url_builder()
config_url = extra_context['config_url']
url = config_url('foo')

self.assertEqual(url, app.config['ARXIV_FOO_URL'],
"Should return configured URL")

@mock.patch('arxiv.base.context_processors.config')
def test_config_url_key_not_on_current_app(self, mock_base_config):
"""config_url() is called for an URL that is not on the current app."""
mock_base_config.ARXIV_FOO_URL = 'https://bar.arxiv.org'
app = Flask('foo')
with app.app_context():
extra_context = config_url_builder()
config_url = extra_context['config_url']
url = config_url('foo')

self.assertEqual(url, 'https://bar.arxiv.org',
"Should return URL configured in arxiv base ")

def test_config_url_key_not_set_anywhere(self):
"""config_url() is called for an URL that is not configured."""
app = Flask('foo')
with app.app_context():
extra_context = config_url_builder()
config_url = extra_context['config_url']
with self.assertRaises(ConfigurationError):
config_url('foo')
91 changes: 91 additions & 0 deletions arxiv/base/tests/test_urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""Tests for :mod:`arxiv.base.urls`."""

from unittest import TestCase, mock
from flask import Flask
from arxiv.base.urls import config_url
from arxiv.base.exceptions import ConfigurationError


class TestConfigURL(TestCase):
"""Tests for :func:`arxiv.base.urls.config_url`."""

def test_config_url_key_exists(self):
"""config_url() is called for an URL that is configured on the app."""
app = Flask('foo')
app.config['EXTERNAL_URLS'] = {
'foo': 'https://foo.arxiv.org'
}
with app.app_context():
url = config_url('foo')

self.assertEqual(url, 'https://foo.arxiv.org',
"Should return configured URL")

@mock.patch('base.urls.config')
def test_config_url_key_not_on_current_app(self, mock_base_config):
"""config_url() is called for an URL that is not on the current app."""
mock_base_config.EXTERNAL_URLS = {}
mock_base_config.EXTERNAL_URLS['foo'] = 'https://bar.arxiv.org'
app = Flask('foo')
with app.app_context():
url = config_url('foo')

self.assertEqual(url, 'https://bar.arxiv.org',
"Should return URL configured in arxiv base")

@mock.patch('base.urls.config')
def test_config_url_key_not_set_anywhere(self, mock_base_config):
"""config_url() is called for an URL that is not configured."""
app = Flask('foo')
mock_base_config.EXTERNAL_URLS = {}
with app.app_context():
with self.assertRaises(ConfigurationError):
config_url('foo')

@mock.patch('base.urls.config')
def test_config_url_with_format_parameter(self, mock_base_config):
"""URL requires a parameter, which is provided."""
mock_base_config.EXTERNAL_URLS = {
'foo': 'https://bar.arxiv.org/{foo}'
}
app = Flask('foo')
with app.app_context():
url = config_url('foo', {'foo': 'bar'})

self.assertEqual(url, 'https://bar.arxiv.org/bar',
"Should return URL configured in arxiv base")

@mock.patch('base.urls.config')
def test_config_url_with_parameter_not_provided(self, mock_base_config):
"""URL requires a parameter, which is not provided."""
mock_base_config.EXTERNAL_URLS = {
'foo': 'https://bar.arxiv.org/{foo}'
}
app = Flask('foo')
with app.app_context():
with self.assertRaises(ValueError):
config_url('foo')

def test_config_url_with_get_param(self):
"""Request parameters are included."""
app = Flask('foo')
app.config['EXTERNAL_URLS'] = {
'foo': 'https://foo.arxiv.org'
}
with app.app_context():
url = config_url('foo', params={'baz': 'bat'})

self.assertEqual(url, 'https://foo.arxiv.org?baz=bat',
"Should return configured URL")

def test_config_url_with_extra_get_param(self):
"""Request parameters are included in addition to those present."""
app = Flask('foo')
app.config['EXTERNAL_URLS'] = {
'foo': 'https://foo.arxiv.org?yes=no'
}
with app.app_context():
url = config_url('foo', params={'baz': 'bat'})

self.assertEqual(url, 'https://foo.arxiv.org?baz=bat&yes=no',
"Should return configured URL")
Loading

0 comments on commit b046ad7

Please sign in to comment.