Hello community,
here is the log from the commit of package python-django-eremaea2 for
openSUSE:Factory checked in at 2020-12-02 13:58:18
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-django-eremaea2 (Old)
and /work/SRC/openSUSE:Factory/.python-django-eremaea2.new.5913 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-django-eremaea2"
Wed Dec 2 13:58:18 2020 rev:2 rq:852354 version:2.0.16
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-django-eremaea2/python-django-eremaea2.changes
2020-09-01 20:11:13.328699211 +0200
+++
/work/SRC/openSUSE:Factory/.python-django-eremaea2.new.5913/python-django-eremaea2.changes
2020-12-02 13:58:20.389808627 +0100
@@ -1,0 +2,6 @@
+Tue Dec 1 08:25:48 UTC 2020 - Matwey Kornilov <[email protected]>
+
+- Version 2.0.16
+ - Support HTTP Digest Auth in eremaeactl tool
+
+-------------------------------------------------------------------
Old:
----
django-eremaea2-2.0.15.tar.gz
New:
----
django-eremaea2-2.0.16.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-django-eremaea2.spec ++++++
--- /var/tmp/diff_new_pack.rExDYu/_old 2020-12-02 13:58:20.937809205 +0100
+++ /var/tmp/diff_new_pack.rExDYu/_new 2020-12-02 13:58:20.937809205 +0100
@@ -20,7 +20,7 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
%define skip_python2 1
Name: python-django-eremaea2
-Version: 2.0.15
+Version: 2.0.16
Release: 0
Summary: A simple Django application to store and show webcam snapshots
License: BSD-2-Clause
@@ -34,6 +34,8 @@
BuildRequires: %{python_module django-dj-inmemorystorage}
BuildRequires: %{python_module djangorestframework >= 3.7.0}
BuildRequires: %{python_module mock}
+BuildRequires: %{python_module requests-mock}
+BuildRequires: %{python_module requests-toolbelt}
BuildRequires: %{python_module requests}
BuildRequires: %{python_module setuptools}
BuildRequires: %{python_module six}
@@ -46,6 +48,7 @@
Requires: python-cmdln
Requires: python-djangorestframework >= 3.7.0
Requires: python-requests
+Requires: python-requests-toolbelt
Requires: python-six
Requires(post): update-alternatives
Requires(postun): update-alternatives
++++++ django-eremaea2-2.0.15.tar.gz -> django-eremaea2-2.0.16.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-eremaea2-2.0.15/PKG-INFO
new/django-eremaea2-2.0.16/PKG-INFO
--- old/django-eremaea2-2.0.15/PKG-INFO 2020-08-28 15:04:32.000000000 +0200
+++ new/django-eremaea2-2.0.16/PKG-INFO 2020-12-01 04:36:37.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: django-eremaea2
-Version: 2.0.15
+Version: 2.0.16
Summary: A simple Django application to store and show webcam snapshots
Home-page: https://github.com/matwey/django-eremaea2
Author: Matwey V. Kornilov
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django-eremaea2-2.0.15/django_eremaea2.egg-info/PKG-INFO
new/django-eremaea2-2.0.16/django_eremaea2.egg-info/PKG-INFO
--- old/django-eremaea2-2.0.15/django_eremaea2.egg-info/PKG-INFO
2020-08-28 15:04:32.000000000 +0200
+++ new/django-eremaea2-2.0.16/django_eremaea2.egg-info/PKG-INFO
2020-12-01 04:36:37.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: django-eremaea2
-Version: 2.0.15
+Version: 2.0.16
Summary: A simple Django application to store and show webcam snapshots
Home-page: https://github.com/matwey/django-eremaea2
Author: Matwey V. Kornilov
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-eremaea2-2.0.15/eremaea/ctl/client.py
new/django-eremaea2-2.0.16/eremaea/ctl/client.py
--- old/django-eremaea2-2.0.15/eremaea/ctl/client.py 2020-08-28
15:04:08.000000000 +0200
+++ new/django-eremaea2-2.0.16/eremaea/ctl/client.py 2020-12-01
04:36:06.000000000 +0100
@@ -1,38 +1,37 @@
import requests
class Client(object):
- def __init__(self, api, **kwargs):
+ def __init__(self, api, token = None):
self.api = api.rstrip('/')
- self.kwargs = kwargs
- self._common_headers = self._get_common_headers()
- def _get_common_headers(self):
- headers = {}
- if 'token' in self.kwargs:
- headers['Authorization'] = 'Token ' +
self.kwargs['token']
- return headers
+ self._session = requests.Session()
+
+ if token is not None:
+ self._session.headers.update({'Authorization': 'Token
{}'.format(token)})
+
def upload(self, file, collection, retention_policy = None):
url = self.api + '/snapshots/'
- headers = self._common_headers
- headers['Content-Disposition'] = 'attachment; filename=' +
file.name
- headers['Content-Type'] = file.mimetype
+ headers = {
+ 'Content-Disposition': 'attachment;
filename=\"{}\"'.format(file.name),
+ 'Content-Type': file.mimetype
+ }
params = {'collection': collection}
if retention_policy:
params['retention_policy'] = retention_policy
- r = requests.post(url, params=params, headers=headers,
data=file.read())
+ r = self._session.post(url, params=params, headers=headers,
data=file.content)
if r.status_code == 201:
return True
r.raise_for_status()
+
def purge(self, retention_policy):
url = self.api + '/retention_policies/' + retention_policy +
"/purge/"
- headers = self._common_headers
- r = requests.post(url, headers=headers)
+ r = self._session.post(url)
if r.status_code == 201:
return True
r.raise_for_status()
+
def retention_policies(self):
url = self.api + '/retention_policies/'
- headers = self._common_headers
- r = requests.get(url, headers=headers)
+ r = self._session.get(url)
if r.status_code == 200:
return [x["name"] for x in r.json()]
r.raise_for_status()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-eremaea2-2.0.15/eremaea/ctl/commandline.py
new/django-eremaea2-2.0.16/eremaea/ctl/commandline.py
--- old/django-eremaea2-2.0.15/eremaea/ctl/commandline.py 2020-08-28
15:04:08.000000000 +0200
+++ new/django-eremaea2-2.0.16/eremaea/ctl/commandline.py 2020-12-01
04:36:06.000000000 +0100
@@ -1,7 +1,7 @@
import cmdln
import time
import sys
-from eremaea.ctl.file import FileFactory
+from eremaea.ctl.file import create_stream
from eremaea.ctl.client import Client
from django.utils.dateparse import parse_duration
@@ -31,7 +31,7 @@
${cmd_usage}
${cmd_option_list}
"""
- self.client.upload(FileFactory().create(file), collection,
opts.retention_policy)
+ self.client.upload(next(create_stream(file)), collection,
opts.retention_policy)
@cmdln.option("--all", dest="all", action="store_true", help="purge all
retention policies")
def do_purge(self, subcmd, opts, *retention_policies):
"""${cmd_name}: purge retention policies
@@ -55,10 +55,10 @@
${cmd_option_list}
"""
duration = parse_duration(opts.interval)
- filetype = FileFactory().resolve(file)
+ stream = create_stream(file)
while True:
try:
- self.client.upload(filetype(file), collection,
opts.retention_policy)
+ self.client.upload(next(stream), collection,
opts.retention_policy)
except Exception as e:
if not opts.quite:
sys.stderr.write(str(e) + "\n")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-eremaea2-2.0.15/eremaea/ctl/file.py
new/django-eremaea2-2.0.16/eremaea/ctl/file.py
--- old/django-eremaea2-2.0.15/eremaea/ctl/file.py 2020-08-28
15:04:08.000000000 +0200
+++ new/django-eremaea2-2.0.16/eremaea/ctl/file.py 2020-12-01
04:36:06.000000000 +0100
@@ -1,68 +1,72 @@
+from collections import namedtuple
+from six import Iterator
from six.moves.urllib.parse import urlparse
import os.path
-import requests
import mimetypes
+import requests
+from requests_toolbelt.auth.guess import GuessAuth
+
-class File(object):
- def __init__(self, name, mimetype):
- self.name = name
- self.mimetype = mimetype
- def read(self):
- raise NotImplementedError("File.read")
-
-class LocalFile(File):
- def __init__(self, path):
- self._file = open(path, "rb")
- (mimetype, encoding) = mimetypes.guess_type(path)
- name = os.path.basename(path)
- super(LocalFile, self).__init__(name, mimetype)
- def read(self):
- return self._file.read()
-
-class ContentFile(File):
- def __init__(self, content, name, mimetype):
- self.content = content
- super(ContentFile, self).__init__(name, mimetype)
- def read(self):
- return self.content
+File = namedtuple("File", ("name", "mimetype", "content"))
-class HTTPFile(File):
+class Stream(Iterator):
def __init__(self, url):
- creds = None
+ self._url = url
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ raise NotImplementedError
+
+class LocalFileStream(Stream):
+ def __init__(self, url):
+ super(LocalFileStream, self).__init__(url)
+
+ def __next__(self):
+ with open(self._url, "rb") as f:
+ (mimetype, _encoding) = mimetypes.guess_type(self._url)
+ name = os.path.basename(self._url)
+ return File(name, mimetype, f.read())
+
+class HTTPFileStream(Stream):
+ def __init__(self, url):
+ super(HTTPFileStream, self).__init__(url)
+
parsed_url = urlparse(url)
+
+ self._name = [x for x in parsed_url.path.rsplit("/") if x][-1]
+ self._session = requests.Session()
+ self._session.allow_redirects = True
+
if parsed_url.username is not None:
login = parsed_url.username
- passwd = parsed_url.password
- if parsed_url.password is None:
- passwd = ''
- creds = (login, passwd)
- self.response = requests.get(url, allow_redirects=True,
auth=creds)
- self.response.raise_for_status()
- name = [x for x in urlparse(url).path.rsplit("/") if x][-1]
- mimetype = None
- if 'content-type' in self.response.headers:
- mimetype = self.response.headers['content-type']
- if not mimetype:
- (mimetype, encoding) = mimetypes.guess_type(name)
- super(HTTPFile, self).__init__(name, mimetype)
- def read(self):
- return self.response.content
-
-class FileFactory(object):
- def __init__(self):
- self._scheme = {}
- self._scheme[''] = LocalFile
- self._scheme['http'] = HTTPFile
- self._scheme['https'] = HTTPFile
-
- def resolve(self, url):
- scheme = urlparse(url).scheme
- if scheme in self._scheme:
- return self._scheme[scheme]
- return None
- def create(self, url):
- filetype = self.resolve(url)
- if filetype:
- return filetype(url)
- return None
+ passwd = parsed_url.password if parsed_url.password is
not None else ""
+ self._session.auth = GuessAuth(login, passwd)
+
+ def __next__(self):
+ r = self._session.get(self._url)
+ r.raise_for_status()
+
+ mimetype = r.headers.get('content-type')
+ if mimetype is None:
+ (mimetype, _encoding) = mimetypes.guess_type(self._name)
+
+ return File(self._name, mimetype, r.content)
+
+
+def create_stream(url):
+ streams = {
+ '': LocalFileStream,
+ 'http': HTTPFileStream,
+ 'https': HTTPFileStream,
+ }
+
+ parsed_url = urlparse(url)
+
+ stream_class = streams.get(parsed_url.scheme)
+
+ if stream_class is not None:
+ return stream_class(url)
+ return None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-eremaea2-2.0.15/setup.py
new/django-eremaea2-2.0.16/setup.py
--- old/django-eremaea2-2.0.15/setup.py 2020-08-28 15:04:08.000000000 +0200
+++ new/django-eremaea2-2.0.16/setup.py 2020-12-01 04:36:06.000000000 +0100
@@ -1,5 +1,4 @@
import os
-import sys
from setuptools import setup
with open(os.path.join(os.path.dirname(__file__), 'README.md')) as readme:
@@ -10,7 +9,7 @@
setup(
name='django-eremaea2',
- version='2.0.15',
+ version='2.0.16',
packages=['eremaea','eremaea.ctl','eremaea.migrations'],
entry_points={'console_scripts': [
'eremaeactl = eremaea.ctl.commandline:execute_from_commandline',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-eremaea2-2.0.15/tests/ctl/client.py
new/django-eremaea2-2.0.16/tests/ctl/client.py
--- old/django-eremaea2-2.0.15/tests/ctl/client.py 2020-08-28
15:04:08.000000000 +0200
+++ new/django-eremaea2-2.0.16/tests/ctl/client.py 2020-12-01
04:36:06.000000000 +0100
@@ -1,11 +1,10 @@
from datetime import datetime, timedelta
-from django.core.files.base import ContentFile as DjangoContentFile
+from django.core.files.base import ContentFile
from django.contrib.auth.models import User
-from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from django.test import LiveServerTestCase
from eremaea import models
from eremaea.ctl.client import Client
-from eremaea.ctl.file import ContentFile
+from eremaea.ctl.file import File
from rest_framework.authentication import TokenAuthentication
from rest_framework.authtoken.models import Token
from rest_framework.permissions import IsAuthenticatedOrReadOnly
@@ -17,19 +16,23 @@
from mock import patch
class ClientTest(LiveServerTestCase):
+ @property
+ def api_endpoint(self):
+ return self.live_server_url
+
def setUp(self):
- self.client = Client(self.live_server_url)
+ self.client = Client(self.api_endpoint)
def test_upload1(self):
content = b"123"
retention_policy =
models.RetentionPolicy.objects.create(name="hourly",
duration=timedelta(hours=1))
collection = models.Collection.objects.create(name="mycol",
default_retention_policy=retention_policy)
-
self.assertTrue(self.client.upload(ContentFile(content,"file.jpg","image/jpeg"),
"mycol"))
+
self.assertTrue(self.client.upload(File("file.jpg","image/jpeg",content),
"mycol"))
snapshot = models.Snapshot.objects.all()[0]
self.assertEqual(snapshot.retention_policy, retention_policy)
self.assertEqual(snapshot.file.read(), content)
def test_purge1(self):
- file = DjangoContentFile(b"123")
+ file = ContentFile(b"123")
file.name = "file.jpg"
retention_policy =
models.RetentionPolicy.objects.create(name="hourly",
duration=timedelta(hours=1))
collection = models.Collection.objects.create(name="mycol",
default_retention_policy=retention_policy)
@@ -51,4 +54,4 @@
def setUp(self):
user = User.objects.create(username="test")
token = Token.objects.create(user=user)
- self.client = Client(self.live_server_url, token = token.key)
+ self.client = Client(self.api_endpoint, token = token.key)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-eremaea2-2.0.15/tests/ctl/file.py
new/django-eremaea2-2.0.16/tests/ctl/file.py
--- old/django-eremaea2-2.0.15/tests/ctl/file.py 2020-08-28
15:04:08.000000000 +0200
+++ new/django-eremaea2-2.0.16/tests/ctl/file.py 2020-12-01
04:36:06.000000000 +0100
@@ -1,5 +1,9 @@
-from eremaea.ctl.file import ContentFile, LocalFile, HTTPFile, FileFactory
+from eremaea.ctl.file import File, create_stream, LocalFileStream,
HTTPFileStream
from unittest import TestCase
+import hashlib
+import re
+import requests
+import requests_mock
try:
from unittest.mock import patch, mock_open, create_autospec
except ImportError:
@@ -7,47 +11,76 @@
class FileTest(TestCase):
def test_content_file1(self):
- f = ContentFile("content", "text.txt", "text/plain")
+ f = File("text.txt", "text/plain", "content")
self.assertEqual(f.name, "text.txt")
self.assertEqual(f.mimetype, "text/plain")
- self.assertEqual(f.read(), "content")
+ self.assertEqual(f.content, "content")
@patch('eremaea.ctl.file.open', mock_open(read_data='hello_world'),
create=True)
def test_local_file1(self):
- f = LocalFile("path/text.txt")
+ f = next(LocalFileStream("path/text.txt"))
self.assertEqual(f.name, "text.txt")
self.assertEqual(f.mimetype, "text/plain")
- self.assertEqual(f.read(), "hello_world")
+ self.assertEqual(f.content, "hello_world")
- @patch('requests.get')
+ def test_create_stream1(self):
+ self.assertIsInstance(create_stream("text.txt"),
LocalFileStream)
+ self.assertIsInstance(create_stream("/root/text.txt"),
LocalFileStream)
+
self.assertIsInstance(create_stream("http://localhost/root/text.txt"),
HTTPFileStream)
+
self.assertIsInstance(create_stream("https://localhost/root/text.txt"),
HTTPFileStream)
+
+ @requests_mock.Mocker()
def test_http_file1(self, mock):
- class ResponseMock(object):
- def raise_for_status(self):
- pass
- mock.return_value = ResponseMock()
- mock.return_value.headers = {}
- mock.return_value.content = "hello_world"
- f = HTTPFile("http://localhost/path/text.txt")
+ mock.register_uri("GET", "http://localhost/path/text.txt",
content=b"hello_world")
+ f = next(HTTPFileStream("http://localhost/path/text.txt"))
self.assertEqual(f.name, "text.txt")
self.assertEqual(f.mimetype, "text/plain")
- self.assertEqual(f.read(), "hello_world")
+ self.assertEqual(f.content, b"hello_world")
- @patch('requests.get')
+ @requests_mock.Mocker()
def test_http_file2(self, mock):
- class ResponseMock(object):
- def raise_for_status(self):
- pass
- mock.return_value = ResponseMock()
- mock.return_value.headers = {"content-type":"application/json"}
- mock.return_value.content = "hello_world"
- f = HTTPFile("http://localhost/path/text.txt")
+ mock.register_uri("GET", "http://localhost/path/text.txt",
content=b"hello_world", headers={'content-type': 'application/json'})
+ f = next(HTTPFileStream("http://localhost/path/text.txt"))
self.assertEqual(f.name, "text.txt")
self.assertEqual(f.mimetype, "application/json")
- self.assertEqual(f.read(), "hello_world")
+ self.assertEqual(f.content, b"hello_world")
+
+ @requests_mock.Mocker()
+ def test_http_file_auth_basic1(self, mock):
+ mock.register_uri("GET",
"http://test:pwd@localhost/path/text.txt", [
+ {"status_code": 401, "headers": {"www-authenticate":
"basic realm=\"test\""}},
+ {"status_code": 200, "content": b"hello_world"}
+ ])
+ f =
next(HTTPFileStream("http://test:pwd@localhost/path/text.txt"))
+
self.assertNotIn("authorization",mock.request_history[0].headers)
+
self.assertEqual(mock.request_history[1].headers['authorization'], 'Basic
dGVzdDpwd2Q=')
+ self.assertEqual(f.name, "text.txt")
+ self.assertEqual(f.mimetype, "text/plain")
+ self.assertEqual(f.content, b"hello_world")
- def test_factory1(self):
- f = FileFactory()
- self.assertEqual(LocalFile, f.resolve("text.txt"))
- self.assertEqual(LocalFile, f.resolve("/root/text.txt"))
- self.assertEqual(HTTPFile,
f.resolve("http://localhost/root/text.txt"))
- self.assertEqual(HTTPFile,
f.resolve("https://localhost/root/text.txt"))
+ @requests_mock.Mocker()
+ def test_http_file_auth_basic2(self, mock):
+ mock.register_uri("GET",
"http://test:pwd@localhost/path/text.txt", status_code=401,
headers={"www-authenticate": "basic realm=\"test\""})
+ self.assertRaises(requests.exceptions.HTTPError, lambda:
next(HTTPFileStream("http://test:pwd@localhost/path/text.txt")))
+
+ @requests_mock.Mocker()
+ def test_http_file_auth_digest1(self, mock):
+ HA1 = hashlib.md5(b"test:test:pwd").hexdigest()
+ HA2 = hashlib.md5(b"GET:/path/text.txt").hexdigest()
+ expected = hashlib.md5(HA1.encode("ascii") + b":1234:" +
HA2.encode("ascii")).hexdigest()
+ reg=re.compile(r'(\w+)[=] ?"?(\w+)"?')
+ mock.register_uri("GET",
"http://test:pwd@localhost/path/text.txt", [
+ {"status_code": 401, "headers": {"www-authenticate":
"digest realm=\"test\", nonce=\"1234\""}},
+ {"status_code": 200, "content": b"hello_world"}
+ ])
+ f =
next(HTTPFileStream("http://test:pwd@localhost/path/text.txt"))
+
self.assertNotIn("authorization",mock.request_history[0].headers)
+
self.assertDictEqual(dict(reg.findall(mock.request_history[1].headers['authorization'])),
{
+ 'nonce': '1234',
+ 'realm': 'test',
+ 'response': expected,
+ 'username': 'test',
+ })
+ self.assertEqual(f.name, "text.txt")
+ self.assertEqual(f.mimetype, "text/plain")
+ self.assertEqual(f.content, b"hello_world")
_______________________________________________
openSUSE Commits mailing list -- [email protected]
To unsubscribe, email [email protected]
List Netiquette: https://en.opensuse.org/openSUSE:Mailing_list_netiquette
List Archives:
https://lists.opensuse.org/archives/list/[email protected]