From 692f2d2fcaf8717d7937606b96a044e494b35afc Mon Sep 17 00:00:00 2001 From: davfsa Date: Fri, 24 Dec 2021 00:48:33 +0100 Subject: [PATCH] Switch to pdoc (#856) * Fix webpage links to reduce the redirect count * Switch to pdoc * Add inventory (objects.inv) --- .github/workflows/ci.yml | 2 +- README.md | 10 +- dev-requirements.txt | 2 +- docs/assets/crates.svg | 3 + docs/assets/discord.svg | 3 + docs/assets/home.svg | 3 + docs/body.mako | 23 - docs/config.mako | 54 - docs/css.mako | 290 ----- docs/documentation.mako | 957 --------------- docs/footer.mako | 20 - docs/frame.html.jinja2 | 17 + docs/head.mako | 47 - docs/html.mako | 83 -- docs/index.html.jinja2 | 14 + docs/main.css | 443 +++++++ docs/module.html.jinja2 | 152 +++ docs/patched_pdoc.py | 160 ++- docs/search.mako | 233 ---- docs/syntax-highlighting.css | 559 +++++++++ examples/simple_dashboard/simple_dashboard.py | 15 +- hikari/__init__.py | 14 +- hikari/__init__.pyi | 5 +- hikari/_about.py | 2 +- hikari/api/cache.py | 136 +-- hikari/api/entity_factory.py | 110 +- hikari/api/event_factory.py | 28 +- hikari/api/event_manager.py | 132 +-- hikari/api/interaction_server.py | 45 +- hikari/api/rest.py | 1035 ++++++++--------- hikari/api/shard.py | 75 +- hikari/api/special_endpoints.py | 451 ++----- hikari/api/voice.py | 31 +- hikari/applications.py | 116 +- hikari/channels.py | 215 ++-- hikari/colors.py | 57 +- hikari/colours.py | 9 +- hikari/commands.py | 34 +- hikari/config.py | 143 +-- hikari/embeds.py | 220 ++-- hikari/emojis.py | 28 +- hikari/errors.py | 35 +- hikari/events/base_events.py | 32 +- hikari/events/channel_events.py | 86 +- hikari/events/guild_events.py | 200 +--- hikari/events/interaction_events.py | 8 +- hikari/events/lifetime_events.py | 6 +- hikari/events/member_events.py | 48 +- hikari/events/message_events.py | 231 +--- hikari/events/reaction_events.py | 142 +-- hikari/events/role_events.py | 36 +- hikari/events/shard_events.py | 99 +- hikari/events/typing_events.py | 44 +- hikari/events/user_events.py | 10 +- hikari/events/voice_events.py | 43 +- hikari/files.py | 146 +-- hikari/guilds.py | 360 +++--- hikari/impl/bot.py | 273 ++--- hikari/impl/buckets.py | 66 +- hikari/impl/event_manager.py | 74 +- hikari/impl/event_manager_base.py | 2 +- hikari/impl/interaction_server.py | 48 +- hikari/impl/rate_limits.py | 54 +- hikari/impl/rest.py | 88 +- hikari/impl/rest_bot.py | 134 ++- hikari/impl/shard.py | 53 +- hikari/impl/special_endpoints.py | 16 +- hikari/intents.py | 18 +- hikari/interactions/base_interactions.py | 91 +- hikari/interactions/command_interactions.py | 22 +- hikari/interactions/component_interactions.py | 34 +- hikari/internal/aio.py | 10 +- hikari/internal/attr_extensions.py | 12 +- hikari/internal/cache.py | 26 +- hikari/internal/collections.py | 16 +- hikari/internal/data_binding.py | 36 +- hikari/internal/deprecation.py | 77 +- hikari/internal/ed25519.py | 10 +- hikari/internal/enums.py | 126 +- hikari/internal/enums.pyi | 25 +- hikari/internal/fast_protocol.py | 2 +- hikari/internal/mentions.py | 16 +- hikari/internal/net.py | 30 +- hikari/internal/reflect.py | 6 +- hikari/internal/routes.py | 28 +- hikari/internal/time.py | 20 +- hikari/internal/ux.py | 66 +- hikari/invites.py | 52 +- hikari/iterators.py | 144 +-- hikari/messages.py | 322 +++-- hikari/permissions.py | 22 +- hikari/presences.py | 4 +- hikari/snowflakes.py | 14 +- hikari/stickers.py | 8 +- hikari/templates.py | 10 +- hikari/traits.py | 269 ++--- hikari/undefined.py | 14 +- hikari/users.py | 156 ++- hikari/voices.py | 6 +- hikari/webhooks.py | 226 ++-- pages/index.html | 376 ++++-- pages/logo.png | Bin 174180 -> 153709 bytes pages/site.css | 247 ---- pipelines/codespell.nox.py | 4 +- pipelines/config.py | 2 + pipelines/format.nox.py | 4 +- pipelines/nox.py | 2 +- pipelines/pages.nox.py | 128 -- pipelines/pdoc.nox.py | 73 ++ pipelines/utils.nox.py | 31 +- pyproject.toml | 1 - scripts/prebuild_index.js | 54 - tests/__init__.py | 6 +- tests/hikari/internal/test_deprecation.py | 60 +- 114 files changed, 4610 insertions(+), 6606 deletions(-) create mode 100644 docs/assets/crates.svg create mode 100644 docs/assets/discord.svg create mode 100644 docs/assets/home.svg delete mode 100644 docs/body.mako delete mode 100644 docs/config.mako delete mode 100644 docs/css.mako delete mode 100644 docs/documentation.mako delete mode 100644 docs/footer.mako create mode 100644 docs/frame.html.jinja2 delete mode 100644 docs/head.mako delete mode 100644 docs/html.mako create mode 100644 docs/index.html.jinja2 create mode 100644 docs/main.css create mode 100644 docs/module.html.jinja2 delete mode 100644 docs/search.mako create mode 100644 docs/syntax-highlighting.css delete mode 100644 pages/site.css delete mode 100644 pipelines/pages.nox.py create mode 100644 pipelines/pdoc.nox.py delete mode 100644 scripts/prebuild_index.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 796ef400c4..33f9525a45 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -144,7 +144,7 @@ jobs: - name: Build pages run: | pip install nox - nox -s pages + nox -s pdoc - name: Upload artifacts if: github.event_name != 'release' diff --git a/README.md b/README.md index cb8c8e0351..9ef0d46bb7 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Test coverage
Discord invite -Documentation status +Documentation status

An opinionated, static typed Discord microframework for Python3 and asyncio that supports Discord's V8 REST API and @@ -60,9 +60,9 @@ bot = hikari.GatewayBot(intents=hikari.Intents.ALL, token="...") The above example would enable all intents, thus enabling events relating to member presences to be received (you'd need to whitelist your application first to be able to start the bot if you do this). -[Other options also exist](https://hikari-py.dev/hikari/impl/bot.html#hikari.impl.bot.GatewayBot) such as -[customising timeouts for requests](https://hikari-py.dev/hikari/config.html#hikari.config.HTTPSettings.timeouts) -and [enabling a proxy](https://hikari-py.dev/hikari/config.html#hikari.config.ProxySettings). +[Other options also exist](https://www.hikari-py.dev/hikari/impl/bot.html#hikari.impl.bot.GatewayBot) such as +[customising timeouts for requests](https://www.hikari-py.dev/hikari/config.html#hikari.config.HTTPSettings.timeouts) +and [enabling a proxy](https://www.hikari-py.dev/hikari/config.html#hikari.config.ProxySettings). Also note that you could pass extra options to `bot.run` during development, for example: @@ -77,7 +77,7 @@ bot.run( ) ``` -[Many other helpful options](https://hikari-py.dev/hikari/impl/bot.html#hikari.impl.bot.GatewayBot.run) +[Many other helpful options](https://www.hikari-py.dev/hikari/impl/bot.html#hikari.impl.bot.GatewayBot.run) exist for you to take advantage of if you wish. Events are determined by the type annotation on the event parameter, or alternatively as a type passed to the diff --git a/dev-requirements.txt b/dev-requirements.txt index 6d4c46dc3e..c1428d62cd 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -21,7 +21,7 @@ async-timeout==4.0.2 # Used for timeouts in some test cases. # DOCUMENTATION # ################# -pdoc3==0.10.0 +pdoc==8.0.1 sphobjinv==2.1 ################# diff --git a/docs/assets/crates.svg b/docs/assets/crates.svg new file mode 100644 index 0000000000..fb24fe9d18 --- /dev/null +++ b/docs/assets/crates.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/assets/discord.svg b/docs/assets/discord.svg new file mode 100644 index 0000000000..2727db171a --- /dev/null +++ b/docs/assets/discord.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/assets/home.svg b/docs/assets/home.svg new file mode 100644 index 0000000000..8f3ffe3e94 --- /dev/null +++ b/docs/assets/home.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/body.mako b/docs/body.mako deleted file mode 100644 index 3a7f964d9e..0000000000 --- a/docs/body.mako +++ /dev/null @@ -1,23 +0,0 @@ -## Copyright (c) 2020 Nekokatt -## Copyright (c) 2021 davfsa -## -## Permission is hereby granted, free of charge, to any person obtaining a copy -## of this software and associated documentation files (the "Software"), to deal -## in the Software without restriction, including without limitation the rights -## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -## copies of the Software, and to permit persons to whom the Software is -## furnished to do so, subject to the following conditions: -## -## The above copyright notice and this permission notice shall be included in all -## copies or substantial portions of the Software. -## -## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -## SOFTWARE. -<%include file="head.mako"/> -<%include file="documentation.mako"/> -<%include file="footer.mako"/> diff --git a/docs/config.mako b/docs/config.mako deleted file mode 100644 index fd99c93e53..0000000000 --- a/docs/config.mako +++ /dev/null @@ -1,54 +0,0 @@ -## Copyright (c) 2020 Nekokatt -## Copyright (c) 2021 davfsa -## -## Permission is hereby granted, free of charge, to any person obtaining a copy -## of this software and associated documentation files (the "Software"), to deal -## in the Software without restriction, including without limitation the rights -## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -## copies of the Software, and to permit persons to whom the Software is -## furnished to do so, subject to the following conditions: -## -## The above copyright notice and this permission notice shall be included in all -## copies or substantial portions of the Software. -## -## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -## SOFTWARE. -<%! - import hikari as _hikari - - show_inherited_members = True - extract_module_toc_into_sidebar = True - list_class_variables_in_index = True - sort_identifiers = True - show_type_annotations = True - - show_source_code = True - - git_link_template = "https://github.com/hikari-py/hikari/blob/{commit}/{path}#L{start_line}-L{end_line}" - - link_prefix = "" - - hljs_style = "atom-one-dark" - - # Doesn't really do anything, just enables lurn_search - lunr_search = {"fuzziness": 0} - - site_accent = "#ff029a" - site_logo_name = "logo.png" - site_logo_url = "https://hikari-py.dev/logo.png" - site_description = "A Discord Bot framework for modern Python and asyncio built on good intentions" - - # Versions of stuff - mathjax_version = "2.7.5" - bootstrap_version = "4.5.0" - highlightjs_version = "9.12.0" - jquery_version = "3.5.1" - popperjs_version = "1.16.0" - - root_url = "https://github.com/hikari-py/hikari" -%> diff --git a/docs/css.mako b/docs/css.mako deleted file mode 100644 index ddbb64c76b..0000000000 --- a/docs/css.mako +++ /dev/null @@ -1,290 +0,0 @@ -## Copyright (c) 2020 Nekokatt -## Copyright (c) 2021 davfsa -## -## Permission is hereby granted, free of charge, to any person obtaining a copy -## of this software and associated documentation files (the "Software"), to deal -## in the Software without restriction, including without limitation the rights -## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -## copies of the Software, and to permit persons to whom the Software is -## furnished to do so, subject to the following conditions: -## -## The above copyright notice and this permission notice shall be included in all -## copies or substantial portions of the Software. -## -## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -## SOFTWARE. - -img#logo { - border-radius: 15px; - width: 30px; - height: 30px; - margin-right: 0.5em; - ## Hide alt when image is not there - text-indent: 100%; - white-space: nowrap; - overflow: hidden; -} - -small.smaller { - font-size: 0.50em; -} - -html { - height: 100%; - scroll-behavior: smooth; - scrollbar-color: #202324 #454a4d; -} - -body { - background-color: #181A1B; - color: #C9C5C0; - height: fit-content; -} - -h1 { - margin-top: 3rem; -} - -h2 { - margin-top: 1.75rem; - margin-bottom: 1em; -} - -h3 { - margin-top: 1.25rem; -} - -h4 { - margin-top: 1rem; -} - -.nav-section { - margin-top: 2em; -} - -.monospaced { - font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; -} - -a.sidebar-nav-pill, -a.sidebar-nav-pill:active, -a.sidebar-nav-pill:hover { - color: #BDB7AF; -} - -.module-source > details > pre { - display: block; - overflow-x: auto; - overflow-y: auto; - max-height: 600px; - font-size: 0.8em; -} - -a { - color: #DE4F91; -} - -a:hover { - color: #64B1F2; -} - -.container > li { - margin-left: 1em; - margin-top: 2.5em; -} - -.jumbotron { - background-color: #232627; -} - -.breadcrumb-item.inactive > a { - color: #d264d0 !important; -} - -.breadcrumb-item.active > a { - color: #de4f91 !important; -} - -.breadcrumb-item+.breadcrumb-item::before { - content: "."; -} - -.module-breadcrumb { - padding-left: 0 !important; - background-color: #232627; -} - -ul.nested { - margin-left: 1em; -} - -h2#parameters::after { - margin-left: 2em; -} - -.anchor:target { - background-color: var(--dark); -} - -@media screen and (max-width: 990px) { - .anchor:target { - margin-left: -2em; - padding-left: 2em; - } -} - -@media screen and (min-width: 990px) { - .anchor:target { - border-radius: 0.5em; !important - margin-right: -2em; - padding-right: 2em; - margin-top: -1em; - padding-top: 1em; - } -} - -dt { - margin-left: 2em; -} - -dd { - margin-left: 4em; -} - -dl.no-nest > dt { - margin-left: 0em; -} - -dl.no-nest > dd { - margin-left: 2em; -} - -dl.root { - margin-bottom: 2em; -} - -.definition { - display: block; - margin-bottom: 8em !important; -} - -.definition .row { - display: block; - margin-bottom: 4em !important; -} - -.definition h2 { - font-size: 1em; - font-weight: bolder; -} - -.sep { - height: 2em; -} - -code { - color: #DB61D9; -} - -## Check this to change it -code .active { - color: #e83e8c; -} - -code a { - color: #E94A93; -} - -a.dotted:hover, abbr:hover { - text-decoration: underline #9E9689 dotted !important; -} - -a.dotted, abbr { - text-decoration: none !important; -} - -## Custom search formatting to look somewhat bootstrap-py -.gsc-search-box, .gsc-search-box-tools, .gsc-control-cse { - background: none !important; - border: none !important; -} - -.gsc-search-button-v2, .gsc-search-button-v2:hover, .gsc-search-button-v2:focus { - color: var(--success) !important; - border-color: var(--success) !important; - background: none !important; - padding: 6px 32px !important; - font-size: inherit !important; -} - -.gsc-search-button-v2 > svg { - fill: var(--success) !important; -} - -.gsc-input-box { - border-radius: 3px; -} - -.gsc-control-cse { - width: 300px !important; - margin-top: 0 !important; -} - -.gsc-control-cse .gsc-control-cse-en { - margin-top: 0 !important; -} - -.bg-dark { - background-color: #2C2F31 !important; -} - -.text-muted { - color: #9E9689 !important; -} - -.alert-primary { - color: #7CC3FF; - background-color: #262A2B; - border-color: #003B7B; -} - -.alert-secondary { - color: #C2BCB4; - background-color: #282B2C; - border-color: #3B4042; -} - -.alert-success { - color: #99E6AB; - background-color: #1A3E29; - border-color: #255A32; -} - -.alert-info { - color: #8EE3F1; - background-color: #143B43; - border-color: #1E5961; -} - -.alert-warning { - color: #FBD770; - background-color: #513E00; - border-color: #7B5C00; -} - -.alert-danger { - color: rgb(225, 134, 143); - background-color: rgb(67, 12, 17); - border-color: rgb(104, 18, 27); -} - -mark { - background-color: #333333; - border-radius: 0.1em; - color: #DB61D9; -} diff --git a/docs/documentation.mako b/docs/documentation.mako deleted file mode 100644 index ea0413d4a6..0000000000 --- a/docs/documentation.mako +++ /dev/null @@ -1,957 +0,0 @@ -## Copyright (c) 2020 Nekokatt -## Copyright (c) 2021 davfsa -## -## Permission is hereby granted, free of charge, to any person obtaining a copy -## of this software and associated documentation files (the "Software"), to deal -## in the Software without restriction, including without limitation the rights -## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -## copies of the Software, and to permit persons to whom the Software is -## furnished to do so, subject to the following conditions: -## -## The above copyright notice and this permission notice shall be included in all -## copies or substantial portions of the Software. -## -## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -## SOFTWARE. -<%! - import os - import typing - import builtins - import importlib - import inspect - import re - import sphobjinv - import urllib.error - - inventory_urls = [ - "https://docs.python.org/3/objects.inv", - "https://docs.aiohttp.org/en/stable/objects.inv", - "https://www.attrs.org/en/stable/objects.inv", - "https://multidict.readthedocs.io/en/latest/objects.inv", - "https://yarl.readthedocs.io/en/latest/objects.inv", - ] - - inventories = {} - - for i in inventory_urls: - try: - print("Prefetching", i) - inv = sphobjinv.Inventory(url=i) - url, _, _ = i.partition("objects.inv") - inventories[url] = inv.json_dict() - except urllib.error.URLError as ex: - # Ignore not being able to fetch inventory when not on CI - if "CI" not in os.environ: - print(f"Not able to prefetch {i}. Will continue without it") - continue - - raise - - # Remove the `about` section from all the inventories - for inv in inventories.values(): - to_delete = [] - for n, obj in inv.items(): - if isinstance(obj, dict) and obj["name"] == "about": - to_delete.append(n) - - for n in to_delete: - del inv[n] - - - located_external_refs = {} - unlocatable_external_refs = set() - - def discover_source(fqn): - if fqn in unlocatable_external_refs: - return - - if fqn.startswith("builtins."): - fqn = fqn.replace("builtins.", "") - - if fqn not in located_external_refs: - # print("attempting to find intersphinx reference for", fqn) - for base_url, inv in inventories.items(): - for obj in inv.values(): - if isinstance(obj, dict) and obj["name"] == fqn: - uri_frag = obj["uri"] - if uri_frag.endswith("$"): - uri_frag = uri_frag[:-1] + fqn - - url = base_url + uri_frag - # print("discovered", fqn, "at", url) - located_external_refs[fqn] = url - break - try: - return located_external_refs[fqn] - except KeyError: - # print("blacklisting", fqn, "as it cannot be dereferenced from external documentation") - unlocatable_external_refs.add(fqn) - - project_inventory = sphobjinv.Inventory() - - import atexit - - @atexit.register - def dump_inventory(): - import hikari - - project_inventory.project = "hikari" - project_inventory.version = hikari.__version__ - - text = project_inventory.data_file(contract=True) - ztext = sphobjinv.compress(text) - sphobjinv.writebytes('public/objects.inv', ztext) - - - # To get links to work in type hints to builtins, we do a bit of hacky search-replace using regex. - # This generates regex to match general builtins in typehints. - builtin_patterns = [ - re.compile(f"(? -<% - import abc - import ast - import enum - import functools - import inspect - import re - import textwrap - - import pdoc - - from pdoc.html_helpers import extract_toc, glimpse, to_html as _to_html, format_git_link - - # Hikari Enum hack - from hikari.internal import enums - - # Allow imports to resolve properly. - typing.TYPE_CHECKING = True - - - QUAL_ABC = "abstract" - QUAL_ABSTRACT = "abstract" - QUAL_ASYNC_DEF = "async def" - QUAL_CLASS = "class" - QUAL_DATACLASS = "dataclass" - QUAL_CACHED_PROPERTY = "cached property" - QUAL_CONST = "const" - QUAL_DEF = "def" - QUAL_ENUM = "enum" - QUAL_ENUM_FLAG = "enum flag" - QUAL_EXCEPTION = "exception" - QUAL_EXTERNAL = "extern" - QUAL_INTERFACE = "abstract trait" - QUAL_METACLASS = "meta" - QUAL_MODULE = "module" - QUAL_NAMESPACE = "namespace" - QUAL_PACKAGE = "package" - QUAL_PROPERTY = "property" - QUAL_PROTOCOL = "trait" - QUAL_TYPEHINT = "type hint" - QUAL_VAR = "var" - QUAL_WARNING = "warning" - - def get_url_for_object_from_imports(name, dobj): - if dobj.module: - fqn = dobj.module.obj.__name__ + "." + dobj.obj.__qualname__ - elif hasattr(dobj.obj, "__module__"): - fqn = dobj.obj.__module__ + "." + dobj.obj.__qualname__ - else: - fqn = dobj.name - - url = discover_source(fqn) - if url is None: - url = discover_source(name) - - if url is None: - url = get_url_from_imports(fqn) - - return url - - def get_url_from_imports(fqn): - if fqn_match := re.match(r"([a-z_]+)\.((?:[^\.]|^\s)+)", fqn): - if import_match := re.search(f"from (.*) import (.*) as {fqn_match.group(1)}", module.source): - fqn = import_match.group(1) + "." + import_match.group(2) + "." + fqn_match.group(2) - try: - return pdoc._global_context[fqn].url(relative_to=module, link_prefix=link_prefix, top_ancestor=not show_inherited_members) - # print(old_fqn, "->", fqn, "via", url) - except KeyError: - # print("maybe", fqn, "is external but aliased?") - return discover_source(fqn) - elif import_match := re.search(f"from (.*) import {fqn_match.group(1)}", module.source): - old_fqn = fqn - fqn = import_match.group(1) + "." + fqn_match.group(1) + "." + fqn_match.group(2) - try: - return pdoc._global_context[fqn].url(relative_to=module, link_prefix=link_prefix, top_ancestor=not show_inherited_members) - # print(old_fqn, "->", fqn, "via", url) - except KeyError: - # print("maybe", fqn, "is external but aliased?") - return discover_source(fqn) - elif import_match := re.search(f"import (.*) as {fqn_match.group(1)}", module.source): - old_fqn = fqn - fqn = import_match.group(1) + "." + fqn_match.group(2) - try: - return pdoc._global_context[fqn].url(relative_to=module, link_prefix=link_prefix, top_ancestor=not show_inherited_members) - # print(old_fqn, "->", fqn, "via", url) - except KeyError: - # print("maybe", fqn, "is external but aliased?") - return discover_source(fqn) - else: - return None - - def get_url_to_object_maybe_module(dobj): - try: - # ref = dobj if not hasattr(dobj.obj, "__module__") else pdoc._global_context[dobj.obj.__module__ + "." + dobj.obj.__qualname__] - ref = pdoc._global_context[dobj.obj.__module__ + "." + dobj.obj.__qualname__] - return ref.url(relative_to=module, link_prefix=link_prefix, top_ancestor=not show_inherited_members) - except Exception: - return dobj.url(relative_to=module, link_prefix=link_prefix, top_ancestor=not show_inherited_members) - - def debuiltinify(phrase: str): - if phrase.startswith("builtins."): - phrase = phrase[len("builtins."):] - elif phrase.startswith("typing."): - phrase = phrase[len("typing."):] - elif phrase.startswith("asyncio."): - phrase = phrase[len("asyncio."):] - return phrase - - # Fixed Linkify that works with nested type hints... - def _fixed_linkify(match: typing.Match, *, link: typing.Callable[..., str], module: pdoc.Module, wrap_code=False): - #print("matched", match.groups(), match.group()) - - try: - code_span = match.group('code') - except IndexError: - code_span = match.group() - - if code_span.startswith("`") and code_span.endswith("`"): - code_span = codespan[1:-1] - - # Extract identifiers. - items = list(re.finditer(r"(\w|\.)+", code_span))[::-1] - - # For each identifier, replace it with a link. - for match in items: - phrase = match.group() - - ident = module.find_ident(phrase) - - if isinstance(ident, pdoc.External): - phrase = debuiltinify(ident.name) - - url = get_url_for_object_from_imports(phrase, ident) - - if url is None: - module_part = module.find_ident(phrase.split('.')[0]) - if not isinstance(module_part, pdoc.External): - print(f"Code reference `{phrase}` in module '{module.refname}' does not match any documented object.") - print("Type", module_part.__class__, module_part) - - bits = ident.name.split(".")[:-1] - - while bits: - partial_phrase = ".".join(bits) - if partial_phrase in pdoc._global_context: - url = pdoc._global_context[partial_phrase].url(relative_to=module, link_prefix=link_prefix, top_ancestor=not show_inherited_members) - a_tag = f"{repr(phrase)[1:-1]}" - break - - bits = bits[:-1] - else: - a_tag = phrase - else: - a_tag = f"{repr(phrase)[1:-1]}" - - - else: - url = ident.url(relative_to=module, link_prefix=link_prefix, top_ancestor=not show_inherited_members) - a_tag = f"{repr(ident.name)[1:-1]}" - - chunk = slice(*match.span()) - code_span = "".join(( - code_span[:chunk.start], - a_tag, - code_span[chunk.stop:], - )) - - if wrap_code: - code_span = code_span.replace('[', '\\[') - - # Wrapping in HTML as opposed to backticks evaluates markdown */_ markers, - # so let's escape them in text (but not in HTML tag attributes). - # Backticks also cannot be used because html returned from `link()` - # would then become escaped. - # This finds overlapping matches, https://stackoverflow.com/a/5616910/1090455 - cleaned = re.sub(r'(_(?=[^>]*?(?:<|$)))', r'\\\1', code_span) - return '{}'.format(cleaned) - return code_span - - - from pdoc import html_helpers - html_helpers._linkify = _fixed_linkify - - # Help, it is a monster! - def link( - dobj: pdoc.Doc, - *, - with_prefixes=False, - simple_names=False, - css_classes="", - name=None, - default_type="", - dotted=True, - anchor=False, - fully_qualified=False, - hide_ref=False, - recurse=True, - ): - prefix = "" - name = name or dobj.name - - name = debuiltinify(name) - - show_object = False - if with_prefixes: - if isinstance(dobj, pdoc.Function): - qual = QUAL_ASYNC_DEF if dobj._is_async else QUAL_DEF - - if not simple_names: - if getattr(dobj.obj, "__isabstractmethod__", False): - prefix = f"{QUAL_ABSTRACT} " - - prefix = "" + prefix + qual + " " - - elif isinstance(dobj, pdoc.Variable): - if getattr(dobj.obj, "__isabstractmethod__", False) and not simple_names: - prefix = f"{QUAL_ABSTRACT} " - - descriptor = None - is_descriptor = False - - if hasattr(dobj.cls, "obj"): - for cls in dobj.cls.obj.mro(): - if (descriptor := cls.__dict__.get(dobj.name)) is not None: - is_descriptor = hasattr(descriptor, "__get__") - break - - if all(not c.isalpha() or c.isupper() for c in dobj.name): - prefix = f"{prefix}{QUAL_CONST} " - elif is_descriptor: - qual = QUAL_CACHED_PROPERTY if isinstance(descriptor, functools.cached_property) else QUAL_PROPERTY - prefix = f"{prefix}{qual} " - elif dobj.module.name == "typing" or dobj.docstring and dobj.docstring.casefold().startswith(("type hint", "typehint", "type alias")): - show_object = not simple_names - prefix = f"{prefix}{QUAL_TYPEHINT} " - else: - prefix = f"{prefix}{QUAL_VAR} " - - elif isinstance(dobj, pdoc.Class): - qual = "" - - if getattr(dobj.obj, "_is_protocol", False): - qual += QUAL_PROTOCOL - elif issubclass(dobj.obj, type): - qual += QUAL_METACLASS - else: - if enums.Flag in dobj.obj.mro() or enum.Flag in dobj.obj.mro(): - qual += QUAL_ENUM_FLAG - elif enums.Enum in dobj.obj.mro() or enum.Enum in dobj.obj.mro(): - qual += QUAL_ENUM - elif hasattr(dobj.obj, "__attrs_attrs__"): - qual += QUAL_DATACLASS - elif issubclass(dobj.obj, Warning): - qual += QUAL_WARNING - elif issubclass(dobj.obj, BaseException): - qual += QUAL_EXCEPTION - else: - qual += QUAL_CLASS - - if not simple_names: - if inspect.isabstract(dobj.obj): - if re.match(r"^I[A-Za-z]", dobj.name): - qual = f"{QUAL_INTERFACE} {qual}" - else: - qual = f"{QUAL_ABC} {qual}" - - prefix = f"{qual} " - - elif isinstance(dobj, pdoc.Module): - qual = QUAL_PACKAGE if dobj.is_package else QUAL_NAMESPACE if dobj.is_namespace else QUAL_MODULE - prefix = f"{qual} " - - else: - if isinstance(dobj, pdoc.External): - prefix = f"{QUAL_EXTERNAL} {default_type} " - else: - prefix = f"{default_type} " - else: - name = name or dobj.name or "" - - if fully_qualified and not simple_names: - name = dobj.module.name + "." + dobj.obj.__qualname__ - - if isinstance(dobj, pdoc.External): - url = get_url_for_object_from_imports(name, dobj) - - if url is None: - # print("Could not resolve where", fqn, "came from :(") - return name - else: - url = get_url_to_object_maybe_module(dobj) - - if simple_names: - name = simple_name(name) - - extra = "" - if show_object: - extra = f" = {dobj.obj}" - - classes = [] - class_str = " ".join(classes) - - if class_str.strip(): - class_str = f"class={class_str!r}" - - anchor = "" if not anchor else f'id="{dobj.refname}"' - - return '{}{}{}'.format(prefix, dobj.name + " -- " + glimpse(dobj.docstring), url, anchor, class_str, name, extra) - - def simple_name(s): - _, _, name = s.rpartition(".") - return name - - def get_annotation(bound_method, sep=':'): - annot = bound_method(link=link) - - annot = annot.replace("NoneType", "None") - # Remove quotes. - if annot.startswith("'") and annot.endswith("'"): - annot = annot[1:-1] - - if annot.startswith("builtins."): - annot = annot[len("builtins."):] - - if annot: - annot = ' ' + sep + '\N{NBSP}' + annot - - # for pattern in builtin_patterns: - # annot = pattern.sub(r"builtins.\1", annot) - - return annot - - def to_html(text): - text = _to_html(text, module=module, link=link, latex_math=latex_math) - replacements = [ - ('class="admonition info"', 'class="alert alert-primary"'), - ('class="admonition warning"', 'class="alert alert-warning"'), - ('class="admonition danger"', 'class="alert alert-danger"'), - ('class="admonition note"', 'class="alert alert-success"') - ] - - for before, after in replacements: - text = text.replace(before, after) - - return text -%> -<%def name="ident(name)">${name} -<%def name="breadcrumb()"> - <% - module_breadcrumb = [] - - sm = module - while sm is not None: - module_breadcrumb.append(sm) - sm = sm.supermodule - - module_breadcrumb.reverse() - %> - - - -<%def name="show_var(v)"> - <% - return_type = get_annotation(v.type_annotation) - parent = v.cls.obj if v.cls is not None else v.module.obj - if return_type == "": - - if hasattr(parent, "mro"): - for cls in parent.mro(): - if hasattr(cls, "__annotations__") and v.name in cls.__annotations__: - return_type = get_annotation(lambda *_, **__: cls.__annotations__[v.name]) - if return_type != "": - break - - if hasattr(parent, "__annotations__") and v.name in parent.__annotations__: - return_type = get_annotation(lambda *_, **__: parent.__annotations__[v.name]) - - return_type = re.sub(r'[\w\.]+', functools.partial(_fixed_linkify, link=link, module=v.module), return_type) - - value = None - if v.cls is not None: - try: - obj = getattr(v.cls.obj, v.name) - - simple_bases = ( - bytes, int, bool, str, float, complex, list, set, frozenset, dict, tuple, type(None), - enum.Enum, typing.Container - ) - - if isinstance(obj, simple_bases): - value = str(obj) - - # Combined enum tidyup - if value.count("|") > 3 and isinstance(obj, enum.Enum): - start = "\n\N{EM SPACE}\N{EM SPACE}\N{EM SPACE}" - value = f"({start} " + f"{start} | ".join(value.split(" | ")) + "\n)" - - except Exception as ex: - print(v.name, type(ex).__name__, ex) - - if value: - for enum_mapping in ("_value2member_map_", "_value_to_member_map_"): - if mapping := getattr(v.cls.obj, enum_mapping, None): - try: - real_value = getattr(v.cls.obj, v.name) - if real_value in mapping.values(): - return_type += f" = {real_value.value!r}" - break - except AttributeError: - pass - else: - return_type += f" = {value}" - - if hasattr(parent, "mro"): - name = f"{parent.__module__}.{parent.__qualname__}.{v.name}" - else: - name = f"{parent.__name__}.{v.qualname}" - - project_inventory.objects.append( - sphobjinv.DataObjStr( - name = name, - domain = "py", - role = "var", - uri = v.url(), - priority = "1", - dispname = "-", - ) - ) - %> -
-
-
${link(v, with_prefixes=True)}${return_type}
-
-
${v.docstring | to_html}
-
- -<%def name="show_func(f)"> - <% - params = f.params(annotate=show_type_annotations, link=link) - return_type = get_annotation(f.return_annotation, '->') - qual = QUAL_ASYNC_DEF if f._is_async else QUAL_DEF - anchored_name = f'{f.name}' - - example_str = qual + f.name + "(" + ", ".join(params) + ")" + return_type - - if params and params[0] in ("self", "mcs", "mcls", "metacls"): - params = params[1:] - - if len(params) > 4 or len(params) > 0 and len(example_str) > 70: - representation = "\n".join(( - qual + " " + anchored_name + "(", - *(f" {p}," for p in params), - ")" + return_type + ": ..." - )) - - elif params: - representation = f"{qual} {anchored_name}({', '.join(params)}){return_type}: ..." - else: - representation = f"{qual} {anchored_name}(){return_type}: ..." - - if f.module.name != f.obj.__module__: - try: - ref = pdoc._global_context[f.obj.__module__ + "." + f.obj.__qualname__] - redirect = True - except KeyError: - redirect = False - else: - redirect = False - - if not redirect: - project_inventory.objects.append( - sphobjinv.DataObjStr( - name = f.obj.__module__ + "." + f.obj.__qualname__, - domain = "py", - role = "func", - uri = f.url(), - priority = "1", - dispname = "-", - ) - ) - %> -
-
-
${representation}
-
-
- % if inspect.isabstract(f.obj): - This function is abstract! - % endif - % if redirect: - ${show_desc(f, short=True)} - This function is defined explicitly at ${link(ref, with_prefixes=False, fully_qualified=True)}. Visit that link to view the full documentation! - % else: - ${show_desc(f)} - - ${show_source(f)} - % endif -
-
-
- - -<%def name="show_class(c)"> - <% - variables = c.instance_variables(show_inherited_members, sort=sort_identifiers) + c.class_variables(show_inherited_members, sort=sort_identifiers) - methods = c.methods(show_inherited_members, sort=sort_identifiers) + c.functions(show_inherited_members, sort=sort_identifiers) - mro = c.mro() - subclasses = c.subclasses() - - # No clue why I need to do this, and I don't care at this point. This hurts my brain. - params = [p.replace("builtins.", "") for p in c.params(annotate=show_type_annotations, link=link)] - - example_str = f"{QUAL_CLASS} " + c.name + "(" + ", ".join(params) + ")" - - suppress_params = getattr(c.obj, "_is_protocol", False) - - if not suppress_params and (len(params) > 4 or len(example_str) > 70 and len(params) > 0): - representation = "\n".join(( - f"{QUAL_CLASS} {c.name} (", - *(f" {p}," for p in params), - "): ..." - )) - elif params and not suppress_params: - representation = f"{QUAL_CLASS} {c.name} (" + ", ".join(params) + "): ..." - else: - representation = f"{QUAL_CLASS} {c.name}: ..." - - if c.module.name != c.obj.__module__: - try: - ref = pdoc._global_context[c.obj.__module__ + "." + c.obj.__qualname__] - redirect = True - except KeyError: - redirect = False - else: - redirect = False - - if not redirect: - project_inventory.objects.append( - sphobjinv.DataObjStr( - name = c.obj.__module__ + "." + c.obj.__qualname__, - domain = "py", - role = "class", - uri = c.url(), - priority = "1", - dispname = "-", - ) - ) - %> -
-
- % if redirect: -

reference to ${link(c, with_prefixes=True)}

- % else: -

${link(c, with_prefixes=True, simple_names=True)}

- % endif -
-
- % if redirect: - ${show_desc(c, short=True)} - % else: -
${representation}
- - ${show_desc(c)} -
- ${show_source(c)} -
- - % if subclasses: -
Subclasses
-
- % for sc in subclasses: - % if not isinstance(sc, pdoc.External): -
${link(sc, with_prefixes=True, default_type="class")}
-
${sc.docstring or sc.obj.__doc__ or "" | glimpse, to_html}
- % endif - % endfor -
-
- % endif - - % if mro: -
Method resolution order
-
-
${link(c, with_prefixes=True)}
-
That's this class!
- % for mro_c in mro: - <% - if mro_c.obj is None: - module, _, cls = mro_c.qualname.rpartition(".") - try: - cls = getattr(importlib.import_module(module), cls) - mro_c.docstring = cls.__doc__ or "" - except: - pass - %> - -
${link(mro_c, with_prefixes=True, default_type="class")}
-
${mro_c.docstring | glimpse, to_html}
- % endfor -
-
- % endif - - % if variables: -
Variables and properties
-
- % for i in variables: - ${show_var(i)} - % endfor -
-
- % endif - - % if methods: -
Methods
-
- % for m in methods: - ${show_func(m)} - % endfor -
-
- % endif - % endif -
-
- -<%def name="show_desc(d, short=False)"> - <% - inherits = ' inherited' if d.inherits else '' - docstring = d.docstring or d.obj.__doc__ or "" - %> - % if not short: - % if inherits: -

- Inherited from: - % if hasattr(d.inherits, 'cls'): - ${link(d.inherits.cls, with_prefixes=False)}.${link(d.inherits, name=d.name, with_prefixes=False)} - % else: - ${link(d.inherits, with_prefixes=False)} - % endif -

- % endif - - ${docstring | to_html} - % else: - ${docstring | glimpse, to_html} - % endif - -<%def name="show_source(d)"> - % if (show_source_code or git_link_template) and d.source and d.obj is not getattr(d.inherits, 'obj', None): - <% git_link = format_git_link(git_link_template, d) %> - % if show_source_code: -
- - Expand source code - % if git_link: -
- Browse git - %endif -
-
${d.source | h}
-
- % elif git_link: - - %endif - %endif - - -
-
-

${breadcrumb()}

-

${module.docstring | to_html}

-
-
- -
-
- <% - variables = module.variables(sort=sort_identifiers and module.name != "hikari") - classes = module.classes(sort=sort_identifiers and module.name != "hikari") - functions = module.functions(sort=sort_identifiers and module.name != "hikari") - submodules = module.submodules() - supermodule = module.supermodule - - project_inventory.objects.append( - sphobjinv.DataObjStr( - name = module.name, - domain = "py", - role = "module", - uri = module.url(), - priority = "1", - dispname = "-", - ) - ) - %> - -
- - % if submodules: -
    - % for child_module in submodules: -
  • ${link(child_module, with_prefixes=True, css_classes="sidebar-nav-pill", dotted=False, simple_names=True)}
  • - % endfor -
- % endif - - % if variables or functions or classes: -

This module

- % endif - - % if variables: -
    - % for variable in variables: -
  • ${link(variable, with_prefixes=True, css_classes="sidebar-nav-pill", dotted=False, simple_names=True)}
  • - % endfor -
- % endif - - % if functions: -
    - % for function in functions: -
  • ${link(function, with_prefixes=True, css_classes="sidebar-nav-pill", dotted=False, simple_names=True)}
  • - % endfor -
- % endif - - % if classes: -
    - % for c in classes: - <% - if c.module.name != c.obj.__module__: - try: - ref = pdoc._global_context[c.obj.__module__ + "." + c.obj.__qualname__] - redirect = True - except KeyError: - redirect = False - else: - redirect = False - - members = c.functions(sort=sort_identifiers) + c.methods(sort=sort_identifiers) - - if list_class_variables_in_index: - members += (c.instance_variables(sort=sort_identifiers) + c.class_variables(sort=sort_identifiers)) - - if not show_inherited_members: - members = [i for i in members if not i.inherits] - - if sort_identifiers: - members = sorted(members) - %> - - ## Purposely using one item per list for layout reasons. -
  • - ${link(c, with_prefixes=True, css_classes="sidebar-nav-pill", dotted=False, simple_names=True)} - -
      - % if members and not redirect: - % for member in members: -
    • - ${link(member, with_prefixes=True, css_classes="sidebar-nav-pill", dotted=False, simple_names=True)} -
    • - % endfor - % endif -
      -
    -
  • - % endfor -
- % endif - -
- -
-
-
- ${show_source(module)} -
-
- - - % if submodules: -

Child Modules

-
-
- % for m in submodules: -
${link(m, simple_names=True, with_prefixes=True)}
-
${m.docstring | glimpse, to_html}
- % endfor -
-
- % endif - - % if variables: -

Variables and Type Hints

-
-
- % for v in variables: - ${show_var(v)} - % endfor -
-
- % endif - - % if functions: -

Functions

-
-
- % for f in functions: - ${show_func(f)} - % endfor -
-
- % endif - - % if classes: -

Classes

-
-
- % for c in classes: - ${show_class(c)} - % endfor -
-
- % endif -
-
-
diff --git a/docs/footer.mako b/docs/footer.mako deleted file mode 100644 index 679fb7cc34..0000000000 --- a/docs/footer.mako +++ /dev/null @@ -1,20 +0,0 @@ -## Copyright (c) 2020 Nekokatt -## Copyright (c) 2021 davfsa -## -## Permission is hereby granted, free of charge, to any person obtaining a copy -## of this software and associated documentation files (the "Software"), to deal -## in the Software without restriction, including without limitation the rights -## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -## copies of the Software, and to permit persons to whom the Software is -## furnished to do so, subject to the following conditions: -## -## The above copyright notice and this permission notice shall be included in all -## copies or substantial portions of the Software. -## -## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -## SOFTWARE. diff --git a/docs/frame.html.jinja2 b/docs/frame.html.jinja2 new file mode 100644 index 0000000000..70195ed6f5 --- /dev/null +++ b/docs/frame.html.jinja2 @@ -0,0 +1,17 @@ + + + + + + + {% block title %}{% endblock %} | v{{ __hikari_version__ }} + + + {% block head %}{% endblock %} + {% block style %} + + {% endblock %} + + +{% block body %}{% endblock %} + diff --git a/docs/head.mako b/docs/head.mako deleted file mode 100644 index 1035755274..0000000000 --- a/docs/head.mako +++ /dev/null @@ -1,47 +0,0 @@ -## Copyright (c) 2020 Nekokatt -## Copyright (c) 2021 davfsa -## -## Permission is hereby granted, free of charge, to any person obtaining a copy -## of this software and associated documentation files (the "Software"), to deal -## in the Software without restriction, including without limitation the rights -## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -## copies of the Software, and to permit persons to whom the Software is -## furnished to do so, subject to the following conditions: -## -## The above copyright notice and this permission notice shall be included in all -## copies or substantial portions of the Software. -## -## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -## SOFTWARE. -<% - import hikari - - DEPTH = '../' * module.url().count('/') -%> - diff --git a/docs/html.mako b/docs/html.mako deleted file mode 100644 index caf2132ed2..0000000000 --- a/docs/html.mako +++ /dev/null @@ -1,83 +0,0 @@ -## Copyright (c) 2020 Nekokatt -## Copyright (c) 2021 davfsa -## -## Permission is hereby granted, free of charge, to any person obtaining a copy -## of this software and associated documentation files (the "Software"), to deal -## in the Software without restriction, including without limitation the rights -## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -## copies of the Software, and to permit persons to whom the Software is -## furnished to do so, subject to the following conditions: -## -## The above copyright notice and this permission notice shall be included in all -## copies or substantial portions of the Software. -## -## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -## SOFTWARE. -############################# IMPORTS ############################## -<%! - import os - from pdoc import html_helpers -%> -########################### CONFIGURATION ########################## -<%include file="config.mako"/> -############################ COMPONENTS ############################ - - - - - - - % if module_list: - ${module.name} module list - - % else: - ${module.name} API documentation - - % endif - - ## Determine how to name the page. - % if "." in module.name: - - % else: - - % endif - - - - - - - - ## Google Search Engine integration - - - ## Bootstrap 4 stylesheet - - ## Highlight.js stylesheet - - ## Custom stylesheets - - - ## Provide LaTeX math support - - - - - <%include file="body.mako"/> - ## Script dependencies for Bootstrap. - - - - ## Highlightjs stuff - - - - diff --git a/docs/index.html.jinja2 b/docs/index.html.jinja2 new file mode 100644 index 0000000000..698be2ca59 --- /dev/null +++ b/docs/index.html.jinja2 @@ -0,0 +1,14 @@ +{# This site will never be publicly available, its just for developers sake #} +{% extends "default/index.html.jinja2" %} + diff --git a/docs/main.css b/docs/main.css new file mode 100644 index 0000000000..b9c5c85baa --- /dev/null +++ b/docs/main.css @@ -0,0 +1,443 @@ +:root { + --pdoc-background: #212529; +} + +.pdoc { + --text: #f7f7f7; + --muted: #9d9d9d; + --link: #d264d0; + --link-hover: #3989ff; + --code: #333; + --active: #555; + --accent: #343434; + --accent2: #555; + --nav-hover: rgba(0, 0, 0, 0.1); + --def: #ff79c6; + --name: #61aeee; + --annotation: #F471E6; + + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +/* Colors of overall document */ +body { + background-color: var(--pdoc-background); +} + +/* Responsive Layout */ +html, body { + width: 100%; + height: 100%; + word-break: break-word; +} + +@media (max-width: 1079px) { + :root { + --sidebar-width: 30rem; + } + + html { + font-size: 3vw; + } + + main, header { + padding: 2rem 3vw 0 1.5rem; + } + + /* Prevent scrolling when sidebar is open */ + html:not(.sidebar-hidden) body { + overflow: hidden !important; + } + + .sidebar-hidden nav.pdoc { + transform: translateX(calc(0px - var(--sidebar-width))); + } + + nav.pdoc { + transition: transform 0.2s; + } + + /* We only want to show this on mobile */ + .pdoc .sidebar-toggle { + position: fixed; + top: 0; + bottom: calc(100% - 6rem); + left: var(--sidebar-width); + + border-left: 5px solid grey; + border-top: 5px solid rgba(0, 0, 0, 0); + border-bottom: 5px solid rgba(0, 0, 0, 0); + } +} + +@media (min-width: 1080px) { + :root { + --sidebar-width: clamp(12.5rem, 28vw, 26rem); + } + + main, header { + padding: 3rem 2rem 3rem calc(var(--sidebar-width) + 3rem); + } +} + +/* Nav */ +nav.pdoc { + position: fixed; + left: 0; + top: 0; + bottom: 0; + height: 100vh; + width: var(--sidebar-width); + + z-index: 1; +} + +.pdoc .sidebar { + height: 100vh; + overflow: auto; + + padding: 1rem 1rem; + + background-color: var(--accent); + border-right: 1px solid var(--accent2); + scrollbar-color: var(--accent2) transparent /* Scrollbar color on Firefox */ +} + +.pdoc .sidebar::-webkit-scrollbar-thumb { + background-color: var(--accent2); /* Scrollbar color on Chromium-based browsers */ +} + +.pdoc .sidebar input[type=search] { + display: block; + outline-offset: 0; + width: 102%; +} + +.pdoc .sidebar ul { + list-style: none; + padding-left: 1rem; +} + +.pdoc .sidebar li { + display: block; + margin: 0; + padding: .2rem 0 .2rem .5rem; + transition: all 100ms; +} + +.pdoc .sidebar > ul > li { + padding-left: 0; +} + +.pdoc .sidebar li:hover { + background-color: var(--nav-hover); +} + +.pdoc .sidebar a:hover { + color: var(--text); +} + +.pdoc .sidebar a { + display: block; +} + +.pdoc .sidebar > h2:first-of-type { + margin-top: 1rem; +} + +.pdoc .sidebar .class:before { + content: "class "; + color: var(--muted); +} + +.pdoc .sidebar .function:after { + content: "()"; + color: var(--muted); +} + +.pdoc .sidebar .sidebar-buttons { + display: flex; + width: 100%; + align-items: center; + margin-bottom: 1rem; +} + +.pdoc .sidebar .sidebar-buttons .push { + margin-left: auto; +} + +.pdoc .svg-button > svg { + width: 1.5rem; + margin-left: .5rem; + cursor: pointer; +} + +/* General styling */ +html, main { + scroll-behavior: smooth; +} + +.pdoc { + color: var(--text); + /* enforce some styling even if bootstrap reboot is not included */ + box-sizing: border-box; + line-height: 1.5; + /* override background from pygments */ + background: none; +} + +.pdoc h1, .pdoc h2, .pdoc h3 { + font-weight: 300; + margin: .3em 0; + padding: .2em 0; +} + +.pdoc a { + text-decoration: none; + color: var(--link); +} + +.pdoc a:hover { + color: var(--link-hover); +} + +.pdoc blockquote { + margin-left: 2rem; +} + +.pdoc pre { + background-color: var(--code); + border-top: 1px solid var(--accent2); + border-bottom: 1px solid var(--accent2); + margin-bottom: 1em; + padding: .5rem 0 .5rem .5rem; + overflow-x: auto; +} + +.pdoc code { + color: var(--text); + padding: .2em .4em; + margin: 0; + font-size: 85%; + background-color: var(--code); + border-radius: 6px; +} + +.pdoc a > code { + color: inherit; +} + +.pdoc pre > code { + display: inline-block; + font-size: inherit; + background: none; + border: none; + padding: 0; +} + +/* Page Heading */ +.pdoc .modulename { + margin-top: 0; + font-weight: bold; +} + +.pdoc .modulename a { + color: var(--link); + transition: 100ms all; +} + +/* GitHub Button */ +.pdoc .git-button { + float: right; + border: solid var(--link) 1px; +} + +.pdoc .git-button:hover { + background-color: var(--link); + color: var(--pdoc-background); +} + +/* View Source */ +.pdoc details { + --shift: -2.4rem; + text-align: right; + margin-top: var(--shift); + margin-bottom: calc(0px - var(--shift)); + clear: both; + /* + stay on top of .attr even if it is filtered, see + https://stackoverflow.com/questions/25764404/why-does-stacking-order-change-on-webkit-filter-hover + */ + filter: opacity(1); +} + +.pdoc details:not([open]) { + height: 0; + overflow: visible; +} + +.pdoc details > summary { + font-size: .75rem; + cursor: pointer; + color: var(--muted); + border-width: 0; + padding: 0 .7em; + /* Firefox hides the arrow if we specify inline-block, + see https://bugzilla.mozilla.org/show_bug.cgi?id=1270163. + Chrome on the other hand does not support the two-property syntax yet, + so the last statement is ignored. See https://crbug.com/995106. */ + display: inline-block; + display: inline list-item; + user-select: none; +} + +.pdoc details > summary:focus { + outline: 0; +} + +.pdoc details > div { + margin-top: calc(0px - var(--shift) / 2); + text-align: left; +} + +/* Docstrings */ +.pdoc .docstring { + margin: 0 0 2rem 2rem; +} + +.pdoc .docstring pre { + margin-left: 1em; + margin-right: 1em; +} + +.pdoc .docstring li { + margin-bottom: 15px; +} + +.pdoc .docstring li:last-child { + margin-bottom: 0; +} + +.pdoc .docstring p { + margin-top: 0; + margin-bottom: .5rem; +} + + +/* Highlight focused element */ +.pdoc h1:target, +.pdoc h2:target, +.pdoc h3:target, +.pdoc h4:target, +.pdoc h5:target, +.pdoc h6:target { + background-color: var(--active); + box-shadow: -1rem 0 0 0 var(--active); +} + +.pdoc div:target > .attr, +.pdoc section:target > .attr, +.pdoc dd:target > a { + background-color: var(--active); +} + +.pdoc .attr:hover { + filter: contrast(0.95); +} + +/* Header link */ +.pdoc .headerlink { + position: absolute; + width: 0; + margin-left: -1.5rem; + line-height: 1.4rem; + /*font-size: 1.5rem;*/ + font-weight: normal; + transition: all 100ms ease-in-out; + opacity: 0; +} + +.pdoc .attr > .headerlink { + margin-left: -2.5rem; +} + +.pdoc *:hover > .headerlink, +.pdoc *:target > .attr > .headerlink { + opacity: 1; +} + +/* Attributes */ +.pdoc .attr { + display: block; + color: var(--text); + margin: 1rem 0 .5rem; + /* + lots of padding on the right to accommodate the view source button. + This is not ideal, but probably good enough for now. + */ + padding: .4rem 5rem .4rem 1rem; + background-color: var(--accent); +} + +.pdoc .classattr { + margin-left: 2rem; +} + +.pdoc .name { + color: var(--name); + font-weight: bold; +} + +.pdoc .def { + color: var(--def); + font-weight: bold; +} + +.pdoc .signature { + white-space: pre-wrap; +} + +.pdoc .annotation { + color: var(--annotation); +} + +/* Inherited Members */ +.pdoc .inherited { + margin-left: 2rem; +} + +.pdoc .inherited div { + margin-left: 2rem; +} + +.pdoc .inherited dt { + font-weight: 700; +} + +.pdoc .inherited dt, .pdoc .inherited dd { + display: inline; + margin-left: 0; + margin-bottom: .5rem; +} + +.pdoc .inherited dd:not(:last-child):after { + content: ", "; +} + +.pdoc .inherited .class:before { + content: "class "; +} + +.pdoc .inherited .function a:after { + content: "()"; +} + +/* Search results */ +.pdoc .search-result .docstring { + overflow: auto; + max-height: 25vh; +} + +.pdoc .search-result.focused > .attr { + background-color: var(--active); +} diff --git a/docs/module.html.jinja2 b/docs/module.html.jinja2 new file mode 100644 index 0000000000..3549d808ad --- /dev/null +++ b/docs/module.html.jinja2 @@ -0,0 +1,152 @@ +{# This file expands on pdoc's module template #} +{% extends "default/module.html.jinja2" %} + +{# ##### #} +{# Theme #} +{# ##### #} +{% block style %} + {% filter minify_css %} + + + + {% endfilter %} +{% endblock %} + +{# ######################## #} +{# Better inherited section #} +{# ######################## #} +{% macro inherited(cls) %} +{% for base, members in cls.inherited_members.items() %} +{% set m = None %}{# workaround for https://github.com/pallets/jinja/issues/1427 #} +{% set member_html %} +{% for m in members if is_public(m) | trim %} +
+ {{- m.taken_from | link(text=m.name.replace("__init__",base[1])) -}} +
+{% endfor %} +{% endset %} +{# we may not have any public members, in which case we don't want to print anything. #} +{% if member_html %} +
{{ base | link }}:
+ {{ member_html }} +
+{% endif %} +{% endfor %} +{% endmacro %} + + +{# #################### #} +{# Remove default value #} +{# #################### #} +{% macro variable(var) %} +
{{ headerlink(var) }} + {{ var.name }}{{ annotation(var) }} +
+{% endmacro %} + +{# ######### #} +{# Inventory #} +{# ######### #} +{% macro member(doc) %} + {{ add_to_inventory(doc) }} + {% if doc.type == "class" %} + {{ class(doc) }} + {% elif doc.type == "function" %} + {{ function(doc) }} + {% elif doc.type == "module" %} + {{ submodule(doc) }} + {% else %} + {{ variable(doc) }} + {% endif %} + {% if doc.type != "variable" %} + {{ view_source(doc) }} + {% endif %} + {{ docstring(doc) }} +{% endmacro %} + +{# ############## #} +{# Better sidebar #} +{# ############## #} +{% block nav %} +{{ add_to_inventory(module) if module.name != "" }} + + + +{% endblock %} diff --git a/docs/patched_pdoc.py b/docs/patched_pdoc.py index d45d176d61..33e1e605ce 100644 --- a/docs/patched_pdoc.py +++ b/docs/patched_pdoc.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# cython: language_level=3 # Copyright (c) 2020 Nekokatt # Copyright (c) 2021 davfsa # @@ -20,93 +19,76 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. - -import json -import os.path as path -import re - -import pdoc -from pdoc import cli - - -def _patched_generate_lunr_search(modules, index_docstrings, template_config): - # This will only be called once due to how we generate the documentation, so we can ignore the rest - assert len(modules) == 1, "expected only 1 module to be generated, got more" - top_module = modules[0] - - def trim_docstring(docstring): - return re.sub( - r""" - \s+[-=~]{3,}\s+| # title underlines - ^[ \t]*[`~]{3,}\w*$| # code blocks - \s*[`#*]+\s*| # common markdown chars - \s*([^\w\d_>])\1\s*| # sequences of punct of the same kind - \s*]*>\s* # simple HTML tags - \s+ # whitespace sequences - """, - " ", - docstring, - flags=re.VERBOSE | re.MULTILINE, +"""A script that patches some pdoc functionality before calling it.""" +import os +import pathlib +import sys + +import sphobjinv +from pdoc import __main__ as pdoc_main +from pdoc import doc as pdoc_doc +from pdoc.render import env as pdoc_env + +sys.path.append(os.getcwd()) + +import hikari + +# '-o' is the flag to provide the output dir. If it wasn't provided, we don't output the inventory +generate_inventory = "-o" in sys.argv + +if generate_inventory: + project_inventory = sphobjinv.Inventory() + project_inventory.project = "hikari" + project_inventory.version = hikari.__version__ + + type_to_role = { + "module": "module", + "class": "class", + "function": "func", + "variable": "var", + } + + def _add_to_inventory(dobj: pdoc_doc.Doc): + if dobj.name.startswith("_"): + # These won't be documented anyways, so we can ignore them + return "" + + uri = dobj.modulename.replace(".", "/") + ".html" + + if dobj.qualname: + uri += "#" + dobj.qualname + + project_inventory.objects.append( + sphobjinv.DataObjStr( + name=dobj.fullname, + domain="py", + role=type_to_role[dobj.type], + uri=uri, + priority="1", + dispname="-", + ) ) - def recursive_add_to_index(dobj): - if dobj.module.name != "hikari": # Do not index root - url = to_url_id(dobj) - # r: ref - # u: url - # d: docstring - # f: function - info = {"r": dobj.refname, "u": url} - if index_docstrings: - info["d"] = trim_docstring(dobj.docstring) - if isinstance(dobj, pdoc.Function): - info["f"] = 1 - - index.append(info) - - for member_dobj in getattr(dobj, "doc", {}).values(): - if dobj.module.name == "hikari" and not isinstance(member_dobj, pdoc.Module): - continue - - recursive_add_to_index(member_dobj) - - def to_url_id(dobj): - # pdocs' .url() doesn't take in account that some attributes are inherited, - # which generates an invalid url. Because of this, we need to take matter - # into our own hands. - url = dobj.refname.replace(".", "/") - if not isinstance(dobj, pdoc.Module): - depth = 1 - obj = getattr(dobj, "cls", None) - while obj: - depth += 1 - obj = getattr(obj, "cls", None) - - url = "/".join(url.split("/")[:-depth]) - - if top_module.is_package: # Reference from subfolder if its a package - _, url = url.split("/", maxsplit=1) - if url not in url_cache: - url_cache[url] = len(url_cache) - return url_cache[url] - - index = [] - url_cache = {} - recursive_add_to_index(top_module) - urls = sorted(url_cache.keys(), key=url_cache.__getitem__) - - # If top module is a package, output the index in its subfolder, else, in the output dir - main_path = path.join(cli.args.output_dir, *top_module.name.split(".") if top_module.is_package else "") - with cli._open_write_file(path.join(main_path, "index.json")) as f: - json.dump({"urls": urls, "index": index}, f) - - # Generate search.html - with cli._open_write_file(path.join(main_path, "search.html")) as f: - rendered_template = pdoc._render_template("/search.mako", module=top_module, **template_config) - f.write(rendered_template) - - -if __name__ == "__main__": - cli._generate_lunr_search = _patched_generate_lunr_search - - cli.main() + return "" + + pdoc_env.globals["add_to_inventory"] = _add_to_inventory + +else: + # Just dummy functions + def _empty(*args, **kwargs): + return "" + + pdoc_env.globals["add_to_inventory"] = _empty + +# Run pdoc +pdoc_env.globals["__hikari_version__"] = hikari.__version__ +pdoc_env.globals["__git_sha1__"] = hikari.__git_sha1__ +pdoc_main.cli() + +if generate_inventory: + # Output the inventory + text = project_inventory.data_file(contract=True) + ztext = sphobjinv.compress(text) + path = str(pathlib.Path.cwd() / "public" / "docs" / "objects.inv") + sphobjinv.writebytes(path, ztext) + print(f"Inventory written to {path!r}") diff --git a/docs/search.mako b/docs/search.mako deleted file mode 100644 index 9e8b245133..0000000000 --- a/docs/search.mako +++ /dev/null @@ -1,233 +0,0 @@ -## Copyright (c) 2020 Nekokatt -## Copyright (c) 2021 davfsa -## -## Permission is hereby granted, free of charge, to any person obtaining a copy -## of this software and associated documentation files (the "Software"), to deal -## in the Software without restriction, including without limitation the rights -## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -## copies of the Software, and to permit persons to whom the Software is -## furnished to do so, subject to the following conditions: -## -## The above copyright notice and this permission notice shall be included in all -## copies or substantial portions of the Software. -## -## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -## SOFTWARE. -########################### CONFIGURATION ########################## -<%include file="config.mako"/> -############################ COMPONENTS ############################ - - - - - - - Search documentation - ${module.name.capitalize()} - - - - - - - - - - - ## Bootstrap 4 stylesheet - - ## Custom stylesheets - - - - - <%include file="head.mako"/> - - - -
-
-

- - -

-

-
-
- -
-
- - - - diff --git a/docs/syntax-highlighting.css b/docs/syntax-highlighting.css new file mode 100644 index 0000000000..34d2bdecec --- /dev/null +++ b/docs/syntax-highlighting.css @@ -0,0 +1,559 @@ +/* Comment */ +.pdoc .c { + color: #6a7aaa; +} + +/* Error */ +.pdoc .err { + color: #ff5555; + background-color: #1e0010; +} + +/* Keyword */ +.pdoc .k { + color: #ff79c6; +} + +/* Literal */ +.pdoc .l { + color: #ae81ff; +} + +/* Name */ +.pdoc .n { + color: #f8f8f2; +} + +/* Operator */ +.pdoc .o { + color: #ff79c6; +} + +/* Punctuation */ +.pdoc .p { + color: #f8f8f2; +} + +/* Comment.Hashbang */ +.pdoc .ch { + color: #6a7aaa; +} + +/* Comment.Multiline */ +.pdoc .cm { + color: #6a7aaa; +} + +/* Comment.Preproc */ +.pdoc .cp { + color: #6a7aaa; +} + +/* Comment.PreprocFile */ +.pdoc .cpf { + color: #6a7aaa; +} + +/* Comment.Single */ +.pdoc .c1 { + color: #6a7aaa; +} + +/* Comment.Special */ +.pdoc .cs { + color: #6a7aaa; +} + +/* Generic.Deleted */ +.pdoc .gd { + color: #6a7aaa; +} + +/* Generic.Emph */ +.pdoc .ge { + font-style: italic; +} + +/* Generic.Inserted */ +.pdoc .gi { + color: #a6e22e; +} + +/* Generic.Output */ +.pdoc .go { + color: #ff79c6; +} + +/* Generic.Prompt */ +.pdoc .gp { + color: #f92672; + font-weight: bold; +} + +/* Generic.Strong */ +.pdoc .gs { + font-weight: bold; +} + +/* Generic.Subheading */ +.pdoc .gu { + color: #75715e; +} + +/* Keyword.Constant */ +.pdoc .kc { + color: #ff79c6; +} + +/* Keyword.Declaration */ +.pdoc .kd { + color: #bd93f9; +} + +/* Keyword.Namespace */ +.pdoc .kn { + color: #ff79c6; +} + +/* Keyword.Pseudo */ +.pdoc .kp { + color: #bd93f9; +} + +/* Keyword.Reserved */ + +.pdoc .kr { + color: #ff79c6; +} + +/* Keyword.Type */ + +.pdoc .kt { + color: #ff79c6; +} + +/* Literal.Date */ + +.pdoc .ld { + color: #e6db74; +} + +/* Literal.Number */ + +.pdoc .m { + color: #ae81ff; +} + +/* Literal.String */ + +.pdoc .s { + color: #e6db74; +} + +/* Name.Attribute */ + +.pdoc .na { + color: #a6e22e; +} + +/* Name.Builtin */ + +.pdoc .nb { + color: #8be9fd; +} + +/* Name.Class */ + +.pdoc .nc { + color: #e6c07b; +} + +/* Name.Constant */ + +.pdoc .no { + color: #ff79c6; +} + +/* Name.Entity */ + +.pdoc .ni { + color: #ff79c6; +} + +/* Name.Exception */ + +.pdoc .ne { + color: #8be9fd; +} + +/* Name.Function */ + +.pdoc .nf { + color: #61aeee; +} + +/* Name.Label */ + +.pdoc .nl { + color: #f8f8f2; +} + +/* Name.Namespace */ + +.pdoc .nn { + color: #f8f8f2; +} + +/* Name.Other */ + +.pdoc .nx { + color: #a6e22e; +} + +/* Name.Property */ + +.pdoc .py { + color: #f8f8f2; +} + +/* Name.Tag */ + +.pdoc .nt { + color: #f92672; +} + +/* Name.Variable */ + +.pdoc .nv { + color: #f8f8f2; +} + +/* Operator.Word */ + +.pdoc .ow { + color: #ff79c6; +} + +/* Text.Whitespace */ + +.pdoc .w { + color: #f8f8f2; +} + +/* Literal.Number.Bin */ + +.pdoc .mb { + color: #ae81ff; +} + +/* Literal.Number.Float */ + +.pdoc .mf { + color: #ae81ff; +} + +/* Literal.Number.Hex */ +.pdoc .mh { + color: #ae81ff; +} + +/* Literal.Number.Integer */ +.pdoc .mi { + color: #ae81ff; +} + +/* Literal.Number.Oct */ +.pdoc .mo { + color: #ae81ff; +} + +/* Literal.String.Affix */ +.pdoc .sa { + color: #ff79c6; +} + +/* Literal.String.Backtick */ +.pdoc .sb { + color: #e6db74; +} + +/* Literal.String.Char */ +.pdoc .sc { + color: #e6db74; +} + +/* Literal.String.Delimiter */ +.pdoc .dl { + color: #e6db74; +} + +/* Literal.String.Doc */ +.pdoc .sd { + color: #6272a4; +} + +/* Literal.String.Double */ +.pdoc .s2 { + color: #e6db74; +} + +/* Literal.String.Escape */ +.pdoc .se { + color: #ae81ff; +} + +/* Literal.String.Heredoc */ +.pdoc .sh { + color: #e6db74; +} + +/* Literal.String.Interpol. AKA f-strings */ +.pdoc .si { + color: #bd93f9; +} + +/* Literal.String.Other */ +.pdoc .sx { + color: #e6db74; +} + +/* Literal.String.Regex */ +.pdoc .sr { + color: #e6db74; +} + +/* Literal.String.Single */ +.pdoc .s1 { + color: #e6db74; +} + +/* Literal.String.Symbol */ + +.pdoc .ss { + color: #e6db74; +} + +/* Name.Builtin.Pseudo */ +.pdoc .bp { + color: #bd93f9; +} + +/* Name.Function.Magic */ +.pdoc .fm { + color: #bd93f9; +} + +/* Name.Variable.Class */ +.pdoc .vc { + color: #bd93f9; +} + +/* Name.Variable.Global */ +.pdoc .vg { + color: #f8f8f2; +} + +/* Name.Variable.Instance */ +.pdoc .vi { + color: #ffffff; +} + +/* Name.Variable.Magic */ +.pdoc .vm { + color: #bd93f9; +} + +/* Literal.Number.Integer.Long */ +.pdoc .il { + color: #ae81ff; +} + +/* Decorator module i.e., @typing.? */ +.pdoc .nd { + color: #61aeee; +} + +/* This is for the actual doc style */ +.pdoc .classattr { + color: #fff; +} + +/* Docstrings */ +.pdoc p { + color: #fff; + font-weight: 300; +} + +/* Module name */ + +.pdoc .modulename { + color: #de4f91; + font-weight: 0; + margin: 0.3em 0; + padding: 0.2em 0; +} + +.pdoc .modulename a:hover { + filter: brightness(80%); +} + +/* header */ +.pdoc h5 { + color: white; + font-style: italic; +} + +/* nav function */ +nav.pdoc a.function { + color: #61aeee; +} + +/* nav vars. For some reason pdoc treats properties as class vars */ +nav.pdoc a.variable { + color: white; +} + +/* Nav class name */ +nav.pdoc a.class { + color: #8be9fd; +} + +nav.pdoc a.class:hover { + color: #fff; +} + +/* nav modules */ +nav.pdoc li a { + color: #bd93f9; +} + +/* Assigned values */ +.pdoc span.default_value { + color: #e6db74; +} + +.pdoc .attr { + color: white; +} + +/* +Something +--------- +*/ + +.pdoc h6 { + padding-top: 1rem; + font-size: 2rem; + color: white; +} + +/* +Notes +----- +*/ + +.pdoc h6#notes { + color: orange; +} + +/* +Raises +------ +*/ + +.pdoc h6#raises { + color: #ff6666; +} + +/* False, True literals */ +.pdoc .kc { + color: #bd93f9; +} + +.pdoc li { + color: white; +} + +/* Decorator color */ +.pdoc div.decorator { + color: #61aeee; +} + +/* Function docs params name. i.e. + +Parameters / Returns / Raises +---------- +name : type + A parameter +*/ +.pdoc li strong { + color: #DB61D9; +} + +b, strong { + font-weight: bold; +} + + +/* colors for .. warning:: and .. note:: */ +.pdoc em { + color: orange; +} + + +/* Class name */ +.pdoc span.name { + color: #61aeee; +} + +/* Inherited class name */ +.pdoc span.base { + color: #8be9fd; +} + +/* Functions */ +.pdoc span.def { + font-weight: normal; +} + +/* Before inherited members colors, i.e., "Exception" */ +.pdoc .inherited dt, +.pdoc .inherited dt::before { + color: #61aeee; +} + +/* Whatever comes after the type inherited member. */ +.pdoc .inherited dd, +.pdoc .inherited dd { + color: #fff; +} + +/* Commas that separates parameters "," color */ +.pdoc .inherited dd:not(:last-child)::after { + color: #fff; +} + +/* Top left nav button */ +nav.pdoc .module-list-button { + display: inline-flex; + align-items: center; + margin-bottom: 1rem; + color: white; + border-color: white; +} + +nav.pdoc .module-list-button:hover { + border-color: white; + color: white; +} + +/* Contents, Submodules, API Documentations */ +/* This also can be separated */ +.pdoc h1, +.pdoc h2, +.pdoc h3 { + font-weight: 300; + margin: 0.3em 0; + padding: 0.2em 0; + color: white; +} diff --git a/examples/simple_dashboard/simple_dashboard.py b/examples/simple_dashboard/simple_dashboard.py index b1a144fd69..301d14a0a7 100644 --- a/examples/simple_dashboard/simple_dashboard.py +++ b/examples/simple_dashboard/simple_dashboard.py @@ -104,17 +104,10 @@ async def message(event: hikari.GuildMessageCreateEvent) -> None: return # Command Framework 101 :D - if event.content.startswith(PREFIX): - if is_command("ping", event.content): - await event.message.respond("Ping?") - elif is_command("value", event.content): - await event.message.respond(f"Current value: {bot.data.value}") - - -@bot.listen() -async def on_ready(event: hikari.ShardReadyEvent) -> None: - """Log when the bot is ready.""" - logging.info("Bot is ready! %s", event) + if is_command("ping", event.content): + await event.message.respond("Ping?") + elif is_command("value", event.content): + await event.message.respond(f"Current value: {bot.data.value}") bot.run() diff --git a/hikari/__init__.py b/hikari/__init__.py index 052cfa4e3f..4342cc4274 100644 --- a/hikari/__init__.py +++ b/hikari/__init__.py @@ -22,14 +22,13 @@ # SOFTWARE. """A sane Python framework for writing modern Discord bots. -To get started, you will want to initialize an instance of `GatewayBot` -for writing a bot, or `RESTApp` if you only need to use the REST API. +To get started, you will want to initialize an instance of `hikari.impl.bot.GatewayBot` +for writing a gateway based bot, `hikari.impl.rest_bot.RESTBot` for a REST based bot, +or `hikari.impl.rest.RESTApp` if you only need to use the REST API. """ from __future__ import annotations -import os as _os - from hikari import api from hikari import impl from hikari._about import __author__ @@ -38,6 +37,7 @@ from hikari._about import __discord_invite__ from hikari._about import __docs__ from hikari._about import __email__ +from hikari._about import __git_sha1__ from hikari._about import __issue_tracker__ from hikari._about import __license__ from hikari._about import __url__ @@ -104,9 +104,3 @@ from hikari.users import * from hikari.voices import * from hikari.webhooks import * - -# Only expose this during documentation, as we need it to make anything visible. -if _os.getenv("PDOC3_GENERATING") == "1": # pragma: no cover - __all__ = [name for name in dir() if not name.startswith("_")] - -del _os diff --git a/hikari/__init__.pyi b/hikari/__init__.pyi index 6d5b36df1f..43ab99168a 100644 --- a/hikari/__init__.pyi +++ b/hikari/__init__.pyi @@ -1,8 +1,6 @@ # DO NOT MANUALLY EDIT THIS FILE! # This file was automatically generated by `nox -s generate-stubs` -from typing import Any - from hikari import api as api from hikari import impl as impl from hikari._about import __author__ as __author__ @@ -11,6 +9,7 @@ from hikari._about import __copyright__ as __copyright__ from hikari._about import __discord_invite__ as __discord_invite__ from hikari._about import __docs__ as __docs__ from hikari._about import __email__ as __email__ +from hikari._about import __git_sha1__ as __git_sha1__ from hikari._about import __issue_tracker__ as __issue_tracker__ from hikari._about import __license__ as __license__ from hikari._about import __url__ as __url__ @@ -77,5 +76,3 @@ from hikari.undefined import UndefinedType as UndefinedType from hikari.users import * from hikari.voices import * from hikari.webhooks import * - -__all__: Any diff --git a/hikari/_about.py b/hikari/_about.py index 49044ebb3f..4feffd6051 100644 --- a/hikari/_about.py +++ b/hikari/_about.py @@ -34,7 +34,7 @@ __copyright__: typing.Final[str] = "© 2021 davfsa" __coverage__: typing.Final[str] = "https://codeclimate.com/github/hikari-py/hikari" __discord_invite__: typing.Final[str] = "https://discord.gg/Jx4cNGG" -__docs__: typing.Final[str] = "https://hikari-py.dev/hikari" +__docs__: typing.Final[str] = "https://www.hikari-py.dev/hikari" __email__: typing.Final[str] = "davfsa@gmail.com" __issue_tracker__: typing.Final[str] = "https://github.com/hikari-py/hikari/issues" __license__: typing.Final[str] = "MIT" diff --git a/hikari/api/cache.py b/hikari/api/cache.py index 19e6c3337c..b0e6b71fe4 100644 --- a/hikari/api/cache.py +++ b/hikari/api/cache.py @@ -65,7 +65,7 @@ def get_item_at(self, index: slice, /) -> typing.Sequence[_ValueT]: @abc.abstractmethod def get_item_at(self, index: typing.Union[slice, int], /) -> typing.Union[_ValueT, typing.Sequence[_ValueT]]: - ... + """Get an item at a specific position or slice.""" @abc.abstractmethod def iterator(self) -> iterators.LazyIterator[_ValueT]: @@ -103,7 +103,7 @@ def get_dm_channel_id( ------- typing.Optional[hikari.snowflakes.Snowflake] ID of the DM channel which was found cached for the supplied user or - `builtins.None`. + `None`. """ @abc.abstractmethod @@ -130,7 +130,7 @@ def get_emoji( Returns ------- typing.Optional[hikari.emojis.KnownCustomEmoji] - The object of the emoji that was found in the cache or `builtins.None`. + The object of the emoji that was found in the cache or `None`. """ @abc.abstractmethod @@ -168,7 +168,7 @@ def get_guild( ) -> typing.Optional[guilds.GatewayGuild]: """Get a guild from the cache. - !!! warning + .. warning:: This will return a guild regardless of whether it is available or not. To only query available guilds, use `get_available_guild` instead. Likewise, to only query unavailable guilds, use @@ -182,7 +182,7 @@ def get_guild( Returns ------- typing.Optional[hikari.guilds.GatewayGuild] - The object of the guild if found, else `builtins.None`. + The object of the guild if found, else `None`. """ @abc.abstractmethod @@ -199,7 +199,7 @@ def get_available_guild( Returns ------- typing.Optional[hikari.guilds.GatewayGuild] - The object of the guild if found, else `builtins.None`. + The object of the guild if found, else `None`. """ @abc.abstractmethod @@ -208,7 +208,7 @@ def get_unavailable_guild( ) -> typing.Optional[guilds.GatewayGuild]: """Get the object of a unavailable guild from the cache. - !!! note + .. note:: Unlike `Cache.get_available_guild`, the objects returned by this method will likely be out of date and inaccurate as they are considered unavailable, meaning that we are not receiving gateway @@ -222,7 +222,7 @@ def get_unavailable_guild( Returns ------- typing.Optional[hikari.guilds.GatewayGuild] - The object of the guild if found, else `builtins.None`. + The object of the guild if found, else `None`. """ @abc.abstractmethod @@ -249,7 +249,7 @@ def get_available_guilds_view(self) -> CacheView[snowflakes.Snowflake, guilds.Ga def get_unavailable_guilds_view(self) -> CacheView[snowflakes.Snowflake, guilds.GatewayGuild]: """Get a view of the unavailable guild objects in the cache. - !!! note + .. note:: Unlike `Cache.get_available_guilds_view`, the objects returned by this method will likely be out of date and inaccurate as they are considered unavailable, meaning that we are not receiving gateway @@ -276,7 +276,7 @@ def get_guild_channel( ------- typing.Optional[hikari.channels.GuildChannel] The object of the guild channel that was found in the cache or - `builtins.None`. + `None`. """ @abc.abstractmethod @@ -314,13 +314,13 @@ def get_invite(self, code: typing.Union[invites.InviteCode, str], /) -> typing.O Parameters ---------- - code : typing.Union[hikari.invites.InviteCode, builtins.str] + code : typing.Union[hikari.invites.InviteCode, str] The object or string code of the invite to get from the cache. Returns ------- typing.Optional[hikari.invites.InviteWithMetadata] - The object of the invite that was found in the cache or `builtins.None`. + The object of the invite that was found in the cache or `None`. """ @abc.abstractmethod @@ -329,7 +329,7 @@ def get_invites_view(self) -> CacheView[str, invites.InviteWithMetadata]: Returns ------- - CacheView[builtins.str, hikari.invites.InviteWithMetadata] + CacheView[str, hikari.invites.InviteWithMetadata] A view of string codes to objects of the invites that were found in the cache. """ @@ -347,7 +347,7 @@ def get_invites_view_for_guild( Returns ------- - CacheView[builtins.str, hikari.invites.InviteWithMetadata] + CacheView[str, hikari.invites.InviteWithMetadata] A view of string code to objects of the invites that were found in the cache for the specified guild. """ @@ -382,7 +382,7 @@ def get_me(self) -> typing.Optional[users.OwnUser]: Returns ------- typing.Optional[hikari.users.OwnUser] - The own user object that was found in the cache, else `builtins.None`. + The own user object that was found in the cache, else `None`. """ @abc.abstractmethod @@ -404,7 +404,7 @@ def get_member( Returns ------- typing.Optional[hikari.guilds.Member] - The object of the member found in the cache, else `builtins.None`. + The object of the member found in the cache, else `None`. """ @abc.abstractmethod @@ -449,7 +449,7 @@ def get_message( Returns ------- typing.Optional[hikari.messages.Message] - The object of the message found in the cache or `builtins.None`. + The object of the message found in the cache or `None`. """ @abc.abstractmethod @@ -482,7 +482,7 @@ def get_presence( ------- typing.Optional[hikari.presences.MemberPresence] The object of the presence that was found in the cache or - `builtins.None`. + `None`. """ @abc.abstractmethod @@ -528,7 +528,7 @@ def get_role(self, role: snowflakes.SnowflakeishOr[guilds.PartialRole], /) -> ty Returns ------- typing.Optional[hikari.guilds.Role] - The object of the role found in the cache or `builtins.None`. + The object of the role found in the cache or `None`. """ @abc.abstractmethod @@ -572,7 +572,7 @@ def get_user(self, user: snowflakes.SnowflakeishOr[users.PartialUser], /) -> typ ------- typing.Optional[hikari.users.User] The object of the user that was found in the cache, else - `builtins.None`. + `None`. """ @abc.abstractmethod @@ -605,7 +605,7 @@ def get_voice_state( ------- typing.Optional[hikari.voices.VoiceState] The object of the voice state that was found in the cache, or - `builtins.None`. + `None`. """ @abc.abstractmethod @@ -702,7 +702,7 @@ def delete_dm_channel_id( ------- typing.Optional[hikari.snowflakes.Snowflake] The DM channel ID which was removed from the cache if found, else - `builtins.None`. + `None`. """ @abc.abstractmethod @@ -726,7 +726,7 @@ def set_dm_channel_id( def clear_emojis(self) -> CacheView[snowflakes.Snowflake, emojis.KnownCustomEmoji]: """Remove all the known custom emoji objects from the cache. - !!! note + .. note:: This will skip emojis that are being kept alive by a reference on a presence entry. @@ -743,15 +743,15 @@ def clear_emojis_for_guild( ) -> CacheView[snowflakes.Snowflake, emojis.KnownCustomEmoji]: """Remove the known custom emoji objects cached for a specific guild. + .. note:: + This will skip emojis that are being kept alive by a reference + on a presence entry. + Parameters ---------- guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] Object or ID of the guild to remove the cached emoji objects for. - !!! note - This will skip emojis that are being kept alive by a reference - on a presence entry. - Returns ------- CacheView[hikari.snowflakes.Snowflake, hikari.emojis.KnownCustomEmoji] @@ -770,7 +770,7 @@ def delete_emoji( emoji : hikari.snowflakes.SnowflakeishOr[hikari.emojis.CustomEmoji] Object or ID of the emoji to remove from the cache. - !!! note + .. note:: This will not delete emojis that are being kept alive by a reference on a presence entry. @@ -778,7 +778,7 @@ def delete_emoji( ------- typing.Optional[hikari.emojis.KnownCustomEmoji] The object of the emoji that was removed from the cache or - `builtins.None`. + `None`. """ @abc.abstractmethod @@ -805,9 +805,9 @@ def update_emoji( Returns ------- typing.Tuple[typing.Optional[hikari.emojis.KnownCustomEmoji], typing.Optional[hikari.emojis.KnownCustomEmoji]] - A tuple of the old cached emoji object if found (else `builtins.None`) + A tuple of the old cached emoji object if found (else `None`) and the new cached emoji object if it could be cached (else - `builtins.None`). + `None`). """ @abc.abstractmethod @@ -836,7 +836,7 @@ def delete_guild( ------- typing.Optional[hikari.guilds.GatewayGuild] The object of the guild that was removed from the cache, will be - `builtins.None` if not found. + `None` if not found. """ @abc.abstractmethod @@ -859,7 +859,7 @@ def set_guild_availability( ---------- guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] Object or ID of the guild to set the availability for. - is_available : builtins.bool + is_available : bool The availability to set for the guild. """ @@ -877,9 +877,9 @@ def update_guild( Returns ------- typing.Tuple[typing.Optional[hikari.guilds.GatewayGuild], typing.Optional[hikari.guilds.GatewayGuild]] - A tuple of the old cached guild object if found (else `builtins.None`) + A tuple of the old cached guild object if found (else `None`) and the object of the guild that was added to the cache if it could - be added (else `builtins.None`). + be added (else `None`). """ # noqa E501 - Line too long @abc.abstractmethod @@ -926,7 +926,7 @@ def delete_guild_channel( ------- typing.Optional[hikari.channels.GuildChannel] The object of the guild channel that was removed from the cache if - found, else `builtins.None`. + found, else `None`. """ @abc.abstractmethod @@ -953,9 +953,9 @@ def update_guild_channel( Returns ------- typing.Tuple[typing.Optional[hikari.channels.GuildChannel], typing.Optional[hikari.channels.GuildChannel]] - A tuple of the old cached guild channel if found (else `builtins.None`) + A tuple of the old cached guild channel if found (else `None`) and the new cached guild channel if it could be cached - (else `builtins.None`). + (else `None`). """ # noqa E501 - Line too long @abc.abstractmethod @@ -964,7 +964,7 @@ def clear_invites(self) -> CacheView[str, invites.InviteWithMetadata]: Returns ------- - CacheView[builtins.str, hikari.invites.InviteWithMetadata] + CacheView[str, hikari.invites.InviteWithMetadata] A view of invite code strings to objects of the invites that were removed from the cache. """ @@ -982,7 +982,7 @@ def clear_invites_for_guild( Returns ------- - CacheView[builtins.str, hikari.invites.InviteWithMetadata] + CacheView[str, hikari.invites.InviteWithMetadata] A view of invite code strings to objects of the invites that were removed from the cache for the specified guild. """ @@ -1005,7 +1005,7 @@ def clear_invites_for_channel( Returns ------- - CacheView[builtins.str, hikari.invites.InviteWithMetadata] + CacheView[str, hikari.invites.InviteWithMetadata] A view of invite code strings to objects of the invites that were removed from the cache for the specified channel. """ @@ -1018,14 +1018,14 @@ def delete_invite( Parameters ---------- - code : typing.Union[hikari.invites.InviteCode, builtins.str] + code : typing.Union[hikari.invites.InviteCode, str] Object or string code of the invite to remove from the cache. Returns ------- typing.Optional[hikari.invites.InviteWithMetadata] The object of the invite that was removed from the cache if found, - else `builtins.None`. + else `None`. """ @abc.abstractmethod @@ -1053,8 +1053,8 @@ def update_invite( ------- typing.Tuple[typing.Optional[hikari.invites.InviteWithMetadata], typing.Optional[hikari.invites.InviteWithMetadata]] A tuple of the old cached invite object if found (else - `builtins.None`) and the new cached invite object if it could be - cached (else `builtins.None`). + `None`) and the new cached invite object if it could be + cached (else `None`). """ # noqa E501 - Line too long @abc.abstractmethod @@ -1065,7 +1065,7 @@ def delete_me(self) -> typing.Optional[users.OwnUser]: ------- typing.Optional[hikari.users.OwnUser] The own user object that was removed from the cache if found, - else `builtins.None`. + else `None`. """ @abc.abstractmethod @@ -1093,8 +1093,8 @@ def update_me( ------- typing.Tuple[typing.Optional[hikari.users.OwnUser], typing.Optional[hikari.users.OwnUser]] A tuple of the old cached own user object if found (else - `builtins.None`) and the new cached own user object if it could be - cached, else `builtins.None`. + `None`) and the new cached own user object if it could be + cached, else `None`. """ @abc.abstractmethod @@ -1114,15 +1114,15 @@ def clear_members_for_guild( ) -> CacheView[snowflakes.Snowflake, guilds.Member]: """Remove the members for a specific guild from the cache. + .. note:: + This will skip members that are being referenced by other entries in + the cache; a matching voice state will keep a member entry alive. + Parameters ---------- guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] Object or ID of the guild to remove cached members for. - !!! note - This will skip members that are being referenced by other entries in - the cache; a matching voice state will keep a member entry alive. - Returns ------- CacheView[hikari.snowflakes.Snowflake, hikari.guilds.Member] @@ -1146,7 +1146,7 @@ def delete_member( user : hikari.snowflakes.SnowflakeishOr[hikari.users.PartialUser] Object or ID of the user to remove a member from the cache for. - !!! note + .. note:: You cannot delete a member entry that's being referenced by other entries in the cache; a matching voice state will keep a member entry alive. @@ -1155,7 +1155,7 @@ def delete_member( ------- typing.Optional[hikari.guilds.Member] The object of the member that was removed from the cache if found, - else `builtins.None`. + else `None`. """ @abc.abstractmethod @@ -1182,9 +1182,9 @@ def update_member( Returns ------- typing.Tuple[typing.Optional[hikari.guilds.Member], typing.Optional[hikari.guilds.Member]] - A tuple of the old cached member object if found (else `builtins.None`) + A tuple of the old cached member object if found (else `None`) and the new cached member object if it could be cached (else - `builtins.None`) + `None`) """ @abc.abstractmethod @@ -1238,7 +1238,7 @@ def delete_presence( ------- typing.Optional[hikari.presences.MemberPresence] The object of the presence that was removed from the cache if found, - else `builtins.None`. + else `None`. """ @abc.abstractmethod @@ -1265,9 +1265,9 @@ def update_presence( Returns ------- typing.Tuple[typing.Optional[hikari.presences.MemberPresence], typing.Optional[hikari.presences.MemberPresence]] - A tuple of the old cached invite object if found (else `builtins.None` + A tuple of the old cached invite object if found (else `None` and the new cached invite object if it could be cached ( else - `builtins.None`). + `None`). """ # noqa E501 - Line too long @abc.abstractmethod @@ -1312,7 +1312,7 @@ def delete_role(self, role: snowflakes.SnowflakeishOr[guilds.PartialRole], /) -> ------- typing.Optional[hikari.guilds.Role] The object of the role that was removed from the cache if found, - else `builtins.None`. + else `None`. """ @abc.abstractmethod @@ -1339,9 +1339,9 @@ def update_role( Returns ------- typing.Tuple[typing.Optional[hikari.guilds.Role], typing.Optional[hikari.guilds.Role]] - A tuple of the old cached role object if found (else `builtins.None` + A tuple of the old cached role object if found (else `None` and the new cached role object if it could be cached (else - `builtins.None`). + `None`). """ @abc.abstractmethod @@ -1416,7 +1416,7 @@ def delete_voice_state( ------- typing.Optional[hikari.voices.VoiceState] The object of the voice state that was removed from the cache if - found, else `builtins.None`. + found, else `None`. """ @abc.abstractmethod @@ -1443,9 +1443,9 @@ def update_voice_state( Returns ------- typing.Tuple[typing.Optional[hikari.voices.VoiceState], typing.Optional[hikari.voices.VoiceState]] - A tuple of the old cached voice state if found (else `builtins.None`) + A tuple of the old cached voice state if found (else `None`) and the new cached voice state object if it could be cached - (else `builtins.None`). + (else `None`). """ @abc.abstractmethod @@ -1473,7 +1473,7 @@ def delete_message( ------- typing.Optional[hikari.messages.Message] The object of the message that was removed from the cache if found, - else `builtins.None`. + else `None`. """ @abc.abstractmethod @@ -1500,7 +1500,7 @@ def update_message( Returns ------- typing.Tuple[typing.Optional[hikari.messages.Message], typing.Optional[hikari.messages.Message]] - A tuple of the old cached message object if found (else `builtins.None`) + A tuple of the old cached message object if found (else `None`) and the new cached message object if it could be cached (else - `builtins.None`). + `None`). """ diff --git a/hikari/api/entity_factory.py b/hikari/api/entity_factory.py index 91020f6373..86c028b80d 100644 --- a/hikari/api/entity_factory.py +++ b/hikari/api/entity_factory.py @@ -69,27 +69,27 @@ class GatewayGuildDefinition: channels: typing.Optional[typing.Mapping[snowflakes.Snowflake, channel_models.GuildChannel]] = attr.field() """Mapping of channel IDs to the channels that belong to the guild. - Will be `builtins.None` when returned by guild update gateway events rather + Will be `None` when returned by guild update gateway events rather than create. """ members: typing.Optional[typing.Mapping[snowflakes.Snowflake, guild_models.Member]] = attr.field() """Mapping of user IDs to the members that belong to the guild. - Will be `builtins.None` when returned by guild update gateway events rather + Will be `None` when returned by guild update gateway events rather than create. - !!! note + .. note:: This may be a partial mapping of members in the guild. """ presences: typing.Optional[typing.Mapping[snowflakes.Snowflake, presence_models.MemberPresence]] = attr.field() """Mapping of user IDs to the presences that are active in the guild. - Will be `builtins.None` when returned by guild update gateway events rather + Will be `None` when returned by guild update gateway events rather than create. - !!! note + .. note:: This may be a partial mapping of presences active in the guild. """ @@ -102,7 +102,7 @@ class GatewayGuildDefinition: voice_states: typing.Optional[typing.Mapping[snowflakes.Snowflake, voice_models.VoiceState]] = attr.field() """Mapping of user IDs to the voice states that are active in the guild. - !!! note + .. note:: This may be a partial mapping of voice states active in the guild. """ @@ -358,9 +358,8 @@ def deserialize_guild_category( The ID of the guild this channel belongs to. If passed then this will be prioritised over `"guild_id"` in the payload. - !!! note - `guild_id` currently only covers the gateway GUILD_CREATE event - where `"guild_id"` is not included in the channel's payload. + This currently only covers the gateway `GUILD_CREATE` event, + where it is not included in the channel's payload. Returns ------- @@ -369,7 +368,7 @@ def deserialize_guild_category( Raises ------ - builtins.KeyError + KeyError If `guild_id` is left as `hikari.undefined.UNDEFINED` when `"guild_id"` is not present in the passed payload. """ @@ -394,9 +393,8 @@ def deserialize_guild_text_channel( The ID of the guild this channel belongs to. If passed then this will be prioritised over `"guild_id"` in the payload. - !!! note - `guild_id` currently only covers the gateway GUILD_CREATE event - where `"guild_id"` is not included in the channel's payload. + This currently only covers the gateway `GUILD_CREATE` event, + where it is not included in the channel's payload. Returns ------- @@ -405,7 +403,7 @@ def deserialize_guild_text_channel( Raises ------ - builtins.KeyError + KeyError If `guild_id` is left as `hikari.undefined.UNDEFINED` when `"guild_id"` is not present in the passed payload. """ @@ -430,9 +428,8 @@ def deserialize_guild_news_channel( The ID of the guild this channel belongs to. If passed then this will be prioritised over `"guild_id"` in the payload. - !!! note - `guild_id` currently only covers the gateway GUILD_CREATE event - where `"guild_id"` is not included in the channel's payload. + This currently only covers the gateway `GUILD_CREATE` event, + where it is not included in the channel's payload. Returns ------- @@ -441,7 +438,7 @@ def deserialize_guild_news_channel( Raises ------ - builtins.KeyError + KeyError If `guild_id` is left as `hikari.undefined.UNDEFINED` when `"guild_id"` is not present in the passed payload. """ @@ -466,9 +463,8 @@ def deserialize_guild_store_channel( The ID of the guild this channel belongs to. If passed then this will be prioritised over `"guild_id"` in the payload. - !!! note - `guild_id` currently only covers the gateway GUILD_CREATE event - where `"guild_id"` is not included in the channel's payload. + This currently only covers the gateway `GUILD_CREATE` event, + where it is not included in the channel's payload. Returns ------- @@ -477,7 +473,7 @@ def deserialize_guild_store_channel( Raises ------ - builtins.KeyError + KeyError If `guild_id` is left as `hikari.undefined.UNDEFINED` when `"guild_id"` is not present in the passed payload. """ @@ -502,9 +498,8 @@ def deserialize_guild_voice_channel( The ID of the guild this channel belongs to. If passed then this will be prioritised over `"guild_id"` in the payload. - !!! note - `guild_id` currently only covers the gateway GUILD_CREATE event - where `"guild_id"` is npt included in the channel's payload. + This currently only covers the gateway `GUILD_CREATE` event, + where it is not included in the channel's payload. Returns ------- @@ -513,7 +508,7 @@ def deserialize_guild_voice_channel( Raises ------ - builtins.KeyError + KeyError If `guild_id` is left as `hikari.undefined.UNDEFINED` when `"guild_id"` is not present in the passed payload. """ @@ -538,9 +533,8 @@ def deserialize_guild_stage_channel( The ID of the guild this channel belongs to. If passed then this will be prioritised over `"guild_id"` in the payload. - !!! note - `guild_id` currently only covers the gateway GUILD_CREATE event - where `"guild_id"` is npt included in the channel's payload. + This currently only covers the gateway `GUILD_CREATE` event, + where it is not included in the channel's payload. Returns ------- @@ -549,7 +543,7 @@ def deserialize_guild_stage_channel( Raises ------ - builtins.KeyError + KeyError If `guild_id` is left as `hikari.undefined.UNDEFINED` when `"guild_id"` is not present in the passed payload. """ @@ -563,6 +557,10 @@ def deserialize_channel( ) -> channel_models.PartialChannel: """Parse a raw payload from Discord into a channel object. + .. note:: + `guild_id` currently only covers the gateway GUILD_CREATE event + where `"guild_id"` is not included in the channel's payload. + Parameters ---------- payload : hikari.internal.data_binding.JSONObject @@ -575,10 +573,6 @@ def deserialize_channel( for DM and group DM channels and will be prioritised over `"guild_id"` in the payload when passed. - !!! note - `guild_id` currently only covers the gateway GUILD_CREATE event - where `"guild_id"` is not included in the channel's payload. - Returns ------- hikari.channels.PartialChannel @@ -586,7 +580,7 @@ def deserialize_channel( Raises ------ - builtins.KeyError + KeyError If `guild_id` is left as `hikari.undefined.UNDEFINED` when `"guild_id"` is not present in the passed payload of a guild channel. @@ -782,6 +776,11 @@ def deserialize_member( ) -> guild_models.Member: """Parse a raw payload from Discord into a member object. + .. note:: + `guild_id` covers cases such as the GUILD_CREATE gateway event and + GET Guild Member where `"guild_id"` is not included in the returned + payload. + Parameters ---------- payload : hikari.internal.data_binding.JSONObject @@ -796,11 +795,6 @@ def deserialize_member( The ID of the guild this member belongs to. If this is specified then this will be prioritised over `"guild_id"` in the payload. - !!! note - `guild_id` covers cases such as the GUILD_CREATE gateway event and - GET Guild Member where `"guild_id"` is not included in the returned - payload. - Returns ------- hikari.guilds.Member @@ -808,7 +802,7 @@ def deserialize_member( Raises ------ - builtins.KeyError + KeyError If `guild_id` is left as `hikari.undefined.UNDEFINED` when `"guild_id"` is not present in the passed payload. """ @@ -879,7 +873,7 @@ def deserialize_integration( Raises ------ - builtins.KeyError + KeyError If `guild_id` is left as `hikari.undefined.UNDEFINED` when `"guild_id"` is not present in the passed payload for the payload of the integration. @@ -985,7 +979,7 @@ def deserialize_command( Raises ------ - builtins.KeyError + KeyError If `guild_id` is left as `hikari.undefined.UNDEFINED` when `"guild_id"` is not present in the passed payload for the payload of the integration. @@ -1059,7 +1053,7 @@ def deserialize_command_interaction( def deserialize_interaction(self, payload: data_binding.JSONObject) -> base_interactions.PartialInteraction: """Parse a raw payload from Discord into a interaction object. - !!! note + .. note:: This isn't required to implement logic for deserializing PING interactions and if you want to unmarshal those `EntityFactory.deserialize_partial_interaction` should be compatible. @@ -1334,6 +1328,12 @@ def deserialize_member_presence( ) -> presence_models.MemberPresence: """Parse a raw payload from Discord into a member presence object. + .. note:: + At the time of writing, the only place where `guild_id` will be + mandatory is when parsing presences sent in a `GUILD_CREATE` event + from Discord, since the `guild_id` attribute in the payload will + have been omitted for redundancy. + Parameters ---------- payload : hikari.internal.data_binding.JSONObject @@ -1345,12 +1345,6 @@ def deserialize_member_presence( The ID of the guild the presence belongs to. If this is specified then it is prioritised over `guild_id` in the payload. - !!! note - At the time of writing, the only place where `guild_id` will be - mandatory is when parsing presences sent in a `GUILD_CREATE` event - from Discord, since the `guild_id` attribute in the payload will - have been omitted for redundancy. - Returns ------- hikari.presences.MemberPresence @@ -1358,7 +1352,7 @@ def deserialize_member_presence( Raises ------ - builtins.KeyError + KeyError If `guild_id` is not an attribute of the `payload` dict, and no guild ID was passed for the `guild_id` parameter. @@ -1432,6 +1426,12 @@ def deserialize_voice_state( ) -> voice_models.VoiceState: """Parse a raw payload from Discord into a voice state object. + .. note:: + At the time of writing, `GUILD_CREATE` events are the only known + place where neither `guild_id` nor `member` will be keys on the + payload. In this case, you will need to provide the former + parameters explicitly. + Parameters ---------- payload : hikari.internal.data_binding.JSONObject @@ -1447,12 +1447,6 @@ def deserialize_voice_state( specified then this will be prioritised over `"member"` in the payload. - !!! note - At the time of writing, `GUILD_CREATE` events are the only known - place where neither `guild_id` nor `member` will be keys on the - payload. In this case, you will need to provide the former - parameters explicitly. - Returns ------- hikari.voices.VoiceState @@ -1460,7 +1454,7 @@ def deserialize_voice_state( Raises ------ - builtins.KeyError + KeyError If `guild_id` is left as `hikari.undefined.UNDEFINED` when `"guild_id"` is not present in the passed payload for the payload of the voice state. diff --git a/hikari/api/event_factory.py b/hikari/api/event_factory.py index 3a58e196b4..d041999ba0 100644 --- a/hikari/api/event_factory.py +++ b/hikari/api/event_factory.py @@ -100,7 +100,7 @@ def deserialize_guild_channel_update_event( payload : hikari.internal.data_binding.JSONObject The dict payload to parse. old_channel : typing.Optional[hikari.channels.GuildChannel] - The guild channel object or `builtins.None`. + The guild channel object or `None`. Returns ------- @@ -201,7 +201,7 @@ def deserialize_invite_delete_event( payload : hikari.internal.data_binding.JSONObject The dict payload to parse. old_invite: typing.Optional[hikari.invites.InviteWithMetadata] - The invite object or `builtins.None`. + The invite object or `None`. Returns ------- @@ -291,7 +291,7 @@ def deserialize_guild_update_event( payload : hikari.internal.data_binding.JSONObject The dict payload to parse. old_guild : typing.Optional[hikari.guilds.GatewayGuild] - The guild object or `builtins.None`. + The guild object or `None`. Returns ------- @@ -316,7 +316,7 @@ def deserialize_guild_leave_event( payload : hikari.internal.data_binding.JSONObject The dict payload to parse. old_guild : typing.Optional[hikari.guilds.GatewayGuild] - The guild object or `builtins.None`. + The guild object or `None`. Returns ------- @@ -398,7 +398,7 @@ def deserialize_guild_emojis_update_event( payload : hikari.internal.data_binding.JSONObject The dict payload to parse. old_emojis : typing.Optional[typing.Sequence[hikari.emojis.KnownCustomEmoji]] - The sequence of emojis or `builtins.None`. + The sequence of emojis or `None`. Returns ------- @@ -480,7 +480,7 @@ def deserialize_presence_update_event( payload : hikari.internal.data_binding.JSONObject The dict payload to parse. old_presence: typing.Optional[hikari.presences.MemberPresence] - The presence object or `builtins.None`. + The presence object or `None`. Returns ------- @@ -553,7 +553,7 @@ def deserialize_guild_member_update_event( payload : hikari.internal.data_binding.JSONObject The dict payload to parse. old_member: typing.Optional[hikari.guilds.Member] - The member object or `builtins.None`. + The member object or `None`. Returns ------- @@ -578,7 +578,7 @@ def deserialize_guild_member_remove_event( payload : hikari.internal.data_binding.JSONObject The dict payload to parse. old_member: typing.Optional[hikari.guilds.Member] - The member object or `builtins.None`. + The member object or `None`. Returns ------- @@ -626,7 +626,7 @@ def deserialize_guild_role_update_event( payload : hikari.internal.data_binding.JSONObject The dict payload to parse. old_role: typing.Optional[hikari.guilds.Role] - The role object or `builtins.None`. + The role object or `None`. Returns ------- @@ -651,7 +651,7 @@ def deserialize_guild_role_delete_event( payload : hikari.internal.data_binding.JSONObject The dict payload to parse. old_role: typing.Optional[hikari.guilds.Role] - The role object or `builtins.None`. + The role object or `None`. Returns ------- @@ -743,7 +743,7 @@ def deserialize_message_update_event( payload : hikari.internal.data_binding.JSONObject The dict payload to parse. old_message: typing.Optional[hikari.messages.PartialMessage] - The message object or `builtins.None`. + The message object or `None`. Returns ------- @@ -897,7 +897,7 @@ def deserialize_shard_payload_event( The shard that emitted this event. payload : hikari.internal.data_binding.JSONObject The dict payload to parse. - name : builtins.str + name : str Name of the event. Returns @@ -1012,7 +1012,7 @@ def deserialize_own_user_update_event( payload : hikari.internal.data_binding.JSONObject The dict payload to parse. old_user: typing.Optional[hikari.users.OwnUser] - The OwnUser object or `builtins.None`. + The OwnUser object or `None`. Returns ------- @@ -1041,7 +1041,7 @@ def deserialize_voice_state_update_event( payload : hikari.internal.data_binding.JSONObject The dict payload to parse. old_state: typing.Optional[hikari.voices.VoiceState] - The VoiceState object or `builtins.None`. + The VoiceState object or `None`. Returns ------- diff --git a/hikari/api/event_manager.py b/hikari/api/event_manager.py index 57e060a9a7..184480f946 100644 --- a/hikari/api/event_manager.py +++ b/hikari/api/event_manager.py @@ -84,7 +84,7 @@ class EventStream(iterators.LazyIterator[EventT], abc.ABC): See Also -------- - LazyIterator: `hikari.iterators.LazyIterator` + `hikari.iterators.LazyIterator` """ __slots__: typing.Sequence[str] = () @@ -95,7 +95,7 @@ def close(self) -> None: If called on an already closed streamer then this will do nothing. - !!! note + .. note:: `with streamer` may be used as a short-cut for opening and closing a streamer. """ @@ -106,7 +106,7 @@ def open(self) -> None: If called on an already started streamer then this will do nothing. - !!! note + .. note:: `with streamer` may be used as a short-cut for opening and closing a stream. """ @@ -122,17 +122,17 @@ def filter( Each condition is treated as a predicate, being called with each item that this iterator would return when it is requested. - All conditions must evaluate to `builtins.True` for the item to be + All conditions must evaluate to `True` for the item to be returned. If this is not met, then the item is discarded and ignored, the next matching item will be returned instead, if there is one. Parameters ---------- - *predicates : typing.Union[typing.Callable[[ValueT], builtins.bool], typing.Tuple[builtins.str, typing.Any]] + *predicates : typing.Union[typing.Callable[[ValueT], bool], typing.Tuple[str, typing.Any]] Predicates to invoke. These are functions that take a value and - return `builtins.True` if it is of interest, or `builtins.False` - otherwise. These may instead include 2-`builtins.tuple` objects - consisting of a `builtins.str` attribute name (nested attributes + return `True` if it is of interest, or `False` + otherwise. These may instead include 2-`tuple` objects + consisting of a `str` attribute name (nested attributes are referred to using the `.` operator), and values to compare for equality. This allows you to specify conditions such as `members.filter(("user.bot", True))`. @@ -187,7 +187,7 @@ def consume_raw_event( Raises ------ - builtins.LookupError + LookupError If there is no consumer for the event. """ @@ -267,11 +267,11 @@ async def on_everyone_mentioned(event): See Also -------- - Listen: `hikari.api.event_manager.EventManager.listen` - Stream: `hikari.api.event_manager.EventManager.stream` - Subscribe: `hikari.api.event_manager.EventManager.subscribe` - Unsubscribe: `hikari.api.event_manager.EventManager.unsubscribe` - Wait_for: `hikari.api.event_manager.EventManager.wait_for` + `hikari.api.event_manager.EventManager.listen` + `hikari.api.event_manager.EventManager.stream` + `hikari.api.event_manager.EventManager.subscribe` + `hikari.api.event_manager.EventManager.unsubscribe` + `hikari.api.event_manager.EventManager.wait_for` """ # Yes, this is not generic. The reason for this is MyPy complains about @@ -309,11 +309,11 @@ async def on_message(event): See Also -------- - Dispatch: `hikari.api.event_manager.EventManager.dispatch` - Listen: `hikari.api.event_manager.EventManager.listen` - Stream: `hikari.api.event_manager.EventManager.stream` - Unsubscribe: `hikari.api.event_manager.EventManager.unsubscribe` - Wait_for: `hikari.api.event_manager.EventManager.wait_for` + `hikari.api.event_manager.EventManager.dispatch` + `hikari.api.event_manager.EventManager.listen` + `hikari.api.event_manager.EventManager.stream` + `hikari.api.event_manager.EventManager.unsubscribe` + `hikari.api.event_manager.EventManager.wait_for` """ # Yes, this is not generic. The reason for this is MyPy complains about @@ -349,11 +349,11 @@ async def on_message(event): See Also -------- - Dispatch: `hikari.api.event_manager.EventManager.dispatch` - Listen: `hikari.api.event_manager.EventManager.listen` - Stream: `hikari.api.event_manager.EventManager.stream` - Subscribe: `hikari.api.event_manager.EventManager.subscribe` - Wait_for: `hikari.api.event_manager.EventManager.wait_for` + `hikari.api.event_manager.EventManager.dispatch` + `hikari.api.event_manager.EventManager.listen` + `hikari.api.event_manager.EventManager.stream` + `hikari.api.event_manager.EventManager.subscribe` + `hikari.api.event_manager.EventManager.wait_for` """ @abc.abstractmethod @@ -371,23 +371,17 @@ def get_listeners( event_type : typing.Type[T] The event type to look for. `T` must be a subclass of `hikari.events.base_events.Event`. - polymorphic : builtins.bool - If `builtins.True`, this will also return the listeners of the - subclasses of the given event type. If `builtins.False`, then + polymorphic : bool + If `True`, this will also return the listeners of the + subclasses of the given event type. If `False`, then only listeners for this class specifically are returned. The - default is `builtins.True`. + default is `True`. Returns ------- - typing.Collection[typing.Callable[[T], typing.Coroutine[typing.Any, typing.Any, builtins.None]] + typing.Collection[typing.Callable[[T], typing.Coroutine[typing.Any, typing.Any, None]] A copy of the collection of listeners for the event. Will return an empty collection if nothing is registered. - - `T` must be a subclass of `hikari.events.base_events.Event`. - - See Also - -------- - Has listener: `hikari.api.event_manager.EventManager.has_listener` """ @abc.abstractmethod @@ -405,7 +399,6 @@ def listen( The event type to subscribe to. The implementation may allow this to be undefined. If this is the case, the event type will be inferred instead from the type hints on the function signature. - `T` must be a subclass of `hikari.events.base_events.Event`. Returns @@ -417,11 +410,11 @@ def listen( See Also -------- - Dispatch: `hikari.api.event_manager.EventManager.dispatch` - Stream: `hikari.api.event_manager.EventManager.stream` - Subscribe: `hikari.api.event_manager.EventManager.subscribe` - Unsubscribe: `hikari.api.event_manager.EventManager.unsubscribe` - Wait_for: `hikari.api.event_manager.EventManager.wait_for` + `hikari.api.event_manager.EventManager.dispatch` + `hikari.api.event_manager.EventManager.stream` + `hikari.api.event_manager.EventManager.subscribe` + `hikari.api.event_manager.EventManager.unsubscribe` + `hikari.api.event_manager.EventManager.wait_for` """ @abc.abstractmethod @@ -434,18 +427,23 @@ def stream( ) -> EventStream[EventT_co]: """Return a stream iterator for the given event and sub-events. + .. warning:: + If you use `await stream.open()` to start the stream then you must + also close it with `await stream.close()` otherwise it may queue + events in memory indefinitely. + Parameters ---------- event_type : typing.Type[hikari.events.base_events.Event] The event type to listen for. This will listen for subclasses of this type additionally. - timeout : typing.Optional[builtins.int, builtins.float] + timeout : typing.Optional[int, float] How long this streamer should wait for the next event before - ending the iteration. If `builtins.None` then this will continue + ending the iteration. If `None` then this will continue until explicitly broken from. - limit : typing.Optional[builtins.int] + limit : typing.Optional[int] The limit for how many events this should queue at one time before - dropping extra incoming events, leave this as `builtins.None` for + dropping extra incoming events, leave this as `None` for the cache size to be unlimited. Returns @@ -455,14 +453,8 @@ def stream( with `with stream:` or `stream.open()` before asynchronously iterating over it. - !!! warning - If you use `stream.open()` to start the stream then you must - also close it with `stream.close()` otherwise it may queue - events in memory indefinitely. - Examples -------- - ```py with bot.stream(events.ReactionAddEvent, timeout=30).filter(("message_id", message.id)) as stream: async for user_id in stream.map("user_id").limit(50): @@ -483,11 +475,11 @@ def stream( See Also -------- - Dispatch: `hikari.api.event_manager.EventManager.dispatch` - Listen: `hikari.api.event_manager.EventManager.listen` - Subscribe: `hikari.api.event_manager.EventManager.subscribe` - Unsubscribe: `hikari.api.event_manager.EventManager.unsubscribe` - Wait_for: `hikari.api.event_manager.EventManager.wait_for` + `hikari.api.event_manager.EventManager.dispatch` + `hikari.api.event_manager.EventManager.listen` + `hikari.api.event_manager.EventManager.subscribe` + `hikari.api.event_manager.EventManager.unsubscribe` + `hikari.api.event_manager.EventManager.wait_for` """ @abc.abstractmethod @@ -500,6 +492,9 @@ async def wait_for( ) -> EventT_co: """Wait for a given event to occur once, then return the event. + .. warning:: + Async predicates are not supported. + Parameters ---------- event_type : typing.Type[hikari.events.base_events.Event] @@ -507,17 +502,14 @@ async def wait_for( this type additionally. predicate A function taking the event as the single parameter. - This should return `builtins.True` if the event is one you want to - return, or `builtins.False` if the event should not be returned. + This should return `True` if the event is one you want to + return, or `False` if the event should not be returned. If left as `None` (the default), then the first matching event type that the bot receives (or any subtype) will be the one returned. - - !!! warning - Async predicates are not supported. - timeout : typing.Union[builtins.float, builtins.int, builtins.None] + timeout : typing.Union[float, int, None] The amount of time to wait before raising an `asyncio.TimeoutError` and giving up instead. This is measured in seconds. If - `builtins.None`, then no timeout will be waited for (no timeout can + `None`, then no timeout will be waited for (no timeout can result in "leaking" of coroutines that never complete if called in an uncontrolled way, so is not recommended). @@ -529,14 +521,14 @@ async def wait_for( Raises ------ asyncio.TimeoutError - If the timeout is not `builtins.None` and is reached before an - event is received that the predicate returns `builtins.True` for. + If the timeout is not `None` and is reached before an + event is received that the predicate returns `True` for. See Also -------- - Dispatch: `hikari.api.event_manager.EventManager.dispatch` - Listen: `hikari.api.event_manager.EventManager.listen` - Stream: `hikari.api.event_manager.EventManager.stream` - Subscribe: `hikari.api.event_manager.EventManager.subscribe` - Unsubscribe: `hikari.api.event_manager.EventManager.unsubscribe` + `hikari.api.event_manager.EventManager.dispatch` + `hikari.api.event_manager.EventManager.listen` + `hikari.api.event_manager.EventManager.stream` + `hikari.api.event_manager.EventManager.subscribe` + `hikari.api.event_manager.EventManager.unsubscribe` """ diff --git a/hikari/api/interaction_server.py b/hikari/api/interaction_server.py index 4e1f0a10a3..ae75f17333 100644 --- a/hikari/api/interaction_server.py +++ b/hikari/api/interaction_server.py @@ -50,7 +50,7 @@ subclass for the provided interaction type which will instruct the server on how to respond. -!!! note +.. note:: For the standard implementations of `hikari.api.special_endpoints.InteractionResponseBuilder` see `hikari.impl.special_endpoints`. @@ -68,28 +68,16 @@ class Response(typing.Protocol): @property def headers(self) -> typing.Optional[typing.Mapping[str, str]]: - """Headers that should be added to the response if applicable. - - Returns - ------- - typing.Optional[typing.Mapping[builtins.str, builtins.str]] - A mapping of string header names to string header values that should - be included in the response if applicable else `builtins.None`. - """ + """Headers that should be added to the response if applicable.""" raise NotImplementedError @property def payload(self) -> typing.Optional[bytes]: """Payload to provide in the response. - !!! note - If this is not `builtins.None` then an appropriate `"Content-Type"` + .. note:: + If this is not `None` then an appropriate `"Content-Type"` header should be declared in `Response.headers` - - Returns - ------- - typing.Optional[builtins.bytes] - The bytes payload to respond with if applicable else `builtins.None`. """ raise NotImplementedError @@ -97,12 +85,7 @@ def payload(self) -> typing.Optional[bytes]: def status_code(self) -> int: """Status code that should be used to respond. - Returns - ------- - builtins.int - The response code to use for the response. This should be a valid - HTTP status code, for more information see - https://developer.mozilla.org/en-US/docs/Web/HTTP/Status. + For more information see . """ raise NotImplementedError @@ -118,11 +101,11 @@ async def on_interaction(self, body: bytes, signature: bytes, timestamp: bytes) Parameters ---------- - body : builtins.bytes + body : bytes The interaction payload. - signature : builtins.bytes + signature : bytes Value of the `"X-Signature-Ed25519"` header used to verify the body. - timestamp : builtins.bytes + timestamp : bytes Value of the `"X-Signature-Timestamp"` header used to verify the body. Returns @@ -161,7 +144,7 @@ def get_listener( ------- typing.Optional[ListenersT[hikari.interactions.base_interactions.PartialInteraction, hikari.api.special_endpoints.InteractionResponseBuilder] The callback registered for the provided interaction type if found, - else `builtins.None`. + else `None`. """ # noqa E501 - Line too long @typing.overload @@ -204,18 +187,18 @@ def set_listener( interaction_type : typing.Type[hikari.interactions.base_interactions.PartialInteraction] The type of interaction this listener should be registered for. listener : typing.Optional[ListenerT[hikari.interactions.base_interactions.PartialInteraction, hikari.api.special_endpoints.InteractionResponseBuilder]] - The asynchronous listener callback to set or `builtins.None` to + The asynchronous listener callback to set or `None` to unset the previous listener. Other Parameters ---------------- - replace : builtins.bool + replace : bool Whether this call should replace the previously set listener or not. - This call will raise a `builtins.ValueError` if set to `builtins.False` + This call will raise a `ValueError` if set to `False` when a listener is already set. Raises ------ - builtins.TypeError - If `replace` is `builtins.False` when a listener is already set. + TypeError + If `replace` is `False` when a listener is already set. """ # noqa E501 - Line too long diff --git a/hikari/api/rest.py b/hikari/api/rest.py index 4b71cf35c3..66a96295d2 100644 --- a/hikari/api/rest.py +++ b/hikari/api/rest.py @@ -30,6 +30,7 @@ from hikari import traits from hikari import undefined +from hikari.internal import deprecation if typing.TYPE_CHECKING: import datetime @@ -68,13 +69,7 @@ class TokenStrategy(abc.ABC): @property @abc.abstractmethod def token_type(self) -> typing.Union[applications.TokenType, str]: - """Type of token this strategy returns. - - Returns - ------- - typing.Union[hikari.applications.TokenType, builtins.str] - The type of token this strategy returns. - """ + """Type of token this strategy returns.""" @abc.abstractmethod async def acquire(self, client: RESTClient) -> str: @@ -82,7 +77,7 @@ async def acquire(self, client: RESTClient) -> str: Returns ------- - builtins.str + str The current authorization token to use for this client and it's prefix. """ @@ -91,14 +86,14 @@ async def acquire(self, client: RESTClient) -> str: def invalidate(self, token: typing.Optional[str]) -> None: """Invalidate the cached token in this handler. - !!! note + .. note:: `token` may be provided in-order to avoid newly generated tokens from being invalidated due to multiple calls being made by separate subroutines which are handling the same token. Parameters ---------- - token : typing.Optional[builtins.str] + token : typing.Optional[str] The token to specifically invalidate. If provided then this will only invalidate the cached token if it matches this, otherwise it'll be invalidated regardless. @@ -125,13 +120,8 @@ def entity_factory(self) -> entity_factory_.EntityFactory: def token_type(self) -> typing.Union[str, applications.TokenType, None]: """Type of token this client is using for most requests. - Returns - ------- - typing.Union[builtins.str, hikari.applications.TokenType, builtins.None] - The type of token this client is using for most requests. - - If this is `builtins.None` then this client will likely only work - for some endpoints such as public and webhook ones. + If this is `None` then this client will likely only work + for some endpoints such as public and webhook ones. """ @abc.abstractmethod @@ -170,7 +160,7 @@ async def fetch_channel( `hikari.channels.TextableChannel` can be used to determine if the channel provides textual functionality to the application. - You can check for these using the `builtins.isinstance` + You can check for these using the `isinstance` builtin function. Raises @@ -229,32 +219,32 @@ async def edit_channel( Other Parameters ---------------- - name : hikari.undefined.UndefinedOr[[builtins.str] + name : hikari.undefined.UndefinedOr[[str] If provided, the new name for the channel. - position : hikari.undefined.UndefinedOr[[builtins.int] + position : hikari.undefined.UndefinedOr[[int] If provided, the new position for the channel. - topic : hikari.undefined.UndefinedOr[builtins.str] + topic : hikari.undefined.UndefinedOr[str] If provided, the new topic for the channel. - nsfw : hikari.undefined.UndefinedOr[builtins.bool] + nsfw : hikari.undefined.UndefinedOr[bool] If provided, whether the channel should be marked as NSFW or not. - bitrate : hikari.undefined.UndefinedOr[builtins.int] + bitrate : hikari.undefined.UndefinedOr[int] If provided, the new bitrate for the channel. - video_quality_mode: hikari.undefined.UndefinedOr[typing.Union[hikari.channels.VideoQualityMode, builtins.int]] + video_quality_mode: hikari.undefined.UndefinedOr[typing.Union[hikari.channels.VideoQualityMode, int]] If provided, the new video quality mode for the channel. - user_limit : hikari.undefined.UndefinedOr[builtins.int] + user_limit : hikari.undefined.UndefinedOr[int] If provided, the new user limit in the channel. rate_limit_per_user : hikari.undefined.UndefinedOr[hikari.internal.time.Intervalish] If provided, the new rate limit per user in the channel. - region : hikari.undefined.UndefinedOr[typing.Union[builtins.str, hikari.voices.VoiceRegion]] + region : hikari.undefined.UndefinedOr[typing.Union[str, hikari.voices.VoiceRegion]] If provided, the voice region to set for this channel. Passing - `builtins.None` here will set it to "auto" mode where the used + `None` here will set it to "auto" mode where the used region will be decided based on the first person who connects to it when it's empty. permission_overwrites : hikari.undefined.UndefinedOr[typing.Sequence[hikari.channels.PermissionOverwrite]] If provided, the new permission overwrites for the channel. parent_category : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildCategory]] If provided, the new guild category for the channel. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -286,7 +276,7 @@ async def edit_channel( nature, and will trigger this exception if they occur. hikari.errors.InternalServerError If an internal error occurs on Discord while handling the request. - """ # noqa: E501 - Line too long + """ @abc.abstractmethod async def follow_channel( @@ -376,7 +366,7 @@ async def delete_channel( hikari.errors.InternalServerError If an internal error occurs on Discord while handling the request. - !!! note + .. note:: For Public servers, the set 'Rules' or 'Guidelines' channels and the 'Public Server Updates' channel cannot be deleted. """ @@ -392,7 +382,7 @@ async def edit_my_voice_state( ) -> None: """Edit the current user's voice state in a stage channel. - !!! note + .. note:: The current user has to have already joined the target stage channel before any calls can be made to this endpoint. @@ -405,21 +395,18 @@ async def edit_my_voice_state( Other Parameters ---------------- - suppress : hikari.undefined.UndefinedOr[builtins.bool] + suppress : hikari.undefined.UndefinedOr[bool] If specified, whether the user should be allowed to become a speaker in the target stage channel with `builtin.True` suppressing them from becoming one. - request_to_speak : typing.Union[hikari.undefined.UndefinedType, builtins.bool, datetime.datetime] + request_to_speak : typing.Union[hikari.undefined.UndefinedType, bool, datetime.datetime] Whether to request to speak. This may be one of the following: * `True` to indicate that the bot wants to speak. * `False` to remove any previously set request to speak. * `datetime.datetime` to specify when they want their request to - speak timestamp to be set to. - - !!! note - If a datetime from the past is passed then Discord will use the - current time instead. + speak timestamp to be set to. If a datetime from the past is + passed then Discord will use the current time instead. Raises ------ @@ -457,6 +444,10 @@ async def edit_voice_state( ) -> None: """Edit an existing voice state in a stage channel. + .. note:: + The target user must already be present in the stage channel before + any calls are made to this endpoint. + Parameters ---------- guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] @@ -468,14 +459,10 @@ async def edit_voice_state( Other Parameters ---------------- - suppress : hikari.undefined.UndefinedOr[builtins.bool] + suppress : hikari.undefined.UndefinedOr[bool] If defined, whether the user should be allowed to become a speaker in the target stage channel. - !!! note - The target user must already be present in the stage channel before - any calls are made to this endpoint. - Raises ------ hikari.errors.BadRequestError @@ -561,13 +548,13 @@ async def edit_permission_overwrites( If provided, the new vale of all allowed permissions. deny : hikari.undefined.UndefinedOr[hikari.permissions.Permissions] If provided, the new vale of all disallowed permissions. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. Raises ------ - builtins.TypeError + TypeError If `target_type` is unset and we were unable to determine the type from `target`. hikari.errors.BadRequestError @@ -701,13 +688,13 @@ async def create_invite( Other Parameters ---------------- - max_age : hikari.undefined.UndefinedOr[typing.Union[datetime.timedelta, builtins.float, builtins.int]] + max_age : hikari.undefined.UndefinedOr[typing.Union[datetime.timedelta, float, int]] If provided, the duration of the invite before expiry. - max_uses : hikari.undefined.UndefinedOr[builtins.int] + max_uses : hikari.undefined.UndefinedOr[int] If provided, the max uses the invite can have. - temporary : hikari.undefined.UndefinedOr[builtins.bool] + temporary : hikari.undefined.UndefinedOr[bool] If provided, whether the invite only grants temporary membership. - unique : hikari.undefined.UndefinedOr[builtins.bool] + unique : hikari.undefined.UndefinedOr[bool] If provided, whether the invite should be unique. target_type : hikari.undefined.UndefinedOr[hikari.invites.TargetType] If provided, the target type of this invite. @@ -715,17 +702,17 @@ async def create_invite( If provided, the target user id for this invite. This may be the object or the ID of an existing user. - !!! note + .. note:: This is required if `target_type` is `STREAM` and the targeted user must be streaming into the channel. target_application : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication]] If provided, the target application id for this invite. This may be the object or the ID of an existing application. - !!! note + .. note:: This is required if `target_type` is `EMBEDDED_APPLICATION` and the targeted application must have the `EMBEDDED` flag. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -766,9 +753,11 @@ def trigger_typing( ) -> special_endpoints.TypingIndicator: """Trigger typing in a text channel. + Notes + ----- The result of this call can be awaited to trigger typing once, or can be used as an async context manager to continually type until the - context manager is left. + context manager is left. Any errors documented below will happen then. Examples -------- @@ -781,7 +770,7 @@ def trigger_typing( await asyncio.sleep(60) ``` - !!! warning + .. warning:: Sending a message to the channel will cause the typing indicator to disappear until it is re-triggered. @@ -818,7 +807,7 @@ def trigger_typing( hikari.errors.InternalServerError If an internal error occurs on Discord while handling the request. - !!! note + .. note:: The exceptions on this endpoint will only be raised once the result is awaited or iterated over. Invoking this function itself will not raise any of the above types. @@ -957,6 +946,14 @@ def fetch_messages( ) -> iterators.LazyIterator[messages_.Message]: """Browse the message history for a given text channel. + Notes + ----- + This call is not a coroutine function, it returns a special type of + lazy iterator that will perform API calls as you iterate across it, + thus any errors documented below will happen then. + + See `hikari.iterators` for the full API for this iterator type. + Parameters ---------- channel : hikari.snowflakes.SnowflakeishOr[hikari.channels.TextableChannel] @@ -986,14 +983,9 @@ def fetch_messages( hikari.iterators.LazyIterator[hikari.messages.Message] An iterator to fetch the messages. - !!! note - This call is not a coroutine function, it returns a special type of - lazy iterator that will perform API calls as you iterate across it. - See `hikari.iterators` for the full API for this iterator type. - Raises ------ - builtins.TypeError + TypeError If you specify more than one of `before`, `after`, `about`. hikari.errors.UnauthorizedError If you are unauthorized to make the request (invalid/missing token). @@ -1014,13 +1006,7 @@ def fetch_messages( nature, and will trigger this exception if they occur. hikari.errors.InternalServerError If an internal error occurs on Discord while handling the request. - - !!! note - The exceptions on this endpoint (other than `builtins.TypeError`) will only - be raised once the result is awaited or iterated over. Invoking - this function itself will not raise anything (other than - `builtins.TypeError`). - """ # noqa: E501 - Line too long + """ @abc.abstractmethod async def fetch_message( @@ -1102,7 +1088,7 @@ async def create_message( If provided, the message contents. If `hikari.undefined.UNDEFINED`, then nothing will be sent in the content. Any other value here will be cast to a - `builtins.str`. + `str`. If this is a `hikari.embeds.Embed` and no `embed` nor `embeds` kwarg is provided, then this will instead update the embed. This allows @@ -1117,6 +1103,32 @@ async def create_message( attachment : hikari.undefined.UndefinedOr[hikari.files.Resourceish], If provided, the message attachment. This can be a resource, or string of a path on your computer or a URL. + + Attachments can be passed as many different things, to aid in + convenience. + + - If a `pathlib.PurePath` or `str` to a valid URL, the + resource at the given URL will be streamed to Discord when + sending the message. Subclasses of + `hikari.files.WebResource` such as + `hikari.files.URL`, + `hikari.messages.Attachment`, + `hikari.emojis.Emoji`, + `EmbedResource`, etc will also be uploaded this way. + This will use bit-inception, so only a small percentage of the + resource will remain in memory at any one time, thus aiding in + scalability. + - If a `hikari.files.Bytes` is passed, or a `str` + that contains a valid data URI is passed, then this is uploaded + with a randomized file name if not provided. + - If a `hikari.files.File`, `pathlib.PurePath` or + `str` that is an absolute or relative path to a file + on your file system is passed, then this resource is uploaded + as an attachment using non-blocking code internally and streamed + using bit-inception where possible. This depends on the + type of `concurrent.futures.Executor` that is being used for + the application (default is a thread pool which supports this + behaviour). attachments : hikari.undefined.UndefinedOr[typing.Sequence[hikari.files.Resourceish]], If provided, the message attachments. These can be resources, or strings consisting of paths on your computer or URLs. @@ -1129,10 +1141,10 @@ async def create_message( If provided, the message embed. embeds : hikari.undefined.UndefinedOr[typing.Sequence[hikari.embeds.Embed]] If provided, the message embeds. - tts : hikari.undefined.UndefinedOr[builtins.bool] + tts : hikari.undefined.UndefinedOr[bool] If provided, whether the message will be read out by a screen reader using Discord's TTS (text-to-speech) system. - nonce : hikari.undefined.UndefinedOr[builtins.str] + nonce : hikari.undefined.UndefinedOr[str] An arbitrary identifier to associate with the message. This can be used to identify it later in received events. If provided, this must be less than 32 bytes. If not provided, then @@ -1140,58 +1152,31 @@ async def create_message( see this value. reply : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage]] If provided, the message to reply to. - mentions_everyone : hikari.undefined.UndefinedOr[builtins.bool] + mentions_everyone : hikari.undefined.UndefinedOr[bool] If provided, whether the message should parse @everyone/@here mentions. - mentions_reply : hikari.undefined.UndefinedOr[builtins.bool] + mentions_reply : hikari.undefined.UndefinedOr[bool] If provided, whether to mention the author of the message that is being replied to. This will not do anything if not being used with `reply`. - user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], builtins.bool]] - If provided, and `builtins.True`, all user mentions will be detected. - If provided, and `builtins.False`, all user mentions will be ignored + user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], bool]] + If provided, and `True`, all user mentions will be detected. + If provided, and `False`, all user mentions will be ignored if appearing in the message body. Alternatively this may be a collection of `hikari.snowflakes.Snowflake`, or `hikari.users.PartialUser` derivatives to enforce mentioning specific users. - role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], builtins.bool]] - If provided, and `builtins.True`, all role mentions will be detected. - If provided, and `builtins.False`, all role mentions will be ignored + role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], bool]] + If provided, and `True`, all role mentions will be detected. + If provided, and `False`, all role mentions will be ignored if appearing in the message body. Alternatively this may be a collection of `hikari.snowflakes.Snowflake`, or `hikari.guilds.PartialRole` derivatives to enforce mentioning specific roles. - !!! note - Attachments can be passed as many different things, to aid in - convenience. - - - If a `pathlib.PurePath` or `builtins.str` to a valid URL, the - resource at the given URL will be streamed to Discord when - sending the message. Subclasses of - `hikari.files.WebResource` such as - `hikari.files.URL`, - `hikari.messages.Attachment`, - `hikari.emojis.Emoji`, - `EmbedResource`, etc will also be uploaded this way. - This will use bit-inception, so only a small percentage of the - resource will remain in memory at any one time, thus aiding in - scalability. - - If a `hikari.files.Bytes` is passed, or a `builtins.str` - that contains a valid data URI is passed, then this is uploaded - with a randomized file name if not provided. - - If a `hikari.files.File`, `pathlib.PurePath` or - `builtins.str` that is an absolute or relative path to a file - on your file system is passed, then this resource is uploaded - as an attachment using non-blocking code internally and streamed - using bit-inception where possible. This depends on the - type of `concurrent.futures.Executor` that is being used for - the application (default is a thread pool which supports this - behaviour). - Returns ------- hikari.messages.Message @@ -1199,12 +1184,12 @@ async def create_message( Raises ------ - builtins.ValueError + ValueError If more than 100 unique objects/entities are passed for `role_mentions` or `user_mentions` or if both `attachment` and `attachments`, `component` and `components` or `embed` and `embeds` are specified. - builtins.TypeError + TypeError If `attachments`, `components` or `embeds` is passed but is not a sequence. hikari.errors.BadRequestError @@ -1312,6 +1297,26 @@ async def edit_message( ) -> messages_.Message: """Edit an existing message in a given channel. + .. warning:: + If the message was not sent by your user, the only parameter + you may provide to this call is the `flags` parameter. Anything + else will result in a `hikari.errors.ForbiddenError` being raised. + + Notes + ----- + Mentioning everyone, roles, or users in message edits currently + will not send a push notification showing a new mention to people + on Discord. It will still highlight in their chat as if they + were mentioned, however. + + Also important to note that if you specify a text `content`, `mentions_everyone`, + `mentions_reply`, `user_mentions`, and `role_mentions` will default + to `False` as the message will be re-parsed for mentions. This will + also occur if only one of the four are specified + + This is a limitation of Discord's design. If in doubt, specify all + four of them each time. + Parameters ---------- channel : hikari.snowflakes.SnowflakeishOr[hikari.channels.TextableChannel] @@ -1323,9 +1328,9 @@ async def edit_message( content : hikari.undefined.UndefinedOr[typing.Any] If provided, the message content to update with. If `hikari.undefined.UNDEFINED`, then the content will not - be changed. If `builtins.None`, then the content will be removed. + be changed. If `None`, then the content will be removed. - Any other value will be cast to a `builtins.str` before sending. + Any other value will be cast to a `str` before sending. If this is a `hikari.embeds.Embed` and neither the `embed` or `embeds` kwargs are provided or if this is a @@ -1339,67 +1344,67 @@ async def edit_message( attachment : hikari.undefined.UndefinedOr[hikari.files.Resourceish] If provided, the attachment to set on the message. If `hikari.undefined.UNDEFINED`, the previous attachment, if - present, is not changed. If this is `builtins.None`, then the + present, is not changed. If this is `None`, then the attachment is removed, if present. Otherwise, the new attachment that was provided will be attached. attachments : hikari.undefined.UndefinedOr[typing.Sequence[hikari.files.Resourceish]] If provided, the attachments to set on the message. If `hikari.undefined.UNDEFINED`, the previous attachments, if - present, are not changed. If this is `builtins.None`, then the + present, are not changed. If this is `None`, then the attachments is removed, if present. Otherwise, the new attachments that were provided will be attached. component : hikari.undefined.UndefinedNoneOr[hikari.api.special_endpoints.ComponentBuilder] If provided, builder object of the component to set for this message. This component will replace any previously set components and passing - `builtins.None` will remove all components. + `None` will remove all components. components : hikari.undefined.UndefinedNoneOr[typing.Sequence[hikari.api.special_endpoints.ComponentBuilder]] If provided, a sequence of the component builder objects set for this message. These components will replace any previously set - components and passing `builtins.None` or an empty sequence will + components and passing `None` or an empty sequence will remove all components. embed : hikari.undefined.UndefinedNoneOr[hikari.embeds.Embed] If provided, the embed to set on the message. If `hikari.undefined.UNDEFINED`, the previous embed(s) are not changed. - If this is `builtins.None` then any present embeds are removed. + If this is `None` then any present embeds are removed. Otherwise, the new embed that was provided will be used as the replacement. embeds : hikari.undefined.UndefinedNoneOr[typing.Sequence[hikari.embeds.Embed]] If provided, the embeds to set on the message. If `hikari.undefined.UNDEFINED`, the previous embed(s) are not changed. - If this is `builtins.None` then any present embeds are removed. + If this is `None` then any present embeds are removed. Otherwise, the new embeds that were provided will be used as the replacement. replace_attachments: bool Whether to replace the attachments with the provided ones. Defaults - to `builtins.False`. + to `False`. Note this will also overwrite the embed attachments. - mentions_everyone : hikari.undefined.UndefinedOr[builtins.bool] + mentions_everyone : hikari.undefined.UndefinedOr[bool] If provided, sanitation for `@everyone` mentions. If `hikari.undefined.UNDEFINED`, then the previous setting is - not changed. If `builtins.True`, then `@everyone`/`@here` mentions + not changed. If `True`, then `@everyone`/`@here` mentions in the message content will show up as mentioning everyone that can view the chat. - mentions_reply : hikari.undefined.UndefinedOr[builtins.bool] + mentions_reply : hikari.undefined.UndefinedOr[bool] If provided, whether to mention the author of the message that is being replied to. This will not do anything if `message` is not a reply message. - user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], builtins.bool]] + user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], bool]] If provided, sanitation for user mentions. If `hikari.undefined.UNDEFINED`, then the previous setting is - not changed. If `builtins.True`, all valid user mentions will behave - as mentions. If `builtins.False`, all valid user mentions will not + not changed. If `True`, all valid user mentions will behave + as mentions. If `False`, all valid user mentions will not behave as mentions. You may alternatively pass a collection of `hikari.snowflakes.Snowflake` user IDs, or `hikari.users.PartialUser`-derived objects. - role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], builtins.bool]] + role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], bool]] If provided, sanitation for role mentions. If `hikari.undefined.UNDEFINED`, then the previous setting is - not changed. If `builtins.True`, all valid role mentions will behave - as mentions. If `builtins.False`, all valid role mentions will not + not changed. If `True`, all valid role mentions will behave + as mentions. If `False`, all valid role mentions will not behave as mentions. You may alternatively pass a collection of @@ -1414,33 +1419,6 @@ async def edit_message( have `MANAGE_MESSAGES` permissions, you can use this call to suppress embeds on another user's message. - !!! note - Mentioning everyone, roles, or users in message edits currently - will not send a push notification showing a new mention to people - on Discord. It will still highlight in their chat as if they - were mentioned, however. - - !!! warning - If you specify a non-embed `content`, `mentions_everyone`, - `mentions_reply`, `user_mentions`, and `role_mentions` will default - to `builtins.False` as the message will be re-parsed for mentions. - - This is a limitation of Discord's design. If in doubt, specify all - four of them each time. - - !!! warning - If you specify one of `mentions_everyone`, `mentions_reply`, - `user_mentions`, or `role_mentions`, then all others will default to - `builtins.False`, even if they were enabled previously. - - This is a limitation of Discord's design. If in doubt, specify all - four of them each time. - - !!! warning - If the message was not sent by your user, the only parameter - you may provide to this call is the `flags` parameter. Anything - else will result in a `hikari.errors.ForbiddenError` being raised. - Returns ------- hikari.messages.Message @@ -1448,10 +1426,10 @@ async def edit_message( Raises ------ - builtins.ValueError + ValueError If both `attachment` and `attachments`, `component` and `components` or `embed` and `embeds` are specified. - builtins.TypeError + TypeError If `attachments`, `components` or `embeds` is passed but is not a sequence. hikari.errors.BadRequestError @@ -1537,21 +1515,7 @@ async def delete_messages( ) -> None: """Bulk-delete messages from the channel. - Parameters - ---------- - channel : hikari.snowflakes.SnowflakeishOr[hikari.channels.TextableChannel] - The channel to bulk delete the messages in. This may be - the object or the ID of an existing channel. - messages : typing.Union[hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage], hikari.snowflakes.SnowflakeishIterable[hikari.messages.PartialMessage]] - Either the object/ID of an existing message to delete or an iterable - of the objects and/or IDs of existing messages to delete. - - Other Parameters - ---------------- - *other_messages : hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage] - The objects and/or IDs of other existing messages to delete. - - !!! note + .. note:: This API endpoint will only be able to delete 100 messages at a time. For anything more than this, multiple requests will be executed one-after-the-other, since the rate limits for this @@ -1560,24 +1524,38 @@ async def delete_messages( If one message is left over from chunking per 100 messages, or only one message is passed to this coroutine function, then the logic is expected to defer to `delete_message`. The implication - of this is that the `delete_message` endpoint is ratelimited + of this is that the `delete_message` endpoint is rate limited by a different bucket with different usage rates. - !!! warning + .. warning:: This endpoint is not atomic. If an error occurs midway through a bulk delete, you will **not** be able to revert any changes made up to this point. - !!! warning + .. warning:: Specifying any messages more than 14 days old will cause the call to fail, potentially with partial completion. + Parameters + ---------- + channel : hikari.snowflakes.SnowflakeishOr[hikari.channels.TextableChannel] + The channel to bulk delete the messages in. This may be + the object or the ID of an existing channel. + messages : typing.Union[hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage], hikari.snowflakes.SnowflakeishIterable[hikari.messages.PartialMessage]] + Either the object/ID of an existing message to delete or an iterable + of the objects and/or IDs of existing messages to delete. + + Other Parameters + ---------------- + *other_messages : hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage] + The objects and/or IDs of other existing messages to delete. + Raises ------ hikari.errors.BulkDeleteError An error containing the messages successfully deleted, and the messages that were not removed. The - `builtins.BaseException.__cause__` of the exception will be the + `BaseException.__cause__` of the exception will be the original error that terminated this process. """ # noqa: E501 - Line too long @@ -1600,7 +1578,7 @@ async def add_reaction( message : hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage] The message to add a reaction to. This may be the object or the ID of an existing message. - emoji : typing.Union[builtins.str, hikari.emojis.Emoji] + emoji : typing.Union[str, hikari.emojis.Emoji] Object or name of the emoji to react with. Other Parameters @@ -1655,7 +1633,7 @@ async def delete_my_reaction( message : hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage] The message to delete a reaction from. This may be the object or the ID of an existing message. - emoji : typing.Union[builtins.str, hikari.emojis.Emoji] + emoji : typing.Union[str, hikari.emojis.Emoji] Object or name of the emoji to remove your reaction for. Other Parameters @@ -1707,7 +1685,7 @@ async def delete_all_reactions_for_emoji( message : hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage] The message to delete a reactions from. This may be the object or the ID of an existing message. - emoji : typing.Union[builtins.str, hikari.emojis.Emoji] + emoji : typing.Union[str, hikari.emojis.Emoji] Object or name of the emoji to remove all the reactions for. Other Parameters @@ -1767,7 +1745,7 @@ async def delete_reaction( object or the ID of an existing message. user: hikari.snowflakes.SnowflakeishOr[hikari.users.PartialUser] Object or ID of the user to remove the reaction of. - emoji : typing.Union[builtins.str, hikari.emojis.Emoji] + emoji : typing.Union[str, hikari.emojis.Emoji] Object or name of the emoji to react with. Other Parameters @@ -1856,6 +1834,14 @@ def fetch_reactions_for_emoji( ) -> iterators.LazyIterator[users.User]: """Fetch reactions for an emoji from a message. + Notes + ----- + This call is not a coroutine function, it returns a special type of + lazy iterator that will perform API calls as you iterate across it, + thus any errors documented below will happen then. + + See `hikari.iterators` for the full API for this iterator type. + Parameters ---------- channel : hikari.snowflakes.SnowflakeishOr[hikari.channels.TextableChannel] @@ -1864,7 +1850,7 @@ def fetch_reactions_for_emoji( message : hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage] The message to delete all reaction from. This may be the object or the ID of an existing message. - emoji : typing.Union[builtins.str, hikari.emojis.Emoji] + emoji : typing.Union[str, hikari.emojis.Emoji] Object or name of the emoji to get the reactions for. Other Parameters @@ -1879,11 +1865,6 @@ def fetch_reactions_for_emoji( hikari.iterators.LazyIterator[hikari.users.User] An iterator to fetch the users. - !!! note - This call is not a coroutine function, it returns a special type of - lazy iterator that will perform API calls as you iterate across it. - See `hikari.iterators` for the full API for this iterator type. - Raises ------ hikari.errors.BadRequestError @@ -1906,11 +1887,6 @@ def fetch_reactions_for_emoji( nature, and will trigger this exception if they occur. hikari.errors.InternalServerError If an internal error occurs on Discord while handling the request. - - !!! note - The exceptions on this endpoint will only be raised once the - result is awaited or iterated over. Invoking this function - itself will not raise anything. """ @abc.abstractmethod @@ -1936,7 +1912,7 @@ async def create_webhook( ---------------- avatar : typing.Optional[hikari.files.Resourceish] If provided, the avatar for the webhook. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -1987,7 +1963,7 @@ async def fetch_webhook( Other Parameters ---------------- - token : hikari.undefined.UndefinedOr[builtins.str] + token : hikari.undefined.UndefinedOr[str] If provided, the webhoook token that will be used to fetch the webhook instead of the token the client was initialized with. @@ -2124,17 +2100,17 @@ async def edit_webhook( Other Parameters ---------------- - token : hikari.undefined.UndefinedOr[builtins.str] + token : hikari.undefined.UndefinedOr[str] If provided, the webhoook token that will be used to edit the webhook instead of the token the client was initialized with. - name : hikari.undefined.UndefinedOr[builtins.str] + name : hikari.undefined.UndefinedOr[str] If provided, the new webhook name. avatar : hikari.undefined.UndefinedNoneOr[hikari.files.Resourceish] - If provided, the new webhook avatar. If `builtins.None`, will + If provided, the new webhook avatar. If `None`, will remove the webhook avatar. channel : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.WebhookChannelT]] If provided, the text channel to move the webhook to. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -2184,7 +2160,7 @@ async def delete_webhook( Other Parameters ---------------- - token : hikari.undefined.UndefinedOr[builtins.str] + token : hikari.undefined.UndefinedOr[str] If provided, the webhoook token that will be used to delete the webhook instead of the token the client was initialized with. @@ -2240,18 +2216,22 @@ async def execute_webhook( ) -> messages_.Message: """Execute a webhook. + .. warning:: + As of writing, `username` and `avatar_url` are ignored for + interaction webhooks. + Parameters ---------- webhook : typing.Union[hikari.snowflakes.Snowflakeish, hikari.webhooks.ExecutableWebhook] The webhook to execute. This may be the object or the ID of an existing webhook. - token: builtins.str + token: str The webhook token. content : hikari.undefined.UndefinedOr[typing.Any] If provided, the message contents. If `hikari.undefined.UNDEFINED`, then nothing will be sent in the content. Any other value here will be cast to a - `builtins.str`. + `str`. If this is a `hikari.embeds.Embed` and no `embed` nor no `embeds` kwarg is provided, then this will instead @@ -2264,15 +2244,41 @@ async def execute_webhook( Other Parameters ---------------- - username : hikari.undefined.UndefinedOr[builtins.str] + username : hikari.undefined.UndefinedOr[str] If provided, the username to override the webhook's username for this request. - avatar_url : typing.Union[hikari.undefined.UndefinedType, builtins.str, hikari.files.URL] + avatar_url : typing.Union[hikari.undefined.UndefinedType, hikari.files.URL, str] If provided, the url of an image to override the webhook's avatar with for this request. attachment : hikari.undefined.UndefinedOr[hikari.files.Resourceish], If provided, the message attachment. This can be a resource, or string of a path on your computer or a URL. + + Attachments can be passed as many different things, to aid in + convenience. + + - If a `pathlib.PurePath` or `str` to a valid URL, the + resource at the given URL will be streamed to Discord when + sending the message. Subclasses of + `hikari.files.WebResource` such as + `hikari.files.URL`, + `hikari.messages.Attachment`, + `hikari.emojis.Emoji`, + `EmbedResource`, etc will also be uploaded this way. + This will use bit-inception, so only a small percentage of the + resource will remain in memory at any one time, thus aiding in + scalability. + - If a `hikari.files.Bytes` is passed, or a `str` + that contains a valid data URI is passed, then this is uploaded + with a randomized file name if not provided. + - If a `hikari.files.File`, `pathlib.PurePath` or + `str` that is an absolute or relative path to a file + on your file system is passed, then this resource is uploaded + as an attachment using non-blocking code internally and streamed + using bit-inception where possible. This depends on the + type of `concurrent.futures.Executor` that is being used for + the application (default is a thread pool which supports this + behaviour). attachments : hikari.undefined.UndefinedOr[typing.Sequence[hikari.files.Resourceish]], If provided, the message attachments. These can be resources, or strings consisting of paths on your computer or URLs. @@ -2285,73 +2291,36 @@ async def execute_webhook( If provided, the message embed. embeds : hikari.undefined.UndefinedOr[typing.Sequence[hikari.embeds.Embed]] If provided, the message embeds. - tts : hikari.undefined.UndefinedOr[builtins.bool] + tts : hikari.undefined.UndefinedOr[bool] If provided, whether the message will be read out by a screen reader using Discord's TTS (text-to-speech) system. - nonce : hikari.undefined.UndefinedOr[builtins.str] - An arbitrary identifier to associate with the message. This - can be used to identify it later in received events. If provided, - this must be less than 32 bytes. If not provided, then - a null value is placed on the message instead. All users can - see this value. - mentions_everyone : hikari.undefined.UndefinedOr[builtins.bool] + mentions_everyone : hikari.undefined.UndefinedOr[bool] If provided, whether the message should parse @everyone/@here mentions. - user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], builtins.bool]] - If provided, and `builtins.True`, all user mentions will be detected. - If provided, and `builtins.False`, all user mentions will be ignored + user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], bool]] + If provided, and `True`, all user mentions will be detected. + If provided, and `False`, all user mentions will be ignored if appearing in the message body. Alternatively this may be a collection of `hikari.snowflakes.Snowflake`, or `hikari.users.PartialUser` derivatives to enforce mentioning specific users. - role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], builtins.bool]] - If provided, and `builtins.True`, all role mentions will be detected. - If provided, and `builtins.False`, all role mentions will be ignored + role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], bool]] + If provided, and `True`, all role mentions will be detected. + If provided, and `False`, all role mentions will be ignored if appearing in the message body. Alternatively this may be a collection of `hikari.snowflakes.Snowflake`, or `hikari.guilds.PartialRole` derivatives to enforce mentioning specific roles. - flags : typing.Union[hikari.undefined.UndefinedType, builtins.int, hikari.messages.MessageFlag] + flags : typing.Union[hikari.undefined.UndefinedType, int, hikari.messages.MessageFlag] The flags to set for this webhook message. - !!! warning + .. warning:: As of writing this can only be set for interaction webhooks and the only settable flag is EPHEMERAL; this field is just ignored for non-interaction webhooks. - !!! warning - As of writing, `username` and `avatar_url` are ignored for - interaction webhooks. - - !!! note - Attachments can be passed as many different things, to aid in - convenience. - - - If a `pathlib.PurePath` or `builtins.str` to a valid URL, the - resource at the given URL will be streamed to Discord when - sending the message. Subclasses of - `hikari.files.WebResource` such as - `hikari.files.URL`, - `hikari.messages.Attachment`, - `hikari.emojis.Emoji`, - `EmbedResource`, etc will also be uploaded this way. - This will use bit-inception, so only a small percentage of the - resource will remain in memory at any one time, thus aiding in - scalability. - - If a `hikari.files.Bytes` is passed, or a `builtins.str` - that contains a valid data URI is passed, then this is uploaded - with a randomized file name if not provided. - - If a `hikari.files.File`, `pathlib.PurePath` or - `builtins.str` that is an absolute or relative path to a file - on your file system is passed, then this resource is uploaded - as an attachment using non-blocking code internally and streamed - using bit-inception where possible. This depends on the - type of `concurrent.futures.Executor` that is being used for - the application (default is a thread pool which supports this - behaviour). - Returns ------- hikari.messages.Message @@ -2359,11 +2328,11 @@ async def execute_webhook( Raises ------ - builtins.ValueError + ValueError If more than 100 unique objects/entities are passed for `role_mentions` or `user_mentions` or if both `attachment` and `attachments` or `embed` and `embeds` are specified. - builtins.TypeError + TypeError If `attachments`, or `embeds` is passed but is not a sequence. hikari.errors.BadRequestError This may be raised in several discrete situations, such as messages @@ -2405,7 +2374,7 @@ async def fetch_webhook_message( webhook : typing.Union[hikari.snowflakes.Snowflakeish, hikari.webhooks.ExecutableWebhook] The webhook to execute. This may be the object or the ID of an existing webhook. - token: builtins.str + token: str The webhook token. message : hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage] The message to fetch. This may be the object or the ID of an @@ -2465,12 +2434,27 @@ async def edit_webhook_message( ) -> messages_.Message: """Edit a message sent by a webhook. + Notes + ----- + Mentioning everyone, roles, or users in message edits currently + will not send a push notification showing a new mention to people + on Discord. It will still highlight in their chat as if they + were mentioned, however. + + Also important to note that if you specify a text `content`, `mentions_everyone`, + `mentions_reply`, `user_mentions`, and `role_mentions` will default + to `False` as the message will be re-parsed for mentions. This will + also occur if only one of the four are specified + + This is a limitation of Discord's design. If in doubt, specify all + four of them each time. + Parameters ---------- webhook : typing.Union[hikari.snowflakes.Snowflakeish, hikari.webhooks.ExecutableWebhook] The webhook to execute. This may be the object or the ID of an existing webhook. - token: builtins.str + token: str The webhook token. message : hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage] The message to delete. This may be the object or the ID of @@ -2478,9 +2462,9 @@ async def edit_webhook_message( content : hikari.undefined.UndefinedOr[typing.Any] If provided, the message content to update with. If `hikari.undefined.UNDEFINED`, then the content will not - be changed. If `builtins.None`, then the content will be removed. + be changed. If `None`, then the content will be removed. - Any other value will be cast to a `builtins.str` before sending. + Any other value will be cast to a `str` before sending. If this is a `hikari.embeds.Embed` and neither the `embed` or `embeds` kwargs are provided or if this is a @@ -2494,86 +2478,64 @@ async def edit_webhook_message( attachment : hikari.undefined.UndefinedOr[hikari.files.Resourceish] If provided, the attachment to set on the message. If `hikari.undefined.UNDEFINED`, the previous attachment, if - present, is not changed. If this is `builtins.None`, then the + present, is not changed. If this is `None`, then the attachment is removed, if present. Otherwise, the new attachment that was provided will be attached. attachments : hikari.undefined.UndefinedOr[typing.Sequence[hikari.files.Resourceish]] If provided, the attachments to set on the message. If `hikari.undefined.UNDEFINED`, the previous attachments, if - present, are not changed. If this is `builtins.None`, then the + present, are not changed. If this is `None`, then the attachments is removed, if present. Otherwise, the new attachments that were provided will be attached. component : hikari.undefined.UndefinedNoneOr[hikari.api.special_endpoints.ComponentBuilder] If provided, builder object of the component to set for this message. This component will replace any previously set components and passing - `builtins.None` will remove all components. + `None` will remove all components. components : hikari.undefined.UndefinedNoneOr[typing.Sequence[hikari.api.special_endpoints.ComponentBuilder]] If provided, a sequence of the component builder objects set for this message. These components will replace any previously set - components and passing `builtins.None` or an empty sequence will + components and passing `None` or an empty sequence will remove all components. embed : hikari.undefined.UndefinedNoneOr[hikari.embeds.Embed] If provided, the embed to set on the message. If `hikari.undefined.UNDEFINED`, the previous embed(s) are not changed. - If this is `builtins.None` then any present embeds are removed. + If this is `None` then any present embeds are removed. Otherwise, the new embed that was provided will be used as the replacement. embeds : hikari.undefined.UndefinedNoneOr[typing.Sequence[hikari.embeds.Embed]] If provided, the embeds to set on the message. If `hikari.undefined.UNDEFINED`, the previous embed(s) are not changed. - If this is `builtins.None` then any present embeds are removed. + If this is `None` then any present embeds are removed. Otherwise, the new embeds that were provided will be used as the replacement. replace_attachments: bool Whether to replace the attachments with the provided ones. Defaults - to `builtins.False`. + to `False`. Note this will also overwrite the embed attachments. - mentions_everyone : hikari.undefined.UndefinedOr[builtins.bool] + mentions_everyone : hikari.undefined.UndefinedOr[bool] If provided, sanitation for `@everyone` mentions. If `hikari.undefined.UNDEFINED`, then the previous setting is - not changed. If `builtins.True`, then `@everyone`/`@here` mentions + not changed. If `True`, then `@everyone`/`@here` mentions in the message content will show up as mentioning everyone that can view the chat. - user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], builtins.bool]] - If provided, and `builtins.True`, all user mentions will be detected. - If provided, and `builtins.False`, all user mentions will be ignored + user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], bool]] + If provided, and `True`, all user mentions will be detected. + If provided, and `False`, all user mentions will be ignored if appearing in the message body. Alternatively this may be a collection of `hikari.snowflakes.Snowflake`, or `hikari.users.PartialUser` derivatives to enforce mentioning specific users. - role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], builtins.bool]] - If provided, and `builtins.True`, all role mentions will be detected. - If provided, and `builtins.False`, all role mentions will be ignored + role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], bool]] + If provided, and `True`, all role mentions will be detected. + If provided, and `False`, all role mentions will be ignored if appearing in the message body. Alternatively this may be a collection of `hikari.snowflakes.Snowflake`, or `hikari.guilds.PartialRole` derivatives to enforce mentioning specific roles. - !!! note - Mentioning everyone, roles, or users in message edits currently - will not send a push notification showing a new mention to people - on Discord. It will still highlight in their chat as if they - were mentioned, however. - - !!! warning - If you specify a non-embed `content`, `mentions_everyone`, - `mentions_reply`, `user_mentions`, and `role_mentions` will default - to `builtins.False` as the message will be re-parsed for mentions. - - This is a limitation of Discord's design. If in doubt, specify all - three of them each time. - - !!! warning - If you specify one of `mentions_everyone`, `mentions_reply`, - `user_mentions`, or `role_mentions`, then all others will default to - `builtins.False`, even if they were enabled previously. - - This is a limitation of Discord's design. If in doubt, specify all - three of them each time. - Returns ------- hikari.messages.Message @@ -2581,10 +2543,10 @@ async def edit_webhook_message( Raises ------ - builtins.ValueError + ValueError If both `attachment` and `attachments`, `component` and `components` or `embed` and `embeds` are specified. - builtins.TypeError + TypeError If `attachments`, `components` or `embeds` is passed but is not a sequence. hikari.errors.BadRequestError @@ -2627,7 +2589,7 @@ async def delete_webhook_message( webhook : typing.Union[hikari.snowflakes.Snowflakeish, hikari.webhooks.ExecutableWebhook] The webhook to execute. This may be the object or the ID of an existing webhook. - token: builtins.str + token: str The webhook token. message : hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage] The message to delete. This may be the object or the ID of @@ -2658,7 +2620,7 @@ async def delete_webhook_message( async def fetch_gateway_url(self) -> str: """Fetch the gateway url. - !!! note + .. note:: This endpoint does not require any valid authorization. Raises @@ -2712,7 +2674,7 @@ async def fetch_invite(self, invite: typing.Union[invites.InviteCode, str]) -> i Parameters ---------- - invite : typing.Union[hikari.invites.InviteCode, builtins.str] + invite : typing.Union[hikari.invites.InviteCode, str] The invite to fetch. This may be an invite object or the code of an existing invite. @@ -2748,7 +2710,7 @@ async def delete_invite(self, invite: typing.Union[invites.InviteCode, str]) -> Parameters ---------- - invite : typing.Union[hikari.invites.InviteCode, builtins.str] + invite : typing.Union[hikari.invites.InviteCode, str] The invite to delete. This may be an invite object or the code of an existing invite. @@ -2821,10 +2783,10 @@ async def edit_my_user( Other Parameters ---------------- - username : undefined.UndefinedOr[builtins.str] + username : undefined.UndefinedOr[str] If provided, the new username. avatar : undefined.UndefinedNoneOr[hikari.files.Resourceish] - If provided, the new avatar. If `builtins.None`, + If provided, the new avatar. If `None`, the avatar will be removed. Returns @@ -2837,8 +2799,8 @@ async def edit_my_user( hikari.errors.BadRequestError If any of the fields that are passed have an invalid value. - Discord also returns this on a ratelimit: - https://github.com/discord/discord-api-docs/issues/1462 + Discord also returns this on a rate limit: + hikari.errors.UnauthorizedError If you are unauthorized to make the request (invalid/missing token). hikari.errors.InternalServerError @@ -2882,11 +2844,19 @@ def fetch_my_guilds( ) -> iterators.LazyIterator[applications.OwnGuild]: """Fetch the token's associated guilds. + Notes + ----- + This call is not a coroutine function, it returns a special type of + lazy iterator that will perform API calls as you iterate across it, + thus any errors documented below will happen then. + + See `hikari.iterators` for the full API for this iterator type. + Other Parameters ---------------- - newest_first : builtins.bool + newest_first : bool Whether to fetch the newest first or the olders first. - Defaults to `builtins.False`. + Defaults to `False`. start_at : hikari.undefined.UndefinedOr[hikari.snowflakes.SearchableSnowflakeishOr[hikari.guilds.PartialGuild]] If provided, will start at this snowflake. If you provide a datetime object, it will be transformed into a snowflake. This @@ -2898,11 +2868,6 @@ def fetch_my_guilds( hikari.iterators.LazyIterator[hikari.applications.OwnGuild] The token's associated guilds. - !!! note - This call is not a coroutine function, it returns a special type of - lazy iterator that will perform API calls as you iterate across it. - See `hikari.iterators` for the full API for this iterator type. - Raises ------ hikari.errors.BadRequestError @@ -2922,11 +2887,6 @@ def fetch_my_guilds( nature, and will trigger this exception if they occur. hikari.errors.InternalServerError If an internal error occurs on Discord while handling the request. - - !!! note - The exceptions on this endpoint will only be raised once the - result is awaited or iterated over. Invoking this function - itself will not raise anything. """ @abc.abstractmethod @@ -3001,7 +2961,7 @@ async def create_dm_channel(self, user: snowflakes.SnowflakeishOr[users.PartialU async def fetch_application(self) -> applications.Application: """Fetch the token's associated application. - !!! warning + .. warning:: This endpoint can only be used with a Bot token. Using this with a Bearer token will result in a `hikari.errors.UnauthorizedError`. @@ -3034,7 +2994,7 @@ async def fetch_application(self) -> applications.Application: async def fetch_authorization(self) -> applications.AuthorizationInformation: """Fetch the token's authorization information. - !!! warning + .. warning:: This endpoint can only be used with a Bearer token. Using this with a Bot token will result in a `hikari.errors.UnauthorizedError`. @@ -3076,9 +3036,9 @@ async def authorize_client_credentials_token( ---------- client : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] Object or ID of the application to authorize as. - client_secret : builtins.str + client_secret : str Secret of the application to authorize as. - scopes : typing.Sequence[typing.Union[hikari.applications.OAuth2Scope, builtins.str]] + scopes : typing.Sequence[typing.Union[hikari.applications.OAuth2Scope, str]] The scopes to authorize for. Returns @@ -3121,11 +3081,11 @@ async def authorize_access_token( ---------- client : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] Object or ID of the application to authorize with. - client_secret : builtins.str + client_secret : str Secret of the application to authorize with. - code : builtins.str + code : str The authorization code to exchange for an OAuth2 access token. - redirect_uri : builtins.str + redirect_uri : str The redirect uri that was included in the authorization request. Returns @@ -3167,7 +3127,7 @@ async def refresh_access_token( ) -> applications.OAuth2AuthorizationToken: """Refresh an access token. - !!! warning + .. warning:: As of writing this Discord currently ignores any passed scopes, therefore you should use `hikari.applications.OAuth2AuthorizationToken.scopes` to validate @@ -3177,14 +3137,14 @@ async def refresh_access_token( ---------- client : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] Object or ID of the application to authorize with. - client_secret : builtins.str + client_secret : str Secret of the application to authorize with. - refresh_token : builtins.str + refresh_token : str The refresh token to use. Other Parameters ---------------- - scopes : typing.Sequence[typing.Union[hikari.applications.OAuth2Scope, builtins.str]] + scopes : typing.Sequence[typing.Union[hikari.applications.OAuth2Scope, str]] The scope of the access request. Returns @@ -3226,9 +3186,9 @@ async def revoke_access_token( ---------- client : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] Object or ID of the application to authorize with. - client_secret : builtins.str + client_secret : str Secret of the application to authorize with. - token : typing.Union[builtins.str, hikari.applications.PartialOAuth2Token] + token : typing.Union[str, hikari.applications.PartialOAuth2Token] Object or string of the access token to revoke. Raises @@ -3265,7 +3225,7 @@ async def add_user_to_guild( ) -> typing.Optional[guilds.Member]: """Add a user to a guild. - !!! note + .. note:: This requires the `access_token` to have the `hikari.applications.OAuth2Scope.GUILDS_JOIN` scope enabled along with the authorization of a Bot which has `MANAGE_INVITES` @@ -3273,7 +3233,7 @@ async def add_user_to_guild( Parameters ---------- - access_token : typing.Union[builtins.str, hikari.applications.PartialOAuth2Token] + access_token : typing.Union[str, hikari.applications.PartialOAuth2Token] Object or string of the access token to use for this request. guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] The guild to add the user to. This may be the object @@ -3284,7 +3244,7 @@ async def add_user_to_guild( Other Parameters ---------------- - nick : hikari.undefined.UndefinedOr[builtins.str] + nick : hikari.undefined.UndefinedOr[str] If provided, the nick to add to the user when he joins the guild. Requires the `MANAGE_NICKNAMES` permission on the guild. @@ -3293,11 +3253,11 @@ async def add_user_to_guild( This may be a collection objects or IDs of existing roles. Requires the `MANAGE_ROLES` permission on the guild. - mute : hikari.undefined.UndefinedOr[builtins.bool] + mute : hikari.undefined.UndefinedOr[bool] If provided, the mute state to add the user when he joins the guild. Requires the `MUTE_MEMBERS` permission on the guild. - deaf : hikari.undefined.UndefinedOr[builtins.bool] + deaf : hikari.undefined.UndefinedOr[bool] If provided, the deaf state to add the user when he joins the guild. Requires the `DEAFEN_MEMBERS` permission on the guild. @@ -3305,7 +3265,7 @@ async def add_user_to_guild( Returns ------- typing.Optional[hikari.guilds.Member] - `builtins.None` if the user was already part of the guild, else + `None` if the user was already part of the guild, else `hikari.guilds.Member`. Raises @@ -3341,7 +3301,7 @@ async def add_user_to_guild( async def fetch_voice_regions(self) -> typing.Sequence[voices.VoiceRegion]: """Fetch available voice regions. - !!! note + .. note:: This endpoint doesn't return VIP voice regions. Returns @@ -3415,6 +3375,14 @@ def fetch_audit_log( ) -> iterators.LazyIterator[audit_logs.AuditLog]: """Fetch the guild's audit log. + Notes + ----- + This call is not a coroutine function, it returns a special type of + lazy iterator that will perform API calls as you iterate across it, + thus any errors documented below will happen then. + + See `hikari.iterators` for the full API for this iterator type. + Parameters ---------- guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] @@ -3430,7 +3398,7 @@ def fetch_audit_log( date the object was first created will be used. user : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.users.PartialUser]] If provided, the user to filter for. - event_type : hikari.undefined.UndefinedOr[typing.Union[hikari.audit_logs.AuditLogEventType, builtins.int]] + event_type : hikari.undefined.UndefinedOr[typing.Union[hikari.audit_logs.AuditLogEventType, int]] If provided, the event type to filter for. Returns @@ -3438,11 +3406,6 @@ def fetch_audit_log( hikari.iterators.LazyIterator[hikari.audit_logs.AuditLog] The guild's audit log. - !!! note - This call is not a coroutine function, it returns a special type of - lazy iterator that will perform API calls as you iterate across it. - See `hikari.iterators` for the full API for this iterator type. - Raises ------ hikari.errors.BadRequestError @@ -3464,11 +3427,6 @@ def fetch_audit_log( nature, and will trigger this exception if they occur. hikari.errors.InternalServerError If an internal error occurs on Discord while handling the request. - - !!! note - The exceptions on this endpoint will only be raised once the - result is awaited or iterated over. Invoking this function - itself will not raise anything. """ @abc.abstractmethod @@ -3569,7 +3527,7 @@ async def create_emoji( guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] The guild to create the emoji on. This can be a guild object or the ID of an existing guild. - name : builtins.str + name : str The name for the emoji. image : hikari.files.Resourceish The 128x128 image for the emoji. Maximum upload size is 256kb. @@ -3581,7 +3539,7 @@ async def create_emoji( If provided, a collection of the roles that will be able to use this emoji. This can be a `hikari.guilds.PartialRole` or the ID of an existing role. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -3639,13 +3597,13 @@ async def edit_emoji( Other Parameters ---------------- - name : hikari.undefined.UndefinedOr[builtins.str] + name : hikari.undefined.UndefinedOr[str] If provided, the new name for the emoji. roles : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole]] If provided, the new collection of roles that will be able to use this emoji. This can be a `hikari.guilds.PartialRole` or the ID of an existing role. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -3700,7 +3658,7 @@ async def delete_emoji( Other Parameters ---------------- - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -3895,23 +3853,23 @@ async def create_sticker( guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] The guild to create the sticker on. This can be a guild object or the ID of an existing guild. - name : builtins.str + name : str The name for the sticker. - tag : builtins.str + tag : str The tag for the sticker. image : hikari.files.Resourceish The 320x320 image for the sticker. Maximum upload size is 500kb. This can be a still or an animated PNG or a Lottie. - !!! note + .. note:: Lottie support is only available for verified and partnered servers. Other Parameters ---------------- - description: hikari.undefined.UndefinedOr[builtins.str] + description: hikari.undefined.UndefinedOr[str] If provided, the description of the sticker. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -3970,13 +3928,13 @@ async def edit_sticker( Other Parameters ---------------- - name : hikari.undefined.UndefinedOr[builtins.str] + name : hikari.undefined.UndefinedOr[str] If provided, the new name for the sticker. - description : hikari.undefined.UndefinedOr[builtins.str] + description : hikari.undefined.UndefinedOr[str] If provided, the new description for the sticker. - tag : hikari.undefined.UndefinedOr[builtins.str] + tag : hikari.undefined.UndefinedOr[str] If provided, the new sticker tag. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -4031,7 +3989,7 @@ async def delete_sticker( Other Parameters ---------------- - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -4064,7 +4022,7 @@ def guild_builder(self, name: str, /) -> special_endpoints.GuildBuilder: Parameters ---------- - name : builtins.str + name : str The new guilds name. Returns @@ -4073,7 +4031,7 @@ def guild_builder(self, name: str, /) -> special_endpoints.GuildBuilder: The guild builder to use. This will allow to create a guild later with `hikari.api.special_endpoints.GuildBuilder.create`. - !!! note + .. note:: This endpoint can only be used by bots in less than 10 guilds. Raises @@ -4097,7 +4055,7 @@ def guild_builder(self, name: str, /) -> special_endpoints.GuildBuilder: hikari.errors.InternalServerError If an internal error occurs on Discord while handling the request. - !!! note + .. note:: The exceptions on this endpoint will only be raised once `hikari.api.special_endpoints.GuildBuilder.create` is called. Invoking this function itself will not raise any of @@ -4105,7 +4063,7 @@ def guild_builder(self, name: str, /) -> special_endpoints.GuildBuilder: See Also -------- - Guild builder: `hikari.api.special_endpoints.GuildBuilder` + `hikari.api.special_endpoints.GuildBuilder` """ @abc.abstractmethod @@ -4161,7 +4119,7 @@ async def fetch_guild_preview(self, guild: snowflakes.SnowflakeishOr[guilds.Part hikari.guilds.GuildPreview The requested guild preview. - !!! note + .. note:: This will only work for guilds you are a part of or are public. Raises @@ -4228,7 +4186,7 @@ async def edit_guild( Other Parameters ---------------- - name : hikari.undefined.UndefinedOr[builtins.str] + name : hikari.undefined.UndefinedOr[str] If provided, the new name for the guild. verification_level : hikari.undefined.UndefinedOr[hikari.guilds.GuildVerificationLevel] If provided, the new verification level. @@ -4247,7 +4205,7 @@ async def edit_guild( owner : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.users.PartialUser]]] If provided, the new guild owner. - !!! warning + .. warning:: You need to be the owner of the server to use this. splash : hikari.undefined.UndefinedNoneOr[hikari.files.Resourceish] If provided, the new guild splash. Must be a 16:9 image and the @@ -4261,9 +4219,9 @@ async def edit_guild( If provided, the new rules channel. public_updates_channel : hikari.undefined.UndefinedNoneOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildTextChannel]] If provided, the new public updates channel. - preferred_locale : hikari.undefined.UndefinedNoneOr[builtins.str] + preferred_locale : hikari.undefined.UndefinedNoneOr[str] If provided, the new preferred locale. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -4393,17 +4351,17 @@ async def create_guild_text_channel( guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] The guild to create the channel in. This may be the object or the ID of an existing guild. - name : builtins.str + name : str The channels name. Must be between 2 and 1000 characters. Other Parameters ---------------- - position : hikari.undefined.UndefinedOr[builtins.int] + position : hikari.undefined.UndefinedOr[int] If provided, the position of the channel (relative to the category, if any). - topic : hikari.undefined.UndefinedOr[builtins.str] + topic : hikari.undefined.UndefinedOr[str] If provided, the channels topic. Maximum 1024 characters. - nsfw : hikari.undefined.UndefinedOr[builtins.bool] + nsfw : hikari.undefined.UndefinedOr[bool] If provided, whether to mark the channel as NSFW. rate_limit_per_user : hikari.undefined.UndefinedOr[hikari.internal.time.Intervalish] If provided, the amount of seconds a user has to wait @@ -4414,7 +4372,7 @@ async def create_guild_text_channel( category : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildCategory]] The category to create the channel under. This may be the object or the ID of an existing category. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -4471,17 +4429,17 @@ async def create_guild_news_channel( guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] The guild to create the channel in. This may be the object or the ID of an existing guild. - name : builtins.str + name : str The channels name. Must be between 2 and 1000 characters. Other Parameters ---------------- - position : hikari.undefined.UndefinedOr[builtins.int] + position : hikari.undefined.UndefinedOr[int] If provided, the position of the channel (relative to the category, if any). - topic : hikari.undefined.UndefinedOr[builtins.str] + topic : hikari.undefined.UndefinedOr[str] If provided, the channels topic. Maximum 1024 characters. - nsfw : hikari.undefined.UndefinedOr[builtins.bool] + nsfw : hikari.undefined.UndefinedOr[bool] If provided, whether to mark the channel as NSFW. rate_limit_per_user : hikari.undefined.UndefinedOr[hikari.internal.time.Intervalish] If provided, the amount of seconds a user has to wait @@ -4492,7 +4450,7 @@ async def create_guild_news_channel( category : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildCategory]] The category to create the channel under. This may be the object or the ID of an existing category. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -4550,34 +4508,34 @@ async def create_guild_voice_channel( guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] The guild to create the channel in. This may be the object or the ID of an existing guild. - name : builtins.str + name : str The channels name. Must be between 2 and 1000 characters. Other Parameters ---------------- - position : hikari.undefined.UndefinedOr[builtins.int] + position : hikari.undefined.UndefinedOr[int] If provided, the position of the channel (relative to the category, if any). - user_limit : hikari.undefined.UndefinedOr[builtins.int] + user_limit : hikari.undefined.UndefinedOr[int] If provided, the maximum users in the channel at once. Must be between 0 and 99 with 0 meaning no limit. - bitrate : hikari.undefined.UndefinedOr[builtins.int] + bitrate : hikari.undefined.UndefinedOr[int] If provided, the bitrate for the channel. Must be between 8000 and 96000 or 8000 and 128000 for VIP servers. - video_quality_mode: hikari.undefined.UndefinedOr[typing.Union[hikari.channels.VideoQualityMode, builtins.int]] + video_quality_mode: hikari.undefined.UndefinedOr[typing.Union[hikari.channels.VideoQualityMode, int]] If provided, the new video quality mode for the channel. permission_overwrites : hikari.undefined.UndefinedOr[typing.Sequence[hikari.channels.PermissionOverwrite]] If provided, the permission overwrites for the channel. - region : hikari.undefined.UndefinedOr[typing.Union[hikari.voices.VoiceRegion, builtins.str]] + region : hikari.undefined.UndefinedOr[typing.Union[hikari.voices.VoiceRegion, str]] If provided, the voice region to for this channel. Passing - `builtins.None` here will set it to "auto" mode where the used + `None` here will set it to "auto" mode where the used region will be decided based on the first person who connects to it when it's empty. category : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildCategory]] The category to create the channel under. This may be the object or the ID of an existing category. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -4634,32 +4592,32 @@ async def create_guild_stage_channel( guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] The guild to create the channel in. This may be the object or the ID of an existing guild. - name : builtins.str + name : str The channel's name. Must be between 2 and 1000 characters. Other Parameters ---------------- - position : hikari.undefined.UndefinedOr[builtins.int] + position : hikari.undefined.UndefinedOr[int] If provided, the position of the channel (relative to the category, if any). - user_limit : hikari.undefined.UndefinedOr[builtins.int] + user_limit : hikari.undefined.UndefinedOr[int] If provided, the maximum users in the channel at once. Must be between 0 and 99 with 0 meaning no limit. - bitrate : hikari.undefined.UndefinedOr[builtins.int] + bitrate : hikari.undefined.UndefinedOr[int] If provided, the bitrate for the channel. Must be between 8000 and 96000 or 8000 and 128000 for VIP servers. permission_overwrites : hikari.undefined.UndefinedOr[typing.Sequence[hikari.channels.PermissionOverwrite]] If provided, the permission overwrites for the channel. - region : hikari.undefined.UndefinedOr[typing.Union[hikari.voices.VoiceRegion, builtins.str]] + region : hikari.undefined.UndefinedOr[typing.Union[hikari.voices.VoiceRegion, str]] If provided, the voice region to for this channel. Passing - `builtins.None` here will set it to "auto" mode where the used + `None` here will set it to "auto" mode where the used region will be decided based on the first person who connects to it when it's empty. category : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildCategory]] The category to create the channel under. This may be the object or the ID of an existing category. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -4712,16 +4670,16 @@ async def create_guild_category( guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] The guild to create the channel in. This may be the object or the ID of an existing guild. - name : builtins.str + name : str The channels name. Must be between 2 and 1000 characters. Other Parameters ---------------- - position : hikari.undefined.UndefinedOr[builtins.int] + position : hikari.undefined.UndefinedOr[int] If provided, the position of the category. permission_overwrites : hikari.undefined.UndefinedOr[typing.Sequence[hikari.channels.PermissionOverwrite]] If provided, the permission overwrites for the category. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -4768,7 +4726,7 @@ async def reposition_channels( guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] The guild to reposition the channels in. This may be the object or the ID of an existing guild. - positions : typing.Mapping[builtins.int, hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildChannel]] + positions : typing.Mapping[int, hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildChannel]] A mapping of of the object or the ID of an existing channel to the new position, relative to their parent category, if any. @@ -4844,6 +4802,20 @@ def fetch_members( ) -> iterators.LazyIterator[guilds.Member]: """Fetch the members from a guild. + .. warning:: + This endpoint requires the `GUILD_MEMBERS` intent to be enabled in + the dashboard, not necessarily authenticated with it if using the + gateway. If you don't have the intents you can use `search_members` + which doesn't require any intents. + + Notes + ----- + This call is not a coroutine function, it returns a special type of + lazy iterator that will perform API calls as you iterate across it, + thus any errors documented below will happen then. + + See `hikari.iterators` for the full API for this iterator type. + Parameters ---------- guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] @@ -4855,11 +4827,6 @@ def fetch_members( hikari.iterators.LazyIterator[hikari.guilds.Member] An iterator to fetch the members. - !!! note - This call is not a coroutine function, it returns a special type of - lazy iterator that will perform API calls as you iterate across it. - See `hikari.iterators` for the full API for this iterator type. - Raises ------ hikari.errors.UnauthorizedError @@ -4879,19 +4846,6 @@ def fetch_members( nature, and will trigger this exception if they occur. hikari.errors.InternalServerError If an internal error occurs on Discord while handling the request. - - !!! note - The exceptions on this endpoint will only be raised once the - result is awaited or iterated over. Invoking this function - itself will not raise anything. - - !!! warning - This endpoint requires the `GUILD_MEMBERS` intent to be enabled in - the dashboard, not necessarily authenticated with it if using the - gateway. - - If you don't have the intents you can use `search_members` which - doesn't require any intents. """ @abc.abstractmethod @@ -4934,7 +4888,7 @@ async def search_members( hikari.errors.InternalServerError If an internal error occurs on Discord while handling the request. - !!! note + .. note:: Unlike `RESTClient.fetch_members` this endpoint isn't paginated and therefore will return all the members in one go rather than needing to be asynchronously iterated over. @@ -4968,8 +4922,8 @@ async def edit_member( Other Parameters ---------------- - nick : hikari.undefined.UndefinedNoneOr[builtins.str] - If provided, the new nick for the member. If `builtins.None`, + nick : hikari.undefined.UndefinedNoneOr[str] + If provided, the new nick for the member. If `None`, will remove the members nick. Requires the `MANAGE_NICKNAMES` permission. @@ -4977,27 +4931,27 @@ async def edit_member( If provided, the new roles for the member. Requires the `MANAGE_ROLES` permission. - mute : hikari.undefined.UndefinedOr[builtins.bool] + mute : hikari.undefined.UndefinedOr[bool] If provided, the new server mute state for the member. Requires the `MUTE_MEMBERS` permission. - deaf : hikari.undefined.UndefinedOr[builtins.bool] + deaf : hikari.undefined.UndefinedOr[bool] If provided, the new server deaf state for the member. Requires the `DEAFEN_MEMBERS` permission. voice_channel : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildVoiceChannel]]] - If provided, `builtins.None` or the object or the ID of + If provided, `None` or the object or the ID of an existing voice channel to move the member to. - If `builtins.None`, will disconnect the member from voice. + If `None`, will disconnect the member from voice. Requires the `MOVE_MEMBERS` permission and the `CONNECT` permission in the original voice channel and the target voice channel. - !!! note + .. note:: If the member is not in a voice channel, this will take no effect. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -5049,9 +5003,9 @@ async def edit_my_member( Other Parameters ---------------- - nickname : hikari.undefined.UndefinedNoneOr[builtins.str] + nickname : hikari.undefined.UndefinedNoneOr[str] If provided, the new nickname for the member. If - `builtins.None`, will remove the members nickname. + `None`, will remove the members nickname. Requires the `CHANGE_NICKNAME` permission. If provided, the reason that will be recorded in the audit logs. @@ -5087,6 +5041,7 @@ async def edit_my_member( If an internal error occurs on Discord while handling the request. """ + @deprecation.deprecated("2.0.0.dev104", "Use `edit_my_member`'s `nick` argument instead.") @abc.abstractmethod async def edit_my_nick( self, @@ -5097,21 +5052,18 @@ async def edit_my_nick( ) -> None: """Edit the associated token's member nick. - .. deprecated:: 2.0.0.dev104 - Use `RESTClient.edit_my_member`'s `nick` argument instead. - Parameters ---------- guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] The guild to edit. This may be the object or the ID of an existing guild. - nick : typing.Optional[builtins.str] - The new nick. If `builtins.None`, + nick : typing.Optional[str] + The new nick. If `None`, will remove the nick. Other Parameters ---------------- - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -5163,7 +5115,7 @@ async def add_role_to_member( Other Parameters ---------------- - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -5215,7 +5167,7 @@ async def remove_role_from_member( Other Parameters ---------------- - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -5263,7 +5215,7 @@ async def kick_user( Other Parameters ---------------- - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -5322,10 +5274,10 @@ async def ban_user( Other Parameters ---------------- - delete_message_days : hikari.undefined.UndefinedNoneOr[builtins.int] + delete_message_days : hikari.undefined.UndefinedNoneOr[int] If provided, the number of days to delete messages for. This must be between 0 and 7. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -5386,7 +5338,7 @@ async def unban_user( Other Parameters ---------------- - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -5574,7 +5526,7 @@ async def create_role( Other Parameters ---------------- - name : hikari.undefined.UndefinedOr[builtins.str] + name : hikari.undefined.UndefinedOr[str] If provided, the name for the role. permissions : hikari.undefined.UndefinedOr[hikari.permissions.Permissions] The permissions to give the role. This will default to setting @@ -5585,15 +5537,15 @@ async def create_role( If provided, the role's color. colour : hikari.undefined.UndefinedOr[hikari.colors.Colorish] An alias for `color`. - hoist : hikari.undefined.UndefinedOr[builtins.bool] + hoist : hikari.undefined.UndefinedOr[bool] If provided, whether to hoist the role. icon : hikari.undefined.UndefinedOr[hikari.files.Resourceish] If provided, the role icon. Must be a 64x64 image under 256kb. - unicode_emoji : hikari.undefined.UndefinedOr[builtins.str] + unicode_emoji : hikari.undefined.UndefinedOr[str] If provided, the standard emoji to set as the role icon. - mentionable : hikari.undefined.UndefinedOr[builtins.bool] + mentionable : hikari.undefined.UndefinedOr[bool] If provided, whether to make the role mentionable. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -5604,7 +5556,7 @@ async def create_role( Raises ------ - builtins.TypeError + TypeError If both `color` and `colour` are specified or if both `icon` and `unicode_emoji` are specified. hikari.errors.BadRequestError @@ -5643,7 +5595,7 @@ async def reposition_roles( guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] The guild to reposition the roles in. This may be the object or the ID of an existing guild. - positions : typing.Mapping[builtins.int, hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialRole]] + positions : typing.Mapping[int, hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialRole]] A mapping of the position to the role. Raises @@ -5698,7 +5650,7 @@ async def edit_role( Other Parameters ---------------- - name : hikari.undefined.UndefinedOr[builtins.str] + name : hikari.undefined.UndefinedOr[str] If provided, the new name for the role. permissions : hikari.undefined.UndefinedOr[hikari.permissions.Permissions] If provided, the new permissions for the role. @@ -5706,16 +5658,16 @@ async def edit_role( If provided, the new color for the role. colour : hikari.undefined.UndefinedOr[hikari.colors.Colorish] An alias for `color`. - hoist : hikari.undefined.UndefinedOr[builtins.bool] + hoist : hikari.undefined.UndefinedOr[bool] If provided, whether to hoist the role. icon : hikari.undefined.UndefinedNoneOr[hikari.files.Resourceish] If provided, the new role icon. Must be a 64x64 image under 256kb. - unicode_emoji : hikari.undefined.UndefinedNoneOr[builtins.str] + unicode_emoji : hikari.undefined.UndefinedNoneOr[str] If provided, the new unicode emoji to set as the role icon. - mentionable : hikari.undefined.UndefinedOr[builtins.bool] + mentionable : hikari.undefined.UndefinedOr[bool] If provided, whether to make the role mentionable. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -5726,7 +5678,7 @@ async def edit_role( Raises ------ - builtins.TypeError + TypeError If both `color` and `colour` are specified or if both `icon` and `unicode_emoji` are specified. hikari.errors.BadRequestError @@ -5810,7 +5762,7 @@ async def estimate_guild_prune_count( Other Parameters ---------------- - days : hikari.undefined.UndefinedOr[builtins.int] + days : hikari.undefined.UndefinedOr[int] If provided, number of days to count prune for. include_roles : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole]]] If provided, the role(s) to include. By default, this endpoint will @@ -5820,7 +5772,7 @@ async def estimate_guild_prune_count( Returns ------- - builtins.int + int The estimated guild prune count. Raises @@ -5846,7 +5798,7 @@ async def estimate_guild_prune_count( nature, and will trigger this exception if they occur. hikari.errors.InternalServerError If an internal error occurs on Discord while handling the request. - """ # noqa: E501 - Line too long + """ @abc.abstractmethod async def begin_guild_prune( @@ -5868,9 +5820,9 @@ async def begin_guild_prune( Other Parameters ---------------- - days : hikari.undefined.UndefinedOr[builtins.int] + days : hikari.undefined.UndefinedOr[int] If provided, number of days to count prune for. - compute_prune_count: hikari.snowflakes.SnowflakeishOr[builtins.bool] + compute_prune_count: hikari.snowflakes.SnowflakeishOr[bool] If provided, whether to return the prune count. This is discouraged for large guilds. include_roles : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole]] @@ -5878,15 +5830,15 @@ async def begin_guild_prune( not count users with roles. Providing roles using this attribute will make members with the specified roles also get included into the count. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. Returns ------- - typing.Optional[builtins.int] - If `compute_prune_count` is not provided or `builtins.True`, the - number of members pruned. Else `builtins.None`. + typing.Optional[int] + If `compute_prune_count` is not provided or `True`, the + number of members pruned. Else `None`. Raises ------ @@ -5920,7 +5872,7 @@ async def fetch_guild_voice_regions( ) -> typing.Sequence[voices.VoiceRegion]: """Fetch the available voice regions for a guild. - !!! note + .. note:: Unlike `RESTClient.fetch_voice_regions`, this will return the VIP regions if the guild has access to them. @@ -6096,11 +6048,11 @@ async def edit_widget( Other Parameters ---------------- channel : hikari.undefined.UndefinedNoneOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildChannel]] - If provided, the channel to set the widget to. If `builtins.None`, + If provided, the channel to set the widget to. If `None`, will not set to any. - enabled : hikari.undefined.UndefinedOr[builtins.bool] + enabled : hikari.undefined.UndefinedOr[bool] If provided, whether to enable the widget. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -6187,17 +6139,17 @@ async def edit_welcome_screen( Other Parameters ---------------- - description : undefined.UndefinedNoneOr[builtins.str] + description : undefined.UndefinedNoneOr[str] If provided, the description to set for the guild's welcome screen. - This may be `builtins.None` to unset the description. - enabled : undefined.UndefinedOr[builtins.bool] + This may be `None` to unset the description. + enabled : undefined.UndefinedOr[bool] If provided, Whether the guild's welcome screen should be enabled. channels : hikari.undefined.UndefinedNoneOr[typing.Sequence[hikari.guilds.WelcomeChannel]] If provided, a sequence of up to 5 public channels to set in this - guild's welcome screen. This may be passed as `builtins.None` to + guild's welcome screen. This may be passed as `None` to remove all welcome channels - !!! note + .. note:: Custom emojis may only be included in a guild's welcome channels if it's boost status is tier 2 or above. @@ -6293,7 +6245,7 @@ async def create_template( Other Parameters ---------------- - description : hikari.undefined.UndefinedNoneOr[builtins.str] + description : hikari.undefined.UndefinedNoneOr[str] The description to set for the template. Returns @@ -6337,9 +6289,9 @@ async def create_guild_from_template( Parameters ---------- - template : typing.Union[builtins.str, hikari.templates.Template] + template : typing.Union[str, hikari.templates.Template] The object or string code of the template to create a guild based on. - name : builtins.str + name : str The new guilds name. Other Parameters @@ -6353,7 +6305,7 @@ async def create_guild_from_template( hikari.guilds.RESTGuild Object of the created guild. - !!! note + .. note:: This endpoint can only be used by bots in less than 10 guilds. Raises @@ -6437,14 +6389,14 @@ async def edit_template( ---------- guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] The guild to edit a template in. - template : typing.Union[builtins.str, hikari.templates.Template] + template : typing.Union[str, hikari.templates.Template] Object or string code of the template to modify. Other Parameters ---------------- - name : hikari.undefined.UndefinedOr[builtins.str] + name : hikari.undefined.UndefinedOr[str] The name to set for this template. - description : hikari.undefined.UndefinedNoneOr[builtins.str] + description : hikari.undefined.UndefinedNoneOr[str] The description to set for the template. Returns @@ -6482,7 +6434,7 @@ async def fetch_template(self, template: typing.Union[str, templates.Template]) Parameters ---------- - template : typing.Union[builtins.str, hikari.templates.Template] + template : typing.Union[str, hikari.templates.Template] The object or string code of the template to fetch. Returns @@ -6563,7 +6515,7 @@ async def sync_guild_template( ---------- guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] The guild to sync a template in. - template : typing.Union[builtins.str, hikari.templates.Template] + template : typing.Union[str, hikari.templates.Template] Object or code of the template to sync. Returns @@ -6601,10 +6553,10 @@ def command_builder(self, name: str, description: str, /) -> special_endpoints.C Parameters ---------- - name : builtins.str + name : str The command's name. This should match the regex `^[\w-]{1,32}$` in Unicode mode and be lowercase. - description : builtins.str + description : str The description to set for the command. This should be inclusively between 1-100 characters in length. @@ -6733,10 +6685,10 @@ async def create_application_command( ---------- application: hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] Object or ID of the application to create a command for. - name : builtins.str + name : str The command's name. This should match the regex `^[\w-]{1,32}$` in Unicode mode and be lowercase. - description : builtins.str + description : str The description to set for the command. This should be inclusively between 1-100 characters in length. @@ -6748,11 +6700,11 @@ async def create_application_command( a global command rather than a guild specific one. options : hikari.undefined.UndefinedOr[typing.Sequence[hikari.commands.CommandOption]] A sequence of up to 10 options for this command. - default_permission : hikari.undefined.UndefinedOr[builtins.bool] + default_permission : hikari.undefined.UndefinedOr[bool] Whether this command should be enabled by default (without any permissions) when added to a guild. - Defaults to `builtins.True`. + Defaults to `True`. Returns ------- @@ -6793,7 +6745,7 @@ async def set_application_commands( ) -> typing.Sequence[commands.Command]: """Set the commands for an application. - !!! warning + .. warning:: Any existing commands not included in the provided commands array will be deleted. @@ -6868,10 +6820,10 @@ async def edit_application_command( Object or ID of the guild to edit a command for if this is a guild specific command. Leave this as `hikari.undefined.UNDEFINED` to delete a global command. - name : hikari.undefined.UndefinedOr[builtins.str] + name : hikari.undefined.UndefinedOr[str] The name to set for the command. Leave as `hikari.undefined.UNDEFINED` to not change. - description : hikari.undefined.UndefinedOr[builtins.str] + description : hikari.undefined.UndefinedOr[str] The description to set for the command. Leave as `hikari.undefined.UNDEFINED` to not change. options : hikari.undefined.UndefinedOr[typing.Sequence[hikari.commands.CommandOption]] @@ -7054,7 +7006,7 @@ async def set_application_guild_commands_permissions( ) -> typing.Sequence[commands.GuildCommandPermissions]: """Set permissions in a guild for multiple commands. - !!! note + .. note:: This overwrites any previously set permissions for the specified commands. @@ -7068,7 +7020,7 @@ async def set_application_guild_commands_permissions( Mapping of objects and/or IDs of commands to sequences of the commands to set for the specified guild. - !!! warning + .. warning:: Only a maximum of up to 10 permissions can be set per command. Returns @@ -7109,7 +7061,7 @@ async def set_application_command_permissions( ) -> commands.GuildCommandPermissions: """Set permissions for a specific command. - !!! note + .. note:: This overwrites any previously set permissions. Parameters @@ -7159,7 +7111,7 @@ def interaction_deferred_builder( Parameters ---------- - type: typing.Union[hikari.interactions.base_interactions.ResponseType, builtins.int] + type: typing.Union[hikari.interactions.base_interactions.ResponseType, int] The type of deferred message response this builder is for. Returns @@ -7176,7 +7128,7 @@ def interaction_message_builder( Parameters ---------- - type : typing.Union[hikari.interactions.base_interactions.ResponseType, builtins.int] + type : typing.Union[hikari.interactions.base_interactions.ResponseType, int] The type of message response this builder is for. Returns @@ -7195,7 +7147,7 @@ async def fetch_interaction_response( ---------- application: hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] Object or ID of the application to fetch a command for. - token: builtins.str + token: str Token of the interaction to get the initial response for. Returns @@ -7250,7 +7202,7 @@ async def create_interaction_response( ) -> None: """Create the initial response for a interaction. - !!! warning + .. warning:: Calling this with an interaction which already has an initial response will result in this raising a `hikari.errors.NotFoundError`. This includes if the REST interaction server has already responded @@ -7260,9 +7212,9 @@ async def create_interaction_response( ---------- interaction : hikari.snowflakes.SnowflakeishOr[hikari.interactions.base_interactions.PartialInteraction] Object or ID of the interaction this response is for. - token : builtins.str + token : str The command interaction's token. - response_type : typing.Union[builtins.int, hikari.interactions.base_interactions.ResponseType] + response_type : typing.Union[int, hikari.interactions.base_interactions.ResponseType] The type of interaction response this is. Other Parameters @@ -7271,7 +7223,7 @@ async def create_interaction_response( If provided, the message contents. If `hikari.undefined.UNDEFINED`, then nothing will be sent in the content. Any other value here will be cast to a - `builtins.str`. + `str`. If this is a `hikari.embeds.Embed` and no `embed` nor no `embeds` kwarg is provided, then this will instead @@ -7286,28 +7238,28 @@ async def create_interaction_response( If provided, the message embed. embeds : hikari.undefined.UndefinedOr[typing.Sequence[hikari.embeds.Embed]] If provided, the message embeds. - flags : typing.Union[builtins.int, hikari.messages.MessageFlag, hikari.undefined.UndefinedType] + flags : typing.Union[int, hikari.messages.MessageFlag, hikari.undefined.UndefinedType] If provided, the message flags this response should have. As of writing the only message flag which can be set here is `hikari.messages.MessageFlag.EPHEMERAL`. - tts : hikari.undefined.UndefinedOr[builtins.bool] + tts : hikari.undefined.UndefinedOr[bool] If provided, whether the message will be read out by a screen reader using Discord's TTS (text-to-speech) system. - mentions_everyone : hikari.undefined.UndefinedOr[builtins.bool] + mentions_everyone : hikari.undefined.UndefinedOr[bool] If provided, whether the message should parse @everyone/@here mentions. - user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], builtins.bool]] - If provided, and `builtins.True`, all user mentions will be detected. - If provided, and `builtins.False`, all user mentions will be ignored + user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], bool]] + If provided, and `True`, all user mentions will be detected. + If provided, and `False`, all user mentions will be ignored if appearing in the message body. Alternatively this may be a collection of `hikari.snowflakes.Snowflake`, or `hikari.users.PartialUser` derivatives to enforce mentioning specific users. - role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], builtins.bool]] - If provided, and `builtins.True`, all role mentions will be detected. - If provided, and `builtins.False`, all role mentions will be ignored + role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], bool]] + If provided, and `True`, all role mentions will be detected. + If provided, and `False`, all role mentions will be ignored if appearing in the message body. Alternatively this may be a collection of `hikari.snowflakes.Snowflake`, or @@ -7316,10 +7268,10 @@ async def create_interaction_response( Raises ------ - builtins.ValueError + ValueError If more than 100 unique objects/entities are passed for `role_mentions` or `user_mentions`. - builtins.TypeError + TypeError If both `embed` and `embeds` are specified. hikari.errors.BadRequestError This may be raised in several discrete situations, such as messages @@ -7372,11 +7324,26 @@ async def edit_interaction_response( ) -> messages_.Message: """Edit the initial response to a command interaction. + Notes + ----- + Mentioning everyone, roles, or users in message edits currently + will not send a push notification showing a new mention to people + on Discord. It will still highlight in their chat as if they + were mentioned, however. + + Also important to note that if you specify a text `content`, `mentions_everyone`, + `mentions_reply`, `user_mentions`, and `role_mentions` will default + to `False` as the message will be re-parsed for mentions. This will + also occur if only one of the four are specified + + This is a limitation of Discord's design. If in doubt, specify all + four of them each time. + Parameters ---------- application: hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] Object or ID of the application to edit a command response for. - token : builtins.str + token : str The interaction's token. Other Parameters @@ -7384,9 +7351,9 @@ async def edit_interaction_response( content : hikari.undefined.UndefinedOr[typing.Any] If provided, the message content to update with. If `hikari.undefined.UNDEFINED`, then the content will not - be changed. If `builtins.None`, then the content will be removed. + be changed. If `None`, then the content will be removed. - Any other value will be cast to a `builtins.str` before sending. + Any other value will be cast to a `str` before sending. If this is a `hikari.embeds.Embed` and neither the `embed` or `embeds` kwargs are provided or if this is a @@ -7397,75 +7364,61 @@ async def edit_interaction_response( attachment : hikari.undefined.UndefinedOr[hikari.files.Resourceish] If provided, the attachment to set on the message. If `hikari.undefined.UNDEFINED`, the previous attachment, if - present, is not changed. If this is `builtins.None`, then the + present, is not changed. If this is `None`, then the attachment is removed, if present. Otherwise, the new attachment that was provided will be attached. attachments : hikari.undefined.UndefinedOr[typing.Sequence[hikari.files.Resourceish]] If provided, the attachments to set on the message. If `hikari.undefined.UNDEFINED`, the previous attachments, if - present, are not changed. If this is `builtins.None`, then the + present, are not changed. If this is `None`, then the attachments is removed, if present. Otherwise, the new attachments that were provided will be attached. component : hikari.undefined.UndefinedNoneOr[hikari.api.special_endpoints.ComponentBuilder] If provided, builder object of the component to set for this message. This component will replace any previously set components and passing - `builtins.None` will remove all components. + `None` will remove all components. components : hikari.undefined.UndefinedNoneOr[typing.Sequence[hikari.api.special_endpoints.ComponentBuilder]] If provided, a sequence of the component builder objects set for this message. These components will replace any previously set - components and passing `builtins.None` or an empty sequence will + components and passing `None` or an empty sequence will remove all components. embed : hikari.undefined.UndefinedNoneOr[hikari.embeds.Embed] If provided, the embed to set on the message. If `hikari.undefined.UNDEFINED`, the previous embed(s) are not changed. - If this is `builtins.None` then any present embeds are removed. + If this is `None` then any present embeds are removed. Otherwise, the new embed that was provided will be used as the replacement. embeds : hikari.undefined.UndefinedNoneOr[typing.Sequence[hikari.embeds.Embed]] If provided, the embeds to set on the message. If `hikari.undefined.UNDEFINED`, the previous embed(s) are not changed. - If this is `builtins.None` then any present embeds are removed. + If this is `None` then any present embeds are removed. Otherwise, the new embeds that were provided will be used as the replacement. replace_attachments: bool Whether to replace the attachments with the provided ones. Defaults - to `builtins.False`. + to `False`. Note this will also overwrite the embed attachments. - mentions_everyone : hikari.undefined.UndefinedOr[builtins.bool] + mentions_everyone : hikari.undefined.UndefinedOr[bool] If provided, whether the message should parse @everyone/@here mentions. - user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], builtins.bool]] - If provided, and `builtins.True`, all user mentions will be detected. - If provided, and `builtins.False`, all user mentions will be ignored + user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], bool]] + If provided, and `True`, all user mentions will be detected. + If provided, and `False`, all user mentions will be ignored if appearing in the message body. Alternatively this may be a collection of `hikari.snowflakes.Snowflake`, or `hikari.users.PartialUser` derivatives to enforce mentioning specific users. - role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], builtins.bool]] - If provided, and `builtins.True`, all role mentions will be detected. - If provided, and `builtins.False`, all role mentions will be ignored + role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], bool]] + If provided, and `True`, all role mentions will be detected. + If provided, and `False`, all role mentions will be ignored if appearing in the message body. Alternatively this may be a collection of `hikari.snowflakes.Snowflake`, or `hikari.guilds.PartialRole` derivatives to enforce mentioning specific roles. - !!! note - Mentioning everyone, roles, or users in message edits currently - will not send a push notification showing a new mention to people - on Discord. It will still highlight in their chat as if they - were mentioned, however. - - !!! warning - If you specify one of `mentions_everyone`, `user_mentions`, or - `role_mentions`, then all others will default to `builtins.False`, - even if they were enabled previously. - - This is a limitation of Discord's design. If in doubt, specify all three of - them each time. - Returns ------- hikari.messages.Message @@ -7473,10 +7426,10 @@ async def edit_interaction_response( Raises ------ - builtins.ValueError + ValueError If both `attachment` and `attachments`, `component` and `components` or `embed` and `embeds` are specified. - builtins.TypeError + TypeError If `attachments`, `components` or `embeds` is passed but is not a sequence. hikari.errors.BadRequestError @@ -7514,7 +7467,7 @@ async def delete_interaction_response( ---------- application: hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] Object or ID of the application to delete a command response for. - token : builtins.str + token : str The interaction's token. Raises diff --git a/hikari/api/shard.py b/hikari/api/shard.py index e23c23b9e5..c381841a66 100644 --- a/hikari/api/shard.py +++ b/hikari/api/shard.py @@ -73,58 +73,31 @@ class GatewayShard(abc.ABC): @property @abc.abstractmethod def heartbeat_latency(self) -> float: - """Return the shard's most recent heartbeat latency. + """Shard's most recent heartbeat latency. - Returns - ------- - builtins.float - Heartbeat latency measured in seconds. If the information is - not yet available, then this will be `float('nan')` instead. + If the information is not yet available, then this will be + `float('nan')` instead. """ @property @abc.abstractmethod def id(self) -> int: - """Return the shard ID for this shard. - - Returns - ------- - builtins.int - The integer 0-based shard ID. - """ + """0-based shard ID for this shard.""" @property @abc.abstractmethod def intents(self) -> intents_.Intents: - """Return the intents set on this shard. - - Returns - ------- - hikari.intents.Intents - The intents being used on this shard. - """ + """Intents set on this shard.""" @property @abc.abstractmethod def is_alive(self) -> bool: - """Return `builtins.True` if the shard is alive and connected. - - Returns - ------- - builtins.bool - `builtins.True` if connected, or `builtins.False` if not. - """ + """Whether the shard is alive and connected.""" @property @abc.abstractmethod def shard_count(self) -> int: - """Return the total number of shards expected in the entire application. - - Returns - ------- - builtins.int - A number of shards greater than or equal to 1. - """ + """Return the total number of shards expected in the entire application.""" @abc.abstractmethod async def get_user_id(self) -> snowflakes.Snowflake: @@ -175,8 +148,8 @@ async def update_presence( idle_since : hikari.undefined.UndefinedNoneOr[datetime.datetime] The datetime that the user started being idle. If undefined, this will not be changed. - afk : hikari.undefined.UndefinedOr[builtins.bool] - If `builtins.True`, the user is marked as AFK. If `builtins.False`, + afk : hikari.undefined.UndefinedOr[bool] + If `True`, the user is marked as AFK. If `False`, the user is marked as being active. If undefined, this will not be changed. activity : hikari.undefined.UndefinedNoneOr[hikari.presences.Activity] @@ -202,15 +175,15 @@ async def update_voice_state( guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] The guild or guild ID to update the voice state for. channel : typing.Optional[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildVoiceChannel]] - The channel or channel ID to update the voice state for. If `builtins.None` + The channel or channel ID to update the voice state for. If `None` then the bot will leave the voice channel that it is in for the given guild. - self_mute : builtins.bool - If specified and `builtins.True`, the bot will mute itself in that - voice channel. If `builtins.False`, then it will unmute itself. - self_deaf : builtins.bool - If specified and `builtins.True`, the bot will deafen itself in that - voice channel. If `builtins.False`, then it will undeafen itself. + self_mute : bool + If specified and `True`, the bot will mute itself in that + voice channel. If `False`, then it will unmute itself. + self_deaf : bool + If specified and `True`, the bot will deafen itself in that + voice channel. If `False`, then it will undeafen itself. """ @abc.abstractmethod @@ -226,6 +199,10 @@ async def request_guild_members( ) -> None: """Request for a guild chunk. + .. note:: + To request the full list of members, set `query` to `""` (empty + string) and `limit` to `0`. + Parameters ---------- guild: hikari.guilds.Guild @@ -233,21 +210,17 @@ async def request_guild_members( Other Parameters ---------------- - include_presences: hikari.undefined.UndefinedOr[builtins.bool] + include_presences: hikari.undefined.UndefinedOr[bool] If provided, whether to request presences. - query: builtins.str + query: str If not `""`, request the members which username starts with the string. - limit: builtins.int + limit: int Maximum number of members to send matching the query. users: hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishSequence[hikari.users.User]] If provided, the users to request for. - nonce: hikari.undefined.UndefinedOr[builtins.str] + nonce: hikari.undefined.UndefinedOr[str] If provided, the nonce to be sent with guild chunks. - !!! note - To request the full list of members, set `query` to `""` (empty - string) and `limit` to `0`. - Raises ------ ValueError diff --git a/hikari/api/special_endpoints.py b/hikari/api/special_endpoints.py index eb793b6928..3e5f16a097 100644 --- a/hikari/api/special_endpoints.py +++ b/hikari/api/special_endpoints.py @@ -77,7 +77,7 @@ class TypingIndicator(abc.ABC): the typing indicator once, or an async context manager to keep triggering the typing indicator repeatedly until the context finishes. - !!! note + .. note:: This is a helper class that is used by `hikari.api.rest.RESTClient`. You should only ever need to use instances of this class that are produced by that API. @@ -110,7 +110,7 @@ class GuildBuilder(abc.ABC): the logic behind creating a guild on an API level is somewhat confusing and detailed. - !!! note + .. note:: This is a helper class that is used by `hikari.api.rest.RESTClient`. You should only ever need to use instances of this class that are produced by that API, thus, any details about the constructor are @@ -147,15 +147,15 @@ class GuildBuilder(abc.ABC): await guild_builder.create() ``` - !!! warning + .. warning:: The first role must always be the `@everyone` role. - !!! note + .. note:: If you call `add_role`, the default roles provided by discord will be created. This also applies to the `add_` functions for text channels/voice channels/categories. - !!! note + .. note:: Functions that return a `hikari.snowflakes.Snowflake` do **not** provide the final ID that the object will have once the API call is made. The returned IDs are only able to be used to @@ -181,13 +181,7 @@ class GuildBuilder(abc.ABC): @property @abc.abstractmethod def name(self) -> str: - """Name of the guild to create. - - Returns - ------- - builtins.str - The guild name. - """ + """Name of the guild to create.""" @property @abc.abstractmethod @@ -195,11 +189,6 @@ def default_message_notifications(self) -> undefined.UndefinedOr[guilds.GuildMes """Default message notification level that can be overwritten. If not overridden, this will use the Discord default level. - - Returns - ------- - hikari.undefined.UndefinedOr[hikari.guilds.GuildMessageNotificationsLevel] - The default message notification level, if overwritten. """ # noqa: D401 - Imperative mood @default_message_notifications.setter @@ -214,11 +203,6 @@ def explicit_content_filter_level(self) -> undefined.UndefinedOr[guilds.GuildExp """Explicit content filter level that can be overwritten. If not overridden, this will use the Discord default level. - - Returns - ------- - hikari.undefined.UndefinedOr[hikari.guilds.GuildExplicitContentFilterLevel] - The explicit content filter level, if overwritten. """ @explicit_content_filter_level.setter @@ -233,11 +217,6 @@ def icon(self) -> undefined.UndefinedOr[files.Resourceish]: """Guild icon to use that can be overwritten. If not overridden, the guild will not have an icon. - - Returns - ------- - hikari.undefined.UndefinedOr[hikari.files.Resourceish] - The guild icon to use, if overwritten. """ @icon.setter @@ -249,12 +228,7 @@ def icon(self, icon: undefined.UndefinedOr[files.Resourceish], /) -> None: def verification_level(self) -> undefined.UndefinedOr[typing.Union[guilds.GuildVerificationLevel, int]]: """Verification level required to join the guild that can be overwritten. - If not overridden, the guild will use the default verification level for - - Returns - ------- - hikari.undefined.UndefinedOr[typing.Union[hikari.guilds.GuildVerificationLevel, builtins.int]] - The verification level required to join the guild, if overwritten. + If not overridden, the guild will use the Discord default level. """ @verification_level.setter @@ -303,12 +277,12 @@ def add_role( ) -> snowflakes.Snowflake: """Create a role. - !!! warning + .. warning:: The first role you create must always be the `@everyone` role. Parameters ---------- - name : builtins.str + name : str The role's name. Other Parameters @@ -319,11 +293,11 @@ def add_role( If provided, the role's color. colour : hikari.undefined.UndefinedOr[hikari.colors.Colorish] An alias for `color`. - hoist : hikari.undefined.UndefinedOr[builtins.bool] + hoist : hikari.undefined.UndefinedOr[bool] If provided, whether to hoist the role. - mentionable : hikari.undefined.UndefinedOr[builtins.bool] + mentionable : hikari.undefined.UndefinedOr[bool] If provided, whether to make the role mentionable. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -338,9 +312,9 @@ def add_role( Raises ------ - builtins.ValueError + ValueError If you are defining the first role, but did not name it `@everyone`. - builtins.TypeError + TypeError If you specify both `color` and `colour` together or if you try to specify `color`, `colour`, `hoisted`, `mentionable` or `position` for the `@everyone` role. @@ -361,12 +335,12 @@ def add_category( Parameters ---------- - name : builtins.str + name : str The channels name. Must be between 2 and 1000 characters. Other Parameters ---------------- - position : hikari.undefined.UndefinedOr[builtins.int] + position : hikari.undefined.UndefinedOr[int] If provided, the position of the category. permission_overwrites : hikari.undefined.UndefinedOr[typing.Sequence[hikari.channels.PermissionOverwrite]] If provided, the permission overwrites for the category. @@ -379,7 +353,7 @@ def add_category( When the guild is created, this will be replaced with a different ID. - """ # noqa: E501 - Line too long + """ @abc.abstractmethod def add_text_channel( @@ -400,25 +374,25 @@ def add_text_channel( Parameters ---------- - name : builtins.str + name : str The channels name. Must be between 2 and 1000 characters. Other Parameters ---------------- - position : hikari.undefined.UndefinedOr[builtins.int] + position : hikari.undefined.UndefinedOr[int] If provided, the position of the channel (relative to the category, if any). - topic : hikari.undefined.UndefinedOr[builtins.str] + topic : hikari.undefined.UndefinedOr[str] If provided, the channels topic. Maximum 1024 characters. - nsfw : hikari.undefined.UndefinedOr[builtins.bool] + nsfw : hikari.undefined.UndefinedOr[bool] If provided, whether to mark the channel as NSFW. - rate_limit_per_user : hikari.undefined.UndefinedOr[builtins.int] + rate_limit_per_user : hikari.undefined.UndefinedOr[int] If provided, the amount of seconds a user has to wait before being able to send another message in the channel. Maximum 21600 seconds. permission_overwrites : hikari.undefined.UndefinedOr[typing.Sequence[hikari.channels.PermissionOverwrite]] If provided, the permission overwrites for the channel. - category : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildCategory]] + parent_id : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildCategory]] The category to create the channel under. This may be the object or the ID of an existing category. @@ -452,31 +426,31 @@ def add_voice_channel( Parameters ---------- - name : builtins.str + name : str The channels name. Must be between 2 and 1000 characters. Other Parameters ---------------- - position : hikari.undefined.UndefinedOr[builtins.int] + position : hikari.undefined.UndefinedOr[int] If provided, the position of the channel (relative to the category, if any). - user_limit : hikari.undefined.UndefinedOr[builtins.int] + user_limit : hikari.undefined.UndefinedOr[int] If provided, the maximum users in the channel at once. Must be between 0 and 99 with 0 meaning no limit. - bitrate : hikari.undefined.UndefinedOr[builtins.int] + bitrate : hikari.undefined.UndefinedOr[int] If provided, the bitrate for the channel. Must be between 8000 and 96000 or 8000 and 128000 for VIP servers. - video_quality_mode: hikari.undefined.UndefinedOr[typing.Union[hikari.channels.VideoQualityMode, builtins.int]] + video_quality_mode: hikari.undefined.UndefinedOr[typing.Union[hikari.channels.VideoQualityMode, int]] If provided, the new video quality mode for the channel. permission_overwrites : hikari.undefined.UndefinedOr[typing.Sequence[hikari.channels.PermissionOverwrite]] If provided, the permission overwrites for the channel. - region : hikari.undefined.UndefinedOr[typing.Union[hikari.voices.VoiceRegion, builtins.str]] + region : hikari.undefined.UndefinedOr[typing.Union[hikari.voices.VoiceRegion, str]] If provided, the voice region to for this channel. Passing - `builtins.None` here will set it to "auto" mode where the used + `None` here will set it to "auto" mode where the used region will be decided based on the first person who connects to it when it's empty. - category : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildCategory]] + parent_id : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildCategory]] The category to create the channel under. This may be the object or the ID of an existing category. @@ -509,26 +483,26 @@ def add_stage_channel( Parameters ---------- - name : builtins.str + name : str The channels name. Must be between 2 and 1000 characters. Other Parameters ---------------- - position : hikari.undefined.UndefinedOr[builtins.int] + position : hikari.undefined.UndefinedOr[int] If provided, the position of the channel (relative to the category, if any). - user_limit : hikari.undefined.UndefinedOr[builtins.int] + user_limit : hikari.undefined.UndefinedOr[int] If provided, the maximum users in the channel at once. Must be between 0 and 99 with 0 meaning no limit. - bitrate : hikari.undefined.UndefinedOr[builtins.int] + bitrate : hikari.undefined.UndefinedOr[int] If provided, the bitrate for the channel. Must be between 8000 and 96000 or 8000 and 128000 for VIP servers. permission_overwrites : hikari.undefined.UndefinedOr[typing.Sequence[hikari.channels.PermissionOverwrite]] If provided, the permission overwrites for the channel. - region : hikari.undefined.UndefinedOr[typing.Union[hikari.voices.VoiceRegion, builtins.str]] + region : hikari.undefined.UndefinedOr[typing.Union[hikari.voices.VoiceRegion, str]] If provided, the voice region to for this channel. Passing - `builtins.None` here will set it to "auto" mode where the used + `None` here will set it to "auto" mode where the used region will be decided based on the first person who connects to it when it's empty. category : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildCategory]] @@ -552,13 +526,7 @@ class InteractionResponseBuilder(abc.ABC): @property @abc.abstractmethod def type(self) -> typing.Union[int, base_interactions.ResponseType]: - """Return the type of this response. - - Returns - ------- - typing.Union[builtins.int, hikari.interactions.base_interactions.ResponseType] - The type of response this is. - """ + """Return the type of this response.""" @abc.abstractmethod def build(self, entity_factory: entity_factory_.EntityFactory, /) -> data_binding.JSONObject: @@ -584,40 +552,28 @@ class InteractionDeferredBuilder(InteractionResponseBuilder, abc.ABC): @property @abc.abstractmethod def type(self) -> base_interactions.DeferredResponseTypesT: - """Return the type of this response. - - Returns - ------- - hikari.interactions.base_interactions.DeferredResponseTypesT - The type of response this is. - """ + """Return the type of this response.""" @property @abc.abstractmethod def flags(self) -> typing.Union[undefined.UndefinedType, int, messages.MessageFlag]: """Message flags this response should have. - !!! note + .. note:: As of writing the only message flag which can be set here is `hikari.messages.MessageFlag.EPHEMERAL`. - - Returns - ------- - typing.Union[hikari.undefined.UndefinedType, builtins.int, hikari.messages.MessageFlag] - The message flags this response should have if set else - `hikari.undefined.UNDEFINED`. """ @abc.abstractmethod def set_flags(self: _T, flags: typing.Union[undefined.UndefinedType, int, messages.MessageFlag], /) -> _T: """Set message flags for this response. - !!! note + .. note:: As of writing, the only message flag which can be set is EPHEMERAL. Parameters ---------- - flags : typing.Union[hikari.undefined.UndefinedType, builtins.int, hikari.messages.MessageFlag] + flags : typing.Union[hikari.undefined.UndefinedType, int, hikari.messages.MessageFlag] The message flags to set for this response. Returns @@ -642,13 +598,7 @@ class InteractionMessageBuilder(InteractionResponseBuilder, abc.ABC): @property @abc.abstractmethod def type(self) -> base_interactions.MessageResponseTypesT: - """Return the type of this response. - - Returns - ------- - hikari.interactions.base_interactions.MessageResponseTypesT - The type of response this is. - """ + """Return the type of this response.""" # Extendable fields @@ -660,41 +610,23 @@ def components(self) -> typing.Sequence[ComponentBuilder]: @property @abc.abstractmethod def embeds(self) -> typing.Sequence[embeds_.Embed]: - """Sequence of up to 10 of the embeds included in this response. - - Returns - ------- - typing.Sequence[hikari.embeds.Embed] - A sequence of up to 10 of the embeds included in this response. - """ + """Sequence of up to 10 of the embeds included in this response.""" # Settable fields @property @abc.abstractmethod def content(self) -> undefined.UndefinedOr[str]: - """Response's message content. - - Returns - ------- - hikari.undefined.UndefinedOr[builtins.str] - The response's message content, if set. - """ + """Response's message content.""" @property @abc.abstractmethod def flags(self) -> typing.Union[undefined.UndefinedType, int, messages.MessageFlag]: """Message flags this response should have. - !!! note + .. note:: As of writing the only message flag which can be set here is `hikari.messages.MessageFlag.EPHEMERAL`. - - Returns - ------- - typing.Union[hikari.undefined.UndefinedType, builtins.int, hikari.messages.MessageFlag] - The message flags this response should have if set else - `hikari.undefined.UNDEFINED`. """ @property @@ -702,11 +634,7 @@ def flags(self) -> typing.Union[undefined.UndefinedType, int, messages.MessageFl def is_tts(self) -> undefined.UndefinedOr[bool]: """Whether this response's content should be treated as text-to-speech. - Returns - ------- - builtins.bool - Whether this response's content should be treated as text-to-speech. - If left as `hikari.undefined.UNDEFINED` then this will be disabled. + If left as `hikari.undefined.UNDEFINED` then this will be disabled. """ @property @@ -714,11 +642,7 @@ def is_tts(self) -> undefined.UndefinedOr[bool]: def mentions_everyone(self) -> undefined.UndefinedOr[bool]: """Whether @everyone and @here mentions should be enabled for this response. - Returns - ------- - hikari.undefined.UndefinedOr[builtins.bool] - Whether @everyone mentions should be enabled for this response. - If left as `hikari.undefined.UNDEFINED` then they will be disabled. + If left as `hikari.undefined.UNDEFINED` then they will be disabled. """ @property @@ -728,13 +652,10 @@ def role_mentions( ) -> undefined.UndefinedOr[typing.Union[snowflakes.SnowflakeishSequence[guilds.PartialRole], bool]]: """Whether and what role mentions should be enabled for this response. - Returns - ------- - hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], builtins.bool]] - Either a sequence of object/IDs of the roles mentions should be enabled for, - `builtins.False` or `hikari.undefined.UNDEFINED` to disallow any role - mentions or `True` to allow all role mentions. - """ # noqa: E501 - Line too long + Either a sequence of object/IDs of the roles mentions should be enabled + for, `False` or `hikari.undefined.UNDEFINED` to disallow any + role mentions or `True` to allow all role mentions. + """ @property @abc.abstractmethod @@ -743,13 +664,10 @@ def user_mentions( ) -> undefined.UndefinedOr[typing.Union[snowflakes.SnowflakeishSequence[users.PartialUser], bool]]: """Whether and what user mentions should be enabled for this response. - Returns - ------- - hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], builtins.bool]] - Either a sequence of object/IDs of the users mentions should be enabled for, - `builtins.False` or `hikari.undefined.UNDEFINED` to disallow any user - mentions or `True` to allow all user mentions. - """ # noqa: E501 - Line too long + Either a sequence of object/IDs of the users mentions should be enabled + for, `False` or `hikari.undefined.UNDEFINED` to disallow any + user mentions or `True` to allow all user mentions. + """ @abc.abstractmethod def add_component(self: _T, component: ComponentBuilder, /) -> _T: @@ -778,7 +696,7 @@ def add_embed(self: _T, embed: embeds_.Embed, /) -> _T: Returns ------- InteractionMessageBuilder - Object of this builder. + Object of this builder to allow for chained calls. """ @abc.abstractmethod @@ -787,31 +705,32 @@ def set_content(self: _T, content: undefined.UndefinedOr[str], /) -> _T: Parameters ---------- - content : hikari.undefined.UndefinedOr[builtins.str] + content : hikari.undefined.UndefinedOr[str] The message content to set for this response. Returns ------- InteractionMessageBuilder - Object of this builder. + Object of this builder to allow for chained calls. """ @abc.abstractmethod def set_flags(self: _T, flags: typing.Union[undefined.UndefinedType, int, messages.MessageFlag], /) -> _T: """Set message flags for this response. - !!! note - As of writing, the only message flag which can be set is EPHEMERAL. + .. note:: + As of writing, the only message flag which can be set is + `hikari.messages.MessageFlag.EPHEMERAL`.. Parameters ---------- - flags : typing.Union[hikari.undefined.UndefinedType, builtins.int, hikari.messages.MessageFlag] + flags : typing.Union[hikari.undefined.UndefinedType, int, hikari.messages.MessageFlag] The message flags to set for this response. Returns ------- InteractionMessageBuilder - Object of this builder. + Object of this builder to allow for chained calls. """ @abc.abstractmethod @@ -825,7 +744,7 @@ def set_tts(self: _T, tts: undefined.UndefinedOr[bool], /) -> _T: Returns ------- InteractionMessageBuilder - Object of this builder. + Object of this builder to allow for chained calls. """ @abc.abstractmethod @@ -834,13 +753,13 @@ def set_mentions_everyone(self: _T, mentions: undefined.UndefinedOr[bool] = unde Parameters ---------- - mentions : hikari.undefined.UndefinedOr[builtins.bool] + mentions : hikari.undefined.UndefinedOr[bool] Whether this response should be able to mention @everyone/@here. Returns ------- InteractionMessageBuilder - Object of this builder. + Object of this builder to allow for chained calls. """ @abc.abstractmethod @@ -855,15 +774,15 @@ def set_role_mentions( Parameters ---------- - mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], builtins.bool]] + mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], bool]] Either a sequence of object/IDs of the roles mentions should be enabled for, - `builtins.False` or `hikari.undefined.UNDEFINED` to disallow any role + `False` or `hikari.undefined.UNDEFINED` to disallow any role mentions or `True` to allow all role mentions. Returns ------- InteractionMessageBuilder - Object of this builder. + Object of this builder to allow for chained calls. """ # noqa: E501 - Line too long @abc.abstractmethod @@ -878,15 +797,15 @@ def set_user_mentions( Parameters ---------- - mentions: hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], builtins.bool]] + mentions: hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], bool]] Either a sequence of object/IDs of the users mentions should be enabled for, - `builtins.False` or `hikari.undefined.UNDEFINED` to disallow any user + `False` or `hikari.undefined.UNDEFINED` to disallow any user mentions or `True` to allow all user mentions. Returns ------- InteractionMessageBuilder - Object of this builder. + Object of this builder to allow for chained calls. """ # noqa: E501 - Line too long @@ -900,14 +819,9 @@ class CommandBuilder(abc.ABC): def name(self) -> str: r"""Name to set for this command. - !!! warning + .. warning:: This should match the regex `^[\w-]{1,32}$` in Unicode mode and must be lowercase. - - Returns - ------- - builtins.str - The name to set for this command. """ @property @@ -915,48 +829,26 @@ def name(self) -> str: def description(self) -> str: """Return the description to set for this command. - !!! warning + .. warning:: This should be inclusively between 1-100 characters in length. - - Returns - ------- - builtins.str - The description to set for this command. """ @property @abc.abstractmethod def options(self) -> typing.Sequence[commands.CommandOption]: - """Sequence of up to 25 of the options set for this command. - - Returns - ------- - typing.Sequence[hikari.commands.CommandOption] - A sequence of up to 25 of the options set for this command. - """ + """Sequence of up to 25 of the options set for this command.""" @property @abc.abstractmethod def id(self) -> undefined.UndefinedOr[snowflakes.Snowflake]: - """ID of this command. - - Returns - ------- - hikari.undefined.UndefinedOr[hikari.snowflakes.Snowflake] - The ID of this command if set. - """ + """ID of this command, if set.""" @property @abc.abstractmethod def default_permission(self) -> undefined.UndefinedOr[bool]: """Whether the command should be enabled by default (without any permissions). - Defaults to `builtins.bool`. - - Returns - ------- - undefined.UndefinedOr[builtins.bool] - Whether the command should be enabled by default (without any permissions). + Defaults to `bool`. """ @abc.abstractmethod @@ -971,7 +863,7 @@ def set_id(self: _T, id_: undefined.UndefinedOr[snowflakes.Snowflakeish], /) -> Returns ------- CommandBuilder - Object of this command builder. + Object of this command builder to allow for chained calls. """ @abc.abstractmethod @@ -980,20 +872,20 @@ def set_default_permission(self: _T, state: undefined.UndefinedOr[bool], /) -> _ Parameters ---------- - state : hikari.undefined.UndefinedOr[builtins.bool] + state : hikari.undefined.UndefinedOr[bool] Whether this command should be enabled by default. Returns ------- CommandBuilder - Object of this command builder for chained calls. + Object of this command builder to allow for chained calls. """ @abc.abstractmethod def add_option(self: _T, option: commands.CommandOption) -> _T: """Add an option to this command. - !!! note + .. note:: A command can have up to 25 options. Parameters @@ -1004,7 +896,7 @@ def add_option(self: _T, option: commands.CommandOption) -> _T: Returns ------- CommandBuilder - Object of this command builder. + Object of this command builder to allow for chained calls. """ @abc.abstractmethod @@ -1047,24 +939,14 @@ class ButtonBuilder(ComponentBuilder, abc.ABC, typing.Generic[_ContainerT]): @property @abc.abstractmethod def style(self) -> typing.Union[messages.ButtonStyle, int]: - """Button's style. - - Returns - ------- - typing.Union[builtins.int, hikari.messages.ButtonStyle] - The button's style. - """ + """Button's style.""" @property @abc.abstractmethod def emoji(self) -> typing.Union[snowflakes.Snowflakeish, emojis.Emoji, str, undefined.UndefinedType]: """Emoji which should appear on this button. - Returns - ------- - typing.Union[hikari.snowflakes.Snowflakeish, hikari.emojis.Emoji, builtins.str, hikari.undefined.UndefinedType] - Object or ID or raw string of the emoji which should be displayed - on this button if set. + This can be the object, ID or raw string of the emoji. """ @property @@ -1072,14 +954,9 @@ def emoji(self) -> typing.Union[snowflakes.Snowflakeish, emojis.Emoji, str, unde def label(self) -> undefined.UndefinedOr[str]: """Text label which should appear on this button. - !!! note + .. note:: The text label to that should appear on this button. This may be up to 80 characters long. - - Returns - ------- - hikari.undefined.UndefinedOr[builtins.str] - Text label which should appear on this button. """ @property @@ -1087,13 +964,8 @@ def label(self) -> undefined.UndefinedOr[str]: def is_disabled(self) -> bool: """Whether the button should be marked as disabled. - !!! note - Defaults to `builtins.False`. - - Returns - ------- - builtins.bool - Whether the button should be marked as disabled. + .. note:: + Defaults to `False`. """ @abc.abstractmethod @@ -1104,7 +976,7 @@ def set_emoji( Parameters ---------- - emoji : typing.Union[hikari.snowflakes.Snowflakeish, hikari.emojis.Emoji, builtins.str, hikari.undefined.UndefinedType] + emoji : typing.Union[hikari.snowflakes.Snowflakeish, hikari.emojis.Emoji, str, hikari.undefined.UndefinedType] Object, ID or raw string of the emoji which should be displayed on this button. @@ -1120,7 +992,7 @@ def set_label(self: _T, label: undefined.UndefinedOr[str], /) -> _T: Parameters ---------- - label : hikari.undefined.UndefinedOr[builtins.str] + label : hikari.undefined.UndefinedOr[str] The text label to show on this button. This may be up to 80 characters long. @@ -1165,13 +1037,7 @@ class LinkButtonBuilder(ButtonBuilder[_ContainerT], abc.ABC): @property @abc.abstractmethod def url(self) -> str: - """Url this button should link to when pressed. - - Returns - ------- - builtins.str - Url this button should link to when pressed. - """ + """URL this button should link to when pressed.""" class InteractiveButtonBuilder(ButtonBuilder[_ContainerT], abc.ABC): @@ -1180,13 +1046,7 @@ class InteractiveButtonBuilder(ButtonBuilder[_ContainerT], abc.ABC): @property @abc.abstractmethod def custom_id(self) -> str: - """Developer set custom ID used for identifying interactions with this button. - - Returns - ------- - builtins.str - Developer set custom ID used for identifying interactions with this button. - """ + """Developer set custom ID used for identifying interactions with this button.""" class SelectOptionBuilder(ComponentBuilder, abc.ABC, typing.Generic[_SelectMenuBuilderT]): @@ -1197,46 +1057,24 @@ class SelectOptionBuilder(ComponentBuilder, abc.ABC, typing.Generic[_SelectMenuB @property @abc.abstractmethod def label(self) -> str: - """User-facing name of the option, max 100 characters. - - Returns - ------- - builtins.str - User-facing name of the option. - """ + """User-facing name of the option, max 100 characters.""" @property @abc.abstractmethod def value(self) -> str: - """Developer-defined value of the option, max 100 characters. - - Returns - ------- - builtins.str - Developer-defined value of the option. - """ + """Developer-defined value of the option, max 100 characters.""" @property @abc.abstractmethod def description(self) -> undefined.UndefinedOr[str]: - """Return the description of the option, max 100 characters. - - Returns - ------- - hikari.undefined.UndefinedOr[builtins.str] - Description of the option, if set. - """ + """Return the description of the option, max 100 characters.""" @property @abc.abstractmethod def emoji(self) -> typing.Union[snowflakes.Snowflakeish, emojis.Emoji, str, undefined.UndefinedType]: """Emoji which should appear on this option. - Returns - ------- - typing.Union[hikari.snowflakes.Snowflakeish, hikari.emojis.Emoji, builtins.str, hikari.undefined.UndefinedType] - Object or ID or raw string of the emoji which should be displayed - on this option if set. + This can be the object, ID or raw string of the emoji. """ @property @@ -1244,12 +1082,7 @@ def emoji(self) -> typing.Union[snowflakes.Snowflakeish, emojis.Emoji, str, unde def is_default(self) -> bool: """Whether this option should be marked as selected by default. - Defaults to `builtins.False`. - - Returns - ------- - builtins.bool - Whether this option should be marked as selected by default. + Defaults to `False`. """ @abc.abstractmethod @@ -1258,7 +1091,7 @@ def set_description(self: _T, value: undefined.UndefinedOr[str], /) -> _T: Parameters ---------- - value : hikari.undefined.UndefinedOr[builtins.str] + value : hikari.undefined.UndefinedOr[str] Description to set for this option. This can be up to 100 characters long. @@ -1276,7 +1109,7 @@ def set_emoji( Parameters ---------- - emoji : typing.Union[hikari.snowflakes.Snowflakeish, hikari.emojis.Emoji, builtins.str, hikari.undefined.UndefinedType] + emoji : typing.Union[hikari.snowflakes.Snowflakeish, hikari.emojis.Emoji, str, hikari.undefined.UndefinedType] Object, ID or raw string of the emoji which should be displayed on this option. @@ -1292,7 +1125,7 @@ def set_is_default(self: _T, state: bool, /) -> _T: Parameters ---------- - state : builtins.bool + state : bool Whether this option should be selected by default. Returns @@ -1322,49 +1155,26 @@ class SelectMenuBuilder(ComponentBuilder, abc.ABC, typing.Generic[_ContainerT]): @property @abc.abstractmethod def custom_id(self) -> str: - """Developer set custom ID used for identifying interactions with this menu. - - Returns - ------- - builtins.str - Developer set custom ID used for identifying interactions with this menu. - """ + """Developer set custom ID used for identifying interactions with this menu.""" @property @abc.abstractmethod def is_disabled(self) -> bool: """Whether the select menu should be marked as disabled. - !!! note - Defaults to `builtins.False`. - - Returns - ------- - builtins.bool - Whether the select menu should be marked as disabled. + .. note:: + Defaults to `False`. """ @property @abc.abstractmethod def options(self: _SelectMenuBuilderT) -> typing.Sequence[SelectOptionBuilder[_SelectMenuBuilderT]]: - """Sequence of the options set for this select menu. - - Returns - ------- - typing.Sequence[SelectOptionBuilder[Self]] - Sequence of the options set for this select menu. - """ + """Sequence of the options set for this select menu.""" @property @abc.abstractmethod def placeholder(self) -> undefined.UndefinedOr[str]: - """Return the placeholder text to display when no options are selected. - - Returns - ------- - hikari.undefined.UndefinedOr[builtins.str] - Placeholder text to display when no options are selected, if defined. - """ + """Return the placeholder text to display when no options are selected.""" @property @abc.abstractmethod @@ -1374,11 +1184,6 @@ def min_values(self) -> int: Defaults to 1. Must be less than or equal to `SelectMenuBuilder.max_values` and greater than or equal to 0. - - Returns - ------- - builtins.str - Minimum number of options which must be chosen. """ @property @@ -1389,40 +1194,38 @@ def max_values(self) -> int: Defaults to 1. Must be greater than or equal to `SelectMenuBuilder.min_values` and less than or equal to 25. - - Returns - ------- - builtins.str - Maximum number of options which can be chosen. """ @abc.abstractmethod def add_option(self: _SelectMenuBuilderT, label: str, value: str, /) -> SelectOptionBuilder[_SelectMenuBuilderT]: """Add an option to this menu. + .. note:: + Setup should be finalised by calling `add_to_menu` in the builder + returned. + Parameters ---------- - label : builtins.str + label : str The user-facing name of this option, max 100 characters. - value : builtins.str + value : str The developer defined value of this option, max 100 characters. Returns ------- - SelectOptionBuilder[Self] + SelectOptionBuilder[SelectMenuBuilder] Option builder object. - This should be finalised by calling `SelectOptionBuilder.add_to_menu`. """ @abc.abstractmethod def set_is_disabled(self: _T, state: bool, /) -> _T: """Set whether this option is disabled. - Defaults to `builtins.False`. + Defaults to `False`. Parameters ---------- - state : builtins.bool + state : bool Whether this option is disabled. Returns @@ -1437,7 +1240,7 @@ def set_placeholder(self: _T, value: undefined.UndefinedOr[str], /) -> _T: Parameters ---------- - value : hikari.undefined.UndefinedOr[builtins.str] + value : hikari.undefined.UndefinedOr[str] Place-holder text to be displayed when no option is selected. Max 100 characters. @@ -1451,13 +1254,13 @@ def set_placeholder(self: _T, value: undefined.UndefinedOr[str], /) -> _T: def set_min_values(self: _T, value: int, /) -> _T: """Set the minimum amount of options which need to be selected for this menu. - !!! note + .. note:: This defaults to 1 if not set and must be greater than or equal to 0 and less than or equal to `SelectMenuBuilder.max_values`. Parameters ---------- - value : builtins.int + value : int The minimum amount of options which need to be selected for this menu. Returns @@ -1470,13 +1273,13 @@ def set_min_values(self: _T, value: int, /) -> _T: def set_max_values(self: _T, value: int, /) -> _T: """Set the maximum amount of options which can be selected for this menu. - !!! note + .. note:: This defaults to 1 if not set and must be less than or equal to 25 and greater than or equal to `SelectMenuBuilder.min_values`. Parameters ---------- - value : builtins.int + value : int The maximum amount of options which can selected for this menu. Returns @@ -1504,13 +1307,7 @@ class ActionRowBuilder(ComponentBuilder, abc.ABC): @property @abc.abstractmethod def components(self) -> typing.Sequence[ComponentBuilder]: - """Sequence of the component builders registered within this action row. - - Returns - ------- - typing.Sequence[ComponentBuilder] - Sequence of the component builders registered within this action row. - """ + """Sequence of the component builders registered within this action row.""" @abc.abstractmethod def add_component( @@ -1520,7 +1317,7 @@ def add_component( ) -> _T: """Add a component to this action row builder. - !!! warning + .. warning:: It is generally better to use `ActionRowBuilder.add_button` and `ActionRowBuilder.add_select_menu` to add your component to the builder. Those methods utilize this one. @@ -1558,9 +1355,9 @@ def add_button( Parameters ---------- - style : typing.Union[builtins.int, hikari.messages.ButtonStyle] + style : typing.Union[int, hikari.messages.ButtonStyle] The button's style. - url_or_custom_id : builtins.str + url_or_custom_id : str For interactive button styles this is a developer-defined custom identifier used to identify which button triggered component interactions. @@ -1581,7 +1378,7 @@ def add_select_menu(self: _T, custom_id: str, /) -> SelectMenuBuilder[_T]: Parameters ---------- - custom_id : builtins.str + custom_id : str A developer-defined custom identifier used to identify which menu triggered component interactions. diff --git a/hikari/api/voice.py b/hikari/api/voice.py index ccd0aee9fe..25f0720a32 100644 --- a/hikari/api/voice.py +++ b/hikari/api/voice.py @@ -35,7 +35,9 @@ from hikari import guilds from hikari import snowflakes -_VoiceConnectionT = typing.TypeVar("_VoiceConnectionT", bound="VoiceConnection") + _T = typing.TypeVar("_T") + + _VoiceConnectionT = typing.TypeVar("_VoiceConnectionT", bound="VoiceConnection") class VoiceComponent(abc.ABC): @@ -99,12 +101,12 @@ async def connect_to( voice_connection_type : typing.Type[VoiceConnection] The type of voice connection to use. This should be initialized internally using the `VoiceConnection.initialize` - `builtins.classmethod`. - deaf : builtins.bool - Defaulting to `builtins.False`, if `builtins.True`, the client will + `classmethod`. + deaf : bool + Defaulting to `False`, if `True`, the client will enter the voice channel deafened (thus unable to hear other users). - mute : builtins.bool - Defaulting to `builtins.False`, if `builtins.True`, the client will + mute : bool + Defaulting to `False`, if `True`, the client will enter the voice channel muted (thus unable to send audio). **kwargs : typing.Any Any arguments to provide to the `VoiceConnection.initialize` @@ -135,9 +137,6 @@ class VoiceConnection(abc.ABC): __slots__: typing.Sequence[str] = () - if typing.TYPE_CHECKING: - _T = typing.TypeVar("_T") - @classmethod @abc.abstractmethod async def initialize( @@ -171,12 +170,12 @@ async def initialize( connection is unregistered from the voice component safely. owner : VoiceComponent The component that made this connection object. - session_id : builtins.str + session_id : str The voice session ID to use. - shard_id : builtins.int + shard_id : int The associated shard ID that the voice connection was generated from. - token : builtins.str + token : str The voice token to use. user_id : hikari.snowflakes.Snowflake The user ID of the account that just joined the voice channel. @@ -193,22 +192,22 @@ async def initialize( @property @abc.abstractmethod def channel_id(self) -> snowflakes.Snowflake: - """Return the ID of the voice channel this voice connection is in.""" + """ID of the voice channel this voice connection is in.""" @property @abc.abstractmethod def guild_id(self) -> snowflakes.Snowflake: - """Return the ID of the guild this voice connection is in.""" + """ID of the guild this voice connection is in.""" @property @abc.abstractmethod def is_alive(self) -> bool: - """Return `builtins.True` if the connection is alive.""" + """Whether the connection is alive.""" @property @abc.abstractmethod def shard_id(self) -> int: - """Return the ID of the shard that requested the connection.""" + """ID of the shard that requested the connection.""" @property @abc.abstractmethod diff --git a/hikari/applications.py b/hikari/applications.py index 6566d3445c..0b7862f0b4 100644 --- a/hikari/applications.py +++ b/hikari/applications.py @@ -102,31 +102,31 @@ class OAuth2Scope(str, enums.Enum): ACTIVITIES_READ = "activities.read" """Enables fetching the "Now Playing/Recently Played" list. - !!! note + .. note:: You must be whitelisted to use this scope. """ ACTIVITIES_WRITE = "activities.write" """Enables updating a user's activity. - !!! note + .. note:: You must be whitelisted to use this scope. - !!! note + .. note:: This is not required to use the GameSDK activity manager. """ APPLICATIONS_BUILDS_READ = "applications.builds.read" """Enables reading build data for a user's applications. - !!! note + .. note:: You must be whitelisted to use this scope. """ APPLICATIONS_BUILDS_UPLOAD = "applications.builds.upload" """Enables uploading/updating builds for a user's applications. - !!! note + .. note:: You must be whitelisted to use this scope. """ @@ -149,14 +149,14 @@ class OAuth2Scope(str, enums.Enum): This includes store listings, achievements, SKU's, etc. - !!! note + .. note:: The store API is deprecated and may be removed in the future. """ BOT = "bot" """Enables adding a bot application to a guild. - !!! note + .. note:: This requires you to have set up a bot account for your application. """ @@ -169,7 +169,7 @@ class OAuth2Scope(str, enums.Enum): GROUP_DM_JOIN = "gdm.join" """Enables joining users into a group DM. - !!! warning + .. warning:: This cannot add the bot to a group DM. """ @@ -179,14 +179,14 @@ class OAuth2Scope(str, enums.Enum): GUILDS_JOIN = "guilds.join" """Enables adding the user to a specific guild. - !!! note + .. note:: This requires you to have set up a bot account for your application. """ IDENTIFY = "identify" """Enables viewing info about itself. - !!! note + .. note:: This does not include email address info. Use the `EMAIL` scope instead to retrieve this information. """ @@ -194,14 +194,14 @@ class OAuth2Scope(str, enums.Enum): RELATIONSHIPS_READ = "relationships.read" """Enables viewing a user's friend list. - !!! note + .. note:: You must be whitelisted to use this scope. """ RPC = "rpc" """Enables the RPC application to control the local user's Discord client. - !!! note + .. note:: You must be whitelisted to use this scope. """ @@ -211,7 +211,7 @@ class OAuth2Scope(str, enums.Enum): RPC_NOTIFICATIONS_READ = "rpc.notifications.read" """Enables the RPC application to read from all channels the user is in. - !!! note + .. note:: You must be whitelisted to use this scope. """ @@ -244,7 +244,7 @@ class OwnConnection: id: str = attr.field(hash=True, repr=True) """The string ID of the third party connected account. - !!! warning + .. warning:: Seeing as this is a third party ID, it will not be a snowflakes. """ @@ -255,19 +255,19 @@ class OwnConnection: """The type of service this connection is for.""" is_revoked: bool = attr.field(eq=False, hash=False, repr=False) - """`builtins.True` if the connection has been revoked.""" + """`True` if the connection has been revoked.""" integrations: typing.Sequence[guilds.PartialIntegration] = attr.field(eq=False, hash=False, repr=False) """A sequence of the partial guild integration objects this connection has.""" is_verified: bool = attr.field(eq=False, hash=False, repr=False) - """`builtins.True` if the connection has been verified.""" + """`True` if the connection has been verified.""" is_friend_sync_enabled: bool = attr.field(eq=False, hash=False, repr=False) - """`builtins.True` if friends should be added based on this connection.""" + """`True` if friends should be added based on this connection.""" is_activity_visible: bool = attr.field(eq=False, hash=False, repr=False) - """`builtins.True` if this connection's activities are shown in the user's presence.""" + """`True` if this connection's activities are shown in the user's presence.""" visibility: typing.Union[ConnectionVisibility, int] = attr.field(eq=False, hash=False, repr=True) """The visibility of the connection.""" @@ -281,7 +281,7 @@ class OwnGuild(guilds.PartialGuild): """A list of the features in this guild.""" is_owner: bool = attr.field(eq=False, hash=False, repr=True) - """`builtins.True` when the current user owns this guild.""" + """`True` when the current user owns this guild.""" my_permissions: permissions_.Permissions = attr.field(eq=False, hash=False, repr=False) """The guild-level permissions that apply to the current user or bot.""" @@ -309,7 +309,7 @@ class TeamMember(users.User): permissions: typing.Sequence[str] = attr.field(repr=False) """This member's permissions within a team. - At the time of writing, this will always be a sequence of one `builtins.str`, + At the time of writing, this will always be a sequence of one `str`, which will always be `"*"`. This may change in the future, however. """ @@ -405,7 +405,7 @@ class Team(snowflakes.Unique): icon_hash: typing.Optional[str] = attr.field(eq=False, hash=False, repr=False) """The CDN hash of this team's icon. - If no icon is provided, this will be `builtins.None`. + If no icon is provided, this will be `None`. """ members: typing.Mapping[snowflakes.Snowflake, TeamMember] = attr.field(eq=False, hash=False, repr=False) @@ -423,13 +423,7 @@ def __str__(self) -> str: @property def icon_url(self) -> typing.Optional[files.URL]: - """Team icon URL. - - Returns - ------- - typing.Optional[hikari.files.URL] - The URL, or `builtins.None` if no icon exists. - """ + """Icon URL, or `None` if no icon exists.""" return self.make_icon_url() def make_icon_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optional[files.URL]: @@ -437,21 +431,21 @@ def make_icon_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optiona Parameters ---------- - ext : builtins.str + ext : str The extension to use for this URL, defaults to `png`. Supports `png`, `jpeg`, `jpg` and `webp`. - size : builtins.int + size : int The size to set for the URL, defaults to `4096`. Can be any power of two between `16` and `4096` inclusive. Returns ------- typing.Optional[hikari.files.URL] - The URL, or `builtins.None` if no icon exists. + The URL, or `None` if no icon exists. Raises ------ - builtins.ValueError + ValueError If the size is not an integer power of 2 between 16 and 4096 (inclusive). """ @@ -485,13 +479,7 @@ class InviteApplication(guilds.PartialApplication): @property def cover_image_url(self) -> typing.Optional[files.URL]: - """Cover image URL used in the store or for embedded games. - - Returns - ------- - typing.Optional[hikari.files.URL] - The URL, or `builtins.None` if no cover image exists. - """ + """Cover image URL, or `None` if no cover image exists.""" return self.make_cover_image_url() def make_cover_image_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optional[files.URL]: @@ -499,21 +487,21 @@ def make_cover_image_url(self, *, ext: str = "png", size: int = 4096) -> typing. Parameters ---------- - ext : builtins.str + ext : str The extension to use for this URL, defaults to `png`. Supports `png`, `jpeg`, `jpg` and `webp`. - size : builtins.int + size : int The size to set for the URL, defaults to `4096`. Can be any power of two between 16 and 4096. Returns ------- typing.Optional[hikari.files.URL] - The URL, or `builtins.None` if no cover image exists. + The URL, or `None` if no cover image exists. Raises ------ - builtins.ValueError + ValueError If the size is not an integer power of 2 between 16 and 4096 (inclusive). """ @@ -540,10 +528,10 @@ class Application(guilds.PartialApplication): """The client application that models may use for procedures.""" is_bot_public: bool = attr.field(eq=False, hash=False, repr=True) - """`builtins.True` if the bot associated with this application is public.""" + """`True` if the bot associated with this application is public.""" is_bot_code_grant_required: bool = attr.field(eq=False, hash=False, repr=False) - """`builtins.True` if this application's bot is requiring code grant for invites.""" + """`True` if this application's bot is requiring code grant for invites.""" owner: users.User = attr.field(eq=False, hash=False, repr=True) """The application's owner.""" @@ -560,7 +548,7 @@ class Application(guilds.PartialApplication): team: typing.Optional[Team] = attr.field(eq=False, hash=False, repr=False) """The team this application belongs to. - If the application is not part of a team, this will be `builtins.None`. + If the application is not part of a team, this will be `None`. """ guild_id: typing.Optional[snowflakes.Snowflake] = attr.field(eq=False, hash=False, repr=False) @@ -586,13 +574,7 @@ class Application(guilds.PartialApplication): @property def cover_image_url(self) -> typing.Optional[files.URL]: - """Cover image URL used in the store or for embedded games. - - Returns - ------- - typing.Optional[hikari.files.URL] - The URL, or `builtins.None` if no cover image exists. - """ + """Cover image URL, or `None` if no cover image exists.""" return self.make_cover_image_url() def make_cover_image_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optional[files.URL]: @@ -600,21 +582,21 @@ def make_cover_image_url(self, *, ext: str = "png", size: int = 4096) -> typing. Parameters ---------- - ext : builtins.str + ext : str The extension to use for this URL, defaults to `png`. Supports `png`, `jpeg`, `jpg` and `webp`. - size : builtins.int + size : int The size to set for the URL, defaults to `4096`. Can be any power of two between 16 and 4096. Returns ------- typing.Optional[hikari.files.URL] - The URL, or `builtins.None` if no cover image exists. + The URL, or `None` if no cover image exists. Raises ------ - builtins.ValueError + ValueError If the size is not an integer power of 2 between 16 and 4096 (inclusive). """ @@ -635,7 +617,7 @@ async def fetch_guild(self) -> typing.Optional[guilds.RESTGuild]: Returns ------- typing.Optional[hikari.guilds.RESTGuild] - The requested guild if the application is linked to a guild, else `builtins.None`. + The requested guild if the application is linked to a guild, else `None`. Raises ------ @@ -669,9 +651,9 @@ async def fetch_guild_preview(self) -> typing.Optional[guilds.GuildPreview]: Returns ------- typing.Optional[hikari.guilds.GuildPreview] - The requested guild preview if the application is linked to a guild, else `builtins.None`. + The requested guild preview if the application is linked to a guild, else `None`. - !!! note + .. note:: This will only work if you are a part of that guild or it is public. Raises @@ -709,15 +691,15 @@ class AuthorizationApplication(guilds.PartialApplication): """The key used for verifying interaction and GameSDK payload signatures.""" is_bot_public: typing.Optional[bool] = attr.field(eq=False, hash=False, repr=True) - """`builtins.True` if the bot associated with this application is public. + """`True` if the bot associated with this application is public. - Will be `builtins.None` if this application doesn't have an associated bot. + Will be `None` if this application doesn't have an associated bot. """ is_bot_code_grant_required: typing.Optional[bool] = attr.field(eq=False, hash=False, repr=False) - """`builtins.True` if this application's bot is requiring code grant for invites. + """`True` if this application's bot is requiring code grant for invites. - Will be `builtins.None` if this application doesn't have a bot. + Will be `None` if this application doesn't have a bot. """ terms_of_service_url: typing.Optional[str] = attr.field(eq=False, hash=False, repr=False) @@ -782,14 +764,14 @@ class OAuth2AuthorizationToken(PartialOAuth2Token): """Object of the webhook that was created. This will only be present if this token was authorized with the - `webhooks.incoming` scope, otherwise this will be `builtins.None`. + `webhooks.incoming` scope, otherwise this will be `None`. """ guild: typing.Optional[guilds.RESTGuild] = attr.field(eq=False, hash=False, repr=True) """Object of the guild the user was added to. This will only be present if this token was authorized with the - `bot` scope, otherwise this will be `builtins.None`. + `bot` scope, otherwise this will be `None`. """ @@ -826,7 +808,7 @@ def get_token_id(token: str) -> snowflakes.Snowflake: Raises ------ - builtins.ValueError + ValueError If the passed token has an unexpected format. """ try: diff --git a/hikari/channels.py b/hikari/channels.py index 5b44684184..5362bc95ed 100644 --- a/hikari/channels.py +++ b/hikari/channels.py @@ -227,16 +227,16 @@ async def fetch_webhook(self) -> webhooks.ChannelFollowerWebhook: def get_channel(self) -> typing.Union[GuildNewsChannel, GuildTextChannel, None]: """Get the channel being followed from the cache. - !!! warning - This will always be `builtins.None` if you are not + .. warning:: + This will always be `None` if you are not in the guild that this channel exists in. Returns ------- - typing.Union[hikari.channels.GuildNewsChannel, hikari.channels.GuildTextChannel, builtins.None] + typing.Union[hikari.channels.GuildNewsChannel, hikari.channels.GuildTextChannel, None] The object of the guild channel that was found in the cache or - `builtins.None`. While this will usually be `GuildNewsChannel` or - `builtins.None`, if the channel referenced has since lost it's news + `None`. While this will usually be `GuildNewsChannel` or + `None`, if the channel referenced has since lost it's news status then this will return a `GuildTextChannel`. """ if not isinstance(self.app, traits.CacheAware): @@ -364,7 +364,7 @@ async def delete(self) -> PartialChannel: hikari.errors.InternalServerError If an internal error occurs on Discord while handling the request. - !!! note + .. note:: For Public servers, the set 'Rules' or 'Guidelines' channels and the 'Public Server Updates' channel cannot be deleted. """ @@ -387,6 +387,12 @@ def fetch_history( ) -> iterators.LazyIterator[messages.Message]: """Browse the message history for a given text channel. + .. note:: + This call is not a coroutine function, it returns a special type of + lazy iterator that will perform API calls as you iterate across it, + thus any errors documented below will happen then. + See `hikari.iterators` for the full API for this iterator type. + Other Parameters ---------------- before : hikari.undefined.UndefinedOr[snowflakes.SearchableSnowflakeishOr[hikari.snowflakes.Unique]] @@ -412,7 +418,7 @@ def fetch_history( Raises ------ - builtins.TypeError + TypeError If you specify more than one of `before`, `after`, `about`. hikari.errors.UnauthorizedError If you are unauthorized to make the request (invalid/missing token). @@ -423,13 +429,7 @@ def fetch_history( If the channel is not found. hikari.errors.InternalServerError If an internal error occurs on Discord while handling the request. - - !!! note - The exceptions on this endpoint (other than `builtins.TypeError`) will only - be raised once the result is awaited or interacted with. Invoking - this function itself will not raise anything (other than - `builtins.TypeError`). - """ # noqa: E501 - Line too long + """ return self.app.rest.fetch_messages(self.id, before=before, after=after, around=around) async def fetch_message(self, message: snowflakes.SnowflakeishOr[messages.PartialMessage]) -> messages.Message: @@ -501,7 +501,7 @@ async def send( If provided, the message contents. If `hikari.undefined.UNDEFINED`, then nothing will be sent in the content. Any other value here will be cast to a - `builtins.str`. + `str`. If this is a `hikari.embeds.Embed` and no `embed` nor `embeds` kwarg is provided, then this will instead update the embed. This allows @@ -516,6 +516,32 @@ async def send( attachment : hikari.undefined.UndefinedOr[hikari.files.Resourceish], If provided, the message attachment. This can be a resource, or string of a path on your computer or a URL. + + Attachments can be passed as many different things, to aid in + convenience. + + - If a `pathlib.PurePath` or `str` to a valid URL, the + resource at the given URL will be streamed to Discord when + sending the message. Subclasses of + `hikari.files.WebResource` such as + `hikari.files.URL`, + `hikari.messages.Attachment`, + `hikari.emojis.Emoji`, + `EmbedResource`, etc will also be uploaded this way. + This will use bit-inception, so only a small percentage of the + resource will remain in memory at any one time, thus aiding in + scalability. + - If a `hikari.files.Bytes` is passed, or a `str` + that contains a valid data URI is passed, then this is uploaded + with a randomized file name if not provided. + - If a `hikari.files.File`, `pathlib.PurePath` or + `str` that is an absolute or relative path to a file + on your file system is passed, then this resource is uploaded + as an attachment using non-blocking code internally and streamed + using bit-inception where possible. This depends on the + type of `concurrent.futures.Executor` that is being used for + the application (default is a thread pool which supports this + behaviour). attachments : hikari.undefined.UndefinedOr[typing.Sequence[hikari.files.Resourceish]], If provided, the message attachments. These can be resources, or strings consisting of paths on your computer or URLs. @@ -528,63 +554,36 @@ async def send( If provided, the message embed. embeds : hikari.undefined.UndefinedOr[typing.Sequence[hikari.embeds.Embed]] If provided, the message embeds. - tts : hikari.undefined.UndefinedOr[builtins.bool] + tts : hikari.undefined.UndefinedOr[bool] If provided, whether the message will be TTS (Text To Speech). - nonce : hikari.undefined.UndefinedOr[builtins.str] + nonce : hikari.undefined.UndefinedOr[str] If provided, a nonce that can be used for optimistic message sending. reply : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage]] If provided, the message to reply to. - mentions_everyone : hikari.undefined.UndefinedOr[builtins.bool] + mentions_everyone : hikari.undefined.UndefinedOr[bool] If provided, whether the message should parse @everyone/@here mentions. - mentions_reply : hikari.undefined.UndefinedOr[builtins.bool] + mentions_reply : hikari.undefined.UndefinedOr[bool] If provided, whether to mention the author of the message that is being replied to. This will not do anything if not being used with `reply`. - user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], builtins.bool]] - If provided, and `builtins.True`, all mentions will be parsed. - If provided, and `builtins.False`, no mentions will be parsed. + user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], bool]] + If provided, and `True`, all mentions will be parsed. + If provided, and `False`, no mentions will be parsed. Alternatively this may be a collection of `hikari.snowflakes.Snowflake`, or `hikari.users.PartialUser` derivatives to enforce mentioning specific users. - role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], builtins.bool]] - If provided, and `builtins.True`, all mentions will be parsed. - If provided, and `builtins.False`, no mentions will be parsed. + role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], bool]] + If provided, and `True`, all mentions will be parsed. + If provided, and `False`, no mentions will be parsed. Alternatively this may be a collection of `hikari.snowflakes.Snowflake`, or `hikari.guilds.PartialRole` derivatives to enforce mentioning specific roles. - !!! note - Attachments can be passed as many different things, to aid in - convenience. - - - If a `pathlib.PurePath` or `builtins.str` to a valid URL, the - resource at the given URL will be streamed to Discord when - sending the message. Subclasses of - `hikari.files.WebResource` such as - `hikari.files.URL`, - `hikari.messages.Attachment`, - `hikari.emojis.Emoji`, - `EmbedResource`, etc will also be uploaded this way. - This will use bit-inception, so only a small percentage of the - resource will remain in memory at any one time, thus aiding in - scalability. - - If a `hikari.files.Bytes` is passed, or a `builtins.str` - that contains a valid data URI is passed, then this is uploaded - with a randomized file name if not provided. - - If a `hikari.files.File`, `pathlib.PurePath` or - `builtins.str` that is an absolute or relative path to a file - on your file system is passed, then this resource is uploaded - as an attachment using non-blocking code internally and streamed - using bit-inception where possible. This depends on the - type of `concurrent.futures.Executor` that is being used for - the application (default is a thread pool which supports this - behaviour). - Returns ------- hikari.messages.Message @@ -607,10 +606,10 @@ async def send( If the channel is not found. hikari.errors.InternalServerError If an internal error occurs on Discord while handling the request. - builtins.ValueError + ValueError If more than 100 unique objects/entities are passed for `role_mentions` or `user_mentions`. - builtins.TypeError + TypeError If both `attachment` and `attachments` are specified. """ # noqa: E501 - Line too long return await self.app.rest.create_message( @@ -645,7 +644,7 @@ def trigger_typing(self) -> special_endpoints.TypingIndicator: await asyncio.sleep(35) # keep typing until this finishes ``` - !!! note + .. note:: Sending a message to this channel will stop the typing indicator. If using an `async with`, it will start up again after a few seconds. This is a limitation of Discord's API. @@ -768,18 +767,7 @@ async def delete_messages( ) -> None: """Bulk-delete messages from the channel. - Parameters - ---------- - messages : typing.Union[hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage], hikari.snowflakes.SnowflakeishIterable[hikari.messages.PartialMessage]] - Either the object/ID of an existing message to delete or an iterable - of the objects and/or IDs of existing messages to delete. - - Other Parameters - ---------------- - *other_messages : hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage] - The objects and/or IDs of other existing messages to delete. - - !!! note + .. note:: This API endpoint will only be able to delete 100 messages at a time. For anything more than this, multiple requests will be executed one-after-the-other, since the rate limits for this @@ -791,21 +779,32 @@ async def delete_messages( of this is that the `delete_message` endpoint is ratelimited by a different bucket with different usage rates. - !!! warning + .. warning:: This endpoint is not atomic. If an error occurs midway through a bulk delete, you will **not** be able to revert any changes made up to this point. - !!! warning + .. warning:: Specifying any messages more than 14 days old will cause the call to fail, potentially with partial completion. + Parameters + ---------- + messages : typing.Union[hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage], hikari.snowflakes.SnowflakeishIterable[hikari.messages.PartialMessage]] + Either the object/ID of an existing message to delete or an iterable + of the objects and/or IDs of existing messages to delete. + + Other Parameters + ---------------- + *other_messages : hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage] + The objects and/or IDs of other existing messages to delete. + Raises ------ hikari.errors.BulkDeleteError An error containing the messages successfully deleted, and the messages that were not removed. The - `builtins.BaseException.__cause__` of the exception will be the + `BaseException.__cause__` of the exception will be the original error that terminated this process. """ # noqa: E501 - Line too long return await self.app.rest.delete_messages(self.id, messages, *other_messages) @@ -818,7 +817,7 @@ class PrivateChannel(PartialChannel): last_message_id: typing.Optional[snowflakes.Snowflake] = attr.field(eq=False, hash=False, repr=False) """The ID of the last message sent in this channel. - !!! warning + .. warning:: This might point to an invalid or deleted message. Do not assume that this will always be valid. """ @@ -844,7 +843,7 @@ def __str__(self) -> str: class GroupDMChannel(PrivateChannel): """Represents a group direct message channel. - !!! note + .. note:: This doesn't have the methods found on `TextableChannel` as bots cannot interact with a group DM that they own by sending or seeing messages in it. @@ -865,7 +864,7 @@ class GroupDMChannel(PrivateChannel): application_id: typing.Optional[snowflakes.Snowflake] = attr.field(eq=False, hash=False, repr=False) """The ID of the application that created the group DM. - If the group DM was not created by a bot, this will be `builtins.None`. + If the group DM was not created by a bot, this will be `None`. """ def __str__(self) -> str: @@ -884,21 +883,21 @@ def make_icon_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optiona Parameters ---------- - ext : builtins.str + ext : str The extension to use for this URL, defaults to `png`. Supports `png`, `jpeg`, `jpg` and `webp`. - size : builtins.int + size : int The size to set for the URL, defaults to `4096`. Can be any power of two between 16 and 4096. Returns ------- typing.Optional[hikari.files.URL] - The URL, or `builtins.None` if no icon is present. + The URL, or `None` if no icon is present. Raises ------ - builtins.ValueError + ValueError If `size` is not a power of two between 16 and 4096 (inclusive). """ if self.icon_hash is None: @@ -937,29 +936,25 @@ class GuildChannel(PartialChannel): is_nsfw: typing.Optional[bool] = attr.field(eq=False, hash=False, repr=False) """Whether the channel is marked as NSFW. - !!! warning - This will be `builtins.None` when received over the gateway in certain events + .. warning:: + This will be `None` when received over the gateway in certain events (e.g Guild Create). """ parent_id: typing.Optional[snowflakes.Snowflake] = attr.field(eq=False, hash=False, repr=True) """The ID of the parent category the channel belongs to. - If no parent category is set for the channel, this will be `builtins.None`. + If no parent category is set for the channel, this will be `None`. """ @property def mention(self) -> str: """Return a raw mention string for the guild channel. - !!! note + .. note:: As of writing, GuildCategory channels are a special case - for this and mentions of them will not resolve as clickable. - - Returns - ------- - builtins.str - The mention string to use. + for this and mentions of them will not resolve as clickable, but + will still parse as mentions. """ return f"<#{self.id}>" @@ -967,7 +962,7 @@ def mention(self) -> str: def shard_id(self) -> typing.Optional[int]: """Return the shard ID for the shard. - This may be `builtins.None` if the shard count is not known. + This may be `None` if the shard count is not known. """ if isinstance(self.app, traits.ShardAware): return snowflakes.calculate_shard_id(self.app, self.guild_id) @@ -1034,13 +1029,13 @@ async def edit_overwrite( If provided, the new vale of all allowed permissions. deny : hikari.undefined.UndefinedOr[hikari.permissions.Permissions] If provided, the new vale of all disallowed permissions. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. Raises ------ - builtins.TypeError + TypeError If `target_type` is unset and we were unable to determine the type from `target`. hikari.errors.BadRequestError @@ -1135,32 +1130,32 @@ async def edit( Other Parameters ---------------- - name : hikari.undefined.UndefinedOr[[builtins.str] + name : hikari.undefined.UndefinedOr[[str] If provided, the new name for the channel. - position : hikari.undefined.UndefinedOr[[builtins.int] + position : hikari.undefined.UndefinedOr[[int] If provided, the new position for the channel. - topic : hikari.undefined.UndefinedOr[builtins.str] + topic : hikari.undefined.UndefinedOr[str] If provided, the new topic for the channel. - nsfw : hikari.undefined.UndefinedOr[builtins.bool] + nsfw : hikari.undefined.UndefinedOr[bool] If provided, whether the channel should be marked as NSFW or not. - bitrate : hikari.undefined.UndefinedOr[builtins.int] + bitrate : hikari.undefined.UndefinedOr[int] If provided, the new bitrate for the channel. - video_quality_mode: hikari.undefined.UndefinedOr[typing.Union[hikari.channels.VideoQualityMode, builtins.int]] + video_quality_mode: hikari.undefined.UndefinedOr[typing.Union[hikari.channels.VideoQualityMode, int]] If provided, the new video quality mode for the channel. - user_limit : hikari.undefined.UndefinedOr[builtins.int] + user_limit : hikari.undefined.UndefinedOr[int] If provided, the new user limit in the channel. rate_limit_per_user : hikari.undefined.UndefinedOr[hikari.internal.time.Intervalish] If provided, the new rate limit per user in the channel. - region : hikari.undefined.UndefinedOr[typing.Union[hikari.voices.VoiceRegion, builtins.str]] + region : hikari.undefined.UndefinedOr[typing.Union[hikari.voices.VoiceRegion, str]] If provided, the voice region to set for this channel. Passing - `builtins.None` here will set it to "auto" mode where the used + `None` here will set it to "auto" mode where the used region will be decided based on the first person who connects to it when it's empty. permission_overwrites : hikari.undefined.UndefinedOr[typing.Sequence[hikari.channels.PermissionOverwrite]] If provided, the new permission overwrites for the channel. parent_category : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildCategory]] If provided, the new guild category for the channel. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -1192,7 +1187,7 @@ async def edit( nature, and will trigger this exception if they occur. hikari.errors.InternalServerError If an internal error occurs on Discord while handling the request. - """ # noqa: E501 - Line too long + """ return await self.app.rest.edit_channel( self.id, name=name, @@ -1236,7 +1231,7 @@ class GuildTextChannel(TextableGuildChannel): last_message_id: typing.Optional[snowflakes.Snowflake] = attr.field(eq=False, hash=False, repr=False) """The ID of the last message sent in this channel. - !!! warning + .. warning:: This might point to an invalid or deleted message. Do not assume that this will always be valid. """ @@ -1246,7 +1241,7 @@ class GuildTextChannel(TextableGuildChannel): If there is no rate limit, this will be 0 seconds. - !!! note + .. note:: Any user that has permissions allowing `MANAGE_MESSAGES`, `MANAGE_CHANNEL`, `ADMINISTRATOR` will not be limited. Likewise, bots will not be affected by this rate limit. @@ -1255,8 +1250,8 @@ class GuildTextChannel(TextableGuildChannel): last_pin_timestamp: typing.Optional[datetime.datetime] = attr.field(eq=False, hash=False, repr=False) """The timestamp of the last-pinned message. - !!! note - This may be `builtins.None` in several cases; Discord does not document what + .. note:: + This may be `None` in several cases; Discord does not document what these cases are. Trust no one! """ @@ -1271,7 +1266,7 @@ class GuildNewsChannel(TextableGuildChannel): last_message_id: typing.Optional[snowflakes.Snowflake] = attr.field(eq=False, hash=False, repr=False) """The ID of the last message sent in this channel. - !!! warning + .. warning:: This might point to an invalid or deleted message. Do not assume that this will always be valid. """ @@ -1279,8 +1274,8 @@ class GuildNewsChannel(TextableGuildChannel): last_pin_timestamp: typing.Optional[datetime.datetime] = attr.field(eq=False, hash=False, repr=False) """The timestamp of the last-pinned message. - !!! note - This may be `builtins.None` in several cases; Discord does not document what + .. note:: + This may be `None` in several cases; Discord does not document what these cases are. Trust no one! """ @@ -1305,7 +1300,7 @@ class GuildVoiceChannel(GuildChannel): region: typing.Optional[str] = attr.field(eq=False, hash=False, repr=False) """ID of the voice region for this voice channel. - If set to `builtins.None` then this is set to "auto" mode where the used + If set to `None` then this is set to "auto" mode where the used region will be decided based on the first person who connects to it when it's empty. """ @@ -1330,7 +1325,7 @@ class GuildStageChannel(GuildChannel): region: typing.Optional[str] = attr.field(eq=False, hash=False, repr=False) """ID of the voice region for this stage channel. - If set to `builtins.None` then this is set to "auto" mode where the used + If set to `None` then this is set to "auto" mode where the used region will be decided based on the first person who connects to it when it's empty. """ diff --git a/hikari/colors.py b/hikari/colors.py index 0550f768d8..3acbbdcaa6 100644 --- a/hikari/colors.py +++ b/hikari/colors.py @@ -66,7 +66,7 @@ class Color(int): This value is immutable. - This is a specialization of `builtins.int` which provides alternative overrides for + This is a specialization of `int` which provides alternative overrides for common methods and color system conversions. This currently supports: @@ -163,7 +163,7 @@ class Color(int): def __init__(self, raw_rgb: typing.SupportsInt) -> None: if not (0 <= int(raw_rgb) <= 0xFFFFFF): raise ValueError(f"raw_rgb must be in the exclusive range of 0 and {0xFF_FF_FF}") - # The __new__ for `builtins.int` initializes the value for us, this super-call does nothing other + # The __new__ for `int` initializes the value for us, this super-call does nothing other # than keeping the linter happy. super().__init__() @@ -223,10 +223,9 @@ def raw_hex_code(self) -> str: components = self.rgb return "".join(hex(c)[2:].zfill(2) for c in components).upper() - # Ignore docstring not starting in an imperative mood @property - def is_web_safe(self) -> bool: # noqa: D401 - """`builtins.True` if the color is web safe, `builtins.False` otherwise.""" + def is_web_safe(self) -> bool: + """Whether the color is web safe.""" return not (((self & 0xFF0000) % 0x110000) or ((self & 0xFF00) % 0x1100) or ((self & 0xFF) % 0x11)) @classmethod @@ -237,11 +236,11 @@ def from_rgb(cls, red: int, green: int, blue: int, /) -> Color: Parameters ---------- - red : builtins.int + red : int Red channel. - green : builtins.int + green : int Green channel. - blue : builtins.int + blue : int Blue channel. Returns @@ -251,7 +250,7 @@ def from_rgb(cls, red: int, green: int, blue: int, /) -> Color: Raises ------ - builtins.ValueError + ValueError If red, green, or blue are outside the range [0x0, 0xFF]. """ if not 0 <= red <= 0xFF: @@ -271,11 +270,11 @@ def from_rgb_float(cls, red: float, green: float, blue: float, /) -> Color: Parameters ---------- - red : builtins.float + red : float Red channel. - green : builtins.float + green : float Green channel. - blue : builtins.float + blue : float Blue channel. Returns @@ -285,7 +284,7 @@ def from_rgb_float(cls, red: float, green: float, blue: float, /) -> Color: Raises ------ - builtins.ValueError + ValueError If red, green or blue are outside the range [0, 1]. """ if not 0 <= red <= 1: @@ -306,7 +305,7 @@ def from_hex_code(cls, hex_code: str, /) -> Color: Parameters ---------- - hex_code : builtins.str + hex_code : str A hexadecimal color code to parse. This may optionally start with a case insensitive `0x` or `#`. @@ -317,7 +316,7 @@ def from_hex_code(cls, hex_code: str, /) -> Color: Raises ------ - builtins.ValueError + ValueError If `hex_code` is not a hexadecimal or is a invalid length. """ if hex_code.startswith("#"): @@ -394,7 +393,7 @@ def from_tuple_string(cls, tuple_str: str, /) -> Color: Parameters ---------- - tuple_str : builtins.str + tuple_str : str The string to parse. Returns @@ -437,12 +436,12 @@ def from_bytes( Parameters ---------- - bytes_ : typing.Iterable[builtins.int] + bytes_ : typing.Iterable[int] A iterable of int byte values. - byteorder : builtins.str + byteorder : str The endianness of the value represented by the bytes. Can be `"big"` endian or `"little"` endian. - signed : builtins.bool + signed : bool Whether the value is signed or unsigned. Returns @@ -547,17 +546,17 @@ def to_bytes( Parameters ---------- - length : builtins.int + length : int The number of bytes to produce. Should be around `3`, but not less. - byteorder : builtins.str + byteorder : str The endianness of the value represented by the bytes. Can be `"big"` endian or `"little"` endian. - signed : builtins.bool + signed : bool Whether the value is signed or unsigned. Returns ------- - builtins.bytes + bytes The bytes representation of the Color. """ return int(self).to_bytes(length, byteorder, signed=signed) @@ -578,12 +577,12 @@ def to_bytes( 1. `hikari.colors.Color` 2. `hikari.colours.Colour` (an alias for `hikari.colors.Color`). -3. A value that can be cast to an `builtins.int` (RGB hex-code). -4. a 3-`builtins.tuple` of `builtins.int` (RGB integers in range 0 through 255). -5. a 3-`builtins.tuple` of `builtins.float` (RGB floats in range 0 through 1). -6. a list of `builtins.int`. -7. a list of `builtins.float`. -8. a `builtins.str` hex colour code. +3. A value that can be cast to an `int` (RGB hex-code). +4. a 3-`tuple` of `int` (RGB integers in range 0 through 255). +5. a 3-`tuple` of `float` (RGB floats in range 0 through 1). +6. a list of `int`. +7. a list of `float`. +8. a `str` hex colour code. A hex colour code is expected to be in one of the following formats. Each of the following examples means the same thing semantically. diff --git a/hikari/colours.py b/hikari/colours.py index 213236d936..cc83177c5f 100644 --- a/hikari/colours.py +++ b/hikari/colours.py @@ -28,5 +28,10 @@ import typing -from hikari.colors import Color as Colour -from hikari.colors import Colorish as Colourish +from hikari import colors + +Colour = colors.Color +"""An alias for `hikari.colors.Color`.""" + +Colourish = colors.Colorish +"""An alias for `hikari.colors.Colorish`.""" diff --git a/hikari/commands.py b/hikari/commands.py index 922d81c119..c64d61e386 100644 --- a/hikari/commands.py +++ b/hikari/commands.py @@ -112,7 +112,7 @@ class CommandOption: name: str = attr.field(repr=True) r"""The command option's name. - !!! note + .. note:: This will match the regex `^[\w-]{1,32}$` in Unicode mode and will be lowercase. """ @@ -120,7 +120,7 @@ class CommandOption: description: str = attr.field(repr=False) """The command option's description. - !!! note + .. note:: This will be inclusively between 1-100 characters in length. """ @@ -130,7 +130,7 @@ class CommandOption: choices: typing.Optional[typing.Sequence[CommandChoice]] = attr.field(default=None, repr=False) """A sequence of up to (and including) 25 choices for this command. - This will be `builtins.None` if the input values for this option aren't + This will be `None` if the input values for this option aren't limited to specific values or if it's a subcommand or subcommand-group type option. """ @@ -143,21 +143,21 @@ class CommandOption: ) """The channel types that this option will accept. - If `builtins.None`, then all channel types will be accepted. + If `None`, then all channel types will be accepted. """ min_value: typing.Union[int, float, None] = attr.field(default=None, repr=False) """The minimum value permitted (inclusive). - This will be `builtins.int` if the type of the option is `hikari.commands.OptionType.INTEGER` - and `builtins.float` if the type is `hikari.commands.OptionType.NUMBER`. + This will be `int` if the type of the option is `hikari.commands.OptionType.INTEGER` + and `float` if the type is `hikari.commands.OptionType.NUMBER`. """ max_value: typing.Union[int, float, None] = attr.field(default=None, repr=False) """The maximum value permitted (inclusive). - This will be `builtins.int` if the type of the option is `hikari.commands.OptionType.INTEGER` - and `builtins.float` if the type is `hikari.commands.OptionType.NUMBER`. + This will be `int` if the type of the option is `hikari.commands.OptionType.INTEGER` + and `float` if the type is `hikari.commands.OptionType.NUMBER`. """ @@ -178,7 +178,7 @@ class Command(snowflakes.Unique): name: str = attr.field(eq=False, hash=False, repr=True) r"""The command's name. - !!! note + .. note:: This will match the regex `^[\w-]{1,32}$` in Unicode mode and will be lowercase. """ @@ -186,7 +186,7 @@ class Command(snowflakes.Unique): description: str = attr.field(eq=False, hash=False, repr=False) """The command's description. - !!! note + .. note:: This will be inclusively between 1-100 characters in length. """ @@ -196,14 +196,14 @@ class Command(snowflakes.Unique): default_permission: bool = attr.field(eq=False, hash=False, repr=True) """Whether the command is enabled by default when added to a guild. - Defaults to `builtins.True`. This behaviour is overridden by command + Defaults to `True`. This behaviour is overridden by command permissions. """ guild_id: typing.Optional[snowflakes.Snowflake] = attr.field(eq=False, hash=False, repr=False) """ID of the guild this command is in. - This will be `builtins.None` if this is a global command. + This will be `None` if this is a global command. """ version: snowflakes.Snowflake = attr.field(eq=False, hash=False, repr=True) @@ -254,14 +254,10 @@ async def edit( Other Parameters ---------------- - guild : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild]] - Object or ID of the guild to edit a command for if this is a guild - specific command. Leave this as `hikari.undefined.UNDEFINED` to delete - a global command. - name : hikari.undefined.UndefinedOr[builtins.str] + name : hikari.undefined.UndefinedOr[str] The name to set for the command. Leave as `hikari.undefined.UNDEFINED` to not change. - description : hikari.undefined.UndefinedOr[builtins.str] + description : hikari.undefined.UndefinedOr[str] The description to set for the command. Leave as `hikari.undefined.UNDEFINED` to not change. options : hikari.undefined.UndefinedOr[typing.Sequence[CommandOption]] @@ -381,7 +377,7 @@ async def set_guild_permissions( ) -> GuildCommandPermissions: """Set permissions for this command in a specific guild. - !!! note + .. note:: This overwrites any previously set permissions. Parameters diff --git a/hikari/config.py b/hikari/config.py index f2440debf5..79495e4727 100644 --- a/hikari/config.py +++ b/hikari/config.py @@ -68,43 +68,22 @@ class BasicAuthHeader: username: str = attr.field(validator=attr.validators.instance_of(str)) """Username for the header. - Returns - ------- - builtins.str - The username to use. This must not contain `":"`. + ...warning :: This must not contain `":"`. """ password: str = attr.field(repr=False, validator=attr.validators.instance_of(str)) - """Password to use. - - Returns - ------- - builtins.str - The password to use. - """ + """Password to use.""" charset: str = attr.field(default="utf-8", validator=attr.validators.instance_of(str)) """Encoding to use for the username and password. Default is `"utf-8"`, but you may choose to use something else, including third-party encodings (e.g. IBM's EBCDIC codepages). - - Returns - ------- - builtins.str - The encoding to use. """ @property def header(self) -> str: - """Create the full `Authentication` header value. - - Returns - ------- - builtins.str - A base64-encoded string containing - `"{username}:{password}"`. - """ + """Create the full `Authentication` header value.""" raw_token = f"{self.username}:{self.password}".encode(self.charset) token_part = base64.b64encode(raw_token).decode(self.charset) return f"{_BASICAUTH_TOKEN_PREFIX} {token_part}" @@ -121,20 +100,20 @@ class ProxySettings: auth: typing.Any = attr.field(default=None) """Authentication header value to use. - When cast to a `builtins.str`, this should provide the full value + When cast to a `str`, this should provide the full value for the authentication header. If you are using basic auth, you should consider using the `BasicAuthHeader` helper object here, as this will provide any transformations you may require into a base64 string. - The default is to have this set to `builtins.None`, which will + The default is to have this set to `None`, which will result in no authentication being provided. Returns ------- typing.Any - The value for the `Authentication` header, or `builtins.None` + The value for the `Authentication` header, or `None` to disable. """ @@ -144,12 +123,12 @@ class ProxySettings: url: typing.Union[None, str, yarl.URL] = attr.field(default=None) """Proxy URL to use. - Defaults to `builtins.None` which disables the use of an explicit proxy. + Defaults to `None` which disables the use of an explicit proxy. Returns ------- - typing.Union[builtins.None, builtins.str, yarl.URL] - The proxy URL to use, or `builtins.None` to disable it. + typing.Union[None, str, yarl.URL] + The proxy URL to use, or `None` to disable it. """ @url.validator @@ -160,23 +139,23 @@ def _(self, _: attr.Attribute[typing.Union[None, str, yarl.URL]], value: typing. trust_env: bool = attr.field(default=False, validator=attr.validators.instance_of(bool)) """Toggle whether to look for a `netrc` file or environment variables. - If `builtins.True`, and no `url` is given on this object, then + If `True`, and no `url` is given on this object, then `HTTP_PROXY` and `HTTPS_PROXY` will be used from the environment variables, or a `netrc` file may be read to determine credentials. - If `builtins.False`, then this information is instead ignored. + If `False`, then this information is instead ignored. - Defaults to `builtins.False` to prevent potentially unwanted behavior. + Defaults to `False` to prevent potentially unwanted behavior. - !!! note + .. note:: For more details of using `netrc`, visit: - https://www.gnu.org/software/inetutils/manual/html_node/The-_002enetrc-file.html + Returns ------- - builtins.bool - `builtins.True` if allowing the use of environment variables - and/or `netrc` to determine proxy settings; `builtins.False` + bool + `True` if allowing the use of environment variables + and/or `netrc` to determine proxy settings; `False` if this should be disabled explicitly. """ @@ -184,11 +163,7 @@ def _(self, _: attr.Attribute[typing.Union[None, str, yarl.URL]], value: typing. def all_headers(self) -> typing.Optional[data_binding.Headers]: """Return all proxy headers. - Returns - ------- - typing.Optional[hikari.internal.data_binding.Headers] - Any headers that are set, or `builtins.None` if no headers are to - be sent with any request. + Will be `None` if no headers are to be send with any request. """ if self.headers is None: if self.auth is None: @@ -208,45 +183,29 @@ class HTTPTimeoutSettings: acquire_and_connect: typing.Optional[float] = attr.field(default=None) """Timeout for `request_socket_connect` PLUS connection acquisition. - By default, this has no timeout allocated. - - Returns - ------- - typing.Optional[builtins.float] - The timeout, or `builtins.None` to disable it. + By default, this has no timeout allocated. Setting it to `None` + will disable it. """ request_socket_connect: typing.Optional[float] = attr.field(default=None) """Timeout for connecting a socket. - By default, this has no timeout allocated. - - Returns - ------- - typing.Optional[builtins.float] - The timeout, or `builtins.None` to disable it. + By default, this has no timeout allocated. Setting it to `None` + will disable it. """ request_socket_read: typing.Optional[float] = attr.field(default=None) """Timeout for reading a socket. - By default, this has no timeout allocated. - - Returns - ------- - typing.Optional[builtins.float] - The timeout, or `builtins.None` to disable it. + By default, this has no timeout allocated. Setting it to `None` + will disable it. """ total: typing.Optional[float] = attr.field(default=30.0) """Total timeout for entire request. - By default, this has a 30 second timeout allocated. - - Returns - ------- - typing.Optional[builtins.float] - The timeout, or `builtins.None` to disable it. + By default, this has a 30 second timeout allocated. Setting it to `None` + will disable it. """ @acquire_and_connect.validator @@ -268,41 +227,29 @@ class HTTPSettings: enable_cleanup_closed: bool = attr.field(default=True, validator=attr.validators.instance_of(bool)) """Toggle whether to clean up closed transports. - This defaults to `builtins.True` to combat various protocol and asyncio + This defaults to `True` to combat various protocol and asyncio issues present when using Microsoft Windows. If you are sure you know what you are doing, you may instead set this to `False` to disable this behavior internally. - - Returns - ------- - builtins.bool - `builtins.True` to enable this behavior, `builtins.False` to disable - it. """ force_close_transports: bool = attr.field(default=True, validator=attr.validators.instance_of(bool)) """Toggle whether to force close transports on shutdown. - This defaults to `builtins.True` to combat various protocol and asyncio + This defaults to `True` to combat various protocol and asyncio issues present when using Microsoft Windows. If you are sure you know what you are doing, you may instead set this to `False` to disable this behavior internally. - - Returns - ------- - builtins.bool - `builtins.True` to enable this behavior, `builtins.False` to disable - it. """ max_redirects: typing.Optional[int] = attr.field(default=10) """Behavior for handling redirect HTTP responses. - If a `builtins.int`, allow following redirects from `3xx` HTTP responses + If a `int`, allow following redirects from `3xx` HTTP responses for up to this many redirects. Exceeding this value will raise an exception. - If `builtins.None`, then disallow any redirects. + If `None`, then disallow any redirects. The default is to disallow this behavior for security reasons. @@ -310,17 +257,9 @@ class HTTPSettings: future where you need to enable this if Discord change their URL without warning. - !!! note + .. note:: This will only apply to the REST API. WebSockets remain unaffected by any value set here. - - Returns - ------- - typing.Optional[builtins.int] - The number of redirects to allow at a maximum per request. - `builtins.None` disables the handling - of redirects and will result in exceptions being raised instead - should one occur. """ @max_redirects.validator @@ -337,24 +276,24 @@ def _(self, _: attr.Attribute[typing.Optional[int]], value: typing.Optional[int] ) """SSL context to use. - This may be __assigned__ a `builtins.bool` or an `ssl.SSLContext` object. + This may be __assigned__ a `bool` or an `ssl.SSLContext` object. - If assigned to `builtins.True`, a default SSL context is generated by + If assigned to `True`, a default SSL context is generated by this class that will enforce SSL verification. This is then stored in this field. - If `builtins.False`, then a default SSL context is generated by this + If `False`, then a default SSL context is generated by this class that will **NOT** enforce SSL verification. This is then stored in this field. If an instance of `ssl.SSLContext`, then this context will be used. - !!! warning + .. warning:: Setting a custom value here may have security implications, or may result in the application being unable to connect to Discord at all. - !!! warning + .. warning:: Disabling SSL verification is almost always unadvised. This is because your application will no longer check whether you are connecting to Discord, or to some third party spoof designed @@ -365,11 +304,6 @@ class that will **NOT** enforce SSL verification. This is then stored allows you to work around any issues that are occurring, but you should immediately seek a better solution where possible if any form of personal security is in your interest. - - Returns - ------- - ssl.SSLContext - The SSL context to use for this application. """ timeouts: HTTPTimeoutSettings = attr.field( @@ -379,11 +313,6 @@ class that will **NOT** enforce SSL verification. This is then stored The behaviour if this is not explicitly defined is to use sane defaults that are most efficient for optimal use of this library. - - Returns - ------- - HTTPTimeoutSettings - The HTTP timeout settings to use for connection timeouts. """ diff --git a/hikari/embeds.py b/hikari/embeds.py index 7f1af048cb..fcbe51fd7a 100644 --- a/hikari/embeds.py +++ b/hikari/embeds.py @@ -71,24 +71,12 @@ class EmbedResource(files.Resource[AsyncReaderT]): @property @typing.final def url(self) -> str: - """URL of this embed resource. - - Returns - ------- - typing.Optional[builtins.str] - The URL of this embed resource. - """ + """URL of this embed resource.""" return self.resource.url @property def filename(self) -> str: - """File name of this embed resource. - - Returns - ------- - typing.Optional[builtins.str] - The file name of this embed resource. - """ + """File name of this embed resource.""" return self.resource.filename def stream( @@ -103,10 +91,10 @@ def stream( ---------- executor : typing.Optional[concurrent.futures.Executor] The executor to run in for blocking operations. - If `builtins.None`, then the default executor is used for the + If `None`, then the default executor is used for the current event loop. - head_only : builtins.bool - Defaults to `builtins.False`. If `builtins.True`, then the + head_only : bool + Defaults to `False`. If `True`, then the implementation may only retrieve HEAD information if supported. This currently only has any effect for web requests. """ @@ -118,9 +106,9 @@ class EmbedResourceWithProxy(EmbedResource[AsyncReaderT]): """Resource with a corresponding proxied element.""" proxy_resource: typing.Optional[files.Resource[AsyncReaderT]] = attr.field(default=None, repr=False) - """The proxied version of the resource, or `builtins.None` if not present. + """The proxied version of the resource, or `None` if not present. - !!! note + .. note:: This field cannot be set by bots or webhooks while sending an embed and will be ignored during serialization. Expect this to be populated on any received embed attached to a message event. @@ -129,27 +117,13 @@ class EmbedResourceWithProxy(EmbedResource[AsyncReaderT]): @property @typing.final def proxy_url(self) -> typing.Optional[str]: - """Proxied URL of this embed resource if applicable. - - Returns - ------- - typing.Optional[builtins.str] - The proxied URL of this embed resource if applicable, else - `builtins.None`. - """ + """Proxied URL of this embed resource if applicable, else `None`.""" return self.proxy_resource.url if self.proxy_resource else None @property @typing.final def proxy_filename(self) -> typing.Optional[str]: - """File name of the proxied version of this embed resource if applicable. - - Returns - ------- - typing.Optional[builtins.str] - The file name of the proxied version of this embed resource if - applicable, else `builtins.None`. - """ + """File name of the proxied version of this embed resource if applicable, else `None`.""" return self.proxy_resource.filename if self.proxy_resource else None @@ -161,10 +135,10 @@ class EmbedFooter: # Discord says this is never None. We know that is invalid because Discord.py # sets it to None. Seems like undocumented behaviour again. text: typing.Optional[str] = attr.field(default=None, repr=True) - """The footer text, or `builtins.None` if not present.""" + """The footer text, or `None` if not present.""" icon: typing.Optional[EmbedResourceWithProxy[files.AsyncReader]] = attr.field(default=None, repr=True) - """The URL of the footer icon, or `builtins.None` if not present.""" + """The URL of the footer icon, or `None` if not present.""" @attr.define(hash=False, kw_only=True, weakref_slot=False) @@ -172,18 +146,18 @@ class EmbedImage(EmbedResourceWithProxy[AsyncReaderT]): """Represents an embed image.""" height: typing.Optional[int] = attr.field(default=None, repr=False) - """The height of the image, if present and known, otherwise `builtins.None`. + """The height of the image, if present and known, otherwise `None`. - !!! note + .. note:: This field cannot be set by bots or webhooks while sending an embed and will be ignored during serialization. Expect this to be populated on any received embed attached to a message event. """ width: typing.Optional[int] = attr.field(default=None, repr=False) - """The width of the image, if present and known, otherwise `builtins.None`. + """The width of the image, if present and known, otherwise `None`. - !!! note + .. note:: This field cannot be set by bots or webhooks while sending an embed and will be ignored during serialization. Expect this to be populated on any received embed attached to a message event. @@ -194,7 +168,7 @@ class EmbedImage(EmbedResourceWithProxy[AsyncReaderT]): class EmbedVideo(EmbedResourceWithProxy[AsyncReaderT]): """Represents an embed video. - !!! note + .. note:: This object cannot be set by bots or webhooks while sending an embed and will be ignored during serialization. Expect this to be populated on any received embed attached to a message event with a video attached. @@ -215,7 +189,7 @@ class yourself.** class EmbedProvider: """Represents an embed provider. - !!! note + .. note:: This object cannot be set by bots or webhooks while sending an embed and will be ignored during serialization. Expect this to be populated on any received embed attached to a message event provided by an external @@ -238,16 +212,16 @@ class EmbedAuthor: """Represents an author of an embed.""" name: typing.Optional[str] = attr.field(default=None, repr=True) - """The name of the author, or `builtins.None` if not specified.""" + """The name of the author, or `None` if not specified.""" url: typing.Optional[str] = attr.field(default=None, repr=True) """The URL that the author's name should act as a hyperlink to. - This may be `builtins.None` if no hyperlink on the author's name is specified. + This may be `None` if no hyperlink on the author's name is specified. """ icon: typing.Optional[EmbedResourceWithProxy[files.AsyncReader]] = attr.field(default=None, repr=False) - """The author's icon, or `builtins.None` if not present.""" + """The author's icon, or `None` if not present.""" @attr_extensions.with_copy @@ -267,9 +241,9 @@ class EmbedField: # in the constructor for `_inline`. @property def is_inline(self) -> bool: - """Return `builtins.True` if the field should display inline. + """Return `True` if the field should display inline. - Defaults to False. + Defaults to `False`. """ return self._inline @@ -303,9 +277,6 @@ class Embed: "_fields", ) - # Don't document this. - __pdoc__: typing.ClassVar[typing.Mapping[str, typing.Any]] = {"from_received_embed": False} - @classmethod def from_received_embed( cls, @@ -325,7 +296,8 @@ def from_received_embed( ) -> Embed: """Generate an embed from the given attributes. - You should never call this. + .. warning:: + **This function is for internal use only!** """ # Create an empty instance without the overhead of invoking the regular # constructor. @@ -386,12 +358,7 @@ def __init__( def title(self) -> typing.Optional[str]: """Return the title of the embed. - This will be `builtins.None` if not set. - - Returns - ------- - typing.Optional[builtins.str] - The title of the embed. + This will be `None` if not set. """ return self._title @@ -403,12 +370,7 @@ def title(self, value: typing.Any) -> None: def description(self) -> typing.Optional[str]: """Return the description of the embed. - This will be `builtins.None` if not set. - - Returns - ------- - typing.Optional[builtins.str] - The description of the embed. + This will be `None` if not set. """ return self._description @@ -420,12 +382,7 @@ def description(self, value: typing.Any) -> None: def url(self) -> typing.Optional[str]: """Return the URL of the embed title. - This will be `builtins.None` if not set. - - Returns - ------- - typing.Optional[builtins.str] - The URL of the embed title + This will be `None` if not set. """ return self._url @@ -437,12 +394,7 @@ def url(self, value: typing.Optional[str]) -> None: def color(self) -> typing.Optional[colors.Color]: """Return the colour of the embed. - This will be `builtins.None` if not set. - - Returns - ------- - typing.Optional[hikari.colors.Color] - The colour that is set. + This will be `None` if not set. """ return self._color @@ -457,12 +409,7 @@ def color(self, value: typing.Optional[colors.Colorish]) -> None: def colour(self) -> typing.Optional[colors.Color]: """Return the colour of the embed. This is an alias of `Embed.color`. - This will be `builtins.None` if not set. - - Returns - ------- - typing.Optional[hikari.colors.Color] - The colour that is set. + This will be `None` if not set. """ return self._color @@ -477,14 +424,9 @@ def colour(self, value: typing.Optional[colors.Colorish]) -> None: def timestamp(self) -> typing.Optional[datetime.datetime]: """Return the timestamp of the embed. - This will be `builtins.None` if not set. - - Returns - ------- - typing.Optional[datetime.datetime] - The timestamp set on the embed. + This will be `None` if not set. - !!! warning + .. warning:: Setting a non-timezone-aware datetime will result in a warning being raised. This is done due to potential confusion caused by Discord requiring a UTC timestamp for this field. Any non-timezone @@ -597,10 +539,7 @@ def __warn_naive_datetime() -> None: def footer(self) -> typing.Optional[EmbedFooter]: """Return the footer of the embed. - Will be `builtins.None` if not set. - - typing.Optional[EmbedFooter] - The footer of the embed. + Will be `None` if not set. """ return self._footer @@ -608,12 +547,9 @@ def footer(self) -> typing.Optional[EmbedFooter]: def image(self) -> typing.Optional[EmbedImage[files.AsyncReader]]: """Return the image set in the embed. - Will be `builtins.None` if not set. + Will be `None` if not set. - typing.Optional[EmbedImage] - The image of the embed. - - !!! note + .. note:: Use `set_image` to update this value. """ return self._image @@ -622,12 +558,9 @@ def image(self) -> typing.Optional[EmbedImage[files.AsyncReader]]: def thumbnail(self) -> typing.Optional[EmbedImage[files.AsyncReader]]: """Return the thumbnail set in the embed. - Will be `builtins.None` if not set. - - typing.Optional[EmbedImage] - The thumbnail of the embed. + Will be `None` if not set. - !!! note + .. note:: Use `set_thumbnail` to update this value. """ return self._thumbnail @@ -636,14 +569,9 @@ def thumbnail(self) -> typing.Optional[EmbedImage[files.AsyncReader]]: def video(self) -> typing.Optional[EmbedVideo[files.AsyncReader]]: """Return the video to show in the embed. - Will be `builtins.None` if not set. - - Returns - ------- - typing.Optional[EmbedVideo] - The video of the embed. + Will be `None` if not set. - !!! note + .. note:: This object cannot be set by bots or webhooks while sending an embed and will be ignored during serialization. Expect this to be populated on any received embed attached to a message event with a @@ -655,14 +583,9 @@ def video(self) -> typing.Optional[EmbedVideo[files.AsyncReader]]: def provider(self) -> typing.Optional[EmbedProvider]: """Return the provider to show in the embed. - Will be `builtins.None` if not set. + Will be `None` if not set. - Returns - ------- - typing.Optional[EmbedProvider] - The provider of the embed. - - !!! note + .. note:: This object cannot be set by bots or webhooks while sending an embed and will be ignored during serialization. Expect this to be populated on any received embed attached to a message event with a @@ -674,14 +597,9 @@ def provider(self) -> typing.Optional[EmbedProvider]: def author(self) -> typing.Optional[EmbedAuthor]: """Return the author to show in the embed. - Will be `builtins.None` if not set. - - Returns - ------- - typing.Optional[EmbedAuthor] - The author of the embed. + Will be `None` if not set. - !!! note + .. note:: Use `set_author` to update this value. """ return self._author @@ -690,7 +608,7 @@ def author(self) -> typing.Optional[EmbedAuthor]: def fields(self) -> typing.Sequence[EmbedField]: """Return the sequence of fields in the embed. - !!! note + .. note:: Use `add_field` to add a new field, `edit_field` to edit an existing field, or `remove_field` to remove a field. """ @@ -707,17 +625,17 @@ def set_author( Parameters ---------- - name : typing.Optional[builtins.str] + name : typing.Optional[str] The optional name of the author. - url : typing.Optional[builtins.str] + url : typing.Optional[str] The optional URL of the author. icon : typing.Optional[hikari.files.Resourceish] The optional image to show next to the embed author. This can be many different things, to aid in convenience. - - If `builtins.None`, nothing is set. - - If a `pathlib.PurePath` or `builtins.str` to a valid URL, the URL + - If `None`, nothing is set. + - If a `pathlib.PurePath` or `str` to a valid URL, the URL is linked to directly. - Subclasses of `hikari.files.WebResource` such as `hikari.files.URL`, @@ -725,11 +643,11 @@ def set_author( `hikari.emojis.Emoji`, `EmbedResource`, etc will have their URL linked to directly. this field. - - If a `hikari.files.Bytes` is passed, or a `builtins.str` + - If a `hikari.files.Bytes` is passed, or a `str` that contains a valid data URI is passed, then this is uploaded as an attachment and linked into the embed. - If a `hikari.files.File`, `pathlib.PurePath` or - `builtins.str` that is an absolute or relative path to a file + `str` that is an absolute or relative path to a file on your file system is passed, then this resource is uploaded as an attachment using non-blocking code internally and linked into the embed. @@ -753,14 +671,14 @@ def set_footer(self, text: typing.Optional[str], *, icon: typing.Optional[files. ---------- text : typing.Optional[str] The mandatory text string to set in the footer. - If `builtins.None`, the footer is removed. + If `None`, the footer is removed. icon : typing.Optional[hikari.files.Resourceish] The optional image to show next to the embed footer. This can be many different things, to aid in convenience. - - If `builtins.None`, nothing is set. - - If a `pathlib.PurePath` or `builtins.str` to a valid URL, the URL + - If `None`, nothing is set. + - If a `pathlib.PurePath` or `str` to a valid URL, the URL is linked to directly. - Subclasses of `hikari.files.WebResource` such as `hikari.files.URL`, @@ -768,11 +686,11 @@ def set_footer(self, text: typing.Optional[str], *, icon: typing.Optional[files. `hikari.emojis.Emoji`, `EmbedResource`, etc will have their URL linked to directly. this field. - - If a `hikari.files.Bytes` is passed, or a `builtins.str` + - If a `hikari.files.Bytes` is passed, or a `str` that contains a valid data URI is passed, then this is uploaded as an attachment and linked into the embed. - If a `hikari.files.File`, `pathlib.PurePath` or - `builtins.str` that is an absolute or relative path to a file + `str` that is an absolute or relative path to a file on your file system is passed, then this resource is uploaded as an attachment using non-blocking code internally and linked into the embed. @@ -805,8 +723,8 @@ def set_image(self, image: typing.Optional[files.Resourceish] = None, /) -> Embe This can be many different things, to aid in convenience. - - If `builtins.None`, nothing is set. - - If a `pathlib.PurePath` or `builtins.str` to a valid URL, the URL + - If `None`, nothing is set. + - If a `pathlib.PurePath` or `str` to a valid URL, the URL is linked to directly. - Subclasses of `hikari.files.WebResource` such as `hikari.files.URL`, @@ -814,11 +732,11 @@ def set_image(self, image: typing.Optional[files.Resourceish] = None, /) -> Embe `hikari.emojis.Emoji`, `EmbedResource`, etc will have their URL linked to directly. this field. - - If a `hikari.files.Bytes` is passed, or a `builtins.str` + - If a `hikari.files.Bytes` is passed, or a `str` that contains a valid data URI is passed, then this is uploaded as an attachment and linked into the embed. - If a `hikari.files.File`, `pathlib.PurePath` or - `builtins.str` that is an absolute or relative path to a file + `str` that is an absolute or relative path to a file on your file system is passed, then this resource is uploaded as an attachment using non-blocking code internally and linked into the embed. @@ -845,19 +763,19 @@ def set_thumbnail(self, image: typing.Optional[files.Resourceish] = None, /) -> This can be many different things, to aid in convenience. - - If `builtins.None`, nothing is set. - - If a `pathlib.PurePath` or `builtins.str` to a valid URL, the URL + - If `None`, nothing is set. + - If a `pathlib.PurePath` or `str` to a valid URL, the URL is linked to directly. - Subclasses of `hikari.files.WebResource` such as `hikari.files.URL`, `hikari.messages.Attachment`, `hikari.emojis.Emoji`, `EmbedResource`, etc will have their URL linked to directly. - - If a `hikari.files.Bytes` is passed, or a `builtins.str` + - If a `hikari.files.Bytes` is passed, or a `str` that contains a valid data URI is passed, then this is uploaded as an attachment and linked into the embed. - If a `hikari.files.File`, `pathlib.PurePath` or - `builtins.str` that is an absolute or relative path to a file + `str` that is an absolute or relative path to a file on your file system is passed, then this resource is uploaded as an attachment using non-blocking code internally and linked into the embed. @@ -889,9 +807,9 @@ def add_field(self, name: str, value: str, *, inline: bool = False) -> Embed: Other Parameters ---------------- inline : bool - If `builtins.True`, the embed field may be shown "inline" on some - Discord clients with other fields. If `builtins.False`, it is always placed - on a separate line. This will default to `builtins.False`. + If `True`, the embed field may be shown "inline" on some + Discord clients with other fields. If `False`, it is always placed + on a separate line. This will default to `False`. Returns ------- @@ -927,8 +845,8 @@ def edit_field( value : hikari.undefined.UndefinedOr[str] The new field value to use. If left to the default (`undefined`), then it will not be changed. - inline : hikari.undefined.UndefinedOr[builtins.bool] - `builtins.True` to inline the field, or `builtins.False` to force + inline : hikari.undefined.UndefinedOr[bool] + `True` to inline the field, or `False` to force it to be on a separate line. If left to the default (`undefined`), then it will not be changed. @@ -939,7 +857,7 @@ def edit_field( Raises ------ - builtins.IndexError + IndexError Raised if the index is greater than `len(embed.fields) - 1` or less than `-len(embed.fields)` """ @@ -970,7 +888,7 @@ def remove_field(self, index: int, /) -> Embed: Raises ------ - builtins.IndexError + IndexError Raised if the index is greater than `len(embed.fields) - 1` or less than `-len(embed.fields)` """ diff --git a/hikari/emojis.py b/hikari/emojis.py index ef23b582a4..2b40c09220 100644 --- a/hikari/emojis.py +++ b/hikari/emojis.py @@ -86,7 +86,7 @@ def parse(cls, string: str, /) -> Emoji: Parameters ---------- - string : builtins.str + string : str The emoji object to parse. Returns @@ -97,7 +97,7 @@ def parse(cls, string: str, /) -> Emoji: Raises ------ - builtins.ValueError + ValueError If a mention is given that has an invalid format. """ if string.startswith("<") and string.endswith(">"): @@ -108,7 +108,7 @@ def parse(cls, string: str, /) -> Emoji: class UnicodeEmoji(str, Emoji): """Represents a unicode emoji. - !!! warning + .. warning:: A word of warning if you try to upload this emoji as a file attachment. While this emoji type can be used to upload the Twemoji representations @@ -178,7 +178,11 @@ def url(self) -> str: Example ------- - https://github.com/twitter/twemoji/raw/master/assets/72x72/1f004.png + ```py + >>> emoji = hikari.UnicodeEmoji("\N{OK HAND SIGN}") + >>> emoji.url + 'https://raw.githubusercontent.com/twitter/twemoji/master/assets/72x72/1f44c.png' + ``` """ return _TWEMOJI_PNG_BASE_URL + self.filename @@ -217,7 +221,7 @@ def parse(cls, string: str, /) -> UnicodeEmoji: Parameters ---------- - string : builtins.str + string : str The emoji object to parse. Returns @@ -248,7 +252,7 @@ class CustomEmoji(snowflakes.Unique, Emoji): >>> picks = random.choices(emojis, 5) >>> await event.respond(files=picks) - !!! warning + .. warning:: Discord will not provide information on whether these emojis are animated or not when a reaction is removed and an event is fired. This is problematic if you need to try and determine the emoji that was @@ -256,7 +260,7 @@ class CustomEmoji(snowflakes.Unique, Emoji): will not be correct. This will not be changed as stated here: - https://github.com/discord/discord-api-docs/issues/1614#issuecomment-628548913 + """ id: snowflakes.Snowflake = attr.field(hash=True, repr=True) @@ -298,7 +302,7 @@ def parse(cls, string: str, /) -> CustomEmoji: Parameters ---------- - string : builtins.str + string : str The emoji mention to parse. Returns @@ -308,7 +312,7 @@ def parse(cls, string: str, /) -> CustomEmoji: Raises ------ - builtins.ValueError + ValueError If a mention is given that has an invalid format. """ if emoji_match := _CUSTOM_EMOJI_REGEX.match(string): @@ -346,8 +350,8 @@ class KnownCustomEmoji(CustomEmoji): user: typing.Optional[users.User] = attr.field(eq=False, hash=False, repr=False) """The user that created the emoji. - !!! note - This will be `builtins.None` if you are missing the `MANAGE_EMOJIS_AND_STICKERS` + .. note:: + This will be `None` if you are missing the `MANAGE_EMOJIS_AND_STICKERS` permission in the server the emoji is from. """ @@ -360,5 +364,5 @@ class KnownCustomEmoji(CustomEmoji): is_available: bool = attr.field(eq=False, hash=False, repr=False) """Whether this emoji can currently be used. - May be `builtins.False` due to a loss of Sever Boosts on the emoji's guild. + May be `False` due to a loss of Sever Boosts on the emoji's guild. """ diff --git a/hikari/errors.py b/hikari/errors.py index aaa6da050c..bbfcb4d9f0 100644 --- a/hikari/errors.py +++ b/hikari/errors.py @@ -73,7 +73,7 @@ class HikariError(RuntimeError): Any exceptions should derive from this. - !!! note + .. note:: You should never initialize this exception directly. """ @@ -85,7 +85,7 @@ class HikariWarning(RuntimeWarning): Any warnings should derive from this. - !!! note + .. note:: You should never initialize this warning directly. """ @@ -167,7 +167,7 @@ class ShardCloseCode(int, enums.Enum): @property def is_standard(self) -> bool: - """Return `builtins.True` if this is a standard code.""" + """Return `True` if this is a standard code.""" return (self.value // 1000) == 1 @@ -188,25 +188,25 @@ class GatewayServerClosedConnectionError(GatewayError): Returns ------- - typing.Union[ShardCloseCode, builtins.int, builtins.None] + typing.Union[ShardCloseCode, int, None] The shard close code if there was one. Will be a `ShardCloseCode` if the definition is known. Undocumented close codes may instead be - an `builtins.int` instead. + an `int` instead. - If no close code was received, this will be `builtins.None`. + If no close code was received, this will be `None`. """ can_reconnect: bool = attr.field(default=False) - """Return `builtins.True` if we can recover from this closure. + """Return `True` if we can recover from this closure. - If `builtins.True`, it will try to reconnect after this is raised rather - than it being propagated to the caller. If `builtins.False`, this will + If `True`, it will try to reconnect after this is raised rather + than it being propagated to the caller. If `False`, this will be raised, thus stopping the application unless handled explicitly by the user. Returns ------- - builtins.bool + bool Whether the closure can be recovered from via a reconnect. """ @@ -286,7 +286,7 @@ class BadRequestError(ClientHTTPResponseError): """Dict of top level field names to field specific error paths. For more information, this error format is loosely defined at - https://discord.com/developers/docs/reference#error-messages and is commonly + and is commonly returned for 50035 errors. """ @@ -416,11 +416,6 @@ def remaining(self) -> typing.Literal[0]: """The number of requests remaining in this window. This will always be `0` symbolically. - - Returns - ------- - builtins.int - The number of requests remaining. Always `0`. """ # noqa: D401 - Imperative mood return 0 @@ -461,13 +456,7 @@ class BulkDeleteError(HikariError): @property def percentage_completion(self) -> float: - """Return the percentage completion of the bulk delete before it failed. - - Returns - ------- - builtins.float - A percentage completion between 0 and 100 inclusive. - """ + """Return the percentage completion of the bulk delete before it failed.""" deleted = len(self.messages_deleted) total = deleted + len(self.messages_skipped) return 100 * deleted / total diff --git a/hikari/events/base_events.py b/hikari/events/base_events.py index 1b3f7a3c67..efc4842ef8 100644 --- a/hikari/events/base_events.py +++ b/hikari/events/base_events.py @@ -60,13 +60,7 @@ class Event(abc.ABC): @property @abc.abstractmethod def app(self) -> traits.RESTAware: - """App instance for this application. - - Returns - ------- - hikari.traits.RESTAware - The REST-aware app trait. - """ + """App instance for this application.""" def get_required_intents_for(event_type: typing.Type[Event]) -> typing.Collection[intents.Intents]: @@ -130,7 +124,7 @@ def decorator(cls: typing.Type[T]) -> typing.Type[T]: doc = inspect.getdoc(cls) or "" doc += ( "\n" - "!!! warning\n" + ".. warning::\n" " Any exceptions raised by handlers for this event will be dumped to the\n" " application logger and silently discarded, preventing recursive loops\n" " produced by faulty exception event handling. Thus, it is imperative\n" @@ -160,8 +154,8 @@ def is_no_recursive_throw_event(obj: typing.Union[T, typing.Type[T]]) -> bool: class ExceptionEvent(Event, typing.Generic[FailedEventT]): """Event that is raised when another event handler raises an `Exception`. - !!! note - Only exceptions that derive from `builtins.Exception` will be caught. + .. note:: + Only exceptions that derive from `Exception` will be caught. Other exceptions outside this range will propagate past this callback. This prevents event handlers interfering with critical exceptions such as `KeyboardError` which would have potentially undesired @@ -173,7 +167,7 @@ class ExceptionEvent(Event, typing.Generic[FailedEventT]): Returns ------- - builtins.Exception + Exception Exception that was raised in the event handler. """ @@ -204,13 +198,8 @@ def app(self) -> traits.RESTAware: def shard(self) -> typing.Optional[gateway_shard.GatewayShard]: """Shard that received the event, if there was one associated. - Returns - ------- - typing.Optional[hikari.api.shard.GatewayShard] - Shard that raised this exception. - - This may be `builtins.None` if no specific shard was the cause of this - exception (e.g. when starting up or shutting down). + This may be `None` if no specific shard was the cause of this + exception (e.g. when starting up or shutting down). """ shard = getattr(self.failed_event, "shard", None) if isinstance(shard, gateway_shard.GatewayShard): @@ -221,11 +210,8 @@ def shard(self) -> typing.Optional[gateway_shard.GatewayShard]: def exc_info(self) -> typing.Tuple[typing.Type[Exception], Exception, typing.Optional[types.TracebackType]]: """Exception triplet that follows the same format as `sys.exc_info`. - Returns - ------- - builtins.tuple[typing.Type[Exception], Exception, typing.Optional[types.TracebackType]] - The `sys.exc_info`-compatible tuple of the exception type, the - exception instance, and the traceback of the exception. + The `sys.exc_info` tiplet consists of the exception type, the exception + instance, and the traceback of the exception. """ return type(self.exception), self.exception, self.exception.__traceback__ diff --git a/hikari/events/channel_events.py b/hikari/events/channel_events.py index f0febefe37..0895b86189 100644 --- a/hikari/events/channel_events.py +++ b/hikari/events/channel_events.py @@ -74,19 +74,13 @@ class ChannelEvent(shard_events.ShardEvent, abc.ABC): @property @abc.abstractmethod def channel_id(self) -> snowflakes.Snowflake: - """ID of the channel the event relates to. - - Returns - ------- - hikari.snowflakes.Snowflake - The ID of the channel this event relates to. - """ + """ID of the channel the event relates to.""" @abc.abstractmethod async def fetch_channel(self) -> channels.PartialChannel: """Perform an API call to fetch the details about this channel. - !!! note + .. note:: For `GuildChannelDeleteEvent` events, this will always raise an exception, since the channel will have already been removed. @@ -130,24 +124,18 @@ class GuildChannelEvent(ChannelEvent, abc.ABC): @property @abc.abstractmethod def guild_id(self) -> snowflakes.Snowflake: - """ID of the guild that this event relates to. - - Returns - ------- - hikari.snowflakes.Snowflake - The ID of the guild that relates to this event. - """ + """ID of the guild that this event relates to.""" def get_guild(self) -> typing.Optional[guilds.GatewayGuild]: """Get the cached guild that this event relates to, if known. - If not, return `builtins.None`. + If not, return `None`. Returns ------- typing.Optional[hikari.guilds.GatewayGuild] The gateway guild this event relates to, if known. Otherwise - this will return `builtins.None`. + this will return `None`. """ if not isinstance(self.app, traits.CacheAware): return None @@ -189,13 +177,13 @@ async def fetch_guild(self) -> guilds.RESTGuild: def get_channel(self) -> typing.Optional[channels.GuildChannel]: """Get the cached channel that this event relates to, if known. - If not, return `builtins.None`. + If not, return `None`. Returns ------- typing.Optional[hikari.channels.GuildChannel] The cached channel this event relates to. If not known, this - will return `builtins.None` instead. + will return `None` instead. """ if not isinstance(self.app, traits.CacheAware): return None @@ -205,7 +193,7 @@ def get_channel(self) -> typing.Optional[channels.GuildChannel]: async def fetch_channel(self) -> channels.GuildChannel: """Perform an API call to fetch the details about this channel. - !!! note + .. note:: For `GuildChannelDeleteEvent` events, this will always raise an exception, since the channel will have already been removed. @@ -251,7 +239,7 @@ class DMChannelEvent(ChannelEvent, abc.ABC): async def fetch_channel(self) -> channels.PrivateChannel: """Perform an API call to fetch the details about this channel. - !!! note + .. note:: For `GuildChannelDeleteEvent` events, this will always raise an exception, since the channel will have already been removed. @@ -299,13 +287,7 @@ class GuildChannelCreateEvent(GuildChannelEvent): # <>. channel: channels.GuildChannel = attr.field(repr=True) - """Guild channel that this event represents. - - Returns - ------- - hikari.channels.GuildChannel - The guild channel that was created. - """ + """Guild channel that this event represents.""" @property def app(self) -> traits.RESTAware: @@ -335,17 +317,11 @@ class GuildChannelUpdateEvent(GuildChannelEvent): old_channel: typing.Optional[channels.GuildChannel] = attr.field(repr=True) """The old guild channel object. - This will be `builtins.None` if the channel missing from the cache. + This will be `None` if the channel missing from the cache. """ channel: channels.GuildChannel = attr.field(repr=True) - """Guild channel that this event represents. - - Returns - ------- - hikari.channels.GuildChannel - The guild channel that was updated. - """ + """Guild channel that this event represents.""" @property def app(self) -> traits.RESTAware: @@ -373,13 +349,7 @@ class GuildChannelDeleteEvent(GuildChannelEvent): # <>. channel: channels.GuildChannel = attr.field(repr=True) - """Guild channel that this event represents. - - Returns - ------- - hikari.channels.GuildChannel - The guild channel that was deleted. - """ + """Guild channel that this event represents.""" @property def app(self) -> traits.RESTAware: @@ -413,14 +383,8 @@ class PinsUpdateEvent(ChannelEvent, abc.ABC): def last_pin_timestamp(self) -> typing.Optional[datetime.datetime]: """Datetime of when the most recent message was pinned in the channel. - Will be `builtins.None` if nothing is pinned or the information is + Will be `None` if nothing is pinned or the information is unavailable. - - Returns - ------- - typing.Optional[datetime.datetime] - The datetime of the most recent pinned message in the channel, - or `builtins.None` if no pins are available. """ @abc.abstractmethod @@ -470,13 +434,13 @@ class GuildPinsUpdateEvent(PinsUpdateEvent, GuildChannelEvent): def get_channel(self) -> typing.Optional[channels.TextableGuildChannel]: """Get the cached channel that this event relates to, if known. - If not, return `builtins.None`. + If not, return `None`. Returns ------- typing.Optional[hikari.channels.TextableGuildChannel] The cached channel this event relates to. If not known, this - will return `builtins.None` instead. + will return `None` instead. """ channel = super().get_channel() assert channel is None or isinstance(channel, channels.TextableGuildChannel) @@ -579,13 +543,7 @@ class InviteEvent(GuildChannelEvent, abc.ABC): @property @abc.abstractmethod def code(self) -> str: - """Code that is used in the URL for the invite. - - Returns - ------- - builtins.str - The invite code. - """ + """Code that is used in the URL for the invite.""" async def fetch_invite(self) -> invites.Invite: """Perform an API call to retrieve an up-to-date image of this invite. @@ -628,13 +586,7 @@ class InviteCreateEvent(InviteEvent): # <>. invite: invites.InviteWithMetadata = attr.field() - """Invite that was created. - - Returns - ------- - hikari.invites.InviteWithMetadata - The created invite object. - """ + """Invite that was created.""" @property def app(self) -> traits.RESTAware: @@ -683,7 +635,7 @@ class InviteDeleteEvent(InviteEvent): old_invite: typing.Optional[invites.InviteWithMetadata] = attr.field() """Object of the old cached invite. - This will be `builtins.None` if the invite is missing from the cache. + This will be `None` if the invite is missing from the cache. """ if typing.TYPE_CHECKING: diff --git a/hikari/events/guild_events.py b/hikari/events/guild_events.py index e0d48c222f..eb16ebb9c8 100644 --- a/hikari/events/guild_events.py +++ b/hikari/events/guild_events.py @@ -76,13 +76,7 @@ class GuildEvent(shard_events.ShardEvent, abc.ABC): @property @abc.abstractmethod def guild_id(self) -> snowflakes.Snowflake: - """ID of the guild that this event relates to. - - Returns - ------- - hikari.snowflakes.Snowflake - The ID of the guild that relates to this event. - """ + """ID of the guild that this event relates to.""" async def fetch_guild(self) -> guilds.RESTGuild: """Perform an API call to get the guild that this event relates to. @@ -107,12 +101,12 @@ async def fetch_guild_preview(self) -> guilds.GuildPreview: def get_guild(self) -> typing.Optional[guilds.GatewayGuild]: """Get the cached guild that this event relates to, if known. - If not known, this will return `builtins.None` instead. + If not known, this will return `None` instead. Returns ------- typing.Optional[hikari.guilds.GatewayGuild] - The guild this event relates to, or `builtins.None` if not known. + The guild this event relates to, or `None` if not known. """ if not isinstance(self.app, traits.CacheAware): return None @@ -141,7 +135,7 @@ class GuildAvailableEvent(GuildVisibilityEvent): This will occur on startup or after outages. - !!! note + .. note:: Some fields like `members` and `presences` are included here but not on the other `GuildUpdateEvent` and `GuildUnavailableEvent` guild visibility event models. @@ -151,80 +145,33 @@ class GuildAvailableEvent(GuildVisibilityEvent): # <>. guild: guilds.GatewayGuild = attr.field() - """Guild that just became available. - - Returns - ------- - hikari.guilds.Guild - The guild that relates to this event. - """ + """Guild that just became available.""" emojis: typing.Mapping[snowflakes.Snowflake, emojis_.KnownCustomEmoji] = attr.field(repr=False) - """Mapping of emoji IDs to the emojis in the guild. - - Returns - ------- - typing.Mapping[hikari.snowflakes.Snowflake, hikari.emojis.KnownCustomEmoji] - The emojis in the guild. - """ + """Mapping of emoji IDs to the emojis in the guild.""" roles: typing.Mapping[snowflakes.Snowflake, guilds.Role] = attr.field(repr=False) - """Mapping of role IDs to the roles in the guild. - - Returns - ------- - typing.Mapping[hikari.snowflakes.Snowflake, hikari.guilds.Role] - The roles in the guild. - """ + """Mapping of role IDs to the roles in the guild.""" channels: typing.Mapping[snowflakes.Snowflake, channels_.GuildChannel] = attr.field(repr=False) - """Mapping of channel IDs to the channels in the guild. - - Returns - ------- - typing.Mapping[hikari.snowflakes.Snowflake, hikari.channels.GuildChannel] - The channels in the guild. - """ + """Mapping of channel IDs to the channels in the guild.""" members: typing.Mapping[snowflakes.Snowflake, guilds.Member] = attr.field(repr=False) - """Mapping of user IDs to the members in the guild. - - Returns - ------- - typing.Mapping[hikari.snowflakes.Snowflake, hikari.guilds.Member] - The members in the guild. - """ + """Mapping of user IDs to the members in the guild.""" presences: typing.Mapping[snowflakes.Snowflake, presences_.MemberPresence] = attr.field(repr=False) - """Mapping of user IDs to the presences for the guild. - - Returns - ------- - typing.Mapping[hikari.snowflakes.Snowflake, hikari.presences.MemberPresence] - The member presences in the guild. - """ + """Mapping of user IDs to the presences for the guild.""" voice_states: typing.Mapping[snowflakes.Snowflake, voices.VoiceState] = attr.field(repr=False) - """Mapping of user IDs to the voice states active in this guild. - - Returns - ------- - typing.Mapping[hikari.snowflakes.Snowflake, hikari.voices.VoiceState] - The voice states active in the guild. - """ + """Mapping of user IDs to the voice states active in this guild.""" chunk_nonce: typing.Optional[str] = attr.field(repr=False, default=None) """Nonce used to request the member chunks for this guild. - This will be `builtins.None` if no chunks were requested. + This will be `None` if no chunks were requested. - !!! note + .. note:: This is a synthetic field. - - Returns - ------- - typing.Optional[builtins.str] - The nonce used to request the member chunks. """ @property @@ -244,7 +191,7 @@ def guild_id(self) -> snowflakes.Snowflake: class GuildJoinEvent(GuildVisibilityEvent): """Event fired when the bot joins a new guild. - !!! note + .. note:: Some fields like `members` and `presences` are included here but not on the other `GuildUpdateEvent` and `GuildUnavailableEvent` guild visibility event models. @@ -277,9 +224,9 @@ class GuildJoinEvent(GuildVisibilityEvent): chunk_nonce: typing.Optional[str] = attr.field(repr=False, default=None) """Nonce used to request the member chunks for this guild. - This will be `builtins.None` if no chunks were requested. + This will be `None` if no chunks were requested. - !!! note + .. note:: This is a synthetic field. """ @@ -315,7 +262,7 @@ class GuildLeaveEvent(GuildVisibilityEvent): old_guild: typing.Optional[guilds.GatewayGuild] = attr.field() """The old guild object. - This will be `builtins.None` if the guild missing from the cache. + This will be `None` if the guild missing from the cache. """ if typing.TYPE_CHECKING: @@ -352,35 +299,17 @@ class GuildUpdateEvent(GuildEvent): old_guild: typing.Optional[guilds.GatewayGuild] = attr.field() """The old guild object. - This will be `builtins.None` if the guild missing from the cache. + This will be `None` if the guild missing from the cache. """ guild: guilds.GatewayGuild = attr.field() - """Guild that was just updated. - - Returns - ------- - hikari.guilds.Guild - The guild that relates to this event. - """ + """Guild that was just updated.""" emojis: typing.Mapping[snowflakes.Snowflake, emojis_.KnownCustomEmoji] = attr.field(repr=False) - """Mapping of emoji IDs to the emojis in the guild. - - Returns - ------- - typing.Mapping[hikari.snowflakes.Snowflake, hikari.emojis.KnownCustomEmoji] - The emojis in the guild. - """ + """Mapping of emoji IDs to the emojis in the guild.""" roles: typing.Mapping[snowflakes.Snowflake, guilds.Role] = attr.field(repr=False) - """Mapping of role IDs to the roles in the guild. - - Returns - ------- - typing.Mapping[hikari.snowflakes.Snowflake, hikari.guilds.Role] - The roles in the guild. - """ + """Mapping of role IDs to the roles in the guild.""" @property def app(self) -> traits.RESTAware: @@ -407,13 +336,12 @@ def app(self) -> traits.RESTAware: @property @abc.abstractmethod def user(self) -> users.User: - """User that this ban event affects. + """User that this ban event affects.""" - Returns - ------- - hikari.users.User - The user that this event concerns. - """ + @property + def user_id(self) -> snowflakes.Snowflake: + """User ID of the user that got banned.""" + return self.user.id async def fetch_user(self) -> users.User: """Perform an API call to fetch the user this ban event affects. @@ -441,17 +369,6 @@ class BanCreateEvent(BanEvent): user: users.User = attr.field() # <>. - @property - def user_id(self) -> snowflakes.Snowflake: - """User ID of the user that got banned. - - Returns - ------- - hikari.snowflakes.Snowflake - ID of the user the event concerns. - """ - return self.user.id - async def fetch_ban(self) -> guilds.GuildBan: """Perform an API call to fetch the details about this ban. @@ -500,17 +417,11 @@ class EmojisUpdateEvent(GuildEvent): old_emojis: typing.Optional[typing.Sequence[emojis_.KnownCustomEmoji]] = attr.field() """Sequence of all old emojis in this guild. - This will be `builtins.None` if it's missing from the cache. + This will be `None` if it's missing from the cache. """ emojis: typing.Sequence[emojis_.KnownCustomEmoji] = attr.field() - """Sequence of all emojis in this guild. - - Returns - ------- - typing.Sequence[emojis_.KnownCustomEmoji] - All emojis in the guild. - """ + """Sequence of all emojis in this guild.""" async def fetch_emojis(self) -> typing.Sequence[emojis_.KnownCustomEmoji]: """Perform an API call to retrieve an up-to-date view of the emojis. @@ -532,34 +443,22 @@ class IntegrationEvent(GuildEvent, abc.ABC): @property @abc.abstractmethod def application_id(self) -> typing.Optional[snowflakes.Snowflake]: - """ID of Discord bot application this integration is connected to. - - Returns - ------- - typing.Optional[hikari.snowflakes.Snowflake] - The ID of Discord bot application this integration is connected to. - """ + """ID of Discord bot application this integration is connected to.""" @property @abc.abstractmethod def id(self) -> snowflakes.Snowflake: - """ID of the integration. - - Returns - ------- - hikari.snowflakes.Snowflake - The ID of the integration. - """ + """ID of the integration.""" async def fetch_integrations(self) -> typing.Sequence[guilds.Integration]: """Perform an API call to fetch some number of guild integrations. - !!! warning + .. warning:: The results of this are not clearly defined by Discord. The current behaviour appears to be that only the first 50 integrations actually get returned. Discord have made it clear that they are not willing to fix this in - https://github.com/discord/discord-api-docs/issues/1990. + . Returns ------- @@ -677,17 +576,11 @@ class PresenceUpdateEvent(shard_events.ShardEvent): old_presence: typing.Optional[presences_.MemberPresence] = attr.field() """The old member presence object. - This will be `builtins.None` if the member presence missing from the cache. + This will be `None` if the member presence missing from the cache. """ presence: presences_.MemberPresence = attr.field() - """Member presence. - - Returns - ------- - hikari.presences.MemberPresence - Presence for the user in this guild. - """ + """Member presence.""" user: typing.Optional[users.PartialUser] = attr.field() """User that was updated. @@ -695,14 +588,9 @@ class PresenceUpdateEvent(shard_events.ShardEvent): This is a partial user object that only contains the fields that were updated on the user profile. - Will be `builtins.None` if the user itself did not change. + Will be `None` if the user itself did not change. This is always the case if the user only updated their member representation and did not change their user profile directly. - - Returns - ------- - typing.Optional[hikari.users.PartialUser] - The partial user containing the updated fields. """ @property @@ -712,24 +600,12 @@ def app(self) -> traits.RESTAware: @property def user_id(self) -> snowflakes.Snowflake: - """User ID of the user that updated their presence. - - Returns - ------- - hikari.snowflakes.Snowflake - ID of the user the event concerns. - """ + """User ID of the user that updated their presence.""" return self.presence.user_id @property def guild_id(self) -> snowflakes.Snowflake: - """Guild ID that the presence was updated in. - - Returns - ------- - hikari.snowflakes.Snowflake - ID of the guild the event occurred in. - """ + """Guild ID that the presence was updated in.""" return self.presence.guild_id def get_user(self) -> typing.Optional[users.User]: @@ -738,7 +614,7 @@ def get_user(self) -> typing.Optional[users.User]: Returns ------- typing.Optional[hikari.users.User] - The full cached user, or `builtins.None` if not cached. + The full cached user, or `None` if not cached. """ if not isinstance(self.app, traits.CacheAware): return None diff --git a/hikari/events/interaction_events.py b/hikari/events/interaction_events.py index be84606d21..74c495f06f 100644 --- a/hikari/events/interaction_events.py +++ b/hikari/events/interaction_events.py @@ -49,13 +49,7 @@ class InteractionCreateEvent(shard_events.ShardEvent): """Shard that received this event.""" interaction: base_interactions.PartialInteraction = attr.field(repr=True) - """Interaction that this event is related to. - - Returns - ------- - hikari.interactions.base_interactions.PartialInteraction - Object of the interaction that this event is related to. - """ + """Interaction that this event is related to.""" @property def app(self) -> traits.RESTAware: diff --git a/hikari/events/lifetime_events.py b/hikari/events/lifetime_events.py index 4538b935dc..b3793cf38b 100644 --- a/hikari/events/lifetime_events.py +++ b/hikari/events/lifetime_events.py @@ -50,7 +50,7 @@ class StartingEvent(base_events.Event): opening database connections and other resources that need to be initialized within a coroutine function. - !!! warning + .. warning:: The application will not proceed to connect to Discord until all event handlers for this event have completed/terminated. This prevents the risk of race conditions occurring (e.g. allowing message events @@ -92,7 +92,7 @@ class StoppingEvent(base_events.Event): closing database connections and other resources that need to be closed within a coroutine function. - !!! warning + .. warning:: The application will not proceed to disconnect from Discord until all event handlers for this event have completed/terminated. This prevents the risk of race conditions occurring from code that relies @@ -115,7 +115,7 @@ class StoppedEvent(base_events.Event): closing database connections and other resources that need to be closed within a coroutine function. - !!! warning + .. warning:: The application will not proceed to leave the `bot.run` call until all event handlers for this event have completed/terminated. This prevents the risk of race conditions occurring where a script may diff --git a/hikari/events/member_events.py b/hikari/events/member_events.py index c6f1d5a561..904a14e27f 100644 --- a/hikari/events/member_events.py +++ b/hikari/events/member_events.py @@ -62,46 +62,28 @@ def app(self) -> traits.RESTAware: @property @abc.abstractmethod def guild_id(self) -> snowflakes.Snowflake: - """ID of the guild that this event relates to. - - Returns - ------- - hikari.snowflakes.Snowflake - The ID of the guild that relates to this event. - """ + """ID of the guild that this event relates to.""" @property @abc.abstractmethod def user(self) -> users.User: - """User object for the member this event concerns. - - Returns - ------- - hikari.users.User - User object for the member this event concerns. - """ + """User object for the member this event concerns.""" @property def user_id(self) -> snowflakes.Snowflake: - """ID of the user that this event concerns. - - Returns - ------- - hikari.snowflakes.Snowflake - The ID of the user that this event relates to. - """ + """ID of the user that this event concerns.""" return self.user.id def get_guild(self) -> typing.Optional[guilds.GatewayGuild]: """Get the cached view of the guild this member event occurred in. - If the guild itself is not cached, this will return `builtins.None`. + If the guild itself is not cached, this will return `None`. Returns ------- typing.Optional[hikari.guilds.GatewayGuild] The guild that this event occurred in, if known, else - `builtins.None`. + `None`. """ if not isinstance(self.app, traits.CacheAware): return None @@ -119,13 +101,7 @@ class MemberCreateEvent(MemberEvent): # <>. member: guilds.Member = attr.field() - """Member object for the member that joined the guild. - - Returns - ------- - hikari.guilds.Member - The member object for the member who just joined. - """ + """Member object for the member that joined the guild.""" @property def guild_id(self) -> snowflakes.Snowflake: @@ -153,17 +129,11 @@ class MemberUpdateEvent(MemberEvent): old_member: typing.Optional[guilds.Member] = attr.field() """The old member object. - This will be `builtins.None` if the member missing from the cache. + This will be `None` if the member missing from the cache. """ member: guilds.Member = attr.field() - """Member object for the member that was updated. - - Returns - ------- - hikari.guilds.Member - The member object for the member that was updated. - """ + """Member object for the member that was updated.""" @property def guild_id(self) -> snowflakes.Snowflake: @@ -194,5 +164,5 @@ class MemberDeleteEvent(MemberEvent): old_member: typing.Optional[guilds.Member] = attr.field() """The old member object. - This will be `builtins.None` if the member was missing from the cache. + This will be `None` if the member was missing from the cache. """ diff --git a/hikari/events/message_events.py b/hikari/events/message_events.py index 31c23186e3..e4abaa38fb 100644 --- a/hikari/events/message_events.py +++ b/hikari/events/message_events.py @@ -69,24 +69,12 @@ class MessageEvent(shard_events.ShardEvent, abc.ABC): @property @abc.abstractmethod def channel_id(self) -> snowflakes.Snowflake: - """ID of the channel that this event concerns. - - Returns - ------- - hikari.snowflakes.Snowflake - The ID of the channel that this event concerns. - """ + """ID of the channel that this event concerns.""" @property @abc.abstractmethod def message_id(self) -> snowflakes.Snowflake: - """ID of the message that this event concerns. - - Returns - ------- - hikari.snowflakes.Snowflake - The ID of the message that this event concerns. - """ + """ID of the message that this event concerns.""" @base_events.requires_intents(intents.Intents.DM_MESSAGES, intents.Intents.GUILD_MESSAGES) @@ -102,24 +90,12 @@ def app(self) -> traits.RESTAware: @property def author(self) -> users.User: - """User that sent the message. - - Returns - ------- - hikari.users.User - The user that sent the message. - """ + """User that sent the message.""" return self.message.author @property def author_id(self) -> snowflakes.Snowflake: - """ID of the author of the message this event concerns. - - Returns - ------- - hikari.snowflakes.Snowflake - The ID of the author. - """ + """ID of the author of the message this event concerns.""" return self.author.id @property @@ -131,81 +107,41 @@ def channel_id(self) -> snowflakes.Snowflake: def content(self) -> typing.Optional[str]: """Content of the message. - Returns - ------- - typing.Optional[builtins.str] - The content of the message, if present. This may be `builtins.None` - or an empty string (or any falsy value) if no content is present - (e.g. if only an embed was sent). + The content of the message, if present. This will be `None` + if no content is present (e.g. if only an embed was sent). """ return self.message.content @property def embeds(self) -> typing.Sequence[embeds_.Embed]: - """Sequence of embeds in the message. - - Returns - ------- - typing.Sequence[hikari.embeds.Embed] - The embeds in the message. - """ + """Sequence of embeds in the message.""" return self.message.embeds @property def is_bot(self) -> bool: - """Return `builtins.True` if the message is from a bot. - - Returns - ------- - builtins.bool - `builtins.True` if from a bot, or `builtins.False` otherwise. - """ + """Return `True` if the message is from a bot.""" return self.message.author.is_bot @property def is_human(self) -> bool: - """Return `builtins.True` if the message was created by a human. - - Returns - ------- - builtins.bool - `builtins.True` if from a human user, or `builtins.False` otherwise. - """ + """Return `True` if the message was created by a human.""" # Not second-guessing some weird edge case will occur in the future with this, # so I am being safe rather than sorry. return not self.message.author.is_bot and self.message.webhook_id is None @property def is_webhook(self) -> bool: - """Return `builtins.True` if the message was created by a webhook. - - Returns - ------- - builtins.bool - `builtins.True` if from a webhook, or `builtins.False` otherwise. - """ + """Return `True` if the message was created by a webhook.""" return self.message.webhook_id is not None @property @abc.abstractmethod def message(self) -> messages.Message: - """Message that was sent in the event. - - Returns - ------- - hikari.messages.Message - The message object that was sent with this event. - """ + """Message that was sent in the event.""" @property def message_id(self) -> snowflakes.Snowflake: - """ID of the message that this event concerns. - - Returns - ------- - hikari.snowflakes.Snowflake - The ID of the message that this event concerns. - """ + """ID of the message that this event concerns.""" return self.message.id @@ -226,36 +162,17 @@ class GuildMessageCreateEvent(MessageCreateEvent): @property def author(self) -> users.User: - """User object of the user that sent the message. - - Returns - ------- - hikari.users.User - The user object of the user that sent the message. - """ + """User object of the user that sent the message.""" return self.message.author @property def member(self) -> typing.Optional[guilds.Member]: - """Member object of the user that sent the message. - - Returns - ------- - typing.Optional[hikari.guilds.Member] - The member object of the user that sent the message or - `builtins.None` if sent by a webhook. - """ + """Member object of the user that sent the message.""" return self.message.member @property def guild_id(self) -> snowflakes.Snowflake: - """ID of the guild that this event occurred in. - - Returns - ------- - hikari.snowflakes.Snowflake - The ID of the guild that this event occurred in. - """ + """ID of the guild that this event occurred in.""" guild_id = self.message.guild_id # Always present on guild events assert isinstance(guild_id, snowflakes.Snowflake), "no guild_id attribute set" @@ -268,7 +185,7 @@ def get_channel(self) -> typing.Optional[channels.TextableGuildChannel]: ------- typing.Optional[hikari.channels.TextableGuildChannel] The channel that the message was sent in, if known and cached, - otherwise, `builtins.None`. + otherwise, `None`. """ if not isinstance(self.app, traits.CacheAware): return None @@ -282,7 +199,7 @@ def get_channel(self) -> typing.Optional[channels.TextableGuildChannel]: def get_guild(self) -> typing.Optional[guilds.GatewayGuild]: """Get the cached guild that this event occurred in, if known. - !!! note + .. note:: This will require the `GUILDS` intent to be specified on start-up in order to be known. @@ -290,7 +207,7 @@ def get_guild(self) -> typing.Optional[guilds.GatewayGuild]: ------- typing.Optional[hikari.guilds.GatewayGuild] The guild that this event occurred in, if cached. Otherwise, - `builtins.None` instead. + `None` instead. """ if not isinstance(self.app, traits.CacheAware): return None @@ -331,7 +248,7 @@ class DMMessageCreateEvent(MessageCreateEvent): class MessageUpdateEvent(MessageEvent, abc.ABC): """Event that is fired when a message is updated. - !!! note + .. note:: Less information will be available here than in the creation event due to Discord limitations. """ @@ -371,13 +288,11 @@ def channel_id(self) -> snowflakes.Snowflake: def content(self) -> undefined.UndefinedNoneOr[str]: """Content of the message. - Returns - ------- - hikari.undefined.UndefinedNoneOr[builtins.str] - The content of the message, if present. This may be `builtins.None` - or an empty string (or any falsy value) if no content is present - (e.g. if only an embed was sent). If not part of the update, then - this will be `hikari.undefined.UNDEFINED` instead. + The content of the message, if present. This may be `None` + if no content is present (e.g. if only an embed was sent). + + If not part of the update, then this will be + `hikari.undefined.UNDEFINED` instead. """ return self.message.content @@ -385,26 +300,18 @@ def content(self) -> undefined.UndefinedNoneOr[str]: def embeds(self) -> undefined.UndefinedOr[typing.Sequence[embeds_.Embed]]: """Sequence of embeds in the message. - Returns - ------- - hikari.undefined.UndefinedOr[typing.Sequence[hikari.embeds.Embed]] - The embeds in the message. If the embeds were not changed in this - event, then this may instead be `hikari.undefined.UNDEFINED`. + If the embeds were not changed in this event, then this may instead be + `hikari.undefined.UNDEFINED`. """ return self.message.embeds @property def is_bot(self) -> undefined.UndefinedOr[bool]: - """Return `builtins.True` if the message is from a bot. + """Whether the message is from a bot. - Returns - ------- - typing.Optional[builtins.bool] - `builtins.True` if from a bot, or `builtins.False` otherwise. - - If the author is not known, due to the update event being caused - by Discord adding an embed preview to accompany a URL, then this - will return `hikari.undefined.UNDEFINED` instead. + If the author is not known, due to the update event being caused + by Discord adding an embed preview to accompany a URL, then this + will return `hikari.undefined.UNDEFINED` instead. """ if (author := self.message.author) is not undefined.UNDEFINED: return author.is_bot @@ -413,16 +320,11 @@ def is_bot(self) -> undefined.UndefinedOr[bool]: @property def is_human(self) -> undefined.UndefinedOr[bool]: - """Return `builtins.True` if the message was created by a human. - - Returns - ------- - typing.Optional[builtins.bool] - `builtins.True` if from a human user, or `builtins.False` otherwise. + """Whether the message was created by a human. - If the author is not known, due to the update event being caused - by Discord adding an embed preview to accompany a URL, then this - may return `hikari.undefined.UNDEFINED` instead. + If the author is not known, due to the update event being caused + by Discord adding an embed preview to accompany a URL, then this + may return `hikari.undefined.UNDEFINED` instead. """ # Not second-guessing some weird edge case will occur in the future with this, # so I am being safe rather than sorry. @@ -436,12 +338,11 @@ def is_human(self) -> undefined.UndefinedOr[bool]: @property def is_webhook(self) -> undefined.UndefinedOr[bool]: - """Return `builtins.True` if the message was created by a webhook. + """Whether the message was created by a webhook. - Returns - ------- - builtins.bool - `builtins.True` if from a webhook, or `builtins.False` otherwise. + If the author is not known, due to the update event being caused + by Discord adding an embed preview to accompany a URL, then this + may return `hikari.undefined.UNDEFINED` instead. """ if (webhook_id := self.message.webhook_id) is not undefined.UNDEFINED: return webhook_id is not None @@ -451,23 +352,11 @@ def is_webhook(self) -> undefined.UndefinedOr[bool]: @property @abc.abstractmethod def message(self) -> messages.PartialMessage: - """Partial message that was sent in the event. - - Returns - ------- - hikari.messages.PartialMessage - The partial message object that was sent with this event. - """ + """Partial message that was sent in the event.""" @property def message_id(self) -> snowflakes.Snowflake: - """ID of the message that this event concerns. - - Returns - ------- - hikari.snowflakes.Snowflake - The ID of the message that this event concerns. - """ + """ID of the message that this event concerns.""" return self.message.id @@ -477,7 +366,7 @@ def message_id(self) -> snowflakes.Snowflake: class GuildMessageUpdateEvent(MessageUpdateEvent): """Event that is fired when a message is updated in a guild. - !!! note + .. note:: Less information will be available here than in the creation event due to Discord limitations. """ @@ -485,7 +374,7 @@ class GuildMessageUpdateEvent(MessageUpdateEvent): old_message: typing.Optional[messages.PartialMessage] = attr.field() """The old message object. - This will be `builtins.None` if the message missing from the cache. + This will be `None` if the message missing from the cache. """ message: messages.PartialMessage = attr.field() @@ -498,7 +387,7 @@ class GuildMessageUpdateEvent(MessageUpdateEvent): def member(self) -> undefined.UndefinedNoneOr[guilds.Member]: """Member that sent the message if provided by the event. - If the message is not in a guild, this will be `builtins.None`. + If the message is not in a guild, this will be `None`. This will also be `hikari.undefined.UNDEFINED` in some cases such as when Discord updates a message with an embed URL preview. @@ -520,13 +409,7 @@ def get_member(self) -> typing.Optional[guilds.Member]: @property def guild_id(self) -> snowflakes.Snowflake: - """ID of the guild that this event occurred in. - - Returns - ------- - hikari.snowflakes.Snowflake - The ID of the guild that this event occurred in. - """ + """ID of the guild that this event occurred in.""" guild_id = self.message.guild_id # Always present on guild events assert isinstance(guild_id, snowflakes.Snowflake), f"expected guild_id, got {guild_id}" @@ -539,7 +422,7 @@ def get_channel(self) -> typing.Optional[channels.TextableGuildChannel]: ------- typing.Optional[hikari.channels.TextableGuildChannel] The channel that the message was sent in, if known and cached, - otherwise, `builtins.None`. + otherwise, `None`. """ if not isinstance(self.app, traits.CacheAware): return None @@ -553,7 +436,7 @@ def get_channel(self) -> typing.Optional[channels.TextableGuildChannel]: def get_guild(self) -> typing.Optional[guilds.GatewayGuild]: """Get the cached guild that this event occurred in, if known. - !!! note + .. note:: This will require the `GUILDS` intent to be specified on start-up in order to be known. @@ -561,7 +444,7 @@ def get_guild(self) -> typing.Optional[guilds.GatewayGuild]: ------- typing.Optional[hikari.guilds.GatewayGuild] The guild that this event occurred in, if cached. Otherwise, - `builtins.None` instead. + `None` instead. """ if not isinstance(self.app, traits.CacheAware): return None @@ -575,7 +458,7 @@ def get_guild(self) -> typing.Optional[guilds.GatewayGuild]: class DMMessageUpdateEvent(MessageUpdateEvent): """Event that is fired when a message is updated in a DM. - !!! note + .. note:: Less information will be available here than in the creation event due to Discord limitations. """ @@ -583,7 +466,7 @@ class DMMessageUpdateEvent(MessageUpdateEvent): old_message: typing.Optional[messages.PartialMessage] = attr.field() """The old message object. - This will be `builtins.None` if the message missing from the cache. + This will be `None` if the message missing from the cache. """ message: messages.PartialMessage = attr.field() @@ -597,7 +480,7 @@ class DMMessageUpdateEvent(MessageUpdateEvent): class MessageDeleteEvent(MessageEvent, abc.ABC): """Special event that is triggered when a message gets deleted. - !!! note + .. note:: Due to Discord limitations, most message information is unavailable during deletion events. """ @@ -624,7 +507,7 @@ def old_message(self) -> typing.Optional[messages.Message]: class GuildMessageDeleteEvent(MessageDeleteEvent): """Event that is triggered if a message is deleted in a guild. - !!! note + .. note:: Due to Discord limitations, most message information is unavailable during deletion events. """ @@ -653,7 +536,7 @@ def get_channel(self) -> typing.Optional[channels.TextableGuildChannel]: Returns ------- typing.Optional[hikari.channels.TextableGuildChannel] - The channel the messages were sent in, or `builtins.None` if not + The channel the messages were sent in, or `None` if not known/cached. """ if not isinstance(self.app, traits.CacheAware): @@ -668,7 +551,7 @@ def get_channel(self) -> typing.Optional[channels.TextableGuildChannel]: def get_guild(self) -> typing.Optional[guilds.GatewayGuild]: """Get the cached guild this event corresponds to, if known. - !!! note + .. note:: You will need `hikari.intents.Intents.GUILDS` enabled to receive this information. @@ -690,7 +573,7 @@ def get_guild(self) -> typing.Optional[guilds.GatewayGuild]: class DMMessageDeleteEvent(MessageDeleteEvent): """Event that is triggered if a message is deleted in a DM. - !!! note + .. note:: Due to Discord limitations, most message information is unavailable during deletion events. """ @@ -717,7 +600,7 @@ class DMMessageDeleteEvent(MessageDeleteEvent): class GuildBulkMessageDeleteEvent(shard_events.ShardEvent): """Event that is triggered when a bulk deletion is triggered in a guild. - !!! note + .. note: Due to Discord limitations, most message information is unavailable during deletion events. """ @@ -749,7 +632,7 @@ def get_channel(self) -> typing.Optional[channels.TextableGuildChannel]: Returns ------- typing.Optional[hikari.channels.TextableGuildChannel] - The channel the messages were sent in, or `builtins.None` if not + The channel the messages were sent in, or `None` if not known/cached. """ if not isinstance(self.app, traits.CacheAware): @@ -764,7 +647,7 @@ def get_channel(self) -> typing.Optional[channels.TextableGuildChannel]: def get_guild(self) -> typing.Optional[guilds.GatewayGuild]: """Get the cached guild this event corresponds to, if known. - !!! note + .. note:: You will need `hikari.intents.Intents.GUILDS` enabled to receive this information. diff --git a/hikari/events/reaction_events.py b/hikari/events/reaction_events.py index 2d438adf1e..ec6e053f35 100644 --- a/hikari/events/reaction_events.py +++ b/hikari/events/reaction_events.py @@ -69,24 +69,12 @@ class ReactionEvent(shard_events.ShardEvent, abc.ABC): @property @abc.abstractmethod def channel_id(self) -> snowflakes.Snowflake: - """ID of the channel that this event concerns. - - Returns - ------- - hikari.snowflakes.Snowflake - The ID of the channel that this event concerns. - """ + """ID of the channel that this event concerns.""" @property @abc.abstractmethod def message_id(self) -> snowflakes.Snowflake: - """ID of the message that this event concerns. - - Returns - ------- - hikari.snowflakes.Snowflake - The ID of the message that this event concerns. - """ + """ID of the message that this event concerns.""" @base_events.requires_intents(intents.Intents.GUILD_MESSAGE_REACTIONS) @@ -98,13 +86,7 @@ class GuildReactionEvent(ReactionEvent, abc.ABC): @property @abc.abstractmethod def guild_id(self) -> snowflakes.Snowflake: - """ID of the guild that this event concerns. - - Returns - ------- - hikari.snowflakes.Snowflake - The ID of the guild that this event concerns. - """ + """ID of the guild that this event concerns.""" @base_events.requires_intents(intents.Intents.DM_MESSAGE_REACTIONS) @@ -123,66 +105,42 @@ class ReactionAddEvent(ReactionEvent, abc.ABC): @property @abc.abstractmethod def user_id(self) -> snowflakes.Snowflake: - """ID of the user that added this reaction. - - Returns - ------- - hikari.snowflakes.Snowflake - The ID of the user that added this reaction. - """ + """ID of the user that added this reaction.""" @property @abc.abstractmethod def emoji_name(self) -> typing.Union[emojis.UnicodeEmoji, str, None]: """Name of the emoji which was added if known. - !!! note - This will be `builtins.None` when the relevant custom emoji's data - is not available (e.g. the emoji has been deleted). - - Returns - ------- - typing.Union[hikari.emojis.UnicodeEmoji, builtins.str, builtins.None] - Either the string name of the custom emoji which was added - or the object of the `hikari.emojis.UnicodeEmoji` which was added. + This can either be the string name of the custom emoji which was added, + the object of the `hikari.emojis.UnicodeEmoji` which was added or + `None` when the relevant custom emoji's data is not available + (e.g. the emoji has been deleted). """ @property @abc.abstractmethod def emoji_id(self) -> typing.Optional[snowflakes.Snowflake]: - """ID of the emoji which was added if it is custom. - - Returns - ------- - typing.Optional[hikari.snowflakes.Snowflake] - ID of the emoji which was added if it was a custom emoji or - `builtins.None`. - """ + """ID of the emoji which was added if it is custom, else `None`.""" @property @abc.abstractmethod def is_animated(self) -> bool: - """Whether the emoji which was added is animated. - - Returns - ------- - builtins.bool - Whether the emoji which was added is animated. - """ + """Whether the emoji which was added is animated.""" def is_for_emoji(self, emoji: typing.Union[emojis.Emoji, str], /) -> bool: """Get whether the reaction event is for a specific emoji. Parameters ---------- - emoji : typing.Union[hikari.emojis.Emoji, builtins.str] + emoji : typing.Union[hikari.emojis.Emoji, str] The emoji to check. - Passing `builtins.str` here indicates a unicode emoji. + Passing `str` here indicates a unicode emoji. Returns ------- - builtins.bool + bool Whether the emoji is the one which was added. """ return emoji.id == self.emoji_id if isinstance(emoji, emojis.CustomEmoji) else emoji == self.emoji_name @@ -197,55 +155,37 @@ class ReactionDeleteEvent(ReactionEvent, abc.ABC): @property @abc.abstractmethod def user_id(self) -> snowflakes.Snowflake: - """User ID for the user that added this reaction initially. - - Returns - ------- - hikari.snowflakes.Snowflake - The ID of the user that removed this reaction. - """ + """User ID for the user that added this reaction initially.""" @property @abc.abstractmethod def emoji_name(self) -> typing.Union[emojis.UnicodeEmoji, str, None]: """Name of the emoji which was removed. - !!! note - This will be `builtins.None` when the relevant custom emoji's data - is not available (e.g. the emoji has been deleted). - - Returns - ------- - typing.Union[hikari.emojis.UnicodeEmoji, builtins.str, builtins.None] - Either the string name of the custom emoji which was removed - or the object of the `hikari.emojis.UnicodeEmoji` which was removed. + Either the string name of the custom emoji which was removed, the object + of the `hikari.emojis.UnicodeEmoji` which was removed or `None` + when the relevant custom emoji's data is not available (e.g. the emoji + has been deleted). """ @property @abc.abstractmethod def emoji_id(self) -> typing.Optional[snowflakes.Snowflake]: - """ID of the emoji which was removed if it was custom. - - Returns - ------- - typing.Optional[hikari.snowflakes.Snowflake] - ID of the emoji which was removed if it was a custom emoji or - `builtins.None`. - """ + """ID of the emoji which was added if it is custom, else `None`.""" def is_for_emoji(self, emoji: typing.Union[emojis.Emoji, str], /) -> bool: """Get whether the reaction event is for a specific emoji. Parameters ---------- - emoji : typing.Union[hikari.emojis.Emoji, builtins.str] + emoji : typing.Union[hikari.emojis.Emoji, str] The emoji to check. - Passing `builtins.str` here indicates a unicode emoji. + Passing `str` here indicates a unicode emoji. Returns ------- - builtins.bool + bool Whether the emoji is the one which was removed. """ return emoji.id == self.emoji_id if isinstance(emoji, emojis.CustomEmoji) else emoji == self.emoji_name @@ -267,44 +207,32 @@ class ReactionDeleteEmojiEvent(ReactionEvent, abc.ABC): @property @abc.abstractmethod def emoji_name(self) -> typing.Union[emojis.UnicodeEmoji, str, None]: - """Name of the emoji which was removed if known. - - !!! note - This will be `builtins.None` when the relevant custom emoji's data - is not available (e.g. the emoji has been deleted). + """Name of the emoji which was removed. - Returns - ------- - typing.Union[hikari.emojis.UnicodeEmoji, builtins.str, builtins.None] - Either the string name of the custom emoji which was removed - or the object of the `hikari.emojis.UnicodeEmoji` which was removed. + Either the string name of the custom emoji which was removed, the object + of the `hikari.emojis.UnicodeEmoji` which was removed or `None` + when the relevant custom emoji's data is not available (e.g. the emoji + has been deleted). """ @property @abc.abstractmethod def emoji_id(self) -> typing.Optional[snowflakes.Snowflake]: - """ID of the emoji which was removed if it was custom. - - Returns - ------- - typing.Optional[hikari.snowflakes.Snowflake] - ID of the emoji which was removed if it was a custom emoji or - `builtins.None`. - """ + """ID of the emoji which was added if it is custom, else `None`.""" def is_for_emoji(self, emoji: typing.Union[emojis.Emoji, str], /) -> bool: """Get whether the reaction event is for a specific emoji. Parameters ---------- - emoji : typing.Union[hikari.emojis.Emoji, builtins.str] + emoji : typing.Union[hikari.emojis.Emoji, str] The emoji to check. - Passing `builtins.str` here indicates a unicode emoji. + Passing `str` here indicates a unicode emoji. Returns ------- - builtins.bool + bool Whether the emoji is the one which was removed. """ return emoji.id == self.emoji_id if isinstance(emoji, emojis.CustomEmoji) else emoji == self.emoji_name @@ -320,13 +248,7 @@ class GuildReactionAddEvent(GuildReactionEvent, ReactionAddEvent): # <>. member: guilds.Member = attr.field() - """Member that added the reaction. - - Returns - ------- - hikari.guilds.Member - The member which added this reaction. - """ + """Member that added the reaction.""" channel_id: snowflakes.Snowflake = attr.field() # <>. diff --git a/hikari/events/role_events.py b/hikari/events/role_events.py index d844f5e882..c7585bf9d2 100644 --- a/hikari/events/role_events.py +++ b/hikari/events/role_events.py @@ -56,24 +56,12 @@ class RoleEvent(shard_events.ShardEvent, abc.ABC): @property @abc.abstractmethod def guild_id(self) -> snowflakes.Snowflake: - """ID of the guild that this event relates to. - - Returns - ------- - hikari.snowflakes.Snowflake - The ID of the guild that relates to this event. - """ + """ID of the guild that this event relates to.""" @property @abc.abstractmethod def role_id(self) -> snowflakes.Snowflake: - """ID of the role that this event relates to. - - Returns - ------- - hikari.snowflakes.Snowflake - The ID of the role that relates to this event. - """ + """ID of the role that this event relates to.""" @attr_extensions.with_copy @@ -86,13 +74,7 @@ class RoleCreateEvent(RoleEvent): # <>. role: guilds.Role = attr.field() - """Role that was created. - - Returns - ------- - hikari.guilds.Role - The created role. - """ + """Role that was created.""" @property def app(self) -> traits.RESTAware: @@ -122,17 +104,11 @@ class RoleUpdateEvent(RoleEvent): old_role: typing.Optional[guilds.Role] = attr.field() """The old role object. - This will be `builtins.None` if the role missing from the cache. + This will be `None` if the role missing from the cache. """ role: guilds.Role = attr.field() - """Role that was updated. - - Returns - ------- - hikari.guilds.Role - The created role. - """ + """Role that was updated.""" @property def app(self) -> traits.RESTAware: @@ -171,5 +147,5 @@ class RoleDeleteEvent(RoleEvent): old_role: typing.Optional[guilds.Role] = attr.field() """The old role object. - This will be `builtins.None` if the role was missing from the cache. + This will be `None` if the role was missing from the cache. """ diff --git a/hikari/events/shard_events.py b/hikari/events/shard_events.py index ffadea504c..5be6b386ba 100644 --- a/hikari/events/shard_events.py +++ b/hikari/events/shard_events.py @@ -62,13 +62,7 @@ class ShardEvent(base_events.Event, abc.ABC): @property @abc.abstractmethod def shard(self) -> gateway_shard.GatewayShard: - """Shard that received this event. - - Returns - ------- - hikari.api.shard.GatewayShard - The shard that triggered the event. - """ + """Shard that received this event.""" @attr_extensions.with_copy @@ -76,7 +70,7 @@ def shard(self) -> gateway_shard.GatewayShard: class ShardPayloadEvent(ShardEvent): """Event fired for most shard events with their raw payload. - !!! note + .. note:: This will only be dispatched for real dispatch events received from Discord and not artificial events like the `ShardStateEvent` events. """ @@ -136,61 +130,26 @@ class ShardReadyEvent(ShardStateEvent): # <>. actual_gateway_version: int = attr.field(repr=True) - """Actual gateway version being used. - - Returns - ------- - builtins.int - The actual gateway version we are actively using for this protocol. - """ + """Actual gateway version being used.""" session_id: str = attr.field(repr=True) - """ID for this session. - - Returns - ------- - builtins.str - The session ID for this gateway session. - """ + """ID for this session.""" my_user: users.OwnUser = attr.field(repr=True) - """User for the current bot account this connection is authenticated with. - - Returns - ------- - hikari.users.OwnUser - This bot's user. - """ + """User for the current bot account this connection is authenticated with.""" unavailable_guilds: typing.Sequence[snowflakes.Snowflake] = attr.field(repr=False) """Sequence of the IDs for all guilds this bot is currently in. All guilds will start off "unavailable" and should become available after a few seconds of connecting one-by-one. - - Returns - ------- - typing.Sequence[hikari.snowflakes.Snowflake] - All guild IDs that the bot is in for this shard. """ application_id: snowflakes.Snowflake = attr.field(repr=True) - """ID of the application this ready event is for. - - Returns - ------- - hikari.snowflakes.Snowflake - The current application's ID. - """ + """ID of the application this ready event is for.""" application_flags: applications.ApplicationFlags = attr.field(repr=True) - """Flags of the application this ready event is for. - - Returns - ------- - hikari.applications.ApplicationFlags - The current application's flags. - """ + """Flags of the application this ready event is for.""" @property def app(self) -> traits.RESTAware: @@ -225,65 +184,33 @@ class MemberChunkEvent(ShardEvent, typing.Sequence["guilds.Member"]): # <>. members: typing.Mapping[snowflakes.Snowflake, guilds.Member] = attr.field(repr=False) - """Mapping of user IDs to the objects of the members in this chunk. - - Returns - ------- - typing.Mapping[hikari.snowflakes.Snowflake, hikari.guilds.Member] - Mapping of user IDs to corresponding member objects. - """ + """Mapping of user IDs to the objects of the members in this chunk.""" chunk_index: int = attr.field(repr=True) - """Zero-indexed position of this within the queued up chunks for this request. - - Returns - ------- - builtins.int - The sequence index for this chunk. - """ + """Zero-indexed position of this within the queued up chunks for this request.""" chunk_count: int = attr.field(repr=True) - """Total number of expected chunks for the request this is associated with. - - Returns - ------- - builtins.int - Total number of chunks to be expected. - """ + """Total number of expected chunks for the request this is associated with.""" not_found: typing.Sequence[snowflakes.Snowflake] = attr.field(repr=True) """Sequence of the snowflakes that were not found while making this request. This is only applicable when user IDs are specified while making the member request the chunk is associated with. - - Returns - ------- - typing.Sequence[hikari.snowflakes.Snowflake] - Sequence of user IDs that were not found. """ presences: typing.Mapping[snowflakes.Snowflake, presences_.MemberPresence] = attr.field(repr=False) """Mapping of user IDs to found member presence objects. This will be empty if no presences are found or `include_presences` is not passed as - `builtins.True` while requesting the member chunks. - - Returns - ------- - typing.Mapping[hikari.snowflakes.Snowflake, hikari.presences.MemberPresence] - Mapping of user IDs to corresponding presences. + `True` while requesting the member chunks. """ nonce: typing.Optional[str] = attr.field(repr=True) """String nonce used to identify the request member chunks are associated with. - This is the nonce value passed while requesting member chunks. - - Returns - ------- - typing.Optional[builtins.str] - The request nonce if set, or `builtins.None` otherwise. + This is the nonce value passed while requesting member chunks or `None` + if there was no nonce passed. """ @typing.overload diff --git a/hikari/events/typing_events.py b/hikari/events/typing_events.py index 5b9ad41e29..d7587a1b6a 100644 --- a/hikari/events/typing_events.py +++ b/hikari/events/typing_events.py @@ -60,35 +60,17 @@ class TypingEvent(shard_events.ShardEvent, abc.ABC): @property @abc.abstractmethod def channel_id(self) -> snowflakes.Snowflake: - """ID of the channel that this event concerns. - - Returns - ------- - hikari.snowflakes.Snowflake - The ID of the channel that this event concerns. - """ + """ID of the channel that this event concerns.""" @property @abc.abstractmethod def user_id(self) -> snowflakes.Snowflake: - """ID of the user who triggered this typing event. - - Returns - ------- - hikari.snowflakes.Snowflake - ID of the user who is typing. - """ + """ID of the user who triggered this typing event.""" @property @abc.abstractmethod def timestamp(self) -> datetime.datetime: - """Timestamp of when this typing event started. - - Returns - ------- - datetime.datetime - UTC timestamp of when the user started typing. - """ + """Timestamp of when this typing event started.""" async def fetch_channel(self) -> channels.TextableChannel: """Perform an API call to fetch an up-to-date image of this channel. @@ -173,22 +155,10 @@ class GuildTypingEvent(TypingEvent): # <>. guild_id: snowflakes.Snowflake = attr.field() - """ID of the guild that this event relates to. - - Returns - ------- - hikari.snowflakes.Snowflake - The ID of the guild that relates to this event. - """ + """ID of the guild that this event relates to.""" member: guilds.Member = attr.field(repr=False) - """Object of the member who triggered this typing event. - - Returns - ------- - hikari.guilds.Member - Object of the member who triggered this typing event. - """ + """Object of the member who triggered this typing event.""" @property def app(self) -> traits.RESTAware: @@ -264,12 +234,12 @@ def get_channel(self) -> typing.Optional[channels.TextableGuildChannel]: def get_guild(self) -> typing.Optional[guilds.GatewayGuild]: """Get the cached object of the guild this typing event occurred in. - If the guild is not found then this will return `builtins.None`. + If the guild is not found then this will return `None`. Returns ------- typing.Optional[hikari.guilds.GatewayGuild] - The object of the gateway guild if found else `builtins.None`. + The object of the gateway guild if found else `None`. """ if not isinstance(self.app, traits.CacheAware): return None diff --git a/hikari/events/user_events.py b/hikari/events/user_events.py index ec2593f88f..6c08d268a0 100644 --- a/hikari/events/user_events.py +++ b/hikari/events/user_events.py @@ -49,17 +49,11 @@ class OwnUserUpdateEvent(shard_events.ShardEvent): old_user: typing.Optional[users.OwnUser] = attr.field() """The old application user. - This will be `builtins.None` if the user missing from the cache. + This will be `None` if the user missing from the cache. """ user: users.OwnUser = attr.field() - """This application user. - - Returns - ------- - hikari.users.OwnUser - This application user. - """ + """This application user.""" @property def app(self) -> traits.RESTAware: diff --git a/hikari/events/voice_events.py b/hikari/events/voice_events.py index 5f65e58f39..9b15e7d900 100644 --- a/hikari/events/voice_events.py +++ b/hikari/events/voice_events.py @@ -55,13 +55,7 @@ class VoiceEvent(shard_events.ShardEvent, abc.ABC): @property @abc.abstractmethod def guild_id(self) -> snowflakes.Snowflake: - """ID of the guild this event is for. - - Returns - ------- - hikari.snowflakes.Snowflake - The guild ID of the guild this event relates to. - """ + """ID of the guild this event is for.""" @base_events.requires_intents(intents.Intents.GUILD_VOICE_STATES) @@ -82,17 +76,11 @@ class VoiceStateUpdateEvent(VoiceEvent): old_state: typing.Optional[voices.VoiceState] = attr.field(repr=True) """The old voice state. - This will be `builtins.None` if the voice state missing from the cache. + This will be `None` if the voice state missing from the cache. """ state: voices.VoiceState = attr.field(repr=True) - """Voice state that this update contained. - - Returns - ------- - hikari.voices.VoiceState - The voice state that was updated. - """ + """Voice state that this update contained.""" @property def app(self) -> traits.RESTAware: @@ -124,44 +112,27 @@ class VoiceServerUpdateEvent(VoiceEvent): # <> token: str = attr.field(repr=False) - """Token that should be used to authenticate with the voice gateway. - - Returns - ------- - builtins.str - The token to use to authenticate with the voice gateway. - """ + """Token that should be used to authenticate with the voice gateway.""" raw_endpoint: typing.Optional[str] = attr.field(repr=True) """Raw endpoint URI that Discord sent. - If this is `builtins.None`, it means that the server has been deallocated + If this is `None`, it means that the server has been deallocated and you have to disconnect. You will later receive a new event specifying what endpoint to connect to. - !!! warning + .. warning:: This will not contain the scheme to use. Use the `endpoint` property to get a representation that has this prepended. - - Returns - ------- - builtins.str - A scheme-less endpoint URI for the endpoint to use for a new voice - websocket. """ @property def endpoint(self) -> typing.Optional[str]: """URI for this voice server host, with the correct scheme prepended. - If this is `builtins.None`, it means that the server has been deallocated + If this is `None`, it means that the server has been deallocated and you have to disconnect. You will later receive a new event specifying what endpoint to connect to. - - Returns - ------- - typing.Optional[builtins.str] - If not `builtins.None`, the URI to use to connect to the voice gateway. """ if self.raw_endpoint is None: return None diff --git a/hikari/files.py b/hikari/files.py index 5abd9c9ad7..32911ca8f2 100644 --- a/hikari/files.py +++ b/hikari/files.py @@ -73,12 +73,12 @@ ReaderImplT = typing.TypeVar("ReaderImplT", bound="AsyncReader") ReaderImplT_co = typing.TypeVar("ReaderImplT_co", bound="AsyncReader", covariant=True) -Pathish = typing.Union["os.PathLike[str]", str] +Pathish = typing.Union[os.PathLike, str] """Type hint representing a literal file or path. This may be one of: -- `builtins.str` path. +- `str` path. - `os.PathLike` derivative, such as `pathlib.PurePath` and `pathlib.Path`. """ @@ -128,21 +128,6 @@ - `aiohttp.StreamReader` """ -Resourceish = typing.Union["Resource[typing.Any]", Pathish, Rawish] -"""Type hint representing a file or path to a file/URL/data URI. - -This may be one of: - -- `Resource` or a derivative. -- `builtins.str` path. -- `os.PathLike` derivative, such as `pathlib.PurePath` and `pathlib.Path`. -- `bytes` -- `bytearray` -- `memoryview` -- `io.BytesIO` -- `io.StringIO` (assuming UTF-8 encoding). -""" - def ensure_path(pathish: Pathish) -> pathlib.Path: """Convert a path-like object to a `pathlib.Path` instance.""" @@ -169,7 +154,7 @@ def ensure_resource(url_or_resource: Resourceish, /) -> Resource[AsyncReader]: Parameters ---------- url_or_resource : Resourceish - The item to convert. Ff a `Resource` is passed, it is + The item to convert. If a `Resource` is passed, it is simply returned again. Anything else is converted to a `Resource` first. Returns @@ -205,13 +190,13 @@ def guess_mimetype_from_filename(name: str, /) -> typing.Optional[str]: Parameters ---------- - name : builtins.bytes + name : bytes The filename to inspect. Returns ------- - typing.Optional[builtins.str] - The closest guess to the given filename. May be `builtins.None` if + typing.Optional[str] + The closest guess to the given filename. May be `None` if no match was found. """ guess, _ = mimetypes.guess_type(name) @@ -221,20 +206,20 @@ def guess_mimetype_from_filename(name: str, /) -> typing.Optional[str]: def guess_mimetype_from_data(data: bytes, /) -> typing.Optional[str]: """Guess the mimetype of some data from the header. - !!! warning + .. warning:: This function only detects valid image headers that Discord allows the use of. Anything else will go undetected. Parameters ---------- - data : builtins.bytes + data : bytes The byte content to inspect. Returns ------- - typing.Optional[builtins.str] + typing.Optional[str] The mimetype, if it was found. If the header is unrecognised, then - `builtins.None` is returned. + `None` is returned. """ if data.startswith(b"\211PNG\r\n\032\n"): return "image/png" @@ -252,7 +237,7 @@ def guess_file_extension(mimetype: str) -> typing.Optional[str]: Parameters ---------- - mimetype : builtins.str + mimetype : str The mimetype to guess the extension for. Example @@ -264,9 +249,9 @@ def guess_file_extension(mimetype: str) -> typing.Optional[str]: Returns ------- - typing.Optional[builtins.str] + typing.Optional[str] The file extension, prepended with a `.`. If no match was found, - return `builtins.None`. + return `None`. """ return mimetypes.guess_extension(mimetype) @@ -281,16 +266,16 @@ def generate_filename_from_details( Parameters ---------- - mimetype : typing.Optional[builtins.str] - The mimetype of the content, or `builtins.None` if not known. - extension : typing.Optional[builtins.str] - The file extension to use, or `builtins.None` if not known. - data : typing.Optional[builtins.bytes] - The data to inspect, or `builtins.None` if not known. + mimetype : typing.Optional[str] + The mimetype of the content, or `None` if not known. + extension : typing.Optional[str] + The file extension to use, or `None` if not known. + data : typing.Optional[bytes] + The data to inspect, or `None` if not known. Returns ------- - builtins.str + str A generated quasi-unique filename. """ if data is not None and mimetype is None: @@ -313,14 +298,14 @@ def to_data_uri(data: bytes, mimetype: typing.Optional[str]) -> str: Parameters ---------- - data : builtins.bytes + data : bytes The data to encode as base64. - mimetype : typing.Optional[builtins.str] - The mimetype, or `builtins.None` if we should attempt to guess it. + mimetype : typing.Optional[str] + The mimetype, or `None` if we should attempt to guess it. Returns ------- - builtins.str + str A data URI string. """ if mimetype is None: @@ -345,7 +330,7 @@ class AsyncReader(typing.AsyncIterable[bytes], abc.ABC): """The filename of the resource.""" mimetype: typing.Optional[str] = attr.field(repr=True) - """The mimetype of the resource. May be `builtins.None` if not known.""" + """The mimetype of the resource. May be `None` if not known.""" async def data_uri(self) -> str: """Fetch the data URI. @@ -355,7 +340,7 @@ async def data_uri(self) -> str: return to_data_uri(await self.read(), self.mimetype) async def read(self) -> bytes: - """Read the rest of the resource and return it in a `builtins.bytes` object.""" + """Read the rest of the resource and return it in a `bytes` object.""" buff = bytearray() async for chunk in self: buff.extend(chunk) @@ -449,7 +434,7 @@ async def read( data = await reader.read() ``` - !!! warning + .. warning:: If you simply wish to re-upload this resource to Discord via any endpoint in Hikari, you should opt to just pass this resource object directly. This way, Hikari can perform byte @@ -460,12 +445,12 @@ async def read( ---------- executor : typing.Optional[concurrent.futures.Executor] The executor to run in for blocking operations. - If `builtins.None`, then the default executor is used for the + If `None`, then the default executor is used for the current event loop. Returns ------- - builtins.bytes + bytes The entire resource. """ async with self.stream(executor=executor) as reader: @@ -484,10 +469,10 @@ def stream( ---------- executor : typing.Optional[concurrent.futures.Executor] The executor to run in for blocking operations. - If `builtins.None`, then the default executor is used for the + If `None`, then the default executor is used for the current event loop. - head_only : builtins.bool - Defaults to `builtins.False`. If `builtins.True`, then the + head_only : bool + Defaults to `False`. If `True`, then the implementation may only retrieve HEAD information if supported. This currently only has any effect for web requests. This will fetch the headers for the HTTP resource this object points to @@ -543,7 +528,7 @@ class WebReader(AsyncReader): """The size of the resource, if known.""" head_only: bool = attr.field() - """If `builtins.True`, then only the HEAD was requested. + """If `True`, then only the HEAD was requested. In this case, neither `__aiter__` nor `read` would return anything other than an empty byte string. @@ -629,16 +614,15 @@ class WebResource(Resource[WebReader], abc.ABC): The logic for identifying this resource is left to each implementation to define. - !!! info - For a usable concrete implementation, use `URL` instead. + For a usable concrete implementation, use `URL` instead. - !!! note + .. note:: Some components may choose to not upload this resource directly and instead simply refer to the URL as needed. The main place this will occur is within embeds. If you need to re-upload the resource, you should download it into - a `builtins.bytes` and pass that instead in these cases. + a `bytes` and pass that instead in these cases. """ __slots__: typing.Sequence[str] = () @@ -658,8 +642,8 @@ def stream( ---------- executor : typing.Optional[concurrent.futures.Executor] Not used. Provided only to match the underlying interface. - head_only : builtins.bool - Defaults to `builtins.False`. If `builtins.True`, then the + head_only : bool + Defaults to `False`. If `True`, then the implementation may only retrieve HEAD information if supported. This currently only has any effect for web requests. @@ -720,16 +704,16 @@ class URL(WebResource): Parameters ---------- - url : builtins.str + url : str The URL of the resource. - !!! note + .. note:: Some components may choose to not upload this resource directly and instead simply refer to the URL as needed. The main place this will occur is within embeds. If you need to re-upload the resource, you should download it into - a `builtins.bytes` and pass that instead in these cases. + a `bytes` and pass that instead in these cases. """ __slots__: typing.Sequence[str] = ("_url",) @@ -848,21 +832,21 @@ class File(Resource[FileReader]): Parameters ---------- - path : typing.Union[builtins.str, os.PathLike, pathlib.Path] + path : typing.Union[str, os.PathLike, pathlib.Path] The path to use. - !!! note + .. note:: If passing a `pathlib.Path`, this must not be a `pathlib.PurePath` directly, as it will be used to expand tokens such as `~` that denote the home directory, and `..` for relative paths. This will all be performed as required in an executor to prevent blocking the event loop. - filename : typing.Optional[builtins.str] - The filename to use. If this is `builtins.None`, the name of the file is taken + filename : typing.Optional[str] + The filename to use. If this is `None`, the name of the file is taken from the path instead. spoiler : bool - Whether to mark the file as a spoiler in Discord. Defaults to `builtins.False`. + Whether to mark the file as a spoiler in Discord. Defaults to `False`. """ __slots__: typing.Sequence[str] = ("path", "_filename", "is_spoiler") @@ -906,9 +890,9 @@ def stream( ---------- executor : typing.Optional[concurrent.futures.Executor] The executor to run the blocking read operations in. If - `builtins.None`, the default executor for the running event loop + `None`, the default executor for the running event loop will be used instead. - head_only : builtins.bool + head_only : bool Not used. Provided only to match the underlying interface. Returns @@ -1008,14 +992,14 @@ class Bytes(Resource[IteratorReader]): ---------- data : typing.Union[Rawish, LazyByteIteratorish] The raw data. - filename : builtins.str + filename : str The filename to use. - mimetype : typing.Optional[builtins.str] - The mimetype, or `builtins.None` if you do not wish to specify this. + mimetype : typing.Optional[str] + The mimetype, or `None` if you do not wish to specify this. If not provided, then this will be generated from the file extension of the filename instead. spoiler : bool - Whether to mark the file as a spoiler in Discord. Defaults to `builtins.False`. + Whether to mark the file as a spoiler in Discord. Defaults to `False`. """ __slots__: typing.Sequence[str] = ("data", "_filename", "mimetype", "is_spoiler") @@ -1024,7 +1008,7 @@ class Bytes(Resource[IteratorReader]): """The raw data/provider of raw data to upload.""" mimetype: typing.Optional[str] - """The provided mimetype, if provided. Otherwise `builtins.None`.""" + """The provided mimetype, if provided. Otherwise `None`.""" is_spoiler: bool """Whether the file will be marked as a spoiler.""" @@ -1076,7 +1060,7 @@ def stream( ---------- executor : typing.Optional[concurrent.futures.Executor] Not used. Provided only to match the underlying interface. - head_only : builtins.bool + head_only : bool Not used. Provided only to match the underlying interface. Returns @@ -1093,9 +1077,9 @@ def from_data_uri(data_uri: str, filename: typing.Optional[str] = None) -> Bytes Parameters ---------- - data_uri : builtins.str + data_uri : str The data URI to parse. - filename : typing.Optional[builtins.str] + filename : typing.Optional[str] Filename to use. If this is not provided, then this is generated instead. @@ -1106,7 +1090,7 @@ def from_data_uri(data_uri: str, filename: typing.Optional[str] = None) -> Bytes Raises ------ - builtins.ValueError + ValueError If the parsed argument is not a data URI. """ if not data_uri.startswith("data:"): @@ -1125,3 +1109,19 @@ def from_data_uri(data_uri: str, filename: typing.Optional[str] = None) -> Bytes filename = generate_filename_from_details(mimetype=mimetype, data=data) return Bytes(data, filename, mimetype=mimetype) + + +Resourceish = typing.Union[Resource[typing.Any], Pathish, Rawish] +"""Type hint representing a file or path to a file/URL/data URI. + +This may be one of: + +- `Resource` or a derivative. +- `str` path. +- `os.PathLike` derivative, such as `pathlib.PurePath` and `pathlib.Path`. +- `bytes` +- `bytearray` +- `memoryview` +- `io.BytesIO` +- `io.StringIO` (assuming UTF-8 encoding). +""" diff --git a/hikari/guilds.py b/hikari/guilds.py index ccb7464685..c3b33aacf8 100644 --- a/hikari/guilds.py +++ b/hikari/guilds.py @@ -283,7 +283,7 @@ class GuildWidget: async def fetch_channel(self) -> typing.Optional[channels_.GuildChannel]: """Fetch the widget channel. - This will be `builtins.None` if not set. + This will be `None` if not set. Returns ------- @@ -291,7 +291,7 @@ async def fetch_channel(self) -> typing.Optional[channels_.GuildChannel]: The requested channel. You can check the type of the channel by - using `builtins.isinstance`. + using `isinstance`. Raises ------ @@ -333,14 +333,14 @@ class Member(users.User): """The ID of the guild this member belongs to.""" is_deaf: undefined.UndefinedOr[bool] = attr.field(repr=False) - """`builtins.True` if this member is deafened in the current voice channel. + """`True` if this member is deafened in the current voice channel. This will be `hikari.undefined.UNDEFINED` if it's state is unknown. """ is_mute: undefined.UndefinedOr[bool] = attr.field(repr=False) - """`builtins.True` if this member is muted in the current voice channel. + """`True` if this member is muted in the current voice channel. This will be `hikari.undefined.UNDEFINED` if it's state is unknown. """ @@ -357,13 +357,13 @@ class Member(users.User): nickname: typing.Optional[str] = attr.field(repr=True) """This member's nickname. - This will be `builtins.None` if not set. + This will be `None` if not set. """ premium_since: typing.Optional[datetime.datetime] = attr.field(repr=False) """The datetime of when this member started "boosting" this guild. - Will be `builtins.None` if the member is not a premium user. + Will be `None` if the member is not a premium user. """ role_ids: typing.Sequence[snowflakes.Snowflake] = attr.field(repr=False) @@ -379,9 +379,9 @@ class Member(users.User): """This member's corresponding user object.""" guild_avatar_hash: typing.Optional[str] = attr.field(eq=False, hash=False, repr=False) - """Hash of the member's guild avatar guild if set, else `builtins.None`. + """Hash of the member's guild avatar guild if set, else `None`. - !!! note + .. note:: This takes precedence over `Member.avatar_hash`. """ @@ -402,7 +402,7 @@ def avatar_url(self) -> typing.Optional[files.URL]: def guild_avatar_url(self) -> typing.Optional[files.URL]: """Guild Avatar URL for the user, if they have one set. - May be `builtins.None` if no guild avatar is set. In this case, you + May be `None` if no guild avatar is set. In this case, you should use `avatar_hash` or `default_avatar_url` instead. """ return self.make_guild_avatar_url() @@ -434,15 +434,10 @@ def display_name(self) -> str: If the member has a nickname, this will return that nickname. Otherwise, it will return the username instead. - Returns - ------- - builtins.str - The member display name. - See Also -------- - Nickname: `Member.nickname` - Username: `Member.username` + `Member.nickname` + `Member.username` """ return self.nickname if isinstance(self.nickname, str) else self.username @@ -479,11 +474,6 @@ def mention(self) -> str: >>> some_member_with_nickname.mention '<@!123456789123456789>' ``` - - Returns - ------- - builtins.str - The mention string to use. """ return f"<@!{self.id}>" if self.nickname is not None else self.user.mention @@ -497,7 +487,7 @@ def get_presence(self) -> typing.Optional[presences_.MemberPresence]: Returns ------- typing.Optional[hikari.presences.MemberPresence] - The member presence, or `builtins.None` if not known. + The member presence, or `None` if not known. """ if not isinstance(self.user.app, traits.CacheAware): return None @@ -531,7 +521,7 @@ def get_top_role(self) -> typing.Optional[Role]: Returns ------- typing.Optional[hikari.guilds.Role] - `builtins.None` if the cache is missing the roles information or + `None` if the cache is missing the roles information or the highest role the user has. """ roles = sorted(self.get_roles(), key=lambda r: r.position, reverse=True) @@ -553,21 +543,21 @@ def make_guild_avatar_url( ) -> typing.Optional[files.URL]: """Generate the guild specific avatar url for this member, if set. - If no guild avatar is set, this returns `builtins.None`. You can then + If no guild avatar is set, this returns `None`. You can then use the `make_avatar_url` to get their global custom avatar or `default_avatar_url` if they have no custom avatar set. Parameters ---------- - ext : typing.Optional[builtins.str] + ext : typing.Optional[str] The ext to use for this URL, defaults to `png` or `gif`. Supports `png`, `jpeg`, `jpg`, `webp` and `gif` (when animated). Will be ignored for default avatars which can only be `png`. - If `builtins.None`, then the correct default extension is + If `None`, then the correct default extension is determined based on whether the icon is animated or not. - size : builtins.int + size : int The size to set for the URL, defaults to `4096`. Can be any power of two between 16 and 4096. Will be ignored for default avatars. @@ -575,11 +565,11 @@ def make_guild_avatar_url( Returns ------- typing.Optional[hikari.files.URL] - The URL to the avatar, or `builtins.None` if not present. + The URL to the avatar, or `None` if not present. Raises ------ - builtins.ValueError + ValueError If `size` is not a power of two or not between 16 and 4096. """ if self.guild_avatar_hash is None: @@ -674,10 +664,10 @@ async def ban( Other Parameters ---------------- - delete_message_days : hikari.undefined.UndefinedNoneOr[builtins.int] + delete_message_days : hikari.undefined.UndefinedNoneOr[int] If provided, the number of days to delete messages for. This must be between 0 and 7. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -718,7 +708,7 @@ async def unban( Other Parameters ---------------- - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -757,7 +747,7 @@ async def kick( Other Parameters ---------------- - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -800,7 +790,7 @@ async def add_role( Other Parameters ---------------- - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -841,7 +831,7 @@ async def remove_role( Other Parameters ---------------- - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -885,8 +875,8 @@ async def edit( Other Parameters ---------------- - nick : hikari.undefined.UndefinedNoneOr[builtins.str] - If provided, the new nick for the member. If `builtins.None`, + nick : hikari.undefined.UndefinedNoneOr[str] + If provided, the new nick for the member. If `None`, will remove the members nick. Requires the `MANAGE_NICKNAMES` permission. @@ -894,27 +884,27 @@ async def edit( If provided, the new roles for the member. Requires the `MANAGE_ROLES` permission. - mute : hikari.undefined.UndefinedOr[builtins.bool] + mute : hikari.undefined.UndefinedOr[bool] If provided, the new server mute state for the member. Requires the `MUTE_MEMBERS` permission. - deaf : hikari.undefined.UndefinedOr[builtins.bool] + deaf : hikari.undefined.UndefinedOr[bool] If provided, the new server deaf state for the member. Requires the `DEAFEN_MEMBERS` permission. voice_channel : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildVoiceChannel]]] - If provided, `builtins.None` or the object or the ID of + If provided, `None` or the object or the ID of an existing voice channel to move the member to. - If `builtins.None`, will disconnect the member from voice. + If `None`, will disconnect the member from voice. Requires the `MOVE_MEMBERS` permission and the `CONNECT` permission in the original voice channel and the target voice channel. - !!! note + .. note:: If the member is not in a voice channel, this will take no effect. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -971,7 +961,7 @@ def __eq__(self, other: object) -> bool: @attr_extensions.with_copy @attr.define(hash=True, kw_only=True, weakref_slot=False) class PartialRole(snowflakes.Unique): - """Represents a partial guild bound Role object.""" + """Represents a partial guild bound role object.""" app: traits.RESTAware = attr.field( repr=False, eq=False, hash=False, metadata={attr_extensions.SKIP_DEEP_COPY: True} @@ -986,13 +976,7 @@ class PartialRole(snowflakes.Unique): @property def mention(self) -> str: - """Return a raw mention string for the role. - - Returns - ------- - builtins.str - The mention string to use. - """ + """Return a raw mention string for the role.""" return f"<@&{self.id}>" def __str__(self) -> str: @@ -1015,14 +999,14 @@ class Role(PartialRole): is_hoisted: bool = attr.field(eq=False, hash=False, repr=True) """Whether this role is hoisting the members it's attached to in the member list. - members will be hoisted under their highest role where this is set to `builtins.True`. + members will be hoisted under their highest role where this is set to `True`. """ icon_hash: typing.Optional[str] = attr.field(eq=False, hash=False, repr=False) - """Hash of the role's icon if set, else `builtins.None`.""" + """Hash of the role's icon if set, else `None`.""" unicode_emoji: typing.Optional[emojis_.UnicodeEmoji] = attr.field(eq=False, hash=False, repr=False) - """Role's icon as an unicode emoji if set, else `builtins.None`.""" + """Role's icon as an unicode emoji if set, else `None`.""" is_managed: bool = attr.field(eq=False, hash=False, repr=False) """Whether this role is managed by an integration.""" @@ -1046,13 +1030,13 @@ class Role(PartialRole): bot_id: typing.Optional[snowflakes.Snowflake] = attr.field(eq=False, hash=False, repr=True) """The ID of the bot this role belongs to. - If `builtins.None`, this is not a bot role. + If `None`, this is not a bot role. """ integration_id: typing.Optional[snowflakes.Snowflake] = attr.field(eq=False, hash=False, repr=True) """The ID of the integration this role belongs to. - If `builtins.None`, this is not a integration role. + If `None`, this is not a integration role. """ is_premium_subscriber_role: bool = attr.field(eq=False, hash=False, repr=True) @@ -1070,32 +1054,32 @@ def icon_url(self) -> typing.Optional[files.URL]: Returns ------- typing.Optional[hikari.files.URL] - The URL, or `builtins.None` if no icon exists. + The URL, or `None` if no icon exists. """ return self.make_icon_url() def make_icon_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optional[files.URL]: """Generate the icon URL for this role, if set. - If no role icon is set, this returns `builtins.None`. + If no role icon is set, this returns `None`. Parameters ---------- - ext : builtins.str + ext : str The extension to use for this URL, defaults to `png`. Supports `png`, `jpeg`, `jpg` and `webp`. - size : builtins.int + size : int The size to set for the URL, defaults to `4096`. Can be any power of two between 16 and 4096. Returns ------- typing.Optional[hikari.files.URL] - The URL to the icon, or `builtins.None` if not present. + The URL to the icon, or `None` if not present. Raises ------ - builtins.ValueError + ValueError If `size` is not a power of two or not between 16 and 4096. """ if self.icon_hash is None: @@ -1173,13 +1157,7 @@ def __str__(self) -> str: @property def icon_url(self) -> typing.Optional[files.URL]: - """Team icon URL, if there is one. - - Returns - ------- - typing.Optional[hikari.files.URL] - The URL, or `builtins.None` if no icon exists. - """ + """Team icon URL, if there is one.""" return self.make_icon_url() def make_icon_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optional[files.URL]: @@ -1187,21 +1165,21 @@ def make_icon_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optiona Parameters ---------- - ext : builtins.str + ext : str The extension to use for this URL, defaults to `png`. Supports `png`, `jpeg`, `jpg` and `webp`. - size : builtins.int + size : int The size to set for the URL, defaults to `4096`. Can be any power of two between 16 and 4096. Returns ------- typing.Optional[hikari.files.URL] - The URL, or `builtins.None` if no icon exists. + The URL, or `None` if no icon exists. Raises ------ - builtins.ValueError + ValueError If the size is not an integer power of 2 between 16 and 4096 (inclusive). """ @@ -1260,16 +1238,16 @@ class Integration(PartialIntegration): This will not be enacted until after `GuildIntegration.expire_grace_period` passes. - !!! note - This will always be `builtins.None` for Discord integrations. + .. note:: + This will always be `None` for Discord integrations. """ expire_grace_period: typing.Optional[datetime.timedelta] = attr.field(eq=False, hash=False, repr=False) """How many days users with expired subscriptions are given until `GuildIntegration.expire_behavior` is enacted out on them. - !!! note - This will always be `builtins.None` for Discord integrations. + .. note:: + This will always be `None` for Discord integrations. """ is_enabled: bool = attr.field(eq=False, hash=False, repr=True) @@ -1299,7 +1277,7 @@ class Integration(PartialIntegration): application: typing.Optional[IntegrationApplication] = attr.field(eq=False, hash=False, repr=False) """The bot/OAuth2 application associated with this integration. - !!! note + .. note:: This is only available for Discord integrations. """ @@ -1320,7 +1298,7 @@ class WelcomeChannel: ) """The emoji shown in the welcome screen channel if set to a unicode emoji. - !!! warning + .. warning:: While it may also be present for custom emojis, this is neither guaranteed to be provided nor accurate. """ @@ -1347,7 +1325,7 @@ class GuildBan: """Used to represent guild bans.""" reason: typing.Optional[str] = attr.field(repr=True) - """The reason for this ban, will be `builtins.None` if no reason was given.""" + """The reason for this ban, will be `None` if no reason was given.""" user: users.User = attr.field(repr=True) """The object of the user this ban targets.""" @@ -1377,7 +1355,7 @@ def __str__(self) -> str: @property def icon_url(self) -> typing.Optional[files.URL]: - """Icon URL for the guild, if set; otherwise `builtins.None`.""" + """Icon URL for the guild, if set; otherwise `None`.""" return self.make_icon_url() @property @@ -1399,25 +1377,25 @@ def make_icon_url(self, *, ext: typing.Optional[str] = None, size: int = 4096) - Parameters ---------- - ext : typing.Optional[builtins.str] + ext : typing.Optional[str] The extension to use for this URL, defaults to `png` or `gif`. Supports `png`, `jpeg`, `jpg`, `webp` and `gif` (when animated). - If `builtins.None`, then the correct default extension is + If `None`, then the correct default extension is determined based on whether the icon is animated or not. - size : builtins.int + size : int The size to set for the URL, defaults to `4096`. Can be any power of two between 16 and 4096. Returns ------- typing.Optional[hikari.files.URL] - The URL to the resource, or `builtins.None` if no icon is set. + The URL to the resource, or `None` if no icon is set. Raises ------ - builtins.ValueError + ValueError If `size` is not a power of two or not between 16 and 4096. """ if self.icon_hash is None: @@ -1453,10 +1431,10 @@ async def ban( Other Parameters ---------------- - delete_message_days : hikari.undefined.UndefinedNoneOr[builtins.int] + delete_message_days : hikari.undefined.UndefinedNoneOr[int] If provided, the number of days to delete messages for. This must be between 0 and 7. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -1501,7 +1479,7 @@ async def unban( Other Parameters ---------------- - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -1546,7 +1524,7 @@ async def kick( Other Parameters ---------------- - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -1607,7 +1585,7 @@ async def edit( Parameters ---------- - name : hikari.undefined.UndefinedOr[builtins.str] + name : hikari.undefined.UndefinedOr[str] If provided, the new name for the guild. verification_level : hikari.undefined.UndefinedOr[hikari.guilds.GuildVerificationLevel] If provided, the new verification level. @@ -1626,7 +1604,7 @@ async def edit( owner : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.users.PartialUser]]] If provided, the new guild owner. - !!! warning + .. warning:: You need to be the owner of the server to use this. splash : hikari.undefined.UndefinedNoneOr[hikari.files.Resourceish] If provided, the new guild splash. Must be a 16:9 image and the @@ -1640,9 +1618,9 @@ async def edit( If provided, the new rules channel. public_updates_channel : hikari.undefined.UndefinedNoneOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildTextChannel]] If provided, the new public updates channel. - preferred_locale : hikari.undefined.UndefinedNoneOr[builtins.str] + preferred_locale : hikari.undefined.UndefinedNoneOr[str] If provided, the new preferred locale. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -1843,25 +1821,25 @@ async def create_sticker( ) -> stickers.GuildSticker: """Create a sticker in a guild. + .. note:: + Lottie support is only available for verified and partnered + servers. + Parameters ---------- - name : builtins.str + name : str The name for the sticker. - tag : builtins.str + tag : str The tag for the sticker. image : hikari.files.Resourceish The 320x320 image for the sticker. Maximum upload size is 500kb. This can be a still or an animated PNG or a Lottie. - !!! note - Lottie support is only available for verified and partnered - servers. - Other Parameters ---------------- - description: hikari.undefined.UndefinedOr[builtins.str] + description: hikari.undefined.UndefinedOr[str] If provided, the description of the sticker. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -1916,13 +1894,13 @@ async def edit_sticker( Other Parameters ---------------- - name : hikari.undefined.UndefinedOr[builtins.str] + name : hikari.undefined.UndefinedOr[str] If provided, the new name for the sticker. - description : hikari.undefined.UndefinedOr[builtins.str] + description : hikari.undefined.UndefinedOr[str] If provided, the new description for the sticker. - tag : hikari.undefined.UndefinedOr[builtins.str] + tag : hikari.undefined.UndefinedOr[str] If provided, the new sticker tag. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -1975,7 +1953,7 @@ async def delete_sticker( Other Parameters ---------------- - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -2017,16 +1995,16 @@ async def create_category( Parameters ---------- - name : builtins.str + name : str The channels name. Must be between 2 and 1000 characters. Other Parameters ---------------- - position : hikari.undefined.UndefinedOr[builtins.int] + position : hikari.undefined.UndefinedOr[int] If provided, the position of the category. permission_overwrites : hikari.undefined.UndefinedOr[typing.Sequence[hikari.channels.PermissionOverwrite]] If provided, the permission overwrites for the category. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -2081,17 +2059,17 @@ async def create_text_channel( Parameters ---------- - name : builtins.str + name : str The channels name. Must be between 2 and 1000 characters. Other Parameters ---------------- - position : hikari.undefined.UndefinedOr[builtins.int] + position : hikari.undefined.UndefinedOr[int] If provided, the position of the channel (relative to the category, if any). - topic : hikari.undefined.UndefinedOr[builtins.str] + topic : hikari.undefined.UndefinedOr[str] If provided, the channels topic. Maximum 1024 characters. - nsfw : hikari.undefined.UndefinedOr[builtins.bool] + nsfw : hikari.undefined.UndefinedOr[bool] If provided, whether to mark the channel as NSFW. rate_limit_per_user : hikari.undefined.UndefinedOr[hikari.internal.time.Intervalish] If provided, the amount of seconds a user has to wait @@ -2102,7 +2080,7 @@ async def create_text_channel( category : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildCategory]] The category to create the channel under. This may be the object or the ID of an existing category. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -2165,17 +2143,17 @@ async def create_news_channel( Parameters ---------- - name : builtins.str + name : str The channels name. Must be between 2 and 1000 characters. Other Parameters ---------------- - position : hikari.undefined.UndefinedOr[builtins.int] + position : hikari.undefined.UndefinedOr[int] If provided, the position of the channel (relative to the category, if any). - topic : hikari.undefined.UndefinedOr[builtins.str] + topic : hikari.undefined.UndefinedOr[str] If provided, the channels topic. Maximum 1024 characters. - nsfw : hikari.undefined.UndefinedOr[builtins.bool] + nsfw : hikari.undefined.UndefinedOr[bool] If provided, whether to mark the channel as NSFW. rate_limit_per_user : hikari.undefined.UndefinedOr[hikari.internal.time.Intervalish] If provided, the amount of seconds a user has to wait @@ -2186,7 +2164,7 @@ async def create_news_channel( category : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildCategory]] The category to create the channel under. This may be the object or the ID of an existing category. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -2250,37 +2228,34 @@ async def create_voice_channel( Parameters ---------- - guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] - The guild to create the channel in. This may be the - object or the ID of an existing guild. - name : builtins.str + name : str The channels name. Must be between 2 and 1000 characters. Other Parameters ---------------- - position : hikari.undefined.UndefinedOr[builtins.int] + position : hikari.undefined.UndefinedOr[int] If provided, the position of the channel (relative to the category, if any). - user_limit : hikari.undefined.UndefinedOr[builtins.int] + user_limit : hikari.undefined.UndefinedOr[int] If provided, the maximum users in the channel at once. Must be between 0 and 99 with 0 meaning no limit. - bitrate : hikari.undefined.UndefinedOr[builtins.int] + bitrate : hikari.undefined.UndefinedOr[int] If provided, the bitrate for the channel. Must be between 8000 and 96000 or 8000 and 128000 for VIP servers. - video_quality_mode: hikari.undefined.UndefinedOr[typing.Union[hikari.channels.VideoQualityMode, builtins.int]] + video_quality_mode: hikari.undefined.UndefinedOr[typing.Union[hikari.channels.VideoQualityMode, int]] If provided, the new video quality mode for the channel. permission_overwrites : hikari.undefined.UndefinedOr[typing.Sequence[hikari.channels.PermissionOverwrite]] If provided, the permission overwrites for the channel. - region : hikari.undefined.UndefinedOr[typing.Union[hikari.voices.VoiceRegion, builtins.str]] + region : hikari.undefined.UndefinedOr[typing.Union[hikari.voices.VoiceRegion, str]] If provided, the voice region to for this channel. Passing - `builtins.None` here will set it to "auto" mode where the used + `None` here will set it to "auto" mode where the used region will be decided based on the first person who connects to it when it's empty. category : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildCategory]] The category to create the channel under. This may be the object or the ID of an existing category. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -2344,32 +2319,32 @@ async def create_stage_channel( Parameters ---------- - name : builtins.str + name : str The channel's name. Must be between 2 and 1000 characters. Other Parameters ---------------- - position : hikari.undefined.UndefinedOr[builtins.int] + position : hikari.undefined.UndefinedOr[int] If provided, the position of the channel (relative to the category, if any). - user_limit : hikari.undefined.UndefinedOr[builtins.int] + user_limit : hikari.undefined.UndefinedOr[int] If provided, the maximum users in the channel at once. Must be between 0 and 99 with 0 meaning no limit. - bitrate : hikari.undefined.UndefinedOr[builtins.int] + bitrate : hikari.undefined.UndefinedOr[int] If provided, the bitrate for the channel. Must be between 8000 and 96000 or 8000 and 128000 for VIP servers. permission_overwrites : hikari.undefined.UndefinedOr[typing.Sequence[hikari.channels.PermissionOverwrite]] If provided, the permission overwrites for the channel. - region : hikari.undefined.UndefinedOr[typing.Union[hikari.voices.VoiceRegion, builtins.str]] + region : hikari.undefined.UndefinedOr[typing.Union[hikari.voices.VoiceRegion, str]] If provided, the voice region to for this channel. Passing - `builtins.None` here will set it to "auto" mode where the used + `None` here will set it to "auto" mode where the used region will be decided based on the first person who connects to it when it's empty. category : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildCategory]] The category to create the channel under. This may be the object or the ID of an existing category. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. Maximum of 512 characters. @@ -2419,7 +2394,7 @@ async def delete_channel( ) -> channels_.GuildChannel: """Delete a channel in the guild. - !!! note + .. note:: This method can also be used for deleting guild categories as well. Parameters @@ -2455,7 +2430,7 @@ async def delete_channel( hikari.errors.InternalServerError If an internal error occurs on Discord while handling the request. - !!! note + .. note:: For Public servers, the set 'Rules' or 'Guidelines' channels and the 'Public Server Updates' channel cannot be deleted. """ @@ -2569,10 +2544,10 @@ def make_discovery_splash_url(self, *, ext: str = "png", size: int = 4096) -> ty Parameters ---------- - ext : builtins.str + ext : str The extension to use for this URL, defaults to `png`. Supports `png`, `jpeg`, `jpg` and `webp`. - size : builtins.int + size : int The size to set for the URL, defaults to `4096`. Can be any power of two between 16 and 4096. @@ -2583,7 +2558,7 @@ def make_discovery_splash_url(self, *, ext: str = "png", size: int = 4096) -> ty Raises ------ - builtins.ValueError + ValueError If `size` is not a power of two or not between 16 and 4096. """ if self.discovery_splash_hash is None: @@ -2602,21 +2577,21 @@ def make_splash_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optio Parameters ---------- - ext : builtins.str + ext : str The extension to use for this URL, defaults to `png`. Supports `png`, `jpeg`, `jpg` and `webp`. - size : builtins.int + size : int The size to set for the URL, defaults to `4096`. Can be any power of two between 16 and 4096. Returns ------- typing.Optional[hikari.files.URL] - The URL to the splash, or `builtins.None` if not set. + The URL to the splash, or `None` if not set. Raises ------ - builtins.ValueError + ValueError If `size` is not a power of two or not between 16 and 4096. """ if self.splash_hash is None: @@ -2641,13 +2616,13 @@ class Guild(PartialGuild): application_id: typing.Optional[snowflakes.Snowflake] = attr.field(eq=False, hash=False, repr=False) """The ID of the application that created this guild. - This will always be `builtins.None` for guilds that weren't created by a bot. + This will always be `None` for guilds that weren't created by a bot. """ afk_channel_id: typing.Optional[snowflakes.Snowflake] = attr.field(eq=False, hash=False, repr=False) """The ID for the channel that AFK voice users get sent to. - If `builtins.None`, then no AFK channel is set up for this guild. + If `None`, then no AFK channel is set up for this guild. """ afk_timeout: datetime.timedelta = attr.field(eq=False, hash=False, repr=False) @@ -2661,7 +2636,7 @@ class Guild(PartialGuild): """The hash for the guild's banner. This is only present if the guild has `GuildFeature.BANNER` in - `Guild.features` for this guild. For all other purposes, it is `builtins.None`. + `Guild.features` for this guild. For all other purposes, it is `None`. """ default_message_notifications: typing.Union[GuildMessageNotificationsLevel, int] = attr.field( @@ -2673,7 +2648,7 @@ class Guild(PartialGuild): """The guild's description. This is only present if certain `GuildFeature`'s are set in - `Guild.features` for this guild. Otherwise, this will always be `builtins.None`. + `Guild.features` for this guild. Otherwise, this will always be `None`. """ discovery_splash_hash: typing.Optional[str] = attr.field(eq=False, hash=False, repr=False) @@ -2687,13 +2662,13 @@ class Guild(PartialGuild): is_widget_enabled: typing.Optional[bool] = attr.field(eq=False, hash=False, repr=False) """Describes whether the guild widget is enabled or not. - If this information is not present, this will be `builtins.None`. + If this information is not present, this will be `None`. """ max_video_channel_users: typing.Optional[int] = attr.field(eq=False, hash=False, repr=False) """The maximum number of users allowed in a video channel together. - This information may not be present, in which case, it will be `builtins.None`. + This information may not be present, in which case, it will be `None`. """ mfa_level: typing.Union[GuildMFALevel, int] = attr.field(eq=False, hash=False, repr=False) @@ -2712,7 +2687,7 @@ class Guild(PartialGuild): premium_subscription_count: typing.Optional[int] = attr.field(eq=False, hash=False, repr=False) """The number of nitro boosts that the server currently has. - This information may not be present, in which case, it will be `builtins.None`. + This information may not be present, in which case, it will be `None`. """ premium_tier: typing.Union[GuildPremiumTier, int] = attr.field(eq=False, hash=False, repr=False) @@ -2723,14 +2698,14 @@ class Guild(PartialGuild): from Discord. This is only present if `GuildFeature.COMMUNITY` is in `Guild.features` for - this guild. For all other purposes, it should be considered to be `builtins.None`. + this guild. For all other purposes, it should be considered to be `None`. """ rules_channel_id: typing.Optional[snowflakes.Snowflake] = attr.field(eq=False, hash=False, repr=False) """The ID of the channel where guilds with the `GuildFeature.COMMUNITY` `features` display rules and guidelines. - If the `GuildFeature.COMMUNITY` feature is not defined, then this is `builtins.None`. + If the `GuildFeature.COMMUNITY` feature is not defined, then this is `None`. """ splash_hash: typing.Optional[str] = attr.field(eq=False, hash=False, repr=False) @@ -2740,15 +2715,10 @@ class Guild(PartialGuild): """Return flags for the guild system channel. These are used to describe which notifications are suppressed. - - Returns - ------- - GuildSystemChannelFlag - The system channel flags for this channel. """ system_channel_id: typing.Optional[snowflakes.Snowflake] = attr.field(eq=False, hash=False, repr=False) - """The ID of the system channel or `builtins.None` if it is not enabled. + """The ID of the system channel or `None` if it is not enabled. Welcome messages and Nitro boost messages may be sent to this channel. """ @@ -2757,7 +2727,7 @@ class Guild(PartialGuild): """The vanity URL code for the guild's vanity URL. This is only present if `GuildFeature.VANITY_URL` is in `Guild.features` for - this guild. If not, this will always be `builtins.None`. + this guild. If not, this will always be `None`. """ verification_level: typing.Union[GuildVerificationLevel, int] = attr.field(eq=False, hash=False, repr=False) @@ -2767,7 +2737,7 @@ class Guild(PartialGuild): """The channel ID that the widget's generated invite will send the user to. If this information is unavailable or this is not enabled for the guild then - this will be `builtins.None`. + this will be `None`. """ nsfw_level: GuildNSFWLevel = attr.field(eq=False, hash=False, repr=False) @@ -2870,21 +2840,21 @@ def make_banner_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optio Parameters ---------- - ext : builtins.str + ext : str The extension to use for this URL, defaults to `png`. Supports `png`, `jpeg`, `jpg` and `webp`. - size : builtins.int + size : int The size to set for the URL, defaults to `4096`. Can be any power of two between 16 and 4096. Returns ------- typing.Optional[hikari.files.URL] - The URL of the banner, or `builtins.None` if no banner is set. + The URL of the banner, or `None` if no banner is set. Raises ------ - builtins.ValueError + ValueError If `size` is not a power of two or not between 16 and 4096. """ if self.banner_hash is None: @@ -2903,10 +2873,10 @@ def make_discovery_splash_url(self, *, ext: str = "png", size: int = 4096) -> ty Parameters ---------- - ext : builtins.str + ext : str The extension to use for this URL, defaults to `png`. Supports `png`, `jpeg`, `jpg` and `webp`. - size : builtins.int + size : int The size to set for the URL, defaults to `4096`. Can be any power of two between 16 and 4096. @@ -2917,7 +2887,7 @@ def make_discovery_splash_url(self, *, ext: str = "png", size: int = 4096) -> ty Raises ------ - builtins.ValueError + ValueError If `size` is not a power of two or not between 16 and 4096. """ if self.discovery_splash_hash is None: @@ -2936,21 +2906,21 @@ def make_splash_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optio Parameters ---------- - ext : builtins.str + ext : str The extension to use for this URL, defaults to `png`. Supports `png`, `jpeg`, `jpg` and `webp`. - size : builtins.int + size : int The size to set for the URL, defaults to `4096`. Can be any power of two between 16 and 4096. Returns ------- typing.Optional[hikari.files.URL] - The URL to the splash, or `builtins.None` if not set. + The URL to the splash, or `None` if not set. Raises ------ - builtins.ValueError + ValueError If `size` is not a power of two or not between 16 and 4096. """ if self.splash_hash is None: @@ -2978,7 +2948,7 @@ def get_channel( Returns ------- typing.Optional[hikari.channels.GuildChannel] - The object of the guild channel found in cache or `builtins.None. + The object of the guild channel found in cache or `None. """ if not isinstance(self.app, traits.CacheAware): return None @@ -2996,7 +2966,7 @@ def get_member(self, user: snowflakes.SnowflakeishOr[users.PartialUser]) -> typi Returns ------- typing.Optional[Member] - The cached member object if found, else `builtins.None`. + The cached member object if found, else `None`. """ if not isinstance(self.app, traits.CacheAware): return None @@ -3009,7 +2979,7 @@ def get_my_member(self) -> typing.Optional[Member]: Returns ------- typing.Optional[Member] - The cached member for this guild, or `builtins.None` if not known. + The cached member for this guild, or `None` if not known. """ if not isinstance(self.app, traits.ShardAware): return None @@ -3033,7 +3003,7 @@ def get_presence( Returns ------- typing.Optional[hikari.presences.MemberPresence] - The cached presence object if found, else `builtins.None`. + The cached presence object if found, else `None`. """ if not isinstance(self.app, traits.CacheAware): return None @@ -3053,7 +3023,7 @@ def get_voice_state( Returns ------- typing.Optional[hikari.voices.VoiceState] - The cached voice state object if found, else `builtins.None`. + The cached voice state object if found, else `None`. """ if not isinstance(self.app, traits.CacheAware): return None @@ -3074,7 +3044,7 @@ def get_emoji( ------- typing.Optional[hikari.emojis.KnownCustomEmoji] The object of the custom emoji if found in cache, else - `builtins.None`. + `None`. """ if not isinstance(self.app, traits.CacheAware): return None @@ -3092,7 +3062,7 @@ def get_role(self, role: snowflakes.SnowflakeishOr[PartialRole]) -> typing.Optio Returns ------- typing.Optional[Role] - The object of the role found in cache, else `builtins.None`. + The object of the role found in cache, else `None`. """ if not isinstance(self.app, traits.CacheAware): return None @@ -3132,12 +3102,12 @@ async def fetch_owner(self) -> Member: async def fetch_widget_channel(self) -> typing.Optional[channels_.GuildChannel]: """Fetch the widget channel. - This will be `builtins.None` if not set. + This will be `None` if not set. Returns ------- typing.Optional[hikari.channels.GuildChannel] - The channel the widget is linked to or else `builtins.None`. + The channel the widget is linked to or else `None`. Raises ------ @@ -3174,7 +3144,7 @@ async def fetch_afk_channel(self) -> typing.Optional[channels_.GuildVoiceChannel Returns ------- typing.Optional[hikari.channels.GuildVoiceChannel] - The AFK channel or `builtins.None` if not enabled. + The AFK channel or `None` if not enabled. Raises ------ @@ -3211,7 +3181,7 @@ async def fetch_system_channel(self) -> typing.Optional[channels_.GuildTextChann Returns ------- typing.Optional[hikari.channels.GuildTextChannel] - The system channel for this guild or `builtins.None` if not + The system channel for this guild or `None` if not enabled. Raises @@ -3246,12 +3216,12 @@ async def fetch_system_channel(self) -> typing.Optional[channels_.GuildTextChann async def fetch_rules_channel(self) -> typing.Optional[channels_.GuildTextChannel]: """Fetch the channel where guilds display rules and guidelines. - If the `GuildFeature.COMMUNITY` feature is not defined, then this is `builtins.None`. + If the `GuildFeature.COMMUNITY` feature is not defined, then this is `None`. Returns ------- typing.Optional[hikari.channels.GuildTextChannel] - The channel where the rules of the guild are specified or else `builtins.None`. + The channel where the rules of the guild are specified or else `None`. Raises ------ @@ -3286,7 +3256,7 @@ async def fetch_public_updates_channel(self) -> typing.Optional[channels_.GuildT """Fetch channel ID of the channel where admins and moderators receive notices from Discord. This is only present if `GuildFeature.COMMUNITY` is in `Guild.features` for - this guild. For all other purposes, it should be considered to be `builtins.None`. + this guild. For all other purposes, it should be considered to be `None`. Returns ------- @@ -3338,19 +3308,19 @@ class RESTGuild(Guild): approximate_active_member_count: typing.Optional[int] = attr.field(eq=False, hash=False, repr=False) """The approximate number of members in the guild that are not offline. - This will be `builtins.None` when creating a guild. + This will be `None` when creating a guild. """ approximate_member_count: typing.Optional[int] = attr.field(eq=False, hash=False, repr=False) """The approximate number of members in the guild. - This will be `builtins.None` when creating a guild. + This will be `None` when creating a guild. """ max_presences: typing.Optional[int] = attr.field(eq=False, hash=False, repr=False) """The maximum number of presences for the guild. - If `builtins.None`, then there is no limit. + If `None`, then there is no limit. """ max_members: int = attr.field(eq=False, hash=False, repr=False) @@ -3366,7 +3336,7 @@ class GatewayGuild(Guild): This information is only available if the guild was sent via a `GUILD_CREATE` event. If the guild is received from any other place, this will always be - `builtins.None`. + `None`. The implications of a large guild are that presence information will not be sent about members who are offline or invisible. @@ -3377,7 +3347,7 @@ class GatewayGuild(Guild): This information is only available if the guild was sent via a `GUILD_CREATE` event. If the guild is received from any other place, this will always be - `builtins.None`. + `None`. """ member_count: typing.Optional[int] = attr.field(eq=False, hash=False, repr=False) @@ -3385,5 +3355,5 @@ class GatewayGuild(Guild): This information is only available if the guild was sent via a `GUILD_CREATE` event. If the guild is received from any other place, this will always be - `builtins.None`. + `None`. """ diff --git a/hikari/impl/bot.py b/hikari/impl/bot.py index fb895cf8b8..435771d850 100644 --- a/hikari/impl/bot.py +++ b/hikari/impl/bot.py @@ -80,15 +80,22 @@ class GatewayBot(traits.GatewayBotAware): This is the class you will want to use to start, control, and build a bot with. + .. note:: + Settings that control the gateway session are provided to the + `GatewayBot.run` and `GatewayBot.start` functions in this class. This is done + to allow you to contextually customise details such as sharding + configuration without having to re-initialize the entire application + each time. + Parameters ---------- - token : builtins.str + token : str The bot token to sign in with. Other Parameters ---------------- - allow_color : builtins.bool - Defaulting to `builtins.True`, this will enable coloured console logs + allow_color : bool + Defaulting to `True`, this will enable coloured console logs on any platform that is a TTY. Setting a `"CLICOLOR"` environment variable to any **non `0`** value will override this setting. @@ -98,12 +105,12 @@ class GatewayBot(traits.GatewayBotAware): awkward or not support features in a standard way, the option to explicitly disable this is provided. See `force_color` for an alternative. - banner : typing.Optional[builtins.str] + banner : typing.Optional[str] The package to search for a `banner.txt` in. Defaults to `"hikari"` for the `"hikari/banner.txt"` banner. - Setting this to `builtins.None` will disable the banner being shown. + Setting this to `None` will disable the banner being shown. executor : typing.Optional[concurrent.futures.Executor] - Defaults to `builtins.None`. If non-`builtins.None`, then this executor + Defaults to `None`. If non-`None`, then this executor is used instead of the `concurrent.futures.ThreadPoolExecutor` attached to the `asyncio.AbstractEventLoop` that the bot will run on. This executor is used primarily for file-IO. @@ -114,11 +121,13 @@ class GatewayBot(traits.GatewayBotAware): relies on all objects used in IPC to be `pickle`able. Many third-party libraries will not support this fully though, so your mileage may vary on using ProcessPoolExecutor implementations with this parameter. - force_color : builtins.bool - Defaults to `builtins.False`. If `builtins.True`, then this application + force_color : bool + Defaults to `False`. If `True`, then this application will __force__ colour to be used in console-based output. Specifying a `"CLICOLOR_FORCE"` environment variable with a non-`"0"` value will override this setting. + + This will take precedence over `allow_color` if both are specified. cache_settings : typing.Optional[hikari.config.CacheSettings] Optional cache settings. If unspecified, will use the defaults. http_settings : typing.Optional[hikari.config.HTTPSettings] @@ -130,10 +139,10 @@ class GatewayBot(traits.GatewayBotAware): Defaults to `hikari.intents.Intents.ALL_UNPRIVILEGED`. This allows you to change which intents your application will use on the gateway. This can be used to control and change the types of events you will receive. - logs : typing.Union[builtins.None, LoggerLevel, typing.Dict[str, typing.Any]] + logs : typing.Union[None, LoggerLevel, typing.Dict[str, typing.Any]] Defaults to `"INFO"`. - If `builtins.None`, then the Python logging system is left uninitialized + If `None`, then the Python logging system is left uninitialized on startup, and you will need to configure it manually to view most logs that are output by components of this library. @@ -154,7 +163,7 @@ class GatewayBot(traits.GatewayBotAware): Note that `"TRACE_HIKARI"` is a library-specific logging level which is expected to be more verbose than `"DEBUG"`. - max_rate_limit : builtins.float + max_rate_limit : float The max number of seconds to backoff for when rate limited. Anything greater than this will instead raise an error. @@ -167,28 +176,18 @@ class GatewayBot(traits.GatewayBotAware): Note that this only applies to the REST API component that communicates with Discord, and will not affect sharding or third party HTTP endpoints that may be in use. - max_retries : typing.Optional[builtins.int] + max_retries : typing.Optional[int] Maximum number of times a request will be retried if - it fails with a `5xx` status. Defaults to 3 if set to `builtins.None`. + it fails with a `5xx` status. Defaults to 3 if set to `None`. proxy_settings : typing.Optional[config.ProxySettings] Custom proxy settings to use with network-layer logic in your application to get through an HTTP-proxy. - rest_url : typing.Optional[builtins.str] - Defaults to the Discord REST API URL if `builtins.None`. Can be + rest_url : typing.Optional[str] + Defaults to the Discord REST API URL if `None`. Can be overridden if you are attempting to point to an unofficial endpoint, or if you are attempting to mock/stub the Discord API for any reason. Generally you do not want to change this. - !!! note - `force_color` will always take precedence over `allow_color`. - - !!! note - Settings that control the gateway session are provided to the - `GatewayBot.run` and `GatewayBot.start` functions in this class. This is done - to allow you to contextually customise details such as sharding - configuration without having to re-initialize the entire application - each time. - Example ------- Setting up logging using a dictionary configuration: @@ -213,6 +212,15 @@ class GatewayBot(traits.GatewayBotAware): ``` """ + shards: typing.Mapping[int, gateway_shard.GatewayShard] + """Mapping of shards in this application instance. + + Each shard ID is mapped to the corresponding shard instance. + + If the application has not started, it is acceptable to assume the + result of this call will be an empty mapping. + """ # noqa: D401 - Imperative mood + __slots__: typing.Sequence[str] = ( "_cache", "_closing_event", @@ -296,7 +304,7 @@ def __init__( # We populate these on startup instead, as we need to possibly make some # HTTP requests to determine what to put in this mapping. self._shards: typing.Dict[int, gateway_shard.GatewayShard] = {} - self.shards: typing.Mapping[int, gateway_shard.GatewayShard] = types.MappingProxyType(self._shards) + self.shards = types.MappingProxyType(self._shards) @property def cache(self) -> cache_.Cache: @@ -493,11 +501,11 @@ async def on_everyone_mentioned(event): See Also -------- - Listen: `hikari.impl.bot.GatewayBot.listen` - Stream: `hikari.impl.bot.GatewayBot.stream` - Subscribe: `hikari.impl.bot.GatewayBot.subscribe` - Unubscribe: `hikari.impl.bot.GatewayBot.unsubscribe` - Wait_for: `hikari.impl.bot.GatewayBot.wait_for` + `hikari.impl.bot.GatewayBot.listen` + `hikari.impl.bot.GatewayBot.stream` + `hikari.impl.bot.GatewayBot.subscribe` + `hikari.impl.bot.GatewayBot.unsubscribe` + `hikari.impl.bot.GatewayBot.wait_for` """ return self._event_manager.dispatch(event) @@ -511,19 +519,17 @@ def get_listeners( event_type : typing.Type[T] The event type to look for. `T` must be a subclass of `hikari.events.base_events.Event`. - polymorphic : builtins.bool - If `builtins.True`, this will also return the listeners of the - subclasses of the given event type. If `builtins.False`, then + polymorphic : bool + If `True`, this will also return the listeners of the + subclasses of the given event type. If `False`, then only listeners for this class specifically are returned. The - default is `builtins.True`. + default is `True`. Returns ------- - typing.Collection[typing.Callable[[T], typing.Coroutine[typing.Any, typing.Any, builtins.None]] + typing.Collection[typing.Callable[[T], typing.Coroutine[typing.Any, typing.Any, None]] A copy of the collection of listeners for the event. Will return an empty collection if nothing is registered. - - `T` must be a subclass of `hikari.events.base_events.Event`. """ return self._event_manager.get_listeners(event_type, polymorphic=polymorphic) @@ -552,7 +558,6 @@ def listen( The event type to subscribe to. The implementation may allow this to be undefined. If this is the case, the event type will be inferred instead from the type hints on the function signature. - `T` must be a subclass of `hikari.events.base_events.Event`. Returns @@ -564,11 +569,11 @@ def listen( See Also -------- - Dispatch: `hikari.impl.bot.GatewayBot.dispatch` - Stream: `hikari.impl.bot.GatewayBot.stream` - Subscribe: `hikari.impl.bot.GatewayBot.subscribe` - Unubscribe: `hikari.impl.bot.GatewayBot.unsubscribe` - Wait_for: `hikari.impl.bot.GatewayBot.wait_for` + `hikari.impl.bot.GatewayBot.dispatch` + `hikari.impl.bot.GatewayBot.stream` + `hikari.impl.bot.GatewayBot.subscribe` + `hikari.impl.bot.GatewayBot.unsubscribe` + `hikari.impl.bot.GatewayBot.wait_for` """ return self._event_manager.listen(event_type) @@ -585,19 +590,18 @@ def print_banner(banner: typing.Optional[str], allow_color: bool, force_color: b Parameters ---------- - banner : typing.Optional[builtins.str] + banner : typing.Optional[str] The package to find a `banner.txt` in. - allow_color : builtins.bool + allow_color : bool A flag that allows advising whether to allow color if supported or not. Can be overridden by setting a `"CLICOLOR"` environment variable to a non-`"0"` string. - force_color : builtins.bool + force_color : bool A flag that allows forcing color to always be output, even if the terminal device may not support it. Setting the `"CLICOLOR_FORCE"` environment variable to a non-`"0"` string will override this. - !!! note - `force_color` will always take precedence over `allow_color`. + This will take precedence over `allow_color` if both are specified. """ ux.print_banner(banner, allow_color, force_color) @@ -626,25 +630,25 @@ def run( ---------------- activity : typing.Optional[hikari.presences.Activity] The initial activity to display in the bot user presence, or - `builtins.None` (default) to not show any. - afk : builtins.bool + `None` (default) to not show any. + afk : bool The initial AFK state to display in the bot user presence, or - `builtins.False` (default) to not show any. - asyncio_debug : builtins.bool - Defaults to `builtins.False`. If `builtins.True`, then debugging is + `False` (default) to not show any. + asyncio_debug : bool + Defaults to `False`. If `True`, then debugging is enabled for the asyncio event loop in use. - check_for_updates : builtins.bool - Defaults to `builtins.True`. If `builtins.True`, will check for + check_for_updates : bool + Defaults to `True`. If `True`, will check for newer versions of `hikari` on PyPI and notify if available. - close_passed_executor : builtins.bool - Defaults to `builtins.False`. If `builtins.True`, any custom + close_passed_executor : bool + Defaults to `False`. If `True`, any custom `concurrent.futures.Executor` passed to the constructor will be shut down when the application terminates. This does not affect the default executor associated with the event loop, and will not do anything if you do not provide a custom executor to the constructor. - close_loop : builtins.bool - Defaults to `builtins.True`. If `builtins.True`, then once the bot + close_loop : bool + Defaults to `True`. If `True`, then once the bot enters a state where all components have shut down permanently during application shutdown, then all asyngens and background tasks will be destroyed, and the event loop will be shut down. @@ -653,50 +657,50 @@ def run( had time to attempt to shut down correctly (around 250ms), and on Python 3.9 and newer, will also shut down the default event loop executor too. - coroutine_tracking_depth : typing.Optional[builtins.int] - Defaults to `builtins.None`. If an integer value and supported by + coroutine_tracking_depth : typing.Optional[int] + Defaults to `None`. If an integer value and supported by the interpreter, then this many nested coroutine calls will be tracked with their call origin state. This allows you to determine where non-awaited coroutines may originate from, but generally you do not want to leave this enabled for performance reasons. - enable_signal_handlers : builtins.bool - Defaults to `builtins.True`. If on a __non-Windows__ OS with builtin + enable_signal_handlers : bool + Defaults to `True`. If on a __non-Windows__ OS with builtin support for kernel-level POSIX signals, then setting this to - `builtins.True` will allow treating keyboard interrupts and other + `True` will allow treating keyboard interrupts and other OS signals to safely shut down the application as calls to shut down the application properly rather than just killing the process in a dirty state immediately. You should leave this disabled unless you plan to implement your own signal handling yourself. idle_since : typing.Optional[datetime.datetime] The `datetime.datetime` the user should be marked as being idle - since, or `builtins.None` (default) to not show this. - ignore_session_start_limit : builtins.bool - Defaults to `builtins.False`. If `builtins.False`, then attempting + since, or `None` (default) to not show this. + ignore_session_start_limit : bool + Defaults to `False`. If `False`, then attempting to start more sessions than you are allowed in a 24 hour window will throw a `hikari.errors.GatewayError` rather than going ahead and hitting the IDENTIFY limit, which may result in your token - being reset. Setting to `builtins.True` disables this behavior. - large_threshold : builtins.int + being reset. Setting to `True` disables this behavior. + large_threshold : int Threshold for members in a guild before it is treated as being "large" and no longer sending member details in the `GUILD CREATE` event. Defaults to `250`. - propagate_interrupts : builtins.bool - Defaults to `builtins.False`. If set to `builtins.True`, then any + propagate_interrupts : bool + Defaults to `False`. If set to `True`, then any internal `hikari.errors.HikariInterrupt` that is raises as a result of catching an OS level signal will result in the exception being rethrown once the application has closed. This can allow you to use hikari signal handlers and still be able to determine what kind of interrupt the application received after - it closes. When `builtins.False`, nothing is raised and the call + it closes. When `False`, nothing is raised and the call will terminate cleanly and silently where possible instead. - shard_ids : typing.Optional[typing.AbstractSet[builtins.int]] - The shard IDs to create shards for. If not `builtins.None`, then + shard_ids : typing.Optional[typing.AbstractSet[int]] + The shard IDs to create shards for. If not `None`, then a non-`None` `shard_count` must ALSO be provided. Defaults to - `builtins.None`, which means the Discord-recommended count is used + `None`, which means the Discord-recommended count is used for your application instead. - shard_count : typing.Optional[builtins.int] + shard_count : typing.Optional[int] The number of shards to use in the entire distributed application. - Defaults to `builtins.None` which results in the count being + Defaults to `None` which results in the count being determined dynamically on startup. status : hikari.presences.Status The initial status to show for the user presence on startup. @@ -706,7 +710,7 @@ def run( ------ hikari.errors.ComponentStateConflictError If bot is already running. - builtins.TypeError + TypeError If `shard_ids` is passed without `shard_count`. """ if self._is_alive: @@ -837,34 +841,34 @@ async def start( ---------------- activity : typing.Optional[hikari.presences.Activity] The initial activity to display in the bot user presence, or - `builtins.None` (default) to not show any. - afk : builtins.bool + `None` (default) to not show any. + afk : bool The initial AFK state to display in the bot user presence, or - `builtins.False` (default) to not show any. - check_for_updates : builtins.bool - Defaults to `builtins.True`. If `builtins.True`, will check for + `False` (default) to not show any. + check_for_updates : bool + Defaults to `True`. If `True`, will check for newer versions of `hikari` on PyPI and notify if available. idle_since : typing.Optional[datetime.datetime] The `datetime.datetime` the user should be marked as being idle - since, or `builtins.None` (default) to not show this. - ignore_session_start_limit : builtins.bool - Defaults to `builtins.False`. If `builtins.False`, then attempting + since, or `None` (default) to not show this. + ignore_session_start_limit : bool + Defaults to `False`. If `False`, then attempting to start more sessions than you are allowed in a 24 hour window will throw a `hikari.errors.GatewayError` rather than going ahead and hitting the IDENTIFY limit, which may result in your token - being reset. Setting to `builtins.True` disables this behavior. - large_threshold : builtins.int + being reset. Setting to `True` disables this behavior. + large_threshold : int Threshold for members in a guild before it is treated as being "large" and no longer sending member details in the `GUILD CREATE` event. Defaults to `250`. - shard_ids : typing.Optional[typing.AbstractSet[builtins.int]] - The shard IDs to create shards for. If not `builtins.None`, then + shard_ids : typing.Optional[typing.AbstractSet[int]] + The shard IDs to create shards for. If not `None`, then a non-`None` `shard_count` must ALSO be provided. Defaults to - `builtins.None`, which means the Discord-recommended count is used + `None`, which means the Discord-recommended count is used for your application instead. - shard_count : typing.Optional[builtins.int] + shard_count : typing.Optional[int] The number of shards to use in the entire distributed application. - Defaults to `builtins.None` which results in the count being + Defaults to `None` which results in the count being determined dynamically on startup. status : hikari.presences.Status The initial status to show for the user presence on startup. @@ -872,10 +876,10 @@ async def start( Raises ------ + TypeError + If `shard_ids` is passed without `shard_count`. hikari.errors.ComponentStateConflictError If bot is already running. - builtins.TypeError - If `shard_ids` is passed without `shard_count`. """ if self._is_alive: raise errors.ComponentStateConflictError("bot is already running") @@ -1004,18 +1008,23 @@ def stream( ) -> event_manager_.EventStream[event_manager_.EventT_co]: """Return a stream iterator for the given event and sub-events. + .. warning:: + If you use `await stream.open()` to start the stream then you must + also close it with `await stream.close()` otherwise it may queue + events in memory indefinitely. + Parameters ---------- event_type : typing.Type[hikari.events.base_events.Event] The event type to listen for. This will listen for subclasses of this type additionally. - timeout : typing.Optional[builtins.int, builtins.float] + timeout : typing.Optional[int, float] How long this streamer should wait for the next event before - ending the iteration. If `builtins.None` then this will continue + ending the iteration. If `None` then this will continue until explicitly broken from. - limit : typing.Optional[builtins.int] + limit : typing.Optional[int] The limit for how many events this should queue at one time before - dropping extra incoming events, leave this as `builtins.None` for + dropping extra incoming events, leave this as `None` for the cache size to be unlimited. Returns @@ -1025,14 +1034,8 @@ def stream( with `async with stream:` or `await stream.open()` before asynchronously iterating over it. - !!! warning - If you use `await stream.open()` to start the stream then you must - also close it with `await stream.close()` otherwise it may queue - events in memory indefinitely. - Examples -------- - ```py async with bot.stream(events.ReactionAddEvent, timeout=30).filter(("message_id", message.id)) as stream: async for user_id in stream.map("user_id").limit(50): @@ -1053,11 +1056,11 @@ def stream( See Also -------- - Dispatch: `hikari.impl.bot.GatewayBot.dispatch` - Listen: `hikari.impl.bot.GatewayBot.listen` - Subscribe: `hikari.impl.bot.GatewayBot.subscribe` - Unubscribe: `hikari.impl.bot.GatewayBot.unsubscribe` - Wait_for: `hikari.impl.bot.GatewayBot.wait_for` + `hikari.impl.bot.GatewayBot.dispatch` + `hikari.impl.bot.GatewayBot.listen` + `hikari.impl.bot.GatewayBot.subscribe` + `hikari.impl.bot.GatewayBot.unsubscribe` + `hikari.impl.bot.GatewayBot.wait_for` """ self._check_if_alive() return self._event_manager.stream(event_type, timeout=timeout, limit=limit) @@ -1092,11 +1095,11 @@ async def on_message(event): See Also -------- - Dispatch: `hikari.impl.bot.GatewayBot.dispatch` - Listen: `hikari.impl.bot.GatewayBot.listen` - Stream: `hikari.impl.bot.GatewayBot.stream` - Unubscribe: `hikari.impl.bot.GatewayBot.unsubscribe` - Wait_for: `hikari.impl.bot.GatewayBot.wait_for` + `hikari.impl.bot.GatewayBot.dispatch` + `hikari.impl.bot.GatewayBot.listen` + `hikari.impl.bot.GatewayBot.stream` + `hikari.impl.bot.GatewayBot.unsubscribe` + `hikari.impl.bot.GatewayBot.wait_for` """ self._event_manager.subscribe(event_type, callback) @@ -1128,11 +1131,11 @@ async def on_message(event): See Also -------- - Dispatch: `hikari.impl.bot.GatewayBot.dispatch` - Listen: `hikari.impl.bot.GatewayBot.listen` - Stream: `hikari.impl.bot.GatewayBot.stream` - Subscribe: `hikari.impl.bot.GatewayBot.subscribe` - Wait_for: `hikari.impl.bot.GatewayBot.wait_for` + `hikari.impl.bot.GatewayBot.dispatch` + `hikari.impl.bot.GatewayBot.listen` + `hikari.impl.bot.GatewayBot.stream` + `hikari.impl.bot.GatewayBot.subscribe` + `hikari.impl.bot.GatewayBot.wait_for` """ self._event_manager.unsubscribe(event_type, callback) @@ -1145,6 +1148,9 @@ async def wait_for( ) -> event_manager_.EventT_co: """Wait for a given event to occur once, then return the event. + .. warning:: + Async predicates are not supported. + Parameters ---------- event_type : typing.Type[hikari.events.base_events.Event] @@ -1152,17 +1158,14 @@ async def wait_for( this type additionally. predicate A function taking the event as the single parameter. - This should return `builtins.True` if the event is one you want to - return, or `builtins.False` if the event should not be returned. + This should return `True` if the event is one you want to + return, or `False` if the event should not be returned. If left as `None` (the default), then the first matching event type that the bot receives (or any subtype) will be the one returned. - - !!! warning - Async predicates are not supported. - timeout : typing.Union[builtins.float, builtins.int, builtins.None] + timeout : typing.Union[float, int, None] The amount of time to wait before raising an `asyncio.TimeoutError` and giving up instead. This is measured in seconds. If - `builtins.None`, then no timeout will be waited for (no timeout can + `None`, then no timeout will be waited for (no timeout can result in "leaking" of coroutines that never complete if called in an uncontrolled way, so is not recommended). @@ -1174,16 +1177,16 @@ async def wait_for( Raises ------ asyncio.TimeoutError - If the timeout is not `builtins.None` and is reached before an - event is received that the predicate returns `builtins.True` for. + If the timeout is not `None` and is reached before an + event is received that the predicate returns `True` for. See Also -------- - Dispatch: `hikari.impl.bot.GatewayBot.dispatch` - Listen: `hikari.impl.bot.GatewayBot.listen` - Stream: `hikari.impl.bot.GatewayBot.stream` - Subscribe: `hikari.impl.bot.GatewayBot.subscribe` - Unubscribe: `hikari.impl.bot.GatewayBot.unsubscribe` + `hikari.impl.bot.GatewayBot.dispatch` + `hikari.impl.bot.GatewayBot.listen` + `hikari.impl.bot.GatewayBot.stream` + `hikari.impl.bot.GatewayBot.subscribe` + `hikari.impl.bot.GatewayBot.unsubscribe` """ self._check_if_alive() return await self._event_manager.wait_for(event_type, timeout=timeout, predicate=predicate) diff --git a/hikari/impl/buckets.py b/hikari/impl/buckets.py index 03b75eb01a..45362fc201 100644 --- a/hikari/impl/buckets.py +++ b/hikari/impl/buckets.py @@ -63,7 +63,7 @@ Rate limits, on the other hand, apply to a bucket and are specific to the major parameters of the compiled route. This means that `POST /channels/123/messages` and `POST /channels/456/messages` do not share the same real bucket, despite -Discord providing the same bucket hash. A real bucket hash is the `builtins.str` hash of +Discord providing the same bucket hash. A real bucket hash is the `str` hash of the bucket that Discord sends us in a response concatenated to the corresponding major parameters. This is used for quick bucket indexing internally in this module. @@ -121,15 +121,15 @@ These headers are: * `X-RateLimit-Limit`: - an `builtins.int` describing the max requests in the bucket from empty to + an `int` describing the max requests in the bucket from empty to being rate limited. * `X-RateLimit-Remaining`: - an `builtins.int` describing the remaining number of requests before rate + an `int` describing the remaining number of requests before rate limiting occurs in the current window. * `X-RateLimit-Bucket`: - a `builtins.str` containing the initial bucket hash. + a `str` containing the initial bucket hash. * `X-RateLimit-Reset-After`: - a `builtins.float` containing the number of seconds when the current rate + a `float` containing the number of seconds when the current rate limit bucket will reset with decimal millisecond precision. Each of the above values should be passed to the `update_rate_limits` method to @@ -165,7 +165,7 @@ No information is sent in headers about these specific limits. You will only be made aware that they exist once you get ratelimited. In the 429 ratelimited -response, you will have the `"global"` attribute set to `builtins.False`, and a +response, you will have the `"global"` attribute set to `False`, and a `"reset_after"` attribute that differs entirely to the `X-RateLimit-Reset-After` header. Thus, it is important to not assume the value in the 429 response for the reset time is the same as the one in the bucket headers. Hikari's @@ -248,13 +248,13 @@ async def __aexit__( @property def is_unknown(self) -> bool: - """Return `builtins.True` if the bucket represents an `UNKNOWN` bucket.""" + """Return `True` if the bucket represents an `UNKNOWN` bucket.""" return self.name.startswith(UNKNOWN_HASH) async def acquire(self) -> None: """Acquire time on this rate limiter. - !!! note + .. note:: You should afterwards invoke `RESTBucket.update_rate_limit` to update any rate limit information you are made aware of. @@ -288,14 +288,14 @@ def update_rate_limit(self, remaining: int, limit: int, reset_at: float) -> None Parameters ---------- - remaining : builtins.int + remaining : int The calls remaining in this time window. - limit : builtins.int + limit : int The total calls allowed in this time window. - reset_at : builtins.float + reset_at : float The epoch at which to reset the limit. - !!! note + .. note:: The `reset_at` epoch is expected to be a `time.monotonic_timestamp` monotonic epoch, rather than a `time.time` date-based epoch. """ @@ -307,7 +307,7 @@ def update_rate_limit(self, remaining: int, limit: int, reset_at: float) -> None def drip(self) -> None: """Decrement the remaining count for this bucket. - !!! note + .. note:: If the bucket is marked as `RESTBucket.is_unknown`, then this will not do anything. `Unknown` buckets have infinite rate limits. """ @@ -321,12 +321,12 @@ def resolve(self, real_bucket_hash: str) -> None: Parameters ---------- - real_bucket_hash: builtins.str + real_bucket_hash: str The real bucket hash for this bucket. Raises ------ - builtins.RuntimeError + RuntimeError If the hash of the bucket is already known. """ if not self.is_unknown: @@ -348,7 +348,7 @@ class RESTBucketManager: Parameters ---------- - max_rate_limit : builtins.float + max_rate_limit : float The max number of seconds to backoff for when rate limited. Anything greater than this will instead raise an error. """ @@ -412,10 +412,10 @@ def start(self, poll_period: float = 20.0, expire_after: float = 10.0) -> None: Parameters ---------- - poll_period : builtins.float + poll_period : float Period to poll the garbage collector at in seconds. Defaults to `20` seconds. - expire_after : builtins.float + expire_after : float Time after which the last `reset_at` was hit for a bucket to remove it. Higher values will retain unneeded ratelimit info for longer, but may produce more effective rate-limiting logic as a @@ -453,16 +453,16 @@ async def gc(self, poll_period: float, expire_after: float) -> None: Parameters ---------- - poll_period : builtins.float + poll_period : float The period to poll at. - expire_after : builtins.float + expire_after : float Time after which the last `reset_at` was hit for a bucket to remove it. Higher values will retain unneeded ratelimit info for longer, but may produce more effective ratelimiting logic as a result. Using `0` will make the bucket get garbage collected as soon as the rate limit has reset. - !!! warning + .. warning:: You generally have no need to invoke this directly. Use `RESTBucketManager.start` and `RESTBucketManager.close` to control this instead. @@ -490,13 +490,13 @@ def do_gc_pass(self, expire_after: float) -> None: Parameters ---------- - expire_after : builtins.float + expire_after : float Time after which the last `reset_at` was hit for a bucket to\ remove it. Defaults to `reset_at` + 20 seconds. Higher values will retain unneeded ratelimit info for longer, but may produce more effective ratelimiting logic as a result. - !!! warning + .. warning:: You generally have no need to invoke this directly. Use `RESTBucketManager.start` and `RESTBucketManager.close` to control this instead. @@ -543,10 +543,10 @@ def acquire(self, compiled_route: routes.CompiledRoute) -> typing.AsyncContextMa Returns ------- - typing.AsyncContextManager[builtins.None] + typing.AsyncContextManager[None] A context manager to enter while doing the request. - !!! note + .. note:: You MUST keep the context manager acquired during the whole of the request. From making the request until calling `update_rate_limits`. """ @@ -582,14 +582,14 @@ def update_rate_limits( ---------- compiled_route : hikari.internal.routes.CompiledRoute The compiled route to get the bucket for. - bucket_header : typing.Optional[builtins.str] + bucket_header : typing.Optional[str] The `X-RateLimit-Bucket` header that was provided in the response. - remaining_header : builtins.int - The `X-RateLimit-Remaining` header cast to an `builtins.int`. - limit_header : builtins.int - The `X-RateLimit-Limit` header cast to an `builtins.int`. - reset_after : builtins.float - The `X-RateLimit-Reset-After` header cast to a `builtins.float`. + remaining_header : int + The `X-RateLimit-Remaining` header cast to an `int`. + limit_header : int + The `X-RateLimit-Limit` header cast to an `int`. + reset_after : float + The `X-RateLimit-Reset-After` header cast to a `float`. """ self.routes_to_hashes[compiled_route.route] = bucket_header real_bucket_hash = compiled_route.create_real_bucket_hash(bucket_header) @@ -634,5 +634,5 @@ def update_rate_limits( @property def is_started(self) -> bool: - """Return `builtins.True` if the rate limiter GC task is started.""" + """Return `True` if the rate limiter GC task is started.""" return self.gc_task is not None diff --git a/hikari/impl/event_manager.py b/hikari/impl/event_manager.py index 25c43255a4..05ed431bd6 100644 --- a/hikari/impl/event_manager.py +++ b/hikari/impl/event_manager.py @@ -88,7 +88,7 @@ def __init__( super().__init__(event_factory=event_factory, intents=intents) async def on_ready(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#ready for more info.""" + """See for more info.""" # TODO: cache unavailable guilds on startup, I didn't bother for the time being. event = self._event_factory.deserialize_ready_event(shard, payload) @@ -98,11 +98,11 @@ async def on_ready(self, shard: gateway_shard.GatewayShard, payload: data_bindin await self.dispatch(event) async def on_resumed(self, shard: gateway_shard.GatewayShard, _: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#resumed for more info.""" + """See for more info.""" await self.dispatch(self._event_factory.deserialize_resumed_event(shard)) async def on_channel_create(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#channel-create for more info.""" + """See for more info.""" event = self._event_factory.deserialize_guild_channel_create_event(shard, payload) if self._cache: @@ -111,7 +111,7 @@ async def on_channel_create(self, shard: gateway_shard.GatewayShard, payload: da await self.dispatch(event) async def on_channel_update(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#channel-update for more info.""" + """See for more info.""" old = self._cache.get_guild_channel(snowflakes.Snowflake(payload["id"])) if self._cache else None event = self._event_factory.deserialize_guild_channel_update_event(shard, payload, old_channel=old) @@ -121,7 +121,7 @@ async def on_channel_update(self, shard: gateway_shard.GatewayShard, payload: da await self.dispatch(event) async def on_channel_delete(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#channel-delete for more info.""" + """See for more info.""" event = self._event_factory.deserialize_guild_channel_delete_event(shard, payload) if self._cache: @@ -130,12 +130,12 @@ async def on_channel_delete(self, shard: gateway_shard.GatewayShard, payload: da await self.dispatch(event) async def on_channel_pins_update(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#channel-pins-update for more info.""" + """See for more info.""" # TODO: we need a method for this specifically await self.dispatch(self._event_factory.deserialize_channel_pins_update_event(shard, payload)) async def on_guild_create(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#guild-create for more info.""" + """See for more info.""" event: typing.Union[guild_events.GuildAvailableEvent, guild_events.GuildJoinEvent] if "unavailable" in payload: @@ -189,7 +189,7 @@ async def on_guild_create(self, shard: gateway_shard.GatewayShard, payload: data await self.dispatch(event) async def on_guild_update(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#guild-update for more info.""" + """See for more info.""" old = self._cache.get_guild(snowflakes.Snowflake(payload["id"])) if self._cache else None event = self._event_factory.deserialize_guild_update_event(shard, payload, old_guild=old) @@ -207,7 +207,7 @@ async def on_guild_update(self, shard: gateway_shard.GatewayShard, payload: data await self.dispatch(event) async def on_guild_delete(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#guild-delete for more info.""" + """See for more info.""" event: typing.Union[guild_events.GuildUnavailableEvent, guild_events.GuildLeaveEvent] if payload.get("unavailable", False): event = self._event_factory.deserialize_guild_unavailable_event(shard, payload) @@ -234,15 +234,15 @@ async def on_guild_delete(self, shard: gateway_shard.GatewayShard, payload: data await self.dispatch(event) async def on_guild_ban_add(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#guild-ban-add for more info.""" + """See for more info.""" await self.dispatch(self._event_factory.deserialize_guild_ban_add_event(shard, payload)) async def on_guild_ban_remove(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#guild-ban-remove for more info.""" + """See for more info.""" await self.dispatch(self._event_factory.deserialize_guild_ban_remove_event(shard, payload)) async def on_guild_emojis_update(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#guild-emojis-update for more info.""" + """See for more info.""" guild_id = snowflakes.Snowflake(payload["guild_id"]) old = list(self._cache.clear_emojis_for_guild(guild_id).values()) if self._cache else None @@ -255,7 +255,7 @@ async def on_guild_emojis_update(self, shard: gateway_shard.GatewayShard, payloa await self.dispatch(event) async def on_guild_integrations_update(self, _: gateway_shard.GatewayShard, __: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#guild-integrations-update for more info.""" + """See for more info.""" # This is only here to stop this being logged or dispatched as an "unknown event". # This event is made redundant by INTEGRATION_CREATE/DELETE/UPDATE and is thus not parsed or dispatched. return None @@ -273,7 +273,7 @@ async def on_integration_update(self, shard: gateway_shard.GatewayShard, payload await self.dispatch(event) async def on_guild_member_add(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#guild-member-add for more info.""" + """See for more info.""" event = self._event_factory.deserialize_guild_member_add_event(shard, payload) if self._cache: @@ -282,7 +282,7 @@ async def on_guild_member_add(self, shard: gateway_shard.GatewayShard, payload: await self.dispatch(event) async def on_guild_member_remove(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#guild-member-remove for more info.""" + """See for more info.""" old: typing.Optional[guilds.Member] = None if self._cache: old = self._cache.delete_member( @@ -293,7 +293,7 @@ async def on_guild_member_remove(self, shard: gateway_shard.GatewayShard, payloa await self.dispatch(event) async def on_guild_member_update(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#guild-member-update for more info.""" + """See for more info.""" old: typing.Optional[guilds.Member] = None if self._cache: old = self._cache.get_member( @@ -308,7 +308,7 @@ async def on_guild_member_update(self, shard: gateway_shard.GatewayShard, payloa await self.dispatch(event) async def on_guild_members_chunk(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#guild-members-chunk for more info.""" + """See for more info.""" event = self._event_factory.deserialize_guild_member_chunk_event(shard, payload) if self._cache: @@ -321,7 +321,7 @@ async def on_guild_members_chunk(self, shard: gateway_shard.GatewayShard, payloa await self.dispatch(event) async def on_guild_role_create(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#guild-role-create for more info.""" + """See for more info.""" event = self._event_factory.deserialize_guild_role_create_event(shard, payload) if self._cache: @@ -330,7 +330,7 @@ async def on_guild_role_create(self, shard: gateway_shard.GatewayShard, payload: await self.dispatch(event) async def on_guild_role_update(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#guild-role-update for more info.""" + """See for more info.""" old = self._cache.get_role(snowflakes.Snowflake(payload["role"]["id"])) if self._cache else None event = self._event_factory.deserialize_guild_role_update_event(shard, payload, old_role=old) @@ -340,7 +340,7 @@ async def on_guild_role_update(self, shard: gateway_shard.GatewayShard, payload: await self.dispatch(event) async def on_guild_role_delete(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#guild-role-delete for more info.""" + """See for more info.""" old: typing.Optional[guilds.Role] = None if self._cache: old = self._cache.delete_role(snowflakes.Snowflake(payload["role_id"])) @@ -350,7 +350,7 @@ async def on_guild_role_delete(self, shard: gateway_shard.GatewayShard, payload: await self.dispatch(event) async def on_invite_create(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#invite-create for more info.""" + """See for more info.""" event = self._event_factory.deserialize_invite_create_event(shard, payload) if self._cache: @@ -359,7 +359,7 @@ async def on_invite_create(self, shard: gateway_shard.GatewayShard, payload: dat await self.dispatch(event) async def on_invite_delete(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#invite-delete for more info.""" + """See for more info.""" old: typing.Optional[invites.InviteWithMetadata] = None if self._cache: old = self._cache.delete_invite(payload["code"]) @@ -369,7 +369,7 @@ async def on_invite_delete(self, shard: gateway_shard.GatewayShard, payload: dat await self.dispatch(event) async def on_message_create(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#message-create for more info.""" + """See for more info.""" event = self._event_factory.deserialize_message_create_event(shard, payload) if self._cache: @@ -378,7 +378,7 @@ async def on_message_create(self, shard: gateway_shard.GatewayShard, payload: da await self.dispatch(event) async def on_message_update(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#message-update for more info.""" + """See for more info.""" old = self._cache.get_message(snowflakes.Snowflake(payload["id"])) if self._cache else None event = self._event_factory.deserialize_message_update_event(shard, payload, old_message=old) @@ -388,7 +388,7 @@ async def on_message_update(self, shard: gateway_shard.GatewayShard, payload: da await self.dispatch(event) async def on_message_delete(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#message-delete for more info.""" + """See for more info.""" if self._cache: message_id = snowflakes.Snowflake(payload["id"]) old_message = self._cache.delete_message(message_id) @@ -400,7 +400,7 @@ async def on_message_delete(self, shard: gateway_shard.GatewayShard, payload: da await self.dispatch(event) async def on_message_delete_bulk(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#message-delete-bulk for more info.""" + """See for more info.""" old_messages = {} if self._cache: @@ -417,7 +417,7 @@ async def on_message_delete_bulk(self, shard: gateway_shard.GatewayShard, payloa async def on_message_reaction_add( self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject ) -> None: - """See https://discord.com/developers/docs/topics/gateway#message-reaction-add for more info.""" + """See for more info.""" await self.dispatch(self._event_factory.deserialize_message_reaction_add_event(shard, payload)) # TODO: this is unlikely but reaction cache? @@ -425,23 +425,23 @@ async def on_message_reaction_add( async def on_message_reaction_remove( self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject ) -> None: - """See https://discord.com/developers/docs/topics/gateway#message-reaction-remove for more info.""" + """See for more info.""" await self.dispatch(self._event_factory.deserialize_message_reaction_remove_event(shard, payload)) async def on_message_reaction_remove_all( self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject ) -> None: - """See https://discord.com/developers/docs/topics/gateway#message-reaction-remove-all for more info.""" + """See for more info.""" await self.dispatch(self._event_factory.deserialize_message_reaction_remove_all_event(shard, payload)) async def on_message_reaction_remove_emoji( self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject ) -> None: - """See https://discord.com/developers/docs/topics/gateway#message-reaction-remove-emoji for more info.""" + """See for more info.""" await self.dispatch(self._event_factory.deserialize_message_reaction_remove_emoji_event(shard, payload)) async def on_presence_update(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#presence-update for more info.""" + """See for more info.""" old: typing.Optional[presences.MemberPresence] = None if self._cache: old = self._cache.get_presence( @@ -459,11 +459,11 @@ async def on_presence_update(self, shard: gateway_shard.GatewayShard, payload: d await self.dispatch(event) async def on_typing_start(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#typing-start for more info.""" + """See for more info.""" await self.dispatch(self._event_factory.deserialize_typing_start_event(shard, payload)) async def on_user_update(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#user-update for more info.""" + """See for more info.""" old = self._cache.get_me() if self._cache else None event = self._event_factory.deserialize_own_user_update_event(shard, payload, old_user=old) @@ -473,7 +473,7 @@ async def on_user_update(self, shard: gateway_shard.GatewayShard, payload: data_ await self.dispatch(event) async def on_voice_state_update(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#voice-state-update for more info.""" + """See for more info.""" old: typing.Optional[voices.VoiceState] = None if self._cache: old = self._cache.get_voice_state( @@ -490,13 +490,13 @@ async def on_voice_state_update(self, shard: gateway_shard.GatewayShard, payload await self.dispatch(event) async def on_voice_server_update(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#voice-server-update for more info.""" + """See for more info.""" await self.dispatch(self._event_factory.deserialize_voice_server_update_event(shard, payload)) async def on_webhooks_update(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#webhooks-update for more info.""" + """See for more info.""" await self.dispatch(self._event_factory.deserialize_webhook_update_event(shard, payload)) async def on_interaction_create(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None: - """See https://discord.com/developers/docs/topics/gateway#interaction-create for more info.""" + """See for more info.""" await self.dispatch(self._event_factory.deserialize_interaction_create_event(shard, payload)) diff --git a/hikari/impl/event_manager_base.py b/hikari/impl/event_manager_base.py index 8f972fd2bd..fce1c58bd6 100644 --- a/hikari/impl/event_manager_base.py +++ b/hikari/impl/event_manager_base.py @@ -85,7 +85,7 @@ async def call_weak_method(event: event_manager_.EventT) -> None: class EventStream(event_manager_.EventStream[event_manager_.EventT]): """An implementation of an event `EventStream` class. - !!! note + .. note:: While calling `EventStream.filter` on an active "opened" event stream will return a wrapping lazy iterator, calling it on an inactive "closed" event stream will return the event stream and add the given predicates diff --git a/hikari/impl/interaction_server.py b/hikari/impl/interaction_server.py index 0a3b6dbe51..180dfd971a 100644 --- a/hikari/impl/interaction_server.py +++ b/hikari/impl/interaction_server.py @@ -134,9 +134,9 @@ class InteractionServer(interaction_server.InteractionServer): The JSON encoder this server should use. Defaults to `json.dumps`. loads : aiohttp.typedefs.JSONDecoder The JSON decoder this server should use. Defaults to `json.loads`. - public_key : builtins.bytes + public_key : bytes The public key this server should use for verifying request payloads from - Discord. If left as `builtins.None` then the client will try to work this + Discord. If left as `None` then the client will try to work this out using `rest_client`. rest_client : hikari.api.rest.RESTClient The client this should use for making REST requests. @@ -179,13 +179,7 @@ def __init__( @property def is_alive(self) -> bool: - """Whether this interaction server is active. - - Returns - ------- - builtins.bool - Whether this interaction server is active - """ + """Whether this interaction server is active.""" return self._server is not None async def _fetch_public_key(self) -> ed25519.VerifierT: @@ -299,18 +293,18 @@ async def join(self) -> None: async def on_interaction(self, body: bytes, signature: bytes, timestamp: bytes) -> interaction_server.Response: """Handle an interaction received from Discord as a REST server. - !!! note + .. note:: If this server instance is alive then this will be called internally by the server but if the instance isn't alive then this may still be called externally to trigger interaction dispatch. Parameters ---------- - body : builtins.bytes + body : bytes The interaction payload. - signature : builtins.bytes + signature : bytes Value of the `"X-Signature-Ed25519"` header used to verify the body. - timestamp : builtins.bytes + timestamp : bytes Value of the `"X-Signature-Timestamp"` header used to verify the body. Returns @@ -388,42 +382,42 @@ async def start( ) -> None: """Start the bot and wait for the internal server to startup then return. + .. note:: + For more information on the other parameters such as defaults see + AIOHTTP's documentation. + Other Parameters ---------------- - backlog : builtins.int + backlog : int The number of unaccepted connections that the system will allow before refusing new connections. - enable_signal_handlers : builtins.bool - Defaults to `builtins.True`. If on a __non-Windows__ OS with builtin + enable_signal_handlers : bool + Defaults to `True`. If on a __non-Windows__ OS with builtin support for kernel-level POSIX signals, then setting this to - `builtins.True` will allow treating keyboard interrupts and other + `True` will allow treating keyboard interrupts and other OS signals to safely shut down the application as calls to shut down the application properly rather than just killing the process in a dirty state immediately. You should leave this disabled unless you plan to implement your own signal handling yourself. - host : typing.Optional[typing.Union[builtins.str, aiohttp.web.HostSequence]] + host : typing.Optional[typing.Union[str, aiohttp.web.HostSequence]] TCP/IP host or a sequence of hosts for the HTTP server. - port : typing.Optional[builtins.int] + port : typing.Optional[int] TCP/IP port for the HTTP server. - path : typing.Optional[builtins.str] + path : typing.Optional[str] File system path for HTTP server unix domain socket. - reuse_address : typing.Optional[builtins.bool] + reuse_address : typing.Optional[bool] Tells the kernel to reuse a local socket in TIME_WAIT state, without waiting for its natural timeout to expire. - reuse_port : typing.Optional[builtins.bool] + reuse_port : typing.Optional[bool] Tells the kernel to allow this endpoint to be bound to the same port as other existing endpoints are also bound to. socket : typing.Optional[socket.socket] A pre-existing socket object to accept connections on. - shutdown_timeout : builtins.float + shutdown_timeout : float A delay to wait for graceful server shutdown before forcefully disconnecting all open client sockets. This defaults to 60 seconds. ssl_context : typing.Optional[ssl.SSLContext] SSL context for HTTPS servers. - - !!! note - For more information on the other parameters such as defaults see - AIOHTTP's documentation. """ if self._server: raise errors.ComponentStateConflictError("Cannot start an already active interaction server") diff --git a/hikari/impl/rate_limits.py b/hikari/impl/rate_limits.py index 0cd5866226..ad100aa61e 100644 --- a/hikari/impl/rate_limits.py +++ b/hikari/impl/rate_limits.py @@ -92,7 +92,7 @@ class BurstRateLimiter(BaseRateLimiter, abc.ABC): """The name of the rate limiter.""" throttle_task: typing.Optional[asyncio.Task[typing.Any]] - """The throttling task, or `builtins.None` if it is not running.""" + """The throttling task, or `None` if it is not running.""" queue: typing.List[asyncio.Future[typing.Any]] """The queue of any futures under a rate limit.""" @@ -138,7 +138,7 @@ def close(self) -> None: @property def is_empty(self) -> bool: - """Return `builtins.True` if no futures are on the queue being rate limited.""" + """Return `True` if no futures are on the queue being rate limited.""" return len(self.queue) == 0 @@ -195,18 +195,18 @@ def throttle(self, retry_after: float) -> None: Parameters ---------- - retry_after : builtins.float + retry_after : float How long to sleep for before unlocking and releasing any futures in the queue. - !!! note + .. note:: This will invoke `ManualRateLimiter.unlock_later` as a scheduled task in the future (it will not await it to finish). When the `ManualRateLimiter.unlock_later` coroutine function completes, it should be expected to set the `throttle_task` to - `builtins.None`. This means you can check if throttling is occurring - by checking if `throttle_task` is not `builtins.None`. + `None`. This means you can check if throttling is occurring + by checking if `throttle_task` is not `None`. If this is invoked while another throttle is in progress, that one is cancelled and a new one is started. This enables new rate limits @@ -223,18 +223,18 @@ async def unlock_later(self, retry_after: float) -> None: Parameters ---------- - retry_after : builtins.float + retry_after : float How long to sleep for before unlocking and releasing any futures in the queue. - !!! note + .. note:: You should not need to invoke this directly. Call `ManualRateLimiter.throttle` instead. When the `ManualRateLimiter.unlock_later` coroutine function completes, it should be expected to set the `throttle_task` to - `builtins.None`. This means you can check if throttling is occurring - by checking if `throttle_task` is not `builtins.None`. + `None`. This means you can check if throttling is occurring + by checking if `throttle_task` is not `None`. """ _LOGGER.warning("you are being globally rate limited for %ss", retry_after) await asyncio.sleep(retry_after) @@ -329,10 +329,10 @@ def get_time_until_reset(self, now: float) -> float: Parameters ---------- - now : builtins.float + now : float The monotonic `time.monotonic_timestamp` timestamp. - !!! warning + .. warning:: Invoking this method will update the internal state if we were previously rate limited, but at the given time are no longer under that limit. This makes it imperative that you only pass the current @@ -341,7 +341,7 @@ def get_time_until_reset(self, now: float) -> float: Returns ------- - builtins.float + float The time left to sleep before the rate limit is reset. If no rate limit is in effect, then this will return `0.0` instead. """ @@ -354,16 +354,16 @@ def is_rate_limited(self, now: float) -> bool: Parameters ---------- - now : builtins.float + now : float The monotonic `time.monotonic_timestamp` timestamp. Returns ------- - builtins.bool - `builtins.True` if we are being rate limited, or `builtins.False` if + bool + `True` if we are being rate limited, or `False` if we are not. - !!! warning + .. warning:: Invoking this method will update the internal state if we were previously rate limited, but at the given time are no longer under that limit. This makes it imperative that you only pass the current @@ -387,14 +387,14 @@ async def throttle(self) -> None: Iterates repeatedly while the queue is not empty, adhering to any rate limits that occur in the mean time. - !!! note + .. note:: You should usually not need to invoke this directly, but if you do, ensure to call it using `asyncio.create_task`, and store the task immediately in `throttle_task`. When this coroutine function completes, it will set the - `throttle_task` to `builtins.None`. This means you can check if throttling - is occurring by checking if `throttle_task` is not `builtins.None`. + `throttle_task` to `None`. This means you can check if throttling + is occurring by checking if `throttle_task` is not `None`. """ _LOGGER.debug( "you are being rate limited on bucket %s, backing off for %ss", @@ -428,23 +428,23 @@ class ExponentialBackOff: Parameters ---------- - base : builtins.float + base : float The base to use. Defaults to `2.0`. - maximum : builtins.float + maximum : float The max value the backoff can be in a single iteration. Anything above this will be capped to this base value plus random jitter. - jitter_multiplier : builtins.float + jitter_multiplier : float The multiplier for the random jitter. Defaults to `1.0`. Set to `0` to disable jitter. - initial_increment : builtins.int + initial_increment : int The initial increment to start at. Defaults to `0`. Raises ------ ValueError - If an `builtins.int` that's too big to be represented as a - `builtins.float` or a non-finite value is passed in place of a field - that's annotated as `builtins.float`. + If an `int` that's too big to be represented as a + `float` or a non-finite value is passed in place of a field + that's annotated as `float`. """ __slots__: typing.Sequence[str] = ("base", "increment", "maximum", "jitter_multiplier") diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py index 7ef058da43..469cfec691 100644 --- a/hikari/impl/rest.py +++ b/hikari/impl/rest.py @@ -121,7 +121,7 @@ class ClientCredentialsStrategy(rest_api.TokenStrategy): client: typing.Optional[snowflakes.SnowflakeishOr[guilds.PartialApplication]] Object or ID of the application this client credentials strategy should authorize as. - client_secret : typing.Optional[builtins.str] + client_secret : typing.Optional[str] Client secret to use when authorizing. Other Parameters @@ -160,13 +160,7 @@ def __init__( @property def client_id(self) -> snowflakes.Snowflake: - """ID of the application this token strategy authenticates with. - - Returns - ------- - hikari.snowflakes.Snowflake - ID of the application this token strategy authenticates with. - """ + """ID of the application this token strategy authenticates with.""" return self._client_id @property @@ -175,13 +169,7 @@ def _is_expired(self) -> bool: @property def scopes(self) -> typing.Sequence[typing.Union[applications.OAuth2Scope, str]]: - """Scopes this token strategy authenticates for. - - Returns - ------- - typing.Sequence[typing.Union[hikari.applications.OAuth2Scope, builtins.str]] - The scopes this token strategy authenticates for. - """ + """Sequence of scopes this token strategy authenticates for.""" return self._scopes @property @@ -267,16 +255,20 @@ class RESTApp(traits.ExecutorAware): `RESTClientImpl` instances for specific credentials acquired from it. + .. note:: + This event loop will be bound to a connector when the first call + to `acquire` is made. + Parameters ---------- executor : typing.Optional[concurrent.futures.Executor] - The executor to use for blocking file IO operations. If `builtins.None` + The executor to use for blocking file IO operations. If `None` is passed, then the default `concurrent.futures.ThreadPoolExecutor` for the `asyncio.AbstractEventLoop` will be used instead. http_settings : typing.Optional[hikari.config.HTTPSettings] HTTP settings to use. Sane defaults are used if this is - `builtins.None`. - max_rate_limit : builtins.float + `None`. + max_rate_limit : float Maximum number of seconds to sleep for when rate limited. If a rate limit occurs that is longer than this value, then a `hikari.errors.RateLimitedError` will be raised instead of waiting. @@ -285,19 +277,15 @@ class RESTApp(traits.ExecutorAware): rate limits. Defaults to five minutes if unspecified. - max_retries : typing.Optional[builtins.int] + max_retries : typing.Optional[int] Maximum number of times a request will be retried if - it fails with a `5xx` status. Defaults to 3 if set to `builtins.None`. + it fails with a `5xx` status. Defaults to 3 if set to `None`. proxy_settings : typing.Optional[hikari.config.ProxySettings] - Proxy settings to use. If `builtins.None` then no proxy configuration + Proxy settings to use. If `None` then no proxy configuration will be used. - url : typing.Optional[builtins.str] + url : typing.Optional[str] The base URL for the API. You can generally leave this as being - `builtins.None` and the correct default API base URL will be generated. - - !!! note - This event loop will be bound to a connector when the first call - to `acquire` is made. + `None` and the correct default API base URL will be generated. """ __slots__: typing.Sequence[str] = ( @@ -357,7 +345,7 @@ def acquire( ) -> RESTClientImpl: """Acquire an instance of this REST client. - !!! note + .. note:: The returned REST client should be started before it can be used, either by calling `RESTClientImpl.start` or by using it as an asynchronous context manager. @@ -375,16 +363,16 @@ def acquire( Parameters ---------- - token : typing.Union[builtins.str, builtins.None, hikari.api.rest.TokenStrategy] + token : typing.Union[str, None, hikari.api.rest.TokenStrategy] The bot or bearer token. If no token is to be used, this can be undefined. - token_type : typing.Union[builtins.str, hikari.applications.TokenType, builtins.None] - The type of token in use. This should only be passed when `builtins.str` + token_type : typing.Union[str, hikari.applications.TokenType, None] + The type of token in use. This should only be passed when `str` is passed for `token`, can be `"Bot"` or `"Bearer"` and will be defaulted to `"Bearer"` in this situation. - This should be left as `builtins.None` when either - `hikari.api.rest.TokenStrategy` or `builtins.None` is passed for + This should be left as `None` when either + `hikari.api.rest.TokenStrategy` or `None` is passed for `token`. Returns @@ -394,7 +382,7 @@ def acquire( Raises ------ - builtins.ValueError + ValueError If `token_type` is provided when a token strategy is passed for `token`. """ # Since we essentially mimic a fake App instance, we need to make a circular provider. @@ -426,7 +414,7 @@ def acquire( class _LiveAttributes: """Fields which are only present within `RESTClientImpl` while it's "alive". - !!! note + .. note:: This must be started within an active asyncio event loop. """ @@ -444,7 +432,7 @@ def build( ) -> _LiveAttributes: """Build a live attributes object. - !!! warning + .. warning:: This can only be called when the current thread has an active asyncio loop. """ @@ -504,36 +492,36 @@ class RESTClientImpl(rest_api.RESTClient): The entity factory to use. executor : typing.Optional[concurrent.futures.Executor] The executor to use for blocking IO. Defaults to the `asyncio` thread - pool if set to `builtins.None`. - max_rate_limit : builtins.float + pool if set to `None`. + max_rate_limit : float Maximum number of seconds to sleep for when rate limited. If a rate limit occurs that is longer than this value, then a `hikari.errors.RateLimitedError` will be raised instead of waiting. This is provided since some endpoints may respond with non-sensible rate limits. - max_retries : typing.Optional[builtins.int] + max_retries : typing.Optional[int] Maximum number of times a request will be retried if - it fails with a `5xx` status. Defaults to 3 if set to `builtins.None`. - token : typing.Union[builtins.str, builtins.None, hikari.api.rest.TokenStrategy] + it fails with a `5xx` status. Defaults to 3 if set to `None`. + token : typing.Union[str, None, hikari.api.rest.TokenStrategy] The bot or bearer token. If no token is to be used, this can be undefined. - token_type : typing.Union[builtins.str, hikari.applications.TokenType, builtins.None] - The type of token in use. This must be passed when a `builtins.str` is + token_type : typing.Union[str, hikari.applications.TokenType, None] + The type of token in use. This must be passed when a `str` is passed for `token` but and can be `"Bot"` or `"Bearer"`. - This should be left as `builtins.None` when either - `hikari.api.rest.TokenStrategy` or `builtins.None` is passed for + This should be left as `None` when either + `hikari.api.rest.TokenStrategy` or `None` is passed for `token`. - rest_url : builtins.str + rest_url : str The HTTP API base URL. This can contain format-string specifiers to interpolate information such as API version in use. Raises ------ - builtins.ValueError + ValueError * If `token_type` is provided when a token strategy is passed for `token`. - * if `token_type` is left as `builtins.None` when a string is passed for `token`. + * if `token_type` is left as `None` when a string is passed for `token`. * If the a value more than 5 is provided for `max_retries` """ @@ -628,7 +616,7 @@ async def close(self) -> None: def start(self) -> None: """Start the HTTP client. - !!! note + .. note:: This must be called within an active event loop. Raises @@ -2739,7 +2727,7 @@ async def edit_my_member( assert isinstance(response, dict) return self._entity_factory.deserialize_member(response, guild_id=snowflakes.Snowflake(guild)) - @deprecation.deprecated("2.0.0.dev104", alternative="RESTClientImpl.edit_my_member's nickname parameter") + @deprecation.deprecated("2.0.0.dev104", "Use `edit_my_member`'s `nick` argument instead.") async def edit_my_nick( self, guild: snowflakes.SnowflakeishOr[guilds.Guild], diff --git a/hikari/impl/rest_bot.py b/hikari/impl/rest_bot.py index ab7620c1e8..8d5d350512 100644 --- a/hikari/impl/rest_bot.py +++ b/hikari/impl/rest_bot.py @@ -66,22 +66,22 @@ class RESTBot(traits.RESTBotAware, interaction_server_.InteractionServer): Parameters ---------- - token : typing.Union[builtins.str, builtins.None, hikari.api.rest.TokenStrategy] + token : typing.Union[str, None, hikari.api.rest.TokenStrategy] The bot or bearer token. If no token is to be used, this can be undefined. - token_type : typing.Union[builtins.str, hikari.applications.TokenType, builtins.None] - The type of token in use. This should only be passed when `builtins.str` + token_type : typing.Union[str, hikari.applications.TokenType, None] + The type of token in use. This should only be passed when `str` is passed for `token`, can be `"Bot"` or `"Bearer"` and will be defaulted to `"Bearer"` in this situation. - This should be left as `builtins.None` when either - `hikari.api.rest.TokenStrategy` or `builtins.None` is passed for + This should be left as `None` when either + `hikari.api.rest.TokenStrategy` or `None` is passed for `token`. Other Parameters ---------------- - allow_color : builtins.bool - Defaulting to `builtins.True`, this will enable coloured console logs + allow_color : bool + Defaulting to `True`, this will enable coloured console logs on any platform that is a TTY. Setting a `"CLICOLOR"` environment variable to any **non `0`** value will override this setting. @@ -91,12 +91,12 @@ class RESTBot(traits.RESTBotAware, interaction_server_.InteractionServer): awkward or not support features in a standard way, the option to explicitly disable this is provided. See `force_color` for an alternative. - banner : typing.Optional[builtins.str] + banner : typing.Optional[str] The package to search for a `banner.txt` in. Defaults to `"hikari"` for the `"hikari/banner.txt"` banner. - Setting this to `builtins.None` will disable the banner being shown. + Setting this to `None` will disable the banner being shown. executor : typing.Optional[concurrent.futures.Executor] - Defaults to `builtins.None`. If non-`builtins.None`, then this executor + Defaults to `None`. If non-`None`, then this executor is used instead of the `concurrent.futures.ThreadPoolExecutor` attached to the `asyncio.AbstractEventLoop` that the bot will run on. This executor is used primarily for file-IO. @@ -107,20 +107,22 @@ class RESTBot(traits.RESTBotAware, interaction_server_.InteractionServer): relies on all objects used in IPC to be `pickle`able. Many third-party libraries will not support this fully though, so your mileage may vary on using ProcessPoolExecutor implementations with this parameter. - force_color : builtins.bool - Defaults to `builtins.False`. If `builtins.True`, then this application + force_color : bool + Defaults to `False`. If `True`, then this application will __force__ colour to be used in console-based output. Specifying a `"CLICOLOR_FORCE"` environment variable with a non-`"0"` value will override this setting. + + This will take precedence over `allow_color` if both are specified. http_settings : typing.Optional[hikari.config.HTTPSettings] Optional custom HTTP configuration settings to use. Allows you to customise functionality such as whether SSL-verification is enabled, what timeouts `aiohttp` should expect to use for requests, and behavior regarding HTTP-redirects. - logs : typing.Union[builtins.None, LoggerLevel, typing.Dict[str, typing.Any]] + logs : typing.Union[None, LoggerLevel, typing.Dict[str, typing.Any]] Defaults to `"INFO"`. - If `builtins.None`, then the Python logging system is left uninitialized + If `None`, then the Python logging system is left uninitialized on startup, and you will need to configure it manually to view most logs that are output by components of this library. @@ -141,7 +143,7 @@ class RESTBot(traits.RESTBotAware, interaction_server_.InteractionServer): Note that `"TRACE_HIKARI"` is a library-specific logging level which is expected to be more verbose than `"DEBUG"`. - max_rate_limit : builtins.float + max_rate_limit : float The max number of seconds to backoff for when rate limited. Anything greater than this will instead raise an error. @@ -154,31 +156,28 @@ class RESTBot(traits.RESTBotAware, interaction_server_.InteractionServer): Note that this only applies to the REST API component that communicates with Discord, and will not affect sharding or third party HTTP endpoints that may be in use. - max_retries : typing.Optional[builtins.int] + max_retries : typing.Optional[int] Maximum number of times a request will be retried if - it fails with a `5xx` status. Defaults to 3 if set to `builtins.None`. + it fails with a `5xx` status. Defaults to 3 if set to `None`. proxy_settings : typing.Optional[config.ProxySettings] Custom proxy settings to use with network-layer logic in your application to get through an HTTP-proxy. - public_key : typing.Union[builtins.str, builtins.bytes, builtins.None] + public_key : typing.Union[str, bytes, None] The public key to use to verify received interaction requests. - This may be a hex encoded `builtins.str` or the raw `builtins.bytes`. - If left as `builtins.None` then the client will try to work this value + This may be a hex encoded `str` or the raw `bytes`. + If left as `None` then the client will try to work this value out based on `token`. - rest_url : typing.Optional[builtins.str] - Defaults to the Discord REST API URL if `builtins.None`. Can be + rest_url : typing.Optional[str] + Defaults to the Discord REST API URL if `None`. Can be overridden if you are attempting to point to an unofficial endpoint, or if you are attempting to mock/stub the Discord API for any reason. Generally you do not want to change this. - !!! note - `force_color` will always take precedence over `allow_color`. - Raises ------ - builtins.ValueError + ValueError * If `token_type` is provided when a token strategy is passed for `token`. - * if `token_type` is left as `builtins.None` when a string is passed for `token`. + * if `token_type` is left as `None` when a string is passed for `token`. """ __slots__: typing.Sequence[str] = ( @@ -327,19 +326,18 @@ def print_banner(banner: typing.Optional[str], allow_color: bool, force_color: b Parameters ---------- - banner : typing.Optional[builtins.str] + banner : typing.Optional[str] The package to find a `banner.txt` in. - allow_color : builtins.bool + allow_color : bool A flag that allows advising whether to allow color if supported or not. Can be overridden by setting a `"CLICOLOR"` environment variable to a non-`"0"` string. - force_color : builtins.bool + force_color : bool A flag that allows forcing color to always be output, even if the terminal device may not support it. Setting the `"CLICOLOR_FORCE"` environment variable to a non-`"0"` string will override this. - !!! note - `force_color` will always take precedence over `allow_color`. + This will take precedence over `allow_color` if both are specified. """ ux.print_banner(banner, allow_color, force_color) @@ -389,17 +387,17 @@ def run( Other Parameters ---------------- - asyncio_debug : builtins.bool - Defaults to `builtins.False`. If `builtins.True`, then debugging is + asyncio_debug : bool + Defaults to `False`. If `True`, then debugging is enabled for the asyncio event loop in use. - backlog : builtins.int + backlog : int The number of unaccepted connections that the system will allow before refusing new connections. - check_for_updates : builtins.bool - Defaults to `builtins.True`. If `builtins.True`, will check for + check_for_updates : bool + Defaults to `True`. If `True`, will check for newer versions of `hikari` on PyPI and notify if available. - close_loop : builtins.bool - Defaults to `builtins.True`. If `builtins.True`, then once the bot + close_loop : bool + Defaults to `True`. If `True`, then once the bot enters a state where all components have shut down permanently during application shutdown, then all asyngens and background tasks will be destroyed, and the event loop will be shut down. @@ -408,42 +406,42 @@ def run( had time to attempt to shut down correctly (around 250ms), and on Python 3.9 and newer, will also shut down the default event loop executor too. - close_passed_executor : builtins.bool - Defaults to `builtins.False`. If `builtins.True`, any custom + close_passed_executor : bool + Defaults to `False`. If `True`, any custom `concurrent.futures.Executor` passed to the constructor will be shut down when the application terminates. This does not affect the default executor associated with the event loop, and will not do anything if you do not provide a custom executor to the constructor. - coroutine_tracking_depth : typing.Optional[builtins.int] - Defaults to `builtins.None`. If an integer value and supported by + coroutine_tracking_depth : typing.Optional[int] + Defaults to `None`. If an integer value and supported by the interpreter, then this many nested coroutine calls will be tracked with their call origin state. This allows you to determine where non-awaited coroutines may originate from, but generally you do not want to leave this enabled for performance reasons. - enable_signal_handlers : builtins.bool - Defaults to `builtins.True`. If on a __non-Windows__ OS with builtin + enable_signal_handlers : bool + Defaults to `True`. If on a __non-Windows__ OS with builtin support for kernel-level POSIX signals, then setting this to - `builtins.True` will allow treating keyboard interrupts and other + `True` will allow treating keyboard interrupts and other OS signals to safely shut down the application as calls to shut down the application properly rather than just killing the process in a dirty state immediately. You should leave this disabled unless you plan to implement your own signal handling yourself. - host : typing.Optional[typing.Union[builtins.str, aiohttp.web.HostSequence]] + host : typing.Optional[typing.Union[str, aiohttp.web.HostSequence]] TCP/IP host or a sequence of hosts for the HTTP server. - port : typing.Optional[builtins.int] + port : typing.Optional[int] TCP/IP port for the HTTP server. - path : typing.Optional[builtins.str] + path : typing.Optional[str] File system path for HTTP server unix domain socket. - reuse_address : typing.Optional[builtins.bool] + reuse_address : typing.Optional[bool] Tells the kernel to reuse a local socket in TIME_WAIT state, without waiting for its natural timeout to expire. - reuse_port : typing.Optional[builtins.bool] + reuse_port : typing.Optional[bool] Tells the kernel to allow this endpoint to be bound to the same port as other existing endpoints are also bound to. socket : typing.Optional[socket.socket] A pre-existing socket object to accept connections on. - shutdown_timeout : builtins.float + shutdown_timeout : float A delay to wait for graceful server shutdown before forcefully disconnecting all open client sockets. This defaults to 60 seconds. ssl_context : typing.Optional[ssl.SSLContext] @@ -505,45 +503,45 @@ async def start( ) -> None: """Start the bot and wait for the internal server to startup then return. + .. note:: + For more information on the other parameters such as defaults see + AIOHTTP's documentation. + Other Parameters ---------------- - backlog : builtins.int + backlog : int The number of unaccepted connections that the system will allow before refusing new connections. - check_for_updates : builtins.bool - Defaults to `builtins.True`. If `builtins.True`, will check for + check_for_updates : bool + Defaults to `True`. If `True`, will check for newer versions of `hikari` on PyPI and notify if available. - enable_signal_handlers : builtins.bool - Defaults to `builtins.True`. If on a __non-Windows__ OS with builtin + enable_signal_handlers : bool + Defaults to `True`. If on a __non-Windows__ OS with builtin support for kernel-level POSIX signals, then setting this to - `builtins.True` will allow treating keyboard interrupts and other + `True` will allow treating keyboard interrupts and other OS signals to safely shut down the application as calls to shut down the application properly rather than just killing the process in a dirty state immediately. You should leave this disabled unless you plan to implement your own signal handling yourself. - host : typing.Optional[typing.Union[builtins.str, aiohttp.web.HostSequence]] + host : typing.Optional[typing.Union[str, aiohttp.web.HostSequence]] TCP/IP host or a sequence of hosts for the HTTP server. - port : typing.Optional[builtins.int] + port : typing.Optional[int] TCP/IP port for the HTTP server. - path : typing.Optional[builtins.str] + path : typing.Optional[str] File system path for HTTP server unix domain socket. - reuse_address : typing.Optional[builtins.bool] + reuse_address : typing.Optional[bool] Tells the kernel to reuse a local socket in TIME_WAIT state, without waiting for its natural timeout to expire. - reuse_port : typing.Optional[builtins.bool] + reuse_port : typing.Optional[bool] Tells the kernel to allow this endpoint to be bound to the same port as other existing endpoints are also bound to. socket : typing.Optional[socket.socket] A pre-existing socket object to accept connections on. - shutdown_timeout : builtins.float + shutdown_timeout : float A delay to wait for graceful server shutdown before forcefully disconnecting all open client sockets. This defaults to 60 seconds. ssl_context : typing.Optional[ssl.SSLContext] SSL context for HTTPS servers. - - !!! note - For more information on the other parameters such as defaults see - AIOHTTP's documentation. """ if self.is_alive: raise errors.ComponentStateConflictError("Cannot start an already active interaction server") diff --git a/hikari/impl/shard.py b/hikari/impl/shard.py index 9482c42322..c35e25c9e2 100644 --- a/hikari/impl/shard.py +++ b/hikari/impl/shard.py @@ -107,11 +107,6 @@ def filterer(entry: str) -> str: return filterer -if typing.TYPE_CHECKING: - # noinspection PyProtectedMember,PyUnresolvedReferences - _ZlibDecompressor = zlib._Decompress - - @typing.final class _GatewayTransport(aiohttp.ClientWebSocketResponse): """Internal component to handle lower-level communication logic. @@ -127,7 +122,7 @@ class _GatewayTransport(aiohttp.ClientWebSocketResponse): def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None: super().__init__(*args, **kwargs) - self.zlib: _ZlibDecompressor = zlib.decompressobj() + self.zlib = zlib.decompressobj() self.sent_close = False # Initialized from `connect' @@ -322,11 +317,22 @@ async def connect( class GatewayShardImpl(shard.GatewayShard): """Implementation of a V8 compatible gateway. + .. note:: + If all four of `initial_activity`, `initial_idle_since`, + `initial_is_afk`, and `initial_status` are not defined and left to their + default values, then the presence will not be _updated_ on startup + at all. + If any of these _are_ specified, then any that are not specified will + be set to sane defaults, which may change the previous status. This will + only occur during startup, and is an artifact of how Discord manages + these updates internally. All other calls to update the status of + the shard will support partial updates. + Parameters ---------- - token : builtins.str + token : str The bot token to use. - url : builtins.str + url : str The gateway URL to use. This should not contain a query-string or fragments. event_manager : hikari.api.event_manager.EventManager @@ -336,48 +342,37 @@ class GatewayShardImpl(shard.GatewayShard): Other Parameters ---------------- - compression : typing.Optional[builtins.str] + compression : typing.Optional[str] Compression format to use for the shard. Only supported values are - `"transport_zlib_stream"` or `builtins.None` to disable it. + `"transport_zlib_stream"` or `None` to disable it. initial_activity : typing.Optional[hikari.presences.Activity] The initial activity to appear to have for this shard, or - `builtins.None` if no activity should be set initially. This is the + `None` if no activity should be set initially. This is the default. initial_idle_since : typing.Optional[datetime.datetime] - The datetime to appear to be idle since, or `builtins.None` if the - shard should not provide this. The default is `builtins.None`. + The datetime to appear to be idle since, or `None` if the + shard should not provide this. The default is `None`. initial_is_afk : bool Whether to appear to be AFK or not on login. Defaults to - `builtins.False`. + `False`. initial_status : hikari.presences.Status The initial status to set on login for the shard. Defaults to `hikari.presences.Status.ONLINE`. intents : hikari.intents.Intents Collection of intents to use. - large_threshold : builtins.int + large_threshold : int The number of members to have in a guild for it to be considered large. - shard_id : builtins.int + shard_id : int The shard ID. - shard_count : builtins.int + shard_count : int The shard count. http_settings : hikari.config.HTTPSettings The HTTP-related settings to use while negotiating a websocket. proxy_settings : hikari.config.ProxySettings The proxy settings to use while negotiating a websocket. - data_format : builtins.str + data_format : str Data format to use for inbound data. Only supported format is `"json"`. - - !!! note - If all four of `initial_activity`, `initial_idle_since`, - `initial_is_afk`, and `initial_status` are not defined and left to their - default values, then the presence will not be _updated_ on startup - at all. - If any of these _are_ specified, then any that are not specified will - be set to sane defaults, which may change the previous status. This will - only occur during startup, and is an artifact of how Discord manages - these updates internally. All other calls to update the status of - the shard will support partial updates. """ __slots__: typing.Sequence[str] = ( diff --git a/hikari/impl/special_endpoints.py b/hikari/impl/special_endpoints.py index 48b1fb3f0e..3ba6795a14 100644 --- a/hikari/impl/special_endpoints.py +++ b/hikari/impl/special_endpoints.py @@ -100,7 +100,7 @@ class TypingIndicator(special_endpoints.TypingIndicator): the typing indicator once, or an async context manager to keep triggering the typing indicator repeatedly until the context finishes. - !!! note + .. note:: This is a helper class that is used by `hikari.api.rest.RESTClient`. You should only ever need to use instances of this class that are produced by that API. @@ -184,7 +184,7 @@ class GuildBuilder(special_endpoints.GuildBuilder): the logic behind creating a guild on an API level is somewhat confusing and detailed. - !!! note + .. note:: This is a helper class that is used by `hikari.api.rest.RESTClient`. You should only ever need to use instances of this class that are produced by that API, thus, any details about the constructor are @@ -221,15 +221,15 @@ class GuildBuilder(special_endpoints.GuildBuilder): await guild_builder.create() ``` - !!! warning + .. warning:: The first role must always be the `@everyone` role. - !!! note + .. note:: If you call `add_role`, the default roles provided by discord will be created. This also applies to the `add_` functions for text channels/voice channels/categories. - !!! note + .. note:: Functions that return a `hikari.snowflakes.Snowflake` do **not** provide the final ID that the object will have once the API call is made. The returned IDs are only able to be used to @@ -747,7 +747,7 @@ class InteractionMessageBuilder(special_endpoints.InteractionMessageBuilder): Other Parameters ---------------- - content : hikari.undefined.UndefinedOr[builtins.str] + content : hikari.undefined.UndefinedOr[str] The content of this response, if supplied. This follows the same rules as "content" on create message. """ @@ -958,12 +958,12 @@ def _build_emoji( Parameters ---------- - emoji : typing.Union[hikari.snowflakes.Snowflakeish, hikari.emojis.Emoji, builtins.str, hikari.undefined.UndefinedType] + emoji : typing.Union[hikari.snowflakes.Snowflakeish, hikari.emojis.Emoji, str, hikari.undefined.UndefinedType] The ID, object or raw string of an emoji to set on a component. Returns ------- - typing.Tuple[hikari.undefined.UndefinedOr[builtins.str], hikari.undefined.UndefinedOr[builtins.str]] + typing.Tuple[hikari.undefined.UndefinedOr[str], hikari.undefined.UndefinedOr[str]] A union of the custom emoji's id if defined (index 0) or the unicode emoji's string representation (index 1). """ # noqa E501 - Line too long diff --git a/hikari/intents.py b/hikari/intents.py index 978ccc265a..af3ec152c5 100644 --- a/hikari/intents.py +++ b/hikari/intents.py @@ -41,7 +41,7 @@ class Intents(enums.Flag): Any events not in an intent category will be fired regardless of what intents you provide. - !!! info + .. note:: Discord now places limits on certain events you can receive without whitelisting your bot first. On the `Bot` tab in the developer's portal for your bot, you should now have the option to enable functionality @@ -51,7 +51,7 @@ class Intents(enums.Flag): your bot for, you will be disconnected on startup with a `4014` closure code. - !!! warning + .. warning:: If you are using the V7 Gateway, you will be REQUIRED to provide some form of intent value when you connect. Failure to do so may result in immediate termination of the session server-side. @@ -194,7 +194,7 @@ class Intents(enums.Flag): * `GUILD_MEMBER_UPDATE` * `GUILD_MEMBER_REMOVE` - !!! warning + .. warning:: This intent is privileged, and requires enabling/whitelisting to use. """ @@ -243,7 +243,7 @@ class Intents(enums.Flag): * `PRESENCE_UPDATE` - !!! warning + .. warning:: This intent is privileged, and requires enabling/whitelisting to use.""" GUILD_MESSAGES = 1 << 9 @@ -315,7 +315,7 @@ class Intents(enums.Flag): ALL_GUILDS_PRIVILEGED = GUILD_MEMBERS | GUILD_PRESENCES """All privileged guild intents. - !!! warning + .. warning:: This set of intent is privileged, and requires enabling/whitelisting to use. """ @@ -326,7 +326,7 @@ class Intents(enums.Flag): This combines `Intents.ALL_GUILDS_UNPRIVILEGED` and `Intents.ALL_GUILDS_PRIVILEGED`. - !!! warning + .. warning:: This set of intent is privileged, and requires enabling/whitelisting to use. """ @@ -349,7 +349,7 @@ class Intents(enums.Flag): ALL_PRIVILEGED = ALL_GUILDS_PRIVILEGED """All privileged intents. - !!! warning + .. warning:: This set of intent is privileged, and requires enabling/whitelisting to use. """ @@ -357,7 +357,7 @@ class Intents(enums.Flag): ALL = ALL_UNPRIVILEGED | ALL_PRIVILEGED """All unprivileged and privileged intents. - !!! warning + .. warning:: This set of intent is privileged, and requires enabling/whitelisting to use. """ @@ -366,7 +366,7 @@ class Intents(enums.Flag): def is_privileged(self) -> bool: """Determine whether the intent requires elevated privileges. - If this is `builtins.True`, you will be required to opt-in to using + If this is `True`, you will be required to opt-in to using this intent on the Discord Developer Portal before you can utilise it in your application. """ diff --git a/hikari/interactions/base_interactions.py b/hikari/interactions/base_interactions.py index 1d6d05763f..2adc0a9d2f 100644 --- a/hikari/interactions/base_interactions.py +++ b/hikari/interactions/base_interactions.py @@ -247,7 +247,7 @@ async def create_initial_response( ) -> None: """Create the initial response for this interaction. - !!! warning + .. warning:: Calling this on an interaction which already has an initial response will result in this raising a `hikari.errors.NotFoundError`. This includes if the REST interaction server has already responded @@ -255,7 +255,7 @@ async def create_initial_response( Parameters ---------- - response_type : typing.Union[builtins.int, CommandResponseTypesT] + response_type : typing.Union[int, CommandResponseTypesT] The type of interaction response this is. Other Parameters @@ -264,7 +264,7 @@ async def create_initial_response( If provided, the message contents. If `hikari.undefined.UNDEFINED`, then nothing will be sent in the content. Any other value here will be cast to a - `builtins.str`. + `str`. If this is a `hikari.embeds.Embed` and no `embed` nor `embeds` kwarg is provided, then this will instead update the embed. This allows @@ -278,28 +278,28 @@ async def create_initial_response( If provided, the message embed. embeds : hikari.undefined.UndefinedOr[typing.Sequence[hikari.embeds.Embed]] If provided, the message embeds. - flags : typing.Union[builtins.int, hikari.messages.MessageFlag, hikari.undefined.UndefinedType] + flags : typing.Union[int, hikari.messages.MessageFlag, hikari.undefined.UndefinedType] If provided, the message flags this response should have. As of writing the only message flag which can be set here is `hikari.messages.MessageFlag.EPHEMERAL`. - tts : hikari.undefined.UndefinedOr[builtins.bool] + tts : hikari.undefined.UndefinedOr[bool] If provided, whether the message will be read out by a screen reader using Discord's TTS (text-to-speech) system. - mentions_everyone : hikari.undefined.UndefinedOr[builtins.bool] + mentions_everyone : hikari.undefined.UndefinedOr[bool] If provided, whether the message should parse @everyone/@here mentions. - user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], builtins.bool]] - If provided, and `builtins.True`, all user mentions will be detected. - If provided, and `builtins.False`, all user mentions will be ignored + user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], bool]] + If provided, and `True`, all user mentions will be detected. + If provided, and `False`, all user mentions will be ignored if appearing in the message body. Alternatively this may be a collection of `hikari.snowflakes.Snowflake`, or `hikari.users.PartialUser` derivatives to enforce mentioning specific users. - role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], builtins.bool]] - If provided, and `builtins.True`, all role mentions will be detected. - If provided, and `builtins.False`, all role mentions will be ignored + role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], bool]] + If provided, and `True`, all role mentions will be detected. + If provided, and `False`, all role mentions will be ignored if appearing in the message body. Alternatively this may be a collection of `hikari.snowflakes.Snowflake`, or @@ -308,10 +308,10 @@ async def create_initial_response( Raises ------ - builtins.ValueError + ValueError If more than 100 unique objects/entities are passed for `role_mentions` or `user_mentions`. - builtins.TypeError + TypeError If both `embed` and `embeds` are specified. hikari.errors.BadRequestError This may be raised in several discrete situations, such as messages @@ -376,13 +376,28 @@ async def edit_initial_response( ) -> messages.Message: """Edit the initial response of this command interaction. + .. note:: + Mentioning everyone, roles, or users in message edits currently + will not send a push notification showing a new mention to people + on Discord. It will still highlight in their chat as if they + were mentioned, however. + + .. warning:: + If you specify a text `content`, `mentions_everyone`, + `mentions_reply`, `user_mentions`, and `role_mentions` will default + to `False` as the message will be re-parsed for mentions. This will + also occur if only one of the four are specified + + This is a limitation of Discord's design. If in doubt, specify all + four of them each time. + Other Parameters ---------------- content : hikari.undefined.UndefinedNoneOr[typing.Any] If provided, the message contents. If `hikari.undefined.UNDEFINED`, then nothing will be sent in the content. Any other value here will be cast to a - `builtins.str`. + `str`. If this is a `hikari.embeds.Embed` and neither the `embed` or `embeds` kwargs are provided or if this is a @@ -397,75 +412,61 @@ async def edit_initial_response( attachment : hikari.undefined.UndefinedOr[hikari.files.Resourceish] If provided, the attachment to set on the message. If `hikari.undefined.UNDEFINED`, the previous attachment, if - present, is not changed. If this is `builtins.None`, then the + present, is not changed. If this is `None`, then the attachment is removed, if present. Otherwise, the new attachment that was provided will be attached. attachments : hikari.undefined.UndefinedOr[typing.Sequence[hikari.files.Resourceish]] If provided, the attachments to set on the message. If `hikari.undefined.UNDEFINED`, the previous attachments, if - present, are not changed. If this is `builtins.None`, then the + present, are not changed. If this is `None`, then the attachments is removed, if present. Otherwise, the new attachments that were provided will be attached. component : hikari.undefined.UndefinedNoneOr[hikari.api.special_endpoints.ComponentBuilder] If provided, builder object of the component to set for this message. This component will replace any previously set components and passing - `builtins.None` will remove all components. + `None` will remove all components. components : hikari.undefined.UndefinedNoneOr[typing.Sequence[hikari.api.special_endpoints.ComponentBuilder]] If provided, a sequence of the component builder objects set for this message. These components will replace any previously set - components and passing `builtins.None` or an empty sequence will + components and passing `None` or an empty sequence will remove all components. embed : hikari.undefined.UndefinedNoneOr[hikari.embeds.Embed] If provided, the embed to set on the message. If `hikari.undefined.UNDEFINED`, the previous embed(s) are not changed. - If this is `builtins.None` then any present embeds are removed. + If this is `None` then any present embeds are removed. Otherwise, the new embed that was provided will be used as the replacement. embeds : hikari.undefined.UndefinedNoneOr[typing.Sequence[hikari.embeds.Embed]] If provided, the embeds to set on the message. If `hikari.undefined.UNDEFINED`, the previous embed(s) are not changed. - If this is `builtins.None` then any present embeds are removed. + If this is `None` then any present embeds are removed. Otherwise, the new embeds that were provided will be used as the replacement. replace_attachments: bool Whether to replace the attachments with the provided ones. Defaults - to `builtins.False`. + to `False`. Note this will also overwrite the embed attachments. - mentions_everyone : hikari.undefined.UndefinedOr[builtins.bool] + mentions_everyone : hikari.undefined.UndefinedOr[bool] If provided, whether the message should parse @everyone/@here mentions. - user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], builtins.bool]] - If provided, and `builtins.True`, all user mentions will be detected. - If provided, and `builtins.False`, all user mentions will be ignored + user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], bool]] + If provided, and `True`, all user mentions will be detected. + If provided, and `False`, all user mentions will be ignored if appearing in the message body. Alternatively this may be a collection of `hikari.snowflakes.Snowflake`, or `hikari.users.PartialUser` derivatives to enforce mentioning specific users. - role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], builtins.bool]] - If provided, and `builtins.True`, all role mentions will be detected. - If provided, and `builtins.False`, all role mentions will be ignored + role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], bool]] + If provided, and `True`, all role mentions will be detected. + If provided, and `False`, all role mentions will be ignored if appearing in the message body. Alternatively this may be a collection of `hikari.snowflakes.Snowflake`, or `hikari.guilds.PartialRole` derivatives to enforce mentioning specific roles. - !!! note - Mentioning everyone, roles, or users in message edits currently - will not send a push notification showing a new mention to people - on Discord. It will still highlight in their chat as if they - were mentioned, however. - - !!! warning - If you specify one of `mentions_everyone`, `user_mentions`, or - `role_mentions`, then all others will default to `builtins.False`, - even if they were enabled previously. - - This is a limitation of Discord's design. If in doubt, specify all three of - them each time. - Returns ------- hikari.messages.Message @@ -473,10 +474,10 @@ async def edit_initial_response( Raises ------ - builtins.ValueError + ValueError If more than 100 unique objects/entities are passed for `role_mentions` or `user_mentions`. - builtins.TypeError + TypeError If both `embed` and `embeds` are specified. hikari.errors.BadRequestError This may be raised in several discrete situations, such as messages diff --git a/hikari/interactions/command_interactions.py b/hikari/interactions/command_interactions.py index 8f9cdf9359..06e862ec6d 100644 --- a/hikari/interactions/command_interactions.py +++ b/hikari/interactions/command_interactions.py @@ -142,15 +142,15 @@ class CommandInteraction(base_interactions.MessageResponseMixin[CommandResponseT guild_id: typing.Optional[snowflakes.Snowflake] = attr.field(eq=False, hash=False, repr=True) """ID of the guild this command interaction event was triggered in. - This will be `builtins.None` for command interactions triggered in DMs. + This will be `None` for command interactions triggered in DMs. """ member: typing.Optional[base_interactions.InteractionMember] = attr.field(eq=False, hash=False, repr=True) """The member who triggered this command interaction. - This will be `builtins.None` for command interactions triggered in DMs. + This will be `None` for command interactions triggered in DMs. - !!! note + .. note:: This member object comes with the extra field `permissions` which contains the member's permissions in the current channel. """ @@ -173,7 +173,7 @@ class CommandInteraction(base_interactions.MessageResponseMixin[CommandResponseT def build_response(self) -> special_endpoints.InteractionMessageBuilder: """Get a message response builder for use in the REST server flow. - !!! note + .. note:: For interactions received over the gateway `CommandInteraction.create_initial_response` should be used to set the interaction response message. @@ -200,12 +200,12 @@ async def handle_command_interaction(interaction: CommandInteraction) -> Interac def build_deferred_response(self) -> special_endpoints.InteractionDeferredBuilder: """Get a deferred message response builder for use in the REST server flow. - !!! note + .. note:: For interactions received over the gateway `CommandInteraction.create_initial_response` should be used to set the interaction response message. - !!! note + .. note:: Unlike `hikari.api.special_endpoints.InteractionMessageBuilder`, the result of this call can be returned as is without any modifications being made to it. @@ -258,15 +258,15 @@ async def fetch_channel(self) -> channels.TextableChannel: def get_channel(self) -> typing.Optional[channels.TextableGuildChannel]: """Get the guild channel this was triggered in from the cache. - !!! note - This will always return `builtins.None` for interactions triggered + .. note:: + This will always return `None` for interactions triggered in a DM channel. Returns ------- typing.Optional[hikari.channels.TextableGuildChannel] The object of the guild channel that was found in the cache or - `builtins.None`. + `None`. """ if isinstance(self.app, traits.CacheAware): channel = self.app.cache.get_guild_channel(self.channel_id) @@ -315,7 +315,7 @@ async def fetch_guild(self) -> typing.Optional[guilds.RESTGuild]: Returns ------- typing.Optional[hikari.guilds.RESTGuild] - Object of the guild this interaction happened in or `builtins.None` + Object of the guild this interaction happened in or `None` if this occurred within a DM channel. Raises @@ -351,7 +351,7 @@ def get_guild(self) -> typing.Optional[guilds.GatewayGuild]: Returns ------- typing.Optional[hikari.guilds.GatewayGuild] - The object of the guild if found, else `builtins.None`. + The object of the guild if found, else `None`. """ if self.guild_id and isinstance(self.app, traits.CacheAware): return self.app.cache.get_guild(self.guild_id) diff --git a/hikari/interactions/component_interactions.py b/hikari/interactions/component_interactions.py index 8fb78c4a98..2352a634e3 100644 --- a/hikari/interactions/component_interactions.py +++ b/hikari/interactions/component_interactions.py @@ -91,7 +91,7 @@ class ComponentInteraction(base_interactions.MessageResponseMixin[ComponentRespo component_type: typing.Union[messages.ComponentType, int] = attr.field(eq=False) """The type of component which triggers this interaction. - !!! note + .. note:: This will never be `ButtonStyle.LINK` as link buttons don't trigger interactions. """ @@ -105,7 +105,7 @@ class ComponentInteraction(base_interactions.MessageResponseMixin[ComponentRespo guild_id: typing.Optional[snowflakes.Snowflake] = attr.field(eq=False) """ID of the guild this interaction was triggered in. - This will be `builtins.None` for command interactions triggered in DMs. + This will be `None` for command interactions triggered in DMs. """ message: messages.Message = attr.field(eq=False, repr=False) """Object of the message the components for this interaction are attached to.""" @@ -113,9 +113,9 @@ class ComponentInteraction(base_interactions.MessageResponseMixin[ComponentRespo member: typing.Optional[base_interactions.InteractionMember] = attr.field(eq=False, hash=False, repr=True) """The member who triggered this interaction. - This will be `builtins.None` for interactions triggered in DMs. + This will be `None` for interactions triggered in DMs. - !!! note + .. note:: This member object comes with the extra field `permissions` which contains the member's permissions in the current channel. """ @@ -126,14 +126,14 @@ class ComponentInteraction(base_interactions.MessageResponseMixin[ComponentRespo def build_response(self, type_: _ImmediateTypesT, /) -> special_endpoints.InteractionMessageBuilder: """Get a message response builder for use in the REST server flow. - !!! note + .. note:: For interactions received over the gateway `ComponentInteraction.create_initial_response` should be used to set the interaction response message. Parameters ---------- - type_ : typing.Union[builtins.int, hikari.interactions.base_interactions.ResponseType] + type_ : typing.Union[int, hikari.interactions.base_interactions.ResponseType] The type of immediate response this should be. This may be one of the following: @@ -166,19 +166,19 @@ async def handle_component_interaction(interaction: ComponentInteraction) -> Int def build_deferred_response(self, type_: _DeferredTypesT, /) -> special_endpoints.InteractionDeferredBuilder: """Get a deferred message response builder for use in the REST server flow. - !!! note + .. note:: For interactions received over the gateway `ComponentInteraction.create_initial_response` should be used to set the interaction response message. - !!! note + .. note:: Unlike `hikari.api.special_endpoints.InteractionMessageBuilder`, the result of this call can be returned as is without any modifications being made to it. Parameters ---------- - type_ : typing.Union[builtins.int, hikari.interactions.base_interactions.ResponseType] + type_ : typing.Union[int, hikari.interactions.base_interactions.ResponseType] The type of deferred response this should be. This may be one of the following: @@ -236,15 +236,15 @@ async def fetch_channel(self) -> channels.TextableChannel: def get_channel(self) -> typing.Union[channels.GuildTextChannel, channels.GuildNewsChannel, None]: """Get the guild channel this interaction occurred in. - !!! note - This will always return `builtins.None` for interactions triggered + .. note:: + This will always return `None` for interactions triggered in a DM channel. Returns ------- - typing.Union[hikari.channels.GuildTextChannel, hikari.channels.GuildNewsChannel, builtins.None] + typing.Union[hikari.channels.GuildTextChannel, hikari.channels.GuildNewsChannel, None] The object of the guild channel that was found in the cache or - `builtins.None`. + `None`. """ if isinstance(self.app, traits.CacheAware): channel = self.app.cache.get_guild_channel(self.channel_id) @@ -259,7 +259,7 @@ async def fetch_guild(self) -> typing.Optional[guilds.RESTGuild]: Returns ------- typing.Optional[hikari.guilds.RESTGuild] - Object of the guild this interaction happened in or `builtins.None` + Object of the guild this interaction happened in or `None` if this occurred within a DM channel. Raises @@ -295,7 +295,7 @@ def get_guild(self) -> typing.Optional[guilds.GatewayGuild]: Returns ------- typing.Optional[hikari.guilds.GatewayGuild] - The object of the guild if found, else `builtins.None`. + The object of the guild if found, else `None`. """ if self.guild_id and isinstance(self.app, traits.CacheAware): return self.app.cache.get_guild(self.guild_id) @@ -312,7 +312,7 @@ async def fetch_parent_message(self) -> messages.Message: Raises ------ - builtins.ValueError + ValueError If `token` is not available. hikari.errors.UnauthorizedError If you are unauthorized to make the request (invalid/missing token). @@ -340,7 +340,7 @@ def get_parent_message(self) -> typing.Optional[messages.PartialMessage]: Returns ------- typing.Optional[hikari.messages.Message] - The object of the message found in the cache or `builtins.None`. + The object of the message found in the cache or `None`. """ if isinstance(self.app, traits.CacheAware): return self.app.cache.get_message(self.message.id) diff --git a/hikari/internal/aio.py b/hikari/internal/aio.py index b77313317e..757aab8b2d 100644 --- a/hikari/internal/aio.py +++ b/hikari/internal/aio.py @@ -53,8 +53,8 @@ def completed_future(result: typing.Optional[T_inv] = None, /) -> asyncio.Future result : T The value to set for the result of the future. `T` is a generic type placeholder for the type that - the future will have set as the result. `T` may be `builtins.None`, in - which case, this will return `asyncio.Future[builtins.None]`. + the future will have set as the result. `T` may be `None`, in + which case, this will return `asyncio.Future[None]`. Returns ------- @@ -119,10 +119,10 @@ async def first_completed( *aws : typing.Awaitable[typing.Any] Awaitables to wait for. timeout : typing.Optional[float] - Optional timeout to wait for, or `builtins.None` to not use one. + Optional timeout to wait for, or `None` to not use one. If the timeout is reached, all awaitables are cancelled immediately. - !!! note + .. note:: If more than one awaitable is completed before entering this call, then the first future is always returned. """ @@ -158,7 +158,7 @@ async def all_of( *aws : typing.Awaitable[T_co] Awaitables to wait for. timeout : typing.Optional[float] - Optional timeout to wait for, or `builtins.None` to not use one. + Optional timeout to wait for, or `None` to not use one. If the timeout is reached, all awaitables are cancelled immediately. Returns diff --git a/hikari/internal/attr_extensions.py b/hikari/internal/attr_extensions.py index eaf965abc4..e91975890d 100644 --- a/hikari/internal/attr_extensions.py +++ b/hikari/internal/attr_extensions.py @@ -73,7 +73,7 @@ def get_fields_definition( Returns ------- - typing.Sequence[typing.Tuple[builtins.str, builtins.str]] + typing.Sequence[typing.Tuple[str, str]] A sequence of tuples of string attribute names to string key-word names. """ init_results = [] @@ -227,13 +227,13 @@ def deep_copy_attrs(model: ModelT, memo: typing.Optional[typing.MutableMapping[i ---------- model : ModelT The attrs model to deep copy. - memo : typing.Optional[typing.MutableMapping[builtins.int, typing.Any]] + memo : typing.Optional[typing.MutableMapping[int, typing.Any]] A memo dictionary of objects already copied during the current copying - pass, see https://docs.python.org/3/library/copy.html for more details. + pass, see for more details. - !!! note + .. note:: This won't deep copy attributes where "skip_deep_copy" is set to - `builtins.True` in their metadata. + `True` in their metadata. Returns ------- @@ -252,7 +252,7 @@ def deep_copy_attrs(model: ModelT, memo: typing.Optional[typing.MutableMapping[i def with_copy(cls: typing.Type[ModelT]) -> typing.Type[ModelT]: """Add a custom implementation for copying attrs models to a class. - !!! note + .. note:: This will only work if the class has an attrs generated init. """ cls.__copy__ = copy_attrs # type: ignore[attr-defined] diff --git a/hikari/internal/cache.py b/hikari/internal/cache.py index 80db93e041..bfbaa147c7 100644 --- a/hikari/internal/cache.py +++ b/hikari/internal/cache.py @@ -197,34 +197,34 @@ class GuildRecord: is_available: typing.Optional[bool] = attr.field(default=None) """Whether the cached guild is available or not. - This will be `builtins.None` when no `GuildRecord.guild` is also - `builtins.None` else `builtins.bool`. + This will be `None` when no `GuildRecord.guild` is also + `None` else `bool`. """ guild: typing.Optional[guilds.GatewayGuild] = attr.field(default=None) """A cached guild object. - This will be `hikari.guilds.GatewayGuild` or `builtins.None` if not cached. + This will be `hikari.guilds.GatewayGuild` or `None` if not cached. """ channels: typing.Optional[typing.MutableSet[snowflakes.Snowflake]] = attr.field(default=None) """A set of the IDs of the guild channels cached for this guild. - This will be `builtins.None` if no channels are cached for this guild else + This will be `None` if no channels are cached for this guild else `typing.MutableSet[hikari.snowflakes.Snowflake]` of channel IDs. """ emojis: typing.Optional[typing.MutableSet[snowflakes.Snowflake]] = attr.field(default=None) """A set of the IDs of the emojis cached for this guild. - This will be `builtins.None` if no emojis are cached for this guild else + This will be `None` if no emojis are cached for this guild else `typing.MutableSet[hikari.snowflakes.Snowflake]` of emoji IDs. """ invites: typing.Optional[typing.MutableSequence[str]] = attr.field(default=None) - """A set of the `builtins.str` codes of the invites cached for this guild. + """A set of the `str` codes of the invites cached for this guild. - This will be `builtins.None` if no invites are cached for this guild else + This will be `None` if no invites are cached for this guild else `typing.MutableSequence[str]` of invite codes. """ @@ -233,7 +233,7 @@ class GuildRecord: ] = attr.field(default=None) """A mapping of user IDs to the objects of members cached for this guild. - This will be `builtins.None` if no members are cached for this guild else + This will be `None` if no members are cached for this guild else `hikari.internal.collections.ExtendedMutableMapping[hikari.snowflakes.Snowflake, MemberData]`. """ @@ -242,14 +242,14 @@ class GuildRecord: ] = attr.field(default=None) """A mapping of user IDs to objects of the presences cached for this guild. - This will be `builtins.None` if no presences are cached for this guild else + This will be `None` if no presences are cached for this guild else `hikari.internal.collections.ExtendedMutableMapping[hikari.snowflakes.Snowflake, MemberPresenceData]`. """ roles: typing.Optional[typing.MutableSet[snowflakes.Snowflake]] = attr.field(default=None) """A set of the IDs of the roles cached for this guild. - This will be `builtins.None` if no roles are cached for this guild else + This will be `None` if no roles are cached for this guild else `typing.MutableSet[hikari.snowflakes.Snowflake]` of role IDs. """ @@ -258,7 +258,7 @@ class GuildRecord: ] = attr.field(default=None) """A mapping of user IDs to objects of the voice states cached for this guild. - This will be `builtins.None` if no voice states are cached for this guild else + This will be `None` if no voice states are cached for this guild else `hikari.internal.collections.ExtendedMutableMapping[hikari.snowflakes.Snowflake, VoiceStateData]`. """ @@ -267,7 +267,7 @@ def empty(self) -> bool: Returns ------- - builtins.bool + bool Whether this guild record has any resources attached to it. """ # As `.is_available` should be paired with `.guild`, we don't need to check both. @@ -288,7 +288,7 @@ def empty(self) -> bool: class BaseData(abc.ABC, typing.Generic[ValueT]): """A data class used for in-memory storage of entities in a more primitive form. - !!! note + .. note:: This base implementation assumes that all the fields it'll handle will be immutable and to handle mutable fields you'll have to override build_entity and build_from_entity to explicitly copy them. diff --git a/hikari/internal/collections.py b/hikari/internal/collections.py index 44db2b9070..7327a88f5d 100644 --- a/hikari/internal/collections.py +++ b/hikari/internal/collections.py @@ -75,7 +75,7 @@ def copy(self: ExtendedMapT) -> ExtendedMapT: This may look like calling `dict.copy` and wrapping the result in a mapped collection. - !!! note + .. note:: Any removal policy on this mapped collection will be copied over. Returns @@ -92,7 +92,7 @@ def freeze(self) -> typing.MutableMapping[KeyT, ValueT]: around how the data is being stored to allow for a more efficient copy. This may look like calling `dict.copy`. - !!! note + .. note:: Unlike `ExtendedMutableMapping.copy`, this should return a pure mapping with no removal policy at all. @@ -171,13 +171,13 @@ class TimedCacheMap(ExtendedMutableMapping[KeyT, ValueT]): Other Parameters ---------------- - source : typing.Optional[typing.Dict[KeyT, typing.Tuple[builtins.float, ValueT]] + source : typing.Optional[typing.Dict[KeyT, typing.Tuple[float, ValueT]] A source dictionary of keys to tuples of float timestamps and values to create this from. on_expire : typing.Optional[typing.Callable[[ValueT], None]] A function to call each time an item is garbage collected from this map. This should take one positional argument of the same type stored - in this mapping as the value and should return `builtins.None`. + in this mapping as the value and should return `None`. This will always be called after the entry has been removed. """ @@ -264,7 +264,7 @@ class LimitedCapacityCacheMap(ExtendedMutableMapping[KeyT, ValueT]): on_expire : typing.Optional[typing.Callable[[ValueT], None]] A function to call each time an item is garbage collected from this map. This should take one positional argument of the same type stored - in this mapping as the value and should return `builtins.None. + in this mapping as the value and should return `None. This will always be called after the entry has been removed. """ @@ -342,13 +342,13 @@ class SnowflakeSet(typing.MutableSet[snowflakes.Snowflake]): $$ \mathcal{O} \left( \log n \right) $$ and best case will be $$ \Omega \left\( k \right) $$ - !!! warning + .. warning:: This is not thread-safe and must not be iterated across whilst being concurrently modified. Other Parameters ---------------- - *ids : builtins.int + *ids : int The IDs to fill this table with. """ @@ -447,7 +447,7 @@ def get_index_or_slice( Raises ------ TypeError - If `index_or_slice` isn't a `builtins.slice` or `builtins.int`. + If `index_or_slice` isn't a `slice` or `int`. IndexError If `index_or_slice` is an int and is outside the range of the mapping's contents. diff --git a/hikari/internal/data_binding.py b/hikari/internal/data_binding.py index a6876bd689..3594fa9c7a 100644 --- a/hikari/internal/data_binding.py +++ b/hikari/internal/data_binding.py @@ -33,6 +33,7 @@ "load_json", "JSONDecodeError", "JSONObjectBuilder", + "StringMapBuilder", "URLEncodedFormBuilder", ] @@ -144,8 +145,8 @@ class StringMapBuilder(multidict.MultiDict[str]): the amount of boilerplate needed for generating the headers and query strings for low-level HTTP API interaction, amongst other things. - !!! warning - Because this subclasses `builtins.dict`, you should not use the + .. warning:: + Because this subclasses `dict`, you should not use the index operator to set items on this object. Doing so will skip any form of validation on the type. Use the `put*` methods instead. """ @@ -187,9 +188,15 @@ def put( ) -> None: """Add a key and value to the string map. + .. note:: + The value will always be cast to a `str` before inserting it. + `True` will be translated to `"true"`, `False` will be + translated to `"false"`, and `None` will be translated to + `"null"`. + Parameters ---------- - key : builtins.str + key : str The string key. value : hikari.undefined.UndefinedOr[typing.Any] The value to set. @@ -198,13 +205,6 @@ def put( ---------------- conversion : typing.Optional[typing.Callable[[typing.Any], typing.Any]] An optional conversion to perform. - - !!! note - The value will always be cast to a `builtins.str` before inserting it. - - `builtins.True` will be translated to `"true"`, `builtins.False` - ill be translated to `"false"`, and `builtins.None` will be - translated to `"null"`. """ if value is not undefined.UNDEFINED: if conversion is not None: @@ -235,8 +235,8 @@ class JSONObjectBuilder(typing.Dict[str, JSONish]): This speeds up generation of JSON payloads for low level HTTP and websocket API interaction. - !!! warning - Because this subclasses `builtins.dict`, you should not use the + .. warning:: + Because this subclasses `dict`, you should not use the index operator to set items on this object. Doing so will skip any form of validation on the type. Use the `put*` methods instead. """ @@ -276,7 +276,7 @@ def put( Parameters ---------- - key : builtins.str + key : str The key to give the element. value : hikari.undefined.UndefinedOr[typing.Any] The JSON type to put. This may be a non-JSON type if a conversion @@ -332,7 +332,7 @@ def put_array( Parameters ---------- - key : builtins.str + key : str The key to give the element. values : hikari.undefined.UndefinedOr[typing.Iterable[T]] The JSON types to put. This may be an iterable of non-JSON types if @@ -359,11 +359,11 @@ def put_snowflake( Parameters ---------- - key : builtins.str + key : str The key to give the element. value : hikari.undefined.UndefinedNoneOr[hikari.snowflakes.SnowflakeishOr[hikari.snowflakes.Unique]] The JSON type to put. This may alternatively be undefined, in this - case, nothing is performed. This may also be `builtins.None`, in this + case, nothing is performed. This may also be `None`, in this case the value isn't cast. """ if value is not undefined.UNDEFINED and value is not None: @@ -381,11 +381,11 @@ def put_snowflake_array( If the value is `hikari.undefined.UNDEFINED` it will not be stored. - Each snowflake should be castable to an `builtins.int`. + Each snowflake should be castable to an `int`. Parameters ---------- - key : builtins.str + key : str The key to give the element. values : hikari.undefined.UndefinedOr[typing.Iterable[hikari.snowflakes.SnowflakeishOr[hikari.snowflakes.Unique]]] The JSON snowflakes to put. This may alternatively be undefined. diff --git a/hikari/internal/deprecation.py b/hikari/internal/deprecation.py index a02396ad7b..0a5e1f986c 100644 --- a/hikari/internal/deprecation.py +++ b/hikari/internal/deprecation.py @@ -24,7 +24,7 @@ from __future__ import annotations -__all__: typing.List[str] = ["deprecated", "warn_deprecated"] +__all__: typing.List[str] = ["warn_deprecated", "deprecated"] import functools import inspect @@ -35,72 +35,63 @@ T = typing.TypeVar("T", bound=typing.Callable[..., typing.Any]) -def warn_deprecated( - obj: typing.Any, - /, - *, - version: typing.Optional[str] = None, - alternative: typing.Optional[str] = None, - stack_level: int = 3, -) -> None: +def warn_deprecated(obj: typing.Any, additional_information: str, /, *, stack_level: int = 3) -> None: """Raise a deprecated warning. Parameters ---------- obj: typing.Any The object that is deprecated. + additional_information: str + Additional information on the deprecation for the user. Other Parameters ---------------- - version: typing.Optional[str] - If specified, the version it will be removed in. - alternative: typing.Optional[str] - If specified, the alternative to use. stack_level: int The stack level for the warning. Defaults to `3`. """ - if inspect.isclass(obj) or inspect.isfunction(obj): + if inspect.isclass(obj): + action = ("Instantiation of", "class") obj = f"{obj.__module__}.{obj.__qualname__}" + else: + if inspect.isfunction(obj): + obj = f"{obj.__module__}.{obj.__qualname__}" - version_str = f"version {version}" if version is not None else "a following version" - message = f"'{obj}' is deprecated and will be removed in {version_str}." + action = ("Call to", "function/method") - if alternative is not None: - message += f" You can use '{alternative}' instead." + warnings.warn( + f"{action[0]} deprecated {action[1]} {obj!r} ({additional_information})", + category=DeprecationWarning, + stacklevel=stack_level, + ) - warnings.warn(message, category=DeprecationWarning, stacklevel=stack_level) +def deprecated(version: str, additional_information: str) -> typing.Callable[[T], T]: + """Mark a function or object as being deprecated. -def deprecated( - version: typing.Optional[str] = None, alternative: typing.Optional[str] = None -) -> typing.Callable[[T], T]: - """Mark a function as deprecated. - - Other Parameters - ---------------- - version: typing.Optional[str] - If specified, the version it will be removed in. - alternative: typing.Optional[str] - If specified, the alternative to use. + Parameters + ---------- + version: typing.Any + The version this function or object is deprecated in. + additional_information: str + Additional information on the deprecation for the user. """ def decorator(obj: T) -> T: - type_str = "class" if inspect.isclass(obj) else "function" - version_str = f"version {version}" if version is not None else "a following version" - alternative_str = f"You can use `{alternative}` instead." if alternative else "" - - doc = inspect.getdoc(obj) or "" - doc += ( - "\n" - "!!! warning\n" - f" This {type_str} is deprecated and will be removed in {version_str}.\n" - f" {alternative_str}\n" - ) - obj.__doc__ = doc + old_doc = inspect.getdoc(obj) + + # If the docstring is inherited we can assume that the deprecation warning was already added there + if old_doc: + first_line_end = old_doc.index("\n") + obj.__doc__ = ( + old_doc[:first_line_end] + + f"\n\n.. deprecated:: {version}\n {additional_information}" + + old_doc[first_line_end:] + ) @functools.wraps(obj) def wrapper(*args: typing.Any, **kwargs: typing.Any) -> typing.Any: - warn_deprecated(obj, version=version, alternative=alternative, stack_level=3) + warn_deprecated(obj, additional_information) return obj(*args, **kwargs) return typing.cast("T", wrapper) diff --git a/hikari/internal/ed25519.py b/hikari/internal/ed25519.py index 9119a09db7..6462887237 100644 --- a/hikari/internal/ed25519.py +++ b/hikari/internal/ed25519.py @@ -35,16 +35,16 @@ Parameters ---------- -body : builtins.bytes +body : bytes The interaction payload. -signature : builtins.bytes +signature : bytes Value of the `"X-Signature-Ed25519"` header. -timestamp : builtins.bytes +timestamp : bytes Value of the `"X-Signature-Timestamp"` header. Returns ------- -builtins.bool +bool Whether the provided arguments match this public key. """ @@ -54,7 +54,7 @@ Parameters ---------- -public_key : builtins.bytes +public_key : bytes The public key to use to verify received interaction requests against. Returns diff --git a/hikari/internal/enums.py b/hikari/internal/enums.py index b0a1354a33..0bc9041358 100644 --- a/hikari/internal/enums.py +++ b/hikari/internal/enums.py @@ -101,6 +101,7 @@ def __setitem__(self, name: str, value: typing.Any) -> None: class _EnumMeta(type): def __call__(cls, value: typing.Any) -> typing.Any: + """Cast a value to the enum, returning the raw value that was passed if value not found.""" try: return cls._value_to_member_map_[value] except KeyError: @@ -236,37 +237,37 @@ class Enum(metaclass=_EnumMeta): Operators on the class ---------------------- * `EnumType["FOO"]` : - Return the member that has the name `FOO`, raising a `builtins.KeyError` + Return the member that has the name `FOO`, raising a `KeyError` if it is not present. * `EnumType.FOO` : Return the member that has the name `FOO`, raising a - `builtins.AttributeError` if it is not present. + `AttributeError` if it is not present. * `EnumType(x)` : Attempt to cast `x` to the enum type by finding an existing member that has the same __value__. If this fails, you should expect a - `builtins.ValueError` to be raised. + `ValueError` to be raised. Operators on each enum member ----------------------------- - * `e1 == e2` : `builtins.bool` + * `e1 == e2` : `bool` Compare equality. - * `e1 != e2` : `builtins.bool` + * `e1 != e2` : `bool` Compare inequality. - * `builtins.repr(e)` : `builtins.str` + * `repr(e)` : `str` Get the machine readable representation of the enum member `e`. - * `builtins.str(e)` : `builtins.str` - Get the `builtins.str` name of the enum member `e`. + * `str(e)` : `str` + Get the `str` name of the enum member `e`. Special properties on each enum member -------------------------------------- - * `name` : `builtins.str` + * `name` : `str` The name of the member. * `value` : The value of the member. The type depends on the implementation type of the enum you are using. All other methods and operators on enum members are inherited from the - member's __value__. For example, an enum extending `builtins.int` would + member's __value__. For example, an enum extending `int` would be able to be used as an `int` type outside these overridden definitions. """ @@ -281,7 +282,7 @@ class Enum(metaclass=_EnumMeta): @property def name(self) -> str: - """Return the name of the enum member as a `builtins.str`.""" + """Return the name of the enum member as a `str`.""" return self._name_ @property @@ -322,6 +323,7 @@ def _name_resolver(members: typing.Dict[int, _Flag], value: int) -> typing.Gener class _FlagMeta(type): def __call__(cls, value: int = 0) -> typing.Any: + """Cast a value to the flag enum, returning the raw value that was passed if values not found.""" # We want to handle value invariantly to avoid issues brought in by different behaviours from sub-classed ints # and floats. This also ensures that .__int__ only returns an invariant int. value = int(value) @@ -455,23 +457,20 @@ class Flag(metaclass=_FlagMeta): implementation, while retaining the majority of the external interface that Python's `enum.Flag` provides. - In simple terms, an `Flag` is a set of wrapped constant `builtins.int` + In simple terms, an `Flag` is a set of wrapped constant `int` values that can be combined in any combination to make a special value. This is a more efficient way of combining things like permissions together into a single integral value, and works by setting individual `1`s and `0`s on the binary representation of the integer. This implementation has extra features, in that it will actively behave - like a `builtins.set` as well. + like a `set` as well. - !!! warning - Despite wrapping `builtins.int` values, conceptually this does not - behave as if it were a subclass of `int`. - - !!! danger - Some semantics such as subtype checking and instance checking may - differ. It is recommended to compare these values using the - `==` operator rather than the `is` operator for safety reasons. + .. warning:: + It is important to keep in mind that some semantics such as subtype + checking and instance checking may differ. It is recommended to compare + these values using the `==` operator rather than the `is` operator for + safety reasons. Especially where pseudo-members created from combinations are cached, results of using of `is` may not be deterministic. This is a side @@ -480,6 +479,9 @@ class Flag(metaclass=_FlagMeta): Failing to observe this __will__ result in unexpected behaviour occurring in your application! + Also important to note is that despite wrapping `int` values, + conceptually this does not behave as if it were a subclass of `int`. + Special Members on the class ---------------------------- * `__enumtype__` : @@ -490,16 +492,16 @@ class Flag(metaclass=_FlagMeta): An immutable `typing.Mapping` that maps each member name to the member value. * ` __objtype__` : - Always `builtins.int`. + Always `int`. Operators on the class ---------------------- * `FlagType["FOO"]` : - Return the member that has the name `FOO`, raising a `builtins.KeyError` + Return the member that has the name `FOO`, raising a `KeyError` if it is not present. * `FlagType.FOO` : Return the member that has the name `FOO`, raising a - `builtins.AttributeError` if it is not present. + `AttributeError` if it is not present. * `FlagType(x)` : Attempt to cast `x` to the enum type by finding an existing member that has the same __value__. If this fails, then a special __composite__ @@ -511,17 +513,17 @@ class Flag(metaclass=_FlagMeta): * `e1 & e2` : Bitwise `AND` operation. Will return a member that contains all flags that are common between both oprands on the values. This also works with - one of the oprands being an `builtins.int`eger. You may instead use + one of the oprands being an `int`eger. You may instead use the `intersection` method. * `e1 | e2` : Bitwise `OR` operation. Will return a member that contains all flags that appear on at least one of the oprands. This also works with - one of the oprands being an `builtins.int`eger. You may instead use + one of the oprands being an `int`eger. You may instead use the `union` method. * `e1 ^ e2` : Bitwise `XOR` operation. Will return a member that contains all flags that only appear on at least one and at most one of the oprands. - This also works with one of the oprands being an `builtins.int`eger. + This also works with one of the oprands being an `int`eger. You may instead use the `symmetric_difference` method. * `~e` : Return the inverse of this value. This is equivalent to disabling all @@ -532,11 +534,11 @@ class Flag(metaclass=_FlagMeta): Bitwise set difference operation. Returns all flags set on `e1` that are not set on `e2` as well. You may instead use the `difference` method. - * `bool(e)` : `builtins.bool` - Return `builtins.True` if `e` has a non-zero value, otherwise - `builtins.False`. - * `E.A in e`: `builtins.bool` - `builtins.True` if `E.A` is in `e`. This is functionally equivalent + * `bool(e)` : `bool` + Return `True` if `e` has a non-zero value, otherwise + `False`. + * `E.A in e`: `bool` + `True` if `E.A` is in `e`. This is functionally equivalent to `E.A & e == E.A`. * `iter(e)` : Explode the value into a iterator of each __documented__ flag that can @@ -546,36 +548,36 @@ class Flag(metaclass=_FlagMeta): powers of two (this means if converted to twos-compliment binary, exactly one bit must be a `1`). In simple terms, this means that you should not expect combination flags to be returned. - * `e1 == e2` : `builtins.bool` + * `e1 == e2` : `bool` Compare equality. - * `e1 != e2` : `builtins.bool` + * `e1 != e2` : `bool` Compare inequality. - * `e1 < e2` : `builtins.bool` + * `e1 < e2` : `bool` Compare by ordering. - * `builtins.int(e)` : `builtins.int` + * `int(e)` : `int` Get the integer value of this flag - * `builtins.repr(e)` : `builtins.str` + * `repr(e)` : `str` Get the machine readable representation of the flag member `e`. - * `builtins.str(e)` : `builtins.str` - Get the `builtins.str` name of the flag member `e`. + * `str(e)` : `str` + Get the `str` name of the flag member `e`. Special properties on each flag member -------------------------------------- - * `e.name` : `builtins.str` + * `e.name` : `str` The name of the member. For composite members, this will be generated. - * `e.value` : `builtins.int` + * `e.value` : `int` The value of the member. Special members on each flag member ----------------------------------- - * `e.all(E.A, E.B, E.C, ...)` : `builtins.bool` - Returns `builtins.True` if __all__ of `E.A`, `E.B`, `E.C`, et cetera + * `e.all(E.A, E.B, E.C, ...)` : `bool` + Returns `True` if __all__ of `E.A`, `E.B`, `E.C`, et cetera make up the value of `e`. - * `e.any(E.A, E.B, E.C, ...)` : `builtins.bool` - Returns `builtins.True` if __any__ of `E.A`, `E.B`, `E.C`, et cetera + * `e.any(E.A, E.B, E.C, ...)` : `bool` + Returns `True` if __any__ of `E.A`, `E.B`, `E.C`, et cetera make up the value of `e`. - * `e.none(E.A, E.B, E.C, ...)` : `builtins.bool` - Returns `builtins.True` if __none__ of `E.A`, `E.B`, `E.C`, et cetera + * `e.none(E.A, E.B, E.C, ...)` : `bool` + Returns `True` if __none__ of `E.A`, `E.B`, `E.C`, et cetera make up the value of `e`. * `e.split()` : `typing.Sequence` Explode the value into a sequence of each __documented__ flag that can @@ -586,7 +588,7 @@ class Flag(metaclass=_FlagMeta): All other methods and operators on `Flag` members are inherited from the member's __value__. - !!! note + .. note:: Due to limitations around how this is re-implemented, this class is not considered a subclass of `Enum` at runtime, even if MyPy believes this is possible @@ -605,14 +607,14 @@ class Flag(metaclass=_FlagMeta): @property def name(self) -> str: - """Return the name of the flag combination as a `builtins.str`.""" + """Return the name of the flag combination as a `str`.""" if self._name_ is None: self._name_ = "|".join(_name_resolver(self._value_to_member_map_, self._value_)) return self._name_ @property def value(self) -> int: - """Return the `builtins.int` value of the flag.""" + """Return the `int` value of the flag.""" return self._value_ def all(self: _T, *flags: _T) -> bool: @@ -620,9 +622,9 @@ def all(self: _T, *flags: _T) -> bool: Returns ------- - builtins.bool - `builtins.True` if any of the given flags are part of this value. - Otherwise, return `builtins.False`. + bool + `True` if any of the given flags are part of this value. + Otherwise, return `False`. """ return all((flag & self) == flag for flag in flags) @@ -631,9 +633,9 @@ def any(self: _T, *flags: _T) -> bool: Returns ------- - builtins.bool - `builtins.True` if any of the given flags are part of this value. - Otherwise, return `builtins.False`. + bool + `True` if any of the given flags are part of this value. + Otherwise, return `False`. """ return any((flag & self) == flag for flag in flags) @@ -661,8 +663,8 @@ def is_disjoint(self: _T, other: typing.Union[_T, int]) -> bool: """Return whether two sets have a intersection or not. If the two sets have an intersection, then this returns - `builtins.False`. If no common flag values exist between them, then - this returns `builtins.True`. + `False`. If no common flag values exist between them, then + this returns `True`. """ return not (self & other) @@ -680,14 +682,14 @@ def is_superset(self: _T, other: typing.Union[_T, int]) -> bool: def none(self: _T, *flags: _T) -> bool: """Check if none of the given flags are part of this value. - !!! note + .. note:: This is essentially the opposite of `Flag.any`. Returns ------- - builtins.bool - `builtins.True` if none of the given flags are part of this value. - Otherwise, return `builtins.False`. + bool + `True` if none of the given flags are part of this value. + Otherwise, return `False`. """ return not self.any(*flags) diff --git a/hikari/internal/enums.pyi b/hikari/internal/enums.pyi index 5633c1472f..5fbf00c989 100644 --- a/hikari/internal/enums.pyi +++ b/hikari/internal/enums.pyi @@ -63,19 +63,18 @@ class Flag(__enum.IntFlag): def __len__(self) -> int: ... @staticmethod def __new__(cls: __Type[__FlagT], value: int = 0) -> __FlagT: ... - # Aliases - def isdisjoint(self: __FlagT, other: __Union[int, __FlagT]) -> bool: ... # is_disjoint - def issuperset(self: __FlagT, other: __Union[int, __FlagT]) -> bool: ... # is_superset - def symmetricdifference(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # symmetric_difference + def isdisjoint(self: __FlagT, other: __Union[int, __FlagT]) -> bool: ... # is_disjoint + def issuperset(self: __FlagT, other: __Union[int, __FlagT]) -> bool: ... # is_superset + def symmetricdifference(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # symmetric_difference def issubset(self: __FlagT, other: __Union[int, __FlagT]) -> bool: ... # is_subset def __contains__(self: __FlagT, other: __Union[int, __FlagT]) -> bool: ... # is_subset - def __rand__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # intersection - def __and__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # intersection - def __ror__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # union - def __or__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # union - def __rsub__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # difference - def __sub__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # difference - def __rxor__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # symmetric_difference - def __xor__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # symmetric_difference - def __invert__(self: __FlagT) -> __FlagT: ... # invert + def __rand__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # intersection + def __and__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # intersection + def __ror__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # union + def __or__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # union + def __rsub__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # difference + def __sub__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # difference + def __rxor__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # symmetric_difference + def __xor__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # symmetric_difference + def __invert__(self: __FlagT) -> __FlagT: ... # invert diff --git a/hikari/internal/fast_protocol.py b/hikari/internal/fast_protocol.py index f657f281a8..918efc3d9a 100644 --- a/hikari/internal/fast_protocol.py +++ b/hikari/internal/fast_protocol.py @@ -105,7 +105,7 @@ def __instancecheck__(self: _T, other: typing.Any) -> bool: class FastProtocolChecking(typing.Protocol, metaclass=_FastProtocolChecking): """An extension to make protocols with faster instance checks. - !!! note + .. note:: All protocols that subclass this class must be decorated with `@typing.runtime_checkable` to keep mypy happy. """ diff --git a/hikari/internal/mentions.py b/hikari/internal/mentions.py index 10e3216f5b..f414be2b01 100644 --- a/hikari/internal/mentions.py +++ b/hikari/internal/mentions.py @@ -46,19 +46,19 @@ def generate_allowed_mentions( Parameters ---------- - mentions_everyone : hikari.undefined.UndefinedOr[builtins.bool] + mentions_everyone : hikari.undefined.UndefinedOr[bool] Whether @everyone and @here mentions are enabled. If - `hikari.undefined.UNDEFINED` or `builtins.False` then this will be disabled. - mentions_reply : hikari.undefined.UndefinedOr[builtins.bool] + `hikari.undefined.UNDEFINED` or `False` then this will be disabled. + mentions_reply : hikari.undefined.UndefinedOr[bool] Whether the reply mention should be enabled. If `hikari.undefined.UNDEFINED` - or `builtins.False` then this will be disabled. - user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], builtins.bool]] + or `False` then this will be disabled. + user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], bool]] Either a sequence of objects/IDs of the users to enabled mentions for, - `True` to allow all mentions or `builtins.False`/`hikari.undefined.UNDEFINED` + `True` to allow all mentions or `False`/`hikari.undefined.UNDEFINED` to disable all user mentions. - role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], builtins.bool]] + role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], bool]] Either a sequence of objects/IDs of the roles to enabled mentions for, - `True` to allow all mentions or `builtins.False`/`hikari.undefined.UNDEFINED` + `True` to allow all mentions or `False`/`hikari.undefined.UNDEFINED` to disable all user mentions. Returns diff --git a/hikari/internal/net.py b/hikari/internal/net.py index d60aac3eda..23e655c144 100644 --- a/hikari/internal/net.py +++ b/hikari/internal/net.py @@ -87,12 +87,12 @@ def create_tcp_connector( Optional Parameters ------------------- - dns_cache: typing.Union[builtins.None, builtins.bool, int] - If `builtins.True`, DNS caching is used with a default TTL of 10 seconds. - If `builtins.False`, DNS caching is disabled. If an `builtins.int` is + dns_cache: typing.Union[None, bool, int] + If `True`, DNS caching is used with a default TTL of 10 seconds. + If `False`, DNS caching is disabled. If an `int` is given, then DNS caching is enabled with an explicit TTL set. If - `builtins.None`, the cache will be enabled and never invalidate. - limit : builtins.int + `None`, the cache will be enabled and never invalidate. + limit : int Number of connections to allow in the pool at a maximum. Returns @@ -120,10 +120,10 @@ def create_client_session( ) -> aiohttp.ClientSession: """Generate a client session using the given settings. - !!! warning + .. warning:: You must invoke this from within a running event loop. - !!! note + .. note:: If you pass an explicit connector, then the connection that is created will not own the connector. You will be expected to manually close it __after__ the returned @@ -133,17 +133,17 @@ def create_client_session( ---------- connector : aiohttp.BaseConnector The connector to use. - connector_owner : builtins.bool - If `builtins.True`, then the client session will close the + connector_owner : bool + If `True`, then the client session will close the connector on shutdown. Otherwise, you must do it manually. http_settings : hikari.config.HTTPSettings HTTP settings to use. - raise_for_status : builtins.bool - `builtins.True` to default to throwing exceptions if a request - fails, or `builtins.False` to default to not. - trust_env : builtins.bool - `builtins.True` to trust anything in environment variables - and the `netrc` file, `builtins.False` to ignore it. + raise_for_status : bool + `True` to default to throwing exceptions if a request + fails, or `False` to default to not. + trust_env : bool + `True` to trust anything in environment variables + and the `netrc` file, `False` to ignore it. ws_response_cls : typing.Type[aiohttp.ClientWebSocketResponse] The websocket response class to use. diff --git a/hikari/internal/reflect.py b/hikari/internal/reflect.py index a6f1abfa38..a9ea0b7853 100644 --- a/hikari/internal/reflect.py +++ b/hikari/internal/reflect.py @@ -46,8 +46,8 @@ def resolve_signature(func: typing.Callable[..., typing.Any]) -> inspect.Signatu func : typing.Callable[..., typing.Any] The function to get the resolved annotations from. - !!! warning - This will use `builtins.eval` to resolve string type-hints and forward + .. warning:: + This will use `eval` to resolve string type-hints and forward references. This has a slight performance overhead, so attempt to cache this info as much as possible. @@ -84,7 +84,7 @@ def profiled(call: typing.Callable[..., _T]) -> typing.Callable[..., _T]: # pra Profile results are dumped to stdout. - !!! warning + .. warning:: This is NOT part of the public API. It should be considered to be internal detail and will likely be removed without prior warning in the future. You have been warned! diff --git a/hikari/internal/routes.py b/hikari/internal/routes.py index b3cfd8dc03..af30246c71 100644 --- a/hikari/internal/routes.py +++ b/hikari/internal/routes.py @@ -77,12 +77,12 @@ def create_url(self, base_url: str) -> str: Parameters ---------- - base_url : builtins.str + base_url : str The base of the URL to prepend to the compiled path. Returns ------- - builtins.str + str The full URL for the route. """ return base_url + self.compiled_path @@ -95,13 +95,13 @@ def create_real_bucket_hash(self, initial_bucket_hash: str) -> str: Parameters ---------- - initial_bucket_hash : builtins.str + initial_bucket_hash : str The initial bucket hash provided by Discord in the HTTP headers for a given response. Returns ------- - builtins.str + str The input hash amalgamated with a hash code produced by the major parameters in this compiled route instance. """ @@ -122,9 +122,9 @@ class Route: Parameters ---------- - method : builtins.str + method : str The HTTP method - path_template : builtins.str + path_template : str The template string for the path to use. """ @@ -204,7 +204,7 @@ def _(self, _: attr.Attribute[typing.AbstractSet[str]], values: typing.AbstractS raise ValueError(f"{self.path_template} must have at least one valid format set") sizable: bool = attr.field(default=True, kw_only=True, repr=False, hash=False, eq=False) - """`builtins.True` if a `size` param can be specified, or `builtins.False` otherwise.""" + """`True` if a `size` param can be specified, or `False` otherwise.""" def compile( self, @@ -218,30 +218,30 @@ def compile( Parameters ---------- - base_url : builtins.str + base_url : str The base URL for the CDN. The generated route is concatenated onto this. - file_format : builtins.str + file_format : str The file format to use for the asset. - size : typing.Optional[builtins.int] - The custom size query parameter to set. If `builtins.None`, + size : typing.Optional[int] + The custom size query parameter to set. If `None`, it is not passed. **kwargs : typing.Any Parameters to interpolate into the path template. Returns ------- - builtins.str + str The full asset URL. Raises ------ - builtins.TypeError + TypeError If a GIF is requested, but the asset is not animated; if an invalid file format for the endpoint is passed; or if a `size` is passed but the route is not `sizable`. - builtins.ValueError + ValueError If `size` is specified, but is not an integer power of `2` between `16` and `4096` inclusive or is negative. """ diff --git a/hikari/internal/time.py b/hikari/internal/time.py index 8795901c7b..09d3bcb130 100644 --- a/hikari/internal/time.py +++ b/hikari/internal/time.py @@ -49,7 +49,7 @@ This is a type that is like an interval of some sort. This is an alias for `typing.Union[int, float, datetime.datetime]`, -where `builtins.int` and `builtins.float` types are interpreted as a number of seconds. +where `int` and `float` types are interpreted as a number of seconds. """ DISCORD_EPOCH: typing.Final[int] = 1_420_070_400 @@ -71,7 +71,7 @@ def slow_iso8601_datetime_string_to_datetime(datetime_str: str) -> datetime.date Parameters ---------- - datetime_str : builtins.str + datetime_str : str The date string to parse. Returns @@ -110,7 +110,7 @@ def discord_epoch_to_datetime(epoch: int, /) -> datetime.datetime: Parameters ---------- - epoch : builtins.int + epoch : int Number of milliseconds since `1/1/2015 00:00:00 UTC`. Returns @@ -122,7 +122,7 @@ def discord_epoch_to_datetime(epoch: int, /) -> datetime.datetime: def datetime_to_discord_epoch(timestamp: datetime.datetime) -> int: - """Parse a `datetime.datetime` object into an `builtins.int` `DISCORD_EPOCH` offset. + """Parse a `datetime.datetime` object into an `int` `DISCORD_EPOCH` offset. Parameters ---------- @@ -131,7 +131,7 @@ def datetime_to_discord_epoch(timestamp: datetime.datetime) -> int: Returns ------- - builtins.int + int Number of milliseconds since `1/1/2015 00:00:00 UTC`. """ return int((timestamp.timestamp() - DISCORD_EPOCH) * 1_000) @@ -140,17 +140,17 @@ def datetime_to_discord_epoch(timestamp: datetime.datetime) -> int: def unix_epoch_to_datetime(epoch: typing.Union[int, float], /, *, is_millis: bool = True) -> datetime.datetime: """Parse a UNIX epoch to a `datetime.datetime` object. - !!! note + .. note:: If an epoch that's outside the range of what this system can handle, this will return `datetime.datetime.max` if the timestamp is positive, or `datetime.datetime.min` otherwise. Parameters ---------- - epoch : typing.Union[builtins.int, builtins.float] + epoch : typing.Union[int, float] Number of seconds/milliseconds since `1/1/1970 00:00:00 UTC`. - is_millis : builtins.bool - `builtins.True` by default, indicates the input timestamp is measured in + is_millis : bool + `True` by default, indicates the input timestamp is measured in milliseconds rather than seconds Returns @@ -180,7 +180,7 @@ def timespan_to_int(value: Intervalish, /) -> int: Returns ------- - builtins.int + int The integer number of seconds. Fractions are discarded. Negative values are removed. """ diff --git a/hikari/internal/ux.py b/hikari/internal/ux.py index ff6215095e..7657392585 100644 --- a/hikari/internal/ux.py +++ b/hikari/internal/ux.py @@ -73,28 +73,28 @@ def init_logging( Parameters ---------- - flavor : typing.Optional[builtins.None, builtins.str, typing.Dict[builtins.str, typing.Any]] + flavor : typing.Optional[None, str, typing.Dict[str, typing.Any]] The hint for configuring logging. - This can be `builtins.None` to not enable logging automatically. + This can be `None` to not enable logging automatically. - If you pass a `builtins.str` or a `builtins.int`, it is interpreted as + If you pass a `str` or a `int`, it is interpreted as the global logging level to use, and should match one of `"DEBUG"`, - `"INFO"`, `"WARNING"`, `"ERROR"` or `"CRITICAL"`, if `builtins.str`. + `"INFO"`, `"WARNING"`, `"ERROR"` or `"CRITICAL"`, if `str`. The configuration will be set up to use a `colorlog` coloured logger, and to use a sane logging format strategy. The output will be written to `sys.stderr` using this configuration. - If you pass a `builtins.dict`, it is treated as the mapping to pass to + If you pass a `dict`, it is treated as the mapping to pass to `logging.config.dictConfig`. If the dict defines any handlers, default handlers will not be setup. - allow_color : builtins.bool - If `builtins.False`, no colour is allowed. If `builtins.True`, the - output device must be supported for this to return `builtins.True`. - force_color : builtins.bool - If `builtins.True`, return `builtins.True` always, otherwise only - return `builtins.True` if the device supports colour output and the - `allow_color` flag is not `builtins.False`. + allow_color : bool + If `False`, no colour is allowed. If `True`, the + output device must be supported for this to return `True`. + force_color : bool + If `True`, return `True` always, otherwise only + return `True` if the device supports colour output and the + `allow_color` flag is not `False`. """ # One observation that has been repeatedly made from seeing beginners writing # bots in Python is that most people seem to have no idea what logging is or @@ -159,19 +159,19 @@ def print_banner(package: typing.Optional[str], allow_color: bool, force_color: Parameters ---------- - package : typing.Optional[builtins.str] - The package to find the `banner.txt` in, or `builtins.None` if no + package : typing.Optional[str] + The package to find the `banner.txt` in, or `None` if no banner should be shown. - !!! note + .. note:: The `banner.txt` must be in the root folder of the package. - allow_color : builtins.bool - If `builtins.False`, no colour is allowed. If `builtins.True`, the - output device must be supported for this to return `builtins.True`. - force_color : builtins.bool - If `builtins.True`, return `builtins.True` always, otherwise only - return `builtins.True` if the device supports colour output and the - `allow_color` flag is not `builtins.False`. + allow_color : bool + If `False`, no colour is allowed. If `True`, the + output device must be supported for this to return `True`. + force_color : bool + If `True`, return `True` always, otherwise only + return `True` if the device supports colour output and the + `allow_color` flag is not `False`. """ if package is None: return @@ -211,23 +211,23 @@ def print_banner(package: typing.Optional[str], allow_color: bool, force_color: def supports_color(allow_color: bool, force_color: bool) -> bool: - """Return `builtins.True` if the terminal device supports color output. + """Return `True` if the terminal device supports color output. Parameters ---------- - allow_color : builtins.bool - If `builtins.False`, no color is allowed. If `builtins.True`, the - output device must be supported for this to return `builtins.True`. - force_color : builtins.bool - If `builtins.True`, return `builtins.True` always, otherwise only - return `builtins.True` if the device supports color output and the - `allow_color` flag is not `builtins.False`. + allow_color : bool + If `False`, no color is allowed. If `True`, the + output device must be supported for this to return `True`. + force_color : bool + If `True`, return `True` always, otherwise only + return `True` if the device supports color output and the + `allow_color` flag is not `False`. Returns ------- - builtins.bool - `builtins.True` if color is allowed on the output terminal, or - `builtins.False` otherwise. + bool + `True` if color is allowed on the output terminal, or + `False` otherwise. """ if not allow_color: return False diff --git a/hikari/invites.py b/hikari/invites.py index cc5a9ebd11..2708816689 100644 --- a/hikari/invites.py +++ b/hikari/invites.py @@ -74,13 +74,7 @@ class InviteCode(abc.ABC): @property @abc.abstractmethod def code(self) -> str: - """Return the code for this invite. - - Returns - ------- - builtins.str - The invite code that can be appended to a URL. - """ + """Code for this invite.""" def __str__(self) -> str: return f"https://discord.gg/{self.code}" @@ -117,14 +111,14 @@ class InviteGuild(guilds.PartialGuild): """The hash for the guild's banner. This is only present if `hikari.guilds.GuildFeature.BANNER` is in the - `features` for this guild. For all other purposes, it is `builtins.None`. + `features` for this guild. For all other purposes, it is `None`. """ description: typing.Optional[str] = attr.field(eq=False, hash=False, repr=False) """The guild's description. This is only present if certain `features` are set in this guild. - Otherwise, this will always be `builtins.None`. For all other purposes, it is `builtins.None`. + Otherwise, this will always be `None`. For all other purposes, it is `None`. """ verification_level: typing.Union[guilds.GuildVerificationLevel, int] = attr.field(eq=False, hash=False, repr=False) @@ -134,7 +128,7 @@ class InviteGuild(guilds.PartialGuild): """The vanity URL code for the guild's vanity URL. This is only present if `hikari.guilds.GuildFeature.VANITY_URL` is in the - `features` for this guild. If not, this will always be `builtins.None`. + `features` for this guild. If not, this will always be `None`. """ welcome_screen: typing.Optional[guilds.WelcomeScreen] = attr.field(eq=False, hash=False, repr=False) @@ -153,21 +147,21 @@ def make_splash_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optio Parameters ---------- - ext : builtins.str + ext : str The extension to use for this URL, defaults to `png`. Supports `png`, `jpeg`, `jpg` and `webp`. - size : builtins.int + size : int The size to set for the URL, defaults to `4096`. Can be any power of two between 16 and 4096. Returns ------- typing.Optional[hikari.files.URL] - The URL to the splash, or `builtins.None` if not set. + The URL to the splash, or `None` if not set. Raises ------ - builtins.ValueError + ValueError If `size` is not a power of two or not between 16 and 4096. """ if self.splash_hash is None: @@ -191,21 +185,21 @@ def make_banner_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optio Parameters ---------- - ext : builtins.str + ext : str The extension to use for this URL, defaults to `png`. Supports `png`, `jpeg`, `jpg` and `webp`. - size : builtins.int + size : int The size to set for the URL, defaults to `4096`. Can be any power of two between 16 and 4096. Returns ------- typing.Optional[hikari.files.URL] - The URL of the banner, or `builtins.None` if no banner is set. + The URL of the banner, or `None` if no banner is set. Raises ------ - builtins.ValueError + ValueError If `size` is not a power of two or not between 16 and 4096. """ if self.banner_hash is None: @@ -236,20 +230,20 @@ class Invite(InviteCode): guild: typing.Optional[InviteGuild] = attr.field(eq=False, hash=False, repr=False) """The partial object of the guild this invite belongs to. - Will be `builtins.None` for group DM invites and when attached to a gateway event; + Will be `None` for group DM invites and when attached to a gateway event; for invites received over the gateway you should refer to `Invite.guild_id`. """ guild_id: typing.Optional[snowflakes.Snowflake] = attr.field(eq=False, hash=False, repr=True) """The ID of the guild this invite belongs to. - Will be `builtins.None` for group DM invites. + Will be `None` for group DM invites. """ channel: typing.Optional[channels.PartialChannel] = attr.field(eq=False, hash=False, repr=False) """The partial object of the channel this invite targets. - Will be `builtins.None` for invite objects that are attached to gateway events, + Will be `None` for invite objects that are attached to gateway events, in which case you should refer to `Invite.channel_id`. """ @@ -284,8 +278,8 @@ class Invite(InviteCode): """When this invite will expire. This field is only returned by the GET Invite REST endpoint and will be - returned as `builtins.None` by said endpoint if the invite doesn't have a set - expiry date. Other places will always return this as `builtins.None`. + returned as `None` by said endpoint if the invite doesn't have a set + expiry date. Other places will always return this as `None`. """ @@ -303,7 +297,7 @@ class InviteWithMetadata(Invite): max_uses: typing.Optional[int] = attr.field(eq=False, hash=False, repr=True) """The limit for how many times this invite can be used before it expires. - If set to `builtins.None` then this is unlimited. + If set to `None` then this is unlimited. """ # TODO: can we use a non-None value to represent infinity here somehow, or @@ -311,7 +305,7 @@ class InviteWithMetadata(Invite): max_age: typing.Optional[datetime.timedelta] = attr.field(eq=False, hash=False, repr=False) """The timedelta of how long this invite will be valid for. - If set to `builtins.None` then this is unlimited. + If set to `None` then this is unlimited. """ is_temporary: bool = attr.field(eq=False, hash=False, repr=True) @@ -323,18 +317,14 @@ class InviteWithMetadata(Invite): expires_at: typing.Optional[datetime.datetime] """When this invite will expire. - If this invite doesn't have a set expiry then this will be `builtins.None`. + If this invite doesn't have a set expiry then this will be `None`. """ @property def uses_left(self) -> typing.Optional[int]: """Return the number of uses left for this invite. - Returns - ------- - typing.Optional[builtins.int] - The number of uses left for this invite. This will be `builtins.None` - if the invite has unlimited uses. + This will be `None` if the invite has unlimited uses. """ if self.max_uses: return self.max_uses - self.uses diff --git a/hikari/iterators.py b/hikari/iterators.py index fef101c501..14f04c6624 100644 --- a/hikari/iterators.py +++ b/hikari/iterators.py @@ -54,13 +54,13 @@ class All(typing.Generic[ValueT]): """Helper that wraps predicates and invokes them together. Calling this object will pass the input item to each item, returning - `builtins.True` only when all wrapped predicates return True when called + `True` only when all wrapped predicates return True when called with the given item. For example... ```py - if w(foo) and x(foo) andy(foo) and z(foo): + if w(foo) and x(foo) and y(foo) and z(foo): ... ``` is equivalent to @@ -71,30 +71,30 @@ class All(typing.Generic[ValueT]): ... ``` - This behaves like a lazy wrapper implementation of the `builtins.all` builtin. + This behaves like a lazy wrapper implementation of the `all` builtin. - !!! note + .. note:: Like the rest of the standard library, this is a short-circuiting - operation. This means that if a predicate returns `builtins.False`, no + operation. This means that if a predicate returns `False`, no predicates after this are invoked, as the result is already known. In this sense, they are invoked in-order. - !!! warning + .. warning:: You should not generally need to use this outside of extending the iterators API in this library! Operators --------- * `this(value : ValueT) -> bool`: - Return `builtins.True` if all conditions return `builtins.True` when + Return `True` if all conditions return `True` when invoked with the given value. * `~this`: Return a condition that, when invoked with the value, returns - `builtins.False` if all conditions were `builtins.True` in this object. + `False` if all conditions were `True` in this object. Parameters ---------- - *conditions : typing.Callable[[ValueT], builtins.bool] + *conditions : typing.Callable[[ValueT], bool] The predicates to wrap. """ @@ -129,7 +129,7 @@ class AttrComparator(typing.Generic[ValueT]): Parameters ---------- - attr_name : builtins.str + attr_name : str The attribute name. Can be prepended with a `.` optionally. If the attribute name ends with a `()`, then the call is invoked rather than treated as a property (useful for methods like @@ -197,7 +197,7 @@ class LazyIterator(typing.Generic[ValueT], abc.ABC): Additionally, you can make use of some of the provided helper methods on this class to perform basic operations easily. - Iterating across the items with indexes (like `builtins.enumerate` for normal + Iterating across the items with indexes (like `enumerate` for normal iterables): ```py @@ -241,7 +241,7 @@ def map( Parameters ---------- - transformation : typing.Union[typing.Callable[[ValueT], builtins.bool], builtins.str] + transformation : typing.Union[typing.Callable[[ValueT], bool], str] The function to use to map the attribute. This may alternatively be a string attribute name to replace the input value with. You can provide nested attributes using the `.` operator. @@ -275,17 +275,17 @@ def filter( Each condition is treated as a predicate, being called with each item that this iterator would return when it is requested. - All conditions must evaluate to `builtins.True` for the item to be + All conditions must evaluate to `True` for the item to be returned. If this is not met, then the item is discarded and ignored, the next matching item will be returned instead, if there is one. Parameters ---------- - *predicates : typing.Union[typing.Callable[[ValueT], builtins.bool], typing.Tuple[builtins.str, typing.Any]] + *predicates : typing.Union[typing.Callable[[ValueT], bool], typing.Tuple[str, typing.Any]] Predicates to invoke. These are functions that take a value and - return `builtins.True` if it is of interest, or `builtins.False` - otherwise. These may instead include 2-`builtins.tuple` objects - consisting of a `builtins.str` attribute name (nested attributes + return `True` if it is of interest, or `False` + otherwise. These may instead include 2-`tuple` objects + consisting of a `str` attribute name (nested attributes are referred to using the `.` operator), and values to compare for equality. This allows you to specify conditions such as `members.filter(("user.bot", True))`. @@ -311,11 +311,11 @@ def take_while( Parameters ---------- - *predicates : typing.Union[typing.Callable[[ValueT], builtins.bool], typing.Tuple[builtins.str, typing.Any]] + *predicates : typing.Union[typing.Callable[[ValueT], bool], typing.Tuple[str, typing.Any]] Predicates to invoke. These are functions that take a value and - return `builtins.True` if it is of interest, or `builtins.False` - otherwise. These may instead include 2-`builtins.tuple` objects - consisting of a `builtins.str` attribute name (nested attributes + return `True` if it is of interest, or `False` + otherwise. These may instead include 2-`tuple` objects + consisting of a `str` attribute name (nested attributes are referred to using the `.` operator), and values to compare for equality. This allows you to specify conditions such as `members.take_while(("user.bot", True))`. @@ -341,11 +341,11 @@ def take_until( Parameters ---------- - *predicates : typing.Union[typing.Callable[[ValueT], builtins.bool], typing.Tuple[builtins.str, typing.Any]] + *predicates : typing.Union[typing.Callable[[ValueT], bool], typing.Tuple[str, typing.Any]] Predicates to invoke. These are functions that take a value and - return `builtins.True` if it is of interest, or `builtins.False` - otherwise. These may instead include 2-`builtins.tuple` objects - consisting of a `builtins.str` attribute name (nested attributes are + return `True` if it is of interest, or `False` + otherwise. These may instead include 2-`tuple` objects + consisting of a `str` attribute name (nested attributes are referred to using the `.` operator), and values to compare for equality. This allows you to specify conditions such as `members.take_until(("user.bot", True))`. @@ -373,11 +373,11 @@ def skip_while( Parameters ---------- - *predicates : typing.Union[typing.Callable[[ValueT], builtins.bool], typing.Tuple[builtins.str, typing.Any]] + *predicates : typing.Union[typing.Callable[[ValueT], bool], typing.Tuple[str, typing.Any]] Predicates to invoke. These are functions that take a value and - return `builtins.True` if it is of interest, or `builtins.False` - otherwise. These may instead include 2-`builtins.tuple` objects - consisting of a `builtins.str` attribute name (nested attributes + return `True` if it is of interest, or `False` + otherwise. These may instead include 2-`tuple` objects + consisting of a `str` attribute name (nested attributes are referred to using the `.` operator), and values to compare for equality. This allows you to specify conditions such as `members.skip_while(("user.bot", True))`. @@ -405,11 +405,11 @@ def skip_until( Parameters ---------- - *predicates : typing.Union[typing.Callable[[ValueT], builtins.bool], typing.Tuple[builtins.str, typing.Any]] + *predicates : typing.Union[typing.Callable[[ValueT], bool], typing.Tuple[str, typing.Any]] Predicates to invoke. These are functions that take a value and - return `builtins.True` if it is of interest, or `builtins.False` - otherwise. These may instead include 2-`builtins.tuple` objects - consisting of a `builtins.str` attribute name (nested attributes are + return `True` if it is of interest, or `False` + otherwise. These may instead include 2-`tuple` objects + consisting of a `str` attribute name (nested attributes are referred to using the `.` operator), and values to compare for equality. This allows you to specify conditions such as `members.skip_until(("user.bot", True))`. @@ -429,42 +429,44 @@ def skip_until( def enumerate(self, *, start: int = 0) -> LazyIterator[typing.Tuple[int, ValueT]]: """Enumerate the paginated results lazily. - This behaves as an asyncio-friendly version of `builtins.enumerate` + This behaves as an asyncio-friendly version of `enumerate` which uses much less memory than collecting all the results first and - calling `builtins.enumerate` across them. + calling `enumerate` across them. Parameters ---------- - start : builtins.int + start : int Optional int to start at. If omitted, this is `0`. Examples -------- - >>> async for i, item in paginated_results.enumerate(): - ... print(i, item) - (0, foo) - (1, bar) - (2, baz) - (3, bork) - (4, qux) - - >>> async for i, item in paginated_results.enumerate(start=9): - ... print(i, item) - (9, foo) - (10, bar) - (11, baz) - (12, bork) - (13, qux) - - >>> async for i, item in paginated_results.enumerate(start=9).limit(3): - ... print(i, item) - (9, foo) - (10, bar) - (11, baz) + ```py + >>> async for i, item in paginated_results.enumerate(): + ... print(i, item) + (0, foo) + (1, bar) + (2, baz) + (3, bork) + (4, qux) + + >>> async for i, item in paginated_results.enumerate(start=9): + ... print(i, item) + (9, foo) + (10, bar) + (11, baz) + (12, bork) + (13, qux) + + >>> async for i, item in paginated_results.enumerate(start=9).limit(3): + ... print(i, item) + (9, foo) + (10, bar) + (11, baz) + ``` Returns ------- - LazyIterator[typing.Tuple[builtins.int, T]] + LazyIterator[typing.Tuple[int, T]] A paginated results view that asynchronously yields an increasing counter in a tuple with each result, lazily. """ @@ -475,13 +477,15 @@ def limit(self, limit: int) -> LazyIterator[ValueT]: Parameters ---------- - limit : builtins.int + limit : int The number of items to get. This must be greater than zero. Examples -------- - >>> async for item in paginated_results.limit(3): - ... print(item) + ```py + >>> async for item in paginated_results.limit(3): + ... print(item) + ``` Returns ------- @@ -496,7 +500,7 @@ def skip(self, number: int) -> LazyIterator[ValueT]: Parameters ---------- - number : builtins.int + number : int The max number of items to drop before any items are yielded. Returns @@ -517,7 +521,7 @@ async def next(self) -> ValueT: Raises ------ - builtins.LookupError + LookupError If no more results exist. """ try: @@ -533,12 +537,12 @@ async def last(self) -> ValueT: ValueT The last result. - !!! note + .. note:: This method will consume the whole iterator if run. Raises ------ - builtins.LookupError + LookupError If no result exists. """ return await self.reversed().next() @@ -575,7 +579,7 @@ async def count(self) -> int: Returns ------- - builtins.int + int Number of results found. """ count = 0 @@ -650,20 +654,20 @@ def awaiting(self, window_size: int = 10) -> LazyIterator[ValueT]: LazyIterator[ValueT] The new lazy iterator to return. - !!! warning + .. warning:: Setting a large window size, or setting it to 0 to await everything is a dangerous thing to do if you are making API calls. Some endpoints will get ratelimited and cause a backup of waiting tasks, others may begin to spam global rate limits instead (the `fetch_user` endpoint seems to be notorious for doing this). - !!! note + .. note:: This call assumes that the iterator contains awaitable values as input. MyPy cannot detect this nicely, so any cast is forced internally. If the item is not awaitable, you will receive a - `builtins.TypeError` instead. + `TypeError` instead. You have been warned. You cannot escape the ways of the duck type young grasshopper. @@ -738,7 +742,7 @@ class BufferedLazyIterator(typing.Generic[ValueT], LazyIterator[ValueT], abc.ABC thus reducing the amount of work needed if only a few objects out of, say, 100, need to be deserialized. - This `_next_chunk` should return `builtins.None` once the end of all items + This `_next_chunk` should return `None` once the end of all items has been reached. An example would look like the following: diff --git a/hikari/messages.py b/hikari/messages.py index c1d5f9fa96..6e5dc83667 100644 --- a/hikari/messages.py +++ b/hikari/messages.py @@ -311,13 +311,13 @@ def get_members(self) -> undefined.UndefinedOr[typing.Mapping[snowflakes.Snowfla If this message was sent in a DM, this will always be empty. - !!! warning + .. warning:: This will only return valid results on gateway events. For REST endpoints, this will potentially be empty. This is a limitation of Discord's API, as they do not consistently notify of the ID of the guild a message was sent in. - !!! note + .. note:: If you are using a stateless application such as a stateless bot or a REST-only client, this will always be empty. Furthermore, if you are running a stateful bot and have the GUILD_MEMBERS @@ -345,13 +345,13 @@ def get_roles(self) -> undefined.UndefinedOr[typing.Mapping[snowflakes.Snowflake If this message was sent in a DM, this will always be empty. - !!! warning + .. warning:: This will only return valid results on gateway events. For REST endpoints, this will potentially be empty. This is a limitation of Discord's API, as they do not consistently notify of the ID of the guild a message was sent in. - !!! note + .. note:: If you are using a stateless application such as a stateless bot or a REST-only client, this will always be empty. Furthermore, if you are running a stateful bot and have the GUILD intent @@ -404,7 +404,7 @@ class MessageReference: id: typing.Optional[snowflakes.Snowflake] = attr.field(repr=True) """The ID of the original message. - This will be `builtins.None` for channel follow add messages. This may + This will be `None` for channel follow add messages. This may point to a deleted message. """ @@ -414,7 +414,7 @@ class MessageReference: guild_id: typing.Optional[snowflakes.Snowflake] = attr.field(repr=True) """The ID of the guild that the message originated from. - This will be `builtins.None` when the original message is not from + This will be `None` when the original message is not from a guild. """ @@ -432,13 +432,7 @@ class MessageApplication(guilds.PartialApplication): @property def cover_image_url(self) -> typing.Optional[files.URL]: - """Cover image URL used on the store. - - Returns - ------- - typing.Optional[hikari.files.URL] - The URL, or `builtins.None` if no cover image exists. - """ + """Cover image URL used on the store or `None` if no cover image exists.""" return self.make_cover_image_url() def make_cover_image_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optional[files.URL]: @@ -446,21 +440,21 @@ def make_cover_image_url(self, *, ext: str = "png", size: int = 4096) -> typing. Parameters ---------- - ext : builtins.str + ext : str The extension to use for this URL, defaults to `png`. Supports `png`, `jpeg`, `jpg` and `webp`. - size : builtins.int + size : int The size to set for the URL, defaults to `4096`. Can be any power of two between 16 and 4096. Returns ------- typing.Optional[hikari.files.URL] - The URL, or `builtins.None` if no cover image exists. + The URL, or `None` if no cover image exists. Raises ------ - builtins.ValueError + ValueError If the size is not an integer power of 2 between 16 and 4096 (inclusive). """ @@ -501,18 +495,18 @@ class ComponentType(int, enums.Enum): ACTION_ROW = 1 """A non-interactive container component for other types of components. - !!! note + .. note:: As this is a container component it can never be contained within another component and therefore will always be top-level. - !!! note + .. note:: As of writing this can only contain one component type. """ BUTTON = 2 """A button component. - !!! note + .. note:: This cannot be top-level and must be within a container component such as `ComponentType.ACTION_ROW`. """ @@ -520,7 +514,7 @@ class ComponentType(int, enums.Enum): SELECT_MENU = 3 """A select menu component. - !!! note + .. note:: This cannot be top-level and must be within a container component such as `ComponentType.ACTION_ROW`. """ @@ -531,7 +525,7 @@ class ButtonStyle(int, enums.Enum): """Enum of the available button styles. More information, such as how these look, can be found at - https://discord.com/developers/docs/interactions/message-components#buttons-button-styles + """ PRIMARY = 1 @@ -549,7 +543,7 @@ class ButtonStyle(int, enums.Enum): LINK = 5 """A grey button which navigates to a URL. - !!! warning + .. warning:: Unlike the other button styles, clicking this one will not trigger an interaction and custom_id shouldn't be included for this style. """ @@ -601,7 +595,7 @@ class PartialComponent: class ButtonComponent(PartialComponent): """Represents a message button component. - !!! note + .. note:: This is an embedded component and will only ever be found within top-level container components such as `ActionRowComponent`. """ @@ -618,7 +612,7 @@ class ButtonComponent(PartialComponent): custom_id: typing.Optional[str] = attr.field(hash=True) """Developer defined identifier for this button (will be <= 100 characters). - !!! note + .. note:: This is required for the following button styles: * `ButtonStyle.PRIMARY` @@ -658,7 +652,7 @@ class SelectMenuOption: class SelectMenuComponent(PartialComponent): """Represents a message button component. - !!! note + .. note:: This is an embedded component and will only ever be found within top-level container components such as `ActionRowComponent`. """ @@ -694,7 +688,7 @@ class SelectMenuComponent(PartialComponent): class ActionRowComponent(PartialComponent): """Represents a row of components attached to a message. - !!! note + .. note:: This is a top-level container component and will never be found within another component. """ @@ -731,7 +725,7 @@ class PartialMessage(snowflakes.Unique): `MessageUpdateEvent`, but for all other purposes should be treated as being optionally specified. - !!! warning + .. warning:: All fields on this model except `channel` and `id` may be set to `hikari.undefined.UNDEFINED` (a singleton) if we have not received information about their state from Discord alongside field @@ -750,11 +744,11 @@ class PartialMessage(snowflakes.Unique): """The ID of the channel that the message was sent in.""" guild_id: typing.Optional[snowflakes.Snowflake] = attr.field(hash=False, eq=False, repr=True) - """The ID of the guild that the message was sent in or `builtins.None` for messages out of guilds. + """The ID of the guild that the message was sent in or `None` for messages out of guilds. - !!! warning - This will also be `builtins.None` for messages received from the REST API. - This is a Discord limitation as stated here https://github.com/discord/discord-api-docs/issues/912 + .. warning:: + This will also be `None` for messages received from the REST API. + This is a Discord limitation as stated here """ author: undefined.UndefinedOr[users_.User] = attr.field(hash=False, eq=False, repr=True) @@ -767,14 +761,14 @@ class PartialMessage(snowflakes.Unique): member: undefined.UndefinedNoneOr[guilds.Member] = attr.field(hash=False, eq=False, repr=False) """The member for the author who created the message. - If the message is not in a guild, this will be `builtins.None`. + If the message is not in a guild, this will be `None`. This will also be `hikari.undefined.UNDEFINED` in some cases such as when Discord updates a message with an embed URL preview. - !!! warning - This will also be `builtins.None` for messages received from the REST API. - This is a Discord limitation as stated here https://github.com/discord/discord-api-docs/issues/912 + .. warning:: + This will also be `None` for messages received from the REST API. + This is a Discord limitation as stated here """ content: undefined.UndefinedNoneOr[str] = attr.field(hash=False, eq=False, repr=False) @@ -786,7 +780,7 @@ class PartialMessage(snowflakes.Unique): edited_timestamp: undefined.UndefinedNoneOr[datetime.datetime] = attr.field(hash=False, eq=False, repr=False) """The timestamp that the message was last edited at. - Will be `builtins.None` if the message wasn't ever edited, or `undefined` + Will be `None` if the message wasn't ever edited, or `undefined` if the info is not available. """ @@ -796,7 +790,7 @@ class PartialMessage(snowflakes.Unique): mentions: Mentions = attr.field(hash=False, eq=False, repr=True) """Description of who is mentioned in a message. - !!! warning + .. warning:: If the contents have not mutated and this is a message update event, some fields that are not affected may be empty instead. @@ -824,7 +818,7 @@ class PartialMessage(snowflakes.Unique): activity: undefined.UndefinedNoneOr[MessageActivity] = attr.field(hash=False, eq=False, repr=False) """The message activity. - !!! note + .. note:: This will only be provided for messages with rich-presence related chat embeds. """ @@ -832,7 +826,7 @@ class PartialMessage(snowflakes.Unique): application: undefined.UndefinedNoneOr[MessageApplication] = attr.field(hash=False, eq=False, repr=False) """The message application. - !!! note + .. note:: This will only be provided for messages with rich-presence related chat embeds. """ @@ -859,7 +853,7 @@ class PartialMessage(snowflakes.Unique): If `type` is `MessageType.REPLY` and `hikari.undefined.UNDEFINED`, Discord's backend didn't attempt to fetch the message, so the status is unknown. If - `type` is `MessageType.REPLY` and `builtins.None`, the message was deleted. + `type` is `MessageType.REPLY` and `None`, the message was deleted. """ interaction: undefined.UndefinedNoneOr[MessageInteraction] = attr.field(hash=False, repr=False) @@ -868,7 +862,7 @@ class PartialMessage(snowflakes.Unique): application_id: undefined.UndefinedNoneOr[snowflakes.Snowflake] = attr.field(hash=False, repr=False) """ID of the application this message was sent by. - !!! note + .. note:: This will only be provided for interaction messages. """ @@ -880,21 +874,19 @@ def make_link(self, guild: typing.Optional[snowflakes.SnowflakeishOr[guilds.Part Other Parameters ---------------- - guild : typing.Union[hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild]] - Object or ID of the guild this message is in or `builtins.None` + guild : typing.Optional[hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild]] + Object or ID of the guild this message is in or `None` to generate a DM message link. - !!! note - This parameter is necessary since `PartialMessage.guild_id` - isn't returned by the REST API regardless of whether the message - is in a DM or not. + This parameter is necessary since `PartialMessage.guild_id` + isn't returned by the REST API regardless of whether the message + is in a DM or not. Returns ------- - builtins.str + str The jump link to the message. """ - # TODO: this doesn't seem like a safe assumption for rest only applications guild_id_str = "@me" if guild is None else str(int(guild)) return f"{urls.BASE_URL}/channels/{guild_id_str}/{self.channel_id}/{self.id}" @@ -958,14 +950,34 @@ async def edit( ) -> Message: """Edit an existing message in a given channel. + .. note:: + Mentioning everyone, roles, or users in message edits currently + will not send a push notification showing a new mention to people + on Discord. It will still highlight in their chat as if they + were mentioned, however. + + .. warning:: + If you specify a text `content`, `mentions_everyone`, + `mentions_reply`, `user_mentions`, and `role_mentions` will default + to `False` as the message will be re-parsed for mentions. This will + also occur if only one of the four are specified + + This is a limitation of Discord's design. If in doubt, specify all + four of them each time. + + .. warning:: + If the message was not sent by your user, the only parameter + you may provide to this call is the `flags` parameter. Anything + else will result in a `hikari.errors.ForbiddenError` being raised. + Parameters ---------- content : hikari.undefined.UndefinedOr[typing.Any] If provided, the message content to update with. If `hikari.undefined.UNDEFINED`, then the content will not - be changed. If `builtins.None`, then the content will be removed. + be changed. If `None`, then the content will be removed. - Any other value will be cast to a `builtins.str` before sending. + Any other value will be cast to a `str` before sending. If this is a `hikari.embeds.Embed` and neither the `embed` or `embeds` kwargs are provided or if this is a @@ -979,67 +991,67 @@ async def edit( attachment : hikari.undefined.UndefinedOr[hikari.files.Resourceish] If provided, the attachment to set on the message. If `hikari.undefined.UNDEFINED`, the previous attachment, if - present, is not changed. If this is `builtins.None`, then the + present, is not changed. If this is `None`, then the attachment is removed, if present. Otherwise, the new attachment that was provided will be attached. attachments : hikari.undefined.UndefinedOr[typing.Sequence[hikari.files.Resourceish]] If provided, the attachments to set on the message. If `hikari.undefined.UNDEFINED`, the previous attachments, if - present, are not changed. If this is `builtins.None`, then the + present, are not changed. If this is `None`, then the attachments is removed, if present. Otherwise, the new attachments that were provided will be attached. component : hikari.undefined.UndefinedNoneOr[hikari.api.special_endpoints.ComponentBuilder] If provided, builder object of the component to set for this message. This component will replace any previously set components and passing - `builtins.None` will remove all components. + `None` will remove all components. components : hikari.undefined.UndefinedNoneOr[typing.Sequence[hikari.api.special_endpoints.ComponentBuilder]] If provided, a sequence of the component builder objects set for this message. These components will replace any previously set - components and passing `builtins.None` or an empty sequence will + components and passing `None` or an empty sequence will remove all components. embed : hikari.undefined.UndefinedNoneOr[hikari.embeds.Embed] If provided, the embed to set on the message. If `hikari.undefined.UNDEFINED`, the previous embed(s) are not changed. - If this is `builtins.None` then any present embeds are removed. + If this is `None` then any present embeds are removed. Otherwise, the new embed that was provided will be used as the replacement. embeds : hikari.undefined.UndefinedNoneOr[typing.Sequence[hikari.embeds.Embed]] If provided, the embeds to set on the message. If `hikari.undefined.UNDEFINED`, the previous embed(s) are not changed. - If this is `builtins.None` then any present embeds are removed. + If this is `None` then any present embeds are removed. Otherwise, the new embeds that were provided will be used as the replacement. replace_attachments: bool Whether to replace the attachments with the provided ones. Defaults - to `builtins.False`. + to `False`. Note this will also overwrite the embed attachments. - mentions_everyone : hikari.undefined.UndefinedOr[builtins.bool] + mentions_everyone : hikari.undefined.UndefinedOr[bool] Sanitation for `@everyone` mentions. If `hikari.undefined.UNDEFINED`, then the previous setting is - not changed. If `builtins.True`, then `@everyone`/`@here` mentions + not changed. If `True`, then `@everyone`/`@here` mentions in the message content will show up as mentioning everyone that can view the chat. - mentions_reply : hikari.undefined.UndefinedOr[builtins.bool] + mentions_reply : hikari.undefined.UndefinedOr[bool] If provided, whether to mention the author of the message that is being replied to. This will not do anything if this is not a reply message. - user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], builtins.bool]] + user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], bool]] Sanitation for user mentions. If `hikari.undefined.UNDEFINED`, then the previous setting is - not changed. If `builtins.True`, all valid user mentions will behave - as mentions. If `builtins.False`, all valid user mentions will not + not changed. If `True`, all valid user mentions will behave + as mentions. If `False`, all valid user mentions will not behave as mentions. You may alternatively pass a collection of `hikari.snowflakes.Snowflake` user IDs, or `hikari.users.PartialUser`-derived objects. - role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], builtins.bool]] + role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], bool]] Sanitation for role mentions. If `hikari.undefined.UNDEFINED`, then the previous setting is - not changed. If `builtins.True`, all valid role mentions will behave - as mentions. If `builtins.False`, all valid role mentions will not + not changed. If `True`, all valid role mentions will behave + as mentions. If `False`, all valid role mentions will not behave as mentions. You may alternatively pass a collection of @@ -1054,33 +1066,6 @@ async def edit( have `MANAGE_MESSAGES` permissions, you can use this call to suppress embeds on another user's message. - !!! note - Mentioning everyone, roles, or users in message edits currently - will not send a push notification showing a new mention to people - on Discord. It will still highlight in their chat as if they - were mentioned, however. - - !!! warning - If you specify a non-embed `content`, `mentions_everyone`, - `mentions_reply`, `user_mentions`, and `role_mentions` will default - to `builtins.False` as the message will be re-parsed for mentions. - - This is a limitation of Discord's design. If in doubt, specify all - four of them each time. - - !!! warning - If you specify one of `mentions_everyone`, `mentions_reply`, - `user_mentions`, or `role_mentions`, then all others will default to - `builtins.False`, even if they were enabled previously. - - This is a limitation of Discord's design. If in doubt, specify all - four of them each time. - - !!! warning - If the message was not sent by your user, the only parameter - you may provide to this call is the `flags` parameter. Anything - else will result in a `hikari.errors.ForbiddenError` being raised. - Returns ------- hikari.messages.Message @@ -1155,7 +1140,7 @@ async def respond( If provided, the message contents. If `hikari.undefined.UNDEFINED`, then nothing will be sent in the content. Any other value here will be cast to a - `builtins.str`. + `str`. If this is a `hikari.embeds.Embed` and no `embed` nor `embeds` kwarg is provided, then this will instead update the embed. This allows @@ -1170,6 +1155,32 @@ async def respond( attachment : hikari.undefined.UndefinedOr[hikari.files.Resourceish], If provided, the message attachment. This can be a resource, or string of a path on your computer or a URL. + + Attachments can be passed as many different things, to aid in + convenience. + + - If a `pathlib.PurePath` or `str` to a valid URL, the + resource at the given URL will be streamed to Discord when + sending the message. Subclasses of + `hikari.files.WebResource` such as + `hikari.files.URL`, + `hikari.messages.Attachment`, + `hikari.emojis.Emoji`, + `EmbedResource`, etc will also be uploaded this way. + This will use bit-inception, so only a small percentage of the + resource will remain in memory at any one time, thus aiding in + scalability. + - If a `hikari.files.Bytes` is passed, or a `str` + that contains a valid data URI is passed, then this is uploaded + with a randomized file name if not provided. + - If a `hikari.files.File`, `pathlib.PurePath` or + `str` that is an absolute or relative path to a file + on your file system is passed, then this resource is uploaded + as an attachment using non-blocking code internally and streamed + using bit-inception where possible. This depends on the + type of `concurrent.futures.Executor` that is being used for + the application (default is a thread pool which supports this + behaviour). attachments : hikari.undefined.UndefinedOr[typing.Sequence[hikari.files.Resourceish]], If provided, the message attachments. These can be resources, or strings consisting of paths on your computer or URLs. @@ -1182,63 +1193,36 @@ async def respond( If provided, the message embed. embeds : hikari.undefined.UndefinedOr[typing.Sequence[hikari.embeds.Embed]] If provided, the message embeds. - tts : hikari.undefined.UndefinedOr[builtins.bool] + tts : hikari.undefined.UndefinedOr[bool] If provided, whether the message will be TTS (Text To Speech). - nonce : hikari.undefined.UndefinedOr[builtins.str] + nonce : hikari.undefined.UndefinedOr[str] If provided, a nonce that can be used for optimistic message sending. - reply : typing.Union[hikari.undefined.UndefinedType, hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage], builtins.bool] - If provided and `builtins.True`, reply to this message. - If provided and not `builtins.bool`, the message to reply to. - mentions_everyone : hikari.undefined.UndefinedOr[builtins.bool] + reply : typing.Union[hikari.undefined.UndefinedType, hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage], bool] + If provided and `True`, reply to this message. + If provided and not `bool`, the message to reply to. + mentions_everyone : hikari.undefined.UndefinedOr[bool] If provided, whether the message should parse @everyone/@here mentions. - mentions_reply : hikari.undefined.UndefinedOr[builtins.bool] + mentions_reply : hikari.undefined.UndefinedOr[bool] If provided, whether to mention the author of the message that is being replied to. This will not do anything if not being used with `reply`. - user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], builtins.bool]] - If provided, and `builtins.True`, all mentions will be parsed. - If provided, and `builtins.False`, no mentions will be parsed. + user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], bool]] + If provided, and `True`, all mentions will be parsed. + If provided, and `False`, no mentions will be parsed. Alternatively this may be a collection of `hikari.snowflakes.Snowflake`, or `hikari.users.PartialUser` derivatives to enforce mentioning specific users. - role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], builtins.bool]] - If provided, and `builtins.True`, all mentions will be parsed. - If provided, and `builtins.False`, no mentions will be parsed. + role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], bool]] + If provided, and `True`, all mentions will be parsed. + If provided, and `False`, no mentions will be parsed. Alternatively this may be a collection of `hikari.snowflakes.Snowflake`, or `hikari.guilds.PartialRole` derivatives to enforce mentioning specific roles. - !!! note - Attachments can be passed as many different things, to aid in - convenience. - - - If a `pathlib.PurePath` or `builtins.str` to a valid URL, the - resource at the given URL will be streamed to Discord when - sending the message. Subclasses of - `hikari.files.WebResource` such as - `hikari.files.URL`, - `hikari.messages.Attachment`, - `hikari.emojis.Emoji`, - `EmbedResource`, etc will also be uploaded this way. - This will use bit-inception, so only a small percentage of the - resource will remain in memory at any one time, thus aiding in - scalability. - - If a `hikari.files.Bytes` is passed, or a `builtins.str` - that contains a valid data URI is passed, then this is uploaded - with a randomized file name if not provided. - - If a `hikari.files.File`, `pathlib.PurePath` or - `builtins.str` that is an absolute or relative path to a file - on your file system is passed, then this resource is uploaded - as an attachment using non-blocking code internally and streamed - using bit-inception where possible. This depends on the - type of `concurrent.futures.Executor` that is being used for - the application (default is a thread pool which supports this - behaviour). - Returns ------- hikari.messages.Message @@ -1261,10 +1245,10 @@ async def respond( If the channel is not found. hikari.errors.InternalServerError If an internal error occurs on Discord while handling the request. - builtins.ValueError + ValueError If more than 100 unique objects/entities are passed for `role_mentions` or `user_mentions`. - builtins.TypeError + TypeError If both `attachment` and `attachments` are specified. """ # noqa: E501 - Line too long if reply is True: @@ -1328,7 +1312,7 @@ async def add_reaction( Parameters ---------- - emoji: typing.Union[builtins.str, hikari.emojis.Emoji] + emoji: typing.Union[str, hikari.emojis.Emoji] Object or name of the emoji to react with. Note that if the emoji is an `hikari.emojis.CustomEmoji` @@ -1408,7 +1392,7 @@ async def remove_reaction( Parameters ---------- - emoji : typing.Union[builtins.str, hikari.emojis.Emoji] + emoji : typing.Union[str, hikari.emojis.Emoji] Object or name of the emoji to remove the reaction for. Other Parameters @@ -1423,24 +1407,26 @@ async def remove_reaction( Examples -------- - # Using a unicode emoji and removing the bot's reaction from this - # reaction. - await message.remove_reaction("\N{OK HAND SIGN}") + ```py + # Using a unicode emoji and removing the bot's reaction from this + # reaction. + await message.remove_reaction("\N{OK HAND SIGN}") - # Using a custom emoji's name and ID to remove a specific user's - # reaction from this reaction. - await message.remove_reaction("a:Distraction", 745991233939439616, user=some_user) + # Using a custom emoji's name and ID to remove a specific user's + # reaction from this reaction. + await message.remove_reaction("a:Distraction", 745991233939439616, user=some_user) - # Using a unicode emoji and removing a specific user from this - # reaction. - await message.remove_reaction("\N{OK HAND SIGN}", user=some_user) + # Using a unicode emoji and removing a specific user from this + # reaction. + await message.remove_reaction("\N{OK HAND SIGN}", user=some_user) - # Using the name and id. - await message.add_reaction("rooAYAYA", 705837374319493284) + # Using the name and id. + await message.add_reaction("rooAYAYA", 705837374319493284) - # Using an Emoji object and removing a specific user from this - # reaction. - await message.remove_reaction(some_emoji_object, user=some_user) + # Using an Emoji object and removing a specific user from this + # reaction. + await message.remove_reaction(some_emoji_object, user=some_user) + ``` Raises ------ @@ -1495,7 +1481,7 @@ async def remove_all_reactions( Other Parameters ---------------- - emoji : hikari.undefined.UndefinedOr[typing.Union[builtins.str, hikari.emojis.Emoji]] + emoji : hikari.undefined.UndefinedOr[typing.Union[str, hikari.emojis.Emoji]] Object or name of the emoji to get the reactions for. If not specified then all reactions are removed. emoji_id : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.emojis.CustomEmoji]] @@ -1503,17 +1489,19 @@ async def remove_all_reactions( This should only be provided when a custom emoji's name is passed for `emoji`. - Example + Examples -------- - # Using a unicode emoji and removing all 👌 reacts from the message. - # reaction. - await message.remove_all_reactions("\N{OK HAND SIGN}") + ```py + # Using a unicode emoji and removing all 👌 reacts from the message. + # reaction. + await message.remove_all_reactions("\N{OK HAND SIGN}") - # Using the name and id. - await message.add_reaction("rooAYAYA", 705837374319493284) + # Using the name and id. + await message.add_reaction("rooAYAYA", 705837374319493284) - # Removing all reactions entirely. - await message.remove_all_reactions() + # Removing all reactions entirely. + await message.remove_all_reactions() + ``` Raises ------ @@ -1562,7 +1550,7 @@ class Message(PartialMessage): edited_timestamp: typing.Optional[datetime.datetime] """The timestamp that the message was last edited at. - Will be `builtins.None` if it wasn't ever edited. + Will be `None` if it wasn't ever edited. """ is_tts: bool @@ -1592,7 +1580,7 @@ class Message(PartialMessage): activity: typing.Optional[MessageActivity] """The message activity. - !!! note + .. note:: This will only be provided for messages with rich-presence related chat embeds. """ @@ -1600,7 +1588,7 @@ class Message(PartialMessage): application: typing.Optional[MessageApplication] """The message application. - !!! note + .. note:: This will only be provided for messages with rich-presence related chat embeds. """ @@ -1626,7 +1614,7 @@ class Message(PartialMessage): application_id: typing.Optional[snowflakes.Snowflake] """ID of the application this message was sent by. - !!! note + .. note:: This will only be provided for interaction messages. """ diff --git a/hikari/permissions.py b/hikari/permissions.py index 5e94e92468..4d67aa9783 100644 --- a/hikari/permissions.py +++ b/hikari/permissions.py @@ -98,7 +98,7 @@ class Permissions(enums.Flag): KICK_MEMBERS = 1 << 1 """Allows kicking members. - !!! note + .. note:: In guilds with server-wide 2FA enabled this permission can only be used by users who have two-factor authentication enabled on their account (or their owner's account in the case of bot users) and the guild owner. @@ -107,7 +107,7 @@ class Permissions(enums.Flag): BAN_MEMBERS = 1 << 2 """Allows banning members. - !!! note + .. note:: In guilds with server-wide 2FA enabled this permission can only be used by users who have two-factor authentication enabled on their account (or their owner's account in the case of bot users) and the guild owner. @@ -116,7 +116,7 @@ class Permissions(enums.Flag): ADMINISTRATOR = 1 << 3 """Allows all permissions and bypasses channel permission overwrites. - !!! note + .. note:: In guilds with server-wide 2FA enabled this permission can only be used by users who have two-factor authentication enabled on their account (or their owner's account in the case of bot users) and the guild owner. @@ -125,7 +125,7 @@ class Permissions(enums.Flag): MANAGE_CHANNELS = 1 << 4 """Allows management and editing of channels. - !!! note + .. note:: In guilds with server-wide 2FA enabled this permission can only be used by users who have two-factor authentication enabled on their account (or their owner's account in the case of bot users) and the guild owner. @@ -134,7 +134,7 @@ class Permissions(enums.Flag): MANAGE_GUILD = 1 << 5 """Allows management and editing of the guild. - !!! note + .. note:: In guilds with server-wide 2FA enabled this permission can only be used by users who have two-factor authentication enabled on their account (or their owner's account in the case of bot users) and the guild owner. @@ -164,7 +164,7 @@ class Permissions(enums.Flag): MANAGE_MESSAGES = 1 << 13 """Allows for deletion of other users messages. - !!! note + .. note:: In guilds with server-wide 2FA enabled this permission can only be used by users who have two-factor authentication enabled on their account (or their owner's account in the case of bot users) and the guild owner. @@ -219,7 +219,7 @@ class Permissions(enums.Flag): MANAGE_ROLES = 1 << 28 """Allows management and editing of roles. - !!! note + .. note:: In guilds with server-wide 2FA enabled this permission can only be used by users who have two-factor authentication enabled on their account (or their owner's account in the case of bot users) and the guild owner. @@ -228,7 +228,7 @@ class Permissions(enums.Flag): MANAGE_WEBHOOKS = 1 << 29 """Allows management and editing of webhooks. - !!! note + .. note:: In guilds with server-wide 2FA enabled this permission can only be used by users who have two-factor authentication enabled on their account (or their owner's account in the case of bot users) and the guild owner. @@ -237,7 +237,7 @@ class Permissions(enums.Flag): MANAGE_EMOJIS_AND_STICKERS = 1 << 30 """Allows management and editing of emojis and stickers. - !!! note + .. note:: In guilds with server-wide 2FA enabled this permission can only be used by users who have two-factor authentication enabled on their account (or their owner's account in the case of bot users) and the guild owner. @@ -249,7 +249,7 @@ class Permissions(enums.Flag): REQUEST_TO_SPEAK = 1 << 32 """Allows for requesting to speak in stage channels. - !!! warning + .. warning:: This permissions is currently defined as being "under active development" by Discord meaning that "it may be changed or removed" without warning. @@ -258,7 +258,7 @@ class Permissions(enums.Flag): MANAGE_THREADS = 1 << 34 """Allows for deleting and archiving threads, and viewing all private threads. - !!! note + .. note:: In guilds with server-wide 2FA enabled this permission can only be used by users who have two-factor authentication enabled on their account (or their owner's account in the case of bot users) and the guild owner. diff --git a/hikari/presences.py b/hikari/presences.py index 73192e781f..564db5fb8f 100644 --- a/hikari/presences.py +++ b/hikari/presences.py @@ -65,7 +65,7 @@ class ActivityType(int, enums.Enum): STREAMING = 1 """Shows up as `Streaming` and links to a Twitch or YouTube stream/video. - !!! warning + .. warning:: You **MUST** provide a valid Twitch or YouTube stream URL to the activity you create in order for this to be valid. If you fail to do this, then the activity **WILL NOT** update. @@ -83,7 +83,7 @@ class ActivityType(int, enums.Enum): To set an emoji with the status, place a unicode emoji or Discord emoji (`:smiley:`) as the first part of the status activity name. - !!! warning + .. warning:: Bots **DO NOT** support setting custom statuses. """ diff --git a/hikari/snowflakes.py b/hikari/snowflakes.py index 05092a0e00..9a5506264d 100644 --- a/hikari/snowflakes.py +++ b/hikari/snowflakes.py @@ -52,7 +52,7 @@ class Snowflake(int): """A concrete representation of a unique ID for an entity on Discord. - This object can be treated as a regular `builtins.int` for most purposes. + This object can be treated as a regular `int` for most purposes. """ __slots__: typing.Sequence[str] = () @@ -157,7 +157,7 @@ def calculate_shard_id( Parameters ---------- - app_or_count : typing.Union[hikari.traits.ShardAware, builtins.int] + app_or_count : typing.Union[hikari.traits.ShardAware, int] The shard aware app of the current application or the integer count of the current app's shards. guild : SnowflakeishOr[hikari.guilds.PartialGuild] @@ -165,7 +165,7 @@ def calculate_shard_id( Returns ------- - builtins.int + int The zero-indexed integer ID of the shard that should cover this guild. """ shard_count = app_or_count if isinstance(app_or_count, int) else app_or_count.shard_count @@ -182,7 +182,7 @@ def calculate_shard_id( The valid types for this type hint are: -- `builtins.int` +- `int` - `Snowflake` """ @@ -194,8 +194,8 @@ def calculate_shard_id( The valid types for this type hint are: -- `builtins.str` containing digits. -- `builtins.int` +- `str` containing digits. +- `int` - `Snowflake` - `datetime.datetime` """ @@ -208,7 +208,7 @@ def calculate_shard_id( This is a value that is `Snowflake`-ish or a specific type covariant. If you see `SnowflakeishOr[Foo]` anywhere as a type hint, it means the value -may be a `Foo` instance, a `Snowflake`, a `builtins.int` or a `builtins.str` +may be a `Foo` instance, a `Snowflake`, a `int` or a `str` with numeric digits only. Essentially this represents any concrete object, or ID of that object. It is diff --git a/hikari/stickers.py b/hikari/stickers.py index cc19455f8a..b9cbcbedbd 100644 --- a/hikari/stickers.py +++ b/hikari/stickers.py @@ -72,7 +72,7 @@ class StickerFormatType(int, enums.Enum): LOTTIE = 3 """A lottie sticker. - More information can be found here: https://airbnb.io/lottie/ + More information can be found here: """ @@ -112,10 +112,10 @@ def make_banner_url(self, *, ext: str = "png", size: int = 4096) -> files.URL: Parameters ---------- - ext : builtins.str + ext : str The extension to use for this URL, defaults to `png`. Supports `png`, `jpeg`, `jpg` and `webp`. - size : builtins.int + size : int The size to set for the URL, defaults to `4096`. Can be any power of two between 16 and 4096. @@ -126,7 +126,7 @@ def make_banner_url(self, *, ext: str = "png", size: int = 4096) -> files.URL: Raises ------ - builtins.ValueError + ValueError If `size` is not a power of two or not between 16 and 4096. """ return routes.CDN_STICKER_PACK_BANNER.compile_to_file( diff --git a/hikari/templates.py b/hikari/templates.py index fd8d668dbb..af957bd7be 100644 --- a/hikari/templates.py +++ b/hikari/templates.py @@ -63,7 +63,7 @@ class TemplateRole(guilds.PartialRole): is_hoisted: bool = attr.field(eq=False, hash=False, repr=True) """Whether this role is hoisting the members it's attached to in the member list. - members will be hoisted under their highest role where this is set to `builtins.True`. + members will be hoisted under their highest role where this is set to `True`. """ is_mentionable: bool = attr.field(eq=False, hash=False, repr=False) @@ -108,7 +108,7 @@ class TemplateGuild(guilds.PartialGuild): roles: typing.Mapping[snowflakes.Snowflake, TemplateRole] = attr.field(eq=False, hash=False, repr=False) """The roles in the guild. - !!! note + .. note:: `hikari.guilds.Role.id` will be a unique placeholder on all the role objects found attached this template guild. """ @@ -118,7 +118,7 @@ class TemplateGuild(guilds.PartialGuild): ) """The channels for the guild. - !!! note + .. note:: `hikari.channels.GuildChannel.id` will be a unique placeholder on all the channel objects found attached this template guild. """ @@ -126,11 +126,11 @@ class TemplateGuild(guilds.PartialGuild): afk_channel_id: typing.Optional[snowflakes.Snowflake] = attr.field(eq=False, hash=False, repr=False) """The ID for the channel that AFK voice users get sent to. - If `builtins.None`, then no AFK channel is set up for this guild. + If `None`, then no AFK channel is set up for this guild. """ system_channel_id: typing.Optional[snowflakes.Snowflake] = attr.field(eq=False, hash=False, repr=False) - """The ID of the system channel or `builtins.None` if it is not enabled. + """The ID of the system channel or `None` if it is not enabled. Welcome messages and Nitro boost messages may be sent to this channel. """ diff --git a/hikari/traits.py b/hikari/traits.py index c94a543d99..c1b35cc401 100644 --- a/hikari/traits.py +++ b/hikari/traits.py @@ -74,24 +74,12 @@ class NetworkSettingsAware(fast_protocol.FastProtocolChecking, typing.Protocol): @property def http_settings(self) -> config.HTTPSettings: - """Return the HTTP settings in use by this component. - - Returns - ------- - hikari.config.HTTPSettings - The HTTP settings in use. - """ + """HTTP settings in use by this component.""" raise NotImplementedError @property def proxy_settings(self) -> config.ProxySettings: - """Return the proxy settings in use by this component. - - Returns - ------- - hikari.config.ProxySettings - The proxy settings in use. - """ + """Proxy settings in use by this component.""" raise NotImplementedError @@ -106,13 +94,7 @@ class EventManagerAware(fast_protocol.FastProtocolChecking, typing.Protocol): @property def event_manager(self) -> event_manager_.EventManager: - """Return the event manager for this object. - - Returns - ------- - hikari.api.event_manager.EventManager - The event manager component. - """ + """Event manager for this object.""" raise NotImplementedError @@ -127,13 +109,7 @@ class EntityFactoryAware(fast_protocol.FastProtocolChecking, typing.Protocol): @property def entity_factory(self) -> entity_factory_.EntityFactory: - """Return the entity factory implementation for this object. - - Returns - ------- - hikari.api.entity_factory.EntityFactory - The entity factory component. - """ + """Entity factory implementation for this object.""" raise NotImplementedError @@ -142,7 +118,7 @@ class ExecutorAware(fast_protocol.FastProtocolChecking, typing.Protocol): """Structural supertype for an executor-aware object. These components will contain an `executor` attribute that may return - a `concurrent.futures.Executor` or `builtins.None` if the + a `concurrent.futures.Executor` or `None` if the default `asyncio` thread pool for the event loop is used. """ @@ -150,16 +126,10 @@ class ExecutorAware(fast_protocol.FastProtocolChecking, typing.Protocol): @property def executor(self) -> typing.Optional[futures.Executor]: - """Return the executor to use for blocking operations. + """Executor to use for blocking operations. - This may return `builtins.None` if the default `asyncio` thread pool + This may return `None` if the default `asyncio` thread pool should be used instead. - - Returns - ------- - typing.Optional[concurrent.futures.Executor] - The executor to use, or `builtins.None` to use the `asyncio` default - instead. """ raise NotImplementedError @@ -175,13 +145,7 @@ class EventFactoryAware(fast_protocol.FastProtocolChecking, typing.Protocol): @property def event_factory(self) -> event_factory_.EventFactory: - """Return the event factory component. - - Returns - ------- - hikari.api.event_factory.EventFactory - The event factory component. - """ + """Event factory component.""" raise NotImplementedError @@ -193,13 +157,7 @@ class IntentsAware(fast_protocol.FastProtocolChecking, typing.Protocol): @property def intents(self) -> intents_.Intents: - """Return the intents registered for the application. - - Returns - ------- - hikari.intents.Intents - The intents registered on this application. - """ + """Intents registered for the application.""" raise NotImplementedError @@ -216,13 +174,7 @@ class RESTAware( @property def rest(self) -> rest_.RESTClient: - """Return the REST client to use for HTTP requests. - - Returns - ------- - hikari.api.rest.RESTClient - The REST client to use. - """ + """REST client to use for HTTP requests.""" raise NotImplementedError @@ -238,13 +190,7 @@ class VoiceAware(fast_protocol.FastProtocolChecking, typing.Protocol): @property def voice(self) -> voice_.VoiceComponent: - """Return the voice connection manager component for this application. - - Returns - ------- - hikari.api.voice.VoiceComponent - The voice component for the application. - """ + """Voice connection manager component for this application.""" raise NotImplementedError @@ -267,61 +213,38 @@ class ShardAware( @property def heartbeat_latencies(self) -> typing.Mapping[int, float]: - """Return a mapping of shard ID to heartbeat latency. + """Mapping of shard ID to heartbeat latency. Any shards that are not yet started will be `float('nan')`. - - Returns - ------- - typing.Mapping[builtins.int, builtins.float] - Each shard ID mapped to the corresponding heartbeat latency. - Each latency is measured in seconds. - """ + """ # noqa: D401 - Imperative mood raise NotImplementedError @property def heartbeat_latency(self) -> float: - """Return the average heartbeat latency of all started shards. + """Average heartbeat latency of all started shards. If no shards are started, this will return `float('nan')`. - - Returns - ------- - builtins.float - The average heartbeat latency of all started shards, or - `float('nan')` if no shards are started. This is measured - in seconds. - """ + """ # noqa: D401 - Imperative mood raise NotImplementedError @property def shards(self) -> typing.Mapping[int, gateway_shard.GatewayShard]: - """Return a mapping of shards in this application instance. + """Mapping of shards in this application instance. Each shard ID is mapped to the corresponding shard instance. If the application has not started, it is acceptable to assume the result of this call will be an empty mapping. - - Returns - ------- - typing.Mapping[int, hikari.api.shard.GatewayShard] - The shard mapping. - """ + """ # noqa: D401 - Imperative mood raise NotImplementedError @property def shard_count(self) -> int: - """Return the number of shards in the total application. + """Number of shards in the total application. This may not be the same as the size of `shards`. If the application is auto-sharded, this may be `0` until the shards are started. - - Returns - ------- - builtins.int - The number of shards in the total application. - """ + """ # noqa: D401 - Imperative mood raise NotImplementedError def get_me(self) -> typing.Optional[users_.OwnUser]: @@ -330,12 +253,12 @@ def get_me(self) -> typing.Optional[users_.OwnUser]: This should be available as soon as the bot has fired the `hikari.events.lifetime_events.StartingEvent`. - Until then, this may or may not be `builtins.None`. + Until then, this may or may not be `None`. Returns ------- typing.Optional[hikari.users.OwnUser] - The bot user, if known, otherwise `builtins.None`. + The bot user, if known, otherwise `None`. """ raise NotImplementedError @@ -354,13 +277,25 @@ async def update_presence( retained. This means you do not have to track the global presence in your code. + .. note:: + This will only send the update payloads to shards that are alive. + Any shards that are not alive will cache the new presence for + when they do start. + + .. note:: + If you want to set presences per shard, access the shard you wish + to update (e.g. by using `GatewayBot.shards`), and call + `hikari.api.shard.GatewayShard.update_presence` on that shard. + This method is simply a facade to make performing this in bulk + simpler. + Other Parameters ---------------- idle_since : hikari.undefined.UndefinedNoneOr[datetime.datetime] The datetime that the user started being idle. If undefined, this will not be changed. - afk : hikari.undefined.UndefinedOr[builtins.bool] - If `builtins.True`, the user is marked as AFK. If `builtins.False`, + afk : hikari.undefined.UndefinedOr[bool] + If `True`, the user is marked as AFK. If `False`, the user is marked as being active. If undefined, this will not be changed. activity : hikari.undefined.UndefinedNoneOr[hikari.presences.Activity] @@ -368,19 +303,6 @@ async def update_presence( changed. status : hikari.undefined.UndefinedOr[hikari.presences.Status] The web status to show. If undefined, this will not be changed. - - !!! note - This will only send the update payloads to shards that are alive. - Any shards that are not alive will cache the new presence for - when they do start. - - !!! note - If you want to set presences per shard, access the shard you wish - to update (e.g. by using `GatewayBot.shards`), and call - `hikari.api.shard.GatewayShard.update_presence` on that shard. - - This method is simply a facade to make performing this in bulk - simpler. """ raise NotImplementedError @@ -399,19 +321,19 @@ async def update_voice_state( guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] The guild or guild ID to update the voice state for. channel : typing.Optional[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildVoiceChannel]] - The channel or channel ID to update the voice state for. If `builtins.None` + The channel or channel ID to update the voice state for. If `None` then the bot will leave the voice channel that it is in for the given guild. - self_mute : builtins.bool - If specified and `builtins.True`, the bot will mute itself in that - voice channel. If `builtins.False`, then it will unmute itself. - self_deaf : builtins.bool - If specified and `builtins.True`, the bot will deafen itself in that - voice channel. If `builtins.False`, then it will undeafen itself. + self_mute : bool + If specified and `True`, the bot will mute itself in that + voice channel. If `False`, then it will unmute itself. + self_deaf : bool + If specified and `True`, the bot will deafen itself in that + voice channel. If `False`, then it will undeafen itself. Raises ------ - builtins.RuntimeError + RuntimeError If the guild passed isn't covered by any of the shards in this sharded client. """ @@ -428,6 +350,10 @@ async def request_guild_members( ) -> None: """Request for a guild chunk. + .. note:: + To request the full list of members, set `query` to `""` (empty + string) and `limit` to `0`. + Parameters ---------- guild: hikari.guilds.Guild @@ -435,21 +361,17 @@ async def request_guild_members( Other Parameters ---------------- - include_presences: hikari.undefined.UndefinedOr[builtins.bool] + include_presences: hikari.undefined.UndefinedOr[bool] If provided, whether to request presences. - query: builtins.str + query: str If not `""`, request the members which username starts with the string. - limit: builtins.int + limit: int Maximum number of members to send matching the query. users: hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishSequence[hikari.users.User]] If provided, the users to request for. - nonce: hikari.undefined.UndefinedOr[builtins.str] + nonce: hikari.undefined.UndefinedOr[str] If provided, the nonce to be sent with guild chunks. - !!! note - To request the full list of members, set `query` to `""` (empty - string) and `limit` to `0`. - Raises ------ ValueError @@ -458,7 +380,7 @@ async def request_guild_members( hikari.errors.MissingIntentError When trying to request presences without the `GUILD_MEMBERS` or when trying to request the full list of members without `GUILD_PRESENCES`. - builtins.RuntimeError + RuntimeError If the guild passed isn't covered by any of the shards in this sharded client. """ @@ -472,13 +394,7 @@ class InteractionServerAware(RESTAware, EntityFactoryAware, fast_protocol.FastPr @property def interaction_server(self) -> interaction_server_.InteractionServer: - """Interaction server this app is bound to. - - Returns - ------- - hikari.api.interaction_server.InteractionServer - The interaction server this app is bound to. - """ + """Interaction server this app is bound to.""" raise NotImplementedError @@ -493,13 +409,7 @@ class CacheAware(fast_protocol.FastProtocolChecking, typing.Protocol): @property def cache(self) -> cache_.Cache: - """Return the immutable cache implementation for this object. - - Returns - ------- - hikari.api.cache.Cache - The cache component for this object. - """ + """Immutable cache implementation for this object.""" raise NotImplementedError @@ -511,16 +421,11 @@ class Runnable(fast_protocol.FastProtocolChecking, typing.Protocol): @property def is_alive(self) -> bool: - """Check whether the application is running or not. + """Whether the application is running or not. This is useful as some functions might raise `hikari.errors.ComponentStateConflictError` if this is - `builtins.False`. - - Returns - ------- - builtins.bool - Whether the bot is running or not. + `False`. """ raise NotImplementedError @@ -569,12 +474,12 @@ async def join(self, until_close: bool = True) -> None: Other Parameters ---------------- - until_close : builtins.bool - Defaults to `builtins.True`. If set, the waiter will stop as soon as + until_close : bool + Defaults to `True`. If set, the waiter will stop as soon as a request for shut down is processed. This can allow you to break and begin closing your own resources. - If `builtins.False`, then this will wait until all shards' tasks + If `False`, then this will wait until all shards' tasks have died. """ raise NotImplementedError @@ -598,12 +503,12 @@ def run( ---------------- activity : typing.Optional[hikari.presences.Activity] The initial activity to display in the bot user presence, or - `builtins.None` (default) to not show any. - afk : builtins.bool + `None` (default) to not show any. + afk : bool The initial AFK state to display in the bot user presence, or - `builtins.False` (default) to not show any. - close_executor : builtins.bool - Defaults to `builtins.False`. If `builtins.True`, any custom + `False` (default) to not show any. + close_passed_executor : bool + Defaults to `False`. If `True`, any custom `concurrent.futures.Executor` passed to the constructor will be shut down when the application terminates. This does not affect the default executor associated with the event loop, and will not @@ -611,25 +516,25 @@ def run( constructor. idle_since : typing.Optional[datetime.datetime] The `datetime.datetime` the user should be marked as being idle - since, or `builtins.None` (default) to not show this. - ignore_session_start_limit : builtins.bool - Defaults to `builtins.False`. If `builtins.False`, then attempting + since, or `None` (default) to not show this. + ignore_session_start_limit : bool + Defaults to `False`. If `False`, then attempting to start more sessions than you are allowed in a 24 hour window will throw a `hikari.errors.GatewayError` rather than going ahead and hitting the IDENTIFY limit, which may result in your token - being reset. Setting to `builtins.True` disables this behavior. - large_threshold : builtins.int + being reset. Setting to `True` disables this behavior. + large_threshold : int Threshold for members in a guild before it is treated as being "large" and no longer sending member details in the `GUILD CREATE` event. Defaults to `250`. - shard_ids : typing.Optional[typing.AbstractSet[builtins.int]] - The shard IDs to create shards for. If not `builtins.None`, then + shard_ids : typing.Optional[typing.AbstractSet[int]] + The shard IDs to create shards for. If not `None`, then a non-`None` `shard_count` must ALSO be provided. Defaults to - `builtins.None`, which means the Discord-recommended count is used + `None`, which means the Discord-recommended count is used for your application instead. - shard_count : typing.Optional[builtins.int] + shard_count : typing.Optional[int] The number of shards to use in the entire distributed application. - Defaults to `builtins.None` which results in the count being + Defaults to `None` which results in the count being determined dynamically on startup. status : hikari.presences.Status The initial status to show for the user presence on startup. @@ -655,31 +560,31 @@ async def start( ---------------- activity : typing.Optional[hikari.presences.Activity] The initial activity to display in the bot user presence, or - `builtins.None` (default) to not show any. - afk : builtins.bool + `None` (default) to not show any. + afk : bool The initial AFK state to display in the bot user presence, or - `builtins.False` (default) to not show any. + `False` (default) to not show any. idle_since : typing.Optional[datetime.datetime] The `datetime.datetime` the user should be marked as being idle - since, or `builtins.None` (default) to not show this. - ignore_session_start_limit : builtins.bool - Defaults to `builtins.False`. If `builtins.False`, then attempting + since, or `None` (default) to not show this. + ignore_session_start_limit : bool + Defaults to `False`. If `False`, then attempting to start more sessions than you are allowed in a 24 hour window will throw a `hikari.errors.GatewayError` rather than going ahead and hitting the IDENTIFY limit, which may result in your token - being reset. Setting to `builtins.True` disables this behavior. - large_threshold : builtins.int + being reset. Setting to `True` disables this behavior. + large_threshold : int Threshold for members in a guild before it is treated as being "large" and no longer sending member details in the `GUILD CREATE` event. Defaults to `250`. - shard_ids : typing.Optional[typing.AbstractSet[builtins.int]] - The shard IDs to create shards for. If not `builtins.None`, then + shard_ids : typing.Optional[typing.AbstractSet[int]] + The shard IDs to create shards for. If not `None`, then a non-`None` `shard_count` must ALSO be provided. Defaults to - `builtins.None`, which means the Discord-recommended count is used + `None`, which means the Discord-recommended count is used for your application instead. - shard_count : typing.Optional[builtins.int] + shard_count : typing.Optional[int] The number of shards to use in the entire distributed application. - Defaults to `builtins.None` which results in the count being + Defaults to `None` which results in the count being determined dynamically on startup. status : hikari.presences.Status The initial status to show for the user presence on startup. diff --git a/hikari/undefined.py b/hikari/undefined.py index ae65e8b3a8..5195fcc7e2 100644 --- a/hikari/undefined.py +++ b/hikari/undefined.py @@ -91,17 +91,17 @@ def __new__(cls: UndefinedType) -> typing.NoReturn: # pragma: nocover If you see a type with this marker, it may be `UNDEFINED` or the value it wraps. For example, `UndefinedOr[float]` would mean the value could be a -`builtins.float`, or the literal `UNDEFINED` value. +`float`, or the literal `UNDEFINED` value. On the other hand, `typing.Optional[float]` would mean the value could be -a `builtins.float`, or the literal `builtins.None` value. +a `float`, or the literal `None` value. The reason for using this is in some places, there is a semantic difference -between specifying something as being `builtins.None`, i.e. "no value", and +between specifying something as being `None`, i.e. "no value", and having a default to specify that the value has just not been mentioned. The main example of this is in `edit` endpoints where the contents will only be changed if they are explicitly mentioned in the call. Editing a message content -and setting it to `builtins.None` would be expected to clear the content, +and setting it to `None` would be expected to clear the content, whereas setting it to `UNDEFINED` would be expected to leave the value as it is without changing it. @@ -112,13 +112,13 @@ def __new__(cls: UndefinedType) -> typing.NoReturn: # pragma: nocover - `UNDEFINED` means there is no value present, or that it has been left to the default value. -- `builtins.None` means the value is present and explicitly empty/null/void, +- `None` means the value is present and explicitly empty/null/void, where this has a deterministic documented behaviour and no differentiation - is made between a `builtins.None` value, and one that has been omitted. + is made between a `None` value, and one that has been omitted. """ UndefinedNoneOr = typing.Union[UndefinedOr[T], None] -"""Type hint for a value that may be `undefined.UNDEFINED`, or `builtins.None`. +"""Type hint for a value that may be `undefined.UNDEFINED`, or `None`. `UndefinedNoneOr[T]` is simply an alias for `UndefinedOr[typing.Optional[T]]`, which would expand to diff --git a/hikari/users.py b/hikari/users.py index 343f7f14d9..282b63adf1 100644 --- a/hikari/users.py +++ b/hikari/users.py @@ -136,7 +136,7 @@ def app(self) -> traits.RESTAware: @property @abc.abstractmethod def avatar_hash(self) -> undefined.UndefinedNoneOr[str]: - """Avatar hash for the user, if they have one, otherwise `builtins.None`.""" + """Avatar hash for the user, if they have one, otherwise `None`.""" @property @abc.abstractmethod @@ -146,7 +146,7 @@ def banner_hash(self) -> undefined.UndefinedNoneOr[str]: @property @abc.abstractmethod def accent_color(self) -> undefined.UndefinedNoneOr[colors.Color]: - """The custom banner color for the user, if set else `builtins.None`. + """Custom banner color for the user if set, else `None`. Will be `hikari.undefined.UNDEFINED` if not known. @@ -171,12 +171,12 @@ def username(self) -> undefined.UndefinedOr[str]: @property @abc.abstractmethod def is_bot(self) -> undefined.UndefinedOr[bool]: - """`builtins.True` if this user is a bot account, `builtins.False` otherwise.""" + """Whether this user is a bot account.""" @property @abc.abstractmethod def is_system(self) -> undefined.UndefinedOr[bool]: - """`builtins.True` if this user is a system account, `builtins.False` otherwise.""" + """`Whether this user is a system account.""" @property @abc.abstractmethod @@ -190,16 +190,10 @@ def mention(self) -> str: Example ------- - ```py >>> some_user.mention '<@123456789123456789>' ``` - - Returns - ------- - builtins.str - The mention string to use. """ async def fetch_dm_channel(self) -> channels.DMChannel: @@ -290,7 +284,7 @@ async def send( If provided, the message contents. If `hikari.undefined.UNDEFINED`, then nothing will be sent in the content. Any other value here will be cast to a - `builtins.str`. + `str`. If this is a `hikari.embeds.Embed` and no `embed` nor `embeds` kwarg is provided, then this will instead update the embed. This allows @@ -306,6 +300,32 @@ async def send( attachment : hikari.undefined.UndefinedOr[hikari.files.Resourceish], If provided, the message attachment. This can be a resource, or string of a path on your computer or a URL. + + Attachments can be passed as many different things, to aid in + convenience. + + - If a `pathlib.PurePath` or `str` to a valid URL, the + resource at the given URL will be streamed to Discord when + sending the message. Subclasses of + `hikari.files.WebResource` such as + `hikari.files.URL`, + `hikari.messages.Attachment`, + `hikari.emojis.Emoji`, + `EmbedResource`, etc will also be uploaded this way. + This will use bit-inception, so only a small percentage of the + resource will remain in memory at any one time, thus aiding in + scalability. + - If a `hikari.files.Bytes` is passed, or a `str` + that contains a valid data URI is passed, then this is uploaded + with a randomized file name if not provided. + - If a `hikari.files.File`, `pathlib.PurePath` or + `str` that is an absolute or relative path to a file + on your file system is passed, then this resource is uploaded + as an attachment using non-blocking code internally and streamed + using bit-inception where possible. This depends on the + type of `concurrent.futures.Executor` that is being used for + the application (default is a thread pool which supports this + behaviour). attachments : hikari.undefined.UndefinedOr[typing.Sequence[hikari.files.Resourceish]], If provided, the message attachments. These can be resources, or strings consisting of paths on your computer or URLs. @@ -318,10 +338,10 @@ async def send( If provided, the message embed. embeds : hikari.undefined.UndefinedOr[typing.Sequence[hikari.embeds.Embed]] If provided, the message embeds. - tts : hikari.undefined.UndefinedOr[builtins.bool] + tts : hikari.undefined.UndefinedOr[bool] If provided, whether the message will be read out by a screen reader using Discord's TTS (text-to-speech) system. - nonce : hikari.undefined.UndefinedOr[builtins.str] + nonce : hikari.undefined.UndefinedOr[str] An arbitrary identifier to associate with the message. This can be used to identify it later in received events. If provided, this must be less than 32 bytes. If not provided, then @@ -329,58 +349,31 @@ async def send( see this value. reply : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage]] If provided, the message to reply to. - mentions_everyone : hikari.undefined.UndefinedOr[builtins.bool] + mentions_everyone : hikari.undefined.UndefinedOr[bool] If provided, whether the message should parse @everyone/@here mentions. - mentions_reply : hikari.undefined.UndefinedOr[builtins.bool] + mentions_reply : hikari.undefined.UndefinedOr[bool] If provided, whether to mention the author of the message that is being replied to. This will not do anything if not being used with `reply`. - user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], builtins.bool]] - If provided, and `builtins.True`, all user mentions will be detected. - If provided, and `builtins.False`, all user mentions will be ignored + user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], bool]] + If provided, and `True`, all user mentions will be detected. + If provided, and `False`, all user mentions will be ignored if appearing in the message body. Alternatively this may be a collection of `hikari.snowflakes.Snowflake`, or `hikari.users.PartialUser` derivatives to enforce mentioning specific users. - role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], builtins.bool]] - If provided, and `builtins.True`, all role mentions will be detected. - If provided, and `builtins.False`, all role mentions will be ignored + role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], bool]] + If provided, and `True`, all role mentions will be detected. + If provided, and `False`, all role mentions will be ignored if appearing in the message body. Alternatively this may be a collection of `hikari.snowflakes.Snowflake`, or `hikari.guilds.PartialRole` derivatives to enforce mentioning specific roles. - !!! note - Attachments can be passed as many different things, to aid in - convenience. - - - If a `pathlib.PurePath` or `builtins.str` to a valid URL, the - resource at the given URL will be streamed to Discord when - sending the message. Subclasses of - `hikari.files.WebResource` such as - `hikari.files.URL`, - `hikari.messages.Attachment`, - `hikari.emojis.Emoji`, - `EmbedResource`, etc will also be uploaded this way. - This will use bit-inception, so only a small percentage of the - resource will remain in memory at any one time, thus aiding in - scalability. - - If a `hikari.files.Bytes` is passed, or a `builtins.str` - that contains a valid data URI is passed, then this is uploaded - with a randomized file name if not provided. - - If a `hikari.files.File`, `pathlib.PurePath` or - `builtins.str` that is an absolute or relative path to a file - on your file system is passed, then this resource is uploaded - as an attachment using non-blocking code internally and streamed - using bit-inception where possible. This depends on the - type of `concurrent.futures.Executor` that is being used for - the application (default is a thread pool which supports this - behaviour). - Returns ------- hikari.messages.Message @@ -388,10 +381,10 @@ async def send( Raises ------ - builtins.ValueError + ValueError If more than 100 unique objects/entities are passed for `role_mentions` or `user_mentions`. - builtins.TypeError + TypeError If both `attachment` and `attachments` are specified. hikari.errors.BadRequestError This may be raised in several discrete situations, such as messages @@ -463,7 +456,7 @@ def app(self) -> traits.RESTAware: @property @abc.abstractmethod def accent_color(self) -> typing.Optional[colors.Color]: - """The custom banner color for the user, if set else `builtins.None`. + """The custom banner color for the user, if set else `None`. The official client will decide the default color if not set. """ # noqa: D401 - Imperative mood @@ -476,13 +469,13 @@ def accent_colour(self) -> typing.Optional[colors.Color]: @property @abc.abstractmethod def avatar_hash(self) -> typing.Optional[str]: - """Avatar hash for the user, if they have one, otherwise `builtins.None`.""" + """Avatar hash for the user, if they have one, otherwise `None`.""" @property def avatar_url(self) -> typing.Optional[files.URL]: """Avatar URL for the user, if they have one set. - May be `builtins.None` if no custom avatar is set. In this case, you + May be `None` if no custom avatar is set. In this case, you should use `default_avatar_url` instead. """ return self.make_avatar_url() @@ -496,7 +489,7 @@ def banner_hash(self) -> typing.Optional[str]: def banner_url(self) -> typing.Optional[files.URL]: """Banner URL for the user, if they have one set. - May be `builtins.None` if no custom banner is set. + May be `None` if no custom banner is set. """ return self.make_banner_url() @@ -522,12 +515,12 @@ def flags(self) -> UserFlag: @property @abc.abstractmethod def is_bot(self) -> bool: - """`builtins.True` if this user is a bot account, `builtins.False` otherwise.""" + """Whether user is a bot account.""" @property @abc.abstractmethod def is_system(self) -> bool: - """`builtins.True` if this user is a system account, `builtins.False` otherwise.""" + """Whether this user is a system account.""" @property @abc.abstractmethod @@ -536,16 +529,10 @@ def mention(self) -> str: Example ------- - ```py >>> some_user.mention '<@123456789123456789>' ``` - - Returns - ------- - builtins.str - The mention string to use. """ @property @@ -556,21 +543,21 @@ def username(self) -> str: def make_avatar_url(self, *, ext: typing.Optional[str] = None, size: int = 4096) -> typing.Optional[files.URL]: """Generate the avatar URL for this user, if set. - If no custom avatar is set, this returns `builtins.None`. You can then + If no custom avatar is set, this returns `None`. You can then use the `default_avatar_url` attribute instead to fetch the displayed URL. Parameters ---------- - ext : typing.Optional[builtins.str] + ext : typing.Optional[str] The ext to use for this URL, defaults to `png` or `gif`. Supports `png`, `jpeg`, `jpg`, `webp` and `gif` (when animated). Will be ignored for default avatars which can only be `png`. - If `builtins.None`, then the correct default extension is + If `None`, then the correct default extension is determined based on whether the icon is animated or not. - size : builtins.int + size : int The size to set for the URL, defaults to `4096`. Can be any power of two between 16 and 4096. Will be ignored for default avatars. @@ -578,11 +565,11 @@ def make_avatar_url(self, *, ext: typing.Optional[str] = None, size: int = 4096) Returns ------- typing.Optional[hikari.files.URL] - The URL to the avatar, or `builtins.None` if not present. + The URL to the avatar, or `None` if not present. Raises ------ - builtins.ValueError + ValueError If `size` is not a power of two or not between 16 and 4096. """ if self.avatar_hash is None: @@ -605,29 +592,29 @@ def make_avatar_url(self, *, ext: typing.Optional[str] = None, size: int = 4096) def make_banner_url(self, *, ext: typing.Optional[str] = None, size: int = 4096) -> typing.Optional[files.URL]: """Generate the banner URL for this user, if set. - If no custom banner is set, this returns `builtins.None`. + If no custom banner is set, this returns `None`. Parameters ---------- - ext : typing.Optional[builtins.str] + ext : typing.Optional[str] The ext to use for this URL, defaults to `png` or `gif`. Supports `png`, `jpeg`, `jpg`, `webp` and `gif` (when animated). - If `builtins.None`, then the correct default extension is + If `None`, then the correct default extension is determined based on whether the banner is animated or not. - size : builtins.int + size : int The size to set for the URL, defaults to `4096`. Can be any power of two between 16 and 4096. Returns ------- typing.Optional[hikari.files.URL] - The URL to the banner, or `builtins.None` if not present. + The URL to the banner, or `None` if not present. Raises ------ - builtins.ValueError + ValueError If `size` is not a power of two or not between 16 and 4096. """ if self.banner_hash is None: @@ -703,11 +690,6 @@ def mention(self) -> str: >>> some_user.mention '<@123456789123456789>' ``` - - Returns - ------- - builtins.str - The mention string to use. """ return f"<@{self.id}>" @@ -732,10 +714,10 @@ class UserImpl(PartialUserImpl, User): """The user's username.""" avatar_hash: typing.Optional[str] - """The user's avatar hash, if they have one, otherwise `builtins.None`.""" + """The user's avatar hash, if they have one, otherwise `None`.""" banner_hash: typing.Optional[str] - """Banner hash of the user, if they have one, otherwise `builtins.None`""" + """Banner hash of the user, if they have one, otherwise `None`""" accent_color: typing.Optional[colors.Color] """The custom banner color for the user, if set. @@ -744,10 +726,10 @@ class UserImpl(PartialUserImpl, User): """ # noqa: D401 - Imperative mood is_bot: bool - """`builtins.True` if this user is a bot account, `builtins.False` otherwise.""" + """`True` if this user is a bot account, `False` otherwise.""" is_system: bool - """`builtins.True` if this user is a system account, `builtins.False` otherwise.""" + """`True` if this user is a system account, `False` otherwise.""" flags: UserFlag """The public flags for this user.""" @@ -766,21 +748,21 @@ class OwnUser(UserImpl): is_verified: typing.Optional[bool] = attr.field(eq=False, hash=False, repr=False) """Whether the email for this user's account has been verified. - Will be `builtins.None` if retrieved through the OAuth2 flow without the `email` + Will be `None` if retrieved through the OAuth2 flow without the `email` scope. """ email: typing.Optional[str] = attr.field(eq=False, hash=False, repr=False) """The user's set email. - Will be `builtins.None` if retrieved through OAuth2 flow without the `email` - scope. Will always be `builtins.None` for bot users. + Will be `None` if retrieved through OAuth2 flow without the `email` + scope. Will always be `None` for bot users. """ premium_type: typing.Union[PremiumType, int, None] = attr.field(eq=False, hash=False, repr=False) """The type of Nitro Subscription this user account had. - This will always be `builtins.None` for bots. + This will always be `None` for bots. """ async def fetch_self(self) -> OwnUser: diff --git a/hikari/voices.py b/hikari/voices.py index c2281ebb51..c9eb12764a 100644 --- a/hikari/voices.py +++ b/hikari/voices.py @@ -53,7 +53,7 @@ class VoiceState: channel_id: typing.Optional[snowflakes.Snowflake] = attr.field(eq=False, hash=False, repr=True) """The ID of the channel this user is connected to. - This will be `builtins.None` if they are leaving voice. + This will be `None` if they are leaving voice. """ guild_id: snowflakes.Snowflake = attr.field(eq=False, hash=False, repr=True) @@ -96,7 +96,7 @@ class VoiceState: requested_to_speak_at: typing.Optional[datetime.datetime] = attr.field(eq=False, hash=False, repr=True) """When the user requested to speak in a stage channel. - Will be `builtins.None` if they have not requested to speak. + Will be `None` if they have not requested to speak. """ @@ -108,7 +108,7 @@ class VoiceRegion: id: str = attr.field(hash=True, repr=True) """The string ID of this region. - !!! note + .. note:: Unlike most parts of this API, this ID will always be a string type. This is intentional. """ diff --git a/hikari/webhooks.py b/hikari/webhooks.py index f0eb9ff069..d333bfba9b 100644 --- a/hikari/webhooks.py +++ b/hikari/webhooks.py @@ -80,38 +80,21 @@ class ExecutableWebhook(abc.ABC): @property @abc.abstractmethod def app(self) -> traits.RESTAware: - """Client application that models may use for procedures. - - Returns - ------- - hikari.traits.RESTAware - The client application that models may use for procedures. - """ + """Client application that models may use for procedures.""" @property @abc.abstractmethod def webhook_id(self) -> snowflakes.Snowflake: - """ID used to execute this entity as a webhook. - - Returns - ------- - hikari.snowflakes.Snowflake - The ID used to execute this entity as a webhook. - """ + """ID used to execute this entity as a webhook.""" @property @abc.abstractmethod def token(self) -> typing.Optional[str]: """Webhook's token. - !!! info - If this is `builtins.None` then the methods provided by `ExecutableWebhook` - will always raise a `builtins.ValueError`. - - Returns - ------- - typing.Optional[builtins.str] - The token for the webhook if known, else `builtins.None`. + .. note:: + If this is `None` then the methods provided by `ExecutableWebhook` + will always raise a `ValueError`. """ async def execute( @@ -138,13 +121,17 @@ async def execute( ) -> messages_.Message: """Execute the webhook to create a message. + .. warning:: + At the time of writing, `username` and `avatar_url` are ignored for + interaction webhooks. + Parameters ---------- content : hikari.undefined.UndefinedOr[typing.Any] If provided, the message contents. If `hikari.undefined.UNDEFINED`, then nothing will be sent in the content. Any other value here will be cast to a - `builtins.str`. + `str`. If this is a `hikari.embeds.Embed` and no `embed` kwarg is provided, then this will instead update the embed. This allows for @@ -156,10 +143,10 @@ async def execute( Other Parameters ---------------- - username : hikari.undefined.UndefinedOr[builtins.str] + username : hikari.undefined.UndefinedOr[str] If provided, the username to override the webhook's username for this request. - avatar_url : typing.Union[hikari.undefined.UndefinedType, builtins.str, hikari.files.URL] + avatar_url : typing.Union[hikari.undefined.UndefinedType, hikari.files.URL, str] If provided, the url of an image to override the webhook's avatar with for this request. tts : hikari.undefined.UndefinedOr[bool] @@ -179,35 +166,31 @@ async def execute( If provided, the message embed. embeds : hikari.undefined.UndefinedOr[typing.Sequence[hikari.embeds.Embed]] If provided, the message embeds. - mentions_everyone : hikari.undefined.UndefinedOr[builtins.bool] + mentions_everyone : hikari.undefined.UndefinedOr[bool] If provided, whether the message should parse @everyone/@here mentions. - user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], builtins.bool]] - If provided, and `builtins.True`, all mentions will be parsed. - If provided, and `builtins.False`, no mentions will be parsed. + user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], bool]] + If provided, and `True`, all mentions will be parsed. + If provided, and `False`, no mentions will be parsed. Alternatively this may be a collection of `hikari.snowflakes.Snowflake`, or `hikari.users.PartialUser` derivatives to enforce mentioning specific users. - role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], builtins.bool]] - If provided, and `builtins.True`, all mentions will be parsed. - If provided, and `builtins.False`, no mentions will be parsed. + role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], bool]] + If provided, and `True`, all mentions will be parsed. + If provided, and `False`, no mentions will be parsed. Alternatively this may be a collection of `hikari.snowflakes.Snowflake`, or `hikari.guilds.PartialRole` derivatives to enforce mentioning specific roles. - flags : typing.Union[hikari.undefined.UndefinedType, builtins.int, hikari.messages.MessageFlag] + flags : typing.Union[hikari.undefined.UndefinedType, int, hikari.messages.MessageFlag] The flags to set for this webhook message. - !!! warning + .. warning:: As of writing this can only be set for interaction webhooks and the only settable flag is EPHEMERAL; this field is just ignored for non-interaction webhooks. - !!! warning - As of writing, `username` and `avatar_url` are ignored for - interaction webhooks. - Returns ------- hikari.messages.Message @@ -226,11 +209,11 @@ async def execute( due to it being outside of the range of a 64 bit integer. hikari.errors.UnauthorizedError If you pass a token that's invalid for the target webhook. - builtins.ValueError - If either `ExecutableWebhook.token` is `builtins.None` or more than 100 unique + ValueError + If either `ExecutableWebhook.token` is `None` or more than 100 unique objects/entities are passed for `role_mentions` or `user_mentions or if `token` is not available. - builtins.TypeError + TypeError If both `attachment` and `attachments` are specified. """ # noqa: E501 - Line too long if not self.token: @@ -271,7 +254,7 @@ async def fetch_message(self, message: snowflakes.SnowflakeishOr[messages_.Messa Raises ------ - builtins.ValueError + ValueError If `token` is not available. hikari.errors.UnauthorizedError If you are unauthorized to make the request (invalid/missing token). @@ -320,6 +303,21 @@ async def edit_message( ) -> messages_.Message: """Edit a message sent by a webhook. + .. note:: + Mentioning everyone, roles, or users in message edits currently + will not send a push notification showing a new mention to people + on Discord. It will still highlight in their chat as if they + were mentioned, however. + + .. warning:: + If you specify a text `content`, `mentions_everyone`, + `mentions_reply`, `user_mentions`, and `role_mentions` will default + to `False` as the message will be re-parsed for mentions. This will + also occur if only one of the four are specified + + This is a limitation of Discord's design. If in doubt, specify all + four of them each time. + Parameters ---------- message : hikari.snowflakes.SnowflakeishOr[hikari.messages.PartialMessage] @@ -329,7 +327,7 @@ async def edit_message( If provided, the message contents. If `hikari.undefined.UNDEFINED`, then nothing will be sent in the content. Any other value here will be cast to a - `builtins.str`. + `str`. If this is a `hikari.embeds.Embed` and no `embed` nor no `embeds` kwarg is provided, then this will instead @@ -345,86 +343,64 @@ async def edit_message( attachment : hikari.undefined.UndefinedOr[hikari.files.Resourceish] If provided, the attachment to set on the message. If `hikari.undefined.UNDEFINED`, the previous attachment, if - present, is not changed. If this is `builtins.None`, then the + present, is not changed. If this is `None`, then the attachment is removed, if present. Otherwise, the new attachment that was provided will be attached. attachments : hikari.undefined.UndefinedOr[typing.Sequence[hikari.files.Resourceish]] If provided, the attachments to set on the message. If `hikari.undefined.UNDEFINED`, the previous attachments, if - present, are not changed. If this is `builtins.None`, then the + present, are not changed. If this is `None`, then the attachments is removed, if present. Otherwise, the new attachments that were provided will be attached. component : hikari.undefined.UndefinedNoneOr[hikari.api.special_endpoints.ComponentBuilder] If provided, builder object of the component to set for this message. This component will replace any previously set components and passing - `builtins.None` will remove all components. + `None` will remove all components. components : hikari.undefined.UndefinedNoneOr[typing.Sequence[hikari.api.special_endpoints.ComponentBuilder]] If provided, a sequence of the component builder objects set for this message. These components will replace any previously set - components and passing `builtins.None` or an empty sequence will + components and passing `None` or an empty sequence will remove all components. embed : hikari.undefined.UndefinedNoneOr[hikari.embeds.Embed] If provided, the embed to set on the message. If `hikari.undefined.UNDEFINED`, the previous embed(s) are not changed. - If this is `builtins.None` then any present embeds are removed. + If this is `None` then any present embeds are removed. Otherwise, the new embed that was provided will be used as the replacement. embeds : hikari.undefined.UndefinedNoneOr[typing.Sequence[hikari.embeds.Embed]] If provided, the embeds to set on the message. If `hikari.undefined.UNDEFINED`, the previous embed(s) are not changed. - If this is `builtins.None` then any present embeds are removed. + If this is `None` then any present embeds are removed. Otherwise, the new embeds that were provided will be used as the replacement. replace_attachments: bool Whether to replace the attachments with the provided ones. Defaults - to `builtins.False`. + to `False`. Note this will also overwrite the embed attachments. - mentions_everyone : hikari.undefined.UndefinedOr[builtins.bool] + mentions_everyone : hikari.undefined.UndefinedOr[bool] If provided, sanitation for `@everyone` mentions. If `hikari.undefined.UNDEFINED`, then the previous setting is - not changed. If `builtins.True`, then `@everyone`/`@here` mentions + not changed. If `True`, then `@everyone`/`@here` mentions in the message content will show up as mentioning everyone that can view the chat. - user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], builtins.bool]] - If provided, and `builtins.True`, all user mentions will be detected. - If provided, and `builtins.False`, all user mentions will be ignored + user_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], bool]] + If provided, and `True`, all user mentions will be detected. + If provided, and `False`, all user mentions will be ignored if appearing in the message body. Alternatively this may be a collection of `hikari.snowflakes.Snowflake`, or `hikari.users.PartialUser` derivatives to enforce mentioning specific users. - role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], builtins.bool]] - If provided, and `builtins.True`, all role mentions will be detected. - If provided, and `builtins.False`, all role mentions will be ignored + role_mentions : hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole], bool]] + If provided, and `True`, all role mentions will be detected. + If provided, and `False`, all role mentions will be ignored if appearing in the message body. Alternatively this may be a collection of `hikari.snowflakes.Snowflake`, or `hikari.guilds.PartialRole` derivatives to enforce mentioning specific roles. - !!! note - Mentioning everyone, roles, or users in message edits currently - will not send a push notification showing a new mention to people - on Discord. It will still highlight in their chat as if they - were mentioned, however. - - !!! warning - If you specify a non-embed `content`, `mentions_everyone`, - `mentions_reply`, `user_mentions`, and `role_mentions` will default - to `builtins.False` as the message will be re-parsed for mentions. - - This is a limitation of Discord's design. If in doubt, specify all - three of them each time. - - !!! warning - If you specify one of `mentions_everyone`, `mentions_reply`, - `user_mentions`, or `role_mentions`, then all others will default to - `builtins.False`, even if they were enabled previously. - - This is a limitation of Discord's design. If in doubt, specify all - three of them each time. - Returns ------- hikari.messages.Message @@ -432,10 +408,10 @@ async def edit_message( Raises ------ - builtins.ValueError + ValueError If more than 100 unique objects/entities are passed for `role_mentions` or `user_mentions` or `token` is not available. - builtins.TypeError + TypeError If both `attachment` and `attachments` are specified or if both `embed` and `embeds` are specified. hikari.errors.BadRequestError @@ -493,7 +469,7 @@ async def delete_message(self, message: snowflakes.SnowflakeishOr[messages_.Mess Raises ------ - builtins.ValueError + ValueError If `token` is not available. hikari.errors.UnauthorizedError If you are unauthorized to make the request (invalid/missing token). @@ -551,23 +527,17 @@ def __str__(self) -> str: def mention(self) -> str: """Return a raw mention string for the given webhook's user. - !!! note + .. note:: This exists purely for consistency. Webhooks do not receive events from the gateway, and without some bot backend to support it, will not be able to detect mentions of their webhook. Example ------- - ```py >>> some_webhook.mention '<@123456789123456789>' ``` - - Returns - ------- - builtins.str - The mention string to use. """ return f"<@{self.id}>" @@ -575,18 +545,14 @@ def mention(self) -> str: def avatar_url(self) -> typing.Optional[files_.URL]: """URL for this webhook's avatar, if set. - May be `builtins.None` if no avatar is set. In this case, you should use + May be `None` if no avatar is set. In this case, you should use `default_avatar_url` instead. """ return self.make_avatar_url() @property def default_avatar_url(self) -> files_.URL: - """Avatar URL for the user, if they have one set. - - May be `builtins.None` if no custom avatar is set. In this case, you - should use `default_avatar_url` instead. - """ + """Default avatar URL for the user.""" # noqa: D401 - Imperative mood return routes.CDN_DEFAULT_USER_AVATAR.compile_to_file( urls.CDN_URL, discriminator=0, @@ -601,10 +567,10 @@ def make_avatar_url(self, ext: str = "png", size: int = 4096) -> typing.Optional Parameters ---------- - ext : builtins.str + ext : str The extension to use for this URL, defaults to `png`. Supports `png`, `jpeg`, `jpg` and `webp`. - size : builtins.int + size : int The size to set for the URL, defaults to `4096`. Can be any power of two between 16 and 4096. Will be ignored for default avatars. @@ -612,12 +578,12 @@ def make_avatar_url(self, ext: str = "png", size: int = 4096) -> typing.Optional Returns ------- typing.Optional[hikari.files.URL] - The URL of the resource. `builtins.None` if no avatar is set (in + The URL of the resource. `None` if no avatar is set (in this case, use the `default_avatar` instead). Raises ------ - builtins.ValueError + ValueError If `size` is not a power of two between 16 and 4096 (inclusive). """ if self.avatar_hash is None: @@ -650,15 +616,15 @@ class IncomingWebhook(PartialWebhook, ExecutableWebhook): author: typing.Optional[users_.User] = attr.field(eq=False, hash=False, repr=True) """The user that created the webhook - !!! info - This will be `builtins.None` when fetched with the webhook's token + .. note:: + This will be `None` when fetched with the webhook's token rather than bot authorization or when received within audit logs. """ token: typing.Optional[str] = attr.field(eq=False, hash=False, repr=False) """The token for the webhook. - !!! info + .. note:: This is only available for incoming webhooks that are created in the channel settings. """ @@ -673,9 +639,9 @@ async def delete(self, *, use_token: undefined.UndefinedOr[bool] = undefined.UND Other Parameters ---------------- - use_token : hikari.undefined.UndefinedOr[builtins.bool] - If set to `builtins.True` then the webhook's token will be used for - this request; if set to `builtins.False` then bot authorization will + use_token : hikari.undefined.UndefinedOr[bool] + If set to `True` then the webhook's token will be used for + this request; if set to `False` then bot authorization will be used; if not specified then the webhook's token will be used for the request if it's set else bot authorization. @@ -686,9 +652,9 @@ async def delete(self, *, use_token: undefined.UndefinedOr[bool] = undefined.UND hikari.errors.ForbiddenError If you either lack the `MANAGE_WEBHOOKS` permission or are not a member of the guild this webhook belongs to. - builtins.ValueError - If `use_token` is passed as `builtins.True` when `IncomingWebhook.token` is - `builtins.None`. + ValueError + If `use_token` is passed as `True` when `IncomingWebhook.token` is + `None`. """ token: undefined.UndefinedOr[str] = undefined.UNDEFINED if use_token: @@ -714,21 +680,21 @@ async def edit( Other Parameters ---------------- - name : hikari.undefined.UndefinedOr[builtins.str] + name : hikari.undefined.UndefinedOr[str] If provided, the new name string. avatar : hikari.undefined.UndefinedOr[hikari.files.Resourceish] - If provided, the new avatar image. If `builtins.None`, then + If provided, the new avatar image. If `None`, then it is removed. If not specified, nothing is changed. channel : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.WebhookChannelT]] If provided, the object or ID of the new channel the given webhook should be moved to. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the audit log reason explaining why the operation was performed. This field will be used when using the webhook's token rather than bot authorization. - use_token : hikari.undefined.UndefinedOr[builtins.bool] - If set to `builtins.True` then the webhook's token will be used for - this request; if set to `builtins.False` then bot authorization will + use_token : hikari.undefined.UndefinedOr[bool] + If set to `True` then the webhook's token will be used for + this request; if set to `False` then bot authorization will be used; if not specified then the webhook's token will be used for the request if it's set else bot authorization. @@ -739,8 +705,8 @@ async def edit( Raises ------ - builtins.ValueError - If `use_token` is passed as `builtins.True` when `IncomingWebhook.token` is `builtins.None`. + ValueError + If `use_token` is passed as `True` when `IncomingWebhook.token` is `None`. hikari.errors.BadRequestError If any invalid snowflake IDs are passed; a snowflake may be invalid due to it being outside of the range of a 64 bit integer. @@ -764,7 +730,7 @@ async def edit( nature, and will trigger this exception if they occur. hikari.errors.InternalServerError If an internal error occurs on Discord while handling the request. - """ # noqa: E501 - Line too long + """ token: undefined.UndefinedOr[str] = undefined.UNDEFINED if use_token: if self.token is None: @@ -824,9 +790,9 @@ async def fetch_self(self, *, use_token: undefined.UndefinedOr[bool] = undefined Other Parameters ---------------- - use_token : hikari.undefined.UndefinedOr[builtins.bool] - If set to `builtins.True` then the webhook's token will be used for - this request; if set to `builtins.False` then bot authorization will + use_token : hikari.undefined.UndefinedOr[bool] + If set to `True` then the webhook's token will be used for + this request; if set to `False` then bot authorization will be used; if not specified then the webhook's token will be used for the request if it's set else bot authorization. @@ -837,9 +803,9 @@ async def fetch_self(self, *, use_token: undefined.UndefinedOr[bool] = undefined Raises ------ - builtins.ValueError - If `use_token` is passed as `builtins.True` when `Webhook.token` - is `builtins.None`. + ValueError + If `use_token` is passed as `True` when `Webhook.token` + is `None`. hikari.errors.ForbiddenError If you're not in the guild that owns this webhook or lack the `MANAGE_WEBHOOKS` permission. @@ -888,8 +854,8 @@ class ChannelFollowerWebhook(PartialWebhook): author: typing.Optional[users_.User] = attr.field(eq=False, hash=False, repr=True) """The user that created the webhook - !!! info - This will be `builtins.None` when received within an audit log. + .. note:: + This will be `None` when received within an audit log. """ source_channel: channels_.PartialChannel = attr.field(eq=False, hash=False, repr=True) @@ -923,15 +889,15 @@ async def edit( Other Parameters ---------------- - name : hikari.undefined.UndefinedOr[builtins.str] + name : hikari.undefined.UndefinedOr[str] If provided, the new name string. avatar : hikari.undefined.UndefinedOr[hikari.files.Resourceish] - If provided, the new avatar image. If `builtins.None`, then + If provided, the new avatar image. If `None`, then it is removed. If not specified, nothing is changed. channel : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.WebhookChannelT]] If provided, the object or ID of the new channel the given webhook should be moved to. - reason : hikari.undefined.UndefinedOr[builtins.str] + reason : hikari.undefined.UndefinedOr[str] If provided, the audit log reason explaining why the operation was performed. This field will be used when using the webhook's token rather than bot authorization. @@ -966,7 +932,7 @@ async def edit( nature, and will trigger this exception if they occur. hikari.errors.InternalServerError If an internal error occurs on Discord while handling the request. - """ # noqa: E501 - Line too long + """ webhook = await self.app.rest.edit_webhook( self.id, name=name, diff --git a/pages/index.html b/pages/index.html index e4ddf7124d..3f31e3243b 100644 --- a/pages/index.html +++ b/pages/index.html @@ -1,35 +1,257 @@ - - + + Hikari - - + + - - Hikari - - + + + - + + + -
+
-              oooo         o8o  oooo                            o8o       © 2020 Nekokatt, licensed under MIT   
-              `888         `"'  `888                            `"'       Installed at:  /home/hikari-py/code/hikari
-               888 .oo.   oooo   888  oooo   .oooo.   oooo d8b oooo       Support:       https://discord.gg/Jx4cNGG
-               888P"Y88b  `888   888 .8P'   `P  )88b  `888""8P `888       Documentation: https://hikari-py.dev/hikari
-               888   888   888   888888.     .oP"888   888      888   
-               888   888   888   888 `88b.  d8(  888   888      888       CPython 3.8.5  
-              o888o o888o o888o o888o o888o `Y888""8o d888b    o888o      compiled with GCC 10.1.0 (default May 17 2020 18:15:42)  
-
-                                                           v2.0.0     
+              oooo         o8o  oooo                            o8o       å…‰ 2.0.0.dev101  [HEAD] 
+              `888         `"'  `888                            `"'       © 2021 davfsa - MIT license /home/nekokatt/code/hikari
+               888 .oo.   oooo   888  oooo   .oooo.   oooo d8b oooo       interpreter:   CPython 3.8.10
+               888P"Y88b  `888   888 .8P'   `P  )88b  `888""8P `888       running on:    x86_64 Linux 5.8.0-59-generic
+               888   888   888   888888.     .oP"888   888      888       installed at:  /home/nekokatt/code/hikari
+               888   888   888   888 `88b.  d8(  888   888      888       documentation: https://www.hikari-py.dev/hikari
+              o888o o888o o888o o888o o888o `Y888""8o d888b    o888o      support:       https://discord.gg/Jx4cNGG
 
             I 2020-07-15 16:45:11,476               hikari: single-sharded configuration -- you have started 29/1000 sessions prior to connecting (resets at 16/07/20 11:38:05 BST)
             I 2020-07-15 16:45:11,779     hikari.gateway.0: received HELLO, heartbeat interval is 41.25s
@@ -37,7 +259,7 @@
 
             ^C
             Terminated 13897
-            [nekokatt@pc ~/hikari ] $  cat bot.py
+            [nekokatt@pc ~/hikari ] $ cat bot.py
             
             #!/usr/bin/env python
             # -*- coding: utf-8 -*-
@@ -54,7 +276,8 @@
                 def decorator(fn):
                     @functools.wraps(fn)
                     async def wrapper(event):
-                        if event.is_human and event.message.content.startswith(name):
+                        content = event.message.content
+                        if event.is_human and content is not None and content.startswith(name):
                             await fn(event)
 
                     return wrapper
@@ -75,6 +298,7 @@
                     return (
                         event.message.author == e.message.author
                         and event.message.channel_id == e.message.channel_id
+                        and e.message.content
                         and e.message.content.isdigit()
                     )
 
@@ -99,63 +323,53 @@
             bot.run()
             
         
-
- -
-
-
- -
-
- -
-

pip install
hikari

-

A new, powerful, static-typed Python API for writing Discord bots.

-

- This API is still in a alpha state, and is a work in progress! Features may change - or undergo improvements before the design is finalized. Until then, why not join our Discord? Feel free - to drop in to ask questions, get updates on progress, and be able to provide valuable contributions and - feedback. -

-

- Tutorials, tips, and additional resources will come soon! -

-
-

- Slide into our server -

-
- -
-
- © 2020, Nekokatt, MIT
© 2021, davfsa, MIT -
-
-
- - - - - - - - +
+
+
+
+ +
+
+
+

pip install
hikari

+

A new, powerful, static-typed Python API for writing Discord bots.

+

+ This API is still in an alpha state, and is a work in progress! Features may change + or undergo improvements before the design is finalized. Until then, why not join our Discord? Feel free + to drop in to ask questions, get updates on progress, and be able to provide valuable contributions and + feedback. +

+

+ Tutorials, tips, and additional resources will come soon! +

+
+

+ Slide into our server +

+
+
+
+ © 2020, Nekokatt, MIT
© 2021, davfsa, MIT +
+
+
+ + + + + + diff --git a/pages/logo.png b/pages/logo.png index 73392f24dda8e55e80e34ba1956ef56132fcea2c..807782f703b162f7aa4f39fdf4a570f671ee7a93 100644 GIT binary patch literal 153709 zcmV(>K-j;DP) zaB^>EX>4U6ba`-PAZ2)IW&i+q+O3>xlH|COUjK6yvjhSJKwvp|(M&tg@^ucPy6hL+9uDA8+{U8e#nF&#(IDH?jWtpg+IhgU>&Y zSN8k$&o{}>fP_jl&^cY1^Q{3_8YmAeL@@MsVV~g`~o|1pU7vHBniy4g1jQe^XejNY4mvFAz&h=)f zTzLgv8Ur8ZRR3@PT>qzE{=fXWzHd52;jVv=755d5EN;V@^KV|ogoN+UyyY{%fBpG0 z{r5k`524Bo<}=Kd2kiIweZqwG)VcHcq&S#?kaJwzWK7Rf;`PQFy*BT8!+CAYu|$6iHZc+TDbLH?w5Ky! zDP`j4O_dnx`N=8gT=JXim0RxRDXHXA%3B1v{yuMd^IP8ct#5mKeQK(?mipFOTkS2j z2Mo-uw71pPTJQ1dT&VL|o!57MGU9kf8hMoQjyl@tlkk~o=2_-D>uj^HxSj<}tg_x! zS6h9DYl9R!?Yzr=cinCGk68PjFMatd-}}|Cef{rQ`!lQm>CgYetc5?b7SBxiy7%u{ zf6Uy))cxmq`!8l~$#e6}*e?qm)B7R&z8J>4ud?>@ ze0$#C=2-*7y8Fy;kKzrp66!aenZLcyE1p>xle+g>b-v%-I@ilPuj^hwPJX|*>T~bZ z+s#mx(t54~#+fC3%uP(?i+jh;K&8AUAu%^>ydH9~^hvH&9(l{3b%tFA)6iCmi@;eW z<}qsFZBOwBu-D5S_udJFdwx6Q_VQ8pSGwRoQ+c?7!yfOBr7{qur}lu;3&LG})~CRf z3sgw?9&^Q>65#ucapxR%Pah(pRqk@koCf$1c20y}UE>AvCzFL%c^aRxSM6_HEl(~n z1d@koXE+IoCyaE}eb*vCGK2NmjcG)&G3MVBC!uifPRNTOOe?DVYHIuE9_8H+vm}6lhEi^RXiVPtcZ?+u*t9d-ABgq|b!WW7rSSl@SzA zFz3F0?fO0UC^vvqnRWuK_hw)bhP^OC-{Uh9hQv$j&nEEEHl_4261Vg2g3?Iy`qqkL zjYX<6O`{K0WQM#i)9cQwWnG?@~eKYk5*Tz^D7`tM=hF`)pG$%qjRQ(RwfAW6CNG}pLzz-S7JH zk#``Mi2Sed*bH@m1n%%&Le~8>GA=QmfIZXo7T~6-)%MeKRDg%IfeG)rqWh3w`)20A zy|*!?3>4YVvt~m8@aD6(g9-?Q0CT+QsWDWd5u~j9n=%pjbCWCp97w}`q#Vay5M+t9 z4HGtQ*efKy=E77?h^o)Y#Kp3{*mkMpHjLgrkZm)CA6eG=T5IffK~@C#oZp(g%?&hc z(#>I>??}1*h5FHOcme?ys(eLOhrAlF1P~K$S9+z(QOM882)+B|NjLn^4~~OTg8+${ zEGPFF&!m`bAK3emby5Z3dQZeCxm#u}*q9m22!iv*rzDkmc)pqw7b8*#s|@ur=7Y2Y z6#bAeK~e5#Uy|@(utS0*=XeE_Lacm|&@(LyIzX~cgSP>!!NsdU z8)1`i6aU5N<}M%zXw?)QH&B|;kP{`MUAT!+VvalB$cvmlkdjehkUo4%A;6!;(~y*_ zwoQohYXBsI!4Fe#C5SX2#5R$6gY2L-lMcP@7uw>9Ct_(mo!OZ8s%5X4pJx)CfW@ae z*w3hYTzllZ8!KQq!4VtRgUc&VUk8qiy8_-t1YQJS>#{dY(+ODUtP#u4Q2Cqv4Cj1y z(L8Yl{@jm=_lP`gs3=+jjbVSLl}FC^9_+Nm zBl-{!gG^;q08$bL?J7!#`_k1THs;E;G8gE-2zv1sgUM~zK?pvQ3eX1+w+BcjGs~M6 z1b_qDC~D~8+9VOr-^I4{n9ha4R;#?g2y!*&_lAV`1P!=5sQqCWP~bb0(il(75m9x$ zk8A;Kp-7NY_i|V^S_m*&5!^iaCdW>^1aoR|e8G$+@z&`4i)^eUFPR=9j$^!+c3R$_I}>~NL?j@YxW>1oq!*?` zurVc2@dH>A6>~&$pJ@jSWynlWUJsN#Rw9TY|M@c3uoX8I2E4rz`c6t^>iH-Dw94+f z+zk37@X@W$tW5NU#ChSgTXFI z4pc=B0CTyAEm1)(#MX-6RY{p)Aq)_Ig!>rD^D{>;;DwwjL?oIY9x8Ac*w17QMv5@9 z7RIwP|NVm2LSJtbiI(DW?xVQA92Zs#^+AN?|j!)h(&bY6sqD|0dmk-Sqj(;6a;Y>00@wJ@6axE4v+(xFK!)gyAVPB zyU8vn8~`WApo6u5-euN66XPa+pt*?W0v2wR@|~`OW;oCaPxK8xat%hJ!aWQ827x~H zX09YxIADO`K{ewjkspkJc)M!7NNq_S7xN0q@JMy`0J2J60b=AO$bj+-&KlYaJ&9T* zQsjEx;7C08Mam+OCO#4HA0-m331L^2%rGPA_;Ph)1eFA zxC~ZB0a2F=m}BTZ32TjzMKbca zP>Zl#fc!Bb9}El)4JI1u^JAEI>}lM=amC_22vd?A^OXGvA(|&>tzSziAITCvF+$Q2 zurCBOuxJgXDkahG*!(A>b(nitpQoF7Dc$m9N`a%76hT?=TfFwPM6fa2dT79VBF5gA z*^FwzV41|bPiz4v7he<+(%)_jOY zU35h(EvO70gQz|Q_9761Wr2)}6!-xj#rztC?84JN+#yy_1|Sa6*Wm+pP8kD$tZF@6 z*F8*t3F8aD_t%_NOGvKs91*@I z+p@vwJm@6D#Rau&!w4tv;E=#CZz2(7#mND>D2aO97cziE1d$4YXQ$%CF=>zol+E}J zq;J7vCIDaU)ENrs9Q*!+gJ3#3{J~^M$bp^*nhPrRZkuJD7QP34RJ|Zi^Q!q{=YJ9r z#T0CyFZE+>Rlry$P?cORJEQxNKVMU@$BiTzS2pFKxHXgf!QG>ux3C|>;DYd7B4Sst z9>kQBF&_qU-K0xa)D^-S0ed0ccqp{Z{tzOJ954e25)cM_Q1)4N8_14ex$nuokFN@{ z_JM7B=-7c;5c9D}c7F7E$R2{h3Yae(l8&_P(V+k#~tupdf3@g7DEWav6k zW!Fl`&dlAzb0b{P7Snmd*L{8do4)F=P(Yo1}yX)Au0nqch<{ zr42q;f&+tteTZYBl3kTR4mB0^*Dq4JPPI5hfwv~`T%7>Ap^er9G@Tw4*t#LA4J+ud z@*=1vGg(uIxtzCkWp(n68e-^`MFe_4Nw){gCH4r1y~{w=Vy?i(9V0S(e-K!|Ed`P~ z9r^uSGbc-BKoSP7deOg4qYCmM?jc2hMtkx&}vsI$E!Z29_g(Pt0hD7gI#_SaC;^Se07W%VZBdNcZ zT;M~%2+J`m6cZrW!=uCEbpzr661Wdizko8y_KEs(d_3X?ARAz`&qF?QZ9)zmhpXl8 zCOoHkGdN&ec)L&309F(p|M=H2- zR)8m5uLI%@Yp=<>f{L|)Jz7uB(5vFHGB6(M@D$fE8sGTK`&m9dE^uX&jG>W;h~93TV|r_$%aB`QvvqY(cSln@UXB43GzEf=7k@nRiGJlx+oF z2V(a)N6Ob{`v7u)-0ok7?*Rx19)NrvmrI^*01Eg|;J%WTQ`|P8P8+41-O8kXj1*Qybmg5q)83%E_tn@YLE|Ua9{D1#VQjQAqmo^De;gr z^CsHJ0!%Rc9_1k}-CVAAbn&fUBP8JeTF8kDHbfqCDUB-%4GCiTtqM+jLO>wi)zW~X z8I?iA5y)_z(#ztf!Q{FKN&+XzXrD}i@v!DWb3sf3Py&J#Yp%{u1HW|PhPWrtENsp3 z&7_BC)=-a3W>?VmrTu%-y1O53Elqg}oWdo}!Gr#1cD|?Uc^RDE_znllV9Omq&3x+( z4p%PSc>b`yp&QiTgFIlxW5w(_H7!sc$YdUgKOi3hNa2bCM-xn5@*4VOMoKqQ&|PB? zCB#K}iCb>CS)l*a(@bo#N|bzV^jAY4*$V-*GHvlJiCHWpMTSnIqHY+jkU*1kW1?ji za)5Z!Og3_DHL#2nK?V*K=z<`5(Q}@806O}Yi4twvMg=?iESErrk<5$=>KO%gC&dd& zSKsfF5yDkioi6<0LXW9ZpLJrkbFU9(0M|DPfeT?JJ2X)1+{trW`M>u?oOSUVE6;es zZhM*xhMrCtv}DSY#doa%?&&Kz8TMxItFS^N4`fYtGHwlBCbJeA6P!n53?5^xZ|02v zwN754S4bXG*9C9s2UP5D^3MW6|#<-+*-?s&AcHC!SSEDjlGe*&ysf{HLLJo=U7Y?x zxBxV$a{&&ZGa&ycm$pj|fW7Z3q~OwLlx%FLA5vfuYpRh6Dj`zB(@_W!J&*Dt(4EcY{)xuZB2)v9$)=ZXEqn<%VTRKNege*^ixXwisG! zz$YMg$-=$G6IBB&E4qI;!VK5PUGTpsPyj)Vm?yRY#hk(H-w10_%I4WgHd2#NTQ68j ze%q!I(DBbu8Y5Og)N1%!Vd}vp2L9(oXrat*g!Kmw-oyv2&@er?o53yPEnS#k+iPN(LF z)y5XT43^1{=T0?UR&8AJ)dySk1Tl#>z!yG1e>)8lff%QP->;WdDmX^tDD--*=bW21QTUoeN0-;EKBE|;GdU}4)Hbf``>k9JqSvG$&7yIzR z*bLawno7$yeYxxqU@=^F8af10IVG!78*v+qe>7VTqh7}WAon4^tXV{AO~g#MLx8Dy zaV5xZR}u?USroG!NO2MNeGpddtfu1o8ALvc2`vM|ZLa~2{I;Vg5kILS+ha!2xzdzE zwvfDBFI?T4Hc{O3KQUn};{R|GD6rdwUJ4_~itL;@oq&nh~RZMO=~gJQ8!2A~gx0Ydq9as*z#&N_SC-$(8cP|m~r!&|=3 zuC`CiATw-@>qtgIh5Duybc24pT=~nqIpCR_t zaLnRa=MCBt00t|HlIEFOu?WPkfaUWOB8V8lo5!=La1`8v!*Ye#84HFA%1OY_6DxRe zRhi{R8mr?+EWk(AR06y%|D^s|^F4Y-*0kUFw&R^Ue|RDnzR=D+UxbdUxt1gCSxhzv zpWcvoG`{`~*|Hw&Dd;l1?Qg>BH%R`46LHBU`HLF>384q_vojmuKdFm#%U?xXrH#9= z9{^$p#q1OVp}wqM!B~#FYZ?eJVD zu?u9;v{9H5N%68dYj19`lOm&{j**$=I}IA!f{vYQ9n)D04~pbUK*P5_oIO6z8fAeb z_k`ER1J}FYhj}L9>N`n}Dbmd4#VG*99dU3_GW zAezRlqh5h2%zv+A0kfYSlZXNyqr6zrf;>(nEJ;PS-SK5o!4#4{YcyMD1b#0kp?Q)0 zo&!7uAxQLeyVS@{LU1-#E|O@E*6T7~T~;9+zjVN>Sq>X6Zt6X55e2OXSux)Di)rT~ zfU!R`aMOh+4rd*w0J<2q8$qC8 z&OO;XL{yO%@p;WB1qXKPQOk*=x}A6(%k&|izrOS#r&W1H%PnM~g=f?otc;|X5azOH z^d=w}_(RsbhAAKE5`Y&(CSh6V-WGo`r!JKdN^z0;XO)nAXZ*Hmo#@1{D?M+-08{S6=y}$Zk7%#jEUYs`RHLmg6eFfkYXnQDaTz6SzAt(s<{Fw8H zaG>wT<_X)7Vv1n zs_AsOhiBxm?iUVBT>kVmVj=I-jJfUL;-${eL`-z#8Q0j$!W>)*j6jp1peeU!xUq?k zjd?>uB?{SNq;UdWnV1TmT3k@eijvW#1~f*w7UDf4hJ7AzHJoN&B*f!dLI`WSg6`1T zq6rU&G^d+ckk-=$N~>QMHLqj7HowvY-3zoSw;h*gqhcrJZOxJtNQ|@V!P6F=w~%a% zlB7UJ0KoYg4<=u~s*xFeKMyK2f`aC&E6j7F6iywlk>;TRloh=NK&m2wW(4~AE&v$s zV`bek;&j==QZi9zLl$;&pe~4!l&s>xxTU>%C89}&K%0LSu;Rr{0wjV*Nu^!Uc$x<62pcaKJ&CF*yfB(KLn!iAsy-mbTUD=4H1qT z@|!5U!zBX`OA`;**1gthr|yqP9x%0`i7|vWdI2bi!-=~a`y-(aA1lpLE7yBS!kr4@ zPfob95$#4g6oG?2jABC(v-a||zZVN^ zRsmrr0G{)<(c7iHi&=O3g}vT(+r5bN;N9K7`sA(3CM=BpB#Lu0i$L4?6>OAPjA6yo zV$onISc4IHZTwg(z(bg?5v*ZYqrd{f+3(J99GP>g-K`3s^Z_ljL~%J|%y2M8b_F3C zO2w@K`7Anor~S{GvKyALUd2VUydiER4~=(Wjf zzI)D`!DlkF9+aSc1Mm~z`;j{hiJ0fkewZLhsg#6tF#Yy7U~^pDzq9$aN# zr&O?>$27o9Jq@Pb!+v}?7LY_r2QScMoo&}0^cH5mz!3XhQG@Q=2o8ZwTFK>>QLs7) z;U_SywwqzkL2E+2Bnx4Y;VbS3Apx)a*kp$%FhYUnzAy-@qPpDTVVek2U_kW?j9&J+ zEK6xnzcWHek}2@dcezUiGS9hb=@|-})5*3z9y>!S)(1+KkkB)kTG5*%PJW4g{s?cVWU%AwXus zq`9tR3&SYx8N&+AR~Y+Zd`t~nlD~VL>Jgkvypr`*trYBk*7(L= zZ#!I;3(|LV9kQIs;Ax(AVaOop`)h8Y0%WbZ*++sCM2uN5>ie``A?oU|+Vg6+PS@zX z|GMzU2ifOh&~WNOO0Jx8}$X> zx7Qk$YdeiN3k*`N#_%aaKURvl&wjeWxeS>B<2&W9OoK(F0#=4tOe-Y3)glH!gR^JN zuCw%B*#+#dL$s>|in(2WNw%V*%Rv?wz!1|f$m|CGf-XviYfb9<*UmbN?rX@V zgV=~Jb)(aG%O>D#t}QSx_8Kp1c?p4a`ZoX-45|y5K>d8d?8LB=9$FI2h?{&-0q&d* z%ib{F;t~S9oPmsfD%yP!dVu3?LvTDbmgo9-y`336xErT@bZdRK*-kPZP^>uV7s!LNz&pTxL@~>xfBew z-AWB27+K?1kYug&tV9yTjLb0LN)B!eaizZoqy|J~|2vf76T7vd?6di@+XQnVCGCS~ z*fX0jFoP~ux-~thfe2-g*-Zh_J?v-Yo{4_kLWqee8@*Si`;JJ|v+%=G zgDqxJ3pG#cbGI)T^nnkq<+9sjgQCgE@YYyc;likn!ie%&`Q2{DcU950Wri1)d%HWV z;^C#mWiB@kV839Q1F@KHAmVes9IWm_*P2p=H$4r3Pm67bc^Hi+ZhM`SvzW1VjTj7eNW|e-!fdA-1N4(D=_v3Ojku^0{&+u8()gCR)G$yJ#II!tH1o}8a!LD zR@nLvzwf7kan*9<)y~cvN9S>)7^wwh&T6k@aqqRO@@|5K1)^>~EHDrK>vlE_P?)F> z+*H2IT_(HjXt(}$wb?o!cD@r9U%MN#Ft+vyX=Cet-2F!E0if^Msd`pi!j;`4NQQ%b z$jHNBC^#Ek7NUTgW!RZwRCctj6Nv!QBQBzK7Hxw4Z`{)*-SZ$%#810@W&<~0Pm>#& z8-fBwm)jx4nH9rlTRj*8mqQV_E|eX9xiq)tRqm0XaktrLi!LJECHK4sH$bFxQiFTD zBkE-!CgU?E`dckPq(I6j*m|L;Yy==hp-+O4@Y^8PaQADsjn=LnJjA%s@XZR08SugW zipO@cVWSDI_lDvj-rmBirXNtqAYdyd7S(Q6O%cM&w|TbEqM^XSPx6u8w^_gIcwRLsS!a2?xUu^>yD400gH>!)aZeg51t0S^`w8t9 z+d0JdY;SWdw7MFl#N%on3*mOyR4!XWTsYR_nF!le4Mx$O`-`DXHq4AT&W2877brmd zKYaF$4~1(HV7F~U)g>C8(1!v;*L>>UZgjf&oDj;^de5HqQP%xBJ8p+|V>Xd7+o7%J zvL?|ipX^X_zI6~^DuRXm-nUgldx-DGoZWUG6XCq4{8s!Utl^f6^jt52I+3iBx|?F2 zLT2x0j+PK+4EL~g6NRGTMy)P4^z$KE@BYL;CzHbtXG+-((XNU)v!1~(rYyuoX*4@gMFxJ}5Zom6{lbITL? zbC%5ZSw=BL>M4!g`k4D1WRoI`BYOa=QtjHI)k$Y^)a5K*1x=md=l+QK%-7khS z{0dFMk8%td%-%=^vu;eQ?9 zaKb+X*BAMAHQh3hv@EAqOf#}b#%%W2VH#~60!F&tyuIM6ZAhP;HozR}JA1%`L+{Q$ z*+uL63JSKN;<;Tw1~t|`yH|4u>O%q+RzSt^=WK34=Aofs%eb?r8=%EqsYC&A`E(N* z{e?89y9r^*l`gwbm_s*g8Oq=%kjm`JBF)=rDE;@aNGm?LyQlFQ)y^H#?1SfAb#-T^ zc*f<>f13;rAul7v#c;GuOwi8R$YX(WxTg+4L93v-PwxTRLkW11pY~eQCvhp zVlB(W9~#~zay!}{ZuPN3-j(g=KEaLl*v-|h0Fa(LEx$X}3%P4Qer_s!E$CSe*=_@| zMkZzrGvEP<=5psn^^BJ*N77CkeP+vD?)H~Bulu*n(pHM(Zge+QpS@oQVxTuYk0m|3 z)ZOoY757@(tZ7Lem{{&iAWowD>)+E4o(Q}c&$eJ&?VEc5o}WR^?w3nY#)E@&yQ>|P z)vj~TzR_wXps;+*w+;VrGWIL(jOynUU3D`nmDYX_!cYU_PxkuJBKpR1$F1dJn*B#^*oIkw=JjnTYJH(vu3jFT02H=UAj4za`8%L|*e!x0 zw_(xWU(;Z59h1w$nM{db&+NMM$-M{&a6Tn%URk(fcg>Z1xypUO{qsB@+owat9j$&2 zhQU@mH%^!FdGHg=8d+z_5Fmr;^6hR&!-1z`PVcZBdJpPx0XbYS>1&~}GK69M+9^ms z)hVfH_pM{krQB77X3Kb*6N^LH>#E|iR5mYn$b@Nq8G#R0R=ag<*s#=eR@S*?TSISq zbBPozG4S*b3_fIOw~R}a@lWruBI0&AX4=|twh+v6niwraWH)6qQsiJ8Zp6j}#OC5! z`Dr=c;eiw3oIPO~MdZn@^4ew%Yux8JBb$)1aEqrT$lW&coW_`7!({;V7}t0ebT@)@ zLy)JU?h2#bQ0`)k9SR635T@=*v%&6EGM-Zg`0P9f>WIz>K}NeMkSSw6<1gKOI%gK9 zGg#w*Ql`h#oi^=T2=?#@0AI`mx!Pml;~M3c>uyztjrpC3LO!HXXXJ@iVKAX=%v9>0Etd>sx0RE0jy=2y z!(c$*Yqw{X&xO2oWDFqbf*PS}qeD+_I(;#&ZZQNmZTYm#Jtsy&SIy+RhuHQ2Z2#Vg z_qbqmuV}PS`~90`q33doM!bKW{s>vOj@`CKb#fTjACz&=>Ec~Bt?Y`Aw57%t0AxH8 zEW1j+t_N{Dm}rlru~PzEctL3Jqn)Epbh^tON)cUw5kOtrMv)W_XsLLfylfX(dh-q!g#3E(^ob`5p6g?ob`UXuS#HOgx$NheWpJZAN!{Mh zTX09Nh*x`#JVhhBZGOAA8;qa26QlWT(t!Y`r=l=@P|A&7J%vu#S^e9uWEBlDV&|_% zcw{setVhVW;kPBH5c;>@;wfi`=WwUB`;*@djF(<=&4 z=%$8D8dV(pIU*?9DWafImR33H?0iN<*qeo7wJjrTYs9O%j@*k4;`Wkx7!!^TVlK8= z?GI5?PD2*sg{0-37}P7-llywWNOC9D(G52M4?2f)z|FSwPL`XS3eM=UK*fE^>6{QG z^#b1)7a0dB2*6?Ob=Dq)IWRn^JlswRU_1}@?H&XPT#9Ok4RNnAXO{y;>YnRo@!6WL zTX&6P4;HHLeUM_|YCMO{x*}xPJVyd2fJb!UNFU3sI~&($WpKK(P7HhIio2jZ3~9Pr zh4_rGhn(Wo#B4>IhqIVNk2l)8&Yjr36j}S2D||i##=J+jZf;K%DpFTM$DbYNb9n|` zI1&en@#r9eq9%9oBzGQIYjtDV@&j^lsN8pW?9lDTim0oeGona5NF*{HvpsGxci#&a z0jRI)i43SG)a;!tSMmAK4iTu_#5ca(5+_=ES}q}?e8d5UASTzn!O&^#`#P|bQQV#f zN<@G?>pcOK54jy9MwcLsCnyaJmKB6<*M0;6*hUVZQg!^Y}m(6cMsyMw= ziJy}Op3Fp`7E;&Rug&4jTHslF0qp^Qh->FRYSG0~Zv0^ou|&<)y8-?NMH$a?n&5M+ zXXze)MKm2~dT=F|yQFh%)AUzlij06>a`@d7bxyh3g1mk7GH6`NQ@pTiT`nguIqPaDg$>R9c z?V85;Tn!%t_9;bN;WXsC6*V!2aQ*2z(j+VGsx|quDUk^w{gC?&(4Bc$j%>CAsiuj@ z3mXzM}sNs1IG4mSPoy87mzdr^W-|8OE`|PiV zm)#It=%uvWGoBaE)LWH)u{c{n z321z$^hENFb|$0PKhUS=O}gado^WQ(n1im}TINEviz9ZXTFY4OwYKv~`Eq+qWjo79 zXEEVZ72D~aJk{))mUW9oiqog=j`X;NsySVs%DXzn?{68X+E#VW`a?MObK>CRnPE`X z^-M%xB@K1+^>#Iofqe;C7mLnTJFZ;aAn%zPkL#Bmfe71?YsEG$F*gyr)$K;pw zFmJ#_J}A-762@cM1$E8%$v7Kp-7jYBJ%_(qtt^_Gx%VSC zd@37~jBwwB?|;q+RF7Tk0NfNWf9>7@t48L5TXA_jAP-w@U?TXJhlWih@|(^{UFABe zDY{8AdEjm|p4#bnu&}dTuhpaA$Zp$o?8xy*kJ{Zq!RU4i{q-AegGC7jtJr{FPS&)} z%BzRI%GEBaTQaqbuAuYKx9Sd;oZ8D9^O$a1xUfrz(BRQgmM@aKLP*<=;3t&{9aj*Q zt@%9;?wpa85hVHtT(wJX89#VAZb!BPRzP5JRoAl@mGjzgLSdf6f3_z`6qj|Qeba8h zMmF}ZGZ$d+#hg!w!P9n79tQ6@?aJdupeT>y_H>!-X&Z*p2s|2LV?8*#6~+)}xL?%B zV&rUFJwA-|UY>a~Ptk|Warf$Z8qp?Tx8z_u3qXZogR<*ZTky zB4#i54!oO0TJKzEp(1pblK4C~PghTcBVdXLhDCRCpRM=BvwF~g&#z$@NWX^L2>b*r zTJU#V@2lVXEujog&E4+NLHl7EdgSih&T)<^v~tx~2)AR@Pk#jXf^oLf8m|N~pgS?Y z7Kh28Klgduq)Xp`VCJO@e01%6dbnhQj+$M2_Am#?X1ZOn;6y|=Pr_s7D)crZ zc689FeC{>?#hN>3e?vackiQ;Vf}r$lEN=C;9>0Inak3Z znOn-mp4ET8qvWaB-1+Oy560-XLFuoVxr>KCZXSNv1?M?mR*S@OV6hgr!EI~)Yub`m z+ywFgo~Lan7{PnQzeDp+VEG$BgB*3gK=5>zXum7A;Oq12#@e| zy3KywYYdM~ukgQ;e*&%cDPA7eNnUswuswrd(6SWH_p+TauJjG&e>(Z{u~>8~^8K z;&Xd*dZw_uVck1~)=i!g%C9^W;If(B^AVYV*?-f|`R&4&M>+t2{~Qj;H3)webJDdeL&uN1@Gq}hs?WzE z4P7x*YS@;9W495+4dLGjS3dt_!ngmC3GVf@L(}!w*M`JDF@lkPejm%l;HB$-0g&JQ zp8gA<@c;k;glR)VP)S2WAaHVTW@&6?004NLeUUv#!$2IxUsH>vR2*7F#34gR@u|7c^-|Qd}Gb*Mfr|i&X~~XI&j!1wrrw#Ldk~(M3x9Us`Ap+gyR{0F6Ro6h0tmb~&c_JQy9+eyj`Mx& zIL#Bl{|sE|9e=d}%zTnw?`W|jpnn^3_cmMDZ7%NrcfvV?`QN)IUsTi zgw|TUwfAxQ0A#4E)D3WO2#gjfd)?#R!S3GvJ=5;*2Mp|TehvR7bpQYW32;bRa{vGf z6951U69E94oEQKA00(qQO+^Rg2onk%0{w3)h5!H{07*naRCwC#y?2mp>2=-r+g~{0 z-p((~ym>Q&$q9)729XJbNR~(tj3Ox(GYLy9S}serBrB9GE0I!JvgDF!OSa`$v`Vt8 zTq;|V%Q7vQWRU_B2!bSl0cHRUC%@F)H=J|6wEf3-x&gb&E(b{jsQ#v2&Ad0SZ{O}d zXP>p#UTbZ*I2Y&QT%0eGBNyL-i}Qazf8^dDzSV_5V(Q%Gpabk?I*Fh(e% zAVNrqSQ3iCXhE$Z8#u@fOVcpMfO^Mi$!v1jvfDqu5k9@j@wrt_&yFcQGoaN}8a$KKa)go32 zAz+N5fn2~@5T*VD&NoQo zNC;5K2%?3c6EP1g7rWeb{W>>q-sbqu35(?}ISX2pECvu_j9R z;zw@f^x;2t=WpG)_^DqMtc&wSa{k7<|K!`O^3Eq8dE_0hyzpC3-+B4f*FW~)1HAJc z@8Cm!{~w}bU|g^18%LTZd~4Z`BiHY_&Py-8i1Q9D1&U&=!&!@#1Y#&U7ai+}A>h47 zwNOeyRVk%lodXm*M0&AgZwLZnJ*r@>CB}$%mZBLIC{h^m$ll>TZ}{@};D^8qpLw3o zeEK<@aio+fMKK6Tg^~jjEDN`bdP@{YC=)`o<~Oz_{?a;^Ul_G~=np^tAOG^jRdG?U zF3uOo`PUx$fp;(W7GM6Bo9i#TChk2Jb4dkM1ZNBwA*IBFk3YpHKlU5ArU6w#nJ8q8 zwOH$U;K2uZ?z!g}$C1lNm+`*k*3Da>O4Bx!DI!@ZR&dsmQmO?DC|ZaqlXJrR2J0>T zCa^5R;DwEVaUDquMnQ+bqV;4Frm1jZaQ~A}@zN(gK`Ah9)-3u3Mgdi{WO4yC z6N|<9jvOac!8M*~n(*F(F`$aImXPADJWN0L>Amh}Ken^{nZN$gpZNKUtLCC$U7UYG z=TBVu-iPiM_tp3Hi}&4SoA)k4zH5Qwv`=IUsYJpQ0U_ti&dv@uZrot8=*X%Z?H@9Z zTY^SZlrc>7-IAOO&RLd=9iDjnalr5!zxGk4VZ<6kjFDUlZPQRn!8Z*d1ae9^?+GDL z=Kt@kBgY9bFsYIOYg(iTE-79CDVWBf4n`6phLjUFD>#dmxwxs)H7zBjisYdFxv?O^ zW;?LIzt8^uKBuQEhH(S~r4(|`SZB#7U2+`i9)l^b9&7$5H+A}7Un2Z3fAppQ?754p z>Y`v>oPUPrM<4mY_grb)-+kG*uf5VN-nlbn7CF(!jM)Sl15@XS)V3_8gw6=#?6hr* zmclqqh!KV|qDE=kj*=8B25Sw@SQ^(5hFXmJz6W3mf%UM(JBKj_(OMKxWwBUbt!3P7 z$)#Y$ke!mGkOfe|nU-EWCT8*w$R)9V^?=v5#|$PTp|IO5FciG^+&MYnzH9d|P7?si zKsCQ%oY0vsQxq?TEJ9I;DY0BG+27yiWVK?u-GB%fA(xDGj#3hNHPJ1E%|@?OV9l0KX-ApT@0{uc9ir-h~U$aX`M zGQF|*QeX%;QKo)LC{RkKl*%i4?@1|1Fmn0bMX*_##qD{`mSTy^=wva#$iBJ85(6W8NuLegHumm480o0 z?LZ{6dv%}NX^R{#m|i{Rz%CibEzWtS5V-I9byg>*48uS<?te&?{(q9T|OXoiVmEyY3r)-}f#y!-|i8>|^YTA68(#z~GmP|3w8izB6)_QVEV=Dej%Ib%f5r<5Xs3Mi0sCTpEiDsXi95+OyNfBt#Kal{zQ zI8HRSr46+hid1bz(>JKeNK*^@kQHLW8Q4slCk{G!V)tnG-};ID-~UhV2>;#9&HBIn zYoGne=P$0>3xxGA-T8N}{m?hP<>>l%9*+4tOdf3^5lLiUpfU6cWtd1(@Wvy6vz8Lm z%s17dQA$A+f)=KjX32oVHx16YbMjeL5fNlA04H|DEjz+y;Og!XtJ`-t*xjSI4HV`0 zWOa_Ny1vIOS`y0X=_yhQoo|UTVrvhERWNgrBxjNotgVR2sKwD>5LASb%%;h_;wN?< zyO(!=$NPEqZ~qkKqc0L-tc1I@6+NZMIF7V!SKq%CtaXGiVx)i(C>41bXW8A~XR}$? z!d%LE;r0NUx%=`}Y~%UlGtaQIw+l1k^xk6-3W{}A>QPJ~dq-(J6P9(%c~JSgcSQcq zpa1NC@%JvS;tPcJJNo>oOW$?*3zadZB9(d4W{tty1{JvP{`EDTRmbe;uzqd&0?Pg)xSBHaKt=*Lb>RH7hRFzAY zFS8v6UU}sej*c#I=hiJAx%Yl{mwWuyi!ZQEnJWi}tcES2D5oisY<+}p!obpH_QC`K z+EZZ%&!LKS?c%#mTl-yq`JwOmweIOBfAZ`9&41-5AA0B27cZ{ve=)~id<*{hpFjH4 z554OT-u>P`@yK%jKe^=n`@53wGA3htLokWxLA?bdII(nCTD6!^Xw=YRkfK=Cxy`CA zQL;i37|E!saM_5|DcM}OockOSGsfU-CCWE>%TsTEJNLflJ-qVEzd~ah@)*!rq?t;g zT`VY~9B)?Dic>PXiygu^GHnOCwmom#a;mKvLTyR4wp|G&X(47M=R%=Wp2`X0Ey)z_ zd+-5vP9ry;`7GOEQ>6$cAqI@Mq>^ynQ8W``Ktw1dp;}){=C3!;#C%MNVHi+gv0U)T zBad*pS+UI%uim+d^eukTkhQShZa^WWg2%A%9bOH6>q$8QwhCyCuhY+%h?xo#Zhhz@KmEeR)qYX1zG%;%fAE7J__llA_TvwC`+w!BReJkD z%1uYX+DvhUsfDdc1TSC>4MH!L9pmvyv9aJ0TCvzdEn1DEh?0zwjZ(Z}lvLX?aWj9# zc?;My4XR2?6$u$zHBQ;Wa=F8v^}PDY=digJwURT=IlQko^i&edyD|!=Sz#5FHNGcm4DP%GA_cI?Ab3&Zp1g2pmy>f?>7hh(YCNP}ad793Xb0nrv zhY2}jy~BuL#9*yG|GEIoT#!YC5CcUO=N-2;r<|lM1BLq@x}Py8UVZIV_I7r-cGopL z2B!*BCZBGJF*A5U%;$=g&%zWUi$%{mj;u}O@i#ryOcLLI@|(|n@ZGnsyy@wk$A_O^ zef*h=tN((-`l301`rbeAU9Z1<@Wbuxo4@O^-Cd?zw>i1T6H6kD6G$yWjcJiw*<+We z+>Kbgs|xZd40!8sjU%Omh?27s!$c_=@BKMR9aS){nWt;j?fE|=t-E79&8MU{{f0NzxgCS-~L-XR!b z1c@0FGP{cA#l7cI-n`+Vm1D;zcpe1RCN=(PZu%;Di7<211zwulHh7sLvZfvkXF z+>G+(7I=#FJ&s|*T8jv5!-kzb%QR(BK`^AOc;^tc)%U1XCebzxcVghC7OWatYv~bO zN?28viv`DJ;&xy7itqhq?DYfw(SQ2iaQ`Px8M~I-;(qG3w14a0{;j|Hw=eF73xxFr zd;Zu1KlFV+`0&^N#C@JWbN~aH z7?h0hU>bvXLn2{@L@SOIBVr7tD5_L%9guUO))K*pka7eR?>t46Fa@l*bF#bXdfaSy z-dpUDtg>325<-|!ODzy0f_0Aly*;LBs+uoRT!Ox7Sd9Zk!25seUXR|wUEPvl!P(ve8`}=${U95mP?l| zp+$)?Qeq-C2H9IuLSQ&u)B6siiaq1|5NHfBCvs6Ridtx_tGrc+h&YV1n8vf7Mno*@ zIMTEYs<3n&RwH4x#%Q7OmVUXzjZ%L0#hCuapL*%<{N%;ma)GdZ`&z$;Klm-b_kpka z>-Tp3A6_QCUsk8=G%aBov9@8F5-I}DgI22xB8atk@7dkkt5SucXenqen7PftLcK>F zwSh4ek7+>!8t>`)4pU$nH{>zVxQ3iF!#LrbpUG%BM_;HpC#9RF1q>lXQp`A8-_JR! z;%r!0P*GB@Z6$C{tysnUZYiW5uYs`|cY!8V?4j6D72@8R}m zUSxIW1n~})iaJ;8^*N!AQjs9k_HE|NoU4$us^IHXaqr-O^?HK}WPxVc(=2+j7B=fO zRt)Xz0;n&q1kGesQl5!?V=CH0=Hjn7>p@_M0pmUQ+}~sBvi_MnAO85o-E&c}{)L@CvHYI*{{E*v@bBK!F8)j#!$U{T zv1bgIc6PaQ^Coc^>H7sa7rgK2ny!i!5im7hD#UuM?&$G5p$g~tJLQau>u|UL}PX*7Wg(ZJZDK28q5m(c;G)--z#&LkT6)Gj8S|@QS zMT%CA=&VS6zWwLybxSEY=V+T2&4u0FU5XXf+YQcpj2KGFwE(+16&+&$Biw%JH8dn- zR=A5X48wqP4tHk3k=nYRwSvBJb78JuFQy`-oxMF`O6(mTpj5x18o_rBVT!cglS?Mg z1BP*$NI8+UYSs$1?Ta~5RBM}Clx)m-F)dQH7Kk}yE|M>Uryz9@tg~7@;W6RE7CwyXElk64qLVVVFsIH~0RP=dsq}eM95B8DRyc zX{;^Ha)EVb=EBZY;exe*IGBs-nY!H=gL7^s$~i}0)>=}|99}wNDw)-GL$~a)1#(I> zuA-=r61MRiuTL?t;A+_><|<>zDW6X{&wdGYH~~5n8guca${Mhi<=!saFmbY4F~)%L zj;Pfz1Od^TCZy><_s}KeA852nK9=RPBVWz zMJrOJdM3{bN}^6H$1rjA`Zcz3LcC)vi4Y^b^H9#nb9fQO_`p7$X?5XsI`*Qc3un&oo&cYyCOGLZEFMltPRFnRQU!J9c-M zSlhAP4(A2$tUWZQYN6a5J9GA{sInafLaupP!!S@{qVp|I44`Cz&9G&$zsu>JV}x3e zD{iXXkrh)vm)4@9b1Q0`C6h76a&T~fIL8=5O-!BRWo{oI6H`TN2Ek*_B_6x`dx$j* z(?|it*{bSKiBd8dB~xZptb`Qnz(dXy{RZnjAqI>Q8s{tjSd^XJC5zn!Lzvixu{s7s z!3e(Vh#J{UBNMRbn%%~^uU=aJ-Y>cI=F^|O^Q#}bxa%$o*8l7Ceb>MHi6402tA6Zp z*ZigX)!n}wCYDAZN1{nA+Lok=Kw>1J&QPp@+4f?M$73NTY=&mPukAzIGERY*>f|ZU zZI@A@#DoHk_4uyENL7prTC=LGqwavH#ix`)NQr5xdF)N&2_fRWpQnRn=6|YwNmXW4 zWNS*Nb%@l&(8Z$1dxtf`6h^Ey%oG^T8x+TSwWjO3b2Bx-FbwC`m|FZMa?bdsz6a+$ znR1T6TH}agZ9!Y_Nu?srqr)RoE)3&H)3%l4(prSiBp22iik`ieMYSN-vE12V97l#} zJm*IoEU$LV^FieSB~d|k;kZQcsDvFz>d*A{Qw zf-w|v9VSea z^@c<5??15JcYN8kcV2(s@Qt7Rm0KUad2ttB6s-S8=ifW}{vZ9igKIzWsPn&TA%KI_ zSM_#NV0Sc3OV22UQOm4fG&tX4#X|ldOjE>&!-(Z{HDawh&u}-!FplFnFZTIp>#Sw$+LnT{83&BBs4AQF8Yk5v zG{k^di`(hzv^3>vs5YibFJhvoVofbnF-Ft~##*vy<(d>$n>EuoVVl~@N_7bF*jwMo z?%n~PdFGSs^-Ii*;GFYJIdXe-LYUi6HB!BfQWGe|8In~t?RUSNYc8_d{HJ(-Zt}ANn~I;Xpk@4ur;3EnP`0 z*5lhA&Hekodimz~_tzi(#f!V~qG0`Bo^QG9+n@e@4}aZXedFPkKiZ6&LsKGZGsV{S z$QHpz=+tpDZc(819evYbjibau;~Kne5M!&-TPlI;ysPP+)YVu;m2S>?5)qu3*={0O zF{F~wTs7ZP*VK?TVO`CCs;x!gWU~S5X^cbW@jEK#$)na*q*Z3_MrPtX#xNJAGtp&j zm5ljZdza?K(YEbLxh8vle!iBSg?lGwQ8S2AgLnOVvV6`8v3@wnK~^@rK;h_ zoY=0nXf7-li`v$XW95aM!+B52nUEsJ8pIkxijaO$9G`OI z#tmBUDKUXDGig4toyJ;7r53AF=IQJFvr|*iY$}B?xAV^XTI77g-p(#LCWh@$A1sYS zoWYt(-aA{1{Z6}JwcW6~b`J~3=R&B^Y8!C!?!&3 z=%qJ&<`?gLbnm*&)+;!%#*dg{%de!ACv32_Ej9}-3(mOn(R&mnQJ5&?5GW;MY;C3buIK8tyBUT`kW10?w9M+Ov4~g< ziWsS!l4u>xmq@WfROOMUUeAyc!||#XJ~nxm@ZQfREDdUnbtUL5psxFW6wQ;05KaXr;KW@t%em&*QZG18De5_aKHnPKgMR9$eRs~G3Q%wWJXzt%;Wqs?baDN zUaDf=8W1Iyimr??Ow)u&6|{yBP=!l}M;ssDspjZ;=|swjnCb#mv8FDHm|Hljf>R+I zm}ZSrDTQIZu1h3xVH^gI?z)2OTGABJSW%%f7K)Nep3zkzri5{>qO8WVyR)Ea8n(kg z+qPI!qn8(Lj{tqs5vPd~EBexuKZn#xB9uf1@+=w^twg^yrfTMlvW*jWU%Q8HXUS@{ zqU#r&oSuM44Jyo4ht?QNoPXX{YCCF+r%48jMK=>=%Ix&Zs?!qB1CKw(D>4yIH(Zg1 z)-Q(Z@YTs z-+y9n_q_+Jk(jsm-lJ9-)*D2@sjJIIjYZAbBq`Kr6KblaTAd4uVJeZDfmcnsMU~cC zB-fk2T`sW3&~+UpM+7CsK;L)dnIG}qVvWpka``-XPY|qFx^97JZ6jZM`4#TGc7rZg zYvUGDI}8=oY4!zM(g z5XiF^uOx+1WebT+&`h)tOU4>Vu3C?rF*u>}Q8|%HZHd>zz%&Iy3@mnf4vr2OL#Wmy zE$km2Vyt5dwGC4d4iAoi!ks&}SS*&c1>9~KhRT&qF%VL$B2Ob2C&W@%Pa|r~EH_ZZ zIg(ach?eR=kl7o#4HNs9kNElzd;`bF$9&?MXXyH#rfu=ov`$5mx-G-Z5DQ1Ly8l{fVQ4($qXg4cX-G{uX~v7dc%6N zdE=%1gZDjs^tR2<-u#7My(n1!GI3Sce(OAnmfVdy|Q#}otP zLTB452~bNakg4upv4Wa{Z>qKl1EEAhiQoijJdeKljoi5L8aYI~SUi?Z8KAMmR5!-t z7-}|o2&5P>&M?KmJ@?;3BvFP5L#Ycy#nh2{f!1}TF|gD0r1cgV18pwY?6J0Pz}b!? z!#EN1Y{Th0hBzR$&@LKI*2mTeo$detAOJ~3K~&@rXidX3MTQtLzF|9#Bo$oK*2$ST zN-E?jaeHaF{M4g}^St!ZYk1=@xn`rILQ*9;gDgBO8j{T3$hM)i9$UbvKq<7&pU1}- z0nvi9hAa2n1>SJ$&M~fOuuV%Y5KuPb%rE0Y_la0ak9TSC37}yLWzww(NV}JjEl9U({4uexm*K{<} zU{vUQ$D-*0A(C(lN4w(*WhCa2Y0mK)ML5ku9!Cx~TS|ss~? zk9hg^Z8St4yuG@#2-7#c?eI;94_tb~hky0>qnnF@^*j80@ahk}?=441f9>`A``>lR zJ6dbml)!3?=fv~b-W8ouM@pGoDhGtrwjpOdSAsLQFvfuId#=9W32uJov*-EaDJN7a z_hAr1m?$CDC$cegbDQYeh6f*ej5r0ht1UXGCWsh})F3&;pekT3c}`Bq3Xu%9AdLYJ zI&)su8XE7u-y&}!&IGQm28>lhZ#-n*ou+Z{3p3PKAgh44WkZmF4Kq}QknWhNY5Ta7BURONk`CXB7E@MgWKnfgeLCo<6V{kskq2XA@(;g`JdPjCJ5r!NZD z@6hwd9{dA8`i7$`fA#)u`Q|MG~v_3V0opDTJUJAp z(I<)!Q>@#T(VBZcqpu-Pgpx=lF^1Y=IcIqEu_w6a(MNgV*=K2ehjW$`6WchVR)`e7 z@&g~>*=IjV(oD>SjFNK3HGW=EUofrX^mN7P?K^nqD>56lv&;aDGZk&dz;?AFhlCbt zvH!eZXYz`a^SLjN0>)N>=&&8Iv&JizGJnsjCB|4o)AW>_I9;89z|qkWo6V+Xg2h-L zI5Uq_N~xThRN_1vA>~Zh_w-%IYPH5#gKMhpP3DEMWeys2tz+13@xH0vMdK=0R|;M% zR^}`q6wR5OYFn4I)Oa3e(NrhLi*88_39*i@?-7)?mAYI+z&KZ9c&q~yXFN`%F3@nU zK7gFTI!DuVEL{IkqvmVgzW?<2Gk1RR7cUCd@1XOoSHAVqcVGL;AA9$ud;Z)#Q{3I3 zCYmWA6h>f3^~M05pPKdNQIwsX9keJZCNx*$E~chkR(^8fn{>5CZq! zdoRv=#;~nOYtB@6uAykDgs`cR$G&N+9tf}qnyXEuP?1(liC8j-)R>pXqqZu-lgr== z{r-|=-%(7RF5Z0Y1`ogE?W8-$9G|SnCDHhnkWO|)i@fepAK)yPEcb=rJ~c4Bjk*;J!0JKr>qE3RBE18)7HHa z)}dmk0SQ%nno~Yk{>PMPd_yT2k%|<0*rMnf9)05$D*B-Ll>Zdkrhq z=q@vgPdO9aoOc$Kc9U3yyn7Jc``^9)^ubg6Z~mnZo&L&jQLw(q&vzbv^V9FS`}$vb z*X64}(A{{2<#t755|@tlv5TJ5aUeQ}&lyeeybzs5sraTs^D+w+t#{`_&on>LGkU(@ z0TE)1OfisBH3e_C+e-49d4hKq7bA9F&Y*0EDo`w@j`>Y7s2Fe#(=>>+q@uO`w1y!B zeA80W92HPJv4rBEpQl?P}_SW=WSum8C;cuq>>rOk))aZgI(VD)n7xu=y~zQ7tdV; zPzAmUwXLQ495qLVoC&~~Ga)I%7_i0@r~0vKA?AsJk$kiA_oC0DjYctfeiz(uh(x*sYl+K!WEgJLq>o33gQ$Kh6!!KPFtS{2@{a1eP zw|vRH_x2BTi$|TS(Tvc zq%<2|#oBXim2aAJubOq1<<1W4VM|e+VzCS-_Z=J{qM$_z|Y>Z&oFqg#p=>>MLgROg~*FwSC~BhRAOY2HFzbY5m*4aPZM ze)SbT`KeDagz3CxJKN%;R0FeHwBT$-T~g%?%!1Lgq|^{2#uygMB^crKW#x87oTb48@uH(9uHE^elIFNmHQYgkQ8|SGYBu;ukdpuoQUu zt#9F}C!gZgmtNt8&pwalvqD|qy2RG^H-16=P6aqpXXQC%T=*+uwNkO|Sj@ zjbHirMZx+aJ3n^!2mipA9Nqnw@9P&2x9c?$ws>F2B$6oWlo(S2>o983u*J?%DQEF1 zwdJer+Hz;7-egd-!lh<~pS3b)N^)l7t!m|gjI-ytRw-su%#{2&BQglqdxj~HfV=Ox z&JZJEyT#giLrv3E#h6`3E`_L7A>Q^qM^~HHrSR!r{}{$O(iGWQ?vRUO zO+!eDLT!T*f|V+>Kn!Tc&T+=mb|S^hqU+F@2%G8bu18%(cVo^~6srX_6(zNeV+tdU zF_fG!z9FIK?X#KfICeI57p)3%t2KUWjfSMvNy!~eD{sl@(IO}SgpE5a@b2E98P{qUoMKi@II760PV{N8*?XX?E?``(s;iuOh{e_Ey z^+j;L?$S3lU;DZ*|1eI@rI}0O0!sU^2&`Gp==CeN<^&%E1_(*15MZS%B!z&a&p3Y82RjPew1gQ`8YRk z-C=)spPUOh3TaYePDF($N_2uc!KgB9H{cA$I*b~cZh@Oa1u2F&rwaPA(zVg6{&w7QMPd;X&t0k=jw#ARBKNX z>NL^_G8?b^wxK{p+O6m7zwZ4!``mN$b|71=JE}7(uj|N$l(4=YT& zrOgH1j`Sfdmt*<*Z@BVq@9uj1XFq=Hqxqs>eSx0uIQrIm9=UY%7auu1`a{=NfszMG z$u*SDT4K&K*)27~S3E``#ff&-Rat9k=GLz5`#OC}HQ?>RhaP0PSn%?zuhJUF{@(t% zx325D^QUypQ<}}j#>{q`YSa}3F?F(7X4pTBBd%$qtT#0#?FXp3>AshVkGA=Gvce6 z*__4T%$A>!B3Y|iz08xnnkHKHaXKsPl`4eY-#_HjpZzQ%R36RySvD~*taXq8jIRl; zbCSdU;gXYh%ENDYgm-`Q`|$TJIJ$Bd<7Wmw@{wO-xtrS7Tf*OoC8f$0Mv0`U^l=C$U{dBcrXLpyKy1>r-|Q_qlZW60f}c3Vqv0$ z6;r*4n3^?}&u%&^WQ8eJk71d;d1u^RZMzI@+tM$4Vv3v&0}-87{k7PnnM^m%)#1W? z(-*DexS3n^qI39P$ultzS!xSbv=DQqq(tK^wrR+v#wI`e>Ce=?i`4fujWvi-i`La+ z7K%7D3tH;UrCLZ#Y<1wRANW%4eflAuIeCut?G?Z9U;h)Fv%K-ir?`FNCevo1^UcgG z)TJBhEaDvnL)g}MoSGLuo3jP_wjt$62nmfDqk^c=@0h1|oWK8xgExHamu~&qYZnFU z3uO{`*Y~{Vk)7Qid$jGp@!AmB*$mw25?iT!g)xps8oW9hwK%m{70?6}jAPDJugI&+ z3Uuonr>j++M)f_Xr>AVU+v)`zBQeCvc{#^oxx~&W?#x82=R4oQx#rx*r*m6as63MM zo;#-}G>euoMb^UrsgAm}6y{+WMX1H6l$q$4ns`u@<^Cakw_7=~ka1#h#uIa46Go;K zXC*fjSM4Y5V!@ab$%0!f*uQd>ZHSCHkr9fi%JM8ivW4QSPR)qH7Al7mBWVg`DHKt5 z4)*W|3sxs5_@dSDJxdFGTjOc!qKcZDU|o%5ai+nDpQnumP3}FO&wq9%^<;au@ zah@RRtkcRf!Jcxi52@K^qv-jviK@*i=f$vS<;=vGDp8rIg=U`oRRO8s8c&F+1_zcp zeau=pEN?wxVbVaJ7uq(~W9O6&ZIrMKJo!~`X1d34auj*-xtI8@|Ka0ohBbK#OzVNx zxXKmIZE?}c*{Q7$l@u#_wp){7f>=;ev$~4g$^ZKGZ^4O)f zKKIMFe)YF53f6D)`Q`_{|7#!EUH*l~j*i~DoB}(5$q>E4w=GRm71=V6xxK54NqyTe zO`F z`vw!!I1<8iPB0r|2syD>EEu;N7F}OkAu%}LoKK^Y)~V!_VqLT_e?5(LoNTt$AS^=L zHdS7c6OlX@97{HaNtKwZ$=HZx2oo_Y);pTE<@ltgbP6*sQew3cwVp8r)ELwl*5kk? zOkBGAD!yqrK0T?iFx!DUuiavrQ&*>0IlVj&8qg|$wXVha2IuRpKVP!n z^uM$BCO?*?=b7K{o6d0WjVUANn&-Jl7O55`wggI~D2qZ%O{zt;WJ!=WUI>PVMeo}Z z)bgT@wgLYU-gxJQyFCHjl3FBH;-n%=RLz-LRXIdt#GTGL-{{5rMU-H`Bf47+sxFWq zP|V88h`9GX@9;do2dpgKlLDWlh}*W6OI(uUL!w|>M?h5i*+{WdTGfz@dN)u~ppvSE z?M!W7cZ^wmM6=LZqm5=ed9<};BkK9}W?NP6mRZ}%j;>;TQci@BFiMO0ykt_2oUE5D z4tIIv@h7$T;*H$p<$14|vh0+SG0;>2XVK_TuzkyH-pTby!x%lfa||hvjiYoeSvN#uh)H9O z8*t>iYPiA$xV$m;f zSui`sHVh<+kzy25Mh<%oC`XEl6g4qvGLFelOn#6UvJ{z8pcr)?YFdn8%9(5|zCcE7 zJ7e_FSS~;G07t``tB*X$W6wOr{nOK`qbTGcg>9YXGF-Hl#eCg&8S7Bh#* zgT2zDlfq`vn}epSpSY+zjgSvue~ro{QV1C|I&UeJn`UzeCexS z#2%9ir7pX3DQq^w`Tjv`LkN+rD&*D@wPG8BT=}%dSjPj`ALRJX zJ@&c<7y2Dcgxp$IULw?g;!9uP=>9Qdm$Z53M0G-lExIUN^|%0zCh+;gsP_3%7~4~sB>Rm z$?_p~2+g$Au}uO|U5(YMXqn)}K4Ud^PEXjG@9^S_F9_CUyavC1qMC-H1^ZtwnEDiV|srf=~k4WVF@zl*AC+SdmQ>K|u>w9h$CXyV>BH zhW(2dSdUvK?-^<|{nJlB&E-oMdGDQfNj@^`dp6T3-B?a_1XT3B=+q`JtAkQw9JRA# zU>N0SlVW23;DDo}BiT*XQM7NnN{0p27>-w`>>V61jv_;eDX|*1f(I&*DGt2z#_PQP zrfB6%arm6ry?Ai`-WdEuHgYJCPz@=3+BIm`LXi<$sfv%z!reb(-Jivk6>BMbI~1Kr zD#}5m?XTj~%go>3d)^9vA-~GJ@O!w?Qinv_y$mOd%^wlrX z&t|;)&KvZ_kyDa1V(&#f=-fv+b)|aM8hN^UE1{cbnBg;y)aWOc_fPS|$o}pgUEi}g zS+O-)V#_InVc^3LZxc%)mn1ZXl4P$_WS6z^17nDUoX^kHsV?v_R#ff^R;vwJDV#Bs zm>?z$V$YdRlk&zj2}j6Jehtbpg89ojqA|bFpLu<5g+Ed$5BerX!B80k19|# zPpU>|`RJMI)vYKfR^WN>y!94Wu3lx{&$)B^F5@)Ov>mQ(&mn-wG)&it0a@ij+qPI` zDOr8=!Kxi2O3d0>Wm$;VE32ej%awEro?K+X-pqQYX<~PGmuuIrbANftW*Bf?%W}Eo z-HFBJ=BDT~>P^zjHEFT2JQ(>+0|J9#|Hk!SI1D=23dG6l3hmV06 z0x2ep*7#|Xhe?WbJ9ExNWGRMo|7qhIy1rwx-4dgxah9u>uknS?e~}xvZn7N)$!=_$ zifndhCmnRoS$wL-o&VXQB~~@)wVa3FmTF;YokJPRaTw4hV}O(b8bu88(!p%;$!B+8 zKKlCcx4!pr!TPuS`s#zf`J2zo5B~9EwtMPe<+?{_n?w@jc{{#gLuCO@R}bL?HXK-P-&c3?^g(>16NaY53O zJ1fTNSh666V=W9tuA3z!OfN_nG3&O1=hd1x$xcqFa-!C^#?l#stvF`twF#v}ORuQd zl_Zx=Ef?0tI*fIE@ZoLVzx6(#I6S<_Y&IuG`QR6{SbtQm0>?s5xtgeDH~Hft>1Wbw)>ASEB)kNNc6yPdd%M8KC}G=?|gWRAN9f)9ySJ1~aH2ZA$9tzmG6Lg0nZe3D0A{siy*#rIjCtWdd# zWdf=VN1ld~8dKHFNn)fZg_49H{`ljMvtF$@I=au*E0>AeK=zaDG3rk1yvtB9U5?=35imu>UUWf=i1pMB;8oK zhEQNhDgj{S|=Ycqon)NqH+&9 z*JU`wTnH%<5%aa{JCwCtx^|6|^_r~2o)m(NclJy;O06!S(A6#Q4)jFie$#O}`(#4u8mffB2FmARIoO|g4PrvXK-~Zls zxPN>~P6cgRbjsuq**`eIYK`|1Yc-qIhV^R0>B%XjGQ!RRI26bpH46sn|m zTR#bA#5FjrNg*=d+u{DnG0W2>&Nk#MV*@oBE2V@okfJA*gvp}7FG@l}l~$A*@Gw=x ziZPZkVPMwJM2s2& zXoXLa5Cfz4B24vOWDE6bn6o4xDJ5fT@LoDPrQ|(SN~NtvK_nMguQvprxbgl?{`IS0 z7iN}{_n@qgBv8DP{ z;XI@@mx3+@>kOt5EYUEHXrq{>sgCv%xQkJ!Txfj2v<~Ggslp)hlnBGf)=xyrh285{ zUVi$aS8jjf<~LsZxM2ModVTrQul@4p4j%mdgJPZyC#ST^(9W!c1ayYgILIkGXIxg4 zAyAYRvVqcI#D?LM@R*b~1PWKLU*pcbyCek*uTfqYMrSn2Tnh8Wj$F7J%Sj0|4_spJ ziAT|Q?$KmL+qJaX5w-!6V1tb^7!)ccR6et(h`Ll=w1p{6eEP>CM z*<@PRU@+vEaoV2agzg<3({&xzTE6((zr!0}|2nIq6L$JJCd0}7`>dBMNx04hBd#2I zz@>a{=~CilFTnr+AOJ~3K~&bT83&rS!B|@l1M)?e@==(sD~byd+P=lkXLzfaig4LX z-B)TLlyqpP_`*|{KY8oV@BaILJ^c5+VEx1Ezy77iclZ9_$<)1MVVh>Zg%1IrC0cwn4l)8LIJ|b5$DV$cZa$|wnb?`lNk%io$l=9H zgweA;T@zDce&K+V61e`#i~Pi|{ypye$+yrk%caLBf=@I}Tc30i@|j{1pIT0Ym?){L zsnc1vp&=_;*PMqSIBlU6CY?B)mNFXU#25m}ipEGAgSLj~1C24vT#L^#YL=`>QFKpT z-;tQP$FLm!lRkT;F zR3@J@!_Fo`379>jI>AD`B z!3Xh_7F8I%bahTi2nbmbgX{rgk^_u#nmw!e!4H0b^#x}%(>M}C#AzcMD(}ztjt2RA zSgoP6^8L*MS2WI$OQvmFF_u@dD+N?4>}Z2GicuBD6!BzoZ+={`{(ruH{mQTZCN3DY;Nmro@aX z24@@etUJetaBh<^WF`Krw(EH3op*Tdl}|Co$olPDq?}2OWhC*y!w+%o$~8{z+`~A> zY-hpIxTfsQc!(N&JIF?)ehCjuabX%(fRbvGtP#dDO0_>$+`w zN)-Vw=5xBHm5X2!Ja*eRG|rJH8NX(2k1;a#8|NgYJ51cV^#RM1C7sfQlF?cbV#GRE zX%@)WS>xKY7BB3WJt-&JuI0kTi>%gbN`angg15MZATth5{*?fmT|JI)~P80iD zp1f(%aeH;j(Q?W5bjAAK{rbSwj0V<3o_Og+j=%XDr^icHK`>$Wl~8 zC|O99&Q>v`fcc67AEPMI%SX16DFlA@D_^Eq&7C_RvfXUhhDny?)hicjqjPhB0ng#to*`Jn{77=n29U#ABEuQ!aR7wf#t*VTu`N8=Q4wpw?$+ zb%PagfT+HeAyJLXRNr2Kk`=3QL#bM))vzI!LI|GCdLw%+rD)oQrt2t4DuY%M2_I4- zCb1^z%23b9vOXhBEn_UsI7|)c$f=0ZysFWSu_Og!$vpD>Gw8-~d~(X@r^3a5R7gRv%o#CcZ^nVHX$jjc zqxYz~m&{1V=2}UjrpjMpMdU=CSnTd_@ycbMf9XZ;pWNqkxjbLg8+84pNF+85C}&x; zJr0Y`^{AnN){zXDl<~_Y3a}UV9 zzW^OU;=Z&$24=BvWE8=`Y%%BX;sulstnMALupK&bZsLtbGbzdQOG!9Msbpdu4pJ$o z))6VJw;OhLcX4ft-wv!xV5JkI0oz)LiNkKe+_k7w7?S71)d_FB{Wj*#lFcxR;8R(q zT$mzwrI|{`mrOE>pcDZZ(nP4}lTeDNt$|`?WK&Maq5&Dbe)z##y!q~1INNe~coCy5 z!#MK!U-%E`yN27h@1Rn~CTNr_$90t-gpknIfwn{i+c4slC!5Ut!i-<}NB=3?<29>0 z_lQ~(vc!35>(I(#ZG$xp({@0i2vcCBD$h$fXMkxw0`X;}%e56!sBA+qj#LSrnM~8P z^nHh)JVq<}wjoSo9c8OljB<`jjycs}Q|YWq_2!+8Vq*?6CYHKTu3eJSnzn1{W;0A9 z%kd!vKKFA!Lr$6ZZr+e9tCcP*r;IH~l)V;RTY(yrrsZf*$3xe)qUD>s=+08aTCpTV zzm+0bu{>58a;VIzBAxw|y)fB|7{GCcDJN#T3-&Hv;Qo4vZDo>38e%0j_#BzSKq`s( z?wmK?e3N%?+@z`2EsMgTvC4?g(->}FauQ5CCYmYJ>xRy?Y-3=ug=Dk@iwqMOxctav z+QvR%y6)1`mp}E#-+KSgRv#Cv|A*I4@BB)0t(pH<545x2-ZzFe1f0{Hri9Z92ZS&Y zwp-d73X$t5lqA0pKaqJ;x zX5E||BcmUL-L4~__}$;YAD{5U@4rs#8fMK5qpj>HVj^Z}XEVCKXP72z=h%!JtTWig zG7baJJpU3mfA}7EKfKF!GeFFebEsvJ?n9t)qIfT48e=6DM->bjoeEBwiUqD|&sA`# ztwB3YOcB#Kj6E}G=ksFKw-Sayy)5qUA7Hyyc!Hi#e-d!iT21tk zFeMBP6ob}I*ycr%5~NgvQH`Y(AqAX~IF&W*m>GStBnn$IapUwu9(eXap8xru;nn~B zf92+lo3u@bH9eQFJjf;nrd%*>i*lAo1e+^q#GIv)m|{R1C{tkGHrQCe2V5>VrLc%jDCLAN5#x+tTS{gX2521HVS}|& zV4i#C8IJDVgOVAhiPko3hLOjfdW!3hJj}Zv+>*czB|5C4g%~>JL{Ko5#3oD(w$Qjn z3QQG%q7}v(Tq|*5XEq*y7=t7iX~*%MyM$q6-gbn^v%kB`l}nd-|AY4>DAkD`N;j6` z5Clz5Xd_*vF>+7|If|{wfNI59?omuB;Zwo2eeK|c+ut}xV;#Xq8k`(uOlCVx99+1- zIF0mu&(ZO5eSlS7T9qq^0X$<}i^kB{9s)QVMm1yeZ{fbNhXk zCUb9Gk$Q{L;0p4dPYIthlggxa;^g*S!pVRe!)HuT`s+u3{=a=(u>P;KR#$)hKe@is z|KTOu;_6*DBdIp>w6-W?MMPOr4b7D9NzLQEOD=P$iu& zfJ+^#l+to3tbBK)#E6YpCjD z>c5>+CWJ^zLhQVB`3mI1$>}L=+j9HP9s0gwcQ$9*j_53VwyjDcj@6kZvfQ0~pmPnE z9=MLrfnnOpGCl=j&Zt(F@#;*$PpMWPiz*OCPuF(Lx}L`#dyMUROPm6I)79L;fFCE8 z>lLf*S~@JHF-GH>hG7`k+uvt*f0xD1j$8>-V$t@bC>lEjv`r(6`KqC6nueX79p>}- zIoti-{bNE1bbZh1a!J#+%({;Ky?u_4jjU!#x(YS_?f}}g=ToavTbS^~%!i|xK zUP+m*k*{&s4p?KDhKbfST)lRU&34Or959U|DaC5qve?@pj2lYjwUaWpZr!ThK%te! z7-^hi97md_5wEI`a!odkcrbG&7i-n^PF*r zreq93)X0d5IVYidh7gH0jI>m^-C0DFYg$n)BTo=lgCw4N{(08RWgRy}#7G@wY(>yW zP(qFGQi>^tnl3yrc~1dW>otA~Rl{S6IWPu~tH8P`B}p!}EmP%QiFmqR4vSbSv=IiE z_zgM7&7_>!-`NFVHEuACVekX@KKOtu`-hxuV}fsxFl@z4HTpsQAI34Ndh9265W+;oM@`5 zK1uvLL}8QskG=5t=id0<`+xTO#|5jtK7aX_ zK6AZo{^_AsPh3FJ#w1C)TH}i%8_Sdfk+Yn>8p5KKi2jN&)QeIm>m;_k^1++7!-t5{ zhQI%nukzaWU#~zsCEV{^NCldzJZv2qPCR zUF4@e|9MW@= zOB`Ie#L4ODxoGidy{sfs;iWfyCzs8$pobd%5K=m4*86EX7nrI7#Eh=`DY@#E>WNe5 zi8p=M3H39IEJ10F&x!jdC)}^Bu9y=gN@o>Ql|5K<-btZ^gJtXfGWf{oCl-6VOyeY) zr^b<^$S|yPc<+fJpsk_pgw5YJ;xstZ$emFzl+v>AQ|3I`Ce^R|EFw;!&SS%h(uB%p zA9Jd^I+z(L)(86s93CFByR*aX+qbK-Uh)XH!$9Bn7_Bi?=XYMXOUAZxa8b?>wdcXd zAL8>r_cMI&d*5Y-&{;btKli_$OcYB#4}Fa}qm08Y_SkL)R0d}?Kl!=OGWhlU&b>QN zzwr1^z53ewufG4czF__QrC&R|*1G@t!CCwH%g)gG3F0K7}tO{Y=?=P?|&dxiY%>kikXm#gfZ8rPpN3yjQk#LETyWe zg&idnOQXbsb0%qQ+m`FsAK=cN+tmo{i80YMjf796{MY^)>ij~Ls_jCOT12KXc`xV? zt=M|WEA(CxjY}#tuA?)npYNUFk5UL0?<|Mg`MoaibN`m$&LMTmB zq_r&yNixn!l9J1rF&ZUGd?#TZXVLQ7SVETY*ml;RYm}^$&cm3Gv`S~1Hy=Zlei(cR z4AXdiZFF6WF_ylcQIv46a*-g7oHM(-yTlO4IWwEhpd^79o?S%iF4P)R^;dFC&`OhH zWIJxfN>gMw*G8@|+i7A5ojN6=^nQO!yqZ$v%W`b>4a^4U+nJfarN3Y8CM%g zAxbepk?c$DEbY7_lGubbC)1L>i@SXK?|zoozW*9_rE4Ue6x>{$2q9-;sUA-%hu%8I z(qgn`KAW*Ty~iVuK7du3)6?T?*4QhLU-ibALE>e@~p@L0F1Gf&8^Pg z@F94}5|*keSs=tDCfgXLBeHe=FLF6^*3vkKwT9(##k=pk%jx|kDP}RhA{m5!nuMxQ zDytg_c_{Tm5UbuOWijMV!P7Mz!#JXiVX+WwjBYJjS&~nrdfA(%h_SkMpOW2YYFCqT zlCRmw!9i8ke+Zt&H4-wB6R|E`2O?g>XMXa>*?IITul_H8L4G>M>57JV^0X|?e5i4C!eFL+qT6y$7a~lbPcoFjO}(SyUQv#1^Db|Kg(jV;P~iB zKHNrbfl4V!1Fsr2t!nyDjMB*}P1`l{V3{U%=DYH951!F`jCGjCu^k56d1#`Yu8z?H@yA!+hkxqn_;Zw+O_Lky?UKD-hPXwkpv&5MQ^9HNGx32VjIH{ zN0CSD^gREGmpC~-X8G<4pM15w!#MeBLy`wP+SCR>4BtL$a`cchO=o%gowqnTx-0ug z+dgYd+kET8Z~pVYwFT=}ANr4f_rRIoUl3@0G$5h&_B2_+UOc16ROgY2AQO^Z>Y3-T$F zt;RGCZ4HfW>9rBfo0X$+7N-q;+hUv(j7^2&b$!orwL;bOoHM_l zuZ(!3L>b-n-T9R(Cupp2rN|oba0~lr@FU%4`N`yL-d#s4B7I;|`7Nxl!UX7Gh9 z#_**d8C7N^@#$AS&FSs?eCyR$+27e^-gX?XPY5Z|b?y1}Dk}sjT|tK>RpgDaLV8fD zdh(=0X}gw1wdr^t7{&o(Ep6XPSL7_)%|@R5r4UNu!i7V84ChHfAx8SHt67kR=mRLl zv(G+@R+_6HPh8a=OGg z$77E_&i&I(y{GY&cz=5OQK0rs~a%9A3M~c0F+Ktz)L> z1wUPbNRk%RO|IQlpb$zXWXQRxL9_!gd8{qC#*#z8W|YSkXv`>#pi%0ZzUjJ3u#}`=i76LmQ%t#L;3-+^*F+WS8w_j6S=CTZ>AGSP5h@{~ zl-M+4%A_cbcZvz;T9F215f!E+nZ2VA)q<0mLZt0FiEYnPM2u@{%#>mqC+xguJ5DHV z2}vvcV@Mjh#f;4~FvWyv8jhDsE?mCE)oa%oysuPE&V7MXitHU8qLpSC zC+54ms01j((eW|wz4IQ^ddsJudx;Nj+!DIy&JHt*+)xrMWx8N)O&jDw8ZB@<() ziPMIZYVuCC+$d)VQ(&5erm{$KsUWCkF`SQLTD^s_}MRT`@;{p{lSOQhBpmH z%V-c{I#*!#vpL;jm!YC+bL}RKk*kidld65C=JHKpB&0~r8EdU9ZLNc<1v*2gge1%0 zMho#Vl`5FjQrvuqG;K#JiJ~%1*HTnD=Pxy_@VSOzz}QNYP;wmTx)ziWQMI$g5ZT+` z7xHJ!v~7nUJu%7-qEsYIR)UA|6{CU@Z`##sS82PBqvIp-%vB*~h!Nd5Ow%xK2b!+s z!i5V=li*Z_Y2xzb%Z#fv$G7j|wD63})X<2Z=Wx5jsAV;HBAz5RU|9YaKG&9vTvH7wU_IV!}+IE^yO*92}; zJ$PDcT;p(!$N*B3;M=q1bIzHr>o8g~o6SBNQDbEApY4r4T%aZ3srTf6l2c1uDmYG?X3dC+j4sBN^Pr6y~eLQzGc-a?|W z9Y$FSib&omvvw={H-Whk1wA8gK+2eua8H^EUcq=QD{$Dahf6bGrZ1F zGHD!W6)bb+-o3lL_s+YOARsFA`YoqxB%L|Nz!OhC#r)ua)8$gQ?ez^7Ydkuo(gkvo zMYlCz%lSUbId^W)IC}#tD?78ZOP;k73K3Gqv@OLt#wg2Y*LMUJ@g-5L1esbR#@1mR zi7~RXvs1xy3h%w_;%Y9Qu?C!FIc#|5+2@$1Knw|6!xFS>P}Wk6J;%dL!HX2bXohLT zI*V;vPS-1rPfi5A(>53^a9KEiV!1B-wM2N7_iQsk9jisM*7)|zVFY2R<#yR+1Xgxbsf_<5kffE z$c^4J#0l57vIA?H^Uhn>VkcN>jWLed?woBL&>9X84q2Tp1Fo!_WAaAzsoR0>=k z3?ZP~hNxieM~X2r78VK8jY%GUl!WnH5$#$-XbiC!Vk$UV*%Rl2v0!w1JT=O__Tjhx zryu!(^|ROh!~f{??!7-ut0U$(T5U<2#OMpbHn212;isPDgFClTC85c5I^%86>B$Lw z+lg6sGqAhZA&jz*&{`9Nydie?ckxPba(XHS$Tk$Eh*5xXRB>Sz%AKf~tw9@E$jh6# zF2rjHK`9?)-i1`iR^rw}m0v^*0Zr7%Rg=J!JY$*!j29K_&6dStfl`{|lM|kQ{&_-7 zoGwpUZPxW9FHhDXWpvwOQN)<>Qy@k`4O2alp(H{{_)-W&xqO-WVQoZfG!8>Oi8}zc z(@63WZ@|rZarJLTwyPC61Y(SIZOeMS=E1Aixp3it6cQ#u=bR+Q2>VzNG4;=N*3ooA zFHlBH2dL!Q8e<})NUUiv!!R)>&o~9vn>EH+eDIKCO*>BVFe!y6pL&WI0-MtnJF~gi zjSzj@y_5TOPi0xJ*39}|&@$fRoOD<*W;UA*Qw-<$m{L^bpDQsr`@nj;;rg{}Ow+`6 zyQQD^^oto?+Y(Y_96cch0YqBMX4uxlfC!wo zw6T~*vH*mpkw1z}%O3^FDrGP_GmB9S@Xiw37UfzpEn)QJZ6IY&QF2%Gs(kj^^)J5n z)`x%k?H}2K^@S_H`iYCP?!Wz+&wuXFEVm5H6)^-^j2ew;8~U9Y4?X-KAKbiw&6(ac zfM!bals48dZny00EEva87)7}-P9t64p_StJ{(XjVWY})RU9UB&6k_x!5LzU;?f?|d zHH0vcqwJjw^7~UtHG&?BG8C=wS+0^2A+Kn=4%4)Pgdte&3KsFUY9p{S9QPq&!wW2+`eDx}g z=*`Ih03ZNKL_t*ZgB?!p-WBg#9W};j!ZlX*d^zKX38O8u#STd+bl_73)ORuh9~7$>y07^RqXEvxlXzOJ0v zKe#}MiS=e($8ARrv4+n=OrfSu%SH0+NU_?i#oFU6J||+4!s%mx3Lm_3`3l`^#(K4; zYh_nyT~j-`UZ8lfcB%z-cX!ziBjYqxHojy_TIX2I7o09vIM>m4qTE-d$R*bY(bkzN zBz2CQBeCLa(89GZTI2l$wxWWJrkGM64h@gDxv_E_B4>h9Y)ZsW zBFZg3u+w%pE$)M&#oHK4Cii>KJ$3!F|LTYD|LN@?$%6ID;?aM0r7fTTov(d`;m(G) z-+h;z-908EStneh3EK^K-@QRspCTba&<>M5F-%bRLR0k67^alaR$6B3oQV61!lG%> z=$KGBQ)&d36cl;mln^kr;7u{rr8(##smVEk9|I{t$c1b)rCz{_X$WX$i(Pj2_F1jg zlpOGW!Wd0w3^_zD?C!HXK9Su(k;T7qV(3k!(mt&L>%@;HUkC+>@v%)uETEc(s1(U5 zOy>lIhwNl1;_IVYIg+3F%#Xdolg~ZL55NCCwtgTP1mM=TOh(ro2;eNKv8+jKRc4(d zx@&ml$A28xcl`0!zs~8fVVvZ;*|Z%en>87QN#ub$Ev@>DnPcaj-;bgtVHXRtqdJ&XrVa8a@wLg$LX|TAYgZT zvS}Gg=K7fngrNmSU*($J#m?Nc8WDAo@cn7&LoP;ft zDmdg2m5b0^b-ip-{k16xbTY<3M6q!V#dR3dU`xTJOlJ)~Of>UWRv!f#-LYsFJodsP zhm9$F-}vFb{2zYg3f6DG{JZ~I3;#$>k+=TxuNYSn+f+(n*bcH#_LC@|t(HL08p$pm zG1+qo?NUpCL@sjCK~+zdIo|_Cqwr*+kyCEc5|GgC%sE~yX=WWqt0mpeoNUDSY-=Eb z*d&B(nJSABha#0kl$uvw1!8tP2c;baEpO1AQKi&?R@njSx;EEJkwT{@ptmqu5L%-K^)m z8#frI@jSWO8bg>S=KYLuD`V&xZ_2c;veP9CFs4XJiD#dFhVOjq+c;xsvPLB#TsYfc zl!Qmzy?2-6<74&?b_prqS_=krHazy=<7~ETrpe>F8JLz~h)f<14=KT zsT8hUyT*Duve?j`o8Dy|MD;K=3l)*@Sb~j?{Rc= zkFM?5ZZ`C7%X+(IZ-0+*9K>FQ=)ekv7y@0_;k{=ZM|of>E$fDoNk}voR-v6xACwWo zg>i;VqU+_qVH??l&buC?t*khzYrIKi#cBy*NjVD<1I4VLp{g83H@4x!>ADM#n`Rl*&nI@P2 zuGf0UX-K2z{_*mhU6f+LoQ+a7DqQ7qP6~C_5KEwG8oB7DAPK#yO5Z7j5U|FQg9tIR zwdYr?kFFu2>K4PaX117}?>4lSP&|AAGn!^GvT!}qHl?Dm*2)jCUKVZD>?q{v z7b-q?@8E#D_fD$^uO>z-VJNk2i!qv)UVe!?ckeKc16?mLyHpA!DNNQ`a!ztqFY=-2 z`<})aVw580y%$8UF^pacsjfRYp%;j>Vv2!A8;BC=rR#y=EIO=|!kweL93EWY`1n4@ zNB2a%ZpBJ6o6T6S*EF4wL3Qm|b7hKmT_;(9A<(rgv)x@%NaqEvl)`Gg=J@!S)6*r+ zSkg2>k|T)G7BGxKE_Ab5&uY8EwvN$nnJ;?cn0V&#=Xmdf8~9v

W_50=v71eCg-D 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

XTdtI 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 diff --git a/pages/site.css b/pages/site.css deleted file mode 100644 index 2f26c83e22..0000000000 --- a/pages/site.css +++ /dev/null @@ -1,247 +0,0 @@ -@import url('https://fonts.googleapis.com/css2?family=Montserrat+Alternates:wght@100;300;400&display=swap&family=Source+Code+Pro:wght@200'); - -:root { - --dark-bg: #191919; - --light-fg: #C66CB7; - --unfocused-fg: #C66CB7; - --dark-fg: #8F3881; - --accent-1: #e44ecc; -} -/* - * Globals - */ - -/* Links */ -a, -a:focus, -a:hover { - color: var(--light-fg); -} - -.display-1 { - color: var(--accent-1); - font-size: 4rem; - font-weight: 300 !important; -} - -.display-1 > small { - font-family: monospace; - color: var(--accent-1); - font-size: 0.35em; - font-weight: 100 !important; - font-family: 'Source Code Pro', monospace; -} - -.mono { - font-family: SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace -} - -/* - * Base structure - */ - -html, body { - margin: 0; - padding: 0; - height: 100%; - min-width: 360px; - background-color: var(--dark-bg); - overflow-y: hidden; -} - -header, main, footer { - display: none; -} - -html, body, .lead { - font-family: 'Montserrat Alternates', sans-serif !important; - font-weight: 400 !important; -} - -.lead { - font-weight: 400 !important; -} - -strong { - font-weight: 400 !important; - text-decoration: underline; -} - -body { - display: -ms-flexbox; - display: -webkit-box; - display: flex; - -ms-flex-pack: center; - -webkit-box-pack: center; - color: rgba(255, 255, 255, 255); - box-shadow: inset 0 0 100rem rgba(0, 0, 0, 1); -} - -.cover-container { - overflow-y: auto; - padding-left: 10% !important; - padding-right: 10% !important; - max-width: 100%; - position: absolute; -} - -/* -* Header -*/ -.nav-masthead .nav-link { - padding: .25rem 0; - font-weight: 700; - color: rgba(255, 255, 255, 0.5); - background-color: transparent; - border-bottom: .25rem solid transparent; -} - -.nav-masthead .nav-link:hover, -.nav-masthead .nav-link:focus { - border-bottom-color: var(--unfocused-bg); -} - -.nav-masthead .nav-link + .nav-link { - margin-left: 1rem; -} - -.nav-masthead .active { - color: rgba(255, 255, 255, 0.75); - border-bottom-color: var(--light-fg); -} - -/* -* Cover -*/ -.cover { - padding: 0 1.5rem; -} -.cover .btn-lg { - padding: .75rem 1.25rem; - font-weight: 700; -} - -/* -* Footer -*/ -.mastfoot { - color: rgba(255, 255, 255, .2); -} - -/* -* Add text to the background -*/ -.background-only-base { - background: inherit; - display: contents; - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - z-index: -1; -} - -.background-only { - overflow: hidden; - padding-top: 10vh; - filter: blur(0.15em) contrast(45%) brightness(50%); - font-size: 0.7vmax; -} - -/* -* highlight.js -* https://github.com/highlightjs/highlight.js/blob/master/src/styles/monokai-sublime.css -*/ -.hljs { - display: block; - overflow-x: hidden; - overflow-y: hidden; - background: inherit; -} - -.hljs, -.hljs-tag, -.hljs-subst { - color: #f8f8f2; -} - -.hljs-strong, -.hljs-emphasis { - color: #a8a8a2; -} - -.hljs-bullet, -.hljs-quote, -.hljs-number, -.hljs-regexp, -.hljs-literal, -.hljs-link { - color: #ae81ff; -} - -.hljs-code, -.hljs-title, -.hljs-section, -.hljs-selector-class { - color: #a6e22e; -} - -.hljs-strong { - font-weight: bold; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-name, -.hljs-attr { - color: #f92672; -} - -.hljs-symbol, -.hljs-attribute { - color: #66d9ef; -} - -.hljs-params, -.hljs-class .hljs-title { - color: #f8f8f2; -} - -.hljs-string, -.hljs-type, -.hljs-built_in, -.hljs-builtin-name, -.hljs-selector-id, -.hljs-selector-attr, -.hljs-selector-pseudo, -.hljs-addition, -.hljs-variable, -.hljs-template-variable { - color: #e6db74; -} - -.hljs-comment, -.hljs-deletion, -.hljs-meta { - color: #75715e; -} - -.red { color: red !important; } -.ora { color: orange !important; } -.yel { color: yellow !important; } -.gre { color: green !important; } -.cya { color: cyan !important; } -.blu { color: blue !important; } -.pur { color: purple !important; } -.gra { color: gray !important; } -.whi { color: white !important; } -.bwh { color: white !important; font-weight: bold; } -.lin { color: aqua !important; text-decoration: underline; } -.pre { - white-space: pre; -} diff --git a/pipelines/codespell.nox.py b/pipelines/codespell.nox.py index a32d408410..db9ebdb361 100644 --- a/pipelines/codespell.nox.py +++ b/pipelines/codespell.nox.py @@ -27,4 +27,6 @@ def codespell(session: nox.Session) -> None: """Run codespell to check for spelling mistakes.""" session.install("-r", "dev-requirements.txt") - session.run("codespell", *config.FULL_REFORMATTING_PATHS) + + ignore_words_list_flag = ",".join(i for i in config.CODESPELL_IGNORE_WORDS) + session.run("codespell", "--ignore-words-list", ignore_words_list_flag, *config.FULL_REFORMATTING_PATHS) diff --git a/pipelines/config.py b/pipelines/config.py index 528007bcd9..2226b48aca 100644 --- a/pipelines/config.py +++ b/pipelines/config.py @@ -97,3 +97,5 @@ "docs", "changes", ) + +CODESPELL_IGNORE_WORDS = ("nd",) diff --git a/pipelines/format.nox.py b/pipelines/format.nox.py index 0498b37ac0..d80efe6338 100644 --- a/pipelines/format.nox.py +++ b/pipelines/format.nox.py @@ -40,7 +40,9 @@ def reformat_code(session: nox.Session) -> None: session.run("isort", *config.PYTHON_REFORMATTING_PATHS) session.run("black", *config.PYTHON_REFORMATTING_PATHS) - session.run("codespell", "-w", *config.FULL_REFORMATTING_PATHS) + + ignore_words_list_flag = ",".join(i for i in config.CODESPELL_IGNORE_WORDS) + session.run("codespell", "--ignore-words-list", ignore_words_list_flag, "-w", *config.FULL_REFORMATTING_PATHS) @nox.session(reuse_venv=True) diff --git a/pipelines/nox.py b/pipelines/nox.py index cf8d7f45f7..8b567f493f 100644 --- a/pipelines/nox.py +++ b/pipelines/nox.py @@ -32,7 +32,7 @@ from pipelines import config # Default sessions should be defined here -_options.sessions = ["reformat-code", "pytest", "flake8", "mypy", "verify-types", "safety", "pages"] +_options.sessions = ["reformat-code", "pytest", "flake8", "mypy", "verify-types", "safety", "pdoc"] _NoxCallbackSig = typing.Callable[[Session], None] diff --git a/pipelines/pages.nox.py b/pipelines/pages.nox.py deleted file mode 100644 index 874004b872..0000000000 --- a/pipelines/pages.nox.py +++ /dev/null @@ -1,128 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020 Nekokatt -# Copyright (c) 2021 davfsa -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -"""Website pages generation.""" -import contextlib -import functools -import http.server -import logging -import os -import shutil -import socket -import threading -import webbrowser - -from pipelines import config -from pipelines import nox - - -def copy_from_in(src: str, dest: str) -> None: - for parent, _, files in os.walk(src): - sub_parent = os.path.relpath(parent, src) - - for file in files: - sub_src = os.path.join(parent, file) - sub_dest = os.path.normpath(os.path.join(dest, sub_parent, file)) - print(sub_src, "->", sub_dest) - shutil.copy(sub_src, sub_dest) - - -@nox.session(reuse_venv=True) -@nox.inherit_environment_vars -def pages(session: nox.Session) -> None: - """Generate website pages.""" - if not os.path.exists(config.ARTIFACT_DIRECTORY): - os.mkdir(config.ARTIFACT_DIRECTORY) - - # Static - print("Copying static objects...") - copy_from_in(config.PAGES_DIRECTORY, config.ARTIFACT_DIRECTORY) - - # Documentation - session.install("-r", "requirements.txt", "-r", "dev-requirements.txt") - session.env["PDOC3_GENERATING"] = "1" - - print("Building documentation...") - session.run( - "python", - "docs/patched_pdoc.py", - config.MAIN_PACKAGE, - "--html", - "--output-dir", - config.ARTIFACT_DIRECTORY, - "--template-dir", - config.DOCUMENTATION_DIRECTORY, - "--force", - ) - - # Rename `hikari` into `documentation` - # print("Renaming output dir...") - # print(f"{config.ARTIFACT_DIRECTORY}/{config.MAIN_PACKAGE} -> {config.ARTIFACT_DIRECTORY}/documentation") - # shutil.rmtree(f"{config.ARTIFACT_DIRECTORY}/documentation", ignore_errors=True) - # shutil.move(f"{config.ARTIFACT_DIRECTORY}/{config.MAIN_PACKAGE}", f"{config.ARTIFACT_DIRECTORY}/documentation") - - # Pre-generated indexes - if shutil.which("npm") is None: - message = "'npm' not installed, can't prebuild index" - if "CI" in os.environ: - session.error(message) - - session.skip(message) - - print("Prebuilding index...") - session.run("npm", "install", "lunr@2.3.7", external=True) - session.run( - "node", - "scripts/prebuild_index.js", - f"{config.ARTIFACT_DIRECTORY}/hikari/index.json", - f"{config.ARTIFACT_DIRECTORY}/hikari/prebuilt_index.json", - external=True, - ) - - -class HTTPServerThread(threading.Thread): - def __init__(self) -> None: - logging.basicConfig(level="INFO") - - super().__init__(name="HTTP Server", daemon=True) - # Use a socket to obtain a random free port to host the HTTP server on. - with contextlib.closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock: - sock.bind(("", 0)) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self.host, self.port = sock.getsockname() - - handler = functools.partial(http.server.SimpleHTTPRequestHandler, directory=config.ARTIFACT_DIRECTORY) - self.server = http.server.HTTPServer((self.host, self.port), handler) - - def run(self) -> None: - self.server.serve_forever() - - def close(self) -> None: - self.server.shutdown() - - -@nox.session(reuse_venv=True) -def test_pages(_: nox.Session) -> None: - """Start an HTTP server for any generated pages in `/public`.""" - with contextlib.closing(HTTPServerThread()) as thread: - thread.start() - webbrowser.open(f"http://{thread.host}:{thread.port}") - thread.join() diff --git a/pipelines/pdoc.nox.py b/pipelines/pdoc.nox.py new file mode 100644 index 0000000000..7ffcf9d354 --- /dev/null +++ b/pipelines/pdoc.nox.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020 Nekokatt +# Copyright (c) 2021 davfsa +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +"""Website pages generation.""" +import os +import shutil +import typing + +from pipelines import config +from pipelines import nox + + +def copy_from_in(src: str, dest: str) -> None: + for parent, _, files in os.walk(src): + sub_parent = os.path.relpath(parent, src) + + for file in files: + sub_src = os.path.join(parent, file) + sub_dest = os.path.normpath(os.path.join(dest, sub_parent, file)) + print(sub_src, "->", sub_dest) + shutil.copy(sub_src, sub_dest) + + +def _pdoc(session: nox.Session, extra_arguments: typing.Sequence[str] = ()): + session.install("-r", "requirements.txt", "-r", "dev-requirements.txt") + + session.run( + "python", + "docs/patched_pdoc.py", + "--docformat", + "numpy", + "-t", + "./docs", + "./hikari", + *extra_arguments, + *session.posargs, + ) + + +@nox.session(reuse_venv=True) +def pdoc(session: nox.Session) -> None: + """Generate documentation using pdoc.""" + if not os.path.exists(config.ARTIFACT_DIRECTORY): + os.mkdir(config.ARTIFACT_DIRECTORY) + + _pdoc(session, ("-o", os.path.join(config.ARTIFACT_DIRECTORY, "docs"))) + + +@nox.session(reuse_venv=True) +def pdoc_int(session: nox.Session) -> None: + """Run pdoc in interactive mode.""" + if not os.path.exists(config.ARTIFACT_DIRECTORY): + os.mkdir(config.ARTIFACT_DIRECTORY) + + _pdoc(session, ("-n",)) diff --git a/pipelines/utils.nox.py b/pipelines/utils.nox.py index c2b05ca4e7..5236c9a1bc 100644 --- a/pipelines/utils.nox.py +++ b/pipelines/utils.nox.py @@ -25,29 +25,36 @@ from pipelines import nox -TRASH = [ +DIRECTORIES_TO_DELETE = [ ".nox", "build", "dist", "hikari.egg-info", "public", - ".coverage", ".pytest_cache", ".mypy_cache", "node_modules", +] + +FILES_TO_DELETE = [ + ".coverage", "package-lock.json", ] +TO_DELETE = [ + (shutil.rmtree, DIRECTORIES_TO_DELETE), + (os.remove, FILES_TO_DELETE), +] + @nox.session(reuse_venv=False, venv_backend="none") -def purge(_: nox.Session) -> None: +def purge(session: nox.Session) -> None: """Delete any nox-generated files.""" - for trash in TRASH: - print("Removing", trash) - try: - os.remove(trash) - except: - # Ignore errors - pass - - shutil.rmtree(trash, ignore_errors=True) + for func, trash_list in TO_DELETE: + for trash in trash_list: + try: + func(trash) + except Exception as exc: + session.warn(f"[ FAIL ] Failed to remove {trash!r}: {exc!s}") + else: + session.log(f"[ OK ] Removed {trash!r}") diff --git a/pyproject.toml b/pyproject.toml index 6196d99c03..b2a89ea0b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,6 @@ norecursedirs = [ "public", "ci", ] -filterwarnings = ["ignore:.*\"@coroutine\" decorator is deprecated.*:DeprecationWarning"] [tool.towncrier] package = "hikari" diff --git a/scripts/prebuild_index.js b/scripts/prebuild_index.js deleted file mode 100644 index 3a8efda31a..0000000000 --- a/scripts/prebuild_index.js +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2020 Nekokatt -// Copyright (c) 2021 davfsa -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -// Prebuilds the indexes for faster loading times. -// -// First argument must be the `index.json` file generated by pdoc. -// Second argument is the path to save the prebuilt index to. - -const lunr = require('lunr'); -const fs = require('fs'); - -const args = process.argv.slice(2); -const data = require('./' + '../'.repeat(args[0].split("/").length - 2) + args[0]); - -// i: id -// r: ref -// n: name -// d: docstring -var idx = lunr(function () { - this.ref('i'); - this.field('r', { boost: 10 }); - this.field('n', { boost: 5 }); - this.field('d'); - this.metadataWhitelist = ['position']; - data.index.forEach((doc, i) => { - const parts = doc.r.split('.'); - doc['n'] = parts[parts.length - 1]; - doc['i'] = i; - this.add(doc); - }, this); -}); - -fs.writeFile(args[1], JSON.stringify(idx), function (err) { - if (err) { throw err; } - console.log('Prebuilt index saved to ' + args[1]); -}); diff --git a/tests/__init__.py b/tests/__init__.py index ef6102e274..64c02c351c 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -19,8 +19,4 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. - -"""Pytest fails without this file in some cases. - -Fixes https://gitlab.com/nekokatt/hikari/-/issues/408. -""" +"""Mark this as a module to help pytest discover it.""" diff --git a/tests/hikari/internal/test_deprecation.py b/tests/hikari/internal/test_deprecation.py index ed9122f12b..0e1dea7621 100644 --- a/tests/hikari/internal/test_deprecation.py +++ b/tests/hikari/internal/test_deprecation.py @@ -19,62 +19,42 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import mock import pytest from hikari.internal import deprecation class TestWarnDeprecated: - def test_when_obj(self): + def test_when_function(self): def test(): ... with pytest.warns( DeprecationWarning, match=( - r"'tests.hikari.internal.test_deprecation.TestWarnDeprecated.test_when_obj..test'" - r" is deprecated and will be removed in a following version." + r"Call to deprecated function/method " + r"'tests.hikari.internal.test_deprecation.TestWarnDeprecated.test_when_function..test' " + r"\(Too cool\)" ), ): - deprecation.warn_deprecated(test) + deprecation.warn_deprecated(test, "Too cool") + + def test_when_class(self): + class Test: + ... - def test_when_alternative(self): with pytest.warns( DeprecationWarning, - match=r"'test' is deprecated and will be removed in a following version. You can use 'foo.bar' instead.", + match=( + r"Instantiation of deprecated class " + r"'tests.hikari.internal.test_deprecation.TestWarnDeprecated.test_when_class..Test' \(Too old\)" + ), ): - deprecation.warn_deprecated("test", alternative="foo.bar") - - def test_when_version(self): - with pytest.warns(DeprecationWarning, match=r"'test' is deprecated and will be removed in version 0.0.1"): - deprecation.warn_deprecated("test", version="0.0.1") - - -class TestDeprecated: - def test_on_function(self): - call_mock = mock.Mock() - - @deprecation.deprecated("0.0.0", "other") - def test(): - return call_mock() + deprecation.warn_deprecated(Test, "Too old") - with mock.patch.object(deprecation, "warn_deprecated") as warn_deprecated: - assert test() is call_mock.return_value - - warn_deprecated.assert_called_once_with(test.__wrapped__, version="0.0.0", alternative="other", stack_level=3) - - def test_on_class(self): - called = False - - @deprecation.deprecated("0.0.0", "other") - class Test: - def __init__(self): - nonlocal called - called = True - - with mock.patch.object(deprecation, "warn_deprecated") as warn_deprecated: - Test() - - assert called is True - warn_deprecated.assert_called_once_with(Test.__wrapped__, version="0.0.0", alternative="other", stack_level=3) + def test_when_str(self): + with pytest.warns( + DeprecationWarning, + match=r"Call to deprecated function/method 'testing' \(Use 'foo.bar' instead\)", + ): + deprecation.warn_deprecated("testing", "Use 'foo.bar' instead")