2026-02-01 09:31:38 +01:00

1872 lines
64 KiB
Python

"""Provide classes related to widgets."""
from __future__ import annotations
from json import JSONEncoder, dumps
from pathlib import Path
from typing import TYPE_CHECKING, Any, TypeVar
from ...const import API_PATH
from ...util import _deprecate_args
from ...util.cache import cachedproperty
from ..base import PRAWBase
from ..list.base import BaseList
if TYPE_CHECKING: # pragma: no cover
import praw.models
WidgetType: TypeVar = TypeVar("WidgetType", bound="Widget")
class Button(PRAWBase):
"""Class to represent a single button inside a :class:`.ButtonWidget`.
.. include:: ../../typical_attributes.rst
============== =====================================================================
Attribute Description
============== =====================================================================
``color`` The hex color used to outline the button.
``fillColor`` The hex color for the background of the button.
``height`` Image height. Only present on image buttons.
``hoverState`` A ``dict`` describing the state of the button when hovered over.
Optional.
``kind`` Either ``"text"`` or ``"image"``.
``linkUrl`` A link that can be visited by clicking the button. Only present on
image buttons.
``text`` The text displayed on the button.
``textColor`` The hex color for the text of the button.
``url`` - If the button is a text button, a link that can be visited by
clicking the button.
- If the button is an image button, the URL of a Reddit-hosted image.
``width`` Image width. Only present on image buttons.
============== =====================================================================
"""
class CalendarConfiguration(PRAWBase):
"""Class to represent the configuration of a :class:`.Calendar`.
.. include:: ../../typical_attributes.rst
=================== ================================================
Attribute Description
=================== ================================================
``numEvents`` The number of events to display on the calendar.
``showDate`` Whether to show the dates of events.
``showDescription`` Whether to show the descriptions of events.
``showLocation`` Whether to show the locations of events.
``showTime`` Whether to show the times of events.
``showTitle`` Whether to show the titles of events.
=================== ================================================
"""
class Hover(PRAWBase):
"""Class to represent the hover data for a :class:`.ButtonWidget`.
These values will take effect when the button is hovered over (the user moves their
cursor so it's on top of the button).
.. include:: ../../typical_attributes.rst
============= =====================================================================
Attribute Description
============= =====================================================================
``color`` The hex color used to outline the button.
``fillColor`` The hex color for the background of the button.
``textColor`` The hex color for the text of the button.
``height`` Image height. Only present on image buttons.
``kind`` Either ``text`` or ``image``.
``text`` The text displayed on the button.
``url`` - If the button is a text button, a link that can be visited by
clicking the button.
- If the button is an image button, the URL of a Reddit-hosted image.
``width`` Image width. Only present on image buttons.
============= =====================================================================
"""
class Image(PRAWBase):
"""Class to represent an image that's part of a :class:`.ImageWidget`.
.. include:: ../../typical_attributes.rst
=========== =================================================
Attribute Description
=========== =================================================
``height`` Image height.
``linkUrl`` A link that can be visited by clicking the image.
``url`` The URL of the (Reddit-hosted) image.
``width`` Image width.
=========== =================================================
"""
class ImageData(PRAWBase):
"""Class for image data that's part of a :class:`.CustomWidget`.
.. include:: ../../typical_attributes.rst
========== =========================================
Attribute Description
========== =========================================
``height`` The image height.
``name`` The image name.
``url`` The URL of the image on Reddit's servers.
``width`` The image width.
========== =========================================
"""
class MenuLink(PRAWBase):
"""Class to represent a single link inside a :class:`.Menu` or :class:`.Submenu`.
.. include:: ../../typical_attributes.rst
========= ====================================
Attribute Description
========= ====================================
``text`` The text of the menu link.
``url`` The URL that the menu item links to.
========= ====================================
"""
class Styles(PRAWBase):
"""Class to represent the style information of a widget.
.. include:: ../../typical_attributes.rst
=================== ========================================================
Attribute Description
=================== ========================================================
``backgroundColor`` The background color of a widget, given as a hexadecimal
(``0x######``).
``headerColor`` The header color of a widget, given as a hexadecimal
(``0x######``).
=================== ========================================================
"""
class Submenu(BaseList):
r"""Class to represent a submenu of links inside a :class:`.Menu`.
.. include:: ../../typical_attributes.rst
============ ======================================================================
Attribute Description
============ ======================================================================
``children`` A list of the :class:`.MenuLink`\ s in this submenu. Can be iterated
over by iterating over the :class:`.Submenu` (e.g., ``for menu_link in
submenu``).
``text`` The name of the submenu.
============ ======================================================================
"""
CHILD_ATTRIBUTE = "children"
class SubredditWidgets(PRAWBase):
"""Class to represent a :class:`.Subreddit`'s widgets.
Create an instance like so:
.. code-block:: python
widgets = reddit.subreddit("test").widgets
Data will be lazy-loaded. By default, PRAW will not request progressively loading
images from Reddit. To enable this, instantiate a :class:`.SubredditWidgets` object
via :meth:`~.Subreddit.widgets`, then set the attribute ``progressive_images`` to
``True`` before performing any action that would result in a network request.
.. code-block:: python
widgets = reddit.subreddit("test").widgets
widgets.progressive_images = True
for widget in widgets.sidebar:
# do something
...
Access a :class:`.Subreddit`'s widgets with the following attributes:
.. code-block:: python
print(widgets.id_card)
print(widgets.moderators_widget)
print(widgets.sidebar)
print(widgets.topbar)
The attribute :attr:`.id_card` contains the :class:`.Subreddit`'s ID card, which
displays information like the number of subscribers.
The attribute :attr:`.moderators_widget` contains the :class:`.Subreddit`'s
moderators widget, which lists the moderators of the subreddit.
The attribute :attr:`.sidebar` contains a list of widgets which make up the sidebar
of the subreddit.
The attribute :attr:`.topbar` contains a list of widgets which make up the top bar
of the subreddit.
To edit a :class:`.Subreddit`'s widgets, use :attr:`~.SubredditWidgets.mod`. For
example:
.. code-block:: python
widgets.mod.add_text_area(
short_name="My title",
text="**bold text**",
styles={"backgroundColor": "#FFFF66", "headerColor": "#3333EE"},
)
For more information, see :class:`.SubredditWidgetsModeration`.
To edit a particular widget, use ``.mod`` on the widget. For example:
.. code-block:: python
for widget in widgets.sidebar:
widget.mod.update(shortName="Exciting new name")
For more information, see :class:`.WidgetModeration`.
**Currently available widgets**:
- :class:`.ButtonWidget`
- :class:`.Calendar`
- :class:`.CommunityList`
- :class:`.CustomWidget`
- :class:`.IDCard`
- :class:`.ImageWidget`
- :class:`.Menu`
- :class:`.ModeratorsWidget`
- :class:`.PostFlairWidget`
- :class:`.RulesWidget`
- :class:`.TextArea`
"""
@cachedproperty
def id_card(self) -> praw.models.IDCard:
"""Get this :class:`.Subreddit`'s :class:`.IDCard` widget."""
return self.items[self.layout["idCardWidget"]]
@cachedproperty
def items(self) -> dict[str, praw.models.Widget]:
"""Get this :class:`.Subreddit`'s widgets as a dict from ID to widget."""
items = {}
for item_name, data in self._raw_items.items():
data["subreddit"] = self.subreddit
items[item_name] = self._reddit._objector.objectify(data)
return items
@cachedproperty
def mod(self) -> praw.models.SubredditWidgetsModeration:
"""Get an instance of :class:`.SubredditWidgetsModeration`.
.. note::
Using any of the methods of :class:`.SubredditWidgetsModeration` will likely
result in the data of this :class:`.SubredditWidgets` being outdated. To
re-sync, call :meth:`.refresh`.
"""
return SubredditWidgetsModeration(self.subreddit, self._reddit)
@cachedproperty
def moderators_widget(self) -> praw.models.ModeratorsWidget:
"""Get this :class:`.Subreddit`'s :class:`.ModeratorsWidget`."""
return self.items[self.layout["moderatorWidget"]]
@cachedproperty
def sidebar(self) -> list[praw.models.Widget]:
r"""Get a list of :class:`.Widget`\ s that make up the sidebar."""
return [
self.items[widget_name] for widget_name in self.layout["sidebar"]["order"]
]
@cachedproperty
def topbar(self) -> list[praw.models.Menu]:
r"""Get a list of :class:`.Widget`\ s that make up the top bar."""
return [
self.items[widget_name] for widget_name in self.layout["topbar"]["order"]
]
def __getattr__(self, attr: str) -> Any:
"""Return the value of ``attr``."""
if not attr.startswith("_") and not self._fetched:
self._fetch()
return getattr(self, attr)
msg = f"{self.__class__.__name__!r} object has no attribute {attr!r}"
raise AttributeError(msg)
def __init__(self, subreddit: praw.models.Subreddit):
"""Initialize a :class:`.SubredditWidgets` instance.
:param subreddit: The :class:`.Subreddit` the widgets belong to.
"""
self._raw_items = None
self._fetched = False
self.subreddit = subreddit
self.progressive_images = False
super().__init__(subreddit._reddit, {})
def __repr__(self) -> str:
"""Return an object initialization representation of the instance."""
return f"SubredditWidgets(subreddit={self.subreddit!r})"
def _fetch(self):
data = self._reddit.get(
API_PATH["widgets"].format(subreddit=self.subreddit),
params={"progressive_images": self.progressive_images},
)
self._raw_items = data.pop("items")
super().__init__(self.subreddit._reddit, data)
cached_property_names = [
"id_card",
"moderators_widget",
"sidebar",
"topbar",
"items",
]
inst_dict_pop = self.__dict__.pop
for name in cached_property_names:
inst_dict_pop(name, None)
self._fetched = True
def refresh(self):
"""Refresh the :class:`.Subreddit`'s widgets.
By default, PRAW will not request progressively loading images from Reddit. To
enable this, set the attribute ``progressive_images`` to ``True`` prior to
calling ``refresh()``.
.. code-block:: python
widgets = reddit.subreddit("test").widgets
widgets.progressive_images = True
widgets.refresh()
"""
self._fetch()
class Widget(PRAWBase):
"""Base class to represent a :class:`.Widget`."""
@cachedproperty
def mod(self) -> praw.models.WidgetModeration:
"""Get an instance of :class:`.WidgetModeration` for this widget.
.. note::
Using any of the methods of :class:`.WidgetModeration` will likely make the
data in the :class:`.SubredditWidgets` that this widget belongs to outdated.
To remedy this, call :meth:`~.SubredditWidgets.refresh`.
"""
return WidgetModeration(self, self.subreddit, self._reddit)
def __eq__(self, other: object) -> bool:
"""Check equality against another object."""
if isinstance(other, Widget):
return self.id.lower() == other.id.lower()
return str(other).lower() == self.id.lower()
def __init__(self, reddit: praw.Reddit, _data: dict[str, Any]):
"""Initialize a :class:`.Widget` instance."""
self.subreddit = "" # in case it isn't in _data
self.id = "" # in case it isn't in _data
super().__init__(reddit, _data=_data)
self._mod = None
class WidgetEncoder(JSONEncoder):
"""Class to encode widget-related objects."""
def default(self, o: Any) -> Any:
"""Serialize ``PRAWBase`` objects."""
if isinstance(o, self._subreddit_class):
return str(o)
if isinstance(o, PRAWBase):
return {key: val for key, val in vars(o).items() if not key.startswith("_")}
return JSONEncoder.default(self, o)
class ButtonWidget(Widget, BaseList):
r"""Class to represent a widget containing one or more buttons.
Find an existing one:
.. code-block:: python
button_widget = None
widgets = reddit.subreddit("test").widgets
for widget in widgets.sidebar:
if isinstance(widget, praw.models.ButtonWidget):
button_widget = widget
break
for button in button_widget:
print(button.text, button.url)
Create one:
.. code-block:: python
widgets = reddit.subreddit("test").widgets
buttons = [
{
"kind": "text",
"text": "View source",
"url": "https://github.com/praw-dev/praw",
"color": "#FF0000",
"textColor": "#00FF00",
"fillColor": "#0000FF",
"hoverState": {
"kind": "text",
"text": "ecruos weiV",
"color": "#000000",
"textColor": "#FFFFFF",
"fillColor": "#0000FF",
},
},
{
"kind": "text",
"text": "View documentation",
"url": "https://praw.readthedocs.io",
"color": "#FFFFFF",
"textColor": "#FFFF00",
"fillColor": "#0000FF",
},
]
styles = {"backgroundColor": "#FFFF66", "headerColor": "#3333EE"}
button_widget = widgets.mod.add_button_widget(
"Things to click", "Click some of these *cool* links!", buttons, styles
)
For more information on creation, see :meth:`.add_button_widget`.
Update one:
.. code-block:: python
new_styles = {"backgroundColor": "#FFFFFF", "headerColor": "#FF9900"}
button_widget = button_widget.mod.update(shortName="My fav buttons", styles=new_styles)
Delete one:
.. code-block:: python
button_widget.mod.delete()
.. include:: ../../typical_attributes.rst
==================== ==============================================================
Attribute Description
==================== ==============================================================
``buttons`` A list of :class:`.Button`\ s. These can also be accessed just
by iterating over the :class:`.ButtonWidget` (e.g., ``for
button in button_widget``).
``description`` The description, in Markdown.
``description_html`` The description, in HTML.
``id`` The widget ID.
``kind`` The widget kind (always ``"button"``).
``shortName`` The short name of the widget.
``styles`` A ``dict`` with the keys ``"backgroundColor"`` and
``"headerColor"``.
``subreddit`` The :class:`.Subreddit` the button widget belongs to.
==================== ==============================================================
"""
CHILD_ATTRIBUTE = "buttons"
class Calendar(Widget):
"""Class to represent a calendar widget.
Find an existing one:
.. code-block:: python
calendar = None
widgets = reddit.subreddit("test").widgets
for widget in widgets.sidebar:
if isinstance(widget, praw.models.Calendar):
calendar = widget
break
print(calendar.googleCalendarId)
Create one:
.. code-block:: python
widgets = reddit.subreddit("test").widgets
styles = {"backgroundColor": "#FFFF66", "headerColor": "#3333EE"}
config = {
"numEvents": 10,
"showDate": True,
"showDescription": False,
"showLocation": False,
"showTime": True,
"showTitle": True,
}
cal_id = "y6nm89jy427drk8l71w75w9wjn@group.calendar.google.com"
calendar = widgets.mod.add_calendar(
short_name="Upcoming Events",
google_calendar_id=cal_id,
requires_sync=True,
configuration=config,
styles=styles,
)
For more information on creation, see :meth:`.add_calendar`.
Update one:
.. code-block:: python
new_styles = {"backgroundColor": "#FFFFFF", "headerColor": "#FF9900"}
calendar = calendar.mod.update(shortName="My fav events", styles=new_styles)
Delete one:
.. code-block:: python
calendar.mod.delete()
.. include:: ../../typical_attributes.rst
================= =====================================================
Attribute Description
================= =====================================================
``configuration`` A ``dict`` describing the calendar configuration.
``data`` A list of dictionaries that represent events.
``id`` The widget ID.
``kind`` The widget kind (always ``"calendar"``).
``requiresSync`` A ``bool`` representing whether the calendar requires
synchronization.
``shortName`` The short name of the widget.
``styles`` A ``dict`` with the keys ``"backgroundColor"`` and
``"headerColor"``.
``subreddit`` The :class:`.Subreddit` the button widget belongs to.
================= =====================================================
"""
class CommunityList(Widget, BaseList):
r"""Class to represent a Related Communities widget.
Find an existing one:
.. code-block:: python
community_list = None
widgets = reddit.subreddit("test").widgets
for widget in widgets.sidebar:
if isinstance(widget, praw.models.CommunityList):
community_list = widget
break
print(community_list)
Create one:
.. code-block:: python
widgets = reddit.subreddit("test").widgets
styles = {"backgroundColor": "#FFFF66", "headerColor": "#3333EE"}
subreddits = ["learnpython", reddit.subreddit("test")]
community_list = widgets.mod.add_community_list(
short_name="Related subreddits",
data=subreddits,
styles=styles,
description="description",
)
For more information on creation, see :meth:`.add_community_list`.
Update one:
.. code-block:: python
new_styles = {"backgroundColor": "#FFFFFF", "headerColor": "#FF9900"}
community_list = community_list.mod.update(shortName="My fav subs", styles=new_styles)
Delete one:
.. code-block:: python
community_list.mod.delete()
.. include:: ../../typical_attributes.rst
============= =====================================================================
Attribute Description
============= =====================================================================
``data`` A list of :class:`.Subreddit`\ s. These can also be iterated over by
iterating over the :class:`.CommunityList` (e.g., ``for sub in
community_list``).
``id`` The widget ID.
``kind`` The widget kind (always ``"community-list"``).
``shortName`` The short name of the widget.
``styles`` A ``dict`` with the keys ``"backgroundColor"`` and ``"headerColor"``.
``subreddit`` The :class:`.Subreddit` the button widget belongs to.
============= =====================================================================
"""
CHILD_ATTRIBUTE = "data"
class CustomWidget(Widget):
"""Class to represent a custom widget.
Find an existing one:
.. code-block:: python
custom = None
widgets = reddit.subreddit("test").widgets
for widget in widgets.sidebar:
if isinstance(widget, praw.models.CustomWidget):
custom = widget
break
print(custom.text)
print(custom.css)
Create one:
.. code-block:: python
widgets = reddit.subreddit("test").widgets
styles = {"backgroundColor": "#FFFF66", "headerColor": "#3333EE"}
custom = widgets.mod.add_custom_widget(
short_name="My custom widget",
text="# Hello world!",
css="/**/",
height=200,
image_data=[],
styles=styles,
)
For more information on creation, see :meth:`.add_custom_widget`.
Update one:
.. code-block:: python
new_styles = {"backgroundColor": "#FFFFFF", "headerColor": "#FF9900"}
custom = custom.mod.update(shortName="My fav customization", styles=new_styles)
Delete one:
.. code-block:: python
custom.mod.delete()
.. include:: ../../typical_attributes.rst
================= ============================================================
Attribute Description
================= ============================================================
``css`` The CSS of the widget, as a ``str``.
``height`` The height of the widget, as an ``int``.
``id`` The widget ID.
``imageData`` A ``list`` of :class:`.ImageData` that belong to the widget.
``kind`` The widget kind (always ``"custom"``).
``shortName`` The short name of the widget.
``styles`` A ``dict`` with the keys ``"backgroundColor"`` and
``"headerColor"``.
``stylesheetUrl`` A link to the widget's stylesheet.
``subreddit`` The :class:`.Subreddit` the button widget belongs to.
``text`` The text contents, as Markdown.
``textHtml`` The text contents, as HTML.
================= ============================================================
"""
def __init__(self, reddit: praw.Reddit, _data: dict[str, Any]):
"""Initialize a :class:`.CustomWidget` instance."""
_data["imageData"] = [
ImageData(reddit, data) for data in _data.pop("imageData")
]
super().__init__(reddit, _data=_data)
class IDCard(Widget):
"""Class to represent an ID card widget.
.. code-block:: python
widgets = reddit.subreddit("test").widgets
id_card = widgets.id_card
print(id_card.subscribersText)
Update one:
.. code-block:: python
widgets.id_card.mod.update(currentlyViewingText="Bots")
.. include:: ../../typical_attributes.rst
========================= =======================================================
Attribute Description
========================= =======================================================
``currentlyViewingCount`` The number of redditors viewing the subreddit.
``currentlyViewingText`` The text displayed next to the view count. For example,
``"users online"``.
``description`` The subreddit description.
``id`` The widget ID.
``kind`` The widget kind (always ``"id-card"``).
``shortName`` The short name of the widget.
``styles`` A ``dict`` with the keys ``"backgroundColor"`` and
``"headerColor"``.
``subreddit`` The :class:`.Subreddit` the button widget belongs to.
``subscribersCount`` The number of subscribers to the subreddit.
``subscribersText`` The text displayed next to the subscriber count. For
example, "users subscribed".
========================= =======================================================
"""
class ImageWidget(Widget, BaseList):
r"""Class to represent an image widget.
Find an existing one:
.. code-block:: python
image_widget = None
widgets = reddit.subreddit("test").widgets
for widget in widgets.sidebar:
if isinstance(widget, praw.models.ImageWidget):
image_widget = widget
break
for image in image_widget:
print(image.url)
Create one:
.. code-block:: python
widgets = reddit.subreddit("test").widgets
image_paths = ["/path/to/image1.jpg", "/path/to/image2.png"]
image_data = [
{
"width": 600,
"height": 450,
"linkUrl": "",
"url": widgets.mod.upload_image(img_path),
}
for img_path in image_paths
]
styles = {"backgroundColor": "#FFFF66", "headerColor": "#3333EE"}
image_widget = widgets.mod.add_image_widget(
short_name="My cool pictures", data=image_data, styles=styles
)
For more information on creation, see :meth:`.add_image_widget`.
Update one:
.. code-block:: python
new_styles = {"backgroundColor": "#FFFFFF", "headerColor": "#FF9900"}
image_widget = image_widget.mod.update(shortName="My fav images", styles=new_styles)
Delete one:
.. code-block:: python
image_widget.mod.delete()
.. include:: ../../typical_attributes.rst
============= =====================================================================
Attribute Description
============= =====================================================================
``data`` A list of the :class:`.Image`\ s in this widget. Can be iterated over
by iterating over the :class:`.ImageWidget` (e.g., ``for img in
image_widget``).
``id`` The widget ID.
``kind`` The widget kind (always ``"image"``).
``shortName`` The short name of the widget.
``styles`` A ``dict`` with the keys ``"backgroundColor"`` and ``"headerColor"``.
``subreddit`` The :class:`.Subreddit` the button widget belongs to.
============= =====================================================================
"""
CHILD_ATTRIBUTE = "data"
class Menu(Widget, BaseList):
r"""Class to represent the top menu widget of a :class:`.Subreddit`.
Menus can generally be found as the first item in a :class:`.Subreddit`'s top bar.
.. code-block:: python
topbar = reddit.subreddit("test").widgets.topbar
if len(topbar) > 0:
probably_menu = topbar[0]
assert isinstance(probably_menu, praw.models.Menu)
for item in probably_menu:
if isinstance(item, praw.models.Submenu):
print(item.text)
for child in item:
print("\t", child.text, child.url)
else: # MenuLink
print(item.text, item.url)
Create one:
.. code-block:: python
widgets = reddit.subreddit("test").widgets
menu_contents = [
{"text": "My homepage", "url": "https://example.com"},
{
"text": "Python packages",
"children": [
{"text": "PRAW", "url": "https://praw.readthedocs.io/"},
{"text": "requests", "url": "http://python-requests.org"},
],
},
{"text": "Reddit homepage", "url": "https://reddit.com"},
]
menu = widgets.mod.add_menu(data=menu_contents)
For more information on creation, see :meth:`.add_menu`.
Update one:
.. code-block:: python
menu_items = list(menu)
menu_items.reverse()
menu = menu.mod.update(data=menu_items)
Delete one:
.. code-block:: python
menu.mod.delete()
.. include:: ../../typical_attributes.rst
============= ====================================================================
Attribute Description
============= ====================================================================
``data`` A list of the :class:`.MenuLink`\ s and :class:`.Submenu`\ s in this
widget. Can be iterated over by iterating over the :class:`.Menu`
(e.g., ``for item in menu``).
``id`` The widget ID.
``kind`` The widget kind (always ``"menu"``).
``subreddit`` The :class:`.Subreddit` the button widget belongs to.
============= ====================================================================
"""
CHILD_ATTRIBUTE = "data"
class ModeratorsWidget(Widget, BaseList):
r"""Class to represent a moderators widget.
.. code-block:: python
widgets = reddit.subreddit("test").widgets
print(widgets.moderators_widget)
Update one:
.. code-block:: python
new_styles = {"backgroundColor": "#FFFFFF", "headerColor": "#FF9900"}
widgets.moderators_widget.mod.update(styles=new_styles)
.. include:: ../../typical_attributes.rst
============= =====================================================================
Attribute Description
============= =====================================================================
``id`` The widget ID.
``kind`` The widget kind (always ``"moderators"``).
``mods`` A list of the :class:`.Redditor`\ s that moderate the subreddit. Can
be iterated over by iterating over the :class:`.ModeratorsWidget`
(e.g., ``for mod in widgets.moderators_widget``).
``styles`` A ``dict`` with the keys ``"backgroundColor"`` and ``"headerColor"``.
``subreddit`` The :class:`.Subreddit` the button widget belongs to.
``totalMods`` The total number of moderators in the subreddit.
============= =====================================================================
"""
CHILD_ATTRIBUTE = "mods"
def __init__(self, reddit: praw.Reddit, _data: dict[str, Any]):
"""Initialize a :class:`.ModeratorsWidget` instance."""
if self.CHILD_ATTRIBUTE not in _data:
# .mod.update() sometimes returns payload without "mods" field
_data[self.CHILD_ATTRIBUTE] = []
super().__init__(reddit, _data=_data)
class PostFlairWidget(Widget, BaseList):
"""Class to represent a post flair widget.
Find an existing one:
.. code-block:: python
post_flair_widget = None
widgets = reddit.subreddit("test").widgets
for widget in widgets.sidebar:
if isinstance(widget, praw.models.PostFlairWidget):
post_flair_widget = widget
break
for flair in post_flair_widget:
print(flair)
print(post_flair_widget.templates[flair])
Create one:
.. code-block:: python
subreddit = reddit.subreddit("test")
widgets = subreddit.widgets
flairs = [f["id"] for f in subreddit.flair.link_templates]
styles = {"backgroundColor": "#FFFF66", "headerColor": "#3333EE"}
post_flair = widgets.mod.add_post_flair_widget(
short_name="Some flairs", display="list", order=flairs, styles=styles
)
For more information on creation, see :meth:`.add_post_flair_widget`.
Update one:
.. code-block:: python
new_styles = {"backgroundColor": "#FFFFFF", "headerColor": "#FF9900"}
post_flair = post_flair.mod.update(shortName="My fav flairs", styles=new_styles)
Delete one:
.. code-block:: python
post_flair.mod.delete()
.. include:: ../../typical_attributes.rst
============= =====================================================================
Attribute Description
============= =====================================================================
``display`` The display style of the widget, either ``"cloud"`` or ``"list"``.
``id`` The widget ID.
``kind`` The widget kind (always ``"post-flair"``).
``order`` A list of the flair IDs in this widget. Can be iterated over by
iterating over the :class:`.PostFlairWidget` (e.g., ``for flair_id in
post_flair``).
``shortName`` The short name of the widget.
``styles`` A ``dict`` with the keys ``"backgroundColor"`` and ``"headerColor"``.
``subreddit`` The :class:`.Subreddit` the button widget belongs to.
``templates`` A ``dict`` that maps flair IDs to dictionaries that describe flairs.
============= =====================================================================
"""
CHILD_ATTRIBUTE = "order"
class RulesWidget(Widget, BaseList):
"""Class to represent a rules widget.
.. code-block:: python
widgets = reddit.subreddit("test").widgets
rules_widget = None
for widget in widgets.sidebar:
if isinstance(widget, praw.models.RulesWidget):
rules_widget = widget
break
from pprint import pprint
pprint(rules_widget.data)
Update one:
.. code-block:: python
new_styles = {"backgroundColor": "#FFFFFF", "headerColor": "#FF9900"}
rules_widget.mod.update(display="compact", shortName="The LAWS", styles=new_styles)
.. include:: ../../typical_attributes.rst
============= =====================================================================
Attribute Description
============= =====================================================================
``data`` A list of the subreddit rules. Can be iterated over by iterating over
the :class:`.RulesWidget` (e.g., ``for rule in rules_widget``).
``display`` The display style of the widget, either ``"full"`` or ``"compact"``.
``id`` The widget ID.
``kind`` The widget kind (always ``"subreddit-rules"``).
``shortName`` The short name of the widget.
``styles`` A ``dict`` with the keys ``"backgroundColor"`` and ``"headerColor"``.
``subreddit`` The :class:`.Subreddit` the button widget belongs to.
============= =====================================================================
"""
CHILD_ATTRIBUTE = "data"
def __init__(self, reddit: praw.Reddit, _data: dict[str, Any]):
"""Initialize a :class:`.RulesWidget` instance."""
if self.CHILD_ATTRIBUTE not in _data:
# .mod.update() sometimes returns payload without "data" field
_data[self.CHILD_ATTRIBUTE] = []
super().__init__(reddit, _data=_data)
class TextArea(Widget):
"""Class to represent a text area widget.
Find a text area in a subreddit:
.. code-block:: python
widgets = reddit.subreddit("test").widgets
text_area = None
for widget in widgets.sidebar:
if isinstance(widget, praw.models.TextArea):
text_area = widget
break
print(text_area.text)
Create one:
.. code-block:: python
widgets = reddit.subreddit("test").widgets
styles = {"backgroundColor": "#FFFF66", "headerColor": "#3333EE"}
text_area = widgets.mod.add_text_area(
short_name="My cool title", text="*Hello* **world**!", styles=styles
)
For more information on creation, see :meth:`.add_text_area`.
Update one:
.. code-block:: python
new_styles = {"backgroundColor": "#FFFFFF", "headerColor": "#FF9900"}
text_area = text_area.mod.update(shortName="My fav text", styles=new_styles)
Delete one:
.. code-block:: python
text_area.mod.delete()
.. include:: ../../typical_attributes.rst
============= =====================================================================
Attribute Description
============= =====================================================================
``id`` The widget ID.
``kind`` The widget kind (always ``"textarea"``).
``shortName`` The short name of the widget.
``styles`` A ``dict`` with the keys ``"backgroundColor"`` and ``"headerColor"``.
``subreddit`` The :class:`.Subreddit` the button widget belongs to.
``text`` The widget's text, as Markdown.
``textHtml`` The widget's text, as HTML.
============= =====================================================================
"""
class WidgetModeration:
"""Class for moderating a particular widget.
Example usage:
.. code-block:: python
widget = reddit.subreddit("test").widgets.sidebar[0]
widget.mod.update(shortName="My new title")
widget.mod.delete()
"""
def __init__(
self,
widget: Widget,
subreddit: praw.models.Subreddit | str,
reddit: praw.Reddit,
):
"""Initialize a :class:`.WidgetModeration` instance."""
self.widget = widget
self._reddit = reddit
self._subreddit = subreddit
def delete(self):
"""Delete the widget.
Example usage:
.. code-block:: python
widget.mod.delete()
"""
path = API_PATH["widget_modify"].format(
widget_id=self.widget.id, subreddit=self._subreddit
)
self._reddit.delete(path)
def update(self, **kwargs: Any) -> Widget:
"""Update the widget. Returns the updated widget.
Parameters differ based on the type of widget. See `Reddit documentation
<https://www.reddit.com/dev/api#PUT_api_widget_{widget_id}>`_ or the document of
the particular type of widget.
:returns: The updated :class:`.Widget`.
For example, update a text widget like so:
.. code-block:: python
text_widget.mod.update(shortName="New text area", text="Hello!")
.. note::
Most parameters follow the ``lowerCamelCase`` convention. When in doubt,
check the Reddit documentation linked above.
"""
path = API_PATH["widget_modify"].format(
widget_id=self.widget.id, subreddit=self._subreddit
)
payload = {
key: value
for key, value in vars(self.widget).items()
if not key.startswith("_")
}
del payload["subreddit"] # not JSON serializable
if "mod" in payload:
del payload["mod"]
payload.update(kwargs)
widget = self._reddit.put(
path, data={"json": dumps(payload, cls=WidgetEncoder)}
)
widget.subreddit = self._subreddit
return widget
class SubredditWidgetsModeration:
"""Class for moderating a :class:`.Subreddit`'s widgets.
Get an instance of this class from :attr:`.SubredditWidgets.mod`.
Example usage:
.. code-block:: python
styles = {"backgroundColor": "#FFFF66", "headerColor": "#3333EE"}
reddit.subreddit("test").widgets.mod.add_text_area(
short_name="My title", text="**bold text**", styles=styles
)
.. note::
To use this class's methods, the authenticated user must be a moderator with
appropriate permissions.
"""
def __init__(self, subreddit: praw.models.Subreddit, reddit: praw.Reddit):
"""Initialize a :class:`.SubredditWidgetsModeration` instance."""
self._subreddit = subreddit
self._reddit = reddit
def _create_widget(self, payload: dict[str, Any]) -> WidgetType:
path = API_PATH["widget_create"].format(subreddit=self._subreddit)
widget = self._reddit.post(
path, data={"json": dumps(payload, cls=WidgetEncoder)}
)
widget.subreddit = self._subreddit
return widget
@_deprecate_args("short_name", "description", "buttons", "styles")
def add_button_widget(
self,
*,
buttons: list[dict[str, dict[str, str | int] | str | int]],
description: str,
short_name: str,
styles: dict[str, str],
**other_settings: Any,
) -> praw.models.ButtonWidget:
"""Add and return a :class:`.ButtonWidget`.
:param buttons: A list of dictionaries describing buttons, as specified in
`Reddit docs`_. As of this writing, the format is:
Each button is either a text button or an image button. A text button looks
like this:
.. code-block:: text
{
"kind": "text",
"text": a string no longer than 30 characters,
"url": a valid URL,
"color": a 6-digit rgb hex color, e.g., `#AABBCC`,
"textColor": a 6-digit rgb hex color, e.g., `#AABBCC`,
"fillColor": a 6-digit rgb hex color, e.g., `#AABBCC`,
"hoverState": {...}
}
An image button looks like this:
.. code-block:: text
{
"kind": "image",
"text": a string no longer than 30 characters,
"linkUrl": a valid URL,
"url": a valid URL of a Reddit-hosted image,
"height": an integer,
"width": an integer,
"hoverState": {...}
}
Both types of buttons have the field ``hoverState``. The field does not have
to be included (it is optional). If it is included, it can be one of two
types: ``"text"`` or ``"image"``. A text ``hoverState`` looks like this:
.. code-block:: text
{
"kind": "text",
"text": a string no longer than 30 characters,
"color": a 6-digit rgb hex color, e.g., `#AABBCC`,
"textColor": a 6-digit rgb hex color, e.g., `#AABBCC`,
"fillColor": a 6-digit rgb hex color, e.g., `#AABBCC`
}
An image ``hoverState`` looks like this:
.. code-block:: text
{
"kind": "image",
"url": a valid URL of a Reddit-hosted image,
"height": an integer,
"width": an integer
}
.. note::
The method :meth:`.upload_image` can be used to upload images to Reddit
for a ``url`` field that holds a Reddit-hosted image.
.. note::
An image ``hoverState`` may be paired with a text widget, and a text
``hoverState`` may be paired with an image widget.
:param description: Markdown text to describe the widget.
:param short_name: A name for the widget, no longer than 30 characters.
:param styles: A dictionary with keys ``"backgroundColor"`` and
``"headerColor"``, and values of hex colors. For example,
``{"backgroundColor": "#FFFF66", "headerColor": "#3333EE"}``.
:returns: The created :class:`.ButtonWidget`.
.. _reddit docs: https://www.reddit.com/dev/api#POST_api_widget
Example usage:
.. code-block:: python
widget_moderation = reddit.subreddit("test").widgets.mod
my_image = widget_moderation.upload_image("/path/to/pic.jpg")
buttons = [
{
"kind": "text",
"text": "View source",
"url": "https://github.com/praw-dev/praw",
"color": "#FF0000",
"textColor": "#00FF00",
"fillColor": "#0000FF",
"hoverState": {
"kind": "text",
"text": "ecruos weiV",
"color": "#FFFFFF",
"textColor": "#000000",
"fillColor": "#0000FF",
},
},
{
"kind": "image",
"text": "View documentation",
"linkUrl": "https://praw.readthedocs.io",
"url": my_image,
"height": 200,
"width": 200,
"hoverState": {"kind": "image", "url": my_image, "height": 200, "width": 200},
},
]
styles = {"backgroundColor": "#FFFF66", "headerColor": "#3333EE"}
new_widget = widget_moderation.add_button_widget(
short_name="Things to click",
description="Click some of these *cool* links!",
buttons=buttons,
styles=styles,
)
"""
button_widget = {
"buttons": buttons,
"description": description,
"kind": "button",
"shortName": short_name,
"styles": styles,
}
button_widget.update(other_settings)
return self._create_widget(button_widget)
@_deprecate_args(
"short_name", "google_calendar_id", "requires_sync", "configuration", "styles"
)
def add_calendar(
self,
*,
configuration: dict[str, bool | int],
google_calendar_id: str,
requires_sync: bool,
short_name: str,
styles: dict[str, str],
**other_settings: Any,
) -> praw.models.Calendar:
"""Add and return a :class:`.Calendar` widget.
:param configuration: A dictionary as specified in `Reddit docs`_. For example:
.. code-block:: python
{
"numEvents": 10,
"showDate": True,
"showDescription": False,
"showLocation": False,
"showTime": True,
"showTitle": True,
}
:param google_calendar_id: An email-style calendar ID. To share a Google
Calendar, make it public, then find the "Calendar ID".
:param requires_sync: Whether the calendar needs synchronization.
:param short_name: A name for the widget, no longer than 30 characters.
:param styles: A dictionary with keys ``"backgroundColor"`` and
``"headerColor"``, and values of hex colors. For example,
``{"backgroundColor": "#FFFF66", "headerColor": "#3333EE"}``.
:returns: The created :class:`.Calendar`.
.. _reddit docs: https://www.reddit.com/dev/api#POST_api_widget
Example usage:
.. code-block:: python
widget_moderation = reddit.subreddit("test").widgets.mod
styles = {"backgroundColor": "#FFFF66", "headerColor": "#3333EE"}
config = {
"numEvents": 10,
"showDate": True,
"showDescription": False,
"showLocation": False,
"showTime": True,
"showTitle": True,
}
calendar_id = "y6nm89jy427drk8l71w75w9wjn@group.calendar.google.com"
new_widget = widget_moderation.add_calendar(
short_name="Upcoming Events",
google_calendar_id=calendar_id,
requires_sync=True,
configuration=config,
styles=styles,
)
"""
calendar = {
"shortName": short_name,
"googleCalendarId": google_calendar_id,
"requiresSync": requires_sync,
"configuration": configuration,
"styles": styles,
"kind": "calendar",
}
calendar.update(other_settings)
return self._create_widget(calendar)
@_deprecate_args("short_name", "data", "styles", "description")
def add_community_list(
self,
*,
data: list[str | praw.models.Subreddit],
description: str = "",
short_name: str,
styles: dict[str, str],
**other_settings: Any,
) -> praw.models.CommunityList:
"""Add and return a :class:`.CommunityList` widget.
:param data: A list of subreddits. Subreddits can be represented as ``str`` or
as :class:`.Subreddit`. These types may be mixed within the list.
:param description: A string containing Markdown (default: ``""``).
:param short_name: A name for the widget, no longer than 30 characters.
:param styles: A dictionary with keys ``"backgroundColor"`` and
``"headerColor"``, and values of hex colors. For example,
``{"backgroundColor": "#FFFF66", "headerColor": "#3333EE"}``.
:returns: The created :class:`.CommunityList`.
Example usage:
.. code-block:: python
widget_moderation = reddit.subreddit("test").widgets.mod
styles = {"backgroundColor": "#FFFF66", "headerColor": "#3333EE"}
subreddits = ["learnpython", reddit.subreddit("redditdev")]
new_widget = widget_moderation.add_community_list(
short_name="My fav subs", data=subreddits, styles=styles, description="description"
)
"""
community_list = {
"data": data,
"kind": "community-list",
"shortName": short_name,
"styles": styles,
"description": description,
}
community_list.update(other_settings)
return self._create_widget(community_list)
@_deprecate_args("short_name", "text", "css", "height", "image_data", "styles")
def add_custom_widget(
self,
*,
css: str,
height: int,
image_data: list[dict[str, str | int]],
short_name: str,
styles: dict[str, str],
text: str,
**other_settings: Any,
) -> praw.models.CustomWidget:
"""Add and return a :class:`.CustomWidget`.
:param css: The CSS for the widget, no longer than 100000 characters.
.. note::
As of this writing, Reddit will not accept empty CSS. If you wish to
create a custom widget without CSS, consider using ``"/**/"`` (an empty
comment) as your CSS.
:param height: The height of the widget, between 50 and 500.
:param image_data: A list of dictionaries as specified in `Reddit docs`_. Each
dictionary represents an image and has the key ``"url"`` which maps to the
URL of an image hosted on Reddit's servers. Images should be uploaded using
:meth:`.upload_image`.
For example:
.. code-block:: python
[
{
"url": "https://some.link", # from upload_image()
"width": 600,
"height": 450,
"name": "logo",
},
{
"url": "https://other.link", # from upload_image()
"width": 450,
"height": 600,
"name": "icon",
},
]
:param short_name: A name for the widget, no longer than 30 characters.
:param styles: A dictionary with keys ``"backgroundColor"`` and
``"headerColor"``, and values of hex colors. For example,
``{"backgroundColor": "#FFFF66", "headerColor": "#3333EE"}``.
:param text: The Markdown text displayed in the widget.
:returns: The created :class:`.CustomWidget`.
.. _reddit docs: https://www.reddit.com/dev/api#POST_api_widget
Example usage:
.. code-block:: python
widget_moderation = reddit.subreddit("test").widgets.mod
image_paths = ["/path/to/image1.jpg", "/path/to/image2.png"]
image_urls = [widget_moderation.upload_image(img_path) for img_path in image_paths]
image_data = [
{"width": 600, "height": 450, "name": "logo", "url": image_urls[0]},
{"width": 450, "height": 600, "name": "icon", "url": image_urls[1]},
]
styles = {"backgroundColor": "#FFFF66", "headerColor": "#3333EE"}
new_widget = widget_moderation.add_custom_widget(
image_short_name="My widget",
text="# Hello world!",
css="/**/",
height=200,
image_data=image_data,
styles=styles,
)
"""
custom_widget = {
"css": css,
"height": height,
"imageData": image_data,
"kind": "custom",
"shortName": short_name,
"styles": styles,
"text": text,
}
custom_widget.update(other_settings)
return self._create_widget(custom_widget)
@_deprecate_args("short_name", "data", "styles")
def add_image_widget(
self,
*,
data: list[dict[str, str | int]],
short_name: str,
styles: dict[str, str],
**other_settings: Any,
) -> praw.models.ImageWidget:
"""Add and return an :class:`.ImageWidget`.
:param data: A list of dictionaries as specified in `Reddit docs`_. Each
dictionary has the key ``"url"`` which maps to the URL of an image hosted on
Reddit's servers. Images should be uploaded using :meth:`.upload_image`.
For example:
.. code-block:: python
[
{
"url": "https://some.link", # from upload_image()
"width": 600,
"height": 450,
"linkUrl": "https://github.com/praw-dev/praw",
},
{
"url": "https://other.link", # from upload_image()
"width": 450,
"height": 600,
"linkUrl": "https://praw.readthedocs.io",
},
]
:param short_name: A name for the widget, no longer than 30 characters.
:param styles: A dictionary with keys ``"backgroundColor"`` and
``"headerColor"``, and values of hex colors. For example,
``{"backgroundColor": "#FFFF66", "headerColor": "#3333EE"}``.
:returns: The created :class:`.ImageWidget`.
.. _reddit docs: https://www.reddit.com/dev/api#POST_api_widget
Example usage:
.. code-block:: python
widget_moderation = reddit.subreddit("test").widgets.mod
image_paths = ["/path/to/image1.jpg", "/path/to/image2.png"]
image_data = [
{
"width": 600,
"height": 450,
"linkUrl": "",
"url": widget_moderation.upload_image(img_path),
}
for img_path in image_paths
]
styles = {"backgroundColor": "#FFFF66", "headerColor": "#3333EE"}
new_widget = widget_moderation.add_image_widget(
short_name="My cool pictures", data=image_data, styles=styles
)
"""
image_widget = {
"data": data,
"kind": "image",
"shortName": short_name,
"styles": styles,
}
image_widget.update(other_settings)
return self._create_widget(image_widget)
@_deprecate_args("data")
def add_menu(
self,
*,
data: list[dict[str, list[dict[str, str]] | str]],
**other_settings: Any,
) -> praw.models.Menu | Widget:
"""Add and return a :class:`.Menu` widget.
:param data: A list of dictionaries describing menu contents, as specified in
`Reddit docs`_. As of this writing, the format is:
.. code-block:: text
[
{
"text": a string no longer than 20 characters,
"url": a valid URL
},
OR
{
"children": [
{
"text": a string no longer than 20 characters,
"url": a valid URL,
},
...
],
"text": a string no longer than 20 characters,
},
...
]
:returns: The created :class:`.Menu`.
.. _reddit docs: https://www.reddit.com/dev/api#POST_api_widget
Example usage:
.. code-block:: python
widget_moderation = reddit.subreddit("test").widgets.mod
menu_contents = [
{"text": "My homepage", "url": "https://example.com"},
{
"text": "Python packages",
"children": [
{"text": "PRAW", "url": "https://praw.readthedocs.io/"},
{"text": "requests", "url": "https://docs.python-requests.org/"},
],
},
{"text": "Reddit homepage", "url": "https://reddit.com"},
]
new_widget = widget_moderation.add_menu(data=menu_contents)
"""
menu = {"data": data, "kind": "menu"}
menu.update(other_settings)
return self._create_widget(menu)
@_deprecate_args("short_name", "display", "order", "styles")
def add_post_flair_widget(
self,
*,
display: str,
order: list[str],
short_name: str,
styles: dict[str, str],
**other_settings: Any,
) -> praw.models.PostFlairWidget | Widget:
"""Add and return a :class:`.PostFlairWidget`.
:param display: Display style. Either ``"cloud"`` or ``"list"``.
:param order: A list of flair template IDs. You can get all flair template IDs
in a subreddit with:
.. code-block:: python
flairs = [f["id"] for f in subreddit.flair.link_templates]
:param short_name: A name for the widget, no longer than 30 characters.
:param styles: A dictionary with keys ``"backgroundColor"`` and
``"headerColor"``, and values of hex colors. For example,
``{"backgroundColor": "#FFFF66", "headerColor": "#3333EE"}``.
:returns: The created :class:`.PostFlairWidget`.
Example usage:
.. code-block:: python
subreddit = reddit.subreddit("test")
widget_moderation = subreddit.widgets.mod
flairs = [f["id"] for f in subreddit.flair.link_templates]
styles = {"backgroundColor": "#FFFF66", "headerColor": "#3333EE"}
new_widget = widget_moderation.add_post_flair_widget(
short_name="Some flairs", display="list", order=flairs, styles=styles
)
"""
post_flair = {
"kind": "post-flair",
"display": display,
"shortName": short_name,
"order": order,
"styles": styles,
}
post_flair.update(other_settings)
return self._create_widget(post_flair)
@_deprecate_args("short_name", "text", "styles")
def add_text_area(
self,
*,
short_name: str,
styles: dict[str, str],
text: str,
**other_settings: Any,
) -> praw.models.TextArea:
"""Add and return a :class:`.TextArea` widget.
:param short_name: A name for the widget, no longer than 30 characters.
:param styles: A dictionary with keys ``"backgroundColor"`` and
``"headerColor"``, and values of hex colors. For example,
``{"backgroundColor": "#FFFF66", "headerColor": "#3333EE"}``.
:param text: The Markdown text displayed in the widget.
:returns: The created :class:`.TextArea`.
Example usage:
.. code-block:: python
widget_moderation = reddit.subreddit("test").widgets.mod
styles = {"backgroundColor": "#FFFF66", "headerColor": "#3333EE"}
new_widget = widget_moderation.add_text_area(
short_name="My cool title", text="*Hello* **world**!", styles=styles
)
"""
text_area = {
"shortName": short_name,
"text": text,
"styles": styles,
"kind": "textarea",
}
text_area.update(other_settings)
return self._create_widget(text_area)
@_deprecate_args("new_order", "section")
def reorder(self, new_order: list[Widget | str], *, section: str = "sidebar"):
"""Reorder the widgets.
:param new_order: A list of widgets. Represented as a list that contains
:class:`.Widget` objects, or widget IDs as strings. These types may be
mixed.
:param section: The section to reorder (default: ``"sidebar"``).
Example usage:
.. code-block:: python
widgets = reddit.subreddit("test").widgets
order = list(widgets.sidebar)
order.reverse()
widgets.mod.reorder(order)
"""
order = [
thing.id if isinstance(thing, Widget) else str(thing) for thing in new_order
]
path = API_PATH["widget_order"].format(
subreddit=self._subreddit, section=section
)
self._reddit.patch(path, data={"json": dumps(order), "section": section})
def upload_image(self, file_path: str) -> str:
"""Upload an image to Reddit and get the URL.
:param file_path: The path to the local file.
:returns: The URL of the uploaded image as a ``str``.
This method is used to upload images for widgets. For example, it can be used in
conjunction with :meth:`.add_image_widget`, :meth:`.add_custom_widget`, and
:meth:`.add_button_widget`.
Example usage:
.. code-block:: python
my_sub = reddit.subreddit("test")
image_url = my_sub.widgets.mod.upload_image("/path/to/image.jpg")
image_data = [{"width": 300, "height": 300, "url": image_url, "linkUrl": ""}]
styles = {"backgroundColor": "#FFFF66", "headerColor": "#3333EE"}
my_sub.widgets.mod.add_image_widget(
short_name="My cool pictures", data=image_data, styles=styles
)
"""
file = Path(file_path)
img_data = {
"filepath": file.name,
"mimetype": "image/jpeg",
}
if file_path.lower().endswith(".png"):
img_data["mimetype"] = "image/png"
url = API_PATH["widget_lease"].format(subreddit=self._subreddit)
# until we learn otherwise, assume this request always succeeds
upload_lease = self._reddit.post(url, data=img_data)["s3UploadLease"]
upload_data = {item["name"]: item["value"] for item in upload_lease["fields"]}
upload_url = f"https:{upload_lease['action']}"
with file.open("rb") as image:
response = self._reddit._core._requestor._http.post(
upload_url, data=upload_data, files={"file": image}
)
response.raise_for_status()
return f"{upload_url}/{upload_data['key']}"