Skip to content

Commit

Permalink
[MISC] Overhaul docs
Browse files Browse the repository at this point in the history
  • Loading branch information
Aedial committed Jun 2, 2023
1 parent b875efe commit e14ecb2
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 4 deletions.
2 changes: 2 additions & 0 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@
"confval": "modal", # for custom object
"mod": "modal", # for Python Sphinx Domain
"class": "modal", # for Python Sphinx Domain
"attr": "tooltip", # for Python Sphinx Domain
"meth": "tooltip", # for Python Sphinx Domain
}


Expand Down
7 changes: 7 additions & 0 deletions docs/source/novelai_api/novelai_api.high_level.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
novelai\_api.high\_level
========================

.. automodule:: novelai_api._high_level
:members:
:undoc-members:
:show-inheritance:
7 changes: 7 additions & 0 deletions docs/source/novelai_api/novelai_api.low_level.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
novelai\_api.low\_level
=======================

.. automodule:: novelai_api._low_level
:members:
:undoc-members:
:show-inheritance:
2 changes: 2 additions & 0 deletions docs/source/novelai_api/novelai_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ novelai-api package
.. toctree::
:maxdepth: 2

novelai_api.low_level
novelai_api.high_level
novelai_api.BanList
novelai_api.BiasGroup
novelai_api.GlobalSettings
Expand Down
18 changes: 17 additions & 1 deletion example/boilerplate.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,23 @@

class API:
"""
Boilerplate for the redundant parts
Boilerplate for the redundant parts.
Using the object as a context manager will automatically login using the environment variables
``NAI_USERNAME`` and ``NAI_PASSWORD``.
Usage:
.. code-block:: python
async with API() as api:
api = api.api
encryption_key = api.encryption_key
logger = api.logger
... # Do stuff
A custom base address can be passed to the constructor to replace the default
(:attr:`BASE_ADDRESS <novelai_api.NovelAI_API.NovelAIAPI.BASE_ADDRESS>`)
"""

_username: str
Expand Down
25 changes: 25 additions & 0 deletions novelai_api/NovelAI_API.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,48 @@

class NovelAIAPI:
# Constants

#: The base address for the API
BASE_ADDRESS: str = "https://api.novelai.net"
LIB_ROOT: str = dirname(abspath(__file__))

# Variables

#: The logger for the API
logger: Logger
#: The client session for the API (None if synchronous)
session: Optional[ClientSession]

#: The timeout for a request (in seconds)
timeout: ClientTimeout
#: The headers for a request
headers: CIMultiDict
#: The cookies for a request
cookies: SimpleCookie
#: The proxy for a request (None if no proxy)
proxy: Optional[StrOrURL] = None
#: The proxy authentication for a request (None if no proxy)
proxy_auth: Optional[BasicAuth] = None

# API parts

#: The low-level API (thin wrapper)
low_level: LowLevel
#: The high-level API (abstraction on top of low-level)
high_level: HighLevel

def __init__(self, session: Optional[ClientSession] = None, logger: Optional[Logger] = None):
"""
Create a new NovelAIAPI object, which can be used to interact with the API.
Use the low_level and high_level attributes for this purpose
Use attach_session and detach_session to switch between synchronous and asynchronous requests
by attaching a ClientSession
:param session: The ClientSession to use for requests (None for synchronous)
:param logger: The logger to use for the API (None for creating an empty default logger)
"""

# variable passing
if session is not None and not isinstance(session, ClientSession):
raise ValueError(f"Expected None or type 'ClientSession' for session, but got type '{type(session)}'")
Expand Down
15 changes: 14 additions & 1 deletion novelai_api/_high_level.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@


class HighLevel:
"""
High level API for NovelAI. This class is not meant to be used directly,
but rather through :attr:`NovelAIAPI.high_level <novelai_api.NovelAI_API.NovelAIAPI.high_level>`.
The most relevant methods are:
* :meth:`login <novelai_api._high_level.HighLevel.login>`
* :meth:`generate <novelai_api._high_level.HighLevel.generate>`
* :meth:`generate_image <novelai_api._high_level.HighLevel.generate_image>`
"""

_parent: "NovelAIAPI" # noqa: F821

