<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import with_statement

import unittest
import cookielib

try:
    import omnijson as json
except ImportError:
    import json

import requests

HTTPBIN_URL = 'http://httpbin.org/'
HTTPSBIN_URL = 'https://httpbin.ep.io/'

# HTTPBIN_URL = 'http://staging.httpbin.org/'
# HTTPSBIN_URL = 'https://httpbin-staging.ep.io/'

def httpbin(*suffix):
    """Returns url for HTTPBIN resource."""

    return HTTPBIN_URL + '/'.join(suffix)

def httpsbin(*suffix):
    """Returns url for HTTPSBIN resource."""

    return HTTPSBIN_URL + '/'.join(suffix)

class RequestsTestSuite(unittest.TestCase):
    """Requests test cases."""

    def setUp(self):
        pass

    def tearDown(self):
        """Teardown."""
        pass

    def test_invalid_url(self):
        self.assertRaises(ValueError, requests.get, 'hiwpefhipowhefopw')

    def test_HTTP_200_OK_GET(self):
        r = requests.get(httpbin('/'))
        self.assertEqual(r.status_code, 200)

    def test_HTTPS_200_OK_GET(self):
        r = requests.get(httpsbin('/'))
        self.assertEqual(r.status_code, 200)

    def test_HTTP_200_OK_GET_WITH_PARAMS(self):
        heads = {'User-agent': 'Mozilla/5.0'}

        r = requests.get(httpbin('user-agent'), headers=heads)

        assert heads['User-agent'] in r.content
        self.assertEqual(r.status_code, 200)

    def test_HTTP_200_OK_GET_WITH_MIXED_PARAMS(self):
        heads = {'User-agent': 'Mozilla/5.0'}

        r = requests.get(httpbin('get') + '?test=true', params={'q': 'test'}, headers=heads)
        self.assertEqual(r.status_code, 200)

    def test_user_agent_transfers(self):
        """Issue XX"""

        heads = {
            'User-agent':
                'Mozilla/5.0 (github.com/kennethreitz/requests)'
        }

        r = requests.get(httpbin('user-agent'), headers=heads);
        self.assertTrue(heads['User-agent'] in r.content)

        heads = {
            'user-agent':
                'Mozilla/5.0 (github.com/kennethreitz/requests)'
        }

        r = requests.get(httpbin('user-agent'), headers=heads);
        self.assertTrue(heads['user-agent'] in r.content)

    def test_HTTP_200_OK_HEAD(self):
        r = requests.head(httpbin('/'))
        self.assertEqual(r.status_code, 200)

    def test_HTTPS_200_OK_HEAD(self):
        r = requests.head(httpsbin('/'))
        self.assertEqual(r.status_code, 200)

    def test_HTTP_200_OK_PUT(self):
        r = requests.put(httpbin('put'))
        self.assertEqual(r.status_code, 200)

    def test_HTTPS_200_OK_PUT(self):
        r = requests.put(httpsbin('put'))
        self.assertEqual(r.status_code, 200)

    def test_HTTP_200_OK_PATCH(self):
        r = requests.patch(httpbin('patch'))
        self.assertEqual(r.status_code, 200)

    def test_HTTPS_200_OK_PATCH(self):
        r = requests.patch(httpsbin('patch'))
        self.assertEqual(r.status_code, 200)

    def test_AUTH_HTTPS_200_OK_GET(self):
        auth = ('user', 'pass')
        url = httpsbin('basic-auth', 'user', 'pass')
        r = requests.get(url, auth=auth)

        self.assertEqual(r.status_code, 200)

        r = requests.get(url)
        self.assertEqual(r.status_code, 200)

        # reset auto authentication
        requests.auth_manager.empty()

    def test_POSTBIN_GET_POST_FILES(self):
        url = httpbin('post')
        post = requests.post(url).raise_for_status()

        post = requests.post(url, data={'some': 'data'})
        self.assertEqual(post.status_code, 200)

        post2 = requests.post(url, files={'some': open('test_requests.py')})
        self.assertEqual(post2.status_code, 200)

        post3 = requests.post(url, data='[{"some": "json"}]')
        self.assertEqual(post3.status_code, 200)

    def test_POSTBIN_GET_POST_FILES_WITH_PARAMS(self):

        url = httpbin('post')
        post = requests.post(url, files={'some': open('test_requests.py')}, data={'some': 'data'})
        self.assertEqual(post.status_code, 200)

    def test_POSTBIN_GET_POST_FILES_WITH_HEADERS(self):

        url = httpbin('post')

        post2 = requests.post(url, files={'some': open('test_requests.py')},
            headers = {'User-Agent': 'requests-tests'})

        self.assertEqual(post2.status_code, 200)

    def test_nonzero_evaluation(self):
        r = requests.get(httpbin('status', '500'))
        self.assertEqual(bool(r), False)

        r = requests.get(httpbin('/'))
        self.assertEqual(bool(r), True)

    def test_request_ok_set(self):
        r = requests.get(httpbin('status', '404'))
        self.assertEqual(r.ok, False)

    def test_status_raising(self):
        r = requests.get(httpbin('status', '404'))
        self.assertRaises(requests.HTTPError, r.raise_for_status)

        r = requests.get(httpbin('status', '200'))
        self.assertFalse(r.error)
        r.raise_for_status()

    def test_cookie_jar(self):

        jar = cookielib.CookieJar()
        self.assertFalse(jar)

        url = httpbin('cookies', 'set', 'requests_cookie', 'awesome')
        r = requests.get(url, cookies=jar)
        self.assertTrue(jar)

        cookie_found = False
        for cookie in jar:
            if cookie.name == 'requests_cookie':
                self.assertEquals(cookie.value, 'awesome')
                cookie_found = True
        self.assertTrue(cookie_found)

        r = requests.get(httpbin('cookies'), cookies=jar)
        self.assertTrue('awesome' in r.content)

    def test_decompress_gzip(self):

        r = requests.get(httpbin('gzip'))
        r.content.decode('ascii')

    def test_autoauth(self):

        http_auth = ('user', 'pass')
        requests.auth_manager.add_auth('httpbin.org', http_auth)

        r = requests.get(httpbin('basic-auth', 'user', 'pass'))
        self.assertEquals(r.status_code, 200)

    def test_unicode_get(self):

        url = httpbin('/')

        requests.get(url, params={'foo': u'føø'})
        requests.get(url, params={u'føø': u'føø'})
        requests.get(url, params={'føø': 'føø'})
        requests.get(url, params={'foo': u'foo'})
        requests.get(httpbin('ø'), params={'foo': u'foo'})

    def test_httpauth_recursion(self):
        http_auth = ('user', 'BADpass')

        r = requests.get(httpbin('basic-auth', 'user', 'pass'), auth=http_auth)
        self.assertEquals(r.status_code, 401)

    def test_settings(self):

        def test():
            r = requests.get(httpbin(''))
            r.raise_for_status()

        with requests.settings(timeout=0.0000001):
            self.assertRaises(requests.Timeout, test)

        with requests.settings(timeout=100):
            requests.get(httpbin(''))

    def test_urlencoded_post_data(self):
        r = requests.post(httpbin('post'), data=dict(test='fooaowpeuf'))
        self.assertEquals(r.status_code, 200)
        self.assertEquals(r.headers['content-type'], 'application/json')
        self.assertEquals(r.url, httpbin('post'))
        rbody = json.loads(r.content)
        self.assertEquals(rbody.get('form'), dict(test='fooaowpeuf'))
        self.assertEquals(rbody.get('data'), '')

    def test_nonurlencoded_post_data(self):
        r = requests.post(httpbin('post'), data='fooaowpeuf')
        self.assertEquals(r.status_code, 200)
        self.assertEquals(r.headers['content-type'], 'application/json')
        self.assertEquals(r.url, httpbin('post'))
        rbody = json.loads(r.content)
        # Body wasn't valid url encoded data, so the server returns None as
        # "form" and the raw body as "data".
        self.assertEquals(rbody.get('form'), None)
        self.assertEquals(rbody.get('data'), 'fooaowpeuf')

    def test_urlencoded_post_querystring(self):
        r = requests.post(httpbin('post'), params=dict(test='fooaowpeuf'))
        self.assertEquals(r.status_code, 200)
        self.assertEquals(r.headers['content-type'], 'application/json')
        self.assertEquals(r.url, httpbin('post?test=fooaowpeuf'))
        rbody = json.loads(r.content)
        self.assertEquals(rbody.get('form'), {}) # No form supplied
        self.assertEquals(rbody.get('data'), '')

    def test_nonurlencoded_post_querystring(self):
        r = requests.post(httpbin('post'), params='fooaowpeuf')
        self.assertEquals(r.status_code, 200)
        self.assertEquals(r.headers['content-type'], 'application/json')
        self.assertEquals(r.url, httpbin('post?fooaowpeuf'))
        rbody = json.loads(r.content)
        self.assertEquals(rbody.get('form'), {}) # No form supplied
        self.assertEquals(rbody.get('data'), '')

    def test_urlencoded_post_query_and_data(self):
        r = requests.post(httpbin('post'), params=dict(test='fooaowpeuf'),
                          data=dict(test2="foobar"))
        self.assertEquals(r.status_code, 200)
        self.assertEquals(r.headers['content-type'], 'application/json')
        self.assertEquals(r.url, httpbin('post?test=fooaowpeuf'))
        rbody = json.loads(r.content)
        self.assertEquals(rbody.get('form'), dict(test2='foobar'))
        self.assertEquals(rbody.get('data'), '')

    def test_nonurlencoded_post_query_and_data(self):
        r = requests.post(httpbin('post'), params='fooaowpeuf',
                          data="foobar")
        self.assertEquals(r.status_code, 200)
        self.assertEquals(r.headers['content-type'], 'application/json')
        self.assertEquals(r.url, httpbin('post?fooaowpeuf'))
        rbody = json.loads(r.content)
        self.assertEquals(rbody.get('form'), None)
        self.assertEquals(rbody.get('data'), 'foobar')

    def test_idna(self):
        r = requests.get(u'http://➡.ws/httpbin')
        assert 'tinyarrows.com' in r.url

    def test_urlencoded_get_query_multivalued_param(self):
        r = requests.get(httpbin('get'), params=dict(test=['foo','baz']))
        self.assertEquals(r.status_code, 200)
        self.assertEquals(r.url, httpbin('get?test=foo&test=baz'))

    def test_urlencoded_post_querystring_multivalued(self):
        r = requests.post(httpbin('post'), params=dict(test=['foo','baz']))
        self.assertEquals(r.status_code, 200)
        self.assertEquals(r.headers['content-type'], 'application/json')
        self.assertEquals(r.url, httpbin('post?test=foo&test=baz'))
        rbody = json.loads(r.content)
        self.assertEquals(rbody.get('form'), {}) # No form supplied
        self.assertEquals(rbody.get('data'), '')

    def test_urlencoded_post_query_multivalued_and_data(self):
        r = requests.post(httpbin('post'), params=dict(test=['foo','baz']),
                          data=dict(test2="foobar",test3=['foo','baz']))
        self.assertEquals(r.status_code, 200)
        self.assertEquals(r.headers['content-type'], 'application/json')
        self.assertEquals(r.url, httpbin('post?test=foo&test=baz'))
        rbody = json.loads(r.content)
        self.assertEquals(rbody.get('form'), dict(test2='foobar',test3='foo'))
        self.assertEquals(rbody.get('data'), '')

    def test_redirect_history(self):
        r = requests.get(httpbin('redirect', '3'))
        self.assertEquals(r.status_code, 200)
        self.assertEquals(len(r.history), 3)

        r = requests.get(httpsbin('redirect', '3'))
        self.assertEquals(r.status_code, 200)
        self.assertEquals(len(r.history), 3)

