Skip to content

Commit 9bef2db

Browse files
committed
Set up docs
1 parent ce4b043 commit 9bef2db

22 files changed

+882
-78
lines changed

anisette/__init__.py

Lines changed: 0 additions & 75 deletions
This file was deleted.

docs/Makefile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Minimal makefile for Sphinx documentation
2+
#
3+
4+
# You can set these variables from the command line, and also
5+
# from the environment for the first two.
6+
SPHINXOPTS ?=
7+
SPHINXBUILD ?= sphinx-build
8+
SOURCEDIR = .
9+
BUILDDIR = _build
10+
11+
# Put it first so that "make" without argument is like "make help".
12+
help:
13+
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14+
15+
.PHONY: help Makefile
16+
17+
# Catch-all target: route all unknown targets to Sphinx using the new
18+
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19+
%: Makefile
20+
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

docs/conf.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Configuration file for the Sphinx documentation builder.
2+
#
3+
# For the full list of built-in configuration values, see the documentation:
4+
# https://www.sphinx-doc.org/en/master/usage/configuration.html
5+
6+
# -- Project information -----------------------------------------------------
7+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
8+
9+
import anisette
10+
11+
project = "Anisette.py"
12+
release = anisette.__version__
13+
14+
# -- General configuration ---------------------------------------------------
15+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
16+
17+
extensions = [
18+
"myst_parser",
19+
"sphinx.ext.duration",
20+
"sphinx.ext.autodoc",
21+
"sphinx.ext.inheritance_diagram",
22+
"autoapi.extension",
23+
]
24+
25+
templates_path = ["_templates"]
26+
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
27+
28+
# -- AutoAPI Options ---------------------------------------------------------
29+
autoapi_dirs = ["../src/anisette/"]
30+
autoapi_add_toctree_entry = False
31+
autoapi_options = [
32+
"members",
33+
"undoc-members",
34+
"show-inheritance",
35+
"show-module-summary",
36+
"special-members",
37+
"imported-members",
38+
]
39+
40+
# -- Options for HTML output -------------------------------------------------
41+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
42+
43+
html_theme = "furo"
44+
html_static_path = ["_static"]

