Metadata-Version: 2.4
Name: nifty_anilist
Version: 0.1.2
Summary: Utilities for the Anilist GraphQL API.
Author-email: Alexey Polishchuk <alexpolishchukmail@gmail.com>
License: MIT
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: asyncio>=4.0.0
Requires-Dist: colorama>=0.4.6
Requires-Dist: httpx>=0.28.1
Requires-Dist: keyring>=25.6.0
Requires-Dist: pydantic>=2.11.9
Requires-Dist: pydantic-settings>=2.11.0
Requires-Dist: pyjwt>=2.10.1
Requires-Dist: python-dotenv>=1.1.1
Requires-Dist: selenium>=4.35.0
Requires-Dist: structlog>=25.4.0
Requires-Dist: webdriver-manager>=4.0.2
Dynamic: license-file

# Nifty Anilist Tools
 
## Overview

This is a simple utility library for interfacing with the [Anilist GraphQL API](https://docs.anilist.co/).
It provides useful tools like an Anilist client to make validated requests with query builder objects and handles authentication for you.

### Using the Library

This library is available on [PyPi](https://pypi.org/project/nifty-anilist/).

To use this library, you will need to have the variables shown in [.env.example](./.env.example) in environment variables or your local `.env` file.
Any blank variables need to be present and will throw an error if are missing.

## Features

### GraphQL Requests
The Anilist API is GraphQL-based and provides a [public schema](https://studio.apollographql.com/sandbox/schema/reference). This library uses an [Ariadne](https://ariadnegraphql.org/client/intro) code-generated GraphQL client to make GraphQL requests to Anilist. 

To make requests to Anilist, create an `AnilistClient` and use the `anilist_request()` function. Example:
```py
client = AnilistClient()
async with client:
    data = await client.anilist_request(my_query)

# OR

async with AnilistClient() as client:
    data = await client.anilist_request(my_query)
```

One of the benefits of Ariadne is that queries (and manipulations, etc.) are made with objects instead of raw GraphQL strings, so you get a sort of schema pre-validation for your requests. These objects are also generated by Ariadne and are available from the `nifty_anilist.client.custom_XXX` files. Feel free to reference the [Anilist schema](https://studio.apollographql.com/sandbox/schema/reference) and use the [playground](https://studio.apollographql.com/sandbox/explorer?endpoint=https://graphql.anilist.co) before writing these queries in code.

The following is a simple example for building such a query for getting a user's avatar:
```py
from nifty_anilist.client.custom_queries import Query
from nifty_anilist.client.custom_fields import UserFields, UserAvatarFields

...

query = Query.user(name=username).fields(
    UserFields.avatar().fields(
        UserAvatarFields.large
    )
)

response = await client.anilist_request(query)

avatar_url = response["User"]["avatar"]["large"]
print(f"Avatar URL: {avatar_url}")
```

The client takes an optional user ID to make requests for. If not provided, the client will try to use global user. You can also turn off authentication by setting `use_auth` to `False`.
These settings should not be changed for an existing client; you should make a new one if you need to make requests under a different user's credentials.

### Anilist Auth

#### Token Storage
This library will store Anilist auth token(s) locally in one of two ways (customizable with the `TOKEN_SAVING_METHOD` environment variable):
1. Using your system's keyring (kept "permanently"): `KEYRING`
2. In-memory (lost when you restart your program): `IN_MEMORY`

#### Using Auth
In order to get an auth token for the first time, you can use the `sign_in_if_no_global()` function from [auth.py](./nifty_anilist/auth.py). After the first time, these details will be stored locally and added to your Anilist requests automatically.

**Note:** The sign-in function currently opens an instance of Google Chrome to the Anilist login page, from which your auth code will be automatically extracted. There will be more ways to do sign-in later.

There are two ways that auth headers can be added to your requests:
1. Using a global user ID stored in the `.env` file: The ID is stored as `ANILIST_CURRENT_USER`. This is the user ID that will be used to retrieve the token from the storage method(s) above. When making requests with the client's `anilist_request()`, you can ignore the optional `user_id` parameter to use this approach. Example:
```py
async def do_something():
    query = # some query
    async with AnilistClient() as client:
        return await client.anilist_request(query)

if __name__ == "__main__":
    sign_in_if_no_global()
    data = asyncio.run(do_something())
    print(data)
```
2. Manually passing in a user ID to the `anilist_request()` with the `user_id` parameter will try to get that user's token from local storage and use it instead of the global one. Example:
```py
async def do_something(user_id: str):
    query = # some query
    async with AnilistClient(user_id=user_id) as client:
        return await client.anilist_request(query)

if __name__ == "__main__":
    my_user_id = "12345"
    data = asyncio.run(do_something(user_id=my_user_id))
    print(data)
```

You can also choose to not use auth on requests with the `use_auth` parameter (default is `True`):
```py
async def do_something():
    query = # some query
    async with AnilistClient(use_auth=False) as client:
        return await client.anilist_request(query)
```