if __name__ == '__main__':
    unittest.main()
<commit_msg>test fix<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import with_statement

import unittest
import cookielib

try:
    import omnijson as json
except ImportError:
    import json

import requests

HTTPBIN_URL = 'http://httpbin.org/'
HTTPSBIN_URL = 'https://httpbin.ep.io/'

# HTTPBIN_URL = 'http://staging.httpbin.org/'
# HTTPSBIN_URL = 'https://httpbin-staging.ep.io/'

def httpbin(*suffix):
    """Returns url for HTTPBIN resource."""

    return HTTPBIN_URL + '/'.join(suffix)

def httpsbin(*suffix):
    """Returns url for HTTPSBIN resource."""

    return HTTPSBIN_URL + '/'.join(suffix)

class RequestsTestSuite(unittest.TestCase):
    """Requests test cases."""

    def setUp(self):
        pass

    def tearDown(self):
        """Teardown."""
        pass

    def test_invalid_url(self):
        self.assertRaises(ValueError, requests.get, 'hiwpefhipowhefopw')

    def test_HTTP_200_OK_GET(self):
        r = requests.get(httpbin('/'))
        self.assertEqual(r.status_code, 200)

    def test_HTTPS_200_OK_GET(self):
        r = requests.get(httpsbin('/'))
        self.assertEqual(r.status_code, 200)

    def test_HTTP_200_OK_GET_WITH_PARAMS(self):
        heads = {'User-agent': 'Mozilla/5.0'}

        r = requests.get(httpbin('user-agent'), headers=heads)

        assert heads['User-agent'] in r.content
        self.assertEqual(r.status_code, 200)

    def test_HTTP_200_OK_GET_WITH_MIXED_PARAMS(self):
        heads = {'User-agent': 'Mozilla/5.0'}

        r = requests.get(httpbin('get') + '?test=true', params={'q': 'test'}, headers=heads)
        self.assertEqual(r.status_code, 200)

    def test_user_agent_transfers(self):
        """Issue XX"""

        heads = {
            'User-agent':
                'Mozilla/5.0 (github.com/kennethreitz/requests)'
        }

        r = requests.get(httpbin('user-agent'), headers=heads);
        self.assertTrue(heads['User-agent'] in r.content)

        heads = {
            'user-agent':
                'Mozilla/5.0 (github.com/kennethreitz/requests)'
        }

        r = requests.get(httpbin('user-agent'), headers=heads);
        self.assertTrue(heads['user-agent'] in r.content)

    def test_HTTP_200_OK_HEAD(self):
        r = requests.head(httpbin('/'))
        self.assertEqual(r.status_code, 200)

    def test_HTTPS_200_OK_HEAD(self):
        r = requests.head(httpsbin('/'))
        self.assertEqual(r.status_code, 200)

    def test_HTTP_200_OK_PUT(self):
        r = requests.put(httpbin('put'))
        self.assertEqual(r.status_code, 200)

    def test_HTTPS_200_OK_PUT(self):
        r = requests.put(httpsbin('put'))
        self.assertEqual(r.status_code, 200)

    def test_HTTP_200_OK_PATCH(self):
        r = requests.patch(httpbin('patch'))
        self.assertEqual(r.status_code, 200)

    def test_HTTPS_200_OK_PATCH(self):
        r = requests.patch(httpsbin('patch'))
        self.assertEqual(r.status_code, 200)

    def test_AUTH_HTTPS_200_OK_GET(self):
        auth = ('user', 'pass')
        url = httpsbin('basic-auth', 'user', 'pass')
        r = requests.get(url, auth=auth)

        self.assertEqual(r.status_code, 200)

        r = requests.get(url)
        self.assertEqual(r.status_code, 200)

        # reset auto authentication
        requests.auth_manager.empty()

    def test_POSTBIN_GET_POST_FILES(self):
        url = httpbin('post')
        post = requests.post(url).raise_for_status()

        post = requests.post(url, data={'some': 'data'})
        self.assertEqual(post.status_code, 200)

        post2 = requests.post(url, files={'some': open('test_requests.py')})
        self.assertEqual(post2.status_code, 200)

        post3 = requests.post(url, data='[{"some": "json"}]')
        self.assertEqual(post3.status_code, 200)

    def test_POSTBIN_GET_POST_FILES_WITH_PARAMS(self):

        url = httpbin('post')
        post = requests.post(url, files={'some': open('test_requests.py')}, data={'some': 'data'})
        self.assertEqual(post.status_code, 200)

    def test_POSTBIN_GET_POST_FILES_WITH_HEADERS(self):

        url = httpbin('post')

        post2 = requests.post(url, files={'some': open('test_requests.py')},
            headers = {'User-Agent': 'requests-tests'})

        self.assertEqual(post2.status_code, 200)

    def test_nonzero_evaluation(self):
        r = requests.get(httpbin('status', '500'))
        self.assertEqual(bool(r), False)

        r = requests.get(httpbin('/'))
        self.assertEqual(bool(r), True)

    def test_request_ok_set(self):
        r = requests.get(httpbin('status', '404'))
        self.assertEqual(r.ok, False)

    def test_status_raising(self):
        r = requests.get(httpbin('status', '404'))
        self.assertRaises(requests.HTTPError, r.raise_for_status)

        r = requests.get(httpbin('status', '200'))
        self.assertFalse(r.error)
        r.raise_for_status()

    def test_cookie_jar(self):

        jar = cookielib.CookieJar()
        self.assertFalse(jar)

        url = httpbin('cookies', 'set', 'requests_cookie', 'awesome')
        r = requests.get(url, cookies=jar)
        self.assertTrue(jar)

        cookie_found = False
        for cookie in jar:
            if cookie.name == 'requests_cookie':
                self.assertEquals(cookie.value, 'awesome')
                cookie_found = True
        self.assertTrue(cookie_found)

        r = requests.get(httpbin('cookies'), cookies=jar)
        self.assertTrue('awesome' in r.content)

    def test_decompress_gzip(self):

        r = requests.get(httpbin('gzip'))
        r.content.decode('ascii')

    def test_autoauth(self):

        http_auth = ('user', 'pass')
        requests.auth_manager.add_auth('httpbin.org', http_auth)

        r = requests.get(httpbin('basic-auth', 'user', 'pass'))
        self.assertEquals(r.status_code, 200)

    def test_unicode_get(self):

        url = httpbin('/')

        requests.get(url, params={'foo': u'føø'})
        requests.get(url, params={u'føø': u'føø'})
        requests.get(url, params={'føø': 'føø'})
        requests.get(url, params={'foo': u'foo'})
        requests.get(httpbin('ø'), params={'foo': u'foo'})

    def test_httpauth_recursion(self):
        http_auth = ('user', 'BADpass')

        r = requests.get(httpbin('basic-auth', 'user', 'pass'), auth=http_auth)
        self.assertEquals(r.status_code, 401)

    def test_settings(self):

        def test():
            r = requests.get(httpbin(''))
            r.raise_for_status()

        with requests.settings(timeout=0.0000001):
            self.assertRaises(requests.Timeout, test)

        with requests.settings(timeout=100):
            requests.get(httpbin(''))

    def test_urlencoded_post_data(self):
        r = requests.post(httpbin('post'), data=dict(test='fooaowpeuf'))
        self.assertEquals(r.status_code, 200)
        self.assertEquals(r.headers['content-type'], 'application/json')
        self.assertEquals(r.url, httpbin('post'))
        rbody = json.loads(r.content)
        self.assertEquals(rbody.get('form'), dict(test='fooaowpeuf'))
        self.assertEquals(rbody.get('data'), '')

    def test_nonurlencoded_post_data(self):
        r = requests.post(httpbin('post'), data='fooaowpeuf')
        self.assertEquals(r.status_code, 200)
        self.assertEquals(r.headers['content-type'], 'application/json')
        self.assertEquals(r.url, httpbin('post'))
        rbody = json.loads(r.content)
        # Body wasn't valid url encoded data, so the server returns None as
        # "form" and the raw body as "data".
        self.assertEquals(rbody.get('form'), None)
        self.assertEquals(rbody.get('data'), 'fooaowpeuf')

    def test_urlencoded_post_querystring(self):
        r = requests.post(httpbin('post'), params=dict(test='fooaowpeuf'))
        self.assertEquals(r.status_code, 200)
        self.assertEquals(r.headers['content-type'], 'application/json')
        self.assertEquals(r.url, httpbin('post?test=fooaowpeuf'))
        rbody = json.loads(r.content)
        self.assertEquals(rbody.get('form'), {}) # No form supplied
        self.assertEquals(rbody.get('data'), '')

    def test_nonurlencoded_post_querystring(self):
        r = requests.post(httpbin('post'), params='fooaowpeuf')
        self.assertEquals(r.status_code, 200)
        self.assertEquals(r.headers['content-type'], 'application/json')
        self.assertEquals(r.url, httpbin('post?fooaowpeuf'))
        rbody = json.loads(r.content)
        self.assertEquals(rbody.get('form'), {}) # No form supplied
        self.assertEquals(rbody.get('data'), '')

    def test_urlencoded_post_query_and_data(self):
        r = requests.post(httpbin('post'), params=dict(test='fooaowpeuf'),
                          data=dict(test2="foobar"))
        self.assertEquals(r.status_code, 200)
        self.assertEquals(r.headers['content-type'], 'application/json')
        self.assertEquals(r.url, httpbin('post?test=fooaowpeuf'))
        rbody = json.loads(r.content)
        self.assertEquals(rbody.get('form'), dict(test2='foobar'))
        self.assertEquals(rbody.get('data'), '')

    def test_nonurlencoded_post_query_and_data(self):
        r = requests.post(httpbin('post'), params='fooaowpeuf',
                          data="foobar")
        self.assertEquals(r.status_code, 200)
        self.assertEquals(r.headers['content-type'], 'application/json')
        self.assertEquals(r.url, httpbin('post?fooaowpeuf'))
        rbody = json.loads(r.content)
        self.assertEquals(rbody.get('form'), None)
        self.assertEquals(rbody.get('data'), 'foobar')

    def test_idna(self):
        r = requests.get(u'http://➡.ws/httpbin')
        assert 'httpbin' in r.url

    def test_urlencoded_get_query_multivalued_param(self):
        r = requests.get(httpbin('get'), params=dict(test=['foo','baz']))
        self.assertEquals(r.status_code, 200)
        self.assertEquals(r.url, httpbin('get?test=foo&test=baz'))

    def test_urlencoded_post_querystring_multivalued(self):
        r = requests.post(httpbin('post'), params=dict(test=['foo','baz']))
        self.assertEquals(r.status_code, 200)
        self.assertEquals(r.headers['content-type'], 'application/json')
        self.assertEquals(r.url, httpbin('post?test=foo&test=baz'))
        rbody = json.loads(r.content)
        self.assertEquals(rbody.get('form'), {}) # No form supplied
        self.assertEquals(rbody.get('data'), '')

    def test_urlencoded_post_query_multivalued_and_data(self):
        r = requests.post(httpbin('post'), params=dict(test=['foo','baz']),
                          data=dict(test2="foobar",test3=['foo','baz']))
        self.assertEquals(r.status_code, 200)
        self.assertEquals(r.headers['content-type'], 'application/json')
        self.assertEquals(r.url, httpbin('post?test=foo&test=baz'))
        rbody = json.loads(r.content)
        self.assertEquals(rbody.get('form'), dict(test2='foobar',test3='foo'))
        self.assertEquals(rbody.get('data'), '')

    def test_redirect_history(self):
        r = requests.get(httpbin('redirect', '3'))
        self.assertEquals(r.status_code, 200)
        self.assertEquals(len(r.history), 3)

        r = requests.get(httpsbin('redirect', '3'))
        self.assertEquals(r.status_code, 200)
        self.assertEquals(len(r.history), 3)

