Compare commits

..

1 Commits

Author SHA1 Message Date
Ben Lopatin 5dd1dab051 Run test workflow on pull request 2019-10-13 18:15:51 -04:00
22 changed files with 178 additions and 466 deletions
-3
View File
@@ -1,3 +0,0 @@
c0db7215c95dbd31770ade1fc6ea65aa426d4590
0177c46356b9d0fc4b93f09aab7a224643a3685e
f6b4c02fc0f144dffc88cdd48b8261a69228d2f0
-33
View File
@@ -1,33 +0,0 @@
name: Publish package to PyPI
on:
push:
branches: [master]
release:
types: [created]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine
- name: Build and publish distribution
if: (github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')) || github.event_name == 'release'
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
python setup.py sdist bdist_wheel
twine upload dist/*
+12 -14
View File
@@ -1,25 +1,23 @@
name: Tests
on: [ push, pull_request ]
on: [push, pull_request]
jobs:
test:
name: Test on Python ${{ matrix.python-version }}
name: Test on Python ${{ matrix.py_version }}
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11" ]
py_version: [2.7, 3.5, 3.6, 3.7]
steps:
- uses: actions/checkout@v1
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install tox tox-gh-actions
- name: Test with tox
run: tox
- uses: actions/checkout@v1
- name: Set up Python ${{ matrix.py_version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.py_version }}
- name: Install mock for Python 2.7
run: pip install mock
- name: Run tests
run: python setup.py test
-6
View File
@@ -36,9 +36,3 @@ coverage.xml
# Sphinx documentation
docs/_build/
# Pyenv
.python-version
# PyCharm
.idea
+11
View File
@@ -0,0 +1,11 @@
sudo: false
language: python
python:
- "2.7"
- "3.4"
- "3.5"
- "3.6"
- "pypy"
- "pypy3"
script: python setup.py test
+1 -4
View File
@@ -9,7 +9,4 @@ Scott Nixon
Jason Dorweiler
Pierre-Alain Dupont
Karl Goetz
Alex Kerney
Gustav <https://github.com/dkgv>
Sebastian2023 <https://github.com/Sebastian2023>
Dominik George <https://github.com/Natureshadow>
+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.txt
pip install -r requirements.dev.txt
pip install -e .
python setup.py test
-35
View File
@@ -3,41 +3,6 @@
Release history
===============
1.3.0
-----
- Add fix for handling global Discourse timeouts
- Add group owners
- Update API for add_group_owner
1.2.0
-----
- BREAKING? Dropped support for Python 2.7, 3.4, 3.5
- Added numerous new endpoint queries
- Updated category querying
1.1.2
-----
- Fix for Discourse users API change
1.1.1
-----
- Fix for empty dictionary and 413 API response
- Fix for getting member groups
1.1.0
-----
- Added ability to follow redirects in requests
1.0.0
-----
- Authenticate with headers
0.9.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 = '0.9'
# The full version, including alpha/beta/rc tags.
release = '1.1.1'
release = '0.9.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
+5
View File
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
__version__ = "0.9.0"
from pydiscourse.client import DiscourseClient
@@ -128,30 +128,6 @@ class DiscourseClient(object):
**kwargs
)
def user_by_id(self, pk):
"""
Get user from ID
Args:
pk: user id
Returns:
user
"""
return self._get("/admin/users/{0}.json".format(pk))
def user_by_email(self, email):
"""
Get user from email
Args:
email: user email
Returns:
user
"""
return self._get("/admin/users/list/all.json?email={0}".format(email))
def create_user(self, name, username, email, password, **kwargs):
"""
Create a Discourse user
@@ -171,7 +147,7 @@ class DiscourseClient(object):
????
"""
r = self._get("/session/hp.json")
r = self._get("/users/hp.json")
challenge = r["challenge"][::-1] # reverse challenge, discourse security check
confirmations = r["value"]
return self._post(
@@ -395,7 +371,7 @@ class DiscourseClient(object):
"""
return self._put(
"/users/{0}/preferences/username".format(username),
new_username=new_username,
username=new_username,
**kwargs
)
@@ -513,14 +489,8 @@ class DiscourseClient(object):
JSON API response
"""
return self._get(
"/c/{0}.json".format(category_id),
override_request_kwargs={"allow_redirects": True},
**kwargs
)
return self._get("/c/{0}.json".format(category_id), **kwargs)
# Doesn't work on recent Discourse versions (2014+)
# https://github.com/discourse/discourse_api/pull/204
def hot_topics(self, **kwargs):
"""
@@ -532,15 +502,6 @@ class DiscourseClient(object):
"""
return self._get("/hot.json", **kwargs)
def top_topics(self, **kwargs):
"""
Get top topics
Returns:
List of top topics
"""
return self._get("/top.json", **kwargs)
def latest_topics(self, **kwargs):
"""
@@ -603,36 +564,6 @@ class DiscourseClient(object):
"""
return self._get("/t/{0}/{1}.json".format(topic_id, post_id), **kwargs)
def post_action_users(self, post_id, post_action_type_id=None, **kwargs):
"""
Args:
post_id: int
post_action_type_id: Optional[int]
**kwargs:
Returns:
"""
# https://meta.discourse.org/t/getting-who-liked-a-post-from-the-api/103618
kwargs["id"] = post_id
if post_action_type_id is not None:
kwargs["post_action_type_id"] = post_action_type_id
return self._get("/post_action_users", **kwargs)
def post_by_id(self, post_id, **kwargs):
"""
Get a post from its id
Args:
post_id: id of the post
**kwargs:
Returns:
post
"""
return self._get("/posts/{0}.json".format(post_id), **kwargs)
def posts(self, topic_id, post_ids=None, **kwargs):
"""
Get a set of posts from a topic
@@ -649,21 +580,6 @@ 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
@@ -776,14 +692,6 @@ class DiscourseClient(object):
kwargs["post[edit_reason]"] = edit_reason
return self._put("/posts/{0}".format(post_id), **kwargs)
def reset_bump_date(self, topic_id, **kwargs):
"""
Reset bump date
See https://meta.discourse.org/t/what-is-a-bump/105562
"""
return self._put("/t/{0}/reset-bump-date".format(topic_id), **kwargs)
def topics_by(self, username, **kwargs):
"""
@@ -924,18 +832,21 @@ class DiscourseClient(object):
"""
return self._get("/categories.json", **kwargs)["category_list"]["categories"]
def category(self, category_id, parent=None, **kwargs):
def category(self, name, parent=None, **kwargs):
"""
Args:
category_id:
name:
parent:
**kwargs:
Returns:
"""
if parent:
name = u"{0}/{1}".format(parent, name)
return self._get(u"/c/{0}/show.json".format(category_id), **kwargs)
return self._get(u"/category/{0}.json".format(name), **kwargs)
def delete_category(self, category_id, **kwargs):
"""
@@ -950,32 +861,12 @@ class DiscourseClient(object):
"""
return self._delete(u"/categories/{0}".format(category_id), **kwargs)
def get_site_info(self):
"""
Get site info to fetch all categories and subcategories
"""
return self._get("/site.json")
def get_site_settings(self):
"""
Get site settings
"""
return self._get("/admin/site_settings.json")
def category_latest_topics(self, name, parent=None, **kwargs):
"""
Get latest topics from a category
"""
if parent:
name = u"{0}/{1}".format(parent, name)
return self._get(u"/c/{0}/l/latest.json".format(name), **kwargs)
def site_settings(self, **kwargs):
"""
Update site settings
Args:
**kwargs: key-value of properties to update
settings:
**kwargs:
Returns:
@@ -1054,7 +945,7 @@ class DiscourseClient(object):
"""
Get all infos of a group by group name
"""
return self._get("/groups/{0}.json".format(group_name))
return self._get("/groups/{0}/members.json".format(group_name))
def create_group(
self,
@@ -1134,24 +1025,7 @@ class DiscourseClient(object):
"""
return self._put(
"/admin/groups/{0}/owners.json".format(groupid), **{"group[usernames]": username}
)
def add_group_owners(self, groupid, usernames):
"""
Add a list of owners to a group by usernames
Args:
groupid: the ID of the group
username: the list of new owner usernames
Returns:
JSON API response
"""
usernames = ",".join(usernames)
return self._put(
"/admin/groups/{0}/owners.json".format(groupid), **{"group[usernames]": usernames}
"/admin/groups/{0}/owners.json".format(groupid), usernames=username
)
def delete_group_owner(self, groupid, userid):
@@ -1407,29 +1281,7 @@ class DiscourseClient(object):
kwargs["parent_tag_name"] = parent_tag_name
return self._post("/tag_groups", json=True, **kwargs)["tag_group"]
def data_explorer_query(self, query_id, **kwargs):
"""
Run a query with database explorer plugin.
Requires discourse-data-explorer installed
https://github.com/discourse/discourse-data-explorer
"""
return self._post(
"/admin/plugins/explorer/queries/{}/run".format(query_id), **kwargs
)
def notifications(self, category_id, **kwargs):
"""
Get notifications
Args:
category_id
**kwargs:
notification_level=(int)
"""
return self._post("/category/{}/notifications".format(category_id), **kwargs)
def _get(self, path, override_request_kwargs=None, **kwargs):
def _get(self, path, **kwargs):
"""
Args:
@@ -1439,11 +1291,9 @@ class DiscourseClient(object):
Returns:
"""
return self._request(
GET, path, params=kwargs, override_request_kwargs=override_request_kwargs
)
return self._request(GET, path, params=kwargs)
def _put(self, path, json=False, override_request_kwargs=None, **kwargs):
def _put(self, path, json=False, **kwargs):
"""
Args:
@@ -1454,18 +1304,12 @@ class DiscourseClient(object):
"""
if not json:
return self._request(
PUT, path, data=kwargs, override_request_kwargs=override_request_kwargs
)
return self._request(PUT, path, data=kwargs)
else:
return self._request(
PUT, path, json=kwargs, override_request_kwargs=override_request_kwargs
)
return self._request(PUT, path, json=kwargs)
def _post(
self, path, files=None, json=False, override_request_kwargs=None, **kwargs
):
def _post(self, path, files={}, json=False, **kwargs):
"""
Args:
@@ -1476,24 +1320,12 @@ class DiscourseClient(object):
"""
if not json:
return self._request(
POST,
path,
files=files,
data=kwargs,
override_request_kwargs=override_request_kwargs,
)
return self._request(POST, path, files=files, data=kwargs)
else:
return self._request(
POST,
path,
files=files,
json=kwargs,
override_request_kwargs=override_request_kwargs,
)
return self._request(POST, path, files=files, json=kwargs)
def _delete(self, path, override_request_kwargs=None, **kwargs):
def _delete(self, path, **kwargs):
"""
Args:
@@ -1503,20 +1335,9 @@ class DiscourseClient(object):
Returns:
"""
return self._request(
DELETE, path, params=kwargs, override_request_kwargs=override_request_kwargs
)
return self._request(DELETE, path, params=kwargs)
def _request(
self,
verb,
path,
params=None,
files=None,
data=None,
json=None,
override_request_kwargs=None,
):
def _request(self, verb, path, params={}, files={}, data={}, json={}):
"""
Executes HTTP request to API and handles response
@@ -1524,22 +1345,17 @@ class DiscourseClient(object):
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
override_request_kwargs: dictionary of requests.request
keyword arguments to override defaults
Returns:
dictionary of response body data or None
"""
override_request_kwargs = override_request_kwargs or {}
params["api_key"] = self.api_key
if "api_username" not in params:
params["api_username"] = self.api_username
url = self.host + path
headers = {
"Accept": "application/json; charset=utf-8",
"Api-Key": self.api_key,
"Api-Username": self.api_username,
}
headers = {"Accept": "application/json; charset=utf-8"}
# How many times should we retry if rate limited
retry_count = 4
@@ -1547,7 +1363,9 @@ class DiscourseClient(object):
retry_backoff = 1
while retry_count > 0:
request_kwargs = dict(
response = requests.request(
verb,
url,
allow_redirects=False,
params=params,
files=files,
@@ -1557,10 +1375,6 @@ class DiscourseClient(object):
timeout=self.timeout,
)
request_kwargs.update(override_request_kwargs)
response = requests.request(verb, url, **request_kwargs)
log.debug("response %s: %s", response.status_code, repr(response.text))
if response.ok:
break
@@ -1576,29 +1390,20 @@ 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.
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"]
) # how long to back off for.
else:
# We got an early 429 error without a proper JSON body
ret = response.content
wait_delay = retry_backoff + 10
rj = response.json()
wait_delay = (
retry_backoff + rj["extras"]["wait_seconds"]
) # how long to back off for.
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.debug("API returned {0}".format(ret))
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(rj))
continue
else:
raise DiscourseClientError(msg, response=response)
@@ -1636,10 +1441,7 @@ class DiscourseClient(object):
except ValueError:
raise DiscourseError("failed to decode response", response=response)
# Checking "errors" length because
# data-explorer (e.g. POST /admin/plugins/explorer/queries/{}/run)
# sends an empty errors array
if "errors" in decoded and len(decoded["errors"]) > 0:
if "errors" in decoded:
message = decoded.get("message")
if not message:
message = u",".join(decoded["errors"])
+17
View File
@@ -0,0 +1,17 @@
from requests.exceptions import HTTPError
class DiscourseError(HTTPError):
""" A generic error while attempting to communicate with Discourse """
class DiscourseServerError(DiscourseError):
""" The Discourse Server encountered an error while processing the request """
class DiscourseClientError(DiscourseError):
""" An invalid request has been made """
class DiscourseRateLimitedError(DiscourseError):
""" Request required more than the permissible number of retries """
@@ -1,7 +1,5 @@
#!/usr/bin/env python
"""Simple command line interface for making Discourse API queries."""
import cmd
import json
import logging
@@ -14,19 +12,15 @@ from pydiscourse.client import DiscourseClient, DiscourseError
class DiscourseCmd(cmd.Cmd):
"""Handles CLI commands"""
prompt = "discourse>"
output = sys.stdout
def __init__(self, client):
"""Initialize command"""
cmd.Cmd.__init__(self)
self.client = client
self.prompt = "%s>" % self.client.host
def __getattr__(self, attr):
"""Gets attributes with dynamic name handling"""
if attr.startswith("do_"):
method = getattr(self.client, attr[3:])
@@ -54,7 +48,6 @@ class DiscourseCmd(cmd.Cmd):
raise AttributeError
def postcmd(self, result, line):
"""Writes output of the command to console"""
try:
json.dump(
result, self.output, sort_keys=True, indent=4, separators=(",", ": ")
@@ -64,7 +57,6 @@ class DiscourseCmd(cmd.Cmd):
def main():
"""Runs the CLI application"""
op = optparse.OptionParser()
op.add_option("--host", default="http://localhost:4000")
op.add_option("--api-user", default="system")
+13 -11
View File
@@ -1,4 +1,6 @@
"""Implement Single Sign On for Discourse with a Python managed auth DB.
"""
Utilities to implement Single Sign On for Discourse with a Python managed
authentication DB
https://meta.discourse.org/t/official-single-sign-on-for-discourse/13045
@@ -24,20 +26,23 @@ from base64 import b64encode, b64decode
import hmac
import hashlib
from urllib.parse import unquote, urlencode, parse_qs
try: # py3
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
def sso_validate(payload, signature, secret):
"""Validates SSO payload.
Args:
"""
payload: provided by Discourse HTTP call to your SSO endpoint as sso GET param
signature: provided by Discourse HTTP call to your SSO endpoint as sig GET param
secret: the secret key you entered into Discourse sso secret
return value: The nonce used by discourse to validate the redirect URL
return value: The nonce used by discourse to validate the redirect URL
"""
if None in [payload, signature]:
raise DiscourseError("No SSO payload or signature.")
@@ -67,7 +72,6 @@ def sso_validate(payload, signature, secret):
def sso_payload(secret, **kwargs):
"""Returns an encoded SSO payload"""
return_payload = b64encode(urlencode(kwargs).encode("utf-8"))
h = hmac.new(secret.encode("utf-8"), return_payload, digestmod=hashlib.sha256)
query_string = urlencode({"sso": return_payload, "sig": h.hexdigest()})
@@ -75,16 +79,14 @@ def sso_payload(secret, **kwargs):
def sso_redirect_url(nonce, secret, email, external_id, username, **kwargs):
"""Returns the Discourse redirection URL.
Args:
"""
nonce: returned by sso_validate()
secret: the secret key you entered into Discourse sso secret
user_email: email address of the user who logged in
user_id: the internal id of the logged in user
user_username: username of the logged in user
return value: URL to redirect users back to discourse, now logged in as user_username
return value: URL to redirect users back to discourse, now logged in as user_username
"""
kwargs.update(
{
-2
View File
@@ -1,2 +0,0 @@
pytest==6.2.5
pytest-cov==3.0.0
+1 -46
View File
@@ -1,47 +1,2 @@
[metadata]
name = pydiscourse
version = attr: pydiscourse.__version__
author = "Marc Sibson and contributors"
author_email = "ben@benlopatin.com"
license = "MIT"
url = https://github.com/bennylope/pydiscourse
description = "A Python library for the Discourse API"
long_description = file: README.rst, HISTORY.rst
platforms =
OS Independent
[options]
zip_safe = False
include_package_data = True
packages = find:
package_dir =
=src
install_requires =
requests>=2.4.2
typing; python_version<"3.6"
classifiers =
Development Status :: 5 - Production/Stable
Environment :: Web Environment
Intended Audience :: Developers
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
[options.packages.find]
where=src
[options.entry_points]
console_scripts =
pydiscoursecli = pydiscourse.main:main
[bdist_wheel]
[wheel]
universal = 1
[build-system]
requires =
setuptools >= "40.9.0"
wheel
+49 -6
View File
@@ -1,7 +1,50 @@
# -*- coding: utf-8 -*-
"""
See setup.cfg for packaging settings
"""
from setuptools import setup, find_packages
from setuptools import setup
setup()
README = open('README.rst').read()
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 + '\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.4.2',
],
tests_require=[
'mock',
],
test_suite='tests',
entry_points={
'console_scripts': [
'pydiscoursecli = pydiscourse.main:main'
]
},
classifiers=[
"Development Status :: 5 - Production/Stable"
"Environment :: Web Environment",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
'Programming Language :: Python :: Implementation :: PyPy',
],
zip_safe=False,
)
-10
View File
@@ -1,10 +0,0 @@
# -*- coding: utf-8 -*-
"""Python client for the Discourse API."""
__version__ = "1.3.0"
from pydiscourse.client import DiscourseClient
__all__ = ["DiscourseClient"]
-19
View File
@@ -1,19 +0,0 @@
"""API exceptions."""
from requests.exceptions import HTTPError
class DiscourseError(HTTPError):
"""A generic error while attempting to communicate with Discourse"""
class DiscourseServerError(DiscourseError):
"""The Discourse Server encountered an error while processing the request"""
class DiscourseClientError(DiscourseError):
"""An invalid request has been made"""
class DiscourseRateLimitedError(DiscourseError):
"""Request required more than the permissible number of retries"""
+8 -10
View File
@@ -1,7 +1,10 @@
import sys
import unittest
from unittest import mock
try:
from unittest import mock
except ImportError:
import mock
from pydiscourse import client
@@ -46,12 +49,12 @@ class ClientBaseTestCase(unittest.TestCase):
self.assertEqual(args[0], verb)
self.assertEqual(args[1], self.host + url)
headers = kwargs["headers"]
self.assertEqual(headers.pop("Api-Username"), self.api_username)
self.assertEqual(headers.pop("Api-Key"), self.api_key)
kwargs = kwargs["params"]
self.assertEqual(kwargs.pop("api_username"), self.api_username)
self.assertEqual(kwargs.pop("api_key"), self.api_key)
if verb == "GET":
self.assertEqual(kwargs["params"], params)
self.assertEqual(kwargs, params)
class TestClientRequests(ClientBaseTestCase):
@@ -182,11 +185,6 @@ 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")
+12 -3
View File
@@ -1,9 +1,18 @@
from base64 import b64decode
import unittest
try: # py26
import unittest2 as unittest
except ImportError:
import unittest
try: # py3
from urllib.parse import unquote
from urllib.parse import urlparse, parse_qs
except ImportError:
from urlparse import urlparse, parse_qs
from urllib import unquote
from urllib.parse import unquote
from urllib.parse import urlparse, parse_qs
from pydiscourse import sso
from pydiscourse.exceptions import DiscourseError
+4 -13
View File
@@ -1,19 +1,10 @@
[tox]
envlist = py37, py38, py39, py310
[gh-actions]
python =
3.7: py37
3.8: py38
3.9: py39
3.10: py310
envlist = py27, py34, py35, py36, py37, pypy, pypy3
[testenv]
setenv =
PYTHONPATH = {toxinidir}:{toxinidir}/pydiscourse
commands = pytest {posargs} --cov=pydiscourse
deps =
-r{toxinidir}/requirements.txt
commands = python setup.py test
[testenv:flake8]
basepython=python
@@ -21,10 +12,10 @@ deps=
flake8
flake8_docstrings
commands=
flake8 src/pydiscourse --docstring-convention google --ignore D415
flake8 pydiscourse
[flake8]
ignore = E126,E128
max-line-length = 119
max-line-length = 99
exclude = .ropeproject
max-complexity = 10