Compare commits

...

20 Commits

Author SHA1 Message Date
Ben Lopatin acdcb03283 Version bump 0.6.0 2016-07-22 08:48:57 -04:00
Ben Lopatin 6bd73fdd5c Merge pull request #4 from Meal-Mentor/master
Added method to add group to the user
2016-07-22 08:46:22 -04:00
Scott Nixon c13b456b79 Added method to add group to the user 2016-07-19 11:33:27 -07:00
Ben Lopatin 977885967d Update makefile 2016-06-13 11:41:28 -04:00
Ben Lopatin b9066ca637 Version bump 0.5.0 2016-06-13 11:37:12 -04:00
Ben Lopatin 77254f441c Merge pull request #3 from msfernandes/badges_endpoint
Added badges endpoint to pydiscourse
2016-06-10 11:12:26 -04:00
Matheus Fernandes 66089011f7 Added 'user-badges' endpoint
Signed-off-by: Matheus Fernandes <matheus.souza.fernandes@gmail.com>
2016-06-10 11:46:56 -03:00
Matheus Fernandes fe317b6be8 Added badges endpoint to pydiscourse
Signed-off-by: Matheus Fernandes <matheus.souza.fernandes@gmail.com>
2016-06-10 10:59:03 -03:00
Ben Lopatin 15e82aacd1 Add specific methods for interface to groups
Hide 'verbs' from users.
2016-05-04 08:28:44 -04:00
Ben Lopatin 008f21d6fe Merge pull request #2 from citadelgrad/master
Added partial groups support
2016-05-04 07:36:59 -04:00
Scott Nixon 6baf51bbe1 Added partial groups support
Groups method returns the list of all groups.
Created group_owners which allows you to add and delete owners.
Created group_members which allows you to add and delete members.
2016-05-03 16:11:10 -07:00
Ben Lopatin 8304e7b2f5 Version bump 0.3.2 2016-04-17 11:36:09 -04:00
Ben Lopatin 5806beef34 Merge pull request #1 from danielzohar/master
Only return `nonce` from given payload
2016-04-17 11:32:41 -04:00
Daniel Zohar f905a957f4 Only return nonce from given payload 2016-04-17 16:00:35 +01:00
Ben Lopatin 06ca2c5a58 Update changelog to include in package description 2016-04-11 11:36:25 -04:00
Ben Lopatin bde4325776 Add autodoc generation 2016-04-08 18:45:57 -04:00
Ben Lopatin 6b7e570475 Update README
[ci skip]
2016-04-08 18:27:02 -04:00
Ben Lopatin 3659724f11 Remove duplicate requests dependency in tox config 2016-04-08 18:03:10 -04:00
Ben Lopatin 1e151fc51f Remove module import from setup.py
Get the version number from the file, not by importing and thus trying
to import requests
2016-04-08 17:57:19 -04:00
Ben Lopatin 63f120ddca Require requests for tests 2016-04-08 17:47:26 -04:00
13 changed files with 335 additions and 38 deletions
+6
View File
@@ -1,2 +1,8 @@
(Based on original authors list and may be incomplete)
Marc Sibson
James Potter
Ben Lopatin
Daniel Zohar
Matheus Fernandes
Scott Nixon
+28 -7
View File
@@ -1,19 +1,40 @@
=========
Changelog
=========
.. :changelog:
Release history
===============
0.6.0
-----
* Adds method to add user to group by user ID
0.5.0
-----
* Adds badges functionality
0.4.0
-----
* Adds initial groups functionality
0.3.2
-----
* SSO functionality fixes
0.3.1
=====
-----
* Fix how empty responses are handled
0.3.0
=====
-----
* Added method to unsuspend suspended user
0.2.0
=====
-----
* Inital fork, including gberaudo's changes
* Packaging cleanup, dropping Python 2.6 support and adding Python 3.5, PyPy,
@@ -21,7 +42,7 @@ Changelog
* Packaging on PyPI
0.1.0.dev
=========
---------
All pre-PyPI development
+6
View File
@@ -0,0 +1,6 @@
include setup.py
include README.rst
include MANIFEST.in
include HISTORY.rst
include LICENSE
recursive-include pydiscourse
+13 -6
View File
@@ -37,18 +37,25 @@ test-all: ## Run all tox test environments, parallelized
check: clean-build clean-pyc clean-test lint test-coverage
release: clean ## Uploads new source and wheel distributions (cleans first)
python setup.py sdist upload
python setup.py bdist_wheel upload
build: clean ## Create distribution files for release
python setup.py sdist bdist_wheel
dist: clean ## Creates new source and wheel distributions (cleans first)
release: build ## Create distribution files and publish to PyPI
python setup.py check -r -s
twine upload dist/*
sdist: clean ##sdist Create source distribution only
python setup.py sdist
python setup.py bdist_wheel
ls -l dist
docs: ## Builds and open docs
api-docs: ## Build autodocs from docstrings
sphinx-apidoc -f -o docs pydiscourse
manual-docs: ## Build written docs
$(MAKE) -C docs clean
$(MAKE) -C docs html
docs: api-docs manual-docs ## Builds and open docs
open docs/_build/html/index.html
help:
+12 -2
View File
@@ -14,10 +14,20 @@ additional functionality, and to distribute a package on PyPI.
Goals
=====
* Exceptional documentation
* Support all supported Python versions
* Provide functional parity with the Discourse API, for the currently supported
version of Discourse (something of a moving target)
* Support all supported Python versions
* Document API
The order here is important. The Discourse API is itself poorly documented so
the level of documentation in the Python client is critical.
Installation
============
::
pip install pydiscourse
Examples
========
+3 -5
View File
@@ -28,9 +28,7 @@ import os
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
]
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.napoleon']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -53,9 +51,9 @@ copyright = u'2014, Marc Sibson'
# built documents.
#
# The short X.Y version.
version = '0.3'
version = '0.6'
# The full version, including alpha/beta/rc tags.
release = '0.3.1'
release = '0.6.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
+7
View File
@@ -0,0 +1,7 @@
pydiscourse
===========
.. toctree::
:maxdepth: 4
pydiscourse
+46
View File
@@ -0,0 +1,46 @@
pydiscourse package
===================
Submodules
----------
pydiscourse.client module
-------------------------
.. automodule:: pydiscourse.client
:members:
:undoc-members:
:show-inheritance:
pydiscourse.exceptions module
-----------------------------
.. automodule:: pydiscourse.exceptions
:members:
:undoc-members:
:show-inheritance:
pydiscourse.main module
-----------------------
.. automodule:: pydiscourse.main
:members:
:undoc-members:
:show-inheritance:
pydiscourse.sso module
----------------------
.. automodule:: pydiscourse.sso
:members:
:undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: pydiscourse
:members:
:undoc-members:
:show-inheritance:
+1 -1
View File
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
__version__ = '0.3.1'
__version__ = '0.6.0'
from pydiscourse.client import DiscourseClient
+179 -8
View File
@@ -13,6 +13,12 @@ from pydiscourse.sso import sso_payload
log = logging.getLogger('pydiscourse.client')
# HTTP verbs to be used as non string literals
DELETE = "DELETE"
GET = "GET"
POST = "POST"
PUT = "PUT"
class DiscourseClient(object):
"""Discourse API client"""
@@ -535,6 +541,43 @@ class DiscourseClient(object):
kwargs['term'] = term
return self._get('/search.json', **kwargs)
def badges(self, **kwargs):
"""
Args:
**kwargs:
Returns:
"""
return self._get('/admin/badges.json', **kwargs)
def grant_badge_to(self, username, badge_id, **kwargs):
"""
Args:
username:
badge_id:
**kwargs:
Returns:
"""
return self._post('/user_badges', username=username, badge_id=badge_id, **kwargs)
def user_badges(self, username, **kwargs):
"""
Args:
username:
Returns:
"""
return self._get('/user-badges/{}.json'.format(username))
def create_category(self, name, color, text_color='FFFFFF',
permissions=None, parent=None, **kwargs):
"""
@@ -611,7 +654,134 @@ class DiscourseClient(object):
"""
for setting, value in kwargs.items():
setting = setting.replace(' ', '_')
self._request('PUT', '/admin/site_settings/{0}'.format(setting), {setting: value})
self._request(PUT, '/admin/site_settings/{0}'.format(setting), {setting: value})
def groups(self, **kwargs):
"""
Returns a list of all groups.
Returns:
List of dictionaries of groups
[
{
'alias_level': 0,
'automatic': True,
'automatic_membership_email_domains': None,
'automatic_membership_retroactive': False,
'grant_trust_level': None,
'has_messages': True,
'id': 1,
'incoming_email': None,
'mentionable': False,
'name': 'admins',
'notification_level': 2,
'primary_group': False,
'title': None,
'user_count': 9,
'visible': True
},
{
'alias_level': 0,
'automatic': True,
'automatic_membership_email_domains': None,
'automatic_membership_retroactive': False,
'grant_trust_level': None,
'has_messages': False,
'id': 0,
'incoming_email': None,
'mentionable': False,
'name': 'everyone',
'notification_level': None,
'primary_group': False,
'title': None,
'user_count': 0,
'visible': True
}
]
"""
return self._get("/admin/groups.json", **kwargs)
def add_group_owner(self, groupid, username):
"""
Add an owner to a group by username
Args:
groupid: the ID of the group
username: the new owner usernmae
Returns:
JSON API response
"""
return self._put("/admin/groups/{0}/owners.json".format(groupid), usernames=username)
def delete_group_owner(self, groupid, userid):
"""
Deletes an owner from a group by user ID
Does not delete the user from Discourse.
Args:
groupid: the ID of the group
userid: the ID of the user
Returns:
JSON API response
"""
return self._delete("/admin/groups/{0}/owners.json".format(groupid), user_id=userid)
def add_group_member(self, groupid, username):
"""
Add a member to a group by username
Args:
groupid: the ID of the group
username: the new member usernmae
Returns:
JSON API response
Raises:
DiscourseError if user is already member of group
"""
return self._put("/admin/groups/{0}/members.json".format(groupid), usernames=username)
def add_user_to_group(self, groupid, userid):
"""
Add a member to a group by with user id.
Args:
groupid: the ID of the group
userid: the member id
Returns:
JSON API response
Raises:
DiscourseError if user is already member of group
"""
return self._post("/admin/users/{0}/groups".format(userid), group_id=groupid)
def delete_group_member(self, groupid, userid):
"""
Deletes a member from a group by user ID
Does not delete the user from Discourse.
Args:
groupid: the ID of the group
userid: the ID of the user
Returns:
JSON API response
"""
return self._delete("/admin/groups/{0}/members.json".format(groupid), user_id=userid)
def _get(self, path, **kwargs):
"""
@@ -623,7 +793,7 @@ class DiscourseClient(object):
Returns:
"""
return self._request('GET', path, kwargs)
return self._request(GET, path, kwargs)
def _put(self, path, **kwargs):
"""
@@ -635,7 +805,7 @@ class DiscourseClient(object):
Returns:
"""
return self._request('PUT', path, kwargs)
return self._request(PUT, path, kwargs)
def _post(self, path, **kwargs):
"""
@@ -647,7 +817,7 @@ class DiscourseClient(object):
Returns:
"""
return self._request('POST', path, kwargs)
return self._request(POST, path, kwargs)
def _delete(self, path, **kwargs):
"""
@@ -659,15 +829,16 @@ class DiscourseClient(object):
Returns:
"""
return self._request('DELETE', path, kwargs)
return self._request(DELETE, path, kwargs)
def _request(self, verb, path, params):
"""
Executes HTTP request to API and handles response
Args:
verb:
path:
params:
verb: HTTP verb as string: GET, DELETE, PUT, POST
path: the path on the Discourse API
params: dictionary of parameters to include to the API
Returns:
+5 -4
View File
@@ -27,9 +27,10 @@ import hmac
import hashlib
try: # py3
from urllib.parse import unquote, urlencode
from urllib.parse import unquote, urlencode, parse_qs
except ImportError:
from urllib import unquote, urlencode
from urlparse import parse_qs
from pydiscourse.exceptions import DiscourseError
@@ -63,9 +64,9 @@ def sso_validate(payload, signature, secret):
if this_signature != signature:
raise DiscourseError('Payload does not match signature.')
nonce = decoded.split('=')[1]
return nonce
# Discourse returns querystring encoded value. We only need `nonce`
qs = parse_qs(decoded)
return qs['nonce'][0]
def sso_payload(secret, **kwargs):
+14 -4
View File
@@ -2,21 +2,31 @@ from setuptools import setup, find_packages
README = open('README.rst').read()
VERSION = __import__("pydiscourse").__version__
HISTORY = open('HISTORY.rst').read().replace('.. :changelog:', '')
with open("pydiscourse/__init__.py", "r") as module_file:
for line in module_file:
if line.startswith("__version__"):
version_string = line.split("=")[1]
VERSION = version_string.strip().replace("'", "")
setup(
name="pydiscourse",
version=VERSION,
description="A Python library for the Discourse API",
long_description=README,
long_description=README + '\n\n' + HISTORY,
author="Marc Sibson and contributors",
author_email="ben+pydiscourse@benlopatin.com",
license="BSD",
url="https://github.com/bennylope/pydiscourse",
packages=find_packages(exclude=["tests.*", "tests"]),
install_requires=['requests>=2.0.0'],
tests_require=['mock'],
install_requires=[
'requests>=2.0.0',
],
tests_require=[
'mock',
],
test_suite='tests',
entry_points={
'console_scripts': [
+15 -1
View File
@@ -1,7 +1,6 @@
import sys
import unittest
import mock
import requests
from pydiscourse import client
@@ -121,6 +120,11 @@ class TestUser(ClientBaseTestCase):
self.client.unsuspend(123)
self.assertRequestCalled(request, 'PUT', '/admin/users/123/unsuspend')
def test_user_bagdes(self, request):
prepare_response(request)
self.client.user_badges('username')
self.assertRequestCalled(request, 'GET', '/user-badges/{}.json'.format('username'))
@mock.patch('requests.request')
class TestTopics(ClientBaseTestCase):
@@ -176,3 +180,13 @@ class MiscellaneousTests(ClientBaseTestCase):
prepare_response(request)
self.client.users()
self.assertRequestCalled(request, 'GET', '/admin/users/list/active.json')
def test_badges(self, request):
prepare_response(request)
self.client.badges()
self.assertRequestCalled(request, 'GET', '/admin/badges.json')
def test_grant_badge_to(self, request):
prepare_response(request)
self.client.grant_badge_to('username', 1)
self.assertRequestCalled(request, 'POST', '/user_badges', username='username', badge_id=1)