if __name__ == '__main__':
    unittest.main()
<|endoftext|>


<commit_before>import unittest, responses
from resource import Resource, API

class FooAPI(API):
	base_url = "https://api.foo.com"
	headers = {"foo": "bar"}

class SimpleResource(Resource):
	resource_path = '/api/v1/foo/{id}'
	api_class=FooAPI

class BarResource(Resource):
	resource_path = '/api/v1/{user}/{id}'
	api_class=FooAPI

class ResourceTestCase(unittest.TestCase):

	def setUp(self):
		self.resource = BarResource()

	def test_init_resource(self):
		pass

	def test_init_resource_sets_base_url_from_api_class(self):
		assert self.resource.base_url == 'https://api.foo.com'

	def test_init_resource_sets_headers_from_api_class(self):
		assert self.resource.headers.get('foo') == 'bar'

	def test_extract_params(self):

		params = self.resource.extract_params('/test/{user}/{id}')
		assert params == ['user', 'id']

	def test_get_url(self):
		test_urls = [
			('/repo/{user}/{id}', {"user": "test", "id": "123"}, '/repo/test/123',),
			('/repo/{user}/{id}', {"user": "test"}, '/repo/test/',),
			('/repo/{id}', {"id": "123"}, '/repo/123',),
		]
		for url, keys, expected_path in test_urls:
			actual_url = self.resource.get_url(url, keys)
			expected_url = "{}{}" . format ("https://api.foo.com", expected_path)
			assert actual_url == expected_url, \
				'Expected {} with keys: {} to be: {}, but it was: {}' \
					. format (url, keys, expected_url, actual_url)

class ResourceGetTestCase(unittest.TestCase):

	def setUp(self):
		self.resource = BarResource()

	@responses.activate
	def test_get(self):

		responses.add(responses.GET, 'https://api.foo.com/api/v1/test/123',
                  body='{"some": "result"}', status=200,
                  content_type='application/json')

		keys = {"user":"test", "id":123}
		result = self.resource.get(keys)

	@responses.activate
	def test_get_custom_headers(self):

		responses.add(responses.GET, 'https://api.foo.com/api/v1/test/123',
                  body='{"some": "result"}', status=200,
                  content_type='application/json')

		keys = {"user":"test", "id":123}
		result = self.resource.get(keys, headers= {"baz": "bus"})

		responses.calls[0].request.headers['foo'] == 'bar'
		responses.calls[0].request.headers['baz'] == 'bus'

	def test_result_populates_objects_list_if_top_level_result_is_list(self):
		result_json = [
			{"first_name": "John", "last_name": "Snow"},
			{"first_name": "Daenerys", "last_name": "Targaryen"},
			{"first_name": "Sansa", "last_name": "Start"},
			{"first_name": "Cersei", "last_name": "Lannnister"},
		]

		responses.add(responses.GET, 'https://api.foo.com/api/v1/test/123',
                  body='{"some": "result"}', status=200,
                  content_type='application/json')

class ResourceQueryTestCase(unittest.TestCase):

	def setUp(self):
		self.resource = BarResource()
		self.simple_resource = SimpleResource()

	@responses.activate
	def test_query(self):

		expected_url = "https://api.foo.com/api/v1/test/"
		responses.add(responses.GET, expected_url)
		qs = "?q=a%3D1%3Ab%3D2&s=something"
		keys = {'user': 'test'}
		params = {'q': 'a=1:b=2', 's': 'something'}
		result = self.resource.list(keys=keys, params=params)

		call = responses.calls[0]
		full_url = 'https://api.foo.com/api/v1/test/?q=a%3D1%3Ab%3D2&s=something'
		assert call.request.url == full_url, \
			"Expected {} to be {}" . format (call.request.url, full_url)

class ResourceCreateTestCase(unittest.TestCase):

	def test_create_simple_resource_with_data(self):
		pass

if __name__ == '__main__':
    unittest.main() 		<commit_msg>fix the tests<commit_after>import unittest, responses
from resource import Resource, API

class FooAPI(API):
	base_url = "https://api.foo.com"
	headers = {"foo": "bar"}

class SimpleResource(Resource):
	resource_path = '/api/v1/foo/{id}'
	api_class=FooAPI

class BarResource(Resource):
	resource_path = '/api/v1/{user}/{id}'
	api_class=FooAPI

class ResourceTestCase(unittest.TestCase):

	def setUp(self):
		self.resource = BarResource()

	def test_init_resource(self):
		pass

	def test_init_resource_sets_base_url_from_api_class(self):
		assert self.resource.base_url == 'https://api.foo.com'

	def test_init_resource_sets_headers_from_api_class(self):
		assert self.resource.headers.get('foo') == 'bar'

	def test_extract_params(self):

		params = self.resource.extract_params('/test/{user}/{id}')
		assert params == ['user', 'id']

	def test_get_url(self):
		test_urls = [
			('/repo/{user}/{id}', {"user": "test", "id": "123"}, '/repo/test/123',),
			('/repo/{user}/{id}', {"user": "test"}, '/repo/test/',),
			('/repo/{id}', {"id": "123"}, '/repo/123',),
		]
		for url, keys, expected_path in test_urls:
			actual_url = self.resource.get_url(url, keys)
			expected_url = "{}{}" . format ("https://api.foo.com", expected_path)
			assert actual_url == expected_url, \
				'Expected {} with keys: {} to be: {}, but it was: {}' \
					. format (url, keys, expected_url, actual_url)

