Compare commits

..

22 Commits

Author SHA1 Message Date
Ben Lopatin 4382449a72 Remove Python 3.7 support 2023-08-31 12:53:06 -04:00
Ben Lopatin dfc0c4173c Update changelog 2023-08-31 12:52:02 -04:00
Ben Lopatin aa2b7eedf6 Bump version 2023-08-31 12:49:19 -04:00
Ben Lopatin 905febc27b Merge pull request #82 from inducer/group-members-pagination
Respect pagination when returning all group members
2023-08-31 12:45:53 -04:00
Andreas Kloeckner 16297f9206 Respect pagination when returning all group members 2023-08-31 12:42:22 -04:00
Ben Lopatin c566377ccb Merge pull request #77 from akhmerov/patch-1
use an up to date endpoint for group owner creation
2023-08-31 12:42:11 -04:00
Anton Akhmerov 9c07f97e87 use an up to date endpoint for group owner creation 2023-08-29 10:19:59 -04:00
Ben Lopatin eb195bb6bc Merge pull request #81 from Dettorer/tox-add-py311
Enable the tests for Python 3.11
2023-08-29 10:19:49 -04:00
Paul Dettorer Hervot 71f9da07c7 Enable the tests for Python 3.11 2023-08-29 14:50:35 +02:00
Ben Lopatin ed4efd82aa Merge pull request #80 from inducer/fix-group-membership-admin
Fix group membership admin functionality
2023-08-15 15:23:09 -04:00
Andreas Kloeckner e33a37b8b9 Fix group membership administration endpoints 2023-08-09 19:40:14 -05:00
Ben Lopatin 50465b4502 Merge pull request #74 from themotleyfool/add-update-category
Add client endpoint for updating category
2023-02-28 20:24:46 -05:00
Max Lancaster e9748279b8 Add client endpoint for updating category 2023-02-28 19:55:26 -05:00
Karl Goetz 7898ff3ff1 Merge pull request #69 from themotleyfool/add-latest-posts
Add endpoint to fetch latest posts across topics
2023-01-25 21:51:55 +11:00
Max Lancaster 9709744b33 Add endpoint to fetch latest posts across topics
Added endpoint for looking up latest posts across topics, see Discourse api documentation here:

https://docs.discourse.org/#tag/Posts/operation/listPosts
2023-01-24 18:21:42 -05:00
Karl Goetz 22e236a009 Merge pull request #72 from bennylope/test-against-python311
Add Python 3.11 to test matrix
2023-01-25 07:23:35 +11:00
Ben Lopatin 0857a9cfe7 Add Python 3.11 to test matrix 2023-01-22 16:53:04 -05:00
Ben Lopatin 30e2068b4d Merge pull request #70 from themotleyfool/fix-github-action-display
Fix python version display in github actions
2023-01-22 16:51:53 -05:00
Ben Lopatin 227924f098 Merge pull request #68 from inducer/rate-limit-improvements
Rate limit improvements
2023-01-22 16:48:05 -05:00
Max Lancaster 201ff3d717 Fix python version display in github actions
test.yml uses the wrong python version variable which is causing to not display the title correctly in the github actions interface, see github documentation here:

