From 05992cfe1a7d207906ddae60630f47a1a2e5c3ae Mon Sep 17 00:00:00 2001 From: jmkdev <21228906+jmkd3v@users.noreply.github.com> Date: Mon, 18 Mar 2024 11:58:12 -0400 Subject: [PATCH] docs improvements --- docs/tutorials/bases.md | 44 +++++++++++++++++++++++--------- docs/tutorials/error-handling.md | 35 ++++++++++++------------- docs/tutorials/get-started.md | 42 ++++++++++++++++-------------- 3 files changed, 72 insertions(+), 49 deletions(-) diff --git a/docs/tutorials/bases.md b/docs/tutorials/bases.md index ec676495..62c1e477 100644 --- a/docs/tutorials/bases.md +++ b/docs/tutorials/bases.md @@ -1,18 +1,38 @@ -In most cases, when sending requests to Roblox endpoints, only the ID of the item is required. -For example, `users.roblox.com/v1/users/{userId}/username-history` only requires the user ID. - -Let's say you already have a user ID and you *just* want their username history, and you don't need other information -like their name or display name. For example, this code sends 1 unnecessary request: -```python -user = await client.get_user(1) # we don't need this! +# Bases +Let's say you want to use ro.py to fetch the username history of a user, and you already know their user ID. You could do this: +```py +user = await client.get_user(968108160) async for username in user.username_history(): print(username) ``` -In this case, we already have their user ID. There's no reason to call `get_user` here. -Instead, we can call `get_base_user`: -```python -user = client.get_base_user(1) +This code works, but it has an issue: we're sending an unnecessary request to Roblox. + +To explain why, let's take a look at what ro.py is doing behind the scenes in this code. +- First, we call `await client.get_user(2067807455)`. ro.py asks Roblox for information about the user with the ID 2067807455 and returns it as a User object. +- Next, we iterate through `user.username_history`. ro.py asks Roblox for the username history for user 2067807455 and returns it to you. + +In this code, we call `await client.get_user()`, but we don't use any user information, like `user.name` or `user.description`. We don't need to make this request! + +ro.py lets you do this with the `client.get_base_TYPE` functions. We'll can the `client.get_base_user()` function to improve the code: +```py +user = client.get_base_user(2067807455) # no await! async for username in user.username_history(): print(username) ``` -This code is functionally identical but won't send any unnecessary requests. \ No newline at end of file + +!!! hint + In ro.py, all functions you `await` or paginators you iterate through with `async for` make at least one request internally. Notice how you need to `await` the `get_user` function, but not the `get_base_user` function! + +This works for other Roblox types as well, like groups and assets. For example, this code kicks a user from a group with only 1 request: +```py +group = client.get_base_group(9695397) +user = client.get_base_user(2067807455) +await group.kick_user(user) +``` + +There's another technique we can use to optimize this example further. For functions that accept only one type, like `kick_user` which always accepts a user, ro.py accepts bare user IDs: +```py +group = client.get_base_group(9695397) +await group.kick_user(2067807455) +``` + diff --git a/docs/tutorials/error-handling.md b/docs/tutorials/error-handling.md index becef22f..d220573c 100644 --- a/docs/tutorials/error-handling.md +++ b/docs/tutorials/error-handling.md @@ -1,13 +1,14 @@ # Error handling -You can import ro.py exceptions from the `roblox.utilities.exceptions` or just from the `roblox` library: -```python +You can import ro.py exceptions from the `roblox.utilities.exceptions` module or from the main `roblox` module: + +```py from roblox.utilities.exceptions import InternalServerError +# or from roblox import InternalServerError ``` ## Client errors -All of the `Client.get_SINGULAR()` methods, like `get_user()` and `get_group()`, raise exceptions when you pass an -invalid input. +All of the `Client.get_TYPE()` methods, like `get_user()` and `get_group()`, raise their own exceptions. | Method | Exception | |---------------------------------|--------------------| @@ -20,34 +21,33 @@ invalid input. | `client.get_user()` | `UserNotFound` | | `client.get_user_by_username()` | `UserNotFound` | -Here is an example of catching a `UserNotFound` error: +Here is an example of catching one of these exceptions: ```python -username = "InvalidUsername!!!" try: - user = await client.get_user_by_username(username) - print("ID:", user.id) + user = await client.get_user_by_username("InvalidUsername!!!") except UserNotFound: - print("Invalid username.") + print("Invalid username!") ``` -All of these exceptions are subclasses of `ItemNotFound`. +All of these exceptions are subclasses of `ItemNotFound`, which you can use as a catch-all. ## HTTP errors -ro.py also raises HTTP errors when Roblox says something is wrong. -For example, if we try to shout on a group that we don't have permissions on, Roblox stops us and returns a +When Roblox returns an error, ro.py raises an HTTP exception. + +For example, if we try to post a group shout to a group that we don't the necessary permissions in, Roblox stops us and returns a `401 Unauthorized` error: ```python group = await client.get_group(1) await group.update_shout("Shout!") ``` -When running this code, you will see an error message like this: +This code will raise an error like this: ```pytb roblox.utilities.exceptions.Unauthorized: 401 Unauthorized: https://groups.roblox.com/v1/groups/1/status. Errors: 0: Authorization has been denied for this request. ``` -Here is an example of catching a `Unauthorized` error: +You can catch this error as follows:: ```python group = await client.get_group(1) try: @@ -57,7 +57,7 @@ except Unauthorized: print("Not allowed to shout.") ``` -These are the different types of exceptions raised depending on the HTTP error Roblox returns: +These are the different types of exceptions raised depending on the HTTP error code Roblox returns: | HTTP status code | Exception | |------------------|-----------------------| @@ -67,8 +67,7 @@ These are the different types of exceptions raised depending on the HTTP error R | 429 | `TooManyRequests` | | 500 | `InternalServerError` | -All of these exceptions are subclasses of the `HTTPException` error. -For other unrecognized error codes, ro.py will fallback to the default `HTTPException`. +All of these exceptions are subclasses of the `HTTPException` error, which you can use as a catch-all. For other unrecognized error codes, ro.py will fallback to the default `HTTPException`. ### Getting more error information For all HTTP exceptions, ro.py exposes a `response` attribute so you can get the response information: @@ -81,7 +80,7 @@ except Unauthorized as exception: print("Not allowed to shout.") print("URL:", exception.response.url) ``` -Roblox also returns extra error data, which is what you see in our error messages. +Roblox also returns extra error data, which is what you see in the default error message. We can access this with the `.errors` attribute, which is a list of [`ResponseError`][roblox.utilities.exceptions.ResponseError]: ```python group = await client.get_group(1) diff --git a/docs/tutorials/get-started.md b/docs/tutorials/get-started.md index f53a81b9..4a2ea27a 100644 --- a/docs/tutorials/get-started.md +++ b/docs/tutorials/get-started.md @@ -1,56 +1,60 @@ # Get started -At the beginning of every ro.py application is the client. The client represents a user's session on Roblox. +At the beginning of every ro.py application is the client. The client represents a Roblox session, and it's your gateway to everything in ro.py. + To initialize a client, import it from the `roblox` module: ```python title="main.py" from roblox import Client client = Client() ``` -Great, we've got a client! But how can we use it? -We start by calling `await client.get_OBJECT()` where `OBJECT` is a Roblox datatype, like a User, Group or Universe. -But wait - if you tried to run code like this: +We can use the client to get information from Roblox by calling `await client.get_TYPE()`, where `TYPE` is a Roblox datatype, like a user or group. + +There's a problem, though: if we run the following code... ```python title="main.py" from roblox import Client client = Client() await client.get_user(1) ``` - -You would get an error like this: +...it'll raise an error like this: ```pytb - File "", line 1 + File "...", line 1 SyntaxError: 'await' outside function ``` -This may seem confusing - but this is [intended design.](https://lukasa.co.uk/2016/07/The_Function_Colour_Myth/) -To fix this, we need to wrap our code in an asynchronous function, and then run it with `get_event_loop().run_until_complete`, like so: +This is because ro.py, like many Python libraries, is based on [asyncio](https://docs.python.org/3/library/asyncio.html), a builtin Python library that allows for concurrent code. In the case of ro.py, this means your app can do something, like process Discord bot commands, while ro.py waits for Roblox to respond, saving tons of time and preventing one slow function from slowing down the whole program. Neat! + +This means we need to wrap our code in an **asynchronous function** and then run it with `asyncio.run`, like so: + ```python title="main.py" import asyncio from roblox import Client client = Client() async def main(): - await client.get_user(1) + await client.get_user(1) -asyncio.get_event_loop().run_until_complete(main()) +asyncio.run(main()) ``` -This is the basic structure of every ro.py application. -Great, our code works - but it's not doing anything yet. Let's print out some information about this user by replacing -the code in `main()` with this: -```python -user = await client.get_user(1) +This is the basic structure of every simple ro.py application. More complicated apps might not work like this - for example, in a Discord bot, another library might already be handling the asyncio part for you - but for simple scripts, this is what you'll be doing. + +Now the error is gone, but our code doesn't do anything yet. Let's try printing out some information about this user. Add these lines to the end of your main function: + +```python title="main.py" print("Name:", user.name) print("Display Name:", user.display_name) print("Description:", user.description) ``` Great! We now have a program that prints out a user's name, display name, and description. This same basic concept works -for other kinds of objects on Roblox, like groups: +for other kinds of objects on Roblox, like groups. Try replacing the code in your main function with this: ```python group = await client.get_group(1) print("Name:", group.name) print("Description:", group.description) ``` -But what if we want to send requests as if we are an actual, logged-in user browsing the site? For example, what if I wanted to change the group's shout? -Because only users with permission to change the group shout can actually change it, we need to tell Roblox that we can change that shout by "authenticating". + +To see a list of everything you can do with the client, see [`Client`][roblox.client.Client] in the Code Reference. + +So far, we've been using ro.py **unauthenticated**. Basically, we aren't logged in to Roblox, which means we can't perform any actions, like updating our description, or access any sensitive information, like which game our friend is playing right now. Your next mission, if you choose to accept it, is [authenticating your client](./authentication.md). \ No newline at end of file