class ResourceGetTestCase(unittest.TestCase):

	def setUp(self):
		self.resource = BarResource()

	@responses.activate
	def test_get(self):

		responses.add(responses.GET, 'https://api.foo.com/api/v1/test/123',
                  body='{"some": "result"}', status=200,
                  content_type='application/json')

		keys = {"user":"test", "id":123}
		result = self.resource.get(keys)

	@responses.activate
	def test_get_custom_headers(self):

		responses.add(responses.GET, 'https://api.foo.com/api/v1/test/123',
                  body='{"some": "result"}', status=200,
                  content_type='application/json')

		keys = {"user":"test", "id":123}
		result = self.resource.get(keys, headers= {"baz": "bus"})

		responses.calls[0].request.headers['foo'] == 'bar'
		responses.calls[0].request.headers['baz'] == 'bus'

	def test_result_populates_objects_list_if_top_level_result_is_list(self):
		result_json = [
			{"first_name": "John", "last_name": "Snow"},
			{"first_name": "Daenerys", "last_name": "Targaryen"},
			{"first_name": "Sansa", "last_name": "Start"},
			{"first_name": "Cersei", "last_name": "Lannnister"},
		]

		responses.add(responses.GET, 'https://api.foo.com/api/v1/test/123',
                  body='{"some": "result"}', status=200,
                  content_type='application/json')

class ResourceQueryTestCase(unittest.TestCase):

	def setUp(self):
		self.resource = BarResource()
		self.simple_resource = SimpleResource()

	@responses.activate
	def test_query(self):

		expected_url = "https://api.foo.com/api/v1/test/"
		responses.add(responses.GET, expected_url)
		qs = "?q=a%3D1%3Ab%3D2&s=something"
		keys = {'user': 'test'}
		params = {'q': 'a=1:b=2', 's': 'something'}
		result = self.resource.list(keys=keys, params=params)

		call = responses.calls[0]

		expected_params = [
			'q=a%3D1%3Ab%3D2',
			's=something'
		]
		for param in expected_params:
			assert param in call.request.url, \
				"Expected {} to be in {}" . format (param, call.request.url)

class ResourceCreateTestCase(unittest.TestCase):

	def test_create_simple_resource_with_data(self):
		pass

if __name__ == '__main__':
    unittest.main() 		<|endoftext|>


<commit_before>''' API calls with respect group projects'''
import json
from urllib import urlencode
from django.conf import settings
from lazy.lazy import lazy

from group_project_v2.utils import build_date_field
from .json_requests import GET, POST, PUT, DELETE
from .api_error import api_error_protect

API_PREFIX = '/'.join(['api', 'server'])

WORKGROUP_API = '/'.join([API_PREFIX, 'workgroups'])
PEER_REVIEW_API = '/'.join([API_PREFIX, 'peer_reviews'])
WORKGROUP_REVIEW_API = '/'.join([API_PREFIX, 'workgroup_reviews'])
USERS_API = '/'.join([API_PREFIX, 'users'])
SUBMISSION_API = '/'.join([API_PREFIX, 'submissions'])
GROUP_API = '/'.join([API_PREFIX, 'groups'])
COURSES_API = '/'.join([API_PREFIX, 'courses'])

# TODO: this class crosses service boundary, but some methods post-process responses, while other do not
# There're two things to improve:
# * SRP - it should only cross the service boundary, and not do any post-processing
# * Service isolation - it should be used only through some other (not existent yet) class that would ALWAYS do
# post-processing in order to isolate clients from response format changes. As of now, if format changes
# virtually every method in group_project might be affected.
# pylint: disable=invalid-name
class ProjectAPI(object):
    def __init__(self, address, dry_run=False):
        self._api_server_address = address
        self.dry_run = dry_run

    # TODO: self._api_server_address is used virtually everywhere - maybe should extract method, e.g.
    # send_request(GET, USERS_API, user_id, 'preferences')
    # send_request(GET, WORKGROUP_API, group_id, 'preferences', querystring=urlencode(qs_params))
    # send_request(PUT, PEER_REVIEW_API, question_data['id'], data=question_data)

    def send_request(self, method, url_parts, data=None, query_params=None, no_trailing_slash=False):
        if self.dry_run:
            return {}

        url_template = "{}/" + "{}/" * len(url_parts)
        url_parameters = [self._api_server_address]
        url_parameters.extend(url_parts)
        url = url_template.format(*url_parameters)  # pylint: disable=star-args
        if no_trailing_slash:
            url = url[:-1]

        if query_params:
            url += "?"+urlencode(query_params)

        if data is not None:
            response = method(url, data)
        else:
            response = method(url)

        if method == DELETE:
            return None

        return json.loads(response.read())

    @api_error_protect
    def get_user_preferences(self, user_id):
        """ gets users preferences information """
        return self.send_request(GET, (USERS_API, user_id, 'preferences'), no_trailing_slash=True)

    @api_error_protect
    def get_peer_review_items_for_group(self, group_id, content_id):
        qs_params = {"content_id": content_id}
        return self.send_request(GET, (WORKGROUP_API, group_id, 'peer_reviews'), query_params=qs_params)

    @api_error_protect
    def update_peer_review_assessment(self, question_data):
        return self.send_request(PUT, (PEER_REVIEW_API, question_data['id']), data=question_data)

    @api_error_protect
    def create_peer_review_assessment(self, question_data):
        return self.send_request(POST, (PEER_REVIEW_API,), data=question_data)

    @api_error_protect
    def delete_peer_review_assessment(self, assessment_id):
        self.send_request(DELETE, (PEER_REVIEW_API, assessment_id))

    @api_error_protect
    def get_workgroup_review_items_for_group(self, group_id, content_id):
        qs_params = {"content_id": content_id}
        return self.send_request(GET, (WORKGROUP_API, group_id, 'workgroup_reviews'), query_params=qs_params)

    @api_error_protect
    def update_workgroup_review_assessment(self, question_data):
        return self.send_request(PUT, (WORKGROUP_REVIEW_API, question_data['id']), data=question_data)

    @api_error_protect
    def create_workgroup_review_assessment(self, question_data):
        return self.send_request(POST, (WORKGROUP_REVIEW_API, ), data=question_data)

    @api_error_protect
    def delete_workgroup_review_assessment(self, assessment_id):
        self.send_request(DELETE, (WORKGROUP_REVIEW_API, assessment_id))

    @api_error_protect
    def get_workgroup_by_id(self, group_id):
        return self.send_request(GET, (WORKGROUP_API, group_id))

    @api_error_protect
    def get_user_workgroup_for_course(self, user_id, course_id):
        qs_params = {"course_id": course_id}
        workgroups_list = self.send_request(GET, (USERS_API, user_id, 'workgroups'), query_params=qs_params)

        if not workgroups_list or workgroups_list['count'] < 1:
            return None

        return self.get_workgroup_by_id(workgroups_list['results'][0]['id'])

    @api_error_protect
    def get_user_details(self, user_id):
        return self.send_request(GET, (USERS_API, user_id), no_trailing_slash=True)

    @api_error_protect
    def get_user_grades(self, user_id, course_id):
        return self.send_request(GET, (USERS_API, user_id, 'courses', course_id, 'grades'), no_trailing_slash=True)

    @api_error_protect
    def set_group_grade(self, group_id, course_id, activity_id, grade_value, max_grade):
        grade_data = {
            "course_id": unicode(course_id),
            "content_id": activity_id,
            "grade": grade_value,
            "max_grade": max_grade,
        }

        return self.send_request(POST, (WORKGROUP_API, group_id, 'grades'), data=grade_data)

    @api_error_protect
    def create_submission(self, submit_hash):
        return self.send_request(POST, (SUBMISSION_API, ), data=submit_hash)

    @api_error_protect
    def get_workgroup_submissions(self, group_id):
        return self.send_request(GET, (WORKGROUP_API, group_id, 'submissions'))

    @api_error_protect
    def get_review_assignment_groups(self, user_id, course_id, xblock_id):
        qs_params = {
            "course": course_id,
            "type": "reviewassignment",
            "data__xblock_id": xblock_id,
        }
        return self.send_request(GET, (USERS_API, user_id, 'groups'), query_params=qs_params)["groups"]

    @api_error_protect
    def get_workgroups_for_assignment(self, assignment_id):
        workgroups = self.send_request(GET, (GROUP_API, assignment_id, 'workgroups'), no_trailing_slash=True)
        return workgroups["results"]

    @api_error_protect
    def get_group_detail(self, group_id):
        return self.send_request(GET, (GROUP_API, group_id))

    @api_error_protect
    def get_workgroups_to_review(self, user_id, course_id, xblock_id):
        assignments = self.get_review_assignment_groups(user_id, course_id, xblock_id)

        workgroup_assignments = []
        for assignment in assignments:
            workgroup_assignments += self.get_workgroups_for_assignment(assignment["id"])

        return workgroup_assignments

    @api_error_protect
    def get_workgroup_reviewers(self, group_id):
        review_assignments = self.send_request(GET, (WORKGROUP_API, group_id, 'groups'), no_trailing_slash=True)

        reviewers = []
        for review_assignment in review_assignments:
            # stripping forward slash as we're adding it in send_request anyway
            review_assignment_url = review_assignment["url"].lstrip("/")
            review_assignment_details = self.send_request(GET, (review_assignment_url, 'users'))
            reviewers.extend(review_assignment_details["users"])

        return reviewers

    @api_error_protect
    def mark_as_complete(self, course_id, content_id, user_id, stage_id=None):
        completion_data = {
            "content_id": content_id,
            "user_id": user_id,
        }

        if stage_id is not None:
            completion_data["stage"] = str(stage_id)

        return self.send_request(POST, (COURSES_API, course_id, 'completions'), data=completion_data)

    @api_error_protect
    def get_stage_completions(self, course_id, content_id, stage_id):
        qs_params = {
            "content_id": content_id,
            "stage": stage_id
        }
        return self.send_request(GET, (COURSES_API, course_id, 'completions'), query_params=qs_params)['results']

    def get_stage_state(self, course_id, content_id, user_id, stage):
        user_workgroup = self.get_user_workgroup_for_course(user_id, course_id)
        if user_workgroup:
            users_in_group = {user['id'] for user in user_workgroup['users']}
        else:
            users_in_group = set()

        stage_completions = self.get_stage_completions(course_id, content_id, stage)
        if stage_completions:
            completed_users = {completion['user_id'] for completion in stage_completions}
        else:
            completed_users = set()

        return users_in_group, completed_users

    @api_error_protect
    def get_user_roles_for_course(self, user_id, course_id):
        qs_params = {
            "user_id": user_id,
        }

        return self.send_request(GET, (COURSES_API, course_id, 'roles'), query_params=qs_params)

    # TODO: this method post-process api response: probably they should be moved outside of this class
    def get_peer_review_items(self, reviewer_id, peer_id, group_id, content_id):
        group_peer_items = self.get_peer_review_items_for_group(group_id, content_id)
        return [pri for pri in group_peer_items if
                pri['reviewer'] == reviewer_id and (pri['user'] == peer_id or pri['user'] == int(peer_id))]

    # TODO: this method post-process api response: probably they should be moved outside of this class
    def get_user_peer_review_items(self, user_id, group_id, content_id):
        group_peer_items = self.get_peer_review_items_for_group(group_id, content_id)
        return [pri for pri in group_peer_items if pri['user'] == user_id or pri['user'] == int(user_id)]

    # TODO: this method pre-process api request: probably they should be moved outside of this class
    def submit_peer_review_items(self, reviewer_id, peer_id, group_id, content_id, data):
        # get any data already there
        current_data = {pi['question']: pi for pi in
                        self.get_peer_review_items(reviewer_id, peer_id, group_id, content_id)}
        for k, v in data.iteritems():
            if k in current_data:
                question_data = current_data[k]

                if question_data['answer'] != v:
                    if len(v) > 0:
                        # update with relevant data
                        del question_data['created']
                        del question_data['modified']
                        question_data['answer'] = v

                        self.update_peer_review_assessment(question_data)
                    else:
                        self.delete_peer_review_assessment(question_data['id'])

            elif len(v) > 0:
                question_data = {
                    "question": k,
                    "answer": v,
                    "workgroup": group_id,
                    "user": peer_id,
                    "reviewer": reviewer_id,
                    "content_id": content_id,
                }
                self.create_peer_review_assessment(question_data)

    # TODO: this method post-process api response: probably they should be moved outside of this class
    def get_workgroup_review_items(self, reviewer_id, group_id, content_id):
        group_review_items = self.get_workgroup_review_items_for_group(group_id, content_id)
        return [gri for gri in group_review_items if gri['reviewer'] == reviewer_id and gri['content_id'] == content_id]

    # TODO: this method pre-process api request: probably they should be moved outside of this class
    def submit_workgroup_review_items(self, reviewer_id, group_id, content_id, data):
        # get any data already there
        current_data = {ri['question']: ri for ri in self.get_workgroup_review_items(reviewer_id, group_id, content_id)}
        for k, v in data.iteritems():
            if k in current_data:
                question_data = current_data[k]

                if question_data['answer'] != v:
                    if len(v) > 0:
                        # update with relevant data
                        del question_data['created']
                        del question_data['modified']
                        question_data['answer'] = v

                        self.update_workgroup_review_assessment(question_data)
                    else:
                        self.delete_workgroup_review_assessment(question_data['id'])

            elif len(v) > 0:
                question_data = {
                    "question": k,
                    "answer": v,
                    "workgroup": group_id,
                    "reviewer": reviewer_id,
                    "content_id": content_id,
                }
                self.create_workgroup_review_assessment(question_data)

    # TODO: this method post-process api response: probably they should be moved outside of this class
    def get_latest_workgroup_submissions_by_id(self, group_id):
        submission_list = self.get_workgroup_submissions(group_id)

        user_details_cache = {}

        def get_user_details(user_id):
            if user_id not in user_details_cache:
                user_details_cache[user_id] = self.get_user_details(user_id)
            return user_details_cache[user_id]

        submissions_by_id = {}
        for submission in submission_list:
            submission_id = submission['document_id']
            if submission['user']:
                submission[u'user_details'] = get_user_details(submission['user'])
            if submission_id in submissions_by_id:
                last_modified = build_date_field(submissions_by_id[submission_id]["modified"])
                this_modified = build_date_field(submission["modified"])
                if this_modified > last_modified:
                    submissions_by_id[submission["document_id"]] = submission
            else:
                submissions_by_id[submission["document_id"]] = submission

        return submissions_by_id