https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#using-the-python-starter-workflow
2023-01-17 19:33:35 -05:00
Andreas Kloeckner 393422f964 Rate limiting: print limit name, log before waiting 2022-12-14 17:06:17 -06:00
Andreas Kloeckner d9e0af7e59 Rate-limited: do not fail if no Content-Type header 2022-12-14 17:05:51 -06:00
10 changed files with 106 additions and 31 deletions
+2 -2
View File
@@ -5,11 +5,11 @@ on: [ push, pull_request ]
jobs:
test:
name: Test on Python ${{ matrix.py_version }}
name: Test on Python ${{ matrix.python-version }}
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ "3.7", "3.8", "3.9", "3.10" ]
python-version: [ "3.8", "3.9", "3.10", "3.11" ]
steps:
- uses: actions/checkout@v1
+6
View File
@@ -36,3 +36,9 @@ coverage.xml
# Sphinx documentation
docs/_build/
# Pyenv
.python-version
# PyCharm
.idea
+1 -1
View File
@@ -43,7 +43,7 @@ Or it's slightly faster cousin `detox
Alternatively, you can run the self test with the following commands::
pip install -r requirements.dev.txt
pip install -r requirements.txt
pip install -e .
python setup.py test
+15
View File
@@ -3,6 +3,21 @@
Release history
===============
1.5.0
-----
- Owner creation endpoint update from @akhmerov
- Python 3.11 support from @Dettorer
- Group membership fixes from @inducer
- Rate limiting fixes from @inducer
- Latest posts endpoint from @max-lancaster
1.4.0
-----
- Documented here as skipped release
1.3.0
-----
+2 -2
View File
@@ -51,9 +51,9 @@ copyright = u'2014, Marc Sibson'
# built documents.
#
# The short X.Y version.
version = '1.1'
version = '1.5'
# The full version, including alpha/beta/rc tags.
release = '1.1.1'
release = '1.5.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
+1 -1
View File
@@ -26,10 +26,10 @@ classifiers =
License :: OSI Approved :: MIT License
Operating System :: OS Independent
Programming Language :: Python
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
[options.packages.find]
where=src
+1 -3
View File
@@ -1,8 +1,6 @@
# -*- coding: utf-8 -*-
"""Python client for the Discourse API."""
__version__ = "1.3.0"
__version__ = "1.5.0"
from pydiscourse.client import DiscourseClient
+66 -20
View File
@@ -649,6 +649,21 @@ class DiscourseClient(object):
kwargs["post_ids[]"] = post_ids
return self._get("/t/{0}/posts.json".format(topic_id), **kwargs)
def latest_posts(self, before=None, **kwargs):
"""
List latest posts across topics
Args:
before: Load posts with an id lower than this value. Useful for pagination.
**kwargs:
Returns:
"""
if before:
kwargs["before"] = before
return self._get("/posts.json", **kwargs)
def topic_timings(self, topic_id, time, timings={}, **kwargs):
"""
Set time spent reading a post
@@ -922,6 +937,18 @@ class DiscourseClient(object):
return self._get(u"/c/{0}/show.json".format(category_id), **kwargs)
def update_category(self, category_id, **kwargs):
"""
Args:
category_id:
**kwargs:
Returns:
"""
return self._put("/categories/{0}".format(category_id), json=True, **kwargs)
def delete_category(self, category_id, **kwargs):
"""
Remove category
@@ -1118,9 +1145,7 @@ class DiscourseClient(object):
JSON API response
"""
return self._put(
"/admin/groups/{0}/owners.json".format(groupid), **{"group[usernames]": username}
)
return self.add_group_owners(groupid, [username])
def add_group_owners(self, groupid, usernames):
"""
@@ -1136,7 +1161,7 @@ class DiscourseClient(object):
"""
usernames = ",".join(usernames)
return self._put(
"/admin/groups/{0}/owners.json".format(groupid), **{"group[usernames]": usernames}
"/groups/{0}/owners.json".format(groupid), **{"usernames": usernames}
)
def delete_group_owner(self, groupid, userid):
@@ -1164,13 +1189,28 @@ class DiscourseClient(object):
group = self._get("/groups/{0}/members.json".format(group_name))
return group["owners"]
def _get_paginated_list(self, url, name, offset, **kwargs):
result = []
initial_offset = offset
while True:
kwargs["offset"] = offset
response = self._get(url, **kwargs)
nreturned = len(response[name])
result.extend(response[name])
offset += nreturned
if response["meta"]["total"] == len(result) - initial_offset:
return result
if nreturned == 0:
raise RuntimeError("more items expected, but none returned")
def group_members(self, group_name, offset=0, **kwargs):
"""
Get all members of a group by group name
"""
kwargs["offset"] = offset
group = self._get("/groups/{0}/members.json".format(group_name), **kwargs)
return group["members"]
return self._get_paginated_list(
"/groups/{0}/members.json".format(group_name),
"members", offset, **kwargs)
def add_group_member(self, groupid, username):
"""
@@ -1188,7 +1228,7 @@ class DiscourseClient(object):
"""
return self._put(
"/admin/groups/{0}/members.json".format(groupid), usernames=username
"/groups/{0}/members.json".format(groupid), usernames=username
)
def add_group_members(self, groupid, usernames):
@@ -1208,7 +1248,8 @@ class DiscourseClient(object):
"""
usernames = ",".join(usernames)
return self._put(
"/admin/groups/{0}/members.json".format(groupid), usernames=usernames
"/groups/{0}/members.json".format(groupid), usernames=usernames,
json=True,
)
def add_user_to_group(self, groupid, userid):
@@ -1228,7 +1269,7 @@ class DiscourseClient(object):
"""
return self._post("/admin/users/{0}/groups".format(userid), group_id=groupid)
def delete_group_member(self, groupid, userid):
def delete_group_member(self, groupid, username):
"""
Deletes a member from a group by user ID
@@ -1236,15 +1277,16 @@ class DiscourseClient(object):
Args:
groupid: the ID of the group
userid: the ID of the user
username: the user name of the user
Returns:
JSON API response
"""
return self._delete(
"/admin/groups/{0}/members.json".format(groupid), user_id=userid
)
return self._request(
DELETE, "/groups/{0}/members.json".format(groupid),
json={"usernames": username})
def color_schemes(self, **kwargs):
"""
@@ -1561,7 +1603,8 @@ class DiscourseClient(object):
if 400 <= response.status_code < 500:
if 429 == response.status_code:
# This codepath relies on wait_seconds from Discourse v2.0.0.beta3 / v1.9.3 or higher.
if "application/json" in response.headers.get("Content-Type"):
content_type = response.headers.get("Content-Type")
if content_type is not None and "application/json" in content_type:
ret = response.json()
wait_delay = (
retry_backoff + ret["extras"]["wait_seconds"]
@@ -1571,14 +1614,17 @@ class DiscourseClient(object):
ret = response.content
wait_delay = retry_backoff + 10
limit_name = response.headers.get(
"Discourse-Rate-Limit-Error-Code", "<unknown>")
log.info(
"We have been rate limited (limit: {2}) and will wait {0} seconds ({1} retries left)".format(
wait_delay, retry_count, limit_name
)
)
if retry_count > 1:
time.sleep(wait_delay)
retry_count -= 1
log.info(
"We have been rate limited and waited {0} seconds ({1} retries left)".format(
wait_delay, retry_count
)
)
log.debug("API returned {0}".format(ret))
continue
else:
+10
View File
@@ -182,6 +182,11 @@ class TestTopics(ClientBaseTestCase):
@mock.patch("pydiscourse.client.requests.request")
class MiscellaneousTests(ClientBaseTestCase):
def test_latest_posts(self, request):
prepare_response(request)
r = self.client.latest_posts(before=54321)
self.assertRequestCalled(request, "GET", "/posts.json", before=54321)
def test_search(self, request):
prepare_response(request)
self.client.search("needle")
@@ -193,6 +198,11 @@ class MiscellaneousTests(ClientBaseTestCase):
self.assertRequestCalled(request, "GET", "/categories.json")
self.assertEqual(r, request().json()["category_list"]["categories"])
def test_update_category(self, request):
prepare_response(request)
self.client.update_category(123, a="a", b="b")
self.assertRequestCalled(request, "PUT", "/categories/123", a="a", b="b")
def test_users(self, request):
prepare_response(request)
self.client.users()
+2 -2
View File
@@ -1,12 +1,12 @@
[tox]
envlist = py37, py38, py39, py310
envlist = py37, py38, py39, py310, py311
[gh-actions]
python =
3.7: py37
3.8: py38
3.9: py39
3.10: py310
3.11: py311
[testenv]
setenv =