docs/index.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Anisette.py
2+
3+
## Jump To
4+
5+
[//]: # (This is hidden to prevent it from showing on the home page)
6+
```{toctree}
7+
:hidden:
8+
Home <self>
9+
```
10+
11+
[//]: # (Show these with a maxdepth of 1)
12+
```{toctree}
13+
:maxdepth: 1
14+
API Reference <autoapi/anisette/index>
15+
genindex
16+
```

pyproject.toml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,15 @@ dependencies = [
1414

1515
[dependency-groups]
1616
dev = [
17+
"furo>=2024.8.6",
18+
"myst-parser>=3.0.1",
1719
"packaging>=24.2",
1820
"pre-commit>=4.1.0",
1921
"pyright>=1.1.392.post0",
2022
"pytest>=8.3.4",
2123
"ruff>=0.9.3",
24+
"sphinx>=7.4.7",
25+
"sphinx-autoapi>=3.6.0",
2226
"tomli>=2.2.1",
2327
]
2428

@@ -37,8 +41,6 @@ select = [
3741
"ALL",
3842
]
3943
ignore = [
40-
"ANN101", # annotations on `self`
41-
"ANN102", # annotations on `cls`
4244
"FIX002", # resolving TODOs
4345

4446
"ERA001", # commented out code
@@ -57,7 +59,16 @@ ignore = [
5759
"FIX",
5860
]
5961

62+
[tool.ruff.lint.per-file-ignores]
63+
"docs/*" = ["ALL"]
64+
"tests/*" = ["D", "T", "ANN", "INP", "SLF"]
65+
"scripts/*" = ["T201"]
66+
6067
[tool.pytest.ini_options]
6168
pythonpath = [
6269
"."
6370
]
71+
72+
[build-system]
73+
requires = ["setuptools", "setuptools-scm"]
74+
build-backend = "setuptools.build_meta"

scripts/supported_py_versions.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#!/usr/bin/env python3
22

3+
"""Find supported python versions for this package."""
4+
35
import json
46
from collections.abc import Generator
57
from itertools import count

src/anisette/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
"""Anisette provider in a Python package."""
2+
3+
from importlib.metadata import version
4+
5+
from ._device import AnisetteDeviceConfig
6+
from .anisette import Anisette
7+
8+
__version__ = version("anisette")
9+
10+
__all__ = ("Anisette", "AnisetteDeviceConfig")
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

src/anisette/anisette.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
"""Anisette provider in a Python package."""
2+
3+
from __future__ import annotations
4+
5+
import base64
6+
import logging
7+
from contextlib import ExitStack
8+
from ctypes import c_ulonglong
9+
from typing import TYPE_CHECKING, Any, BinaryIO
10+
11+
from typing_extensions import Self
12+
13+
from ._ani_provider import AnisetteProvider
14+
from ._fs import FSCollection
15+
from ._library import LibraryStore
16+
from ._util import open_file
17+
18+
if TYPE_CHECKING:
19+
from pathlib import Path
20+
21+
from ._device import AnisetteDeviceConfig
22+
23+
24+
class Anisette:
25+
"""
26+
The main Anisette provider class.
27+
28+
This is the main Anisette provider class, which provides the user-facing functionality of this package.
29+
Each instance of :class:`Anisette` represents a single Anisette session.
30+
31+
This class should not be instantiated directly through its __init__ method.
32+
Instead, you should use :meth:`Anisette.init` or :meth:`Anisette.load` depending on your use case.
33+
"""
34+
35+
def __init__(self, ani_provider: AnisetteProvider) -> None:
36+
"""
37+
Init.
38+
39+
:meta private:
40+
"""
41+
self._ani_provider = ani_provider
42+
43+
self._ds_id = c_ulonglong(-2).value
44+
45+
@classmethod
46+
def init(
47+
cls,
48+
apk_file: BinaryIO | str | Path,
49+
default_device_config: AnisetteDeviceConfig | None = None,
50+
) -> Self:
51+
"""
52+
Initialize a new Anisette session.
53+
54+
:param apk_file:
55+
:param default_device_config:
56+
:return:
57+
"""
58+
with open_file(apk_file, "rb") as apk:
59+
library_store = LibraryStore.from_apk(apk)
60+
61+
fs_collection = FSCollection(libs=library_store)
62+
ani_provider = AnisetteProvider(fs_collection, default_device_config)
63+
64+
return cls(ani_provider)
65+
66+
@classmethod
67+
def load(cls, *files: BinaryIO | str | Path, default_device_config: AnisetteDeviceConfig | None = None) -> Self:
68+
"""
69+
Load a previously-initialized Anisette session.
70+
71+
:param files: File objects or paths that together form the provider's virtual file system. These can be obtained
72+
using the :meth:`Anisette.save_provisioning`, :meth:`Anisette.save_libs`
73+
and/or :meth:`Anisette.save_all` methods.
74+
:type files: BinaryIO, str, Path
75+
:return: An instance of :class:`Anisette`.
76+
:rtype: :class:`Anisette`
77+
"""
78+
with ExitStack() as stack:
79+
file_objs = [stack.enter_context(open_file(f, "rb")) for f in files]
80+
ani_provider = AnisetteProvider.load(*file_objs, default_device_config=default_device_config)
81+
82+
return cls(ani_provider)
83+
84+
def save_provisioning(self, file: BinaryIO | str | Path) -> None:
85+
"""
86+
Save provisioning data of this Anisette session to a file.
87+
88+
The size of this file is usually in the order of kilobytes.
89+
90+
Saving provisioning data is required if you want to re-use this session at a later time.
91+
92+
A session may be reconstructed from saved data using the :meth:`Anisette.load` method.
93+
94+
The advantage of using this method over :meth:`Anisette.save_all` is that it results in less overall disk usage
95+
when saving many sessions, since library data can be saved separately and may be re-used across sessions.
96+
97+
:param file: The file or path to save provisioning data to.
98+
:type file: BinaryIO, str, Path
99+
"""
100+
with open_file(file, "wb+") as f:
101+
self._ani_provider.save(f, exclude=["libs"])
102+
103+
def save_libs(self, file: BinaryIO | str | Path) -> None:
104+
"""
105+
Save library data to a file.
106+
107+
The size of this file is usually in the order of megabytes.
108+
109+
Library data is session-agnostic and may be used in as many sessions as you wish.
110+
111+
The advantage of using this method over :meth:`Anisette.save_all` is that it results in less overall disk usage
112+
when saving many sessions, since library data can be saved separately and may be re-used across sessions.
113+
114+
:param file: The file or path to save library data to.
115+
:type file: BinaryIO, str, Path
116+
"""
117+
with open_file(file, "wb+") as f:
118+
self._ani_provider.save(f, include=["libs"])
119+
120+
def save_all(self, file: BinaryIO | str | Path) -> None:
121+
"""
122+
Save a complete copy of this Anisette session to a file.
123+
124+
The size of this file is usually in the order of megabytes.
125+
126+
Saving session data is required if you want to re-use this session at a later time.
127+
128+
A session may be reconstructed from saved data using the :meth:`Anisette.load` method.
129+
130+
The advantage of using this method over :meth:`Anisette.save_provisioning` and :meth:`Anisette.save_libs`
131+
is that it is easier to use, since all information to reconstruct the session is contained in a single file.
132+
133+
:param file: The file or path to save session data to.
134+
:type file: BinaryIO, str, Path
135+
"""
136+
with open_file(file, "wb+") as f:
137+
self._ani_provider.save(f)
138+
139+
def provision(self) -> None:
140+
"""
141+
Provision the virtual device, if it has not been provisioned yet.
142+
143+
In most cases it is not necessary to manually use this method, since :meth:`Anisette.get_data`
144+
will call it implicitly.
145+
"""
146+
if not self._ani_provider.adi.is_machine_provisioned(self._ds_id):
147+
logging.info("Provisioning...")
148+
self._ani_provider.provisioning_session.provision(self._ds_id)
149+
150+
def get_data(self) -> dict[str, Any]: # FIXME: make TypedDict
151+
"""
152+
Obtain Anisette headers for this session.
153+
154+
:return: Anisette headers that may be used for authentication purposes.
155+
"""
156+
self.provision()
157+
otp = self._ani_provider.adi.request_otp(self._ds_id)
158+
159+
# FIXME: return other fields as well
160+
return {
161+
"X-Apple-I-MD": base64.b64encode(bytes(otp.otp)).decode(),
162+
"X-Apple-I-MD-M": base64.b64encode(bytes(otp.machine_id)).decode(),
163+
}
File renamed without changes.

0 commit comments

Comments
 (0)