# Looks like it's an issue, but technically it's not; this code runs in LMS, so 127.0.0.1 is always correct
# location for API server, as it's basically executed in a neighbour thread/process/whatever.
api_server = "http://127.0.0.1:8000"
if hasattr(settings, 'API_LOOPBACK_ADDRESS'):
    api_server = settings.API_LOOPBACK_ADDRESS

class ProjectAPIXBlockMixin(object):
    @lazy
    def project_api(self):
        author_mode = getattr(self.runtime, 'is_author_mode', False)
        return ProjectAPI(api_server, author_mode)
<commit_msg>Fixed broken URL formatting<commit_after>''' API calls with respect group projects'''
import json
from urllib import urlencode
from django.conf import settings
from lazy.lazy import lazy

from group_project_v2.utils import build_date_field
from .json_requests import GET, POST, PUT, DELETE
from .api_error import api_error_protect

API_PREFIX = '/'.join(['api', 'server'])

WORKGROUP_API = '/'.join([API_PREFIX, 'workgroups'])
PEER_REVIEW_API = '/'.join([API_PREFIX, 'peer_reviews'])
WORKGROUP_REVIEW_API = '/'.join([API_PREFIX, 'workgroup_reviews'])
USERS_API = '/'.join([API_PREFIX, 'users'])
SUBMISSION_API = '/'.join([API_PREFIX, 'submissions'])
GROUP_API = '/'.join([API_PREFIX, 'groups'])
COURSES_API = '/'.join([API_PREFIX, 'courses'])

