Permalink
Comparing changes
Open a pull request
- 8 commits
- 22 files changed
- 0 commit comments
- 3 contributors
Unified
Split
Showing
with
365 additions
and 320 deletions.
- +6 −0 development.ini
- +0 −15 h/__init__.py
- 0 h/api/__init__.py
- +15 −0 h/api/auth.py
- +141 −0 h/api/db.py
- +9 −0 h/api/models.py
- +45 −0 h/api/resources.py
- +9 −0 h/api/subscribers.py
- 0 h/api/test/__init__.py
- +35 −36 h/{test/api_test.py → api/test/views_test.py}
- +16 −0 h/api/tweens.py
- +24 −171 h/{api.py → api/views.py}
- +29 −29 h/app.py
- +2 −2 h/conftest.py
- +0 −12 h/resources.py
- +1 −5 h/script.py
- +5 −5 h/static/scripts/vendor/annotator.document.js
- +1 −1 h/streamer.py
- +2 −8 h/subscribers.py
- +21 −22 h/test/app_test.py
- +0 −13 h/tweens.py
- +4 −1 setup.py
| @@ -2,6 +2,12 @@ | ||
| pipeline: h | ||
| [app:api] | ||
| use: egg:h#api | ||
| basemodel.should_create_all: True | ||
| [app:h] | ||
| use: egg:h | ||
| @@ -1,19 +1,4 @@ | ||
| # -*- coding: utf-8 -*- | ||
| """Initialize configuration.""" | ||
| from .app import main | ||
| from ._version import get_versions | ||
| __version__ = get_versions()['version'] | ||
| del get_versions | ||
| __all__ = ['main'] | ||
| def includeme(config): | ||
| """Include config sections and setup Jinja.""" | ||
| config.include('.views') | ||
| config.include('pyramid_jinja2') | ||
| config.add_jinja2_renderer('.js') | ||
| config.add_jinja2_renderer('.txt') | ||
| config.add_jinja2_renderer('.html') |
No changes.
| @@ -0,0 +1,15 @@ | ||
| # -*- coding: utf-8 -*- | ||
| from annotator.auth import Consumer, User | ||
| def get_user(request): | ||
| """Create a User object for annotator-store.""" | ||
| userid = request.unauthenticated_userid | ||
| if userid is not None: | ||
| for principal in request.effective_principals: | ||
| if principal.startswith('consumer:'): | ||
| key = principal[9:] | ||
| consumer = Consumer(key) | ||
| return User(userid, consumer, False) | ||
| return None |
| @@ -0,0 +1,141 @@ | ||
| # -*- coding: utf-8 -*- | ||
| import logging | ||
| import time | ||
| from annotator import es | ||
| from elasticsearch import exceptions as elasticsearch_exceptions | ||
| from pyramid.settings import asbool | ||
| from .models import Annotation | ||
| from .models import Document | ||
| log = logging.getLogger(__name__) | ||
| def store_from_settings(settings): | ||
| """Configure the Elasticsearch wrapper provided by annotator-store.""" | ||
| if 'es.host' in settings: | ||
| es.host = settings['es.host'] | ||
| if 'es.index' in settings: | ||
| es.index = settings['es.index'] | ||
| if 'es.compatibility' in settings: | ||
| es.compatibility_mode = settings['es.compatibility'] | ||
| # We want search results to be filtered according to their | ||
| # read-permissions, which is done in the store itself. | ||
| es.authorization_enabled = True | ||
| return es | ||
| def _ensure_es_plugins(es_conn): | ||
| """Ensure that the ICU analysis plugin is installed for ES.""" | ||
| # Pylint issue #258: https://bitbucket.org/logilab/pylint/issue/258 | ||
| # | ||
| # pylint: disable=unexpected-keyword-arg | ||
| names = [x.strip() for x in es_conn.cat.plugins(h='component').split('\n')] | ||
| if 'analysis-icu' not in names: | ||
| message = ("ICU Analysis plugin is not installed for ElasticSearch\n" | ||
| " See the installation instructions for more details:\n" | ||
| " https://github.com/hypothesis/h/blob/master/" | ||
| "INSTALL.rst#installing") | ||
| raise RuntimeError(message) | ||
| def create_db(): | ||
| """Create the ElasticSearch index for Annotations and Documents.""" | ||
| # Check for required plugin(s) | ||
| _ensure_es_plugins(es.conn) | ||
| models = [Annotation, Document] | ||
| mappings = {} | ||
| analysis = {} | ||
| # Collect the mappings and analysis settings | ||
| for model in models: | ||
| mappings.update(model.get_mapping()) | ||
| for section, items in model.get_analysis().items(): | ||
| existing_items = analysis.setdefault(section, {}) | ||
| for name in items: | ||
| if name in existing_items: | ||
| fmt = "Duplicate definition of 'index.analysis.{}.{}'." | ||
| msg = fmt.format(section, name) | ||
| raise RuntimeError(msg) | ||
| existing_items.update(items) | ||
| # Create the index | ||
| try: | ||
| # Pylint issue #258: https://bitbucket.org/logilab/pylint/issue/258 | ||
| # | ||
| # pylint: disable=unexpected-keyword-arg | ||
| response = es.conn.indices.create(es.index, ignore=400, body={ | ||
| 'mappings': mappings, | ||
| 'settings': {'analysis': analysis}, | ||
| }) | ||
| except elasticsearch_exceptions.ConnectionError as e: | ||
| msg = ('Can not access ElasticSearch at {0}! ' | ||
| 'Check to ensure it is running.').format(es.host) | ||
| raise elasticsearch_exceptions.ConnectionError('N/A', msg, e) | ||
| # Bad request (400) is ignored above, to prevent warnings in the log, but | ||
| # the failure could be for reasons other than that the index exists. If so, | ||
| # raise the error here. | ||
| if 'error' in response and 'IndexAlreadyExists' not in response['error']: | ||
| raise elasticsearch_exceptions.RequestError(400, response['error']) | ||
| # Update analysis settings | ||
| settings = es.conn.indices.get_settings(index=es.index) | ||
| existing = settings[es.index]['settings']['index'].get('analysis', {}) | ||
| if existing != analysis: | ||
| try: | ||
| es.conn.indices.close(index=es.index) | ||
| es.conn.indices.put_settings(index=es.index, body={ | ||
| 'analysis': analysis | ||
| }) | ||
| finally: | ||
| es.conn.indices.open(index=es.index) | ||
| # Update mappings | ||
| try: | ||
| for doc_type, body in mappings.items(): | ||
| es.conn.indices.put_mapping( | ||
| index=es.index, | ||
| doc_type=doc_type, | ||
| body=body | ||
| ) | ||
| except elasticsearch_exceptions.RequestError as e: | ||
| if e.error.startswith('MergeMappingException'): | ||
| date = time.strftime('%Y-%m-%d') | ||
| message = ("Elasticsearch index mapping is incorrect! Please " | ||
| "reindex it. For example, run: " | ||
| "./bin/hypothesis reindex {0} {1} {1}-{2}" | ||
| .format('yourconfig.ini', es.index, date) | ||
| ) | ||
| log.critical(message) | ||
| raise RuntimeError(message) | ||
| raise | ||
| def delete_db(): | ||
| """Delete the Annotation and Document databases.""" | ||
| Annotation.drop_all() | ||
| Document.drop_all() | ||
| def includeme(config): | ||
| """Configure and possibly initialize ElasticSearch and its models.""" | ||
| registry = config.registry | ||
| settings = registry.settings | ||
| # Configure ElasticSearch | ||
| store_from_settings(settings) | ||
| # Maybe initialize the models | ||
| if asbool(settings.get('basemodel.should_drop_all', False)): | ||
| delete_db() | ||
| if asbool(settings.get('basemodel.should_create_all', False)): | ||
| create_db() |
| @@ -0,0 +1,9 @@ | ||
| # -*- coding: utf-8 -*- | ||
| from ..models import Annotation | ||
| from ..models import Document | ||
| __all__ = [ | ||
| 'Annotation', | ||
| 'Document' | ||
| ] |
| @@ -0,0 +1,45 @@ | ||
| # -*- coding: utf-8 -*- | ||
| from pyramid.security import Allow, Authenticated, ALL_PERMISSIONS | ||
| from .models import Annotation | ||
| class Resource(dict): | ||
| """ | ||
| Resource is an entry in the traversal tree. | ||
| """ | ||
| __name__ = None | ||
| __parent__ = None | ||
| def __init__(self, request): | ||
| pass | ||
| def add(self, name, obj): | ||
| obj.__name__ = name | ||
| obj.__parent__ = self | ||
| self[name] = obj | ||
| class Annotations(object): | ||
| """ | ||
| Annotations is a container resource that exposes annotations as its | ||
| children in the tree. | ||
| """ | ||
| def __getitem__(self, key): | ||
| instance = Annotation.fetch(key) | ||
| if instance is None: | ||
| raise KeyError(key) | ||
| instance.__name__ = key | ||
| instance.__parent__ = self | ||
| return instance | ||
| class Root(Resource): | ||
| __acl__ = [ | ||
| (Allow, 'group:admin', ALL_PERMISSIONS), | ||
| (Allow, Authenticated, 'create'), | ||
| ] | ||
| def __init__(self, request): | ||
| self.add('annotations', Annotations()) |
| @@ -0,0 +1,9 @@ | ||
| # -*- coding: utf-8 -*- | ||
| def set_user_from_oauth(event): | ||
| """A subscriber that checks requests for OAuth credentials and sets the | ||
| 'REMOTE_USER' environment key to the authorized user (or ``None``).""" | ||
| request = event.request | ||
| request.verify_request() | ||
| request.environ['REMOTE_USER'] = getattr(request, 'user', None) |
No changes.
Oops, something went wrong.