XEP-0363: Types, docs, and new-style kwargs

This commit is contained in:
mathieui 2021-02-13 18:18:23 +01:00
parent a7d690813c
commit ea2d851a93
3 changed files with 90 additions and 32 deletions

View file

@ -8,6 +8,12 @@ XEP-0363: HTTP File Upload
:members:
:exclude-members: session_bind, plugin_init, plugin_end
.. autoclass:: UploadServiceNotFound
.. autoclass:: FileTooBig
.. autoclass:: HTTPError
Stanza elements
---------------

View file

@ -1,4 +1,3 @@
# slixmpp: The Slick XMPP Library
# Copyright (C) 2018 Emmanuel Gil Peyrot
# This file is part of slixmpp.
@ -6,6 +5,12 @@
from slixmpp.plugins.base import register_plugin
from slixmpp.plugins.xep_0363.stanza import Request, Slot, Put, Get, Header
from slixmpp.plugins.xep_0363.http_upload import XEP_0363
from slixmpp.plugins.xep_0363.http_upload import (
XEP_0363,
UploadServiceNotFound,
FileTooBig,
HTTPError,
FileUploadError,
)
register_plugin(XEP_0363)

View file

@ -1,18 +1,21 @@
"""
slixmpp: The Slick XMPP Library
Copyright (C) 2018 Emmanuel Gil Peyrot
This file is part of slixmpp.
See the file LICENSE for copying permission.
"""
# slixmpp: The Slick XMPP Library
# Copyright (C) 2018 Emmanuel Gil Peyrot
# This file is part of slixmpp.
# See the file LICENSE for copying permission.
import logging
import os.path
from aiohttp import ClientSession
from asyncio import Future
from mimetypes import guess_type
from typing import (
Optional,
IO,
)
from slixmpp import Iq, __version__
from slixmpp import JID, __version__
from slixmpp.stanza import Iq
from slixmpp.plugins import BasePlugin
from slixmpp.xmlstream import register_stanza_plugin
from slixmpp.xmlstream.handler import Callback
@ -25,19 +28,39 @@ class FileUploadError(Exception):
pass
class UploadServiceNotFound(FileUploadError):
pass
"""
Raised if no upload service can be found.
"""
class FileTooBig(FileUploadError):
"""
Raised if the file size is above advertised server limits.
args:
- size of the file
- max file size allowed
"""
def __str__(self):
return 'File size too large: {} (max: {} bytes)' \
.format(self.args[0], self.args[1])
class HTTPError(FileUploadError):
"""
Raised when we receive an HTTP error response during upload.
args:
- HTTP Error code
- Content of the HTTP response
"""
def __str__(self):
return 'Could not upload file: %d (%s)' % (self.args[0], self.args[1])
class XEP_0363(BasePlugin):
''' This plugin only supports Python 3.5+ '''
"""
XEP-0363: HTTP File Upload
"""
name = 'xep_0363'
description = 'XEP-0363: HTTP File Upload'
@ -62,9 +85,7 @@ class XEP_0363(BasePlugin):
self._handle_request))
def plugin_end(self):
self._http_session.close()
self.xmpp.remove_handler('HTTP Upload Request')
self.xmpp.remove_handler('HTTP Upload Slot')
self.xmpp['xep_0030'].del_feature(feature=Request.namespace)
def session_bind(self, jid):
@ -73,9 +94,14 @@ class XEP_0363(BasePlugin):
def _handle_request(self, iq):
self.xmpp.event('http_upload_request', iq)
async def find_upload_service(self, domain=None, timeout=None):
async def find_upload_service(self, domain: Optional[JID] = None, **iqkwargs) -> Optional[Iq]:
"""Find an upload service on a domain (our own by default).
:param domain: Domain to disco to find a service.
"""
results = await self.xmpp['xep_0030'].get_info_from_domain(
domain=domain, timeout=timeout)
domain=domain, **iqkwargs
)
candidates = []
for info in results:
@ -87,26 +113,49 @@ class XEP_0363(BasePlugin):
if feature == Request.namespace:
return info
def request_slot(self, jid, filename, size, content_type=None, ifrom=None,
timeout=None, callback=None, timeout_callback=None):
iq = self.xmpp.Iq()
iq['to'] = jid
iq['from'] = ifrom
iq['type'] = 'get'
def request_slot(self, jid: JID, filename: str, size: int,
content_type: Optional[str] = None, *,
ifrom: Optional[JID] = None, **iqkwargs) -> Future:
"""Request an HTTP upload slot from a service.
:param jid: Service to request the slot from.
:param filename: Name of the file that will be uploaded.
:param size: size of the file in bytes.
:param content_type: Type of the file that will be uploaded.
"""
iq = self.xmpp.make_iq_get(ito=jid, ifrom=ifrom)
request = iq['http_upload_request']
request['filename'] = filename
request['size'] = str(size)
request['content-type'] = content_type or self.default_content_type
return iq.send(timeout=timeout, callback=callback,
timeout_callback=timeout_callback)
return iq.send(**iqkwargs)
async def upload_file(self, filename, size=None, content_type=None, *,
input_file=None, ifrom=None, domain=None, timeout=None,
callback=None, timeout_callback=None):
''' Helper function which does all of the uploading process. '''
async def upload_file(self, filename: str, size: Optional[int] = None,
content_type: Optional[str] = None, *,
input_file: Optional[IO[bytes]]=None,
domain: Optional[JID] = None,
**iqkwargs) -> str:
'''Helper function which does all of the uploading discovery and
process.
:param filename: Path to the file to upload (or only the name if
``input_file`` is provided.
:param size: size of the file in bytes.
:param content_type: Type of the file that will be uploaded.
:param input_file: Binary file stream on the file.
:param domain: Domain to query to find an HTTP upload service.
:raises .UploadServiceNotFound: If slixmpp is unable to find an
an available upload service.
:raises .FileTooBig: If the filesize is above what is accepted by
the service.
:raises .HTTPError: If there is an error in the HTTP operation.
:returns: The URL of the uploaded file.
'''
timeout = iqkwargs.get('timeout', None)
if self.upload_service is None:
info_iq = await self.find_upload_service(
domain=domain, timeout=timeout)
domain=domain, **iqkwargs
)
if info_iq is None:
raise UploadServiceNotFound()
self.upload_service = info_iq['from']
@ -137,9 +186,7 @@ class XEP_0363(BasePlugin):
basename = os.path.basename(filename)
slot_iq = await self.request_slot(self.upload_service, basename, size,
content_type, ifrom, timeout,
callback=callback,
timeout_callback=timeout_callback)
content_type, **iqkwargs)
slot = slot_iq['http_upload_slot']
headers = {