# TODO: this class crosses service boundary, but some methods post-process responses, while other do not
# There're two things to improve:
# * SRP - it should only cross the service boundary, and not do any post-processing
# * Service isolation - it should be used only through some other (not existent yet) class that would ALWAYS do
# post-processing in order to isolate clients from response format changes. As of now, if format changes
# virtually every method in group_project might be affected.
# pylint: disable=invalid-name
class ProjectAPI(object):
    def __init__(self, address, dry_run=False):
        self._api_server_address = address
        self.dry_run = dry_run

    # TODO: self._api_server_address is used virtually everywhere - maybe should extract method, e.g.
    # send_request(GET, USERS_API, user_id, 'preferences')
    # send_request(GET, WORKGROUP_API, group_id, 'preferences', querystring=urlencode(qs_params))
    # send_request(PUT, PEER_REVIEW_API, question_data['id'], data=question_data)

    def send_request(self, method, url_parts, data=None, query_params=None, no_trailing_slash=False):
        if self.dry_run:
            return {}

        url_template = "{}/" + "{}/" * len(url_parts)
        url_parameters = [self._api_server_address]
        url_parameters.extend(url_parts)
        url = url_template.format(*url_parameters)  # pylint: disable=star-args
        if no_trailing_slash:
            url = url[:-1]

        if query_params:
            url += "?"+urlencode(query_params)

        if data is not None:
            response = method(url, data)
        else:
            response = method(url)

        if method == DELETE:
            return None

        return json.loads(response.read())

    @api_error_protect
    def get_user_preferences(self, user_id):
        """ gets users preferences information """
        return self.send_request(GET, (USERS_API, user_id, 'preferences'), no_trailing_slash=True)

    @api_error_protect
    def get_peer_review_items_for_group(self, group_id, content_id):
        qs_params = {"content_id": content_id}
        return self.send_request(GET, (WORKGROUP_API, group_id, 'peer_reviews'), query_params=qs_params)

    @api_error_protect
    def update_peer_review_assessment(self, question_data):
        return self.send_request(PUT, (PEER_REVIEW_API, question_data['id']), data=question_data)

    @api_error_protect
    def create_peer_review_assessment(self, question_data):
        return self.send_request(POST, (PEER_REVIEW_API,), data=question_data)

    @api_error_protect
    def delete_peer_review_assessment(self, assessment_id):
        self.send_request(DELETE, (PEER_REVIEW_API, assessment_id))

    @api_error_protect
    def get_workgroup_review_items_for_group(self, group_id, content_id):
        qs_params = {"content_id": content_id}
        return self.send_request(GET, (WORKGROUP_API, group_id, 'workgroup_reviews'), query_params=qs_params)

    @api_error_protect
    def update_workgroup_review_assessment(self, question_data):
        return self.send_request(PUT, (WORKGROUP_REVIEW_API, question_data['id']), data=question_data)

    @api_error_protect
    def create_workgroup_review_assessment(self, question_data):
        return self.send_request(POST, (WORKGROUP_REVIEW_API, ), data=question_data)

    @api_error_protect
    def delete_workgroup_review_assessment(self, assessment_id):
        self.send_request(DELETE, (WORKGROUP_REVIEW_API, assessment_id))

    @api_error_protect
    def get_workgroup_by_id(self, group_id):
        return self.send_request(GET, (WORKGROUP_API, group_id))

    @api_error_protect
    def get_user_workgroup_for_course(self, user_id, course_id):
        qs_params = {"course_id": course_id}
        workgroups_list = self.send_request(GET, (USERS_API, user_id, 'workgroups'), query_params=qs_params)

        if not workgroups_list or workgroups_list['count'] < 1:
            return None

        return self.get_workgroup_by_id(workgroups_list['results'][0]['id'])

    @api_error_protect
    def get_user_details(self, user_id):
        return self.send_request(GET, (USERS_API, user_id), no_trailing_slash=True)

    @api_error_protect
    def get_user_grades(self, user_id, course_id):
        return self.send_request(GET, (USERS_API, user_id, 'courses', course_id, 'grades'), no_trailing_slash=True)

    @api_error_protect
    def set_group_grade(self, group_id, course_id, activity_id, grade_value, max_grade):
        grade_data = {
            "course_id": unicode(course_id),
            "content_id": activity_id,
            "grade": grade_value,
            "max_grade": max_grade,
        }

        return self.send_request(POST, (WORKGROUP_API, group_id, 'grades'), data=grade_data)

    @api_error_protect
    def create_submission(self, submit_hash):
        return self.send_request(POST, (SUBMISSION_API, ), data=submit_hash)

    @api_error_protect
    def get_workgroup_submissions(self, group_id):
        return self.send_request(GET, (WORKGROUP_API, group_id, 'submissions'))

    @api_error_protect
    def get_review_assignment_groups(self, user_id, course_id, xblock_id):
        qs_params = {
            "course": course_id,
            "type": "reviewassignment",
            "data__xblock_id": xblock_id,
        }
        return self.send_request(GET, (USERS_API, user_id, 'groups'), query_params=qs_params)["groups"]

    @api_error_protect
    def get_workgroups_for_assignment(self, assignment_id):
        workgroups = self.send_request(GET, (GROUP_API, assignment_id, 'workgroups'), no_trailing_slash=True)
        return workgroups["results"]

    @api_error_protect
    def get_group_detail(self, group_id):
        return self.send_request(GET, (GROUP_API, group_id))

    @api_error_protect
    def get_workgroups_to_review(self, user_id, course_id, xblock_id):
        assignments = self.get_review_assignment_groups(user_id, course_id, xblock_id)

        workgroup_assignments = []
        for assignment in assignments:
            workgroup_assignments += self.get_workgroups_for_assignment(assignment["id"])

        return workgroup_assignments

    @api_error_protect
    def get_workgroup_reviewers(self, group_id):
        review_assignments = self.send_request(GET, (WORKGROUP_API, group_id, 'groups'), no_trailing_slash=True)

        reviewers = []
        for review_assignment in review_assignments:
            # stripping slashes as we're adding it in send_request anyway
            review_assignment_url = review_assignment["url"].strip("/")
            review_assignment_details = self.send_request(GET, (review_assignment_url, 'users'))
            reviewers.extend(review_assignment_details["users"])

        return reviewers

    @api_error_protect
    def mark_as_complete(self, course_id, content_id, user_id, stage_id=None):
        completion_data = {
            "content_id": content_id,
            "user_id": user_id,
        }

        if stage_id is not None:
            completion_data["stage"] = str(stage_id)

        return self.send_request(POST, (COURSES_API, course_id, 'completions'), data=completion_data)

    @api_error_protect
    def get_stage_completions(self, course_id, content_id, stage_id):
        qs_params = {
            "content_id": content_id,
            "stage": stage_id
        }
        return self.send_request(GET, (COURSES_API, course_id, 'completions'), query_params=qs_params)['results']

    def get_stage_state(self, course_id, content_id, user_id, stage):
        user_workgroup = self.get_user_workgroup_for_course(user_id, course_id)
        if user_workgroup:
            users_in_group = {user['id'] for user in user_workgroup['users']}
        else:
            users_in_group = set()

        stage_completions = self.get_stage_completions(course_id, content_id, stage)
        if stage_completions:
            completed_users = {completion['user_id'] for completion in stage_completions}
        else:
            completed_users = set()

        return users_in_group, completed_users

    @api_error_protect
    def get_user_roles_for_course(self, user_id, course_id):
        qs_params = {
            "user_id": user_id,
        }

        return self.send_request(GET, (COURSES_API, course_id, 'roles'), query_params=qs_params)

    # TODO: this method post-process api response: probably they should be moved outside of this class
    def get_peer_review_items(self, reviewer_id, peer_id, group_id, content_id):
        group_peer_items = self.get_peer_review_items_for_group(group_id, content_id)
        return [pri for pri in group_peer_items if
                pri['reviewer'] == reviewer_id and (pri['user'] == peer_id or pri['user'] == int(peer_id))]

    # TODO: this method post-process api response: probably they should be moved outside of this class
    def get_user_peer_review_items(self, user_id, group_id, content_id):
        group_peer_items = self.get_peer_review_items_for_group(group_id, content_id)
        return [pri for pri in group_peer_items if pri['user'] == user_id or pri['user'] == int(user_id)]

    # TODO: this method pre-process api request: probably they should be moved outside of this class
    def submit_peer_review_items(self, reviewer_id, peer_id, group_id, content_id, data):
        # get any data already there
        current_data = {pi['question']: pi for pi in
                        self.get_peer_review_items(reviewer_id, peer_id, group_id, content_id)}
        for k, v in data.iteritems():
            if k in current_data:
                question_data = current_data[k]

                if question_data['answer'] != v:
                    if len(v) > 0:
                        # update with relevant data
                        del question_data['created']
                        del question_data['modified']
                        question_data['answer'] = v

                        self.update_peer_review_assessment(question_data)
                    else:
                        self.delete_peer_review_assessment(question_data['id'])

            elif len(v) > 0:
                question_data = {
                    "question": k,
                    "answer": v,
                    "workgroup": group_id,
                    "user": peer_id,
                    "reviewer": reviewer_id,
                    "content_id": content_id,
                }
                self.create_peer_review_assessment(question_data)

    # TODO: this method post-process api response: probably they should be moved outside of this class
    def get_workgroup_review_items(self, reviewer_id, group_id, content_id):
        group_review_items = self.get_workgroup_review_items_for_group(group_id, content_id)
        return [gri for gri in group_review_items if gri['reviewer'] == reviewer_id and gri['content_id'] == content_id]

    # TODO: this method pre-process api request: probably they should be moved outside of this class
    def submit_workgroup_review_items(self, reviewer_id, group_id, content_id, data):
        # get any data already there
        current_data = {ri['question']: ri for ri in self.get_workgroup_review_items(reviewer_id, group_id, content_id)}
        for k, v in data.iteritems():
            if k in current_data:
                question_data = current_data[k]

                if question_data['answer'] != v:
                    if len(v) > 0:
                        # update with relevant data
                        del question_data['created']
                        del question_data['modified']
                        question_data['answer'] = v

                        self.update_workgroup_review_assessment(question_data)
                    else:
                        self.delete_workgroup_review_assessment(question_data['id'])

            elif len(v) > 0:
                question_data = {
                    "question": k,
                    "answer": v,
                    "workgroup": group_id,
                    "reviewer": reviewer_id,
                    "content_id": content_id,
                }
                self.create_workgroup_review_assessment(question_data)

    # TODO: this method post-process api response: probably they should be moved outside of this class
    def get_latest_workgroup_submissions_by_id(self, group_id):
        submission_list = self.get_workgroup_submissions(group_id)

        user_details_cache = {}

        def get_user_details(user_id):
            if user_id not in user_details_cache:
                user_details_cache[user_id] = self.get_user_details(user_id)
            return user_details_cache[user_id]

        submissions_by_id = {}
        for submission in submission_list:
            submission_id = submission['document_id']
            if submission['user']:
                submission[u'user_details'] = get_user_details(submission['user'])
            if submission_id in submissions_by_id:
                last_modified = build_date_field(submissions_by_id[submission_id]["modified"])
                this_modified = build_date_field(submission["modified"])
                if this_modified > last_modified:
                    submissions_by_id[submission["document_id"]] = submission
            else:
                submissions_by_id[submission["document_id"]] = submission

        return submissions_by_id

# Looks like it's an issue, but technically it's not; this code runs in LMS, so 127.0.0.1 is always correct
# location for API server, as it's basically executed in a neighbour thread/process/whatever.
api_server = "http://127.0.0.1:8000"
if hasattr(settings, 'API_LOOPBACK_ADDRESS'):
    api_server = settings.API_LOOPBACK_ADDRESS

class ProjectAPIXBlockMixin(object):
    @lazy
    def project_api(self):
        author_mode = getattr(self.runtime, 'is_author_mode', False)
        return ProjectAPI(api_server, author_mode)
