Skip to content

Commit

Permalink
docs improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
jmkd3v committed Mar 18, 2024
1 parent 25b06bd commit 05992cf
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 49 deletions.
44 changes: 32 additions & 12 deletions docs/tutorials/bases.md
Original file line number Diff line number Diff line change
@@ -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.

!!! 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)
```

35 changes: 17 additions & 18 deletions docs/tutorials/error-handling.md
Original file line number Diff line number Diff line change
@@ -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 |
|---------------------------------|--------------------|
Expand All @@ -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:
Expand All @@ -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 |
|------------------|-----------------------|
Expand All @@ -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:
Expand All @@ -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)
Expand Down
42 changes: 23 additions & 19 deletions docs/tutorials/get-started.md
Original file line number Diff line number Diff line change
@@ -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 "<input>", 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).

0 comments on commit 05992cf

Please sign in to comment.