diff --git a/docs/contributing/getting_started.rst b/docs/contributing/getting_started.rst index fd759066d..c0a973093 100644 --- a/docs/contributing/getting_started.rst +++ b/docs/contributing/getting_started.rst @@ -34,7 +34,7 @@ Before submitting we recommend checking a few things Do's and Dont's ---------------- -- Do keep your PR scope small. We would rather review many small PRs with seperate features than one giant one. +- Do keep your PR scope small. We would rather review many small PRs with separate features than one giant one. - Make draft PRs. Project structure @@ -45,6 +45,6 @@ Nextcore is currently split into 3 main modules - nextcore.http - nextcore.gateway -Common is for common utilies that needs to be shared between the other modules. +Common is for common utilities that needs to be shared between the other modules. HTTP is for the REST API. Gateway is for the WebSocket gateway. diff --git a/docs/events.rst b/docs/events.rst index 423bcfbc8..415aa0cd7 100644 --- a/docs/events.rst +++ b/docs/events.rst @@ -7,7 +7,7 @@ This is a document showing you the arguments from the different instances of :cl Raw Dispatcher -------------- -Can be found on :attr:`ShardManadger.raw_dispatcher ` and :attr:`Shard.raw_dispatcher `. +Can be found on :attr:`ShardManager.raw_dispatcher ` and :attr:`Shard.raw_dispatcher `. These are the raw dispatchers that just relay raw events from the discord websocket (the gateway). The event name here is the gateway `opcode `__. @@ -22,7 +22,7 @@ The event name here is the gateway `opcode ` and :attr:`Shard.event_dispatcher `. +Can be found on :attr:`ShardManager.event_dispatcher ` and :attr:`Shard.event_dispatcher `. These dispatchers dispatch the data inside the ``d`` key of a :attr:`GatewayOpcode.DISPATCH` event. The event name is the Dispatch `event name `__. diff --git a/docs/index.rst b/docs/index.rst index ba04ec6d1..156439e09 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -41,5 +41,5 @@ The documentation will now split into different pages depending on what function Helping out ============= -We would appriciate your help writing nextcore and related libraries. -See :ref:`contributing` for more info +We would appreciate your help in developing nextcore and its related libraries. +Please see :ref:`contributing` for more info diff --git a/docs/serve_dev.py b/docs/serve_dev.py new file mode 100644 index 000000000..4e264a8a5 --- /dev/null +++ b/docs/serve_dev.py @@ -0,0 +1,32 @@ +# The MIT License (MIT) +# Copyright (c) 2021-present nextcore developers +# +# 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. + +from livereload import Server, shell + +if __name__ == '__main__': + shell("make.bat html") # initial make + server = Server() + server.watch('*.rst', shell('make.bat html'), delay=1) + server.watch('*.md', shell('make.bat html'), delay=1) + server.watch('*.py', shell('make.bat html'), delay=1) + server.watch('_static/*', shell('make.bat html'), delay=1) + server.watch('contributing/*', shell('make.bat html'), delay=1) + server.serve(root='_build/html') diff --git a/nextcore/common/errors.py b/nextcore/common/errors.py index b43c59a89..733680cf4 100644 --- a/nextcore/common/errors.py +++ b/nextcore/common/errors.py @@ -26,8 +26,8 @@ if TYPE_CHECKING: from typing import Final -__all__: Final[tuple[str, ...]] = () +__all__: Final[tuple[str, ...]] = ("RateLimitedError",) class RateLimitedError(Exception): - """A error for when a :class:`~nextcore.common.TimesPer` is rate limited and ``wait`` was :data:`False`""" + """An error for when a :class:`~nextcore.common.TimesPer` is rate limited and ``wait`` was :data:`False`""" diff --git a/nextcore/http/__init__.py b/nextcore/http/__init__.py index 5c71a07fb..fc3296c9e 100644 --- a/nextcore/http/__init__.py +++ b/nextcore/http/__init__.py @@ -21,8 +21,8 @@ """Do requests to Discord over the HTTP API. -This module includes a HTTP client that handles rate limits for you, -and gives you convinient methods around the API. +This module includes an HTTP client that handles rate limits for you, +and gives you convenient methods around the API. """ from .authentication import * diff --git a/nextcore/http/bucket.py b/nextcore/http/bucket.py index 09349a217..b584717c1 100644 --- a/nextcore/http/bucket.py +++ b/nextcore/http/bucket.py @@ -56,7 +56,7 @@ class Bucket: def __init__(self, metadata: BucketMetadata): self.metadata: BucketMetadata = metadata - self._remaining: int | None = None # None signifies unlimited or not used yet (due to a optimization) + self._remaining: int | None = None # None signifies unlimited or not used yet (due to an optimization) self._pending: PriorityQueue[RequestSession] = PriorityQueue() self._reserved: list[RequestSession] = [] self._resetting: bool = False @@ -66,7 +66,7 @@ def __init__(self, metadata: BucketMetadata): @asynccontextmanager async def acquire(self, *, priority: int = 0, wait: bool = True) -> AsyncIterator[None]: - """Use a spot in the rate limit. + """Uses a spot in the rate limit. Parameters ---------- @@ -105,9 +105,9 @@ async def acquire(self, *, priority: int = 0, wait: bool = True) -> AsyncIterato raise RateLimitedError() self._pending.put_nowait( session - ) # This can't raise a exception as pending is always infinite unless someone else modified it + ) # This can't raise an exception as pending is always infinite unless someone else modified it await session.pending_future # Wait for a spot in the rate limit. - # This will automatically be removed by the waker. + # This will automatically be removed by the waker. TODO: maybe find out what this is and describe it. self._reserved.append(session) try: @@ -122,10 +122,10 @@ async def acquire(self, *, priority: int = 0, wait: bool = True) -> AsyncIterato self._reserved.remove(session) return - # We have no info on rate limits, so we have to do a "blind" request to find out what the rate limits is. - # We will only do one "blind" request at a time per bucket though in case the rate limit is small. - # This could be tweaked to use more on routes with higher rate limits, however this would require hard coding which is not a thing I want - # for nextcore. + # We have no info on rate limits, so we have to do a "blind" request to find out what the rate limits is. We + # will only do one "blind" request at a time per bucket though in case the rate limit is small. This could be + # tweaked to use more on routes with higher rate limits, however this would require complex code which is not + # a thing I want for nextcore. # TODO: maybe something to mention in the contributor docs? session = RequestSession(priority=priority) if self._can_do_blind_request.is_set(): @@ -191,7 +191,7 @@ async def update( def _reset_callback(self) -> None: self._resetting = False # Allow future resets - self._remaining = None # It should use metadata's limit as a starting point. + self._remaining = None # It should use the metadata limit as a starting point. # Reset up to the limit self._release_pending(self.metadata.limit) @@ -203,9 +203,9 @@ def _release_pending(self, max_count: int | None = None): max_count = min(max_count, self._pending.qsize()) for _ in range(max_count): - session = self._pending.get_nowait() # This can't raise a exception due to the guard clause. + session = self._pending.get_nowait() # This can't raise an exception due to the guard clause. - # Mark it as completed in the queue to avoid a infinitly overflowing int + # Mark it as completed in the queue to avoid an infinitely overflowing int self._pending.task_done() session.pending_future.set_result(None) diff --git a/nextcore/http/bucket_metadata.py b/nextcore/http/bucket_metadata.py index 2f321aefd..1552f2a21 100644 --- a/nextcore/http/bucket_metadata.py +++ b/nextcore/http/bucket_metadata.py @@ -38,7 +38,7 @@ class BucketMetadata: The maximum number of requests that can be made in the given time period. unlimited: Whether the bucket has an unlimited number of requests. If this is :class:`True`, - limit has to be None. + limit must be None. Attributes ---------- @@ -50,7 +50,7 @@ class BucketMetadata: This will also be :data:`None` if no limit has been fetched yet. unlimited: - Wheter the bucket has no rate limiting enabled. + Whether the bucket has an unlimited number of requests. """ __slots__ = ("limit", "unlimited") diff --git a/nextcore/http/errors.py b/nextcore/http/errors.py index b3aaf5bd4..886e808c5 100644 --- a/nextcore/http/errors.py +++ b/nextcore/http/errors.py @@ -45,7 +45,7 @@ class RateLimitingFailedError(Exception): """When rate limiting has failed more than :attr:`HTTPClient.max_retries` times .. hint:: - This can be due to a un-syncronized clock. + This can be due to an un-synchronised clock. You can change :attr:`HTTPClient.trust_local_time` to :data:`False` to disable using your local clock, or you could sync your clock. @@ -100,14 +100,14 @@ class RateLimitingFailedError(Exception): Parameters ---------- max_retries: - How many retries the request used that failed. + How many retries were allowed for the request. response: The response to the last request that failed. Attributes ---------- max_retries: - How many retries the request used that failed. + How many retries were allowed for the request. response: The response to the last request that failed. """ @@ -139,7 +139,7 @@ class HTTPRequestStatusError(Exception): message: The error message. error: - The error json from the body. + The error in json from the body. """ def __init__(self, error: HTTPErrorResponseData, response: ClientResponse) -> None: @@ -153,29 +153,32 @@ def __init__(self, error: HTTPErrorResponseData, response: ClientResponse) -> No super().__init__(f"({self.error_code}) {self.message}") -# TODO: Can the docstrings be improved here? class BadRequestError(HTTPRequestStatusError): - """A 400 error.""" + """Indicates that the server cannot or will not process the request due to something that is perceived to be a + client error""" class UnauthorizedError(HTTPRequestStatusError): - """A 401 error.""" + """Indicates that the client request has not been completed because it lacks valid authentication credentials for + the requested resource.""" class ForbiddenError(HTTPRequestStatusError): - """A 403 error.""" + """Indicates that the server understands the request but refuses to authorize it, typically means that + permissions are missing for the resource requested.""" class NotFoundError(HTTPRequestStatusError): - """A 404 error.""" + """Indicates that the server cannot find the requested resource.""" class InternalServerError(HTTPRequestStatusError): - """A 5xx error.""" + """A 5xx error. Indicates that the server encountered an unexpected condition that prevented it from fulfilling + the request, this is typically not a user error.""" class CloudflareBanError(Exception): - """A error for when you get banned by cloudflare + """An error for when you get banned by cloudflare This happens due to getting too many ``401``, ``403`` or ``429`` responses from discord. This will block your access to the API temporarily for an hour. diff --git a/nextcore/http/file.py b/nextcore/http/file.py index d04bd2115..714bdde9b 100644 --- a/nextcore/http/file.py +++ b/nextcore/http/file.py @@ -33,7 +33,8 @@ __all__: Final[tuple[str, ...]] = ("File",) -# This is not a attr.dataclass because it does not support slots. + +# This is not an attr.dataclass because it does not support slots. class File: """A utility class for uploading files to the API. @@ -43,7 +44,7 @@ class File: The name of the file. .. warning:: - Only files ending with a `supported file extension `__ can be included in embeds. + Only files ending with a `supported file extension `__ can be used in embeds. contents: The contents of the file. @@ -53,7 +54,7 @@ class File: The name of the file. .. warning:: - Only files ending with a `supported file extension `__ can be included in embeds. + Only files ending with a `supported file extension `__ can be used in embeds. contents: The contents of the file. """ diff --git a/nextcore/http/rate_limit_storage.py b/nextcore/http/rate_limit_storage.py index e75566f5e..a5bea6801 100644 --- a/nextcore/http/rate_limit_storage.py +++ b/nextcore/http/rate_limit_storage.py @@ -49,8 +49,8 @@ class RateLimitStorage: Attributes ---------- - global_lock: - The users per user global rate limit. + global_rate_limiter: + The users per-user global rate limit. """ __slots__ = ("_nextcore_buckets", "_discord_buckets", "_bucket_metadata", "global_rate_limiter") @@ -69,29 +69,29 @@ def __init__(self) -> None: # These are async and not just public dicts because we want to support custom implementations that use asyncio. # This does introduce some overhead, but it's not too bad. async def get_bucket_by_nextcore_id(self, nextcore_id: str) -> Bucket | None: - """Get a rate limit bucket from a nextcore created id. + """Gets a rate limit bucket from a nextcore created id. Parameters ---------- nextcore_id: - The nextcore generated bucket id. This can be gotten by using :attr:`Route.bucket` + The nextcore generated bucket id. This can be retrieved by using :attr:`Route.bucket` """ return self._nextcore_buckets.get(nextcore_id) async def store_bucket_by_nextcore_id(self, nextcore_id: str, bucket: Bucket) -> None: - """Store a rate limit bucket by nextcore generated id. + """Stores a rate limit bucket by nextcore generated id. Parameters ---------- nextcore_id: - The nextcore generated id of the + The nextcore generated id of the bucket. bucket: The bucket to store. """ self._nextcore_buckets[nextcore_id] = bucket async def get_bucket_by_discord_id(self, discord_id: str) -> Bucket | None: - """Get a rate limit bucket from the Discord bucket hash. + """Gets a rate limit bucket from the Discord bucket hash. This can be obtained via the ``X-Ratelimit-Bucket`` header. @@ -103,7 +103,7 @@ async def get_bucket_by_discord_id(self, discord_id: str) -> Bucket | None: return self._discord_buckets.get(discord_id) async def store_bucket_by_discord_id(self, discord_id: str, bucket: Bucket) -> None: - """Store a rate limit bucket by the discord bucket hash. + """Stores a rate limit bucket by the discord bucket hash. This can be obtained via the ``X-Ratelimit-Bucket`` header. @@ -117,7 +117,7 @@ async def store_bucket_by_discord_id(self, discord_id: str, bucket: Bucket) -> N self._discord_buckets[discord_id] = bucket async def get_bucket_metadata(self, bucket_route: str) -> BucketMetadata | None: - """Get the metadata for a bucket from the route. + """Gets the metadata for a bucket from the route. Parameters ---------- @@ -127,7 +127,7 @@ async def get_bucket_metadata(self, bucket_route: str) -> BucketMetadata | None: return self._bucket_metadata.get(bucket_route) async def store_metadata(self, bucket_route: str, metadata: BucketMetadata) -> None: - """Store the metadata for a bucket from the route. + """Stores the metadata for a bucket from the route. Parameters ---------- @@ -151,14 +151,15 @@ def _cleanup_buckets(self, phase: Literal["start", "stop"], info: dict[str, int] for bucket_id, bucket in self._nextcore_buckets.copy().items(): if not bucket.dirty: logger.debug("Cleaning up bucket %s", bucket_id) - # Delete the main reference. Other references like RateLimitStorage._discord_buckets should get cleaned up automatically as it is a weakref. + # Delete the main reference. Other references like RateLimitStorage._discord_buckets should get + # cleaned up automatically as it is a weakref. del self._nextcore_buckets[bucket_id] async def close(self) -> None: """Clean up before deletion. .. warning:: - If this is not called before you delete this or it goes out of scope, you will get a memory leak. + If this is not called before you delete this, or it goes out of scope; you will get a memory leak. """ # Remove the garbage collection callback gc.callbacks.remove(self._cleanup_buckets) diff --git a/nextcore/http/request_session.py b/nextcore/http/request_session.py index def1d077d..03c342495 100644 --- a/nextcore/http/request_session.py +++ b/nextcore/http/request_session.py @@ -39,6 +39,8 @@ class RequestSession: If this request was made when the bucket was unlimited. This exists to make sure that there is no bad state when switching between unlimited and limited. + priority: + The priority of the request (A lower number means it will be executed faster). Attributes ---------- @@ -47,7 +49,9 @@ class RequestSession: This exists to make sure that there is no bad state when switching between unlimited and limited. pending_future: - The future that when set will execute the request. + The future that would be executed when set. + priority: + The priority of the request (A lower number means it will be executed faster). """ __slots__: Final[tuple[str, ...]] = ("pending_future", "priority", "unlimited") diff --git a/nextcore/http/route.py b/nextcore/http/route.py index 51e68eb23..ee9dd62d5 100644 --- a/nextcore/http/route.py +++ b/nextcore/http/route.py @@ -46,7 +46,7 @@ class Route: guild_id: channel_id: webhook_id: - webhhook_token: + webhook_token: Major parameters which will be included in ``parameters`` and count towards the rate limit. parameters: The parameters of the route. These will be used to format the path. @@ -56,7 +56,7 @@ class Route: Attributes ---------- method: - The HTTP method of the route + The HTTP method of the route. route: The path of the route. This can include python formatting strings ({var_here}) from kwargs. path: diff --git a/pyproject.toml b/pyproject.toml index c00e421ba..aa99347e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,7 @@ pre-commit = "^2.18.1" sphinxext-opengraph = "^0.6.3" slotscheck = "^0.14.0" sphinx-inline-tabs = "^2022.1.2-beta.11" +livereload = "^2.6.3" [build-system] requires = ["poetry-core>=1.0.0"]