<|endoftext|>


<commit_before>from . import base
from . import messages
from grow.pods import locales
from grow.pods import urls
import fnmatch
import mimetypes
import re
import hashlib
import webob
import os

mimetypes.add_type('application/font-woff', '.woff')
mimetypes.add_type('application/font-woff', '.woff')
mimetypes.add_type('image/bmp', '.cur')
mimetypes.add_type('image/svg+xml', '.svg')
mimetypes.add_type('text/css', '.css')

SKIP_PATTERNS = [
    '**/.**',
]

class Error(Exception):
  pass

class BadStaticFileError(Error):
  pass

class StaticFile(object):

  def __init__(self, pod_path, serving_path, locale=None, localization=None,
               controller=None, pod=None):
    self.pod = pod
    self.default_locale = pod.podspec.default_locale
    self.locale = pod.normalize_locale(locale)
    self.localization = localization
    self.pod_path = pod_path
    self.serving_path = serving_path
    self.controller = controller
    self.basename = os.path.basename(pod_path)
    self.base, self.ext = os.path.splitext(self.basename)

  def __repr__(self):
    if self.locale:
      return "<StaticFile({}, locale='{}')>".format(self.pod_path, self.locale)
    return "<StaticFile({})>".format(self.pod_path)

  def __eq__(self, other):
    return (self.pod_path == other.pod_path and self.pod == other.pod
            and other.locale == self.locale)

  def __ne__(self, other):
    return not self.__eq__(other)

  @property
  def exists(self):
    return self.pod.file_exists(self.pod_path)

  @property
  def modified(self):
    return self.pod.file_modified(self.pod_path)

  @property
  def size(self):
    return self.pod.file_size(self.pod_path)

  @property
  def fingerprint(self):
    return StaticFile._create_fingerprint(self.pod, self.pod_path)

  @staticmethod
  def _create_fingerprint(pod, pod_path):
    md5 = hashlib.md5()
    with pod.open_file(pod_path, 'rb') as fp:
      content = fp.read()
      md5.update(content)
    return md5.hexdigest()

  @property
  def url(self):
    serving_path = self.serving_path
    path_format = self.controller.path_format.replace('{filename}', '')
    suffix = serving_path.replace(path_format, '')
    if '{fingerprint}' in path_format:
      path_format = path_format.replace('{fingerprint}', self.fingerprint)
    if self.localization:
      localized_pod_path = self.localization['static_dir'] + suffix
      localized_pod_path = localized_pod_path.format(locale=self.locale)
      if self.pod.file_exists(localized_pod_path):
        # Internal paths use Babel locales, serving paths use aliases.
        locale = self.locale.alias if self.locale is not None else self.locale
        localized_serving_path = self.localization['serve_at'] + suffix
        localized_serving_path = localized_serving_path.format(locale=locale)
        serving_path = localized_serving_path
    return urls.Url(path=serving_path) if serving_path else None

class StaticController(base.BaseController):
  KIND = messages.Kind.STATIC

  def __init__(self, path_format, source_format=None, localized=False,
               localization=None, pod=None):
    # path_format: "serve_at"
    # source_format: "static_dir"
    self.path_format = path_format.replace('<grow:', '{').replace('>', '}')
    self.source_format = source_format.replace('<grow:', '{').replace('>', '}')
    self.pod = pod
    self.localized = localized
    self.localization = localization
    self.route_params = {}

  def __repr__(self):
    return '<Static(format=\'{}\')>'.format(self.source_format)

  def get_localized_pod_path(self):
    if (self.localization
       and '{locale}' in self.localization['static_dir']
       and 'locale' in self.route_params):
      source_format = self.localization['serve_at']
      source_format += '/{filename}'
      source_format = source_format.replace('//', '/')
      kwargs = self.route_params
      if 'locale' in kwargs:
        locale = locales.Locale.from_alias(self.pod, kwargs['locale'])
        kwargs['locale'] = str(locale)
      pod_path = source_format.format(**kwargs)
      if self.pod.file_exists(pod_path):
        return pod_path

  def get_pod_path(self):
    # If a localized file exists, serve it. Otherwise, serve the base file.
    return (self.get_localized_pod_path()
            or self.source_format.format(**self.route_params))

  def validate(self):
    if not self.pod.file_exists(self.get_pod_path()):
      path = self.pod.abs_path(self.get_pod_path())
      message = '{} does not exist.'.format(path)
      raise webob.exc.HTTPNotFound(message)

  def render(self):
    return self.pod.read_file(self.get_pod_path())

  @property
  def mimetype(self):
    return mimetypes.guess_type(self.get_pod_path())[0]

  def get_http_headers(self):
    path = self.pod.abs_path(self.get_pod_path())
    headers = super(StaticController, self).get_http_headers()
    self.pod.storage.update_headers(headers, path)
    modified = str(self.pod.storage.modified(path))
    headers['Last-Modified'] = modified.split('.')[0]
    headers['Cache-Control'] = 'max-age'
    return headers

  def match_pod_path(self, pod_path):
    if self.path_format == pod_path:
      return self.path_format
    tokens = re.findall('.?{([^>]+)}.?', self.path_format)
    if 'filename' in tokens:
      source_regex = self.source_format.replace(
          '{filename}', '(?P<filename>.*)')
      source_regex = source_regex.replace('{locale}', '(?P<locale>[^/]*)')
      source_regex = source_regex.replace('{locale}', '(?P<root>[^/])')
      match = re.match(source_regex, pod_path)
      if match:
        kwargs = match.groupdict()
        kwargs['root'] = self.pod.podspec.root
        if 'locale' in kwargs:
          locale = locales.Locale.from_alias(self.pod, kwargs['locale'])
          kwargs['locale'] = str(locale)
        path = self.path_format.format(**kwargs)
        path = path.replace('//', '/')
        return path

  def list_concrete_paths(self):
    concrete_paths = set()
    tokens = re.findall('.?{([^}]+)}.?', self.path_format)

    source_regex = self.source_format.replace('{filename}', '(?P<filename>.*)')
    source_regex = source_regex.replace('{locale}', '(?P<locale>[^/]*)')

    if '{' not in self.path_format:
      concrete_paths.add(self.path_format)

    elif 'filename' in tokens:
      # NOTE: This should be updated to support globbing directories,
      # and not simply strip all sub-paths beneath {locale}.
      source = self.source_format.replace('{filename}', '')[1:]
      source = re.sub('{locale}.*', '', source)
      source = source.rstrip('/')
      paths = self.pod.list_dir(source)
      paths = [('/' + source + path).replace(self.pod.root, '')
               for path in paths]

      # Exclude paths matched by skip patterns.
      for pattern in SKIP_PATTERNS:
        # .gitignore-style treatment of paths without slashes.
        if '/' not in pattern:
          pattern = '**{}**'.format(pattern)
        for skip_paths in fnmatch.filter(paths, pattern):
          paths = [path for path in paths
                   if path.replace(self.pod.root, '') not in skip_paths]

      for path in paths:
        match = re.match(source_regex, path)
        # Skip adding localized paths in subfolders of other rules.
        if not self.localized and self.localization:
          localized_source_format = self.localization['static_dir']
          localized_source_regex = localized_source_format.replace(
              '{filename}', '(?P<filename>.*)')
          localized_source_regex = localized_source_regex.replace(
              '{locale}', '(?P<locale>[^/]*)')
          if re.match(localized_source_regex, path):
            continue
        if match:
          kwargs = match.groupdict()
          kwargs['root'] = self.pod.podspec.root
          if 'fingerprint' in self.path_format:
            pod_path = os.path.join(source, kwargs['filename'])
            fingerprint = StaticFile._create_fingerprint(self.pod, pod_path)
            kwargs['fingerprint'] = fingerprint
          if 'locale' in kwargs:
            normalized_locale = self.pod.normalize_locale(kwargs['locale'])
            kwargs['locale'] = (
                normalized_locale.alias if normalized_locale is not None
                else normalized_locale)
          matched_path = self.path_format.format(**kwargs)
          matched_path = matched_path.replace('//', '/')
          concrete_paths.add(matched_path)

    return list(concrete_paths)
<commit_msg>Fix localized, fingerprinted static paths.<commit_after>from . import base
from . import messages
from grow.pods import locales
from grow.pods import urls
import fnmatch
import mimetypes
import re
import hashlib
import webob
import os

mimetypes.add_type('application/font-woff', '.woff')
mimetypes.add_type('application/font-woff', '.woff')
mimetypes.add_type('image/bmp', '.cur')
mimetypes.add_type('image/svg+xml', '.svg')
mimetypes.add_type('text/css', '.css')

SKIP_PATTERNS = [
    '**/.**',
]

class Error(Exception):
  pass

class BadStaticFileError(Error):
  pass

