From 573d0f28411c299bc9c675d8d6e7de6029df6760 Mon Sep 17 00:00:00 2001 From: davfsa Date: Fri, 24 Dec 2021 00:48:33 +0100 Subject: [PATCH 01/38] 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 ++++++++ hikari/__init__.py | 14 +- hikari/__init__.pyi | 5 +- hikari/_about.py | 2 +- hikari/api/cache.py | 122 +- hikari/api/config.py | 24 +- hikari/api/entity_factory.py | 103 +- hikari/api/event_factory.py | 28 +- hikari/api/event_manager.py | 132 +- hikari/api/interaction_server.py | 38 +- hikari/api/rest.py | 1126 ++++++++--------- hikari/api/shard.py | 40 +- hikari/api/special_endpoints.py | 208 +-- hikari/api/voice.py | 18 +- hikari/applications.py | 110 +- hikari/channels.py | 215 ++-- hikari/colors.py | 57 +- hikari/colours.py | 9 +- hikari/commands.py | 30 +- hikari/embeds.py | 144 +-- hikari/emojis.py | 22 +- hikari/errors.py | 24 +- hikari/events/base_events.py | 12 +- hikari/events/channel_events.py | 28 +- hikari/events/guild_events.py | 32 +- hikari/events/interaction_events.py | 8 +- hikari/events/lifetime_events.py | 6 +- hikari/events/member_events.py | 8 +- hikari/events/message_events.py | 86 +- hikari/events/reaction_events.py | 44 +- hikari/events/role_events.py | 4 +- hikari/events/shard_events.py | 16 +- hikari/events/typing_events.py | 4 +- hikari/events/user_events.py | 2 +- hikari/events/voice_events.py | 16 +- hikari/files.py | 130 +- hikari/guilds.py | 378 +++--- hikari/impl/bot.py | 275 ++-- hikari/impl/buckets.py | 62 +- hikari/impl/config.py | 143 +-- hikari/impl/event_manager.py | 74 +- hikari/impl/event_manager_base.py | 2 +- hikari/impl/interaction_server.py | 36 +- hikari/impl/rate_limits.py | 54 +- hikari/impl/rest.py | 92 +- hikari/impl/rest_bot.py | 128 +- hikari/impl/shard.py | 53 +- hikari/impl/special_endpoints.py | 16 +- hikari/intents.py | 20 +- hikari/interactions/base_interactions.py | 68 +- hikari/interactions/command_interactions.py | 38 +- hikari/interactions/component_interactions.py | 38 +- hikari/internal/aio.py | 10 +- hikari/internal/attr_extensions.py | 10 +- hikari/internal/cache.py | 26 +- hikari/internal/collections.py | 12 +- hikari/internal/data_binding.py | 30 +- hikari/internal/deprecation.py | 82 +- hikari/internal/enums.py | 119 +- hikari/internal/fast_protocol.py | 2 +- hikari/internal/mentions.py | 16 +- hikari/internal/net.py | 32 +- hikari/internal/reflect.py | 6 +- hikari/internal/routes.py | 39 +- hikari/internal/time.py | 20 +- hikari/internal/ux.py | 70 +- hikari/invites.py | 46 +- hikari/iterators.py | 92 +- hikari/messages.py | 320 +++-- hikari/permissions.py | 22 +- hikari/presences.py | 34 +- hikari/scheduled_events.py | 12 +- hikari/snowflakes.py | 18 +- hikari/stickers.py | 6 +- hikari/templates.py | 13 +- hikari/traits.py | 122 +- hikari/undefined.py | 14 +- hikari/users.py | 154 +-- hikari/voices.py | 6 +- hikari/webhooks.py | 164 +-- 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/pdoc.nox.py | 73 ++ pipelines/utils.nox.py | 31 +- scripts/prebuild_index.js | 54 - tests/__init__.py | 6 +- tests/hikari/impl/test_rest.py | 5 +- tests/hikari/internal/test_deprecation.py | 60 +- tests/hikari/test_guilds.py | 2 +- 112 files changed, 4481 insertions(+), 5299 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 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 130ddf50d2..ee0c61b11a 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 88ada16509..b7f11d2db3 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 @@ -72,9 +72,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/impl/config.html#hikari.impl.config.HTTPSettings.timeouts) +and [enabling a proxy](https://www.hikari-py.dev/hikari/impl/config.html#hikari.impl.config.ProxySettings). Also note that you could pass extra options to `bot.run` during development, for example: @@ -89,7 +89,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 cb14f8bf5b..060c89d684 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==10.0.4 sphobjinv==2.2.2 ################# 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 130099a8d1..0000000000 --- a/docs/body.mako +++ /dev/null @@ -1,23 +0,0 @@ -## Copyright (c) 2020 Nekokatt -## Copyright (c) 2021-present 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 054b3b8bf9..0000000000 --- a/docs/config.mako +++ /dev/null @@ -1,54 +0,0 @@ -## Copyright (c) 2020 Nekokatt -## Copyright (c) 2021-present 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 d347912521..0000000000 --- a/docs/css.mako +++ /dev/null @@ -1,290 +0,0 @@ -## Copyright (c) 2020 Nekokatt -## Copyright (c) 2021-present 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 eedaa05f38..0000000000 --- a/docs/documentation.mako +++ /dev/null @@ -1,957 +0,0 @@ -## Copyright (c) 2020 Nekokatt -## Copyright (c) 2021-present 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 53872eab27..0000000000 --- a/docs/footer.mako +++ /dev/null @@ -1,20 +0,0 @@ -## Copyright (c) 2020 Nekokatt -## Copyright (c) 2021-present 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..0f78bf10e6 --- /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 7c538976fe..0000000000 --- a/docs/head.mako +++ /dev/null @@ -1,47 +0,0 @@ -## Copyright (c) 2020 Nekokatt -## Copyright (c) 2021-present 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 aa9a30392c..0000000000 --- a/docs/html.mako +++ /dev/null @@ -1,83 +0,0 @@ -## Copyright (c) 2020 Nekokatt -## Copyright (c) 2021-present 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..7fc97a5002 --- /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 b0e5f2d6c2..442f9cc9cc 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-present 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 ce5f4a7067..0000000000 --- a/docs/search.mako +++ /dev/null @@ -1,233 +0,0 @@ -## Copyright (c) 2020 Nekokatt -## Copyright (c) 2021-present 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/hikari/__init__.py b/hikari/__init__.py index 674a9a4f3e..9d143b57a4 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 applications from hikari import events @@ -44,6 +43,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__ @@ -127,9 +127,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 1d4bea181f..4aab517742 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 applications as applications from hikari import events as events @@ -17,6 +15,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__ @@ -100,5 +99,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 efacec3310..c259890bef 100644 --- a/hikari/_about.py +++ b/hikari/_about.py @@ -34,7 +34,7 @@ __copyright__: typing.Final[str] = "© 2021-present 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 02a0ca550f..a21634a607 100644 --- a/hikari/api/cache.py +++ b/hikari/api/cache.py @@ -109,7 +109,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 @@ -136,7 +136,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 @@ -174,7 +174,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 @@ -188,7 +188,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 @@ -205,7 +205,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 @@ -214,7 +214,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 @@ -228,7 +228,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 @@ -255,7 +255,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 @@ -282,7 +282,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 @@ -320,13 +320,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 @@ -335,7 +335,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. """ @@ -353,7 +353,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. """ @@ -388,7 +388,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 @@ -410,7 +410,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 @@ -455,7 +455,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 @@ -488,7 +488,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 @@ -534,7 +534,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 @@ -578,7 +578,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 @@ -611,7 +611,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 @@ -708,7 +708,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 @@ -732,7 +732,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. @@ -754,7 +754,7 @@ def clear_emojis_for_guild( guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] Object or ID of the guild to remove the cached emoji objects for. - !!! note + .. note:: This will skip emojis that are being kept alive by a reference on a presence entry. @@ -776,7 +776,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. @@ -784,7 +784,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 @@ -811,9 +811,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 @@ -842,7 +842,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 @@ -865,7 +865,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. """ @@ -883,9 +883,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 @@ -932,7 +932,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 @@ -959,9 +959,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 @@ -970,7 +970,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. """ @@ -988,7 +988,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. """ @@ -1011,7 +1011,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. """ @@ -1024,14 +1024,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 @@ -1059,8 +1059,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 @@ -1071,7 +1071,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 @@ -1099,8 +1099,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 @@ -1125,7 +1125,7 @@ def clear_members_for_guild( guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] Object or ID of the guild to remove cached members for. - !!! note + .. 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. @@ -1152,7 +1152,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. @@ -1161,7 +1161,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 @@ -1188,9 +1188,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 @@ -1244,7 +1244,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 @@ -1271,9 +1271,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 @@ -1318,7 +1318,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 @@ -1345,9 +1345,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 @@ -1422,7 +1422,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 @@ -1449,9 +1449,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 @@ -1479,7 +1479,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 @@ -1506,7 +1506,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/config.py b/hikari/api/config.py index 839c5a1cbf..0bb5eda6af 100644 --- a/hikari/api/config.py +++ b/hikari/api/config.py @@ -99,7 +99,7 @@ class ProxySettings(abc.ABC): def url(self) -> typing.Union[None, str]: """Proxy URL to use. - If this is `builtins.None` then no explicit proxy is being used. + If this is `None` then no explicit proxy is being used. """ @property @@ -107,13 +107,13 @@ def url(self) -> typing.Union[None, str]: def trust_env(self) -> 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. - !!! note + .. note:: For more details of using `netrc`, visit: https://www.gnu.org/software/inetutils/manual/html_node/The-_002enetrc-file.html """ @@ -129,11 +129,11 @@ class HTTPSettings(abc.ABC): def max_redirects(self) -> typing.Optional[int]: """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. @@ -141,7 +141,7 @@ def max_redirects(self) -> typing.Optional[int]: 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. """ @@ -151,24 +151,24 @@ def max_redirects(self) -> typing.Optional[int]: def ssl(self) -> ssl_.SSLContext: """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 diff --git a/hikari/api/entity_factory.py b/hikari/api/entity_factory.py index a09f7b4fc4..19ce303c34 100644 --- a/hikari/api/entity_factory.py +++ b/hikari/api/entity_factory.py @@ -59,8 +59,8 @@ class GatewayGuildDefinition(abc.ABC): """Structure for handling entities within guild create and update events. - !!! warning - The methods on this class may raise `builtins.LookupError` if called + .. warning:: + The methods on this class may raise `LookupError` if called when the relevant resource isn't available in the inner payload. """ @@ -87,7 +87,7 @@ def guild(self) -> guild_models.GatewayGuild: def members(self) -> typing.Mapping[snowflakes.Snowflake, guild_models.Member]: """Get a mapping of user IDs to the members that belong to the guild. - !!! note + .. note:: This may be a partial mapping of members in the guild. """ @@ -95,7 +95,7 @@ def members(self) -> typing.Mapping[snowflakes.Snowflake, guild_models.Member]: def presences(self) -> typing.Mapping[snowflakes.Snowflake, presence_models.MemberPresence]: """Get a mapping of user IDs to the presences that are active in the guild. - !!! note + .. note:: This may be a partial mapping of presences active in the guild. """ @@ -359,9 +359,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 ------- @@ -370,7 +369,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. """ @@ -395,9 +394,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 ------- @@ -406,7 +404,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. """ @@ -431,9 +429,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 ------- @@ -442,7 +439,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. """ @@ -467,9 +464,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 ------- @@ -478,7 +474,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. """ @@ -503,9 +499,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 ------- @@ -514,7 +509,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. """ @@ -528,6 +523,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 @@ -540,10 +539,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 @@ -551,7 +546,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. @@ -747,6 +742,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 @@ -761,11 +761,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 @@ -773,7 +768,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. """ @@ -844,7 +839,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. @@ -950,7 +945,7 @@ def deserialize_slash_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. @@ -983,7 +978,7 @@ def deserialize_context_menu_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. @@ -1016,7 +1011,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. @@ -1090,7 +1085,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. @@ -1303,6 +1298,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 @@ -1314,12 +1315,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 @@ -1327,7 +1322,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. @@ -1497,6 +1492,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 @@ -1512,12 +1513,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 @@ -1525,7 +1520,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 91515b1b97..e6af049443 100644 --- a/hikari/api/event_factory.py +++ b/hikari/api/event_factory.py @@ -104,7 +104,7 @@ def deserialize_guild_channel_update_event( Other Parameters ---------------- old_channel : typing.Optional[hikari.channels.GuildChannel] - The guild channel object or `builtins.None`. + The guild channel object or `None`. Returns ------- @@ -208,7 +208,7 @@ def deserialize_invite_delete_event( Other Parameters ---------------- old_invite: typing.Optional[hikari.invites.InviteWithMetadata] - The invite object or `builtins.None`. + The invite object or `None`. Returns ------- @@ -301,7 +301,7 @@ def deserialize_guild_update_event( Other Parameters ---------------- old_guild : typing.Optional[hikari.guilds.GatewayGuild] - The guild object or `builtins.None`. + The guild object or `None`. Returns ------- @@ -329,7 +329,7 @@ def deserialize_guild_leave_event( Other Parameters ---------------- old_guild : typing.Optional[hikari.guilds.GatewayGuild] - The guild object or `builtins.None`. + The guild object or `None`. Returns ------- @@ -414,7 +414,7 @@ def deserialize_guild_emojis_update_event( Other Parameters ---------------- old_emojis : typing.Optional[typing.Sequence[hikari.emojis.KnownCustomEmoji]] - The sequence of emojis or `builtins.None`. + The sequence of emojis or `None`. Returns ------- @@ -499,7 +499,7 @@ def deserialize_presence_update_event( Other Parameters ---------------- old_presence: typing.Optional[hikari.presences.MemberPresence] - The presence object or `builtins.None`. + The presence object or `None`. Returns ------- @@ -575,7 +575,7 @@ def deserialize_guild_member_update_event( Other Parameters ---------------- old_member: typing.Optional[hikari.guilds.Member] - The member object or `builtins.None`. + The member object or `None`. Returns ------- @@ -603,7 +603,7 @@ def deserialize_guild_member_remove_event( Other Parameters ---------------- old_member: typing.Optional[hikari.guilds.Member] - The member object or `builtins.None`. + The member object or `None`. Returns ------- @@ -654,7 +654,7 @@ def deserialize_guild_role_update_event( Other Parameters ---------------- old_role: typing.Optional[hikari.guilds.Role] - The role object or `builtins.None`. + The role object or `None`. Returns ------- @@ -682,7 +682,7 @@ def deserialize_guild_role_delete_event( Other Parameters ---------------- old_role: typing.Optional[hikari.guilds.Role] - The role object or `builtins.None`. + The role object or `None`. Returns ------- @@ -886,7 +886,7 @@ def deserialize_message_update_event( Other Parameters ---------------- old_message: typing.Optional[hikari.messages.PartialMessage] - The message object or `builtins.None`. + The message object or `None`. Returns ------- @@ -1046,7 +1046,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 @@ -1164,7 +1164,7 @@ def deserialize_own_user_update_event( Other Parameters ---------------- old_user: typing.Optional[hikari.users.OwnUser] - The OwnUser object or `builtins.None`. + The OwnUser object or `None`. Returns ------- @@ -1196,7 +1196,7 @@ def deserialize_voice_state_update_event( Other Parameters ---------------- 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 8e2d3464ad..1b69c40f45 100644 --- a/hikari/api/event_manager.py +++ b/hikari/api/event_manager.py @@ -81,7 +81,7 @@ class EventStream(iterators.LazyIterator[base_events.EventT], abc.ABC): See Also -------- - LazyIterator: `hikari.iterators.LazyIterator` + `hikari.iterators.LazyIterator` """ __slots__: typing.Sequence[str] = () @@ -92,7 +92,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. """ @@ -103,7 +103,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. """ @@ -119,17 +119,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))`. @@ -184,7 +184,7 @@ def consume_raw_event( Raises ------ - builtins.LookupError + LookupError If there is no consumer for the event. """ @@ -264,11 +264,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 @@ -306,11 +306,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 @@ -346,11 +346,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 @@ -368,23 +368,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 for all the - event types `event_type` will dispatch. If `builtins.False`, then + polymorphic : bool + If `True`, this will also return the listeners for all the + event types `event_type` will dispatch. 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 @@ -402,7 +396,6 @@ def listen( The event types 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 @@ -414,11 +407,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 @@ -431,18 +424,23 @@ def stream( ) -> EventStream[base_events.EventT]: """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 @@ -452,14 +450,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): @@ -480,11 +472,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 @@ -497,6 +489,9 @@ async def wait_for( ) -> base_events.EventT: """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] @@ -504,17 +499,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). @@ -526,14 +518,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 56a3e455e9..087b831949 100644 --- a/hikari/api/interaction_server.py +++ b/hikari/api/interaction_server.py @@ -52,7 +52,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`. @@ -85,31 +85,21 @@ def headers(self) -> typing.Optional[typing.MutableMapping[str, str]]: @property def payload(self) -> typing.Optional[bytes]: - """Payload to provide in the response. - - Returns - ------- - typing.Optional[builtins.bytes] - The bytes payload to respond with if applicable else `builtins.None`. - """ + """Payload to provide in the response.""" raise NotImplementedError @property 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 class InteractionServer(abc.ABC): - """Interface for an implementation of a Interactions compatible REST server.""" + """Interface for an implementation of an interactions compatible REST server.""" __slots__: typing.Sequence[str] = () @@ -119,11 +109,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 @@ -178,7 +168,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 @@ -235,18 +225,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 9068d96722..25dc3e2133 100644 --- a/hikari/api/rest.py +++ b/hikari/api/rest.py @@ -31,6 +31,7 @@ from hikari import scheduled_events from hikari import traits from hikari import undefined +from hikari.internal import deprecation if typing.TYPE_CHECKING: import datetime @@ -70,13 +71,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: @@ -84,7 +79,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. """ @@ -93,14 +88,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. @@ -127,13 +122,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 @@ -172,7 +162,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 @@ -231,32 +221,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. @@ -288,7 +278,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( @@ -345,6 +335,10 @@ async def delete_channel( ) -> channels_.PartialChannel: """Delete a channel in a guild, or close a DM. + .. note:: + For Public servers, the set 'Rules' or 'Guidelines' channels and the + 'Public Server Updates' channel cannot be deleted. + Parameters ---------- channel : hikari.snowflakes.SnowflakeishOr[hikari.channels.PartialChannel] @@ -377,10 +371,6 @@ async def delete_channel( nature, and will trigger this exception if they occur. hikari.errors.InternalServerError If an internal error occurs on Discord while handling the request. - - !!! note - For Public servers, the set 'Rules' or 'Guidelines' channels and the - 'Public Server Updates' channel cannot be deleted. """ @abc.abstractmethod @@ -394,7 +384,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. @@ -407,21 +397,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 ------ @@ -459,6 +446,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] @@ -470,14 +461,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 @@ -563,13 +550,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 @@ -703,13 +690,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. @@ -717,17 +704,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. @@ -768,9 +755,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 -------- @@ -783,7 +772,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. @@ -819,11 +808,6 @@ def trigger_typing( 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 any of the above types. """ @abc.abstractmethod @@ -959,6 +943,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] @@ -988,14 +980,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). @@ -1016,13 +1003,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( @@ -1103,7 +1084,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 @@ -1118,6 +1099,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. @@ -1130,63 +1137,36 @@ 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. 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 @@ -1194,12 +1174,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 @@ -1307,6 +1287,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] @@ -1318,9 +1318,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 @@ -1334,67 +1334,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 @@ -1409,33 +1409,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 @@ -1443,10 +1416,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 @@ -1532,21 +1505,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 @@ -1555,24 +1514,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 @@ -1595,7 +1568,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 @@ -1650,7 +1623,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 @@ -1702,7 +1675,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 @@ -1762,7 +1735,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 @@ -1851,6 +1824,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] @@ -1859,7 +1840,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 @@ -1874,11 +1855,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 @@ -1901,11 +1877,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 @@ -1931,7 +1902,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. @@ -1982,7 +1953,7 @@ async def fetch_webhook( Other Parameters ---------------- - token : hikari.undefined.UndefinedOr[builtins.str] + token : hikari.undefined.UndefinedOr[str] If provided, the webhook token that will be used to fetch the webhook instead of the token the client was initialized with. @@ -2119,17 +2090,17 @@ async def edit_webhook( Other Parameters ---------------- - token : hikari.undefined.UndefinedOr[builtins.str] + token : hikari.undefined.UndefinedOr[str] If provided, the webhook 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. @@ -2179,7 +2150,7 @@ async def delete_webhook( Other Parameters ---------------- - token : hikari.undefined.UndefinedOr[builtins.str] + token : hikari.undefined.UndefinedOr[str] If provided, the webhook token that will be used to delete the webhook instead of the token the client was initialized with. @@ -2235,18 +2206,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 @@ -2259,15 +2234,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. @@ -2280,73 +2281,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 @@ -2354,11 +2318,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 @@ -2400,7 +2364,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 @@ -2460,12 +2424,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 @@ -2473,9 +2452,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 @@ -2489,86 +2468,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 @@ -2576,10 +2533,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 @@ -2622,7 +2579,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 @@ -2653,7 +2610,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 @@ -2707,7 +2664,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. @@ -2743,7 +2700,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. @@ -2816,10 +2773,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 @@ -2832,8 +2789,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 @@ -2877,11 +2834,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 oldest 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 @@ -2893,11 +2858,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 @@ -2917,11 +2877,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 @@ -2996,7 +2951,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`. @@ -3029,7 +2984,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`. @@ -3071,9 +3026,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 @@ -3116,11 +3071,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 @@ -3162,7 +3117,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 @@ -3172,14 +3127,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 @@ -3221,9 +3176,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 @@ -3261,7 +3216,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` @@ -3269,7 +3224,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 @@ -3280,22 +3235,22 @@ async def add_user_to_guild( Other Parameters ---------------- - nickname : hikari.undefined.UndefinedOr[builtins.str] + nickname : 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. - nick : hikari.undefined.UndefinedOr[builtins.str] + nick : hikari.undefined.UndefinedOr[str] Deprecated alias for `nickname`. roles : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole]] If provided, the roles to add to the user when he joins the 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. @@ -3303,7 +3258,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 @@ -3410,6 +3365,14 @@ def fetch_audit_log( ) -> iterators.LazyIterator[audit_logs.AuditLog]: """Fetch pages of 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] @@ -3425,7 +3388,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 @@ -3433,11 +3396,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 @@ -3459,11 +3417,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 @@ -3564,7 +3517,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. @@ -3576,7 +3529,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. @@ -3634,13 +3587,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. @@ -3695,7 +3648,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. @@ -3890,23 +3843,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. @@ -3965,13 +3918,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. @@ -4026,7 +3979,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. @@ -4057,9 +4010,17 @@ async def delete_sticker( def guild_builder(self, name: str, /) -> special_endpoints.GuildBuilder: """Make a guild builder to create a guild with. + Notes + ----- + This endpoint can only be used by bots in less than 10 guilds. + + 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. + Parameters ---------- - name : builtins.str + name : str The new guilds name. Returns @@ -4068,9 +4029,6 @@ 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 - This endpoint can only be used by bots in less than 10 guilds. - Raises ------ hikari.errors.BadRequestError @@ -4092,15 +4050,9 @@ def guild_builder(self, name: str, /) -> special_endpoints.GuildBuilder: 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 - `hikari.api.special_endpoints.GuildBuilder.create` is called. - Invoking this function itself will not raise any of - the above types. - See Also -------- - Guild builder: `hikari.api.special_endpoints.GuildBuilder` + `hikari.api.special_endpoints.GuildBuilder` """ @abc.abstractmethod @@ -4156,7 +4108,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 @@ -4223,7 +4175,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. @@ -4242,7 +4194,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 @@ -4256,9 +4208,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. @@ -4388,17 +4340,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 @@ -4409,7 +4361,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. @@ -4466,17 +4418,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 @@ -4487,7 +4439,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. @@ -4545,34 +4497,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. @@ -4629,32 +4581,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. @@ -4707,16 +4659,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. @@ -4763,7 +4715,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. @@ -4839,6 +4791,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] @@ -4850,11 +4816,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 @@ -4874,26 +4835,13 @@ 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 async def fetch_my_member(self, guild: snowflakes.SnowflakeishOr[guilds.PartialGuild]) -> guilds.Member: """Fetch the Oauth token's associated member in a guild. - !!! 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`. @@ -4963,7 +4911,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. @@ -4999,44 +4947,44 @@ async def edit_member( Other Parameters ---------------- - nickname : hikari.undefined.UndefinedNoneOr[builtins.str] - If provided, the new nick for the member. If `builtins.None`, + nickname : hikari.undefined.UndefinedNoneOr[str] + If provided, the new nick for the member. If `None`, will remove the members nick. Requires the `MANAGE_NICKNAMES` permission. - nick : hikari.undefined.UndefinedOr[builtins.str] + nick : hikari.undefined.UndefinedOr[str] Deprecated alias for `nickname`. roles : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole]] 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. communication_disabled_until : hikari.undefined.UndefinedNoneOr[datetime.datetime] If provided, the datetime when the timeout (disable communication) - of the member expires, up to 28 days in the future, or `builtins.None` + of the member expires, up to 28 days in the future, or `None` to remove the timeout from the member. Requires the `MODERATE_MEMBERS` permission. - 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. @@ -5088,9 +5036,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. @@ -5126,6 +5074,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, @@ -5136,21 +5085,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. @@ -5202,7 +5148,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. @@ -5254,7 +5200,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. @@ -5302,7 +5248,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. @@ -5361,10 +5307,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. @@ -5425,7 +5371,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. @@ -5613,7 +5559,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 @@ -5624,15 +5570,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. @@ -5643,7 +5589,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 @@ -5682,7 +5628,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 @@ -5737,7 +5683,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. @@ -5745,16 +5691,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. @@ -5765,7 +5711,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 @@ -5849,7 +5795,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 @@ -5859,7 +5805,7 @@ async def estimate_guild_prune_count( Returns ------- - builtins.int + int The estimated guild prune count. Raises @@ -5885,7 +5831,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( @@ -5907,9 +5853,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]] @@ -5917,15 +5863,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 ------ @@ -6131,11 +6077,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. @@ -6222,17 +6168,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. @@ -6328,7 +6274,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 @@ -6372,9 +6318,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 @@ -6388,7 +6334,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 @@ -6472,14 +6418,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 @@ -6517,7 +6463,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 @@ -6598,7 +6544,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 @@ -6639,10 +6585,10 @@ def command_builder(self, name: str, description: str) -> special_endpoints.Slas 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. @@ -6662,10 +6608,10 @@ def slash_command_builder( 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 if this is a slash command. This should be inclusively between 1-100 characters in length. @@ -6687,7 +6633,7 @@ def context_menu_command_builder( ---------- type : commands.CommandType The commands's type. - name : builtins.str + name : str The command's name. Returns @@ -6818,10 +6764,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. guild : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] @@ -6833,11 +6779,11 @@ async def create_application_command( ---------------- 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 ------- @@ -6886,10 +6832,10 @@ async def create_slash_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. @@ -6901,11 +6847,11 @@ async def create_slash_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 ------- @@ -6953,11 +6899,11 @@ async def create_context_menu_command( ---------- application: hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] Object or ID of the application to create a command for. - type : typing.Union[hikari.commands.CommandType, builtins.int] + type : typing.Union[hikari.commands.CommandType, int] The type of menu command to make. Only USER and MESSAGE are valid here. - name : builtins.str + name : str The command's name. This should match the regex `^[\w-]{1,32}$` in Unicode mode and be lowercase. @@ -6967,11 +6913,11 @@ async def create_context_menu_command( Object or ID of the specific guild this should be made for. If left as `hikari.undefined.UNDEFINED` then this call will create a global command rather than a guild specific one. - 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 ------- @@ -7012,7 +6958,7 @@ async def set_application_commands( ) -> typing.Sequence[commands.PartialCommand]: """Set the commands for an application. - !!! warning + .. warning:: Any existing commands not included in the provided commands array will be deleted. @@ -7087,10 +7033,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]] @@ -7273,7 +7219,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. @@ -7287,7 +7233,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 @@ -7328,7 +7274,7 @@ async def set_application_command_permissions( ) -> commands.GuildCommandPermissions: """Set permissions for a specific command. - !!! note + .. note:: This overwrites any previously set permissions. Parameters @@ -7378,7 +7324,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 @@ -7407,7 +7353,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 @@ -7426,7 +7372,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 @@ -7483,7 +7429,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 @@ -7493,9 +7439,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 @@ -7504,7 +7450,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 @@ -7525,28 +7471,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 @@ -7555,10 +7501,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 @@ -7611,11 +7557,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 @@ -7623,9 +7584,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 @@ -7636,75 +7597,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 @@ -7712,10 +7659,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 @@ -7753,7 +7700,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 @@ -7790,7 +7737,7 @@ async def create_autocomplete_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. Other Parameters @@ -7837,13 +7784,16 @@ async def fetch_scheduled_event( event: snowflakes.SnowflakeishOr[scheduled_events.ScheduledEvent], /, ) -> scheduled_events.ScheduledEvent: - """Fetch a channel. + """Fetch a scheduled event. Parameters ---------- - channel : hikari.snowflakes.SnowflakeishOr[hikari.channels.PartialChannel] - The channel to fetch. This may be the object or the ID of an - existing channel. + guild : hikari.snowflakes.SnowflakeishOr[hikari.channels.PartialGuild] + The guild the event bellongs to. This may be the object or the + ID of an existing guild. + event : hikari.snowflakes.SnowflakeishOr[hikari.scheduled_events.ScheduledEvent] + The event to fetch. This may be the object or the + ID of an existing event. Returns ------- @@ -7882,7 +7832,7 @@ async def fetch_scheduled_events( ) -> typing.Sequence[scheduled_events.ScheduledEvent]: """Fetch the scheduled events for a guild. - !!! note + .. note:: `VOICE` and `STAGE_CHANNEL` events are only included if the bot has `VOICE` or `STAGE_CHANNEL` permissions in the associated channel. @@ -7959,7 +7909,7 @@ async def create_stage_event( The event's privacy level. This effects who can view and subscribe to the event. - 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. @@ -8038,7 +7988,7 @@ async def create_voice_event( The event's privacy level. This effects who can view and subscribe to the event. - 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. @@ -8117,7 +8067,7 @@ async def create_external_event( The event's privacy level. This effects who can view and subscribe to the event. - 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. @@ -8215,7 +8165,7 @@ async def edit_scheduled_event( `SCHEDULED` events can be set to `ACTIVE` and `CANCELED`. `ACTIVE` events can only be set to `COMPLETED`. - 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. @@ -8306,6 +8256,14 @@ def fetch_scheduled_event_users( ) -> iterators.LazyIterator[scheduled_events.ScheduledEventUser]: """Asynchronously iterate over the users who're subscribed to a scheduled event. + 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] @@ -8315,10 +8273,10 @@ def fetch_scheduled_event_users( Other Parameters ---------------- - newest_first : builtins.bool + newest_first : bool Whether to fetch the newest first or the oldest 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 @@ -8330,11 +8288,6 @@ def fetch_scheduled_event_users( hikari.iterators.LazyIterator[hikari.scheduled_events.ScheduledEventUser] 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 @@ -8356,9 +8309,4 @@ def fetch_scheduled_event_users( 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. """ diff --git a/hikari/api/shard.py b/hikari/api/shard.py index 4f5fcffe23..cb7efc70a5 100644 --- a/hikari/api/shard.py +++ b/hikari/api/shard.py @@ -77,7 +77,7 @@ def heartbeat_latency(self) -> float: Returns ------- - builtins.float + float Heartbeat latency measured in seconds. If the information is not yet available, then this will be `float('nan')` instead. """ @@ -89,7 +89,7 @@ def id(self) -> int: Returns ------- - builtins.int + int The integer 0-based shard ID. """ @@ -107,12 +107,12 @@ def intents(self) -> intents_.Intents: @property @abc.abstractmethod def is_alive(self) -> bool: - """Return `builtins.True` if the shard is alive and connected. + """Return `True` if the shard is alive and connected. Returns ------- - builtins.bool - `builtins.True` if connected, or `builtins.False` if not. + bool + `True` if connected, or `False` if not. """ @property @@ -122,7 +122,7 @@ def shard_count(self) -> int: Returns ------- - builtins.int + int A number of shards greater than or equal to 1. """ @@ -175,8 +175,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 +202,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 @@ -233,18 +233,18 @@ 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 + .. note:: To request the full list of members, set `query` to `""` (empty string) and `limit` to `0`. diff --git a/hikari/api/special_endpoints.py b/hikari/api/special_endpoints.py index ce83ebac32..a4f1028ba6 100644 --- a/hikari/api/special_endpoints.py +++ b/hikari/api/special_endpoints.py @@ -81,7 +81,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. @@ -114,7 +114,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 @@ -151,15 +151,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 @@ -189,7 +189,7 @@ def name(self) -> str: Returns ------- - builtins.str + str The guild name. """ @@ -257,7 +257,7 @@ def verification_level(self) -> undefined.UndefinedOr[typing.Union[guilds.GuildV Returns ------- - hikari.undefined.UndefinedOr[typing.Union[hikari.guilds.GuildVerificationLevel, builtins.int]] + hikari.undefined.UndefinedOr[typing.Union[hikari.guilds.GuildVerificationLevel, int]] The verification level required to join the guild, if overwritten. """ @@ -307,12 +307,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 @@ -323,11 +323,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. @@ -342,9 +342,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. @@ -365,12 +365,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. @@ -404,19 +404,19 @@ 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. @@ -456,28 +456,28 @@ 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]] @@ -513,26 +513,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]] @@ -562,7 +562,7 @@ def type(self) -> typing.Union[int, base_interactions.ResponseType]: Returns ------- - typing.Union[builtins.int, hikari.interactions.base_interactions.ResponseType] + typing.Union[int, hikari.interactions.base_interactions.ResponseType] The type of response this is. """ @@ -606,13 +606,13 @@ def type(self) -> base_interactions.DeferredResponseTypesT: 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] + typing.Union[hikari.undefined.UndefinedType, int, hikari.messages.MessageFlag] The message flags this response should have if set else `hikari.undefined.UNDEFINED`. """ @@ -621,12 +621,12 @@ def flags(self) -> typing.Union[undefined.UndefinedType, int, messages.MessageFl 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 @@ -706,7 +706,7 @@ def content(self) -> undefined.UndefinedOr[str]: Returns ------- - hikari.undefined.UndefinedOr[builtins.str] + hikari.undefined.UndefinedOr[str] The response's message content, if set. """ @@ -715,13 +715,13 @@ def content(self) -> undefined.UndefinedOr[str]: 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] + typing.Union[hikari.undefined.UndefinedType, int, hikari.messages.MessageFlag] The message flags this response should have if set else `hikari.undefined.UNDEFINED`. """ @@ -733,7 +733,7 @@ def is_tts(self) -> undefined.UndefinedOr[bool]: Returns ------- - builtins.bool + bool Whether this response's content should be treated as text-to-speech. If left as `hikari.undefined.UNDEFINED` then this will be disabled. """ @@ -745,7 +745,7 @@ def mentions_everyone(self) -> undefined.UndefinedOr[bool]: Returns ------- - hikari.undefined.UndefinedOr[builtins.bool] + hikari.undefined.UndefinedOr[bool] Whether @everyone mentions should be enabled for this response. If left as `hikari.undefined.UNDEFINED` then they will be disabled. """ @@ -759,9 +759,9 @@ def role_mentions( Returns ------- - hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], builtins.bool]] + 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. """ # noqa: E501 - Line too long @@ -774,9 +774,9 @@ def user_mentions( Returns ------- - hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], builtins.bool]] + 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. """ # noqa: E501 - Line too long @@ -831,7 +831,7 @@ 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 @@ -844,12 +844,12 @@ def set_content(self: _T, content: undefined.UndefinedOr[str], /) -> _T: 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 @@ -878,7 +878,7 @@ 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 @@ -899,9 +899,9 @@ 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 @@ -922,9 +922,9 @@ 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 @@ -944,13 +944,13 @@ 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 + str The name to set for this command. """ @@ -981,11 +981,11 @@ def id(self) -> undefined.UndefinedOr[snowflakes.Snowflake]: def default_permission(self) -> undefined.UndefinedOr[bool]: """Whether the command should be enabled by default (without any permissions). - Defaults to `builtins.bool`. + Defaults to `bool`. Returns ------- - undefined.UndefinedOr[builtins.bool] + undefined.UndefinedOr[bool] Whether the command should be enabled by default (without any permissions). """ @@ -1010,7 +1010,7 @@ 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 @@ -1076,12 +1076,12 @@ class SlashCommandBuilder(CommandBuilder): 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 + str The description to set for this command. """ @@ -1100,7 +1100,7 @@ def options(self) -> typing.Sequence[commands.CommandOption]: 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 @@ -1218,7 +1218,7 @@ def style(self) -> typing.Union[messages.ButtonStyle, int]: Returns ------- - typing.Union[builtins.int, hikari.messages.ButtonStyle] + typing.Union[int, hikari.messages.ButtonStyle] The button's style. """ @@ -1229,7 +1229,7 @@ def emoji(self) -> typing.Union[snowflakes.Snowflakeish, emojis.Emoji, str, unde Returns ------- - typing.Union[hikari.snowflakes.Snowflakeish, hikari.emojis.Emoji, builtins.str, hikari.undefined.UndefinedType] + typing.Union[hikari.snowflakes.Snowflakeish, hikari.emojis.Emoji, str, hikari.undefined.UndefinedType] Object or ID or raw string of the emoji which should be displayed on this button if set. """ @@ -1239,13 +1239,13 @@ 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] + hikari.undefined.UndefinedOr[str] Text label which should appear on this button. """ @@ -1254,12 +1254,12 @@ def label(self) -> undefined.UndefinedOr[str]: def is_disabled(self) -> bool: """Whether the button should be marked as disabled. - !!! note - Defaults to `builtins.False`. + .. note:: + Defaults to `False`. Returns ------- - builtins.bool + bool Whether the button should be marked as disabled. """ @@ -1271,7 +1271,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. @@ -1287,7 +1287,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. @@ -1338,7 +1338,7 @@ def url(self) -> str: Returns ------- - builtins.str + str Url this button should link to when pressed. """ @@ -1355,7 +1355,7 @@ def custom_id(self) -> str: Returns ------- - builtins.str + str Developer set custom ID used for identifying interactions with this button. """ @@ -1372,7 +1372,7 @@ def label(self) -> str: Returns ------- - builtins.str + str User-facing name of the option. """ @@ -1383,7 +1383,7 @@ def value(self) -> str: Returns ------- - builtins.str + str Developer-defined value of the option. """ @@ -1394,7 +1394,7 @@ def description(self) -> undefined.UndefinedOr[str]: Returns ------- - hikari.undefined.UndefinedOr[builtins.str] + hikari.undefined.UndefinedOr[str] Description of the option, if set. """ @@ -1405,7 +1405,7 @@ def emoji(self) -> typing.Union[snowflakes.Snowflakeish, emojis.Emoji, str, unde Returns ------- - typing.Union[hikari.snowflakes.Snowflakeish, hikari.emojis.Emoji, builtins.str, hikari.undefined.UndefinedType] + typing.Union[hikari.snowflakes.Snowflakeish, hikari.emojis.Emoji, str, hikari.undefined.UndefinedType] Object or ID or raw string of the emoji which should be displayed on this option if set. """ @@ -1415,11 +1415,11 @@ 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`. + Defaults to `False`. Returns ------- - builtins.bool + bool Whether this option should be marked as selected by default. """ @@ -1429,7 +1429,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. @@ -1447,7 +1447,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. @@ -1463,7 +1463,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 @@ -1497,7 +1497,7 @@ def custom_id(self) -> str: Returns ------- - builtins.str + str Developer set custom ID used for identifying interactions with this menu. """ @@ -1506,12 +1506,12 @@ def custom_id(self) -> str: def is_disabled(self) -> bool: """Whether the select menu should be marked as disabled. - !!! note - Defaults to `builtins.False`. + .. note:: + Defaults to `False`. Returns ------- - builtins.bool + bool Whether the select menu should be marked as disabled. """ @@ -1533,7 +1533,7 @@ def placeholder(self) -> undefined.UndefinedOr[str]: Returns ------- - hikari.undefined.UndefinedOr[builtins.str] + hikari.undefined.UndefinedOr[str] Placeholder text to display when no options are selected, if defined. """ @@ -1548,7 +1548,7 @@ def min_values(self) -> int: Returns ------- - builtins.str + str Minimum number of options which must be chosen. """ @@ -1563,7 +1563,7 @@ def max_values(self) -> int: Returns ------- - builtins.str + str Maximum number of options which can be chosen. """ @@ -1573,9 +1573,9 @@ def add_option(self: _SelectMenuBuilderT, label: str, value: str, /) -> SelectOp 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 @@ -1589,11 +1589,11 @@ def add_option(self: _SelectMenuBuilderT, label: str, value: str, /) -> SelectOp 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 @@ -1608,7 +1608,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. @@ -1622,13 +1622,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 @@ -1641,13 +1641,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 @@ -1691,7 +1691,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. @@ -1734,9 +1734,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. @@ -1757,7 +1757,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 93b4e1693d..f08f20de5d 100644 --- a/hikari/api/voice.py +++ b/hikari/api/voice.py @@ -99,12 +99,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` @@ -171,12 +171,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. @@ -203,7 +203,7 @@ def guild_id(self) -> snowflakes.Snowflake: @property @abc.abstractmethod def is_alive(self) -> bool: - """Return `builtins.True` if the connection is alive.""" + """Return `True` if the connection is alive.""" @property @abc.abstractmethod diff --git a/hikari/applications.py b/hikari/applications.py index 3a74ff49d9..431fcf2df0 100644 --- a/hikari/applications.py +++ b/hikari/applications.py @@ -108,31 +108,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. """ @@ -155,14 +155,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. """ @@ -175,7 +175,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. """ @@ -185,14 +185,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. """ @@ -200,14 +200,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. """ @@ -217,7 +217,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. """ @@ -253,7 +253,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. """ @@ -264,19 +264,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.""" @@ -290,7 +290,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.""" @@ -318,7 +318,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. """ @@ -414,7 +414,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) @@ -432,13 +432,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]: @@ -446,21 +440,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). """ @@ -494,13 +488,7 @@ class InviteApplication(guilds.PartialApplication): @property def cover_image_url(self) -> typing.Optional[files.URL]: - """Rich presence cover image URL for this application, if set. - - Returns - ------- - typing.Optional[hikari.files.URL] - The URL, or `builtins.None` if no cover image exists. - """ + """Rich presence cover image URL for this application, if set.""" return self.make_cover_image_url() def make_cover_image_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optional[files.URL]: @@ -508,21 +496,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). """ @@ -549,10 +537,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.""" @@ -569,7 +557,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`. """ cover_image_hash: typing.Optional[str] = attr.field(eq=False, hash=False, repr=False) @@ -583,13 +571,7 @@ class Application(guilds.PartialApplication): @property def cover_image_url(self) -> typing.Optional[files.URL]: - """Rich presence cover image URL for this application, if set. - - Returns - ------- - typing.Optional[hikari.files.URL] - The URL, or `builtins.None` if no cover image exists. - """ + """Rich presence cover image URL for this application, if set.""" return self.make_cover_image_url() def make_cover_image_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optional[files.URL]: @@ -597,21 +579,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). """ @@ -636,15 +618,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) @@ -709,14 +691,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`. """ @@ -753,7 +735,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 0188521e23..b8a1bcc084 100644 --- a/hikari/channels.py +++ b/hikari/channels.py @@ -223,16 +223,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): @@ -360,7 +360,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. """ @@ -383,6 +383,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]] @@ -408,7 +414,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). @@ -419,13 +425,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: @@ -496,7 +496,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 @@ -511,6 +511,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. @@ -523,60 +549,33 @@ 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). 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 @@ -599,10 +598,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( @@ -636,7 +635,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. @@ -759,18 +758,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 @@ -782,21 +770,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) @@ -809,7 +808,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. """ @@ -835,7 +834,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. @@ -856,7 +855,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: @@ -875,21 +874,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: @@ -928,29 +927,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}>" @@ -958,7 +953,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) @@ -971,7 +966,7 @@ def get_guild(self) -> typing.Optional[guilds.GatewayGuild]: Returns ------- typing.Optional[hikari.guilds.Guild] - The linked guild object or `builtins.None` if it's not cached. + The linked guild object or `None` if it's not cached. """ if not isinstance(self.app, traits.CacheAware): return None @@ -1038,13 +1033,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 @@ -1137,32 +1132,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. @@ -1194,7 +1189,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, @@ -1238,7 +1233,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. """ @@ -1248,7 +1243,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. @@ -1257,8 +1252,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! """ @@ -1273,7 +1268,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. """ @@ -1281,8 +1276,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! """ @@ -1297,7 +1292,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. """ @@ -1322,7 +1317,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 cbdb931783..d41381dcbb 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 this 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 012be20fa7..a456f99068 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 618a06ab88..a21e6a4374 100644 --- a/hikari/commands.py +++ b/hikari/commands.py @@ -131,7 +131,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. """ @@ -139,7 +139,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. """ @@ -149,7 +149,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. """ @@ -162,7 +162,7 @@ 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. """ autocomplete: bool = attr.field(default=False, repr=False) @@ -171,15 +171,15 @@ class CommandOption: 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.FLOAT`. + This will be `int` if the type of the option is `hikari.commands.OptionType.INTEGER` + and `float` if the type is `hikari.commands.OptionType.FLOAT`. """ 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.FLOAT`. + This will be `int` if the type of the option is `hikari.commands.OptionType.INTEGER` + and `float` if the type is `hikari.commands.OptionType.FLOAT`. """ @@ -203,7 +203,7 @@ class PartialCommand(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. """ @@ -211,14 +211,14 @@ class PartialCommand(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) @@ -274,10 +274,10 @@ async def edit( 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]] @@ -398,7 +398,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 @@ -450,7 +450,7 @@ class SlashCommand(PartialCommand): None if this command is not a slash command. - !!! note + .. note:: This will be inclusively between 1-100 characters in length. """ diff --git a/hikari/embeds.py b/hikari/embeds.py index 24321c523b..b8b370f943 100644 --- a/hikari/embeds.py +++ b/hikari/embeds.py @@ -75,7 +75,7 @@ def url(self) -> str: Returns ------- - typing.Optional[builtins.str] + typing.Optional[str] The URL of this embed resource. """ return self.resource.url @@ -86,7 +86,7 @@ def filename(self) -> str: Returns ------- - typing.Optional[builtins.str] + typing.Optional[str] The file name of this embed resource. """ return self.resource.filename @@ -103,10 +103,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 +118,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. @@ -133,9 +133,9 @@ def proxy_url(self) -> typing.Optional[str]: Returns ------- - typing.Optional[builtins.str] + typing.Optional[str] The proxied URL of this embed resource if applicable, else - `builtins.None`. + `None`. """ return self.proxy_resource.url if self.proxy_resource else None @@ -146,9 +146,9 @@ def proxy_filename(self) -> typing.Optional[str]: Returns ------- - typing.Optional[builtins.str] + typing.Optional[str] The file name of the proxied version of this embed resource if - applicable, else `builtins.None`. + applicable, else `None`. """ return self.proxy_resource.filename if self.proxy_resource else None @@ -161,10 +161,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 +172,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 +194,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 +215,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 +238,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,7 +267,7 @@ 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. """ @@ -386,11 +386,11 @@ def __init__( def title(self) -> typing.Optional[str]: """Return the title of the embed. - This will be `builtins.None` if not set. + This will be `None` if not set. Returns ------- - typing.Optional[builtins.str] + typing.Optional[str] The title of the embed. """ return self._title @@ -403,11 +403,11 @@ 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. + This will be `None` if not set. Returns ------- - typing.Optional[builtins.str] + typing.Optional[str] The description of the embed. """ return self._description @@ -420,11 +420,11 @@ 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. + This will be `None` if not set. Returns ------- - typing.Optional[builtins.str] + typing.Optional[str] The URL of the embed title """ return self._url @@ -437,7 +437,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. + This will be `None` if not set. Returns ------- @@ -457,7 +457,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. + This will be `None` if not set. Returns ------- @@ -477,14 +477,14 @@ 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. + This will be `None` if not set. Returns ------- typing.Optional[datetime.datetime] The timestamp set on the embed. - !!! 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,7 +597,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. + Will be `None` if not set. typing.Optional[EmbedFooter] The footer of the embed. @@ -608,12 +608,12 @@ 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 +622,12 @@ 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. + Will be `None` if not set. typing.Optional[EmbedImage] The thumbnail of the embed. - !!! note + .. note:: Use `set_thumbnail` to update this value. """ return self._thumbnail @@ -636,14 +636,14 @@ 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. + Will be `None` if not set. Returns ------- typing.Optional[EmbedVideo] The video 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 @@ -655,14 +655,14 @@ 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 +674,14 @@ 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. + Will be `None` if not set. Returns ------- typing.Optional[EmbedAuthor] The author of the embed. - !!! note + .. note:: Use `set_author` to update this value. """ return self._author @@ -690,7 +690,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 +707,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 +725,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 +753,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 +768,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 +805,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 +814,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 +845,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 +889,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 +927,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 +939,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 +970,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)` """ @@ -997,7 +997,7 @@ def total_length(self) -> int: Returns ------- - builtins.int + int The total character count of this embed, including title, description, fields, footer, and author combined. """ diff --git a/hikari/emojis.py b/hikari/emojis.py index 53f4ccb70f..d4e7e5c35f 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,7 @@ def url(self) -> str: Example ------- - https://github.com/twitter/twemoji/raw/master/assets/72x72/1f004.png + """ return _TWEMOJI_PNG_BASE_URL + self.filename @@ -217,7 +217,7 @@ def parse(cls, string: str, /) -> UnicodeEmoji: Parameters ---------- - string : builtins.str + string : str The emoji object to parse. Returns @@ -248,7 +248,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 @@ -298,7 +298,7 @@ def parse(cls, string: str, /) -> CustomEmoji: Parameters ---------- - string : builtins.str + string : str The emoji mention to parse. Returns @@ -308,7 +308,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 +346,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 +360,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 470bb553ab..5b22385679 100644 --- a/hikari/errors.py +++ b/hikari/errors.py @@ -74,7 +74,7 @@ class HikariError(RuntimeError): Any exceptions should derive from this. - !!! note + .. note:: You should never initialize this exception directly. """ @@ -87,7 +87,7 @@ class HikariWarning(RuntimeWarning): Any warnings should derive from this. - !!! note + .. note:: You should never initialize this warning directly. """ @@ -169,7 +169,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 @@ -190,25 +190,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. """ @@ -429,7 +429,7 @@ def remaining(self) -> typing.Literal[0]: Returns ------- - builtins.int + int The number of requests remaining. Always `0`. """ # noqa: D401 - Imperative mood return 0 @@ -475,7 +475,7 @@ def percentage_completion(self) -> float: Returns ------- - builtins.float + float A percentage completion between 0 and 100 inclusive. """ deleted = len(self.messages_deleted) diff --git a/hikari/events/base_events.py b/hikari/events/base_events.py index fd34e912eb..2f947ea057 100644 --- a/hikari/events/base_events.py +++ b/hikari/events/base_events.py @@ -166,7 +166,7 @@ def decorator(cls: _T) -> _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" @@ -196,8 +196,8 @@ def is_no_recursive_throw_event(obj: typing.Union[_T, typing.Type[_T]]) -> bool: class ExceptionEvent(Event, typing.Generic[EventT]): """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 @@ -209,7 +209,7 @@ class ExceptionEvent(Event, typing.Generic[EventT]): Returns ------- - builtins.Exception + Exception Exception that was raised in the event handler. """ @@ -245,7 +245,7 @@ def shard(self) -> typing.Optional[gateway_shard.GatewayShard]: 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 + 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) @@ -259,7 +259,7 @@ def exc_info(self) -> typing.Tuple[typing.Type[Exception], Exception, typing.Opt Returns ------- - builtins.tuple[typing.Type[Exception], Exception, typing.Optional[types.TracebackType]] + 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. """ diff --git a/hikari/events/channel_events.py b/hikari/events/channel_events.py index 0f1dc039d1..b2b2bae788 100644 --- a/hikari/events/channel_events.py +++ b/hikari/events/channel_events.py @@ -87,7 +87,7 @@ def channel_id(self) -> snowflakes.Snowflake: 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. @@ -142,13 +142,13 @@ def guild_id(self) -> snowflakes.Snowflake: 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 @@ -190,13 +190,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 @@ -206,7 +206,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. @@ -252,7 +252,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. @@ -336,7 +336,7 @@ 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) @@ -414,14 +414,14 @@ 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. + or `None` if no pins are available. """ @abc.abstractmethod @@ -471,13 +471,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) @@ -584,7 +584,7 @@ def code(self) -> str: Returns ------- - builtins.str + str The invite code. """ @@ -684,7 +684,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 2e8c5bd20e..b3f71e6c05 100644 --- a/hikari/events/guild_events.py +++ b/hikari/events/guild_events.py @@ -107,12 +107,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 +141,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. @@ -216,14 +216,14 @@ class GuildAvailableEvent(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. Returns ------- - typing.Optional[builtins.str] + typing.Optional[str] The nonce used to request the member chunks. """ @@ -244,7 +244,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 +277,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 +315,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,7 +352,7 @@ 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() @@ -500,7 +500,7 @@ 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() @@ -554,7 +554,7 @@ def id(self) -> snowflakes.Snowflake: 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 @@ -677,7 +677,7 @@ 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() @@ -695,7 +695,7 @@ 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. @@ -738,7 +738,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 ae6786e4cc..750015b071 100644 --- a/hikari/events/interaction_events.py +++ b/hikari/events/interaction_events.py @@ -47,13 +47,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 0f76a4be29..f5ae19c4de 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 8b91268ebe..8a67c45005 100644 --- a/hikari/events/member_events.py +++ b/hikari/events/member_events.py @@ -90,13 +90,13 @@ def user_id(self) -> snowflakes.Snowflake: 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 @@ -148,7 +148,7 @@ 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() @@ -189,5 +189,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 e108a82ed7..4b4b220657 100644 --- a/hikari/events/message_events.py +++ b/hikari/events/message_events.py @@ -133,8 +133,8 @@ def content(self) -> typing.Optional[str]: Returns ------- - typing.Optional[builtins.str] - The content of the message, if present. This may be `builtins.None` + typing.Optional[str] + The content of the message, if present. This may be `None` or an empty string (or any falsy value) if no content is present (e.g. if only an embed was sent). """ @@ -153,23 +153,23 @@ def embeds(self) -> typing.Sequence[embeds_.Embed]: @property def is_bot(self) -> bool: - """Return `builtins.True` if the message is from a bot. + """Return `True` if the message is from a bot. Returns ------- - builtins.bool - `builtins.True` if from a bot, or `builtins.False` otherwise. + bool + `True` if from a bot, or `False` otherwise. """ return self.message.author.is_bot @property def is_human(self) -> bool: - """Return `builtins.True` if the message was created by a human. + """Return `True` if the message was created by a human. Returns ------- - builtins.bool - `builtins.True` if from a human user, or `builtins.False` otherwise. + bool + `True` if from a human user, or `False` otherwise. """ # Not second-guessing some weird edge case will occur in the future with this, # so I am being safe rather than sorry. @@ -177,12 +177,12 @@ def is_human(self) -> bool: @property def is_webhook(self) -> bool: - """Return `builtins.True` if the message was created by a webhook. + """Return `True` if the message was created by a webhook. Returns ------- - builtins.bool - `builtins.True` if from a webhook, or `builtins.False` otherwise. + bool + `True` if from a webhook, or `False` otherwise. """ return self.message.webhook_id is not None @@ -243,7 +243,7 @@ def member(self) -> typing.Optional[guilds.Member]: ------- typing.Optional[hikari.guilds.Member] The member object of the user that sent the message or - `builtins.None` if sent by a webhook. + `None` if sent by a webhook. """ return self.message.member @@ -268,7 +268,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 +282,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 +290,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 +331,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. """ @@ -373,8 +373,8 @@ def content(self) -> undefined.UndefinedNoneOr[str]: Returns ------- - hikari.undefined.UndefinedNoneOr[builtins.str] - The content of the message, if present. This may be `builtins.None` + hikari.undefined.UndefinedNoneOr[str] + The content of the message, if present. This may be `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. @@ -395,12 +395,12 @@ def embeds(self) -> undefined.UndefinedOr[typing.Sequence[embeds_.Embed]]: @property def is_bot(self) -> undefined.UndefinedOr[bool]: - """Return `builtins.True` if the message is from a bot. + """Return `True` if the message is from a bot. Returns ------- - typing.Optional[builtins.bool] - `builtins.True` if from a bot, or `builtins.False` otherwise. + typing.Optional[bool] + `True` if from a bot, or `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 @@ -413,12 +413,12 @@ 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. + """Return `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. + typing.Optional[bool] + `True` if from a human user, or `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 @@ -436,12 +436,12 @@ 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. + """Return `True` if the message was created by a webhook. Returns ------- - builtins.bool - `builtins.True` if from a webhook, or `builtins.False` otherwise. + bool + `True` if from a webhook, or `False` otherwise. """ if (webhook_id := self.message.webhook_id) is not undefined.UNDEFINED: return webhook_id is not None @@ -477,7 +477,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 +485,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 +498,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. @@ -539,7 +539,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 +553,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 +561,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 +575,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 +583,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 +597,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 +624,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 +653,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 +668,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 +690,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 +717,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 +749,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 +764,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 c9110c5cc3..0f97c840a3 100644 --- a/hikari/events/reaction_events.py +++ b/hikari/events/reaction_events.py @@ -136,13 +136,13 @@ def user_id(self) -> snowflakes.Snowflake: 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 + .. note:: + This will be `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] + typing.Union[hikari.emojis.UnicodeEmoji, str, None] Either the string name of the custom emoji which was added or the object of the `hikari.emojis.UnicodeEmoji` which was added. """ @@ -156,7 +156,7 @@ def emoji_id(self) -> typing.Optional[snowflakes.Snowflake]: ------- typing.Optional[hikari.snowflakes.Snowflake] ID of the emoji which was added if it was a custom emoji or - `builtins.None`. + `None`. """ @property @@ -166,7 +166,7 @@ def is_animated(self) -> bool: Returns ------- - builtins.bool + bool Whether the emoji which was added is animated. """ @@ -175,14 +175,14 @@ def is_for_emoji(self, emoji: typing.Union[emojis.Emoji, str], /) -> bool: 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 @@ -210,13 +210,13 @@ def user_id(self) -> snowflakes.Snowflake: 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 + .. note:: + This will be `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] + typing.Union[hikari.emojis.UnicodeEmoji, str, None] Either the string name of the custom emoji which was removed or the object of the `hikari.emojis.UnicodeEmoji` which was removed. """ @@ -230,7 +230,7 @@ def emoji_id(self) -> typing.Optional[snowflakes.Snowflake]: ------- typing.Optional[hikari.snowflakes.Snowflake] ID of the emoji which was removed if it was a custom emoji or - `builtins.None`. + `None`. """ def is_for_emoji(self, emoji: typing.Union[emojis.Emoji, str], /) -> bool: @@ -238,14 +238,14 @@ def is_for_emoji(self, emoji: typing.Union[emojis.Emoji, str], /) -> bool: 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 @@ -269,13 +269,13 @@ class ReactionDeleteEmojiEvent(ReactionEvent, abc.ABC): 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 + .. note:: + This will be `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] + typing.Union[hikari.emojis.UnicodeEmoji, str, None] Either the string name of the custom emoji which was removed or the object of the `hikari.emojis.UnicodeEmoji` which was removed. """ @@ -289,7 +289,7 @@ def emoji_id(self) -> typing.Optional[snowflakes.Snowflake]: ------- typing.Optional[hikari.snowflakes.Snowflake] ID of the emoji which was removed if it was a custom emoji or - `builtins.None`. + `None`. """ def is_for_emoji(self, emoji: typing.Union[emojis.Emoji, str], /) -> bool: @@ -297,14 +297,14 @@ def is_for_emoji(self, emoji: typing.Union[emojis.Emoji, str], /) -> bool: 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 diff --git a/hikari/events/role_events.py b/hikari/events/role_events.py index e888f17a2b..1498d83ae2 100644 --- a/hikari/events/role_events.py +++ b/hikari/events/role_events.py @@ -117,7 +117,7 @@ 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() @@ -166,5 +166,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 3e4440e8e0..8ced43ed03 100644 --- a/hikari/events/shard_events.py +++ b/hikari/events/shard_events.py @@ -76,7 +76,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. """ @@ -140,7 +140,7 @@ class ShardReadyEvent(ShardStateEvent): Returns ------- - builtins.int + int The actual gateway version we are actively using for this protocol. """ @@ -149,7 +149,7 @@ class ShardReadyEvent(ShardStateEvent): Returns ------- - builtins.str + str The session ID for this gateway session. """ @@ -238,7 +238,7 @@ class MemberChunkEvent(ShardEvent, typing.Sequence["guilds.Member"]): Returns ------- - builtins.int + int The sequence index for this chunk. """ @@ -247,7 +247,7 @@ class MemberChunkEvent(ShardEvent, typing.Sequence["guilds.Member"]): Returns ------- - builtins.int + int Total number of chunks to be expected. """ @@ -267,7 +267,7 @@ class MemberChunkEvent(ShardEvent, typing.Sequence["guilds.Member"]): """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. + `True` while requesting the member chunks. Returns ------- @@ -282,8 +282,8 @@ class MemberChunkEvent(ShardEvent, typing.Sequence["guilds.Member"]): Returns ------- - typing.Optional[builtins.str] - The request nonce if set, or `builtins.None` otherwise. + typing.Optional[str] + The request nonce if set, or `None` otherwise. """ @typing.overload diff --git a/hikari/events/typing_events.py b/hikari/events/typing_events.py index 40756df7a3..f15af81a52 100644 --- a/hikari/events/typing_events.py +++ b/hikari/events/typing_events.py @@ -260,12 +260,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 f332b0b4fa..612e9a32fb 100644 --- a/hikari/events/user_events.py +++ b/hikari/events/user_events.py @@ -49,7 +49,7 @@ 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() diff --git a/hikari/events/voice_events.py b/hikari/events/voice_events.py index 23c7bec440..98b842e5f9 100644 --- a/hikari/events/voice_events.py +++ b/hikari/events/voice_events.py @@ -78,7 +78,7 @@ 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) @@ -124,24 +124,24 @@ class VoiceServerUpdateEvent(VoiceEvent): Returns ------- - builtins.str + str The token to use 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 + str A scheme-less endpoint URI for the endpoint to use for a new voice websocket. """ @@ -150,14 +150,14 @@ class VoiceServerUpdateEvent(VoiceEvent): 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. + typing.Optional[str] + If not `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 80b896e81c..aae054d964 100644 --- a/hikari/files.py +++ b/hikari/files.py @@ -80,7 +80,7 @@ This may be one of: -- `builtins.str` path. +- `str` path. - `os.PathLike` derivative, such as `pathlib.PurePath` and `pathlib.Path`. """ @@ -136,7 +136,7 @@ This may be one of: - `Resource` or a derivative. -- `builtins.str` path. +- `str` path. - `os.PathLike` derivative, such as `pathlib.PurePath` and `pathlib.Path`. - `bytes` - `bytearray` @@ -207,13 +207,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) @@ -223,20 +223,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" @@ -254,7 +254,7 @@ def guess_file_extension(mimetype: str) -> typing.Optional[str]: Parameters ---------- - mimetype : builtins.str + mimetype : str The mimetype to guess the extension for. Example @@ -266,9 +266,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) @@ -283,16 +283,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: @@ -315,14 +315,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: @@ -347,7 +347,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. @@ -357,7 +357,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) @@ -451,7 +451,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 @@ -462,12 +462,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: @@ -486,10 +486,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 @@ -547,7 +547,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. @@ -633,16 +633,15 @@ class WebResource(Resource[WebReader], abc.ABC): The logic for identifying this resource is left to each implementation to define. - !!! info + .. note:: For a usable concrete implementation, use `URL` instead. - !!! 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] = () @@ -662,8 +661,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. @@ -722,18 +721,18 @@ def stream( class URL(WebResource): """A URL that represents a web resource. - Parameters - ---------- - url : builtins.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. + + Parameters + ---------- + url : str + The URL of the resource. """ __slots__: typing.Sequence[str] = ("_url",) @@ -897,21 +896,20 @@ 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 - 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. + 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 + This will all be performed as required in an executor to prevent + blocking the event loop. + 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") @@ -955,9 +953,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 @@ -979,7 +977,7 @@ def stream( return _ThreadedFileReaderContextManagerImpl(executor, self.filename, self.path) if not isinstance(executor, concurrent.futures.ProcessPoolExecutor): - raise TypeError("The executor must be a ProcessPoolExecutor, ThreadPoolExecutor, or `builtins.None`.") + raise TypeError("The executor must be a ProcessPoolExecutor, ThreadPoolExecutor, or `None`.") return _MultiProcessingFileReaderContextManagerImpl(executor, self.filename, self.path) @@ -1068,14 +1066,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") @@ -1084,7 +1082,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.""" @@ -1136,7 +1134,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 @@ -1153,9 +1151,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. @@ -1166,7 +1164,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:"): diff --git a/hikari/guilds.py b/hikari/guilds.py index 0155f078af..47f653d1ac 100644 --- a/hikari/guilds.py +++ b/hikari/guilds.py @@ -288,7 +288,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 ------- @@ -296,7 +296,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 ------ @@ -338,14 +338,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. """ @@ -362,21 +362,21 @@ 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. """ raw_communication_disabled_until: typing.Optional[datetime.datetime] = attr.field(repr=False) """The datetime when this member's timeout will expire. - Will be `builtins.None` if the member is not timed out. + Will be `None` if the member is not timed out. - !!! note + .. note:: The datetime might be in the past, so it is recommended to use `communication_disabled_until` method to check if the member is timed out at the time of the call. @@ -395,9 +395,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`. """ @@ -418,7 +418,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() @@ -454,15 +454,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 @@ -499,11 +494,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 @@ -511,9 +501,9 @@ def communication_disabled_until(self) -> typing.Optional[datetime.datetime]: """Return when the timeout for this member ends. Unlike `raw_communication_disabled_until`, this will always be - `builtins.None` if the member is not currently timed out. + `None` if the member is not currently timed out. - !!! note + .. note:: The output of this function can depend based on when the function is called. """ @@ -530,7 +520,7 @@ def get_guild(self) -> typing.Optional[Guild]: Returns ------- typing.Optional[hikari.guilds.Guild] - The linked guild object or `builtins.None` if it's not cached. + The linked guild object or `None` if it's not cached. """ if not isinstance(self.user.app, traits.CacheAware): return None @@ -547,7 +537,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 @@ -581,7 +571,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) @@ -603,21 +593,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. @@ -625,11 +615,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: @@ -724,10 +714,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. @@ -768,7 +758,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. @@ -807,7 +797,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. @@ -850,7 +840,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. @@ -891,7 +881,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. @@ -937,44 +927,44 @@ async def edit( Other Parameters ---------------- - nickname : hikari.undefined.UndefinedNoneOr[builtins.str] - If provided, the new nick for the member. If `builtins.None`, + nickname : hikari.undefined.UndefinedNoneOr[str] + If provided, the new nick for the member. If `None`, will remove the members nick. Requires the `MANAGE_NICKNAMES` permission. - nick : hikari.undefined.UndefinedNoneOr[builtins.str] + nick : hikari.undefined.UndefinedNoneOr[str] Deprecated alias for `nickname`. roles : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole]] 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. communication_disabled_until : hikari.undefined.UndefinedNoneOr[datetime.datetime] If provided, the datetime when the timeout (disable communication) - of the member expires, up to 28 days in the future, or `builtins.None` + of the member expires, up to 28 days in the future, or `None` to remove the timeout from the member. Requires the `MODERATE_MEMBERS` permission. - 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. @@ -1008,7 +998,7 @@ async def edit( If an internal error occurs on Discord while handling the request. """ if nick is not undefined.UNDEFINED: - deprecation.warn_deprecated("nick", alternative="nickname") + deprecation.warn_deprecated("nick", "Use 'nickname' argument instead") nickname = nick return await self.user.app.rest.edit_member( @@ -1036,7 +1026,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} @@ -1051,13 +1041,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: @@ -1080,14 +1064,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.""" @@ -1111,13 +1095,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) @@ -1135,32 +1119,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: @@ -1235,13 +1219,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]: @@ -1249,21 +1227,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). """ @@ -1322,16 +1300,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) @@ -1361,7 +1339,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. """ @@ -1382,7 +1360,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. """ @@ -1409,7 +1387,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.""" @@ -1439,7 +1417,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 @@ -1461,25 +1439,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: @@ -1515,10 +1493,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. @@ -1563,7 +1541,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. @@ -1608,7 +1586,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. @@ -1669,7 +1647,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. @@ -1688,7 +1666,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 @@ -1702,9 +1680,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. @@ -1905,25 +1883,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. @@ -1978,13 +1956,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. @@ -2037,7 +2015,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. @@ -2079,16 +2057,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. @@ -2143,17 +2121,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 @@ -2164,7 +2142,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. @@ -2227,17 +2205,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 @@ -2248,7 +2226,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. @@ -2312,37 +2290,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. @@ -2406,32 +2381,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. @@ -2481,7 +2456,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 @@ -2517,7 +2492,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. """ @@ -2631,10 +2606,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. @@ -2645,7 +2620,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: @@ -2664,21 +2639,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: @@ -2703,13 +2678,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) @@ -2723,7 +2698,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( @@ -2735,7 +2710,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) @@ -2749,13 +2724,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) @@ -2774,7 +2749,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) @@ -2785,14 +2760,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) @@ -2802,15 +2777,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. """ @@ -2819,7 +2789,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) @@ -2829,7 +2799,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) @@ -2932,25 +2902,25 @@ def make_banner_url(self, *, ext: typing.Optional[str] = None, size: int = 4096) 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 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: @@ -2976,10 +2946,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. @@ -2990,7 +2960,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: @@ -3009,21 +2979,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: @@ -3051,7 +3021,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 @@ -3069,7 +3039,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 @@ -3082,7 +3052,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 @@ -3106,7 +3076,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 @@ -3126,7 +3096,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 @@ -3147,7 +3117,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 @@ -3165,7 +3135,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 @@ -3205,12 +3175,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 ------ @@ -3247,7 +3217,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 ------ @@ -3284,7 +3254,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 @@ -3319,12 +3289,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 ------ @@ -3359,7 +3329,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 ------- @@ -3411,19 +3381,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) @@ -3439,7 +3409,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. @@ -3450,7 +3420,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) @@ -3458,5 +3428,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 d77820cc2a..6556ec6a15 100644 --- a/hikari/impl/bot.py +++ b/hikari/impl/bot.py @@ -157,15 +157,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. @@ -175,12 +182,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. @@ -191,11 +198,12 @@ 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.impl.config.CacheSettings] Optional cache settings. If unspecified, will use the defaults. http_settings : typing.Optional[hikari.impl.config.HTTPSettings] @@ -207,10 +215,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. @@ -231,7 +239,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. @@ -244,28 +252,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[hikari.impl.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: @@ -290,6 +288,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", @@ -375,7 +382,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: @@ -575,11 +582,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` - Unsubscribe: `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) @@ -593,19 +600,17 @@ def get_listeners( event_type : typing.Type[EventT] The event type to look for. `EventT` 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[[EventT], typing.Coroutine[typing.Any, typing.Any, builtins.None]] + typing.Collection[typing.Callable[[EventT], 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. - - `EventT` must be a subclass of `hikari.events.base_events.Event`. """ return self._event_manager.get_listeners(event_type, polymorphic=polymorphic) @@ -644,11 +649,11 @@ def listen( See Also -------- - Dispatch: `hikari.impl.bot.GatewayBot.dispatch` - Stream: `hikari.impl.bot.GatewayBot.stream` - Subscribe: `hikari.impl.bot.GatewayBot.subscribe` - Unsubscribe: `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_types) @@ -670,26 +675,25 @@ def print_banner( 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`. - extra_args : typing.Optional[typing.Dict[builtins.str, builtins.str]] + This will take precedence over `allow_color` if both are specified. + extra_args : typing.Optional[typing.Dict[str, str]] If provided, extra $-substitutions to use when printing the banner. Default substitutions can not be overwritten. Raises ------ - builtins.ValueError + ValueError If `extra_args` contains a default $-substitution. """ ux.print_banner(banner, allow_color, force_color, extra_args=extra_args) @@ -719,25 +723,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 asyncgens and background tasks will be destroyed, and the event loop will be shut down. @@ -746,17 +750,17 @@ 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 : typing.Optional[builtins.bool] - Defaults to `builtins.True` if this is started in the main thread. + enable_signal_handlers : typing.Optional[bool] + Defaults to `True` if this is started in the main thread. If on a __non-Windows__ OS with builtin support for kernel-level - POSIX signals, then setting this to `builtins.True` will allow + POSIX signals, then setting this to `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. @@ -764,34 +768,34 @@ def run( 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. @@ -801,7 +805,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: @@ -937,34 +941,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. @@ -972,10 +976,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") @@ -1101,18 +1105,23 @@ def stream( ) -> event_manager_.EventStream[base_events.EventT]: """Return a stream iterator for the given event and sub-events. + .. 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. + 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 @@ -1122,14 +1131,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): @@ -1150,11 +1153,11 @@ def stream( See Also -------- - Dispatch: `hikari.impl.bot.GatewayBot.dispatch` - Listen: `hikari.impl.bot.GatewayBot.listen` - Subscribe: `hikari.impl.bot.GatewayBot.subscribe` - Unsubscribe: `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) @@ -1193,11 +1196,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` - Unsubscribe: `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) @@ -1233,11 +1236,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) @@ -1250,6 +1253,9 @@ async def wait_for( ) -> base_events.EventT: """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] @@ -1257,17 +1263,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). @@ -1279,16 +1282,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` - Unsubscribe: `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 bc5848d80a..e2a249f5ef 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,7 +248,7 @@ 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) def release(self) -> None: @@ -258,7 +258,7 @@ def release(self) -> None: async def acquire(self) -> None: """Acquire time and the lock on this bucket. - !!! note + .. note:: You should afterwards invoke `RESTBucket.update_rate_limit` to update any rate limit information you are made aware of and `RESTBucket.release` to release the lock. @@ -295,14 +295,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. """ @@ -316,12 +316,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: @@ -343,7 +343,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. """ @@ -407,10 +407,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 @@ -448,16 +448,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. @@ -485,13 +485,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. @@ -541,7 +541,7 @@ def acquire(self, compiled_route: routes.CompiledRoute) -> RESTBucket: hikari.impl.RESTBucket The bucket for this route. - !!! note + .. note:: You MUST keep the context manager of the bucket acquired during the full duration of the request. From making the request until calling `update_rate_limits`. @@ -576,14 +576,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) @@ -628,5 +628,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/config.py b/hikari/impl/config.py index 9fc035971a..d40e38ebb0 100644 --- a/hikari/impl/config.py +++ b/hikari/impl/config.py @@ -66,43 +66,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}" @@ -119,20 +98,20 @@ class ProxySettings(config.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. """ @@ -142,34 +121,34 @@ class ProxySettings(config.ProxySettings): url: typing.Union[None, str] = 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] - The proxy URL to use, or `builtins.None` to disable it. + typing.Union[None, str] + The proxy URL to use, or `None` to disable it. """ 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. """ @@ -177,11 +156,7 @@ class ProxySettings(config.ProxySettings): 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: @@ -201,45 +176,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 @@ -261,41 +220,29 @@ class HTTPSettings(config.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. @@ -303,17 +250,9 @@ class HTTPSettings(config.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 @@ -330,24 +269,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 @@ -358,11 +297,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( @@ -372,11 +306,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/impl/event_manager.py b/hikari/impl/event_manager.py index a9feacbf79..fe7c61d22e 100644 --- a/hikari/impl/event_manager.py +++ b/hikari/impl/event_manager.py @@ -113,7 +113,7 @@ def _cache_enabled_for(self, components: config.CacheComponents, /) -> bool: @event_manager_base.filtered(shard_events.ShardReadyEvent, config.CacheComponents.ME) 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) @@ -124,12 +124,12 @@ async def on_ready(self, shard: gateway_shard.GatewayShard, payload: data_bindin @event_manager_base.filtered(shard_events.ShardResumedEvent) 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)) @event_manager_base.filtered(channel_events.GuildChannelCreateEvent, config.CacheComponents.GUILD_CHANNELS) 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: @@ -139,7 +139,7 @@ async def on_channel_create(self, shard: gateway_shard.GatewayShard, payload: da @event_manager_base.filtered(channel_events.GuildChannelUpdateEvent, config.CacheComponents.GUILD_CHANNELS) 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) @@ -150,7 +150,7 @@ async def on_channel_update(self, shard: gateway_shard.GatewayShard, payload: da @event_manager_base.filtered(channel_events.GuildChannelDeleteEvent, config.CacheComponents.GUILD_CHANNELS) 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: @@ -160,7 +160,7 @@ async def on_channel_delete(self, shard: gateway_shard.GatewayShard, payload: da @event_manager_base.filtered((channel_events.GuildPinsUpdateEvent, channel_events.DMPinsUpdateEvent)) 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)) @@ -168,7 +168,7 @@ async def on_channel_pins_update(self, shard: gateway_shard.GatewayShard, payloa async def on_guild_create( # noqa: C901 - Function too complex 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, None] if "unavailable" in payload and self._enabled_for_event(guild_events.GuildAvailableEvent): @@ -278,7 +278,7 @@ async def on_guild_create( # noqa: C901 - Function too complex # Internal granularity is preferred for GUILD_UPDATE over decorator based filtering due to its large scope. 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.""" event: typing.Optional[guild_events.GuildUpdateEvent] if self._enabled_for_event(guild_events.GuildUpdateEvent): guild_id = snowflakes.Snowflake(payload["id"]) @@ -334,7 +334,7 @@ async def on_guild_update(self, shard: gateway_shard.GatewayShard, payload: data | config.CacheComponents.MEMBERS, ) 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"): event = self._event_factory.deserialize_guild_unavailable_event(shard, payload) @@ -362,17 +362,17 @@ async def on_guild_delete(self, shard: gateway_shard.GatewayShard, payload: data @event_manager_base.filtered(guild_events.BanCreateEvent) 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)) @event_manager_base.filtered(guild_events.BanDeleteEvent) 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)) @event_manager_base.filtered(guild_events.EmojisUpdateEvent, config.CacheComponents.EMOJIS) 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 @@ -386,7 +386,7 @@ async def on_guild_emojis_update(self, shard: gateway_shard.GatewayShard, payloa @event_manager_base.filtered(()) # An empty sequence here means that this method will always be skipped. 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. raise NotImplementedError @@ -408,7 +408,7 @@ async def on_integration_update(self, shard: gateway_shard.GatewayShard, payload @event_manager_base.filtered(member_events.MemberCreateEvent, config.CacheComponents.MEMBERS) 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: @@ -418,7 +418,7 @@ async def on_guild_member_add(self, shard: gateway_shard.GatewayShard, payload: @event_manager_base.filtered(member_events.MemberDeleteEvent, config.CacheComponents.MEMBERS) 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( @@ -430,7 +430,7 @@ async def on_guild_member_remove(self, shard: gateway_shard.GatewayShard, payloa @event_manager_base.filtered(member_events.MemberUpdateEvent, config.CacheComponents.MEMBERS) 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( @@ -446,7 +446,7 @@ async def on_guild_member_update(self, shard: gateway_shard.GatewayShard, payloa @event_manager_base.filtered(shard_events.MemberChunkEvent, config.CacheComponents.MEMBERS) 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: @@ -460,7 +460,7 @@ async def on_guild_members_chunk(self, shard: gateway_shard.GatewayShard, payloa @event_manager_base.filtered(role_events.RoleCreateEvent, config.CacheComponents.ROLES) 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: @@ -470,7 +470,7 @@ async def on_guild_role_create(self, shard: gateway_shard.GatewayShard, payload: @event_manager_base.filtered(role_events.RoleUpdateEvent, config.CacheComponents.ROLES) 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) @@ -481,7 +481,7 @@ async def on_guild_role_update(self, shard: gateway_shard.GatewayShard, payload: @event_manager_base.filtered(role_events.RoleDeleteEvent, config.CacheComponents.ROLES) 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"])) @@ -492,7 +492,7 @@ async def on_guild_role_delete(self, shard: gateway_shard.GatewayShard, payload: @event_manager_base.filtered(channel_events.InviteCreateEvent, config.CacheComponents.INVITES) 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: @@ -502,7 +502,7 @@ async def on_invite_create(self, shard: gateway_shard.GatewayShard, payload: dat @event_manager_base.filtered(channel_events.InviteDeleteEvent, config.CacheComponents.INVITES) 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"]) @@ -515,7 +515,7 @@ async def on_invite_delete(self, shard: gateway_shard.GatewayShard, payload: dat (message_events.GuildMessageCreateEvent, message_events.DMMessageCreateEvent), config.CacheComponents.MESSAGES ) 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: @@ -527,7 +527,7 @@ async def on_message_create(self, shard: gateway_shard.GatewayShard, payload: da (message_events.GuildMessageUpdateEvent, message_events.DMMessageUpdateEvent), config.CacheComponents.MESSAGES ) 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) @@ -540,7 +540,7 @@ async def on_message_update(self, shard: gateway_shard.GatewayShard, payload: da (message_events.GuildMessageDeleteEvent, message_events.DMMessageDeleteEvent), config.CacheComponents.MESSAGES ) 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) @@ -555,7 +555,7 @@ async def on_message_delete(self, shard: gateway_shard.GatewayShard, payload: da (message_events.GuildMessageDeleteEvent, message_events.DMMessageDeleteEvent), config.CacheComponents.MESSAGES ) 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: @@ -573,7 +573,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? @@ -582,7 +582,7 @@ 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)) @event_manager_base.filtered( @@ -591,7 +591,7 @@ async def on_message_reaction_remove( 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)) @event_manager_base.filtered( @@ -600,12 +600,12 @@ async def on_message_reaction_remove_all( 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)) @event_manager_base.filtered(guild_events.PresenceUpdateEvent, config.CacheComponents.PRESENCES) 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( @@ -624,12 +624,12 @@ async def on_presence_update(self, shard: gateway_shard.GatewayShard, payload: d @event_manager_base.filtered((typing_events.GuildTypingEvent, typing_events.DMTypingEvent)) 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)) @event_manager_base.filtered(user_events.OwnUserUpdateEvent, config.CacheComponents.ME) 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) @@ -640,7 +640,7 @@ async def on_user_update(self, shard: gateway_shard.GatewayShard, payload: data_ @event_manager_base.filtered(voice_events.VoiceStateUpdateEvent, config.CacheComponents.VOICE_STATES) 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( @@ -658,17 +658,17 @@ async def on_voice_state_update(self, shard: gateway_shard.GatewayShard, payload @event_manager_base.filtered(voice_events.VoiceServerUpdateEvent) 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)) @event_manager_base.filtered(channel_events.WebhookUpdateEvent) 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)) @event_manager_base.filtered(interaction_events.InteractionCreateEvent) 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)) @event_manager_base.filtered(scheduled_events.ScheduledEventCreateEvent) diff --git a/hikari/impl/event_manager_base.py b/hikari/impl/event_manager_base.py index c4a98354c9..fc05d0e0a8 100644 --- a/hikari/impl/event_manager_base.py +++ b/hikari/impl/event_manager_base.py @@ -119,7 +119,7 @@ async def call_weak_method(event: base_events.EventT) -> None: class EventStream(event_manager_.EventStream[base_events.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 96036a094a..c955574a51 100644 --- a/hikari/impl/interaction_server.py +++ b/hikari/impl/interaction_server.py @@ -173,9 +173,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. @@ -237,7 +237,7 @@ def is_alive(self) -> bool: Returns ------- - builtins.bool + bool Whether this interaction server is active """ return self._server is not None @@ -376,18 +376,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 @@ -471,40 +471,40 @@ async def start( Other Parameters ---------------- - backlog : builtins.int + backlog : int The number of unaccepted connections that the system will allow before refusing new connections. - enable_signal_handlers : typing.Optional[builtins.bool] - Defaults to `builtins.True` if this is started in the main thread. + enable_signal_handlers : typing.Optional[bool] + Defaults to `True` if this is started in the main thread. If on a __non-Windows__ OS with builtin support for kernel-level - POSIX signals, then setting this to `builtins.True` will allow + POSIX signals, then setting this to `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 enabled 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 + .. note:: For more information on the other parameters such as defaults see AIOHTTP's documentation. """ diff --git a/hikari/impl/rate_limits.py b/hikari/impl/rate_limits.py index 0ecd2c1544..e4b22ad71f 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) @@ -328,10 +328,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 @@ -340,7 +340,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. """ @@ -353,16 +353,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 @@ -386,14 +386,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", @@ -427,23 +427,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 a9e7c6c1cd..995cf90eb6 100644 --- a/hikari/impl/rest.py +++ b/hikari/impl/rest.py @@ -124,7 +124,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 @@ -163,13 +163,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 @@ -178,13 +172,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 @@ -270,16 +258,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.impl.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. @@ -288,19 +280,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.impl.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] = ( @@ -360,7 +348,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. @@ -378,16 +366,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 @@ -397,7 +385,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. @@ -432,7 +420,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. """ @@ -450,7 +438,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. """ @@ -511,36 +499,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` """ @@ -635,7 +623,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 @@ -2060,7 +2048,7 @@ async def add_user_to_guild( deaf: undefined.UndefinedOr[bool] = undefined.UNDEFINED, ) -> typing.Optional[guilds.Member]: if nick is not undefined.UNDEFINED: - deprecation.warn_deprecated("nick", alternative="nickname") + deprecation.warn_deprecated("nick", "Use 'nickname' argument instead") nickname = nick route = routes.PUT_GUILD_MEMBER.compile(guild=guild, user=user) @@ -2644,7 +2632,7 @@ async def edit_member( reason: undefined.UndefinedOr[str] = undefined.UNDEFINED, ) -> guilds.Member: if nick is not undefined.UNDEFINED: - deprecation.warn_deprecated("nick", alternative="nickname") + deprecation.warn_deprecated("nick", "Use 'nickname' argument instead") nickname = nick route = routes.PATCH_GUILD_MEMBER.compile(guild=guild, user=user) @@ -2683,7 +2671,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 efba07f5d5..2f58399c18 100644 --- a/hikari/impl/rest_bot.py +++ b/hikari/impl/rest_bot.py @@ -67,19 +67,19 @@ class RESTBot(traits.RESTBotAware, interaction_server_.InteractionServer): Parameters ---------- - token : typing.Union[builtins.str, hikari.api.rest.TokenStrategy] + token : typing.Union[str, hikari.api.rest.TokenStrategy] The bot or bearer token. - token_type : typing.Union[builtins.str, hikari.applications.TokenType] - The type of token in use. This should only be passed when `builtins.str` + token_type : typing.Union[str, hikari.applications.TokenType] + The type of token in use. This should only be passed when `str` is passed for `token`, can be `"Bot"` or `"Bearer"`. - This should be left as `builtins.None` when `hikari.api.rest.TokenStrategy` + This should be left as `None` when `hikari.api.rest.TokenStrategy` 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. @@ -89,12 +89,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. @@ -105,20 +105,20 @@ 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. - http_settings : typing.Optional[hikari.config.HTTPSettings] + http_settings : typing.Optional[hikari.config.impl.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. @@ -139,7 +139,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. @@ -152,31 +152,31 @@ 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 + .. 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] = ( @@ -333,26 +333,26 @@ def print_banner( 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 + .. note:: `force_color` will always take precedence over `allow_color`. - extra_args : typing.Optional[typing.Dict[builtins.str, builtins.str]] + extra_args : typing.Optional[typing.Dict[str, str]] If provided, extra $-substitutions to use when printing the banner. Default substitutions can not be overwritten. Raises ------ - builtins.ValueError + ValueError If `extra_args` contains a default $-substitution. """ ux.print_banner(banner, allow_color, force_color, extra_args=extra_args) @@ -403,17 +403,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 asyncgens and background tasks will be destroyed, and the event loop will be shut down. @@ -422,44 +422,44 @@ 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 : typing.Optional[builtins.bool] - Defaults to `builtins.True` if this is started in the main thread. + enable_signal_handlers : typing.Optional[bool] + Defaults to `True` if this is started in the main thread. If on a __non-Windows__ OS with builtin support for kernel-level - POSIX signals, then setting this to `builtins.True` will allow + POSIX signals, then setting this to `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 enabled 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] @@ -523,43 +523,43 @@ async def start( 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 : typing.Optional[builtins.bool] - Defaults to `builtins.True` if this is started in the main thread. + enable_signal_handlers : typing.Optional[bool] + Defaults to `True` if this is started in the main thread. If on a __non-Windows__ OS with builtin support for kernel-level - POSIX signals, then setting this to `builtins.True` will allow + POSIX signals, then setting this to `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 enabled 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 + .. note:: For more information on the other parameters such as defaults see AIOHTTP's documentation. """ diff --git a/hikari/impl/shard.py b/hikari/impl/shard.py index 8b7aa3b0dc..32c23bf2d4 100644 --- a/hikari/impl/shard.py +++ b/hikari/impl/shard.py @@ -119,11 +119,6 @@ def filterer(entry: str) -> str: return filterer -if typing.TYPE_CHECKING: - # noinspection PyProtectedMember,PyUnresolvedReferences - _ZlibDecompressor = zlib._Decompress - - # aiohttp.ClientWebSocketResponse isn't slotted @typing.final class _GatewayTransport(aiohttp.ClientWebSocketResponse): @@ -138,7 +133,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' @@ -350,11 +345,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 @@ -364,48 +370,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.impl.config.HTTPSettings The HTTP-related settings to use while negotiating a websocket. proxy_settings : hikari.impl.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 772d15592c..f3c01060c1 100644 --- a/hikari/impl/special_endpoints.py +++ b/hikari/impl/special_endpoints.py @@ -109,7 +109,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. @@ -193,7 +193,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 @@ -230,15 +230,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 @@ -852,7 +852,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. """ @@ -1151,12 +1151,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 b0b326f66f..d68e1155a8 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 @@ -298,7 +298,7 @@ class Intents(enums.Flag): DM's to the bot and messages that mention it are exempt from this. - !!! warning + .. warning:: This intent is privileged, and requires enabling/whitelisting to use. """ @@ -335,7 +335,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. """ @@ -346,7 +346,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. """ @@ -369,7 +369,7 @@ class Intents(enums.Flag): ALL_PRIVILEGED = ALL_GUILDS_PRIVILEGED | MESSAGE_CONTENT """All privileged intents. - !!! warning + .. warning:: This set of intent is privileged, and requires enabling/whitelisting to use. """ @@ -377,7 +377,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. """ @@ -386,7 +386,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 f5e42f4fb3..26e0261be3 100644 --- a/hikari/interactions/base_interactions.py +++ b/hikari/interactions/base_interactions.py @@ -260,7 +260,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 @@ -268,7 +268,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 @@ -277,7 +277,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 @@ -297,28 +297,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 @@ -327,10 +327,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 @@ -403,7 +403,7 @@ async def edit_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 neither the `embed` or `embeds` kwargs are provided or if this is a @@ -418,70 +418,70 @@ 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 + .. 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 + .. warning:: If you specify one of `mentions_everyone`, `user_mentions`, or - `role_mentions`, then all others will default to `builtins.False`, + `role_mentions`, then all others will default to `False`, even if they were enabled previously. This is a limitation of Discord's design. If in doubt, specify all three of @@ -494,10 +494,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 ddebb1cc9a..03283254d4 100644 --- a/hikari/interactions/command_interactions.py +++ b/hikari/interactions/command_interactions.py @@ -49,7 +49,6 @@ if typing.TYPE_CHECKING: from hikari import guilds - from hikari import locales from hikari import messages as messages_ from hikari import permissions as permissions_ from hikari import users as users_ @@ -171,7 +170,10 @@ class AutocompleteInteractionOption(CommandInteractionOption): @attr_extensions.with_copy @attr.define(hash=True, kw_only=True, weakref_slot=False) class BaseCommandInteraction(base_interactions.PartialInteraction): - """Represents a base command interaction on Discord.""" + """Represents a base command interaction on Discord. + + May be a command interaction or an autocomplete interaction. + """ channel_id: snowflakes.Snowflake = attr.field(eq=False, hash=False, repr=True) """ID of the channel this command interaction event was triggered in.""" @@ -179,15 +181,15 @@ class BaseCommandInteraction(base_interactions.PartialInteraction): 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. """ - guild_locale: typing.Optional[typing.Union[str, locales.Locale]] = attr.field(eq=False, hash=False, repr=True) + guild_locale: typing.Optional[str] = attr.field(eq=False, hash=False, repr=True) """The preferred language of the guild this command 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. - !!! note + .. note:: This value can usually only be changed if `COMMUNITY` is in `hikari.guilds.Guild.features` for the guild and will otherwise default to `en-US`. """ @@ -195,9 +197,9 @@ class BaseCommandInteraction(base_interactions.PartialInteraction): 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. """ @@ -205,7 +207,7 @@ class BaseCommandInteraction(base_interactions.PartialInteraction): user: users_.User = attr.field(eq=False, hash=False, repr=True) """The user who triggered this command interaction.""" - locale: typing.Union[str, locales.Locale] = attr.field(eq=False, hash=False, repr=True) + locale: str = attr.field(eq=False, hash=False, repr=True) """The selected language of the user who triggered this command interaction.""" command_id: snowflakes.Snowflake = attr.field(eq=False, hash=False, repr=True) @@ -261,15 +263,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) @@ -318,7 +320,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 @@ -354,7 +356,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) @@ -376,7 +378,7 @@ class CommandInteraction(BaseCommandInteraction, base_interactions.MessageRespon 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. @@ -403,12 +405,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. @@ -434,7 +436,7 @@ def build_response( ) -> special_endpoints.InteractionAutocompleteBuilder: """Get a message response builder for use in the REST server flow. - !!! note + .. note:: For interactions received over the gateway `AutocompleteInteraction.create_response` should be used to set the interaction response. diff --git a/hikari/interactions/component_interactions.py b/hikari/interactions/component_interactions.py index 9892184452..b1fb7b3a12 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,15 +105,15 @@ 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 component interactions triggered in DMs. + This will be `None` for component interactions triggered in DMs. """ guild_locale: typing.Optional[typing.Union[str, locales.Locale]] = attr.field(eq=False, hash=False, repr=True) """The preferred language of the guild this component interaction was triggered in. - This will be `builtins.None` for component interactions triggered in DMs. + This will be `None` for component interactions triggered in DMs. - !!! note + .. note:: This value can usually only be changed if `COMMUNITY` is in `hikari.guilds.Guild.features` for the guild and will otherwise default to `en-US`. """ @@ -124,9 +124,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. """ @@ -140,14 +140,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: @@ -180,19 +180,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: @@ -250,15 +250,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) @@ -273,7 +273,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 @@ -309,7 +309,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) @@ -326,7 +326,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). @@ -354,7 +354,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 bcd0618c5a..814fddbe34 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 82fa474379..b330c45968 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. - !!! 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 d620728e0f..734b438e07 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 db61827baf..7d2a7c7344 100644 --- a/hikari/internal/collections.py +++ b/hikari/internal/collections.py @@ -72,7 +72,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 @@ -89,7 +89,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. @@ -175,7 +175,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. """ @@ -253,13 +253,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. """ @@ -358,7 +358,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 b7390a4263..02d74afea8 100644 --- a/hikari/internal/data_binding.py +++ b/hikari/internal/data_binding.py @@ -146,8 +146,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. """ @@ -191,7 +191,7 @@ def put( Parameters ---------- - key : builtins.str + key : str The string key. value : hikari.undefined.UndefinedOr[typing.Any] The value to set. @@ -201,11 +201,11 @@ 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. + .. note:: + The value will always be cast to a `str` before inserting it. - `builtins.True` will be translated to `"true"`, `builtins.False` - ill be translated to `"false"`, and `builtins.None` will be + `True` will be translated to `"true"`, `False` + ill be translated to `"false"`, and `None` will be translated to `"null"`. """ if value is not undefined.UNDEFINED: @@ -237,8 +237,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. """ @@ -278,7 +278,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 @@ -334,7 +334,7 @@ def put_array( Parameters ---------- - key : builtins.str + key : str The key to give the element. values : hikari.undefined.UndefinedOr[typing.Iterable[T_co]] The JSON types to put. This may be an iterable of non-JSON types if @@ -361,11 +361,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: @@ -383,11 +383,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 b328091a52..e35232a34e 100644 --- a/hikari/internal/deprecation.py +++ b/hikari/internal/deprecation.py @@ -35,14 +35,7 @@ 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 @@ -50,57 +43,60 @@ def warn_deprecated( obj: typing.Any The object that is deprecated. + Possible values are: + - A class + - A function/method + - An argument + 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__}" + elif inspect.isfunction(obj): + action = ("Call to", "function/method") + obj = f"{obj.__module__}.{obj.__qualname__}" + else: + action = ("Use of", "argument") - 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}." - - if alternative is not None: - message += f" You can use '{alternative}' instead." - - warnings.warn(message, category=DeprecationWarning, stacklevel=stack_level) + warnings.warn( + f"{action[0]} deprecated {action[1]} {obj!r} ({additional_information})", + category=DeprecationWarning, + stacklevel=stack_level, + ) -def deprecated( - version: typing.Optional[str] = None, alternative: typing.Optional[str] = None -) -> typing.Callable[[T], T]: - """Mark a function as deprecated. +def deprecated(version: str, additional_information: str) -> typing.Callable[[T], T]: + """Mark a function or object as being 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/enums.py b/hikari/internal/enums.py index 1742951299..7e184c4f0b 100644 --- a/hikari/internal/enums.py +++ b/hikari/internal/enums.py @@ -147,7 +147,7 @@ def __new__( }, } - # We don't want to override the __str__ behaviour inherited from builtins.str for string based enums. + # We don't want to override the __str__ behaviour inherited from str for string based enums. if issubclass(base, str): new_namespace.pop("__str__", None) @@ -209,7 +209,7 @@ class Enum(metaclass=_EnumMeta): that can be used in place of this type. This acts as a type-safe way of representing a set number of "things". - !!! warning + .. warning:: 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. @@ -230,43 +230,43 @@ class Enum(metaclass=_EnumMeta): ... PARTIAL = "partial" ... MEMBER = "member" >>> print(UserType.__objtype__) - + ``` 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 +281,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 @@ -455,20 +455,19 @@ 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 + .. warning:: + Despite wrapping `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. @@ -490,16 +489,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 +510,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 +531,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 +545,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 +585,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 +604,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 +619,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 +630,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 +660,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 +679,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/fast_protocol.py b/hikari/internal/fast_protocol.py index 5cc27a5cb4..289267ee77 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 964579cd2c..c426ca7d1a 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 4e0f0cd817..d2d62120d6 100644 --- a/hikari/internal/net.py +++ b/hikari/internal/net.py @@ -90,12 +90,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 @@ -123,10 +123,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 @@ -136,17 +136,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 : hikari.impl.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 d28af6c36e..b769fc0924 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 66a66e76eb..718da2ec5c 100644 --- a/hikari/internal/routes.py +++ b/hikari/internal/routes.py @@ -78,12 +78,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 @@ -96,13 +96,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. """ @@ -123,9 +123,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,8 +204,8 @@ def _(self, _: attr.Attribute[typing.AbstractSet[str]], values: typing.AbstractS if not values: 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.""" + is_sizable: bool = attr.field(default=True, kw_only=True, repr=False, hash=False, eq=False) + """Whether a `size` param can be specified.""" def compile( self, @@ -219,30 +219,29 @@ 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 + is passed but the route is not sizable. + ValueError If `size` is specified, but is not an integer power of `2` between `16` and `4096` inclusive or is negative. """ @@ -262,7 +261,7 @@ def compile( url = base_url + self.path_template.format(**kwargs) + f".{file_format}" if size is not None: - if not self.sizable: + if not self.is_sizable: raise TypeError("This asset cannot be resized.") if size < 0: @@ -548,7 +547,7 @@ def compile_to_file( ) CDN_GUILD_BANNER: typing.Final[CDNRoute] = CDNRoute("/banners/{guild_id}/{hash}", {PNG, *JPEG_JPG, WEBP, GIF}) -CDN_DEFAULT_USER_AVATAR: typing.Final[CDNRoute] = CDNRoute("/embed/avatars/{discriminator}", {PNG}, sizable=False) +CDN_DEFAULT_USER_AVATAR: typing.Final[CDNRoute] = CDNRoute("/embed/avatars/{discriminator}", {PNG}, is_sizable=False) CDN_USER_AVATAR: typing.Final[CDNRoute] = CDNRoute("/avatars/{user_id}/{hash}", {PNG, *JPEG_JPG, WEBP, GIF}) CDN_USER_BANNER: typing.Final[CDNRoute] = CDNRoute("/banners/{user_id}/{hash}", {PNG, *JPEG_JPG, WEBP, GIF}) CDN_MEMBER_AVATAR: typing.Final[CDNRoute] = CDNRoute( @@ -568,7 +567,7 @@ def compile_to_file( # undocumented on the Discord docs. CDN_CHANNEL_ICON: typing.Final[CDNRoute] = CDNRoute("/channel-icons/{channel_id}/{hash}", {PNG, *JPEG_JPG, WEBP}) -CDN_STICKER: typing.Final[CDNRoute] = CDNRoute("/stickers/{sticker_id}", {PNG, LOTTIE}, sizable=False) +CDN_STICKER: typing.Final[CDNRoute] = CDNRoute("/stickers/{sticker_id}", {PNG, LOTTIE}, is_sizable=False) CDN_STICKER_PACK_BANNER: typing.Final[CDNRoute] = CDNRoute( "/app-assets/710982414301790216/store/{hash}", {PNG, *JPEG_JPG, WEBP} ) diff --git a/hikari/internal/time.py b/hikari/internal/time.py index f0149e3816..a2bbb464c0 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 e4604a5c23..b8a5d916d8 100644 --- a/hikari/internal/ux.py +++ b/hikari/internal/ux.py @@ -72,28 +72,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 @@ -163,26 +163,26 @@ def print_banner( 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`. - extra_args : typing.Optional[typing.Dict[builtins.str, builtins.str]] + 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`. + extra_args : typing.Optional[typing.Dict[str, str]] If provided, extra $-substitutions to use when printing the banner. Default substitutions can not be overwritten. Raises ------ - builtins.ValueError + ValueError If `extra_args` contains a default $-substitution. """ if package is None: @@ -228,23 +228,23 @@ def print_banner( 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 694892b476..d556f4e454 100644 --- a/hikari/invites.py +++ b/hikari/invites.py @@ -78,7 +78,7 @@ def code(self) -> str: Returns ------- - builtins.str + str The invite code that can be appended to a URL. """ @@ -117,14 +117,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 +134,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 +153,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,25 +191,25 @@ def make_banner_url(self, *, ext: typing.Optional[str] = None, size: int = 4096) 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 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: @@ -247,20 +247,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`. """ @@ -295,8 +295,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`. """ @@ -314,7 +314,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 @@ -322,7 +322,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) @@ -334,7 +334,7 @@ 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 @@ -343,8 +343,8 @@ def uses_left(self) -> typing.Optional[int]: Returns ------- - typing.Optional[builtins.int] - The number of uses left for this invite. This will be `builtins.None` + typing.Optional[int] + The number of uses left for this invite. This will be `None` if the invite has unlimited uses. """ if self.max_uses: diff --git a/hikari/iterators.py b/hikari/iterators.py index 0aeeabe6ec..56a37ac693 100644 --- a/hikari/iterators.py +++ b/hikari/iterators.py @@ -54,7 +54,7 @@ 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... @@ -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,13 +429,13 @@ 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 @@ -464,7 +464,7 @@ def enumerate(self, *, start: int = 0) -> LazyIterator[typing.Tuple[int, ValueT] 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,7 +475,7 @@ 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 @@ -496,7 +496,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 +517,7 @@ async def next(self) -> ValueT: Raises ------ - builtins.LookupError + LookupError If no more results exist. """ try: @@ -533,12 +533,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 +575,7 @@ async def count(self) -> int: Returns ------- - builtins.int + int Number of results found. """ count = 0 @@ -650,20 +650,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 +738,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 33b86802f7..6af79a92e8 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. """ @@ -429,13 +429,7 @@ class MessageApplication(guilds.PartialApplication): @property def cover_image_url(self) -> typing.Optional[files.URL]: - """Rich presence cover image URL for this application, if set. - - Returns - ------- - typing.Optional[hikari.files.URL] - The URL, or `builtins.None` if no cover image exists. - """ + """Rich presence cover image URL for this application, if set.""" return self.make_cover_image_url() def make_cover_image_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optional[files.URL]: @@ -443,21 +437,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). """ @@ -498,18 +492,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`. """ @@ -517,7 +511,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`. """ @@ -528,7 +522,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 @@ -546,7 +540,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. """ @@ -598,7 +592,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`. """ @@ -615,7 +609,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` @@ -655,7 +649,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`. """ @@ -691,7 +685,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. """ @@ -728,7 +722,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 @@ -747,11 +741,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) @@ -764,14 +758,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) @@ -783,7 +777,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. """ @@ -793,7 +787,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. @@ -821,7 +815,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. """ @@ -829,7 +823,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. """ @@ -856,7 +850,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, eq=False, repr=False) @@ -865,7 +859,7 @@ class PartialMessage(snowflakes.Unique): application_id: undefined.UndefinedNoneOr[snowflakes.Snowflake] = attr.field(hash=False, eq=False, repr=False) """ID of the application this message was sent by. - !!! note + .. note:: This will only be provided for interaction messages. """ @@ -877,21 +871,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}" @@ -955,14 +947,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 @@ -976,67 +988,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 @@ -1051,33 +1063,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 @@ -1151,7 +1136,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 @@ -1166,6 +1151,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. @@ -1178,60 +1189,33 @@ 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). - 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 @@ -1254,10 +1238,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: @@ -1320,7 +1304,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` @@ -1400,7 +1384,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 @@ -1415,24 +1399,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 ------ @@ -1487,7 +1473,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]] @@ -1495,17 +1481,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 ------ @@ -1547,7 +1535,7 @@ class Message(PartialMessage): edited_timestamp: typing.Optional[datetime.datetime] = attr.field(hash=False, eq=False, repr=False) """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 = attr.field(hash=False, eq=False, repr=False) @@ -1574,7 +1562,7 @@ class Message(PartialMessage): activity: typing.Optional[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. """ @@ -1582,7 +1570,7 @@ class Message(PartialMessage): application: typing.Optional[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. """ @@ -1608,7 +1596,7 @@ class Message(PartialMessage): application_id: typing.Optional[snowflakes.Snowflake] = attr.field(hash=False, eq=False, repr=False) """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 5e1f358840..1000cce9bd 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 fc8a68ea55..8362554c78 100644 --- a/hikari/presences.py +++ b/hikari/presences.py @@ -68,7 +68,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. @@ -86,7 +86,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. """ @@ -168,8 +168,8 @@ def _make_asset_url(self, asset: typing.Optional[str], ext: str, size: int) -> t def large_image_url(self) -> typing.Optional[files.URL]: """Large image asset URL. - !!! note - This will be `builtins.None` if no large image asset exists or if the + .. note:: + This will be `None` if no large image asset exists or if the asset's dynamic URL (indicated by a `{name}:` prefix) is not known. """ try: @@ -181,30 +181,30 @@ def large_image_url(self) -> typing.Optional[files.URL]: def make_large_image_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optional[files.URL]: """Generate the large image asset URL for this application. - !!! note + .. note:: `ext` and `size` are ignored for images hosted outside of Discord or on Discord's media proxy. 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). - builtins.RuntimeError + RuntimeError If `ActivityAssets.large_image` points towards an unknown asset type. """ return self._make_asset_url(self.large_image, ext, size) @@ -213,8 +213,8 @@ def make_large_image_url(self, *, ext: str = "png", size: int = 4096) -> typing. def small_image_url(self) -> typing.Optional[files.URL]: """Small image asset URL. - !!! note - This will be `builtins.None` if no large image asset exists or if the + .. note:: + This will be `None` if no large image asset exists or if the asset's dynamic URL (indicated by a `{name}:` prefix) is not known. """ try: @@ -228,24 +228,24 @@ def make_small_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 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). - builtins.RuntimeError + RuntimeError If `ActivityAssets.small_image` points towards an unknown asset type. """ return self._make_asset_url(self.small_image, ext, size) diff --git a/hikari/scheduled_events.py b/hikari/scheduled_events.py index f6d9cd0055..23a95b8d26 100644 --- a/hikari/scheduled_events.py +++ b/hikari/scheduled_events.py @@ -144,7 +144,7 @@ class ScheduledEvent(snowflakes.Unique): user_count: typing.Optional[int] = attr.field(hash=False, repr=False) """The number of users that have subscribed to the event. - This will be `builtins.None` on gateway events when creating and + This will be `None` on gateway events when creating and editing a scheduled event. """ @@ -161,21 +161,21 @@ def make_image_url(self, *, ext: str = "png", size: int = 4096) -> typing.Option 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 is set. + The URL, or `None` if no cover image is set. Raises ------ - builtins.ValueError + ValueError If `size` is not a power of two between 16 and 4096 (inclusive). """ if self.image_hash is None: @@ -198,7 +198,7 @@ class ScheduledExternalEvent(ScheduledEvent): location: str = attr.field(hash=False, repr=False) """The location of the scheduled event. - !!! note + .. note:: There is no strict format for this field, and it will likely be a user friendly string. """ diff --git a/hikari/snowflakes.py b/hikari/snowflakes.py index b0f0657674..5567843130 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 @@ -218,7 +218,7 @@ def calculate_shard_id( The valid types for this type hint are: -- `builtins.int` +- `int` - `Snowflake` """ @@ -235,7 +235,7 @@ def calculate_shard_id( The valid types for this type hint are: -- `builtins.int` +- `int` - `Snowflake` - `datetime.datetime` """ diff --git a/hikari/stickers.py b/hikari/stickers.py index 753214b2be..99bb1ab597 100644 --- a/hikari/stickers.py +++ b/hikari/stickers.py @@ -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 616348fc32..07ffec99c1 100644 --- a/hikari/templates.py +++ b/hikari/templates.py @@ -38,7 +38,6 @@ from hikari import channels as channels_ from hikari import colors - from hikari import locales from hikari import permissions as permissions_ from hikari import snowflakes from hikari import users @@ -64,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) @@ -92,7 +91,7 @@ class TemplateGuild(guilds.PartialGuild): ) """The setting for the explicit content filter in this guild.""" - preferred_locale: typing.Union[str, locales.Locale] = attr.field(eq=False, hash=False, repr=False) + preferred_locale: str = attr.field(eq=False, hash=False, repr=False) """The preferred locale to use for this guild. This can only be change if `GuildFeature.COMMUNITY` is in `Guild.features` @@ -109,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. """ @@ -119,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. """ @@ -127,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 0778dc6bea..c1297a76a6 100644 --- a/hikari/traits.py +++ b/hikari/traits.py @@ -78,7 +78,7 @@ def http_settings(self) -> config.HTTPSettings: Returns ------- - hikari.config.HTTPSettings + hikari.impl.config.HTTPSettings The HTTP settings in use. """ raise NotImplementedError @@ -89,7 +89,7 @@ def proxy_settings(self) -> config.ProxySettings: Returns ------- - hikari.config.ProxySettings + hikari.impl.config.ProxySettings The proxy settings in use. """ raise NotImplementedError @@ -142,7 +142,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. """ @@ -152,13 +152,13 @@ class ExecutorAware(fast_protocol.FastProtocolChecking, typing.Protocol): def executor(self) -> typing.Optional[futures.Executor]: """Return the 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 + The executor to use, or `None` to use the `asyncio` default instead. """ raise NotImplementedError @@ -273,7 +273,7 @@ def heartbeat_latencies(self) -> typing.Mapping[int, float]: Returns ------- - typing.Mapping[builtins.int, builtins.float] + typing.Mapping[int, float] Each shard ID mapped to the corresponding heartbeat latency. Each latency is measured in seconds. """ @@ -287,7 +287,7 @@ def heartbeat_latency(self) -> float: Returns ------- - builtins.float + float The average heartbeat latency of all started shards, or `float('nan')` if no shards are started. This is measured in seconds. @@ -319,7 +319,7 @@ def shard_count(self) -> int: Returns ------- - builtins.int + int The number of shards in the total application. """ raise NotImplementedError @@ -330,12 +330,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 @@ -359,8 +359,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] @@ -369,12 +369,12 @@ async def update_presence( status : hikari.undefined.UndefinedOr[hikari.presences.Status] The web status to show. If undefined, this will not be changed. - !!! note + .. 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 + .. 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. @@ -399,19 +399,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. """ @@ -435,18 +435,18 @@ 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 + .. note:: To request the full list of members, set `query` to `""` (empty string) and `limit` to `0`. @@ -458,7 +458,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. """ @@ -515,11 +515,11 @@ def is_alive(self) -> bool: This is useful as some functions might raise `hikari.errors.ComponentStateConflictError` if this is - `builtins.False`. + `False`. Returns ------- - builtins.bool + bool Whether the bot is running or not. """ raise NotImplementedError @@ -569,12 +569,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 +598,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_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 +611,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 +655,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 4853646134..1d53496f00 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 35ce810034..ba12fb2f42 100644 --- a/hikari/users.py +++ b/hikari/users.py @@ -140,7 +140,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 @@ -150,7 +150,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. @@ -175,12 +175,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 @@ -194,16 +194,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: @@ -293,7 +287,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 @@ -309,6 +303,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. @@ -321,63 +341,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 read out by a screen reader using Discord's TTS (text-to-speech) system. 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 @@ -385,10 +378,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 @@ -459,7 +452,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 @@ -472,13 +465,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() @@ -492,7 +485,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() @@ -523,12 +516,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 @@ -537,16 +530,10 @@ def mention(self) -> str: Example ------- - ```py >>> some_user.mention '<@123456789123456789>' ``` - - Returns - ------- - builtins.str - The mention string to use. """ @property @@ -557,21 +544,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. @@ -579,11 +566,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: @@ -606,29 +593,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: @@ -704,11 +691,6 @@ def mention(self) -> str: >>> some_user.mention '<@123456789123456789>' ``` - - Returns - ------- - builtins.str - The mention string to use. """ return f"<@{self.id}>" @@ -729,10 +711,10 @@ class UserImpl(PartialUserImpl, User): """The user's username.""" avatar_hash: typing.Optional[str] = attr.field(eq=False, hash=False, repr=False) - """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] = attr.field(eq=False, hash=False, repr=False) - """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] = attr.field(eq=False, hash=False, repr=False) """The custom banner color for the user, if set. @@ -741,10 +723,10 @@ class UserImpl(PartialUserImpl, User): """ # noqa: D401 - Imperative mood is_bot: bool = attr.field(eq=False, hash=False, repr=True) - """`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 = attr.field(eq=False, hash=False, repr=True) - """`builtins.True` if this user is a system account, `builtins.False` otherwise.""" + """`True` if this user is a system account, `False` otherwise.""" flags: UserFlag = attr.field(eq=False, hash=False, repr=True) """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 37196964c0..adb2f44300 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 9eeb4ed43a..be26465f43 100644 --- a/hikari/webhooks.py +++ b/hikari/webhooks.py @@ -104,14 +104,14 @@ def webhook_id(self) -> snowflakes.Snowflake: 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`. + .. note:: + If this is `None` then the methods provided by `ExecutableWebhook` + will always raise a `ValueError`. Returns ------- - typing.Optional[builtins.str] - The token for the webhook if known, else `builtins.None`. + typing.Optional[str] + The token for the webhook if known, else `None`. """ async def execute( @@ -144,7 +144,7 @@ async def execute( 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 +156,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, str, hikari.files.URL] If provided, the url of an image to override the webhook's avatar with for this request. tts : hikari.undefined.UndefinedOr[bool] @@ -179,32 +179,32 @@ 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 + .. warning:: As of writing, `username` and `avatar_url` are ignored for interaction webhooks. @@ -226,11 +226,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 +271,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). @@ -329,7 +329,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,82 +345,82 @@ 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 + .. 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 + .. 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. + to `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 + .. 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. + `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. @@ -432,10 +432,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 +493,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,7 +551,7 @@ 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. @@ -566,7 +566,7 @@ def mention(self) -> str: Returns ------- - builtins.str + str The mention string to use. """ return f"<@{self.id}>" @@ -575,7 +575,7 @@ 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() @@ -584,7 +584,7 @@ def avatar_url(self) -> typing.Optional[files_.URL]: 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 + May be `None` if no custom avatar is set. In this case, you should use `default_avatar_url` instead. """ return routes.CDN_DEFAULT_USER_AVATAR.compile_to_file( @@ -601,10 +601,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 +612,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 +650,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 +673,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 +686,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 +714,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 +739,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. @@ -824,9 +824,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 +837,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 +888,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 +923,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. 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 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

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 027dcaeb75..1232583c25 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 0e917e74a1..170e9e6ffb 100644 --- a/pipelines/config.py +++ b/pipelines/config.py @@ -96,3 +96,5 @@ "docs", "changes", ) + +CODESPELL_IGNORE_WORDS = ("nd",) diff --git a/pipelines/format.nox.py b/pipelines/format.nox.py index 5a9fca5774..70d3caea88 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/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 221433f2e2..06ca094a70 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/scripts/prebuild_index.js b/scripts/prebuild_index.js deleted file mode 100644 index 608dd591bf..0000000000 --- a/scripts/prebuild_index.js +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2020 Nekokatt -// Copyright (c) 2021-present 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 adaf7da3d9..85795eeb35 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/impl/test_rest.py b/tests/hikari/impl/test_rest.py index f821e52419..a13d600fdc 100644 --- a/tests/hikari/impl/test_rest.py +++ b/tests/hikari/impl/test_rest.py @@ -3380,8 +3380,7 @@ async def test_add_user_to_guild_with_deprecated_nick_field(self, rest_client): rest_client._entity_factory.deserialize_member = mock.Mock(return_value=member) with pytest.warns( - DeprecationWarning, - match="'nick' is deprecated and will be removed in a following version. You can use 'nickname' instead.", + DeprecationWarning, match=r"Use of deprecated argument 'nick' \(Use 'nickname' argument instead\)" ): returned = await rest_client.add_user_to_guild( "token", @@ -4053,7 +4052,7 @@ async def test_edit_member_with_deprecated_nick_field(self, rest_client): with pytest.warns( DeprecationWarning, - match="'nick' is deprecated and will be removed in a following version. You can use 'nickname' instead.", + match=r"Use of deprecated argument 'nick' \(Use 'nickname' argument instead\)", ): result = await rest_client.edit_member(StubModel(123), StubModel(456), nick="eeeeeestrogen") diff --git a/tests/hikari/internal/test_deprecation.py b/tests/hikari/internal/test_deprecation.py index 06792f46e2..aaa4e9fe38 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"Use of deprecated argument 'testing' \(Use 'foo.bar' instead\)", + ): + deprecation.warn_deprecated("testing", "Use 'foo.bar' instead") diff --git a/tests/hikari/test_guilds.py b/tests/hikari/test_guilds.py index 551481e1a3..d08bd888a6 100644 --- a/tests/hikari/test_guilds.py +++ b/tests/hikari/test_guilds.py @@ -458,7 +458,7 @@ async def test_edit_when_deprecated_nick_field(self, model): with pytest.warns( DeprecationWarning, - match="'nick' is deprecated and will be removed in a following version. You can use 'nickname' instead.", + match=r"Use of deprecated argument 'nick' \(Use 'nickname' argument instead\)", ): edit = await model.edit(nick="meow") From 166f3c7cb91392451c10face88bf8016753ae061 Mon Sep 17 00:00:00 2001 From: davfsa Date: Sun, 6 Feb 2022 13:38:15 +0100 Subject: [PATCH 02/38] Multiple version documentation (#946) --- .github/workflows/ci.yml | 53 +- .github/workflows/fragments-check.yml | 4 +- .github/workflows/prepare-release.yml | 2 +- .github/workflows/release.yml | 9 +- .gitignore | 2 +- .../Jinja2 HTML Template.html.jinja2 | 34 + .idea/fileTemplates/Mako Template.mako | 34 - README.md | 10 +- dev-requirements.txt | 1 + docs/assets/github.svg | 3 + docs/assets/up.svg | 3 + docs/frame.html.jinja2 | 17 - docs/index.html.jinja2 | 57 +- docs/main.css | 443 -------- docs/module.html.jinja2 | 445 +++++--- docs/patched_pdoc.py | 34 +- docs/style.css | 991 ++++++++++++++++++ docs/syntax-highlighting.css | 559 ---------- hikari/_about.py | 2 +- hikari/api/cache.py | 36 +- hikari/api/interaction_server.py | 3 +- hikari/api/rest.py | 57 +- hikari/api/shard.py | 49 +- hikari/api/special_endpoints.py | 332 ++---- hikari/api/voice.py | 15 +- hikari/channels.py | 8 +- hikari/commands.py | 4 - hikari/embeds.py | 101 +- hikari/emojis.py | 8 +- hikari/errors.py | 19 +- hikari/events/base_events.py | 24 +- hikari/events/channel_events.py | 62 +- hikari/events/guild_events.py | 170 +-- hikari/events/lifetime_events.py | 4 +- hikari/events/member_events.py | 40 +- hikari/events/message_events.py | 191 +--- hikari/events/reaction_events.py | 124 +-- hikari/events/role_events.py | 32 +- hikari/events/shard_events.py | 95 +- hikari/events/typing_events.py | 40 +- hikari/events/user_events.py | 8 +- hikari/events/voice_events.py | 35 +- hikari/files.py | 11 +- hikari/guilds.py | 8 +- hikari/impl/bot.py | 1 + hikari/impl/buckets.py | 42 +- hikari/impl/event_manager.py | 1 + hikari/impl/interaction_server.py | 16 +- hikari/impl/rate_limits.py | 82 +- hikari/impl/rest.py | 12 +- hikari/impl/rest_bot.py | 20 +- hikari/impl/special_endpoints.py | 6 +- hikari/interactions/base_interactions.py | 29 +- hikari/internal/aio.py | 8 +- hikari/internal/attr_extensions.py | 10 +- hikari/internal/collections.py | 2 +- hikari/internal/data_binding.py | 14 +- hikari/internal/deprecation.py | 20 +- hikari/internal/enums.py | 15 +- hikari/internal/mentions.py | 4 +- hikari/internal/reflect.py | 10 +- hikari/internal/routes.py | 5 +- hikari/internal/ux.py | 26 +- hikari/invites.py | 14 +- hikari/iterators.py | 82 +- hikari/stickers.py | 2 +- hikari/traits.py | 169 +-- hikari/users.py | 2 +- hikari/webhooks.py | 96 +- pages/CNAME | 1 - pages/index.html | 375 ------- pages/logo.png | Bin 153709 -> 0 bytes pipelines/config.py | 2 +- pipelines/format.nox.py | 2 +- pipelines/nox.py | 8 +- pipelines/pages.nox.py | 128 --- pipelines/pdoc.nox.py | 57 +- scripts/deploy-manually.sh | 9 +- scripts/deploy-pages-manually.sh | 44 + scripts/deploy-pages.sh | 51 +- scripts/deploy-webhook.sh | 2 +- scripts/deploy.sh | 9 +- scripts/prepare-release.sh | 1 + tests/hikari/internal/test_routes.py | 60 +- tests/hikari/internal/test_ux.py | 4 +- 85 files changed, 2212 insertions(+), 3408 deletions(-) create mode 100644 .idea/fileTemplates/Jinja2 HTML Template.html.jinja2 delete mode 100644 .idea/fileTemplates/Mako Template.mako create mode 100644 docs/assets/github.svg create mode 100644 docs/assets/up.svg delete mode 100644 docs/frame.html.jinja2 delete mode 100644 docs/main.css create mode 100644 docs/style.css delete mode 100644 docs/syntax-highlighting.css delete mode 100644 pages/CNAME delete mode 100644 pages/index.html delete mode 100644 pages/logo.png delete mode 100644 pipelines/pages.nox.py create mode 100644 scripts/deploy-pages-manually.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee0c61b11a..99fbceff85 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - name: Checkout repo + - name: Checkout repository uses: actions/checkout@v3 - name: Setup python @@ -56,7 +56,7 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout repo + - name: Checkout repository uses: actions/checkout@v3 - name: Setup python @@ -86,7 +86,7 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout repo + - name: Checkout repository uses: actions/checkout@v3 - name: Setup python @@ -113,7 +113,7 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout repo + - name: Checkout repository uses: actions/checkout@v3 - name: Setup python @@ -126,11 +126,11 @@ jobs: pip install nox nox -s twemoji-test - pages: + docs: runs-on: ubuntu-latest steps: - - name: Checkout repo + - name: Checkout repository uses: actions/checkout@v3 - name: Setup python @@ -141,24 +141,48 @@ jobs: - name: Setup node uses: actions/setup-node@v3 - - name: Build pages + - name: Build documentation run: | pip install nox nox -s pdoc - name: Upload artifacts - if: github.event_name != 'release' + if: github.event.event_name == 'pull_request' uses: actions/upload-artifact@v2 with: - name: pages - path: public + name: docs + path: public/docs retention-days: 2 if-no-files-found: error - # Allows us to add this as a required check in Github branch rules, as all the - # other jobs are subject to change + deploy-master-docs: + if: github.event.event_name == 'push' && endsWith(github.ref, 'master') + needs: [docs] + + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2.4.0 + + - name: Setup python + uses: actions/setup-python@v2.3.2 + with: + python-version: 3.8 + + - name: Update master docs + env: + GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} + REPO_SLUG: ${{ github.repository }} + DOCUMENTATION_REPO_SLUG: hikari-py/hikari-docs + run: | + pip install nox + bash scripts/deploy-master-pages.sh + + # Allows us to add this as a required check in Github branch rules, as all the other jobs are subject to change ci-done: - needs: [upload-coverage, linting, twemoji, pages] + # All new need jobs need to also be added to "env" with the prefix "RESULT_" + needs: [upload-coverage, linting, twemoji, docs] if: always() runs-on: ubuntu-latest @@ -166,11 +190,10 @@ jobs: steps: - name: Mark status based on past job status env: - # All new need jobs need to be added here with the prefix "RESULT_" RESULT_UPLOAD_COVERAGE: ${{ needs.upload-coverage.result }} RESULT_LINTING: ${{ needs.linting.result }} RESULT_TWEMOJI: ${{ needs.twemoji.result }} - RESULT_PAGES: ${{ needs.pages.result }} + RESULT_DOCS: ${{ needs.docs.result }} run: | if [ "$(env | grep 'RESULT_')" = "$(env | grep "RESULT_" | grep '=success')" ]; then exit 0 diff --git a/.github/workflows/fragments-check.yml b/.github/workflows/fragments-check.yml index 5f40c08c16..2f3d40be8d 100644 --- a/.github/workflows/fragments-check.yml +++ b/.github/workflows/fragments-check.yml @@ -12,12 +12,12 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout repo + - name: Checkout repository uses: actions/checkout@v3 with: # `towncrier check` runs `git diff --name-only origin/main...`, which # needs a non-shallow clone. - fetch-depth: 0 + fetch-depth: 2 - name: Setup python uses: actions/setup-python@v3 diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 91b1dfb02a..750b931e73 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout repo + - name: Checkout repository uses: actions/checkout@v3 - name: Setup python diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 73f30e0ad5..06cd6d1684 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout repo + - name: Checkout repository uses: actions/checkout@v3 with: token: ${{ secrets.PAT_TOKEN }} @@ -25,7 +25,7 @@ jobs: git config --global user.email "90276125+hikari-bot[bot]@users.noreply.github.com" - name: Temporarily disable "include administrators" branch protection - uses: benjefferies/branch-protection-bot@master + uses: benjefferies/branch-protection-bot@1.0.7 with: access_token: ${{ secrets.PAT_TOKEN }} enforce_admins: false @@ -35,14 +35,15 @@ jobs: GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} VERSION: ${{ github.event.release.tag_name }} REF: ${{ github.sha }} - GITHUB_REPO_SLUG: ${{ github.repository }} + REPO_SLUG: ${{ github.repository }} + DOCUMENTATION_REPO_SLUG: hikari-py/hikari-docs DEPLOY_WEBHOOK_URL: ${{ secrets.DEPLOY_WEBHOOK_URL }} TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} run: bash scripts/deploy.sh - name: Re-enable "include administrators" branch protection - uses: benjefferies/branch-protection-bot@master + uses: benjefferies/branch-protection-bot@1.0.7 if: always() with: access_token: ${{ secrets.PAT_TOKEN }} diff --git a/.gitignore b/.gitignore index a2c4d32f18..e72a499eab 100644 --- a/.gitignore +++ b/.gitignore @@ -64,4 +64,4 @@ prof/ # Logs *.log - Pipfile* +Pipfile* diff --git a/.idea/fileTemplates/Jinja2 HTML Template.html.jinja2 b/.idea/fileTemplates/Jinja2 HTML Template.html.jinja2 new file mode 100644 index 0000000000..3f4ec8748e --- /dev/null +++ b/.idea/fileTemplates/Jinja2 HTML Template.html.jinja2 @@ -0,0 +1,34 @@ +{# +Copyright (c) 2020 Nekokatt +Copyright (c) 2021-present 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. +#} + + + + + + + {% block title %}{% endblock %} + {% block head %}{% endblock %} + {% block style %}{% endblock %} + +{% block body %}{% endblock %} + diff --git a/.idea/fileTemplates/Mako Template.mako b/.idea/fileTemplates/Mako Template.mako deleted file mode 100644 index e9bfded947..0000000000 --- a/.idea/fileTemplates/Mako Template.mako +++ /dev/null @@ -1,34 +0,0 @@ -## Copyright (c) 2020 Nekokatt -## Copyright (c) 2021-present 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. - -<% - title = "Hello, World!" -%> - - - - ${title} - - -

${title}

-

${title}

- - \ No newline at end of file diff --git a/README.md b/README.md index b7f11d2db3..467e530bed 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 @@ -72,9 +72,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://www.hikari-py.dev/hikari/impl/bot.html#hikari.impl.bot.GatewayBot) such as -[customising timeouts for requests](https://www.hikari-py.dev/hikari/impl/config.html#hikari.impl.config.HTTPSettings.timeouts) -and [enabling a proxy](https://www.hikari-py.dev/hikari/impl/config.html#hikari.impl.config.ProxySettings). +[Other options also exist](https://docs.hikari-py.dev/stable/hikari/impl/bot.html#GatewayBot) such as +[customising timeouts for requests](https://docs.hikari-py.dev/stable/hikari/impl/config.html#HTTPSettings.timeouts) +and [enabling a proxy](https://docs.hikari-py.dev/stable/hikari/impl/config.html#ProxySettings). Also note that you could pass extra options to `bot.run` during development, for example: @@ -89,7 +89,7 @@ bot.run( ) ``` -[Many other helpful options](https://www.hikari-py.dev/hikari/impl/bot.html#hikari.impl.bot.GatewayBot.run) +[Many other helpful options](https://www.hikari-py.dev/docs/stable/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 060c89d684..c42d26967d 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -23,6 +23,7 @@ async-timeout==4.0.2 # Used for timeouts in some test cases. pdoc==10.0.4 sphobjinv==2.2.2 +minify-html==0.8 ################# # TYPE CHECKING # diff --git a/docs/assets/github.svg b/docs/assets/github.svg new file mode 100644 index 0000000000..fc6ff7116d --- /dev/null +++ b/docs/assets/github.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/assets/up.svg b/docs/assets/up.svg new file mode 100644 index 0000000000..c027461560 --- /dev/null +++ b/docs/assets/up.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/frame.html.jinja2 b/docs/frame.html.jinja2 deleted file mode 100644 index 0f78bf10e6..0000000000 --- a/docs/frame.html.jinja2 +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - {% block title %}{% endblock %} | v{{ __hikari_version__ }} - - - {% block head %}{% endblock %} - {% block style %} - - {% endblock %} - - -{% block body %}{% endblock %} - diff --git a/docs/index.html.jinja2 b/docs/index.html.jinja2 index 698be2ca59..c0272dcee1 100644 --- a/docs/index.html.jinja2 +++ b/docs/index.html.jinja2 @@ -1,14 +1,45 @@ -{# This site will never be publicly available, its just for developers sake #} -{% extends "default/index.html.jinja2" %} - + + + + + + + Redirecting… + + +

Redirecting…

+ Click here if you are not redirected. + + diff --git a/docs/main.css b/docs/main.css deleted file mode 100644 index b9c5c85baa..0000000000 --- a/docs/main.css +++ /dev/null @@ -1,443 +0,0 @@ -: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 index 7fc97a5002..462492b568 100644 --- a/docs/module.html.jinja2 +++ b/docs/module.html.jinja2 @@ -1,56 +1,91 @@ -{# This file expands on pdoc's module template #} -{% extends "default/module.html.jinja2" %} +{# +Copyright (c) 2020 Nekokatt +Copyright (c) 2021-present davfsa -{# ##### #} -{# Theme #} -{# ##### #} -{% block style %} - {% filter minify_css %} - - - - {% endfilter %} -{% endblock %} +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: -{# ######################## #} -{# 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 }} +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. +#} +{# ---- MACRO DEFINITIONS BEGIN ---- #} +{% macro bases(cls) %} + {%- if cls.bases -%} + ( + {%- for base in cls.bases -%} + {{ base[:2] | link(text=base[2]) }} + {%- if loop.nextitem %}, {% endif %} + {%- endfor -%} + ) + {%- endif -%} +{% endmacro %} +{% macro annotation(var) %} + {%- if var.annotation_str -%} + {{ var.annotation_str | escape | linkify }} + {%- endif -%} +{% endmacro %} +{% macro decorators(doc) %} + {% for d in doc.decorators if not d.startswith("@_") %} +
{{ d }}
+ {% endfor %} +{% endmacro %} +{% macro headerlink(doc) -%} + #   +{% endmacro %} +{% macro function(fn) -%} +
{{ headerlink(fn) }} + {{ decorators(fn) }} + {{ fn.funcdef }}  + {#- no space -#} + {{ fn.name }} + {#- no space -#} + {% autoescape false %} + {# Extreemly hacky solution, but it makes it work nicely with minify_html #} + {{ fn.signature | escape | replace("\n", "
") | replace("
", "
  ") | linkify }}:
+ {% endautoescape %}
-{% endif %} -{% endfor %} {% endmacro %} - - -{# #################### #} -{# Remove default value #} -{# #################### #} {% macro variable(var) %}
{{ headerlink(var) }} {{ var.name }}{{ annotation(var) }}
{% endmacro %} - -{# ######### #} -{# Inventory #} -{# ######### #} -{% macro member(doc) %} - {{ add_to_inventory(doc) }} +{% macro submodule(mod) %} +
{{ headerlink(mod) }}{{ mod.taken_from | link }}
+{% endmacro %} +{% macro class(cls) %} +
+ {{ headerlink(cls) }} + {{ decorators(cls) }} + class  + {#- no space -#} + {{ cls.qualname }} + {{- bases(cls) -}}: +
+{% endmacro %} +{% macro member(doc, should_add_to_inventory = true) %} + {% if add_to_inventory is defined and should_add_to_inventory %} + {{ add_to_inventory(doc) }} + {% endif %} {% if doc.type == "class" %} {{ class(doc) }} + {% if add_to_inventory is defined and should_add_to_inventory %} + {% for m in doc.members.values() %} + {{ add_to_inventory(m) }} + {% endfor %} + {% endif %} {% elif doc.type == "function" %} {{ function(doc) }} {% elif doc.type == "module" %} @@ -58,95 +93,265 @@ {% else %} {{ variable(doc) }} {% endif %} - {% if doc.type != "variable" %} - {{ view_source(doc) }} - {% endif %} + {{ view_source(doc) }} {{ docstring(doc) }} {% endmacro %} - -{# ############## #} -{# Better sidebar #} -{# ############## #} -{% block nav %} -{{ add_to_inventory(module) if module.name != "" }} - -
+
{% include "assets/up.svg" %} Back to top From d56d12fbf0c330fe346d6e31daaa3eb4852e312d Mon Sep 17 00:00:00 2001 From: davfsa Date: Thu, 31 Mar 2022 09:19:14 +0200 Subject: [PATCH 05/38] Change master docs workflow to be requested instead of on every commit --- .github/workflows/ci.yml | 24 ------------------ .github/workflows/deploy-master-docs.yml | 31 ++++++++++++++++++++++++ .github/workflows/prepare-release.yml | 2 ++ scripts/deploy-pages.sh | 12 +++++---- 4 files changed, 40 insertions(+), 29 deletions(-) create mode 100644 .github/workflows/deploy-master-docs.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99fbceff85..be4a264015 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -155,30 +155,6 @@ jobs: retention-days: 2 if-no-files-found: error - deploy-master-docs: - if: github.event.event_name == 'push' && endsWith(github.ref, 'master') - needs: [docs] - - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v2.4.0 - - - name: Setup python - uses: actions/setup-python@v2.3.2 - with: - python-version: 3.8 - - - name: Update master docs - env: - GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} - REPO_SLUG: ${{ github.repository }} - DOCUMENTATION_REPO_SLUG: hikari-py/hikari-docs - run: | - pip install nox - bash scripts/deploy-master-pages.sh - # Allows us to add this as a required check in Github branch rules, as all the other jobs are subject to change ci-done: # All new need jobs need to also be added to "env" with the prefix "RESULT_" diff --git a/.github/workflows/deploy-master-docs.yml b/.github/workflows/deploy-master-docs.yml new file mode 100644 index 0000000000..9f57a31bac --- /dev/null +++ b/.github/workflows/deploy-master-docs.yml @@ -0,0 +1,31 @@ +name: Deploy master docs + +on: + workflow_dispatch: + +jobs: + deploy-master-docs: + runs-on: ubuntu-latest + + if: github.ref == 'refs/heads/master' + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup python + uses: actions/setup-python@v3 + with: + python-version: 3.8 + + - name: Setup node + uses: actions/setup-node@v3 + + - name: Deploy master docs + env: + VERSION: master + GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} + REPO_SLUG: ${{ github.repository }} + DOCUMENTATION_REPO_SLUG: hikari-py/hikari-docs + run: | + bash scripts/deploy-pages.sh diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 750b931e73..1c55d72ba5 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -11,6 +11,8 @@ jobs: prepare-release: runs-on: ubuntu-latest + if: github.ref == 'refs/heads/master' + steps: - name: Checkout repository uses: actions/checkout@v3 diff --git a/scripts/deploy-pages.sh b/scripts/deploy-pages.sh index bca809ab2d..1eb933e381 100755 --- a/scripts/deploy-pages.sh +++ b/scripts/deploy-pages.sh @@ -25,8 +25,6 @@ env | grep -oP "^[^=]+" | sort if [ -z ${VERSION+x} ]; then echo '$VERSION environment variable is missing' && exit 1; fi if [ -z "${VERSION}" ]; then echo '$VERSION environment variable is empty' && exit 1; fi -if [ -z ${REF+x} ]; then echo '$REF environment variable is missing' && exit 1; fi -if [ -z "${REF}" ]; then echo '$REF environment variable is empty' && exit 1; fi if [ -z ${GITHUB_TOKEN+x} ]; then echo '$GITHUB_TOKEN environment variable is missing' && exit 1; fi if [ -z "${GITHUB_TOKEN}" ]; then echo '$GITHUB_TOKEN environment variable is empty' && exit 1; fi if [ -z ${REPO_SLUG+x} ]; then echo '$REPO_SLUG environment variable is missing' && exit 1; fi @@ -35,6 +33,9 @@ if [ -z ${DOCUMENTATION_REPO_SLUG+x} ]; then echo '$DOCUMENTATION_REPO_SLUG envi if [ -z "${DOCUMENTATION_REPO_SLUG}" ]; then echo '$DOCUMENTATION_REPO_SLUG environment variable is empty' && exit 1; fi if [ "${VERSION}" != "master" ]; then + if [ -z ${REF+x} ]; then echo '$REF environment variable is missing' && exit 1; fi + if [ -z "${REF}" ]; then echo '$REF environment variable is empty' && exit 1; fi + regex='__version__: typing\.Final\[str\] = "([^"]*)"' if [[ $(cat hikari/_about.py) =~ $regex ]]; then if [ "${BASH_REMATCH[1]}" != "${VERSION}" ]; then @@ -43,6 +44,8 @@ if [ "${VERSION}" != "master" ]; then else echo "Variable '__version__' not found in about!" && exit 1 fi +else + REF="MASTER" fi rm -rf public @@ -54,8 +57,7 @@ sed "/^__git_sha1__.*/, \${s||__git_sha1__: typing.Final[str] = \"${REF}\"|g; b} nox -s pdoc cd public/docs || exit 1 -# We do it here before we create the empty repository -if [ "${REF}" == "MASTER" ]; then +if [ "${VERSION}" == "master" ]; then REF="$(git rev-parse HEAD)" fi @@ -66,4 +68,4 @@ git remote add origin "https://git:${GITHUB_TOKEN}@github.com/${DOCUMENTATION_RE git checkout -B "release/${VERSION}" git add -Av . git commit -am "Documentation for ${VERSION} [https://github.com/${REPO_SLUG}/commit/${REF}]" -git push -u origin "release/${VERSION}" -f +git push -u origin "release/${VERSION}/${REF}" -f From d7e0ff200472ab7050a4cadf9e5ac786e9aabfda Mon Sep 17 00:00:00 2001 From: davfsa Date: Thu, 31 Mar 2022 09:21:39 +0200 Subject: [PATCH 06/38] Fix link in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 467e530bed..5035dffd77 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ bot.run( ) ``` -[Many other helpful options](https://www.hikari-py.dev/docs/stable/hikari/impl/bot.html#hikari.impl.bot.GatewayBot.run) +[Many other helpful options](https://docs.hikari-py.dev/stable/hikari/impl/bot.html#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 From 1e92c307816ba8d28d792914beffb33cdf4a3633 Mon Sep 17 00:00:00 2001 From: davfsa Date: Thu, 31 Mar 2022 16:02:55 +0200 Subject: [PATCH 07/38] Remove crosslinkage between both repositories --- .github/workflows/deploy-master-docs.yml | 31 -------------- scripts/deploy-manually.sh | 8 ++-- scripts/deploy-pages-manually.sh | 11 +---- scripts/deploy-pages.sh | 51 ++++++------------------ 4 files changed, 17 insertions(+), 84 deletions(-) delete mode 100644 .github/workflows/deploy-master-docs.yml diff --git a/.github/workflows/deploy-master-docs.yml b/.github/workflows/deploy-master-docs.yml deleted file mode 100644 index 9f57a31bac..0000000000 --- a/.github/workflows/deploy-master-docs.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Deploy master docs - -on: - workflow_dispatch: - -jobs: - deploy-master-docs: - runs-on: ubuntu-latest - - if: github.ref == 'refs/heads/master' - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Setup python - uses: actions/setup-python@v3 - with: - python-version: 3.8 - - - name: Setup node - uses: actions/setup-node@v3 - - - name: Deploy master docs - env: - VERSION: master - GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} - REPO_SLUG: ${{ github.repository }} - DOCUMENTATION_REPO_SLUG: hikari-py/hikari-docs - run: | - bash scripts/deploy-pages.sh diff --git a/scripts/deploy-manually.sh b/scripts/deploy-manually.sh index 999636689a..6f25380e02 100755 --- a/scripts/deploy-manually.sh +++ b/scripts/deploy-manually.sh @@ -28,13 +28,13 @@ posix_read() { return ${?} } -posix_read "Twine username" TWINE_USERNAME -posix_read "Twine password" TWINE_PASSWORD -posix_read "GitHub deploy token (must have permissions to push to the documentation repository)" GITHUB_TOKEN -posix_read "Discord deployment webhook URL" DEPLOY_WEBHOOK_URL posix_read "Tag" VERSION posix_read "Repository slug (e.g. hikari-py/hikari)" REPO_SLUG posix_read "Documentation repository slug (e.g. hikari-py/hikari-docs)" DOCUMENTATION_REPO_SLUG +posix_read "Twine username" TWINE_USERNAME +posix_read "Twine password" TWINE_PASSWORD +posix_read "Github token (must have permissions to push to the documentation repository and trigger workflows)" GITHUB_TOKEN +posix_read "Discord deployment webhook URL" DEPLOY_WEBHOOK_URL git checkout "${VERSION}" export REF=$(git rev-parse HEAD) diff --git a/scripts/deploy-pages-manually.sh b/scripts/deploy-pages-manually.sh index 71cde4d992..b7858ba782 100644 --- a/scripts/deploy-pages-manually.sh +++ b/scripts/deploy-pages-manually.sh @@ -29,16 +29,7 @@ posix_read() { } posix_read "Tag ('master' for master documentation)" VERSION -posix_read "GitHub deploy token (must have permissions to push to the documentation repository)" GITHUB_TOKEN -posix_read "Repository slug (e.g. hikari-py/hikari)" REPO_SLUG posix_read "Documentation repository slug (e.g. hikari-py/hikari-docs)" DOCUMENTATION_REPO_SLUG - -if [ "${VERSION}" != "master" ]; then - git checkout "${VERSION}" - export REF=$(git rev-parse HEAD) - echo "Detected REF to be ${REF}" -else - export REF="MASTER" -fi +posix_read "Github token (must have permissions to push to the documentation repository and trigger workflows)" GITHUB_TOKEN bash scripts/deploy-pages.sh diff --git a/scripts/deploy-pages.sh b/scripts/deploy-pages.sh index 1eb933e381..4ae7b0553b 100755 --- a/scripts/deploy-pages.sh +++ b/scripts/deploy-pages.sh @@ -27,45 +27,18 @@ if [ -z ${VERSION+x} ]; then echo '$VERSION environment variable is missing' && if [ -z "${VERSION}" ]; then echo '$VERSION environment variable is empty' && exit 1; fi if [ -z ${GITHUB_TOKEN+x} ]; then echo '$GITHUB_TOKEN environment variable is missing' && exit 1; fi if [ -z "${GITHUB_TOKEN}" ]; then echo '$GITHUB_TOKEN environment variable is empty' && exit 1; fi -if [ -z ${REPO_SLUG+x} ]; then echo '$REPO_SLUG environment variable is missing' && exit 1; fi -if [ -z "${REPO_SLUG}" ]; then echo '$REPO_SLUG environment variable is empty' && exit 1; fi if [ -z ${DOCUMENTATION_REPO_SLUG+x} ]; then echo '$DOCUMENTATION_REPO_SLUG environment variable is missing' && exit 1; fi if [ -z "${DOCUMENTATION_REPO_SLUG}" ]; then echo '$DOCUMENTATION_REPO_SLUG environment variable is empty' && exit 1; fi -if [ "${VERSION}" != "master" ]; then - if [ -z ${REF+x} ]; then echo '$REF environment variable is missing' && exit 1; fi - if [ -z "${REF}" ]; then echo '$REF environment variable is empty' && exit 1; fi - - regex='__version__: typing\.Final\[str\] = "([^"]*)"' - if [[ $(cat hikari/_about.py) =~ $regex ]]; then - if [ "${BASH_REMATCH[1]}" != "${VERSION}" ]; then - echo "Variable '__version__' does not match the version this deploy is for! [__version__='${BASH_REMATCH[1]}'; VERSION='${VERSION}']" && exit 1 - fi - else - echo "Variable '__version__' not found in about!" && exit 1 - fi -else - REF="MASTER" -fi - -rm -rf public -mkdir public - -echo "-- Setting __git_sha1__ to '${REF}' --" -sed "/^__git_sha1__.*/, \${s||__git_sha1__: typing.Final[str] = \"${REF}\"|g; b}; \$q1" -i hikari/_about.py || (echo "Variable '__git_sha1__' not found in about!" && exit 1) - -nox -s pdoc -cd public/docs || exit 1 - -if [ "${VERSION}" == "master" ]; then - REF="$(git rev-parse HEAD)" -fi - -echo "===== DEPLOYING PAGES =====" -git init -git remote add origin "https://git:${GITHUB_TOKEN}@github.com/${DOCUMENTATION_REPO_SLUG}" - -git checkout -B "release/${VERSION}" -git add -Av . -git commit -am "Documentation for ${VERSION} [https://github.com/${REPO_SLUG}/commit/${REF}]" -git push -u origin "release/${VERSION}/${REF}" -f +echo "===== TRIGGERING WORKFLOW IN ${DOCUMENTATION_REPO_SLUG} =====" +curl \ + -X POST \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/${DOCUMENTATION_REPO_SLUG}/actions/workflows/deploy-docs.yml/dispatches" \ + -d '{ + "ref": "master", + "inputs": { + "version": "'"${VERSION}"'" + } + }' From ae0c333f699a2ede8c03f9a613469f65dc413e52 Mon Sep 17 00:00:00 2001 From: davfsa Date: Thu, 31 Mar 2022 16:06:51 +0200 Subject: [PATCH 08/38] Fix posix_read description for GITHUB_TOKEN --- scripts/deploy-manually.sh | 2 +- scripts/deploy-pages-manually.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/deploy-manually.sh b/scripts/deploy-manually.sh index 6f25380e02..1545d1630c 100755 --- a/scripts/deploy-manually.sh +++ b/scripts/deploy-manually.sh @@ -33,7 +33,7 @@ posix_read "Repository slug (e.g. hikari-py/hikari)" REPO_SLUG posix_read "Documentation repository slug (e.g. hikari-py/hikari-docs)" DOCUMENTATION_REPO_SLUG posix_read "Twine username" TWINE_USERNAME posix_read "Twine password" TWINE_PASSWORD -posix_read "Github token (must have permissions to push to the documentation repository and trigger workflows)" GITHUB_TOKEN +posix_read "Github token (must have permissions to trigger workflows in the documentation repository)" GITHUB_TOKEN posix_read "Discord deployment webhook URL" DEPLOY_WEBHOOK_URL git checkout "${VERSION}" diff --git a/scripts/deploy-pages-manually.sh b/scripts/deploy-pages-manually.sh index b7858ba782..4bc906cc4c 100644 --- a/scripts/deploy-pages-manually.sh +++ b/scripts/deploy-pages-manually.sh @@ -30,6 +30,6 @@ posix_read() { posix_read "Tag ('master' for master documentation)" VERSION posix_read "Documentation repository slug (e.g. hikari-py/hikari-docs)" DOCUMENTATION_REPO_SLUG -posix_read "Github token (must have permissions to push to the documentation repository and trigger workflows)" GITHUB_TOKEN +posix_read "Github token (must have permissions to trigger workflows in the documentation repository)" GITHUB_TOKEN bash scripts/deploy-pages.sh From e3220b2812b126726ff0aaff63d9ffc422d4480d Mon Sep 17 00:00:00 2001 From: davfsa Date: Thu, 31 Mar 2022 18:18:05 +0200 Subject: [PATCH 09/38] Use github app token instead of public access token --- .github/workflows/prepare-release.yml | 1 - .github/workflows/release.yml | 16 +++++++++++----- scripts/deploy-manually.sh | 4 ---- scripts/deploy.sh | 4 ++-- scripts/prepare-release.sh | 2 -- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 1c55d72ba5..10dbea466b 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -37,7 +37,6 @@ jobs: - name: Run prepare script env: VERSION: ${{ github.event.inputs.version }} - GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} run: bash scripts/prepare-release.sh - name: Create pull request diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 06cd6d1684..10aa44ccca 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,10 +9,17 @@ jobs: runs-on: ubuntu-latest steps: + - name: Generate token + id: generate_token + uses: tibdex/github-app-token@v1 + with: + app_id: ${{ secrets.APP_ID }} + private_key: ${{ secrets.PRIVATE_KEY }} + - name: Checkout repository uses: actions/checkout@v3 with: - token: ${{ secrets.PAT_TOKEN }} + token: ${{ steps.generate_token.outputs.token }} - name: Setup python uses: actions/setup-python@v3 @@ -27,14 +34,13 @@ jobs: - name: Temporarily disable "include administrators" branch protection uses: benjefferies/branch-protection-bot@1.0.7 with: - access_token: ${{ secrets.PAT_TOKEN }} + access_token: ${{ steps.generate_token.outputs.token }} enforce_admins: false - name: Deploy env: - GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} + GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} VERSION: ${{ github.event.release.tag_name }} - REF: ${{ github.sha }} REPO_SLUG: ${{ github.repository }} DOCUMENTATION_REPO_SLUG: hikari-py/hikari-docs DEPLOY_WEBHOOK_URL: ${{ secrets.DEPLOY_WEBHOOK_URL }} @@ -46,5 +52,5 @@ jobs: uses: benjefferies/branch-protection-bot@1.0.7 if: always() with: - access_token: ${{ secrets.PAT_TOKEN }} + access_token: ${{ steps.generate_token.outputs.token }} enforce_admins: true diff --git a/scripts/deploy-manually.sh b/scripts/deploy-manually.sh index 1545d1630c..3480743fd6 100755 --- a/scripts/deploy-manually.sh +++ b/scripts/deploy-manually.sh @@ -36,8 +36,4 @@ posix_read "Twine password" TWINE_PASSWORD posix_read "Github token (must have permissions to trigger workflows in the documentation repository)" GITHUB_TOKEN posix_read "Discord deployment webhook URL" DEPLOY_WEBHOOK_URL -git checkout "${VERSION}" -export REF=$(git rev-parse HEAD) -echo "Detected REF to be ${REF}" - bash scripts/deploy.sh diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 855116a885..52e6faac09 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -30,8 +30,6 @@ env | grep -oP "^[^=]+" | sort if [ -z ${VERSION+x} ]; then echo '$VERSION environment variable is missing' && exit 1; fi if [ -z "${VERSION}" ]; then echo '$VERSION environment variable is empty' && exit 1; fi -if [ -z ${REF+x} ]; then echo '$REF environment variable is missing' && exit 1; fi -if [ -z "${REF}" ]; then echo '$REF environment variable is empty' && exit 1; fi if [ -z ${GITHUB_TOKEN+x} ]; then echo '$GITHUB_TOKEN environment variable is missing' && exit 1; fi if [ -z "${GITHUB_TOKEN}" ]; then echo '$GITHUB_TOKEN environment variable is empty' && exit 1; fi if [ -z ${REPO_SLUG+x} ]; then echo '$REPO_SLUG environment variable is missing' && exit 1; fi @@ -63,6 +61,8 @@ pip install twine pip install nox pip install -r requirements.txt +REF=$(git rev-parse HEAD) + echo "===== DEPLOYING TO PYPI =====" echo "-- Setting __git_sha1__ (ref: ${REF}) --" sed "/^__git_sha1__.*/, \${s||__git_sha1__: typing.Final[str] = \"${REF}\"|g; b}; \$q1" -i hikari/_about.py || (echo "Variable '__git_sha1__' not found in about!" && exit 1) diff --git a/scripts/prepare-release.sh b/scripts/prepare-release.sh index d55f8aca2c..f71f08de94 100644 --- a/scripts/prepare-release.sh +++ b/scripts/prepare-release.sh @@ -26,8 +26,6 @@ env | grep -oP "^[^=]+" | sort if [ -z ${VERSION+x} ]; then echo '$VERSION environment variable is missing' && exit 1; fi if [ -z "${VERSION}" ]; then echo '$VERSION environment variable is empty' && exit 1; fi -if [ -z ${GITHUB_TOKEN+x} ]; then echo '$GITHUB_TOKEN environment variable is missing' && exit 1; fi -if [ -z "${GITHUB_TOKEN}" ]; then echo '$GITHUB_TOKEN environment variable is empty' && exit 1; fi echo "===== INSTALLING DEPENDENCIES =====" pip install towncrier From 9d13d5ceca4a388efa9819a890469d1ad7905802 Mon Sep 17 00:00:00 2001 From: davfsa Date: Tue, 5 Apr 2022 20:09:48 +0200 Subject: [PATCH 10/38] Remove "Returns" section from properties --- hikari/api/special_endpoints.py | 21 ++------------------- hikari/guilds.py | 8 +------- hikari/snowflakes.py | 8 +------- 3 files changed, 4 insertions(+), 33 deletions(-) diff --git a/hikari/api/special_endpoints.py b/hikari/api/special_endpoints.py index 5cbbe7d83f..c9ea7a2d8e 100644 --- a/hikari/api/special_endpoints.py +++ b/hikari/api/special_endpoints.py @@ -876,13 +876,7 @@ def name(self) -> str: @property @abc.abstractmethod def type(self) -> commands.CommandType: - """Return the type of this command. - - Returns - ------- - hikari.commands.CommandType - The type of this command. - """ + """Type of this command.""" @property @abc.abstractmethod @@ -983,23 +977,12 @@ def description(self) -> str: .. warning:: This should be inclusively between 1-100 characters in length. - - Returns - ------- - 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.""" @abc.abstractmethod def add_option(self: _T, option: commands.CommandOption) -> _T: diff --git a/hikari/guilds.py b/hikari/guilds.py index 31599789c8..033d1b37ed 100644 --- a/hikari/guilds.py +++ b/hikari/guilds.py @@ -1114,13 +1114,7 @@ def colour(self) -> colours.Colour: @property def icon_url(self) -> typing.Optional[files.URL]: - """Role icon URL, if there is one. - - Returns - ------- - typing.Optional[hikari.files.URL] - The URL, or `None` if no icon exists. - """ + """Role 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]: diff --git a/hikari/snowflakes.py b/hikari/snowflakes.py index 5567843130..219d75018c 100644 --- a/hikari/snowflakes.py +++ b/hikari/snowflakes.py @@ -122,13 +122,7 @@ class Unique(abc.ABC): @property @abc.abstractmethod def id(self) -> Snowflake: - """Return the ID of this entity. - - Returns - ------- - Snowflake - The snowflake ID of this object. - """ + """ID of this entity.""" @property def created_at(self) -> datetime.datetime: From d611c5d197a9481bdd76db7bc20182b8232bf142 Mon Sep 17 00:00:00 2001 From: davfsa Date: Sat, 9 Apr 2022 18:00:25 +0200 Subject: [PATCH 11/38] PR feedback --- .github/workflows/ci.yml | 2 +- .github/workflows/prepare-release.yml | 7 ------- dev-requirements.txt | 2 +- docs/module.html.jinja2 | 2 +- docs/patched_pdoc.py | 2 +- pipelines/config.py | 1 - pipelines/pdoc.nox.py | 2 +- 7 files changed, 5 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index be4a264015..889e452f59 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -147,7 +147,7 @@ jobs: nox -s pdoc - name: Upload artifacts - if: github.event.event_name == 'pull_request' + if: github.event_name == 'pull_request' uses: actions/upload-artifact@v2 with: name: docs diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 10dbea466b..94ca7a3930 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -27,13 +27,6 @@ jobs: git config --global user.name "hikari-bot" git config --global user.email "90276125+hikari-bot[bot]@users.noreply.github.com" - - name: Generate token - id: generate_token - uses: tibdex/github-app-token@v1 - with: - app_id: ${{ secrets.APP_ID }} - private_key: ${{ secrets.PRIVATE_KEY }} - - name: Run prepare script env: VERSION: ${{ github.event.inputs.version }} diff --git a/dev-requirements.txt b/dev-requirements.txt index c42d26967d..f583e01337 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 # ################# -pdoc==10.0.4 +pdoc==11.0.0 sphobjinv==2.2.2 minify-html==0.8 diff --git a/docs/module.html.jinja2 b/docs/module.html.jinja2 index 0cd0e36e92..7b7e98b68b 100644 --- a/docs/module.html.jinja2 +++ b/docs/module.html.jinja2 @@ -269,7 +269,7 @@ SOFTWARE. {% endfor %}
- {# These shoul only load on release builds #} + {# These should only load on release builds #} {% if __hikari_version__ != "head" %} {% endif %} diff --git a/docs/patched_pdoc.py b/docs/patched_pdoc.py index 2497f7ed4e..fbfe9e2c3e 100644 --- a/docs/patched_pdoc.py +++ b/docs/patched_pdoc.py @@ -40,7 +40,7 @@ def patched_html_module(*args, **kwargs) -> str: - # We patch this to minify the HTML output before passing writing to the file + # We patch this to minify the HTML output before sending it further output = pdoc_html_module(*args, **kwargs) return minify_html.minify(output, minify_js=True, minify_css=True) diff --git a/pipelines/config.py b/pipelines/config.py index f724df8189..b0c4baa14d 100644 --- a/pipelines/config.py +++ b/pipelines/config.py @@ -92,7 +92,6 @@ *PYTHON_REFORMATTING_PATHS, *(f for f in _os.listdir(".") if _os.path.isfile(f) and f.endswith(REFORMATTING_FILE_EXTS)), ".github", - "pages", "docs", "changes", ) diff --git a/pipelines/pdoc.nox.py b/pipelines/pdoc.nox.py index f7297ac8e1..32e4c93939 100644 --- a/pipelines/pdoc.nox.py +++ b/pipelines/pdoc.nox.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020 Nekokatt -# Copyright (c) 2021 davfsa +# Copyright (c) 2021-present 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 From 4c368d463f36bda7a537c414f8bef34bedfc58fb Mon Sep 17 00:00:00 2001 From: davfsa Date: Sat, 9 Apr 2022 18:04:19 +0200 Subject: [PATCH 12/38] Add changelog fragment --- changes/1118.documentation.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/1118.documentation.md diff --git a/changes/1118.documentation.md b/changes/1118.documentation.md new file mode 100644 index 0000000000..c335d5d680 --- /dev/null +++ b/changes/1118.documentation.md @@ -0,0 +1 @@ +Documentation overhaul and move to docs.hikari-py.dev domain. From 9fd16de5e0e389a2a2c8ca88fce463e6945a602b Mon Sep 17 00:00:00 2001 From: davfsa Date: Sat, 9 Apr 2022 22:13:25 +0200 Subject: [PATCH 13/38] Fix typehint in docstring for `delete_message_days` --- hikari/api/rest.py | 2 +- hikari/guilds.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hikari/api/rest.py b/hikari/api/rest.py index d1c88d4c69..7344cca96b 100644 --- a/hikari/api/rest.py +++ b/hikari/api/rest.py @@ -5295,7 +5295,7 @@ async def ban_user( Other Parameters ---------------- - delete_message_days : hikari.undefined.UndefinedNoneOr[int] + delete_message_days : hikari.undefined.UndefinedOr[int] If provided, the number of days to delete messages for. This must be between 0 and 7. reason : hikari.undefined.UndefinedOr[str] diff --git a/hikari/guilds.py b/hikari/guilds.py index 033d1b37ed..606e1e8aff 100644 --- a/hikari/guilds.py +++ b/hikari/guilds.py @@ -714,7 +714,7 @@ async def ban( Other Parameters ---------------- - delete_message_days : hikari.undefined.UndefinedNoneOr[int] + delete_message_days : hikari.undefined.UndefinedOr[int] If provided, the number of days to delete messages for. This must be between 0 and 7. reason : hikari.undefined.UndefinedOr[str] @@ -1487,7 +1487,7 @@ async def ban( Other Parameters ---------------- - delete_message_days : hikari.undefined.UndefinedNoneOr[int] + delete_message_days : hikari.undefined.UndefinedOr[int] If provided, the number of days to delete messages for. This must be between 0 and 7. reason : hikari.undefined.UndefinedOr[str] From 0caa436861242ee83d0d28ad3c4ce4c8f226336b Mon Sep 17 00:00:00 2001 From: davfsa Date: Fri, 29 Apr 2022 18:01:57 +0200 Subject: [PATCH 14/38] Remove returns section from fields --- hikari/channels.py | 24 +++--------------------- hikari/errors.py | 17 +---------------- hikari/events/base_events.py | 24 +++--------------------- hikari/impl/config.py | 18 ------------------ hikari/users.py | 2 +- hikari/webhooks.py | 2 +- 6 files changed, 9 insertions(+), 78 deletions(-) diff --git a/hikari/channels.py b/hikari/channels.py index eaceb8c157..681044116f 100644 --- a/hikari/channels.py +++ b/hikari/channels.py @@ -123,31 +123,13 @@ class ChannelFollow: app: traits.RESTAware = attr.field( repr=False, eq=False, hash=False, metadata={attr_extensions.SKIP_DEEP_COPY: True} ) - """Return the client application that models may use for procedures. - - Returns - ------- - hikari.traits.RESTAware - The REST-aware application object. - """ + """The client application that models may use for procedures.""" channel_id: snowflakes.Snowflake = attr.field(hash=True, repr=True) - """Return the channel ID of the channel being followed. - - Returns - ------- - hikari.snowflakes.Snowflake - The channel ID for the channel being followed. - """ + """Return the channel ID of the channel being followed.""" webhook_id: snowflakes.Snowflake = attr.field(hash=True, repr=True) - """Return the ID of the webhook for this follow. - - Returns - ------- - hikari.snowflakes.Snowflake - The ID of the webhook that was created for this follow. - """ + """Return the ID of the webhook for this follow.""" async def fetch_channel(self) -> typing.Union[GuildNewsChannel, GuildTextChannel]: """Fetch the object of the guild channel being followed. diff --git a/hikari/errors.py b/hikari/errors.py index 81c429c0b4..e547fa5ae4 100644 --- a/hikari/errors.py +++ b/hikari/errors.py @@ -186,17 +186,7 @@ class GatewayServerClosedConnectionError(GatewayError): """An exception raised when the server closes the connection.""" code: typing.Union[ShardCloseCode, int, None] = attr.field(default=None) - """Return the close code that was received, if there is one. - - Returns - ------- - 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 `int` instead. - - If no close code was received, this will be `None`. - """ + """Return the close code that was received, if there is one.""" can_reconnect: bool = attr.field(default=False) """Return `True` if we can recover from this closure. @@ -205,11 +195,6 @@ class GatewayServerClosedConnectionError(GatewayError): than it being propagated to the caller. If `False`, this will be raised, thus stopping the application unless handled explicitly by the user. - - Returns - ------- - bool - Whether the closure can be recovered from via a reconnect. """ def __str__(self) -> str: diff --git a/hikari/events/base_events.py b/hikari/events/base_events.py index 22be478fe3..8d5dfe9761 100644 --- a/hikari/events/base_events.py +++ b/hikari/events/base_events.py @@ -199,31 +199,13 @@ class ExceptionEvent(Event, typing.Generic[EventT]): """ exception: Exception = attr.field() - """Exception that was raised. - - Returns - ------- - Exception - Exception that was raised in the event handler. - """ + """Exception that was raised.""" failed_event: EventT = attr.field() - """Event instance that caused the exception. - - Returns - ------- - hikari.events.base_events.Event - Event that was being processed when the exception occurred. - """ + """Event instance that caused the exception.""" failed_callback: FailedCallbackT[EventT] = attr.field() - """Event callback that threw an exception. - - Returns - ------- - callback - Event callback that failed execution. - """ + """Event callback that threw an exception.""" @property def app(self) -> traits.RESTAware: diff --git a/hikari/impl/config.py b/hikari/impl/config.py index d7bf61541a..ff24b1ea37 100644 --- a/hikari/impl/config.py +++ b/hikari/impl/config.py @@ -107,12 +107,6 @@ class ProxySettings(config.ProxySettings): 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 `None` - to disable. """ headers: typing.Optional[data_binding.Headers] = attr.field(default=None) @@ -122,11 +116,6 @@ class ProxySettings(config.ProxySettings): """Proxy URL to use. Defaults to `None` which disables the use of an explicit proxy. - - Returns - ------- - typing.Union[None, str] - The proxy URL to use, or `None` to disable it. """ trust_env: bool = attr.field(default=False, validator=attr.validators.instance_of(bool)) @@ -143,13 +132,6 @@ class ProxySettings(config.ProxySettings): .. note:: For more details of using `netrc`, visit: - - Returns - ------- - bool - `True` if allowing the use of environment variables - and/or `netrc` to determine proxy settings; `False` - if this should be disabled explicitly. """ @property diff --git a/hikari/users.py b/hikari/users.py index 910a6b5534..c66412a996 100644 --- a/hikari/users.py +++ b/hikari/users.py @@ -651,7 +651,7 @@ class PartialUserImpl(PartialUser): app: traits.RESTAware = attr.field( repr=False, eq=False, hash=False, metadata={attr_extensions.SKIP_DEEP_COPY: True} ) - """Reference to the client application that models may use for procedures.""" + """The client application that models may use for procedures.""" discriminator: undefined.UndefinedOr[str] = attr.field(eq=False, hash=False, repr=True) """Four-digit discriminator for the user.""" diff --git a/hikari/webhooks.py b/hikari/webhooks.py index eef3e725e3..46a63a63f4 100644 --- a/hikari/webhooks.py +++ b/hikari/webhooks.py @@ -80,7 +80,7 @@ class ExecutableWebhook(abc.ABC): @property @abc.abstractmethod def app(self) -> traits.RESTAware: - """Client application that models may use for procedures.""" + """The client application that models may use for procedures.""" @property @abc.abstractmethod From 5c763aec29c9f8d9612db2b249b3ce853995790a Mon Sep 17 00:00:00 2001 From: PerchunPak Date: Sat, 28 May 2022 22:22:35 +0300 Subject: [PATCH 15/38] Use right roles in `objects.inv` file (#1174) --- docs/patched_pdoc.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/docs/patched_pdoc.py b/docs/patched_pdoc.py index fbfe9e2c3e..bf40bbec75 100644 --- a/docs/patched_pdoc.py +++ b/docs/patched_pdoc.py @@ -55,13 +55,6 @@ def patched_html_module(*args, **kwargs) -> str: 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 anyway, so we can ignore them @@ -76,7 +69,7 @@ def _add_to_inventory(dobj: pdoc_doc.Doc): sphobjinv.DataObjStr( name=dobj.fullname, domain="py", - role=type_to_role[dobj.type], + role=dobj.type, uri=uri, priority="1", dispname="-", From a164da77861b320cb65cdfd454d2d0ae8f985448 Mon Sep 17 00:00:00 2001 From: davfsa Date: Mon, 1 Aug 2022 15:14:08 +0200 Subject: [PATCH 16/38] Remove old notes and builtins docs format --- hikari/api/cache.py | 10 +++++----- hikari/api/event_factory.py | 2 +- hikari/api/rest.py | 12 ++++++------ hikari/api/special_endpoints.py | 4 ++-- hikari/channels.py | 2 +- hikari/events/guild_events.py | 2 +- hikari/guilds.py | 2 +- hikari/internal/cache.py | 2 +- hikari/messages.py | 22 +++++++++++----------- 9 files changed, 29 insertions(+), 29 deletions(-) diff --git a/hikari/api/cache.py b/hikari/api/cache.py index a363bc4942..f587ce5772 100644 --- a/hikari/api/cache.py +++ b/hikari/api/cache.py @@ -183,7 +183,7 @@ def get_sticker( Returns ------- typing.Optional[hikari.stickers.GuildSticker] - The object of the sticker that was found in the cache or `builtins.None`. + The object of the sticker that was found in the cache or `None`. """ @abc.abstractmethod @@ -866,7 +866,7 @@ def update_emoji( def clear_stickers(self) -> CacheView[snowflakes.Snowflake, stickers.GuildSticker]: """Remove all the sticker objects from the cache. - !!! note + .. note:: This will skip stickers that are being kept alive by a reference. Returns @@ -887,7 +887,7 @@ def clear_stickers_for_guild( guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild] Object or ID of the guild to remove the cached sticker objects for. - !!! note + .. note:: This will skip stickers that are being kept alive by a reference. Returns @@ -908,14 +908,14 @@ def delete_sticker( sticker : hikari.snowflakes.SnowflakeishOr[hikari.stickers.GuildSticker] Object or ID of the sticker to remove from the cache. - !!! note + .. note:: This will not delete stickers that are being kept alive by a reference. Returns ------- typing.Optional[hikari.stickers.GuildSticker] The object of the sticker that was removed from the cache or - `builtins.None`. + `None`. """ @abc.abstractmethod diff --git a/hikari/api/event_factory.py b/hikari/api/event_factory.py index 388b74b007..1d90209184 100644 --- a/hikari/api/event_factory.py +++ b/hikari/api/event_factory.py @@ -466,7 +466,7 @@ def deserialize_guild_stickers_update_event( Other Parameters ---------------- typing.Optional[typing.Sequence[hikari.guilds.stickers.GuildSticker]] - The sequence of stickers or `builtins.None`. + The sequence of stickers or `None`. Returns ------- diff --git a/hikari/api/rest.py b/hikari/api/rest.py index 805468b90a..a9ed2fbba1 100644 --- a/hikari/api/rest.py +++ b/hikari/api/rest.py @@ -5459,7 +5459,7 @@ def fetch_bans( ) -> iterators.LazyIterator[guilds.GuildBan]: """Fetch the bans of a guild. - !!! note + .. 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. @@ -5472,10 +5472,10 @@ def fetch_bans( Other Parameters ---------------- - newest_first : builtins.bool + newest_first : bool Whether to fetch the newest first or the oldest first. - Defaults to `builtins.False`. + Defaults to `False`. start_at : undefined.UndefinedOr[snowflakes.SearchableSnowflakeishOr[users.PartialUser]] If provided, will start at this snowflake. If you provide a datetime object, it will be transformed into a snowflake. This @@ -6797,7 +6797,7 @@ async def create_slash_command( If `0`, then it will be available for all members. Note that this doesn't affect administrators of the guild and overwrites. - dm_enabled : hikari.undefined.UndefinedOr[builtins.bool] + dm_enabled : hikari.undefined.UndefinedOr[bool] Whether this command is enabled in DMs with the bot. This can only be applied to non-guild commands. @@ -6870,7 +6870,7 @@ async def create_context_menu_command( If `0`, then it will be available for all members. Note that this doesn't affect administrators of the guild and overwrites. - dm_enabled : hikari.undefined.UndefinedOr[builtins.bool] + dm_enabled : hikari.undefined.UndefinedOr[bool] Whether this command is enabled in DMs with the bot. This can only be applied to non-guild commands. @@ -7007,7 +7007,7 @@ async def edit_application_command( If `0`, then it will be available for all members. Note that this doesn't affect administrators of the guild and overwrites. - dm_enabled : hikari.undefined.UndefinedOr[builtins.bool] + dm_enabled : hikari.undefined.UndefinedOr[bool] Whether this command is enabled in DMs with the bot. This can only be applied to non-guild commands. diff --git a/hikari/api/special_endpoints.py b/hikari/api/special_endpoints.py index aa0af86906..07736db5c5 100644 --- a/hikari/api/special_endpoints.py +++ b/hikari/api/special_endpoints.py @@ -922,7 +922,7 @@ def set_default_member_permissions( Parameters ---------- - default_member_permissions : hikari.undefined.UndefinedOr[builtins.bool] + default_member_permissions : hikari.undefined.UndefinedOr[bool] The default member permissions to utilize this command by default. If `0`, then it will be available for all members. Note that this doesn't affect @@ -940,7 +940,7 @@ def set_is_dm_enabled(self: _T, state: undefined.UndefinedOr[bool], /) -> _T: Parameters ---------- - state : hikari.undefined.UndefinedOr[builtins.bool] + state : hikari.undefined.UndefinedOr[bool] Whether this command is enabled in DMs with the bot. Returns diff --git a/hikari/channels.py b/hikari/channels.py index 952eea217f..04ee33770f 100644 --- a/hikari/channels.py +++ b/hikari/channels.py @@ -1289,7 +1289,7 @@ class GuildVoiceChannel(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. """ diff --git a/hikari/events/guild_events.py b/hikari/events/guild_events.py index 44386c5486..5c75df7c65 100644 --- a/hikari/events/guild_events.py +++ b/hikari/events/guild_events.py @@ -463,7 +463,7 @@ class StickersUpdateEvent(GuildEvent): old_stickers: typing.Optional[typing.Sequence[guilds.stickers.GuildSticker]] = attr.field() """Sequence of all old stickers 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. """ stickers: typing.Sequence[guilds.stickers.GuildSticker] = attr.field() diff --git a/hikari/guilds.py b/hikari/guilds.py index df28dd345a..a8247d7266 100644 --- a/hikari/guilds.py +++ b/hikari/guilds.py @@ -3129,7 +3129,7 @@ def get_sticker( ------- typing.Optional[hikari.stickers.GuildSticker] The object of the sticker if found in cache, else - `builtins.None`. + `None`. """ if not isinstance(self.app, traits.CacheAware): return None diff --git a/hikari/internal/cache.py b/hikari/internal/cache.py index 9ea861dc59..3cf2daf36e 100644 --- a/hikari/internal/cache.py +++ b/hikari/internal/cache.py @@ -223,7 +223,7 @@ class GuildRecord: stickers: typing.Optional[typing.MutableSet[snowflakes.Snowflake]] = attr.field(default=None) """A sequence of sticker IDs cached for this guild. - This will be `builtins.None` if no stickers are cached for this guild else + This will be `None` if no stickers are cached for this guild else `typing.Sequence[hikari.snowflakes.Snowflake]` of emoji IDs. """ diff --git a/hikari/messages.py b/hikari/messages.py index 97516c62a3..9598acc851 100644 --- a/hikari/messages.py +++ b/hikari/messages.py @@ -795,7 +795,7 @@ class PartialMessage(snowflakes.Unique): ) """Users who were notified by their mention in the 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. @@ -807,7 +807,7 @@ class PartialMessage(snowflakes.Unique): ) """IDs of roles that were notified by their mention in the 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. @@ -821,7 +821,7 @@ class PartialMessage(snowflakes.Unique): If the message is not crossposted, this will always be empty. - !!! 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. @@ -831,7 +831,7 @@ class PartialMessage(snowflakes.Unique): mentions_everyone: undefined.UndefinedOr[bool] = attr.field(hash=False, eq=False, repr=False) """Whether the message notifies using `@everyone` or `@here`. - !!! 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. @@ -916,7 +916,7 @@ def channel_mention_ids(self) -> undefined.UndefinedOr[typing.Sequence[snowflake If the message is not crossposted, this will always be empty. - !!! 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. @@ -931,7 +931,7 @@ def channel_mention_ids(self) -> undefined.UndefinedOr[typing.Sequence[snowflake def user_mentions_ids(self) -> undefined.UndefinedOr[typing.Sequence[snowflakes.Snowflake]]: """Ids of the users who were notified by their mention in the 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. @@ -947,13 +947,13 @@ def get_member_mentions(self) -> undefined.UndefinedOr[typing.Mapping[snowflakes 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 @@ -980,13 +980,13 @@ def get_role_mentions(self) -> undefined.UndefinedOr[typing.Mapping[snowflakes.S 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 @@ -1729,7 +1729,7 @@ class Message(PartialMessage): referenced_message: typing.Optional[PartialMessage] = attr.field(hash=False, eq=False, repr=False) """The message that was replied to. - If `type` is `MessageType.REPLY` and `builtins.None`, the message was deleted. + If `type` is `MessageType.REPLY` and `None`, the message was deleted. """ interaction: typing.Optional[MessageInteraction] = attr.field(hash=False, eq=False, repr=False) From 6b67cf84e3e27ab155b07d621b167fd238d0fea3 Mon Sep 17 00:00:00 2001 From: davfsa Date: Tue, 2 Aug 2022 22:15:44 +0200 Subject: [PATCH 17/38] Switch docs to Sphinx (#1185) --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 2 - .readthedocs.yml | 26 + CHANGELOG.md | 154 +-- README.md | 43 +- changes/.template.rst | 36 + dev-requirements.txt | 12 +- docs/.gitignore | 1 + docs/_static/extra.css | 3 + docs/_templates/numpydoc_docstring.rst | 17 + docs/_templates/python/module.rst | 116 ++ docs/assets/crates.svg | 3 - docs/assets/discord.svg | 3 - docs/assets/github.svg | 3 - docs/assets/home.svg | 3 - docs/assets/up.svg | 3 - docs/changelog/index.md | 4 + docs/conf.py | 135 +++ docs/index.html.jinja2 | 45 - docs/index.md | 12 + docs/module.html.jinja2 | 357 ------- docs/patched_pdoc.py | 97 -- docs/style.css | 991 ------------------ hikari/_about.py | 2 +- hikari/api/cache.py | 6 +- hikari/api/config.py | 2 +- hikari/api/entity_factory.py | 23 +- hikari/api/event_factory.py | 18 +- hikari/api/event_manager.py | 218 ++-- hikari/api/rest.py | 320 +++--- hikari/api/shard.py | 12 +- hikari/api/special_endpoints.py | 95 +- hikari/api/voice.py | 1 - hikari/applications.py | 6 +- hikari/audit_logs.py | 8 +- hikari/banner.txt | 2 +- hikari/channels.py | 61 +- hikari/colors.py | 212 ++-- hikari/commands.py | 2 +- hikari/emojis.py | 16 +- hikari/events/guild_events.py | 2 +- hikari/files.py | 65 +- hikari/guilds.py | 58 +- hikari/impl/bot.py | 240 ++--- hikari/impl/buckets.py | 9 +- hikari/impl/config.py | 5 +- hikari/impl/interaction_server.py | 2 +- hikari/impl/rate_limits.py | 59 +- hikari/impl/rest.py | 77 +- hikari/impl/rest_bot.py | 8 +- hikari/impl/shard.py | 2 +- hikari/impl/special_endpoints.py | 83 +- hikari/intents.py | 114 +- hikari/interactions/base_interactions.py | 20 +- hikari/interactions/command_interactions.py | 42 +- hikari/interactions/component_interactions.py | 18 +- hikari/internal/aio.py | 1 + hikari/internal/cache.py | 10 +- hikari/internal/collections.py | 9 +- hikari/internal/data_binding.py | 6 +- hikari/internal/deprecation.py | 90 +- hikari/internal/enums.py | 18 +- hikari/internal/routes.py | 2 +- hikari/internal/time.py | 2 +- hikari/internal/ux.py | 5 +- hikari/invites.py | 4 +- hikari/iterators.py | 195 ++-- hikari/locales.py | 60 +- hikari/messages.py | 126 +-- hikari/permissions.py | 14 +- hikari/presences.py | 20 +- hikari/scheduled_events.py | 2 +- hikari/stickers.py | 4 +- hikari/templates.py | 2 +- hikari/traits.py | 20 +- hikari/users.py | 42 +- hikari/voices.py | 2 +- hikari/webhooks.py | 24 +- pipelines/config.py | 3 - pipelines/nox.py | 2 - pipelines/pdoc.nox.py | 76 -- pipelines/sphinx.nox.py | 80 ++ pyproject.toml | 10 +- scripts/deploy-manually.sh | 2 - scripts/deploy-pages-manually.sh | 35 - scripts/deploy-pages.sh | 44 - scripts/deploy.sh | 7 - tests/hikari/events/test_guild_events.py | 9 +- tests/hikari/impl/test_rest.py | 62 -- tests/hikari/internal/test_deprecation.py | 45 +- tests/hikari/internal/test_ux.py | 8 +- tests/hikari/test_guilds.py | 23 - tests/hikari/test_messages.py | 5 + 93 files changed, 1695 insertions(+), 3250 deletions(-) create mode 100644 .readthedocs.yml create mode 100644 changes/.template.rst create mode 100644 docs/.gitignore create mode 100644 docs/_static/extra.css create mode 100644 docs/_templates/numpydoc_docstring.rst create mode 100644 docs/_templates/python/module.rst delete mode 100644 docs/assets/crates.svg delete mode 100644 docs/assets/discord.svg delete mode 100644 docs/assets/github.svg delete mode 100644 docs/assets/home.svg delete mode 100644 docs/assets/up.svg create mode 100644 docs/changelog/index.md create mode 100644 docs/conf.py delete mode 100644 docs/index.html.jinja2 create mode 100644 docs/index.md delete mode 100644 docs/module.html.jinja2 delete mode 100644 docs/patched_pdoc.py delete mode 100644 docs/style.css delete mode 100644 pipelines/pdoc.nox.py create mode 100644 pipelines/sphinx.nox.py delete mode 100644 scripts/deploy-pages-manually.sh delete mode 100755 scripts/deploy-pages.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1143c3d1d..80d9fb9591 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -148,7 +148,7 @@ jobs: - name: Build documentation run: | pip install nox - nox -s pdoc + nox -s sphinx - name: Upload artifacts if: github.event_name == 'pull_request' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f9d43eda39..2bbf09ad1e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,10 +39,8 @@ jobs: - name: Deploy env: - GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} VERSION: ${{ github.event.release.tag_name }} REPO_SLUG: ${{ github.repository }} - DOCUMENTATION_REPO_SLUG: hikari-py/hikari-docs DEPLOY_WEBHOOK_URL: ${{ secrets.DEPLOY_WEBHOOK_URL }} TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 0000000000..f6dbe9fef6 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,26 @@ +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.8" + +sphinx: + configuration: docs/conf.py + builder: dirhtml + +python: + install: + - requirements: dev-requirements.txt + +search: + ignore: + # Defaults + - search.html + - search/index.html + - 404.html + - 404/index.html + + # Custom + - index.html + - changelog/index.html diff --git a/CHANGELOG.md b/CHANGELOG.md index a3106cb194..c86cf9a99d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,6 @@ -# Changelog +## Hikari 2.0.0.dev109 (2022-06-26) -All notable changes to this project will be documented in this file. - -This file is updated every release with the use of `towncrier` from the fragments found under `changes/`. - -.. towncrier release notes start - -Hikari 2.0.0.dev109 (2022-06-26) -================================ - -Breaking Changes ----------------- +### Breaking Changes - Removal of all application commands v1 related fields and endpoints. - Discord has completely disabled some endpoints, so we unfortunately can't @@ -18,16 +8,12 @@ Breaking Changes - Removed the `resolved` attribute from `AutocompleteInteraction` as autocomplete interactions never have resolved objects. ([#1152](https://github.com/hikari-py/hikari/issues/1152)) - `build` methods are now typed as returning `MutableMapping[str, typing.Any]`. ([#1164](https://github.com/hikari-py/hikari/issues/1164)) - -Deprecation ------------ +### Deprecation - `messages.Mentions` object deprecated - Alternatives can be found in the base message object ([#1149](https://github.com/hikari-py/hikari/issues/1149)) - -Features --------- +### Features - Add `create` method to `CommandBuilder`. ([#1016](https://github.com/hikari-py/hikari/issues/1016)) - Support for attachments in REST-based interaction responses. ([#1048](https://github.com/hikari-py/hikari/issues/1048)) @@ -41,9 +27,7 @@ Features - Added `hikari.events.application_events.ApplicationCommandPermissionsUpdate`. - Added `APPLICATION_COMMAND_PERMISSION_UPDATE` audit log entry ([#1148](https://github.com/hikari-py/hikari/issues/1148)) - -Bugfixes --------- +### Bugfixes - Improved pyright support. ([#1108](https://github.com/hikari-py/hikari/issues/1108)) - `RESTClientImpl.fetch_bans` now return a `LazyIterator` to allow pagination of values. ([#1119](https://github.com/hikari-py/hikari/issues/1119)) @@ -54,12 +38,12 @@ Bugfixes - Properly garbage collect message references in the cache - Properly deserialize `PartialMessage.referenced_message` as a partial message ([#1192](https://github.com/hikari-py/hikari/issues/1192)) +--- + -Hikari 2.0.0.dev108 (2022-03-27) -================================ +## Hikari 2.0.0.dev108 (2022-03-27) -Breaking Changes ----------------- +### Breaking Changes - `hikari.config` has now been split up to `hikari.api.config` and `hikari.impl.config` to avoid leaking impl detail. This also means that config types are no-longer accessible at the top level (directly on `hikari`). ([#1067](https://github.com/hikari-py/hikari/issues/1067)) @@ -69,15 +53,11 @@ Breaking Changes - Remove `VoiceRegion.is_vip` due to Discord no longer sending it. ([#1086](https://github.com/hikari-py/hikari/issues/1086)) - Remove store sku related application fields and store channels. ([#1092](https://github.com/hikari-py/hikari/issues/1092)) - -Deprecation ------------ +### Deprecation - Renamed `nick` argument to `nickname` for edit member and add user to guild REST methods. ([#1095](https://github.com/hikari-py/hikari/issues/1095)) - -Features --------- +### Features - Scheduled event support. ([#1056](https://github.com/hikari-py/hikari/issues/1056)) - `get_guild()` is now available on `hikari.GuildChannel`. ([#1057](https://github.com/hikari-py/hikari/issues/1057)) @@ -86,9 +66,7 @@ Features - Optimize event dispatching by only deserializing events when they are needed. ([#1094](https://github.com/hikari-py/hikari/issues/1094)) - Add `hikari.locales.Locale` to help with Discord locale strings. ([#1090](https://github.com/hikari-py/hikari/issues/1090)) - -Bugfixes --------- +### Bugfixes - `fetch_my_guilds` no-longer returns duplicate guilds nor makes unnecessary (duplicated) requests when `newest_first` is set to `True`. ([#1059](https://github.com/hikari-py/hikari/issues/1059)) - Add `InviteEvent` to `hikari.events.channel_events.__all__`, `hikari.events.__all__` and `hikari.__all__`. ([#1065](https://github.com/hikari-py/hikari/issues/1065)) @@ -97,12 +75,12 @@ Bugfixes - Take the major param for webhook without token endpoints when handling bucket ratelimits. ([#1102](https://github.com/hikari-py/hikari/issues/1102)) - Fix incorrect value for `GuildFeature.MORE_STICKERS`. ([#1989](https://github.com/hikari-py/hikari/issues/1989)) +--- + -Hikari 2.0.0.dev107 (2022-03-04) -================================ +## Hikari 2.0.0.dev107 (2022-03-04) -Features --------- +### Features - Added a `total_length` function to `hikari.embeds.Embed` - Takes into account the character length of the embed's title, description, fields (all field names and values), footer, and author combined. @@ -114,9 +92,7 @@ Features - The notorious "failed to communicate with server" log message is now a warning rather than an error. ([#1041](https://github.com/hikari-py/hikari/issues/1041)) - `hikari.applications`, `hikari.files`, `hikari.snowflakes` and `hikari.undefined` are now all explicitly exported by `hikari.__init__`, allowing pyright to see them without a direct import. ([#1042](https://github.com/hikari-py/hikari/issues/1042)) - -Bugfixes --------- +### Bugfixes - Fix bucket lock not being released on errors while being acquired, which locked the bucket infinitely ([#841](https://github.com/hikari-py/hikari/issues/841)) - `enable_signal_handlers` now only defaults to `True` when the run/start method is called in the main thread. @@ -128,28 +104,24 @@ Bugfixes The multiprocessing file reader strategy now expands user relative (`~`) links (like the threaded strategy). ([#1046](https://github.com/hikari-py/hikari/issues/1046)) +--- + -Hikari 2.0.0.dev106 (2022-02-03) -================================ +## Hikari 2.0.0.dev106 (2022-02-03) -Breaking Changes ----------------- +### Breaking Changes - Running the standard interaction server implementation now requires a `hikari[server]` install. This matches a switch over to PyNacl for the cryptographic payload validation. ([#986](https://github.com/hikari-py/hikari/issues/986)) - -Deprecation ------------ +### Deprecation - Deprecated `RESTClient.command_builder` and `RESTClient.create_application_command`. You should switch to `RESTClient.slash_command_builder`and `RESTClient.create_slash_command` respectively. ([#924](https://github.com/hikari-py/hikari/issues/924)) - -Features --------- +### Features - Add context menu commands and command autocomplete. ([#924](https://github.com/hikari-py/hikari/issues/924)) - Added support for GET /users/@me/guilds/{guild}/member. ([#955](https://github.com/hikari-py/hikari/issues/955)) @@ -166,21 +138,19 @@ Features This helps avoids a common mistake with trailing new-lines which leads to confusing errors on request. ([#989](https://github.com/hikari-py/hikari/issues/989)) - -Bugfixes --------- +### Bugfixes - Relaxed typing of methods with union entry specific specialisations through overloads. ([#876](https://github.com/hikari-py/hikari/issues/876)) - Fix deprecation warnings raised by usage of `asyncio.gather` outside of an active event loop in `GatewayBot.run`. ([#954](https://github.com/hikari-py/hikari/issues/954)) - UTF-8 characters are now properly handled for audit-log reasons in REST requests. ([#963](https://github.com/hikari-py/hikari/issues/963)) - Fix magic methods for `UserImpl` and its subclasses. ([#982](https://github.com/hikari-py/hikari/issues/982)) +--- + -Hikari 2.0.0.dev105 (2022-01-01) -================================ +## Hikari 2.0.0.dev105 (2022-01-01) -Features --------- +### Features - Add min_value and max_value to `CommandOption` ([#920](https://github.com/hikari-py/hikari/issues/920)) - Add `flags` attribute to Application ([#939](https://github.com/hikari-py/hikari/issues/939)) @@ -189,9 +159,7 @@ Features - Add `MODERATE_MEMBERS` to `Permission` - Add `communication_disabled_until` attribute to `edit_member` ([#940](https://github.com/hikari-py/hikari/issues/940)) - -Bugfixes --------- +### Bugfixes - Improved pyright compatibility and introduced pyright "type-completeness" checking. ([#916](https://github.com/hikari-py/hikari/issues/916)) - Add EventStream.filter specialisation to the abc. ([#917](https://github.com/hikari-py/hikari/issues/917)) @@ -203,28 +171,24 @@ Bugfixes - Remove case for setting `member` and `reference_message` to `undefined.Undefined` in full message deserialization - Don't set `message.member` to `undefined.UNDEFINED` on partial message deserialization if message was sent by a webhook ([#933](https://github.com/hikari-py/hikari/issues/933)) +--- + -Hikari 2.0.0.dev104 (2021-11-22) -================================ +## Hikari 2.0.0.dev104 (2021-11-22) -Breaking Changes ----------------- +### Breaking Changes - Remove the redundant `ChannelCreateEvent`, `ChannelUpdateEvent` and `ChannelDeleteEvent` base classes. `GuildChannelCreateEvent`, `GuildChannelUpdateEvent` and `GuildChannelDeleteEvent` should now be used. ([#862](https://github.com/hikari-py/hikari/issues/862)) - Split bulk message delete from normal delete - The new event is now `hikari.events.message_events.GuildBulkMessageDeleteEvent` ([#897](https://github.com/hikari-py/hikari/issues/897)) - -Deprecation ------------ +### Deprecation - `edit_my_nick` rest method. ([#827](https://github.com/hikari-py/hikari/issues/827)) - EventStream is now a sync context manager, not async. ([#864](https://github.com/hikari-py/hikari/issues/864)) - -Features --------- +### Features - User banners and accent colors to user models. ([#811](https://github.com/hikari-py/hikari/issues/811)) - Add attachment "is_ephemeral" field ([#824](https://github.com/hikari-py/hikari/issues/824)) @@ -238,9 +202,7 @@ Features - Add `old_message` attribute to `hikari.events.message_events.MessageDelete` ([#897](https://github.com/hikari-py/hikari/issues/897)) - Switch to more relaxed requirements. ([#906](https://github.com/hikari-py/hikari/issues/906)) - -Bugfixes --------- +### Bugfixes - Don't raise in bulk delete when message not found by delete single message endpoint ([#828](https://github.com/hikari-py/hikari/issues/828)) - Setup basic handler if no handlers are defined in favour passed to `logging.config.dictConfig` ([#832](https://github.com/hikari-py/hikari/issues/832)) @@ -249,12 +211,12 @@ Bugfixes - Fix logic for asserting listeners to not error when using defaults for other arguments ([#911](https://github.com/hikari-py/hikari/issues/911)) - Fix error message given by action row when a conflicted type is added. ([#912](https://github.com/hikari-py/hikari/issues/912)) +--- + -Hikari 2.0.0.dev103 (2021-10-06) -================================ +## Hikari 2.0.0.dev103 (2021-10-06) -Breaking Changes ----------------- +### Breaking Changes - `USE_PUBLIC_THREADS` and `USE_PRIVATE_THREADS` permissions have been removed in favour of new threads permission - New permissions are split into `CREATE_PUBLIC_THREADS`, `CREATE_PRIVATE_THREADS` and `SEND_MESSAGES_IN_THREADS` ([#799](https://github.com/hikari-py/hikari/issues/799)) @@ -264,9 +226,7 @@ Breaking Changes - The message that is sent with the error code is the info that the enum contained ([#816](https://github.com/hikari-py/hikari/issues/816)) - PermissionOverwrite doesn't inherit from Unique anymore and isn't hashable. Equality checks now consider all its fields. ([#820](https://github.com/hikari-py/hikari/issues/820)) - -Features --------- +### Features - Add new `START_EMBEDDED_ACTIVITIES` permission ([#798](https://github.com/hikari-py/hikari/issues/798)) - Support new `channel_types` field in `CommandOption` ([#800](https://github.com/hikari-py/hikari/issues/800)) @@ -274,9 +234,7 @@ Features - Add `old_guild` attribute to `GuildLeaveEvent`. ([#806](https://github.com/hikari-py/hikari/issues/806)) - Add `GuildJoinEvent` that will fire when the bot joins new guilds ([#809](https://github.com/hikari-py/hikari/issues/809)) - -Bugfixes --------- +### Bugfixes - Fix re-uploading forms with resources ([#787](https://github.com/hikari-py/hikari/issues/787)) - Prevent double linking embed resources, which causes them to upload twice @@ -284,26 +242,22 @@ Bugfixes - Fix `BulkDeleteError` returning incorrect values for `messages_skipped` - This affected the `__str__` and `percentage_completion`, which also returned incorrect values ([#817](https://github.com/hikari-py/hikari/issues/817)) - -Documentation Improvements --------------------------- +### Documentation Improvements - Add docstrings to the remaining undocumented `GatewayBot` methods ([#804](https://github.com/hikari-py/hikari/issues/804)) +--- + -Hikari 2.0.0.dev102 (2021-09-19) -================================ +## Hikari 2.0.0.dev102 (2021-09-19) -Deprecations and Removals -------------------------- +### Deprecations and Removals - `MessageType.APPLICATION_COMMAND` renamed to `MessageType.CHAT_INPUT` ([#775](https://github.com/hikari-py/hikari/issues/775)) - Removal of deprecated `hikari.impl.bot.BotApp` and `hikari.traits.BotAware` - Use `hikari.impl.bot.GatewayBot` and `hikari.traits.GatewayBotAware` respectively instead ([#778](https://github.com/hikari-py/hikari/issues/778)) - -Features --------- +### Features - Message components support ([#684](https://github.com/hikari-py/hikari/issues/684)) - Web dashboard example with `rillrate` ([#752](https://github.com/hikari-py/hikari/issues/752)) @@ -318,9 +272,7 @@ Features - Add `MessageType.CONTEXT_MENU_COMMAND` message type ([#775](https://github.com/hikari-py/hikari/issues/775)) - Add `ApplicationCommand.version` ([#776](https://github.com/hikari-py/hikari/issues/776)) - -Bugfixes --------- +### Bugfixes - Handling of interaction models passed to the webhook message endpoints as the "webhook" field ([#759](https://github.com/hikari-py/hikari/issues/759)) - Fix passing `embeds` arguments in `create_interaction_response` and `edit_initial_response` endpoints @@ -335,13 +287,13 @@ Bugfixes - Set `PartialMessage.member` to `undefined.UNDEFINED` when Discord edit the message to display an embed/attachment ([#783](https://github.com/hikari-py/hikari/issues/783)) - `CommandInteractionOption.value` will now be cast to a `Snowflake` for types 6-9 ([#785](https://github.com/hikari-py/hikari/issues/785)) - -Improved Documentation ----------------------- +### Documentation Improvements - Fix typo in Colorish docstring ([#755](https://github.com/hikari-py/hikari/issues/755)) - Remove duplicate raise type in REST and guilds docstrings ([#768](https://github.com/hikari-py/hikari/issues/768)) - Fix various spelling mistakes ([#773](https://github.com/hikari-py/hikari/issues/773)) +--- + -*This file was added during the development of version 2.0.0.dev102, so nothing before that is documented.* +*The changelog was added during the development of version 2.0.0.dev102, so nothing before that is documented here.* diff --git a/README.md b/README.md index 39608181a2..99a9984ecb 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,12 @@ Supported python versions
CI status -Mypy badge +Mypy badge Black badge 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 @@ -125,15 +125,27 @@ async def print_my_user(token): my_user = await client.fetch_my_user() print(my_user) -asyncio.run(print_my_user("user token here")) +asyncio.run(print_my_user("user token acquired through OAuth here")) ``` --- ## Optional Features -* `hikari[server]` - Install dependencies required to enable Hikari's standard interaction server (RESTBot) functionality. -* `hikari[speedups]` - Detailed in [`hikari[speedups]`](#hikarispeedups). +Optional features can be specified when installing hikari: + +* `server` - Install dependencies required to enable Hikari's standard interaction server (RESTBot) functionality. +* `speedups` - Detailed in [`hikari[speedups]`](#hikarispeedups). + +Example: + +```bash +# To install hikari with the speedups feature: +python -m pip install -U hikari[speedups] + +# To install hikari with both the speedups and server features: +python -m pip install -U hikari[speedups, server] +``` ## Additional resources @@ -152,24 +164,23 @@ Hikari does not include a command framework by default, so you will want to pick As your application scales, you may need to adjust some things to keep it performing nicely. -### Python optimisation flags +### Python optimization flags -CPython provides two optimisation flags that remove internal safety checks that are useful for development, and change +CPython provides two optimization flags that remove internal safety checks that are useful for development, and change other internal settings in the interpreter. -- `python bot.py` - no optimisation - this is the default. -- `python -O bot.py` - first level optimisation - features such as internal - assertions will be disabled. -- `python -OO bot.py` - second level optimisation - more features (**including - all docstrings**) will be removed from the loaded code at runtime. +- `python bot.py` - no optimization - this is the default. +- `python -O bot.py` - first level optimization - features such as internal assertions will be disabled. +- `python -OO bot.py` - second level optimization - more features (**including all docstrings**) will be removed from + the loaded code at runtime. -**A minimum of first level of optimizations** is recommended when running bots in a production environment. +**A minimum of first level of optimization** is recommended when running bots in a production environment. ### `hikari[speedups]` -If you have a C compiler (Microsoft VC++ Redistributable 14.0 or newer, or a modern copy of GCC/G++, Clang, etc), you -can install Hikari using `pip install -U hikari[speedups]`. This will install `aiodns`, `cchardet`, `Brotli`, and -`ciso8601` which will provide you with a small performance boost. +If you have a C compiler (Microsoft VC++ Redistributable 14.0 or newer, or a modern copy of GCC/G++, Clang, etc), it is +recommended you install Hikari using `pip install -U hikari[speedups]`. This will install `aiodns`, `cchardet`, +`Brotli`, and `ciso8601` which will provide you with a small performance boost. ### `uvloop` diff --git a/changes/.template.rst b/changes/.template.rst new file mode 100644 index 0000000000..426bcad2f4 --- /dev/null +++ b/changes/.template.rst @@ -0,0 +1,36 @@ +{% if versiondata.name %} +## {{ versiondata.name }} {{ versiondata.version }} ({{ versiondata.date }}) +{% else %} +## {{ versiondata.version }} ({{ versiondata.date }}) +{% endif %} +{% for section, _ in sections.items() %} +{% set anchor = "#" * underlines[0] %}{% if section %}{{ anchor }} {{section}} +{% set anchor = "#" * underlines[1] %} + +{% endif %} + +{% if sections[section] %} +{% for category, val in definitions.items() if category in sections[section]%} +{{ anchor }} {{ definitions[category]['name'] }} + +{% if definitions[category]['showcontent'] %} +{% for text, values in sections[section][category].items() %} +- {{ text }} ({{ values|join(', ') }}) +{% endfor %} + +{% else %} +- {{ sections[section][category]['']|join(', ') }} + +{% endif %} +{% if sections[section][category]|length == 0 %} +No significant changes. + +{% else %} +{% endif %} +{% endfor %} +{% else %} +No significant changes. + +{% endif %} +{% endfor %} +--- diff --git a/dev-requirements.txt b/dev-requirements.txt index 65d28a6eeb..66def0c1d5 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -21,9 +21,13 @@ async-timeout==4.0.2 # Used for timeouts in some test cases. # DOCUMENTATION # ################# -pdoc==11.0.0 -sphobjinv==2.2.2 -minify-html==0.8 +sphinx==5.0.1 +sphinx-autoapi==1.8.4 +numpydoc==1.4.0 +furo==2022.6.21 +myst-parser==0.18.0 +sphinxext.opengraph==0.6.3 +sphinx-copybutton==0.5.0 ################# # TYPE CHECKING # @@ -46,7 +50,7 @@ black==22.6.0 isort==5.10.1 ########### -# Linting # +# LINTING # ########### slotscheck==0.14.1 diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000000..feead5b1fb --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +reference/ diff --git a/docs/_static/extra.css b/docs/_static/extra.css new file mode 100644 index 0000000000..15da03033d --- /dev/null +++ b/docs/_static/extra.css @@ -0,0 +1,3 @@ +html { + word-wrap: anywhere; +} diff --git a/docs/_templates/numpydoc_docstring.rst b/docs/_templates/numpydoc_docstring.rst new file mode 100644 index 0000000000..c3d3f7e317 --- /dev/null +++ b/docs/_templates/numpydoc_docstring.rst @@ -0,0 +1,17 @@ +{{index}} +{{summary}} +{{extended_summary}} +{{warns}} +{{warnings}} +{{notes}} +{{parameters}} +{{other_parameters}} +{{returns}} +{{yields}} +{{receives}} +{{raises}} +{{see_also}} +{{references}} +{{examples}} +{{attributes}} +{{methods}} diff --git a/docs/_templates/python/module.rst b/docs/_templates/python/module.rst new file mode 100644 index 0000000000..085f0f91c5 --- /dev/null +++ b/docs/_templates/python/module.rst @@ -0,0 +1,116 @@ +.. modified version of https://github.com/readthedocs/sphinx-autoapi/blob/master/autoapi/templates/python/module.rst + +{% if not obj.display %} +:orphan: + +{% endif %} +{{ obj.name }} +{{ "=" * obj.name|length }} + +.. py:module:: {{ obj.name }} + +{% if obj.docstring %} +.. autoapi-nested-parse:: + + {{ obj.docstring|indent(3) }} + +{% endif %} + +{% block subpackages %} +{% set visible_subpackages = obj.subpackages|selectattr("display")|list %} +{% if visible_subpackages %} +Subpackages +----------- +.. toctree:: + :titlesonly: + :maxdepth: 1 + +{% for subpackage in visible_subpackages %} + {{ subpackage.short_name }}/index.rst +{% endfor %} + + +{% endif %} +{% endblock %} +{% block submodules %} +{% set visible_submodules = obj.submodules|selectattr("display")|list %} +{% if visible_submodules %} +Submodules +---------- +.. toctree:: + :titlesonly: + :maxdepth: 1 + +{% for submodule in visible_submodules %} + {{ submodule.short_name }}/index.rst +{% endfor %} + + +{% endif %} +{% endblock %} +{% block content %} +{% if obj.all is not none %} +{% set visible_children = obj.children|selectattr("short_name", "in", obj.all)|list %} +{% elif obj.type is equalto("package") %} +{% set visible_children = obj.children|selectattr("display")|list %} +{% else %} +{% set visible_children = obj.children|selectattr("display")|rejectattr("imported")|list %} +{% endif %} +{% if visible_children %} +{{ obj.type|title }} Contents +{{ "-" * obj.type|length }}--------- + +{% set visible_classes = visible_children|selectattr("type", "equalto", "class")|list %} +{% set visible_functions = visible_children|selectattr("type", "equalto", "function")|list %} +{% set visible_attributes = visible_children|selectattr("type", "equalto", "data")|list %} +{% if "show-module-summary" in autoapi_options and (visible_classes or visible_functions) %} +{% block classes scoped %} +{% if visible_classes %} +Classes +~~~~~~~ + +.. autoapisummary:: + +{% for klass in visible_classes %} + {{ klass.id }} +{% endfor %} + + +{% endif %} +{% endblock %} + +{% block functions scoped %} +{% if visible_functions %} +Functions +~~~~~~~~~ + +.. autoapisummary:: + +{% for function in visible_functions %} + {{ function.id }} +{% endfor %} + + +{% endif %} +{% endblock %} + +{% block attributes scoped %} +{% if visible_attributes %} +Attributes +~~~~~~~~~~ + +.. autoapisummary:: + +{% for attribute in visible_attributes %} + {{ attribute.id }} +{% endfor %} + + +{% endif %} +{% endblock %} +{% endif %} +{% for obj_item in visible_children %} +{{ obj_item.render()|indent(0) }} +{% endfor %} +{% endif %} +{% endblock %} diff --git a/docs/assets/crates.svg b/docs/assets/crates.svg deleted file mode 100644 index fb24fe9d18..0000000000 --- a/docs/assets/crates.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/assets/discord.svg b/docs/assets/discord.svg deleted file mode 100644 index 2727db171a..0000000000 --- a/docs/assets/discord.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/assets/github.svg b/docs/assets/github.svg deleted file mode 100644 index fc6ff7116d..0000000000 --- a/docs/assets/github.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/assets/home.svg b/docs/assets/home.svg deleted file mode 100644 index 8f3ffe3e94..0000000000 --- a/docs/assets/home.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/assets/up.svg b/docs/assets/up.svg deleted file mode 100644 index c027461560..0000000000 --- a/docs/assets/up.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/changelog/index.md b/docs/changelog/index.md new file mode 100644 index 0000000000..922f663e66 --- /dev/null +++ b/docs/changelog/index.md @@ -0,0 +1,4 @@ +# Changelog + +```{include} ../../CHANGELOG.md +``` diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000000..a2a019f819 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020 Nekokatt +# Copyright (c) 2021-present 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 file for the Sphinx documentation builder.""" +import os +import re +import types + +# -- Project information ----------------------------------------------------- + +with open(os.path.join("..", "hikari", "_about.py")) as fp: + code = fp.read() + +token_pattern = re.compile(r"^__(?P\w+)?__.*=\s*(?P(?:'{3}|\"{3}|'|\"))(?P.*?)(?P=quote)", re.M) + +groups = {} + +for match in token_pattern.finditer(code): + group = match.groupdict() + groups[group["key"]] = group["value"] + +metadata = types.SimpleNamespace(**groups) + +project = "hikari" +copyright = metadata.copyright +author = metadata.author +release = version = metadata.version + +# So that we don't interfere with any potential config +del os, re, types, code, token_pattern, group, metadata + +# -- General configuration --------------------------------------------------- + +extensions = [ + # Sphinx own extensions + "sphinx.ext.autosummary", + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.viewcode", + "sphinx.ext.mathjax", + # Docs generation + "autoapi.extension", + "numpydoc", + "myst_parser", + # Misc + "sphinxext.opengraph", + "sphinx_copybutton", +] + +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +default_role = "py:obj" + +# -- HTML output -------------------------------------------------------------- + +html_theme = "furo" +html_favicon = "https://www.hikari-py.dev/logo.png" +html_theme_options = { + "top_of_page_button": None, +} +html_static_path = ["_static"] +html_css_files = [ + "extra.css", +] + +# -- OpenGraph ---------------------------------------------------------------- + +ogp_site_url = "https://docs.hikari-py.dev" +ogp_image = "https://www.hikari-py.dev/logo.png" + +ogp_custom_meta_tags = [ + '', +] + +# -- AutoAPI options ---------------------------------------------------------- + +autoapi_root = "reference" +autoapi_dirs = ["../hikari"] +autoapi_ignore = ["__main__.py"] + +autoapi_options = ["members", "show-inheritance"] +autoapi_template_dir = "_templates" + +autoapi_add_toctree_entry = False +autoapi_keep_files = True + +# -- AutoDoc options ---------------------------------------------------------- + +autodoc_typehints = "none" # NumpyDoc will do it for us. We just want to remove them from the signature too +autodoc_preserve_defaults = True + +# -- NumpyDoc options --------------------------------------------------------- + +numpydoc_class_members_toctree = True +numpydoc_show_class_members = False +numpydoc_xref_param_type = True +numpydoc_validation_checks = { + "all", + # Under here are all disabled checks + "GL08", # Missing docstring + "GL06", # Unknown section +} + +# -- Intersphinx options ------------------------------------------------------ + +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), + "aiohttp": ("https://docs.aiohttp.org/en/stable", None), + "attrs": ("https://www.attrs.org/en/stable/", None), + "multidict": ("https://multidict.readthedocs.io/en/stable/", None), + "yarl": ("https://yarl.readthedocs.io/en/stable/", None), +} + +# -- MyST --------------------------------------------------------------------- + +myst_heading_anchors = 3 diff --git a/docs/index.html.jinja2 b/docs/index.html.jinja2 deleted file mode 100644 index c0272dcee1..0000000000 --- a/docs/index.html.jinja2 +++ /dev/null @@ -1,45 +0,0 @@ -{# -Copyright (c) 2020 Nekokatt -Copyright (c) 2021-present 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. -#} -{# Purely developer only page. This won't be in the final documentation #} - - - - {% set url = root_module_name + ".html" %} - {# Nobody likes to be blinded for no reason #} - - - - - - - Redirecting… - - -

Redirecting…

- Click here if you are not redirected. - - diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000000..0441cc23af --- /dev/null +++ b/docs/index.md @@ -0,0 +1,12 @@ +# Welcome to hikari's documentation! + +```{include} ../README.md +``` + +```{toctree} +:titlesonly: +:maxdepth: 1 + +API Reference +/changelog/index +``` diff --git a/docs/module.html.jinja2 b/docs/module.html.jinja2 deleted file mode 100644 index 7b7e98b68b..0000000000 --- a/docs/module.html.jinja2 +++ /dev/null @@ -1,357 +0,0 @@ -{# -Copyright (c) 2020 Nekokatt -Copyright (c) 2021-present 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. -#} -{# ---- MACRO DEFINITIONS BEGIN ---- #} -{% macro bases(cls) %} - {%- if cls.bases -%} - ( - {%- for base in cls.bases -%} - {{ base[:2] | link(text=base[2]) }} - {%- if loop.nextitem %}, {% endif %} - {%- endfor -%} - ) - {%- endif -%} -{% endmacro %} -{% macro annotation(var) %} - {%- if var.annotation_str -%} - {{ var.annotation_str | escape | linkify }} - {%- endif -%} -{% endmacro %} -{% macro decorators(doc) %} - {% for d in doc.decorators if not d.startswith("@_") %} -
{{ d }}
- {% endfor %} -{% endmacro %} -{% macro headerlink(doc) -%} - #   -{% endmacro %} -{% macro function(fn) -%} -
{{ headerlink(fn) }} - {{ decorators(fn) }} - {{ fn.funcdef }}  - {#- no space -#} - {{ fn.name }} - {#- no space -#} - {% autoescape false %} - {# Extreemly hacky solution, but it makes it work nicely with minify_html #} - {{ fn.signature | escape | replace("\n", "
") | replace("
", "
  ") | linkify }}:
- {% endautoescape %} -
-{% endmacro %} -{% macro variable(var) %} -
{{ headerlink(var) }} - {{ var.name }}{{ annotation(var) }} -
-{% endmacro %} -{% macro submodule(mod) %} -
{{ headerlink(mod) }}{{ mod.taken_from | link }}
-{% endmacro %} -{% macro class(cls) %} -
- {{ headerlink(cls) }} - {{ decorators(cls) }} - class  - {#- no space -#} - {{ cls.qualname }} - {{- bases(cls) -}}: -
-{% endmacro %} -{% macro member(doc, should_add_to_inventory = true) %} - {% if add_to_inventory is defined and should_add_to_inventory %} - {{ add_to_inventory(doc) }} - {% endif %} - {% if doc.type == "class" %} - {{ class(doc) }} - {% if add_to_inventory is defined and should_add_to_inventory %} - {% for m in doc.members.values() %} - {{ add_to_inventory(m) }} - {% endfor %} - {% endif %} - {% elif doc.type == "function" %} - {{ function(doc) }} - {% elif doc.type == "module" %} - {{ submodule(doc) }} - {% else %} - {{ variable(doc) }} - {% endif %} - {{ view_source(doc) }} - {{ docstring(doc) }} -{% endmacro %} -{% macro docstring(var) %} - {% if var.docstring %} -
{{ var.docstring | to_markdown | to_html | linkify(namespace=var.qualname) }}
- {% endif %} -{% endmacro %} -{% macro nav_members(members) %} -
    - {% for m in members | sort() if is_public(m) | trim %} -
  • - {% if m.type == "class" %} - {{ m.qualname }} - {% if m.members %} - {{ nav_members(m.members.values()) | indent(12) }} - {% endif %} - {% elif m.type == "module" %} - {{ m.name }} - {% else %} - {{ m.name }} - {% endif %} -
  • - {% endfor %} -
-{% endmacro %} -{% macro is_public(doc) %} - {# - This macro is a bit unconventional in that its output is not rendered, but treated as a boolean: - Returning no text is interpreted as false, returning any other text is iterpreted as true. - Implementing this as a macro makes it very easy to override with a custom template.. - #} - {% if not doc.name.startswith("_") %} - {# members not starting with an underscore are considered public by default #} - true - {% elif doc.name == "__init__" %} - {# the constructor is our special case which we also treat as public #} - true - {% elif doc.name == "__doc__" %} - {# Sometimes we have __doc__ in __all__, which we want to exclude for pdoc's purposes. #} - {# https://github.com/mitmproxy/pdoc/issues/235 #} - {% elif doc.qualname is in (module.obj.__all__ or []) %} - {# members starting with an underscore are still public if mentioned in __all__ #} - true - {% endif %} -{% endmacro %} -{% macro view_source(doc) %} - {% if show_source and doc.source %} -
- View Source - {{ doc.source | highlight }} -
- {% endif %} -{% endmacro %} -{% macro module_name() %} -

- {% set parts = module.modulename.split(".") %} - {% for part in parts %} - {%- set fullname = ".".join(parts[:loop.index]) -%} - {%- if fullname in all_modules and fullname != module.modulename -%} - {{ part }} - {%- else -%} - {{ part }} - {%- endif -%} - {%- if loop.nextitem -%} - . - {%- endif -%} - {% endfor %} -

-{% endmacro %} -{# ---- MACRO DEFINITIONS END ---- #} - - - - - - {{ module.modulename }} API documentation | {{ __hikari_version__ }} - - - - - - - - {% include "math.html.jinja2" %} - - - - -
- - {% include "assets/up.svg" %} - Back to top - -
- {% if add_to_inventory is defined %} - {{ add_to_inventory(module) }} - {% endif %} -
- {{ module_name() }} - {{ docstring(module) }} - {{ view_source(module) }} -
- {% for m in module.flattened_own_members | sort() if is_public(m) | trim %} -
- {{ member(m) }} - {% if m.type == "class" %} - {% set vars = [] %} - {% set functions = [] %} - {% for m in m.members.values() | sort() if m.type != "class" and is_public(m) | trim %} - {% if m.type == "function" %} - {{ functions.append(m) or "" }} - {% else %} - {{ vars.append(m) or "" }} - {% endif %} - {% endfor %} - {% if vars %} -
-
Variables and properties
- {% for m in m.members.values() | sort() if m.type not in ("class", "function") and is_public(m) | trim %} -
- {{ member(m, false) }} -
- {% endfor %} -
- {% endif %} - {% if functions %} -
-
Methods
- {% for m in m.members.values() | sort() if m.type == "function" and is_public(m) | trim %} -
- {{ member(m, false) }} -
- {% endfor %} -
- {% endif %} - {% endif %} -
- {% endfor %} -
- {# These should only load on release builds #} - {% if __hikari_version__ != "head" %} - - {% endif %} - - {% if mtime %} - {% include "livereload.html.jinja2" %} - {% endif %} - {% if search and all_modules|length > 1 %} - {% include "search.html.jinja2" %} - {% endif %} - - diff --git a/docs/patched_pdoc.py b/docs/patched_pdoc.py deleted file mode 100644 index bf40bbec75..0000000000 --- a/docs/patched_pdoc.py +++ /dev/null @@ -1,97 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020 Nekokatt -# Copyright (c) 2021-present 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. -"""A script that patches some pdoc functionality before calling it.""" -import datetime -import os -import pathlib -import sys - -import minify_html -import sphobjinv -from pdoc import __main__ as pdoc_main -from pdoc import doc as pdoc_doc -from pdoc import render as pdoc_render -from pdoc.render import env as pdoc_env - -sys.path.append(os.getcwd()) - -import hikari - -pdoc_html_module = pdoc_render.html_module - - -def patched_html_module(*args, **kwargs) -> str: - # We patch this to minify the HTML output before sending it further - output = pdoc_html_module(*args, **kwargs) - - return minify_html.minify(output, minify_js=True, minify_css=True) - - -pdoc_render.html_module = patched_html_module - -# '-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__ - - def _add_to_inventory(dobj: pdoc_doc.Doc): - if dobj.name.startswith("_"): - # These won't be documented anyway, 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=dobj.type, - uri=uri, - priority="1", - dispname="-", - ) - ) - - return "" - - pdoc_env.globals["add_to_inventory"] = _add_to_inventory - -# Run pdoc -pdoc_env.globals["utcnow"] = datetime.datetime.utcnow -pdoc_env.globals["__hikari_version__"] = ( - hikari.__git_sha1__.lower() if hikari.__git_sha1__.lower() in ("head", "master") else hikari.__version__ -) -pdoc_main.cli() - -if generate_inventory: - # Output the inventory - text = project_inventory.data_file(contract=True) - ztext = sphobjinv.compress(text) - raw_path = sys.argv[sys.argv.index("-o") + 1] - path = str(pathlib.Path(raw_path) / "objects.inv") - sphobjinv.writebytes(path, ztext) - print(f"Inventory written to {path!r}") diff --git a/docs/style.css b/docs/style.css deleted file mode 100644 index 789a4c2ecd..0000000000 --- a/docs/style.css +++ /dev/null @@ -1,991 +0,0 @@ -:root { - --pdoc-background: #212529; - --text: #f7f7f7; - --muted: #9d9d9d; - --link: #DB61D9 ; - --link-hover: #3989ff; - --code: #333; - --accent: #343434; - --accent2: #555; - --nav-hover: rgba(0, 0, 0, 0.1); - --def: #ff79c6; - --name: #61aeee; - --annotation: #DE4F91; -} - -/* Colors of overall document */ -body { - background-color: var(--pdoc-background); -} - -/* Responsive Layout */ -html, body { - width: 100%; - height: 100%; - overflow-wrap: break-word; - scroll-behavior: smooth; - - font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; -} - -@media (max-width: 1079px) { - :root { - --sidebar-width: 27rem; - } - - html { - font-size: .8rem; - } - - main, header { - padding: 2rem 3vw 0 1.5rem; - } - - /* Prevent scrolling and interacting with the body when sidebar is open */ - html:not(.sidebar-hidden) body { - overflow: hidden !important; - } - html:not(.sidebar-hidden) main { - pointer-events: none !important; - } - - .sidebar-hidden nav { - transform: translateX(calc(0px - var(--sidebar-width))); - } - - nav { - transition: transform 0.2s; - } - - .sidebar-toggle { - position: fixed; - top: 0; - bottom: calc(100% - 6rem); - left: var(--sidebar-width); - - border-left: 10px 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, 37vw, 28rem); - } - - main, header { - padding: 3rem 4rem 3rem calc(var(--sidebar-width) + 3rem); - } -} - -@media print { - :root { - --sidebar-width: 0; - } - - nav { - display: none !important; - } - - .sidebar-toggle { - display: none !important; - } - - details { - display: none !important; - } -} - -/* Nav */ -nav { - position: fixed; - left: 0; - top: 0; - bottom: 0; - height: 100vh; - width: var(--sidebar-width); - - z-index: 1; -} - -.sidebar { - height: 100vh; - overflow: auto; - - padding: .5rem 1.5rem 1rem 1rem; - - background-color: var(--accent); - border-right: 1px solid var(--accent2); - scrollbar-color: var(--accent2) transparent; /* Scrollbar color on Firefox */ -} - -::-webkit-scrollbar { - width: 10px; - background: var(--accent) transparent; -} - -::-webkit-scrollbar-thumb { - -webkit-border-radius: 1ex; - background: var(--accent2); -} - -.sidebar input[type=search] { - display: block; - outline-offset: 0; - width: 102%; -} - -.sidebar ul { - list-style: none; - padding-left: 1rem; -} - -.sidebar li { - display: block; - margin: 0; - padding: .2rem 0 .2rem .5rem; - transition: all 100ms; -} - -.sidebar > ul > li { - padding-left: 0; -} - -.sidebar li:hover { - background-color: var(--nav-hover); -} - -.sidebar a:hover { - color: var(--text); -} - -.sidebar a { - display: block; -} - -.sidebar a.function, .sidebar a.variable { - color: #bd93f9; -} - -.sidebar > h2:first-of-type { - margin-top: 1rem; -} - -.sidebar .class:before { - content: "class "; - color: var(--muted); -} - -.sidebar .function:after { - content: "()"; - color: var(--muted); -} - -.sidebar .sidebar-buttons { - display: flex; - width: 103%; - margin-bottom: .5rem; - align-items: center; -} - -.sidebar .sidebar-buttons .push { - margin-left: auto; -} - -.svg-button > svg { - width: 1.7rem; - margin: 0 .3rem; - cursor: pointer; -} - -.version-selector { - background-color: var(--accent); - color: var(--text); - border: hidden; -} - -.version-selector:disabled { - -webkit-appearance: none; - text-indent: 5%; -} - -.version-warning { - background-color: rgb(82, 0, 0); - padding: 1rem 1rem 1rem 1rem; - margin-right: 3rem; - text-align: center; -} - -.version-warning:empty { - display: none; -} - -.version-warning a { - color: rgb(129, 217, 255); -} - -.version-warning a:hover { - color: rgb(0, 178, 255); -} - -/* Back to top button */ -.back-to-top { - text-decoration: none; - - display: none; - position: fixed; - top: 5rem; - padding: 0.5rem 0.75rem 0.5rem 0.5rem; - border-radius: 1rem; - font-size: 0.8125rem; - - background: var(--pdoc-background); - box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), #708090 0 0 1px 0; - - z-index: 1; - - margin-left: 30%; -} - -.back-to-top span { - margin-left: 0.25rem; -} - -.back-to-top svg { - height: 1rem; - width: 1rem; - fill: var(--pdoc-background); - display: inline-block; -} - -/* General styling */ -nav, main { - 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; -} - -h1, h2, h3 { - font-weight: 300; - margin: .3em 0; - padding: .2em 0; -} - -a { - text-decoration: none; - color: var(--link); -} - -a:hover { - color: var(--link-hover); -} - -blockquote { - margin-left: 2rem; -} - -p { - font-weight: 300; - margin-bottom: .7rem; -} - -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; -} - -code { - color: var(--text); - padding: .2em .4em; - margin: 0; - font-size: 85%; - background-color: var(--code); - border-radius: 6px; -} - -a > code { - color: inherit; -} - -pre > code { - display: inline-block; - font-size: inherit; - background: none; - border: none; - padding: 0; -} - -/* Page Heading */ -.modulename { - color: var(--annotation); - font-weight: bold; -} - -.modulename a { - color: var(--link); - transition: 100ms all; -} - -.modulename a:hover { - filter: brightness(80%); -} - -/* View Source */ -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); -} - -details:not([open]) { - height: 0; - overflow: visible; -} - -details > summary { - font-size: .75rem; - cursor: pointer; - color: var(--muted); - border-width: 0; - padding: 0 1rem; - /* 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; -} - -details > summary:focus { - outline: 0; -} - -details > div { - margin-top: calc(0px - var(--shift) / 2); - text-align: left; -} - -/* Docstrings */ -.docstring { - margin: 0 0 2rem 2rem; -} - -.docstring pre { - margin-left: 1em; - margin-right: 1em; -} - -.docstring li { - margin-bottom: 15px; -} - -.docstring li:last-child { - margin-bottom: 0; -} - -/* Class name */ -span.name { - color: #61aeee; -} - -/* Inherited class name */ -span.base { - color: #8be9fd; -} - -/* Highlight focused element */ -h1:target, -h2:target, -h3:target, -h4:target, -h5:target, -h6:target { - background-color: var(--accent2); - box-shadow: -1rem 0 0 0 var(--accent2); -} - - -/* Function docs params name. i.e. - -Parameters / Returns / Raises ----------- -name : type - A parameter -*/ -li strong { - color: var(--def); -} - -b, strong { - font-weight: bold; -} - -/* colors for .. warning:: and .. note:: */ -em { - color: orange; -} - -/* -Something ---------- -*/ - -h6 { - padding-top: 1rem; - font-size: 1.7rem; - color: white; -} - -/* -Notes ------ -*/ - -h6#notes { - color: orange; -} - -/* -Raises ------- -*/ - -h6#raises { - color: #ff6666; -} - -/* Decorator color */ -div.decorator { - color: #61aeee; -} - -div:target > .attr, -section:target > .attr, -dd:target > a { - background-color: var(--accent2); -} - -/* Before inherited members colors, i.e., "Exception" */ -.inherited dt, -.inherited dt::before { - color: #61aeee; -} - -.attr:hover { - filter: contrast(0.95); -} - -/* Header link */ -.headerlink { - position: absolute; - margin-left: -2.5rem; - line-height: 1.4rem; - font-size: 1.5rem; - font-weight: normal; - transition: all 100ms ease-in-out; - opacity: 0; -} - -*:hover > .headerlink, -*:target > .attr > .headerlink { - opacity: 1; -} - -/* Attributes */ -.attr { - display: block; - color: var(--text); - margin: 2rem 0 .5rem 1rem; - /* - 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); -} - -.attr:first-of-type { - margin-left: 0; -} - -.member_divisor { - margin-left: 3rem; -} - -.member_divisor > h5 { - margin-left: -1rem; - font-size: 1.5rem; - color: darkgray; -} - -.member_divisor .attr:first-of-type { - margin-top: 0; -} - -.name { - color: var(--name); - font-weight: bold; -} - -.def { - color: var(--def); - font-weight: bold; -} - -.signature { - white-space: pre-wrap; -} - -.annotation { - color: var(--annotation); -} - -/* Inherited Members */ -.inherited { - margin-left: 4rem; -} - -.inherited div { - margin-left: 2rem; -} - -.inherited dt { - font-weight: 700; -} - -.inherited dt, .inherited dd { - display: inline; - margin-left: 0; - margin-bottom: .5rem; -} - -.inherited dd:not(:last-child):after { - content: ", "; -} - -.inherited .class:before { - content: "class "; -} - -.inherited .function a:after { - content: "()"; -} - -/* Search results */ -.search-result .docstring { - overflow: auto; - max-height: 25vh; -} - -.search-result.focused > .attr { - background-color: var(--accent2); -} - -.attribution-padding { - margin-top: 2rem; -} - -.attribution { - display: block; - color: dimgrey; -} - -/***********************/ -/* Syntax highlighting */ -/***********************/ - -/* Comment */ -.c { - color: #6a7aaa; -} - -/* Error */ -.err { - color: #ff5555; - background-color: #1e0010; -} - -/* Keyword */ -.k { - color: #ff79c6; -} - -/* Literal */ -.l { - color: #ae81ff; -} - -/* Name */ -.n { - color: #f8f8f2; -} - -/* Operator */ -.o { - color: #ff79c6; -} - -/* Punctuation */ -.p { - color: #f8f8f2; -} - -/* Comment.Hashbang */ -.ch { - color: #6a7aaa; -} - -/* Comment.Multiline */ -.cm { - color: #6a7aaa; -} - -/* Comment.Preproc */ -.cp { - color: #6a7aaa; -} - -/* Comment.PreprocFile */ -.cpf { - color: #6a7aaa; -} - -/* Comment.Single */ -.c1 { - color: #6a7aaa; -} - -/* Comment.Special */ -.cs { - color: #6a7aaa; -} - -/* Generic.Deleted */ -.gd { - color: #6a7aaa; -} - -/* Generic.Emph */ -.ge { - font-style: italic; -} - -/* Generic.Inserted */ -.gi { - color: #a6e22e; -} - -/* Generic.Output */ -.go { - color: #ff79c6; -} - -/* Generic.Prompt */ -.gp { - color: #f92672; - font-weight: bold; -} - -/* Generic.Strong */ -.gs { - font-weight: bold; -} - -/* Generic.Subheading */ -.gu { - color: #75715e; -} - -/* Keyword.Constant */ -.kc { - color: #ff79c6; -} - -/* Keyword.Declaration */ -.kd { - color: #bd93f9; -} - -/* Keyword.Namespace */ -.kn { - color: #ff79c6; -} - -/* Keyword.Pseudo */ -.kp { - color: #bd93f9; -} - -/* Keyword.Reserved */ - -.kr { - color: #ff79c6; -} - -/* Keyword.Type */ - -.kt { - color: #ff79c6; -} - -/* Literal.Date */ - -.ld { - color: #e6db74; -} - -/* Literal.Number */ - -.m { - color: #ae81ff; -} - -/* Literal.String */ - -.s { - color: #e6db74; -} - -/* Name.Attribute */ - -.na { - color: #a6e22e; -} - -/* Name.Builtin */ - -.nb { - color: #8be9fd; -} - -/* Name.Class */ - -.nc { - color: #e6c07b; -} - -/* Name.Constant */ - -.no { - color: #ff79c6; -} - -/* Name.Entity */ - -.ni { - color: #ff79c6; -} - -/* Name.Exception */ - -.ne { - color: #8be9fd; -} - -/* Name.Function */ - -.nf { - color: #61aeee; -} - -/* Name.Label */ - -.nl { - color: #f8f8f2; -} - -/* Name.Namespace */ - -.nn { - color: #f8f8f2; -} - -/* Name.Other */ - -.nx { - color: #a6e22e; -} - -/* Name.Property */ - -.py { - color: #f8f8f2; -} - -/* Name.Tag */ - -.nt { - color: #f92672; -} - -/* Name.Variable */ - -.nv { - color: #f8f8f2; -} - -/* Operator.Word */ - -.ow { - color: #ff79c6; -} - -/* Text.Whitespace */ - -.w { - color: #f8f8f2; -} - -/* Literal.Number.Bin */ - -.mb { - color: #ae81ff; -} - -/* Literal.Number.Float */ - -.mf { - color: #ae81ff; -} - -/* Literal.Number.Hex */ -.mh { - color: #ae81ff; -} - -/* Literal.Number.Integer */ -.mi { - color: #ae81ff; -} - -/* Literal.Number.Oct */ -.mo { - color: #ae81ff; -} - -/* Literal.String.Affix */ -.sa { - color: #ff79c6; -} - -/* Literal.String.Backtick */ -.sb { - color: #e6db74; -} - -/* Literal.String.Char */ -.sc { - color: #e6db74; -} - -/* Literal.String.Delimiter */ -.dl { - color: #e6db74; -} - -/* Literal.String.Doc */ -.sd { - color: #6272a4; -} - -/* Literal.String.Double */ -.s2 { - color: #e6db74; -} - -/* Literal.String.Escape */ -.se { - color: #ae81ff; -} - -/* Literal.String.Heredoc */ -.sh { - color: #e6db74; -} - -/* Literal.String.Interpol. AKA f-strings */ -.si { - color: #bd93f9; -} - -/* Literal.String.Other */ -.sx { - color: #e6db74; -} - -/* Literal.String.Regex */ -.sr { - color: #e6db74; -} - -/* Literal.String.Single */ -.s1 { - color: #e6db74; -} - -/* Literal.String.Symbol */ - -.ss { - color: #e6db74; -} - -/* Name.Builtin.Pseudo */ -.bp { - color: #bd93f9; -} - -/* Name.Function.Magic */ -.fm { - color: #bd93f9; -} - -/* Name.Variable.Class */ -.vc { - color: #bd93f9; -} - -/* Name.Variable.Global */ -.vg { - color: #f8f8f2; -} - -/* Name.Variable.Instance */ -.vi { - color: #ffffff; -} - -/* Name.Variable.Magic */ -.vm { - color: #bd93f9; -} - -/* Literal.Number.Integer.Long */ -.il { - color: #ae81ff; -} - -/* Decorator module i.e., @typing.? */ -.nd { - color: #61aeee; -} - -/* False, True literals */ -.kc { - color: #bd93f9; -} diff --git a/hikari/_about.py b/hikari/_about.py index 2c531d6ee2..4fe59a7012 100644 --- a/hikari/_about.py +++ b/hikari/_about.py @@ -31,7 +31,7 @@ __author__: typing.Final[str] = "Nekokatt" __maintainer__: typing.Final[str] = "davfsa" __ci__: typing.Final[str] = "https://github.com/hikari-py/hikari/actions" -__copyright__: typing.Final[str] = "© 2021-present davfsa" +__copyright__: typing.Final[str] = "2021-present, 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://docs.hikari-py.dev/master" diff --git a/hikari/api/cache.py b/hikari/api/cache.py index f587ce5772..585e35b689 100644 --- a/hikari/api/cache.py +++ b/hikari/api/cache.py @@ -670,7 +670,7 @@ def get_voice_states_view( ------- CacheView[hikari.snowflakes.Snowflake, CacheView[hikari.snowflakes.Snowflake, hikari.voices.VoiceState]] A view of guild IDs to views of user IDs to objects of the voice - states that were found in the cache, + states that were found in the cache. """ # noqa E501 - Line too long @abc.abstractmethod @@ -1061,7 +1061,7 @@ def set_guild_channel(self, channel: channels.GuildChannel, /) -> None: def update_guild_channel( self, channel: channels.GuildChannel, / ) -> typing.Tuple[typing.Optional[channels.GuildChannel], typing.Optional[channels.GuildChannel]]: - """Update a guild channel in the cache, + """Update a guild channel in the cache. Parameters ---------- @@ -1302,7 +1302,7 @@ def update_member( typing.Tuple[typing.Optional[hikari.guilds.Member], typing.Optional[hikari.guilds.Member]] A tuple of the old cached member object if found (else `None`) and the new cached member object if it could be cached (else - `None`) + `None`). """ @abc.abstractmethod diff --git a/hikari/api/config.py b/hikari/api/config.py index fafa64a978..68007e141b 100644 --- a/hikari/api/config.py +++ b/hikari/api/config.py @@ -155,7 +155,7 @@ def max_redirects(self) -> typing.Optional[int]: def ssl(self) -> ssl_.SSLContext: """SSL context to use. - This may be __assigned__ a `bool` or an `ssl.SSLContext` object. + This may be assigned a `bool` or an `ssl.SSLContext` object. If assigned to `True`, a default SSL context is generated by this class that will enforce SSL verification. This is then stored in diff --git a/hikari/api/entity_factory.py b/hikari/api/entity_factory.py index 05fbd30e65..7d658988f0 100644 --- a/hikari/api/entity_factory.py +++ b/hikari/api/entity_factory.py @@ -1045,7 +1045,7 @@ def serialize_command_permission(self, permission: commands.CommandPermission) - Parameters ---------- - permission: hikari.commands.CommandPermission + permission : hikari.commands.CommandPermission The command permission object to serialize. Returns @@ -1086,9 +1086,26 @@ def deserialize_command_interaction( The deserialized command interaction object. """ + @abc.abstractmethod + def deserialize_autocomplete_interaction( + self, payload: data_binding.JSONObject + ) -> command_interactions.AutocompleteInteraction: + """Parse a raw payload from Discord into an autocomplete interaction object. + + Parameters + ---------- + payload : hikari.internal.data_binding.JSONObject + The JSON payload to deserialize. + + Returns + ------- + hikari.interactions.command_interactions.AutocompleteInteraction + The deserialized autocomplete interaction object. + """ + @abc.abstractmethod def deserialize_interaction(self, payload: data_binding.JSONObject) -> base_interactions.PartialInteraction: - """Parse a raw payload from Discord into a interaction object. + """Parse a raw payload from Discord into an interaction object. .. note:: This isn't required to implement logic for deserializing @@ -1117,7 +1134,7 @@ def serialize_command_option(self, option: commands.CommandOption) -> data_bindi Parameters ---------- - option: hikari.commands.CommandOption + option : hikari.commands.CommandOption The command option object to serialize. Returns diff --git a/hikari/api/event_factory.py b/hikari/api/event_factory.py index 1d90209184..e97751875b 100644 --- a/hikari/api/event_factory.py +++ b/hikari/api/event_factory.py @@ -231,7 +231,7 @@ def deserialize_invite_delete_event( Other Parameters ---------------- - old_invite: typing.Optional[hikari.invites.InviteWithMetadata] + old_invite : typing.Optional[hikari.invites.InviteWithMetadata] The invite object or `None`. Returns @@ -550,7 +550,7 @@ def deserialize_presence_update_event( Other Parameters ---------------- - old_presence: typing.Optional[hikari.presences.MemberPresence] + old_presence : typing.Optional[hikari.presences.MemberPresence] The presence object or `None`. Returns @@ -626,7 +626,7 @@ def deserialize_guild_member_update_event( Other Parameters ---------------- - old_member: typing.Optional[hikari.guilds.Member] + old_member : typing.Optional[hikari.guilds.Member] The member object or `None`. Returns @@ -654,7 +654,7 @@ def deserialize_guild_member_remove_event( Other Parameters ---------------- - old_member: typing.Optional[hikari.guilds.Member] + old_member : typing.Optional[hikari.guilds.Member] The member object or `None`. Returns @@ -705,7 +705,7 @@ def deserialize_guild_role_update_event( Other Parameters ---------------- - old_role: typing.Optional[hikari.guilds.Role] + old_role : typing.Optional[hikari.guilds.Role] The role object or `None`. Returns @@ -733,7 +733,7 @@ def deserialize_guild_role_delete_event( Other Parameters ---------------- - old_role: typing.Optional[hikari.guilds.Role] + old_role : typing.Optional[hikari.guilds.Role] The role object or `None`. Returns @@ -937,7 +937,7 @@ def deserialize_message_update_event( Other Parameters ---------------- - old_message: typing.Optional[hikari.messages.PartialMessage] + old_message : typing.Optional[hikari.messages.PartialMessage] The message object or `None`. Returns @@ -1215,7 +1215,7 @@ def deserialize_own_user_update_event( Other Parameters ---------------- - old_user: typing.Optional[hikari.users.OwnUser] + old_user : typing.Optional[hikari.users.OwnUser] The OwnUser object or `None`. Returns @@ -1247,7 +1247,7 @@ def deserialize_voice_state_update_event( Other Parameters ---------------- - old_state: typing.Optional[hikari.voices.VoiceState] + old_state : typing.Optional[hikari.voices.VoiceState] The VoiceState object or `None`. Returns diff --git a/hikari/api/event_manager.py b/hikari/api/event_manager.py index 1b69c40f45..5cd5aa504a 100644 --- a/hikari/api/event_manager.py +++ b/hikari/api/event_manager.py @@ -59,29 +59,29 @@ class EventStream(iterators.LazyIterator[base_events.EventT], abc.ABC): where `EventStream.open` and `EventStream.close` are implicitly called based on context. - ```py - with EventStream(app, EventType, timeout=50) as stream: - async for entry in stream: - ... - ``` + .. code-block:: python + + with EventStream(app, EventType, timeout=50) as stream: + async for entry in stream: + ... A streamer may also be directly started and closed using the `EventStream.close` and `EventStream.open`. Note that if you don't call `EventStream.close` after opening a streamer when you're finished with it then it may queue events events in memory indefinitely. - ```py - stream = EventStream(app, EventType, timeout=50) - await stream.open() - async for event in stream: - ... + .. code-block:: python - await stream.close() - ``` + stream = EventStream(app, EventType, timeout=50) + await stream.open() + async for event in stream: + ... + + await stream.close() See Also -------- - `hikari.iterators.LazyIterator` + LazyIterator : `hikari.iterators.LazyIterator`. """ __slots__: typing.Sequence[str] = () @@ -197,62 +197,62 @@ def dispatch(self, event: base_events.Event) -> asyncio.Future[typing.Any]: event : hikari.events.base_events.Event The event to dispatch. - Example - ------- + Examples + -------- We can dispatch custom events by first defining a class that derives from `hikari.events.base_events.Event`. - ```py - import attr + .. code-block:: python + + import attr - from hikari.traits import RESTAware - from hikari.events.base_events import Event - from hikari.users import User - from hikari.snowflakes import Snowflake + from hikari.traits import RESTAware + from hikari.events.base_events import Event + from hikari.users import User + from hikari.snowflakes import Snowflake - @attr.define() - class EveryoneMentionedEvent(Event): - app: RESTAware = attr.field() + @attr.define() + class EveryoneMentionedEvent(Event): + app: RESTAware = attr.field() - author: User = attr.field() - '''The user who mentioned everyone.''' + author: User = attr.field() + '''The user who mentioned everyone.''' - content: str = attr.field() - '''The message that was sent.''' + content: str = attr.field() + '''The message that was sent.''' - message_id: Snowflake = attr.field() - '''The message ID.''' + message_id: Snowflake = attr.field() + '''The message ID.''' - channel_id: Snowflake = attr.field() - '''The channel ID.''' - ``` + channel_id: Snowflake = attr.field() + '''The channel ID.''' We can then dispatch our event as we see fit. - ```py - from hikari.events.messages import MessageCreateEvent + .. code-block:: python - @bot.listen(MessageCreateEvent) - async def on_message(event): - if "@everyone" in event.content or "@here" in event.content: - event = EveryoneMentionedEvent( - author=event.author, - content=event.content, - message_id=event.id, - channel_id=event.channel_id, - ) + from hikari.events.messages import MessageCreateEvent - bot.dispatch(event) - ``` + @bot.listen(MessageCreateEvent) + async def on_message(event): + if "@everyone" in event.content or "@here" in event.content: + event = EveryoneMentionedEvent( + author=event.author, + content=event.content, + message_id=event.id, + channel_id=event.channel_id, + ) + + bot.dispatch(event) This event can be listened to elsewhere by subscribing to it with `EventManager.subscribe`. - ```py - @bot.listen(EveryoneMentionedEvent) - async def on_everyone_mentioned(event): - print(event.user, "just pinged everyone in", event.channel_id) - ``` + .. code-block:: python + + @bot.listen(EveryoneMentionedEvent) + async def on_everyone_mentioned(event): + print(event.user, "just pinged everyone in", event.channel_id) Returns ------- @@ -264,11 +264,11 @@ async def on_everyone_mentioned(event): See Also -------- - `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` + 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`. """ # Yes, this is not generic. The reason for this is MyPy complains about @@ -290,27 +290,27 @@ def subscribe(self, event_type: typing.Type[typing.Any], callback: CallbackT[typ consume an instance of the given event, or an instance of a valid subclass if one exists. Any result is discarded. - Example - ------- + Examples + -------- The following demonstrates subscribing a callback to message creation events. - ```py - from hikari.events.messages import MessageCreateEvent + .. code-block:: python - async def on_message(event): - ... + from hikari.events.messages import MessageCreateEvent + + async def on_message(event): + ... - bot.subscribe(MessageCreateEvent, on_message) - ``` + bot.subscribe(MessageCreateEvent, on_message) See Also -------- - `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` + 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`. """ # Yes, this is not generic. The reason for this is MyPy complains about @@ -330,27 +330,27 @@ def unsubscribe(self, event_type: typing.Type[typing.Any], callback: CallbackT[t callback The callback to unsubscribe. - Example - ------- + Examples + -------- The following demonstrates unsubscribing a callback from a message creation event. - ```py - from hikari.events.messages import MessageCreateEvent + .. code-block:: python - async def on_message(event): - ... + from hikari.events.messages import MessageCreateEvent - bot.unsubscribe(MessageCreateEvent, on_message) - ``` + async def on_message(event): + ... + + bot.unsubscribe(MessageCreateEvent, on_message) See Also -------- - `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` + 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`. """ @abc.abstractmethod @@ -407,11 +407,11 @@ def listen( See Also -------- - `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` + 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`. """ @abc.abstractmethod @@ -452,31 +452,31 @@ def stream( 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): - ... - ``` + .. code-block:: python + + 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): + ... or using `open()` and `close()` - ```py - stream = bot.stream(events.ReactionAddEvent, timeout=30).filter(("message_id", message.id)) - stream.open() + .. code-block:: python - async for user_id in stream.map("user_id").limit(50) - ... + stream = bot.stream(events.ReactionAddEvent, timeout=30).filter(("message_id", message.id)) + stream.open() + + async for user_id in stream.map("user_id").limit(50) + ... - stream.close() - ``` + stream.close() See Also -------- - `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` + 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`. """ @abc.abstractmethod @@ -523,9 +523,9 @@ async def wait_for( See Also -------- - `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` + 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`. """ diff --git a/hikari/api/rest.py b/hikari/api/rest.py index a9ed2fbba1..b88a48f187 100644 --- a/hikari/api/rest.py +++ b/hikari/api/rest.py @@ -31,7 +31,6 @@ from hikari import scheduled_events from hikari import traits from hikari import undefined -from hikari.internal import deprecation if typing.TYPE_CHECKING: import datetime @@ -77,6 +76,11 @@ def token_type(self) -> typing.Union[applications.TokenType, str]: async def acquire(self, client: RESTClient) -> str: """Acquire an authorization token (including the prefix). + Parameters + ---------- + client : hikari.api.rest.RESTClient + The rest client to use to acquire the token. + Returns ------- str @@ -221,9 +225,9 @@ async def edit_channel( Other Parameters ---------------- - name : hikari.undefined.UndefinedOr[[str] + name : hikari.undefined.UndefinedOr[str] If provided, the new name for the channel. - position : hikari.undefined.UndefinedOr[[int] + position : hikari.undefined.UndefinedOr[int] If provided, the new position for the channel. topic : hikari.undefined.UndefinedOr[str] If provided, the new topic for the channel. @@ -231,7 +235,7 @@ async def edit_channel( If provided, whether the channel should be marked as NSFW or not. bitrate : hikari.undefined.UndefinedOr[int] If provided, the new bitrate for the channel. - video_quality_mode: hikari.undefined.UndefinedOr[typing.Union[hikari.channels.VideoQualityMode, 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[int] If provided, the new user limit in the channel. @@ -762,14 +766,14 @@ def trigger_typing( Examples -------- - ```py - # Trigger typing just once. - await rest.trigger_typing(channel) + .. code-block:: python + + # Trigger typing just once. + await rest.trigger_typing(channel) - # Trigger typing repeatedly for 1 minute. - async with rest.trigger_typing(channel): - await asyncio.sleep(60) - ``` + # Trigger typing repeatedly for 1 minute. + async with rest.trigger_typing(channel): + await asyncio.sleep(60) .. warning:: Sending a message to the channel will cause the typing indicator @@ -942,13 +946,12 @@ 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. + .. 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. + See `hikari.iterators` for the full API for this iterator type. Parameters ---------- @@ -1288,20 +1291,19 @@ async def edit_message( 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. + .. 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. - 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 + 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. + This is a limitation of Discord's design. If in doubt, specify all + four of them each time. Parameters ---------- @@ -1360,7 +1362,7 @@ async def edit_message( 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 + replace_attachments : bool Whether to replace the attachments with the provided ones. Defaults to `False`. @@ -1726,7 +1728,7 @@ async def delete_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. - user: hikari.snowflakes.SnowflakeishOr[hikari.users.PartialUser] + user : hikari.snowflakes.SnowflakeishOr[hikari.users.PartialUser] Object or ID of the user to remove the reaction of. emoji : typing.Union[str, hikari.emojis.Emoji] Object or name of the emoji to react with. @@ -1817,13 +1819,12 @@ 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. + .. 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. + See `hikari.iterators` for the full API for this iterator type. Parameters ---------- @@ -2208,7 +2209,7 @@ async def execute_webhook( 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: str + token : str The webhook token. content : hikari.undefined.UndefinedOr[typing.Any] If provided, the message contents. If @@ -2355,7 +2356,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: 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 @@ -2415,27 +2416,26 @@ 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. + .. 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. - 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 + 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. + 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: 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 @@ -2489,7 +2489,7 @@ async def edit_webhook_message( 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 + replace_attachments : bool Whether to replace the attachments with the provided ones. Defaults to `False`. @@ -2567,7 +2567,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: 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 @@ -2822,13 +2822,12 @@ 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. + .. 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. + See `hikari.iterators` for the full API for this iterator type. Other Parameters ---------------- @@ -3197,7 +3196,6 @@ async def add_user_to_guild( user: snowflakes.SnowflakeishOr[users.PartialUser], *, nickname: undefined.UndefinedOr[str] = undefined.UNDEFINED, - nick: undefined.UndefinedOr[str] = undefined.UNDEFINED, roles: undefined.UndefinedOr[snowflakes.SnowflakeishSequence[guilds.PartialRole]] = undefined.UNDEFINED, mute: undefined.UndefinedOr[bool] = undefined.UNDEFINED, deaf: undefined.UndefinedOr[bool] = undefined.UNDEFINED, @@ -3227,11 +3225,6 @@ async def add_user_to_guild( If provided, the nick to add to the user when he joins the guild. Requires the `MANAGE_NICKNAMES` permission on the guild. - nick : hikari.undefined.UndefinedOr[str] - Deprecated alias for `nickname`. - - .. deprecated:: 2.0.0.dev106 - Use `nickname` instead. roles : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole]] If provided, the roles to add to the user when he joins the guild. This may be a collection objects or IDs of existing roles. @@ -3322,7 +3315,7 @@ async def fetch_user(self, user: snowflakes.SnowflakeishOr[users.PartialUser]) - Returns ------- hikari.users.User - The requested user + The requested user. Raises ------ @@ -3356,13 +3349,12 @@ def fetch_audit_log( ) -> iterators.LazyIterator[audit_logs.AuditLog]: """Fetch pages of 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. + .. 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. + See `hikari.iterators` for the full API for this iterator type. Parameters ---------- @@ -3848,7 +3840,7 @@ async def create_sticker( Other Parameters ---------------- - description: hikari.undefined.UndefinedOr[str] + description : hikari.undefined.UndefinedOr[str] If provided, the description of the sticker. reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. @@ -4001,13 +3993,15 @@ async def delete_sticker( def guild_builder(self, name: str, /) -> special_endpoints.GuildBuilder: """Make a guild builder to create a guild with. - Notes - ----- - This endpoint can only be used by bots in less than 10 guilds. + .. note:: + This endpoint can only be used by bots in less than 10 guilds. - 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. + .. 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. Parameters ---------- @@ -4043,7 +4037,7 @@ def guild_builder(self, name: str, /) -> special_endpoints.GuildBuilder: See Also -------- - `hikari.api.special_endpoints.GuildBuilder` + GuildBuilder : `hikari.api.special_endpoints.GuildBuilder`. """ @abc.abstractmethod @@ -4503,7 +4497,7 @@ async def create_guild_voice_channel( 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, 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. @@ -4788,13 +4782,12 @@ def fetch_members( 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. + .. 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. + See `hikari.iterators` for the full API for this iterator type. Parameters ---------- @@ -4915,7 +4908,6 @@ async def edit_member( user: snowflakes.SnowflakeishOr[users.PartialUser], *, nickname: undefined.UndefinedNoneOr[str] = undefined.UNDEFINED, - nick: undefined.UndefinedNoneOr[str] = undefined.UNDEFINED, roles: undefined.UndefinedOr[snowflakes.SnowflakeishSequence[guilds.PartialRole]] = undefined.UNDEFINED, mute: undefined.UndefinedOr[bool] = undefined.UNDEFINED, deaf: undefined.UndefinedOr[bool] = undefined.UNDEFINED, @@ -4943,11 +4935,6 @@ async def edit_member( will remove the members nick. Requires the `MANAGE_NICKNAMES` permission. - nick : hikari.undefined.UndefinedOr[str] - Deprecated alias for `nickname`. - - .. deprecated:: 2.0.0.dev104 - Use `nickname` instead. roles : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole]] If provided, the new roles for the member. @@ -5068,55 +5055,6 @@ async def edit_my_member( If an internal error occurs on Discord while handling the request. """ - @abc.abstractmethod - @deprecation.deprecated("2.0.0.dev104", "2.0.0.dev110", "Use `edit_my_member`'s `nick` argument instead.") - async def edit_my_nick( - self, - guild: snowflakes.SnowflakeishOr[guilds.Guild], - nick: typing.Optional[str], - *, - reason: undefined.UndefinedOr[str] = undefined.UNDEFINED, - ) -> None: - """Edit the associated token's member nick. - - 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[str] - The new nick. If `None`, - will remove the nick. - - Other Parameters - ---------------- - reason : hikari.undefined.UndefinedOr[str] - If provided, the reason that will be recorded in the audit logs. - Maximum of 512 characters. - - Raises - ------ - hikari.errors.ForbiddenError - If you are missing the `CHANGE_NICKNAME` permission. - hikari.errors.UnauthorizedError - If you are unauthorized to make the request (invalid/missing token). - hikari.errors.NotFoundError - If the guild is not found. - hikari.errors.RateLimitTooLongError - Raised in the event that a rate limit occurs that is - longer than `max_rate_limit` when making a request. - hikari.errors.RateLimitedError - Usually, Hikari will handle and retry on hitting - rate-limits automatically. This includes most bucket-specific - rate-limits and global rate-limits. In some rare edge cases, - however, Discord implements other undocumented rules for - rate-limiting, such as limits per attribute. These cannot be - detected or handled normally by Hikari due to their undocumented - nature, and will trigger this exception if they occur. - hikari.errors.InternalServerError - If an internal error occurs on Discord while handling the request. - """ - @abc.abstractmethod async def add_role_to_member( self, @@ -5870,7 +5808,7 @@ async def begin_guild_prune( ---------------- days : hikari.undefined.UndefinedOr[int] If provided, number of days to count prune for. - compute_prune_count: hikari.snowflakes.SnowflakeishOr[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]] @@ -6591,26 +6529,6 @@ async def sync_guild_template( If an internal error occurs on Discord while handling the request. """ - @abc.abstractmethod - @deprecation.deprecated("2.0.0.dev106", "2.0.0.dev110", "Use `slash_command_builder` instead.") - def command_builder(self, name: str, description: str) -> special_endpoints.SlashCommandBuilder: - r"""Create a slash command builder for use in `RESTClient.set_application_commands`. - - Parameters - ---------- - name : str - The command's name. This should match the regex `^[\w-]{1,32}$` in - Unicode mode and be lowercase. - description : str - The description to set for the command. - This should be inclusively between 1-100 characters in length. - - Returns - ------- - hikari.api.special_endpoints.SlashCommandBuilder - The created command builder object. - """ - @abc.abstractmethod def slash_command_builder( self, @@ -6666,9 +6584,9 @@ async def fetch_application_command( Parameters ---------- - application: hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] + application : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] Object or ID of the application to fetch a command for. - command: hikari.snowflakes.SnowflakeishOr[hikari.commands.PartialCommand] + command : hikari.snowflakes.SnowflakeishOr[hikari.commands.PartialCommand] Object or ID of the command to fetch. Other Parameters @@ -6716,7 +6634,7 @@ async def fetch_application_commands( Parameters ---------- - application: hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] + application : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] Object or ID of the application to fetch the commands for. Other Parameters @@ -6775,7 +6693,7 @@ async def create_slash_command( Parameters ---------- - application: hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] + application : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] Object or ID of the application to create a command for. name : str The command's name. This should match the regex `^[\w-]{1,32}$` in @@ -6849,7 +6767,7 @@ async def create_context_menu_command( Parameters ---------- - application: hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] + application : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] Object or ID of the application to create a command for. type : typing.Union[hikari.commands.CommandType, int] The type of menu command to make. @@ -6920,9 +6838,9 @@ async def set_application_commands( Parameters ---------- - application: hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] + application : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] Object or ID of the application to create a command for. - commands: typing.Sequence[hikari.api.special_endpoints.CommandBuilder] + commands : typing.Sequence[hikari.api.special_endpoints.CommandBuilder] A sequence of up to 100 initialised command builder objects of the commands to set for this the application. @@ -6982,7 +6900,7 @@ async def edit_application_command( Parameters ---------- - application: hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] + application : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] Object or ID of the application to edit a command for. command : hikari.snowflakes.SnowflakeishOr[hikari.commands.PartialCommand] Object or ID of the command to modify. @@ -7053,7 +6971,7 @@ async def delete_application_command( Parameters ---------- - application: hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] + application : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] Object or ID of the application to delete a command for. command : hikari.snowflakes.SnowflakeishOr[hikari.commands.PartialCommand] Object or ID of the command to delete. @@ -7098,7 +7016,7 @@ async def fetch_application_guild_commands_permissions( Parameters ---------- - application: hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] + application : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] Object or ID of the application to fetch the command permissions for. guild : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild]] Object or ID of the guild to fetch the command permissions for. @@ -7142,11 +7060,11 @@ async def fetch_application_command_permissions( Parameters ---------- - application: hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] + application : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] Object or ID of the application to fetch the command permissions for. guild : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild]] Object or ID of the guild to fetch the command permissions for. - command: hikari.snowflakes.SnowflakeishOr[hikari.commands.PartialCommand] + command : hikari.snowflakes.SnowflakeishOr[hikari.commands.PartialCommand] Object or ID of the command to fetch the command permissions for. Returns @@ -7199,7 +7117,7 @@ async def set_application_command_permissions( Parameters ---------- - application: hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] + application : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] Object or ID of the application to set the command permissions for. guild : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild]] Object or ID of the guild to set the command permissions for. @@ -7244,7 +7162,7 @@ def interaction_deferred_builder( Parameters ---------- - type: typing.Union[hikari.interactions.base_interactions.ResponseType, int] + type : typing.Union[hikari.interactions.base_interactions.ResponseType, int] The type of deferred message response this builder is for. Returns @@ -7290,9 +7208,9 @@ async def fetch_interaction_response( Parameters ---------- - application: hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] + application : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] Object or ID of the application to fetch a command for. - token: str + token : str Token of the interaction to get the initial response for. Returns @@ -7477,24 +7395,23 @@ 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. + .. 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. - 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 + 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. + 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] + application : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] Object or ID of the application to edit a command response for. token : str The interaction's token. @@ -7547,7 +7464,7 @@ async def edit_interaction_response( 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 + replace_attachments : bool Whether to replace the attachments with the provided ones. Defaults to `False`. @@ -7615,7 +7532,7 @@ async def delete_interaction_response( Parameters ---------- - application: hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] + application : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication] Object or ID of the application to delete a command response for. token : str The interaction's token. @@ -7715,7 +7632,7 @@ async def fetch_scheduled_event( Returns ------- hikari.scheduled_events.ScheduledEvent - The scheduled event + The scheduled event. Raises ------ @@ -8173,13 +8090,12 @@ def fetch_scheduled_event_users( ) -> iterators.LazyIterator[scheduled_events.ScheduledEventUser]: """Asynchronously iterate over the users who're subscribed to a scheduled event. - 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. + .. 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. + See `hikari.iterators` for the full API for this iterator type. Parameters ---------- diff --git a/hikari/api/shard.py b/hikari/api/shard.py index 196d8b77c1..40605fbe4e 100644 --- a/hikari/api/shard.py +++ b/hikari/api/shard.py @@ -205,20 +205,20 @@ async def request_guild_members( Parameters ---------- - guild: hikari.guilds.Guild + guild : hikari.guilds.Guild The guild to request chunk for. Other Parameters ---------------- - include_presences: hikari.undefined.UndefinedOr[bool] + include_presences : hikari.undefined.UndefinedOr[bool] If provided, whether to request presences. - query: str + query : str If not `""`, request the members which username starts with the string. - limit: int + limit : int Maximum number of members to send matching the query. - users: hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishSequence[hikari.users.User]] + users : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishSequence[hikari.users.User]] If provided, the users to request for. - nonce: hikari.undefined.UndefinedOr[str] + nonce : hikari.undefined.UndefinedOr[str] If provided, the nonce to be sent with guild chunks. Raises diff --git a/hikari/api/special_endpoints.py b/hikari/api/special_endpoints.py index 07736db5c5..53cba3220c 100644 --- a/hikari/api/special_endpoints.py +++ b/hikari/api/special_endpoints.py @@ -114,68 +114,62 @@ class GuildBuilder(abc.ABC): and detailed. .. 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 - omitted from the following examples for brevity. + 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:: + 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 + re-reference particular objects while building the guild format + to allow for the creation of channels within categories, + and to provide permission overwrites. Examples -------- Creating an empty guild: - ```py - guild = await rest.guild_builder("My Server!").create() - ``` + .. code-block:: python + + guild = await rest.guild_builder("My Server!").create() Creating a guild with an icon: - ```py - from hikari.files import WebResourceStream + .. code-block:: python - guild_builder = rest.guild_builder("My Server!") - guild_builder.icon = WebResourceStream("cat.png", "http://...") - guild = await guild_builder.create() - ``` + from hikari.files import WebResourceStream + + guild_builder = rest.guild_builder("My Server!") + guild_builder.icon = WebResourceStream("cat.png", "http://...") + guild = await guild_builder.create() Adding roles to your guild: - ```py - from hikari.permissions import Permissions + .. code-block:: python + + from hikari.permissions import Permissions - guild_builder = rest.guild_builder("My Server!") + guild_builder = rest.guild_builder("My Server!") - everyone_role_id = guild_builder.add_role("@everyone") - admin_role_id = guild_builder.add_role("Admins", permissions=Permissions.ADMINISTRATOR) + everyone_role_id = guild_builder.add_role("@everyone") + admin_role_id = guild_builder.add_role("Admins", permissions=Permissions.ADMINISTRATOR) - await guild_builder.create() - ``` + await guild_builder.create() .. warning:: The first role must always be the `@everyone` role. Adding a text channel to your guild: - ```py - guild_builder = rest.guild_builder("My Server!") - - category_id = guild_builder.add_category("My safe place") - channel_id = guild_builder.add_text_channel("general", parent_id=category_id) + .. code-block:: python - await guild_builder.create() - ``` + guild_builder = rest.guild_builder("My Server!") - Notes - ----- - - 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. + category_id = guild_builder.add_category("My safe place") + channel_id = guild_builder.add_text_channel("general", parent_id=category_id) - - 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 - re-reference particular objects while building the guild format - to allow for the creation of channels within categories, - and to provide permission overwrites. + await guild_builder.create() """ __slots__: typing.Sequence[str] = () @@ -299,9 +293,6 @@ def add_role( If provided, whether to hoist the role. mentionable : hikari.undefined.UndefinedOr[bool] If provided, whether to make the role mentionable. - reason : hikari.undefined.UndefinedOr[str] - If provided, the reason that will be recorded in the audit logs. - Maximum of 512 characters. Returns ------- @@ -394,9 +385,8 @@ def add_text_channel( Maximum 21600 seconds. permission_overwrites : hikari.undefined.UndefinedOr[typing.Sequence[hikari.channels.PermissionOverwrite]] If provided, the permission overwrites for the channel. - 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. + parent_id : hikari.undefined.UndefinedOr[hikari.snowflakes.Snowflake] + The ID of the category to create the channel under. Returns ------- @@ -443,7 +433,7 @@ def add_voice_channel( 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, 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. @@ -452,9 +442,8 @@ def add_voice_channel( `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. - 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. + parent_id : hikari.undefined.UndefinedOr[hikari.snowflakes.Snowflake] + The ID of the category to create the channel under. Returns ------- @@ -507,9 +496,8 @@ def add_stage_channel( `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. + parent_id : hikari.undefined.UndefinedOr[hikari.snowflakes.Snowflake] + The ID of the category to create the channel under. Returns ------- @@ -787,7 +775,8 @@ def set_tts(self: _T, tts: undefined.UndefinedOr[bool], /) -> _T: Parameters ---------- - tts : Whether this response should trigger text-to-speech processing. + tts : bool + Whether this response should trigger text-to-speech processing. Returns ------- @@ -845,7 +834,7 @@ def set_user_mentions( Parameters ---------- - mentions: hikari.undefined.UndefinedOr[typing.Union[hikari.snowflakes.SnowflakeishSequence[hikari.users.PartialUser], 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, `False` or `hikari.undefined.UNDEFINED` to disallow any user mentions or `True` to allow all user mentions. diff --git a/hikari/api/voice.py b/hikari/api/voice.py index 239fe0abde..d31290a24d 100644 --- a/hikari/api/voice.py +++ b/hikari/api/voice.py @@ -112,7 +112,6 @@ async def connect_to( Any arguments to provide to the `VoiceConnection.initialize` method. - Returns ------- VoiceConnection diff --git a/hikari/applications.py b/hikari/applications.py index 838287e4df..50329802b8 100644 --- a/hikari/applications.py +++ b/hikari/applications.py @@ -409,7 +409,7 @@ class Team(snowflakes.Unique): app: traits.RESTAware = attr.field( repr=False, eq=False, hash=False, metadata={attr_extensions.SKIP_DEEP_COPY: True} ) - """The client application that models may use for procedures.""" + """Client application that models may use for procedures.""" id: snowflakes.Snowflake = attr.field(hash=True, repr=True) """The ID of this entity.""" @@ -484,7 +484,7 @@ class InviteApplication(guilds.PartialApplication): app: traits.RESTAware = attr.field( repr=False, eq=False, hash=False, metadata={attr_extensions.SKIP_DEEP_COPY: True} ) - """The client application that models may use for procedures.""" + """Client application that models may use for procedures.""" cover_image_hash: typing.Optional[str] = attr.field(eq=False, hash=False, repr=False) """The CDN's hash of this application's default rich presence invite cover image.""" @@ -540,7 +540,7 @@ class Application(guilds.PartialApplication): app: traits.RESTAware = attr.field( repr=False, eq=False, hash=False, metadata={attr_extensions.SKIP_DEEP_COPY: True} ) - """The client application that models may use for procedures.""" + """Client application that models may use for procedures.""" is_bot_public: bool = attr.field(eq=False, hash=False, repr=True) """`True` if the bot associated with this application is public.""" diff --git a/hikari/audit_logs.py b/hikari/audit_logs.py index befe6ecc9b..01403f8817 100644 --- a/hikari/audit_logs.py +++ b/hikari/audit_logs.py @@ -135,7 +135,7 @@ class AuditLogChangeKey(str, enums.Enum): REMOVE_ROLE_FROM_MEMBER = "$remove" COLOUR = COLOR - """Alias for "COLOR""" + """Alias for `COLOR`.""" @attr_extensions.with_copy @@ -206,7 +206,7 @@ class BaseAuditLogEntryInfo(abc.ABC): """A base object that all audit log entry info objects will inherit from.""" app: traits.RESTAware = attr.field(repr=False, eq=False, metadata={attr_extensions.SKIP_DEEP_COPY: True}) - """The client application that models may use for procedures.""" + """Client application that models may use for procedures.""" @attr_extensions.with_copy @@ -388,7 +388,7 @@ class MemberMoveEntryInfo(MemberDisconnectEntryInfo): """Extra information for the voice chat based member move entry.""" channel_id: snowflakes.Snowflake = attr.field(repr=True) - """The channel that the member(s) have been moved to""" + """The channel that the member(s) have been moved to.""" async def fetch_channel(self) -> channels.GuildVoiceChannel: """Fetch the guild voice based channel where the member(s) have been moved to. @@ -433,7 +433,7 @@ class AuditLogEntry(snowflakes.Unique): app: traits.RESTAware = attr.field( repr=False, eq=False, hash=False, metadata={attr_extensions.SKIP_DEEP_COPY: True} ) - """The client application that models may use for procedures.""" + """Client application that models may use for procedures.""" id: snowflakes.Snowflake = attr.field(hash=True, repr=True) """The ID of this entity.""" diff --git a/hikari/banner.txt b/hikari/banner.txt index 577f7b1920..d7de2238d6 100644 --- a/hikari/banner.txt +++ b/hikari/banner.txt @@ -1,6 +1,6 @@ ${bold_red}oooo o8o oooo o8o ${reset}${bold_white} 光 ${hikari_version} ${thin_white}[${hikari_git_sha1}]${reset} - ${red}`888 `"' `888 `"' ${reset}${bold_white}${hikari_copyright}${thin_white} - ${hikari_license} license${reset} + ${red}`888 `"' `888 `"' ${reset}${bold_white}© ${hikari_copyright}${thin_white} - ${hikari_license} license${reset} ${yellow} 888 .oo. oooo 888 oooo .oooo. oooo d8b oooo ${reset}${bold_white}interpreter: ${thin_white}${python_implementation} ${python_version}${reset} ${green} 888P"Y88b `888 888 .8P' `P )88b `888""8P `888 ${reset}${bold_white}running on: ${thin_white}${system_description}${reset} ${blue} 888 888 888 888888. .oP"888 888 888 ${reset}${bold_white}installed at: ${thin_white}${hikari_install_location}${reset} diff --git a/hikari/channels.py b/hikari/channels.py index 04ee33770f..d9ea3edd78 100644 --- a/hikari/channels.py +++ b/hikari/channels.py @@ -123,7 +123,7 @@ class ChannelFollow: app: traits.RESTAware = attr.field( repr=False, eq=False, hash=False, metadata={attr_extensions.SKIP_DEEP_COPY: True} ) - """The client application that models may use for procedures.""" + """Client application that models may use for procedures.""" channel_id: snowflakes.Snowflake = attr.field(hash=True, repr=True) """Return the channel ID of the channel being followed.""" @@ -137,9 +137,10 @@ async def fetch_channel(self) -> typing.Union[GuildNewsChannel, GuildTextChannel Returns ------- typing.Union[hikari.channels.GuildNewsChannel, hikari.channels.GuildTextChannel] - The channel being followed. While this will usually be - `GuildNewsChannel`, if the channel's news status has been removed - then this will be a `GuildTextChannel` + The channel being followed. + + While this will usually be `GuildNewsChannel`, if the channel's + news status has been removed then this will be a `GuildTextChannel`. Raises ------ @@ -244,24 +245,24 @@ class PermissionOverwrite: You may sometimes need to make instances of this object to add/edit permission overwrites on channels. - Example - ------- + Examples + -------- Creating a permission overwrite. - ```py - overwrite = PermissionOverwrite( - type=PermissionOverwriteType.MEMBER, - allow=( - Permissions.VIEW_CHANNEL - | Permissions.READ_MESSAGE_HISTORY - | Permissions.SEND_MESSAGES - ), - deny=( - Permissions.MANAGE_MESSAGES - | Permissions.SPEAK - ), - ) - ``` + .. code-block:: python + + overwrite = PermissionOverwrite( + type=PermissionOverwriteType.MEMBER, + allow=( + Permissions.VIEW_CHANNEL + | Permissions.READ_MESSAGE_HISTORY + | Permissions.SEND_MESSAGES + ), + deny=( + Permissions.MANAGE_MESSAGES + | Permissions.SPEAK + ), + ) """ id: snowflakes.Snowflake = attr.field(converter=snowflakes.Snowflake, repr=True) @@ -298,7 +299,7 @@ class PartialChannel(snowflakes.Unique): app: traits.RESTAware = attr.field( repr=False, eq=False, hash=False, metadata={attr_extensions.SKIP_DEEP_COPY: True} ) - """The client application that models may use for procedures.""" + """Client application that models may use for procedures.""" id: snowflakes.Snowflake = attr.field(hash=True, repr=True) """The ID of this entity.""" @@ -606,16 +607,16 @@ async def send( def trigger_typing(self) -> special_endpoints.TypingIndicator: """Trigger typing in a given channel. - This returns an object that can either be `await`ed to trigger typing + This returns an object that can either be awaited to trigger typing once, or used as an async context manager to keep typing until the block completes. - ```py - await channel.trigger_typing() # type for 10s + .. code-block:: python + + await channel.trigger_typing() # type for 10s - async with channel.trigger_typing(): - await asyncio.sleep(35) # keep typing until this finishes - ``` + async with channel.trigger_typing(): + await asyncio.sleep(35) # keep typing until this finishes .. note:: Sending a message to this channel will stop the typing indicator. If @@ -1112,9 +1113,9 @@ async def edit( Other Parameters ---------------- - name : hikari.undefined.UndefinedOr[[str] + name : hikari.undefined.UndefinedOr[str] If provided, the new name for the channel. - position : hikari.undefined.UndefinedOr[[int] + position : hikari.undefined.UndefinedOr[int] If provided, the new position for the channel. topic : hikari.undefined.UndefinedOr[str] If provided, the new topic for the channel. @@ -1122,7 +1123,7 @@ async def edit( If provided, whether the channel should be marked as NSFW or not. bitrate : hikari.undefined.UndefinedOr[int] If provided, the new bitrate for the channel. - video_quality_mode: hikari.undefined.UndefinedOr[typing.Union[hikari.channels.VideoQualityMode, 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[int] If provided, the new user limit in the channel. diff --git a/hikari/colors.py b/hikari/colors.py index 02a32ef3c7..e566b4efd0 100644 --- a/hikari/colors.py +++ b/hikari/colors.py @@ -82,80 +82,80 @@ class Color(int): -------- Examples of conversions to given formats include: - ```py - >>> c = Color(0xFF051A) - Color(r=0xff, g=0x5, b=0x1a) + .. code-block:: python - >>> hex(c) - 0xff051a + >>> c = Color(0xFF051A) + Color(r=0xff, g=0x5, b=0x1a) + + >>> hex(c) + 0xff051a - >>> c.hex_code - #FF051A + >>> c.hex_code + #FF051A - >>> str(c) - #FF051A + >>> str(c) + #FF051A - >>> int(c) - 16712986 + >>> int(c) + 16712986 - >>> c.rgb - (255, 5, 26) + >>> c.rgb + (255, 5, 26) - >>> c.rgb_float - (1.0, 0.0196078431372549, 0.10196078431372549) - ``` + >>> c.rgb_float + (1.0, 0.0196078431372549, 0.10196078431372549) Alternatively, if you have an arbitrary input in one of the above formats that you wish to become a color, you can use `Color.of` on the class itself to automatically attempt to resolve the color: - ```py - >>> Color.of(0xFF051A) - Color(r=0xff, g=0x5, b=0x1a) + .. code-block:: python - >>> Color.of(16712986) - Color(r=0xff, g=0x5, b=0x1a) + >>> Color.of(0xFF051A) + Color(r=0xff, g=0x5, b=0x1a) + + >>> Color.of(16712986) + Color(r=0xff, g=0x5, b=0x1a) - >>> c = Color.of((255, 5, 26)) - Color(r=0xff, g=0x5, b=1xa) + >>> c = Color.of((255, 5, 26)) + Color(r=0xff, g=0x5, b=1xa) - >>> c = Color.of(255, 5, 26) - Color(r=0xff, g=0x5, b=1xa) + >>> c = Color.of(255, 5, 26) + Color(r=0xff, g=0x5, b=1xa) - >>> c = Color.of([0xFF, 0x5, 0x1a]) - Color(r=0xff, g=0x5, b=1xa) + >>> c = Color.of([0xFF, 0x5, 0x1a]) + Color(r=0xff, g=0x5, b=1xa) - >>> c = Color.of("#1a2b3c") - Color(r=0x1a, g=0x2b, b=0x3c) + >>> c = Color.of("#1a2b3c") + Color(r=0x1a, g=0x2b, b=0x3c) - >>> c = Color.of("#1AB") - Color(r=0x11, g=0xaa, b=0xbb) + >>> c = Color.of("#1AB") + Color(r=0x11, g=0xaa, b=0xbb) - >>> c = Color.of((1.0, 0.0196078431372549, 0.10196078431372549)) - Color(r=0xff, g=0x5, b=0x1a) + >>> c = Color.of((1.0, 0.0196078431372549, 0.10196078431372549)) + Color(r=0xff, g=0x5, b=0x1a) - >>> c = Color.of([1.0, 0.0196078431372549, 0.10196078431372549]) - Color(r=0xff, g=0x5, b=0x1a) - ``` + >>> c = Color.of([1.0, 0.0196078431372549, 0.10196078431372549]) + Color(r=0xff, g=0x5, b=0x1a) Examples of initialization of Color objects from given formats include: - ```py - >>> c = Color(16712986) - Color(r=0xff, g=0x5, b=0x1a) + .. code-block:: python + + >>> c = Color(16712986) + Color(r=0xff, g=0x5, b=0x1a) - >>> c = Color.from_rgb(255, 5, 26) - Color(r=0xff, g=0x5, b=1xa) + >>> c = Color.from_rgb(255, 5, 26) + Color(r=0xff, g=0x5, b=1xa) - >>> c = Color.from_hex_code("#1a2b3c") - Color(r=0x1a, g=0x2b, b=0x3c) + >>> c = Color.from_hex_code("#1a2b3c") + Color(r=0x1a, g=0x2b, b=0x3c) - >>> c = Color.from_hex_code("#1AB") - Color(r=0x11, g=0xaa, b=0xbb) + >>> c = Color.from_hex_code("#1AB") + Color(r=0x11, g=0xaa, b=0xbb) - >>> c = Color.from_rgb_float(1.0, 0.0196078431372549, 0.10196078431372549) - Color(r=0xff, g=0x5, b=0x1a) - ``` + >>> c = Color.from_rgb_float(1.0, 0.0196078431372549, 0.10196078431372549) + Color(r=0xff, g=0x5, b=0x1a) """ __slots__: typing.Sequence[str] = () @@ -181,8 +181,8 @@ def rgb(self) -> typing.Tuple[int, int, int]: Represented as a tuple of R, G, B. Each value is in the range [0, 0xFF]. - Example - ------- + Examples + -------- `(123, 234, 47)` """ # noqa: D401 - Imperative mood return (self >> 16) & 0xFF, (self >> 8) & 0xFF, self & 0xFF @@ -193,8 +193,8 @@ def rgb_float(self) -> typing.Tuple[float, float, float]: Represented as a tuple of R, G, B. Each value is in the range [0, 1]. - Example - ------- + Examples + -------- `(0.1, 0.2, 0.76)` """ r, g, b = self.rgb @@ -206,8 +206,8 @@ def hex_code(self) -> str: This is prepended with a `#` symbol, and will be in upper case. - Example - ------- + Examples + -------- `#1A2B3C` """ return "#" + self.raw_hex_code @@ -216,8 +216,8 @@ def hex_code(self) -> str: def raw_hex_code(self) -> str: """Raw hex code. - Example - ------- + Examples + -------- `1A2B3C` """ components = self.rgb @@ -369,27 +369,27 @@ def from_tuple_string(cls, tuple_str: str, /) -> Color: Examples -------- - ```py - # Floats - "1.0 1.0 1.0" - "(1.0 1.0 1.0)" - "[1.0 1.0 1.0]" - "{1.0 1.0 1.0}" - "1.0, 1.0, 1.0" - "(1.0, 1.0, 1.0)" - "[1.0, 1.0, 1.0]" - "{1.0, 1.0, 1.0}" - - # Ints - "252 252 252" - "(252 252 252)" - "[252 252 252]" - "{252 252 252}" - "252, 252, 252" - "(252, 252, 252)" - "[252, 252, 252]" - "{252, 252, 252}" - ``` + .. code-block:: python + + # Floats + "1.0 1.0 1.0" + "(1.0 1.0 1.0)" + "[1.0 1.0 1.0]" + "{1.0 1.0 1.0}" + "1.0, 1.0, 1.0" + "(1.0, 1.0, 1.0)" + "[1.0, 1.0, 1.0]" + "{1.0, 1.0, 1.0}" + + # Ints + "252 252 252" + "(252 252 252)" + "[252 252 252]" + "{252 252 252}" + "252, 252, 252" + "(252, 252, 252)" + "[252, 252, 252]" + "{252, 252, 252}" Parameters ---------- @@ -437,43 +437,43 @@ def of(cls, value: Colorish, /) -> Color: Examples -------- - ```py - >>> Color.of(0xFF051A) - Color(r=0xff, g=0x5, b=0x1a) + .. code-block:: python - >>> Color.of(16712986) - Color(r=0xff, g=0x5, b=0x1a) + >>> Color.of(0xFF051A) + Color(r=0xff, g=0x5, b=0x1a) - >>> c = Color.of((255, 5, 26)) - Color(r=0xff, g=0x5, b=1xa) + >>> Color.of(16712986) + Color(r=0xff, g=0x5, b=0x1a) - >>> c = Color.of([0xFF, 0x5, 0x1a]) - Color(r=0xff, g=0x5, b=1xa) + >>> c = Color.of((255, 5, 26)) + Color(r=0xff, g=0x5, b=1xa) - >>> c = Color.of("#1a2b3c") - Color(r=0x1a, g=0x2b, b=0x3c) + >>> c = Color.of([0xFF, 0x5, 0x1a]) + Color(r=0xff, g=0x5, b=1xa) - >>> c = Color.of("#1AB") - Color(r=0x11, g=0xaa, b=0xbb) + >>> c = Color.of("#1a2b3c") + Color(r=0x1a, g=0x2b, b=0x3c) - >>> c = Color.of((1.0, 0.0196078431372549, 0.10196078431372549)) - Color(r=0xff, g=0x5, b=0x1a) + >>> c = Color.of("#1AB") + Color(r=0x11, g=0xaa, b=0xbb) - >>> c = Color.of([1.0, 0.0196078431372549, 0.10196078431372549]) - Color(r=0xff, g=0x5, b=0x1a) + >>> c = Color.of((1.0, 0.0196078431372549, 0.10196078431372549)) + Color(r=0xff, g=0x5, b=0x1a) + + >>> c = Color.of([1.0, 0.0196078431372549, 0.10196078431372549]) + Color(r=0xff, g=0x5, b=0x1a) - # Commas and brackets are optional, whitespace is ignored, and these - # are compatible with all-ints between 0-255 or all-floats between - # 0.0 and 1.0 only. - >>> c = Color.of("5, 22, 33") - Color(r=0x5, g=0x16, b=0x21) - >>> c = Color.of("(5, 22, 33)") - Color(r=0x5, g=0x16, b=0x21) - >>> c = Color.of("[5, 22, 33]") - Color(r=0x5, g=0x16, b=0x21) - >>> c = Color.of("{5, 22, 33}") - Color(r=0x5, g=0x16, b=0x21) - ``` + # Commas and brackets are optional, whitespace is ignored, and these + # are compatible with all-ints between 0-255 or all-floats between + # 0.0 and 1.0 only. + >>> c = Color.of("5, 22, 33") + Color(r=0x5, g=0x16, b=0x21) + >>> c = Color.of("(5, 22, 33)") + Color(r=0x5, g=0x16, b=0x21) + >>> c = Color.of("[5, 22, 33]") + Color(r=0x5, g=0x16, b=0x21) + >>> c = Color.of("{5, 22, 33}") + Color(r=0x5, g=0x16, b=0x21) Returns ------- diff --git a/hikari/commands.py b/hikari/commands.py index aa7b2f0563..0f2bfe6082 100644 --- a/hikari/commands.py +++ b/hikari/commands.py @@ -190,7 +190,7 @@ class PartialCommand(snowflakes.Unique): """Represents any application command on Discord.""" app: traits.RESTAware = attr.field(eq=False, hash=False, repr=False) - """The client application that models may use for procedures.""" + """Client application that models may use for procedures.""" id: snowflakes.Snowflake = attr.field(hash=True, repr=True) # <>. diff --git a/hikari/emojis.py b/hikari/emojis.py index e0be6709bf..becab9570e 100644 --- a/hikari/emojis.py +++ b/hikari/emojis.py @@ -176,13 +176,13 @@ def url(self) -> str: match what is on Discord if Discord have failed to keep their emoji packs up-to-date with this repository. - Example - ------- - ```py - >>> emoji = hikari.UnicodeEmoji("\N{OK HAND SIGN}") - >>> emoji.url - 'https://raw.githubusercontent.com/twitter/twemoji/master/assets/72x72/1f44c.png' - ``` + Examples + -------- + .. code-block:: python + + >>> 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 @@ -336,7 +336,7 @@ class KnownCustomEmoji(CustomEmoji): app: traits.RESTAware = attr.field( repr=False, eq=False, hash=False, metadata={attr_extensions.SKIP_DEEP_COPY: True} ) - """The client application that models may use for procedures.""" + """Client application that models may use for procedures.""" guild_id: snowflakes.Snowflake = attr.field(eq=False, hash=False, repr=False) """The ID of the guild this emoji belongs to.""" diff --git a/hikari/events/guild_events.py b/hikari/events/guild_events.py index 5c75df7c65..2f3d2586cc 100644 --- a/hikari/events/guild_events.py +++ b/hikari/events/guild_events.py @@ -439,7 +439,7 @@ async def fetch_emojis(self) -> typing.Sequence[emojis_.KnownCustomEmoji]: Returns ------- - typing.Sequence[emojis_.KnownCustomEmoji] + typing.Sequence[hikari.emojis.KnownCustomEmoji] All emojis in the guild. """ return await self.app.rest.fetch_guild_emojis(self.guild_id) diff --git a/hikari/files.py b/hikari/files.py index b01c4dcca3..a80102476d 100644 --- a/hikari/files.py +++ b/hikari/files.py @@ -257,12 +257,12 @@ def guess_file_extension(mimetype: str) -> typing.Optional[str]: mimetype : str The mimetype to guess the extension for. - Example - ------- - ```py - >>> guess_file_extension("image/png") - ".png" - ``` + Examples + -------- + .. code-block:: python + + >>> guess_file_extension("image/png") + ".png" Returns ------- @@ -444,12 +444,12 @@ async def read( ) -> bytes: """Read the entire resource at once into memory. - ```py - data = await resource.read(...) - # ^-- This is a shortcut for the following --v - async with resource.stream(...) as reader: - data = await reader.read() - ``` + .. code-block:: python + + data = await resource.read(...) + # ^-- This is a shortcut for the following --v + async with resource.stream(...) as reader: + data = await reader.read() .. warning:: If you simply wish to re-upload this resource to Discord via @@ -666,27 +666,32 @@ def stream( Examples -------- Downloading an entire resource at once into memory: - ```py - async with obj.stream() as stream: - data = await stream.read() - ``` + + .. code-block:: python + + async with obj.stream() as stream: + data = await stream.read() + Checking the metadata: - ```py - async with obj.stream() as stream: - mimetype = stream.mimetype - if mimetype is None: - ... - elif mimetype not in whitelisted_mimetypes: - ... - else: - ... - ``` + .. code-block:: python + + async with obj.stream() as stream: + mimetype = stream.mimetype + + if mimetype is None: + ... + elif mimetype not in whitelisted_mimetypes: + ... + else: + ... + Fetching the data-uri of a resource: - ```py - async with obj.stream() as stream: - data_uri = await stream.data_uri() - ``` + + .. code-block:: python + + async with obj.stream() as stream: + data_uri = await stream.data_uri() Returns ------- diff --git a/hikari/guilds.py b/hikari/guilds.py index a8247d7266..1fa8764534 100644 --- a/hikari/guilds.py +++ b/hikari/guilds.py @@ -66,7 +66,6 @@ from hikari import urls from hikari import users from hikari.internal import attr_extensions -from hikari.internal import deprecation from hikari.internal import enums from hikari.internal import routes from hikari.internal import time @@ -277,7 +276,7 @@ class GuildWidget: app: traits.RESTAware = attr.field( repr=False, eq=False, hash=False, metadata={attr_extensions.SKIP_DEEP_COPY: True} ) - """The client application that models may use for procedures.""" + """Client application that models may use for procedures.""" channel_id: typing.Optional[snowflakes.Snowflake] = attr.field(repr=True) """The ID of the channel the invite for this embed targets, if enabled.""" @@ -456,8 +455,8 @@ def display_name(self) -> str: See Also -------- - `Member.nickname` - `Member.username` + Nickname: `Member.nickname`. + Username: `Member.username`. """ return self.nickname if isinstance(self.nickname, str) else self.username @@ -916,8 +915,6 @@ async def edit( will remove the members nick. Requires the `MANAGE_NICKNAMES` permission. - nick : hikari.undefined.UndefinedNoneOr[str] - Deprecated alias for `nickname`. roles : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole]] If provided, the new roles for the member. @@ -981,10 +978,6 @@ async def edit( hikari.errors.InternalServerError If an internal error occurs on Discord while handling the request. """ - if nick is not undefined.UNDEFINED: - deprecation.warn_deprecated("nick", "Use 'nickname' argument instead") - nickname = nick - return await self.user.app.rest.edit_member( self.guild_id, self.user.id, @@ -1015,7 +1008,7 @@ class PartialRole(snowflakes.Unique): app: traits.RESTAware = attr.field( repr=False, eq=False, hash=False, metadata={attr_extensions.SKIP_DEEP_COPY: True} ) - """The client application that models may use for procedures.""" + """Client application that models may use for procedures.""" id: snowflakes.Snowflake = attr.field(hash=True, repr=True) """The ID of this entity.""" @@ -1043,12 +1036,12 @@ class Role(PartialRole): """ guild_id: snowflakes.Snowflake = attr.field(eq=False, hash=False, repr=True) - """The ID of the guild this role belongs to""" + """The ID of the guild this role belongs to.""" 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 `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) @@ -1064,7 +1057,7 @@ class Role(PartialRole): """Whether this role can be mentioned by all regardless of permissions.""" permissions: permissions_.Permissions = attr.field(eq=False, hash=False, repr=False) - """The guild wide permissions this role gives to the members it's attached to, + """The guild wide permissions this role gives to the members it's attached to. This may be overridden by channel overwrites. """ @@ -1283,8 +1276,7 @@ class Integration(PartialIntegration): """ 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. + """How many days users with expired subscriptions are given until the expire behavior is enacted out on them. .. note:: This will always be `None` for Discord integrations. @@ -1379,7 +1371,7 @@ class PartialGuild(snowflakes.Unique): app: traits.RESTAware = attr.field( repr=False, eq=False, hash=False, metadata={attr_extensions.SKIP_DEEP_COPY: True} ) - """The client application that models may use for procedures.""" + """Client application that models may use for procedures.""" id: snowflakes.Snowflake = attr.field(hash=True, repr=True) """The ID of this entity.""" @@ -1466,8 +1458,8 @@ async def ban( Parameters ---------- - user: hikari.snowflakes.Snowflakeish[hikari.users.PartialUser] - The user to ban from the guild + user : hikari.snowflakes.Snowflakeish[hikari.users.PartialUser] + The user to ban from the guild. Other Parameters ---------------- @@ -1514,8 +1506,8 @@ async def unban( Parameters ---------- - user: hikari.snowflakes.Snowflakeish[hikari.users.PartialUser] - The user to unban from the guild + user : hikari.snowflakes.Snowflakeish[hikari.users.PartialUser] + The user to unban from the guild. Other Parameters ---------------- @@ -1555,12 +1547,12 @@ async def kick( *, reason: undefined.UndefinedOr[str] = undefined.UNDEFINED, ) -> None: - """Kicks the given user from this guild. + """Kick the given user from this guild. Parameters ---------- - user: hikari.snowflakes.Snowflakeish[hikari.users.PartialUser] - The user to kick from the guild + user : hikari.snowflakes.Snowflakeish[hikari.users.PartialUser] + The user to kick from the guild. Other Parameters ---------------- @@ -1621,7 +1613,7 @@ async def edit( preferred_locale: undefined.UndefinedOr[typing.Union[str, locales.Locale]] = undefined.UNDEFINED, reason: undefined.UndefinedOr[str] = undefined.UNDEFINED, ) -> RESTGuild: - """Edits the guild. + """Edit the guild. Parameters ---------- @@ -1877,7 +1869,7 @@ async def create_sticker( Other Parameters ---------------- - description: hikari.undefined.UndefinedOr[str] + description : hikari.undefined.UndefinedOr[str] If provided, the description of the sticker. reason : hikari.undefined.UndefinedOr[str] If provided, the reason that will be recorded in the audit logs. @@ -2283,7 +2275,7 @@ async def create_voice_channel( 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, 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. @@ -2523,8 +2515,8 @@ async def fetch_roles(self) -> typing.Sequence[Role]: ------ hikari.errors.UnauthorizedError If you are unauthorized to make the request (invalid/missing token). - hikari.errors.NotFoundError - If the guild is not found. + hikari.errors.NotFoundError + If the guild is not found. hikari.errors.RateLimitTooLongError Raised in the event that a rate limit occurs that is longer than `max_rate_limit` when making a request. @@ -2734,16 +2726,14 @@ class Guild(PartialGuild): """The premium tier for this guild.""" public_updates_channel_id: typing.Optional[snowflakes.Snowflake] = attr.field(eq=False, hash=False, repr=False) - """The channel ID of the channel where admins and moderators receive notices - from Discord. + """The 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 `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. + """The ID of the channel where guilds with the `GuildFeature.COMMUNITY` feature display rules and guidelines. If the `GuildFeature.COMMUNITY` feature is not defined, then this is `None`. """ @@ -3012,7 +3002,7 @@ def get_channel( Returns ------- typing.Optional[hikari.channels.GuildChannel] - The object of the guild channel found in cache or `None. + The object of the guild channel found in cache or `None`. """ if not isinstance(self.app, traits.CacheAware): return None diff --git a/hikari/impl/bot.py b/hikari/impl/bot.py index 4e3cb930aa..f8bd087f29 100644 --- a/hikari/impl/bot.py +++ b/hikari/impl/bot.py @@ -195,7 +195,7 @@ class GatewayBot(traits.GatewayBotAware): While mainly supporting the `concurrent.futures.ThreadPoolExecutor` implementation in the standard lib, Hikari's file handling systems should also work with `concurrent.futures.ProcessPoolExecutor`, which - relies on all objects used in IPC to be `pickle`able. Many third-party + relies on all objects used in IPC to be pickleable. Many third-party libraries will not support this fully though, so your mileage may vary on using ProcessPoolExecutor implementations with this parameter. force_color : bool @@ -225,10 +225,10 @@ class GatewayBot(traits.GatewayBotAware): 1. `auto_chunk_members` is `True`. 2. The members intent is enabled. 3. The server is marked as "large" or the presences intent is not enabled - (since Discord only sends other members when presences are declared, - we should also chunk small guilds if the presences are not declared). + (since Discord only sends other members when presences are declared, + we should also chunk small guilds if the presences are not declared). 4. The members cache is enabled or there are listeners for the - `MemberChunkEvent`. + `MemberChunkEvent`. logs : typing.Union[None, LoggerLevel, typing.Dict[str, typing.Any]] Defaults to `"INFO"`. @@ -278,28 +278,28 @@ class GatewayBot(traits.GatewayBotAware): if you are attempting to mock/stub the Discord API for any reason. Generally you do not want to change this. - Example - ------- + Examples + -------- Setting up logging using a dictionary configuration: - ```py - import os + .. code-block:: python - import hikari + import os - # We want to make gateway logs output as DEBUG, and TRACE for all ratelimit content. - bot = hikari.GatewayBot( - token=os.environ["BOT_TOKEN"], - logs={ - "version": 1, - "incremental": True, - "loggers": { - "hikari.gateway": {"level": "DEBUG"}, - "hikari.ratelimits": {"level": "TRACE_HIKARI"}, + import hikari + + # We want to make gateway logs output as DEBUG, and TRACE for all ratelimit content. + bot = hikari.GatewayBot( + token=os.environ["BOT_TOKEN"], + logs={ + "version": 1, + "incremental": True, + "loggers": { + "hikari.gateway": {"level": "DEBUG"}, + "hikari.ratelimits": {"level": "TRACE_HIKARI"}, + }, }, - }, - ) - ``` + ) """ shards: typing.Mapping[int, gateway_shard.GatewayShard] @@ -534,62 +534,62 @@ def dispatch(self, event: base_events.Event) -> asyncio.Future[typing.Any]: event : hikari.events.base_events.Event The event to dispatch. - Example - ------- + Examples + -------- We can dispatch custom events by first defining a class that derives from `hikari.events.base_events.Event`. - ```py - import attr + .. code-block:: python + + import attr - from hikari.traits import RESTAware - from hikari.events.base_events import Event - from hikari.users import User - from hikari.snowflakes import Snowflake + from hikari.traits import RESTAware + from hikari.events.base_events import Event + from hikari.users import User + from hikari.snowflakes import Snowflake - @attr.define() - class EveryoneMentionedEvent(Event): - app: RESTAware = attr.field() + @attr.define() + class EveryoneMentionedEvent(Event): + app: RESTAware = attr.field() - author: User = attr.field() - '''The user who mentioned everyone.''' + author: User = attr.field() + '''The user who mentioned everyone.''' - content: str = attr.field() - '''The message that was sent.''' + content: str = attr.field() + '''The message that was sent.''' - message_id: Snowflake = attr.field() - '''The message ID.''' + message_id: Snowflake = attr.field() + '''The message ID.''' - channel_id: Snowflake = attr.field() - '''The channel ID.''' - ``` + channel_id: Snowflake = attr.field() + '''The channel ID.''' We can then dispatch our event as we see fit. - ```py - from hikari.events.messages import MessageCreateEvent - - @bot.listen(MessageCreateEvent) - async def on_message(event): - if "@everyone" in event.content or "@here" in event.content: - event = EveryoneMentionedEvent( - author=event.author, - content=event.content, - message_id=event.id, - channel_id=event.channel_id, - ) + .. code-block:: python + + from hikari.events.messages import MessageCreateEvent + + @bot.listen(MessageCreateEvent) + async def on_message(event): + if "@everyone" in event.content or "@here" in event.content: + event = EveryoneMentionedEvent( + author=event.author, + content=event.content, + message_id=event.id, + channel_id=event.channel_id, + ) - bot.dispatch(event) - ``` + bot.dispatch(event) This event can be listened to elsewhere by subscribing to it with - `EventManager.subscribe`. + `hikari.impl.event_manager_base.EventManager.subscribe`. + + .. code-block:: python - ```py - @bot.listen(EveryoneMentionedEvent) - async def on_everyone_mentioned(event): - print(event.user, "just pinged everyone in", event.channel_id) - ``` + @bot.listen(EveryoneMentionedEvent) + async def on_everyone_mentioned(event): + print(event.user, "just pinged everyone in", event.channel_id) Returns ------- @@ -601,11 +601,11 @@ async def on_everyone_mentioned(event): See Also -------- - `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` + Listen : `hikari.impl.bot.GatewayBot.listen`. + Stream : `hikari.impl.bot.GatewayBot.stream`. + Subscribe : `hikari.impl.bot.GatewayBot.subscribe`. + Unsubscribe : `hikari.impl.bot.GatewayBot.unsubscribe`. + Wait_for : `hikari.impl.bot.GatewayBot.wait_for`. """ return self._event_manager.dispatch(event) @@ -668,11 +668,11 @@ def listen( See Also -------- - `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` + Dispatch : `hikari.impl.bot.GatewayBot.dispatch`. + Stream : `hikari.impl.bot.GatewayBot.stream`. + Subscribe : `hikari.impl.bot.GatewayBot.subscribe`. + Unsubscribe : `hikari.impl.bot.GatewayBot.unsubscribe`. + Wait_for : `hikari.impl.bot.GatewayBot.wait_for`. """ return self._event_manager.listen(*event_types) @@ -778,7 +778,7 @@ def run( enable_signal_handlers : typing.Optional[bool] Defaults to `True` if this is started in the main thread. - If on a __non-Windows__ OS with builtin support for kernel-level + If on a non-Windows OS with builtin support for kernel-level POSIX signals, then setting this to `True` will allow treating keyboard interrupts and other OS signals to safely shut down the application as calls to shut down the application properly @@ -1152,31 +1152,31 @@ def stream( 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): - ... - ``` + .. code-block:: python + + 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): + ... or using `open()` and `close()` - ```py - stream = bot.stream(events.ReactionAddEvent, timeout=30).filter(("message_id", message.id)) - stream.open() + .. code-block:: python + + stream = bot.stream(events.ReactionAddEvent, timeout=30).filter(("message_id", message.id)) + stream.open() - async for user_id in stream.map("user_id").limit(50) - ... + async for user_id in stream.map("user_id").limit(50) + ... - stream.close() - ``` + stream.close() See Also -------- - `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` + Dispatch : `hikari.impl.bot.GatewayBot.dispatch`. + Listen : `hikari.impl.bot.GatewayBot.listen`. + Subscribe : `hikari.impl.bot.GatewayBot.subscribe`. + Unsubscribe : `hikari.impl.bot.GatewayBot.unsubscribe`. + Wait_for : `hikari.impl.bot.GatewayBot.wait_for`. """ self._check_if_alive() return self._event_manager.stream(event_type, timeout=timeout, limit=limit) @@ -1199,27 +1199,27 @@ def subscribe(self, event_type: typing.Type[typing.Any], callback: event_manager consume an instance of the given event, or an instance of a valid subclass if one exists. Any result is discarded. - Example - ------- + Examples + -------- The following demonstrates subscribing a callback to message creation events. - ```py - from hikari.events.messages import MessageCreateEvent + .. code-block:: python - async def on_message(event): - ... + from hikari.events.messages import MessageCreateEvent - bot.subscribe(MessageCreateEvent, on_message) - ``` + async def on_message(event): + ... + + bot.subscribe(MessageCreateEvent, on_message) See Also -------- - `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` + Dispatch : `hikari.impl.bot.GatewayBot.dispatch`. + Listen : `hikari.impl.bot.GatewayBot.listen`. + Stream : `hikari.impl.bot.GatewayBot.stream`. + Unsubscribe : `hikari.impl.bot.GatewayBot.unsubscribe`. + Wait_for : `hikari.impl.bot.GatewayBot.wait_for`. """ self._event_manager.subscribe(event_type, callback) @@ -1239,27 +1239,27 @@ def unsubscribe(self, event_type: typing.Type[typing.Any], callback: event_manag callback The callback to unsubscribe. - Example - ------- + Examples + -------- The following demonstrates unsubscribing a callback from a message creation event. - ```py - from hikari.events.messages import MessageCreateEvent + .. code-block:: python + + from hikari.events.messages import MessageCreateEvent - async def on_message(event): - ... + async def on_message(event): + ... - bot.unsubscribe(MessageCreateEvent, on_message) - ``` + bot.unsubscribe(MessageCreateEvent, on_message) See Also -------- - `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` + 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`. """ self._event_manager.unsubscribe(event_type, callback) @@ -1306,11 +1306,11 @@ async def wait_for( See Also -------- - `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` + Dispatch : `hikari.impl.bot.GatewayBot.dispatch`. + Listen : `hikari.impl.bot.GatewayBot.listen`. + Stream : `hikari.impl.bot.GatewayBot.stream`. + Subscribe : `hikari.impl.bot.GatewayBot.subscribe`. + Unsubscribe : `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 3084e41719..ac5c828fef 100644 --- a/hikari/impl/buckets.py +++ b/hikari/impl/buckets.py @@ -316,7 +316,7 @@ def resolve(self, real_bucket_hash: str) -> None: Parameters ---------- - real_bucket_hash: str + real_bucket_hash : str The real bucket hash for this bucket. Raises @@ -360,9 +360,10 @@ class RESTBucketManager: """Maps routes to their `X-RateLimit-Bucket` header being used.""" real_hashes_to_buckets: typing.Final[typing.MutableMapping[str, RESTBucket]] - """Maps full bucket hashes (`X-RateLimit-Bucket` appended with a hash of - major parameters used in that compiled route) to their corresponding rate - limiters. + """Maps full bucket hashes to their corresponding rate limiters. + + The full bucket hash consists of `X-RateLimit-Bucket` appended with a hash of + major parameters used in that compiled route. """ closed_event: typing.Final[asyncio.Event] diff --git a/hikari/impl/config.py b/hikari/impl/config.py index ff24b1ea37..31b2834180 100644 --- a/hikari/impl/config.py +++ b/hikari/impl/config.py @@ -66,7 +66,8 @@ class BasicAuthHeader: username: str = attr.field(validator=attr.validators.instance_of(str)) """Username for the header. - ...warning :: This must not contain `":"`. + .. warning:: + This must not contain `":"`. """ password: str = attr.field(repr=False, validator=attr.validators.instance_of(str)) @@ -251,7 +252,7 @@ def _(self, _: attr.Attribute[typing.Optional[int]], value: typing.Any) -> None: ) """SSL context to use. - This may be __assigned__ a `bool` or an `ssl.SSLContext` object. + This may be assigned a `bool` or an `ssl.SSLContext` object. If assigned to `True`, a default SSL context is generated by this class that will enforce SSL verification. This is then stored in diff --git a/hikari/impl/interaction_server.py b/hikari/impl/interaction_server.py index 3137a05001..d39b16bdc5 100644 --- a/hikari/impl/interaction_server.py +++ b/hikari/impl/interaction_server.py @@ -475,7 +475,7 @@ async def start( enable_signal_handlers : typing.Optional[bool] Defaults to `True` if this is started in the main thread. - If on a __non-Windows__ OS with builtin support for kernel-level + If on a non-Windows OS with builtin support for kernel-level POSIX signals, then setting this to `True` will allow treating keyboard interrupts and other OS signals to safely shut down the application as calls to shut down the application properly diff --git a/hikari/impl/rate_limits.py b/hikari/impl/rate_limits.py index 174800b610..a5badf4916 100644 --- a/hikari/impl/rate_limits.py +++ b/hikari/impl/rate_limits.py @@ -193,19 +193,18 @@ def throttle(self, retry_after: float) -> None: Iterates repeatedly while the queue is not empty, adhering to any rate limits that occur in the mean time. - Notes - ----- - This will invoke `ManualRateLimiter.unlock_later` as a scheduled - task in the future (it will not await it to finish). + .. 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 - `None`. This means you can check if throttling is occurring - by checking if `throttle_task` is not `None`. + When the `ManualRateLimiter.unlock_later` coroutine function + completes, it should be expected to set the `throttle_task` to + `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 - to override existing ones. + 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 + to override existing ones. Parameters ---------- @@ -220,17 +219,16 @@ def throttle(self, retry_after: float) -> None: self.throttle_task = loop.create_task(self.unlock_later(retry_after)) async def unlock_later(self, retry_after: float) -> None: - """Sleeps for a while, then removes the lock. + """Sleep for a while, then remove the lock. - Notes - ----- - You should not need to invoke this directly. Call - `ManualRateLimiter.throttle` instead. + .. warning:: + 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 - `None`. This means you can check if throttling is occurring - by checking if `throttle_task` is not `None`. + When the `ManualRateLimiter.unlock_later` coroutine function + completes, it should be expected to set the `throttle_task` to + `None`. This means you can check if throttling is occurring + by checking if `throttle_task` is not `None`. Parameters ---------- @@ -252,7 +250,7 @@ class WindowedBurstRateLimiter(BurstRateLimiter): Rate limiter for rate limits that last fixed periods of time with a fixed number of times it can be used in that time frame. - To use this, you should call WindowedBurstRateLimiter.acquire` and await the + To use this, you should call `WindowedBurstRateLimiter.acquire` and await the result immediately before performing your rate-limited task. If the rate limit has been hit, acquiring time will return an incomplete @@ -284,17 +282,13 @@ class WindowedBurstRateLimiter(BurstRateLimiter): """The `time.monotonic_timestamp` that the limit window ends at.""" remaining: int - """The number of `WindowedBurstRateLimiter.acquire`'s left in this window - before you will get rate limited. - """ + """The number of `WindowedBurstRateLimiter.acquire`'s left in this window before you will get rate limited.""" period: float """How long the window lasts for from the start in seconds.""" limit: int - """The maximum number of `WindowedBurstRateLimiter.acquire`'s allowed in - this time window. - """ + """The maximum number of `WindowedBurstRateLimiter.acquire`'s allowed in this time window.""" def __init__(self, name: str, period: float, limit: int) -> None: super().__init__(name) @@ -379,7 +373,7 @@ def is_rate_limited(self, now: float) -> bool: return self.remaining <= 0 def drip(self) -> None: - """Decrements the remaining counter.""" + """Decrement the remaining counter.""" self.remaining -= 1 async def throttle(self) -> None: @@ -430,8 +424,9 @@ class ExponentialBackOff: base : float The base to use. Defaults to `2.0`. 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. + The max value the backoff can be in a single iteration. + + All values will be capped to this base value plus some random jitter. jitter_multiplier : float The multiplier for the random jitter. Defaults to `1.0`. Set to `0` to disable jitter. @@ -455,9 +450,7 @@ class ExponentialBackOff: """The current increment.""" maximum: float - """This is the max value the backoff can be in a single iteration before an - `asyncio.TimeoutError` is raised. - """ + """This is the max value the backoff can be in a single iteration before an `asyncio.TimeoutError` is raised.""" jitter_multiplier: typing.Final[float] """The multiplier for the random jitter. diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py index 22c2743020..55e0c90784 100644 --- a/hikari/impl/rest.py +++ b/hikari/impl/rest.py @@ -121,7 +121,7 @@ class ClientCredentialsStrategy(rest_api.TokenStrategy): Parameters ---------- - client: typing.Optional[hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication]] + client : typing.Optional[hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication]] Object or ID of the application this client credentials strategy should authorize as. client_secret : typing.Optional[str] @@ -348,14 +348,14 @@ def acquire( Examples -------- - ```py - rest_app = RESTApp() + .. code-block:: python - # Using the returned client as a context manager to implicitly start - # and stop it. - async with rest_app.acquire("A token", "Bot") as client: - user = await client.fetch_my_user() - ``` + rest_app = RESTApp() + + # Using the returned client as a context manager to implicitly start + # and stop it. + async with rest_app.acquire("A token", "Bot") as client: + user = await client.fetch_my_user() Parameters ---------- @@ -520,9 +520,9 @@ class RESTClientImpl(rest_api.RESTClient): Raises ------ ValueError - * If `token_type` is provided when a token strategy 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` + If `token_type` is provided when a token strategy is passed for `token`, if + `token_type` is left as `None` when a string is passed for `token` or if a + value greater than 5 is provided for `max_retries`. """ __slots__: typing.Sequence[str] = ( @@ -1062,7 +1062,6 @@ async def edit_permission_overwrite( body.put("deny", deny) await self._request(route, json=body, reason=reason) - @deprecation.deprecated("2.0.0.dev110", "edit_permission_overwrite") async def edit_permission_overwrites( self, channel: snowflakes.SnowflakeishOr[channels_.GuildChannel], @@ -1078,8 +1077,15 @@ async def edit_permission_overwrites( """Edit permissions for a specific entity in the given guild channel. .. deprecated:: 2.0.0.dev110 + Will be removed in `2.0.0.dev112`. + Use `RESTClient.edit_permission_overwrite` instead. """ + deprecation.warn_deprecated( + "edit_permission_overwrites", + removal_version="2.0.0.dev112", + additional_info="Use `edit_permission_overwrite` instead", + ) await self.edit_permission_overwrite( channel, target, target_type=target_type, allow=allow, deny=deny, reason=reason ) @@ -2039,15 +2045,10 @@ async def add_user_to_guild( user: snowflakes.SnowflakeishOr[users.PartialUser], *, nickname: undefined.UndefinedOr[str] = undefined.UNDEFINED, - nick: undefined.UndefinedOr[str] = undefined.UNDEFINED, roles: undefined.UndefinedOr[snowflakes.SnowflakeishSequence[guilds.PartialRole]] = undefined.UNDEFINED, mute: undefined.UndefinedOr[bool] = undefined.UNDEFINED, deaf: undefined.UndefinedOr[bool] = undefined.UNDEFINED, ) -> typing.Optional[guilds.Member]: - if nick is not undefined.UNDEFINED: - deprecation.warn_deprecated("nick", "Use 'nickname' argument instead") - nickname = nick - route = routes.PUT_GUILD_MEMBER.compile(guild=guild, user=user) body = data_binding.JSONObjectBuilder() body.put("access_token", str(access_token)) @@ -2618,7 +2619,6 @@ async def edit_member( user: snowflakes.SnowflakeishOr[users.PartialUser], *, nickname: undefined.UndefinedNoneOr[str] = undefined.UNDEFINED, - nick: undefined.UndefinedNoneOr[str] = undefined.UNDEFINED, roles: undefined.UndefinedOr[snowflakes.SnowflakeishSequence[guilds.PartialRole]] = undefined.UNDEFINED, mute: undefined.UndefinedOr[bool] = undefined.UNDEFINED, deaf: undefined.UndefinedOr[bool] = undefined.UNDEFINED, @@ -2628,10 +2628,6 @@ async def edit_member( communication_disabled_until: undefined.UndefinedNoneOr[datetime.datetime] = undefined.UNDEFINED, reason: undefined.UndefinedOr[str] = undefined.UNDEFINED, ) -> guilds.Member: - if nick is not undefined.UNDEFINED: - deprecation.warn_deprecated("nick", "Use 'nickname' argument instead") - nickname = nick - route = routes.PATCH_GUILD_MEMBER.compile(guild=guild, user=user) body = data_binding.JSONObjectBuilder() body.put("nick", nickname) @@ -2668,16 +2664,6 @@ 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", "2.0.0.dev110", "Use `edit_my_member`'s `nick` argument instead.") - async def edit_my_nick( - self, - guild: snowflakes.SnowflakeishOr[guilds.Guild], - nick: typing.Optional[str], - *, - reason: undefined.UndefinedOr[str] = undefined.UNDEFINED, - ) -> None: - await self.edit_my_member(guild, nickname=nick, reason=reason) - async def add_role_to_member( self, guild: snowflakes.SnowflakeishOr[guilds.PartialGuild], @@ -3123,10 +3109,6 @@ async def delete_template( assert isinstance(response, dict) return self._entity_factory.deserialize_template(response) - @deprecation.deprecated("2.0.0.dev106", "2.0.0.dev110", "Use `slash_command_builder` instead.") - def command_builder(self, name: str, description: str) -> special_endpoints.SlashCommandBuilder: - return self.slash_command_builder(name, description) - def slash_command_builder(self, name: str, description: str) -> special_endpoints.SlashCommandBuilder: return special_endpoints_impl.SlashCommandBuilder(name, description) @@ -3205,29 +3187,6 @@ async def _create_application_command( assert isinstance(response, dict) return response -<<<<<<< HEAD - @deprecation.deprecated("2.0.0.dev106", "2.0.0.dev110", "Use `create_slash_command` instead.") - async def create_application_command( - self, - application: snowflakes.SnowflakeishOr[guilds.PartialApplication], - name: str, - description: str, - guild: undefined.UndefinedOr[snowflakes.SnowflakeishOr[guilds.PartialGuild]] = undefined.UNDEFINED, - *, - options: undefined.UndefinedOr[typing.Sequence[commands.CommandOption]] = undefined.UNDEFINED, - default_permission: undefined.UndefinedOr[bool] = undefined.UNDEFINED, - ) -> commands.SlashCommand: - return await self.create_slash_command( - application=application, - name=name, - description=description, - guild=guild, - options=options, - default_permission=default_permission, - ) - -======= ->>>>>>> master async def create_slash_command( self, application: snowflakes.SnowflakeishOr[guilds.PartialApplication], diff --git a/hikari/impl/rest_bot.py b/hikari/impl/rest_bot.py index de4beb9a27..5daf60713e 100644 --- a/hikari/impl/rest_bot.py +++ b/hikari/impl/rest_bot.py @@ -102,7 +102,7 @@ class RESTBot(traits.RESTBotAware, interaction_server_.InteractionServer): While mainly supporting the `concurrent.futures.ThreadPoolExecutor` implementation in the standard lib, Hikari's file handling systems should also work with `concurrent.futures.ProcessPoolExecutor`, which - relies on all objects used in IPC to be `pickle`able. Many third-party + relies on all objects used in IPC to be pickleable. Many third-party libraries will not support this fully though, so your mileage may vary on using ProcessPoolExecutor implementations with this parameter. force_color : bool @@ -157,7 +157,7 @@ class RESTBot(traits.RESTBotAware, interaction_server_.InteractionServer): 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 `None`. - proxy_settings : typing.Optional[config.ProxySettings] + proxy_settings : typing.Optional[hikari.impl.config.ProxySettings] Custom proxy settings to use with network-layer logic in your application to get through an HTTP-proxy. public_key : typing.Union[str, bytes, None] @@ -436,7 +436,7 @@ def run( enable_signal_handlers : typing.Optional[bool] Defaults to `True` if this is started in the main thread. - If on a __non-Windows__ OS with builtin support for kernel-level + If on a non-Windows OS with builtin support for kernel-level POSIX signals, then setting this to `True` will allow treating keyboard interrupts and other OS signals to safely shut down the application as calls to shut down the application properly @@ -534,7 +534,7 @@ async def start( enable_signal_handlers : typing.Optional[bool] Defaults to `True` if this is started in the main thread. - If on a __non-Windows__ OS with builtin support for kernel-level + If on a non-Windows OS with builtin support for kernel-level POSIX signals, then setting this to `True` will allow treating keyboard interrupts and other OS signals to safely shut down the application as calls to shut down the application properly diff --git a/hikari/impl/shard.py b/hikari/impl/shard.py index 8b84c36385..3cf287cd19 100644 --- a/hikari/impl/shard.py +++ b/hikari/impl/shard.py @@ -136,7 +136,7 @@ def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None: self.zlib = zlib.decompressobj() self.sent_close = False - # Initialized from `connect' + # Initialized from `connect` # These are type-hinted here instead of above to prevent MyPy from misinterpreting typing.Callable as a method self.logger: logging.Logger self.log_filterer: typing.Callable[[str], str] diff --git a/hikari/impl/special_endpoints.py b/hikari/impl/special_endpoints.py index 09f0096728..6e9683ce5e 100644 --- a/hikari/impl/special_endpoints.py +++ b/hikari/impl/special_endpoints.py @@ -194,69 +194,62 @@ class GuildBuilder(special_endpoints.GuildBuilder): and detailed. .. 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 - omitted from the following examples for brevity. + 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:: + 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 + re-reference particular objects while building the guild format + to allow for the creation of channels within categories, + and to provide permission overwrites. Examples -------- - Creating an empty guild. + Creating an empty guild: - ```py - guild = await rest.guild_builder("My Server!").create() - ``` + .. code-block:: python - Creating a guild with an icon + guild = await rest.guild_builder("My Server!").create() - ```py - from hikari.files import WebResourceStream + Creating a guild with an icon: - guild_builder = rest.guild_builder("My Server!") - guild_builder.icon = WebResourceStream("cat.png", "http://...") - guild = await guild_builder.create() - ``` + .. code-block:: python - Adding roles to your guild. + from hikari.files import WebResourceStream - ```py - from hikari.permissions import Permissions + guild_builder = rest.guild_builder("My Server!") + guild_builder.icon = WebResourceStream("cat.png", "http://...") + guild = await guild_builder.create() - guild_builder = rest.guild_builder("My Server!") + Adding roles to your guild: - everyone_role_id = guild_builder.add_role("@everyone") - admin_role_id = guild_builder.add_role("Admins", permissions=Permissions.ADMINISTRATOR) + .. code-block:: python - await guild_builder.create() - ``` + from hikari.permissions import Permissions - .. warning:: - The first role must always be the `@everyone` role. + guild_builder = rest.guild_builder("My Server!") - .. 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. + everyone_role_id = guild_builder.add_role("@everyone") + admin_role_id = guild_builder.add_role("Admins", permissions=Permissions.ADMINISTRATOR) - .. 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 - re-reference particular objects while building the guild format. + await guild_builder.create() - This is provided to allow creation of channels within categories, - and to provide permission overwrites. + .. warning:: + The first role must always be the `@everyone` role. + + Adding a text channel to your guild: - Adding a text channel to your guild. + .. code-block:: python - ```py - guild_builder = rest.guild_builder("My Server!") + guild_builder = rest.guild_builder("My Server!") - category_id = guild_builder.add_category("My safe place") - channel_id = guild_builder.add_text_channel("general", parent_id=category_id) + category_id = guild_builder.add_category("My safe place") + channel_id = guild_builder.add_text_channel("general", parent_id=category_id) - await guild_builder.create() - ``` + await guild_builder.create() """ # Required arguments. @@ -1128,7 +1121,7 @@ def set_is_dm_enabled(self: _CommandBuilderT, state: undefined.UndefinedOr[bool] self._is_dm_enabled = state return self - def build(self, _: entity_factory_.EntityFactory, /) -> typing.MutableMapping[str, typing.Any]: + def build(self, entity_factory: entity_factory_.EntityFactory, /) -> typing.MutableMapping[str, typing.Any]: data = data_binding.JSONObjectBuilder() data["name"] = self._name data["type"] = self.type @@ -1512,7 +1505,7 @@ class ActionRowBuilder(special_endpoints.ActionRowBuilder): """Standard implementation of `hikari.api.special_endpoints.ActionRowBuilder`.""" _components: typing.List[special_endpoints.ComponentBuilder] = attr.field(factory=list) - _stored_type: typing.Optional[messages.ComponentType] = attr.field(default=None) + _stored_type: typing.Optional[messages.ComponentType] = attr.field(default=None, init=False) @property def components(self) -> typing.Sequence[special_endpoints.ComponentBuilder]: diff --git a/hikari/intents.py b/hikari/intents.py index d68e1155a8..335e67bb77 100644 --- a/hikari/intents.py +++ b/hikari/intents.py @@ -62,29 +62,29 @@ class Intents(enums.Flag): For example, if we wish to only refer to the `GUILDS` intent, then it is simply a case of accessing it normally. - ```py - my_intents = Intents.GUILDS - ``` + .. code-block:: python + + my_intents = Intents.GUILDS If we wanted to have several intents grouped together, we would use the bitwise-or operator to combine them (`|`). This can be done in-place with the `|=` operator if needed. - ```py - # One or two values that fit on one line. - my_intents = Intents.GUILD_MESSAGES | Intents.PRIVATE_MESSAGES - - # Several intents together. You may find it useful to format these like - # so to keep your code readable. - my_intents = ( - Intents.GUILDS | - Intents.GUILD_BANS | - Intents.GUILD_EMOJIS | - Intents.GUILD_INTEGRATIONS | - Intents.GUILD_MESSAGES | - Intents.PRIVATE_MESSAGES - ) - ``` + .. code-block:: python + + # One or two values that fit on one line. + my_intents = Intents.GUILD_MESSAGES | Intents.PRIVATE_MESSAGES + + # Several intents together. You may find it useful to format these like + # so to keep your code readable. + my_intents = ( + Intents.GUILDS | + Intents.GUILD_BANS | + Intents.GUILD_EMOJIS | + Intents.GUILD_INTEGRATIONS | + Intents.GUILD_MESSAGES | + Intents.PRIVATE_MESSAGES + ) To check if an intent **is present** in a given intents bitfield, you can use the bitwise-and operator (`&`) to check. This returns the "intersection" @@ -92,36 +92,36 @@ class Intents(enums.Flag): use the `==` operator to check that specific values are present. You can check in-place with the `&=` operator if needed. - ```py - # Check if an intent is set: - if (my_intents & Intents.GUILD_MESSAGES) == Intents.GUILD_MESSAGES: - print("Guild messages are enabled") + .. code-block:: python + + # Check if an intent is set: + if (my_intents & Intents.GUILD_MESSAGES) == Intents.GUILD_MESSAGES: + print("Guild messages are enabled") - # Checking if ALL in a combination are set: - expected_intents = (Intents.GUILD_MESSAGES | Intents.PRIVATE_MESSAGES) - if (my_intents & expected_intents) == expected_intents: - print("Messages are enabled in guilds and private messages.") + # Checking if ALL in a combination are set: + expected_intents = (Intents.GUILD_MESSAGES | Intents.PRIVATE_MESSAGES) + if (my_intents & expected_intents) == expected_intents: + print("Messages are enabled in guilds and private messages.") - # Checking if AT LEAST ONE in a combination is set: - expected_intents = (Intents.GUILD_MESSAGES | Intents.PRIVATE_MESSAGES) - if my_intents & expected_intents: - print("Messages are enabled in guilds or private messages.") - ``` + # Checking if AT LEAST ONE in a combination is set: + expected_intents = (Intents.GUILD_MESSAGES | Intents.PRIVATE_MESSAGES) + if my_intents & expected_intents: + print("Messages are enabled in guilds or private messages.") Removing one or more intents from a combination can be done with the bitwise-xor (`^`) operator. The `^=` operator can do this in-place. - ```py - # Remove GUILD_MESSAGES - my_intents = my_intents ^ Intents.GUILD_MESSAGES - # or, simplifying: - my_intents ^= Intents.GUILD_MESSAGES + .. code-block:: python + + # Remove GUILD_MESSAGES + my_intents = my_intents ^ Intents.GUILD_MESSAGES + # or, simplifying: + my_intents ^= Intents.GUILD_MESSAGES - # Remove all messages events. - my_intents = my_intents ^ (Intents.GUILD_MESSAGES | Intents.PRIVATE_MESSAGES) - # or, simplifying - my_intents ^= (Intents.GUILD_MESSAGES | Intents.PRIVATE_MESSAGES) - ``` + # Remove all messages events. + my_intents = my_intents ^ (Intents.GUILD_MESSAGES | Intents.PRIVATE_MESSAGES) + # or, simplifying + my_intents ^= (Intents.GUILD_MESSAGES | Intents.PRIVATE_MESSAGES) What is and is not covered by intents? -------------------------------------- @@ -173,7 +173,7 @@ class Intents(enums.Flag): """Represents no intents.""" GUILDS = 1 << 0 - """Subscribes to the following events: + """Subscribes to the events listed below. * `GUILD_CREATE` * `GUILD_UPDATE` @@ -188,7 +188,7 @@ class Intents(enums.Flag): """ GUILD_MEMBERS = 1 << 1 - """Subscribes to the following events: + """Subscribes to the events listed below. * `GUILD_MEMBER_ADD` * `GUILD_MEMBER_UPDATE` @@ -199,20 +199,20 @@ class Intents(enums.Flag): """ GUILD_BANS = 1 << 2 - """Subscribes to the following events: + """Subscribes to the events listed below. * `GUILD_BAN_ADD` * `GUILD_BAN_REMOVE` """ GUILD_EMOJIS = 1 << 3 - """Subscribes to the following events: + """Subscribes to the events listed below. * `GUILD_EMOJIS_UPDATE` """ GUILD_INTEGRATIONS = 1 << 4 - """Subscribes to the following events: + """Subscribes to the events listed below. * `INTEGRATION_CREATE` * `INTEGRATION_DELETE` @@ -220,26 +220,26 @@ class Intents(enums.Flag): """ GUILD_WEBHOOKS = 1 << 5 - """Subscribes to the following events: + """Subscribes to the events listed below. * `WEBHOOKS_UPDATE` """ GUILD_INVITES = 1 << 6 - """Subscribes to the following events: + """Subscribes to the events listed below. * `INVITE_CREATE` * `INVITE_DELETE` """ GUILD_VOICE_STATES = 1 << 7 - """Subscribes to the following events: + """Subscribes to the events listed below. * `VOICE_STATE_UPDATE` """ GUILD_PRESENCES = 1 << 8 - """Subscribes to the following events: + """Subscribes to the events listed below. * `PRESENCE_UPDATE` @@ -247,7 +247,7 @@ class Intents(enums.Flag): This intent is privileged, and requires enabling/whitelisting to use.""" GUILD_MESSAGES = 1 << 9 - """Subscribes to the following events: + """Subscribes to the events listed below. * `MESSAGE_CREATE` (in guilds only) * `MESSAGE_UPDATE` (in guilds only) @@ -256,7 +256,7 @@ class Intents(enums.Flag): """ GUILD_MESSAGE_REACTIONS = 1 << 10 - """Subscribes to the following events: + """Subscribes to the events listed below. * `MESSAGE_REACTION_ADD` (in guilds only) * `MESSAGE_REACTION_REMOVE` (in guilds only) @@ -265,13 +265,13 @@ class Intents(enums.Flag): """ GUILD_MESSAGE_TYPING = 1 << 11 - """Subscribes to the following events: + """Subscribes to the events listed below. * `TYPING_START` (in guilds only) """ DM_MESSAGES = 1 << 12 - """Subscribes to the following events: + """Subscribes to the events listed below. * `MESSAGE_CREATE` (in private message channels (non-guild bound) only) * `MESSAGE_UPDATE` (in private message channels (non-guild bound) only) @@ -279,7 +279,7 @@ class Intents(enums.Flag): """ DM_MESSAGE_REACTIONS = 1 << 13 - """Subscribes to the following events: + """Subscribes to the events listed below. * `MESSAGE_REACTION_ADD` (in private message channels (non-guild bound) only) * `MESSAGE_REACTION_REMOVE` (in private message channels (non-guild bound) only) @@ -288,7 +288,7 @@ class Intents(enums.Flag): """ DM_MESSAGE_TYPING = 1 << 14 - """Subscribes to the following events: + """Subscribes to the events listed below. * `TYPING_START` (in private message channels (non-guild bound) only) """ @@ -303,7 +303,7 @@ class Intents(enums.Flag): """ GUILD_SCHEDULED_EVENTS = 1 << 16 - """Subscribes to the following events: + """Subscribes to the events listed below. * `GUILD_SCHEDULED_EVENT_CREATE` * `GUILD_SCHEDULED_EVENT_UPDATE` diff --git a/hikari/interactions/base_interactions.py b/hikari/interactions/base_interactions.py index 412b40bdf4..1f531bba58 100644 --- a/hikari/interactions/base_interactions.py +++ b/hikari/interactions/base_interactions.py @@ -177,7 +177,7 @@ class PartialInteraction(snowflakes.Unique, webhooks.ExecutableWebhook): """The base model for all interaction models.""" app: traits.RESTAware = attr.field(repr=False, eq=False, metadata={attr_extensions.SKIP_DEEP_COPY: True}) - """The client application that models may use for procedures.""" + """Client application that models may use for procedures.""" id: snowflakes.Snowflake = attr.field(hash=True, repr=True) # <>. @@ -282,6 +282,14 @@ async def create_initial_response( If this is a `hikari.embeds.Embed` and no `embed` nor `embeds` kwarg is provided, then this will instead update the embed. This allows for simpler syntax when sending an embed alone. + 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[bool] + If provided, whether the message will be read out by a screen + reader using Discord's TTS (text-to-speech) system. 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. @@ -297,14 +305,6 @@ 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[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[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[bool] If provided, whether the message should parse @everyone/@here mentions. @@ -463,7 +463,7 @@ async def edit_initial_response( 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 + replace_attachments : bool Whether to replace the attachments with the provided ones. Defaults to `False`. diff --git a/hikari/interactions/command_interactions.py b/hikari/interactions/command_interactions.py index 69310121e0..1e8aa63b67 100644 --- a/hikari/interactions/command_interactions.py +++ b/hikari/interactions/command_interactions.py @@ -388,15 +388,15 @@ def build_response(self) -> special_endpoints.InteractionMessageBuilder: Examples -------- - ```py - async def handle_command_interaction(interaction: CommandInteraction) -> InteractionMessageBuilder: - return ( - interaction - .build_response() - .add_embed(Embed(description="Hi there")) - .set_content("Konnichiwa") - ) - ``` + .. code-block:: python + + async def handle_command_interaction(interaction: CommandInteraction) -> InteractionMessageBuilder: + return ( + interaction + .build_response() + .add_embed(Embed(description="Hi there")) + .set_content("Konnichiwa") + ) Returns ------- @@ -446,19 +446,19 @@ def build_response( Examples -------- - ```py - async def handle_autocomplete_interaction(interaction: AutocompleteInteraction) -> InteractionAutocompleteBuilder: - return ( - interaction - .build_response( - [ - CommandChoice(name="foo", value="a"), - CommandChoice(name="bar", value="b"), - CommandChoice(name="baz", value="c"), - ] + .. code-block:: python + + async def handle_autocomplete_interaction(interaction: AutocompleteInteraction) -> InteractionAutocompleteBuilder: + return ( + interaction + .build_response( + [ + CommandChoice(name="foo", value="a"), + CommandChoice(name="bar", value="b"), + CommandChoice(name="baz", value="c"), + ] + ) ) - ) - ``` Returns ------- diff --git a/hikari/interactions/component_interactions.py b/hikari/interactions/component_interactions.py index 37d32e217e..a79fc017c3 100644 --- a/hikari/interactions/component_interactions.py +++ b/hikari/interactions/component_interactions.py @@ -161,15 +161,15 @@ def build_response(self, type_: _ImmediateTypesT, /) -> special_endpoints.Intera Examples -------- - ```py - async def handle_component_interaction(interaction: ComponentInteraction) -> InteractionMessageBuilder: - return ( - interaction - .build_response(ResponseType.MESSAGE_UPDATE) - .add_embed(Embed(description="Hi there")) - .set_content("Konnichiwa") - ) - ``` + .. code-block:: python + + async def handle_component_interaction(interaction: ComponentInteraction) -> InteractionMessageBuilder: + return ( + interaction + .build_response(ResponseType.MESSAGE_UPDATE) + .add_embed(Embed(description="Hi there")) + .set_content("Konnichiwa") + ) Returns ------- diff --git a/hikari/internal/aio.py b/hikari/internal/aio.py index 7b687662be..c46cd42269 100644 --- a/hikari/internal/aio.py +++ b/hikari/internal/aio.py @@ -201,6 +201,7 @@ def get_or_make_loop() -> asyncio.AbstractEventLoop: Returns ------- asyncio.AbstractEventLoop + The requested loop. """ # get_event_loop will error under oddly specific cases such as if set_event_loop has been called before even # if it was just called with None or if it's called on a thread which isn't the main Thread. diff --git a/hikari/internal/cache.py b/hikari/internal/cache.py index 3cf2daf36e..07ff848fb8 100644 --- a/hikari/internal/cache.py +++ b/hikari/internal/cache.py @@ -313,7 +313,8 @@ def build_entity(self, app: traits.RESTAware, /) -> ValueT: Returns ------- - The initialised entity object. + ValueT + The initialised entity object. """ @classmethod @@ -328,7 +329,8 @@ def build_from_entity(cls: typing.Type[DataT], entity: ValueT, /) -> DataT: Returns ------- - The built data class. + DataT + The built data class. """ @@ -420,7 +422,7 @@ class MemberData(BaseData[guilds.Member]): is_pending: undefined.UndefinedOr[bool] = attr.field() raw_communication_disabled_until: typing.Optional[datetime.datetime] = attr.field() # meta-attribute - has_been_deleted: bool = attr.field(default=False) + has_been_deleted: bool = attr.field(default=False, init=False) @classmethod def build_from_entity( @@ -1033,7 +1035,7 @@ def unwrap_ref_cell(cell: RefCell[ValueT]) -> ValueT: Parameters ---------- cell : RefCell[ValueT] - The reference cell instance to unwrap + The reference cell instance to unwrap. Returns ------- diff --git a/hikari/internal/collections.py b/hikari/internal/collections.py index 28784906ab..0245af8164 100644 --- a/hikari/internal/collections.py +++ b/hikari/internal/collections.py @@ -142,18 +142,15 @@ class LimitedCapacityCacheMap(ExtendedMutableMapping[KeyT, ValueT]): Parameters ---------- + source : typing.Optional[typing.Dict[KeyT, ValueT]] + A source dictionary of keys to values to create this from. limit : int The limit for how many objects should be stored by this mapping before it starts removing the oldest entries. - - Other Parameters - ---------------- - source : typing.Optional[typing.Dict[KeyT, ValueT]] - A source dictionary of keys to 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 `None. + in this mapping as the value and should return `None`. This will always be called after the entry has been removed. """ diff --git a/hikari/internal/data_binding.py b/hikari/internal/data_binding.py index fced8839a2..216f5e7fc5 100644 --- a/hikari/internal/data_binding.py +++ b/hikari/internal/data_binding.py @@ -81,7 +81,7 @@ if typing.TYPE_CHECKING: JSONDecodeError: typing.Type[Exception] = Exception - """Exception raised when loading an invalid JSON string""" + """Exception raised when loading an invalid JSON string.""" def dump_json(_: typing.Union[JSONArray, JSONObject], /, *, indent: int = ...) -> str: """Convert a Python type to a JSON string.""" @@ -101,7 +101,7 @@ def load_json(_: typing.AnyStr, /) -> typing.Union[JSONArray, JSONObject]: """Convert a JSON string to a Python type.""" JSONDecodeError = json.JSONDecodeError - """Exception raised when loading an invalid JSON string""" + """Exception raised when loading an invalid JSON string.""" @typing.final @@ -140,7 +140,7 @@ class StringMapBuilder(multidict.MultiDict[str]): """Helper class used to quickly build query strings or header maps. This will consume any items that are not `hikari.undefined.UNDEFINED`. - If a value _is_ unspecified, it will be ignored when inserting it. This reduces + If a value is unspecified, it will be ignored when inserting it. This reduces the amount of boilerplate needed for generating the headers and query strings for low-level HTTP API interaction, amongst other things. diff --git a/hikari/internal/deprecation.py b/hikari/internal/deprecation.py index 413124f639..78efa5dc13 100644 --- a/hikari/internal/deprecation.py +++ b/hikari/internal/deprecation.py @@ -24,95 +24,41 @@ from __future__ import annotations -__all__: typing.Sequence[str] = ("deprecated", "warn_deprecated") +__all__: typing.Sequence[str] = ("warn_deprecated", "HikariDeprecationWarning") -import functools -import inspect import typing import warnings from hikari import _about as hikari_about from hikari.internal import ux -if typing.TYPE_CHECKING: - T = typing.TypeVar("T", bound=typing.Callable[..., typing.Any]) +class HikariDeprecationWarning(DeprecationWarning): + """Warning about a hikari deprecation.""" -def warn_deprecated(obj: typing.Any, additional_information: str, /, *, stack_level: int = 3) -> None: - """Raise a deprecated warning. + +def warn_deprecated(name: str, /, *, removal_version: str, additional_info: str, stack_level: int = 3) -> None: + """Issue a deprecation warning. Parameters ---------- - obj: typing.Any - The object that is deprecated. - - Possible values are: - - A class - - A function/method - - An argument - additional_information: str - Additional information on the deprecation for the user. + name : str + What is being deprecated. Other Parameters ---------------- - stack_level: int - The stack level for the warning. Defaults to `3`. + removal_version : str + The version it will be removed in. + additional_info : str + Additional information on the deprecation for the user. + stack_level : int + The stack level to issue the warning in. """ - if inspect.isclass(obj): - action = ("Instantiation of", "class") - obj = f"{obj.__module__}.{obj.__qualname__}" - elif inspect.isfunction(obj): - action = ("Call to", "function/method") - obj = f"{obj.__module__}.{obj.__qualname__}" - else: - action = ("Use of", "argument") + if ux.HikariVersion(hikari_about.__version__) >= ux.HikariVersion(removal_version): + raise HikariDeprecationWarning(f"{name!r} is passed its removal version ({removal_version})") warnings.warn( - f"{action[0]} deprecated {action[1]} {obj!r} ({additional_information})", - category=DeprecationWarning, + f"{name!r} is deprecated and will be removed in `{removal_version}`. {additional_info}", + category=HikariDeprecationWarning, stacklevel=stack_level, ) - - -def deprecated(deprecated_version: str, removal_version: str, additional_information: str) -> typing.Callable[[T], T]: - """Mark a function or object as being deprecated. - - Parameters - ---------- - deprecated_version: str - The version this function or object is deprecated in. - removal_version: str - The version this function or object will be removed in. - additional_information: str - Additional information on the deprecation for the user. - """ - - def decorator(obj: T) -> T: - # No, this will not cause issues. - # Thanks to CI and our deploy pipeline, we will be able to catch these and remove them before deployment - if ux.HikariVersion(hikari_about.__version__) >= ux.HikariVersion(removal_version): - raise DeprecationWarning( - f"'{obj.__module__}.{obj.__qualname__}' is passed its removal version ({removal_version})" - ) - - 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:: {deprecated_version}\n" - + f" Will be removed in *{removal_version}*.\n" - + f" {additional_information}\n" - + old_doc[first_line_end:] - ) - - @functools.wraps(obj) - def wrapper(*args: typing.Any, **kwargs: typing.Any) -> typing.Any: - warn_deprecated(obj, additional_information) - return obj(*args, **kwargs) - - return typing.cast("T", wrapper) - - return decorator diff --git a/hikari/internal/enums.py b/hikari/internal/enums.py index e29659e386..4af0454a67 100644 --- a/hikari/internal/enums.py +++ b/hikari/internal/enums.py @@ -225,14 +225,14 @@ class Enum(metaclass=_EnumMeta): * ` __objtype__` : Always the first type that the enum is derived from. For example: - ```py - >>> class UserType(str, Enum): - ... USER = "user" - ... PARTIAL = "partial" - ... MEMBER = "member" - >>> print(UserType.__objtype__) - - ``` + .. code-block:: python + + >>> class UserType(str, Enum): + ... USER = "user" + ... PARTIAL = "partial" + ... MEMBER = "member" + >>> print(UserType.__objtype__) + Operators on the class ---------------------- @@ -460,7 +460,7 @@ class Flag(metaclass=_FlagMeta): 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 + into a single integral value, and works by setting the individual `1` and `0` on the binary representation of the integer. This implementation has extra features, in that it will actively behave diff --git a/hikari/internal/routes.py b/hikari/internal/routes.py index b6ce148d04..24a19951ce 100644 --- a/hikari/internal/routes.py +++ b/hikari/internal/routes.py @@ -124,7 +124,7 @@ class Route: Parameters ---------- method : str - The HTTP method + The HTTP method. path_template : str The template string for the path to use. """ diff --git a/hikari/internal/time.py b/hikari/internal/time.py index a2bbb464c0..84f0a9cb3e 100644 --- a/hikari/internal/time.py +++ b/hikari/internal/time.py @@ -151,7 +151,7 @@ def unix_epoch_to_datetime(epoch: typing.Union[int, float], /, *, is_millis: boo Number of seconds/milliseconds since `1/1/1970 00:00:00 UTC`. is_millis : bool `True` by default, indicates the input timestamp is measured in - milliseconds rather than seconds + milliseconds rather than seconds. Returns ------- diff --git a/hikari/internal/ux.py b/hikari/internal/ux.py index 2fd5d22469..284ee3833d 100644 --- a/hikari/internal/ux.py +++ b/hikari/internal/ux.py @@ -158,7 +158,7 @@ def print_banner( Inspired by Spring Boot, we display an ASCII logo on startup. This is styled to grab the user's attention, and contains info such as the library version, the Python interpreter, the OS, and links to our Discord server and - documentation. Users can override this by placing a `banner.txt' in some + documentation. Users can override this by placing a `banner.txt` in some package and referencing it in this call. .. note:: @@ -317,9 +317,6 @@ def __str__(self) -> str: def __repr__(self) -> str: return f"HikariVersion('{str(self)}')" - def __hash__(self) -> int: - return id(self) - def __eq__(self, other: typing.Any) -> bool: return self._compare(other, lambda s, o: s == o) diff --git a/hikari/invites.py b/hikari/invites.py index ac507c50ae..bee5c697ca 100644 --- a/hikari/invites.py +++ b/hikari/invites.py @@ -88,7 +88,7 @@ class VanityURL(InviteCode): app: traits.RESTAware = attr.field( repr=False, eq=False, hash=False, metadata={attr_extensions.SKIP_DEEP_COPY: True} ) - """The client application that models may use for procedures.""" + """Client application that models may use for procedures.""" code: str = attr.field(hash=True, repr=True) """The code for this invite.""" @@ -233,7 +233,7 @@ class Invite(InviteCode): app: traits.RESTAware = attr.field( repr=False, eq=False, hash=False, metadata={attr_extensions.SKIP_DEEP_COPY: True} ) - """The client application that models may use for procedures.""" + """Client application that models may use for procedures.""" code: str = attr.field(hash=True, repr=True) """The code for this invite.""" diff --git a/hikari/iterators.py b/hikari/iterators.py index 22a2aaf4d3..2137c19b79 100644 --- a/hikari/iterators.py +++ b/hikari/iterators.py @@ -59,17 +59,19 @@ class All(typing.Generic[ValueT]): For example... - ```py - if w(foo) and x(foo) and y(foo) and z(foo): - ... - ``` + .. code-block:: python + + if w(foo) and x(foo) and y(foo) and z(foo): + ... + is equivalent to - ```py - condition = All([w, x, y, z]) - if condition(foo): - ... - ``` + .. code-block:: python + + condition = All([w, x, y, z]) + + if condition(foo): + ... This behaves like a lazy wrapper implementation of the `all` builtin. @@ -94,7 +96,7 @@ class All(typing.Generic[ValueT]): Parameters ---------- - *conditions : typing.Callable[[ValueT], bool] + conditions : typing.Callable[[ValueT], bool] The predicates to wrap. """ @@ -170,29 +172,29 @@ class LazyIterator(typing.Generic[ValueT], abc.ABC): As an async iterable: - ```py - >>> async for item in paginated_results: - ... process(item) - ``` + .. code-block:: python + + >>> async for item in paginated_results: + ... process(item) As an eagerly retrieved set of results (performs all API calls at once, which may be slow for large sets of data): - ```py - >>> results = await paginated_results - >>> # ... which is equivalent to this... - >>> results = [item async for item in paginated_results] - ``` + .. code-block:: python + + >>> results = await paginated_results + >>> # ... which is equivalent to this... + >>> results = [item async for item in paginated_results] As an async iterator (not recommended): - ```py - >>> try: - ... while True: - ... process(await paginated_results.__anext__()) - ... except StopAsyncIteration: - ... pass - ``` + .. code-block:: python + + >>> try: + ... while True: + ... process(await paginated_results.__anext__()) + ... except StopAsyncIteration: + ... pass Additionally, you can make use of some of the provided helper methods on this class to perform basic operations easily. @@ -200,20 +202,20 @@ class LazyIterator(typing.Generic[ValueT], abc.ABC): Iterating across the items with indexes (like `enumerate` for normal iterables): - ```py - >>> async for i, item in paginated_results.enumerate(): - ... print(i, item) - (0, foo) - (1, bar) - (2, baz) - ``` + .. code-block:: python + + >>> async for i, item in paginated_results.enumerate(): + ... print(i, item) + (0, foo) + (1, bar) + (2, baz) Limiting the number of results you iterate across: - ```py - >>> async for item in paginated_results.limit(3): - ... process(item) - ``` + .. code-block:: python + + >>> async for item in paginated_results.limit(3): + ... process(item) """ __slots__: typing.Sequence[str] = () @@ -257,7 +259,7 @@ def map( return _MappingLazyIterator(self, transformation) async def for_each(self, consumer: typing.Callable[[ValueT], typing.Any]) -> None: - """Pass each value to a given consumer immediately.""" + """Forward each value to a given consumer immediately.""" if asyncio.iscoroutinefunction(consumer): async for item in self: await consumer(item) @@ -440,29 +442,29 @@ def enumerate(self, *, start: int = 0) -> LazyIterator[typing.Tuple[int, ValueT] Examples -------- - ```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) - ``` + .. code-block:: python + + >>> 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 ------- @@ -482,10 +484,10 @@ def limit(self, limit: int) -> LazyIterator[ValueT]: Examples -------- - ```py - >>> async for item in paginated_results.limit(3): - ... print(item) - ``` + .. code-block:: python + + >>> async for item in paginated_results.limit(3): + ... print(item) Returns ------- @@ -612,26 +614,25 @@ def flat_map(self, flattener: _FlattenerT[ValueT, AnotherValueT]) -> LazyIterato A function that returns either an async iterator or iterator of new values. Could be an attribute name instead. - Example - ------- - + Examples + -------- The following example generates a distinct collection of all mentioned users in the given channel from the past 500 messages. - ```py - def iter_mentioned_users(message: hikari.Message) -> typing.Iterable[Snowflake]: - for match in re.findall(r"<@!?(\d+)>", message.content): - yield Snowflake(match) - - mentioned_users = await ( - channel - .history() - .limit(500) - .map(".content") - .flat_map(iter_mentioned_users) - .distinct() - ) - ``` + .. code-block:: python + + def iter_mentioned_users(message: hikari.Message) -> typing.Iterable[Snowflake]: + for match in re.findall(r"<@!?(\d+)>", message.content): + yield Snowflake(match) + + mentioned_users = await ( + channel + .history() + .limit(500) + .map(".content") + .flat_map(iter_mentioned_users) + .distinct() + ) Returns ------- @@ -745,27 +746,27 @@ class BufferedLazyIterator(typing.Generic[ValueT], LazyIterator[ValueT], abc.ABC An example would look like the following: - ```py - async def some_http_call(i): - ... + .. code-block:: python + + async def some_http_call(i): + ... - class SomeEndpointLazyIterator(BufferedLazyIterator[SomeObject]): - def __init__(self): - super().__init__() - self._i = 0 + class SomeEndpointLazyIterator(BufferedLazyIterator[SomeObject]): + def __init__(self): + super().__init__() + self._i = 0 - def _next_chunk(self) -> typing.Optional[typing.Generator[ValueT, None, None]]: - raw_items = await some_http_call(self._i) - self._i += 1 + def _next_chunk(self) -> typing.Optional[typing.Generator[ValueT, None, None]]: + raw_items = await some_http_call(self._i) + self._i += 1 - if not raw_items: - return None + if not raw_items: + return None - generator = (SomeObject(raw_item) for raw_item in raw_items) - return generator - ``` + generator = (SomeObject(raw_item) for raw_item in raw_items) + return generator """ __slots__: typing.Sequence[str] = ("_buffer",) diff --git a/hikari/locales.py b/hikari/locales.py index a02a5b10e1..512a388ff6 100644 --- a/hikari/locales.py +++ b/hikari/locales.py @@ -35,91 +35,91 @@ class Locale(str, enums.Enum): """Possible user/guild locales.""" DA = "da" - """Danish""" + """Danish.""" DE = "de" - """German""" + """German.""" EN_GB = "en-GB" - """English, UK""" + """English, UK.""" EN_US = "en-US" - """English, US""" + """English, US.""" ES_ES = "es-ES" - """Spanish""" + """Spanish.""" FR = "fr" - """French""" + """French.""" HR = "hr" - """Croatian""" + """Croatian.""" IT = "it" - """Italian""" + """Italian.""" LT = "lt" - """Lithuanian""" + """Lithuanian.""" HU = "hu" - """Hungarian""" + """Hungarian.""" NL = "nl" - """Dutch""" + """Dutch.""" NO = "no" - """Norwegian""" + """Norwegian.""" PL = "pl" - """Polish""" + """Polish.""" PT_BR = "pt-BR" - """Portuguese, Bralizian""" + """Portuguese, Bralizian.""" RO = "ro" - """Romian""" + """Romian.""" FI = "fi" - """Finnish""" + """Finnish.""" SV_SE = "sv-SE" - """Swedish""" + """Swedish.""" VI = "vi" - """Vietnamese""" + """Vietnamese.""" TR = "tr" - """Turkish""" + """Turkish.""" CS = "cs" - """Czech""" + """Czech.""" EL = "el" - """Greek""" + """Greek.""" BG = "bg" - """Bulgarian""" + """Bulgarian.""" RU = "ru" - """Russian""" + """Russian.""" UK = "uk" - """Ukrainian""" + """Ukrainian.""" HI = "hi" - """Hindi""" + """Hindi.""" TH = "th" - """Thai""" + """Thai.""" ZH_CN = "zh-CN" - """Chinese, China""" + """Chinese, China.""" JA = "ja" - """Japanese""" + """Japanese.""" ZH_TW = "zh-TW" - """Chinese, Taiwan""" + """Chinese, Taiwan.""" KO = "ko" - """Korean""" + """Korean.""" diff --git a/hikari/messages.py b/hikari/messages.py index 9598acc851..19875bd372 100644 --- a/hikari/messages.py +++ b/hikari/messages.py @@ -149,7 +149,7 @@ class MessageFlag(enums.Flag): """Additional flags for message options.""" NONE = 0 - """None""" + """None.""" CROSSPOSTED = 1 << 0 """This message has been published to subscribed channels via channel following.""" @@ -284,39 +284,31 @@ def channels(self) -> undefined.UndefinedOr[typing.Mapping[snowflakes.Snowflake, If the message is not crossposted, this will always be empty. """ - deprecation.warn_deprecated("Mentions.channels", alternative="channel_mentions in the base message object") return self._message.channel_mentions @property def channels_ids(self) -> undefined.UndefinedOr[typing.Sequence[snowflakes.Snowflake]]: """Sequence of IDs of the channels that were mentioned in the message.""" - deprecation.warn_deprecated( - "Mentions.channels_ids", alternative="channel_mention_ids in the base message object" - ) return self._message.channel_mention_ids @property def users(self) -> undefined.UndefinedOr[typing.Mapping[snowflakes.Snowflake, users_.User]]: """Users who were notified by their mention in the message.""" - deprecation.warn_deprecated("Mentions.users", alternative="user_mentions in the base message object") return self._message.user_mentions @property def user_ids(self) -> undefined.UndefinedOr[typing.Sequence[snowflakes.Snowflake]]: """Sequence of IDs of the users that were mentioned in the message.""" - deprecation.warn_deprecated("Mentions.user_ids", alternative="user_mentions_ids in the base message object") return self._message.user_mentions_ids @property def role_ids(self) -> undefined.UndefinedOr[typing.Sequence[snowflakes.Snowflake]]: """Sequence of IDs of roles that were notified by their mention in the message.""" - deprecation.warn_deprecated("Mentions.role_ids", alternative="role_mention_ids in the base message object") return self._message.role_mention_ids @property def everyone(self) -> undefined.UndefinedOr[bool]: """Whether the message notifies using `@everyone` or `@here`.""" - deprecation.warn_deprecated("Mentions.everyone", alternative="mentions_everyone in the base message object") return self._message.mentions_everyone def get_members(self) -> undefined.UndefinedOr[typing.Mapping[snowflakes.Snowflake, guilds.Member]]: @@ -340,9 +332,6 @@ def get_members(self) -> undefined.UndefinedOr[typing.Mapping[snowflakes.Snowfla means that there is a very small chance that some users provided in `notified_users` may not be present here. """ - deprecation.warn_deprecated( - "Mentions.get_members", alternative="get_member_mentions in the base message object" - ) return self._message.get_member_mentions() def get_roles(self) -> undefined.UndefinedOr[typing.Mapping[snowflakes.Snowflake, guilds.Role]]: @@ -367,7 +356,6 @@ def get_roles(self) -> undefined.UndefinedOr[typing.Mapping[snowflakes.Snowflake in `notifies_role_ids` may not be present here. This is a limitation of Discord, again. """ - deprecation.warn_deprecated("Mentions.get_roles", alternative="get_role_mentions in the base message object") return self._message.get_role_mentions() @@ -383,7 +371,7 @@ class MessageReference: app: traits.RESTAware = attr.field( repr=False, eq=False, hash=False, metadata={attr_extensions.SKIP_DEEP_COPY: True} ) - """The client application that models may use for procedures.""" + """Client application that models may use for procedures.""" id: typing.Optional[snowflakes.Snowflake] = attr.field(repr=True) """The ID of the original message. @@ -553,7 +541,7 @@ class ButtonStyle(int, enums.Enum): InteractiveButtonTypes: typing.AbstractSet[InteractiveButtonTypesT] = frozenset( [ButtonStyle.PRIMARY, ButtonStyle.SECONDARY, ButtonStyle.SUCCESS, ButtonStyle.DANGER] ) -"""Set of the `ButtonType`s which are valid for interactive buttons. +"""Set of `ButtonType` which are valid for interactive buttons. The following values are included in this: @@ -728,7 +716,7 @@ class PartialMessage(snowflakes.Unique): app: traits.RESTAware = attr.field( repr=False, eq=False, hash=False, metadata={attr_extensions.SKIP_DEEP_COPY: True} ) - """The client application that models may use for procedures.""" + """Client application that models may use for procedures.""" id: snowflakes.Snowflake = attr.field(hash=True, repr=True) """The ID of this entity.""" @@ -780,15 +768,33 @@ class PartialMessage(snowflakes.Unique): is_tts: undefined.UndefinedOr[bool] = attr.field(hash=False, eq=False, repr=False) """Whether the message is a TTS message.""" - mentions: Mentions = attr.field(hash=False, eq=False, repr=True) - """Description of who is mentioned in a message. + _mentions: Mentions = attr.field(hash=False, eq=False, repr=True) - .. warning:: - If the contents have not mutated and this is a message update event, - some fields that are not affected may be empty instead. + @property + def mentions(self) -> Mentions: + """Information of who is mentioned in a message. - This is a Discord limitation. - """ + .. deprecated:: 2.0.0.dev109 + Will be removed in `2.0.0.dev112`. + + Use the methods in the base object instead. + + .. warning:: + If the contents have not mutated and this is a message update event, + some fields that are not affected may be empty instead. + + This is a Discord limitation. + """ + deprecation.warn_deprecated( + "mentions", + removal_version="2.0.0.dev112", + additional_info="Use the methods in the base object instead", + ) + return self._mentions + + @mentions.setter + def mentions(self, value: Mentions) -> None: + self._mentions = value user_mentions: undefined.UndefinedOr[typing.Mapping[snowflakes.Snowflake, users_.User]] = attr.field( hash=False, eq=False, repr=False @@ -1157,7 +1163,7 @@ async def edit( 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 + replace_attachments : bool Whether to replace the attachments with the provided ones. Defaults to `False`. @@ -1443,7 +1449,7 @@ async def add_reaction( Parameters ---------- - emoji: typing.Union[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` @@ -1461,19 +1467,19 @@ async def add_reaction( Examples -------- - ```py - # Using a unicode emoji. - await message.add_reaction("👌") + .. code-block:: python - # Using a unicode emoji name. - await message.add_reaction("\N{OK HAND SIGN}") + # Using a unicode emoji. + await message.add_reaction("👌") - # Using the name and id. - await message.add_reaction("rooAYAYA", 705837374319493284) + # Using a unicode emoji name. + await message.add_reaction("\N{OK HAND SIGN}") - # Using an Emoji-derived object. - await message.add_reaction(some_emoji_object) - ``` + # Using the name and id. + await message.add_reaction("rooAYAYA", 705837374319493284) + + # Using an Emoji-derived object. + await message.add_reaction(some_emoji_object) Raises ------ @@ -1538,26 +1544,26 @@ async def remove_reaction( Examples -------- - ```py - # Using a unicode emoji and removing the bot's reaction from this - # reaction. - await message.remove_reaction("\N{OK HAND SIGN}") + .. code-block:: python + + # 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 ------ @@ -1622,17 +1628,17 @@ async def remove_all_reactions( Examples -------- - ```py - # Using a unicode emoji and removing all 👌 reacts from the message. - # reaction. - await message.remove_all_reactions("\N{OK HAND SIGN}") + .. code-block:: python + + # 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 ------ diff --git a/hikari/permissions.py b/hikari/permissions.py index d2185a5f1a..66e5e4d647 100644 --- a/hikari/permissions.py +++ b/hikari/permissions.py @@ -45,6 +45,8 @@ class Permissions(enums.Flag): -------- You can create an enum which combines multiple permissions using the bitwise OR operator (`|`): + .. code-block:: python + my_perms = Permissions.MANAGE_CHANNELS | Permissions.MANAGE_GUILD required_perms = ( @@ -59,6 +61,8 @@ class Permissions(enums.Flag): permissions from one set are present in another set. This is useful, for instance, for checking if a user has all the required permissions + .. code-block:: python + if (my_perms & required_perms) == required_perms: print("I have all of the required permissions!") else: @@ -68,6 +72,8 @@ class Permissions(enums.Flag): bitwise equivalent of the set difference operation, as shown below. This can be used, for instance, to find which of a user's permissions are missing from the required permissions. + .. code-block:: python + missing_perms = ~my_perms & required_perms if (missing_perms): print(f"I'm missing these permissions: {missing_perms}") @@ -75,6 +81,8 @@ class Permissions(enums.Flag): Lastly, if you need all the permissions from a set except for a few, you can use the bitwise NOT operator (`~`). + .. code-block:: python + # All permissions except ADMINISTRATOR. my_perms = ~Permissions.ADMINISTRATOR @@ -171,11 +179,7 @@ class Permissions(enums.Flag): """Allows for reading of message history.""" MENTION_ROLES = 1 << 17 - """Allows for using the `@everyone` tag to notify all users in a channel, - and the `@here` tag to notify all online users in a channel, and the - `@role` tag (even if the role is not mentionable) to notify all users with - that role in a channel. - """ + """Allows for using the `@everyone`, `@here` and `@role` (regardless of its mention status) tag to notify users.""" USE_EXTERNAL_EMOJIS = 1 << 18 """Allows the usage of custom emojis from other guilds.""" diff --git a/hikari/presences.py b/hikari/presences.py index 8362554c78..7a46eb3a54 100644 --- a/hikari/presences.py +++ b/hikari/presences.py @@ -63,7 +63,7 @@ class ActivityType(int, enums.Enum): """The activity type.""" PLAYING = 0 - """Shows up as `Playing `""" + """Shows up as `Playing `.""" STREAMING = 1 """Shows up as `Streaming` and links to a Twitch or YouTube stream/video. @@ -274,22 +274,22 @@ class ActivityFlag(enums.Flag): """ INSTANCE = 1 << 0 - """Instance""" + """Instance.""" JOIN = 1 << 1 - """Join""" + """Join.""" SPECTATE = 1 << 2 - """Spectate""" + """Spectate.""" JOIN_REQUEST = 1 << 3 - """Join Request""" + """Join Request.""" SYNC = 1 << 4 - """Sync""" + """Sync.""" PLAY = 1 << 5 - """Play""" + """Play.""" PARTY_PRIVACY_FRIENDS = 1 << 6 """Party privacy: friends only.""" @@ -328,9 +328,7 @@ class RichActivity(Activity): """When this activity was added to the user's session.""" timestamps: typing.Optional[ActivityTimestamps] = attr.field(repr=False) - """The timestamps for when this activity's current state will start and - end, if applicable. - """ + """The timestamps for when this activity's current state will start and end, if applicable.""" application_id: typing.Optional[snowflakes.Snowflake] = attr.field(repr=False) """The ID of the application this activity is for, if applicable.""" @@ -403,7 +401,7 @@ class MemberPresence: app: traits.RESTAware = attr.field( repr=False, eq=False, hash=False, metadata={attr_extensions.SKIP_DEEP_COPY: True} ) - """The client application that models may use for procedures.""" + """Client application that models may use for procedures.""" user_id: snowflakes.Snowflake = attr.field(repr=True, hash=True) """The ID of the user this presence belongs to.""" diff --git a/hikari/scheduled_events.py b/hikari/scheduled_events.py index 23a95b8d26..0c00f36908 100644 --- a/hikari/scheduled_events.py +++ b/hikari/scheduled_events.py @@ -103,7 +103,7 @@ class ScheduledEvent(snowflakes.Unique): app: traits.RESTAware = attr.field( repr=False, eq=False, hash=False, metadata={attr_extensions.SKIP_DEEP_COPY: True} ) - """The client application that models may use for procedures.""" + """Client application that models may use for procedures.""" id: snowflakes.Snowflake = attr.field(hash=True, repr=True) """ID of the scheduled event.""" diff --git a/hikari/stickers.py b/hikari/stickers.py index 92f8ef9543..7d743b8e4a 100644 --- a/hikari/stickers.py +++ b/hikari/stickers.py @@ -90,7 +90,7 @@ class StickerPack(snowflakes.Unique): """The description of the pack.""" cover_sticker_id: typing.Optional[snowflakes.Snowflake] = attr.field(eq=False, hash=False, repr=False) - """The ID of a sticker in the pack which is shown as the pack's icon""" + """The ID of a sticker in the pack which is shown as the pack's icon.""" stickers: typing.Sequence[StandardSticker] = attr.field(eq=False, hash=False, repr=False) """The stickers that belong to this pack.""" @@ -193,7 +193,7 @@ class GuildSticker(PartialSticker): """The description of this sticker.""" guild_id: snowflakes.Snowflake = attr.field(eq=False, hash=False) - """The guild this sticker belongs to""" + """The guild this sticker belongs to.""" is_available: bool = attr.field(eq=False, hash=False) """Whether the sticker can be used.""" diff --git a/hikari/templates.py b/hikari/templates.py index 07ffec99c1..cdbdcf6861 100644 --- a/hikari/templates.py +++ b/hikari/templates.py @@ -49,7 +49,7 @@ class TemplateRole(guilds.PartialRole): """The partial role object attached to `Template`.""" permissions: permissions_.Permissions = attr.field(eq=False, hash=False, repr=False) - """The guild wide permissions this role gives to the members it's attached to, + """The guild wide permissions this role gives to the members it's attached to. This may be overridden by channel overwrites. """ diff --git a/hikari/traits.py b/hikari/traits.py index d703e30680..571a359156 100644 --- a/hikari/traits.py +++ b/hikari/traits.py @@ -265,8 +265,8 @@ def get_me(self) -> typing.Optional[users_.OwnUser]: async def update_presence( self, *, - status: undefined.UndefinedOr[presences.Status] = undefined.UNDEFINED, idle_since: undefined.UndefinedNoneOr[datetime.datetime] = undefined.UNDEFINED, + status: undefined.UndefinedOr[presences.Status] = undefined.UNDEFINED, activity: undefined.UndefinedNoneOr[presences.Activity] = undefined.UNDEFINED, afk: undefined.UndefinedOr[bool] = undefined.UNDEFINED, ) -> None: @@ -356,20 +356,20 @@ async def request_guild_members( Parameters ---------- - guild: hikari.guilds.Guild + guild : hikari.guilds.Guild The guild to request chunk for. Other Parameters ---------------- - include_presences: hikari.undefined.UndefinedOr[bool] + include_presences : hikari.undefined.UndefinedOr[bool] If provided, whether to request presences. - query: str + query : str If not `""`, request the members which username starts with the string. - limit: int + limit : int Maximum number of members to send matching the query. - users: hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishSequence[hikari.users.User]] + users : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishSequence[hikari.users.User]] If provided, the users to request for. - nonce: hikari.undefined.UndefinedOr[str] + nonce : hikari.undefined.UndefinedOr[str] If provided, the nonce to be sent with guild chunks. Raises @@ -527,6 +527,9 @@ def run( 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`. + status : hikari.presences.Status + The initial status to show for the user presence on startup. + Defaults to `hikari.presences.Status.ONLINE`. 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 @@ -536,9 +539,6 @@ def run( The number of shards to use in the entire distributed application. 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. - Defaults to `hikari.presences.Status.ONLINE`. """ raise NotImplementedError diff --git a/hikari/users.py b/hikari/users.py index c66412a996..1d8763576c 100644 --- a/hikari/users.py +++ b/hikari/users.py @@ -55,7 +55,7 @@ class UserFlag(enums.Flag): """The known user flags that represent account badges.""" NONE = 0 - """None""" + """None.""" DISCORD_EMPLOYEE = 1 << 0 """Discord Employee.""" @@ -180,7 +180,7 @@ def is_bot(self) -> undefined.UndefinedOr[bool]: @property @abc.abstractmethod def is_system(self) -> undefined.UndefinedOr[bool]: - """`Whether this user is a system account.""" + """Whether this user is a system account.""" @property @abc.abstractmethod @@ -192,12 +192,12 @@ def flags(self) -> undefined.UndefinedOr[UserFlag]: def mention(self) -> str: """Return a raw mention string for the given user. - Example - ------- - ```py - >>> some_user.mention - '<@123456789123456789>' - ``` + Examples + -------- + .. code-block:: python + + >>> some_user.mention + '<@123456789123456789>' """ async def fetch_dm_channel(self) -> channels.DMChannel: @@ -297,7 +297,6 @@ async def send( content is instead treated as an attachment if no `attachment` and no `attachments` kwargs are provided. - Other Parameters ---------------- attachment : hikari.undefined.UndefinedOr[hikari.files.Resourceish], @@ -528,12 +527,12 @@ def is_system(self) -> bool: def mention(self) -> str: """Return a raw mention string for the given user. - Example - ------- - ```py - >>> some_user.mention - '<@123456789123456789>' - ``` + Examples + -------- + .. code-block:: python + + >>> some_user.mention + '<@123456789123456789>' """ @property @@ -651,7 +650,7 @@ class PartialUserImpl(PartialUser): app: traits.RESTAware = attr.field( repr=False, eq=False, hash=False, metadata={attr_extensions.SKIP_DEEP_COPY: True} ) - """The client application that models may use for procedures.""" + """Client application that models may use for procedures.""" discriminator: undefined.UndefinedOr[str] = attr.field(eq=False, hash=False, repr=True) """Four-digit discriminator for the user.""" @@ -684,13 +683,12 @@ class PartialUserImpl(PartialUser): def mention(self) -> str: """Return a raw mention string for the given user. - Example - ------- + Examples + -------- + .. code-block:: python - ```py - >>> some_user.mention - '<@123456789123456789>' - ``` + >>> some_user.mention + '<@123456789123456789>' """ return f"<@{self.id}>" diff --git a/hikari/voices.py b/hikari/voices.py index adb2f44300..bf36dee9ac 100644 --- a/hikari/voices.py +++ b/hikari/voices.py @@ -48,7 +48,7 @@ class VoiceState: app: traits.RESTAware = attr.field( repr=False, eq=False, hash=False, metadata={attr_extensions.SKIP_DEEP_COPY: True} ) - """The client application that models may use for procedures.""" + """Client application that models may use for procedures.""" channel_id: typing.Optional[snowflakes.Snowflake] = attr.field(eq=False, hash=False, repr=True) """The ID of the channel this user is connected to. diff --git a/hikari/webhooks.py b/hikari/webhooks.py index 46a63a63f4..24e0c8f863 100644 --- a/hikari/webhooks.py +++ b/hikari/webhooks.py @@ -80,7 +80,7 @@ class ExecutableWebhook(abc.ABC): @property @abc.abstractmethod def app(self) -> traits.RESTAware: - """The client application that models may use for procedures.""" + """Client application that models may use for procedures.""" @property @abc.abstractmethod @@ -210,7 +210,7 @@ async def execute( If you pass a token that's invalid for the target webhook. ValueError If either `ExecutableWebhook.token` is `None` or more than 100 unique - objects/entities are passed for `role_mentions` or `user_mentions or + objects/entities are passed for `role_mentions` or `user_mentions` or if `token` is not available. TypeError If both `attachment` and `attachments`, `component` and `components` @@ -373,7 +373,7 @@ async def edit_message( 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 + replace_attachments : bool Whether to replace the attachments with the provided ones. Defaults to `False`. @@ -503,7 +503,7 @@ class PartialWebhook(snowflakes.Unique): app: traits.RESTAware = attr.field( repr=False, eq=False, hash=False, metadata={attr_extensions.SKIP_DEEP_COPY: True} ) - """The client application that models may use for procedures.""" + """Client application that models may use for procedures.""" id: snowflakes.Snowflake = attr.field(hash=True, repr=True) """The ID of this entity.""" @@ -532,12 +532,12 @@ def mention(self) -> str: 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>' - ``` + Examples + -------- + .. code-block:: python + + >>> some_webhook.mention + '<@123456789123456789>' """ return f"<@{self.id}>" @@ -614,7 +614,7 @@ class IncomingWebhook(PartialWebhook, ExecutableWebhook): """The guild ID of the webhook.""" author: typing.Optional[users_.User] = attr.field(eq=False, hash=False, repr=True) - """The user that created the webhook + """The user that created the webhook. .. note:: This will be `None` when fetched with the webhook's token @@ -852,7 +852,7 @@ class ChannelFollowerWebhook(PartialWebhook): """The guild ID of the webhook.""" author: typing.Optional[users_.User] = attr.field(eq=False, hash=False, repr=True) - """The user that created the webhook + """The user that created the webhook. .. note:: This will be `None` when received within an audit log. diff --git a/pipelines/config.py b/pipelines/config.py index b0c4baa14d..2c0155ce1b 100644 --- a/pipelines/config.py +++ b/pipelines/config.py @@ -28,10 +28,7 @@ # Generating documentation and artifacts. ARTIFACT_DIRECTORY = "public" -PAGES_DIRECTORY = "pages" DOCUMENTATION_DIRECTORY = "docs" -ROOT_INDEX_SOURCE = "index.html" -LOGO_SOURCE = "logo.png" # Linting and test configs. FLAKE8_REPORT = _os.path.join(ARTIFACT_DIRECTORY, "flake8") diff --git a/pipelines/nox.py b/pipelines/nox.py index 9ea1edf6c1..4a67a7254d 100644 --- a/pipelines/nox.py +++ b/pipelines/nox.py @@ -29,8 +29,6 @@ from nox import session as _session from nox.sessions import Session -from pipelines import config - # Default sessions should be defined here _options.sessions = ["reformat-code", "pytest", "flake8", "slotscheck", "mypy", "verify-types"] diff --git a/pipelines/pdoc.nox.py b/pipelines/pdoc.nox.py deleted file mode 100644 index 32e4c93939..0000000000 --- a/pipelines/pdoc.nox.py +++ /dev/null @@ -1,76 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020 Nekokatt -# Copyright (c) 2021-present 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 - - -@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) - - path = os.path.join(config.ARTIFACT_DIRECTORY, "docs") - _pdoc(session, ("-o", path)) - - # Replace index.html with hikari.html - os.replace(os.path.join(path, "hikari.html"), os.path.join(path, "index.html")) - - -@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",)) - - -def _pdoc(session: nox.Session, extra_arguments: typing.Sequence[str] = ()): - # We need to install everything so that typehints link correctly - session.install( - "-r", - "requirements.txt", - "-r", - "dev-requirements.txt", - "-r", - "server-requirements.txt", - "-r", - "speedup-requirements.txt", - ) - - session.run( - "python", - "docs/patched_pdoc.py", - "--docformat", - "numpy", - "-t", - "./docs", - "./hikari", - *extra_arguments, - *session.posargs, - ) diff --git a/pipelines/sphinx.nox.py b/pipelines/sphinx.nox.py new file mode 100644 index 0000000000..3ea088e963 --- /dev/null +++ b/pipelines/sphinx.nox.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020 Nekokatt +# Copyright (c) 2021-present 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 socket +import threading +import webbrowser + +from pipelines import config +from pipelines import nox + + +@nox.session(reuse_venv=True) +def sphinx(session: nox.Session): + if not os.path.exists(config.ARTIFACT_DIRECTORY): + os.mkdir(config.ARTIFACT_DIRECTORY) + + session.install("-r", "dev-requirements.txt") + + session.run( + "sphinx-build", + "-M", + "dirhtml", + config.DOCUMENTATION_DIRECTORY, + os.path.join(config.ARTIFACT_DIRECTORY, "docs"), + ) + + +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() + + directory = os.path.join(config.ARTIFACT_DIRECTORY, "docs", "dirhtml") + handler = functools.partial(http.server.SimpleHTTPRequestHandler, directory=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, venv_backend="none") +def view_docs(_: nox.Session) -> None: + """Start an HTTP server for any generated pages in `/public/docs/dirhtml`.""" + with contextlib.closing(HTTPServerThread()) as thread: + thread.start() + webbrowser.open(f"http://{thread.host}:{thread.port}") + thread.join() diff --git a/pyproject.toml b/pyproject.toml index 29e5efc054..fd3c8d8027 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -92,15 +92,23 @@ norecursedirs = [ "public", "ci", ] -filterwarnings = ["ignore:.*assertions not in test modules or plugins will be ignored because assert statements are not executed by the underlying Python interpreter.*:pytest.PytestConfigWarning"] +filterwarnings = [ + "error", # Treat warnings as errors + "ignore:.*assertions not in test modules or plugins will be ignored *:pytest.PytestConfigWarning", + "ignore::pytest.PytestUnraisableExceptionWarning", + "ignore::hikari.internal.deprecation.HikariDeprecationWarning" +] required_plugins = ["pytest-asyncio"] [tool.towncrier] package = "hikari" package_dir = "hikari" directory = "changes" +template = "changes/.template.rst" filename = "CHANGELOG.md" issue_format = "[#{issue}](https://github.com/hikari-py/hikari/issues/{issue})" +# We use this differently in the template +underlines = [2, 3] type = [ { name = "Breaking Changes", directory = "breaking", showcontent = true }, { name = "Deprecation", directory = "deprecation", showcontent = true }, diff --git a/scripts/deploy-manually.sh b/scripts/deploy-manually.sh index 3480743fd6..27aa377cd9 100755 --- a/scripts/deploy-manually.sh +++ b/scripts/deploy-manually.sh @@ -30,10 +30,8 @@ posix_read() { posix_read "Tag" VERSION posix_read "Repository slug (e.g. hikari-py/hikari)" REPO_SLUG -posix_read "Documentation repository slug (e.g. hikari-py/hikari-docs)" DOCUMENTATION_REPO_SLUG posix_read "Twine username" TWINE_USERNAME posix_read "Twine password" TWINE_PASSWORD -posix_read "Github token (must have permissions to trigger workflows in the documentation repository)" GITHUB_TOKEN posix_read "Discord deployment webhook URL" DEPLOY_WEBHOOK_URL bash scripts/deploy.sh diff --git a/scripts/deploy-pages-manually.sh b/scripts/deploy-pages-manually.sh deleted file mode 100644 index 4bc906cc4c..0000000000 --- a/scripts/deploy-pages-manually.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh -# Copyright (c) 2020 Nekokatt -# Copyright (c) 2021-present 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. -posix_read() { - prompt="${1}" - var_name="${2}" - printf "%s: " "${prompt}" - read -r "${var_name?}" - export "${var_name?}" - return ${?} -} - -posix_read "Tag ('master' for master documentation)" VERSION -posix_read "Documentation repository slug (e.g. hikari-py/hikari-docs)" DOCUMENTATION_REPO_SLUG -posix_read "Github token (must have permissions to trigger workflows in the documentation repository)" GITHUB_TOKEN - -bash scripts/deploy-pages.sh diff --git a/scripts/deploy-pages.sh b/scripts/deploy-pages.sh deleted file mode 100755 index 4ae7b0553b..0000000000 --- a/scripts/deploy-pages.sh +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright (c) 2020 Nekokatt -# Copyright (c) 2021-present 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. -set -e - -echo "Defined environment variables" -env | grep -oP "^[^=]+" | sort - -if [ -z ${VERSION+x} ]; then echo '$VERSION environment variable is missing' && exit 1; fi -if [ -z "${VERSION}" ]; then echo '$VERSION environment variable is empty' && exit 1; fi -if [ -z ${GITHUB_TOKEN+x} ]; then echo '$GITHUB_TOKEN environment variable is missing' && exit 1; fi -if [ -z "${GITHUB_TOKEN}" ]; then echo '$GITHUB_TOKEN environment variable is empty' && exit 1; fi -if [ -z ${DOCUMENTATION_REPO_SLUG+x} ]; then echo '$DOCUMENTATION_REPO_SLUG environment variable is missing' && exit 1; fi -if [ -z "${DOCUMENTATION_REPO_SLUG}" ]; then echo '$DOCUMENTATION_REPO_SLUG environment variable is empty' && exit 1; fi - -echo "===== TRIGGERING WORKFLOW IN ${DOCUMENTATION_REPO_SLUG} =====" -curl \ - -X POST \ - -H "Authorization: Bearer ${GITHUB_TOKEN}" \ - -H "Accept: application/vnd.github.v3+json" \ - "https://api.github.com/repos/${DOCUMENTATION_REPO_SLUG}/actions/workflows/deploy-docs.yml/dispatches" \ - -d '{ - "ref": "master", - "inputs": { - "version": "'"${VERSION}"'" - } - }' diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 52e6faac09..9e70407cff 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -30,12 +30,8 @@ env | grep -oP "^[^=]+" | sort if [ -z ${VERSION+x} ]; then echo '$VERSION environment variable is missing' && exit 1; fi if [ -z "${VERSION}" ]; then echo '$VERSION environment variable is empty' && exit 1; fi -if [ -z ${GITHUB_TOKEN+x} ]; then echo '$GITHUB_TOKEN environment variable is missing' && exit 1; fi -if [ -z "${GITHUB_TOKEN}" ]; then echo '$GITHUB_TOKEN environment variable is empty' && exit 1; fi if [ -z ${REPO_SLUG+x} ]; then echo '$REPO_SLUG environment variable is missing' && exit 1; fi if [ -z "${REPO_SLUG}" ]; then echo '$REPO_SLUG environment variable is empty' && exit 1; fi -if [ -z ${DOCUMENTATION_REPO_SLUG+x} ]; then echo '$DOCUMENTATION_REPO_SLUG environment variable is missing' && exit 1; fi -if [ -z "${DOCUMENTATION_REPO_SLUG}" ]; then echo '$DOCUMENTATION_REPO_SLUG environment variable is empty' && exit 1; fi if [ -z ${DEPLOY_WEBHOOK_URL+x} ]; then echo '$DEPLOY_WEBHOOK_URL environment variable is missing' && exit 1; fi if [ -z "${DEPLOY_WEBHOOK_URL}" ]; then echo '$DEPLOY_WEBHOOK_URL environment variable is empty' && exit 1; fi if [ -z ${TWINE_USERNAME+x} ]; then echo '$TWINE_USERNAME environment variable is missing' && exit 1; fi @@ -85,9 +81,6 @@ echo echo "-- Uploading to PyPI --" python -m twine upload --disable-progress-bar --skip-existing dist/* --non-interactive --repository-url https://upload.pypi.org/legacy/ -echo "===== DEPLOYING PAGES =====" -bash scripts/deploy-pages.sh - echo "===== SENDING WEBHOOK =====" bash scripts/deploy-webhook.sh diff --git a/tests/hikari/events/test_guild_events.py b/tests/hikari/events/test_guild_events.py index f592265954..f3fc230743 100644 --- a/tests/hikari/events/test_guild_events.py +++ b/tests/hikari/events/test_guild_events.py @@ -171,9 +171,10 @@ def event(self): stickers=(mock.Mock(), mock.Mock(), mock.Mock()), ) + @pytest.mark.asyncio() async def test_fetch_stickers(self, event): - event.app.rest.fetch_stickers = mock.AsyncMock() - stickers = await event.fetch_stickers() + event.app.rest.fetch_guild_stickers = mock.AsyncMock() + + assert await event.fetch_stickers() is event.app.rest.fetch_guild_stickers.return_value - event.app.rest.fetch_stickers.assert_awaited_once_with(event.guild_id) - assert stickers is event.app.rest.fetch_stickers.return_value + event.app.rest.fetch_guild_stickers.assert_awaited_once_with(event.guild_id) diff --git a/tests/hikari/impl/test_rest.py b/tests/hikari/impl/test_rest.py index f2b99143b7..fdb6b1ec9d 100644 --- a/tests/hikari/impl/test_rest.py +++ b/tests/hikari/impl/test_rest.py @@ -23,7 +23,6 @@ import contextlib import datetime import http -import warnings import mock import pytest @@ -1096,15 +1095,6 @@ def test_fetch_bans_when_start_at_undefined_and_newest_first(self, rest_client: ) assert iterator is iterator_cls.return_value - def test_command_builder(self, rest_client): - with warnings.catch_warnings(): - warnings.simplefilter("ignore", category=DeprecationWarning) - result = rest_client.command_builder("a name", description="very very good") - - assert isinstance(result, special_endpoints.SlashCommandBuilder) - assert result.name == "a name" - assert result.description == "very very good" - def test_slash_command_builder(self, rest_client): result = rest_client.slash_command_builder("a name", "a description") assert isinstance(result, special_endpoints.SlashCommandBuilder) @@ -3431,27 +3421,6 @@ async def test_add_user_to_guild(self, rest_client): rest_client._request.assert_awaited_once_with(expected_route, json=expected_json) rest_client._entity_factory.deserialize_member.assert_called_once_with({"id": "789"}, guild_id=123) - async def test_add_user_to_guild_with_deprecated_nick_field(self, rest_client): - member = StubModel(789) - expected_route = routes.PUT_GUILD_MEMBER.compile(guild=123, user=456) - expected_json = {"access_token": "token", "nick": "cool nick2"} - rest_client._request = mock.AsyncMock(return_value={"id": "789"}) - rest_client._entity_factory.deserialize_member = mock.Mock(return_value=member) - - with pytest.warns( - DeprecationWarning, match=r"Use of deprecated argument 'nick' \(Use 'nickname' argument instead\)" - ): - returned = await rest_client.add_user_to_guild( - "token", - StubModel(123), - StubModel(456), - nick="cool nick2", - ) - - assert returned is member - rest_client._request.assert_awaited_once_with(expected_route, json=expected_json) - rest_client._entity_factory.deserialize_member.assert_called_once_with({"id": "789"}, guild_id=123) - async def test_add_user_to_guild_when_already_in_guild(self, rest_client): expected_route = routes.PUT_GUILD_MEMBER.compile(guild=123, user=456) expected_json = {"access_token": "token"} @@ -4104,24 +4073,6 @@ async def test_edit_member(self, rest_client): ) rest_client._request.assert_awaited_once_with(expected_route, json=expected_json, reason="because i can") - async def test_edit_member_with_deprecated_nick_field(self, rest_client): - expected_route = routes.PATCH_GUILD_MEMBER.compile(guild=123, user=456) - expected_json = {"nick": "eeeeeestrogen"} - rest_client._request = mock.AsyncMock(return_value={"id": "789"}) - - with pytest.warns( - DeprecationWarning, - match=r"Use of deprecated argument 'nick' \(Use 'nickname' argument instead\)", - ): - result = await rest_client.edit_member(StubModel(123), StubModel(456), nick="eeeeeestrogen") - - assert result is rest_client._entity_factory.deserialize_member.return_value - - rest_client._entity_factory.deserialize_member.assert_called_once_with( - rest_client._request.return_value, guild_id=123 - ) - rest_client._request.assert_awaited_once_with(expected_route, json=expected_json, reason=undefined.UNDEFINED) - async def test_edit_member_when_voice_channel_is_None(self, rest_client): expected_route = routes.PATCH_GUILD_MEMBER.compile(guild=123, user=456) expected_json = {"nick": "test", "roles": ["654", "321"], "mute": True, "deaf": False, "channel_id": None} @@ -4199,19 +4150,6 @@ async def test_edit_my_member_without_optionals(self, rest_client): ) rest_client._request.assert_awaited_once_with(expected_route, json={}, reason=undefined.UNDEFINED) - async def test_edit_my_nick(self, rest_client): - rest_client.edit_my_member = mock.AsyncMock() - rest_client._request = mock.AsyncMock() - - with warnings.catch_warnings(): - warnings.simplefilter("ignore", category=DeprecationWarning) - result = await rest_client.edit_my_nick(123, "hikari is the best", reason="because its true") - - assert result is None - rest_client.edit_my_member.assert_awaited_once_with( - 123, nickname="hikari is the best", reason="because its true" - ) - async def test_add_role_to_member(self, rest_client): expected_route = routes.PUT_GUILD_MEMBER_ROLE.compile(guild=123, user=456, role=789) rest_client._request = mock.AsyncMock() diff --git a/tests/hikari/internal/test_deprecation.py b/tests/hikari/internal/test_deprecation.py index aaa4e9fe38..3ce0cdad93 100644 --- a/tests/hikari/internal/test_deprecation.py +++ b/tests/hikari/internal/test_deprecation.py @@ -19,42 +19,23 @@ # 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 import _about as hikari_about from hikari.internal import deprecation class TestWarnDeprecated: - def test_when_function(self): - def test(): - ... + def test_when_not_past_removal(self): + with mock.patch.object(hikari_about, "__version__", "2.0.1"): + with pytest.warns( + deprecation.HikariDeprecationWarning, + match=r"'testing' is deprecated and will be removed in `2.0.2`. Some info!", + ): + deprecation.warn_deprecated("testing", removal_version="2.0.2", additional_info="Some info!") - with pytest.warns( - DeprecationWarning, - match=( - r"Call to deprecated function/method " - r"'tests.hikari.internal.test_deprecation.TestWarnDeprecated.test_when_function..test' " - r"\(Too cool\)" - ), - ): - deprecation.warn_deprecated(test, "Too cool") - - def test_when_class(self): - class Test: - ... - - with pytest.warns( - DeprecationWarning, - match=( - r"Instantiation of deprecated class " - r"'tests.hikari.internal.test_deprecation.TestWarnDeprecated.test_when_class..Test' \(Too old\)" - ), - ): - deprecation.warn_deprecated(Test, "Too old") - - def test_when_str(self): - with pytest.warns( - DeprecationWarning, - match=r"Use of deprecated argument 'testing' \(Use 'foo.bar' instead\)", - ): - deprecation.warn_deprecated("testing", "Use 'foo.bar' instead") + def test_when_past_removal(self): + with mock.patch.object(hikari_about, "__version__", "2.0.2"): + with pytest.raises(deprecation.HikariDeprecationWarning): + deprecation.warn_deprecated("testing", removal_version="2.0.2", additional_info="Some info!") diff --git a/tests/hikari/internal/test_ux.py b/tests/hikari/internal/test_ux.py index 657655b6f4..94d699df8b 100644 --- a/tests/hikari/internal/test_ux.py +++ b/tests/hikari/internal/test_ux.py @@ -162,7 +162,7 @@ def mock_args(self): stack.enter_context(mock.patch.object(_about, "__version__", new="2.2.2")) stack.enter_context(mock.patch.object(_about, "__git_sha1__", new="12345678901234567890")) - stack.enter_context(mock.patch.object(_about, "__copyright__", new="© 2020 Nekokatt")) + stack.enter_context(mock.patch.object(_about, "__copyright__", new="2020, Nekokatt")) stack.enter_context(mock.patch.object(_about, "__license__", new="MIT")) stack.enter_context(mock.patch.object(_about, "__file__", new="~/hikari")) stack.enter_context(mock.patch.object(_about, "__docs__", new="https://nekokatt.github.io/hikari/docs")) @@ -193,7 +193,7 @@ def test_when_supports_color(self, mock_args): # Hikari stuff. "hikari_version": "2.2.2", "hikari_git_sha1": "12345678", - "hikari_copyright": "© 2020 Nekokatt", + "hikari_copyright": "2020, Nekokatt", "hikari_license": "MIT", "hikari_install_location": "some path", "hikari_documentation_url": "https://nekokatt.github.io/hikari/docs", @@ -236,7 +236,7 @@ def test_when_doesnt_supports_color(self, mock_args): # Hikari stuff. "hikari_version": "2.2.2", "hikari_git_sha1": "12345678", - "hikari_copyright": "© 2020 Nekokatt", + "hikari_copyright": "2020, Nekokatt", "hikari_license": "MIT", "hikari_install_location": "some path", "hikari_documentation_url": "https://nekokatt.github.io/hikari/docs", @@ -280,7 +280,7 @@ def test_use_extra_args(self, mock_args): # Hikari stuff. "hikari_version": "2.2.2", "hikari_git_sha1": "12345678", - "hikari_copyright": "© 2020 Nekokatt", + "hikari_copyright": "2020, Nekokatt", "hikari_license": "MIT", "hikari_install_location": "some path", "hikari_documentation_url": "https://nekokatt.github.io/hikari/docs", diff --git a/tests/hikari/test_guilds.py b/tests/hikari/test_guilds.py index f3e48d4356..de4d3db4e1 100644 --- a/tests/hikari/test_guilds.py +++ b/tests/hikari/test_guilds.py @@ -452,29 +452,6 @@ async def test_edit(self, model): assert edit == model.app.rest.edit_member.return_value - @pytest.mark.asyncio() - async def test_edit_when_deprecated_nick_field(self, model): - model.app.rest.edit_member = mock.AsyncMock() - - with pytest.warns( - DeprecationWarning, - match=r"Use of deprecated argument 'nick' \(Use 'nickname' argument instead\)", - ): - edit = await model.edit(nick="meow") - - model.app.rest.edit_member.assert_awaited_once_with( - 456, - 123, - nickname="meow", - roles=undefined.UNDEFINED, - mute=undefined.UNDEFINED, - deaf=undefined.UNDEFINED, - voice_channel=undefined.UNDEFINED, - communication_disabled_until=undefined.UNDEFINED, - reason=undefined.UNDEFINED, - ) - assert edit == model.app.rest.edit_member.return_value - def test_default_avatar_url_property(self, model, mock_user): assert model.default_avatar_url is mock_user.default_avatar_url diff --git a/tests/hikari/test_messages.py b/tests/hikari/test_messages.py index afd5a561ae..1e5902f3a3 100644 --- a/tests/hikari/test_messages.py +++ b/tests/hikari/test_messages.py @@ -152,6 +152,11 @@ def message(): class TestMessage: + def test_mentions_property(self, message): + message._mentions = object() + + assert message.mentions is message._mentions + def test_make_link_when_guild_is_not_none(self, message): message.id = 789 message.channel_id = 456 From 918f43ba4cb614cb81d52df8b2f7980b5a42d8cd Mon Sep 17 00:00:00 2001 From: davfsa Date: Tue, 2 Aug 2022 22:49:58 +0200 Subject: [PATCH 18/38] Improve headers of imported markdown files --- README.md | 8 ++++---- docs/index.md | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 99a9984ecb..aea15e2e4e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -

hikari

-

-PyPI version +

+

hikari

Supported python versions +PyPI version
CI status Mypy badge @@ -10,7 +10,7 @@
Discord invite Documentation Status -

+
An opinionated, static typed Discord microframework for Python3 and asyncio that supports Discord's V8 REST API and Gateway. diff --git a/docs/index.md b/docs/index.md index 0441cc23af..62e7c6438d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,4 +1,4 @@ -# Welcome to hikari's documentation! +# Welcome! ```{include} ../README.md ``` From 07c18ebb857e8c0e3d89fe480a0daf092104fa2c Mon Sep 17 00:00:00 2001 From: davfsa Date: Wed, 3 Aug 2022 12:13:05 +0200 Subject: [PATCH 19/38] Make full non-shallow clone when running towncrier Signed-off-by: davfsa --- .github/workflows/fragments-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fragments-check.yml b/.github/workflows/fragments-check.yml index bfbe2393d7..60d14791bd 100644 --- a/.github/workflows/fragments-check.yml +++ b/.github/workflows/fragments-check.yml @@ -21,7 +21,7 @@ jobs: with: # `towncrier check` runs `git diff --name-only origin/main...`, which # needs a non-shallow clone. - fetch-depth: 2 + fetch-depth: 0 - name: Setup python uses: actions/setup-python@v4 From 0e0e7bcf3e1ee558f711272e27acb98e98866aed Mon Sep 17 00:00:00 2001 From: davfsa Date: Wed, 3 Aug 2022 12:25:59 +0200 Subject: [PATCH 20/38] Make README render consistent in docs and GitHub --- README.md | 2 +- pyproject.toml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index aea15e2e4e..85d68a19e3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -
+

hikari

Supported python versions PyPI version diff --git a/pyproject.toml b/pyproject.toml index fd3c8d8027..723de5285a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -110,11 +110,11 @@ issue_format = "[#{issue}](https://github.com/hikari-py/hikari/issues/{issue})" # We use this differently in the template underlines = [2, 3] type = [ - { name = "Breaking Changes", directory = "breaking", showcontent = true }, - { name = "Deprecation", directory = "deprecation", showcontent = true }, - { name = "Features", directory = "feature", showcontent = true }, - { name = "Bugfixes", directory = "bugfix", showcontent = true }, - { name = "Documentation Improvements", directory = "doc", showcontent = true }, + { name = "Breaking Changes", directory = "breaking", showcontent = true }, + { name = "Deprecation", directory = "deprecation", showcontent = true }, + { name = "Features", directory = "feature", showcontent = true }, + { name = "Bugfixes", directory = "bugfix", showcontent = true }, + { name = "Documentation Improvements", directory = "documentation", showcontent = true }, ] [tool.isort] From 747ae593af5880c39c72e6ca1e58566b70a687cb Mon Sep 17 00:00:00 2001 From: davfsa Date: Wed, 3 Aug 2022 21:26:29 +0200 Subject: [PATCH 21/38] Cleanup --- .../Jinja2 HTML Template.html.jinja2 | 34 ------------------- hikari/internal/deprecation.py | 10 ++---- pipelines/codespell.nox.py | 4 +-- pipelines/config.py | 2 -- pipelines/format.nox.py | 4 +-- pipelines/sphinx.nox.py | 1 + pyproject.toml | 4 +-- tests/hikari/internal/test_deprecation.py | 4 +-- 8 files changed, 10 insertions(+), 53 deletions(-) delete mode 100644 .idea/fileTemplates/Jinja2 HTML Template.html.jinja2 diff --git a/.idea/fileTemplates/Jinja2 HTML Template.html.jinja2 b/.idea/fileTemplates/Jinja2 HTML Template.html.jinja2 deleted file mode 100644 index 3f4ec8748e..0000000000 --- a/.idea/fileTemplates/Jinja2 HTML Template.html.jinja2 +++ /dev/null @@ -1,34 +0,0 @@ -{# -Copyright (c) 2020 Nekokatt -Copyright (c) 2021-present 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. -#} - - - - - - - {% block title %}{% endblock %} - {% block head %}{% endblock %} - {% block style %}{% endblock %} - -{% block body %}{% endblock %} - diff --git a/hikari/internal/deprecation.py b/hikari/internal/deprecation.py index 78efa5dc13..29be981994 100644 --- a/hikari/internal/deprecation.py +++ b/hikari/internal/deprecation.py @@ -24,7 +24,7 @@ from __future__ import annotations -__all__: typing.Sequence[str] = ("warn_deprecated", "HikariDeprecationWarning") +__all__: typing.Sequence[str] = ("warn_deprecated",) import typing import warnings @@ -33,10 +33,6 @@ from hikari.internal import ux -class HikariDeprecationWarning(DeprecationWarning): - """Warning about a hikari deprecation.""" - - def warn_deprecated(name: str, /, *, removal_version: str, additional_info: str, stack_level: int = 3) -> None: """Issue a deprecation warning. @@ -55,10 +51,10 @@ def warn_deprecated(name: str, /, *, removal_version: str, additional_info: str, The stack level to issue the warning in. """ if ux.HikariVersion(hikari_about.__version__) >= ux.HikariVersion(removal_version): - raise HikariDeprecationWarning(f"{name!r} is passed its removal version ({removal_version})") + raise DeprecationWarning(f"{name!r} is passed its removal version ({removal_version})") warnings.warn( f"{name!r} is deprecated and will be removed in `{removal_version}`. {additional_info}", - category=HikariDeprecationWarning, + category=DeprecationWarning, stacklevel=stack_level, ) diff --git a/pipelines/codespell.nox.py b/pipelines/codespell.nox.py index 1232583c25..027dcaeb75 100644 --- a/pipelines/codespell.nox.py +++ b/pipelines/codespell.nox.py @@ -27,6 +27,4 @@ def codespell(session: nox.Session) -> None: """Run codespell to check for spelling mistakes.""" session.install("-r", "dev-requirements.txt") - - 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) + session.run("codespell", *config.FULL_REFORMATTING_PATHS) diff --git a/pipelines/config.py b/pipelines/config.py index 2c0155ce1b..5a3e7f5ed2 100644 --- a/pipelines/config.py +++ b/pipelines/config.py @@ -92,5 +92,3 @@ "docs", "changes", ) - -CODESPELL_IGNORE_WORDS = ("nd",) diff --git a/pipelines/format.nox.py b/pipelines/format.nox.py index f761db6faa..5b2baa8f4a 100644 --- a/pipelines/format.nox.py +++ b/pipelines/format.nox.py @@ -40,9 +40,7 @@ def reformat_code(session: nox.Session) -> None: session.run("isort", *config.PYTHON_REFORMATTING_PATHS) session.run("black", *config.PYTHON_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) + session.run("codespell", "-w", *config.FULL_REFORMATTING_PATHS) @nox.session(reuse_venv=True, venv_backend="none") diff --git a/pipelines/sphinx.nox.py b/pipelines/sphinx.nox.py index 3ea088e963..8a597c94ba 100644 --- a/pipelines/sphinx.nox.py +++ b/pipelines/sphinx.nox.py @@ -35,6 +35,7 @@ @nox.session(reuse_venv=True) def sphinx(session: nox.Session): + """Generate docs using sphinx.""" if not os.path.exists(config.ARTIFACT_DIRECTORY): os.mkdir(config.ARTIFACT_DIRECTORY) diff --git a/pyproject.toml b/pyproject.toml index 723de5285a..9f4c8f8915 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -94,9 +94,9 @@ norecursedirs = [ ] filterwarnings = [ "error", # Treat warnings as errors - "ignore:.*assertions not in test modules or plugins will be ignored *:pytest.PytestConfigWarning", + "ignore:.*assertions not in test modules or plugins will be ignored .*:pytest.PytestConfigWarning", "ignore::pytest.PytestUnraisableExceptionWarning", - "ignore::hikari.internal.deprecation.HikariDeprecationWarning" + "ignore:'(.*)' is deprecated and will be removed in `(.*)`:DeprecationWarning" ] required_plugins = ["pytest-asyncio"] diff --git a/tests/hikari/internal/test_deprecation.py b/tests/hikari/internal/test_deprecation.py index 3ce0cdad93..071e7446bc 100644 --- a/tests/hikari/internal/test_deprecation.py +++ b/tests/hikari/internal/test_deprecation.py @@ -30,12 +30,12 @@ class TestWarnDeprecated: def test_when_not_past_removal(self): with mock.patch.object(hikari_about, "__version__", "2.0.1"): with pytest.warns( - deprecation.HikariDeprecationWarning, + DeprecationWarning, match=r"'testing' is deprecated and will be removed in `2.0.2`. Some info!", ): deprecation.warn_deprecated("testing", removal_version="2.0.2", additional_info="Some info!") def test_when_past_removal(self): with mock.patch.object(hikari_about, "__version__", "2.0.2"): - with pytest.raises(deprecation.HikariDeprecationWarning): + with pytest.raises(DeprecationWarning): deprecation.warn_deprecated("testing", removal_version="2.0.2", additional_info="Some info!") From 4d08b9539a7eb295fb5f3412e331bd7a39f1ee79 Mon Sep 17 00:00:00 2001 From: davfsa Date: Sat, 6 Aug 2022 16:41:42 +0200 Subject: [PATCH 22/38] Remove nox as a deploy requirement --- scripts/deploy.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 9e70407cff..818f0236e4 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -54,7 +54,6 @@ echo "===== INSTALLING DEPENDENCIES =====" pip install setuptools pip install wheel pip install twine -pip install nox pip install -r requirements.txt REF=$(git rev-parse HEAD) From 94d9519707130fafb2baf4f7f3cee79829c5f80a Mon Sep 17 00:00:00 2001 From: davfsa Date: Mon, 10 Oct 2022 12:48:54 +0200 Subject: [PATCH 23/38] Bump dependencies --- dev-requirements/sphinx.txt | 10 +++++----- hikari/channels.py | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/dev-requirements/sphinx.txt b/dev-requirements/sphinx.txt index 230ca797fd..b7f8dcd5ff 100644 --- a/dev-requirements/sphinx.txt +++ b/dev-requirements/sphinx.txt @@ -1,7 +1,7 @@ -sphinx==5.0.1 -sphinx-autoapi==1.8.4 -numpydoc==1.4.0 -furo==2022.6.21 -myst-parser==0.18.0 +sphinx==5.2.3 +sphinx-autoapi==2.0.0 +numpydoc==1.5.0 +furo==2022.9.29 +myst-parser==0.18.1 sphinxext.opengraph==0.6.3 sphinx-copybutton==0.5.0 diff --git a/hikari/channels.py b/hikari/channels.py index d585e3efda..3a2fa6d2a9 100644 --- a/hikari/channels.py +++ b/hikari/channels.py @@ -264,7 +264,6 @@ class PermissionOverwrite: | Permissions.SPEAK ), ) - ``` """ id: snowflakes.Snowflake = attr.field(converter=snowflakes.Snowflake, repr=True) From 4a5144ad9b96ee788bb99b224bb335197340c5d1 Mon Sep 17 00:00:00 2001 From: davfsa Date: Mon, 10 Oct 2022 13:02:04 +0200 Subject: [PATCH 24/38] Add unreleased changes to changelog in docs --- CHANGELOG.md | 20 +++++++++--------- changes/.template.rst | 4 ---- dev-requirements/sphinx.txt | 1 + docs/_templates/python/data.rst | 36 +++++++++++++++++++++++++++++++++ docs/changelog/index.md | 7 +++++++ docs/conf.py | 16 +++++++-------- 6 files changed, 61 insertions(+), 23 deletions(-) create mode 100644 docs/_templates/python/data.rst diff --git a/CHANGELOG.md b/CHANGELOG.md index 3691bb9256..b00ecaf6bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## Hikari 2.0.0.dev111 (2022-09-26) +## 2.0.0.dev111 (2022-09-26) ### Breaking Changes @@ -57,7 +57,7 @@ --- -## Hikari 2.0.0.dev110 (2022-08-08) +## 2.0.0.dev110 (2022-08-08) ### Breaking Changes @@ -82,7 +82,7 @@ --- -## Hikari 2.0.0.dev109 (2022-06-26) +## 2.0.0.dev109 (2022-06-26) ### Breaking Changes @@ -125,7 +125,7 @@ --- -## Hikari 2.0.0.dev108 (2022-03-27) +## 2.0.0.dev108 (2022-03-27) ### Breaking Changes @@ -162,7 +162,7 @@ --- -## Hikari 2.0.0.dev107 (2022-03-04) +## 2.0.0.dev107 (2022-03-04) ### Features @@ -191,7 +191,7 @@ --- -## Hikari 2.0.0.dev106 (2022-02-03) +## 2.0.0.dev106 (2022-02-03) ### Breaking Changes @@ -232,7 +232,7 @@ --- -## Hikari 2.0.0.dev105 (2022-01-01) +## 2.0.0.dev105 (2022-01-01) ### Features @@ -258,7 +258,7 @@ --- -## Hikari 2.0.0.dev104 (2021-11-22) +## 2.0.0.dev104 (2021-11-22) ### Breaking Changes @@ -298,7 +298,7 @@ --- -## Hikari 2.0.0.dev103 (2021-10-06) +## 2.0.0.dev103 (2021-10-06) ### Breaking Changes @@ -333,7 +333,7 @@ --- -## Hikari 2.0.0.dev102 (2021-09-19) +## 2.0.0.dev102 (2021-09-19) ### Deprecations and Removals diff --git a/changes/.template.rst b/changes/.template.rst index 426bcad2f4..1940463947 100644 --- a/changes/.template.rst +++ b/changes/.template.rst @@ -1,8 +1,4 @@ -{% if versiondata.name %} -## {{ versiondata.name }} {{ versiondata.version }} ({{ versiondata.date }}) -{% else %} ## {{ versiondata.version }} ({{ versiondata.date }}) -{% endif %} {% for section, _ in sections.items() %} {% set anchor = "#" * underlines[0] %}{% if section %}{{ anchor }} {{section}} {% set anchor = "#" * underlines[1] %} diff --git a/dev-requirements/sphinx.txt b/dev-requirements/sphinx.txt index b7f8dcd5ff..6ca0cd1ce4 100644 --- a/dev-requirements/sphinx.txt +++ b/dev-requirements/sphinx.txt @@ -5,3 +5,4 @@ furo==2022.9.29 myst-parser==0.18.1 sphinxext.opengraph==0.6.3 sphinx-copybutton==0.5.0 +sphinxcontrib-towncrier==0.3.0a0 diff --git a/docs/_templates/python/data.rst b/docs/_templates/python/data.rst new file mode 100644 index 0000000000..66079dd86d --- /dev/null +++ b/docs/_templates/python/data.rst @@ -0,0 +1,36 @@ +.. TODO: Remove once https://github.com/readthedocs/sphinx-autoapi/pull/353 is merged + +{% if obj.display %} +.. py:{{ obj.type }}:: {{ obj.name }} + {%- if obj.annotation is not none %} + + :type: {%- if obj.annotation %} {{ obj.annotation }}{%- endif %} + + {%- endif %} + + {%- if obj.value is not none %} + + :value: {% if obj.value is string and obj.value.splitlines()|count > 1 -%} + Multiline-String + + .. raw:: html + +
Show Value + + .. code-block:: text + :linenos: + + {{ obj.value|indent(width=8) }} + + .. raw:: html + +
+ + {%- else -%} + {{ obj.value|string|truncate(100) }} + {%- endif %} + {%- endif %} + + + {{ obj.docstring|indent(3) }} +{% endif %} diff --git a/docs/changelog/index.md b/docs/changelog/index.md index 922f663e66..0d93fb8b8a 100644 --- a/docs/changelog/index.md +++ b/docs/changelog/index.md @@ -1,4 +1,11 @@ # Changelog +```{attention} +Mayor and minor releases also include the changes specified in prior development releases. +``` + +```{towncrier-draft-entries} Unreleased changes +``` + ```{include} ../../CHANGELOG.md ``` diff --git a/docs/conf.py b/docs/conf.py index a2a019f819..d9d78e2a9e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -44,7 +44,6 @@ author = metadata.author release = version = metadata.version -# So that we don't interfere with any potential config del os, re, types, code, token_pattern, group, metadata # -- General configuration --------------------------------------------------- @@ -63,6 +62,7 @@ # Misc "sphinxext.opengraph", "sphinx_copybutton", + "sphinxcontrib.towncrier.ext", ] templates_path = ["_templates"] @@ -74,13 +74,11 @@ html_theme = "furo" html_favicon = "https://www.hikari-py.dev/logo.png" -html_theme_options = { - "top_of_page_button": None, -} +# html_theme_options = { +# "top_of_page_button": None, +# } html_static_path = ["_static"] -html_css_files = [ - "extra.css", -] +html_css_files = ["extra.css"] # -- OpenGraph ---------------------------------------------------------------- @@ -126,8 +124,8 @@ "python": ("https://docs.python.org/3", None), "aiohttp": ("https://docs.aiohttp.org/en/stable", None), "attrs": ("https://www.attrs.org/en/stable/", None), - "multidict": ("https://multidict.readthedocs.io/en/stable/", None), - "yarl": ("https://yarl.readthedocs.io/en/stable/", None), + "multidict": ("https://multidict.aio-libs.org/en/stable/", None), + "yarl": ("https://yarl.aio-libs.org/en/stable/", None), } # -- MyST --------------------------------------------------------------------- From 4dd830505ecf441b4aa030114b499639e917c9f0 Mon Sep 17 00:00:00 2001 From: davfsa Date: Mon, 10 Oct 2022 13:04:57 +0200 Subject: [PATCH 25/38] Rename deploy scripts to release scripts --- .github/workflows/release.yml | 4 ++-- hikari/_about.py | 2 +- scripts/{deploy-manually.sh => release-manually.sh} | 2 +- scripts/{deploy-webhook.sh => release-webhook.sh} | 0 scripts/{deploy.sh => release.sh} | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) rename scripts/{deploy-manually.sh => release-manually.sh} (98%) rename scripts/{deploy-webhook.sh => release-webhook.sh} (100%) rename scripts/{deploy.sh => release.sh} (97%) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2bbf09ad1e..d955502b59 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,14 +37,14 @@ jobs: access_token: ${{ steps.generate_token.outputs.token }} enforce_admins: false - - name: Deploy + - name: Release env: VERSION: ${{ github.event.release.tag_name }} REPO_SLUG: ${{ github.repository }} DEPLOY_WEBHOOK_URL: ${{ secrets.DEPLOY_WEBHOOK_URL }} TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} - run: bash scripts/deploy.sh + run: bash scripts/release.sh - name: Re-enable "include administrators" branch protection uses: benjefferies/branch-protection-bot@1.0.7 diff --git a/hikari/_about.py b/hikari/_about.py index 9105bd3b7a..ab77446461 100644 --- a/hikari/_about.py +++ b/hikari/_about.py @@ -26,7 +26,7 @@ import typing # DO NOT CHANGE THE TYPE HINTS FOR THESE FIELDS. THESE ARE AUTOMATICALLY UPDATED -# FROM THE CI SCRIPT AND DOING THIS MAY LEAD TO THE DEPLOY PROCESS FAILING. +# FROM THE CI SCRIPT AND DOING THIS MAY LEAD TO THE RELEASE PROCESS FAILING. __author__: typing.Final[str] = "Nekokatt" __maintainer__: typing.Final[str] = "davfsa" diff --git a/scripts/deploy-manually.sh b/scripts/release-manually.sh similarity index 98% rename from scripts/deploy-manually.sh rename to scripts/release-manually.sh index 27aa377cd9..71b518b8f2 100755 --- a/scripts/deploy-manually.sh +++ b/scripts/release-manually.sh @@ -34,4 +34,4 @@ posix_read "Twine username" TWINE_USERNAME posix_read "Twine password" TWINE_PASSWORD posix_read "Discord deployment webhook URL" DEPLOY_WEBHOOK_URL -bash scripts/deploy.sh +bash scripts/release.sh diff --git a/scripts/deploy-webhook.sh b/scripts/release-webhook.sh similarity index 100% rename from scripts/deploy-webhook.sh rename to scripts/release-webhook.sh diff --git a/scripts/deploy.sh b/scripts/release.sh similarity index 97% rename from scripts/deploy.sh rename to scripts/release.sh index d48c28305a..c221aabd61 100755 --- a/scripts/deploy.sh +++ b/scripts/release.sh @@ -42,7 +42,7 @@ if [ -z "${TWINE_PASSWORD}" ]; then echo '$TWINE_PASSWORD environment variable i regex='__version__: typing\.Final\[str\] = "([^"]*)"' if [[ $(cat hikari/_about.py) =~ $regex ]]; then if [ "${BASH_REMATCH[1]}" != "${VERSION}" ]; then - echo "Variable '__version__' does not match the version this deploy is for! [__version__='${BASH_REMATCH[1]}'; VERSION='${VERSION}']" && exit 1 + echo "Variable '__version__' does not match the version this release is for! [__version__='${BASH_REMATCH[1]}'; VERSION='${VERSION}']" && exit 1 fi else echo "Variable '__version__' not found in about!" && exit 1 @@ -76,7 +76,7 @@ echo "-- Uploading to PyPI --" python -m twine upload --disable-progress-bar --skip-existing dist/* --non-interactive --repository-url https://upload.pypi.org/legacy/ echo "===== SENDING WEBHOOK =====" -bash scripts/deploy-webhook.sh +bash scripts/release-webhook.sh echo "===== UPDATING VERSION IN REPOSITORY =====" NEW_VERSION=$(python scripts/increase_version_number.py "${VERSION}") From b0ac9067cd41f6c628db7bff8837ed63cd852b36 Mon Sep 17 00:00:00 2001 From: davfsa Date: Mon, 10 Oct 2022 13:37:24 +0200 Subject: [PATCH 26/38] Fix unclosed event loop warning in tests --- docs/changelog/index.md | 2 +- hikari/interactions/base_interactions.py | 14 ++++++++++---- tests/hikari/__init__.py | 4 ++++ tests/hikari/internal/test_aio.py | 1 - 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/docs/changelog/index.md b/docs/changelog/index.md index 0d93fb8b8a..32b4559ced 100644 --- a/docs/changelog/index.md +++ b/docs/changelog/index.md @@ -5,7 +5,7 @@ Mayor and minor releases also include the changes specified in prior development ``` ```{towncrier-draft-entries} Unreleased changes -``` +``` ```{include} ../../CHANGELOG.md ``` diff --git a/hikari/interactions/base_interactions.py b/hikari/interactions/base_interactions.py index ee15f6aad2..a5fa70a8c7 100644 --- a/hikari/interactions/base_interactions.py +++ b/hikari/interactions/base_interactions.py @@ -39,14 +39,20 @@ import attr -from hikari import guilds, snowflakes, undefined, webhooks -from hikari.internal import attr_extensions, enums +from hikari import guilds +from hikari import snowflakes +from hikari import undefined +from hikari import webhooks +from hikari.internal import attr_extensions +from hikari.internal import enums if typing.TYPE_CHECKING: from hikari import embeds as embeds_ - from hikari import files, messages + from hikari import files + from hikari import messages from hikari import permissions as permissions_ - from hikari import traits, users + from hikari import traits + from hikari import users from hikari.api import special_endpoints diff --git a/tests/hikari/__init__.py b/tests/hikari/__init__.py index f77717f78b..696c11569e 100644 --- a/tests/hikari/__init__.py +++ b/tests/hikari/__init__.py @@ -33,6 +33,10 @@ ##################### class TestingPolicy(asyncio.DefaultEventLoopPolicy): def set_event_loop(self, loop: typing.Optional[asyncio.AbstractEventLoop]) -> None: + # Close any old event loops to prevent them from raising warnings + if self._local._loop: + self._local._loop.close() + if loop is not None: loop.set_debug(True) diff --git a/tests/hikari/internal/test_aio.py b/tests/hikari/internal/test_aio.py index e19bc4bf1b..d50317470c 100644 --- a/tests/hikari/internal/test_aio.py +++ b/tests/hikari/internal/test_aio.py @@ -396,7 +396,6 @@ def test_get_or_make_loop(): def test_get_or_make_loop_handles_runtime_error(): - asyncio.get_event_loop_policy().get_event_loop().close() asyncio.set_event_loop(None) mock_loop = mock.Mock(asyncio.AbstractEventLoop) From e3efeaca66b885edf2aa21c3b03477f8bed996b7 Mon Sep 17 00:00:00 2001 From: davfsa Date: Mon, 10 Oct 2022 18:22:38 +0200 Subject: [PATCH 27/38] Fix readthedocs build --- .readthedocs.yml | 4 +++- dev-requirements/sphinx.txt | 11 ++++++++++- docs/conf.py | 11 ++++++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index f6dbe9fef6..2d49d78493 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -11,7 +11,9 @@ sphinx: python: install: - - requirements: dev-requirements.txt + - requirements: dev-requirements/sphinx.txt + - method: pip + path: . search: ignore: diff --git a/dev-requirements/sphinx.txt b/dev-requirements/sphinx.txt index 6ca0cd1ce4..8bbb05da23 100644 --- a/dev-requirements/sphinx.txt +++ b/dev-requirements/sphinx.txt @@ -1,8 +1,17 @@ sphinx==5.2.3 + +# Theme +furo==2022.9.29 + +# Autodocumentation sphinx-autoapi==2.0.0 numpydoc==1.5.0 -furo==2022.9.29 + +# Static files myst-parser==0.18.1 + +# Misc extensions sphinxext.opengraph==0.6.3 sphinx-copybutton==0.5.0 sphinxcontrib-towncrier==0.3.0a0 +readthedocs-sphinx-search==0.1.2 diff --git a/docs/conf.py b/docs/conf.py index d9d78e2a9e..4998b114c9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -21,6 +21,7 @@ # SOFTWARE. """Configuration file for the Sphinx documentation builder.""" import os +import pathlib import re import types @@ -44,7 +45,8 @@ author = metadata.author release = version = metadata.version -del os, re, types, code, token_pattern, group, metadata +PROJECT_ROOT_DIR = pathlib.Path(__file__).parents[1].resolve() + # -- General configuration --------------------------------------------------- @@ -63,6 +65,7 @@ "sphinxext.opengraph", "sphinx_copybutton", "sphinxcontrib.towncrier.ext", + "sphinx_search.extension", ] templates_path = ["_templates"] @@ -131,3 +134,9 @@ # -- MyST --------------------------------------------------------------------- myst_heading_anchors = 3 + +# -- Towncrier ---------------------------------------------------------------- + +towncrier_draft_autoversion_mode = "draft" +towncrier_draft_include_empty = True +towncrier_draft_working_directory = PROJECT_ROOT_DIR From 4ca2c545810c47ddc57316dd66685e97aef132fb Mon Sep 17 00:00:00 2001 From: davfsa Date: Tue, 11 Oct 2022 09:38:06 +0200 Subject: [PATCH 28/38] Fix links in readme --- README.md | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 6dc3f81c45..f229b1cf50 100644 --- a/README.md +++ b/README.md @@ -41,13 +41,15 @@ bot = hikari.GatewayBot(token="...") @bot.listen() async def ping(event: hikari.GuildMessageCreateEvent) -> None: - # If a non-bot user sends a message "hk.ping", respond with "Pong!" - # We check there is actually content first, if no message content exists, - # we would get `None' here. - if event.is_bot or not event.content: + """If a non-bot user mentions your bot, respond with 'Pong!'.""" + + # Do not respond to bots nor webhooks pinging us, only user accounts + if not event.is_human: return - if event.content.startswith("hk.ping"): + me = bot.get_me() + + if me.id in event.message.user_mentions_ids: await event.message.respond("Pong!") bot.run() @@ -61,20 +63,20 @@ enable it manually. This has been implemented after seeing a large number of new writing their first bot in other frameworks simply because of working blind after not understanding or knowing how to set up standard logging messages. -If you wish to customise the intents being used in order to change which events your bot is notified about, then you +If you wish to customize the intents being used in order to change which events your bot is notified about, then you can pass the `intents` kwarg to the `GatewayBot` constructor: ```py # the default is to enable all unprivileged intents (all events that do not target the -# presence or activity of a specific member). +# presence, activity of a specific member nor message content). 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://docs.hikari-py.dev/stable/hikari/impl/bot.html#GatewayBot) such as -[customising timeouts for requests](https://docs.hikari-py.dev/stable/hikari/impl/config.html#HTTPSettings.timeouts) -and [enabling a proxy](https://docs.hikari-py.dev/stable/hikari/impl/config.html#ProxySettings). +[Other options also exist](https://docs.hikari-py.dev/en/stable/reference/hikari/impl/bot/#hikari.impl.bot.GatewayBot) +such as [customizing timeouts for requests](https://docs.hikari-py.dev/en/stable/reference/hikari/impl/config/#hikari.impl.config.HTTPSettings.timeouts) +and [enabling a proxy](https://docs.hikari-py.dev/en/stable/reference/hikari/impl/config/#hikari.impl.config.ProxySettings). Also note that you could pass extra options to `bot.run` during development, for example: @@ -89,7 +91,7 @@ bot.run( ) ``` -[Many other helpful options](https://docs.hikari-py.dev/stable/hikari/impl/bot.html#GatewayBot.run) +[Many other helpful options](https://docs.hikari-py.dev/en/stable/reference/hikari/impl/bot/#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 @@ -232,3 +234,6 @@ Check out the issues tab on GitHub. If you are nervous, look for issues marked a easy to start with! [![good-first-issues](https://img.shields.io/github/issues/hikari-py/hikari/good%20first%20issue)](https://github.com/hikari-py/hikari/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) + +Feel free to also join our [Discord](https://discord.gg/Jx4cNGG) to directly ask questions to the maintainers! They will +be glad to help you out and point you in the right direction. From 823b5a3b69d6a7d38bc037c57734c3bd248bd78c Mon Sep 17 00:00:00 2001 From: davfsa Date: Tue, 11 Oct 2022 09:42:10 +0200 Subject: [PATCH 29/38] Use correct doc links in release scripts - Fix unwanted removal of token fetching - Remove no-longer-used repo-slug environ variable --- .github/workflows/prepare-release.yml | 19 ++++++++++++++----- .github/workflows/release.yml | 11 +++++------ hikari/_about.py | 2 +- scripts/prepare-release.sh | 2 +- scripts/release-manually.sh | 1 - scripts/release-webhook.sh | 2 +- scripts/release.sh | 4 +--- 7 files changed, 23 insertions(+), 18 deletions(-) diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 581fc5f146..839c7d6593 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -14,22 +14,31 @@ jobs: if: github.ref == 'refs/heads/master' steps: + - name: Generate token + id: generate_token + uses: tibdex/github-app-token@v1 + with: + app_id: ${{ secrets.APP_ID }} + private_key: ${{ secrets.PRIVATE_KEY }} + - name: Checkout repository uses: actions/checkout@v3 - - - name: Setup python - uses: actions/setup-python@v4 with: - python-version: 3.8 + token: ${{ steps.generate_token.outputs.token }} - name: Setup git config run: | git config --global user.name "hikari-bot" git config --global user.email "90276125+hikari-bot[bot]@users.noreply.github.com" + - name: Setup python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Run prepare script env: - VERSION: ${{ github.event.inputs.version }} + VERSION: ${{ inputs.version }} run: bash scripts/prepare-release.sh - name: Create pull request diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d955502b59..b3909b90c4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,16 +21,16 @@ jobs: with: token: ${{ steps.generate_token.outputs.token }} - - name: Setup python - uses: actions/setup-python@v4 - with: - python-version: 3.8 - - name: Setup git config run: | git config --global user.name "hikari-bot" git config --global user.email "90276125+hikari-bot[bot]@users.noreply.github.com" + - name: Setup python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Temporarily disable "include administrators" branch protection uses: benjefferies/branch-protection-bot@1.0.7 with: @@ -40,7 +40,6 @@ jobs: - name: Release env: VERSION: ${{ github.event.release.tag_name }} - REPO_SLUG: ${{ github.repository }} DEPLOY_WEBHOOK_URL: ${{ secrets.DEPLOY_WEBHOOK_URL }} TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} diff --git a/hikari/_about.py b/hikari/_about.py index ab77446461..ede58ef03b 100644 --- a/hikari/_about.py +++ b/hikari/_about.py @@ -34,7 +34,7 @@ __copyright__: typing.Final[str] = "2021-present, 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://docs.hikari-py.dev/master" +__docs__: typing.Final[str] = "https://docs.hikari-py.dev/en/master" __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/scripts/prepare-release.sh b/scripts/prepare-release.sh index dd2f5edd10..092a39d7f2 100644 --- a/scripts/prepare-release.sh +++ b/scripts/prepare-release.sh @@ -36,7 +36,7 @@ git checkout -b "task/prepare-release-${VERSION}" echo "-- Bumping repository version to ${VERSION} --" sed "/^__version__.*/, \${s||__version__: typing.Final[str] = \"${VERSION}\"|g; b}; \$q1" -i hikari/_about.py || (echo "Variable '__version__' not found in about!" && exit 1) -sed "/^__docs__.*/, \${s||__docs__: typing.Final[str] = \"https://docs.hikari-py.dev/${VERSION}\"|g; b}; \$q1" -i hikari/_about.py || (echo "Variable '__docs__' not found in about!" && exit 1) +sed "/^__docs__.*/, \${s||__docs__: typing.Final[str] = \"https://docs.hikari-py.dev/en/${VERSION}\"|g; b}; \$q1" -i hikari/_about.py || (echo "Variable '__docs__' not found in about!" && exit 1) echo "-- Running towncrier --" towncrier --yes diff --git a/scripts/release-manually.sh b/scripts/release-manually.sh index 71b518b8f2..6fa7bcd1a4 100755 --- a/scripts/release-manually.sh +++ b/scripts/release-manually.sh @@ -29,7 +29,6 @@ posix_read() { } posix_read "Tag" VERSION -posix_read "Repository slug (e.g. hikari-py/hikari)" REPO_SLUG posix_read "Twine username" TWINE_USERNAME posix_read "Twine password" TWINE_PASSWORD posix_read "Discord deployment webhook URL" DEPLOY_WEBHOOK_URL diff --git a/scripts/release-webhook.sh b/scripts/release-webhook.sh index 1ef296993b..7a3e072c53 100644 --- a/scripts/release-webhook.sh +++ b/scripts/release-webhook.sh @@ -29,7 +29,7 @@ curl \ { "title": "'"${VERSION} has been deployed to PyPI"'", "color": 6697881, - "description": "'"Install it now by executing: \`\`\`pip install hikari==${VERSION}\`\`\`\\nDocumentation can be found at https://docs.hikari-py.dev/${VERSION}"'", + "description": "'"Install it now by executing: \`\`\`pip install hikari==${VERSION}\`\`\`\\nDocumentation can be found at https://docs.hikari-py.dev/en/${VERSION}"'", "footer": { "text": "'"SHA: ${REF}"'" } diff --git a/scripts/release.sh b/scripts/release.sh index c221aabd61..797a38035e 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -30,8 +30,6 @@ env | grep -oP "^[^=]+" | sort if [ -z ${VERSION+x} ]; then echo '$VERSION environment variable is missing' && exit 1; fi if [ -z "${VERSION}" ]; then echo '$VERSION environment variable is empty' && exit 1; fi -if [ -z ${REPO_SLUG+x} ]; then echo '$REPO_SLUG environment variable is missing' && exit 1; fi -if [ -z "${REPO_SLUG}" ]; then echo '$REPO_SLUG environment variable is empty' && exit 1; fi if [ -z ${DEPLOY_WEBHOOK_URL+x} ]; then echo '$DEPLOY_WEBHOOK_URL environment variable is missing' && exit 1; fi if [ -z "${DEPLOY_WEBHOOK_URL}" ]; then echo '$DEPLOY_WEBHOOK_URL environment variable is empty' && exit 1; fi if [ -z ${TWINE_USERNAME+x} ]; then echo '$TWINE_USERNAME environment variable is missing' && exit 1; fi @@ -87,7 +85,7 @@ git checkout -f master echo "-- Bumping to development version (${NEW_VERSION}) --" sed "/^__version__.*/, \${s||__version__: typing.Final[str] = \"${NEW_VERSION}\"|g; b}; \$q1" -i hikari/_about.py || (echo "Variable '__version__' not found in about!" && exit 1) -sed "/^__docs__.*/, \${s||__docs__: typing.Final[str] = \"https://docs.hikari-py.dev/master\"|g; b}; \$q1" -i hikari/_about.py || (echo "Variable '__docs__' not found in about!" && exit 1) +sed "/^__docs__.*/, \${s||__docs__: typing.Final[str] = \"https://docs.hikari-py.dev/en/master\"|g; b}; \$q1" -i hikari/_about.py || (echo "Variable '__docs__' not found in about!" && exit 1) echo "-- Pushing to repository --" git commit -am "Bump to development version (${NEW_VERSION})" From 5b03737bb514253a9123fb8d4769ab9067a9acb6 Mon Sep 17 00:00:00 2001 From: davfsa Date: Tue, 11 Oct 2022 09:48:22 +0200 Subject: [PATCH 30/38] Remove fixed warning ignore --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 059e58111b..45842bba84 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -96,7 +96,6 @@ norecursedirs = [ filterwarnings = [ "error", # Treat warnings as errors "ignore:.*assertions not in test modules or plugins will be ignored .*:pytest.PytestConfigWarning", - "ignore:unclosed Date: Fri, 14 Oct 2022 09:25:03 +0200 Subject: [PATCH 31/38] Remove duplicated word --- hikari/api/rest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hikari/api/rest.py b/hikari/api/rest.py index 3d13bbd11e..12e0c12dd8 100644 --- a/hikari/api/rest.py +++ b/hikari/api/rest.py @@ -2620,7 +2620,7 @@ async def fetch_gateway_url(self) -> str: @abc.abstractmethod async def fetch_gateway_bot_info(self) -> sessions.GatewayBotInfo: - """Fetch the gateway gateway info for the bot. + """Fetch the gateway info for the bot. Returns ------- From c23c565e639c5d134d2138a7422954b022cc1a9d Mon Sep 17 00:00:00 2001 From: davfsa Date: Mon, 24 Oct 2022 11:03:20 +0200 Subject: [PATCH 32/38] Typo Signed-off-by: davfsa --- docs/changelog/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog/index.md b/docs/changelog/index.md index 32b4559ced..45822fff0a 100644 --- a/docs/changelog/index.md +++ b/docs/changelog/index.md @@ -1,7 +1,7 @@ # Changelog ```{attention} -Mayor and minor releases also include the changes specified in prior development releases. +Major and minor releases also include the changes specified in prior development releases. ``` ```{towncrier-draft-entries} Unreleased changes From db9c3111dbab9940db1bdea162c5dc10b0463d02 Mon Sep 17 00:00:00 2001 From: davfsa Date: Sat, 12 Nov 2022 10:51:16 +0100 Subject: [PATCH 33/38] Fix `rules_channel_id` docstring --- hikari/guilds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hikari/guilds.py b/hikari/guilds.py index 4bd1610ca5..5ecc758bf8 100644 --- a/hikari/guilds.py +++ b/hikari/guilds.py @@ -2747,7 +2747,7 @@ class Guild(PartialGuild): """ 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` feature display rules and guidelines. + """The ID of the channel where rules and guidelines will be displayed. If the `GuildFeature.COMMUNITY` feature is not defined, then this is `None`. """ From e858675630e570af429105cc26e43c3a633d38fd Mon Sep 17 00:00:00 2001 From: davfsa Date: Fri, 2 Dec 2022 00:53:19 +0100 Subject: [PATCH 34/38] Remove sphinx-search extension --- dev-requirements/sphinx.txt | 1 - docs/conf.py | 1 - 2 files changed, 2 deletions(-) diff --git a/dev-requirements/sphinx.txt b/dev-requirements/sphinx.txt index 8bbb05da23..897298cd66 100644 --- a/dev-requirements/sphinx.txt +++ b/dev-requirements/sphinx.txt @@ -14,4 +14,3 @@ myst-parser==0.18.1 sphinxext.opengraph==0.6.3 sphinx-copybutton==0.5.0 sphinxcontrib-towncrier==0.3.0a0 -readthedocs-sphinx-search==0.1.2 diff --git a/docs/conf.py b/docs/conf.py index 4998b114c9..2e5311268d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -65,7 +65,6 @@ "sphinxext.opengraph", "sphinx_copybutton", "sphinxcontrib.towncrier.ext", - "sphinx_search.extension", ] templates_path = ["_templates"] From ba56bfcecc97d364a8f427e0e66038037f4c726a Mon Sep 17 00:00:00 2001 From: davfsa Date: Fri, 2 Dec 2022 00:54:57 +0100 Subject: [PATCH 35/38] Update data.rst template --- docs/_templates/python/data.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/_templates/python/data.rst b/docs/_templates/python/data.rst index 66079dd86d..cae882be1b 100644 --- a/docs/_templates/python/data.rst +++ b/docs/_templates/python/data.rst @@ -17,17 +17,20 @@
Show Value - .. code-block:: text - :linenos: + .. code-block:: python - {{ obj.value|indent(width=8) }} + """{{ obj.value|indent(width=8,blank=true) }}""" .. raw:: html
{%- else -%} + {%- if obj.value is string -%} + {{ "%r" % obj.value|string|truncate(100) }} + {%- else -%} {{ obj.value|string|truncate(100) }} + {%- endif -%} {%- endif %} {%- endif %} From 9af9a197d9fc78ebd3c104200a280a07313b572b Mon Sep 17 00:00:00 2001 From: davfsa Date: Fri, 2 Dec 2022 00:55:05 +0100 Subject: [PATCH 36/38] Fix typo --- hikari/api/entity_factory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hikari/api/entity_factory.py b/hikari/api/entity_factory.py index 681a74f388..c9b1e759e8 100644 --- a/hikari/api/entity_factory.py +++ b/hikari/api/entity_factory.py @@ -737,7 +737,7 @@ def deserialize_channel( for DM and group DM channels and will be prioritised over `"guild_id"` in the payload when passed. - This is necesary in GUILD_CREATE events, where `"guild_id"` is not + This is necessary in GUILD_CREATE events, where `"guild_id"` is not included in the channel's payload Returns From ea95ff2e3e01d454575170261a911e96fd90c1aa Mon Sep 17 00:00:00 2001 From: davfsa Date: Fri, 2 Dec 2022 00:56:28 +0100 Subject: [PATCH 37/38] Update requirements --- dev-requirements/sphinx.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dev-requirements/sphinx.txt b/dev-requirements/sphinx.txt index 897298cd66..947eef068e 100644 --- a/dev-requirements/sphinx.txt +++ b/dev-requirements/sphinx.txt @@ -1,4 +1,4 @@ -sphinx==5.2.3 +sphinx==5.3.0 # Theme furo==2022.9.29 @@ -11,6 +11,6 @@ numpydoc==1.5.0 myst-parser==0.18.1 # Misc extensions -sphinxext.opengraph==0.6.3 -sphinx-copybutton==0.5.0 -sphinxcontrib-towncrier==0.3.0a0 +sphinxext.opengraph==0.7.3 +sphinx-copybutton==0.5.1 +sphinxcontrib-towncrier==0.3.1a3 From 26beb7e0927371ed9f0b3eed0d6c4b11a1c40581 Mon Sep 17 00:00:00 2001 From: davfsa Date: Fri, 2 Dec 2022 02:32:27 +0100 Subject: [PATCH 38/38] Fix duplicate TOC entries + drawer issues --- dev-requirements/sphinx.txt | 3 ++- docs/_static/extra.css | 12 ++++++++++++ docs/conf.py | 1 + hikari/api/cache.py | 6 +++--- hikari/api/entity_factory.py | 3 --- 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/dev-requirements/sphinx.txt b/dev-requirements/sphinx.txt index 947eef068e..c579ab5eeb 100644 --- a/dev-requirements/sphinx.txt +++ b/dev-requirements/sphinx.txt @@ -4,7 +4,8 @@ sphinx==5.3.0 furo==2022.9.29 # Autodocumentation -sphinx-autoapi==2.0.0 +#sphinx-autoapi==2.0.0 +git+https://github.com/readthedocs/sphinx-autoapi.git@94295a48cdcec75a057b50d1c3d2ced21f10f2a6 numpydoc==1.5.0 # Static files diff --git a/docs/_static/extra.css b/docs/_static/extra.css index 15da03033d..1a93b4b4f2 100644 --- a/docs/_static/extra.css +++ b/docs/_static/extra.css @@ -1,3 +1,15 @@ html { word-wrap: anywhere; } + +/* Fit all the long names in the TOC drawer */ +.toc-drawer { + width: initial; + padding-right: 0; +} + +@media (max-width: 82em) { + .toc-drawer { + right: -20em; + } +} diff --git a/docs/conf.py b/docs/conf.py index 2e5311268d..de81a24c10 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -101,6 +101,7 @@ autoapi_template_dir = "_templates" autoapi_add_toctree_entry = False +autoapi_add_objects_to_toctree = False autoapi_keep_files = True # -- AutoDoc options ---------------------------------------------------------- diff --git a/hikari/api/cache.py b/hikari/api/cache.py index d09dd76313..09c74b7da1 100644 --- a/hikari/api/cache.py +++ b/hikari/api/cache.py @@ -897,14 +897,14 @@ def delete_sticker( ) -> typing.Optional[stickers.GuildSticker]: """Remove a sticker from the cache. + .. note:: + This will not delete stickers that are being kept alive by a reference. + Parameters ---------- sticker : hikari.snowflakes.SnowflakeishOr[hikari.stickers.GuildSticker] Object or ID of the sticker to remove from the cache. - .. note:: - This will not delete stickers that are being kept alive by a reference. - Returns ------- typing.Optional[hikari.stickers.GuildSticker] diff --git a/hikari/api/entity_factory.py b/hikari/api/entity_factory.py index c9b1e759e8..c690ab626f 100644 --- a/hikari/api/entity_factory.py +++ b/hikari/api/entity_factory.py @@ -1221,9 +1221,6 @@ def deserialize_command( 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. - - Raises - ------ hikari.errors.UnrecognisedEntityError If the command type is unknown. """