def __init__(self, parent: "NovelAIAPI"): # noqa: F821
Expand Down Expand Up @@ -313,7 +324,9 @@ async def generate(
**kwargs,
) -> Dict[str, Any]:
"""
Generate text. The text is returned at once, when generation is finished.
Generate text. The b64-encoded text is returned at once, when generation is finished.
To decode the text, the :meth:`novelai_api.utils.b64_to_tokens`
and :meth:`novelai_api.Tokenizer.Tokenizer.decode` methods should be used.
:param prompt: Context to give to the AI (raw text or list of tokens)
:param model: Model to use for the AI
Expand Down
14 changes: 14 additions & 0 deletions novelai_api/_low_level.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,23 @@ def print_with_parameters(args: Dict[str, Any]):

# === API === #
class LowLevel:
"""
Low level API for NovelAI. This class is not meant to be used directly,
but rather through :attr:`NovelAIAPI.low_level <novelai_api.NovelAI_API.NovelAIAPI.low_level>`.
The most relevant methods are:
* :meth:`login <novelai_api._low_level.LowLevel.login>`
* :meth:`generate <novelai_api._low_level.LowLevel.generate>`
* :meth:`generate_image <novelai_api._low_level.LowLevel.generate_image>`
* :meth:`get_keystore <novelai_api._low_level.LowLevel.get_keystore>`
* :meth:`download_objects <novelai_api._low_level.LowLevel.download_objects>`
"""

_parent: "NovelAIAPI" # noqa: F821
_is_async: bool

#: Enable or disable schema validation for responses. Default is ``True``.
is_schema_validation_enabled: bool

def __init__(self, parent: "NovelAIAPI"): # noqa: F821
Expand Down
32 changes: 30 additions & 2 deletions novelai_api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ def decrypt_user_data(items: Union[List[Dict[str, Any]], Dict[str, Any]], keysto

meta = item["meta"]
if meta not in keystore:
raise NovelAIError(-1, f"Meta of item #{i} ({meta}) missing from keystore")
raise NovelAIError("<UNKNOWN>", -1, f"Meta of item #{i} ({meta}) missing from keystore")

key = keystore[meta]

Expand Down Expand Up @@ -263,7 +263,7 @@ def encrypt_user_data(items: Union[List[Dict[str, Any]], Dict[str, Any]], keysto

meta = item["meta"]
if meta not in keystore:
raise NovelAIError(-1, f"Meta of item #{i} ({meta}) missing from keystore")
raise NovelAIError("<UNKNOWN>", -1, f"Meta of item #{i} ({meta}) missing from keystore")

key = keystore[meta]

Expand All @@ -281,6 +281,10 @@ def link_content_to_story(
stories: Dict[str, Union[str, int, Dict[str, Any]]],
story_contents: Union[List[Dict[str, Any]], Dict[str, Any]],
):
"""
Link the story content to each story in :ref: stories
"""

if not isinstance(stories, (list, tuple)):
stories = [stories]

Expand All @@ -298,6 +302,10 @@ def link_content_to_story(


def unlink_content_from_story(stories: Dict[str, Union[str, int, Dict[str, Any]]]):
"""
Remove the story content from each story in :ref: stories
"""

if not isinstance(stories, (list, tuple)):
stories = [stories]

Expand All @@ -307,20 +315,36 @@ def unlink_content_from_story(stories: Dict[str, Union[str, int, Dict[str, Any]]


def get_decrypted_user_data(items: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
Filter out items that have not been decrypted
"""

return [item for item in items if item.get("decrypted", False)]


def tokens_to_b64(tokens: Iterable[int]) -> str:
"""
Encode a list of tokens into a base64 string that can be sent to the API
"""

return b64encode(b"".join(t.to_bytes(2, "little") for t in tokens)).decode()


def b64_to_tokens(b64: str) -> List[int]:
"""
Decode a base64 string returned by the API into a list of tokens
"""

b = b64decode(b64)

return [int.from_bytes(b[i : i + 2], "little") for i in range(0, len(b), 2)]


def extract_preset_data(presets: List[Dict[str, Any]]) -> Dict[str, Preset]:
"""
Transform a list of preset data into a dict of Preset objects indexed by their id
"""

preset_list = {}
for preset_data in presets:
decompress_user_data(preset_data)
Expand All @@ -330,6 +354,10 @@ def extract_preset_data(presets: List[Dict[str, Any]]) -> Dict[str, Preset]:


def tokenize_if_not(model: Model, o: Union[str, List[int]]) -> List[int]:
"""
Tokenize the string if it is not already tokenized
"""

if isinstance(o, list):
return o

Expand Down

0 comments on commit e14ecb2

Please sign in to comment.