class StaticFile(object):

  def __init__(self, pod_path, serving_path, locale=None, localization=None,
               controller=None, pod=None):
    self.pod = pod
    self.default_locale = pod.podspec.default_locale
    self.locale = pod.normalize_locale(locale)
    self.localization = localization
    self.pod_path = pod_path
    self.serving_path = serving_path
    self.controller = controller
    self.basename = os.path.basename(pod_path)
    self.base, self.ext = os.path.splitext(self.basename)

  def __repr__(self):
    if self.locale:
      return "<StaticFile({}, locale='{}')>".format(self.pod_path, self.locale)
    return "<StaticFile({})>".format(self.pod_path)

  def __eq__(self, other):
    return (self.pod_path == other.pod_path and self.pod == other.pod
            and other.locale == self.locale)

  def __ne__(self, other):
    return not self.__eq__(other)

  @property
  def exists(self):
    return self.pod.file_exists(self.pod_path)

  @property
  def modified(self):
    return self.pod.file_modified(self.pod_path)

  @property
  def size(self):
    return self.pod.file_size(self.pod_path)

  @property
  def fingerprint(self):
    return StaticFile._create_fingerprint(self.pod, self.pod_path)

  @staticmethod
  def _create_fingerprint(pod, pod_path):
    md5 = hashlib.md5()
    with pod.open_file(pod_path, 'rb') as fp:
      content = fp.read()
      md5.update(content)
    return md5.hexdigest()

  @property
  def url(self):
    serving_path = self.serving_path
    path_format = self.controller.path_format.replace('{filename}', '')
    suffix = serving_path.replace(path_format, '')
    if '{fingerprint}' in path_format:
      path_format = path_format.replace('{fingerprint}', self.fingerprint)
    if self.localization:
      localized_pod_path = self.localization['static_dir'] + suffix
      localized_pod_path = localized_pod_path.format(locale=self.locale)
      if self.pod.file_exists(localized_pod_path):
        # Internal paths use Babel locales, serving paths use aliases.
        locale = self.locale.alias if self.locale is not None else self.locale
        localized_serving_path = self.localization['serve_at'] + suffix
        localized_serving_path = localized_serving_path.format(locale=locale)
        serving_path = localized_serving_path
    return urls.Url(path=serving_path) if serving_path else None

class StaticController(base.BaseController):
  KIND = messages.Kind.STATIC

  def __init__(self, path_format, source_format=None, localized=False,
               localization=None, pod=None):
    # path_format: "serve_at"
    # source_format: "static_dir"
    self.path_format = path_format.replace('<grow:', '{').replace('>', '}')
    self.source_format = source_format.replace('<grow:', '{').replace('>', '}')
    self.pod = pod
    self.localized = localized
    self.localization = localization
    self.route_params = {}

  def __repr__(self):
    return '<Static(format=\'{}\')>'.format(self.source_format)

  def get_localized_pod_path(self):
    if (self.localization
       and '{locale}' in self.localization['static_dir']
       and 'locale' in self.route_params):
      source_format = self.localization['serve_at']
      source_format += '/{filename}'
      source_format = source_format.replace('//', '/')
      kwargs = self.route_params
      if 'locale' in kwargs:
        locale = locales.Locale.from_alias(self.pod, kwargs['locale'])
        kwargs['locale'] = str(locale)
      pod_path = source_format.format(**kwargs)
      if self.pod.file_exists(pod_path):
        return pod_path

  def get_pod_path(self):
    # If a localized file exists, serve it. Otherwise, serve the base file.
    return (self.get_localized_pod_path()
            or self.source_format.format(**self.route_params))

  def validate(self):
    if not self.pod.file_exists(self.get_pod_path()):
      path = self.pod.abs_path(self.get_pod_path())
      message = '{} does not exist.'.format(path)
      raise webob.exc.HTTPNotFound(message)

  def render(self):
    return self.pod.read_file(self.get_pod_path())

  @property
  def mimetype(self):
    return mimetypes.guess_type(self.get_pod_path())[0]

  def get_http_headers(self):
    path = self.pod.abs_path(self.get_pod_path())
    headers = super(StaticController, self).get_http_headers()
    self.pod.storage.update_headers(headers, path)
    modified = str(self.pod.storage.modified(path))
    headers['Last-Modified'] = modified.split('.')[0]
    headers['Cache-Control'] = 'max-age'
    return headers

  def match_pod_path(self, pod_path):
    if self.path_format == pod_path:
      return self.path_format
    tokens = re.findall('.?{([^>]+)}.?', self.path_format)
    if 'filename' in tokens:
      source_regex = self.source_format.replace(
          '{filename}', '(?P<filename>.*)')
      source_regex = source_regex.replace('{locale}', '(?P<locale>[^/]*)')
      source_regex = source_regex.replace('{locale}', '(?P<root>[^/])')
      match = re.match(source_regex, pod_path)
      if match:
        kwargs = match.groupdict()
        kwargs['root'] = self.pod.podspec.root
        if 'locale' in kwargs:
          locale = locales.Locale.from_alias(self.pod, kwargs['locale'])
          kwargs['locale'] = str(locale)
        path = self.path_format.format(**kwargs)
        path = path.replace('//', '/')
        return path

  def list_concrete_paths(self):
    concrete_paths = set()
    tokens = re.findall('.?{([^}]+)}.?', self.path_format)

    source_regex = self.source_format.replace('{filename}', '(?P<filename>.*)')
    source_regex = source_regex.replace('{locale}', '(?P<locale>[^/]*)')

    if '{' not in self.path_format:
      concrete_paths.add(self.path_format)

    elif 'filename' in tokens:
      # NOTE: This should be updated to support globbing directories,
      # and not simply strip all sub-paths beneath {locale}.
      source = self.source_format.replace('{filename}', '')[1:]
      source = re.sub('{locale}.*', '', source)
      source = source.rstrip('/')
      paths = self.pod.list_dir(source)
      paths = [('/' + source + path).replace(self.pod.root, '')
               for path in paths]

      # Exclude paths matched by skip patterns.
      for pattern in SKIP_PATTERNS:
        # .gitignore-style treatment of paths without slashes.
        if '/' not in pattern:
          pattern = '**{}**'.format(pattern)
        for skip_paths in fnmatch.filter(paths, pattern):
          paths = [path for path in paths
                   if path.replace(self.pod.root, '') not in skip_paths]

      for path in paths:
        match = re.match(source_regex, path)
        # Skip adding localized paths in subfolders of other rules.
        if not self.localized and self.localization:
          localized_source_format = self.localization['static_dir']
          localized_source_regex = localized_source_format.replace(
              '{filename}', '(?P<filename>.*)')
          localized_source_regex = localized_source_regex.replace(
              '{locale}', '(?P<locale>[^/]*)')
          if re.match(localized_source_regex, path):
            continue
        if match:
          kwargs = match.groupdict()
          kwargs['root'] = self.pod.podspec.root
          if 'fingerprint' in self.path_format:
            fingerprint = StaticFile._create_fingerprint(self.pod, path)
            kwargs['fingerprint'] = fingerprint
          if 'locale' in kwargs:
            normalized_locale = self.pod.normalize_locale(kwargs['locale'])
            kwargs['locale'] = (
                normalized_locale.alias if normalized_locale is not None
                else normalized_locale)
          matched_path = self.path_format.format(**kwargs)
          matched_path = matched_path.replace('//', '/')
          concrete_paths.add(matched_path)

    return list(concrete_paths)
<|endoftext|>


<commit_before>from i3pystatus.file import File
from i3pystatus import Module
import shutil
import subprocess

class Backlight(File):
    """
    Screen backlight info

    .. rubric:: Available formatters

    * `{brightness}` — current brightness relative to max_brightness
    * `{max_brightness}` — maximum brightness value
    * `{percentage}` — current brightness in percent
    """

    settings = (
        ("format", "format string, formatters: brightness, max_brightness, percentage"),
        ("backlight", "backlight, see `/sys/class/backlight/`"),
        "color",
    )
    required = ()

    backlight = "acpi_video0"
    format = "{brightness}/{max_brightness}"

    base_path = "/sys/class/backlight/{backlight}/"
    components = {
        "brightness": (int, "brightness"),
        "max_brightness": (int, "max_brightness"),
    }
    transforms = {
        "percentage": lambda cdict: (cdict["brightness"] / cdict["max_brightness"]) * 100,
    }
    on_upscroll = "lighter"
    on_downscroll = "darker"
    def init(self):
        self.base_path = self.base_path.format(backlight=self.backlight)
        self.has_xbacklight = shutil.which("xbacklight") is not None
        super(Backlight, self).init()

    def lighter(self):
        if self.has_xbacklight:
            subprocess.Popen(["xbacklight", '+5'])

    def darker(self):
        if self.has_xbacklight:
            subprocess.Popen(["xbacklight", '-5'])
<commit_msg>pep8 and docs<commit_after>from i3pystatus.file import File
from i3pystatus import Module
import shutil
import subprocess

class Backlight(File):
    """
    Screen backlight info

    - Reuqire `xbacklight` installed to change the backlight through the scollwheel.

    .. rubric:: Available formatters

    * `{brightness}` — current brightness relative to max_brightness
    * `{max_brightness}` — maximum brightness value
    * `{percentage}` — current brightness in percent
    """

    settings = (
        ("format", "format string, formatters: brightness, max_brightness, percentage"),
        ("backlight", "backlight, see `/sys/class/backlight/`"),
        "color",
    )
    required = ()

    backlight = "acpi_video0"
    format = "{brightness}/{max_brightness}"

    base_path = "/sys/class/backlight/{backlight}/"
    components = {
        "brightness": (int, "brightness"),
        "max_brightness": (int, "max_brightness"),
    }
    transforms = {
        "percentage": lambda cdict: (cdict["brightness"] / cdict["max_brightness"]) * 100,
    }
    on_upscroll = "lighter"
    on_downscroll = "darker"

    def init(self):
        self.base_path = self.base_path.format(backlight=self.backlight)
        self.has_xbacklight = shutil.which("xbacklight") is not None
        super().init()

    def lighter(self):
        if self.has_xbacklight:
            subprocess.Popen(["xbacklight", "+5"])

    def darker(self):
        if self.has_xbacklight:
            subprocess.Popen(["xbacklight", "-5"])
<|endoftext|>