Skip to content
This repository has been archived by the owner on Jan 19, 2022. It is now read-only.

Commit

Permalink
Merge djmitche/build-relengapi:issue169 (PR #169)
Browse files Browse the repository at this point in the history
  • Loading branch information
djmitche committed Jan 13, 2015
2 parents 89dcb20 + 9daa482 commit 0c2dc9f
Show file tree
Hide file tree
Showing 10 changed files with 224 additions and 56 deletions.
14 changes: 14 additions & 0 deletions docs/development/@relengapi/blueprints.rst
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,20 @@ Any conflicts may identify fixes required for continued compatibility with the c
git pull skeleton master
Project Metadata
----------------

You can include metadata about your project in a file named ``setup.cfg`` in the same directory as ``setup.py``.
Include them in a ``[relengapi]`` section.
The currently supported keys are self explanatory, and shown below.

.. code-block:: none
[relengapi]
repository_of_record = https://git.mozilla.org/?p=build/relengapi.git
bug_report_url = https://github.com/mozilla/build-relengapi/issues
Other Useful Stuff
------------------

Expand Down
40 changes: 15 additions & 25 deletions relengapi/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import logging
import os
import pkg_resources
import relengapi
import wsme.types

Expand All @@ -16,6 +15,7 @@
from relengapi.lib import aws
from relengapi.lib import celery
from relengapi.lib import db
from relengapi.lib import introspection
from relengapi.lib import layout
from relengapi.lib import memcached
from relengapi.lib import monkeypatches
Expand All @@ -31,26 +31,6 @@

logger = logging.getLogger(__name__)

_blueprints = None


def get_blueprints():
# get blueprints from pkg_resources. We're careful to load all of the
# blueprints exactly once and before registering any of them, as this
# ensures everything is imported before any of the @bp.register-decorated
# methods are called
global _blueprints
if not _blueprints:
# TODO: warn if relengapi_blueprints is used
entry_points = (list(pkg_resources.iter_entry_points('relengapi_blueprints'))
+ list(pkg_resources.iter_entry_points('relengapi.blueprints')))
_blueprints = []
for ep in entry_points:
bp = ep.load()
bp.dist = ep.dist
_blueprints.append(bp)
return _blueprints


class BlueprintInfo(wsme.types.Base):

Expand All @@ -73,6 +53,10 @@ class DistributionInfo(wsme.types.Base):
#: Version of the distribution
version = unicode

# TODO: ref docs
#: Additional RelengAPI-specific metadata
relengapi_metadata = {unicode: unicode}


class VersionInfo(wsme.types.Base):

Expand All @@ -86,7 +70,7 @@ class VersionInfo(wsme.types.Base):


def create_app(cmdline=False, test_config=None):
blueprints = get_blueprints()
blueprints = introspection.get_blueprints()

app = Flask(__name__)
env_var = 'RELENGAPI_SETTINGS'
Expand Down Expand Up @@ -140,9 +124,15 @@ def root():
@api.apimethod(VersionInfo)
def versions():
dists = {}
for dist in pkg_resources.WorkingSet():
dists[dist.key] = DistributionInfo(project_name=dist.project_name,
version=dist.version)
for dist in introspection.get_distributions().itervalues():
try:
relengapi_metadata = dist.relengapi_metadata
except AttributeError:
relengapi_metadata = {}
dists[dist.key] = DistributionInfo(
project_name=dist.project_name,
version=dist.version,
relengapi_metadata=relengapi_metadata)
blueprints = {}
for bp in app.relengapi_blueprints.itervalues():
blueprints[bp.name] = BlueprintInfo(distribution=bp.dist.key,
Expand Down
1 change: 0 additions & 1 deletion relengapi/blueprints/auth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
bp = Blueprint('auth', __name__,
template_folder='templates',
static_folder='static')
bp.root_widget_template('auth_root_widget.html', priority=-100)


@bp.route("/")
Expand Down
13 changes: 0 additions & 13 deletions relengapi/blueprints/auth/templates/auth_root_widget.html

This file was deleted.

63 changes: 63 additions & 0 deletions relengapi/lib/introspection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import ConfigParser
import pkg_resources

_blueprints = None
_distributions = None


def _fetch():
# get blueprints, dists, and so on from pkg_resources.
#
# We're careful to load all of the blueprints exactly once and before
# registering any of them, as this ensures everything is imported before
# any of the @bp.register-decorated methods are called
global _blueprints
global _distributions

if not _distributions:
_distributions = {}
for dist in pkg_resources.WorkingSet():
dist.relengapi_metadata = {}
_distributions[dist.key] = dist

if not _blueprints:
_blueprints = []
entry_points = (list(pkg_resources.iter_entry_points('relengapi_blueprints'))
+ list(pkg_resources.iter_entry_points('relengapi.blueprints')))
for ep in entry_points:
bp = ep.load()
# make sure we have only one copy of each Distribution
bp.dist = _distributions[ep.dist.key]
if not bp.dist.relengapi_metadata:
relengapi_metadata = _get_relengapi_metadata(bp.dist)
if relengapi_metadata:
bp.dist.relengapi_metadata = relengapi_metadata
_blueprints.append(bp)


def _get_relengapi_metadata(dist):
req = dist.as_requirement()
try:
setup_cfg = pkg_resources.resource_stream(req, 'setup.cfg')
except Exception:
return {}
cfg = ConfigParser.RawConfigParser()
cfg.readfp(setup_cfg)
if not cfg.has_section('relengapi'):
return {}

return {o: cfg.get('relengapi', o) for o in cfg.options('relengapi')}


def get_blueprints():
_fetch()
return _blueprints


def get_distributions():
_fetch()
return _distributions
18 changes: 17 additions & 1 deletion relengapi/lib/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

from flask import current_app
from flask import request


class Layout(object):

Expand All @@ -10,7 +13,20 @@ def __init__(self, app):

@app.context_processor
def _context_processor():
return dict(layout_extra_head_content=self.extra_head_content)
if request.blueprint:
blueprint = current_app.blueprints[request.blueprint]
else:
blueprint = current_app.blueprints['base']
try:
relengapi_metadata = blueprint.dist.relengapi_metadata
except AttributeError:
base_dist = current_app.blueprints['base'].dist
relengapi_metadata = base_dist.relengapi_metadata
return {
'blueprint': blueprint,
'relengapi_metadata': relengapi_metadata,
'layout_extra_head_content': self.extra_head_content,
}

def add_head_content(self, content):
"""
Expand Down
10 changes: 10 additions & 0 deletions relengapi/static/relengapi.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,13 @@ body {
background: #FFF url('background.jpg') no-repeat fixed top left;
}

.navbar-about {
width: 300px;
padding: 0.5em;
}

.navbar-user-info {
width: 300px;
padding: 0.5em;
padding-bottom: 0;
}
88 changes: 72 additions & 16 deletions relengapi/templates/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,30 +41,86 @@
<nav class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<!-- the "hamburger" menu for when the viewport is too narrow --!>
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand hidden-xs" href="/">RelengAPI</a>
</div>
<div id="navbar">
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-expanded="false" href="#">About
<span class="caret"></span></a>
<ul class="dropdown-menu dropdown-menu-right navbar-about" role="menu">
<li role="presentation" class="dropdown-header">
RelengAPI
<li>
<a href="/docs/">Documentation</a>
</li>
{% if blueprint.dist.project_name == 'relengapi' %}
<li>
<a href="{{ relengapi_metadata['repository_of_record'] }}">Contribute!</a>
</li>
<li>
<a href="{{ relengapi_metadata['bug_report_url'] }}">Report a Bug!</a>
</li>
{% endif %}
</li>
{% if blueprint.dist.project_name != 'relengapi' %}
<li role="presentation" class="divider">
<li role="presentation" class="dropdown-header">
{{ blueprint.dist.project_name }} project /
{{ blueprint.name }} blueprint
</li>
{% if relengapi_metadata['repository_of_record'] %}
<li>
<a href="{{ relengapi_metadata['repository_of_record'] }}">Contribute!</a>
</li>
{% endif %}
{% if relengapi_metadata['bug_report_url'] %}
{% endif %}
{% if relengapi_metadata['bug_report_url'] %}
<li>
<a href="{{ relengapi_metadata['bug_report_url'] }}">Report a Bug!</a>
</li>
{% endif %}
{% endif %}
</ul>
</li>
<li>
<p class="navbar-text">
<a class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-expanded="false" href="#">
<span class="glyphicon glyphicon-user"></span>
<span class="caret"></span></a>
<ul class="dropdown-menu dropdown-menu-right navbar-user-info">
{% if current_user.is_authenticated() %}
Logged in as
<li class="text-center">Logged in as
{% if current_user.type == "human" %}
<span class="user_email">{{ current_user.authenticated_email }}</a></span>
{% else %}
{{ current_user.type }}
{% endif %}
</li>
<li class="text-center">
<a class="navbar-link" href="/auth/">Account details..</a>
</li>
{% if current_user.type == "human" %}
<span class="user_email"><a class="navbar-link" href="/auth/">{{ current_user.authenticated_email }}</a></span>
{% else %}
{{ current_user.type }}
<li class="text-center">
<button class="btn btn-danger" id="logout">Logout</button>
</li>
{% endif %}
{% else %}
Not logged in
{% endif %}
</p>
{% if current_user.is_authenticated() %}
{% if current_user.type == "human" %}
<button class="btn btn-default navbar-btn" id="logout">Logout</button>
{% endif %}
{% else %}
<button class="btn btn-default navbar-btn" id="login">Login</button>
{% endif %}
{% else %}
<li class="text-center">Not logged in</li>
<li class="text-center">
<button class="btn btn-primary" id="login">Login</button>
</li>
{% endif %}
</ul>
</li>
</ul>
</div>
Expand Down
29 changes: 29 additions & 0 deletions relengapi/tests/test_lib_introspection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

from nose.tools import eq_
from relengapi.lib import introspection


def test_get_blueprints():
blueprints = introspection.get_blueprints()
base_blueprint = [bp for bp in blueprints if bp.name == 'base'][0]
eq_(base_blueprint.dist.project_name, 'relengapi')


def test_get_distributions():
distributions = introspection.get_distributions()
eq_(distributions['relengapi'].project_name, 'relengapi')


def test_get_relengapi_metadata_this_dist():
distributions = introspection.get_distributions()
meta = introspection._get_relengapi_metadata(distributions['relengapi'])
assert 'repository_of_record' in meta


def test_get_relengapi_metadata_no_data():
distributions = introspection.get_distributions()
meta = introspection._get_relengapi_metadata(distributions['flask'])
eq_(meta, {})
4 changes: 4 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@
traverse-namespace = 1

where = .

[relengapi]
repository_of_record = https://git.mozilla.org/?p=build/relengapi.git
bug_report_url = https://github.com/mozilla/build-relengapi/issues

0 comments on commit 0c2dc9f

Please sign in to comment.